diff --git a/crates/hammerd/src/arje_link.rs b/crates/hammerd/src/arje_link.rs index 6198db84..bebf744f 100644 --- a/crates/hammerd/src/arje_link.rs +++ b/crates/hammerd/src/arje_link.rs @@ -40,8 +40,10 @@ const MAX_FRAME: usize = 1 << 20; // // Discriminantes (postcard = varint del índice de variante), verificados contra arje-bus: // BusPayload: Request=0, Response=1, Event=2 -// BusRequest::Subscribe = 13 (último; ver arje-bus/src/lib.rs) -// BusEvent: EnteCrashed=0, EnteRestarting=1, EnteExited=2 +// BusRequest::Subscribe = 14 (arje insertó StopCardFromDisk el 2026-06-26 y corrió +// el índice 13→14; re-sincronizado 2026-07-16 — ver el +// golden test de arje-bus `contrato_wire`) +// BusEvent: EnteCrashed=0, EnteRestarting=1, EnteExited=2, EnteParked=3, EnteRefloored=4 // LifecycleStatus: Exited=0, Killed=1 #[derive(Deserialize, Debug)] @@ -73,6 +75,12 @@ enum BusEvent { EnteCrashed { id: Ulid, label: String, status: LifecycleStatus }, EnteRestarting { id: Ulid, label: String, delay_ms: u64 }, EnteExited { id: Ulid, label: String }, + /// Ente `Restart` aparcado: su "piso" (una capability) perdió proveedor. + /// No es un crash — no hay señal para la capa de IA; se consume del wire + /// para no tirar el frame como indescifrable. + EnteParked { id: Ulid, label: String }, + /// El piso volvió y arje re-erigió al Ente aparcado. Ídem: sólo consumo. + EnteRefloored { id: Ulid, label: String }, } #[derive(Deserialize, Debug)] @@ -84,29 +92,31 @@ enum LifecycleStatus { impl BusEvent { /// Traduce el evento de arje a la señal normalizada de hammer. El `label` del Ente es el /// `service`; una muerte por señal usa la convención de shell `128 + signum` para que el - /// código sea siempre ≠ 0. - fn to_lifecycle(self) -> Lifecycle { + /// código sea siempre ≠ 0. `None` = evento consumido sin señal (Parked/Refloored: son + /// vaivén del piso gráfico, no ciclo de vida que le importe a la capa de IA). + fn to_lifecycle(self) -> Option { match self { BusEvent::EnteCrashed { label, status, .. } => { let code = match status { LifecycleStatus::Exited(c) => c, LifecycleStatus::Killed(sig) => 128 + sig, }; - Lifecycle { service: label, status: LifeStatus::Crashed { code } } + Some(Lifecycle { service: label, status: LifeStatus::Crashed { code } }) } BusEvent::EnteRestarting { label, delay_ms, .. } => { - Lifecycle { service: label, status: LifeStatus::Restarting { delay_ms } } + Some(Lifecycle { service: label, status: LifeStatus::Restarting { delay_ms } }) } BusEvent::EnteExited { label, .. } => { - Lifecycle { service: label, status: LifeStatus::Exited } + Some(Lifecycle { service: label, status: LifeStatus::Exited }) } + BusEvent::EnteParked { .. } | BusEvent::EnteRefloored { .. } => None, } } } /// Cuerpo del frame `Subscribe` (sin el prefijo de longitud), hand-encodeado: `from=None` + -/// `seq` (varint) + `BusPayload::Request`(0) + `BusRequest::Subscribe`(13). Verificado -/// byte-a-byte contra `postcard::to_stdvec` de arje-bus. +/// `seq` (varint) + `BusPayload::Request`(0) + `BusRequest::Subscribe`(14). Verificado +/// byte-a-byte contra `postcard::to_stdvec` de arje-bus (golden test `contrato_wire`). fn subscribe_body(seq: u64) -> Vec { let mut v = vec![0x00u8]; // Option::None let mut s = seq; // u64 como varint LEB128 @@ -121,7 +131,7 @@ fn subscribe_body(seq: u64) -> Vec { } } v.push(0x00); // BusPayload::Request - v.push(0x0D); // BusRequest::Subscribe (índice 13) + v.push(0x0E); // BusRequest::Subscribe (índice 14 desde 2026-06-26) v } @@ -165,9 +175,10 @@ pub(crate) fn run(sock: &PathBuf, bus: &EventBus) -> std::io::Result<()> { }; match msg.payload { BusPayload::Event(ev) => { - let life = ev.to_lifecycle(); - if let Some(event) = crashes::to_event(&life) { - bus.publish(&event); + if let Some(life) = ev.to_lifecycle() { + if let Some(event) = crashes::to_event(&life) { + bus.publish(&event); + } } } // El ack del Subscribe y cualquier otra respuesta: nada que hacer. @@ -198,10 +209,10 @@ mod tests { #[test] fn subscribe_body_es_el_frame_esperado() { - // Verificado contra arje-bus real: [from=None, seq=1, Request, Subscribe=13]. - assert_eq!(subscribe_body(1), vec![0x00, 0x01, 0x00, 0x0D]); + // Verificado contra arje-bus real: [from=None, seq=1, Request, Subscribe=14]. + assert_eq!(subscribe_body(1), vec![0x00, 0x01, 0x00, 0x0E]); // seq grande → varint multibyte (300 = 0xAC 0x02). - assert_eq!(subscribe_body(300), vec![0x00, 0xAC, 0x02, 0x00, 0x0D]); + assert_eq!(subscribe_body(300), vec![0x00, 0xAC, 0x02, 0x00, 0x0E]); } /// Round-trip local con el MISMO `postcard`/`ulid` que arje-bus: serializamos un @@ -250,7 +261,7 @@ mod tests { let msg: BusMessage = postcard::from_bytes(&bytes).unwrap(); match msg.payload { BusPayload::Event(ev) => { - let life = ev.to_lifecycle(); + let life = ev.to_lifecycle().expect("un crash sí produce señal"); assert_eq!(life.service, "demonio"); // Killed(11) → 128 + 11 = 139. assert_eq!(life.status, LifeStatus::Crashed { code: 139 }); diff --git a/crates/hammerd/tests/arje_link_e2e.rs b/crates/hammerd/tests/arje_link_e2e.rs index 5364695e..725d18b9 100644 --- a/crates/hammerd/tests/arje_link_e2e.rs +++ b/crates/hammerd/tests/arje_link_e2e.rs @@ -81,7 +81,7 @@ fn crashed_de_un_bus_falso_llega_como_event_crashed() { let n = u32::from_be_bytes(len) as usize; let mut body = vec![0u8; n]; conn.read_exact(&mut body).unwrap(); - assert_eq!(body, vec![0x00, 0x01, 0x00, 0x0D], "frame Subscribe inesperado"); + assert_eq!(body, vec![0x00, 0x01, 0x00, 0x0E], "frame Subscribe inesperado"); // 2) Le inyectamos un EnteCrashed por señal (SIGSEGV=11 → 128+11=139). let id = Ulid::from_string("01ARZ3NDEKTSV4RRFFQ69G5FAV").unwrap();