Skip to content

Phase 0.9 — S16 Q&A Schema Design Verification Audit

Phase 0.9 — S16 Q&A Schema Design Verification Audit

Section titled “Phase 0.9 — S16 Q&A Schema Design Verification Audit”

Audit date: 2026-05-11 (KH session S230) Branch: content-items-investigation (worktree-agent-a59bfa5ff71851670) Subject: docs/plans/phase-0-investigation/0.9-spike-S16-qa-schema-design.md at SHA ed0ee5b4 (1582 lines) Auditor scope: accuracy of claims + alignment with 0.9-context.md / 0.9-decision-graph.md / 0.9-intended-architecture.md / 0.9-edit-flow-investigation.md / spike-S2 / spike-S3 / spike-S12 + CLAUDE.md gotchas + live schema + extractor source. Constraint: S16 NOT modified — verification doc only.


PASS-WITH-NOTES.

S16 is structurally sound: the proposed q_a_pairs + q_a_extractions + q_a_pair_history shape, the envelope, MCP retrieval contract, and migration plan are internally consistent and trace correctly to S12 (provenance/confidence), S3 (Shape A temporal + Shape B two-step), S2 (cocoindex Δ semantics), CLAUDE.md gotchas (anon-EXECUTE revoke, search_path, GENERATED ALWAYS avoidance, no JSONB for key data, content_chunks not chunks), and edit-flow §6 ratified UC5/UC6/UC8/UC9 decisions. The 80% confidence claim is defensible.

But there are non-trivial issues that should be fixed before S16 becomes load-bearing for the implementation phase:

  • §1 exec-summary mis-counts new tables (claims 3 — q_a_pairs, q_a_extractions, citations — while §6.5/§10/Q-S16-5 defer citations migration entirely). Should be 2 new tables (q_a_pairs + q_a_extractions) + 1 history table (q_a_pair_history), with citations explicitly deferred.
  • §1 also points the reader to §8 for migration sketch; migration is §10.
  • Vector index uses ivfflat throughout (§6.2 + §6.3 + §10.1 migrations 1+2). KH house pattern is hnsw (m=16, ef_construction=64, per 20260416102457_pre_squash_reconciliation.sql lines 4619 + 4683). S16 doesn’t acknowledge or justify the deviation.
  • Effort estimate “19-27 days impl + 8-10 wall-clock concurrent days” is unanchored. No calibration basis declared. Liam’s concern flagged at S230-start is well-founded. Should be marked CALIBRATION-PENDING.
  • Adapter codepath count mismatch: §3.2 lists extractQaPairs as “Three table formats recognised + 2 positional fallbacks” (5 total). §4.7 says “current pipeline covers only 2 of 5”. This 2 vs 5 framing is wrong about the existing code — extract-qa-pairs.ts lines 199-247 + the extractor_kind CHECK in S16 §6.3 lines 493-501 also count docx_pattern_c and docx_pattern_positional as adapters today. The existing pipeline has 5 codepaths (Patterns A/B/C + 2 positionals); S16 §4.7 understates this.
  • Hybrid client-input recommendation answers half of Liam’s question. Liam asked (a) adapt mempalace approach for KH Q&A, AND (b) define Q&A format pre-v1. S16 answers (b) partly with the YAML-frontmatter template, BUT skirts the harder framing in (a): “what is the v1 canonical Q&A pair definitively”. The hybrid recommendation defers the canonical to “accept-any + canonicalise via adapter dispatcher” without committing to a v1 wire shape that clients must adhere to.
  • Inheritance from S2 cocoindex is overclaimed in places. S2 §6.1 #1 + §6.2 + §4.3 flags that content_fingerprint cannot detect DRAFT-vs-final near-duplicates; S16 §4.2 claims --auto-supersede “auto-flips the DRAFT row’s superseded_by” today and folds it cleanly into the new q_a_pairs shape. The auto-supersede flag exists in import_bid_library.py but the cross-record dedup mechanism it relies on is S10 substrate (still undecided). S16 partially acknowledges this (Q-S16-8) but the implication chain is ambiguous.
  • The “395 prod rows” figure is repeated without sourcing (also true of intended-arch §4.3 origin_kind comment). This is a load-bearing number for migration 4 backfill — should be re-verified via a live count before commit.
  • Citations migration is deferred but breaks §1’s “3 new tables” claim (already flagged) — and Q-S16-5 punts it to “separate migration sketch” which means the post-cutover Migration 5 in §10.3 risk table assumes citations has already migrated when no migration for it exists in this spike.

Severity-tagged inventory below.


A1. §1 vs §6.5/§10 contradiction on new-table count (CRITICAL).

  • §1 line 26: “3 new tables (q_a_pairs, q_a_extractions, citations)”.
  • §6.5 line 587: “Per intended-arch §4.3, the new polymorphic citations table covers q_a_pair-as-citation-source AND q_a_pair-as-citation-target. This spike inherits the design verbatim. Migration sketch in §10.”
  • §10 does NOT contain a citations migration. Migrations 1-4 cover q_a_pairs + q_a_pair_history + q_a_extractions + RPCs + legacy backfill — no citations.
  • §11 Q-S16-5: “Citations table migration — intended-arch §4.3 specifies a new polymorphic citations table; this spike inherits it but doesn’t fully migratecontent_citations… Migration plan for the cutover is out of scope.”
  • §10.3 risk table mentions “Migration 5 (post-cutover cleanup — NOT in this spike)” which drops content_items.answer_standard + answer_advanced — but does NOT mention citations migration.

Effect: A reader believes 3 tables are migrated by S16’s plan; actually 2 + history. Citations is deferred. Fix S16 §1 to match §6.5/Q-S16-5 deferral.

A2. §1 section reference error (CRITICAL).

  • §1 line 26: “Sketch in §8 — staged across 4 migrations.”
  • Migration sketch is in §10, not §8. §8 is “Ingest envelope spec”.
  • §2.2 step 7 “Migration plan — §10” is correct; §1 has the wrong cross-ref.

A3. Vector index type ivfflat deviates from KH house pattern hnsw (HIGH).

S16 prescribes USING ivfflat (... vector_cosine_ops) WITH (lists = 100) for q_a_pairs.question_embedding and q_a_extractions.question_embedding (§6.2 line 454-455, §6.3 line 542-543, §10.1 migrations 1+2). This is repeated 4 times. The existing KH house pattern, per supabase/migrations/20260416102457_pre_squash_reconciliation.sql lines 4619 + 4683, is:

CREATE INDEX idx_content_chunks_embedding ON content_chunks
USING hnsw (embedding vector_cosine_ops)
WITH (m=16, ef_construction=64);

ivfflat needs corpus population before tuning lists (otherwise recall degrades severely on the empty table at migration apply time); hnsw doesn’t have that warm-up requirement and matches the precedent already deployed for content_chunks and content_items.embedding. S16 should either (a) use hnsw to match house pattern, or (b) acknowledge the deviation with justification.

A4. §3.2 + §4.7 adapter-count mismatch (HIGH).

  • §3.2 (“Three table formats recognised + 2 positional fallbacks”): correctly counts 5.
  • §4.7 verdict line 296: “The current pipeline covers only 2 of 5 (Pattern A + B)” — this drops Patterns C, positional_5col, positional_6col from the count.
  • §6.3 line 493-501 extractor_kind CHECK enumerates docx_pattern_a, docx_pattern_b, docx_pattern_c, docx_pattern_positional (4 docx adapters) plus 3 new (markdown_heading_v1, yaml_frontmatter_v1, llm_extraction) — total 7.
  • extract-qa-pairs.ts lines 164-169 declares 5 TableFormat values: audit_6col, draft_5col, numbered_6col, positional_5col, positional_6col.

