procurement-workspaces — Verifier report (S242 WP1.4)
procurement-workspaces — Verifier report (S242 WP1.4)
Section titled “procurement-workspaces — Verifier report (S242 WP1.4)”- Spec under review:
docs/specs/procurement-workspaces/PRODUCT.md(407 L) +docs/specs/procurement-workspaces/TECH.md(768 L) - Drafter commit:
6be70d0e—docs(spec): procurement-workspaces PRODUCT + TECH (absorbs EP8 v5) — S242 T4.7 - Verifier base:
70336a3concontent-items-investigation - Date: 18/05/2026 (S242)
- Verifier: worktree-agent-aea3aa8a39acc68f9 (Opus 4.7 1M)
Verdict
Section titled “Verdict”PASS-WITH-NOTES
The spec is structurally sound, materially correct on its load-bearing claims (10-value BID_STATES preservation, 6-column satellite per OQ-Q38-E, EP8 v5 absorption, forward-ref discipline to substrate specs), and disciplined on the CLAUDE.md gotchas it does cite (content_text_hash GENERATED ALWAYS; summary not ai_summary; HTTP 204 sandbox handling). Three classes of findings are flagged below — all non-blocking but worth addressing before T4 implementation:
- Three stale facts inherited from EP8 v5 (lib/pipeline contents; component count; ConfidencePosture line range) — drafter propagated EP8 claims that have drifted since S209.
- One source-doc spelling drift (
g-cloudvsgcloud) — drafter introduced a hyphen not present in 04-workspace-types §7.3 / PLAN.md §4.9. - Minor lockstep-tag and B-N coverage gaps — T-14 missing
[LOCKSTEP-WITH-MIGRATION]tag; B-2 / B-4 / B-11 lack T-N implementation references.
None of the findings block the T4 + EP8 build sequencing the spec gates.
Axis-by-axis findings
Section titled “Axis-by-axis findings”Axis 1 — Brief compliance — PASS
Section titled “Axis 1 — Brief compliance — PASS”Every brief item covered:
| Brief item | Coverage |
|---|---|
| 6-column satellite | B-3 + T-3 (all 6 columns named: buyer / deadline / submission_date / outcome / outcome_recorded_at / outcome_recorded_by) |
| Workflow state machine | B-5 + T-2 (10 BID_STATES → PROCUREMENT_WORKFLOW_STATES verbatim) |
| Bid composer | B-8..B-12 + T-6 (composer surface + two-step retrieval + question_matches) |
| Q&A .docx import (EP8) | §C B-13..B-16 + T-7..T-10 (ABSORBED-S242 markers throughout) |
| UC5 promotion | B-17 + B-18 + T-11 |
| Bid feedback 3-UC | B-20..B-22 (forward-refs to UC5/UC6/UC8 — no duplication) |
| form_types closed list | B-2 (RATIFIED-S235; v2 deferral) |
| application_type renamed | B-1 + T-1 (procurement umbrella vocabulary) |
| eval baseline | B-28 (forward-ref to product-roadmap §1.4) |
Axis 2 — No-fabrication discipline — PASS-WITH-NOTES
Section titled “Axis 2 — No-fabrication discipline — PASS-WITH-NOTES”Spot-checked 10 citations across PRODUCT + TECH:
| Drafter claim | Verification | Result |
|---|---|---|
types/bid.ts:3-14 BID_STATES const tuple (10 values) | cat types/bid.ts | head -16 → lines 3-14 contain exact 10 values | PASS |
types/bid.ts:16 BidState type | confirmed line 16 | PASS |
types/bid.ts:26-40 BidMetadata interface (12 fields) | confirmed all 13 fields actually (12 line-counted; outcome_recorded_by? at line 39 is 13th) | PASS with slight count slip — see note |
lib/bid/bid-state-machine.ts 87 lines | wc -l lib/bid/bid-state-machine.ts → 86 lines (off-by-1) | MINOR — line count off by 1 |
lib/bid/bid-state-machine.ts:48-59 VALID_TRANSITIONS | confirmed lines 48-59 | PASS |
lib/procurement/procurement-workflow.ts (rename target) | does NOT exist yet (correct for target state) | PASS |
lib/bid-library-ingest/ 4 TS extractor files | confirmed: docx-to-markdown.ts, extract-qa-pairs.ts, extract-answer.ts, resolve-question.ts | PASS |
scripts/import_bid_library.py:288 writes ingest_source='qa_import' | confirmed (line 288 exactly) | PASS |
scripts/import_bid_library.py:467 calls extract_qa_from_docx(emit_markdown=True) | confirmed (line 467 exactly) | PASS |
supabase/migrations/20260428174512_add_ingest_source_to_content_items.sql | exists at exact path | PASS |
types/bid.ts:77-81 ConfidencePosture | actually lines 77-81 ('strong_match' | 'partial_match' | 'needs_sme' | 'no_content') | PASS |
types/bid.ts:82-87 QuestionStatus | confirmed lines 82-87 (5 values) | PASS |
types/bid.ts:88-94 ResponseReviewStatus | confirmed lines 88-94 | PASS |
CONFIDENCE_POSTURE_CONFIG at types/bid.ts:123-156 | confirmed exact lines | PASS |
components/bid/ 15 files | actually 21 files (drafter under-counted) | STALE-FACT — see Finding 2 |
lib/pipeline/ “currently contains only record-run.ts” | actually 3 files: record-run.ts, start-run.ts, update-progress.ts | STALE-FACT — see Finding 1 |
lib/auth/owner-default.ts resolveContentOwnerId | path exists | PASS |
lib/supabase/safe.ts sb() | path exists | PASS |
lib/supersession/set.ts setSupersession | path exists | PASS |
lib/query/query-keys.ts + lib/query/fetchers.ts (TanStack Query infra) | both exist; T-6 query-key shapes consistent | PASS |
Citation accuracy is high. The two stale-fact findings (lib/pipeline contents; components/bid count) are EP8-v5-inherited.
Axis 3 — EP8 absorption completeness — PASS-WITH-NOTES
Section titled “Axis 3 — EP8 absorption completeness — PASS-WITH-NOTES”[ABSORBED-S242 EP8 v5 §X] markers sampled:
| Marker | EP8 source | Drafter content | Match |
|---|---|---|---|
[ABSORBED-S242 EP8 v5 §4 + §5.4] (B-13) | §4.1 + §5.4 (UI route + admin gate) | drafter cites /settings?section=bid-library-import, drop-zone, presigned URL, admin-only | PASS |
[ABSORBED-S242 EP8 v5 §4.1-§4.4 + §5.1-§5.3] (B-14) | §4 (4-stage flow) + §5 (presign/analyse/import API design) | drafter enumerates presign → upload → analyse → import 4-stage exactly | PASS |
[ABSORBED-S242 EP8 v5 §1 + §4.2 + §9.3] (B-15) | §1 (4 required UI behaviours) + §4.2 (decision prompt) + §9.3 (parity test) | drafter enumerates Track Changes / dedup / draft-vs-final / per-file summary | PASS |
[ABSORBED-S242 EP8 v5 §7.1] (B-16) | §7.1 content_items insert payload | drafter T-9 mirrors payload verbatim with all 3 widening columns (ingest_source, content_owner_id, publication_status) | PASS |
[ABSORBED-S242 EP8 v5 §5 + §6 + §13] (T-7) | §5 API + §6 architecture + §13 effort estimate | drafter enumerates 3 route handlers, 2 pre-flight helpers, component tree | PASS — though Wave 1-4 plan from §13 is reduced to a single line |
EP8 v5 §6.3 run-post-insert.ts 8-step pipeline | §6.3 — 8 steps gated by per-caller flags | drafter T-8 mirrors all 8 steps verbatim with signature shape | PASS |
EP8 v5 §7.2 pipeline_runs two-phase pattern | §7.2 — Phase 1 INSERT + Phase 2 updates + recordPipelineRun signature | drafter T-10 captures both phases + signature constraints | PASS |
| EP8 v5 supersession framing | EP8 §15 / lifecycle | drafter PRODUCT line 173 + TECH §Source-doc supersession + risks line 678 | PASS |
EP8 absorption is materially complete. One omission worth noting: EP8 v5 §10 (Migration / Rollout: Storage bucket + lifecycle policy + feature flag NONE + rollback) is not absorbed inline. Drafter T-7 mentions “Storage bucket migration” but the spec doesn’t capture the 24h lifecycle policy or rollback path. Non-blocking — these are operational details that survive in EP8 v5 until Wave 3 archive.
Axis 4 — CLAUDE.md gotcha enforcement — PASS-WITH-NOTES
Section titled “Axis 4 — CLAUDE.md gotcha enforcement — PASS-WITH-NOTES”| Gotcha | Coverage |
|---|---|
Bun fetch hangs on 204 — no .select() workarounds in production code | T-9 line 405 explicitly calls out gotcha; NO .select() workaround suggested. PASS. |
| Data fetching via TanStack Query | T-6 line 220 explicitly uses TanStack useQuery with query-keys + fetchers paths. PASS. |
| No barrel re-exports — direct file imports | Spec uses @/lib/procurement/..., @/lib/supabase/safe, etc. — no barrel-import suggestions. PASS. |
getAuthorisedClient() discriminated union (auth.success) | T-7 mentions getAuthorisedClient(['admin']) but does NOT call out the auth.success check or authFailureResponse(auth) helper. MINOR-NOTE — load-bearing gotcha not surfaced. |
recordPipelineRun() for cron pipeline_runs inserts | T-10 references recordPipelineRun() with full signature constraints. PASS. |
content_text_hash GENERATED ALWAYS — omit from payload | T-9 line 393 explicit. PASS. |
content_items.summary (not ai_summary) | T-9 line 394 explicit. PASS. |
python-docx Track Changes — use open_document_safe() | Drafter does NOT call this out for Python-side parsing. The spec’s Python touchpoint is __tests__/integration/bid-library-ingest/parity.integration.test.ts (consumes CLI output) — not direct python-docx parsing. NON-BLOCKING. |
State machine key={propId} for UI reset | Not applicable (state machine writes are not React-state reset). |
| Cherry-pick (not merge) for parallel agent branches | T-4 line 147 explicit: “Cherry-pick (not merge) per CLAUDE.md … gotcha”. PASS. |
classifyContent userId must be UUID | T-8 line 298 explicit (uses pipeline service account UUID a0000000-0000-4000-8000-000000000001). PASS. |
Embedding vector serialisation (JSON.stringify(embedding)) | T-8 line 297 explicit. PASS. |
REST PATCH on wrong UUID returns 200 OK with 0 rows | T-5 line 195 + Risks line 669 explicit. PASS. |
Silent failures (use sb() / tryQuery()) | T-5 line 195 + T-10 line 451 explicit. PASS. |
mcp-handler breaks on Vercel | Not applicable (Q&A import route uses standard Next.js route handler). |
MINOR finding: auth.success discriminated-union check not called out at T-7 routes table. Implementation author would need to reach into CLAUDE.md.
Axis 5 — State machine correctness — PASS
Section titled “Axis 5 — State machine correctness — PASS”| Check | Result |
|---|---|
| 10 BID_STATES values preserved verbatim per Q-OQR1-02 | Drafter B-5 lists all 10: draft → questions_extracted → matching → drafting → in_review → ready_for_export → submitted → { won | lost | withdrawn }. Matches types/bid.ts:3-14 exactly. |
Names match types/bid.ts:3-14 | Verbatim match. |
Rename target PROCUREMENT_WORKFLOW_STATES in types/procurement.ts | Drafter T-2 specifies target file path + const name. |
Rename target lib/procurement/procurement-workflow.ts | Drafter T-2 specifies (87 lines — off by 1; actual is 86). |
| Module re-exports rename plan | Drafter T-2 enumerates all 5 const renames (LABELS, SHORT_LABELS, COLOURS, PROGRESSION) + 4 function names preserved (canTransition, getAvailableTransitions, isTerminal, isActive). |
VALID_TRANSITIONS table preservation | T-2 confirms verbatim per lib/bid/bid-state-machine.ts:48-59. Verified table is exactly at those line numbers in current code. |
Axis 6 — 6-column satellite correctness — PASS
Section titled “Axis 6 — 6-column satellite correctness — PASS”| OQ-Q38-E column | Drafter B-3 | Drafter T-3 |
|---|---|---|
buyer TEXT NOT NULL | present | present (CHECK enforcement, buyer non-null) |
deadline TIMESTAMPTZ NULL | present | present |
submission_date TIMESTAMPTZ NULL | present | present |
outcome TEXT NULL (CHECK won/lost/withdrawn) | present (B-7 inline CHECK) | present (CHECK (outcome IS NULL OR outcome IN ('won','lost','withdrawn'))) |
outcome_recorded_at TIMESTAMPTZ NULL | present | present |
outcome_recorded_by UUID NULL REFERENCES auth.users(id) | present | present (FK to auth.users(id)) |
All 6 columns per 04-workspace-types.md §4.2 line 107. NOT-NULL + NULL semantics + CHECK constraint properly captured.
Axis 7 — [LOCKSTEP-WITH-MIGRATION] placement — PASS-WITH-NOTES
Section titled “Axis 7 — [LOCKSTEP-WITH-MIGRATION] placement — PASS-WITH-NOTES”Verified prompt-claimed lockstep items:
| Item | Lockstep tag | Result |
|---|---|---|
| B-1 (application_type=procurement) | line 45 | PASS |
| B-3 (6-column population) | line 74 | PASS |
| B-5 (PROCUREMENT_WORKFLOW_STATES rename) | line 96 (extra — verifier didn’t list) | PASS |
| B-12 (citations enum) | line 167 | PASS |
| B-16 (post-T7 import target switch) | line 223 | PASS — but semantically tagged to T7 cutover, NOT to T2 combined-PR. Tag vocabulary in PRODUCT line 13 says [LOCKSTEP-WITH-MIGRATION] is “for items tied to the T2 combined-PR migration” — B-16 stretches that vocabulary to mean “lockstep with A migration” (T7). MINOR vocabulary slippage. |
| B-25 (change_reports rename) | line 332 | PASS (tied to T5, not T2; same vocabulary slippage as B-16) |
| B-27 (MCP tool rename) | line 348 + 358 | PASS |
| T-13 (MCP tool rename TECH) | line 550 | PASS |
| T-14 (RLS policies) | NO explicit tag | MISSING — T-14 lands “Lands in: T2 combined-PR migration” (line 590) which is semantically equivalent but the explicit [LOCKSTEP-WITH-MIGRATION] tag is absent. MINOR. |
Items NOT tied to T2 combined-PR migration (correctly untagged): B-2, B-4, B-8, B-9, B-10, B-11, B-13, B-14, B-15, B-17, B-18, B-19, B-20, B-21, B-22, B-23, B-24, B-26, B-28, B-29 — verified no false-positive tags.
Recommendation: Add [LOCKSTEP-WITH-MIGRATION] tag to T-14 header for tag-grep consistency. Either (a) tighten PRODUCT line 13 vocabulary to “T2 combined-PR migration OR sister T-N migrations” to make B-16 / B-25 explicit, OR (b) use a distinct tag like [LOCKSTEP-WITH-T7] / [LOCKSTEP-WITH-T5] to disambiguate.
Axis 8 — Gap-flag format — PASS
Section titled “Axis 8 — Gap-flag format — PASS”All 3 [GAP-PROC-NNN] flags include the §5.3 required four fields:
| Gap | Category | Sources | Prereq work | Decision-maker |
|---|---|---|---|---|
| GAP-PROC-001 — residual JSONB retain/drop | ”Implementation decision needed (DB migration body authoritative)“ | 3 sources (04-workspace-types §4.2; PLAN.md §4.2 T2.1; types/bid.ts:26-40) | “T2 migration draft author decides at draft time. If retain: keep … If drop: explicit …" | "T2 migration draft author per PLAN.md §4.2 T2” |
| GAP-PROC-002 — post-T7 origin_kind enum value | ”T7 sequencing decision needed” | 3 sources (05-qa-flow §2.1; EP8 v5 §7.1; PLAN.md §4.7 T7) | “T7 cutover decision — extend origin_kind enum with imported_docx_v1 (or similar) for EP8-driven imports vs reusing imported_legacy" | "T7 migration draft author per PLAN.md §4.7 T7” |
| GAP-PROC-003 — state-transition atomicity (TS vs PG function) | “Implementation pattern choice” | 3 sources (lib/bid/bid-state-machine.ts:48-87; CLAUDE.md gotchas; PLAN.md §4.4 T4) | “Recommend Postgres-function pattern for atomicity + observability … fallback to TS-side Promise.all with row-count re-query verification" | "T4 implementation author per PLAN.md §4.4 T4.3” |
Format compliance is clean. One MINOR consistency note: GAP-PROC-001’s “Status” wording uses “Per T-3 above…” narrative rather than a binary STILL-OPEN / BLOCKED-ON marker. Both work; just inconsistent with the rls-pattern P-5 style.
Axis 9 — Forward-ref discipline — PASS
Section titled “Axis 9 — Forward-ref discipline — PASS”Forward-refs verified to substrate specs / architecture docs WITHOUT duplication:
| Forward-ref | Drafter handling |
|---|---|
| reserved-workspace-seats S-1..S-8 | B-4 cites RWS S-3 (cardinality), B-29 cites RWS S-5 (RLS pattern), TECH §Cross-doc refs cites RWS PRODUCT+TECH. No restatement of S-N invariants. |
| rls-pattern P-1..P-5 | B-29 cites P-1 (auto-RLS event trigger) + P-2 (per-role grants helper), T-14 references RLS-PATTERN T-1 + T-2. No restatement. |
| 04-workspace-types §4.2 | B-3 + T-3 cite §4.2 for 6-column ratification. No restatement of the §4.2 satellite-pattern rationale. |
| 05-qa-flow §7 | B-9 cites §7 for question_matches. No restatement of question_kind discriminator. |
| 05-qa-flow §8 | B-21 forward-refs UC6 sub-variants — “does NOT duplicate”. |
| 05-qa-flow §9 | B-17 cites §9.1 (UC5 KH-DB-only); B-19 forward-refs UI shape to qa-promotion-ui per §9.3. |
| 08-new-features §4 | B-25 cites §4 (change_reports). No restatement. |
| 08-new-features §7 | B-20 cites §7 (3-UC composite). |
Discipline strong. One borderline case: B-9 lists 4 ConfidencePosture values inline (strong_match / partial_match / needs_sme / no_content) — but this is a code-level taxonomy from types/bid.ts:77-81, not an architecture-doc concept, so inlining is appropriate.
Axis 10 — Tag vocabulary — PASS
Section titled “Axis 10 — Tag vocabulary — PASS”| Tag | Coverage |
|---|---|
[RATIFIED-S2XX] | Used throughout with explicit session number (S229 / S234 / S235 / S236 / S240 / S242). PASS. |
[DEFERRED-v1.1] | Workspace-private q_a_pairs (out-of-scope §); sidecar materialisation (B-24 + out-of-scope); admin UI (v1.1 candidates); per-method blend UI (B-9 deferral to procurement-question-matching). PASS. |
[DEFERRED-v2] | Data-driven form_type (B-2). PASS. |
[LOCKSTEP-WITH-MIGRATION] | See Axis 7 — minor vocabulary slippage (B-16 / B-25 tagged but tied to T7 / T5, not T2). |
[ABSORBED-S242 EP8 v5 §X] | Used on every EP8-folded behaviour. Explicit section references. PASS. |
[CURRENT-CANONICAL] | PRODUCT line 5 + TECH line 5 carry status. PASS. |
[DEFERRED-qa-promotion-ui-spec] (B-19) + [DEFERRED-procurement-question-matching-tech-spec] (B-9) | Used to defer to CONDITIONAL specs per PLAN.md §5. Non-standard tag form but explicit and traceable. |
| Inline TBD / XXX / TODO | NONE. Verified via grep -niE "TBD|XXX|TODO|FIXME". PASS. |
Axis 11 — Feature-spec pattern — PASS
Section titled “Axis 11 — Feature-spec pattern — PASS”Both PRODUCT.md and TECH.md use feature-spec (Summary + Behavior) shape:
- PRODUCT.md: Summary (line 31-33) + Behavior with numbered B-N (Sections A-I) per the
write-product-specskill. - TECH.md: Per-invariant T-N entries with Current state / Target state / Gate / Validation structure per the
write-tech-specskill.
This matches PLAN.md §5 directive (“/write-product-spec + /write-tech-spec … feature-spec / Summary+Behavior shape … NOT construction-guide pattern per S241 critical-rule 5”). PASS.
MINOR: TECH.md line 603 “The test plan maps every P-N invariant” should read B-N (typo — invariants in companion PRODUCT.md are B-N).
Axis 12 — UK English — PASS-WITH-NOTES
Section titled “Axis 12 — UK English — PASS-WITH-NOTES”| Check | Result |
|---|---|
organize/organization | None found. PASS. |
color/colour | ”colour” used at PRODUCT line 124 (correct UK). PASS. |
favorite / criticize / optimize / analyze | None found. PASS. |
| MM/DD/YYYY dates | None (Last verified uses 18/05/2026). PASS. |
behaviour vs behavior | ”behaviour” used UK consistently in body text. PASS. |
## Behavior section heading | Uses US spelling (PRODUCT line 35). The write-product-spec skill template uses “Behavior” — drafter mirrors template. MINOR-NOTE. All sibling specs (rls-pattern, reserved-workspace-seats) use ## Invariants (construction-guide pattern), so this is the first feature-spec-pattern spec to expose the template’s US spelling. Not blocking — template-imposed. |
Axis 13 — EP8 v5 supersession framing — PASS
Section titled “Axis 13 — EP8 v5 supersession framing — PASS”| Check | Result |
|---|---|
| Spec calls out EP8 v5 is superseded | PRODUCT line 23 + TECH line 5 + line 750 (“Source-doc supersession” section) explicit. |
| Archival deferred to Wave 4 | Drafter says “Wave 3 deliverable per S242 prompt” (PRODUCT line 173 + TECH line 750). User prompt says Wave 4 — minor mismatch with verifier prompt brief (PRODUCT spec defers to “Wave 3” per the original S242 prompt verbiage, but the verifier brief says “(its archive is Wave 4 action)”). NON-BLOCKING — provenance is “deferred-to-archive-later”; whether it’s Wave 3 or Wave 4 is sequencing detail owned by the S242 close action. |
| Risk-mitigation block | TECH lines 678-685 (Risk: EP8 absorption supersession ambiguity) — captures two-source-of-truth risk + mitigation. PASS. |
[ABSORBED-S242 EP8 v5 §X] markers throughout | Consistent application across §C + T-7..T-10. PASS. |
Material findings
Section titled “Material findings”Finding 1 — Stale lib/pipeline/ claim (inherited from EP8 v5)
Section titled “Finding 1 — Stale lib/pipeline/ claim (inherited from EP8 v5)”Severity: MINOR / NON-BLOCKING
Where: TECH.md T-8 line 291 + T-10 line 411 + EP8 v5 §6.3 (cited source).
Claim: “lib/pipeline/ currently contains only record-run.ts” (T-8) and “recordPipelineRun() is the existing helper for the single-insert-on-finish pattern” (T-10).
Reality: lib/pipeline/ contains THREE files:
record-run.ts(212 lines — single-insert-on-finish)start-run.ts(179 lines — at-start INSERT/UPSERT, Pattern E)update-progress.ts(98 lines — mid-flight progress UPDATE; extracted fromapp/api/upload/route.ts:142-164)
The drafter inherited EP8 v5’s S209-vintage claim verbatim under [ABSORBED-S242] without re-verifying. The implementation work for run-post-insert.ts (T-8) remains genuinely greenfield; the at-start + mid-flight halves of the two-phase pattern (T-10) are partly shipped.
Recommendation: In T-10, acknowledge the three existing helpers (record-run.ts + start-run.ts + update-progress.ts); reframe the implementation work as “wire start-run.ts + update-progress.ts into the new import route” rather than “introduce new two-phase pattern”. In T-8, keep the greenfield framing (no run-post-insert.ts exists).
Finding 2 — components/bid/ count understated
Section titled “Finding 2 — components/bid/ count understated”Severity: MINOR / NON-BLOCKING
Where: TECH.md T-4 line 134.
Claim: “components/bid/ (15 files: …)”.
Reality: components/bid/ contains 21 files (the drafter enumerated 15, missing 6: response-version-history.tsx, template-completion-summary.tsx, template-field-review.tsx, template-fill-progress.tsx, tender-metadata-prompt.tsx, tender-upload.tsx).
Recommendation: Update T-4 to “21 files” or refer to “the directory inventory” with a current-vintage marker. The 44-file sweep claim in PLAN.md §4.4 T4 likely also under-counts; this finding may surface a broader inventory-drift risk in T4 acceptance criteria.
Finding 3 — g-cloud vs gcloud form_type spelling drift
Section titled “Finding 3 — g-cloud vs gcloud form_type spelling drift”Severity: MINOR / NON-BLOCKING (cosmetic — but affects T-4 grep semantics)
Where: PRODUCT.md B-2 line 51.
Claim: v1 closed list is bid / rfp / pqq / itt / framework / dps / g-cloud (with hyphen).
Reality: Source-of-truth specs use gcloud (no hyphen):
04-workspace-types.md§7.1 line 180 + §7.3 line 199:bid, rfp, pqq, itt, framework, dps, gcloudPLAN.md§4.10 line 349:'gcloud' for procurement workspaces
Recommendation: Replace g-cloud → gcloud in B-2 to match the architecture record verbatim. If g-cloud is intentionally the user-facing label (vs gcloud the storage value), make that distinction explicit; otherwise this is straightforward drift.
Finding 4 — lib/bid/bid-state-machine.ts line count off by 1
Section titled “Finding 4 — lib/bid/bid-state-machine.ts line count off by 1”Severity: TRIVIAL / NON-BLOCKING
Where: TECH.md T-2 line 60.
Claim: “lib/bid/bid-state-machine.ts — 87 lines.”
Reality: 86 lines per wc -l.
Recommendation: Update to 86 (or omit the count — the file structure is documented elsewhere).
Finding 5 — T-14 missing [LOCKSTEP-WITH-MIGRATION] tag
Section titled “Finding 5 — T-14 missing [LOCKSTEP-WITH-MIGRATION] tag”Severity: MINOR / NON-BLOCKING
Where: TECH.md T-14 (header line 562).
Issue: Verifier prompt asserts T-14 should carry [LOCKSTEP-WITH-MIGRATION]. T-14’s “Lands in: T2 combined-PR migration per PLAN.md §4.2 T2 subtask 7” (line 590) carries the semantic but not the explicit tag.
Recommendation: Add [LOCKSTEP-WITH-MIGRATION] to the T-14 header for grep consistency with B-1 / B-3 / B-12 / B-25 / B-27 / T-13.
Finding 6 — Lockstep-tag vocabulary slippage (B-16 / B-25 → T7 / T5, not T2)
Section titled “Finding 6 — Lockstep-tag vocabulary slippage (B-16 / B-25 → T7 / T5, not T2)”Severity: MINOR / NON-BLOCKING
Where: PRODUCT.md line 13 (tag definition) + B-16 line 223 + B-25 line 332.
Issue: PRODUCT line 13 defines [LOCKSTEP-WITH-MIGRATION] as “for items tied to the T2 combined-PR migration”. B-16 line 223 tags [LOCKSTEP-WITH-MIGRATION] for “the post-T7 path” (T7 cutover, not T2). B-25 line 332 tags [LOCKSTEP-WITH-MIGRATION] for “T5 (digests → change_reports rename per PLAN.md §4.5)” (T5, not T2).
The intent is clear (“lockstep with a migration”), but the vocabulary definition in PRODUCT line 13 is narrower than the usage.
Recommendation: Either (a) widen PRODUCT line 13 to “for items tied to T2 combined-PR migration OR sister migrations (T5 / T7 / T11)”; or (b) use distinct tags like [LOCKSTEP-WITH-T2] / [LOCKSTEP-WITH-T5] / [LOCKSTEP-WITH-T7] / [LOCKSTEP-WITH-T11] for disambiguation.
Finding 7 — TECH.md “P-N” typo (should be “B-N”)
Section titled “Finding 7 — TECH.md “P-N” typo (should be “B-N”)”Severity: TRIVIAL
Where: TECH.md line 603.
Claim: “The test plan maps every P-N invariant to one or more concrete verification steps”.
Issue: PRODUCT.md invariants are numbered B-N, not P-N. (P-N is the rls-pattern construction-guide convention.)
Recommendation: Replace P-N → B-N at TECH.md line 603.
Finding 8 — Internal inconsistency on residual JSONB destination
Section titled “Finding 8 — Internal inconsistency on residual JSONB destination”Severity: MINOR / NON-BLOCKING (decision noted as GAP-PROC-001)
Where: TECH.md T-3 line 116 vs [GAP-PROC-001] line 699.
Issue:
- T-3 step 4 says: “EITHER drop
workspaces.domain_metadataentirely OR retain as a residual JSONB column for migration-tail compatibility” (ambiguous about WHERE the residual JSONB lives — implicitlyworkspaces). - GAP-PROC-001 prerequisite-work clarifies: “If retain: keep
domain_metadataJSONB onprocurement_workspaces(notworkspaces)”.
The gap-flag’s “(not workspaces)” caveat suggests the retained JSONB SHOULD be a procurement_workspaces column (procurement-specific data), but T-3 text reads ambiguously and the backfill SQL at line 105-115 reads w.domain_metadata->>... (from workspaces).
Recommendation: Tighten T-3 step 4 wording: “If retain, the residual JSONB lives on procurement_workspaces (NOT on workspaces), populated from the read-side of the backfill before workspaces.domain_metadata is dropped. The migration body owns the specific column-move SQL.”
Finding 9 — Some B-N invariants lack T-N implementation references
Section titled “Finding 9 — Some B-N invariants lack T-N implementation references”Severity: MINOR / NON-BLOCKING
Where: TECH.md T-N coverage gaps for:
- B-2 (form_types closed list) — no T-N “implements” tag, though the v2 deferral is acknowledged.
- B-4 (1:1 cardinality /
UNIQUE(workspace_id)) — no T-N. T-3 captures the satellite shape but doesn’t explicitly enumerate theUNIQUE(workspace_id)constraint in the migration SQL block at line 96-103. - B-7 (terminal-outcome audit columns) — drafter T-N “implements” tag only lists B-6; the actual T-5 pseudocode at lines 182-189 covers both B-6 + B-7. Add B-7 to T-5’s “implements” list.
- B-11 (QuestionStatus + ResponseReviewStatus preserved) — no T-N. Could fit in T-4 sweep.
Recommendation: Add explicit (implements B-2) to T-4 (closed-list enforcement); add B-4 / UNIQUE constraint to T-3 step 1 satellite shape; correct T-5 to (implements B-6, B-7); add B-11 to T-4 or T-6 sweep.
Blocking findings
Section titled “Blocking findings”None. No fabrications, no critical-rule violations, no spec-pattern errors, no bun fetch hangs on 204 workaround suggestions, no barrel re-exports, no missing CLAUDE.md gotcha discipline on load-bearing claims.
Non-blocking notes / suggestions
Section titled “Non-blocking notes / suggestions”- Finding 1 — Update
lib/pipeline/framing post-S209 (3 helpers exist). - Finding 2 — Update
components/bid/count to 21 (or refer to current inventory). - Finding 3 — Replace
g-cloud→gcloudto match architecture record. - Finding 4 — Correct
bid-state-machine.tsline count (86, not 87). - Finding 5 — Add explicit
[LOCKSTEP-WITH-MIGRATION]tag to T-14 header. - Finding 6 — Disambiguate lockstep-tag vocabulary (T2 vs T5 vs T7).
- Finding 7 — Fix P-N → B-N typo at TECH line 603.
- Finding 8 — Tighten residual-JSONB destination wording in T-3 step 4.
- Finding 9 — Add explicit T-N coverage for B-2 / B-4 / B-7 / B-11.
Additional minor suggestions:
auth.successdiscipline: T-7 routes table should call out thegetAuthorisedClient()discriminated-union check pattern +authFailureResponse(auth)helper (CLAUDE.md gotcha) — load-bearing for the 3 new admin-only endpoints.- EP8 §10 absorption: Storage bucket lifecycle policy (24h auto-delete) + rollback path are not absorbed — survive in EP8 v5 until Wave 3 archive. Worth a brief T-7 footnote.
- Wave 3 vs Wave 4 archive timing: Drafter says “Wave 3 deliverable per S242 prompt” (PRODUCT line 173 + TECH line 750); verifier brief says “Wave 4 action”. Reconcile at S242 close.
Counts
Section titled “Counts”- PRODUCT.md: 407 lines, 29 B-N invariants (B-1..B-29), 9 sections (A-I), 9 out-of-scope items, 6 v1.1 candidates.
- TECH.md: 768 lines, 14 T-N entries (T-1..T-14), 5 risk-and-mitigation blocks, 3
[GAP-PROC-NNN]flags, 9 heritage-doc entries. - Forward-refs validated: RWS S-1..S-8 (4 refs), RLS-PATTERN P-1..P-5 (2 refs), 04-workspace-types §4.2 + §7 + §8 (8 refs), 05-qa-flow §7 + §8 + §9 (10 refs), 08-new-features §3 + §4 + §5 + §7 (8 refs), 02-data-flow §3 + §8 (2 refs), 06-mcp-tooling §6.3 (3 refs), PLAN.md §4.4 T4 + §4.7 T7 + §4.9 T9 + §4.10 T10 + §4.11 T11 + §5 (~15 refs).
- EP8 v5 absorbed sections: §1 (problem statement) + §3.4 (remaining TS port) + §4 (user flow) + §5 (API design) + §6.3 (run-post-insert) + §7.1 (content_items payload) + §7.2 (pipeline_runs two-phase) + §9 (test plan) + §13 (effort estimate). 9 absorbed sections — matches verifier brief.
[ABSORBED-S242 EP8 v5 §X]markers: 11 occurrences across PRODUCT §C + TECH T-7..T-10 + Source-doc supersession section.[LOCKSTEP-WITH-MIGRATION]tags: 11 occurrences (B-1 / B-3 / B-5 / B-12 / B-16 / B-25 / B-27 + T-5 / T-13 + 2 in PRODUCT framing).[RATIFIED-S2XX]tags: 30+ occurrences across both files.- Inline TBD / XXX / TODO / FIXME: 0.
Conclusion
Section titled “Conclusion”The spec is implementation-ready for T4 + EP8 absorption. The 9 findings above are recommended polish; none gate downstream T4 / T7 implementation. The drafter delivered against the WP1.4 brief: feature-spec pattern, 29 B-N invariants with full citation, EP8 v5 absorption with [ABSORBED-S242] markers, 3 gap-flags in §5.3 format, forward-ref discipline to substrate specs, UK English (template-imposed ## Behavior heading excepted), and CLAUDE.md gotcha discipline on load-bearing claims.
Verdict: PASS-WITH-NOTES.
End of verifier report.