Skip to content

ID-59 {59.1} RESEARCH — Per-UC write-back (UC1/4/6/3/5/8) + Diff-UI re-point + edit_intent storage shape

ID-59 {59.1} RESEARCH — Per-UC write-back + Diff-UI re-point + edit_intent storage shape

Section titled “ID-59 {59.1} RESEARCH — Per-UC write-back + Diff-UI re-point + edit_intent storage shape”

Status: RESEARCH complete — recommendations PENDING Liam (§7 decision queue). Authored 08/06/2026, spec-chain START per ID-93 §6.2 (“more than minimal” → start the T9 chain so any schema slice ratifies pre-cutover) + §6.8 (edit_intent storage research run — Liam’s instinct is a sidecar .md, wants a research run to be certain). RESEARCH-only — no implementation, no DDL, no code edits.

Task: ID-59 (T9, in_progress) — “Per-UC write-back (UC1/4/6/3/5/8) + Diff-UI sidecar re-point”. Largest of the T9–T14 family (3 PLAN units).

Companion artefact: this dir’s TECH.md (“Concurrent-Edit Intent Arbitration — TECH”, [CURRENT-CANONICAL], LANDED-S244 commit 12a20963) is the arbitration rule spec (T-1..T-5). This RESEARCH does NOT supersede it — it extends the surface to per-UC write-back wiring, the Diff-UI re-point, and the storage-shape decision the TECH explicitly deferred (TECH §1 out-of-scope bullet 2; §4.2/§4.4 storage option scoping).


0. Headline corrections to the dispatch brief (verify-first discipline)

Section titled “0. Headline corrections to the dispatch brief (verify-first discipline)”

Three load-bearing claims in the surrounding context are WRONG against the live surface and the spec chain must be authored on the corrected facts.

Claim (as received)Verified realityEvidence
”the concurrent-edit-intent-arbitration substrate LANDED S244 — reuse, do not re-invent”The TECH spec landed S244 (commit 12a20963). The arbitration code does NOT exist. grep -rniE 'edit[_]?intent' lib/ app/ components/ types/ scripts/ supabase/ hooks/ contexts/zero hits; grep -rniE 'arbitrat' over the TS corpus → zero hits (08/06/2026). T9 is GREENFIELD implementation against a ratified rule, not a re-point onto landed code.TECH.md T-1 “Current state: Greenfield … zero hits as of 20/05/2026” — re-confirmed 08/06/2026.
edit_intent lands on content_history.edit_intent (+ actor_id array + arbitration_inputs jsonb) — TECH §4.2/§4.4 name these columnsNone of those columns exist. content_history today = id, content_item_id, version, title, content, brief, detail, reference, metadata jsonb, change_summary, change_type, created_by, created_at, change_reason. No edit_intent, no actor_id (there is created_by uuid), no arbitration_inputs. TECH §4.2/§4.4 describe a target shape, not current state — and call it “the most likely choice”, NOT ratified.supabase/migrations/20260416102457_pre_squash_reconciliation.sql:3485 (CREATE TABLE content_history).
write-back lands on source_documents.original_path (PLAN §4.9 + TECH §1)The prod column is storage_path (NOT NULL), not original_path. The pipeline writes the source-relative POSIX path into storage_path, and that same string is the uuid5 PK seed. There is no original_path column on source_documents.SOURCE_DOCUMENTS_SCHEMA flow.py:1185; write site flow.py:1971 "storage_path": rel_path; PK seed flow.py:1809.

Truth set used (per ID-93 §1.2 adversarial discipline): scripts/cocoindex_pipeline/flow.py (the ACTIVE pipeline — NOT the retired scripts/kb_pipeline/; ID-46/T14 retires the latter), the migration corpus under supabase/migrations/, the cocoindex write-model runbook (themes/canonical-pipeline/reference/cocoindex-write-model.md, R1–R5), the v1-completion-sequence ordering board, this dir’s TECH.md, and live empirical import checks (§6).


