Skip to content

Post-migration dead-code cleanup — drop legacy-shape fallbacks once migrations are applied

Why

Each shape migration in the chain (T-074 / T-075 / T-077 / T-078) ships with read-side dual-read code — new shape preferred, legacy fallback kept — so the web app keeps working before / during / after the migration script runs. Once a migration has been applied to prod (and re-running the script reports "nothing to migrate"), the fallback branches become dead code. This task is a sequence of small cleanup PRs, one per migration that's landed, to delete that dead code.

Cleanup checklist (one PR per migration applied)

After T-075's gl[4001] backfill (scripts/backfill-coaching-gl4001.ts)

  • Nothing to clean up. The runtime read path uses effectiveGlCodes derived from existing V1 fields (matchedCoachingPayments / matchedCoachingInvoices); the backfill is for data hygiene only. No fallback code exists to drop.

After T-078's invoice doc shape migration (scripts/migrate-invoice-doc-shape.ts)

In lib/invoiceDocShape.ts — single point of cleanup, called out in the file's docstring: - Drop the tier-2 (invoiceDetail / paymentDetail / lineItems / top-level invoiceTotal / flat email*) accessor fallbacks. - Drop the tier-3 (flat top-level paymentStatus / invoiceIssued / invoiceCleared / invoiceDrafted / invoiceCreated / invoiceNumber / payTo-and-aliases / item{N}) accessor fallbacks. - Drop the hoistPriorTierToFlat pre-pass in transformPayloadToNestedShape. - Drop PRIOR_TIER_TOP_LEVEL_MAPS + LEGACY_VESTIGIAL_KEYS delete arrays in the UPDATE-path delete-emission (no docs left holding those keys).

After T-078 Phase A's coaching invoice migration (scripts/migrate-coaching-invoice-shape.ts)

  • In lib/accounting/coachingInvoices.ts: · getCoachingInvoiceForSession — drop the legacy Sessions/{id}/invoice subcollection fallback after the map-field read. · findCoachingInvoiceByNumber — drop the legacy collectionGroup('invoice') fallback.
  • In lib/accounting/derivedJournals.server.ts (COACHING_ISSUED) — drop the if (sessionsWithLegacy.length > 0) { collectionGroup('invoice') … } branch.
  • In lib/accounting/reports.server.ts (AR aging coaching loop) — drop the same.
  • In pages/api/accounting/matchable-coaching-invoices.ts — drop the else { … sessionDoc.ref.collection('invoice').get() } branch.
  • In pages/api/coaching/invoices/[sessionId]/saved.ts — drop the legacy subcollection fallback (inline; predates the helper).
  • In components/StudentDialog/SessionDetail.tsx — drop the legacy setInvoiceExists fallback after the map-field check.
  • In lib/accounting/transactions.ts:syncCoachingInvoiceTxTrail — drop the subcollection-write branch.
  • Delete scripts/migrate-coaching-invoice-shape.ts (one-off, served its purpose).

After T-078 Phase B's session migration (scripts/migrate-sessions-under-students.ts)

TBD when Phase B lands. Will include collection vs collectionGroup branches.

After T-076's workStatus drop has soaked

  • Remove /api/accounting/sync-project-statuses.ts route (syncProjectWorkStatuses is already a no-op stub).
  • Remove the syncProjectWorkStatuses export from lib/accounting/transactions.ts and lib/accounting/index.ts.

Sequencing

Each cleanup PR is independent and gated on its migration script having been applied to prod cleanly (re-run reports "nothing to migrate"). Don't bundle multiple cleanups into one PR — keeps each easy to revert if a stale-doc edge case is found.

T-075 · T-076 · T-078 · T-079 (the migrations whose fallbacks this task cleans up).

Log

  • 2026-06-20 created. Logged proactively so the fallback cleanup doesn't get lost after the migration scripts run.
  • 2026-06-20 prod migrations applied (T-078: invoice doc shape, T-079 Phase A: coaching invoice → map field, T-079 Phase B: sessions under students). gl[4001] backfill (T-075 data hygiene) applied too. Each script idempotent on re-run.
  • 2026-06-20 PR 1/3 SHIPPED in #770 (claude/t080-invoice-shape-fallback-cleanup-yxMLM). lib/invoiceDocShape.ts shrinks by 321 lines — all tier-2 / tier-3 dual-read fallbacks gone, hoistPriorTierToFlat pre-pass gone, UPDATE-path legacy-key deletes gone. tsc + 114-test accounting suite clean.
  • 2026-06-20 PR 2/3 SHIPPED in #771 (claude/t080-coaching-schema-fallback-cleanup-yxMLM). Coaching schema dual-read fallbacks removed: getCoachingInvoiceForSession + findCoachingInvoiceByNumber now read the canonical shape only; findSessionRef keeps a no-abbr collection-group lookup for the 46 legacy stubs; the if (sessionsWithLegacy.length > 0) branches in derivedJournals.server.ts + reports.server.ts (+ unused parseCoachingInvoiceDocPath) gone; matchable-coaching-invoices.ts subcollection branch gone; SessionDetail.tsx existence check single-path; syncCoachingInvoiceTxTrail subcollection-write branch gone; writer in client.tsx writes directly to Students/{abbr}/Sessions/{id}. Net: 286 lines deleted, 137 added. tsc + 114-test suite clean.
  • 2026-06-20 PR 3/3 SHIPPED — workStatus stub + sync-project-statuses endpoint. Deleted pages/api/accounting/sync-project-statuses.ts; removed the syncProjectWorkStatuses no-op stub from lib/accounting/transactions.ts + its export from lib/accounting/index.ts. tsc + 114-test suite clean. T-080 done.

2026-06-24 — appendix: T-089 (WOPC) dual-read cleanup

  • Attestation (Accounting (Diagnostics)): read AGENTS.md; appendix to the T-080 cleanup chain — T-089's read-side fallbacks were the WOPC counterpart to the invoice / coaching cleanups already in this task.
  • Source: Accounting (Diagnostics) · https://claude.ai/code/session_01G58Y71noihrYCDEDMexmea
  • What changed: lib/wopcDocShape.ts getters now read the canonical bucketed shape only (?? data.legacyX tails removed; legacy if (m) … else { legacy } branches collapsed). Writers in lib/wopc.server.ts stop emitting legacy flat keys (createWOPC, updateWOPCMatched, updateWOPCClosingDirector); three dead writers deleted (updateWOPCCompleted, updateWOPCFailed, updateWOPCPdfReference — 0 callers each). Test suite __tests__/lib/wopcDocShape.test.ts rewritten to the canonical-only contract (26/26 passing).
  • Migration prerequisite met: the Phase 5 migration script + the line-item rule had already run; this cleanup also shipped scripts/backfill-wopc-notes.ts to catch the one field the migration missed (3 docs). Live: 34/34 WOPCs in new shape, no fallback-dependent fields.
  • Cross-link: see T-089's "2026-06-24 — T-080 dual-read cleanup + DONE" entry for the full close-the-loop record (verification, blast radius, remaining follow-ups).

Commit index (backfilled 2026-07-01, best-effort · Coaching (Diagnostic))

Candidate related commits, auto-backfilled from git on main: commits whose message references this task's UID or a PR number it cites. Not verified — this squash-merged history can't yield a precise per-task list, so rows tagged (mentions only) name the task in passing (may be tangential) and untagged work commits may be missing. Treat as a starting point: verify, prune tangential rows, and append any real ones per the AGENTS.md "record every related SHA" policy.

  • ba2025c 2026-06-24 — feat(t-089): createWOPC writes full bucketed shape + migrate 2 stragglers; close T-074 (mentions only)
  • e035e64 2026-06-24 — feat(t-089): T-080 dual-read cleanup + DONE — drop WOPC legacy-shape fallbacks
  • ca5f048 2026-06-24 — Merge pull request #800 from girafeev1/claude/busy-dirac-QmUdM (mentions only)
  • d7b4e0c 2026-06-25 — fix(records): WOPC drawer iframe + transaction-linked preview shape (T-126) (mentions only)
  • d7908e0 2026-06-29 — docs(tickets): I-019 — fix sibling ERL-2026-004-0508 drafted={} + sharpen root cause (mentions only)

2026-07-03 — correction: invoice fallbacks restored for live split-shape survivors

  • Attestation (Codex local session): read AGENTS.md; checked the board by scope, not UID — this is a correction to T-080's invoice fallback cleanup premise. Source: Codex local session · /Users/gutchumi/dev/ArtifactoftheEstablisher-codex-invoice-hotfix
  • Why: The T-080 invoice cleanup assumed every project invoice had been fully migrated to detail.*. Live Firestore disproved that: ERL-2026-004-0508 still had paymentDetail.payTo but not detail.payment.payTo, and new writes could still preserve paymentDetail because the transformer did not fold partial-nested maps.
  • Correction: lib/invoiceDocShape.ts restored narrowly-scoped read tolerance for invoiceDetail / paymentDetail / top-level lineItems survivors and now folds those maps into canonical detail.invoice / detail.payment / detail.lineItems during writes. This is not a general return to flat-field support as the preferred model; it is a safety rail around the exact split-shape tier T-078 introduced.
  • 2026-07-03 follow-up: owner testing showed previews still had items: [] / total: 0 after the first correction. The remaining edge was an empty detail.lineItems map masking valid top-level lineItems.itemN maps. Accessors now treat a canonical lineItems map with no itemN entries as missing and fall back to the survivor map.
  • Cross-link: full owner report, live findings, verification, and blast radius are logged under T-078's 2026-07-03 hotfix entry.