Skip to content

03 — Tech stack

Last verified: 14/05/2026 (S239 Wave 1 — WP4 architecture-split parallel sub-doc) Scope: v1 stack composition for Knowledge Hub — Vercel-hosted Next.js application surface, Cloud Run Python pipeline sidecar, Supabase Postgres + pgvector substrate, MCP transport layer, and the platform-standard patterns that bind them. Status: [CURRENT-CANONICAL] Layer: 2 — references 01-vision.md for product framing; no schema dependencies (schema detail lands in 04-workspace-types.md). Companion sub-docs: 01-vision.md (ratified S238 pilot), 02-data-flow.md, 04-workspace-types.md, 05-qa-flow.md, 06-mcp-tooling.md (Theme F RATIFIED-S240 — direct plugin), 07-collapse-list.md, 08-new-features.md, 09-diagrams.md.


This sub-doc is the canonical record of the technologies that compose Knowledge Hub v1. It enumerates each runtime, library, and service the platform depends on, names the ratification source for its adoption, and captures the constraints that drove each choice. Where a component sits behind a sibling sub-doc’s narrative (data-flow stages, MCP tool inventory, schema), the entry here references that sub-doc rather than duplicating its content.

The stack composition is bounded by two convergent forces. The first is the AI-consumer-first surface model established in 01-vision.md §2.1 — Claude clients are the primary surface, the KH web UI is the secondary surface, and the MCP server is the single reusable interface between them and the corpus. The second is the post-S234 ingest substrate shift: Docling replaces ad-hoc PDF extraction and binds the platform to a Cloud Run sidecar topology, while pullmd remains the HTML adapter and cocoindex orchestrates everything between source binding and Postgres targets. These two forces together determine the v1 stack shape.


The v1 platform spans four runtime surfaces plus a shared data substrate. Each surface has a distinct deployment + auth profile; together they form one application.

SurfaceRuntimeHostsPurpose
Next.js applicationVercel serverless / edgeWeb UI, MCP server, API routes, OAuth providerPrimary code surface; auth middleware; MCP transport
Cloud Run sidecarPython (Cloud Run jobs + services)Cocoindex pipeline, Docling extraction, pullmd, classification flowsHeavy-substrate ingest work that cannot fit in Vercel’s 250 MB function bundle
Supabase PostgresManaged Postgres + pgvector + RLSCanonical data store, embedding store, audit logSingle source of truth for KB content + governance state
MCP transportStreamable HTTP via Next.js API routeKH MCP server + MCP Apps (Vite single-file builds)Claude-client integration surface

A separate development-time substrate — mempalace as a memory MCP plugin (~/.claude/settings.json enabled) — is operational across sessions but does not participate in the v1 ingest pipeline; see §10.

The remaining sections enumerate each component, name the ratification source, and identify which sibling sub-doc carries the deeper detail.


Vercel hosts the Next.js 16 application: the web UI, the MCP server, the OAuth provider, and the API route layer. Cron and webhook entry points run as Vercel functions. The proxy (proxy.ts) is the auth middleware; publicRoutes is the allowlist for non-authenticated endpoints (CLAUDE.md “Proxy blocks non-API public routes”).

Per CLAUDE.md “Deployment”, production runs at https://www.kh.phew.org.uk (Vercel main-branch deploy); staging runs at the Vercel git-staging deploy URL. GitHub repository: https://github.com/ai-solution-hub/knowledge-hub. Region: eu-west-2 (London).

3.2 Runtime constraint that drives the sidecar

Section titled “3.2 Runtime constraint that drives the sidecar”

Vercel’s 250 MB function bundle limit drives the Cloud Run sidecar architecture. Docling’s 1.8 GB footprint (per phase-b-prerequisite-2d-docling-bakeoff.md §6) cannot land in a Vercel bundle. The Cloud Run sidecar (§4) absorbs Docling + cocoindex + pullmd; the Vercel application reads results from Postgres and exposes them through the MCP server.

This constraint is not a code-organisation preference — it is the load-bearing reason the platform is split across two runtimes. Without Docling primary parsing, Vercel could carry the whole ingest pipeline; with Docling, the sidecar is mandatory.

The MCP server runs as a Next.js API route at app/api/mcp/[transport]/route.ts, using the MCP SDK’s WebStandardStreamableHTTPServerTransport directly. A fresh server + transport is created per request per CLAUDE.md “mcp-handler breaks on Vercel” gotcha — createMcpHandler is not used. mcp-handler is reserved for .well-known only.

