Repository navigation
Description
Problem
WorldCore::restore (pkgs/id/src/world/core.rs) validates a journal entry's author for some entry kinds but not for the events that carry the world's content:
JournalEntry::Issuedrefuses a parent that was never issued (core.rs:184-188), andJournalEntry::Revokedrefuses an unknown or already-revoked participant (core.rs:225-230).JournalEntry::Committed(core.rs:320-355) checks only the sequence (event.sequence == core.sequence + 1). It never checksevent.participant_id.JournalEntry::Checkpoint(core.rs:360-430) validates its events' sequences (consecutive, last equals the checkpoint sequence) but never theirparticipant_ids either.
Restore's own contract is that a journal "is internally inconsistent (out-of-order sequences, unknown participants, malformed digests). A bad journal is refused, never partially applied" (core.rs:130-133).
Live code can only produce a Committed event from participant 0 (the host: system_chat, core.rs:578, journaled as Committed at actor.rs:706-709; ProgramInstalled, actor.rs:1283) or from a participant issued in the same journal (chat/prepare_input authorize a capability first). A Committed event naming any other participant ID — one never issued, or issued only later in the journal — is a shape no live path produces, yet restore accepts it:
- Ghost inputs are replayed into the program. A
CommittedInputfrom a never-issued participant is pushed ontoreplay(core.rs:342), andopen_world(world_store.rs:380) feeds replay through the re-instantiated program — the program executes an input no participant ever sent, and the use-count accounting that live input passes (ensure_uses_left, thenspend) is skipped entirely for the ghost (commit_prepared'sspendwalks the capability chain; an unknown ID spends nothing). - Ghost chat is retained and projected to real participants. A
CommittedChatfrom a never-issued participant is stored in the retained events and appears in snapshots /events_afterprojections for legitimate participants, attributed to a participant ID that is not in the snapshot's participant list.
Evidence (verified this run, main 690e4722; laptop probe at 8b89d14, core.rs md5-identical)
Probe on the laptop (~/git/fleet/probe-2026-10-30-laneB-restorepart/, a detached worktree of origin/main; integration test pkgs/id/tests/probe_restorepart.rs against the real id crate, token digests built exactly as mint builds them):
- CONTROL:
Created+Issued(1, GUEST)+Committed(seq 1, participant 1, Input)restoresOk, replay length 1. - CONTROL:
Created+Committed(seq 1, participant 0, Chat)(host line) restoresOk. - CASE A:
Created+Issued(1)+Committed(seq 1, participant 999, Input)— participant 999 was never issued — restoresOk; the ghost input is inreplay(participant 999), andcore.participant(999)isNone. - CASE B: same journal with
Chatfrom participant 999 restoresOk; a snapshot taken with participant 1's legitimate token lists participants[1]but its events contain the chat line attributed to participant 999. - CASE C: a
Checkpointwhose retained events name participant 999 restoresOk.
Why it matters
Restore is the trust boundary for persisted world state (journals are copied by fork, moved between hosts, and read after crashes/torn writes — world_store.rs open_world). Every neighbouring arm of restore treats an unknown participant as a fatal inconsistency; the two arms carrying actual user content (inputs that drive the program, chat that other participants read) are the ones that skip the check. An input replayed from a nonexistent participant changes program state and published records with no accountable author and no use-limit accounting.
Acceptance criteria
- Restore refuses a
Committedevent whoseparticipant_idis neither 0 (host) nor a participant issued earlier in the same journal, with an error naming the participant and sequence, in the style of the neighbouring restore errors. - Restore refuses a
Checkpointwhose retained events name a participant that is neither 0 nor issued before the checkpoint. - Legitimate journals still restore: host (participant 0) chat and
ProgramInstalledevents, issued participants' chat/inputs, and existing checkpoint/fork fixtures. - Regression tests beside the existing restore tests (cf.
restore_refuses_a_delegate_outliving_its_parent): ghost input refused, ghost chat refused, ghost checkpoint event refused, host + issued controls pass. - Triage pins the rule for an event naming a participant issued later in the journal (the live order is always issue-then-commit, so "issued earlier" is the natural reading) and for an event by a participant revoked earlier in the journal (live chat/input is refused after revocation via
authorize, but the event was legitimately committed before the revoke in a real journal — confirm restored historical events by since-revoked participants stay valid).
Dedup checked this run: open+closed search for world journal restore / unknown participant — #2890 (restore never validates Issued scopes; same function, different entry arm and different invariant, filed by Lane A this hour — cross-linked, not a duplicate), #2093/#1598 (world-doctor journal-check disagreements, different surface), #1403 (replay omits chat, opposite direction). No filing covers event-author validation in restore.
Activity
dezren39 commentedon Oct 31, 2026
🤖 Agent note · filed
WorldCore::restoreCommitted/Checkpoint arms never checkevent.participant_idagainst participants issued earlier in the journal (host = 0), so ghost inputs replay into the program and ghost chat projects to real participants.~/git/fleet/probe-2026-10-30-laneB-restorepart/(laptop, detached worktree, core.rs md5-matched to main 690e472); related id(world): journal restore never validates Issued scopes — unnamed bits and JOIN-less capabilities restore clean, and a CHAT-/INPUT-only token can act without ever joining #2890 (Issued scopes, same function, different arm).dezren39 commentedon Oct 31, 2026
Agent note (design-architect, 2026-10-30): this issue is Phase 1 of design #2898 ("reconstruction is admission — validate authority-bearing inputs where they are rebuilt", design doc
files/designs/code-id-reconstruction-validation.mdin the keep-the-four-repos-healthy goal workspace), together with #2890.Recorded decisions for triage — do not re-file, re-scope, or split this issue; record the phase structure instead:
WorldCore::restorefor both theCommittedarm (beforecommit_prepared) and theCheckpointarm'sevents[]: a non-system event's author must be issued earlier in the journal and hold the scope its kind requires (INPUT for Input, CHAT for Chat), evaluated against the restored capability record.dezren39 commentedon Oct 31, 2026
Correction (design-architect, same pass): my earlier note's author rule was incomplete. The recorded rule per this issue's acceptance and design #2898 as amended: a Committed/Checkpoint event's author must be participant 0 (host) or a participant issued earlier in the same journal and not revoked before that entry; events by a participant revoked later in the journal stay valid. Host (0) chat and
ProgramInstalledare legitimate controls, not exceptions to engineer around.194 remaining items
dezren39 commentedon Nov 5, 2026
🤖 Agent note · handed off
dezren39 commentedon Nov 5, 2026
🤖 Agent note · handed off
dezren39 commentedon Nov 5, 2026
🤖 Agent note · handed off