From 42bc665aa888fe47a1f979e127590b2241e5c8fe Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sat, 8 Aug 2026 23:01:55 +0100 Subject: [PATCH] continuity: resume heartbeat artifact sequence --- .../src/bin/epiphany-heartbeat-store.rs | 92 ++++++++++++++++-- .../heartbeat_state/heartbeat_retention.rs | 18 ++-- notes/fresh-workspace-handoff.md | 22 +++++ state/ledgers.msgpack | Bin 581062 -> 581882 bytes state/map.yaml | 8 +- 5 files changed, 120 insertions(+), 20 deletions(-) diff --git a/epiphany-core/src/bin/epiphany-heartbeat-store.rs b/epiphany-core/src/bin/epiphany-heartbeat-store.rs index 22f68de3..2e02a038 100644 --- a/epiphany-core/src/bin/epiphany-heartbeat-store.rs +++ b/epiphany-core/src/bin/epiphany-heartbeat-store.rs @@ -2,6 +2,7 @@ use anyhow::Context; use anyhow::Result; use anyhow::anyhow; use epiphany_core::EpiphanyCultMeshSwarmBrakeEntry; +use epiphany_core::EpiphanyHeartbeatArtifactRetentionReceipt; use epiphany_core::GhostlightSceneParticipantSeed; use epiphany_core::HeartbeatCompleteOptions; use epiphany_core::HeartbeatHeatUpdateOptions; @@ -326,7 +327,7 @@ fn main() -> Result<()> { serde_json::json!({ "schemaVersion": "epiphany.heartbeat.artifact_retention_command.v0", "status": if receipt.is_some() { "completed" } else { "within-bound" }, - "receipt": receipt, + "receipt": receipt.as_ref().map(retention_receipt_projection), "privateStateExposed": false }) ); @@ -535,7 +536,7 @@ fn main() -> Result<()> { artifact_dir.display() ) })?; - let mut completed_iterations = 0_u64; + let mut completed_iterations = highest_pulse_artifact_sequence(&artifact_dir)?; let mut completed_routines = 0_u64; let mut refused_pulses = 0_u64; loop { @@ -604,7 +605,8 @@ fn main() -> Result<()> { "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, + "brakeId":brake.as_ref().map(|value| &value.brake_id), + "retention":retention.as_ref().map(retention_receipt_projection), "privateStateExposed":false }) ); @@ -636,7 +638,8 @@ fn main() -> Result<()> { "artifactDir": iteration_dir, "brakeId": brake.brake_id, "brakeScope": brake.scope, - "reason": brake.reason, "retention": retention, + "reason": brake.reason, + "retention": retention.as_ref().map(retention_receipt_projection), "privateStateExposed": false }) ); @@ -666,7 +669,8 @@ fn main() -> Result<()> { println!( "{}", serde_json::json!({ - "pulse": persona_pulse, "retention": retention, + "pulse": persona_pulse, + "retention": retention.as_ref().map(retention_receipt_projection), "privateStateExposed": false }) ); @@ -704,7 +708,8 @@ fn main() -> Result<()> { "iteration": iteration, "artifactDir": iteration_dir, "routineOk": result["ok"], - "routineArtifact": result["artifactPath"], "retention": retention, + "routineArtifact": result["artifactPath"], + "retention": retention.as_ref().map(retention_receipt_projection), "privateStateExposed": false }) ); @@ -750,6 +755,51 @@ fn next_path(args: &mut impl Iterator, name: &str) -> Result 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 { + 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, name: &str) -> Result { args.next() .ok_or_else(|| anyhow!("{name} requires a value")) @@ -832,6 +882,36 @@ mod brake_tests { assert!(active_swarm_brake(&store_arg, Some("epiphany-yggdrasil"))?.is_some()); Ok(()) } + + #[test] + fn retention_receipt_projection_names_operator_fields() { + let projection = retention_receipt_projection(&EpiphanyHeartbeatArtifactRetentionReceipt { + schema_version: "epiphany.heartbeat.artifact_retention_receipt.v0".into(), + receipt_id: "receipt-1".into(), + plan_id: "plan-1".into(), + status: "completed".into(), + deleted_directories: vec!["pulse-000001".into()], + deleted_file_count: 2, + deleted_byte_count: 3, + completed_at_utc: "2026-08-08T21:00:00Z".into(), + private_state_exposed: false, + }); + assert_eq!(projection["receiptId"], "receipt-1"); + assert_eq!(projection["deletedDirectories"][0], "pulse-000001"); + assert_eq!(projection["deletedFileCount"], 2); + assert!(projection.as_array().is_none()); + } + + #[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(()) + } } fn parse_scene_participant(raw: &str) -> Result { diff --git a/epiphany-core/src/heartbeat_state/heartbeat_retention.rs b/epiphany-core/src/heartbeat_state/heartbeat_retention.rs index 440d7aa1..4fe20cf0 100644 --- a/epiphany-core/src/heartbeat_state/heartbeat_retention.rs +++ b/epiphany-core/src/heartbeat_state/heartbeat_retention.rs @@ -53,17 +53,15 @@ pub fn retain_heartbeat_pulse_artifacts( return Ok(None); } let retire_count = batch_size.min(pulses.len() - retain_pulse_count); + let protected = protected_latest_cognition_pulse(store_path.as_ref(), &artifact_root)?; let members = pulses .iter() + .filter(|(_, path)| protected.as_deref() != path.file_name().and_then(|name| name.to_str())) .take(retire_count) .map(|(_, path)| artifact_member(&artifact_root, path)) .collect::>>()?; - if let Some(protected) = protected_latest_cognition_pulse(store_path.as_ref(), &artifact_root)? - && members - .iter() - .any(|member| member.directory_name == protected) - { - bail!("heartbeat artifact retention refuses the latest cognition artifact pulse"); + if members.len() != retire_count { + bail!("heartbeat artifact retention cannot fill a batch without current cognition"); } let plan_id = retention_plan_id(&artifact_root, retain_pulse_count, batch_size, &members); let plan = EpiphanyHeartbeatArtifactRetentionPlan { @@ -540,10 +538,10 @@ mod tests { }, )?; - assert!( - retain_heartbeat_pulse_artifacts(&store, &artifacts, 2, 2, "2026-08-08T21:03:01Z",) - .is_err() - ); + let receipt = + retain_heartbeat_pulse_artifacts(&store, &artifacts, 2, 2, "2026-08-08T21:03:01Z")? + .expect("retention should skip protected current cognition"); + assert!(!receipt.deleted_directories.contains(&"pulse-000001".into())); assert!(protected.exists()); Ok(()) } diff --git a/notes/fresh-workspace-handoff.md b/notes/fresh-workspace-handoff.md index 8bbd4d38..b09692ed 100644 --- a/notes/fresh-workspace-handoff.md +++ b/notes/fresh-workspace-handoff.md @@ -2,6 +2,28 @@ ## Retention and release-bootstrap pass — 2026-08-08 +Exact corrected release `100d8854` packaged 22 binaries as +`sha256-da8be22948c6eb958edb2e7f7e03c5cc77d17463659fa14127402392194460d3` +with witness +`sha256-6a95957f7e7b2b47b42d2154ed8232d68ffe68d3b6bdf67bee6397fa6aab2027`. +Its packaged runtime-spine closed all four sessions in a fresh byte-identical +v28 copy, replayed closure idempotently, and refused post-close work. A bounded +live retention drain then reduced heartbeat artifacts from 12,511 directories +and 706.5 MB to 312 directories and 17.6 MB. The latest cognition pulse +survived; 191 deletion receipts are in the heartbeat store and the drain log +SHA-256 is `8280dd7567f839b0b038d69deb45bfb219c42092a231a2d331e0e3620a293003`. + +Restarting the resident under `100d8854` exposed another Continuity fault: +serve iteration restarted at `pulse-000001` instead of recovering the highest +surviving sequence. The process was stopped after three pulses; its stdout +SHA-256 is `4a104e39d81e0766261fff815a7d4350de31a9d5b956634a9cb39a9e5ad40b22`. +Current source resumes from the highest exact pulse directory, refuses alien +directories and exhausted sequence space, skips protected current cognition +when forming retirement batches, and emits named operator receipt fields. Four +retention and three heartbeat-binary tests pass. The resident heartbeat remains +stopped pending an authenticated package of this correction; do not restart the +old binary. + The first exact `339d5a6f` build completed its Rust 1.95 graph in 17m07s but failed because the isolated coordinator artifact was absent. Rebuilding that single target restored the cache. A detached clean-worktree retry then exposed diff --git a/state/ledgers.msgpack b/state/ledgers.msgpack index bc7ca9dcfbea2c890419ad2b8ed3d938b2d51fa4..a93b000634ef5b2d159395dbbb577e813f19d336 100644 GIT binary patch delta 876 zcmXw%yKdA#6o%OZLK>(LqC8UC-OXI=v7J;PQ6NEssM5{lcz1}`c4o%8R9jH+0z|w9 z5(Pq3GC&<{9+bp6dlV5CV*Eb?_KLw+{NjG(IlrBw&L&BE+Q@xtcj$8Wf_+N~_RRd5O*l zEb0Q8w7IoK;dqfLsq(7UJmaFsn2?p=RhF$4a}ut6YB9#0pwd-d<)c7hgvkn%2`6}- zp(}+HPL*|ys&vvQ<7!nHL?x@D)=FzbIg2^GN)f`)Ccna2?0t)1n=aDmx@n;9h5*jT zzKeQo8iY$xLL`6RwQ)-6`bOW-UGH$!cM03n+)OtJ0@lN*LY-;0QG}~5oX_F4zeGqk z{RYn1K~ms?CT9m+OmohOUqj5C4`@@jp?VC(nyGmXZ(cy^Kym@cz>B#WKM<28@QTws z7JW~FHcJQ?F>o`o)sWXeAc|wic%l&wU?9 y=|t?t?7Pg{wX}Nd{m*H<_MWbd%RJrgI^b?4Bf{-0OQbLcAL|5(%t&3wIR6J`BOwI< delta 67 zcmex$Q2E$y