Skip to content

ID-145 PLAN — Procurement form-first re-architecture (decomposition {145.4})

ID-145 {145.4} PLAN — decomposition into implementation Subtasks

Section titled “ID-145 {145.4} PLAN — decomposition into implementation Subtasks”

Type: PLAN (decomposition). Companion to PRODUCT.md {145.2} (48 BIs, rev. S467), TECH.md {145.3} (§2 migration plan M1–M6, §3 extraction pipeline, §4 R7 wiring, §5 surfaces, §7 per-BI proposed changes, §8 testing, §9 open decisions), and ARCH-REVIEW.md §5 (the W1–W5 build path this refines). Authored by a FRESH Planner context (distinct from the {145.2}/{145.3} authors) via planning-and-task-breakdown. UK English throughout. Ledger writes on MAIN only — this file is the decomposition record; the Subtask JSON array is applied by the Orchestrator via bun scripts/ledger-cli.ts add-subtasks 145 --file -.

Binding rulings encoded (not re-derived): DR-038, DR-056 (re-key umbrella tools, don’t rename), DR-057 (commonforms direct, Apache-2.0), DR-058 (recover+generalise OOXML readers, no ML), DR-059 (.doc/.xls convert-on-upload). Three genuinely-open decisions (TECH §9) carry named defaults + “owner may revise” into their slices (OQ-1 → {145.19}, OQ-7 + question-embedding persistence → {145.17}).

Re-anchor the procurement item onto the form grain (item IS one form instance): a W1 staging-first migration batch cements the model, then four surfacing/engine waves restore the deleted fillable-structure producer, wire the built-but-unused matching edge, rebuild the item page + dashboard + MCP surfaces onto the form, and add the thin promotion-gate UI. This is re-anchoring + surfacing + three net-new engines (field writer, matching caller, promotion UI), not a green-field rebuild. Subtask ids start at 6 ({145.1}–{145.5} are RESEARCH / PRODUCT / TECH / this PLAN / OQ-12-trace).

Architecture decisions carried into the plan

Section titled “Architecture decisions carried into the plan”
  • W1 is one atomic migration push (M1–M6, load-bearing internal order) that MUST land before the ID-45 client re-ingest (cross-Task ordering guidance, not a dep) — {145.6}.
  • Atomic-release group {145.6}+{145.7}. M3 drops the resolve_or_mint_form_template_id RPC, which has a live TS caller — dropping it while the caller is deployed 500s two routes (PGRST202). The migration batch and the caller-removal ship as ONE PR/deploy, guarded by a CI grep-gate asserting zero surviving caller references in the merged tree (TECH §2 M3 / §10).
  • Sibling-only dependencies. Every dep is another 145.x. Cross-Task couplings (ID-45 re-ingest ordering, id-128 golden-snapshot re-take, id-71 umbrella register, completing-forms sibling Task) are encoded as ordering guidance in details, never as deps. No cross-Task Subtask dep surfaced — no escalation.
  • Shared-file chains sequenced, disjoint-file slices parallelised. The three question-route touches ({145.7} → {145.12} → {145.17} on questions/extract/route.ts) are a sequenced chain; the extractor slices ({145.10} OOXML vs {145.11} PDF) are disjoint-file parallel-safe.
  • Track-close gate = e2e-nightly green ({145.23}), after the {128.10} golden snapshot is re-taken post-W1 (it must not freeze legacy seed shapes).

W1 — migration batch (foundation; atomic push)

