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
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.
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 thepqqform_type key is RENAMEDpqq→psq(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_typeclosed list and its UI surfacing, and the information architecture (nav noun, copy, multi-form navigation) that follows. The companionTECH.md({130.3}) owns the SQL migration, the satellite’s fate (D6), and the workflow config-vs-hardcode call.
How to use this doc
Section titled “How to use this doc”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.
Problem
Section titled “Problem”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 atapp/api/procurement/[id]/outcome/route.ts:POST(19–200), and the list surface atapp/procurement/page.tsx(ProcurementListRow337–382) — gitnexus flowsproc_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.tsexportscanTransition(73),getAvailableTransitions(80),isTerminal(86),PROCUREMENT_WORKFLOW_PROGRESSION(95); the transition table itself is the module-private constVALID_TRANSITIONS(57). TheProcurementWorkflowStatetype is attypes/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(formResponseRowToTeamChange41–67,formResponseRowToRecentWork90–120, moduleActivity) already projectsform_responserows, and effectiveness reads form-keyed citations atapp/api/items/[id]/effectiveness/route.ts(ProcurementCitation13–19). The AI/matching half already keys on the form — the form altitude is the lower-friction landing (RESEARCH §9 guardrail). form_typesurfaces nowhere in the UI today — agrepforform_typeacrosscomponents/procurement/andapp/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:
ProcurementMetadataSchemainline atlib/validation/schemas.ts:1022-1033AND the standaloneVALID_BID_STATUSESconst atlib/validation/schemas.ts:2007-2018— a de-dup hazard B-12 flags for TECH.
Behaviour
Section titled “Behaviour”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.
Section B — Workflow
Section titled “Section B — Workflow”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 pqq → psq (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_typeand 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 Bids → Procurement 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-localBid*type names (BidSummary,BidDashboardData,ExpandedBidState,BidBriefing,bidId) are folded into this IA’s vocabulary; whether they rename toForm*/Procurement*or retainBid*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_metadataJSONB (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 (todayform_templates= 0 rows, so there are zero forms yet); each workspace’sdomain_metadatalifts onto that newly-minted form — a chosen 1:1 shape for the existing workspaces, with the model now permitting many. - The 30 live
form_questionsrows re-key fromworkspace_idto a form id (B-4). With one form per workspace, every existing question maps to that workspace’s single form. - The
deadlineduplication (workspaces.domain_metadata->>'deadline'vsform_templates.deadline, RESEARCH §5 drift #5) is resolved in favour of the form (D5): the form’sdeadlinebecomes 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).
Section F — Scope
Section titled “Section F — Scope”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.
B-25 — Out of scope
Section titled “B-25 — Out of scope”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:
q_a_pairsstay corpus-level — no workspace or form partition; workspace/form relevance is computed at query time viascope_tagoverlap. Onlyform_questions(B-4) re-key to the form; the corpus does not (B-20, B-21).- “bid” is a
form_type, not an application —bidsurvives only as a B-12 closed-list value; the umbrella is “procurement” (B-1, B-17). - No
tenant_id— the database is the only cross-tenant boundary; no invariant introduces a tenant scoping column (B-1, B-25). - 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).