Skip to content

ID-130 {130.2} PRODUCT — 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 workspaces umbrella holding many forms, procurement_workspaces roll-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-145specs/id-145-procurement-form-first/RESEARCH.md. Do not cite the umbrella invariants in new specs.

PRODUCT — Procurement / Form Domain Model + Information Architecture

Section titled “PRODUCT — Procurement / Form Domain Model + Information Architecture”

Status: RATIFIED — 25/06/2026 (S411, Lavish gate). Liam ratified the C/Hybrid model (P1) and locked three metric/vocab decisions: win-rate denominator EXCLUDES SQ/PSQ-stage losses (only engagements that reached final award count; shortlist pass-rate is a separate metric — P2); a failed SQ/PSQ shortlist resolves the engagement to “lost” (P3); and the pqq form_type key is RENAMED pqqpsq (not label-only — TECH runs the CV migration — P4). ID-130 spec chain: {130.1} RESEARCH ✅ → {130.2} PRODUCT ✅ → {130.3} TECH ⟵ next → {130.4} PLAN. Authored S412 (25/06/2026) by a fresh Planner against the ratified RESEARCH (D1–D8, §6.0) and live Platform-DB verification (zjqbrdctesqvouboziae, 25/06/2026). A 4-lens fidelity review (S411) verified every invariant against the ratified decisions + live code/DB — no fabricated invariants, all guardrails held — and its fixes are applied (outcome-is-a-separate-axis clarified; the B-7 roll-up derivation pinned; one code-orientation wording correction). UK English throughout.

Owning theme: Roadmap theme 2 — Procurement (bid drafting), the platform’s proof-of-value capability.

What this spec decides: the altitude of the procurement domain model — which entity owns per-stage buyer / deadline / outcome / workflow, the workspace↔form cardinality, the form_type closed list and its UI surfacing, and the information architecture (nav noun, copy, multi-form navigation) that follows. The companion TECH.md ({130.3}) owns the SQL migration, the satellite’s fate (D6), and the workflow config-vs-hardcode call.

Behaviour invariants are numbered globally (B-1, B-2, …) so TECH.md references each by ID and maps a Proposed change onto it. Each invariant carries a [RATIFIED-…] / [DERIVED-…] / [DEFERRED-…] tag plus a source citation back to RESEARCH §6.0 (the ratified decision register) or the DB-verified schema (RESEARCH §2.2). This is a domain-model + IA spec; visual-design detail is light, but the IA / copy invariants (§D) carry the user-facing surface and reference the Warm Meridian system where a state would otherwise be ambiguous.

Figma: none provided — this Task is a domain-model + IA change; the form-type picker (B-16) is the only net-new visual affordance and its shape is specified in prose. A mock may be commissioned at TECH/implementation time; its absence here is deliberate, not an omission.

The ID-61 program renamed “bid” → “procurement” cleanly but exposed a latent confusion: the platform cannot distinguish a procurement (the umbrella engagement a client runs) from a form (the individual PSQ / ITT / tender artifact with its own questions, deadline, and win/lose outcome). Today, in live code, a procurement IS a workspaces row and one workspace can physically represent only one tender — its single buyer / deadline / outcome / workflow-state live in workspaces.domain_metadata JSONB (RESEARCH §2.2). Yet the ratified S391 ontology (platform-direction.md) and every piece of ingestion / AI infrastructure shipped in the last two months model the form as the unit. RESEARCH ratified the resolution as D1 = Candidate C (Hybrid): the workspace becomes an umbrella holding many forms, per-stage facts move to the form, and the workspace keeps a roll-up. This spec encodes that model and the IA that surfaces it, before the ID-45 first-client re-ingest pours real content into whichever altitude is live (RESEARCH §8).