Effect: §4.7 says “build 3 missing adapters” which leaves Patterns C + 2 positionals unaccounted for. Either keep all 5 docx adapters (clearer story) or collapse positional fallback into the LLM-extraction tier explicitly. The CHECK enum in §6.3 partially fixes this (4 docx values) but elides the distinction between positional_5col and positional_6col. Decide and unify.

A5. Effort estimate is unanchored (HIGH — Liam-flagged).

§12.1 line 1510-1520 breakdown:

ItemEstimate
Migrations 1-4 + revokes + RLS2-3 days
Migration 5 (post-cutover cleanup)1 day
lib/qa/ codebase5-7 days
Pass Q&A LLM extractor3-4 days
q_a_search + q_a_get + q_a_create MCP2-3 days
Pipeline orchestrator evolution2-3 days
Test coverage3-4 days
Doc update1-2 days
Total~19-27 days
Concurrent~8-10 wall-clock days

No basis declared (traditional dev-team weeks? AI-paired velocity? historic KH spike-velocity?). The 8-10 wall-clock-concurrent figure compounds the unanchored estimate by assuming clean parallelism across 8 work-streams — but Migrations 1-4 sequentialise (Migration 2 forward-declares an FK from Migration 1, Migration 4 backfill depends on 1+2 being live), the MCP tools depend on RPC migrations, and the pipeline orchestrator depends on the lib/qa/ codebase. Realistic concurrency is closer to 4 streams (migrations sequential; MCP tools after RPC migration; adapter dev + LLM extractor in parallel; tests overlapping each). A 2-3x ratio of impl-days to wall-clock-days is optimistic without team-size disclosure.

Recommendation: mark §12.1 as CALIBRATION-PENDING per Liam’s S230-start guidance. Either: (a) declare the velocity assumption explicitly (e.g. “1 AI-paired engineer at KH historic pace”); (b) drop the wall-clock figure entirely (too speculative); or (c) replace with a phase ordering only (“migrations → RPCs+MCP → pipeline → LLM extractor → tests → docs”) with no day-count.

A6. §4.7 “6 client files” framing undercounts the corpus (MEDIUM).

§4.7 header: “shape diversity in 6 client files”. The actual corpus inspection in §4.1-§4.5:

  • Shape A: 4 files (4 final docx)
  • Shape B: 1 file (1 DRAFT FAQs docx) — §4.2 names only the FAQs DRAFT but docs/client-documentation-base/binary/ has 3 DRAFT docx (FAQs, Implementation & Support, Security & Compliance) per ls verification
  • Shape C: 1 file (Phew-Bid-Library-2026-v4_4.md)
  • Shape D: 3 files (LMS / Website / Advanced_Audits)
  • Shape E: 1 file (Advanced_Audits, also counted in Shape D)
  • Shape F: 2 files (forms)

Total unique files inspected: 11+ (assuming all 3 DRAFTs reviewed for Shape B; 2 forms for Shape F). §4.7’s “6 client files” undercounts and disagrees with the §4.7 table column “File(s)” which separately says “4 final .docx” + “3 DRAFT .docx” + “1 .md (86KB)” + “3 .md” + “1 .md (mixed Shape D)” + “1 .xlsx + 1 .pdf” — sum is 14 files. §4.7 should re-count or say “5 distinct structural shapes across the corpus.”

A7. §4.2 DRAFT file list is incomplete (MEDIUM).

§4.2 names only one DRAFT file: DRAFT 2026 Tender and Bid Library Template for Phew - FAQs - Copy (1).docx. But binary/ has 3 DRAFTs:

  • DRAFT 2026 Phew - Tender and Bid Library - Implementation & Support .docx
  • DRAFT 2026 Tender and Bid Library Template for Phew - FAQs - Copy (1).docx
  • DRAFT 2026 Tender and Bid Library Template for Phew - Security and Compliance - Copy.docx

The §4.7 summary table line correctly says “3 DRAFT .docx” but §4.2 only inspects one. Either expand §4.2 or note that the other two are structurally identical.

A8. §3.2 claim “128-entry HEADER_MAP normalises ~30 column-header variants” is undercounted (MEDIUM).

lib/bid-library-ingest/extract-qa-pairs.ts HEADER_MAP (lines 28-127) contains 98 entries (I count by visual inspection). The “128-entry” figure may include extract_docx_tables.py’s Python parallel (which has additional standard-selection-questionnaire entries). Either re-count or specify “TS HEADER_MAP + Python parallel combined.”

A9. §3.1 “the 395 prod rows” claim is uncited (MEDIUM).

S16 §3.1 and §6.2 line 382 reference “the existing 395 content_items q_a_pair rows”. Source is intended-arch §4.3 (line 377), which is also uncited. This figure is load-bearing for Migration 4 backfill sizing. Should be re-verified via:

SELECT COUNT(*) FROM content_items WHERE content_type = 'q_a_pair' AND archived_at IS NULL;

…and explicitly sourced (or marked “approximate pre-launch count”).

A10. §6.5 vs §1 “citations table” claim mismatch (MEDIUM).

§6.5 line 587: “Per intended-arch §4.3, the new polymorphic citations table… This spike inherits the design verbatim. Migration sketch in §10.”

§10 does not have a citations migration. §11 Q-S16-5 acknowledges this:

“Citations table migration — intended-arch §4.3 specifies a new polymorphic citations table; this spike inherits it but doesn’t fully migrate content_citations… Migration plan for the cutover is out of scope.”

§6.5 is contradicted by Q-S16-5. Pick one position and reconcile (either §6.5 must say “deferred — see Q-S16-5” or include citations in §10).

A11. §1 line 9 quotes Liam but the trigger is paraphrased (LOW).

§1: “Spike trigger: Liam clarification at S230 start — ‘We are in a position to be able to define what Q&A pairs should look like before they’re added to the main documentation file… Each client will be different, and it’s likely that we need to make sure that the format is suited to however we need it to be.’” — no traceable source pointer (continuation-prompt? session log?). Add a footnote or session reference for audit traceability.

A12. §3.3 import_bid_library.py claim “909 LOC” undercounted (LOW).

S16 §3.3 names “909 LOC”. Verified: wc -l scripts/import_bid_library.py returns 909. Accurate.

A13. extractQaPairs LOC count “497 LOC” (LOW — accurate).

S16 §3.2 says “497 LOC”; wc -l lib/bid-library-ingest/extract-qa-pairs.ts returns 497. Accurate.

A14. §6.7 line 615 “metadata key reference” claim is vague (LOW).

§6.7 row 8 says “Promoted section_name / table_index / row_index from metadata to typed columns; added source_section_path TEXT[] + source_id_token TEXT” — the source for “section_name / table_index / row_index live in metadata today” is §36 of SCHEMA-QUICK-REFERENCE.md (cited in §2.1 materials list) but §6.7 doesn’t explicitly point there. Add cite for traceability.

A15. auto_v1_history_on_q_a_pair_insert trigger name (LOW).

§6.2 line 469 names a new trigger auto_v1_history_on_q_a_pair_insert, “mirror of auto_v1_on_insert”. The real trigger on content_items is ensure_v1_history_at_commit (per supabase/migrations/20260428174512_add_ingest_source_to_content_items.sql). The string auto_v1_on_insert is a change_reason value, not a trigger name (see migration lines 34-90). Pick the right naming convention.


