Skip to content

Declaration Lifecycle

Back to Regnify Product Docs

Previous Next


Overview

Declarations are the central entity in Regnify, representing a representative's submission to the Monetary Authority of Singapore (MAS). Every declaration follows a status state machine that governs its progression from creation through internal approval to MAS submission and final disposition.

The declarations table stores all declarations, with the formType column distinguishing between declaration types. The DeclarationStatus enum in Prisma defines 12 statuses. Valid transitions between statuses are enforced by VALID_TRANSITIONS in declaration-status.ts.

Declaration Types

formType Description Workflow Behaviour
DECLARATION_FORM_3A Appointed Representative application (Form 3A) Auto-creates multi-step workflow instance on submit (from org's template)
DECLARATION_ATTESTATION_QUARTERLY Quarterly compliance attestation Does NOT auto-create workflow instance. Single-step approval via approveAttestation.
DECLARATION_INCIDENT_DISCLOSURE Incident disclosure filing Auto-creates single-step workflow instance (Review by Compliance)

The default formType is "DECLARATION_FORM_3A" if not specified at creation.

Canonical Status Flow

Draft -> Pending Review -> Approved -> Good -> MAS_PENDING_REVIEW -> MAS_APPROVED
                |               |                                        |
             Rejected        Rejected                               MAS_REJECTED
                |               |                                        |
           (re-editable)   (re-editable)                           (re-editable)

PENDING_REVIEW can also transition to:
  - REVISION_REQUESTED (via sendBackDeclaration)
  - which can return to PENDING_REVIEW (via resubmitDeclaration)

The full DeclarationStatus enum (12 values):

Enum Value Display Label Meaning
DRAFT Draft Declaration being edited, not yet submitted
PENDING_REVIEW Pending Review Submitted, awaiting approval chain
APPROVED Approved All internal approvals complete
REJECTED Rejected Rejected by an approver; editable by rep
REVISION_REQUESTED Revision Requested Sent back for revision; prior approvals RETAINED
GOOD Good COMP confirmed all checks complete, ready for MAS
FORM_COMPLETED Form Completed Legacy intermediate status
CLEARED_FOR_MAS Cleared for MAS Submission Legacy intermediate status
SUBMITTED_TO_MAS Submitted to MAS Legacy intermediate status
MAS_PENDING_REVIEW MAS Pending Review Submitted to MAS via CoRe, awaiting MAS response
MAS_APPROVED MAS Approved MAS has approved the representative
MAS_REJECTED MAS Rejected MAS has rejected; editable by rep

Note: FORM_COMPLETED, CLEARED_FOR_MAS, and SUBMITTED_TO_MAS are legacy intermediate statuses from the NYP reference backend. The canonical production flow skips these and goes directly Good -> MAS_PENDING_REVIEW via sendToMas().

Status Transitions and Mutations

Draft -> Pending Review: submitDeclaration

Roles: FI_USER

Pre-conditions: - Declaration status must be Draft or Revision Requested (both in SUBMITTABLE_STATUSES) - At least 1 supporting document attached (Form 3A only; enforced in service, not resolver) - Form data passes validation (validateForm3aForSubmission for Form 3A, validateIncidentFormData for incidents) - Attestation declarations use submitAttestationResponse instead (see below)

Post-conditions: - Status set to Pending Review, statusLabel to PENDING_REVIEW - submittedAt set to current timestamp - Workflow instance auto-created from org's template for DECLARATION_FORM_3A and DECLARATION_INCIDENT_DISCLOSURE - Notifications sent to approvers on pending steps - PubSub event declarationStatusChanged emitted

Pending Review -> Approved: approveDeclaration

Roles: HR_ADMIN, COMP, ORG_ADMIN, SYS_ADMIN

Pre-conditions: Status must be PENDING_REVIEW

Post-conditions: - Status set to Approved, statusLabel to APPROVED - approvedAt set to current timestamp - Notification sent to rep

Note on workflow-step approval: In the multi-step workflow chain, approveDeclaration is the declaration-level mutation. When the LAST workflow step is approved via approveWorkflowStep, the declaration is auto-transitioned to Approved by WorkflowService.approveStep(). Individual step approvals use approveWorkflowStep, not approveDeclaration.

Pending Review -> Rejected: rejectDeclaration

Roles: HR_ADMIN, COMP, ORG_ADMIN, SYS_ADMIN

Pre-conditions: Status must be PENDING_REVIEW

Post-conditions: - Status set to Rejected, statusLabel to REJECTED - All prior approvals are LOST -- rep must restart from Draft - Notification sent to rep

Pending Review -> Revision Requested: sendBackDeclaration

Roles: HR_ADMIN, COMP, ORG_ADMIN, SYS_ADMIN

Pre-conditions: Status must be PENDING_REVIEW

Post-conditions: - Status set to Revision Requested, statusLabel to REVISION_REQUESTED - Prior workflow-step approvals are RETAINED (unlike reject) - Rep revises and calls resubmitDeclaration which resets workflow steps to pending and restarts from step 0 - Notification sent to rep with comment

Key distinction from reject: sendBack retains the approval chain context (approver identities, step history). reject is a full restart where all approval work is discarded.

Revision Requested -> Pending Review: resubmitDeclaration

Roles: FI_USER

Pre-conditions: Status must be Draft or Revision Requested (both in SUBMITTABLE_STATUSES). The service checks: "Declaration can only be resubmitted when status is Revision Requested or Draft".

Post-conditions: - Form data re-validated - Document minimum re-checked (Form 3A only) - All workflow steps reset to pending via resetForResubmission() -- clears comment, actedAt, sets all step statuses back to pending - Instance currentStepIndex reset to 0, instance status set to pending - Declaration version incremented by 1 - Status set to Pending Review - submittedAt set to current timestamp

Approved -> Good: markAsGood

Roles: COMP, SYS_ADMIN

Pre-conditions: Status must be APPROVED

Post-conditions: Status set to Good, statusLabel to GOOD

This is the COMP officer's final internal sign-off confirming all internal checks are complete, and the declaration is ready to be sent to MAS.

Good -> MAS_PENDING_REVIEW: sendToMas

Roles: COMP, SYS_ADMIN

Pre-conditions: Status must be GOOD

Post-conditions: - Status set to MAS_PENDING_REVIEW, statusLabel to MAS_PENDING_REVIEW - submittedAt set to current timestamp (marks MAS submission time)

MAS_PENDING_REVIEW -> MAS_APPROVED: approveMas

Roles: SYS_ADMIN

Pre-conditions: Status must be MAS_PENDING_REVIEW

Post-conditions: - Status set to MAS_APPROVED, statusLabel to MAS_APPROVED - approvedAt set to current timestamp

MAS_PENDING_REVIEW -> MAS_REJECTED: rejectMas

Roles: SYS_ADMIN

Pre-conditions: Status must be MAS_PENDING_REVIEW

Post-conditions: Status set to MAS_REJECTED, statusLabel to MAS_REJECTED

After MAS rejection: The declaration can be re-edited (MAS_REJECTED -> Draft transition is valid per VALID_TRANSITIONS).

Rejected -> Draft

Automatic transition path when the rep re-opens a rejected declaration for editing. Both REJECTED and MAS_REJECTED can transition to DRAFT per VALID_TRANSITIONS.

Attestation-Specific Transitions

Attestation declarations (DECLARATION_ATTESTATION_QUARTERLY) use a different set of mutations, all in AttestationService:

Mutation From To Roles
submitAttestationResponse Draft or Revision Requested Pending Review FI_USER, REP_USER, COMP, HR_ADMIN, ORG_ADMIN, SYS_ADMIN
approveAttestation Pending Review Approved COMP, HR_ADMIN, ORG_ADMIN, SYS_ADMIN

submitAttestationResponse merges the rep's attestation answers (cpdConfirmed, cmfasConfirmed, noAdverseChange, etc.) into formData.responses. It does NOT auto-create a workflow instance. The approveAttestation mutation is a thin wrapper that validates the declaration is an attestation, then delegates to declarationService.approveDeclaration().

Soft Delete

Draft declarations can be soft-deleted (deletedAt set to current time):

Roles: FI_USER, SYS_ADMIN

Constraints: - Only Draft-status declarations can be deleted - FI_USER can only delete their own drafts; SYS_ADMIN can delete any draft - Non-Draft declarations cannot be deleted (only rejected/sent back)

Blocking Rule

A representative cannot submit a new Form 3A if another non-Draft declaration (status != "Draft") already exists for them with formType = "DECLARATION_FORM_3A". This prevents duplicate applications in the approval pipeline. Attestation and incident declarations do not participate in this blocking check -- the rule is specific to Form 3A.

Implementation note: This blocking rule is enforced on the frontend side (the Form 3A single-page form). The backend submitDeclaration mutation does not currently validate for existing non-Draft declarations server-side.

Auto-Save

The Form 3A single-page form auto-saves form data as a Draft every 30 seconds. This is a frontend-side feature implemented in Form3AContainer.tsx, not a backend mutation. The auto-save calls updateDeclaration with the current form state.

Document Minimum

At least 1 supporting document is required before a Form 3A declaration can be submitted. This is enforced in DeclarationService.submitDeclaration() and DeclarationService.resubmitDeclaration():

if (
  (!decl.formType || decl.formType === "DECLARATION_FORM_3A") &&
  !decl.documents?.length
) {
  throw new BadRequestException("At least 1 supporting document is required");
}

This check applies only to Form 3A (and the default null formType, treated as Form 3A). Attestation and incident declarations are exempt.

Supporting Document Categories

The DocumentCategory enum defines 11 categories:

Enum Value Typical Content
RESUME CV / resume
CPF CPF contribution statements
EDUCATION Degree certificates, transcripts
LICENSE Professional licenses
IDENTIFICATION NRIC, passport copies
PROFESSIONAL_QUALIFICATIONS CMFAS certificates, CPD course certificates
EMPLOYMENT_HISTORY Employment letters, contracts
FIT_AND_PROPER_CHECK Fit & Proper assessment records
CMFAS CMFAS exam result slips
CREDIT_BUREAU Credit bureau reports
CPF_CONTRIBUTION CPF contribution history

Overdue Declaration Rule

A declaration is considered "overdue" when it remains in PENDING_REVIEW at any single approval step for more than 5 business days. This is a compliance monitoring metric; the system does not auto-reject or auto-escalate overdue declarations, but the overdue status is surfaced in dashboards and the chatbot.

MAS Processing Timeline

  • Standard MAS processing: 14 business days
  • MAS queries (requests for additional information): typically responded to within 7 business days
  • These are regulatory expectations, not hardcoded system timeouts

Roles Summary

Role Can Create Can Submit (Form 3A) Can Submit (Attestation) Can Approve Can Reject Can sendBack Can markAsGood Can sendToMas Can approveMas/rejectMas
FI_USER Yes Yes (own) Yes (own) No No No No No No
REP_USER No No Yes (own) No No No No No No
HR_ADMIN Yes No Yes Yes Yes Yes No No No
COMP Yes No Yes Yes Yes Yes Yes Yes No
ORG_ADMIN Yes No Yes Yes Yes Yes No No No
SYS_ADMIN Yes No Yes Yes Yes Yes Yes Yes Yes (approveMas, rejectMas)

Note: submitDeclaration (Form 3A) is restricted to @Roles("FI_USER") only. submitAttestationResponse (attestation cycles) allows FI_USER, REP_USER, COMP, HR_ADMIN, ORG_ADMIN, and SYS_ADMIN. These are separate mutations with different role guards.

Key Fields on the Declarations Table

Column Type Description
id Int (auto-increment) Primary key
repId String Representative identifier
userId String Keycloak sub claim of the owning user (String per Loop 84 migration)
repName String? Cached representative name
formData Json? Full form data payload (varies by formType)
formType String (default: "DECLARATION_FORM_3A") Declaration type discriminator
repProfileId Int? FK to rep_profiles (null for users without a rep profile)
status String (default: "Draft") Current status display string
statusLabel String? Enum key (e.g., "PENDING_REVIEW")
version Int (default: 1) Incremented on each resubmission
submittedAt DateTime? When the declaration was submitted to Pending Review or MAS
approvedAt DateTime? When the declaration was approved (internal or MAS)
deletedAt DateTime? Soft-delete timestamp (null = active)

Previous Next

Back to Regnify Product Docs