Pass-2 follow-ups: dead presence check, RefKind derives, stamped world_id, stale imports and comment
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<SubjectKind>)` 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 <noreply@anthropic.com>
This commit is contained in:
parent
53f35afc31
commit
db8eb0b2a7
7 changed files with 23 additions and 35 deletions
|
|
@ -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};
|
||||
|
|
|
|||
|
|
@ -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()))?
|
||||
|
|
|
|||
|
|
@ -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| {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
|
|
|||
|
|
@ -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<SubjectKind>),
|
||||
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<SubjectKind>)` 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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in a new issue