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), andARCH-REVIEW.md§5 (the W1–W5 build path this refines). Authored by a FRESH Planner context (distinct from the {145.2}/{145.3} authors) viaplanning-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 viabun 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}).
Overview
Section titled “Overview”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_idRPC, 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-formssibling Task) are encoded as ordering guidance indetails, 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).
Wave map
Section titled “Wave map”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:integrationgate. (BI-1,2,5,6,27,28,43,44,45) - {145.7} — Drop the
resolveOrMintFormTemplateIdRPC callers +workspaces.statuswrites from the two question routes + match route; add the CI grep-gate. Atomic with {145.6}. (BI-6, atomic-release guard)
W2 — create / upload / extract
Section titled “W2 — create / upload / extract”- {145.8} — Form-first
POST /api/procurement: mint aform_instancesrow 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.tshardening); uploaded doc IS the item’s form; enqueueanalyse_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_extractionsmetadata parity; writeform_questionskeyed onform_instance_id. (BI-10) - {145.13} —
analyse_formworker lane: fold analyse/fill into theprocessing_queueconsumer, 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.tsagainst realform_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_matcheson question create/update; scope tags; form-scoped MCP reader. (BI-34,35,36; OQ-7 + embedding-persistence defaults)
W4 — surfaces
Section titled “W4 — surfaces”- {145.18} — Item-page data + identity: rebuild
procurement-detail-shape.tsonform_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)
W5 — governance
Section titled “W5 — governance”- {145.22} — Thin Governance promotion-gate UI composing the existing RPC + promote route +
dedup queue; no new backend;
/reviewunchanged. (BI-38,39)
Close gate
Section titled “Close gate”- {145.23} — e2e-nightly green + full
bun run test/test:integrationregression; post-W1{128.10}snapshot re-take (cross-Task ordering guidance). (BI-46,47,48; track close)
Dependency graph
Section titled “Dependency graph”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 gateExplicit dependency arrays (sibling-only, all backward-pointing):
| Subtask | Depends on | Subtask | Depends on |
|---|---|---|---|
| 6 | — | 15 | 6, 10, 11, 13 |
| 7 | 6 | 16 | 6, 10, 11 |
| 8 | 6 | 17 | 6, 7, 12 |
| 9 | 6 | 18 | 6, 8 |
| 10 | 6 | 19 | 17, 18 |
| 11 | 6 | 20 | 6, 18 |
| 12 | 6, 7 | 21 | 6 |
| 13 | 6, 9, 10, 11, 12 | 22 | 6 |
| 14 | 6, 10, 11 | 23 | 6,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}(sharedquestions/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.
Atomic-group declaration
Section titled “Atomic-group declaration”{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)”| OQ | Slice | Default encoded | Owner-revise note |
|---|---|---|---|
| OQ-1 roll-up (BI-29) | {145.19} | No roll-up — each grouped form independent; grouping is read-only lineage | Owner 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 v1 | Persist under record_embeddings.owner_kind='form_question' only if re-match-on-corpus-change proves needed |
Risks (carried from TECH §10)
Section titled “Risks (carried from TECH §10)”| Risk | Mitigation | Slice |
|---|---|---|
| DR-030/032 silent api-view drift | M4 regen in-batch; test:integration gate; INV-20 update | 6 |
| RPC-drop / caller gap 500 | Atomic group + grep-gate | 6+7 |
| FK-drop ordering / lineage-before-delete | Load-bearing M1<M3<M5 order | 6 |
| Reader generalisation scope creep | Regression-gate on the fixed corpus | 10 |
| commonforms heavy worker dep | Gate behind async analyse_form; re-verify pin at impl | 11, 13 |
{128.10} freezing legacy shapes | Re-take AFTER W1 | 23 |
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.