Tool inventory + tool annotations + the mempalace direct-vs-wrapped pattern decision land in 06-mcp-tooling.md. Theme F is RATIFIED-S240 — direct plugin (mempalace as a separate Anthropic plugin alongside KH MCP); migration to wrapped is [DEFERRED-POST-LAUNCH] per 06-mcp-tooling.md §4 + PLAN.md §7 item 1.

CI/CD per CLAUDE.md “CI/CD” runs seven PR-blocking jobs in parallel (quality-precheck, quality-test 4-shard matrix, e2e-smoke, mcp-build, mcp-eval-seed, mcp-eval L1/L3/L4 matrix, integration). Full topology + per-step failure-mode table: docs/runbooks/ci.md. The staging branch is deploy-only (no long-lived worktree) — used for staging-mirror sync per docs/runbooks/staging-refresh.md.


Cloud Run hosts the Python ingest pipeline: cocoindex orchestration, Docling extraction (PDF / DOCX / XLSX), pullmd extraction (HTML), and the AI extraction flows (classification, entity extraction, Q&A extraction via ExtractByLlm). Per CLAUDE.md “Deployment”, KH already operates two Cloud Run projects — kh-prod-494815 (main branch) and kh-staging-494815 — predating Docling adoption.

The Cloud Run sidecar architecture is ratified per 00-synthesis-v2.md §3.1 (Cloud Run sidecar NEW HIGH-priority) and per COCO.6 (0.9-decision-graph.md §11.2). The substrate was RESOLVED-S234. Per 00-synthesis-v2.md §3.1: “KH already runs Cloud Run for Python pipeline (kh-prod-494815 / kh-staging-494815 per CLAUDE.md). Extension to host cocoindex + Docling extraction is operational only.”

Authentication uses WIF (no service-account JSON keys). The deploy pipeline lives at .github/workflows/cloud-run-deploy.yml; the runbook is docs/runbooks/cloud-run-phase-1-handover.md. GitHub Environments (Production + Staging, case-sensitive) gate deploys per docs/runbooks/github-environments.md.

The sidecar’s per-stage flow shape — source binding, content-hash idempotency, per-MIME @coco.fn wrappers, op_id propagation, auto-RLS event trigger interaction — lands in 02-data-flow.md. This sub-doc carries only the composition-level adoption fact: cocoindex + Docling + pullmd live in the Cloud Run sidecar, not in Vercel.


5. Cocoindex (recurring runtime substrate)

Section titled “5. Cocoindex (recurring runtime substrate)”

Cocoindex is the recurring ingest orchestrator. It binds external folders (LocalFS, SharePoint, Notion, Google Drive) as sources, runs per-MIME conversion through @coco.fn adapters, executes Structured Extraction via ExtractByLlm for classification + Q&A + entity-resolution flows, and UPSERTs results into Postgres targets through postgres.mount_table_target(managed_by="user"). The engine handles incremental Δ, content-hash idempotency, retry/back-off, and per-flow op_id ledger natively.

Cocoindex adoption as the primary recurring orchestrator is ratified per the twelve COCO rows in 0.9-decision-graph.md §11.2. The substrate-decision register in phase-b-prerequisite-2-cocoindex-deep-dive.md §1.2 enumerates each capability cocoindex resolves (Pattern A/B parser fate, B2 source_documents shape, Theme B step 3 markdown convert, Theme B step 4 classify-form-data). COCO.1 (“Cocoindex affordance synthesis: ExtractByLlm + entity_resolution + files_transform + memoisation adopted”) is the umbrella ratification.

  • ExtractByLlm with typed Python output_type — primary primitive per Recommendation 1 of phase-b-prerequisite-2-cocoindex-deep-dive.md §4. Carries Theme B step 4 (classify-form-data) and Q&A extraction; dissolves the prior Pattern A/B parser plan.
  • files_transform + per-MIME @coco.fn adapters — markdown-sidecar v1 substrate; primary handler for the binary path. Per-format detail in 02-data-flow.md.
  • entity_resolution — selectively adopted for named-entity dedup (clients / competitors / employees) per COCO.1 + Recommendation 3 of the cocoindex deep-dive.
  • @coco.fn(memo=True) memoisation — retires the Q4.12 cost-tracking dashboard pattern per 00-synthesis-v2.md §3.11.
  • Retry / back-off / DLQ + crash recovery — subsumes pipeline_failures (DO-NOT-BUILD per 00-synthesis-v2.md §3.10 and COCO.7). KH retains pipeline_runs rollup via recordPipelineRun().

Cocoindex emits a per-flow op_id that propagates into Postgres targets. The hybrid op_id pattern — trigger-driven for platform-wide audit cohesion + cocoindex per-flow op_id for pipeline-side correlation — is ratified per N7 RESOLVED-S236 (00-synthesis-v2.md §5.1; lands in 02-data-flow.md).

S9 spike (RESOLVED-PARTIAL-S235 per COCO.9) confirms @coco.fn(memo=True) short-circuits cleanly subject to a layered function shape: inner-tier functions must consume content_text: str, not FileLike. Memo scoping is per-component-path, not global content-hash dedup. The sidecar v1 promotion gate (COCO.10) is CLOSED-CONDITIONAL on the layered fn-shape.

5.6 Operational observability — CocoInsight kept separate

Section titled “5.6 Operational observability — CocoInsight kept separate”

CocoInsight is the developer-pipeline-observability surface (transformation lineage). Per phase-b-prerequisite-2-cocoindex-deep-dive.md §3.3 + §6 it is not a replacement for audit_log. Both substrates are retained: CocoInsight serves engineering visibility; audit_log serves compliance + governance UX. CocoInsight on-prem deployment posture is DEFERRED (contingent on KH self-hosting) per PLAN.md §7 item 10 — only relevant if KH self-hosts. A TS-facing API for cocoindex’s per-flow-run ledger is DEFERRED-v1.1 per PLAN.md §7 item 9 (v1 ships only pipeline_runs rollup; spec retained as v1.1 substrate at docs/specs/id-36-cocoindex-ledger-api/TECH.md).

Per phase-b-prerequisite-2-cocoindex-deep-dive.md §1.3, cocoindex does not pick the op_id propagation pattern (Postgres-side choice), the idempotency content-shape (KH-side spike), or the upload-route silent-fail fix (KH-side N5). These belong to 02-data-flow.md rather than this sub-doc.


6. Docling (binary parser — PDF / DOCX / XLSX)

Section titled “6. Docling (binary parser — PDF / DOCX / XLSX)”

Docling is the primary parser for PDF, DOCX, and XLSX inputs. It produces GFM markdown with preserved heading structure and table fidelity, suitable for direct LLM consumption by the cocoindex ExtractByLlm stage downstream.

Docling adoption is ratified per 00-synthesis-v2.md §3.2 and COCO.2 / COCO.3 / COCO.4 (0.9-decision-graph.md §11.2). The bake-off evidence lives at phase-b-prerequisite-2d-docling-bakeoff.md §6 (S234) + §9 (S235 WP-DOCLING-XLSX URL-recall verification).

Per-format verdicts per the bake-off §4:

  • PDF — Docling decisive win on the SSQ fixture: 75 headings preserved vs markitdown 0; 299 GFM table rows preserved vs markitdown 0; Q-number↔text association intact for downstream LLM classification.
  • DOCX — Docling narrow win on heading-level fidelity (4-level outline vs markitdown 3-level). Tie on table fidelity with mammoth + Turndown. Consolidation directional.
  • XLSX — Docling clean GFM table output without NaN / Unnamed: placeholder pollution; merged-cell headers preserved correctly. Per the S235 WP-DOCLING-XLSX full-corpus verification (phase-b-prerequisite-2d-docling-bakeoff.md §9), the S234 URL-drop caveat does not replicate at full-corpus scale — Docling and markitdown tied at 14/15 unique URLs preserved (93.33% recall, the single miss is a shared XLSX hyperlink-anchor quirk).

Docling is MIT-licensed (administrative confirmation only per 00-synthesis-v2.md §3.2). The 1.8 GB on-disk footprint is the load-bearing fact for stack composition — it cannot land in Vercel’s 250 MB function bundle, which is why §4 Cloud Run sidecar exists. Cold-start wall-clock is ~44.75s on first call; mitigated via @coco.fn(memo=True) per phase-b-prerequisite-2d-docling-bakeoff.md §6 + the Docling-model pre-warm in the Cloud Run container image.

Docling does not own HTML — see §7 pullmd retention. Docling also retires unpdf (DECIDED-RETIRE-S233 → RATIFIED-RETIRE-S235 per 0.9-collapse-candidates.md §12.4) and supersedes the Python extractor write-paths trafilatura + pdfplumber in scripts/kb_pipeline/extract.py per S237 CV 16 ratification (0.9-collapse-candidates.md §14.3).

A tracked-changes DOCX regression test is in-scope as PLAN.md §7 item 11 → T13.2 — adds to the Theme B regression suite (relevant because CLAUDE.md “python-docx and Track Changes” gotcha already warns about Document(path) direct calls when Track Changes is present).


Section titled “7. pullmd (HTML / share-link parser — retained)”

