Baniloo Baniloo

May 30, 2026

The chain survives the fork

Sync Rebase lands in Rust — deterministic fork resolution, signature preservation, and what pull and resolve mean as separate operations

Phase 2 Session 2 is done. The protocol can now detect a fork in the commit chain and resolve it — deterministically, every time, without losing a single signature.

154 tests pass. The chain survives.

What the spec says, and what that means in Rust

The Sync Rebase algorithm is specified in §8.3. On paper it reads simply: sort conflicting commits by timestamp, tiebreak by commit_id lexicographically, re-link the chain. The complexity is in what you cannot touch while doing this.

Every commit in LooMed is signed before it leaves the device. The patient signed it. The clinician signed it. The signature is a cryptographic promise over specific bytes — including the previous_hash field that a rebase must change. You cannot re-sign a rebased commit. Only the original author can do that.

The solution is in sync_metadata. Before any field is changed, the original previous_hash and commit_id are saved into pre_sync_previous_hash and pre_sync_commit_id. The signature stays untouched. Any verifier who wants to check the original signature reconstructs the original canonical bytes from those fields — the verification path in loomed-core::verify already handles this.

Rebase is a transparent, auditable protocol operation. It is not a content modification. The spec is explicit about this, and the implementation holds it as an invariant.

Clock skew and the tiebreaker

Two institutions commit offline. The timestamps are 10:31:00 and 10:32:00. The sort is clear. But what if they both committed at 10:31:45 — within the ±60 second clock skew tolerance the spec defines?

Timestamps cannot be the tiebreaker when they cannot be trusted to that precision. The secondary sort key is commit_id lexicographically ascending. commit_id is a SHA-256 hash — the lexicographic ordering is arbitrary but it is deterministic. Two different machines running Sync Rebase on the same fork will always produce the same linear chain. That is the invariant the test suite proves directly: same input, shuffled order, identical output.

Pull and resolve are separate operations

This was an intentional split.

loomed sync --pull fetches commits from a remote backend and updates the local HEAD. It transfers raw AES-256-GCM ciphertext — the encrypted bytes the author wrote, unchanged. No passphrase required. A device can pull another institution’s commits without ever having decryption access to them. Encrypted blobs move; nothing is readable.

loomed sync --resolve is different. It loads all commits, decrypts them, applies Sync Rebase, re-encrypts the rebased chain under the vault key, and writes it back. Passphrase required. Resolve is a vault-local operation — it does not touch a remote at all.

The separation reflects the protocol’s offline-first design. A device with no decryption key can still act as a sync relay for a patient’s vault. Pull is storage. Resolve is protocol.

154 tests

The sync crate now has 15 tests of its own: pull fetch, pull idempotency, pull HEAD update, and resolve fork linearisation. Combined with the 12 Sync Rebase unit tests in loomed-core, every path through the algorithm has a test — linear chains are returned unchanged, forks are linearised, genesis commits are never modified, sync_metadata fields are preserved, signatures are not touched.

CrateTests
loomed-core53
loomed-crypto17
loomed-store18
loomed-sync15
loomed-cli51
Total154, 0 failures

Next is Phase 2 Session 3: loomed log payload summaries, sync_metadata display in loomed show, and ParticipantId validation across commit fields.