Give the world library a sealed public boundary
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 <noreply@anthropic.com>
This commit is contained in:
parent
108b691015
commit
f859d2c4e3
27 changed files with 1074 additions and 947 deletions
24
Cargo.lock
generated
24
Cargo.lock
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
[workspace]
|
||||
members = [
|
||||
"crates/ghostlight",
|
||||
"crates/ghostlight-dungeon",
|
||||
"crates/ghostlight-persona-projection",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<Utc>,
|
||||
}
|
||||
|
||||
impl VerifiedPrincipalEvidence {
|
||||
pub(crate) fn account_subject_hash(&self) -> &str {
|
||||
&self.account_subject_hash
|
||||
}
|
||||
|
||||
pub(crate) fn valid_until(&self) -> DateTime<Utc> {
|
||||
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<String>,
|
||||
valid_until: DateTime<Utc>,
|
||||
) -> 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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ mod heimdall;
|
|||
mod idunn_health;
|
||||
mod mesh;
|
||||
mod runtime;
|
||||
mod world;
|
||||
|
||||
use anyhow::bail;
|
||||
use std::path::PathBuf;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<SocketAddr>,
|
||||
|
|
@ -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<ConsumerRegistry> {
|
||||
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<Option<WorldSnapshot>
|
|||
/// a snapshot field, so no controller lane can reach it.
|
||||
async fn current_operator_view(
|
||||
state: &AppState,
|
||||
) -> anyhow::Result<(Option<WorldSnapshot>, Vec<crate::world::OperatorEvent>)> {
|
||||
) -> anyhow::Result<(Option<WorldSnapshot>, Vec<ghostlight::OperatorEvent>)> {
|
||||
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<dyn crate::world::InferencePort> = match live.trace {
|
||||
Some(path) => Arc::new(crate::world::TracingInferencePort::new(inference, path)),
|
||||
let inference: Arc<dyn ghostlight::InferencePort> = 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<PreparedInference, InferenceFault> {
|
||||
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<PreparedInference, InferenceFault> {
|
||||
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<InferenceOutput, InferenceFault> {
|
||||
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<PreparedInference, InferenceFault> {
|
||||
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<InferenceOutput, InferenceFault> {
|
||||
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),
|
||||
);
|
||||
|
|
|
|||
25
crates/ghostlight/Cargo.toml
Normal file
25
crates/ghostlight/Cargo.toml
Normal file
|
|
@ -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"
|
||||
|
|
@ -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<DecisionEvent, KernelError> {
|
||||
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<ComponentOp>) -> Vec<crate::world::Mismatch> {
|
||||
fn refuse(&mut self, ops: Vec<ComponentOp>) -> Vec<crate::Mismatch> {
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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<Self> {
|
||||
|
|
@ -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<Self> {
|
||||
pub fn new(minutes: u32) -> Option<Self> {
|
||||
(1..=MAX_ROUTE_COST)
|
||||
.contains(&minutes)
|
||||
.then_some(Self(minutes))
|
||||
}
|
||||
|
||||
pub(crate) fn minutes(self) -> u32 {
|
||||
pub fn minutes(self) -> u32 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<WorldId>,
|
||||
/// 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<String, [u8; 32]>,
|
||||
}
|
||||
|
||||
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<Path>) -> Result<Self, String> {
|
||||
pub fn from_secret_file(path: impl AsRef<Path>) -> Result<Self, String> {
|
||||
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<Vec<u8
|
|||
rmp_serde::to_vec_named(document).map_err(|error| error.to_string())
|
||||
}
|
||||
|
||||
pub(crate) fn encode_receipt(receipt: &ConsumerReceiptDocument) -> Result<Vec<u8>, String> {
|
||||
pub fn encode_receipt(receipt: &ConsumerReceiptDocument) -> Result<Vec<u8>, 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;
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -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<Cell>,
|
||||
pub struct Cover {
|
||||
pub tick: TickIndex,
|
||||
pub cells: Vec<Cell>,
|
||||
/// 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<Self, CoverBudgetError> {
|
||||
pub fn validated(self) -> Result<Self, CoverBudgetError> {
|
||||
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<SubjectId>,
|
||||
/// 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<Vec<Sub
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::world::{DecisionScope, ScopeDigest};
|
||||
use crate::{DecisionScope, ScopeDigest};
|
||||
|
||||
fn subject(index: u128) -> SubjectId {
|
||||
serde_json::from_value(serde_json::json!(Uuid::from_u128(index).to_string()))
|
||||
|
|
|
|||
|
|
@ -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<Mismatch>,
|
||||
}
|
||||
|
|
@ -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<String>,
|
||||
}
|
||||
|
||||
#[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<Vec<EvidenceReceipt>, 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<dyn InferencePort>,
|
||||
evidence: Arc<dyn EvidenceSource>,
|
||||
|
|
@ -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<JurisdictionKey> = 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<CommandId, ControllerError>
|
|||
/// 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<dyn InferencePort>,
|
||||
evidence: Arc<dyn EvidenceSource>,
|
||||
|
|
@ -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<SeedOutcome, ControllerError> {
|
||||
pub async fn sweep(&self, sessions: usize) -> Result<SeedOutcome, ControllerError> {
|
||||
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<ScaleDeficitRow> {
|
||||
pub fn select_row(snapshot: &WorldSnapshot) -> Option<ScaleDeficitRow> {
|
||||
snapshot
|
||||
.scale_deficit
|
||||
.iter()
|
||||
|
|
|
|||
|
|
@ -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<crate::world::WorldCommit>| {
|
||||
let rows_of = |commits: Vec<crate::WorldCommit>| {
|
||||
let mut rows: Vec<CultCacheEnvelope> = commits
|
||||
.iter()
|
||||
.map(|commit| {
|
||||
|
|
@ -2647,17 +2647,17 @@ mod custody_tests {
|
|||
rows
|
||||
};
|
||||
|
||||
let honest: Vec<crate::world::WorldCommit> =
|
||||
let honest: Vec<crate::WorldCommit> =
|
||||
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<crate::world::WorldCommit> = honest
|
||||
let forged: Vec<crate::WorldCommit> = 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 { .. })
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -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<Request>,
|
||||
}
|
||||
|
||||
|
|
@ -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<Path>) -> Result<(Self, JoinHandle<()>), KernelError> {
|
||||
pub fn open(path: impl AsRef<Path>) -> 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<AgencyGraph, MailboxError> {
|
||||
pub async fn agency_graph(&self) -> Result<AgencyGraph, MailboxError> {
|
||||
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<Vec<OperatorEvent>, MailboxError> {
|
||||
pub async fn operator_log(&self) -> Result<Vec<OperatorEvent>, 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<WorldSnapshot, MailboxError> {
|
||||
pub async fn snapshot(&self) -> Result<WorldSnapshot, MailboxError> {
|
||||
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<WorldSnapshot, MailboxError> {
|
||||
pub async fn snapshot(&self) -> Result<WorldSnapshot, MailboxError> {
|
||||
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<Request>)
|
|||
#[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,
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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<SubjectKind>),
|
||||
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<String>) -> Self {
|
||||
|
|
@ -230,7 +230,7 @@ impl DraftHandle {
|
|||
/// There is no third form and no `From<String>`.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[serde(tag = "ref", content = "value", rename_all = "snake_case")]
|
||||
pub(crate) enum Ref<Id> {
|
||||
pub enum Ref<Id> {
|
||||
Existing(Id),
|
||||
Draft(DraftHandle),
|
||||
}
|
||||
|
|
@ -238,7 +238,7 @@ pub(crate) enum Ref<Id> {
|
|||
/// 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<SubjectId>),
|
||||
Entity(Ref<EntityId>),
|
||||
Edge(Ref<EdgeId>),
|
||||
|
|
@ -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<String>) -> Option<Self> {
|
||||
pub fn new(value: impl Into<String>) -> Option<Self> {
|
||||
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<RoleSpec>,
|
||||
pub(crate) preconditions: Vec<Precondition>,
|
||||
|
|
@ -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<Declaration>,
|
||||
pub(crate) operations: Vec<ComponentOp>,
|
||||
pub(crate) evidence: Vec<EvidenceRef>,
|
||||
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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<PreparedInference, InferenceFault> {
|
||||
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");
|
||||
|
|
|
|||
|
|
@ -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<String>,
|
||||
}
|
||||
|
||||
pub(crate) struct VaultEvidenceSource {
|
||||
pub struct VaultEvidenceSource {
|
||||
notes: Vec<VaultNote>,
|
||||
/// 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<Self, VaultError> {
|
||||
pub fn open(root: &Path, scope: &str) -> Result<Self, VaultError> {
|
||||
let root = root.canonicalize().map_err(|_| VaultError::Root)?;
|
||||
if !root.is_dir() {
|
||||
return Err(VaultError::Root);
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Reference in a new issue