B1. North Star alignment is strong (PASS).

0.9-context.md §1.1 + §1.2: “data-layer-first; AI consumer first; KB IS the product; we don’t compete, we complement.” S16 satisfies these:

  • AI-consumer-first: §6.1 design principle 3 + §9 MCP tool contract make Q&A retrievable for LLM consumers via two-step Shape B pattern.
  • KB is the product: §6 promotes q_a_pairs to first-class golden record, removing it from content_items.content_type='q_a_pair' confusion.
  • One record, many views: §6.1 design principle 1 (golden + cache + history); §6.5 polymorphic citations.
  • Complement not compete: §9.2 keeps search_knowledge_base_strict as the corpus surface; adds q_a_search as Q&A-specific surface — doesn’t replace, augments.

B2. S228 OQ ratifications: aligned (HIGH — PASS).

  • OQ1 (write-back) ratified — S16 §6 + §10 design honour edit-flow §6 ratified Candidate A/B/C set (UC5 KH-DB-only, UC6 user-direct Candidate-N/A, AI-suggest Candidate B, UC8 v1 Candidate A + LLM-reasoning, UC9 KH-native). §6.2 design adds promoted_from_bid_response_id (UC5), superseded_by (UC8), provenance (UC8 substrate readiness), scope_tag/anti_scope_tag (UC9). Verifiable trace to edit-flow §6.5/§6.6/§6.8/§6.9.
  • OQ3 (SMB data-fix critical) ratified — S16 honours via dedupe-with-temporal substrate readiness (superseded_by, valid_from/valid_to) per CX.22 + S10 deferral.
  • OQ4 (mempalace adopted) ratified — S16 §5.3 + §6.1 explicitly defers to S3 §5.3 “miner is wrong tool for KH’s Q&A extraction; borrow schema, not extraction logic.” Aligned.

B3. Q3.5 [RATIFY-AT-REVIEW] flag is preserved (HIGH — PASS).

S16 §1 line 11 (“medium on… Pattern A/B parser fate (interacts with Q3.5 [RATIFY-AT-REVIEW])”), §2.3 line 66 (“Q3.5 Pattern A/B parser final fate. Working recommendation per intended-arch §6.3 is ‘repurpose as corpus-mining tool’; this spike treats that as the working assumption but flags the [RATIFY-AT-REVIEW] status.”), §13 drag-from list, §11 Q-S16-3 all carry forward the unratified status correctly.

B4. Inheritance from S12 provenance + confidence ratified shape: aligned but partially overclaimed (MEDIUM).

S12 §6 G12 verdict: provenance text CHECK + numeric confidence(3,2) on entity_relationships only. S16 §6.2 line 404-407 + §6.3 line 513-516 apply the same shape at q_a_pairs + q_a_extractions row level — this is a reasonable extension but goes beyond S12’s scope. S12 §7 follow-on flag 1 says: “entity_mentions parallel adopt. The same column shape applies to entity_mentions.confidence… Decision deferred but flagged.” S16 extends this to q_a_pairs without acknowledging S12 deferred the broader question.

Defensible: S12 ratified the SHAPE; S16 applies it to a new domain on the same precedent. But S16 should explicitly say “applying the S12 shape pattern beyond entity_relationships per follow-on flag 1; the broader cross-table provenance question is opened by this spike.”

B5. S3 Shape B two-step pattern: aligned (MEDIUM — PASS).

S16 §9.3 (“Two-step retrieval rationale (S3 §4.1 pattern)”) + §1 line 23 (“Mirrors mempalace’s mempalace_list_drawers + mempalace_get_drawer exactly”) + §9.1.1 q_a_search returning preview-only + §9.1.2 q_a_get returning verbatim. Maps 1:1 to S3 §4.1 ratified list→preview / get→verbatim pattern.

S3 §4.2 also flagged mempalace_search “BROKEN upstream” (later fixed S230 WP8 per CLAUDE.md update). S16 doesn’t depend on that broken path — KH has its own pgvector + tsvector hybrid per §6.2 indexes. Consistent.

B6. S2 cocoindex localfs Δ semantics: aligned (MEDIUM — PASS with caveat).

S2 §6.1 #1 + §4.3 ratifies content_fingerprint cannot detect DRAFT-vs-final near-duplicates. S16 §6.3 line 524-528 captures this correctly:

“Cocoindex Δ invalidation. Set when source file’s content_hash changes (cocoindex post-flow trigger). Once invalidated, NOT auto-removed — kept for audit + comparison; new extraction rows emitted for the new content.”

S16 §8.4 (“rows are immutable extraction snapshots”) + §11 Q-S16-8 (DRAFT-vs-final pattern detection deferred to S10 substrate) correctly defer the cross-record dedup substrate decision.

Caveat: S16 §4.2 line 219 says: “Per import_bid_library.py --auto-supersede, ingesting a final after the corresponding DRAFT auto-flips the DRAFT row’s superseded_by to the final row when content-hash matches AND filename heuristic recognises DRAFT→final transition.”

This claim about the existing pipeline is mostly true (verified via §3.3 step 11: “auto-supersede if --auto-supersede flag set”), BUT the auto-supersede works on content_items.content_text_hash exact match + filename heuristic — DRAFT-vs-final files in binary/ have fully distinct fingerprints per S2 §2.5, so content-hash exact match does NOT fire on the canonical corpus. The current auto-supersede only fires on byte-identical re-uploads. S16 implies it handles DRAFT→final supersession via “filename heuristic” alone, which is a narrower mechanism than implied. Tighten the language.

B7. CX.22 re-ingest reliance: aligned (MEDIUM — PASS).

S16 §10.4 + §11 Q-S16-9 + §13 confidence section all reference CX.22 “no backfill — re-ingest” correctly. Migration 4 (legacy backfill) is positioned as a safety net for any rows that survive re-ingest — consistent with intended-arch §4.2 and decision-graph CX.22 RESOLVED.

B8. UC9 anti_scope_tag column adoption: aligned (LOW — PASS).

Edit-flow §6.9.1 4.8.Q6 RATIFIED: “Separate field on each scope-tag definition + dedicated anti_scope_tag array on each record.”

S16 §6.2 line 399: anti_scope_tag TEXT[] NOT NULL DEFAULT '{}'. Matches.

B9. UC5 promoted_from_bid_response_id: aligned (LOW — PASS).

Edit-flow §6.5 RATIFIED — UC5 KH-DB-only, “no candidate”. Q&A pair created with origin_kind='derived_from_bid_response', source_content_item_id pointing to the relevant content_item the response cited.

S16 §6.2 line 377-383 has promoted_from_bid_response_id UUID REFERENCES bid_responses(id) + origin_kind 'derived_from_bid_response'. Matches.

B10. UC6 multi-Q&A atomicity (4.3.Q6) + history op_id: aligned (LOW — PASS).

Edit-flow §6.6.1 row 4.3.Q6 RATIFIED: “Yes — shared op ID supports multi-Q&A transactional revision.”

S16 §6.4 line 576-577: operation_id UUID on q_a_pair_history. Matches.

B11. content_chunks vs chunks naming: corrected (LOW — PASS).

Intended-arch §4.3 uses chunks (wrong — no such table). S16 §6.2 + §6.3 + §10.1 all use content_chunks (correct). S16 silently corrected the intended-arch typo — a non-bug improvement. Worth noting in §6.7 delta list as #15 if S16 is edited.


4. Schema correctness audit (Liam-flagged Special Check B)

