Repository navigation
docs(id): investigation — ephemeral live data vs the chained event graph (#2034) - #2239
Conversation
|
Warning Review limit reachedYou've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Next included review available in 4 minutes. View limit details
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3fd6cad4e0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| - **The extension carries the semantics.** `ChainExt` (`chain.rs:97-118`) holds `kind`, `group`, `after` (the author's causal frontier, ≤ `MAX_AFTER = 32`, `chain.rs:33`), and optional `tag` / `document` / `removes`. | ||
| - **The event ID is BLAKE3 over the signed bytes.** `chain.rs:263` ("the operation ID: BLAKE3 over `bytes`, signature included") and `chain.rs:305`. Receivers call `decode` (`chain.rs:321-342`), which verifies the signature with `verify_strict` and re-encodes to check canonical CBOR — so every accepted event is individually signed and hash-addressed. | ||
| - **Signing is one signature per event.** `chain.rs:282-309` `sign()` builds the header, signs it, and returns `{id, bytes, header}`. | ||
| - **Storage is a redb database of every verified event.** `pkgs/id/src/event_store/mod.rs:4-23` lists the tables (`events`, `hashes`, `chains`, `forks`, `groups`, `proof_ids`, `proof_headers`, `waiting`, `meta`; plus `actions` and `applied` at `mod.rs:108-112`). Format string `id.events.store.v8` (`mod.rs:83`). There is **no TTL, expiry, compaction or garbage collection for the event store** — a ripgrep for `ttl|retention|compact|expire|prune|evict` over `pkgs/id/src` finds hits only in the *world journal* and the *waiting pool*, never in `event_store` (grounded; see §2). |
There was a problem hiding this comment.
Account for the existing causal-cut pruning API
The reviewed commit already contains EventStore::prune_behind_cut in pkgs/id/src/event_store/mod.rs:527–540, which deletes accepted events and associated indexes while protecting surviving dependencies and backlinks; pkgs/id/tests/retention_pruning.rs exercises it. There may be no automatic TTL policy or production caller, but the claim that no garbage-collection path exists—and that the reported search finds nothing in event_store—is incorrect. Distinguish the missing policy/checkpoint integration from the existing storage primitive so follow-on item 5 assesses or extends that implementation rather than proposing a duplicate.
Useful? React with 👍 / 👎.
| | **Money / currency transfers** | Low volume, strict ordering, high stakes | Both counterparties; any auditor; an independent ledger | **Forever** (financial record) | **Per transfer**, signed by the payer; ledger is separately authoritative **[G]** currency design: `doc/2026-10-28T23-34-15Z_investigation_tigerbeetle_currency_backend/…md` (TigerBeetle is the settlement core; "TigerBeetle never sees the signature") | TigerBeetle cluster (balances) + ID graph (signed claims) **[G]** same doc | Double-spend / repudiation / unreconcilable books. Unacceptable. | | ||
| | **Game match records & audits** | Low volume per match; audit/netcode log is high-volume | Opponent, witnesses, adjudicator | **Match result: forever. Netcode/audit log: bounded (proposed 30–90 days) then a checkpoint hash is kept** **[P]** | **Result: per-match mutual attestation** (see [#2037](https://github.com/developing-today/code/issues/2037)); **audit log: segment checkpoints** **[P]** | Result in the graph; audit blob optionally pinned, else dropped **[P]** | Result loss = no adjudication; audit loss = weaker cheating evidence, acceptable after the window. | | ||
| | **Chat messages** | Moderate volume, human-paced, bursty | Channel members | **Per-channel policy; default durable for IRC-style channels, bounded (proposed 90 days) for high-chatter live channels** **[P]**; world chat already bounded to a 1024-event buffer for *history reads* **[G]** `world/mod.rs:67,92`; `world_caps.rs:92` | **Per message** for durable channels (one signed event) **[G]** `chain.rs:282-309`; **segment checkpoints** for live/low-retention channels **[P]** | Members' stores; world journal for world chat **[G]** `world_store.rs` | Durable: lost history. Live: lost recent context only. | | ||
| | **Presence / typing / cursor / keystroke / pointer live streams** | **Very high volume, sub-second latency** (a cursor update every few ms while moving) | Peers in the same session only, in real time | **Seconds to minutes; none after the session ends** **[G]** cursors dropped 5 min after disconnect, doc state 1 h after last client (`collab.rs:50-54`); world `Input`/`Chat` trimmed to 1024 (`world/core.rs:375`) | **No per-event signature.** One **session-header signature** at join, then a per-session MAC; **segment checkpoints** on commit/blur/interval **[P]** (see §5) | The session relay/host in memory only; nothing durable unless a checkpoint is written **[G]/[P]** | Nothing recoverable that matters — the durable checkpoint already captured the state transition. Acceptable. | |
There was a problem hiding this comment.
Separate bounded world history from durable journal retention
For world streams, the 1024-event limit bounds the in-memory history projection, not durable retention. At the reviewed revision, world/actor.rs:1032–1035 persists chat and :1060–1064 persists accepted input as JournalEntry::Committed. Furthermore, world_store.rs:1345–1386 explicitly tests that programs without snapshot support retain their full journal. Classifying these streams as memory-only with nothing retained after the session therefore understates the existing storage exposure. Separate collaborative-editor ephemera from journaled world input and document the verified-snapshot prerequisite for world compaction.
Useful? React with 👍 / 👎.
|
|
||
| A live view (collaborative document, cursor field, game state) is rebuilt from **the latest durable checkpoint + the ephemeral stream since it**. | ||
|
|
||
| - **Late joiner.** Loads the newest `SegmentCheckpoint` for the session (or the newest signed snapshot blob it names), sets its state to `Sₙ`, then subscribes to `LiveFrame`s with `seq > last_event_seq`. This mirrors today's `collab.rs` Init message `[0, version, doc, mode]` (`collab.rs:8`) and the world snapshot frame (`snapshot_bytes = 512 KiB`, `world/mod.rs:73`; "a client rebases its cursor on the snapshot, it does not need every retained event"). |
There was a problem hiding this comment.
Bind a retained state snapshot into reconstruction checkpoints
Once the segment bytes have expired, the proposed SegmentCheckpoint cannot supply or authenticate the state a late joiner is told to load: its fields contain an ordered-log root and sequence metadata, but neither a state root nor a snapshot reference. A hash of discarded edits cannot reconstruct the document or game state, and an independently supplied snapshot cannot be checked against that hash. Require reconstruction checkpoints to sign a state/snapshot hash and retain the corresponding blob before discarding the replay data; otherwise the central late-join and reconnect paths are not implementable as described.
Useful? React with 👍 / 👎.
| A live view (collaborative document, cursor field, game state) is rebuilt from **the latest durable checkpoint + the ephemeral stream since it**. | ||
|
|
||
| - **Late joiner.** Loads the newest `SegmentCheckpoint` for the session (or the newest signed snapshot blob it names), sets its state to `Sₙ`, then subscribes to `LiveFrame`s with `seq > last_event_seq`. This mirrors today's `collab.rs` Init message `[0, version, doc, mode]` (`collab.rs:8`) and the world snapshot frame (`snapshot_bytes = 512 KiB`, `world/mod.rs:73`; "a client rebases its cursor on the snapshot, it does not need every retained event"). | ||
| - **Reconnecting peer.** On reconnect it presents the last checkpoint it holds; the relay sends the segments after that checkpoint (or just the newest checkpoint plus live frames if the peer accepts state-rebase). Because checkpoints are chained and sequence-numbered, the peer can prove it is not being shown a truncated history. |
There was a problem hiding this comment.
Do not equate checkpoint continuity with proof of freshness
If a reconnecting peer holds checkpoint 5 while the signer has produced checkpoints 6–10, a relay can return only checkpoint 6. Its signature, sequence and predecessor all verify, so the proposed walk cannot detect that 7–10 were withheld. Checkpoint chaining detects missing predecessors, not a missing suffix; periodic signing likewise does not bound withholding when the relay suppresses subsequent checkpoints. Remove this truncation-proof guarantee or specify an independently authenticated latest-head/freshness mechanism before relying on it for reconnect verification.
Useful? React with 👍 / 👎.
| participants: [PubKey] | ||
| started_at: u64 // unix ms | ||
| nonce: [u8; 32] | ||
| mac_key: [u8; 32] // or a commitment to a derived key |
There was a problem hiding this comment.
Keep the live-frame MAC secret out of the signed header
Under the explicitly allowed raw-mac_key variant, any relay that reads the signed session header obtains the same symmetric key used to authenticate LiveFrames and can generate valid MACs for arbitrary replacement frames. Signing the header authenticates its contents but does not conceal the key, so this variant defeats the relay-forgery protection claimed in §6. Require the header to contain only a key commitment and specify confidential end-to-end distribution or derivation of the actual secret that excludes the relay, rather than offering publication of the raw key as an equivalent option.
Useful? React with 👍 / 👎.
What
Investigation doc for #2034 (ID program wave-1, LIST 9a): ephemeral live data vs the chained event graph — retention and intermittent signing.
doc/2026-10-29T10-45-00Z_investigation_ephemeral_live_data_retention/2026-10-29T10-45-00Z_investigation_ephemeral_live_data_retention.mdVerdict in one paragraph
Two-tier model. Keep the per-author signed hash chain (
ChainExt) for durable facts, and move keystroke-, cursor- and input-class traffic off it onto an ephemeral per-session stream authenticated once by a session-header signature and closed by intermittent segment checkpoints (every N events or T seconds, plus on commit/blur) — never a signature per event. "Initial + final signature" alone is too weak (truncation, no timing, relay equivocation); segment checkpoints fix truncation at a cost proportional to segments. Anything where a two-party dispute is the point (money, match results, contract-like chat) must keep per-event/per-transaction signing or add peer countersignatures.The doc is grounded in the current codebase with file:line evidence (
chain.rs,event_store/mod.rs,local_events.rs,web/collab.rs,world/mod.rs,event_sync.rs/log_sync.rs,artifact.rs) and labels every claim grounded or proposed. Notable finding: the "sign every keystroke" cost is a hypothesis, not current behaviour — live collab frames are already unsigned and bounded; what is missing is the signed checkpoint that makes the low-retention stream auditable after the fact, plus any retention path at all inevent_store.Contains: data-class table (9 classes), what the current graph is, what a checkpoint proves and gives up, the signing-chain proposal with mermaid, live-view reconstruction (late joiner / reconnect / partition), retention policy per class, 6 follow-on items (one PoC that must not be built into the permanent feature before review), and open questions for the synthesis issues (#2036, #2037, #2038).
Why docs-only
Investigation/triage task; verification is the recorded result plus routed follow-on work. Follow-on issues are routed from #2034.
Refs #2034, #2056.
Need help on this PR? Tag
@codesmith-botwith what you need.