RECOVERED findings — 7 failed StructuredOutput agents (run wf_da6fa678-4c7)
RECOVERED findings — 7 failed StructuredOutput agents (run wf_da6fa678-4c7)
Section titled “RECOVERED findings — 7 failed StructuredOutput agents (run wf_da6fa678-4c7)”==========================================================================================
E1 pipeline_runs completed-status provenance
Section titled “E1 pipeline_runs completed-status provenance”(agent a1c58c5715f1fab28; 5 StructuredOutput attempts; best=5424 chars; fields=[‘id’, ‘question’, ‘verdict’])
{
“id”: “E1”,
“question”: “What writes pipeline_runs.status=‘completed’ (the ~395/496 prod rows with 0 content_chunks) — E2E debris, a cron, or the pipeline — and do any current tests require a ‘completed’ value?”,
“verdict”: “The ‘completed’ rows are overwhelmingly cron + MCP-tool heartbeat rows, NOT ingestion-pipeline content writes. Of 397 completed rows (prod zjqbrdctesqvouboziae, 498 total — this reconciles the doc’s “~395/496”: completed≈395 and total≈496, both drifted +2 since authoring), 220 (55%) are the daily review_cadence cron, 49 more are other governance/quality crons (publish_classify 42, quality_score 3, content_gaps 2, coverage_alert 2), 121 (30%) are mcp_create_content_item (manual/dogfooding MCP writes = the “debris” leg), 5 are form_draft_all, and ONLY 2 are the real ingestion pipeline (kh_canonical_pipeline, the only ones with op_id) — and those 2 ingestion runs produced 0 content_chunks, which is the genuine “pipeline never completed a content write” gap. Owner hypothesis is confirmed AND refined: it is the review_cadence cron + MCP debris, not E2E debris from the content pipeline; crons emit ‘completed’ even with items_processed=0, so ‘completed’ is decoupled from content_chunks by design. Multiple CURRENT integration/E2E tests assert on status=‘completed’, most notably the cocoindex ingestion E2E gate which BLOCKS until status=‘completed’ — so ‘completed’ is load-bearing and asserted-upon.\n
==========================================================================================
E2 classification-col pipeline-writer audit
Section titled “E2 classification-col pipeline-writer audit”(agent a30da2aac7266a780; 5 StructuredOutput attempts; best=6188 chars; fields=[‘id’, ‘question’, ‘verdict’])
{
“id”: “E2”,
“question”: “For each of the ~25-30 classification cols M3 ADDs to source_documents, does the cocoindex pipeline CURRENTLY write it onto content_items, or is it written-elsewhere / dead / never-written?”,
“verdict”: “The cocoindex pipeline writes ONLY 3 classification cols onto content_items: content_type, primary_domain, primary_subtopic (plus title, derived from suggested_title) — flow.py:2173-2195. Every other classification-family col (secondary_domain, secondary_subtopic, ai_keywords, summary, suggested_title-as-its-own-column, classification_confidence, classification_reasoning, classified_at, updated_by) is written ONLY by the SEPARATE app-side TS classifier lib/ai/classify.ts::classifyContent, NOT the pipeline. captured_date and summary_data are not classification outputs at all (captured_date = ingest timestamp set by app item/upload routes; summary_data = the separate summarise feature). classification_model is genuinely dead — 0 of 33 prod rows, no writer anywhere — the one true vestige that should NOT be ported.\n
==========================================================================================
E4 declare_vector_index avoidance rationale
Section titled “E4 declare_vector_index avoidance rationale”(agent a780069a9c03dc40e; 5 StructuredOutput attempts; best=3807 chars; fields=[‘id’, ‘question’, ‘verdict’])
{
“id”: “E4”,
“question”: “Why does the pipeline deliberately AVOID cocoindex declare_vector_index? Are embeddings added but the pgvector index managed via Supabase migration instead?”,
“verdict”: “Yes, the owner’s reading is exactly right. The pipeline DOES compute and write embeddings; it just does not let cocoindex create the search index. Stage-4 of the flow embeds every document’s text with OpenAI’s text-embedding-3-large (a 1024-number vector) and stores that vector in the row alongside the document. Separately, the pgvector index that makes those vectors searchable (an HNSW cosine index) is created by a hand-written Supabase migration, not by cocoindex. The reason is a governance rule: cocoindex’s declare_vector_index would silently run a CREATE INDEX schema-change command from inside the pipeline, bypassing the safeguard (managed_by=USER) that otherwise stops cocoindex from altering DB structure, and the project’s hard rule is that ALL schema/DDL changes go through Supabase migrations only while the pipeline writes row DATA exclusively.\n
==========================================================================================
E5 id-130 forms matching needs SD/RI?
Section titled “E5 id-130 forms matching needs SD/RI?”(agent ae8fa327b3041eee4; 5 StructuredOutput attempts; best=5161 chars; fields=[‘id’, ‘question’, ‘verdict’])
{
“id”: “E5”,
“question”: “v3 §8.2 re-points forms matching to “q_a_pairs (primary) + reference_items + source_documents”. Does form matching/filling genuinely need SD+RI, or only q_a_pairs?”,
“verdict”: “Matching genuinely needs q_a_pairs (primary) and CAN legitimately include reference_items, but source_documents should NOT be a direct match target. The matcher is fundamentally an embedding cosine search that feeds draftable text into an LLM: q_a_pairs (question_embedding + answer_standard/advanced) and reference_items (embedding + body/summary + taxonomy) both satisfy this and carry citeable answer content, whereas source_documents has NO embedding column and no answer-grained content (only a whole-file extracted_text dump + provenance cols). The correct grain for matching document bodies is content_chunks (embedding-bearing), surfacing the parent source_document only as a citation/provenance link. So v3 §8.2’s third leg is over-stated: re-point to “q_a_pairs (primary) + reference_items”, with source_documents demoted to a provenance/citation-resolution target (via content_chunks), not a vector-match source.\n<parameter name=“evidence”>LIVE forms matcher reads only content_items and ALL content_types: app/api/procurement/[id]/questions/match/route.ts:131 calls rpc(‘search_for_form_response’); body at supabase/migrations/20260617130000_squash_baseline.sql:5102-5121 = ‘FROM content_items ci … WHERE ci.embedding IS NOT NULL AND (1 - (ci.embedding <=> query_embedding)) > 0.25’ (no content_type filter).\n
==========================================================================================
E7 manual q_a_pair authoring shape
Section titled “E7 manual q_a_pair authoring shape”(agent a3e6bb6466e5d9ebc; 5 StructuredOutput attempts; best=5820 chars; fields=[‘classification’, ‘confidence’, ‘id’, ‘ownerDecisionNeeded’, ‘question’, ‘verdict’])
{
“id”: “E7”,
“question”: “Define the required manual q_a_pair authoring shape (BI-33, deferred) and where provenance comes from when it is not a source_document.”,
“verdict”: “A manual q_a_pair authoring shape is fully supported by the EXISTING q_a_pairs schema with zero migration: write directly to q_a_pairs (opaque gen_random_uuid PK) with question_text (NOT NULL), answer_standard (NOT NULL), answer_advanced (nullable), scope_tag text[] (default ’{}’), publication_status ‘draft’, and an origin_kind that records the manual origin. Provenance is a discriminated union keyed by origin_kind, NOT a mandatory source_document_id: source_document_id is NULLABLE and is set ONLY when the hand-authored pair derives from a known ingested document; when there is no source document the provenance is the origin_kind value itself (the human is the origin) plus optional source_workspace_id — exactly as the live form-response path already does (origin_kind=‘derived_from_form_response’ with source_document_id NULL and form-lineage FKs instead). EP8 (ep8-qa-docx-import-ui-spec) is the bulk .docx IMPORT surface and writes to the doomed content_items table (content_type=‘q_a_pair’), so it is a useful field reference but the WRONG target table under v3 — it is not the single-pair manual authoring form the owner now requires.\n<parameter name=“evidence”>ep8 spec located: /Users/liamj/Documents/development/knowledge-hub-docs-site/src/content/docs/specs/ep8-qa-docx-import-ui-spec.md (v5, S209) — §2 Goal 1 + §7.1 payload write to content_items with content_type=‘q_a_pair’ (legacy IMS god-table, eliminated in v3); it is admin bulk-docx import wrapping scripts/import_bid_library.py, NOT a manual single-pair form.||Live q_a_pairs schema (project zjqbrdctesqvouboziae): id uuid DEFAULT gen_random_uuid() (opaque); question_text NOT NULL; answer_standard NOT NULL; answer_advanced nullable; scope_tag text[] NOT NULL DEFAULT ’{}’; anti_scope_tag text[] NOT NULL DEFAULT ’{}’; alternate_question_phrasings text[] NOT NULL DEFAULT ’{}’; origin_kind text NOT NULL DEFAULT ‘curated_explicit’; publication_status text NOT NULL DEFAULT ‘draft’; source_document_id uuid NULLABLE; source_form_response_id/source_question_id/source_form_template_id/source_workspace_id all uuid NULLABLE; question_embedding vector nullable.||CHECK constraints: q_a_pairs_origin_kind_check ∈ {extracted_from_corpus, curated_explicit, derived_from_form_response, imported_legacy}; q_a_pairs_publication_status_check ∈ {draft, in_review, published, archived}.||Create path 1 (corpus pipeline): lib/q-a-pairs/promote-corpus.ts:382-392 sets origin_kind:‘extracted_from_corpus’, publication_status:‘draft’; provenance source_document_id set later via emitCorpusSidecar at :606-635 = sdUuid5(relPath).||Create path 2 (form-response, the no-source-document precedent): app/api/q-a-pairs/promote/route.ts:178-197 sets origin_kind:‘derived_from_form_response’ with provenance from source_form_response_id/source_question_id/source_form_template_id/source_workspace_id and NO source_document_id.||Edit path: app/api/q-a-pairs/[id]/route.ts (USER-DIRECT PATCH) materialises a sidecar + sets source_document_id=sdUuid5(relPath) on first edit only if NULL (:276-286).||NO manual create path exists: grep across lib/app/scripts shows zero writers for origin_kind ‘curated_explicit’ (the DEFAULT) or ‘imported_legacy’, and zero export async function POST in app/api/q-a-pairs. The only generic create verb (app/item/new -> POST /api/items -> lib/mcp/tools/content.ts) writes content_items, removed by BI-33/BI-34.||RLS: q_a_pairs_insert WITH CHECK auth.role() ∈ {authenticated, service_role} — any authenticated user may insert (admin gate is route-level, not DB).||BI-33 = RATIFIED DEFER: PRODUCT.md:334-344 (shape = question + answer_standard + answer_advanced + scope_tag[], opaque PK + recorded provenance origin); TECH.md:503-507 (scaffold OUT of ID-131, target id-71 {71.16}/ID-59/ID-69); .user-scratch/main_session_output.md:1068 + :1277 TECH-carry note recommends the later scaffold add a distinct ‘manually_authored’ origin_kind for lineage auditability; v3 §2.2 recommends RETIRING the Q&A sidecar file round-trip (a Q&A pair is a record, never a file/concept).\n<parameter name=“recommendation”>Scope a new ID-131 sub-task “manual q_a_pair authoring” = a POST /api/q-a-pairs route (getAuthorisedClient editor/admin) + minimal form that INSERTs directly to q_a_pairs. Authoring fields: question_text, answer_standard (both required), answer_advanced (optional), scope_tag[] (optional, default []), publication_status=‘draft’ (review-before-publish, mirroring the form-response path), question_embedding populated post-insert. Provenance: source_document_id is OPTIONAL/nullable — populate only if the author cites an ingested document; otherwise leave NULL and let origin_kind carry the provenance, optionally stamping source_workspace_id for where-authored context. RECOMMENDED design call: add a distinct ‘manually_authored’ origin_kind value (one-line CHECK migration) rather than overloading the ‘curated_explicit’ default, so hand-authored pairs are auditably distinguishable from the default; ALTERNATIVE is to reuse ‘curated_explicit’ (zero migration, it is already the writer-less default). Do NOT write a sidecar file for these (v3 §2.2: keep q_a_pairs.source_document_id as pure-DB provenance, retire the corpus sidecar round-trip). Do NOT resurrect EP8’s content_items target — port only its field mapping (question->title, answer_standard/advanced) onto q_a_pairs.\n<parameter name=“affects”>id-131 new sub-task (manual q_a authoring) / BI-33”,
“classification”: “NEW_SUBTASK”,
“ownerDecisionNeeded”: true,
“confidence”: “high”
}
==========================================================================================
E8 OKF external alignment (Google knowledge-catalog)
Section titled “E8 OKF external alignment (Google knowledge-catalog)”(agent a5902f733beb53682; 5 StructuredOutput attempts; best=6301 chars; fields=[‘id’, ‘question’, ‘verdict’])
{
“id”: “E8”,
“question”: “Is Canonical using OKF correctly? Was knowledge-catalog toolbox/enrichment/src (fileset.ts) reviewed for our enrichment agent, or only the BigQuery agent? Could fileset.ts benefit us over our Source-adapter/sources.py plan? Any OKF-correctness gap?”,
“verdict”: “Our id-132 design IS OKF-conformant with no hard correctness gap, but the TypeScript toolbox/enrichment/src (and fileset.ts) was NOT reviewed: every spec plus the v3 doc grounds exclusively on the Python okf/src/reference_agent, whose only concrete Source is BigQuery (sources/bigquery.py). The question’s premise is a mis-framing — fileset.ts is NOT an alternative to sources.py; it is MarkdownFileset, a list/read/keyword-search-over-a-markdown-directory MCP tool used as an enrichment-CONTEXT source, while the TS toolbox’s actual asset-source is BigQuery-via-mdcode. The true analogue of our planned l_records.py Source adapter is the Python sources/base.py, which our spec already lifted correctly. fileset.ts offers a modest, optional win: a framework-agnostic ready-made pattern for our Pass-2 gated-corpus reader (our L-raw is a local markdown filesystem, which MarkdownFileset fits more cleanly than the web-fetch adaptation TECH currently plans).\ntoolbox|fileset|kcenrich|kcagent|kcmd|mdcode|md-fileset|MarkdownFileset across all 4 spec dirs (id-131..134) + .user-scratch/okf-record-model-v3.md = ZERO hits → toolbox/enrichment was never reviewedkcmd pull)type is required frontmatter; resource=recommended URI of underlying asset; # Citations + index.md/log.md are optional conventions; consumers MUST tolerate missing optional fields / unknown types / broken links
==========================================================================================
E11 dead-loader: DB-based register feasibility
Section titled “E11 dead-loader: DB-based register feasibility”(agent a29e107229dffdc11; 5 StructuredOutput attempts; best=6577 chars; fields=[‘affects’, ‘classification’, ‘confidence’, ‘id’, ‘ownerDecisionNeeded’, ‘question’, ‘recommendation’, ‘verdict’])
{ “id”: “E11”, “question”: “id-133 BI-7 / Decision A retires the dead lib/ontology/loader.ts and makes the docs-site register/fixture authoritative. Owner pushback: could the register instead be DB-based (core) plus client rather than retired, and is that a viable third option that beats Decision A?”, “verdict”: “DB-based core plus client is a viable, well-grounded option and in fact already exists and is authoritative for the open/hybrid CVs (taxonomy_domains, taxonomy_subtopics, layer_vocabulary are DB tables with a provenance column plus a recommend-then-accept workflow; taxonomy_snapshot.json is derived from them). But it does NOT beat Decision A for BI-7’s actual scope, for two separable reasons. First, the loader being retired is the dead markdown reader (gitnexus shows no incoming callers and no process participation); it is dead under every storage model because the runtime already reads the build-time generated tuple for closed enums or the DB-derived snapshot for open dims, never the markdown, so a DB register makes it more dead not resurrected. Second, the register the owner actually names, entity_type and relationship (allowed_types/allowed_relations), is product-CLOSED at v1 (Decision B sets client_extensible false), enforced by hardcoded Python Literals plus DB CHECK constraints, so a provenance-bearing register table would buy zero v1 payoff while adding a further source to keep in lockstep, the exact v3-risk-8 drift hazard Decision A collapses. The owner is right in principle but for the extensible CVs, which already use the DB pattern; the markdown register is correctly reserved for the closed/core CVs.\n<parameter name=“evidence”>[“gitnexus context loadOntologyCVs repo canonical: no incoming callers, no process participation; confirmed dead with no production caller”, “lib/ontology/loader.ts:29 ONTOLOGY_DIR resolves to docs/ontology which no longer exists (register went private at ID-68.27); loader survives only as a parity-test tripwire”, “id-133 TECH.md lines 139-155 Decision A: CHOSEN is retire loadOntologyCVs and ONTOLOGY_DIR; REJECTED is repointing the loader at the docs-site register (adds a reader, reintroduces a node:fs private read, does not remove drift). Authority-chain table lines 158-164: markdown is human source, snapshot/fixture/generated-tuple/code-Literals are derived mirrors held by parity guards”, “SQL on zjqbrdctesqvouboziae: DB already has taxonomy_domains 7 rows, taxonomy_subtopics 34 rows, layer_vocabulary 4 rows, taxonomy_sync_state 1 row. taxonomy_domains and taxonomy_subtopics carry NOT-NULL provenance plus recommended_by, recommended_at, accepted_at columns: a DB-backed core-plus-client register with a recommend-then-accept workflow. All current provenance values are baseline (core)”, “scripts/generate-taxonomy-snapshot.ts lines 52-74 select taxonomy_domains and taxonomy_subtopics FROM the live DB into taxonomy_snapshot.json, so for the open dimensions the DB IS the source of truth and the snapshot is the derived mirror (the inverse of closed enums where markdown is the source)”, “phase-b-prerequisite-1-onthology-pipeline-feedback-investigation.md sections 2.5 and 6, plus phase-b-prerequisite-1-onthology-pipeline.md lines 356-359 and 523: Liam ratified Option (c) hybrid with provenance (S235 Q-OQR1-11, adopt across all hybrid vocabularies), explicitly mirroring the taxonomy_domains.provenance precedent, DB table authoritative for runtime config and code registry kept as contract. The owner’s instinct is the ratified pattern for HYBRID CVs”, “SQL CHECK constraints: entity_mentions_entity_type_check enumerates 12 entity types and entity_relationships_relationship_type_check enumerates 10 predicates, so the KG vocab already has a DB footprint; entity_aliases_provenance_check (core/client/recommended) shows the provenance pattern already spread. No allowed_types or allowed_relations register TABLE exists”, “scripts/cocoindex_pipeline/extraction.py lines 378-391 (entity_type) and 423-434 (relationship) are hardcoded Pydantic Literals: a closed HARD-reject runtime gate, not read from DB or snapshot. id-133 TECH.md lines 201-204 Decision B sets these provenance core, client_extensible false, closed at v1, client-extension research-deferred”, “id-133 TECH.md line 447 v3-risk-8: promoting the KG vocabularies adds a third source (MEDIUM), mitigated by parity guards binding register, fixture, Python Literal and TS const; a DB register would be yet another source under the same hazard”]”, “recommendation”: “KEEP Decision A / BI-7 as-is: retire the dead markdown loader (orthogonal to and unaffected by the storage-model question) and keep the markdown register as the human source-of-truth for the closed/core CVs. RE-FRAME for the owner rather than treating the pushback as a defect: the DB-based core-plus-client register he wants already exists and is authoritative for the genuinely client-extensible CVs (taxonomy_domains, taxonomy_subtopics, layer_vocabulary, provenance baseline-core plus recommend/accept client, snapshot derived from DB). The markdown register is deliberately reserved for the closed/core CVs that are HARD-rejected and product-closed at v1 (content_type, entity_type, relationship). The owner’s named target, entity_type and relationship, is the lever: it is product-CLOSED at v1 (Decision B), so a DB register buys nothing now and adds a lockstep source. RECOMMENDED: ratify Decision A and record the forward path explicitly. If and when the product decides entity_type/relationship should be client-extensible (the research-deferred call in Decision B), promote them to the SAME DB-backed provenance pattern as taxonomy_domains (per ratified S235 Q-OQR1-11), NOT to the markdown register; Decision B’s optional per-value provenance keys (provenance_model, client_extensible, editable_via) are the forward-compat bridge because they are exactly the columns that future table would carry. ALTERNATIVE if the owner wants client-extensible entity types at v1: make entity_type/relationship hybrid now, but that is a product-scope expansion (custom KG ontology per client) with real cost in the cocoindex Pydantic gate (the Literal must become a runtime DB-read), not a storage-of-a-dead-loader question, and should be costed as its own subtask.”, “affects”: [ “id-133 BI-7 / Decision A”, “id-133 Decision B (per-value provenance keys as DB forward-compat bridge)”, “okf-record-model-v3 section 6 ontology pass” ], “classification”: “DESIGN_DECISION_NEEDED”, “ownerDecisionNeeded”: true, “confidence”: “high” }