Section titled “4. Schema correctness audit (Liam-flagged Special Check B)”

For each proposed column / constraint / index / trigger in §6.2 (q_a_pairs) and §6.3 (q_a_extractions), checked against CLAUDE.md gotchas + KH conventions:

ElementCLAUDE.md / conventionVerdict
provenance TEXT NOT NULL DEFAULT 'EXTRACTED' CHECK (...)”Prefer proper schema” + S12 §5.2 Shape B precedentPASS
confidence NUMERIC(3,2) NOT NULL DEFAULT 1.0KH entity_relationships.confidence precedentPASS
valid_from TIMESTAMPTZ NOT NULL DEFAULT NOW()”timestamptz throughout”PASS
valid_to TIMESTAMPTZ (nullable = currently valid)mempalace S3 §3.5 + intended-arch §4.4PASS
publication_status TEXT CHECK (...)matches content_items.publication_status (SCHEMA-QUICK-REF §35)PASS
governance_review_status TEXT CHECK (...)matches content_items.governance_review_status (SCHEMA-QUICK-REF §35)PASS
review_cadence_days INTEGER CHECK (BETWEEN 1 AND 1095)matches content_items.review_cadence_days (SCHEMA-QUICK-REF §35)PASS
superseded_by UUID REFERENCES q_a_pairs(id) ON DELETE SET NULL + CHECK (superseded_by IS NULL OR superseded_by <> id)matches content_items.superseded_by pattern (SCHEMA-QUICK-REF §1 row 135)PASS
archived_at + archived_by + archive_reason + enforce_archive_state_consistency-style triggermatches content_items pattern (SCHEMA-QUICK-REF §1 row 118)PASS
created_at/updated_at + created_by/updated_bystandardPASS
q_a_pair_history separate from content_historyedit-flow §6.6.1 4.3.Q6 RATIFIED multi-Q&A op supportPASS
RLS policies (workspace member SELECT, editor/admin INSERT/UPDATE, admin-only DELETE)matches KH get_user_role() pattern (CLAUDE.md “RLS: role-based”)PASS
REVOKE EXECUTE ON FUNCTION public.q_a_search(...) FROM anon (Migration 3)CLAUDE.md “Supabase auto-grants anon EXECUTE” gotcha — requiredPASS
SET search_path = public, extensions in q_a_search definitionCLAUDE.md “All new PL/pgSQL functions MUST include…”PASS
No GENERATED ALWAYS column proposedavoids content_text_hash gotcha — cleanPASS
No JSONB for typed data (typed columns for source_section_path, source_id_token, source_file_basename etc.)”Prefer proper schema — tables and columns over JSONB”PASS
Metadata JSONB on q_a_extractions only, with comment “Avoid using this for first-class data”acknowledges overflow-only patternPASS

4.2 NOTE — deviations from convention (defensible but worth flagging)

Section titled “4.2 NOTE — deviations from convention (defensible but worth flagging)”
ElementIssueSeverity
q_a_pairs.workspace_id UUID NOT NULL REFERENCES workspaces(id) ON DELETE RESTRICTKH convention for FK to workspaces is ON DELETE CASCADE (see content_item_workspaces, feed_articles, feed_prompts, feed_sources in 20260416102457_pre_squash_reconciliation.sql lines 5366-5471). RESTRICT is defensible (Q&A pairs are high-value curated artefacts; refuse workspace deletion if Q&A pairs exist) — but should be acknowledged + justified.MEDIUM
q_a_extractions.workspace_id UUID NOT NULL REFERENCES workspaces(id) ON DELETE CASCADECASCADE here is fine (extractions are derivative). But the difference between CASCADE on extractions and RESTRICT on pairs is potentially confusing. Comment it.LOW
layer VARCHAR REFERENCES layer_vocabulary(key) ON DELETE SET NULL on q_a_pairscontent_items.layer is varchar with no FK (validated at app level per SCHEMA-QUICK-REF §1 row 131 + §20 row 1069). S16 proposes a hard FK on q_a_pairs.layer that doesn’t exist on the parent table. Defensible (tighter than house) but not consistent. Either match content_items (app-level only) OR justify the upgrade + apply to content_items too.MEDIUM
USING ivfflat (... vector_cosine_ops) WITH (lists = 100)KH convention is USING hnsw (... vector_cosine_ops) WITH (m=16, ef_construction=64) (see A3 above).HIGH
q_a_search declared LANGUAGE plpgsql STABLE SECURITY INVOKERHouse pattern for read-only RPCs is SECURITY INVOKER (per 45 batch 3+4 flips in 20260506091039 + 20260506115705). Matches. But the function is plpgsql returning TABLE — KH also has LANGUAGE sql variants where pure SQL suffices. Either is fine; plpgsql is more flexible for the UNION ALL + as_of param logic.LOW
re_embed_q_a_pair_on_question_change trigger sets question_embedding=NULL for “deferred embedding pickup”Pattern not in current KH triggers — no precedent for NULL-on-update-embedding-trigger style. Acceptable as a v1 hand-off, but the pickup mechanism (background job? on-read embedding generation?) isn’t specified. Worth a follow-on flag.MEDIUM
ElementIssueSeverity
Migration 1 q_a_pairs.promoted_from_extraction_id UUID, -- FK added in migration 2 (forward declaration)Two-step FK declaration is correct for cross-table circular reference. But Migration 1’s CREATE TABLE has the column with NO type-annotation hint (just UUID). The ALTER TABLE in Migration 2 (line 1214-1216) correctly adds the constraint. Concern: if Migration 1 succeeds but Migration 2 fails partway through, q_a_pairs has an orphaned column without FK. Mitigation: add a NOT VALID + VALIDATE pattern OR wrap migrations 1+2 in a single transaction. Either fix the migration sequencing or add explicit rollback in §10.3.MEDIUM
q_a_pair_history change_type CHECK includes 'governance_review_action' (§6.4 line 572 + §10.1 Migration 1 line 1093)Inconsistent with content_history precedent. Verify by checking SCHEMA-QUICK-REF §10 content_history.change_reason allowed values. (Reader observation — not verified live in this audit; flagged for fix-up.)LOW
q_a_pairs has NO unique constraint on (workspace_id, question_text) or any natural keyA single workspace could accumulate duplicate Q&A pairs identical at question_text. UC8 deduces via embedding similarity, but a defensive UNIQUE on (workspace_id, lower(trim(question_text))) would prevent trivial double-inserts. Worth a follow-on flag (deferred to S10 substrate).LOW
q_a_extractions has NO unique constraint on (source_content_item_id, extractor_kind, extractor_version, source_table_index, source_row_index)Re-running the same extractor on the same source row should be idempotent OR explicitly emit a new row. S16 §8.4 (“rows are immutable extraction snapshots — every adapter run on the same source content produces new immutable rows”) implies the latter, but without a clear UNIQUE constraint, two simultaneous flow runs could double-insert. Acceptable for v1; document the choice.LOW
q_a_search RPC SQL signature (Migration 3 line 1262-1273) takes p_query_embedding vector(1024) but the TS input shape (§9.1.1) only takes query: stringThe embedding is computed somewhere between TS input and SQL call — likely the MCP tool handler. This is fine but should be stated explicitly in §8.1 writer responsibilities (already mentions question_embedding generation) or §9.1.1 implementation note.LOW
q_a_search UNION ALL across q_a_pairs + q_a_extractions assumes extracted kind doesn’t compete for ranking with curatedThe cosine similarity is the only ranking signal; in practice users will want curated to dominate. Worth a confidence_boost or kind_priority term in the ORDER BY.LOW

