You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
id: poc: bridge sync session — native store ↔ DO-shaped SQLite store over WebSocket, fork-proof exchange + withholding detection (from #2058) #2276
From the bridge-semantics investigation on #2058 (doc PR linked there): prove the hosted↔native seam with the smallest thing that exercises the real rules, reusing the native store machinery unchanged.
What to build (PoC only)
A bridge sync session between a native pkgs/id store and a DO-shaped SQLite store (same table shape as event_store/: events by (author, seq), chains, forks, proof_ids, proof_headers) over a WebSocket, reusing ingest_batch + resolve with no semantic changes: no bridge-assigned ordering, no re-signing, no authoritative snapshot.
Acceptance criteria
Push and pull across the session converge to identical applied sets and identical live tag claims on both stores
A fork authored on each side while disconnected is detected by the proof-carrying exchange (not LogSync height comparison) and both sides end holding the same fork proof
A withholding bridge (drops one author's events) is detected from chain-head / after-frontier evidence, and the detection is demonstrated in a test
Replaying the same journal into an empty hosted-shaped store and an empty native store yields identical stores (tables compared)
Permanent feature work on top of this PoC (program rule: an unreviewed PoC making its own decisions is not a foundation), custodial keys, SSO binding (#2258), native multi-hop relay (#2249).
Still live: filed today as a PoC; no bridge sync session exists yet.
Notes: body already carries acceptance criteria; none appended. Fork-proof exchange is the property under test — the PoC should state explicitly which fork case it exercises.
Risks: medium — WebSocket session semantics over two store shapes can sprawl; the issue's own "smallest thing that exercises the real rules" scoping is the guard.
Findings for the #2046 Q1 decision (fork-proof cadence)
Measured on the PoC (laptop run, debug build; all 5 PoC tests pass, 10/10 with the shared test-common unit tests):
Fork set (2 authors' worth of state: 3 events, one fork at equal heights): proof-carrying range exchange = 4 flights, 163 reconciliation bytes — and it detected the fork that a height comparison provably misses (pre-session chains had identical next_seq, different heads).
Bootstrap set (100 events / 4 authors, one side empty): 4 flights, 3,235 reconciliation bytes ≈ 32.4 bytes/event — about half the naive bound of mailing every event ID both ways (6,400 B), and the flight count did not grow between the 3-event and 100-event sets at this fan-out. Session wall time 5.3 s in a debug build is dominated by per-event signature verification in ingest_batch, not by the exchange.
Cadence read: at wave-1 scale the exchange itself is cheap (bytes scale ~linearly at ~32 B/event with fingerprint flights, flights stayed at 4); the cost a cadence decision actually budgets is the verification of transferred events, which is paid only for the symmetric difference. Nothing in the measurements argues against running the proof exchange on every bridge session at this scale; the open part of Q1 remains the DO rows/CPU accounting on the Workers side, which this PoC does not measure (no Workers runtime here).
What the PoC proved, per acceptance
Convergence: push+pull in one session → identical applied sets (store canonical state) and identical live tag claims on both stores.
Forks: a fork authored on each side while disconnected is detected by the proof-carrying exchange (≥1 Ingest::Fork outcome per side), and both sides end holding the identical fork proof (same seq, same first/second IDs).
Withholding: a relay that drops one author's events is detected from the announced chain head (seq 4 / head hash) plus the after-frontier reference inside a signed event that did arrive (the receiver holds that event waiting on exactly the withheld hash). Detection names the author, the announced head, and the undelivered event IDs; the honest-session control produces no evidence, and an author whose events arrived but waits on the withheld dependency is not itself flagged.
Replay identity: the same journal replayed into a fresh native store and a fresh hosted (SQLite) store — in opposite arrival orders — produces identical tables (events / chains / forks / proof_ids / proof_headers compared row-for-row).
Limitations (stated in the PR as well)
The sync set is enumerated via the store's public read API, which exposes the lowest fork proof per author; the internal sync set also carries higher fork points' proof headers.
The hello's chain heads are unsigned in the PoC; production evidence for the head half of withholding detection arrives inside signed events (backlinks / after), which is the half the frontier test exercises with real signatures.
The hosted store's SQLite tables are mirrored from the same ingest outcomes in the same step (one batch = one transaction, the DO write shape); a rusqlite-native ingest port is separate work, not needed for the semantics questions this PoC answers.
Added EventStore::waiting_event to look up and decode events directly from WAITING rows, updated held_events to retrieve waiting events, and added bridge_session_converges_with_waiting_events_on_both_sides.
Added sparse_fork_reconciliation_scales_sublinearly_across_set_sizes demonstrating that for a single-fork difference against shared histories of 20, 60, and 100 events, exchange flights remain constant (5) and bytes grow sublinearly (1,036 to 1,356 bytes).
Verification: 12/12 tests in bridge_poc.rs passed cleanly on laptop; PR merged into main.
Activity
developing-today commentedon Oct 29, 2026
CODE-1137
dezren39 commentedon Oct 29, 2026
Intake review — fleet monitor, 2026-10-29
queue/queued— a live queue task exists for it (verified in this run's reconciliation).32 remaining items
test(id): bridge sync session PoC — native store ↔ DO-shaped SQLite s…
dezren39 commentedon Oct 30, 2026
🤖 Agent note · done (PoC delivered; PR awaiting CI)
pkgs/id/tests/bridge_poc.rs, test-only)queue/3431-2276-bridge-sync-poc· commit b3421efFindings for the #2046 Q1 decision (fork-proof cadence)
Measured on the PoC (laptop run, debug build; all 5 PoC tests pass, 10/10 with the shared test-common unit tests):
next_seq, different heads).ingest_batch, not by the exchange.What the PoC proved, per acceptance
Ingest::Forkoutcome per side), and both sides end holding the identical fork proof (same seq, same first/second IDs).after-frontier reference inside a signed event that did arrive (the receiver holds that event waiting on exactly the withheld hash). Detection names the author, the announced head, and the undelivered event IDs; the honest-session control produces no evidence, and an author whose events arrived but waits on the withheld dependency is not itself flagged.Limitations (stated in the PR as well)
after), which is the half the frontier test exercises with real signatures.dezren39 commentedon Oct 30, 2026
🤖 Agent note · parked
test(id): address Codex review on bridge PoC — waiting-event snapshot…
Merge pull request #2848 from developing-today/queue/3431-2276-bridge…
dezren39 commentedon Oct 30, 2026
🤖 Agent note · done
d2eb34ed9d344a8cebd8e7caf0e9071ba38fbbae).EventStore::waiting_eventto look up and decode events directly fromWAITINGrows, updatedheld_eventsto retrieve waiting events, and addedbridge_session_converges_with_waiting_events_on_both_sides.sparse_fork_reconciliation_scales_sublinearly_across_set_sizesdemonstrating that for a single-fork difference against shared histories of 20, 60, and 100 events, exchange flights remain constant (5) and bytes grow sublinearly (1,036 to 1,356 bytes).bridge_poc.rspassed cleanly on laptop; PR merged into main.d2eb34ed9d344a8cebd8e7caf0e9071ba38fbbaefix(id): make the no-SQLite guard read the resolved graph, not the ra…