WP6 INV-1 — layervocabulary (CV 03) intent + bidlibrary (CV 13) finding
WP6 INV-1 — layer_vocabulary (CV 03) intent + bid_library (CV 13) finding
Section titled “WP6 INV-1 — layer_vocabulary (CV 03) intent + bid_library (CV 13) finding”Date: 14/05/2026 · Wave: S236 WP6 INV-1 (sub-agent investigation) · Author: INV-1 sub-agent
Branch: content-items-investigation
Status: Recommendation — for Liam ratification, then C1 editor cascade.
Companion docs: docs/specs/wp6-ontology-harness/verifier-reports/B1-md-vs-db-drift.md (CRITICAL drift on CV 03 + HIGH on CV 13).
This investigation answers two interlocking questions raised by Liam during S236 escalation:
(i) Given current platform direction, what should CV 03 (
layer_vocabulary) actually be in the ontology MD? The B1 verifier flagged the CRITICAL drift; it is C1’s job to set the new content. Before C1 acts, INV-1 must rule on whether the live values themselves are still the intended shape.(ii) What is the right architectural pattern for the underlying problem — content-for-different-audiences + ability-to-utilise-content-for-applications — given that the current
layer_vocabularytable is a metadata tag for filtering, not a UI hierarchy, but theProduct_KB_Dev_Brief.md(Phew’s request) andkb-hub-gap-analysis-action-tracker.md§B.1 both push the layer concept towards a UI-nesting role, while parallel work has builtSummary / In Depth / Takeaways / Original Texttabs and S235 ratifications have introduced corpus-levelq_a_pairsdriven byscope_tag?
The recommendation is grounded in the wider platform direction (§2 below), not the original Phew brief, per the agent prompt constraint.
1. Executive summary (≤200 words)
Section titled “1. Executive summary (≤200 words)”For CV 03 today: confirm the four live keys as the v1 baseline — sales_brief, bid_detail, company_reference, research — but reclassify the CV from editable_via: seed_data to editable_via: admin_ui, retain provenance_model: hybrid, set client_extensible: true, and rewrite the Definition prose to describe a layer-as-audience-and-depth tag (not the regulatory/customer/internal model A1 invented). The four keys are the right v1 set because they map directly to Phew’s stated audience axis (Sales / Bid / Corporate / Research) AND survive the new platform shape: layer remains a useful Layer-1 CV that drives filtering + presentation, even after q_a_pairs go corpus-level.
For Q(ii) — architectural pattern: layers, item-detail tabs, and q_a_pairs are doing three different jobs and should not be collapsed. Recommend a hybrid (option d in §6): keep content_items.layer as the audience tag; keep the existing Summary / In Depth / Takeaways / Original Text tabs as the per-item depth surface (already shipping); and treat q_a_pairs + scope_tag as the per-application surfacing layer (per S235 ratification). UI nesting (Phew’s original ask) lands as a renderer rule on top of the layer tag, not a new column — guide_sections.parent_section_id already exists for the section-level case (per kb-hub-gap-analysis-action-tracker.md §B.1 + §B.4).
For CV 13 bid_library: it is the input parameter name to inferLayer() (TS + Python), NOT an ingest_source DB value. Real ingest_source value written by scripts/import_bid_library.py:288 is qa_import. Recommend: do NOT add bid_library to CV 13 baseline_values. Add §Notes paragraph documenting the parameter-vs-DB-value distinction. Rename TS literal bid_library to bid_library_extraction or similar in a separate cleanup PR (out of WP6 scope).
2. Platform direction — what was ratified post-S232
Section titled “2. Platform direction — what was ratified post-S232”The S234 + S235 closure cycle (consolidated in 00-synthesis-v2.md and 0.9-decision-graph.md §11) materially changes the model in which “layer” is meaningful. The relevant ratifications:
2.1 application_types instance table (Q-OQR1-01, ratified 14/05/2026)
Section titled “2.1 application_types instance table (Q-OQR1-01, ratified 14/05/2026)”Per 00-synthesis-v2.md §3.4 and phase-b-prerequisite-1-onthology-pipeline.md §4.2: workspaces FK to a new application_types instance table (Option (c) hybrid + provenance). 6 baseline core types ship: procurement, intelligence, sales_proposal, product_guide, competitor_research, training_onboarding.
Implication for layer: “what application surfaces this content” is now an explicit FK chain (content_items → content_item_workspaces → workspaces → application_types), NOT the layer column. Layer no longer needs to encode application-served-ness — that lives in the relationship. Layer is freed to be an audience-and-depth tag orthogonal to application.
2.2 q_a_pairs corpus-level + scope_tag-driven (Q-OQR1-06, ratified 14/05/2026)
Section titled “2.2 q_a_pairs corpus-level + scope_tag-driven (Q-OQR1-06, ratified 14/05/2026)”Per 00-synthesis-v2.md §3.6 and phase-b-prerequisite-1-onthology-pipeline-feedback-investigation.md §4: q_a_pairs have NO direct workspace FK; workspace relevance is computed via scope_tag overlap. Nullable source_workspace_id only for provenance audit.
Implication for layer: the “give me the right q_a_pair for this application” problem is now solved by scope_tag matching, NOT by joining on layer. Layer on q_a_pairs (where assigned) becomes a depth/audience hint, not a routing key.
2.3 Core/client/hybrid dimension across hybrid vocabularies (Q-OQR1-11, ratified 14/05/2026)
Section titled “2.3 Core/client/hybrid dimension across hybrid vocabularies (Q-OQR1-11, ratified 14/05/2026)”Per phase-b-prerequisite-1-onthology-pipeline-feedback-investigation.md §6.2 and §6.3: every hybrid Layer-1 CV carries provenance enum (core / client / recommended). Layer vocabulary is explicitly classified as HYBRID in §6.2 line 629:
Content Layer (Sales Brief / Bid Detail / Company Reference) is HYBRID. The 3-tier layered model is core platform-shaped (the “layered content prevents duplication” architectural principle). But specific layer labels per client may diverge — some clients may want different layer names or a 4-tier model.
Implication for layer: layer must support provenance per row in v1.1+ admin UI (Q-OQR1-13 deferred to v1.1) and ship 4 baseline core rows in v1. The current layer_vocabulary table has NO provenance column today; this is gap #1 to flag.
2.4 Layer-1 CV register expansion (S234 + S235 WP1)
Section titled “2.4 Layer-1 CV register expansion (S234 + S235 WP1)”Per phase-b-prerequisite-1-onthology-pipeline.md §2.1 + decision graph §11.1 ONT.13/ONT.14/ONT.15/ONT.16: new Layer-1 CVs application_type, form_type, form_format, edit_intent, change_report_category, extraction_method, diff_mode. None of them subsume layer. Layer remains a peer Layer-1 CV.
2.5 What “layer” means in the post-S235 model
Section titled “2.5 What “layer” means in the post-S235 model”Cross-reading §2.1 + §2.2 + §2.3 + §2.4: layer is a non-routing audience tag. It does not gate workspace assignment (M:N junction does), it does not gate q_a_pair selection (scope_tag does), it does not classify the form/format of a deliverable (form_type/form_format do), and it does not govern the publication state machine (publication_status/governance_review_status do). What it does:
- Serves as a filter in
/browseand admin list views (“show me only Sales Brief content”). - Drives expected_layer on
guide_sectionsso a Product Guide’s Security section can preferentially surfacebid_detailitems. - Drives layer-suggestion banners on create flows (
lib/layer-inference.tsevaluates 7 rules to suggest a default). - Drives per-item layer-switcher nav (
components/item-detail/layer-switcher-nav.tsx— surfaces sibling items at other layers when the same topic exists at multiple depths).
It is therefore a legitimate Layer-1 CV with continuing utility in the ratified shape — provided the four keys reflect the audience axis Phew operates on, AND the CV carries provenance so other tenants can extend.
3. Current implementation reality
Section titled “3. Current implementation reality”3.1 Live DB rows (verified 14/05/2026 against staging branch turayklvaunphgbgscat)
Section titled “3.1 Live DB rows (verified 14/05/2026 against staging branch turayklvaunphgbgscat)”key | label | description | ordersales_brief | Sales Brief | Positioning and messaging for internal sales | 10bid_detail | Bid Detail | Factual content for tender responses | 20company_reference | Company Reference | Controlled corporate documents | 30research | Research | Background material and market intelligence | 40All 4 rows are is_active=true. The table has columns id, key, label, description, display_order, is_active (per supabase/migrations/20260416102457_pre_squash_reconciliation.sql:3837-3849). No provenance column today.
3.2 Static fallback (matches DB)
Section titled “3.2 Static fallback (matches DB)”lib/client-config.ts:159-184 ships an identical 4-key FALLBACK_LAYERS constant (consumed by contexts/layer-vocabulary-context.tsx:43-52 when the DB fetch fails). Not the regulatory/customer/internal model.
3.3 Validation (DB-driven post P1-36)
Section titled “3.3 Validation (DB-driven post P1-36)”Per docs/specs/p1-36-db-driven-layer-validation-spec.md (shipped earlier per lib/validation/layer-schemas.ts:24-33): API handlers fetch active layers from layer_vocabulary at request time. inferLayer() (lib/layer-inference.ts:81-211) hard-codes the 4 keys for inference logic only (out of scope of P1-36 per its §Non-goals). Admin add/remove of layers via app/api/layers/route.ts is live; components/settings/layers-section.tsx exposes the admin UI.
3.4 UI surfaces consuming layer
Section titled “3.4 UI surfaces consuming layer”| Surface | File | Layer’s role |
|---|---|---|
| Browse filter panel | components/browse/filter-panel.tsx, coverage-layer-filter.tsx | Filter chips |
| Content card / row badges | components/content/content-card.tsx, content-row.tsx | Display badge |
| Item detail nav | components/item-detail/layer-switcher-nav.tsx | Sibling-at-other-layer navigation |
| Guide section render | components/guide/guide-section.tsx:101 | expected_layer label per section |
| Create-content layer suggestion | components/content/layer-suggestion-banner.tsx, content-layer-selector.tsx, upload-tab-content.tsx, ingestion-success-card.tsx | Render inferLayer() suggestion |
| Settings admin | components/settings/layers-section.tsx, section-form-dialog.tsx | CRUD admin |
3.5 Per-item depth tabs (the “alternative” Liam referenced)
Section titled “3.5 Per-item depth tabs (the “alternative” Liam referenced)”components/item-detail/content-tabs.tsx:307-370 renders horizontal tabs on every item detail page:
Summary(brief field, AI or human)In Depth(detail field)Takeaways(summary_data.takeawaysJSON array)Full Answer(q_a_pair full answer) OROriginal TextReader(preserved reader-view content)Supporting Detail(reference field)
This is per-item depth gradation, sourced from typed columns on content_items (brief, detail, reference, summary_data). Orthogonal to the layer axis.
3.6 Phew’s original framing (recap, not gospel)
Section titled “3.6 Phew’s original framing (recap, not gospel)”Product_KB_Dev_Brief.md §“Content Layer Model” defines three layers: Sales Brief, Bid Detail, Company Reference. Per-section tables in §“Section 2: Product Guides” mark each subsection with one layer (e.g. “Sales Brief → Bid Detail” for §12 Security). The brief ALSO requests a layered view UI (“toggle between Sales Brief and Bid Detail for the same topic” — §“Key Platform Features”). kb-hub-gap-analysis-action-tracker.md §B.1 records Liam’s escalation that the live platform “treats layers as metadata tags used for filtering, not for UI hierarchy” and rules Option (ii) UI nesting rule: “each topic section displays Sales Brief at the top and Bid Detail nested beneath, programmatically”.
4. Gap analysis findings — what kb-hub-gap-analysis-action-tracker.md says about the layer disconnect
Section titled “4. Gap analysis findings — what kb-hub-gap-analysis-action-tracker.md says about the layer disconnect”Quoted verbatim from docs/client-documentation/kb-hub-gap-analysis-action-tracker.md §B.1 (lines 37-60):
Status: Spec pending. Liam decision: Option (ii). Layer becomes a UI nesting rule: each topic section displays Sales Brief at the top and Bid Detail nested beneath, programmatically. Context: currently layers are metadata tags used for filtering, not for UI hierarchy. Advanced Audits §13 “Security & Compliance” is one section at
bid_detaillayer; mind-map intent was Security (Sales Brief) with Security (Bid Detail) nested.Next action (spec): author
docs/specs/p0-layer-nesting-spec.md:
- Phase 1 — data model: determine whether nesting uses existing
guide_sections.parent_section_idor a newlayer_priority_ordercolumn.- Phase 2 — resolver: extend
get_guide_contentRPC to return layer-grouped buckets.- Phase 3 — UI: Product/Sector Guide section-renderer groups items by
layerwithin each section, Sales-Brief first, Bid-Detail beneath, Reference collapsible.- Migration: zero-row — uses existing columns. Per-guide display order remains unchanged.
- Accessibility: WCAG 2.1 AA — semantic nesting (
<section><h3>…<section><h4>…), not just indentation.
docs/specs/p0-layer-nesting-spec.md does not yet exist (verified via find docs/specs -name "*layer*" — only p1-36-db-driven-layer-validation-spec.md exists). So the §B.1 ruling is spec-pending, not yet shipped. §B.4 (Advanced Audits dual-tier) recommends Option (i) guide_sections.parent_section_id as the section-hierarchy primitive; §B.1 explicitly flags this as composing with B.4.
Consequence for INV-1: Phew’s ask + Liam’s gap-analysis ruling both want layer to do double duty (a) filter tag (already shipping) AND (b) UI nesting driver (spec pending). The post-S235 platform direction does not contradict this — but it splits the implementation: (a) is layer-the-CV; (b) is a renderer rule on top of layer + guide_sections.parent_section_id. The two should not be merged into one column.
5. Original Phew requirement vs new platform shape — divergences
Section titled “5. Original Phew requirement vs new platform shape — divergences”| Original Phew brief (28/02/2026) | Current ratified shape (post-S235) | Divergence and resolution |
|---|---|---|
| 3 layers: Sales Brief / Bid Detail / Company Reference | 4 layers in live DB: + Research | Phew added research later (per gap-analysis Research Feed shipping S189 WP5). Live model is correct; brief is stale. Keep the 4-key live shape. |
Layer = content_layer enum, ranks 1/2/3 | Layer = FK key into layer_vocabulary table with display_order | Live shape is more flexible and admin-extensible. Honors Q-OQR1-13 admin-UI direction (Phew’s brief implicitly asks for client-customisability via the “Admin user role”). |
| ”Layered view: toggle between Sales Brief and Bid Detail for the same topic” — UI feature | Toggle currently realised as filter chips + layer-switcher-nav for sibling items. UI nesting (Phew’s intent per gap-analysis §B.1) is spec-pending. | Diverges. Renderer-level work needed; not a CV change. CV must support the use-case (4 keys + ordering + provenance) but does not implement it. |
Schema sketch shows section / guide / subsection columns, treats layer as one of many tag-shaped fields. Single content column for the body. | Live schema has typed brief / detail / reference / summary_data.takeaways / content fields driving the Summary / In Depth / Takeaways / Original Text tabs. | Diverges. Live shape is a superset — same one-record-many-views principle (CLAUDE.md), but realised as typed depth columns instead of layer-encoded duplicates. The platform now provides BOTH per-item depth (via tabs) AND per-section audience (via layer). The brief’s “Bid Detail always sits underneath the Sales Brief — never duplicates it” rule is honored by typed columns + render hierarchy, not by separate items per layer. |
Per-section-table marks single Content Layer per subsection | guide_sections.expected_layer is a single-value column today | Matches. Where a section needs both layers (e.g. §12 Security “Sales Brief → Bid Detail”), the resolution is: child sections via parent_section_id (per gap-analysis §B.4 Option (i)) OR multi-value via array (not currently planned). |
No mention of q_a_pairs, scope_tag, application_types, form_types | All four are central to the new platform shape | Original brief is pre-platform-architecture. Layer fits cleanly alongside these new primitives, not in their place. |
| No mention of provenance / core-vs-client | All hybrid vocabularies carry provenance per Q-OQR1-11 | Live layer_vocabulary table lacks provenance. Add as a v1.1 admin-UI dependency (or v1 schema migration if appetite exists). |
Bottom line: Phew’s brief and the live platform are compatible on layers, but the live platform is a richer realisation. The ratified S235 model neither retires layer nor demotes it — it sits comfortably as a Layer-1 CV among 29 others.
6. Two questions to answer (Liam’s framing)
Section titled “6. Two questions to answer (Liam’s framing)”6.1 Q(i) — what is required today for the CV 03 MD to accurately reflect intent?
Section titled “6.1 Q(i) — what is required today for the CV 03 MD to accurately reflect intent?”Concrete recommendation for docs/ontology/03-layer-vocabulary.md frontmatter and prose:
---cv_name: layer_vocabularylayer: 1provenance_model: hybridclient_extensible: trueeditable_via: admin_uicore_seed_path: scripts/seed/baseline-layer-vocabulary.sqlrelated_layers: [2, 5]status: activebaseline_values: - key: sales_brief label: Sales Brief provenance: core definition: Positioning and messaging for internal sales - key: bid_detail label: Bid Detail provenance: core definition: Factual content for tender responses - key: company_reference label: Company Reference provenance: core definition: Controlled corporate documents - key: research label: Research provenance: core definition: Background material and market intelligence---Key points C1 needs to act on:
- Replace the entire
baseline_values:block (currently regulatory/customer/internal — invented by A1) with the 4 live keys above. - Change
editable_viafromseed_datatoadmin_ui. Layers ARE admin-CRUD-able today viaapp/api/layers/*+components/settings/layers-section.tsx. They are notseed_data. - Keep
provenance_model: hybridandclient_extensible: true— both are correct per §6.2 of WP-ONTO-R1 (lines 629-630). - Set
core_seed_pathto a real path (e.g.scripts/seed/baseline-layer-vocabulary.sql) — even though no such file exists yet, the path commits the build-Phase-1 work to creating one. Alternatively set tonulland add a §Notes flag “core_seed_path TBD — baseline migration required for v1.1 multi-tenant”. - Rewrite §Definition to describe the layer-as-audience-and-depth tag (matches the four
descriptioncolumns from the live DB). Drop the regulatory/customer/internal model entirely — that was source-doc speculation A1 took as gospel. - Rewrite §Baseline values table to mirror the new frontmatter.
- Notes block additions (TBD-Liam flags):
- “Live
layer_vocabularytable has noprovenancecolumn today; v1.1 admin UI per Q-OQR1-13 will need anALTER TABLEmigration to add it before client-extension can be safely tagged.” - “UI nesting per
kb-hub-gap-analysis-action-tracker.md§B.1 is spec-pending —docs/specs/p0-layer-nesting-spec.mdnot yet authored. CV does not gate that spec; renderer reads layer +guide_sections.parent_section_idto compose the nested view.”
- “Live
6.2 Q(ii) — what approach for content-for-different-audiences + ability-to-utilise-content-for-applications?
Section titled “6.2 Q(ii) — what approach for content-for-different-audiences + ability-to-utilise-content-for-applications?”Compare the four candidates Liam framed:
(a) Layer column on content_items (current). Single-value tag. Works as a filter. Does NOT do UI hierarchy on its own. Cannot natively express “this same item is the Sales-Brief view of that other Bid-Detail item” (would need an item↔item link).
(b) Summary / In Depth / Takeaways / Original Text tabs (existing alt). Typed depth columns on content_items (brief, detail, reference, summary_data). Per-item, not per-section. Solves “show me deeper detail of THIS item” but does NOT solve “show me the Bid-Detail view of THIS topic” (different items, same topic).
(c) q_a_pairs extraction + corpus-level + scope_tag (new platform shape per S235 Q-OQR1-06). Solves “give me the right answer for this question/application” via scope_tag overlap. Does NOT solve audience-tagged content browse, and DOES NOT solve UI nesting of richer documents (q_a_pairs are atomic).
(d) Hybrid: layers for UI nesting + tabs for granularity + q_a_pairs for application-specific surfacing. Each primitive does what it is structurally suited for. Three orthogonal axes that compose:
| Axis | Primitive | Realisation |
|---|---|---|
| Audience (“who reads this?”) | content_items.layer FK to layer_vocabulary | Filter chips, nav badges, layer-suggestion-banner, expected_layer on guide sections |
| Depth / progressive disclosure (“how much detail?“) | typed columns brief / detail / reference / summary_data | Summary / In Depth / Takeaways / Original Text tabs (already shipping) |
| Application surfacing (“which workflow consumes this?”) | q_a_pairs + scope_tag overlap, plus content_item_workspaces M:N | scope_tag-driven query for q_a_pairs (S235); workspace-scoped feeds for content_items (already shipping) |
| UI nesting at the section level (“Sales-Brief item above its sibling Bid-Detail item”) | guide_sections.parent_section_id + layer-grouped renderer | Spec-pending per gap-analysis §B.1; composes with B.4 dual-tier |
Recommend (d). Specifically:
- Do NOT add a new column to encode UI nesting. The renderer should compose layer +
parent_section_idper gap-analysis §B.1 Phase 1. - Do NOT collapse layer into application_types or form_types. Layer is audience-axis; application_type is workflow-axis; form_type is deliverable-shape axis. Three different things.
- Do NOT replace the depth tabs with layer. A single content_item with full progressive depth (brief + detail + reference + takeaways) at
layer=bid_detailis more useful than four separate items at four layers — and matches the CLAUDE.md “one record, many views” principle. - Do NOT add layer to
q_a_pairsbeyond what’s already there. q_a_pairs route via scope_tag in the new model; layer on a q_a_pair is a hint, not a key.
The original Phew brief (3-layer model + UI nesting view) is satisfied by (d). Tabs satisfy “read deeper detail of the same item without duplication”; layer + parent_section_id satisfy “Sales Brief at top, Bid Detail nested beneath” at the section-renderer level; corpus-level q_a_pairs satisfy “the right answer for this application” without per-application duplication.
7. CV 13 bid_library finding (sub-investigation)
Section titled “7. CV 13 bid_library finding (sub-investigation)”7.1 Where bid_library actually lives
Section titled “7.1 Where bid_library actually lives”Verified via grep:
| Site | File | Role |
|---|---|---|
| TS type literal | lib/layer-inference.ts:21 | ingestionSource parameter type on LayerInferenceInput |
| TS rule branch | lib/layer-inference.ts:94-101 | Rule 2 — “Q&A pairs imported from bid documents are bid-level detail” |
| TS adapter | lib/queue/handlers/batch-reclassify.ts:908-913 | platformToSource() maps platform='extraction' to ingestionSource='bid_library' for the inferLayer() call (DB column platform, not ingest_source) |
| Python pipeline | scripts/import_bid_library.py:297 | ingestion_source="bid_library" passed to infer_layer() (Python parallel) |
| Python pipeline | scripts/kb_pipeline/layer_inference.py:96 | Same Rule 2 branch |
| Python tests | scripts/tests/test_layer_inference.py:80,89,277,285 | Rule 2 coverage |
7.2 What the DB actually receives
Section titled “7.2 What the DB actually receives”scripts/import_bid_library.py:288 writes "ingest_source": "qa_import" to the actual content_items.ingest_source column. The string bid_library NEVER lands in content_items.ingest_source. Confirmed by live prod query (only upload observed; documented values include qa_import, never bid_library).
The other bid-library-adjacent values in import_bid_library.py:726, 850 are bid_library_import (with the _import suffix), again only used in pipeline logic — not necessarily reaching the DB column either.
7.3 Disposition recommendation
Section titled “7.3 Disposition recommendation”bid_library is a layer-inference input parameter, NOT an ingest_source vocabulary value. It exists to let the inference function distinguish “this Q&A pair came from a bid library import (so it’s bid-level detail)” from “this Q&A pair came from manual creation”. The right home for it is the inferLayer() parameter shape, which is platform-internal pipeline logic — NOT the ingest_source CV.
Concrete recommendation for docs/ontology/13-ingest-source.md:
- Do NOT add
bid_libraryto thebaseline_values:block. It is not a DB value. - Add a §Notes paragraph (verbatim suggested wording):
Pipeline parameter vs DB value distinction.
lib/layer-inference.ts:21and the Python parallelscripts/kb_pipeline/layer_inference.pydeclare'bid_library'as a literal value of theingestionSourceparameter toinferLayer(). This is not aningest_sourceDB value —scripts/import_bid_library.py:288writes the string'qa_import'to the actualcontent_items.ingest_sourcecolumn when ingesting bid library Q&A. Thebid_libraryparameter literal exists only to drive Rule 2 of layer inference (“Q&A pairs from bid documents →bid_detaillayer”). Treatbid_libraryas inference-function-internal, NOT as a CV term. Renaming to e.g.bid_library_extractionwould reduce confusion and is a separate cleanup PR (out of WP6 scope). - Retain the existing 11-value baseline_values list as drafted by A1; the prod-vs-documented drift the verifier flagged is a separate hardening problem (Phase 1 build needs
CHECKmigration to enforce; see §3 of B1 verifier report). That CHECK migration is the right home for ratifying which values stay and which retire. - Add a §Notes bullet on the column-name drift (DB column
ingest_source, Zod fieldingestion_source, Python pipeline parameteringestion_source) flagging Liam decision required — independent ofbid_library.
8. Recommended C1 actions (concrete instructions for the editor wave)
Section titled “8. Recommended C1 actions (concrete instructions for the editor wave)”8.1 docs/ontology/03-layer-vocabulary.md — REWRITE
Section titled “8.1 docs/ontology/03-layer-vocabulary.md — REWRITE”Frontmatter changes:
editable_via: seed_data→editable_via: admin_ui- Replace
baseline_values:block with the 4 live keys per §6.1 above (each carriesprovenance: core+definition: <DB description column>). - Keep
provenance_model: hybrid,client_extensible: true,related_layers: [2, 5],status: active. core_seed_path:set toscripts/seed/baseline-layer-vocabulary.sql(Notes flag below covers the file’s non-existence).
Body changes:
- Rewrite §Definition to describe layer-as-audience-and-depth tag. Suggested opening: “Cross-cutting audience-and-depth classification on
content_items. Captures who the content is for and at what level of detail —sales_brieffor benefit-led internal-sales positioning,bid_detailfor tender-response factual content,company_referencefor product-agnostic corporate documents,researchfor background material that feeds guides. Ships as 4 baselinecorerows; clients may extend via the admin UI (/settings→ Layers section).” - Rewrite §Baseline values table to mirror the new frontmatter.
- Update §Relationships to add: “
guide_sections.expected_layertext column —layer_vocabulary.keyreferenced by section-level expected-audience hints.” - Update §Consumers — list at minimum:
contexts/layer-vocabulary-context.tsx,components/browse/filter-panel.tsx,components/item-detail/layer-switcher-nav.tsx,components/guide/guide-section.tsx,components/content/layer-suggestion-banner.tsx,components/settings/layers-section.tsx,lib/layer-inference.ts(read),lib/validation/layer-schemas.ts(validation),app/api/layers/*(admin CRUD). - Replace §Notes wholesale with:
- Live
layer_vocabularytable has 4 rows + admin CRUD. Noprovenancecolumn today; v1.1 admin UI per Q-OQR1-13 will need anALTER TABLEmigration to add it before client-extension can be safely tagged. - UI nesting per
kb-hub-gap-analysis-action-tracker.md§B.1 (Liam ruling 22/04/2026: layer becomes a UI nesting rule with Sales Brief → Bid Detail nested) is spec-pending —docs/specs/p0-layer-nesting-spec.mdnot yet authored. The CV does not gate that spec; renderer composeslayer+guide_sections.parent_section_idto produce the nested view. core_seed_pathpoints to a SQL seed file that does not yet exist; create as part of Phase 1 build perphase-b-prerequisite-1-onthology-pipeline.md§6.
- Live
8.2 docs/ontology/13-ingest-source.md — APPEND, no baseline_values change
Section titled “8.2 docs/ontology/13-ingest-source.md — APPEND, no baseline_values change”- Do NOT add
bid_libraryto baseline_values. - Append a §Notes paragraph (text in §7.3 step 2 above) explaining the parameter-vs-DB-value distinction.
- Append §Notes bullet on column-name drift per §7.3 step 4.
- Leave the 11-value baseline_values list as A1 drafted; Liam decisions on (a) trimming retired values and (b) hardening with a CHECK constraint are scoped to a separate Phase-1 build migration, not the CV doc.
8.3 No other CV files require changes for this investigation.
Section titled “8.3 No other CV files require changes for this investigation.”CV 22 (origin_kind) already declares extracted_from_corpus / curated_explicit / derived_from_bid_response / imported_legacy per A1 — that captures the bid-library origin semantics correctly without conflating ingest_source. CV 25 (application_type) ships independently per Q-OQR1-01.
9. Open items needing further Liam input (max 3)
Section titled “9. Open items needing further Liam input (max 3)”-
Layer
provenancecolumn migration timing.layer_vocabularytable needsALTER TABLE … ADD COLUMN provenance textbefore v1.1 admin UI can mark client-added layers asclient-provenance. Land in (a) the Q-OQR1-16 combined PR alongside the entity_aliases category→provenance rename, (b) a separate v1.1 schema migration, or (c) defer until first multi-tenant onboarding. Recommend (a) — keeps all provenance-pattern work in one cross-table PR, but Liam to confirm scope appetite. -
UI nesting spec ownership.
docs/specs/p0-layer-nesting-spec.mdis Spec-pending per gap-analysis §B.1. The §B.1 Phase-1 question (“guide_sections.parent_section_idvs newlayer_priority_ordercolumn”) is unresolved. INV-1’s view: preferparent_section_idfor composability with B.4 dual-tier; do not addlayer_priority_order. Liam to confirm direction before the spec is authored, so WP6 does not have to revisit CV 03 if the spec lands a column-add. -
bid_libraryliteral cleanup PR scope. Thebid_libraryparameter literal inlib/layer-inference.ts:21(and Python parallel) is a maintenance hazard — anyone reading the type signature will reasonably assume it is an ingest_source value. Recommend a separate one-line cleanup PR that renames it tobid_library_extraction(TS + Python + tests). Liam to confirm whether to schedule this for the Q-OQR1-16 combined PR or as standalone post-WP6.
10. Provenance pointers
Section titled “10. Provenance pointers”Every recommendation traces to a source.
| Recommendation | Primary source | Secondary source |
|---|---|---|
| 4 live keys are the right v1 baseline | Live DB query 14/05/2026 (staging branch turayklvaunphgbgscat) | lib/client-config.ts:159-184 FALLBACK_LAYERS |
editable_via: admin_ui | app/api/layers/route.ts, app/api/layers/[id]/route.ts, app/api/layers/reorder/route.ts, components/settings/layers-section.tsx | docs/specs/p1-36-db-driven-layer-validation-spec.md |
provenance_model: hybrid + client_extensible: true | phase-b-prerequisite-1-onthology-pipeline-feedback-investigation.md §6.2 lines 629-630 (HYBRID classification) | 00-synthesis-v2.md §3.7 (provenance pattern adoption) |
| 3 orthogonal axes (layer / depth / application) | §2.1, §2.2, §2.3 above | 00-synthesis-v2.md §3.4 + §3.6 |
| Depth tabs are the right primitive for “deeper detail of THIS item” | components/item-detail/content-tabs.tsx:307-370 (live tabs) | app/item/[id]/page.tsx, app/item/[id]/loading.tsx:28 |
| q_a_pairs corpus-level + scope_tag is the right primitive for “right answer for this application” | phase-b-prerequisite-1-onthology-pipeline-feedback-investigation.md §4 + §4.4 | 00-synthesis-v2.md §3.6 + 0.9-decision-graph.md §11.1 ONT.5 |
UI nesting is renderer + parent_section_id, not CV change | kb-hub-gap-analysis-action-tracker.md §B.1 + §B.4 | Live guide_sections.parent_section_id FK (per supabase/migrations/20260416102457_pre_squash_reconciliation.sql:3792) |
bid_library is a layer-inference parameter, not an ingest_source value | lib/layer-inference.ts:21,94-101 + scripts/import_bid_library.py:288,297 (compare DB write vs parameter) | scripts/kb_pipeline/layer_inference.py:96 |
Live DB CHECK for ingest_source does not exist | phase-b-prerequisite-1-onthology-pipeline.md §2.1 line 56 (“no DB CHECK enforces them”) | B1 verifier report §2 row 13 |
| Phew brief is compatible but pre-platform-architecture | docs/client-documentation/markdown/Product_KB_Dev_Brief.md §“Content Layer Model” + §“Database Schema” (lines 38-58 + 182-209) | 00-synthesis-v2.md (S235 ratifications post-date brief by 2.5 months) |
| Liam’s gap-analysis ruling on UI nesting | docs/client-documentation/kb-hub-gap-analysis-action-tracker.md §B.1 lines 37-60 | Spec docs/specs/p0-layer-nesting-spec.md does NOT exist (verified via find docs/specs -name "*layer*") |
| Verifier flagged CRITICAL drift | docs/specs/wp6-ontology-harness/verifier-reports/B1-md-vs-db-drift.md §3 row “CV 03” + §6 row 1 | Same report, §1 severity rollup |
End of INV-1. C1 editor should consume §8 directly. WP-ONTO-R1 ratification should consume §9 (3 open items) before C1 begins. Q(ii) recommendation (option (d) hybrid) should be cross-checked against any future WP4 04-workspace-types.md and 05-qa-flow.md drafting — it is consistent with the §4 readiness map in 00-synthesis-v2.md but has not yet been ratified as a separate decision.