5. Migration sketch audit (Liam-flagged Special Check C)

Section titled “5. Migration sketch audit (Liam-flagged Special Check C)”
MigrationSequenceVerdict
1: q_a_pairs + q_a_pair_history + RLSFirst (additive)PASS
2: q_a_extractions + FK back to q_a_pairs.promoted_from_extraction_id + RLSSecondPASS — forward-decl FK handled
3: RPCs (q_a_search + REVOKE EXECUTE FROM anon)ThirdPASS
4: Backfill legacy from content_items.q_a_pair rowsFourthPASS-WITH-CAVEAT
5 (post-cutover, NOT in this spike): drop content_items.answer_standard/answer_advanced + remove q_a_pair from content_type CHECKFuturePASS-WITH-CAVEAT

Migration 3 has explicit REVOKE EXECUTE ON FUNCTION public.q_a_search(...) FROM anon; — PASS per CLAUDE.md gotcha.

S16 §10.2 line 1467: “every CREATE FUNCTION in migrations 1-4 must have its companion REVOKE EXECUTE… FROM anon.” Migrations 1, 2, 4 don’t create user-callable functions (only triggers — also need revoke per CLAUDE.md “Trigger functions called only via trigger context still get the revoke”). The triggers in Migration 1 are referenced in line 1120-1124 as placeholders (“definitions in companion migration”). This is a gap: the trigger functions (enforce_q_a_pair_archive_state_consistency, auto_v1_history_on_q_a_pair_insert, update_q_a_pair_updated_at, re_embed_q_a_pair_on_question_change) need explicit DDL with explicit revokes — currently shown only as comments. Either expand Migration 1 with the trigger DDL or add a Migration 1.5 / 1b.

Severity: HIGH — without the trigger functions actually defined, Migration 1 is incomplete.

ConstraintVerdict
origin_kind CHECK (4 values)PASS — values are coherent + traceable to UC origins
provenance CHECK (EXTRACTED / INFERRED / AMBIGUOUS)PASS — matches S12
publication_status CHECKPASS — matches content_items
governance_review_status CHECKPASS — matches content_items
superseded_by <> id self-reference CHECKPASS — defensive
classification_confidence BETWEEN 0 AND 1PASS — matches content_items
review_cadence_days BETWEEN 1 AND 1095PASS — matches content_items
extractor_kind CHECK (7 values)PASS — see A4 note about adapter count (docx_pattern_positional collapses 5-col + 6-col)
change_type CHECK (10 values) on q_a_pair_historyPASS — comprehensive
FK workspace_id → workspaces(id) ON DELETE RESTRICT (pairs) / CASCADE (extractions)NOTE — see §4.2
FK source_content_item_id → content_items(id) ON DELETE SET NULL (pairs) / CASCADE (extractions)PASS — inconsistency between pairs (SET NULL) and extractions (CASCADE) is defensible but should be commented
FK promoted_to_pair_id → q_a_pairs(id) ON DELETE SET NULLPASS
FK verified_by, governance_reviewer_id, created_by, updated_by, archived_byauth.users(id) ON DELETE SET NULLPASS — matches KH SET NULL house pattern
layer VARCHAR REFERENCES layer_vocabulary(key) ON DELETE SET NULLNOTE — see §4.2

Migration 4 backfills q_a_pairs from content_items rows where content_type='q_a_pair'. Concerns:

ConcernSeverity
c.title AS question_text — legacy stores question in title, OKLOW
COALESCE(c.answer_standard, c.content) AS answer_standard — fallback to content field if answer_standard NULL is reasonable but c.content includes “Q: …” prefix per build_content_record in import_bid_library.py lines 221-232 — would copy the prefix into answer_standard. Strip “Q: \n\n” before COALESCE.MEDIUM
c.id AS source_content_item_id PLUS c.id (as q_a_pairs.id) — preserves UUID for citation FK continuity. PASS — but the FK on q_a_pairs.id now references content_items.id which is about to lose its q_a_pair content_type. Document the foreign-key relationship survival.LOW
Workspace lookup via content_item_workspaces first-row LIMIT 1 — “multi-workspace q_a is post-v1”. Acknowledged in §10.3 risks table. PASS-WITH-CAVEAT.LOW
Sentinel UUID 00000000-0000-0000-0000-000000000000 for unassigned workspace — what RLS sees on this row? Will it match any workspace policy? Need explicit handling.MEDIUM
c.embedding AS question_embedding — copies the content embedding into question_embedding column. But the content embedding was generated over title + summary + content (per kb_pipeline/embed.py build_embedding_text), NOT over question text alone. The post-migration question_embedding will be semantically wrong for “match this question shape” queries. Either re-embed on backfill OR mark for background re-embedding.HIGH
'EXTRACTED' AS provenance, 1.0 AS confidence for all legacy rowsLOW — defensible, mimics S12 §5.5 default
COALESCE(c.user_tags, '{}'::TEXT[]) AS scope_tag — repurposes user_tags as scope_tag. Per intended-arch §4.2, content_items already has separate scope/anti-scope tag columns added pre-spike (Q1.7 RESOLVED). S16 doesn’t reference the scope-tag columns on content_items. If scope_tag has been added to content_items, prefer that source over user_tags.MEDIUM
ON CONFLICT (id) DO NOTHING — idempotent re-run safety. PASS.LOW
Migration 4 does NOT remove q_a_pair from content_items.content_type CHECK — left for “Migration 5” deferred. PASS.LOW
Migration 4 does NOT delete the legacy content_items rows. Leaves them as orphans with the legacy content_type=‘q_a_pair’. Until Migration 5 cuts them out, search queries that don’t know about q_a_pairs will return duplicates. Acknowledge in §10.4 “re-ingest plan compatibility.”MEDIUM

5.5 Migration 5 (post-cutover) — referenced but not specified

Section titled “5.5 Migration 5 (post-cutover) — referenced but not specified”

§10.3 row 5: “Dropping content_items.answer_standard + answer_advanced breaks any code path still reading them. Knip + grep audit pre-migration 5; gated on MCP + UI surface fully cut over.”

This is the right risk framing but Migration 5 is not specified in the spike. Code paths reading those columns today (per quick grep):

  • lib/bid-library-ingest/extract-qa-pairs.ts and Python parallel emit them (writer-side, irrelevant post-cutover)
  • scripts/import_bid_library.py writes them (writer-side, irrelevant post-cutover if orchestrator migrates)
  • supabase/types/database.types.ts — auto-regenerated, no concern
  • Any API route reading content_items + returning answer_standard/advanced — need grep + audit before Migration 5

S16 §10.3 + §11 should add a note: “Migration 5 prerequisites: full Knip pass + grep audit for answer_standard|answer_advanced reads in app/api/, lib/bid/, lib/content/, lib/mcp/tools/, hooks/bid/. Don’t drop columns until all reads migrate to q_a_pairs.*.”


6. Hybrid recommendation audit (Liam-flagged Special Check D)

Section titled “6. Hybrid recommendation audit (Liam-flagged Special Check D)”

Per the spike trigger at S230-start (paraphrased in §1):

“We are in a position to be able to define what Q&A pairs should look like before they’re added to the main documentation file… Each client will be different, and it’s likely that we need to make sure that the format is suited to however we need it to be.”

Two distinct asks:

  1. Adapt mempalace’s approach for KH Q&A — what is the architectural pattern that mempalace uses for Q&A, and how do we apply that pattern to KH?
  2. Define Q&A format pre-v1 — what does a v1 KH-canonical Q&A pair definitively look like?

