Skip to content

Phase 0.9 — Spike Plan (Phase 1)

Audit date: 2026-05-10 (S227 initial; S228 corrections pass — corpus + 5 new spikes + re-framings) Branch: content-items-investigation Predecessors: 0.9-decision-graph.md + 0.9-intended-architecture.md + 0.9-context.md + 0.9-edit-flow-investigation.md Companion: 0.9-collapse-candidates.md (tracks functionality slated for retirement under new architecture) Successor: 0.9-spike-results.md (output of this phase)

Purpose: Phase 1 spike phase that gates Phase 2 (architecture-impl) commitment per 0.9-intended-architecture.md §13.1. Each spike has explicit question + method + success criteria + decision gate + budget. Most run in parallel as worktree-isolated sub-agents.

S228 framing: spikes are unblocked to execute in next session in PARALLEL with architecture documentation updates. Most spikes do NOT depend on architecture rewrite landing first — they generate evidence that informs the rewrite. Only S8 + S9 + S10 are sequenced after edit-flow investigation §6 architectural decision.


IDSpikeEffortOwner patternSequencingDecision gateS229 Status
S1cocoindex schema-coupling3-5 daysForeground (main session)First; gates everything elseScenario A vs B → Phase 2 commit or fall back to Phase 0.7 Stream 2⏸ DEFERRED to S230 (multi-day foreground)
S2cocoindex external-folder source binding (using docs/client-documentation-base/)2-3 daysWorktree sub-agentParallel with S1 (after S1 day 1)v1 connector list scope + UC10 polling cadence✅ S229 — localfs-only v1; fs-watch UC10; LMDB-single-writer → NEW S14
S3mempalace observe-only (Shape A + B + C + miner)1-2 daysWorktree sub-agentParallelConfirm Shape A+B+C+miner adoption✅ S229 — PROCEED (Shape A+B+C+miner confirmed; miner doesn’t emit KG triples)
S4pullmd 50-URL bake-off1 dayWorktree sub-agentParallelAdopt or defer✅ S229 — CONDITIONAL PASS (adopt for HTML/CF/GN/Reddit; PDF→Jina; drop Firecrawl)
S5skill-seekers dependency_analyzer.py0.5 dayForegroundParallelAdopt as 3rd BNW-detector or skip✅ S229 — SKIP (0% non-overlapping TP)
S6mcp-scan feasibility + cost (OQ5 RATIFIED-YES; not “investigate”)0.5 dayForegroundParallelConfirm v1 vs v2 adoption shape✅ S229 — PASS v1 CI (inspect mode local); v2 cloud deferred; package renamed snyk-agent-scan
S7Pre-re-ingest eval (graphify data + CSP checklist forms)1-2 daysForeground or sub-agentParallelBaseline + auto-completion target validation⏸ DEFERRED to S230
S8Q&A flow validation (using docs/client-documentation-base/)1-2 daysForeground (post-S1)After S1 + edit-flow §6Confirms §6.2 architecture works⏸ GATES on S1 + Yjs install
S9 (NEW)Write-back semantics validation (UC1 / UC4 Candidate A/B/C)2-3 daysForeground (post-edit-flow §6)After edit-flow §6 architectural decisionCandidate A/B/C choice per UC family + cocoindex idempotency confirmed⏸ GATES on S1 + Yjs install
S10 (NEW)Cross-record dedup substrate (UC8)2-3 daysWorktree sub-agentAfter S2 + S3Cocoindex @coco.fn vs mempalace KG entity-resolution vs skill-seekers metadata — substrate choice⏸ READY-TO-DISPATCH-S230 (S2+S3 done; cocoindex confirmed insufficient — substrate must look beyond)
S11 (NEW)Playwright swap with agent-browser (OQ6 RE-FRAMED — NOT Firecrawl)1 dayWorktree sub-agentParallelAdopt agent-browser Tier 2.5 / keep Playwright✅ S229 — KEEP Playwright; OQ6/CX.7 RESOLVED-NO-SWAP; Playwright surface lives in pullmd Tier-3, not KH cascade
S12 (NEW)Graphify confidence-label taxonomy adopt (Q4.14 RESOLVED-ADOPT)0.5 dayForegroundParallelConfirm taxonomy fits alongside Q4.7 KG-provenance enum without conflict✅ S229 — 1:1 identity; single provenance column + repurpose confidence numeric(3,2)
S13 (NEW)ESLint input-required rule (DW.13 PROVISIONAL-IMMEDIATE-VALUE)0.5 dayForegroundParallelImplement rule + measure precheck overhead; adopt or defer✅ S229 — ADOPT-ADVISORY (warn); 100% precision v2 rule
S14 (NEW S229)Cocoindex concurrency under LMDB single-writer1 dayWorktree sub-agent or foregroundAfter S2 (S2 surfaced LMDB constraint)Single-orchestrator vs queue-based vs read-only-worker topology for Cloud Run✅ S230 — PASSED. Single-orchestrator-instance with isolated per-instance LMDB; S2 “lock blocks multi-worker” framing dissolved empirically

Total: ~14-19 days mostly parallel (S14 added at +1d). Single-session can absorb most on 1M context window. Sequential foundation: S1 → (S2-S7, S11-S13 parallel) → S10 + S14 → edit-flow §6 → (S8, S9 parallel).

S229 progress (8 spikes dispatched in parallel):all 8 done: S2 / S3 / S4 / S5 / S6 / S11 / S12 / S13. ⏸ deferred to S230: S1 / S7 / S8 / S9 / S10 / S14.

S230 progress: ✅ S14 done (cocoindex concurrency / Cloud Run topology — PASSED single-orchestrator).


Question: Does postgres.mount_table_target(PG, "content_items") accept the existing 70-col schema with FKs and triggers (Scenario A — desired) OR does it want schema ownership (Scenario B — fallback)?

Why it gates everything: if Scenario B (engine wants schema ownership), the architecture-impl phase doubles in cost (~12-16 weeks) and the case for cocoindex weakens vs Phase 0.7 Stream 2 build (~9-12 weeks).

Method:

  1. Set up cocoindex in dev environment:

    Terminal window
    pip install cocoindex
    # Bypass sandbox per wave-08 instruction
  2. Spin up a Postgres branch from staging:

    Terminal window
    /opt/homebrew/bin/supabase branches create cocoindex-spike --base staging
  3. Define minimal cocoindex flow (spike/cocoindex_minimal.py).

    API names verified against 0.8.2-cocoindex-evaluation.md lines 38, 46, 51, 142-143, 227 — use localfs.walk_dir source + RecursiveSplitter transform + mount_each target binding (NOT the placeholder names LocalFile.from_folder / SplitRecursively carried over in S227 draft):

    import cocoindex
    from cocoindex.sources import localfs
    from cocoindex.transforms import RecursiveSplitter
    from cocoindex.targets import postgres
    PG = postgres.from_env(SUPABASE_DB_URL)
    @cocoindex.flow_def(name="content_items_spike")
    def flow(src: str = "./fixtures/", table: str = "content_items"):
    async def index_file(item):
    md = await read_text(item.path)
    for chunk in RecursiveSplitter(2000, 500).split(md):
    yield extract_markdown_metadata(chunk, item)
    await coco.mount_each(index_file, localfs.walk_dir(src).items(), table)

    (Spike’s first step: confirm above import paths against installed cocoindex version. If APIs have evolved further, update before sub-agent dispatch.)

  4. Run cocoindex update and observe:

    • Mount succeeds? Postgres schema retained?
    • CHECK constraints respected? (e.g. content_text_hash GENERATED column, content_type CHECK)
    • FK relationships preserved? (e.g. workspace_id FK to workspaces)
    • Triggers continue to fire? (e.g. auto_version_content_items)
    • Δ-update works on second run with modified file?

