FS-05 · Disclosure interrogation
SOP:
SOP_Financial_Statements.md§6 / Step 5.0Actors: FS Preparer (fs-preparer@spade.local) — holdsRESPOND_DISCLOSURE. Pre-state: Engagement atSTATEMENTS_GENERATED. Post-state:FsDisclosureResponserows populated for every triggered mandatory rule (and as many conditional / recommended rules as the engagement requires).
0. Prerequisites
- Engagement at
STATEMENTS_GENERATED(see FS-04). - Disclosure rules constitution loaded fine (run
pnpm --filter @breezycorp/financial-statements testto confirm).
1. Steps
1.1 Read the live gap list
GET /ops/financial-statements/engagements/<engagementId>/disclosures/gaps
Authorization: Bearer <fs-preparer-jwt>The handler:
- Reads every
FsTrialBalanceItem.taxonomyKeyand builds aSet<string>of present keys. - Calls
evaluateDisclosureGaps(presentKeys):- Walks every rule in
disclosure_rules.json - Evaluates each rule's
trigger_condition(alwaysortb_contains_any([...]), joined by" OR ") - Returns triggered rules sorted by priority (
mandatory→conditional→recommended), thenrule_id
- Walks every rule in
- Returns the list, annotated with which prompts already have responses.
1.2 Answer a rule
In the UI: expand the accordion for a rule; fill the prompt inputs; click Save.
Or via API:
POST /ops/financial-statements/engagements/<engagementId>/disclosures/respond
Content-Type: application/json
Authorization: Bearer <fs-preparer-jwt>
{
"ruleId": "DISC-001",
"responses": [
{ "promptId": "DISC-001-01", "value": "Aurora Holdings Pte. Ltd." },
{ "promptId": "DISC-001-02", "value": "202412345A" },
{ "promptId": "DISC-001-03", "value": "10 Anson Road #20-01, International Plaza, Singapore 079903" },
{ "promptId": "DISC-001-04", "value": "Wholesale trade in industrial machinery and parts" },
{ "promptId": "DISC-001-05", "value": "2024-12-31" }
]
}The handler:
- Asserts
RESPOND_DISCLOSURE. - Looks up the rule in the constitution; asserts each
promptIdexists. - Validates each
valueagainst the prompt'sinput_type(text / date / select / boolean / table / multi_text / number). - Upserts one
FsDisclosureResponseper(engagementId, ruleId, promptId). - Emits
AuditEventfs.disclosure.responded(one event per rule, payload lists prompt ids and timestamps).
1.3 Conditional prompt visibility
The UI hides any prompt whose visible_when predicate is false. Example: in DISC-003 (Going Concern), the prompt DISC-003-02 ("Describe the exit strategy") is visible_when: "DISC-003-01 == 'no'". If the answer to DISC-003-01 is yes, the exit-strategy prompt is hidden and not required.
2. Verification
Database
SELECT rule_id, count(*) AS prompt_responses
FROM fs_disclosure_responses
WHERE engagement_id = '<engagementId>'
GROUP BY rule_id
ORDER BY rule_id;Gap check
GET /ops/financial-statements/engagements/<engagementId>/disclosures/gapsMandatory rules should have triggered: true, complete: true. Conditional rules whose trigger fired should have complete: true if you intend to ship; otherwise the notes generator will refuse.
Audit log
| Event type | Notes |
|---|---|
fs.disclosure.responded | One per save action; payload identifies the rule + prompts |
3. Negative & edge cases
- Unknown
ruleIdorpromptId—400 Bad Requestwith the offending id; safer than silently dropping. - Wrong
valueshape forinput_type— e.g. a string passed to adatefield. Server-side validation rejects with400. - Reading gaps before mapping is complete — handler returns the gap list but flags
presentKeysas partial. Useful for early scoping but should not be relied on for completeness. - Mandatory rule unanswered when notes generation is enqueued —
generate-noteshandler refuses (409 Conflict) and returns the list of missing mandatory prompt ids. - Reopening a previously-answered prompt — the upsert replaces the value. The audit log captures both the before and after.
Next
Proceed to FS-06 · Generate notes & Directors Report.