Put lens weights in the world (plan step 15, L1 Cut 2)

Lens weights are world data (L1-Q2), replaced only by an owner command
(L1-Q3), and required at creation with no library default (L1-Q6).

Library:
- WorldState.lens_weights, written by genesis through
  ResolvedPatch.lens_weights (Some on the genesis lane only) and carried
  on CreateWorld and CreateWorldIntent (required).
- CommandBody::SetLensWeights and WorldEffect::LensWeightsReplaced: owner
  only, any phase; reduce decides and apply_effect re-decides owner and
  draw rule. A set that never draws is Mismatch::LensWeightsNeverDraw at
  genesis and in the command; verify_state_shape refuses a stored one.
- WorldSnapshot.lens_weights.
- State and commit schemas go to consumer.v5; v4 stores are refused.
- resolve_patch takes the weights; the action lane passes None.

Dungeon:
- CreatePayload.lens_weights required; world_create.v4 in the descriptor
  and operation_schema; the eighth create binding and a lens weights
  control defaulted to uniform_lens_weights(), which is Dungeon policy.
- The smoke harness states uniform weights. The stale-payload test now
  denies v3, a v4 payload without weights (as a payload error) and one
  without a brief; new tests deny an unknown lens and an all-zero set.

Tests: genesis writes and replays weights; an all-zero genesis is refused
and writes no file; only the owner replaces weights (non-owner, stranger,
elaborator, clock, consumer, stale revision, all-zero, hand-built effects);
weights enter state_digest and no scope digest; a history with a weight
change replays to the same digest; forged weight rows fail replay; a v4
store and an all-zero state row are refused; external non-owner refusal
and unknown-lens decode.

This commit builds (Windows only).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-09-16 15:00:45 +02:00
commit d06fd7ec2e
10 changed files with 815 additions and 33 deletions

View file

