⚠️ BID-ERA HISTORICAL REFERENCE (bannered S462). Last verified S248 — predates
the id-61 renames, id-130 per-form domain model, DR-014 (manual form upload), DR-038
(workspace retirement), and DR-041 (three-zone IA). Valuable for flow/mechanism
archaeology only; hook names, table names, workspace-level state framing, and the
“100s draft-all timeout” (retired S224 — async 202 queue) are all stale. Current
model: ID-145 — specs/id-145-procurement-form-first/RESEARCH.md.
Last verified: Session 248 (20 May 2026) — refreshed for S248 T4 procurement umbrella rename (lib/bid → lib/procurement, components/bid → components/procurement, /api/bids → /api/procurement, BID_STATES → PROCUREMENT_WORKFLOW_STATES, file-level renames); ID-71 bid→forms rename + outcome-grouped MCP tool surface (form_questions/form_responses/form_response_history tables, citations table, get_form_* RPCs, find/get/where_are_we_exposed/whats_in_my_queue tools); prior S224 (05 May 2026) for S224 §5.4.1 batch-draft-all IMPL ship (route now async-queued via processing_queue; UI polling pattern); prior S210 (29 April 2026) for S195-S209 + prod-readiness S10-S13
Pending updates: None (post-S209 spec ratification for §1.7 admin dedup + §1.9 near-duplicate merge dashboard — those impl waves haven’t shipped, so no doc impact)
Completing Forms governs the creation, analysis, drafting, review, and export of
form responses within Canonical. It spans tender file ingestion
(PDF/DOCX), structured Q&A extraction, KB-backed matching via embeddings, a
three-pass AI drafting pipeline (Sonnet/Opus/Haiku), markdown-native Tiptap
editing, and DOCX/XLSX export. Q&A library content (canonical Q&A pairs in
content_items with answer_standard / answer_advanced) is the primary KB
source feeding form drafts; the /library (Q&A Library) surface and the form
session both edit through the same useInlineFieldEdit shape since the S198
§1.5 ContentEditor upgrade.
For current API/component/hook counts, see docs/generated/codebase-stats.md.
For current MCP tool listings, see docs/generated/mcp-inventory.md.
29 route files under app/api/procurement/, organised by functional group. All routes
require authentication; write operations are gated to admin + editor via
getAuthorisedClient(['admin', 'editor']).
| Method | Route | Auth | Purpose | File |
|---|
| GET/POST | /api/procurement | All auth | List and create procurement workspaces | app/api/procurement/route.ts |
| GET/PATCH/DELETE | /api/procurement/[id] | All auth | Retrieve, update, or delete a form | app/api/procurement/[id]/route.ts |
| Method | Route | Auth | Purpose | File |
|---|
| GET/POST | /api/procurement/[id]/questions | All auth | List and create questions | app/api/procurement/[id]/questions/route.ts |
| PATCH/DELETE | /api/procurement/[id]/questions/[qId] | All auth | Update or delete a question | app/api/procurement/[id]/questions/[qId]/route.ts |
| POST | /api/procurement/[id]/questions/extract | All auth | Extract Q&A from uploaded tender | app/api/procurement/[id]/questions/extract/route.ts |
| POST | /api/procurement/[id]/questions/match | All auth | Match questions to KB content (RPC) | app/api/procurement/[id]/questions/match/route.ts |
| Method | Route | Auth | Purpose | File |
|---|
| GET/PATCH | /api/procurement/[id]/responses/[rId] | All auth | Get or update a single response | app/api/procurement/[id]/responses/[rId]/route.ts |
| GET | /api/procurement/[id]/responses/[rId]/history | All auth | Response version history | app/api/procurement/[id]/responses/[rId]/history/route.ts |
| POST | /api/procurement/[id]/responses/[rId]/regenerate | Editor+ | Re-draft via runDraftingPipeline | app/api/procurement/[id]/responses/[rId]/regenerate/route.ts |
| POST | /api/procurement/[id]/responses/[rId]/restore | All auth | Restore a previous response version | app/api/procurement/[id]/responses/[rId]/restore/route.ts |
| POST | /api/procurement/[id]/responses/draft | All auth | Single-question 3-pass drafting | app/api/procurement/[id]/responses/draft/route.ts |
| POST | /api/procurement/[id]/responses/draft-all | Editor+ | Batch drafting via processing_queue (S224 §5.4.1 IMPL — 202+{job_id, pipeline_run_id, deduplicated}, async-polled by UI; handler in lib/queue/handlers/procurement-draft-all.ts, job type form_draft_all) | app/api/procurement/[id]/responses/draft-all/route.ts |
| POST | /api/procurement/[id]/responses/draft-stream | All auth | SSE-streamed drafting via ReadableStream | app/api/procurement/[id]/responses/draft-stream/route.ts |
| POST | /api/procurement/[id]/responses/estimate | All auth | Estimate drafting effort/cost | app/api/procurement/[id]/responses/estimate/route.ts |
| Method | Route | Auth | Purpose | File |
|---|
| POST | /api/procurement/[id]/export/docx | All auth | Export form as DOCX | app/api/procurement/[id]/export/docx/route.ts |
| POST | /api/procurement/[id]/export/xlsx | All auth | Export form as XLSX | app/api/procurement/[id]/export/xlsx/route.ts |
| Method | Route | Auth | Purpose | File |
|---|
| POST | /api/procurement/[id]/tender | All auth | Upload tender (PDF/DOCX) | app/api/procurement/[id]/tender/route.ts |
| GET | /api/procurement/[id]/tender/download | All auth | Download stored tender file | app/api/procurement/[id]/tender/download/route.ts |
| Method | Route | Auth | Purpose | File |
|---|
| GET/POST | /api/procurement/[id]/templates | All auth | List and create templates | app/api/procurement/[id]/templates/route.ts |
| GET/DELETE | /api/procurement/[id]/templates/[templateId] | All auth | Get or delete a template | app/api/procurement/[id]/templates/[templateId]/route.ts |
| POST | /api/procurement/[id]/templates/[templateId]/analyse | All auth | Analyse template requirements | app/api/procurement/[id]/templates/[templateId]/analyse/route.ts |
| POST | /api/procurement/[id]/templates/[templateId]/auto-map | All auth | Auto-map fields to content | app/api/procurement/[id]/templates/[templateId]/auto-map/route.ts |
| POST | /api/procurement/[id]/templates/[templateId]/fill | All auth | Fill template with content | app/api/procurement/[id]/templates/[templateId]/fill/route.ts |
| PATCH | /api/procurement/[id]/templates/[templateId]/fields/[fieldId] | All auth | Update a template field | app/api/procurement/[id]/templates/[templateId]/fields/[fieldId]/route.ts |
| POST | /api/procurement/[id]/templates/[templateId]/fields/bulk-update | All auth | Bulk update template fields | app/api/procurement/[id]/templates/[templateId]/fields/bulk-update/route.ts |
| GET | /api/procurement/[id]/templates/[templateId]/completions/[completionId]/download | All auth | Download completed template | app/api/procurement/[id]/templates/[templateId]/completions/[completionId]/download/route.ts |
| Method | Route | Auth | Purpose | File |
|---|
| GET | /api/procurement/[id]/readiness | All auth | 7-criteria readiness API | app/api/procurement/[id]/readiness/route.ts |
| POST | /api/procurement/[id]/outcome | All auth | Record form outcome | app/api/procurement/[id]/outcome/route.ts |
| POST | /api/procurement/[id]/outcome/integrate | All auth | Integrate outcome learnings | app/api/procurement/[id]/outcome/integrate/route.ts |
Streams incremental response formulation using raw SSE via ReadableStream and
draftResponseStreaming from lib/ai/draft.ts. Emits structured events for
matching, analysis, drafting, and completion. Consumed in the session UI by
useDraftStream.
Wraps runDraftingPipeline() from lib/ai/draft.ts with a 5-req/min/user rate
limit and maxDuration = 120. Validates the body via
ResponseRegenerateBodySchema from lib/validation/schemas.ts. Used by the
“Redraft” UI affordance — internal naming retained as handleRegenerate.
components/procurement/ (21 files) plus components/qa/ (5 files, shared with the Q&A
Library route). For exact counts, see docs/generated/codebase-stats.md.
| Component | File | Purpose |
|---|
ProcurementContextProvider | components/procurement/procurement-context-provider.tsx | Procurement state context for child components |
ProcurementCreationWizard | components/procurement/procurement-creation-wizard.tsx | Multi-step form creation (Create Blank vs Create & Upload Tender) |
ProcurementExportMenu | components/procurement/procurement-export-menu.tsx | DOCX/XLSX export trigger |
ProcurementListCard | components/procurement/procurement-list-card.tsx | Procurement summary card on /procurement |
ProcurementWorkflowBadge / ProcurementWorkflowStepper | components/procurement/procurement-workflow-indicator.tsx | Visual badge + stepper for form lifecycle state |
ProcurementOutcomeDialog | components/procurement/procurement-outcome.tsx | Win/loss/withdrawn outcome dialog |
DraftRecoveryDialog | components/procurement/draft-recovery-dialog.tsx | Prompt to recover localStorage draft on remount |
KBIntegrationReview | components/procurement/kb-integration-review.tsx | Pre-draft KB-match review (post-S198 §1.6: uses stripMarkdown for previews) |
QuestionList | components/procurement/question-list.tsx | Question list view on /procurement/[id] |
QuestionNavigator | components/procurement/question-navigator.tsx | Cross-question navigation in session |
QuestionReview | components/procurement/question-review.tsx | Review extracted questions before drafting |
QuestionRow | components/procurement/question-row.tsx | Individual question row |
ReadinessChecklist | components/procurement/readiness-checklist.tsx | 7-criteria readiness UI; co-exports ReadinessBadge |
ResponseActions | components/procurement/response-actions.tsx | Save/Approve/Redraft/Regenerate action buttons |
ResponseEditor | components/procurement/response-editor.tsx | Tiptap + @tiptap/markdown markdown-native editor (S182 onwards) |
ResponseVersionHistory | components/procurement/response-version-history.tsx | Version history (post-S198 §1.6: renders via ContentRenderer + htmlToMarkdown) |
TemplateCompletionSummary | components/procurement/template-completion-summary.tsx | Summary of completed templates |
TemplateFieldReview | components/procurement/template-field-review.tsx | Review individual template fields |
TemplateFillProgress | components/procurement/template-fill-progress.tsx | Template completion progress |
TenderMetadataPrompt | components/procurement/tender-metadata-prompt.tsx | Prompt for tender metadata entry |
TenderUpload | components/procurement/tender-upload.tsx | Tender document upload |
WarningsBanner | components/dashboard/warnings-banner.tsx | Renders accumulated batch-operation warnings |
| Component | File | Purpose |
|---|
QAAnswerDisplay | components/qa/qa-answer-display.tsx | Read + edit panels for answer_standard / answer_advanced (S198 §1.5) |
QAPairRenderer | components/qa/qa-pair-renderer.tsx | Renders read-mode Q&A pair via ContentRenderer |
QARow | components/qa/qa-row.tsx | Single Q&A row in list contexts |
QAPreviewList | components/qa/qa-preview-list.tsx | Preview list during batch import |
BatchQAPreviewTable | components/qa/batch-qa-preview-table.tsx | Tabular preview of batch-imported Q&A pairs |
| Hook | File | Purpose |
|---|
useFormActions | hooks/procurement/use-procurement-actions.ts | Form CRUD + URL-synced tab/filter state (?tab=&q=&status=&sort=) |
useBidExport | hooks/procurement/use-procurement-export.ts | Orchestrates DOCX/XLSX export + downloads |
useBidReadiness | hooks/procurement/use-procurement-readiness.ts | Fetches readiness checklist (TanStack Query) |
useBidResponseActions | hooks/procurement/use-procurement-response-actions.ts | Response-level mutations (save, approve, regenerate) |
useBidSession | hooks/procurement/use-procurement-session.ts | Session-level state — current question, response, draft buffer |
useDraftRecovery | hooks/streaming/use-draft-recovery.ts | Recovers localStorage-snapshotted unsaved drafts |
useDraftStream | hooks/streaming/use-draft-stream.ts | Manages a single SSE stream for draft response |
useStreamCoordination | hooks/streaming/use-stream-coordination.ts | Coordinates concurrent streams (one per question) |
useInlineFieldEdit | hooks/use-inline-field-edit.ts | Single-field-at-a-time edit shape consumed by QAAnswerDisplay (S198) |
| Module | File | Purpose |
|---|
draft.ts | lib/ai/draft.ts | 3-pass pipeline (analyseQuestion, draftResponse, draftResponseStreaming, runDraftingPipeline) |
match.ts | lib/ai/match.ts | Pure helpers — assessConfidence, deduplicateResults, MATCH_THRESHOLDS |
extract-questions.ts | lib/ai/extract-questions.ts | PDF/DOCX question extraction via Anthropic SDK |
quality-check.ts | lib/ai/quality-check.ts | Pass-3 deterministic + AI quality gate |
| Module | File | Purpose |
|---|
procurement-workflow.ts | lib/procurement/procurement-workflow.ts | 10-state machine, canTransition, getAvailableTransitions, isTerminal, PROCUREMENT_WORKFLOW_PROGRESSION |
procurement-export-docx.ts | lib/procurement/procurement-export-docx.ts | DOCX export via markdownToDocxParagraphs + stripMarkdown (markdown-native since S182) |
procurement-export-xlsx.ts | lib/procurement/procurement-export-xlsx.ts | XLSX export with exceljs |
procurement-export-data.ts | lib/procurement/procurement-export-data.ts | Export-data assembly |
procurement-export-types.ts | lib/procurement/procurement-export-types.ts | Shared export types |
procurement-helpers.ts | lib/procurement/procurement-helpers.ts | Pure helpers (e.g. getDeadlineProximity) |
procurement-queries.ts | lib/procurement/procurement-queries.ts | fetchActiveProcurementWithStats (used by dashboard + reorientation paths) |
| Module | File | Purpose |
|---|
extract-answer.ts | lib/procurement-library-ingest/extract-answer.ts | Answer-cell extraction from Q&A tables |
resolve-question.ts | lib/procurement-library-ingest/resolve-question.ts | Question identification heuristics |
| Module | File | Purpose |
|---|
template-auto-map.ts | lib/templates/template-auto-map.ts | Auto-map template fields to KB content |
template-coverage.ts | lib/templates/template-coverage.ts | Coverage analysis for filled templates |
| Module | File | Form-completion relevance |
|---|
publication-transitions.ts | lib/governance/publication-transitions.ts | KB content rendered into form drafts honours publication_status lifecycle (S205-S208 §5.2 Phases 1+2) |
cadence-renewal.ts | lib/governance/cadence-renewal.ts | Q&A items receive a next_review_date via §5.5 backfill (Q&A cohort: 395 @ 180d) |
| Table | Purpose | Key Columns | RLS / Notes |
|---|
workspaces | Anchor for a form (or other type) | discriminator is application_types.key ('procurement') via JOIN (the workspaces.type col was dropped post-T2); domain_metadata (deadline, buyer, outcome), is_archived | All auth: SELECT; Editor+: write |
form_questions | Extracted tender questions | section_name, question_sequence, confidence_posture, matched_content_ids (uuid[]) | All auth |
form_responses | Drafted responses (1:1 with question) | response_text (markdown), response_text_advanced, review_status, version, drafted_by, metadata (Json), source_content_ids | All auth |
form_response_history | Append-only response snapshots | version, change_reason, response_text | Append-only |
citations | Links form responses to source items (polymorphic) | citing_form_response_id, cited_content_item_id, citation_type | Editor+ to write via cite_content MCP tool |
template_requirements | Required field definitions in templates | requirement_embedding, requirement_text | Editor+ |
template_fields | Template field state | template_id, question_id, mapped_content_ids | Editor+ |
template_completions | Filled-template artefact tracking | fields_filled, storage_path | Editor+ |
content_items | KB source feeding form drafts (Q&A pairs, policies, case studies, etc.) | publication_status (S205-S208), next_review_date, review_cadence_days, answer_standard, answer_advanced | RLS via get_user_role() |
| Function | Purpose | Form-completion relevance |
|---|
search_for_form_response | Hybrid semantic + keyword search tuned for the drafting pipeline (with win-rate boost) | Used by /api/procurement/[id]/questions/match |
hybrid_search | General-purpose hybrid search | Indirect — KB browse |
search_content_chunks | Section-level chunk search (S208 widened with filter_overdue_review + filter_review_due_within_days) | MCP-callable; form agents can request lifecycle-filtered chunks |
get_form_question_stats_batch | Single-RPC stats for all forms | Powers /procurement listing + dashboard cards |
get_form_question_stats | Per-form stats | Powers get_procurement_detail MCP tool |
search_for_form_response defaults include_superseded = false (S186 WP-B.3,
migration 20260421223339_add_include_superseded_to_search_rpcs.sql) — the form
draft pipeline therefore never matches against superseded KB content unless the
caller opts in.
Procurement-specific tools in lib/mcp/tools/procurement.ts. KB search is
served by the outcome-grouped find tool in lib/mcp/tools/search.ts (ID-71
folded the former search_knowledge_base / search_qa_library /
search_content_chunks / find_similar_items tools into one find with
type / scope / granularity / similar_to params). Current tool counts in
docs/generated/mcp-inventory.md.
| Tool | Read-only | Purpose |
|---|
list_active_procurement | Yes | Pipeline view of active procurement workspaces |
get_procurement_detail | Yes | Full form record + question stats |
get_form_question | Yes | Single question + matched content + response |
cite_content | No | Editor+; links a content item to a form response (writes citations — citing_form_response_id / cited_content_item_id) |
get_content_effectiveness | Yes | Surfaces win-rate / citation-count for a content item |
show_procurement_dashboard | Yes | App-trigger tool — renders the Procurement Dashboard MCP App (ui://form-dashboard/app.html) |
find | Yes | Outcome-grouped KB search; the Q&A branch (type) and section-level branch (granularity: 'chunk', S208 review-cadence filters) feed form drafting; similar_to branch for semantic discovery |
search_for_form_response | Yes | RPC name backing /api/procurement/[id]/questions/match (not directly exposed as a tool) |
| Prompt | Args | Purpose |
|---|
form_briefing | form_name | Reorient on a single form |
draft_response | question_text | Draft a response to a tender question via KB |
form_pipeline_review | stale_threshold_days | Pipeline-wide blockers + prioritised next actions |
| URI | Purpose |
|---|
kb://forms/{id} | Single form (procurement) workspace as JSON |
kb://qa/{id} | Q&A pair as JSON |
ui://form-dashboard/app.html | Procurement Dashboard MCP App (interactive UI) |
| Setting | Location | Default | Purpose |
|---|
ANTHROPIC_API_KEY | .env.local | — | All AI calls (analyse, draft, quality-check) |
OPENAI_API_KEY | .env.local | — | Embeddings (text-embedding-3-large) |
MATCH_THRESHOLDS.strong | lib/ai/match.ts | 0.7 | Threshold for strong_match posture (≥2 matches above) |
MATCH_THRESHOLDS.partial | lib/ai/match.ts | 0.5 | Threshold for partial_match posture (≥1 match above) |
MATCH_THRESHOLDS.minimal | lib/ai/match.ts | 0.3 | Below this = no_content |
maxDuration (regenerate) | route file | 120 | Vercel function timeout for response regeneration |
draft-all queue runtime cap | lib/queue/handlers/procurement-draft-all.ts (handler) + app/api/cron/process-queue/route.ts:42 | 60s per cron tick (Vercel Pro) | Per-tick budget for the form_draft_all handler in the queue worker. Multi-tick fan-out via processing_queue.status='pending' requeue. Prior 100s safety timeout retired in S224 §5.4.1 refactor — route is now async-queued (HTTP 202) so a single request never blocks for the loop. |
| Rate limit (regenerate) | app/api/procurement/[id]/responses/[rId]/regenerate/route.ts | 5/min/user | Prevents redraft floods |
Form-completion routes use the project-wide safe-query patterns to prevent
silent failures:
sb() (fail-fast) — throws on any Supabase error, used in critical
paths like draft saving, export generation, and fetchActiveProcurementWithStats.
tryQuery() (Result-returning) — { data, error } for paths where
partial success is acceptable.
warningsEnvelope() — wraps composite responses (e.g. batch drafting)
so the client receives both results and non-fatal warnings.
WarningsBanner (components/dashboard/warnings-banner.tsx) — renders
accumulated warnings from batch operations.
authFailureResponse(auth) — routes the discriminated-union failure
reason to the correct HTTP status (401 / 403 / 500).
Enforced by ESLint rules local/no-unchecked-supabase-error and
local/no-silent-promise-catch. Spec:
docs/specs/silent-failure-prevention-spec.md.
Structured logging (production-readiness §9.16): all form-completion routes emit
correlation-id-tagged logs; failures surface via Sentry with release tagging
(per kh-prod-readiness S10-S13).
Form-completion + Q&A test files (counts move with code; see
docs/generated/codebase-stats.md):
| Test File | Covers |
|---|
__tests__/api/procurement-drafting.test.ts | runDraftingPipeline integration + drafting pipeline flow |
__tests__/api/procurement-questions-pipeline.test.ts | Question extraction pipeline |
__tests__/api/procurement-questions-create.test.ts | POST /questions |
__tests__/api/procurement-export.test.ts | DOCX/XLSX export route handlers |
__tests__/api/procurement-outcome.test.ts | Outcome recording |
__tests__/api/procurement-readiness.test.ts | Readiness checklist API |
__tests__/api/procurement-responses-crud.test.ts | Response CRUD operations |
__tests__/api/procurement-responses.test.ts | Response handlers (draft/draft-all/regenerate/restore/history/save) |
__tests__/api/procurement-workflow-integration.test.ts | State-machine + DB integration |
__tests__/api/procurement.test.ts | Workspace CRUD |
__tests__/api/items/qa-content-rebuild.test.ts | PATCH preserves Q: {question}\n\n prefix (S198 §4.1 H2 fix) |
__tests__/api/items/qa-create-alignment.test.ts | Q&A create populates answer_standard correctly |
__tests__/app/procurement-boundaries.test.tsx | Procurement page error/loading boundaries |
__tests__/app/procurement/[id]/page.test.tsx | Procurement detail page |
__tests__/app/procurement/[id]/page-mobile.test.tsx | Mobile procurement detail UI |
__tests__/components/procurement-creation-wizard.test.tsx | 2-card wizard layout |
__tests__/components/procurement-export-menu.test.tsx | Export menu |
__tests__/components/procurement-outcome.test.tsx | Outcome dialog |
__tests__/components/procurement-workflow-indicator.test.tsx | Workflow badge + stepper |
__tests__/components/procurement/draft-recovery-dialog.test.tsx | Recovery dialog |
__tests__/components/procurement/readiness-checklist.test.tsx | Readiness UI |
__tests__/app/browse/browse-content-from-procurement.test.tsx | Browse-to-procurement pipeline |
__tests__/components/qa-answer-display.test.tsx | S198 §1.5 Q&A ContentEditor (single-field edit, regen-embed checkbox) |
__tests__/components/qa-pair-renderer.test.tsx | Q&A read mode |
__tests__/components/qa-row.test.tsx | Q&A row in list |
__tests__/components/qa-preview-list.test.tsx | Batch preview list |
__tests__/eval/procurement-drafting-eval.test.ts | Form drafting eval wrapper (synthetic gold-standard fixture, baseline deferred — no real form data) |
__tests__/hooks/procurement/use-procurement-actions.test.ts | URL-synced tab state |
__tests__/hooks/use-procurement-readiness.test.ts | Readiness hook |
__tests__/integration/qa-editor-chunk-parity-and-regen.integration.test.ts | S198 chunk + embedding regen parity |
__tests__/integration/qa-editor-create-post-populates-answer-standard.integration.test.ts | Initial Q&A creation alignment |
__tests__/integration/qa-editor-patch-content-shape-reconciliation.integration.test.ts | PATCH content rebuild (Q: prefix) |
__tests__/integration/queue/procurement-draft-all.integration.test.ts | Queued batch draft-all handler |
__tests__/lib/procurement-drafting.test.ts | Pure drafting logic |
__tests__/lib/procurement/procurement-export-docx.test.ts | markdownToDocxParagraphs (incl. tables, lists, headings) |
__tests__/lib/procurement/procurement-export-xlsx.test.ts | exceljs integration |
__tests__/lib/procurement-library-ingest/extract-answer.test.ts | Answer-cell extraction |
__tests__/lib/procurement-library-ingest/resolve-question.test.ts | Question identification |
__tests__/lib/procurement-matching.test.ts | assessConfidence, deduplicateResults |
__tests__/lib/procurement/procurement-workflow.test.ts | All transitions + back-transitions + terminal states |
__tests__/lib/queue/handlers/procurement-draft-all.test.ts | form_draft_all handler unit coverage |
__tests__/lib/qa-detection.test.ts | Q&A detection heuristics |
__tests__/mcp/procurement-pipeline-review-prompt.test.ts | form_pipeline_review MCP prompt |
__tests__/mcp/formatters-bids.test.ts | Form-formatter Markdown output |
__tests__/validation/procurement-schemas.test.ts | Zod schema validation |
__tests__/validation/no-bid-regression-guard.test.ts | Regression guard against reintroducing dropped bid identifiers |
E2E coverage at e2e/tests/bid-pipeline.spec.ts (10 deterministic
test-drift fixes shipped S153) plus 3 new S198 §1.5 scenarios for the Q&A
ContentEditor.
- No isolated draft scope per user — all authenticated users see all
drafts. There is no per-user “my drafts” filter.
- No real-time collaboration —
Tiptap Collaboration is not wired in.
Edits serialise via standard REST PATCH saves, increasing conflict
probability under concurrent editing.
- localStorage-bound recovery —
useDraftRecovery snapshots into
localStorage, so unsaved drafts cannot be retrieved from a different
browser. Spec deliberate: cross-device drafts would require a dedicated
draft-persistence table.
- DOCX extraction relies on a TS+Python ecosystem — a form-library
ingest path and Python-side cell converters fill gaps
(
mammoth.convertToMarkdown() drops tables; the Python cell converter
loses links + lists + nested tables relative to the TS extractor).
- Form drafting eval baseline deferred — the eval runner ships and the
fixture is 24 synthetic items, but the regression baseline is blocked on
real form data per SoTP §AI Evaluation.
search_for_form_response excludes superseded by default — calls that
want superseded matches must pass include_superseded=true. Form drafting
routes do not opt in (this is the intended behaviour).
- §1.7 admin dedup review + §1.9 near-duplicate merge dashboard ratified
but not implemented — specs landed S209 WP2; implementation is on the
pre-launch backlog. Form drafts will continue to surface duplicate
Q&A matches until the dedup UI ships.
| Decision | Rationale | Alternative Considered |
|---|
| 3-pass pipeline (Sonnet → Opus → Haiku) | Citations + structured outputs are incompatible in a single Anthropic call; cheap final QA via Haiku | Single-call output with post-hoc citation extraction |
| Markdown-native editing (S182, P0-BM Phase 2) | Eliminates HTML/MD round-trip data loss; consistent with KB-wide markdown adoption (S166) | HTML round-trip via dompurify + tiptap-html |
| Single-field-at-a-time Q&A edit (S198 §1.5) | Avoids stale-baseline shrinkage when editing both Standard + Advanced together; matches useInlineFieldEdit shape | Dual-editor mount with shared baseline |
| Per-field regen-embedding checkbox (S198 §1.5) | Editing a single field shouldn’t force a re-embed of the joined content_items.content; user opt-in | Always regenerate on save (cost) / never regenerate (drift) |
search_for_form_response with win-rate boost | Empirical signal: matches cited in winning tenders should rank higher | Plain hybrid_search |
| LocalStorage draft recovery | Browser-only, zero round-trip cost; covers the “tab crash” failure mode without a server table | Dedicated draft-persistence table — defer until real-time editing arrives |
KB content honours publication_status lifecycle (S205-S208) | Drafts must not pull from 'draft' or 'archived' KB items; the publication-status enum handles this | Per-content-item visibility flags |
Form review_status on form_responses is a free-text string | Application-level distinction from KB-wide governance_review_status; the two are deliberately separate | Single shared review enum |
Q&A library lives in content_items (not a separate table) | “One record, many views” — Q&A pairs share the canonical content schema with all other KB items | Dedicated qa_pairs table |