Success criteria:

  • All existing constraints + FKs + triggers preserved → Scenario A
  • Cocoindex demands ownership of schema (drop/replace) → Scenario B

Decision gate:

  • Scenario A: Phase 2 architecture-impl commits to cocoindex Option A. Budget unchanged.

  • Scenario B: evaluate fall-back paths:

    • Phase 0.7 Stream 2 bespoke build (9-12 weeks)
    • Hybrid (cocoindex for new tables; KH owns content_items via shadow table)
    • Schema redesign for cocoindex ownership (10-14 weeks)

    Decide via separate ratification.

Output: 0.9-spike-S1-cocoindex-schema-coupling.md documenting:

  • Cocoindex version + config
  • Mount attempts + outcomes
  • Constraint / FK / trigger interactions
  • Decision recommendation

Budget: 3-5 days. Author: foreground (main session).


S2 — cocoindex external-folder source binding

Section titled “S2 — cocoindex external-folder source binding”

Status: ✅ COMPLETE S229. G2 outcome = localfs-only v1; live fs-watch (watchfiles 1.1.1) for UC10. v1 connector list confirmed as localfs only; SharePoint / Notion / Dropbox / Box absent from cocoindex 1.0.3.

Outcome: cocoindex 1.0.3 localfs.walk_dir enumerated the 35-file canonical corpus correctly. End-to-end App.update() flow with @cocoindex.fn(memo=True) observer produced the predicted invocation counts across three runs (35 cold / 0 warm / 4 after UC4.4 mutations). Δ semantics: delete purges cache without invoking; rename presents as add+delete pair (no native rename primitive — S188 surface concern shifts to UI layer); binary-replace indistinguishable from edit at engine level. DRAFT-vs-final pairs have fully-distinct content fingerprints — cocoindex does NOT solve UC8 (S10 substrate evaluation must look beyond cocoindex content-hash).

Critical API drift findings (0.8.2-cocoindex-evaluation.md references stale APIs):

  1. cocoindex 1.0.3 renamed sourcesconnectors — import paths in 0.8.2 + S228 spike-plan need refresh.
  2. Dropped @flow_def for App(name, main_fn) — flow-definition pattern is now App-class based.
  3. Swapped SQLite ops-DB for LMDB — and LMDB single-writer constrains multi-worker Cloud Run topology. Needs new spike (S14) OR singleton-orchestrator architecture decision before cocoindex can ship to Cloud Run.
  4. walk_dir defaults to recursive=False — explicit recursive=True required for nested folder corpora.
  5. dangerouslyDisableSandbox required for Rust-engine LMDB startup in dev — CLAUDE.md gotcha addition.

UC10 cadence resolution:

  • localfs → native fs-watch via watchfiles 1.1.1 (FSEvents on macOS / inotify on Linux, 1600ms debounce). Real-time.
  • Remote sources (when added at v1.1+) → scheduled App.update(). SharePoint v1.1 path = out-of-band sync to local mount + localfs binding.

Output: 0.9-spike-S2-cocoindex-folder-binding.md.

Residual questions (downstream spikes):

  • Postgres target binding shape vs KH’s 70-col schema — S1 / G1.
  • Per-row failure isolation under extraction errors — S1 / S8.
  • NEW S14 cocoindex concurrency — LMDB single-writer multi-process behaviour on Cloud Run.
  • DRAFT-vs-final dedup substrate choice — S10 (cocoindex confirmed insufficient).
  • Webhook wiring for any future remote-source connector.

4 errata flagged for downstream docs (per spike output §errata) — defer to next 0.9 synthesis pass.

Original spec preserved for traceability:

S228 update: baseline corpus is docs/client-documentation-base/. Real client-shaped data covering binary docx + final-and-DRAFT pairs + markdown + forms PDF + Excel. Question: Does cocoindex’s localfs.walk_dir(...) source binding handle our actual client content shape? Does it correctly classify add / edit / delete / rename / binary-replace per UC4.4? What polling cadence + webhook strategy works per connector? What’s the v1 connector list?

Method:

  1. Use canonical baseline corpus:

    docs/client-documentation-base/
    ├── binary/ (4 final docx + 3 DRAFT docx + 1 PDF)
    ├── markdown/ (13 numbered .md + 4 audit .md + 3 DRAFT .md + 4 bid library .md)
    └── forms/ (Cloud Security Principles V5.3 .xlsx + Standard Selection Questionnaire .pdf)
  2. Configure cocoindex source binding (API per 0.8.2-cocoindex-evaluation.md line 46):

    from cocoindex.sources import localfs
    source_iter = localfs.walk_dir("docs/client-documentation-base/").items()
  3. Observe initial-ingest behaviour:

    • File enumeration across the 3 sub-folders + binary/markdown/forms shapes
    • Per-file content_hash computation (md5? sha256? file mtime?)
    • Binary files (PDF, DOCX, XLSX) — opaque blob vs pass-through to adapters
  4. NEW S228 — exercise UC4.4 change classification by simulating each kind:

    • Add: copy a new .md file in
    • Edit: modify a .md (atomic + bounded change)
    • Delete: remove a file
    • Rename: move a file (path-changed-hash-equal)
    • Binary-replace: swap a final .docx for its DRAFT (or vice versa) — same scenario captured in S188 audit (docs/audits/user-feedback-from-re-ingestion.md)
    • Verify each is correctly classified by cocoindex’s Δ semantics
  5. NEW S228 — DRAFT-vs-final dedup test (feeds S10 cross-record dedup):

    • Initial ingest of all 4 final + 3 DRAFT docx files
    • Observe whether cocoindex flags DRAFT/final pairs as near-duplicates natively, or needs custom @coco.fn
    • Confirms whether S10 substrate evaluation needs to extend cocoindex or look elsewhere
  6. NEW S228 — UC10 polling cadence + webhook investigation:

    • localfs: fs-watch real-time? polling fallback?
    • SharePoint connector availability + webhook semantics
    • Notion + Google Drive + Dropbox + Box connector status
    • Document the cadence + webhook capability per connector at current cocoindex version
  7. SharePoint connector (if available):

    • Check cocoindex.sources module for SharePoint binding
    • If exists: stub config, observe API auth + change-detection semantics
    • If not exists: document gap; SharePoint becomes v1.1 vs v1
  8. Verify connector list:

    • Read cocoindex docs / connector list
    • Compile authoritative connector list at current version
    • Cross-reference vs v1 requirements (localfs, SharePoint, Notion, Google Drive, Dropbox, Box)

Success criteria:

  • localfs handles all 5 file types in test folder
  • Δ semantics work as expected for all 5 change kinds (add / edit / delete / rename / binary-replace)
  • SharePoint connector present (or roadmap clear)

Decision gate:

  • All 5 file types + SharePoint: v1 connector list = localfs + SharePoint
  • All 5 file types, no SharePoint: v1 = localfs; SharePoint v1.1
  • Connector limitations on file types: flag for adapter-layer fallback (KH’s existing extractors)

Output: 0.9-spike-S2-cocoindex-folder-binding.md

Budget: 2-3 days. Owner: worktree sub-agent (parallel with S1 after S1 day 1).


Status: ✅ COMPLETE S229. G3 outcome = PROCEED with one upstream caveat (mempalace_search broken at runtime — already known per CLAUDE.md gotcha; KH adopts the Shape B response contract regardless).

