DR-146: git_sync is the sole writer of the bundle tree; the cocoindex engine is not a bundle writer
DR-146 — git_sync is the sole writer of the bundle tree
Section titled “DR-146 — git_sync is the sole writer of the bundle tree”Context
Section titled “Context”localfs.declare_file is a declaration, not a write: the cocoindex engine
performs the physical write only after the flow body returns. Every bundle
artefact — concepts, per-directory indexes, ontology.json, context.jsonld,
log.md — was declared this way, which put the engine’s write after
git_sync.sync_bundle.
That ordering produced a defect class, not a defect. Anything git_sync writes
is overwritten by the engine afterwards. Two live instances:
- The
log.mdfindings block.sync_bundlemerges its reconcile findings intolog.mdand writes them; the engine then rewriteslog.mdfrom the pre-merge declared content, so the DR-016 human-edit warning never reaches disk (id-445 AC-3). - The override fold.
git_sync.reapply_overrides’ own docstring says the fold happens “BEFORE it is handed todeclare_file/sync_bundle”, butflow_def.py:648folds afterwrite_bundlehas declared every concept. The engine writes the un-overridden bytes over the applied override, leaving the working tree and the git index disagreeing.
The obvious narrow fix — let sync_bundle merge, then re-declare log.md — is
impossible. Measured against the installed pin (cocoindex 1.0.18), a second
declare_file on one path in one flow body raises ValueError: Invalid Request: Target state already declared with key. Amendments cannot be threaded back into
a declaration after the fact.
Nothing in OKF asks for the declarative write model. The upstream reference
agent (GoogleCloudPlatform/knowledge-catalog, okf/src/reference_agent) writes
every file with path.write_text(), ships no log.md writer at all, and never
unlinks. The indirection is ours, and so is the defect class.
Decision
Section titled “Decision”No bundle path is registered as a cocoindex target state. git_sync writes the
bundle tree; write_bundle becomes pure content computation.
write_bundle already returns the complete {rel_path: content} map as
RunSummary.declared (added by id-445 for an unrelated reason) — that is the
enabling step. sync_bundle reads the tree, decides, and writes; nothing writes
after it. Executed in id-448.
The engine keeps everything it is actually good at: source ingestion,
memoisation, and incremental re-drafting via enrich_concept / run_web_pass.
write_bundle is plain orchestration, not @coco.fn, so none of that is
touched.
Alternatives Considered
Section titled “Alternatives Considered”Keep declarations, compute the findings before log.md is declared. Requires
splitting sync_bundle into a decide phase and an apply phase and threading the
result back through write_bundle. It fixes the log.md instance and leaves the
override instance, and it adds machinery where the alternative deletes it.
Have the producer write files directly, mirroring the reference agent.
Rejected: it breaks human-edit detection. _decide_and_apply must read the tree
before this run’s content lands on it; a direct write from write_bundle would
clobber the human’s edit before sync_bundle ever saw it, failing DR-016’s
central promise without a symptom. The engine’s bad ordering is currently the
only thing making that branch reachable, which is why the writer moves to
git_sync rather than to write_bundle.
Consequences
Section titled “Consequences”The engine store must be wiped when this lands. A read of the live
platform-staging store (/cocoindex-state/lmdb/mdb) found declarations for all
24 bundle paths, put there by the producer’s own earlier declare_file calls.
Once they stop being declared they drop out of the keyset, and the engine’s
orphan-delete branch is free to delete the whole bundle. The wipe costs a memo
rebuild only; Supabase remains canonical, and bundles are synthetic pre-launch.
This corrects an S552 finding recorded as measured. id-445 concluded “for a DR-016 client-owned git-clone bundle the engine can never be the removal actor”, reasoning that cloned files are never declared and so cannot drop out. That holds only for the first run on a freshly-provisioned container; it stopped being true the moment a run completed. The conclusion was reached by contrapositive from a throwaway local probe, with the real store explicitly recorded as unread.
A non-git write path is still required. When repo_path is None (tests, and
any non-git shape) flow_def returns before sync_bundle, so nothing would
write. That path must exist and must not be the one the deployed shape takes —
bundle_dir == repo_path in deployment, both being OKF_BUNDLE_DIR.
The removal actor becomes documentable. BI-11’s reporting requirement and the physical-removal actor can now be stated separately in TECH, with a single answer rather than two competing channels.