Skip to content

State of the Product — Canonical

Archived S504 (ruling R8): flagged stale/unused by both platform-context.md and platform-direction.md; DR-006 rules it not load-wired. Historical platform context only.

⚠️ SUPERSEDED (S436, 2026-07-02): this doc is deliberately NOT load-wired (DR-006, retired — the rule is now carried by platform-context.md’s known-stale list) — stale/unused by design. The load-at-session-start product anchor is reference/platform-context.md. Do not trust anything here without verifying against reference/platform-context.md + reference/deployment-architecture.md first.

Supersedes: ADS v1.0 (technical sections only; UX principles now in docs/reference/ux-principles.md; ADS v1.0 archived to .planning/.archive/.reference/ads-v1.md in S151) and the Track 1 “Knowledge Platform — Product Design” brainstorm from 3 March 2026 (diffed and merged into §0 below during S152B WP10; source archive to .planning/.archive/.reference/ is scheduled in S152B WP1 — its unique philosophical framing is now captured in §0, the rest was superseded by the actual build).

Purpose: Accurate reference of what is actually built, the real tech stack, and current feature state. Forward-looking work lives in docs/reference/product-roadmap.md; parked/speculative items live in docs/reference/product-backlog.md.

S152B WP10 split note: This document used to contain 53 “Session N additions” blocks spanning S53 → S152A (total 2325 lines). Those blocks were extracted out of this document into a separate session-history archive (since retired). During the S186 refactor, per-phase re-ingestion detail (S153-S185) was likewise removed, keeping this canonical reference focused on current state. Session-specific deliverables live in docs/continuation-prompts/ and memory.

See also:

  • docs/reference/product-roadmap.md — forward-looking work
  • docs/reference/product-backlog.md — parked items
  • .planning/.archive/.audits/si-gap-analysis-s149.md — SI gap analysis (S149, historical)
  • .planning/.archive/.audits/ai-eval-gap-analysis-s149.md — AI eval gap analysis (S149, historical)
  • docs/reference/SCHEMA-QUICK-REFERENCE.md
  • docs/reference/classification-architecture.md
  • docs/reference/data-entry-points.md
  • docs/operations/taxonomy-change-runbook.md
  • docs/reference/field-consumer-dependency-map.md
  • docs/reference/ux-principles.md

Extracted from the Track 1 Product Design brainstorm (3 March 2026, superseded but preserved here) and refined by the decisions made during Sessions 53-152.

SMBs are not prepared for an AI-first future. Creating structured, high-quality knowledge bases as a single source of truth is a huge step towards preparedness. Claude provided with good data can support a wide variety of business use cases — bids, sales, compliance, training, and whatever comes next. The knowledge base is the product; bids are the first application, not the only one.

0.2 Wikipedia Principle — One Record, Many Views

Section titled “0.2 Wikipedia Principle — One Record, Many Views”

Every piece of knowledge exists once. A single content entry has a canonical body, progressive depth layers (brief / detailed / reference sections within the same entry, not separate entries), AI-generated metadata (classification, embeddings, summary, keywords), user-applied context (tags, workspace assignments, notes), version history, and provenance. The UI shows the appropriate depth depending on the user’s context. Editing one version doesn’t risk another becoming stale; search returns one result, not three.

This principle is the reason progressive depth columns live on content_items rather than as a separate table of layered records.

0.3 Observe and Intervene (Governance Philosophy)

Section titled “0.3 Observe and Intervene (Governance Philosophy)”

Not “prevent and approve” (traditional publishing workflow), but “allow changes, track everything, surface for review”:

  1. Content can be updated freely — no blocking workflows by default.
  2. Every change is versioned — who changed what, when, with diff.
  3. Changes are surfaced via change reports (user-facing label; internal code now consistent across UI + code) — daily/weekly summary of what changed.
  4. Configurable posture per client:
    • Open: Anyone with write access can edit. Change report shows changes.
    • Review-on-change: Edits to tagged content (e.g., policies, certifications) trigger a review notification. Content is live immediately but flagged for review.
    • Formal: Draft → Review → Publish workflow for designated content types. Content isn’t “live” until approved.

The freshness-to-governance and quality-to-governance bridges built in S114 implement the “surface for review” side of this model; the content_history audit trail implements the “track everything” side.

0.4 Everyone Ends Up Working Within an LLM

Section titled “0.4 Everyone Ends Up Working Within an LLM”

A strategic insight from the product owner: most users already work within Claude Desktop or similar LLM interfaces. Canonical’s job is to facilitate that — structuring data so AI can access it effectively, not competing for screen time. This motivates the MCP server (58 tools, 12 resources, 7 prompts, 4 MCP Apps exposing Canonical state through Claude.ai and Claude Desktop), the ClaudePromptButton contextual bridge (replaced CopilotKit in S109), and the “reorient me” pattern where AI reads system state and provides a personal briefing when a user returns.

0.5 The Library-to-Application Feedback Loop

Section titled “0.5 The Library-to-Application Feedback Loop”

The circular value proposition: the KB feeds bids → winning bids strengthen the KB → a stronger KB produces better bids. Forward path is built (bid matching pulls from KB to draft responses). Reverse path is partially built (bid outcome dialog has “integrate to KB” checkbox; Phase 2 will surface refined winning responses for one-click promotion back to the Q&A library).

“Helping you get organised, not learning from you.” AI is invisible plumbing that classifies, summarises, embeds, and drafts — it is not a visible product feature that the user is expected to notice, configure, or trust with personal data. The UI visibility policy at docs/reference/ai-visibility-policy.md is the operational rule for this principle.

AI visibility cleanup complete (S156-S158). All 37 findings (12C + 10M + 15 Minor) from the design critique audit resolved across 8 WPs: vocabulary sweep, admin gating, badge/toggle rationalisation, change_reason wiring, client telemetry helper (lib/client-telemetry.ts). Review check at .claude/checks/ai-visibility.md. Audit: docs/audits/design/design-critique-session-7.md.

Save AI for response generation and classification. Use deterministic functions for deterministic tasks. This keeps costs predictable, latency low, and errors debuggable. The entity extraction filters (30+ deterministic rules in lib/ai/classify.ts and scripts/kb_pipeline/classify.py) are the canonical example — AI proposes entities, deterministic rules exclude false positives, AI is only re-invoked for Pass 2 validation when validate: true is set.


ComponentADS v1.0 PlannedActual Implementation
FrontendNext.js + CopilotKit/AG-UI as sole UI protocolNext.js 16 (CopilotKit removed S109 — ClaudePromptButton bridge for in-app AI entry points)
BackendFastAPI (Python)Next.js API routes (~159 route files across ~43 directories, +6 cron routes). No separate backend service.
DatabaseTurso per-tenant (LibSQL/SQLite)Supabase (PostgreSQL + pgvector + Auth + Storage). One project per client.
AuthSuperTokens (self-hosted)Supabase Auth (email/password + magic link, invitation-only)
DeploymentCoolify on UK VPS (Docker)Vercel
AI ReasoningAnthropic SDK + Agent SDK (dual backend)Anthropic SDK via AI service layer (lib/ai/). No Agent SDK.
EmbeddingsVoyage AI voyage-4 (1024-dim)OpenAI text-embedding-3-large (1024-dim, Matryoshka shortening)
EditorTiptap v3 + Tiptap AI Toolkit (paid)Tiptap (open source core only). No AI Toolkit.
ExportTiptap Conversion (DOCX) + LibreOffice (PDF/A)docx npm + exceljs (DOCX/XLSX). No PDF/A.
Document ProcessingPython worker on Railway + python-docxHybrid: Next.js API routes for extraction (unpdf + Claude) + Python scripts for bulk ingestion
OrchestrationLangGraph for state persistenceNone — stateless API routes + Supabase for persistence
Client BrandingBuild-time per-client config (NEXT_PUBLIC_CLIENT_ID), Zod-validated JSON, OKLCH contrast validation, CSS variable injection

Tenancy & entity hierarchy (canonical). Deployment is one Supabase project (database) per client — the DATABASE is the tenant boundary and the only cross-tenant boundary (no tenant_id column). A workspace is a container within that DB bound N:1 to one application_type (workspaces.application_type_id); a client has many workspaces, so workspace ≠ client/tenant. application_type is a use-case class (the application_types reference table — not a container). q_a_pairs are corpus-level — one client’s shared corpus, not workspace-partitioned (source_workspace_id/source_form_response_id are nullable lineage only). Hence ID-120 Q&A dedup is INTRA-tenant (across the client’s workspaces & forms), never cross-tenant. Full glossary: platform-direction.md → “Core entity hierarchy (canonical glossary)“.