Per the “Always Do” code-intel block: edit_intent / arbitrate are GREENFIELD — there is no indexed symbol to run gitnexus_context against (the function the TECH spec mandates, arbitrate() / arbitrateMany(), does not yet exist). The orientation therefore reduces to an adversarial grep sweep of the existing write-back-adjacent surface, which IS the correct greenfield fallback:

  • Editor substrate (EXISTS, deps installed): Tiptap 3.23.4 family incl. @tiptap/extension-collaboration 3.23.4, @tiptap/markdown 3.23.4; yjs ^13.6.30; y-prosemirror ^1.3.7 (package.json:81–92, 131–132). Editor components present: components/item-detail/content-editor.tsx, components/item-detail/inline-content-editor.tsx, components/item-detail/editor-toolbar.tsx, components/qa/qa-answer-display.tsx, components/procurement/response-editor.tsx; save-path helpers lib/editor/save-safety.ts, lib/editor-utils.ts, lib/drawer-insert.ts. Correction to TECH §T-1: the TECH says “the Yjs collab plugin is not yet wired” — the dependency is installed (extension-collaboration
    • yjs + y-prosemirror); what is absent is the wiring of a collab provider and the edit_intent capture UX. State this precisely in PRODUCT/TECH.
  • Write-back routes (EXIST, but no edit_intent): app/api/items/[id]/route.ts, app/api/items/[id]/history/route.ts, app/api/items/[id]/history/[versionId]/route.ts, app/api/items/[id]/rollback/route.ts, app/api/source-documents/[id]/route.ts. These write content_history rows today; none stamps an edit_intent (grep: zero hits).
  • Diff-UI substrate (EXISTS): components/item-detail/version-history.tsx (content-item version diff), components/intelligence/prompt-refinement/prompt-diff-view.tsx (prompt diff — adjacent, not the target). The “source_document_diffs substrate” the PLAN says to re-point FROM does NOT exist as a table in the migration corpus (grep: zero hits) — it was a planned substrate that was never built, so the “re-point” is really “build Diff-UI against the markdown-sidecar/version model for the first time”. Flag this to PRODUCT.

1.2 Coupling-class vocabulary (inherited from ID-93 §1.1)

Section titled “1.2 Coupling-class vocabulary (inherited from ID-93 §1.1)”

C1 ingest-capture · C2 zero-row window · C3 id-coupled (renumbering / source-layout exposure) · C4 shape-only · C5 verified-decoupled. The edit_intent storage decision is the C3 item ID-93 §4 flagged; resolving its class is the core of §4 below.


Per-UC scope is the ID-31 PLAN §4.9 in-scope list (the T9 source of truth). Each UC is mapped to its write surface, whether it touches the cocoindex source-file layout, and its substrate entry points. The arbitration function (TECH T-1..T-5) only sits in the UC1/UC4/UC6 path — the Tiptap+Yjs concurrent-edit paths. UC3/UC5/UC8 do not produce CRDT-merge concurrency and so do not invoke arbitrate().

