Make coordinator launch atomic

This commit is contained in:
Meta Vens 2026-07-11 20:24:48 +01:00
commit 41f5a3dceb
8 changed files with 386 additions and 103 deletions

View file

@ -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,
&current_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<u64>,
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),

View file

@ -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<EpiphanyJobLaunchResult> {
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<CultCacheEnvelope>,
) -> Result<EpiphanyJobLaunchResult> {
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::<EpiphanyRuntimeIdentity>(RUNTIME_IDENTITY_KEY)?
.is_some()
);
assert!(
cache
.get::<EpiphanyRuntimeSession>("epiphany-main")?
.is_some()
);
assert!(cache.get::<EpiphanyRuntimeJob>("backend-1")?.is_some());
assert!(
cache
.get::<EpiphanyRuntimeWorkerLaunchRequest>("backend-1")?
.is_some()
);
assert!(
cache
.get::<EpiphanyRuntimeEvent>("event-job-opened-backend-1")?
.is_some()
);
assert!(
cache
.get::<SubstrateGateRepoAccessGrantReceipt>("substrate-grant-backend-1")?
.is_some()
);
assert_eq!(
cache
.get_required::<EpiphanyThreadStateEntry>(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::<EpiphanyThreadStateEntry>(THREAD_STATE_KEY)?
.state()?,
state
);
assert!(
cache
.get::<EpiphanyRuntimeJob>("backend-refused")?
.is_none()
);
assert!(
cache
.get::<EpiphanyRuntimeWorkerLaunchRequest>("backend-refused")?
.is_none()
);
assert!(
cache
.get::<SubstrateGateRepoAccessGrantReceipt>("substrate-grant-backend-refused")?
.is_none()
);
Ok(())
}
}

View file

@ -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;

View file

