mirror of
https://github.com/runbear-io/beardrive.git
synced 2026-08-25 08:08:08 +02:00
fix(sync): connecting a folder adopts the project's files instead of forking them (#159)
A device's first cycle on a volume was treated as a concurrent edit for every path the project already held. Whichever side's clock happened to sort higher won -- so a joiner's seeded .bdriveignore or agent-written AGENTS.md could replace the team's -- and the loser landed beside it as a .bdrive-conflict-<device>-<time> file. A first cycle is a join, not an edit. Cycle step 1b holds the scan's ops back over the pull and demotes any whose path the project already holds to lamport 0, which sorts under every op a project can carry (scan's clock starts at 1). The project's version then wins deterministically on every device, and conflictCopies skips those ops the way it already skips re-asserted ones. The local content is still journaled and pushed, so it stays in History and `bdrive restore --list <path>` can bring it back. Reported as `adopted: N`. Concurrent edits after the join are untouched. Claude-Session: https://claude.ai/code/session_01GLCyEWP2XZhYjBssvdCDQm Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
7eabb3baf4
commit
33ca0caeab
@@ -27,18 +27,19 @@ classDiagram
|
||||
}
|
||||
note for Session "syncer also exposes LogEntries (causal order, what bdrive restore walks) plus DisplayTime / SortForDisplay — the newest-first-by-clock order bdrive log prints"
|
||||
note for Session "Restore writes a historical blob back into the working folder as an ordinary edit (fetching it from the hub when this device never held it) — the next Cycle journals it like any other change; it takes no lock and appends to no journal itself"
|
||||
note for Session "internal/syncer — scan → commit local ops → pull peer journals → re-assert withdrawn ops → preserve conflicts → refresh rules → prune → materialize → push blobs then own journal"
|
||||
note for Session "internal/syncer — scan → commit local ops → pull peer journals → adopt on join → re-assert withdrawn ops → preserve conflicts → refresh rules → prune → materialize → push blobs then own journal"
|
||||
note for Session "pull returns TWO lists: newly seen ops, and `gone` — ops a peer deleted from a journal this device had already applied. A peer cannot un-say what we already hold: stillHold re-signs each still-held put into OUR journal (reassertNote). Pull resumes at a byte offset by prefix-matching the local journal copy, so a peer's growing journal is read once"
|
||||
note for Session "Bounds and hardening applied throughout: sizeBound/pullBound cap a fetch against the op's declared size, maxPeerJournals caps how many peers one cycle reads, absorbLamport/tickLamport refuse an absurd peer clock and saturate instead of wrapping, safeMode masks a materialized file to 0777 &^ 0022 (no setuid/setgid, no group/other write), and safeDevice + os.SameFile keep a peer's device id from naming this device's own journal file"
|
||||
note for Session "Prune (bdrive forget / sync --prune, never the daemon) journals a delete for every replayed path the SHARED ignore rules exclude — the include scope is per-device and must never prune it"
|
||||
|
||||
class Result {
|
||||
+LocalOps +PulledOps
|
||||
+Conflicts +Pruned +Materialized
|
||||
+Conflicts +Adopted +Pruned +Materialized
|
||||
+Pushed +Offline +OfflineErr
|
||||
+ReadOnly +NoAccess +AccessErr
|
||||
+Reason() string
|
||||
}
|
||||
note for Result "Adopted counts the paths a JOINING device gave to the project (Cycle step 1b). A first cycle is a join, not an edit: a local file at a path the project already holds is demoted to lamport 0 (adoptNote), so the project's version wins on every device and no conflict copy is made — the local content stays journaled and pushed, which is what bdrive restore reads"
|
||||
note for Result "Offline / ReadOnly / NoAccess are three different answers: unreachable (retry all), push refused (pull-only), pull refused (pause, touch nothing)"
|
||||
note for Result "Reason() is accessReason(AccessErr): the hub's own sentence for a refusal, minus the wrapper chain, dropped unless it passes journal.SafeText. 'read-only' summarizes the STATUS CODE — the sentence is the only thing that tells a device-registration 403 from a project the user really is a reader on"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user