Refuse identical lens weights as no change (L1 c2.s1.f1)
An owner's SetLensWeights equal to the world's current weights committed: the revision advanced and the digests moved though nothing canonical changed. `reduce` now refuses it with PatchRejected([NoCanonicalChange]) after admission, so the live submit and replay share the rule and nothing commits. The order is capability, owner, draw rule, then equality: a non-owner's identical set stays Unauthorized, and an identical set that never draws stays LensWeightsNeverDraw. The SetLensWeights doc stated the old rule and now states this one. Tests: the in-crate soul test's pin of the old commit becomes the refusal, with the order pinned (non-owner identical is Unauthorized; an identical all-zero set against a hand-built state is LensWeightsNeverDraw) and the store reopened to the same head. external_admission gains identical_lens_weights_from_the_owner_commit_nothing through the public mailbox: revision, state digest, last commit digest and operator log length unchanged, non-owner identical Unauthorized, a different set commits. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
c8dfdd1a35
commit
afca403fca
2 changed files with 119 additions and 10 deletions
|
|
@ -680,9 +680,10 @@ pub enum CommandBody {
|
|||
},
|
||||
/// Replaces the world's whole lens weight set. Owner only, in any phase,
|
||||
/// under genesis admission: a set that never draws is refused, and a zero
|
||||
/// weight is admitted and never draws. Identical weights are still a
|
||||
/// commit, because the owner's act is canonical. A session that already
|
||||
/// drew keeps its recorded lens.
|
||||
/// weight is admitted and never draws. A set equal to the current one
|
||||
/// changes nothing canonical and is refused with `NoCanonicalChange`
|
||||
/// after admission, so nothing commits. A session that already drew
|
||||
/// keeps its recorded lens.
|
||||
SetLensWeights {
|
||||
weights: LensWeights,
|
||||
},
|
||||
|
|
@ -1682,6 +1683,14 @@ fn reduce(state: &WorldState, command: &CommandEnvelope) -> Result<WorldEffect,
|
|||
Mismatch::LensWeightsNeverDraw,
|
||||
]));
|
||||
}
|
||||
// Admission first, then effect: a caller who may not set weights,
|
||||
// or a set that never draws, is refused as such even when it
|
||||
// equals the current set. Only an admitted set can be a no-op.
|
||||
if weights == &state.lens_weights {
|
||||
return Err(KernelError::PatchRejected(vec![
|
||||
Mismatch::NoCanonicalChange,
|
||||
]));
|
||||
}
|
||||
Ok(WorldEffect::LensWeightsReplaced {
|
||||
weights: weights.clone(),
|
||||
})
|
||||
|
|
@ -14647,17 +14656,60 @@ mod clock_tests {
|
|||
.expect("the owner's effect applies");
|
||||
assert_eq!(candidate.lens_weights, proposed);
|
||||
|
||||
// Identical weights from the owner are still a commit.
|
||||
let current = kernel.snapshot().unwrap();
|
||||
submit_owner(
|
||||
// Identical weights from the owner change nothing canonical: refused
|
||||
// as `NoCanonicalChange`, and nothing commits.
|
||||
let result = submit_as(
|
||||
&mut kernel,
|
||||
¤t,
|
||||
CallerId::Principal(owner()),
|
||||
CommandBody::SetLensWeights {
|
||||
weights: active_weights.clone(),
|
||||
},
|
||||
);
|
||||
assert_eq!(kernel.state.revision, current.revision + 1);
|
||||
assert_eq!(kernel.state.lens_weights, active_weights);
|
||||
let Err(KernelError::PatchRejected(mismatches)) = result else {
|
||||
panic!("identical lens weights were admitted: {result:?}");
|
||||
};
|
||||
assert_eq!(mismatches, vec![Mismatch::NoCanonicalChange]);
|
||||
assert_eq!(kernel.state, before);
|
||||
|
||||
// The order is admission, then effect. Identical weights from a
|
||||
// non-owner are `Unauthorized`; and against a state whose own set
|
||||
// never draws (no store holds one, so the state is built by hand), an
|
||||
// identical all-zero set is `LensWeightsNeverDraw`, not a no-op.
|
||||
let result = submit_as(
|
||||
&mut kernel,
|
||||
CallerId::Principal(human_principal()),
|
||||
CommandBody::SetLensWeights {
|
||||
weights: active_weights.clone(),
|
||||
},
|
||||
);
|
||||
assert!(
|
||||
matches!(result, Err(KernelError::Unauthorized)),
|
||||
"a non-owner's identical set: {result:?}"
|
||||
);
|
||||
assert_eq!(kernel.state, before);
|
||||
let mut never = kernel.state.clone();
|
||||
never.lens_weights = every_lens_at_zero();
|
||||
let current = kernel.snapshot().unwrap();
|
||||
let identical_zero = command(
|
||||
¤t,
|
||||
CommandId::new(),
|
||||
CallerId::Principal(owner()),
|
||||
CommandBody::SetLensWeights {
|
||||
weights: every_lens_at_zero(),
|
||||
},
|
||||
);
|
||||
let result = reduce(&never, &identical_zero);
|
||||
let Err(KernelError::PatchRejected(mismatches)) = result else {
|
||||
panic!("an identical set that never draws: {result:?}");
|
||||
};
|
||||
assert_eq!(mismatches, vec![Mismatch::LensWeightsNeverDraw]);
|
||||
|
||||
// Replay across that history is unchanged: the refused attempts wrote
|
||||
// nothing, and the store reopens to the same head.
|
||||
let (path, world_id) = (directory.path().join("world.cc"), kernel.state.world_id);
|
||||
drop(kernel);
|
||||
let reopened = WorldKernel::open(path, world_id).expect("the history reopens");
|
||||
assert_eq!(reopened.state, before);
|
||||
}
|
||||
|
||||
/// An owner commit that adds a commitment to the first person: a real
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
use chrono::{Duration, Utc};
|
||||
use ghostlight::{
|
||||
CommandBody, CommandId, ControllerMode, ControllerPort, CreateWorldIntent, DecisionInvocation,
|
||||
DecisionOpportunity, KernelError, Lens, LensWeights, MailboxError, PrincipalCommandIntent,
|
||||
DecisionOpportunity, KernelError, Lens, LensWeights, MailboxError, Mismatch, PrincipalCommandIntent,
|
||||
Statement, SubjectId, SubmitReceipt, VerifiedPrincipalEvidence, WorldMailbox, WorldPhase,
|
||||
WorldSnapshot,
|
||||
};
|
||||
|
|
@ -554,6 +554,63 @@ async fn set_lens_weights_from_a_non_owner_is_refused_and_commits_nothing() {
|
|||
assert_eq!(after.lens_weights, real_weights());
|
||||
}
|
||||
|
||||
/// The owner resubmitting the world's current weights changes nothing
|
||||
/// canonical, so the kernel refuses it as `NoCanonicalChange` and nothing
|
||||
/// commits. Admission runs first: the same identical set from a non-owner is
|
||||
/// `Unauthorized`. A different set from the owner then commits, so neither
|
||||
/// refusal is an inert harness.
|
||||
#[tokio::test]
|
||||
async fn identical_lens_weights_from_the_owner_commit_nothing() {
|
||||
let world = World::active().await;
|
||||
let current = world.snapshot().await.lens_weights;
|
||||
let before = world.committed().await;
|
||||
let intent = |snapshot: &WorldSnapshot, weights: LensWeights| PrincipalCommandIntent {
|
||||
id: CommandId::new(),
|
||||
world_id: snapshot.world_id,
|
||||
expected_revision: snapshot.revision,
|
||||
body: CommandBody::SetLensWeights { weights },
|
||||
};
|
||||
let snapshot = world.snapshot().await;
|
||||
|
||||
let result = world
|
||||
.mailbox
|
||||
.submit_principal(intent(&snapshot, current.clone()), &world.principal)
|
||||
.await;
|
||||
let Err(MailboxError::Kernel(KernelError::PatchRejected(mismatches))) = &result else {
|
||||
panic!("the owner's identical lens weights were not refused as no change: {result:?}");
|
||||
};
|
||||
assert_eq!(mismatches, &vec![Mismatch::NoCanonicalChange]);
|
||||
assert_eq!(world.committed().await, before);
|
||||
|
||||
let stranger =
|
||||
VerifiedPrincipalEvidence::new("external-admission-stranger", Utc::now() + Duration::hours(1));
|
||||
let result = world
|
||||
.mailbox
|
||||
.submit_principal(intent(&snapshot, current.clone()), &stranger)
|
||||
.await;
|
||||
assert!(
|
||||
matches!(
|
||||
result,
|
||||
Err(MailboxError::Kernel(KernelError::Unauthorized))
|
||||
),
|
||||
"a non-owner's identical lens weights were not refused as unauthorized: {result:?}"
|
||||
);
|
||||
assert_eq!(world.committed().await, before);
|
||||
|
||||
assert_ne!(current, real_weights());
|
||||
let receipt = world
|
||||
.mailbox
|
||||
.submit_principal(intent(&snapshot, real_weights()), &world.principal)
|
||||
.await
|
||||
.expect("the owner replaces the lens weights with a different set");
|
||||
assert!(matches!(receipt, SubmitReceipt::Applied(_)));
|
||||
let after = world.committed().await;
|
||||
assert_eq!(after.0, before.0 + 1);
|
||||
assert_ne!(after.1, before.1);
|
||||
assert_ne!(after.2, before.2);
|
||||
assert_eq!(world.snapshot().await.lens_weights, real_weights());
|
||||
}
|
||||
|
||||
/// A lens name the library does not know cannot be decoded into a command
|
||||
/// body, beside names it does. This is a serde limit, stated as one: the body
|
||||
/// never exists, so no kernel admission is being proven here.
|
||||
|
|
|
|||
Loading…
Reference in a new issue