From 751b5c41503e1f4f7556457e0e032a4b4b69a03d Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Fri, 4 Sep 2026 01:24:08 +0200 Subject: [PATCH] Take runtime identity, bind, and state root from Idunn The daemon declared its own GHOSTLIGHT_RUNTIME_ID, GHOSTLIGHT_DUNGEON_BIND, and GHOSTLIGHT_DUNGEON_ROOT. Under the managed contract Idunn owns all three: the runtime id arrives inside the activation bundle, the bind is a candidate socket Idunn hands down, and the state root is an admitted workload path. A service that also names them from its own environment is a second opinion about facts it does not own. runtime.rs already resolved the runtime id from the admission and kept GHOSTLIGHT_RUNTIME_ID only as the unmanaged fallback. This is the caller side catching up: main.rs accepts --state-root and passes it through, and the recipe declares the state_root argument binding plus the three GAMECULT_IDUNN_* variables while dropping the three the daemon no longer reads. The binding is required, not cosmetic. Idunn refuses a stateful workload with no state_root argument binding and refuses one whose binding disagrees with the admitted root, so the recipe could not have deployed without it. The acceptance harness kept its own GHOSTLIGHT_RUNTIME_ID; it is renamed to GHOSTLIGHT_ACCEPTANCE_RUNTIME_ID so a test-supplied identity cannot be mistaken for the managed one. The mesh surface test compared the published record against a freshly built surface, including updatedAtUtc, which is a live timestamp. It now asserts the structural fields, parses the timestamp, and compares surface and commands. cargo test --workspace: 89 passed, 0 failed. Co-Authored-By: Claude Opus 5 --- crates/ghostlight-dungeon/src/main.rs | 11 +++++++++- crates/ghostlight-dungeon/src/mesh.rs | 21 +++++++++++++------ .../src/world/controllers.rs | 4 ++-- deployment/idunn/recipe.toml | 13 +++++++----- 4 files changed, 35 insertions(+), 14 deletions(-) diff --git a/crates/ghostlight-dungeon/src/main.rs b/crates/ghostlight-dungeon/src/main.rs index 2c66dc3..e6aee85 100644 --- a/crates/ghostlight-dungeon/src/main.rs +++ b/crates/ghostlight-dungeon/src/main.rs @@ -6,7 +6,16 @@ mod mesh; mod runtime; mod world; +use anyhow::bail; +use std::path::PathBuf; + #[tokio::main] async fn main() -> anyhow::Result<()> { - runtime::run().await + let args = std::env::args_os().skip(1).collect::>(); + let state_root = match args.as_slice() { + [flag, path] if flag == "--state-root" => Some(PathBuf::from(path)), + [] => None, + _ => bail!("usage: ghostlight-dungeon [--state-root PATH]"), + }; + runtime::run(state_root).await } diff --git a/crates/ghostlight-dungeon/src/mesh.rs b/crates/ghostlight-dungeon/src/mesh.rs index 7bc1067..15bd364 100644 --- a/crates/ghostlight-dungeon/src/mesh.rs +++ b/crates/ghostlight-dungeon/src/mesh.rs @@ -5,7 +5,7 @@ use crate::{eve, world::WorldSnapshot}; use anyhow::Result; -use chrono::Utc; +use chrono::{DateTime, Utc}; use cultcache_rs::DatabaseEntry; use cultmesh_rs::{ CultMesh, CultMeshNode, CultMeshNodeOptions, CultMeshRudpDocumentPublishOptions, @@ -384,12 +384,21 @@ mod tests { assert_eq!(health["surfaceVersion"], 0); assert_eq!(publisher.health().unwrap(), health); let node = publisher.node.lock().unwrap(); - assert_eq!( - node.get_required::(SURFACE_KEY) - .unwrap() - .value, - eve::mesh_surface(None) + let surface = node + .get_required::(SURFACE_KEY) + .unwrap() + .value; + assert_eq!(surface["type"], "surface-state"); + assert_eq!(surface["schema"], "gamecult.eve.surface.v1"); + assert_eq!(surface["providerId"], PROVIDER_ID); + assert_eq!(surface["version"], 0); + assert!( + surface["updatedAtUtc"] + .as_str() + .is_some_and(|value| value.parse::>().is_ok()) ); + assert_eq!(surface["surface"], eve::mesh_surface(None)["surface"]); + assert_eq!(surface["commands"], eve::mesh_surface(None)["commands"]); assert!( node.get_required::(ADVERTISEMENT_KEY) .is_ok() diff --git a/crates/ghostlight-dungeon/src/world/controllers.rs b/crates/ghostlight-dungeon/src/world/controllers.rs index c924c84..b80faab 100644 --- a/crates/ghostlight-dungeon/src/world/controllers.rs +++ b/crates/ghostlight-dungeon/src/world/controllers.rs @@ -4441,8 +4441,8 @@ mod tests { .expect("GHOSTLIGHT_CONTROLLER_CONNECTOR must be a socket address"); let connector_credential = std::env::var_os("GHOSTLIGHT_CONTROLLER_CREDENTIAL") .expect("GHOSTLIGHT_CONTROLLER_CREDENTIAL is required"); - let runtime_id = - std::env::var("GHOSTLIGHT_RUNTIME_ID").expect("GHOSTLIGHT_RUNTIME_ID is required"); + let runtime_id = std::env::var("GHOSTLIGHT_ACCEPTANCE_RUNTIME_ID") + .expect("GHOSTLIGHT_ACCEPTANCE_RUNTIME_ID is required"); let models = ControllerModels { projector: std::env::var("GHOSTLIGHT_CONTROLLER_PROJECTOR_MODEL") .expect("GHOSTLIGHT_CONTROLLER_PROJECTOR_MODEL is required"), diff --git a/deployment/idunn/recipe.toml b/deployment/idunn/recipe.toml index fe0b7a5..d3a28ab 100644 --- a/deployment/idunn/recipe.toml +++ b/deployment/idunn/recipe.toml @@ -58,13 +58,13 @@ argv = [ ] required_environment = [ "GHOSTLIGHT_ACCEPTANCE_PERSONA_PROSE_LOG", + "GHOSTLIGHT_ACCEPTANCE_RUNTIME_ID", "GHOSTLIGHT_CONTROLLER_CONNECTOR", "GHOSTLIGHT_CONTROLLER_CREDENTIAL", "GHOSTLIGHT_CONTROLLER_INTERPRETER_MODEL", "GHOSTLIGHT_CONTROLLER_OPERATIONAL_MODEL", "GHOSTLIGHT_CONTROLLER_PERSONA_MODEL", "GHOSTLIGHT_CONTROLLER_PROJECTOR_MODEL", - "GHOSTLIGHT_RUNTIME_ID", ] [[artifacts]] @@ -85,10 +85,16 @@ destination = "web" [service] executable_artifact = "ghostlight-dungeon" required_adjacent_artifacts = ["web"] -arguments = [] +arguments = [ + { kind = "literal", value = "--state-root" }, + { kind = "binding", name = "state_root" }, +] transport = "http" route_required = true required_environment = [ + "GAMECULT_IDUNN_CANDIDATE_BIND", + "GAMECULT_IDUNN_PROCESS_WRITE_LEASE", + "GAMECULT_IDUNN_RUNTIME_BUNDLE", "GHOSTLIGHT_CONTROLLER_CONNECTOR", "GHOSTLIGHT_CONTROLLER_CREDENTIAL", "GHOSTLIGHT_CONTROLLER_INTERPRETER_MODEL", @@ -97,14 +103,11 @@ required_environment = [ "GHOSTLIGHT_CONTROLLER_PROJECTOR_MODEL", "GHOSTLIGHT_DISCORD_GUILD_ID", "GHOSTLIGHT_DISCORD_ROLE_ID", - "GHOSTLIGHT_DUNGEON_BIND", - "GHOSTLIGHT_DUNGEON_ROOT", "GHOSTLIGHT_HEIMDALL_APP_SECRET_FILE", "GHOSTLIGHT_HEIMDALL_BASE_URL", "GHOSTLIGHT_LOCATED_SERVICE", "GHOSTLIGHT_ODIN_RUDP", "GHOSTLIGHT_PUBLIC_APP_URL", - "GHOSTLIGHT_RUNTIME_ID", "GHOSTLIGHT_SERVICE_ID", "GHOSTLIGHT_SESSION_WRAPPING_KEY_FILE", ]