From a3f263c0056f7f19d37efeffabc1e809539efe06 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Mon, 13 Jul 2026 00:30:13 +0100 Subject: [PATCH] Order Hands gate mirrors by creation time --- epiphany-core/src/cultmesh_integration.rs | 49 +++++++++++++++++- ...onceptual-substitution-audit-2026-07-12.md | 10 ++++ notes/fresh-workspace-handoff.md | 2 + state/ledgers.msgpack | Bin 450917 -> 451201 bytes state/map.yaml | 2 +- 5 files changed, 61 insertions(+), 2 deletions(-) diff --git a/epiphany-core/src/cultmesh_integration.rs b/epiphany-core/src/cultmesh_integration.rs index 0a3a7c3e..d94f5310 100644 --- a/epiphany-core/src/cultmesh_integration.rs +++ b/epiphany-core/src/cultmesh_integration.rs @@ -2986,7 +2986,14 @@ pub fn write_epiphany_cultmesh_hands_action_gate( let mut node = open_epiphany_cultmesh_node(&store_path, gate.runtime_id.clone())?; let gate_key = epiphany_cultmesh_hands_action_gate_key(&gate.gate_id); let written = node.put(gate_key.as_str(), &gate)?; - node.put(EPIPHANY_CULTMESH_HANDS_ACTION_GATE_LATEST_KEY, &written)?; + let current_latest = node.get::( + EPIPHANY_CULTMESH_HANDS_ACTION_GATE_LATEST_KEY, + )?; + if current_latest.as_ref().is_none_or(|current| { + hands_action_gate_event_key(&written) >= hands_action_gate_event_key(current) + }) { + node.put(EPIPHANY_CULTMESH_HANDS_ACTION_GATE_LATEST_KEY, &written)?; + } node.flush()?; Ok(written) } @@ -3208,9 +3215,22 @@ fn validate_hands_action_gate(gate: &EpiphanyCultMeshHandsActionGateEntry) -> Re "Hands action gate CultMesh mirror has an empty path, receipt, or command argument" )); } + DateTime::parse_from_rfc3339(&gate.created_at_utc).map_err(|error| { + anyhow!("Hands action gate CultMesh mirror has invalid created at: {error}") + })?; Ok(()) } +fn hands_action_gate_event_key( + gate: &EpiphanyCultMeshHandsActionGateEntry, +) -> (DateTime, &str) { + ( + DateTime::parse_from_rfc3339(&gate.created_at_utc) + .expect("validated Hands action gate timestamp"), + gate.gate_id.as_str(), + ) +} + fn validate_role_review_event(event: &EpiphanyCultMeshRoleReviewEventEntry) -> Result<()> { if event.private_state_exposed { return Err(anyhow!( @@ -7682,6 +7702,33 @@ mod tests { .is_some() ); + let mut newer = gate.clone(); + newer.gate_id = "hands-intent-newer:hands-review-newer".to_string(); + newer.hands_intent_id = "hands-intent-newer".to_string(); + newer.hands_review_id = "hands-review-newer".to_string(); + newer.created_at_utc = "2026-06-18T01:00:00Z".to_string(); + write_epiphany_cultmesh_hands_action_gate(&store, newer.clone())?; + + let mut delayed = gate.clone(); + delayed.gate_id = "hands-intent-delayed:hands-review-delayed".to_string(); + delayed.hands_intent_id = "hands-intent-delayed".to_string(); + delayed.hands_review_id = "hands-review-delayed".to_string(); + delayed.created_at_utc = "2026-06-17T23:00:00Z".to_string(); + write_epiphany_cultmesh_hands_action_gate(&store, delayed)?; + assert_eq!( + load_latest_epiphany_cultmesh_hands_action_gate(&store, "epiphany-test")?, + Some(newer) + ); + + let mut invalid_time = gate.clone(); + invalid_time.gate_id = "hands-intent-invalid:hands-review-invalid".to_string(); + invalid_time.hands_intent_id = "hands-intent-invalid".to_string(); + invalid_time.hands_review_id = "hands-review-invalid".to_string(); + invalid_time.created_at_utc = "not-a-time".to_string(); + let err = write_epiphany_cultmesh_hands_action_gate(&store, invalid_time) + .expect_err("invalid Hands gate time must be refused"); + assert!(err.to_string().contains("invalid created at")); + let mut leaked = gate; leaked.private_state_exposed = true; let err = write_epiphany_cultmesh_hands_action_gate(&store, leaked) diff --git a/notes/conceptual-substitution-audit-2026-07-12.md b/notes/conceptual-substitution-audit-2026-07-12.md index 5e917b14..b6c26311 100644 --- a/notes/conceptual-substitution-audit-2026-07-12.md +++ b/notes/conceptual-substitution-audit-2026-07-12.md @@ -521,3 +521,13 @@ start/completion/next-wake values, reject completion before start, and order the latest mirror by `(completion time, iteration, receipt ID)`. A planned wake may be overdue when a long tick completes; lateness remains observable rather than being rejected as impossible. + +## Last writer presented as current Hands gate + +The operator-safe `hands-action-gate/latest` mirror was overwritten by arrival +order. Although runtime-spine receipts remain the real action authority, a +delayed older mirror could advertise obsolete paths and a stale record-pass +command as the current actuator route. Hands gate mirrors now require a valid +RFC3339 creation time and advance `latest` only by `(creation time, gate ID)`. +Tests prove delayed older gates remain historical and malformed gate time is +refused. diff --git a/notes/fresh-workspace-handoff.md b/notes/fresh-workspace-handoff.md index f4cce9f9..c2f44b54 100644 --- a/notes/fresh-workspace-handoff.md +++ b/notes/fresh-workspace-handoff.md @@ -134,6 +134,8 @@ The confirmed conceptual substitutions have been cut: - Scheduler `latest` is likewise completion-time-owned. Tick timestamps are validated, reversed intervals are refused, and delayed replay cannot rewind the scheduler projection. +- `hands-action-gate/latest` is creation-time-owned. Delayed mirror replay + cannot advertise obsolete actuator scope or record-pass commands as current. The presentation boundary is now plain: `swarm overview` is a generic compact read-only projection. Gjallar is a downstream TUI application on Nightwing and is not an Epiphany organ, provider, owner, runtime, or architectural dependency. Eve/CultUI graphs may be lowered or composited downstream without Epiphany caring which presentation client does it. diff --git a/state/ledgers.msgpack b/state/ledgers.msgpack index 83239b65616262ac5efaae5f8cd6028cfeca37d2..2e71748617e871dbebb02c18f8ea19224fe50389 100644 GIT binary patch delta 242 zcmWlSze)o^5Qp8>Y?E3M4rL&vBwPqsSO}IDf~^+9woYX~XQ z3tHO>qDf)tJ2)x6`NIsq@AtKMd@EjdMR!ylm4}z_8)|hm?}eUsT@ zI*V4SkMF#+)B zU~=kaXpM$sYQAGVjevn5&~+0@1LuGyi}yTxt6`lLp+e_;0@O1K$wUG_j@}Y$SlM7= ZL#+IiSN?56AeRPkl8?XCPA->f`yVy}R@49h delta 57 zcmZoXD*g13^acq=ZX+`*6Z5EJ3~UdaHY+n0GBO%8Pi1PK%ESo7AetG7S%8>z`&1^j G3-