@ -44,9 +44,9 @@ pub(crate) struct EveRouteHint {
pub(crate) transport: Option<String>,
}
/// The `world_create.v3` payload, in one place, so the button that captures it
/// The `world_create.v4` payload, in one place, so the button that captures it
/// and the descriptor that advertises it cannot drift.
const CREATE_BINDINGS: [&str; 7] = [
const CREATE_BINDINGS: [&str; 8] = [
"title",
"brief",
"subject_label",
@ -54,6 +54,7 @@ const CREATE_BINDINGS: [&str; 7] = [
"operational_agent_label",
"targets",
"jurisdictions",
"lens_weights",
];
const SEED_BINDINGS: [&str; 2] = ["vault_scope", "brief"];
@ -256,6 +257,11 @@ pub(crate) fn authenticated_surface(
match snapshot {
None => {
// Dungeon policy, editable before submission; the library holds no
// default weights.
let default_lens_weights =
serde_json::to_string(&crate::runtime::uniform_lens_weights())
.context("the default lens weights encode")?;
children.extend([
json!({
"id":"world.create.title",
@ -299,6 +305,13 @@ pub(crate) fn authenticated_surface(
"stateBindings":[local_draft("jurisdictions", "json")],
"children":[]
}),
json!({
"id":"world.create.lens_weights",
"kind":"control.input.textarea",
"props":{"label":"Lens weights","rows":2,"value":default_lens_weights,"placeholder":default_lens_weights},
"stateBindings":[local_draft("lens_weights", "json")],
"children":[]
}),
command_button(
"world.create",
"Create world",
@ -309,7 +322,7 @@ pub(crate) fn authenticated_surface(
]);
commands.push(command_descriptor(
"world.create",
"ghostlight.world_create.v3",
"ghostlight.world_create.v4",
&CREATE_BINDINGS,
"WorldMailbox",
));
@ -611,7 +624,7 @@ pub(crate) fn operation_schema(operation: &str) -> Option<&'static str> {
"heimdall.auth.begin" => "heimdall.auth_begin_command.v1",
"heimdall.auth.complete" => "heimdall.auth_complete_command.v1",
"app.auth.logout" => "ghostlight.app_logout.v2",
"world.create" => "ghostlight.world_create.v3",
"world.create" => "ghostlight.world_create.v4",
"world.approve" => "ghostlight.world_approve.v0",
"world.activate" => "ghostlight.world_activate.v0",
"world.advance_time" => "ghostlight.world_advance_time.v0",
@ -758,6 +771,27 @@ mod tests {
assert!(!encoded.contains("session_zero"));
assert!(!encoded.contains("campaign"));
assert_eq!(surface["version"], 0);
// The lens control's default is Dungeon's uniform policy, every stock
// lens named, and it decodes as the weights the payload will carry.
fn find<'a>(node: &'a Value, id: &str) -> Option<&'a Value> {
if node["id"] == id {
return Some(node);
}
node.as_object()?
.values()
.flat_map(|value| match value {
Value::Array(items) => items.iter().collect::<Vec<_>>(),
other => vec![other],
})
.find_map(|child| find(child, id))
}
let control = find(&surface, "world.create.lens_weights").expect("a lens weights control");
let default = control["props"]["value"].as_str().expect("a default value");
let decoded: ghostlight::LensWeights = serde_json::from_str(default).unwrap();
assert_eq!(decoded, crate::runtime::uniform_lens_weights());
assert_eq!(decoded.iter().count(), ghostlight::Lens::ALL.len());
assert!(decoded.iter().all(|(_, weight)| weight == 1));
}
#[test]

View file

@ -15,7 +15,7 @@ use ghostlight::{
ConsumerPort, ConsumerRegistry, ControllerError, ControllerModels, ControllerPendingReason,
ControllerRunner, ControllerWorkCustody, Cover, CoverBudget, CreateJurisdictionIntent,
CreateWorldIntent, DEFAULT_SDK_MODEL_PREFIX, DecisionInvocation, DecisionOpportunity,
KernelError, MailboxError, NarrativeRun, OperationalRun, PrincipalCommandIntent, PrincipalId,
KernelError, Lens, LensWeights, MailboxError, NarrativeRun, OperationalRun, PrincipalCommandIntent, PrincipalId,
SdkBinding, SeedOutcome, SeedPort, Statement, SubjectId, SubjectKind, SubmissionDisposition,
SubmitReceipt, TickMinutes, VaultEvidenceSource, VerifiedPrincipalEvidence, WorldMailbox,
WorldPhase, WorldSnapshot, derive_cover, open_controller_work, open_inference,
@ -159,7 +159,7 @@ struct CreatePayload {
title: String,
/// The world's premise in the owner's words, projected to every lane as
/// guidance. Required, may be empty; a payload that omits it is refused,
/// which is what `world_create.v3` means.
/// which is what `world_create.v4` means.
brief: String,
subject_label: String,
#[serde(default)]
@ -169,13 +169,17 @@ struct CreatePayload {
/// World-wide target of goal-bearing subjects per kind. Required, and may
/// be empty: a world with no target is a deliberate choice, not a default
/// that arrives because nobody said anything. A payload that omits it is
/// refused, which is what `world_create.v3` means.
/// refused, which is what `world_create.v4` means.
targets: BTreeMap<SubjectKind, u32>,
/// The jurisdiction roots, declared by genesis beside the commons because
/// `resolve_patch` only resolves roots the same patch declares. A duplicate
/// handle and a permille sum over 1000 are refused by the resolver, not
/// pre-checked here: a pre-check would be a second reducer.
jurisdictions: Vec<CreateJurisdiction>,
/// Required, and must draw: a payload that omits it is refused, which is
/// what `world_create.v4` means. An unknown lens name is refused by
/// deserialization before any handler runs.
lens_weights: LensWeights,
}
#[derive(Debug, Deserialize)]
@ -1471,6 +1475,7 @@ async fn execute_world(
permille: root.permille,
})
.collect(),
lens_weights: payload.lens_weights,
},
verified_principal,
)
@ -1860,6 +1865,12 @@ fn configured_cover_budget() -> anyhow::Result<CoverBudget> {
.context("cover budget configuration is not usable")
}
/// Dungeon policy for a world created before Session Zero's sliders exist
/// (D2). Not a library default; the library requires the weights to be stated.
pub(crate) fn uniform_lens_weights() -> LensWeights {
LensWeights::new(Lens::ALL.into_iter().map(|lens| (lens, 1)).collect())
}
/// Sized to the connector's per-caller quota. `Capacity` and `InFlight`
/// refusals are already retryable faults, so a pool above that quota degrades
/// to retry rather than to corruption.
@ -2883,14 +2894,15 @@ mod tests {
&fixture.cookie,
invocation(
"world.create",
"ghostlight.world_create.v3",
"ghostlight.world_create.v4",
0,
json!({
"title":"Cutover World",
"brief":"",
"subject_label":"Operator",
"targets":{},
"jurisdictions":[]
"jurisdictions":[],
"lens_weights":{"patina":1,"charter":1,"ledger":1,"hearth":1,"tangle":1,"veil":1,"ember":1,"numen":1}
}),
&uuid::Uuid::new_v4().to_string(),
),
@ -2964,14 +2976,15 @@ mod tests {
let id = uuid::Uuid::new_v4().to_string();
let command = invocation(
"world.create",
"ghostlight.world_create.v3",
"ghostlight.world_create.v4",
0,
json!({
"title":"Retry World",
"brief":"",
"subject_label":"Operator",
"targets":{},
"jurisdictions":[]
"jurisdictions":[],
"lens_weights":{"patina":1,"charter":1,"ledger":1,"hearth":1,"tangle":1,"veil":1,"ember":1,"numen":1}
}),
&id,
);
@ -3147,6 +3160,7 @@ mod tests {
operational_agent_label: Some("Operational Agent".into()),
targets,
jurisdictions,
lens_weights: uniform_lens_weights(),
},
&principal,
)
@ -4003,10 +4017,10 @@ mod tests {
// ---- The seed command ------------------------------------------------
/// Spec test 1. `world_create.v2` is not kept alive beside v3: an
/// invocation announcing it dies at validation before any handler runs, and
/// a payload that announces v3 but omits the brief or the scale target is a
/// payload error rather than a defaulted intent. Neither creates a world.
/// Spec test 1. The previous create schema is not kept alive beside
/// `world_create.v4`: a complete payload announcing it dies at validation before any handler runs,
/// and a payload that announces v4 but omits the lens weights or the brief
/// is a payload error rather than a defaulted intent. None creates a world.
#[tokio::test]
async fn a_stale_create_payload_is_refused() {
let fixture = fixture().await;
@ -4015,9 +4029,9 @@ mod tests {
&fixture.cookie,
invocation(
"world.create",
"ghostlight.world_create.v2",
"ghostlight.world_create.v3",
0,
json!({"title":"Stale World","subject_label":"Operator","targets":{},"jurisdictions":[]}),
json!({"title":"Stale World","brief":"","subject_label":"Operator","targets":{},"jurisdictions":[]}),
&uuid::Uuid::new_v4().to_string(),
),
)
@ -4025,14 +4039,39 @@ mod tests {
assert_eq!(stale["state"], "denied");
assert!(current_world(&fixture.state).await.unwrap().is_none());
let unweighted = post(
&fixture.state,
&fixture.cookie,
invocation(
"world.create",
"ghostlight.world_create.v4",
0,
json!({"title":"Unweighted World","brief":"","subject_label":"Operator","targets":{},"jurisdictions":[]}),
&uuid::Uuid::new_v4().to_string(),
),
)
.await;
assert_eq!(unweighted["state"], "denied");
// Refused as a payload, before genesis: a defaulted empty set would
// also be denied, by the resolver, and must not pass for this.
let message = unweighted["message"].as_str().unwrap_or_default();
assert!(
message.contains("missing field `lens_weights`"),
"the omission was not refused at the payload: {message}"
);
assert!(
current_world(&fixture.state).await.unwrap().is_none(),
"a v4 payload with no lens weights created a world anyway"
);
let partial = post(
&fixture.state,
&fixture.cookie,
invocation(
"world.create",
"ghostlight.world_create.v3",
"ghostlight.world_create.v4",
0,
json!({"title":"Half World","subject_label":"Operator","targets":{},"jurisdictions":[]}),
json!({"title":"Half World","subject_label":"Operator","targets":{},"jurisdictions":[],"lens_weights":{"patina":1,"charter":1,"ledger":1,"hearth":1,"tangle":1,"veil":1,"ember":1,"numen":1}}),
&uuid::Uuid::new_v4().to_string(),
),
)
@ -4040,10 +4079,62 @@ mod tests {
assert_eq!(partial["state"], "denied");
assert!(
current_world(&fixture.state).await.unwrap().is_none(),
"a v2 payload with no scale target created a world anyway"
"a v4 payload with no brief created a world anyway"
);
}
/// A lens name the library does not know, beside one it does, is refused
/// by deserialization before any handler runs. No world is created.
#[tokio::test]
async fn a_create_payload_naming_an_unknown_lens_is_refused() {
let fixture = fixture().await;
let result = post(
&fixture.state,
&fixture.cookie,
invocation(
"world.create",
"ghostlight.world_create.v4",
0,
json!({"title":"Tribunal World","brief":"","subject_label":"Operator","targets":{},"jurisdictions":[],"lens_weights":{"patina":1,"tribunal":1}}),
&uuid::Uuid::new_v4().to_string(),
),
)
.await;
assert_eq!(result["state"], "denied");
let message = result["message"].as_str().unwrap_or_default();
assert!(
message.contains("tribunal"),
"the unknown lens was not refused by name: {message}"
);
assert!(current_world(&fixture.state).await.unwrap().is_none());
}
/// Every lens named, every weight zero: the payload decodes, and the
/// library's genesis refuses it with `LensWeightsNeverDraw`. No world.
#[tokio::test]
async fn a_create_payload_whose_lenses_never_draw_is_refused() {
let fixture = fixture().await;
let result = post(
&fixture.state,
&fixture.cookie,
invocation(
"world.create",
"ghostlight.world_create.v4",
0,
json!({"title":"Still World","brief":"","subject_label":"Operator","targets":{},"jurisdictions":[],"lens_weights":{"patina":0,"charter":0,"ledger":0,"hearth":0,"tangle":0,"veil":0,"ember":0,"numen":0}}),
&uuid::Uuid::new_v4().to_string(),
),
)
.await;
assert_eq!(result["state"], "denied");
let message = result["message"].as_str().unwrap_or_default();
assert!(
message.contains("LensWeightsNeverDraw"),
"the refusal did not name the draw rule: {message}"
);
assert!(current_world(&fixture.state).await.unwrap().is_none());
}
/// Every `control.button` in a surface, at whatever depth.
fn surface_buttons(node: &Value, into: &mut Vec<String>) {
if node["kind"] == "control.button"

View file

@ -172,6 +172,7 @@ pub(super) fn exercise(
evidence: Vec::new(),
},
None,
None,
)
.map_err(KernelError::PatchRejected)?
.operations

View file

@ -1219,7 +1219,7 @@ mod tests {
}],
evidence: vec![EvidenceRef::new(MIRROR_EVIDENCE)],
};
let resolved = patch::resolve_patch(&kernel.state, command_id, &foreign, None)
let resolved = patch::resolve_patch(&kernel.state, command_id, &foreign, None, None)
.expect("the patch resolves");
let effect = super::super::WorldEffect::PatchAdmitted {
answers: None,
@ -2234,7 +2234,7 @@ mod tests {
assert!(super::super::journal::verify_state_shape(&previous).is_err());
assert_eq!(
super::super::STATE_SCHEMA,
"ghostlight.world_state.consumer.v4"
"ghostlight.world_state.consumer.v5"
);
}

View file

@ -6667,6 +6667,7 @@ mod tests {
material: None,
};
let snapshot = WorldSnapshot {
lens_weights: crate::tests::stock_weights(),
world_id: opportunity.world_id,
revision: opportunity.revision,
phase: WorldPhase::Active,
@ -6924,6 +6925,7 @@ mod tests {
open: true,
};
let snapshot = WorldSnapshot {
lens_weights: crate::tests::stock_weights(),
world_id: opportunity.world_id,
revision: opportunity.revision,
phase: WorldPhase::Active,
@ -7419,6 +7421,7 @@ mod tests {
affordance_ids: vec![speak_affordance],
};
let snapshot = WorldSnapshot {
lens_weights: crate::tests::stock_weights(),
world_id: opportunity.world_id,
revision: opportunity.revision,
phase: WorldPhase::Active,
@ -8415,6 +8418,7 @@ mod tests {
let creation = mailbox
.create_fixture(
CreateWorld {
lens_weights: crate::tests::stock_weights(),
id: CommandId::new(),
owner: owner.clone(),
title: "Controller Fixture".into(),
@ -9010,6 +9014,7 @@ mod tests {
let creation = mailbox
.create_fixture(
CreateWorld {
lens_weights: crate::tests::stock_weights(),
id: CommandId::new(),
owner: owner.clone(),
title: "Midnight Roll Call".into(),
@ -9413,6 +9418,7 @@ mod tests {
let creation = mailbox
.create_fixture(
CreateWorld {
lens_weights: crate::tests::stock_weights(),
id: CommandId::new(),
owner: owner.clone(),
title: "Elaboration Fixture".into(),
@ -9703,6 +9709,7 @@ mod tests {
let creation = mailbox
.create_fixture(
CreateWorld {
lens_weights: crate::tests::stock_weights(),
id: CommandId::new(),
owner: owner.clone(),
title: "Cover Fixture".into(),
@ -10250,6 +10257,7 @@ mod tests {
let creation = mailbox
.create_fixture(
CreateWorld {
lens_weights: crate::tests::stock_weights(),
id: CommandId::new(),
owner: owner.clone(),
title: "Apart Fixture".into(),
@ -10695,6 +10703,7 @@ mod tests {
mailbox
.create_fixture(
CreateWorld {
lens_weights: crate::tests::stock_weights(),
id: CommandId::new(),
owner: owner.clone(),
title: "Seed Fixture".into(),
@ -11447,6 +11456,7 @@ mod tests {
mailbox
.create(
CreateWorldIntent {
lens_weights: crate::tests::stock_weights(),
id: CommandId::new(),
title: "The Whole Road".into(),
brief: String::new(),
@ -11607,6 +11617,7 @@ mod tests {
let owner = PrincipalId::new("seed-owner");
let authenticated = AuthenticatedCaller::fixture(CallerId::Principal(owner.clone()));
let genesis = |intent: WorldScaleIntentRef, roots: &[&str]| CreateWorld {
lens_weights: crate::tests::stock_weights(),
id: CommandId::new(),
owner: owner.clone(),
title: "Two Roots".into(),
@ -13135,6 +13146,7 @@ mod tests {
let creation = mailbox
.create_fixture(
CreateWorld {
lens_weights: crate::tests::stock_weights(),
id: CommandId::new(),
owner: owner.clone(),
title: "Witness Fixture".into(),
@ -13634,6 +13646,7 @@ mod tests {
let creation = mailbox
.create_fixture(
CreateWorld {
lens_weights: crate::tests::stock_weights(),
id: CommandId::new(),
owner: owner.clone(),
title: "Interruption Fixture".into(),

View file

@ -736,6 +736,11 @@ pub(super) fn verify_state_shape(state: &WorldState) -> Result<(), JournalError>
"the scale intent names a non-place root or distributes more than the whole".into(),
));
}
// The third statement of the draw rule, beside genesis and
// `SetLensWeights`: no stored world holds weights that never draw.
if !state.lens_weights.draws() {
return Err(JournalError::Corrupt("the lens weights never draw".into()));
}
// The civic subgraph, in the slot the deleted `authority_scope` check
// occupied: a jurisdiction names live ground under a canonical kind, an
// office sits on an institution and lends something to a person who holds
@ -1062,6 +1067,7 @@ mod tests {
let owner = PrincipalId::new("owner@example.test");
let authenticated = AuthenticatedCaller::fixture(CallerId::Principal(owner.clone()));
let creation = CreateWorld {
lens_weights: crate::tests::stock_weights(),
id: CommandId::new(),
owner: owner.clone(),
title: "Before".into(),
@ -1144,6 +1150,7 @@ mod tests {
let owner = PrincipalId::new("owner@example.test");
let authenticated = AuthenticatedCaller::fixture(CallerId::Principal(owner.clone()));
let creation = CreateWorld {
lens_weights: crate::tests::stock_weights(),
id: CommandId::new(),
owner: owner.clone(),
title: "Kharad".into(),
@ -1265,6 +1272,186 @@ mod tests {
}
}
/// Lens weights entered both rows — the state row holds them and a commit
/// row carries `LensWeightsReplaced` — so both schemas bumped and both
/// `consumer.v4` strings are refused at open. No migration, no dual read.
#[test]
fn a_store_written_before_the_lens_schema_is_refused() {
for (row_type, schema) in [
(STATE_ROW, "ghostlight.world_state.consumer.v4"),
(COMMIT_ROW, "ghostlight.world_commit.consumer.v4"),
] {
let row = CultCacheEnvelope {
key: "state".into(),
r#type: row_type.into(),
payload: Vec::new(),
stored_at: Utc::now().to_rfc3339(),
schema_id: Some(schema.into()),
};
let error = recover(vec![row], None).unwrap_err();
let JournalError::Corrupt(message) = error else {
panic!("expected a corrupt store for {schema}");
};
assert!(
message.contains("has the wrong schema"),
"unexpected refusal for {schema}: {message}"
);
}
}
/// A world whose history includes an owner's `SetLensWeights`, reopened
/// from its own store rows.
fn lens_history() -> (tempfile::TempDir, WorldKernel, crate::LensWeights) {
use crate::tests::{auth_principal, creation, owner, submit_owner};
use crate::{Lens, LensWeights};
let directory = tempfile::tempdir().unwrap();
let (mut kernel, _) = WorldKernel::create(
directory.path().join("world.cc"),
creation(CommandId::new(), "Lens History"),
&auth_principal(owner()),
)
.unwrap();
let replaced = LensWeights::new(BTreeMap::from([
(Lens::Patina, 0),
(Lens::Charter, 4),
(Lens::Ledger, 1),
(Lens::Numen, 2),
]));
let snapshot = kernel.snapshot().unwrap();
submit_owner(
&mut kernel,
&snapshot,
CommandBody::SetLensWeights {
weights: replaced.clone(),
},
);
(directory, kernel, replaced)
}
fn rows_of(state: &WorldState, commits: &[WorldCommit]) -> Vec<CultCacheEnvelope> {
let mut rows: Vec<CultCacheEnvelope> = commits
.iter()
.map(|commit| {
envelope(COMMIT_ROW, COMMIT_SCHEMA, commit.command.id().key(), commit).unwrap()
})
.collect();
rows.push(envelope(STATE_ROW, STATE_SCHEMA, state.world_id.key(), state).unwrap());
rows
}
/// Replay re-derives the owner's weight change: the honest history reopens
/// to the same state digest and weights, and the store on disk does too.
#[test]
fn a_history_with_a_lens_weight_change_replays_exactly() {
let (directory, kernel, replaced) = lens_history();
let honest: Vec<WorldCommit> = kernel.journal.commits.values().cloned().collect();
let (_, replayed, commits) =
recover(rows_of(&kernel.state, &honest), None).expect("the honest history replays");
assert_eq!(commits.len(), honest.len());
assert_eq!(replayed.state_digest, kernel.state.state_digest);
assert_eq!(replayed.lens_weights, replaced);
let (world_id, digest) = (kernel.state.world_id, kernel.state.state_digest.clone());
drop(kernel);
let reopened = WorldKernel::open(directory.path().join("world.cc"), world_id).unwrap();
assert_eq!(reopened.state.state_digest, digest);
assert_eq!(reopened.state.lens_weights, replaced);
}
/// A stored weight change is not believed about who made it or what it
/// wrote. Each forgery re-seals its row, so the chain digest cannot answer
/// for the authority or draw check.
#[test]
fn a_forged_lens_weights_row_fails_replay() {
use crate::tests::human_principal;
use crate::{Lens, LensWeights};
let (_directory, kernel, _) = lens_history();
let honest: Vec<WorldCommit> = kernel.journal.commits.values().cloned().collect();
let forge = |rewrite: &dyn Fn(&mut WorldCommit)| {
let mut forged_one = false;
let forged: Vec<WorldCommit> = honest
.iter()
.cloned()
.map(|mut commit| {
if matches!(commit.effect, WorldEffect::LensWeightsReplaced { .. }) {
rewrite(&mut commit);
commit.digest = commit_digest(&commit).unwrap();
forged_one = true;
}
commit
})
.collect();
assert!(forged_one, "no lens weights row was found to forge");
let error = recover(rows_of(&kernel.state, &forged), None)
.expect_err("a forged lens weights row replayed");
let JournalError::Corrupt(detail) = &error else {
panic!("unexpected refusal: {error:?}");
};
assert!(
!detail.contains("commit chain is not contiguous"),
"the chain digest answered for the check: {detail}"
);
};
// A non-owner principal with a live approver role in this world.
forge(&|commit| {
if let CommittedCommand::WorldCommand(command) = &mut commit.command {
command.caller = CallerId::Principal(human_principal());
}
});
// The clock capability.
forge(&|commit| {
if let CommittedCommand::WorldCommand(command) = &mut commit.command {
command.caller = CallerId::System(crate::SystemCapability::Clock);
}
});
// Every lens named, all at zero, in both the command and its effect.
let never = LensWeights::new(Lens::ALL.into_iter().map(|lens| (lens, 0)).collect());
forge(&|commit| {
if let CommittedCommand::WorldCommand(command) = &mut commit.command {
command.body = CommandBody::SetLensWeights {
weights: never.clone(),
};
}
commit.effect = WorldEffect::LensWeightsReplaced {
weights: never.clone(),
};
});
}
/// The third statement of the draw rule: a state row whose weights never
/// draw is refused by the shape check itself, not only by replay.
#[test]
fn a_state_row_whose_lens_weights_never_draw_is_refused() {
use crate::{Lens, LensWeights};
let (_directory, kernel, _) = lens_history();
let mut forged = kernel.state.clone();
forged.lens_weights =
LensWeights::new(Lens::ALL.into_iter().map(|lens| (lens, 0)).collect());
let JournalError::Corrupt(detail) =
verify_state_shape(&forged).expect_err("an all-zero weight set passed the shape check")
else {
unreachable!("the shape check only reports corruption")
};
assert!(detail.contains("lens weights"), "unexpected refusal: {detail}");
forged.lens_weights = LensWeights::default();
assert!(verify_state_shape(&forged).is_err());
assert!(verify_state_shape(&kernel.state).is_ok());
let commits: Vec<WorldCommit> = kernel.journal.commits.values().cloned().collect();
let mut zeroed = kernel.state.clone();
zeroed.lens_weights =
LensWeights::new(Lens::ALL.into_iter().map(|lens| (lens, 0)).collect());
assert!(matches!(
recover(rows_of(&zeroed, &commits), None),
Err(JournalError::Corrupt(_))
));
}
/// 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.
@ -1495,6 +1682,7 @@ mod tests {
let authenticated = AuthenticatedCaller::fixture(CallerId::Principal(owner.clone()));
let creation_id = CommandId::new();
let creation = CreateWorld {
lens_weights: crate::tests::stock_weights(),
id: creation_id,
owner: owner.clone(),
title: "Admitted".into(),

View file

@ -171,8 +171,8 @@ impl VerifiedPrincipalEvidence {
}
}
pub const STATE_SCHEMA: &str = "ghostlight.world_state.consumer.v4";
pub(crate) const COMMIT_SCHEMA: &str = "ghostlight.world_commit.consumer.v4";
pub const STATE_SCHEMA: &str = "ghostlight.world_state.consumer.v5";
pub(crate) const COMMIT_SCHEMA: &str = "ghostlight.world_commit.consumer.v5";
/// Compatibility tag derived from [`STATE_SCHEMA`]: the trailing
/// `<family>-<version>` pair (e.g. `foundation-v1`). Callers that publish a
@ -410,6 +410,9 @@ struct CreateWorld {
/// Authored once, here, and never mutated: `CommandBody::AdmitPatch` carries
/// no intent, so write-once is the command shape rather than a check.
scale_intent: WorldScaleIntentRef,
/// The world's flavor as genesis writes it. `SetLensWeights` may replace
/// it later; this command carries what the world began with.
lens_weights: LensWeights,
}
/// Unattributed creation intent. World ingress derives ownership, controller
@ -431,6 +434,9 @@ pub struct CreateWorldIntent {
/// The jurisdiction roots genesis declares beside the commons, because
/// `resolve_patch` only resolves roots the same patch declares.
pub jurisdictions: Vec<CreateJurisdictionIntent>,
/// Required. The library holds no default weights; a consumer states the
/// world's flavor or is refused.
pub lens_weights: LensWeights,
}
/// One jurisdiction root as ingress states it. The handle is a string here and
@ -672,6 +678,14 @@ pub enum CommandBody {
AdvanceTime {
minutes: TickMinutes,
},
/// 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.
SetLensWeights {
weights: LensWeights,
},
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
@ -845,6 +859,10 @@ struct WorldState {
now: FictionalMinutes,
/// The authored scale target, written once by genesis and never mutated.
scale_intent: WorldScaleIntent,
/// Written by genesis and replaced only by `SetLensWeights`; read by the
/// elaborator's draw and by nothing that admits. It enters `state_digest`
/// and no `ScopePreimage`.
lens_weights: LensWeights,
controller_assignments: BTreeMap<DecisionScope, ControllerAssignment>,
/// What an affordance *is*. World-authored, Draft-only, written by
/// `admit_resolved` alone.
@ -896,6 +914,11 @@ enum WorldEffect {
to: FictionalMinutes,
motion: Motion,
},
/// The owner replaced the world's lens weights. `apply_effect` re-decides
/// the owner and the draw rule.
LensWeightsReplaced {
weights: LensWeights,
},
}
/// The exact raw command admitted by the world owner. This is the sole
@ -1113,6 +1136,7 @@ pub struct WorldSnapshot {
/// commitment gazetteer.
pub boundaries: Vec<CausalBoundary>,
pub scale_deficit: Vec<ScaleDeficitRow>,
pub lens_weights: LensWeights,
pub state_digest: String,
pub last_commit_digest: Option<String>,
}
@ -1318,6 +1342,7 @@ fn prepare_creation(
input.id,
&input.patch,
Some(&input.scale_intent),
Some(&input.lens_weights),
)
.map_err(KernelError::PatchRejected)?;
Ok(PreparedCreation {
@ -1640,7 +1665,7 @@ fn reduce(state: &WorldState, command: &CommandEnvelope) -> Result<WorldEffect,
!(patch.declarations.is_empty() && patch.evidence.is_empty()),
)?;
let confinement = require_patch_author(state, &command.caller, answers.as_ref())?;
let resolved = patch::resolve_patch(state, command.id, patch, None)
let resolved = patch::resolve_patch(state, command.id, patch, None, None)
.map_err(KernelError::PatchRejected)?;
if let Some(ground) = confinement {
confine_to_ground(state, &resolved, ground).map_err(KernelError::PatchRejected)?;
@ -1650,6 +1675,17 @@ fn reduce(state: &WorldState, command: &CommandEnvelope) -> Result<WorldEffect,
resolved,
})
}
CommandBody::SetLensWeights { weights } => {
require_owner(state, &command.caller)?;
if !weights.draws() {
return Err(KernelError::PatchRejected(vec![
Mismatch::LensWeightsNeverDraw,
]));
}
Ok(WorldEffect::LensWeightsReplaced {
weights: weights.clone(),
})
}
}
}
@ -1770,6 +1806,9 @@ impl WorldState {
last_opportunity_at: BTreeMap::new(),
now: FictionalMinutes::default(),
scale_intent: WorldScaleIntent::default(),
// Never persisted: genesis overwrites it, and `verify_state_shape`
// refuses a stored set that never draws.
lens_weights: LensWeights::default(),
controller_assignments: BTreeMap::new(),
affordance_catalog: BTreeMap::new(),
affordance_grants: BTreeMap::new(),
@ -1812,6 +1851,7 @@ impl WorldState {
command.id,
&command.patch,
Some(&command.scale_intent),
Some(&command.lens_weights),
)
.map_err(KernelError::PatchRejected)?;
if &expected != resolved {
@ -2047,6 +2087,9 @@ fn admit_resolved(state: &mut WorldState, resolved: &ResolvedPatch) -> Result<()
if let Some(intent) = &resolved.scale_intent {
state.scale_intent = intent.clone();
}
if let Some(weights) = &resolved.lens_weights {
state.lens_weights = weights.clone();
}
apply_operations(state, &resolved.operations, &resolved.evidence)
}
@ -3718,6 +3761,7 @@ fn snapshot(state: &WorldState) -> Result<WorldSnapshot, KernelError> {
now: state.now,
boundaries: derive_boundaries(state)?,
scale_deficit: derive_scale_deficit(state)?,
lens_weights: state.lens_weights.clone(),
state_digest: state.state_digest.clone(),
last_commit_digest: state.last_commit_digest.clone(),
})
@ -4892,6 +4936,14 @@ fn apply_effect(
.insert(written.source, written.magnitude);
}
}
WorldEffect::LensWeightsReplaced { weights } => {
if caller != &CallerId::Principal(state.owner.clone()) || !weights.draws() {
return Err(KernelError::Invariant(
"lens weights effect does not satisfy owner admission".into(),
));
}
state.lens_weights = weights.clone();
}
}
Ok(())
}
@ -5064,6 +5116,7 @@ mod tests {
],
},
scale_intent: WorldScaleIntentRef::default(),
lens_weights: stock_weights(),
}
}
@ -8246,6 +8299,7 @@ mod custody_tests {
let mut candidate = kernel.state.clone();
let forged = WorldEffect::PatchAdmitted {
resolved: ResolvedPatch {
lens_weights: None,
subjects: Vec::new(),
entities: Vec::new(),
routes: Vec::new(),
@ -8840,6 +8894,7 @@ mod custody_tests {
let forge = |operations: Vec<ResolvedOp>| WorldEffect::PatchAdmitted {
resolved: ResolvedPatch {
lens_weights: None,
subjects: Vec::new(),
entities: Vec::new(),
routes: Vec::new(),
@ -11493,6 +11548,7 @@ mod witness_tests {
evidence: Vec::new(),
},
None,
None,
)
.expect("the witness resolves over committed state");
for ground in [
@ -14372,7 +14428,7 @@ mod clock_tests {
panic!("the operations helper builds an admitted patch");
};
let resolved =
patch::resolve_patch(&kernel.state, CommandId::new(), patch, None).expect("resolves");
patch::resolve_patch(&kernel.state, CommandId::new(), patch, None, None).expect("resolves");
assert!(
resolved.scale_intent.is_none(),
"an Active patch resolved a scale intent"
@ -14382,6 +14438,292 @@ mod clock_tests {
assert_eq!(kernel.state.scale_intent, authored);
}
// ---- lens weights: world data, genesis, and the owner command --------
fn every_lens_at_zero() -> LensWeights {
LensWeights::new(Lens::ALL.into_iter().map(|lens| (lens, 0)).collect())
}
#[test]
fn genesis_writes_the_lens_weights_it_was_given() {
let directory = tempfile::tempdir().unwrap();
let path = directory.path().join("world.cc");
let given = LensWeights::new(BTreeMap::from([(Lens::Patina, 2), (Lens::Numen, 1)]));
let mut input = creation(CommandId::new(), "Flavored");
input.lens_weights = given.clone();
let (kernel, _) = WorldKernel::create(&path, input, &auth_principal(owner())).unwrap();
let created = kernel.snapshot().unwrap();
assert_eq!(created.lens_weights, given);
assert_eq!(kernel.state.lens_weights, given);
let world_id = created.world_id;
drop(kernel);
let replayed = WorldKernel::open(&path, world_id).unwrap();
assert_eq!(replayed.snapshot().unwrap(), created);
assert_eq!(replayed.state.state_digest, created.state_digest);
}
#[test]
fn a_genesis_lens_set_that_never_draws_is_refused() {
let directory = tempfile::tempdir().unwrap();
let path = directory.path().join("world.cc");
let mut input = creation(CommandId::new(), "Flavorless");
input.lens_weights = every_lens_at_zero();
let Err(error) = WorldKernel::create(&path, input, &auth_principal(owner())) else {
panic!("a genesis lens set that never draws was admitted");
};
let KernelError::PatchRejected(mismatches) = error else {
panic!("expected a rejected genesis patch, got {error:?}");
};
assert_eq!(mismatches, vec![Mismatch::LensWeightsNeverDraw]);
assert!(!path.exists(), "a refused genesis wrote a world file");
// The same creation with one real weight among the zeros is admitted.
let mut input = creation(CommandId::new(), "Flavorless");
let mut weights = BTreeMap::from_iter(every_lens_at_zero().iter());
weights.insert(Lens::Veil, 1);
input.lens_weights = LensWeights::new(weights);
WorldKernel::create(&path, input, &auth_principal(owner())).expect("one nonzero weight");
}
/// Soul falsification, the lens twin of the scale intent's write-once
/// test: an Active patch resolves no weights; only the owner's
/// `SetLensWeights` replaces them, in Draft and in Active; every other
/// caller is `Unauthorized`; a set that never draws is refused; and
/// `apply_effect` re-decides the owner against a hand-built effect.
#[test]
fn soul_the_lens_weights_are_replaced_only_by_the_owner_command() {
let directory = tempfile::tempdir().unwrap();
let (mut kernel, _) = WorldKernel::create(
directory.path().join("world.cc"),
creation(CommandId::new(), "Owner Flavor"),
&auth_principal(owner()),
)
.unwrap();
let draft_weights = LensWeights::new(BTreeMap::from([
(Lens::Patina, 0),
(Lens::Charter, 3),
(Lens::Ledger, 1),
]));
let draft = kernel.snapshot().unwrap();
submit_owner(
&mut kernel,
&draft,
CommandBody::SetLensWeights {
weights: draft_weights.clone(),
},
);
assert_eq!(kernel.state.revision, draft.revision + 1);
assert_eq!(kernel.state.lens_weights, draft_weights);
let active = activate(&mut kernel);
let active_weights = LensWeights::new(BTreeMap::from([
(Lens::Hearth, 5),
(Lens::Tangle, 0),
(Lens::Ember, 2),
]));
submit_owner(
&mut kernel,
&active,
CommandBody::SetLensWeights {
weights: active_weights.clone(),
},
);
assert_eq!(kernel.state.revision, active.revision + 1);
assert_eq!(kernel.state.lens_weights, active_weights);
// An Active patch is handed no weights, so it cannot resolve any.
let resolved = patch::resolve_patch(
&kernel.state,
CommandId::new(),
&operations_patch(&kernel),
None,
None,
)
.expect("resolves");
assert!(resolved.lens_weights.is_none(), "an Active patch resolved lens weights");
let after_patch = kernel.snapshot().unwrap();
let patch = operations_patch(&kernel);
submit_owner(
&mut kernel,
&after_patch,
CommandBody::AdmitPatch {
answers: None,
patch,
},
);
assert_eq!(kernel.state.lens_weights, active_weights);
// Every caller but the owner, with real weights.
let proposed = super::tests::stock_weights();
let before = kernel.state.clone();
for caller in [
CallerId::Principal(human_principal()),
CallerId::Principal(PrincipalId::new("stranger@example.test")),
elaborator(JurisdictionKey::Uncovered),
CallerId::System(SystemCapability::Clock),
CallerId::System(SystemCapability::Consumer {
consumer: ConsumerId::of_name("lens-consumer"),
}),
] {
let result = submit_as(
&mut kernel,
caller.clone(),
CommandBody::SetLensWeights {
weights: proposed.clone(),
},
);
assert!(
matches!(result, Err(KernelError::Unauthorized)),
"{caller:?} replaced the lens weights: {result:?}"
);
}
assert_eq!(kernel.state, before);
// The owner, with every lens named at zero.
let result = submit_as(
&mut kernel,
CallerId::Principal(owner()),
CommandBody::SetLensWeights {
weights: every_lens_at_zero(),
},
);
let Err(KernelError::PatchRejected(mismatches)) = result else {
panic!("a lens set that never draws was admitted: {result:?}");
};
assert_eq!(mismatches, vec![Mismatch::LensWeightsNeverDraw]);
assert_eq!(kernel.state, before);
// The owner, with valid weights, against the revision before the
// last commit.
let stale = command(
&draft,
CommandId::new(),
CallerId::Principal(owner()),
CommandBody::SetLensWeights {
weights: proposed.clone(),
},
);
assert!(matches!(
kernel.submit(stale, &auth_principal(owner())),
Err(KernelError::RevisionMismatch { .. })
));
assert_eq!(kernel.state, before);
// `apply_effect` re-decides: a hand-built effect under a non-owner or
// a system caller never applies, and neither does one that never draws.
for (caller, weights) in [
(CallerId::Principal(human_principal()), proposed.clone()),
(CallerId::System(SystemCapability::Clock), proposed.clone()),
(CallerId::Principal(owner()), every_lens_at_zero()),
] {
let mut candidate = kernel.state.clone();
let error = apply_effect(
&mut candidate,
CommandId::new(),
&caller,
&WorldEffect::LensWeightsReplaced { weights },
)
.unwrap_err();
assert!(
matches!(
error,
KernelError::Invariant(_) | KernelError::Unauthorized
),
"{caller:?}: {error:?}"
);
assert_eq!(candidate, kernel.state);
}
// The honest effect applies through the same arm.
let mut candidate = kernel.state.clone();
apply_effect(
&mut candidate,
CommandId::new(),
&CallerId::Principal(owner()),
&WorldEffect::LensWeightsReplaced {
weights: proposed.clone(),
},
)
.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(
&mut kernel,
&current,
CommandBody::SetLensWeights {
weights: active_weights.clone(),
},
);
assert_eq!(kernel.state.revision, current.revision + 1);
assert_eq!(kernel.state.lens_weights, active_weights);
}
/// An owner commit that adds a commitment to the first person: a real
/// Active patch with no answer, and nothing to do with lenses.
fn operations_patch(kernel: &WorldKernel) -> WorldPatch {
let subject = kernel
.state
.subjects
.iter()
.find(|(_, state)| state.kind == SubjectKind::Person)
.map(|(id, _)| *id)
.expect("a person");
let CommandBody::AdmitPatch { patch, .. } = operations(vec![ComponentOp::CreateCommitment {
subject: Ref::Existing(subject),
counterparty: None,
kind: CommitmentKind::Goal,
due: FictionalMinutes(kernel.state.now.0 + 500),
period: None,
checks: Vec::new(),
statement: Statement::new("A promise kept with no flavor at all.").unwrap(),
}]) else {
panic!("the operations helper builds an admitted patch");
};
patch
}
#[test]
fn lens_weights_enter_the_state_digest_and_no_scope_digest() {
let directory = tempfile::tempdir().unwrap();
let (mut kernel, _) = WorldKernel::create(
directory.path().join("world.cc"),
creation(CommandId::new(), "Digest Flavor"),
&auth_principal(owner()),
)
.unwrap();
let active = activate(&mut kernel);
let mut other = kernel.state.clone();
other.lens_weights =
LensWeights::new(BTreeMap::from([(Lens::Patina, 1), (Lens::Veil, 7)]));
assert_ne!(
state_digest(&other).unwrap(),
state_digest(&kernel.state).unwrap(),
"lens weights did not enter the state digest"
);
let digests = |kernel: &WorldKernel| -> BTreeMap<SubjectId, ScopeDigest> {
derive_opportunities(&kernel.state)
.unwrap()
.into_iter()
.map(|opportunity| (opportunity.scope.subject_id, opportunity.scope_digest))
.collect()
};
let before = digests(&kernel);
assert!(!before.is_empty(), "the active fixture derives opportunities");
submit_owner(
&mut kernel,
&active,
CommandBody::SetLensWeights {
weights: other.lens_weights.clone(),
},
);
assert_ne!(kernel.state.state_digest, active.state_digest);
assert_eq!(digests(&kernel), before, "lens weights entered a scope digest");
}
/// Soul falsification: a discharge names one subject's own live key.
#[test]
fn soul_discharging_a_foreign_or_already_discharged_commitment_is_refused() {
@ -15029,6 +15371,7 @@ mod clock_tests {
command_id,
&shed_under(clockwork.dead_end, "shed"),
None,
None,
)
.expect("the patch resolves");
let effect = WorldEffect::PatchAdmitted {

View file

@ -188,6 +188,7 @@ impl WorldMailbox {
evidence: Vec::new(),
},
scale_intent,
lens_weights: input.lens_weights,
},
AuthenticatedCaller::verified_principal(principal_id),
)
@ -933,6 +934,7 @@ mod tests {
fn creation(id: CommandId, title: &str) -> CreateWorld {
CreateWorld {
lens_weights: crate::tests::stock_weights(),
id,
owner: PrincipalId::new("owner"),
title: title.into(),
@ -1354,6 +1356,7 @@ mod tests {
let created = mailbox
.create_fixture(
CreateWorld {
lens_weights: crate::tests::stock_weights(),
id: CommandId::new(),
owner: owner.clone(),
title: "Soul Stamping".into(),

View file

@ -14,8 +14,8 @@
use super::clock::{FictionalMinutes, TickMinutes};
use super::tool_schema;
use super::{
AffordanceId, CommandId, ControllerAssignment, ControllerId, EdgeId, EntityId, NewController,
SubjectId, SubjectKind, SubjectState, WorldId,
AffordanceId, CommandId, ControllerAssignment, ControllerId, EdgeId, EntityId, LensWeights,
NewController, SubjectId, SubjectKind, SubjectState, WorldId,
};
use codex_connector::CodexToolDefinition;
use serde::{Deserialize, Serialize};
@ -1552,6 +1552,9 @@ pub enum Mismatch {
/// The permille weights sum over 1000: weights distribute the target and
/// never raise it.
ScaleWeightsExceedWhole,
/// A lens weight set with no nonzero weight, at genesis or in
/// `SetLensWeights`. A single zero weight is admitted and never draws.
LensWeightsNeverDraw,
/// A jurisdictional author wrote outside its jurisdiction. The owner is
/// unconfined and never sees this.
OutsideJurisdiction {
@ -1901,6 +1904,9 @@ pub(super) struct ResolvedPatch {
/// carries no intent, so the write-once rule is the command shape rather
/// than a check.
pub(super) scale_intent: Option<WorldScaleIntent>,
/// `Some` on the genesis lane and nowhere else, as `scale_intent` is. After
/// genesis the weights change only through `SetLensWeights`.
pub(super) lens_weights: Option<LensWeights>,
}
impl ResolvedPatch {
@ -2943,6 +2949,7 @@ pub(super) fn resolve_patch(
command_id: CommandId,
patch: &WorldPatch,
scale_intent: Option<&WorldScaleIntentRef>,
lens_weights: Option<&LensWeights>,
) -> Result<ResolvedPatch, Vec<Mismatch>> {
let world_id = state.world_id;
let admits_human = super::admits_human(state.revision, &state.subjects);
@ -4614,6 +4621,12 @@ pub(super) fn resolve_patch(
}
}
if let Some(weights) = lens_weights
&& !weights.draws()
{
mismatches.push(Mismatch::LensWeightsNeverDraw);
}
if patch.declarations.is_empty() && patch.operations.is_empty() {
mismatches.push(Mismatch::NoCanonicalChange);
}
@ -5283,6 +5296,7 @@ pub(super) fn resolve_patch(
})
.collect(),
}),
lens_weights: lens_weights.cloned(),
})
}
@ -8441,8 +8455,8 @@ mod tests {
assert_ne!(admitted, genesis_human);
let command_id = CommandId::new();
let first = resolve_patch(&kernel.state, command_id, &patch, None).unwrap();
let second = resolve_patch(&kernel.state, command_id, &patch, None).unwrap();
let first = resolve_patch(&kernel.state, command_id, &patch, None, None).unwrap();
let second = resolve_patch(&kernel.state, command_id, &patch, None, None).unwrap();
assert_eq!(first, second);
}
@ -8585,7 +8599,7 @@ mod tests {
Some(Ref::Draft(DraftHandle::new("rhythm-road"))),
),
]);
let resolved = resolve_patch(&kernel.state, CommandId::new(), &patch, None).unwrap();
let resolved = resolve_patch(&kernel.state, CommandId::new(), &patch, None, None).unwrap();
let honest = super::super::WorldEffect::PatchAdmitted {
resolved: resolved.clone(),
answers: None,
@ -8788,6 +8802,7 @@ mod tests {
for edge_id in [topology.span, topology.shutter, topology.toll] {
let forged = super::super::WorldEffect::PatchAdmitted {
resolved: ResolvedPatch {
lens_weights: None,
subjects: Vec::new(),
entities: Vec::new(),
routes: Vec::new(),

View file

@ -19,8 +19,9 @@
use chrono::{Duration, Utc};
use ghostlight::{
CommandBody, CommandId, ControllerMode, ControllerPort, CreateWorldIntent, DecisionInvocation,
DecisionOpportunity, KernelError, MailboxError, PrincipalCommandIntent, Statement,
SubjectId, SubmitReceipt, VerifiedPrincipalEvidence, WorldMailbox, WorldPhase, WorldSnapshot,
DecisionOpportunity, KernelError, Lens, LensWeights, MailboxError, PrincipalCommandIntent,
Statement, SubjectId, SubmitReceipt, VerifiedPrincipalEvidence, WorldMailbox, WorldPhase,
WorldSnapshot,
};
use serde_json::{Value, json};
use std::collections::BTreeMap;
@ -33,6 +34,7 @@ struct World {
_directory: tempfile::TempDir,
_owner: tokio::task::JoinHandle<()>,
mailbox: WorldMailbox,
principal: VerifiedPrincipalEvidence,
}
impl World {
@ -52,6 +54,9 @@ impl World {
operational_agent_label: Some("Operational Agent".into()),
targets: BTreeMap::new(),
jurisdictions: Vec::new(),
// An external crate has no library fixture; it states its
// own weights, as every consumer must.
lens_weights: LensWeights::new(BTreeMap::from([(Lens::Patina, 1)])),
},
&principal,
)
@ -76,6 +81,7 @@ impl World {
_directory: directory,
_owner: owner,
mailbox,
principal,
};
assert_eq!(world.snapshot().await.phase, WorldPhase::Active);
world
@ -477,3 +483,91 @@ async fn a_stale_issued_scope_digest_is_refused_and_commits_nothing() {
assert_eq!(world.committed().await, before, "{what}");
}
}
/// Every stock lens named, with real weights: the shape a consumer's weight
/// control produces, so a refusal below is about the caller and not the value.
fn real_weights() -> LensWeights {
LensWeights::new(BTreeMap::from([
(Lens::Patina, 3),
(Lens::Charter, 1),
(Lens::Ledger, 0),
(Lens::Hearth, 2),
(Lens::Tangle, 1),
(Lens::Veil, 1),
(Lens::Ember, 1),
(Lens::Numen, 1),
]))
}
/// A second verified principal holds a live session and valid weights and is
/// not the owner. The kernel refuses it and nothing commits; the same body
/// from the owner then commits, so the refusal is not an inert harness.
#[tokio::test]
async fn set_lens_weights_from_a_non_owner_is_refused_and_commits_nothing() {
let world = World::active().await;
let snapshot = world.snapshot().await;
let stranger =
VerifiedPrincipalEvidence::new("external-admission-stranger", Utc::now() + Duration::hours(1));
let before = world.committed().await;
let result = world
.mailbox
.submit_principal(
PrincipalCommandIntent {
id: CommandId::new(),
world_id: snapshot.world_id,
expected_revision: snapshot.revision,
body: CommandBody::SetLensWeights {
weights: real_weights(),
},
},
&stranger,
)
.await;
assert!(
matches!(
result,
Err(MailboxError::Kernel(KernelError::Unauthorized))
),
"a non-owner replaced the lens weights: {result:?}"
);
assert_eq!(world.committed().await, before);
assert_ne!(world.snapshot().await.lens_weights, real_weights());
let receipt = world
.mailbox
.submit_principal(
PrincipalCommandIntent {
id: CommandId::new(),
world_id: snapshot.world_id,
expected_revision: snapshot.revision,
body: CommandBody::SetLensWeights {
weights: real_weights(),
},
},
&world.principal,
)
.await
.expect("the owner replaces the lens weights");
assert!(matches!(receipt, SubmitReceipt::Applied(_)));
let after = world.snapshot().await;
assert_eq!(after.revision, before.0 + 1);
assert_eq!(after.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.
#[test]
fn set_lens_weights_with_an_unknown_lens_does_not_decode() {
let refused = serde_json::from_value::<CommandBody>(json!({
"type": "set_lens_weights",
"weights": {"patina": 1, "veil ": 1},
}));
assert!(refused.is_err(), "an unknown lens decoded: {refused:?}");
let accepted = serde_json::from_value::<CommandBody>(json!({
"type": "set_lens_weights",
"weights": {"patina": 1, "veil": 1},
}))
.expect("known lens names decode");
assert!(matches!(accepted, CommandBody::SetLensWeights { .. }));
}