@ -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<CultCacheEnvelope>,
}
#[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<PreparedRuntimeSpineHeartbeatJob> {
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::<EpiphanyRuntimeIdentity>(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::<EpiphanyRuntimeSession>(&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::<EpiphanyRuntimeJob>(&options.job_id)?.is_some() {
return Err(anyhow!("runtime job {:?} already exists", options.job_id));
}
if cache
.get::<EpiphanyRuntimeWorkerLaunchRequest>(&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<Path>,
job_id: &str,

View file

@ -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

View file

@ -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

Binary file not shown.

View file

@ -659,4 +659,4 @@ current_status:
- Service lifecycle receipt history is now event-only: `load_epiphany_cultmesh_daemon_service_lifecycle_receipts` filters out the `epiphany-local/daemon-service-lifecycle-receipt/latest` mirror, while `load_latest_epiphany_cultmesh_daemon_service_lifecycle_receipt` remains the latest-state reader. Focused unit test `service_lifecycle_receipt_history_excludes_latest_mirror` proves two per-receipt writes return exactly two history records and latest points at the second. Fresh smoke `verse-query-lifecycle-history-filter-smoke` and live overview/triage `local-20260618-145632-497-efac6f0d` / `local-20260618-145632-321-867947af` prove compact swarm sights still expose service lifecycle attention plus the present elevated runbook artifact without daemon pokes or service mutation. Do not use a broad unscoped cargo test filter for this check on Windows; it fans out across bin harnesses and hit a broken pipe during this pass.
- `epiphany-verse-query seed-compact` is the compact seed receipt for wrappers and read-only rites. The older `seed` command still prints the full local Verse context for explicit debugging, but compact wrapper modes now avoid full `seed`/final `query` readback after the combined overview proof exposed Windows debug stack overflow from full-context serialization pressure.
last_completed_repo_swarm_cut: Huginn live-fire has crossed the first real repo Body from Persona pressure to branch-local Hands commit and Soul/Mind closure, and now has repeatable closure and redacted-public-proof mouths. On `E:\Projects\Huginn` branch `epiphany/huginn/first-awakening`, `tools/epiphany_local_run.ps1 -Mode repo-persona-intake` accepted item `huginn-inspection-readback-livefire`, Weksa/Persona intake remained sealed, and `epiphany-work derive-plan` lowered it to model-authored safe family `repo.status_section` for `README.md` only. `tools/epiphany_local_run.ps1 -Mode repo-swarm-run` advanced four bounded pulses: run-from-plan, adopt-from-plan, execute-from-plan, and close-from-execute. Huginn commit `e9b07c38fceabbdc89ea35c2156326c80c0393a2` updated only `README.md`; closure receipt `.epiphany\work\work-close-huginn-inspection-readback-livefire.json` records status `closed`, Soul verdict `passed`, Mind state commit `repo-work-close-huginn-inspection-readback-livefire-mind-commit`, safe family `repo.status_section`, path scope `README.md`, Bifrost publication gate, and `privateStateExposed=false`. Verification also ran Huginn's TypeScript compiler through concrete Node path: `C:\Program Files\nodejs\node.exe E:\Projects\Huginn\node_modules\typescript\bin\tsc -p tsconfig.json` passed. Native verifier `epiphany-repo-livefire-closure-smoke --workspace E:\Projects\Huginn --item huginn-inspection-readback-livefire --expected-path README.md --expected-family repo.status_section --expected-gate awaiting-publication --expected-blocker bifrost-publication-missing` checks closure receipt, Mind map entry, overview gate/blocker, authority seals, and `git show` changed paths, returning `status=ok`, `soulVerdict=passed`, `publicationGate=Bifrost`, and `privateStateExposed=false`. Wrapper `tools/epiphany_local_run.ps1 -Mode repo-livefire-closure -Workspace E:\Projects\Huginn -RepoWorkItem huginn-inspection-readback-livefire -SkipBuild` exposes the same read-only proof through the local operator mouth; artifact `local-20260621-190156-803-2f85ba15` printed commit, path, gate, blocker, Soul verdict, Mind commit receipt, and `privateStateExposed=False` without push, merge, publication, or target mutation. New wrapper `tools/epiphany_local_run.ps1 -Mode repo-work-public-proof -Workspace E:\Projects\Huginn -RepoWorkItem huginn-inspection-readback-livefire -SkipBuild` exports the redacted proof bundle and repo-local Verse index without publishing; artifact `local-20260621-190645-345-4555b1dc` wrote `.epiphany\public\proof-bundles\repo-work-public-proof-huginn-inspection-readback-livefire.json`, proof id `repo-work-public-proof-huginn-inspection-readback-livefire`, SHA-256 `857545b199f3e37eb06c2e757e5cfc201958e46667bb1f8403fd333a22620f63`, nine artifact rows, zero publication rows, and `privateStateExposed=False`. Gjallar readback `local-20260621-190701-140-b9519ec6` sees the `REPO-WORK-PUBLIC-PROOF` row and action row from Huginn's repo-local Verse with the same SHA-256 while Bifrost publication remains missing. Launcher compact modes are now named compact operator-context modes instead of read-only modes, and `bifrost-public-proof` summaries print item, target public Verse, room, proof SHA-256, review receipts, and credit receipts so publication receipts are auditable without claiming upstream sync.
next_action: Complete the native launch transaction described in `notes/native-coordinator-liberation-map.md`. Pure launch planning now belongs to `coordinator_launch`; the compatibility bridge only actuates its plan, but runtime documents, optional Substrate Gate grant, and thread-state linkage remain separate writes. Create one atomic prepared launch commit and negative failure proof before removing `thread/epiphany/*` JSON-RPC from `epiphany-mvp-coordinator`.
next_action: Remove `thread/epiphany/*` JSON-RPC from `epiphany-mvp-coordinator` by wiring it directly to the native coordinator service. Launch and acceptance now each have one native atomic CultCache commit; the compatibility bridge delegates both and is source-guarded against regrowing individual writers.