Outcome: Shape A + B + C + miner all confirmed on live mempalace v3.3.5 + canonical corpus. OQ4 ADOPTION-PROVISIONAL → CONFIRMED for schema patterns + MCP patterns + wing-model. Miner library importable BUT does NOT emit KG triples (mempalace’s mining is verbatim-chunk + Q&A-pair extraction; KH retains Pass 2 classifier for KG triple generation).

Sub-validations:

  • Shape A — 1:1 portable. 4 additive columns to entity_relationships (valid_from, valid_to, confidence, source_content_item_id, adapter_name) + 1 temporal index. Verbatim live DDL captured from ~/.mempalace/knowledge_graph.sqlite3.
  • Shape B — response shapes confirmed via live MCP tool calls (mempalace_kg_query, _list_drawers, _status, _get_taxonomy). Adopt contract; do NOT depend on mempalace_search runtime.
  • Shape C (wing-model) — observed 16 live wings × 8 rooms × 128,534 drawers. Maps cleanly to per-client palace / per-user wing / per-workspace room.
  • Miner — library importable via uv-tool Python; subprocess CLI wrappable. Pip-install rejected for Cloud Run (~600 MB transitive deps).

Key surprises:

  1. mine() returns None (stdout-only, not library-shape).
  2. Miner ≠ Pass 2 classifier (miner doesn’t emit KG triples; KH retains its Pass 2 for that).
  3. chunk_exchanges Q&A pattern only fires on >-quoted conversation transcripts (NOT FAQ markdown — KH keeps the existing docx Q&A adapter for client Q&A library shape).
  4. Palace .corrupt-/.drift- directories evidence v3 stability ceiling — v4-alpha PG backend is the longer-term fix (Q4.6 DEFERRED-POST-LAUNCH stands).

Residual questions:

  • Q4.14 provenance-enum unification — graphify’s confidence-label enum vs mempalace’s freeform adapter_name (S12 spike resolved Q4.14 itself; mempalace integration via adapter_name text field is separate).
  • valid_from semantic source — Pass 2 LLM-emitted vs captured_date default — needs §6.6 / §6.8 implementation detail.
  • v4-alpha PG backend timing.

Output: 0.9-spike-S3-mempalace-observe.md (607 lines).

Original spec preserved for traceability:

Question: What does mempalace’s data flow look like in practice? Are Shape A + B + miner adoption assumptions correct? Method: (1) Install mempalace; (2) Run mempalace mine; (3) Inspect drawers + KG triples + Q&A; (4) Validate Shape A portability; (5) Validate Shape B MCP pattern; (6) Validate miner importability. Success criteria: Shape A 1:1 portable; Shape B matches expectations; miner library-importable OR subprocess-wrappable. Decision gate options: All 3 confirmed (proceed) / mismatch (revise architecture). G3 outcome: All confirmed → PROCEED.

Budget: 1-2 days. Owner: worktree sub-agent. Actual: ~8.5 hours main-session worktree sub-agent.


Status: ✅ COMPLETE S229. G4 outcome = CONDITIONAL PASS — adopt pullmd for HTML / Cloudflare / Google News / Reddit; pre-route PDF URLs to KH’s existing Jina Reader Tier 2.5; drop Firecrawl entirely.

Outcome: pullmd nailed the four shape categories (HTML, Cloudflare-protected, Google News, Reddit) with quality scores at or above KH’s current cascade. The PDF shape failed silently (HTTP 200 + body = %PDF-1.6 ... escaped binary), so v1 architecture routes PDF URLs to Jina Reader before pullmd ever sees them.

Key findings:

  1. PDF silent-binary-garbage failure — pullmd returns HTTP 200 with X-Quality 0.5 for .pdf URLs, body rendered as escaped binary. Naive ingest would store unusable embeddings. Mitigation: add HEAD / .pdf content-type sniff before calling pullmd; route PDFs to Jina Reader Tier 2.5 (KH already wired). Confirms 0.8.4 evaluation’s “no PDF support” with the empirical failure mode.
  2. Reddit is a black-and-white win — KH cascade hard-failed 3/3 r/TeachingUK threads via Firecrawl explicit-deny → summary_fallback (1 word output); pullmd nailed 3/3 with full nested comment trees (X-Source: reddit, q=1.0, 677-2257 words). This capability is currently impossible in KH without pullmd. Justifies adoption independent of the other shape categories.
  3. Cloudflare argument weaker than 0.8.4 framed — KH’s plain fetch already handles cf-cache-status: DYNAMIC sites like schoolsweek.co.uk (doesn’t trigger anti-bot at v1 volume). Pullmd still wins on the 1/10 that needed Playwright escalation, but KH isn’t silently broken on Cloudflare today.
  4. KH prod has 0 PDF URLs and 0 Reddit URLs in 95 url_import / rss_feed rows — despite both being v1 target shapes per architecture. Suggests silent ingest failure OR users haven’t tried. Trace pre-launch.
  5. pip install pullmd-cli does NOT exist — README documents only Docker compose / npm-from-source / pre-built Docker image. Spike-plan’s original install instruction was incorrect; v1 deployment uses Docker.
  6. Cosmetic locale leak — pullmd’s Reddit output uses German ## Kommentare (50 von 64) heading (Docker container locale). Configurable; verify pre-v1.

Residual questions for §10 architecture rewrite (sub-agent B’s output) + downstream synthesis:

  • S4-Q1: PDF-Jina-preserved hybrid wording for §10 — clarify pullmd is HTML/Cloudflare/GN/Reddit; Jina is PDF; both are pre-cocoindex adapter tier.
  • S4-Q2: Confirm “drop Firecrawl entirely” — collapse-candidates §7 already lists Firecrawl as CONDITIONAL; S4 promotes to DECIDED-RETIRE.
  • S4-Q3: Why 0 PDF + 0 Reddit in prod? Trace tooling (silent ingest path) — actionable bug investigation.
  • S4-Q5: X-Quality < 0.6 as extraction_quality_log warning threshold — needs schema column + classifier integration.
  • S4-Q8: Re-bake on non-Phew tenant when available — Phew’s URL corpus skews to UK-education sources; cross-tenant validation pre-v1.

Artifacts: 0.9-spike-S4-pullmd-bakeoff.md (398 lines) + raw data at /tmp/claude/pullmd-spike/ (temporary).

Original spec preserved for traceability:

Question: Does pullmd extraction quality match or exceed current Readability + Firecrawl + Playwright cascade? Method: Sample 50 URLs (20 HTML + 10 Cloudflare + 10 Google News + 5 PDF + 5 Reddit); run through Path A (pullmd) / Path B (KH cascade) / Path C (ground truth, 10 URLs). Success criteria: quality score ≥ cascade aggregate; ≥ 90% URL types handled. Decision gate options: Pass (adopt) / Fail-on-Cloudflare (keep agent-browser Tier 2.5) / Fail-on-completeness (evaluate fork). G4 outcome: CONDITIONAL PASS — adopt for HTML/CF/GN/Reddit; PDF pre-route to Jina; drop Firecrawl.

Budget: 1 day. Owner: worktree sub-agent. Actual: ~16.7 hours main-session worktree sub-agent.


S5 — skill-seekers dependency_analyzer.py on KH dev codebase

Section titled “S5 — skill-seekers dependency_analyzer.py on KH dev codebase”

Status: ✅ COMPLETE S229. G5 outcome = SKIP (do not adopt). Stay on knip + graphify two-detector stack.