UCWhatWrite surfaceTouches cocoindex source-file layout?Invokes arbitrate()?Substrate entry point (to build)
UC1 typo fixTiptap+Yjs in-platform editcontent_items.content (DB) + the source file at source_documents.storage_path (Candidate A two-phase; sidecar .md for binary)YES — writes the walked file; cocoindex observes on next POST /walkYESKH-server save endpoint → arbitrate(intents) → write file + DB; stamp merged edit_intent (storage = §4)
UC4 paragraph rewriteTiptap+Yjs + edit-intent gate at savesame as UC1 + citation re-anchorYESYESsame save endpoint as UC1; edit_intent captured at save time per 02-data-flow.md §8
UC6 user-direct Q&A pair revisionTiptap+Yjs, KH-DB-only (05-qa-flow.md §8/§9)q_a_pairs + q_a_pair_history (NO file write — Q&A pairs are KH-DB-only in v1; sidecar materialisation DEFERRED-v1.1, PLAN §5 item 18)NO (v1)YESKH-server Q&A save → arbitrate()q_a_pair_history version row
UC6 AI-suggestsource-doc change → AI proposes → user commits (Candidate B)q_a_pairs after user commitNO (the trigger is a source-doc change cocoindex already detects; the write is DB)NO (single-actor commit)Anthropic API propose → review queue → commit; gated on the cocoindex source-binding trigger (T8 ✅)
UC3 sweeping renametwo variants: (A) KH-server find-replace iteration over affected files; (B) smart-agent Anthropic text-editor-tool, per-match approvethe source files at storage_path (variant A iterates them; variant B edits via tool)YES — variant A/B both rewrite walked filesNO (sweep is a batched single-actor operation)KH-server sweep orchestrator with a shared sweep-ID + per-record provenance; rollback per 06-… §6.0.6 — gated on Spike#3 (write-back adapter contract)
UC5 bid→Q&A promotionbid/form response → q_a_pair draft with lineageq_a_pairs (+ lineage to source response + question), KH-DB-onlyNONOreview-queue promotion (UC5-adjacent); depends on T6 q_a_pairs schema (✅) + the {64.14} form rename for response context
UC8 cross-workspace dedupKH-server orchestration + Claude API similarity reasoning (Candidate A)q_a_pairs merge + q_a_pair_history merge-lineageNO (DB-level merge)NOKH-server dedup orchestrator + Claude reasoning — gated on Spike#10 (UC8 dedup substrate) + UC8 ratification + bl-74 q_a_pair_history lineage columns

Out of T9 scope (PLAN §4.9 scope-out, restated so the spec chain does not creep): UC7 + UC10 (external-folder change — handled natively by cocoindex source binding, T8 ✅); UC9 (scope-tag taxonomy — separate OPS work-package); UC2 (collapsed into UC1, S228); UC8 Candidate C upgrade (DEFERRED-v1.1); Yjs y-supabase persistence (DEFERRED-v1.1, PLAN §5 item 19).