Code-intelligence orientation (gitnexus, repo canonical, 25/06/2026 — cited verbatim so TECH and the implementer can navigate without re-running the step):

  • The umbrella + per-engagement surface lives at app/api/procurement/[id]/route.ts (GET 26–132, DELETE 257–400), the workflow-transition write at app/api/procurement/[id]/outcome/route.ts:POST (19–200), and the list surface at app/procurement/page.tsx (ProcurementListRow 337–382) — gitnexus flows proc_94_post / proc_90_post, module [id]. These are the workspace-altitude surfaces that B-1..B-9 re-anchor.
  • The workflow state machine is code-resident, not on a workspace column: lib/domains/procurement/procurement-workflow.ts exports canTransition (73), getAvailableTransitions (80), isTerminal (86), PROCUREMENT_WORKFLOW_PROGRESSION (95); the transition table itself is the module-private const VALID_TRANSITIONS (57). The ProcurementWorkflowState type is at types/procurement.ts. This confirms the substrate placement that D1’s hybrid model relies on (RESEARCH §4.4) — the state machine is already at a re-homeable altitude.
  • The form altitude is live in the read path: lib/activity/team-changes.ts (formResponseRowToTeamChange 41–67, formResponseRowToRecentWork 90–120, module Activity) already projects form_response rows, and effectiveness reads form-keyed citations at app/api/items/[id]/effectiveness/route.ts (ProcurementCitation 13–19). The AI/matching half already keys on the form — the form altitude is the lower-friction landing (RESEARCH §9 guardrail).
  • form_type surfaces nowhere in the UI today — a grep for form_type across components/procurement/ and app/procurement/ returns zero hits, confirming D7 (the infer + confirmable picker, B-16) is net-new IA, not a relocation.
  • The 10-state enum is double-written: ProcurementMetadataSchema inline at lib/validation/schemas.ts:1022-1033 AND the standalone VALID_BID_STATUSES const at lib/validation/schemas.ts:2007-2018 — a de-dup hazard B-12 flags for TECH.

Section A — Entity model (the ratified altitude)

Section titled “Section A — Entity model (the ratified altitude)”

The “user” of this section is the code that reads and writes the procurement domain model (API routes, the workflow module, the matching stack, the UI’s data layer), plus the human running an engagement who relies on the model holding stage facts correctly.

B-1 — A procurement is a workspace umbrella that holds many forms

Section titled “B-1 — A procurement is a workspace umbrella that holds many forms”

A “procurement” IS a workspaces row with application_types.key = 'procurement' (DB-verified: the existing live procurement workspaces — 11 at RESEARCH time, RESEARCH §2.2), acting as an umbrella that may hold MANY forms. The workspace itself carries NO per-stage buyer / deadline / outcome / workflow-state; it carries only identity (name, description, owner) and a roll-up (B-7). One opportunity flows through several forms under one procurement (e.g. PSQ → ITT → tender in a UK restricted procedure or Competitive Flexible Procedure). The model MUST NOT constrain a workspace to a single form.

[RATIFIED-D1] (RESEARCH §6.0 — Candidate C / Hybrid) + [RATIFIED-D2] (many forms per engagement, research-confirmed). Source: RESEARCH §2.2 (live workspace count), §4.4 (Candidate C). Guardrail: no tenant_id; the database is the tenant boundary (platform-direction.md).

B-2 — The form is the entity that owns per-stage buyer, deadline, outcome, and workflow state

Section titled “B-2 — The form is the entity that owns per-stage buyer, deadline, outcome, and workflow state”

Each individual PSQ / SQ / PSQ-stage / ITT / tender / RFP artifact is a form that owns its own per-stage facts: buyer (issuing organisation), deadline (per-stage submission deadline), submission_date (when that stage was actually submitted), outcome (that stage’s result), and its position in the workflow state machine (B-10). Two forms under the same procurement may sit at different workflow states, have different deadlines, and carry different outcomes simultaneously.

The form-template entity already carries form_type, deadline, issuing_organisation, evaluation_methodology, status, status_reason as first-class columns (DB-verified, form_templates, RESEARCH §2.2) but has no outcome and no submission_date — the model MUST add an outcome/submission home at the form/engagement altitude. Whether that home is the form_templates row itself, the (empty) procurement_workspaces satellite repurposed per-form, or a new per-form engagement row is a storage question deferred to TECH (D6); PRODUCT mandates only that the form is the owning altitude.

[RATIFIED-D1] + [RATIFIED-D5] (deadline on the form). Source: RESEARCH §6.0, §2.2 (form_templates columns + the missing outcome/submission_date), §4.2 (the per-form fields the pipeline already writes). The straw-man’s B-3 6-column satellite (buyer/deadline/submission/ outcome on procurement_workspaces) is SUPERSEDED — those fields move to the form.

B-3 — Workspace↔form cardinality is 1:many; deleting a workspace cascades to its forms

Section titled “B-3 — Workspace↔form cardinality is 1:many; deleting a workspace cascades to its forms”

