Skip to content

I-018 β€” Signed IR56M shows "Draft" on Records

Symptom

Owner signed IR56M forms, but the Records β†’ IR56M tab kept showing them as Draft in the "Status" column.

Root cause

IR56M carried two status notions that were never linked:

  1. The filing doc's own status β€” tebs-erl/ir56mFilings/{endingYear}, field recipients[].status (type IR56MRecipientStatus = 'draft' | 'confirmed' | 'signed' | 'filed'). Set to 'draft' once at seed time (seed.server.ts) and never updated again. The Records "Status" column reads this β†’ so it always showed "Draft".

  2. A separate signing-request doc β€” aote-system/ir56mSigningRequests/{requestId}, field status (draft β†’ assigned β†’ sent β†’ signed β†’ voided). The sign handler recordSignature() flips THIS to 'signed'. The Records "Signing" column reads it via /api/ir56m-signing/lookup β†’ so it correctly showed "Signed".

Unlike WOPC β€” which mirrors its signed/voided state back onto the WOPC doc itself β€” the IR56M signing flow was built with an explicit "no doc-mirror" note, so the filing doc's own status was left stranded at 'draft'.

Fix (commit below)

Mirror the signing lifecycle onto the filing doc itself, the way WOPC does β€” so the IR56M doc is its own source of truth for the "Status" column.

  • lib/taxHK/ir56m/filings.server.ts:
  • New setRecipientStatusServer({ endingYear, payeeId, status }) β€” a targeted single-field update of recipients[].status (leaves form / frozen / history untouched; no-op if absent or already at target).
  • saveIR56MFilingServer now also guards status (alongside the existing frozen-total guard): a routine form-save carries the seeded 'draft', so a prior 'signed'/'filed' is preserved rather than downgraded.
  • lib/taxHK/ir56m/signingRequests/operations.server.ts: recordSignature mirrors β†’ 'signed'; recordVoided mirrors β†’ 'draft' (a voided IR56M needs re-signing). Best-effort + lazy-imported β€” a mirror failure never unwinds the signing transition (the request doc stays authoritative and the "Signing" column reads it directly).

Mapping implemented (minimal, lowest-risk): draft ↔ signed only β€” signed forms now read 'signed'; void reverts to 'draft'. The 'confirmed' (sent for signing) and 'filed' (after the completed copy is distributed) stages are left available to wire later if the owner wants a richer lifecycle.

Decision log

2026-06-29 β€” βœ… Read AGENTS.md before working this ticket. Source: Records (Infrastructure) Β· https://claude.ai/code/session_018RDB37kCqfouHdygVXTAtD

Owner asked (verbatim): "like the WOPCs, an IR56M's status should be stored on the IR56M firestore doc itself … but not on a separate doc?" β€” confirmed the field existed on the filing doc but was never advanced, and implemented the WOPC-style mirror. Within T-047's signing scope (cross-linked, not a parallel task). Verified: tsc --noEmit clean repo-wide; the pure IR56M unit tests cover filing.ts (unchanged) β€” these server modules aren't exercised by them (vitest isn't installed in the cloud sandbox). Runtime confirmation is owner-on-deploy (sign a form β†’ Records "Status" reads "signed").

Blast radius: IR56M signing operations (recordSignature/recordVoided) + saveIR56MFilingServer now also touch recipients[].status on the filing doc; the Records "Status" column now reflects signing. Agents on T-047's signing flow should know the filing doc is now a status mirror.