2.1 The single shared write-back contract (Spike#3)

Section titled “2.1 The single shared write-back contract (Spike#3)”

UC1/UC3/UC4 all rewrite walked source files at source_documents.storage_path. The cocoindex write-model (R1, R4, §1) means the write-back is NOT a cocoindex concern — cocoindex only observes the file on the next POST /walk (boot never walks — ID-83/bl-221). The write-back adapter is a KH-server concern that must:

  1. Resolve the on-disk path under COCOINDEX_SOURCE_PATH from storage_path (the source-relative POSIX string — flow.py _to_source_relative, write-model R4: file.file_path.path is ABSOLUTE in prod, normalised source-relative for storage_path).
  2. Write the new bytes (preserving the path so the uuid5 PK is stable across the next walk — write-model R1/§1: integrity is by uuid5(_KH_PIPELINE_DOC_NS, "<kind>:"+rel_path) derivation, NOT FK; a path change would mint a NEW PK = a new content_item, orphaning the old).
  3. Stamp the merged edit_intent (UC1/UC4) so the next walk’s re-classification policy (02-data-flow.md §8.2) is honoured.

This adapter contract is Spike#3 (PENDING per PLAN §4.9 risk). It is a cross_doc_link, not a sibling dependency (§5). The write-model R1/R4 constraints ARE the empirical substrate Spike#3 must resolve against — much of Spike#3 is already answerable from the landed write-model runbook.


What the PLAN ratified (S243 Item 5): Diff-UI is RETAINED for v1 (was DEFERRED-v1.1), re-pointed from the source_document_diffs substrate to markdown sidecar revision comparison.

Verified reality:

  • source_document_diffs is not a table (grep over migrations: zero hits) — it was a planned substrate, never built. So “re-point FROM source_document_diffs” is a misnomer: there is nothing to re-point off. The work is “build Diff-UI against the available revision model”.
  • The available revision model today is content_history (version rows: version, content, created_at, created_by, change_type, change_summary) — already surfaced by components/item-detail/version-history.tsx and app/api/items/[id]/history/route.ts.
  • The “markdown sidecar revision comparison” framing presupposes a per-revision markdown artefact to diff. For UC1/UC4 (file-backed content) the natural diff is old-file-bytes ↔ new-file-bytes (the markdown the editor produced). For UC6 (KH-DB-only Q&A) the diff is q_a_pair_history old-content ↔ new-content. There is no separate “sidecar diff store” needed — the diff is computed on demand from the two revisions.

Recommended re-point plan (for PRODUCT/TECH to formalise):

  1. Diff-UI consumes the existing version model (content_history for content items; q_a_pair_history for Q&A pairs) — NOT a new source_document_diffs substrate (which would be speculative DDL the OQ-64-5 “structurally complete, based on ratified specs” policy disallows).
  2. Diff computation is client-side or a thin RPC over two revision blobs — no new storage.
  3. The “markdown sidecar” wording resolves to: for file-backed UCs the diffed artefact IS the markdown the sidecar .md/source file carries; this is a rendering decision, not a storage decision. It does NOT, by itself, require the §4 sidecar-.md storage shape for edit_intent.

This means Diff-UI does NOT gate the §4 storage decision — it works off whatever revision model exists regardless of where edit_intent lives. Flag in PRODUCT so the two are not conflated.


4. edit_intent STORAGE shape — the PRIORITY research run (Liam, §6.8)

Section titled “4. edit_intent STORAGE shape — the PRIORITY research run (Liam, §6.8)”

The question: where does the (post-arbitration) edit_intent label live? Three options from ID-93 §4 / phase-b-prerequisite-2b-cocoindex-operational.md §369 / TECH §4.2:

(a) a content_history column; (b) a sidecar .md metadata file (Liam’s instinct); (c) a cocoindex flow arg.

4.1 What “source-file layout frozen at re-ingest” actually means

Section titled “4.1 What “source-file layout frozen at re-ingest” actually means”

This is the crux of the sidecar-.md analysis, and it resolves cleanly against the live pipeline.

The cocoindex source-file layout = the set of files under COCOINDEX_SOURCE_PATH that localfs.walk_dir(live=True, recursive=True) enumerates (flow.py header line 4; _coco_api.py exposes localfs; app_main binds COCOINDEX_SOURCE_PATH flow.py:2877). For each walked file:

  • The source-relative POSIX path becomes source_documents.storage_path (flow.py:1971) and the uuid5 PK seed for content_items / source_documents / content_chunks / entity_mentions (write-model R1/§1; flow.py:1809). Path = identity.
  • A file that is not content but lands in the walked folder is ingested as content unless it is explicitly skipped. The ONLY skip today is a hardcoded single-filename match for the workspace manifest: if file.file_path.path.name == _WORKSPACE_MANIFEST_FILENAME: return (flow.py:1667). There is no generic sidecar-suffix exclusion.
  • .md is a first-class content suffix (_NON_FORM_SUFFIXES = {".md", ".txt", ".html"} flow.py:1346; .mdtext/markdown flow.py:1355; converts successfully via convert_binary_to_markdown). A .md sidecar dropped next to a source file would therefore be walked and ingested as a spurious content_item — exactly the BUG-B failure mode the manifest .json hit on the S297 live smoke (write-model §4 row B; flow.py:1664–1666 comment).

So “freezing the source-file layout” means: deciding, BEFORE the ID-45 full-corpus re-ingest, the exact set of files cocoindex walks and how each is classified (content vs skip), because that set mints the PKs the entire corpus is keyed on. A sidecar-.md storage shape would add a new file class to the walked layout and therefore:

  1. require a new skip rule in ingest_file (generalise the single-filename manifest skip to a sidecar-suffix/-naming convention) — a pipeline code change (C1-adjacent: it changes how the run classifies files), and
  2. that rule MUST be in place before the re-ingest, or every sidecar .md becomes a junk content_item with its own uuid5 PK in the handover corpus (un-cheap to unpick post-run — C3).

This is the coupling ID-93 §4 flagged. It is REAL and confirmed: the sidecar-.md option couples to the re-ingest and would have to settle pre-cutover.

Dimension(a) content_history column(b) sidecar .md metadata(c) cocoindex flow arg
Where it livesNew column(s) on content_history (DB)Frontmatter/companion file in the walked corpusArgument threaded through the cocoindex flow (e.g. via the run/manifest)
Couples to re-ingest?Only via OQ-64-9 (content_history retention disposition, {64.7}). content_history is C3-id-coupled (1,225 prod rows reference content_item_id which renumbers) — BUT ID-93’s edit_intent is a NEW column, write-only post-cutover; it adds nothing to the renumber exposure beyond what {64.7} already plans.YES, hard. Adds a file class to the frozen walked layout → needs a pre-run skip rule (§4.1) or junk content_items. Must settle pre-cutover.YES, conceptually. The flow arg is set at walk time; the re-ingest run would need the arg plumbed. But edit_intent is a post-edit signal, not an ingest signal — there is no edit at first-ingest, so the arg is null/irrelevant for the ID-45 run.
Write-model fit (R1–R5)Clean — content_history is app-written, NOT pipeline-written (write-model: pipeline NEVER writes content_history; flow.py write targets exclude it). No cross-target-FK hazard (R1 N/A). The arbitration write-site (TECH §4.2 step 5) already writes a content_history row per save — adding edit_intent is one more column on a row it already writes.Poor — a sidecar in the walked path fights the “path = identity” model; the pipeline would re-read it on every walk and must be taught to treat it as metadata not content. R4 path-normalisation applies.Poor for storage — a flow arg is a transient run input, not durable storage. It answers “how does the NEXT walk learn the intent” but NOT “where is the intent recorded” — it is a transport, not a store.
Who reads itDiff-UI, audit, the re-classification trigger (read at the save site, passed to the next walk’s policy)cocoindex on walk + any audit readercocoindex flow only
Audit/forensics (TECH §4.4)Native — same row carries created_by, version, change_type; add edit_intent (+ optionally the per-actor arbitration_inputs jsonb) alongside.Awkward — audit must read files, not rows.None — transient.
Cost if chosenOne ALTER TABLE content_history ADD COLUMN edit_intent text (+ optional arbitration_inputs jsonb). C4 shape-only if landed pre-cutover (folds into the {64.7}/{64.8} window cleanly); cheap ALTER even post-cutover (no backfill — write-only forward).Pipeline code change (new skip rule) + the freeze gate + a sidecar materialisation path that v1 otherwise DEFERS (PLAN §5 item 18 keeps Q&A sidecars DEFERRED-v1.1).Plumbing with no durable record — needs (a) or (b) anyway for persistence.

4.3 RECOMMENDATION — option (a) content_history column

Section titled “4.3 RECOMMENDATION — option (a) content_history column”

Recommend (a): a content_history.edit_intent text column (+ optionally arbitration_inputs jsonb for the per-actor inputs the TECH §4.4 audit contract wants). Rationale:

  1. It does NOT gate the re-ingest. content_history is app-written, not pipeline-written — the ID-45 run never touches it. The only re-ingest coupling is OQ-64-9 ({64.7} content_history disposition), and a new write-only column adds nothing to the renumber exposure {64.7} already plans for. By contrast the sidecar-.md option (b) does gate the run (§4.1) — it would force a pre-run pipeline skip-rule change for a v1.1-deferred materialisation path. Choosing (a) keeps the ID-45 critical path clean.
  2. It fits the write-model. The arbitration write-site (TECH §4.2 step 5) already writes a content_history row on each save; edit_intent is one more column on that row. No new file class, no R1 FK hazard, no R4 path-identity entanglement.
  3. It serves audit + Diff-UI natively (§3) — the revision model Diff-UI reads already lives in content_history.
  4. It is the cheapest schema slice — one additive ALTER, write-only forward, no backfill (every pre-existing history row legitimately has edit_intent = NULL).
  5. (c) is not a storage shape — it is a transport for the next walk; it still needs (a) or (b) for persistence. Where the next walk needs to learn the intent (to apply the §8.2 re-run policy), it reads it from the content_history column (a) at the write-back site, OR the write-back adapter (Spike#3) stamps it transiently — that is a transport detail T9 settles, orthogonal to storage.

On Liam’s sidecar-.md instinct: the instinct is sound for a different concern — a human-readable, file-co-located revision/metadata artefact. But for the edit_intent label specifically, the sidecar shape buys nothing the content_history column doesn’t, and it imports the re-ingest freeze gate plus a v1.1-deferred materialisation path. The research run resolves to: store edit_intent in content_history (a); do NOT use a sidecar .md for the intent label. If Liam still wants file-co-located revision artefacts for human/diff reasons, that is a separate v1.1 deliverable (PLAN §5 item 18, DEFERRED) and does not change the v1 storage decision.

4.4 The pre-cutover schema slice (the §6.2 “more than minimal” output)

Section titled “4.4 The pre-cutover schema slice (the §6.2 “more than minimal” output)”

Under OQ-64-5 “structurally complete, based on ratified specs” ({64.8} gate G6), the §4.3 recommendation yields exactly ONE pre-cutover schema slice to ratify:

PRE-CUTOVER SLICE (proposed): ALTER TABLE content_history ADD COLUMN edit_intent text with a CHECK against the closed CV {cosmetic, data, structural} (NULL allowed — pre-edit-feature history), plus optionally ADD COLUMN arbitration_inputs jsonb for the per-actor inputs (TECH §4.4). Lands in the {64.7}/{64.8} pre-re-ingest window so the handover DB is structurally complete for T9. Write-only forward — no backfill, no data migration. This is C4 shape-only (cheap ALTER), and is cheap even if deferred post-cutover — so the slice is OPTIONAL-pre-cutover, not a hard gate. Recommend landing it pre-cutover anyway (zero cost, closes the C3 question ID-93 §4 raised, and {64.7} is already open).

This is the spec slice the §6.2 ratification asked the chain to surface. It is small and not re-ingest-blocking — which is itself the answer to the ID-93 §6.8 “decide jointly with OQ-64-9” framing: the joint decision is trivial once you see content_history is app-written, so the edit_intent column is decoupled from the renumber exposure {64.7} handles.


Section titled “5. External deps — track as cross_doc_links, NOT sibling dependencies”

Per the brief + §3.3 sibling-only rule, these are cross-Task/cross-doc and must NOT become ID-59 sibling-subtask deps:

DepWhatStatusWhere it bitesTrack as
Spike#3write-back adapter contract (path resolution + byte write + intent stamp under COCOINDEX_SOURCE_PATH)PENDING (PLAN §4.9 risk) — but largely answerable from the landed cocoindex-write-model.md R1/R4UC1/UC3/UC4 file write-backcross_doc_link → themes/canonical-pipeline/reference/cocoindex-write-model.md + ID-31 PLAN §4.9
Spike#10UC8 cross-record dedup substrate (cocoindex post-target-write @coco.fn vs mempalace KG vs hybrid)PENDING (PLAN §4.9 risk; T12 + Spike#10)UC8 onlycross_doc_link → ID-31 PLAN §4.9 + §4.8 scope-out; gates UC8 slice only
UC8 ratificationUC8 v1 Candidate A scope confirmationPENDINGUC8 onlycross_doc_link
bl-74q_a_pair_history superseded_by + source_workspace_id columns — needed for UC8 merge-lineage; flipped ready, lands BEFORE ID-45 promotion ({64.8} gate G3)readyUC8 merge-lineage auditcross_doc_link → docs/reference/backlog/74.md; coordinate at the UC8 slice
{64.14} form rename (bid_*form_*)UC5 bid/form-response contextratified, STEP-0 = form_*UC5 response-context namingcross_doc_link → ID-64 {64.14}

Sibling-dependency note: the §4.3 storage slice and the per-UC write-back slices CAN be sibling-ordered within ID-59 (e.g. the content_history.edit_intent slice before the UC1/UC4 slices that stamp it). No cross-Task subtask dep is needed — all external deps are doc-level. No escalation required.


6. Empirical verification (Q-EX2 / OQ-3 — pre-ratification)

Section titled “6. Empirical verification (Q-EX2 / OQ-3 — pre-ratification)”

External-library / installed-surface claims, import-and-call verified 08/06/2026:

Symbol / factPinned versionResult
cocoindex installedcocoindex[postgres]==1.0.7 (requirements.txt:54)PRESENTimport cocoindex; cocoindex.__version__ == '1.0.7'. NOTE: the write-model runbook verified against 1.0.3; the R1–R5 constraints are pin-version-stable (no 1.0.3→1.0.7 write-path break observed; pin bump = bl-252 promote, S324).
cocoindex.connectors.localfs.walk_dir1.0.7PRESENThasattr(localfs, 'walk_dir') == True. Confirms the walked-layout model (§4.1).
Tiptap collab + Yjs deps@tiptap/extension-collaboration@3.23.4, yjs@^13.6.30, y-prosemirror@^1.3.7 (package.json)PRESENT — corrects TECH §T-1 “Yjs collab plugin not yet wired” (dep present; provider + intent-UX absent).
content_history.edit_intent columnn/a (DB)ABSENT — not in pre_squash CREATE TABLE (20260416102457:3485) nor any later ADD COLUMN migration. (This is the §4.4 slice to add.)
source_documents.original_path columnn/a (DB)ABSENT — real column is storage_path (flow.py:1185; migration …:4037). PLAN/TECH original_path is stale (§0).
edit_intent / arbitrate TS symbolsn/a (code)ABSENT — zero hits across lib/ app/ components/ types/ scripts/ supabase/ hooks/ contexts/. T9 is greenfield (§0).

No ABSENT/SIGNATURE_DRIFT on an external API the spec relies on — the ABSENT rows above are the intended-to-build surface (the slice §4.4 adds) and the stale-doc corrections (§0), not a blocker. cocoindex 1.0.7 + localfs.walk_dir (the surfaces this RESEARCH’s §4.1 argument depends on) are both PRESENT. Safe to ratify the RESEARCH on the §4.1 model.


7. Liam decision queue (recommendations — Orchestrator/Liam write the ledger, not this doc)

Section titled “7. Liam decision queue (recommendations — Orchestrator/Liam write the ledger, not this doc)”
  1. edit_intent storage shape (§6.8 research run): RECOMMEND option (a) content_history column, NOT the sidecar .md. The sidecar couples hard to the re-ingest source-layout freeze (§4.1) for a v1.1-deferred materialisation path; the column is app-written, decoupled from the renumber exposure, write-model-clean, and serves audit + Diff-UI natively. (§4.3)
  2. Pre-cutover schema slice: ALTER TABLE content_history ADD COLUMN edit_intent text (CHECK {cosmetic,data,structural}, NULL-allowed) + optional arbitration_inputs jsonb. C4 shape-only, OPTIONAL-pre-cutover, not a hard gate — recommend landing it in the {64.7}/{64.8} window anyway (zero cost, closes ID-93 §4’s C3 question). Does NOT gate ID-45. (§4.4)
  3. Correct the stale references before PRODUCT/TECH: write-back target is source_documents.storage_path (NOT original_path); the arbitration substrate is greenfield code (only the rule spec landed S244); content_history has no edit_intent/actor_id/ arbitration_inputs columns today. (§0)
  4. Diff-UI re-point: there is no source_document_diffs substrate to re-point off — build Diff-UI against the existing content_history / q_a_pair_history revision model. Diff-UI does NOT gate the §4 storage decision. (§3)
  5. TECH §T-1 correction: Tiptap collab + Yjs deps ARE installed; what’s absent is the collab provider wiring + edit_intent capture UX. (§1.1, §6)
  6. External deps (§5): track Spike#3, Spike#10, UC8 ratification, bl-74, {64.14} as cross_doc_links, not sibling deps. UC8 slice is the only one gated by Spike#10 + UC8 ratification + bl-74. Spike#3 is largely pre-answered by the landed cocoindex-write-model.md R1/R4.
  7. Right-sizing the chain: ID-59 IS behaviourally ambiguous + multi-subsystem (6 UCs + Diff-UI + storage) → recommend authoring {59.2} PRODUCT (per-UC behaviour invariants + the Diff-UI UX) and {59.3} TECH (write-back adapter contract + storage slice + the per-UC wiring against the arbitration rule), then {59.4} PLAN (3 PLAN units → decompose; likely one slice per UC/UC-pair + the storage slice + Diff-UI, well under the 25-subtask ceiling). The existing TECH.md (arbitration rule) is an INPUT to {59.3}, not a replacement for it.
  • OQ-59-1: Confirm option (a) content_history.edit_intent over the sidecar .md instinct (§4.3). If Liam still wants file-co-located revision artefacts, confirm that is a separate v1.1 concern (PLAN §5 item 18) and does not change the v1 storage decision.
  • OQ-59-2: Land the §4.4 slice pre-cutover (recommended, zero-cost) or defer post-cutover (also cheap)? Either is fine; pre-cutover closes ID-93 §4 cleanly.
  • OQ-59-3: content_items-without-source_documents audit (PLAN §4.9 / RATIFIED-S243 Item 10) — for content_items with source_document_id IS NULL (e.g. MCP create_content_item, legacy import), is the edit-back behaviour (a) auto-create a source_document with connector='mcp' storage path, or (b) fall back to KH-DB-only edit (no file write)? This is a per-UC1/UC4 behaviour the PRODUCT must pin; needs the prod sweep at PRODUCT time.
  • OQ-59-4: Is Diff-UI in scope for v1 PRODUCT, or does “retained for v1” mean a minimal content_history diff view only (deferring the richer markdown side-by-side to v1.1)? Affects {59.2} scope.

SourceUsed for
scripts/cocoindex_pipeline/flow.py (ACTIVE pipeline) — :1182 SOURCE_DOCUMENTS_SCHEMA, :1185 storage_path, :1346 _NON_FORM_SUFFIXES, :1355 .mdtext/markdown, :1667 manifest skip, :1809 PK seed, :1971 storage_path=rel_path, :2877 COCOINDEX_SOURCE_PATH§0, §4.1, §6 — walked-layout model + write surface
scripts/cocoindex_pipeline/_coco_api.py :57 localfs§4.1 — walk connector
themes/canonical-pipeline/reference/cocoindex-write-model.md R1–R5, §1, §4 (BUG-B)§2.1, §4 — uuid5 identity, no cross-target FK, sidecar=BUG-B class
supabase/migrations/20260416102457_pre_squash_reconciliation.sql :3485 (content_history), :4037 (source_documents.storage_path)§0, §4, §6 — actual DB shape
specs/id-59-concurrent-edit-intent-arbitration/TECH.md T-1..T-5, §4.2/§4.4§0, §1.1, §4, §7 — arbitration rule (input, not replacement)
specs/id-31-canonical-pipeline-implementation-plan/PLAN.md §4.9, §5 (items 5/18/19), §3/§8§2, §3, §5 — per-UC scope, Diff-UI, Spike#3/#10, UC8
specs/ID-93-pre-reingest-intent-gap/RESEARCH.md §4, §6.8§0, §4, §7 — C3 coupling framing, storage-shape research mandate
docs/reference/tasks/ID-64.md {64.7}, {64.8} gate G6, {64.14}§4.4, §5 — pre-cutover window, OQ-64-5/9 coupling, form rename
themes/canonical-pipeline/reference/v1-completion-sequence.md Lane B/A, §2§0, §2 — sequencing, ID-83 boot-never-walks, pin context
package.json :81–92, 131–132; requirements.txt :54§1.1, §6 — Tiptap/Yjs deps, cocoindex pin
grep sweep (edit_intent/arbitrate over TS corpus; source_document_diffs over migrations)§0, §1.1, §3, §6 — greenfield + absent-substrate confirmations

End of RESEARCH. Recommendations PENDING Liam (§7/§8). No implementation, no DDL, no ledger edits performed.