From f859d2c4e38eac29dabd213a7f017b1fa285cbe9 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Wed, 16 Sep 2026 02:06:07 +0200 Subject: [PATCH] Give the world library a sealed public boundary MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Completes the move begun in the previous commit, which does not build on its own. `ghostlight` is now a library crate with an explicit public surface; `ghostlight-dungeon` is a consumer that reaches the kernel through it and cannot reach past it. Deleted first: `mod world;`, the nine unused re-exports, the `#[cfg(test)]` re-export block, `fixture_controller_opportunities`, the five inference `fixture_*` items, Dungeon's own `VerifiedPrincipalEvidence`, and Dungeon's direct `ghostlight-persona-projection` dependency. The seal. `WorldKernel`, `WorldState`, `reduce`, `apply_effect`, `CommandEnvelope`, `AuthenticatedCaller`, the journal writer, every ID `issue()`, and `ScopeDigest::fixture` carry no `pub` of any kind and no feature gate; the crate declares no `[features]`, so no build configuration can lift them. Ten `compile_fail` doc-tests stand outside the crate and pin each one to its error code. The principal seam. The library owns `VerifiedPrincipalEvidence` with a public `new`; `mailbox` still bounds it by `valid_until` at ingress. A Dungeon test counts the minters in production source and fails at anything but one. The port contract (Q1-7). `PreparedInference::prepare` (renamed from the free `prepare_invocation`), `InferenceOutput::new`, and `InferenceFault::{retryable, recovery_required, integrity_violation}` are now the port's public constructors, so a consumer-implemented `InferencePort` can answer without test-only wrappers. `TracingInferencePort` is an ungated decorator. The tick-ordering test (Q1-8) derives its cover from a real active world through `derive_cover` and asserts two cells; its ordering and shared-tick assertions are unchanged. Behaviour is unchanged. The library's 420 unit tests are the base `world::` set with the prefix stripped — the names diff empty. Dungeon keeps all 49 of its names and gains two Soul tests the boundary requires. No schema string, digest preimage, or store layout is touched. Windows verification only; the Linux release is Idunn's build. Co-Authored-By: Claude Opus 5 --- Cargo.lock | 24 +- Cargo.toml | 1 + README.md | 8 +- crates/ghostlight-dungeon/Cargo.toml | 2 +- crates/ghostlight-dungeon/src/app_session.rs | 82 ++- crates/ghostlight-dungeon/src/eve.rs | 22 +- crates/ghostlight-dungeon/src/idunn_health.rs | 2 +- crates/ghostlight-dungeon/src/main.rs | 1 - crates/ghostlight-dungeon/src/mesh.rs | 6 +- crates/ghostlight-dungeon/src/runtime.rs | 193 +++--- crates/ghostlight/Cargo.toml | 25 + crates/ghostlight/src/action.rs | 66 +- crates/ghostlight/src/clock.rs | 8 +- crates/ghostlight/src/consumer.rs | 28 +- crates/ghostlight/src/controllers.rs | 498 +++++++------- crates/ghostlight/src/cover.rs | 42 +- crates/ghostlight/src/elaboration.rs | 32 +- crates/ghostlight/src/journal.rs | 150 ++--- crates/ghostlight/src/lib.rs | 633 ++++++++++-------- crates/ghostlight/src/mailbox.rs | 64 +- crates/ghostlight/src/patch.rs | 42 +- crates/ghostlight/src/sdk_inference.rs | 50 +- crates/ghostlight/src/vault.rs | 6 +- deployment/idunn/recipe.toml | 15 +- docs/architecture/ghostlight-session-zero.md | 6 +- .../architecture/ghostlight-world-ontology.md | 4 +- docs/public-architecture/index.md | 3 +- 27 files changed, 1070 insertions(+), 943 deletions(-) create mode 100644 crates/ghostlight/Cargo.toml 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.