P-04 · Validation & completeness
SOP:
Payroll_Processing.md§6 / Step 3.0 (S4 → S7)Actors: APOE (system). Triggered automatically as the first step ofGenerate Exportand on every file finalize while a cycle is inACTION_REQUIRED. Can also be re-run manually viaPOST /ops/cycles/:id/validate. Pre-state: Cycle inSUBMITTED,ACTION_REQUIRED, orREVISION_REQUESTED. Post-state: Cycle transitions toREADY_FOR_INTERNAL_REVIEW(no blocking failures) orACTION_REQUIRED(one or more blocking failures).
0. Prerequisites
1. Steps
The validation pipeline lives in packages/domain/src/rules/validation-rules.ts and is invoked by the validation-run worker handler.
1.1 Trigger via Generate Export (recommended)
This is the canonical path. See P-07. Validation is the first step inside the Generate Export click; a blocking failure aborts the export and flips the cycle to ACTION_REQUIRED.
1.2 Manual API trigger
POST /ops/cycles/<cycleId>/validate
Authorization: Bearer <staff-jwt>The route enqueues a validation-run pg-boss job and returns 202. The handler:
- Loads the cycle, latest submission, and all files.
- Builds a
ValidationContext. - Evaluates the 5 core rules:
ATTENDANCE_REQUIRED— BLOCKING. At least one ATTENDANCE-kind file or an explicit "no attendance changes" declaration field must be present.DOCUMENT_REQUIREMENTS_MET— BLOCKING. Every activeDocumentRequirementRulefor the change types in the submission must be satisfied (e.g. JOINER → OFFER_LETTER + IDENTITY_NRIC + BANK_DETAILS).FOREIGN_LEAVER_IR21— BLOCKING. AnyLEAVERitem where the employee is foreign (FIN holder) requires an IR21-kind file or an authorized override.NS_DOCUMENTATION— BLOCKING. NS claim items require accompanying NS documentation.DUPLICATE_RECEIPTS— WARNING. Duplicate SHA-256 hashes across receipts in the cycle. Override allowed byPAYROLL_LEAD.
- Persists a
ValidationRunrow + per-ruleValidationResultrows. - For each blocking failure, ensures a corresponding
WorkflowIssueexists (idempotent — repeated runs do not create duplicate issues). - Determines the next status:
- All blocking pass →
READY_FOR_INTERNAL_REVIEW(orREADY_FOR_EXPORTif internal review already cleared). - Any blocking fails →
ACTION_REQUIRED.
- All blocking pass →
- Computes a completeness percentage
(passed_rules / total_rules) * 100.
1.3 Auto-revalidate after upload (singleton)
When the cycle is ACTION_REQUIRED, every POST /portal/files/<id>/finalize enqueues a validation-run job with singletonKey = "validation-run-<cycleId>-action-required". pg-boss collapses a burst of uploads into a single job, so a client uploading three files in quick succession triggers exactly one re-run.
2. Verification
Database
SELECT id, status, completeness_pct, ran_at
FROM validation_runs WHERE cycle_id = '<cycleId>'
ORDER BY ran_at DESC LIMIT 1;
SELECT rule_code, severity, outcome, message
FROM validation_results WHERE run_id = '<runId>';
SELECT id, rule_code, severity, status, opened_at
FROM workflow_issues WHERE cycle_id = '<cycleId>' AND status = 'OPEN';Worker logs
Validation run started cycleId=<id>
Validation run completed cycleId=<id> failedBlocking=2 newStatus=ACTION_REQUIREDWeb UI
/dashboard/cycles/<id> →
- Issues tab lists every open
WorkflowIssue. - Header shows the new status badge (
Action requiredorReady for internal review). - Completeness gauge updates.
3. Negative & edge cases
- No submission yet → 400
"Cycle has no submission to validate." - Cycle already in
EXPORTEDor later → 400"Validation cannot run after export."Use a revision round instead (P-10 revision branch). - Override an issue —
POST /ops/issues/:id/resolvewith{ "override": true, "reason": "..." }. RequiresPAYROLL_LEAD. Writes aWorkflowIssueOverriderow +issue.overriddenaudit event. - Duplicate receipts override — same as above; the WARNING does not block by itself, but if business rules upgrade it to BLOCKING (configurable per client), use override.
Next
- All blocking pass → P-06 · Internal review → P-07 · Generate export.
- Blocking failed → P-05 · Request info from client to bounce the gaps back to the client.