Hybrid recommendation:

  • Path A (opinionated YAML-frontmatter template): docs/templates/client-qa-bundle-template.md — a NEW reference doc (not in this spike’s deliverable) — shows real YAML frontmatter per pair separated by ---. Schema in §7.2.
  • Path B (accept-any adapter dispatcher): for any file that doesn’t match the canonical template, route to the appropriate parser. Coverage table in §7.3.

S16 §7.4 explains the trade-off: pure A loses Phew’s existing 6-shape corpus; pure B over-uses LLM-extraction; hybrid lets clients use what they have AND offers the canonical template.

Question 1 (mempalace approach): PARTIALLY answered. S16 §5.3 + §6.1 explicitly defers to S3: mempalace miner is wrong for KH Q&A; borrow the schema (Shape A temporal) + retrieval pattern (Shape B two-step). This is correctly the “borrow design, not extraction” answer, but it’s distributed across §5.3 and §6.1 design principle 2 + §9 MCP shape. A consolidated “mempalace adoption for Q&A: Shape A schema + Shape B retrieval + KH-owned extraction” paragraph would make this clearer.

Question 2 (v1 canonical Q&A pair): PARTIALLY answered with hedging.

The hybrid does NOT commit to a single canonical wire shape — it commits to:

  • A canonical database shape (q_a_pairs table, 30+ columns) — clear.
  • A canonical envelope shape (Zod schema in lib/qa/envelope.ts) — clear.
  • A canonical client-authored shape (YAML frontmatter template) — RECOMMENDED, not REQUIRED.
  • A canonical MCP retrieval shape (q_a_get output) — clear.

The “what does the v1 KH-canonical Q&A pair look like, definitively” question is answered for the DB and the retrieval surface but explicitly NOT for the client-authored input (the YAML template is “the happy path” — clients with existing libraries can keep them).

This is the right call architecturally (forcing all clients to reformat their content pre-onboarding has a high friction cost), but Liam asked specifically about pre-v1 definition. The spike’s answer is “two shapes: the recommended one + 5+ accept-any adapters” which IS a definition, just not a single-shape one. Worth restating in §1 executive summary that the question Liam asked is “what’s the definitive shape” and S16’s answer is “definitive at the DB/envelope/retrieval layers; recommended-but-not-mandatory at the client-authored input layer.”

  • No commitment on v1 vs v1.1 split. What’s required for v1? The YAML template? All 5 adapters? Pass Q&A LLM? S16 §13 Confidence section has “Drag from: … Q3.5 Pattern A/B parser fate … S10 substrate not yet decided” but doesn’t say “the hybrid recommendation works for v1 with only Patterns A/B + YAML template + LLM-extraction fallback; markdown_heading_v1 is v1.1.” Either commit to “all 5 adapters v1” (which §12.1 implies via the 5-7 day estimate) or split.
  • No criterion for “when LLM-extraction fires.” §7.3 detection logic says “if has_heading_qa_pattern → markdown_heading_v1; else → llm_extraction” — but has_heading_qa_pattern heuristic isn’t defined. Q-S16-4 notes this is implementation-phase work, but the heuristic choice gates cost (deterministic adapters are ~free; LLM is ~$0.03/chunk per §12.2).
  • No A/B test plan. If Phew has 5 distinct shapes, which ones MUST work cleanly to ship? Define an acceptance bar.

Severity: MEDIUM. The hybrid is the right answer; the spike just under-resolves what “v1” means at the client-input boundary.


7. Inheritance claims audit (Liam-flagged Special Check E)

