fix(memory-sync): honest conflict count + owner-writes-only push (06d09cde) - #72
Merged
Merged
Conversation
…rites-only push Two data-integrity defects in the sync path, both reproduced live 2026-08-03: Defect A (silent conflicts=0 on marker carryover): mergeText's fast-path branches (local===base / remote===base) and the append-only success path returned conflict:false without checking whether the winning payload already carried inline conflict markers from a prior pass. In a single sync (pull then push), a genuine pull-time conflict wrote markers to the local file and a clean base; the immediately-following push then saw remote===base and re-labeled that marker-carrying local content as a clean 'local wins' with conflicts=0, then committed it to the hub and base. Fix: hasConflictMarkers() line-anchored check; every conflict:false return that hands back a winning payload now reports conflict:true when that payload carries markers. Payload unchanged, count honest. The genuine single-pass conflict path (constructs markers, conflict:true) is unchanged. Defect B (peer-file echo / last-writer-wins): no ownership concept existed, so a spoke re-offered a peer's machine-state/frictions file it had merely pulled as its own 'local' change. Fix: optional SyncPathConfig.ownerScoped; the push-side collection offers only <profile>.json under an ownerScoped directory, and a companion base-map filter strips foreign ownerScoped keys so push's base-union-local merge never visits a peer file this machine doesn't own. Pull is unchanged (peers are still materialized for read). ownerScoped:true set on machine-state and frictions in all three profiles; memory stays shared. Tests: 19 new (repro A, repro B + owner-tolerance, 15 merge-honesty units); full suite 128 pass / 0 fail; build + typecheck clean; watch-mirror-delete and machine-state-syncpath negative controls unchanged and green. Refs: agent-memory task 06d09cde Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GbC4gdPEf4T1YD8LZV9GA7
…ing, precise marker detection, queue-replay owner filter) Review of f51ff9f (agent-tasks 06d09cde) raised one HIGH + two MEDIUM + two LOW; all fixed here (the local===remote unchanged-path LOW is consciously accepted, its pin unchanged): HIGH (silent own-state data loss): owner-writes-only made config.profile load-bearing; a resolved profile that doesn't match the machine's owner filename silently published nothing for its own machine-state/frictions. Now collectLocalSyncFiles emits a visible warning (surfaced via the operation's notes line) when an ownerScoped directory holds peer files but not this machine's own <profile>.json; still tolerant (no exception) when the directory is empty. CLI profile-resolution semantics are unchanged (minimal fix). MEDIUM (marker false-positive): hasConflictMarkers narrowed to the unambiguous '<<<<<<< ' opener line only, so setext '=======' headings, '===' dividers, and deep '>>>>>>> ' blockquotes in ordinary memory Markdown are no longer flagged as conflicts; an inherited full block still carries the opener and is detected. MEDIUM (queue-replay echo): queued snapshots' localFiles/baseFiles are now routed through filterOwnerScopedBaseMap on replay, so a snapshot enqueued before this machine adopted ownerScoped can no longer echo a peer's file. LOW: local-wins/remote-wins strategy branches upgrade conflict:false->true on a marker-carrying winner (invariant completeness); corrected the misleading 'profile is a cosmetic label' comments in all three profiles. Tests: 9 new/changed (own-file-drop warning + empty-dir control, 5 Markdown exclusion units, 2 queue-replay strip tests, 2 wins-strategy honesty units); full suite 137 pass / 0 fail; build + typecheck clean; coverage 95.14/76.80/ 93.55 (not worsened). Refs: agent-memory task 06d09cde Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GbC4gdPEf4T1YD8LZV9GA7
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Zweck
Zwei Datenintegritätsdefekte im sync-Pfad von
packages/agent-memory-sync, beide live am 2026-08-03 auf dem Mac mini reproduziert (korrupte machine-state-Snapshots im Hub).Defekt A: stille
conflicts=0bei Marker-CarryovermergeTexts Fast-Path-Zweige (local===base/remote===base) und der appendOnly-Erfolgspfad gabenconflict:falsezurück, ohne zu prüfen, ob der Gewinner-Inhalt bereits Inline-Konfliktmarker aus einem früheren Pass trägt. In einemsync(Pull dann Push) schrieb ein echter Pull-Konflikt Marker in die lokale Datei und setzte eine saubere Base; der unmittelbar folgende Push sahremote===baseund etikettierte den marker-tragenden lokalen Inhalt als sauberen „local wins" mitconflicts=0, committet in Hub und Base.Fix:
hasConflictMarkers()(auf den eindeutigen<<<<<<<-Opener verankert); jederconflict:false-Rückgabepfad meldetconflict:true, wenn der Gewinner Marker trägt. Payload unverändert, Zähler ehrlich. Der genuine Einzelpass-Konflikt (baut Marker,conflict:true) bleibt unverändert.Defekt B: Echo fremd-owneder Dateien
Kein Ownership-Konzept: eine Speiche bot beim Push eine Peer-
machine-state/frictions-Datei, die sie nur gepullt hatte, als eigene Änderung an (Echo, last-writer-wins gegen den echten Owner-Push).Fix: optionales
SyncPathConfig.ownerScoped; die Push-Sammlung bietet unter einem ownerScoped-Verzeichnis nur<profile>.jsonan, und ein begleitender Base-Map-Filter strippt fremde ownerScoped-Keys, damit derbase∪local-Merge nie eine fremde Datei besucht. Pull unverändert (Peers werden weiter empfangen).ownerScoped:trueaufmachine-state+frictionsin allen drei Profilen;memorybleibt shared.Review-Findings (adversarialer Reviewer, alle behoben)
config.profiledatenintegritätskritisch, ein nicht passender Profilname hätte eine Maschine still aufhören lassen, ihren eigenen State zu publizieren. Jetzt: sichtbare Warnung (vianotes-Zeile der Operation), wenn ein ownerScoped-Verzeichnis Peer-Dateien enthält, aber nicht die eigene<profile>.json; weiter tolerant (keine Exception) bei leerem Verzeichnis. CLI-Resolution unverändert (minimal).hasConflictMarkersauf den<<<<<<<-Opener verengt, setext-=======-Überschriften,===-Divider und tiefe>>>>>>>-Blockquotes in Memory-Markdown werden nicht mehr als Konflikt geflaggt.Verifikation
Rollout / Scope
ownerScopedwirkt erst nachgit pull+ Rebuild der Profile auf allen Maschinen; ohne Flag inert (rückwärtskompatibel). Konsumenten-Härtung (toolchain-parity soll unparsebare Peer-Snapshots warnen) ist separat als harness-Task690fba7cgefiled.Refs: agent-memory task 06d09cde