| 1 | # Coordinating cloud edits through separate files |
| 2 | |
| 3 | **Draft specification and investigation, 2026-10-11.** |
| 4 | [#120](https://shale.paperclover.net/snowbound/issues/120). This is separate from |
| 5 | shipping Android. Nothing in this document implements or enables a new storage |
| 6 | mode. Existing local/native and direct SMB publication retain their existing |
| 7 | OneNote-compatible exclusion. The wire examples and ownership policies below |
| 8 | are proposals for review, not frozen public APIs. |
| 9 | |
| 10 | Clover's direction is a notebook's `.snowbound/` directory carrying coordination |
| 11 | between Snowbound clients, with one owner process publishing the real `.one` |
| 12 | files. Other builds exchange separate files instead of racing on those files. |
| 13 | Generic cloud mounts cannot be assumed to implement reliable distributed locks. |
| 14 | This should cover Android/Linux provider-backed notebooks and improve iCloud |
| 15 | coordination. The selected direction is automatic takeover with retained branches |
| 16 | and canonical repair. On 2026-10-11 Clover chose “whichever is the more resilient |
| 17 | even if it's more complicated.” Resilience means keeping recoverable history and allowing an |
| 18 | isolated capable device to progress; it does not mean promising exclusive cloud |
| 19 | writes. The selection below is a design contract, not an implemented protocol. |
| 20 | |
| 21 | ## Selected contract |
| 22 | |
| 23 | ```text |
| 24 | Android / Linux / iOS editor |
| 25 | │ existing edits, locally durable SQLite queue |
| 26 | ▼ |
| 27 | .snowbound/ unique immutable request segments + blobs |
| 28 | │ validated complete bytes and causal dependencies |
| 29 | ▼ |
| 30 | branch owner existing merge and revision append |
| 31 | ├── immutable decision/checkpoint evidence ──► other Snowbound replicas |
| 32 | └── ordinary .one / .onetoc2 files ─────────► eventual provider projection |
| 33 | ``` |
| 34 | |
| 35 | A single application publisher reduces ordinary write/write contention. It |
| 36 | cannot prevent a provider from delivering an old queued upload after ownership |
| 37 | has moved. A successful local flush, a successful handoff, and a unique current |
| 38 | logical owner are three different facts. The design must retain enough immutable |
| 39 | evidence to repair the mutable file after a rollback even if every client has |
| 40 | already received a historical publication receipt. |
| 41 | |
| 42 | Required safety properties: |
| 43 | |
| 44 | - An acknowledged local edit remains in retained local storage until its content |
| 45 | and outcome are recoverable elsewhere under an explicit retention contract. |
| 46 | - Seeing a request, seeing a receipt, or seeing a matching file stamp does not |
| 47 | authorize deletion of the only recoverable request/base/blob. |
| 48 | - Missing, stale, incomplete, repeated, or reordered provider observations never |
| 49 | silently delete notes, grant exclusive ownership, retire a branch, or |
| 50 | establish a complete list of devices. |
| 51 | - Every accepted request has a stable identity, complete validated payload, |
| 52 | known causal base, and a recorded disposition. Retried transport does not |
| 53 | repeat the user edit or create duplicate conflict pages. |
| 54 | - Normal edits use existing ops and append normal revisions. A projection is |
| 55 | never rebuilt by diffing or recreating a page model. |
| 56 | - A dependency that has not arrived means wait. Concurrent incompatible content |
| 57 | means the existing merge produces conflict pages or a preserved recovery |
| 58 | branch. A missing dependency is not a content conflict. |
| 59 | - Locked protected content, unavailable providers, and absent owners retain |
| 60 | encrypted pending edits. No network condition turns a protected edit into a |
| 61 | cleartext file. |
| 62 | - Guarantees identify their observation point: local durability, owner custody, |
| 63 | file publication, and provider/cloud visibility are distinct. |
| 64 | |
| 65 | These requirements depend on at least one retained complete copy of each |
| 66 | necessary object eventually being delivered. No file protocol preserves data |
| 67 | when every durable copy is destroyed, or proves delivery when the provider |
| 68 | permanently withholds it. Eventual projection repair additionally requires that |
| 69 | old uploads eventually stop, required objects are eventually delivered, a |
| 70 | capable reconciler keeps running, and necessary keys/versions remain available. |
| 71 | An unbounded stream of stale overwrites can keep the canonical path regressing |
| 72 | forever. Retained history preserves recoverability; it does not make that path |
| 73 | monotonic under an adversarial delivery schedule. |
| 74 | |
| 75 | ## Storage model and observations |
| 76 | |
| 77 | Assume independent client views of one provider-backed directory tree. Reads and |
| 78 | listings may be stale; files may become visible before their bytes are complete; |
| 79 | notifications and uploads may reorder; a complete old `.one` image may replace a |
| 80 | newer one. A provider may omit known children, return unknown metadata, rename a |
| 81 | file while changing its opaque handle, or reject a nominally supported operation. |
| 82 | Clients can remain offline indefinitely. Process clocks are unrelated. A process |
| 83 | can stop at any instruction and later resume with old state. |
| 84 | |
| 85 | There is no assumed atomic directory rename, distributed exclusive create, |
| 86 | compare-and-swap, reliable distributed advisory lock, complete listing, or |
| 87 | multi-file transaction. Independent objects with unguessably unique identities |
| 88 | reduce collisions; they do not turn a provider's filename API into exclusive |
| 89 | creation. Object integrity is checked after reading complete bytes. Local |
| 90 | app-private SQLite ownership is a different boundary and can still serialize |
| 91 | processes sharing that same local replica. |
| 92 | |
| 93 | Android is one transport, not the definition of the protocol. SAF `rw` opens |
| 94 | provide seeking when supported; that says nothing about remote exclusion or |
| 95 | upload ordering. Provider handles/IDs remain opaque, and returned rename handles |
| 96 | must be retained. iCloud coordination/versions remain useful host capabilities, |
| 97 | but they do not establish distributed fencing for the generic mode. |
| 98 | [Android ContentResolver](https://developer.android.com/reference/android/content/ContentResolver), |
| 99 | [DocumentsContract](https://developer.android.com/reference/android/provider/DocumentsContract). |
| 100 | |
| 101 | A provider that cannot retain separately named byte objects, enumerate them |
| 102 | again, or represent the selected notebook subtree cannot carry this transport |
| 103 | without another adapter. Unsupported capabilities must be observed in disposable |
| 104 | provider fixtures; a provider name is not evidence of support. |
| 105 | |
| 106 | ## Ownership choices and their actual guarantees |
| 107 | |
| 108 | | Candidate | Progress while original owner is absent | Logical ownership | Physical `.one` protection | |
| 109 | | --- | --- | --- | --- | |
| 110 | | Fixed designated publisher | Other clients edit/queue; publication waits | One configured owner under non-duplicated local state | No cross-provider upload fencing; late old uploads still require repair | |
| 111 | | Explicit handoff | New owner proceeds after validated handoff dependencies | Old process durably stops admitting/publishing; successor receives state | Provider may still deliver old process's earlier uploads | |
| 112 | | Timeout/heartbeat takeover | Available to isolated clients | Stale views can authorize two writers | Unsafe as a strict single-writer rule | |
| 113 | | Fixed-member majority consensus | Majority can proceed; minority queues | Possible with durable acceptor promises and valid membership changes | Consensus does not make a generic provider reject stale uploads | |
| 114 | | Branch-preserving recovery takeover | Isolated clients can preserve new branches | Cannot promise one global writer/order without additional agreement | Mutable file remains an eventually repairable projection | |
| 115 | | Fenced server/provider | Depends on service availability | Service validates owner epoch for each accepted write | Can reject stale owner writes when checks cover the actual storage destination | |
| 116 | |
| 117 | No recommendation here may describe a wall-clock lease as sufficient fencing. |
| 118 | Heartbeat objects are discovery/liveness hints only. A process that sees no other |
| 119 | owner cannot distinguish an absent owner from a partition or stale listing. |
| 120 | |
| 121 | Majority agreement is a real alternative, not impossible consensus: fixed |
| 122 | acceptors can persist promises, choose one logical history, and recover after |
| 123 | restart. Its availability cost matters for a phone and one laptop. With two |
| 124 | members, a majority requires both. Membership changes and acceptor-state loss |
| 125 | need a complete protocol; a directory full of claims or votes is not one. |
| 126 | [Paxos Made Simple](https://www.microsoft.com/en-us/research/uploads/prod/2016/12/paxos-simple-Copy.pdf). |
| 127 | |
| 128 | Fencing must be checked by the resource receiving the write. A local owner epoch |
| 129 | inside `.snowbound` cannot force a generic mount to reject a queued older `.one` |
| 130 | upload; the provider does not interpret that epoch. Chubby's sequencers address |
| 131 | precisely delayed operations by requiring validation at the recipient. |
| 132 | [The Chubby lock service](https://research.google.com/archive/chubby-osdi06.pdf). |
| 133 | |
| 134 | **Selected policy: branch-preserving automatic takeover.** Prefer one discovered |
| 135 | publisher during ordinary connected operation. An isolated capable client may |
| 136 | start a fresh recovery branch from its retained validated base, without waiting |
| 137 | for the preferred device or pretending to revoke it. A delay/heartbeat can decide |
| 138 | when to attempt recovery, but cannot authorize deletion, invalidate another |
| 139 | branch, or establish exclusive ownership. Explicit handoff remains the efficient |
| 140 | connected path; a fixed publisher is not a prerequisite for progress. Do not add |
| 141 | majority membership to the first version: its loss of offline availability buys |
| 142 | logical serialization, while leaving the demonstrated physical rollback problem. |
| 143 | |
| 144 | | Resilience dimension | Selected guarantee / cost | |
| 145 | | --- | --- | |
| 146 | | Local edit custody | Existing durable queue plus retained exact dependencies; takeover never discards an absent device's work | |
| 147 | | Isolated-device progress | A capable unlocked device can retain decisions and attempt publication from its complete local closure; missing ancestry/keys still block interpretation | |
| 148 | | Accepted-history recovery | All valid accepted branches and pending requests survive reconciliation, including losing projection versions | |
| 149 | | Canonical availability | Ordinary `.one` remains readable when valid; stale uploads can regress it, and repair can be delayed | |
| 150 | | Eventual convergence | Requires complete eventual delivery, stale writes to cease, compatible keys/versions and a running reconciler; reproducible selection rules must pass the gates below | |
| 151 | | Physical exclusion | Not available on this storage model; no timeout, handoff receipt or sidecar epoch supplies it | |
| 152 | | Storage/backup cost | Sidecar recovery history is essential for registered notebooks; initial version has no automatic GC | |
| 153 | |
| 154 | “One owner writes the real file” becomes **one logical publisher per branch**, |
| 155 | with one locally serialized process owning each live branch incarnation. Peers |
| 156 | send requests to it while it is reachable. A takeover opens a separate branch; |
| 157 | it does not seize the old branch's namespace or credentials. During partitions |
| 158 | there can be several physical publishers, plus provider-delayed uploads from |
| 159 | retired ones. On rejoin, discovered publishers stop adding independent work, |
| 160 | retain their unfinished attempts and coordinate through a reconciler. Its |
| 161 | preference/order is a contention reduction rule, not a global lock. No generic |
| 162 | file-only protocol can guarantee both isolated takeover and exclusive physical |
| 163 | writes under these assumptions. |
| 164 | |
| 165 | Accepted branch outcomes are never revoked because another branch was preferred. |
| 166 | They may become conflict/recovery alternatives in the integrated result. A |
| 167 | publication receipt names its branch and covered input identities; it cannot |
| 168 | claim a globally final order or that every offline branch has been seen. The |
| 169 | registered notebook's recovery authority is the validated retained history, |
| 170 | including pending closures, rather than whichever `.one` snapshot arrived last. |
| 171 | Bare-file imports and intentional restores remain explicit operations below. |
| 172 | |
| 173 | ## Identities and duplicate processes |
| 174 | |
| 175 | Notebook identity must be independent of mount path, provider document ID, |
| 176 | display name, host registration, and filename case normalization. Existing |
| 177 | OneNote file/root identities remain the content identities. A new coordination |
| 178 | notebook identity distinguishes separate imported copies and ownership histories; |
| 179 | how deliberate notebook copies inherit or reset this identity remains a |
| 180 | migration decision, not an inference from their path. |
| 181 | |
| 182 | Use distinct roles: |
| 183 | |
| 184 | | Identity | Lifetime / purpose | |
| 185 | | --- | --- | |
| 186 | | Local installation/device identity | Retained private registration and diagnosis; not sufficient owner authorization | |
| 187 | | Process/writer incarnation | Fresh random identity for each process run; disjoint message namespace | |
| 188 | | Local edit identity | Existing durable replica edit ID plus its originating replica/writer identity | |
| 189 | | Request segment identity | Stable once sealed, retries resend identical bytes | |
| 190 | | Owner epoch / branch identity | Unique branch incarnation plus exact parent frontier; no globally increasing epoch or revocation implied | |
| 191 | | Decision identity | Immutable owner result naming its predecessor and accepted request identities | |
| 192 | | Blob/checkpoint identity | Validated immutable bytes, content digest and exact byte length | |
| 193 | |
| 194 | Previously sealed/outstanding records keep their original request, edit and |
| 195 | segment identities after every restart. A fresh process incarnation names only |
| 196 | new records. Retrying an old outbox may write its original namespace/bytes; it |
| 197 | does not rekey old edits into the new process namespace. Otherwise deduplication |
| 198 | would turn an ordinary restart into a second user action. |
| 199 | |
| 200 | Two processes on one installation must not publish the same live branch because |
| 201 | they share a device ID. A locally owned publisher database/socket serializes |
| 202 | processes sharing reliable private storage. Every process run uses its own new |
| 203 | branch incarnation for newly admitted decisions, referencing retained predecessor |
| 204 | history and unresolved attempts. Recovery can confirm an old prepared attempt |
| 205 | with its original identities; it cannot generate a second outcome for it merely |
| 206 | because the process restarted. |
| 207 | |
| 208 | Copied private profiles on separate devices therefore create distinct new |
| 209 | branches, not competing continuations of one immutable namespace. Already sealed |
| 210 | outbox objects retain their original identity and bytes. Two different decisions |
| 211 | claiming the same immutable identity or contradictory dispositions for one exact |
| 212 | prepared transaction remain a protocol contradiction: quarantine/preserve the |
| 213 | evidence and stop automatic interpretation of the affected history. Valid |
| 214 | independent branch claims are expected recovery inputs. A handoff records the |
| 215 | old branch's retirement before its successor is eligible; stale cached retirement |
| 216 | cannot be assumed visible elsewhere. |
| 217 | |
| 218 | ## Proposed object namespace |
| 219 | |
| 220 | Names illustrate roles; this is not a frozen layout or extension: |
| 221 | |
| 222 | ```text |
| 223 | Notebook/ |
| 224 | Notes.one |
| 225 | Notebook.onetoc2 |
| 226 | .snowbound/ |
| 227 | protocol-<notebook-id>-<object-id> |
| 228 | writers/<incarnation>/segments/<bucket>/<segment-id> |
| 229 | owners/<epoch-id>/decisions/<bucket>/<decision-id> |
| 230 | handoffs/<handoff-id> |
| 231 | blobs/<digest-prefix>/<blob-id> |
| 232 | checkpoints/<checkpoint-id> |
| 233 | recovery/<recovery-id> |
| 234 | ``` |
| 235 | |
| 236 | Every published object is immutable. A transport retry keeps its identity and |
| 237 | bytes. Correcting an object creates a distinct object referencing what it |
| 238 | supersedes. Mutable convenience indexes or heartbeats may accelerate discovery |
| 239 | but cannot authorize publication, retirement, or data loss. No implementation |
| 240 | may overwrite one shared `owner.json` or `inbox.json` as the coordination truth. |
| 241 | |
| 242 | Per-process directories avoid ordinary client/client name collisions. Unique |
| 243 | logical object IDs also appear inside the envelope. Two provider documents with |
| 244 | one display name are not automatically one object; the same object under two |
| 245 | names/handles is one logical object only after matching its validated identity |
| 246 | and bytes. Different bodies claiming one immutable identity are a contradiction |
| 247 | and are preserved/quarantined. This cannot retroactively undo side effects that |
| 248 | already occurred before the contradiction became visible. |
| 249 | |
| 250 | No one file per keystroke: transport sealed batches from the existing queue into |
| 251 | segments bounded by encoded bytes and editing bursts. The durable local receipt |
| 252 | remains immediate; the provider upload can coalesce multiple edits. Individual |
| 253 | edit IDs remain present for receipts/dedup. Attachments and large bodies are |
| 254 | separate immutable blobs; transport does not repeatedly embed them in every |
| 255 | segment. Exact segmentation thresholds require measurements, not a public |
| 256 | extension promise. |
| 257 | |
| 258 | ## Framing, validation, and wire examples |
| 259 | |
| 260 | Before accepting an object, check magic/version, bounded envelope/body lengths, |
| 261 | notebook identity, declared kind, complete record digest, sender/object identity, |
| 262 | and all required dependencies. Integrity covers the entire encoded envelope and |
| 263 | body, including identities, owner epoch, predecessor/base/dependency references, |
| 264 | kind and version. A body-only digest permits metadata mixups and is insufficient. |
| 265 | A trailing digest or excluded self-digest field must have an unambiguous encoding |
| 266 | rule. Digests prove byte consistency, not permission to edit. If access/authentication is added, it covers the envelope and dependencies |
| 267 | as well as payload bytes. Provider ACLs and section passwords remain separate. |
| 268 | |
| 269 | Do not trust size/mtime metadata as completeness. A parseable JSON prefix or an |
| 270 | apparently complete `.one` header is not enough; an object references its exact |
| 271 | complete body length/digest. A readiness marker may arrive before the body and |
| 272 | merely adds a dependency to wait for. Atomic rename is unnecessary for acceptance |
| 273 | only if incomplete objects are rejected and complete objects stay immutable. |
| 274 | |
| 275 | Illustrative request envelope (symbolic IDs/digests, not executable wire data): |
| 276 | |
| 277 | ```json |
| 278 | { |
| 279 | "protocol": 1, |
| 280 | "kind": "edit-segment", |
| 281 | "notebook": "N", |
| 282 | "id": "writer-R/segment-S", |
| 283 | "writer": "R", |
| 284 | "predecessor": "writer-R/segment-before-S", |
| 285 | "base": {"section": "OneNote-file-identity", "checkpoint": "C", "decisions": ["D"]}, |
| 286 | "edits": [{"replica": "Q", "id": 42, "author": "Alice", "opsObject": "O"}], |
| 287 | "dependencies": ["C", "D", "O", "blob-B"], |
| 288 | "body": {"length": 1234, "sha256": "complete-body-digest"}, |
| 289 | "recordDigest": "digest-of-envelope-and-body-excluding-this-field" |
| 290 | } |
| 291 | ``` |
| 292 | |
| 293 | Illustrative decision and publication receipt: |
| 294 | |
| 295 | ```json |
| 296 | { |
| 297 | "protocol": 1, |
| 298 | "kind": "owner-decision", |
| 299 | "id": "D-next", |
| 300 | "ownerEpoch": "E", |
| 301 | "predecessor": "D", |
| 302 | "accepted": ["writer-R/segment-S"], |
| 303 | "dispositions": [{"replica": "Q", "id": 42, "result": "merged-or-conflict-retained"}], |
| 304 | "section": "OneNote-file-identity", |
| 305 | "baseCheckpoint": "C", |
| 306 | "resultCheckpoint": "C-next", |
| 307 | "revisions": {"page-space": "sealed-revision-identity"}, |
| 308 | "dependencies": ["D", "C", "C-next", "writer-R/segment-S"], |
| 309 | "recordDigest": "digest-of-envelope-and-body-excluding-this-field" |
| 310 | } |
| 311 | ``` |
| 312 | |
| 313 | ```json |
| 314 | { |
| 315 | "protocol": 1, |
| 316 | "kind": "projection-receipt", |
| 317 | "id": "P", |
| 318 | "decision": "D-next", |
| 319 | "section": "OneNote-file-identity", |
| 320 | "observed": {"header": "OneNote-stamp-header", "length": 5678}, |
| 321 | "checkpoint": "C-next", |
| 322 | "guarantee": "owner-local-file-confirmed", |
| 323 | "recordDigest": "digest-of-envelope-and-body-excluding-this-field" |
| 324 | } |
| 325 | ``` |
| 326 | |
| 327 | The examples omit separate payload bytes. `recordDigest` is symbolic: the codec |
| 328 | must define exact bytes and self-field exclusion before implementation. Every |
| 329 | object kind requires the same metadata integrity, not only edit segments. |
| 330 | |
| 331 | Version 1 is illustrative. The actual serialization must reuse existing |
| 332 | `onestore::op::Edit`/`Op` semantics, with an explicit compatibility boundary |
| 333 | around them. Existing serde representation is not independently promised stable |
| 334 | forever. Do not invent a second page-diff language or CRDT. Live Share's bounded |
| 335 | op batch plus named revisions demonstrates an existing reusable approach; its |
| 336 | network membership/timeout assumptions do not transfer automatically to cloud |
| 337 | files. |
| 338 | |
| 339 | ## Causal bases, merges, and replay identity |
| 340 | |
| 341 | A segment names its exact input base and prior writer segment. A client editing |
| 342 | without the owner continues its own local causal chain. The owner waits for a |
| 343 | complete base/checkpoint, referenced blobs, and required predecessor decisions |
| 344 | before interpreting positional text ops. An old base is not permission to apply |
| 345 | UTF-16 offsets directly to a changed page. |
| 346 | |
| 347 | Reuse the current queue/merge machinery: `PendingEdit` carries author and ops; |
| 348 | `Replica` keeps its base, seals batches, rebases against changed remote state, |
| 349 | and makes conflict pages for incompatible operations. Existing |
| 350 | `resolve::merge` discovers common revision ancestry and deterministic merged |
| 351 | revision identities for retained competing section versions. Where no shared |
| 352 | valid ancestry exists, preserve the foreign/unreadable branch for recovery. |
| 353 | |
| 354 | Deduplication is two related checks: |
| 355 | |
| 356 | 1. Request identity and encoded content digest identify a transport retry. |
| 357 | 2. Stored named revision identities/outcomes identify work already incorporated |
| 358 | into a section/checkpoint, including after owner restart or request replay. |
| 359 | |
| 360 | A numeric edit ID alone is local to one replica. A file stamp alone does not |
| 361 | prove every request's effect is present. A page's visible text matching expected |
| 362 | text also does not prove correct history, attachments, or membership outcome. |
| 363 | |
| 364 | An unmergeable request or section branch remains retained with its original |
| 365 | request/base/blobs and exact error. Custody acknowledgement of |
| 366 | `retained-unresolved` is distinct from semantic acceptance and from file |
| 367 | publication. It cannot appear in an accepted edit list as though the note had |
| 368 | merged. A recoverable conflict outcome names the actual stored conflict page; |
| 369 | a retained foreign/unreadable branch names recovery evidence instead. |
| 370 | |
| 371 | Owner decisions impose an order within their branch. Concurrent branches remain |
| 372 | explicit inputs to integration; never choose the newest wall clock or discard |
| 373 | content because its owner ID sorts later. Stable reconciliation ordering chooses |
| 374 | presentation and replay order among already validated inputs. It is neither |
| 375 | consensus nor permission to edit. The same request accepted independently by two |
| 376 | branches still denotes one user action; section revision identity alone is not a |
| 377 | complete cross-branch request deduplication scheme. |
| 378 | |
| 379 | ## Owner state and write/ack ordering |
| 380 | |
| 381 | Proposed durable states, persisted before their corresponding externally |
| 382 | observable promise: |
| 383 | |
| 384 | | State | Required evidence | Permitted action | |
| 385 | | --- | --- | --- | |
| 386 | | Discovered | Candidate object handles | Read/validate; no ack | |
| 387 | | Complete | Exact body validated | Wait for dependencies or stage locally | |
| 388 | | Ready | Causal base/blobs/ownership validated | Prepare existing ops/merge | |
| 389 | | Retained unresolved | Original request/base/blob closure and exact failure retained | Report custody only; preserve for later merge/review | |
| 390 | | Prepared | Stable request disposition, named revisions, reconstructible input/base retained | Attempt normal append | |
| 391 | | Attempt uncertain | Append/header/flush result ambiguous | Inspect/confirm exact evidence; never blindly reapply | |
| 392 | | File confirmed locally | Named revisions/stamp confirmed at owner's actual adapter boundary | Record historical local publication evidence | |
| 393 | | Recoverable result | Immutable input/decision/checkpoint closure available under custody contract | Publish decision/result receipt | |
| 394 | | Receipted | Request identity maps to durable outcome and recovery closure | Client may advance display/status; retention rules still apply | |
| 395 | |
| 396 | The exact linearization between durable prepared decision, file append, and |
| 397 | immutable result publication needs implementation tests. The invariant is |
| 398 | stronger than a particular file ordering: no externally acknowledged request |
| 399 | may depend solely on mutable `.one` bytes or a volatile owner map. A crash at any |
| 400 | point must leave either an unacknowledged retained request or a recoverable |
| 401 | recorded outcome. Checkpoints are raw section bytes/named append artifacts, |
| 402 | not projected page models. |
| 403 | |
| 404 | An owner can prepare and retain a reproducible named transaction before an |
| 405 | append; after an ambiguous append it uses revision evidence and existing |
| 406 | `CommitState`/confirmation logic. It does not regenerate new revision IDs and |
| 407 | retry the user action. Appending successfully and crashing before writing the |
| 408 | receipt must converge to the same outcome when the original request returns. |
| 409 | |
| 410 | If the provider exposes the receipt before its referenced checkpoint/decision, |
| 411 | the reader waits. If the `.one` file is behind the receipt, the reader obtains |
| 412 | or reconstructs the referenced section from its recovery closure; it does not |
| 413 | claim the mutable provider path already holds it. If the file is ahead, it can |
| 414 | read validated revisions while separately resolving incomplete decision history. |
| 415 | |
| 416 | Once a client has validated a selected decision/checkpoint, a stale bare `.one` |
| 417 | observation must not downgrade its known accepted history. Keep that history |
| 418 | and recovery dependencies locally while repairing the projection. An explicit |
| 419 | intentional restore is a new recorded operation, not a stale-file inference. |
| 420 | |
| 421 | An owner-unavailable client still reads, navigates, edits and uses Undo from its |
| 422 | private replica. A capable client may start a recovery branch; otherwise requests |
| 423 | wait for a capable publisher without blocking local use. Publishing status cannot |
| 424 | report another device's custody merely because local creation of a provider |
| 425 | object succeeded. |
| 426 | |
| 427 | ## Explicit handoff, recovery takeover, and late uploads |
| 428 | |
| 429 | Proposed explicit handoff sequence: |
| 430 | |
| 431 | 1. Old owner stops admitting new work for the epoch and persists that stop in |
| 432 | its retained local owner state. It retains requests arriving later. |
| 433 | 2. Seal/record admitted decisions and their complete recovery closure; publish |
| 434 | an immutable handoff naming successor, last decision, checkpoints and pending |
| 435 | uncertainty. No time-based claim of a complete device/request census. |
| 436 | 3. Successor validates the chain, retrieves every required dependency, resolves |
| 437 | uncertain attempts, and records acceptance in its own durable state. |
| 438 | 4. Successor publishes only under the selected handoff policy. Old owner never |
| 439 | resumes that epoch after a restart; a failed handoff remains recoverable. |
| 440 | 5. Both retain historical recovery evidence, because old provider uploads can |
| 441 | arrive after all application steps have completed. |
| 442 | |
| 443 | A receipt from the successor is useful evidence for operator recovery; it does |
| 444 | not prove every cloud cache drained. Waiting an arbitrary duration is not a |
| 445 | fence. A fixed owner with no ownership transitions avoids one source of late |
| 446 | writes but still needs rollback repair for reordered uploads from its own run. |
| 447 | |
| 448 | Automatic takeover keeps each candidate's decisions in its own immutable branch. |
| 449 | Before admitting work it durably records the exact validated parent frontier, |
| 450 | base/checkpoint closure, its fresh incarnation and any uncertain attempts. Missing |
| 451 | dependencies remain pending; a candidate cannot claim complete recovery from an |
| 452 | incomplete listing. It cannot overwrite another owner's mailbox/checkpoint or |
| 453 | claim exclusive authority based on silence. A branch may progress with a complete |
| 454 | known subset while clearly retaining that subset's identity; it must not label |
| 455 | it the complete notebook history. |
| 456 | |
| 457 | An old process resuming after takeover must inspect discovered branch/handoff |
| 458 | state before new publication, preserve its old outbox, and join reconciliation |
| 459 | when it can. A stale view can still hide that state, so this check is best effort |
| 460 | contention control. The safety mechanism is retained branches and attempts, |
| 461 | not an assertion that the old process can always be stopped. Ordinary canonical |
| 462 | `.one` files may regress or produce provider conflict versions until reconciliation |
| 463 | and delivery settle. |
| 464 | |
| 465 | A quorum alternative can choose an ownership/decision chain if acceptor |
| 466 | membership and durable promises are implemented. It still needs immutable |
| 467 | recovery and projection repair unless the actual provider enforces epochs on |
| 468 | writes. Do not add a quorum merely to hide this physical fencing limitation. |
| 469 | |
| 470 | ## Reconciliation and canonical repair |
| 471 | |
| 472 | The selected policy needs a decision DAG, not a fabricated globally monotonic |
| 473 | epoch. Each branch decision names its predecessor, exact causal input frontier, |
| 474 | prepared transaction/outcome and complete recovery closure. An integration |
| 475 | decision names every covered source request/branch outcome and retained unresolved |
| 476 | alternative. Its references can arrive in any order. Validation waits for the |
| 477 | referenced closure; it never invents missing content from visible filenames. |
| 478 | |
| 479 | ```text |
| 480 | retained seed ──► A decisions ──► A checkpoint ──┐ |
| 481 | └► B decisions ──► B checkpoint ──┼► integration plan / checkpoint |
| 482 | └► offline pending queue ─────────┘ │ |
| 483 | ordinary append / repair |
| 484 | ▼ |
| 485 | mutable .one path |
| 486 | ``` |
| 487 | |
| 488 | A candidate implements this sequence durably: |
| 489 | |
| 490 | 1. Gather validated known branch frontiers, pending requests, external versions |
| 491 | and uncertain attempts. Follow references, preserving exact source bytes; |
| 492 | listing completeness is never asserted. Keep the last valid local view if a |
| 493 | newly observed frontier is incomplete, malformed, foreign or unsupported. |
| 494 | 2. Resolve exact prepared-attempt identities before admitting a retry. Build a |
| 495 | coverage map from `(originating replica, edit ID)` to source request digest, |
| 496 | branch dispositions, named revisions and recovery closure. Identical transport |
| 497 | retries are one action. Independently rebased outcomes of that action are not |
| 498 | automatically identical: retain both, select one semantic application and |
| 499 | record the other as an alternate or retained-unresolved. Selecting one outcome |
| 500 | cannot discard its alternate's dependent descendants or reinterpret their ops |
| 501 | against the chosen base. Require validated causal translation/rebase; otherwise |
| 502 | retain the complete alternate-and-descendant closure unresolved. Contradictory |
| 503 | bytes for one immutable identity halt automatic interpretation of that history. |
| 504 | 3. Form a reproducible integration plan for the **complete known input set**. |
| 505 | Its identity covers notebook lineage, named seed/base, source frontier and |
| 506 | coverage, merge/codec version, stable causal processing order and all outcome |
| 507 | dependencies. Dependency order comes first, then a specified byte order of |
| 508 | immutable source identities for concurrent inputs. Clocks, directory order, |
| 509 | local arrival order and largest epoch are excluded. A newly discovered input |
| 510 | makes a new plan; it cannot make the old plan evidence disappear. |
| 511 | 4. For incomparable histories, choose the same retained primary checkpoint under |
| 512 | that ordering, then rebase the remaining original inputs through existing |
| 513 | ops/retained-version merge. Preserve conflict pages and every opaque/unmergeable |
| 514 | version. Do not integrate “whatever this device's mutable file currently is” |
| 515 | as an unspecified primary. If normal incremental integration depends on |
| 516 | arrival order, reproduce the plan from its named retained raw base in a |
| 517 | disposable section and append normal transactions. No page-model reconstruction |
| 518 | or whole-page diff/save shortcut is allowed. This may cost more I/O. |
| 519 | 5. Persist exact prepared revisions, input coverage, result raw section/catalog |
| 520 | checkpoint and external blobs before issuing a recoverability promise. An |
| 521 | integration checkpoint is a result over source inputs, not another independent |
| 522 | edit. Subsequent branches carry inherited coverage; replaying integrations as |
| 523 | new source edits must not multiply conflicts. Same plan/outcome retries reuse |
| 524 | their identities. Existing named-revision dedup alone does not solve every |
| 525 | independently rebased or differently ordered plan. |
| 526 | 6. Confirm/publish using the actual adapter's append and ambiguity rules. If a |
| 527 | valid projection has rolled back, retain those bytes, import unknown external |
| 528 | revisions as separate inputs, then restore the chosen raw checkpoint or append |
| 529 | repair revisions as the adapter requires. Copying a validated raw checkpoint |
| 530 | repairs a materialized store; it is not recreating notes from a page model. |
| 531 | Retain the checkpoint even after local confirmation. Receipt coverage names |
| 532 | the integrated inputs and retained alternatives, not a globally complete view. |
| 533 | 7. Recheck after publication and on later observations. A delayed stale upload |
| 534 | starts another repair with the same retained history; it does not reapply user |
| 535 | edits. Once a fresh replica sees the chosen checkpoint and the same inputs, |
| 536 | another reconciliation must produce no extra edit/conflict revisions. A |
| 537 | publisher that cannot satisfy these steps retains work without claiming a |
| 538 | recoverable integrated outcome. |
| 539 | |
| 540 | Stable ordering is a proposed integration mechanism to validate, not a claim that |
| 541 | today's `resolve::merge` is commutative. The existing merge keeps the current |
| 542 | branch as the main page when edits overlap. Its deterministic revision names help |
| 543 | retry an original branch, but neither equal names nor presence of both texts |
| 544 | proves equal primary/conflict placement, causal coverage or byte-identical files. |
| 545 | Plan equivalence must compare source/outcome coverage, main and conflict page |
| 546 | identities/content, tombstones, catalog and blob closure. Physical allocation, |
| 547 | header bookkeeping and encoding can differ without a semantic disagreement; |
| 548 | such differences must not trigger endless republishing. |
| 549 | |
| 550 | The implementation gate is stronger than static sorted input testing: two fresh |
| 551 | replicas receiving the same branches in opposite orders, including intermediate |
| 552 | integration checkpoints and new edits based on them, must converge to equivalent |
| 553 | covered outcomes without duplicate conflict pages or silently repeated requests. |
| 554 | Test at least three concurrent branches, ancestor/descendant arrivals, one request |
| 555 | accepted on two branches, namespace operations and protected/opaque payloads. |
| 556 | If the current merge cannot satisfy this, retain unresolved alternatives and fix |
| 557 | the integration algorithm before claiming automatic convergence. Recovery custody |
| 558 | can remain safe while automatic integration is unavailable. |
| 559 | |
| 560 | Eventual repair is conditional. For a finite settled input set, its complete |
| 561 | closures must eventually reach a compatible unlocked reconciler; stale uploads |
| 562 | must eventually stop replacing the path; and a successful repair must eventually |
| 563 | be visible. A deterministic plan alone cannot guarantee these properties. |
| 564 | Repeated infinite old uploads are an explicit liveness counterexample. No finite |
| 565 | number of quiet reads proves that the last stale upload has arrived. Lack of |
| 566 | quiescence must consume retry/backoff budget, not user history or retained bases. |
| 567 | |
| 568 | ## Authority and disaster recovery |
| 569 | |
| 570 | The `.one` files remain the interoperable materialized section format. For an |
| 571 | explicitly registered coordinated notebook, validated retained sidecar history |
| 572 | and its complete closures are authoritative for accepted/pending work when |
| 573 | mutable files regress. Registration binds notebook lineage, section identities, |
| 574 | protocol version and initial recovery closure; an arbitrary adjacent `.snowbound` |
| 575 | folder has no authority over a bare import or a different section at the same |
| 576 | path. This changes the backup contract for the selected mode, not the `.one` |
| 577 | format or native/SMB modes. |
| 578 | |
| 579 | Distinguish three recovery operations: |
| 580 | |
| 581 | | Operation | Meaning | |
| 582 | | --- | --- | |
| 583 | | Open a registered coordinated notebook | Reconcile validated selected history/retained branches with its section identities and observed projection | |
| 584 | | Import a bare `.one`/notebook copy | Treat validated bytes as an import base; no historical receipts or missing sidecar authority inferred | |
| 585 | | Restore a coordinated notebook backup | Restore a consistent closure or explicitly import/reconcile incomplete histories; never reset edit IDs/epochs silently | |
| 586 | |
| 587 | Durable custody is always qualified by its adapter: a private SQLite transaction |
| 588 | is locally retained; a provider close is not a cloud durability receipt. A client |
| 589 | must not evict its only outbox because the owner merely closed a provider file. |
| 590 | Cross-device verified copies or an explicit retained owner-private closure have |
| 591 | their own failure domains, which the recovery contract must name. |
| 592 | |
| 593 | A portable backup of a coordinated notebook needs the section bytes plus every |
| 594 | retained immutable object required by accepted decisions and pending requests. |
| 595 | Copying only `.one` is a valid interoperable snapshot, but not proof that |
| 596 | already receipted cloud edits are recoverable. Copying only `.snowbound` may lack |
| 597 | the seed images or external payloads needed for reconstruction. |
| 598 | |
| 599 | The mode must define when a deliberately restored older `.one` is an intentional |
| 600 | user restore versus provider rollback. Timestamps cannot distinguish them. |
| 601 | A deliberate restore needs a new explicit operation/epoch/disposition preserving |
| 602 | the previous accepted branch, rather than deleting its evidence. Exact copy and |
| 603 | restore UI semantics remain owner-facing design work after evidence; no current |
| 604 | build is changed by this draft. |
| 605 | |
| 606 | ## External writers, replacement, and repair |
| 607 | |
| 608 | On each projection change validate store/header/checksums, section root/file |
| 609 | identity, and revision ancestry. The owner compares the actual base stamp at the |
| 610 | moment of normal publication using available adapter guards; coordination mode |
| 611 | never falls back by silently dropping an existing lock. |
| 612 | |
| 613 | A matching identity with extra external revisions is an import/merge candidate. |
| 614 | The existing tolerant reader and retained-version merge can preserve content. |
| 615 | Record the imported exact base/version so later requests have stable ancestry. |
| 616 | A different section at the same filename is foreign content, not a new base for |
| 617 | old queued ops. Preserve it and stop automatic destructive reconciliation. |
| 618 | Unreadable/partially repaired images do not replace the last validated base. |
| 619 | A repair must record the chosen input and retained rejected alternatives. |
| 620 | |
| 621 | Concurrent OneNote edits on a generic cloud mount bypass the coordination |
| 622 | protocol. This mode cannot guarantee safe live interchange with such writers. |
| 623 | Interoperable native filesystem/direct SMB mode remains the route for OneNote |
| 624 | 2010 side-by-side editing. A user opening a resulting `.one` offline still gets |
| 625 | a normal OneNote file; that does not make its cloud upload fenced. |
| 626 | |
| 627 | On iCloud, continue reading unresolved `NSFileVersion` conflicts as recovery |
| 628 | inputs. Multi-file coordination can reduce ordinary competing application |
| 629 | writes and give them retained request history; it cannot promise the operating |
| 630 | system will create zero conflict versions, nor retire those versions before |
| 631 | complete recoverability is established. |
| 632 | |
| 633 | ## Namespace, TOC, moves, deletes, and Undo |
| 634 | |
| 635 | A notebook has multiple files: section groups, `.onetoc2`, recycle-bin sections, |
| 636 | external payload folders, and section files. A single-section protocol is not |
| 637 | a notebook protocol. Namespace operations are owner-processed intentions named |
| 638 | by stable section/page identities and expected causal state; paths are results. |
| 639 | Only the publisher changes shared canonical namespace/TOC in this mode. |
| 640 | |
| 641 | Create/rename/move/delete outcomes reference all files and object identities |
| 642 | they changed. Provider creation and rename return actual handles; do not infer |
| 643 | success from a requested filename or overwrite an unrelated same-name item. |
| 644 | A provider may expose TOC before a new section or source deletion before a |
| 645 | moved section arrives. Consumers wait for the decision's full closure and keep |
| 646 | last valid local catalog state. |
| 647 | |
| 648 | Multi-file changes use resumable intent records, not fictitious atomicity. |
| 649 | For page/section moves, establish destination durability/recoverability before |
| 650 | retiring the source, validate expected source stamp and queued generation under |
| 651 | actual writer coordination, and make retries identity-stable. Without physical |
| 652 | transaction/fencing, prefer a preserved duplicate to loss. Do not mix this |
| 653 | unsettled transport implementation with the separately investigated |
| 654 | cross-notebook move work. |
| 655 | |
| 656 | Delete is a durable tombstone/outcome, never merely disappearance in a listing. |
| 657 | Retain the historical deleted branch or recycle-bin closure required by Undo |
| 658 | and offline rejoin. An old already-accepted create must not recreate a deleted |
| 659 | object when replayed. A genuinely unseen edit based before deletion receives an |
| 660 | explicit merge/conflict/recovery disposition; its content is never dropped by a |
| 661 | membership timeout. Undo is a new conditional intention against actual latest |
| 662 | content, not blindly replaying an old whole-image inverse over later edits. |
| 663 | |
| 664 | ## Protected content and blobs |
| 665 | |
| 666 | The existing protected replica queue seals edits/payloads under keys derived |
| 667 | from the section key; payload names use keyed digests. Reuse those semantics |
| 668 | when transporting protected data, with fresh nonces and authenticated envelope |
| 669 | context as appropriate. A plain SHA-256 of private attachment bytes can leak |
| 670 | cross-notebook equality; choose protected blob identifiers consistently with |
| 671 | the existing keyed queue. No cleartext ops, attachment previews, page titles, or |
| 672 | raw protected payloads may be written into `.snowbound` as a transport shortcut. |
| 673 | |
| 674 | An owner cannot semantically merge protected requests without the required |
| 675 | section key. Proposed behavior is retain encrypted requests and wait for an |
| 676 | unlocked capable publisher; an encrypted raw-transaction path must preserve |
| 677 | existing validation and cannot pretend it can rebase unknown plaintext. Whether |
| 678 | an always-on publisher may retain unlocked keys and for how long is a policy |
| 679 | question for later design, not an implicit background service requirement. |
| 680 | Password changes/rekeying need explicit key-epoch dependencies, retained old |
| 681 | keys/requests policy, and safe receipts before old material is retired. |
| 682 | |
| 683 | Unprotected pictures/attachments/video remain byte payloads associated with |
| 684 | existing ops. Blobs arrive before or after requests; missing blob means wait. |
| 685 | Validate exact length/digest, reject oversized declarations before allocation, |
| 686 | bound concurrent reads, and stream where the existing storage APIs permit. |
| 687 | Preserve external `_onefiles` identities and original bytes. A raw section |
| 688 | checkpoint does not automatically include external payload files. |
| 689 | |
| 690 | ## Retention, compaction, and device rejoin |
| 691 | |
| 692 | The first implementation retains immutable logs/checkpoints without automatic |
| 693 | deletion. Storage use is real, so measure it and design compaction; finite retention |
| 694 | must not be disguised as a correctness proof. |
| 695 | |
| 696 | No “all devices have seen it” rule can be inferred from current listings or |
| 697 | heartbeats. An offline device may hold unpublished edits indefinitely. Deleting |
| 698 | an accepted request after observing `.one` alone is unsafe because that file |
| 699 | can later roll back. Receipt existence alone is also insufficient if the |
| 700 | receipt's reconstructible dependencies are removed. |
| 701 | |
| 702 | A compacted checkpoint needs complete section/catalog/blob state, accepted |
| 703 | request identity/outcome coverage, selected history and retained branch/tombstone |
| 704 | coverage, plus its exact predecessor closure or an explicit permanent cutoff. |
| 705 | Only a verified replacement recovery closure can justify retiring old objects. |
| 706 | Keep dedup/tombstone evidence needed to recognize old retries after compaction. |
| 707 | |
| 708 | For an offline client older than retained ancestry, retain its private queue and |
| 709 | import a fresh base without discarding it. If exact rebase is unavailable, preserve |
| 710 | its old base/ops as a recovery branch and require an explicit content outcome. |
| 711 | A destructive administrative reset establishes a new notebook lineage and keeps |
| 712 | an export of displaced data; it does not make stale requests silently valid. |
| 713 | An app uninstall destroying the sole private outbox remains an actual loss |
| 714 | boundary unless that outbox reached retained custody elsewhere. |
| 715 | |
| 716 | ## Scale and discovery cost |
| 717 | |
| 718 | A long notebook cannot relist a flat object directory on every keypress. Segment |
| 719 | existing sealed batches, partition by writer/owner and bounded buckets, cache |
| 720 | validated handles/objects locally, follow known dependency/predecessor references, |
| 721 | and use notifications as hints plus periodic reconciliation. Mutable indexes |
| 722 | may reduce scans but cannot establish absence or completeness. |
| 723 | |
| 724 | SAF opaque document IDs make filename-derived constant-time lookup unavailable |
| 725 | in general. Resolve actual handles from queried parents and cache them by |
| 726 | validated object identity. Bucket size and deep directory cost need real provider |
| 727 | measurements; a huge hierarchy can cost more than a flat list. Unknown new writer |
| 728 | discovery still needs enumeration, so “bounded lookup” cannot mean a complete |
| 729 | new-client discovery guarantee without scanning any provider state. |
| 730 | |
| 731 | Measure at least: edits per sealed segment; object and request count; bytes |
| 732 | including retained bases/blobs; cold-open queries; warm sync queries; maximum |
| 733 | listing size; incomplete dependency backlog; latency until local durability, |
| 734 | owner custody and visible remote result; and time to rebuild after projection |
| 735 | rollback. Record provider/API/build identity. A host memory microbenchmark is |
| 736 | not a Google Drive/iCloud listing benchmark. |
| 737 | |
| 738 | ## Status and receipt meanings |
| 739 | |
| 740 | These are engineering states, not proposed public UI strings: |
| 741 | |
| 742 | | Evidence | What is known | What is not known | |
| 743 | | --- | --- | --- | |
| 744 | | Local queue durable | This installation can recover the edit after process death | Provider/owner received it | |
| 745 | | Provider create/close succeeded | This adapter completed local object I/O | Another device or cloud service has it durably | |
| 746 | | Owner intake durable | Owner has validated recoverable request/dependencies under stated custody | Canonical `.one` contains it | |
| 747 | | Owner result recoverable | Accepted outcome can be reconstructed from retained closure | Every client has downloaded it | |
| 748 | | Owner file locally confirmed | Normal append/confirmation completed at owner adapter | Cloud globally ordered/durable, no later rollback | |
| 749 | | Other client observed result | That client's validated state includes decision/checkpoint | All devices saw it or GC is safe | |
| 750 | | Historical published receipt | A publication was confirmed at an earlier point | Its effect remains in today's mutable projection | |
| 751 | |
| 752 | Current `EditStatus::Published` is historical by design. Adding transport states |
| 753 | must preserve that meaning and the existing local receipt boundary. Current |
| 754 | mobile `sb_section_poll` coarse saved/saving/offline values cannot silently |
| 755 | encode these extra guarantees; UI flow/copy changes require a separate approved |
| 756 | mapping after protocol behavior exists. |
| 757 | |
| 758 | ## Fault matrix and practical investigation |
| 759 | |
| 760 | Lab evidence lives outside source under |
| 761 | `target/multi-file-sync-spec-20261011/`. It is a bounded investigation, not a |
| 762 | production backend or commercial-provider certification. The Rust lab uses the |
| 763 | existing `onestore::Transaction::commit`, `notebook::Replica`, receipt logic, |
| 764 | retained-version merge, and cold parser. Provider delivery is a deterministic |
| 765 | simulation; logical drop/reopen is not an OS-kill durability test. The separate |
| 766 | Python model explores object framing/dependencies and ownership rules only. |
| 767 | |
| 768 | ### Recorded results |
| 769 | |
| 770 | The following are observed experiment outcomes. The requirements and future |
| 771 | implementation gates elsewhere in this draft are not proven by these tests. |
| 772 | Native-corpus Rust run 7 completed with exit 0, and transport-model run 3 |
| 773 | completed with exit 0. Reports and raw `.one` outputs remain in the ignored lab |
| 774 | folder; the verification manifest binds their hashes and the exact lab sources. |
| 775 | |
| 776 | | Experiment | Observed outcome | Evidence | |
| 777 | | --- | --- | --- | |
| 778 | | Heartbeat/lease takeover with stale view | Counterexample found: two independently valid append results; winning snapshot omits ALPHA | `run-7/report.json` → `lease_takeover_split_writer` | |
| 779 | | Explicit handoff, then old provider upload | Counterexample found: exact ALPHA BETA becomes ALPHA RIGHT after delayed snapshot delivery, despite no concurrent application writers | `clean_handoff_late_old_upload` | |
| 780 | | Retained independent section branches | Existing merge yields ordinary text exactly ALPHA BETA | `independent_immutable_branches_recover` | |
| 781 | | Late-upload rollback with retained checkpoint | Existing retained-version merge recovers BETA | `late_upload_recovered_from_immutable_checkpoint` | |
| 782 | | Concurrent overlapping changes | Main page retains BOB RIGHT; actual conflict page retains ALICE RIGHT | `overlap_retains_actual_conflict_page` | |
| 783 | | Append succeeds, reply lost, logical close/reopen | One publication total; existing receipt settles without another append | `owner_append_then_crash_before_ack` | |
| 784 | | Historical Published receipt, then projection rollback | Counterexample found: queue empty and historical receipt remains Published while ALPHA is absent | `historical_receipt_does_not_repair_projection_rollback` | |
| 785 | | Resumed old-owner branch and new-owner branch | Existing merge retains OLD and NEW; strict owner uniqueness not established | `old_owner_resumes_branch_preservation` | |
| 786 | | Old offline branch retained through rejoin | Existing merge can import its ALPHA content | `offline_rejoin_request_retention` | |
| 787 | | Original branch redelivered after merge | For each recovery case: three same-database reopen/retries plus one genuinely fresh replica seeded from merged bytes; zero extra publications, identical output | Recovery case `proof` fields | |
| 788 | | Generated minimal section seed | Current cold parse succeeds for both same-file/root branches; exact existing resolve returns “Object space has no active default revision” | `run-7/generated-seed-diagnostic.json` | |
| 789 | | Abstract transport model | 737 truncated prefixes and 737 single-byte mutations rejected; 120 arrival permutations converge once with omission/duplicates; schema/cycle/contradiction checks pass | `visibility-run-3/visibility-report.json` | |
| 790 | | Abstract deep input | 1,101-object dependency chain traversed iteratively; nested 1,100-level JSON rejected without recursion crash | Same transport report | |
| 791 | |
| 792 | The generated-seed diagnostic is specifically the current `create_section` |
| 793 | helper's output and those exact edits. Current-image parsing does not certify |
| 794 | all historical ancestry candidates or OneNote conformance. The existing sync |
| 795 | fallback keeps an unmergeable version whole; this result is not evidence that |
| 796 | all supported production inputs fail to merge. It does mean that a protocol |
| 797 | must represent retained-unresolved custody separately from accepted content. |
| 798 | The native-corpus success demonstrates existing section-version merge/dedup, |
| 799 | not a new request/owner-decision/tombstone implementation. |
| 800 | |
| 801 | Failed investigations remain preserved: run 1 (missing lab dependency), run 3 |
| 802 | (lab type-alias error), runs 2/4/5 (generated-seed refusal), and run 6 (text oracle |
| 803 | looked only at ordinary page-list content, excluding the conflict page). Final |
| 804 | run 7 asserts actual conflict-page text through the existing section conflict |
| 805 | reader and preserves the earlier evidence. No failed run is a product gate. |
| 806 | |
| 807 | Rerun from the repository root on a clean **new output directory**, using the |
| 808 | shared target and one build slot; never reuse a run folder or point the lab at an original notebook: |
| 809 | |
| 810 | ```sh |
| 811 | nice -n 10 env CARGO_BUILD_JOBS=2 CARGO_TARGET_DIR="$PWD/target" cargo run --offline \ |
| 812 | --manifest-path target/multi-file-sync-spec-20261011/lab/Cargo.toml -- \ |
| 813 | "$PWD/target/multi-file-sync-spec-20261011/run-new" |
| 814 | python3 target/multi-file-sync-spec-20261011/lab/visibility.py \ |
| 815 | target/multi-file-sync-spec-20261011/run-new \ |
| 816 | target/multi-file-sync-spec-20261011/visibility-run-new |
| 817 | ``` |
| 818 | |
| 819 | The ignored standalone manifest currently has absolute path dependencies into |
| 820 | `workspaces/android-host-plan`; the verification manifest records them. The lab |
| 821 | links the repository's existing test remote and private merge sources directly, |
| 822 | without copying or changing them. The fixture is a disposable read of |
| 823 | `corpus/conflict-page/native/initial/notebook/synthetic.one`; its input hash is |
| 824 | recorded. No commercial provider, emulator, or OneNote process ran in this lab. |
| 825 | |
| 826 | ### Resilience policy follow-up |
| 827 | |
| 828 | The 2026-10-11 follow-up under |
| 829 | `target/multi-file-sync-resilience-20261011/` tests the selected policy's recovery |
| 830 | primitives and exposes limits of the current merge. Run 2 completed with exit 0 |
| 831 | in 2.4 seconds after the initial compilation populated the shared cache. The |
| 832 | first attempt failed to compile a lab-only immutable binding used by |
| 833 | `Section::conflicts()`; its exact source/log/result remain preserved. Neither |
| 834 | attempt changes production code. |
| 835 | |
| 836 | | Experiment | Observed outcome | |
| 837 | | --- | --- | |
| 838 | | Same overlapping ALICE/BOB branches, opposite primary bases | Both alternatives survive, but main/conflict placement differs; three replays of each original branch add zero publications and preserve exact bytes | |
| 839 | | Cross-deliver those opposite integration artifacts directly to current merge | Both directions publish again, flip the primary text and retain two conflict pages, including an alternative repeating current main text; naïve integration-result exchange is not convergence | |
| 840 | | Sort the same complete two-branch set before choosing a retained primary | Both arrival orders produce the same main/conflict identities and text; raw bytes and some active revision identities still differ | |
| 841 | | Five successive stale canonical replacements, alternating seed/A/B images | Every finite round repairs to exact ALPHA BETA using retained originals/checkpoint; repair costs 2/1/1/2/1 publications, and settled same-database/fresh-replica retries add zero | |
| 842 | | Fresh local recovery with a truncated canonical image | Cold parser rejects the image; retained raw source sections recover exact ALPHA BETA; observing only B lacks ALPHA and cannot establish complete history | |
| 843 | |
| 844 | Ten SQLite files pass integrity checks. Each saved valid `.one` is cold parsed |
| 845 | through the existing store/index/document reader with checksum/current-revision |
| 846 | validation; actual conflict pages are inspected separately from the ordinary |
| 847 | page. The immutable archive stays unchanged across repair. These are disposable |
| 848 | copies of the prior native-corpus fixture and actual production |
| 849 | `Replica`/`resolve::merge`/transaction calls; provider replacements, visibility, |
| 850 | source ordering and logical database reopen are simulated by the lab. |
| 851 | |
| 852 | The sorted-set result proves only a bounded presentation property for this |
| 853 | complete two-branch fixture. It does not prove equivalent opaque/protected |
| 854 | content, downstream causal translation, arbitrary incremental DAG convergence, |
| 855 | namespace operations, request coverage deduplication, cloud custody or OS-crash |
| 856 | durability. The opposite-integration result is why the selected algorithm must |
| 857 | track source coverage and normalize from retained inputs instead of feeding |
| 858 | integration artifacts back as new independent edits. No automatic convergence |
| 859 | claim is justified by this investigation. |
| 860 | |
| 861 | Rerun from a clean new output directory with one granted build slot: |
| 862 | |
| 863 | ```sh |
| 864 | nice -n 10 env CARGO_BUILD_JOBS=2 CARGO_TARGET_DIR="$PWD/target" cargo run --offline \ |
| 865 | --manifest-path target/multi-file-sync-resilience-20261011/lab/Cargo.toml -- \ |
| 866 | "$PWD/target/multi-file-sync-resilience-20261011/run-new" |
| 867 | ``` |
| 868 | |
| 869 | The ignored standalone lab links the current source in |
| 870 | `workspaces/sync-resilience` and reads three bound `.one` inputs from the original |
| 871 | run 7 without modifying them. Its verification manifest binds lab/dependency |
| 872 | sources, inputs, raw outputs, SQLite integrity checks, draft architecture review |
| 873 | and document checks. No commercial provider, emulator or OneNote process runs |
| 874 | in this follow-up. Native OneNote cold-open remains an implementation gate. |
| 875 | |
| 876 | | Fault | Required result / investigation oracle | |
| 877 | | --- | --- | |
| 878 | | Prefix/partial body, invalid length/digest | Reject/wait; last valid state untouched | |
| 879 | | Reordered/duplicate/omitted listings | Same logical requests applied once; omission never deletion | |
| 880 | | Missing base/blob/predecessor | Wait for dependency; do not apply positional ops to wrong base | |
| 881 | | Same immutable ID, different bytes | Preserve contradiction; stop automatic authority selection | |
| 882 | | Owner unavailable | Local editing/Undo/reopen works; requests retained | |
| 883 | | Owner crash after append before ack | Original named outcome recognized after reopen; no repeated edit | |
| 884 | | Lease takeover while old view is stale | Expected counterexample: both writers eligible; never call this fenced | |
| 885 | | Graceful handoff followed by old queued upload | Expected counterexample: canonical `.one` can regress | |
| 886 | | Old owner resumes after takeover | Preserve both decision/section branches; merge/recover without silent loss | |
| 887 | | Published receipt then `.one` rollback | Historical receipt cannot alone restore removed effect | |
| 888 | | Independent retained branches | Actual merge retains both changes, retry idempotent | |
| 889 | | Overlapping retained branches | Actual conflict page preserves both alternatives | |
| 890 | | Retried old branch after owner restart | No duplicate revisions/conflict pages from original immutable input | |
| 891 | | Offline rejoin after delete/compaction | Dedup/tombstones honored; unseen edits retained for explicit outcome | |
| 892 | | Invalid/foreign/repaired `.one` | Preserve bytes; never rebase old requests onto a different section | |
| 893 | | Close/flush/upload ambiguity | Keep attempt evidence and request; do not promise remote durability | |
| 894 | | Protected request without owner key | Encrypted queue stays recoverable; no plaintext transport fallback | |
| 895 | | Attachment missing/partial | Wait; no receipt claiming complete recoverability | |
| 896 | | TOC arrives before section / move halfway | Keep catalog/intent recoverable; prefer duplicate over loss | |
| 897 | |
| 898 | The Android host/provider plan and its two DocumentsProvider fixtures are at |
| 899 | `target/android-wave-20261011/host/`. They are specified, not yet implemented or |
| 900 | run. They test the real platform boundary separately from these simulations. |
| 901 | Commercial provider observations, native OneNote cold-open of new protocol |
| 902 | outputs, large-notebook scale, protected transport, namespace recovery, and actual |
| 903 | process-kill/crash tests are implementation gates, not claims made by this draft. |
| 904 | |
| 905 | ## Implementation boundary and review decisions |
| 906 | |
| 907 | `onestore` remains unaware of providers, coordination folders and ownership. |
| 908 | The data-layer protocol belongs toward `notebook`, reusing queue, merge, |
| 909 | recovery and normal append operations. Platform hosts adapt document/file APIs, |
| 910 | retained local directories and actual I/O/error callbacks. Android rendering and |
| 911 | native IME/accessibility can ship independently of this protocol. |
| 912 | |
| 913 | The owner has selected resilient branch-preserving takeover and retained recovery |
| 914 | authority for registered coordinated notebooks. Before production protocol code, |
| 915 | validate the reconciliation/receipt algorithm against the gates above and define: |
| 916 | |
| 917 | - Explicit registration, bare import, intentional restore and duplicate-copy flows |
| 918 | that preserve the stated lineage/custody boundaries. |
| 919 | - Protected-key custody and unsupported-client migration/escape behavior. |
| 920 | - Measured provider capability floor and storage cost. Initial automatic GC is |
| 921 | excluded; later compaction requires its own recovery proof. |
| 922 | |
| 923 | Unknown protocol/required-op versions must leave remote data untouched and keep |
| 924 | local edits recoverable. Enabling coordination requires an explicit migration |
| 925 | record and closure; old builds that ignore `.snowbound` can still edit `.one` |
| 926 | uncoordinated. A sidecar marker cannot stop those binaries. Rollout therefore |
| 927 | needs an honest compatibility boundary and an opt-in/import path, not a claim |
| 928 | that an unsupported build will obey a new marker. |
| 929 | |
| 930 | The next concrete deliverable after the draft is a narrow transport/owner lab |
| 931 | against real provider fixtures, then the selected policy implemented behind an |
| 932 | explicit storage mode and tested with actual notes. No timeout election or |
| 933 | full-image save shortcut should land as an Android dependency. |