Section titled “7. Inheritance claims audit (Liam-flagged Special Check E)”
S16 claimS12 sourceVerdict
”provenance text CHECK + numeric confidence(3,2) on q_a_pairs + q_a_extractions”S12 §6 G12 verdict — applies to entity_relationships, deferred to entity_mentions per §7 flag 1PARTIAL — S12 didn’t ratify cross-table application; S16 extends per follow-on flag 1 pattern. Acknowledge.
”S12 confidence_score rubric for INFERRED” (§6.1 principle 4 + §8.2)S12 §2.2 rubric values 0.55/0.65/0.75/0.85/0.95 + AMBIGUOUS 0.1-0.3PASS — S16 doesn’t mis-cite the rubric
Discrete 0.4-0.5 empty band escalationS12 §2.2 + §5.2PASS — S16 implicit via “extraction_confidence NUMERIC(3,2)” but the discrete-bucket prompt-engineering rule lives in Pass Q&A prompt (Q-S16-4, deferred)
S16 claimS3 sourceVerdict
”Shape A temporal-aware: valid_from / valid_to / confidence / provenance”S3 §3.5 confirmed 1:1 portable with 4 additive columnsPASS
”Two-step Shape B pattern: list/preview → get/verbatim”S3 §4.1 ratified mempalace_list_drawers + mempalace_get_drawer patternPASS
”Mempalace miner is the wrong tool for KH’s Q&A extraction”S3 §5.3 + §5.4 + §5.6 + §8PASS
”mempalace_search broken upstream”S3 §4.2 — updated to FIXED in S230 WP8 (CLAUDE.md note + v3.3.5 PR #1396)NOTE — S16 doesn’t mention the fix; not load-bearing, but a freshness gap
”Borrow schema; not extraction logic”S3 §5.6 final verdictPASS
S16 claimS2 sourceVerdict
”cocoindex Δ on a source file invalidates corresponding q_a_extractions”S2 §4.2 — cocoindex marks add/edit/delete/rename/binary-replace; per-key invalidation cleanlyPASS for “edit” + “binary-replace” cases; for “delete” the engine purges (not invalidate). S16 §6.3 line 524-528 + §8.4 correctly distinguish “invalidate” (tombstone, keep audit) from “engine-purge” (delete). PASS.
”cocoindex 1.0.3” / “localfs only v1”S2 §1 + §5 G2 verdictPASS
”Pattern A/B/C in S16 §3.2 as extractQaPairs codepaths”extract-qa-pairs.ts lines 164-247 + S2 §2.5 DRAFT vs final fingerprint observationPARTIAL — see A4 (mismatch between “Pattern A/B” framing and the actual 5 codepaths)
S16 claimEdit-flow §6 sourceVerdict
UC5 KH-DB-only, promoted_from_bid_response_id (§6.2 line 377)§6.5 RATIFIEDPASS
UC6 user-direct = KH-DB-only; UC6 AI-suggest Candidate B (§6.6.1 4.3.Q8) — q_a_pairs golden record, Pass 2 source-change detection§6.6 RATIFIED + §6.6.1 4.3.Q1PASS — Q-S16-6 promotion UX deferred is consistent
UC8 v1 Candidate A + LLM-reasoning; superseded_by column + supersession trigger pattern§6.8 RATIFIED + §6.8.1 4.7.Q11 DRAFT-vs-final filename heuristicPASS — substrate (S10) gating acknowledged
UC9 scope_tag + anti_scope_tag arrays (§6.2 lines 398-399)§6.9.1 4.8.Q6 RATIFIED multi-value arrayPASS
UC9 search-filter boolean expression (§9 line 851)§6.9.1 4.8.Q8 RATIFIEDPASS
Citation-immutability + version-on-cite (UC6 §3.5)§6.0.3 REAFFIRMEDPASS — citations table polymorphic citing_entity is the mechanism
S16 claimContext sourceVerdict
”AI-consumer-first” via question_embedding separate from content embedding§1.1 + §1.2PASS
”Two-step retrieval prevents accidental full-corpus dump”S3 §4.1 + Shape B Lens 2PASS
Q3.5 [RATIFY-AT-REVIEW] preservedContext §7PASS

Overall inheritance audit verdict: STRONG, with one cross-table extrapolation (S12 → q_a_pairs/q_a_extractions) that should be acknowledged as such.


8. Effort estimate audit (Liam-flagged Special Check A)

Section titled “8. Effort estimate audit (Liam-flagged Special Check A)”

§12.1 breakdown total 19-27 days impl + 8-10 wall-clock concurrent days.

S16 doesn’t declare what “1 day” means: is this a traditional dev-team day-rate (8 hours of one engineer), or KH AI-paired velocity (effective ~2x), or sub-agent capacity (sub-200K-token-bound)?

S12 §6 G12 verdict declared ~1 day for its migration + Pass 2 update + write-path change + unit-test update — clearly a small scope. S3 §9 declared ~4-5.5 days total for Shape A + B + Shape C-lite + workspaces extension — medium scope. S2 §6 declared no specific day-count.

S16’s 19-27d implementation estimate is the largest in the spike series so far. Without a calibration basis, the figure is unanchored.

ItemEstimatePlausibility check
Migrations 1-4 + revokes + RLS2-3 daysPLAUSIBLE — comparable to S183/S186 supersession migrations (1-2 days) but with more tables + RLS
Migration 5 (post-cutover cleanup)1 dayPLAUSIBLE — Knip + grep + DROP — but only if pre-work to migrate all readers is complete (NOT in this 1 day)
lib/qa/ codebase (envelope, writer, adapters, dispatcher)5-7 daysOPTIMISTIC if all 5 adapters are deterministic + LLM-fallback; markdown_heading_v1 and yaml_frontmatter_v1 are NEW and untested; deterministic parsing of Shape C (free-prose with bold-question heuristic) is non-trivial. 5-7d is plausible for the dispatcher + 2-3 adapters; potentially 8-12d for all 5
Pass Q&A LLM extractor (prompt + caller + pre-filter)3-4 daysOPTIMISTIC. Anthropic prompt engineering on novel extraction targets historically takes >1d of eval iteration alone (per S12 §7 follow-on flag 4 — “fresh Anthropic eval to confirm the model emits the discrete bucket values”). 3-4d assumes the prompt lands first-try; realistic 5-7d including eval.
MCP tools (q_a_search + q_a_get + q_a_create)2-3 daysPLAUSIBLE — comparable to existing search_knowledge_base_strict MCP work but with more types + RLS gates
Pipeline orchestrator evolution2-3 daysOPTIMISTIC — import_bid_library.py is 909 LOC; replacing it with import_qa_bundle.py while preserving —auto-supersede behaviour + idempotency + classify integration + entity extraction is non-trivial. Realistic 4-6d if behaviour parity matters
Test coverage3-4 daysOPTIMISTIC — adapter unit tests + envelope round-trip tests + RPC tests + MCP integration tests + RLS tests + migration tests across 5 adapters + new envelope shape. Realistic 5-8d if comprehensive
Doc update1-2 daysPLAUSIBLE if just decision-graph + intended-arch + spike-plan; client template + onboarding docs add another 2-3d (the template needs example content + per-shape conversion script per §7.2 step 3)
Recalibrated total~30-44 days implIf above pessimistic-but-realistic adjustments are right
Concurrent (8-10 days)OPTIMISTICMigrations 1-4 + RPCs sequential = ~4-5d; orchestrator + MCP + tests can overlap = ~10-15d wall-clock with 2-3 concurrent streams

Liam flagged at S230-start: “we need to be cautious and refrain from putting too much weight behind the time frame terminology.” This audit confirms:

  • 19-27d total is OPTIMISTIC by ~30-50% on realistic plausibility check.
  • 8-10d wall-clock concurrent is OPTIMISTIC by ~50% on dependency-chain check (migrations sequentialise; testing depends on impl).
  • No calibration basis declared.

Recommendation: replace §12.1 with one of:

  • Option A: Mark as CALIBRATION-PENDING. Defer all day-counts to a downstream sizing pass once the open questions (S10 substrate, Q-S16-4 LLM extractor prompt scope, Q-S16-6 promotion UX) are resolved.
  • Option B: Replace day-counts with phase ordering: “Phase A migrations → Phase B RPCs+MCP tools → Phase C lib/qa adapters → Phase D Pass Q&A LLM extractor → Phase E pipeline orchestrator → Phase F tests + docs.” Phase ordering is the load-bearing fact; the day-counts aren’t.
  • Option C: Keep the day-counts but declare velocity assumption explicitly (e.g. “based on KH historic spike-velocity of ~1-2 work-items per day of AI-paired session”) + widen ranges (19-27 → 25-40d).

HIGH — Liam-flagged concern. The estimate is load-bearing for prioritisation conversations. Unanchored numbers mislead.


9. Open questions audit (Liam-flagged Special Check F)

Section titled “9. Open questions audit (Liam-flagged Special Check F)”
#QuestionVerdict
Q-S16-1Workspace scoping for q_a_pairsRIGHT-LEVEL — single-workspace v1 default + multi-workspace deferred is sensible
Q-S16-2TEXT[] vs q_a_question_variants splitRIGHT-LEVEL — v1 ships TEXT[] (simpler); v1.1 evaluates
Q-S16-3Q3.5 Pattern A/B parser fateRIGHT-LEVEL — flagged [RATIFY-AT-REVIEW] correctly
Q-S16-4LLM-extraction prompt + chunk pre-filterRIGHT-LEVEL — implementation-phase work
Q-S16-5Citations table migrationINCOMPLETE — should also specify whether v1 ships with new citations table OR keeps content_citations + adds polymorphism in v1.1
Q-S16-6Promotion UX (extraction → pair)RIGHT-LEVEL — implementation-phase UX
Q-S16-7Multi-language Q&ARIGHT-LEVEL — v2
Q-S16-8DRAFT-vs-final pattern detection for q_a_extractionsRIGHT-LEVEL — S10 substrate dependency
Q-S16-9Embedding cost projectionRIGHT-LEVEL — recorded
Q-S16-10kind discriminator on q_a_pairs for sales proposalsRIGHT-LEVEL — recommendation NO (citations polymorphism handles it)
Section titled “9.2 Missing OQs (recommended NEW additions)”
New OQWhy it should be added
Q-S16-11 — Trigger function definitions for Migration 1§5.2 finding: triggers shown as placeholders in line 1120-1124. Need explicit DDL.
Q-S16-12 — Migration 4 backfill question_embedding correctness§5.4 finding: re-embedding required because c.embedding is content-level not question-level. How is this done — synchronous in Migration 4, OR background job, OR via re_embed_q_a_pair_on_question_change trigger pickup?
Q-S16-13 — Migration 5 reader-side cutover audit§5.5 finding: explicit knip + grep audit of every code path reading `content_items.answer_standard
Q-S16-14 — Vector index choice (hnsw vs ivfflat)§4.2 finding: deviation from house pattern needs justification.
Q-S16-15 — q_a_pairs.workspace_id ON DELETE RESTRICT vs CASCADE§4.2 finding: deviation from house pattern needs justification.
Q-S16-16 — Calibration basis for §12.1 effort estimate§8 finding: Liam-flagged concern; declare velocity assumption or remove day-counts.
Q-S16-17 — All-5-adapters-v1 vs split (markdown_heading_v1 v1.1?)§6.4 finding: v1/v1.1 line is undeclared.
Q-S16-18 — q_a_search UNION ALL ranking — kind_priority/confidence_boost?§4.3 finding: curated vs extracted should rank-prefer curated explicitly.
Q-S16-19 — Backfill workspace sentinel UUID 00000000-... handling§5.4 finding: RLS implications of the unassigned sentinel row.
Q-S16-20 — q_a_extractions immutability invariant§4.3 finding: explicit invariant (“extractions are append-only; UPDATE only via invalidated_at trigger”) or a UNIQUE constraint preventing double-insert on concurrent flow runs.

Before S16 becomes load-bearing for the implementation phase, recommend these line-level changes:

  1. §1 line 26 — fix “3 new tables” claim: change to “2 new tables (q_a_pairs + q_a_extractions) + 1 history table (q_a_pair_history); polymorphic citations table inherited from intended-arch §4.3 — migration deferred to separate sketch per Q-S16-5”. Also fix “Sketch in §8” → “Sketch in §10”.
  2. §3.2 line 113 — re-count HEADER_MAP entries (98 in TS extract-qa-pairs.ts).
  3. §4.2 — expand to mention all 3 DRAFT docx files OR note they’re structurally identical to the FAQs DRAFT.
  4. §4.7 line 287 — re-frame “shape diversity in 6 client files” to “shape diversity across the 11+ inspected client files (5 distinct structural shapes)”.
  5. §4.7 line 296 — fix adapter-count: “current pipeline covers 5 codepaths (Patterns A/B/C + 2 positionals) for docx; loses Q&A structure for markdown shapes C/D/E”. The “2 of 5” framing is wrong about the existing code.
  6. §6.2 line 419 — add comment justifying ON DELETE RESTRICT (vs house pattern CASCADE).
  7. §6.2 line 454-455 + §6.3 line 542-543 — change USING ivfflat to USING hnsw (... vector_cosine_ops) WITH (m=16, ef_construction=64) per KH house pattern.
  8. §6.2 line 448 — add comment that layer VARCHAR REFERENCES layer_vocabulary(key) is tighter than content_items.layer (no FK there) — justify or remove FK.
  9. §6.2 line 467-472 — expand trigger placeholders into explicit DDL or move to a dedicated Migration 1b with full trigger function bodies + revokes.
  10. §6.5 line 587 — change “Migration sketch in §10” to “Migration deferred — see Q-S16-5”.
  11. §6.7 add row 15 — explicitly note “S16 silently corrected intended-arch §4.3 chunkscontent_chunks (actual table name)”.
  12. §7.4 + new §7.6 — add an “what does v1 require?” subsection: declare whether all 5 adapters land v1 or whether markdown_heading_v1 is v1.1.
  13. §8.1 line 800 — clarify where the question_embedding is generated (TS handler or RPC?).
  14. §9.1.1 Migration 3 line 1366 — confirm function signature matches the REVOKE EXECUTE call (currently uses 11 typed params; verify against the actual function signature in the SQL).
  15. §10.1 Migration 1 line 1120-1124 — expand trigger placeholder comments into real DDL (CREATE OR REPLACE FUNCTION + CREATE TRIGGER).
  16. §10.1 Migration 4 line 1430 — fix COALESCE(c.answer_standard, c.content)c.content includes “Q: …” prefix per build_content_record; strip prefix or change to COALESCE(c.answer_standard, regexp_replace(c.content, '^Q: [^\n]*\n+', '')).
  17. §10.1 Migration 4 line 1430 — explicit handling of sentinel workspace UUID (or fail loudly if no workspace assignment).
  18. §10.1 Migration 4 line 1430 — note that c.embedding AS question_embedding is semantically incorrect (content-level embedding ≠ question embedding); mark for re-embed.
  19. §10.1 Migration 4 line 1436 — prefer c.scope_tag over c.user_tags (if scope_tag column has been added per Q1.7 RESOLVED).
  20. §10.3 risks table — add Migration 5 prerequisites: “Knip + grep audit for answer_standard|answer_advanced reads in app/api/, lib/bid/, lib/content/, lib/mcp/tools/, hooks/bid/. Migration 4 leaves legacy content_items rows in place (search hides them via content_type ≠ q_a_pair); Migration 5 should drop the content_type=‘q_a_pair’ rows after the cutover is complete.”
  21. §11 add Q-S16-11 through Q-S16-20 per §9.2 above.
  22. §12.1 line 1520 — replace “~19-27 days; ~8-10 wall-clock concurrent” with CALIBRATION-PENDING marker or rewrite per §8.3 Option B (phase ordering only).
  23. §13 line 1543 — “Migration sketch — standard Supabase CLI pattern; CI lint covers anon-EXECUTE revoke” — add caveat about trigger function placeholders in Migration 1.
  24. §14 line 1576 — add “Effort estimate (§12.1) marked CALIBRATION-PENDING per Liam S230-start guidance.”

Already enumerated in §9.2 above as Q-S16-11 through Q-S16-20. Most critical:

  • Q-S16-11 (trigger DDL completeness) — gates Migration 1 correctness.
  • Q-S16-12 (backfill question_embedding correctness) — gates Migration 4.
  • Q-S16-13 (Migration 5 reader-side audit) — gates the post-cutover cleanup.
  • Q-S16-14 (vector index choice) — gates index correctness.
  • Q-S16-16 (effort calibration) — Liam-flagged.
  • Q-S16-17 (v1 vs v1.1 adapter split) — gates scope.

PASS-WITH-NOTES.

S16 is a competent, mostly-accurate design spike. The proposed schema is consistent with KH conventions where it matters (search_path, anon-EXECUTE revoke, “Prefer proper schema”, timestamptz, content_chunks not chunks, content_items lifecycle parity). The inheritance from S2 / S3 / S12 / edit-flow §6 is largely correct. The North Star alignment is strong.

Issues to fix before treating S16 as load-bearing:

  • 3 high-severity issues: §1 mis-counts new tables; ivfflat vs hnsw; trigger functions shown only as placeholders in Migration 1; effort estimate unanchored.
  • 6 medium-severity issues: Migration 4 question_embedding semantics; adapter count framing in §4.7; layer VARCHAR FK deviation; workspace_id ON DELETE RESTRICT deviation; 395 rows uncited; sentinel workspace UUID handling.
  • 10+ low-severity issues: typos, naming inconsistencies, cross-ref errors.

Confidence rating in §13 (“80% — high on column list / dedup / provenance / temporal cols; medium on client-template UX and Pattern A/B parser fate”) is defensible. Hits at HIGH end where inheritance is clean (provenance, temporal, dedup readiness); the MEDIUM areas are correctly identified.

Liam’s flag about the effort estimate (§12.1) is fully justified per §8 — recommend that be the first fix.

Recommendation: edit S16 per §10 + add OQs per §9.2 + then re-confirm. After fixes, S16 can move from PASS-WITH-NOTES to PASS and become the load-bearing reference for Q&A architecture implementation.


SeverityAccuracyAlignmentSchemaMigrationTotal
Critical20002
High31 (PASS)1 (ivfflat)1 (trigger placeholders)5 issues / 1 PASS
Medium51 (B4) + 1 (B6 caveat)3413
Low54 (PASS)4417 / 4 PASS

Total findings: 2 critical + 5 high + 13 medium + 17 low = 37 findings. Plus: 10 recommended NEW open questions (§9.2).

Verdict: PASS-WITH-NOTES.