A procurement workspace has zero-or-more forms (1:many). A form belongs to exactly one procurement workspace. Deleting a workspace removes its forms (and their questions/responses); deleting a single form leaves the workspace and its sibling forms intact. A workspace with one form is the common v1 case but is a data instance, not a schema constraint (contrast the straw-man B-4’s UNIQUE(workspace_id) 1:1 satellite, SUPERSEDED by D1).

[RATIFIED-D1] + [RATIFIED-D2]. Source: RESEARCH §6.0 (1:many is the ratified cardinality), §4.4.

B-4 — form_questions are re-keyed to a form id, not to the workspace

Section titled “B-4 — form_questions are re-keyed to a form id, not to the workspace”

Today form_questions (30 live rows, DB-verified) are keyed to workspace_id with NO form_template_id FK (RESEARCH §2.2, §5 drift #6) — so questions cannot be partitioned per form within a multi-form workspace. The model requires each question to belong to a specific form (via a form id), so that a PSQ’s questions are distinct from the same workspace’s ITT questions. From the consumer’s perspective: retrieving “the questions for this form” returns only that form’s questions, never a sibling form’s. The migration that re-keys the 30 live rows is a TECH concern; PRODUCT mandates the per-form partitioning as the invariant.

[RATIFIED-D1] (a consequence of the form altitude). Source: RESEARCH §2.2 (form_questions keyed to workspace_id, no FK), §5 drift #6, §9 handoff item 1. Guardrail: q_a_pairs stay corpus-level — this re-keying applies ONLY to form_questions, never to the shared Q&A corpus.

B-5 — outcome is multi-valued: per-form (per-stage) plus a workspace roll-up

Section titled “B-5 — outcome is multi-valued: per-form (per-stage) plus a workspace roll-up”

Because each stage is independently scored — an SQ/PSQ carries a pass / shortlist outcome while an ITT/tender carries the won / lost outcome — outcome is effectively multi-valued. Each form holds its own stage outcome; the workspace holds a derived overall outcome (B-7). The per-form outcome vocabulary is NOT a single closed enum across all form types: a shortlist stage and a final-award stage have different outcome value sets. PRODUCT mandates that the model represent a per-form outcome whose permissible values are appropriate to that form’s stage; TECH owns the exact value sets and their storage.

Crucially, outcome is a SEPARATE AXIS from workflow STATE (B-8): this Task does NOT expand the 10-state workflow or its {won, lost, withdrawn} terminal set — the per-form outcome is a distinct field, and TECH reconciles its stage-appropriate value sets against the existing outcome enum (schemas.ts:1040). A form’s workflow state says where it is in its lifecycle; its outcome says how that stage resolved — they are not the same field.

[DERIVED-D1+D2] (RESEARCH §6.0 “Derived modelling note” — multi-valued outcome). Source: RESEARCH §6.0 derived note, §1 (the per-engagement facts enumerated). The straw-man B-7’s single (won|lost|withdrawn) CHECK on the workspace satellite is SUPERSEDED.

B-6 — Per-form outcome captures audit provenance

Section titled “B-6 — Per-form outcome captures audit provenance”

When a user records a terminal outcome on a form, the platform captures who recorded it and when, alongside the outcome value, as a side-effect of the recording action (not a separately typed field the user fills). This preserves the existing outcome_recorded_at / outcome_recorded_by audit semantics (ProcurementMetadataSchema, lib/validation/schemas.ts:1043-1044) — carried forward verbatim but now at the form altitude. The straw-man B-7 audit behaviour is lifted and re-anchored from the workspace satellite to the form.

[RATIFIED-D1] (re-anchored straw-man B-7). Source: RESEARCH §2.2 (ProcurementMetadataSchema audit fields), straw-man B-7.

B-7 — The workspace exposes a roll-up derived from its forms

Section titled “B-7 — The workspace exposes a roll-up derived from its forms”

The procurement workspace surfaces a roll-up computed from its forms, comprising at minimum: (i) the nearest upcoming deadline across all non-terminal forms (the single deadline the buyer-facing list shows per procurement); (ii) an overall outcome for the engagement; and (iii) the win-rate denominator (whether this engagement counts toward win-rate, and how). The overall-outcome derivation rule (PRODUCT-proposed — Liam ratifies): the final-award-stage form is the form whose form_type ∈ {itt, tender}, selected as the latest such form by deadline (tie-broken by created_at). An engagement is won when its final-award-stage form is won; lost when that form is lost or the engagement is withdrawn; in-progress while no terminal final-stage outcome exists. Nearest-deadline = the earliest deadline across all non-terminal forms; when every form is terminal (or there are no forms yet), no deadline is shown.

Two embedded rules set user-facing metric semantics and need Liam’s conscious blessing (surfaced at the gate, not buried):

  • (a) Shortlist-failure ⇒ lost [RATIFIED-S411/P3]: failing the SQ/PSQ shortlist resolves the overall engagement outcome to lost — the engagement cannot proceed.
  • (b) Win-rate denominator [RATIFIED-S411/P2]: the win-rate denominator counts only engagements that reached final award (a form of form_type ∈ {itt, tender}); an engagement lost at the SQ/PSQ shortlist stage is EXCLUDED from win-rate and is instead tracked as a separate shortlist pass-rate metric. (So “shortlist-failure ⇒ overall lost” per (a) does NOT add that engagement to the win-rate denominator.)

The roll-up is read-only and derived — users never edit the roll-up directly; they record per-form facts (B-2, B-6) and the roll-up follows.

This invariant states the roll-up REQUIREMENT only. Whether the roll-up is materialised (a column/satellite the pipeline maintains) or computed on read is a TECH/storage decision (D6).

[RATIFIED-D1] (workspace roll-up) + [DERIVED-D1+D2] (overall-outcome derivation). Source: RESEARCH §6.0 (D1 roll-up: overall outcome, nearest deadline, win-rate denominator), derived note. The win-rate engine today reads workspace-level outcome (RESEARCH §2.3); the roll-up preserves that read surface.

B-8 — The 10-state workflow lives on the form/engagement, per stage

Section titled “B-8 — The 10-state workflow lives on the form/engagement, per stage”

The procurement lifecycle runs the existing 10-state workflow — draft, questions_extracted, matching, drafting, in_review, ready_for_export, submitted, and the terminal won / lost / withdrawn — but the state is now a property of the form (per stage), not the workspace. Each form advances through the workflow independently; a procurement with a submitted ITT and a drafting tender shows each form at its own state. The state values, allowed transitions, and terminal set are preserved verbatim from the live machine at lib/domains/procurement/procurement-workflow.ts (VALID_TRANSITIONS, isTerminal, PROCUREMENT_WORKFLOW_PROGRESSION); this Task moves the state’s home to the form, it does NOT add, remove, or re-order states.

[RATIFIED-D1] (state machine home = the form, RESEARCH §4.4 substrate placement) + [DERIVED] (verbatim state preservation). Source: RESEARCH §3 (state-machine HOME row), §6.0 D1, live procurement-workflow.ts.

B-9 — Submitting a form records its submission date; recording a terminal outcome captures audit columns

Section titled “B-9 — Submitting a form records its submission date; recording a terminal outcome captures audit columns”

When a form transitions to submitted, the platform sets that form’s submission_date to the transition timestamp (server clock) — the user does not type it. When a form transitions to a terminal state, the platform records that form’s outcome, outcome_recorded_at, and outcome_recorded_by atomically with the state write — and outcome_recorded_at / outcome_recorded_by become REQUIRED at the form altitude on terminal recording (they are .optional() today at schemas.ts:1043-1044; the model tightens this, which TECH applies). The outcome is recorded as a separate field drawn from the stage-appropriate vocabulary (B-5), NOT necessarily equal to the workflow state value. The storage home for these form-level outcome / submission_date fields is the same TECH/D6 decision flagged in B-2 (they do not exist on a form table today). These are the straw-man B-6 + B-7 behaviours re-anchored to the form.

[RATIFIED-D1] (re-anchored straw-man B-6 + B-7). Source: straw-man B-6/B-7, RESEARCH §2.2.

B-10 — Workflow transitions are validated against the live transition table

Section titled “B-10 — Workflow transitions are validated against the live transition table”

A form may only move between states permitted by the transition table; an attempt to make an illegal transition is rejected (the form stays in its current state and the caller is told the transition is invalid). The available next-states for a form are derivable so the UI can offer only legal transitions (getAvailableTransitions). This preserves the live canTransition / VALID_TRANSITIONS behaviour verbatim, now keyed per form.

[RATIFIED-D1] (verbatim transition behaviour). Source: lib/domains/procurement/procurement-workflow.ts:57,73,80.

B-11 — The workflow enum has a single source of truth

Section titled “B-11 — The workflow enum has a single source of truth”

The 10-state enum is currently written twice — inline in ProcurementMetadataSchema (lib/validation/schemas.ts:1022-1033) and as the standalone VALID_BID_STATUSES const (lib/validation/schemas.ts:2007-2018) — a refactor hazard. The model requires a single canonical source for the state set, consumed by validation, the API list params, and the workflow module; the duplicate must be de-duplicated as part of this Task. Whether the canonical source is the procurement-workflow.ts module export, a config row (application_types.state_machine_config, which exists but is unused — RESEARCH §5 drift #9), or a single const is a TECH decision; PRODUCT mandates only that the double-write is removed.

[RATIFIED-D1] (de-dup is in-scope per RESEARCH §9 handoff). Source: RESEARCH §5 drift #3, drift #9. Open question (for TECH): does the workflow become config-driven from application_types.state_machine_config, or stay code-resident in procurement-workflow.ts? PRODUCT does not force this; it forces only the single-source-of-truth invariant.

Section C — form_type closed list + surfacing

Section titled “Section C — form_type closed list + surfacing”

B-12 — The canonical procurement form_type closed list is the 7 procurement-applicable live keys

Section titled “B-12 — The canonical procurement form_type closed list is the 7 procurement-applicable live keys”

The closed list of procurement form_type values is the 7 procurement-applicable keys from the live form_types table (DB-verified, 25/06/2026): bid, checklist, itt, pqq, questionnaire, rfp, tender. The 8th live key, sales_proposal_template, is NOT procurement-applicable and is excluded. The straw-man’s framework / dps / gcloud keys do NOT exist in the live table and are NOT re-added (RESEARCH §5 drift #7). “bid” remains a form_type, not an application (guardrail). (checklist and questionnaire are multi-application form_types that include procurement; the other 5 of the 7 are procurement-exclusive.) Per the ratified B-13 / P4 key rename, the pqq key becomes psq in the canonical list.

[RATIFIED-D4] (align to the live set as baseline) + [DERIVED] (the 7 procurement-applicable keys). Source: RESEARCH §2.2 (live form_types 8 rows), §6.0 D4, live DB query (applicable_application_types).

B-13 — pqq is reviewed against Procurement Act 2023 SQ/PSQ terminology

Section titled “B-13 — pqq is reviewed against Procurement Act 2023 SQ/PSQ terminology”

The Procurement Act 2023 (live February 2025) renamed the pre-qualification stage: PQQ → SQ (Selection Questionnaire) → PSQ. The live form_types row keys this stage as pqq with label “PQQ (Pre-Qualification Questionnaire)” (DB-verified). PRODUCT mandates that the user-facing label for this stage align to current UK procurement terminology (SQ / PSQ as appropriate); the stored key may remain pqq for migration stability OR be renamed to sq/psq — a key rename is a controlled-vocabulary migration TECH must plan and flag. The other live keys (itt, tender, bid, rfp) are confirmed correct and unchanged.

[RATIFIED-D4 + S411/P4]. Source: RESEARCH §6.0 D4, §2.2 (live label). RATIFIED: the stored form_type key is renamed pqqpsq (the Procurement Act 2023 Procurement Specific Questionnaire; TECH confirms psq vs sq during the CV migration) and the user-facing label aligns to SQ/PSQ. This is a small, contained CV migration (verified: form_type is NOT an MCP tool arg — no form_type in lib/mcp; the pqq key appears only in two internal code sites, lib/coverage/gap-scoring.ts:28 + components/coverage/template-coverage-content.tsx:115, plus the DB CV with 0 form rows). TECH ({130.3}) owns the migration; the other keys (itt/tender/bid/rfp/checklist/questionnaire) are unchanged.

B-14 — Each form carries exactly one form_type from the closed list

Section titled “B-14 — Each form carries exactly one form_type from the closed list”

Every form in a procurement workspace carries exactly one form_type value drawn from the B-12 closed list. The form_type is required (a form cannot exist without a type once confirmed, B-16), governs which workflow/outcome vocabulary applies (B-5), and drives the matching stack’s form-type-aware retrieval (the AI/matching half already keys on form_questions + form_types — RESEARCH §9 guardrail).

[RATIFIED-D4]. Source: RESEARCH §2.2 (matching keys on form_types), §6.0.

B-15 — A single procurement may contain forms of different types over its lifetime

Section titled “B-15 — A single procurement may contain forms of different types over its lifetime”

One procurement workspace may hold a pqq/sq form, then an itt form, then a tender form as the opportunity progresses (B-1, B-2). The form-type of one form does not constrain its siblings. Listing the forms in a procurement shows each form’s type so the user can see the engagement’s stage composition at a glance.

[RATIFIED-D2] (many forms, mixed types per engagement). Source: RESEARCH §6.0 D2, §4.4.

B-16 — form_type is surfaced via infer-then-confirm: the platform classifies, the user confirms or overrides

Section titled “B-16 — form_type is surfaced via infer-then-confirm: the platform classifies, the user confirms or overrides”

Today no form_type is surfaced anywhere in the procurement UI (gitnexus orientation: zero hits). This Task introduces a net-new surface where, on uploading/creating a form, the platform infers the form_type by classifying the uploaded document, and presents the inferred type in a confirmable picker pre-selected to the inferred value. The user can accept the inference (one click) or override it by selecting any value from the B-12 closed list. The inference is presented as a normal pre-filled field, not as a flagged “AI guess” — consistent with the AI-invisible-infrastructure policy (ai-visibility-policy.md): the platform behaves as if the type were always known, and the user simply confirms. The picker:

  • Pre-selects the inferred form_type and shows all 7 procurement-applicable options.
  • Shows each option by its human label (e.g. “ITT (Invitation To Tender)”, “PQQ / Selection Questionnaire”) — the labels per the B-13 terminology review, not the raw key.
  • Is confirmable in a single action when the inference is correct (the common path).
  • Records the user’s confirmed/overridden choice as the form’s form_type (B-14); the choice, not the raw inference, is authoritative.
  • Where inference is uncertain or unavailable (no document, classification low-confidence), defaults to no pre-selection and requires the user to pick before the form is created — never silently assigns a type.
  • Confirm-first: no downstream behaviour (match pre-fetch, extraction policy, etc.) keys on an inferred-but-unconfirmed form_type — entailed by “the choice, not the raw inference, is authoritative”. Any speculative pre-fetch on the raw inference is a TECH performance optimisation, not a behaviour the model depends on.

WCAG: the picker conveys selection by more than colour (label + selected-state indicator); UK English labels throughout.

[RATIFIED-D7] (infer + confirmable picker). Source: RESEARCH §6.0 D7, §2.3 (no form_type in UI today), ai-visibility-policy.md. Confirm-first is now stated in the invariant body above — resolved, not an open question (any pre-fetch on the raw inference is a TECH latency note).

Section D — Information architecture + copy

Section titled “Section D — Information architecture + copy”

B-17 — The navigation noun for the umbrella is the singular “Procurement”

Section titled “B-17 — The navigation noun for the umbrella is the singular “Procurement””

The primary navigation noun for the umbrella entity is the singular “Procurement” (not “Procurements”, not “Bids”). Back-navigation reads “Back to Procurement”. This resolves ID-61 flag PJ-1 (the Bids / Back to BidsProcurement decision) as an IA decision, not a mechanical find-and-replace. The noun is singular because “Procurement” reads as the activity / area (like “Settings”), under which individual procurements and their forms are listed.

[RATIFIED-D8 / PJ-1]. Source: RESEARCH §6.0 D8 PJ-1, §1 (“nav noun SINGULAR”). Landing point: app/procurement/page.tsx (ProcurementListRow), nav surfaces.

B-18 — Persona and helper copy is rephrased to the procurement IA, keeping craft terms where natural

Section titled “B-18 — Persona and helper copy is rephrased to the procurement IA, keeping craft terms where natural”

The five ID-61 PJ flags are folded into the IA as copy decisions:

  • PJ-2 — “Manage bid submissions and tender responses” (page.tsx:155) is rephrased to describe managing procurement engagements and their forms (e.g. “Manage your procurement engagements and the forms within them”) — accurate to the umbrella↔form model, not the old 1-workspace-1-tender framing.
  • PJ-3 — the craft term “bid-writing” is KEPT where it reads naturally (it is the industry term of art for the activity); it is NOT mechanically swapped to “procurement-writing”. The umbrella noun is “Procurement” (B-17); the activity may remain “bid-writing” / “bid response”.
  • PJ-4 — the “Past bid responses” persona card is rephrased to “Past responses”, pointing at form responses (B-2/B-4); “bid responses” remains acceptable as a craft term per PJ-3 where it reads naturally.
  • PJ-5 — the mcp-apps-local Bid* type names (BidSummary, BidDashboardData, ExpandedBidState, BidBriefing, bidId) are folded into this IA’s vocabulary; whether they rename to Form*/Procurement* or retain Bid* as a form_type-scoped name is a TECH naming decision, recorded here as in-scope for the IA, not a separate sweep.

Copy uses UK English throughout.

[RATIFIED-D8 / PJ-2..PJ-5]. Source: RESEARCH §6.0 D8, §1, §5 drift #1. The IA folds these per D3 (HOLD ID-61 Unit F — its copy folds in here, not a standalone sweep).

B-19 — A procurement detail surface lists its forms; the user navigates form-by-form

Section titled “B-19 — A procurement detail surface lists its forms; the user navigates form-by-form”

The procurement detail surface lists the engagement’s forms, each showing its form_type (B-15), workflow state (B-8), deadline (B-2), and outcome (B-5) where set. Selecting a form opens that form’s working surface (the composer, B-20). The umbrella surface shows the roll-up (B-7: nearest deadline, overall outcome); the form surface shows that form’s per-stage facts. For a single-form procurement (the common v1 case) the IA still presents one form in the list — the user is never blocked from later adding a second form. This is net-new multi-form navigation; the live UI today has no form-list level (it shows one tender per workspace).

[RATIFIED-D1+D2] (multi-form navigation). Source: RESEARCH §6.0, §9 handoff item 2, §2.3 (no multi-form level today).

B-20 — The composer surface re-anchors to the form

Section titled “B-20 — The composer surface re-anchors to the form”

The composer — where the user composes responses to extracted questions, drawing ranked candidates from the corpus-level q_a_pairs via the matching substrate — renders inside a form’s question-review surface (re-anchored from the straw-man B-8’s workspace-level question-review). The composer is a per-form surface coupled to that form’s workflow state; it is not a standalone route. Per form, the composer renders each question with its ranked match candidates, a preview affordance (two-step list/preview → get/verbatim), an accept affordance, and a confidence-posture indicator — preserving the straw-man B-9..B-11 composer behaviour, now scoped to the form’s questions (B-4) rather than the workspace’s.

[RATIFIED-D1] (re-anchored straw-man B-8..B-11). Source: straw-man B-8..B-11 (composer surface, two-step retrieval, confidence posture), re-anchored to the form altitude. Guardrail: match candidates come from the corpus-level q_a_pairs (no workspace/form partition of the corpus); only the questions are form-scoped.

B-21 — Q&A .docx import and bid-response → Q&A promotion (UC5) re-anchor to the form, corpus unchanged

Section titled “B-21 — Q&A .docx import and bid-response → Q&A promotion (UC5) re-anchor to the form, corpus unchanged”

The Q&A .docx import surface (straw-man B-13..B-16) and the bid-response → Q&A promotion path (UC5, straw-man B-17..B-18) are preserved and re-anchored: imports still land in the corpus-level Q&A library (NOT scoped to a workspace or form — straw-man B-16 guardrail retained verbatim), and promotion still derives a corpus q_a_pair from a shipped response. The only re-anchoring is that the response being promoted, and the question it answered, now live at the form altitude (B-2, B-4) rather than the workspace altitude; lineage provenance (source_form_response_id / source_question_id) already exists as corpus-level nullable provenance; the only NET-NEW addition is associating that lineage with a form_template_id, so the corpus pair records both the workspace and the originating form. Lost/withdrawn responses still default to skip-on-promotion (straw-man B-18). Promotion UI shape detail remains deferred to its own spec (straw-man B-19).

[RATIFIED-D1] (re-anchored straw-man B-13..B-19). Source: straw-man B-13..B-19, RESEARCH §9 guardrail (q_a_pairs corpus-level). Guardrail: q_a_pairs stay corpus-level — no workspace/form partition; relevance is scope_tag overlap at query time.

Section E — Migration shape (product altitude)

Section titled “Section E — Migration shape (product altitude)”

B-22 — The data migration is near-trivial; the model change is the work

Section titled “B-22 — The data migration is near-trivial; the model change is the work”

The schema/model change is substantial but the data migration is near-trivial because the form altitude is schema-present, data-empty: form_templates = 0 rows and procurement_workspaces = 0 rows (DB-verified, RESEARCH §2.2). The work is moving the model, not moving rows. Specifically, at product altitude:

  • The per-engagement facts currently in workspaces.domain_metadata JSONB (buyer, deadline, submission_date, outcome, status, audit) for the existing live procurement workspaces move to the form/engagement entity (B-2). The migration mints one form per existing workspace (today form_templates = 0 rows, so there are zero forms yet); each workspace’s domain_metadata lifts onto that newly-minted form — a chosen 1:1 shape for the existing workspaces, with the model now permitting many.
  • The 30 live form_questions rows re-key from workspace_id to a form id (B-4). With one form per workspace, every existing question maps to that workspace’s single form.
  • The deadline duplication (workspaces.domain_metadata->>'deadline' vs form_templates.deadline, RESEARCH §5 drift #5) is resolved in favour of the form (D5): the form’s deadline becomes canonical; the workspace shows the nearest as a roll-up (B-7). No field is read from two altitudes after migration.

The exact SQL, FK shape, RLS policies, and the order of operations are TECH ({130.3}) concerns; PRODUCT states the shape and the canonicalisation rules only.

[RATIFIED-D1+D5] + [DERIVED] (near-trivial data migration from the 0-row landing). Source: RESEARCH §2.2 (row counts), §8 (ID-45 timing — land before re-ingest), §6.0 D5.

B-23 — The empty procurement_workspaces satellite’s fate is deferred to TECH

Section titled “B-23 — The empty procurement_workspaces satellite’s fate is deferred to TECH”

The procurement_workspaces satellite exists but holds 0 rows and only id, workspace_id, created_at, updated_at (DB-verified). Its fate — activate it as the form/engagement extension point (the roll-up store), repurpose it, or drop it entirely — is a storage/implementation decision deferred to TECH ({130.3}). PRODUCT states only the roll-up requirement (B-7), not its storage home.

[DEFERRED-D6 → TECH]. Source: RESEARCH §6.0 D6, §2.2 (satellite 0 rows).

B-24 — ID-130 subsumes ID-61 Unit F and the deferred ID-61 wire-field cleanup

Section titled “B-24 — ID-130 subsumes ID-61 Unit F and the deferred ID-61 wire-field cleanup”

This Task subsumes the ID-61 Unit F UI-copy sweep (the {61.12} 62-copy-site / 13 lockstep-pair sweep, mapped in specs/id-61-bid-rename/UNIT-F-SCOPE-MAP.md) — held per D3 and folded into the ratified IA (B-17, B-18), NOT executed as a mechanical rename. It also subsumes the deferred ID-61 wire-field cleanup: the active_bids / bid_summary wire fields, the bid_outcome alias in get_content_win_rate, the stale /api/bids JSDoc, the --bid-* design tokens (bl-349), and persisted-literal classes (bl-370) per the ID-61 KEEP/defer register — these are cleaned up as part of the IA/model change where they touch the surfaces above, not as a separate program.

[RATIFIED-D3 / D8]. Source: RESEARCH §7 (scope — what ID-130 subsumes), §6.0 D3.

Explicitly NOT in this Task: the SQL migration specifics, the satellite’s storage fate, and the workflow config-vs-hardcode call (all TECH {130.3}); per-method scoring tunability (embedding vs fulltext blend, UI score presentation — deferred per straw-man B-9); the promotion UI shape detail (deferred per straw-man B-19); the q_a_pair corpus model (corpus-level, settled — a guardrail, not work here); the cocoindex pipeline standup (ID-127); the question_matches scoring internals (ID-57); and any introduction of a tenant_id column (the database is the tenant boundary — guardrail).

[RATIFIED] (scope boundary). Source: RESEARCH §7, §9 guardrails.

Guardrails (must hold across all invariants)

Section titled “Guardrails (must hold across all invariants)”

These are not new invariants but constraints every invariant above is written to respect, called out so TECH and the implementer inherit them:

  1. q_a_pairs stay corpus-level — no workspace or form partition; workspace/form relevance is computed at query time via scope_tag overlap. Only form_questions (B-4) re-key to the form; the corpus does not (B-20, B-21).
  2. “bid” is a form_type, not an applicationbid survives only as a B-12 closed-list value; the umbrella is “procurement” (B-1, B-17).
  3. No tenant_id — the database is the only cross-tenant boundary; no invariant introduces a tenant scoping column (B-1, B-25).
  4. The AI/matching stack already keys on form_questions + form_types — the form altitude is the lower-friction landing for the AI half; B-4 and B-14 align the model to where the AI stack already is (RESEARCH §9).

End of PRODUCT — awaiting Liam ratification. On ratification, a fresh Planner authors {130.3} TECH.md against these invariants (one-to-one Proposed-change mapping), resolving the storage/SQL questions PRODUCT deferred (D6, B-11 config-vs-code, B-13 key-rename, B-23 satellite fate).