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,
|
/// 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
|
/// under genesis admission: a set that never draws is refused, and a zero
|
||||||
/// weight is admitted and never draws. Identical weights are still a
|
/// weight is admitted and never draws. A set equal to the current one
|
||||||
/// commit, because the owner's act is canonical. A session that already
|
/// changes nothing canonical and is refused with `NoCanonicalChange`
|
||||||
/// drew keeps its recorded lens.
|
/// after admission, so nothing commits. A session that already drew
|
||||||
|
/// keeps its recorded lens.
|
||||||
SetLensWeights {
|
SetLensWeights {
|
||||||
weights: LensWeights,
|
weights: LensWeights,
|
||||||
},
|
},
|
||||||
|
|
@ -1682,6 +1683,14 @@ fn reduce(state: &WorldState, command: &CommandEnvelope) -> Result<WorldEffect,
|
||||||
Mismatch::LensWeightsNeverDraw,
|
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 {
|
Ok(WorldEffect::LensWeightsReplaced {
|
||||||
weights: weights.clone(),
|
weights: weights.clone(),
|
||||||
})
|
})
|
||||||
|
|
@ -14647,17 +14656,60 @@ mod clock_tests {
|
||||||
.expect("the owner's effect applies");
|
.expect("the owner's effect applies");
|
||||||
assert_eq!(candidate.lens_weights, proposed);
|
assert_eq!(candidate.lens_weights, proposed);
|
||||||
|
|
||||||
// Identical weights from the owner are still a commit.
|
// Identical weights from the owner change nothing canonical: refused
|
||||||
let current = kernel.snapshot().unwrap();
|
// as `NoCanonicalChange`, and nothing commits.
|
||||||
submit_owner(
|
let result = submit_as(
|
||||||
&mut kernel,
|
&mut kernel,
|
||||||
¤t,
|
CallerId::Principal(owner()),
|
||||||
CommandBody::SetLensWeights {
|
CommandBody::SetLensWeights {
|
||||||
weights: active_weights.clone(),
|
weights: active_weights.clone(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
assert_eq!(kernel.state.revision, current.revision + 1);
|
let Err(KernelError::PatchRejected(mismatches)) = result else {
|
||||||
assert_eq!(kernel.state.lens_weights, active_weights);
|
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
|
/// An owner commit that adds a commitment to the first person: a real
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@
|
||||||
use chrono::{Duration, Utc};
|
use chrono::{Duration, Utc};
|
||||||
use ghostlight::{
|
use ghostlight::{
|
||||||
CommandBody, CommandId, ControllerMode, ControllerPort, CreateWorldIntent, DecisionInvocation,
|
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,
|
Statement, SubjectId, SubmitReceipt, VerifiedPrincipalEvidence, WorldMailbox, WorldPhase,
|
||||||
WorldSnapshot,
|
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());
|
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
|
/// 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
|
/// 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.
|
/// never exists, so no kernel admission is being proven here.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue