Skip to content

WOPC withdraw → reassign lets closing director be the WOPC's own payee (cross-issuance bug)

Goal (owner, 2026-06-15)

BUG on the Records page WOPC tab: after a user WITHDRAWS a WOPC signing-request email, they can re-select ANY available director as closing director — the picker ignores who the WOPC is FOR. The cross-issuance safeguard must hold: the closing director CANNOT be the person the WOPC was created for (a director can't sign off on their own WOPC/payment).

Notes

  • Same financial control as T-048 R3 (directors). The enforcement exists for creation via resolveRequiredClosingDirector(payeeAbbreviation) (lib/accounting/wopcPipeline.server.ts / matchPlugins/wopcInline.tsx), but the withdraw→reassign path on the Records WOPC tab (components/records/WopcSigningRequestModal.tsx director picker) does NOT apply it.
  • Fix: when the WOPC's payee IS a director, exclude that director from the picker options + validate server-side on (re)assign/send. Reuse the existing cross-issuance resolver; don't duplicate the rule.
  • Verify on both the withdraw→re-request flow and a normal request for a director-payee.

Log

  • 2026-06-15 created (owner). Backlog; unrelated to T-048 but same cross-issuance safeguard.
  • 2026-07-09 — doing (Records Infrastructure; routed via EOP Local Assistance fork, owner 2026-07-09). ✅ Read AGENTS.md · checked the board by scope (no dup; T-048 R3 / T-160 share the same resolver). Source: Records (Infrastructure) · https://claude.ai/code/session_018RDB37kCqfouHdygVXTAtD
  • Root cause (verified): two entry points assign a director to a signing request, and neither applied the cross-issuance rule the render layer forces. The Records "Request signature" modal re-requests via createOrGetRequest; the reassign endpoint hits assignDirector. Both let ANY director be chosen — including the WOPC's own payee.
  • Fix: shared assertCrossIssuanceOk(wopc, directorId) in operations.server.ts, reusing resolveRequiredClosingDirector(payeeAbbreviation) (never duplicating the rule). When the WOPC's payee IS a director, the assignee must be the forced-opposite director; enforced server-side on both createOrGetRequest and assignDirector (server is the source of truth). Non-director payee → resolver returns null → unchanged.
  • Client: WopcSigningRequestModal now takes payeeAbbreviation (threaded from the WOPC row in RecordsApp) and disables the payee-director in the picker + shows a "forced" hint, mirroring the creation picker (wopcInline). Defence-in-depth; the server guard is the actual control.
  • Full unfiltered tsc --noEmit clean. PR open for review → flips to done on merge.
  • 2026-07-09 — done (merged to main via PR #875, no deploy — owner: commit + merge every change, don't deploy; main 🟡🟡 = manual). Verdict: shipped as planned — the shared assertCrossIssuanceOk guards BOTH assignment entry points (createOrGetRequest + assignDirector) server-side (the control), and the modal picker disables the payee-director (defence-in-depth). Reused resolveRequiredClosingDirector; no rule duplicated. SHAs: 203d2b80 (fix) · this close-out · PR #875 merge commit. Blast-radius: WOPC signing-request assignment (lib/wopc/signingRequests/operations.server.ts), components/records/WopcSigningRequestModal.tsx, and the signing-modal wiring in RecordsApp.tsx — an agent touching WOPC signing assignment / reassignment should read this. Owner runtime-verify (director-payee withdraw→re-request) still pending.