Skip to content

docs(id): investigation — ephemeral live data vs the chained event graph (#2034) - #2239

Merged
dezren39 merged 1 commit into
mainfrom
queue/3348-2034-ephemeral-live-data
Oct 30, 2026
Merged

dezren39 merged 1 commit into
mainfrom
queue/3348-2034-ephemeral-live-data

Conversation

@dezren39

@dezren39 dezren39 commented Oct 29, 2026 •

Copy link
Copy Markdown
Member

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.md

Verdict 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 in event_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.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

@dezren39
dezren39 requested a review from a team as a code owner October 29, 2026 10:47
@coderabbitai

coderabbitai Bot commented Oct 29, 2026

Copy link
Copy Markdown

Warning

Review limit reached

You'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.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 0143ff9e-5666-463a-81a8-902d941e34c0
Commits

Reviewing files that changed from the base of the PR and between 24901e7 and 3fd6cad.

Files selected for processing: 1
  • doc/2026-10-29T10-45-00Z_investigation_ephemeral_live_data_retention/2026-10-29T10-45-00Z_investigation_ephemeral_live_data_retention.md
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-29T10:51:34.103500Z 3fd6cad PR opened
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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. |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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").

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

@dezren39
dezren39 added this pull request to the merge queue Oct 30, 2026
Merged via the queue into main with commit 281d999 Oct 30, 2026
3 checks passed
@dezren39
dezren39 deleted the queue/3348-2034-ephemeral-live-data branch October 30, 2026 01:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant