Skip to content

feat(id): F12.13 replication wire contract + crypto primitives (#806 phase 1) - #4447

Merged
dezren39 merged 2 commits into
mainfrom
queue/5832-806-replication-contract
Nov 2, 2026
Merged

dezren39 merged 2 commits into
mainfrom
queue/5832-806-replication-contract

Conversation

@dezren39

@dezren39 dezren39 commented Nov 2, 2026 •

Copy link
Copy Markdown
Member

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:

  • Envelope — 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. Ed25519 verify_strict over 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.
  • Cursor/hash-chain — (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.
  • Bounded payloads — 256 KiB per frame (under the transport frame bound), split at entry boundaries via split_entry_payload; a single over-bound entry is refused outright.
  • Receiver verification — 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::frames transport 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).


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

…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.
@dezren39
dezren39 requested a review from a team as a code owner November 2, 2026 16:21
@coderabbitai

coderabbitai Bot commented Nov 2, 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 30 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: ff9f5fe5-c050-48da-b72f-290059b81b25
Commits

Reviewing files that changed from the base of the PR and between e1a9892 and 395a2b4.

Files selected for processing: 2
  • pkgs/id/src/world/mod.rs
  • pkgs/id/src/world/replication.rs
  • 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

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 2, 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-11-02T16:39:29.937586Z 395a2b4 New commits
ℹ️ 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: 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".

Comment thread pkgs/id/src/world/replication.rs Outdated
return None;
}
let payload = frame.strip_prefix(REPLICATION_FRAME_MAGIC)?;
postcard::from_bytes::<Self>(payload).ok()

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

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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

Comment on lines +386 to +387
for entry in payload.split_inclusive(|byte| *byte == b'\n') {
cursor = advance_cursor(cursor, entry);

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

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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

@dezren39

dezren39 commented Nov 2, 2026

Copy link
Copy Markdown
Member Author

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