diff --git a/crates/muninn-daemon/src/main.rs b/crates/muninn-daemon/src/main.rs index cf8e119..b2d21ca 100644 --- a/crates/muninn-daemon/src/main.rs +++ b/crates/muninn-daemon/src/main.rs @@ -9527,10 +9527,13 @@ fn ensure_state_dirs(options: &Options) -> Result<()> { } /// Odin's document set, Muninn's own records, and the producer-agnostic media -/// stream contract. Muninn's records are registered after Odin's, so -/// `muninn-contracts` decides every `muninn.*` binding; Odin's document set -/// still carries stale copies of the same schema ids (and the store may hold -/// legacy Odin records), which is why it stays registered. +/// stream contract. Today Odin's document set (odin-core) is registered first +/// and carries identical `muninn.*` type names and schema ids, so +/// `MuninnRecords` registers the same strings again and binds nothing new; +/// cultcache errors at startup if the two ever diverge. Odin's copies are to be +/// deleted, at which point `muninn-contracts` becomes the sole owner of every +/// `muninn.*` binding. The registration stays so that deletion changes nothing +/// here. #[derive(Clone, Copy, Debug, Default)] struct MuninnDocuments; @@ -10180,7 +10183,7 @@ fn parse_move_marker_camera_source(value: &str) -> Result &'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 ] [--activate-store ] [--stream-action ] [--target-host ] [--media-transport ] [--media-rudp-bind ] [--media-rudp-advertise ] [--media-packet-bytes ] [--rudp-video-bitrate-kbps ] [--rudp-latency-budget-ms ] [--video-source ] [--audio-source ] [--audio-source-id ] [--no-video] [--no-audio] [--loopback-script ] [--ffmpeg ] [--odin-cultmesh-uri ] [--move-state =] [--move-marker-camera =] [--move-psmoveapi-tracker] [--move-tracker-exposure-milli <0..1000>] [--move-marker-width ] [--move-marker-height ] [--move-marker-fps ] [--move-host ] [--move-evidence-stream ] [--move-evidence-verse ] [--move-evidence-ring-slots ] [--move-evidence-slot-bytes ] [--move-evidence-snapshot ] [--quest-adb] [--quest-serial ] [--quest-input-stream ] [--quest-pose-stream ] [--quest-video-input-stream ] [--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 ] [--activate-store ] [--stream-action ] [--target-host ] [--media-transport ] [--media-rudp-bind ] [--media-rudp-advertise ] [--media-packet-bytes ] [--rudp-video-bitrate-kbps ] [--rudp-latency-budget-ms ] [--video-source ] [--audio-source ] [--audio-source-id ] [--no-video] [--no-audio] [--loopback-script ] [--ffmpeg ] [--odin-cultmesh-uri ] [--move-state =] [--move-marker-camera =] [--move-psmoveapi-tracker] [--move-tracker-exposure-milli <0..1000>] [--move-marker-width ] [--move-marker-height ] [--move-marker-fps ] [--move-host ] [--move-evidence-stream ] [--move-evidence-verse ] [--move-evidence-ring-slots ] [--move-evidence-slot-bytes ] [--move-evidence-snapshot ] [--quest-adb] [--quest-serial ] [--quest-input-stream ] [--quest-pose-stream ] [--quest-video-input-stream ] [--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." } fn parse_move_state_source(value: &str) -> Result { @@ -12665,6 +12668,64 @@ Device 00:07:04:A8:00:D0 (public) let _ = fs::remove_file(store_path); } + #[test] + fn help_names_no_retired_health_flag() { + assert!(!help_text().contains("--idunn")); + assert!(!help_text().contains("daemon health")); + } + + #[test] + fn runtime_boundary_names_each_host_daemon_and_its_transport() { + for (host, daemon_id) in [ + ("raven", "muninn"), + ("starfire", "starfire-muninn"), + ("nightwing", "nightwing-muninn"), + ] { + let store_path = std::env::temp_dir().join(format!( + "muninn-boundary-{host}-{}.cc", + timestamp_ns().unwrap() + )); + let options = Options::parse( + ["serve", "--host", host, "--store", store_path.to_str().unwrap()] + .into_iter() + .map(String::from), + ) + .unwrap(); + let mut node = open_node(&options, "muninn-boundary-host-test").unwrap(); + + publish_runtime_boundary_records(&mut node, &options, "idle", &[], &[]).unwrap(); + + let boundary = node + .get_required::(&format!( + "command-boundary:{daemon_id}" + )) + .unwrap(); + let transport = node + .get_required::(&format!( + "transport-profile:{daemon_id}" + )) + .unwrap(); + for record in [&boundary.value, &transport.value] { + assert_eq!( + record.get("daemon_id").and_then(|value| value.as_str()), + Some(daemon_id), + "{host}" + ); + } + assert_eq!( + transport.value.get("current_transport").and_then(|value| value.as_str()), + Some("daemon-owned-cultcache-telemetry-store"), + "{host}" + ); + assert_eq!( + transport.value.get("state").and_then(|value| value.as_str()), + Some("cultcache-provider-store-only"), + "{host}" + ); + let _ = fs::remove_file(store_path); + } + } + #[test] fn runtime_boundary_records_use_explicit_activation_store_path() { let store_path = diff --git a/docs/muninn.md b/docs/muninn.md index bd9f5b5..b262640 100644 --- a/docs/muninn.md +++ b/docs/muninn.md @@ -239,6 +239,11 @@ 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. +Starfire and Nightwing binaries and their `scripts/*-muninn.ps1` actuators +must be installed together: a binary that rejects the retired +`--idunn-rudp-health`, `--idunn-daemon` and `--idunn-health-contract` flags +fails to start under an old restart script, and a new script never passes them. + Each private PSMoveAPI Eye worker owns optical observation admission before the parent aggregates evidence. Positions older than 50 ms, blobs below 2 px, out-of-frame coordinates, implausible radius jumps, and frame-to-frame diff --git a/scripts/health-muninn.cmd b/scripts/health-muninn.cmd index 7811b7d..e2ddbe7 100644 --- a/scripts/health-muninn.cmd +++ b/scripts/health-muninn.cmd @@ -1,3 +1,3 @@ @echo off -echo health-muninn.cmd archived: Raven Muninn health is daemon-published over CultNet/RUDP as muninn.cultnet-rudp-remote-telemetry-health. Use health-muninn.ps1 only as an explicit witness inspection, not Idunn health truth. +echo health-muninn.cmd archived: Raven Muninn has no daemon-published health. Run health-muninn.ps1 as an explicit witness inspection (scheduled-task actions, serve process, store freshness); it is not Idunn health truth. exit /b 2 diff --git a/scripts/health-nightwing-muninn.cmd b/scripts/health-nightwing-muninn.cmd index 6b6554b..eede45d 100644 --- a/scripts/health-nightwing-muninn.cmd +++ b/scripts/health-nightwing-muninn.cmd @@ -1,3 +1,3 @@ @echo off -echo health-nightwing-muninn.cmd archived: Nightwing Muninn health is daemon-published over CultNet/RUDP as muninn.cultnet-rudp-remote-telemetry-and-move-hid. Use health-nightwing-muninn.ps1 only as an explicit witness inspection, not Idunn health truth. +echo health-nightwing-muninn.cmd archived: Nightwing Muninn has no daemon-published health. Run health-nightwing-muninn.ps1 as an explicit witness inspection (serve process, store freshness, PID file); it is not Idunn health truth. exit /b 2 diff --git a/scripts/health-starfire-muninn.cmd b/scripts/health-starfire-muninn.cmd index 36d421c..db26de6 100644 --- a/scripts/health-starfire-muninn.cmd +++ b/scripts/health-starfire-muninn.cmd @@ -1,3 +1,3 @@ @echo off -echo health-starfire-muninn.cmd archived: Starfire Muninn health is daemon-published over CultNet/RUDP as muninn.cultnet-rudp-local-telemetry-and-quest-access. Use health-starfire-muninn.ps1 only as an explicit witness inspection, not Idunn health truth. +echo health-starfire-muninn.cmd archived: Starfire Muninn has no daemon-published health. Run health-starfire-muninn.ps1 as an explicit witness inspection (serve process, store freshness, PID file); it is not Idunn health truth. exit /b 2