From 41f5a3dcebb6c73c27a7e69ace49fb8d8237e7f7 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sat, 11 Jul 2026 20:24:48 +0100 Subject: [PATCH] Make coordinator launch atomic --- epiphany-codex-bridge/src/mutation_service.rs | 97 +------- epiphany-core/src/coordinator_launch.rs | 215 ++++++++++++++++++ epiphany-core/src/lib.rs | 3 + epiphany-core/src/runtime_spine.rs | 137 +++++++++++ notes/fresh-workspace-handoff.md | 19 +- notes/native-coordinator-liberation-map.md | 16 +- state/ledgers.msgpack | Bin 403243 -> 403702 bytes state/map.yaml | 2 +- 8 files changed, 386 insertions(+), 103 deletions(-) diff --git a/epiphany-codex-bridge/src/mutation_service.rs b/epiphany-codex-bridge/src/mutation_service.rs index 77bb2b83..cdbc58fa 100644 --- a/epiphany-codex-bridge/src/mutation_service.rs +++ b/epiphany-codex-bridge/src/mutation_service.rs @@ -5,7 +5,6 @@ use std::path::PathBuf; use epiphany_core::CONTINUITY_RECOVERY_RECEIPT_TYPE; use epiphany_core::EPIPHANY_REORIENT_LAUNCH_BINDING_ID; -use epiphany_core::EPIPHANY_RESEARCH_ROLE_BINDING_ID; use epiphany_core::EYES_EVIDENCE_PACKET_TYPE; use epiphany_core::EpiphanyJobInterruptRequest; use epiphany_core::EpiphanyJobInterruptResult; @@ -25,7 +24,6 @@ use epiphany_core::EpiphanyStateUpdate; use epiphany_core::EpiphanyStateUpdatedField; use epiphany_core::EpiphanyTokenUsageSnapshot; use epiphany_core::MIND_GATEWAY_REVIEW_TYPE; -use epiphany_core::RuntimeSpineHeartbeatJobOptions; use epiphany_core::SOUL_VERDICT_RECEIPT_TYPE; use epiphany_core::SUBSTRATE_GATE_REPO_ACCESS_GRANT_RECEIPT_TYPE; use epiphany_core::apply_coordinator_state_update_to_state; @@ -37,11 +35,8 @@ use epiphany_core::epiphany_role_label; use epiphany_core::evaluate_promotion; use epiphany_core::eyes_evidence_packet_from_research_finding; use epiphany_core::mind_state_commit_receipt; -use epiphany_core::open_runtime_spine_heartbeat_job; -use epiphany_core::put_substrate_gate_repo_access_grant_receipt; use epiphany_core::runtime_substrate_gate_repo_access_grant_receipt; use epiphany_core::soul_verdict_receipt_from_verification_finding; -use epiphany_core::substrate_gate_repo_access_grant_for_launch; use epiphany_state_model::EpiphanyEvidenceRecord; use epiphany_state_model::EpiphanyJobKind as CoreEpiphanyJobKind; use epiphany_state_model::EpiphanyRetrievalState; @@ -213,27 +208,16 @@ pub async fn launch_epiphany_job_on_thread( Uuid::new_v4().to_string(), ) .map_err(|err| EpiphanyBridgeError::InvalidRequest(err.to_string()))?; - open_epiphany_runtime_spine_job( + let thread_id = thread.epiphany_thread_id().await; + epiphany_core::commit_coordinator_job_launch( runtime_store.as_path(), - &launch_plan.planning_state, + &thread_id, + ¤t_state, &request, - launch_plan.backend_job_id.as_str(), - )?; - maybe_put_substrate_gate_launch_grant( - runtime_store.as_path(), - &request, - launch_plan.backend_job_id.as_str(), - )?; - - let epiphany_state = - apply_epiphany_state_update_to_thread(thread, launch_plan.state_update).await?; - - Ok(EpiphanyJobLaunchResult { - epiphany_state, - binding_id: request.binding_id, - launcher_job_id: launch_plan.launcher_job_id, - backend_job_id: launch_plan.backend_job_id, - }) + &launch_plan, + Utc::now().to_rfc3339_opts(SecondsFormat::Secs, true), + ) + .map_err(|error| EpiphanyBridgeError::Fatal(error.to_string())) } pub async fn interrupt_epiphany_job_on_thread( @@ -753,46 +737,6 @@ pub async fn launch_thread_epiphany_reorient( }) } -fn open_epiphany_runtime_spine_job( - store_path: &Path, - state: &EpiphanyThreadState, - request: &EpiphanyJobLaunchRequest, - backend_job_id: &str, -) -> BridgeResult<()> { - let now = Utc::now().to_rfc3339_opts(SecondsFormat::Secs, true); - open_runtime_spine_heartbeat_job( - store_path, - RuntimeSpineHeartbeatJobOptions { - runtime_id: "epiphany-local".to_string(), - display_name: "Epiphany Local".to_string(), - session_id: "epiphany-main".to_string(), - objective: state - .objective - .clone() - .filter(|objective| !objective.trim().is_empty()) - .unwrap_or_else(|| "Epiphany heartbeat activation".to_string()), - coordinator_note: "App-server launch opened this typed runtime session.".to_string(), - job_id: backend_job_id.to_string(), - role: request.owner_role.clone(), - binding_id: request.binding_id.clone(), - authority_scope: request.authority_scope.clone(), - instruction: request.instruction.clone(), - launch_document: request.launch_document.clone(), - output_contract_id: request.output_contract_id.clone(), - organ_launch_contract: request.organ_launch_contract.clone(), - created_at: now, - }, - ) - .map_err(|err| { - EpiphanyBridgeError::Fatal(format!( - "failed to open Epiphany runtime spine job {:?} in {}: {err}", - backend_job_id, - store_path.display() - )) - })?; - Ok(()) -} - fn validate_expected_revision( expected_revision: Option, actual_revision: u64, @@ -837,27 +781,6 @@ fn enforce_current_receipt_proofs( .map_err(map_coordinator_admission_error) } -fn maybe_put_substrate_gate_launch_grant( - runtime_store_path: &Path, - request: &EpiphanyJobLaunchRequest, - backend_job_id: &str, -) -> BridgeResult<()> { - if request.binding_id != EPIPHANY_RESEARCH_ROLE_BINDING_ID { - return Ok(()); - } - let grant = substrate_gate_repo_access_grant_for_launch( - substrate_gate_grant_receipt_id(backend_job_id), - backend_job_id.to_string(), - request, - Utc::now().to_rfc3339_opts(SecondsFormat::Secs, true), - ); - put_substrate_gate_repo_access_grant_receipt(runtime_store_path, &grant).map_err(|err| { - EpiphanyBridgeError::Fatal(format!( - "failed to persist Substrate Gate access grant for research launch: {err}" - )) - }) -} - fn persisted_substrate_gate_grant_types( runtime_store_path: &Path, receipt_id: &str, @@ -920,11 +843,15 @@ mod acceptance_architecture_tests { .unwrap(); let launch = &source[launch_start..interrupt_start]; assert_eq!(launch.matches("plan_coordinator_job_launch").count(), 1); + assert_eq!(launch.matches("commit_coordinator_job_launch").count(), 1); for forbidden in [ ["plan", "runtime", "spine", "heartbeat", "launch"].join("_"), ["replace", "or", "append", "epiphany", "job", "binding"].join("_"), ["runtime", "job", "snapshot"].join("_"), ["epiphany", "state", "update", "validation", "errors"].join("_"), + ["open", "epiphany", "runtime", "spine", "job"].join("_"), + ["maybe", "put", "substrate", "gate", "launch", "grant"].join("_"), + ["apply", "epiphany", "state", "update", "to", "thread"].join("_"), ] { assert!( !launch.contains(&forbidden), diff --git a/epiphany-core/src/coordinator_launch.rs b/epiphany-core/src/coordinator_launch.rs index dce98cda..7a72ab30 100644 --- a/epiphany-core/src/coordinator_launch.rs +++ b/epiphany-core/src/coordinator_launch.rs @@ -1,5 +1,6 @@ use crate::*; use anyhow::{Result, anyhow}; +use cultcache_rs::{CultCache, CultCacheEnvelope}; use epiphany_state_model::{EpiphanyRuntimeLink, EpiphanyThreadState}; use std::path::Path; @@ -12,6 +13,91 @@ pub struct EpiphanyCoordinatorJobLaunchPlan { pub state_update: EpiphanyStateUpdate, } +pub fn commit_coordinator_job_launch( + store: &Path, + thread_id: &str, + current_state: &EpiphanyThreadState, + request: &EpiphanyJobLaunchRequest, + plan: &EpiphanyCoordinatorJobLaunchPlan, + created_at: String, +) -> Result { + if let Some(persisted) = read_accepted_coordinator_state(store)? + && persisted != *current_state + { + return Err(anyhow!( + "authoritative coordinator state changed before launch commit" + )); + } + let mut cache = coordinator_acceptance_cache(store)?; + commit_coordinator_job_launch_in_cache( + &mut cache, + thread_id, + current_state, + request, + plan, + created_at, + Vec::new(), + ) +} + +fn commit_coordinator_job_launch_in_cache( + cache: &mut CultCache, + thread_id: &str, + current_state: &EpiphanyThreadState, + request: &EpiphanyJobLaunchRequest, + plan: &EpiphanyCoordinatorJobLaunchPlan, + created_at: String, + injected_envelopes: Vec, +) -> Result { + let next_state = + apply_coordinator_state_update_to_state(current_state, plan.state_update.clone(), None)?; + let prepared = prepare_runtime_spine_heartbeat_job( + &cache, + RuntimeSpineHeartbeatJobOptions { + runtime_id: "epiphany-local".to_string(), + display_name: "Epiphany Local".to_string(), + session_id: "epiphany-main".to_string(), + objective: plan + .planning_state + .objective + .clone() + .filter(|objective| !objective.trim().is_empty()) + .unwrap_or_else(|| "Epiphany heartbeat activation".to_string()), + coordinator_note: "Native coordinator launch transaction opened this session." + .to_string(), + job_id: plan.backend_job_id.clone(), + role: request.owner_role.clone(), + binding_id: request.binding_id.clone(), + authority_scope: request.authority_scope.clone(), + instruction: request.instruction.clone(), + launch_document: request.launch_document.clone(), + output_contract_id: request.output_contract_id.clone(), + organ_launch_contract: request.organ_launch_contract.clone(), + created_at: created_at.clone(), + }, + )?; + let mut batch = prepared.envelopes; + if request.binding_id == EPIPHANY_RESEARCH_ROLE_BINDING_ID { + let grant = substrate_gate_repo_access_grant_for_launch( + format!("substrate-grant-{}", plan.backend_job_id), + plan.backend_job_id.clone(), + request, + created_at, + ); + batch.push(cache.prepare_entry(&grant.receipt_id, &grant)?.0); + } + let state_entry = EpiphanyThreadStateEntry::from_state(thread_id, &next_state)?; + batch.push(cache.prepare_entry(THREAD_STATE_KEY, &state_entry)?.0); + batch.extend(injected_envelopes); + cache.put_prepared_batch(batch)?; + Ok(EpiphanyJobLaunchResult { + epiphany_state: next_state, + binding_id: request.binding_id.clone(), + launcher_job_id: plan.launcher_job_id.clone(), + backend_job_id: plan.backend_job_id.clone(), + }) +} + pub fn plan_coordinator_job_launch( state: &EpiphanyThreadState, request: &EpiphanyJobLaunchRequest, @@ -113,6 +199,17 @@ fn terminal_runtime_link_for_binding( mod tests { use super::*; + fn research_launch(state: &EpiphanyThreadState) -> EpiphanyJobLaunchRequest { + build_epiphany_role_launch_request( + "thread-1", + EpiphanyRoleResultRoleId::Research, + Some(state.revision), + Some(60), + state, + ) + .expect("research launch request") + } + #[test] fn stale_revision_refuses_launch_before_runtime_writes() { let state = EpiphanyThreadState { @@ -137,4 +234,122 @@ mod tests { .expect_err("stale launch must fail"); assert!(error.to_string().contains("revision mismatch")); } + + #[test] + fn launch_documents_state_and_grant_share_one_snapshot() -> Result<()> { + let temp = tempfile::tempdir()?; + let store = temp.path().join("launch.cc"); + let state = EpiphanyThreadState::default(); + let request = research_launch(&state); + let plan = plan_coordinator_job_launch( + &state, + &request, + &store, + "launcher-1".to_string(), + "backend-1".to_string(), + )?; + let result = commit_coordinator_job_launch( + &store, + "thread-1", + &state, + &request, + &plan, + "2026-07-11T00:00:00Z".to_string(), + )?; + assert_eq!(result.epiphany_state.revision, 1); + let cache = coordinator_acceptance_cache(&store)?; + assert!( + cache + .get::(RUNTIME_IDENTITY_KEY)? + .is_some() + ); + assert!( + cache + .get::("epiphany-main")? + .is_some() + ); + assert!(cache.get::("backend-1")?.is_some()); + assert!( + cache + .get::("backend-1")? + .is_some() + ); + assert!( + cache + .get::("event-job-opened-backend-1")? + .is_some() + ); + assert!( + cache + .get::("substrate-grant-backend-1")? + .is_some() + ); + assert_eq!( + cache + .get_required::(THREAD_STATE_KEY)? + .state()?, + result.epiphany_state + ); + Ok(()) + } + + #[test] + fn refused_batch_preserves_prior_snapshot_without_partial_launch() -> Result<()> { + let temp = tempfile::tempdir()?; + let store = temp.path().join("launch-refused.cc"); + let state = EpiphanyThreadState::default(); + let mut cache = coordinator_acceptance_cache(&store)?; + let initial = EpiphanyThreadStateEntry::from_state("thread-1", &state)?; + cache.put(THREAD_STATE_KEY, &initial)?; + let request = research_launch(&state); + let plan = plan_coordinator_job_launch( + &state, + &request, + &store, + "launcher-refused".to_string(), + "backend-refused".to_string(), + )?; + let poison = CultCacheEnvelope { + key: "injected-refusal".to_string(), + r#type: "unregistered.injected_failure".to_string(), + payload: Vec::new(), + stored_at: "2026-07-11T00:00:00Z".to_string(), + schema_id: None, + }; + assert!( + commit_coordinator_job_launch_in_cache( + &mut cache, + "thread-1", + &state, + &request, + &plan, + "2026-07-11T00:00:00Z".to_string(), + vec![poison], + ) + .is_err() + ); + let cache = coordinator_acceptance_cache(&store)?; + assert_eq!( + cache + .get_required::(THREAD_STATE_KEY)? + .state()?, + state + ); + assert!( + cache + .get::("backend-refused")? + .is_none() + ); + assert!( + cache + .get::("backend-refused")? + .is_none() + ); + assert!( + cache + .get::("substrate-grant-backend-refused")? + .is_none() + ); + Ok(()) + } } diff --git a/epiphany-core/src/lib.rs b/epiphany-core/src/lib.rs index a9866154..442ad95e 100644 --- a/epiphany-core/src/lib.rs +++ b/epiphany-core/src/lib.rs @@ -141,6 +141,7 @@ pub use coordinator_acceptance::reorient_acceptance_claimed_effects; pub use coordinator_acceptance::role_acceptance_claimed_effects; pub use coordinator_acceptance::role_acceptance_enforceable_receipts; pub use coordinator_launch::EpiphanyCoordinatorJobLaunchPlan; +pub use coordinator_launch::commit_coordinator_job_launch; pub use coordinator_launch::plan_coordinator_job_launch; pub use coordinator_results::EpiphanyCoordinatorReorientResultSnapshot; pub use coordinator_results::EpiphanyCoordinatorRoleResultSnapshot; @@ -719,6 +720,7 @@ pub use runtime_spine::EpiphanyRuntimeSessionStatus; pub use runtime_spine::EpiphanyRuntimeSpineStatus; pub use runtime_spine::EpiphanyRuntimeWorkerLaunchRequest; pub use runtime_spine::EpiphanyToolInvocationStatus; +pub use runtime_spine::PreparedRuntimeSpineHeartbeatJob; pub use runtime_spine::RUNTIME_EVENT_TYPE; pub use runtime_spine::RUNTIME_IDENTITY_KEY; pub use runtime_spine::RUNTIME_IDENTITY_TYPE; @@ -750,6 +752,7 @@ pub use runtime_spine::ensure_runtime_session; pub use runtime_spine::initialize_runtime_spine; pub use runtime_spine::open_runtime_spine_heartbeat_job; pub use runtime_spine::plan_runtime_spine_heartbeat_launch; +pub use runtime_spine::prepare_runtime_spine_heartbeat_job; pub use runtime_spine::put_continuity_recovery_receipt; pub use runtime_spine::put_coordinator_run_receipt; pub use runtime_spine::put_eyes_evidence_packet; diff --git a/epiphany-core/src/runtime_spine.rs b/epiphany-core/src/runtime_spine.rs index d297f5cc..1e9b273e 100644 --- a/epiphany-core/src/runtime_spine.rs +++ b/epiphany-core/src/runtime_spine.rs @@ -64,6 +64,7 @@ use anyhow::Context; use anyhow::Result; use anyhow::anyhow; use cultcache_rs::CultCache; +use cultcache_rs::CultCacheEnvelope; use cultcache_rs::DatabaseEntry; use cultcache_rs::SingleFileMessagePackBackingStore; use cultnet_rs::CultNetDocumentMutationContract; @@ -632,6 +633,12 @@ pub struct RuntimeSpineHeartbeatJobOptions { pub created_at: String, } +#[derive(Clone, Debug, PartialEq)] +pub struct PreparedRuntimeSpineHeartbeatJob { + pub job: EpiphanyRuntimeJob, + pub envelopes: Vec, +} + #[derive(Clone, Debug, PartialEq)] pub struct RuntimeSpineHeartbeatLaunchPlanOptions { pub binding_id: String, @@ -1050,6 +1057,136 @@ pub fn open_runtime_spine_heartbeat_job( Ok(job) } +pub fn prepare_runtime_spine_heartbeat_job( + cache: &CultCache, + options: RuntimeSpineHeartbeatJobOptions, +) -> Result { + validate_non_empty(&options.runtime_id, "runtime id")?; + validate_non_empty(&options.display_name, "display name")?; + validate_non_empty(&options.session_id, "session id")?; + validate_non_empty(&options.objective, "objective")?; + validate_non_empty(&options.job_id, "job id")?; + validate_non_empty(&options.role, "role")?; + validate_non_empty(&options.binding_id, "binding id")?; + validate_non_empty(&options.authority_scope, "authority scope")?; + validate_non_empty(&options.instruction, "instruction")?; + validate_non_empty( + options.launch_document.thread_id(), + "worker launch document thread id", + )?; + validate_non_empty(&options.output_contract_id, "output contract id")?; + if options.output_contract_id != options.launch_document.output_contract_id() { + return Err(anyhow!( + "worker launch output_contract_id must match the typed launch document" + )); + } + validate_launch_organ_contract( + &options.organ_launch_contract, + &options.authority_scope, + options.launch_document.document_kind(), + &options.output_contract_id, + )?; + validate_non_empty(&options.created_at, "created at")?; + + let existing_identity = cache.get::(RUNTIME_IDENTITY_KEY)?; + let identity = EpiphanyRuntimeIdentity { + schema_version: RUNTIME_SPINE_SCHEMA_VERSION.to_string(), + runtime_id: options.runtime_id, + display_name: options.display_name, + runtime_kind: "epiphany.native".to_string(), + created_at: existing_identity + .as_ref() + .map(|value| value.created_at.clone()) + .unwrap_or_else(|| options.created_at.clone()), + updated_at: options.created_at.clone(), + supported_document_types: supported_runtime_document_types(), + metadata: BTreeMap::from([("codexEvacuationBridge".to_string(), "temporary".to_string())]), + }; + let session = match cache.get::(&options.session_id)? { + Some(existing) + if matches!( + existing.status, + EpiphanyRuntimeSessionStatus::Completed | EpiphanyRuntimeSessionStatus::Archived + ) => + { + return Err(anyhow!( + "runtime session {:?} is terminal and cannot accept jobs", + options.session_id + )); + } + Some(existing) => existing, + None => EpiphanyRuntimeSession { + schema_version: RUNTIME_SPINE_SCHEMA_VERSION.to_string(), + session_id: options.session_id.clone(), + objective: options.objective, + status: EpiphanyRuntimeSessionStatus::Active, + created_at: options.created_at.clone(), + updated_at: options.created_at.clone(), + coordinator_note: options.coordinator_note, + metadata: BTreeMap::new(), + }, + }; + if cache.get::(&options.job_id)?.is_some() { + return Err(anyhow!("runtime job {:?} already exists", options.job_id)); + } + if cache + .get::(&options.job_id)? + .is_some() + { + return Err(anyhow!( + "runtime worker launch request {:?} already exists", + options.job_id + )); + } + let job = EpiphanyRuntimeJob { + schema_version: RUNTIME_SPINE_SCHEMA_VERSION.to_string(), + job_id: options.job_id.clone(), + session_id: options.session_id.clone(), + role: options.role.clone(), + status: EpiphanyRuntimeJobStatus::Queued, + created_at: options.created_at.clone(), + updated_at: options.created_at.clone(), + summary: format!( + "Heartbeat activation queued for binding {} with authority {}.", + options.binding_id, options.authority_scope + ), + artifact_refs: Vec::new(), + metadata: BTreeMap::new(), + }; + let event = EpiphanyRuntimeEvent { + schema_version: RUNTIME_SPINE_SCHEMA_VERSION.to_string(), + event_id: format!("event-job-opened-{}", options.job_id), + occurred_at: options.created_at, + event_type: "job.opened".to_string(), + source: "runtime-spine".to_string(), + session_id: Some(options.session_id), + job_id: Some(options.job_id.clone()), + summary: "Native runtime job opened.".to_string(), + metadata: BTreeMap::new(), + }; + let request = EpiphanyRuntimeWorkerLaunchRequest { + schema_version: RUNTIME_WORKER_LAUNCH_REQUEST_SCHEMA_VERSION.to_string(), + job_id: options.job_id.clone(), + binding_id: options.binding_id, + role: options.role, + authority_scope: options.authority_scope, + instruction: options.instruction, + output_contract_id: options.output_contract_id, + document_kind: worker_launch_document_kind(&options.launch_document).to_string(), + launch_document_msgpack: encode_worker_launch_document(&options.launch_document)?, + metadata: BTreeMap::new(), + organ_launch_contract: options.organ_launch_contract, + }; + let envelopes = vec![ + cache.prepare_entry(RUNTIME_IDENTITY_KEY, &identity)?.0, + cache.prepare_entry(&session.session_id, &session)?.0, + cache.prepare_entry(&job.job_id, &job)?.0, + cache.prepare_entry(&event.event_id, &event)?.0, + cache.prepare_entry(&request.job_id, &request)?.0, + ]; + Ok(PreparedRuntimeSpineHeartbeatJob { job, envelopes }) +} + pub fn runtime_job_snapshot( store_path: impl AsRef, job_id: &str, diff --git a/notes/fresh-workspace-handoff.md b/notes/fresh-workspace-handoff.md index bd281543..785cc303 100644 --- a/notes/fresh-workspace-handoff.md +++ b/notes/fresh-workspace-handoff.md @@ -1820,15 +1820,16 @@ proof `.epiphany-smoke\repo-work-readiness-20260621-113714` and wrapper proof The machine is no longer cleared to move outward. Free it from the Codex organ first. -Native worker-launch planning has been extracted into -`epiphany-core/src/coordinator_launch.rs`. It owns revision refusal, prior -terminal-link discovery, heartbeat launch planning, binding replacement, and -the derived state update. The bridge delegates those decisions and a source -guard prevents the policy from growing back. The remaining launch wound is -transactional: runtime documents, the optional Substrate Gate grant, and -thread-state linkage are still separate compatibility writes. The next cut is -one prepared atomic launch commit with injected-failure proof; do not remove -the coordinator JSON-RPC dependency before that invariant is real. +Native worker launch is now one transaction. `coordinator_launch` owns revision +refusal, prior terminal-link discovery, heartbeat planning, binding replacement, +derived state, and atomic publication of runtime identity/session/job/event, +worker request, optional Research Substrate Gate grant, and thread state. The +bridge delegates planning and commit; its former runtime/grant/state writers are +deleted and guarded against regrowth. Focused proof reads every document from +the shared snapshot, while an injected batch refusal preserves the prior state +with no partial job, worker request, or grant. The next cut is direct native +`epiphany-mvp-coordinator` operation and removal of its `thread/epiphany/*` +JSON-RPC dependency. ## Immediate Re-entry Instruction diff --git a/notes/native-coordinator-liberation-map.md b/notes/native-coordinator-liberation-map.md index 135a6a31..d7ca9f9b 100644 --- a/notes/native-coordinator-liberation-map.md +++ b/notes/native-coordinator-liberation-map.md @@ -170,7 +170,7 @@ interruption. Best-effort rollback across two files is not atomicity. A negative smoke must inject failure after prerequisite receipts and after state write and prove no unwitnessed accepted state becomes authoritative. -## Launch Transaction Wound +## Launch Transaction Law Launch planning now belongs to `coordinator_launch`: it validates the expected thread revision, inspects the prior runtime binding, prepares the heartbeat @@ -178,13 +178,13 @@ launch documents, and derives the new thread-state linkage without writing. The Codex bridge is a compatibility actuator for that plan; it no longer owns those policy decisions. -Persistence is not yet a coherent launch transaction. Runtime identity, -session, job, event, worker request, optional Substrate Gate grant, and thread -state linkage are still published through separate writes. None may become the -authoritative sign of a completed launch until one native commit primitive -publishes the prepared set atomically, or a typed prepared transaction gives -interruption a deterministic finish-or-refuse law. Rollback and reconciliation -are not owners. +`commit_coordinator_job_launch` is the single launch publication owner. It +prepares runtime identity, session, job, opened event, worker request, optional +Research Substrate Gate grant, and linked thread state against one loaded +CultCache snapshot, then publishes the heterogeneous set in one atomic batch. +The compatibility bridge cannot write any member separately. Injected batch +refusal proves the prior snapshot survives with no job, worker request, grant, +or new state linkage. Rollback and reconciliation are not owners. ## Invariants diff --git a/state/ledgers.msgpack b/state/ledgers.msgpack index 3462e71001ff7c8e98de4c79e3cb2670f7b30d46..b79feef24373fc841aa2fd4436af9077a40c7226 100644 GIT binary patch delta 458 zcmXv~u}T9$5cN(?V`ULYIYc2KmrE`L#n=b|OAAS5x7i(YYc9K&-Mt*CQdo$+F_m9n zBPfWKouwfDi(uzmqQzrpd2eRkyoZm<_h;p?;%&9sopyBozE-MzJ?(g7&wIO?``2^d zBNXpCVS}*wGWSb&yXD*0R^LFGahof zfsWVug1j