ID-130 {130.4} PLAN — Procurement / Form Domain Model + IA
⚠️ PARTIALLY SUPERSEDED (S462, 2026-07-11) — DR-038 + ID-145. The C/Hybrid workspace-umbrella half of this ratified model (PRODUCT B-1, B-3, B-7, B-19, B-22, B-23; TECH AD-2, T-B23 — procurement = a
workspacesumbrella holding many forms,procurement_workspacesroll-up) is superseded by DR-038 (S452) + the owner ruling of 2026-07-11: a procurement item IS one form instance. The per-form half (PRODUCT B-2, B-4..B-6, B-8..B-16, B-20, B-21; TECH AD-1, AD-3, AD-4) remains the canonical keeper machinery. Rework owner: ID-145 —specs/id-145-procurement-form-first/RESEARCH.md. Do not cite the umbrella invariants in new specs.
PLAN — Procurement / Form Domain Model + Information Architecture
Section titled “PLAN — Procurement / Form Domain Model + Information Architecture”STATUS: RATIFIED — S418, Liam via Lavish gate 2026-06-25. Subtasks {130.5}–{130.16} added to the ledger; priorities deliberately NOT set.
Authored S418 (25/06/2026) by a FRESH Planner (Q-PLANNER-2 / B4 — not the {130.2} PRODUCT or {130.3} TECH author) against the RATIFIED spec chain:
RESEARCH.md(D1–D8 §6.0),PRODUCT.md(B-1..B-25),TECH.md(T-B1..T-B25 + AD-1..AD-4 + the 13-step migration plan) — all threedone+ Liam-ratified (S411). This PLAN is decomposition only: it breaks the ratified TECH into implementation Subtasks{130.5}..{130.16}, sequences the staging-first migration, fixes per-Subtask file-ownership boundaries, holds the ID-45 timing gate, maps the subsumed ID-61 scope, and records the out-of-Task follow-ups. UK English throughout.This PLAN does NOT re-litigate any ratified decision (the C/Hybrid model, the data-driven
form_outcome_typesCV, thepqq→psqrename, the win-rate{itt,tender,bid,rfp}final-award set, the code-resident workflow). It decomposes to implement them.
How to use this doc
Section titled “How to use this doc”§Code-intelligence orientation cites the empirical re-verification of the TECH landings against
the live working tree (procurement@HEAD, baseline main) so the Executor inherits confirmed
line numbers. §Wave plan gives the serial-vs-parallel structure. §Subtask register is the
human-readable mirror of the TM-shape JSON returned to the Orchestrator. §ID-45 timing gate,
§Subsumed ID-61 scope, and §Out-of-Task follow-ups close the scope loop.
Code-intelligence orientation (re-verified 25/06/2026 — cited, not paraphrased)
Section titled “Code-intelligence orientation (re-verified 25/06/2026 — cited, not paraphrased)”The {130.4} Planner re-ran gitnexus + ast/grep against the live working tree
(/Users/liamj/Documents/development/procurement, baseline main) because TECH flagged the
gitnexus index as 4 commits stale. Every load-bearing landing below was confirmed against the
actual file (gitnexus canTransition context + grep/Read verification):
gitnexus canTransition blast radius (verified — matches TECH’s CRITICAL): 6 incoming
callers — app/api/procurement/[id]/outcome/route.ts:POST,
…/questions/extract/route.ts:POST, …/questions/match/route.ts:POST,
app/api/procurement/[id]/route.ts:PATCH,
hooks/procurement/use-procurement-actions.ts:handleStatusTransition,
lib/queue/handlers/procurement-draft-all.ts:runFormDraftAllJob. canTransition body =
procurement-workflow.ts:72-77 (TECH said 73 — off-by-one, the export line is 73). The
de-dup must NOT change canTransition’s signature or home — only its currentStatus source
moves from workspaces.status to form_templates.workflow_state.
Win-rate SQL landings (grep-verified against supabase/migrations/):
public.get_content_win_ratebody at squash-baseline line 2248 (line 513 is theapiwrapper — TECH correct). Body readsw.domain_metadata->>'outcome' as bid_outcomejoiningform_responses br → form_questions bq → workspaces w— confirms the rewrite target AND that thebid_outcomealias still lives inget_content_win_rate(the deferred ID-61 cleanup item; Unit-E20260624130000only renamed the alias inget_aggregate_win_rate_stats).public.get_aggregate_win_rate_statswas rewritten by Unit-E20260624130000(unique_bids→unique_procurements,bid_outcome→outcome); its body still readsw.domain_metadata->>'outcome'(line 75 of that migration) — the form re-thread target.- Consumers:
app/api/analytics/win-rate/route.ts(DomainStats11,OverallStats22,WinRateStatsSchema41,.rpc('get_aggregate_win_rate_stats')62);lib/mcp/formatters/procurements.ts:209formatContentEffectiveness;app/api/items/[id]/effectiveness/route.ts:81.rpc('get_content_win_rate').
form_questions re-key blast (grep-verified): 34 TS/TSX files reference form_questions
(TECH’s “~24” is the narrower .from('form_questions') write-surface subset; both consistent —
the re-key is additive so all stay green, but per-form reads must filter on form_template_id).
7 SQL functions read it in the squash baseline: get_aggregate_win_rate_stats,
get_content_win_rate, get_form_question_stats, get_form_question_stats_batch,
get_form_summary, hybrid_search, search_for_form_response. 0 Python consumers of the
string form_questions in scripts/ (the cocoindex pipeline writes via the manifest /
_ingest_form_branch path, not a raw table string — re-verify before the re-key if the pipeline
gains a direct write).
schemas.ts de-dup sites (grep-verified): ProcurementMetadataSchema def at line 1019
(the inline .status enum at 1022-1033 per TECH); VALID_BID_STATUSES at 2007;
ProcurementListParamsSchema.status: z.enum(VALID_BID_STATUSES) at 2020-2021;
parseProcurementMetadata at 1049. Workflow module re-exports PROCUREMENT_WORKFLOW_STATES
from @/types/procurement (line 2/4) — the canonical import target (no barrel).
pqq code sites = exactly 2 (grep-verified): lib/coverage/gap-scoring.ts:28 (pqq: 10)
and components/coverage/template-coverage-content.tsx:115
(pqq: 'Pre-Qualification Questionnaire'). No other TS/TSX pqq reference.
workspaces.ts bid arg-alias (grep-verified): lib/mcp/tools/workspaces.ts:78 —
const typeFilter = args.type === 'bid' ? 'procurement' : args.type; (guarded by if (args.type)
at 77). Retire the alias.
Deferred ID-61 wire-field landings (grep-verified): active_bids/bid_summary →
lib/activity/bid-summary.ts + lib/dashboard.ts + lib/reorient.ts + lib/attention.ts +
app/api/dashboard/route.ts + app/api/reorient/route.ts + lib/mcp/{tools/apps,app-bundles,tools/review}.ts;
--bid-* tokens → app/styles/domain-tokens.css; /api/bids JSDoc →
lib/queue/handlers/procurement-draft-all.ts + lib/validation/schemas.ts; bid_outcome alias
→ get_content_win_rate body (folded into the win-rate rewrite Subtask).
PJ landings (grep-verified): app/procurement/page.tsx:155 (“Manage bid submissions and
tender responses” — PJ-2), :263/:338 ProcurementListRow (prop named bid), :326 “Create
your first bid”; mcp-apps/form-dashboard/src/{types,app}.ts + mcp-apps/reorient-me/src/{types,app}.ts
carry the Bid* type names (PJ-5).
OQ-3 / Q-EX2 external-library check: N/A — this decomposition introduces no net-new external-library symbols (carried verbatim from TECH §Context: all code uses internal Canonical symbols, Postgres/Supabase built-ins, and already-pinned Zod). Recorded so the Checker confirms the step was considered, not skipped.
Architecture decisions for the decomposition
Section titled “Architecture decisions for the decomposition”- The migration is ONE serial DDL spine, not parallel slices. TECH’s 13-step migration plan
is a strict ordering dependency (the
form_templates.outcomeFK needs theform_outcome_typesCV to exist first; the win-rate rewrite needs the CV + columns; the data migration needs every schema object). The spine is decomposed into four serial Subtasks by risk/verification boundary, NOT by table — bundling the pure-additiveADD COLUMN/CREATE TABLEsteps that cannot conflict into one migration, and isolating the two highest-risk steps (the 42P13 win-rate rewrite; the data migration) so each carries its own staging verification. - Schema before types before code. No TS code Subtask starts until the schema + regenerated types are green (TECH step 13). This is the wave boundary: Wave 1 = schema spine, Wave 2 = types/views regen, Wave 3+ = code fan-out.
- Code fan-out parallelises on disjoint file ownership — collisions resolved by ownership
reassignment, not by hoping isolated worktrees won’t clash. The staff-engineer review found
three Wave-3 shared-file collisions; each is resolved so every Wave-3 Subtask OWNS a disjoint
file set:
lib/validation/schemas.tsis owned by {130.10} (the enum de-dup). The/api/bidsJSDoc cleanup that {130.16} would otherwise touch in the same file is folded INTO {130.10} (FIX 1) — {130.16} no longer touchesschemas.ts.app/api/procurement/[id]/route.tsis owned entirely by {130.11}: the T-B1 API GET read-shape change (roll-up offprocurement_workspaces+ list child forms offform_templates, replacing thedomain_metadataread) moves from {130.13} INTO {130.11} (FIX 2). {130.13} now owns ONLY the UI pageapp/procurement/[id]/page.tsx(+ page siblings). T-B1 read-shape is split: API GET → {130.11}, UI consumption → {130.13}.lib/query/query-keys.tsis a 3-way collision ({130.12}/{130.13}/{130.15}). Resolved by SERIALISING the trio (FIX 3): {130.12} ESTABLISHES the form-scoped key namespace under the existingprocurement:object (forms,formTypessub-keys); {130.13} and {130.15} EXTEND it (never rewrite). Deps: {130.13}→[“9”,“12”], {130.15}→[“9”,“11”,“13”].- The detail-surface back-nav stays folded into {130.13} (shares
[id]/page.tsx); the list-page copy/nav (app/procurement/page.tsx) stays a separate Subtask {130.14} (disjoint file).
- The win-rate parity test is the load-bearing acceptance gate, not a live snapshot (the 12 live workspaces carry NULL outcome → a live snapshot is vacuous; TECH §Risks). It rides with the win-rate rewrite Subtask as a synthetic-fixture integration test.
- Sibling-only dependencies hold throughout — every dep below is a bare-digit sibling id. No cross-Task dep surfaced (ID-45 is a timing gate, not a Subtask dep; the deferred ID-61 items are subsumed into this Task, not cross-Task deps). No escalation required.
Wave plan (serial-vs-parallel)
Section titled “Wave plan (serial-vs-parallel)”WAVE 1 — Migration spine (SERIAL; staging-first; ONE migration file PER Subtask — replayable + individually revertible) {130.5} form_outcome_types CV + form_templates engagement cols + form_questions FK + procurement_workspaces rollup cols + q_a_pairs provenance + pqq→psq rename (TECH steps 1–6 — all additive; its own sequential migration file) │ ▼ {130.6} Roll-up recompute fn + trigger (TECH step 7; its own migration file) [dep 5] │ ▼ {130.7} Win-rate rewrite — 42P13 DROP-dance + synthetic parity (TECH step 8; own migration file) │ [dep 5,6] ▼ {130.8} Data migration — mint 12 forms, re-key 30 Qs, recompute 12 rollups (TECH step 9; own migration file) │ [dep 5,6,7] ▼WAVE 2 — Types/views regen (SERIAL; gates all code) {130.9} Regen api.* views (generate-api-views --check) + regen database.types.ts + database-overrides.ts (form_templates shape + FormOutcomeSchema) (TECH steps 10–12) [dep 5,6,7,8] │ ▼WAVE 3 — Code fan-out (mixed parallel/serial; ownership disjoint after collision fixes) Sub-wave 3a (fully PARALLEL — disjoint files, all dep 9): {130.10} Enum de-dup + /api/bids JSDoc in schemas.ts (OWNS lib/validation/schemas.ts) [dep 9] {130.11} Umbrella route re-anchor — GET read-shape + PATCH + outcome (OWNS [id]/route.ts + outcome/route.ts) [dep 9] {130.12} form_type picker — ESTABLISHES query-key namespace (OWNS components/procurement/ picker + query-keys.ts) [dep 9] {130.14} List-page copy/nav + PJ-2..PJ-5 + Bid* (OWNS app/procurement/page.tsx + mcp-apps/) [dep 9] {130.16} Deferred ID-61 wire-field cleanup (NO schemas.ts — FIX 1) [dep 9] Sub-wave 3b (after 3a — query-keys.ts serialisation + read-shape consumption): {130.13} Detail-surface UI form-list + roll-up READ render + back-nav (OWNS app/procurement/[id]/page.tsx; EXTENDS query-keys) [dep 9,12] Sub-wave 3c (after 3b): {130.15} Composer + Q&A promotion re-anchor (EXTENDS query-keys; promotion provenance) [dep 9,11,13] │ ▼WAVE 4 — Gate (Orchestrator, not a Subtask) Full bun run test + generate-api-views --check + schema-parity; then prod migration; THEN ID-45 GO.Critical path: 5 → 6 → 7 → 8 → 9 → 12 → 13 → 15 (with 11 feeding 15 in parallel to the
12→13 arm). The migration spine + types gate is irreducibly serial; within Wave 3 the
query-keys.ts ownership chain (12 establishes → 13 extends → 15 extends) is the only
serialised code arm.
Parallel-vs-serial rationale: Wave 1 is serial because it is one Postgres schema and the
FK/CV/function ordering is a hard dependency (the outcome FK needs its CV target; the win-rate
rewrite needs the columns it reads); each spine Subtask now authors its own sequentially-numbered
migration file (FIX 5) so the staging push is replayable step-by-step and each step is
individually revertible. Wave 3 is mixed, not fully parallel: after the three collision fixes,
sub-wave 3a (10, 11, 12, 14, 16) is genuinely concurrent on disjoint files; 13 waits on 12
(it EXTENDS the query-keys.ts namespace 12 establishes, and consumes the read-shape 11
ships); 15 waits on 13 (further query-keys.ts extension) and on 11 (the form-scoped write
contract). The serialisation is real shared-file ownership, not conservatism.
Subtask register (human mirror of the returned JSON)
Section titled “Subtask register (human mirror of the returned JSON)”Proposed ids continue the existing series ({1}–{4} taken); the Orchestrator finalises ids at
add-subtasks time.
{130.5} — Migration spine: CV + engagement columns + FK + rollup cols + provenance + pqq→psq
Section titled “{130.5} — Migration spine: CV + engagement columns + FK + rollup cols + provenance + pqq→psq”TECH migration steps 1–6 (all additive / non-conflicting). OWNS its own
sequentially-numbered migration file (supabase/migrations/<ts>_id130_spine.sql — NOT a shared
<new>.sql; FIX 5 — replayable + individually revertible). Implements T-B2, T-B3, T-B4 (additive),
T-B5 (CV table+seed), T-B12/T-B13 (pqq→psq — UPDATE targets the LIVE form_types row, not a
migration seed), T-B21 (q_a_pairs provenance col), T-B23 (rollup cols), AD-1, AD-4. Deps: none
(spine head). Boundary: DDL only — no fn/trigger ({130.6}), no win-rate rewrite ({130.7}), no
data backfill ({130.8}).
{130.6} — Roll-up recompute function + trigger
Section titled “{130.6} — Roll-up recompute function + trigger”TECH step 7. OWNS its own sequentially-numbered migration file (FIX 5). Idempotent
recompute_procurement_rollup(p_workspace_id uuid) (INSERT … ON CONFLICT DO UPDATE,
SET search_path, REVOKE EXECUTE … FROM anon) reading form_outcome_types for stage/denominator
classification; AFTER trigger on form_templates engagement-column writes. Implements T-B7
(roll-up half), AD-2. Deps: [5].
{130.7} — Win-rate engine rewrite (42P13 DROP-dance) + synthetic parity test
Section titled “{130.7} — Win-rate engine rewrite (42P13 DROP-dance) + synthetic parity test”RISKIEST. TECH step 8. OWNS its own sequentially-numbered migration file (FIX 5)
app/api/analytics/win-rate/route.ts(types).get_aggregate_win_rate_stats(return-shape change → DROPapi.*wrapper → DROPpublic.*→ CREATE → recreate wrapper);get_content_win_rate(body-only, plainCREATE OR REPLACE, also retires thebid_outcomealias). Both re-thread the CTE throughform_responses → form_questions → form_templates, readft.outcome, joinform_outcome_types fot, denominator =fot.counts_toward_win_rate=true+ terminal won/lost; add the separate shortlist pass-rate aggregate. Synthetic-fixture parity integration test is the load-bearing acceptance gate (NOT a live snapshot — vacuous against all-NULL live data). Implements T-B7 (win-rate half), T-B24 (bid_outcome alias). Deps: [5, 6].
{130.8} — Data migration: mint 12 forms, re-key 30 questions, recompute 12 rollups
Section titled “{130.8} — Data migration: mint 12 forms, re-key 30 questions, recompute 12 rollups”TECH step 9. OWNS its own sequentially-numbered migration file (FIX 5).
Per-workspace INSERT form_templates with the T-B22 transforms (issuing_organisation←buyer,
deadline, submission_date, workflow_state←COALESCE(status,'draft'),
withdrawn→workflow_state='withdrawn'+outcome=NULL, {won,lost}→outcome+audit, default
form_type='bid'); UPDATE form_questions.form_template_id (30 rows); recompute_procurement_rollup
×12. domain_metadata per-engagement keys left in place (reversibility). Implements T-B22, T-B1
(read-shape prep). Deps: [5, 6, 7]. Note: no valid win-rate output exists between {130.7}
and the end of {130.8} (functions read empty ft.outcome) — defer win-rate validation to here.
{130.9} — Regenerate api.* views + types + overrides
Section titled “{130.9} — Regenerate api.* views + types + overrides”TECH steps 10–12. bun scripts/generate-api-views.ts for form_templates,
form_questions, procurement_workspaces, form_outcome_types; generate-api-views --check
green; regen database.types.ts (--schema public,api, never hand-edit); update
database-overrides.ts (new form_templates engagement shape + FormOutcomeSchema types + the
ProcurementMetadata override). RLS verification (TECH step 12: delegate-to-workspaces EXISTS
pattern; form_outcome_types public read-only CV; new fns REVOKE … FROM anon). Deps: [5,6,7,8].
Gates all of Wave 3.
{130.10} — Workflow enum single source of truth (de-dup) + /api/bids JSDoc in schemas.ts
Section titled “{130.10} — Workflow enum single source of truth (de-dup) + /api/bids JSDoc in schemas.ts”Delete VALID_BID_STATUSES (schemas.ts:2007-2018); rewrite
ProcurementMetadataSchema.status (1022-1033) and ProcurementListParamsSchema.status (2020-2021)
to z.enum(PROCUREMENT_WORKFLOW_STATES) imported direct from @/types/procurement (no barrel).
FIX 1 fold: also clears the stale /api/bids JSDoc site(s) in the SAME file (schemas.ts) —
{130.16} no longer touches schemas.ts. Guard test: grep for a 2nd inline copy returns zero.
Implements T-B11, AD-3, T-B24 (the schemas.ts JSDoc slice). OWNS lib/validation/schemas.ts.
Deps: [9].
{130.11} — Umbrella route re-anchored to forms: GET read-shape + PATCH transition + outcome write
Section titled “{130.11} — Umbrella route re-anchored to forms: GET read-shape + PATCH transition + outcome write”FIX 2: OWNS app/api/procurement/[id]/route.ts ENTIRELY (both the GET read-shape AND
the PATCH transition) + app/api/procurement/[id]/outcome/route.ts. GET read-shape (T-B1 API
half, ~lines 26–132): move from “read the single tender off domain_metadata” to “read the roll-up
off procurement_workspaces + list child forms off form_templates”. PATCH + outcome write
(T-B6/T-B8/T-B9/T-B10): target the form — write form_templates.workflow_state; on submitted set
submission_date=now(); on terminal set {outcome, outcome_recorded_at, outcome_recorded_by}
atomically, audit required-on-terminal; withdrawn→workflow_state not outcome;
sb()/tryQuery() + post-UPDATE row-count check; fire roll-up recompute. Do NOT change
canTransition’s signature. Dual-writer guard test. T-B1 read-shape is split: API GET here, UI
consumption in {130.13}. Implements T-B1 (API half), T-B6, T-B8, T-B9, T-B10. Deps: [9].
{130.12} — form_type infer-then-confirm picker (establishes the query-key namespace)
Section titled “{130.12} — form_type infer-then-confirm picker (establishes the query-key namespace)”Net-new picker in components/procurement/ (semantic tokens, WCAG label+selected-state,
UK labels). Pre-selects inferred type (reuse pipeline classification); records the confirmed
choice as form_templates.form_type; option list fetched at runtime from api.form_types
(filtered 'procurement'=ANY(applicable_application_types)) via TanStack Query; confirm-first (no
downstream keys on unconfirmed inference); no-document → no pre-selection. FIX 3: ESTABLISHES
the form-scoped query-key namespace in lib/query/query-keys.ts — add concrete sub-keys under the
existing procurement: object (line 328): forms, formTypes; {130.13}/{130.15} EXTEND this,
never rewrite. OWNS the picker component + lib/query/query-keys.ts. Implements T-B14, T-B16, T-B12
(runtime fetch). Deps: [9].
{130.13} — Procurement detail surface UI: form-list + roll-up READ render + back-nav
Section titled “{130.13} — Procurement detail surface UI: form-list + roll-up READ render + back-nav”FIX 2/3: OWNS ONLY app/procurement/[id]/page.tsx (+ its [id]/ page siblings) —
the UI form-list level, the roll-up READ rendering, and the back-nav copy. Does NOT touch the API
GET (that is {130.11}). Net-new form-list level (each form: form_type, workflow_state,
deadline, outcome); selecting a form opens its composer ({130.15}); renders the roll-up off the
{130.11} GET; single-form v1 case renders a one-item list. Back-nav “Back to Procurement” (T-B17 UI
half). EXTENDS the {130.12} query-keys.ts namespace (form-list/detail read keys), never rewrites
it. T-B1 read-shape: UI consumption here, API GET in {130.11}. Implements T-B1 (UI half), T-B15,
T-B19, T-B17 (back-nav). Deps: [9, 12].
{130.14} — List-page copy/nav fold + PJ-2..PJ-5 + Bid* mcp-apps types
Section titled “{130.14} — List-page copy/nav fold + PJ-2..PJ-5 + Bid* mcp-apps types”app/procurement/page.tsx — singular nav noun “Procurement” (PJ-1/T-B17 list-page
half), PJ-2 rephrase (:155), keep “bid-writing” craft term (PJ-3), “Past responses” card (PJ-4),
ProcurementListRow bid prop tidy; mcp-apps/{form-dashboard,reorient-me}/src/ Bid* types
(PJ-5 — retain where form_type-scoped, rename where umbrella). UK English. Implements T-B17
(list-page), T-B18. OWNS app/procurement/page.tsx + mcp-apps/{form-dashboard,reorient-me}/src/.
Deps: [9].
{130.15} — Composer + Q&A promotion re-anchored to the form
Section titled “{130.15} — Composer + Q&A promotion re-anchored to the form”FIX 4 (concrete OWNS): the composer surface =
components/procurement/question-review.tsx + question-list.tsx + question-navigator.tsx +
question-row.tsx + response-editor.tsx + response-actions.tsx (the per-question composer
cluster rendered from [id]/page.tsx); the promotion provenance write =
app/api/q-a-pairs/promote/route.ts + lib/q-a-pairs/promote-corpus.ts (the two files that write
source_workspace_id/source_form_response_id — they gain source_form_template_id); plus
lib/query/query-keys.ts (EXTEND only). Composer scoped to form_template_id (T-B4); match
candidates still corpus-level q_a_pairs (guardrail); query keys re-scope workspace→form
(extending the {130.12} namespace); promotion records q_a_pairs.source_form_template_id lineage
(corpus stays corpus-level); lost/withdrawn default skip-on-promotion. Implements T-B20, T-B21
(promotion half). Deps: [9, 11, 13].
{130.16} — Deferred ID-61 wire-field cleanup (subsumed)
Section titled “{130.16} — Deferred ID-61 wire-field cleanup (subsumed)”FIX 1: OWNS the wire/token surfaces ONLY — NO lib/validation/schemas.ts (its
/api/bids JSDoc moved to {130.10}). Clean the deferred ID-61 wire fields where they live:
active_bids/bid_summary 4th wire contract (lib/activity/bid-summary.ts + dashboard/reorient/
attention + mcp apps); --bid-* design tokens bl-349 (app/styles/domain-tokens.css); stale
/api/bids JSDoc in lib/queue/handlers/procurement-draft-all.ts; bl-370 persisted literals. (The
bid_outcome alias is cleaned in {130.7}.) Implements T-B24 (wire-field half). Deps: [9].
Boundary: wire/token cleanup only — no behaviour change; if a rename has blast radius run
gitnexus_rename not find-and-replace.
ID-45 timing gate (hard sequencing constraint)
Section titled “ID-45 timing gate (hard sequencing constraint)”The entire migration spine ({130.5}–{130.8}) + types regen ({130.9}) must land on prod BEFORE
ID-45 (first-client full-corpus re-ingest) GO (RESEARCH §8, confirmed against
themes/canonical-pipeline/reference/v1-completion-sequence.md). The pipeline writes the form
altitude; if ID-45 re-ingests before the form altitude exists, it cements the workspace-centric
model by default — the most expensive thing to undo post-launch. The Wave-3 code Subtasks
({130.10}–{130.16}) are buyer-facing UI/IA and may trail the migration, but the schema is the
gate. The Orchestrator holds this gate at Wave-4 (prod migration → ID-45 GO), NOT as a Subtask
dep (ID-45 is a separate Task — a sibling-only-dep escalation is NOT triggered; it is a release
sequencing constraint the Orchestrator enforces).
Subsumed ID-61 scope mapping
Section titled “Subsumed ID-61 scope mapping”| ID-61 deferred item | Folded into | Mechanism |
|---|---|---|
| Unit F UI-copy sweep (UNIT-F-SCOPE-MAP) | {130.13} + {130.14} | IA copy decisions (B-17/B-18), NOT a mechanical 62-site rename |
active_bids/bid_summary 4th wire contract | {130.16} | wire-field cleanup where touched |
bid_outcome alias in get_content_win_rate | {130.7} | retired in the win-rate body rewrite |
stale /api/bids JSDoc | {130.16} | doc cleanup |
--bid-* design tokens (bl-349) | {130.16} | token cleanup |
| persisted-literal classes (bl-370) | {130.16} | literal cleanup |
| 5 PJ flags (PJ-1..PJ-5) | {130.13} (PJ-1 back-nav) + {130.14} (PJ-1 list-noun, PJ-2..PJ-5) | IA/copy fold |
Out-of-Task follow-ups (expand/contract — target PRE-GO; NOT in-Task Subtasks)
Section titled “Out-of-Task follow-ups (expand/contract — target PRE-GO; NOT in-Task Subtasks)”These are tracked as out-of-Task expand/contract debt (TECH §Follow-ups), NOT decomposed into {130.x} Subtasks — surfaced here so the Orchestrator can route them to the Curator:
- Drop deprecated
workspaces.domain_metadataper-engagement keys (outcome,deadline,submission_date,status,outcome_recorded_*). Trigger: grep-zero readers acrossapp/+lib/+supabase/migrations/(excl. the deprecation migration) AND win-rate parity test green for one release. Target: PRE-GO (must not survive into first-client re-ingest). - Drop
form_questions.workspace_idonce all 34 TS + 7 SQL consumers filter onform_template_id; re-scopeUNIQUE(workspace_id, question_text)→UNIQUE(form_template_id, question_text)at the same time. - Rename
form_templates → form_instances(gitnexus_rename-driven, never find-and-replace — it is a misnamed INSTANCE table; the catalogue isform_template_requirements). - Remove unused
application_types.state_machine_configcolumn (dead infrastructure, AD-3).
Risks and mitigations (decomposition-level)
Section titled “Risks and mitigations (decomposition-level)”| Risk | Impact | Mitigation |
|---|---|---|
{130.7} 42P13 on RETURNS TABLE change | High | Explicit DROP-wrapper→DROP→CREATE→recreate ordering; staging-first |
| {130.7} win-rate parity unverifiable on live data | High | Synthetic-fixture parity test is the gate (live snapshot is vacuous) |
| {130.8} ↔ {130.7} transient no-valid-output window | Medium | Defer all win-rate validation to after {130.8}; documented in both briefs |
{130.13}/{130.14} shared app/procurement/ namespace collision | Medium | Split by exact file: [id]/page.tsx (13) vs page.tsx (14) — disjoint |
form_questions.workspace_id retained alongside FK | Medium | Additive-only this Task; per-form reads filter form_template_id; drop deferred |
| ID-45 re-ingest before schema lands | High | Wave-4 gate: prod migration precedes ID-45 GO (Orchestrator-enforced) |
Ratification record (S418 — Liam via Lavish, 2026-06-25)
Section titled “Ratification record (S418 — Liam via Lavish, 2026-06-25)”- RATIFIED — proceed: Subtasks added, commit PLAN, begin the migration spine.
- Priorities: NOT set (Liam). No
must/shoulddistinction — all Subtasks are worked through; there is time before the ID-45 re-ingest, and deferring “cleanup” Subtasks only creates confusion in a multi-agent dispatch config. - {130.14}
Bid*rename: implementer decides per-symbol at build time (TECH T-B18 left this open; ratified to the Executor’s per-symbol judgment). - Wave-3 grouping: accept the post-fix sub-wave plan — 3a ({130.10}/{130.11}/{130.12}/{130.14}/{130.16}) parallel on disjoint files; {130.13} then {130.15} serial on the shared query-key file.
End of PLAN — RATIFIED (S418). On ratification, the Orchestrator
adds {130.5}–{130.16} via bun scripts/ledger-cli.ts add-subtasks 130 --file - and dispatches
Wave 1 (the serial migration spine) first.