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:
Meta Vens 2026-09-16 15:31:09 +02:00
commit afca403fca
2 changed files with 119 additions and 10 deletions

View file

@ -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,
&current,
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(
&current,
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

View file

@ -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.