Outcome: observed 0% non-overlapping true positives vs the 20% success criterion. Analyzer extracted 786 import statements across 250 files but resolved 0 to internal nodes (250 nodes, 0 edges in output graph). Root cause: _resolve_import() does literal string-match only — no tsconfig @/ path-alias expansion, no relative-path joining, no Python package resolution. The tool is designed for Claude Skill references/*.md generation, not dev workflow.

Key findings:

  1. No standalone CLI — invocation requires Python wrapper importing skill_seekers.cli.dependency_analyzer.DependencyAnalyzer.
  2. 0.8.5 §Q5’s “useful supplement” verdict was inferred from docstring, not empirical — this spike contradicts it. Correction note recommended for next 0.9 synthesis.
  3. 50+ transitive deps (langchain, llama-index, pymupdf, gitpython, sqlalchemy, schedule, …) for a 975-LOC analyser with one real dep (networkx). Poor footprint trade.
  4. The hypothesised TS↔Python call-gap use case isn’t solved by import-graph tools anyway — those gaps are process-boundary (child_process.spawn), not import statements.

Output: 0.9-spike-S5-dep-analyzer.md (244 lines).

Original spec preserved for traceability:

Question: Does dependency_analyzer.py surface useful signals for KH dev workflow that aren’t already in knip + graphify? Method: (1) Install skill-seekers; (2) Run dependency_analyzer on KH; (3) Compare vs knip + graphify + ESLint unused-imports; (4) Count signal classes (TP / FP / cross-class drift). Success criteria: ≥ 20% non-overlapping true positives vs knip + graphify. Observed 0%. Decision gate options: Pass (adopt) / Fail (skip). G5 outcome: Fail → SKIP.

Budget: 0.5 day. Owner: foreground. Actual: ~6.5 hours main-session worktree sub-agent.


S6 — mcp-scan feasibility + cost (re-framed S228)

Section titled “S6 — mcp-scan feasibility + cost (re-framed S228)”

Status: ✅ COMPLETE S229. G6 outcome = PASS — v1 CI step adopted (inspect mode local-only); v2 cloud-upload mode deferred pending data-residency sign-off + v1 stability.

Outcome: Tool runs cleanly against KH’s MCP surface via local stdio bridge. False-positive rate post-allow-list ≈ 0-1 per scan (W001 prompt-injection word matches in lib/mcp/tools/{content,bids,governance}.ts for KH-domain terms like “override” / “previous” / “ignored otherwise” — all suppressible via ignore-list). No FP-flood.

Key actionable findings:

  1. Live staging scan blocked by Vercel SSO — tool tries 6 transport variants, all hit 401 from Vercel deployment-protection before reaching the MCP OAuth challenge. v1 mitigation: scan via local stdio bridge (scripts/mcp-scan/serve-fixture.ts re-exporting registerTools).
  2. Package renamed: mcp-scansnyk-agent-scan v0.5.1 (install via pipx install snyk-agent-scan). Spike-plan §S6 install line was stale.
  3. scan mode posts to api.snyk.io with SNYK_TOKEN; inspect mode is purely local. v1 uses inspect only — no cloud dependency, no data egress.
  4. Net-new coverage vs /security-review skill: W001 (prompt-injection words), W015-W018 (schema), destructive/public-sink labels, TF* toxic-flow patterns (v2 cloud-only).
  5. ⚠️ SECURITY GOTCHA — CLI dumps env-var values verbatim in JSON when run with --scan-all-users (auto-discovery mode). Empirical observation during spike: surfaced 10 local stdio MCP configs incl. GITHUB_TOKEN / SUPABASE_ACCESS_TOKEN / Stripe live key. Adoption rule: NEVER use --scan-all-users in CI. Add redaction wrapper before any v2 cloud upload. This is a CLAUDE.md gotcha candidate.

v1 adoption cost: ~0.5 day (CI job + stdio bridge scripts/mcp-scan/serve-fixture.ts + ignore-list config + runbook). v2 adoption cost: ~2-3 days (cloud upload + redaction wrapper + data-residency sign-off + TF* toxic-flow integration) — gated on v1 stability + sign-off.

Output: 0.9-spike-S6-mcp-scan.md (325 lines).

Original spec preserved for traceability:

S228 update: OQ5 is RATIFIED-YES per Liam — adopt mcp-scan. Spike confirms feasibility + adoption cost shape. Question: Does mcp-scan run cleanly against KH’s MCP tool surface? What’s the false-positive rate? What’s the v1-vs-v2 split? Method: (1) Install; (2) Run against KH MCP staging; (3) Review findings (prompt-injection / schema / exfiltration / auth gaps); (4) Cross-reference vs /security-review. Decision gate options: Pass (CI step) / Fail (FP flood) / Findings worth fixing (issues regardless of CI). G6 outcome: Pass → v1 CI inspect-mode + v2 cloud deferred.

Budget: 0.5 day. Owner: foreground. Actual: ~13.5 hours main-session worktree sub-agent.


S7 — Pre-re-ingest evaluation (graphify data + CSP checklist forms)

Section titled “S7 — Pre-re-ingest evaluation (graphify data + CSP checklist forms)”

S228 update: primary eval target is now the CSP checklist Excel + Standard Selection Questionnaire PDF in docs/client-documentation-base/forms/ — these are real client artefacts the platform should auto-complete with citations. Baseline question set still includes graphify comparison.

Question: (a) Can we evaluate KH’s current value-delivery vs graphify-ingested value WITHOUT running a new full ingest? (b) Does the platform auto-complete most fields of the CSP checklist with appropriate citations against current corpus + graphify-ingested context?

Method:

  1. Form-based eval (NEW S228 — primary):

    • Load Cloud Security Principles Checklist V5_3 - PHEW.xlsx — extract questions
    • Run each question via current KH MCP search tools + graphify-baseline data + Anthropic citations API
    • For each question, score: faithfulness / completeness / citation-presence / coverage-vs-gap
    • Output: per-question-with-citation auto-complete; gap list for questions with no citable answer
  2. Question-set eval (legacy S227 framing, secondary):

    • 10-20 client-relevant questions from LBBD-CSP context:
      • PCI-DSS policy questions
      • Production-infrastructure questions (where Bitdefender false-positive happened)
      • FUNC-XXX feature questions
      • Sample bid Q&A pairs
      • Cross-domain questions (security + infrastructure)
  3. Run each via current KH MCP search:

    • Use search_knowledge_base + search_qa_library MCP tools
    • Capture: top-5 results, similarity scores, citations
  4. Run each against graphify-baseline data:

    • Query code-and-docs-with-llm graph (latest run, $18, 22,155 isolated nodes)
    • Use graphify’s query MCP if installed; else direct Cypher / GraphML inspection
    • Capture: relevant nodes + edges + paths
  5. Score per-question:

    • Faithfulness (how true to source)
    • Completeness (how much of relevant context surfaced)
    • Citations (how clear is provenance)
    • Time-to-answer (manual stopwatch)
  6. Compute aggregate delta: graphify-ingested vs current KH.

Success criteria:

  • Score delta is informative (one shape clearly outperforms; or both have specific strengths)

Decision gate:

  • Graphify-ingested wins clearly: urgency to ship canonical pipeline (informs Phase 2 priority)
  • Current KH wins clearly: less urgency on canonical pipeline; prioritise other work
  • Mixed: identify specific shape strengths; informs canonical pipeline emphasis

Output: 0.9-spike-S7-current-vs-graphify-eval.md with per-question scoring + aggregate + recommendation.

Budget: 1 day. Owner: foreground or worktree sub-agent.


S8 — Q&A flow validation (end-to-end test)

Section titled “S8 — Q&A flow validation (end-to-end test)”

S228 update: test corpus is docs/client-documentation-base/ (replaces ad-hoc fixtures); also exercises UC8 cross-record dedup via DRAFT-vs-final pairs + UC4.4 binary-replace path.

Question: Does the §6.2 Q&A flow architecture work end-to-end on the canonical baseline corpus? Does the flow handle DRAFT-vs-final + Track-Changes + form-based question-extraction correctly?

Why: the Q&A separation + extraction + curation + retrieval architecture is novel. Validate before committing to Phase 2-3 build.

Method:

  1. Pre-spike setup (post-S1 cocoindex schema confirmation + post edit-flow §6 architectural decision):

    • Apply q_a_pairs + q_a_extractions + citations migrations on cocoindex-spike branch
    • Set up cocoindex flow with mempalace miner integration
    • Configure cocoindex source binding to docs/client-documentation-base/
  2. Test corpus (canonical baseline):

    • binary/ — 4 final docx + 3 DRAFT docx (exercises track-changes + DRAFT-vs-final dedup)
    • markdown/ — 13 numbered .md (Phew company corpus) + 4 audit .md + 3 DRAFT .md (Q&A heading-based) + 4 bid library .md
    • forms/ — CSP Excel + SSQ PDF (exercises form question-extraction per UC8 + S7)
  3. Execute end-to-end:

    • Trigger ingest via cocoindex
    • Verify q_a_extractions populated (mempalace miner output)
    • Manually curate 2-3 extractions → q_a_pairs
    • Run search_qa_library against test queries
    • Verify retrieval chain (q_a_pairs → q_a_extractions → corpus chunks)
    • Verify citations table populated correctly
  4. Test edge cases:

    • Source content updated (mempalace miner re-runs; q_a_extractions invalidated)
    • q_a_pair valid_to expires (search returns warning)
    • Bid response promoted to q_a_pair (origin_kind tracking)

Success criteria:

  • All 4 ingest paths populate q_a_extractions correctly
  • Curation flow promotes to q_a_pairs cleanly
  • Retrieval chain works (high → low confidence fallback)
  • Edge cases handled

Decision gate:

  • Pass: Phase 2-3 architecture-impl commits to design as documented
  • Fail in retrieval chain: revise §6.2 retrieval logic before Phase 3
  • Fail in extraction: revise mempalace miner integration

Output: 0.9-spike-S8-qa-flow-validation.md

Budget: 1-2 days. Owner: foreground (post-S1).


S9 (NEW S228) — Write-back semantics validation

Section titled “S9 (NEW S228) — Write-back semantics validation”

Gates on: edit-flow investigation §6 architectural decision (UC1 / UC4 Candidate A vs B vs C). Cannot start until §6 lands.

Question: Does the chosen Candidate (A — in-platform editor + filesystem write / B — Claude API text-editor-tool / C — Managed Agent) correctly implement UC1 typo fix end-to-end? Does cocoindex skip flow re-run when content-hash unchanged from prior write (idempotency)? Can Yjs collab plugin enable cleanly on existing Tiptap Q&A ContentEditor?

Method:

  1. Implement chosen Candidate against docs/client-documentation-base/markdown/ test file.
  2. UC1 walk-through: open editor → make typo fix → save → verify both folder file + materialised representation reflect change → verify cocoindex’s response on next scan/webhook.
  3. Cocoindex idempotency test: check if engine skips flow run when input-hash matches its expected output-hash. If not, design marker-file or write-window-lock workaround.
  4. Yjs collab plugin enable: mount Yjs awareness on Tiptap Q&A ContentEditor; verify two simultaneous edit sessions converge cleanly. ~1 day work per §9.8 of edit-flow doc.
  5. Sidecar pattern (binary): test write-back to a markdown sidecar adjacent to a binary docx file; verify cocoindex picks up sidecar change correctly.

Success criteria:

  • UC1 flow works end-to-end without manual intervention
  • Cocoindex idempotency confirmed OR clean workaround in place
  • Yjs collab plugin enables with no Q&A editor regression
  • Sidecar pattern integrates with cocoindex source binding

Decision gate:

  • Pass: chosen Candidate is the v1 implementation; UC4/UC6/UC7+10 inherit
  • Fail in idempotency: revisit Candidate B or C (where DB write is downstream of file write, dissolves idempotency concern)
  • Fail in Yjs: defer collab; concurrent-user conflict goes to git-style or Claude API tool use as alternate

Output: 0.9-spike-S9-write-back-semantics.md

Budget: 2-3 days. Owner: foreground (post-edit-flow §6).


S10 (NEW S228) — Cross-record dedup substrate (UC8)

Section titled “S10 (NEW S228) — Cross-record dedup substrate (UC8)”

Gates on: S2 + S3 (need cocoindex + mempalace observed before substrate evaluation).

Question: Which substrate handles cross-record dedup best for KH’s needs (DRAFT-vs-final detection per S188 + near-duplicate Q&A across bid library re-imports + collapse-N-to-golden-record per UC8)? Three candidates per §10 of edit-flow investigation:

  • (a) Cocoindex post-target-write @coco.fn — content-hash similarity + Postgres similarity search
  • (b) Mempalace KG entity-resolution — name + property + temporal-overlap matching
  • (c) Skill-seekers metadata categorisation — keyword + categories pattern (likely inspiration only per §2.4 verdict)

Method:

  1. Test corpus: docs/client-documentation-base/binary/ DRAFT-vs-final docx pairs (3 pairs known to overlap-but-not-identical) + markdown/ bid library variants (4 versions of similar content).
  2. Configure each substrate against the corpus:
    • (a) Cocoindex: implement post-target-write @coco.fn computing content-similarity + chunk-similarity
    • (b) Mempalace: load corpus through mempalace mine, observe KG entity-resolution output
    • (c) Skill-seekers: run categories keyword match (sanity baseline)
  3. Score per-pair detection accuracy:
    • True-positive rate (catches the known DRAFT-vs-final pairs)
    • False-positive rate (incorrectly flags genuinely-different content as duplicates)
    • Latency / compute cost
  4. Evaluate UX shape compatibility — which substrate naturally surfaces side-by-side diff, golden-record selection, citation-preservation?

Success criteria:

  • ≥1 substrate achieves ≥90% true-positive on known pairs at ≤5% false-positive
  • UX shape supports user-approve-merge flow per UC8

Decision gate:

  • Cocoindex wins: S228 reframe — UC8 substrate is cocoindex; mempalace-as-supporting (audit lineage); skill-seekers retired as substrate candidate
  • Mempalace wins: UC8 substrate is mempalace KG; cocoindex feeds; skill-seekers retired
  • Hybrid wins: combine cocoindex (content-hash) + mempalace (entity overlap) — design composition
  • None wins: UC8 architecture defers; v1 ships without cross-record dedup; user-driven dedup only

Output: 0.9-spike-S10-dedup-substrate.md

Budget: 2-3 days. Owner: worktree sub-agent (after S2 + S3).


S11 (NEW S228) — Playwright swap with agent-browser (OQ6 RE-FRAMED)

Section titled “S11 (NEW S228) — Playwright swap with agent-browser (OQ6 RE-FRAMED)”

Status: ✅ COMPLETE S229. G11 outcome = KEEP Playwright. OQ6 / CX.7 RESOLVED-NO-SWAP.

Outcome: Playwright 20/20 URLs extracted; agent-browser 14/20 (6 failures: 4 Cloudflare bot-detection + 1 SPA-hydration empty body + 1 networkidle hang). Body completeness on URLs both succeed on: agent-browser captures mean 0.81 of Playwright’s word count — fails the ≥-Playwright success criterion. Median latency tied (~2.1s); Playwright mean higher only due to 3 URLs hitting its 15s networkidle cap (which gracefully fell through). Setup overhead penalty: sandbox-incompatible, ANSI/multiline output parsing, global install — without removing Playwright (still needed for KH E2E).

Critical re-framing finding (worth flagging in 0.9 synthesis): the original wave-08 OQ6 framing was about a Playwright tier that does not exist in lib/intelligence/content-extractor.ts (its cascade is RSS → fetch → Jina → Firecrawl). The actual Playwright surface is pullmd’s Tier-3 sidecar — so this spike only matters conditional on S4 pullmd adoption (which landed: CONDITIONAL PASS). Architecture sections §10.1 / §10.2 should clarify the Playwright surface lives in pullmd’s Tier-3, not in KH’s own cascade.

Key surprises:

  1. The original wave-08 OQ6 framing was about a Playwright tier that doesn’t exist in KH’s own extraction cascade.
  2. agent-browser triggers Cloudflare bot-detection on 20% of the sample where Playwright does not — a categorical capability gap unrelated to wait-strategy.
  3. Spike-plan §7 risk-register mitigation “Keep agent-browser as Tier 2.5 fallback” is wrong and should be reversed (Playwright keeps the role; agent-browser is not adopted as a tier).

Residual questions:

  • Sample-size sensitivity (20 → 50 URLs would harden the completeness ratio); a prod-corpus draw (live RSS URLs where method='firecrawl') would improve external validity.
  • agent-browser stealth posture may improve in future versions — re-evaluate annually.
  • pullmd Tier-3 fire-rate not measured — if Cloudflare-native short-circuit handles ≥ 95% of URLs before Tier-3 fires, the swap-cost-impact is negligible regardless.

Output: 0.9-spike-S11-playwright-swap.md (302 lines).

Original spec preserved for traceability:

S228 critical re-framing: original wave-08 OQ3 was about swapping Playwright with agent-browser, NOT Firecrawl (per Liam S228 verbatim correction). Question: Can agent-browser replace Playwright as the JS-rendered fallback in pullmd’s tier 2.5 / 3 cascade? What’s the comparative quality + latency + cost? Method: Sample 20 URLs requiring JS rendering; run Path A (Playwright) / Path B (agent-browser) / Path C (ground truth). Decision gate options: Pass (swap) / Fail (keep Playwright). G11 outcome: Fail → KEEP Playwright; OQ6 closed as no-swap.

Budget: 1 day. Owner: worktree sub-agent. Actual: ~19.9 hours main-session worktree sub-agent.


S12 (NEW S228) — Graphify confidence-label taxonomy adopt (Q4.14)

Section titled “S12 (NEW S228) — Graphify confidence-label taxonomy adopt (Q4.14)”

Status: ✅ COMPLETE S229. G12 outcome = EXTEND-EXISTING-PROPER (revised framing). Q4.14 RESOLVED-ADOPT per S228 decision-graph; spike confirmed schema-integration shape.

Outcome (replaces original extend-vs-replace binary): the spike’s original framing was moot — graphify’s three confidence labels and Q4.7’s three KG-provenance enum values are literally the same set: EXTRACTED, INFERRED, AMBIGUOUS. Zero gaps, zero redundancies. 1:1 identity. Both feedback streams (mempalace wave-08 → Q4.7; graphify §5.2.C → Q4.14) independently converged on the same three-value taxonomy.

Adopted shape:

  • Single new column provenance text NOT NULL DEFAULT 'EXTRACTED' CHECK (...) on entity_relationships — CHECK pattern matches the table’s existing relationship_type precedent.
  • Repurpose existing confidence numeric(3,2) from placeholder-hardcoded-1.0 to graphify’s discrete confidence_score rubric value (1.0 / {0.55-0.95} / 0.1-0.3 by label).
  • Update Pass 2 classifier (lib/ai/classify.ts:1772-1793) to emit both fields.
  • No backfill needed — pre-launch re-ingest (CX.22) wipes the table.

Output: 0.9-spike-S12-graphify-confidence.md (286 lines).

Original spec preserved for traceability:

Question: Does graphify’s confidence-label taxonomy fit alongside the KH-native KG-provenance enum (Q4.7) without semantic collision? What’s the schema-integration shape? Method: (1) Read graphify’s confidence-label taxonomy spec; (2) Map each label to a Q4.7 value; (3) Identify gaps + redundancies; (4) Propose schema: extend OR replace. Decision gate options: Extend (keep Q4.7 + add graphify column) / Replace (Q4.7 becomes graphify-aligned). G12 outcome resolved the binary into a single-column extend pattern using verbatim Q4.7 enum.

Budget: 0.5 day. Owner: foreground. Actual: ~6 hours main-session worktree sub-agent.


S13 (NEW S228) — ESLint input-required rule (DW.13)

Section titled “S13 (NEW S228) — ESLint input-required rule (DW.13)”

Status: ✅ COMPLETE S229. G13 outcome = ADOPT-ADVISORY (warn). Wire rule into quality-precheck at warn severity (not error); promote to error after focused remediation of the 6 known handler-level gaps.

Outcome: Custom ESLint rule eslint-rules/no-unvalidated-route-input.js (244 lines) prototyped + registered in eslint-rules/index.js (NOT yet wired into eslint.config.mjs per spike constraint).

Audit coverage stats (193 routes total):

  • 134 routes (69.4%) use BOTH Zod-helper (parseBody / parseSearchParams / etc.) + auth helper.
  • 4 routes (2.1%) Zod-helper only.
  • 50 routes (25.9%) auth helper only.
  • 5 routes (2.6%) neither — all legitimate (cron secret / OAuth / health / static).
  • Body consumption Zod coverage: 100%. Query consumption Zod coverage: 100%.
  • Handler-level path-param gaps: 6 handlers across 5 files (rule found all 6; grep-audit only caught 3 file-level + missed 2 mixed-handler cases — empirical validation of the rule’s incremental value over grep).

FP/TP rate:

  • v1 (UUID-only regex whitelist): 6 TP / 3 FP (66.7% / 33.3%) — failed the 80%/10% criterion.
  • v2 (regex-naming convention *_RE | *_REGEX | *Regex): 6 TP / 0 FP (100% / 0%) — passes criteria.

Overhead: baseline 23.151s → with rule 23.214s = +63ms (vs 10s budget). Two orders of magnitude headroom.

Wiring instructions in spike doc §7. Rule file at eslint-rules/no-unvalidated-route-input.js; registered in eslint-rules/index.js.

Output: 0.9-spike-S13-eslint-input-required.md.

Original spec preserved for traceability:

S228 V1 finding caveat: KH currently uses the getAuthorisedClient() discriminated-union pattern (per CLAUDE.md), NOT tRPC-shaped Zod. Spike must FIRST audit current input-validation pattern coverage, THEN target the rule against KH’s actual idiom. Question: What’s KH’s actual input-validation pattern coverage across app/api/? Where coverage is missing, can a custom ESLint rule flag it without false-positive flood? What’s the precheck overhead? Method: (1) Audit pattern coverage; (2) Identify gaps; (3) Implement rule; (4) Measure FP/TP rate; (5) Measure overhead. Decision gate options: Pass (precheck) / Fail (defer) / Pass-with-overhead-concern (advisory) / Audit-adequate (RESOLVED-NOT-NEEDED). G13 outcome: Pass-with-overhead-concern → ADVISORY (warn).

Budget: 0.5 day. Owner: foreground. Actual: ~11 hours main-session worktree sub-agent.


S14 (NEW S229) — Cocoindex concurrency under LMDB single-writer

Section titled “S14 (NEW S229) — Cocoindex concurrency under LMDB single-writer”

Status: ✅ COMPLETE S230. G14 outcome = PASSED — single-orchestrator-instance with isolated per-instance LMDB. S2’s “LMDB single-writer hard-blocks concurrent processes” framing dissolved empirically: 10 concurrent App.update() writers against the same LMDB completed cleanly (10/10 success, no lock errors, post-run memo coherent). LMDB IS transaction-level single-writer, but cocoindex’s per-row writes are short — concurrent processes interleave at the lock without contention-induced failure. The real cost shape is “N concurrent processes do ~N× the @coco.fn work” (no cross-process dedup), not “lock contention failure”. Read-only consumer pattern (MVCC) works. Crash-recovery (SIGKILL writer mid-flight) works.

Adopted topology:

  • v1 Cloud Run cocoindex job: min_instances=1, max_instances=1 OR scheduled Cloud Run job. Each instance gets its own ephemeral LMDB. Re-fingerprint cost per cold-start (~7s for 35-file canonical corpus; scales linearly to minutes for full client corpus) — acceptable at v1 scale; no queue infra needed.
  • Postgres-as-ops-DB option does NOT exist in cocoindex 1.0.3 (Rust binary inspection confirmed). Postgres is target-only.
  • Reversibility excellent — schema/data/target bindings unchanged if v1.1 demands multi-writer.

Output: 0.9-spike-S14-cocoindex-concurrency.md.

Errata: S2 §4.5 + §6.1 surprise 5 framing of “LMDB single-process semantics blocks multi-worker” is overcautious; the actual constraint is “wasted compute via uncoordinated parallel work”, not “blocked writers”. Correction note in S14 §6.2 recommends downstream doc updates.

Original spec preserved for traceability:

S2 observed cocoindex 1.0.3 swapped its SQLite ops-DB for LMDB; LMDB is single-writer at the process level. This constrains cocoindex’s multi-worker topology, which directly affects Cloud Run deployment (where horizontal scaling is the default model).

Question: What Cloud Run deployment topology supports cocoindex’s LMDB ops-DB at v1 throughput? Three candidates:

  • (a) Single-orchestrator-instance — exactly one cocoindex process at a time (vertical scale only); other instances are read-only consumers of the materialised representation. Lowest risk; throughput ceiling = single-instance LMDB write rate.
  • (b) Queue-based serialisation — workers enqueue change events; a designated orchestrator-instance dequeues + runs cocoindex flow. Adds queue infra (Cloud Tasks / Pub/Sub) but preserves multi-instance UI / API layer.
  • (c) Read-only-worker fan-out — read replicas of LMDB (if cocoindex supports) for query-side workers; writes funnel through one orchestrator. Closest to existing Cloud Run ergonomics; requires LMDB read-replica support (verify in spike).

Why this gates Phase 2: if (a), cocoindex’s runtime footprint requires re-thinking Cloud Run autoscaling for KH’s pipeline tier. If (b), additional infra cost. If (c), simplest but unverified.

Method:

  1. Verify LMDB ops-DB locking semantics under concurrent App.update() invocations (multi-process test on a single host first; then container-isolated test simulating Cloud Run).
  2. Test cocoindex 1.0.3’s behaviour when two instances attempt simultaneous update() against the same Postgres target — does the second block / fail / corrupt?
  3. Test queue-based pattern: one orchestrator + N workers, workers POST change events, orchestrator drains.
  4. Test read-only-worker pattern: orchestrator writes LMDB; workers read LMDB-as-state (verify LMDB supports concurrent read from separate process).

Success criteria:

  • At least one topology supports KH’s projected v1 throughput (~10-50 concurrent flow events at peak based on cocoindex-eval estimates).
  • Topology decision is reversible if v1.1 cocoindex releases multi-writer support.

Decision gate:

  • Single-orchestrator wins: v1 Cloud Run topology = vertical-scale orchestrator + horizontal-scale API/UI tier. Document in 0.9-intended-architecture.md §10/§11 deployment section.
  • Queue-based wins: add Cloud Tasks / Pub/Sub to v1 infra stack.
  • Read-only-worker wins: verify LMDB cross-process read support; design orchestrator/worker split.
  • None viable: escalate — cocoindex Cloud Run viability becomes a blocker; fall-back paths from S1 / G1 apply.

Output: 0.9-spike-S14-cocoindex-concurrency.md (NEW — to be created post-spike).

Budget: 1 day. Owner: worktree sub-agent or foreground. Sequencing: after S2 (gates on S2’s LMDB finding); ideally before or in parallel with S1 (since S1’s schema-coupling outcome interacts with topology choice).


Day 1: S1 cocoindex schema-coupling [start, foreground]
S5 skill-seekers dep_analyzer [foreground]
S6 mcp-scan feasibility [foreground]
S7 pre-re-ingest eval [start, foreground or sub-agent]
S12 graphify confidence-label [foreground]
S13 ESLint input-required [foreground]
Day 2: S1 [continue]
S2 cocoindex folder binding (using docs/client-documentation-base/) [start, sub-agent]
S3 mempalace observe [start, sub-agent]
S4 pullmd bake-off [start, sub-agent]
S11 Playwright swap with agent-browser [start, sub-agent]
S7 [continue]
Day 3: S1 [continue / decide]
S2 [continue]
S3 [decide]
S4 [decide]
S7 [decide]
S11 [decide]
Day 4: S1 decision gate
S2 decision gate (UC10 polling cadence per connector)
S10 cross-record dedup substrate [start, sub-agent — gated on S2+S3]
Day 5-6: S10 [decide]
Edit-flow §6 architectural decision (parallel main-session work, NOT a spike)
Day 7-8: S8 Q&A flow validation [start, post-S1, post-edit-flow §6]
S9 write-back semantics [start, post-edit-flow §6]
Day 9-10: S8 + S9 [continue / decide]
Spike results doc + Phase 2 commit decision

Wall-clock: 8-10 days with good parallel utilisation. Single-session can absorb most spikes on 1M context window. S9 + S10 + S8 are sequenced tail; the rest are mostly day-1-or-2 starts.

Architecture documentation rewrite (sub-agents B + C — task #9 + #10) can run IN PARALLEL with most spikes (per Liam S228 unlock direction). Only S8 + S9 need edit-flow §6 to land first.


GateSource spikeDecision
G1: cocoindex Scenario A vs BS1Phase 2 commit OR fall-back to Phase 0.7 Stream 2
G2: v1 connector list + UC10 polling cadenceS2LocalFS only / LocalFS + SharePoint / fallback paths; per-connector polling vs webhook
G3: mempalace adoption shape confirmedS3Schema migration OR architecture revision
G4: pullmd adoptionS4Adopt OR keep current cascade
G5: skill-seekers dep_analyzerS5Adopt as 3rd BNW-detector OR skip
G6: mcp-scan v1-vs-v2 splitS6CI step now / deeper integration later
G7: re-ingest urgency + form auto-completion baselineS7Phase 2 priority calibration; CSP checklist coverage gap-list
G8: Q&A flow architecture confirmedS8Phase 2-3 commit OR architecture revision
G9 (NEW): write-back semantics validatedS9Candidate A/B/C choice locked for v1; Yjs collab plugin enable confirmed
G10 (NEW): cross-record dedup substrateS10Cocoindex / mempalace / hybrid / defer
G11 (NEW): Playwright swap with agent-browserS11Adopt swap OR keep Playwright
G12 (NEW): graphify confidence-label schema integrationS12Extend Q4.7 enum OR replace with graphify-aligned
G13 (NEW): ESLint input-required adoptionS13Add to quality-precheck / advisory-only / defer

Phase 2 commit requires: G1 (Scenario A) + G3 (mempalace OK) + G8 (Q&A flow validated) + G9 (write-back path chosen). G2, G4-G7, G10-G13 are calibration, not blocking gates.

If G1 = Scenario B:

  • Architecture revision needed
  • Fall-back evaluation: Phase 0.7 Stream 2 (9-12 weeks) vs hybrid (cocoindex new tables + KH owns shadow) vs schema redesign for cocoindex ownership
  • New ratification cycle before Phase 2 commit

If G10 = no substrate clearly wins: v1 ships without cross-record dedup; user-driven dedup only via UC8 surface; substrate decision deferred to v1.1.


After spike phase completion:

FileContents
docs/plans/phase-0-investigation/0.9-spike-S1-cocoindex-schema-coupling.mdS1 detail + Scenario A/B decision
docs/plans/phase-0-investigation/0.9-spike-S2-cocoindex-folder-binding.mdS2 detail + connector list + UC10 polling cadence
docs/plans/phase-0-investigation/0.9-spike-S3-mempalace-observe.mdS3 detail + adoption confirmation
docs/plans/phase-0-investigation/0.9-spike-S4-pullmd-bakeoff.mdS4 detail + per-URL scoring
docs/plans/phase-0-investigation/0.9-spike-S5-dep-analyzer.mdS5 detail + adoption decision
docs/plans/phase-0-investigation/0.9-spike-S6-mcp-scan.mdS6 detail + v1-vs-v2 split
docs/plans/phase-0-investigation/0.9-spike-S7-current-vs-graphify-eval.mdS7 detail + per-question scoring + CSP coverage gap-list
docs/plans/phase-0-investigation/0.9-spike-S8-qa-flow-validation.mdS8 detail + architecture confirmation
docs/plans/phase-0-investigation/0.9-spike-S9-write-back-semantics.mdNEW — S9 detail + Candidate A/B/C choice + Yjs enable confirmation
docs/plans/phase-0-investigation/0.9-spike-S10-dedup-substrate.mdNEW — S10 detail + dedup substrate decision
docs/plans/phase-0-investigation/0.9-spike-S11-playwright-swap.mdNEW — S11 detail + agent-browser swap decision
docs/plans/phase-0-investigation/0.9-spike-S12-graphify-confidence.mdNEW — S12 detail + Q4.14 schema integration
docs/plans/phase-0-investigation/0.9-spike-S13-eslint-input-required.mdNEW — S13 detail + ESLint rule adoption
docs/plans/phase-0-investigation/0.9-spike-results.mdMaster rollup + Phase 2 commit decision

6. Pre-spike preparation checklist (S228 update)

Section titled “6. Pre-spike preparation checklist (S228 update)”

Before next session starts spike work:

  • User ratified S228 OQ states per 0.9-context.md §2 (decision-graph + intended-architecture + spike-plan all reflect ratifications)
  • Canonical baseline corpus in place at docs/client-documentation-base/ (replaces /tmp/cocoindex-spike/)
  • Mempalace MCP registered in all 3 long-lived worktrees (S228 fix)
  • User confirms spike phase budget (~14-18 days mostly parallel — increased from ~6-8 days due to 5 new spikes)
  • Cocoindex spike branch created on Supabase (/opt/homebrew/bin/supabase branches create cocoindex-spike --base staging)
  • mempalace + skill-seekers + mcp-scan + pullmd + agent-browser tools installed (Python + node packages)
  • NEW S228 (V1 finding) — Yjs collab stack added to package.json: yjs, y-prosemirror, @tiptap/extension-collaboration (currently NOT installed; package.json has only base Tiptap 3.22.0 starter-kit). Required for S9 write-back semantics + UC1/UC4/UC6 in-platform editor concurrent-user CRDT.
  • Worktree-isolated sub-agent prompts pre-drafted for S2 + S3 + S4 + S10 + S11
  • Edit-flow investigation §6 architectural decision landed (gates S8 + S9)
  • Architecture documentation rewrite (sub-agent B for 0.9-intended-architecture.md; sub-agent C for any spike-plan post-spike updates) — runs in PARALLEL with spike phase per S228 unlock direction

RiskLikelihoodMitigation
Cocoindex Scenario B (engine demands schema ownership)MediumArchitecture revision path documented (§4 G1); fall-back to Phase 0.7 Stream 2
Mempalace miner is CLI-only (subprocess overhead)LowConfirmed in S3; cocoindex flow can subprocess-wrap
pullmd quality miss on Cloudflare-protected URLsResolved S229 — LowS4 spike confirmed pullmd handles Cloudflare-protected URLs at parity-or-better with KH’s current cascade. S11 confirmed agent-browser is not a viable fallback (triggers Cloudflare bot-detection on 20% of sample where Playwright does not). Mitigation reversed: keep Playwright (inside pullmd’s Tier-3 sidecar) as fallback, NOT agent-browser.
Sub-agent context budget exhaustedLow (limit may be >200K per S228 observation; needs verification)Brief sub-agents tightly; cap at ~150K explicit-budget per
S1 takes longer than 5 days (cocoindex setup friction)MediumIf day 5: defer S8 to Phase 2 day 1; ship spike-results without S8 confirmation
Spike phase reveals architecture flawLow-mediumArchitecture revision via new ratification cycle (no Phase 2 commit until resolved)
NEW S228 — Cocoindex idempotency unclear (S9)MediumIf engine doesn’t skip on equal-hash, design marker-file or write-window-lock
NEW S228 — No dedup substrate clearly wins (S10)Mediumv1 ships without cross-record dedup; user-driven only; defer to v1.1
NEW S228 — Yjs collab plugin Q&A editor regression (S9)LowTest in worktree first; rollback path = disable plugin
NEW S228 — Architecture rewrite + spike phase desyncLow-mediumSub-agents B/C reference latest spike-results in their reads; rewrite doesn’t bake assumptions ahead of spike outcomes

Spike phase plan confidence: 87% (up from 85% pre-S228).

Improvements vs pre-S228:

  • Concrete corpus (docs/client-documentation-base/) replaces ad-hoc fixtures → more realistic results
  • 5 new spikes target specific S228-surfaced gaps (write-back, dedup, OQ6 re-frame, Q4.14, DW.13) → fewer post-spike surprises
  • OQ ratifications locked → spike outcomes feed downstream decisions cleanly

Remaining drag:

  • Cocoindex install + schema-mount unverified (S1 may surface unforeseen friction)
  • Mempalace v4-alpha PG backend stability unknown (could affect S3)
  • pullmd 50-URL evaluation labour-intensive (may need multiple sub-passes)
  • S10 dedup substrate is genuinely-novel territory — three substrates evaluated; “none wins” outcome possible

Outcomes feel high-confidence-bounded:

  • Best case: all 13 spikes pass; Phase 2 commits immediately. Time: 8 days.
  • Likely case: 1-3 spikes surface friction needing architecture tweaks. Time: 10-12 days + ratification cycle.
  • Worst case: Scenario B from S1 forces architecture revision. Time: 12-14 days + new architecture phase.

Parallel-with-architecture-rewrite dimension (S228 unlock):

  • Most spikes don’t depend on architecture rewrite landing first.
  • Architecture sub-agents B/C run in parallel; they REFERENCE spike outcomes via shared 0.9-context.md updates between sessions.
  • Net: spike phase + architecture rewrite both shipping in 2 sessions instead of 4.

This is a tractable spike phase that lands actionable Phase 2 commit decision on a 1.5-2 week horizon.


End of spike plan. S228 corrections + 5 new spikes applied. Ready for ratification + execution.