Falsify the clock, commitments, pressure, boundaries, and scale

Twenty-two tests over the pass-7 surface: the tick's three stages and its
re-derivation at apply and at replay, the clock capability's single admitted
body, every commitment shape check as one complete sorted set, the attention
order's totality and its refusal to filter, boundary derivation and the
answer-must-be-satisfied rule, and the scale deficit that only admitted
subjects reduce.

Two defects the tests found and this commit fixes: PressureSource mixed struct
and newtype variants under an internal tag, which cannot serialize a scalar
newtype, so it is adjacently tagged; and Motion carried an is_empty no caller
outside a test used.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-09-05 00:07:49 +02:00
commit 2a8cec6b0a
6 changed files with 1783 additions and 20 deletions

View file

@ -7,8 +7,8 @@
//! draws a band, and the draw goes through the one `digest()` owner.
use super::patch::{
self, Affordance, Audience, AudienceSpec, BoundPrecondition, Bounds, CommitmentKind,
ComponentOpKind, Cost, Precondition, Quantity, RefKind, Role, WorldPatch,
self, Affordance, Audience, AudienceSpec, BoundPrecondition, Bounds, ComponentOpKind, Cost,
Precondition, Quantity, RefKind, Role, WorldPatch,
};
use super::{
AffordanceId, AuthorityGrant, AuthorityTarget, CommandId, DecisionEvent, DecisionInvocation,
@ -2682,7 +2682,15 @@ mod tests {
}],
speech: None,
};
let lowered = lower(&entry, 0, &invocation, &bindings).expect("the acquire slot lowers");
let lowered = lower(
&entry,
0,
bench.clerk,
FictionalMinutes::default(),
&invocation,
&bindings,
)
.expect("the acquire slot lowers");
assert_eq!(
lowered,
vec![ComponentOp::AcquireKnowledge {

View file

@ -65,12 +65,6 @@ pub(crate) struct Motion {
pub(crate) pressed: Vec<PressureWritten>,
}
impl Motion {
pub(crate) fn is_empty(&self) -> bool {
self.fulfilled.is_empty() && self.pressed.is_empty()
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(deny_unknown_fields)]
pub(crate) struct RoutineFulfilled {

View file

@ -3844,13 +3844,12 @@ mod tests {
assert!(decode_controller_work(&row).is_err());
}
/// The schema bump to `v5` refuses a prior store rather than migrating it
/// silently: `open` walks every row and demands both the row type and the
/// schema id match the current constants, so a `controller_work.v4` row
/// left over from before the bump is an open-time error, not a quietly
/// dropped or reinterpreted checkpoint.
/// The schema bump refuses a prior store rather than migrating it silently:
/// `open` walks every row and demands both the row type and the schema id
/// match the current constants, so a row left over from before the bump is
/// an open-time error, not a quietly dropped or reinterpreted checkpoint.
#[test]
fn a_controller_work_v4_row_is_refused_at_open() {
fn a_controller_work_row_from_a_prior_schema_is_refused_at_open() {
let directory = tempfile::tempdir().unwrap();
let path = directory.path().join("controller-work.cc");
let command_id = CommandId::new();
@ -3866,15 +3865,15 @@ mod tests {
let mut store = OwnedRedbMessagePackBackingStore::new(&path).unwrap();
let row = CultCacheEnvelope {
key: store_key(command_id).unwrap(),
r#type: "controller_work.v4".into(),
r#type: "controller_work.v5".into(),
payload: rmp_serde::to_vec_named(&work).unwrap(),
stored_at: Utc::now().to_rfc3339(),
schema_id: Some("ghostlight.controller_work.v4".into()),
schema_id: Some("ghostlight.controller_work.v5".into()),
};
store.push(&row).unwrap();
}
let Err(error) = CultCacheControllerWorkStore::open(&path) else {
panic!("a v4 row was accepted by the v5 store");
panic!("a v5 row was accepted by the v6 store");
};
assert!(matches!(error, ControllerWorkStoreError::Fault { .. }));
}

View file

@ -1177,6 +1177,80 @@ mod tests {
));
}
/// A store written at the schema this pass replaced is refused outright.
/// There is no migration adapter, and the refusal is the behaviour.
#[test]
fn a_store_from_the_knowledge_schema_is_refused() {
let row = CultCacheEnvelope {
key: "state".into(),
r#type: "world_state.knowledge.v1".into(),
payload: Vec::new(),
stored_at: Utc::now().to_rfc3339(),
schema_id: Some("ghostlight.world_state.knowledge.v1".into()),
};
let error = recover(vec![row], None).unwrap_err();
let JournalError::Corrupt(message) = error else {
panic!("expected a corrupt store");
};
assert!(
message.contains("unadmitted row type"),
"unexpected refusal: {message}"
);
}
/// The tick is re-derived at replay by the same function that produced it,
/// so a committed motion rewritten after the fact no longer reduces from
/// its command. The clock chain refuses a rewritten reading the same way.
#[test]
fn a_forged_motion_does_not_replay() {
use crate::world::tests::{activate, auth_principal, command, owner};
use crate::world::{
AuthenticatedCaller, CallerId, CommandBody, FictionalMinutes, SystemCapability,
TickMinutes,
};
let directory = tempfile::tempdir().unwrap();
let path = directory.path().join("world.cc");
let authenticated = auth_principal(owner());
let (mut kernel, _) = WorldKernel::create(
&path,
crate::world::tests::creation(CommandId::new(), "Chronicle"),
&authenticated,
)
.unwrap();
let active = activate(&mut kernel);
let command_id = CommandId::new();
let caller = CallerId::System(SystemCapability::Clock);
kernel
.submit(
command(
&active,
command_id,
caller.clone(),
CommandBody::AdvanceTime {
minutes: TickMinutes::new(30).unwrap(),
},
),
&AuthenticatedCaller::fixture(caller),
)
.unwrap();
let mut forged_head = kernel.state.clone();
let mut forged_commits = kernel.journal.commits.clone();
let forged = forged_commits.get_mut(&command_id).unwrap();
let WorldEffect::TimeAdvanced { to, .. } = &mut forged.effect else {
panic!("expected a time-advanced effect");
};
*to = FictionalMinutes(31);
forged.digest = commit_digest(forged).unwrap();
forged_head.last_commit_digest = Some(forged.digest.clone());
assert!(matches!(
verify_history(&forged_head, &forged_commits),
Err(JournalError::Corrupt(_))
));
}
/// Replay re-derives the operation half too: a committed relocation rewritten
/// to move a different subject no longer reduces from its command.
#[test]

File diff suppressed because it is too large Load diff

View file

@ -477,7 +477,7 @@ pub(crate) struct PressureMagnitude(pub(crate) u32);
/// *on* a subject and the typed view shows pressure on self. Source-major has no
/// reader.
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
#[serde(tag = "from", rename_all = "snake_case")]
#[serde(tag = "from", content = "of", rename_all = "snake_case")]
pub(crate) enum PressureSource {
/// A past-due commitment. Carries the promisor and the structural key.
Commitment {
@ -494,7 +494,7 @@ pub(crate) enum PressureSource {
/// The proposal-time twin of [`PressureSource`].
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
#[serde(tag = "from", rename_all = "snake_case")]
#[serde(tag = "from", content = "of", rename_all = "snake_case")]
pub(crate) enum PressureSourceRef {
Commitment {
subject: Ref<SubjectId>,