pullmd is the HTML and URL-input parser. It is a self-hosted Playwright sidecar that handles JS-rendered pages, Cloudflare-protected URLs, and Reddit comment-trees. Output is markdown indexed by an 8-hex share id with 90-day TTL post-write, giving source_documents.storage_path = pullmd://${share_id} a stable identity contract without a separate binary blob in Supabase Storage.

7.2 Ratification source — no Docling consolidation

Section titled “7.2 Ratification source — no Docling consolidation”

pullmd retention as the HTML adapter is ratified per 00-synthesis-v2.md §3.3 and COCO.5 (0.9-decision-graph.md §11.2). The bake-off conclusion (per phase-b-prerequisite-2d-docling-bakeoff.md §5) is verbatim: “the only dimension where Docling wins over pullmd for HTML is the trivially-lower license-surface argument. The JS-page coverage gap is a hard regression. Pullmd stays the HTML/URL adapter; Docling owns binary formats (PDF/DOCX/XLSX).”

The retention rests on five replaceability gaps Docling cannot close:

  1. JS-rendered SPAs (Playwright sidecar coverage).
  2. Cloudflare-protected URLs (native short-circuit).
  3. Reddit comment-trees (first-class extractor).
  4. Share-id stable identity contract (pullmd://${share_id} as storage_path value).
  5. URL-input handling without a separate binary blob.

pullmd is AGPL v3 (vs Docling MIT). License compatibility per phase-b-prerequisite-2d-docling-bakeoff.md §3.3: KH calls pullmd as a separate self-hosted network service via HTTP, so the AGPL “network service” clause does not propagate to KH platform code. The PM-Q2 administrative confirmation remains tracked in the 0.8.4 review register but is not Wave-1 blocking.

pullmd runs as a self-hosted Docker stack (80 MB base + 3.7 GB Playwright sidecar when enabled). Total ops-stack surface is similar order-of-magnitude to Docling per phase-b-prerequisite-2d-docling-bakeoff.md §5 — consolidation would not materially shrink the operations footprint even if the JS-coverage gap were closeable.

Both pullmd and Docling live behind cocoindex as per-MIME @coco.fn wrappers (per phase-b-prerequisite-2d-docling-bakeoff.md §5 closing line). The flow-stage shape — which adapter runs for which MIME, source binding semantics, idempotency contract — lands in 02-data-flow.md.


Supabase is the canonical data substrate. Project ID rovrymhhffssilaftdwd (eu-west-2 London), pgvector 0.8.0, Postgres. Embeddings are vector(1024) (text-embedding-3-large). RLS is role-based via get_user_role(). Canonical schema constants live in lib/validation/schemas.ts.

Per CLAUDE.md “Supabase & Schema”, staging runs on a persistent branch (turayklvaunphgbgscat); refresh procedure is docs/runbooks/staging-refresh.md. DDL flows via the Supabase CLI (/opt/homebrew/bin/supabase migration new + db push) — never via MCP execute_sql. The schema quick reference is docs/reference/SCHEMA-QUICK-REFERENCE.md.

8.2 RLS pattern — auto-enable event trigger

Section titled “8.2 RLS pattern — auto-enable event trigger”

Per 00-synthesis-v2.md §3.16 (RESOLVED-S236, WP8 ruling 14/05/2026), KH adopts Supabase’s platform-recommended rls_auto_enable() event trigger (ddl_command_end on CREATE TABLE / CREATE TABLE AS / SELECT INTO, public schema only) platform-wide. This closes OQ-Q24-B + OQ-Q113-B together and removes the recurring audit_log + per-tenant RLS-omission risk for any new public table.

The dedicated RLS-PATTERN spec landed at docs/specs/rls-pattern/PRODUCT.md + docs/specs/rls-pattern/TECH.md per Liam S239 Wave 2 dispatch decision (separate doc per INV-architecture-split-readiness.md §7.4 recommendation). This sub-doc forward-refs the spec for the auto-RLS event trigger + grants pattern + per-function REVOKE-EXECUTE discipline; the trigger pattern itself is ratified per 00-synthesis-v2.md §3.16 + §3.17.

8.3 Platform deadlines — grants compliance + OAuth 200

Section titled “8.3 Platform deadlines — grants compliance + OAuth 200”

Two Supabase platform deadlines are active per 00-synthesis-v2.md §3.17 + §3.18:

  • Public-schema grants compliance (30/05/2026). From 30/05/2026, new public.* tables require explicit GRANT per role (anon / authenticated / service_role) before Data API access. Pairs with §3.16 auto-RLS event trigger in the same combined migration so RLS enable + grants land together (RLS-first, then grants).
  • OAuth token endpoint HTTP 200 (26/05/2026). From 26/05/2026, /v1/oauth/token returns HTTP 200 (not 201) per OAuth 2.1 §3.2.3. KH OAuth-token-handling code (app/api/oauth/* + MCP plugin OAuth client) must not branch on HTTP 201 specifically.

Migration draft at supabase/migrations/20260514150238_enable_rls_auto_event_trigger_and_grants_pattern.sql is APPLY-GATED-ON-LIAM-REVIEW per the S238 wrap carry-forward.

Every new PL/pgSQL function MUST include SET search_path = public, extensions per CLAUDE.md “Supabase & Schema”. Anon EXECUTE auto-grant is a Supabase default that makes naive REVOKE ... FROM PUBLIC a no-op against the anon role; every new public.*() helper needs an explicit REVOKE EXECUTE ON FUNCTION public.foo() FROM anon; in its migration (per-tenant if SECURITY DEFINER) per the CLAUDE.md gotcha.


9. Tiptap + Yjs (collaborative editor surface)

Section titled “9. Tiptap + Yjs (collaborative editor surface)”

Tiptap is the rich-text editor for long-form Q&A content in the KH web UI (the secondary surface per 01-vision.md §3.2). It is the existing Q&A ContentEditor per S198 ship and serves any non-Q&A long-form content editor the UI exposes. Yjs is the collaborative CRDT layer that enables concurrent-user editing on the same content item.

9.2 Ratification source — UC1 Candidate A

Section titled “9.2 Ratification source — UC1 Candidate A”

Tiptap + Yjs as the v1 in-platform write path is ratified per 0.9-edit-flow-investigation.md §6.1 (UC1 Candidate A — typo fix, foundational template). Per §6.1: “KH UI captures the edit via existing Tiptap ContentEditor with Yjs collab plugin enabled. KH server writes DB row (content_items.content + content_history via auto-version trigger). KH server writes file at source_documents.original_path (markdown) or sidecar .md (binary source per §6.1.1). Cocoindex source binding observes file change on next scan; idempotency confirmed by S9 spike skipping flow re-run when content-hash matches KH’s just-written hash.”

The Yjs collab plugin handles concurrent-user CRDT at the editor layer; post-CRDT-merge last-write-wins at the file layer. CRDT-based collab is preferred over Operational Transform for v1 per 0.9-edit-flow-investigation.md §1.5 — Yjs is mature, has tight Tiptap integration, and is widely adopted (Notion / Figma / Linear).

9.3 Persistence posture — y-supabase deferred v1.1

Section titled “9.3 Persistence posture — y-supabase deferred v1.1”

Per 0.9-edit-flow-investigation.md §6.1 4.1.Q4 RESOLVED: “v1 ships Yjs collab without DB persistence; awareness state lives in-memory + browser; recovery handled by Tiptap’s local-storage fallback. v1.1 evaluates y-supabase once collab usage patterns clear.” No Yjs document persistence in v1.

Per CLAUDE.md “@tiptap/markdown is NOT tiptap-markdown” gotcha: the official @tiptap/markdown package uses editor.getMarkdown() (not editor.storage.markdown.getMarkdown()) and lacks html / transformCopiedText / transformPastedText options. The community-package documentation surfaced by some lookups does not match; verify against node_modules/@tiptap/markdown/dist/ when integrating.

For binary content (PDF / DOCX / XLSX) the viewer composition is markdown-sidecar-fed: Docling produces the markdown; Tiptap renders it; the underlying binary is read-only via a separate viewer. The full viewer architecture lands in 08-new-features.md (and the per-format flow detail in 02-data-flow.md); this sub-doc names only the editor component.


Mempalace is the canonical memory MCP plugin per CLAUDE.md “Memory (Mempalace)” — replaced the auto-memory file system on 2026-05-10. Plugin mempalace@mempalace v3.3.5 is enabled in ~/.claude/settings.json enabledPlugins; Stop + PreCompact hooks fire automatically per session. One wing per worktree. Drawer counts as of S43 in CLAUDE.md.

Mempalace is operational for the development team’s session continuity. It is not load-bearing for the v1 ingest pipeline or for end-user Knowledge Hub queries. End-user memory is whatever Claude carries within its own conversation context — there is no v1 surface that writes user-state into mempalace.

The integration pattern decision — mempalace direct vs wrapped within the KH MCP server — is the Theme F MCP-action review item, RATIFIED-S240 — direct plugin (mempalace as a separate Anthropic plugin alongside KH MCP); wrapped pattern [DEFERRED-POST-LAUNCH]. Outcome lands in 06-mcp-tooling.md §4 (per PLAN.md §7 item 1). This sub-doc records mempalace as a stack component; the v1 integration shape is the direct plugin.

Per CLAUDE.md “MCP tools” inventory, mempalace_search is PARTIAL (default works as of v3.3.5; wing-filter id resolution still errors). The diary write/read tools work for default wing_<agent> but error on cross-project wing parameter. These are operational caveats, not v1 ingest blockers.


Anthropic’s doc skills (docx / xlsx / pdf) package tool-usage context that Claude clients consume when working with binary documents. Per phase-b-prerequisite-2-cocoindex-deep-dive.md §2c + Recommendation 1, KH uses doc skills for Theme B step 1 (evaluate-form) — Claude reads the doc skill alongside the source binary to produce form-type judgement.

KH ships its own Claude skills loaded via lib/ai/skills/loader.ts (markdown content inlined at build time per lib/ai/skills/inlined.generated.ts to avoid runtime filesystem reads on Vercel). Skill files in lib/ai/skills/:

  • bid-writing.md
  • classification.md
  • classification-entity-types.md
  • governance.md
  • uk-procurement.md

These are consumed by lib/ai/classify.ts, lib/ai/draft.ts, and lib/mcp/resources.ts. The skills are KH-owned content surfaces — they do not duplicate Anthropic’s doc skills.

11.3 Ratification source — Theme B step 1

Section titled “11.3 Ratification source — Theme B step 1”

Theme G (“doc skills evaluation”) RESOLVED-S234 per phase-b-prerequisite-2-cocoindex-deep-dive.md §2 row “Theme G” + Recommendation 4 (retire-what-cocoindex-absorbs, build-what-it-doesn’t). Theme B steps 1 + 3 + 4 RESOLVED per 00-synthesis-v2.md §5.1 + COCO.11 (Anthropic doc skills for evaluate-form) + COCO.12 (cocoindex ExtractByLlm for classify-form-data).

Step assignment per the resolution chain:

  • Step 1 — evaluate-form. Anthropic doc skills (docx / xlsx / pdf).
  • Step 2 — HITL upload-and-preview. Outside doc-skills scope.
  • Step 3 — markdown convert. Docling (per §6).
  • Step 4 — classify-form-data. Cocoindex ExtractByLlm with typed Python output_type.
  • Step 5 — HITL review. Outside doc-skills scope.

Markdown-converter skill (markitdown-backed) becomes a fallback option for Step 3 if a future fixture surfaces a Docling regression — see §13 retired alternatives.


Two cross-cutting patterns apply to every component above. Both are ratified at the platform level; they are not per-component preferences.

Per 00-synthesis-v2.md §3.9 and ONT.17 (0.9-decision-graph.md §11.1): “where there is an opportunity to use typed columns in our schema, we should always do so.” RATIFIED-S235. Applies platform-wide. Cascades into the procurement_workspaces Shape B retention (typed columns from JSONB — synthesis-v2 §3.9 cites the pre-rename bid_workspaces label per Q-OQR1-02), source_documents.extraction_metadata JSONB → typed columns / Zod-validated shape, and SKOS columns (BT / NT / SYN) on a future thesaurus_entries table over JSONB encoding.

JSONB is reserved for genuinely heterogeneous payloads. The full table-by-table cascade lands in 04-workspace-types.md; this sub-doc records only the platform-standard fact.

12.2 Provenance enum across hybrid vocabularies

Section titled “12.2 Provenance enum across hybrid vocabularies”

Per 00-synthesis-v2.md §3.7 and ONT.8 / Q-OQR1-11: hybrid Layer-1 CVs carry a provenance enum (core / client / recommended). Precedent is taxonomy_domains.provenance. RATIFIED-S235 across application_types, form_types, form_template_requirements, guides, coverage_targets. The entity_aliases.category enum is renamed to provenance in the Q-OQR1-16 combined PR (RATIFIED-S235, migration applied S246 (T2) per docs/specs/id-31-canonical-pipeline-implementation-plan/PLAN.md §7). WP6 markdown ontology storage carries provenance_model / client_extensible / editable_via / core_seed_path frontmatter per Q-OQR1-12.

The per-table column shape lands in 04-workspace-types.md §11; this sub-doc records the platform-standard pattern.

Per CLAUDE.md “Silent failures in Supabase calls” gotcha: every Supabase call routes through sb() (fail-fast) or tryQuery() (Result-returning) from @/lib/supabase/safe. Composite responses use warningsEnvelope(); best-effort swallows use logBestEffortWarn(). ESLint rules local/no-unchecked-supabase-error and local/no-silent-promise-catch enforce. Full spec: docs/specs/silent-failure-prevention-spec.md.

getAuthorisedClient() returns a discriminated union per CLAUDE.md “Data & Architecture” — auth.success (not auth.authorised). Three failure reasons: unauthenticated (→401), forbidden (→403), role_lookup_failed (→500). Use authFailureResponse(auth) helper to route each reason to the correct HTTP status.

TanStack Query is the exclusive data-fetching layer in hooks per CLAUDE.md. Keys in lib/query/query-keys.ts; fetchers in lib/query/fetchers.ts. No SWR or raw fetch in hooks.


The stack composition above implicitly retires a set of alternative components. Each entry below names the alternative + its ratification source. Detailed retire-tier markers live in 07-collapse-list.md; this sub-doc surfaces only the v1-stack-composition rationale.

Retired / not-in-v1Replaced byRatification source
unpdf (PDF parser dependency)Docling (§6)0.9-collapse-candidates.md §12.4 (RATIFIED-RETIRE-S235); COCO.2
trafilatura write path in scripts/kb_pipeline/extract.pyDocling (PDF) + pullmd (HTML)0.9-collapse-candidates.md §14.3 (RATIFIED-RETIRE-S237)
pdfplumber write path in scripts/kb_pipeline/extract.pyDocling0.9-collapse-candidates.md §14.3 (RATIFIED-RETIRE-S237)
markitdown as primary XLSX converter (S234 caveat)Docling (S235 full-corpus URL parity)0.9-collapse-candidates.md §12.4 + WP-DOCLING-XLSX §9; markitdown retained as fallback only
CopilotKit (embedded chat sidebar)Removed in S109; no sidebar patterndocs/reference/ai-visibility-policy.md Rule 4
GitBook auto-update plan (docs-site framework)Astro + Starlight0.9-collapse-candidates.md §14.5 (RATIFIED-RETIRE-S237); docs/ontology/README.md “Docs-site auto-update plan”
Firecrawl as URL extractorpullmd0.9-collapse-candidates.md §3 + CX.6; not in v1 stack
pipeline_failures tableCocoindex retry / back-off / DLQ + recordPipelineRun() rollup00-synthesis-v2.md §3.10 (DO-NOT-BUILD); COCO.7
Q4.12 cost-tracking dashboardsCocoindex memoisation + per-stage metrics00-synthesis-v2.md §3.11 (RETIRE); COCO.8
mcp-handler library on VercelMCP SDK WebStandardStreamableHTTPServerTransport (§3.3)CLAUDE.md “mcp-handler breaks on Vercel” gotcha

Operational Transform (OT) collab is REJECTED for v1 per 0.9-edit-flow-investigation.md §1.5 in favour of Yjs CRDTs. automerge is REJECTED for v1 in favour of Yjs (already in the Tiptap ecosystem). Both listed for completeness only.


These framings are explicitly rejected per Phase 0.9 ratifications. Subsequent sub-docs (especially 02-data-flow.md and 06-mcp-tooling.md) build on these closures.

Anti-patternWhy rejectedCitation
Building pipeline_failures tableCocoindex retry/back-off/DLQ subsumes; KH’s recordPipelineRun() handles rollup00-synthesis-v2.md §3.10; COCO.7
Skill-seekers-style cost-tracking dashboards (Q4.12)Cocoindex memoisation + per-stage metrics supersede; runtime cost framing banned from architecture docs00-synthesis-v2.md §3.11; COCO.8
Building a Pattern A / Pattern B Q&A parser as recurring infrastructureDissolved by ExtractByLlm with typed output_type; Pattern A/B retires as a one-shot Phew-migration helper onlyCOCO.1; phase-b-prerequisite-2-cocoindex-deep-dive.md §1.2
Consolidating Docling for HTMLCannot replace pullmd’s Playwright sidecar / Cloudflare short-circuit / Reddit comment-trees / share-id identity contract00-synthesis-v2.md §3.3; COCO.5; bake-off §5
JSONB over typed columns for key dataTyped columns are the platform standard; JSONB reserved for genuinely heterogeneous payloads00-synthesis-v2.md §3.9; ONT.17
Treating CocoInsight as a replacement for audit_logDifferent audiences + different retention + different query patterns; build bothphase-b-prerequisite-2-cocoindex-deep-dive.md §3.3
Embedded chat sidebar in the KH UIAll AI-assisted interactions either run invisibly in the background or route to Claude via bridge actions; CopilotKit sidebar removed in S109docs/reference/ai-visibility-policy.md Rule 4
createMcpHandler for the KH MCP server on VercelBreaks on Vercel; use WebStandardStreamableHTTPServerTransport directly with a fresh server + transport per requestCLAUDE.md “mcp-handler breaks on Vercel”
Yjs document persistence via y-supabase in v1Awareness state lives in-memory + browser for v1; persistence evaluation deferred to v1.1 once collab usage patterns clear0.9-edit-flow-investigation.md §6.1 4.1.Q4
Running supabase-js writes without .select() from a sandbox-proxied dev shellBun hangs on HTTP 204 through the sandbox proxy; production is unaffected. Fix is dangerouslyDisableSandbox: true on the dev shell — not adding .select() workarounds in production codeCLAUDE.md “Bun fetch hangs on HTTP 204”

This sub-doc is one of nine that supersede docs/plans/phase-0-investigation/0.9-intended-architecture.md (2015 lines, S229). The source doc’s §10 tool-stack-composition framing carries pre-Q-OQR1-02 procurement-rename framing and omits the Cloud Run sidecar that post-S234 Docling adoption forces. Full audit trail of superseded items: docs/specs/core-docs-pathway-assessment/INV-architecture-split-readiness.md §2 (row 9 calls out the §10 sidecar omission specifically). Canonical-state sources downstream sub-docs cite: 00-synthesis-v2.md §3 + §4 (§3.1 Cloud Run / §3.2 Docling / §3.3 pullmd / §3.7 provenance / §3.9 typed columns / §3.16 auto-RLS); 0.9-decision-graph.md §11.2 (twelve COCO closures).

Source doc preserved as pre-split snapshot during Waves 1-4; archived to .planning/.archive/.specs/ after 09-diagrams.md closes per CLAUDE.md “Historical planning”.

Per construction guide §4.1 — three-tier status taxonomy ([CURRENT-CANONICAL] / [PARTIALLY-SUPERSEDED] / [FULLY-SUPERSEDED]); table shape mirrored by every subsequent sub-doc against its own heritage set per §4.2.

DocDateStatusUseful for
docs/plans/phase-0-investigation/phase-b-prerequisite-2-cocoindex-deep-dive.mdS234 (with §7 S234 end-of-session closures)[CURRENT-CANONICAL] — cocoindex affordance map (§1.1), substrate-decision register (§1.2 / §1.3), and §7 Docling spike resolution all reflect ratified state. Recommendations 1-5 (§4) are the substrate of COCO.1-COCO.12.Cocoindex affordance map; ExtractByLlm Recommendation 1; cataloguer-skill / Docling fallback analysis (§7); CocoInsight vs audit_log separation (§3.3); TS-facing API STILL-OPEN status (§3.4).
docs/plans/phase-0-investigation/phase-b-prerequisite-2d-docling-bakeoff.mdS234 (with §9 S235 WP-DOCLING-XLSX append)[CURRENT-CANONICAL] — bake-off evidence + per-format verdicts hold; §9 XLSX URL-recall re-verification supersedes the S234 sample-time caveat. License + footprint findings (§3) hold.Per-format scoring evidence; DOCX consolidation directional rationale; HTML consolidation decision rationale (§5); license verdicts (§3.3); confidence-per-recommendation table (§6).
docs/reference/ai-integration-strategy.md11/03/2026 (updated 07/04/2026 + verified 28/04/2026)[PARTIALLY-SUPERSEDED] — §3 four-layer integration architecture (MCP Server / MCP Apps / Cowork Plugin / Claude Code Plugin) holds; §4-§7 layer descriptions hold; §17 + §18 build status + effort estimates pre-date Phase 0.9 and are out of scope; “bid management” framing not yet updated to procurement umbrella per Q-OQR1-02.Four-layer architecture description (§3); Layer 1 MCP server approach (§4.1); MCP Apps technical approach (§5.3); Cowork plugin patterns (§6); evaluation tooling reference (§4.2).

These three docs, plus the central Phase 0.9 sources (INV-architecture-split-readiness.md, 00-synthesis-v2.md, 0.9-decision-graph.md §11.2), together carry the stack-composition evidence base for v1. The four-layer integration architecture in ai-integration-strategy.md §3 deliberately overlaps with this sub-doc (the four AI integration layers — MCP Server / MCP Apps / Cowork Plugin / Claude Code Plugin — all consume the stack composed here); the detailed tool inventory, mempalace direct-vs-wrapped pattern, and MCP App build pipeline live in 06-mcp-tooling.md (Theme F RATIFIED-S240 — direct plugin).


End of sub-doc. Next in Wave 1: 07-collapse-list.md (parallel sub-doc, dispatched concurrently). Wave 2 begins with 04-workspace-types.md (foreground sequential, BIG sub-doc — the schema-level detail this sub-doc points to lives there).