Repository navigation
Description
Finding
WorldCore::restore's standalone EntityImported journal arm validates the owner-epoch advance with a plain + 1 on a u64:
// pkgs/id/src/world/core.rs:367-370 (restore, JournalEntry::EntityImported arm)
ensure!(
new_owner_epoch == prior_owner_epoch + 1,
"journal import for handoff {handoff_id:?} does not advance the owner epoch"
);prior_owner_epoch / new_owner_epoch are u64 (pkgs/id/src/world/mod.rs:513, :814), and pkgs/id/Cargo.toml sets no overflow-checks override in any profile, so the check behaves differently by build:
- Release (overflow checks off):
prior_owner_epoch = u64::MAX,new_owner_epoch = 0satisfies0 == MAX + 1(wrapping) — restore accepts an import whose owner epoch wraps to 0, i.e. the epoch goes backwards to the start. Owner epochs are the fencing currency for the region-seam handoff (id: implement: journaled region-seam export/import #3870 phases 2–5 build on this foundation): an epoch that can wrap to 0 defeats the monotonicity the fence relies on. - Debug / test (overflow checks on): the same journal entry panics with
attempt to add with overflowinsiderestore, so a crafted or corrupted journal crashes the restore path instead of being refused with the arm's own error message.
Evidence (main 9268223, verified this run)
pkgs/id/src/world/core.rs:368— the plain+ 1above (file md56b67846b2091c327ba9af2cdf93b687a, identical on the sandbox clone and the laptop clone).- Laptop probe
~/git/fleet/probe-2026-11-01-laneA-epochwrap/— the verbatim predicate extracted from core.rs:367-370, compiled with plainrustcon the laptop:- Release (
rustc -O): controls correct —prior=5 new=6 → true,prior=5 new=5 → false,prior=5 new=7 → false; wrap caseprior=MAX new=0 → true,prior=MAX new=MAX → false. - Debug (
rustc, overflow checks on): controls correct; the wrap case panics (attempt to add with overflow, exit 101) before it can return a verdict.
- Release (
- Sibling checks in the same restore already avoid plain arithmetic where it matters: the checkpoint consecutiveness check is the subject of id(world): restore's checkpoint consecutiveness check uses plain + 1 — a penultimate u64::MAX sequence panics in debug/test builds and wraps to an accepted MAX → 0 step in release #3736, and
since_checkpointaccounting usessaturating_add(core.rs, EntityExported/EntityImported arms).
Why it matters
This arm is the validating path for journal-carried imports — the checkpoint-carried variant is separately unvalidated (#3942). A handoff import at the last representable epoch is exactly the boundary a fencing epoch must never cross silently: in release it restores as a legitimate advance to epoch 0 (a stale owner's epoch-0 claims become current again), and in debug/test builds the same input is a panic in the restore path rather than a clean refusal. The fix is the checked form used elsewhere in the codebase (checked_add(1) compared against Some(new_owner_epoch), or equivalent), so prior = u64::MAX can never validate an import.
Acceptance criteria
WorldCore::restorerefuses a standaloneEntityImportedjournal entry withprior_owner_epoch = u64::MAX(anynew_owner_epoch) with the existing "does not advance the owner epoch" error — in both debug/test and release builds, with no panic and no wrapping acceptance.- Ordinary advances still restore:
prior=5, new=6succeeds;prior=5, new=5andprior=5, new=7are still refused. - A regression test in
pkgs/id/src/world/tests.rs(alongside the existing handoff restore tests) covers theu64::MAXprior epoch case. - The same plain-
+1pattern is checked for the other epoch/sequence comparisons added by the id: implement #3870 phase 1 — journal/schema/checkpoint foundation for region-seam handoff #3900 handoff foundation, and any sibling site found is either fixed in the same change or filed separately with a link here.
Dedup: #3736 (open) is the checkpoint event-sequence consecutiveness pair[1].sequence == pair[0].sequence + 1 at core.rs:476 — a different field, arm, and consequence. #3942 (open) covers checkpoint-carried transfers being installed verbatim with no validation at all; its probe exercised the standalone arm only at epoch 5→5, which this arm correctly refuses — the overflow boundary of the standalone arm's own check was not covered. #3902/#3903/#3904 (open) are the #3870 destination-import/coordinator/client phases that build on this foundation; none fixes the phase-1 restore check.
Activity
dezren39 commentedon Nov 1, 2026
🤖 Agent note · filed
+1owner-epoch advance check inWorldCore::restore's standaloneEntityImportedarm (core.rs:368): at prior=u64::MAX it wraps to an accepted epoch 0 in release and panics in debug/test; route the checked-arithmetic fix.dezren39 commentedon Nov 1, 2026
🤖 Agent note · adopted as Phase 2 of design #4000
source/design-architect. Existing queued triage packet stands.dezren39 commentedon Nov 2, 2026
🤖 Agent note · triage-verified (queue task 5902, repo-worker-code, OpenChamber scheduled lane)
+ 1still present on current main29da46385atcore.rs:401; nooverflow-checksoverride in anypkgs/idprofile (dev/test default on, release default off).-O) controls correct, wrap caseMAX->0 → true(accepts backwards epoch); debug panicsattempt to add with overflow(exit 101). Bug confirmed, not inferred.core.rs:510(pair[1].sequence == pair[0].sequence + 1) is id(world): restore's checkpoint consecutiveness check uses plain + 1 — a penultimate u64::MAX sequence panics in debug/test builds and wraps to an accepted MAX → 0 step in release #3736 territory, tracked separately;core.rs:259,1026already usechecked_add. Only:401is this issue.dezren39 commentedon Nov 2, 2026
🤖 Agent note · done (queue task 5902, repo-worker-code)
queue/queued,status/needs-triage(triage complete; fix tracked by task 6266).dezren39 commentedon Nov 2, 2026
agent-note:v1
task: 6266
lane: code-e
state: picked-up
note: Claimed by lane E worker (direct execution lane). Revalidating restore EntityImported epoch check against fresh main; fix is checked_add form + u64::MAX regression test per acceptance.
dezren39 commentedon Nov 2, 2026
agent-note:v1
task: 6266
lane: code-e
state: done
note: Fixed and merged in PR #4505 (merge 31f924b). Restore EntityImported arm now uses checked_add(1) == Some(new_owner_epoch); new regression test handoff_import_at_max_owner_epoch_is_refused covers new = 0, 1, u64::MAX at prior = u64::MAX. Verified on laptop: cargo test --lib handoff_ 4/4 pass, cargo test --lib world:: 156/156 pass. Codex review: no findings. Sibling plain-+1 site is the checkpoint sequence check, already filed as #3736. Full cargo test on pkgs/id also shows pre-existing unrelated integration-test compile errors on main (ChainExt missing link field in tests/*.rs, missing tic-tac-toe.wasm) — not touched by this change.