Delete the heartbeat scheduler authority

This commit is contained in:
Meta Vens 2026-08-23 06:21:38 +02:00
commit 4d0db2c7b4
36 changed files with 280 additions and 4303 deletions

View file

@ -131,10 +131,6 @@ path = "epiphany-core/src/bin/epiphany-host-identity.rs"
name = "epiphany-swarm"
path = "epiphany-core/src/bin/epiphany-swarm.rs"
[[bin]]
name = "epiphany-heartbeat-store"
path = "epiphany-core/src/bin/epiphany-heartbeat-store.rs"
[[bin]]
name = "epiphany-runtime-spine"
path = "epiphany-core/src/bin/epiphany-runtime-spine.rs"

View file

@ -73,7 +73,7 @@ Domain policy & \code{epiphany-core} & Verdicts, launch policy, acceptance rules
Vendored Codex & \code{vendor/codex} & Retain host/app-server/auth/model compatibility where useful; do not let Codex own Epiphany prompts, scheduler, or state. \\
Runtime spine & \code{epiphany-core/src/runtime_spine.rs} and \code{state/runtime-spine.cc} & Runtime identity, jobs, events, receipts, tool/model documents are typed CultCache records. \\
Persona memory & keyed Mind Persona-memory documents & Persona decisions admit bounded memories by semantic identity; no parallel role-memory aggregate or generation head exists. \\
Heartbeat physiology & \code{state/agent-heartbeats.cc} via \code{epiphany-heartbeat-store} & Scheduling, cooldown, explicit pressure, and active-turn exclusion only. Heartbeat owns no cognition, personality, or memory mutation. \\
Resident Self physiology & Resident Self CultCache documents via \code{epiphany-swarm} & Typed pressure, exact grant creation, launch exclusion, leases, terminal receipts, retry, and bounded retention have one owner. No scheduler singleton or acknowledgement loop exists. \\
Local Verse & CultMesh over \code{state/local-verse.cc} & Local Verse context is compact prompt/operator context; private state does not become public by flipping a flag. \\
Schema catalog & \code{schemas/cultnet/index.json} & JSON schema publishes contracts; typed Rust documents remain runtime authority. \\
Operator artifacts & \code{.epiphany-*}, \code{tools/epiphany_local_run.ps1} & Artifacts witness and lower state; they do not become state owners. \\
@ -85,7 +85,7 @@ The checkout has nine primary Epiphany Rust crates:
\begin{itemize}
\item \code{epiphany-core}: the main domain organ. It owns state-update
validation, launch documents, runtime spine, heartbeat, memory graph, Persona
validation, launch documents, runtime spine, Resident Self, Persona
projection, Substrate Gate, Mind, Eyes, Hands, Soul, Continuity, CultMesh
integration, surface derivation, smokes, and local operator binaries.
\item \code{epiphany-state-model}: the durable \code{EpiphanyThreadState}
@ -133,10 +133,9 @@ implementation, not file authority.
\hline
\code{state/map.yaml} & Canonical slow project map: objective, constraints, invariants, dataflow, active subgoals, current status, guardrails, and rejected paths. \\
\code{state/ledgers.cc} & CultCache-shaped evidence and branch ledger. Future behavior-changing lessons go here, not in an endless activity feed. \\
\code{state/agents.cc} & Organ self-memory and Persona projection source. Resident work organs use \code{work_organ}; public/person-shaped agents use \code{persona}. \\
\code{state/agent-heartbeats.cc} & Heartbeat initiative, cooldown, pending turns, cognition lanes, routine physiology, and scheduler pressure. \\
\code{state/runtime-spine.cc} & Runtime identity, sessions, jobs, job results, events, worker launch requests, role/reorient findings, receipts, model/tool documents, the admitted RepoModel, and schema catalog surfaces. \\
\code{state/thread-state.cc} & Native thread-state store. \\
Resident Self store & Keyed pressure, grants, prepared and active launch authority, terminal receipts, and retention head. \\
Persona social store & Keyed mentions, immutable turn requests, terminal receipts, quarantine records, and retention plans. \\
\code{state/runtime-spine.cc} & Runtime identity, sessions, jobs, results, worker launch requests, receipts, and model/tool transport documents. Durable decisions and keyed RepoModel documents live in Mind. \\
\code{state/local-verse.cc} & Local CultMesh Verse context for compact operator and prompt-context packets. \\
\code{state/semantic-projection.cc} & Typed Qdrant projection receipts. Cache health lives here, outside the canonical RepoModel. \\
\code{state/persona-discord.toml} & Persona Discord boundary configuration. \\
@ -260,7 +259,7 @@ Current operator access is partly native and partly witness-artifact based:
\begin{itemize}
\item Native binaries such as \code{epiphany-state},
\code{epiphany-mvp-coordinator}, \code{epiphany-runtime-spine},
\code{epiphany-heartbeat-store}, and the family-specific worker services
\code{epiphany-swarm}, and the family-specific worker services
inspect or update their owned typed state.
\item Local artifact directories such as \code{.epiphany-run/},
\code{.epiphany-dogfood/}, \code{.epiphany-smoke/},

View file

@ -998,15 +998,6 @@ mod tests {
);
}
#[test]
fn live_worker_contracts_contain_no_codex_route_vocabulary() {
let source = include_str!("agent_launch.rs");
let production = source.split("#[cfg(test)]").next().unwrap_or(source);
assert!(!production.contains("thread/epiphany/"));
assert!(production.contains("epiphany.coordinator.reorient.launch"));
assert!(production.contains("researchDecision"));
}
#[test]
fn bundled_epiphany_agent_prompts_do_not_name_codex_as_prompt_authority() {
let prompts = epiphany_specialist_prompt_config();

View file

@ -21,7 +21,7 @@ use epiphany_core::EpiphanyCultMeshSwarmBrakeEntry;
use epiphany_core::EpiphanyLocalVerseContext;
use epiphany_core::EpiphanyProcessObservation as ProcessObservation;
use epiphany_core::authenticate_epiphany_cultmesh_semantic_projector_launch;
use epiphany_core::authenticate_resident_provider_pair;
use epiphany_core::authenticate_resident_provider;
use epiphany_core::epiphany_cultmesh_daemon_poke_intent_from_status;
use epiphany_core::epiphany_cultmesh_daemon_poke_receipt_for_intent;
use epiphany_core::idunn_recover_memory_semantic_projection_from_cultmesh;
@ -934,25 +934,17 @@ fn publish_managed_service_iteration_health(
let mut terminal_current = 0_usize;
let mut warming = 0_usize;
let mut contradictions = Vec::new();
if let (Some(heartbeat_store), Some(resident_store)) = (
args.resident_heartbeat_store.as_deref(),
args.resident_self_store.as_deref(),
) {
expected += 2;
let pair = authenticate_resident_provider_pair(
if let Some(resident_store) = args.resident_self_store.as_deref() {
expected += 1;
let provider = authenticate_resident_provider(
release,
authenticated_release_witness_sha256,
heartbeat_store,
resident_store,
args.resident_provider_stale_seconds.saturating_mul(1000),
);
terminal_current += pair.terminal_current;
warming += pair.warming;
contradictions.extend(pair.contradictions);
} else if args.resident_heartbeat_store.is_some() || args.resident_self_store.is_some() {
expected += 2;
contradictions
.push("resident health requires both heartbeat and Self provider stores".into());
terminal_current += provider.terminal_current;
warming += provider.warming;
contradictions.extend(provider.contradictions);
}
for &service_id in required {
let Some(policy) = policies
@ -3897,17 +3889,6 @@ mod provider_status_ownership_tests {
assert_eq!(unresolved_attempt_failure_count(3), 4);
}
#[test]
fn supervisor_source_has_no_provider_status_writer() {
let source = include_str!("epiphany-daemon-supervisor.rs");
let provider_writer = ["write_epiphany_cultmesh_", "daemon_status"].concat();
let heartbeat_assignment = ["next_status.", "last_heartbeat_utc"].concat();
let action_assignment = ["next_status.", "operator_action"].concat();
assert!(!source.contains(&provider_writer));
assert!(!source.contains(&heartbeat_assignment));
assert!(!source.contains(&action_assignment));
}
}
#[cfg(test)]
@ -3970,40 +3951,6 @@ mod service_lifecycle_brake_authority_tests {
}
}
#[test]
fn supervisor_physiology_survives_the_lifecycle_brake_without_reconciliation() {
let source = include_str!("epiphany-daemon-supervisor.rs");
let start = source.find("fn managed_service_serve(").unwrap();
let tail = &source[start..];
let end = tail.find("\nfn ").unwrap();
let body = &tail[..end];
let brake = body.find("load_epiphany_cultmesh_swarm_brake").unwrap();
let guard = body.find("if !lifecycle_braked").unwrap();
let reconcile = body.find("managed_service_reconcile").unwrap();
let health = body
.find("publish_managed_service_iteration_health")
.unwrap();
assert!(brake < guard && guard < reconcile && reconcile < health);
assert!(body.contains("\"reconciledPolicyCount\""));
}
#[test]
fn direct_managed_reconcile_checks_the_brake_before_any_lifecycle_mutation() {
let source = include_str!("epiphany-daemon-supervisor.rs");
let start = source.find("fn managed_service_reconcile(").unwrap();
let tail = &source[start..];
let end = tail.find("\nfn ").unwrap();
let body = &tail[..end];
let brake = body.find("load_epiphany_cultmesh_swarm_brake").unwrap();
let gate = body
.find("assert_swarm_brake_allows_service_lifecycle_entry")
.unwrap();
let policy = body
.find("load_epiphany_cultmesh_managed_service_policy")
.unwrap();
assert!(brake < gate && gate < policy);
}
#[test]
fn daemon_poke_reads_the_narrow_brake_without_weakening_scope() {
let daemon = daemon();
@ -4807,7 +4754,6 @@ struct Args {
idunn_deployment_request_id: Option<String>,
idunn_provider_health_identity_store: Option<PathBuf>,
idunn_provider_health_public_anchor: Option<PathBuf>,
resident_heartbeat_store: Option<PathBuf>,
resident_self_store: Option<PathBuf>,
resident_provider_stale_seconds: u64,
}
@ -4865,7 +4811,6 @@ impl Args {
let mut idunn_deployment_request_id = None;
let mut idunn_provider_health_identity_store = None;
let mut idunn_provider_health_public_anchor = None;
let mut resident_heartbeat_store = None;
let mut resident_self_store = None;
let mut resident_provider_stale_seconds = 180_u64;
@ -5078,13 +5023,6 @@ impl Args {
"missing --idunn-provider-health-public-anchor value",
)?));
}
"--resident-heartbeat-store" => {
resident_heartbeat_store = Some(PathBuf::from(
values
.next()
.context("missing --resident-heartbeat-store value")?,
));
}
"--resident-self-store" => {
resident_self_store = Some(PathBuf::from(
values
@ -5273,7 +5211,6 @@ impl Args {
idunn_deployment_request_id,
idunn_provider_health_identity_store,
idunn_provider_health_public_anchor,
resident_heartbeat_store,
resident_self_store,
resident_provider_stale_seconds,
})

View file

@ -1,601 +0,0 @@
use anyhow::Context;
use anyhow::Result;
use anyhow::anyhow;
use epiphany_core::EpiphanyCultMeshSwarmBrakeEntry;
use epiphany_core::EpiphanyHeartbeatArtifactRetentionReceipt;
use epiphany_core::HeartbeatStaleTurnRepairOptions;
use epiphany_core::heartbeat_local_provider_status;
use epiphany_core::heartbeat_status_projection;
use epiphany_core::initialize_heartbeat_store;
use epiphany_core::load_epiphany_cultmesh_swarm_brake;
use epiphany_core::load_heartbeat_state_entry;
use epiphany_core::pulse_resident_self_heartbeat;
use epiphany_core::recover_stale_heartbeat_store;
use epiphany_core::resident_cognitive_runtime_id;
use epiphany_core::retain_heartbeat_pulse_artifacts;
use epiphany_core::{
ResidentProviderReadiness, acquire_resident_process_singleton,
authenticate_epiphany_packaged_release, capture_process_instance,
publish_resident_provider_readiness,
};
use std::env;
use std::fs;
use std::path::Path;
use std::path::PathBuf;
use std::sync::{
Arc,
atomic::{AtomicBool, Ordering},
};
use std::thread;
use std::time::Duration;
fn main() -> Result<()> {
let mut args = env::args().skip(1);
let Some(command) = args.next() else {
return usage();
};
let mut store_path: Option<PathBuf> = None;
let mut artifact_dir: Option<PathBuf> = None;
let mut local_verse_store: Option<PathBuf> = None;
let mut target_heartbeat_rate = 1.0_f64;
let mut schedule_id = "epiphany-heartbeat".to_string();
let mut source_scene_ref = "epiphany/coordinator".to_string();
let mut limit = 8_usize;
let mut resident_self_store: Option<PathBuf> = None;
let mut resident_runtime_store: Option<PathBuf> = None;
let mut repair_reason: Option<String> = None;
let mut max_age_seconds = 1800_i64;
let mut now_utc: Option<String> = None;
let mut interval_seconds = 120_u64;
let mut max_iterations = 0_u64;
let mut retain_pulse_artifacts = 256_usize;
let mut retention_batch_size = 64_usize;
let mut release_store: Option<PathBuf> = None;
let mut release_runtime_id: Option<String> = None;
let mut release_id: Option<String> = None;
let mut release_witness_sha256: Option<String> = None;
while let Some(arg) = args.next() {
match arg.as_str() {
"--store" => store_path = Some(next_path(&mut args, "--store")?),
"--artifact-dir" => artifact_dir = Some(next_path(&mut args, "--artifact-dir")?),
"--local-verse-store" => {
local_verse_store = Some(next_path(&mut args, "--local-verse-store")?)
}
"--target-heartbeat-rate" => {
target_heartbeat_rate = next_value(&mut args, "--target-heartbeat-rate")?.parse()?
}
"--schedule-id" => schedule_id = next_value(&mut args, "--schedule-id")?,
"--source-scene-ref" => source_scene_ref = next_value(&mut args, "--source-scene-ref")?,
"--limit" => limit = next_value(&mut args, "--limit")?.parse()?,
"--resident-self-store" => {
resident_self_store = Some(next_path(&mut args, "--resident-self-store")?)
}
"--resident-runtime-store" => {
resident_runtime_store = Some(next_path(&mut args, "--resident-runtime-store")?)
}
"--reason" => repair_reason = Some(next_value(&mut args, "--reason")?),
"--max-age-seconds" => {
max_age_seconds = next_value(&mut args, "--max-age-seconds")?.parse()?
}
"--now-utc" => now_utc = Some(next_value(&mut args, "--now-utc")?),
"--interval-seconds" => {
interval_seconds = next_value(&mut args, "--interval-seconds")?.parse()?
}
"--max-iterations" => {
max_iterations = next_value(&mut args, "--max-iterations")?.parse()?
}
"--retain-pulse-artifacts" => {
retain_pulse_artifacts =
next_value(&mut args, "--retain-pulse-artifacts")?.parse()?
}
"--retention-batch-size" => {
retention_batch_size = next_value(&mut args, "--retention-batch-size")?.parse()?
}
"--release-store" => release_store = Some(next_path(&mut args, "--release-store")?),
"--release-runtime-id" => {
release_runtime_id = Some(next_value(&mut args, "--release-runtime-id")?)
}
"--release-id" => release_id = Some(next_value(&mut args, "--release-id")?),
"--release-witness-sha256" => {
release_witness_sha256 = Some(next_value(&mut args, "--release-witness-sha256")?)
}
_ => return Err(anyhow!("unknown argument {arg:?}")),
}
}
match command.as_str() {
"init" => {
let store_path = store_path.ok_or_else(|| anyhow!("init requires --store"))?;
let state = initialize_heartbeat_store(&store_path, target_heartbeat_rate)?;
println!(
"{}",
serde_json::json!({
"ok": true,
"command": "init",
"storeFile": store_path,
"schemaVersion": state.schema_version,
"participants": state.participants.len(),
"history": state.history.len(),
})
);
}
"repair-stale" => {
let store_path = store_path.ok_or_else(|| anyhow!("repair-stale requires --store"))?;
let artifact_dir =
artifact_dir.ok_or_else(|| anyhow!("repair-stale requires --artifact-dir"))?;
let result = recover_stale_heartbeat_store(
&store_path,
&artifact_dir,
HeartbeatStaleTurnRepairOptions {
max_age_seconds,
now_utc,
reason: repair_reason.unwrap_or_else(|| {
"Operator requested stale heartbeat turn recovery.".to_string()
}),
},
)?;
println!("{}", result);
}
"retain-artifacts" => {
let store_path =
store_path.ok_or_else(|| anyhow!("retain-artifacts requires --store"))?;
let artifact_dir =
artifact_dir.ok_or_else(|| anyhow!("retain-artifacts requires --artifact-dir"))?;
let receipt = retain_heartbeat_pulse_artifacts(
&store_path,
&artifact_dir,
retain_pulse_artifacts,
retention_batch_size,
&chrono::Utc::now().to_rfc3339(),
)?;
println!(
"{}",
serde_json::json!({
"schemaVersion": "epiphany.heartbeat.artifact_retention_command.v0",
"status": if receipt.is_some() { "completed" } else { "within-bound" },
"receipt": receipt.as_ref().map(retention_receipt_projection),
"privateStateExposed": false
})
);
}
"status" => {
let store_path = store_path.ok_or_else(|| anyhow!("status requires --store"))?;
if let Some(artifact_dir) = artifact_dir {
println!(
"{}",
heartbeat_status_projection(
&store_path,
artifact_dir,
target_heartbeat_rate,
limit
)?
);
} else {
let state = load_heartbeat_state_entry(&store_path)?;
println!(
"{}",
serde_json::json!({
"ok": true,
"command": "status",
"store": store_path,
"present": state.is_some(),
"schemaVersion": state.as_ref().map(|value| value.schema_version.as_str()),
"participants": state.as_ref().map(|value| value.participants.len()),
"history": state.as_ref().map(|value| value.history.len()),
})
);
}
}
"serve" => {
if interval_seconds == 0 {
return Err(anyhow!(
"serve requires --interval-seconds greater than zero"
));
}
let store_path = store_path.ok_or_else(|| anyhow!("serve requires --store"))?;
let _singleton = acquire_resident_process_singleton("heartbeat", &store_path)?;
let artifact_dir =
artifact_dir.ok_or_else(|| anyhow!("serve requires --artifact-dir"))?;
let provider_release = if resident_self_store.is_some() {
let release_store = release_store
.as_deref()
.ok_or_else(|| anyhow!("resident heartbeat serve requires --release-store"))?;
let runtime_id = release_runtime_id.as_deref().ok_or_else(|| {
anyhow!("resident heartbeat serve requires --release-runtime-id")
})?;
let release_id = release_id
.as_deref()
.ok_or_else(|| anyhow!("resident heartbeat serve requires --release-id"))?;
let witness = release_witness_sha256.as_deref().ok_or_else(|| {
anyhow!("resident heartbeat serve requires --release-witness-sha256")
})?;
Some(authenticate_epiphany_packaged_release(
release_store,
runtime_id,
release_id,
witness,
)?)
} else {
None
};
let provider_process = if provider_release.is_some() {
Some(capture_process_instance(std::process::id())?)
} else {
None
};
let brake_runtime_id = heartbeat_brake_runtime_id(
&resident_self_store,
&resident_runtime_store,
release_runtime_id.as_deref(),
)?;
fs::create_dir_all(&artifact_dir).with_context(|| {
format!(
"failed to create heartbeat serve artifact directory {}",
artifact_dir.display()
)
})?;
let mut completed_iterations = highest_pulse_artifact_sequence(&artifact_dir)?;
let mut refused_pulses = 0_u64;
let shutdown_requested = install_shutdown_signal_owner()?;
loop {
if heartbeat_serve_should_stop(
&shutdown_requested,
max_iterations,
completed_iterations,
) {
break;
}
let iteration = completed_iterations + 1;
if let (Some(release), Some(process)) = (&provider_release, &provider_process) {
publish_resident_provider_readiness(
&store_path,
ResidentProviderReadiness {
schema_version:
epiphany_core::RESIDENT_PROVIDER_READINESS_SCHEMA_VERSION.into(),
provider: "heartbeat".into(),
runtime_id: release.runtime_id.clone(),
release_id: release.release_id.clone(),
release_witness_sha256: release_witness_sha256
.clone()
.expect("authenticated witness"),
source_commit: release.source_commit_sha.clone(),
publisher_sequence: 0,
observed_at_millis: chrono::Utc::now().timestamp_millis().max(0) as u64,
process_id: process.process_id,
process_creation_token: process.creation_token,
process_executable_path: process.executable_path.display().to_string(),
status: heartbeat_local_provider_status(
&store_path,
resident_self_store.as_deref().expect("resident store"),
)
.into(),
private_state_exposed: false,
},
)?;
}
let iteration_dir = artifact_dir.join(format!("pulse-{iteration:06}"));
let brake = active_swarm_brake(&local_verse_store, brake_runtime_id.as_deref())?;
if let Some(resident_store) = resident_self_store.as_deref() {
let pulse = pulse_resident_self_heartbeat(
&store_path,
resident_store,
&iteration_dir,
brake.is_some(),
&format!("{schedule_id}.serve-{iteration:06}"),
&source_scene_ref,
)?;
if pulse.status != "idle" {
completed_iterations = iteration;
refused_pulses += u64::from(brake.is_some());
let retention = retain_heartbeat_pulse_artifacts(
&store_path,
&artifact_dir,
retain_pulse_artifacts,
retention_batch_size,
&chrono::Utc::now().to_rfc3339(),
)?;
println!(
"{}",
serde_json::json!({
"schemaVersion":"epiphany.heartbeat.serve_pulse.v0", "status":pulse.status,
"owner":"heartbeat", "iteration":iteration, "artifactDir":iteration_dir,
"acknowledgedTerminalId":pulse.acknowledged_terminal_id, "grantId":pulse.grant_id,
"brakeId":brake.as_ref().map(|value| &value.brake_id),
"retention":retention.as_ref().map(retention_receipt_projection),
"privateStateExposed":false
})
);
if max_iterations > 0 && completed_iterations >= max_iterations {
break;
}
wait_for_shutdown(
&shutdown_requested,
Duration::from_secs(interval_seconds),
);
continue;
}
}
if let Some(brake) = brake {
completed_iterations = iteration;
refused_pulses += 1;
let retention = retain_heartbeat_pulse_artifacts(
&store_path,
&artifact_dir,
retain_pulse_artifacts,
retention_batch_size,
&chrono::Utc::now().to_rfc3339(),
)?;
println!(
"{}",
serde_json::json!({
"schemaVersion": "epiphany.heartbeat.serve_pulse.v0",
"status": "refused-by-swarm-brake",
"owner": "heartbeat",
"lifecycleOwner": "Idunn",
"iteration": iteration,
"artifactDir": iteration_dir,
"brakeId": brake.brake_id,
"brakeScope": brake.scope,
"reason": brake.reason,
"retention": retention.as_ref().map(retention_receipt_projection),
"privateStateExposed": false
})
);
if max_iterations > 0 && completed_iterations >= max_iterations {
break;
}
wait_for_shutdown(&shutdown_requested, Duration::from_secs(interval_seconds));
continue;
}
completed_iterations = iteration;
let retention = retain_heartbeat_pulse_artifacts(
&store_path,
&artifact_dir,
retain_pulse_artifacts,
retention_batch_size,
&chrono::Utc::now().to_rfc3339(),
)?;
println!(
"{}",
serde_json::json!({
"schemaVersion": "epiphany.heartbeat.serve_pulse.v0",
"status": "idle",
"owner": "heartbeat",
"lifecycleOwner": "Idunn",
"iteration": iteration,
"artifactDir": iteration_dir,
"retention": retention.as_ref().map(retention_receipt_projection),
"privateStateExposed": false
})
);
if max_iterations > 0 && completed_iterations >= max_iterations {
break;
}
wait_for_shutdown(&shutdown_requested, Duration::from_secs(interval_seconds));
}
println!(
"{}",
serde_json::json!({
"schemaVersion": "epiphany.heartbeat.serve_receipt.v0",
"status": "stopped-cleanly",
"owner": "heartbeat",
"lifecycleOwner": "Idunn",
"store": store_path,
"artifactDir": artifact_dir,
"intervalSeconds": interval_seconds,
"completedIterations": completed_iterations,
"refusedPulses": refused_pulses,
"bounded": max_iterations > 0,
"shutdownRequested": shutdown_requested.load(Ordering::SeqCst),
"privateStateExposed": false
})
);
}
_ => return usage(),
}
Ok(())
}
fn install_shutdown_signal_owner() -> Result<Arc<AtomicBool>> {
let requested = Arc::new(AtomicBool::new(false));
let signal = Arc::clone(&requested);
ctrlc::try_set_handler(move || {
signal.store(true, Ordering::SeqCst);
})
.context("failed to install Heartbeat shutdown signal owner")?;
Ok(requested)
}
fn heartbeat_serve_should_stop(
shutdown_requested: &AtomicBool,
max_iterations: u64,
completed_iterations: u64,
) -> bool {
shutdown_requested.load(Ordering::SeqCst)
|| (max_iterations > 0 && completed_iterations >= max_iterations)
}
fn wait_for_shutdown(requested: &AtomicBool, duration: Duration) {
let deadline = std::time::Instant::now() + duration;
while !requested.load(Ordering::SeqCst) {
let remaining = deadline.saturating_duration_since(std::time::Instant::now());
if remaining.is_zero() {
break;
}
thread::sleep(remaining.min(Duration::from_millis(50)));
}
}
fn next_path(args: &mut impl Iterator<Item = String>, name: &str) -> Result<PathBuf> {
Ok(PathBuf::from(next_value(args, name)?))
}
fn retention_receipt_projection(
receipt: &EpiphanyHeartbeatArtifactRetentionReceipt,
) -> serde_json::Value {
serde_json::json!({
"schemaVersion": receipt.schema_version,
"receiptId": receipt.receipt_id,
"planId": receipt.plan_id,
"status": receipt.status,
"deletedDirectories": receipt.deleted_directories,
"deletedFileCount": receipt.deleted_file_count,
"deletedByteCount": receipt.deleted_byte_count,
"completedAtUtc": receipt.completed_at_utc,
"privateStateExposed": receipt.private_state_exposed,
})
}
fn highest_pulse_artifact_sequence(artifact_root: &Path) -> Result<u64> {
let mut highest = 0_u64;
for entry in fs::read_dir(artifact_root)? {
let entry = entry?;
let file_type = entry.file_type()?;
if file_type.is_symlink() {
return Err(anyhow!("heartbeat artifact root contains a symlink"));
}
if !file_type.is_dir() {
continue;
}
let name = entry
.file_name()
.into_string()
.map_err(|_| anyhow!("heartbeat artifact directory name is not UTF-8"))?;
let digits = name
.strip_prefix("pulse-")
.filter(|digits| digits.len() == 6 && digits.bytes().all(|byte| byte.is_ascii_digit()))
.ok_or_else(|| {
anyhow!("heartbeat artifact root contains unknown directory {name:?}")
})?;
highest = highest.max(digits.parse()?);
}
if highest >= 999_999 {
return Err(anyhow!("heartbeat pulse artifact sequence is exhausted"));
}
Ok(highest)
}
fn next_value(args: &mut impl Iterator<Item = String>, name: &str) -> Result<String> {
args.next()
.ok_or_else(|| anyhow!("{name} requires a value"))
}
fn usage() -> Result<()> {
Err(anyhow!(concat!(
"usage: epiphany-heartbeat-store init --store <path> [--target-heartbeat-rate <n>]\n",
" epiphany-heartbeat-store repair-stale --store <path> --artifact-dir <path> [options]\n",
" epiphany-heartbeat-store retain-artifacts --store <path> --artifact-dir <path> [--retain-pulse-artifacts <n>] [--retention-batch-size <n>]\n",
" epiphany-heartbeat-store status --store <path> [--artifact-dir <path>]\n",
" epiphany-heartbeat-store serve --store <path> --artifact-dir <path> [--local-verse-store <path>] [--resident-self-store <path> --resident-runtime-store <path>] [--interval-seconds <n>] [--max-iterations <n>]"
)))
}
fn heartbeat_brake_runtime_id(
resident_self_store: &Option<PathBuf>,
resident_runtime_store: &Option<PathBuf>,
release_runtime_id: Option<&str>,
) -> Result<Option<String>> {
if resident_self_store.is_some() {
let runtime_store = resident_runtime_store
.as_deref()
.ok_or_else(|| anyhow!("resident heartbeat serve requires --resident-runtime-store"))?;
return Ok(Some(resident_cognitive_runtime_id(runtime_store)?));
}
Ok(release_runtime_id.map(str::to_string))
}
fn active_swarm_brake(
local_verse_store: &Option<PathBuf>,
runtime_id: Option<&str>,
) -> Result<Option<EpiphanyCultMeshSwarmBrakeEntry>> {
let Some(local_verse_store) = local_verse_store else {
return Ok(None);
};
if !local_verse_store.exists() {
return Ok(None);
}
let runtime_id = runtime_id
.ok_or_else(|| anyhow!("heartbeat local Verse brake lookup requires runtime identity"))?;
Ok(
load_epiphany_cultmesh_swarm_brake(local_verse_store, runtime_id)?
.filter(|brake| brake.status == "engaged"),
)
}
#[cfg(test)]
mod brake_tests {
use super::*;
#[test]
fn heartbeat_serve_stops_for_signal_or_bounded_completion() {
let requested = AtomicBool::new(false);
assert!(!heartbeat_serve_should_stop(&requested, 0, 42));
assert!(!heartbeat_serve_should_stop(&requested, 43, 42));
assert!(heartbeat_serve_should_stop(&requested, 42, 42));
requested.store(true, Ordering::SeqCst);
assert!(heartbeat_serve_should_stop(&requested, 0, 42));
}
#[test]
fn heartbeat_shutdown_interrupts_interval_wait() {
let requested = AtomicBool::new(true);
let started = std::time::Instant::now();
wait_for_shutdown(&requested, Duration::from_secs(1));
assert!(started.elapsed() < Duration::from_millis(100));
}
#[test]
fn heartbeat_brake_lookup_uses_the_exact_requested_namespace() -> Result<()> {
let temp = tempfile::tempdir()?;
let store = temp.path().join("verse.cc");
let store_arg = Some(store.clone());
let mut brake =
epiphany_core::default_epiphany_cultmesh_swarm_brake("2026-07-18T00:00:00Z");
brake.status = "engaged".into();
brake.reason = "test".into();
let mut legacy_node =
epiphany_core::open_epiphany_cultmesh_node(&store, "legacy-writer-runtime")?;
legacy_node.put("epiphany-local/swarm-brake", &brake)?;
legacy_node.flush()?;
assert!(active_swarm_brake(&store_arg, Some("epiphany-yggdrasil"))?.is_some());
epiphany_core::write_epiphany_cultmesh_swarm_brake(&store, "wrong-runtime", brake.clone())?;
assert!(active_swarm_brake(&store_arg, Some("epiphany-yggdrasil"))?.is_none());
epiphany_core::write_epiphany_cultmesh_swarm_brake(&store, "epiphany-yggdrasil", brake)?;
assert!(active_swarm_brake(&store_arg, Some("epiphany-yggdrasil"))?.is_some());
Ok(())
}
#[test]
fn resident_heartbeat_uses_mounted_cognitive_runtime_for_braking() -> Result<()> {
let temp = tempfile::tempdir()?;
let runtime_store = temp.path().join("runtime.cc");
epiphany_core::initialize_runtime_spine(
&runtime_store,
epiphany_core::RuntimeSpineInitOptions {
runtime_id: "cognitive-runtime".into(),
display_name: "Cognitive runtime".into(),
created_at: "2026-08-10T00:00:00Z".into(),
},
)?;
let selected = heartbeat_brake_runtime_id(
&Some(temp.path().join("resident.cc")),
&Some(runtime_store),
Some("deployment-runtime"),
)?;
assert_eq!(selected.as_deref(), Some("cognitive-runtime"));
Ok(())
}
#[test]
fn resident_heartbeat_resumes_after_highest_artifact_sequence() -> Result<()> {
let temp = tempfile::tempdir()?;
fs::create_dir(temp.path().join("pulse-000003"))?;
fs::create_dir(temp.path().join("pulse-012557"))?;
assert_eq!(highest_pulse_artifact_sequence(temp.path())?, 12_557);
fs::create_dir(temp.path().join("alien"))?;
assert!(highest_pulse_artifact_sequence(temp.path()).is_err());
Ok(())
}
}

View file

@ -2215,21 +2215,6 @@ fn home_dir() -> PathBuf {
mod tests {
use super::*;
#[test]
fn resident_required_action_refusal_precedes_the_action_match() {
let source = include_str!("epiphany-mvp-coordinator.rs");
let refusal = source
.find("requiredActionRefusal")
.expect("required-action refusal");
let action_match = source
.find("match action.as_str()")
.expect("coordinator action match");
assert!(refusal < action_match);
let refusal_body = &source[refusal..action_match];
assert!(refusal_body.contains("consequence\": \"none"));
assert!(refusal_body.contains("break;"));
}
#[test]
fn proposal_cli_argument_is_only_an_assertion_over_self_derived_authority() {
let request = "repo-frontier-proposal-modeling-request";
@ -2338,51 +2323,6 @@ mod tests {
Ok(())
}
#[test]
fn coordinator_binary_has_no_codex_host_or_epiphany_json_rpc_dependency() {
let source = include_str!("epiphany-mvp-coordinator.rs");
let production = source.split("#[cfg(test)]").next().unwrap_or(source);
for forbidden in ["AppServerClient", "thread/epiphany/", "codex-app-server"] {
assert!(
!production.contains(forbidden),
"native coordinator regrew host dependency {forbidden:?}"
);
}
let compatibility_field = ["epiphany", "State"].concat();
assert!(!production.contains(&compatibility_field));
assert!(!production.contains("--thread-state-store"));
}
#[test]
fn coordinator_uses_keyed_family_owners_without_generic_role_routing() {
let source = include_str!("epiphany-mvp-coordinator.rs");
let production = source.split("#[cfg(test)]").next().unwrap_or(source);
for required in [
"launch_current_imagination_consideration_work",
"launch_current_admitted_model_direction_consideration_work",
"launch_current_frontier_research_work",
"launch_current_frontier_verification_work",
] {
assert!(
production.contains(required),
"missing keyed owner {required:?}"
);
}
for forbidden in [
"fn launch_role(",
"fn accept_role(",
"fn supersede_role_result(",
".apply_state_update(",
".accept_role(",
"stateRevision",
] {
assert!(
!production.contains(forbidden),
"generic aggregate role authority survived: {forbidden:?}"
);
}
}
#[test]
fn continue_implementation_is_not_a_passive_stop_action() {
assert!(!is_stop_action("continueImplementation"));

View file

@ -623,20 +623,4 @@ mod tests {
assert_eq!(sanitized["decision"]["action"], "continue");
}
#[test]
fn source_has_no_thread_aggregate_or_generic_interrupt() {
let source = include_str!("epiphany-mvp-status.rs");
let production = source.split("#[cfg(test)]").next().unwrap_or(source);
for forbidden in [
"EpiphanyThreadState",
"read_accepted_coordinator_state",
"latest_runtime_job_id_for_binding",
"derive_coordinator_finding_signals",
"expected_revision",
"interrupt_job",
"stateRevision",
] {
assert!(!production.contains(forbidden));
}
}
}

View file

@ -9,9 +9,10 @@ use epiphany_core::{
bind_runtime_repository_domain, capture_process_instance, coordinator_run_receipts,
derive_resident_cognition_readiness, enqueue_resident_self_pressure_idempotent,
import_bifrost_persona_feedback_deliveries, ingest_resident_self_current_work_pressure,
live_resident_self_typed_request_ids, load_epiphany_cultmesh_swarm_brake,
issue_resident_self_grant, live_resident_self_typed_request_ids,
load_epiphany_cultmesh_swarm_brake,
load_resident_self_state, materialize_resident_self_domain_obligations,
observe_process_instance, pending_resident_self_acks, prepare_resident_self_launch,
observe_process_instance, prepare_resident_self_launch, resident_self_terminal_receipts,
publish_resident_provider_readiness, reap_exited_child_process,
recover_dead_runtime_worker_attempts, resident_cognitive_runtime_id,
resident_prepared_launch_thread_id, resident_self_child_claim,
@ -53,15 +54,9 @@ fn main() -> Result<()> {
&args.policy.release_store,
],
)?;
if args.heartbeat_store == args.state_store {
return Err(anyhow!(
"heartbeat and resident Self stores must be physically separate"
));
}
if matches!(args.command, CommandKind::Status) {
let projection = derive_resident_cognition_readiness(ResidentReadinessRequest {
release_store: &args.policy.release_store,
heartbeat_store: &args.heartbeat_store,
resident_store: &args.state_store,
policy: &args.policy,
release_runtime_id: &args.policy.release_runtime_id,
@ -73,7 +68,7 @@ fn main() -> Result<()> {
return Ok(());
}
require_resident_git("git")?;
let _singleton = acquire_resident_process_singleton("resident-self", &args.state_store)?;
let _singleton = acquire_resident_process_singleton(&args.state_store)?;
if let Some(pressure) = args.pressure.as_ref() {
enqueue_resident_self_pressure_idempotent(&args.state_store, pressure)?;
}
@ -96,11 +91,9 @@ fn main() -> Result<()> {
loop {
let shutting_down = shutdown_requested.load(Ordering::SeqCst);
let outcome = cycle(&args, &mut state, &mut ports, shutting_down)?;
if resident_maintenance_required(
state.revision,
last_maintained_revision,
std::time::Instant::now() >= next_maintenance_at,
) {
if last_maintained_revision != Some(state.revision)
|| std::time::Instant::now() >= next_maintenance_at
{
maintain_resident_runtime(&args, &state)?;
last_maintained_revision = Some(state.revision);
next_maintenance_at = std::time::Instant::now() + RESIDENT_MAINTENANCE_INTERVAL;
@ -151,22 +144,11 @@ fn maintain_resident_runtime(args: &Args, state: &ResidentSelfState) -> Result<(
Ok(())
}
fn resident_maintenance_required(
revision: u64,
last_maintained_revision: Option<u64>,
interval_due: bool,
) -> bool {
interval_due || last_maintained_revision != Some(revision)
}
fn retain_runtime_receipts(args: &Args, state: &ResidentSelfState) -> Result<()> {
if !runtime_receipt_retention_allowed(
state.active_turn.is_some(),
state.prepared_launch.is_some(),
) {
if state.active_turn.is_some() || state.prepared_launch.is_some() {
return Ok(());
}
let mut preserved = pending_resident_self_acks(&args.state_store)?
let mut preserved = resident_self_terminal_receipts(&args.state_store)?
.into_iter()
.map(|ack| ack.coordinator_receipt_id)
.collect::<BTreeSet<_>>();
@ -207,10 +189,6 @@ fn retain_runtime_worker_attempts(args: &Args) -> Result<()> {
Ok(())
}
fn runtime_receipt_retention_allowed(has_active_turn: bool, has_prepared_launch: bool) -> bool {
!has_active_turn && !has_prepared_launch
}
fn publish_self_readiness(args: &Args) -> Result<()> {
let process = capture_process_instance(std::process::id())?;
publish_resident_provider_readiness(
@ -234,26 +212,6 @@ fn publish_self_readiness(args: &Args) -> Result<()> {
Ok(())
}
fn run_feedback_import_if_released(
brake_engaged: bool,
import: impl FnOnce() -> Result<()>,
) -> Result<()> {
if brake_engaged {
return Ok(());
}
import()
}
fn run_cycle_ingress_if_released(
blocked: bool,
ingress: impl FnOnce() -> Result<()>,
) -> Result<()> {
if blocked {
return Ok(());
}
ingress()
}
fn install_shutdown_signal_owner() -> Result<Arc<AtomicBool>> {
let requested = Arc::new(AtomicBool::new(false));
let signal = Arc::clone(&requested);
@ -308,7 +266,7 @@ fn cycle(
recover_dead_runtime_worker_attempts(&args.policy.runtime_store, now)?;
let brake_engaged = ports.brake_engaged()?;
let cognitive_runtime_id = ports.cognitive_runtime_id().to_string();
run_feedback_import_if_released(brake_engaged || shutdown_requested, || {
if !brake_engaged && !shutdown_requested {
import_bifrost_persona_feedback_deliveries(
&args.persona_feedback_source_store,
&args.persona_feedback_store,
@ -319,14 +277,13 @@ fn cycle(
)?;
admit_persona_feedback_to_social(
&args.persona_feedback_store,
&args.heartbeat_store,
&args.persona_social_store,
&cognitive_runtime_id,
&args.policy.model_provider,
&args.persona_model_allowed_data_classifications,
)?;
Ok(())
})?;
run_cycle_ingress_if_released(brake_engaged || shutdown_requested, || {
}
if !brake_engaged && !shutdown_requested {
bind_runtime_repository_domain(
&args.policy.runtime_store,
&args.feedback_target_repository,
@ -345,8 +302,7 @@ fn cycle(
&args.policy.runtime_store,
now,
)?;
Ok(())
})?;
}
*state = load_resident_self_state(&args.state_store)?;
if let Some(prepared) = state.prepared_launch.clone() {
if let Some(claim) = resident_self_child_claim(&args.state_store, &prepared.preparation_id)?
@ -442,6 +398,7 @@ fn cycle(
if shutdown_requested || ports.brake_engaged()? {
return Ok(ResidentSelfOutcome::Braked);
}
issue_resident_self_grant(&args.state_store, now)?;
let Some(prepared) = prepare_resident_self_launch(&args.state_store, &args.policy, now)? else {
return Ok(ResidentSelfOutcome::Sleeping);
};
@ -479,7 +436,7 @@ fn summary(
"activeTurnId": state.active_turn.as_ref().map(|turn| &turn.turn_id),
"shutdownRequested": shutdown_requested,
"nextEligibleAtMillis": state.next_eligible_at_millis,
"wakeAuthority": "standard heartbeat consumes typed operator, admitted Modeling-map direction consideration, Persona feedback, or Imagination proposal pressure and emits one single-consumption Self grant",
"wakeAuthority": "Resident Self claims one typed pending pressure and emits one single-consumption grant",
"preparedRecovery": if state.prepared_launch.is_some() { "fail-closed-awaiting-exact-child-claim-or-witnessed-recovery" } else { "not-required" },
"authority": "Self may launch one bounded coordinator turn; it cannot directly invoke model/tools, mutate Mind/Hands, review, release, or deploy",
"privateStateExposed": false
@ -505,7 +462,7 @@ fn validate_command_turn_bound(command: CommandKind, max_steps: u64) -> Result<(
struct Args {
command: CommandKind,
state_store: PathBuf,
heartbeat_store: PathBuf,
persona_social_store: PathBuf,
provider_freshness_seconds: u64,
retained_closed_lifecycles: usize,
retained_coordinator_receipts: usize,
@ -615,7 +572,7 @@ impl Args {
Ok(Self {
command,
state_store: path("--state-store")?,
heartbeat_store: path("--heartbeat-store")?,
persona_social_store: path("--persona-social-store")?,
provider_freshness_seconds: u64v("--provider-freshness-seconds", 180)?,
retained_closed_lifecycles: u64v("--retained-closed-lifecycles", 256)?
.try_into()
@ -877,68 +834,4 @@ mod brake_tests {
Ok(())
}
#[test]
fn engaged_brake_does_not_touch_the_bifrost_provider_store() -> Result<()> {
let touched = std::cell::Cell::new(false);
run_feedback_import_if_released(true, || {
touched.set(true);
anyhow::bail!("absent Bifrost delivery store was touched")
})?;
assert!(!touched.get());
assert!(
run_feedback_import_if_released(false, || {
anyhow::bail!("released cognition requires Bifrost")
})
.is_err()
);
Ok(())
}
#[test]
fn terminal_fulfillment_has_explicit_operator_projection() {
assert_eq!(
ResidentSelfOutcome::AwaitingFulfillment.operator_status(),
"awaiting-fulfillment"
);
}
#[test]
fn shutdown_wait_returns_immediately_when_already_requested() {
let requested = AtomicBool::new(true);
let started = std::time::Instant::now();
wait_for_shutdown(&requested, Duration::from_secs(1));
assert!(started.elapsed() < Duration::from_millis(100));
}
#[test]
fn brake_or_shutdown_does_not_admit_new_cycle_ingress() -> Result<()> {
let touched = std::cell::Cell::new(false);
run_cycle_ingress_if_released(true, || {
touched.set(true);
Ok(())
})?;
assert!(!touched.get());
run_cycle_ingress_if_released(false, || {
touched.set(true);
Ok(())
})?;
assert!(touched.get());
Ok(())
}
#[test]
fn runtime_receipt_retention_refuses_active_or_prepared_launch_authority() {
assert!(runtime_receipt_retention_allowed(false, false));
assert!(!runtime_receipt_retention_allowed(true, false));
assert!(!runtime_receipt_retention_allowed(false, true));
assert!(!runtime_receipt_retention_allowed(true, true));
}
#[test]
fn maintenance_follows_revision_and_deadline_not_poll_outcome() {
assert!(resident_maintenance_required(7, None, false,));
assert!(!resident_maintenance_required(7, Some(7), false,));
assert!(resident_maintenance_required(8, Some(7), false,));
assert!(resident_maintenance_required(7, Some(7), true,));
}
}

View file

@ -11118,22 +11118,4 @@ mod tests {
);
Ok(())
}
#[test]
fn bifrost_mirrors_name_arrival_order_without_rewriting_storage_keys() {
let source = include_str!("cultmesh_integration.rs");
let old_loader = ["load_", "latest_epiphany_cultmesh_bifrost_"].concat();
let old_field = ["pub ", "latest_bifrost_"].concat();
assert!(!source.contains(&old_loader));
assert!(!source.contains(&old_field));
assert_eq!(
source
.lines()
.filter(|line| {
line.contains("\"gamecult-local/bifrost/") && line.contains("/latest\"")
})
.count(),
6
);
}
}

View file

@ -3849,13 +3849,8 @@ mod tests {
&store,
1,
)?);
let current_work_grant = crate::heartbeat_issue_resident_self_grant(
&resident_store,
"current-work-schedule",
"current-work-action",
2,
)?
.expect("the exact current-work projection should receive one grant");
let current_work_grant = crate::issue_resident_self_grant(&resident_store, 2)?
.expect("the exact current-work projection should receive one grant");
assert_eq!(
current_work_grant.pressure_kind,
crate::RESIDENT_SELF_CURRENT_WORK_PRESSURE_KIND
@ -3921,9 +3916,9 @@ mod tests {
"a grant derived from the prior Mind projection must not launch"
);
assert!(crate::pending_resident_self_grant(&resident_store)?.is_none());
let supersession_acks = crate::pending_resident_self_acks(&resident_store)?;
assert_eq!(supersession_acks.len(), 1);
assert_eq!(supersession_acks[0].terminal_status, "superseded");
let terminal_receipts = crate::resident_self_terminal_receipts(&resident_store)?;
assert_eq!(terminal_receipts.len(), 1);
assert_eq!(terminal_receipts[0].terminal_status, "superseded");
assert_current_work_reentry_is_read_only(&store, &scheduled_work)?;
let mut scheduled_cache = crate::runtime_spine_cache(&store)?;
scheduled_cache.pull_all_backing_stores()?;
@ -4059,13 +4054,8 @@ mod tests {
!crate::ingest_resident_self_current_work_pressure(&resident_store, &store, 5)?,
"unchanged failed-attempt state must remain idempotent"
);
let retry_grant = crate::heartbeat_issue_resident_self_grant(
&resident_store,
"current-work-retry-schedule",
"current-work-retry-action",
6,
)?
.expect("failed Body attempt must receive a fresh exact-state grant");
let retry_grant = crate::issue_resident_self_grant(&resident_store, 6)?
.expect("failed Body attempt must receive a fresh exact-state grant");
assert_eq!(
retry_grant.provenance_ref,
format!(

File diff suppressed because it is too large Load diff

View file

@ -1,264 +0,0 @@
use cultcache_rs::DatabaseEntry;
use serde::Deserialize;
use serde::Serialize;
pub const HEARTBEAT_STATE_TYPE: &str = "epiphany.agent_heartbeat";
pub const HEARTBEAT_STATE_KEY: &str = "default";
pub const HEARTBEAT_STATE_SCHEMA_VERSION: &str = "epiphany.agent_heartbeat.v1";
pub const HEARTBEAT_STATUS_SCHEMA_VERSION: &str = "epiphany.agent_heartbeat_status.v0";
pub const INITIATIVE_SCHEMA_VERSION: &str = "epiphany.heartbeat_schedule.v0";
pub const HEARTBEAT_STALE_TURN_REPAIR_TYPE: &str = "epiphany.heartbeat.stale_turn_repair";
pub const HEARTBEAT_STALE_TURN_REPAIR_SCHEMA_VERSION: &str =
"epiphany.heartbeat.stale_turn_repair.v0";
pub const HEARTBEAT_STALE_TURN_REPAIR_LATEST_KEY: &str = "heartbeat/stale-turn-repair/latest";
pub const HEARTBEAT_ARTIFACT_RETENTION_PLAN_TYPE: &str =
"epiphany.heartbeat.artifact_retention_plan";
pub const HEARTBEAT_ARTIFACT_RETENTION_PLAN_SCHEMA_VERSION: &str =
"epiphany.heartbeat.artifact_retention_plan.v0";
pub const HEARTBEAT_ARTIFACT_RETENTION_PLAN_LATEST_KEY: &str =
"heartbeat/artifact-retention-plan/latest";
pub const HEARTBEAT_ARTIFACT_RETENTION_RECEIPT_TYPE: &str =
"epiphany.heartbeat.artifact_retention_receipt";
pub const HEARTBEAT_ARTIFACT_RETENTION_RECEIPT_SCHEMA_VERSION: &str =
"epiphany.heartbeat.artifact_retention_receipt.v0";
pub const HEARTBEAT_ARTIFACT_RETENTION_RECEIPT_LATEST_KEY: &str =
"heartbeat/artifact-retention-receipt/latest";
#[derive(Clone, Debug, PartialEq, DatabaseEntry)]
#[cultcache(
type = "epiphany.agent_heartbeat",
schema = "EpiphanyHeartbeatStateEntry"
)]
pub struct EpiphanyHeartbeatStateEntry {
#[cultcache(key = 0)]
pub schema_version: String,
#[cultcache(key = 1)]
pub target_heartbeat_rate: f64,
#[cultcache(key = 2)]
pub scene_clock: f64,
#[cultcache(key = 3)]
pub selection_policy: HeartbeatSelectionPolicy,
#[cultcache(key = 4)]
pub pacing_policy: HeartbeatPacingPolicy,
#[cultcache(key = 5)]
pub participants: Vec<HeartbeatParticipant>,
#[cultcache(key = 6)]
pub history: Vec<HeartbeatHistoryEvent>,
}
#[derive(Clone, Debug, PartialEq, DatabaseEntry)]
#[cultcache(
type = "epiphany.heartbeat.stale_turn_repair",
schema = "EpiphanyHeartbeatStaleTurnRepairReceipt"
)]
pub struct EpiphanyHeartbeatStaleTurnRepairReceipt {
#[cultcache(key = 0)]
pub schema_version: String,
#[cultcache(key = 1)]
pub receipt_id: String,
#[cultcache(key = 2)]
pub repaired_at_utc: String,
#[cultcache(key = 3)]
pub role_id: String,
#[cultcache(key = 4)]
pub agent_id: String,
#[cultcache(key = 5)]
pub action_id: String,
#[cultcache(key = 6)]
pub schedule_id: String,
#[cultcache(key = 7)]
pub started_at_utc: String,
#[cultcache(key = 8)]
pub stale_age_seconds: i64,
#[cultcache(key = 9)]
pub reason: String,
#[cultcache(key = 10)]
pub resulting_status: String,
#[cultcache(key = 11)]
pub next_ready_at: f64,
#[cultcache(key = 12)]
pub private_state_exposed: bool,
#[cultcache(key = 13)]
pub notes: Vec<String>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct HeartbeatArtifactRetentionMember {
pub directory_name: String,
pub manifest_sha256: String,
pub file_count: u64,
pub byte_count: u64,
}
#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)]
#[cultcache(
type = "epiphany.heartbeat.artifact_retention_plan",
schema = "EpiphanyHeartbeatArtifactRetentionPlan"
)]
pub struct EpiphanyHeartbeatArtifactRetentionPlan {
#[cultcache(key = 0)]
pub schema_version: String,
#[cultcache(key = 1)]
pub plan_id: String,
#[cultcache(key = 2)]
pub artifact_root: String,
#[cultcache(key = 3)]
pub retain_pulse_count: u64,
#[cultcache(key = 4)]
pub batch_size: u64,
#[cultcache(key = 5)]
pub members: Vec<HeartbeatArtifactRetentionMember>,
#[cultcache(key = 6)]
pub planned_at_utc: String,
#[cultcache(key = 7)]
pub private_state_exposed: bool,
}
#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)]
#[cultcache(
type = "epiphany.heartbeat.artifact_retention_receipt",
schema = "EpiphanyHeartbeatArtifactRetentionReceipt"
)]
pub struct EpiphanyHeartbeatArtifactRetentionReceipt {
#[cultcache(key = 0)]
pub schema_version: String,
#[cultcache(key = 1)]
pub receipt_id: String,
#[cultcache(key = 2)]
pub plan_id: String,
#[cultcache(key = 3)]
pub status: String,
#[cultcache(key = 4)]
pub deleted_directories: Vec<String>,
#[cultcache(key = 5)]
pub deleted_file_count: u64,
#[cultcache(key = 6)]
pub deleted_byte_count: u64,
#[cultcache(key = 7)]
pub completed_at_utc: String,
#[cultcache(key = 8)]
pub private_state_exposed: bool,
}
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct HeartbeatSelectionPolicy {
pub mode: String,
pub reaction_precedence: bool,
pub minimum_speed: f64,
pub tie_breakers: Vec<String>,
}
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct HeartbeatPacingPolicy {
pub cooldown_starts_after_turn_completion: bool,
pub work_base_recovery: f64,
pub idle_base_recovery: f64,
pub sleep_heartbeat_rate_multiplier: f64,
pub minimum_effective_rate: f64,
}
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct HeartbeatParticipant {
pub agent_id: String,
pub role_id: String,
pub display_name: String,
#[serde(default)]
pub arena: String,
#[serde(default)]
pub participant_kind: String,
pub initiative_speed: f64,
pub next_ready_at: f64,
pub reaction_bias: f64,
pub interrupt_threshold: f64,
pub current_load: f64,
pub status: String,
pub constraints: Vec<String>,
pub last_action_id: Option<String>,
pub last_woke_at: Option<String>,
pub last_finished_at: Option<String>,
pub pending_turn: Option<HeartbeatPendingTurn>,
}
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct HeartbeatPendingTurn {
pub status: String,
#[serde(rename = "scheduleId")]
pub schedule_id: String,
#[serde(rename = "actionId")]
pub action_id: String,
#[serde(rename = "actionType")]
pub action_type: String,
#[serde(rename = "actionScale", default)]
pub action_scale: String,
#[serde(rename = "localAffordanceBasis", default)]
pub local_affordance_basis: Vec<String>,
#[serde(rename = "startedAt")]
pub started_at: String,
#[serde(rename = "startedSceneClock")]
pub started_scene_clock: f64,
#[serde(rename = "baseRecovery")]
pub base_recovery: f64,
pub recovery: f64,
#[serde(rename = "cooldownPolicy")]
pub cooldown_policy: String,
#[serde(rename = "completedAt", default)]
pub completed_at: Option<String>,
#[serde(rename = "completedSceneClock", default)]
pub completed_scene_clock: Option<f64>,
#[serde(rename = "nextReadyAt", default)]
pub next_ready_at: Option<f64>,
#[serde(rename = "initiativeFrozen", default)]
pub initiative_frozen: bool,
#[serde(rename = "initiativeFreezeReason", default)]
pub initiative_freeze_reason: Option<String>,
}
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct HeartbeatHistoryEvent {
pub ts: String,
#[serde(rename = "scheduleId")]
pub schedule_id: String,
#[serde(rename = "selectedRole")]
pub selected_role: String,
#[serde(rename = "selectedAgentId")]
pub selected_agent_id: String,
#[serde(rename = "actionId")]
pub action_id: String,
#[serde(rename = "actionType")]
pub action_type: String,
#[serde(default)]
pub arena: String,
#[serde(rename = "participantKind", default)]
pub participant_kind: String,
#[serde(rename = "actionScale", default)]
pub action_scale: String,
#[serde(rename = "coordinatorAction", default)]
pub coordinator_action: Option<String>,
#[serde(rename = "targetRole", default)]
pub target_role: Option<String>,
#[serde(rename = "workRole", default)]
pub work_role: Option<String>,
#[serde(rename = "sceneClock", default)]
pub scene_clock: Option<f64>,
#[serde(rename = "nextReadyAt", default)]
pub next_ready_at: Option<f64>,
#[serde(rename = "turnStatus", default)]
pub turn_status: Option<String>,
#[serde(rename = "cooldownStartedAfterCompletion", default)]
pub cooldown_started_after_completion: Option<bool>,
}
#[derive(Clone, Debug, PartialEq)]
pub(super) struct HeartbeatTickOptions {
pub(super) target_heartbeat_rate: f64,
pub(super) schedule_id: String,
pub(super) source_scene_ref: String,
pub(super) resident_self_store: Option<std::path::PathBuf>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct HeartbeatStaleTurnRepairOptions {
pub max_age_seconds: i64,
pub now_utc: Option<String>,
pub reason: String,
}

View file

@ -1,275 +0,0 @@
use super::HEARTBEAT_STATE_KEY;
use super::HEARTBEAT_STATE_TYPE;
use super::HEARTBEAT_STATUS_SCHEMA_VERSION;
use super::HeartbeatHistoryEvent;
use super::HeartbeatParticipant;
use super::HeartbeatPendingTurn;
use super::HeartbeatSelectionPolicy;
use super::load_heartbeat_state_entry;
use super::participant_arena;
use super::participant_kind;
use anyhow::Result;
use serde_json::Value;
use std::cmp::Reverse;
use std::fs;
use std::path::Path;
pub fn heartbeat_status_projection(
store_path: impl AsRef<Path>,
artifact_dir: impl AsRef<Path>,
target_heartbeat_rate: f64,
artifact_limit: usize,
) -> Result<Value> {
let store_path = store_path.as_ref();
let Some(state) = load_heartbeat_state_entry(store_path)? else {
return Ok(serde_json::json!({
"schema_version": HEARTBEAT_STATUS_SCHEMA_VERSION,
"ok": true,
"status": "missing",
"schedulerStatus": "missing",
"stateFile": null,
"storeFile": store_path,
"cultCacheStore": cultcache_status(store_path),
"artifactDir": artifact_dir.as_ref(),
"targetHeartbeatRate": if target_heartbeat_rate > 0.0 { Some(target_heartbeat_rate) } else { None },
"sceneClock": null,
"participants": [],
"latestEvent": null,
"history": [],
"latestArtifacts": latest_json_artifacts(artifact_dir, artifact_limit),
"availableActions": ["init", "repair-stale", "retain-artifacts", "status", "serve"],
}));
};
let scheduler_status = if state.participants.is_empty() {
"unconfigured"
} else if state.participants.iter().all(|participant| {
participant.status == "active"
&& participant
.pending_turn
.as_ref()
.is_none_or(|turn| turn.status == "running")
}) {
"active"
} else {
"attention"
};
let history: Vec<_> = state
.history
.iter()
.rev()
.take(artifact_limit)
.cloned()
.collect::<Vec<_>>()
.into_iter()
.rev()
.map(history_event_json)
.collect();
Ok(serde_json::json!({
"schema_version": HEARTBEAT_STATUS_SCHEMA_VERSION,
"ok": true,
"status": "loaded",
"schedulerStatus": scheduler_status,
"stateFile": null,
"storeFile": store_path,
"cultCacheStore": cultcache_status(store_path),
"artifactDir": artifact_dir.as_ref(),
"targetHeartbeatRate": state.target_heartbeat_rate,
"sceneClock": state.scene_clock,
"participants": state.participants.iter().map(participant_status_json).collect::<Vec<_>>(),
"latestEvent": history.last().cloned(),
"history": history,
"latestArtifacts": latest_json_artifacts(artifact_dir, artifact_limit),
"availableActions": ["init", "repair-stale", "retain-artifacts", "status", "serve"],
}))
}
fn latest_json_artifacts(artifact_dir: impl AsRef<Path>, limit: usize) -> Vec<Value> {
let artifact_dir = artifact_dir.as_ref();
let Ok(read_dir) = fs::read_dir(artifact_dir) else {
return Vec::new();
};
let mut paths: Vec<_> = read_dir
.filter_map(Result::ok)
.map(|entry| entry.path())
.filter(|path| {
path.extension()
.is_some_and(|extension| extension == "json")
})
.filter_map(|path| {
let modified = path.metadata().and_then(|meta| meta.modified()).ok()?;
Some((path, modified))
})
.collect();
paths.sort_by_key(|item| Reverse(item.1));
paths
.into_iter()
.take(limit)
.filter_map(|(path, modified)| {
let raw = fs::read_to_string(&path).ok()?;
let payload: Value = serde_json::from_str(&raw).ok()?;
let modified_at: chrono::DateTime<chrono::Utc> = modified.into();
Some(serde_json::json!({
"path": path,
"name": path.file_name().and_then(|name| name.to_str()),
"modifiedAt": modified_at.to_rfc3339_opts(chrono::SecondsFormat::Secs, true),
"schemaVersion": payload.get("schema_version"),
"kind": path.file_stem().and_then(|stem| stem.to_str()).and_then(|stem| stem.rsplit('.').next()).unwrap_or("json"),
"summary": artifact_summary(&payload),
}))
})
.collect()
}
fn artifact_summary(payload: &Value) -> Value {
let event = if payload.get("actionId").is_some() {
Some(payload)
} else {
payload.get("event")
};
if let Some(event) = event {
return serde_json::json!({
"selectedRole": event.get("selectedRole"),
"actionType": event.get("actionType"),
"actionId": event.get("actionId"),
"coordinatorAction": event.get("coordinatorAction"),
});
}
if let Some(selection) = payload
.get("next_actor_selection")
.or_else(|| payload.get("nextActorSelection"))
{
return serde_json::json!({
"selectionKind": selection.get("selection_kind").or_else(|| selection.get("selectionKind")),
"selectedActorId": selection.get("selected_actor_id").or_else(|| selection.get("selectedActorId")),
});
}
let keys = payload
.as_object()
.map(|object| object.keys().take(8).cloned().collect::<Vec<_>>())
.unwrap_or_default();
serde_json::json!({ "keys": keys })
}
fn cultcache_status(store_path: &Path) -> Value {
let metadata = store_path.metadata().ok();
let modified_at = metadata
.as_ref()
.and_then(|meta| meta.modified().ok())
.map(|time| {
let time: chrono::DateTime<chrono::Utc> = time.into();
time.to_rfc3339_opts(chrono::SecondsFormat::Secs, true)
});
serde_json::json!({
"storeFile": store_path,
"present": metadata.is_some(),
"sizeBytes": metadata.as_ref().map(|meta| meta.len()),
"modifiedAt": modified_at,
"entryType": HEARTBEAT_STATE_TYPE,
"entryKey": HEARTBEAT_STATE_KEY,
})
}
fn participant_status_json(participant: &HeartbeatParticipant) -> Value {
serde_json::json!({
"agentId": participant.agent_id,
"roleId": participant.role_id,
"displayName": participant.display_name,
"arena": participant_arena(participant),
"participantKind": participant_kind(participant),
"initiativeSpeed": participant.initiative_speed,
"initiativeFrozen": participant
.pending_turn
.as_ref()
.is_some_and(|turn| turn.status == "running"),
"initiativeFreezeReason": participant
.pending_turn
.as_ref()
.and_then(|turn| turn.initiative_freeze_reason.as_ref()),
"nextReadyAt": participant.next_ready_at,
"reactionBias": participant.reaction_bias,
"interruptThreshold": participant.interrupt_threshold,
"currentLoad": participant.current_load,
"status": participant.status,
"lastActionId": participant.last_action_id,
"lastWokeAt": participant.last_woke_at,
"lastFinishedAt": participant.last_finished_at,
"pendingTurn": participant.pending_turn.as_ref().map(pending_turn_json),
})
}
pub(super) fn schedule_participant_json(participant: &HeartbeatParticipant) -> Value {
serde_json::json!({
"agent_id": participant.agent_id,
"role_id": participant.role_id,
"display_name": participant.display_name,
"arena": participant_arena(participant),
"participant_kind": participant_kind(participant),
"initiative_speed": participant.initiative_speed,
"initiative_frozen": participant
.pending_turn
.as_ref()
.is_some_and(|turn| turn.status == "running"),
"initiative_freeze_reason": participant
.pending_turn
.as_ref()
.and_then(|turn| turn.initiative_freeze_reason.as_ref()),
"next_ready_at": participant.next_ready_at,
"reaction_bias": participant.reaction_bias,
"interrupt_threshold": participant.interrupt_threshold,
"current_load": participant.current_load,
"status": participant.status,
"pending_turn": participant.pending_turn.as_ref().map(pending_turn_json),
"constraints": participant.constraints,
})
}
pub(super) fn selection_policy_json(policy: &HeartbeatSelectionPolicy) -> Value {
serde_json::json!({
"mode": policy.mode,
"reaction_precedence": policy.reaction_precedence,
"minimum_speed": policy.minimum_speed,
"tie_breakers": policy.tie_breakers,
})
}
pub(super) fn pending_turn_json(turn: &HeartbeatPendingTurn) -> Value {
serde_json::json!({
"status": turn.status,
"scheduleId": turn.schedule_id,
"actionId": turn.action_id,
"actionType": turn.action_type,
"actionScale": turn.action_scale,
"localAffordanceBasis": turn.local_affordance_basis,
"startedAt": turn.started_at,
"startedSceneClock": turn.started_scene_clock,
"baseRecovery": turn.base_recovery,
"initiativeFrozen": turn.initiative_frozen,
"initiativeFreezeReason": turn.initiative_freeze_reason.as_ref(),
"recovery": turn.recovery,
"cooldownPolicy": turn.cooldown_policy,
"completedAt": turn.completed_at,
"completedSceneClock": turn.completed_scene_clock,
"nextReadyAt": turn.next_ready_at,
})
}
pub(super) fn history_event_json(event: HeartbeatHistoryEvent) -> Value {
serde_json::json!({
"ts": event.ts,
"scheduleId": event.schedule_id,
"selectedRole": event.selected_role,
"selectedAgentId": event.selected_agent_id,
"actionId": event.action_id,
"actionType": event.action_type,
"arena": event.arena,
"participantKind": event.participant_kind,
"actionScale": event.action_scale,
"coordinatorAction": event.coordinator_action,
"targetRole": event.target_role,
"workRole": event.work_role,
"sceneClock": event.scene_clock,
"nextReadyAt": event.next_ready_at,
"turnStatus": event.turn_status,
"cooldownStartedAfterCompletion": event.cooldown_started_after_completion,
})
}

View file

@ -1,472 +0,0 @@
use super::{
EpiphanyHeartbeatArtifactRetentionPlan, EpiphanyHeartbeatArtifactRetentionReceipt,
HEARTBEAT_ARTIFACT_RETENTION_PLAN_LATEST_KEY, HEARTBEAT_ARTIFACT_RETENTION_PLAN_SCHEMA_VERSION,
HEARTBEAT_ARTIFACT_RETENTION_RECEIPT_LATEST_KEY,
HEARTBEAT_ARTIFACT_RETENTION_RECEIPT_SCHEMA_VERSION, HeartbeatArtifactRetentionMember,
heartbeat_state_cache,
};
use anyhow::{Context, Result, anyhow, bail};
use cultcache_rs::{DatabaseEntry, SingleFileMessagePackBackingStore};
use sha2::{Digest, Sha256};
use std::fs;
use std::path::{Path, PathBuf};
pub fn retain_heartbeat_pulse_artifacts(
store_path: impl AsRef<Path>,
artifact_root: impl AsRef<Path>,
retain_pulse_count: usize,
batch_size: usize,
completed_at_utc: &str,
) -> Result<Option<EpiphanyHeartbeatArtifactRetentionReceipt>> {
if retain_pulse_count == 0 || batch_size == 0 {
bail!("heartbeat artifact retention requires non-zero retain and batch counts");
}
if completed_at_utc.trim().is_empty() {
bail!("heartbeat artifact retention requires completion time");
}
let artifact_root = artifact_root
.as_ref()
.canonicalize()
.with_context(|| format!("failed to resolve {}", artifact_root.as_ref().display()))?;
if !artifact_root.is_dir() {
bail!("heartbeat artifact root is not a directory");
}
if let Some(pending) = pending_retention_plan(store_path.as_ref())? {
if PathBuf::from(&pending.artifact_root) != artifact_root {
bail!("pending heartbeat retention plan belongs to another artifact root");
}
return reconcile_retention_plan(
store_path.as_ref(),
&artifact_root,
&pending,
completed_at_utc,
)
.map(Some);
}
let pulses = pulse_directories(&artifact_root)?;
let threshold = retain_pulse_count
.checked_add(batch_size)
.ok_or_else(|| anyhow!("heartbeat artifact retention threshold overflow"))?;
if pulses.len() <= threshold {
return Ok(None);
}
let retire_count = batch_size.min(pulses.len() - retain_pulse_count);
let members = pulses
.iter()
.take(retire_count)
.map(|(_, path)| artifact_member(&artifact_root, path))
.collect::<Result<Vec<_>>>()?;
let plan_id = retention_plan_id(&artifact_root, retain_pulse_count, batch_size, &members);
let plan = EpiphanyHeartbeatArtifactRetentionPlan {
schema_version: HEARTBEAT_ARTIFACT_RETENTION_PLAN_SCHEMA_VERSION.to_string(),
plan_id,
artifact_root: artifact_root.display().to_string(),
retain_pulse_count: retain_pulse_count as u64,
batch_size: batch_size as u64,
members,
planned_at_utc: completed_at_utc.to_string(),
private_state_exposed: false,
};
validate_retention_plan(&plan)?;
write_retention_plan(store_path.as_ref(), &plan)?;
reconcile_retention_plan(store_path.as_ref(), &artifact_root, &plan, completed_at_utc).map(Some)
}
fn pending_retention_plan(
store_path: &Path,
) -> Result<Option<EpiphanyHeartbeatArtifactRetentionPlan>> {
let cache = heartbeat_state_cache(store_path)?;
let plan = cache.get::<EpiphanyHeartbeatArtifactRetentionPlan>(
HEARTBEAT_ARTIFACT_RETENTION_PLAN_LATEST_KEY,
)?;
let receipt = cache.get::<EpiphanyHeartbeatArtifactRetentionReceipt>(
HEARTBEAT_ARTIFACT_RETENTION_RECEIPT_LATEST_KEY,
)?;
Ok(plan.filter(|plan| {
receipt
.as_ref()
.is_none_or(|receipt| receipt.plan_id != plan.plan_id)
}))
}
fn reconcile_retention_plan(
store_path: &Path,
artifact_root: &Path,
plan: &EpiphanyHeartbeatArtifactRetentionPlan,
completed_at_utc: &str,
) -> Result<EpiphanyHeartbeatArtifactRetentionReceipt> {
validate_retention_plan(plan)?;
for member in &plan.members {
let path = artifact_root.join(&member.directory_name);
if !path.exists() {
continue;
}
let observed = artifact_member(artifact_root, &path)?;
if observed != *member {
bail!(
"heartbeat artifact {:?} changed after retention planning",
member.directory_name
);
}
fs::remove_dir_all(&path)
.with_context(|| format!("failed to retire heartbeat artifact {}", path.display()))?;
}
for member in &plan.members {
if artifact_root.join(&member.directory_name).exists() {
bail!("heartbeat artifact retirement did not remove planned directory");
}
}
let receipt = EpiphanyHeartbeatArtifactRetentionReceipt {
schema_version: HEARTBEAT_ARTIFACT_RETENTION_RECEIPT_SCHEMA_VERSION.to_string(),
receipt_id: format!("artifact-retention-receipt-{}", plan.plan_id),
plan_id: plan.plan_id.clone(),
status: "completed".to_string(),
deleted_directories: plan
.members
.iter()
.map(|member| member.directory_name.clone())
.collect(),
deleted_file_count: plan.members.iter().map(|member| member.file_count).sum(),
deleted_byte_count: plan.members.iter().map(|member| member.byte_count).sum(),
completed_at_utc: completed_at_utc.to_string(),
private_state_exposed: false,
};
validate_retention_receipt(&receipt, plan)?;
write_retention_receipt(store_path, &receipt)?;
Ok(receipt)
}
fn pulse_directories(root: &Path) -> Result<Vec<(u64, PathBuf)>> {
let mut pulses = Vec::new();
for entry in fs::read_dir(root)? {
let entry = entry?;
let file_type = entry.file_type()?;
if !file_type.is_dir() {
continue;
}
if file_type.is_symlink() {
bail!("heartbeat artifact retention refuses symlink directories");
}
let name = entry
.file_name()
.into_string()
.map_err(|_| anyhow!("heartbeat artifact directory name is not UTF-8"))?;
let sequence = pulse_sequence(&name)
.ok_or_else(|| anyhow!("heartbeat artifact retention refuses directory {name:?}"))?;
pulses.push((sequence, entry.path()));
}
pulses.sort_by_key(|(sequence, _)| *sequence);
if pulses.windows(2).any(|pair| pair[0].0 == pair[1].0) {
bail!("heartbeat artifact root contains duplicate pulse sequences");
}
Ok(pulses)
}
fn pulse_sequence(name: &str) -> Option<u64> {
let digits = name.strip_prefix("pulse-")?;
(digits.len() == 6 && digits.bytes().all(|byte| byte.is_ascii_digit()))
.then(|| digits.parse().ok())
.flatten()
}
fn artifact_member(root: &Path, directory: &Path) -> Result<HeartbeatArtifactRetentionMember> {
let name = directory
.file_name()
.and_then(|value| value.to_str())
.ok_or_else(|| anyhow!("heartbeat artifact directory has no UTF-8 name"))?;
pulse_sequence(name).ok_or_else(|| anyhow!("invalid heartbeat pulse directory {name:?}"))?;
let canonical = directory.canonicalize()?;
if canonical.parent() != Some(root) {
bail!("heartbeat artifact directory escapes its canonical root");
}
let mut rows = Vec::new();
collect_manifest_rows(&canonical, &canonical, &mut rows)?;
rows.sort();
let mut digest = Sha256::new();
let mut file_count = 0_u64;
let mut byte_count = 0_u64;
for row in rows {
if row.kind == b'F' {
file_count += 1;
byte_count = byte_count
.checked_add(row.byte_count)
.ok_or_else(|| anyhow!("heartbeat artifact byte count overflow"))?;
}
digest.update([row.kind]);
digest.update((row.relative_path.len() as u64).to_be_bytes());
digest.update(row.relative_path.as_bytes());
digest.update(row.byte_count.to_be_bytes());
digest.update(row.sha256.as_bytes());
}
Ok(HeartbeatArtifactRetentionMember {
directory_name: name.to_string(),
manifest_sha256: format!("sha256-{:x}", digest.finalize()),
file_count,
byte_count,
})
}
#[derive(Eq, Ord, PartialEq, PartialOrd)]
struct ManifestRow {
kind: u8,
relative_path: String,
byte_count: u64,
sha256: String,
}
fn collect_manifest_rows(root: &Path, directory: &Path, rows: &mut Vec<ManifestRow>) -> Result<()> {
for entry in fs::read_dir(directory)? {
let entry = entry?;
let file_type = entry.file_type()?;
if file_type.is_symlink() {
bail!("heartbeat artifact retention refuses symlinks");
}
let path = entry.path();
let relative = path
.strip_prefix(root)?
.to_string_lossy()
.replace('\\', "/");
if file_type.is_dir() {
rows.push(ManifestRow {
kind: b'D',
relative_path: relative,
byte_count: 0,
sha256: String::new(),
});
collect_manifest_rows(root, &path, rows)?;
} else if file_type.is_file() {
let bytes = fs::read(&path)?;
rows.push(ManifestRow {
kind: b'F',
relative_path: relative,
byte_count: bytes.len() as u64,
sha256: format!("sha256-{:x}", Sha256::digest(bytes)),
});
} else {
bail!("heartbeat artifact retention refuses non-file artifacts");
}
}
Ok(())
}
fn retention_plan_id(
root: &Path,
retain_pulse_count: usize,
batch_size: usize,
members: &[HeartbeatArtifactRetentionMember],
) -> String {
let mut digest = Sha256::new();
digest.update(b"epiphany.heartbeat.artifact-retention-plan.v0\0");
digest.update(root.to_string_lossy().as_bytes());
digest.update((retain_pulse_count as u64).to_be_bytes());
digest.update((batch_size as u64).to_be_bytes());
for member in members {
digest.update(member.directory_name.as_bytes());
digest.update(member.manifest_sha256.as_bytes());
digest.update(member.file_count.to_be_bytes());
digest.update(member.byte_count.to_be_bytes());
}
format!("sha256-{:x}", digest.finalize())
}
fn validate_retention_plan(plan: &EpiphanyHeartbeatArtifactRetentionPlan) -> Result<()> {
if plan.schema_version != HEARTBEAT_ARTIFACT_RETENTION_PLAN_SCHEMA_VERSION
|| plan.private_state_exposed
|| plan.plan_id.trim().is_empty()
|| plan.artifact_root.trim().is_empty()
|| plan.retain_pulse_count == 0
|| plan.batch_size == 0
|| plan.members.is_empty()
|| plan.planned_at_utc.trim().is_empty()
{
bail!("invalid heartbeat artifact retention plan");
}
if plan.members.len() as u64 > plan.batch_size {
bail!("heartbeat artifact retention plan exceeds its batch size");
}
let mut names = std::collections::BTreeSet::new();
for member in &plan.members {
if pulse_sequence(&member.directory_name).is_none()
|| !member.manifest_sha256.starts_with("sha256-")
|| !names.insert(member.directory_name.as_str())
{
bail!("invalid heartbeat artifact retention member");
}
}
Ok(())
}
fn validate_retention_receipt(
receipt: &EpiphanyHeartbeatArtifactRetentionReceipt,
plan: &EpiphanyHeartbeatArtifactRetentionPlan,
) -> Result<()> {
if receipt.schema_version != HEARTBEAT_ARTIFACT_RETENTION_RECEIPT_SCHEMA_VERSION
|| receipt.private_state_exposed
|| receipt.status != "completed"
|| receipt.plan_id != plan.plan_id
|| receipt.deleted_directories
!= plan
.members
.iter()
.map(|member| member.directory_name.clone())
.collect::<Vec<_>>()
|| receipt.completed_at_utc.trim().is_empty()
{
bail!("invalid heartbeat artifact retention receipt");
}
Ok(())
}
fn write_retention_plan(
store_path: &Path,
plan: &EpiphanyHeartbeatArtifactRetentionPlan,
) -> Result<()> {
let cache = heartbeat_state_cache(store_path)?;
let (plan_entry, _) = cache.prepare_entry(&plan.plan_id, plan)?;
let (latest_entry, _) =
cache.prepare_entry(HEARTBEAT_ARTIFACT_RETENTION_PLAN_LATEST_KEY, plan)?;
let expected = cache
.snapshot_envelopes()
.into_iter()
.filter(|entry| {
entry.r#type == <EpiphanyHeartbeatArtifactRetentionPlan as DatabaseEntry>::TYPE
&& (entry.key == plan.plan_id
|| entry.key == HEARTBEAT_ARTIFACT_RETENTION_PLAN_LATEST_KEY)
})
.collect::<Vec<_>>();
if !SingleFileMessagePackBackingStore::new(store_path)
.compare_and_swap_batch(&expected, vec![plan_entry, latest_entry])?
{
bail!("heartbeat artifact retention plan lost exact compare-and-swap");
}
Ok(())
}
fn write_retention_receipt(
store_path: &Path,
receipt: &EpiphanyHeartbeatArtifactRetentionReceipt,
) -> Result<()> {
let cache = heartbeat_state_cache(store_path)?;
if let Some(existing) =
cache.get::<EpiphanyHeartbeatArtifactRetentionReceipt>(&receipt.receipt_id)?
{
if existing == *receipt {
return Ok(());
}
bail!("heartbeat artifact retention receipt identity collision");
}
let (receipt_entry, _) = cache.prepare_entry(&receipt.receipt_id, receipt)?;
let (latest_entry, _) =
cache.prepare_entry(HEARTBEAT_ARTIFACT_RETENTION_RECEIPT_LATEST_KEY, receipt)?;
let expected = cache
.snapshot_envelopes()
.into_iter()
.filter(|entry| {
entry.r#type == <EpiphanyHeartbeatArtifactRetentionReceipt as DatabaseEntry>::TYPE
&& (entry.key == receipt.receipt_id
|| entry.key == HEARTBEAT_ARTIFACT_RETENTION_RECEIPT_LATEST_KEY)
})
.collect::<Vec<_>>();
if !SingleFileMessagePackBackingStore::new(store_path)
.compare_and_swap_batch(&expected, vec![receipt_entry, latest_entry])?
{
bail!("heartbeat artifact retention receipt lost exact compare-and-swap");
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::initialize_heartbeat_store;
fn pulse(root: &Path, sequence: u64, content: &str) -> Result<PathBuf> {
let path = root.join(format!("pulse-{sequence:06}"));
fs::create_dir_all(path.join("nested"))?;
fs::write(path.join("nested").join("artifact.json"), content)?;
Ok(path)
}
#[test]
fn retention_is_receipted_bounded_and_idempotent() -> Result<()> {
let temp = tempfile::tempdir()?;
let store = temp.path().join("heartbeat.cc");
let artifacts = temp.path().join("artifacts");
fs::create_dir(&artifacts)?;
initialize_heartbeat_store(&store, 1.0)?;
for sequence in 1..=5 {
pulse(&artifacts, sequence, &format!("artifact-{sequence}"))?;
}
let receipt =
retain_heartbeat_pulse_artifacts(&store, &artifacts, 2, 2, "2026-08-08T21:00:00Z")?
.expect("retention should run above hysteresis threshold");
assert_eq!(
receipt.deleted_directories,
vec!["pulse-000001", "pulse-000002"]
);
assert_eq!(receipt.deleted_file_count, 2);
assert_eq!(pulse_directories(&artifacts)?.len(), 3);
assert!(
retain_heartbeat_pulse_artifacts(&store, &artifacts, 2, 2, "2026-08-08T21:00:01Z",)?
.is_none()
);
Ok(())
}
#[test]
fn pending_plan_refuses_changed_artifact_and_preserves_it() -> Result<()> {
let temp = tempfile::tempdir()?;
let store = temp.path().join("heartbeat.cc");
let artifacts = temp.path().join("artifacts");
fs::create_dir(&artifacts)?;
initialize_heartbeat_store(&store, 1.0)?;
let first = pulse(&artifacts, 1, "before")?;
for sequence in 2..=5 {
pulse(&artifacts, sequence, "stable")?;
}
let members = vec![artifact_member(&artifacts.canonicalize()?, &first)?];
let root = artifacts.canonicalize()?;
let plan = EpiphanyHeartbeatArtifactRetentionPlan {
schema_version: HEARTBEAT_ARTIFACT_RETENTION_PLAN_SCHEMA_VERSION.to_string(),
plan_id: retention_plan_id(&root, 2, 2, &members),
artifact_root: root.display().to_string(),
retain_pulse_count: 2,
batch_size: 2,
members,
planned_at_utc: "2026-08-08T21:01:00Z".to_string(),
private_state_exposed: false,
};
write_retention_plan(&store, &plan)?;
fs::write(first.join("nested").join("artifact.json"), "after")?;
assert!(
retain_heartbeat_pulse_artifacts(&store, &artifacts, 2, 2, "2026-08-08T21:01:01Z",)
.is_err()
);
assert!(first.exists());
Ok(())
}
#[test]
fn unknown_directory_fails_closed_before_deletion() -> Result<()> {
let temp = tempfile::tempdir()?;
let store = temp.path().join("heartbeat.cc");
let artifacts = temp.path().join("artifacts");
fs::create_dir(&artifacts)?;
initialize_heartbeat_store(&store, 1.0)?;
for sequence in 1..=5 {
pulse(&artifacts, sequence, "stable")?;
}
fs::create_dir(artifacts.join("operator-evidence"))?;
assert!(
retain_heartbeat_pulse_artifacts(&store, &artifacts, 2, 2, "2026-08-08T21:02:00Z",)
.is_err()
);
assert!(artifacts.join("pulse-000001").exists());
Ok(())
}
}

View file

@ -1,119 +0,0 @@
use super::EpiphanyHeartbeatStateEntry;
use super::HEARTBEAT_ARENA_MAINTENANCE;
use super::HEARTBEAT_STATE_SCHEMA_VERSION;
use super::HeartbeatPacingPolicy;
use super::HeartbeatParticipant;
use super::HeartbeatSelectionPolicy;
use super::PARTICIPANT_KIND_AGENT;
use super::write_heartbeat_state_entry;
use anyhow::Result;
use std::path::Path;
pub(super) const ROLE_ORDER: &[&str] = &["coordinator"];
pub fn default_heartbeat_state(target_heartbeat_rate: f64) -> EpiphanyHeartbeatStateEntry {
EpiphanyHeartbeatStateEntry {
schema_version: HEARTBEAT_STATE_SCHEMA_VERSION.to_string(),
target_heartbeat_rate,
scene_clock: 0.0,
selection_policy: HeartbeatSelectionPolicy {
mode: "readiness_queue".to_string(),
reaction_precedence: true,
minimum_speed: 0.2,
tie_breakers: vec![
"reaction_readiness_desc".to_string(),
"next_ready_at_asc".to_string(),
"initiative_speed_desc".to_string(),
"stable_actor_id_asc".to_string(),
],
},
pacing_policy: HeartbeatPacingPolicy {
cooldown_starts_after_turn_completion: true,
work_base_recovery: 6.0,
idle_base_recovery: 2.0,
sleep_heartbeat_rate_multiplier: 0.05,
minimum_effective_rate: 0.001,
},
participants: ROLE_ORDER
.iter()
.map(|role_id| default_participant(role_id))
.collect(),
history: Vec::new(),
}
}
pub fn initialize_heartbeat_store(
store_path: impl AsRef<Path>,
target_heartbeat_rate: f64,
) -> Result<EpiphanyHeartbeatStateEntry> {
write_heartbeat_state_entry(store_path, &default_heartbeat_state(target_heartbeat_rate))
}
pub(super) fn default_participant(role_id: &str) -> HeartbeatParticipant {
HeartbeatParticipant {
agent_id: agent_id_for_role(role_id).to_string(),
role_id: role_id.to_string(),
display_name: display_name_for_role(role_id).to_string(),
arena: HEARTBEAT_ARENA_MAINTENANCE.to_string(),
participant_kind: PARTICIPANT_KIND_AGENT.to_string(),
initiative_speed: initiative_speed_for_role(role_id),
next_ready_at: 0.0,
reaction_bias: reaction_bias_for_role(role_id),
interrupt_threshold: interrupt_threshold_for_role(role_id),
current_load: 0.0,
status: "active".to_string(),
constraints: participant_constraints(role_id)
.into_iter()
.map(str::to_string)
.collect(),
last_action_id: None,
last_woke_at: None,
last_finished_at: None,
pending_turn: None,
}
}
pub(super) fn agent_id_for_role(role_id: &str) -> &'static str {
match role_id {
"coordinator" => "epiphany.self",
_ => "epiphany.unknown",
}
}
pub(super) fn display_name_for_role(role_id: &str) -> &'static str {
match role_id {
"coordinator" => "Self",
_ => "Unknown",
}
}
fn initiative_speed_for_role(role_id: &str) -> f64 {
match role_id {
"coordinator" => 1.28,
_ => 1.0,
}
}
fn reaction_bias_for_role(role_id: &str) -> f64 {
match role_id {
"coordinator" => 0.88,
_ => 0.5,
}
}
fn interrupt_threshold_for_role(role_id: &str) -> f64 {
match role_id {
"coordinator" => 0.42,
_ => 0.5,
}
}
fn participant_constraints(role_id: &str) -> Vec<&'static str> {
let role_specific = match role_id {
"coordinator" => {
"Routes and reviews; must not implement, verify, or accept its own comfort."
}
_ => "Unknown role.",
};
vec![role_specific]
}

View file

@ -1,330 +0,0 @@
use super::EpiphanyHeartbeatArtifactRetentionPlan;
use super::EpiphanyHeartbeatArtifactRetentionReceipt;
use super::EpiphanyHeartbeatStaleTurnRepairReceipt;
use super::EpiphanyHeartbeatStateEntry;
use super::HEARTBEAT_ARENA_MAINTENANCE;
use super::HEARTBEAT_STALE_TURN_REPAIR_LATEST_KEY;
use super::HEARTBEAT_STALE_TURN_REPAIR_SCHEMA_VERSION;
use super::HEARTBEAT_STATE_KEY;
use super::HEARTBEAT_STATE_SCHEMA_VERSION;
use super::PARTICIPANT_KIND_AGENT;
use super::participant_arena;
use super::participant_kind;
use anyhow::Result;
use anyhow::anyhow;
use cultcache_rs::SingleFileMessagePackBackingStore;
use cultcache_rs::{CacheBackingStore, CultCache, CultCacheEnvelope, DatabaseEntry};
use std::collections::HashSet;
use std::path::Path;
pub fn heartbeat_state_cache(store_path: impl AsRef<Path>) -> Result<CultCache> {
let mut cache = CultCache::new();
cache.register_entry_type::<EpiphanyHeartbeatStateEntry>()?;
cache.register_entry_type::<EpiphanyHeartbeatStaleTurnRepairReceipt>()?;
cache.register_entry_type::<EpiphanyHeartbeatArtifactRetentionPlan>()?;
cache.register_entry_type::<EpiphanyHeartbeatArtifactRetentionReceipt>()?;
import_owned_heartbeat_envelopes(&mut cache, store_path.as_ref())?;
Ok(cache)
}
fn import_owned_heartbeat_envelopes(cache: &mut CultCache, store_path: &Path) -> Result<()> {
let state_type = <EpiphanyHeartbeatStateEntry as DatabaseEntry>::TYPE;
let mut identities = HashSet::new();
for envelope in SingleFileMessagePackBackingStore::new(store_path).pull_all()? {
let owned = (envelope.r#type == state_type && envelope.key == HEARTBEAT_STATE_KEY)
|| envelope.r#type == <EpiphanyHeartbeatStaleTurnRepairReceipt as DatabaseEntry>::TYPE;
let owned = owned
|| envelope.r#type == <EpiphanyHeartbeatArtifactRetentionPlan as DatabaseEntry>::TYPE
|| envelope.r#type
== <EpiphanyHeartbeatArtifactRetentionReceipt as DatabaseEntry>::TYPE;
if !owned {
continue;
}
if !identities.insert((envelope.r#type.clone(), envelope.key.clone())) {
return Err(anyhow!(
"heartbeat store contains duplicate owner entry type {:?} key {:?}",
envelope.r#type,
envelope.key
));
}
if envelope.r#type == state_type {
cache.load_envelope::<EpiphanyHeartbeatStateEntry>(envelope)?;
} else if envelope.r#type == <EpiphanyHeartbeatArtifactRetentionPlan as DatabaseEntry>::TYPE
{
cache.load_envelope::<EpiphanyHeartbeatArtifactRetentionPlan>(envelope)?;
} else if envelope.r#type
== <EpiphanyHeartbeatArtifactRetentionReceipt as DatabaseEntry>::TYPE
{
cache.load_envelope::<EpiphanyHeartbeatArtifactRetentionReceipt>(envelope)?;
} else {
cache.load_envelope::<EpiphanyHeartbeatStaleTurnRepairReceipt>(envelope)?;
}
}
Ok(())
}
fn commit_owned_entry<T: DatabaseEntry>(
store_path: &Path,
cache: &CultCache,
key: &str,
value: &T,
) -> Result<T> {
let expected = cache
.snapshot_envelopes()
.into_iter()
.find(|entry| entry.r#type == T::TYPE && entry.key == key);
let (replacement, written) = cache.prepare_entry(key, value)?;
let backing = SingleFileMessagePackBackingStore::new(store_path);
let committed = match expected {
Some(expected) => backing.compare_and_swap_entry(&expected, replacement)?,
None => backing.insert_entry_if_absent(replacement)?,
};
if !committed {
return Err(anyhow!(
"heartbeat owner entry lost exact atomic compare-and-swap"
));
}
Ok(written)
}
pub fn load_heartbeat_state_entry(
store_path: impl AsRef<Path>,
) -> Result<Option<EpiphanyHeartbeatStateEntry>> {
let cache = heartbeat_state_cache(store_path)?;
cache.get::<EpiphanyHeartbeatStateEntry>(HEARTBEAT_STATE_KEY)
}
pub fn write_heartbeat_state_entry(
store_path: impl AsRef<Path>,
state: &EpiphanyHeartbeatStateEntry,
) -> Result<EpiphanyHeartbeatStateEntry> {
validate_heartbeat_state(state)?;
let store_path = store_path.as_ref();
let cache = heartbeat_state_cache(store_path)?;
commit_owned_entry(store_path, &cache, HEARTBEAT_STATE_KEY, state)
}
pub fn load_heartbeat_state_transaction(
store_path: impl AsRef<Path>,
) -> Result<(
Option<EpiphanyHeartbeatStateEntry>,
Option<CultCacheEnvelope>,
)> {
let cache = heartbeat_state_cache(store_path)?;
let envelope = cache.snapshot_envelopes().into_iter().find(|entry| {
entry.r#type == <EpiphanyHeartbeatStateEntry as DatabaseEntry>::TYPE
&& entry.key == HEARTBEAT_STATE_KEY
});
Ok((
cache.get::<EpiphanyHeartbeatStateEntry>(HEARTBEAT_STATE_KEY)?,
envelope,
))
}
pub fn commit_heartbeat_state_transaction(
store_path: impl AsRef<Path>,
expected: Option<CultCacheEnvelope>,
state: &EpiphanyHeartbeatStateEntry,
) -> Result<()> {
validate_heartbeat_state(state)?;
let cache = heartbeat_state_cache(store_path.as_ref())?;
let (replacement, _) = cache.prepare_entry(HEARTBEAT_STATE_KEY, state)?;
let backing = SingleFileMessagePackBackingStore::new(store_path.as_ref());
let committed = match expected {
Some(expected) => backing.compare_and_swap_entry(&expected, replacement)?,
None => backing.insert_entry_if_absent(replacement)?,
};
if !committed {
return Err(anyhow!(
"heartbeat state lost exact atomic compare-and-swap"
));
}
Ok(())
}
pub fn write_heartbeat_stale_turn_repair_receipt(
store_path: impl AsRef<Path>,
receipt: &EpiphanyHeartbeatStaleTurnRepairReceipt,
) -> Result<EpiphanyHeartbeatStaleTurnRepairReceipt> {
validate_heartbeat_stale_turn_repair_receipt(receipt)?;
let store_path = store_path.as_ref();
let cache = heartbeat_state_cache(store_path)?;
let written = receipt.clone();
let (receipt_entry, _) = cache.prepare_entry(&receipt.receipt_id, receipt)?;
let (latest_entry, _) = cache.prepare_entry(HEARTBEAT_STALE_TURN_REPAIR_LATEST_KEY, receipt)?;
let expected = cache
.snapshot_envelopes()
.into_iter()
.filter(|entry| {
entry.r#type == <EpiphanyHeartbeatStaleTurnRepairReceipt as DatabaseEntry>::TYPE
&& (entry.key == receipt.receipt_id
|| entry.key == HEARTBEAT_STALE_TURN_REPAIR_LATEST_KEY)
})
.collect::<Vec<_>>();
if !SingleFileMessagePackBackingStore::new(store_path)
.compare_and_swap_batch(&expected, vec![receipt_entry, latest_entry])?
{
return Err(anyhow!(
"heartbeat stale-turn receipt lost exact atomic compare-and-swap"
));
}
Ok(written)
}
pub fn load_latest_heartbeat_stale_turn_repair_receipt(
store_path: impl AsRef<Path>,
) -> Result<Option<EpiphanyHeartbeatStaleTurnRepairReceipt>> {
let cache = heartbeat_state_cache(store_path)?;
cache.get::<EpiphanyHeartbeatStaleTurnRepairReceipt>(HEARTBEAT_STALE_TURN_REPAIR_LATEST_KEY)
}
pub fn validate_heartbeat_stale_turn_repair_receipt(
receipt: &EpiphanyHeartbeatStaleTurnRepairReceipt,
) -> Result<()> {
if receipt.schema_version != HEARTBEAT_STALE_TURN_REPAIR_SCHEMA_VERSION {
return Err(anyhow!(
"heartbeat stale-turn repair schema_version is {:?}, expected {:?}",
receipt.schema_version,
HEARTBEAT_STALE_TURN_REPAIR_SCHEMA_VERSION
));
}
if receipt.private_state_exposed {
return Err(anyhow!(
"heartbeat stale-turn repair receipt must not expose private state"
));
}
if receipt.receipt_id.trim().is_empty()
|| receipt.role_id.trim().is_empty()
|| receipt.agent_id.trim().is_empty()
|| receipt.action_id.trim().is_empty()
{
return Err(anyhow!(
"heartbeat stale-turn repair receipt requires receipt, role, agent, and action ids"
));
}
if receipt.stale_age_seconds < 0 {
return Err(anyhow!(
"heartbeat stale-turn repair receipt stale_age_seconds must be non-negative"
));
}
if receipt.reason.trim().is_empty() {
return Err(anyhow!(
"heartbeat stale-turn repair receipt requires a reason"
));
}
if receipt.resulting_status != "repaired" {
return Err(anyhow!(
"heartbeat stale-turn repair receipt resulting_status must be repaired"
));
}
Ok(())
}
pub fn validate_heartbeat_state(state: &EpiphanyHeartbeatStateEntry) -> Result<()> {
if state.schema_version != HEARTBEAT_STATE_SCHEMA_VERSION {
return Err(anyhow!(
"heartbeat state schema_version is {:?}, expected {:?}",
state.schema_version,
HEARTBEAT_STATE_SCHEMA_VERSION
));
}
if state.participants.is_empty() {
return Err(anyhow!("heartbeat state has no participants"));
}
if state.target_heartbeat_rate < 0.0 {
return Err(anyhow!(
"heartbeat target_heartbeat_rate must be non-negative"
));
}
for participant in &state.participants {
if participant.agent_id.trim().is_empty() {
return Err(anyhow!("heartbeat participant has empty agent_id"));
}
if participant.role_id.trim().is_empty() {
return Err(anyhow!(
"heartbeat participant {} has empty role_id",
participant.agent_id
));
}
if participant.initiative_speed <= 0.0 {
return Err(anyhow!(
"heartbeat participant {} initiative_speed must be positive",
participant.agent_id
));
}
let arena = participant_arena(participant);
if arena != HEARTBEAT_ARENA_MAINTENANCE {
return Err(anyhow!(
"heartbeat participant {} arena {:?} is unsupported",
participant.agent_id,
arena
));
}
let participant_kind = participant_kind(participant);
if participant_kind != PARTICIPANT_KIND_AGENT {
return Err(anyhow!(
"heartbeat participant {} participant_kind {:?} is unsupported",
participant.agent_id,
participant_kind
));
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::heartbeat_state::default_heartbeat_state;
use pretty_assertions::assert_eq;
#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)]
#[cultcache(type = "test.foreign.readiness", schema = "ForeignReadiness")]
struct ForeignReadiness {
#[cultcache(key = 0)]
status: String,
}
#[test]
fn round_trips_scheduler_state() -> Result<()> {
let temp = tempfile::tempdir()?;
let store_path = temp.path().join("heartbeats.msgpack");
let state = default_heartbeat_state(1.0);
write_heartbeat_state_entry(&store_path, &state)?;
let loaded = load_heartbeat_state_entry(&store_path)?
.expect("heartbeat state should round-trip through CultCache");
assert_eq!(loaded.schema_version, HEARTBEAT_STATE_SCHEMA_VERSION);
assert_eq!(loaded.participants.len(), state.participants.len());
Ok(())
}
#[test]
fn heartbeat_owner_reads_and_writes_preserve_foreign_shared_store_rows() -> Result<()> {
let temp = tempfile::tempdir()?;
let store_path = temp.path().join("heartbeats.msgpack");
let mut foreign_cache = CultCache::new();
foreign_cache.register_entry_type::<ForeignReadiness>()?;
let (foreign, _) = foreign_cache.prepare_entry(
"provider-readiness",
&ForeignReadiness {
status: "ready".into(),
},
)?;
let mut backing = SingleFileMessagePackBackingStore::new(&store_path);
backing.push(&foreign)?;
assert!(load_heartbeat_state_entry(&store_path)?.is_none());
write_heartbeat_state_entry(&store_path, &default_heartbeat_state(1.0))?;
let rows = backing.pull_all()?;
assert!(rows.contains(&foreign));
assert!(rows.iter().any(|row| {
row.r#type == <EpiphanyHeartbeatStateEntry as DatabaseEntry>::TYPE
&& row.key == HEARTBEAT_STATE_KEY
}));
Ok(())
}
}

View file

@ -10,7 +10,6 @@ mod distillation;
mod eyes_gateway;
mod frontier_plan_chain;
mod hands_gateway;
mod heartbeat_state;
mod host_identity;
mod idunn_provider_health;
mod idunn_runtime_health;
@ -488,43 +487,6 @@ pub use eyes_gateway::default_eyes_cultnet_contracts;
pub use eyes_gateway::eyes_evidence_packet_from_research_finding;
pub use frontier_plan_chain::validate_frontier_plan_decision_chain;
pub use hands_gateway::*;
pub use heartbeat_state::EpiphanyHeartbeatArtifactRetentionPlan;
pub use heartbeat_state::EpiphanyHeartbeatArtifactRetentionReceipt;
pub use heartbeat_state::EpiphanyHeartbeatStaleTurnRepairReceipt;
pub use heartbeat_state::EpiphanyHeartbeatStateEntry;
pub use heartbeat_state::HEARTBEAT_ARTIFACT_RETENTION_PLAN_LATEST_KEY;
pub use heartbeat_state::HEARTBEAT_ARTIFACT_RETENTION_PLAN_SCHEMA_VERSION;
pub use heartbeat_state::HEARTBEAT_ARTIFACT_RETENTION_PLAN_TYPE;
pub use heartbeat_state::HEARTBEAT_ARTIFACT_RETENTION_RECEIPT_LATEST_KEY;
pub use heartbeat_state::HEARTBEAT_ARTIFACT_RETENTION_RECEIPT_SCHEMA_VERSION;
pub use heartbeat_state::HEARTBEAT_ARTIFACT_RETENTION_RECEIPT_TYPE;
pub use heartbeat_state::HEARTBEAT_STALE_TURN_REPAIR_LATEST_KEY;
pub use heartbeat_state::HEARTBEAT_STALE_TURN_REPAIR_SCHEMA_VERSION;
pub use heartbeat_state::HEARTBEAT_STALE_TURN_REPAIR_TYPE;
pub use heartbeat_state::HEARTBEAT_STATE_KEY;
pub use heartbeat_state::HEARTBEAT_STATE_SCHEMA_VERSION;
pub use heartbeat_state::HEARTBEAT_STATE_TYPE;
pub use heartbeat_state::HeartbeatArtifactRetentionMember;
pub use heartbeat_state::HeartbeatHistoryEvent;
pub use heartbeat_state::HeartbeatPacingPolicy;
pub use heartbeat_state::HeartbeatParticipant;
pub use heartbeat_state::HeartbeatPendingTurn;
pub use heartbeat_state::HeartbeatSelectionPolicy;
pub use heartbeat_state::HeartbeatStaleTurnRepairOptions;
pub use heartbeat_state::ResidentSelfHeartbeatPulse;
pub use heartbeat_state::default_heartbeat_state;
pub use heartbeat_state::heartbeat_state_cache;
pub use heartbeat_state::heartbeat_status_projection;
pub use heartbeat_state::initialize_heartbeat_store;
pub use heartbeat_state::load_heartbeat_state_entry;
pub use heartbeat_state::load_latest_heartbeat_stale_turn_repair_receipt;
pub use heartbeat_state::pulse_resident_self_heartbeat;
pub use heartbeat_state::reconcile_resident_self_heartbeat_ack;
pub use heartbeat_state::recover_stale_heartbeat_store;
pub use heartbeat_state::retain_heartbeat_pulse_artifacts;
pub use heartbeat_state::validate_heartbeat_state;
pub use heartbeat_state::write_heartbeat_stale_turn_repair_receipt;
pub use heartbeat_state::write_heartbeat_state_entry;
pub use host_identity::{
HOST_IDENTITY_KEY, HOST_IDENTITY_SCHEMA_VERSION, HOST_IDENTITY_TRUST_ANCHOR_KEY,
HOST_IDENTITY_TRUST_ANCHOR_TYPE, HOST_IDENTITY_TYPE, HostIdentitySignature, HostIdentitySigner,

View file

@ -857,26 +857,6 @@ mod tests {
use super::*;
use epiphany_model_adapter::EpiphanyModelRequest;
#[test]
fn reorientation_has_no_aggregate_or_freshness_authority_path() {
let coordinator = include_str!("bin/epiphany-mvp-coordinator.rs");
let status = include_str!("bin/epiphany-mvp-status.rs");
let agent_launch = include_str!("agent_launch.rs");
let worker_launch = include_str!("surfaces/worker_launch.rs");
for (source, forbidden) in [
(coordinator, "build_epiphany_reorient_launch_request"),
(status, "recommend_reorientation"),
(status, "crrc_regather_current_after_relinquishment"),
(agent_launch, "build_epiphany_reorient_launch_request"),
(worker_launch, "EpiphanyReorientLaunchRequestInput"),
] {
assert!(
!source.contains(forbidden),
"obsolete reorientation authority {forbidden:?} survived"
);
}
}
#[test]
fn reorientation_is_decision_auditable_thread_free_and_keyed() -> Result<()> {
let temp = tempfile::tempdir()?;

View file

@ -1,8 +1,8 @@
use crate::{
ProcessInstanceIdentity, ProcessInstanceObservation, ResidentSelfPolicy,
authenticate_epiphany_packaged_release, epiphany_packaged_release_binary_path,
load_epiphany_cultmesh_swarm_brake, load_heartbeat_state_entry, load_resident_self_state,
observe_process_instance, validate_resident_self_store_separation,
load_epiphany_cultmesh_swarm_brake, load_resident_self_state, observe_process_instance,
validate_resident_self_store_separation,
};
use anyhow::{Result, anyhow, bail};
use cultcache_rs::{
@ -45,12 +45,12 @@ pub struct ResidentProcessSingletonGuard {
}
#[cfg(unix)]
fn resident_process_singleton_lock_path(role: &str, store: &Path) -> Result<PathBuf> {
fn resident_process_singleton_lock_path(store: &Path) -> Result<PathBuf> {
let file_name = store
.file_name()
.ok_or_else(|| anyhow!("resident singleton store has no file name"))?
.to_string_lossy();
Ok(store.with_file_name(format!("{file_name}.{role}.process.lock")))
Ok(store.with_file_name(format!("{file_name}.resident-self.process.lock")))
}
impl Drop for ResidentProcessSingletonGuard {
@ -68,34 +68,30 @@ impl Drop for ResidentProcessSingletonGuard {
}
pub fn acquire_resident_process_singleton(
role: &str,
store: impl AsRef<Path>,
) -> Result<ResidentProcessSingletonGuard> {
if !matches!(role, "heartbeat" | "resident-self") {
bail!("unsupported resident singleton role: {role}");
}
let store = canonical_or_absolute(store.as_ref());
#[cfg(windows)]
{
let identity = format!(
"{:x}",
Sha256::digest(format!("{role}|{}", store.to_string_lossy().to_lowercase()).as_bytes())
Sha256::digest(store.to_string_lossy().to_lowercase().as_bytes())
);
let name = OsStr::new(&format!("Global\\EpiphanyResident-{role}-{identity}"))
let name = OsStr::new(&format!("Global\\EpiphanyResidentSelf-{identity}"))
.encode_wide()
.chain(std::iter::once(0))
.collect::<Vec<_>>();
let handle = unsafe { CreateMutexW(std::ptr::null_mut(), 1, name.as_ptr()) };
if handle.is_null() {
bail!(
"resident {role} singleton mutex creation failed: {}",
"resident Self singleton mutex creation failed: {}",
unsafe { GetLastError() }
);
}
if unsafe { GetLastError() } == ERROR_ALREADY_EXISTS {
unsafe { CloseHandle(handle) };
bail!(
"resident {role} owner already exists for canonical store {}",
"resident Self owner already exists for canonical store {}",
store.display()
);
}
@ -103,7 +99,7 @@ pub fn acquire_resident_process_singleton(
}
#[cfg(unix)]
{
let path = resident_process_singleton_lock_path(role, &store)?;
let path = resident_process_singleton_lock_path(&store)?;
let file = File::options()
.create(true)
.read(true)
@ -111,7 +107,7 @@ pub fn acquire_resident_process_singleton(
.open(path)?;
if unsafe { libc::flock(file.as_raw_fd(), libc::LOCK_EX | libc::LOCK_NB) } != 0 {
bail!(
"resident {role} owner already exists for canonical store {}",
"resident Self owner already exists for canonical store {}",
store.display()
);
}
@ -123,24 +119,21 @@ pub fn acquire_resident_process_singleton(
))
}
fn resident_process_singleton_is_owned(role: &str, store: &Path) -> Result<bool> {
if !matches!(role, "heartbeat" | "resident-self") {
bail!("unsupported resident singleton role: {role}");
}
fn resident_process_singleton_is_owned(store: &Path) -> Result<bool> {
let store = canonical_or_absolute(store);
#[cfg(windows)]
{
let identity = format!(
"{:x}",
Sha256::digest(format!("{role}|{}", store.to_string_lossy().to_lowercase()).as_bytes())
Sha256::digest(store.to_string_lossy().to_lowercase().as_bytes())
);
let name = OsStr::new(&format!("Global\\EpiphanyResident-{role}-{identity}"))
let name = OsStr::new(&format!("Global\\EpiphanyResidentSelf-{identity}"))
.encode_wide()
.chain(std::iter::once(0))
.collect::<Vec<_>>();
let handle = unsafe { CreateMutexW(std::ptr::null_mut(), 0, name.as_ptr()) };
if handle.is_null() {
bail!("resident {role} singleton mutex probe failed: {}", unsafe {
bail!("resident Self singleton mutex probe failed: {}", unsafe {
GetLastError()
});
}
@ -155,7 +148,7 @@ fn resident_process_singleton_is_owned(role: &str, store: &Path) -> Result<bool>
}
other => {
unsafe { CloseHandle(handle) };
bail!("resident {role} singleton mutex probe returned {other}");
bail!("resident Self singleton mutex probe returned {other}");
}
};
unsafe { CloseHandle(handle) };
@ -163,7 +156,7 @@ fn resident_process_singleton_is_owned(role: &str, store: &Path) -> Result<bool>
}
#[cfg(unix)]
{
let path = resident_process_singleton_lock_path(role, &store)?;
let path = resident_process_singleton_lock_path(&store)?;
if !path.exists() {
return Ok(false);
}
@ -225,7 +218,7 @@ impl ResidentProviderReadiness {
if self.schema_version != RESIDENT_PROVIDER_READINESS_SCHEMA_VERSION {
bail!("resident provider readiness schema is unsupported");
}
if !matches!(self.provider.as_str(), "heartbeat" | "resident-self") {
if self.provider != "resident-self" {
bail!("resident provider readiness owner is invalid");
}
if self.runtime_id.trim().is_empty()
@ -306,22 +299,6 @@ pub fn load_resident_provider_readiness(store: &Path) -> Result<Option<ResidentP
Ok(readiness_snapshot(store)?.1)
}
pub fn heartbeat_local_provider_status(
heartbeat_store: &Path,
resident_store: &Path,
) -> &'static str {
if distinct_physical_paths(heartbeat_store, resident_store)
&& load_heartbeat_state_entry(heartbeat_store)
.ok()
.flatten()
.is_some()
{
"ready"
} else {
"warming"
}
}
pub fn resident_self_local_provider_status(
resident_store: &Path,
policy: &ResidentSelfPolicy,
@ -354,7 +331,6 @@ pub fn resident_self_local_provider_status(
#[derive(Clone, Debug)]
pub struct ResidentReadinessRequest<'a> {
pub release_store: &'a Path,
pub heartbeat_store: &'a Path,
pub resident_store: &'a Path,
pub policy: &'a ResidentSelfPolicy,
pub release_runtime_id: &'a str,
@ -375,8 +351,6 @@ pub struct ResidentCognitionReadinessProjection {
pub source_commit: Option<String>,
pub release_authenticated: bool,
pub physical_stores_separate: bool,
pub heartbeat_initialized: bool,
pub heartbeat_provider_fresh: bool,
pub resident_provider_fresh: bool,
pub resident_state_coherent: bool,
pub active_lease_observation: String,
@ -402,10 +376,6 @@ pub fn derive_resident_cognition_readiness(
.as_ref()
.ok()
.map(|value| value.source_commit_sha.as_str());
let heartbeat_executable = release
.as_ref()
.ok()
.and_then(|value| epiphany_packaged_release_binary_path(value, "heartbeat").ok());
let resident_executable = release
.as_ref()
.ok()
@ -424,26 +394,10 @@ pub fn derive_resident_cognition_readiness(
}
};
let physical_stores_separate =
validate_resident_self_store_separation(request.resident_store, request.policy).is_ok()
&& distinct_physical_paths(request.heartbeat_store, request.resident_store);
validate_resident_self_store_separation(request.resident_store, request.policy).is_ok();
if !physical_stores_separate {
reasons.push("resident cognition stores are not physically separated".into());
}
let heartbeat_initialized = load_heartbeat_state_entry(request.heartbeat_store)
.ok()
.flatten()
.is_some();
if !heartbeat_initialized {
reasons.push("heartbeat state is absent or unreadable".into());
}
let heartbeat_provider_fresh = provider_is_fresh(
request.heartbeat_store,
"heartbeat",
&request,
expected_source_commit,
heartbeat_executable.as_deref(),
&mut reasons,
);
let resident_provider_fresh = provider_is_fresh(
request.resident_store,
"resident-self",
@ -491,15 +445,13 @@ pub fn derive_resident_cognition_readiness(
}
let ready = release_authenticated
&& physical_stores_separate
&& heartbeat_initialized
&& heartbeat_provider_fresh
&& resident_provider_fresh
&& resident_state_coherent
&& cognitive_runtime_id.is_some()
&& workspace_ready
&& credential_ready;
ResidentCognitionReadinessProjection {
schema_version: "epiphany.resident_cognition.readiness.v0".into(),
schema_version: "epiphany.resident_cognition.readiness.v1".into(),
status: if ready {
"active"
} else if release_authenticated {
@ -521,8 +473,6 @@ pub fn derive_resident_cognition_readiness(
.map(|value| value.source_commit_sha.clone()),
release_authenticated,
physical_stores_separate,
heartbeat_initialized,
heartbeat_provider_fresh,
resident_provider_fresh,
resident_state_coherent,
active_lease_observation,
@ -571,34 +521,24 @@ fn classify_active_lease_observation(
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ResidentProviderPairHealth {
pub struct ResidentProviderHealth {
pub terminal_current: usize,
pub warming: usize,
pub contradictions: Vec<String>,
}
pub fn authenticate_resident_provider_pair(
pub fn authenticate_resident_provider(
release: &crate::EpiphanyPackagedReleaseEntry,
witness: &str,
heartbeat_store: &Path,
resident_store: &Path,
freshness_millis: u64,
) -> ResidentProviderPairHealth {
let mut result = ResidentProviderPairHealth {
) -> ResidentProviderHealth {
let mut result = ResidentProviderHealth {
terminal_current: 0,
warming: 0,
contradictions: Vec::new(),
};
if !distinct_physical_paths(heartbeat_store, resident_store) {
result
.contradictions
.push("heartbeat and resident Self provider stores alias physically".into());
return result;
}
for (owner, store, role) in [
("heartbeat", heartbeat_store, "heartbeat"),
("resident-self", resident_store, "swarm"),
] {
for (owner, store, role) in [("resident-self", resident_store, "swarm")] {
let executable = epiphany_packaged_release_binary_path(release, role).ok();
let value = match load_resident_provider_readiness(store) {
Ok(Some(value)) => value,
@ -613,7 +553,7 @@ pub fn authenticate_resident_provider_pair(
continue;
}
};
let role_owner_present = match resident_process_singleton_is_owned(owner, store) {
let role_owner_present = match resident_process_singleton_is_owned(store) {
Ok(value) => value,
Err(error) => {
result.contradictions.push(format!(
@ -665,7 +605,7 @@ fn provider_is_fresh(
return false;
}
};
let role_owner_present = match resident_process_singleton_is_owned(owner, store) {
let role_owner_present = match resident_process_singleton_is_owned(store) {
Ok(value) => value,
Err(error) => {
reasons.push(format!(
@ -772,11 +712,6 @@ fn provider_matches_authority(
.is_empty()
}
fn distinct_physical_paths(left: &Path, right: &Path) -> bool {
canonical_or_absolute(left) != canonical_or_absolute(right)
&& matches!(crate::same_existing_file(left, right), Ok(false))
}
fn canonical_or_absolute(path: &Path) -> PathBuf {
fs::canonicalize(path).unwrap_or_else(|_| path.to_path_buf())
}
@ -838,17 +773,15 @@ mod tests {
use super::*;
#[test]
fn resident_process_singleton_excludes_only_the_same_role_and_store() -> Result<()> {
fn resident_process_singleton_excludes_only_the_same_store() -> Result<()> {
let temp = tempfile::tempdir()?;
let store = temp.path().join("resident.cc");
fs::write(&store, [])?;
let owner = acquire_resident_process_singleton("resident-self", &store)?;
assert!(acquire_resident_process_singleton("resident-self", &store).is_err());
let other_role = acquire_resident_process_singleton("heartbeat", &store)?;
let other_store =
acquire_resident_process_singleton("resident-self", temp.path().join("other.cc"))?;
drop((owner, other_role, other_store));
assert!(acquire_resident_process_singleton("resident-self", &store).is_ok());
let owner = acquire_resident_process_singleton(&store)?;
assert!(acquire_resident_process_singleton(&store).is_err());
let other_store = acquire_resident_process_singleton(temp.path().join("other.cc"))?;
drop((owner, other_store));
assert!(acquire_resident_process_singleton(&store).is_ok());
Ok(())
}
@ -857,13 +790,9 @@ mod tests {
fn unix_resident_singleton_lock_is_shared_beside_the_mounted_store() -> Result<()> {
let store = Path::new("/state/resident.cc");
assert_eq!(
resident_process_singleton_lock_path("resident-self", store)?,
resident_process_singleton_lock_path(store)?,
PathBuf::from("/state/resident.cc.resident-self.process.lock")
);
assert_eq!(
resident_process_singleton_lock_path("heartbeat", store)?,
PathBuf::from("/state/resident.cc.heartbeat.process.lock")
);
Ok(())
}
@ -894,7 +823,7 @@ mod tests {
fn provider() -> ResidentProviderReadiness {
ResidentProviderReadiness {
schema_version: RESIDENT_PROVIDER_READINESS_SCHEMA_VERSION.into(),
provider: "heartbeat".into(),
provider: "resident-self".into(),
runtime_id: "ygg".into(),
release_id: "release-a".into(),
release_witness_sha256: format!("sha256-{}", "a".repeat(64)),
@ -915,7 +844,7 @@ mod tests {
let executable = std::env::current_exe().unwrap();
assert!(provider_matches_authority(
&value,
"heartbeat",
"resident-self",
"ygg",
"release-a",
&value.release_witness_sha256,
@ -927,7 +856,7 @@ mod tests {
));
assert!(!provider_matches_authority(
&value,
"heartbeat",
"resident-self",
"ygg",
"release-b",
&value.release_witness_sha256,
@ -939,7 +868,7 @@ mod tests {
));
assert!(!provider_matches_authority(
&value,
"heartbeat",
"resident-self",
"ygg",
"release-a",
&value.release_witness_sha256,
@ -951,7 +880,7 @@ mod tests {
));
assert!(!provider_matches_authority(
&value,
"heartbeat",
"resident-self",
"ygg",
"release-a",
&value.release_witness_sha256,
@ -966,22 +895,22 @@ mod tests {
#[test]
fn role_singleton_probe_observes_only_a_live_owner() -> Result<()> {
let temp = tempfile::tempdir()?;
let store = temp.path().join("heartbeat.cc");
assert!(!resident_process_singleton_is_owned("heartbeat", &store)?);
let store = temp.path().join("resident.cc");
assert!(!resident_process_singleton_is_owned(&store)?);
let (ready_tx, ready_rx) = std::sync::mpsc::sync_channel(0);
let (release_tx, release_rx) = std::sync::mpsc::sync_channel(0);
let owned_store = store.clone();
let owner = std::thread::spawn(move || -> Result<()> {
let _guard = acquire_resident_process_singleton("heartbeat", &owned_store)?;
let _guard = acquire_resident_process_singleton(&owned_store)?;
ready_tx.send(())?;
release_rx.recv()?;
Ok(())
});
ready_rx.recv()?;
assert!(resident_process_singleton_is_owned("heartbeat", &store)?);
assert!(resident_process_singleton_is_owned(&store)?);
release_tx.send(())?;
owner.join().expect("singleton owner thread")?;
assert!(!resident_process_singleton_is_owned("heartbeat", &store)?);
assert!(!resident_process_singleton_is_owned(&store)?);
Ok(())
}
@ -1041,17 +970,6 @@ mod tests {
Ok(())
}
#[test]
fn hardlink_store_aliases_are_not_physically_distinct() -> Result<()> {
let temp = tempfile::tempdir()?;
let first = temp.path().join("first.cc");
let alias = temp.path().join("alias.cc");
fs::write(&first, b"cultcache")?;
fs::hard_link(&first, &alias)?;
assert!(!distinct_physical_paths(&first, &alias));
Ok(())
}
#[cfg(unix)]
#[test]
fn unix_credentials_reject_group_or_other_access() -> Result<()> {
@ -1083,35 +1001,6 @@ mod tests {
Ok(())
}
#[test]
fn projection_schema_has_no_systemd_or_secret_substitution_surface() {
let projection = ResidentCognitionReadinessProjection {
schema_version: "epiphany.resident_cognition.readiness.v0".into(),
status: "warming".into(),
runtime_id: Some("epiphany-yggdrasil".into()),
release_runtime_id: Some("epiphany-release-yggdrasil".into()),
release_id: Some("release-1".into()),
release_witness_sha256: Some("sha256:witness".into()),
source_commit: Some("commit-1".into()),
release_authenticated: true,
physical_stores_separate: true,
heartbeat_initialized: true,
heartbeat_provider_fresh: false,
resident_provider_fresh: false,
resident_state_coherent: true,
active_lease_observation: "none".into(),
brake_engaged: true,
workspace_ready: true,
credential_ready: true,
reasons: vec!["provider absent".into()],
private_state_exposed: false,
};
let json = serde_json::to_string(&projection).unwrap();
assert!(!json.contains("systemd"));
assert!(!json.contains("credentialPath"));
assert!(!json.contains("secret"));
}
#[test]
fn readiness_uses_mounted_cognitive_identity_for_projection_and_brake() -> Result<()> {
let temp = tempfile::tempdir()?;
@ -1159,11 +1048,9 @@ mod tests {
release_id: "sha256:release".into(),
release_witness_sha256: "sha256:witness".into(),
};
let heartbeat_store = shared.join("heartbeat.cc");
let resident_store = shared.join("resident.cc");
let projection = derive_resident_cognition_readiness(ResidentReadinessRequest {
release_store: &policy.release_store,
heartbeat_store: &heartbeat_store,
resident_store: &resident_store,
policy: &policy,
release_runtime_id: &policy.release_runtime_id,
@ -1209,7 +1096,7 @@ mod tests {
assert_eq!(
provider_authority_failures(
&value,
"heartbeat",
"resident-self",
"ygg",
"release-a",
&value.release_witness_sha256,
@ -1236,7 +1123,7 @@ mod tests {
let executable = PathBuf::from(&value.process_executable_path);
assert!(provider_matches_authority(
&value,
"heartbeat",
"resident-self",
"ygg",
"release-a",
&value.release_witness_sha256,
@ -1249,7 +1136,7 @@ mod tests {
value.observed_at_millis = 2_001;
assert!(!provider_matches_authority(
&value,
"heartbeat",
"resident-self",
"ygg",
"release-a",
&value.release_witness_sha256,
@ -1262,9 +1149,8 @@ mod tests {
}
#[test]
fn aggregate_pair_warms_on_missing_and_rejects_wrong_release() -> Result<()> {
fn aggregate_health_warms_on_missing_and_rejects_wrong_release() -> Result<()> {
let temp = tempfile::tempdir()?;
let heartbeat = temp.path().join("heartbeat.cc");
let resident = temp.path().join("resident.cc");
let executable = std::env::current_exe()?;
let binary = |role: &str| crate::EpiphanyPackagedReleaseBinary {
@ -1284,13 +1170,12 @@ mod tests {
toolchain_fingerprint: "toolchain".into(),
created_at_utc: "now".into(),
package_root: temp.path().display().to_string(),
binaries: vec![binary("heartbeat"), binary("swarm")],
binaries: vec![binary("swarm")],
private_state_exposed: false,
};
let witness = format!("sha256-{}", "a".repeat(64));
let missing =
authenticate_resident_provider_pair(&release, &witness, &heartbeat, &resident, 10);
assert_eq!(missing.warming, 2);
let missing = authenticate_resident_provider(&release, &witness, &resident, 10);
assert_eq!(missing.warming, 1);
let process = crate::capture_process_instance(std::process::id())?;
let mut wrong = provider();
wrong.runtime_id = "ygg".into();
@ -1301,9 +1186,8 @@ mod tests {
wrong.process_id = process.process_id;
wrong.process_creation_token = process.creation_token;
wrong.process_executable_path = process.executable_path.display().to_string();
publish_resident_provider_readiness(&heartbeat, wrong)?;
let rejected =
authenticate_resident_provider_pair(&release, &witness, &heartbeat, &resident, 10);
publish_resident_provider_readiness(&resident, wrong)?;
let rejected = authenticate_resident_provider(&release, &witness, &resident, 10);
assert!(
rejected
.contradictions

View file

@ -8,7 +8,7 @@ use std::collections::{BTreeSet, HashSet};
use std::path::{Path, PathBuf};
pub const RESIDENT_SELF_STATE_KEY: &str = "resident-self";
pub const RESIDENT_SELF_STATE_SCHEMA_VERSION: &str = "epiphany.resident_self.state.v0";
pub const RESIDENT_SELF_STATE_SCHEMA_VERSION: &str = "epiphany.resident_self.state.v1";
pub const RESIDENT_SELF_RUNTIME_RECEIPT_SCHEMA_VERSION: &str =
"epiphany.resident_self.runtime_receipt.v0";
pub const RESIDENT_SELF_PRESSURE_SCHEMA_VERSION: &str = "epiphany.resident_self.pressure.v0";
@ -21,8 +21,9 @@ pub const RESIDENT_SELF_ATLAS_IMPACT_PROVENANCE_PREFIX: &str = "cultcache://atla
pub const RESIDENT_SELF_ATLAS_NO_HANDS_AUTHORITY_CLAUSE: &str =
"This wake grants no Hands authority.";
pub const RESIDENT_SELF_CURRENT_WORK_PROVENANCE_PREFIX: &str = "cultcache://current-work/";
pub const RESIDENT_SELF_GRANT_SCHEMA_VERSION: &str = "epiphany.resident_self.heartbeat_grant.v1";
pub const RESIDENT_SELF_ACK_SCHEMA_VERSION: &str = "epiphany.resident_self.terminal_ack.v0";
pub const RESIDENT_SELF_GRANT_SCHEMA_VERSION: &str = "epiphany.resident_self.grant.v1";
pub const RESIDENT_SELF_TERMINAL_RECEIPT_SCHEMA_VERSION: &str =
"epiphany.resident_self.terminal_receipt.v1";
pub const RESIDENT_SELF_CHILD_CLAIM_SCHEMA_VERSION: &str = "epiphany.resident_self.child_claim.v0";
pub const RESIDENT_SELF_RETENTION_HEAD_SCHEMA_VERSION: &str =
"epiphany.resident_self.retention_head.v0";
@ -139,11 +140,8 @@ impl ResidentSelfPressure {
}
#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)]
#[cultcache(
type = "epiphany.resident_self.heartbeat_grant.v0",
schema = "ResidentSelfHeartbeatGrant"
)]
pub struct ResidentSelfHeartbeatGrant {
#[cultcache(type = "epiphany.resident_self.grant.v1", schema = "ResidentSelfGrant")]
pub struct ResidentSelfGrant {
#[cultcache(key = 0)]
pub schema_version: String,
#[cultcache(key = 1)]
@ -157,18 +155,14 @@ pub struct ResidentSelfHeartbeatGrant {
#[cultcache(key = 5)]
pub objective: String,
#[cultcache(key = 6)]
pub heartbeat_schedule_id: String,
#[cultcache(key = 7)]
pub heartbeat_action_id: String,
#[cultcache(key = 8)]
pub issued_at_millis: u64,
#[cultcache(key = 9, default)]
#[cultcache(key = 7, default)]
pub consumed_at_millis: Option<u64>,
#[cultcache(key = 10, default)]
#[cultcache(key = 8, default)]
pub private_state_exposed: bool,
#[cultcache(key = 11, default)]
#[cultcache(key = 9, default)]
pub terminal_at_millis: Option<u64>,
#[cultcache(key = 12, default)]
#[cultcache(key = 10, default)]
pub terminal_status: Option<String>,
}
@ -178,8 +172,6 @@ pub struct ResidentSelfGrantLifecycleProjection {
pub grant_id: String,
pub pressure_id: String,
pub pressure_kind: String,
pub heartbeat_schedule_id: String,
pub heartbeat_action_id: String,
pub issued_at_millis: u64,
pub consumed_at_millis: Option<u64>,
pub terminal_at_millis: Option<u64>,
@ -189,43 +181,31 @@ pub struct ResidentSelfGrantLifecycleProjection {
pub launchable: bool,
}
fn resident_self_grant_is_pending(
grant: &ResidentSelfHeartbeatGrant,
legacy_terminal_grant_ids: &BTreeSet<String>,
) -> bool {
grant.consumed_at_millis.is_none()
&& grant.terminal_at_millis.is_none()
&& !(grant.schema_version == "epiphany.resident_self.heartbeat_grant.v0"
&& legacy_terminal_grant_ids.contains(&grant.grant_id))
fn resident_self_grant_is_pending(grant: &ResidentSelfGrant) -> bool {
grant.consumed_at_millis.is_none() && grant.terminal_at_millis.is_none()
}
#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)]
#[cultcache(
type = "epiphany.resident_self.terminal_ack.v0",
schema = "ResidentSelfTerminalAck"
type = "epiphany.resident_self.terminal_receipt.v1",
schema = "ResidentSelfTerminalReceipt"
)]
pub struct ResidentSelfTerminalAck {
pub struct ResidentSelfTerminalReceipt {
#[cultcache(key = 0)]
pub schema_version: String,
#[cultcache(key = 1)]
pub ack_id: String,
pub receipt_id: String,
#[cultcache(key = 2)]
pub grant_id: String,
#[cultcache(key = 3)]
pub heartbeat_schedule_id: String,
#[cultcache(key = 4)]
pub heartbeat_action_id: String,
#[cultcache(key = 5)]
pub launch_digest: String,
#[cultcache(key = 6)]
#[cultcache(key = 4)]
pub coordinator_receipt_id: String,
#[cultcache(key = 7)]
#[cultcache(key = 5)]
pub terminal_status: String,
#[cultcache(key = 8)]
#[cultcache(key = 6)]
pub completed_at_millis: u64,
#[cultcache(key = 9, default)]
pub consumed_by_heartbeat_at_millis: Option<u64>,
#[cultcache(key = 10, default)]
#[cultcache(key = 7, default)]
pub private_state_exposed: bool,
}
@ -506,7 +486,7 @@ pub struct ResidentSelfTurnLease {
pub struct ResidentSelfPreparedLaunch {
pub preparation_id: String,
pub prepared_at_millis: u64,
pub grant: ResidentSelfHeartbeatGrant,
pub grant: ResidentSelfGrant,
pub argv: Vec<String>,
pub launch_digest: String,
pub policy_digest: String,
@ -728,7 +708,7 @@ fn resident_self_atlas_required_action(pressure_kind: &str) -> Option<&'static s
fn resident_coordinator_binding_for_grant(
policy: &ResidentSelfPolicy,
grant: &ResidentSelfHeartbeatGrant,
grant: &ResidentSelfGrant,
) -> Result<Option<ResidentCoordinatorBinding>> {
let mut cache = crate::runtime_spine_cache(&policy.runtime_store)?;
cache.pull_all_backing_stores()?;
@ -807,17 +787,25 @@ fn state_cache(path: &Path) -> Result<CultCache> {
cache.register_entry_type::<ResidentSelfState>()?;
cache.register_entry_type::<ResidentSelfRuntimeReceipt>()?;
cache.register_entry_type::<ResidentSelfPressure>()?;
cache.register_entry_type::<ResidentSelfHeartbeatGrant>()?;
cache.register_entry_type::<ResidentSelfTerminalAck>()?;
cache.register_entry_type::<ResidentSelfGrant>()?;
cache.register_entry_type::<ResidentSelfTerminalReceipt>()?;
cache.register_entry_type::<ResidentSelfChildClaim>()?;
cache.register_entry_type::<ResidentSelfRetentionHead>()?;
let mut identities = HashSet::new();
for envelope in SingleFileMessagePackBackingStore::new(path).pull_all()? {
if matches!(
envelope.r#type.as_str(),
"epiphany.resident_self.heartbeat_grant.v0" | "epiphany.resident_self.terminal_ack.v0"
) {
return Err(anyhow!(
"resident Self store contains a pre-cut heartbeat lifecycle document"
));
}
let owned = envelope.r#type == ResidentSelfState::TYPE
|| envelope.r#type == ResidentSelfRuntimeReceipt::TYPE
|| envelope.r#type == ResidentSelfPressure::TYPE
|| envelope.r#type == ResidentSelfHeartbeatGrant::TYPE
|| envelope.r#type == ResidentSelfTerminalAck::TYPE
|| envelope.r#type == ResidentSelfGrant::TYPE
|| envelope.r#type == ResidentSelfTerminalReceipt::TYPE
|| envelope.r#type == ResidentSelfChildClaim::TYPE
|| envelope.r#type == ResidentSelfRetentionHead::TYPE;
if !owned {
@ -840,11 +828,11 @@ fn state_cache(path: &Path) -> Result<CultCache> {
ResidentSelfPressure::TYPE => {
cache.load_envelope::<ResidentSelfPressure>(envelope)?;
}
ResidentSelfHeartbeatGrant::TYPE => {
cache.load_envelope::<ResidentSelfHeartbeatGrant>(envelope)?;
ResidentSelfGrant::TYPE => {
cache.load_envelope::<ResidentSelfGrant>(envelope)?;
}
ResidentSelfTerminalAck::TYPE => {
cache.load_envelope::<ResidentSelfTerminalAck>(envelope)?;
ResidentSelfTerminalReceipt::TYPE => {
cache.load_envelope::<ResidentSelfTerminalReceipt>(envelope)?;
}
ResidentSelfChildClaim::TYPE => {
cache.load_envelope::<ResidentSelfChildClaim>(envelope)?;
@ -855,6 +843,26 @@ fn state_cache(path: &Path) -> Result<CultCache> {
_ => unreachable!("owned resident Self type was matched above"),
};
}
if let Some(state) = cache.get::<ResidentSelfState>(RESIDENT_SELF_STATE_KEY)?
&& state.schema_version != RESIDENT_SELF_STATE_SCHEMA_VERSION
{
return Err(anyhow!(
"resident Self store has an obsolete writable state epoch"
));
}
if cache
.get_all::<ResidentSelfGrant>()?
.iter()
.any(|grant| grant.schema_version != RESIDENT_SELF_GRANT_SCHEMA_VERSION)
|| cache
.get_all::<ResidentSelfTerminalReceipt>()?
.iter()
.any(|receipt| receipt.schema_version != RESIDENT_SELF_TERMINAL_RECEIPT_SCHEMA_VERSION)
{
return Err(anyhow!(
"resident Self store has an obsolete writable lifecycle epoch"
));
}
Ok(cache)
}
@ -1057,12 +1065,10 @@ pub fn ingest_resident_self_current_work_pressure(
)
}
pub fn heartbeat_issue_resident_self_grant(
pub fn issue_resident_self_grant(
path: &Path,
schedule_id: &str,
action_id: &str,
now_millis: u64,
) -> Result<Option<ResidentSelfHeartbeatGrant>> {
) -> Result<Option<ResidentSelfGrant>> {
let mut cache = state_cache(path)?;
if cache
.get::<ResidentSelfState>(RESIDENT_SELF_STATE_KEY)?
@ -1079,16 +1085,8 @@ pub fn heartbeat_issue_resident_self_grant(
if state.active_turn.is_some() || state.prepared_launch.is_some() {
return Ok(None);
}
let legacy_terminal_grant_ids = cache
.get_all::<ResidentSelfTerminalAck>()?
.into_iter()
.map(|ack| ack.grant_id)
.collect::<std::collections::BTreeSet<_>>();
let grants = cache.get_all::<ResidentSelfHeartbeatGrant>()?;
if grants.iter().any(|grant| {
(grant.heartbeat_schedule_id == schedule_id && grant.heartbeat_action_id == action_id)
|| resident_self_grant_is_pending(grant, &legacy_terminal_grant_ids)
}) {
let grants = cache.get_all::<ResidentSelfGrant>()?;
if grants.iter().any(resident_self_grant_is_pending) {
return Ok(None);
}
let mut pending = cache
@ -1110,18 +1108,16 @@ pub fn heartbeat_issue_resident_self_grant(
.count()
+ 1;
let grant_id = format!(
"resident-self-grant-{schedule_id}-{action_id}-attempt-{attempt_ordinal}-{}",
"resident-self-grant-attempt-{attempt_ordinal}-{}",
pressure.pressure_id
);
let grant = ResidentSelfHeartbeatGrant {
let grant = ResidentSelfGrant {
schema_version: RESIDENT_SELF_GRANT_SCHEMA_VERSION.into(),
grant_id: grant_id.clone(),
pressure_id: pressure.pressure_id.clone(),
pressure_kind: pressure.kind.clone(),
provenance_ref: pressure.provenance_ref.clone(),
objective: pressure.objective.clone(),
heartbeat_schedule_id: schedule_id.into(),
heartbeat_action_id: action_id.into(),
issued_at_millis: now_millis,
consumed_at_millis: None,
private_state_exposed: false,
@ -1155,9 +1151,7 @@ pub fn heartbeat_issue_resident_self_grant(
&[expected_state, expected_pressure],
vec![state_entry, pressure_entry, grant_entry],
)? {
return Err(anyhow!(
"heartbeat lost resident Self pressure-to-grant CAS"
));
return Err(anyhow!("resident Self lost pressure-to-grant CAS"));
}
Ok(Some(grant))
}
@ -1184,13 +1178,8 @@ pub fn resident_self_grant_lifecycle_projection(
let state = cache
.get::<ResidentSelfState>(RESIDENT_SELF_STATE_KEY)?
.unwrap_or_default();
let legacy_terminal_grant_ids = cache
.get_all::<ResidentSelfTerminalAck>()?
.into_iter()
.map(|ack| ack.grant_id)
.collect::<BTreeSet<_>>();
let mut grants = cache
.get_all::<ResidentSelfHeartbeatGrant>()?
.get_all::<ResidentSelfGrant>()?
.into_iter()
.filter(|grant| grant_id.is_none_or(|id| grant.grant_id == id))
.collect::<Vec<_>>();
@ -1214,15 +1203,11 @@ pub fn resident_self_grant_lifecycle_projection(
.as_ref()
.map(|prepared| prepared.grant.grant_id.as_str())
== Some(grant.grant_id.as_str());
let launchable = resident_self_grant_is_pending(&grant, &legacy_terminal_grant_ids)
&& !active
&& !prepared;
let launchable = resident_self_grant_is_pending(&grant) && !active && !prepared;
ResidentSelfGrantLifecycleProjection {
grant_id: grant.grant_id,
pressure_id: grant.pressure_id,
pressure_kind: grant.pressure_kind,
heartbeat_schedule_id: grant.heartbeat_schedule_id,
heartbeat_action_id: grant.heartbeat_action_id,
issued_at_millis: grant.issued_at_millis,
consumed_at_millis: grant.consumed_at_millis,
terminal_at_millis: grant.terminal_at_millis,
@ -1240,7 +1225,7 @@ pub fn resident_self_grant_lifecycle_projection(
pub fn live_resident_self_typed_request_ids(path: &Path) -> Result<BTreeSet<String>> {
let cache = state_cache(path)?;
let mut request_ids = BTreeSet::new();
for grant in cache.get_all::<ResidentSelfHeartbeatGrant>()? {
for grant in cache.get_all::<ResidentSelfGrant>()? {
if let Some(request) = resident_self_typed_request_ref(&grant)? {
request_ids.insert(request.request_id().to_string());
}
@ -1254,7 +1239,7 @@ pub fn verify_resident_self_grant_fulfillment(
grant_id: &str,
) -> Result<ResidentSelfGrantFulfillment> {
let grant = state_cache(resident_store)?
.get::<ResidentSelfHeartbeatGrant>(grant_id)?
.get::<ResidentSelfGrant>(grant_id)?
.ok_or_else(|| anyhow!("resident Self fulfillment check lost its grant"))?;
let request = resident_self_typed_request_ref(&grant)?;
let Some(request) = request else {
@ -1275,7 +1260,7 @@ fn resident_self_grant_typed_request_is_superseded(
grant_id: &str,
) -> Result<bool> {
let grant = state_cache(resident_store)?
.get::<ResidentSelfHeartbeatGrant>(grant_id)?
.get::<ResidentSelfGrant>(grant_id)?
.ok_or_else(|| anyhow!("resident Self supersession check lost its grant"))?;
let Some(crate::RuntimeTypedRequestRef::AdmittedModelDirection(request_id)) =
resident_self_typed_request_ref(&grant)?
@ -1291,7 +1276,7 @@ fn resident_self_grant_typed_request_is_superseded(
}
fn resident_self_typed_request_ref<'a>(
grant: &'a ResidentSelfHeartbeatGrant,
grant: &'a ResidentSelfGrant,
) -> Result<Option<crate::RuntimeTypedRequestRef<'a>>> {
Ok(match grant.pressure_kind.as_str() {
"repo-frontier-proposal-modeling" => {
@ -1346,7 +1331,7 @@ pub fn resident_self_typed_attempt_exists(
grant_id: &str,
) -> Result<bool> {
let grant = state_cache(resident_store)?
.get::<ResidentSelfHeartbeatGrant>(grant_id)?
.get::<ResidentSelfGrant>(grant_id)?
.ok_or_else(|| anyhow!("resident Self attempt check lost its grant"))?;
let Some(request) = resident_self_typed_request_ref(&grant)? else {
return Ok(false);
@ -1361,7 +1346,7 @@ pub fn recover_dead_resident_typed_worker(
recovered_at_millis: u64,
) -> Result<bool> {
let grant = state_cache(resident_store)?
.get::<ResidentSelfHeartbeatGrant>(grant_id)?
.get::<ResidentSelfGrant>(grant_id)?
.ok_or_else(|| anyhow!("typed worker death recovery lost its grant"))?;
let Some(request) = resident_self_typed_request_ref(&grant)? else {
return Ok(false);
@ -1496,7 +1481,7 @@ pub fn resident_self_grant_has_typed_request(
grant_id: &str,
) -> Result<bool> {
let grant = state_cache(resident_store)?
.get::<ResidentSelfHeartbeatGrant>(grant_id)?
.get::<ResidentSelfGrant>(grant_id)?
.ok_or_else(|| anyhow!("resident Self typed-request check lost its grant"))?;
Ok(resident_self_typed_request_ref(&grant)?.is_some())
}
@ -1799,7 +1784,7 @@ pub fn recover_receipt_free_dead_coordinator_session(
));
}
let grant = cache
.get::<ResidentSelfHeartbeatGrant>(&lease.grant_id)?
.get::<ResidentSelfGrant>(&lease.grant_id)?
.ok_or_else(|| anyhow!("coordinator death recovery lost its grant"))?;
if grant.consumed_at_millis.is_none()
|| grant.terminal_at_millis.is_some()
@ -1882,17 +1867,12 @@ pub fn recover_receipt_free_dead_coordinator_session(
Ok(Some(recovery))
}
pub fn pending_resident_self_grant(path: &Path) -> Result<Option<ResidentSelfHeartbeatGrant>> {
pub fn pending_resident_self_grant(path: &Path) -> Result<Option<ResidentSelfGrant>> {
let cache = state_cache(path)?;
let legacy_terminal_grant_ids = cache
.get_all::<ResidentSelfTerminalAck>()?
.into_iter()
.map(|ack| ack.grant_id)
.collect::<std::collections::BTreeSet<_>>();
let mut grants = cache
.get_all::<ResidentSelfHeartbeatGrant>()?
.get_all::<ResidentSelfGrant>()?
.into_iter()
.filter(|grant| resident_self_grant_is_pending(grant, &legacy_terminal_grant_ids))
.filter(resident_self_grant_is_pending)
.collect::<Vec<_>>();
grants.sort_by(|a, b| {
a.issued_at_millis
@ -1930,9 +1910,9 @@ pub fn resident_self_policy_digest(policy: &ResidentSelfPolicy) -> String {
fn supersede_unlaunched_resident_self_derived_grant(
path: &Path,
grant: &ResidentSelfHeartbeatGrant,
grant: &ResidentSelfGrant,
now_millis: u64,
) -> Result<ResidentSelfTerminalAck> {
) -> Result<ResidentSelfTerminalReceipt> {
if matches!(
grant.pressure_kind.as_str(),
"operator-objective" | "persona-feedback"
@ -1953,7 +1933,7 @@ fn supersede_unlaunched_resident_self_derived_grant(
));
}
let mut current_grant = cache
.get::<ResidentSelfHeartbeatGrant>(&grant.grant_id)?
.get::<ResidentSelfGrant>(&grant.grant_id)?
.ok_or_else(|| anyhow!("superseded continuation grant is missing"))?;
if current_grant != *grant {
return Err(anyhow!("superseded continuation grant authority changed"));
@ -1973,17 +1953,14 @@ fn supersede_unlaunched_resident_self_derived_grant(
grant.grant_id.as_str(),
grant.provenance_ref.as_str(),
]);
let ack = ResidentSelfTerminalAck {
schema_version: RESIDENT_SELF_ACK_SCHEMA_VERSION.into(),
ack_id: format!("resident-self-superseded-{}", grant.grant_id),
let terminal = ResidentSelfTerminalReceipt {
schema_version: RESIDENT_SELF_TERMINAL_RECEIPT_SCHEMA_VERSION.into(),
receipt_id: format!("resident-self-superseded-{}", grant.grant_id),
grant_id: grant.grant_id.clone(),
heartbeat_schedule_id: grant.heartbeat_schedule_id.clone(),
heartbeat_action_id: grant.heartbeat_action_id.clone(),
launch_digest,
coordinator_receipt_id: format!("resident-self-superseded-input-{}", grant.grant_id),
terminal_status: "superseded".into(),
completed_at_millis: now_millis,
consumed_by_heartbeat_at_millis: None,
private_state_exposed: false,
};
let envelopes = cache.snapshot_envelopes();
@ -2006,7 +1983,7 @@ fn supersede_unlaunched_resident_self_derived_grant(
let expected_grant = envelopes
.iter()
.find(|entry| {
entry.r#type == <ResidentSelfHeartbeatGrant as DatabaseEntry>::TYPE
entry.r#type == <ResidentSelfGrant as DatabaseEntry>::TYPE
&& entry.key == grant.grant_id
})
.cloned()
@ -2018,14 +1995,14 @@ fn supersede_unlaunched_resident_self_derived_grant(
let (state_entry, _) = cache.prepare_entry(RESIDENT_SELF_STATE_KEY, &state)?;
let (pressure_entry, _) = cache.prepare_entry(&pressure.pressure_id, &pressure)?;
let (grant_entry, _) = cache.prepare_entry(&current_grant.grant_id, &current_grant)?;
let (ack_entry, _) = cache.prepare_entry(&ack.ack_id, &ack)?;
let (terminal_entry, _) = cache.prepare_entry(&terminal.receipt_id, &terminal)?;
if !SingleFileMessagePackBackingStore::new(path).compare_and_swap_batch(
&[expected_state, expected_pressure, expected_grant],
vec![state_entry, pressure_entry, grant_entry, ack_entry],
vec![state_entry, pressure_entry, grant_entry, terminal_entry],
)? {
return Err(anyhow!("resident Self lost derived-grant supersession CAS"));
}
Ok(ack)
Ok(terminal)
}
pub fn prepare_resident_self_launch(
@ -2154,7 +2131,7 @@ pub fn prepare_resident_self_launch(
let grant_expected = snapshot
.iter()
.find(|entry| {
entry.r#type == <ResidentSelfHeartbeatGrant as DatabaseEntry>::TYPE
entry.r#type == <ResidentSelfGrant as DatabaseEntry>::TYPE
&& entry.key == grant.grant_id
})
.cloned()
@ -2405,7 +2382,7 @@ pub fn complete_resident_self_turn_after_death(
recovery: &crate::EpiphanyCoordinatorDeathRecovery,
now_millis: u64,
cooldown_seconds: u64,
) -> Result<ResidentSelfTerminalAck> {
) -> Result<ResidentSelfTerminalReceipt> {
if recovery.session_id
!= crate::coordinator_run_session_id(&lease.turn_id, Some(&lease.launch_digest))?
|| recovery.thread_id != lease.turn_id
@ -2439,30 +2416,27 @@ fn complete_resident_self_turn_with_terminal(
now_millis: u64,
cooldown_seconds: u64,
count_failure: bool,
) -> Result<ResidentSelfTerminalAck> {
) -> Result<ResidentSelfTerminalReceipt> {
let cache = state_cache(path)?;
let mut state = cache
.get::<ResidentSelfState>(RESIDENT_SELF_STATE_KEY)?
.ok_or_else(|| anyhow!("resident Self state missing at terminal ack"))?;
.ok_or_else(|| anyhow!("resident Self state missing at terminal receipt"))?;
if state.active_turn.as_ref() != Some(lease) {
return Err(anyhow!(
"resident Self active lease changed before terminal ack"
"resident Self active lease changed before terminal receipt"
));
}
let mut grant = cache
.get::<ResidentSelfHeartbeatGrant>(&lease.grant_id)?
.ok_or_else(|| anyhow!("resident Self grant missing at terminal ack"))?;
let ack = ResidentSelfTerminalAck {
schema_version: RESIDENT_SELF_ACK_SCHEMA_VERSION.into(),
ack_id: format!("resident-self-ack-{}", lease.grant_id),
.get::<ResidentSelfGrant>(&lease.grant_id)?
.ok_or_else(|| anyhow!("resident Self grant missing at terminal receipt"))?;
let terminal = ResidentSelfTerminalReceipt {
schema_version: RESIDENT_SELF_TERMINAL_RECEIPT_SCHEMA_VERSION.into(),
receipt_id: format!("resident-self-terminal-{}", lease.grant_id),
grant_id: lease.grant_id.clone(),
heartbeat_schedule_id: grant.heartbeat_schedule_id.clone(),
heartbeat_action_id: grant.heartbeat_action_id.clone(),
launch_digest: lease.launch_digest.clone(),
coordinator_receipt_id: terminal_id.to_string(),
terminal_status: terminal_status.to_string(),
completed_at_millis: now_millis,
consumed_by_heartbeat_at_millis: None,
private_state_exposed: false,
};
let envelopes = cache.snapshot_envelopes();
@ -2477,11 +2451,11 @@ fn complete_resident_self_turn_with_terminal(
let expected_grant = envelopes
.iter()
.find(|entry| {
entry.r#type == <ResidentSelfHeartbeatGrant as DatabaseEntry>::TYPE
entry.r#type == <ResidentSelfGrant as DatabaseEntry>::TYPE
&& entry.key == grant.grant_id
})
.cloned()
.ok_or_else(|| anyhow!("resident Self grant lost envelope at terminal ack"))?;
.ok_or_else(|| anyhow!("resident Self grant lost envelope at terminal receipt"))?;
state.active_turn = None;
state.last_coordinator_receipt_id = Some(terminal_id.to_string());
state.next_eligible_at_millis =
@ -2493,14 +2467,14 @@ fn complete_resident_self_turn_with_terminal(
grant.terminal_status = Some(terminal_status.to_string());
let (state_entry, _) = cache.prepare_entry(RESIDENT_SELF_STATE_KEY, &state)?;
let (grant_entry, _) = cache.prepare_entry(&grant.grant_id, &grant)?;
let (ack_entry, _) = cache.prepare_entry(&ack.ack_id, &ack)?;
let (terminal_entry, _) = cache.prepare_entry(&terminal.receipt_id, &terminal)?;
if !SingleFileMessagePackBackingStore::new(path).compare_and_swap_batch(
&[expected_state, expected_grant],
vec![state_entry, grant_entry, ack_entry],
vec![state_entry, grant_entry, terminal_entry],
)? {
return Err(anyhow!("resident Self lost terminal-ack CAS"));
return Err(anyhow!("resident Self lost terminal-receipt CAS"));
}
Ok(ack)
Ok(terminal)
}
pub fn cancel_resident_self_turn(
@ -2509,7 +2483,7 @@ pub fn cancel_resident_self_turn(
status: &str,
reason: &str,
now_millis: u64,
) -> Result<ResidentSelfTerminalAck> {
) -> Result<ResidentSelfTerminalReceipt> {
if !matches!(
status,
"brake-cancelled" | "shutdown-cancelled" | "timed-out" | "process-failed" | "unfulfilled"
@ -2527,7 +2501,7 @@ pub fn cancel_resident_self_turn(
return Err(anyhow!("resident Self cancellation lease changed"));
}
let mut grant = cache
.get::<ResidentSelfHeartbeatGrant>(&lease.grant_id)?
.get::<ResidentSelfGrant>(&lease.grant_id)?
.ok_or_else(|| anyhow!("resident Self cancellation grant missing"))?;
let mut pressure = cache
.get::<ResidentSelfPressure>(&grant.pressure_id)?
@ -2539,17 +2513,14 @@ pub fn cancel_resident_self_turn(
"resident Self cancellation pressure authority changed"
));
}
let ack = ResidentSelfTerminalAck {
schema_version: RESIDENT_SELF_ACK_SCHEMA_VERSION.into(),
ack_id: format!("resident-self-cancel-{}-{status}", lease.grant_id),
let terminal = ResidentSelfTerminalReceipt {
schema_version: RESIDENT_SELF_TERMINAL_RECEIPT_SCHEMA_VERSION.into(),
receipt_id: format!("resident-self-cancel-{}-{status}", lease.grant_id),
grant_id: lease.grant_id.clone(),
heartbeat_schedule_id: grant.heartbeat_schedule_id.clone(),
heartbeat_action_id: grant.heartbeat_action_id.clone(),
launch_digest: lease.launch_digest.clone(),
coordinator_receipt_id: format!("resident-self-runtime-{status}-{}", lease.grant_id),
terminal_status: status.into(),
completed_at_millis: now_millis,
consumed_by_heartbeat_at_millis: None,
private_state_exposed: false,
};
let envelopes = cache.snapshot_envelopes();
@ -2572,7 +2543,7 @@ pub fn cancel_resident_self_turn(
let expected_grant = envelopes
.iter()
.find(|entry| {
entry.r#type == <ResidentSelfHeartbeatGrant as DatabaseEntry>::TYPE
entry.r#type == <ResidentSelfGrant as DatabaseEntry>::TYPE
&& entry.key == grant.grant_id
})
.cloned()
@ -2589,68 +2560,23 @@ pub fn cancel_resident_self_turn(
let (state_entry, _) = cache.prepare_entry(RESIDENT_SELF_STATE_KEY, &state)?;
let (pressure_entry, _) = cache.prepare_entry(&pressure.pressure_id, &pressure)?;
let (grant_entry, _) = cache.prepare_entry(&grant.grant_id, &grant)?;
let (ack_entry, _) = cache.prepare_entry(&ack.ack_id, &ack)?;
let (terminal_entry, _) = cache.prepare_entry(&terminal.receipt_id, &terminal)?;
if !SingleFileMessagePackBackingStore::new(path).compare_and_swap_batch(
&[expected_state, expected_pressure, expected_grant],
vec![state_entry, pressure_entry, grant_entry, ack_entry],
vec![state_entry, pressure_entry, grant_entry, terminal_entry],
)? {
return Err(anyhow!("resident Self lost cancellation CAS"));
}
Ok(ack)
Ok(terminal)
}
pub fn pending_resident_self_ack_for(
path: &Path,
schedule_id: &str,
action_id: &str,
) -> Result<Option<ResidentSelfTerminalAck>> {
pub fn resident_self_terminal_receipts(path: &Path) -> Result<Vec<ResidentSelfTerminalReceipt>> {
Ok(state_cache(path)?
.get_all::<ResidentSelfTerminalAck>()?
.get_all::<ResidentSelfTerminalReceipt>()?
.into_iter()
.find(|ack| {
ack.heartbeat_schedule_id == schedule_id
&& ack.heartbeat_action_id == action_id
&& ack.consumed_by_heartbeat_at_millis.is_none()
}))
}
pub fn pending_resident_self_acks(path: &Path) -> Result<Vec<ResidentSelfTerminalAck>> {
Ok(state_cache(path)?
.get_all::<ResidentSelfTerminalAck>()?
.into_iter()
.filter(|ack| ack.consumed_by_heartbeat_at_millis.is_none())
.collect())
}
pub fn heartbeat_consume_resident_self_ack(
path: &Path,
ack_id: &str,
now_millis: u64,
) -> Result<()> {
let cache = state_cache(path)?;
let mut ack = cache
.get::<ResidentSelfTerminalAck>(ack_id)?
.ok_or_else(|| anyhow!("resident Self terminal ack is missing"))?;
if ack.consumed_by_heartbeat_at_millis.is_some() {
return Err(anyhow!("resident Self terminal ack was already consumed"));
}
let expected = cache
.snapshot_envelopes()
.into_iter()
.find(|entry| {
entry.r#type == <ResidentSelfTerminalAck as DatabaseEntry>::TYPE && entry.key == ack_id
})
.ok_or_else(|| anyhow!("terminal ack lost envelope"))?;
ack.consumed_by_heartbeat_at_millis = Some(now_millis);
let (replacement, _) = cache.prepare_entry(ack_id, &ack)?;
if !SingleFileMessagePackBackingStore::new(path)
.compare_and_swap_entry(&expected, replacement)?
{
return Err(anyhow!("heartbeat lost terminal ack CAS"));
}
Ok(())
}
pub fn retain_resident_self_lifecycles(
path: &Path,
retain_closed: usize,
@ -2666,23 +2592,20 @@ pub fn retain_resident_self_lifecycles(
.map(|pressure| (pressure.pressure_id.clone(), pressure))
.collect::<std::collections::HashMap<_, _>>();
let grants = cache
.get_all::<ResidentSelfHeartbeatGrant>()?
.get_all::<ResidentSelfGrant>()?
.into_iter()
.map(|grant| (grant.grant_id.clone(), grant))
.collect::<std::collections::HashMap<_, _>>();
let claims = cache.get_all::<ResidentSelfChildClaim>()?;
let mut closed = cache
.get_all::<ResidentSelfTerminalAck>()?
.get_all::<ResidentSelfTerminalReceipt>()?
.into_iter()
.filter_map(|ack| {
let grant = grants.get(&ack.grant_id)?;
.filter_map(|terminal| {
let grant = grants.get(&terminal.grant_id)?;
let pressure = pressures.get(&grant.pressure_id)?;
let terminal_grant = if grant.schema_version == RESIDENT_SELF_GRANT_SCHEMA_VERSION {
grant.terminal_at_millis == Some(ack.completed_at_millis)
&& grant.terminal_status.as_deref() == Some(ack.terminal_status.as_str())
} else {
grant.schema_version == "epiphany.resident_self.heartbeat_grant.v0"
};
let terminal_grant = grant.schema_version == RESIDENT_SELF_GRANT_SCHEMA_VERSION
&& grant.terminal_at_millis == Some(terminal.completed_at_millis)
&& grant.terminal_status.as_deref() == Some(terminal.terminal_status.as_str());
let inactive = state.active_turn.as_ref().map(|lease| &lease.grant_id)
!= Some(&grant.grant_id)
&& state
@ -2690,15 +2613,14 @@ pub fn retain_resident_self_lifecycles(
.as_ref()
.map(|prepared| &prepared.grant.grant_id)
!= Some(&grant.grant_id);
(ack.consumed_by_heartbeat_at_millis.is_some()
&& grant.consumed_at_millis.is_some()
(grant.consumed_at_millis.is_some()
&& terminal_grant
&& pressure.status == "consumed"
&& pressure.consumed_by_grant_id.as_deref() == Some(grant.grant_id.as_str())
&& inactive)
.then_some((
ack.completed_at_millis,
ack.ack_id.clone(),
terminal.completed_at_millis,
terminal.receipt_id.clone(),
grant.grant_id.clone(),
))
})
@ -2733,8 +2655,8 @@ pub fn retain_resident_self_lifecycles(
.ok_or_else(|| anyhow!("closed resident lifecycle lost its pressure"))?;
for (entry_type, key) in [
(ResidentSelfPressure::TYPE, pressure.pressure_id.as_str()),
(ResidentSelfHeartbeatGrant::TYPE, grant_id.as_str()),
(ResidentSelfTerminalAck::TYPE, ack_id.as_str()),
(ResidentSelfGrant::TYPE, grant_id.as_str()),
(ResidentSelfTerminalReceipt::TYPE, ack_id.as_str()),
] {
expected.push(
snapshot
@ -2956,15 +2878,13 @@ mod coordinator_launch_contract_tests {
},
)?;
let policy = test_policy(temp.path(), runtime_store);
let grant = ResidentSelfHeartbeatGrant {
let grant = ResidentSelfGrant {
schema_version: RESIDENT_SELF_GRANT_SCHEMA_VERSION.into(),
grant_id: "legacy-receipt-grant".into(),
pressure_id: "legacy-receipt-pressure".into(),
pressure_kind: RESIDENT_SELF_COORDINATOR_CONTINUATION_PRESSURE_KIND.into(),
provenance_ref: "cultcache://coordinator-receipt/old-receipt".into(),
objective: "Replay the old receipt action".into(),
heartbeat_schedule_id: "schedule".into(),
heartbeat_action_id: "action".into(),
issued_at_millis: 1,
consumed_at_millis: None,
private_state_exposed: false,
@ -2974,18 +2894,6 @@ mod coordinator_launch_contract_tests {
assert!(resident_coordinator_binding_for_grant(&policy, &grant)?.is_none());
Ok(())
}
#[test]
fn domain_materialization_has_no_scheduler_pressure_writer() {
let source = include_str!("resident_self.rs");
let body = source
.split("pub fn materialize_resident_self_domain_obligations")
.nth(1)
.and_then(|tail| tail.split("fn resident_self_current_work_objective").next())
.expect("domain materialization function must remain inspectable");
assert!(!body.contains("ResidentSelfPressure"));
assert!(!body.contains("enqueue_resident_self_pressure"));
}
}
#[cfg(test)]
@ -3020,25 +2928,20 @@ mod atlas_pressure_tests {
Ok(())
}
fn atlas_grant(
lane: crate::AtlasImpactLane,
proposal_id: uuid::Uuid,
) -> ResidentSelfHeartbeatGrant {
fn atlas_grant(lane: crate::AtlasImpactLane, proposal_id: uuid::Uuid) -> ResidentSelfGrant {
let (kind, lane_name) = match lane {
crate::AtlasImpactLane::Modeling => {
(RESIDENT_SELF_ATLAS_MODELING_PRESSURE_KIND, "Modeling")
}
crate::AtlasImpactLane::Soul => (RESIDENT_SELF_ATLAS_SOUL_PRESSURE_KIND, "Soul"),
};
ResidentSelfHeartbeatGrant {
ResidentSelfGrant {
schema_version: RESIDENT_SELF_GRANT_SCHEMA_VERSION.into(),
grant_id: format!("atlas-test-grant-{proposal_id}"),
pressure_id: format!("{kind}-{proposal_id}"),
pressure_kind: kind.into(),
provenance_ref: format!("{RESIDENT_SELF_ATLAS_IMPACT_PROVENANCE_PREFIX}{proposal_id}"),
objective: resident_self_atlas_objective(lane_name, proposal_id),
heartbeat_schedule_id: "atlas-test-schedule".into(),
heartbeat_action_id: "atlas-test-action".into(),
issued_at_millis: 100,
consumed_at_millis: Some(200),
private_state_exposed: false,
@ -3047,7 +2950,7 @@ mod atlas_pressure_tests {
}
}
fn atlas_lease(grant: &ResidentSelfHeartbeatGrant) -> ResidentSelfTurnLease {
fn atlas_lease(grant: &ResidentSelfGrant) -> ResidentSelfTurnLease {
ResidentSelfTurnLease {
turn_id: "atlas-test-turn".into(),
wake: ResidentSelfWake::Explicit {
@ -3117,7 +3020,7 @@ mod atlas_pressure_tests {
Some("launchVerification")
);
let grant = heartbeat_issue_resident_self_grant(&store, "atlas", "wake", 102)?
let grant = issue_resident_self_grant(&store, 102)?
.expect("oldest Atlas pressure should receive one resident grant");
assert_eq!(
grant.pressure_kind,
@ -3155,9 +3058,7 @@ mod atlas_pressure_tests {
&atlas_decision(crate::AtlasImpactLane::Modeling, active_id),
100,
)?;
assert!(
heartbeat_issue_resident_self_grant(&active_store, "atlas", "active", 101)?.is_none()
);
assert!(issue_resident_self_grant(&active_store, 101)?.is_none());
let prepared_store = test_store("prepared");
let prepared_id = uuid::Uuid::new_v4();
@ -3187,10 +3088,7 @@ mod atlas_pressure_tests {
&atlas_decision(crate::AtlasImpactLane::Soul, prepared_id),
100,
)?;
assert!(
heartbeat_issue_resident_self_grant(&prepared_store, "atlas", "prepared", 101)?
.is_none()
);
assert!(issue_resident_self_grant(&prepared_store, 101)?.is_none());
assert!(pending_resident_self_pressure(&active_store)?);
assert!(pending_resident_self_pressure(&prepared_store)?);
std::fs::remove_file(active_store)?;
@ -3228,7 +3126,7 @@ mod atlas_pressure_tests {
assert!(state.active_turn.is_none());
assert_eq!(state.next_eligible_at_millis, 12_000);
let grant = state_cache(&store)?
.get::<ResidentSelfHeartbeatGrant>(&grant.grant_id)?
.get::<ResidentSelfGrant>(&grant.grant_id)?
.expect("completed Atlas grant");
assert_eq!(grant.terminal_at_millis, Some(5_000));
std::fs::remove_file(store)?;

View file

@ -26,8 +26,6 @@ use crate::eyes_gateway::EYES_SOURCE_LOOKUP_RECEIPT_TYPE;
use crate::eyes_gateway::EyesEvidencePacket;
use crate::eyes_gateway::EyesSourceLookupReceipt;
use crate::hands_gateway::*;
use crate::heartbeat_state::HEARTBEAT_STATE_SCHEMA_VERSION;
use crate::heartbeat_state::HEARTBEAT_STATE_TYPE;
use crate::organ_dependencies::EpiphanyLaunchOrganContract;
use crate::repo_model_gateway::{
REPO_FRONTIER_AUTONOMOUS_PROPOSAL_BINDING_CONTRACT,
@ -13945,20 +13943,6 @@ fn epiphany_mutation_contracts() -> Vec<CultNetDocumentMutationContract> {
"OpenAI terminal receipts are provider-specific evidence behind the model receipt.",
],
),
mutation_contract(
HEARTBEAT_STATE_TYPE,
HEARTBEAT_STATE_SCHEMA_VERSION,
vec![
CultNetDocumentOperation::Snapshot,
CultNetDocumentOperation::ReceiptWatch,
],
CultNetMutationAuthority::ReadOnly,
vec![],
vec![],
vec![
"Heartbeat is internal scheduling physiology for Resident Self grants and Persona turns; external callers may inspect it but cannot steer it.",
],
),
mutation_contract(
STATE_LEDGER_STORE_TYPE,
STATE_LEDGER_SCHEMA_VERSION,

View file

@ -290,17 +290,4 @@ mod tests {
assert!(acquire_semantic_projector_singleton(&modeling).is_ok());
Ok(())
}
#[test]
fn authority_body_has_no_public_raw_session_or_session_local_windows_lock() {
let source = include_str!("semantic_projector_service.rs");
let public_session = ["pub struct Local", "IdunnSemanticProjectorSession"].concat();
let public_singleton = ["pub fn acquire_semantic", "_projector_singleton"].concat();
let local_namespace = ["Local", "\\\\EpiphanySemanticProjector"].concat();
assert!(!source.contains(&public_session));
assert!(!source.contains(&public_singleton));
assert!(!source.contains(&local_namespace));
assert!(source.contains("Global\\\\EpiphanySemanticProjector"));
assert!(source.contains("pub struct SemanticProjectorServiceBody"));
}
}

View file

@ -290,22 +290,3 @@ fn planning_decision(
};
Some(value)
}
#[cfg(test)]
mod authority_tests {
#[test]
fn source_has_no_aggregate_or_latest_lane_routing() {
let source = include_str!("coordinator_decision.rs");
let production = source.split("#[cfg(test)]").next().unwrap_or(source);
for forbidden in [
"EpiphanyThreadState",
"accepted_after",
"latest_result",
"finding_signals",
"state_revision",
"coordinator_run_receipt",
] {
assert!(!production.contains(forbidden));
}
}
}

View file

@ -220,19 +220,4 @@ mod tests {
assert_eq!(jobs[2].status, EpiphanyJobStatus::Blocked);
assert!(jobs.iter().all(|job| job.runtime_job_id.is_none()));
}
#[test]
fn source_contains_no_aggregate_job_or_runtime_link_owner() {
let source = include_str!("jobs.rs");
let production = source.split("#[cfg(test)]").next().unwrap_or(source);
for forbidden in [
"EpiphanyThreadState",
"EpiphanyJobBinding",
"EpiphanyRuntimeLink",
"latest_runtime_link",
"state_revision",
] {
assert!(!production.contains(forbidden));
}
}
}

View file

@ -120,20 +120,4 @@ mod tests {
duplicate.evidence.push(evidence("evidence-a"));
assert!(duplicate.validate().is_err());
}
#[test]
fn role_decision_sources_have_no_generic_patch_authority() {
let retired_type = ["Epiphany", "Role", "State", "Patch", "Document"].concat();
let retired_field = ["state", "Patch"].concat();
for source in [
include_str!("../agent_launch.rs"),
include_str!("../current_work.rs"),
include_str!("../runtime_spine.rs"),
include_str!("role_result.rs"),
] {
let production = source.split("#[cfg(test)]").next().unwrap_or(source);
assert!(!production.contains(&retired_type));
assert!(!production.contains(&retired_field));
}
}
}

View file

@ -314,21 +314,3 @@ pub fn render_role_board_note(
"Mind {state_status}; {active} role lane(s) carry current work; continuity recommendation {recommendation:?}."
)
}
#[cfg(test)]
mod tests {
#[test]
fn source_has_no_generic_lane_or_aggregate_authority() {
let source = include_str!("role_board.rs");
let production = source.split("#[cfg(test)]").next().unwrap_or(source);
for forbidden in [
"EpiphanyThreadState",
"binding_id",
"acceptance_receipt",
"state_revision",
"epiphany.coordinator.role.launch",
] {
assert!(!production.contains(forbidden));
}
}
}

View file

@ -106,7 +106,6 @@ pub fn required_packaged_release_binaries(target_triple: &str) -> Vec<(&'static
("repository-body", file_name("epiphany-repository-body")),
("host-identity", file_name("epiphany-host-identity")),
("swarm", file_name("epiphany-swarm")),
("heartbeat", file_name("epiphany-heartbeat-store")),
("runtime-spine", file_name("epiphany-runtime-spine")),
(
"persona-feedback-ingress",
@ -632,7 +631,6 @@ fn required_release_build_target(role: &str) -> Result<(&'static str, &'static s
"repository-body" => Ok(("epiphany-core", "epiphany-repository-body")),
"host-identity" => Ok(("epiphany-core", "epiphany-host-identity")),
"swarm" => Ok(("epiphany-core", "epiphany-swarm")),
"heartbeat" => Ok(("epiphany-core", "epiphany-heartbeat-store")),
"runtime-spine" => Ok(("epiphany-core", "epiphany-runtime-spine")),
"persona-feedback-ingress" => Ok(("epiphany-core", "epiphany-persona-feedback-ingress")),
"persona-service" => Ok(("epiphany-openai-runtime", "epiphany-persona-service")),
@ -1259,7 +1257,6 @@ mod tests {
.collect::<BTreeSet<_>>();
for role in [
"swarm",
"heartbeat",
"runtime-spine",
"state-steward",
"coordinator",
@ -1277,10 +1274,6 @@ mod tests {
required_release_build_target("swarm").unwrap(),
("epiphany-core", "epiphany-swarm")
);
assert_eq!(
required_release_build_target("heartbeat").unwrap(),
("epiphany-core", "epiphany-heartbeat-store")
);
assert_eq!(
required_release_build_target("runtime-spine").unwrap(),
("epiphany-core", "epiphany-runtime-spine")

View file

@ -266,9 +266,9 @@ authority.
These are distinct authorities and must not be collapsed into one state enum:
- Heartbeat owns scheduling pressure and terminal acknowledgement consumption.
- Resident Self owns grant, coordinator lease, settlement, cooldown, and
requeue.
- Resident Self owns typed pressure, exact grant creation, prepared and active
launch exclusion, coordinator lease, settlement, cooldown, retry, terminal
receipts, and bounded lifecycle retention.
- Coordinator process owns its incarnation receipt or exact-death closure.
- Runtime worker attempt owns immutable launch, process claim, activation,
typed result, job result, and terminal attempt classification.
@ -279,19 +279,13 @@ The attempt aggregate centralizes exact request association and typed terminal
classification. Archived fulfilled attempts must retain recoverable decision
authority, not merely an ID/digest tombstone.
Heartbeat is scheduling physiology only. Exact `d1685df8` deletes its
personality, appraisal, rumination, dreaming, void-routine cognition, and
memory-graph paths. Exact `39dd9fdb` deletes the parallel agent-memory
identity/generation store, utterance vector, and generic self-patch admission.
When no explicit typed pressure exists, heartbeat sleeps and authors no Mind
state.
Exact `2164bd0a` removes the unowned scheduler generality. Heartbeat has one
live participant: Resident Self. Ghostlight scene scheduling, generic
tick/pump/heat/complete controls, adaptive pacing, initiative heat, and their
public intent schemas are gone. Modeling, Eyes, Hands, Soul, and Imagination
route from typed current-work obligations and never acquire idle heartbeat
lanes.
The heartbeat scheduler does not exist. Resident Self directly selects the
oldest pending pressure when no grant, prepared launch, or active lease exists.
Grant creation consumes that exact pressure through one batch CAS. Terminal
settlement writes the final receipt and terminal grant state atomically; there
is no acknowledgement consumer, scheduler history, pacing head, stale repair,
or cross-store reconciliation loop. A loop iteration is physiology only and
authors no state when there is no unresolved obligation.
Persona owns a separate keyed social corpus. Each mention, immutable turn
request, terminal receipt, quarantine record, retention head, and retention
@ -299,7 +293,8 @@ plan has its own CultCache identity. The Persona daemon derives an exact request
from pending mentions, reserves them through batch CAS, and cites that request
envelope as the observed source of its admitted pass input. Failed turns make
the mention pending with the terminal receipt bound into its next deterministic
request identity. Heartbeat cannot launch, block, or terminalize Persona work.
request identity. Resident Self cannot launch, block, or terminalize Persona
work.
## RepoModel and semantic projection

View file

@ -3,7 +3,7 @@
Updated: 2026-08-23
Branch: `codex/epiphany-shakedown-live`
Latest committed implementation cut: `39dd9fdb`
Current worktree: heartbeat concurrency-domain audit; Ox17 remains paused
Current worktree: final heartbeat deletion and behavioral-test audit; Ox17 remains paused
## Orientation
@ -28,8 +28,8 @@ authorize the next capstone or Gate 1.
`de0fc6b360ce03493b13208d917dc8349801f03364617d966152c85846c47482`.
- Model provider: OpenRouter `stealth/ox-alpha`, selected explicitly and
injected through the root-owned systemd credential boundary.
- `epiphany.service`, `epiphany-heartbeat.service`, and
`epiphany-swarm.service` are inactive with zero restarts after the hard epoch
- `epiphany.service` and `epiphany-swarm.service` are inactive with zero
restarts after the hard epoch
refusal rollback. The d2ca stores and swarm brake remain historical
production state; no newer package is admitted there.
- Idunn source `8b972715c47731f2418d0c423cb0dd2076940bd7` is provenance-exact and
@ -44,6 +44,30 @@ authorize the next capstone or Gate 1.
## What just landed
The current uncommitted subtraction removes the final heartbeat scheduler.
- Resident Self directly owns typed pressure selection, pressure-to-grant
batch CAS, prepared and active launch exclusion, leases, terminal receipts,
retry, and bounded retention. Terminal receipts are final records; no second
daemon consumes acknowledgements.
- Persona owns its separate keyed social store. The swarm receives that store
directly and cannot use a scheduler singleton to launch, block, retain, or
terminalize Persona work.
- The heartbeat Rust module, six submodules, CLI binary, release role, schema,
store contract, systemd unit, deployment lifecycle, readiness checks,
artifact directory, pacing/history state, stale repair, and old cross-store
reconciliation are deleted. Old heartbeat grants, acknowledgements, and
Resident Self v0 stores fail the writable epoch check.
- Native core passes 426/426 after the first fake-test pruning slice. Twenty-
one tests across library and runtime targets that only inspected source
spelling, copied removed fields, or exercised one-line helper truth tables
are gone. Daemon supervisor passes 29/29, coordinator 8/8, status 1/1, and
swarm 4/4. Exact CAS, hostile substitution, concurrency, lifecycle,
readiness, and re-entry proofs remain.
- The gamecult-ops contract removes 52 net lines, and Git Bash syntax plus the
deployment outage-order check pass. Bootstrap retains one deliberate cleanup
line to remove an obsolete installed heartbeat unit.
The global thread-state authority is gone.
- `1662a012` makes coordinator policy a pure projection over keyed Mind,

View file

@ -3,7 +3,7 @@
This folder is the canonical paperwork shrine for Epiphany's shared state
contracts.
If a Persona field, Mind document, or heartbeat scheduling surface matters
If a Persona field or Mind document matters
enough to steer the machine, it should have a receipt here instead of living
only in one Rust struct or one developer's damp recollection.
@ -19,8 +19,6 @@ only in one Rust struct or one developer's damp recollection.
companion custom-label fields.
- [cultnet/epiphany.work_organ_state.v0.schema.json](./cultnet/epiphany.work_organ_state.v0.schema.json):
light function-shaped state for Epiphany internal work organs.
- [heartbeat-state-schema.md](./heartbeat-state-schema.md):
typed initiative and routine-state contract for the swarm heartbeat organ.
- [cultnet/README.md](./cultnet/README.md):
published CultNet-facing state, surface, intent, and receipt schemas that
Aquarium and other runtimes can discover through Epiphany's schema-catalog
@ -30,7 +28,6 @@ only in one Rust struct or one developer's damp recollection.
The living implementation is in code:
- [heartbeat_state.rs](/E:/Projects/EpiphanyAgent/epiphany-core/src/heartbeat_state.rs)
- [mind_documents.rs](/E:/Projects/EpiphanyAgent/epiphany-core/src/mind_documents.rs)
The rule is simple:
@ -45,7 +42,6 @@ When changing any of the following, update this folder in the same pass:
- canonical organ-state or Persona family names
- standing role trait names
- heartbeat store shape or pacing semantics
- schema version identifiers
If a change lands in code without a matching receipt here, assume the machine

View file

@ -1,50 +0,0 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://gamecult.dev/epiphany/cultnet/epiphany.agent-heartbeat.schema.json",
"title": "Epiphany Heartbeat State",
"description": "Typed scheduling physiology for Resident Self grants.",
"type": "object",
"additionalProperties": false,
"properties": {
"schemaVersion": {
"const": "epiphany.agent_heartbeat.v1"
},
"targetHeartbeatRate": {
"type": "number"
},
"sceneClock": {
"type": "number"
},
"selectionPolicy": {
"type": "object",
"additionalProperties": true
},
"pacingPolicy": {
"type": "object",
"additionalProperties": true
},
"participants": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": true
}
},
"history": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": true
}
}
},
"required": [
"schemaVersion",
"targetHeartbeatRate",
"sceneClock",
"selectionPolicy",
"pacingPolicy",
"participants",
"history"
]
}

View file

@ -257,17 +257,6 @@
"title": "Epiphany Work Organ State v0",
"path": "epiphany.work_organ_state.v0.schema.json"
},
{
"schemaId": "https://gamecult.dev/epiphany/cultnet/epiphany.agent-heartbeat.schema.json",
"kind": "document_payload",
"wireContracts": [
"cultnet.schema.v0"
],
"schemaVersion": "epiphany.agent_heartbeat.v1",
"documentType": "epiphany.agent_heartbeat",
"title": "Epiphany Heartbeat State",
"path": "epiphany.agent-heartbeat.schema.json"
},
{
"schemaId": "https://gamecult.dev/epiphany/cultnet/epiphany.state-ledger.schema.json",
"kind": "document_payload",

View file

@ -1,41 +0,0 @@
# Heartbeat State Schema
Epiphany's heartbeat store is scheduling physiology. It decides when an
already-existing typed obligation may receive a turn; it does not decide what
the organism believes or invent cognition to fill idle time.
The executable contract lives in
`epiphany-core/src/heartbeat_state/heartbeat_documents.rs` and the scheduling
law lives in `heartbeat_state.rs` and `heartbeat_store.rs`.
## Store identity
- document type: `epiphany.agent_heartbeat`
- schema version: `epiphany.agent_heartbeat.v1`
- key: `default`
The singleton carries Resident Self scheduler readiness, one pending turn, and
bounded scheduling history. Persona social state is not part of this document.
Pending mentions, immutable turn requests, terminal receipts, quarantine
records, and retention state are keyed CultCache documents owned by Persona.
## Scheduling invariants
- An existing pending turn cannot be scheduled again.
- Cooldown begins after terminal completion, not at launch.
- Resident Self pressure may wake the coordinator; heartbeat does not create
that pressure.
- The swarm brake prevents new Resident Self cognition while allowing terminal
acknowledgement and recovery physiology to settle.
- When no typed obligation exists, the scheduler sleeps.
Heartbeat owns no personality, mood, appraisal, rumination, dreaming, memory
graph, utterance vector, or generic self-modification surface. Durable Persona
memory and social interpretation are keyed Mind documents admitted through the
Persona decision context. Persona derives its own current turn from pending
keyed social documents; heartbeat cannot launch, block, or terminalize it. Work
for Modeling, Eyes, Hands, Soul, and Imagination comes from the pure
current-work projection over typed state obligations.
JSON emitted by the heartbeat CLI is an operator artifact only. CultCache
documents remain authority.

Binary file not shown.

View file

@ -48,10 +48,10 @@ current_status:
package_baseline: Exact d2ca66301fb6af4e7d2d27fff0b772b0f0fccdf4 remains the admitted production source and current symlink target. Its native serialized full-workspace gate passed with receipt SHA-256 de0fc6b360ce03493b13208d917dc8349801f03364617d966152c85846c47482. Idunn sealed 26 root-owned non-writable binaries plus release-witness.ccmp as release sha256-46407552b4a0937f63d2b7f2bd09a1dacb89d671a6e3807c97209159541aef06; witness-file SHA-256 is 348785ffb0fc3130d3b4538329870c6e6f8442a8da4a01b32fa9b7ffb1f01357. Source 9b9b5c85 also passed Idunn's native test gate under receipt SHA-256 6c6a71359f8c31297419665d2872f6982a89f84e197d71fc5b36a7fc86216093 and remains sealed as release sha256-30a910785a380224bbdbd56a7b742c8d058fa70cb72dab3562e3907903e0d191 with witness-file SHA-256 75bf52d31c56ea3b08e1a702273301c1db12a4b1ffae45e3d948ce8a29b50e1d. A foreign-target helper restarted Idunn during the Epiphany transaction; rollback restored d2ca, removed deployment.env, and left production Epiphany units inactive. OpenRouter model stealth/ox-alpha and its systemd-injected credential are readiness-proven. Idunn is active with zero restarts and its deployment brake is engaged.
summary: The keyed RepoModel hard cut, decision-context foundation, Model Atlas code slice, complete Body, Proposal, frontier-verdict Modeling, frontier Research, frontier Verification, frontier Planning/PlanMind, Reorientation, and Persona decision-context lifecycles are landed on codex/epiphany-shakedown-live. Keyed Mind plus exact runtime receipts own coordinator presence, work discovery, deterministic launch, job-bound continuation, review, typed failure, semantic admission, and retained decision reachability. Exact 07d891ba and ec1431ff delete the persisted/global thread aggregate and its generic coordinator/prompt/revision surfaces. Exact f7948795 deletes MindGatewayReview, MindStateCommitReceipt v0, the generic interpreter prompt, runtime registration/read APIs, false thought/state-effect/adoption CultNet mouths, and stale launch proof profiles. CultMesh now projects only reasoning basis, decision context, and exact Mind commit receipt, read-only. Exact 1c9aafd8 seals the terminal decision context before model-backed frontier-Planning failure and binds the typed failure plus generic job result to it. Exact e0e75a30 deletes the residual aggregate role patch/parser/policy surface, gives Research one closed typed decision, and deletes the unowned generic Imagination planning patch. Exact 12b1b285 makes runtime model/tool binding validation the shared owner for execution and decision sealing; hostile provider, receipt, and stored-binding substitutions refuse, while retained basis/context audit survives transcript-delta deletion. Exact 01602fd3 advances Mind/runtime to v2/v1 and refuses historical or split writable identity pairs before registered writers see them. Exact 26b6a5bf proves simultaneous disjoint Modeling writes, simultaneous Verification+Modeling admission, same-identity conflict, exact replay, and reopened graph assembly; raw mutation primitives are crate-private. Exact 7c2ebd81 moves Persona state-note admission from the legacy agent-memory aggregate into keyed Mind documents and proves a concrete Hands consequence can commit simultaneously. Exact 79c0e373 seals the assembled Persona input and observed source versions in Mind before inference. Exact d3300bba authenticates that input through its one exact Mind commit, refuses naked typed documents, re-enters without external observation or model inference, and proves current-work reconstruction is identical and read-only across Launch, Wait, completed, and post-Research boundaries. No compatibility reader, bootstrap aggregate, schema migrator, or dual reader survives. The source-level Decision-Auditable Concurrent Mind migration is accepted; the fresh exact-package capstone remains open. The machine remains a supervised engineering alpha; Model Atlas operational Gate 1 stays paused.
public_positioning: docs/positioning.md is the current public claim owner. Epiphany is the organizational layer above frontier AI workers; global coherence collapse is the primary failure; prompts are bounded projections of governed organizational state; humans retain purpose, judgment, authority, disagreement, exceptions, and acceptance. GameCult is the forge and longitudinal evidence program for a target federation of one locally owned Epiphany project organism per living project Body. Model Atlas carries typed offers, claims, evidence, and derived technical relationships; Bifrost is the intended external work, membership, governance, attribution, and identity-linked receipt rail. Public Persona relationships and visitor surfaces are intended social infrastructure, while private Mind and consequence authority remain local. The fleet, real three-repository Gate 1, complete Bifrost attribution, sustained autonomous production, visitor experience, and external design-partner pilots remain gated alpha targets.
latest_phase: The fresh-package Ox17 lane remains paused for source subtraction. Exact 1d5a1f17 removed 19,252 net non-vendor lines, exact 387afe49 removed 25,317, exact bf516f99 removed 2,701, exact 360073a3 removed 1,726, exact d1685df8 removed 4,389, and exact 39dd9fdb removes 6,433 net lines. The latest cuts delete heartbeat-authored personality, appraisal, rumination, dreaming, void-routine cognition, the aggregate agent-memory identity/generation store, the 64-float utterance vector, generic selfPatch decoding/admission, phantom sleep-distillation contracts, stale aggregate/thread schemas, and tests that only copied DTOs or inspected static contract vectors. Heartbeat now schedules explicit pressure only; keyed Mind owns durable Persona memory. Native core check passes, the library suite passes 455/455, and the provider-runtime suite passes 27/27. No Idunn build, package, deployment, or Ox root is authorized yet. Ox16 remains sealed historical evidence and must never resume.
persona_social_cut: The current source cut removes Persona from heartbeat authority. Pending mentions, immutable requests, terminal receipts, quarantine records, retention heads, and retention plans are separate keyed CultCache documents. Persona derives its own deterministic work identity, reserves and terminalizes through exact batch CAS, cites the exact request envelope as pass-input provenance, and retries failed mentions without a timestamp or global revision. The old heartbeat bridge and terminal receipt types have no writable compatibility path. Core passes 448/448; the heartbeat, Persona, and swarm binaries check; Persona re-entry passes 1/1.
latest_phase: The fresh-package Ox17 lane remains paused for source subtraction. The current worktree deletes the final heartbeat scheduler module, binary, schema, store, service, deployment lifecycle, artifact tree, stale repair, pacing/history state, and acknowledgement consumer. Resident Self now owns pressure-to-grant CAS, launch exclusion, leases, terminal receipts, retry, and bounded retention directly; Persona owns its disjoint keyed social corpus. The hard-cut Resident Self epoch refuses old heartbeat grants, acknowledgements, and v0 state. After removing twenty-one source-spelling, constructor-copy, and one-line-helper tests across library and runtime targets, native core passes 426/426; daemon supervisor passes 29/29, coordinator 8/8, status 1/1, and swarm 4/4. No Idunn build, package, deployment, or Ox root is authorized yet. Ox16 remains sealed historical evidence and must never resume.
persona_social_cut: Pending mentions, immutable requests, terminal receipts, quarantine records, retention heads, and retention plans are separate keyed CultCache documents. Persona derives its own deterministic work identity, reserves and terminalizes through exact batch CAS, cites the exact request envelope as pass-input provenance, and retries failed mentions without a timestamp or global revision. No scheduler can launch, block, retain, or provide pass provenance for Persona work.
operational_state:
local_worktree: subtraction_commit_360073a3_pushed_modeling_only_semantic_projection_verified_ox17_and_deployment_paused
local_worktree: final_heartbeat_scheduler_and_fake_test_subtraction_in_progress_ox17_and_deployment_paused
docker_engine_access: starfire_compiler_retired_all_yggdrasil_epiphany_transient_containers_volumes_package_roots_and_per_run_test_target_removed_future_idunn_epiphany_builds_native_rust_1_95_0_via_gamecult_ops_89f0d78
operator_wireguard: restored
starfire_persona_permit_firewall: persistent_exact_gamecult_ops_6ee08a4
@ -91,8 +91,8 @@ current_status:
starfire_linux_modeling_peak: 109887488_bytes_resident_body_plus_modeling_circuit
yggdrasil_epiphany_service_contract: idunn_source_change_owner_eve_and_local_typed_operator_surfaces_no_discord_control_plane
resident_physiology: historical_c011_exact_465af24d_zero_zombie_proof_accepted_current_containers_exited_255
unresolved_authority: Persona social state is now a keyed CultCache corpus owned and scheduled by Persona. Heartbeat cannot launch, block, terminalize, retain, or provide pass provenance for Persona. The remaining heartbeat singleton has only Resident Self, yet still carries selection, pacing, history, artifact retention, stale repair, and cross-store grant/ack reconciliation machinery. A one-participant scheduler may no longer protect an invariant that Resident Self does not already own. Fresh exact-package Ox17 and Model Atlas Gate 1 remain paused until this scheduler audit closes. Bifrost remains Persona and governed external-consequence transport. Historical c011 and partial Gate roots stay untouched/read-only.
next_action: Map the Resident Self grant lifecycle from typed pressure through grant, lease, launch, terminal ack, and retention. Decide whether any heartbeat state remains necessary. If Resident Self already owns exclusion and terminality, replace the singleton scheduler with a stateless physiological pulse and delete heartbeat state, selection/projection, artifact retention, stale repair, and tests that only preserve those mechanisms. Do not run a full-workspace compile, package, deployment, or Ox root before this subtraction audit closes.
unresolved_authority: The final heartbeat scheduler authority is gone. The next corrosion front is the test/code feedback loop: source-spelling assertions, constructor copies, one-line helper truth tables, and tests that preserve superseded shapes can make deletions expensive without protecting behavior. Twenty-one such tests are already removed in this worktree; the remaining source-inspection concentration is primarily the daemon supervisor and workspace-coverage service. Fresh exact-package Ox17 and Model Atlas Gate 1 remain paused until the subtraction pass closes. Bifrost remains Persona and governed external-consequence transport. Historical c011 and partial Gate roots stay untouched/read-only.
next_action: Land the verified final-heartbeat cut. Then map the remaining daemon-supervisor and workspace-coverage source-inspection tests to actual authority invariants. Delete those with no behavioral witness; replace only critical sequencing claims with transition-level tests at the consequence layer. Do not run a local full-workspace compile, package, deployment, or Ox root.
active_subgoals:
- Complete the fresh exact-package Decision-Auditable Concurrent Mind capstone before any further operational Gate run.

View file

@ -51,17 +51,20 @@ and janitors may reduce the symptom but cannot substitute for deleting code.
## Immediate audit order
1. In progress: exact `2164bd0a` deletes Ghostlight scheduling, generic manual
heartbeat mutation controls, five dead schemas, adaptive pacing/initiative
heat, five fake organ lanes, and eight tests that only kept those surfaces
alive. Heartbeat now schedules only Resident Self and Persona. Next split
Persona queue/turn/blocked-pressure/retention members into keyed documents;
no shared vector may remain in the scheduler CAS identity.
1. Done in the current worktree: Persona social state is keyed and separately
owned; the final heartbeat scheduler module, binary, schema, service, store,
deployment path, pacing/history, stale repair, and acknowledgement consumer
are deleted. Resident Self owns the complete grant lifecycle directly.
2. Done: delete `runtime_store_migration`, its receipt registration, and its
test. The approved migration has no writable migrator.
3. Done: delete the local PowerShell operator wrapper, direct Persona/operator
binaries, shipped `verse-query` control plane, and orphan Rider integration.
4. Done: reduce `epiphany-core` from 30 binary targets to the one native
compaction helper. Runtime executables belong to the release bundle.
5. Run focused tests after each cut. Only after source subtraction stabilizes
5. In progress: delete tests that inspect spelling, duplicate constructors, or
exercise one-line helpers without reaching a consequence. Preserve exact
CAS, conflict, substitution, concurrency, lifecycle, and re-entry proofs.
The remaining source-inspection concentration is the daemon supervisor and
workspace-coverage service; map those claims before deleting them.
6. Run focused tests after each cut. Only after source subtraction stabilizes
should Idunn's build scratch contract be reconsidered.