Skip to content

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: 6be70d0edocs(spec): procurement-workspaces PRODUCT + TECH (absorbs EP8 v5) — S242 T4.7
  • Verifier base: 70336a3c on content-items-investigation
  • Date: 18/05/2026 (S242)
  • Verifier: worktree-agent-aea3aa8a39acc68f9 (Opus 4.7 1M)

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:

  1. Three stale facts inherited from EP8 v5 (lib/pipeline contents; component count; ConfidencePosture line range) — drafter propagated EP8 claims that have drifted since S209.
  2. One source-doc spelling drift (g-cloud vs gcloud) — drafter introduced a hyphen not present in 04-workspace-types §7.3 / PLAN.md §4.9.
  3. 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.


Every brief item covered:

Brief itemCoverage
6-column satelliteB-3 + T-3 (all 6 columns named: buyer / deadline / submission_date / outcome / outcome_recorded_at / outcome_recorded_by)
Workflow state machineB-5 + T-2 (10 BID_STATES → PROCUREMENT_WORKFLOW_STATES verbatim)
Bid composerB-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 promotionB-17 + B-18 + T-11
Bid feedback 3-UCB-20..B-22 (forward-refs to UC5/UC6/UC8 — no duplication)
form_types closed listB-2 (RATIFIED-S235; v2 deferral)
application_type renamedB-1 + T-1 (procurement umbrella vocabulary)
eval baselineB-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 claimVerificationResult
types/bid.ts:3-14 BID_STATES const tuple (10 values)cat types/bid.ts | head -16 → lines 3-14 contain exact 10 valuesPASS
types/bid.ts:16 BidState typeconfirmed line 16PASS
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 lineswc -l lib/bid/bid-state-machine.ts86 lines (off-by-1)MINOR — line count off by 1
lib/bid/bid-state-machine.ts:48-59 VALID_TRANSITIONSconfirmed lines 48-59PASS
lib/procurement/procurement-workflow.ts (rename target)does NOT exist yet (correct for target state)PASS
lib/bid-library-ingest/ 4 TS extractor filesconfirmed: docx-to-markdown.ts, extract-qa-pairs.ts, extract-answer.ts, resolve-question.tsPASS
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.sqlexists at exact pathPASS
types/bid.ts:77-81 ConfidencePostureactually lines 77-81 ('strong_match' | 'partial_match' | 'needs_sme' | 'no_content')PASS
types/bid.ts:82-87 QuestionStatusconfirmed lines 82-87 (5 values)PASS
types/bid.ts:88-94 ResponseReviewStatusconfirmed lines 88-94PASS
CONFIDENCE_POSTURE_CONFIG at types/bid.ts:123-156confirmed exact linesPASS
components/bid/ 15 filesactually 21 files (drafter under-counted)STALE-FACT — see Finding 2
lib/pipeline/ “currently contains only record-run.tsactually 3 files: record-run.ts, start-run.ts, update-progress.tsSTALE-FACT — see Finding 1
lib/auth/owner-default.ts resolveContentOwnerIdpath existsPASS
lib/supabase/safe.ts sb()path existsPASS
lib/supersession/set.ts setSupersessionpath existsPASS
lib/query/query-keys.ts + lib/query/fetchers.ts (TanStack Query infra)both exist; T-6 query-key shapes consistentPASS

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:

MarkerEP8 sourceDrafter contentMatch
[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-onlyPASS
[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 exactlyPASS
[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 summaryPASS
[ABSORBED-S242 EP8 v5 §7.1] (B-16)§7.1 content_items insert payloaddrafter 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 estimatedrafter enumerates 3 route handlers, 2 pre-flight helpers, component treePASS — 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 flagsdrafter T-8 mirrors all 8 steps verbatim with signature shapePASS
EP8 v5 §7.2 pipeline_runs two-phase pattern§7.2 — Phase 1 INSERT + Phase 2 updates + recordPipelineRun signaturedrafter T-10 captures both phases + signature constraintsPASS
EP8 v5 supersession framingEP8 §15 / lifecycledrafter PRODUCT line 173 + TECH §Source-doc supersession + risks line 678PASS

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”
GotchaCoverage
Bun fetch hangs on 204 — no .select() workarounds in production codeT-9 line 405 explicitly calls out gotcha; NO .select() workaround suggested. PASS.
Data fetching via TanStack QueryT-6 line 220 explicitly uses TanStack useQuery with query-keys + fetchers paths. PASS.
No barrel re-exports — direct file importsSpec 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 insertsT-10 references recordPipelineRun() with full signature constraints. PASS.
content_text_hash GENERATED ALWAYS — omit from payloadT-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 resetNot applicable (state machine writes are not React-state reset).
Cherry-pick (not merge) for parallel agent branchesT-4 line 147 explicit: “Cherry-pick (not merge) per CLAUDE.md … gotcha”. PASS.
classifyContent userId must be UUIDT-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 rowsT-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 VercelNot 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”
CheckResult
10 BID_STATES values preserved verbatim per Q-OQR1-02Drafter 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-14Verbatim match.
Rename target PROCUREMENT_WORKFLOW_STATES in types/procurement.tsDrafter T-2 specifies target file path + const name.
Rename target lib/procurement/procurement-workflow.tsDrafter T-2 specifies (87 lines — off by 1; actual is 86).
Module re-exports rename planDrafter T-2 enumerates all 5 const renames (LABELS, SHORT_LABELS, COLOURS, PROGRESSION) + 4 function names preserved (canTransition, getAvailableTransitions, isTerminal, isActive).
VALID_TRANSITIONS table preservationT-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 columnDrafter B-3Drafter T-3
buyer TEXT NOT NULLpresentpresent (CHECK enforcement, buyer non-null)
deadline TIMESTAMPTZ NULLpresentpresent
submission_date TIMESTAMPTZ NULLpresentpresent
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 NULLpresentpresent
outcome_recorded_by UUID NULL REFERENCES auth.users(id)presentpresent (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:

ItemLockstep tagResult
B-1 (application_type=procurement)line 45PASS
B-3 (6-column population)line 74PASS
B-5 (PROCUREMENT_WORKFLOW_STATES rename)line 96 (extra — verifier didn’t list)PASS
B-12 (citations enum)line 167PASS
B-16 (post-T7 import target switch)line 223PASS — 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 332PASS (tied to T5, not T2; same vocabulary slippage as B-16)
B-27 (MCP tool rename)line 348 + 358PASS
T-13 (MCP tool rename TECH)line 550PASS
T-14 (RLS policies)NO explicit tagMISSING — 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.

All 3 [GAP-PROC-NNN] flags include the §5.3 required four fields:

GapCategorySourcesPrereq workDecision-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-refDrafter handling
reserved-workspace-seats S-1..S-8B-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-5B-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.2B-3 + T-3 cite §4.2 for 6-column ratification. No restatement of the §4.2 satellite-pattern rationale.
05-qa-flow §7B-9 cites §7 for question_matches. No restatement of question_kind discriminator.
05-qa-flow §8B-21 forward-refs UC6 sub-variants — “does NOT duplicate”.
05-qa-flow §9B-17 cites §9.1 (UC5 KH-DB-only); B-19 forward-refs UI shape to qa-promotion-ui per §9.3.
08-new-features §4B-25 cites §4 (change_reports). No restatement.
08-new-features §7B-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.

TagCoverage
[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 / TODONONE. Verified via grep -niE "TBD|XXX|TODO|FIXME". 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-spec skill.
  • TECH.md: Per-invariant T-N entries with Current state / Target state / Gate / Validation structure per the write-tech-spec skill.

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”
CheckResult
organize/organizationNone found. PASS.
color/colour”colour” used at PRODUCT line 124 (correct UK). PASS.
favorite / criticize / optimize / analyzeNone found. PASS.
MM/DD/YYYY datesNone (Last verified uses 18/05/2026). PASS.
behaviour vs behavior”behaviour” used UK consistently in body text. PASS.
## Behavior section headingUses 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”
CheckResult
Spec calls out EP8 v5 is supersededPRODUCT line 23 + TECH line 5 + line 750 (“Source-doc supersession” section) explicit.
Archival deferred to Wave 4Drafter 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 blockTECH lines 678-685 (Risk: EP8 absorption supersession ambiguity) — captures two-source-of-truth risk + mitigation. PASS.
[ABSORBED-S242 EP8 v5 §X] markers throughoutConsistent application across §C + T-7..T-10. PASS.

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 from app/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, gcloud
  • PLAN.md §4.10 line 349: 'gcloud' for procurement workspaces

Recommendation: Replace g-cloudgcloud 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 (digestschange_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_metadata entirely OR retain as a residual JSONB column for migration-tail compatibility” (ambiguous about WHERE the residual JSONB lives — implicitly workspaces).
  • GAP-PROC-001 prerequisite-work clarifies: “If retain: keep domain_metadata JSONB on procurement_workspaces (not workspaces)”.

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 the UNIQUE(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.


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.

  1. Finding 1 — Update lib/pipeline/ framing post-S209 (3 helpers exist).
  2. Finding 2 — Update components/bid/ count to 21 (or refer to current inventory).
  3. Finding 3 — Replace g-cloudgcloud to match architecture record.
  4. Finding 4 — Correct bid-state-machine.ts line count (86, not 87).
  5. Finding 5 — Add explicit [LOCKSTEP-WITH-MIGRATION] tag to T-14 header.
  6. Finding 6 — Disambiguate lockstep-tag vocabulary (T2 vs T5 vs T7).
  7. Finding 7 — Fix P-N → B-N typo at TECH line 603.
  8. Finding 8 — Tighten residual-JSONB destination wording in T-3 step 4.
  9. Finding 9 — Add explicit T-N coverage for B-2 / B-4 / B-7 / B-11.

Additional minor suggestions:

  • auth.success discipline: T-7 routes table should call out the getAuthorisedClient() 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.

  • 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.

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.