fix(sync): bound agent-scoped reconciliation to its requested roots - #1319
Conversation
roborev: Combined Review (
|
roborev: Combined Review (
|
|
working on this |
roborev: Combined Review (
|
roborev: Combined Review (
|
roborev: Combined Review (
|
Squash of the reconciliation-scoping branch, including its base branch perf/bound-quick-sync-cutoff-io (kenn-io#1291) and review fixes. - fix(opencode): bound shared-container sync to the changed session - fix(opencode): keep single-session mtime lookup off the whole container - fix(opencode): detect deleted children and stop stamping torn reads - fix(opencode): carry the child digest on non-discovery source lookups - fix(opencode): carry metadata times in the digest and skip it when unused - fix(opencode): digest the complete ordered child identity - fix(opencode): do not re-query legacy containers for a digest they never emit - fix(opencode): require session_id indexes before composite freshness - perf(opencode): bound watcher-event container listing to the session row - perf(opencode): bound watcher materialization and idle full-pass scans - docs(opencode): state the full deferral contract and its bounded floor - fix(opencode): compare the watcher watermark like-for-like with stored metadata times - fix(opencode): bound reconcile discovery and guard watermark skips by capture - test(sync): pin steady-state Claude sync to zero source re-reads - fix(sync): bound reconciliation authority to the roots a pass was asked about - fix(sync): prove a scope in the spelling its traversal roots discover - test(db): bound ownership paging on the query rather than on what follows it - fix(sync): withhold deletion authority from a root that has no bounded proof - fix(db): keep a separator-terminated proof scope from matching no stored child - fix(parser): prove a reconciliation scope in the spelling its sources were stored under - fix(parser): prove a hermes profile in its container's configured spelling - fix(parser): resolve a hermes profile to its on-disk spelling, not the request's - fix(parser): keep an unowned profile child out of hermes traversal - fix(parser): resolve a hermes profile scope only for a profile the container owns - fix(parser): widen an unowned hermes profile child to its container instead of dropping it - fix(parser): widen an opencode container alias to its whole virtual membership - fix(parser): keep a covered request's descendant proof under its remaining ancestor - test(sync): stop NewProvider from writing shared test provider state - perf(sync): walk a shared reconciliation gateway once per pass - fix(sync): group traversal scopes without indexing an unproven slice - fix(parser): resolve a flat hermes root's descendants generically - fix(sync): revalidate the container capture before watermark-only filtering - perf(sync): bound watcher fast-path memory to one page plus the changed batch - fix(sync): advance the freshness pager past all-stale raw pages - fix(parser): widen multi-session container requests to their virtual membership
49ad79d to
f70b0fd
Compare
roborev: Combined Review (
|
…g the first One archive can resolve through several branches — its own configured root and a container's profile enumeration — in any order when overlapping roots are configured and requested. The scope dedup kept only the first arrival's fields and appended retry roots, so a non-covering profile-branch scope arriving first silently dropped the later covering scope's CoverageIdentities: a pass requesting every configured root could never satisfy its required identities and withheld aggregate-member deletion. Colliding scopes describe the same unit, so traversal roots, proof scopes, and coverage identities now merge on collision.
roborev: Combined Review (
|
…th a relocation check A pass scoped to one complete archive proves a member gone from that container, but two guards still demanded full provider-root coverage: aggregate-owned rows bailed in aggregateOwnedMemberGone, and virtual rows bailed in the resolver branch, so a state.db member deleted from a Hermes archive stayed active whenever any other configured root was unrequested. Both paths now accept a completed scope whose proof spans the row's whole container membership — and, since a scoped spool cannot see other roots, they first ask the provider across its full configured scope whether the session still resolves anywhere: a same-ID copy under another root is a move and is preserved, a session found nowhere is reclaimed. Cutoff filtering had the inverse hole: it trusted a carried watermark-only mtime even after the post-discovery recapture invalidated the pass, so a child-only commit landing during discovery could leave a stale session-row watermark below the cutoff and drop the file before full fingerprinting saw the update. The carried watermark now decides the cutoff only while the pass's container capture is live; otherwise the live composite is resolved.
roborev: Combined Review (
|
The persistent-archive branch gained scoped container-proof authority without the relocation check the other member paths carry: a session moved from the requested container to another configured root's container was tombstoned even though the provider still resolves it there. The branch now asks the provider across its full configured scope before claiming the member, and the relocation helper withholds deletion when no provider is available, which also removes the nil flow NilAway flagged. The OpenCode test fixtures now close their writer handle in cleanup; Windows cannot delete a database file with an open handle, which failed TempDir removal for tests that discarded the writer.
roborev: Combined Review (
|
The relocation guard ran only inside the branch paths — the persistent branch and the resolver branch when the stale spelling still resolved — so a virtual member whose home container was itself deleted reached the shared missing-path tombstone with no relocation check at all, and a scoped pass reclaimed a session that had moved to another configured root. The guard now sits once at the shared pre-tombstone site: any scoped pass about to tombstone a virtual-member row first asks the provider across its full configured scope whether the session still resolves anywhere, and the branch-local copies are removed.
roborev: Combined Review (
|
Part of the prep work for #1208, along with #1307 and #1318. The branch was squashed and includes #1291's changes; if #1291 merges on its own, this needs a rebase.
The bug
Asking reconciliation to check one directory could delete sessions in other directories. The requested path got widened to the whole configured root, and that widened claim was treated as proof when deciding what to tombstone.
The fix
Each provider now says exactly what a request covers:
Hermes, OpenCode, Zed, Shelley, Trae, Aider, VS Copilot, and Omnigent each get topology rules for their own layouts (shared databases, archives, profile folders).
Also in here (from #1291)
Watcher performance for shared SQLite databases: one session's write used to re-parse the whole database. Now each session has its own change signal, and a file event does work proportional to what changed, not to the archive size.
Where to look
internal/parser/provider.go— the scope planinternal/sync/engine.go— how the engine uses itinternal/parser/hermes_provider.go,opencode_provider.go,multi_session_container.go— per-provider rules