Serve Odin snapshots and retire Gjallar bridge debt
This commit is contained in:
parent
3bfb80143a
commit
b43d2a3136
7 changed files with 322 additions and 89 deletions
19
README.md
19
README.md
|
|
@ -21,16 +21,17 @@ documents, ingest ports, normalization, and repository persistence so unit tests
|
|||
can use mocked inputs and pipeline smokes can prove typed handoff without
|
||||
booting the whole daemon. Gjallar is not part of that Rust record spine: it is
|
||||
the Nightwing-resident terminal compositor in `E:\Projects\Gjallar` that
|
||||
consumes Odin's published Eve/CultUI deck and renders the live display.
|
||||
consumes Odin's accepted `gamecult.eve.surface_state` snapshot over CultNet/RUDP
|
||||
and renders the live display.
|
||||
|
||||
## Gjallar
|
||||
|
||||
Gjallar is the herald display daemon that runs on Nightwing. Odin sees the
|
||||
Verses, accepts provider surfaces, and publishes the `odin.providers` catalog.
|
||||
Gjallar talks to Odin, enumerates active providers, composes the multi-scale
|
||||
tiled dashboard, lowers Odin's canonical marquee tape into continuous gutter
|
||||
text, owns dense character-level update behavior, and writes the visible
|
||||
framebuffer.
|
||||
Gjallar consumes Odin's accepted surface snapshot over CultNet/RUDP, composes
|
||||
the multi-scale tiled dashboard from that typed state, lowers Odin's canonical
|
||||
marquee tape into continuous gutter text, owns dense character-level update
|
||||
behavior, and writes the visible framebuffer.
|
||||
|
||||
Local package surfaces:
|
||||
|
||||
|
|
@ -95,7 +96,13 @@ The Eve/CultUI deck endpoint is a compatibility bridge:
|
|||
ws://127.0.0.1:8797/eve/deck
|
||||
```
|
||||
|
||||
Nightwing can consume the LAN bridge endpoint:
|
||||
The native CultNet/RUDP snapshot endpoint is:
|
||||
|
||||
```text
|
||||
127.0.0.1:17871
|
||||
```
|
||||
|
||||
Legacy browser/deck lowerers can consume the LAN bridge endpoint:
|
||||
|
||||
```text
|
||||
ws://192.168.1.66:8797/eve/deck
|
||||
|
|
|
|||
|
|
@ -951,14 +951,7 @@ fn swarm_surgery_plan(
|
|||
plans: &[IdunnDaemonSurgeryPlanRecord],
|
||||
updated_at: &str,
|
||||
) -> IdunnSwarmSurgeryPlanRecord {
|
||||
let next_target = preferred_next_target(targets, plans);
|
||||
let next_target = if let Some(target) = next_target {
|
||||
target
|
||||
} else if let Some(target) = targets.iter().find(|target| target.enabled) {
|
||||
target.daemon_id.as_str()
|
||||
} else {
|
||||
"none"
|
||||
};
|
||||
let next_target = preferred_next_target(targets, plans).unwrap_or("none");
|
||||
|
||||
IdunnSwarmSurgeryPlanRecord {
|
||||
plan_id: format!("swarm-surgery:{profile}"),
|
||||
|
|
@ -987,11 +980,11 @@ fn swarm_surgery_plan(
|
|||
"5. Delete or demote compatibility probes once every target has daemon-owned publication and advertised lifecycle authority.".to_string(),
|
||||
],
|
||||
current_phase:
|
||||
"Phase 24: Nightwing Gjallar now publishes a daemon-owned gamecult.eve.surface_state witness, Odin proxies a real Nightwing Gjallar panel from the live remote store, and the remaining Gjallar debt is replacing the compatibility Odin WebSocket deck input with native CultMesh/CultNet intake."
|
||||
"Phase 25: Nightwing Gjallar now consumes Odin's accepted gamecult.eve.surface_state snapshot over CultNet/RUDP, so every active Starfire-local daemon target publishes daemon-owned RUDP health and typed boundary state; the remaining debt is demoting compatibility probes and bridge-only lowerings without letting them retake ownership."
|
||||
.to_string(),
|
||||
next_target: next_target.to_string(),
|
||||
cut_line:
|
||||
"Muninn, Vili, Idunn, Odin, Stonks, Weksa, VoidBot, Nightwing Gjallar, Mimir Eve dashboard, Nightwing Eve dashboard, Nightwing Eve browser reference, yggdrasil-streampixels, yggdrasil-heimdall, and yggdrasil-repixelizer now exercise daemon-owned RUDP health. Raven GameCult\\Vili has been refreshed from Odin, the hidden scheduled task now launches start-vili-daemon.ps1 with Idunn RUDP arguments, and live Idunn accepts vili.cultnet-rudp-animation-health from 10.77.0.4. VoidBot now publishes a daemon-owned provider catalog, provider advertisements, command_boundary, and transport_profile records from E:\\Projects\\VoidBot\\.voidbot\\status\\cultmesh\\voidbot-swarm-state.cc while health-voidbot.cmd is demoted to a fast compatibility witness. Raven Muninn now publishes provider advertisement, command_boundary, and transport_profile records from C:\\Meta\\Odin\\state\\muninn.telemetry.cc, Starfire Muninn now publishes the same daemon-owned witness records from C:\\Meta\\Odin\\state\\starfire.muninn.telemetry.cc, Nightwing Muninn now publishes the same daemon-owned witness records from /home/metacrat/.local/state/gamecult/muninn/muninn.telemetry.cc, and live Idunn accepts muninn from 10.77.0.4, starfire-muninn from 127.0.0.1, and nightwing-muninn from 10.77.0.3. Raven's GameCult-Muninn, GameCult-Muninn-Activate, and GameCult-Muninn-VideoProof tasks now execute wscript.exe actions whose hidden VBS launchers call noninteractive hidden PowerShell entrypoints directly; .cmd wrappers, where present, are manual compatibility entrypoints only. Live yggdrasil-streampixels publishes streampixels.cultnet-rudp-service-health from 10.77.0.1, live yggdrasil-heimdall publishes heimdall.cultnet-rudp-provider-health from 10.77.0.1 with a boundary store at /srv/heimdall/cultcache/heimdall.service.cc, live yggdrasil-repixelizer publishes repixelizer.cultnet-rudp-service-health from 10.77.0.1 with a boundary store at /srv/repixelizer/cultcache/repixelizer.service.cc, live Nightwing Gjallar publishes a boundary store at /var/lib/gamecult/gjallar/cultcache/gjallar.service.cc plus a daemon-owned gamecult.eve.surface_state witness, live Odin now ingests remote Raven Vili and Nightwing Gjallar witness stores into the accepted provider catalog, and /eve/deck/nightwing-gjallar now proxies a real Nightwing Gjallar panel from the live remote store. Live Mimir Eve dashboard now publishes CultMesh state at /var/lib/gamecult/eve-dashboard/cultmesh/eve-dashboard.ccmp plus a boundary store at /var/lib/gamecult/eve-dashboard/cultcache/eve-dashboard.service.cc, and live Nightwing Eve browser reference now publishes a boundary store at /var/lib/gamecult/eve-browser-reference/cultcache/eve-browser-reference.service.cc. Gjallar remains the next compatibility debt until its native CultMesh/CultNet input path replaces the Odin WebSocket deck bridge."
|
||||
"Muninn, Vili, Idunn, Odin, Stonks, Weksa, VoidBot, Nightwing Gjallar, Mimir Eve dashboard, Nightwing Eve dashboard, Nightwing Eve browser reference, yggdrasil-streampixels, yggdrasil-heimdall, and yggdrasil-repixelizer now exercise daemon-owned RUDP health. Raven GameCult\\Vili has been refreshed from Odin, the hidden scheduled task now launches start-vili-daemon.ps1 with Idunn RUDP arguments, and live Idunn accepts vili.cultnet-rudp-animation-health from 10.77.0.4. VoidBot now publishes a daemon-owned provider catalog, provider advertisements, command_boundary, and transport_profile records from E:\\Projects\\VoidBot\\.voidbot\\status\\cultmesh\\voidbot-swarm-state.cc while health-voidbot.cmd is demoted to a fast compatibility witness. Raven Muninn now publishes provider advertisement, command_boundary, and transport_profile records from C:\\Meta\\Odin\\state\\muninn.telemetry.cc, Starfire Muninn now publishes the same daemon-owned witness records from C:\\Meta\\Odin\\state\\starfire.muninn.telemetry.cc, Nightwing Muninn now publishes the same daemon-owned witness records from /home/metacrat/.local/state/gamecult/muninn/muninn.telemetry.cc, and live Idunn accepts muninn from 10.77.0.4, starfire-muninn from 127.0.0.1, and nightwing-muninn from 10.77.0.3. Raven's GameCult-Muninn, GameCult-Muninn-Activate, and GameCult-Muninn-VideoProof tasks now execute wscript.exe actions whose hidden VBS launchers call noninteractive hidden PowerShell entrypoints directly; .cmd wrappers, where present, are manual compatibility entrypoints only. Live yggdrasil-streampixels publishes streampixels.cultnet-rudp-service-health from 10.77.0.1, live yggdrasil-heimdall publishes heimdall.cultnet-rudp-provider-health from 10.77.0.1 with a boundary store at /srv/heimdall/cultcache/heimdall.service.cc, live yggdrasil-repixelizer publishes repixelizer.cultnet-rudp-service-health from 10.77.0.1 with a boundary store at /srv/repixelizer/cultcache/repixelizer.service.cc, live Nightwing Gjallar publishes a boundary store at /var/lib/gamecult/gjallar/cultcache/gjallar.service.cc plus a daemon-owned gamecult.eve.surface_state witness, live Gjallar now consumes Odin's accepted surface:gamecult.network.status snapshot over CultNet/RUDP from 192.168.1.66:17871, live Odin now ingests remote Raven Vili and Nightwing Gjallar witness stores into the accepted provider catalog, and /eve/deck/nightwing-gjallar now proxies a renderer-only lowering of the live remote store. Live Mimir Eve dashboard now publishes CultMesh state at /var/lib/gamecult/eve-dashboard/cultmesh/eve-dashboard.ccmp plus a boundary store at /var/lib/gamecult/eve-dashboard/cultcache/eve-dashboard.service.cc, and live Nightwing Eve browser reference now publishes a boundary store at /var/lib/gamecult/eve-browser-reference/cultcache/eve-browser-reference.service.cc. The remaining debt is demoting compatibility probes and bridge-only lowerings without letting them retake daemon truth."
|
||||
.to_string(),
|
||||
verification_layer:
|
||||
"CultMesh keepalive store records plus live Idunn decision cycles, not process exit codes or chat summaries."
|
||||
|
|
@ -1238,9 +1231,9 @@ fn daemon_transport_profile(
|
|||
"VoidBot stack health is published over CultNet/RUDP from the local orchestrator pulse, and the daemon-owned CultMesh witness store at E:\\Projects\\VoidBot\\.voidbot\\status\\cultmesh\\voidbot-swarm-state.cc now carries provider advertisement catalog, provider advertisement, command_boundary, and transport_profile records. The operations probe remains fallback evidence only.",
|
||||
),
|
||||
"nightwing-gjallar" => (
|
||||
"daemon-published-rudp-health + daemon-owned-cultcache-service-boundary + compatibility.local-command fallback",
|
||||
"daemon-published-rudp-health + daemon-owned-cultcache-service-boundary + native-odin-cultnet-rudp-snapshot-input + compatibility.local-command fallback",
|
||||
"partial-rudp-health-and-provider-store-live",
|
||||
"Gjallar framebuffer composition health is published over CultNet/RUDP from Nightwing, and the daemon-owned boundary store at /var/lib/gamecult/gjallar/cultcache/gjallar.service.cc carries provider advertisement, runtime_config, frame_status, command_boundary, transport_profile, and daemon-health summary state. Native C# CultNet/RUDP deck input remains migration debt before the service/status probe can be deleted.",
|
||||
"Gjallar framebuffer composition health is published over CultNet/RUDP from Nightwing, the daemon-owned boundary store at /var/lib/gamecult/gjallar/cultcache/gjallar.service.cc carries provider advertisement, runtime_config, frame_status, gamecult.eve.surface_state, command_boundary, transport_profile, and daemon-health summary state, and live composition now consumes Odin's accepted surface:gamecult.network.status snapshot over CultNet/RUDP. The service/status probe is fallback evidence only.",
|
||||
),
|
||||
"nightwing-eve-dashboard" => (
|
||||
"daemon-published-rudp-health + daemon-owned-cultmesh-state + daemon-owned-cultcache-boundary-store + compatibility.local-command fallback",
|
||||
|
|
@ -1401,23 +1394,23 @@ fn daemon_surgery_plan(target: &DaemonTarget, updated_at: &str) -> IdunnDaemonSu
|
|||
.to_string();
|
||||
}
|
||||
"nightwing-gjallar" => {
|
||||
status = "catalog-coherence-probe";
|
||||
status = "partial-rudp-health-and-provider-store-live";
|
||||
severity = "critical";
|
||||
owner = "Gjallar C# runtime plus Odin provider feed";
|
||||
owner = "Gjallar C# runtime plus Odin accepted snapshot surface";
|
||||
current_mechanism =
|
||||
"Nightwing Gjallar publishes gjallar.cultnet-rudp-framebuffer-composition-health over CultNet/RUDP from the C# runtime, and the daemon-owned boundary store at /var/lib/gamecult/gjallar/cultcache/gjallar.service.cc now contains provider advertisement, gamecult.eve.surface_state, runtime_config, frame_status, command_boundary, transport_profile, and daemon-health summary records. Live Odin ingests that witness store and now proxies /eve/deck/nightwing-gjallar as a real panel."
|
||||
"Nightwing Gjallar publishes gjallar.cultnet-rudp-framebuffer-composition-health over CultNet/RUDP from the C# runtime, the daemon-owned boundary store at /var/lib/gamecult/gjallar/cultcache/gjallar.service.cc now contains provider advertisement, gamecult.eve.surface_state, runtime_config, frame_status, command_boundary, transport_profile, and daemon-health summary records, and the live compositor now polls Odin's accepted surface:gamecult.network.status snapshot over cultnet.transport.rudp.v0 from 192.168.1.66:17871. Live Odin ingests that witness store and /eve/deck/nightwing-gjallar remains a renderer-only lowering."
|
||||
.to_string();
|
||||
intended_authority =
|
||||
"Gjallar subscribes to Odin/provider deck state over CultNet/RUDP and publishes framebuffer composition health, provider advertisement, provider-owned Eve surface, runtime config, command boundary, and transport profile as typed CultMesh/CultNet state."
|
||||
"Gjallar consumes Odin's accepted provider surface snapshot over CultNet/RUDP and publishes framebuffer composition health, provider advertisement, provider-owned Eve surface, runtime config, command boundary, and transport profile as typed CultMesh/CultNet state."
|
||||
.to_string();
|
||||
cut_line =
|
||||
"Keep the service/status probe as fallback only until Gjallar's native CultMesh/RUDP input path replaces the Odin WebSocket deck bridge; the daemon-owned witness now owns provider advertisement, gamecult.eve.surface_state, command_boundary, and transport_profile truth, and the remaining debt is deleting the compatibility deck bridge from the live input path."
|
||||
"Keep the service/status probe as fallback only; the daemon-owned witness now owns provider advertisement, gamecult.eve.surface_state, command_boundary, and transport_profile truth, live input now comes from Odin's accepted CultNet/RUDP snapshot surface, and the remaining debt is deleting or demoting compatibility probes and renderer-only deck lowerings without letting them reclaim daemon truth."
|
||||
.to_string();
|
||||
steps = vec![
|
||||
"Keep live gjallar.cultnet-rudp-framebuffer-composition-health publication running from Nightwing's Gjallar service.".to_string(),
|
||||
"Keep Gjallar's daemon-owned boundary store at /var/lib/gamecult/gjallar/cultcache/gjallar.service.cc publishing provider advertisement, gamecult.eve.surface_state, runtime_config, frame_status, command_boundary, transport_profile, and daemon-health summary records.".to_string(),
|
||||
"Replace Odin WebSocket deck consumption with a native C# CultMesh/RUDP input path when the deck contract is ready.".to_string(),
|
||||
"Keep Odin's hashed-store ingestion shim strictly as a xenos-boundary bridge until Gjallar's native CultMesh/CultNet input path and interoperable witness path make the compatibility deck bridge disposable.".to_string(),
|
||||
"Keep the native C# CultNet/RUDP snapshot polling path pointed at Odin's accepted surface:gamecult.network.status surface on 192.168.1.66:17871.".to_string(),
|
||||
"Keep /eve/deck/nightwing-gjallar as a renderer-only lowering; it must not become Gjallar's live input path again.".to_string(),
|
||||
"Delete or demote health-nightwing-gjallar.cmd to a manual deployment witness with no lifecycle truth.".to_string(),
|
||||
];
|
||||
}
|
||||
|
|
@ -2829,9 +2822,9 @@ mod tests {
|
|||
|
||||
assert_eq!(plan.plan_id, "swarm-surgery:starfire-local");
|
||||
assert_eq!(plan.status, "active-transport-migration");
|
||||
assert_eq!(plan.next_target, "nightwing-gjallar");
|
||||
assert_eq!(plan.next_target, "none");
|
||||
assert!(plan.current_phase.contains(
|
||||
"Nightwing Gjallar now publishes a daemon-owned gamecult.eve.surface_state witness"
|
||||
"Nightwing Gjallar now consumes Odin's accepted gamecult.eve.surface_state snapshot over CultNet/RUDP"
|
||||
));
|
||||
assert!(
|
||||
plan.cut_line.contains(
|
||||
|
|
@ -2904,10 +2897,9 @@ mod tests {
|
|||
assert!(plan.cut_line.contains(
|
||||
"/var/lib/gamecult/eve-browser-reference/cultcache/eve-browser-reference.service.cc"
|
||||
));
|
||||
assert!(
|
||||
plan.cut_line
|
||||
.contains("Gjallar remains the next compatibility debt")
|
||||
);
|
||||
assert!(plan.cut_line.contains("surface:gamecult.network.status"));
|
||||
assert!(plan.cut_line.contains("192.168.1.66:17871"));
|
||||
assert!(plan.cut_line.contains("renderer-only lowering"));
|
||||
assert!(plan.verification_layer.contains("CultMesh keepalive store"));
|
||||
assert!(
|
||||
plan.invariants
|
||||
|
|
@ -3033,13 +3025,21 @@ mod tests {
|
|||
);
|
||||
|
||||
let gjallar_plan = daemon_surgery_plan(&nightwing_gjallar, "unix:100");
|
||||
assert_eq!(gjallar_plan.status, "catalog-coherence-probe");
|
||||
assert_eq!(
|
||||
gjallar_plan.status,
|
||||
"partial-rudp-health-and-provider-store-live"
|
||||
);
|
||||
assert!(
|
||||
gjallar_plan
|
||||
.current_mechanism
|
||||
.contains("gjallar.cultnet-rudp-framebuffer-composition-health")
|
||||
);
|
||||
assert!(gjallar_plan.cut_line.contains("gamecult.eve.surface_state"));
|
||||
assert!(
|
||||
gjallar_plan
|
||||
.current_mechanism
|
||||
.contains("surface:gamecult.network.status")
|
||||
);
|
||||
|
||||
let nightwing_eve_dashboard_plan =
|
||||
daemon_surgery_plan(&nightwing_eve_dashboard, "unix:100");
|
||||
|
|
@ -3423,13 +3423,14 @@ mod tests {
|
|||
assert_eq!(profile.state, "partial-rudp-health-and-provider-store-live");
|
||||
assert_eq!(
|
||||
profile.current_transport,
|
||||
"daemon-published-rudp-health + daemon-owned-cultcache-service-boundary + compatibility.local-command fallback"
|
||||
"daemon-published-rudp-health + daemon-owned-cultcache-service-boundary + native-odin-cultnet-rudp-snapshot-input + compatibility.local-command fallback"
|
||||
);
|
||||
assert!(
|
||||
profile
|
||||
.cut_line
|
||||
.contains("Gjallar framebuffer composition health is published over CultNet/RUDP")
|
||||
);
|
||||
assert!(profile.cut_line.contains("surface:gamecult.network.status"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
param(
|
||||
[int] $Port = 8797,
|
||||
[string] $StateDir = "E:\Projects\Odin\scratch\odin",
|
||||
[string] $CultNetRudpBind = "0.0.0.0:17871",
|
||||
[string] $IdunnRudpHealth = "127.0.0.1:17870",
|
||||
[string] $IdunnDaemon = "odin",
|
||||
[string] $IdunnHealthContract = "odin.cultnet-rudp-provider-health",
|
||||
|
|
@ -33,6 +34,7 @@ $args = @(
|
|||
$scriptPath,
|
||||
"--port", "$Port",
|
||||
"--stateDir", $StateDir,
|
||||
"--cultnet-rudp-bind", $CultNetRudpBind,
|
||||
"--idunn-rudp-health", $IdunnRudpHealth,
|
||||
"--idunn-daemon", $IdunnDaemon,
|
||||
"--idunn-health-contract", $IdunnHealthContract
|
||||
|
|
@ -56,3 +58,4 @@ if ($proc.HasExited) {
|
|||
Write-Host "Odin started as PID $($proc.Id)."
|
||||
Write-Host "Health: http://127.0.0.1:$Port/health"
|
||||
Write-Host "Eve deck: ws://127.0.0.1:$Port/eve/deck"
|
||||
Write-Host "CultNet/RUDP snapshot: $CultNetRudpBind"
|
||||
|
|
|
|||
|
|
@ -56,9 +56,11 @@ Idunn is a Rust daemon inside Odin's Cargo workspace. The live local runtime is
|
|||
one long-lived `idunn.exe` process that owns the whole Starfire-local swarm:
|
||||
Odin, local adjunct daemons, the Yggdrasil deploy lanes, and the Nightwing
|
||||
display services. Each target declares a daemon-owned health contract and keeps
|
||||
its own interval and deploy/restart authority. The current shell health
|
||||
commands are compatibility probes until each daemon updates its CultLib
|
||||
dependency and publishes health through CultNet over the shared RUDP transport.
|
||||
its own interval and deploy/restart authority. The shell health commands now
|
||||
exist as compatibility probes and actuators; daemon truth is expected to come
|
||||
first from typed CultNet/RUDP publication and daemon-owned boundary stores, and
|
||||
the local commands only matter when that witness is absent or when an explicit
|
||||
restart/deploy boundary is advertised.
|
||||
The scheduler and continuity witness now belong to one Rust process instead of
|
||||
a PowerShell-herded pile of one-daemon workers.
|
||||
|
||||
|
|
@ -220,61 +222,33 @@ probe. The record must match the daemon id, health contract, RUDP transport,
|
|||
and freshness window; otherwise the compatibility probe remains fallback
|
||||
evidence.
|
||||
|
||||
The current active cut has moved past Raven daemon launcher surgery and into the
|
||||
remaining Yggdrasil deployments, with repixelizer now the explicit next target.
|
||||
Heimdall has crossed the first live transport cut: the Yggdrasil runtime now
|
||||
publishes `heimdall.cultnet-rudp-provider-health`, writes a daemon-owned
|
||||
boundary witness at `/srv/heimdall/cultcache/heimdall.service.cc`, and ships
|
||||
the required `CultLib` snapshot through the source-artifact lane. Heimdall
|
||||
still owes Odin ingestion of that typed store plus the later redacted
|
||||
auth-document witness export, but it is no longer blocked on basic daemon
|
||||
health publication. Vili now publishes RUDP health from Raven
|
||||
through the hidden `GameCult\Vili` scheduled task, and live Idunn accepts
|
||||
`vili.cultnet-rudp-animation-health` from `10.77.0.4`. The Raven repair
|
||||
actuator in [restart-vili.ps1](/E:/Projects/Odin/scripts/restart-vili.ps1)
|
||||
syncs the authoritative Vili runtime plus flattened CultLib node modules before
|
||||
reinstalling the hidden task. Vili also writes a daemon-owned
|
||||
`.vili\vili.service.cc` store with provider advertisement, operator state, Eve
|
||||
surface, command boundary, and transport profile records; Odin local discovery
|
||||
can ingest that typed store. Weksa publishes RUDP health and a daemon-owned
|
||||
provider store containing provider advertisement, operator state, Eve surface,
|
||||
command boundary, and transport profile records; Odin local discovery can ingest
|
||||
that typed store too. Weksa still owes CultNet/RUDP command document ingress
|
||||
for MiMo VoiceDesign before its HTTP command route can become debug-only.
|
||||
Stonks publishes RUDP health and a daemon-owned CultCache store containing
|
||||
provider advertisement, market snapshot, Eve surface, command boundary, and
|
||||
transport profile records; Odin local discovery can ingest that store as
|
||||
provider truth. StreamPixels now publishes a service-owned CultCache boundary
|
||||
store with provider advertisement, command boundary, transport profile, and
|
||||
Idunn health summary; Odin local discovery can ingest that store, and the live
|
||||
Yggdrasil service now publishes daemon health over CultNet/RUDP with
|
||||
`STREAMPIXELS_IDUNN_RUDP_HEALTH=10.77.0.2:17870` and contract
|
||||
`streampixels.cultnet-rudp-service-health` in
|
||||
`/srv/streampixels/env/service.env`. Live Idunn accepts
|
||||
`yggdrasil-streampixels` from `10.77.0.1`, and the service-owned boundary store
|
||||
now lives at
|
||||
`/srv/streampixels/app/.streampixels-data/cultcache/streampixels.service.cc`.
|
||||
The remaining debt is only to demote SSH/systemd/HTTP probes from compatibility
|
||||
proof to deployment/debug witness once Odin consumes the typed store without
|
||||
fallback. Repixelizer is still plain GUI/systemd compatibility debt for now,
|
||||
which makes it the next Yggdrasil surgery target:
|
||||
`repixelizer-gui.service`, `/api/health`, `/api/config`, and nginx routing are
|
||||
still the live witnesses until the runtime publishes internal RUDP health and
|
||||
typed queue/provider state. Raven Muninn task actions are also an explicit ops invariant: Task
|
||||
Scheduler must execute `wscript.exe //B //Nologo` hidden launcher actions
|
||||
directly for `GameCult-Muninn`, `GameCult-Muninn-Activate`, and
|
||||
The active transport cut is no longer "make the daemons speak RUDP at all."
|
||||
Nightwing Gjallar now consumes Odin's accepted
|
||||
`surface:gamecult.network.status` snapshot over CultNet/RUDP, so every active
|
||||
Starfire-local daemon target now publishes daemon-owned RUDP health plus typed
|
||||
boundary state. The remaining debt is deleting or demoting compatibility
|
||||
probes, HTTP/WebSocket/TCP lowerings, and xenos-boundary ingestion shims
|
||||
without letting them reclaim ownership. Weksa still owes CultNet/RUDP command
|
||||
document ingress for MiMo VoiceDesign before its HTTP command route can become
|
||||
debug-only, and Odin still owes removal of hashed-store/C# witness shims once
|
||||
direct interoperable witness publication is ready.
|
||||
|
||||
Raven Muninn task actions are also an explicit ops invariant: Task Scheduler
|
||||
must execute `wscript.exe //B //Nologo` hidden launcher actions directly for
|
||||
`GameCult-Muninn`, `GameCult-Muninn-Activate`, and
|
||||
`GameCult-Muninn-VideoProof`, never raw `.cmd` wrappers, and the hidden VBS
|
||||
layer must call `.ps1` launchers directly instead of `cmdPath` trampolines.
|
||||
Those three live Raven tasks have been repaired and verified. The repair actuator now uploads its
|
||||
Raven PowerShell body with `sftp` and runs a tiny cleanup wrapper, so the
|
||||
hidden-task repair does not hit Windows command-line limits before it can run.
|
||||
The long-running Muninn serve bodies on Raven, Nightwing, and Starfire now also
|
||||
carry their own `--idunn-rudp-health`, `--idunn-daemon`, and
|
||||
`--idunn-health-contract` arguments, and live Idunn accepts those daemon-owned
|
||||
health records directly instead of relying on one-shot health commands for the
|
||||
truth.
|
||||
After those live cuts, continue daemon-by-daemon until compatibility HTTP, TCP,
|
||||
WebSocket, and command probes are fallback witnesses only.
|
||||
Those three live Raven tasks have been repaired and verified. The repair
|
||||
actuator now uploads its Raven PowerShell body with `sftp` and runs a tiny
|
||||
cleanup wrapper, so the hidden-task repair does not hit Windows command-line
|
||||
limits before it can run. The long-running Muninn serve bodies on Raven,
|
||||
Nightwing, and Starfire now also carry their own `--idunn-rudp-health`,
|
||||
`--idunn-daemon`, and `--idunn-health-contract` arguments, and live Idunn
|
||||
accepts those daemon-owned health records directly instead of relying on
|
||||
one-shot health commands for the truth.
|
||||
|
||||
From here on out, the work is not "teach another daemon to speak." It is
|
||||
"delete the witness masks and keep ownership where it belongs."
|
||||
|
||||
Rust no longer gets to claim the transport is imaginary:
|
||||
`vendor/cultnet-rs` includes the canonical CNR0 `cultnet.transport.rudp.v0`
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ const fs = require("fs");
|
|||
const path = require("path");
|
||||
|
||||
const { buildConfig, loadCultRuntime } = require("./odin/config.cjs");
|
||||
const { createCultNetRudpSurfaceServer } = require("./odin/cultnet-rudp.cjs");
|
||||
const { defineOdinDocuments } = require("./odin/documents.cjs");
|
||||
const { createInterfaceDiscovery } = require("./odin/interfaces.cjs");
|
||||
const { createIdunnRudpHealthPublisher, publishIdunnRudpHealth } = require("./odin/idunn-rudp.cjs");
|
||||
|
|
@ -41,6 +42,7 @@ const stateBuilder = createStateBuilder({
|
|||
});
|
||||
|
||||
let meshNodePromise = null;
|
||||
let cultNetRudpSurfaceServer = null;
|
||||
let currentState = stateBuilder.buildPendingState("Coordinator starting");
|
||||
let lastRefresh = {
|
||||
completedAt: null,
|
||||
|
|
@ -59,6 +61,20 @@ async function main() {
|
|||
if (cultRuntime.CultMesh && documents.surfaceDefinition) {
|
||||
meshNodePromise = createDurableSurfaceNode();
|
||||
}
|
||||
if (config.cultnetRudpBind) {
|
||||
cultNetRudpSurfaceServer = createCultNetRudpSurfaceServer({
|
||||
bind: config.cultnetRudpBind,
|
||||
documents,
|
||||
getCache: async () => {
|
||||
if (!meshNodePromise) {
|
||||
throw new Error("CultMesh runtime is unavailable; Odin cannot serve a CultNet/RUDP snapshot.");
|
||||
}
|
||||
return (await meshNodePromise).cache;
|
||||
},
|
||||
});
|
||||
await cultNetRudpSurfaceServer.start();
|
||||
console.log(`CultNet/RUDP snapshot: ${config.cultnetRudpBind}`);
|
||||
}
|
||||
|
||||
const dashboardServer = createDashboardServer({
|
||||
applyClientCommand: (command) => layoutStore.applyClientCommand(command),
|
||||
|
|
@ -177,6 +193,7 @@ function health(clients) {
|
|||
error: cultRuntime.error?.message || null,
|
||||
},
|
||||
discovery: {
|
||||
cultNetRudpBind: config.cultnetRudpBind || null,
|
||||
seedDeckUrls: config.seedDeckUrls,
|
||||
discoveredDeckUrls: interfaceDiscovery.getDiscoveredDeckUrls(),
|
||||
interfaceBindingStores: config.interfaceBindingStores,
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ function buildConfig(argv) {
|
|||
const args = parseArgs(argv);
|
||||
const port = Number(args.port || 8797);
|
||||
const host = args.host || "0.0.0.0";
|
||||
const cultnetRudpBind = args["cultnet-rudp-bind"] ? String(args["cultnet-rudp-bind"]) : "";
|
||||
const intervalMs = Number(args.intervalMs || 5000);
|
||||
const stateDir = args.stateDir || path.join(repoRoot, "scratch", "odin");
|
||||
const cachePath = args.cachePath || path.join(stateDir, "odin.ccmp");
|
||||
|
|
@ -57,6 +58,7 @@ function buildConfig(argv) {
|
|||
return {
|
||||
args,
|
||||
cachePath,
|
||||
cultnetRudpBind,
|
||||
gamecultTextDocumentStorePath,
|
||||
host,
|
||||
interfaceBindingStores,
|
||||
|
|
|
|||
229
src/odin/cultnet-rudp.cjs
Normal file
229
src/odin/cultnet-rudp.cjs
Normal file
|
|
@ -0,0 +1,229 @@
|
|||
"use strict";
|
||||
|
||||
const dgram = require("dgram");
|
||||
const path = require("path");
|
||||
const { createRequire } = require("module");
|
||||
|
||||
const requireCultNet = createRequire(path.resolve(
|
||||
__dirname,
|
||||
"..",
|
||||
"..",
|
||||
"..",
|
||||
"CultLib",
|
||||
"packages",
|
||||
"cultnet-ts",
|
||||
"package.json",
|
||||
));
|
||||
|
||||
const {
|
||||
CultNetDocumentRegistry,
|
||||
CultNetRudpSession,
|
||||
cultNetBuiltinSchemaRegistry,
|
||||
decodeRudpPacket,
|
||||
defineCultNetDocumentBinding,
|
||||
encodeCultNetMessageForWire,
|
||||
encodeRudpPacket,
|
||||
parseCultNetMessage,
|
||||
} = requireCultNet("./dist/index.js");
|
||||
const { decode, encode } = requireCultNet("@msgpack/msgpack");
|
||||
|
||||
const ODIN_RUDP_CONNECTION_ID = 0x0d1d0002;
|
||||
const DEFAULT_SESSION_TIMEOUT_MS = 30_000;
|
||||
|
||||
function createCultNetRudpSurfaceServer(options) {
|
||||
if (!options?.bind) return null;
|
||||
if (!options.documents?.surfaceDefinition) {
|
||||
throw new Error("Odin CultNet/RUDP surface server requires a surface document definition.");
|
||||
}
|
||||
if (typeof options.getCache !== "function") {
|
||||
throw new Error("Odin CultNet/RUDP surface server requires an async getCache function.");
|
||||
}
|
||||
|
||||
const bind = parseEndpoint(options.bind);
|
||||
const documentRegistry = new CultNetDocumentRegistry([
|
||||
defineCultNetDocumentBinding({ definition: options.documents.surfaceDefinition }),
|
||||
]);
|
||||
const sessions = new Map();
|
||||
const socket = dgram.createSocket(bind.host.includes(":") ? "udp6" : "udp4");
|
||||
const resendPollMs = Math.max(10, Number(options.resendPollMs || 25));
|
||||
const sessionTimeoutMs = Math.max(1_000, Number(options.sessionTimeoutMs || DEFAULT_SESSION_TIMEOUT_MS));
|
||||
let resendTimer = null;
|
||||
|
||||
socket.on("message", (wire, remote) => {
|
||||
try {
|
||||
const packet = decodeRudpPacket(wire);
|
||||
handlePacket(packet, remote).catch((error) => {
|
||||
logError(options, `CultNet/RUDP packet handling failed for ${remote.address}:${remote.port}`, error);
|
||||
});
|
||||
} catch (error) {
|
||||
logError(options, "CultNet/RUDP packet decode failed", error);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("error", (error) => {
|
||||
logError(options, "CultNet/RUDP socket error", error);
|
||||
});
|
||||
|
||||
async function start() {
|
||||
await new Promise((resolve, reject) => {
|
||||
socket.once("error", reject);
|
||||
socket.bind(bind.port, bind.host, () => {
|
||||
socket.off("error", reject);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
resendTimer = setInterval(() => {
|
||||
try {
|
||||
const nowMs = Date.now();
|
||||
for (const [key, record] of sessions) {
|
||||
if (record.session.checkTimeout(nowMs, sessionTimeoutMs)) {
|
||||
sessions.delete(key);
|
||||
continue;
|
||||
}
|
||||
for (const packet of record.session.dueResends(nowMs)) {
|
||||
sendPacket(record.remote, packet);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logError(options, "CultNet/RUDP resend loop failed", error);
|
||||
}
|
||||
}, resendPollMs);
|
||||
resendTimer.unref?.();
|
||||
}
|
||||
|
||||
function close() {
|
||||
if (resendTimer) {
|
||||
clearInterval(resendTimer);
|
||||
resendTimer = null;
|
||||
}
|
||||
sessions.clear();
|
||||
socket.close();
|
||||
}
|
||||
|
||||
async function handlePacket(packet, remote) {
|
||||
const key = remoteKey(remote);
|
||||
const nowMs = Date.now();
|
||||
let record = sessions.get(key);
|
||||
|
||||
if (packet.packetType === "connect") {
|
||||
record = {
|
||||
remote: { address: remote.address, family: remote.family, port: remote.port },
|
||||
session: new CultNetRudpSession({
|
||||
connectionId: ODIN_RUDP_CONNECTION_ID,
|
||||
initialSequence: 1,
|
||||
resendDelayMs: 100,
|
||||
}),
|
||||
};
|
||||
sessions.set(key, record);
|
||||
sendPacket(record.remote, record.session.acceptConnect(packet, nowMs, new Uint8Array()));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!record) {
|
||||
return;
|
||||
}
|
||||
|
||||
const result = record.session.receive(packet, nowMs);
|
||||
if (result.reply) {
|
||||
sendPacket(record.remote, result.reply);
|
||||
}
|
||||
for (const frame of result.delivered) {
|
||||
if (frame.channelId !== "schema") {
|
||||
continue;
|
||||
}
|
||||
await handleSchemaFrame(record, frame.payload);
|
||||
}
|
||||
if (result.disconnected) {
|
||||
sessions.delete(key);
|
||||
return;
|
||||
}
|
||||
if (packet.packetType === "accept" || result.delivered.length > 0) {
|
||||
sendPacket(record.remote, record.session.createAck());
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSchemaFrame(record, payload) {
|
||||
const message = parseCultNetMessage(decode(payload), "cultnet.schema.v0");
|
||||
switch (message.schemaVersion) {
|
||||
case "cultnet.snapshot_request.v0": {
|
||||
try {
|
||||
const cache = await options.getCache();
|
||||
const response = documentRegistry.createRawSnapshotResponse(cache, message.messageId, message);
|
||||
sendSchemaMessage(record, response);
|
||||
} catch (error) {
|
||||
sendSchemaMessage(record, {
|
||||
schemaVersion: "cultnet.error.v0",
|
||||
error: error.message,
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
case "cultnet.schema_catalog_request.v0":
|
||||
sendSchemaMessage(record, cultNetBuiltinSchemaRegistry.createCatalogResponse(message));
|
||||
return;
|
||||
default:
|
||||
sendSchemaMessage(record, {
|
||||
schemaVersion: "cultnet.error.v0",
|
||||
error: `Unsupported Odin CultNet/RUDP request ${message.schemaVersion}.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function sendSchemaMessage(record, message) {
|
||||
const payload = encode(encodeCultNetMessageForWire(message, "cultnet.schema.v0"));
|
||||
for (const packet of record.session.sendMany("schema", payload, {
|
||||
reliable: true,
|
||||
ordered: true,
|
||||
nowMs: Date.now(),
|
||||
})) {
|
||||
sendPacket(record.remote, packet);
|
||||
}
|
||||
}
|
||||
|
||||
function sendPacket(remote, packet) {
|
||||
const wire = Buffer.from(encodeRudpPacket(packet));
|
||||
socket.send(wire, remote.port, remote.address);
|
||||
}
|
||||
|
||||
return {
|
||||
bind,
|
||||
start,
|
||||
close,
|
||||
};
|
||||
}
|
||||
|
||||
function parseEndpoint(value) {
|
||||
const text = String(value || "").trim();
|
||||
const ipv6 = text.match(/^\[([^\]]+)\]:(\d+)$/);
|
||||
if (ipv6) return { host: ipv6[1], port: parsePort(ipv6[2]) };
|
||||
const index = text.lastIndexOf(":");
|
||||
if (index <= 0) {
|
||||
throw new Error(`CultNet/RUDP bind must be host:port, got "${value}".`);
|
||||
}
|
||||
return { host: text.slice(0, index), port: parsePort(text.slice(index + 1)) };
|
||||
}
|
||||
|
||||
function parsePort(value) {
|
||||
const port = Number(value);
|
||||
if (!Number.isInteger(port) || port <= 0 || port > 65535) {
|
||||
throw new Error(`CultNet/RUDP port is invalid: ${value}`);
|
||||
}
|
||||
return port;
|
||||
}
|
||||
|
||||
function remoteKey(remote) {
|
||||
return `${remote.address}:${remote.port}`;
|
||||
}
|
||||
|
||||
function logError(options, label, error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
if (typeof options?.logError === "function") {
|
||||
options.logError(`${label}: ${message}`);
|
||||
return;
|
||||
}
|
||||
console.error(`${label}: ${message}`);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createCultNetRudpSurfaceServer,
|
||||
};
|
||||
Loading…
Reference in a new issue