Retire --idunn-rudp-health and the daemon-health publisher

The flag family (--idunn-rudp-health, --idunn-daemon,
--idunn-health-contract), IdunnRudpHealthOptions, the health worker
thread, idunn_health_payload, the RUDP health connection and the
idunn.daemon_health document had no consumer since the previous Idunn
generation. The raven-muninn recipe never passed them; liveness is the
runtime-presence publisher. Removes the IdunnDaemonHealthRecord odin-core
symbol (three remain).

The daemon id is now derived from the host alone. The transport profile
and command-boundary records no longer carry health_publication, and
health_contract is null. The starfire, nightwing and raven health and
restart scripts stop passing and checking the flags; muninn.md follows.

Tests: the retired flags are unknown arguments; the boundary record
carries the host-derived daemon id and no health_publication.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-09-29 23:17:13 +02:00
commit e9580009ea
8 changed files with 42 additions and 426 deletions

View file

@ -21,12 +21,12 @@ use cultnet_rs::{
GAMECULT_MEDIA_STREAM_REQUEST_SCHEMA, GAMECULT_RUNTIME_PRESENCE_HEALTH_SCHEMA,
GameCultMediaStreamAdvertisementRecord, GameCultMediaStreamRequestRecord,
GameCultRuntimePresenceHealthRecord, decode_cultnet_message_from_slice,
encode_cultnet_message_to_vec, media_stream_request_key, validate_media_stream_request,
media_stream_request_key, validate_media_stream_request,
};
#[cfg(feature = "psmoveapi-tracker")]
use odin_core::MuninnMoveTrackerHealthRecord;
use odin_core::{
EveProviderAdvertisementRecord, EveSurfaceStateRecord, IdunnDaemonHealthRecord,
EveProviderAdvertisementRecord, EveSurfaceStateRecord,
MUNINN_MOVE_HUE_PROGRAM_SCHEMA, MUNINN_OBS_STREAM_CATALOG_SCHEMA,
MuninnCaptureStreamCommandRecord, MuninnCaptureStreamRecord, MuninnCommandBoundaryCompatRecord,
MuninnHidControllerStateRecord, MuninnMoveControllerStateRecord,
@ -70,7 +70,6 @@ use std::os::windows::ffi::OsStrExt;
use windows_sys::Win32::UI::Input::XboxController::{XINPUT_GAMEPAD, XINPUT_STATE, XInputGetState};
const CULTNET_RUDP_PROTOCOL_ID: &str = "cultnet.transport.rudp.v0";
const IDUNN_HEALTH_RUDP_CONNECTION_ID: u32 = 0x1d0d_0001;
const MUNINN_MEDIA_RUDP_CONNECTION_ID: u32 = 0x6d75_0001;
const MUNINN_HID_CONTROLLER_RUDP_CONNECTION_ID: u32 = 0x6d75_0005;
const MUNINN_COMMAND_RUDP_CONNECTION_ID: u32 = 0x61e0_0001;
@ -214,7 +213,6 @@ struct Options {
quest_input_stream_id: Option<String>,
quest_pose_stream_id: Option<String>,
quest_video_input_stream_id: Option<String>,
idunn_rudp_health: Option<IdunnRudpHealthOptions>,
odin_cultmesh_uri: Option<String>,
hid_controller_rudp_target: Option<SocketAddr>,
hid_controller_rudp_bind: Option<SocketAddr>,
@ -275,13 +273,6 @@ struct MoveStateSource {
hidraw_path: String,
}
#[derive(Clone, Debug, PartialEq, Eq)]
struct IdunnRudpHealthOptions {
endpoint: SocketAddr,
daemon_id: String,
health_contract: String,
}
#[derive(Clone, Debug, PartialEq, Eq)]
struct MuxPlan {
command_line: String,
@ -406,7 +397,6 @@ fn serve(options: Options) -> Result<()> {
let move_runtime_enabled = serve_should_manage_move_runtime(&options);
let mut active_move_states =
active_move_state_sources(serve_move_state_sources(&options, move_runtime_enabled));
start_daemon_health_worker(&options);
start_idunn_presence_worker(&options);
let mut active_move_marker_cameras =
active_move_marker_camera_sources(&options, Arc::clone(&move_hue_program));
@ -613,21 +603,6 @@ fn start_idunn_presence_worker(options: &Options) {
});
}
fn start_daemon_health_worker(options: &Options) {
if options.idunn_rudp_health.is_none() {
return;
}
let options = options.clone();
thread::spawn(move || {
loop {
if let Err(error) = run_daemon_health_publisher(&options) {
eprintln!("Muninn Idunn health publish failed: {error:#}");
}
thread::sleep(Duration::from_secs(5));
}
});
}
fn move_hue_program_key(host_id: &str) -> String {
format!("muninn:{host_id}:move-hue-program")
}
@ -2846,16 +2821,8 @@ fn publish_runtime_boundary_records(
.as_ref()
.map(|command| json!([command]))
.unwrap_or_else(|| json!([]));
let current_transport = if options.idunn_rudp_health.is_some() {
"daemon-published-rudp-health + daemon-owned-cultcache-telemetry-store"
} else {
"daemon-owned-cultcache-telemetry-store + missing-rudp-health-publication"
};
let transport_state = if options.idunn_rudp_health.is_some() {
"rudp-health-and-provider-store-live"
} else {
"cultcache-provider-store-only"
};
let current_transport = "daemon-owned-cultcache-telemetry-store";
let transport_state = "cultcache-provider-store-only";
let hid_controller_endpoint = options.hid_controller_rudp_bind.map(|bind| {
options
.hid_controller_rudp_advertise
@ -2994,18 +2961,6 @@ fn publish_runtime_boundary_records(
"state_store": store_path,
"log_root": log_root,
"lifecycle_authority": "idunn-supervisor-command",
"health_publication": options.idunn_rudp_health.as_ref().map(|idunn| json!({
"contract": idunn.health_contract,
"transport": CULTNET_RUDP_PROTOCOL_ID,
"publication_source": "daemon-published",
"endpoint": idunn.endpoint.to_string(),
"state_owner": "Muninn serve process"
})).unwrap_or_else(|| json!({
"contract": serde_json::Value::Null,
"transport": "unconfigured",
"publication_source": "missing-daemon-publication",
"state_owner": "Muninn local store"
})),
"commands": [
{
"command": "muninn.capture_stream_command",
@ -3053,7 +3008,7 @@ fn publish_runtime_boundary_records(
"target_transport": CULTNET_RUDP_PROTOCOL_ID,
"current_transport": current_transport,
"state": transport_state,
"health_contract": options.idunn_rudp_health.as_ref().map(|idunn| idunn.health_contract.clone()),
"health_contract": serde_json::Value::Null,
"provider_advertisement_schema": "gamecult.eve.provider_advertisement.v1",
"command_boundary_schema": "muninn.command_boundary.v1",
"telemetry_surface_schema": "muninn.telemetry_surface.v1",
@ -3191,9 +3146,6 @@ fn muninn_provider_id(options: &Options) -> String {
}
fn muninn_daemon_id(options: &Options) -> String {
if let Some(idunn) = options.idunn_rudp_health.as_ref() {
return idunn.daemon_id.clone();
}
match options.host_id.as_str() {
"raven" => "muninn".to_string(),
"starfire" => "starfire-muninn".to_string(),
@ -8604,18 +8556,6 @@ fn health_check(options: &Options) -> Result<()> {
Ok(detail) => ("active", detail.clone()),
Err(error) => ("failed", error.to_string()),
};
if let Some(idunn) = options.idunn_rudp_health.as_ref() {
let publish_result = publish_idunn_rudp_health(idunn, state, &detail, &observed_at);
if let Err(publish_error) = publish_result {
return match result {
Ok(_) => Err(publish_error).context("publishing Muninn health to Idunn RUDP"),
Err(health_error) => Err(anyhow!(
"{health_error}; also failed to publish Muninn health to Idunn RUDP: {publish_error}"
)),
};
}
}
match result {
Ok(detail) => {
println!("{detail}");
@ -8674,22 +8614,6 @@ fn evaluate_health_from_node(
))
}
fn publish_idunn_rudp_health(
options: &IdunnRudpHealthOptions,
state: &str,
detail: &str,
observed_at: &str,
) -> Result<()> {
let mut transport = connect_idunn_rudp_health(options)?;
transport
.send(
"schema",
idunn_health_payload(options, state, detail, observed_at)?,
)
.with_context(|| format!("sending Idunn health to {}", options.endpoint))?;
Ok(())
}
fn smootherstep(value: f64) -> f64 {
let t = value.clamp(0.0, 1.0);
t * t * t * (t * (t * 6.0 - 15.0) + 10.0)
@ -8700,104 +8624,6 @@ fn wrapped_hue_lerp(source: f64, target: f64, amount: f64) -> f64 {
(source + delta * amount).rem_euclid(1.0)
}
fn run_daemon_health_publisher(options: &Options) -> Result<()> {
let idunn = options
.idunn_rudp_health
.as_ref()
.context("Muninn daemon health publisher requires Idunn RUDP options")?;
let mut transport = connect_idunn_rudp_health(idunn)?;
let cadence = Duration::from_secs(options.interval_seconds.unwrap_or(15).max(1));
let mut next_publish_at = Instant::now();
loop {
if Instant::now() >= next_publish_at {
let observed_at = idunn_timestamp()?;
let (state, detail) = match evaluate_health(options) {
Ok(detail) => ("active", detail),
Err(error) => ("failed", error.to_string()),
};
transport
.send(
"schema",
idunn_health_payload(idunn, state, &detail, &observed_at)?,
)
.with_context(|| format!("sending Idunn health to {}", idunn.endpoint))?;
next_publish_at = Instant::now() + cadence;
}
let _ = transport.receive_once()?;
transport.poll_resends()?;
}
}
fn idunn_health_payload(
options: &IdunnRudpHealthOptions,
state: &str,
detail: &str,
observed_at: &str,
) -> Result<Vec<u8>> {
let health = IdunnDaemonHealthRecord {
daemon_id: options.daemon_id.clone(),
state: state.to_string(),
detail: detail.to_string(),
observed_at: observed_at.to_string(),
health_contract: options.health_contract.clone(),
publication_source: "daemon-published".to_string(),
transport: CULTNET_RUDP_PROTOCOL_ID.to_string(),
};
let message = CultNetMessage::DocumentPutRaw {
message_id: format!(
"muninn-health:{}:{}",
options.daemon_id,
observed_at.replace(':', "-")
),
document: CultNetRawDocumentRecord {
schema_id: "idunn.daemon_health".to_string(),
record_key: options.daemon_id.clone(),
stored_at: observed_at.to_string(),
payload_encoding: CultNetRawPayloadEncoding::Messagepack,
payload: rmp_serde::to_vec(&health).context("encoding Idunn daemon health")?,
source_runtime_id: Some("muninn-daemon".to_string()),
source_agent_id: None,
source_role: Some("daemon-health-publisher".to_string()),
tags: Some(vec![CULTNET_RUDP_PROTOCOL_ID.to_string()]),
},
};
encode_cultnet_message_to_vec(&message, CultNetWireContract::CultNetSchemaV0)
.context("encoding Idunn health CultNet message")
}
fn connect_idunn_rudp_health(
options: &IdunnRudpHealthOptions,
) -> Result<CultNetRudpSocketTransportConnection> {
let bind_address = if options.endpoint.is_ipv4() {
"0.0.0.0:0"
} else {
"[::]:0"
};
let socket = UdpSocket::bind(bind_address)
.with_context(|| format!("binding Muninn RUDP sender at {bind_address}"))?;
socket.set_read_timeout(Some(Duration::from_secs(1)))?;
let mut transport =
CultNetRudpSocketTransportConnection::new(CultNetRudpSocketTransportOptions::client(
"muninn-daemon",
socket,
options.endpoint,
IDUNN_HEALTH_RUDP_CONNECTION_ID,
))?;
transport.connect(Vec::new())?;
let deadline = Instant::now() + Duration::from_secs(2);
while !transport.connected() {
let _ = transport.receive_once()?;
transport.poll_resends()?;
if Instant::now() >= deadline {
return Err(anyhow!(
"timed out connecting Muninn RUDP sender to {}",
options.endpoint
));
}
}
Ok(transport)
}
fn verify_move_sources_fresh(options: &Options, node: &cultmesh_rs::CultMeshNode) -> Result<()> {
let move_state_sources =
serve_move_state_sources(options, serve_should_manage_move_runtime(options));
@ -9837,7 +9663,6 @@ impl Options {
quest_input_stream_id: None,
quest_pose_stream_id: None,
quest_video_input_stream_id: None,
idunn_rudp_health: None,
odin_cultmesh_uri: Some("cultmesh://odin/rendezvous/provider-catalog".to_string()),
hid_controller_rudp_target: None,
hid_controller_rudp_bind: None,
@ -9848,9 +9673,6 @@ impl Options {
};
let mut args = args.peekable();
let mut idunn_rudp_health_endpoint = None;
let mut idunn_daemon_id = None;
let mut idunn_health_contract = None;
while let Some(arg) = args.next() {
match arg.as_str() {
"serve" => options.mode = Mode::Serve,
@ -10087,19 +9909,6 @@ impl Options {
options.quest_video_input_stream_id =
Some(take_value(&mut args, "--quest-video-input-stream")?)
}
"--idunn-rudp-health" => {
idunn_rudp_health_endpoint = Some(
take_value(&mut args, "--idunn-rudp-health")?
.parse()
.context("--idunn-rudp-health must be a socket address")?,
)
}
"--idunn-daemon" => {
idunn_daemon_id = Some(take_value(&mut args, "--idunn-daemon")?)
}
"--idunn-health-contract" => {
idunn_health_contract = Some(take_value(&mut args, "--idunn-health-contract")?)
}
"--odin-cultmesh-rudp" => {
let _ = take_value(&mut args, "--odin-cultmesh-rudp")?;
return Err(anyhow!(
@ -10287,32 +10096,11 @@ impl Options {
options.quest_video_input_stream_id.as_ref(),
),
("--move-host", options.move_host_address.as_ref()),
("--idunn-daemon", idunn_daemon_id.as_ref()),
("--idunn-health-contract", idunn_health_contract.as_ref()),
] {
if value.is_some_and(|value| value.trim().is_empty()) {
return Err(anyhow!("{name} must be non-empty"));
}
}
options.idunn_rudp_health = match (
idunn_rudp_health_endpoint,
idunn_daemon_id,
idunn_health_contract,
) {
(None, None, None) => None,
(Some(endpoint), Some(daemon_id), Some(health_contract)) => {
Some(IdunnRudpHealthOptions {
endpoint,
daemon_id,
health_contract,
})
}
_ => {
return Err(anyhow!(
"--idunn-rudp-health, --idunn-daemon, and --idunn-health-contract must be provided together"
));
}
};
Ok(options)
}
}
@ -10330,14 +10118,6 @@ fn timestamp() -> Result<String> {
Ok(format!("unix-{seconds}"))
}
fn idunn_timestamp() -> Result<String> {
let seconds = SystemTime::now()
.duration_since(UNIX_EPOCH)
.context("system clock is before Unix epoch")?
.as_secs();
Ok(format!("unix:{seconds}"))
}
fn timestamp_ns() -> Result<i64> {
let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
@ -10406,7 +10186,7 @@ fn parse_move_marker_camera_source(value: &str) -> Result<MoveMarkerCameraSource
}
fn help_text() -> &'static str {
"Usage: muninn [serve|activate|request-stream|capture-stream-status|obs-catalog-status|request-move-light|move-light-status|move-identity-status|move-source-status|move-state-status|claim-move-host|quest-access-status] [--store <path>] [--activate-store <path>] [--stream-action <start|stop>] [--target-host <cultmesh-uri>] [--media-transport <rudp>] [--media-rudp-bind <addr>] [--media-rudp-advertise <host:port>] [--media-packet-bytes <bytes>] [--rudp-video-bitrate-kbps <kbps>] [--rudp-latency-budget-ms <ms>] [--video-source <source-id=label>] [--audio-source <source-id=label>] [--audio-source-id <source-id>] [--no-video] [--no-audio] [--loopback-script <path>] [--ffmpeg <path>] [--odin-cultmesh-uri <cultmesh-uri>] [--move-state <move-id>=<hidraw-path>] [--move-marker-camera <camera-id>=<device-path>] [--move-psmoveapi-tracker] [--move-tracker-exposure-milli <0..1000>] [--move-marker-width <px>] [--move-marker-height <px>] [--move-marker-fps <fps>] [--move-host <bt-addr>] [--move-evidence-stream <stream-id>] [--move-evidence-verse <verse-id>] [--move-evidence-ring-slots <slots>] [--move-evidence-slot-bytes <bytes>] [--move-evidence-snapshot <path>] [--quest-adb] [--quest-serial <serial>] [--quest-input-stream <stream-id>] [--quest-pose-stream <stream-id>] [--quest-video-input-stream <stream-id>] [--idunn-rudp-health <addr>] [--idunn-daemon <id>] [--idunn-health-contract <contract>] [--dry-run] [--health]\n\nMuninn is Odin's portable telemetry Verse assembler. serve publishes cheap typed telemetry affordances, optional Quest USB access surfaces, and the explicitly configured Move runtime; when serve receives --move-state, --move-marker-camera, --move-host, or --move-evidence-stream it may publish source-local Move controller state, source-local optical marker candidates, typed Move identity records, a CultMesh Move evidence stream, optionally write a latest one-copy Move proof evidence snapshot for Mimir field capture/replay, and keep USB-attached PS Moves claimed to that explicit Bluetooth host; --move-psmoveapi-tracker delegates camera exposure and optical extraction to the reference PSMoveAPI backend while Muninn retains stable-ID light actuation; serve consumes typed capture stream commands from its provider-owned activation store and owns the local ffmpeg/loopback activation child lifecycle, listens for media receivers on --media-rudp-bind and advertises that endpoint (--media-rudp-advertise when the bind address is not reachable as-is), and publishes its discovery advertisement through --odin-cultmesh-uri; activate starts an explicitly requested local CultNet RUDP stream as a daemon child that serves every receiver dialling --media-rudp-bind; request-stream discovers the provider through Odin and sends its typed command to that provider; obs-catalog-status pulls Odin-owned muninn.obs_stream_catalog discovery into the local compatibility store for OBS; capture-stream-status reads typed capture stream command receipts; use --no-video or --no-audio to request one leg over the CultNet RUDP media lane; request-move-light publishes a typed Move light command for Muninn serve to execute; move-light-status reads typed command receipts; move-identity-status reads typed Move identity records; move-source-status prints live Move source discovery; move-state-status reads typed controller-state records; claim-move-host assigns USB-attached PS Moves to a Bluetooth host; quest-access-status reads typed Quest access state. In --health mode, the Idunn RUDP flags publish the same typed daemon health document to Idunn for explicit diagnostics."
"Usage: muninn [serve|activate|request-stream|capture-stream-status|obs-catalog-status|request-move-light|move-light-status|move-identity-status|move-source-status|move-state-status|claim-move-host|quest-access-status] [--store <path>] [--activate-store <path>] [--stream-action <start|stop>] [--target-host <cultmesh-uri>] [--media-transport <rudp>] [--media-rudp-bind <addr>] [--media-rudp-advertise <host:port>] [--media-packet-bytes <bytes>] [--rudp-video-bitrate-kbps <kbps>] [--rudp-latency-budget-ms <ms>] [--video-source <source-id=label>] [--audio-source <source-id=label>] [--audio-source-id <source-id>] [--no-video] [--no-audio] [--loopback-script <path>] [--ffmpeg <path>] [--odin-cultmesh-uri <cultmesh-uri>] [--move-state <move-id>=<hidraw-path>] [--move-marker-camera <camera-id>=<device-path>] [--move-psmoveapi-tracker] [--move-tracker-exposure-milli <0..1000>] [--move-marker-width <px>] [--move-marker-height <px>] [--move-marker-fps <fps>] [--move-host <bt-addr>] [--move-evidence-stream <stream-id>] [--move-evidence-verse <verse-id>] [--move-evidence-ring-slots <slots>] [--move-evidence-slot-bytes <bytes>] [--move-evidence-snapshot <path>] [--quest-adb] [--quest-serial <serial>] [--quest-input-stream <stream-id>] [--quest-pose-stream <stream-id>] [--quest-video-input-stream <stream-id>] [--dry-run] [--health]\n\nMuninn is Odin's portable telemetry Verse assembler. serve publishes cheap typed telemetry affordances, optional Quest USB access surfaces, and the explicitly configured Move runtime; when serve receives --move-state, --move-marker-camera, --move-host, or --move-evidence-stream it may publish source-local Move controller state, source-local optical marker candidates, typed Move identity records, a CultMesh Move evidence stream, optionally write a latest one-copy Move proof evidence snapshot for Mimir field capture/replay, and keep USB-attached PS Moves claimed to that explicit Bluetooth host; --move-psmoveapi-tracker delegates camera exposure and optical extraction to the reference PSMoveAPI backend while Muninn retains stable-ID light actuation; serve consumes typed capture stream commands from its provider-owned activation store and owns the local ffmpeg/loopback activation child lifecycle, listens for media receivers on --media-rudp-bind and advertises that endpoint (--media-rudp-advertise when the bind address is not reachable as-is), and publishes its discovery advertisement through --odin-cultmesh-uri; activate starts an explicitly requested local CultNet RUDP stream as a daemon child that serves every receiver dialling --media-rudp-bind; request-stream discovers the provider through Odin and sends its typed command to that provider; obs-catalog-status pulls Odin-owned muninn.obs_stream_catalog discovery into the local compatibility store for OBS; capture-stream-status reads typed capture stream command receipts; use --no-video or --no-audio to request one leg over the CultNet RUDP media lane; request-move-light publishes a typed Move light command for Muninn serve to execute; move-light-status reads typed command receipts; move-identity-status reads typed Move identity records; move-source-status prints live Move source discovery; move-state-status reads typed controller-state records; claim-move-host assigns USB-attached PS Moves to a Bluetooth host; quest-access-status reads typed Quest access state. In --health mode, the Idunn RUDP flags publish the same typed daemon health document to Idunn for explicit diagnostics."
}
fn parse_move_state_source(value: &str) -> Result<MoveStateSource> {
@ -10430,6 +10210,7 @@ fn parse_move_state_source(value: &str) -> Result<MoveStateSource> {
mod tests {
use super::*;
use crate::media_send::{MediaKind, QueuedMediaGroup};
use cultnet_rs::encode_cultnet_message_to_vec;
fn media_stream_request() -> GameCultMediaStreamRequestRecord {
GameCultMediaStreamRequestRecord {
@ -10875,39 +10656,20 @@ mod tests {
}
#[test]
fn health_idunn_rudp_options_are_explicit_bundle() {
let options = Options::parse(
[
"--health",
"--idunn-rudp-health",
"127.0.0.1:17870",
"--idunn-daemon",
"starfire-muninn",
"--idunn-health-contract",
"muninn.cultnet-rudp-local-telemetry-and-quest-access",
]
.into_iter()
.map(String::from),
)
.unwrap();
let idunn = options.idunn_rudp_health.unwrap();
assert_eq!(options.mode, Mode::Health);
assert_eq!(idunn.endpoint, "127.0.0.1:17870".parse().unwrap());
assert_eq!(idunn.daemon_id, "starfire-muninn");
assert_eq!(
idunn.health_contract,
"muninn.cultnet-rudp-local-telemetry-and-quest-access"
);
let error = Options::parse(
["--health", "--idunn-rudp-health", "127.0.0.1:17870"]
.into_iter()
.map(String::from),
)
.unwrap_err()
.to_string();
assert!(error.contains("must be provided together"));
fn retired_idunn_rudp_health_flags_are_unknown_arguments() {
for flag in [
"--idunn-rudp-health",
"--idunn-daemon",
"--idunn-health-contract",
] {
let error = Options::parse(["--health", flag, "127.0.0.1:17870"].into_iter().map(String::from))
.unwrap_err()
.to_string();
assert!(
error.contains(&format!("unknown Muninn argument: {flag}")),
"{flag} must be rejected, got: {error}"
);
}
}
#[test]
@ -10995,67 +10757,6 @@ mod tests {
assert!(error.contains("request-stream requires --odin-cultmesh-uri"));
}
#[test]
fn idunn_rudp_health_publisher_sends_raw_daemon_health_document() {
let receiver = UdpSocket::bind("127.0.0.1:0").unwrap();
receiver
.set_read_timeout(Some(Duration::from_secs(1)))
.unwrap();
let receiver_addr = receiver.local_addr().unwrap();
let handle = thread::spawn(move || {
let mut transport = CultNetRudpSocketTransportConnection::new(
CultNetRudpSocketTransportOptions::server(
"idunn-test",
receiver,
IDUNN_HEALTH_RUDP_CONNECTION_ID,
),
)
.unwrap();
let deadline = Instant::now() + Duration::from_secs(2);
loop {
if let Some(frame) = transport.receive_once().unwrap() {
return cultnet_rs::decode_cultnet_message_from_slice(
&frame.payload,
CultNetWireContract::CultNetSchemaV0,
)
.unwrap();
}
transport.poll_resends().unwrap();
if Instant::now() >= deadline {
panic!("timed out waiting for Muninn RUDP health frame");
}
}
});
let options = IdunnRudpHealthOptions {
endpoint: receiver_addr,
daemon_id: "starfire-muninn".to_string(),
health_contract: "muninn.cultnet-rudp-local-telemetry-and-quest-access".to_string(),
};
publish_idunn_rudp_health(&options, "active", "Muninn healthy", "unix:100").unwrap();
let message = handle.join().unwrap();
let CultNetMessage::DocumentPutRaw { document, .. } = message else {
panic!("expected raw document put");
};
assert_eq!(document.schema_id, "idunn.daemon_health");
assert_eq!(document.record_key, "starfire-muninn");
assert_eq!(
document.payload_encoding,
CultNetRawPayloadEncoding::Messagepack
);
let health: IdunnDaemonHealthRecord = rmp_serde::from_slice(&document.payload).unwrap();
assert_eq!(health.daemon_id, "starfire-muninn");
assert_eq!(health.state, "active");
assert_eq!(
health.health_contract,
"muninn.cultnet-rudp-local-telemetry-and-quest-access"
);
assert_eq!(health.publication_source, "daemon-published");
assert_eq!(health.transport, CULTNET_RUDP_PROTOCOL_ID);
assert_eq!(health.observed_at, "unix:100");
}
#[test]
fn repeated_capture_stream_start_is_equivalent_when_media_shape_matches() {
let options = Options::parse(
@ -12985,12 +12686,6 @@ Device 00:07:04:A8:00:D0 (public)
"C:/Meta/Odin/state/muninn.activate.cc",
"--target-host",
"cultmesh://odin/media/muninn-raven-av",
"--idunn-rudp-health",
"198.51.100.10:17870",
"--idunn-daemon",
"muninn",
"--idunn-health-contract",
"muninn.cultnet-rudp-remote-telemetry-health",
]
.into_iter()
.map(String::from),
@ -13023,6 +12718,13 @@ Device 00:07:04:A8:00:D0 (public)
assert!(invocation.contains("request-stream"));
assert!(invocation.contains("--target-host cultmesh://odin/media/muninn-raven-av"));
assert_eq!(
boundary.value.get("daemon_id").and_then(|value| value.as_str()),
Some("muninn")
);
assert!(boundary.value.get("health_publication").is_none());
assert!(transport.value.get("health_contract").is_some_and(|value| value.is_null()));
let command_lowerings = transport
.value
.get("command_lowerings")

View file

@ -22,7 +22,7 @@ microphones, cameras, and future sensors.
```powershell
cargo build -p muninn-daemon
muninn serve --store C:\Meta\Odin\state\muninn.telemetry.cc --interval-seconds 15 --idunn-rudp-health $env:IDUNN_RUDP_HEALTH --idunn-daemon starfire-muninn --idunn-health-contract muninn.cultnet-rudp-local-telemetry-and-quest-access
muninn serve --store C:\Meta\Odin\state\muninn.telemetry.cc --interval-seconds 15
muninn --health --store C:\Meta\Odin\state\muninn.telemetry.cc
```
@ -97,10 +97,7 @@ E:\Projects\Odin\scripts\restart-starfire-muninn.cmd
E:\Projects\Odin\scripts\health-starfire-muninn.cmd
```
The restart script launches Muninn hidden with `--host starfire --quest-adb`,
`--idunn-rudp-health` from explicit `-IdunnRudpHealth` or
`IDUNN_RUDP_HEALTH`, `--idunn-daemon starfire-muninn`, and
`--idunn-health-contract muninn.cultnet-rudp-local-telemetry-and-quest-access`.
The restart script launches Muninn hidden with `--host starfire --quest-adb`.
If the CultCache store at `C:\Meta\Odin\state\starfire.muninn.telemetry.cc`
fails MessagePack decode on boot, the restart path archives the corrupt file,
clears the stale `.lock`, and relaunches the daemon instead of leaving the lane
@ -192,11 +189,7 @@ muninn move-light-status \
Idunn keeps the Muninn daemon alive. Idunn does not learn a Move-specific
watcher, and Mimir does not write HID directly except through temporary smoke
scripts used to prove hardware behavior before a Muninn daemon is available.
When `serve` is launched with `--idunn-rudp-health`, `--idunn-daemon`, and
`--idunn-health-contract`, the long-running Muninn body publishes
`idunn.daemon_health` directly to Idunn over `cultnet.transport.rudp.v0` on its
normal cadence. `--health` keeps the same publication path for manual proof and
compatibility probes, but the live owner is the daemon's `serve` process.
`--health` reads the telemetry store and reports; it publishes nothing.
Quest ADB probing is a telemetry input, not daemon liveness. If `adb` is
missing or the Quest is unavailable, Muninn publishes `muninn.quest_access` as
`unavailable` and keeps serving the local telemetry surface.
@ -235,10 +228,6 @@ actuators only, not lifecycle owners. The binary is installed at
`/home/metacrat/.local/state/gamecult/muninn/muninn.telemetry.cc`, and the
restart actuator launches `serve --host nightwing --interval-seconds 15` with
`--move-state move-usb=/dev/input/by-id/usb-Sony_Computer_Entertainment_Motion_Controller-joystick`,
`--idunn-rudp-health` supplied by explicit `-IdunnRudpHealth` or
`IDUNN_RUDP_HEALTH`,
`--idunn-daemon nightwing-muninn`,
`--idunn-health-contract muninn.cultnet-rudp-remote-telemetry-and-move-hid`,
PID, and logs under
`/home/metacrat/.local/state/gamecult/muninn`.
With that Move state source attached, `serve` also publishes the
@ -248,10 +237,7 @@ Nightwing source discovery must emit one Move source per controller id. The USB
pairing collection and Bluetooth joystick can expose the same controller as
separate `/dev/input/js*` paths; `nightwing-move-state-sources.sh` prefers the
Bluetooth `HID_ID=0005:0000054C:000003D5` path so Idunn health does not demand
fresh records from two faces of the same controller. The Nightwing health
actuator requires the same configured Idunn RUDP endpoint, but that command is
fallback proof only; the live keepalive contract is now published by the
long-running Nightwing `serve` process.
fresh records from two faces of the same controller.
Each private PSMoveAPI Eye worker owns optical observation admission before the
parent aggregates evidence. Positions older than 50 ms, blobs below 2 px,

View file

@ -701,8 +701,7 @@ by continuity in under 10 s. The catalog on Odin named the new endpoint the
first time the restarted `serve` advertised.
Still Raven-specific and unmanaged: the two activation scripts and
`health-muninn.ps1`, which still passes the retired `--idunn-rudp-health`
flags. That flag family (`IdunnRudpHealthOptions`, `run_daemon_health_publisher`,
`idunn.daemon_health` documents) has no consumer since the previous Idunn
generation was retired and is the next cut; nightwing and starfire's restart
scripts still require it, so it goes with them.
`health-muninn.ps1`. The retired `--idunn-rudp-health` flag family
(`IdunnRudpHealthOptions`, `run_daemon_health_publisher`, `idunn.daemon_health`
documents) had no consumer since the previous Idunn generation was retired; it
was deleted from `muninn`, its five host scripts and `muninn.md` in named cut 3.

View file

@ -5,7 +5,6 @@ param(
[string] $ActivateStorePath = "C:\Meta\Odin\state\muninn.activate.cc",
[string] $LogRoot = "C:\Meta\Odin\logs\muninn",
[int] $MaxStoreAgeSeconds = 180,
[string] $IdunnRudpHealth = $env:IDUNN_RUDP_HEALTH,
[int] $ConnectTimeoutSeconds = 10,
[string] $SshUser = "madman's lullaby",
[string] $IdentityFile = ""
@ -13,10 +12,6 @@ param(
$ErrorActionPreference = "Stop"
if ([string]::IsNullOrWhiteSpace($IdunnRudpHealth)) {
throw "Idunn RUDP health endpoint must be supplied by -IdunnRudpHealth or IDUNN_RUDP_HEALTH; no Starfire LAN default is allowed."
}
function Set-AsciiFile {
param(
[Parameter(Mandatory = $true)] [string] $Path,
@ -177,10 +172,7 @@ if (`$null -eq `$process) {
}
foreach (`$pattern in @(
"--host raven",
"--activate-store $ActivateStorePath",
"--idunn-rudp-health $IdunnRudpHealth",
"--idunn-daemon muninn",
"--idunn-health-contract muninn.cultnet-rudp-remote-telemetry-health"
"--activate-store $ActivateStorePath"
)) {
if (`$process.CommandLine -notlike "*`$pattern*") {
throw "Muninn serve process is missing expected command-line segment `${pattern}: `$(`$process.CommandLine)"

View file

@ -5,16 +5,11 @@ param(
[string] $PidPath = "/home/metacrat/.local/state/gamecult/muninn/muninn.pid",
[string[]] $MoveState = @(),
[int] $IntervalSeconds = 15,
[int] $MaxStoreAgeSeconds = 180,
[string] $IdunnRudpHealth = $env:IDUNN_RUDP_HEALTH
[int] $MaxStoreAgeSeconds = 180
)
$ErrorActionPreference = "Stop"
if ([string]::IsNullOrWhiteSpace($IdunnRudpHealth)) {
throw "Idunn RUDP health endpoint must be supplied by -IdunnRudpHealth or IDUNN_RUDP_HEALTH; no WireGuard endpoint default is allowed."
}
function Set-AsciiFile {
param(
[Parameter(Mandatory = $true)] [string] $Path,
@ -76,16 +71,6 @@ if [ -z "`$process_line" ]; then
echo 'Nightwing Muninn serve command line is unavailable' >&2
exit 1
fi
for pattern in \
'--idunn-rudp-health $IdunnRudpHealth' \
'--idunn-daemon nightwing-muninn' \
'--idunn-health-contract muninn.cultnet-rudp-remote-telemetry-and-move-hid'
do
if ! printf '%s\n' "`$process_line" | grep -F -- "`$pattern" >/dev/null 2>&1; then
echo "Nightwing Muninn serve command line is missing `$pattern" >&2
exit 1
fi
done
if [ ! -f '$StorePath' ]; then
echo 'Muninn telemetry store is missing on Nightwing' >&2
exit 1

View file

@ -3,16 +3,11 @@ param(
[string] $StorePath = "C:\Meta\Odin\state\starfire.muninn.telemetry.cc",
[string] $LogRoot = "C:\Meta\Odin\logs\starfire-muninn",
[string[]] $MoveState = @(),
[int] $MaxStoreAgeSeconds = 180,
[string] $IdunnRudpHealth = $env:IDUNN_RUDP_HEALTH
[int] $MaxStoreAgeSeconds = 180
)
$ErrorActionPreference = "Stop"
if ([string]::IsNullOrWhiteSpace($IdunnRudpHealth)) {
throw "Idunn RUDP health endpoint must be supplied by -IdunnRudpHealth or IDUNN_RUDP_HEALTH; no Starfire LAN default is allowed."
}
if (-not (Test-Path -LiteralPath $MuninnExe)) {
throw "Muninn executable not found at $MuninnExe"
}
@ -28,15 +23,6 @@ $process = Get-CimInstance Win32_Process |
if ($null -eq $process) {
throw "Starfire Muninn serve process is not running with Quest access enabled"
}
foreach ($pattern in @(
"--idunn-rudp-health $IdunnRudpHealth",
"--idunn-daemon starfire-muninn",
"--idunn-health-contract muninn.cultnet-rudp-local-telemetry-and-quest-access"
)) {
if ($process.CommandLine -notlike "*$pattern*") {
throw "Starfire Muninn serve process is missing expected command-line segment ${pattern}: $($process.CommandLine)"
}
}
if (-not (Test-Path -LiteralPath $StorePath)) {
throw "Starfire Muninn telemetry store is missing at $StorePath"
}

View file

@ -17,9 +17,6 @@ param(
"nightwing-eye-1" = 100
},
[int] $IntervalSeconds = 15,
[string] $IdunnRudpHealth = $env:IDUNN_RUDP_HEALTH,
[string] $IdunnDaemon = "nightwing-muninn",
[string] $IdunnHealthContract = "muninn.cultnet-rudp-remote-telemetry-and-move-hid",
[string] $OdinCultMeshUri = $(if ($env:ODIN_CULTMESH_URI) { $env:ODIN_CULTMESH_URI } else { "cultmesh://odin/rendezvous/provider-catalog" }),
[string] $OdinCultMeshRudpEndpoint = $(if ($env:CULTMESH_URI_ODIN_RUDP) { $env:CULTMESH_URI_ODIN_RUDP } else { "10.77.0.2:17871" }),
[string] $HidControllerRudpBind = "0.0.0.0:17888",
@ -34,12 +31,6 @@ if ($env:IDUNN_ACTUATOR -ne "1" -or $env:IDUNN_COMMAND_AUTHORITY -ne "idunn-daem
throw "restart-nightwing-muninn.ps1 is an Idunn actuator body. Redeploy by poking Idunn; direct service restart is not an owned path."
}
if ([string]::IsNullOrWhiteSpace($IdunnRudpHealth)) {
throw "Idunn RUDP health endpoint must be supplied by -IdunnRudpHealth or IDUNN_RUDP_HEALTH; no Starfire LAN default is allowed."
}
if ($IdunnRudpHealth -match '^(127\.0\.0\.1|localhost):(\d+)$') {
$IdunnRudpHealth = "10.77.0.2:$($Matches[2])"
}
if (-not [string]::IsNullOrWhiteSpace($HidControllerRudpBind) -and [string]::IsNullOrWhiteSpace($HidControllerRudpAdvertise)) {
throw "HID controller RUDP advertise endpoint must be supplied by -HidControllerRudpAdvertise or MUNINN_HID_CONTROLLER_RUDP_ADVERTISE when HID RUDP bind is enabled; no Nightwing LAN default is allowed."
}
@ -162,10 +153,7 @@ set -- serve \
--host nightwing
$moveRuntimeSetBlock
set -- "`$@" \
--interval-seconds '$IntervalSeconds' \
--idunn-rudp-health '$IdunnRudpHealth' \
--idunn-daemon '$IdunnDaemon' \
--idunn-health-contract '$IdunnHealthContract'
--interval-seconds '$IntervalSeconds'
$odinCultMeshUriSetBlock
$hidControllerRudpSetBlock
$commandRudpSetBlock
@ -179,10 +167,9 @@ muninn_pid="`$(cat '$LogRoot/muninn.pid')"
kill -0 "`$muninn_pid" 2>/dev/null
muninn_cmdline="`$(tr '\0' ' ' < "/proc/`$muninn_pid/cmdline")"
if ! printf '%s\n' "`$muninn_cmdline" | grep -F -- '--host nightwing' >/dev/null 2>&1 ||
! printf '%s\n' "`$muninn_cmdline" | grep -F -- '--idunn-rudp-health $IdunnRudpHealth' >/dev/null 2>&1 ||
! printf '%s\n' "`$muninn_cmdline" | grep -F -- '--move-marker-camera nightwing-eye-0=/dev/video2' >/dev/null 2>&1 ||
! printf '%s\n' "`$muninn_cmdline" | grep -F -- '--move-marker-camera nightwing-eye-1=/dev/video3' >/dev/null 2>&1; then
echo 'Nightwing Muninn serve command line is missing Idunn health or dual-camera arguments' >&2
echo 'Nightwing Muninn serve command line is missing dual-camera arguments' >&2
exit 1
fi
"@

View file

@ -6,9 +6,6 @@ param(
[string] $MoveBluetoothHost = "",
[string[]] $MoveState = @(),
[switch] $EnableUsbMoveState,
[string] $IdunnRudpHealth = $env:IDUNN_RUDP_HEALTH,
[string] $IdunnDaemon = "starfire-muninn",
[string] $IdunnHealthContract = "muninn.cultnet-rudp-local-telemetry-and-quest-access",
[string] $OdinCultMeshUri = $(if ($env:ODIN_CULTMESH_URI) { $env:ODIN_CULTMESH_URI } else { "cultmesh://odin/rendezvous/provider-catalog" }),
[string] $OdinCultMeshRudpEndpoint = $(if ($env:CULTMESH_URI_ODIN_RUDP) { $env:CULTMESH_URI_ODIN_RUDP } else { "127.0.0.1:17871" }),
[string] $HidControllerRudpBind = "",
@ -29,9 +26,6 @@ if ($env:IDUNN_ACTUATOR -ne "1" -or $env:IDUNN_COMMAND_AUTHORITY -ne "idunn-daem
if (-not (Test-Path -LiteralPath $MuninnExe)) {
throw "Muninn executable not found at $MuninnExe"
}
if ([string]::IsNullOrWhiteSpace($IdunnRudpHealth)) {
throw "Idunn RUDP health endpoint must be supplied by -IdunnRudpHealth or IDUNN_RUDP_HEALTH; no Starfire LAN default is allowed."
}
if (-not [string]::IsNullOrWhiteSpace($HidControllerRudpBind) -and [string]::IsNullOrWhiteSpace($HidControllerRudpAdvertise)) {
throw "HID controller RUDP advertise endpoint must be supplied by -HidControllerRudpAdvertise or MUNINN_HID_CONTROLLER_RUDP_ADVERTISE when HID RUDP bind is enabled; no Starfire LAN default is allowed."
}
@ -63,10 +57,7 @@ $arguments = @(
"--host", "starfire",
"--interval-seconds", "15",
"--quest-adb",
"--quest-serial", $QuestSerial,
"--idunn-rudp-health", $IdunnRudpHealth,
"--idunn-daemon", $IdunnDaemon,
"--idunn-health-contract", $IdunnHealthContract
"--quest-serial", $QuestSerial
)
if (-not [string]::IsNullOrWhiteSpace($OdinCultMeshUri)) {
$arguments += @("--odin-cultmesh-uri", $OdinCultMeshUri)
@ -160,15 +151,3 @@ if ($null -eq $processCheck) {
if ($null -eq $processCheck) {
throw "Starfire Muninn serve process is not running after restart"
}
foreach ($pattern in @(
"--idunn-rudp-health",
$IdunnRudpHealth,
"--idunn-daemon",
$IdunnDaemon,
"--idunn-health-contract",
$IdunnHealthContract
)) {
if ($processCheck.CommandLine -notlike "*$pattern*") {
throw "Starfire Muninn serve command line is missing ${pattern}: $($processCheck.CommandLine)"
}
}