diff --git a/epiphany-core/src/bin/epiphany-prompt-context-smoke.rs b/epiphany-core/src/bin/epiphany-prompt-context-smoke.rs index 7c5c43c9..59235822 100644 --- a/epiphany-core/src/bin/epiphany-prompt-context-smoke.rs +++ b/epiphany-core/src/bin/epiphany-prompt-context-smoke.rs @@ -20,15 +20,17 @@ use epiphany_core::query_epiphany_local_verse_context; use epiphany_core::render_epiphany_prompt_context; use epiphany_core::seed_epiphany_local_verse_context; use serde_json::json; -use std::env; use std::path::PathBuf; fn main() -> Result<()> { - let store = env::args().nth(1).map(PathBuf::from).unwrap_or_else(|| { - PathBuf::from(".epiphany-smoke") - .join("cultmesh") - .join("epiphany-prompt-context.ccmp") - }); + if std::env::args().nth(1).is_some() { + anyhow::bail!( + "prompt-context smoke accepts no arguments and writes only beneath .epiphany-smoke" + ); + } + let store = PathBuf::from(".epiphany-smoke") + .join("cultmesh") + .join("epiphany-prompt-context.ccmp"); if let Some(parent) = store.parent() { std::fs::create_dir_all(parent)?; } diff --git a/epiphany-core/src/coordinator_launch_context.rs b/epiphany-core/src/coordinator_launch_context.rs index d0a21cb1..02fe51f7 100644 --- a/epiphany-core/src/coordinator_launch_context.rs +++ b/epiphany-core/src/coordinator_launch_context.rs @@ -6,6 +6,8 @@ use crate::EpiphanyMemoryContextQuery; use crate::EpiphanyMemoryProfile; use crate::EpiphanyPromptContextInput; use crate::RuntimeHandsReceiptChainSummary; +use crate::load_epiphany_cultmesh_cluster_topology; +use crate::load_epiphany_cultmesh_status; use crate::load_latest_epiphany_cultmesh_work_loop_telemetry; use crate::load_memory_graph_snapshot; use crate::memory_graph_from_epiphany_graphs; @@ -16,7 +18,6 @@ use crate::runtime_hands_command_receipt; use crate::runtime_hands_commit_receipt; use crate::runtime_hands_patch_receipt; use crate::runtime_latest_hands_receipt_chain_after; -use crate::seed_epiphany_local_verse_context; use crate::write_epiphany_cultmesh_work_loop_telemetry; use crate::write_memory_graph_snapshot; use chrono::SecondsFormat; @@ -72,17 +73,35 @@ pub fn render_launch_dynamic_prompt_context( focus: String, ) -> Result { let local_verse_store = local_verse_store_path(runtime_store_path); - seed_epiphany_local_verse_context( + load_epiphany_cultmesh_status(&local_verse_store, EPIPHANY_LOCAL_VERSE_RUNTIME_ID) + .map_err(|error| { + format!( + "failed to inspect local Verse context store {}: {error}", + local_verse_store.display() + ) + })? + .ok_or_else(|| { + format!( + "local Verse is not bootstrapped at {}; initialize it before building worker launch context", + local_verse_store.display() + ) + })?; + let topology = load_epiphany_cultmesh_cluster_topology( &local_verse_store, EPIPHANY_LOCAL_VERSE_RUNTIME_ID, - Utc::now().to_rfc3339_opts(SecondsFormat::Secs, true), ) .map_err(|error| { format!( - "failed to seed local Verse context store {}: {error}", + "failed to inspect local Verse topology store {}: {error}", local_verse_store.display() ) })?; + if topology.is_empty() { + return Err(format!( + "local Verse has no persisted cluster topology at {}; initialize it before building worker launch context", + local_verse_store.display() + )); + } let local_verse = query_epiphany_local_verse_context(&local_verse_store, EPIPHANY_LOCAL_VERSE_RUNTIME_ID) .map_err(|error| { @@ -681,6 +700,7 @@ mod tests { use crate::put_hands_commit_receipt; use crate::put_hands_patch_receipt; use crate::runtime_worker_launch_request; + use crate::seed_epiphany_local_verse_context; use epiphany_state_model::EpiphanyAcceptanceReceipt; use std::fs; use uuid::Uuid; @@ -713,6 +733,11 @@ mod tests { objective: Some("Test launch context.".to_string()), ..Default::default() }; + seed_epiphany_local_verse_context( + local_verse_store_path(&runtime_store), + EPIPHANY_LOCAL_VERSE_RUNTIME_ID, + "2026-07-12T00:00:00Z", + )?; let rendered = render_launch_dynamic_prompt_context( &runtime_store, @@ -771,6 +796,33 @@ mod tests { Ok(()) } + #[test] + fn launch_context_refuses_to_bootstrap_shared_state() -> anyhow::Result<()> { + let temp = std::env::temp_dir().join(format!( + "epiphany-launch-context-unbootstrapped-{}", + Uuid::new_v4() + )); + fs::create_dir_all(&temp)?; + let runtime_store = temp.join("runtime-spine.msgpack"); + let local_verse_store = local_verse_store_path(&runtime_store); + let state = EpiphanyThreadState { + revision: 1, + objective: Some("Prove launch assembly cannot initialize shared state.".to_string()), + ..Default::default() + }; + + let error = render_launch_dynamic_prompt_context( + &runtime_store, + &state, + role_launch_context_focus(&state, "modeling"), + ) + .expect_err("unbootstrapped launch context must fail closed"); + + assert!(error.contains("local Verse is not bootstrapped")); + assert!(!local_verse_store.exists()); + Ok(()) + } + #[test] fn verification_launch_context_includes_hands_receipt_chain() -> anyhow::Result<()> { let temp = std::env::temp_dir().join(format!("epiphany-hands-context-{}", Uuid::new_v4())); diff --git a/notes/conceptual-substitution-audit-2026-07-12.md b/notes/conceptual-substitution-audit-2026-07-12.md index 318ad846..832dcb3b 100644 --- a/notes/conceptual-substitution-audit-2026-07-12.md +++ b/notes/conceptual-substitution-audit-2026-07-12.md @@ -169,3 +169,21 @@ paths may initialize the local Verse. Every other mutating query command passes and refuses before store creation when either is absent. Requesters now write into an existing body; they cannot manufacture the body as a side effect of asking it for work. + +## Launch-context/bootstrap split + +The final implicit production caller was +`render_launch_dynamic_prompt_context`. Building a worker launch packet seeded +the sibling local Verse before reading it, so launch preparation owned shared +policy, topology, contracts, brake initialization, and operator status. + +Launch-context assembly now requires persisted Epiphany status and topology and +fails before creating the Verse store when they are absent. Tests and the +prompt-context smoke initialize their fixtures explicitly. The smoke no longer +accepts an arbitrary output path; it writes only +`.epiphany-smoke/cultmesh/epiphany-prompt-context.ccmp`. + +The remaining non-test seed callers are now limited to the explicit Verse seed +commands and two named, path-confined smoke fixture initializers. No production +provider, supervisor, requester, diagnostic, or launch path bootstraps shared +state as a convenience side effect. diff --git a/notes/epiphany-current-algorithmic-map.md b/notes/epiphany-current-algorithmic-map.md index 5e29a329..723d22fa 100644 --- a/notes/epiphany-current-algorithmic-map.md +++ b/notes/epiphany-current-algorithmic-map.md @@ -106,6 +106,10 @@ quarantined smoke initialize local Verse state. Every operator/requester mutation requires persisted status and topology before writing its bounded brake, poke, tool, Eve, Bifrost, metrics, artifact, or feedback document. +- Coordinator launch-context assembly also requires persisted status and + topology. Rendering a worker prompt cannot initialize shared state. Its + standalone smoke accepts no path arguments and writes only beneath + `.epiphany-smoke`. - Store/runtime overrides fail before fixture seeding or receipt construction. - Negative proof: targeting `state/local-verse.ccmp` was rejected and its SHA-256 did not change. - Positive proof: the built-in quarantined smoke completes successfully and reports its quarantine coordinates. diff --git a/notes/fresh-workspace-handoff.md b/notes/fresh-workspace-handoff.md index 2fea958a..6d76313a 100644 --- a/notes/fresh-workspace-handoff.md +++ b/notes/fresh-workspace-handoff.md @@ -30,6 +30,9 @@ The confirmed conceptual substitutions have been cut: - query-CLI requester/operator mutations also require persisted bootstrap. Only explicit seed commands and the fixed aggregate smoke may initialize the shared local Verse. +- worker launch-context rendering requires the existing Verse and cannot seed + it. The prompt-context smoke accepts no destination and is fixed beneath + `.epiphany-smoke`. The presentation boundary is now plain: `swarm overview` is a generic compact read-only projection. Gjallar is a downstream TUI application on Nightwing and is not an Epiphany organ, provider, owner, runtime, or architectural dependency. Eve/CultUI graphs may be lowered or composited downstream without Epiphany caring which presentation client does it. diff --git a/state/ledgers.msgpack b/state/ledgers.msgpack index 7884584e..be3590ab 100644 Binary files a/state/ledgers.msgpack and b/state/ledgers.msgpack differ diff --git a/state/map.yaml b/state/map.yaml index 153a8b71..e434fef3 100644 --- a/state/map.yaml +++ b/state/map.yaml @@ -70,7 +70,7 @@ data_flow: - move historical proof to git, commits, smoke artifacts, or distilled evidence rather than plan prose active_subgoals: - - Use `notes/conceptual-substitution-audit-2026-07-12.md` plus `notes/receipt-writer-provenance-audit-2026-07-12.md` as the current authority-corrosion maps. Snapshot promotion, synthetic smoke escape, unbound operator completion, hidden service execution flags, generic-bootstrap publication of provider advertisements/Eve surfaces/hosted tools, the central topology-derived Odin publisher, all-provider state writers, cluster-daemon bootstrap, daemon-supervisor production bootstrap, and requester/operator query-side bootstrap are cut. Provider absence survives bootstrap; only explicit seed/fixture paths initialize; each cluster daemon writes its own presence and pulse; Idunn and requester paths require the shared body rather than repainting it; synthetic seeding is quarantined. Next remove or test-gate dead public receipt constructors where provider binaries are absent and audit remaining projections, caches, schedulers, adapters, and non-receipt smokes for borrowed authority or path escape. + - Use `notes/conceptual-substitution-audit-2026-07-12.md` plus `notes/receipt-writer-provenance-audit-2026-07-12.md` as the current authority-corrosion maps. Snapshot promotion, synthetic smoke escape, unbound operator completion, hidden service execution flags, provider/bootstrap collapse, the central topology-derived Odin publisher, all-provider writers, cluster-daemon bootstrap, daemon-supervisor production bootstrap, requester/operator query bootstrap, and launch-context bootstrap are cut. Only explicit seed commands and named path-confined fixtures initialize; providers, Idunn, requesters, diagnostics, and worker-launch assembly require the existing shared body. Next remove or test-gate dead public receipt constructors where provider binaries are absent and audit remaining projections, caches, schedulers, adapters, and non-receipt smokes for borrowed authority or path escape. - `swarm-overview` is an Epiphany-local, renderer-neutral diagnostic projection over provider-owned typed state. It owns no lifecycle, discovery, composition, or mutation authority. Epiphany publishes clean provider-owned Eve surfaces; Odin may discover them; downstream lowerers and compositors remain outside Epiphany's concern. - Idunn owns daemon survival and service lifecycle physiology. Codex/operator sessions may inspect, generate runbooks, and route explicit operator authority, but keeping daemons alive belongs to Idunn-owned service lifecycle receipts, scheduler policy, audits, and aftercare. Do not let Self, diagnostic projections, or ad hoc wrapper summaries become a second daemon-keeper. Global tool readbacks for Self `service-health`, `service-policy-directory`, and `swarm-online-runbook` state `lifecycleOwner=Idunn` and `hostedBody=Epiphany` without service mutation or private-state exposure. - Use `notes/epiphany-cultmesh-dreaming-roadmap.md` as the design authority for distributed Epiphany dreaming. Public dreams are separately authored typed documents distributed through CultMesh/CultNet; private state remains local and cannot be shared by flipping a visibility flag. Foreign dreams are external thought weather until a reviewed local adoption receipt digests them into memory, planning, doctrine, or governance.