Next.js 16 on Vercel
├── Frontend (React + shadcn/ui + Tiptap + TanStack Query v5 (40/40 hooks migrated) + @tanstack/eslint-plugin-query + ClaudePromptButton bridge)
│ └── Client Branding (build-time per-client config via NEXT_PUBLIC_CLIENT_ID, CSS variable injection, BrandLogo with light/dark variants)
├── API Routes (~159 route files across ~43 directories, +6 cron routes)
│ ├── Content CRUD (/api/items/)
│ ├── Bid workflow (/api/bids/)
│ ├── Search (/api/search/)
│ ├── AI operations (classify, extract, summaries, change-reports, vision)
│ ├── Governance (/api/governance/, /api/review/)
│ ├── Claude prompts (lib/claude-prompts.ts)
│ ├── Activity feed (/api/activity/)
│ ├── MCP server (/api/mcp/[transport]/)
│ ├── OAuth decision (/api/oauth/decision/)
│ ├── Plugin (/api/plugin/)
│ └── Admin (/api/admin/)
├── AI Service Layer (lib/ai/ — 12 modules)
│ ├── classify, summarise, change-report, embed, match, draft
│ ├── extract-content, extract-questions, vision, quality-check
│ ├── errors (AIServiceError pattern), index (barrel exports)
│ └── skills/ (5 Markdown skill files + loader)
├── MCP Server (lib/mcp/ — 30+ .ts files across tools/, formatters/, and top-level modules)
│ ├── tools/ (54 tools across 15 category files + index.ts + shared.ts)
│ ├── formatters/ (14 files — Markdown formatters per domain, including intelligence)
│ ├── resources.ts (12 resources + 7 prompts — +1 ui://intelligence-feed S144)
│ ├── auth.ts (per-user Supabase client, role check)
│ ├── app-bundles.ts (generated inline HTML for 4 MCP Apps — Vercel-safe)
│ └── plugin-bundle.ts (generated ZIP for plugin download — Vercel-safe)
├── OAuth & Protected Resource Metadata
│ ├── app/.well-known/oauth-protected-resource/ (RFC 9728 via mcp-handler)
│ └── app/oauth/consent/ (Warm Meridian consent page)
├── Anthropic SDK (Claude claude-sonnet-4-6 for classification, summaries, drafting)
├── OpenAI SDK (text-embedding-3-large for embeddings)
└── Supabase client (server + browser)
Supabase (eu-west-2 London)
├── PostgreSQL (42 tables, ~120 RLS policies, 1 squashed migration as of S176)
│ ├── 75+ RPC functions (search, analytics, entity operations, intelligence feeds, bid matching)
├── pgvector (HNSW indexes, cosine similarity)
├── Auth (email/password, magic link, invitation-only)
├── OAuth 2.1 Server (dynamic client registration, consent page)
├── Storage (documents bucket for uploads)
├── Auto-create viewer role trigger (on auth.users INSERT)
└── Edge Functions (not used — all logic in Next.js API routes)
Python scripts (local execution, not deployed)
├── kb_pipeline/ (ingest, classify, embed, store, dedup, layer_inference)
├── import_bid_library.py (Q&A extraction from client DOCX, --entities flag)
├── ingest_markdown.py (markdown ingestion with entity/relationship/temporal storage)
└── extract_docx_tables.py (table extraction)

Core: content_items (KB content with progressive depth: brief/detail/reference columns + answer_standard/answer_advanced for Q&A pairs + promoted columns: citation_count, source_file, layer, starred — S117; ai_summary renamed to summary per AI Visibility Policy — S164b), workspaces (containers — type: bid|kb_section), content_item_workspaces (junction), user_roles (admin/editor/viewer + display_name)

Bid: bid_questions (extracted tender questions), bid_responses (AI-drafted + human-edited responses), bid_response_history (bid response version snapshots)

Governance: content_history (immutable version snapshots), governance_config (review settings), notifications (user notifications), review_assignments (scheduled review assignments with assignee, due date, priority — S114), verification_history (immutable verification audit trail with optional notes — S115), coverage_targets (per-domain coverage goals with extensible metric model — S117)

Knowledge Graph: entity_mentions (entity mentions with metadata JSONB — certification/framework/registration enrichment added S103), entity_relationships (relationships between entities), content_citations (citation links between content items)

Templates: template_requirements (tender template structure), template_completions (filled template instances), templates and template_fields (tender template structure definitions), content_templates (content creation templates with pre-fill structure — S115)

Guides: guides (guide definitions), guide_sections (ordered section structure per guide)

Multi-client: entity_aliases (DB-driven alias resolution), layer_vocabulary (DB-driven layer definitions)

Source tracking: source_documents (uploaded file lineage with version chain and re-upload detection), source_document_diffs (Q&A pair-level diffs between document versions)

Sector Intelligence (S132-S149): company_profiles (per-workspace company context with sectors/services/certifications + cached embedding column added S149), feed_sources (RSS/Atom/Web sources with ETag and freshness tracking), feed_prompts (versioned scoring prompts with rollback audit trail), feed_articles (raw + filtered + passed articles with relevance scores and extraction_method column added S149), feed_flags (user feedback on classification), si_processing_queue (concurrency guard)

Support: read_marks, change_reports, pipeline_runs, processing_queue, ingestion_quality_log, taxonomy_domains, taxonomy_subtopics

Taxonomy: 15 domains (12 baseline + 3 SI), 56 subtopics in DB and classification prompt (S204 WP-D: corporate.financial merged into corporate.financial-standing — PQQ-aligned canonical; 16 row-updates across 5 columns / 4 tables; soft-delete preserves audit trail; lib/taxonomy/taxonomy.ts reduced to a 24-line re-export shim). provenance column tracks origin (baseline/client/recommended). S128: slug normalisation + display_name. S133: SI domains (legislation-policy, market-intelligence, sector-news) + 19 subtopics. S134: full reclassification against 15-domain taxonomy (251 items, 1,063 entities, 786 relationships).

1 squashed migration file (S176 squash: 43→1, reconciling entity_aliases table

  • 14 column drift + 4 MCP-created functions + storage bucket/policy gaps). Schema reconciliation report: docs/operations/schema-reconciliation-report-s176.md. 90+ public RPC functions.

4. Navigation & Pages (As Built + Planned)

Section titled “4. Navigation & Pages (As Built + Planned)”
RoutePurposeNotes
/ (Home)Activity & Action Centre — Reorient Me (urgent items, active bids, recent work, team changes), Needs Attention, Active Bids, Quick Stats, Activity Feed. McpSetupNudge gated on KB ≥1 item (S177). S190 P0-4 first-run onboarding (DashboardFirstRunCard): for admin + editor on first login (isFirstLogin && !dismissed && role !== 'viewer') a dashed-border welcome card surfaces above ReorientSection with role-branched CTAs, dismissable via localStorage, plus an optional 3-link persona hint row that writes user_metadata.primary_focus (bid_writing / account_management / marketing) — same value is editable in Profile Settings dropdown. Viewers see an updated welcome one-liner inside ReorientSection instead. When the KB has zero content (isKBEmpty), 5 sections are suppressed (ContentPerformanceSection, QuickStatsStrip, ComplianceStatusSection, PipelineRunsPanel, Recent Activity) alongside McpSetupNudge’s existing gate. Shared signal helpers live in lib/dashboard-signals.ts.Dashboard with search bar
/browseFilter-driven content browsing — grid/list views, multi-filter panel, infinite scroll, bulk actionsExcludes Q&A pairs by default (Session 54 decision)
/searchRedirect to /browse (search consolidated into Browse, S111/S113)Preserves ?q= param; API endpoint /api/search remains
/workspacesWorkspace launcher — type selection (bid, kb_section) with mental model framingS110: replaced /bid as nav entry point
/bidBid listing and creationWorkspaces with type=bid
/bid/[id]Bid detail — questions, metadata, tender extraction, statusQuestion extraction, confidence postures, progress tracking. ReadinessChecklist with 7-criteria readiness badge (S113-S114)
/bid/[id]/sessionWorking session — question-by-question draftingTiptap editor, Content Library drawer, ClaudePromptButton for AI-assisted drafting. Crash recovery (localStorage auto-save, DraftRecoveryDialog). “Browse for content” button for search-to-bid pipeline
/item/[id]Content item detail — split into ReaderView (viewers) + EditorView (editor+), shared data hooks, ContentTabs, metadata, version history. Consolidated to 2 edit paths: read-only (viewers) + inline edit via useInlineFieldEdit (editor+). DraftToggle routed through PATCH for audit trail (S177).Content-type-aware: articles use progressive depth tabs, Q&A pairs get dedicated layout. Keyboard shortcuts: E→focus suggested_title, Escape→cancel edit. VerificationBadge with binary trust levels (S115).
/item/newContent creation — consolidated 4-tab surface (S178 P0-2): “Write content” (Tiptap + RHF + Zod, with fullwidth TemplateSelector zero-state gate), “Import from URL” (fetch + extract + classify), “Upload file” (folds former FileUploadDialog inline), “Batch Q&A” (folds former /item/new/batch). ?tab= deep links. Legacy /item/new/batch → 308 permanent-redirect to ?tab=batch.S98-S99, S114-S115, S178
/reviewGovernance review queue — verify/flag items, undo toast, session counter, side panel, review cadence card, assignment managerEditor+ only. Keyboard shortcuts. Server-side sort with confidence priority (S114).
/change-reportsChange reports (formerly “digests” — renamed S246+S251 W1B) — generation + history. Reframed S115
/documents/[id]/diffSource document diff review — Q&A pair-level changes between document versionsS104-S106
/guide308 permanent redirect to /coverage?tab=guides (S188 P1-28 consolidation). The standalone guide listing route was removed; the guides tab in Coverage is the canonical surface and supports ?tab=guides&id=<slug> deep-links that scroll + highlight the matching cardS188
/guide/[slug]Guide detail — ordered sections, content per section, progress tracking, research feedSector and product guides
/intelligenceIntelligence hub — workspace grid, company profiles, pipeline statusS140: SI Phase 1b Waves 1-2
/intelligence/profilesCompany profile management — create/edit/delete profiles with sectors, services, certificationsS140
/intelligence/[workspaceId]Workspace detail — overview with RSS panel, feed sources, articles, metrics, prompts (sub-nav: Overview/Sources/Articles/Metrics/Prompts)S140-S142
/intelligence/[workspaceId]/metricsFull metrics dashboard — filter ratio trend chart (SVG), prompt performance table, period/granularity selectorsS142
/api/feeds/[workspaceId]/rssPublic RSS 2.0 feed of passed articles (no auth, 15-min cache)S142
/api/feeds/[workspaceId]/rss/filteredPublic RSS 2.0 feed of near-miss filtered articles (no auth)S142
/intelligence/[workspaceId]/sourcesFeed source management — add/edit/delete/test RSS/Atom/Web sourcesS140
/settings9-section settings sidebar in 3 groups — Personal: Profile, Connections; Content Management: Content Organisation, Content Owners, Organisations & People, Guides; System: Team, Quality Review, Provenance (links to /provenance). Developer configuration collapsed into an admin-only “For developers” accordion inside Connections (S185 P1-20)Role-adaptive sidebar
/loginSupabase Auth loginEmail/password + magic link
Browse | Q&A Library | Coverage | Change Reports | Workspaces | Intelligence | Review | Settings

S188: /guide nav entry removed (consolidated into Coverage → Guides tab); /change-reports Change Reports added to NAV_LINKS with requiresEdit: false so viewers/Tom can reach it (D-61 discoverability closure).

(Home is the logo link; Settings is shown for all roles; Review requires editor+)

RoutePurposeSession
/libraryDedicated Q&A pair management — list, filter, copy, edit, bulk actions, groupingSession 55-58 (Spec 5)
/coverageCoverage dashboard — taxonomy, template, and guide coverage (3 tabs). Freshness heatmap view (domain x subtopic grid with view toggle, S112). Editor+admin only (S189 P1-11 Option A) — viewers are redirected to /browse via client-side useUserRole().canEdit guard in coverage-tabs.tsx; /coverage nav link hidden for viewers (requiresEdit: true in NAV_LINKS). API routes remain auth-only (defence-in-depth deferred per spec OQ-3).Session 58, 85, 88, 112, S189 P1-11
/bidBid listing and creation — workspaces with type=bidSession 63 (nav redesign)
/guide308 → /coverage?tab=guides (consolidation). Detail reader route /guide/[slug] preserved.Session 81, S188 P1-28

Content Type Separation (Session 54 Decision)

Section titled “Content Type Separation (Session 54 Decision)”

Q&A pairs (93% of content) and KB content (articles, research, policies) have fundamentally different interaction models. Session 54 investigation found:

  • Progressive depth tabs are empty for all Q&A pairs
  • AI summaries are useless import artifacts for Q&A pairs
  • Q&A pairs need copy-to-clipboard as primary action, not read-and-learn
  • Standard/Advanced answer variants are concatenated as unstructured text

Decision: Separate Q&A management into /library (see Spec 5). /browse is for KB content. Both share the same content_items table and search index.

Browse + Search (consolidated S111/S113): Browse now includes integrated semantic search. Entering a query in the Browse search bar triggers hybrid vector+keyword search with content-type-aware results (S113). The standalone /search page redirects to /browse preserving ?q=. The /api/search endpoint remains for the Content Library Drawer and MCP tools.

Projects + Bids → Workspaces: Same projects table with type discriminator. Merging under “Workspaces” reinforces the KB-first framing: knowledge base is the foundation, workspaces are where you act on it.

5. Feature State (What’s Actually Built)

Section titled “5. Feature State (What’s Actually Built)”

Production counts: Content-item, test, MCP-tool, and migration counts drift every session. docs/generated/codebase-stats.md and docs/generated/mcp-inventory.md are the canonical source — this document no longer tracks counts inline.

Capability summary only. Session-by-session narrative is tracked in the session ledgers.

  • Full CRUD (create, read, update, archive/delete)
  • Edit-path consolidation: 2 paths (read-only for viewers, inline edit via useInlineFieldEdit for editor+). 4 inline components in content-tabs.tsx (InlineTitle, InlineSuggestedTitle, InlineContent, InlineAnswer). DraftToggle routed through PATCH (creates content_history audit entry). Keyboard shortcuts: E focuses suggested_title (or cancels when editing), Escape cancels.
  • Single content creation entry point at /item/new — 4-tab surface (Write / Import URL / Upload / Batch). /item/new/batch permanent-redirects via HTTP 308 to /item/new?tab=batch.
  • Browse header strip — single + New split-button dropdown, single “Display” dropdown, Sort. Cards show “SI” badge when metadata.source === 'intelligence_pipeline'. Source filter plumbed through URL param + Supabase metadata->>source JSONB filter.
  • Browse FilterPanel — three-tier disclosure (Primary / Secondary / Advanced) with Domain + Subtopic + Content Type + Content Layer + Freshness primary. PresetBar above the panel. Saved filter presets via PresetBar / SavePresetDialog / ManagePresetsDialog / useFilterPresets (localStorage).
  • Browse cold-start persona prompts — <SearchPromptCards> grid renders when /browse has no query, no filters, not unread-only, not loading, totalCount > 0. Three persona branches (bid_writing, account_management, marketing) read via usePrimaryFocus() from user_metadata.primary_focus + viewer fallback. Discriminated-union card data model (kind: 'filter' | 'search' | 'chipComposite'); filter cards write URL params, search cards run semantic search, chipComposite cards render top-3 domain chips (hooks/browse/use-top-domains.ts, 24h-cached). 5-condition visibility gate in lib/browse-cold-start.ts shouldShowColdStartPrompts().
  • Unified SearchBar — three Browse search surfaces (hero / compact / inline) share one component. Live preview dropdown via GET /api/search/preview + useDebouncedPreview hook (TanStack useQuery, AbortController, staleTime: 30_000). Inline dropdown layers Recent searches / preview / Popular topics with aria-live="polite", ArrowUp/Down keyboard navigation, “See all results” footer. from_bid URL param sticky through filter mutations.
  • Item detail Source Information accordion — content-type-aware via SourceMetadata (components/reader/source-metadata.tsx) branching on content_type: QAPairFields, MarkdownFields (10 prod values via INGESTION_SOURCE_LABELS), FeedArticleFields (feed name + published date via feed_articles → feed_sources join). Role-gated classification_confidence (admin/editor only).
  • Q&A answer editing via dynamically-imported Tiptap ContentEditor for answer_standard / answer_advanced — single-field-at-a-time pattern, per-field save-safety guard (≥20% shrink blocks), per-field regen-embedding checkbox. PATCH preserves Q: {question}\n\n prefix in rebuilt content_items.content.
  • Inline body editing with Tiptap (outputs markdown via @tiptap/markdown). GFM table node family registered (@tiptap/extension-table + row/cell/header) — tables round-trip through edit/save without loss. Save-safety guard in lib/editor/save-safety.ts (SAVE_SAFETY_MIN_RATIO = 0.8) wired into Save-button path and Cmd/Ctrl+S path.
  • Markdown canonical format end-to-end. All 8 extraction paths (HTML/Readability, DOCX/mammoth, PDF/unpdf, SI Tier 1 RSS, SI Tier 2 fetch, Python trafilatura, Python PDF) produce markdown. ContentRenderer (react-markdown) for full display; stripMarkdown() for snippets. Shared Turndown instance with GFM plugin for HTML→markdown. PipelineExtractionResult interface for structured extraction metadata.
  • Heading-based content chunking — content_chunks table with per-chunk vector(1024) embeddings, populated by chunkByHeadings() in lib/content/chunking.ts (H2 boundaries with H1 fallback, sub-100-char merge, sub-500-char single-chunk). Chunks generated on file upload / URL ingest / web-form create / MCP create / content edit. Parallel Python implementation in scripts/kb_pipeline/chunk.py. Backfill CLI at scripts/backfill-chunks.ts.
  • Re-ingestion quality protocol — operator runbook at docs/operations/re-ingestion-quality-protocol.md (6-step before-and-after protocol, embedding smoke-test, pipeline divergence assessment, DOCX pre-processing checklist, source-document inventory).
  • Markdown batch ingest UI (EP2, §1.11) — admin/editor drag-and-drop multi-file .md upload at /item/new “Upload file” tab routes to dedicated app/api/ingest/markdown/route.ts (maxDuration=300, batch ceiling 10 files / 5 MB total / 1 MB per file, UTF-8 only, .md extension only). Two-phase orchestration via lib/ingest/markdown-orchestrator.ts: phase=analyse returns per-file analysis (front-matter parse + title extraction + diff-marker scan + dedup pre-check, no DB writes) so reviewers see flags before importing; phase=import runs the full pipeline (insert content_items with ingest_source='upload' + content_owner_id resolved + draft/final → publication_status mapping per D-A: 'draft'/'unknown' → 'draft', 'final' → 'in_review', then classify + chunk + embed). Pattern E end-to-end (server-side pipeline_runs writes via recordPipelineRun() + updatePipelineProgress() + UI polling on GET /api/pipeline-runs/:id every 1-2s during import); rich results_summary shape (stored[]/dedup_flagged[]/superseded[]/skipped_excluded[]/errored[]) returned in the POST response and persisted to pipeline_runs.result. Dedup is soft-block (matched rows still insert with dedup_status='suspected_duplicate'); admin skip_dedup=true bypasses, editor request silently ignored. EP3 single-file upload route untouched (maxDuration=60 preserved); EP2 dedicated route is strictly additive.
  • Publication-review tab on /review (§5.2-P4B + Radix Tabs refactor + §5.3 bulk-approve UX) — admin/editor surface for items in publication_status='in_review' (the state EP2 markdown imports land in for final-flagged content). 6-tab Radix surface replaces the prior popover-button filter modes: Drafts / Pending changes / Verified content review / Verified (audit) / All / Awaiting publication (canonical order per components/review/review-tabs.tsx TAB_SPECS). Tab state lives in URL (?tab=publication-review etc., default verified-review); deep-links round-trip; back button skips tab clicks (router.replace, no history entry per click). Per-row actions on the publication-review tab: Approve & publish (PATCHes publication_status='published'), Return to draft (PATCHes 'draft'), Open in editor (link to /item/[id]). Both transitions are role-allowed for admin and editor per lib/governance/publication-transitions.ts:75-80. Stats route widened to surface awaiting_publication: number; new route app/api/admin/content-dedup/... already covered by §1.7 narrative above. §5.3 bulk-approve UX (S219 + S220, fully shipped end-to-end): per-row checkboxes + sticky bulk action bar (<PublicationBulkActionBar>) mount above the queue when ≥1 row selected. Bar surfaces “N of M selected” live region, “Select all on page” master checkbox (indeterminate at partial selection), Clear, Approve selected, Return selected to draft. D-3 ratified cap = 50 client-side (action buttons aria-disabled + cap message past 50, mirroring server-side Zod cap); D-4 symmetric Radix AlertDialog confirmations on both bulk actions. Bulk endpoint POST /api/review/publication-bulk-action iterates the per-row PATCH semantics sequentially with bulk-specific change_reason='bulk_approve' | 'bulk_return_to_draft' audit literals (distinct from singleton-PATCH phrasing so audit queries differentiate); HTTP 200 always with { totalRequested, successCount, failureCount, results[] } envelope and 5-status enum (success | conflict | forbidden | not_found | error); pre-loop fromStatus !== 'in_review' guard returns 'conflict' regardless of role/action (D-10 defence-in-depth on top of .eq('publication_status','in_review') UPDATE filter so stale-queue selections cannot silently 'published' → 'draft'). Partial-failure result dialog (<PublicationBulkResultDialog>) opens automatically when failureCount > 0, listing per-row outcomes via itemTitleLookup Map (falls back to <UUID> (item no longer in queue) when title missing). Selection state lives in usePublicationReviewSelection() (page-scope ephemeral Set, clears on tab unmount). Spec archived to .planning/.archive/.specs/publication-approval-gate-spec.md. Shipped S215 W1+W3 (tab + per-row actions); §5.3 W1 endpoint S219 W1; §5.3 W2 UI + W3 tests S220.
  • Audit-of-verified surface (/review?tab=verified-audit, S214 OQ1 + S215 T9 fold-in) — 4th tab “Verified (audit)” preserves the bulk re-verify and unverify capabilities on already-verified items. Admin and editor can mark a verified item as freshly checked (re-verify, refreshing verified_at) or push it back to the review queue (unverify). MCP tool lib/mcp/tools/review.ts verified enum stays intact regardless of UI tabs (LLM-side surface unchanged). Capability previously undocumented in SoTP / roadmap / backlog; folded in S215 T9 per Liam OQ1 decision (kept as a tab rather than redirecting to /browse?review_status=verified).

Re-ingestion arc (S175-S182, cutover complete)

Section titled “Re-ingestion arc (S175-S182, cutover complete)”

The blank-DB re-ingestion of Phew’s content onto a fresh Supabase project (mgrmucazfiibsomdmndh, eu-west-2) spanned eight sessions across four phases: prep (S175-S178, migration squash 43 to 1, restore matrix, runbook, guide-regeneration prompts), execution (S179-S180, Stage 0 snapshot of 342 items, Stage 1 auto-ingest 291 items, Stage 2 client markdown 220 items, two rounds of post-squash drift closure), cutover hardening (S181, full schema reconciliation with 6 ADD COLUMN + 29 SET NOT NULL, chunk backfill, skills-bundle build-time inlining), and cutover + close-out (S182, Path A+ project transfer with zero downtime, screenshot parity audit, 34 missing embeddings + 78 empty-string classification cells backfilled). The consolidated review doc is at docs/operations/re-ingestion-status-review-2026-04-21.md.

Capability summary only. Session-by-session narrative is tracked in the session ledgers.

  • Production project (rovrymhhffssilaftdwd): prod app reads from this project (Vercel + GitHub env vars flipped 22/04/2026; prod JS bundle carries the project ref). Sister project ‘M’ deleted via Supabase dashboard 23/04/2026; full pg_dump backup archived at docs/database/M_full_backup_20260423.sql (gitignored).

  • Quality gate: scripts/quality-gate.ts with 12 generic + 7 audit-content checks across 4 profiles (re-ingest / batch / onboarding / audit-content). CLI: --threshold | --profile | --format | --output | --fail-on | --include-check | --exclude-check. Config sidecars under scripts/config/quality-gate/. UUIDv7 run IDs, git_sha + timestamp + project_id fields in JSON envelope. excludeArtefacts() helper excludes [E2E% + [SUPERSEDE% titles from generic checks.

  • Shared Python post_insert helperscripts/kb_pipeline/post_insert.py::run_post_insert(...) runs 8 post-insert side-effects in canonical order (content_history v1 → chunks → entity aliases → entity_mentions → entity_relationships → metadata.ai_temporal_references → temporal-to-entity bridge → layer inference). Wired into all 4 ingest scripts (pipeline.py, ingest_markdown.py, ingest_stage2_markdown.py, import_bid_library.py). Step 1 (history) is a deliberate no-op — the DB trigger 20260422060118 is the single source of truth for v1 history writes.

  • Supersession: content items can be marked superseded via admin UI (supersede-content-dialog.tsx + PATCH /api/items/:id admin-only double-gated), MCP supersede_content_item tool (defineTool wrapper with destructiveHint), or Python CLI --auto-supersede / --auto-supersede-dry-run (mutually-exclusive). Superseded items hidden from search by default (include_superseded=false); undo re-exposes the row. Schema: superseded_by UUID FK on content_items + partial index + self-reference CHECK; dedup_status enum widened with 'superseded'.

  • v1 history guarantee: three-layer structural prevention — DB trigger backstop (migration 20260422060118, trg_content_items_ensure_v1_history, DEFERRABLE INITIALLY DEFERRED), app-level explicit writes in 5 of 8 TS/MCP insert paths, quality-gate history_v1_present monitoring, and guard test (__tests__/validation/content-items-v1-history-guard.test.ts) enforcing every content_items insert pairs with a v1 write or sits on a documented allowlist.

  • Dedup: content-hash soft-block across all 11 entry points (Python EP1/EP2/EP2b/EP8 + TS EP3/EP4/EP5/EP6/EP9/EP10/EP11). Admin-only skip_dedup override. RSS D3 many-to-many via content_item_workspaces junction.

  • Admin dedup review surface (§1.7): /admin/content-dedup/ queue + /admin/content-dedup/[id] detail page (admin-only, route-group page; allow-listed in proxy.ts). Lists dedup_status='suspected_duplicate' items with metadata.suspected_duplicate_of canonical reference; per-item actions confirm-duplicate (delegates to app/api/items/[id]/archive/route.ts), confirm-unique (flips to 'confirmed_unique'), and supersede (delegates to lib/supersession/set.ts::setSupersession()). Supersede supports both directions (subject→canonical default, canonical→subject reverse) via direction body field; reverse path retires canonical AND flips subject to 'confirmed_unique' so queue resolves either way (per docs/specs/§1.7-admin-dedup-supersede-fix-spec.md). All five routes — GET /api/admin/content-dedup, GET /api/admin/content-dedup/[id], POST /api/admin/content-dedup/[id]/{confirm-duplicate,confirm-unique,supersede} — write content_history rows with category-specific change_reason constants; history insert failures use logBestEffortWarn(). UI components in components/admin/content-dedup/ (queue, row card, filter bar, detail, action buttons, empty state). Playwright E2E coverage on staging via e2e/tests/admin-dedup-queue.spec.ts (read-only — list, filter, detail compare, RBAC matrix) + e2e/tests/admin-dedup-actions.spec.ts (mutating — confirm-duplicate, confirm-unique, supersede A/B, SAME_ID guard, audit trail per action) — see Test Infrastructure §8.

  • Near-duplicate merge dashboard (§1.9): /admin/content-dedup/near-duplicates list + /admin/content-dedup/near-duplicates/[pairId] detail page (admin-only). Pair-id is canonical lex-sorted ${uuid}__${uuid} (helper lib/dedup/pair-id.ts). Lists embedding-cosine pairs from find_duplicate_pairs RPC; threshold slider default 0.95, range [0.85, 0.99], 300ms debounce; primary-domain filter; terminal-status pairs (merged/confirmed_unique/archived) excluded so resolved pairs stop re-surfacing. Detail view re-computes similarity at click time so reviewers see drift between flag-time and review-time scores. Two pair-level actions: merge (POST /api/admin/content-dedup/near-duplicates/[pairId]/merge, delegates to setSupersession) — direction defaults to retain-newer-with-richer-content via defaultMergeDirection heuristic, admin can flip; confirm-unique (POST .../confirm-unique) — calls transactional resolve_near_dup_confirm_unique PL/pgSQL RPC that flips both pair members to 'confirmed_unique' and writes both content_history snapshot rows in one transaction (idempotent re-flip; SECURITY INVOKER; anon REVOKEd). Both actions persist OQ2 audit context (metadata.similarity_at_resolution, metadata.threshold_at_resolution) so future audits can reconstruct flag-time score + threshold. Components in components/admin/content-dedup/near-duplicates/ — pair list, filter bar, pair detail, action buttons, merge-direction dialog, empty state, pair row card (forked from §1.7 row card; types differ: left/right pair members vs subject/canonical discriminant). Playwright E2E coverage on staging via e2e/tests/admin-dedup-near-duplicates.spec.ts (read-only — pair list, threshold slider debounce, domain filter, pair detail, RBAC) + e2e/tests/admin-dedup-near-dup-actions.spec.ts (mutating — merge with OQ2 audit-context assertion, transactional confirm-both-unique, empty state) — see Test Infrastructure §8.

  • Pipeline markdown parity: lib/extraction/extraction-result.ts (TS) and scripts/kb_pipeline/extraction_result.py (Python) produce parity PipelineExtractionResult output; cross-language parity tests cover 8 fixtures × 8 assertions in each language.

  • Progressive depth: brief, detail, reference columns on content_items + content (full text)

  • ContentTabs component with human-authored vs AI-generated toggle per depth level

  • SummaryData (executive/detailed/takeaways) via AI generation

  • Semantic + keyword hybrid search (superseded items excluded by default)

  • Browse with 12+ filter dimensions, saved filter presets (localStorage)

  • Content Library Drawer (Cmd+L in bid session) with search, copy, insert-with-citation

  • File upload to Supabase Storage

  • PDF/DOCX extraction to markdown (unpdf with --- page separators; mammoth HTML→Turndown two-step for table preservation)

  • Anthropic Files API integration (upload-once, query-many)

Capability summary only. Session-by-session narrative is tracked in the session ledgers.

  • AI classification via Claude tool use (domain, subtopic, keywords, summary, confidence). Reclassification on demand (force=true). Taxonomy loaded from DB (taxonomy_domains + taxonomy_subtopics, admin-editable).
  • AI summary generation (executive/detailed/takeaways). Embedding generation via OpenAI text-embedding-3-large (1024-dim). Embedding truncation at MAX_EMBEDDING_CHARS = 24_000; TS+Python parity guard.
  • ClaudePromptButton bridge (contextual prompt generation, opens Claude directly).
  • Classification skill (lib/ai/skills/classification.md, 795 lines) as single source of truth for the classification prompt (v4.5). Loaded at runtime with 6 placeholders resolved from CLIENT_CONFIG.
  • 30+ post-extraction entity filters across 9 categories in both TS and Python pipelines (full parity). Entity type taxonomy spec v1.1 (docs/reference/entity-type-taxonomy-spec.md) — 12 entity types with diagnostic questions, disambiguation matrix, and 8 few-shot examples. CLIENT_DISAMBIGUATION rules extracted to lib/client-config.ts for multi-client readiness.
  • Two-pass entity validation (Pass 1 Sonnet + Pass 2 Haiku) controlled by validate parameter. Pass 2 temperature: 0. Surgical bulk-cert rule with enumerated co-occurrence sets (3 named groups). Architecture: docs/reference/two-pass-validation-architecture.md. Recommendation: batch-only.
  • Entity-mention dedup at upsert boundary — pre-upsert dedup by (content_item_id, canonical_name, entity_type) eliminates Postgres error 21000. Merges matched_text arrays, sums mention_count, takes max confidence.
  • Entity relationships UNIQUE constraint — entity_relationships_unique_tuple covers (source_entity, relationship_type, target_entity, source_item_id) with NULLS NOT DISTINCT (PG 17+). lib/ai/classify.ts relationship insert uses .upsert({ ignoreDuplicates: true }); Python store_relationships treats 409 as skipped.
  • Entity-management admin inline type-change — entity detail panel <Select> + entity list TypeEditDialog both via TanStack useMutation calling admin-only PATCH /api/entities/[canonical_name]/type (writes entity_type_override on entity_mentions — citations preserved, no cascade). TYPE_COLOURS covers all 12 entity types.
  • Publish-from-draft AI processing — when a draft-created item is published and classified_at IS NULL, the publish path re-runs classifyContent + regenerateChunks against the service client so the item gains entity_mentions, entity_relationships, summary, and content_chunks before becoming live-searchable. Both paths log a publish_classify entry to pipeline_runs. Non-fatal — classify/chunk failure surfaces as a warning but does not un-publish.
  • Cert classifier holder disambiguation (full TS + Python parity) — app/api/certifications/route.ts + MCP tool get_certification_status filter holds relationships by source_entity == BRANDING.organisationName.toLowerCase() AND require metadata.holder explicitly set. Python classifier emits metadata={holder: 'self'|'supplier', supplier_name?} on entity_mentions when holder context is extractable. TS deriveHolderMetadata carries the rule end-to-end. Synonym fallback accepts complies_with / evidences for certification targets only when (a) target is a certification entity, (b) source is the client org or extracted org entity, (c) no canonical holds rel exists for that target.
  • Tag canonicalisation at classify write-path — pluralize@8 (TS) + inflect==7.5.0 (Python) for content_items.ai_keywords morphology. Domain carve-outs (11 -ics fields-of-study, news/means/series/species, DUNS allowlist) run as a pre-pass override; compound last-token guard prevents Latin/Greek regressions. Admin triage UI for library-vs-actual disagreements lives at /settings?section=tag-morphology (tag_morphology_drift_flags table, admin/editor RLS). Shared cross-language fixture __tests__/fixtures/keyword-normalisation-cases.json (74 cases) enforces TS↔Py parity.
  • Structured outputs for tender metadata extraction.
  • Entity classification current metrics: precision 72.7%, recall 80.0%, F1 76.2%, type accuracy 94.8%, exclusion compliance 77.8%, cross-item consistency 96.6%. Gold standard fixture: 93 items. ISO family type override forces 6 ISO families to certification deterministically. Iteration log: docs/audits/s154-entity-classification-iteration-log.md.
  • Temporal metadata bridgemetadata.ai_temporal_references extracted at classify time then bridged to entity mentions via inferContextType heuristic + parseDuration (ISO 8601 time components incl. PT72H). 17 generic methodology/framework terms in GENERIC_CONCEPTS (TS+Python parity).

Capability summary only. Session-by-session narrative is tracked in the session ledgers.

  • Procurement creation with metadata (title, buyer, deadline). ProcurementCreationWizard equal-weight Create Blank vs Create & Upload Tender (grid-cols-1 sm:grid-cols-2).
  • Tender upload (PDF/DOCX) with question extraction.
  • KB matching with confidence postures (strong/partial/needs_sme/no_content).
  • 3-pass AI response drafting (match → draft → stream).
  • Tiptap editor with inline editing, citation markers.
  • Response versioning (counter + history trigger).
  • Procurement workflow state machine (PROCUREMENT_WORKFLOW_STATES: draft → questions_extracted → drafting → in_review → ready_for_export → submitted → won/lost/withdrawn). Source: lib/procurement/procurement-workflow.ts.
  • DOCX + XLSX export (lib/procurement/procurement-export-{docx,xlsx,data,types}.ts). Template completion (python-docx write-back). Tender metadata extraction (Claude tool use).
  • Procurement draft crash recovery (useDraftRecovery, DraftRecoveryDialog — localStorage auto-save with session restore).
  • Readiness checklist (7-criteria API, ReadinessChecklist component, ReadinessBadge).
  • Search-to-procurement pipeline (QuickAssignButton, useQuickAssign, batch-workspaces API, ?from_bid URL context — URL param preserved for backward-link stability).
  • Overview tab lean layout — NextActionCard is the single state-driven CTA; Submission Readiness remains as the reviewer-landing key card.
  • Citation panel default-open for admin + review-or-later states; collapsed by default for editor + drafting states. Per-question remount via key prop preserves the initial default on question switch.
  • URL-synced tab + filter state on procurement detail (/procurement/[id]) — ?tab=<id>&q=<search>&status=<filter>&sort=<field> drive active tab and question-list filters; deep-linkable, shareable between collaborators.
  • Q&A library DOCX extractor set — lib/procurement-library-ingest/{docx-to-markdown,extract-qa-pairs}.ts provides a pair-level Q&A extractor with markdown-preserving output via two-step mammoth → Turndown pipeline (GFM plugin for tables) keeping bold / italic / links / lists / nested tables intact. Python side has scripts/docx_cell_to_markdown.py (pandoc subprocess wrapper) and import_bid_library.py build_content_record emitting canonical Q: {q}\n\n{standard}\n\n {advanced} markdown. Cross-language parity test (KH_RUN_INTEGRATION=1-gated). Known limitation: Python cell converter preserves bold + italic only while TS preserves links / lists / nested tables.
  • S248 T4 procurement umbrella rename — code-side surface renamed end-to-end: lib/bid/* → lib/procurement/*, lib/bid-library-ingest/ → lib/procurement-library-ingest/, components/bid/* → components/procurement/*, types/bid.ts → types/procurement.ts, hooks/bid/* → hooks/procurement/*, app URL paths /bid → /procurement + /api/bids/* → /api/procurement/*, MCP tool renames (show_bid_dashboard → show_procurement_dashboard, formatBidDashboard → formatProcurementDashboard, list_active_bids → list_active_procurement, etc.). Symbols: BID_STATES → PROCUREMENT_WORKFLOW_STATES, BidState → ProcurementWorkflowState, Bid → Procurement cascade across ~60 PascalCase + ~35 camelCase identifiers. CI guard __tests__/validation/no-bid-regression-guard.test.ts prevents regression. Deferred items tracked as backlog ID-21 (TanStack workspace-types retire — reframed S249 Path (c); still parked). ID-22 (RPC return-field rename) and ID-23 (defer-rename investigation for lib/mcp/formatters/bids.ts + lib/ai/skills/bid-writing.md) both closed in subsequent sessions: ID-23 done S249 (commit 95b660efbids.ts → procurements.ts, bid-writing.md → procurement-writing.md), ID-22 done S250 (migration 20260521100650 — RPC return-shape rename + broken-body fix).

Capability summary only. Session-by-session narrative is tracked in the session ledgers.

  • governance_review_status on content_items: 5 values (pending, approved, reverted, changes_requested, review_overdue). 'draft' removed post-publication-lifecycle split. Code-side guard at app/api/governance/review/route.ts and lib/mcp/tools/governance.ts uses shared ALLOWED_REVIEW_INPUT_STATUSES = ['pending', 'review_overdue'] allow-list (lib/governance/review-input-statuses.ts).
  • Recurring review cadence (§5.5 Phases 1-5 fully shipped). content_items.next_review_date date NULL + content_items.review_cadence_days integer NULL (CHECK 1-1095). Partial index idx_content_items_next_review_date (excludes superseded + archived). Daily cron at app/api/cron/review-cadence/route.ts (03:45 UTC) flags next_review_date < CURRENT_DATE items as 'review_overdue' (only for items currently NULL or 'approved'), writes 1 notification per recipient (idempotent within-day), uses batch summary at threshold 20, records pipeline_runs row with pipeline_name='review_cadence'. Auto-renewal in 'approve' branch via shared lib/governance/cadence-renewal.ts computeNextReviewDate(...) helper.
  • Recurring review cadence — UI surfaces. “Overdue reviews” toggle in components/review/review-filters.tsx with count pill from stats.overdue; route app/api/review/queue/route.ts widens default filter to OR clause verified_at.is.null,governance_review_status.eq.review_overdue when include_overdue=true. components/shared/review-cadence-badge.tsx four-band matrix: overdue (red) / due ≤14d (amber) / due ≤30d (muted) / >30d (no badge); pure helper calculateReviewBand() exported for unit testing. components/content/review-cadence-editor.tsx admin/editor cadence control with <input type="date"> + 5-preset Select (None / 90 / 182 / 365 / Custom 1-1095). Provenance per-item-tab.tsx Review Schedule subsection renders Next review date / Review cadence / Last reviewed via canonical formatDateUK helper.
  • Recurring review cadence — MCP filter widening. search_content_chunks widened with overdue_review: boolean + review_due_within_days: integer (1-365); RPC migration 20260428212936_extend_search_content_chunks_review_filters.sql. get_governance_queue widened with include_overdue: boolean + status_filter: enum('pending'|'review_overdue'|'all'). Existing 4-arg callers unchanged.
  • Cadence-compliance scorer. lib/quality/quality-score.ts exports pure helper cadenceCompliancePenalty(nextReviewDate, now) returning 0/5-10/15/25/40 per spec §9.3 schedule (>30d no penalty / 1-30d graduated linear -10 / 1-14d overdue -15 / 15-30d overdue -25 / >30d overdue -40). freshnessRaw() applies penalty only when nextReviewDate is non-null (preservation rule). Caller wires: content-card.tsx, app/api/cron/quality-score/route.ts, metadata-sidebar.tsx pass next_review_date + review_cadence_days to QualityScoreBreakdown.
  • Unified publication lifecycle (§5.2 — fully shipped). content_items.publication_status (NOT NULL DEFAULT 'published', CHECK enum {'draft', 'in_review', 'published', 'archived'}) is the four-state lifecycle column. Three partial indexes (idx_content_items_publication_status_published, idx_content_items_published_recent, idx_content_items_archived). Bidirectional enforce_archive_state_consistency PL/pgSQL trigger enforces archived_at <-> publication_status='archived' invariant across 4 directions. PATCH route at app/api/items/[id]/route.ts accepts field='publication_status' with optimistic-concurrency guard (.eq('publication_status', fromStatus) → 409 PGRST116 on stale write); MCP update_publication_status tool mirrors the route semantics; both consume shared helper lib/governance/publication-transitions.ts (VALID_PUBLICATION_STATUSES, computeAllowedTransitions(state, role), applyTransitionSideEffects(...)). All 6 production writers target the new column; AC6.5 grep guard test enforces zero write-position regressions. RPC visibility filter (Phase 3). Eight production RPCs gate visibility on publication_status='published' by default — hybrid_search, search_for_bid_response, search_content_chunks accept a visibility_filter VARCHAR DEFAULT 'default' parameter ('default' → published-only, 'all' → exclude archived, 'admin' → all states); the four coverage/guide RPCs (get_coverage_matrix, get_coverage_summary, get_guide_content, get_guide_coverage) and get_review_breakdown_stats enforce publication_status='published' in-body (no parameter). Migration 20260430192325_widen_search_rpcs_visibility_filter.sql. get_filter_counts (browse-sidebar checkbox counts + Browse-by-domain chip composite) widened in S217 W1C to match — migration 20260501173008_widen_get_filter_counts_publication_status.sql switches its three sub-aggregations from archived_at IS NULL to publication_status='published' so counts and search results stay symmetric across the same surface. Admin UI opts into visibility_filter='admin' to see drafts/in-review/archived; default user-facing search and browse return published rows only. Supersession + cron-exclusion (Phase 5). setSupersession() archives the OLD row in the same UPDATE — sets publication_status='archived', archived_at=now(), archived_by, and archive_reason='Superseded by item ${newId}' (overridable via optional archiveReason arg) in lockstep with the legacy superseded_by / dedup_status='superseded' writes; the §6.6 trigger keeps archived_at and publication_status in sync, but the explicit dual-write defends against trigger bypass. app/api/cron/review-cadence/route.ts adds .neq('publication_status', 'archived') belt-and-braces alongside the existing archived_at IS NULL filter to skip archived rows. Phase 4 UI surfaces (PublicationStatusBadge S212 + publication-review queue tab S215) shipped earlier — see §5 above.
  • Governance review API (approve, request_changes, revert).
  • Review queue page with sticky progress bar, session counter, undo toast. ReviewQueuePanel with L-key toggle + item selection. Sort driven exclusively by filter popover’s sort URL param (confidence_asc, quality_score_asc, created_at desc). Review toolbar reduced to two pills (Session summary + Panel toggle) with ReviewCadenceCard rendered unconditionally.
  • Content version history with immutable snapshots; version comparison and rollback.
  • Governance config table with preset-based configuration (Light-touch / Strict presets; preset column on governance_config). Admin selects a preset per domain which populates concrete column values at save time. API accepts { domain, preset }.
  • Quality-to-governance bridge — auto-triggers review when quality score drops below threshold.
  • Freshness-to-governance bridge — auto-triggers review on freshness state transition.
  • Classification confidence as review priority — low-confidence items sorted higher, server-side sort.
  • Review assignment system — review_assignments table, POST/GET /api/review/assignments, AssignmentManager UI with assignee/due-date/priority.
  • Review cadence dashboard — GET /api/review/cadence, ReviewCadenceCard showing overdue/due-soon/completed stats.
  • Browse-review convergence — useQuickReview, QuickReviewActions on browse cards/rows.
  • Send-to-review from browse — bulk action to flag items for governance review.
  • Diff-to-review pipeline — inline diff highlighting (DiffHighlightedText, diffWords) with side-by-side + card views.
  • Verification history — immutable audit trail in verification_history table, displayed on item detail.
  • Provenance via content_history.change_reason — every version captures WHY it was created. Wired across all 9 TS write paths and Python ingest pipelines. Admin UI “Why change?” input on content-tab editors. Canonical vocabulary in docs/reference/data-entry-points.md Appendix D. Structural guard test prevents regression.
  • Source document notifications (lib/source-document-notifications.ts — notifies content owners on source document updates).
  • Content owner assignment — ContentOwnerSelector, bulk assign, owner filter, freshness cron targeting. Single “Assign owner” dialog with a RadioGroup scope toggle (Unowned only / By domain). bulk_assign_owner MCP tool — admin-only, NON_IDEMPOTENT_WRITE_ANNOTATIONS. Wraps bulk_assign_content_owner RPC with scope filter (domain / subtopic / content_type), dry-run preview, cursor pagination (opaque base64url of {last_id, scope_hash} with mid-pagination scope drift detection), skip-if-owned default plus force_override: true opt-in, notification control, and per-item content_history audit write (change_reason: 'owner_change') via sb() + logBestEffortWarn().
  • Notifications system (governance, quality, change-report types).
  • AI-generated change reports (originally “digests” pre-KH; renamed S246 + S251 W1B completed full terminology cascade). /change-reports period-scoped grid with delta-focused counters (items added, items modified, freshness transitions); single period dropdown filter. Separate “Current KB Health” card carries fresh/aging/stale/expired state-of-the-world counts. Auto-gen cost guard: pre-flight item count; if typedItems.length >= 150 (CHANGE_REPORT_AUTO_GEN_MAX_ITEMS), the API returns HTTP 413 with structured CHANGE_REPORT_TOO_MANY_ITEMS error and the client renders a “KB too large for auto-summary” empty state. user_notification_prefs.auto_generate_change_reports opt-out switch in Settings → Profile → Notification Preferences. AbortController drives a cancel button.
  • Quality flagging (ingestion_quality_log + browse filter).

Capability summary only. Session-by-session narrative is tracked in the session ledgers.

  • Supabase Auth with invitation-only registration. 3 roles (admin, editor, viewer) via user_roles table.
  • Phew-domain auth hook. public.hook_restrict_signup_to_phew_domain(event jsonb) wired to the before-user-created auth event via Supabase dashboard gates sign-ups to @phew.org.uk only; non-matching emails rejected with HTTP 403. Captured in migration 20260424202806_capture_phew_domain_hook.sql (SET search_path = public, extensions + REVOKE EXECUTE ... FROM public, anon, authenticated, service_role + GRANT EXECUTE ... TO supabase_auth_admin). Dashboard wiring is NOT captured in SQL — must be re-configured after any project reset. Full audit: docs/reference/auth-hooks.md. Combined with viewer-default trigger (on_auth_user_created), new Phew users get least-privilege access on first magic-link request. Multi-client table-driven allowlist tracked as backlog 33; admin invite-only flow as OPS-28.
  • display_name column on user_roles — explicit name field for UI display and review history attribution.
  • Role-adaptive UI (edit controls hidden for viewers).
  • Team management in Settings (invite, role change, remove) — single responsive flex-row list (TeamMemberRow with role="listitem"); inline ghost Deactivate button triggers AlertDialog confirm.
  • Display name resolution via get_user_display_names(user_ids uuid[]) Postgres function, consumed via lib/users/display-names.ts (replaces N+1 auth.admin.getUserById pattern).
  • GoTrue NULL-token defensive stack. Runtime BEFORE INSERT OR UPDATE trigger on auth.users coerces NULL → '' on 8 GoTrue token columns, preventing upstream GoTrue bug (supabase/auth#1940) from causing listUsers failures. 5-layer defence: corrective migration, runtime filter, migration guard test, runtime trigger, integration witness test. Post-restore detection via verifyPipelineUserShape() in scripts/seed-e2e-users.ts. Full investigation at docs/audits/s156-gotrue-upstream-investigation.md.
  • scripts/seed-e2e-users.ts CLI provisions E2E test users idempotently via auth.admin.createUser(). Database rebuild runbook at docs/operations/database-rebuild-runbook.md.
  • Settings sidebar consolidation. tags-section.tsx is a 2-tab container wiring tags-cleanup.tsx (duplicates, domain-grouped, bulk-actions) + tags-browse.tsx (virtual-scrolled per-tag CRUD); auto-selects Clean-up when duplicates exist, Browse otherwise. “For developers” accordion inside ConnectionsSection carries plugin download / .mcp.json config / Claude Code setup, gated by useUserRole().canAdmin. 9-entry sidebar.
  • Organisation profile — first app-wide organisation-profile surface, promoting SI company_profiles table to platform-wide grounding context. Schema widened with is_primary boolean NOT NULL DEFAULT false + idx_company_profiles_primary_singleton partial unique index enforcing at most one primary active profile. Settings → Organisation section (admin + editor only) with 10 editable fields (name, description, website URL, sectors[], services[], certifications[], geographic_scope[], target_customers, value_proposition, key_topics[]). isComplete = name + ≥1 sector + ≥1 service. Slug auto-generates (kebab-case + numeric suffix on UNIQUE collision). GET/PUT /api/organisation/profile via getAuthorisedClient(['admin','editor']) and sb(). hooks/use-organisation-profile.ts returns OrganisationProfileStatus. Dashboard nudge renders when !isComplete. Phase 2 (grounding wiring) + Phase 3 (SI deprecation) deferred.
  • Notification preferences — first first-class settings surface. NotificationPreferences renders as Card sub-section inside profile-section.tsx (Personal group, all roles); no new sidebar entry. Three email toggles (Weekly Change Report, Review assignments, Owned content flags) all default ON (silence-first with release valve). Backed by user_notification_prefs table (user_id PK → auth.users cascade, 3 booleans + auto_generate_change_reports, RLS users-own-own-row, update_user_notification_prefs_updated_at trigger, SET search_path = public, extensions). API: GET /api/notifications/preferences returns stored prefs or defaults; PUT validates with NotificationPreferencesPutBodySchema (strict, rejects empty / unknown / all-undefined) and upserts via sb(). TanStack Query via queryKeys.notifications.preferences. Email delivery itself out of scope — this surface ships the settings only; downstream cron jobs query the table directly.

Capability summary only. Session-by-session narrative is tracked in the session ledgers.

A complete domain workspace for monitoring and curating sector news. The intelligence pipeline polls RSS/Atom/Web feeds on a 15-minute cron, filters articles for relevance against per-workspace company profiles using a two-stage scoring pipeline (embedding pre-filter + Claude Haiku LLM scoring with HIGH/MEDIUM/LOW/IRRELEVANT criteria), classifies passed articles, and exposes curated content via the KB, MCP tools, and public RSS feeds.

  • Schema: 6 tables (company_profiles, feed_sources, feed_prompts, feed_articles, feed_flags, si_processing_queue) + 3 SI taxonomy domains (legislation-policy, market-intelligence, sector-news) with 19 subtopics. get_due_feed_sources and get_filter_ratio_trend RPCs.
  • Pipeline: Feed poller (lib/intelligence/feed-poller.ts) with ETag/If-Modified-Since handling on both RSS and web source types (web parity via HEAD pre-flight per RFC 7232 — 304 short-circuit skips Firecrawl credit), 4-tier content extraction (RSS → fetch → Firecrawl → summary fallback), URL normalisation, Google News URL resolution before dedup, feed title whitespace normalisation, concurrency guard via si_processing_queue, per-domain rate limiting with exponential backoff (getGlobalRateLimiter().waitForDomain() applies to RSS and web equally). validateWebUrl() lives in leaf module lib/intelligence/url-validation.ts and runs at source-creation time via async superRefine on FeedSourceCreateSchema so admins get an immediate 400 for non-HTML URLs. Firecrawl-credit telemetry surfaces via intelligence.web-source.firecrawl-call Sentry breadcrumbs + WebPollResult.firecrawlCalled flag (per-pipeline_runs.result aggregation deferred to roadmap §3.7.3). Company embedding cache avoids regeneration per run. Per-workspace pipeline-load hoisting. source_url resolution prefers Firecrawl-resolved URLs (metadata.sourceURL) over raw feed URLs, preventing Google News redirect URLs from persisting on content_items.
  • Management UI: Intelligence hub (/intelligence), company profile management (/intelligence/profiles), workspace detail with sub-nav (Overview / Sources / Articles / Metrics / Prompts), feed source CRUD with test polling, article review with passed/filtered tabs and flagging, prompt editor with version history and rollback. 11 API routes, 8 TanStack Query hooks. Quiet-week auto-collapse: when the current period has zero new passed articles, zero unresolved flags, and zero sources with errors, the overview’s low-signal sections wrap in a native <details>/<summary> disclosure (HealthPanel + Quick Actions remain always visible).
  • Output: Public RSS 2.0 feeds (/api/feeds/[workspaceId]/rss and .../rss/filtered), metrics dashboard with filter ratio trend chart (SVG) and prompt performance table, guide auto-creation (lib/intelligence/guide-generator.ts) with hierarchical structure via parent_section_id and topic-to-section mappings in lib/intelligence/topic-mappings.ts. UI rendering of hierarchical guides deferred.
  • MCP integration: workspace_id filter on search_knowledge_base, get_workspace_items and get_intelligence_summary tools, Intelligence Feed MCP App, intelligence formatters.
  • Prompt Refinement Skill (“Review and Refine”): end-to-end workflow for improving scoring prompts based on user flag patterns. lib/intelligence/flag-analyser.ts uses Claude to cluster flag patterns and generate prompt recommendations. Three backend routes (analyse flags, preview re-scoring, resolve flags) with admin+editor auth, workspace access checks, and cross-workspace privilege escalation prevention. Rescoring preview uses cursor-based worker pool (concurrency cap 3) with partial-failure semantics via warningsEnvelope(). UI: refinement panel as primary prompts page interface (6 reachable states), flag analysis view with collapsible pattern clusters, LCS-based prompt diff view, rescoring preview with direction icons (colour never the sole signal). Safety guards: minimum flag threshold warning + catastrophic prompt change warning (role="status"). PromptEditor retained as “Advanced: edit prompt directly” disclosure.
  • Production hardening: Article cleanup cron (90-day retention), AI summariser for passed articles, pipeline health monitoring endpoint, workspace-level relevance threshold, SOURCES_PER_INVOCATION = 10, coerceSubtopic() for empty-subtopic prevention. Backfill script (scripts/backfill-classify-content-items.ts) for unclassified workspace content.
  • Starter pack feed seeding: 4 sector-specific starter packs (Education, Safeguarding, Health & Social Care, Procurement) as importable feed-source configurations. Admin-gated seed endpoint (POST /api/intelligence/workspaces/[id]/seed-starter-pack) with SELECT-before-INSERT idempotency, warningsEnvelope() response, TanStack Query mutation hook. 18 feeds across 4 packs.
  • Outstanding: Email change-report scheduling, multi-workspace aggregation, and database-level aggregation are SI Phase 2 (deferred). Hierarchical guide UI rendering deferred. See docs/specs/si-hardening-implementation-plan-s154.md and docs/reference/product-roadmap.md §2 for remaining items.

Capability summary only. Session-by-session narrative is tracked in the session ledgers.

A measurement-driven evaluation framework for the AI touchpoints. Built out of the recognition that the entity classification false-positive rate could only be improved with reliable measurement.

  • Phase 1 — Evaluation Foundation: Shared eval infrastructure (lib/eval/types.ts, lib/eval/metrics.ts with precision/recall/F1/ROUGE-L/MRR/NDCG/P@K, lib/eval/baseline.ts for save/load/regression, lib/eval/reporter.ts for console + JSON output). 4 gold-standard fixtures: classification (91 items, 11 domains), summarisation (36 items), bid drafting (24 synthetic items), entity classification (95 items, 12 entity types). 4 eval runners (scripts/eval-classification.ts, scripts/eval-summarisation.ts, scripts/eval-search.ts, scripts/eval-bid-drafting.ts). 5 gated Vitest eval test wrappers (EVAL=1). Fixture count guard test runs in the normal suite. Two-tier summarisation refactor (Tier 1 long-form evaluates summary_data, Tier 2 short-form evaluates ai_summary only). Baselines: classification (domain 97.5%, subtopic 97.5%, secondary 84%, keywords 96.5%), summarisation (T1 ROUGE-L 36.1%, T2 47.1%, structural 100%), search (MRR 84.6%, P@5 56.8%, P@10 49.3%). Bid drafting baseline deferred (no real bid data yet). eval:all and eval:entity package.json scripts.
  • Phase 2a — Classification skill conversion: Classification prompt restructured into lib/ai/skills/classification.md (795 lines) as single source of truth, loaded at runtime with 6 placeholders.
  • Phase 2b-2c — Filters + diagnostic questions: 30+ post-extraction entity filters across 9 categories (TS+Python parity). 12 entity types with diagnostic questions, disambiguation matrix, 8 few-shot examples. Entity types reference loaded at runtime.
  • Phase 2d — Two-pass entity validation: Pass 1 (Sonnet) + Pass 2 (Haiku) validation. Controlled by validate parameter. Architecture: docs/reference/two-pass-validation-architecture.md. Pass 2 trades -6.6 pp recall for +6.8 pp precision (net F1 -2.6 pp); recommendation batch-only. Pass 2 temperature: 0 (+2.5 pp cross-item type consistency). Surgical bulk-cert rule (3 enumerated co-occurrence sets) restored two-pass precision to 91.0%. Full audit: docs/audits/two-pass-cost-quality-measurement.md.
  • Wave 3 quick fixes: Entity eval migrated to shared infra with --live mode. BERTScore integration. Gold-standard fixtures expanded (classification 79→91, entity 85→95 items).

Outstanding: AI Eval Phases 3-5 implementation not started (regression CI infrastructure, human-in-the-loop refinement, full coverage). Phase 3/4/5 specs implementation-ready. Bid drafting eval baseline blocked on real bid data. Pass 2 prompt hardening to close the recall regression remains scoped via roadmap §3.1. See docs/reference/product-roadmap.md §3 for remaining items.

6. Progressive Depth vs Content Layers — Current State

Section titled “6. Progressive Depth vs Content Layers — Current State”

Progressive depth is built and working. Three columns on content_items:

  • brief — executive/sales-level summary (human-authored)
  • detail — detailed explanation (human-authored)
  • reference — technical/reference material (human-authored)
  • content — canonical full text

Plus AI-generated equivalents in summary_data JSONB (executive, detailed, takeaways). ContentTabs shows these as tabs with a human/AI toggle when both exist.

Progressive-depth generation (S189). Automated progressive-depth generation via scripts/kb_pipeline/progressive_depth.py, wired into post_insert.py for all Python ingest paths. Generates brief and detail columns from content text. 380-row backfill applied to production corpus. 44 Python tests.

Content layers are fully implemented (S100-S107). Items have a metadata.layer value (sales_brief, bid_detail, company_reference, research) managed via the layer_vocabulary table with admin UI in Settings. The LayerSwitcherNav shows linked items sharing a topic_id, TopicLayerComparison enables cross-layer comparison, and LayerSuggestionBanner suggests layers post-creation. Feature-gated via content_layers flag (enabled by default). 251 items have layer assignments. This is a SEPARATE concept from progressive depth, though there is conceptual overlap:

  • Progressive depth = different levels of detail for the SAME content item
  • Layers = different PURPOSE/AUDIENCE views of the same TOPIC across multiple items

These are orthogonal. A Sales Brief item about “SCP Market Overview” could have its own brief/detail/reference depth. A Bid Detail item about the same topic could have different brief/detail/reference content. The topic_id in metadata links them as the same topic at different audience levels.

7. Governance vs Document Control — Current State

Section titled “7. Governance vs Document Control — Current State”

Governance review (built): Tracks whether CHANGES to existing content have been reviewed. Trigger: content is edited → status becomes pending → reviewer approves/requests changes/reverts. This is change management.

Document control (not built): Would track whether content is READY FOR USE. Lifecycle: draft → in_review → published → archived. This is publication management.

The governance system does NOT gate visibility. All content is visible to all authenticated users regardless of governance_review_status. There is no concept of “draft content that only the creator can see.”

All AI operations are centralised in lib/ai/ with 12 modules. API routes import from @/lib/ai rather than calling Anthropic/OpenAI SDKs directly. Key modules:

ModulePurposeSDK
classifyDomain, subtopic, keywords, summary, confidenceAnthropic (tool use)
summariseExecutive/detailed/takeaways summariesAnthropic
change-reportsChange report generationAnthropic
embedEmbedding generation (1024-dim), env-var-driven model/dimsOpenAI
matchKB matching with confidence assessmentOpenAI + Anthropic
draft3-pass bid response drafting pipelineAnthropic
extract-contentStructured content extractionAnthropic
extract-questionsTender question + metadata extractionAnthropic (tool use)
visionPDF/image analysisAnthropic (vision)
quality-checkDeterministic + AI response quality checksAnthropic
errorsAIServiceError with HTTP status codes
skills/5 Markdown skill files + loader
IntegrationRouteAI Service Module
Classification/api/items/[id]/classifyclassifyContent()
Summary generation/api/summaries/generategenerateSummary()
Bid question extraction/api/bids/[id]/questions/extractextractPDFQuestions() / extractDOCXQuestions()
Bid KB matching/api/bids/[id]/matchgenerateQueryEmbedding() + assessConfidence()
Bid response drafting/api/bids/[id]/responses/draftrunDraftingPipeline()
CopilotKit chat/api/copilotkitRemoved — replaced by ClaudePromptButton bridge
Content extraction/api/extractextractStructuredContent()
Tender metadata/api/bids/[id]/extract-metadataextractTenderMetadata()
Digest generation/api/change-reports/generategenerateChangeReport()
PDF vision/api/visionanalyseVision()

Capability summary only. Session-by-session narrative is tracked in the session ledgers.

The MCP server (lib/mcp/ — 30+ .ts files across tools/, formatters/, and top-level modules; 58 tools, 12 resources, 7 prompts, 4 MCP Apps) provides external AI client access to the same AI service layer:

MCP ToolAI Module UsedRole Required
search_knowledge_basegenerateEmbedding()Any authenticated
classify_contentclassifyContent()Editor+
generate_summarygenerateSummary()Editor+
create_content_itemgenerateEmbedding()Editor+
search_qa_librarygenerateEmbedding()Any authenticated
find_similar_itemsgenerateEmbedding()Any authenticated
find_duplicate_candidatesgenerateEmbedding()Any authenticated
search_content_chunksgenerateEmbedding()Any authenticated

search_content_chunks returns heading-bounded chunks rather than whole documents, with breadcrumb heading_path and similarity score. get_content_item appends a Document Sections list summarising the item’s chunks. create_content_item regenerates chunks for non-draft items on insert.

Other MCP tools query Supabase directly without AI module calls. Three tools (#22-24) are app triggers that serve MCP App UIs (Coverage Matrix, Bid Dashboard, Reorient Me). Tools #25-26 provide archival and duplicate management. Tools #27-29 provide template coverage analysis. Tool #30 provides batch governance status updates. Tool #33 provides source document version lineage. Tool #34 (get_certification_status) provides certification/framework/registration status and expiry data. Tool #35 (get_document_diff) provides Q&A pair-level diffs between source document versions.

Capabilities live across the layer:

  • HTML→markdown bridge in lib/content/html-to-markdown.ts (GFM-aware, idempotent) — fixes the raw-HTML-in-markdown-payload class.
  • defineTool wrapper + annotation invariant at lib/mcp/tools/shared.ts with five named annotation constants (READ_ONLY_ANNOTATIONS / SAFE_WRITE_ANNOTATIONS / DESTRUCTIVE_WRITE_ANNOTATIONS / NON_IDEMPOTENT_WRITE_ANNOTATIONS / NON_IDEMPOTENT_OPEN_WORLD_WRITE_ANNOTATIONS); all tools register through the wrappers with Required<ToolAnnotations> enforced; regression test at __tests__/mcp/tool-annotations-coverage.test.ts; scripts/lib/mcp-parser.ts and the codebase-stats generator recognise both wrapper and legacy registration patterns.
  • Guide tools + builder skill — four tools (create_guide, update_guide, get_guide, list_guides) at lib/mcp/tools/guides.ts, formatters at lib/mcp/formatters/guides.ts, schemas reused from lib/validation/guide-schemas.ts; plugin skill knowledge-hub:guide-builder ships an 8-step conversational workflow.
  • create_content_item typed provenance + pipeline_runs audit. Three typed optional fields (source_url, source_file, source_document_id) persisted to typed columns; legacy metadata.source_document writes return Zod errors. Every invocation emits exactly one pipeline_runs row via recordPipelineRun({ pipelineName: 'mcp_create_content_item', … }); audit coverage spans success / partial / draft / auth-fail / catch-all paths (lazy-imported service-role client where editor RLS would otherwise drop the row).
  • content_owner_id auto-assign at all 6 ingest entry points via lib/auth/owner-default.ts::resolveContentOwnerId({ explicit, role, userId }). 5-of-6 ingest payload Zod schemas widened with optional content_owner_id field; KBIntegrationBodySchema (EP10) is hard-coded route-side. Service-account UUID list canonical: ['a0000000-0000-4000-8000-000000000001'].
  • Ingest path consistency end-to-end. content_items.ingest_source typed column populated with one of 11 canonical values across all 8 INSERT-time entry points (4 TS + 4 Python). ensure_v1_history_at_commit() is the single authority for v1 content_history rows; app-level v1 inserts have been removed. Inverted guard at __tests__/validation/content-items-v1-history-guard.test.ts enforces the trigger-sole-authority contract. Canonical pipeline_runs.pipeline_name list lives in data-entry-points.md Appendix G.
  • outputSchema runtime-validation scaffold on 5 highest-usage tools. Zod outputSchema registered via defineTool(...) config on search_knowledge_base + search_content_chunks (lib/mcp/tools/search.ts), get_governance_queue + review_governance_item (lib/mcp/tools/governance.ts), and get_change_report (lib/mcp/tools/change-report.ts). Ten Zod schemas exported from lib/mcp/formatters/{search,governance,change-report}.ts mirroring each response interface field-for-field (nullability preserved). Smoke test __tests__/mcp/output-schema-smoke.test.ts exercises safeParse() for known-good + known-bad payloads across the 5 tools. Full ~58-tool rollout deferred (R-WP22, Wave 7) pending main-track MCP cleanup. Source: docs/specs/id-16-ast-dataflow-tool/type-safety-pipeline/TECH.md §WP-E.

Capability summary only. Session-by-session narrative is tracked in the session ledgers.

Admin-only route /provenance with 5 tabs, gated by useUserRole() on the client and getAuthorisedClient(['admin']) on all API routes.

  • Per-item tab — UUID lookup returning classification (confidence, domain/subtopic, full reasoning), processing (model, tokens, cost via lib/provenance/pricing.ts), and drafting (bid response attribution with PIPELINE_SYSTEM_USER_ID promotion). API: GET /api/provenance/item/[id].
  • Pipeline Health tab — keyset-paginated admin API at GET /api/admin/provenance/pipeline-runs with server-side rollup, 20k truncation guard, time-range + kind filters, failure drawer.
  • Audit tab — 1:1 lift of ActivitySection from /settings; heading renamed to “Audit”; same filters and ActivityFeed component. Hosts the PDF export button.
  • Cost tab (stub) — queries pipeline_runs.cost aggregate; labelled “Interim — Wave B”.
  • Disputes tab (stub) — queries classification_disputes; labelled “Interim — Wave C”.
  • PDF exportGET /api/admin/provenance/export/verification-history generates a downloadable A4 PDF (via @react-pdf/renderer) with day-grouped verification events, UK date formatting, and access logging via recordPipelineRun.
  • Schema: classification_disputes table (RLS, 5 policies, 4 indexes, resolution-completeness CHECK) + 7 nullable cost/token columns on content_items. Two pipeline_runs indexes for Pipeline Health queries.
  • drafted_by promotion: three AI-drafting routes changed from null to PIPELINE_SYSTEM_USER_ID. Per-item tab remaps to “Knowledge Hub”.
  • Policy: AI-visibility policy §2 amended with admin Provenance carve-out.
  • Redirects: /activity/provenance?tab=audit; /settings?section=activity/provenance?tab=audit; command palette entry relabelled “Provenance › Audit”.

Capability summary only. Session-by-session narrative is tracked in the session ledgers.

The AI-visibility policy is fully enforced across all user-facing surfaces. No model names, cost pills, token counts, classification reasoning, or raw confidence percentages are visible to viewer roles. Admin Provenance carve-out at /provenance?tab=per-item exposes all AI-mechanism data. Editors and admins see classification_confidence as a plain-text percentage inside the /item/[id] Source Information accordion only — accordion is collapsed by default, has no AI branding or colour coding, and no accompanying mechanism fields. Full policy: docs/reference/ai-visibility-policy.md.

  • 4 Provenance E2E specs (15 tests): admin access + role blocking, tab navigation + deep-links, redirects, Per-item UUID lookup, Pipeline Health filters, Audit tab, PDF export download.
  • MCP guide tool eval L3: 4 checks (token efficiency + structural quality).
  • MCP guide tool eval L4: 6 checks — full CRUD cycle + error paths with cleanup.

Four MCP Apps built (Vite + vanilla TS single-file builds, inlined in lib/mcp/app-bundles.ts):

AppTrigger ToolResourcePurpose
Coverage Matrixshow_coverage_matrixui://coverage-matrixDomains x freshness grid, drill-down, heat map, quality indicators
Bid Dashboardshow_bid_dashboardui://bid-dashboardBid cards, urgency sorting, progress bars, drill-down to detail
Reorient Meshow_reorient_meui://reorient-mePersonal briefing with urgent items, active bids, recent work, team changes
Intelligence Feedshow_intelligence_feedui://intelligence-feedSector intelligence dashboard with passed articles, filter ratios, prompts

Knowledge Hub plugin at .claude/plugins/knowledge-hub/1.0.0/ — 6 commands, 9 skills. Published to local marketplace. lib/mcp/plugin-bundle.ts is a committed artefact (generated by bun run build:plugin).

Plugin skills:

  • procurement-writing, classification, content-creation, content-governance, guide-builder, knowledge-synthesis, search-strategy.
  • governance-review — 9-step triage workflow for the governance queue, coexists with content-governance (framework) as the active-workflow companion. (Renamed from change-management to avoid collision with the Anthropic operations:change-management skill.)
  • daily-briefing — 5-persona daily stand-up skill that composes with the Anthropic sales:daily-briefing via explicit skill-name delegation (not trigger-phrase routing), with a KB-only fallback when the Anthropic plugin is absent on the host. Uses list_user_workspaces MCP tool to resolve the intelligence workspace before invoking get_intelligence_summary.

Maintenance Layer:

  • Taxonomy Sync: scripts/sync-plugin-taxonomy.ts synchronises skill files with canonical docs/schemas via <!-- TAXONOMY_INJECT --> markers.
  • Consistency Tests: __tests__/plugin-taxonomy-consistency.test.ts validates plugin-codebase alignment.
  • Pre-bundle Validation: scripts/bundle-plugin.ts enforces taxonomy validity before generating the Base64 ZIP bundle.

Five Vercel cron jobs implemented:

  • Freshness transitions — daily scan for content state changes (fresh to ageing to stale to expired)
  • Content gap alerts — weekly scan for template requirement gaps, creates notifications
  • Classification quality — weekly audit of low-confidence classifications
  • Coverage alerts — weekly domain coverage threshold checks
  • Quality score — periodic quality score recalculation, triggers governance review when scores drop below threshold

Pipeline run tracking: recordPipelineRun() records every cron and ingest outcome with sb() fail-fast + Sentry alerting. All 5 cron handlers migrated. Admin dashboard tile shows last-24h pipeline status.

Background queue infrastructure (lib/queue/* + app/api/cron/process-queue + app/api/jobs/[id]/cancel): End-to-end queue contract — chokepoint enqueue API, cron worker, backoff window, visibility-timeout RPC, FIRST migration candidate bid_draft_all shipped end-to-end, 14 W3 integration tests + 11 W4 unit tests + 12 W4 integration tests. Producer side: QueueJobPayload<TBody> envelope type (envelope.ts) carries auth_context (user_id + role + optional workspace_id) so workers can reconstruct the caller’s authorisation state via createServiceClient() + user_roles re-validation; buildIdempotencyKey({ jobType, scopedId, requestHash, dateUtc? }) enforces the spec §5.5 formula <job_type>:<scoped_id>:<YYYY-MM-DD>:<requestHash> (date bucket mandatory to prevent cross-day dedup-forever). enqueueQueueJob wraps the dedup SELECT + INSERT in tryQuery / sb and returns { jobId, deduplicated }. emitQueueSentry + emitQueueAnalytics publish typed lifecycle events with PostHog-stub fallback via Sentry.addBreadcrumb({ category: 'queue.analytics' }). Worker side: app/api/cron/process-queue/route.ts (GET, maxDuration=60 post-S224 W4 §5.4.1 D-3 ratification — Vercel Pro plan; TIMEOUT_BUFFER_MS=50_000 keeping 10s headroom from the 60s function cap; * * * * * registered in vercel.json post-S224 W5 operational tweak — cron cadence flipped from */5 * * * * to 1-minute cadence per Vercel Pro to cut worst-case queue-poll latency 5×) verifies cron auth, calls reap_stuck_jobs(p_timeout_seconds) RPC (5-min default, flips orphaned processing rows back to pending with atomic attempts = attempts + 1), then loops calling claim_next_job() RPC (WHERE status = 'pending' AND updated_at <= NOW() — backoff-window gate so requeued rows are invisible until their backoff expires; FOR UPDATE SKIP LOCKED concurrency-safe) within a 50s budget — for each row, dispatches via runJobByType(job, supabase) switch — case 'bid_draft_all' registered post-S224 W4, case 'batch_reclassify' registered post-S225 W1 (7 remaining historic job_types fall through to permanent-failure default PermanentJobError('no_handler_registered: …') until each §5.4.x candidate registers its own case). Failure classifier handleJobFailure distinguishes transient (Anthropic 429/503, supabase 503, embedding timeout, Firecrawl 5xx — retry with linear-with-jitter backoff (attempts × 30s) + random(0..5000ms) written as future-dated updated_at) from permanent ({permanent:true} duck-type on PermanentJobErrorstatus='failed', no retry; attempts still increments to record the attempt that was made); transient at attempts ≥ max_attempts writes status='dead_lettered'. Auth re-validation via reValidateAuthContext(serviceClient, userId, enqueuedRole, requiredRole) returns {ok:true} | {ok:false, reason} with verbatim spec §4.2 error messages. User-initiated cancellation via PATCH /api/jobs/[id]/cancel (admin/editor only, race-safe .in('status', ...) filter widened post-S225 W1 to include 'processing' for opt-in job_types in COOPERATIVELY_CANCELLABLE_JOB_TYPES allow-list at lib/queue/cooperative-cancel.ts — currently ['batch_reclassify']; non-opt-in types still return 409 on 'processing' or terminal, preserving §5.4.1 hard-409 behaviour for bid_draft_all). processing_queue table extended with idempotency_key text + partial UNIQUE index WHERE status IN ('pending','processing','completed') + status enum widened to include 'dead_lettered' + job_type CHECK enum widened to 11 values (8 historic + 'bid_draft_all' per S224 W4 §5.4.1 IMPL + 'batch_reclassify' per S225 W1 §5.4.2 IMPL + 'markdown_batch' per S226 §5.4.4 IMPL) + RLS tightened to admin-only SELECT/UPDATE/DELETE + editor+admin INSERT. Archive pg_cron job archive-processing-queue runs weekly (Sunday 03:00 UTC) deleting completed/failed/cancelled/dead-lettered rows older than 30 days (supabase/migrations/20260506131535_s226_archive_processing_queue_pg_cron.sql — closes infra spec §9 R4 archive-strategy gap that §5.4.1 + §5.4.2 each deferred). Lifecycle + concurrency integration tests at __tests__/integration/queue/{lifecycle,concurrency}.integration.test.ts exercise all 12 ACs from W3 spec §8 through real Supabase staging (S223 W3); §5.4.1 unit + integration + E2E tests at __tests__/lib/queue/handlers/bid-draft-all.test.ts (11 cases) + __tests__/integration/queue/bid-draft-all.integration.test.ts (12 cases covering AC-1..AC-9 — env-gated, run via bun run test:integration) + e2e/tests/bid-draft-all.spec.ts (Playwright click→queued→polling → button-disable, S224 W4-C). All tests follow real-behaviour discipline: no mocked supabase, no mocked queue lib; assertions only on observable DB state transitions; mock external API boundaries only.

§5.4.1 batch-draft-all (FIRST migration candidate, SHIPPED S224 W4). lib/queue/handlers/bid-draft-all.ts (404L) iterates bid_questions ordered by (section_sequence, question_sequence), calls runDraftingPipeline per question (continue-with-partial — D-2 ratified), upserts bid_responses with drafted_response_ids: string[] tracked for pipeline_runs.items_created, throws PermanentJobError on bid-not-found / bid-not-draftable / zero-questions; case 'bid_draft_all' in lib/queue/dispatch.ts validates envelope, calls reValidateAuthContext(... 'editor') per D-1 ratification, dispatches handler, then finalises caller-allocated pipeline_runs row via DIRECT UPDATE (NOT recordPipelineRun() — INSERT-only helper would create a 2nd row; drift documented inline; verifier confirmed SELECT count(*) WHERE pipeline_run_id = … = 1). Producer app/api/bids/[id]/responses/draft-all/route.ts refactored from sync 326-line loop to 175-line 202+enqueue (SHA-256 truncated requestHash, crypto.randomUUID() pipelineRunId, pipeline_runs INSERT with status='running', enqueueQueueJob chokepoint, returns {job_id, pipeline_run_id, status: 'queued', deduplicated}, maxDuration=30 per D-6). UI hook hooks/bid/use-bid-actions.ts mutation refactored from sync {drafted, skipped, failed} to async polling via useQuery({refetchInterval: 3000}) against /api/jobs/${jobId}/status, toast on click + dedup-aware copy + terminal status branching (completed/completed_with_errors/failed/dead_lettered/ cancelled). Migration 20260505164817_s224_widen_job_type_check_bid_draft_all.sql applied to staging + prod; idempotent re-affirm follow-up 20260505173311_s224_post_impl_schema_doc_sync_reaffirm.sql syncs SCHEMA-QUICK-REFERENCE.md §8 row.

§5.4.2 batch-reclassify (SECOND migration candidate, SHIPPED S225 W1). lib/queue/handlers/batch-reclassify.ts is the literal extraction of the CLI’s L943-1242 main loop — taxonomy load, candidate filter, per-item Anthropic classify + entity-extract + write logic — with the dry-run branch removed (queued = always execute), stdout logging swapped for result.results[] aggregation, and Anthropic SDK instantiated at handler call-time (not module-load). Continue-with- partial per item per D-2 ratified default; per-item Anthropic permanent errors (content-policy refusal post-prompt-tightening eval) record status: 'failed' and continue, escalating to a PermanentJobError('eval_rule_regression: …') if the per-item failure rate exceeds 80% of items processed (operator notice path per spec §5.2). Cooperative cancellation via processing_queue.status poll every 10 items: handler reads via createServiceClient() from @/lib/supabase/server, breaks the loop on 'cancelled', returns partial result with cancelled: true marker. case 'batch_reclassify' in lib/queue/dispatch.ts validates envelope, calls reValidateAuthContext(... 'editor') per D-1 ratified flip (matches §5.4.1 D-1 ratified default for §5.4.x family symmetry), dispatches handler, then finalises caller-allocated pipeline_runs row via DIRECT UPDATE (NOT recordPipelineRun() — same drift as §5.4.1; verifier confirmed count(*) = 1 end-to-end). Cancel-detected finalisation writes status='completed_with_errors' with error_message='cancelled mid-run after N/M items' per D-9.1 (no new enum value). Producer route app/api/admin/batch-reclassify/route.ts POST returns HTTP 202 with {job_id, pipeline_run_id, status: 'queued', deduplicated} — auth via getAuthorisedClient(['admin', 'editor']) per D-1 flip, body parse via parseBody(BatchReclassifyBodyZodSchema, request), optionsHash = SHA-256 hex truncated 16 char of canonical-JSON body (alphabetical keys), idempotencyKey via buildIdempotencyKey({ jobType: 'batch_reclassify', scopedId, requestHash: optionsHash }) (date bucket per §5.5), pre-allocated pipelineRunId, pipeline_runs INSERT with status='running', enqueueQueueJob chokepoint, maxDuration=30. UI page deferred per D-4 ratified flip (CLI-only with future UI — producer endpoint exists for future UI to call but no /admin/reclassify page in this candidate). CLI deprecation banner per D-7 ratified default printed on every --execute invocation routing operators to future UI; full deletion deferred to follow-up §5.4.x. body.workspace_id body-only encoded per D-8 (client_id is non-UUID 'default'; envelope auth_context.workspace_id omitted, handler reads body.workspace_id). Migration 20260505211806_s225_widen_job_type_check_batch_reclassify.sql applied to staging + prod — paired with lib/queue/envelope.ts JobType union widening + SCHEMA-QUICK-REFERENCE.md §8 row + Last verified bump in same commit per feedback_doc_freshness_guard_per_commit and feedback_db_check_ts_union_paired_widening. AC-11 (UI flow E2E) deferred per D-4 flip — 10 of 11 spec §8 ACs covered by 17 unit + 13 integration env-gated + 14 producer-route + 2 cancel-route cooperative cases.

§5.4.4 EP2 markdown-batch (THIRD migration candidate, SHIPPED S226). lib/queue/handlers/markdown-batch.ts is a thin wrapper around the existing orchestrateMarkdownBatch({phase:'import', ...}) from lib/ingest/markdown-orchestrator.ts — the orchestrator already implements the full Pattern E lifecycle (at-start INSERT via startPipelineRun, mid-flight updatePipelineProgress writes, terminal UPDATE via finaliseRun) so the handler does NOT touch pipeline_runs directly. KEY DIFFERENCE from §5.4.1 + §5.4.2: the dispatch case for markdown_batch does NOT do an inline pipeline_runs.update — orchestrator’s finaliseRun already wrote terminal status (avoiding double-write). Cooperative cancellation cadence=1 per spec §10 D-8 ratified flip (poll BEFORE every file, NOT every-N-items as in §5.4.2 — typical markdown_batch is 1-3 files, max ~10, so every-N would defeat the purpose); allow-list extended to ['batch_reclassify', 'markdown_batch']. body.caller_user_id mismatch defence per spec §4.3:783-784 throws PermanentJobError('caller_user_id_mismatch'). Producer route app/api/ingest/markdown/route.ts phase=import branch refactored from sync await orchestrate(...) to 202+enqueue: pre-allocate pipelineRunId, INSERT pipeline_runs with status='running', pipeline_name='upload_markdown_batch', compute fileSetHash = SHA-256-hex truncated 16 char of sorted-by-filename [{filename, contentSha256: sha256(content)}] JSON (per-file content hash nested per D-9 ratified flip — NOT filenames+sizes, NOT concatenated buffer — collision-resistant), call enqueueQueueJob, return {job_id, pipeline_run_id, status: 'queued', deduplicated}. maxDuration=60 per D-7 ratified flip (was 300 sync). phase=analyse branch UNCHANGED — still returns sync 200 + {analysis}. Path B UPSERT per D-11: lib/pipeline/start-run.ts switches .insert(...) to .upsert(..., {onConflict:'id', ignoreDuplicates:true}) to absorb the producer-pre-INSERT vs orchestrator-at-start-INSERT PK collision. new Date(Date.now()).toISOString() symmetry pattern enables deterministic vi.spyOn(Date, 'now') testing. UI surface: components/create-content/upload-tab-content.tsx ships Cancel batch button (mandatory per D-8 ratification — business users won’t use API/CLI) visible whenever polled pipeline_runs.status === 'running'; deduplicated: true toast renders “Already importing — joining the existing batch…”. Migrations: 20260506125704_s226_widen_job_type_check_markdown_batch.sql (JobType CHECK 10→11) + 20260506131535_s226_archive_processing_queue_pg_cron.sql (D-5 scope-in: weekly archive job — closes infra R4 gap). 12 ACs ship (AC-12 added for archive cron); V_W1 PASS WITH NOTES, 0 HIGH/MEDIUM, 2 LOW (caller_user_id mismatch + auth-context rename — both fixed fix-V_W1).

§5.4.5 queue-operational-dashboard spec v1 DRAFT at docs/specs/queue-operational-dashboard-spec.md (S225 W3-A spec + V_W3 fix — 10 sections / 6 ACs / 6 D-x / 7 risks; 6 D-x awaiting Liam ratification covering surface choice, metric set, data source, alert thresholds, refresh cadence, cost surface visibility — IMPL deferred to a follow-up session post-ratification). Each §5.4.x candidate registers its own case in dispatch + extends the JobType union via paired DB-CHECK + TS-union widening per feedback_db_check_ts_union_paired_widening.

  • Template requirements table with 2 catalogued templates (Standard SQ, Charnwood ITT)
  • Template coverage analysis on Coverage page (Templates tab)
  • Gap summary banner with requirement-type breakdown
  • Auto-mapping of template fields to bid questions
  • Template fill and download workflow
  • Gap-driven content creation — content creation skill, cron-triggered gap alerts, notification types for coverage gaps
  • Product Guide subtopic_filter wiring. Data migration 20260422174420 populates subtopic_filter on 57 guide_sections rows (19 sections x 3 Product Guides), enabling section-to-content resolution.
  • Research Feed sections. Data migration 20260422174117 adds a Research Feed section to each of the 3 Product Guides; guide_sections total is 60.

Capability summary only. Session-by-session narrative is tracked in the session ledgers.

  • E2E critical coverage live. 9 Must-tier gaps deliberate-break verified across OAuth consent, MCP tool invocation, bid create, file/URL ingestion, viewer write enforcement, bid draft-stream, bid regen+restore plus conditional-assertion hardening on existing specs.
  • React act() regression guard. __tests__/setup.ts registers a console.error wrapper that throws on /wrapped into act|not wrapped in act|inside a test was not wrapped in act/ — surfaces the offending test via Vitest’s stack trace at the exact call site rather than letting act warnings drift across CI shards. Composable with the 11 existing per-test vi.spyOn(console, 'error').mockImplementation overlay-and-restore patterns. Backed by S37 W4 verification scan: 0 act-warning regressions across 12,481 tests at activation. Memory feedback_react_act_warning_classes documents the 3-class taxonomy (A bare dispatchEvent, B child useEffect fetch, C waitFor drain).
  • Guard tests: claude-md-consistency.test.ts (107 checks), doc-freshness.test.ts (14 checks), mcp-fixture-sync.test.ts (20 checks), content-history-change-reason.test.ts (3 checks), reference-doc-edit-coupled-freshness.test.ts (12 checks; tracked-doc edits coupled to <!-- Last verified --> header bumps in the same commit, plus migration-coupled SCHEMA-QUICK-REF freshness with auto-skip for pure-DML), roadmap-no-shipped-rows.test.ts (3 checks; fails CI on Done/Shipped/Closed rows in product-roadmap.md), backlog-no-closed-rows.test.ts (7 checks; fails CI on strikethrough/closure-status rows in the active backlog table).
  • Silent-failure prevention. sb() fail-fast wrapper + warningsEnvelope() + logBestEffortWarn() + tryQuery() Result-type helper from @/lib/supabase/safe. ESLint rule local/no-unchecked-supabase-error at error level scoped to app/api/**/*.ts + lib/**/*.ts + app/**/*.tsx (App Router Server Components included; **/*.test.tsx + **/__tests__/**/*.tsx excluded). Client-side extension via lib/client-telemetry.ts::captureClientException() replacing console.error anti-pattern. Spec: docs/specs/silent-failure-prevention-spec.md.
  • Supabase-result Record-cast prevention. ESLint rule local/no-supabase-record-cast at error level scoped to lib/** + app/api/**. Flags as Record<string, unknown> / as Record<string, any> casts on Supabase result rows (direct chain, destructured data, RPC .data). Four escape hatches: 22-entry JSONB column allowlist (mirroring type-safety-pipeline TECH.md §JSONB inventory), third-party fetch() / axios responses, test/spec/e2e file paths, explicit eslint-disable-next-line with justification. Source: docs/specs/id-16-ast-dataflow-tool/type-safety-pipeline/TECH.md §ESLint rule design. Test suite at eslint-rules/tests/no-supabase-record-cast.test.ts (20 RuleTester cases). Pairs with R-WP17 type-drift detector (.type-drift-report.md, gitignored at repo root; regenerated by bun run ast-dataflow type-drift-detect --pretty) — drift report names interfaces with no route annotation; ESLint rule prevents new Record casts on Supabase rows.
  • Knip integrated with deterministic baseline. Unused file/export/type detection via bun run knip + bun run ci:knip-check. Single canonical config at knip.config.ts (knip.json retired); Tailwind v4 @plugin/@import CSS directives detected via syncCompilers.css regex (Jimmy Guzman pattern) so @tailwindcss/typography no longer needs an ignoreDependencies shim. Baseline .knip-baseline.json tracked in CI (“tighten or trace” discipline per docs/runbooks/ci.md §6.3.1) — current {types: 285, exports: 57, dependencies: 0}. Triage doc at docs/audits/kh-production-readiness-phase-1/research/19-knip-baseline-triage.md enumerates the 4 buckets (BACKWARD-COMPAT-REEXPORT, RECENT-IMPL-ORPHAN-NEEDS-WIRING, GENUINELY-DEAD, PUBLIC-API-INTENTIONAL) and the path to OPS-50 ≤170 types target via Wave B (S25+ post-handover prep).
  • Test-DB wiring audited. 11 integration + 34 E2E specs are env-sourced with zero hardcoded project IDs and zero write-without-cleanup patterns. Audit: docs/audits/test-db-wiring-s188.md.
  • Radix pointer shims centralised. __tests__/helpers/radix-pointer-shims.ts::installRadixPointerShims() is the canonical shim set for Radix Select / cmdk in jsdom; adopted across the test base via beforeEach.
  • Canonical MCP server-mock helper. __tests__/helpers/mcp-server.ts::createMockMcpServer(overrides?: Partial<MockMcpServer>) is the canonical mock-server factory for __tests__/mcp/. Consolidated 28 duplicated createMockMcpServer / createMockServer / createTestServer variants spread across MCP test files (kh-prod-readiness-S43 W-RA; -419 LOC net). Helper exports symmetric tool / prompt / resource handler registration plus a dual Record+Array tool exposure (mockServer.tools + mockServer.toolList) so files asserting tool counts (e.g. tool-annotations-coverage’s 58-tool guard) absorb cleanly. MockToolResult.structuredContent typed any with eslint-disable to preserve consumer ergonomics; strict consumers can pass an explicit MockToolResult<MyShape> wrapper.
  • Shared test-factory infrastructure. __tests__/helpers/factories/ is the canonical landing place for cross-file test-fixture builders. Four NEW helpers added kh-prod-readiness-S44 W2-RG: cron-request.ts (createMockCronRequest() — 6 cron-route consumers), file-upload.ts (File + Request mock builders for upload routes — 8 consumers), api-request.ts (NextRequest builder createMockApiRequest() — 3 consumers), components/item.ts (createMockItem + createMockQAItem prop fixtures — 6 component-test consumers). mock-supabase.ts extended with createMockSupabaseTable() per-table dispatch shape (5 lib consumers). Pattern-references W-RA: named exports, JSDoc preambles, overrides?: Partial<T> signatures, no barrel re-exports.
  • Chain-method assertion remediation. ~115 invocation-shape chain asserts (_chain.eq.toHaveBeenCalledWith and siblings) removed from api+lib unit tests (kh-prod-readiness-S44 W2-RD; 24 api files / 6 lib files). Replaced with observable-outcome assertions on response status + body shape (api) or function-return Result envelope (lib). Multi-tenant security-contract carve-outs (user_id-scoped DELETE/UPDATE chain asserts) migrate to integration tier via inline remediation-plan.md §3.5 NOTE breadcrumbs; W-RD’ integration-suite seeding pending. E2E false-pass hardening continues (provenance-pipeline-audit, provenance-per-item, settings-mutations joined the S43 W-RB.1 wave1-spec set; hard expect(X).toBeVisible() replacing if (X.isVisible())).
  • Client branding system. Build-time per-client branding via NEXT_PUBLIC_CLIENT_ID. Each client has a Zod-validated JSON config at lib/branding/clients/{id}.json (colours, logos, metadata). OKLCH contrast validation enforces WCAG 2.1 AA compliance (3:1 non-text warning, 4.5:1 text failure). CSS variables injected via inline <style> in root layout. BrandLogo component supports light/dark variants with SVG+PNG fallback. Phew is onboarded as the first client. Administration docs at docs/product-functionality/administration/technical.md. 61+ unit/loader/component/integration tests.
  • Admin-dedup E2E fixture infrastructure. Deterministic 1024-d vector helpers (e2e/fixtures/admin-dedup-vectors.ts) generate pairs at controlled cosine similarity (perturbation by orthogonal-noise mix); seed/cleanup helpers (e2e/fixtures/admin-dedup-fixture-helpers.ts) produce the §1.7 + §1.9 dataset (26 rows / 12 queue + 14 near-dup pairs) with run-id-tagged metadata->>'e2e_dedup_fixture_run_id' cleanup; a worker-scoped Playwright fixture (e2e/fixtures/admin-dedup-fixture.ts) wraps seed → verifySeededPairs smoke gate → finally-block cleanup; one-shot CLI (scripts/seed-admin-dedup-fixtures.ts) supports --tag / --cleanup / --cleanup-all / --dry-run / --yes / --help with interactive confirmation on destructive actions and exit codes 0/1/2/3. 47 unit tests (vectors 25 + helpers 22). Inline embedding write (single-step JSON.stringify(vec) insert) + find_duplicate_pairs verifier; FK-safe cleanup order (clear superseded_by, then chunks → history → content_items). Decisions contract in docs/audits/s213b-admin-dedup-fixtures-design.md §9 (cleanup gating, run-id format, vector-seed determinism, cross-domain pair, embedding write path, overlap negative case). globalSetup runs a >2h orphan-sweep at session start; globalTeardown runs a tag-based safety-net sweep on metadata->>'e2e_dedup_fixture_run_id' rows beyond the legacy [E2E- title-prefix sweep. Spec coverage: 4 spec files / 17 tests covering §1.7 ACs 1–8 + §1.9 ACs 1–7 + 9–10. Read-only specs consume the worker-scoped fixture; mutating specs seed per-test pairs with FK-safe afterEach teardown so the worker fixture stays read-only canonical. All specs run on chromium-desktop + chromium-mobile against staging.
  • Boot-time env validation + schema-split. lib/env.ts exports Zod-parsed serverEnv and re-exports clientEnv from lib/env-client.ts (split keeps server-schema names out of client bundle — zero leaks across 118 client chunks). Required vars throw with field-level errors naming the offending key. Variable names align with Supabase→Vercel integration defaults (NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY, SUPABASE_SERVICE_ROLE_KEY, bare SENTRY_*). All production paths + Sentry config files + next.config.ts consume the validated env. Vitest pattern documented in lib/env.ts JSDoc (parse-at-load + vi.stubEnv + vi.resetModules + dynamic import). 8 dedicated tests at __tests__/lib/env.test.ts cover happy path, required rejections, optional passthrough.

Capability summary only. Session-by-session narrative is tracked in the session ledgers.

The production-readiness wave (Phase 1 — see docs/audits/kh-production-readiness-phase-1/STATUS.md) hardened the observability + build chain. Current capabilities:

  • Sentry release tagging livewithSentryConfig sets release.name = process.env.VERCEL_GIT_COMMIT_SHA. Production stack traces map to deploy SHA; bare SENTRY_* env vars match the Vercel→Sentry integration default. Sentry Turbopack sourcemap upload de-silenced via silent: !process.env.CI + release.create: false, finalize: false.
  • Error-boundary Sentry forwarding — 22 segment app/**/error.tsx + new app/global-error.tsx (Next.js 16 root convention) all forward to Sentry.captureException. Sentry SDK init reads DSN directly from process.env (decoupled from the clientEnv Zod gate so env-validation failures cannot silence the very tool meant to surface them).
  • build:vercel chain wiredvercel.json buildCommand invokes the bun run generate:skills && bun run build:mcp-apps && next build chain so production builds regenerate skill bundles.
  • Boot-time env validation operationallib/env.ts (serverEnv) + lib/env-client.ts (clientEnv) parse Zod schemas at module load, with literal-by-literal process.env.NEXT_PUBLIC_* accesses to preserve Next.js compile-time substitution. Build-output regression test (__tests__/build/env-substitution.test.ts, gated RUN_BUILD_TESTS=1) scans .next/static/chunks/*.js to guard against substitution-defeat.
  • Persistent staging Supabase branch operationalturayklvaunphgbgscat at migration parity with prod, mapped to Vercel Preview; prod stays on rovrymhhffssilaftdwd mapped to Production. Schema sync via supabase db push --linked (canonical migration mechanism). supabase/seed.sql
    • per-branch [remotes.staging.db.seed] block establish the schema-vs-data boundary; data-bearing migrations gated with parent-row EXISTS checks for clean data-empty branch behaviour. vector extension lives in extensions schema (advisor-clean across both envs); EXT-FIX migration closes the squash divergence. Data-refresh strategy: Path (ii) reference-refresh + content fixtures (WP-CI.RES.7 shipped). Periodic refresh restricted to reference tables (taxonomies, layer_vocabulary, content_types, etc.) via scripts/staging-reference-refresh.sh driven by .github/workflows/staging-reference-refresh.yml (fortnightly cron + workflow_dispatch). Integration test fixtures live at __tests__/integration/fixtures/** (5 modules — staging-fixture-generator, reference-data-validator, content-fixtures, entity-fixtures, publication-fixtures) and scripts/seed-integration-fixtures.ts. The earlier pg_dump-based PII-scrub orchestrator + staging-live-mirror.yml workflow retired — Path A trigger-silencing was structurally exhausted (SUSET-gated session_replication_role + lack of ownership on auth.* tables). Migration parity verified post-FK-corrective: prod + staging both at 20260504225603_align_auth_user_indexes. Schema-parity guard (.github/workflows/schema-parity.yml) validates structural alignment between branches with managed-drift normalisation (auth.users staging-only indexes filtered as Auth-owned).
  • .env.local is the single source of truth for both TS and Python pipelines; .env retired. CLI scripts gain --env={prod,staging,auto} flag with assertion-only enforcement. Canonical guide: docs/runbooks/local-development.md.
  • ci.yml PR-blockingquality split into quality-precheck (lint/format/knip/build/test:build/pytest, NODE_OPTIONS: --max-old-space-size=4096 on Build step to avoid OOM) + 4-shard quality-test matrix (bunx vitest run --shard=N/4, existing forks pool retained, fail-fast: false) → ~4 min CI wall-clock target vs ~8 min pre-split. Four additional CI gates ship in parallel: e2e-smoke (46 @smoke-tagged Playwright tests on Staging env), mcp-eval topology built once via dedicated mcp-build job (Staging-scoped) + mcp-eval-seed job (deterministic Q&A corpus seed, MCP_EVAL_SEED_METADATA_FLAG=mcp_eval_seed, version-aware embedding regeneration) feeding L1/L3/L4 fan-out matrix downloading the shared build artifact (scripts/mcp-eval/seed-fixtures.ts + seed-data.ts; cleanup preserves seed rows via isPersistentMcpEvalSeed() filter), and integration job (Staging env scope, single shard, 25-min timeout, AI keys explicit + CRON_SECRET; continue-on-error: true removed per WP-CI.RES.7 Phase 2 cutover — integration job is hard-required). Branch protection enforced via GitHub rulesets (main-protection 15785019; production-protection 15785711 covering both staging + production-readiness). Schema parity validated nightly + on-push by .github/workflows/schema-parity.yml (managed-drift normalisation for Auth-owned indexes; PG 17 pg_dump pinned). REVOKE-guard CI lint (migration-revoke-guard.yml) + Supabase advisor cron job baselines drift. Runbook: docs/runbooks/ci.md.
  • REVOKE-guard CI lint + cron audit.github/workflows/migration-revoke-guard.yml scans every PR’s supabase/migrations/ diff and asserts that for each CREATE FUNCTION public.<name>(<args>) there is a paired REVOKE EXECUTE ON FUNCTION public.<name>(<args>) FROM PUBLIC, anon in the same migration file. Belt-and-braces backstop for the per-function REVOKE pattern (Supabase pg_default_acl auto-grants anon EXECUTE on every new public PL/pgSQL function — REVOKE FROM PUBLIC alone is a no-op). Plus a periodic cron audit query against prod via scripts/check-revoke-guard.ts flagging any public.* function with has_function_privilege('anon', ...) = TRUE outside the intentional-anon enumerate-list (today: set_config). Documented at spec docs/audits/kh-production-readiness-phase-1/specs/wp-ops43.3-revoke-guard-spec.md.
  • Single-track local development (post-S71 collapse, ID-24) — local work is single-track on main; the former long-lived production-readiness top-level worktree was retired (S261, ID-24.1 — branch ref retained on origin for history). Parallel work now uses transient worktree-isolated agents under .claude/worktrees/ cherry-picked back onto main (see CLAUDE.md § Development Workflow). The origin staging / production-readiness branches and their GitHub rulesets are unchanged by the local collapse.
  • Permanent git staging branch + reference-refresh workflow (Path (ii)) — git branch staging lives on origin as the canonical pre-main integration point per the feature-branch / worktree → stagingmain flow codified in docs/runbooks/ci.md §3.4. Branch protection enforced via GitHub rulesets (production-protection ruleset 15785711 covers both refs/heads/staging + refs/heads/production-readiness; main-protection ruleset 15785019 covers refs/heads/main). .github/workflows/staging-reference-refresh.yml (fortnightly cron + workflow_dispatch, environment: Staging) refreshes reference tables (taxonomies, layer_vocabulary, content_types, etc.) from prod via scripts/staging-reference-refresh.sh (Path (ii) orchestrator — ~50 LoC bash). Schema-level alignment is migration-driven via supabase db push --linked and verified by .github/workflows/schema-parity.yml with managed-drift normalisation. Auth-hook [remotes.staging.auth.hook.before_user_created] block in supabase/config.toml applies automatically via Supabase GitHub integration on PR-merge into mapped staging git branch (Hold (d-cont) closed). The earlier staging-live-mirror.yml workflow + staging-mirror-and-scrub.ts + scrub-staging-pii.sql + verify-scrub.ts orchestration (Path A pg_dump- with-PII-scrub) was retired in favour of Path (ii) reference-only refresh + deterministic content fixtures, eliminating trigger-ordering, PII scrub, and auth.* exclusion failure classes simultaneously. Runbook §4.4 of docs/runbooks/staging-refresh.md.
  • Dependabot ecosystem live + reduced-signal CI for Dependabot PRs.github/dependabot.yml runs the bun + pip + github_actions ecosystems so PRs update lockfiles + workflow action versions. claude-code-action@v1 is excluded (Routines via Claude.ai seats instead). GitHub blocks secrets.* for Dependabot-triggered runs (env-scoped + repo-level both empty); ci.yml carries 9 if: github.actor != 'dependabot[bot]' guards: 3 step-level inside quality-precheck (Build / build-output regression scan / pytest) plus 6 job-level on quality-test, e2e-smoke, mcp-build, mcp-eval-seed, mcp-eval, integration. Dependabot PRs retain lint + format-check + knip baseline guard + REVOKE-guard PR lint signal — runtime-code bumps need manual local verify before merge per docs/runbooks/ci.md §4.4. Branch-protection ruleset 15785019 requires only ci-summary (single aggregator check). ci-summary aggregator IMPL shipped S33 — .github/workflows/ci.yml carries an if: always() + needs: [quality-precheck, quality-test, e2e-smoke, mcp-build, mcp-eval-seed, mcp-eval, integration] job that fails iff any upstream needs.<job>.result is failure (treats skipped + success as pass). All ${{ needs.<job>.result }} routed through env: block per GHA injection-prevention guidance. Single fixed-name ci-summary posts on every PR including Dependabot. Ruleset PUT applied kh-prod-readiness-S36 (replaces prior 2-check requirement Quality pre-check + E2E smoke with single ci-summary); other rules (deletion + non_fast_forward + bypass actors) preserved. OPS-58 closed.
  • GitHub Environments canonical — repo-level secrets removed; canonical Production + Staging env-scoped slots populated. Capital-first convention; per-var type/value rules per env target documented at docs/runbooks/github-environments.md.
  • GitHub repository hosted under ai-solution-hub org — canonical URL https://github.com/ai-solution-hub/knowledge-hub. Migrated from liam-jons/knowledge-hub 29/04/2026 to access GH Team plan branch protection on private repos. Integrations rebound: Vercel (both projects), Supabase GitHub App, Sentry, CodeRabbit; Dependabot + GH Actions secrets/ vars auto-migrated via redirect. Long-lived worktrees (main, KPF, production-readiness) re-pointed via git remote set-url.
  • Vercel × Supabase × env-var canonical mapping at docs/audits/kh-production-readiness-phase-1/research/09-vercel-supabase-env-mapping-spec.md with Liam-action checklist; integration sync model documented in research/11-supabase-vercel-integration-sync-behaviour.md.
  • Structured logging Phase 1+2+3+4-app-api shipped — Pino root logger at lib/logger/index.ts with AsyncLocalStorage request-context mixin, Sentry bridge forwarding warn/error/fatal, PII-redacting serialisers (REDACT_PATHS). Proxy mints crypto.randomUUID() per request and propagates via x-request-id headers. lib/error.ts deliberately stays client-safe (no logger import — pino + node:async_hooks can’t bundle for the browser; routes call logger.error() direct in catch arms). withRequestContext overloaded in lib/logger/request-context.ts to accept either value-form (ctx, fn) or decorator-form (handler) — the 6 highest-volume API routes (items POST, items/[id], search, ingest/url, items/[id]/classify, freshness/{calculate,recalculate-all}) wrap their handlers with the decorator. Phase 3 priority lib/ modules (lib/ai/{classify,change-reports,summarise,extract-questions}.ts, lib/source-documents/document-diff.ts) emit through logger.* with structured op tags. Phase 4 long-tail migrated app/api/**/route.ts console callsites — 262 calls (380→118 baseline drop, of which ~16 are intentional CLI sinks + sanctioned dev-mode debug). Phase 4 closure pending: the remaining 102 calls live in lib/ modules and client components transitively imported by client bundles; closure needs a lib/logger/client entry point (console-backed) so shared chokepoints can import a logger interface without dragging Pino + AsyncLocalStorage into the Turbopack client bundle. Phase 5 (Python correlation, gated on WP-RUN provisioning) + Phase 6 (Axiom destination, D-5) carry forward per docs/specs/structured-logging-spec.md v1.1.
  • public.user_profiles mirror table operationalauth.users mirrored via consolidated trigger; count_auth_users() SECURITY DEFINER RPC for parity probe; get_user_display_names() LEFT JOINs user_profiles; admin users route queries user_profiles + user_roles via PostgREST. OPS-1 closed.
  • GDPR data export runbook at docs/handover/gdpr-data-export.md with scripts/export-user-data.ts enumerating 20 PII tables and producing SHA-256-manifested Article-15 + Article-20 bundles. First of 13 W5 handover artefacts shipped.
  • Cutover row-count diff utilityscripts/db-row-count-diff.ts (CLI: bun run db:row-count-diff) compares per-table row counts source vs target with allowlist for known-empty staging tables; exit codes 0/1/2. Closes OPS-8.
  • Sentry project for handover (G6.2): single project knowledge-hub-phew-design (org tw-group-s3, region DE).
  • Cloud Run pipeline — RETIRED (S298). Cloud Run is fully decommissioned; the cloudrun/ manifests and .github/workflows/cloud-run-deploy.yml are deleted. The ingestion pipeline now deploys on-prem (IONOS VPS + Coolify) via .github/workflows/onprem-deploy.yml — see docs/runbooks/onprem-b1-deploy.md. The detail below is retained as a point-in-time record of the (now superseded) Cloud Run deploy path.
  • Cloud Run pipeline operational on staging + prod (historical — superseded S298; see above) — 4 per-tenant Knative Job manifests cloudrun/jobs/{prod,staging}-{phew,kpf}.yaml deployed across both projects with deploy-time NEXT_PUBLIC_CLIENT_ID baking per D-RUN-12.1. Build chain: cloudrun/cloudbuild.yaml produces slim + eval image variants via Python buildpack (multi-tag publish via pack build --tag --publish). Deploy workflow .github/workflows/cloud-run-deploy.yml runs WIF auth → Cloud Build → manifest replace → declarative --set-secrets mount of the 11 always-required Secret Manager secrets (Phase 2 / T2.2 — ANTHROPIC_API_KEY, OPENAI_API_KEY, SUPABASE_*, NEXT_PUBLIC_*, FIRECRAWL_API_KEY, CRON_SECRET, SENTRY_AUTH_TOKEN) → smoke verify → Vercel notify dispatch. Runtime per-tenant SAs ({phew,kpf}-pipeline-sa) hold project-scoped roles/secretmanager.secretAccessor (T2.3 — 4 bindings; project-scope chosen over per-secret since secrets are tenant-shared today). Firstinvoke verification GREEN on staging × 2 tenants via KH_SMOKE_TEST=1 env-var trigger in scripts/ingest.py: gcloud run jobs update --update-env-vars= KH_SMOKE_TEST=1 then execute --wait returns exit 0 with log line KH_SMOKE_TEST=1: env + imports OK; exiting 0 without DB writes. The env-var path supersedes the unreachable --smoke-test argparse flag because Cloud Run buildpack launchers bake the entrypoint command at image-build time — runtime --args overrides do not reach the script (four override paths empirically verified ineffective: --args on execute/update, --command override, GOOGLE_ENTRYPOINT env var). The argparse flag remains for local-dev parity. All security-hardened with env-var indirection on ${{ }} interpolations. Handover runbook docs/runbooks/cloud-run-phase-1-handover.md covers T1 IAM/WIF/GH-vars setup + T2.2/T2.3 close-out (§8). Spec: docs/audits/kh-production-readiness-phase-1/specs/wp-run-cloud-run-provisioning-spec.md v1.4 RATIFIED (§2.14.1-§2.14.5 4-entry Cloud Scheduler matrix + YAML schema + wrapper script + IAM pre-flight + OIDC audience pin; §2.13.1 NEW Cloud Build cache image plumbing — --cache-image=europe-west2-docker.pkg.dev/$PROJECT_ID/pipeline/kh-pipeline-{slim,eval}:cache flag wired in cloudrun/cloudbuild.yaml for both build steps; AC-2.1 verifies :cache tag present post-first-build; cold rebuild ~14 min baseline, steady-state cache hit on scripts/**/*.py-only changes projected ~3-5 min, first cache-bearing build is cold + cache push ~15-16 min one-off; Phase 3 IMPL gated on Phase 2 T2.5/T2.6/T2.7 pipeline_log.py widening + cross-track sync).
  • CI prod-deploy gate via Vercel notify patternvercel/repository-dispatch/actions/status@v1 posts workflow status from ci.yml (ci-summary aggregator) to Vercel so prod deploys gate on that named check. if: always() ensures Vercel sees both success and failure signals. Vercel-side opt-in via Settings → Git → Required Checks. (Historically cloud-run-deploy.yml also posted a Vercel - knowledge-hub: cloud-run-deploy check; that workflow is removed at S298 and no longer posts — drop it from Vercel Required Checks if it was opted in, or prod deploys hang.) See docs/runbooks/ci.md §2.2.
  • D-9 ESLint no-console regression guardeslint.config.mjs bans console.* in app/**/*.{ts,tsx} + lib/**/*.{ts,tsx} with file-level allowlist for 4 documented intentional residuals (lib/logger/client.ts chokepoint shim, lib/client-telemetry.ts dev-mode debug, lib/eval/reporter.ts CLI eval reporter, lib/mcp/app-bundles.ts autogen bundle). Subsumes the originally-proposed grep guard test. WP-G5.4 Phase 4 closure documented in docs/specs/structured-logging-spec.md §5.
  • Vercel function timeout audit complete — 12 maxDuration rows in vercel.json audited and classified: 9 STAY-ON-VERCEL (latency-sensitive user-triggered work, complementary Vercel cron per D-2), 2 AUDIT pending Sentry telemetry scan (responses/draft-all bulk-draft sustained-timeout risk, cron/intelligence-poll every-15-min cadence at 120s ceiling), 0 unconditional MIGRATE-TO-CLOUD-RUN. WP-RUN.5 handoff list captures the 2 AUDIT entries as conditional candidates. Per docs/audits/ kh-production-readiness-phase-1/wp-run-phase-1-5-vercel-audit.md.

This document is the canonical record of what is built. Sister documents cover the rest of the product picture:

DocumentPurpose
docs/reference/product-roadmap.mdForward-looking only — what is coming next
docs/reference/product-backlog.mdParked, deferred, and speculative items
.planning/.archive/.audits/si-gap-analysis-s149.mdSector Intelligence gap analysis (S149, historical — roadmap is now source of truth)
.planning/.archive/.audits/ai-eval-gap-analysis-s149.mdAI Evaluation gap analysis (S149, historical — roadmap is now source of truth)
docs/reference/SCHEMA-QUICK-REFERENCE.mdDatabase schema canonical reference
docs/reference/classification-architecture.mdClassification pipeline architecture
docs/reference/classification-prompt.mdCurrent classification prompt v4.5
docs/reference/data-entry-points.mdAll KB ingestion entry points
docs/operations/taxonomy-change-runbook.md7-step taxonomy change chain
docs/reference/field-consumer-dependency-map.mdField-to-consumer dependency map (S139)
docs/reference/entity-type-taxonomy-spec.mdEntity type taxonomy v1.1 (S140)
docs/reference/two-pass-validation-architecture.mdTwo-pass entity validation design (S149)
docs/audits/two-pass-cost-quality-measurement.mdTask 15 measurement — batch-only recommendation (S168)
docs/operations/re-ingestion-quality-protocol.mdOperator runbook for re-ingestion quality gate (S168)
docs/operations/blank-db-restore-matrix.mdBlank-DB restore matrix — entry points + side tables (S175)
docs/operations/two-stage-re-ingestion-runbook.mdTwo-stage re-ingestion runbook — Stage 0→2 (S175)
docs/operations/guide-regeneration-prompts.mdMCP guide/item recreation prompts (S175)
docs/reference/sector-intelligence-pathway.mdSector Intelligence product pathway
docs/reference/ai-integration-strategy.mdAI integration strategy and visibility policy
docs/reference/state-of-the-product.mdThis document — canonical present-tense capability ledger
docs/reference/ux-principles.mdUX principles (vision content from ADS v1.0)
docs/reference/ai-visibility-policy.mdAI-as-infrastructure UI visibility policy
docs/generated/codebase-stats.mdAuto-generated code statistics
docs/generated/mcp-inventory.mdAuto-generated MCP tool/resource/prompt inventory