ID-130 {130.1} RESEARCH — 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.
RESEARCH — Procurement / Form Domain Model + Information Architecture
Section titled “RESEARCH — Procurement / Form Domain Model + Information Architecture”Status:
RATIFIED — 25/06/2026 (S411).Liam ratified D1–D8 via the Lavish ratification gate (.lavish/id-130-ratification.html), accepting all recommended calls. RESEARCH is the load-bearing gate of the ID-130 spec chain ({130.1} RESEARCH ✅ → {130.2} PRODUCT → {130.3} TECH → {130.4} PLAN). The ratified decisions are locked in §6.0; {130.2} PRODUCT is authored against them.Owning theme: Roadmap theme 2 — Procurement (bid drafting), the platform’s proof-of-value capability.
Authored: S411 (24/06/2026), from a 17-agent grounding workflow (15 source digests + synthesis + adversarial completeness critic) plus live Platform-DB verification (
zjqbrdctesqvouboziae). Provenance + per-source digests in the Appendix.Reading order: §1 (the question) → §2 (verified ground truth) → §4 (the three candidate resolutions + recommendation) → §6 (your decisions). §3/§5 are the evidence base; §7–§9 are scope, timing, and the handoff to PRODUCT.
0. Why this Task exists (one paragraph)
Section titled “0. Why this Task exists (one paragraph)”The ID-61 program renamed “bid” → “procurement” across the codebase. It was mechanically clean, but it exposed a confusion that was always present: the platform cannot distinguish “a procurement” (the umbrella project a client runs) from “a tender/form” (the individual document that has its own questions, deadline, and win/lose outcome). Today, in the live product, a procurement IS a workspace and one workspace = one tender — yet the ratified ontology and every piece of ingestion/AI infrastructure shipped in the last two months model the form as the unit. ID-130 must pick one altitude and make schema + UI + ingestion agree before the first-client re-ingest (ID-45) pours real content into whichever model is live. Pre-launch is the cheapest moment to fix this.
1. The question RESEARCH must answer
Section titled “1. The question RESEARCH must answer”What IS “a procurement”, and where do
buyer,deadline,submission_date,outcome, and the 10-state workflow live — on the WORKSPACE (umbrella container) or on the FORM (the individual tender/questionnaire artifact)?
The tension in one sentence: the ratified S391 glossary says one workspace hosts many forms, each with its own buyer/deadline/outcome — but the live schema and UI store ONE buyer/deadline/outcome/ workflow-state per workspace, so today a workspace can only physically represent ONE tender.
This is not a naming question (ID-61 settled naming). It is an altitude question: at which entity do the per-engagement facts and the lifecycle live. Everything else in the Task — UI/IA, the migration, the copy work (ID-61 Unit F), the form-type picker — is downstream of this one decision.
2. Ground truth (verified this session)
Section titled “2. Ground truth (verified this session)”2.1 The ratified ontology (north-star — platform-direction.md, S391 glossary)
Section titled “2.1 The ratified ontology (north-star — platform-direction.md, S391 glossary)”- tenant / client = ONE Supabase DB per client. The DB is the only cross-tenant boundary; there is
no
tenant_idcolumn. - application_type = a use-case CLASS (reference table
application_types, 6 keys: procurement, intelligence, sales_proposal, product_guide, competitor_research, training_onboarding). NOT a container; there is noapplicationstable. It is the layer ABOVE workspace. - workspace = a container bound N:1 to exactly one application_type. One client has MANY workspaces.
workspace≠client/tenant. - form = a submission/questionnaire (
form_templates/form_questions/form_responses), workspace-scoped. One workspace hosts MANY forms.form_type∈ {bid, rfp, pqq, itt, tender, questionnaire, …}. “bid” is a form_TYPE, not an application. - q_a_pair = corpus-level reusable knowledge unit; NOT workspace-partitioned (one client’s shared
corpus; workspace relevance computed at query time via
scope_tagoverlap). - Containment: tenant ⊃ workspaces (1 per application_type) ⊃ forms ⊃ form_responses; q_a_pairs are corpus-level.
2.2 Live schema reality (DB-verified, Platform zjqbrdctesqvouboziae, 24/06/2026)
Section titled “2.2 Live schema reality (DB-verified, Platform zjqbrdctesqvouboziae, 24/06/2026)”| Fact | Verified value |
|---|---|
| Live procurement workspaces | 11 (workspaces JOIN application_types WHERE key=‘procurement’) |
| Where buyer/deadline/submission/outcome + 10-state workflow live | workspaces.domain_metadata JSONB, validated by ProcurementMetadataSchema (lib/validation/schemas.ts:1019); workspaces.status column mirrors state via trigger |
procurement_workspaces satellite | EXISTS but 0 rows — empty extension-point marker; columns only id, workspace_id, created_at, updated_at (no buyer/deadline/outcome) |
workspaces columns | id, name, description, color, created_at, updated_at, domain_metadata, is_archived, status, created_by, updated_by, icon, application_type_id — NO type column |
form_templates | 0 rows; columns include first-class form_type, deadline, issuing_organisation, evaluation_methodology — but no outcome / submission_date |
form_questions | 30 rows, keyed to workspace_id; columns end …template_requirement_id — NO form_template_id FK |
form_types CV | 8 rows: bid, checklist, itt, pqq, questionnaire, rfp, sales_proposal_template, tender. Procurement-applicable = 7 (all except sales_proposal_template; checklist/questionnaire are multi-application). The straw-man’s framework/dps/gcloud do not exist. |
q_a_pairs | Corpus-level, no workspace_id FK (relevance via scope_tag overlap) |
2.3 The split — and why it matters
Section titled “2.3 The split — and why it matters”- Workspace altitude = live and populated. 11 procurement workspaces; all per-engagement facts in
workspaces.domain_metadataJSONB; 30 questions hanging offworkspace_id. The buyer-facing UI (creation wizard, detail page, win-rate engine) is built on “1 workspace = 1 tender” and surfaces no form-type concept anywhere. - Form altitude = built but empty.
form_templatescarries the right per-form columns and the AI/ matching stack (ID-57question_matches) keys onform_questions+form_types— butform_templateshas 0 rows. The columns exist; nothing populates them yet. - The lopsidedness is the key insight: the form altitude is schema-present, data-empty. ID-45 re-ingest is the event that will populate one altitude or the other. Decide first, or re-ingest cements the workspace-centric model (B) by default.
3. Source-of-truth map (canonical NOW vs stale / superseded)
Section titled “3. Source-of-truth map (canonical NOW vs stale / superseded)”| Topic | Canonical NOW | Stale / superseded |
|---|---|---|
| Ontology glossary | platform-direction.md (S391) — §2.1 above | Any “workspace = client/tenant” framing (task-120 pre-S391 layers, self-retracted) |
| What “a procurement” IS in live code | A workspaces row with application_types.key='procurement', addressed at /api/procurement/[id] | The straw-man’s implied dedicated procurements/satellite entity — no procurements table exists |
| Where buyer/deadline/outcome + workflow live TODAY | workspaces.domain_metadata JSONB + workspaces.status | Straw-man + intended-arch claim a procurement_workspaces SATELLITE holds them — it is empty |
| Where buyer/deadline/form_type SHOULD live (per shipped ingestion) | form_templates columns (form_type, deadline, issuing_organisation, evaluation_methodology) | — (live ingestion reality; contradicts the straw-man) |
form_type | form_types CV (8 rows, §2.2); bid → ['procurement']; bid is a form_TYPE | ”procurement-is-a-form-type” inverted framing = ratified-REJECTED (task-71 {71.12}); straw-man’s 7-key {bid,rfp,pqq,itt,framework,dps,gcloud} |
q_a_pairs | Corpus-level, no workspace_id FK | ”q_a_pairs.workspace_id NOT NULL” / “cross-workspace = tenancy wall” (RATIFIED-RETIRE) |
| MCP/tool vocabulary | Umbrella = procurement (list_active_procurement); item = form_* (get_form_question) | Per-item bid_* naming; residual get_bid_summary flagged “verify” |
| Workflow state-machine HOME (intent) | Unresolved — substrate (reserved-seats) says state machines live in lib/<key>/ code; ID-61 KEEP-list says VALID_BID_STATUSES belongs to the form_type lifecycle | Straw-man + intended-arch put a 10-state machine ON the workspace satellite |
bid_questions/bid_responses table names | Renamed form_questions/form_responses (ID-64, ~2026-06-09; zero bid% tables in prod) | S403 recon “tables retain bid names” — superseded |
| Deployment / tool ceiling | IONOS/Coolify; per-tool structured justification (ID-71 WS-11) | “Cloud Run sidecar”; “~30–40 tool ceiling” (intended-arch 01-vision) |
4. The core domain question — FORM vs WORKSPACE
Section titled “4. The core domain question — FORM vs WORKSPACE”4.1 Stated crisply
Section titled “4.1 Stated crisply”The glossary says one workspace hosts many forms, each with its own buyer/deadline/outcome. The live schema + UI store one of each per workspace. So a workspace can physically represent only one tender today. Three resolutions, each grounded in the verified evidence.
4.2 Candidate A — FORM-CENTRIC
Section titled “4.2 Candidate A — FORM-CENTRIC”Move workflow + deadline + outcome + buyer to the form; the workspace becomes a pure umbrella.
- Backed by: every shipped ingestion task — ID-52 (
form_templatesalready carriesform_type/deadline/issuing_organisation/evaluation_methodology), ID-80 (the pipeline writes those onto the form), ID-57 (matching keys onform_questions+form_types), ID-71 (item layer = form vocabulary). The reserved-seats 1:1 satellite cardinality argues for it (one satellite row can’t hold N tenders). - Cost / lift (largest, buyer-facing): (i) a real per-form engagement entity that owns
outcome/submission_date/workflow (todayform_templateshas neither column); (ii) re-keyform_questionsfromworkspace_idto a form id (it has noform_template_idFK today); (iii) lift theProcurementMetadataSchemaJSONB blob +workspaces.statusonto the form entity; (iv) the UI must expose form-type / multi-form (today it shows none). - Verdict: most glossary- and re-ingest-consistent; the AI/matching stack already lives at this altitude, so it is the lower-friction landing for everything except the buyer-facing UI.
4.3 Candidate B — WORKSPACE-CENTRIC
Section titled “4.3 Candidate B — WORKSPACE-CENTRIC”Keep buyer/deadline/outcome/workflow on the workspace; accept 1 engagement = 1 workspace.
- Backed by: the live code reality + the ID-61 rename, which baked “1 workspace = 1 tender” into the wizard, detail page, and win-rate engine.
- Cost / lift: cheapest — matches what is shipped; ID-61 Unit F copy ships as-is.
- Risk: directly contradicts the glossary; strands the per-form
form_templatescolumns the pipeline already writes (duplication/fork); breaks the moment a client runs a PQQ then an ITT in one engagement. This is the option ID-45 would harden by default if no decision is made.
4.4 Candidate C — HYBRID
Section titled “4.4 Candidate C — HYBRID”Workspace = umbrella that may hold many forms; per-tender fields live on the form; the workspace keeps only an aggregate/roll-up (e.g. nearest deadline, overall status, win-rate denominator).
- Backed by: the substrate placing state machines in
lib/<key>/code (not workspace columns) +form_templatesalready owning the per-form fields; reconciles the ID-61 internal split (state machine documented as the form_type’s, driving fields on the workspace) by moving the fields to the form and keeping a thin workspace roll-up. - Cost / lift: most design work — must define the form/engagement entity, the roll-up semantics, AND the multi-form UI/IA.
- Risk: most faithful long-term and re-ingest-safe; risk of over-building if v1 clients genuinely run one form per engagement.
4.5 Recommendation (for ratification, not a pre-commit)
Section titled “4.5 Recommendation (for ratification, not a pre-commit)”The evidence leans A/C (form-ward). The entire AI/ingestion half already votes form-centric, ID-45
re-ingest writes the form altitude, and the form altitude is schema-present, data-empty — so moving
now is cheap on the data side (almost nothing to migrate: form_templates = 0 rows) and expensive only
on the buyer-facing UI side. The single decisive sub-question is §6 D2 (does a v1 engagement ever
hold more than one form?):
- If yes / plausibly → Candidate C (umbrella + per-form fields + workspace roll-up). Most defensible; avoids a second migration when multi-form demand arrives.
- If firmly no for v1 → a thin Candidate C (≈ A with the workspace as a 1:1 convenience umbrella) still beats B, because it puts the lifecycle where the AI stack and re-ingest already are, and keeps B’s “1 = 1” as a UI simplification rather than a schema constraint.
Against B: it is cheapest this week but forks live pipeline columns and converts a presentation simplification into a schema law that ID-45 then cements — the most expensive thing to undo post-launch.
PRODUCT ({130.2}) will carry the ratified choice as a numbered invariant; RESEARCH’s job is to surface the choice and its evidence, which it now has.
5. Drifts & contradictions found (9 — corrected against live DB)
Section titled “5. Drifts & contradictions found (9 — corrected against live DB)”- Task-61 identity (RESOLVED, not a remap). Ledger Task 61’s title is “Pre-launch operational pre-decisions + observability (T13)”. The bid→procurement rename is a sub-scope of it (subtasks {61.3}, {61.7/9/10/11/12} = Units A–F). No ID-90/102 remap occurred — a title/scope collision under one umbrella number. The rename is owned by 61 but is not its title.
- Straw-man “RATIFIED” ≠ approved.
specs/procurement-workspaces/{PRODUCT,TECH}.md(in the private docs-site) is markedRATIFIED-S242, but that was ratification against May-2026 investigation docs, predating both the S391 glossary and the conflation discovery; the S242 verifier never interrogated form-vs-workspace. Treat as straw-man, not authority. - ID-61’s own internal split. The rename put buyer/deadline/submission/outcome on the WORKSPACE but
KEPT
VALID_BID_STATUSES/TERMINAL_BID_STATUSESwith the rationale “bid is a genuine form_type with its own lifecycle” — i.e. the state machine is documented as a form_type property while its driving fields sit on the workspace. (Note: the 10 states are also written twice —ProcurementMetadataSchemainline atschemas.ts:1022-1033AND the standaloneVALID_BID_STATUSESconst atschemas.ts:2007-2018— a refactor hazard PRODUCT/TECH should de-dup.) bidarg-alias, not a schema axis (CORRECTED). Earlier framing claimed aworkspaces.type='bid'column creating a “parallel encoding of bid”. Live DB has NOworkspaces.typecolumn. The only livetype==='bid'is an MCP arg-compatibility shim (lib/mcp/tools/workspaces.ts:78,args.type==='bid' ? 'procurement'). Retire the alias, but it is not a schema-level dual-axis problem.deadlineduplicated across altitudes.workspaces.domain_metadata->>'deadline'ANDform_templates.deadlineboth exist; which is canonical is unresolved (ingestion writes one, UI reads the other). The altitude decision (§4) resolves this.form_questionskeyed toworkspace_id, not a form id. “Forms as distinct artifacts within a workspace” are not partitioned at the question/response layer — contradicts “one workspace hosts many forms” at the data level. (Candidate A/C must re-key.)- Intended-arch internal inconsistency.
04-workspace-types.md§7.1 lists 7 procurement form_types (no tender); §7.3 says “8 including tender”. The closed list was never authoritatively pinned — superseded by the liveform_typestable (§2.2). - Mis-attributed eval claim. “Phew bid library = eval ground-truth” is NOT in Task-104 (eval infra; “baseline” there means regression snapshots, not domain data) — it originates in the straw-man B-28. Do not let it leak into ID-130 sourcing as a Task-104 fact.
- State-machine source-of-truth drift.
application_types.state_machine_configJSONB exists but the live 10-state workflow is hardcoded inProcurementMetadataSchema, not driven from that column. Whether the workflow becomes config-driven, and at which altitude, is open.
6. Decisions for Liam (ratification register)
Section titled “6. Decisions for Liam (ratification register)”6.0 RATIFIED — 25/06/2026 (S411, Lavish gate)
Section titled “6.0 RATIFIED — 25/06/2026 (S411, Lavish gate)”Liam accepted all recommended calls. These are now binding inputs to {130.2} PRODUCT.
| # | Ratified decision |
|---|---|
| D1 | C — Hybrid. Workspace = umbrella holding many forms; per-stage buyer/deadline/outcome/workflow live on the form; workspace keeps a roll-up (overall outcome, nearest deadline, win-rate denominator). |
| D2 | Many forms per engagement (PSQ → ITT → tender under one procurement) — research-confirmed; this is the cardinality PRODUCT must model. |
| D3 | HOLD ID-61 Unit F — fold its UI copy into the ratified IA, do not ship as a standalone sweep. |
| D4 | Align the form_type list to Procurement Act 2023 — PRODUCT reviews pqq → SQ/PSQ terminology and confirms itt/tender/bid/rfp; the live set is the baseline. |
| D5 | deadline on the form (per-stage); workspace shows nearest as a roll-up. Kills the current dual-altitude duplication. |
| D6 | Defer the empty procurement_workspaces satellite’s fate to TECH ({130.3}) — activate-as-roll-up vs drop is an implementation detail. |
| D7 | Surface form_type in the UI via infer + confirmable picker — classify from the uploaded document, user confirms/overrides. |
| D8 | Fold the 5 ID-61 PJ flags into the IA — singular nav noun “Procurement”; keep industry craft terms (“bid-writing”) where natural; PRODUCT resolves the rest. No mechanical re-litigation. |
Derived modelling note (from D1+D2 + research): because each stage is independently scored and the
SQ/PSQ carries a pass/shortlist outcome while the ITT carries the win/lose, outcome is effectively
multi-valued — per-form (per-stage) outcome + a workspace-level overall outcome. PRODUCT must model
both and define how the workspace roll-up derives the overall result.
The original options below are retained as the rationale behind §6.0. The headline call (D1) drove everything; the rest followed from it.
- D1 — The altitude: A / B / C (§4). Recommendation: C, or a thin-C ≈ A; against B. Everything downstream depends on this.
- D2 — Does a v1 engagement ever hold more than one form? (PQQ → ITT → tender in one engagement?) If yes/plausibly → the form altitude is needed now. If firmly no → a 1:1 umbrella simplification is defensible. This is the decisive sub-question for D1.
- D3 — Hold or ship ID-61 Unit F (UI copy)? Unit F hard-codes “procurement workspace = one tender”; shipping before D1 creates rework. Recommendation: HOLD pending this RESEARCH.
- D4 — Canonical
form_typeclosed list. Ratify the liveform_types(7 procurement-applicable: bid, checklist, itt, pqq, questionnaire, rfp, tender) — and confirm the straw-man’s framework/dps/gcloud are dropped, not re-added. - D5 — Canonical
deadlinealtitude (form vs workspace, or a roll-up) — kills drift #5. - D6 — Fate of the empty
procurement_workspacessatellite — activate as the form/engagement extension point, repurpose, or drop. - D7 — Surface
form_typein the UI at all in v1? If so, where — a picker at form-creation, or inferred from the uploaded document’s classification (FormMetadata.form_type)? Today it is invisible. - D8 — The 5 ID-61 Unit-F product-judgment flags (fold into the IA decision rather than
re-litigate mechanically):
- PJ-1 nav noun singular vs plural (
Bids/Back to Bids→Procurement) — the first IA hook for the form-type question. - PJ-2 “Manage bid submissions and tender responses” rephrase (
page.tsx:155). - PJ-3 “bid-writing” craft term — keep or rephrase (proposal-/tender-writing)?
- PJ-4 “Past bid responses” card (persona cluster copy).
- PJ-5 mcp-apps-local
Bid*type names (BidSummary/BidDashboardData/ExpandedBidState/BidBriefing/bidId).
- PJ-1 nav noun singular vs plural (
7. Scope — what ID-130 subsumes
Section titled “7. Scope — what ID-130 subsumes”- ID-61 Unit F (the {61.12} UI-copy sweep — 62 copy-sites / 13 lockstep pairs / DOM-ids / e2e /
bare-words, mapped in
specs/id-61-bid-rename/UNIT-F-SCOPE-MAP.md) — held and folded into the ratified IA, not executed as a mechanical rename. - Deferred ID-61 wire-field cleanup —
active_bids/bid_summarywire fields (the 4th wire contract Unit D didn’t cover);bid_outcomealias inget_content_win_rate; stale/api/bidsJSDoc; the--bid-*design tokens (bl-349) and persisted-literal classes (bl-370) per the ID-61 KEEP/defer register. - NOT in scope: the q_a_pair corpus model (corpus-level, settled); the dedup flywheel (ID-120 done); the cocoindex pipeline standup (ID-127); the question_matches scoring internals (ID-57).
8. Timing & sequencing (the ID-45 constraint)
Section titled “8. Timing & sequencing (the ID-45 constraint)”ID-45 (first-client full-corpus re-ingest, in_progress; {45.3} TECH / {45.4} PLAN unwritten + a GO
gate) writes content into whichever altitude is live. The pipeline already writes the form altitude
(form_templates columns), so:
- If ID-130 lands on A/C, re-ingest naturally populates the right altitude (and the data migration is
near-trivial today:
form_templates= 0 rows,procurement_workspaces= 0 rows). - If ID-130 lands on B, the pipeline’s per-form columns become dead/forked data and re-ingest cements the workspace-centric model.
Sequencing rule: the ID-130 altitude decision (D1) and the schema migration that follows it should land before ID-45 GO. ID-130 RESEARCH→PRODUCT→TECH can proceed in parallel with the ID-127 pipeline standup (disjoint lanes); they converge at ID-45.
9. Handoff to PRODUCT ({130.2})
Section titled “9. Handoff to PRODUCT ({130.2})”A fresh Planner authors PRODUCT.md against this ratified RESEARCH. PRODUCT must:
- Carry the ratified D1 altitude as numbered, testable invariants — define the entity that owns buyer/deadline/submission/outcome/workflow, and the workspace↔form cardinality.
- Specify the UI/IA for form-type surfacing (D7) and multi-form navigation (if D2 = many), folding in the 5 PJ flags (D8) as copy/IA decisions.
- Specify the form_type closed list (D4) and the canonical
deadlinehome (D5). - State the migration shape at product altitude (what moves from
workspaces.domain_metadatato the chosen home; the fate of the empty satellite, D6) — TECH ({130.3}) owns the SQL. - Hold the guardrails: keep
q_a_pairscorpus-level; keep “bid = form_type, not application”; notenant_id; the AI/matching stack already keys onform_questions+form_types, so the form altitude is the lower-friction landing for the rest of the AI stack.
Appendix — grounding provenance
Section titled “Appendix — grounding provenance”Authored from the S411 grounding workflow (wf_97aef791-2e1): 15 parallel source digesters → synthesis
→ adversarial completeness critic (verdict usable-with-gaps; all flagged overclaims corrected against
the live DB in §2.2/§5). Sources digested: ledger Tasks 120, 80, 52, 56, 57, 61, 71, 104; the straw-man
procurement-workspaces spec (PRODUCT+TECH); reserved-workspace-seats; intended-architecture
(01-vision, 04-workspace-types, 05-qa-flow, 08-new-features); the ID-61 rename map (STRATEGY,
DECISION-REGISTER, UNIT-F-SCOPE-MAP); live DB schema; live procurement UI/code. Per-source digests and
the full grounding brief are retained in the S411 session scratchpad.
End of RESEARCH — awaiting Liam ratification (D1–D8, §6).