diff --git a/Cargo.lock b/Cargo.lock index 3281539..aa377dd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -657,6 +657,28 @@ dependencies = [ "polyval", ] +[[package]] +name = "ghostlight" +version = "0.1.0" +dependencies = [ + "async-trait", + "chrono", + "codex-connector", + "cultcache-rs", + "ghostlight-persona-projection", + "rmp-serde", + "serde", + "serde_bytes", + "serde_json", + "sha2", + "tempfile", + "thiserror", + "tokio", + "tracing", + "uuid", + "zeroize", +] + [[package]] name = "ghostlight-dungeon" version = "0.1.0" @@ -672,7 +694,7 @@ dependencies = [ "cultcache-rs", "cultmesh-rs", "cultnet-rs", - "ghostlight-persona-projection", + "ghostlight", "hmac", "jsonwebtoken", "libc", diff --git a/Cargo.toml b/Cargo.toml index cdaf955..76d2879 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,6 @@ [workspace] members = [ + "crates/ghostlight", "crates/ghostlight-dungeon", "crates/ghostlight-persona-projection", ] diff --git a/README.md b/README.md index 5eeedf6..b5a4fc2 100644 --- a/README.md +++ b/README.md @@ -162,8 +162,12 @@ The shortest reliable re-entry path is: ## Repository Shape -- `crates/ghostlight-dungeon/`: Rust daemon, kernels, compiler, persistence, - provider stages, Eve projection/command ingress, and acceptance harnesses; +- `crates/ghostlight/`: the library crate — world kernel, ontology and reducer, + journal, mailbox, controllers, elaboration, cover, clock, consumer admission, + inference ports, and evidence sources; +- `crates/ghostlight-dungeon/`: Rust daemon consuming that library — HTTP + routes, Eve projection/command ingress, Heimdall and app sessions, CultMesh + publication, Idunn health, and acceptance harnesses; - `crates/ghostlight-persona-projection/`: generalized projection membrane owned by Ghostlight and consumed by Epiphany; - `docs/architecture/`: durable contracts and authority maps; diff --git a/crates/ghostlight-dungeon/Cargo.toml b/crates/ghostlight-dungeon/Cargo.toml index 67a1047..94b772a 100644 --- a/crates/ghostlight-dungeon/Cargo.toml +++ b/crates/ghostlight-dungeon/Cargo.toml @@ -24,7 +24,7 @@ cultmesh-rs.workspace = true jsonwebtoken.workspace = true libc = "0.2" hmac.workspace = true -ghostlight-persona-projection = { path = "../ghostlight-persona-projection" } +ghostlight = { path = "../ghostlight" } rand.workspace = true reqwest.workspace = true rmp-serde.workspace = true diff --git a/crates/ghostlight-dungeon/src/app_session.rs b/crates/ghostlight-dungeon/src/app_session.rs index 43912a4..0a3a77f 100644 --- a/crates/ghostlight-dungeon/src/app_session.rs +++ b/crates/ghostlight-dungeon/src/app_session.rs @@ -4,6 +4,7 @@ //! selection, commands, idempotency, exports, or fictional state. use crate::heimdall::{VerifiedSessionAdmission, VerifiedSessionRefresh}; +use ghostlight::VerifiedPrincipalEvidence; use aes_gcm::{Aes256Gcm, KeyInit, aead::Aead}; use anyhow::{Context, bail}; use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD}; @@ -51,45 +52,6 @@ pub(crate) struct RefreshCandidate { pub(crate) refresh_claim: String, } -/// Opaque proof that the session owner verified a live Heimdall principal. -/// -/// The account hash is intentionally readable by world ingress, but this type -/// can only be constructed while `AppSessionOwner` holds valid custody. -/// -/// `valid_until` is minted from the session's own `access_expires_at`, not a -/// fresh clock read at construction: the evidence's authority cannot outlive -/// the session that vouched for it, and a `SeedPort` that holds this value for -/// a whole multi-round session is bounded by this expiry, not only by the -/// request's own lifetime. -#[derive(Clone, Debug, PartialEq, Eq)] -pub(crate) struct VerifiedPrincipalEvidence { - account_subject_hash: String, - valid_until: DateTime, -} - -impl VerifiedPrincipalEvidence { - pub(crate) fn account_subject_hash(&self) -> &str { - &self.account_subject_hash - } - - pub(crate) fn valid_until(&self) -> DateTime { - self.valid_until - } - - /// Test-only. Production keeps exactly one minter — a live cookie resolved - /// by `account_for_cookie` — and this constructor is compiled out of it. - #[cfg(test)] - pub(crate) fn fixture( - account_subject_hash: impl Into, - valid_until: DateTime, - ) -> Self { - Self { - account_subject_hash: account_subject_hash.into(), - valid_until, - } - } -} - pub(crate) struct AppSessionOwner { store: OwnedRedbMessagePackBackingStore, row: CultCacheEnvelope, @@ -229,9 +191,11 @@ impl AppSessionOwner { .verified_capabilities .iter() .any(|value| value == "app_access")) - .then(|| VerifiedPrincipalEvidence { - account_subject_hash: session.account_subject_hash.clone(), - valid_until: session.access_expires_at, + .then(|| { + VerifiedPrincipalEvidence::new( + session.account_subject_hash.clone(), + session.access_expires_at, + ) })) } @@ -556,6 +520,40 @@ fn unwrap_refresh( mod tests { use super::*; + /// The library owns `VerifiedPrincipalEvidence` and gives it a public + /// constructor, so the single-minter rule its private fields used to + /// enforce is this count: production mints one, in `account_for_cookie`, + /// from a live cookie this owner holds custody of. A second minter + /// anywhere in Dungeon's production source is a second authority over who + /// the kernel believes is speaking. + #[test] + fn soul_exactly_one_production_site_mints_verified_principal_evidence() { + let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("src"); + let mut total = 0; + for entry in std::fs::read_dir(&root).expect("the source tree reads") { + let path = entry.expect("a readable entry").path(); + if path.extension().and_then(|value| value.to_str()) != Some("rs") { + continue; + } + let text = std::fs::read_to_string(&path) + .expect("the source reads") + .replace("\r\n", "\n"); + // Only the production half; a test may mint what production must + // not. + let production = text + .split_once("\n#[cfg(test)]\nmod tests {") + .map(|(before, _)| before.to_owned()) + .unwrap_or(text); + total += production + .matches("VerifiedPrincipalEvidence::new(") + .count(); + } + assert_eq!( + total, 1, + "production mints VerifiedPrincipalEvidence in exactly one place" + ); + } + fn session(claim: &str) -> VerifiedSessionAdmission { VerifiedSessionAdmission::fixture( "account-1", diff --git a/crates/ghostlight-dungeon/src/eve.rs b/crates/ghostlight-dungeon/src/eve.rs index 7f6ddd4..fa24a66 100644 --- a/crates/ghostlight-dungeon/src/eve.rs +++ b/crates/ghostlight-dungeon/src/eve.rs @@ -1,9 +1,7 @@ //! Eve/CultUI projection for the one live world owner. -use crate::{ - mesh::{COMMAND_BOUNDARY, COMMAND_RESULT_SCHEMA, PROVIDER_ID, SURFACE_ID}, - world::{ControllerMode, JurisdictionKey, OperatorEvent, WorldPhase, WorldSnapshot}, -}; +use crate::mesh::{COMMAND_BOUNDARY, COMMAND_RESULT_SCHEMA, PROVIDER_ID, SURFACE_ID}; +use ghostlight::{ControllerMode, JurisdictionKey, OperatorEvent, WorldPhase, WorldSnapshot}; use anyhow::{Context, bail}; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; @@ -179,7 +177,7 @@ fn cover_card(panel: &CoverPanel) -> Value { /// `AppState`; the last outcome is already in the invocation's own /// `command_result` receipt, which is where a command's result belongs. fn seed_card(world: &WorldSnapshot) -> Value { - let next = crate::world::select_row(world).map_or_else( + let next = ghostlight::select_row(world).map_or_else( || "Nothing short.".to_owned(), |row| { format!( @@ -332,10 +330,10 @@ pub(crate) fn authenticated_surface( WorldPhase::Draft => { if world .required_approvers - .contains(&crate::world::PrincipalId::new(account)) + .contains(&ghostlight::PrincipalId::new(account)) && !world .draft_approvals - .contains(&crate::world::PrincipalId::new(account)) + .contains(&ghostlight::PrincipalId::new(account)) { children.push(command_button( "world.approve", @@ -351,7 +349,7 @@ pub(crate) fn authenticated_surface( "WorldMailbox", )); } - if world.owner == crate::world::PrincipalId::new(account) { + if world.owner == ghostlight::PrincipalId::new(account) { children.extend([ json!({ "id":"world.seed.vault_scope", @@ -383,7 +381,7 @@ pub(crate) fn authenticated_surface( "SeedPort", )); } - if world.owner == crate::world::PrincipalId::new(account) + if world.owner == ghostlight::PrincipalId::new(account) && world.draft_approvals == world.required_approvers { children.push(command_button( @@ -402,7 +400,7 @@ pub(crate) fn authenticated_surface( } } WorldPhase::Active => { - if world.owner == crate::world::PrincipalId::new(account) { + if world.owner == ghostlight::PrincipalId::new(account) { children.extend([ json!({ "id":"world.advance_time.minutes", @@ -446,7 +444,7 @@ pub(crate) fn authenticated_surface( "children":story })); - let principal = crate::world::PrincipalId::new(account); + let principal = ghostlight::PrincipalId::new(account); let opportunity = world.opportunities.iter().find_map(|opportunity| { world.subjects.iter().find_map(|subject| { // The catalog entry is found by kind name among the @@ -491,7 +489,7 @@ pub(crate) fn authenticated_surface( )); } - if world.owner == crate::world::PrincipalId::new(account) { + if world.owner == ghostlight::PrincipalId::new(account) { children.push(cover_card(cover)); let mut has_controller_command = false; for (index, opportunity) in world diff --git a/crates/ghostlight-dungeon/src/idunn_health.rs b/crates/ghostlight-dungeon/src/idunn_health.rs index c097bfa..f16189d 100644 --- a/crates/ghostlight-dungeon/src/idunn_health.rs +++ b/crates/ghostlight-dungeon/src/idunn_health.rs @@ -5,7 +5,7 @@ //! one process write lease. Ghostlight reports what this process actually is; //! it never promotes its route or manufactures Ready. -use crate::world::{STATE_SCHEMA, state_schema_compatibility_tag}; +use ghostlight::{STATE_SCHEMA, state_schema_compatibility_tag}; use anyhow::{Context, Result, anyhow, bail, ensure}; #[cfg(test)] use cultcache_rs::CacheBackingStore; diff --git a/crates/ghostlight-dungeon/src/main.rs b/crates/ghostlight-dungeon/src/main.rs index e6aee85..51030f4 100644 --- a/crates/ghostlight-dungeon/src/main.rs +++ b/crates/ghostlight-dungeon/src/main.rs @@ -4,7 +4,6 @@ mod heimdall; mod idunn_health; mod mesh; mod runtime; -mod world; use anyhow::bail; use std::path::PathBuf; diff --git a/crates/ghostlight-dungeon/src/mesh.rs b/crates/ghostlight-dungeon/src/mesh.rs index 977117a..5c8a16f 100644 --- a/crates/ghostlight-dungeon/src/mesh.rs +++ b/crates/ghostlight-dungeon/src/mesh.rs @@ -3,10 +3,8 @@ //! The mesh publishes the world owner's current surface. It never pulls or //! repairs world state and it has no command authority. -use crate::{ - eve, - world::{CONSUMER_PATCH_SCHEMA, CONSUMER_RECEIPT_SCHEMA, STATE_SCHEMA, WorldSnapshot}, -}; +use crate::eve; +use ghostlight::{CONSUMER_PATCH_SCHEMA, CONSUMER_RECEIPT_SCHEMA, STATE_SCHEMA, WorldSnapshot}; use anyhow::Result; use chrono::{DateTime, Utc}; use cultcache_rs::DatabaseEntry; diff --git a/crates/ghostlight-dungeon/src/runtime.rs b/crates/ghostlight-dungeon/src/runtime.rs index 959a9c7..1b6aaaf 100644 --- a/crates/ghostlight-dungeon/src/runtime.rs +++ b/crates/ghostlight-dungeon/src/runtime.rs @@ -1,7 +1,7 @@ //! One-process runtime for the sealed world owner. use crate::{ - app_session::{AppSessionOwner, VerifiedPrincipalEvidence}, + app_session::AppSessionOwner, eve::{self, EveCommandInvocation}, heimdall::{self, HeimdallClient}, idunn_health::{ @@ -9,16 +9,16 @@ use crate::{ TARGET as GHOSTLIGHT_TARGET, }, mesh::{self, MeshPublisher, MeshRuntimeIdentity}, - world::{ - AffordanceId, CONSUMER_BODY_LIMIT, Cell, CellRun, CommandBody, CommandId, ConnectorBinding, - ConsumerPort, ConsumerRegistry, ControllerError, ControllerModels, ControllerPendingReason, - ControllerRunner, ControllerWorkCustody, Cover, CoverBudget, CreateJurisdictionIntent, - CreateWorldIntent, DEFAULT_SDK_MODEL_PREFIX, DecisionInvocation, DecisionOpportunity, - KernelError, MailboxError, NarrativeRun, OperationalRun, PrincipalCommandIntent, - PrincipalId, SdkBinding, SeedOutcome, SeedPort, Statement, SubjectId, SubjectKind, - SubmissionDisposition, SubmitReceipt, TickMinutes, VaultEvidenceSource, WorldMailbox, - WorldPhase, WorldSnapshot, derive_cover, open_controller_work, open_inference, - }, +}; +use ghostlight::{ + AffordanceId, CONSUMER_BODY_LIMIT, Cell, CellRun, CommandBody, CommandId, ConnectorBinding, + ConsumerPort, ConsumerRegistry, ControllerError, ControllerModels, ControllerPendingReason, + ControllerRunner, ControllerWorkCustody, Cover, CoverBudget, CreateJurisdictionIntent, + CreateWorldIntent, DEFAULT_SDK_MODEL_PREFIX, DecisionInvocation, DecisionOpportunity, + KernelError, 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, }; use anyhow::{Context, bail, ensure}; use axum::{ @@ -501,7 +501,7 @@ fn api_router(state: AppState) -> Router { /// The consumer ingress's door. Loopback and content type are the two gates /// `/cultnet/snapshot` already established; everything past them belongs to -/// `world::consumer`, which owns decode, bounds, authentication, and the one +/// `ghostlight`'s `consumer`, which owns decode, bounds, authentication, and the one /// receipt. This handler holds no opinion about a patch. async fn cultnet_world_patch( ConnectInfo(peer): ConnectInfo, @@ -515,8 +515,8 @@ async fn cultnet_world_patch( if headers.get(header::CONTENT_TYPE) != Some(&HeaderValue::from_static("application/msgpack")) { return StatusCode::UNSUPPORTED_MEDIA_TYPE.into_response(); } - let receipt = crate::world::admit_document(&state.consumer, &state.consumers, &body).await; - match crate::world::encode_receipt(&receipt) { + let receipt = ghostlight::admit_document(&state.consumer, &state.consumers, &body).await; + match ghostlight::encode_receipt(&receipt) { Ok(bytes) => ( StatusCode::OK, [(header::CONTENT_TYPE, "application/msgpack")], @@ -532,7 +532,7 @@ async fn cultnet_world_patch( /// startup outright, so a mistyped credential cannot silently read as "no /// consumers configured". fn open_consumer_registry() -> anyhow::Result { - let Ok(path) = std::env::var(crate::world::CONSUMER_CREDENTIALS_ENVIRONMENT) else { + let Ok(path) = std::env::var(ghostlight::CONSUMER_CREDENTIALS_ENVIRONMENT) else { return Ok(ConsumerRegistry::empty()); }; ConsumerRegistry::from_secret_file(&path) @@ -1081,15 +1081,15 @@ async fn dispatch_controller( // There is no second concurrency owner. let permit = state.controller_permits.clone().acquire_owned().await; let result = match opportunity.controller_mode { - crate::world::ControllerMode::NarrativePersona => controller + ghostlight::ControllerMode::NarrativePersona => controller .run_narrative(command_id, &opportunity) .await .map(controller_narrative_result), - crate::world::ControllerMode::OperationalAgent => controller + ghostlight::ControllerMode::OperationalAgent => controller .run_operational(command_id, &opportunity) .await .map(controller_operational_result), - crate::world::ControllerMode::Human => unreachable!("human opportunity was not admitted"), + ghostlight::ControllerMode::Human => unreachable!("human opportunity was not admitted"), }; drop(permit); let quarantine = match &result { @@ -1198,7 +1198,7 @@ async fn admit_controller_command( )?; let payload: ControllerActPayload = serde_json::from_value(invocation.payload.clone()) .map_err(|error| RuntimeCommandError::Payload(error.to_string()))?; - if payload.opportunity.controller_mode == crate::world::ControllerMode::Human { + if payload.opportunity.controller_mode == ghostlight::ControllerMode::Human { return Err(RuntimeCommandError::Payload( "human decisions cannot enter through the controller runner".into(), )); @@ -1322,7 +1322,7 @@ fn controller_operational_result(run: OperationalRun) -> ControllerHttpResult { } fn controller_pending_result( - mode: crate::world::ControllerMode, + mode: ghostlight::ControllerMode, reason: ControllerPendingReason, persona_prose: Option<&str>, ) -> ControllerHttpResult { @@ -1618,7 +1618,7 @@ async fn current_world(state: &AppState) -> anyhow::Result /// a snapshot field, so no controller lane can reach it. async fn current_operator_view( state: &AppState, -) -> anyhow::Result<(Option, Vec)> { +) -> anyhow::Result<(Option, Vec)> { let snapshot = current_world(state).await?; let log = match state.world.operator_log().await { Ok(log) => log, @@ -2489,8 +2489,8 @@ mod tests { assert!(prepare_admitted_state_layout(&hardlink_root).is_err()); } - fn controller_commit() -> crate::world::CommitReceipt { - crate::world::CommitReceipt { + fn controller_commit() -> ghostlight::CommitReceipt { + ghostlight::CommitReceipt { command_id: CommandId::new(), resulting_revision: 9, resulting_state_digest: "sha256:declined-state".into(), @@ -2578,8 +2578,8 @@ mod tests { trace: None, }); let inference = open_inference(live.connector, live.sdk, &live.models).unwrap(); - let inference: Arc = match live.trace { - Some(path) => Arc::new(crate::world::TracingInferencePort::new(inference, path)), + let inference: Arc = match live.trace { + Some(path) => Arc::new(ghostlight::TracingInferencePort::new(inference, path)), None => inference, }; let work = open_controller_work(directory.path().join("controller-work.cc")).unwrap(); @@ -2670,7 +2670,7 @@ mod tests { /// The consumer door's two transport gates, the same two /// `/cultnet/snapshot` established. Everything past them belongs to - /// `world::consumer`, which is tested there. + /// `ghostlight`'s `consumer`, which is tested there. #[tokio::test] async fn a_non_loopback_peer_is_forbidden_and_a_wrong_content_type_is_unsupported() { let fixture = fixture().await; @@ -3078,11 +3078,11 @@ mod tests { assert_eq!(submitted.minutes(), CLOCK_TICK_MINUTES); } - use crate::world::{ + use ghostlight::{ ControllerPort, ControllerWork, ControllerWorkLookup, ControllerWorkStore, ControllerWorkStoreError, ControllerWorkWrite, InferenceEvent, InferenceFault, InferenceOutput, InferencePort, InferencePurpose, InferenceRequest, PreparedInference, - SubjectId, fixture_inference_events, fixture_inference_output, fixture_prepared_inference, + SubjectId, }; use std::sync::atomic::AtomicUsize; @@ -3240,7 +3240,7 @@ mod tests { #[async_trait::async_trait] impl InferencePort for CountingInferencePort { fn prepare(&self, request: InferenceRequest) -> Result { - fixture_prepared_inference(request) + PreparedInference::prepare("ghostlight-controller-test", 4_102_444_800_000, request) } async fn infer( @@ -3252,9 +3252,9 @@ mod tests { self.high_water.fetch_max(in_flight, Ordering::SeqCst); tokio::task::yield_now().await; self.in_flight.fetch_sub(1, Ordering::SeqCst); - Ok(fixture_inference_output( - "The cover tick fixture speaks.", - "counting-port", + Ok(InferenceOutput::new( + vec![InferenceEvent::Text("The cover tick fixture speaks.".into())], + "sha256:counting-port", )) } } @@ -3307,7 +3307,7 @@ mod tests { #[async_trait::async_trait] impl InferencePort for QuarantiningInferencePort { fn prepare(&self, request: InferenceRequest) -> Result { - fixture_prepared_inference(request) + PreparedInference::prepare("ghostlight-controller-test", 4_102_444_800_000, request) } async fn infer( @@ -3315,7 +3315,7 @@ mod tests { _request: PreparedInference, ) -> Result { self.calls.fetch_add(1, Ordering::SeqCst); - Err(InferenceFault::fixture_integrity_violation( + Err(InferenceFault::integrity_violation( "the fixture port disputes every receipt", )) } @@ -3379,7 +3379,7 @@ mod tests { /// Lands a real commit on the world's operational agent, through its own /// granted `speak` affordance and `WorldMailbox::submit_controller` — the /// same production port a controller's own decision uses, not a test-only - /// ingress. Mirrors `world::controllers`'s own `MidTurnCommit::Speech`, + /// ingress. Mirrors `ghostlight`'s `controllers`'s own `MidTurnCommit::Speech`, /// but from outside the `world` module, using only what this module's /// tests already have `pub(crate)` access to. async fn speak_through(mailbox: &WorldMailbox, speaker: SubjectId, text: &str) { @@ -3431,7 +3431,7 @@ mod tests { #[async_trait::async_trait] impl InferencePort for InterruptingCoverPort { fn prepare(&self, request: InferenceRequest) -> Result { - fixture_prepared_inference(request) + PreparedInference::prepare("ghostlight-controller-test", 4_102_444_800_000, request) } async fn infer( @@ -3439,13 +3439,14 @@ mod tests { request: PreparedInference, ) -> Result { match request.purpose { - InferencePurpose::Projector => Ok(fixture_inference_output( - "The room holds its breath.", - "projector", + InferencePurpose::Projector => Ok(InferenceOutput::new( + vec![InferenceEvent::Text("The room holds its breath.".into())], + "sha256:projector", + )), + InferencePurpose::Persona => Ok(InferenceOutput::new( + vec![InferenceEvent::Text(self.source.clone())], + "sha256:persona", )), - InferencePurpose::Persona => { - Ok(fixture_inference_output(self.source.clone(), "persona")) - } InferencePurpose::Interpreter => { let round = self.interpreter_calls.fetch_add(1, Ordering::SeqCst); let text = if round == 0 { @@ -3454,7 +3455,7 @@ mod tests { "A second bell rings over the yard." }; speak_through(&self.mailbox, self.speaker, text).await; - Ok(fixture_inference_events( + Ok(InferenceOutput::new( vec![ InferenceEvent::ToolCall { call_id: format!("call_speak_{round}"), @@ -3468,7 +3469,7 @@ mod tests { arguments: "{}".into(), }, ], - &format!("interpreter-{round}"), + format!("sha256:interpreter-{round}"), )) } // The operational agent's own cell shares this tick and this @@ -3476,7 +3477,7 @@ mod tests { // the one narrative turn it scripts. InferencePurpose::OperationalAgent | InferencePurpose::GroupedAgent - | InferencePurpose::Elaboration => Err(InferenceFault::fixture_recovery_required( + | InferencePurpose::Elaboration => Err(InferenceFault::recovery_required( "fixture agent declines every purpose but the narrative one", )), } @@ -3488,7 +3489,7 @@ mod tests { /// `run_cover_tick`), exercised through the tick driver rather than /// `ControllerRunner::run_narrative` directly. The operational agent /// speaks once between the narrative cell's Persona turn and its first - /// submit — a re-lowering, per `world::controllers`'s own + /// submit — a re-lowering, per `ghostlight`'s `controllers`'s own /// `a_neighbours_speech_between_the_turn_and_submit_is_re_lowered_once` /// — and once more between the re-lowered submit and its own commit, /// spending the one re-lowering the turn is owed @@ -3498,7 +3499,7 @@ mod tests { /// operational agent's two acts and nothing from the overtaken turn. #[tokio::test] async fn a_second_mid_turn_change_reaches_the_drivers_interrupted_arm() { - use crate::world::ControllerMode; + use ghostlight::ControllerMode; let fixture = fixture().await; active_two_cell_world(&fixture.state, &fixture.cookie).await; @@ -3555,9 +3556,56 @@ mod tests { ); } + /// The library half of this rule is `sdk_inference`'s own + /// `soul_no_credential_name_appears_in_the_ports_own_source`, which can + /// only read its own crate's source. This is the Dungeon half, on the same + /// needles: nothing in the daemon's production source may name a + /// credential path or token variable the SDK port refuses to read. + #[test] + fn soul_no_credential_name_appears_in_the_runtimes_own_source() { + let source = std::fs::read_to_string( + std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("src/runtime.rs"), + ) + .expect("the source reads") + .replace("\r\n", "\n"); + // Assembled from halves so this test's own source is not a match. + let needles: [String; 8] = [ + format!(".{}", "credentials.json"), + format!("CLAUDE_CODE{}", "_OAUTH_TOKEN"), + format!(".{}", "claude.json"), + format!("apiKey{}", "Helper"), + format!("USER{}", "PROFILE"), + format!("home{}", "_dir"), + format!("GHOSTLIGHT_SDK{}", "_TOKEN"), + format!("GHOSTLIGHT_SDK{}", "_CREDENTIAL"), + ]; + // Only the production half; a test may name what production must not. + let production = source + .split_once("\n#[cfg(test)]\nmod tests {") + .map(|(before, _)| before.to_owned()) + .unwrap_or(source); + for needle in &needles { + assert!( + !production.contains(needle.as_str()), + "runtime.rs names {needle}" + ); + } + // `ANTHROPIC_API_KEY` may be named in a comment, never anywhere that + // could read it. + let anthropic = format!("ANTHROPIC{}", "_"); + for line in production.lines() { + if line.contains(anthropic.as_str()) { + assert!( + line.trim_start().starts_with("///") || line.trim_start().starts_with("//"), + "runtime.rs names an ANTHROPIC variable outside a comment: {line}" + ); + } + } + } + /// `drive_one_tick` never names `run_cover_tick`: `run_cover` is opaque to - /// it, so this test substitutes a fake that walks a real `Cover` (three - /// singleton cells, from three fabricated opportunities) and records each + /// it, so this test substitutes a fake that walks a real `Cover` — derived + /// through production ingress from a real active world — and records each /// cell's tick index, alongside a fake clock submitter that records its /// own call. Asserts the ordering invariant `drive_one_tick` exists to /// buy — every cell recorded before the clock — and that every recorded @@ -3565,24 +3613,19 @@ mod tests { /// cover, matching `drive_cover_tick`'s own doc comment. #[tokio::test] async fn drive_one_tick_runs_every_cell_before_the_clock_and_all_share_one_tick() { - use crate::world::{ - AgencyGraph, Cell, ControllerMode, FictionalMinutes, TickIndex, - fixture_controller_opportunities, - }; + use ghostlight::{Cell, TickIndex}; - let opportunities = fixture_controller_opportunities(&[ - ControllerMode::NarrativePersona, - ControllerMode::OperationalAgent, - ControllerMode::OperationalAgent, - ]); - let world_id = opportunities[0].world_id; - let now = FictionalMinutes(u64::from(CLOCK_TICK_MINUTES) * 7); + let fixture = fixture().await; + active_two_cell_world(&fixture.state, &fixture.cookie).await; + + let snapshot = fixture.state.world.snapshot().await.unwrap(); + let graph = fixture.state.world.agency_graph().await.unwrap(); let cover = derive_cover( - world_id, - now, + snapshot.world_id, + snapshot.now, CLOCK_TICK_MINUTES, - &opportunities, - &AgencyGraph::default(), + &snapshot.opportunities, + &graph, CoverBudget { cells: 240, constituent_cap: 24, @@ -3591,8 +3634,8 @@ mod tests { ); assert_eq!( cover.cells.len(), - 3, - "three distinct subjects should derive three singleton cells" + 2, + "a genesis world's two controller-bearing subjects derive two singleton cells" ); #[derive(Debug, Clone, PartialEq, Eq)] @@ -3634,7 +3677,7 @@ mod tests { [Event::Clock], "the clock must be the last thing recorded" ); - assert_eq!(cell_events.len(), 3, "every cell must have run"); + assert_eq!(cell_events.len(), 2, "every cell must have run"); assert!( cell_events .iter() @@ -3655,7 +3698,7 @@ mod tests { static ENV_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(()); let _guard = ENV_LOCK.lock().unwrap_or_else(|poison| poison.into_inner()); - let previous = std::env::var(crate::world::CONSUMER_CREDENTIALS_ENVIRONMENT).ok(); + let previous = std::env::var(ghostlight::CONSUMER_CREDENTIALS_ENVIRONMENT).ok(); let directory = tempfile::tempdir().unwrap(); let path = directory.path().join("consumers.cc"); std::fs::write(&path, b"not a consumer credentials file").unwrap(); @@ -3663,7 +3706,7 @@ mod tests { // binary reads or writes this variable concurrently. unsafe { std::env::set_var( - crate::world::CONSUMER_CREDENTIALS_ENVIRONMENT, + ghostlight::CONSUMER_CREDENTIALS_ENVIRONMENT, path.as_os_str(), ); } @@ -3672,9 +3715,9 @@ mod tests { unsafe { match &previous { Some(value) => { - std::env::set_var(crate::world::CONSUMER_CREDENTIALS_ENVIRONMENT, value) + std::env::set_var(ghostlight::CONSUMER_CREDENTIALS_ENVIRONMENT, value) } - None => std::env::remove_var(crate::world::CONSUMER_CREDENTIALS_ENVIRONMENT), + None => std::env::remove_var(ghostlight::CONSUMER_CREDENTIALS_ENVIRONMENT), } } assert!( @@ -3774,12 +3817,12 @@ mod tests { draft.world_id, draft.revision, draft.scale_deficit.len(), - crate::world::select_row(&draft) + ghostlight::select_row(&draft) )); let mut seed_patches = 0usize; for round in 1..=seed_sessions { let before = state.world.snapshot().await.unwrap(); - if crate::world::select_row(&before).is_none() { + if ghostlight::select_row(&before).is_none() { line(format!("seed round {round} skipped: no shortfall left")); break; } @@ -4264,7 +4307,7 @@ mod tests { card["children"].as_array().unwrap().len(), draft.scale_deficit.len() ); - let selected = crate::world::select_row(&draft).expect("a shortfall to answer"); + let selected = ghostlight::select_row(&draft).expect("a shortfall to answer"); assert_eq!(selected.target, 9); assert!( card["props"]["nextShortfall"] @@ -4310,7 +4353,7 @@ mod tests { .expect("the fixture cookie names a live session"); // Same account as the live evidence, but minted with an expiry that // has already passed. - let expired = VerifiedPrincipalEvidence::fixture( + let expired = VerifiedPrincipalEvidence::new( live.account_subject_hash().to_owned(), Utc::now() - chrono::Duration::seconds(1), ); diff --git a/crates/ghostlight/Cargo.toml b/crates/ghostlight/Cargo.toml new file mode 100644 index 0000000..1641bfb --- /dev/null +++ b/crates/ghostlight/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "ghostlight" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +async-trait.workspace = true +chrono.workspace = true +codex-connector.workspace = true +cultcache-rs.workspace = true +ghostlight-persona-projection = { path = "../ghostlight-persona-projection" } +rmp-serde.workspace = true +serde.workspace = true +serde_bytes = "0.11" +serde_json.workspace = true +sha2.workspace = true +thiserror.workspace = true +tokio.workspace = true +tracing.workspace = true +uuid.workspace = true +zeroize.workspace = true + +[dev-dependencies] +tempfile = "3" diff --git a/crates/ghostlight/src/action.rs b/crates/ghostlight/src/action.rs index 45bd462..cc2bc43 100644 --- a/crates/ghostlight/src/action.rs +++ b/crates/ghostlight/src/action.rs @@ -22,7 +22,7 @@ use std::collections::{BTreeMap, BTreeSet}; /// like `Mismatch`, and like it never serialized: it states the affordance /// contract, which no commit records. #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub(crate) enum ActionMismatch { +pub enum ActionMismatch { UnboundRole { role: Role, }, @@ -1016,18 +1016,18 @@ fn lower( #[cfg(test)] mod tests { use super::*; - use crate::world::custody_tests::custody_kernel; - use crate::world::patch::{ + use crate::custody_tests::custody_kernel; + use crate::patch::{ AffordanceDeclaration, ComponentOp, DraftHandle, OutcomeBand, Ref as PatchRef, ResolvedOp, RoleSpec, }; - use crate::world::tests::{ + use crate::tests::{ ADMIT_KIND, COMMAND_KIND, Civic, LEVY_KIND, OPENING_BALANCE, SEIZURE_GRIEVANCE, Topology, WARDEN_OFFICE, activate, affordance_named, auth_principal, authority_kind, civic_world, command, custody_world, grant_to, grievance, office, operations, opportunity_for, over_place, over_subject, player, reject_owner, submit_owner, }; - use crate::world::{ + use crate::{ AffordanceKindName, AuthenticatedCaller, AuthoredSource, CallerId, CommandBody, Confidence, Declaration, EffectSlot, EntityKind, ProposedEffect, RoleBinding, Statement, SubmitReceipt, WorldEffect, WorldKernel, WorldPatch, WorldSnapshot, apply_effect, @@ -1073,7 +1073,7 @@ mod tests { opportunity: &DecisionOpportunity, invocation: &DecisionInvocation, ) -> Result { - let granted = crate::world::require_granted(state, opportunity, invocation.affordance)?; + let granted = crate::require_granted(state, opportunity, invocation.affordance)?; exercise(state, command_id, opportunity, &granted, invocation) } @@ -1753,7 +1753,7 @@ mod tests { // The two the speech lowering prepends, and nothing from a slot. assert_eq!(event.effects.len(), 2); assert_eq!( - crate::world::tests::spoken(&kernel.state, event), + crate::tests::spoken(&kernel.state, event), Some("I open the door.") ); assert_eq!(kernel.state.holdings, holdings); @@ -1923,7 +1923,7 @@ mod tests { self.refresh(); } - fn refuse(&mut self, ops: Vec) -> Vec { + fn refuse(&mut self, ops: Vec) -> Vec { let before = self.kernel.snapshot().unwrap(); reject_owner(&mut self.kernel, &before, operations(ops)) } @@ -2096,7 +2096,7 @@ mod tests { }, }, ); - assert!(rejected.contains(&crate::world::Mismatch::ReservedRole { handle })); + assert!(rejected.contains(&crate::Mismatch::ReservedRole { handle })); } /// The two rejection lanes stay distinguishable: an over-levy inside its @@ -2113,7 +2113,7 @@ mod tests { }; assert_eq!( rejected, - vec![crate::world::Mismatch::InsufficientCustody { operation: 0 }] + vec![crate::Mismatch::InsufficientCustody { operation: 0 }] ); } @@ -2165,12 +2165,12 @@ mod tests { }; assert_eq!( rejected, - vec![crate::world::Mismatch::NoOperationEffect { operation: 0 }] + vec![crate::Mismatch::NoOperationEffect { operation: 0 }] ); let hall = bench.civic.hall; assert_eq!( bench.refuse(vec![grant_to(outsider, LEVY_KIND, over_place(hall))]), - vec![crate::world::Mismatch::OverlappingJurisdiction { operation: 0 }] + vec![crate::Mismatch::OverlappingJurisdiction { operation: 0 }] ); } @@ -2206,9 +2206,9 @@ mod tests { .state .authority .get(&treasury) - .is_some_and(|grants| grants.contains(&crate::world::AuthorityGrant { + .is_some_and(|grants| grants.contains(&crate::AuthorityGrant { kind: authority_kind(LEVY_KIND), - over: crate::world::AuthorityTarget::PlaceSubtree(hall), + over: crate::AuthorityTarget::PlaceSubtree(hall), })), "the revoked grant is gone from the ledger" ); @@ -2292,7 +2292,7 @@ mod tests { .expect("the committed event"); assert_eq!(event.effects.len(), 2); assert_eq!( - crate::world::tests::spoken(&bench.kernel.state, event), + crate::tests::spoken(&bench.kernel.state, event), Some("The tithe was taken twice.") ); @@ -2323,18 +2323,18 @@ mod tests { assert!( bench .refuse(vec![step.clone()]) - .contains(&crate::world::Mismatch::RouteAccessRestricted { operation: 0 }) + .contains(&crate::Mismatch::RouteAccessRestricted { operation: 0 }) ); let yard = bench.topology.yard; bench.admit(vec![grant_to(walker, ADMIT_KIND, over_place(yard))]); assert!( bench .refuse(vec![step.clone()]) - .contains(&crate::world::Mismatch::RouteAccessRestricted { operation: 0 }) + .contains(&crate::Mismatch::RouteAccessRestricted { operation: 0 }) ); // Reachable reads the same rule from the other side. - let authority = crate::world::subject_authority(&bench.kernel.state, walker); + let authority = crate::subject_authority(&bench.kernel.state, walker); assert!(!reachable( &bench.kernel.state, walker, @@ -2346,7 +2346,7 @@ mod tests { // A grant covering the destination, minted in the same patch as the // move, resolves through the candidate authority shadow. let hall = bench.civic.hall; - let authority = crate::world::subject_authority(&bench.kernel.state, walker); + let authority = crate::subject_authority(&bench.kernel.state, walker); assert!(!patch::route_admits( &bench.kernel.state, &authority, @@ -2358,7 +2358,7 @@ mod tests { bench.kernel.state.positions[&walker].place, bench.civic.chamber ); - let authority = crate::world::subject_authority(&bench.kernel.state, walker); + let authority = crate::subject_authority(&bench.kernel.state, walker); assert!(patch::route_admits( &bench.kernel.state, &authority, @@ -2517,24 +2517,24 @@ mod tests { let mut forged = bench.kernel.state.clone(); forged.positions.insert( walker, - crate::world::patch::Position { + crate::patch::Position { place: bench.topology.yard, }, ); let mut refused = forged.clone(); - let error = crate::world::apply_operations(&mut refused, std::slice::from_ref(&step), &[]) + let error = crate::apply_operations(&mut refused, std::slice::from_ref(&step), &[]) .unwrap_err(); assert!(matches!(error, KernelError::Invariant(_))); let mut admitted = forged; admitted.authority.insert( walker, - BTreeSet::from([crate::world::AuthorityGrant { - kind: crate::world::tests::authority_kind(ADMIT_KIND), - over: crate::world::AuthorityTarget::PlaceSubtree(bench.civic.hall), + BTreeSet::from([crate::AuthorityGrant { + kind: crate::tests::authority_kind(ADMIT_KIND), + over: crate::AuthorityTarget::PlaceSubtree(bench.civic.hall), }]), ); - crate::world::apply_operations(&mut admitted, std::slice::from_ref(&step), &[]) + crate::apply_operations(&mut admitted, std::slice::from_ref(&step), &[]) .expect("the named key opens the door at the component writer too"); assert_eq!(admitted.positions[&walker].place, bench.civic.chamber); } @@ -2558,7 +2558,7 @@ mod tests { let invocation = bench.levy(farmer, 1); bench.admit(vec![ComponentOp::RevokeAuthority { holder: PatchRef::Existing(treasury), - grant: crate::world::tests::grant_of(LEVY_KIND, over_place(hall)), + grant: crate::tests::grant_of(LEVY_KIND, over_place(hall)), }]); // The envelope carries the current revision, so only the stale scope // digest can refuse it. @@ -2603,14 +2603,14 @@ mod tests { // The farmer stands under the hall the treasury already levies, so the // covering predicate says both grants would answer for the same // target, and admission refuses the narrower grant landing second. - assert!(crate::world::covers( + assert!(crate::covers( &bench.kernel.state, - crate::world::AuthorityTarget::PlaceSubtree(hall), + crate::AuthorityTarget::PlaceSubtree(hall), Target::Subject(farmer) )); assert_eq!( bench.refuse(vec![grant_to(treasury, LEVY_KIND, over_subject(farmer))]), - vec![crate::world::Mismatch::OverlappingJurisdiction { operation: 0 }] + vec![crate::Mismatch::OverlappingJurisdiction { operation: 0 }] ); // The symmetric order: a direct grant over the farmer, minted first @@ -2620,7 +2620,7 @@ mod tests { bench.admit(vec![grant_to(treasury, AUDIT_KIND, over_subject(farmer))]); assert_eq!( bench.refuse(vec![grant_to(treasury, AUDIT_KIND, over_place(hall))]), - vec![crate::world::Mismatch::OverlappingJurisdiction { operation: 0 }] + vec![crate::Mismatch::OverlappingJurisdiction { operation: 0 }] ); } @@ -2677,8 +2677,8 @@ mod tests { invocation, }, ); - let first = crate::world::reduce(&before, &envelope).expect("the act reduces"); - let second = crate::world::reduce(&before, &envelope).expect("the act reduces again"); + let first = crate::reduce(&before, &envelope).expect("the act reduces"); + let second = crate::reduce(&before, &envelope).expect("the act reduces again"); assert_eq!(first, second, "reduce is not a pure function of the state"); let receipt = bench .kernel diff --git a/crates/ghostlight/src/clock.rs b/crates/ghostlight/src/clock.rs index 6f8e023..9d32fd6 100644 --- a/crates/ghostlight/src/clock.rs +++ b/crates/ghostlight/src/clock.rs @@ -18,7 +18,7 @@ use std::collections::BTreeMap; Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash, )] #[serde(transparent)] -pub(crate) struct FictionalMinutes(pub(crate) u64); +pub struct FictionalMinutes(pub u64); impl FictionalMinutes { pub(crate) fn checked_add(self, span: TickMinutes) -> Option { @@ -39,16 +39,16 @@ impl FictionalMinutes { /// due date in the world and makes the size of a commit a caller-chosen number. #[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)] #[serde(transparent)] -pub(crate) struct TickMinutes(u32); +pub struct TickMinutes(u32); impl TickMinutes { - pub(crate) fn new(minutes: u32) -> Option { + pub fn new(minutes: u32) -> Option { (1..=MAX_ROUTE_COST) .contains(&minutes) .then_some(Self(minutes)) } - pub(crate) fn minutes(self) -> u32 { + pub fn minutes(self) -> u32 { self.0 } } diff --git a/crates/ghostlight/src/consumer.rs b/crates/ghostlight/src/consumer.rs index 6593f4b..284a986 100644 --- a/crates/ghostlight/src/consumer.rs +++ b/crates/ghostlight/src/consumer.rs @@ -11,8 +11,10 @@ use super::mailbox::{ConsumerPort, MailboxError}; use super::patch::{self, PatchDecodeError}; use super::{ - CommandId, ConsumerId, KernelError, Mismatch, PatchAnswer, SubmitReceipt, WorldId, WorldPatch, + CommandId, ConsumerId, KernelError, Mismatch, PatchAnswer, SubmitReceipt, WorldId, }; +#[cfg(test)] +use super::WorldPatch; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; use std::collections::BTreeMap; @@ -20,8 +22,8 @@ use std::path::Path; /// The inbound document and the outbound receipt. Ghostlight-owned typed /// records in canonical MessagePack, not CultNet control messages. -pub(crate) const CONSUMER_PATCH_SCHEMA: &str = "ghostlight.consumer_patch.v0"; -pub(crate) const CONSUMER_RECEIPT_SCHEMA: &str = "ghostlight.consumer_receipt.v0"; +pub const CONSUMER_PATCH_SCHEMA: &str = "ghostlight.consumer_patch.v0"; +pub const CONSUMER_RECEIPT_SCHEMA: &str = "ghostlight.consumer_receipt.v0"; /// The one namespace a consumer's command key is derived under. const CONSUMER_COMMAND_NAMESPACE: &str = "ghostlight.consumer.command.v0"; @@ -30,10 +32,10 @@ const CONSUMER_COMMAND_NAMESPACE: &str = "ghostlight.consumer.command.v0"; /// `patch::MAX_PATCH_BYTES` and stays there; this is derived from it so the /// transport guard cannot become a second opinion about how big a patch may be. const CONSUMER_ENVELOPE_SLACK: usize = 8 * 1024; -pub(crate) const CONSUMER_BODY_LIMIT: usize = patch::MAX_PATCH_BYTES + CONSUMER_ENVELOPE_SLACK; +pub const CONSUMER_BODY_LIMIT: usize = patch::MAX_PATCH_BYTES + CONSUMER_ENVELOPE_SLACK; /// The environment variable naming the credentials file. -pub(crate) const CONSUMER_CREDENTIALS_ENVIRONMENT: &str = "GHOSTLIGHT_CONSUMER_CREDENTIALS"; +pub const CONSUMER_CREDENTIALS_ENVIRONMENT: &str = "GHOSTLIGHT_CONSUMER_CREDENTIALS"; #[derive(Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(deny_unknown_fields)] @@ -85,7 +87,7 @@ impl std::fmt::Debug for ConsumerPatchDocument { #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(deny_unknown_fields)] -pub(crate) struct ConsumerReceiptDocument { +pub struct ConsumerReceiptDocument { pub(crate) schema: String, pub(crate) world_id: Option, /// Absent when the document did not decode far enough to derive one. The @@ -160,12 +162,12 @@ pub(crate) enum ConsumerRefusal { /// That is the fail-closed default, and it is why this door can exist before an /// operator has configured anything. #[derive(Clone, Debug, Default)] -pub(crate) struct ConsumerRegistry { +pub struct ConsumerRegistry { entries: BTreeMap, } impl ConsumerRegistry { - pub(crate) fn empty() -> Self { + pub fn empty() -> Self { Self::default() } @@ -173,7 +175,7 @@ impl ConsumerRegistry { /// comments ignored. A file that does not exist yields no consumers; a file /// that exists and is malformed is an error, because a mistyped credential /// file must not read as an empty one. - pub(crate) fn from_secret_file(path: impl AsRef) -> Result { + pub fn from_secret_file(path: impl AsRef) -> Result { let path = path.as_ref(); if !path.exists() { return Ok(Self::empty()); @@ -246,7 +248,7 @@ fn decode_digest(text: &str) -> Option<[u8; 32]> { /// Decode, bound, authenticate, submit, project. One atomic verdict per /// document: there is no partial acceptance, because there is one command. -pub(crate) async fn admit_document( +pub async fn admit_document( port: &ConsumerPort, registry: &ConsumerRegistry, bytes: &[u8], @@ -429,7 +431,7 @@ pub(crate) fn encode_document(document: &ConsumerPatchDocument) -> Result Result, String> { +pub fn encode_receipt(receipt: &ConsumerReceiptDocument) -> Result, String> { rmp_serde::to_vec_named(receipt).map_err(|error| error.to_string()) } @@ -898,7 +900,7 @@ mod tests { async fn submit_through( mailbox: &WorldMailbox, - principal: crate::world::PrincipalId, + principal: crate::PrincipalId, body: CommandBody, ) -> SubmitReceipt { let snapshot = mailbox.snapshot().await.unwrap(); @@ -963,7 +965,7 @@ mod tests { submit_through(&mailbox, owner(), CommandBody::ApproveDraft).await; submit_through( &mailbox, - crate::world::tests::player(), + crate::tests::player(), CommandBody::ApproveDraft, ) .await; diff --git a/crates/ghostlight/src/controllers.rs b/crates/ghostlight/src/controllers.rs index 17f3f06..1a6c629 100644 --- a/crates/ghostlight/src/controllers.rs +++ b/crates/ghostlight/src/controllers.rs @@ -14,8 +14,10 @@ use super::sdk_inference::{ ChildProcessLink, DEFAULT_SDK_MODEL_PREFIX, RoutedInferencePort, SdkBinding, SdkInferencePort, }; use super::tool_schema; -use crate::world::{ - AffordanceId, AffordanceSnapshot, AuthorityGrant, Bounds, Cell, CellId, CommandId, +#[cfg(test)] +use crate::AffordanceId; +use crate::{ + AffordanceSnapshot, AuthorityGrant, Bounds, Cell, CellId, CommandId, CommitReceipt, Confidence, Constituent, ControllerMode, ControllerPort, Cost, DecisionInvocation, DecisionOpportunity, DependencyTarget, EdgeId, ElaborationPort, EntityId, EntityKind, FactStandingView, KernelError, KnowledgeSnapshot, KnowledgeSource, Magnitude, @@ -93,7 +95,7 @@ const PERSONA_PROVIDER_INSTRUCTIONS: &str = "Respond only in natural prose to the lived moment in the user message."; #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub(crate) enum InferencePurpose { +pub enum InferencePurpose { Projector, Persona, Interpreter, @@ -109,7 +111,7 @@ pub(crate) enum InferencePurpose { /// One exact provider request. Keeping the native request visible at this seam /// makes the prose-only Persona boundary structurally inspectable. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub(crate) struct InferenceRequest { +pub struct InferenceRequest { purpose: InferencePurpose, provider: CodexProviderRequest, } @@ -117,7 +119,7 @@ pub(crate) struct InferenceRequest { impl InferenceRequest { /// The model this request names, read before it is prepared. Routing is the /// one decision that has to be made on an unprepared request. - pub(super) fn provider_model(&self) -> &str { + pub fn provider_model(&self) -> &str { &self.provider.model } } @@ -126,13 +128,13 @@ impl InferenceRequest { /// Replaying this value may recover a completed connector response; rebuilding /// it under the same request ID would be a replay conflict. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub(crate) struct PreparedInference { - pub(crate) purpose: InferencePurpose, +pub struct PreparedInference { + pub purpose: InferencePurpose, pub(super) invocation: CodexTransportInvocation, } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub(crate) enum InferenceEvent { +pub enum InferenceEvent { Text(String), ToolCall { call_id: String, @@ -142,12 +144,21 @@ pub(crate) enum InferenceEvent { } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub(crate) struct InferenceOutput { +pub struct InferenceOutput { pub(super) events: Vec, pub(super) receipt_digest: String, } impl InferenceOutput { + /// What a port returns from `infer`. The receipt digest is the provider's + /// own, carried verbatim; this constructor does not mint one. + pub fn new(events: Vec, receipt_digest: impl Into) -> Self { + Self { + events, + receipt_digest: receipt_digest.into(), + } + } + pub(super) fn prose_only( self, purpose: InferencePurpose, @@ -176,7 +187,7 @@ impl InferenceOutput { #[derive(Clone, Debug, Error, PartialEq, Eq)] #[error("{detail}")] -pub(crate) struct InferenceFault { +pub struct InferenceFault { disposition: InferenceFaultDisposition, detail: String, } @@ -196,45 +207,37 @@ impl InferenceFault { } } - pub(super) fn retryable(detail: impl Into) -> Self { + /// Transport trouble the owning flow may retry. + pub fn retryable(detail: impl Into) -> Self { Self { disposition: InferenceFaultDisposition::Retryable, detail: detail.into(), } } - pub(super) fn integrity_violation(detail: impl Into) -> Self { + /// The one disposition that makes `ControllerError::requires_quarantine` + /// true for an `Inference` fault: the provider's answer cannot be trusted, + /// so the tick driver stops rather than commits. + pub fn integrity_violation(detail: impl Into) -> Self { Self { disposition: InferenceFaultDisposition::IntegrityViolation, detail: detail.into(), } } - pub(super) fn recovery_required(&self) -> bool { + /// The non-quarantining failure: this purpose cannot complete, but a + /// sibling cell in the same tick is unaffected. + pub fn recovery_required(detail: impl Into) -> Self { + Self::new(detail) + } + + pub(super) fn requires_recovery(&self) -> bool { self.disposition == InferenceFaultDisposition::RecoveryRequired } pub(super) fn integrity_was_violated(&self) -> bool { self.disposition == InferenceFaultDisposition::IntegrityViolation } - - /// The one disposition that makes `ControllerError::requires_quarantine` - /// true for an `Inference` fault. A test port outside this module needs to - /// raise exactly this to exercise the tick driver's quarantine edge, and - /// this is that one legal way in. - #[cfg(test)] - pub(crate) fn fixture_integrity_violation(detail: impl Into) -> Self { - Self::integrity_violation(detail) - } - - /// The non-quarantining counterpart: a test port outside this module that - /// needs one purpose to fail without raising - /// `ControllerError::requires_quarantine` (so a sibling cell in the same - /// tick is unaffected) has no other legal way to build one. - #[cfg(test)] - pub(crate) fn fixture_recovery_required(detail: impl Into) -> Self { - Self::new(detail) - } } /// One lane's own answer to "what does this tool call return", lent to the @@ -245,7 +248,7 @@ impl InferenceFault { /// is the same fold, over the calls made so far, into fresh state. A port that /// computed its own answer would put a string in front of the model that no /// durable evidence records and no check can catch. -pub(crate) trait ToolResultOracle: Send { +pub trait ToolResultOracle: Send { /// The lane's remaining round budget, lowered to a transport's turn cap so /// one query cannot exceed what the evaluator would have allowed. fn remaining_rounds(&self) -> u32; @@ -256,7 +259,7 @@ pub(crate) trait ToolResultOracle: Send { } #[async_trait] -pub(crate) trait InferencePort: Send + Sync { +pub trait InferencePort: Send + Sync { fn prepare(&self, request: InferenceRequest) -> Result; async fn infer(&self, request: PreparedInference) -> Result; @@ -269,20 +272,20 @@ pub(crate) trait InferencePort: Send + Sync { } } -/// Test-only. Wraps any `InferencePort` and appends every prepared request and -/// its output to one file, so a road run can show the whole membrane: what the -/// Projector, the Persona, and the Interpreter were each given and what each -/// returned. Production never constructs it; prose reaches durable state only -/// as receipt-bound evidence, and the logs redact it by design. -#[cfg(test)] -pub(crate) struct TracingInferencePort { +/// Wraps any `InferencePort` and appends every prepared request and its output +/// to one file, so a road run can show the whole membrane: what the Projector, +/// the Persona, and the Interpreter were each given and what each returned. +/// +/// It decorates; it constructs nothing sealed. Whether a deployment ever builds +/// one is the consumer's rule, not this crate's: prose reaches durable state +/// only as receipt-bound evidence, and the logs redact it by design. +pub struct TracingInferencePort { inner: Arc, path: PathBuf, } -#[cfg(test)] impl TracingInferencePort { - pub(crate) fn new(inner: Arc, path: PathBuf) -> Self { + pub fn new(inner: Arc, path: PathBuf) -> Self { Self { inner, path } } @@ -341,7 +344,6 @@ impl TracingInferencePort { } } -#[cfg(test)] #[async_trait] impl InferencePort for TracingInferencePort { fn prepare(&self, request: InferenceRequest) -> Result { @@ -369,65 +371,31 @@ impl InferencePort for TracingInferencePort { } } -/// Builds a `PreparedInference` outside the real CodexConnector wiring. Exists -/// so a test port defined outside this module (`runtime`'s own spec tests, for -/// the tick driver's concurrency and quarantine behaviour) has one legal way to -/// answer `InferencePort::prepare` without reaching into `PreparedInference`'s -/// private fields or standing up a real connector. -#[cfg(test)] -pub(crate) fn fixture_prepared_inference( - request: InferenceRequest, -) -> Result { - prepare_invocation("ghostlight-controller-test", 4_102_444_800_000, request) -} - -/// The one place a `PreparedInference` is built. Every port calls it, so a -/// request prepared by one transport and a request prepared by another are the -/// same value under the same digests, and every `integrity_is_valid` variant -/// validates one identity scheme rather than two. -pub(super) fn prepare_invocation( - caller_runtime_id: &str, - expires_at_unix_ms: u64, - request: InferenceRequest, -) -> Result { - let request_bytes = - serde_json::to_vec(&request).map_err(|error| InferenceFault::new(error.to_string()))?; - let purpose = request.purpose; - let invocation = CodexTransportInvocation::new( - caller_runtime_id, - expires_at_unix_ms, - Sha256::digest(request_bytes).into(), - request.provider, - ) - .map_err(|error| InferenceFault::new(error.to_string()))?; - Ok(PreparedInference { - purpose, - invocation, - }) -} - -/// The `infer` half of the same seam: a canned prose output a test port can -/// return without naming `InferenceOutput`'s or `InferenceEvent`'s private -/// fields. -#[cfg(test)] -pub(crate) fn fixture_inference_output(text: impl Into, receipt: &str) -> InferenceOutput { - InferenceOutput { - events: vec![InferenceEvent::Text(text.into())], - receipt_digest: format!("sha256:{receipt}"), - } -} - -/// The same seam as `fixture_inference_output`, for a test port outside this -/// module that needs to answer with something other than plain prose — an -/// Interpreter's `speak`/`finish_interpretation` tool calls, in particular. -#[cfg(test)] -pub(crate) fn fixture_inference_events( - events: Vec, - receipt: &str, -) -> InferenceOutput { - InferenceOutput { - events, - receipt_digest: format!("sha256:{receipt}"), +impl PreparedInference { + /// The one place a `PreparedInference` is built. Every port calls it, + /// including a port a consumer implements outside this crate, so a request + /// prepared by one transport and a request prepared by another are the same + /// value under the same digests, and every `integrity_is_valid` variant + /// validates one identity scheme rather than two. + pub fn prepare( + caller_runtime_id: &str, + expires_at_unix_ms: u64, + request: InferenceRequest, + ) -> Result { + let request_bytes = + serde_json::to_vec(&request).map_err(|error| InferenceFault::new(error.to_string()))?; + let purpose = request.purpose; + let invocation = CodexTransportInvocation::new( + caller_runtime_id, + expires_at_unix_ms, + Sha256::digest(request_bytes).into(), + request.provider, + ) + .map_err(|error| InferenceFault::new(error.to_string()))?; + Ok(PreparedInference { + purpose, + invocation, + }) } } @@ -559,7 +527,7 @@ impl CodexConnectorInferencePort { #[async_trait] impl InferencePort for CodexConnectorInferencePort { fn prepare(&self, request: InferenceRequest) -> Result { - prepare_invocation( + PreparedInference::prepare( &self.caller_runtime_id, unix_ms()?.saturating_add(REQUEST_EXPIRY.as_millis() as u64), request, @@ -589,7 +557,7 @@ pub(super) fn unix_ms() -> Result { /// second copy of a digest-bound value is a value that can disagree. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] -pub(super) struct ConstituentWork { +pub struct ConstituentWork { subject: SubjectId, opportunity: DecisionOpportunity, granted: Vec, @@ -611,7 +579,7 @@ pub(super) struct ConstituentWork { /// the ledger already owns. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(tag = "stage", rename_all = "snake_case")] -enum GroupedCheckpoint { +pub enum GroupedCheckpoint { AgentInFlight { command_id: CommandId, cell: CellId, @@ -637,7 +605,7 @@ enum GroupedCheckpoint { /// never separately persisted. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(tag = "mode", content = "checkpoint", rename_all = "snake_case")] -pub(crate) enum ControllerWork { +pub enum ControllerWork { Narrative(NarrativeCheckpoint), Operational(OperationalCheckpoint), /// One coarse cell's cognition. The only durable trace a cover leaves, and @@ -659,7 +627,7 @@ pub(crate) enum ControllerWork { /// which says how a *subject* is controlled: the elaborator is not a subject /// and has no mode. #[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub(crate) enum WorkLane { +pub enum WorkLane { Narrative, Operational, Grouped, @@ -669,7 +637,7 @@ pub(crate) enum WorkLane { #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(tag = "stage", rename_all = "snake_case")] -enum NarrativeCheckpoint { +pub enum NarrativeCheckpoint { Projector { command_id: CommandId, identity: String, @@ -740,7 +708,7 @@ enum NarrativeCheckpoint { #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(tag = "stage", rename_all = "snake_case")] -enum OperationalCheckpoint { +pub enum OperationalCheckpoint { AgentInFlight { command_id: CommandId, agent_prompt: String, @@ -1161,16 +1129,16 @@ fn cell_constituents_are_valid(constituents: &[ConstituentWork]) -> bool { } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub(crate) struct NarrativeCapture { +pub struct NarrativeCapture { /// The spoken words as the verbatim spans that carry them, in prose /// order. An utterance split by narration ("So," I say, "does it open") /// is several spans and one statement. - pub(crate) speech: Vec, + pub speech: Vec, /// The visible acts, bound the same way, in prose order; one display. - pub(crate) display: Vec, - pub(crate) gaps: Vec, - pub(crate) finalization: InterpretationFinalization, - pub(crate) inference_receipts: Vec, + pub display: Vec, + pub gaps: Vec, + pub finalization: InterpretationFinalization, + pub inference_receipts: Vec, } impl NarrativeCapture { @@ -1238,7 +1206,7 @@ pub(crate) enum Perceived { /// request byte for byte without a snapshot. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] -pub(crate) struct Interruption { +pub struct Interruption { /// The subject's own components at re-lowering. The "after"; the /// checkpoint's `components` is the "before". pub(crate) components: ScopeComponents, @@ -1262,23 +1230,23 @@ fn interpreter_round(interruption: &Option, completed: &[Inference } #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub(crate) struct SourceRange { - pub(crate) start_byte: usize, - pub(crate) end_byte: usize, +pub struct SourceRange { + pub start_byte: usize, + pub end_byte: usize, } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub(crate) struct TranslationGapSummary { - pub(crate) kind: TranslationGapKind, - pub(crate) source: SourceRange, - pub(crate) detail: String, +pub struct TranslationGapSummary { + pub kind: TranslationGapKind, + pub source: SourceRange, + pub detail: String, } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub(crate) struct OperationalCapture { - pub(crate) proposal: Option, - pub(crate) needs: Vec, - pub(crate) inference_receipts: Vec, +pub struct OperationalCapture { + pub proposal: Option, + pub needs: Vec, + pub inference_receipts: Vec, } /// One opportunity's selection against one snapshot. @@ -1522,7 +1490,7 @@ fn persona_request_shape_is_valid(request: &PreparedInference) -> bool { } #[derive(Clone, Debug, Error, PartialEq, Eq)] -pub(crate) enum ControllerWorkStoreError { +pub enum ControllerWorkStoreError { #[error("Eve command ID already belongs to the other controller mode")] CommandModeConflict, #[error("{detail}")] @@ -1538,7 +1506,7 @@ impl ControllerWorkStoreError { } #[async_trait] -pub(crate) trait ControllerWorkStore: Send + Sync { +pub trait ControllerWorkStore: Send + Sync { async fn lookup( &self, command_id: CommandId, @@ -1556,21 +1524,21 @@ pub(crate) trait ControllerWorkStore: Send + Sync { } #[derive(Clone, Debug, PartialEq, Eq)] -pub(crate) enum ControllerWorkLookup { +pub enum ControllerWorkLookup { Missing, Confirmed(ControllerWork), CustodyUncertain(ControllerWork), } #[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub(crate) enum ControllerWorkWrite { +pub enum ControllerWorkWrite { Applied, AlreadyPresent, CustodyUncertain, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub(crate) enum ControllerWorkCustody { +pub enum ControllerWorkCustody { Owned { narrative_commands: usize, operational_commands: usize, @@ -1603,7 +1571,7 @@ struct CultCacheControllerWorkStore { /// The production controller-work owner, opened for the caller that names its /// path. The store type stays private; only the trait object leaves here. -pub(crate) fn open_controller_work( +pub fn open_controller_work( path: impl AsRef, ) -> Result, ControllerOpenError> { Ok(Arc::new(CultCacheControllerWorkStore::open(path)?)) @@ -2254,18 +2222,18 @@ fn store_key(command_id: CommandId) -> Result } #[derive(Clone, Debug)] -pub(crate) struct ControllerModels { - pub(crate) projector: String, - pub(crate) persona: String, - pub(crate) interpreter: String, - pub(crate) operational_agent: String, +pub struct ControllerModels { + pub projector: String, + pub persona: String, + pub interpreter: String, + pub operational_agent: String, /// The authoring lane's model. The existing config shape already gates /// cognition by model name, so the driver starts on this and no mode flag /// is added. The model name also selects the transport: a /// `GHOSTLIGHT_SDK_MODEL_PREFIX`-prefixed model reaches the SDK sidecar and /// anything else reaches the connector, which is why there is still no mode /// flag. - pub(crate) elaborator: String, + pub elaborator: String, } impl ControllerModels { @@ -2288,7 +2256,7 @@ impl ControllerModels { } #[derive(Debug, Error)] -pub(crate) enum ControllerOpenError { +pub enum ControllerOpenError { #[error("controller model IDs must be exact nonempty identifiers without whitespace")] InvalidModels, #[error("CodexConnector controller transport could not open: {0}")] @@ -2303,15 +2271,15 @@ pub(crate) enum ControllerOpenError { /// Everything the CodexConnector transport needs to open, gathered so /// `ControllerRunner::open` takes a port rather than building one. -pub(crate) struct ConnectorBinding { - pub(crate) endpoint: SocketAddr, - pub(crate) key_path: PathBuf, - pub(crate) caller_runtime_id: String, +pub struct ConnectorBinding { + pub endpoint: SocketAddr, + pub key_path: PathBuf, + pub caller_runtime_id: String, } /// Builds the one port every lane shares. A lane whose model no configured /// backend claims fails here, at open, rather than at its first tick. -pub(crate) fn open_inference( +pub fn open_inference( connector: Option, sdk: Option, models: &ControllerModels, @@ -2366,12 +2334,12 @@ pub(crate) enum NarrativeProposal { } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub(crate) struct ControllerNeed { +pub struct ControllerNeed { pub(crate) detail: String, } #[derive(Debug)] -pub(crate) enum SubmissionDisposition { +pub enum SubmissionDisposition { NoProposal(SubmitReceipt), Completed(SubmitReceipt), /// Derived on demand from the WorldMailbox journal. Controller work never @@ -2381,20 +2349,20 @@ pub(crate) enum SubmissionDisposition { /// A committed turn's one re-lowering, as the operator reads it. #[derive(Clone, Debug, PartialEq, Eq)] -pub(crate) struct ReLowering { - pub(crate) bound_scope_digest: String, - pub(crate) renewed_scope_digest: String, +pub struct ReLowering { + pub bound_scope_digest: String, + pub renewed_scope_digest: String, } #[derive(Debug)] -pub(crate) struct NarrativeDecision { +pub struct NarrativeDecision { turn: PersonaTurn, capture: NarrativeCapture, submission: SubmissionDisposition, } impl NarrativeDecision { - pub(crate) fn persona_turn(&self) -> &PersonaTurn { + pub fn persona_turn(&self) -> &PersonaTurn { &self.turn } @@ -2410,7 +2378,7 @@ impl NarrativeDecision { /// prose was bound to and the one it was re-lowered against. `None` for a /// turn lowered once. Read from the binding the receipt carries, so the /// operator's line and the persisted row cannot disagree. - pub(crate) fn re_lowering(&self) -> Option { + pub fn re_lowering(&self) -> Option { let renewed = self.turn.binding(); renewed.interrupted_from.as_deref().map(|bound| ReLowering { bound_scope_digest: bound.scope_digest.clone(), @@ -2418,13 +2386,13 @@ impl NarrativeDecision { }) } - pub(crate) fn into_parts(self) -> (PersonaTurn, NarrativeCapture, SubmissionDisposition) { + pub fn into_parts(self) -> (PersonaTurn, NarrativeCapture, SubmissionDisposition) { (self.turn, self.capture, self.submission) } } #[derive(Debug)] -pub(crate) enum NarrativeRun { +pub enum NarrativeRun { Completed(NarrativeDecision), Pending(NarrativePending), /// The turn was interrupted and could not be lowered again: its one @@ -2435,7 +2403,7 @@ pub(crate) enum NarrativeRun { } #[derive(Debug)] -pub(crate) struct NarrativeInterruption { +pub struct NarrativeInterruption { turn: PersonaTurn, subject: SubjectId, bound_scope_digest: String, @@ -2450,29 +2418,29 @@ pub(crate) struct NarrativeInterruption { } impl NarrativeInterruption { - pub(crate) fn persona_turn(&self) -> &PersonaTurn { + pub fn persona_turn(&self) -> &PersonaTurn { &self.turn } - pub(crate) fn subject(&self) -> SubjectId { + pub fn subject(&self) -> SubjectId { self.subject } - pub(crate) fn bound_scope_digest(&self) -> &str { + pub fn bound_scope_digest(&self) -> &str { &self.bound_scope_digest } - pub(crate) fn fresh_scope_digest(&self) -> Option<&str> { + pub fn fresh_scope_digest(&self) -> Option<&str> { self.fresh_scope_digest.as_ref().map(ScopeDigest::as_str) } - pub(crate) fn gap(&self) -> &TranslationGapSummary { + pub fn gap(&self) -> &TranslationGapSummary { &self.gap } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub(crate) enum ControllerPendingReason { +pub enum ControllerPendingReason { /// The exact persisted connector invocation can be presented again. The /// connector decides whether this is an admitted completed replay. InferenceRetryable, @@ -2494,7 +2462,7 @@ fn inference_pending_reason(error: &ControllerError) -> Option None, - ControllerError::Inference { source, .. } if source.recovery_required() => { + ControllerError::Inference { source, .. } if source.requires_recovery() => { Some(ControllerPendingReason::InferenceRecoveryRequired) } ControllerError::Inference { .. } => Some(ControllerPendingReason::InferenceRetryable), @@ -2506,27 +2474,27 @@ fn inference_pending_reason(error: &ControllerError) -> Option ControllerMode { + pub fn mode(&self) -> ControllerMode { ControllerMode::NarrativePersona } - pub(crate) fn reason(&self) -> ControllerPendingReason { + pub fn reason(&self) -> ControllerPendingReason { self.reason } - pub(crate) fn persona_prose(&self) -> Option<&str> { + pub fn persona_prose(&self) -> Option<&str> { self.work.persona_prose() } } #[derive(Debug)] -pub(crate) struct OperationalDecision { +pub struct OperationalDecision { capture: OperationalCapture, submission: SubmissionDisposition, } @@ -2540,13 +2508,13 @@ impl OperationalDecision { &self.submission } - pub(crate) fn into_parts(self) -> (OperationalCapture, SubmissionDisposition) { + pub fn into_parts(self) -> (OperationalCapture, SubmissionDisposition) { (self.capture, self.submission) } } #[derive(Debug)] -pub(crate) enum OperationalRun { +pub enum OperationalRun { Completed(OperationalDecision), Pending(OperationalPending), } @@ -2555,50 +2523,50 @@ pub(crate) enum OperationalRun { /// subject's controller mode names, and its outcome is that lane's own: the /// detail path is not wrapped, re-shaped, or summarised. #[derive(Debug)] -pub(crate) enum CellRun { +pub enum CellRun { Narrative(NarrativeRun), Operational(OperationalRun), Grouped(GroupedRun), } #[derive(Debug)] -pub(crate) struct ConstituentSubmission { +pub struct ConstituentSubmission { pub(crate) subject: SubjectId, - pub(crate) submission: SubmissionDisposition, + pub submission: SubmissionDisposition, } /// One coarse cell's outcome: one inference, N ordinary one-opportunity /// submissions, and whatever the decode could not attribute. #[derive(Debug)] -pub(crate) struct GroupedRun { - pub(crate) cell: CellId, - pub(crate) resolution: Resolution, - pub(crate) submissions: Vec, - pub(crate) needs: Vec, +pub struct GroupedRun { + pub cell: CellId, + pub resolution: Resolution, + pub submissions: Vec, + pub needs: Vec, /// Set when the run stopped before every constituent was submitted. The /// constituents already in `submissions` are committed; the rest resume from /// the persisted row against the kernel's idempotency ledger. - pub(crate) pending: Option, + pub pending: Option, } #[derive(Debug)] -pub(crate) struct OperationalPending { +pub struct OperationalPending { work: OperationalCheckpoint, reason: ControllerPendingReason, } impl OperationalPending { - pub(crate) fn mode(&self) -> ControllerMode { + pub fn mode(&self) -> ControllerMode { ControllerMode::OperationalAgent } - pub(crate) fn reason(&self) -> ControllerPendingReason { + pub fn reason(&self) -> ControllerPendingReason { self.reason } } #[derive(Debug, Error)] -pub(crate) enum ControllerError { +pub enum ControllerError { #[error("world snapshot failed: {0}")] Snapshot(MailboxError), #[error("subject has no exact {expected:?} opportunity")] @@ -2638,7 +2606,7 @@ impl ControllerError { /// Quarantine only the cognition organ. WorldMailbox and AppSession remain /// authoritative and available; this error must never become a daemon-wide /// fatal signal. - pub(crate) fn requires_quarantine(&self) -> bool { + pub fn requires_quarantine(&self) -> bool { match self { Self::WorkPersistence(_) | Self::Serialization(_) => true, Self::Inference { source, .. } => source.integrity_was_violated(), @@ -2668,7 +2636,7 @@ enum ControllerWorldSubmission { Pending(ControllerPendingReason), } -pub(crate) struct ControllerRunner { +pub struct ControllerRunner { mailbox: ControllerPort, /// The authoring lane's own narrowing of the same mailbox. It is opened /// here because this constructor is where a whole `WorldMailbox` is @@ -2688,7 +2656,7 @@ impl ControllerRunner { /// narrows to a `ControllerPort` before the runner ever sees it, so nothing /// inside this module can reach past the five requests a controller lane /// makes. - pub(crate) fn open( + pub fn open( mailbox: WorldMailbox, inference: Arc, work: Arc, @@ -2709,7 +2677,7 @@ impl ControllerRunner { /// The authoring lane, built from the same ports the decision lanes use. /// `NullEvidenceSource` is what production supplies until a retrieval organ /// lands; the bound that buys is stated where the source is defined. - pub(crate) fn elaborator(&self) -> ElaborationRunner { + pub fn elaborator(&self) -> ElaborationRunner { ElaborationRunner::new( self.elaboration.clone(), Arc::clone(&self.inference), @@ -2725,7 +2693,7 @@ impl ControllerRunner { /// asked for the work, and the evidence source is supplied by that request /// too. `NullEvidenceSource` stays the elaborator's; there is no mode flag /// choosing between them. - pub(crate) fn seeder( + pub fn seeder( &self, port: SeedPort, evidence: Arc, @@ -2741,11 +2709,11 @@ impl ControllerRunner { ) } - pub(crate) async fn custody_probe(&self) -> Result { + pub async fn custody_probe(&self) -> Result { Ok(self.work.custody_probe().await?) } - pub(crate) async fn run_narrative( + pub async fn run_narrative( &self, command_id: CommandId, opportunity: &DecisionOpportunity, @@ -2835,7 +2803,7 @@ impl ControllerRunner { .await } - pub(crate) async fn run_operational( + pub async fn run_operational( &self, command_id: CommandId, opportunity: &DecisionOpportunity, @@ -3054,7 +3022,7 @@ impl ControllerRunner { /// One cell's cognition. It receives a `&Cell` and never a `Cover`: it /// cannot see other cells, the budget, or the agency graph, and the tick /// index reaches it only as an opaque value threaded into id derivation. - pub(crate) async fn run_cell(&self, cell: &Cell) -> Result { + pub async fn run_cell(&self, cell: &Cell) -> Result { match cell { Cell::Singleton { id, tick, member } => { // The driver's singleton turn takes a derived id, so it is as @@ -6408,9 +6376,9 @@ mod tests { } } use super::*; - use crate::world::elaboration::{EvidenceError, EvidenceQuery, EvidenceReceipt}; - use crate::world::patch::{RECORD_GAP_PATCH_TOOL, kernel_speak_entry, kernel_speak_grant}; - use crate::world::{ + use crate::elaboration::{EvidenceError, EvidenceQuery, EvidenceReceipt}; + use crate::patch::{RECORD_GAP_PATCH_TOOL, kernel_speak_entry, kernel_speak_grant}; + use crate::{ CommitmentKind, CoverBudget, CreateJurisdictionIntent, CreateWorldIntent, EntityDeclaration, EntityId, EvidenceRef, JurisdictionKey, Ref, SeedOutcome, SeedPort, TickMinutes, WorldScaleIntentRef, derive_cover, @@ -6424,7 +6392,7 @@ mod tests { entry: kernel_speak_entry(), } } - use crate::world::{ + use crate::{ AuthenticatedCaller, CallerId, CommandBody, CommandEnvelope, ControllerId, CreateWorld, DecisionScope, Declaration, DraftHandle, NewController, PrincipalId, ScopeDigest, SubjectDeclaration, SubjectKind, WorldId, WorldPatch, WorldPhase, @@ -6715,7 +6683,7 @@ mod tests { opportunities: vec![opportunity.clone()], state_digest: "sha256:projector-must-not-see-this-digest".into(), last_commit_digest: Some("sha256:projector-must-not-see-the-commit".into()), - now: crate::world::FictionalMinutes::default(), + now: crate::FictionalMinutes::default(), boundaries: Vec::new(), scale_deficit: Vec::new(), }; @@ -6772,8 +6740,8 @@ mod tests { /// except the listener's. #[test] fn a_subject_does_not_perceive_speech_it_was_not_in_reach_of() { - use crate::world::tests::{auth_principal, command, opportunity_for, owner, speech_world}; - use crate::world::{ + use crate::tests::{auth_principal, command, opportunity_for, owner, speech_world}; + use crate::{ AuthenticatedCaller, DecisionInvocation, Role, RoleBinding, Statement, SubmitReceipt, Target, WorldKernel, }; @@ -6781,7 +6749,7 @@ mod tests { let directory = tempfile::tempdir().unwrap(); let mut kernel = WorldKernel::create( directory.path().join("world.cc"), - crate::world::tests::creation(CommandId::new(), "Leakage"), + crate::tests::creation(CommandId::new(), "Leakage"), &auth_principal(owner()), ) .expect("a created world") @@ -6863,7 +6831,7 @@ mod tests { /// and a route that touches neither endpoint stay out of the surface. #[test] fn soul_the_typed_view_exposes_only_the_actors_place_and_incident_routes() { - use crate::world::{ + use crate::{ AccessKind, Cost, EdgeId, EntityId, PlaceSnapshot, Quantity, ResourceSnapshot, RouteSnapshot, }; @@ -6979,7 +6947,7 @@ mod tests { opportunities: vec![opportunity.clone()], state_digest: "sha256:state".into(), last_commit_digest: None, - now: crate::world::FictionalMinutes::default(), + now: crate::FictionalMinutes::default(), boundaries: Vec::new(), scale_deficit: Vec::new(), }; @@ -7046,18 +7014,18 @@ mod tests { // target, and no other subject's jurisdiction. let mut civic = custodial; let hall = EntityId::issue(); - civic.subject.components.authority = BTreeSet::from([crate::world::AuthorityGrant { - kind: crate::world::AuthorityKindName("levy".into()), - over: crate::world::AuthorityTarget::PlaceSubtree(hall), + civic.subject.components.authority = BTreeSet::from([crate::AuthorityGrant { + kind: crate::AuthorityKindName("levy".into()), + over: crate::AuthorityTarget::PlaceSubtree(hall), }]); civic.subject.offices_held = vec![OfficeSnapshot { institution: other_id, - office: crate::world::OfficeName("warden".into()), + office: crate::OfficeName("warden".into()), incumbent: Some(actor_id), authority: civic.subject.components.authority.clone(), }]; - civic.subject.redress = vec![crate::world::ForumSnapshot { - grievance: crate::world::GrievanceKindName("seizure".into()), + civic.subject.redress = vec![crate::ForumSnapshot { + grievance: crate::GrievanceKindName("seizure".into()), forum: other_id, }]; let view = civic.typed_view().unwrap(); @@ -7119,7 +7087,7 @@ mod tests { #[test] fn the_generated_tool_catalog_equals_the_granted_catalog() { - use crate::world::{ + use crate::{ Affordance, AffordanceKindName, Bounds, ComponentOpKind, EffectSlot, OutcomeBand, Quantity, RefKind, Role, RoleSpec, }; @@ -7135,7 +7103,7 @@ mod tests { }, RoleSpec { role: Role("resource".into()), - kind: RefKind::Entity(crate::world::EntityKind::Resource), + kind: RefKind::Entity(crate::EntityKind::Resource), }, ], preconditions: Vec::new(), @@ -7296,7 +7264,7 @@ mod tests { #[async_trait] impl InferencePort for RecordingPort { fn prepare(&self, request: InferenceRequest) -> Result { - fixture_prepared_inference(request) + PreparedInference::prepare("ghostlight-controller-test", 4_102_444_800_000, request) } async fn infer( @@ -7341,8 +7309,8 @@ mod tests { /// revision, and nothing of a bystander's state. #[test] fn a_seeded_person_sees_its_own_life_and_no_ids() { - use crate::world::patch::{CommitmentKey, CommitmentKind, PressureMagnitude, PressureSource}; - use crate::world::{ + use crate::patch::{CommitmentKey, CommitmentKind, PressureMagnitude, PressureSource}; + use crate::{ AccessKind, CommitmentSnapshot, Cost, EdgeId, EntityId, FictionalMinutes, PlaceSnapshot, PressureSnapshot, Quantity, ResourceSnapshot, RouteSnapshot, }; @@ -7410,7 +7378,7 @@ mod tests { }], ); let mut actor = actor; - actor.material = Some(crate::world::PersonaMaterial { + actor.material = Some(crate::PersonaMaterial { values: vec![Statement::new("A debt paid late is paid twice.").unwrap()], voice: Statement::new("Short sentences, never a wasted one.").unwrap(), memories: vec![Statement::new("The gate froze the winter the ferry sank.").unwrap()], @@ -7562,13 +7530,13 @@ mod tests { } } - fn fixture_route(from: EntityId, to: EntityId) -> crate::world::patch::EdgeRecord { - crate::world::patch::EdgeRecord::Route { + fn fixture_route(from: EntityId, to: EntityId) -> crate::patch::EdgeRecord { + crate::patch::EdgeRecord::Route { label: "a fixture route".into(), from, to, - access: crate::world::AccessKind::Public, - cost: crate::world::Cost(6), + access: crate::AccessKind::Public, + cost: crate::Cost(6), open: true, } } @@ -7609,7 +7577,7 @@ mod tests { opportunity: opportunity.clone(), granted, completed, - invocation: fixture_prepared_inference(request).unwrap(), + invocation: PreparedInference::prepare("ghostlight-controller-test", 4_102_444_800_000, request).unwrap(), }) } @@ -7939,8 +7907,8 @@ mod tests { .unwrap() }; let expiry = 4_102_444_800_000; - let connector_side = prepare_invocation("ghostlight-runtime", expiry, build()).unwrap(); - let sdk_side = prepare_invocation("ghostlight-runtime", expiry, build()).unwrap(); + let connector_side = PreparedInference::prepare("ghostlight-runtime", expiry, build()).unwrap(); + let sdk_side = PreparedInference::prepare("ghostlight-runtime", expiry, build()).unwrap(); assert_eq!(connector_side, sdk_side); assert!(prepared_matches_request(&sdk_side, &build(), command_id, 0)); } @@ -8009,10 +7977,10 @@ mod tests { // The two clock stamps the two ports would make. Identity must not // depend on either of them. for (purpose, round, build) in builders { - let sdk_side = prepare_invocation("ghostlight-sdk-runtime", 4_102_444_800_000, build()) + let sdk_side = PreparedInference::prepare("ghostlight-sdk-runtime", 4_102_444_800_000, build()) .expect("the SDK-shaped preparation builds"); let connector_side = - prepare_invocation("ghostlight-sdk-runtime", 4_000_000_000_000, build()) + PreparedInference::prepare("ghostlight-sdk-runtime", 4_000_000_000_000, build()) .expect("the connector-shaped preparation builds"); assert_eq!(sdk_side.purpose, purpose); assert!( @@ -8063,7 +8031,7 @@ mod tests { opportunity: opportunity.clone(), granted: vec![speak_snapshot(opportunity.affordance_ids[0])], completed: Vec::new(), - invocation: prepare_invocation("ghostlight-sdk-runtime", 4_102_444_800_000, request) + invocation: PreparedInference::prepare("ghostlight-sdk-runtime", 4_102_444_800_000, request) .unwrap(), }); assert!( @@ -8364,7 +8332,7 @@ mod tests { opportunity: opportunity.clone(), granted: vec![speak_snapshot(opportunity.affordance_ids[0])], completed, - invocation: fixture_prepared_inference(request).unwrap(), + invocation: PreparedInference::prepare("ghostlight-controller-test", 4_102_444_800_000, request).unwrap(), }) } @@ -9397,7 +9365,7 @@ mod tests { #[async_trait] impl InferencePort for ElaborationScript { fn prepare(&self, request: InferenceRequest) -> Result { - fixture_prepared_inference(request) + PreparedInference::prepare("ghostlight-controller-test", 4_102_444_800_000, request) } async fn infer( @@ -9463,12 +9431,12 @@ mod tests { kind: EntityKind::Place, container: Some(Ref::Draft(DraftHandle::new("commons"))), }), - Declaration::Route(crate::world::RouteDeclaration { + Declaration::Route(crate::RouteDeclaration { handle: DraftHandle::new("lane"), label: "The Long Lane".into(), from: Ref::Draft(DraftHandle::new("commons")), to: Ref::Draft(DraftHandle::new("road")), - access: crate::world::AccessKind::Public, + access: crate::AccessKind::Public, cost: Cost(1), }), Declaration::Subject(SubjectDeclaration { @@ -9581,7 +9549,7 @@ mod tests { let outcome = first.step(jurisdiction).await.unwrap(); assert_eq!( outcome, - crate::world::elaboration::ElaborationOutcome::Rejected + crate::elaboration::ElaborationOutcome::Rejected ); drop(first); @@ -9636,7 +9604,7 @@ mod tests { let outcome = second.step(jurisdiction).await.unwrap(); assert_eq!( outcome, - crate::world::elaboration::ElaborationOutcome::Committed + crate::elaboration::ElaborationOutcome::Committed ); // One identity across the whole session, and the answered boundary is @@ -9693,7 +9661,7 @@ mod tests { let outcome = runner.step(JurisdictionKey::Uncovered).await.unwrap(); assert_eq!( outcome, - crate::world::elaboration::ElaborationOutcome::Clean + crate::elaboration::ElaborationOutcome::Clean ); assert!(script.seen.lock().unwrap().is_empty()); assert!(store.work.lock().unwrap().is_empty()); @@ -10560,7 +10528,7 @@ mod tests { task.await.unwrap(); let replayed = - crate::world::WorldKernel::open(&path, live.world_id).expect("the world store replays"); + crate::WorldKernel::open(&path, live.world_id).expect("the world store replays"); assert_eq!( replayed.state.state_digest, live.state_digest, "a grouped tick did not replay to the same digest" @@ -10579,7 +10547,7 @@ mod tests { // A second open is the same open: replay is a function of the store. let expected = replayed.state.clone(); drop(replayed); - let again = crate::world::WorldKernel::open(&path, live.world_id) + let again = crate::WorldKernel::open(&path, live.world_id) .expect("the world store replays twice"); assert_eq!(again.state, expected); } @@ -10711,7 +10679,7 @@ mod tests { mailbox: WorldMailbox, task: tokio::task::JoinHandle<()>, owner: PrincipalId, - principal: crate::app_session::VerifiedPrincipalEvidence, + principal: crate::VerifiedPrincipalEvidence, sere: EntityId, speak: AffordanceId, } @@ -10787,7 +10755,7 @@ mod tests { mailbox, task, owner, - principal: crate::app_session::VerifiedPrincipalEvidence::fixture( + principal: crate::VerifiedPrincipalEvidence::fixture( "seed-owner", Utc::now() + chrono::Duration::hours(1), ), @@ -10985,7 +10953,7 @@ mod tests { // on anyone else's evidence is refused by the reducer. let stranger = SeedPort::new( fixture.mailbox.clone(), - crate::app_session::VerifiedPrincipalEvidence::fixture( + crate::VerifiedPrincipalEvidence::fixture( "not-the-owner", Utc::now() + chrono::Duration::hours(1), ), @@ -11470,7 +11438,7 @@ mod tests { let directory = tempfile::tempdir().unwrap(); let path = directory.path().join("world.cc"); let owner = PrincipalId::new("seed-owner"); - let principal = crate::app_session::VerifiedPrincipalEvidence::fixture( + let principal = crate::VerifiedPrincipalEvidence::fixture( "seed-owner", Utc::now() + chrono::Duration::hours(1), ); @@ -11731,7 +11699,7 @@ mod tests { assert!( mismatches.iter().any(|mismatch| matches!( mismatch, - crate::world::Mismatch::UnknownJurisdictionRoot { .. } + crate::Mismatch::UnknownJurisdictionRoot { .. } )), "{mismatches:?}" ); @@ -11761,7 +11729,7 @@ mod tests { assert!( mismatches.iter().any(|mismatch| matches!( mismatch, - crate::world::Mismatch::ScaleWeightsExceedWhole + crate::Mismatch::ScaleWeightsExceedWhole )), "{mismatches:?}" ); @@ -11885,11 +11853,11 @@ mod tests { snapshot.world_id, WorldPatch { declarations: Vec::new(), - operations: vec![crate::world::patch::ComponentOp::CreateCommitment { + operations: vec![crate::patch::ComponentOp::CreateCommitment { subject: Ref::Existing(owner_subject), counterparty: None, kind: CommitmentKind::Goal, - due: crate::world::FictionalMinutes(600), + due: crate::FictionalMinutes(600), period: None, checks: Vec::new(), statement: Statement::new("What was promised, as the promisor would say it.").unwrap(), @@ -11949,7 +11917,7 @@ mod tests { expected_revision: snapshot.revision, caller: CallerId::Principal(fixture.owner.clone()), body: CommandBody::AdmitPatch { - answers: Some(crate::world::PatchAnswer::Deficit( + answers: Some(crate::PatchAnswer::Deficit( JurisdictionKey::PlaceSubtree(fixture.sere), )), patch: shed(), @@ -12467,7 +12435,7 @@ mod tests { } // ---------------------------------------------------------------- step 9 - use crate::world::{ControllerAssignment, ScopePreimage, digest as world_digest}; + use crate::{ControllerAssignment, ScopePreimage, digest as world_digest}; use ghostlight_persona_projection::PersonaTurnIntegrityError; use std::sync::atomic::AtomicUsize; @@ -12494,7 +12462,7 @@ mod tests { /// every remaining un-authored cause: a transfer out of a neighbour's /// custody, a route closing under the actor's feet, a grant revoked out /// from under it, a commitment that names a counterparty. - Ops(Vec), + Ops(Vec), /// An owner patch witnessing a pre-declared fact over `place`. Unlike a /// neighbour's speech, this names no speaker and writes no `minted_at` /// row: `KnowledgeSource::Witnessed` is not `Told`, so @@ -12582,11 +12550,11 @@ mod tests { patch: WorldPatch { declarations: Vec::new(), operations: vec![ - crate::world::patch::ComponentOp::CreateCommitment { + crate::patch::ComponentOp::CreateCommitment { subject: Ref::Existing(*subject), counterparty: None, kind: CommitmentKind::Goal, - due: crate::world::FictionalMinutes(600), + due: crate::FictionalMinutes(600), period: None, checks: Vec::new(), statement: Statement::new("What was promised, as the promisor would say it.").unwrap(), @@ -12641,7 +12609,7 @@ mod tests { answers: None, patch: WorldPatch { declarations: Vec::new(), - operations: vec![crate::world::patch::ComponentOp::Witness { + operations: vec![crate::patch::ComponentOp::Witness { fact: Ref::Existing(*fact), place: Ref::Existing(*place), confidence: Confidence::Certain, @@ -12675,7 +12643,7 @@ mod tests { #[async_trait] impl InferencePort for InterruptingPort { fn prepare(&self, request: InferenceRequest) -> Result { - fixture_prepared_inference(request) + PreparedInference::prepare("ghostlight-controller-test", 4_102_444_800_000, request) } async fn infer( @@ -13139,11 +13107,11 @@ mod tests { kind: EntityKind::Place, container: None, }), - Declaration::Fact(crate::world::patch::FactDeclaration { + Declaration::Fact(crate::patch::FactDeclaration { handle: DraftHandle::new("bell"), label: "A bell rings over the yard".into(), statement: Statement::new("A bell rings over the yard.").unwrap(), - standing: crate::world::patch::FactStandingRef::Canonical { + standing: crate::patch::FactStandingRef::Canonical { evidence: ledger.clone(), }, }), @@ -13179,10 +13147,10 @@ mod tests { // fact here rather than through a second patch — a // live `EntityId` to read back, not a fact this test's // actor is meant to already know. - operations: vec![crate::world::patch::ComponentOp::AcquireKnowledge { + operations: vec![crate::patch::ComponentOp::AcquireKnowledge { subject: Ref::Draft(DraftHandle::new("subject1")), fact: Ref::Draft(DraftHandle::new("bell")), - source: crate::world::patch::AuthoredSource::Witnessed, + source: crate::patch::AuthoredSource::Witnessed, confidence: Confidence::Certain, }], evidence: vec![ledger], @@ -13410,7 +13378,7 @@ mod tests { opportunity: moved.clone(), granted: vec![speak_snapshot(moved.affordance_ids[0])], completed: Vec::new(), - invocation: fixture_prepared_inference( + invocation: PreparedInference::prepare("ghostlight-controller-test", 4_102_444_800_000, interpreter_request(command_id, 1, "interpreter", Vec::new()).unwrap(), ) .unwrap(), @@ -13618,8 +13586,8 @@ mod tests { // persisted row actually holds, and over the asymmetry the deleted stage // detectors left between the two detail lanes. - use crate::world::patch::{AuthorityGrantRef, AuthorityTargetRef, RouteDeclaration}; - use crate::world::{AccessKind, AuthorityKindName, AuthorityTarget}; + use crate::patch::{AuthorityGrantRef, AuthorityTargetRef, RouteDeclaration}; + use crate::{AccessKind, AuthorityKindName, AuthorityTarget}; /// An Active world carrying the furniture every un-authored cause needs: a /// route the actor stands on, a resource its neighbour holds, and a @@ -13696,13 +13664,13 @@ mod tests { inhabitant("neighbour", "Subject 1", NewController::OperationalAgent), ], operations: vec![ - crate::world::patch::ComponentOp::Admit { + crate::patch::ComponentOp::Admit { holder: Ref::Draft(DraftHandle::new("neighbour")), resource: Ref::Draft(DraftHandle::new("tithe")), qty: Quantity(5), evidence: ledger.clone(), }, - crate::world::patch::ComponentOp::GrantAuthority { + crate::patch::ComponentOp::GrantAuthority { holder: Ref::Draft(DraftHandle::new("actor")), grant: AuthorityGrantRef { kind: AuthorityKindName("levy".into()), @@ -13817,7 +13785,7 @@ mod tests { "a transfer", "- what this person holds changed", (|world| { - MidTurnCommit::Ops(vec![crate::world::patch::ComponentOp::Transfer { + MidTurnCommit::Ops(vec![crate::patch::ComponentOp::Transfer { from: Ref::Existing(world.neighbour), to: Ref::Existing(world.actor), resource: Ref::Existing(world.tithe), @@ -13826,7 +13794,7 @@ mod tests { }) as Cause, ), ("a closed route", "- a way out of here changed", |world| { - MidTurnCommit::Ops(vec![crate::world::patch::ComponentOp::CloseRoute { + MidTurnCommit::Ops(vec![crate::patch::ComponentOp::CloseRoute { route: Ref::Existing(world.ramp), }]) }), @@ -13834,7 +13802,7 @@ mod tests { "a revoked grant", "- what this person is authorized over changed", |world| { - MidTurnCommit::Ops(vec![crate::world::patch::ComponentOp::RevokeAuthority { + MidTurnCommit::Ops(vec![crate::patch::ComponentOp::RevokeAuthority { holder: Ref::Existing(world.actor), grant: AuthorityGrantRef { kind: AuthorityKindName("levy".into()), @@ -13918,11 +13886,11 @@ mod tests { source, "Then it is agreed.", 1, - MidTurnCommit::Ops(vec![crate::world::patch::ComponentOp::CreateCommitment { + MidTurnCommit::Ops(vec![crate::patch::ComponentOp::CreateCommitment { subject: Ref::Existing(world.actor), counterparty: Some(Ref::Existing(world.neighbour)), kind: CommitmentKind::Obligation, - due: crate::world::FictionalMinutes(900), + due: crate::FictionalMinutes(900), period: None, checks: Vec::new(), statement: Statement::new("What was promised, as the promisor would say it.").unwrap(), @@ -13983,13 +13951,13 @@ mod tests { // Checked once on the fixture as declared, then again after a transfer // and a route closure have moved two more component kinds. for cause in [ - MidTurnCommit::Ops(vec![crate::world::patch::ComponentOp::Transfer { + MidTurnCommit::Ops(vec![crate::patch::ComponentOp::Transfer { from: Ref::Existing(world.neighbour), to: Ref::Existing(world.actor), resource: Ref::Existing(world.tithe), qty: Quantity(1), }]), - MidTurnCommit::Ops(vec![crate::world::patch::ComponentOp::CloseRoute { + MidTurnCommit::Ops(vec![crate::patch::ComponentOp::CloseRoute { route: Ref::Existing(world.ramp), }]), ] { @@ -14115,7 +14083,7 @@ mod tests { // Then the world moves under both rows. apply_mid_turn( &mailbox, - &MidTurnCommit::Ops(vec![crate::world::patch::ComponentOp::CloseRoute { + &MidTurnCommit::Ops(vec![crate::patch::ComponentOp::CloseRoute { route: Ref::Existing(world.ramp), }]), ) diff --git a/crates/ghostlight/src/cover.rs b/crates/ghostlight/src/cover.rs index 711fabb..02c0adb 100644 --- a/crates/ghostlight/src/cover.rs +++ b/crates/ghostlight/src/cover.rs @@ -37,7 +37,7 @@ const COVER_NAMESPACE: &str = "ghostlight.cover.v1"; Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Default, )] #[serde(transparent)] -pub(crate) struct TickIndex(pub(crate) u64); +pub struct TickIndex(pub u64); impl TickIndex { /// Floor division by the span one `AdvanceTime` carries. The driver submits @@ -52,7 +52,7 @@ impl TickIndex { /// nowhere in world state: representation is a compute budget, not world truth. #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(tag = "resolution", rename_all = "snake_case")] -pub(crate) enum Resolution { +pub enum Resolution { Detail, Coarse { constituents: usize }, } @@ -62,7 +62,7 @@ pub(crate) enum Resolution { /// member)`; no `uuid/v5`. #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] #[serde(transparent)] -pub(crate) struct CellId(Uuid); +pub struct CellId(Uuid); impl std::fmt::Display for CellId { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -113,15 +113,15 @@ pub(crate) fn cell_constituent_uuid( } #[derive(Clone, Debug, PartialEq, Eq)] -pub(crate) struct Constituent { - pub(crate) subject: SubjectId, +pub struct Constituent { + pub subject: SubjectId, pub(crate) opportunity: DecisionOpportunity, } /// One inference's worth of the world. A singleton keeps the detail path and its /// membrane; a group is one inference over N labeled, never-unioned views. #[derive(Clone, Debug, PartialEq, Eq)] -pub(crate) enum Cell { +pub enum Cell { Singleton { id: CellId, tick: TickIndex, @@ -147,27 +147,27 @@ impl Cell { /// Derived, disposable, dropped at the end of a tick. #[derive(Clone, Debug, PartialEq, Eq)] -pub(crate) struct Cover { - pub(crate) tick: TickIndex, - pub(crate) cells: Vec, +pub struct Cover { + pub tick: TickIndex, + pub cells: Vec, /// True when the constituent cap could not hold every active subject inside /// the cell budget, so the cap yielded and every cell grew. "Every active /// subject is in the cover every tick" is an ontology invariant; the cap is /// a prompt-size preference. Rendered read-only: it is the honest signal /// that the world outgrew its prompt budget rather than a truncation nobody /// sees. - pub(crate) oversubscribed: bool, + pub oversubscribed: bool, } impl Cover { - pub(crate) fn singletons(&self) -> usize { + pub fn singletons(&self) -> usize { self.cells .iter() .filter(|cell| matches!(cell, Cell::Singleton { .. })) .count() } - pub(crate) fn groups(&self) -> usize { + pub fn groups(&self) -> usize { self.cells.len() - self.singletons() } } @@ -175,19 +175,19 @@ impl Cover { /// How many inferences one tick may spend, and how large one prompt may get. /// Two budgets, deliberately not one: only the second bounds tokens. #[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub(crate) struct CoverBudget { +pub struct CoverBudget { /// B: cells per tick. - pub(crate) cells: u16, + pub cells: u16, /// C: the per-cell constituent cap. At least two, or a group is a singleton /// with extra steps. - pub(crate) constituent_cap: u16, + pub constituent_cap: u16, /// U: singleton slots reserved for the head of the attention order. The /// rest of the singleton slots carry the rotation window. - pub(crate) urgency_slots: u16, + pub urgency_slots: u16, } #[derive(Debug, thiserror::Error, PartialEq, Eq)] -pub(crate) enum CoverBudgetError { +pub enum CoverBudgetError { #[error("cover cell budget must be at least one")] NoCells, #[error("cover constituent cap must be at least two")] @@ -195,7 +195,7 @@ pub(crate) enum CoverBudgetError { } impl CoverBudget { - pub(crate) fn validated(self) -> Result { + pub fn validated(self) -> Result { if self.cells < 1 { return Err(CoverBudgetError::NoCells); } @@ -212,7 +212,7 @@ impl CoverBudget { /// graph, so a controller organ that could read adjacency fails to compile /// rather than failing a test. #[derive(Clone, Debug, Default, PartialEq, Eq)] -pub(crate) struct AgencyGraph { +pub struct AgencyGraph { /// Ascending. Exactly the subjects with a non-Human controller assignment. pub(crate) subjects: Vec, /// Undirected, canonicalised `(lo, hi)` with `lo < hi`. @@ -260,7 +260,7 @@ impl AgencyGraph { /// attention debt, then id) and is never re-sorted and never filtered here: a /// Human opportunity is skipped because a human turn is not an inference, which /// is a fact about cognition rather than a scheduling preference. -pub(crate) fn derive_cover( +pub fn derive_cover( world: WorldId, now: FictionalMinutes, tick_minutes: u32, @@ -545,7 +545,7 @@ fn components(rest: &[&DecisionOpportunity], graph: &AgencyGraph) -> Vec SubjectId { serde_json::from_value(serde_json::json!(Uuid::from_u128(index).to_string())) diff --git a/crates/ghostlight/src/elaboration.rs b/crates/ghostlight/src/elaboration.rs index ab37bac..c034fcc 100644 --- a/crates/ghostlight/src/elaboration.rs +++ b/crates/ghostlight/src/elaboration.rs @@ -54,7 +54,7 @@ const ELABORATION_INSTRUCTIONS: &str = "Use only the supplied tools to author st /// The answer a session is bound to, plus the ancestry it was built against. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] -pub(super) struct ElaboratorSession { +pub struct ElaboratorSession { pub(super) world_id: WorldId, pub(super) jurisdiction: JurisdictionKey, pub(super) answer: PatchAnswer, @@ -73,7 +73,7 @@ pub(super) struct ElaboratorSession { /// the kernel, not the model. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(tag = "stage", rename_all = "snake_case")] -pub(super) enum ElaborationCheckpoint { +pub enum ElaborationCheckpoint { ElaboratorInFlight { command_id: CommandId, session: ElaboratorSession, @@ -109,7 +109,7 @@ pub(super) enum ElaborationCheckpoint { /// draft there, and the refusal is the user turn that follows that round. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] -pub(super) struct Refusal { +pub struct Refusal { pub(super) after_round: usize, pub(super) mismatches: Vec, } @@ -264,18 +264,18 @@ fn refusal_reopens( #[derive(Clone, Debug, Error, PartialEq, Eq)] #[error("{detail}")] -pub(crate) struct EvidenceError { +pub struct EvidenceError { detail: String, } /// The referents the answer names, as world labels. #[derive(Clone, Debug, PartialEq, Eq)] -pub(crate) struct EvidenceQuery { +pub struct EvidenceQuery { pub(crate) referents: Vec, } #[derive(Clone, Debug, PartialEq, Eq)] -pub(crate) struct EvidenceReceipt { +pub struct EvidenceReceipt { /// The exact reference an admitted patch may carry. pub(crate) reference: EvidenceRef, /// What the elaborator is allowed to read. Prompt material, never state. @@ -289,7 +289,7 @@ pub(crate) struct EvidenceReceipt { /// string is canonical and that an `Admit`'s reference is listed in the same /// patch. #[async_trait] -pub(crate) trait EvidenceSource: Send + Sync { +pub trait EvidenceSource: Send + Sync { async fn retrieve(&self, query: &EvidenceQuery) -> Result, EvidenceError>; } @@ -343,7 +343,7 @@ pub(super) enum ElaborationOutcome { /// One jurisdiction's authoring loop. Holds no state between steps; every field /// is a port or an identity. -pub(crate) struct ElaborationRunner { +pub struct ElaborationRunner { mailbox: ElaborationPort, inference: Arc, evidence: Arc, @@ -604,7 +604,7 @@ impl ElaborationRunner { /// the uncovered residual. Sequential: a boundary binds to its own digest, /// so the loops' logical independence is preserved without eight tasks /// against one connector and a capacity-32 mailbox. - pub(crate) async fn sweep(&self) -> Result<(), ControllerError> { + pub async fn sweep(&self) -> Result<(), ControllerError> { let snapshot = self.mailbox.snapshot().await.map_err(snapshot_error)?; let mut jurisdictions: Vec = snapshot .scale_deficit @@ -1303,7 +1303,7 @@ const SEED_INSTRUCTIONS: &str = "Use only the supplied tools to author living st /// answer field it must never submit would be a field that lies. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] -pub(super) struct SeedSession { +pub struct SeedSession { pub(super) world_id: WorldId, pub(super) jurisdiction: JurisdictionKey, pub(super) kind: SubjectKind, @@ -1331,7 +1331,7 @@ fn seed_command_id(session: &SeedSession) -> Result /// the conversation does not produce. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(tag = "stage", rename_all = "snake_case")] -pub(super) enum SeedCheckpoint { +pub enum SeedCheckpoint { SeedInFlight { command_id: CommandId, session: SeedSession, @@ -1489,7 +1489,7 @@ pub(super) fn valid_seed_progression(existing: &SeedCheckpoint, next: &SeedCheck /// What one seed `step` did, for the transport that asked for it. #[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub(crate) enum SeedOutcome { +pub enum SeedOutcome { /// Every row's deficit is zero: the terminating condition. Clean, /// The world is not Draft. Seeding is Draft's lane exactly as elaboration @@ -1508,7 +1508,7 @@ pub(crate) enum SeedOutcome { } impl SeedOutcome { - pub(crate) fn name(self) -> &'static str { + pub fn name(self) -> &'static str { match self { Self::Clean => "clean", Self::NotDraft => "not_draft", @@ -1523,7 +1523,7 @@ impl SeedOutcome { /// The Draft authoring loop. Holds no state between steps; every field is a /// port, an identity, or the owner's own one-sentence brief. -pub(crate) struct SeedRunner { +pub struct SeedRunner { mailbox: SeedPort, inference: Arc, evidence: Arc, @@ -1743,7 +1743,7 @@ impl SeedRunner { /// One bounded sweep. The budget is the caller's, because who may spend a /// paid endpoint is a transport question and how far a deficit has fallen /// is not. - pub(crate) async fn sweep(&self, sessions: usize) -> Result { + pub async fn sweep(&self, sessions: usize) -> Result { let mut last = SeedOutcome::Clean; for _ in 0..sessions { last = self.step().await?; @@ -1803,7 +1803,7 @@ impl SeedRunner { /// The first shortfall the world reports, in the order the snapshot reports /// them. The panel calls this too, so the card and the runner cannot disagree /// about what happens next. -pub(crate) fn select_row(snapshot: &WorldSnapshot) -> Option { +pub fn select_row(snapshot: &WorldSnapshot) -> Option { snapshot .scale_deficit .iter() diff --git a/crates/ghostlight/src/journal.rs b/crates/ghostlight/src/journal.rs index b8a9681..b0121af 100644 --- a/crates/ghostlight/src/journal.rs +++ b/crates/ghostlight/src/journal.rs @@ -1021,10 +1021,10 @@ fn kernel_error(error: KernelError) -> JournalError { #[cfg(test)] mod tests { use super::*; - use crate::world::WorldScaleIntentRef; - use crate::world::patch::kernel_speak_grant; - use crate::world::tests::speak_entry; - use crate::world::{ + use crate::WorldScaleIntentRef; + use crate::patch::kernel_speak_grant; + use crate::tests::speak_entry; + use crate::{ AuthenticatedCaller, CallerId, CommandBody, CommandEnvelope, CreateWorld, Declaration, DraftHandle, EntityDeclaration, EntityId, EntityKind, NewController, Position, PrincipalId, Ref, SubjectDeclaration, SubjectKind, WorldKernel, WorldPatch, @@ -1270,8 +1270,8 @@ mod tests { /// its command. The clock chain refuses a rewritten reading the same way. #[test] fn a_forged_motion_does_not_replay() { - use crate::world::tests::{activate, auth_principal, command, owner}; - use crate::world::{ + use crate::tests::{activate, auth_principal, command, owner}; + use crate::{ AuthenticatedCaller, CallerId, CommandBody, FictionalMinutes, SystemCapability, TickMinutes, }; @@ -1281,7 +1281,7 @@ mod tests { let authenticated = auth_principal(owner()); let (mut kernel, _) = WorldKernel::create( &path, - crate::world::tests::creation(CommandId::new(), "Chronicle"), + crate::tests::creation(CommandId::new(), "Chronicle"), &authenticated, ) .unwrap(); @@ -1322,14 +1322,14 @@ mod tests { /// to move a different subject no longer reduces from its command. #[test] fn a_forged_relocate_effect_does_not_apply() { - use crate::world::tests::{activate, admit_topology, auth_principal, command, owner}; + use crate::tests::{activate, admit_topology, auth_principal, command, owner}; let directory = tempfile::tempdir().unwrap(); let path = directory.path().join("world.cc"); let authenticated = auth_principal(owner()); let (mut kernel, _) = WorldKernel::create( &path, - crate::world::tests::creation(CommandId::new(), "Kharad"), + crate::tests::creation(CommandId::new(), "Kharad"), &authenticated, ) .unwrap(); @@ -1346,7 +1346,7 @@ mod tests { answers: None, patch: WorldPatch { declarations: Vec::new(), - operations: vec![crate::world::ComponentOp::Relocate { + operations: vec![crate::ComponentOp::Relocate { subject: Ref::Existing(topology.walker), via: Ref::Existing(topology.ramp), }], @@ -1364,11 +1364,11 @@ mod tests { let WorldEffect::PatchAdmitted { resolved, .. } = &mut forged.effect else { panic!("expected an admitted patch effect"); }; - let crate::world::ResolvedOp::Relocate { subject_id, .. } = &mut resolved.operations[0] + let crate::ResolvedOp::Relocate { subject_id, .. } = &mut resolved.operations[0] else { panic!("expected a lowered relocation"); }; - *subject_id = crate::world::SubjectId::issue(); + *subject_id = crate::SubjectId::issue(); forged.digest = commit_digest(forged).unwrap(); forged_head.last_commit_digest = Some(forged.digest.clone()); @@ -1386,22 +1386,22 @@ mod tests { /// contiguous chain, and asserts recovery refuses it. #[test] fn soul_a_forged_band_or_effect_in_the_journal_dies_at_replay() { - use crate::world::tests::{ + use crate::tests::{ activate, admit_custody, admit_topology, affordance_named, auth_principal, command, opportunity_for, owner, }; - use crate::world::{ + use crate::{ AuthenticatedCaller, DecisionInvocation, Magnitude, ProposedEffect, Quantity, RoleBinding, Target, }; - let forge = |kind: &str, mutate: &dyn Fn(&mut crate::world::DecisionEvent)| { + let forge = |kind: &str, mutate: &dyn Fn(&mut crate::DecisionEvent)| { let directory = tempfile::tempdir().unwrap(); let path = directory.path().join("world.cc"); let authenticated = auth_principal(owner()); let (mut kernel, _) = WorldKernel::create( &path, - crate::world::tests::creation(CommandId::new(), "ForgedDecision"), + crate::tests::creation(CommandId::new(), "ForgedDecision"), &authenticated, ) .unwrap(); @@ -1413,7 +1413,7 @@ mod tests { let caller = CallerId::Controller(opportunity.controller_id); let command_id = CommandId::new(); let role = |name: &str, target| RoleBinding { - role: crate::world::Role(name.into()), + role: crate::Role(name.into()), target, }; kernel @@ -1460,7 +1460,7 @@ mod tests { forged.digest = commit_digest(forged).unwrap(); forged_head.last_commit_digest = Some(forged.digest.clone()); forged_head.state_digest = String::new(); - forged_head.state_digest = crate::world::state_digest(&forged_head).unwrap(); + forged_head.state_digest = crate::state_digest(&forged_head).unwrap(); (forged_head, forged_commits) }; @@ -1475,7 +1475,7 @@ mod tests { // A magnitude the ceiling admitted but the lowering never produced. let (head, commits) = forge("carry", &|event| { - let Some(crate::world::ResolvedOp::Transfer { qty, .. }) = event.effects.first_mut() + let Some(crate::ResolvedOp::Transfer { qty, .. }) = event.effects.first_mut() else { panic!("expected a lowered transfer"); }; @@ -1547,14 +1547,14 @@ mod tests { /// `recover`, which is the path `WorldJournal::open` takes. #[test] fn soul_forged_topology_store_rows_are_refused_on_recover() { - use crate::world::patch::{AccessKind, Cost, EdgeRecord, MAX_ROUTE_COST}; - use crate::world::tests::{admit_topology, auth_principal, owner}; + use crate::patch::{AccessKind, Cost, EdgeRecord, MAX_ROUTE_COST}; + use crate::tests::{admit_topology, auth_principal, owner}; let directory = tempfile::tempdir().unwrap(); let path = directory.path().join("world.cc"); let (mut kernel, _) = WorldKernel::create( &path, - crate::world::tests::creation(CommandId::new(), "Forged Rows"), + crate::tests::creation(CommandId::new(), "Forged Rows"), &auth_principal(owner()), ) .unwrap(); @@ -1603,7 +1603,7 @@ mod tests { let non_place_endpoint = { let mut state = kernel.state.clone(); state.entities.get_mut(&topology.road).unwrap().kind = - crate::world::EntityKind::Resource; + crate::EntityKind::Resource; state }; let stray_position = { @@ -1629,13 +1629,13 @@ mod tests { /// refused outright, with no migration adapter in the path. #[test] fn soul_a_pre_topology_store_row_is_refused() { - use crate::world::tests::{admit_topology, auth_principal, owner}; + use crate::tests::{admit_topology, auth_principal, owner}; let directory = tempfile::tempdir().unwrap(); let path = directory.path().join("world.cc"); let (mut kernel, _) = WorldKernel::create( &path, - crate::world::tests::creation(CommandId::new(), "Old Store"), + crate::tests::creation(CommandId::new(), "Old Store"), &auth_principal(owner()), ) .unwrap(); @@ -1678,8 +1678,8 @@ mod tests { /// the same envelope is already applied. #[test] fn restart_replay_after_a_levy_is_exact() { - use crate::world::tests::{affordance_named, civic_world, command, opportunity_for, owner}; - use crate::world::{ + use crate::tests::{affordance_named, civic_world, command, opportunity_for, owner}; + use crate::{ DecisionInvocation, Magnitude, ProposedEffect, Quantity, Role, RoleBinding, SubmitReceipt, Target, }; @@ -1688,8 +1688,8 @@ mod tests { let path = directory.path().join("world.cc"); let (mut kernel, _) = WorldKernel::create( &path, - crate::world::tests::creation(CommandId::new(), "Replayed Levy"), - &crate::world::tests::auth_principal(owner()), + crate::tests::creation(CommandId::new(), "Replayed Levy"), + &crate::tests::auth_principal(owner()), ) .unwrap(); let (_, civic, active) = civic_world(&mut kernel); @@ -1751,18 +1751,18 @@ mod tests { /// institution, or empties a delegation is refused by `recover`. #[test] fn a_forged_authority_or_incumbency_is_corrupt() { - use crate::world::tests::{ + use crate::tests::{ BAILIFF_OFFICE, LEVY_KIND, WARDEN_OFFICE, auth_principal, authority_kind, civic_world, office, owner, }; - use crate::world::{AuthorityGrant, AuthorityTarget, Office}; + use crate::{AuthorityGrant, AuthorityTarget, Office}; use std::collections::BTreeSet; let directory = tempfile::tempdir().unwrap(); let path = directory.path().join("world.cc"); let (mut kernel, _) = WorldKernel::create( &path, - crate::world::tests::creation(CommandId::new(), "Forged Civics"), + crate::tests::creation(CommandId::new(), "Forged Civics"), &auth_principal(owner()), ) .unwrap(); @@ -1864,13 +1864,13 @@ mod tests { /// refused outright, with no migration adapter in the path. #[test] fn a_store_from_the_previous_schema_is_refused() { - use crate::world::tests::{auth_principal, civic_world, owner}; + use crate::tests::{auth_principal, civic_world, owner}; let directory = tempfile::tempdir().unwrap(); let path = directory.path().join("world.cc"); let (mut kernel, _) = WorldKernel::create( &path, - crate::world::tests::creation(CommandId::new(), "Pre-Authority"), + crate::tests::creation(CommandId::new(), "Pre-Authority"), &auth_principal(owner()), ) .unwrap(); @@ -1917,17 +1917,17 @@ mod tests { /// decision current. #[test] fn a_relocation_never_corrupts_a_civic_store() { - use crate::world::tests::{ + use crate::tests::{ LEVY_KIND, auth_principal, civic_world, operations, over_subject, owner, reject_owner, submit_owner, }; - use crate::world::{ComponentOp, Mismatch, SubmitReceipt}; + use crate::{ComponentOp, Mismatch, SubmitReceipt}; let directory = tempfile::tempdir().unwrap(); let path = directory.path().join("world.cc"); let (mut kernel, _) = WorldKernel::create( &path, - crate::world::tests::creation(CommandId::new(), "Moving Civics"), + crate::tests::creation(CommandId::new(), "Moving Civics"), &auth_principal(owner()), ) .unwrap(); @@ -1941,19 +1941,19 @@ mod tests { )); verify_state_shape(&kernel.state).expect("the committed store keeps its shape"); assert!( - crate::world::overlapping_holder(&kernel.state).is_none(), + crate::overlapping_holder(&kernel.state).is_none(), "a committed store never holds two overlapping jurisdictions" ); }; let place_covers = |kernel: &WorldKernel| { - crate::world::covers( + crate::covers( &kernel.state, - crate::world::AuthorityTarget::PlaceSubtree(civic.hall), - crate::world::Target::Subject(civic.farmer), + crate::AuthorityTarget::PlaceSubtree(civic.hall), + crate::Target::Subject(civic.farmer), ) }; let name_farmer = || { - vec![crate::world::tests::grant_to( + vec![crate::tests::grant_to( civic.treasury, LEVY_KIND, over_subject(civic.farmer), @@ -2001,7 +2001,7 @@ mod tests { #[cfg(test)] mod custody_tests { use super::*; - use crate::world::{ + use crate::{ CallerId, CommandBody, CommandId, ComponentOp, DependencyRef, EntityKind, Quantity, Ref, SubmitReceipt, WorldKernel, WorldPatch, tests::{admit_custody, admit_topology, auth_principal, command, creation, owner}, @@ -2020,7 +2020,7 @@ mod custody_tests { .unwrap(); let topology = admit_topology(&mut kernel); let custody = admit_custody(&mut kernel, &topology); - let active = crate::world::tests::activate(&mut kernel); + let active = crate::tests::activate(&mut kernel); let envelope = command( &active, @@ -2062,8 +2062,8 @@ mod custody_tests { /// seed does not reproduce fails effect equality on the way back in. #[test] fn restart_replay_after_an_action_is_exact() { - use crate::world::tests::affordance_named; - use crate::world::{AuthenticatedCaller, Magnitude, ProposedEffect, RoleBinding, Target}; + use crate::tests::affordance_named; + use crate::{AuthenticatedCaller, Magnitude, ProposedEffect, RoleBinding, Target}; let directory = tempfile::tempdir().unwrap(); let path = directory.path().join("world.cc"); @@ -2076,13 +2076,13 @@ mod custody_tests { .unwrap(); let topology = admit_topology(&mut kernel); let custody = admit_custody(&mut kernel, &topology); - let active = crate::world::tests::activate(&mut kernel); + let active = crate::tests::activate(&mut kernel); let carry = affordance_named(&active, "carry"); - let opportunity = crate::world::tests::opportunity_for(&active, custody.holder); + let opportunity = crate::tests::opportunity_for(&active, custody.holder); let caller = CallerId::Controller(opportunity.controller_id); let role = |name: &str, target| RoleBinding { - role: crate::world::Role(name.into()), + role: crate::Role(name.into()), target, }; let envelope = command( @@ -2091,7 +2091,7 @@ mod custody_tests { caller.clone(), CommandBody::ExerciseDecision { opportunity: opportunity.clone(), - invocation: crate::world::DecisionInvocation { + invocation: crate::DecisionInvocation { affordance: carry, bindings: vec![ role("from", Target::Subject(custody.holder)), @@ -2180,7 +2180,7 @@ mod custody_tests { .unwrap(); let topology = admit_topology(&mut kernel); let custody = admit_custody(&mut kernel, &topology); - crate::world::tests::activate(&mut kernel); + crate::tests::activate(&mut kernel); verify_state_shape(&kernel.state).expect("the admitted world has a canonical shape"); let mut zero = kernel.state.clone(); @@ -2218,7 +2218,7 @@ mod custody_tests { let mut dangling = kernel.state.clone(); dangling.dependencies.insert( custody.holder, - BTreeSet::from([crate::world::DependencyTarget::Subject(custody.holder)]), + BTreeSet::from([crate::DependencyTarget::Subject(custody.holder)]), ); assert!(matches!( verify_state_shape(&dangling), @@ -2244,7 +2244,7 @@ mod custody_tests { // `Admit` and its evidence are Draft-only, so the opening balance is // already on the log before activation. let custody = admit_custody(&mut kernel, &topology); - let active = crate::world::tests::activate(&mut kernel); + let active = crate::tests::activate(&mut kernel); let envelope = command( &active, @@ -2324,14 +2324,14 @@ mod custody_tests { .unwrap(); let topology = admit_topology(&mut kernel); let custody = admit_custody(&mut kernel, &topology); - crate::world::tests::activate(&mut kernel); + crate::tests::activate(&mut kernel); verify_state_shape(&kernel.state).expect("the admitted world has a canonical shape"); // A place is not a resource, so a resource dependency naming one dangles. let mut wrong_kind = kernel.state.clone(); wrong_kind.dependencies.insert( custody.holder, - BTreeSet::from([crate::world::DependencyTarget::Resource(topology.yard)]), + BTreeSet::from([crate::DependencyTarget::Resource(topology.yard)]), ); assert!(matches!( verify_state_shape(&wrong_kind), @@ -2352,7 +2352,7 @@ mod custody_tests { let absent = *orphan.subjects.keys().next().unwrap(); orphan.dependencies.insert( absent, - BTreeSet::from([crate::world::DependencyTarget::Resource(custody.tithe)]), + BTreeSet::from([crate::DependencyTarget::Resource(custody.tithe)]), ); orphan.subjects.remove(&absent); assert!(matches!( @@ -2364,8 +2364,8 @@ mod custody_tests { /// A world with one routine, one past-due goal, and a commit chain that /// reaches them: the fixture the two clock-forgery checks below need. fn ticking_world(path: &std::path::Path) -> (WorldKernel, super::super::SubjectId, CommandId) { - use crate::world::tests::{activate, auth_principal, command, operations, owner}; - use crate::world::{ + use crate::tests::{activate, auth_principal, command, operations, owner}; + use crate::{ AuthenticatedCaller, CallerId, CommandBody, CommitmentKind, ComponentOp, FictionalMinutes, Ref, SystemCapability, TickMinutes, }; @@ -2373,7 +2373,7 @@ mod custody_tests { let authenticated = auth_principal(owner()); let (mut kernel, _) = WorldKernel::create( path, - crate::world::tests::creation(CommandId::new(), "Ticking"), + crate::tests::creation(CommandId::new(), "Ticking"), &authenticated, ) .unwrap(); @@ -2394,7 +2394,7 @@ mod custody_tests { due: FictionalMinutes(10), period: Some(TickMinutes::new(10).unwrap()), checks: Vec::new(), - statement: crate::world::Statement::new("What was promised, as the promisor would say it.").unwrap(), + statement: crate::Statement::new("What was promised, as the promisor would say it.").unwrap(), }, ComponentOp::CreateCommitment { subject: Ref::Existing(subject), @@ -2403,7 +2403,7 @@ mod custody_tests { due: FictionalMinutes(20), period: None, checks: Vec::new(), - statement: crate::world::Statement::new("What was promised, as the promisor would say it.").unwrap(), + statement: crate::Statement::new("What was promised, as the promisor would say it.").unwrap(), }, ]), ), @@ -2436,7 +2436,7 @@ mod custody_tests { /// magnitude, a rewritten roll, and a fabricated row each stop replaying. #[test] fn soul_a_forged_tick_motion_does_not_replay() { - use crate::world::{FictionalMinutes, PressureMagnitude, PressureSource}; + use crate::{FictionalMinutes, PressureMagnitude, PressureSource}; let directory = tempfile::tempdir().unwrap(); let path = directory.path().join("world.cc"); @@ -2453,7 +2453,7 @@ mod custody_tests { assert!(!motion.fulfilled.is_empty(), "the routine rolled"); assert!(!motion.pressed.is_empty(), "the past-due goal pressed"); - let forge = |rewrite: fn(&mut crate::world::Motion)| { + let forge = |rewrite: fn(&mut crate::Motion)| { let mut forged_head = kernel.state.clone(); let mut forged_commits = kernel.journal.commits.clone(); let forged = forged_commits.get_mut(&tick_id).unwrap(); @@ -2466,7 +2466,7 @@ mod custody_tests { verify_history(&forged_head, &forged_commits) }; - let rewrites: [fn(&mut crate::world::Motion); 4] = [ + let rewrites: [fn(&mut crate::Motion); 4] = [ // A magnitude the tick never wrote. |motion| motion.pressed[0].magnitude = PressureMagnitude(42), // A roll to a date the period does not reach. @@ -2502,8 +2502,8 @@ mod custody_tests { // The forgery an in-crate caller cannot write: `TickMinutes::new` // refuses zero, and the field is private, so the only door is serde. - assert!(crate::world::TickMinutes::new(0).is_none()); - let zero: crate::world::TickMinutes = + assert!(crate::TickMinutes::new(0).is_none()); + let zero: crate::TickMinutes = rmp_serde::from_slice(&rmp_serde::to_vec(&0u32).unwrap()).expect("a transparent span"); let mut forged = kernel.state.clone(); @@ -2546,8 +2546,8 @@ mod custody_tests { /// to stops recovering. #[test] fn soul_a_forged_elaborator_commit_row_fails_replay() { - use crate::world::tests::{activate, submit_owner}; - use crate::world::{ + use crate::tests::{activate, submit_owner}; + use crate::{ AuthenticatedCaller, CausalBoundary, Declaration, DraftHandle, EntityDeclaration, JurisdictionKey, PatchAnswer, RouteDeclaration, SystemCapability, }; @@ -2580,8 +2580,8 @@ mod custody_tests { label: "The Long Lane".into(), from: Ref::Existing(commons), to: Ref::Draft(DraftHandle::new("road")), - access: crate::world::AccessKind::Public, - cost: crate::world::Cost(1), + access: crate::AccessKind::Public, + cost: crate::Cost(1), }), ], operations: Vec::new(), @@ -2591,7 +2591,7 @@ mod custody_tests { ); activate(&mut kernel); - let boundary = crate::world::derive_boundaries(&kernel.state) + let boundary = crate::derive_boundaries(&kernel.state) .unwrap() .into_iter() .find(|boundary| matches!(boundary, CausalBoundary::UnelaboratedDestination { .. })) @@ -2636,7 +2636,7 @@ mod custody_tests { ) .unwrap() }; - let rows_of = |commits: Vec| { + let rows_of = |commits: Vec| { let mut rows: Vec = commits .iter() .map(|commit| { @@ -2647,17 +2647,17 @@ mod custody_tests { rows }; - let honest: Vec = + let honest: Vec = kernel.journal.commits.values().cloned().collect(); recover(rows_of(honest.clone()), None).expect("the honest history replays"); // Rewrite only the recorded caller, to a jurisdiction that does not // cover the answer the same row carries. let mut forged_one = false; - let forged: Vec = honest + let forged: Vec = honest .into_iter() .map(|mut commit| { - if let crate::world::CommittedCommand::WorldCommand(command) = &mut commit.command + if let crate::CommittedCommand::WorldCommand(command) = &mut commit.command && matches!( command.caller, CallerId::System(SystemCapability::Elaborator { .. }) diff --git a/crates/ghostlight/src/lib.rs b/crates/ghostlight/src/lib.rs index 631e63f..9b9f500 100644 --- a/crates/ghostlight/src/lib.rs +++ b/crates/ghostlight/src/lib.rs @@ -1,9 +1,59 @@ -//! Sealed replacement world owner under construction. +//! The Ghostlight world library. //! //! The world owner is one deterministic authority: authenticated commands enter, //! one reducer decides, and one journal atomically commits the resulting state. //! Controllers may use models, but models never own lifecycle, scope, affordances, //! opportunities, reduction, or persistence. +//! +//! A consumer reaches this crate through create/open, immutable snapshots, +//! command submission, typed receipts, and the runner and port entry points +//! below. The kernel itself is sealed: there is no mutable state, no canonical +//! ID allocator, no reducer entry, no journal writer, and no authenticated-caller +//! constructor on the public surface, under every configuration. This crate +//! declares no cargo features, so no build can lift the seal. +//! +//! The negative proofs are doc-tests, which compile as an external crate and so +//! stand exactly where a consumer stands. +//! +//! ```compile_fail,E0603 +//! use ghostlight::WorldKernel; +//! ``` +//! +//! ```compile_fail,E0603 +//! use ghostlight::reduce; +//! ``` +//! +//! ```compile_fail,E0603 +//! use ghostlight::WorldState; +//! ``` +//! +//! ```compile_fail,E0603 +//! use ghostlight::CommandEnvelope; +//! ``` +//! +//! ```compile_fail,E0603 +//! use ghostlight::AuthenticatedCaller; +//! ``` +//! +//! ```compile_fail,E0603 +//! use ghostlight::journal::WorldJournal; +//! ``` +//! +//! ```compile_fail,E0599 +//! let _ = ghostlight::SubjectId::issue(); +//! ``` +//! +//! ```compile_fail,E0603 +//! let _ = ghostlight::ScopeDigest::fixture("x"); +//! ``` +//! +//! ```compile_fail,E0425 +//! let _ = ghostlight::fixture_controller_opportunities(&[]); +//! ``` +//! +//! ```compile_fail,E0425 +//! let _ = ghostlight::fixture_inference_output("prose", "receipt"); +//! ``` mod action; mod clock; @@ -19,35 +69,40 @@ mod tool_schema; mod vault; pub(crate) use action::ActionMismatch; -pub(crate) use clock::{FictionalMinutes, Motion, TickMinutes}; -pub(crate) use consumer::{ +pub(crate) use clock::Motion; +pub use clock::{FictionalMinutes, TickMinutes}; +pub use consumer::{ CONSUMER_BODY_LIMIT, CONSUMER_CREDENTIALS_ENVIRONMENT, CONSUMER_PATCH_SCHEMA, CONSUMER_RECEIPT_SCHEMA, ConsumerRegistry, admit_document, encode_receipt, }; -pub(crate) use controllers::{ - CellRun, ConnectorBinding, ControllerError, ControllerModels, ControllerOpenError, - ControllerPendingReason, ControllerRunner, ControllerWorkCustody, NarrativeCapture, - NarrativeDecision, NarrativePending, NarrativeRun, OperationalCapture, OperationalDecision, - OperationalPending, OperationalRun, SourceRange, SubmissionDisposition, TranslationGapSummary, - open_controller_work, open_inference, +pub use controllers::{ + CellRun, ConnectorBinding, ControllerError, ControllerModels, ControllerNeed, + ControllerPendingReason, ControllerRunner, ControllerWork, ControllerWorkCustody, + ControllerWorkLookup, ControllerWorkStore, ControllerWorkStoreError, ControllerWorkWrite, + GroupedCheckpoint, InferenceEvent, InferenceFault, InferenceOutput, InferencePort, + InferencePurpose, InferenceRequest, NarrativeCheckpoint, NarrativeRun, OperationalCheckpoint, + OperationalRun, PreparedInference, SubmissionDisposition, ToolResultOracle, + TracingInferencePort, open_controller_work, open_inference, }; -pub(crate) use cover::{ - AgencyGraph, Cell, CellId, Constituent, Cover, CoverBudget, Resolution, TickIndex, derive_cover, +pub(crate) use cover::{CellId, Constituent, Resolution}; +pub use cover::{AgencyGraph, Cell, Cover, CoverBudget, CoverBudgetError, TickIndex, derive_cover}; +pub use elaboration::{ + ElaborationCheckpoint, ElaborationRunner, EvidenceError, EvidenceQuery, EvidenceReceipt, + EvidenceSource, SeedCheckpoint, SeedOutcome, SeedRunner, select_row, }; -pub(crate) use elaboration::{SeedOutcome, select_row}; -pub(crate) use mailbox::{ - ConsumerPort, ControllerPort, ElaborationPort, MailboxError, SeedPort, WorldMailbox, +pub(crate) use mailbox::ElaborationPort; +pub use mailbox::{ConsumerPort, ControllerPort, MailboxError, SeedPort, WorldMailbox}; +pub use patch::{ + CommitmentKey, DraftHandle, JurisdictionKey, Mismatch, PatchAnswer, Ref, RefKind, RefName, Role, + Site, Statement, WorldPatch, }; pub(crate) use patch::{ - AccessKind, Affordance, AffordanceKindName, Audience, AuthoredSource, AuthorityGrant, - AuthorityKindName, AuthorityTarget, BoundPrecondition, Bounds, ChannelRecord, Commitment, - PersonaMaterial, - CommitmentKey, CommitmentKind, ComponentOpKind, Confidence, Cost, Declaration, - DependencyTarget, DraftHandle, EffectSlot, EntityDeclaration, EntityKind, EvidenceRef, - FactRecord, FactStanding, Forum, GrievanceKindName, JurisdictionKey, Knowledge, - KnowledgeSource, Mismatch, Office, OfficeName, OutcomeBand, PatchAnswer, Position, - Precondition, PressureMagnitude, PressureSource, Quantity, Reach, Ref, RefKind, Role, RoleSpec, - Statement, SubjectDeclaration, WorldPatch, WorldScaleIntent, WorldScaleIntentRef, + AccessKind, Affordance, Audience, AuthoredSource, AuthorityGrant, AuthorityTarget, + BoundPrecondition, Bounds, ChannelRecord, Commitment, CommitmentKind, Confidence, Cost, + Declaration, DependencyTarget, EntityDeclaration, EntityKind, EvidenceRef, FactRecord, + FactStanding, Forum, GrievanceKindName, Knowledge, KnowledgeSource, Office, OfficeName, + PersonaMaterial, Position, PressureMagnitude, PressureSource, Quantity, Reach, + SubjectDeclaration, WorldScaleIntent, WorldScaleIntentRef, }; #[cfg(test)] use patch::{ @@ -55,22 +110,13 @@ use patch::{ DependencyRef, FactDeclaration, FactStandingRef, ReachRef, RouteDeclaration, }; #[cfg(test)] -pub(crate) use patch::{AuthorityGrantRef, AuthorityTargetRef}; -pub(crate) use sdk_inference::{DEFAULT_SDK_MODEL_PREFIX, SdkBinding}; -pub(crate) use vault::VaultEvidenceSource; -// Test-only narrowing of the controller organ's inference and work-store -// seams, so `runtime`'s own spec tests can drive `ControllerRunner` over a -// counting/scripted `InferencePort` and an in-memory `ControllerWorkStore` -// instead of the real CodexConnector and CultCache-backed store. Production -// code never sees these names. -#[cfg(test)] -pub(crate) use controllers::{ - ControllerWork, ControllerWorkLookup, ControllerWorkStore, ControllerWorkStoreError, - ControllerWorkWrite, InferenceEvent, InferenceFault, InferenceOutput, InferencePort, - InferencePurpose, InferenceRequest, PreparedInference, TracingInferencePort, - fixture_inference_events, fixture_inference_output, fixture_prepared_inference, +pub(crate) use patch::{ + AffordanceKindName, AuthorityGrantRef, AuthorityKindName, AuthorityTargetRef, ComponentOpKind, + EffectSlot, OutcomeBand, Precondition, RoleSpec, }; -use patch::{EdgeRecord, EntityRecord, LedgerDelta, ResolvedOp, ResolvedPatch, Site}; +pub use sdk_inference::{DEFAULT_SDK_MODEL_PREFIX, SdkBinding}; +pub use vault::{VaultError, VaultEvidenceSource}; +use patch::{EdgeRecord, EntityRecord, LedgerDelta, ResolvedOp, ResolvedPatch}; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; @@ -81,37 +127,85 @@ use std::path::Path; use thiserror::Error; use uuid::Uuid; -pub(crate) const STATE_SCHEMA: &str = "ghostlight.world_state.consumer.v4"; +/// Proof that the consumer verified a live principal, and the moment that +/// proof stops being worth anything. +/// +/// The consumer verifies; this library only bounds. `valid_until` is the +/// consumer's own session expiry, not a fresh clock read at construction, and +/// [`WorldMailbox::submit_principal`] refuses evidence presented after it — so +/// a long-lived holder such as [`SeedPort`] is bounded by the session that +/// vouched for it rather than by one request's lifetime. +/// +/// Keeping this type here, rather than taking the consumer's own session type, +/// is what keeps the consumer's principal format out of the kernel. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct VerifiedPrincipalEvidence { + account_subject_hash: String, + valid_until: DateTime, +} + +impl VerifiedPrincipalEvidence { + pub fn new(account_subject_hash: impl Into, valid_until: DateTime) -> Self { + Self { + account_subject_hash: account_subject_hash.into(), + valid_until, + } + } + + pub fn account_subject_hash(&self) -> &str { + &self.account_subject_hash + } + + pub fn valid_until(&self) -> DateTime { + self.valid_until + } + + #[cfg(test)] + pub(crate) fn fixture( + account_subject_hash: impl Into, + valid_until: DateTime, + ) -> Self { + Self::new(account_subject_hash, valid_until) + } +} + +pub const STATE_SCHEMA: &str = "ghostlight.world_state.consumer.v4"; pub(crate) const COMMIT_SCHEMA: &str = "ghostlight.world_commit.consumer.v4"; /// Compatibility tag derived from [`STATE_SCHEMA`]: the trailing /// `-` pair (e.g. `foundation-v1`). Callers that publish a /// compatibility marker alongside the schema string must derive it from here /// rather than hand-copying a second literal that can drift from the schema. -pub(crate) fn state_schema_compatibility_tag() -> String { +pub fn state_schema_compatibility_tag() -> String { let mut segments = STATE_SCHEMA.rsplit('.'); let version = segments.next().unwrap_or_default(); let family = segments.next().unwrap_or_default(); format!("{family}-{version}") } +/// The visibility argument is the whole boundary decision for an ID type: the +/// ones a consumer names by value are `pub`, the rest stay `pub(crate)`, and no +/// `issue()` impl is emitted here at all — issuance is private in every case. macro_rules! opaque_uuid { - ($name:ident) => { + ($name:ident, $vis:vis) => { #[derive( Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash, )] #[serde(transparent)] - pub(crate) struct $name(Uuid); + $vis struct $name(Uuid); + }; + ($name:ident) => { + opaque_uuid!($name, pub(crate)); }; } -opaque_uuid!(WorldId); -opaque_uuid!(CommandId); -opaque_uuid!(SubjectId); -opaque_uuid!(EntityId); -opaque_uuid!(EdgeId); +opaque_uuid!(WorldId, pub); +opaque_uuid!(CommandId, pub); +opaque_uuid!(SubjectId, pub); +opaque_uuid!(EntityId, pub); +opaque_uuid!(EdgeId, pub); opaque_uuid!(ControllerId); -opaque_uuid!(AffordanceId); +opaque_uuid!(AffordanceId, pub); opaque_uuid!(EventId); impl CommandId { @@ -119,11 +213,11 @@ impl CommandId { Self(Uuid::new_v4()) } - pub(crate) fn new() -> Self { + pub fn new() -> Self { Self::issue() } - pub(crate) fn parse_uuid(value: &str) -> Result { + pub fn parse_uuid(value: &str) -> Result { Uuid::parse_str(value) .map(Self) .map_err(|_| KernelError::InvalidCommandId) @@ -178,7 +272,7 @@ impl WorldId { /// A named zero for tests that need a world identity and no world. #[cfg(test)] - pub(super) fn nil_for_test() -> Self { + pub(crate) fn nil_for_test() -> Self { Self(Uuid::nil()) } @@ -233,24 +327,24 @@ impl EventId { #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] #[serde(transparent)] -pub(crate) struct PrincipalId(String); +pub struct PrincipalId(String); impl PrincipalId { - pub(crate) fn new(value: impl Into) -> Self { + pub fn new(value: impl Into) -> Self { Self(value.into()) } } #[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] -pub(crate) enum WorldPhase { +pub enum WorldPhase { Draft, Active, } #[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] #[serde(rename_all = "snake_case")] -pub(crate) enum SubjectKind { +pub enum SubjectKind { Person, Institution, Population, @@ -258,7 +352,7 @@ pub(crate) enum SubjectKind { #[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] #[serde(rename_all = "snake_case")] -pub(crate) enum ControllerMode { +pub enum ControllerMode { Human, NarrativePersona, OperationalAgent, @@ -319,32 +413,32 @@ struct CreateWorld { /// Unattributed creation intent. World ingress derives ownership, controller /// identity, handles, kinds, and affordances from verified principal evidence. #[derive(Clone, Debug)] -pub(crate) struct CreateWorldIntent { - pub(crate) id: CommandId, - pub(crate) title: String, +pub struct CreateWorldIntent { + pub id: CommandId, + pub title: String, /// The premise, verbatim from the owner. May be empty: a world with no /// brief is a deliberate choice, and its lanes then get no guidance. - pub(crate) brief: String, - pub(crate) human_subject_label: String, - pub(crate) narrative_persona_label: Option, - pub(crate) operational_agent_label: Option, + pub brief: String, + pub human_subject_label: String, + pub narrative_persona_label: Option, + pub operational_agent_label: Option, /// 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. - pub(crate) targets: BTreeMap, + pub targets: BTreeMap, /// The jurisdiction roots genesis declares beside the commons, because /// `resolve_patch` only resolves roots the same patch declares. - pub(crate) jurisdictions: Vec, + pub jurisdictions: Vec, } /// One jurisdiction root as ingress states it. The handle is a string here and /// a `DraftHandle` inside the world subtree, which is the one lowering: the /// handle constructor is not reachable from runtime ingress. #[derive(Clone, Debug)] -pub(crate) struct CreateJurisdictionIntent { - pub(crate) handle: String, - pub(crate) label: String, - pub(crate) permille: u32, +pub struct CreateJurisdictionIntent { + pub handle: String, + pub label: String, + pub permille: u32, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] @@ -427,8 +521,8 @@ impl AuthenticatedCaller { #[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] #[serde(deny_unknown_fields)] -pub(crate) struct DecisionScope { - pub(crate) subject_id: SubjectId, +pub struct DecisionScope { + pub subject_id: SubjectId, } /// The digest of exactly the components a proposal's verification reads: the @@ -437,7 +531,7 @@ pub(crate) struct DecisionScope { /// helper over ordered containers, so it is a pure function of committed state. #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] #[serde(transparent)] -pub(crate) struct ScopeDigest(String); +pub struct ScopeDigest(String); impl ScopeDigest { fn as_str(&self) -> &str { @@ -464,14 +558,14 @@ struct ScopePreimage<'a> { #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(deny_unknown_fields)] -pub(crate) struct DecisionOpportunity { - pub(crate) world_id: WorldId, +pub struct DecisionOpportunity { + pub world_id: WorldId, pub(crate) revision: u64, pub(crate) scope_digest: ScopeDigest, - pub(crate) scope: DecisionScope, + pub scope: DecisionScope, pub(crate) controller_id: ControllerId, - pub(crate) controller_mode: ControllerMode, - pub(crate) affordance_ids: Vec, + pub controller_mode: ControllerMode, + pub affordance_ids: Vec, } impl DecisionOpportunity { @@ -480,33 +574,6 @@ impl DecisionOpportunity { } } -/// One opportunity per requested mode, all in one freshly issued world with a -/// freshly issued subject each. Not a committed scope — nothing here is -/// verifiable against a kernel — just enough shape to hand `derive_cover` a -/// cover to partition. Exists so `runtime`'s tick-driver tests can build a -/// small `Cover` without reaching into this module's private ID issuance -/// (`WorldId::issue`, `SubjectId::issue`, `ControllerId::issue`) themselves. -#[cfg(test)] -pub(crate) fn fixture_controller_opportunities( - modes: &[ControllerMode], -) -> Vec { - let world_id = WorldId::issue(); - modes - .iter() - .map(|mode| DecisionOpportunity { - world_id, - revision: 1, - scope_digest: ScopeDigest::fixture("fixture-scope"), - scope: DecisionScope { - subject_id: SubjectId::issue(), - }, - controller_id: ControllerId::issue(), - controller_mode: *mode, - affordance_ids: Vec::new(), - }) - .collect() -} - /// The referent a role is bound to. Canonical IDs only: an invocation happens /// in Active, where no declaration exists, so a draft reference is /// unrepresentable here and the action lane runs no draft resolution. @@ -520,7 +587,7 @@ pub(crate) enum Target { #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] #[serde(deny_unknown_fields)] -pub(crate) struct RoleBinding { +pub struct RoleBinding { pub(crate) role: Role, pub(crate) target: Target, } @@ -539,25 +606,25 @@ pub(crate) enum Magnitude { /// ceiling check is a comparison rather than a graph walk. #[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(deny_unknown_fields)] -pub(crate) struct ProposedEffect { +pub struct ProposedEffect { pub(crate) slot: usize, pub(crate) magnitude: Magnitude, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(deny_unknown_fields)] -pub(crate) struct DecisionInvocation { - pub(crate) affordance: AffordanceId, - pub(crate) bindings: Vec, +pub struct DecisionInvocation { + pub affordance: AffordanceId, + pub bindings: Vec, /// Exactly one entry per slot in the entry, in any order. - pub(crate) proposed: Vec, + pub proposed: Vec, /// Command input, and the only place an utterance's text enters the kernel. /// The committed home of those bytes is `facts[fact].statement`. - pub(crate) speech: Option, + pub speech: Option, /// A visible act in the actor's own words, and the only place it enters /// the kernel. It accompanies any invocation: a body is visible whatever /// it is doing. Its audience is the actor's place, never the entry's. - pub(crate) display: Option, + pub display: Option, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] @@ -572,16 +639,16 @@ struct CommandEnvelope { /// Unattributed human command intent. The mailbox derives the caller from /// live app-session evidence before the reducer sees an envelope. #[derive(Clone, Debug)] -pub(crate) struct PrincipalCommandIntent { - pub(crate) id: CommandId, - pub(crate) world_id: WorldId, - pub(crate) expected_revision: u64, - pub(crate) body: CommandBody, +pub struct PrincipalCommandIntent { + pub id: CommandId, + pub world_id: WorldId, + pub expected_revision: u64, + pub body: CommandBody, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(tag = "type", rename_all = "snake_case")] -pub(crate) enum CommandBody { +pub enum CommandBody { ApproveDraft, ActivateWorld, ExerciseDecision { @@ -872,16 +939,16 @@ struct WorldCommit { } #[derive(Clone, Debug, PartialEq, Eq)] -pub(crate) struct SubjectSnapshot { - pub(crate) id: SubjectId, - pub(crate) label: String, - pub(crate) kind: SubjectKind, +pub struct SubjectSnapshot { + pub id: SubjectId, + pub label: String, + pub kind: SubjectKind, /// Absent for an externally controlled mirror, which has no controller and /// no turn. Every reader fails closed on `None`; none gets a default. pub(crate) controller_id: Option, - pub(crate) controller_mode: Option, - pub(crate) human_controller: Option, - pub(crate) affordances: BTreeSet, + pub controller_mode: Option, + pub human_controller: Option, + pub affordances: BTreeSet, pub(crate) position: Option, /// Exactly what the scope digest binds for this subject, carried whole /// rather than lowered into projections that then have to be diffed back @@ -918,7 +985,7 @@ pub(crate) struct SubjectSnapshot { /// whether this subject already reduces its jurisdiction's scale deficit. /// The seed brief needs to say which subjects count; recomputing the /// conjunction outside the kernel would be a second definition of alive. - pub(crate) qualified: bool, + pub qualified: bool, } #[derive(Clone, Debug, PartialEq, Eq)] @@ -988,26 +1055,26 @@ pub(crate) struct ForumSnapshot { /// block — is a projection of it and a narrower snapshot would be a second /// vocabulary to keep in step. #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] -pub(crate) struct AffordanceSnapshot { - pub(crate) id: AffordanceId, - pub(crate) entry: Affordance, +pub struct AffordanceSnapshot { + pub id: AffordanceId, + pub entry: Affordance, } #[derive(Clone, Debug, PartialEq, Eq)] -pub(crate) struct ResourceSnapshot { +pub struct ResourceSnapshot { pub(crate) id: EntityId, pub(crate) label: String, } #[derive(Clone, Debug, PartialEq, Eq)] -pub(crate) struct PlaceSnapshot { - pub(crate) id: EntityId, - pub(crate) label: String, +pub struct PlaceSnapshot { + pub id: EntityId, + pub label: String, pub(crate) container: Option, } #[derive(Clone, Debug, PartialEq, Eq)] -pub(crate) struct RouteSnapshot { +pub struct RouteSnapshot { pub(crate) id: EdgeId, pub(crate) label: String, pub(crate) from: EntityId, @@ -1018,61 +1085,61 @@ pub(crate) struct RouteSnapshot { } #[derive(Clone, Debug, PartialEq, Eq)] -pub(crate) struct WorldSnapshot { - pub(crate) world_id: WorldId, - pub(crate) revision: u64, - pub(crate) phase: WorldPhase, - pub(crate) owner: PrincipalId, - pub(crate) title: String, - pub(crate) brief: String, - pub(crate) draft_approvals: BTreeSet, - pub(crate) required_approvers: BTreeSet, - pub(crate) subjects: Vec, - pub(crate) affordances: Vec, - pub(crate) places: Vec, - pub(crate) resources: Vec, - pub(crate) routes: Vec, +pub struct WorldSnapshot { + pub world_id: WorldId, + pub revision: u64, + pub phase: WorldPhase, + pub owner: PrincipalId, + pub title: String, + pub brief: String, + pub draft_approvals: BTreeSet, + pub required_approvers: BTreeSet, + pub subjects: Vec, + pub affordances: Vec, + pub places: Vec, + pub resources: Vec, + pub routes: Vec, /// Already ordered by pressure, then attention debt, then id: one owner, so /// Eve, the mesh projection, and any future driver read the same order and /// none computes its own. - pub(crate) opportunities: Vec, + pub opportunities: Vec, /// Everyone in a world shares the clock, and no controller can read a `due` /// without it. - pub(crate) now: FictionalMinutes, + pub now: FictionalMinutes, /// The elaborator's surface. There is no global pressure register and no /// commitment gazetteer. - pub(crate) boundaries: Vec, - pub(crate) scale_deficit: Vec, - pub(crate) state_digest: String, - pub(crate) last_commit_digest: Option, + pub boundaries: Vec, + pub scale_deficit: Vec, + pub state_digest: String, + pub last_commit_digest: Option, } /// One committed act as the human operator's story feed reads it. It is the /// operator surface, not a perception surface: no subject-facing lane receives /// it, and `SelectedDecision` never sees an event log at all. #[derive(Clone, Debug, PartialEq, Eq)] -pub(crate) struct OperatorEvent { - pub(crate) revision: u64, - pub(crate) speaker: SubjectId, - pub(crate) speaker_label: String, - pub(crate) speech: Option, - pub(crate) display: Option, +pub struct OperatorEvent { + pub revision: u64, + pub speaker: SubjectId, + pub speaker_label: String, + pub speech: Option, + pub display: Option, } #[derive(Clone, Debug, PartialEq, Eq)] -pub(crate) struct CommitReceipt { - pub(crate) command_id: CommandId, - pub(crate) resulting_revision: u64, - pub(crate) resulting_state_digest: String, - pub(crate) commit_digest: String, +pub struct CommitReceipt { + pub command_id: CommandId, + pub resulting_revision: u64, + pub resulting_state_digest: String, + pub commit_digest: String, } #[derive(Clone, Debug, PartialEq, Eq)] -pub(crate) struct CreationReceipt { - pub(crate) command_id: CommandId, - pub(crate) world_id: WorldId, - pub(crate) resulting_state_digest: String, - pub(crate) commit_digest: String, +pub struct CreationReceipt { + pub command_id: CommandId, + pub world_id: WorldId, + pub resulting_state_digest: String, + pub commit_digest: String, } impl CreationReceipt { @@ -1108,13 +1175,13 @@ impl From<&WorldCommit> for CommitReceipt { } #[derive(Clone, Debug, PartialEq, Eq)] -pub(crate) enum SubmitReceipt { +pub enum SubmitReceipt { Applied(CommitReceipt), AlreadyApplied(CommitReceipt), } #[derive(Debug, Error)] -pub(crate) enum KernelError { +pub enum KernelError { #[error("command ID must be a UUID")] InvalidCommandId, #[error("world title must not be empty")] @@ -3094,7 +3161,7 @@ fn overlapping_holder(state: &WorldState) -> Option { /// scope digest — is byte-identical. #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(deny_unknown_fields)] -pub(super) struct ScopeComponents { +pub struct ScopeComponents { position: Option, routes: BTreeMap, holdings: BTreeMap, @@ -3657,7 +3724,7 @@ fn snapshot(state: &WorldState) -> Result { /// derived when its predicate stops holding. #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] #[serde(tag = "boundary", rename_all = "snake_case")] -pub(crate) enum CausalBoundary { +pub enum CausalBoundary { UnelaboratedDestination { route: EdgeId, place: EntityId, @@ -3686,17 +3753,17 @@ pub(crate) enum CausalBoundary { /// the one `digest()` owner, in the `ScopeDigest` idiom. #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] #[serde(transparent)] -pub(crate) struct BoundaryDigest(String); +pub struct BoundaryDigest(String); impl BoundaryDigest { /// The digest text, for the one command-key derivation that hashes it. - pub(super) fn text(&self) -> &str { + pub(crate) fn text(&self) -> &str { &self.0 } /// The deficit lane's answer has no preimage of its own: its digest is the /// session's, produced by the same `sha256:` spelling. - pub(super) fn from_digest(value: String) -> Self { + pub(crate) fn from_digest(value: String) -> Self { Self(value) } } @@ -3826,12 +3893,12 @@ fn exact_boundary( /// One region's count for one subject kind. #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub(crate) struct ScaleDeficitRow { - pub(crate) jurisdiction: JurisdictionKey, - pub(crate) kind: SubjectKind, - pub(crate) target: u32, - pub(crate) qualified: u32, - pub(crate) deficit: u32, +pub struct ScaleDeficitRow { + pub jurisdiction: JurisdictionKey, + pub kind: SubjectKind, + pub target: u32, + pub qualified: u32, + pub deficit: u32, } /// Whether a subject counts toward the world's scale target. Aliveness is @@ -4059,9 +4126,9 @@ fn granted_entries( /// entry, the scope holds it, and `entry` is its catalog definition. It is the /// only way to name an affordance to `action::exercise`, so a caller cannot /// reach the invocation pipeline without passing the grant check first. -pub(super) struct GrantedAffordance<'a> { - pub(super) id: AffordanceId, - pub(super) entry: &'a Affordance, +pub(crate) struct GrantedAffordance<'a> { + pub(crate) id: AffordanceId, + pub(crate) entry: &'a Affordance, } /// The membership half of the affordance check, shared by `reduce` and @@ -4853,26 +4920,26 @@ mod tests { /// home of an utterance: `facts[fact].statement`. The committed event's /// own `AssertClaim` effect carries the same bytes as the replay witness, /// but no projection reads that copy; `facts` is the surface. - pub(super) fn spoken<'a>(state: &'a WorldState, event: &DecisionEvent) -> Option<&'a str> { + pub(crate) fn spoken<'a>(state: &'a WorldState, event: &DecisionEvent) -> Option<&'a str> { event .speech .and_then(|fact| state.facts.get(&fact)) .map(|record| record.statement.as_str()) } - pub(super) fn owner() -> PrincipalId { + pub(crate) fn owner() -> PrincipalId { PrincipalId::new("owner@example.test") } - pub(super) fn player() -> PrincipalId { + pub(crate) fn player() -> PrincipalId { PrincipalId::new("player@example.test") } - pub(super) fn auth_principal(principal: PrincipalId) -> AuthenticatedCaller { + pub(crate) fn auth_principal(principal: PrincipalId) -> AuthenticatedCaller { AuthenticatedCaller::fixture(CallerId::Principal(principal)) } - pub(super) fn subject( + pub(crate) fn subject( handle: &str, label: &str, kind: SubjectKind, @@ -4891,17 +4958,17 @@ mod tests { } /// The one place the base fixture world declares. - pub(super) const COMMONS: &str = "commons"; + pub(crate) const COMMONS: &str = "commons"; /// How many entities the base fixture world admits at genesis: the commons /// and nothing else. A patch-lane test that proves a rejection allocated /// nothing compares against this rather than against zero. - pub(super) const FIXTURE_ENTITIES: usize = 1; + pub(crate) const FIXTURE_ENTITIES: usize = 1; /// The committed Speak entry. A post-genesis declaration grants it by /// canonical reference, like every other structure a previous commit /// allocated: the kernel synthesizes the entry once, at genesis. - pub(super) fn speak_entry(kernel: &WorldKernel) -> Ref { + pub(crate) fn speak_entry(kernel: &WorldKernel) -> Ref { Ref::Existing( *kernel .state @@ -4916,7 +4983,7 @@ mod tests { /// A catalog entry by kind name. An affordance id names an entry, not a /// subject's copy of one, so a test that needs an entry a scope does not /// hold asks for it by name rather than by list position. - pub(super) fn affordance_named(snapshot: &WorldSnapshot, kind: &str) -> AffordanceId { + pub(crate) fn affordance_named(snapshot: &WorldSnapshot, kind: &str) -> AffordanceId { snapshot .affordances .iter() @@ -4925,7 +4992,7 @@ mod tests { .expect("the fixture world declares this affordance") } - pub(super) fn creation(id: CommandId, title: &str) -> CreateWorld { + pub(crate) fn creation(id: CommandId, title: &str) -> CreateWorld { CreateWorld { id, owner: owner(), @@ -4991,7 +5058,7 @@ mod tests { } } - pub(super) fn command( + pub(crate) fn command( snapshot: &WorldSnapshot, id: CommandId, caller: CallerId, @@ -5006,7 +5073,7 @@ mod tests { } } - pub(super) fn submit_owner( + pub(crate) fn submit_owner( kernel: &mut WorldKernel, snapshot: &WorldSnapshot, body: CommandBody, @@ -5024,7 +5091,7 @@ mod tests { .unwrap() } - pub(super) fn activate(kernel: &mut WorldKernel) -> WorldSnapshot { + pub(crate) fn activate(kernel: &mut WorldKernel) -> WorldSnapshot { let genesis = kernel.snapshot().unwrap(); submit_owner(kernel, &genesis, CommandBody::ApproveDraft); let after_owner = kernel.snapshot().unwrap(); @@ -5047,15 +5114,15 @@ mod tests { /// The canonical IDs of the shared topology fixture: three places, four /// routes with every access and open combination the operations read, and one /// subject standing in the yard. - pub(super) struct Topology { - pub(super) yard: EntityId, - pub(super) road: EntityId, - pub(super) gate: EntityId, - pub(super) ramp: EdgeId, - pub(super) shutter: EdgeId, - pub(super) toll: EdgeId, - pub(super) span: EdgeId, - pub(super) walker: SubjectId, + pub(crate) struct Topology { + pub(crate) yard: EntityId, + pub(crate) road: EntityId, + pub(crate) gate: EntityId, + pub(crate) ramp: EdgeId, + pub(crate) shutter: EdgeId, + pub(crate) toll: EdgeId, + pub(crate) span: EdgeId, + pub(crate) walker: SubjectId, } fn place(handle: &str, label: &str) -> Declaration { @@ -5085,7 +5152,7 @@ mod tests { }) } - pub(super) fn topology_patch(speak: Ref) -> WorldPatch { + pub(crate) fn topology_patch(speak: Ref) -> WorldPatch { WorldPatch { declarations: vec![ place("yard", "The Cavity Yard"), @@ -5143,7 +5210,7 @@ mod tests { } } - pub(super) fn admit_topology(kernel: &mut WorldKernel) -> Topology { + pub(crate) fn admit_topology(kernel: &mut WorldKernel) -> Topology { let before = kernel.snapshot().unwrap(); let receipt = submit_owner( kernel, @@ -5193,25 +5260,25 @@ mod tests { /// The canonical IDs of the custody fixture: two resources and two holder /// subjects standing in the topology, with the first holder carrying an /// evidenced opening balance. - pub(super) struct Custody { - pub(super) tithe: EntityId, - pub(super) ingot: EntityId, - pub(super) holder: SubjectId, - pub(super) counterparty: SubjectId, + pub(crate) struct Custody { + pub(crate) tithe: EntityId, + pub(crate) ingot: EntityId, + pub(crate) holder: SubjectId, + pub(crate) counterparty: SubjectId, } /// The seed world's authority kinds, offices, and grievance. They are world /// data: the kernel compares these strings and reads them no other way. - pub(super) const LEVY_KIND: &str = "levy"; - pub(super) const ADMIT_KIND: &str = "admit"; - pub(super) const COMMAND_KIND: &str = "command"; - pub(super) const JUDGE_KIND: &str = "judge"; - pub(super) const WARDEN_OFFICE: &str = "warden"; - pub(super) const BAILIFF_OFFICE: &str = "bailiff"; - pub(super) const SEIZURE_GRIEVANCE: &str = "seizure"; + pub(crate) const LEVY_KIND: &str = "levy"; + pub(crate) const ADMIT_KIND: &str = "admit"; + pub(crate) const COMMAND_KIND: &str = "command"; + pub(crate) const JUDGE_KIND: &str = "judge"; + pub(crate) const WARDEN_OFFICE: &str = "warden"; + pub(crate) const BAILIFF_OFFICE: &str = "bailiff"; + pub(crate) const SEIZURE_GRIEVANCE: &str = "seizure"; - pub(super) const TITHE_RECEIPT: &str = "receipt:rhythm-tithe-census"; - pub(super) const OPENING_BALANCE: u64 = 7; + pub(crate) const TITHE_RECEIPT: &str = "receipt:rhythm-tithe-census"; + pub(crate) const OPENING_BALANCE: u64 = 7; fn resource(handle: &str, label: &str) -> Declaration { Declaration::Entity(EntityDeclaration { @@ -5240,7 +5307,7 @@ mod tests { }) } - pub(super) const CARRY_HANDLE: &str = "carry"; + pub(crate) const CARRY_HANDLE: &str = "carry"; /// The worked affordances for the action lane. Every one carries the same /// four roles and the same bounded `Transfer` slot, so a test varies exactly @@ -5346,7 +5413,7 @@ mod tests { /// Carry, and five variants that each move one dial: a holding demand above /// the opening balance, a reach budget short of the two-hop path, one that /// covers it, a band that names no effect, and three equally weighted bands. - pub(super) fn carry_affordances() -> Vec { + pub(crate) fn carry_affordances() -> Vec { vec![ carry_variant( CARRY_HANDLE, @@ -5432,16 +5499,16 @@ mod tests { } /// Yard to gate over the open shutter alone. - pub(super) const NEAR_REACH: u32 = 5; + pub(crate) const NEAR_REACH: u32 = 5; /// Yard to gate over the ramp and the span, which is the only open public /// path while the shutter is closed. - pub(super) const FAR_REACH: u32 = 19; + pub(crate) const FAR_REACH: u32 = 19; /// Declarations and evidence are Draft-only, so the resources, the holders, /// and the one evidenced `Admit` that creates the opening balance all land /// before activation. There is no holdings declaration field: quantity is /// created by `Admit` and by nothing else, in this lane as in every other. - pub(super) fn custody_patch(topology: &Topology, speak: Ref) -> WorldPatch { + pub(crate) fn custody_patch(topology: &Topology, speak: Ref) -> WorldPatch { WorldPatch { declarations: carry_affordances() .into_iter() @@ -5462,7 +5529,7 @@ mod tests { } } - pub(super) fn admit_custody(kernel: &mut WorldKernel, topology: &Topology) -> Custody { + pub(crate) fn admit_custody(kernel: &mut WorldKernel, topology: &Topology) -> Custody { let before = kernel.snapshot().unwrap(); let receipt = submit_owner( kernel, @@ -5501,7 +5568,7 @@ mod tests { /// Topology, custody, then activation: everything a custody test needs, in /// the one order the phase rules allow. - pub(super) fn custody_world(kernel: &mut WorldKernel) -> (Topology, Custody, WorldSnapshot) { + pub(crate) fn custody_world(kernel: &mut WorldKernel) -> (Topology, Custody, WorldSnapshot) { let topology = admit_topology(kernel); let custody = admit_custody(kernel, &topology); let active = activate(kernel); @@ -5512,47 +5579,47 @@ mod tests { /// institution holding jurisdiction over the hall, a person who holds that /// jurisdiction only through an office, a person and a resource inside the /// hall, and a person standing outside it. - pub(super) struct Civic { - pub(super) hall: EntityId, - pub(super) chamber: EntityId, - pub(super) passage: EdgeId, - pub(super) causeway: EdgeId, - pub(super) postern: EdgeId, - pub(super) treasury: SubjectId, - pub(super) reeve: SubjectId, - pub(super) farmer: SubjectId, - pub(super) outsider: SubjectId, - pub(super) grain: EntityId, + pub(crate) struct Civic { + pub(crate) hall: EntityId, + pub(crate) chamber: EntityId, + pub(crate) passage: EdgeId, + pub(crate) causeway: EdgeId, + pub(crate) postern: EdgeId, + pub(crate) treasury: SubjectId, + pub(crate) reeve: SubjectId, + pub(crate) farmer: SubjectId, + pub(crate) outsider: SubjectId, + pub(crate) grain: EntityId, } - pub(super) fn authority_kind(name: &str) -> AuthorityKindName { + pub(crate) fn authority_kind(name: &str) -> AuthorityKindName { AuthorityKindName(name.into()) } - pub(super) fn office(name: &str) -> OfficeName { + pub(crate) fn office(name: &str) -> OfficeName { OfficeName(name.into()) } - pub(super) fn grievance(name: &str) -> GrievanceKindName { + pub(crate) fn grievance(name: &str) -> GrievanceKindName { GrievanceKindName(name.into()) } - pub(super) fn over_place(place: EntityId) -> AuthorityTargetRef { + pub(crate) fn over_place(place: EntityId) -> AuthorityTargetRef { AuthorityTargetRef::PlaceSubtree(Ref::Existing(place)) } - pub(super) fn over_subject(subject_id: SubjectId) -> AuthorityTargetRef { + pub(crate) fn over_subject(subject_id: SubjectId) -> AuthorityTargetRef { AuthorityTargetRef::Subject(Ref::Existing(subject_id)) } - pub(super) fn grant_of(kind: &str, over: AuthorityTargetRef) -> AuthorityGrantRef { + pub(crate) fn grant_of(kind: &str, over: AuthorityTargetRef) -> AuthorityGrantRef { AuthorityGrantRef { kind: authority_kind(kind), over, } } - pub(super) fn grant_to(holder: SubjectId, kind: &str, over: AuthorityTargetRef) -> ComponentOp { + pub(crate) fn grant_to(holder: SubjectId, kind: &str, over: AuthorityTargetRef) -> ComponentOp { ComponentOp::GrantAuthority { holder: Ref::Existing(holder), grant: grant_of(kind, over), @@ -5748,7 +5815,7 @@ mod tests { .collect() } - pub(super) fn civic_patch(topology: &Topology, speak: Ref) -> WorldPatch { + pub(crate) fn civic_patch(topology: &Topology, speak: Ref) -> WorldPatch { let person = |handle: &str, label: &str, place: Ref, handles: &[&str]| { Declaration::Subject(SubjectDeclaration { handle: DraftHandle::new(handle), @@ -5910,7 +5977,7 @@ mod tests { } } - pub(super) fn admit_civic(kernel: &mut WorldKernel, topology: &Topology) -> Civic { + pub(crate) fn admit_civic(kernel: &mut WorldKernel, topology: &Topology) -> Civic { let before = kernel.snapshot().unwrap(); let receipt = submit_owner( kernel, @@ -5963,7 +6030,7 @@ mod tests { } /// Topology, the civic subgraph, then activation. - pub(super) fn civic_world(kernel: &mut WorldKernel) -> (Topology, Civic, WorldSnapshot) { + pub(crate) fn civic_world(kernel: &mut WorldKernel) -> (Topology, Civic, WorldSnapshot) { let topology = admit_topology(kernel); let civic = admit_civic(kernel, &topology); let active = activate(kernel); @@ -5973,28 +6040,28 @@ mod tests { /// The speech bench: a hall containing a yard, a speaker and a listener in /// the hall, a bystander in the yard, a stranger nowhere, one evidenced /// canonical fact, and a horn that carries over the hall subtree. - pub(super) struct Speech { - pub(super) hall: EntityId, - pub(super) yard: EntityId, - pub(super) speaker: SubjectId, - pub(super) listener: SubjectId, - pub(super) bystander: SubjectId, - pub(super) stranger: SubjectId, - pub(super) flood: EntityId, - pub(super) horn: EntityId, - pub(super) whisper: AffordanceId, - pub(super) proclaim: AffordanceId, - pub(super) recant: AffordanceId, - pub(super) stair: EdgeId, + pub(crate) struct Speech { + pub(crate) hall: EntityId, + pub(crate) yard: EntityId, + pub(crate) speaker: SubjectId, + pub(crate) listener: SubjectId, + pub(crate) bystander: SubjectId, + pub(crate) stranger: SubjectId, + pub(crate) flood: EntityId, + pub(crate) horn: EntityId, + pub(crate) whisper: AffordanceId, + pub(crate) proclaim: AffordanceId, + pub(crate) recant: AffordanceId, + pub(crate) stair: EdgeId, } - pub(super) const FLOOD_STATEMENT: &str = "The lower hinge is flooding."; - pub(super) const FLOOD_EVIDENCE: &str = "vault:flood-survey"; + pub(crate) const FLOOD_STATEMENT: &str = "The lower hinge is flooding."; + pub(crate) const FLOOD_EVIDENCE: &str = "vault:flood-survey"; /// A world with one room inside another, four subjects, a fact, and a /// channel. Two world-declared speaking entries exercise both audiences: an /// addressed co-located `whisper` and a channel-bound `proclaim`. - pub(super) fn speech_world(kernel: &mut WorldKernel) -> (Speech, WorldSnapshot) { + pub(crate) fn speech_world(kernel: &mut WorldKernel) -> (Speech, WorldSnapshot) { let before = kernel.snapshot().unwrap(); let speak = speak_entry(kernel); let person = |handle: &str, label: &str, place: Option<&str>| { @@ -6174,7 +6241,7 @@ mod tests { } /// Submits as owner and returns the complete mismatch set. - pub(super) fn reject_owner( + pub(crate) fn reject_owner( kernel: &mut WorldKernel, snapshot: &WorldSnapshot, body: CommandBody, @@ -6196,7 +6263,7 @@ mod tests { mismatches } - pub(super) fn operations(operations: Vec) -> CommandBody { + pub(crate) fn operations(operations: Vec) -> CommandBody { CommandBody::AdmitPatch { answers: None, patch: WorldPatch { @@ -6207,7 +6274,7 @@ mod tests { } } - pub(super) fn opportunity_for( + pub(crate) fn opportunity_for( snapshot: &WorldSnapshot, subject_id: SubjectId, ) -> DecisionOpportunity { @@ -7571,7 +7638,7 @@ mod tests { #[test] fn an_institution_and_its_voice_are_two_subjects_joined_by_an_office() { let directory = tempfile::tempdir().unwrap(); - let mut kernel = crate::world::custody_tests::custody_kernel(directory.path(), "Two"); + let mut kernel = crate::custody_tests::custody_kernel(directory.path(), "Two"); let (_, civic, active) = civic_world(&mut kernel); let treasury = opportunity_for(&active, civic.treasury); @@ -7620,7 +7687,7 @@ mod tests { #[test] fn a_levy_round_trips_from_catalog_to_conservation() { let directory = tempfile::tempdir().unwrap(); - let mut kernel = crate::world::custody_tests::custody_kernel(directory.path(), "Levy"); + let mut kernel = crate::custody_tests::custody_kernel(directory.path(), "Levy"); let (_, civic, active) = civic_world(&mut kernel); let before_total: u64 = kernel .state @@ -7706,7 +7773,7 @@ mod tests { #[test] fn office_admission_is_person_and_institution_shaped() { let directory = tempfile::tempdir().unwrap(); - let mut kernel = crate::world::custody_tests::custody_kernel(directory.path(), "Offices"); + let mut kernel = crate::custody_tests::custody_kernel(directory.path(), "Offices"); let (_, civic, _) = civic_world(&mut kernel); let refuse = |kernel: &mut WorldKernel, ops: Vec| { let before = kernel.snapshot().unwrap(); @@ -7756,7 +7823,7 @@ mod tests { #[test] fn overlapping_jurisdiction_within_one_subject_is_rejected() { let directory = tempfile::tempdir().unwrap(); - let mut kernel = crate::world::custody_tests::custody_kernel(directory.path(), "Overlap"); + let mut kernel = crate::custody_tests::custody_kernel(directory.path(), "Overlap"); let (_, civic, _) = civic_world(&mut kernel); let refuse = |kernel: &mut WorldKernel, ops: Vec| { let before = kernel.snapshot().unwrap(); @@ -7823,7 +7890,7 @@ mod tests { #[test] fn civic_operations_are_rejected_with_the_complete_set() { let directory = tempfile::tempdir().unwrap(); - let mut kernel = crate::world::custody_tests::custody_kernel(directory.path(), "CivicSet"); + let mut kernel = crate::custody_tests::custody_kernel(directory.path(), "CivicSet"); let (_, civic, _) = civic_world(&mut kernel); let before = kernel.snapshot().unwrap(); let broken = vec![ @@ -7897,7 +7964,7 @@ mod tests { fn an_idempotent_civic_operation_is_no_change() { let directory = tempfile::tempdir().unwrap(); let mut kernel = - crate::world::custody_tests::custody_kernel(directory.path(), "Idempotent"); + crate::custody_tests::custody_kernel(directory.path(), "Idempotent"); let (_, civic, _) = civic_world(&mut kernel); for operation in [ grant_to(civic.treasury, LEVY_KIND, over_place(civic.hall)), @@ -7933,7 +8000,7 @@ mod tests { #[test] fn scope_digest_reads_authority_and_delegation_but_not_occupancy() { let directory = tempfile::tempdir().unwrap(); - let mut kernel = crate::world::custody_tests::custody_kernel(directory.path(), "Digest"); + let mut kernel = crate::custody_tests::custody_kernel(directory.path(), "Digest"); let (topology, civic, _) = civic_world(&mut kernel); let scope = |subject_id| DecisionScope { subject_id }; let digest_of = |kernel: &WorldKernel, subject_id| { @@ -8023,7 +8090,7 @@ mod tests { #[test] fn a_forum_is_visible_only_to_its_standing() { let directory = tempfile::tempdir().unwrap(); - let mut kernel = crate::world::custody_tests::custody_kernel(directory.path(), "Redress"); + let mut kernel = crate::custody_tests::custody_kernel(directory.path(), "Redress"); let (_, civic, active) = civic_world(&mut kernel); let of = |subject_id: SubjectId| { active @@ -8074,7 +8141,7 @@ mod tests { #[test] fn a_same_patch_destination_is_covered_by_no_grant() { let directory = tempfile::tempdir().unwrap(); - let mut kernel = crate::world::custody_tests::custody_kernel(directory.path(), "DraftDoor"); + let mut kernel = crate::custody_tests::custody_kernel(directory.path(), "DraftDoor"); let topology = admit_topology(&mut kernel); let before = kernel.snapshot().unwrap(); let patch = WorldPatch { @@ -8137,7 +8204,7 @@ mod custody_tests { }; use super::*; - pub(super) fn custody_kernel(path: &Path, title: &str) -> WorldKernel { + pub(crate) fn custody_kernel(path: &Path, title: &str) -> WorldKernel { WorldKernel::create( path.join("world.cc"), creation(CommandId::new(), title), @@ -12038,7 +12105,7 @@ mod witness_tests { /// opportunity's binding. #[test] fn soul_persona_material_is_set_whole_and_outside_the_scope_digest() { - use crate::world::patch::PersonaReadRef; + use crate::patch::PersonaReadRef; let directory = tempfile::tempdir().unwrap(); let (mut kernel, world, active) = nesting_kernel(directory.path(), "Material"); @@ -12207,7 +12274,7 @@ mod clock_tests { /// The canonical IDs of the clock fixture: a hall containing a yard, a /// dead end reached by exactly one route, three subjects, a resource, and /// one commitment of each kind. - pub(super) struct Clockwork { + pub(crate) struct Clockwork { yard: EntityId, dead_end: EntityId, gate: EdgeId, diff --git a/crates/ghostlight/src/mailbox.rs b/crates/ghostlight/src/mailbox.rs index ffba1f1..a8ab471 100644 --- a/crates/ghostlight/src/mailbox.rs +++ b/crates/ghostlight/src/mailbox.rs @@ -7,7 +7,7 @@ use super::{ WorldKernel, WorldPatch, WorldPhase, WorldScaleIntentRef, WorldSnapshot, journal, patch::kernel_speak_grant, prepare_creation, }; -use crate::app_session::VerifiedPrincipalEvidence; +use crate::VerifiedPrincipalEvidence; use chrono::Utc; use std::path::Path; use thiserror::Error; @@ -21,7 +21,7 @@ const GENESIS_PLACE: &str = "commons"; const REQUEST_CAPACITY: usize = 32; #[derive(Clone, Debug)] -pub(crate) struct WorldMailbox { +pub struct WorldMailbox { sender: mpsc::Sender, } @@ -80,7 +80,7 @@ enum Request { } #[derive(Debug, Error)] -pub(crate) enum MailboxError { +pub enum MailboxError { #[error("world owner mailbox is unavailable")] Unavailable, #[error("world command outcome is unknown after enqueue: {command_id:?}")] @@ -90,7 +90,7 @@ pub(crate) enum MailboxError { } impl WorldMailbox { - pub(crate) fn open(path: impl AsRef) -> Result<(Self, JoinHandle<()>), KernelError> { + pub fn open(path: impl AsRef) -> Result<(Self, JoinHandle<()>), KernelError> { let owned = match journal::WorldJournal::open_owner(path.as_ref())? { journal::JournalOpen::Empty(empty) => OwnedWorld::Empty(empty), journal::JournalOpen::Live { journal, state } => { @@ -102,7 +102,7 @@ impl WorldMailbox { Ok((Self { sender }, task)) } - pub(crate) async fn create( + pub async fn create( &self, input: CreateWorldIntent, principal: &VerifiedPrincipalEvidence, @@ -223,7 +223,7 @@ impl WorldMailbox { /// minted. `VerifiedPrincipalEvidence` can outlive one request inside a /// long-running port such as `SeedPort`, so this is the gate that keeps /// holding it bounded by the session's own expiry. - pub(crate) async fn submit_principal( + pub async fn submit_principal( &self, intent: PrincipalCommandIntent, principal: &VerifiedPrincipalEvidence, @@ -352,7 +352,7 @@ impl WorldMailbox { /// the tick task cannot know the live revision either. It is visible inside /// the world subtree and unreachable from runtime ingress, which builds /// `CallerId::Principal` from verified evidence and nothing else. - pub(crate) async fn submit_clock( + pub async fn submit_clock( &self, command_id: CommandId, minutes: TickMinutes, @@ -445,7 +445,7 @@ impl WorldMailbox { /// The tick driver's adjacency projection. It is on `WorldMailbox` and on /// neither `ControllerPort` nor `ElaborationPort`: a controller organ that /// tried to read adjacency would fail to compile. - pub(crate) async fn agency_graph(&self) -> Result { + pub async fn agency_graph(&self) -> Result { let (reply, response) = oneshot::channel(); self.sender .send(Request::AgencyGraph { reply }) @@ -458,7 +458,7 @@ impl WorldMailbox { .map_err(MailboxError::Kernel) } - pub(crate) async fn operator_log(&self) -> Result, MailboxError> { + pub async fn operator_log(&self) -> Result, MailboxError> { let (reply, response) = oneshot::channel(); self.sender .send(Request::OperatorLog { reply }) @@ -471,7 +471,7 @@ impl WorldMailbox { .map_err(MailboxError::Kernel) } - pub(crate) async fn snapshot(&self) -> Result { + pub async fn snapshot(&self) -> Result { let (reply, response) = oneshot::channel(); self.sender .send(Request::Snapshot { reply }) @@ -549,7 +549,7 @@ pub(crate) struct ElaborationPort { } impl ElaborationPort { - pub(crate) fn new(mailbox: WorldMailbox) -> Self { + pub fn new(mailbox: WorldMailbox) -> Self { Self { mailbox } } @@ -585,13 +585,13 @@ impl ElaborationPort { /// submission through `submit_principal` is refused once that expiry has /// passed, whatever the port itself still thinks the session's shape is. #[derive(Clone)] -pub(crate) struct SeedPort { +pub struct SeedPort { mailbox: WorldMailbox, principal: VerifiedPrincipalEvidence, } impl SeedPort { - pub(crate) fn new(mailbox: WorldMailbox, principal: VerifiedPrincipalEvidence) -> Self { + pub fn new(mailbox: WorldMailbox, principal: VerifiedPrincipalEvidence) -> Self { Self { mailbox, principal } } @@ -652,12 +652,12 @@ impl SeedPort { /// nothing, and returns a receipt rather than state — so a snapshot method here /// would be authority granted before the pass that needs it. #[derive(Clone)] -pub(crate) struct ConsumerPort { +pub struct ConsumerPort { mailbox: WorldMailbox, } impl ConsumerPort { - pub(crate) fn new(mailbox: WorldMailbox) -> Self { + pub fn new(mailbox: WorldMailbox) -> Self { Self { mailbox } } @@ -684,16 +684,16 @@ impl ConsumerPort { } #[derive(Clone)] -pub(crate) struct ControllerPort { +pub struct ControllerPort { mailbox: WorldMailbox, } impl ControllerPort { - pub(crate) fn new(mailbox: WorldMailbox) -> Self { + pub fn new(mailbox: WorldMailbox) -> Self { Self { mailbox } } - pub(crate) async fn snapshot(&self) -> Result { + pub async fn snapshot(&self) -> Result { self.mailbox.snapshot().await } @@ -718,7 +718,7 @@ impl ControllerPort { .await } - pub(crate) async fn submit_controller( + pub async fn submit_controller( &self, command_id: CommandId, opportunity: &DecisionOpportunity, @@ -920,7 +920,7 @@ async fn run_owner(mut owned: OwnedWorld, mut receiver: mpsc::Receiver) #[cfg(test)] mod tests { use super::*; - use crate::world::{ + use crate::{ CallerId, CommandBody, Declaration, DraftHandle, EntityDeclaration, EntityKind, Mismatch, NewController, PrincipalId, SubjectDeclaration, SubjectKind, WorldId, }; @@ -1391,7 +1391,7 @@ mod tests { .unwrap(); snapshot = mailbox.snapshot().await.unwrap(); } - let pick = |mode: crate::world::ControllerMode| { + let pick = |mode: crate::ControllerMode| { snapshot .opportunities .iter() @@ -1399,14 +1399,14 @@ mod tests { .expect("an active opportunity") .clone() }; - let bound = pick(crate::world::ControllerMode::OperationalAgent); - let unrelated = pick(crate::world::ControllerMode::NarrativePersona); + let bound = pick(crate::ControllerMode::OperationalAgent); + let unrelated = pick(crate::ControllerMode::NarrativePersona); let speak = |opportunity: &DecisionOpportunity, text: &str| DecisionInvocation { affordance: opportunity.affordance_ids[0], bindings: Vec::new(), proposed: Vec::new(), - speech: Some(crate::world::Statement::new(text).unwrap()), + speech: Some(crate::Statement::new(text).unwrap()), display: None, }; mailbox @@ -1454,17 +1454,17 @@ mod tests { .unwrap(); snapshot = mailbox.snapshot().await.unwrap(); } - assert_eq!(snapshot.phase, crate::world::WorldPhase::Active); + assert_eq!(snapshot.phase, crate::WorldPhase::Active); let forged = DecisionOpportunity { world_id: WorldId::issue(), revision: 0, - scope_digest: crate::world::ScopeDigest::fixture("sha256:not-a-scope"), - scope: crate::world::DecisionScope { - subject_id: crate::world::SubjectId::issue(), + scope_digest: crate::ScopeDigest::fixture("sha256:not-a-scope"), + scope: crate::DecisionScope { + subject_id: crate::SubjectId::issue(), }, - controller_id: crate::world::ControllerId::issue(), - controller_mode: crate::world::ControllerMode::OperationalAgent, - affordance_ids: vec![crate::world::AffordanceId::issue()], + controller_id: crate::ControllerId::issue(), + controller_mode: crate::ControllerMode::OperationalAgent, + affordance_ids: vec![crate::AffordanceId::issue()], }; let result = mailbox .submit_controller( @@ -1474,7 +1474,7 @@ mod tests { affordance: forged.affordance_ids[0], bindings: Vec::new(), proposed: Vec::new(), - speech: Some(crate::world::Statement::new("Let me in.").unwrap()), + speech: Some(crate::Statement::new("Let me in.").unwrap()), display: None, }, ) diff --git a/crates/ghostlight/src/patch.rs b/crates/ghostlight/src/patch.rs index f7280d1..fd3f7ca 100644 --- a/crates/ghostlight/src/patch.rs +++ b/crates/ghostlight/src/patch.rs @@ -26,7 +26,7 @@ use uuid::Uuid; #[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] #[serde(rename_all = "snake_case")] -pub(crate) enum EntityKind { +pub enum EntityKind { Place, Resource, Fact, @@ -36,7 +36,7 @@ pub(crate) enum EntityKind { /// Exactly the edge kinds that have a record shape. #[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] #[serde(rename_all = "snake_case")] -pub(crate) enum EdgeKind { +pub enum EdgeKind { Route, } @@ -209,7 +209,7 @@ pub(crate) struct Position { /// carries no components and can never be a role's target. #[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] #[serde(tag = "namespace", content = "kind", rename_all = "snake_case")] -pub(crate) enum RefKind { +pub enum RefKind { Subject(Option), Entity(EntityKind), Edge(EdgeKind), @@ -218,7 +218,7 @@ pub(crate) enum RefKind { #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] #[serde(transparent)] -pub(crate) struct DraftHandle(String); +pub struct DraftHandle(String); impl DraftHandle { pub(super) fn new(value: impl Into) -> Self { @@ -230,7 +230,7 @@ impl DraftHandle { /// There is no third form and no `From`. #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] #[serde(tag = "ref", content = "value", rename_all = "snake_case")] -pub(crate) enum Ref { +pub enum Ref { Existing(Id), Draft(DraftHandle), } @@ -238,7 +238,7 @@ pub(crate) enum Ref { /// A referent in any of the three referent namespaces, or in the catalog. #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] #[serde(tag = "namespace", content = "ref", rename_all = "snake_case")] -pub(crate) enum RefName { +pub enum RefName { Subject(Ref), Entity(Ref), Edge(Ref), @@ -251,13 +251,13 @@ pub(crate) enum RefName { /// what keeps genre out of the reducer. #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] #[serde(transparent)] -pub(crate) struct AffordanceKindName(pub(crate) String); +pub struct AffordanceKindName(pub String); /// A named slot in one affordance, bound to a referent at invocation time. /// Canonical text, `[a-z][a-z0-9_]{0,31}` — it becomes a tool parameter name. #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] #[serde(transparent)] -pub(crate) struct Role(pub(crate) String); +pub struct Role(pub(crate) String); /// What a role must be bound to. `RefKind` is reused rather than mirrored: it /// already spells subject, entity kind, and edge kind, so the binding kind-check @@ -275,15 +275,15 @@ pub(crate) struct RoleSpec { /// byte-identical statements are two facts. #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] #[serde(transparent)] -pub(crate) struct Statement(String); +pub struct Statement(String); impl Statement { - pub(crate) fn new(value: impl Into) -> Option { + pub fn new(value: impl Into) -> Option { let value = value.into(); is_canonical_text(&value).then_some(Self(value)) } - pub(crate) fn as_str(&self) -> &str { + pub fn as_str(&self) -> &str { &self.0 } } @@ -476,7 +476,7 @@ pub(crate) enum CommitmentKind { /// commitment by identity, so a structural key is the whole answer. #[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] #[serde(deny_unknown_fields)] -pub(crate) struct CommitmentKey { +pub struct CommitmentKey { pub(crate) command: CommandId, pub(crate) index: u32, } @@ -651,7 +651,7 @@ pub(crate) struct WorldScaleIntentRef { /// visible while reducing no target. #[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] #[serde(tag = "jurisdiction", content = "root", rename_all = "snake_case")] -pub(crate) enum JurisdictionKey { +pub enum JurisdictionKey { PlaceSubtree(EntityId), Uncovered, } @@ -883,8 +883,8 @@ pub(crate) struct OutcomeBand { /// `affordance_grants` and nothing else, so an entry carries no audience. #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(deny_unknown_fields)] -pub(crate) struct Affordance { - pub(crate) kind: AffordanceKindName, +pub struct Affordance { + pub kind: AffordanceKindName, /// Ordered, so the generated tool's parameters are stable across builds. pub(crate) roles: Vec, pub(crate) preconditions: Vec, @@ -912,7 +912,7 @@ pub(crate) struct AffordanceDeclaration { /// operations alike. #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] #[serde(tag = "site", content = "at", rename_all = "snake_case")] -pub(crate) enum Site { +pub enum Site { Declaration(DraftHandle), Operation(usize), } @@ -1198,14 +1198,14 @@ pub(crate) enum ComponentOp { /// deficit is nonzero, and the commit must satisfy what it answered. #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(tag = "answer", rename_all = "snake_case")] -pub(crate) enum PatchAnswer { +pub enum PatchAnswer { Boundary(super::CausalBoundary), Deficit(JurisdictionKey), } #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] #[serde(deny_unknown_fields)] -pub(crate) struct WorldPatch { +pub struct WorldPatch { pub(crate) declarations: Vec, pub(crate) operations: Vec, pub(crate) evidence: Vec, @@ -1282,7 +1282,7 @@ pub(crate) fn check_patch_caps(patch: &WorldPatch) -> Result<(), PatchDecodeErro /// controller-owned telemetry under its own row type and its own schema. #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] #[serde(tag = "mismatch", rename_all = "snake_case")] -pub(crate) enum Mismatch { +pub enum Mismatch { EmptyHandle { position: usize, }, @@ -7292,11 +7292,11 @@ mod catalog_tests { #[cfg(test)] mod tests { use super::*; - use crate::world::tests::{ + use crate::tests::{ FIXTURE_ENTITIES, activate, admit_topology, auth_principal, command, creation, owner, player, reject_owner, speak_entry, submit_owner, }; - use crate::world::{ + use crate::{ CallerId, CommandBody, CommandId, KernelError, SubmitReceipt, WorldKernel, WorldPhase, }; diff --git a/crates/ghostlight/src/sdk_inference.rs b/crates/ghostlight/src/sdk_inference.rs index 98d3644..eb0a507 100644 --- a/crates/ghostlight/src/sdk_inference.rs +++ b/crates/ghostlight/src/sdk_inference.rs @@ -8,7 +8,7 @@ use super::controllers::{ InferenceEvent, InferenceFault, InferenceOutput, InferencePort, InferenceRequest, - PreparedInference, REQUEST_EXPIRY, RESPONSE_TIMEOUT, ToolResultOracle, prepare_invocation, + PreparedInference, REQUEST_EXPIRY, RESPONSE_TIMEOUT, ToolResultOracle, unix_ms, }; use async_trait::async_trait; @@ -352,14 +352,14 @@ const SDK_RECEIPT_SCHEMA: &str = "ghostlight.sdk_inference_receipt.v1"; /// one. It holds even with no SDK port built, so a `claude-`prefixed lane on a /// connector-only deployment is refused at open instead of quietly reaching the /// wrong backend. -pub(crate) const DEFAULT_SDK_MODEL_PREFIX: &str = "claude"; +pub const DEFAULT_SDK_MODEL_PREFIX: &str = "claude"; /// Everything the SDK sidecar needs to open, gathered so `open_inference` takes /// a binding rather than reading the environment itself. -pub(crate) struct SdkBinding { - pub(crate) sidecar_entry: PathBuf, - pub(crate) caller_runtime_id: String, - pub(crate) model_prefix: String, +pub struct SdkBinding { + pub sidecar_entry: PathBuf, + pub caller_runtime_id: String, + pub model_prefix: String, } /// The Claude Agent SDK behind Ghostlight's inference seam, through a Node @@ -666,7 +666,7 @@ fn assemble_output( #[async_trait] impl InferencePort for SdkInferencePort { fn prepare(&self, request: InferenceRequest) -> Result { - prepare_invocation( + PreparedInference::prepare( &self.caller_runtime_id, unix_ms()?.saturating_add(REQUEST_EXPIRY.as_millis() as u64), request, @@ -775,18 +775,18 @@ impl InferencePort for RoutedInferencePort { #[cfg(test)] mod tests { use super::*; - use crate::world::CommandId; - use crate::world::controllers::{ + use crate::CommandId; + use crate::controllers::{ ConnectorBinding, ControllerModels, ControllerOpenError, InferencePurpose, OperationalOracle, RequestShape, catalog_tools, open_inference, tool_request, }; - use crate::world::elaboration::{ + use crate::elaboration::{ ElaborationLoopEvaluation, ElaborationOracle, SEED_ROUND_BUDGET, evaluate_elaboration_loop, }; - use crate::world::patch::{ + use crate::patch::{ RECORD_GAP_PATCH_TOOL, SUBMIT_PATCH_TOOL, kernel_speak_entry, patch_tools, }; - use crate::world::{AffordanceId, AffordanceSnapshot}; + use crate::{AffordanceId, AffordanceSnapshot}; use codex_connector::CodexToolDefinition; use std::collections::VecDeque; @@ -1041,7 +1041,7 @@ mod tests { .infer(prepared) .await .expect_err("a closed pipe produced an output"); - assert!(fault.recovery_required(), "{fault:?}"); + assert!(fault.requires_recovery(), "{fault:?}"); assert!(!fault.integrity_was_violated()); assert_eq!(link.restarts(), 1); } @@ -1229,13 +1229,13 @@ mod tests { async fn an_expired_invocation_is_recovery_required() { let link = ScriptedLink::new(Vec::new()); let port = SdkInferencePort::new(link.clone(), TEST_RUNTIME); - let stale = prepare_invocation(TEST_RUNTIME, 1_000, prose_request("Say something true.")) + let stale = PreparedInference::prepare(TEST_RUNTIME, 1_000, prose_request("Say something true.")) .expect("the stale invocation builds"); let fault = port .infer(stale) .await .expect_err("a stale invocation re-ran"); - assert!(fault.recovery_required(), "{fault:?}"); + assert!(fault.requires_recovery(), "{fault:?}"); assert!( link.sent().is_empty(), "a stale invocation reached the pipe" @@ -1282,7 +1282,7 @@ mod tests { .infer(prepared) .await .expect_err("a contradicted terminal produced an output"); - assert!(fault.recovery_required(), "{fault:?}"); + assert!(fault.requires_recovery(), "{fault:?}"); assert_eq!(link.restarts(), 1); assert_eq!( link.sent() @@ -1379,7 +1379,7 @@ mod tests { ); } - /// Soul: the port's `prepare` is `prepare_invocation` and nothing else, so + /// Soul: the port's `prepare` is `PreparedInference::prepare` and nothing else, so /// everything but the expiry stamp is identical to what any other port /// would have written for the same request. #[test] @@ -1387,7 +1387,7 @@ mod tests { let port = SdkInferencePort::new(ScriptedLink::new(Vec::new()), TEST_RUNTIME); let request = prose_request("Say something true."); let through_port = port.prepare(request.clone()).expect("the port prepares"); - let through_owner = prepare_invocation(TEST_RUNTIME, 4_102_444_800_000, request.clone()) + let through_owner = PreparedInference::prepare(TEST_RUNTIME, 4_102_444_800_000, request.clone()) .expect("the owner prepares"); assert_eq!(through_port.purpose, through_owner.purpose); assert_eq!( @@ -1464,7 +1464,7 @@ mod tests { for reason in [RateLimited, Overloaded, ServerError, ApiTimeout] { let fault = reason.into_fault("detail".into()); assert!( - !fault.recovery_required() && !fault.integrity_was_violated(), + !fault.requires_recovery() && !fault.integrity_was_violated(), "{reason:?} was not retryable" ); } @@ -1480,7 +1480,7 @@ mod tests { Unknown, ] { assert!( - reason.into_fault("detail".into()).recovery_required(), + reason.into_fault("detail".into()).requires_recovery(), "{reason:?} was not recovery-required" ); } @@ -1523,7 +1523,7 @@ mod tests { #[tokio::test] async fn soul_a_lend_refused_before_the_query_is_still_reclaimed() { let port = SdkInferencePort::new(ScriptedLink::new(Vec::new()), TEST_RUNTIME); - let stale = prepare_invocation(TEST_RUNTIME, 1_000, prose_request("Say something true.")) + let stale = PreparedInference::prepare(TEST_RUNTIME, 1_000, prose_request("Say something true.")) .expect("the stale invocation builds"); port.lend_tool_results( &stale, @@ -1771,7 +1771,7 @@ mod tests { ); assert!(connector_only.route(TEST_MODEL).is_none()); let prepared = - prepare_invocation(TEST_RUNTIME, 4_102_444_800_000, prose_request("Say it.")).unwrap(); + PreparedInference::prepare(TEST_RUNTIME, 4_102_444_800_000, prose_request("Say it.")).unwrap(); for fault in [ connector_only .prepare(prose_request("Say it.")) @@ -1821,11 +1821,7 @@ mod tests { format!("GHOSTLIGHT_SDK{}", "_CREDENTIAL"), ]; let mut sources = Vec::new(); - for file in [ - "world/sdk_inference.rs", - "world/controllers.rs", - "runtime.rs", - ] { + for file in ["sdk_inference.rs", "controllers.rs"] { let text = std::fs::read_to_string(root.join(file)) .expect("the source reads") .replace("\r\n", "\n"); diff --git a/crates/ghostlight/src/vault.rs b/crates/ghostlight/src/vault.rs index e79c47e..905a775 100644 --- a/crates/ghostlight/src/vault.rs +++ b/crates/ghostlight/src/vault.rs @@ -33,7 +33,7 @@ const MAX_NOTE_BYTES: u64 = 512 * 1024; /// must be too. `Root` names no path at all; `Scope` carries only the /// vault-relative scope string the caller supplied. #[derive(Debug, Error)] -pub(crate) enum VaultError { +pub enum VaultError { #[error("vault root is not a readable directory")] Root, #[error("vault scope escapes its root: {0}")] @@ -60,7 +60,7 @@ struct VaultNote { links: Vec, } -pub(crate) struct VaultEvidenceSource { +pub struct VaultEvidenceSource { notes: Vec, /// Many-to-one: filename stem, frontmatter title, and each alias all key /// the same note. Ambiguity is resolved by the shortest reference path, and @@ -74,7 +74,7 @@ impl VaultEvidenceSource { /// so `..`, an absolute scope, and a symlink out are all refused here. A /// spoiler tier is a directory in every vault that has one, so it is /// expressed as a scope and there is no tier field anywhere in this file. - pub(crate) fn open(root: &Path, scope: &str) -> Result { + pub fn open(root: &Path, scope: &str) -> Result { let root = root.canonicalize().map_err(|_| VaultError::Root)?; if !root.is_dir() { return Err(VaultError::Root); diff --git a/deployment/idunn/recipe.toml b/deployment/idunn/recipe.toml index d3a28ab..5d56a68 100644 --- a/deployment/idunn/recipe.toml +++ b/deployment/idunn/recipe.toml @@ -10,7 +10,13 @@ runner = "rust-build" argv = ["cargo", "test", "--locked", "-p", "ghostlight-persona-projection"] [[steps]] -id = "test-world-owner" +id = "test-world-library" +phase = "test" +runner = "rust-build" +argv = ["cargo", "test", "--locked", "-p", "ghostlight"] + +[[steps]] +id = "test-dungeon" phase = "test" runner = "rust-build" argv = ["cargo", "test", "--locked", "-p", "ghostlight-dungeon", "--bin", "ghostlight-dungeon"] @@ -48,10 +54,9 @@ argv = [ "test", "--locked", "-p", - "ghostlight-dungeon", - "--bin", - "ghostlight-dungeon", - "world::controllers::tests::real_codex_connector_cognition_modes_commit_speech", + "ghostlight", + "--lib", + "controllers::tests::real_codex_connector_cognition_modes_commit_speech", "--", "--ignored", "--exact", diff --git a/docs/architecture/ghostlight-session-zero.md b/docs/architecture/ghostlight-session-zero.md index f3c4a91..e415f7f 100644 --- a/docs/architecture/ghostlight-session-zero.md +++ b/docs/architecture/ghostlight-session-zero.md @@ -18,9 +18,9 @@ binding, and elaborator sessions in Draft. Other consumers use the same capabilities with their own policy; Delvehold, for one, starts from a `Uniform` detail rule. -Source does not yet match this boundary: the world kernel lives in -`crates/ghostlight-dungeon/src/world/` beside Dungeon's runtime. Extracting it -into a library crate is the first pass of plan step 15. +The world kernel lives in the `ghostlight` library crate +(`crates/ghostlight/`); `ghostlight-dungeon` consumes it across a public +boundary and cannot reach its internals. ## Objective diff --git a/docs/architecture/ghostlight-world-ontology.md b/docs/architecture/ghostlight-world-ontology.md index d9e33d4..5d2cb93 100644 --- a/docs/architecture/ghostlight-world-ontology.md +++ b/docs/architecture/ghostlight-world-ontology.md @@ -937,8 +937,8 @@ invariants did not ask for. ## Build budget No new crate, binary, dependency, or service. Types and reduction inside -`crates/ghostlight-dungeon/src/world/`, one new `patch` module, one new -`affordance` module, focused `ghostlight-dungeon` tests. No workspace-wide or +`crates/ghostlight/src/`, one new `patch` module, one new `affordance` module, +focused `ghostlight` tests. No workspace-wide or release build is admitted for this stage. ## Verification contract diff --git a/docs/public-architecture/index.md b/docs/public-architecture/index.md index a8a9198..dd235fc 100644 --- a/docs/public-architecture/index.md +++ b/docs/public-architecture/index.md @@ -43,7 +43,8 @@ unchanged. ## Scope -The public reference follows the current source in `crates/ghostlight-dungeon`. +The public reference follows the current source in `crates/ghostlight` (the +library) and `crates/ghostlight-dungeon` (the daemon that consumes it). Research plans and older fixture pipelines remain in the repository but are not presented as live runtime stages. Physical model names are deployment choices; the architecture names the stable logical tiers: Fast, Balanced, and Capable.