Merge ghostlight-verse/verse-genesis: the canonical verse world is created from Aetheria's catalog

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-10-11 03:41:21 +02:00
commit 4eff650755
7 changed files with 740 additions and 15 deletions

View file

@ -524,3 +524,66 @@ fn bin_and_ext_fields_aetheria_adds_at_higher_keys_are_ignored() {
read_aetheria_catalog(FIXTURE).unwrap()
);
}
/// A faction record re-encoded with `change` applied to its decoded cells, for
/// shapes `serde_json` cannot spell: a non-string map key, raw invalid bytes.
fn with_cells(key: &str, change: impl FnOnce(&mut Vec<rmpv::Value>)) -> Vec<CultCacheEnvelope> {
let mut rows = fixture_rows();
let row = row_of(&mut rows, key);
let rmpv::Value::Array(mut cells) =
rmpv::decode::read_value(&mut row.payload.as_slice()).unwrap()
else {
panic!("not positional");
};
change(&mut cells);
let mut payload = Vec::new();
rmpv::encode::write_value(&mut payload, &rmpv::Value::Array(cells)).unwrap();
row.payload = payload;
rows
}
#[test]
fn an_allegiance_key_that_is_not_a_string_is_malformed_by_key_and_field() {
for key in [
rmpv::Value::from(5),
rmpv::Value::Nil,
rmpv::Value::Binary(b"faction-adrasteia".to_vec()),
] {
let rows = with_cells("faction-brannoch", |cells| {
cells[12] = rmpv::Value::Map(vec![(key, rmpv::Value::F64(0.5))]);
});
assert_eq!(
read(&rows),
Err(CatalogReadError::MalformedRecord {
record: "faction-brannoch".into(),
field: 12
})
);
}
}
#[test]
fn text_that_is_not_utf8_is_malformed_by_key_and_field() {
// The marker is overwritten in the encoded bytes with an invalid sequence of
// the same length, since a decoded string cannot hold one.
const MARKER: &str = "ZZZZ";
for field in [1usize, 3] {
let mut rows = with_cells("faction-brannoch", |cells| {
cells[field] = rmpv::Value::from(MARKER);
});
let payload = &mut row_of(&mut rows, "faction-brannoch").payload;
let at = payload
.windows(MARKER.len())
.position(|window| window == MARKER.as_bytes())
.unwrap();
payload[at..at + MARKER.len()].copy_from_slice(&[0xff, 0xfe, 0xfd, 0xfc]);
assert_eq!(
read(&rows),
Err(CatalogReadError::MalformedRecord {
record: "faction-brannoch".into(),
field: field as u8
}),
"field {field}"
);
}
}

View file