Section titled “W1 — migration batch (foundation; atomic push)”
  • {145.6} — W1 migration batch M1–M6: lineage migrate → purge NULL-ftid questions → rename/reshape to form_instances → api-view + type regen → drop workspace stratum → reseed exemplar. test:integration gate. (BI-1,2,5,6,27,28,43,44,45)
  • {145.7} — Drop the resolveOrMintFormTemplateId RPC callers + workspaces.status writes from the two question routes + match route; add the CI grep-gate. Atomic with {145.6}. (BI-6, atomic-release guard)
  • {145.8} — Form-first POST /api/procurement: mint a form_instances row via FormTypePicker, never a bare workspace; creation copy is item/form language. (BI-7,8,12)
  • {145.9} — DR-014 manual form-upload entry (reuse tender/route.ts hardening); uploaded doc IS the item’s form; enqueue analyse_form. (BI-9,16)
  • {145.10} — Recover + generalise the id-52 OOXML field extractors (DR-058) → Plane-2 form_instance_fields; regression-gate on the real corpus. (BI-20; Python)
  • {145.11} — PDF field detection via commonforms (DR-057) + mandatory pdfplumber label-pairing → form_instance_fields + fillable-PDF artefact. (BI-20; Python)
  • {145.12} — Extend Plane-1 Claude question extraction to XLSX + q_a_extractions metadata parity; write form_questions keyed on form_instance_id. (BI-10)
  • {145.13}analyse_form worker lane: fold analyse/fill into the processing_queue consumer, format-route (incl. .doc/.xls LibreOffice convert, DR-059), deploy. (BI-20; Python)

W3 — match / fill / catalogue / retrieval

Section titled “W3 — match / fill / catalogue / retrieval”
  • {145.14} — Un-orphan auto-map/route.ts against real form_instance_fields. (BI-21)
  • {145.15} — Per-format fill writers + partial-fill success-with-gaps + re-entrant re-fill; fill anchored to the form id. (BI-22,23; Python + TS)
  • {145.16} — Un-strand Path-C catalogue-form-requirements (human-confirmed writes only). (BI-24)
  • {145.17} — Wire R7: recompute question_matches on question create/update; scope tags; form-scoped MCP reader. (BI-34,35,36; OQ-7 + embedding-persistence defaults)
  • {145.18} — Item-page data + identity: rebuild procurement-detail-shape.ts on form_instances; re-anchor route [id] to the form; no legacy redirect. (BI-1,2,3,4,5,13-facts,14,15,16,17,18)
  • {145.19} — Item-page UI surfaces + grouping lineage + drafting re-point. (BI-13,18,19,28,29,40,41; OQ-1 default)
  • {145.20} — Dashboard re-wire onto the form grain (list, links, Content Performance, copy). (BI-30,31,32,33)
  • {145.21} — MCP re-key + false-vocabulary renames + citation activation. (BI-37,42)
  • {145.22} — Thin Governance promotion-gate UI composing the existing RPC + promote route + dedup queue; no new backend; /review unchanged. (BI-38,39)
  • {145.23} — e2e-nightly green + full bun run test / test:integration regression; post-W1 {128.10} snapshot re-take (cross-Task ordering guidance). (BI-46,47,48; track close)
6 ──┬─ 7 (ATOMIC group: one PR/deploy; CI grep-gate)
├─ 8 ───────────────┬─ 18 ──┬─ 19 ── (needs 17)
│ │ └─ 20
├─ 9 ──┐ │
├─ 10 ─┼─┐ │
├─ 11 ─┼─┼─┐ │
│ │ │ │ │
├─ 12 ─┘ │ │ │ (7 → 12 → 17 : shared questions/extract/route.ts chain)
│ │ │ │
│ 13 ◄──┴─┴─ 12 │ (13 = worker orch; deps 6,9,10,11,12)
│ ▲ │
├─ 14 ◄─ 10,11 │
├─ 15 ◄─ 10,11,13 │
├─ 16 ◄─ 10,11 │
├─ 17 ◄─ 7,12 ──────┘
├─ 21
└─ 22
23 ◄─ ALL (6..22) track-close gate

Explicit dependency arrays (sibling-only, all backward-pointing):

SubtaskDepends onSubtaskDepends on
6156, 10, 11, 13
76166, 10, 11
86176, 7, 12
96186, 8
1061917, 18
116206, 18
126, 7216
136, 9, 10, 11, 12226
146, 10, 11236,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22

Parallelisation guidance (for Orchestrator dispatch sequencing)

Section titled “Parallelisation guidance (for Orchestrator dispatch sequencing)”
  • Atomic group {6,7} — dispatch together, merge as ONE PR. The grep-gate (zero surviving resolveOrMintFormTemplateId / .rpc('resolve_or_mint_form_template_id')) must be green in the merged tree before the W1 migration lands. Nothing else merges until this pair lands.
  • W2 parallel batch (post 6+7): {8, 9, 10, 11, 12} — disjoint file sets, dispatch in parallel. {12} waits on {7} (shared questions/extract/route.ts). Then {13} integrates (deps 9,10,11,12) — dispatch alone after the batch.
  • W3: {14, 16, 17} parallel; {15} after {13} (shared bid_worker.py). {17} is the tail of the questions-route chain (7 → 12 → 17) — sequence or group-dispatch with {12}.
  • W4: {18} first, then {19, 20} parallel; {21} parallel from the start (deps 6 only). {19} waits on {17} (BI-40 zero-candidate flow needs recomputed matches) and {18} (data shape).
  • {22} is dep-6-only — dispatchable any time after W1 (governance files disjoint from all).
  • {23} last — depends on everything.

{145.6} + {145.7} are a single atomic release. Rationale (TECH §2 M3 / §10): M3 drops the resolve_or_mint_form_template_id RPC; its live caller resolveOrMintFormTemplateId (lib/domains/procurement/resolve-form-template.ts:93, invoked from app/api/procurement/[id]/questions/route.ts + questions/extract/route.ts) would 500 both routes (PGRST202) in any gap window. The two subtasks are dispatched grouped, cherry-picked into one PR, and gated by a CI grep-gate asserting zero surviving caller references in the merged tree. App-not-live latitude (zero users) is the secondary guard; the grep-gate is primary.

Close gate — e2e-nightly green ({145.23})

Section titled “Close gate — e2e-nightly green ({145.23})”

Track-close requires: {128.10} golden snapshot re-taken AFTER W1 (must not freeze legacy seed shapes — cross-Task id-128 coordination; id-128 stays PAUSED excl {128.3} until ID-145 lands), the seeded form-first exemplar (M6) present, e2e-nightly green, and full bun run test + test:integration regression clean. No item page renders “No forms yet” with a question count; no dashboard link resolves to /bids/<uuid>; list_active_procurement / get_procurement_detail retain their names and return form-scoped data; no procurement/citations surface names content_item.

Open decisions carried as defaults (TECH §9)

Section titled “Open decisions carried as defaults (TECH §9)”
OQSliceDefault encodedOwner-revise note
OQ-1 roll-up (BI-29){145.19}No roll-up — each grouped form independent; grouping is read-only lineageOwner may revise to shortlist-failure ⇒ lost roll-up
OQ-7 scope-tag config (BI-35){145.17}form_type + sector (S462 note)Confirm where tenant config lives before wiring
Question-embedding persistence{145.17}Compute-on-recompute for v1Persist under record_embeddings.owner_kind='form_question' only if re-match-on-corpus-change proves needed
RiskMitigationSlice
DR-030/032 silent api-view driftM4 regen in-batch; test:integration gate; INV-20 update6
RPC-drop / caller gap 500Atomic group + grep-gate6+7
FK-drop ordering / lineage-before-deleteLoad-bearing M1<M3<M5 order6
Reader generalisation scope creepRegression-gate on the fixed corpus10
commonforms heavy worker depGate behind async analyse_form; re-verify pin at impl11, 13
{128.10} freezing legacy shapesRe-take AFTER W123

End of PLAN — ID-145.4. The Subtask JSON array (ids 6–23) is returned to the Orchestrator for bun scripts/ledger-cli.ts add-subtasks 145 --file -. Not committed here (the Orchestrator commits after the Checker gate). Subtask status unchanged.