From db8eb0b2a7933e4fbb23b1a9800c0b2408a7d7e7 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Fri, 4 Sep 2026 18:19:35 +0200 Subject: [PATCH] Pass-2 follow-ups: dead presence check, RefKind derives, stamped world_id, stale imports and comment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Four cuts Soul named on pass 2, none of them behaviour changes. - runtime.rs: delete the `source_version` presence check in `admit_controller_command`. It called `.ok_or_else(...)?` and discarded the value, so it validated nothing. The `expected_revision = source_version - 1` computation on the approve/activate path is untouched; that one still needs a caller-supplied binding. - world/patch.rs: drop `Serialize`/`Deserialize` from `RefKind`. `RefKind::Subject(Option)` is an internally tagged newtype over a non-map, which serde refuses at runtime, so the derives were unexercised weight. `RefKind` is never persisted: it appears only in `Mismatch` (not serializable), the resolution index, and the three kind constants. Soul's `soul_ref_kind_is_not_a_serializable_shape` cannot compile as a negative without the derives it falsifies, so it is deleted; the doc comment on `RefKind` now carries the rule it was proving. - world/mailbox.rs: document that `submit_stamped` stamps `world_id` as well as `revision`, and why — the mailbox owns exactly one world, so there is no other world a controller runner could mean. The consequence is that `WorldKernel::submit`'s `WorldMismatch` check is unreachable on this path; `soul_a_stamped_submission_still_fails_closed_on_a_foreign_scope` stays as the proof that scope still fails closed. - world/mod.rs: `ComponentOp` and `RouteDeclaration` are test-only, so their import moves behind `#[cfg(test)]`. ghostlight-persona-projection: `opportunity_digest` no longer claims to bind "world state as one value"; pass 2 made it bind the scope digest. cargo test -p ghostlight-dungeon --bin ghostlight-dungeon world:: 75 passed (76 minus the deleted RefKind test), 1 ignored. cargo test -p ghostlight-dungeon: 110 passed + 1, 1 ignored. Co-Authored-By: Claude Fable 5.1 --- crates/ghostlight-dungeon/src/idunn_health.rs | 2 +- crates/ghostlight-dungeon/src/runtime.rs | 5 ---- .../ghostlight-dungeon/src/world/journal.rs | 8 +----- .../ghostlight-dungeon/src/world/mailbox.rs | 8 ++++++ crates/ghostlight-dungeon/src/world/mod.rs | 4 ++- crates/ghostlight-dungeon/src/world/patch.rs | 28 ++++++------------- .../ghostlight-persona-projection/src/lib.rs | 3 +- 7 files changed, 23 insertions(+), 35 deletions(-) diff --git a/crates/ghostlight-dungeon/src/idunn_health.rs b/crates/ghostlight-dungeon/src/idunn_health.rs index 0f3a97b..9c55cf5 100644 --- a/crates/ghostlight-dungeon/src/idunn_health.rs +++ b/crates/ghostlight-dungeon/src/idunn_health.rs @@ -5,8 +5,8 @@ //! one process write lease. Ghostlight reports what this process actually is; //! it never promotes its route or manufactures Ready. -use anyhow::{Context, Result, anyhow, bail, ensure}; use crate::world::{STATE_SCHEMA, state_schema_compatibility_tag}; +use anyhow::{Context, Result, anyhow, bail, ensure}; #[cfg(test)] use cultcache_rs::CacheBackingStore; use cultcache_rs::{CultCacheEnvelope, DatabaseEntry, SingleFileMessagePackBackingStore}; diff --git a/crates/ghostlight-dungeon/src/runtime.rs b/crates/ghostlight-dungeon/src/runtime.rs index 57afea2..ac2f6a4 100644 --- a/crates/ghostlight-dungeon/src/runtime.rs +++ b/crates/ghostlight-dungeon/src/runtime.rs @@ -972,11 +972,6 @@ async fn admit_controller_command( "human decisions cannot enter through the controller runner".into(), )); } - invocation - .operation - .route_hint - .source_version - .ok_or_else(|| RuntimeCommandError::Payload("source version is required".into()))?; let snapshot = current_world(state) .await .map_err(|error| RuntimeCommandError::Payload(error.to_string()))? diff --git a/crates/ghostlight-dungeon/src/world/journal.rs b/crates/ghostlight-dungeon/src/world/journal.rs index f86f0d1..6d309ec 100644 --- a/crates/ghostlight-dungeon/src/world/journal.rs +++ b/crates/ghostlight-dungeon/src/world/journal.rs @@ -974,13 +974,7 @@ mod tests { .commits .values() .map(|commit| { - envelope( - COMMIT_ROW, - COMMIT_SCHEMA, - commit.command.id().key(), - commit, - ) - .unwrap() + envelope(COMMIT_ROW, COMMIT_SCHEMA, commit.command.id().key(), commit).unwrap() }) .collect(); let rows_for = |state: &WorldState, row_type: &str| { diff --git a/crates/ghostlight-dungeon/src/world/mailbox.rs b/crates/ghostlight-dungeon/src/world/mailbox.rs index 8fd96aa..807a0dd 100644 --- a/crates/ghostlight-dungeon/src/world/mailbox.rs +++ b/crates/ghostlight-dungeon/src/world/mailbox.rs @@ -194,6 +194,14 @@ impl WorldMailbox { .await } + /// Stamps both `world_id` and `expected_revision` from the live kernel. The + /// mailbox owns exactly one world, so there is no other world a caller could + /// have meant, and asking a controller runner to restate an ID it cannot + /// choose would only invent a way to be wrong. The consequence is that + /// `WorldKernel::submit`'s `WorldMismatch` check cannot fire on this path: + /// scope, not world identity, is what fails an opportunity-bearing command + /// here, and `soul_a_stamped_submission_still_fails_closed_on_a_foreign_scope` + /// is the proof that it still fails closed. async fn submit_stamped( &self, command_id: CommandId, diff --git a/crates/ghostlight-dungeon/src/world/mod.rs b/crates/ghostlight-dungeon/src/world/mod.rs index 0f258f8..045ca3a 100644 --- a/crates/ghostlight-dungeon/src/world/mod.rs +++ b/crates/ghostlight-dungeon/src/world/mod.rs @@ -21,7 +21,9 @@ pub(crate) use patch::{ AccessKind, Cost, Declaration, DraftHandle, EntityDeclaration, EntityKind, Mismatch, PatchAnswer, Position, Ref, SubjectDeclaration, WorldPatch, }; -use patch::{ComponentOp, EdgeRecord, EntityRecord, ResolvedOp, ResolvedPatch, RouteDeclaration}; +#[cfg(test)] +use patch::{ComponentOp, RouteDeclaration}; +use patch::{EdgeRecord, EntityRecord, ResolvedOp, ResolvedPatch}; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; diff --git a/crates/ghostlight-dungeon/src/world/patch.rs b/crates/ghostlight-dungeon/src/world/patch.rs index 94a2d68..1729a42 100644 --- a/crates/ghostlight-dungeon/src/world/patch.rs +++ b/crates/ghostlight-dungeon/src/world/patch.rs @@ -62,8 +62,11 @@ pub(crate) struct Position { /// expects; resolution refuses a handle that answers with another one. A subject /// expectation carries `None` where the referring position constrains the /// namespace but not the kind. -#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] -#[serde(tag = "namespace", rename_all = "snake_case")] +/// +/// Not serializable: a `RefKind` is never persisted. It lives in `Mismatch`, in +/// the resolution index, and in the three constants below, all of which stay in +/// memory for the length of one reduction. +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] pub(crate) enum RefKind { Subject(Option), Entity(EntityKind), @@ -2112,18 +2115,6 @@ mod tests { assert_eq!(candidate, kernel.state); } - /// Soul falsification: `RefKind` is never persisted, and it could not be. - /// `Subject(Option)` under `#[serde(tag = "namespace")]` is an - /// internally tagged newtype variant over a non-map, which serde refuses at - /// runtime. The derives are unexercised weight, and the deviation is safe - /// only because nothing writes a `RefKind`. - #[test] - fn soul_ref_kind_is_not_a_serializable_shape() { - assert!(serde_json::to_value(RefKind::Entity(EntityKind::Place)).is_ok()); - assert!(serde_json::to_value(ANY_SUBJECT).is_err()); - assert!(serde_json::to_value(RefKind::Subject(Some(SubjectKind::Person))).is_err()); - } - /// Soul falsification: the three pass-2 `Mismatch` variants that no landed /// test names are each reachable from a real patch. #[test] @@ -2250,12 +2241,9 @@ mod tests { }, }; let mut candidate = kernel.state.clone(); - let error = super::super::apply_effect( - &mut candidate, - &CallerId::Principal(owner()), - &forged, - ) - .unwrap_err(); + let error = + super::super::apply_effect(&mut candidate, &CallerId::Principal(owner()), &forged) + .unwrap_err(); assert!(matches!(error, KernelError::Invariant(_))); assert_eq!(candidate, kernel.state); } diff --git a/crates/ghostlight-persona-projection/src/lib.rs b/crates/ghostlight-persona-projection/src/lib.rs index 7f1905c..df86b9b 100644 --- a/crates/ghostlight-persona-projection/src/lib.rs +++ b/crates/ghostlight-persona-projection/src/lib.rs @@ -142,7 +142,8 @@ pub struct PersonaTurnBinding { pub controller_id: String, /// Digest of the complete canonical decision opportunity supplied to the /// Projector. It binds scope, controller mode, affordances, revision, and - /// world state as one value instead of inventing a second opportunity ID. + /// the scope digest as one value instead of inventing a second opportunity + /// ID. pub opportunity_digest: String, pub world_revision: u64, pub scope_digest: String,