@ -8,7 +8,7 @@
//! digest its verbs do not have.
use crate::SubjectKind;
use crate::patch::{AffordanceKindName, EntityKind, RefKind, RoleSpec, kernel_speak_entry};
use crate::patch::{AffordanceKindName, EntityKind, RefKind, Role, RoleSpec, kernel_speak_entry};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use std::collections::BTreeMap;
@ -63,10 +63,20 @@ impl GrammarVerb {
/// Whether a declared role list is this verb's signature: same names, same
/// referent kinds, same order.
pub(crate) fn admits_roles(&self, roles: &[RoleSpec]) -> bool {
self.roles.len() == roles.len()
&& self.roles.iter().zip(roles).all(|(want, have)| {
want.role == have.role.0 && want.referent.ref_kind() == have.kind
self.role_specs() == roles
}
/// This verb's signature as the kernel declares it: the one lowering of a
/// grammar role, so declaring a verb and admitting a declaration cannot
/// disagree.
pub(crate) fn role_specs(&self) -> Vec<RoleSpec> {
self.roles
.iter()
.map(|role| RoleSpec {
role: Role(role.role.clone()),
kind: role.referent.ref_kind(),
})
.collect()
}
}
@ -165,6 +175,11 @@ impl GrammarBinding {
}
}
/// Every verb, in kind order.
pub(crate) fn verbs(&self) -> impl Iterator<Item = (&str, &GrammarVerb)> {
self.verbs.iter().map(|(kind, verb)| (kind.as_str(), verb))
}
pub(crate) fn verb(&self, kind: &str) -> Option<&GrammarVerb> {
self.verbs.get(kind)
}

View file

@ -144,8 +144,8 @@ pub use table::{
};
pub use vault::{VaultError, VaultEvidenceSource};
pub use verse::{
CALL_RECORD_SCHEMA, CallRecordError, CallRecordStore, ProviderCallRecord, RecordingInferencePort,
ReplayInferencePort, RunField, RunId, RunStart,
CALL_RECORD_SCHEMA, CallRecordError, CallRecordStore, CreateVerseWorldIntent, ProviderCallRecord,
RecordingInferencePort, ReplayInferencePort, RunField, RunId, RunStart, VerseGenesisError,
};
use patch::{EdgeRecord, EntityRecord, LedgerDelta, ResolvedOp, ResolvedPatch};

View file

@ -3,9 +3,10 @@ use super::{
ConsumerId, CreateWorld, CreateWorldIntent, CreationReceipt, DecisionInvocation,
DecisionOpportunity, Declaration, DraftHandle, EntityDeclaration, EntityKind, JurisdictionKey,
KernelError, NewController, OperatorEvent, PatchAnswer, PrincipalCommandIntent, PrincipalId,
Ref, SubjectDeclaration, SubjectKind, SubmitReceipt, SystemCapability, TickMinutes, WorldId,
EntityId, Ref, SubjectDeclaration, SubjectKind, SubmitReceipt, SystemCapability, TickMinutes, WorldId,
WorldKernel, WorldPatch, WorldPhase, WorldScaleIntentRef, WorldSnapshot, journal,
patch::kernel_speak_grant, prepare_creation,
verse::{CreateVerseWorldIntent, VerseGenesisError},
};
use crate::VerifiedPrincipalEvidence;
use chrono::Utc;
@ -18,6 +19,21 @@ use tokio::task::JoinHandle;
/// other. A world that wants more declares them through the patch lane.
const GENESIS_PLACE: &str = "commons";
/// The commons every genesis declares, the one room its first subjects share.
pub(crate) fn commons_declaration() -> Declaration {
Declaration::Entity(EntityDeclaration {
handle: DraftHandle::new(GENESIS_PLACE),
label: "The Commons".into(),
kind: EntityKind::Place,
container: None,
})
}
/// Where a genesis subject stands.
pub(crate) fn commons_ref() -> Ref<EntityId> {
Ref::Draft(DraftHandle::new(GENESIS_PLACE))
}
const REQUEST_CAPACITY: usize = 32;
#[derive(Clone, Debug)]
@ -120,7 +136,7 @@ impl WorldMailbox {
kind,
controller,
affordances: kernel_speak_grant(),
position: Some(Ref::Draft(DraftHandle::new(GENESIS_PLACE))),
position: Some(commons_ref()),
import_key: None,
})
};
@ -130,12 +146,7 @@ impl WorldMailbox {
// jurisdiction's population target. The owner subject lands in
// `Uncovered`, visible and reducing nothing.
let mut declarations = vec![
Declaration::Entity(EntityDeclaration {
handle: DraftHandle::new(GENESIS_PLACE),
label: "The Commons".into(),
kind: EntityKind::Place,
container: None,
}),
commons_declaration(),
declare(
"first-person",
input.human_subject_label,
@ -219,6 +230,20 @@ impl WorldMailbox {
.map_err(mailbox_outcome)
}
/// Creates the canonical Aetheria verse world through the same
/// authenticated creation `create` uses.
pub async fn create_verse(
&self,
intent: CreateVerseWorldIntent,
principal: &VerifiedPrincipalEvidence,
) -> Result<CreationReceipt, VerseGenesisError> {
let principal_id = PrincipalId::new(principal.account_subject_hash());
let input = intent.lower(principal_id.clone())?;
Ok(self
.create_authenticated(input, AuthenticatedCaller::verified_principal(principal_id))
.await?)
}
/// A wall-clock read at the boundary a request crosses into the mailbox is
/// ingress, not reduce: the reducer stays deterministic over its journal,
/// and this is the one place a caller's own claim of continued authority

View file

@ -0,0 +1,143 @@
//! The canonical Aetheria verse world's genesis.
//!
//! One intent lowers to one `CreateWorld`: a commons, every grammar verb
//! declared as an affordance, and each Aetheria faction as an institution
//! imported by its record key. The world is bound to the catalog's grammar, so
//! the kernel refuses any affordance the grammar does not carry.
//!
//! Phase A verbs record the act and change no component: an outcome band with
//! no effects, and the act carried as the utterance that makes an entry
//! admissible. Ghostlight owns each verb's lowering to kernel effects (ruling
//! `verb-effects-ghostlight`), and those arrive with phase B.
use crate::aetheria::AetheriaCatalog;
use crate::mailbox::{MailboxError, commons_declaration, commons_ref};
use crate::patch::{
AffordanceDeclaration, AffordanceKindName, AudienceSpec, Declaration, DraftHandle, EvidenceRef,
FactDeclaration, FactStandingRef, OutcomeBand, Precondition, Ref, Statement, SubjectDeclaration,
kernel_speak_entry, kernel_speak_grant,
};
use crate::{
CommandId, CreateWorld, LensWeights, NewController, PrincipalId, SubjectKind,
WorldPatch, WorldScaleIntentRef,
};
use std::collections::BTreeMap;
const COMMIT_LEN: usize = 40;
/// What a caller states to create the canonical verse world. Everything else a
/// world needs (the commons, the subjects, the affordances, their grants and
/// the provenance of what is known) is derived from the catalog.
#[derive(Clone, Debug)]
pub struct CreateVerseWorldIntent {
pub id: CommandId,
pub title: String,
pub brief: String,
pub catalog: AetheriaCatalog,
/// The Aetheria commit the catalog was read at: 40 lower-case hex digits.
/// It is the provenance of every fact the world holds about a faction.
pub aetheria_commit: String,
pub lens_weights: LensWeights,
pub targets: BTreeMap<SubjectKind, u32>,
}
#[derive(Debug, thiserror::Error)]
pub enum VerseGenesisError {
#[error("aetheria_commit is not 40 lower-case hex digits")]
NotACommit,
#[error(transparent)]
Mailbox(#[from] MailboxError),
}
impl CreateVerseWorldIntent {
/// The one lowering. The kernel supplies the `speak` entry at genesis and
/// holds it to the grammar, so it is granted here and never declared.
pub(crate) fn lower(self, owner: PrincipalId) -> Result<CreateWorld, VerseGenesisError> {
let commit = &self.aetheria_commit;
if commit.len() != COMMIT_LEN
|| !commit
.bytes()
.all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))
{
return Err(VerseGenesisError::NotACommit);
}
let speak = kernel_speak_entry().kind;
let mut grants = kernel_speak_grant();
let mut declarations = vec![commons_declaration()];
for (kind, verb) in self.catalog.grammar.verbs() {
if kind == speak.0 {
continue;
}
let handle = DraftHandle::new(format!("verb-{kind}"));
grants.insert(Ref::Draft(handle.clone()));
declarations.push(Declaration::Affordance(AffordanceDeclaration {
handle,
kind: AffordanceKindName(kind.to_owned()),
roles: verb.role_specs(),
preconditions: vec![Precondition::CanBroadcast {
via: AudienceSpec::Colocated,
}],
effect_slots: Vec::new(),
outcome_bands: vec![OutcomeBand {
weight: 1,
effects: Vec::new(),
}],
// The kernel refuses an entry that can neither change a
// component nor speak, and a speaking entry needs an audience,
// so a phase A verb is its own utterance to the room: the act
// is recorded as speech and nothing else changes.
carries_speech: true,
}));
}
let mut evidence = Vec::new();
for (position, faction) in self.catalog.factions.iter().enumerate() {
declarations.push(Declaration::Subject(SubjectDeclaration {
handle: DraftHandle::new(format!("faction-{position}")),
label: faction.name.trim().to_owned(),
kind: SubjectKind::Institution,
controller: NewController::OperationalAgent,
affordances: grants.clone(),
position: Some(commons_ref()),
import_key: Some(faction.key.clone()),
}));
// A faction with no description has nothing to state.
if let Some(statement) = Statement::new(faction.description.trim()) {
let receipt = EvidenceRef::new(format!(
"aetheria:{commit}:{}/{}",
faction.key.schema(),
faction.key.record()
));
evidence.push(receipt.clone());
declarations.push(Declaration::Fact(FactDeclaration {
handle: DraftHandle::new(format!("fact-{position}")),
label: faction.name.trim().to_owned(),
statement,
standing: FactStandingRef::Canonical { evidence: receipt },
}));
}
}
Ok(CreateWorld {
id: self.id,
owner,
title: self.title,
brief: self.brief,
patch: WorldPatch {
declarations,
operations: Vec::new(),
evidence,
},
scale_intent: WorldScaleIntentRef {
targets: self.targets,
jurisdictions: BTreeMap::new(),
},
lens_weights: self.lens_weights,
grammar: Some(self.catalog.grammar),
})
}
}
#[cfg(test)]
mod tests;

View file

@ -0,0 +1,476 @@
use super::*;
use crate::aetheria::read_aetheria_catalog;
use crate::patch::{FactStanding, Mismatch};
use crate::tests::{auth_principal, owner, stock_weights};
use crate::{
CommandBody, ControllerMode, GrammarBinding, GrammarReferentKind, GrammarRole, GrammarVerb,
KernelError, PrincipalCommandIntent, RenderPath, VerifiedPrincipalEvidence, WorldKernel,
WorldMailbox, WorldPhase,
};
use chrono::{Duration, Utc};
use std::collections::BTreeSet;
/// Aetheria master when the fixture catalog was written.
const COMMIT: &str = "92b82e9aa5bb422c568c4bfc85a8f641426a41fc";
const OTHER_COMMIT: &str = "0123456789abcdef0123456789abcdef01234567";
const FIXTURE: &str = concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/fixtures/aetheria-verse-catalog.cc"
);
fn catalog() -> AetheriaCatalog {
read_aetheria_catalog(FIXTURE).unwrap()
}
fn intent_of(catalog: AetheriaCatalog, commit: &str) -> CreateVerseWorldIntent {
CreateVerseWorldIntent {
id: CommandId::new(),
title: "The Verse".into(),
brief: "Aetheria.".into(),
catalog,
aetheria_commit: commit.into(),
lens_weights: stock_weights(),
targets: BTreeMap::new(),
}
}
/// A world created through the kernel from the lowering, for what the
/// snapshot hides: the facts' evidence.
fn kernel_of(intent: CreateVerseWorldIntent) -> (tempfile::TempDir, Result<WorldKernel, KernelError>) {
let dir = tempfile::tempdir().unwrap();
let world = intent.lower(owner()).unwrap();
let result = WorldKernel::create(dir.path().join("world.cc"), world, &auth_principal(owner()))
.map(|(kernel, _)| kernel);
(dir, result)
}
struct Verse {
_dir: tempfile::TempDir,
_task: tokio::task::JoinHandle<()>,
mailbox: WorldMailbox,
principal: VerifiedPrincipalEvidence,
receipt: crate::CreationReceipt,
}
async fn verse(intent: CreateVerseWorldIntent) -> Verse {
let dir = tempfile::tempdir().unwrap();
let (mailbox, task) = WorldMailbox::open(dir.path().join("world.cc")).unwrap();
let principal =
VerifiedPrincipalEvidence::fixture("verse-owner", Utc::now() + Duration::hours(1));
let receipt = mailbox.create_verse(intent, &principal).await.unwrap();
Verse {
_dir: dir,
_task: task,
mailbox,
principal,
receipt,
}
}
#[tokio::test]
async fn verse_genesis_from_the_aetheria_fixture() {
let catalog = catalog();
let verse = verse(intent_of(catalog.clone(), COMMIT)).await;
let snapshot = verse.mailbox.snapshot().await.unwrap();
// One institution per faction, keyed by its record, named by its Name.
let mut factions: Vec<_> = snapshot
.subjects
.iter()
.map(|subject| {
(
subject.import_key.clone().unwrap(),
subject.label.clone(),
subject.kind,
subject.controller_mode,
)
})
.collect();
factions.sort_by(|a, b| a.0.cmp(&b.0));
let expected: Vec<_> = catalog
.factions
.iter()
.map(|faction| {
(
faction.key.clone(),
faction.name.clone(),
SubjectKind::Institution,
Some(ControllerMode::OperationalAgent),
)
})
.collect();
assert_eq!(factions.len(), 2);
assert_eq!(factions, expected);
// One fact per faction, carrying its description.
let statements: BTreeSet<_> = snapshot
.facts
.iter()
.map(|fact| fact.statement.as_str().to_owned())
.collect();
assert_eq!(
statements,
catalog
.factions
.iter()
.map(|faction| faction.description.clone())
.collect::<BTreeSet<_>>()
);
// The world is bound to the catalog's grammar.
assert_eq!(snapshot.grammar, Some(catalog.grammar.identity()));
assert_eq!(verse.receipt.world_id, snapshot.world_id);
// Every faction stands in the one commons.
let commons: Vec<_> = snapshot
.places
.iter()
.filter(|place| place.label == "The Commons")
.collect();
assert_eq!(commons.len(), 1);
assert!(
snapshot
.subjects
.iter()
.all(|subject| subject.position == Some(commons[0].id))
);
}
#[test]
fn each_fact_cites_its_faction_record_and_the_commit_it_was_read_at() {
for commit in [COMMIT, OTHER_COMMIT] {
let (_dir, kernel) = kernel_of(intent_of(catalog(), commit));
let kernel = kernel.unwrap();
let evidence: BTreeSet<String> = kernel
.state
.facts
.values()
.map(|fact| match &fact.standing {
FactStanding::Canonical { evidence } => evidence.text().to_owned(),
other => panic!("a verse fact is canonical, not {other:?}"),
})
.collect();
assert_eq!(
evidence,
catalog()
.factions
.iter()
.map(|faction| format!(
"aetheria:{commit}:aetheria.faction/{}",
faction.key.record()
))
.collect::<BTreeSet<_>>()
);
}
}
#[test]
fn a_commit_is_forty_lower_case_hex_digits() {
for bad in [
"",
&COMMIT[..39],
&format!("{COMMIT}0"),
&COMMIT.to_uppercase(),
&format!("{}g", &COMMIT[..39]),
] {
assert!(
matches!(
intent_of(catalog(), bad).lower(owner()),
Err(VerseGenesisError::NotACommit)
),
"{bad:?}"
);
}
assert!(intent_of(catalog(), COMMIT).lower(owner()).is_ok());
assert!(!VerseGenesisError::NotACommit.to_string().contains(COMMIT));
}
#[test]
fn verse_genesis_refuses_a_duplicate_faction_record() {
let mut twice = catalog();
let first = twice.factions[0].clone();
twice.factions.push(first);
// The lowering does not decide: it declares all three.
let world = intent_of(twice.clone(), COMMIT).lower(owner()).unwrap();
let subjects = world
.patch
.declarations
.iter()
.filter(|declaration| matches!(declaration, Declaration::Subject(_)))
.count();
assert_eq!(subjects, 3);
let (_dir, kernel) = kernel_of(intent_of(twice, COMMIT));
let Err(KernelError::PatchRejected(set)) = kernel else {
panic!("a second faction under one record key was admitted");
};
assert!(
set.iter()
.any(|mismatch| matches!(mismatch, Mismatch::DuplicateImportKey { .. })),
"{set:?}"
);
}
#[tokio::test]
async fn every_granted_affordance_is_a_grammar_verb() {
let catalog = catalog();
let verse = verse(intent_of(catalog.clone(), COMMIT)).await;
let snapshot = verse.mailbox.snapshot().await.unwrap();
let mut kinds: Vec<_> = snapshot
.affordances
.iter()
.map(|affordance| affordance.entry.kind.0.clone())
.collect();
kinds.sort();
// Each verb once: the kernel's speak is the grammar's speak.
assert_eq!(
kinds,
catalog
.grammar
.verbs()
.map(|(kind, _)| kind.to_owned())
.collect::<Vec<_>>()
);
assert!(kinds.contains(&"speak".to_owned()));
// Each has its render path from the grammar, and phase A changes nothing.
for affordance in &snapshot.affordances {
let verb = catalog.grammar.verb(&affordance.entry.kind.0).unwrap();
assert_eq!(affordance.render_path, Some(verb.render_path));
assert!(affordance.entry.effect_slots.is_empty());
assert_eq!(affordance.entry.outcome_bands.len(), 1);
assert!(affordance.entry.outcome_bands[0].effects.is_empty());
}
// Every faction is granted every verb.
let all: BTreeSet<_> = snapshot.affordances.iter().map(|a| a.id).collect();
for subject in &snapshot.subjects {
assert_eq!(subject.affordances, all);
}
}
#[tokio::test]
async fn factions_get_opportunities() {
let verse = verse(intent_of(catalog(), COMMIT)).await;
for body in [CommandBody::ApproveDraft, CommandBody::ActivateWorld] {
let revision = verse.mailbox.snapshot().await.unwrap().revision;
verse
.mailbox
.submit_principal(
PrincipalCommandIntent {
id: CommandId::new(),
world_id: verse.receipt.world_id,
expected_revision: revision,
body,
},
&verse.principal,
)
.await
.unwrap();
}
let snapshot = verse.mailbox.snapshot().await.unwrap();
assert_eq!(snapshot.phase, WorldPhase::Active);
assert_eq!(snapshot.opportunities.len(), catalog().factions.len());
let scopes: BTreeSet<_> = snapshot.opportunities.iter().map(|o| o.scope).collect();
assert_eq!(scopes.len(), snapshot.opportunities.len());
for opportunity in &snapshot.opportunities {
assert_eq!(opportunity.controller_mode, ControllerMode::OperationalAgent);
assert_eq!(opportunity.affordance_ids.len(), snapshot.affordances.len());
}
}
#[test]
fn a_faction_without_a_description_holds_no_fact() {
let mut bare = catalog();
bare.factions[0].description = " ".into();
bare.factions[1].description = " Second. ".into();
let (_dir, kernel) = kernel_of(intent_of(bare, COMMIT));
let facts: Vec<_> = kernel
.unwrap()
.state
.facts
.values()
.map(|fact| fact.statement.as_str().to_owned())
.collect();
assert_eq!(facts, vec!["Second.".to_owned()]);
}
#[test]
fn a_grammar_the_kernel_speak_cannot_live_in_refuses_genesis() {
let mut mute = catalog();
mute.grammar = GrammarBinding::new(
"aetheria.verse_grammar",
1,
[(
"haul".to_owned(),
GrammarVerb {
render_path: RenderPath::ShipAction,
roles: vec![GrammarRole {
role: "cargo".into(),
referent: GrammarReferentKind::Cargo,
}],
},
)],
)
.unwrap();
let (_dir, kernel) = kernel_of(intent_of(mute, COMMIT));
let Err(KernelError::PatchRejected(set)) = kernel else {
panic!("a world whose grammar has no speak was admitted");
};
assert!(
set.iter()
.any(|mismatch| matches!(mismatch, Mismatch::AffordanceKindNotInGrammar { .. })),
"{set:?}"
);
}
#[tokio::test]
async fn the_dungeon_ingress_still_creates_an_unbound_world() {
let dir = tempfile::tempdir().unwrap();
let (mailbox, _task) = WorldMailbox::open(dir.path().join("world.cc")).unwrap();
let principal =
VerifiedPrincipalEvidence::fixture("dungeon-owner", Utc::now() + Duration::hours(1));
mailbox
.create(
crate::CreateWorldIntent {
id: CommandId::new(),
title: "Dungeon".into(),
brief: String::new(),
human_subject_label: "Player".into(),
narrative_persona_label: None,
operational_agent_label: None,
targets: BTreeMap::new(),
jurisdictions: Vec::new(),
lens_weights: stock_weights(),
},
&principal,
)
.await
.unwrap();
let snapshot = mailbox.snapshot().await.unwrap();
assert_eq!(snapshot.grammar, None);
assert!(
snapshot
.places
.iter()
.any(|place| place.label == "The Commons")
);
}
#[tokio::test]
async fn a_faction_name_is_trimmed_into_its_institution_label() {
let mut padded = catalog();
let expected = padded.factions[0].name.trim().to_owned();
padded.factions[0].name = format!(" {expected}\t ");
let key = padded.factions[0].key.clone();
let verse = verse(intent_of(padded, COMMIT)).await;
let snapshot = verse.mailbox.snapshot().await.unwrap();
let subject = snapshot
.subjects
.iter()
.find(|subject| subject.import_key.as_ref() == Some(&key))
.expect("the padded faction was created");
assert_eq!(subject.label, expected);
}
#[tokio::test]
async fn genesis_twice_with_one_intent_is_one_creation() {
let dir = tempfile::tempdir().unwrap();
let (mailbox, _task) = WorldMailbox::open(dir.path().join("world.cc")).unwrap();
let principal =
VerifiedPrincipalEvidence::fixture("verse-owner", Utc::now() + Duration::hours(1));
let intent = intent_of(catalog(), COMMIT);
let first = mailbox.create_verse(intent.clone(), &principal).await.unwrap();
let before = mailbox.snapshot().await.unwrap();
let again = mailbox.create_verse(intent, &principal).await.unwrap();
let after = mailbox.snapshot().await.unwrap();
assert_eq!(again, first);
assert_eq!(after.revision, before.revision);
assert_eq!(after.state_digest, before.state_digest);
assert_eq!(after.subjects.len(), before.subjects.len());
}
#[tokio::test]
async fn genesis_under_another_command_id_finds_the_world_occupied() {
let verse = verse(intent_of(catalog(), COMMIT)).await;
let before = verse.mailbox.snapshot().await.unwrap();
let refused = verse
.mailbox
.create_verse(intent_of(catalog(), COMMIT), &verse.principal)
.await;
assert!(
matches!(
refused,
Err(VerseGenesisError::Mailbox(MailboxError::Kernel(
KernelError::CreationTargetOccupied
)))
),
"{refused:?}"
);
let after = verse.mailbox.snapshot().await.unwrap();
assert_eq!(after.revision, before.revision);
assert_eq!(after.state_digest, before.state_digest);
}
/// Evidence for the open question `ship-acts-are-not-speech`, pinning what the
/// phase A lowering does today: a ship verb is only admissible as an utterance,
/// and committing it asserts a claim fact the room hears.
#[test]
fn phase_a_ship_verbs_are_spoken_acts_in_the_room() {
use crate::tests::{command, opportunity_for, spoken, submit_owner};
use crate::{
AuthenticatedCaller, CallerId, DecisionInvocation, Role, RoleBinding, SubmitReceipt,
Target,
};
let (_scratch, kernel) = kernel_of(intent_of(catalog(), COMMIT));
let mut kernel = kernel.unwrap();
for body in [CommandBody::ApproveDraft, CommandBody::ActivateWorld] {
let snapshot = kernel.snapshot().unwrap();
submit_owner(&mut kernel, &snapshot, body);
}
let active = kernel.snapshot().unwrap();
let attack = active
.affordances
.iter()
.find(|a| a.entry.kind.0 == "attack")
.unwrap()
.id;
let [first, second] = [&active.subjects[0], &active.subjects[1]];
let opportunity = opportunity_for(&active, first.id);
let caller = CallerId::Controller(opportunity.controller_id);
let invocation = |speech: Option<&str>| DecisionInvocation {
affordance: attack,
bindings: vec![RoleBinding {
role: Role("target".into()),
target: Target::Subject(second.id),
}],
proposed: Vec::new(),
speech: speech.map(|text| crate::patch::Statement::new(text).unwrap()),
display: None,
};
let try_it = |kernel: &mut WorldKernel, speech: Option<&str>| {
kernel.submit(
command(
&active,
CommandId::new(),
caller.clone(),
CommandBody::ExerciseDecision {
opportunity: opportunity.clone(),
invocation: invocation(speech),
},
),
&AuthenticatedCaller::fixture(caller.clone()),
)
};
// Without an utterance the act is refused: the verb has no other way in.
assert!(try_it(&mut kernel, None).is_err());
// With one, the act commits and its words are a claim fact.
let receipt = try_it(&mut kernel, Some("We strike.")).expect("the attack commits");
assert!(matches!(receipt, SubmitReceipt::Applied(_)));
let event = kernel.state.events.last().unwrap();
assert_eq!(spoken(&kernel.state, event), Some("We strike."));
}

View file

@ -1,9 +1,12 @@
//! The Aetheria verse run's own organs. The verse binary and its run record
//! live above this module; here sit the parts of a run that belong to the
//! library: the record of every provider call and the spend it implies.
//! library: the record of every provider call and the spend it implies, and the
//! genesis of the canonical verse world.
mod calls;
mod genesis;
pub use genesis::{CreateVerseWorldIntent, VerseGenesisError};
pub use calls::{
CALL_RECORD_SCHEMA, CallRecordError, CallRecordStore, ProviderCallRecord,
RecordingInferencePort, ReplayInferencePort, RunField, RunId, RunStart,