Repository navigation
feat(id): F12.13 replication wire contract + crypto primitives (#806 phase 1) - #4447
Conversation
…phase 1) Signed replication envelope (Ed25519 verify_strict, domain-separated), cursor/hash-chain over exact journal-entry bytes seeded by world/source/header-version, bounded payloads split at entry boundaries, terminal-frame commit, and a receiver-side ReplicationTracker refusing tamper, truncation, gap, fork, replay, untrusted source, and unsupported envelope/journal versions. F12.6 seam: #1063 slice 1 (PR #4017) defines no reusable wire framing, so per design doc 2026-11-01_design_id806.md sections 4.6/7 this is the explicitly versioned F12.13-owned envelope. 9 test vectors pass (cargo test -p id --lib world::replication). Queue task 5832.
|
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 30 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. |
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: f331d57771
ℹ️ 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".
| return None; | ||
| } | ||
| let payload = frame.strip_prefix(REPLICATION_FRAME_MAGIC)?; | ||
| postcard::from_bytes::<Self>(payload).ok() |
There was a problem hiding this comment.
Reject trailing bytes when decoding signed frames
If bytes are appended to an otherwise valid signed frame, this decoder still accepts it: postcard::from_bytes ignores unused input. Signature verification then succeeds because it reserializes only the decoded envelope, but accept computes last_frame_digest over the entire received buffer, including the unsigned suffix. Consequently, the modified frame advances receiver state and causes the writer's next legitimate frame to fail with FrameChainMismatch. Use take_from_bytes and require an empty remainder so modified frames are refused before state advances.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 395a2b4: decode_frame now uses postcard::take_from_bytes and refuses any non-empty remainder, so a frame with an appended unsigned suffix no longer decodes (regression test: trailing_bytes_and_undecodable_entries_are_refused).
| for entry in payload.split_inclusive(|byte| *byte == b'\n') { | ||
| cursor = advance_cursor(cursor, entry); |
There was a problem hiding this comment.
Decode journal entries before advancing the verified cursor
If the pinned source signs a payload such as b"not-json\n", the tracker accepts it and subsequently accepts a terminal committing its cursor: this loop treats every newline-terminated byte sequence as a valid journal entry. That contradicts the tracker's entry-decoding guarantee and returns malformed journal data in an AcceptedFrame even though the existing journal restore path cannot decode it. Validate each line as a JournalEntry before advancing verified state, returning MalformedPayload without changing the tracker when decoding fails.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 395a2b4: chain_payload now decodes every payload line as a JournalEntry (serde_json) before advancing the cursor and returns MalformedPayload without state change otherwise — a signed-but-undecodable payload can no longer move the verified cursor (same regression test covers it).
|
Codex findings disposition (task 5832, lane code-j): both P2 findings are accepted as valid — (1) trailing-bytes rejection on signed-frame decode, (2) JournalEntry decode before cursor advance. Fixes are parked, not waived: the laptop load gate for this task (sustained load < 20; currently ~117 with ~71 rustc processes) blocks the edit+test cycle this shift. Task 5832 is parked with these findings as the resume condition; the PR will not be merged past them silently. |
…cation contract Codex review findings on PR #4447: (1) decode_frame used postcard::from_bytes, which ignores trailing bytes — an unsigned suffix would desynchronize the frame chain; now take_from_bytes with an empty-remainder requirement. (2) chain_payload treated every newline-terminated byte sequence as an entry; each line now must decode as a JournalEntry before the verified cursor advances. Regression tests cover both.
Phase 1 of 5 for #806 (design: doc/2026-11-01_design_id806.md, PR #3822; queue task 5832). Contract only — no network serving, receiver storage, or CLI (phases 2–5).
What lands
New module
pkgs/id/src/world/replication.rs:ReplicationEnvelope+SignedReplicationEnvelope: world ID, source node ID, journal header version, kind (Snapshot/Tail/Terminal), session nonce, cursor before/after, previous-frame digest, payload digest, exact payload bytes. Ed25519verify_strictover a domain-separated (developing-today/id/world-replication/v1) canonical encoding of every field plus the exact payload; signing refuses a key that is not the claimed source.(ordinal, rolling BLAKE3 digest)over the exact journal-entry bytes (JSONL lines as stored). Chain base seeded with domain, world ID, source node ID, and header version, so compaction/header bumps change the base and force a reset snapshot; recomputable from the journal file after restart.split_entry_payload; a single over-bound entry is refused outright.ReplicationTracker(pinned source, per-session nonce): checks envelope version, journal header version at the boundary (F11.12 rule), world, pinned source, signature, nonce, frame chain, cursor continuity, payload digest, and entry chaining before any payload is visible. Terminal frames commit a batch cursor; state never advances on a refused frame.F12.6 seam disposition
Design §4.6/§7: map to F12.6 framing if #1063 slice 1 defines one, otherwise F12.13 owns a versioned envelope. #1063 slice 1 (PR #4017, still open) is an audit-log feature and defines no reusable wire framing, so the fallback applies — this is the explicitly versioned F12.13-owned envelope, carried as the payload of the existing
crate::framestransport framing. PR #4017 is not touched. Also recorded on #806.Tests
cargo test -p id --lib world::replication— 9 passed on the laptop, including a pinned chain-base test vector and negatives for tamper, truncation, gap, fork, replay (incl. cross-session nonce), untrusted source, unsupported envelope/journal versions, and size limits, plus snapshot rebase on header bump.Refs #806 (does not close it — #806 closes when phase 5 lands).
Open in [code]smith · Need help on this PR? Tag
@codesmith-botwith what you need.