Compare commits
55 changed files with 19449 additions and 15526 deletions
12
.gitignore
vendored
12
.gitignore
vendored
|
|
@ -1,12 +0,0 @@
|
|||
node_modules/
|
||||
scratch/
|
||||
bin/
|
||||
obj/
|
||||
target/
|
||||
*.log
|
||||
*.pid
|
||||
artifacts/
|
||||
**/artifacts/
|
||||
|
||||
# Agent scratch. Reached 50MB of tarballs and lockfile snapshots once.
|
||||
.codex-tmp/
|
||||
89
AGENTS.md
89
AGENTS.md
|
|
@ -1,89 +0,0 @@
|
|||
# Odin Instructions
|
||||
|
||||
## Project Purpose
|
||||
|
||||
Odin is the all-seer and rendezvous organ for the CultMesh Verse: discovery,
|
||||
schema awareness, translation routes, and interface aggregation. Every other
|
||||
GameCult target is observed *through* Odin, which is why its availability is
|
||||
load-bearing for the estate rather than for itself.
|
||||
|
||||
Estate-wide doctrine — the Prime Directive, Code Is A Liability, the Loud
|
||||
Rebuild Contract, Odin-class architecture, the CultCache/CultNet/CultMesh
|
||||
substrate rules — lives in `F:\Projects\CLAUDE.md` and `~/.claude/CLAUDE.md` and
|
||||
is loaded automatically. It is not repeated here.
|
||||
|
||||
## Canonical State
|
||||
|
||||
- `docs/architecture.md` — the source-grounded architecture note.
|
||||
- `docs/transport-shortcut-inventory.md` — a **dated ledger**. Entries are left
|
||||
as written and corrected by dated notes on top, never rewritten. Follow that
|
||||
convention for anything else recording history here.
|
||||
- `notes/` — compact re-entry packets. Read the newest before touching
|
||||
deployment.
|
||||
- `state/map.yaml` — the current deployment and admission map. This is the one
|
||||
file that goes stale fastest; verify it against the host before trusting it.
|
||||
- `personas/gjallar.persona_state.cc` — Gjallar's Persona state, in the portable
|
||||
`gamecult.persona_state.v0` shape.
|
||||
|
||||
## Important Paths
|
||||
|
||||
Relative to the repository root.
|
||||
|
||||
- Daemon: `crates/odin-daemon`
|
||||
- Shared records and documents: `crates/odin-core`
|
||||
- Deployment recipe Idunn seals and builds from: `deployment/idunn/recipe.toml`
|
||||
- Operator binding (lives on the host, not here):
|
||||
`/etc/gamecult/idunn/bindings/odin.toml`
|
||||
|
||||
## Build And Deploy Target
|
||||
|
||||
Odin runs on Linux under Idunn on `yggdrasil`. This workstation is Windows and
|
||||
is where the shell is, not where the artifact runs: **a local compile is not
|
||||
evidence about the Linux release.** Build on the target or through the deploy
|
||||
path's own builder, and say which one you used.
|
||||
|
||||
```bash
|
||||
git archive HEAD | ssh ygg 'sudo rm -rf /srv/build/odin && sudo mkdir -p /srv/build/odin && sudo tar -x -C /srv/build/odin && sudo chown -R gamecultadmin:gamecultadmin /srv/build/odin'
|
||||
ssh ygg 'sudo docker run --rm -v /srv/build/odin:/w -v /etc/machine-id:/etc/machine-id:ro -v /srv/build/cargo-registry:/usr/local/cargo/registry -v /srv/build/cargo-git:/usr/local/cargo/git -w /w rust:1.95-bookworm cargo test -p odin-core -p odin-daemon'
|
||||
```
|
||||
|
||||
`/etc/machine-id` **must** be mounted or every identity-enrolling test fails
|
||||
with `Linux machine-id is unavailable` — a wall of failures that looks like
|
||||
broken code and is a missing mount.
|
||||
|
||||
Scope test runs to the crates you are changing. Muninn's crates live in
|
||||
`GameCult/Muninn` now, not in this workspace.
|
||||
|
||||
## Session Bootstrap And Re-entry Protocol
|
||||
|
||||
On fresh session load, before touching deployment:
|
||||
|
||||
1. Read the newest file in `notes/`.
|
||||
2. Read `state/map.yaml`, then **verify it on the host** — it describes a live
|
||||
system and is the first thing to rot.
|
||||
3. `git log --oneline -5` and `git status --short --branch`.
|
||||
|
||||
The host is authoritative for every question of the form "is X running", "is X
|
||||
admitted", "what release is live". Two commands answer most of them:
|
||||
|
||||
```bash
|
||||
ssh ygg 'systemctl list-units --all "idunn-odin-*" --state=active'
|
||||
ssh ygg 'sudo /usr/local/bin/idunn status --state-store /var/lib/gamecult/idunn/control.cc'
|
||||
```
|
||||
|
||||
## Operating Discipline
|
||||
|
||||
- **Never conclude from a partial listing.** `systemctl list-units | head` sorts
|
||||
alphabetically and will hide the one active unit among dozens of failed ones.
|
||||
Enumerate completely or state that the answer is unknown.
|
||||
- **A unit can be `active` and own no process.** `odin.service` in
|
||||
`/etc/systemd/system/` is exactly that: active, `MainPID 0`, owning nothing.
|
||||
It has misled at least two separate readings. Check `-p MainPID` before
|
||||
believing a status line.
|
||||
- **Read the journal with `sudo journalctl -q`.** Unprivileged `journalctl` shows
|
||||
only your own messages and returns a false zero on `Started idunn-odin`
|
||||
counts.
|
||||
- Verify changing facts against the host or source, never against a document —
|
||||
including the documents in this repository.
|
||||
- Redeploying Odin is not routine. Read `notes/` first; the failure mode is
|
||||
documented and it is not obvious.
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
Repository doctrine for this project lives in AGENTS.md.
|
||||
|
||||
@AGENTS.md
|
||||
1430
Cargo.lock
generated
1430
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
46
Cargo.toml
46
Cargo.toml
|
|
@ -1,11 +1,37 @@
|
|||
[workspace]
|
||||
members = [
|
||||
"crates/odin-core",
|
||||
"crates/odin-daemon",
|
||||
]
|
||||
resolver = "3"
|
||||
[package]
|
||||
name = "idunn-daemon"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
publish.workspace = true
|
||||
|
||||
[workspace.package]
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
publish = false
|
||||
[[bin]]
|
||||
name = "idunn"
|
||||
path = "src/main.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "idunn-provision"
|
||||
path = "src/bin/idunn-provision.rs"
|
||||
|
||||
[lib]
|
||||
name = "idunn_daemon"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
cultmesh-rs = { path = "../../vendor/cultmesh-rs" }
|
||||
cultcache-rs = { git = "https://github.com/GameCult/CultLib.git", rev = "c13b6ba07e99f2535b455da74d07d88c308731f5", package = "cultcache-rs" }
|
||||
cultnet-rs = { git = "https://github.com/GameCult/CultLib.git", rev = "c13b6ba07e99f2535b455da74d07d88c308731f5", package = "cultnet-rs" }
|
||||
rmp-serde = "1.3"
|
||||
sha2 = "0.10"
|
||||
chrono = "0.4"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
ed25519-dalek = "2.2"
|
||||
toml = "0.8"
|
||||
uuid = { version = "1", features = ["v4"] }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3"
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
libc = "0.2"
|
||||
|
|
|
|||
21
LICENSE
21
LICENSE
|
|
@ -1,21 +0,0 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2026 GameCult
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
133
README.md
133
README.md
|
|
@ -1,133 +0,0 @@
|
|||
# Odin
|
||||
|
||||
Odin is the GameCult all-seer: the central CultMesh node every Verse can use to discover the other Verses, inspect schema catalogs, and find translation paths between local realities.
|
||||
|
||||
It is not the renderer. It is not Eve. It is not a Starfire utility script wearing a bigger coat. Odin owns discovery, schema awareness, translation planning, and accepted operator surfaces. Eve clients and Gjallar lower Odin's published CultUI surface into whatever body they have.
|
||||
|
||||
Odin is also the compliance witness for the GameCult service architecture:
|
||||
durable service state in CultCache `.cc`, local Verse visibility through
|
||||
CultMesh, meaningful presentation as Eve GUI/TUI DSL, and renderers as lowerers
|
||||
only.
|
||||
|
||||
## Rust Spine
|
||||
|
||||
The target Odin body is Rust-first: ingest through narrow ports, normalize into
|
||||
typed Odin records, persist through CultCache `.cc`, expose through CultMesh /
|
||||
CultNet document registries over the shared RUDP transport, and lower
|
||||
interface state through Eve/CultUI.
|
||||
|
||||
The first Rust core lives in `crates/odin-core` and already separates typed
|
||||
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 Yggdrasil-resident C# composition daemon in `F:\Projects\Gjallar`. It
|
||||
consumes Odin's accepted provider snapshot and publishes one tiled
|
||||
`gjallar.overview` Eve surface for GUI, TUI, and agent lowerers.
|
||||
|
||||
## Typed access discovery
|
||||
|
||||
Odin persists only document schemas it has registered. That is a deliberate
|
||||
typed-store boundary, not a license to accept anonymous blobs and hope later
|
||||
code understands them. The live Yggdrasil body at
|
||||
`ba76a7239a7bb40aa1774df7d93b9388dc27b222`, using CultLib
|
||||
`75c180782aeba7cfd22d6412877397708a4ed28f`, registers Heimdall's provider,
|
||||
command-boundary, Eve-plugin, and transport-profile documents plus
|
||||
Ghostlight's public catalog envelope. Provider daemons own those documents;
|
||||
Odin owns typed catalog acceptance, ordered durable persistence, and exact
|
||||
retrieval. Sensitive auth commands and claims travel directly between the
|
||||
consuming app and Heimdall, never through Odin.
|
||||
|
||||
Ghostlight publishes a `ghostlight.schema_catalog.v1` envelope containing only
|
||||
the state contracts named by its public provider surface. Odin registers that
|
||||
envelope so it can persist and return the provider-owned catalog; it does not
|
||||
register every private Ghostlight receipt, transition, or simulation schema.
|
||||
|
||||
## Typed access discovery
|
||||
|
||||
Odin persists only document schemas it has registered. That is a deliberate
|
||||
typed-store boundary, not a license to accept anonymous blobs and hope later
|
||||
code understands them. The live Yggdrasil body at
|
||||
`b4f9a2e95f0b41cebdeddc49223781d1d3c7b42a` registers Heimdall's provider,
|
||||
command-boundary, Eve-plugin, and transport-profile documents. Heimdall owns
|
||||
their contents; Odin owns catalog acceptance and exact retrieval. Sensitive
|
||||
auth commands and claims travel directly between the consuming app and
|
||||
Heimdall, never through Odin.
|
||||
|
||||
## Gjallar
|
||||
|
||||
Gjallar is the herald composition daemon that runs beside Odin and Idunn on
|
||||
Yggdrasil. Odin sees the Verses and accepts provider surfaces. Gjallar consumes
|
||||
that accepted snapshot and publishes one multi-scale tiled Eve surface. Eve
|
||||
clients—including EveCanvas—own graphical and TUI lowering.
|
||||
|
||||
Local package surfaces:
|
||||
|
||||
- Organ contract: `docs/gjallar.md`
|
||||
- Branding Persona state: `personas/gjallar.persona_state.cc`
|
||||
- Runtime source: `F:\Projects\Gjallar`
|
||||
- Avatar asset: `assets/personas/gjallar-avatar.png`
|
||||
- Pixel avatar: `assets/personas/gjallar-avatar-pixel-256.png`
|
||||
|
||||
## Idunn
|
||||
|
||||
Idunn (`GameCult/Idunn`) owns deployment and daemon survival for every GameCult
|
||||
target, Odin included: it builds from the target's recipe, activates and
|
||||
restarts the admitted incarnation, and holds the deployment and lifecycle
|
||||
brakes. Odin owns none of that. Odin does discovery, schema awareness,
|
||||
rendezvous, and interface aggregation, and it reads Idunn's topology projection
|
||||
to learn which incarnations exist. Agents do not deploy daemons directly: they
|
||||
configure the target's recipe and binding and let Idunn run the transaction.
|
||||
|
||||
Local package surfaces:
|
||||
|
||||
- Deployment recipe: `deployment/idunn/recipe.toml`
|
||||
- Operator binding: `/etc/gamecult/idunn/bindings/odin.toml` on the host, not in
|
||||
this repository
|
||||
- Live deployment and admission map: `state/map.yaml`
|
||||
- Idunn's contract, brakes, and runbooks: the `GameCult/Idunn` repository
|
||||
|
||||
## Authority Map
|
||||
|
||||
- Owner: Odin owns the network-wide Verse registry, schema catalog index, translation map, and accepted provider catalog/proxy surfaces.
|
||||
- Inputs: CultMesh/CultNet peer announcements, schema catalog responses, daemon
|
||||
health/provider publications over `cultnet.transport.rudp.v0`, Idunn's
|
||||
read-only topology projection, and provider-owned Eve/CultUI surfaces.
|
||||
- Outputs: CultCache-backed Odin state, CultMesh documents, and CultNet
|
||||
schema/catalog messages. Browser, GUI, TUI, and framebuffer renderers lower
|
||||
those documents outside Odin instead of asking Odin to host web surfaces.
|
||||
- Derived state: Gjallar's aggregate surface is derived from Odin/provider state; Eve clients derive pixels from that aggregate.
|
||||
- Forbidden writers: renderers do not probe the network or decide Verse truth; individual projects do not maintain private incompatible discovery ledgers once Odin can see them.
|
||||
- Shared paths: human dashboards, worker schedulers, Verse bootstrap code, and compact TUI views consume the same registry and schema catalog.
|
||||
- Not Odin's: process lifecycle, restart, deployment, and daemon survival belong to Idunn.
|
||||
|
||||
## Runtime Body
|
||||
|
||||
Odin runs as `odin-daemon` under Idunn on Yggdrasil, built from
|
||||
`deployment/idunn/recipe.toml`. There is no local start script. The daemon:
|
||||
|
||||
- reads Idunn's read-only projection to learn which incarnations of which
|
||||
targets exist;
|
||||
- admits provider presences over RUDP for those incarnations and persists them
|
||||
through CultMesh/CultCache;
|
||||
- publishes signed runtime-topology correlation records;
|
||||
- answers CultNet/RUDP snapshot and document requests on its route.
|
||||
|
||||
Odin's native document catalog is addressed by CultMesh URI. Concrete RUDP
|
||||
bootstrap is configured behind CultMesh URI resolution by the operator or by
|
||||
Odin/Idunn deployment state:
|
||||
|
||||
```text
|
||||
cultmesh://odin/rendezvous/provider-catalog
|
||||
```
|
||||
|
||||
That URI accepts typed document publication and schema/catalog requests through
|
||||
the shared CultMesh runtime. Consumers that need Odin's accepted surface can
|
||||
still request the current CultNet snapshot after CultMesh resolves the transport.
|
||||
|
||||
Browser and deck lowerers consume Odin's CultMesh state through their own
|
||||
lowering process. Odin does not host browser-deck surfaces or publish deck URLs
|
||||
as discovery seed material.
|
||||
|
||||
Provider advertisements and CultNet/RUDP transport profiles are the discovery
|
||||
path. External host probes, product health checks, port probes, and renderer
|
||||
bridges are debug or lowering surfaces outside Odin only.
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 149 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 2.4 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 38 KiB |
|
|
@ -1,22 +0,0 @@
|
|||
[package]
|
||||
name = "odin-core"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
publish.workspace = true
|
||||
|
||||
[lib]
|
||||
name = "odin_core"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
cultcache-rs = { git = "https://github.com/GameCult/CultLib.git", rev = "3bf1c0cea53e0092187fa2e59a75ba1571947ffe", package = "cultcache-rs" }
|
||||
cultmesh-rs = { git = "https://github.com/GameCult/CultLib.git", rev = "3bf1c0cea53e0092187fa2e59a75ba1571947ffe", package = "cultmesh-rs" }
|
||||
cultnet-rs = { git = "https://github.com/GameCult/CultLib.git", rev = "3bf1c0cea53e0092187fa2e59a75ba1571947ffe", package = "cultnet-rs" }
|
||||
serde_json = "1"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "1.4"
|
||||
tempfile = "3"
|
||||
|
|
@ -1,280 +0,0 @@
|
|||
use crate::EveProviderAdvertisementRecord;
|
||||
use cultmesh_rs::CultMeshNode;
|
||||
use serde_json::Value;
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub struct OdinEndpointQuery<'a> {
|
||||
pub schema: Option<&'a str>,
|
||||
pub transport_contains: Option<&'a str>,
|
||||
pub host_hint: Option<&'a str>,
|
||||
pub device_filter: Option<&'a str>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct OdinEndpointMatch {
|
||||
pub address: String,
|
||||
pub schema: Option<String>,
|
||||
pub transport: Option<String>,
|
||||
pub stream_id: Option<String>,
|
||||
}
|
||||
|
||||
pub fn discover_provider_endpoints(
|
||||
node: &CultMeshNode,
|
||||
query: OdinEndpointQuery<'_>,
|
||||
) -> Vec<OdinEndpointMatch> {
|
||||
let mut matches = Vec::new();
|
||||
for provider in node
|
||||
.cache()
|
||||
.get_all::<EveProviderAdvertisementRecord>()
|
||||
.ok()
|
||||
.into_iter()
|
||||
.flatten()
|
||||
{
|
||||
collect_provider_endpoint_matches(&provider.value, &query, &mut matches);
|
||||
}
|
||||
dedupe_endpoint_matches(matches)
|
||||
}
|
||||
|
||||
fn collect_provider_endpoint_matches(
|
||||
value: &Value,
|
||||
query: &OdinEndpointQuery<'_>,
|
||||
matches: &mut Vec<OdinEndpointMatch>,
|
||||
) {
|
||||
if query
|
||||
.host_hint
|
||||
.is_some_and(|hint| !provider_matches_host_hint(value, hint))
|
||||
{
|
||||
return;
|
||||
}
|
||||
collect_endpoint_matches_from_provider_value(value, query, matches);
|
||||
}
|
||||
|
||||
fn collect_endpoint_matches_from_provider_value(
|
||||
value: &Value,
|
||||
query: &OdinEndpointQuery<'_>,
|
||||
matches: &mut Vec<OdinEndpointMatch>,
|
||||
) {
|
||||
if let Some(streams) = value
|
||||
.get("inputStreams")
|
||||
.or_else(|| value.get("input_streams"))
|
||||
.and_then(|streams| streams.as_array())
|
||||
{
|
||||
for stream in streams {
|
||||
if let Some(endpoint_match) = endpoint_match_from_entry(stream, query, true) {
|
||||
matches.push(endpoint_match);
|
||||
}
|
||||
}
|
||||
}
|
||||
for collection_name in ["endpoints", "routes"] {
|
||||
if let Some(entries) = value
|
||||
.get(collection_name)
|
||||
.and_then(|entries| entries.as_array())
|
||||
{
|
||||
for entry in entries {
|
||||
if let Some(endpoint_match) = endpoint_match_from_entry(entry, query, false) {
|
||||
matches.push(endpoint_match);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn endpoint_match_from_entry(
|
||||
entry: &Value,
|
||||
query: &OdinEndpointQuery<'_>,
|
||||
allow_device_filter: bool,
|
||||
) -> Option<OdinEndpointMatch> {
|
||||
let schema = entry
|
||||
.get("schema")
|
||||
.or_else(|| entry.get("schemaId"))
|
||||
.or_else(|| entry.get("schema_id"))
|
||||
.and_then(|value| value.as_str());
|
||||
if let Some(wanted_schema) = query.schema
|
||||
&& schema != Some(wanted_schema)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let transport = entry.get("transport").and_then(|value| value.as_str());
|
||||
if let Some(wanted_transport) = query.transport_contains
|
||||
&& !transport
|
||||
.unwrap_or_default()
|
||||
.to_ascii_lowercase()
|
||||
.contains(&wanted_transport.to_ascii_lowercase())
|
||||
{
|
||||
return None;
|
||||
}
|
||||
if let Some(filter) = query.device_filter
|
||||
&& allow_device_filter
|
||||
&& !input_stream_matches_filter(entry, filter)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let address = entry.get("address").and_then(|value| value.as_str())?;
|
||||
if !endpoint_looks_like_socket(address) {
|
||||
return None;
|
||||
}
|
||||
Some(OdinEndpointMatch {
|
||||
address: address.to_string(),
|
||||
schema: schema.map(ToString::to_string),
|
||||
transport: transport.map(ToString::to_string),
|
||||
stream_id: entry
|
||||
.get("streamId")
|
||||
.or_else(|| entry.get("stream_id"))
|
||||
.and_then(|value| value.as_str())
|
||||
.map(ToString::to_string),
|
||||
})
|
||||
}
|
||||
|
||||
fn input_stream_matches_filter(stream: &Value, filter: &str) -> bool {
|
||||
stream
|
||||
.get("streamId")
|
||||
.or_else(|| stream.get("stream_id"))
|
||||
.and_then(|value| value.as_str())
|
||||
.is_some_and(|stream_id| stream_id.contains(filter))
|
||||
|| stream
|
||||
.get("devices")
|
||||
.and_then(|value| value.as_array())
|
||||
.is_some_and(|devices| {
|
||||
devices.iter().any(|device| {
|
||||
device
|
||||
.get("deviceId")
|
||||
.or_else(|| device.get("device_id"))
|
||||
.and_then(|value| value.as_str())
|
||||
== Some(filter)
|
||||
|| device
|
||||
.get("deviceKind")
|
||||
.or_else(|| device.get("device_kind"))
|
||||
.and_then(|value| value.as_str())
|
||||
== Some(filter)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn provider_matches_host_hint(value: &Value, host_hint: &str) -> bool {
|
||||
let normalized = host_hint
|
||||
.trim()
|
||||
.trim_end_matches(".local")
|
||||
.to_ascii_lowercase();
|
||||
if normalized.is_empty() {
|
||||
return true;
|
||||
}
|
||||
let explicit_identity = [
|
||||
"providerId",
|
||||
"provider_id",
|
||||
"id",
|
||||
"verseId",
|
||||
"verse_id",
|
||||
"hostId",
|
||||
"host_id",
|
||||
"runtimeId",
|
||||
"runtime_id",
|
||||
"locatedService",
|
||||
"located_service",
|
||||
"cultMeshAddress",
|
||||
"cult_mesh_address",
|
||||
]
|
||||
.into_iter()
|
||||
.filter_map(|field| value.get(field).and_then(|value| value.as_str()));
|
||||
explicit_identity
|
||||
.map(normalized_host_fragment)
|
||||
.any(|identity| identity == normalized || identity.contains(&normalized))
|
||||
}
|
||||
|
||||
fn normalized_host_fragment(value: &str) -> String {
|
||||
value.trim().trim_end_matches(".local").to_ascii_lowercase()
|
||||
}
|
||||
|
||||
fn endpoint_looks_like_socket(endpoint: &str) -> bool {
|
||||
let Some((host, port)) = endpoint.rsplit_once(':') else {
|
||||
return false;
|
||||
};
|
||||
!host.trim().is_empty()
|
||||
&& port
|
||||
.parse::<u16>()
|
||||
.is_ok_and(|parsed_port| parsed_port != 0)
|
||||
}
|
||||
|
||||
fn dedupe_endpoint_matches(mut matches: Vec<OdinEndpointMatch>) -> Vec<OdinEndpointMatch> {
|
||||
matches.sort_by(|left, right| {
|
||||
(
|
||||
&left.address,
|
||||
&left.schema,
|
||||
&left.transport,
|
||||
&left.stream_id,
|
||||
)
|
||||
.cmp(&(
|
||||
&right.address,
|
||||
&right.schema,
|
||||
&right.transport,
|
||||
&right.stream_id,
|
||||
))
|
||||
});
|
||||
matches.dedup();
|
||||
matches
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use serde_json::json;
|
||||
|
||||
#[test]
|
||||
fn host_hint_matches_explicit_provider_identity_not_nested_mentions() {
|
||||
let starfire_provider = json!({
|
||||
"providerId": "muninn.telemetry.starfire",
|
||||
"verseId": "starfire.local",
|
||||
"inputStreams": [{
|
||||
"address": "198.51.100.66:17888",
|
||||
"schema": "muninn.hid_controller_state.v1",
|
||||
"transport": "cultnet.transport.rudp.v0",
|
||||
"streamId": "raven:xbox-raven:hid-controller-state",
|
||||
"devices": [{
|
||||
"deviceId": "xbox-raven",
|
||||
"deviceKind": "xinput-controller"
|
||||
}]
|
||||
}]
|
||||
});
|
||||
|
||||
let mut matches = Vec::new();
|
||||
collect_provider_endpoint_matches(
|
||||
&starfire_provider,
|
||||
&OdinEndpointQuery {
|
||||
schema: Some("muninn.hid_controller_state.v1"),
|
||||
transport_contains: Some("rudp"),
|
||||
host_hint: Some("raven"),
|
||||
device_filter: Some("xbox-raven"),
|
||||
},
|
||||
&mut matches,
|
||||
);
|
||||
|
||||
assert!(matches.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn host_hint_matches_normalized_provider_catalog_identity() {
|
||||
let starfire_provider = json!({
|
||||
"id": "muninn.telemetry.starfire",
|
||||
"locatedService": "asgard.starfire.muninn",
|
||||
"routes": [{
|
||||
"address": "198.51.100.66:17888",
|
||||
"schema": "muninn.hid_controller_state.v1",
|
||||
"transport": "cultnet.transport.rudp.v0"
|
||||
}]
|
||||
});
|
||||
|
||||
let mut matches = Vec::new();
|
||||
collect_provider_endpoint_matches(
|
||||
&starfire_provider,
|
||||
&OdinEndpointQuery {
|
||||
schema: Some("muninn.hid_controller_state.v1"),
|
||||
transport_contains: Some("rudp"),
|
||||
host_hint: Some("starfire"),
|
||||
device_filter: Some("nav-windows-psnav-0"),
|
||||
},
|
||||
&mut matches,
|
||||
);
|
||||
|
||||
assert_eq!(matches.len(), 1);
|
||||
assert_eq!(matches[0].address, "198.51.100.66:17888");
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,11 +0,0 @@
|
|||
pub mod discovery;
|
||||
pub mod documents;
|
||||
pub mod pipeline;
|
||||
pub mod ports;
|
||||
pub mod repository;
|
||||
|
||||
pub use discovery::*;
|
||||
pub use documents::*;
|
||||
pub use pipeline::*;
|
||||
pub use ports::*;
|
||||
pub use repository::*;
|
||||
|
|
@ -1,260 +0,0 @@
|
|||
use crate::documents::{
|
||||
OdinInterfaceRecord, OdinObservationStreamRecord, OdinRecords, OdinServiceRecord,
|
||||
OdinSnapshotRecord, OdinTranslationRouteRecord, OdinVerseRecord,
|
||||
};
|
||||
use crate::ports::{
|
||||
Clock, InterfaceIngest, ObservationStreamIngest, OdinInputBatch, ServiceIngest,
|
||||
TranslationRouteIngest, VerseIngest,
|
||||
};
|
||||
use anyhow::Result;
|
||||
|
||||
pub struct OdinIngestPipeline<V, S, I, O, T, C> {
|
||||
verses: V,
|
||||
services: S,
|
||||
interfaces: I,
|
||||
streams: O,
|
||||
translations: T,
|
||||
clock: C,
|
||||
}
|
||||
|
||||
impl<V, S, I, O, T, C> OdinIngestPipeline<V, S, I, O, T, C>
|
||||
where
|
||||
V: VerseIngest,
|
||||
S: ServiceIngest,
|
||||
I: InterfaceIngest,
|
||||
O: ObservationStreamIngest,
|
||||
T: TranslationRouteIngest,
|
||||
C: Clock,
|
||||
{
|
||||
pub fn new(
|
||||
verses: V,
|
||||
services: S,
|
||||
interfaces: I,
|
||||
streams: O,
|
||||
translations: T,
|
||||
clock: C,
|
||||
) -> Self {
|
||||
Self {
|
||||
verses,
|
||||
services,
|
||||
interfaces,
|
||||
streams,
|
||||
translations,
|
||||
clock,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn collect(&self) -> Result<OdinInputBatch> {
|
||||
Ok(OdinInputBatch {
|
||||
observed_at: self.clock.now(),
|
||||
source: "odin.rust.ingest".to_string(),
|
||||
verses: self.verses.observe_verses()?,
|
||||
services: self.services.observe_services()?,
|
||||
interfaces: self.interfaces.observe_interfaces()?,
|
||||
observation_streams: self.streams.observe_streams()?,
|
||||
translation_routes: self.translations.observe_translation_routes()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn normalize_odin_records(input: OdinInputBatch) -> OdinRecords {
|
||||
let verses = input
|
||||
.verses
|
||||
.into_iter()
|
||||
.map(|entry| OdinVerseRecord {
|
||||
verse_id: entry.verse_id,
|
||||
name: entry.name,
|
||||
role: entry.role,
|
||||
status: entry.status,
|
||||
capabilities: entry.capabilities,
|
||||
observed_at: input.observed_at.clone(),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let services = input
|
||||
.services
|
||||
.into_iter()
|
||||
.map(|entry| OdinServiceRecord {
|
||||
service_id: entry.service_id,
|
||||
verse_id: entry.verse_id,
|
||||
name: entry.name,
|
||||
state: entry.state,
|
||||
detail: entry.detail,
|
||||
authority: entry.authority,
|
||||
observed_at: input.observed_at.clone(),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let interfaces = input
|
||||
.interfaces
|
||||
.into_iter()
|
||||
.map(|entry| OdinInterfaceRecord {
|
||||
provider_id: entry.provider_id,
|
||||
title: entry.title,
|
||||
state: entry.state,
|
||||
source: entry.source,
|
||||
version: entry.version,
|
||||
updated_at: entry.updated_at,
|
||||
observed_at: input.observed_at.clone(),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let observation_streams = input
|
||||
.observation_streams
|
||||
.into_iter()
|
||||
.map(|entry| {
|
||||
let stream_key = format!("{}:{}:{}", entry.device_id, entry.stream_id, entry.kind);
|
||||
OdinObservationStreamRecord {
|
||||
stream_key,
|
||||
device_id: entry.device_id,
|
||||
stream_id: entry.stream_id,
|
||||
kind: entry.kind,
|
||||
state: entry.state,
|
||||
detail: entry.detail,
|
||||
owner: entry.owner,
|
||||
observed_at: input.observed_at.clone(),
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let translation_routes = input
|
||||
.translation_routes
|
||||
.into_iter()
|
||||
.map(|entry| {
|
||||
let route_id = format!("{}=>{}", entry.source_schema, entry.target_schema);
|
||||
OdinTranslationRouteRecord {
|
||||
route_id,
|
||||
source_schema: entry.source_schema,
|
||||
target_schema: entry.target_schema,
|
||||
translation_kind: entry.translation_kind,
|
||||
owner: entry.owner,
|
||||
version: entry.version,
|
||||
notes: entry.notes,
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let snapshot = OdinSnapshotRecord {
|
||||
snapshot_id: "latest".to_string(),
|
||||
observed_at: input.observed_at,
|
||||
verse_count: verses.len() as u32,
|
||||
service_count: services.len() as u32,
|
||||
interface_count: interfaces.len() as u32,
|
||||
observation_stream_count: observation_streams.len() as u32,
|
||||
source: input.source,
|
||||
};
|
||||
|
||||
OdinRecords {
|
||||
snapshot: Some(snapshot),
|
||||
verses,
|
||||
services,
|
||||
interfaces,
|
||||
observation_streams,
|
||||
translation_routes,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::ports::{
|
||||
FixedClock, InterfaceObservation, ObservationStreamObservation, ServiceObservation,
|
||||
TranslationRouteObservation, VerseObservation,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
struct MockIngest;
|
||||
|
||||
impl VerseIngest for MockIngest {
|
||||
fn observe_verses(&self) -> Result<Vec<VerseObservation>> {
|
||||
Ok(vec![VerseObservation {
|
||||
verse_id: "starfire.local".to_string(),
|
||||
name: "Starfire".to_string(),
|
||||
role: "coordinator".to_string(),
|
||||
status: "active".to_string(),
|
||||
capabilities: vec!["cultmesh".to_string()],
|
||||
}])
|
||||
}
|
||||
}
|
||||
|
||||
impl ServiceIngest for MockIngest {
|
||||
fn observe_services(&self) -> Result<Vec<ServiceObservation>> {
|
||||
Ok(vec![ServiceObservation {
|
||||
service_id: "odin".to_string(),
|
||||
verse_id: "starfire.local".to_string(),
|
||||
name: "Odin".to_string(),
|
||||
state: "active".to_string(),
|
||||
detail: "typed state".to_string(),
|
||||
authority: "odin".to_string(),
|
||||
}])
|
||||
}
|
||||
}
|
||||
|
||||
impl InterfaceIngest for MockIngest {
|
||||
fn observe_interfaces(&self) -> Result<Vec<InterfaceObservation>> {
|
||||
Ok(vec![InterfaceObservation {
|
||||
provider_id: "odin.allseer".to_string(),
|
||||
title: "Odin".to_string(),
|
||||
state: "active".to_string(),
|
||||
source: "cultmesh".to_string(),
|
||||
version: Some("1".to_string()),
|
||||
updated_at: None,
|
||||
}])
|
||||
}
|
||||
}
|
||||
|
||||
impl ObservationStreamIngest for MockIngest {
|
||||
fn observe_streams(&self) -> Result<Vec<ObservationStreamObservation>> {
|
||||
Ok(vec![ObservationStreamObservation {
|
||||
device_id: "periwinkle".to_string(),
|
||||
stream_id: "motion".to_string(),
|
||||
kind: "sensor".to_string(),
|
||||
state: "active".to_string(),
|
||||
detail: "fresh".to_string(),
|
||||
owner: "mimir".to_string(),
|
||||
}])
|
||||
}
|
||||
}
|
||||
|
||||
impl TranslationRouteIngest for MockIngest {
|
||||
fn observe_translation_routes(&self) -> Result<Vec<TranslationRouteObservation>> {
|
||||
Ok(vec![TranslationRouteObservation {
|
||||
source_schema: "mimir.eve_sensor_observation.v1".to_string(),
|
||||
target_schema: "odin.observation_stream.v1".to_string(),
|
||||
translation_kind: "projection".to_string(),
|
||||
owner: "odin".to_string(),
|
||||
version: "v1".to_string(),
|
||||
notes: "dashboard projection".to_string(),
|
||||
}])
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pipeline_collects_from_injected_ports() -> Result<()> {
|
||||
let pipeline = OdinIngestPipeline::new(
|
||||
MockIngest,
|
||||
MockIngest,
|
||||
MockIngest,
|
||||
MockIngest,
|
||||
MockIngest,
|
||||
FixedClock::new("2026-06-03T00:00:00Z"),
|
||||
);
|
||||
|
||||
let records = normalize_odin_records(pipeline.collect()?);
|
||||
|
||||
assert_eq!(records.snapshot.unwrap().service_count, 1);
|
||||
assert_eq!(records.verses[0].verse_id, "starfire.local");
|
||||
assert_eq!(records.services[0].authority, "odin");
|
||||
assert_eq!(
|
||||
records.observation_streams[0].stream_key,
|
||||
"periwinkle:motion:sensor"
|
||||
);
|
||||
assert_eq!(
|
||||
records.translation_routes[0].route_id,
|
||||
"mimir.eve_sensor_observation.v1=>odin.observation_stream.v1"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
@ -1,102 +0,0 @@
|
|||
use anyhow::Result;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct VerseObservation {
|
||||
pub verse_id: String,
|
||||
pub name: String,
|
||||
pub role: String,
|
||||
pub status: String,
|
||||
pub capabilities: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct ServiceObservation {
|
||||
pub service_id: String,
|
||||
pub verse_id: String,
|
||||
pub name: String,
|
||||
pub state: String,
|
||||
pub detail: String,
|
||||
pub authority: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct InterfaceObservation {
|
||||
pub provider_id: String,
|
||||
pub title: String,
|
||||
pub state: String,
|
||||
pub source: String,
|
||||
pub version: Option<String>,
|
||||
pub updated_at: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct ObservationStreamObservation {
|
||||
pub device_id: String,
|
||||
pub stream_id: String,
|
||||
pub kind: String,
|
||||
pub state: String,
|
||||
pub detail: String,
|
||||
pub owner: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct TranslationRouteObservation {
|
||||
pub source_schema: String,
|
||||
pub target_schema: String,
|
||||
pub translation_kind: String,
|
||||
pub owner: String,
|
||||
pub version: String,
|
||||
pub notes: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub struct OdinInputBatch {
|
||||
pub observed_at: String,
|
||||
pub source: String,
|
||||
pub verses: Vec<VerseObservation>,
|
||||
pub services: Vec<ServiceObservation>,
|
||||
pub interfaces: Vec<InterfaceObservation>,
|
||||
pub observation_streams: Vec<ObservationStreamObservation>,
|
||||
pub translation_routes: Vec<TranslationRouteObservation>,
|
||||
}
|
||||
|
||||
pub trait VerseIngest {
|
||||
fn observe_verses(&self) -> Result<Vec<VerseObservation>>;
|
||||
}
|
||||
|
||||
pub trait ServiceIngest {
|
||||
fn observe_services(&self) -> Result<Vec<ServiceObservation>>;
|
||||
}
|
||||
|
||||
pub trait InterfaceIngest {
|
||||
fn observe_interfaces(&self) -> Result<Vec<InterfaceObservation>>;
|
||||
}
|
||||
|
||||
pub trait ObservationStreamIngest {
|
||||
fn observe_streams(&self) -> Result<Vec<ObservationStreamObservation>>;
|
||||
}
|
||||
|
||||
pub trait TranslationRouteIngest {
|
||||
fn observe_translation_routes(&self) -> Result<Vec<TranslationRouteObservation>>;
|
||||
}
|
||||
|
||||
pub trait Clock {
|
||||
fn now(&self) -> String;
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct FixedClock {
|
||||
now: String,
|
||||
}
|
||||
|
||||
impl FixedClock {
|
||||
pub fn new(now: impl Into<String>) -> Self {
|
||||
Self { now: now.into() }
|
||||
}
|
||||
}
|
||||
|
||||
impl Clock for FixedClock {
|
||||
fn now(&self) -> String {
|
||||
self.now.clone()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,185 +0,0 @@
|
|||
use crate::documents::{
|
||||
OdinDocuments, OdinRecords, OdinServiceRecord, OdinSnapshotRecord, OdinVerseRecord,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use cultmesh_rs::{CultMesh, CultMeshNode, CultMeshNodeOptions};
|
||||
use std::path::Path;
|
||||
|
||||
pub trait OdinRepository {
|
||||
fn persist_records(&mut self, records: &OdinRecords) -> Result<()>;
|
||||
}
|
||||
|
||||
pub struct CultMeshOdinRepository {
|
||||
node: CultMeshNode,
|
||||
}
|
||||
|
||||
impl CultMeshOdinRepository {
|
||||
pub fn open(store_path: impl AsRef<Path>, runtime_id: impl Into<String>) -> Result<Self> {
|
||||
let node = CultMesh::create_node(
|
||||
store_path,
|
||||
OdinDocuments,
|
||||
CultMeshNodeOptions {
|
||||
runtime_id: runtime_id.into(),
|
||||
pull_on_start: true,
|
||||
},
|
||||
)?;
|
||||
Ok(Self { node })
|
||||
}
|
||||
|
||||
pub fn get_snapshot(&self) -> Result<OdinSnapshotRecord> {
|
||||
self.node.get_required::<OdinSnapshotRecord>("latest")
|
||||
}
|
||||
|
||||
pub fn get_service(&self, service_id: &str) -> Result<Option<OdinServiceRecord>> {
|
||||
self.node.get::<OdinServiceRecord>(service_id)
|
||||
}
|
||||
|
||||
pub fn get_verse(&self, verse_id: &str) -> Result<Option<OdinVerseRecord>> {
|
||||
self.node.get::<OdinVerseRecord>(verse_id)
|
||||
}
|
||||
|
||||
pub fn document_binding_version(&self, document_type: &str) -> Option<String> {
|
||||
self.node
|
||||
.documents()
|
||||
.binding(document_type)
|
||||
.and_then(|binding| binding.payload_schema_version.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl OdinRepository for CultMeshOdinRepository {
|
||||
fn persist_records(&mut self, records: &OdinRecords) -> Result<()> {
|
||||
if let Some(snapshot) = &records.snapshot {
|
||||
self.node.put("latest", snapshot)?;
|
||||
}
|
||||
for verse in &records.verses {
|
||||
self.node.put(&verse.verse_id, verse)?;
|
||||
}
|
||||
for service in &records.services {
|
||||
self.node.put(&service.service_id, service)?;
|
||||
}
|
||||
for interface in &records.interfaces {
|
||||
self.node.put(&interface.provider_id, interface)?;
|
||||
}
|
||||
for stream in &records.observation_streams {
|
||||
self.node.put(&stream.stream_key, stream)?;
|
||||
}
|
||||
for route in &records.translation_routes {
|
||||
self.node.put(&route.route_id, route)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct InMemoryOdinRepository {
|
||||
pub records: OdinRecords,
|
||||
}
|
||||
|
||||
impl OdinRepository for InMemoryOdinRepository {
|
||||
fn persist_records(&mut self, records: &OdinRecords) -> Result<()> {
|
||||
self.records = records.clone();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn persist_pipeline_records(
|
||||
repository: &mut impl OdinRepository,
|
||||
records: OdinRecords,
|
||||
) -> Result<OdinRecords> {
|
||||
repository.persist_records(&records)?;
|
||||
Ok(records)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::documents::ODIN_SERVICE_SCHEMA;
|
||||
use crate::pipeline::normalize_odin_records;
|
||||
use crate::ports::{
|
||||
InterfaceObservation, ObservationStreamObservation, OdinInputBatch, ServiceObservation,
|
||||
TranslationRouteObservation, VerseObservation,
|
||||
};
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
fn sample_records() -> OdinRecords {
|
||||
normalize_odin_records(OdinInputBatch {
|
||||
observed_at: "2026-06-03T00:00:00Z".to_string(),
|
||||
source: "unit-test".to_string(),
|
||||
verses: vec![VerseObservation {
|
||||
verse_id: "starfire.local".to_string(),
|
||||
name: "Starfire".to_string(),
|
||||
role: "coordinator".to_string(),
|
||||
status: "active".to_string(),
|
||||
capabilities: vec!["cultmesh".to_string()],
|
||||
}],
|
||||
services: vec![ServiceObservation {
|
||||
service_id: "odin".to_string(),
|
||||
verse_id: "starfire.local".to_string(),
|
||||
name: "Odin".to_string(),
|
||||
state: "active".to_string(),
|
||||
detail: "persisted".to_string(),
|
||||
authority: "odin".to_string(),
|
||||
}],
|
||||
interfaces: vec![InterfaceObservation {
|
||||
provider_id: "odin.allseer".to_string(),
|
||||
title: "Odin".to_string(),
|
||||
state: "active".to_string(),
|
||||
source: "cultmesh".to_string(),
|
||||
version: Some("1".to_string()),
|
||||
updated_at: None,
|
||||
}],
|
||||
observation_streams: vec![ObservationStreamObservation {
|
||||
device_id: "periwinkle".to_string(),
|
||||
stream_id: "camera".to_string(),
|
||||
kind: "media".to_string(),
|
||||
state: "active".to_string(),
|
||||
detail: "fresh".to_string(),
|
||||
owner: "mimir".to_string(),
|
||||
}],
|
||||
translation_routes: vec![TranslationRouteObservation {
|
||||
source_schema: "source.v1".to_string(),
|
||||
target_schema: "target.v1".to_string(),
|
||||
translation_kind: "projection".to_string(),
|
||||
owner: "odin".to_string(),
|
||||
version: "v1".to_string(),
|
||||
notes: "test".to_string(),
|
||||
}],
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn memory_repository_supports_fast_unit_tests() -> Result<()> {
|
||||
let records = sample_records();
|
||||
let mut repository = InMemoryOdinRepository::default();
|
||||
|
||||
repository.persist_records(&records)?;
|
||||
|
||||
assert_eq!(repository.records.services[0].service_id, "odin");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cultmesh_repository_round_trips_typed_records() -> Result<()> {
|
||||
let temp = tempfile::tempdir()?;
|
||||
let store_path = temp.path().join("odin.cc");
|
||||
let records = sample_records();
|
||||
|
||||
let mut repository = CultMeshOdinRepository::open(&store_path, "odin-test")?;
|
||||
repository.persist_records(&records)?;
|
||||
assert_eq!(
|
||||
repository
|
||||
.document_binding_version("odin.service")
|
||||
.as_deref(),
|
||||
Some(ODIN_SERVICE_SCHEMA)
|
||||
);
|
||||
|
||||
let reloaded = CultMeshOdinRepository::open(&store_path, "odin-test-reloaded")?;
|
||||
assert_eq!(reloaded.get_snapshot()?.service_count, 1);
|
||||
assert_eq!(reloaded.get_service("odin")?.unwrap().detail, "persisted");
|
||||
assert_eq!(
|
||||
reloaded.get_verse("starfire.local")?.unwrap().capabilities,
|
||||
vec!["cultmesh".to_string()]
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
[package]
|
||||
name = "odin-daemon"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
publish.workspace = true
|
||||
|
||||
[lib]
|
||||
name = "odin_daemon"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
signal-hook = "0.3"
|
||||
anyhow = "1"
|
||||
chrono = "0.4"
|
||||
cultcache-rs = { git = "https://github.com/GameCult/CultLib.git", rev = "3bf1c0cea53e0092187fa2e59a75ba1571947ffe", package = "cultcache-rs" }
|
||||
cultmesh-rs = { git = "https://github.com/GameCult/CultLib.git", rev = "3bf1c0cea53e0092187fa2e59a75ba1571947ffe", package = "cultmesh-rs" }
|
||||
cultnet-rs = { git = "https://github.com/GameCult/CultLib.git", rev = "3bf1c0cea53e0092187fa2e59a75ba1571947ffe", package = "cultnet-rs" }
|
||||
fs2 = "0.4"
|
||||
rmp-serde = "1.3"
|
||||
serde = "1"
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3"
|
||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,74 +0,0 @@
|
|||
schema = "gamecult.idunn.target_declaration.v1"
|
||||
target = "odin"
|
||||
source_stamp_environment = "ODIN_BUILD_COMMIT"
|
||||
required_gitlinks = []
|
||||
|
||||
[[steps]]
|
||||
id = "test-odin-daemon"
|
||||
phase = "test"
|
||||
runner = "rust-build"
|
||||
required_environment = ["CARGO_HOME", "HOME"]
|
||||
argv = ["cargo", "test", "--locked", "-p", "odin-daemon"]
|
||||
|
||||
[[steps]]
|
||||
id = "build-odin-daemon"
|
||||
phase = "build"
|
||||
runner = "rust-build"
|
||||
required_environment = ["CARGO_HOME", "HOME"]
|
||||
argv = [
|
||||
"cargo",
|
||||
"build",
|
||||
"--locked",
|
||||
"--release",
|
||||
"-p",
|
||||
"odin-daemon",
|
||||
"--bin",
|
||||
"odin-daemon",
|
||||
]
|
||||
|
||||
[[artifacts]]
|
||||
id = "odin-daemon"
|
||||
source_kind = "runner-output"
|
||||
runner = "rust-build"
|
||||
source = "target/release/odin-daemon"
|
||||
destination = "odin-daemon"
|
||||
executable = true
|
||||
|
||||
[service]
|
||||
executable_artifact = "odin-daemon"
|
||||
arguments = [
|
||||
{ kind = "literal", value = "--state-root" },
|
||||
{ kind = "binding", name = "state_root" },
|
||||
{ kind = "literal", value = "--idunn-projection" },
|
||||
{ kind = "binding", name = "idunn_projection_path" },
|
||||
{ kind = "literal", value = "--idunn-anchor" },
|
||||
{ kind = "binding", name = "idunn_anchor_path" },
|
||||
]
|
||||
transport = "rudp"
|
||||
route_required = true
|
||||
required_environment = [
|
||||
"GAMECULT_IDUNN_CANDIDATE_BIND",
|
||||
"GAMECULT_IDUNN_PROCESS_WRITE_LEASE",
|
||||
"GAMECULT_IDUNN_RUNTIME_BUNDLE",
|
||||
"ODIN_TOPOLOGY_IDENTITY",
|
||||
]
|
||||
|
||||
[service.health]
|
||||
contract = "odin.runtime-health.v1"
|
||||
|
||||
[state]
|
||||
schema_generation = "odin-v2"
|
||||
|
||||
[[state.slots]]
|
||||
id = "topology"
|
||||
relative_path = "topology.cc"
|
||||
kind = "cultcache-file"
|
||||
schema = "odin.runtime_topology_correlation.v2"
|
||||
writer = "process-bound-single-writer"
|
||||
recovery = "preserve"
|
||||
startup = "create-or-open-after-write-lease"
|
||||
|
||||
[[provides]]
|
||||
capability = "odin.verse-rendezvous"
|
||||
schema = "odin.verse-topology.v1"
|
||||
compatibility = "v1"
|
||||
|
|
@ -1,287 +0,0 @@
|
|||
# Odin Architecture
|
||||
|
||||
## Objective
|
||||
|
||||
Odin is the central all-seer node for GameCult's CultMesh world: every Verse can discover every other Verse, learn which schemas they speak, and ask for a translation route when their local document shape differs.
|
||||
|
||||
## Current Mechanism
|
||||
|
||||
```text
|
||||
Eve/CultUI provider surfaces
|
||||
+ provider advertisements
|
||||
-> Odin observation cycle
|
||||
-> Verse-owned service records
|
||||
-> Provider-owned interface records
|
||||
-> Odin state document
|
||||
-> CultMesh/CultCache persistence
|
||||
-> Eve dashboard state
|
||||
-> Eve, browser, compact TUI, and future renderers
|
||||
```
|
||||
|
||||
This first path proves the operator surface and persistent state. It does not yet pretend to be full peer exchange.
|
||||
|
||||
## Rust Target Spine
|
||||
|
||||
The target Odin machine is Rust-first and typed-state-first:
|
||||
|
||||
```text
|
||||
Verse / host / device / provider inputs
|
||||
-> ingest ports
|
||||
-> normalization
|
||||
-> typed Odin records
|
||||
-> CultMesh node
|
||||
-> CultCache .cc persistence
|
||||
-> CultNet/CultMesh document registry
|
||||
-> Odin Eve/CultUI deck projection
|
||||
-> Gjallar Yggdrasil aggregate Eve composition
|
||||
-> compact display feeds
|
||||
```
|
||||
|
||||
The first Rust core lives in `crates/odin-core`:
|
||||
|
||||
- `documents.rs`: typed Odin records and the CultMesh document set.
|
||||
- `ports.rs`: narrow ingest traits plus clock injection for deterministic tests.
|
||||
- `pipeline.rs`: collection and normalization from input observations to typed
|
||||
Odin records.
|
||||
- `repository.rs`: `OdinRepository` abstraction, in-memory mock repository, and
|
||||
CultMesh-backed repository.
|
||||
|
||||
The deployed body is `crates/odin-daemon`, built and run by Idunn from
|
||||
`deployment/idunn/recipe.toml`. `crates/odin-core` holds the typed record spine
|
||||
that the rest of the machine grows into.
|
||||
|
||||
## Runtime Body
|
||||
|
||||
Odin's executable body is split by ownership:
|
||||
|
||||
- `crates/odin-daemon`: the deployed process. Reads Idunn's read-only topology
|
||||
projection, admits provider presences over RUDP for the incarnations Idunn
|
||||
projects, persists them through CultMesh/CultCache, publishes signed
|
||||
runtime-topology correlation records, and answers CultNet/RUDP snapshot and
|
||||
document requests. Deployment, restart, and survival of this process are
|
||||
Idunn's.
|
||||
- `crates/odin-core`: typed Odin documents, ingest ports, normalization, and
|
||||
CultMesh/CultCache repository boundaries.
|
||||
|
||||
If a new owner is needed, name the owner and its invariant before adding code.
|
||||
|
||||
The accepted document registry is also the persistence admission boundary.
|
||||
Unknown schemas may be observed for diagnostics but are not silently persisted
|
||||
as untyped truth. Heimdall access discovery therefore registers
|
||||
`gamecult.eve.provider_advertisement.v1`,
|
||||
`heimdall.command_boundary.v1`,
|
||||
`gamecult.eve.plugin_advertisement.v1`, and
|
||||
`heimdall.transport_profile.v1`. Their globally unique record keys prevent one
|
||||
schema from overwriting another in the shared catalog. Odin returns the
|
||||
redacted route metadata; it does not proxy private auth operations or own any
|
||||
claim, completion, token, or app secret.
|
||||
|
||||
Gjallar is the Yggdrasil-resident aggregate compositor for what Odin can show.
|
||||
Its runtime lives in `/srv/gjallar/current` and consumes Odin's accepted
|
||||
provider-state snapshot. Gjallar owns enumeration, tiling, and publication of
|
||||
the typed `gjallar.overview` Eve surface. Eve clients own graphical, terminal,
|
||||
and framebuffer lowering. Gjallar must not own the underlying registry, probe,
|
||||
provider truth, client pixels, or translation decisions.
|
||||
|
||||
Idunn (`GameCult/Idunn`) owns deployment and daemon survival, including Odin's.
|
||||
It builds from each target's recipe, activates and restarts the admitted
|
||||
incarnation, and holds the deployment and lifecycle brakes. Odin publishes what
|
||||
it can see and reads Idunn's topology projection; it does not start, restart,
|
||||
deploy, or health-probe any daemon, and no Odin code path keeps a process alive.
|
||||
When human action is needed, Idunn uses CultMesh to request a Bifrost-owned
|
||||
operator notification crossing. Idunn must not own Verse discovery, schema
|
||||
truth, provider dashboards, identity grants, Discord delivery, owner-DM
|
||||
delivery, or renderer layout.
|
||||
|
||||
Muninn is the portable local telemetry Verse assembler. Its Rust body lives in
|
||||
the `GameCult/Muninn` repository and publishes `muninn.telemetry_surface.v1`
|
||||
through CultMesh/CultCache. Muninn may run on Raven, Nightwing, Starfire, or any future
|
||||
device body. It names locally accessible telemetry affordances: screen capture,
|
||||
loopback audio, microphones, cameras, and future sensors. Muninn does not start
|
||||
expensive capture streams merely because the daemon is alive. The default
|
||||
`serve` posture publishes an idle typed surface; explicit activation, such as
|
||||
`muninn activate` for Raven A/V over SRT, is the only path that starts FFmpeg,
|
||||
WASAPI loopback, video capture, or similar resource-consuming workers.
|
||||
|
||||
Muninn owns local telemetry discovery and stream activation boundaries. It does
|
||||
not own Mimir's normalized ingest ledger, OBS rendering, Gjallar composition,
|
||||
Odin discovery truth, or Idunn keepalive policy. Active stream records such as
|
||||
`muninn.capture_stream.v1` are evidence of requested streams, not permission for
|
||||
startup to burn capture resources.
|
||||
|
||||
Move optical marker extraction belongs to Muninn because it is sensor stream
|
||||
exposure, not Mimir fusion or Odin registry truth. The native helper lives at
|
||||
`crates/muninn-move-tracker`; Muninn may publish per-frame candidates as
|
||||
`muninn.move_marker_candidate.v1`. USB Move controller facts are
|
||||
`muninn.move_controller_state.v1` receipts. Those records are not the hot
|
||||
tracking transport: Muninn bundles marker candidates and controller states into
|
||||
a CultMesh bytes stream frame with metadata schema
|
||||
`mimir.muninn_move_evidence_stream_frame.v1`. Mimir consumes that stream into
|
||||
tracking buffers and later fusion. Odin indexes the schema and projection
|
||||
surface only.
|
||||
|
||||
Bifrost is the bridge for Persona speech and other public/owner-facing
|
||||
crossings. When a Persona interpreter decides a Persona speaks, the accepted
|
||||
side effect is a Bifrost CultMesh command or document that names actor,
|
||||
authority, target surface, context, policy result, and receipt path. VoidBot
|
||||
observes Discord, preserves room cognition, moderates, and may provide
|
||||
compatibility delivery, but it is not the owner of swarm speech transport.
|
||||
VoidBot's repo search, Discord history search, archive lookup, and source
|
||||
retrieval are required native CultCache/CultMesh service surfaces. Any remaining
|
||||
VoidBot-local or MCP-only implementation is migration debt. MCP is the bridge
|
||||
for external agentic access, not the native path for GameCult agents that
|
||||
already have CultMesh affordances.
|
||||
|
||||
## Target Mechanism
|
||||
|
||||
```text
|
||||
Verse announcement
|
||||
-> CultNet hello and schema catalog exchange
|
||||
-> Odin registry
|
||||
-> compatibility and translation index
|
||||
-> subscriptions / worker routing / dashboard projection
|
||||
```
|
||||
|
||||
## Invariants
|
||||
|
||||
- Odin owns the accepted registry of known Verses.
|
||||
- A Verse owns its own schemas and authority model; Odin indexes and translates, it does not silently rewrite local truth.
|
||||
- Device clients own sensor and media capture; Mimir owns the normalized ingest ledger; Odin owns the aggregate operator projection.
|
||||
- Muninn advertises local telemetry affordances cheaply and starts capture only
|
||||
after an explicit activation request.
|
||||
- Muninn's live Move evidence is a CultMesh stream frame body; CultCache
|
||||
Move records are receipts/debug state and must not become Mimir's hot
|
||||
tracking path.
|
||||
- Translation paths must name source schema, target schema, lossiness, authority, and version.
|
||||
- Service presentation flows are CultMesh/Eve/CultUI interface projections. Odin aggregates those projection graphs; it does not replace them with nameplate summaries.
|
||||
- Renderers lower surfaces only. If a renderer fixes network truth, the machine is split-brained.
|
||||
- CultCache is the durable state substrate; CultNet is the wire vocabulary; CultMesh is the Verse and peer-consensus layer.
|
||||
- The Eve surface carries explicit `verse` and `service` nodes plus provider-owned retained interface trees. Compact renderers may derive visual facets from those nodes, but may not invent observation truth.
|
||||
- Rust organs must accept mocked inputs through narrow traits. Unit tests prove
|
||||
local invariants; pipeline smokes prove adjacent typed handoff; full daemon
|
||||
boots are not the only test path.
|
||||
- JSON is not state authority. It is allowed only for schema publication,
|
||||
debugging, compatibility export, or external xenos boundaries.
|
||||
|
||||
## Test Surfaces
|
||||
|
||||
Current Rust verification:
|
||||
|
||||
- `pipeline_collects_from_injected_ports`: proves ingest ports and clock injection.
|
||||
- `memory_repository_supports_fast_unit_tests`: proves repository consumers can test without CultMesh.
|
||||
- `cultmesh_repository_round_trips_typed_records`: proves typed Odin records
|
||||
persist through CultMesh/CultCache and reload from `.cc`.
|
||||
|
||||
## Service Architecture Contract
|
||||
|
||||
Odin is the witness for the GameCult service contract:
|
||||
|
||||
```text
|
||||
durable service state -> CultCache .cc
|
||||
shared local visibility -> CultMesh
|
||||
interactive presentation -> Eve GUI/TUI DSL
|
||||
discovery and aggregation -> Odin
|
||||
renderer bodies -> Eve clients, browser, compact TUI, native surfaces, overlays
|
||||
```
|
||||
|
||||
When Odin sees a service, it should be able to answer:
|
||||
|
||||
- What Verse owns this service?
|
||||
- Which typed schemas does it publish?
|
||||
- Where is its durable `.cc` state or CultCache-compatible store?
|
||||
- Which CultMesh documents or providers make it visible locally?
|
||||
- Which Eve GUI/TUI surface represents its meaningful presentation and controls?
|
||||
- Which command boundary accepts, denies, forwards, or reconciles user intent?
|
||||
- Which fields are stale, predicted, denied, or authoritative?
|
||||
|
||||
This is not a reporting nicety. It is how Odin prevents services from becoming
|
||||
private little islands with separate websites, dashboards, state formats, and
|
||||
separate command languages.
|
||||
|
||||
## Service Surface
|
||||
|
||||
Odin's service records come from provider-owned presences and advertisements
|
||||
received through CultMesh/RUDP, not from host probes.
|
||||
|
||||
Gjallar consumes Odin's accepted CultMesh/Eve state and composes every visible
|
||||
provider surface into one typed aggregate on Yggdrasil. EveCanvas, browser, and
|
||||
TUI clients lower that aggregate. If Odin starts deciding tile composition, the
|
||||
composition owner has leaked upward. If providers tune themselves for one
|
||||
client instead of emitting clean Eve/CultUI surfaces, provider truth has leaked
|
||||
downward.
|
||||
|
||||
## Current Interface Surface
|
||||
|
||||
Odin discovers provider advertisements and interface bindings from daemon-owned
|
||||
CultMesh/CultCache stores plus live CultNet/RUDP announcements. Providers own
|
||||
their compositions; Odin embeds each provider's `surface.root` as an
|
||||
`interface` child with provenance, version, status, source witness, and layout
|
||||
metadata.
|
||||
|
||||
This is the model for future services: if a service publishes an operator interface, ingest the Eve/CultUI composition graph and lower it. Do not collapse it into a service-status tile unless the graph is unavailable and the tile is explicitly a temporary probe.
|
||||
|
||||
The expected provider output is Eve DSL or an equivalent
|
||||
`gamecult.eve.surface.v1` retained tree. GUI and TUI are lowerings of the same
|
||||
interactive language; they are not separate dashboard products. Huginn's `.cc`
|
||||
inspection surface is the current clean example: Huginn inspects CultCache bytes
|
||||
and emits Eve DSL, while Eve or any other runtime owns presentation.
|
||||
|
||||
Provider advertisements are the promotion path out of probing. Odin's document
|
||||
registry already accepts `gamecult.eve.provider_advertisement.v1` alongside
|
||||
`gamecult.eve.interface_binding.v1` and `gamecult.eve.surface_state.v1`.
|
||||
Daemons should publish advertisements that name service id, Verse id, schema
|
||||
catalog, `.cc` witnesses, Eve surface keys, command boundaries, nested Verses,
|
||||
style capabilities, freshness, and redaction policy. Once an advertisement is
|
||||
available, Odin should prefer it over LAN scans, hardcoded deck URLs, private
|
||||
layout files, host probes, or web-dashboard scraping. Daemon health and
|
||||
provider state should publish through `cultnet.transport.rudp.v0`;
|
||||
product/debug routes are outside-Odin lowerings only and must not become
|
||||
daemon-owned truth.
|
||||
|
||||
Provider advertisements should also publish semantic CultMesh addresses in this
|
||||
shape:
|
||||
|
||||
```text
|
||||
asgard.<machine>.<service>/<resource>
|
||||
```
|
||||
|
||||
Examples:
|
||||
|
||||
```text
|
||||
asgard.starfire.odin/eve/providers
|
||||
asgard.starfire.bifrost/eve/tui
|
||||
asgard.starfire.bifrost/eve/gui
|
||||
asgard.yggdrasil.streampixels/eve/tui
|
||||
asgard.yggdrasil.streampixels/eve/gui
|
||||
```
|
||||
|
||||
The canonical service may omit the current machine when identity should survive
|
||||
relocation, such as `asgard.bifrost`. Located service addresses name the current
|
||||
host, such as `asgard.starfire.bifrost` now and
|
||||
`asgard.yggdrasil.bifrost` after migration. CultNet routes are transport
|
||||
metadata for resolving those names. Native daemon transport is CultNet over the
|
||||
shared RUDP profile; renderer URLs belong outside Odin and are not service
|
||||
identity.
|
||||
|
||||
The canonical contract lives in
|
||||
`E:\Projects\Eve\docs\provider-advertisement-contract.md`.
|
||||
|
||||
## Observation Surface
|
||||
|
||||
Odin no longer tails Mimir JSONL artifacts for observation truth. Device and
|
||||
media observations must arrive as provider-owned CultMesh/Odin records or
|
||||
retained Eve/CultUI surfaces; Odin may project those accepted records, but it
|
||||
does not scrape artifact logs to synthesize observation streams.
|
||||
|
||||
## First Translation Model
|
||||
|
||||
Odin's translation registry should start as data, not magic:
|
||||
|
||||
- `sourceSchema`
|
||||
- `targetSchema`
|
||||
- `translationKind`: `identity`, `projection`, `lossyProjection`, `adapter`, or `unsupported`
|
||||
- `owner`
|
||||
- `version`
|
||||
- `notes`
|
||||
|
||||
No regex tribunals for meaning. Natural-language schema interpretation can be assisted by models later, but accepted translation routes must be inspectable typed state.
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
# Gjallar
|
||||
|
||||
Gjallar is the Yggdrasil-resident aggregate compositor for Odin's domain.
|
||||
|
||||
Odin accepts Verse discovery and provider-owned Eve surfaces. Gjallar consumes
|
||||
that accepted snapshot and publishes one typed `gjallar.overview` composition
|
||||
containing every currently visible provider surface. EveCanvas, browsers, TUI
|
||||
clients, and agent readers lower the same aggregate without becoming its owner.
|
||||
|
||||
## Authority Map
|
||||
|
||||
- Owner: Gjallar owns aggregate membership, tiling, marquee placement, layout
|
||||
intent, visibility, and composition versioning.
|
||||
- Inputs: Odin's accepted CultMesh/CultNet snapshot, provider-owned surface
|
||||
graphs, Odin's canonical marquee tape, and display-independent layout hints.
|
||||
- Outputs: `gjallar.overview`, its CultCache/CultMesh/CultNet publication, and
|
||||
compact composition/freshness telemetry.
|
||||
- Derived state: panel weights, ordering, tile layout, unavailable presentation,
|
||||
and aggregate version are derived from Odin/provider state.
|
||||
- Forbidden writers: Gjallar does not probe hosts, accept discovery truth,
|
||||
mutate provider state, open `/dev/fb0` in daemon mode, capture client input,
|
||||
or decide client pixels. Eve clients do not decide aggregate membership.
|
||||
- Shared paths: iOS/UIKit, browser, optional TUI/framebuffer, and agent capture
|
||||
lower the same published overview.
|
||||
- Deletion line: Nightwing's `gjallar.service`, framebuffer lifecycle target,
|
||||
and `nightwing-gjallar` Idunn actuator are obsolete authorities and must not
|
||||
survive Yggdrasil admission.
|
||||
|
||||
## Body
|
||||
|
||||
- Runtime source: `F:\Projects\Gjallar` / `GameCult/Gjallar`.
|
||||
- Daemon host: Yggdrasil beside `odin.service` and
|
||||
`idunn-yggdrasil.service`.
|
||||
- Unit: `gjallar-yggdrasil.service`.
|
||||
- Runtime mode: `--headless`; no framebuffer, font atlas, or mouse device is
|
||||
opened.
|
||||
- Input: Yggdrasil-local Odin accepted snapshot over CultNet/RUDP.
|
||||
- State: `/var/lib/gamecult/gjallar/gjallar.service.cc`.
|
||||
- Deployment: immutable source/dependency pins, a digest-pinned .NET SDK image,
|
||||
and Idunn's root-owned `gjallar` actuator manifest.
|
||||
- Continuity: systemd restarts the admitted release. Until Gjallar publishes
|
||||
authenticated C# health, Idunn observes its diagnostic health but does not
|
||||
restart merely because authenticated publication is missing.
|
||||
|
||||
## Runtime Contract
|
||||
|
||||
```text
|
||||
provider-owned Eve surfaces
|
||||
-> Odin accepted discovery/provider snapshot
|
||||
-> Gjallar aggregation and tiling
|
||||
-> typed gjallar.overview Eve surface
|
||||
-> EveCanvas / browser / TUI / agent lowerers
|
||||
```
|
||||
|
||||
## Invariants
|
||||
|
||||
- Odin owns discovery and provider acceptance.
|
||||
- Providers own their surface truth and command consequences.
|
||||
- Gjallar owns composition, not discovery or pixel rendering.
|
||||
- Eve clients own rendering and local input capture.
|
||||
- Missing or invalid provider surfaces become explicit unavailable state or are
|
||||
omitted; Gjallar never invents replacement truth.
|
||||
- Daemon health follows accepted-snapshot and aggregate-publication freshness,
|
||||
not rendered frames.
|
||||
|
|
@ -1,306 +0,0 @@
|
|||
# Hermodr Extraction Plan
|
||||
|
||||
## Objective
|
||||
|
||||
Hermodr is the Eve-to-browser lowering bridge. It should resolve providers
|
||||
through Odin's directory, subscribe to those providers for their state, render
|
||||
their surfaces to browsers, carry operator intent back to the provider that owns
|
||||
the consequence, and — new — emit a surface as static bytes for audiences that
|
||||
are not Verse participants.
|
||||
|
||||
It currently runs its own provider ingress and its own state store instead. This
|
||||
plan deletes that, moves the daemon into its own repository, and adds static
|
||||
lowering as a second output mode.
|
||||
|
||||
## What Odin is
|
||||
|
||||
Odin is a rendezvous organ. It tells you what state is available, what schemas
|
||||
speak it, and where to reach it. It does not tell you what the state is, and it
|
||||
is not in the data path.
|
||||
|
||||
A provider owns its state and serves it. Odin owns the directory. A consumer
|
||||
resolves through the directory and then talks to the provider.
|
||||
|
||||
Hermodr's command path already works this way. `findProviderCommandRoute` looks
|
||||
a route up in the catalog and then publishes the command to the provider's own
|
||||
advertised endpoint. Erycina's surface says the same thing from the other side:
|
||||
`discoveryOwner: "Odin"`, while `canonicalSurfaceUri` points at
|
||||
`cultmesh://asgard.starfire.erycina/eve/operator/surface` — Odin tells you where,
|
||||
you go ask Erycina.
|
||||
|
||||
## Current mechanism
|
||||
|
||||
Hermodr lives in Odin as Node inside a Rust repository: `src/hermodr-daemon.cjs`
|
||||
(1,462 lines), `src/hermodr-state-stream.cjs` (32), `test/hermodr-*.test.cjs`
|
||||
(191), four PowerShell lifecycle scripts, and a persona avatar. Eve holds the
|
||||
browser-side consumer at `web/hermodr-provider-catalog.mjs`.
|
||||
|
||||
### The intent path is already correct
|
||||
|
||||
`POST /hermodr/commands/eve` is generic across providers. It resolves a
|
||||
`cultmesh://` target from the body or from the provider catalog, wraps intent as
|
||||
`gamecult.eve.command.v1`, stamps `publishedBy: "hermodr-browser-lowering"`,
|
||||
publishes to the provider's advertised route, and returns a receipt. Eve's client
|
||||
passes `commandSink: publishCommandIntent` into the renderer and refuses any
|
||||
response that is not a correlated `gamecult.eve.command_receipt.v1` in state
|
||||
`reconciled`.
|
||||
|
||||
This path is kept unchanged. It is also the reference for what the state path
|
||||
should look like.
|
||||
|
||||
### The state path is the defect
|
||||
|
||||
`main()` constructs an `OdinLivePublicationSource` — an in-process `Map` with a
|
||||
read side (`latest`, `watch`, `watchLifecycle`) and a write side (`accept`,
|
||||
`withdraw`). It opens its own provider-session ingress, bound to its own address
|
||||
with its own session token, and feeds accepted documents into that private
|
||||
store. `HermodrStateStreamRegistry` watches the store and pushes lifecycle
|
||||
events to browsers.
|
||||
|
||||
So providers publish their state directly into Hermodr, and Hermodr decides what
|
||||
it holds. A surface reaches a browser only if its provider binds to Hermodr,
|
||||
which makes a required ingress out of something named a bridge.
|
||||
|
||||
The class is called `OdinLivePublicationSource` and lives in `src/odin/`. Odin
|
||||
does not use it. Neither the coordinator nor any Odin test references it, and
|
||||
the same is true of `provider-session-ingress.cjs`. This is Hermodr's own state
|
||||
machinery wearing Odin's name and filed under Odin's shared modules, which is
|
||||
why it reads as though Odin sanctioned it.
|
||||
|
||||
### The loop does not close
|
||||
|
||||
The command path was added after the fact, on discovering the original bridge
|
||||
was entirely non-interactive. It was added well, but to one leg only, and the
|
||||
rest still assumes a viewer.
|
||||
|
||||
Intent leaves correctly, to the provider that owns the consequence. The
|
||||
consequence returns as newly published provider state — and that reaches the
|
||||
browser only through Hermodr's private ingress. A provider must therefore
|
||||
publish twice: once wherever the Verse expects it, and once into Hermodr, so the
|
||||
operator can see their own action land.
|
||||
|
||||
The core issue is not a missing write path. Intent goes out the right door and
|
||||
consequence comes back through the wrong one. Deleting the ingress is what
|
||||
closes the loop: once state arrives by subscription to the provider, a command's
|
||||
effect returns by the same route as every other change.
|
||||
|
||||
The client already carries the seed of the static case. `publishCommandIntent`
|
||||
checks `liveHermodr` and, when absent, logs the intent instead of posting it.
|
||||
|
||||
## Invariants
|
||||
|
||||
- A provider owns its state and serves it. Nothing else holds it as truth.
|
||||
- Odin owns the directory: what exists, what schema, where. Never the data path.
|
||||
- Hermodr owns the browser edge: session, transport, lowering, relay of intent.
|
||||
It holds projections with sequence numbers, never truth.
|
||||
- A lowering runtime cannot make something true by rendering it.
|
||||
- Static output carries no back-channel and must not pretend otherwise.
|
||||
|
||||
## Authority map
|
||||
|
||||
**Owner.** Each provider owns its own state, its surface document, and its
|
||||
command routes. Odin owns provider discovery, schema awareness, and route
|
||||
advertisement.
|
||||
|
||||
**Inputs to Hermodr.** Odin's catalog, to resolve providers and routes. Provider
|
||||
subscriptions, for state. Browser requests.
|
||||
|
||||
**Outputs.** Lowered surfaces; live state streams; typed
|
||||
`gamecult.eve.command.v1` documents published to provider routes; receipts;
|
||||
static artifacts.
|
||||
|
||||
**Derived, not owner.** Everything Hermodr holds about provider state is a
|
||||
projection carrying a sequence number. Its staleness marks are display facts,
|
||||
not withdrawal decisions.
|
||||
|
||||
**Forbidden writers.** Hermodr may not accept or withdraw a publication. It may
|
||||
not bind a provider session. It may not author a command as itself. Odin may not
|
||||
become a state relay; if a consumer can read state *from* Odin, the directory
|
||||
has become the data path.
|
||||
|
||||
**Shared paths.** State resolution and command routing must use the same
|
||||
catalog lookup. Live and static lowering must render from the same surface graph
|
||||
through the same composition primitive; if they diverge visually, the static
|
||||
path is wrong, not the graph.
|
||||
|
||||
## Intended change
|
||||
|
||||
1. Delete the ingress and the private store. Hermodr resolves a provider through
|
||||
the catalog, exactly as the command path already does, and subscribes to that
|
||||
provider for state.
|
||||
2. `HermodrStateStreamRegistry` survives untouched. It consumes only
|
||||
`source.forProvider(id).watchLifecycle(schemaId, recordKey, cb)`, which is
|
||||
already a narrow port. Replace what sits behind that port with a provider
|
||||
subscription client and every stream, sequence number, and browser keeps
|
||||
working.
|
||||
3. Extract the daemon to `GameCult/Hermodr`.
|
||||
4. Add static lowering as a library with a CLI entry point.
|
||||
|
||||
## Cut line
|
||||
|
||||
Deleted outright:
|
||||
|
||||
- the `OdinLivePublicationSource` construction and every write into it
|
||||
- `createProviderSessionIngress`, and the `--provider-session-bind` and
|
||||
`--provider-session-token` options
|
||||
|
||||
Leaves Odin with Hermodr, because Odin never used it:
|
||||
|
||||
- `src/odin/live-publication-source.cjs` and
|
||||
`src/odin/provider-session-ingress.cjs`, both referenced only by
|
||||
`hermodr-daemon.cjs` and `test/hermodr-live-publication.test.cjs`. They are
|
||||
misfiled Hermodr code, not shared Odin modules. If the subscription client
|
||||
fully replaces them, they are deleted rather than moved.
|
||||
- `src/odin/utils.cjs` (68 lines), also Hermodr-only. Copy the handful of
|
||||
helpers actually used; do not extract a utility library.
|
||||
|
||||
Moved out of Odin, for its own reasons:
|
||||
|
||||
- `src/odin/idunn-rudp.cjs` (324 lines) follows Idunn to `GameCult/Idunn` as a
|
||||
published health-client contract. Genuinely shared — the coordinator and a
|
||||
health test use it too — so both consume it from its owner rather than each
|
||||
carrying a copy.
|
||||
|
||||
Resolved, not copied:
|
||||
|
||||
- `src/odin/documents.cjs` (536 lines) is the only true shared dependency, used
|
||||
by the coordinator, Hermodr, and two discovery tests. It survives as a decode
|
||||
contract and must become a published package. Forking it creates two
|
||||
definitions of one wire format, which is the failure this plan exists to
|
||||
remove.
|
||||
|
||||
## Subtraction budget
|
||||
|
||||
Hermodr loses its ingress and its state store. Odin loses the Node daemon, four
|
||||
lifecycle scripts, three misfiled Hermodr modules, and `idunn-rudp.cjs`. One
|
||||
shared document package appears. Net: a duplicate acceptance authority deleted,
|
||||
`src/odin/` reduced from fourteen modules to ten, one package added, one
|
||||
repository added.
|
||||
|
||||
The static lowering library is the only genuinely new surface. It earns its keep
|
||||
by making public storefronts hostable without a mesh, a runtime, or a daemon —
|
||||
the difference between a founding creator being able to self-host and not.
|
||||
|
||||
## Build budget
|
||||
|
||||
Node only; no Rust targets change. Odin's crates are untouched. The new
|
||||
repository is one package with a test target, and static lowering adds a CLI
|
||||
entry to that same package rather than a second package.
|
||||
|
||||
## Phases
|
||||
|
||||
**Phase 1 — Idunn RUDP client. Done, as a split.** The transport moved to
|
||||
CultLib as `cultnet-ts`'s `signed-daemon-health`; Idunn now documents its
|
||||
connection id and schema names; Odin keeps a 65-line adapter that declares that
|
||||
contract. The whole-file move originally specified was not carried out, for the
|
||||
reasons below.
|
||||
|
||||
**Phase 2 — Delete the ingress, in place. Done.** `ProviderSubscriptionSource`
|
||||
replaces the private store behind the existing port, the ingress and its options
|
||||
are gone, and `src/odin/` is down to twelve modules. Verified by unit tests;
|
||||
behaviour against a live provider over RUDP remains an operator step.
|
||||
|
||||
**Phase 3 — Publish the document contract. Done.** The 45 definitions are
|
||||
`cultcache-ts`'s `defineSwarmDocuments`, verified byte-identical to what
|
||||
`documents.cjs` produced. Odin keeps a 26-line adapter under the name its
|
||||
consumers already call. `utils.cjs` left with Hermodr, which used one of its six
|
||||
functions.
|
||||
|
||||
**Phase 4 — Extract. Done.** `GameCult/Hermodr`, MIT, sixteen files. The three
|
||||
dependencies on Odin were resolved rather than carried: the catalog comes from
|
||||
CultLib, Idunn's health contract is declared locally over the CultLib transport
|
||||
with `sourceRuntimeId` `hermodr`, and `parseArgs` is seventeen lines of
|
||||
`args.cjs`. `src/odin/` is down from fourteen modules to eleven, and Odin's
|
||||
CommonJS surface from sixteen files to twelve.
|
||||
|
||||
**Phase 5 — Static lowering. Done, in `GameCult/Hermodr`.** A library and CLI
|
||||
rendering a surface graph to standalone HTML through the same renderEveSurface
|
||||
the browser uses, under jsdom, with command controls disabled and labelled
|
||||
because a static page has no back-channel. Runnable without the daemon.
|
||||
|
||||
All five phases are complete. This document stays here as the record of what
|
||||
left Odin and why; the work now happens in Hermodr.
|
||||
|
||||
## Verification
|
||||
|
||||
Positive:
|
||||
|
||||
- A provider that publishes only to its own advertised address reaches a browser
|
||||
through Hermodr. This fails today and is the point of the work.
|
||||
- Live state streams keep their sequence numbers, and stale and reconnected
|
||||
transitions still fire across a provider restart.
|
||||
- A browser command still reaches its provider and returns a correlated receipt.
|
||||
- Static output of a surface renders equivalently to the live lowering of the
|
||||
same graph.
|
||||
|
||||
Negative, and these are the ones that prove the authority moved:
|
||||
|
||||
- Hermodr cannot be made to accept a provider publication. The code path is
|
||||
gone, not merely unused.
|
||||
- Starting Hermodr with the removed ingress options fails loudly rather than
|
||||
silently ignoring them.
|
||||
- With Odin stopped, Hermodr cannot resolve providers it has not already
|
||||
resolved, but existing provider subscriptions keep delivering. If stopping
|
||||
Odin stops live state, Odin is in the data path and this plan failed.
|
||||
- Nothing reads state *from* Odin. A consumer able to do so proves the directory
|
||||
became a relay.
|
||||
- Two Hermodr instances against the same provider show the same state.
|
||||
- Static output contains no command controls that appear operable. A dead button
|
||||
in a static page is a lie about a back-channel that does not exist.
|
||||
|
||||
## Why Phase 1 was split rather than moved
|
||||
|
||||
Reading `idunn-rudp.cjs` before moving it changed the answer. Of its 324 lines,
|
||||
13 mention Idunn, and most of those are error message strings. The genuinely
|
||||
Idunn-owned content is two constants: the connection ID `0x1d0d0001` and the
|
||||
schema name `idunn.signed_daemon_health.v1`. Everything else — Ed25519 signing,
|
||||
RUDP session management, packet receipt, endpoint parsing, msgpack framing — is
|
||||
generic CultNet client plumbing.
|
||||
|
||||
Moving the file to Idunn would put 311 lines of transport code into a Rust
|
||||
repository with no Node packaging, and would give Odin a cross-repo filesystem
|
||||
dependency on a sibling checkout. The file already resolves CultLib by walking
|
||||
`__dirname/../../../CultLib`, which works only while Odin sits beside CultLib;
|
||||
Erycina had the identical bug and it broke the moment that repository moved.
|
||||
Repeating it deliberately, in the plan whose purpose is removing misplaced
|
||||
ownership, would be a poor trade.
|
||||
|
||||
It also buys nothing yet. The duplication it prevents does not exist until
|
||||
Hermodr leaves in Phase 4, because both consumers still live in this repository.
|
||||
|
||||
Three options, in the order I would rank them:
|
||||
|
||||
1. **Generic to CultLib, constants to Idunn.** The transport belongs beside
|
||||
`cultnet-ts`, which Odin already resolves into and Hermodr will too. Idunn
|
||||
owns the connection ID and the schema name. This is the real fix, and it
|
||||
removes the sibling-path bug rather than relocating it.
|
||||
2. **Defer to Phase 4.** Correct and free. Move it when the duplication actually
|
||||
threatens, and decide the shape then.
|
||||
3. **Move it whole to Idunn.** As originally written. Cheapest to type, and it
|
||||
puts transport code where it does not belong while adding a fragile path.
|
||||
|
||||
Option 1 was chosen and carried out. `idunn-rudp.cjs` fell from 324 lines to 65:
|
||||
Idunn's three constants and the call shapes Odin's consumers already use.
|
||||
`packages/cultnet-ts/src/signed-daemon-health.ts` holds the transport, taking
|
||||
the contract as a parameter. Idunn's
|
||||
`docs/signed-daemon-health-authority.md` gained the wire-contract table it was
|
||||
missing — the connection id had existed only in this consumer's code.
|
||||
|
||||
Both the moved module and the adapter dropped the sibling-path assumption on the
|
||||
way, honouring `CULTLIB_ROOT` instead of walking up three directories from
|
||||
wherever the file happens to sit.
|
||||
|
||||
Phase 3's remaining decision is unchanged: `documents.cjs` is now the only
|
||||
genuinely shared dependency left between Odin and a future Hermodr.
|
||||
|
||||
## Open questions for the operator
|
||||
|
||||
1. **`documents.cjs` ownership.** Published from Odin, or promoted into CultLib
|
||||
beside the other typed contracts? It is a wire format several services share,
|
||||
which argues for CultLib, but Odin authors it.
|
||||
2. **Ephemeral view state.** Expanded panels, unsent field contents, optimistic
|
||||
pending states. Client-side today by omission rather than decision.
|
||||
3. **Static lowering and identity.** A live surface knows who is viewing; a
|
||||
static artifact does not. Either static surfaces are public-only, or the
|
||||
lowering takes an audience parameter and emits per-audience artifacts.
|
||||
Public-only is smaller and covers the storefront.
|
||||
|
|
@ -1,570 +0,0 @@
|
|||
# Odin store repair and recovery: cut map
|
||||
|
||||
**Operator rulings, 2026-09-30:**
|
||||
- **Q-R1:** "You got it". A foreign file under a running Odin is set aside and Odin continues.
|
||||
- **Q-R2:** "Yep". Keep the previous generation on disk.
|
||||
- **Q-R3:** "Start empty". Nothing decodable at activation means Odin starts empty.
|
||||
- **Q-R4:** "Yep, and Idunn needs better reporting capabilities so it can serve as watchdog. I ought to get a message
|
||||
about this on Discord if it ever happens."
|
||||
- Stop writing after the third set-aside in 10 minutes, and keep serving.
|
||||
- **New requirement:** Idunn watchdog reporting that reaches the operator on Discord. It is mapped as its own
|
||||
campaign and is not an Odin cut.
|
||||
- **Q-R5:** "That's fine, Odin still runs after all, right?" Yes. Odin keeps serving, and the store condition is
|
||||
published as its own typed record, not as a degraded presence.
|
||||
- **Q-R6:** "Approved". Both drills.
|
||||
|
||||
**R1 LANDED: CultLib main `daedfdd6` (2026-09-30).** The merged tree `0e6891b3` was verified on Yggdrasil in every
|
||||
runtime, including the four-runtime store interop test. That test runs close to its 30 s per-command limit under
|
||||
load (`cult-cache.test.ts:1407`), so it is fragile: follow-up. Consumer pins still gate their own bumps: Bifrost only
|
||||
with R3b's TS fsync; Muninn and Idunn with their error-text matches.
|
||||
|
||||
**R1 status (Self, 2026-09-30).** Built on CultLib `hands/cultcache-read-failures` (`c0b56e06`..`b7908668`), then
|
||||
fix batch 2 (`..2d7a3c78`). Soul's verdict on batch 2 was to hold the merge. Batch 3 is in Hands, fixing:
|
||||
- **Blocker, F1 (zero-byte stores on Yggdrasil).**
|
||||
- Two zero-byte stores are live: `/srv/bifrost/persona-feedback/{inbox/observations,outbox/deliveries}.cc`.
|
||||
- They are pre-created by `touch` in gamecult-ops `install-bifrost-persona-feedback-native-yggdrasil.sh:42,46`
|
||||
and `bootstrap-bifrost-persona-feedback-yggdrasil.sh:22,29`.
|
||||
- Bifrost `persona-feedback.mjs serve` and Epiphany `epiphany-swarm` crash-loop on them once their CultLib pins
|
||||
move past R1.
|
||||
- **Neither pin may move until the scripts stop pre-creating stores and Self has moved the live files aside.**
|
||||
- **F4.** Rust errors echo record values through serde's text (`lib.rs:753-758`). The header is echoed without bound.
|
||||
- **F6.** A dangling symlink reads as absent, and the writer then replaces it.
|
||||
- **F7.** The same NotFound-only rule in Python's `JsonLinesBackingStore` and in TS `pullAll`'s repair write.
|
||||
- **M7.** A header-prefix fixture.
|
||||
Recorded for later cuts:
|
||||
- **F3 → R2/R3b.** TS, Python and C# decode the whole buffer before reading the header. A truncated newer-format
|
||||
store (20..n-1 bytes) is "unsupported format" in Rust and a raw decode error elsewhere.
|
||||
- **F5 → R3.** C# `ReadSnapshot` reads ENOTDIR/EISDIR as an empty store (`FileInfo.Exists`, `CultCache.cs:3439`).
|
||||
C# `DirectoryMessagePackBackingStore` `File.Exists` (`:409`, `:497`) is the same. This is a live disagreement between
|
||||
runtimes until R3.
|
||||
- **F8 → R3b.** Python leaves its staging file behind when a write fails (`stores.py:124-128`).
|
||||
- **Tooling.**
|
||||
- `packages/cultcache-rs` has no committed `Cargo.lock`, so the map's `cargo test --locked` cannot run.
|
||||
- The cultcache-ts interop test needs `CARGO_TARGET_DIR` unset under ygg-verify.
|
||||
- **Soul on batch 3 (`2d7a3c78..3c9e195b`, gamecult-ops `bca7978`), 2026-09-30: merge both.** Pre-merge batch 4 is in
|
||||
Hands: fix the README symlink claim (S1), close value echoes in the redb and typed-decode paths (S2) and in the
|
||||
Python/TS non-string key echo (S3), make the unlock policy coherent (S9), classify encode refusals separately from
|
||||
`NotReplaced` (S10), and fix a test timeout (S11). The ops branch deletes the guard test (S6) and the dead compose
|
||||
layout (S5).
|
||||
- **Open for R3:** writes through a symlinked store path replace the link, so a store behind a link to a mounted
|
||||
volume moves into the container layer. R3 decides resolve or refuse.
|
||||
- **Open for R3b:** Python and TS never fsync. After F1 their first create-by-rename is not covered by ext4
|
||||
`auto_da_alloc`, so a crash can leave zero bytes, which every post-R1 reader refuses forever. **Bifrost's CultLib
|
||||
pin moves past R1 only together with R3b's TS fsync.**
|
||||
- **For W0 (Odin):** classify a write failure through `CultCacheStoreWriteFailed.kind` together with its `io::Error`,
|
||||
never retry on `NotReplaced` alone (S10).
|
||||
- Deployed Epiphany is `d2ca6630`, using a vendored cultcache-rs (`ba6a4874`) that reads a missing store as empty.
|
||||
All Epiphany units have been inactive since 2026-08-22.
|
||||
- **Done 2026-09-30 20:48 CEST (Self).** gamecult-ops merged at `3fa22aa`. The inbox is setgid 2750. All three
|
||||
zero-byte stores were renamed in place and moved to `/root/zero-byte-stores-20260930/` on Yggdrasil, with
|
||||
`manifest.txt` recording owner, mode and mtime. None had gained bytes. All three Bifrost units stayed active, and
|
||||
persona-feedback logged nothing and did not restart.
|
||||
- The compose/container lane (the compose file, two deploy scripts, the `.ps1` uploader, two runbooks) is dead:
|
||||
the live Bifrost units run native `dotnet` and `node`. **Deleted 2026-09-30, operator: "delete the Bifrost lane".**
|
||||
gamecult-ops `fdb4356`. Its live trigger, the Idunn legacy actuator route `deploy bifrost-persona-feedback`, was
|
||||
retired in Idunn `7793b5b` and installed on Yggdrasil at 21:50 CEST (sha256 `d95fa08c...`; the previous binary is
|
||||
at `/root/idunn-yggdrasil.pre-bifrost-retire-20260930`). The manifest and `/srv/bifrost/compose` were moved to
|
||||
`/srv/retired/retired-2026-09-30/`. All three Bifrost units stayed active. **Follow-up:** persona-feedback has no
|
||||
Idunn deploy route now. Give it a real Idunn binding.
|
||||
- Live zero-byte stores to move aside after the ops merge, in Soul's order (setgid on inbox, then rename in place,
|
||||
then relocate): `observations.cc`, `deliveries.cc`, and a third, `/var/lib/gamecult/epiphany/idunn-provider-health-admission.cc`.
|
||||
- **Text matches.** gamecult-ops `scripts/idunn/start-idunn-local.ps1:143` matches the same removed text as Muninn
|
||||
(below). It must change with Idunn's next CultLib bump.
|
||||
- **Consumer follow-up that must travel with the next Muninn CultLib bump:**
|
||||
`Muninn scripts/restart-starfire-muninn.ps1:144` matches `*failed to decode MessagePack*` to trigger
|
||||
`Reset-CorruptMuninnStore`. R1 removes that text, so the reset would stop firing silently. The script must match
|
||||
the typed `CultCacheStoreUnreadable` kind, or the new wording, in the same change.
|
||||
- C#'s truncated-store exception (`EndOfStreamException`) is an `IOException`, the same I/O-versus-undecodable
|
||||
overlap. It goes with R3's C# work.
|
||||
|
||||
Status: cut map, Imagination pass 1 (Opus), 2026-09-30. Nothing has landed. This map owns the means for repairing
|
||||
and recovering `/var/lib/gamecult/odin/topology.cc`. It follows `docs/write-pattern-cut.md` (Cuts 1-3) and starts
|
||||
from that campaign's landed head.
|
||||
|
||||
Anchors:
|
||||
- Odin `hands/odin-write` at `cda2087` (Cuts 1-2 plus two test commits). Q3 B (peer puts fsynced before ACK) is
|
||||
being finished on that branch now. **Re-anchor every Odin `file:line` below on the head that lands; the line
|
||||
numbers here are `cda2087`.**
|
||||
- CultLib `6d0cc963`. `packages/cultcache-rs` is byte-identical to Odin's pin `3bf1c0ce`, so the Rust line numbers
|
||||
hold at both.
|
||||
- Idunn `a96ad9d`, Heimdall `e8e2832`, gamecult-ops `runbooks/gamecult-state-backup.md` as of 2026-09-30.
|
||||
|
||||
Operator, 2026-09-30, on the three options Self offered for an undecodable foreign write (degraded but loud, end
|
||||
Odin, unchanged): **"None of these options involve repair and recovery, which is what ultimately needs to happen."**
|
||||
|
||||
Open: Q-R1 to Q-R6 (section 8). D1 (section 6, O1) is a Self default the operator may overrule.
|
||||
|
||||
## Short answer
|
||||
|
||||
Odin already holds the one thing a repair needs: its in-memory working set, which is authoritative for everything
|
||||
it owns while it holds the write lease. Today it never uses it for repair. It ends on a decodable foreign write, and
|
||||
it silently stops persisting on an undecodable one. At activation it has no working set and no second copy, so it
|
||||
crash-loops.
|
||||
|
||||
The repair has one rule: **a store file that is not what Odin last wrote is set aside, never deleted and never
|
||||
merged, and Odin writes its working set over it under the lease.** At activation Odin has no working set, so it
|
||||
falls back to a previous generation kept on disk, and then to an empty store that providers refill. The
|
||||
correlation sequence gets a time floor, so no rollback can recreate the watermark deadlock. Recovery is published as
|
||||
a typed record in Odin's own catalog. The mechanism (typed read failures, a set-aside exchange, a retained previous
|
||||
generation, no staging file left behind by a failed write) belongs to CultLib, because every consumer of the
|
||||
single-file store has the same gap.
|
||||
|
||||
## 1. Body facts (probed)
|
||||
|
||||
### 1.1 Probe: cultcache-rs single-file store under fault
|
||||
|
||||
The probe was a scratch crate at CultLib `3bf1c0ce`, `scratchpad/odin-recovery-probe` (Imagination session), commit
|
||||
`b19eba9`, run through the stopgap on Yggdrasil in slot 2. It ran with `--tmpfs /small:size=64k --tmpfs /ro:ro`,
|
||||
using `trap '' XFSZ; ulimit -f 40` for the file-size case. The log is `scratchpad/odin-recovery-probe.log`.
|
||||
|
||||
| Fault | Result |
|
||||
|---|---|
|
||||
| P1: file truncated to half | `compare_exchange_snapshot` and `pull_all` return `Err`, **not** `Ok(false)`. There is no `io::Error` in the chain; the decode error is flattened to text (`lib.rs:720-725`). The message is wrong: "invalid type: string \"cultcache.store.v1\", expected struct CultCacheEnvelope". `store_header` (`lib.rs:317-341`) deserializes the *whole* array to find the header, so a truncated v1 file has no header and falls through to the legacy decoder. The file is unchanged by the failed exchange |
|
||||
| P2: 12 bytes of garbage appended | `pull_all` returns `Ok`, all 3 records. `compare_exchange_snapshot(expected)` returns **`Ok(true)` and silently drops the tail**. `rmp_serde::from_slice` ignores trailing bytes. C#'s `DeserializeSnapshot` (`CultDocumentMessagePackSerialization.cs:179-215`) never checks for the end either |
|
||||
| P2b: one byte flipped inside a payload | `pull_all` returns `Ok` (payloads are opaque bytes). `compare_exchange_snapshot` returns **`Ok(false)`**. To Odin, bit rot is a *decodable foreign write* |
|
||||
| P3: header `cultcache.store.v4...` | `Err` with a distinct message, "not readable; this runtime reads v1 only". It is distinguishable only by its text |
|
||||
| P4: another handle wrote a valid snapshot | `Ok(false)` |
|
||||
| P5: `hard_link(topology.cc, topology.cc.displaced.<ts>.x)`, then an exchange through the store | The link keeps the old bytes exactly, and the store writes the new file. The staging sweep (`remove_abandoned_staging_files`, `lib.rs:2730-2766`) removes a `<name>.<uuid>.tmp` beside it and leaves the displaced name alone |
|
||||
| read-only filesystem, no store | `pull_all` returns `Ok(0)`. The exchange fails opening the lock: `io::Error` `ReadOnlyFilesystem` (os 30) |
|
||||
| `RLIMIT_FSIZE` 40 KiB, 200 KB write | `Err` `io::Error` `FileTooLarge` (os 27). The old file is intact and still decodes. **A 40,960 B partial staging file is left behind.** The next attempt sweeps it and leaves a new one |
|
||||
| 64 KiB tmpfs, 200 KB write (ENOSPC) | `Err` `StorageFull` (os 28). The old file is intact. **A 61,440 B partial staging file is left behind: all of the filesystem's remaining space.** The same holds on every retry |
|
||||
|
||||
What follows from the probe:
|
||||
- **Decodable is not the same as legitimate.** A flipped payload byte is "decodable and foreign", and appended
|
||||
garbage is "decodable and ours". The decodable/undecodable split cannot be what decides recovery. One rule has to
|
||||
cover both: *not what Odin last wrote*.
|
||||
- **A write that fails for lack of space holds the space it failed on.** `/var/lib/gamecult` is shared by every
|
||||
service on Yggdrasil. Odin retries once per second (and, under Q3 B, on every put), so it re-takes whatever
|
||||
another service frees. C# deletes its temp file in a `finally` (`CultCache.cs:3496-3531`); Rust does not
|
||||
(`write_all_unlocked`, `lib.rs:741-768`). This is a Rust parity defect.
|
||||
- **The atomic write keeps no previous generation.** `write_all_unlocked` stages, fsyncs, renames over the file and
|
||||
fsyncs the directory. After the rename, the old inode is gone. The brief's "last good checkpoint kept by the
|
||||
atomic write" does not exist on disk. The only last-good copy is Odin's in-memory `flushed` snapshot
|
||||
(`lib.rs:506-510`), and it dies with the process.
|
||||
|
||||
### 1.2 Odin today (`cda2087`)
|
||||
|
||||
| Case | Path | Outcome |
|
||||
|---|---|---|
|
||||
| Decodable foreign write while serving | `MemoryOdinTopologyStore::flush` `lib.rs:613-626`: `compare_exchange_snapshot(flushed, current)` returns `false`, so `ForeignStoreWrite`. `survive` `main.rs:879-895` returns it, and Odin ends | Idunn restarts Odin (`control_plane.rs:68-71`: 6 restarts per hour, the wait doubling from 5 s). The new Odin **loads the foreign file as truth**. Odin's own unflushed changes, and every record of Odin's that the foreign file lacks, are lost without a trace. A flipped payload byte (P2b) takes this path |
|
||||
| Undecodable foreign write while serving | the same exchange returns `Err` (decode). `survive` logs it and retries every second, forever. Nothing is persisted again | **Idunn's crossing breaks too.** `receive` (`Idunn drivers.rs:5005-5030`) reads the same file and propagates the error to `admit_latest_topology` (`control_plane.rs:7903-7907`) and `refresh_admitted_topology` (`:6040`). Every Odin-correlated deployment and refresh fails until the file is readable, which is never |
|
||||
| Corrupt file at activation | `try_activate` `main.rs:246-297` calls `MemoryOdinTopologyStore::load` `lib.rs:519-538`, which calls `pull_all()?`. `main.rs:762` `while !try_activate()?` exits the process | Crash loop until Idunn's budget is spent. Then Idunn reports once, to its log only (`control_plane.rs:5709-5723`, `report_once` `:4599-4610` is `eprintln`), and stops. The Verse rendezvous is down |
|
||||
| Disk full or read-only | the exchange returns `Err` (io). `survive` logs and retries every second | Odin serves from memory. Each attempt leaves a partial staging file (1.1). A stop or deploy in this state loses everything unflushed. Nothing typed says so |
|
||||
|
||||
Detection latency for a foreign write: `flush` writes only when dirty (`main.rs:307-314`). Odin's own heartbeat
|
||||
(`HEARTBEAT_INTERVAL` 5 s, `main.rs:69`) dirties the set, so a foreign write is met at the latest one heartbeat plus
|
||||
one flush interval (about 6 s) after it happens. No polling read is needed, and Cut 1's rule (no reads while
|
||||
serving) stands.
|
||||
|
||||
### 1.3 Other sources of truth, and what they can recover
|
||||
|
||||
| Source | Holds | Freshness | Recovers |
|
||||
|---|---|---|---|
|
||||
| Odin's working set (W) | everything, including unflushed changes | live | everything, but only while this process lives and holds the lease |
|
||||
| `flushed` (in memory) | the last snapshot Odin wrote | at most one interval old | nothing more than W; it is the comparison base, not a store |
|
||||
| previous generation on disk (new, O3 and R3) | the snapshot before the last one this store's owner verified and replaced | one write old | everything except the last write's delta, including publish-once documents, presence history and the watermark |
|
||||
| nightly backup (`gamecult-state-backup.md`) | `/var/lib/gamecult/odin` whole, shared-lock copy | up to 24 h old, 3 days on Yggdrasil, 14 days plus 8 weekly on Raven | publish-once documents. **Never automatically**: "It never becomes live authority, and nothing restores it automatically"; "restore a store only after stopping its owner" |
|
||||
| providers republishing | presences (2.7-12 s), Muninn documents (2 s), ads (about 30 s); Heimdall HEAD republishes `heimdall.command_boundary` and its three siblings every 60 s (`Heimdall src/index.ts:19,35-39`, gated on its write lease) | seconds to a minute | every document whose publisher republishes |
|
||||
| Idunn's projection plus presences | correlations | one refresh pass (250 ms) after the presences return | correlations. They are a projection and never need restoring |
|
||||
| Idunn's `odin_publisher_sequence_cursor` (`control_plane.rs:1456,1537,6081`) | the highest correlation sequence Idunn admitted, per target | live | nothing directly (Idunn owns it), but it is why the watermark must never roll back |
|
||||
|
||||
Premise correction: `heimdall.command_boundary` is not publish-once at Heimdall HEAD. A `stored_at` of 09-23
|
||||
proves only that the publisher's own `stored_at` has not changed, because Odin stores the publisher's
|
||||
`stored_at` (`persist_generic_document`, `main.rs:1330-1347`). Whether the deployed Heimdall re-puts is
|
||||
unverified; O3's outstanding-republish list measures it.
|
||||
|
||||
**The watermark is the one record whose loss is not self-healing.** Idunn refuses any correlation sequence at or
|
||||
below its cursor (`control_plane.rs:1763,1785`). An Odin that starts from an empty store, from the previous
|
||||
generation, or from a backup restarts a target's count below that cursor. That is the deadlock documented at
|
||||
`lib.rs:33-49`: Idunn refuses every correlation until the count climbs past the old mark, and the refusal
|
||||
withdraws the correlation before it can climb. Every recovery source except W has this defect. O1 removes it.
|
||||
|
||||
Other facts the design leans on:
|
||||
- **Idunn fences before it grants.** `CultCacheWriteLeaseDriver` (`Idunn drivers.rs:4050-4205`): "revocation
|
||||
follows an exact incumbent stop", and grant is a nonblocking exchange from empty. A second *Odin* cannot hold
|
||||
the lease while the first is still writing, so a foreign writer seen by a lease-holding Odin is not an Odin.
|
||||
- **Idunn has a lifecycle brake** (`idunn-provision lifecycle-brake-engage|release|status`,
|
||||
`provisioning.rs:95-97`; `odin-lifecycle-brake.cc` per `runbooks/odin-yggdrasil.md:29-30`). An engaged brake
|
||||
denies continuity restarts (`control_plane.rs:5771-5774`, `:6219`). This is the operator's hold for a manual
|
||||
restore.
|
||||
- **A non-`active` Odin presence fans out.** `reconcile` sets `ready` only when the presence state is `active`
|
||||
(`lib.rs:946`), and `dependency_evidence` (`lib.rs:1184ff`) makes every managed dependent of Odin not Ready.
|
||||
The presence schema allows `degraded` (`cultnet-rs runtime_authority_contracts.rs:193`), but using it would
|
||||
be a readiness statement about every dependent, not a store report.
|
||||
- **The catalog serves only peer types and presences** (`public_document`, `main.rs:590-640`). No `odin.*` record
|
||||
is served today, and neither Odin nor Idunn publishes an Eve surface. F3 in the write map (the unowned
|
||||
`surface:gamecult.network.status`) is where an Eve lowering belongs.
|
||||
|
||||
## 2. Ends
|
||||
|
||||
1. No single fault, whether a foreign write of any kind, a corrupt file, or a failed write, ends Odin or leaves it
|
||||
silently unpersisted. Only losing the write lease ends it.
|
||||
2. A store file that is not what Odin last wrote is preserved byte for byte beside the store and never deleted,
|
||||
merged or adopted by any automatic path. The owner (Odin under the lease) writes its own state over it.
|
||||
3. At activation a corrupt store is recovered from the best source on the host (previous generation, then empty),
|
||||
and never from the backup automatically.
|
||||
4. No recovery can make a correlation sequence repeat or fall below one Idunn has admitted.
|
||||
5. Every repair, and every stretch in which Odin is serving without persisting, is typed state that can be
|
||||
queried through Odin's catalog while it is happening, including when the disk refuses writes.
|
||||
|
||||
Invariants carried over: one writer (the lease, checked immediately before every write); Idunn's lockless
|
||||
crossing reads a whole `.cc` snapshot at `topology.cc`; the path never becomes absent (Idunn reads an absent file
|
||||
as an empty snapshot, which withdraws every correlation); no reads of the store while serving; one bad record
|
||||
never stops Odin.
|
||||
|
||||
## 3. Identity, lifecycle, authority
|
||||
|
||||
| Persistent kind | Named by | Over time | Decided by |
|
||||
|---|---|---|---|
|
||||
| store file `topology.cc` | the state root plus `TOPOLOGY_SLOT` | replaced whole by the owner's exchange; never absent once created | Odin under the lease |
|
||||
| displaced file (new) | `topology.cc.displaced.<UTC yyyymmddThhmmssZ>.<uuid>`, same directory (it must not end in `.tmp`) | created by a set-aside exchange as a hard link to the replaced inode; never modified; never deleted by any program. Travels to Raven with the nightly backup | created by Odin through CultLib; **disposed of only by the operator** |
|
||||
| previous generation (new, Q-R2) | `topology.cc.previous` | replaced on every *verified* exchange (current == expected) by a hard link to the outgoing inode; **not** rotated by a set-aside exchange, so it always holds a generation that some writer verified before replacing | CultLib mechanism; Odin opts in |
|
||||
| store condition record (new) | type `odin.store_condition`, schema `odin.store_condition.v1`, key `topology` | one record held in W; rewritten when the condition changes; flushed with the store; served by the catalog | Odin |
|
||||
| correlation sequence floor (new rule, not a record) | per target, in the existing watermark | next = max(mark + 1, prior + 1, now in unix ms) | Odin |
|
||||
| backup snapshot | `/var/backups/gamecult-state/<date>/` and Raven | nightly; never live authority | the operator restores it, with the lifecycle brake engaged (runbook) |
|
||||
|
||||
## 4. Authority map (the whole campaign)
|
||||
|
||||
- **Owner:** Odin, while it holds the current process-write lease, decides what `topology.cc` holds, when it is
|
||||
set aside, and what activation recovers from. CultLib owns the mechanism (read classification, set-aside
|
||||
exchange, previous generation, staging cleanup) and no policy. Idunn owns whether Odin runs (continuity budget,
|
||||
lifecycle brake, deploys). The operator owns restores from backup and the disposal of displaced files.
|
||||
- **Inputs:** W; `flushed`; the lease (read immediately before every write); at activation, `topology.cc` and then
|
||||
`topology.cc.previous`; the clock (the sequence floor).
|
||||
- **Outputs:** one atomic `topology.cc`; displaced files; `topology.cc.previous`; the `odin.store_condition` record
|
||||
in the catalog.
|
||||
- **Derived state:** `topology.cc` is a checkpoint and Idunn's crossing, never an input while serving. The
|
||||
displaced files are evidence only: nothing reads them automatically. The previous generation is read only by
|
||||
activation recovery. The condition record is a report and decides nothing.
|
||||
- `ForeignStoreWrite` **is no longer an owner of Odin's life**. A foreign write becomes a set-aside event.
|
||||
- **Forbidden writers and deciders:**
|
||||
- Any path that ends Odin on a store condition. `survive`'s `ForeignStoreWrite` arm (`main.rs:884`) is
|
||||
deleted; `WriteLeaseLost` stays, and so does the temporary `PresenceAuthorityRefused` rule.
|
||||
- Any automatic delete of a displaced or previous file.
|
||||
- Any merge of records from a foreign or displaced file into W.
|
||||
- Any automatic restore from the backup.
|
||||
- Idunn writing or repairing `topology.cc`.
|
||||
- Any second writer of the file inside Odin: activation never writes. It prepares W and `flushed`, and the one
|
||||
`flush` makes the first write through the same set-aside exchange.
|
||||
- **Shared paths:**
|
||||
- The interval flush, the Q3 B per-put flush, the stop flush and the activation-recovery first flush all go
|
||||
through one primitive, `replace_snapshot_setting_aside`.
|
||||
- Every path that reserves a correlation sequence goes through the one floored reservation.
|
||||
- **Deletion line:** `ForeignStoreWrite` (`lib.rs:630-641`) and its `survive` arm are deleted before the set-aside
|
||||
exchange is wired in. The `Err` escape in `load` (`lib.rs:521`, `pull_all()?`) is replaced by the recovery
|
||||
ladder, not wrapped.
|
||||
|
||||
## 5. Recovery by case
|
||||
|
||||
| Case | Detected by | Action | Recovered from | Loss bound | Visible as |
|
||||
|---|---|---|---|---|---|
|
||||
| **A. Decodable foreign write while serving** (a hand edit, a tool, a restore made against the runbook, or bit rot in a payload per P2b) | the next flush: current != `flushed` | the lease is checked; the file is set aside (hard link); W is written; Odin keeps serving | W | none of Odin's own records. The foreign file's content is preserved, not adopted | condition event `displaced`, reason `mismatch`, with path, sha256, bytes and time |
|
||||
| **B. Undecodable foreign write while serving** (truncation, garbage, a foreign format, and after R2 trailing bytes too) | the next flush: current does not decode | the same | W | none. Idunn's crossing is unreadable for at most one heartbeat plus one flush (about 6 s), instead of forever | the same, reason `undecodable` |
|
||||
| **A/B repeated** (a live foreign writer) | a third set-aside inside 10 minutes in one activation | Odin stops writing and keeps serving; puts are not ACKed (Q3 B: ACK means durable); no new displaced files (Q-R4) | W stays in memory | nothing while the process lives | condition `contested`, since when, with the displaced files |
|
||||
| **C1. Undecodable store at activation** | `load` gets `Undecodable` | W is built from `topology.cc.previous` if it decodes; `flushed` is set to "unreadable", so the first flush sets the corrupt file aside and writes W | the previous generation | the last write's delta: one interval of Odin bookkeeping, or one fsynced put. **A put ACKed as durable can be lost here; say so in Q3 B's record** | event `recovered-previous`, plus an outstanding-republish list: peer records in the previous generation not yet re-put by their publisher, cleared as they arrive |
|
||||
| **C2. Nothing decodable at activation** | `.previous` absent or undecodable too | start empty (Q-R3); the first flush sets the corrupt file aside | providers republish; correlations re-derive in one pass; the sequence floor holds | every publish-once document; presence history (F4 makes this harmless) | event `started-empty`; outstanding list is `unknown`. The operator's path is the backup, with the lifecycle brake engaged |
|
||||
| **C3. Unsupported format at activation** (a newer runtime wrote v4, or a downgrade) | `load` gets `UnsupportedFormat` | refuse to activate. The file is untouched and nothing is set aside | the deploy or rollback that caused it; Idunn owns that | none | Idunn's continuity budget and the Odin journal. This is a deployment fault, not a store fault |
|
||||
| **C4. I/O error reading the store at activation** (EIO, EACCES after a restore with the wrong owner, EISDIR) | `load` gets an `io::Error` | refuse to activate. Nothing is set aside: **only bytes that were read are ever judged** | the operator fixes the host or the mode | none | as C3 |
|
||||
| **D. Write fails while serving** (ENOSPC, EDQUOT, EFBIG, EROFS, EIO) | the exchange returns an `io::Error` while staging or renaming | nothing is set aside; the staging file is removed (R1); W is kept; retry at the flush interval; puts are not ACKed; Odin keeps serving | W, when the fault clears | nothing, unless Odin is stopped or redeployed before the fault clears (follow-up I-F1) | condition `unpersisted` since T, with the last error kind (`no-space`, `read-only`, `io`) and the dirty-record count, served from memory |
|
||||
| **D'. Stop while unpersisted** | the stop flush fails | log it and stop, as today (`main.rs:794-799`) | the next activation loads the last good file | the unflushed delta | the final journal line only. I-F1 is the owner fix |
|
||||
|
||||
Rollback safety, for C1, C2 and an operator restore: every source except W carries a watermark below Idunn's
|
||||
cursor. The floor (O1) makes the next sequence exceed every sequence published before, whatever the store holds.
|
||||
|
||||
## 6. Cuts
|
||||
|
||||
Order: the Rust substrate gaps first (R1), then the watermark floor (O1, independent and small), the set-aside
|
||||
primitive (R3), Odin's serving repair (O2), activation recovery (O3), parity hardening (R2, R3b), and finally the
|
||||
runbook and drill (X1). Each cut is its own branch and its own Soul pass. Odin verification:
|
||||
`cargo test --locked -p odin-daemon` from the Odin root. CultLib Rust: `cd packages/cultcache-rs && cargo test
|
||||
--locked`. All of it runs on Yggdrasil through the stopgap. **A permission-based test is unproven there (root).**
|
||||
The injections below avoid permissions: truncation, a directory at the store path (EISDIR), tmpfs size and
|
||||
read-only options, and `RLIMIT_FSIZE`.
|
||||
|
||||
### R1. cultcache-rs: typed read failures, no staging file left behind, a header that survives truncation (CultLib)
|
||||
|
||||
- **Repo/branch:** CultLib `hands/cultcache-rs-store-faults` from `main`. Depends on nothing.
|
||||
- **Deletes first:** the flattening `map_err(|error| anyhow!(...))` in `read_all_unlocked` (`lib.rs:720-725`);
|
||||
`store_header`'s whole-array visitor (`lib.rs:317-341`).
|
||||
- **Adds:**
|
||||
- `pub struct CultCacheStoreUnreadable { path, kind: Undecodable | UnsupportedFormat }`, carried in the error
|
||||
chain of every read of a single-file store whose bytes were read and not accepted, so consumers can
|
||||
`downcast_ref`. An `io::Error` stays an `io::Error`, and the two never overlap.
|
||||
- `store_header` reads only the array header and the first string, reusing `read_array_header` and
|
||||
`read_string` (`lib.rs:2661-2700`). A truncated v1 file then reports as an undecodable v1 file, not as legacy.
|
||||
- `write_all_unlocked` removes its own staging file on every failure after creating it. This is C# parity with
|
||||
`CultCache.cs:3525-3530`.
|
||||
- **Rules that must die under their own mutation:**
|
||||
- a truncated store is `Undecodable`, and a v4 header is `UnsupportedFormat`;
|
||||
- an `io::Error` read failure is never classified `Unreadable`;
|
||||
- after a failed write, the directory holds no `<name>.*.tmp`.
|
||||
- **Verification:**
|
||||
- Tests describe behaviour. The staging-cleanup test forces the failure without permissions. One option is a
|
||||
child process under `RLIMIT_FSIZE` with SIGXFSZ ignored (the probe's method); another is a path whose parent
|
||||
fills a size-capped tmpfs, if the test runner can mount one. Hands picks one and names it.
|
||||
- `cargo mutants --in-diff`.
|
||||
- **Estimate:** about -30 / +70.
|
||||
|
||||
### O1. The correlation sequence has a time floor (Odin) — default D1
|
||||
|
||||
- **Repo/branch:** Odin `hands/odin-sequence-floor` from the landed `hands/odin-write` head. Depends on nothing
|
||||
else.
|
||||
- **Change:** `reserve_publisher_sequence` (`lib.rs:702-723`) returns max(mark + 1, prior + 1, now in unix ms),
|
||||
and records it as the mark. The doc comment at `lib.rs:33-49` gains the rule: "A mark read from any older copy
|
||||
of the store is a floor, never the next value; the clock makes every rollback safe."
|
||||
- **Why a floor and not a read of Idunn's cursor:** it needs no new crossing. It holds for every recovery source,
|
||||
including the backup, and the host clock is already trusted for presence freshness.
|
||||
- **Assumption:** the clock does not step back across a rollback by more than the rollback's age. Soul checks that
|
||||
Idunn accepts a jump: its rule is "strictly above the cursor" (`control_plane.rs:1763,1785`). No test may pin a
|
||||
gap limit.
|
||||
- **Verification:**
|
||||
- Existing tests pinning counts (`withdrawing_a_correlation_does_not_reset_its_publisher_sequence`
|
||||
`lib.rs:1996-2014`) are restated as order properties.
|
||||
- New test: a store rolled back to an earlier snapshot, then one refresh, yields a correlation sequence greater
|
||||
than every sequence published before the rollback.
|
||||
- New test: two reservations inside one millisecond are strictly increasing.
|
||||
- The clock must be injectable at the reservation. At least one mutant must be a function of the time (an offset
|
||||
or a scale), with probe values where it would show.
|
||||
- **Estimate:** about -5 / +25.
|
||||
|
||||
### R3. The set-aside exchange and the previous generation (CultLib, Rust and C#; TS and Python in R3b)
|
||||
|
||||
- **Repo/branch:** CultLib `hands/cultcache-set-aside` from R1. Q-R2 decides the previous-generation half.
|
||||
- **Adds, on `SingleFileMessagePackBackingStore`:**
|
||||
- `replace_snapshot_setting_aside(expected, replacements) -> Result<SnapshotReplacement>`, with
|
||||
`SnapshotReplacement::{Exchanged, SetAside { displaced: PathBuf, sha256, bytes, reason: Mismatch |
|
||||
Undecodable }}`. It makes one exclusive lock hold, in this order:
|
||||
1. stage the replacement (write and fsync);
|
||||
2. read current;
|
||||
3. if current equals expected, rename the staged file over (rotating `.previous` first when enabled);
|
||||
4. if the read was an `io::Error`, remove the staging file, return the error and change nothing;
|
||||
5. otherwise hard-link current to the displaced name, fsync the directory, rename the staged file over, and
|
||||
fsync the directory again.
|
||||
|
||||
Staging comes first, so a full disk sets nothing aside. The path is never absent: a link, never a rename. A
|
||||
set-aside never rotates `.previous`.
|
||||
- Opt-in previous generation: a constructor option (`retaining_previous_generation`, or Hands' spelling of it)
|
||||
plus `pull_previous_generation() -> Result<Vec<CultCacheEnvelope>>`, read under the shared lock and classified
|
||||
like any read. Off by default, because existing consumers must not grow files they did not ask for.
|
||||
- Filesystems without hard links: return an explicit error naming the filesystem. Do not fall back to a
|
||||
copy-then-rename, which would make the path briefly hold new content while the evidence is still being
|
||||
written. Windows NTFS links work; the Windows path is **not yet reached** by the Yggdrasil suite and is
|
||||
recorded as such.
|
||||
- C# reference: the same operation on `SingleFileBackingStore` (`CultCache.cs:3268ff`), with the same names
|
||||
across runtimes.
|
||||
- **Verification (Rust and C#):**
|
||||
- a mismatch preserves the foreign bytes exactly and writes the replacement;
|
||||
- an undecodable current is preserved and replaced;
|
||||
- an I/O read failure (a directory at the path) changes nothing and leaves no staging file;
|
||||
- a staging failure (size cap) changes nothing and sets nothing aside;
|
||||
- the path is readable at every instant: a reader thread loops `pull_all_read_only_snapshot` through 1,000
|
||||
exchanges and never sees an absent file or an empty snapshot;
|
||||
- `.previous` rotates only on a verified exchange;
|
||||
- two set-asides in the same second get distinct names;
|
||||
- mutation tools: `cargo mutants --in-diff`, and Stryker.NET on the C# diff.
|
||||
- **R3b (parity, not blocking Odin):** cultcache-ts `single-file-messagepack-backing-store.ts:34` and cultcache-py
|
||||
`stores.py:83` gain the same operation and option, with a shared fixture test.
|
||||
- **Estimate:** Rust about +140, C# about +120, R3b about +200.
|
||||
|
||||
### O2. A foreign store file while serving is set aside, not fatal (Odin)
|
||||
|
||||
- **Repo/branch:** Odin `hands/odin-store-repair` from O1, with a CultLib pin bump to R3. Needs Q-R1 and Q-R4.
|
||||
- **Deletes first:** `ForeignStoreWrite` (`lib.rs:630-641`) and its arm in `survive` (`main.rs:884`). The test
|
||||
`a_flush_writes_only_changes_and_never_over_a_foreign_file` (`lib.rs:2055-2090`) and the `survive` tests at
|
||||
`main.rs:2842-2844` and `:3110` pin the old rule: restate them, do not delete them.
|
||||
- **Changes:**
|
||||
- `MemoryOdinTopologyStore::flush` (`lib.rs:613-626`) calls `replace_snapshot_setting_aside(flushed, current)`.
|
||||
On `SetAside` it records a `displaced` event and counts it toward the contested bound. On an I/O failure it
|
||||
records `unpersisted` and returns the error, which `survive` logs and retries as today.
|
||||
- `RuntimeState::flush` (`main.rs:307-314`) keeps its lease check immediately before the write.
|
||||
- The Q3 B per-put path uses this same flush. Hands re-anchors on the Q3 B head.
|
||||
- **Adds:**
|
||||
- The `odin.store_condition` record (schema in `odin-core` `documents.rs`), held in W. It carries:
|
||||
- `generation` and `written_by`, updated inside the flush before the snapshot is taken;
|
||||
- `unpersisted_since` and `last_write_error`;
|
||||
- `contested_since`;
|
||||
- a bounded list of the last 16 recovery events: kind, time, displaced path, sha256 and bytes, source, records
|
||||
loaded, and the outstanding list (bounded).
|
||||
- One arm in `public_document` (`main.rs:590-608`) that serves exactly this type. Providers still cannot write
|
||||
it: `is_peer_document_type` is unchanged.
|
||||
- **Rules that must die under their own mutation:**
|
||||
- a flush never leaves the store dirty (the condition record is written inside the flush, not after it);
|
||||
- a set-aside is preceded by a lease check;
|
||||
- the third set-aside inside the window stops writing, and the second does not;
|
||||
- after `contested`, no file is written or created;
|
||||
- on an I/O failure, nothing is set aside;
|
||||
- the condition is served while writes fail.
|
||||
- **Authority map:** as in section 4. The deletion line is the `ForeignStoreWrite` type and arm.
|
||||
- **Verification (fault injection, each at the file layer, not a log line):**
|
||||
- T-A: activate, serve, write a different valid snapshot behind Odin. After the next pass the file decodes to
|
||||
W, the displaced file's bytes equal the foreign bytes, `serving_pass` returned `Ok`, the catalog is unchanged,
|
||||
and the served condition names the displaced file and its sha256.
|
||||
- T-A2 (P2b): flip one payload byte behind Odin; the same outcome, reason `mismatch`.
|
||||
- T-B: truncate the file behind Odin; the same outcome, reason `undecodable`, and
|
||||
`pull_all_read_only_snapshot` (Idunn's crossing) decodes the file after one pass.
|
||||
- T-B-latency: with no puts, the set-aside happens within one heartbeat plus one flush interval.
|
||||
- T-contested: three foreign writes inside the window make exactly two set-asides and a third `contested`
|
||||
report. After that the file and directory are untouched by further passes, and Odin still serves.
|
||||
- T-D: replace the store path with a directory (EISDIR, which works as root). Nothing is set aside, no staging
|
||||
file remains, the condition is `unpersisted` and is served, and a put is not ACKed. Remove the directory, and
|
||||
the next pass writes W, clears `unpersisted` and ACKs.
|
||||
- T-lease: the lease withdrawn before a flush that would set aside means no link, no write, and Odin ends
|
||||
(`WriteLeaseLost`).
|
||||
- Negative: `rg -n "ForeignStoreWrite" crates` matches nothing; `rg -n "remove_file|remove_dir" crates/odin-daemon/src`
|
||||
matches nothing new outside tests.
|
||||
- `cargo mutants --in-diff`.
|
||||
- **Estimate:** about -40 / +160.
|
||||
|
||||
### O3. Activation recovers a corrupt store (Odin)
|
||||
|
||||
- **Repo/branch:** Odin `hands/odin-store-activation` from O2. Needs Q-R2 and Q-R3.
|
||||
- **Change:** `MemoryOdinTopologyStore::load` (`lib.rs:519-538`) becomes the recovery ladder:
|
||||
|
||||
| `load` result | Outcome |
|
||||
|---|---|
|
||||
| `Ok` | as today |
|
||||
| absent | empty, as today |
|
||||
| `Unreadable::Undecodable` | try `pull_previous_generation()`, then empty (Q-R3); `flushed` becomes a sentinel meaning "the file is not a snapshot Odin can compare", so the first flush's exchange sets it aside |
|
||||
| `Unreadable::UnsupportedFormat` | return the error: refuse |
|
||||
| `io::Error` | return the error: refuse |
|
||||
|
||||
Activation itself never writes. The first `serving_pass` flush (`main.rs:833-842`, due at once) writes W. That
|
||||
set-aside is recorded under the activation event and is not counted toward the contested bound.
|
||||
- **Adds:** the outstanding-republish list. It holds the peer-type records present in the recovered previous
|
||||
generation, each cleared when its publisher re-puts it. After 10 minutes the remainder is marked "not
|
||||
republished", which is the live answer to "who is publish-once". For `started-empty` the list is `unknown`.
|
||||
- **Rules that must die under their own mutation:**
|
||||
- Unsupported-format and I/O failures never set aside, never fall back, and never start.
|
||||
- A recovered activation writes before it serves its first catalog snapshot, or it serves the recovered set and
|
||||
marks the condition before the first write. Hands picks one and states it.
|
||||
- **Verification:**
|
||||
- T-C1: a truncated `topology.cc` and a valid `.previous` activate with the previous content. The first pass
|
||||
writes it and sets the corrupt file aside byte-exact, and the condition is `recovered-previous` with its
|
||||
outstanding list. Re-putting one listed document clears it.
|
||||
- T-C2: both undecodable (Q-R3 A) start empty. Both files are preserved, the condition is `started-empty`, and
|
||||
the next correlation sequence is above the floor.
|
||||
- T-C3: a v4 header refuses to activate. The file is byte-identical afterwards, and there is no displaced file
|
||||
and no `.previous` change.
|
||||
- T-C4: a directory at the store path refuses, as C3.
|
||||
- T-restart: a recovered Odin stopped and restarted loads its own first write normally, with no second
|
||||
set-aside.
|
||||
- `cargo mutants --in-diff`.
|
||||
- **Estimate:** about -10 / +110.
|
||||
|
||||
### R2. Single-file decoders reject trailing bytes, in every runtime (CultLib)
|
||||
|
||||
- **Repo/branch:** CultLib `hands/cultcache-trailing-bytes`. Independent of Odin; land it before O3 ships so that
|
||||
an appended file is caught as undecodable, not overwritten silently (P2).
|
||||
- **Change:** C# `DeserializeSnapshot` (`CultDocumentMessagePackSerialization.cs:179-215`) checks `reader.End`.
|
||||
Rust `decode_store_snapshot` (`lib.rs:2583ff`) and the legacy path check that the slice is consumed. TS and
|
||||
Python do the same. **One release**: a runtime that accepts what another rejects is a parity split.
|
||||
- **Verification:** one shared fixture (a valid store plus one byte) is rejected as undecodable by all four
|
||||
runtimes, and the valid store is accepted by all four.
|
||||
- **Estimate:** about +40 plus tests across four runtimes.
|
||||
|
||||
### X1. Runbook and recovery drill (gamecult-ops, then live)
|
||||
|
||||
- **Repo:** gamecult-ops `runbooks/odin-yggdrasil.md` gains a "Store recovery" section:
|
||||
- how to read `odin.store_condition` from the catalog;
|
||||
- what displaced and previous files mean, and that disposing of them is the operator's decision;
|
||||
- the manual restore: engage Odin's lifecycle brake, stop the `idunn-odin-*` unit, set the live file aside with
|
||||
`ln` (never `rm`), extract the snapshot's `topology.cc` with its owner, mode and `.lock`, release the brake,
|
||||
and read the condition. The sequence floor makes an old watermark safe.
|
||||
- **Drill D-1 (off-live, after O3; needs the Q2-style approval, Q-R6):** extract the newest Yggdrasil snapshot's
|
||||
`topology.cc` into a container on Yggdrasil. Activate a test-harness Odin on it through the existing fixture
|
||||
authority. Then truncate the copy and activate again with the extracted file as `.previous`. Report the records
|
||||
per type, the outstanding list and the sequence floor. Delete the copy afterwards.
|
||||
- **Drill D-2 (live, operator-scheduled, Q-R6), after O3 deploys:**
|
||||
1. Record the baseline: catalog types, Idunn correlated targets, route timeouts.
|
||||
2. Engage the lifecycle brake and stop Odin.
|
||||
3. `ln topology.cc /var/tmp/odin-drill-keep.cc` (the evidence of the evidence), then truncate `topology.cc` in
|
||||
place to half.
|
||||
4. Release the brake and let Idunn restart Odin.
|
||||
5. Pass when all of these hold:
|
||||
- Odin is serving within one restart;
|
||||
- the condition is `recovered-previous`, with a displaced file whose sha256 equals the truncated file's;
|
||||
- every Odin-correlated target is Ready again within 60 s;
|
||||
- there are 0 route-challenge timeouts in the next hour;
|
||||
- the catalog's type set equals the baseline after 10 minutes, or the outstanding list names every
|
||||
difference.
|
||||
6. Then run D-2b, a foreign write while serving: `ln` the live file aside, then truncate it. It passes when the
|
||||
condition shows `displaced` within about 6 s and Idunn's correlation reads never fail for longer than that
|
||||
(the journal).
|
||||
|
||||
## 7. Subtraction ledger (estimate)
|
||||
|
||||
| Cut | Removed | Added | Formats and targets |
|
||||
|---|---|---|---|
|
||||
| R1 | about 30 | about 70 | none |
|
||||
| O1 | about 5 | about 25 | none |
|
||||
| R3 | 0 | about 260 (Rust 140, C# 120); R3b about 200 | two sibling-file conventions (`.displaced.*`, `.previous`); no format change |
|
||||
| O2 | about 40 | about 160 | one Odin schema, `odin.store_condition.v1` |
|
||||
| O3 | about 10 | about 110 | none |
|
||||
| R2 | 0 | about 40 plus tests | none (it tightens the decoders) |
|
||||
|
||||
The campaign is net additive, at about +570 without R3b. It buys ends 1-5, which the operator asked for, and
|
||||
removes one fatal path and one silent path. Every addition in CultLib is a capability a general consumer of the
|
||||
single-file store would expect, and two of them (staging cleanup, and the header that survives truncation) are
|
||||
defects.
|
||||
|
||||
## 8. Operator questions
|
||||
|
||||
- **Q-R1. A store file changed underneath a running Odin (decodable or not).**
|
||||
- Context: Idunn fences the old Odin before granting the lease, so the other writer is never an Odin. It is a
|
||||
tool, a hand edit, a restore made against the runbook, or disk corruption; P2b shows a flipped byte looks
|
||||
exactly like a foreign write. Today a decodable change ends Odin, and the restart adopts the foreign file
|
||||
silently. An undecodable change stops persistence forever and breaks Idunn's crossing.
|
||||
- A: set the file aside (kept byte for byte, never deleted), write Odin's working set over it, keep serving,
|
||||
and report it as typed state.
|
||||
- B: set it aside, write the working set, then end Odin so a fresh process starts from the rewritten file.
|
||||
- **Recommended: A.** B's restart protects nothing that the lease check before the write does not already
|
||||
protect, and costs a route outage.
|
||||
- **Q-R2. Keep one previous generation on disk (`topology.cc.previous`)?**
|
||||
- Context: without it, a store that is corrupt at activation has no on-host source except "empty", and the
|
||||
backup is up to 24 h old and needs the operator. It costs one hard link per write (a directory update, no data
|
||||
written) and one more file of the store's size. It is a CultLib option, off by default for other consumers.
|
||||
- A: yes. B: no; activation recovery goes straight to empty.
|
||||
- **Recommended: A.** It is the only source that recovers publish-once documents, presence history and
|
||||
correlations without the operator. Its loss bound is one write.
|
||||
- **Q-R3. Activation finds nothing decodable: no usable `topology.cc`, no usable previous generation.**
|
||||
- Context: presences and most catalog documents return within seconds to a minute, and correlations re-derive
|
||||
in one refresh pass. Publish-once documents (if any exist, see 1.3) stay missing until their publisher
|
||||
re-puts them, or until the operator restores from the backup.
|
||||
- A: start empty automatically, set the corrupt file aside, and report `started-empty`.
|
||||
- B: refuse to start, so Odin crash-loops until the operator restores. Every service's rendezvous is down
|
||||
meanwhile.
|
||||
- **Recommended: A.** A down Odin loses everything the Verse needs; an empty one loses only what nobody
|
||||
republishes, and the restore path stays open.
|
||||
- **Q-R4. A foreign writer keeps writing (a third set-aside within 10 minutes).**
|
||||
- Context: repeated repair against a live writer makes a displaced file every second.
|
||||
- A: stop writing, keep serving from memory, withhold put ACKs, and report `contested` until the operator
|
||||
finds the writer. The working set survives.
|
||||
- B: end Odin. Idunn restarts it, and the restart loads whatever the foreign writer last wrote.
|
||||
- **Recommended: A.** B hands the store to the unknown writer. A is loud and loses nothing that is in memory.
|
||||
- **Q-R5. Should a store condition change Odin's presence to `degraded`?**
|
||||
- Context: a non-`active` Odin presence makes every managed dependent of Odin not Ready (`lib.rs:946`,
|
||||
`dependency_evidence`). The store condition does not affect serving.
|
||||
- A: no. The presence stays `active`, and the condition record is the signal.
|
||||
- B: `degraded` while `unpersisted` or `contested`.
|
||||
- **Recommended: A.** Otherwise a full disk becomes an outage of every dependent, and a restart that Idunn might
|
||||
take in response would destroy the only complete copy.
|
||||
- **Q-R6. The drills.**
|
||||
- D-1: decode a copy of last night's backup `topology.cc` in a container on Yggdrasil, the same terms as Q2 for
|
||||
the write map, with the copy deleted afterwards.
|
||||
- D-2: after O3 deploys, a scheduled live drill that truncates the real `topology.cc` behind the lifecycle brake,
|
||||
keeping a hard link of the original.
|
||||
- **Recommended: yes to both**, D-1 before O3 ships and D-2 in a window the operator names.
|
||||
|
||||
## 9. Follow-ups outside this map
|
||||
|
||||
- **I-F1 (Idunn):** a deploy or a stop of Odin while `odin.store_condition` says `unpersisted` or `contested`
|
||||
destroys the only complete copy. Idunn could read the condition from the catalog and hold the deployment
|
||||
(deployment brake, never continuity). This is Idunn's decision, raised with the Idunn map.
|
||||
- **I-F2 (Idunn):** "Idunn stopped restarting admitted odin" is a log line (`report_once`, `control_plane.rs:4599`),
|
||||
not typed state. It belongs to Idunn's own operator surface.
|
||||
- **F3 (from the write map):** the Eve lowering of `odin.store_condition` belongs to Odin's operator surface.
|
||||
This map publishes the typed record only.
|
||||
- **H-F1 (Heimdall):** confirm whether the deployed Heimdall re-puts its Odin documents every 60 s. O3's
|
||||
outstanding list measures it after any recovery.
|
||||
- **Q3 B's record in the write map:** "an ACK means durable" holds against crashes, not against a corruption at
|
||||
activation, which loses at most the last write (C1).
|
||||
|
|
@ -1,720 +0,0 @@
|
|||
# Transport Shortcut Inventory
|
||||
|
||||
> **Path note, 2026-09-05.** Idunn was extracted to
|
||||
> https://github.com/GameCult/Idunn. Entries below citing `scripts/idunn-*.ps1`,
|
||||
> `scripts/health-idunn-*`, `scripts/notify-idunn-*` or `scripts/start-idunn-local.ps1`
|
||||
> refer to paths that moved to `gamecult-ops/scripts/idunn/`; unit files and Idunn
|
||||
> documentation moved to the Idunn repository. This is a dated ledger and the
|
||||
> entries are left as written.
|
||||
|
||||
Status date: 2026-07-03
|
||||
|
||||
## Authority Map
|
||||
|
||||
Owner: Odin owns Verse rendezvous, accepted provider/interface catalogs, and CultMesh/CultNet document discovery.
|
||||
|
||||
Inputs: child-daemon CultMesh/CultCache witness stores, daemon-published `idunn.daemon_health` over `cultnet.transport.rudp.v0`, provider advertisements, Eve surface records, and explicit lifecycle command boundaries.
|
||||
|
||||
Outputs: accepted provider catalog, interface surfaces, daemon desired state, transport profiles, command boundaries, keepalive decisions, and operator-facing lowerings.
|
||||
|
||||
Derived state: browser HTTP/WebSocket endpoints, Hermodr pages, `.cmd` wrappers, health scripts, and SSH/systemd probes are lowerings, debug witnesses, or command ergonomics. They do not own daemon health, service discovery, input transport selection, or Sleipnir input mapping route selection.
|
||||
|
||||
Forbidden writers: compatibility health commands, HTTP status endpoints, WebSocket decks, explicit endpoint mapping fields, stale HID records, and local command probes must not decide daemon health or transport selection.
|
||||
|
||||
Shared paths: user UI actions, typed input-mapping updates, startup, reconnect, and RUDP timeout recovery must all discover Muninn input streams from Odin/CultMesh provider advertisements and consume actual HID frames through the advertised CultNet/RUDP endpoint.
|
||||
|
||||
Deletion line: delete endpoint overrides and fallback health/input paths before adding ergonomic replacement controls.
|
||||
|
||||
## Cut In This Pass
|
||||
|
||||
- Sleipnir no longer accepts `--muninn-rudp` from the launcher or CLI.
|
||||
- Sleipnir no longer reads `muninnRudp` / `muninn_rudp` from mapping JSON as transport authority.
|
||||
- Sleipnir no longer applies virtual HID state from stale CultMesh store records when the fast RUDP stream is absent.
|
||||
- Sleipnir UI actions no longer carry `muninnRudp`; they select device/remap only.
|
||||
- Hermodr no longer requires or writes `muninnRudp` for Sleipnir mapping commands.
|
||||
- Hermodr no longer synthesizes `gamecult.hermodr.fallback_odin_catalog.v1` from `/eve/deck/providers` when Odin's snapshot is unreadable.
|
||||
- Idunn no longer runs compatibility health commands to decide daemon health when fresh daemon-published RUDP health is missing.
|
||||
- Idunn now publishes a non-actuating `dependency-unavailable` supervisor observation on the daemon key when daemon RUDP health is absent.
|
||||
- Odin interface discovery no longer scrapes `/eve/deck/providers` from seeded WebSocket deck URLs.
|
||||
- Odin no longer performs LAN Eve deck scans to find provider truth.
|
||||
- Odin no longer fetches provider surfaces over compatibility WebSocket decks or converts legacy dashboard `nodes` into Eve surfaces.
|
||||
- Odin provider normalization no longer promotes `compatibility-eve-deck` routes to provider transport endpoints.
|
||||
- Odin no longer hosts an HTTP health endpoint or `/eve/deck` WebSocket/HTTP renderer surface at all. The `src/odin/websocket.cjs` side-door module was deleted, `odin-coordinator.cjs` refreshes only into CultMesh/CultNet state, and Idunn reads Odin health as `daemon-published-rudp:odin.cultnet-rudp-provider-health`. `health-odin.ps1` remains an explicit daemon witness inspection only.
|
||||
- Muninn transport profiles no longer advertise local CLI health/activation as fallback transport; activation is a command lowering and provider routes remain provider transport only.
|
||||
- Stonks no longer binds daemon-owned HTTP health, JSON snapshot, or WebSocket Eve deck side doors. The daemon refreshes typed CultCache/CultMesh records, publishes Idunn RUDP health, and optionally announces its provider advertisement to Odin through CultMesh/RUDP; renderer lowerings must be owned outside Stonks.
|
||||
- VoidBot swarm transport profile no longer advertises `health-voidbot.cmd` or `compatibility.local-command fallback`; operations health remains a debug witness only.
|
||||
- VoidBot Idunn health publisher and orchestrator wrapper no longer default health publication to `127.0.0.1:17870`; `--endpoint` or `VOIDBOT_IDUNN_RUDP_HEALTH` is required for publication, otherwise the orchestrator records an explicit skipped-health-publication event.
|
||||
- StreamPixels, Heimdall, and Repixelizer no longer describe SSH/systemd/HTTP witnesses as fallback transport authority; missing RUDP health is represented as missing daemon publication.
|
||||
- Vili no longer describes HTTP/WebSocket/JSON lowerings as compatibility transport, and missing Idunn RUDP health now surfaces as missing publication instead of local-only fallback.
|
||||
- Weksa command and transport profiles no longer carry null `compatibility` objects; deleted HTTP command lanes and debug lowerings are explicit non-authorities.
|
||||
- Gjallar no longer falls back to Odin `/eve/deck` WebSocket catalog/provider input when Odin CultNet/RUDP snapshot input is absent.
|
||||
- Gjallar runtime config and provider advertisement no longer publish a `DeckUrl`/`--url` input endpoint; missing Odin RUDP configuration is visible missing input transport.
|
||||
- Idunn generated transport profiles no longer append local-command, HTTP/WebSocket, or SSH/systemd/HTTP fallback transport for migrated targets.
|
||||
- Idunn command boundaries no longer grant health authority to command probes; health authority is daemon-published RUDP health, while local commands remain lifecycle/debug entrypoints only.
|
||||
- Odin WebSocket provider lowering no longer accepts `compatibility-eve-deck` routes as provider transport endpoints.
|
||||
- Mimir Nightwing Eve dashboard and browser reference command boundaries no longer publish `idunn.local-command.restart + compatibility.systemd...` lifecycle authority.
|
||||
- Mimir Nightwing Eve dashboard and browser reference transport profiles no longer publish `compatibility.*` input transports; health checks now reject HTTP/WebSocket if it reappears as transport authority.
|
||||
- Odin health scripts for the Nightwing Eve dashboard and browser reference now verify the daemon-owned supervisor/debug-lowering records instead of requiring the old compatibility lifecycle/input transport values.
|
||||
- Idunn transport profile and command boundary schema fields have been renamed from compatibility-shaped field names to `debug_mechanism` and `command_lowerings` while preserving their CultCache slot positions.
|
||||
- VoidBot swarm command boundary and transport profile exports now use `command_lowerings` and `debug_mechanism` instead of compatibility-shaped field names.
|
||||
- Mimir's OBS diagnostic program route is now labelled `obs-debug-sink`; OBS remains a diagnostic sink, not program composition or Verse publication authority.
|
||||
- Bifrost provider advertisements and interface bindings no longer advertise the Starfire Odin WebSocket deck bridge or `compatibility-eve-deck` route; the remaining provider route is the CultCache/CultMesh witness.
|
||||
- Odin, Mimir Eve dashboard, Stonks, Spotiverse, and Vili no longer serve local `/eve/deck/providers` provider catalogs as discovery shortcuts.
|
||||
- Mimir Eve dashboard no longer serves `/eve/deck/manifest` or `/eve/dashboard/manifest` as HTTP publication side doors; provider manifests remain daemon-owned CultCache/CultMesh records.
|
||||
- Eve Android no longer scrapes Mimir `/eve/deck/providers`; provider options come from the CultMesh dashboard state document it already consumes.
|
||||
- VoidBot worker no longer reads Odin `/eve/deck/providers` or jumps from an Odin-discovered interface to a provider WebSocket for command execution. Provider listing is derived from Odin's embedded interface state, and command execution now refuses until a CultMesh command document path exists.
|
||||
- Spotiverse and Vili smoke/export surfaces now use typed provider advertisements instead of deck provider manifests.
|
||||
- Active Mimir branch worktrees (`Mimir-obs-muninn-branch`, `Mimir-raven-build`) had stale `/eve/deck/providers`/manifest handlers removed from dashboard/Muninn/Raven/Fensalir daemon code, and persisted witness route metadata was demoted from `websocket-bridge` to debug lowerings.
|
||||
- Odin no longer accepts or reports seeded Eve deck URLs as a discovery configuration surface; provider/interface discovery is from CultMesh stores and live announcements.
|
||||
- VoidBot MCP Odin tools no longer read Odin through `/eve/deck` WebSocket snapshots or accept `ODIN_BASE_URL`; they read Odin's `gamecult.eve.surface_state.v1` from the configured Odin CultMesh store (`ODIN_CULTMESH_STORE` or the default `E:\Projects\Odin\scratch\odin\odin.ccmp`).
|
||||
- Idunn generated swarm plans no longer use `compatibility`/`fallback` wording for demoted probes; probe paths are described as debug witnesses and cannot satisfy daemon truth.
|
||||
- Ymir no longer serves HTTP routes for discovery, operator state, projectile steps, overlap queries, or cast queries. `ymir-daemon publish-service` writes provider, operator, and Eve surface records to a CultCache service publication store; local step smokes are CLI-only.
|
||||
- Spotiverse, Stonks, Vili, active Mimir branch docs, and Nightwing ops runbooks no longer advertise provider catalogs or manifest endpoints as live discovery surfaces.
|
||||
- Aetheria's Unity Ymir bridge no longer posts JSON to hard-coded local Ymir HTTP endpoints for projectile steps, overlap queries, or cast queries. The bridge now calls the typed `YmirPhysicsQueries` surface directly, and `Aetheria.State.Verify` now guards against reintroducing the deleted HTTP fields/methods.
|
||||
- StreamPixels no longer hand-assembles its service boundary `.cc` file with bespoke MessagePack/CultCache store encoding. `apps/service/src/verse-state.ts` opens a CultMesh node and writes provider advertisement, command boundary, transport profile, and Idunn health records through typed CultCache document definitions.
|
||||
- Heimdall no longer hand-assembles its auth runtime `.cc` file with bespoke MessagePack/CultCache store encoding. `src/verse-state.ts` opens a CultMesh node and writes provider advertisement, command boundary, transport profile, and Idunn health records through typed CultCache document definitions.
|
||||
- Loki no longer writes durable Chrome/provider/Eve state as typed text `.cc` witness sidecars while describing CultMesh publication as future. `src/loki-daemon.mjs` now writes snapshot, provider advertisement, Eve surface, command boundary, transport profile, debug probe, page snapshot, and Idunn health records through a typed CultMesh store at `state/loki.service.ccmp`, with optional explicit Odin startup announcement through `--odin-cultmesh-uri` / `LOKI_ODIN_CULTMESH_URI`.
|
||||
- Idunn-managed restart scripts in Odin no longer act as independent deploy/restart authorities. `restart-odin.ps1`, Muninn/Vili/Nightwing restart scripts, and the Stonks/Weksa/VoidBot restart `.cmd` wrappers now require `IDUNN_ACTUATOR=1` plus `IDUNN_COMMAND_AUTHORITY=idunn-daemon`; without those Idunn actuator marks they fail before SSH, systemd, Task Scheduler, or local process launch work can begin.
|
||||
- Idunn-managed deploy/restart `.cmd` wrappers in Odin no longer present manual side doors over guarded actuator bodies. The deploy wrappers for Nightwing Gjallar and Yggdrasil StreamPixels/Heimdall/Repixelizer, plus Odin/Muninn/Vili/Nightwing restart wrappers, now require `IDUNN_ACTUATOR=1` and `IDUNN_COMMAND_AUTHORITY=idunn-daemon` before they delegate.
|
||||
- Odin's Idunn deployment target catalog now names every guarded deploy/restart actuator path as a target command, target alias, or shared Idunn actuator. Odin, Stonks, Weksa, VoidBot, Raven Muninn, Vili, Nightwing Eve dashboard, and Nightwing Eve browser reference are represented in `scripts/idunn-deployment-targets.ps1`, so the guarded scripts are configuration-owned instead of floating side doors.
|
||||
- Yggdrasil StreamPixels, Heimdall, and Repixelizer remote deploy bodies in `gamecult-ops` no longer act as standalone server-side deployment runbooks. Odin's Idunn deploy wrappers pass `GAMECULT_IDUNN_REMOTE_ACTUATOR=1` plus `IDUNN_COMMAND_AUTHORITY=idunn-daemon` through `sudo env`, and the remote bodies reject missing marks. StreamPixels and Repixelizer also no longer git-clone/fetch/reset on the target host; they require the audited source tarball shipped by Idunn.
|
||||
- Odin no longer has an `--includeRemoteWitnesses` path that imports copied Nightwing/Raven Muninn/sleipnir witness stores from `scratch/remote-witnesses`. The untracked `scripts/sync-muninn-remote-witnesses.ps1` SCP/SFTP helper was deleted outright; remote providers must announce through Odin/CultMesh/RUDP or an explicitly configured live CultMesh store, not copied local witness files.
|
||||
- Weksa no longer writes or health-checks top-level provider/operator text witness sidecars (`provider-advertisement.cc`, `operator-state.cc`, `eve-surfaces.cc`, `command-boundary.cc`, `transport-profile.cc`, `cultmesh-publications.cc`). The daemon writes provider advertisement, operator state, Eve surface, command boundary, and transport profile records to `.weksa/provider-advertisement-store.cc`; `--health` now reads those typed CultMesh records, while MiMo request/receipt `.cc` artifacts remain command trace records.
|
||||
- Bifrost no longer exports or advertises a singleton `.bifrost/provider-advertisement.cc` witness as its Odin discovery surface. `tools/provider-advertisement.mjs export` now writes `.bifrost/provider-store.cc` containing provider advertisement, operator surface, and Eve interface binding records, and advertises the route as `cultmesh-store`.
|
||||
- Odin no longer seeds interface discovery from repo-local `.cc` paths or machine-local `C:\Meta\Odin\state\...` stores. Provider discovery is owned by live CultMesh/RUDP announcements through Odin; explicit local store inspection is a debug import and must be configured as a `cultmesh-store:file://...` URI via `--interfaceBindingStore` or `ODIN_INTERFACE_BINDING_STORES`.
|
||||
- Mimir Eve dashboard no longer accepts arbitrary `--provider id|title|ws-url` WebSocket deck registrations or synthesizes a StreamPixels SSH/LAN edge provider. The broker catalog now contains only its local/dashboard providers and typed witness-backed surfaces.
|
||||
- Eve browser reference no longer includes a hardcoded `VoidBot Live` provider that dials Mimir's `/eve/deck` broker with a custom `mimir-eve-deck` transport. The browser reference is fixture/local-advertisement only until live provider advertisements arrive through Odin/CultMesh.
|
||||
- Eve Android no longer bakes in `ws://192.168.1.66:8795/eve/deck/cultmesh` or `ws://192.168.1.66:8796/eve/periwinkle`. The native app reads explicit `org.gamecult.eve.DASHBOARD_URI` and `org.gamecult.eve.SENSOR_URI` launch extras; when absent, receive/publication stays visibly disabled until Odin/CultMesh discovery provides the endpoints.
|
||||
- Eve iOS no longer cycles through baked localhost/Starfire WebSocket candidates for frame stream, dashboard, camera, or microphone routes. `EVE_STREAM_URLS`, `EVE_DASHBOARD_URLS`, `EVE_CAMERA_URLS`, and `EVE_MIC_URLS` must be configured by Odin/CultMesh tooling or operator config; absent configuration stays visibly missing.
|
||||
- Mimir's Verse recorder, Nightwing typed witness publisher, and `start-nightwing-move-tracking.ps1` wrapper no longer default to localhost/Starfire Periwinkle WebSocket endpoints. The wrapper requires `-WitnessUrl`/`MIMIR_NIGHTWING_WITNESS_URL` and `-SubscribeUrl`/`MIMIR_NIGHTWING_SUBSCRIBE_URL` supplied by Odin/CultMesh discovery or explicit operator config.
|
||||
- `gamecult-ops` Nightwing visible-ops TUI/framebuffer lowerers no longer default to `ws://192.168.1.66:8797/eve/deck`. The Python lowerers require `--url`, and the shell launchers require `GAMECULT_VISIBLE_OPS_URL` from Odin/CultMesh discovery.
|
||||
- Odin's README, architecture map, and Gjallar handoff doc no longer describe LAN deck URLs, seeded deck URLs, or the old HTTP/WebSocket deck bridge as discovery/input mechanisms.
|
||||
- Sleipnir input mapping no longer uses `starfire.sleipnir.mapping.json`, `raven.sleipnir.mapping.json`, `--mapping-path`, Hermodr SSH/PowerShell writes, or an hermodr-configured local store fallback. Hermodr accepts only a `cultmesh://` Sleipnir command route advertised by Odin, publishes `sleipnir.input_mapping.v1` to Odin over the configured Odin CultMesh/RUDP substrate, and rejects raw host/port routes instead of dialing them. Sleipnir pulls `sleipnir.input_mapping.v1` from Odin discovery and chooses the newest local-or-Odin mapping record.
|
||||
- Odin marquee generation no longer falls back to Stonks' HTTP `/market/state` endpoint. Stonks market tape must arrive through the provider's CultMesh/Eve surface (`marqueeText`) in the Stonks CultCache store.
|
||||
- Odin provider advertisement inspection no longer promotes `ws://` endpoints ahead of CultMesh/provider addresses when deriving `transportEndpoint`; WebSocket endpoints remain lowerings/debug routes unless explicitly represented by typed CultMesh state.
|
||||
- Vili provider advertisements no longer publish HTTP routes or deck WebSocket URLs as provider endpoints; the provider endpoint is the Vili CultMesh service store, while HTTP/WS routes live under `debugLowerings`.
|
||||
- Vili motion generation no longer accepts HTTP command authority through `/motion/generate`; that route now returns `410 command_route_removed`. Vili polls `gamecult.vili.motion_request.v1` documents from `vili.service.cc`, runs the Kimodo worker from that typed request, and writes `gamecult.vili.motion_job.v1` receipts before JSON debug exports.
|
||||
- VoidBot repo-face and Metame owner voice projection no longer submit Weksa MiMo VoiceDesign commands over `WEKSA_DAEMON_BASE_URL` / HTTP `/speech-provider/mimo/voicedesign`; `WeksaSpeechClient` now requires the configured `WEKSA_CULTMESH_COMMAND_URI` and fails closed until a real CultMesh command-document writer publishes `weksa.mimo_voicedesign_command.v0`.
|
||||
- VoidBot no longer defaults Weksa speech command transport to a localhost/direct endpoint or advertises stale `ODIN_BASE_URL` in `.env.example`. `REPO_FACE_WEKSA_SPEECH_ENABLED=true` now requires an explicit `WEKSA_CULTMESH_COMMAND_URI` supplied by Odin/CultMesh discovery or operator config.
|
||||
- Weksa's service contract and Idunn's generated Weksa surgery plan no longer teach a direct MiMo VoiceDesign command ingress. The command path is described as the Odin/CultMesh command URI carrying `weksa.mimo_voicedesign_command.v0`.
|
||||
- `gamecult-ops` Nightwing tunnel runbook and service inventory no longer present Nightwing LAN broker/browser URLs, Starfire Eve deck URLs, or Starfire Periwinkle URLs as current operator recipes. Broker checks are SSH-local debug probes, browser access is explicit tunnel/Odin-discovered URL only, Gjallar consumes configured `GJALLAR_ODIN_CULTMESH_URI`, and the Nightwing witness publisher uses configured `MIMIR_WITNESS_URL`.
|
||||
- Bifrost's provider advertisement help and Verse contract no longer show `127.0.0.1:17871` as the Odin publication endpoint. Publication uses `--odin-cultmesh-uri` / `BIFROST_ODIN_CULTMESH_URI`, and the examples use Odin's CultMesh rendezvous URI.
|
||||
- Spotiverse's README no longer shows a raw Odin RUDP startup publication endpoint; startup publication remains optional and explicit through `SPOTIVERSE_ODIN_CULTMESH_URI`, with examples using Odin's CultMesh rendezvous URI.
|
||||
- Active Mimir branch Fensalir daemon docs, launcher output, provider manifest wording, and state capability labels no longer call local `/eve/deck` or `/eve/deck/cultmesh` WebSocket routes provider/bridge transport. They are explicitly local debug lowerings while CultCache `.ccmp` and typed CultMesh/Eve state remain authority.
|
||||
- Active Mimir branch Raven daemon no longer starts a daemon-owned HTTP/WebSocket Eve side door. `Mimir.RavenDaemon` now publishes only `mimir.raven_capture_mux_state.v1` through its CultMesh/CultCache store while FFmpeg/SRT owns media fanout; the deleted server body no longer exposes a health route, deck route, or deck-shaped shadow document.
|
||||
- Active Mimir branch Fensalir daemon no longer starts a daemon-owned HTTP/WebSocket Eve side door. `Mimir.FensalirDaemon` now runs only its Well-drinking state loop and persists `mimir.fensalir_daemon_state.v1`, `mimir.fensalir_reservoir_worker_state.v1`, and `mimir.fensalir_reservoir_pressure.v1` through CultCache; the deleted server body no longer exposes a health route, deck route, binary deck route, provider manifest, or deck-shaped shadow surface.
|
||||
- Active Mimir branch EveDashboard no longer starts the old deck broker or accepts launch-time `id|title|url` provider registrations. `Mimir.EveDashboard` now publishes built-in dashboard provider state through CultNet/CultMesh only; the deleted broker path no longer exposes health, deck, compatibility, per-provider deck, frame-command, or external-provider WebSocket routes.
|
||||
- Active Mimir branch online supervisor no longer passes a Fensalir port, writes a Fensalir deck bridge route, or waits on Fensalir HTTP health after starting `Mimir.FensalirDaemon`. It now waits for the run-local Fensalir CultCache state store instead of treating the deleted network surface as readiness.
|
||||
- Active Mimir branch EveSensorReceiver no longer exposes an HTTP health route or starts with baked camera port/path defaults. `--port` and `--path` are required from Odin/CultMesh tooling or explicit operator config, and the online supervisor now waits for the receiver startup log instead of probing localhost health. The remaining receiver WebSocket lane is classified as explicit media/sensor ingress, not service discovery or daemon health authority.
|
||||
- Standalone Gjallar docs/state no longer teach `192.168.1.66:17871` or the old Odin Eve deck WebSocket input as a retained compatibility fallback. They now match the already-cut runtime: configured Odin CultNet/RUDP snapshot input only, with the deck bridge removed.
|
||||
- Standalone Fensalir's native Eve surface client no longer defaults to `ws://127.0.0.1:8795/eve/deck`. `FENSALIR_EVE_BROKER` must be supplied by Odin/CultMesh tooling or explicit operator config; absent configuration is visible missing input instead of a localhost broker guess.
|
||||
- Ghostlight's provider advertisement fixture no longer advertises CultMesh/Odin provider discovery as future work. The fixture now names `CultMesh/Odin provider discovery`, matching the existing `publish-odin` CultMesh/RUDP publication path.
|
||||
- `gamecult-ops` local inventory no longer preserves Raven/Starfire/Qwen LAN addresses as operator recipes. It uses `OFFSITE_BACKUP_SSH_TARGET`, `LOCAL_INFERENCE_BASE_URL`, and `LOCALCAST_RECEIVER_HOST` placeholders so local notes do not re-teach deleted transport shortcuts.
|
||||
- `gamecult-ops` script inventory now names localhost HTTP checks as deployment/debug probes for product web stacks and nginx routing. The note explicitly denies ownership of Verse discovery, provider catalogs, daemon health, or command transport, and requires CultMesh-boundary checks to verify configured witnesses instead of replacing them with localhost truth.
|
||||
- Eve renderer parity docs no longer say the browser reference renders "live VoidBot" through the provider picker. The web reference is documented as local provider advertisements and fixture surfaces only; live provider feed remains future Odin/CultMesh input.
|
||||
- Bifrost's provider-advertisement exporter no longer hardcodes `http://127.0.0.1:5080/healthz` and `/readyz` into the operator-surface stats path. HTTP probe URLs are optional explicit inputs via `--health-url` / `--ready-url` or `BIFROST_HEALTH_URL` / `BIFROST_READY_URL`; absent probes render as `unprobed` instead of localhost-shaped daemon truth.
|
||||
- VoidBot's local `.env` no longer carries the deleted Weksa HTTP shortcut. It now follows the live config surface with blank `WEKSA_CULTMESH_COMMAND_URI` while `REPO_FACE_WEKSA_SPEECH_ENABLED=false`, so enabling Weksa speech still requires an explicit Odin/CultMesh-discovered route.
|
||||
- Heimdall state memory and fresh-workspace handoff no longer tell operators to deploy with `GC_ACCESS_IDUNN_RUDP_HEALTH=10.77.0.2:17870` or confirm source-address acceptance from `10.77.0.1`. They now describe Odin/CultMesh-discovered Idunn RUDP health routing, with HTTP/JWKS/systemd/nginx as product/debug witnesses.
|
||||
- Heimdall no longer has hardcoded Repixelizer or Spotiverse backend-callback URL exceptions in `src/app.ts`. Backend handoff trust is owned by `GC_ACCESS_APP_<APP>_BACKEND_CALLBACK_URLS` / `config.appBackendCallbacks`, so app callback routes are configured operator knowledge instead of app-specific transport lore in Heimdall code.
|
||||
- Spotiverse no longer defaults its Heimdall peer service URL to `http://127.0.0.1:4100`. `HEIMDALL_BASE_URL` is required and must come from Odin/CultMesh discovery or explicit operator config; Spotiverse's own local return/callback defaults remain local product-web callback ergonomics, not daemon discovery authority.
|
||||
- Spotiverse command authority now ingests `spotiverse.command_request.v1` documents from the daemon `.ccmp`, routes CultMesh requests through the same executor as debug lowerings, and writes `spotiverse.command_receipt.v1` documents back to CultMesh. `/commands/add-to-queue` and Eve `spotiverse-command` WebSocket frames are debug lowerings only.
|
||||
- Odin no longer runs TCP/SSH/systemd/GPU host probes during provider catalog state builds or uses LAN-IP source inference to assign Verse body identity. Remote Verse tiles are derived from provider advertisements and interface records; absent provider-owned witnesses stay visibly `waiting`.
|
||||
- `gamecult-ops` Gjallar systemd unit no longer pins Odin input to a raw RUDP endpoint; the unit consumes `GJALLAR_ODIN_CULTMESH_URI` from `/etc/gamecult/gjallar.env` or defaults to Odin's CultMesh rendezvous URI, and rejects stale raw Odin endpoint env.
|
||||
- Weksa start/restart wrappers no longer default Odin startup publication to `127.0.0.1:17871`; `-OdinCultMeshUri` comes from an explicit parameter or `WEKSA_ODIN_CULTMESH_URI`, otherwise the daemon uses Odin's CultMesh rendezvous URI.
|
||||
- Bifrost bridge, dispatch, request-lane, and governance mutation CLIs no longer accept `--allow-unreceipted-activity`, `BIFROST_ALLOW_UNRECEIPTED_ACTIVITY`, `--allow-ungated-github`, or `BIFROST_ALLOW_UNGATED_GITHUB` as operator recovery hatches. Mutating activity now fails closed unless `BIFROST_BRIDGE_BASE_URL` and `BIFROST_BRIDGE_TOKEN` route it through the Bifrost receipt/gate path.
|
||||
- Odin's Starfire/Raven/Nightwing Muninn and Sleipnir launchers no longer default Odin startup publication to `192.168.1.66:17871`; `-OdinCultMeshUri` comes from an explicit parameter or `ODIN_CULTMESH_URI`.
|
||||
- Odin's Raven Sleipnir launcher no longer advertises or opens a raw command RUDP endpoint. Sleipnir publishes `cultmesh://odin/command/sleipnir/input_mapping` as its command route and reads the typed `sleipnir.input_mapping.v1` command document from Odin discovery; `--command-rudp-bind` is a fail-closed removed flag.
|
||||
- Odin's Raven Sleipnir deploy/restart actuators no longer default SSH to `192.168.1.84`; they use the named `raven` target by default while endpoint-bearing daemon transport remains explicit CultMesh/CultNet configuration.
|
||||
- Odin's Starfire Sleipnir launcher no longer passes `E:\Projects\Odin\scratch\odin\odin.ccmp` as a default discovery store. Sleipnir discovers Muninn provider records from Odin through `--odin-cultmesh-uri`; local `--discovery-store` use is explicit debug/operator input only.
|
||||
- Active `Odin-clean-move-identity` worktree deploy/restart scripts no longer preserve stale Idunn/Odin endpoint defaults, Raven media target defaults, or direct Vili operator-state HTTP checks. Deploy/restart `.ps1` bodies and `.cmd` wrappers now require `IDUNN_ACTUATOR=1` plus `IDUNN_COMMAND_AUTHORITY=idunn-daemon`, matching the current Odin actuator boundary.
|
||||
- Active `Odin-clean-move-identity` source no longer keeps Odin's old seeded deck discovery, LAN deck scan, copied/SFTP witness import, Stonks HTTP market fallback, remote TCP/SSH/systemd health probes, or `compatibility-eve-deck` route promotion. Provider/interface discovery is CultMesh/Odin-owned, and explicit local debug imports must use `cultmesh-store:file://...`.
|
||||
- Odin's Nightwing Gjallar deploy actuator no longer defaults the live snapshot input to `192.168.1.66:17871`; it requires `-OdinCultMeshUri`, `GJALLAR_ODIN_CULTMESH_URI`, or `ODIN_CULTMESH_URI` and fails closed on stale raw endpoint env.
|
||||
- Odin's Raven/Starfire/Nightwing Muninn restart and health wrappers no longer default daemon health publication checks to `192.168.1.66:17870`; `-IdunnRudpHealth` comes from an explicit parameter or `IDUNN_RUDP_HEALTH`, and wrappers fail closed without it.
|
||||
- Odin's own start/restart wrappers no longer default daemon health publication to `127.0.0.1:17870`; `-IdunnRudpHealth` comes from an explicit parameter, `ODIN_IDUNN_RUDP_HEALTH`, or `IDUNN_RUDP_HEALTH`, and wrappers fail closed without it.
|
||||
- Odin's Nightwing Gjallar deploy actuator, Raven Vili restart actuator, and Nightwing Muninn health actuator no longer default Idunn health publication/checks to `10.77.0.2:17870`; their Idunn endpoint comes from explicit parameters or service-specific environment variables.
|
||||
- Vili's own start/restart/task-install wrappers no longer default Idunn health publication to `10.77.0.2:17870`; they require `-IdunnRudpHealth`, `VILI_IDUNN_RUDP_HEALTH`, or `IDUNN_RUDP_HEALTH`.
|
||||
- `gamecult-ops` StreamPixels, Heimdall, and Repixelizer deploy scripts no longer write `10.77.0.2:17870` into missing service env files. They require an existing service env assignment or an explicit `*_IDUNN_RUDP_HEALTH_DEFAULT` deployment variable.
|
||||
- Odin's Starfire/Raven/Nightwing Muninn restart wrappers no longer default HID controller provider advertisements to `192.168.*` endpoints; when HID RUDP bind is enabled, `-HidControllerRudpAdvertise` or `MUNINN_HID_CONTROLLER_RUDP_ADVERTISE` is required.
|
||||
- Odin's Raven Muninn restart wrapper no longer defaults media, OBS, or OBS catalog publication to Starfire LAN endpoints. `-TargetHost` or `MUNINN_MEDIA_TARGET_HOST` is required, `-ObsTargetHost`/`MUNINN_OBS_TARGET_HOST` derives from the media target when omitted, and `-ObsCatalogRudpTarget`/`MUNINN_OBS_CATALOG_RUDP_TARGET` is optional instead of guessed.
|
||||
- Odin's Raven Muninn restart and health wrappers no longer expose capture-command RUDP target or bind knobs. `request-stream` publishes typed commands through Odin/CultMesh, `serve` pulls them from Odin, and local activation storage is daemon-owned persistence/receipt body only.
|
||||
- Odin's Raven Muninn activation launcher no longer defaults media or OBS targets to `10.77.0.2`. `activate-muninn-raven-av-srt.ps1` requires `-TargetHost` or `MUNINN_MEDIA_TARGET_HOST`, and `-ObsTargetHost` derives from the explicit media target unless supplied.
|
||||
- Muninn itself no longer defaults media or OBS targets to `10.77.0.2`. `activate`, `request-stream`, and dry-run media plans require an explicit `--target-host`, and the published command boundary advertises no executable activation lowering until a media target is configured.
|
||||
- Odin core discovery tests and Eve's provider advertisement contract no longer preserve real LAN/WireGuard addresses as example discovery routes. Tests use documentation address space, and Eve's route example uses a CultMesh URI instead of `cultnet://10.77.0.4:3075`.
|
||||
- Odin's Starfire WireGuard elevation helper no longer rewrites unresolved `Nightwing.Home` to a baked Nightwing LAN endpoint or logs a baked Starfire LAN address. The fallback endpoint and optional LAN address must come from explicit parameters or `STARFIRE_WIREGUARD_NIGHTWING_ENDPOINT_FALLBACK` / `STARFIRE_LAN_ADDRESS`.
|
||||
- `gamecult-ops` Raven connectivity checker no longer defaults Raven LAN ICMP to `192.168.1.84`; the required check is Raven's WireGuard address, and LAN probing only runs when `-RavenLanIp` or `RAVEN_LAN_IP` is supplied.
|
||||
- Active Mimir branch proof/relay scripts no longer default Raven senders, Starfire receivers, Nightwing witness URLs, or Eve relay LAN host to `192.168.1.*` endpoints. The scripts and `Mimir.EveRelay` now require explicit parameters or `MIMIR_*` environment variables and fail closed without them.
|
||||
- Root Mimir CultMesh AV sender/receiver scripts and media runtime no longer default relay input to `10.77.0.1`; `MIMIR_CULTMESH_RELAY_HOST`, `--host`, or `--endpoint` must be supplied by Odin/CultMesh discovery or operator config.
|
||||
- Root Mimir Raven Muninn OBS feed, `Mimir-raven-build` `Mimir.Muninn`, and `Mimir-obs-muninn-branch` `Mimir.RavenDaemon` no longer default media/OBS target host to `10.77.0.2`; `MIMIR_MUNINN_TARGET_HOST` / `--target-host` is required, and OBS target derives from the explicit media target unless supplied.
|
||||
- Active Mimir branch Well/VerseRecorder stream producers no longer default publish or subscribe transport to localhost Periwinkle WebSocket endpoints. `Mimir.Well` requires `--publish-url` or `MIMIR_WELL_PUBLISH_URL`; `Mimir.VerseRecorder` requires `--url` or `MIMIR_VERSE_RECORDER_URL`; `start-online-verse-daemons.ps1` requires `-WellPublishUrl` / `-SubscribeUrl` or the same environment variables.
|
||||
- Mimir's neighbor sender setup doc no longer presents old Raven/Starfire LAN addresses as current SSH or SRT endpoint recipes; it now names configured aliases and receiver-host placeholders supplied by Odin/CultMesh discovery or explicit operator config.
|
||||
- Active Mimir branch Nightwing typed witness publishers no longer default to Starfire's Periwinkle WebSocket URL. `--url` or `MIMIR_WITNESS_URL` is required.
|
||||
- `Mimir-obs-muninn-branch` OBS Muninn source no longer carries baked Raven command or Starfire media endpoints as defaults, no longer migrates stale private endpoints to another private endpoint, and no longer reconstructs provider-supplied RUDP URLs from local media-target defaults. Missing command/media endpoints remain empty until supplied by typed catalog/discovery or explicit settings.
|
||||
- Active Mimir docs and `audio-field.example.json` examples no longer present old Raven/Starfire LAN addresses as sender, receiver, Eve dashboard, SRT, CultNet, SSH, or calibration host recipes. They now use configured aliases, placeholders, or example hostnames.
|
||||
- `Gjallar-rollback` no longer preserves a runnable Odin Eve deck WebSocket renderer. The rollback executable is a fail-closed archive tombstone that exits before dialing the removed transport and points operators at active Gjallar's CultMesh URI input path.
|
||||
- Hermodr no longer treats Odin's localhost HTTP/WebSocket deck or `latest-surface.json` export as default discovery truth. It reads Odin's `gamecult.eve.surface_state.v1` document from the Odin CultMesh store (`--odinCachePath`, `--odinSurfaceKey`), with `--odinStatePath` left as an explicit debug fallback only.
|
||||
- Vili no longer writes provider/operator/surface/command/transport/backend JSON sidecars by default. `--write-debug-sidecars` or `VILI_WRITE_DEBUG_SIDECARS=1` is required for explicit debug exports, and the daemon/smoke tooling no longer defaults to Raven's baked WireGuard HTTP URL.
|
||||
- Odin's `health-vili.cmd` is archived and no longer curls Vili HTTP health at all; Vili health is daemon-published as `vili.cultnet-rudp-animation-health`.
|
||||
- Muninn's Odin startup RUDP parser test no longer preserves `10.77.0.2:17871` as the example endpoint literal.
|
||||
- Weksa no longer writes provider/operator/surface/command/transport/publication or MiMo request/receipt JSON mirrors by default. `--write-debug-json` or `WEKSA_WRITE_DEBUG_JSON=1` is required for explicit debug exports; `.cc` witnesses and the CultMesh store remain authority.
|
||||
- Weksa start/restart wrappers no longer default Idunn health publication to `127.0.0.1:17870`; `-IdunnRudpHealth` comes from an explicit parameter or `WEKSA_IDUNN_RUDP_HEALTH`, and service launch fails closed without it.
|
||||
- Stonks start wrapper no longer defaults Idunn health publication to `127.0.0.1:17870` or Odin startup publication to `127.0.0.1:17871`; `-IdunnRudpHealth` comes from explicit parameters or `STONKS_IDUNN_RUDP_HEALTH` / `IDUNN_RUDP_HEALTH`, and `-OdinCultMeshUri` comes from `STONKS_ODIN_CULTMESH_URI`.
|
||||
- Spotiverse no longer writes `latest-surface.json` or `latest-spotify-state.json` by default, and no longer hydrates playback state from `latest-spotify-state.json` on boot by default. `--write-debug-json` / `SPOTIVERSE_WRITE_DEBUG_JSON=1` and `--read-debug-json` / `SPOTIVERSE_READ_DEBUG_JSON=1` are explicit debug/migration paths; the durable surface witness is the CultMesh `.ccmp` store.
|
||||
- Odin's Raven Vili restart actuator no longer waits on Vili's HTTP operator-state route. It verifies restart through the Raven-local daemon state file and the configured Idunn RUDP health status.
|
||||
- Odin no longer writes `scratch/odin/latest-surface.json` by default. The accepted network surface is persisted to the Odin CultMesh store, and the JSON surface export requires `--write-debug-surface-json` or `ODIN_WRITE_DEBUG_SURFACE_JSON=1`.
|
||||
- Weksa no longer reads `docs/fixtures/weksa-provider-advertisement.json` as live daemon provider truth. The daemon constructs the live `gamecult.eve.provider_advertisement.v1` from runtime-owned code and current transport settings; checked-in provider fixtures stay examples only.
|
||||
- Repixelizer's docs and repo state map no longer describe `/api/health`, `/api/config`, nginx, or `repixelizer-gui.service` as live daemon truth. The live GUI runtime writes provider, surface, queue/job, command-boundary, transport-profile, and `idunn.daemon_health` records to its configured `.cc` store, and missing Idunn RUDP health remains visible missing publication.
|
||||
- Idunn swarm targets no longer carry `health_command` as part of the target contract, desired daemon records publish `health_command: None`, transport profiles report `debug_mechanism: none`, and `--health-command` is rejected. Missing daemon health is represented as a non-actuating missing CultNet/RUDP publication on the daemon key.
|
||||
- Muninn transport profiles no longer advertise a literal `muninn --health --store ...` command as a debug lowering. The `serve` process owns Idunn health publication over CultNet/RUDP; `--health` remains an explicit diagnostic publisher, not a transport/profile shortcut.
|
||||
- Idunn's README no longer teaches `--health-command` or command-exit health examples. Manual runs now document missing daemon RUDP publication as typed absence, and actuation examples use deploy/restart authority only.
|
||||
- Idunn's architecture doc no longer describes command probes as compatibility health fallback. It now names daemon-published RUDP health as the health owner and missing/stale publication as typed missing-publication state.
|
||||
- Odin surface/layout scoring no longer carries `legacy-dashboard-nodes` or `legacy-node-groups` compatibility branches. Provider-owned Eve/CultUI surface structure now drives overview eligibility without old dashboard-node marker exceptions.
|
||||
- Spotiverse's local agent notes no longer describe CultMesh command documents as future work; they name `spotiverse.command_request.v1` as the live command input and HTTP/Eve command frames as debug lowerings.
|
||||
- Repixelizer's README and witness contract no longer describe CultCache/CultMesh publication as future/reserved witness work. They name `RepixelizerVerseRuntime` and `repixelizer.service.cc` as the live daemon-owned witness while keeping the provider-advertisement fixture read-only.
|
||||
- StreamPixels now accepts typed `streampixels.command_request.v1` CultMesh command documents for `preview-event`, routes them through `EventService.applyPreviewEvent`, and writes `streampixels.command_receipt.v1` receipts. `/api/admin/preview-event` is a debug/operator lowering into the same executor, not the command authority.
|
||||
- Aetheria's package metadata and Perfect Machine map no longer describe CultMesh catalog/command bridge work as future where the typed state runtime and `AetheriaEveCommandBridge` already exist. Runtime presenters emit typed command documents; provider-owned bridge code accepts them from the CultCache/CultMesh state node.
|
||||
- Odin and active Mimir branch docs no longer call WebSocket deck routes or old dashboard paths compatibility transports; they name renderer/debug lowerings. StreamPixels docs no longer show concrete `10.77.0.2` Idunn/Odin RUDP examples and instead require configured endpoints.
|
||||
- Active Mimir branch Eve program-output docs no longer teach EveCanvas to try localhost and then fall back to a receiver host. Eve uses explicit `EVE_STREAM_URLS`; SSH tunnels and direct relay URLs are operator-supplied lowerings, not candidate fallback discovery.
|
||||
- Heimdall's fresh-workspace handoff no longer teaches `GC_ACCESS_IDUNN_RUDP_HEALTH=10.77.0.2:17870`; it uses a configured Idunn endpoint placeholder.
|
||||
- `gamecult-ops` and VoidBot offsite-backup docs/scripts no longer teach Raven's old LAN IP or a named Qwen box as the backup target. VoidBot already requires `OFFSITE_BACKUP_SSH_TARGET`; the docs now describe the configured SSH target instead of a baked workstation route.
|
||||
|
||||
## Current Shortcut Inventory
|
||||
|
||||
| Area | Current State | Required Direction |
|
||||
| --- | --- | --- |
|
||||
| Sleipnir input transport and mapping | Uses Odin/CultMesh provider advertisements to discover Muninn HID RUDP endpoints, publishes the Sleipnir command route as `cultmesh://odin/command/sleipnir/input_mapping`, and reads `sleipnir.input_mapping.v1` from local plus Odin discovery CultMesh stores for device/remap intent. Hermodr command lowering has no configured store fallback and no direct raw endpoint dial; it publishes the typed mapping document to Odin through the configured Odin CultMesh/RUDP substrate only when Odin advertises a `cultmesh://` Sleipnir command route. Startup publication to Odin is explicit/env-configured, not a Starfire LAN default. | Keep endpoint choice and command route selection derived from provider advertisements only, and route cross-host command ergonomics through Odin/CultMesh documents instead of remote file writes, raw host routes, or local store assumptions. |
|
||||
| Idunn health | Fresh daemon-published RUDP health is the only healthy daemon truth; missing publication becomes an alarmable supervisor observation. | Keep command probes unable to satisfy health. |
|
||||
| Hermodr browser lowering | Lowers Odin's typed `gamecult.eve.surface_state.v1` document from the Odin CultMesh store and proxies browser command ergonomics into Odin-advertised writable routes; no provider-catalog fallback, local Sleipnir store fallback, or default Odin HTTP/WebSocket deck input remains. Explicit `--odinStatePath` is debug fallback only. | Keep browser surfaces as lowerings only; do not let Hermodr invent Verse catalogs, command routes, or read renderer exports as default truth. |
|
||||
| Odin daemon surface | Uses CultMesh stores and live CultNet/RUDP announcements for provider/interface truth; compatibility deck scraping, legacy node lowering, remote TCP/SSH/systemd probes, LAN-IP body inference, default JSON surface export, HTTP health, and Odin-hosted `/eve/deck` are removed from daemon runtime. | Keep Odin as a CultMesh/CultNet daemon. Renderers lower typed CultMesh state outside Odin; do not reintroduce HTTP/WebSocket side doors in Odin. |
|
||||
| Muninn telemetry, HID, and media | Publishes provider advertisement, command boundary, transport profile, telemetry surfaces, HID input streams, media profile, and daemon health through CultCache/CultMesh/CultNet; activation commands are command lowerings, not provider routes. Startup publication to Odin, daemon health publication to Idunn, HID provider advertisement, and Raven media/activation targets are explicit/env-configured rather than Starfire LAN or WireGuard defaults. Muninn's own media CLI no longer has a baked target host, and boundary records leave activation lowerings empty until a target is configured. Transport profiles no longer advertise one-shot health commands; `serve` owns daemon health publication over CultNet/RUDP. | Keep Sleipnir discovery derived from Muninn provider advertisements and HID RUDP streams; move media/OBS routing toward Odin/CultMesh-resolved endpoints instead of launcher constants. |
|
||||
| Stonks daemon surface | Publishes CultCache/CultMesh provider records and daemon RUDP health; the Stonks daemon no longer binds HTTP health, JSON snapshot, or WebSocket deck routes. | Keep market state and Eve/CultUI surfaces in typed CultCache/CultMesh records. Renderer lowerings must attach outside the daemon and must not become discovery or health authority. |
|
||||
| Odin marquee/Stonks input | Odin builds market ticker text from Stonks' discovered Eve surface `marqueeText` only; direct HTTP `/market/state` scraping and `stonksStateUrl` config are removed. | Keep Stonks market data projected through typed CultMesh/CultCache state before Odin uses it. |
|
||||
| Spotiverse / Stonks / Vili provider catalogs | Local deck provider-list HTTP endpoints are removed; provider advertisements and surfaces remain daemon-owned typed records or explicit debug/operator lowerings. Spotiverse command requests and receipts now use typed CultMesh documents in `spotiverse.ccmp`; HTTP and Eve WebSocket command shortcuts are removed. Vili motion generation now consumes `gamecult.vili.motion_request.v1` from `vili.service.cc` and writes `gamecult.vili.motion_job.v1`; `/motion/generate` is a `410` tombstone. | Keep provider discovery and command authority through Odin/CultMesh, not service-local manifest scraping or private command lanes. |
|
||||
| VoidBot swarm surface | Publishes provider advertisement, command boundary, surface state, and transport profile from `.voidbot/status/cultmesh/voidbot-swarm-state.cc`; Idunn health is RUDP-published from the orchestrator. VoidBot MCP reads Odin embedded interface state from Odin's CultMesh store and refuses command execution without a CultMesh command transport. Repo-face and Metame voice projections send Weksa MiMo VoiceDesign commands over CultNet/RUDP instead of HTTP. | Keep operations-health JSON and HTML dashboard as debug/render lowerings only; add real CultMesh command document execution before re-enabling agent command sends. |
|
||||
| StreamPixels / Heimdall / Repixelizer | Command boundaries and transport profiles name SSH/systemd/HTTP/api/nginx surfaces as deployment or debug lowerings, not transport authority. StreamPixels accepts `preview-event` through typed CultMesh command request/receipt documents, with HTTP preview as a debug/operator lowering only. Deploy scripts require explicit Idunn health endpoint config instead of writing a WireGuard endpoint default. Repixelizer now publishes its live GUI-owned Verse witness through `src/repixelizer/verse_state.py`; `/api/health` reports that runtime state rather than owning daemon truth. | Keep RUDP health publication daemon-owned; missing publication must remain visible instead of hidden by probes. Extend command documents one service primitive at a time instead of reviving private HTTP command lanes. |
|
||||
| Bifrost bridge and request lane | External bridge writes, GitHub mutations, dispatch runs, request-lane mutations, snapshot imports, and governance mutations require the Bifrost bridge URL/token so receipt/gate state is recorded. The old unreceipted and ungated flags are rejected everywhere, including tests. | Keep Bifrost mutation authority behind the bridge receipt/gate contract; do not reintroduce local recovery hatches that let workers or operators mutate off-book. |
|
||||
| Raven connectivity ops | Required connectivity checks use Raven's WireGuard address and SSH alias; Raven LAN ICMP is optional explicit diagnostics only. | Keep LAN probes out of required service health/discovery, and prefer Odin/CultMesh daemon publications for service truth. |
|
||||
| StreamPixels service boundary store | The service writes `streampixels.service.cc` through `CultMesh.createNode(...).put(...)` with typed document definitions; the old bespoke single-file MessagePack writer is removed. | Keep service-owned CultCache/CultMesh publication as the Verse boundary; web `SERVICE_URL` stays product web/backend routing, not Odin discovery. |
|
||||
| Heimdall service boundary store | The auth runtime writes `heimdall.service.cc` through `CultMesh.createNode(...).put(...)` with typed document definitions; the old bespoke single-file MessagePack writer is removed. | Keep Heimdall's auth/provider witness publication as typed CultMesh/CultCache state; HTTP auth/JWKS/discovery endpoints remain product and debug lowerings, not Odin discovery authority. |
|
||||
| Loki Chrome observer | Chrome extension ingest and CDP probing are local observation lowerings. Loki now writes its daemon-owned snapshot, provider advertisement, Eve surface, command boundary, transport profile, debug/page snapshot, and Idunn health records through `state/loki.service.ccmp`, and only announces to Odin when an explicit Odin CultMesh/RUDP endpoint is configured. | Keep browser-extension HTTP as local input ergonomics only; do not restore text `.cc` witness sidecars or future-CultMesh prose as durable Verse state. |
|
||||
| Repixelizer service boundary store | The GUI runtime writes provider advertisement, Eve surface state, runtime config, auth projection, queue snapshot, job/job-event records, command boundary, transport profile, and `idunn.daemon_health` to the configured `.cc` witness store. It publishes Idunn RUDP health when `GC_ACCESS_IDUNN_RUDP_HEALTH` is configured; otherwise the transport profile reports missing RUDP health publication. | Keep queue/job ownership in `GuiJobManager` until a deliberate typed-store ownership move, and keep HTTP/systemd/nginx as deployment/debug witnesses only. |
|
||||
| Vili / Weksa | Vili publishes provider, operator, command, transport, motion request, and motion job records in its service CultCache store. HTTP/WS are operator/debug lowerings, JSON sidecars require explicit debug export, and the Vili smoke probe requires an explicit base URL. Weksa accepts commands over CultNet/RUDP, records deleted HTTP command lanes as non-authority, writes JSON mirrors only when debug export is explicit, and constructs its live provider advertisement from daemon-owned code rather than docs fixtures. Vili Idunn health, Weksa Idunn health, and Weksa startup publication are explicit/env-configured, not baked endpoint defaults. | Keep renderer, fixtures, and debug endpoints outside provider transport and command ownership, and route future Vili/Weksa command intake through typed records rather than JSON mirrors. |
|
||||
| Gjallar | Runs headless beside Odin and Idunn on Yggdrasil, consumes Odin accepted provider state through CultNet/RUDP snapshot input, and publishes `gjallar.overview`; no WebSocket deck scrape, direct provider WebSocket receiver, baked Starfire endpoint, or live Nightwing framebuffer body remains. | Keep Yggdrasil aggregate membership/layout derived from Odin-owned provider state; EveCanvas, browser, TUI, and optional framebuffer clients lower the published tree without becoming owners. |
|
||||
| Idunn surgery/profile records | Migrated target profiles name daemon-owned RUDP health plus daemon-owned CultCache/CultMesh stores. Swarm targets no longer carry health commands, desired daemon records publish `health_command: None`, `--health-command` fails closed, and missing health becomes a non-actuating missing-publication observation instead of a probe result. | Rename legacy schema fields in a later schema migration if consumers can move together. |
|
||||
| Mimir Nightwing Eve dashboard/browser reference | Publish daemon-owned command boundaries and transport profiles with supervisor lifecycle authority and HTTP/WebSocket/static HTTP as lowerings, not compatibility transport authority. The Eve dashboard broker no longer imports arbitrary remote WebSocket deck providers or invents a StreamPixels SSH/LAN provider. Active proof/relay branch scripts require explicit endpoint config instead of baked Starfire/Raven LAN routes. | Keep Odin/Nightwing consumers reading the typed CultMesh/CultCache witnesses rather than service probes; add new remote surfaces through Odin/CultMesh ingestion, not broker launch args. |
|
||||
| Mimir OBS diagnostic sink | OBS route is labelled and configured as a debug sink; Mimir owns composition and Yggdrasil/site publication remains the publication path. | Keep OBS outside Verse discovery, provider ownership, and publication authority. |
|
||||
| Eve browser reference | The browser reference renders local fixtures and checked-in provider advertisements only; it no longer opens live providers by dialing Mimir's Eve deck broker directly. | Feed the picker from Odin/CultMesh `gamecult.eve.provider_advertisement.v1` documents before reintroducing live provider entries. |
|
||||
| Eve native edges | Android no longer has hardcoded Mimir dashboard or Periwinkle sensor URLs; dashboard receive and sensor/media publication require explicit launch extras and otherwise report missing Odin/CultMesh discovery. iOS no longer has baked frame/dashboard/camera/mic WebSocket candidate lists; configured URL lists must come from Odin/CultMesh tooling or operator config and otherwise remain visibly missing. Standalone Fensalir's Direct2D Eve client also requires an explicit `FENSALIR_EVE_BROKER` and fails visible when Odin/CultMesh tooling has not supplied a lowering endpoint. | Replace explicit launch/config values with Odin/CultMesh-resolved dashboard/sensor/media endpoints, and keep absent discovery visible rather than falling back to LAN constants. |
|
||||
| Mimir Nightwing sensor tooling | `Mimir.VerseRecorder`, `nightwing_typed_witness_publisher.py`, branch witness publishers, and `start-nightwing-move-tracking.ps1` no longer carry or synthesize direct default WebSocket endpoints. The wrappers start local receivers where needed but require publication/subscription URLs from Odin/CultMesh discovery or explicit operator config. | Replace explicit operator config with Odin/CultMesh-resolved sensor publication/subscription endpoints, and keep direct tool invocations fail-closed without discovered/configured endpoints. |
|
||||
| Mimir OBS Muninn source | The OBS source consumes provider/catalog supplied RUDP URLs and command/media endpoints; stale private endpoint settings are cleared instead of rewritten to another private shortcut, and missing command/media endpoints stay inert. | Keep OBS as a renderer/diagnostic sink consuming typed Muninn catalog discovery; do not let the plugin own Raven/Starfire transport coordinates. |
|
||||
| Nightwing visible ops lowerers | `gamecult-ops` TUI and framebuffer lowerers no longer default to the Starfire Odin deck URL; direct runs require `--url`, and shell launchers require `GAMECULT_VISIBLE_OPS_URL`. | Feed visible ops from Odin/CultMesh discovery or a generated operator env file, not hardcoded LAN deck addresses. |
|
||||
| Starfire WireGuard bootstrap | The elevation helper starts the configured WireGuard service and records local diagnostics, but unresolved `Nightwing.Home` can only be replaced with an explicit operator-supplied endpoint fallback. | Keep network bootstrap separate from Verse discovery and do not let it smuggle hardcoded host identity back into service transport configuration. |
|
||||
| Ymir physics service | Provider advertisement, operator state, and Eve surface publication are CultCache records in `.ymir/service.cc`; HTTP serve routes and command/query lowerings are removed. Aetheria no longer depends on Ymir's localhost HTTP endpoints for Unity physics queries. | Keep any future remote Ymir routing behind typed CultMesh query handles, not service-specific endpoint fields. |
|
||||
| Aetheria Ymir bridge | Unity projectile, overlap, and cast queries use the typed local `YmirPhysicsQueries` surface; direct localhost Ymir endpoint fields and `HttpWebRequest` transport are removed. | Promote the same operation semantics into CultMesh query handles so local/in-process and remote Ymir routing share one semantic API. |
|
||||
| Stale rollback clones | `Gjallar-rollback` is now an archived fail-closed tombstone instead of a runnable Odin Eve deck WebSocket client. `Odin-clean-move-identity` has been cleaned as an active branch worktree. | Keep rollback clones unable to act as service transport authorities; delete/archive any future clone that cannot be made fail-closed. |
|
||||
| Remaining remote service witnesses | Framework-level CultMesh fallback route hints and Aetheria verifier guard strings remain; these are not service shortcut authorities. | Continue service-by-service cuts, moving provider, health, command boundary, and surface records into daemon-owned CultMesh/CultCache publication. |
|
||||
|
||||
## Verification
|
||||
|
||||
- `cargo test -p sleipnir-daemon`
|
||||
- `node --check E:\Projects\Odin\src\hermodr-daemon.cjs`
|
||||
- `node --check E:\Projects\Odin\src\odin\documents.cjs`
|
||||
- `node --check E:\Projects\Odin\src\odin\marquee.cjs`
|
||||
- `node --check E:\Projects\Odin\src\odin\config.cjs`
|
||||
- `node --check E:\Projects\Odin\src\odin\state.cjs`
|
||||
- `node --check E:\Projects\Odin\src\odin-coordinator.cjs`
|
||||
- `node --check E:\Projects\Odin\src\odin\interfaces.cjs`
|
||||
- `rg -n "mapping_path|mapping-path|starfire\.sleipnir\.mapping\.json|raven\.sleipnir\.mapping\.json|SleipnirMapping|SleipnirMapping|ravenSsh|readRemoteWindowsJson|writeRemoteWindowsJson|ssh-powershell-compat|local-file" E:\Projects\Odin\crates\sleipnir-daemon\src\main.rs E:\Projects\Odin\src\hermodr-daemon.cjs E:\Projects\Odin\scripts\start-hermodr.ps1 E:\Projects\Odin\scripts\restart-hermodr.ps1 E:\Projects\Odin\scripts\start-starfire-sleipnir.ps1` returned no hits.
|
||||
- `rg -n "muninnRudp|muninn_rudp" E:\Projects\Odin\crates\sleipnir-daemon\src\main.rs E:\Projects\Odin\src\hermodr-daemon.cjs E:\Projects\Odin\scripts` only finds the negative test fixture proving the old key is ignored.
|
||||
- `node --check E:\Projects\Odin\src\hermodr-daemon.cjs`
|
||||
- PowerShell parse checks passed for `scripts\start-hermodr.ps1` and `scripts\restart-hermodr.ps1` after adding `-sleipnirProviderId`.
|
||||
- Hermodr Sleipnir command smoke started a temporary target-local Hermodr with `--sleipnirProviderId sleipnir.input-mirror.starfire`, verified a configured command wrote `sleipnir.input_mapping.v1` to the CultMesh store, and verified a `sleipnir.input-mirror.raven` command was rejected with HTTP 409 instead of being written to the wrong store.
|
||||
- `rg -n "stonksStateUrl|/market/state|httpGet" E:\Projects\Odin\src\odin\marquee.cjs E:\Projects\Odin\src\odin\config.cjs E:\Projects\Odin\src\odin\state.cjs E:\Projects\Odin\src\odin-coordinator.cjs E:\Projects\Odin\src\odin\interfaces.cjs` returned no hits.
|
||||
- `rg -n -F 'startsWith(\"ws://' E:\Projects\Odin\src\odin\interfaces.cjs` returned no hits.
|
||||
- `node --check E:\Projects\Vili\scripts\vili-daemon.mjs`
|
||||
- `node E:\Projects\Vili\scripts\vili-daemon.mjs --print-provider-advertisement --state-root E:\Projects\Vili\.vili-test-codex` printed a provider advertisement whose only provider endpoint is the `cultmesh-store` witness and whose HTTP/WS routes are `debugLowerings`.
|
||||
- `node --check E:\Projects\Vili\scripts\smoke-vili.mjs`
|
||||
- `node E:\Projects\Vili\scripts\smoke-vili.mjs` exits 2 without `VILI_URL` or positional base URL.
|
||||
- `node E:\Projects\Vili\scripts\vili-daemon.mjs --print-provider-advertisement --host 0.0.0.0 --port 8824` shows the `cultmesh-store` endpoint and local `127.0.0.1:8824` debug lowerings, with no `10.77.0.4:8824` baked URL.
|
||||
- Temporary Vili daemon boot without `--write-debug-sidecars` wrote `vili.service.cc`, `vili-daemon.pid`, and `jobs`, and did not write `provider-advertisement.json`, `operator-state.json`, `eve-operator-surface.json`, `command-boundary.json`, `transport-profile.json`, or `backend-status.json`.
|
||||
- Temporary Vili daemon boot with `--write-debug-sidecars` wrote the explicit debug JSON sidecars.
|
||||
- `cmd /c scripts\health-vili.cmd` exits 2 as an archived HTTP health probe.
|
||||
- `cargo test -p muninn-daemon serve_accepts_odin_cultmesh_rudp_startup_target`
|
||||
- `rg -n "10\.77\.0\.4:8824|10\.77\.0\.2:17871|192\.168\.1\.66:17870|/eve/deck/providers|ODIN_BASE_URL|WEKSA_DAEMON_BASE_URL" E:\Projects\Odin\scripts E:\Projects\Odin\crates\muninn-daemon\src\main.rs E:\Projects\Vili -g '!node_modules' -g '!**/.git/**'` returned no hits.
|
||||
- `node --check E:\Projects\weksa\scripts\weksa-daemon.mjs`
|
||||
- Temporary Weksa daemon boot before the provider-store cut wrote the now-deleted top-level text witness sidecars. The later Weksa CultMesh health smoke verified the live daemon now writes `provider-advertisement-store.cc` and does not recreate `provider-advertisement.cc`, `operator-state.cc`, `eve-surfaces.cc`, `command-boundary.cc`, `transport-profile.cc`, or `cultmesh-publications.cc`.
|
||||
- Temporary Weksa daemon boot with `--write-debug-json` wrote the explicit debug JSON mirrors.
|
||||
- Temporary Weksa daemon boot with `--write-debug-json` verified the live provider advertisement is `mode: daemon-live`, contains the MiMo VoiceDesign command boundary with `acceptedByDaemon: true`, and contains no `fixture-read-only`, `acceptedByFixture`, or `Fixture exports` language.
|
||||
- `rg -n "docs/fixtures/weksa-provider-advertisement\.json|fixturePath|acceptedByFixture|fixture-read-only|Fixture exports" E:\Projects\weksa\scripts\weksa-daemon.mjs E:\Projects\weksa\docs\verse-service-contract.md` returned no hits.
|
||||
- PowerShell parse checks passed for `E:\Projects\weksa\scripts\start-weksa-daemon.ps1` and `E:\Projects\weksa\scripts\restart-weksa-daemon.ps1` after removing the localhost Idunn health default.
|
||||
- `powershell.exe -NoProfile -ExecutionPolicy Bypass -File E:\Projects\weksa\scripts\start-weksa-daemon.ps1` with `WEKSA_IDUNN_RUDP_HEALTH` unset exits nonzero before starting the daemon and reports that `-IdunnRudpHealth` or `WEKSA_IDUNN_RUDP_HEALTH` is required.
|
||||
- Source/doc scan of Weksa launchers and Odin Idunn docs for `127.0.0.1:17870`, stale Weksa command-ingress debt wording, and `IdunnRudpHealth = "127` returned no hits; the only remaining `127.0.0.1:17870` hit is this inventory note.
|
||||
- Weksa command-ingress scan now verifies the live daemon publishes a CultMesh command route and no longer exposes `--mimo-command-json` or `sendWeksaMimoVoiceDesignCommand` as direct RUDP client shortcuts.
|
||||
- PowerShell parse check passed for `E:\Projects\Odin\scripts\restart-vili.ps1` after replacing the HTTP operator-state health wait with Raven-local daemon state file verification.
|
||||
- `node --check E:\Projects\Odin\src\odin\config.cjs`
|
||||
- `node --check E:\Projects\Odin\src\odin-coordinator.cjs`
|
||||
- Inline `buildConfig` smoke verified `writeDebugSurfaceJson` is false by default and true with `--write-debug-surface-json` or `ODIN_WRITE_DEBUG_SURFACE_JSON=1`.
|
||||
- `rg -n "latest-surface\.json|writeDebugSurfaceJson|ODIN_WRITE_DEBUG_SURFACE_JSON|write-debug-surface-json|fs\.writeFileSync\(path\.join\(config\.stateDir" E:\Projects\Odin\src\odin-coordinator.cjs E:\Projects\Odin\src\odin\config.cjs E:\Projects\Odin\README.md E:\Projects\Odin\docs\transport-shortcut-inventory.md` shows the JSON write is guarded by `config.writeDebugSurfaceJson`.
|
||||
- `cargo test -p idunn-daemon -p odin-core`
|
||||
- `rg -n "health_command: Some|--health-command \[|manual\.command-health|test\.command-exit|__idunn_probe|exit 1|compatibility_health_key|probe_health" E:\Projects\Odin\crates\idunn-daemon\src\main.rs E:\Projects\Odin\crates\odin-core\src\idunn.rs` returned no hits.
|
||||
- `cargo test -p muninn-daemon`
|
||||
- `rg -n -e "health_command" -e "muninn --health --store" -e "command-probe exit semantics" E:\Projects\Odin\crates\muninn-daemon\src\main.rs` returned no hits.
|
||||
- `rg -n "motion_request\.v0|motion_job\.v0|/eve/deck/providers|ravenDeckUrl|compatibilityMechanisms|compatibility lowerings|compatibility export|endpoints:\s*\{|deckBroker|providerDeck" E:\Projects\Vili\scripts\vili-daemon.mjs E:\Projects\Vili\docs\verse-service-contract.md E:\Projects\Vili\README.md` returned no hits.
|
||||
- `git -C E:\Projects\Vili diff --check -- scripts/vili-daemon.mjs docs/verse-service-contract.md` passed with CRLF normalization warnings only.
|
||||
- `node E:\Projects\VoidBot\node_modules\typescript\bin\tsc -b --pretty false`
|
||||
- `node --check E:\Projects\VoidBot\scripts\publish-idunn-rudp-health.cjs`
|
||||
- PowerShell parse check passed for `E:\Projects\VoidBot\scripts\run-gamecult-orchestrator.ps1` after removing the localhost Idunn health default.
|
||||
- `node E:\Projects\VoidBot\scripts\publish-idunn-rudp-health.cjs` with `VOIDBOT_IDUNN_RUDP_HEALTH` unset exits nonzero and reports that `--endpoint` or `VOIDBOT_IDUNN_RUDP_HEALTH` is required.
|
||||
- `Select-String -SimpleMatch` across VoidBot health publisher and orchestrator wrapper found no `127.0.0.1:17870`; the only `localhost default` hit is the refusal message saying no localhost default is assumed.
|
||||
- `rg -n "WEKSA_DAEMON_BASE_URL|daemonBaseUrl|speech-provider/mimo/voicedesign|http://127\.0\.0\.1:8813" E:\Projects\VoidBot\.env.example E:\Projects\VoidBot\packages E:\Projects\VoidBot\apps -g "*.ts" -g "*.example"` returned no hits.
|
||||
- `git -C E:\Projects\VoidBot diff --check -- .env.example packages/core/src/weksa-speech.ts packages/config/src/index.ts apps/worker/src/index.ts apps/bot/src/metame-owner-voice-bridge.ts`
|
||||
- `node --check E:\Projects\Odin\src\odin\probes.cjs`
|
||||
- `node --check E:\Projects\Odin\src\odin\state.cjs`
|
||||
- `node --check E:\Projects\Odin\src\odin\surface.cjs`
|
||||
- Mocked Odin state build with `NODE_PATH=E:\Projects\CultLib\packages` verified Nightwing becomes `active` from provider evidence and Yggdrasil remains `waiting` without provider evidence.
|
||||
- `rg -n "hostChecks|checksFor|tcpCheck|remoteServices|remoteGpu|hostState|hostServices|systemdState|systemctl is-active|nvidia-smi|192\.168\.1\.75|192\.168\.1\.84|192\.168\.1\.72|192\.168\.1\.66|yggdrasil\.gamecult\.org|net\.createConnection" E:\Projects\Odin\src\odin E:\Projects\Odin\README.md E:\Projects\Odin\docs\architecture.md` now only finds the WebSocket bridge's `net.createConnection`.
|
||||
- `cargo test -p idunn-daemon`
|
||||
- `cargo test -p muninn-daemon`
|
||||
- `C:\Users\Meta\.cache\codex-runtimes\codex-primary-runtime\dependencies\node\bin\node.exe scripts/check-syntax.cjs`
|
||||
- `C:\Users\Meta\.cache\codex-runtimes\codex-primary-runtime\dependencies\node\bin\node.exe --check src\stonks-daemon.cjs`
|
||||
- `C:\Users\Meta\.cache\codex-runtimes\codex-primary-runtime\dependencies\node\bin\node.exe --check src\spotiverse-daemon.cjs`
|
||||
- `C:\Users\Meta\.cache\codex-runtimes\codex-primary-runtime\dependencies\node\bin\node.exe --check scripts\export-provider-advertisement.cjs`
|
||||
- `C:\Users\Meta\.cache\codex-runtimes\codex-primary-runtime\dependencies\node\bin\node.exe --check tools\provider-advertisement.mjs` in `E:\Projects\Bifrost`
|
||||
- `C:\Users\Meta\.cache\codex-runtimes\codex-primary-runtime\dependencies\node\bin\node.exe tools\provider-advertisement.mjs print` checked for absence of `compatibility-eve-deck`, `websocket-bridge`, and `ws://192.168.1.66:8797/eve/deck`
|
||||
- `C:\Users\Meta\.cache\codex-runtimes\codex-primary-runtime\dependencies\node\bin\node.exe tools\provider-advertisement.mjs print-binding` checked for absence of `compatibility-eve-deck`, `websocket-bridge`, and `ws://192.168.1.66:8797/eve/deck`
|
||||
- `C:\Users\Meta\.cache\codex-runtimes\codex-primary-runtime\dependencies\node\bin\node.exe --check scripts\render-voidbot-swarm-dashboard.mjs`
|
||||
- `C:\Users\Meta\.cache\codex-runtimes\codex-primary-runtime\dependencies\node\bin\node.exe scripts\render-voidbot-swarm-dashboard.mjs`
|
||||
- `C:\Users\Meta\.cache\codex-runtimes\codex-primary-runtime\dependencies\node\bin\node.exe scripts\export-voidbot-provider-advertisements.mjs --check`
|
||||
- `C:\Users\Meta\.cache\codex-runtimes\codex-primary-runtime\dependencies\node\bin\node.exe ./node_modules/typescript/bin/tsc -p tsconfig.json --noEmit` in `E:\Projects\Heimdall`
|
||||
- `C:\Users\Meta\.cache\codex-runtimes\codex-primary-runtime\dependencies\node\bin\node.exe ..\..\node_modules\typescript\bin\tsc -p tsconfig.json --noEmit` in `E:\Projects\StreamPixels\apps\service`
|
||||
- `C:\Users\Meta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\python.exe -m py_compile src\repixelizer\verse_state.py`
|
||||
- `E:\Projects\repixelizer\.venv\Scripts\python.exe -m pytest tests\test_verse_state.py` passed 2 tests.
|
||||
- `rg -n "future CultCache export|next Idunn transport cut|plain GUI/systemd transport debt|still the live witnesses|does not own queue truth or write CultCache|add a daemon-owned \`.cc\` witness|publish Repixelizer daemon health to Idunn" E:\Projects\repixelizer\docs\verse-service-contract.md E:\Projects\repixelizer\state\map.yaml E:\Projects\Odin\docs\idunn.md` returned no hits; the inventory ledger is excluded so the quoted scan pattern cannot self-match.
|
||||
- `C:\Users\Meta\.cache\codex-runtimes\codex-primary-runtime\dependencies\node\bin\node.exe --check scripts\vili-daemon.mjs`
|
||||
- `C:\Users\Meta\.cache\codex-runtimes\codex-primary-runtime\dependencies\node\bin\node.exe --check scripts\smoke-vili.mjs`
|
||||
- `C:\Users\Meta\.cache\codex-runtimes\codex-primary-runtime\dependencies\node\bin\node.exe --check scripts\weksa-daemon.mjs`
|
||||
- `node node_modules\typescript\bin\tsc -b --pretty false` in `E:\Projects\VoidBot`
|
||||
- Active-code and steering-doc scan for `compatibility-eve-deck`, `/eve/deck/providers`, deck manifests, `ODIN_BASE_URL`, `odinBaseUrl`, seeded deck URLs, and Odin/provider WebSocket snapshot helpers across active repo worktrees; remaining hits are this inventory plus the `gamecult-ops` audit detector. Mimir scratch build copies and state/evidence ledgers were excluded as historical/generated records.
|
||||
- `rg -n "compatibility|fallback" crates\idunn-daemon\src\main.rs src\odin\config.cjs src\odin\interfaces.cjs src\odin-coordinator.cjs` returned no hits.
|
||||
- `dotnet build .\src\Gjallar\Gjallar.csproj`
|
||||
- `dotnet build .\src\Mimir.EveDashboard\Mimir.EveDashboard.csproj` in `E:\Projects\Mimir`
|
||||
- `dotnet build .\src\Mimir.EveBrowserReference\Mimir.EveBrowserReference.csproj` in `E:\Projects\Mimir`
|
||||
- `dotnet build .\src\Mimir.Runtime\Mimir.Runtime.csproj` in `E:\Projects\Mimir`
|
||||
- `dotnet build .\src\Mimir.EveDashboard\Mimir.EveDashboard.csproj --no-restore` in `E:\Projects\Mimir-obs-muninn-branch`
|
||||
- `dotnet build .\src\Mimir.RavenDaemon\Mimir.RavenDaemon.csproj --no-restore` in `E:\Projects\Mimir-obs-muninn-branch`
|
||||
- `dotnet build .\src\Mimir.FensalirDaemon\Mimir.FensalirDaemon.csproj` in `E:\Projects\Mimir-obs-muninn-branch`
|
||||
- `dotnet build .\src\Mimir.EveDashboard\Mimir.EveDashboard.csproj --no-restore` in `E:\Projects\Mimir-raven-build`
|
||||
- `dotnet build .\src\Mimir.Muninn\Mimir.Muninn.csproj --no-restore` in `E:\Projects\Mimir-raven-build`
|
||||
- `dotnet build .\src\Mimir.FensalirDaemon\Mimir.FensalirDaemon.csproj` in `E:\Projects\Mimir-raven-build`
|
||||
- `cargo test -p odin-core idunn_records_round_trip_through_cultmesh`
|
||||
- `cargo test -p muninn-daemon runtime_boundary_records_use_explicit_activation_store_path`
|
||||
- `dotnet build .\src\ymir-daemon\ymir-daemon.csproj` in `E:\Projects\Ymir`
|
||||
- `dotnet test` in `E:\Projects\Ymir` passed 23 tests.
|
||||
- `dotnet run --project .\src\ymir-daemon -- publish-service --path .ymir\service.cc` in `E:\Projects\Ymir`
|
||||
- `rg -n "HttpListener|System\.Net|serve|8877|/simulate/step|/query/overlap|/query/cast|CommandLowerings = Http|remote/debug physics|local HTTP daemon|HTTP endpoints|provider-advertisement|operator-state|eve/operator|GET /health" src README.md docs -S` in `E:\Projects\Ymir`; remaining hits are ordinary prose words, not routes or transport code.
|
||||
- `rg -n "http://127\.0\.0\.1:8877|PostJson|ServiceUrl|OverlapSphereUrl|OverlapCircleUrl|CastCircleUrl|CastSphereUrl|TimeoutMilliseconds|HttpWebRequest|Ymir daemon URL|JSON-over-local-HTTP|local HTTP Ymir|posts JSON to Ymir|hard-coded local URL|/simulate/step|/query/overlap|/query/cast" Assets\Scripts docs Aetheria.State -S` in `E:\Projects\Aetheria` returned no hits.
|
||||
- `rg -n "http://127\.0\.0\.1:8877|PostJson<|public string ServiceUrl|public string OverlapSphereUrl|public string OverlapCircleUrl|public string CastCircleUrl|public string CastSphereUrl|/simulate/step|/query/overlap|/query/cast|HttpListener|Ymir serving physics" E:\Projects\Ymir E:\Projects\Aetheria -S ... -g "!**/Aetheria.State.Verify/Program.cs"` returned no hits; the verifier file intentionally retains old symbols as forbidden strings.
|
||||
- `powershell -ExecutionPolicy Bypass -File .\Aetheria.State\scripts\verify-stage7d-unity-parity.ps1` in `E:\Projects\Aetheria` failed before Ymir checks on existing `MainMenu must read Verse host settings through the typed document handle.`
|
||||
- `dotnet run --project .\Aetheria.State.Verify\Aetheria.State.Verify.csproj --no-restore` in `E:\Projects\Aetheria` compiled far enough to run verifier checks and failed later on existing provider-advertisement state refs in `RequireDaemonVersePublication`; the updated Ymir HTTP guard did not trigger.
|
||||
- `dotnet build .\Assembly-CSharp.csproj /clp:ErrorsOnly /p:UseSharedCompilation=false` in `E:\Projects\Aetheria` failed on existing missing `AetheriaRuntimeEveSurfaceAdvertisement`; one parallel build also hit locked `obj\Debug\UniRx.dll`.
|
||||
- `dotnet build .\Tests.csproj /clp:ErrorsOnly /p:UseSharedCompilation=false` in `E:\Projects\Aetheria` failed on existing missing `AetheriaRuntimeEveSurfaceAdvertisement`.
|
||||
- `node node_modules\typescript\bin\tsc -p apps\service\tsconfig.json --noEmit` in `E:\Projects\StreamPixels`
|
||||
- StreamPixels verse-state smoke with `STREAMPIXELS_CULTCACHE_PATH=.streampixels-data\cultcache\codex-smoke.service.cc` wrote the store through `publishStreamPixelsVerseState`.
|
||||
- `inspectCultCacheBytes` on the StreamPixels smoke store found `streampixels.command_boundary.v1`, `streampixels.transport_profile.v1`, `gamecult.eve.provider_advertisement.v1`, and `idunn.daemon_health.v1`.
|
||||
- `pnpm install` in `E:\Projects\Heimdall` restored local links and updated `pnpm-lock.yaml`; pnpm exited nonzero only because build-script approval for `esbuild@0.27.7` is enforced in this environment.
|
||||
- `node node_modules\typescript\bin\tsc -p tsconfig.json --noEmit` in `E:\Projects\Heimdall`
|
||||
- `node node_modules\vitest\vitest.mjs run --config vitest.config.ts tests/verse-runtime.test.ts` in `E:\Projects\Heimdall`
|
||||
- `node node_modules\vitest\vitest.mjs run --config vitest.config.ts` in `E:\Projects\Heimdall` passed 29 tests.
|
||||
- Heimdall verse-state smoke with `GC_ACCESS_CULTCACHE_PATH=.heimdall-data\cultcache\codex-smoke.service.cc` wrote the store through `publishHeimdallVerseState`.
|
||||
- `inspectCultCacheBytes` on the Heimdall smoke store found `gamecult.eve.provider_advertisement.v1`, `heimdall.command_boundary.v1`, `heimdall.transport_profile.v1`, and `idunn.daemon_health.v1`.
|
||||
- `rg -n -e 'STORE_FORMAT_VERSION' -e 'CultCacheRecord' -e 'writeCultCacheSnapshot' -e 'mkdir, rename, rm, writeFile' -e 'from "@msgpack/msgpack"' -e 'encode\(\[STORE_FORMAT_VERSION' -e 'schemaId: "idunn\.daemon_health"' E:\Projects\Heimdall\src\verse-state.ts E:\Projects\Heimdall\tests\verse-runtime.test.ts` returned no hits.
|
||||
- Active-service scan for `STORE_FORMAT_VERSION`, `writeCultCacheSnapshot`, `CultCacheRecord`, and hand-built schema catalog JSON across `E:\Projects` found no remaining daemon shortcut writers; remaining hits are CultCache library implementations/tests, vendored CultCache sources, and historical clean-room/rollback clones.
|
||||
- `dotnet build .\src\Mimir.EveDashboard\Mimir.EveDashboard.csproj --no-restore` in `E:\Projects\Mimir`
|
||||
- `rg -n "RemoteDashboardProvider|YggdrasilStreamPixelsProvider|--provider|External EveDeck|registered WebSocket endpoint|id\|title\|ws-url|TCP/WebSocket over LAN|ssh-tunnel|yggdrasil\.streampixels\.edge" E:\Projects\Mimir\src\Mimir.EveDashboard\Program.cs` returned no hits.
|
||||
- `node --check web\surface.js` in `E:\Projects\Eve`
|
||||
- `rg -n "mimir-eve-deck|openVoidBot|VoidBot Live|Mimir broker error|8795/eve/deck" E:\Projects\Eve\web\surface.js E:\Projects\Eve\README.md E:\Projects\Eve\docs\renderer-parity.md E:\Projects\Eve\docs\cultmesh-streaming-ui-framework.md` returned no hits.
|
||||
- `powershell -ExecutionPolicy Bypass -File .\scripts\build-android.ps1` in `E:\Projects\Eve` built `artifacts\android\eve-debug.apk`; the script printed two `findstr` command-not-found diagnostics plus Java/Kotlin deprecation warnings, but exited 0.
|
||||
- `Select-String` scan for `192.168.1.66:8795`, `192.168.1.66:8796`, `URI.create("ws://192.168.1.66`, `waiting for Mimir`, `waiting for receiver`, `Mimir broker health`, `monkey -p org.gamecult.eve`, `mimir-eve-deck`, `openVoidBot`, and `VoidBot Live` across the touched Eve Android/browser/docs files returned no hits.
|
||||
- `rg -n "ws://127\.0\.0\.1|ws://192\.168\.1\.66|8792/stream|8793/eve/camera|8794/eve/mic|8795/eve/deck|8797/eve/deck|awaiting Mimir|waiting for Mimir" E:\Projects\Eve\Sources E:\Projects\Eve\README.md E:\Projects\Eve\docs\cultmesh-streaming-ui-framework.md E:\Projects\Eve\docs\renderer-parity.md` returned no hits.
|
||||
- `git -C E:\Projects\Eve diff --check -- Sources/EVEViewController.mm README.md` passed with CRLF normalization warnings only.
|
||||
- `dotnet build .\src\Mimir.VerseRecorder\Mimir.VerseRecorder.csproj --no-restore` in `E:\Projects\Mimir`
|
||||
- `dotnet run --project .\src\Mimir.VerseRecorder\Mimir.VerseRecorder.csproj --no-build -- --seconds 0.01` in `E:\Projects\Mimir` exits 2 and prints that `--url` is required from an explicit CultMesh/Odin-discovered sensor subscription endpoint.
|
||||
- `C:\Users\Meta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\python.exe -m py_compile tools\nightwing_typed_witness_publisher.py` in `E:\Projects\Mimir`
|
||||
- `C:\Users\Meta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\python.exe tools\nightwing_typed_witness_publisher.py --once` in `E:\Projects\Mimir` exits 2 and reports required `--url`.
|
||||
- `Select-String` scan for `ws://127.0.0.1:8796/eve/periwinkle/subscribe`, `ws://192.168.1.66:8796/eve/periwinkle`, `parser.add_argument("--url", default`, and `ParseString(args, "--url", "ws://` across `Mimir.VerseRecorder`, `nightwing_typed_witness_publisher.py`, and `start-nightwing-move-tracking.ps1` returned no hits.
|
||||
- `powershell -NoProfile -ExecutionPolicy Bypass -File E:\Projects\Mimir\scripts\start-nightwing-move-tracking.ps1 -DryRun` exits 1 and reports required `-WitnessUrl` / `MIMIR_NIGHTWING_WITNESS_URL`.
|
||||
- `powershell -NoProfile -ExecutionPolicy Bypass -File E:\Projects\Mimir\scripts\start-nightwing-move-tracking.ps1 -DryRun -WitnessUrl ws://example.invalid:8796/eve/periwinkle -SubscribeUrl ws://example.invalid:8796/eve/periwinkle/subscribe` shows the receiver, recorder, and Nightwing publisher using only the supplied URLs.
|
||||
- `Select-String` scan for `StarfireHost`, `192\.168\.1\.66`, `ws://127\.0\.0\.1:\$Port`, `witnessUrl = "ws://`, and `subscribeUrl = "ws://` in `E:\Projects\Mimir\scripts\start-nightwing-move-tracking.ps1` returned no hits.
|
||||
- `C:\Users\Meta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\python.exe -m py_compile scripts\nightwing-visible-ops-tui.py scripts\nightwing-visible-ops-fb.py` in `E:\Projects\gamecult-ops`
|
||||
- `C:\Users\Meta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\python.exe scripts\nightwing-visible-ops-tui.py --help` and `... scripts\nightwing-visible-ops-fb.py --help` show required `--url`.
|
||||
- `C:\Users\Meta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\python.exe scripts\nightwing-visible-ops-tui.py --tty /dev/null --provider odin.providers` and `... scripts\nightwing-visible-ops-fb.py --frames 1` exit 2 without `--url`.
|
||||
- `Select-String` scan for `ws://192.168.1.66:8797/eve/deck`, `GAMECULT_VISIBLE_OPS_URL:-ws`, and `--url", default` across `gamecult-ops` visible-ops lowerers returned no hits.
|
||||
- Shell syntax for `gamecult-ops` visible-ops launchers was not executed locally because the only `bash.exe` is the WSL shim and WSL is unavailable in this environment; the changed shell blocks were inspected and the removed LAN default was scanned.
|
||||
- `Select-String` scan for `ws://192.168.1.66:8797/eve/deck`, `ws://127.0.0.1:8796/eve/deck`, `known provider seed`, `seed deck URLs`, `defaulting to ws://192`, and `compatibility HTTP/WebSocket deck bridge` across `E:\Projects\Odin\README.md`, `docs\gjallar.md`, and `docs\architecture.md` returned no hits after the architecture wording update.
|
||||
- PowerShell parse checks passed for `scripts\start-starfire-sleipnir.ps1`, `scripts\restart-starfire-muninn.ps1`, `scripts\restart-nightwing-muninn.ps1`, `scripts\restart-muninn.ps1`, `scripts\deploy-nightwing-gjallar.ps1`, `E:\Projects\weksa\scripts\start-weksa-daemon.ps1`, and `E:\Projects\weksa\scripts\restart-weksa-daemon.ps1`.
|
||||
- `cargo test -p idunn-daemon` passed 16 tests after replacing Idunn's embedded Gjallar Starfire endpoint steering text with configured-endpoint language.
|
||||
- Active-code scan across Odin, `gamecult-ops`, Weksa, Vili, VoidBot, Eve, and Mimir for `192.168.1.66:17871`, `127.0.0.1:17871`, Starfire Odin deck URLs, visible-ops URL defaults, `ODIN_BASE_URL`, and `/eve/deck/providers` now returns only this inventory, the `gamecult-ops` audit detector, and Odin README local debug endpoint documentation.
|
||||
- PowerShell parse checks passed for `scripts\restart-starfire-muninn.ps1`, `scripts\restart-nightwing-muninn.ps1`, `scripts\restart-muninn.ps1`, `scripts\health-muninn.ps1`, and `scripts\health-starfire-muninn.ps1` after removing the `192.168.1.66:17870` health default.
|
||||
- `cargo test -p idunn-daemon` passed 16 tests after replacing Idunn's embedded Raven Muninn health endpoint steering text with configured-endpoint language and adding negative assertions against `192.168.1.66:17870`.
|
||||
- `cargo test -p idunn-daemon` passed 17 tests after replacing Idunn's swarm surgery plan source-address lore (`10.77.0.x` and `127.0.0.1` acceptance text) with daemon-id/configured-route language and flipping the tests to negative assertions against those endpoint literals.
|
||||
- `rg -n "10\.77\.0\.[0-9]+|192\.168\.1\.66:17870|from 127\.0\.0\.1|from 10\.77|Idunn accepts .* from" E:\Projects\Odin\crates\idunn-daemon\src\main.rs` now finds only negative assertions and configured-route/source-address-folklore wording, not active plan guidance.
|
||||
- `rg -n -e '192\.168\.1\.66:17870' -e '--idunn-rudp-health 192\.168\.1\.66:17870' E:\Projects\Odin E:\Projects\gamecult-ops ...` now finds only this inventory's cut note, negative Idunn assertions, and unrelated deployed-service env examples for Yggdrasil service health endpoints.
|
||||
- PowerShell parse checks passed for `scripts\start-odin.ps1` and `scripts\restart-odin.ps1` after removing the localhost Idunn health default.
|
||||
- `powershell.exe -NoProfile -ExecutionPolicy Bypass -File E:\Projects\Odin\scripts\start-odin.ps1` with `ODIN_IDUNN_RUDP_HEALTH` and `IDUNN_RUDP_HEALTH` unset exits nonzero before starting Odin and reports that an explicit Idunn RUDP health endpoint is required.
|
||||
- `Select-String` across Odin, Weksa, Vili, and `gamecult-ops` script targets plus Odin README/Muninn docs for `127.0.0.1:17870`, `10.77.0.2:17870`, `192.168.1.66:17870`, and localhost-shaped `IdunnRudpHealth` defaults returned no hits.
|
||||
- PowerShell parse check passed for `E:\Projects\Stonks\scripts\start-stonks.ps1` after removing localhost Idunn/Odin RUDP defaults.
|
||||
- `powershell.exe -NoProfile -ExecutionPolicy Bypass -File E:\Projects\Stonks\scripts\start-stonks.ps1` with `STONKS_IDUNN_RUDP_HEALTH` and `IDUNN_RUDP_HEALTH` unset exits nonzero before starting Stonks and reports that an explicit Idunn RUDP health endpoint is required.
|
||||
- `Select-String` across Stonks launcher, README, and source for `127.0.0.1:17870`, `127.0.0.1:17871`, localhost-shaped Idunn/Odin RUDP parameter defaults, and the old `--idunn-rudp-health 127.0.0.1:17870` recipe returned no hits.
|
||||
- Active publication-default scan across Odin, Stonks, VoidBot, Weksa, Vili, and `gamecult-ops` launch scripts plus Odin/Stonks/Muninn README surfaces found no remaining localhost-shaped Idunn health parameter defaults or old `--idunn-rudp-health 127.0.0.1:17870` launch recipe. Remaining local `127.0.0.1:17870` references document Idunn's own ingress bind, not child-daemon publication defaults.
|
||||
- `node --check E:\Projects\Spotiverse\src\spotiverse-daemon.cjs`
|
||||
- `node --check E:\Projects\Spotiverse\scripts\export-provider-advertisement.cjs`
|
||||
- PowerShell parse check passed for `E:\Projects\Spotiverse\scripts\start-spotiverse.ps1` after adding explicit `-WriteDebugJson` / `-ReadDebugJson` switches.
|
||||
- Spotiverse no-debug smoke started the daemon with a temporary state dir and no debug flags, then verified `latest-surface.json` and `latest-spotify-state.json` were not written.
|
||||
- Spotiverse debug smoke started the daemon with `--write-debug-json`, then verified `latest-surface.json` and `latest-spotify-state.json` were written only on that explicit path.
|
||||
- `rg -n -e 'loadLatestSnapshot\(\) \|\|' -e 'Debug witnesses: `latest-surface\.json`, `latest-spotify-state\.json`, and' -e 'currentDebugMirror' -e 'Promote `latest-spotify-state\.json`' E:\Projects\Spotiverse\src\spotiverse-daemon.cjs E:\Projects\Spotiverse\README.md E:\Projects\Spotiverse\docs E:\Projects\Spotiverse\scripts\export-provider-advertisement.cjs` now finds only Heimdall-account and command-receipt debug mirrors, not the demoted surface/playback JSON mirrors.
|
||||
- Follow-up active JSON shortcut scan for `latest-surface.json`, `latest-spotify-state.json`, provider/operator JSON mirrors, deck provider manifests, `ODIN_BASE_URL`, `WEKSA_DAEMON_BASE_URL`, and compatibility deck transports across Odin, Spotiverse, Stonks, Vili, Weksa, VoidBot, Mimir, and Eve found only debug-gated writers (`Odin`, `Vili`, `Weksa`, `Spotiverse`) plus static fixture references. The relevant code contexts all gate JSON output behind explicit debug flags.
|
||||
- PowerShell parse checks passed for `scripts\deploy-nightwing-gjallar.ps1`, `scripts\restart-vili.ps1`, and `scripts\health-nightwing-muninn.ps1` after removing the `10.77.0.2:17870` defaults.
|
||||
- `cargo test -p idunn-daemon` passed 16 tests and `cargo test -p muninn-daemon` passed 141 tests after replacing remaining real Idunn endpoint fixtures/steering text with configured-endpoint language.
|
||||
- Bash syntax checks for `gamecult-ops` deploy scripts could not run locally because `bash.exe` is the WSL shim and `/bin/bash` is unavailable in this environment.
|
||||
- `rg -n -e '10\.77\.0\.2:17870' -e '192\.168\.1\.66:17870' E:\Projects\Odin E:\Projects\gamecult-ops ...` now finds only this inventory's cut notes and negative Idunn assertions.
|
||||
- PowerShell parse check passed for `scripts\restart-muninn.ps1` after removing Raven media/OBS/OBS catalog Starfire LAN defaults.
|
||||
- `cargo test -p muninn-daemon` passed 141 tests after the Raven Muninn launcher media-target cut.
|
||||
- `Select-String` scan of `scripts\restart-muninn.ps1` confirmed no `192.168.1.66` media/OBS endpoint remains; media target comes from `MUNINN_MEDIA_TARGET_HOST`, OBS target from `MUNINN_OBS_TARGET_HOST` or the media target, and OBS catalog target from `MUNINN_OBS_CATALOG_RUDP_TARGET` only.
|
||||
- PowerShell parse check passed for `scripts\activate-muninn-raven-av-srt.ps1`; invoking it with `MUNINN_MEDIA_TARGET_HOST` and `MUNINN_OBS_TARGET_HOST` cleared fails before remote upload with the required explicit-target error.
|
||||
- `rg -n -e '\$TargetHost = "10\.77\.0\.2"' -e '\$ObsTargetHost = "10\.77\.0\.2"' -e 'muninn\.exe --health.*fallback' -e 'fallback/manual proof' E:\Projects\Odin\scripts\activate-muninn-raven-av-srt.ps1 E:\Projects\Odin\docs\muninn.md E:\Projects\Odin\docs\idunn.md` returned no hits.
|
||||
- `cargo test -p muninn-daemon` passed 142 tests after removing Muninn's baked media target defaults.
|
||||
- `rg -n "10\.77\.0\.2|192\.168\.1\.66" E:\Projects\Odin\crates\muninn-daemon\src\main.rs` returned no hits.
|
||||
- `rg -n -e '--health-command' -e 'command probe' -e 'compatibility probe' -e 'fallback' -e 'exit 0' -e 'exit 1' E:\Projects\Odin\src\Idunn\README.md` returned no hits.
|
||||
- `rg -n -e "compatibility command" -e "command-probe fallback" -e "command probe remains" -e "local compatibility command" -e "compatibility probes" -e "fallback proof" -e "fallback health" -e "health/deck" -e "compatibility health" E:\Projects\Odin\docs\idunn.md E:\Projects\Odin\src\Idunn\README.md` returned no hits.
|
||||
- `node --check E:\Projects\Odin\src\odin\surface.cjs`
|
||||
- `node --check E:\Projects\Odin\src\odin\layout.cjs`
|
||||
- `rg -n "legacy-dashboard-nodes|legacy-node-groups|props\?\.compatibility" E:\Projects\Odin\src\odin E:\Projects\Odin\README.md E:\Projects\Odin\docs -g '!node_modules'` returned no hits.
|
||||
- PowerShell parse check passed for `scripts\start-starfire-wireguard-elevated.ps1` after replacing the baked Nightwing endpoint fallback and Starfire LAN diagnostic address with explicit config.
|
||||
- `rg -n "192\.168\.1\.(66|75|84)|10\.77\.0\.2:17870" E:\Projects\Odin\scripts E:\Projects\Odin\crates E:\Projects\Odin\src E:\Projects\Odin\docs E:\Projects\Odin\README.md` now finds only this inventory and negative Idunn assertions.
|
||||
- PowerShell parse check passed for `E:\Projects\gamecult-ops\scripts\check-raven-connectivity.ps1` after demoting Raven LAN ICMP to optional explicit diagnostics.
|
||||
- `rg -n "192\.168\.1\.(66|75|84)...|/eve/deck/providers|ODIN_BASE_URL|WEKSA_DAEMON_BASE_URL" E:\Projects\gamecult-ops ...` now finds only inventory/runbook history plus the audit detector string.
|
||||
- PowerShell parse checks passed for the active Mimir branch proof scripts after replacing baked Starfire/Raven/Nightwing endpoints with `MIMIR_*` environment configuration and fail-closed guards.
|
||||
- `dotnet build .\src\Mimir.EveRelay\Mimir.EveRelay.csproj` passed in `E:\Projects\Mimir-raven-build` and `E:\Projects\Mimir-obs-muninn-branch`; both builds restored missing project assets and reported only the pre-existing CA2024 analyzer warning.
|
||||
- `rg -n "192\.168\.1\.(66|75|84)...|/eve/deck/providers|ODIN_BASE_URL|WEKSA_DAEMON_BASE_URL" E:\Projects\Mimir-obs-muninn-branch\src ... E:\Projects\Mimir-raven-build\scripts ...` returned no hits after the branch proof/relay cut.
|
||||
- `E:\Projects\Mimir\docs\neighbor-sender-setup.md` was updated so sender SSH and SRT receiver routes are configured placeholders rather than old LAN addresses.
|
||||
- `C:\Users\Meta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\python.exe -m py_compile` passed for both active Mimir branch `tools\nightwing_typed_witness_publisher.py` files.
|
||||
- `C:\Users\Meta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\python.exe E:\Projects\Mimir-raven-build\tools\nightwing_typed_witness_publisher.py --once` exits 2 and reports required `--url` / `MIMIR_WITNESS_URL`.
|
||||
- `cmake --build E:\Projects\Mimir-obs-muninn-branch\native\obs_stem_source\build --target muninn_rudp_url_tests --config Release` and the resulting `muninn_rudp_url_tests.exe` passed after replacing real LAN fixture URLs with documentation addresses.
|
||||
- `cmake --build E:\Projects\Mimir-obs-muninn-branch\native\obs_stem_source\build --target mimir_obs_stem_source --config Release` passed after removing OBS Muninn source endpoint defaults.
|
||||
- Corrected `Get-ChildItem | Select-String` scan across `E:\Projects\Mimir`, `E:\Projects\Mimir-raven-build`, and `E:\Projects\Mimir-obs-muninn-branch` docs/config for `192.168.1.66`, `192.168.1.75`, `192.168.1.84`, Starfire WebSocket/CultNet URLs, `TargetHost 192.168.1.66`, and `audio-field.example.json` Raven address returned no hits.
|
||||
- Active-code scan across Odin, `gamecult-ops`, Vili, Weksa, VoidBot, Eve, Mimir, and active Mimir branch source/script/tool/native paths for the old LAN/WireGuard endpoint families, `/eve/deck/providers`, `ODIN_BASE_URL`, and `WEKSA_DAEMON_BASE_URL` now returns only negative Idunn assertions, a Weksa state-map note, and the `gamecult-ops` audit detector.
|
||||
- Follow-up active-code scan after the Vili sidecar/health cuts across Odin, `gamecult-ops`, Vili, Weksa, VoidBot, Eve, Mimir, and active Mimir branch source/script/tool/native paths for the old LAN/WireGuard endpoint families, `/eve/deck/providers`, `ODIN_BASE_URL`, and `WEKSA_DAEMON_BASE_URL` still returns only negative Idunn assertions, a Weksa state-map note, and the `gamecult-ops` audit detector.
|
||||
- Broad active-code scan after the Weksa/Odin JSON cuts for old LAN/WireGuard endpoint families, deck provider/manifest shortcuts, `ODIN_BASE_URL`, `WEKSA_DAEMON_BASE_URL`, `latest-surface.json`, and service JSON sidecar names returns only gated debug writers/docs, checked-in fixtures, negative Idunn assertions, the `gamecult-ops` audit detector, and this inventory.
|
||||
- `node --check E:\Projects\Odin\src\hermodr-daemon.cjs` passed after moving Hermodr's Odin input from default HTTP/JSON export to the Odin CultMesh store.
|
||||
- PowerShell parse checks passed for `scripts\start-hermodr.ps1` and `scripts\restart-hermodr.ps1` after replacing `-OdinHttpBase` / `-OdinWsBase` with `-OdinCachePath` / `-OdinSurfaceKey`.
|
||||
- Hermodr CultMesh smoke created a temporary Odin store containing `surface:gamecult.network.status`, started `src\hermodr-daemon.cjs` with `--odinCachePath`, and verified `/hermodr/verse/catalog` returned the typed `smoke.provider` surface.
|
||||
- `rg -n "odinHttpBase|odinWsBase|OdinHttpBase|OdinWsBase|latest-surface\.json|http://127\.0\.0\.1:8797|ws://127\.0\.0\.1:8797|hermodr-http-to-odin-cultmesh-bridge"` across Hermodr source and launchers returned no hits.
|
||||
- `dotnet build .\src\Mimir.Well\Mimir.Well.csproj` and `dotnet build .\src\Mimir.VerseRecorder\Mimir.VerseRecorder.csproj` passed in both `E:\Projects\Mimir-obs-muninn-branch` and `E:\Projects\Mimir-raven-build` after removing the localhost Periwinkle defaults.
|
||||
- PowerShell parser checks passed for both active Mimir branch `scripts\start-online-verse-daemons.ps1` files.
|
||||
- `dotnet run --project .\src\Mimir.Well\Mimir.Well.csproj --no-build -- --seconds 0.01` now exits before startup in both active Mimir branches and reports that `--publish-url` / `MIMIR_WELL_PUBLISH_URL` is required.
|
||||
- `dotnet run --project .\src\Mimir.VerseRecorder\Mimir.VerseRecorder.csproj --no-build -- --seconds 0.01` now exits before startup in both active Mimir branches and reports that `--url` / `MIMIR_VERSE_RECORDER_URL` is required.
|
||||
- `start-online-verse-daemons.ps1 -DryRun` with only `MIMIR_WITNESS_URL` set now fails in both active Mimir branches with the required `-SubscribeUrl` / `MIMIR_VERSE_RECORDER_URL` message; explicit dry-runs with example Well/Recorder URLs route only the supplied `example.invalid` endpoints.
|
||||
- `rg -n "ws://127\.0\.0\.1:8796/eve/periwinkle|ws://127\.0\.0\.1:8798/eve/periwinkle|MIMIR_WELL_PUBLISH_URL|MIMIR_VERSE_RECORDER_URL"` across the touched active Mimir branch Well, VerseRecorder, and online-daemon launcher files now finds only the explicit environment-variable controls and fail-closed messages, not the deleted endpoint defaults.
|
||||
- `node node_modules\typescript\bin\tsc -b --pretty false` passed in `E:\Projects\VoidBot` after deleting the Weksa RUDP localhost default.
|
||||
- VoidBot config smoke verified `REPO_FACE_WEKSA_SPEECH_ENABLED=false` accepts a blank `WEKSA_CULTMESH_COMMAND_URI`, while `REPO_FACE_WEKSA_SPEECH_ENABLED=true` rejects a blank URI with the explicit Odin/CultMesh-discovered command URI message.
|
||||
- `rg -n -e 'ODIN_BASE_URL' -e 'WEKSA_CULTMESH_COMMAND_URI=.*127\.0\.0\.1' -e 'weksa.*127\.0\.0\.1:8813' E:\Projects\VoidBot\.env.example E:\Projects\VoidBot\packages\config\src E:\Projects\VoidBot\apps E:\Projects\VoidBot\packages\core\src` returned no live shortcut hits.
|
||||
- `cargo test -p idunn-daemon` passed 17 tests after replacing the remaining Weksa generated-plan text with Odin/CultMesh command URI language.
|
||||
- `rg -n "rudp://127\.0\.0\.1:8813|speech_provider\.mimo\.voicedesign now arrives on rudp://|VoiceDesign commands arrive on `rudp://"` across Idunn's daemon source plus Weksa docs/scripts/README returned no hits after the Weksa contract wording cut.
|
||||
- Aetheria's `Aetheria.Rts.Web\Electron\main.ts` localhost RUDP default was inspected and left in place because it is the Electron shell's local-daemon launch path when no remote daemon endpoint is configured, not a provider discovery or command shortcut. `AETHERIA_RTS_PEER_CULTMESH_ENDPOINTS` localhost references are smoke/test fixtures.
|
||||
- `rg -n -e '192\.168\.1\.75' -e '192\.168\.1\.66' -e '8795/eve/deck' -e '8796/eve/periwinkle' -e 'http://192\.168\.' -e 'ws://192\.168\.' E:\Projects\gamecult-ops\runbooks\nightwing-cultmesh-worker-tunnel.md E:\Projects\gamecult-ops\inventory.md` returned no hits after demoting the old Nightwing/Starfire LAN recipes.
|
||||
- `git -C E:\Projects\gamecult-ops diff --check -- runbooks\nightwing-cultmesh-worker-tunnel.md inventory.md` passed with CRLF normalization warnings only.
|
||||
- `node --check tools\provider-advertisement.mjs` passed in `E:\Projects\Bifrost` after replacing the local Odin publication endpoint example.
|
||||
- `node tools\provider-advertisement.mjs --help` now shows `$env:BIFROST_ODIN_CULTMESH_URI="cultmesh://odin/rendezvous/provider-catalog"; node tools/provider-advertisement.mjs publish-odin` instead of a localhost endpoint.
|
||||
- `rg -n "127\.0\.0\.1:17871|BIFROST_ODIN_CULTMESH_RUDP" E:\Projects\Bifrost\tools\provider-advertisement.mjs E:\Projects\Bifrost\docs\verse-service-contract.md` returns no active publication path outside removed-config guards and this inventory.
|
||||
- `node --check tools\provider-advertisement.mjs` passed in `E:\Projects\Bifrost` after making HTTP health/readiness probes explicit, and `node tools\provider-advertisement.mjs print-surface --generated-at 2026-07-03T00:00:00.000Z` reports `status: "unprobed"` with health/ready `not configured` when no probe URLs are supplied.
|
||||
- `Select-String` over `E:\Projects\Bifrost\tools\provider-advertisement.mjs` now finds no implicit `fetchProbe("http://127.0.0.1:5080...")`; only the explicit `BIFROST_HEALTH_URL` / `BIFROST_READY_URL` controls and help example remain.
|
||||
- `node --check tools\agent-transport.mjs`, `node --check tools\dispatch-agent-requests.mjs`, `node --check tools\governance-threads.mjs`, and `node --check tools\bifrost-bridge.mjs` passed in `E:\Projects\Bifrost` after deleting the unreceipted/ungated recovery paths.
|
||||
- `DOTNET_ROLL_FORWARD=Major dotnet test tests\Bifrost.Web.Tests\Bifrost.Web.Tests.csproj --filter FullyQualifiedName~BridgeCliTests --no-restore` passed 30 focused Bifrost bridge CLI tests after moving successful fixtures onto a fake bridge receipt/gate endpoint and leaving old flags as rejection coverage.
|
||||
- `node --check E:\Projects\Spotiverse\src\spotiverse-daemon.cjs` and a PowerShell parser check for `E:\Projects\Spotiverse\scripts\start-spotiverse.ps1` passed after replacing the Spotiverse README's local Odin publication recipe.
|
||||
- `rg -n "SPOTIVERSE_ODIN_CULTMESH_RUDP|127\.0\.0\.1:17871" E:\Projects\Spotiverse\README.md E:\Projects\Spotiverse\.env.example E:\Projects\Spotiverse\scripts\start-spotiverse.ps1 E:\Projects\Spotiverse\src\spotiverse-daemon.cjs E:\Projects\Spotiverse\docs\verse-service-contract.md` returned no active raw Odin publication path outside removed-config guards.
|
||||
- `node --check E:\Projects\Spotiverse\src\spotiverse-daemon.cjs` passed after removing the localhost Heimdall peer default, and `rg -n -e 'HEIMDALL_BASE_URL=http://127\.0\.0\.1:4100' -e 'http://127\.0\.0\.1:4100/v1/oauth/spotify/callback' -e 'process\.env\.HEIMDALL_BASE_URL \|\| "http://127\.0\.0\.1:4100"' E:\Projects\Spotiverse\README.md E:\Projects\Spotiverse\.env.example E:\Projects\Spotiverse\src\spotiverse-daemon.cjs` returned no hits.
|
||||
- `node --check E:\Projects\Spotiverse\src\spotiverse-daemon.cjs` and `node --check E:\Projects\Spotiverse\scripts\export-provider-advertisement.cjs` passed after promoting Spotiverse command requests/receipts to typed CultMesh documents.
|
||||
- Spotiverse CultMesh command smoke started the daemon with a temporary `.ccmp`, wrote `spotiverse.command/requests/codex-smoke-command` from a separate CultMesh node, and observed `spotiverse.command/receipts/codex-smoke-command` with `source: "cultmesh"` and the expected unlinked-Spotify rejection.
|
||||
- `rg -n "future CultMesh|commandFramesDelegateTo|/eve/deck/providers|HTTP/WebSocket frame|provider manifest|http-provider-manifest|compatibility-eve-deck" E:\Projects\Spotiverse\README.md E:\Projects\Spotiverse\docs\verse-service-contract.md E:\Projects\Spotiverse\docs\architecture.md E:\Projects\Spotiverse\scripts\export-provider-advertisement.cjs E:\Projects\Spotiverse\src\spotiverse-daemon.cjs` returned no hits.
|
||||
- `rg -n "future CultMesh|commandFramesDelegateTo|/eve/deck/providers|HTTP/WebSocket frame|provider manifest|http-provider-manifest|compatibility-eve-deck" E:\Projects\Spotiverse\AGENTS.md E:\Projects\Spotiverse\README.md E:\Projects\Spotiverse\docs\verse-service-contract.md E:\Projects\Spotiverse\docs\architecture.md E:\Projects\Spotiverse\scripts\export-provider-advertisement.cjs E:\Projects\Spotiverse\src\spotiverse-daemon.cjs` returned no hits after the local agent-note cleanup.
|
||||
- `rg -n "future CultCache|future CultMesh|future \\.cc|reserved witness path|future durable witnesses|future CultMesh publication" E:\Projects\repixelizer\README.md E:\Projects\repixelizer\docs\repixelizer-witness-contract.md` returned no hits.
|
||||
- `node node_modules\typescript\bin\tsc -p apps\service\tsconfig.json --noEmit` passed in `E:\Projects\StreamPixels` after adding `streampixels.command_request.v1` / `streampixels.command_receipt.v1` and the service command poller.
|
||||
- StreamPixels CultMesh command smoke wrote `streampixels.command/requests/codex-smoke` from a separate CultMesh node, ran `pollStreamPixelsCommandRequests`, and observed `streampixels.command/receipts/codex-smoke` with `source: "cultmesh"` and `status: "accepted"`.
|
||||
- `node node_modules\vitest\vitest.mjs run --config vitest.config.ts apps/service/src/app.test.ts` passed 28 tests in `E:\Projects\StreamPixels`.
|
||||
- `rg -n "future CultMesh|future CultCache|future CultMesh catalog boot|future CultMesh command" E:\Projects\Aetheria\docs\aetheria-perfect-machine-map.md E:\Projects\Aetheria\Packages\org.gamecult.aetheria.state\package.json` returned no hits.
|
||||
- `Get-Content E:\Projects\Aetheria\Packages\org.gamecult.aetheria.state\package.json | ConvertFrom-Json` parsed successfully after the package description update.
|
||||
- `rg -n -e 'compatibility deck' -e 'compatibility transports' -e '10\.77\.0\.2:1787(0|1)' E:\Projects\Odin\README.md E:\Projects\Odin\docs\architecture.md E:\Projects\Mimir-raven-build\README.md E:\Projects\Mimir-obs-muninn-branch\README.md E:\Projects\StreamPixels\docs\configuration.md` returned no hits.
|
||||
- `rg -n "tries `ws://127\.0\.0\.1:8792/stream` first|falls back to\s*`ws://<receiver-host>:8792/stream`|ws://127\.0\.0\.1:8792/stream|10\.77\.0\.2:17870" E:\Projects\Mimir-raven-build\docs\eve-program-output.md E:\Projects\Mimir-obs-muninn-branch\docs\eve-program-output.md E:\Projects\Heimdall\notes\fresh-workspace-handoff.md` returned no hits.
|
||||
- `rg -n "EVE_STREAM_URLS|ws://127\.0\.0\.1:8792/stream|ws://<receiver-host>:8792/stream" E:\Projects\Eve\Sources\EVEViewController.mm E:\Projects\Mimir-raven-build\docs\eve-program-output.md E:\Projects\Mimir-obs-muninn-branch\docs\eve-program-output.md` confirmed Eve reads explicit `EVE_STREAM_URLS` and the Mimir docs no longer preserve endpoint candidates.
|
||||
- `rg -n "192\.168\.1\.84|local inference box|Qwen box|madman's lullaby@192\.168\.1\.84" E:\Projects\gamecult-ops\runbooks\voidbot-local-stack-and-reindex.md E:\Projects\gamecult-ops\inventory.md E:\Projects\VoidBot\README.md E:\Projects\VoidBot\scripts -g '!**/node_modules/**'` returned no hits after the offsite-backup route cleanup.
|
||||
- PowerShell parser check passed for `E:\Projects\VoidBot\scripts\install-offsite-backup-task.ps1`.
|
||||
- `dotnet build .\src\Mimir.FensalirDaemon\Mimir.FensalirDaemon.csproj` passed in both `E:\Projects\Mimir-obs-muninn-branch` and `E:\Projects\Mimir-raven-build` after demoting Fensalir's local Eve/CultMesh WebSocket routes to debug lowerings.
|
||||
- PowerShell parser checks passed for both active Mimir branch `scripts\start-fensalir-daemon.ps1` launchers.
|
||||
- `rg -n -e 'bridge: ws://127\.0\.0\.1:8799/eve/deck' -e 'speaks a CultNet-shaped WebSocket Eve provider' -e 'speaking Eve/CultMesh' -e 'CultNet/WebSocket transport' -e 'cultnet-websocket' -e 'MessagePack WebSocket provider' -e 'Bridge route:' -e 'Eve provider:'` across both active Mimir branch Fensalir docs, daemon sources, and launchers returned no hits.
|
||||
- `E:\Projects\Heimdall\tests\verse-runtime.test.ts` no longer uses the live-looking `10.77.0.2:17870` Idunn health fixture; the fixture uses documentation address `203.0.113.10:17870` while preserving endpoint parse and publication behavior.
|
||||
- `node node_modules\vitest\vitest.mjs run --config vitest.config.ts tests\verse-runtime.test.ts` passed in `E:\Projects\Heimdall`.
|
||||
- `E:\Projects\VoidBot\docs\persona-intake\sai.persona-intake.yaml` no longer describes Sai/Eve projection as future CultMesh renderer work; the persona prose now says CultMesh-fed renderers.
|
||||
- `dotnet build .\src\Gjallar\Gjallar.csproj --no-restore` passed in standalone `E:\Projects\Gjallar` after aligning README/state with the runtime's configured CultNet/RUDP input.
|
||||
- `rg -n -e '192\.168\.1\.66:17871' -e 'compatibility Eve deck bridge' -e 'compatibility fallback' -e 'old Odin Eve deck WebSocket path retained' -e '--url' -e '/eve/deck/providers' E:\Projects\Gjallar\README.md E:\Projects\Gjallar\state\gjallar-state.md E:\Projects\Gjallar\src -g '!**/bin/**' -g '!**/obj/**'` returned no hits.
|
||||
- `dotnet build .\src\Aquarium.EveSurface\Aquarium.EveSurface.csproj --no-restore` passed in standalone `E:\Projects\Fensalir` after removing the local Eve deck broker default.
|
||||
- `rg -n -e 'ws://127\.0\.0\.1:8795/eve/deck' -e 'DefaultBrokerUrl' -e 'Mimir.*eve/deck|Mimir.*VoidBot dashboard' -e '/eve/deck/providers' E:\Projects\Fensalir\src E:\Projects\Fensalir\README.md E:\Projects\Fensalir\docs -g '!**/bin/**' -g '!**/obj/**'` returned no hits.
|
||||
- `cargo test operator_snapshot_distills_status_json_into_typed_cultmesh_document` passed in `E:\Projects\EpiphanyAgent\epiphany-core` after demoting the Codex app-server note from compatibility transport to external edge adapter.
|
||||
- Ghostlight provider advertisement JSON parsed with `ConvertFrom-Json`, `node --check tools\publish_odin_provider_advertisement.cjs` passed, and `C:\Users\Meta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\python.exe -m py_compile tools\ghostlight_state.py` passed after the provider-discovery wording cut.
|
||||
- `rg -n 'future CultMesh|future CultMesh provider discovery|CultMesh/Odin provider discovery' E:\Projects\Ghostlight\examples\ghostlight-provider-advertisement.v0.json E:\Projects\Ghostlight\tools E:\Projects\Ghostlight\docs -g '!**/node_modules/**'` now finds only the live `CultMesh/Odin provider discovery` phrase.
|
||||
- `node --check src\loki-daemon.mjs`, `node --check test\fixture-cdp-smoke.mjs`, `node --check test\extension-ingest-smoke.mjs`, and `node --check test\cultmesh-store.mjs` passed in `E:\Projects\Loki` after replacing Loki's text `.cc` witness writer with typed CultMesh store publication.
|
||||
- `node test\fixture-cdp-smoke.mjs` and `node test\extension-ingest-smoke.mjs` passed in `E:\Projects\Loki`; both smokes read Loki records back from `state/loki.service.ccmp` through `CultMesh.createNode(...)`.
|
||||
- `rg -n "state-dir|loki\.chrome_snapshot\.cc|provider_advertisement\.cc|eve_surface\.cc|CultNet/CultMesh publication|typed text witnesses|awaits a direct|cultcache-text-witness|LOKI_STATE_DIR|Add CultCache|Add CultNet" E:\Projects\Loki` returned no hits after the Loki CultMesh promotion.
|
||||
- Guarded deletion removed the untracked runnable stale `E:\Projects\Mimir\scratch\raven-daemon-build` tree after verifying it was untracked and contained 699 files under `E:\Projects\Mimir\scratch`; a follow-up `git -C E:\Projects\Mimir status --short -- scratch\raven-daemon-build` returned no output.
|
||||
- PowerShell parser checks passed for `restart-nightwing-eve-dashboard.ps1`, `restart-nightwing-eve-browser-reference.ps1`, `restart-nightwing-gjallar.ps1`, `restart-odin.ps1`, `restart-starfire-muninn.ps1`, `restart-nightwing-muninn.ps1`, `restart-vili.ps1`, and `restart-muninn.ps1` after adding the Idunn-only actuator guard.
|
||||
- Direct no-env probes of `restart-nightwing-eve-dashboard.ps1`, `restart-nightwing-eve-browser-reference.ps1`, `restart-nightwing-gjallar.ps1`, and `restart-vili.ps1` failed at the Idunn actuator guard before any remote or local restart action.
|
||||
- Direct no-env probes of `restart-odin.ps1`, `restart-starfire-muninn.ps1`, `restart-nightwing-muninn.ps1`, and `restart-muninn.ps1` failed at the Idunn actuator guard before any remote or local restart action.
|
||||
- Direct no-env probes of `restart-stonks.cmd`, `restart-weksa.cmd`, and `restart-voidbot.cmd` failed at the Idunn actuator guard before delegating to their restart bodies.
|
||||
- Direct no-env probes of `deploy-yggdrasil-streampixels.cmd`, `deploy-nightwing-gjallar.cmd`, `restart-muninn.cmd`, and `restart-nightwing-eve-dashboard.cmd` failed at the wrapper-level Idunn actuator guard before delegating to child scripts.
|
||||
- PowerShell parser checks passed for `deploy-nightwing-gjallar.ps1`, `deploy-yggdrasil-streampixels.ps1`, `deploy-yggdrasil-heimdall.ps1`, and `deploy-yggdrasil-repixelizer.ps1` after tightening deploy guards to require `IDUNN_COMMAND_AUTHORITY=idunn-daemon` as well as `IDUNN_ACTUATOR=1`.
|
||||
- Direct no-env probes of those four deploy scripts failed at the Idunn actuator guard before SFTP, SSH, build, or remote deploy work.
|
||||
- Focused guard scan over active Idunn deploy/restart command bodies found every script requiring both `IDUNN_ACTUATOR` and `IDUNN_COMMAND_AUTHORITY`.
|
||||
- PowerShell parser checks passed for the Yggdrasil Idunn deploy wrappers after adding `sudo env GAMECULT_IDUNN_REMOTE_ACTUATOR=1 IDUNN_COMMAND_AUTHORITY=idunn-daemon` to the remote deploy call.
|
||||
- Static residue scan over `gamecult-ops` Yggdrasil deploy bodies found no remaining `git clone`, target-host `git fetch`, `git checkout`, `reset --hard`, `REPO_URL`, or `BRANCH` fallback path in StreamPixels/Repixelizer, and confirmed all three remote bodies require `GAMECULT_IDUNN_REMOTE_ACTUATOR` plus `IDUNN_COMMAND_AUTHORITY`.
|
||||
- `git diff --check` passed for the touched Odin deploy wrappers and `gamecult-ops` Yggdrasil remote deploy bodies/README; Git reported only expected LF-to-CRLF working-copy warnings.
|
||||
- `cargo test -p idunn-daemon` passed 17 tests after the Idunn restart actuator guard cut.
|
||||
- `node --check src\odin\config.cjs` and `node scripts\check-syntax.cjs` passed after removing Odin's copied remote witness import knob.
|
||||
- `rg -n -F -e "includeRemoteWitnesses" -e "remote-witnesses" -e "sync-muninn-remote-witnesses" -e "nightwing.muninn.telemetry.cc" -e "raven.muninn.telemetry.cc" -e "raven.sleipnir.cc" E:\Projects\Odin\src E:\Projects\Odin\scripts E:\Projects\Odin\docs E:\Projects\Odin\README.md` no longer finds an Odin copied-witness import path; Raven Sleipnir's local store is daemon-owned and its lifecycle is represented through Idunn deployment/restart actuators.
|
||||
- `node --check E:\Projects\weksa\scripts\weksa-daemon.mjs` passed after moving Weksa health to `.weksa/provider-advertisement-store.cc`.
|
||||
- Weksa CultMesh health smoke started the daemon in a temporary state root, waited for `provider-advertisement-store.cc`, ran `weksa-daemon.mjs --health`, received `state: healthy` with the temp store path, and confirmed the deleted top-level text witnesses were not recreated.
|
||||
- `Select-String` over `E:\Projects\weksa\scripts\weksa-daemon.mjs` and `E:\Projects\weksa\docs\verse-service-contract.md` found no remaining references to the deleted top-level Weksa text witnesses.
|
||||
- Bifrost provider-store smoke ran `node tools\provider-advertisement.mjs export --out <temp>\bifrost-provider-store-*.cc`, created a non-empty typed store, and reported `.bifrost/provider-store.cc` in the exported witness list.
|
||||
- `node --check E:\Projects\Bifrost\tools\provider-advertisement.mjs`, `node --check E:\Projects\Odin\src\odin\config.cjs`, and `node E:\Projects\Odin\scripts\check-syntax.cjs` passed after the Bifrost provider-store rename.
|
||||
- Focused Bifrost/Odin scan found no remaining Bifrost `provider-advertisement.cc` route/docs/tool references; remaining hits now point to `.bifrost/provider-store.cc` producer/export documentation, not an Odin default store path.
|
||||
- Odin config smoke verified `buildConfig([])` has no default interface store imports, while `ODIN_INTERFACE_BINDING_STORES=cultmesh-store:file:///E:/Projects/Odin/scratch/odin/odin.ccmp` is preserved as an explicit local debug CultMesh store URI.
|
||||
- `node E:\Projects\Odin\scripts\check-syntax.cjs` passed after cutting Odin's repo-local and `C:\Meta\Odin\state\...` default interface store imports.
|
||||
- `powershell.exe -NoProfile -ExecutionPolicy Bypass -File E:\Projects\Odin\scripts\health-idunn-swarm-deployment-coverage.ps1` now reports 20 cataloged targets and 30 cataloged actuator paths, and fails if any guarded `deploy-*`/`restart-*` script with `IDUNN_ACTUATOR` is missing from the Idunn catalog/alias/shared-actuator set.
|
||||
- `cargo test -p idunn-daemon` passed 18/18 after syncing the PowerShell Idunn target catalog with the Rust swarm profile.
|
||||
- PowerShell parser checks passed for `scripts\deploy-raven-sleipnir.ps1`, `scripts\restart-raven-sleipnir.ps1`, and `scripts\start-starfire-sleipnir.ps1` after removing the Raven IP and local Odin discovery-store defaults.
|
||||
- `cargo test -p sleipnir-daemon` passed 16/16 after the Sleipnir launcher cleanup.
|
||||
- Focused scan of `scripts\deploy-raven-sleipnir.ps1`, `scripts\restart-raven-sleipnir.ps1`, and `scripts\start-starfire-sleipnir.ps1` found no active `192.168.1.84` defaults or default `E:\Projects\Odin\scratch\odin\odin.ccmp` discovery-store path; remaining hits are inventory history/debug notes and the explicit `--discovery-store` parameter gate.
|
||||
- Active `Odin-clean-move-identity` worktree checks verified all deploy/restart `.ps1` bodies and `.cmd` wrappers require `IDUNN_COMMAND_AUTHORITY`, no actuator scripts retain `192.168.1.66:17871`, `10.77.0.2:17870`, `127.0.0.1:17870`, `10.77.0.4`, or default `10.77.*` media target literals, and all branch deploy/restart PowerShell files parse.
|
||||
- `git -C E:\Projects\Odin diff --check -- ...` passed for the touched Idunn docs and restart actuator scripts; Git reported only expected LF-to-CRLF working-copy warnings.
|
||||
- `rg -n '192\.168\.1\.(66|75|84)|madman''s lullaby@192\.168\.1\.84|http://192\.168\.1\.84|srt://192\.168\.1\.66' E:\Projects\gamecult-ops\inventory.local.md` returned no hits after the local inventory cleanup.
|
||||
- `rg -n -i -e 'localhost health|127\.0\.0\.1|/api/health|/healthz|provider|cultcache|idunn|rudp|odin|eve/deck|fallback|compat'` over `gamecult-ops\scripts\check-*` classified localhost health checks as deployment/readiness probes; Heimdall, Repixelizer, and StreamPixels checks validate configured CultCache/Idunn fields where those services expose CultMesh boundaries.
|
||||
- Focused service-to-service HTTP scan found product/API boundaries only: Spotiverse and StreamPixels call Heimdall auth endpoints, StreamPixels web calls its service backend, VoidBot reads Odin through `ODIN_CULTMESH_STORE` despite `fetchOdinSnapshot` naming, and Aetheria's Ymir localhost HTTP example is explicitly labelled a "Current leak" with a typed CultMesh desired shape.
|
||||
- `git -C E:\Projects\gamecult-ops diff --check -- scripts\README.md` passed after the localhost-probe boundary note.
|
||||
- Sidecar/fixture scan classified Odin's `latest-surface.json` writer as gated by `--write-debug-surface-json` / `ODIN_WRITE_DEBUG_SURFACE_JSON=1`, Eve's browser reference provider list as local fixtures, and VoidBot JSON hits as fixture/smoke/dashboard debug outputs or typed-state CLI projections rather than Verse discovery authority.
|
||||
- `rg -n -i 'live VoidBot|Mimir.*deck broker|opens live providers|dialing Mimir|mimir-eve-deck|8795/eve/deck' E:\Projects\Eve\README.md E:\Projects\Eve\docs E:\Projects\Eve\web -g '!**/node_modules/**'` now finds only the guard sentence saying the browser reference no longer opens live providers by dialing Mimir's deck broker.
|
||||
- `git -C E:\Projects\Eve diff --check -- docs\renderer-parity.md` passed after the Eve parity wording cut.
|
||||
- `rg -n 'WEKSA_DAEMON_BASE_URL|rudp://127\.0\.0\.1:8813|http://127\.0\.0\.1:8813' E:\Projects\VoidBot\.env E:\Projects\VoidBot\.env.example E:\Projects\VoidBot\packages\config\src E:\Projects\VoidBot\apps -g '!**/node_modules/**'` returned no hits after the local env cleanup.
|
||||
- VoidBot config smoke loaded local `.env` without printing secrets and verified `REPO_FACE_WEKSA_SPEECH_ENABLED=true` fails without `WEKSA_CULTMESH_COMMAND_URI`.
|
||||
- `rg -n '10\.77\.0\.[0-9]+|192\.168\.1\.66:17870|127\.0\.0\.1:17870|compatibility witnesses|<idunn-rudp-health-host>:17870' E:\Projects\Heimdall\state\map.yaml E:\Projects\Heimdall\notes\fresh-workspace-handoff.md E:\Projects\Heimdall\README.md E:\Projects\Heimdall\.env.example E:\Projects\Heimdall\docs E:\Projects\Heimdall\src E:\Projects\Heimdall\tests -g '!**/node_modules/**'` returned no hits after the Heimdall memory cleanup.
|
||||
- `git -C E:\Projects\Heimdall diff --check -- state\map.yaml notes\fresh-workspace-handoff.md` passed.
|
||||
- `node node_modules\vitest\vitest.mjs run --config vitest.config.ts tests\app.test.ts` passed 22 tests and `node node_modules\typescript\bin\tsc -p tsconfig.json --noEmit` passed after deleting Heimdall's hardcoded Repixelizer/Spotiverse backend-callback exceptions.
|
||||
- `rg -n "handoff\.callbackUrl ===|http://127\.0\.0\.1:8796/auth/heimdall/callback" E:\Projects\Heimdall\src\app.ts E:\Projects\Heimdall\tests\app.test.ts E:\Projects\Heimdall\.env.example E:\Projects\Heimdall\docs -g '!**/node_modules/**'` returned no hits for the deleted active-code callback shortcut.
|
||||
- Follow-up config/default scans over StreamPixels, Heimdall, Mimir, Vili, Stonks, Weksa, Repixelizer, Bifrost, Gjallar, Fensalir, and Ghostlight source/doc/script surfaces found no additional daemon peer discovery shortcuts. Remaining local endpoint hits were classified as product-web boundaries (`StreamPixels` web/service URLs, Heimdall public issuer/base examples), daemon-local binds or operator/debug routes (`Stonks`, `Mimir`, `Spotiverse`), explicit debug sidecars (`Vili_WRITE_DEBUG_SIDECARS` / `--write-debug-sidecars`), local subprocess plumbing (`Vili` Kimodo worker), or deployment/readiness probes (`Bifrost`, `gamecult-ops`).
|
||||
- PowerShell parser checks passed for `scripts\restart-muninn.ps1` after removing the Raven Muninn capture-command RUDP loopback target default; Raven Sleipnir's old standalone start script has been removed because Idunn owns deployment and restart.
|
||||
- `rg -n -e '192\.168\.1\.66:17871' -e '192\.168\.1\.84:17889' -e '127\.0\.0\.1:17884' -e 'CaptureCommandRudpTarget = "127\.0\.0\.1:17884"' E:\Projects\Odin\scripts\deploy-raven-sleipnir.ps1 E:\Projects\Odin\scripts\restart-raven-sleipnir.ps1 E:\Projects\Odin\scripts\restart-muninn.ps1 E:\Projects\Odin\crates\sleipnir-daemon\src\main.rs E:\Projects\Odin\crates\muninn-daemon\src\main.rs` returned no hits after the Raven launcher cuts.
|
||||
- `cargo test -p odin-core discovery` passed after replacing the remaining live-looking `192.168.1.66:17888` discovery fixture with documentation address space.
|
||||
- `rg -n -e '192\.168\.1\.66:17888' -e 'cultnet://10\.77\.0\.4:3075' E:\Projects\Odin\crates\odin-core\src\discovery.rs E:\Projects\Eve\docs\provider-advertisement-contract.md` returned no hits after the fixture/contract cleanup.
|
||||
- PowerShell parser checks passed for `E:\Projects\Mimir\scripts\start-starfire-cultmesh-av-receiver.ps1`, `E:\Projects\Mimir\scripts\start-raven-cultmesh-av-sender.ps1`, `E:\Projects\Mimir\scripts\start-raven-muninn-obs-feed.ps1`, `E:\Projects\Mimir-raven-build\scripts\start-muninn.ps1`, and `E:\Projects\Mimir-obs-muninn-branch\scripts\start-raven-daemon.ps1` after removing the `10.77.0.1` / `10.77.0.2` defaults.
|
||||
- `dotnet build .\src\Mimir.CultMeshMedia\Mimir.CultMeshMedia.csproj --no-restore`, `dotnet build .\src\Mimir.Muninn\Mimir.Muninn.csproj --no-restore`, and `dotnet build .\src\Mimir.RavenDaemon\Mimir.RavenDaemon.csproj --no-restore` passed after making Mimir media relay/target endpoints explicit.
|
||||
- Negative smokes confirmed `Mimir.CultMeshMedia send` fails without `--endpoint` / `--host`, and `Mimir.Muninn` / `Mimir.RavenDaemon` fail without `--target-host`, each with the explicit Odin/CultMesh discovery or operator config error.
|
||||
- `rg -n -e '10\.77\.0\.1' -e '10\.77\.0\.2'` over the touched root Mimir, `Mimir-raven-build`, and `Mimir-obs-muninn-branch` media launcher/runtime files returned no hits after the Mimir WireGuard default cut.
|
||||
- Focused source/script scans across Aetheria, Ymir, Ghostlight, Gjallar, Fensalir, Brokkr, Huginn, Norn, Loki, Sindri, CultLib, EpiphanyAgent, AquaSynth, Odin, Mimir active branches, VoidBot, and Aetheria found no new daemon discovery shortcuts in active GameCult service paths. Remaining local endpoint hits are classified as local daemon bootstrap/smoke fixtures (`Aetheria.Rts.Web`), protocol self-tests/examples (`CultLib`), local product/debug health lowerings (`Odin`, `Mimir`, Fensalir bridge scripts), external local ML/storage dependencies (`VoidBot` Qdrant/Ollama and AquaSynth IPA worker), or negative Idunn assertions against old endpoint strings.
|
||||
- `rg -n -F` for old WireGuard/LAN URL forms over Brokkr, Huginn, Norn, Loki, and Sindri source/script/doc surfaces returned no hits.
|
||||
- `rg -n -F` for `10.77.0.1`, `10.77.0.2`, `192.168.1.66`, and `192.168.1.84` over active Odin/Mimir source and scripts now returns only Idunn negative assertions, not runtime defaults.
|
||||
- Consolidated broad scan for `future CultMesh`, compatibility deck/HTTP/WebSocket/transport/command phrasing, deck provider manifests, `ODIN_BASE_URL`, `WEKSA_DAEMON_BASE_URL`, old LAN/WireGuard endpoint families, localhost Odin/Idunn RUDP literals, Weksa localhost RUDP, and local Eve deck URLs across Odin, active Mimir branches, `gamecult-ops`, VoidBot, Eve, Spotiverse, Stonks, StreamPixels, Heimdall, Vili, Weksa, Bifrost, repixelizer, Ymir, Aetheria, Gjallar, Fensalir, Ghostlight, CultLib/CultCache/CultNet libraries, gamecult-bifrost, Brokkr, Huginn, Norn, Loki, Sindri, AquaSynth, EpiphanyAgent, and outer client repos now returns only classified residue: local renderer/debug deck docs in Odin/Spotiverse, Odin's own local CultMesh/RUDP bind docs, Idunn's own health bind docs/test default, Muninn parser tests for an explicitly supplied diagnostic endpoint, Idunn negative assertions against old endpoints, the `gamecult-ops` audit detector string, CultLib protocol-level `compatibility.transport_version` fields, and an AquaSynth planning note about Pink Trombone compatibility commands.
|
||||
- Follow-up narrow config/deployment scans on 2026-07-03 found no active daemon peer defaults in Odin/gamecult-ops, VoidBot/Spotiverse/Eve, StreamPixels/Heimdall/Stonks/Vili/Weksa/Repixelizer/Bifrost, Aetheria/Ymir/Gjallar/Fensalir/Ghostlight, or Brokkr/Huginn/Norn/Loki/Sindri. Remaining hits were classified as WireGuard connectivity inventory, external local Ollama dependency, or no-hit groups.
|
||||
- The same Mimir scan found untracked runnable artifact launchers still embedding direct relay/proof endpoints (`10.77.0.1`, `10.77.0.2`). Because they were untracked generated launchers rather than source-owned contracts, the shortcut was deleted outright: `E:\Projects\Mimir\artifacts\mimir-cultmesh-media-raven-stage\run-raven-sender.ps1`, `run-raven-video-silent-sender.ps1`, `run-raven-video-silent-sender.cmd`, and `E:\Projects\Mimir\artifacts\muninn-raven-video-to-starfire-obs.cmd`.
|
||||
- Re-running the focused Mimir config/deployment scan after deleting those artifact launchers returned no remaining active hits for the old LAN/WireGuard endpoint families outside archived runtime evidence.
|
||||
- Follow-up Mimir branch audit found stale `Mimir-raven-build` / `Mimir-obs-muninn-branch` docs and state maps still describing Starfire/Raven WireGuard SRT and SSH defaults as the current Muninn/Raven route. Those maps now say target hosts come from Odin/CultMesh discovery or explicit operator config; the old direct endpoint route is not a default.
|
||||
- Root Mimir's CultMesh media map no longer names Yggdrasil as `10.77.0.1:3075`; it describes Yggdrasil relay routing as Odin/CultMesh-discovered or explicit operator configuration, with only the Starfire OBS lowering remaining local.
|
||||
- Mimir Runtime transport catalogs and provider fixture/export wording no longer describe diagnostic SRT/LAN media or Eve state as "legacy remote program feeds" / "provider manifests"; they now say diagnostic lowering and typed provider advertisements.
|
||||
- The OBS Muninn source documentation no longer preserves a LAN-default stream fallback when catalog discovery is absent. The native source already uses a `discovery-missing` placeholder with an empty media host and sanitizes stale `10.77.*` / `192.168.*` saved endpoints; the docs now match that behavior.
|
||||
- Odin Idunn plan and health-script wording for the Nightwing Eve dashboard now refers to typed provider advertisements rather than provider manifests.
|
||||
- Focused rerun over the previously suspicious Odin/Mimir/VoidBot/Weksa files now leaves only classified residue: Odin/Idunn local RUDP binds and tests, Idunn negative assertions against old private endpoints, and VoidBot/Weksa external Ollama host notes.
|
||||
- Odin provider catalogs no longer promote `/eve/deck/{provider}` WebSocket lowerings or arbitrary first advertisement endpoints to `transportEndpoint`; the Odin-hosted deck path has now been deleted outright. `src\odin\interfaces.cjs` and `src\odin\provider-ingress.cjs` ignore non-CultMesh/CultNet `provider.endpoint` values for transport authority instead of falling back to HTTP/WS endpoints.
|
||||
- Hermodr's browser bridge remains a browser-runtime lowering, not a discovery or command authority. `src\hermodr-daemon.cjs`, `scripts\start-hermodr.ps1`, `scripts\restart-hermodr.ps1`, and `scripts\health-hermodr.ps1` now run/probe a lowering over Odin's CultMesh store while rejecting old Odin HTTP/WebSocket inputs, raw sleipnir/Aetheria RUDP command endpoints, renderer-export state paths, and local mapping-store fallbacks. Browser ergonomics survive; provider discovery and command route ownership stay with Odin/CultMesh typed documents.
|
||||
- `node --check` passed for `src\odin-coordinator.cjs`, `src\odin\config.cjs`, `src\hermodr-daemon.cjs`, and `scripts\write-gjallar-persona-cc.mjs`; `node scripts\check-syntax.cjs` checked the remaining 15 Odin CommonJS files after deleting `src\odin\websocket.cjs`.
|
||||
- Odin operator layout intent no longer writes durable truth to `scratch\odin\interface-layout.json`. `odin.interface_layout.v1` is now a typed document in Odin's CultMesh store under record key `odin.providers`; the old JSON file is migration input only. Renderer WebSocket `odin-layout-intent` frames call the async layout store and receive `odin-command-rejected` frames if the CultMesh write fails instead of silently becoming a sidecar owner.
|
||||
- Odin layout smoke created a temp `odin.ccmp`, seeded an old `interface-layout.json`, verified the layout migrated into `odin.interface_layout.v1`, applied a renderer `toggle` intent, and observed the updated tile in the CultMesh record. `node scripts\check-syntax.cjs` passed for all 16 CommonJS files after the layout promotion.
|
||||
- Spotiverse no longer accepts command authority through HTTP `/commands/add-to-queue` or Eve WebSocket `spotiverse-command` frames. The HTTP route now returns `410 command_route_removed`, the WebSocket frame returns `command_frame_removed`, the old HTTP handler functions were deleted, and the only remaining `executeCommand` caller is the CultMesh `spotiverse.command_request.v1` poller. Spotiverse's provider advertisement now uses a `cultmesh:` provider endpoint and no command debug lowerings.
|
||||
- `node --check` passed for `E:\Projects\Spotiverse\src\spotiverse-daemon.cjs` and `E:\Projects\Spotiverse\scripts\export-provider-advertisement.cjs`; focused scans over Spotiverse source/docs/export find no remaining debug HTTP/WebSocket command authority wording outside the removed HTTP tombstone route.
|
||||
- Vili's Eve surface no longer advertises `href: /motion/generate` for motion generation, and its command boundary no longer names HTTP POST as intake. `scripts\vili-daemon.mjs` now polls `gamecult.vili.motion_request.v1` from the Vili CultMesh store, processes requests through the existing Kimodo job path, and writes `gamecult.vili.motion_job.v1` receipts. `POST /motion/generate` remains only as a `410 command_route_removed` tombstone.
|
||||
- `node --check E:\Projects\Vili\scripts\vili-daemon.mjs` passed; focused scans found no `href`/`path`/`source`/`lowersTo` active HTTP motion-command residues in Vili source/docs. Runtime smoke started Vili in a temp state dir, verified `POST /motion/generate` returns HTTP 410, wrote an invalid `gamecult.vili.motion_request.v1` document into `vili.service.cc`, and observed exactly one deterministic `gamecult.vili.motion_job.v1` receipt (`jobId: vili-codex-invalid`, `state: failed`) plus `motionCommands.pendingRequests: 0` / `failedRequests: 1` in operator state.
|
||||
- `rg -n "seedDeckUrls|discoverDeck|refreshLanDeck|localIpv4|fetchEveProvider|sftp://|compatibility-eve-deck|websocket-bridge|providerDeckUrl|activeProviderInterface|stonksStateUrl|http://127\.0\.0\.1:8802|ws://192\.168|192\.168\.1\.75|192\.168\.1\.66|10\.77\.0\.4:8824|10\.77\.0\.2:17870|127\.0\.0\.1:17870" E:\Projects\Odin-clean-move-identity\src E:\Projects\Odin-clean-move-identity\scripts -g "!**/target/**"` returned only the intentional raw-network-store rejection guard in `src\odin\interfaces.cjs`.
|
||||
- `node --check src\odin\config.cjs`, `src\odin\interfaces.cjs`, `src\odin\marquee.cjs`, `src\odin\state.cjs`, `src\odin\surface.cjs`, `src\odin\probes.cjs`, `src\odin\websocket.cjs`, `src\odin-coordinator.cjs`, and `node scripts\check-syntax.cjs` passed in `E:\Projects\Odin-clean-move-identity`.
|
||||
- PowerShell parser checks passed in `E:\Projects\Odin-clean-move-identity` for `scripts\start-odin.ps1`, `scripts\health-muninn.ps1`, `scripts\health-nightwing-muninn.ps1`, `scripts\health-starfire-muninn.ps1`, and `scripts\start-starfire-wireguard-elevated.ps1`.
|
||||
- `git -C E:\Projects\Odin-clean-move-identity diff --check -- README.md docs/gjallar.md scripts src` passed; Git reported only expected LF-to-CRLF working-copy warnings.
|
||||
- `rg -n -F` over `E:\Projects\Gjallar-rollback` for old LAN/WireGuard endpoint literals, `/eve/deck/providers`, `compatibility-eve-deck`, `websocket-bridge`, `ODIN_BASE_URL`, seeded deck strings, and SFTP/WebSocket shortcut forms returned no hits outside ignored build artifacts after replacing the rollback body with a fail-closed archive stub.
|
||||
- `dotnet build .\src\Gjallar\Gjallar.csproj --no-restore` passed in `E:\Projects\Gjallar-rollback`, and `dotnet run --project .\src\Gjallar\Gjallar.csproj --no-build` produced the archived-transport tombstone and exited with the expected code 2.
|
||||
- `rg -n -F` over `E:\Projects\Mimir-obs-muninn` for old Raven/Starfire LAN and WireGuard endpoint literals, `/eve/deck/providers`, `compatibility-eve-deck`, `websocket-bridge`, `ODIN_BASE_URL`, seeded deck strings, SFTP, WebSocket, and SRT private endpoint forms returned no hits after the neighbor sender docs/config cleanup.
|
||||
- `git -C E:\Projects\Mimir-obs-muninn diff --check -- config docs state` and `git -C E:\Projects\Gjallar-rollback diff --check -- README.md src state` passed; Git reported only expected LF-to-CRLF working-copy warnings.
|
||||
- Focused Odin scan for `websocket.cjs`, `createDashboardServer`, `broadcastState`, `openWebSocket`, `/eve/deck`, `ws://127.0.0.1:8797`, `http://127.0.0.1:8797`, `Odin WebSocket`, `Eve deck`, `rudp://127.0.0.1:17871`, Odin `Port = 8797`, and `--port ... 8797` returned no hits outside this inventory after deleting the Odin HTTP/WebSocket server and removing the Hermodr localhost Odin default.
|
||||
- PowerShell parser checks passed for `scripts\start-odin.ps1`, `scripts\restart-odin.ps1`, `scripts\health-odin.ps1`, and `scripts\start-hermodr.ps1` after the Odin side-door cut.
|
||||
- `git -C E:\Projects\Odin diff --check -- README.md docs scripts src` passed after the Odin HTTP/WebSocket side-door deletion; Git reported only expected LF-to-CRLF working-copy warnings.
|
||||
- Stonks HTTP/WebSocket side-door cut verification passed: `node --check E:\Projects\Stonks\src\stonks-daemon.cjs`, PowerShell parser check for `E:\Projects\Stonks\scripts\start-stonks.ps1`, focused Stonks scan for the old HTTP/WebSocket listener/routes, and `git -C E:\Projects\Stonks diff --check -- src\stonks-daemon.cjs scripts\start-stonks.ps1 README.md docs\architecture.md AGENTS.md`. The focused scan found no active listener/route residue after removing one negative AGENTS breadcrumb.
|
||||
- Active Mimir branch Raven daemon verification passed: `dotnet build .\src\Mimir.RavenDaemon\Mimir.RavenDaemon.csproj --no-restore`, PowerShell parser check for `scripts\start-raven-daemon.ps1`, and focused scan over `src\Mimir.RavenDaemon\Program.cs`, `scripts\start-raven-daemon.ps1`, `docs\raven-capture-mux-daemon.md`, and `state\map.yaml` for the removed Eve port, deck route, health route, listener class, and WebSocket helper residue.
|
||||
- Active Mimir branch Fensalir daemon verification passed: `dotnet build .\src\Mimir.FensalirDaemon\Mimir.FensalirDaemon.csproj --no-restore`, PowerShell parser check for `scripts\start-fensalir-daemon.ps1`, and focused scan over `src\Mimir.FensalirDaemon\Program.cs`, `scripts\start-fensalir-daemon.ps1`, `docs\fensalir-reservoir-daemon.md`, and `state\map.yaml` for the removed port knob, deck route, health route, listener class, WebSocket helpers, dashboard projection types, and debug-lowering residue.
|
||||
- Active Mimir branch EveDashboard verification passed: `dotnet build .\src\Mimir.EveDashboard\Mimir.EveDashboard.csproj --no-restore`, focused scan over `src\Mimir.EveDashboard\Program.cs` and `docs\eve-dashboard.md` for the removed listener, HTTP/deck routes, frame-command path, compatibility endpoint, launch-time external provider registration, and WebSocket broker residue, plus `git diff --check` on the touched EveDashboard files.
|
||||
- Active Mimir branch online supervisor follow-through verification passed: PowerShell parser checks for `scripts\start-online-verse-daemons.ps1` and `scripts\start-fensalir-daemon.ps1`, `dotnet build` for Fensalir and EveDashboard, and a focused scan over the touched supervisor/docs/code for stale Fensalir port/bridge/health and EveDashboard broker route residue. Remaining VerseRelay health probing is a separate sensor relay readiness path, not the deleted Fensalir/EveDashboard surface.
|
||||
- Active Mimir branch EveSensorReceiver verification passed: `dotnet build .\src\Mimir.EveSensorReceiver\Mimir.EveSensorReceiver.csproj`, PowerShell parser check for `scripts\start-online-verse-daemons.ps1`, and exact literal scans over the receiver and online supervisor for the deleted HTTP health wait, baked camera path/port defaults, and URL-shaped listener log.
|
||||
- Active Mimir branch Well publication side-door cut: `Mimir.Well` no longer imports `System.Net.WebSockets`, constructs a `ClientWebSocket`, accepts `--publish-url`, reads `MIMIR_WELL_PUBLISH_URL`, or publishes Well snapshots/pages/stream frames through a Periwinkle WebSocket. It now opens a CultMesh node at `--cultmesh-cache` / `MIMIR_WELL_CULTMESH_CACHE` and writes typed `mimir.well_published_document.v1` envelopes into CultCache for Odin discovery.
|
||||
- `scripts\start-online-verse-daemons.ps1` now passes Well a run-local `mimir-well.ccmp` cache instead of a publish URL, and its supervisor manifest no longer advertises debug WebSocket lowerings or URL fields as service routes. The stale generated `artifacts\runtime\online-verse-daemons-20260703-083019\supervisor.json` residue that still contained the old lowering routes was untracked and deleted outright.
|
||||
- Active Mimir branch Well verification passed: `dotnet build .\src\Mimir.Well\Mimir.Well.csproj --no-restore`, PowerShell parser check for `scripts\start-online-verse-daemons.ps1`, `git diff --check` on the touched Well/supervisor/map files, and focused scans found no remaining Well `ClientWebSocket`, `--publish-url`, `MIMIR_WELL_PUBLISH_URL`, debug WebSocket lowering, or WebSocket/JSON publication-pressure residue outside this inventory.
|
||||
- Active Mimir branch sensor relay/recorder subscription side-door cut: `Mimir.EveSensorReceiver` no longer exposes `/eve/periwinkle/subscribe`, maintains WebSocket subscribers, or broadcasts normalized records over a daemon-owned subscriber socket. It publishes accepted observations as typed `mimir.sensor_relay_observation.v1` CultMesh/CultCache records instead.
|
||||
- `Mimir.VerseRecorder` no longer imports `System.Net.WebSockets`, constructs a `ClientWebSocket`, accepts `--url`, or reads `MIMIR_VERSE_RECORDER_URL`. It now requires `--cultmesh-cache` / `MIMIR_SENSOR_RELAY_CULTMESH_CACHE`, follows the relay cache, and writes the same `session.json` / `observations.jsonl` / paged body index outputs from typed relay records.
|
||||
- `scripts\start-online-verse-daemons.ps1` now creates a run-local `eve-sensor-relay.ccmp`, passes it to both receiver and recorder, and no longer passes the removed subscribe path or recorder URL. Verification passed: `dotnet build .\src\Mimir.EveSensorReceiver\Mimir.EveSensorReceiver.csproj --no-restore`, `dotnet build .\src\Mimir.VerseRecorder\Mimir.VerseRecorder.csproj --no-restore`, PowerShell parser check for the supervisor, `git diff --check` on the touched relay/recorder/map files, and focused scans found no remaining receiver subscriber route, recorder WebSocket client, `MIMIR_VERSE_RECORDER_URL`, or debug WebSocket lowering residue outside this inventory.
|
||||
- Active Mimir branch Nightwing witness publisher side-door cut: `tools\nightwing_typed_witness_publisher.py` no longer implements a hand-rolled WebSocket client, accepts `--url`, reads `MIMIR_WITNESS_URL`, or sends observations directly to `Mimir.EveSensorReceiver`. It now emits length-prefixed MessagePack `mimir.eve_sensor_observation.v1` payloads to stdout for local diagnostics/future CultMesh sender plumbing, so it cannot act as a daemon transport shortcut.
|
||||
- `scripts\start-online-verse-daemons.ps1` no longer stages or starts the Nightwing remote witness publisher, and the old `start-stream-proof.ps1` / `start-mvp-leap-eyes-kiyo.ps1` launchers are fail-closed archive stubs because they depended on the removed Nightwing witness side door. Nightwing compact observations must return through Odin/CultMesh-discovered witness publication before those profiles are live again.
|
||||
- Nightwing witness verification passed: bundled Python `py_compile` for `tools\nightwing_typed_witness_publisher.py`, PowerShell parser checks for the touched launchers, `git diff --check` on the touched Nightwing/script/doc/map files, and focused scans found no remaining `MIMIR_WITNESS_URL`, witness `--url`, `nightwing_typed_witness_publisher.py --url`, or old `ws://<receiver-host>:8796/eve/periwinkle` route residue outside this inventory.
|
||||
- Active Mimir branch Eve camera/mic receiver side-door cut: active Well and Raven/Eve runtime configs no longer launch `Mimir.EveSensorReceiver` for `eve-camera` / `eve-mic`, and the `8793` `/eve/camera` plus `8794` `/eve/mic` receiver paths were removed from source-owned config/docs. `Mimir.EveSensorReceiver` itself is now a fail-closed archive stub; stale invocations cannot open the old sensor ingress listener.
|
||||
- `scripts\start-online-verse-daemons.ps1` no longer starts the receiver process or carries the dead relay port/readiness wait. Fensalir's process surface marks the old relay PID shape as archived instead of expected live daemon state. The Raven/Eve ingest doc is now an archive note, and the network-display/implementation/Eve-dashboard docs say Eve observations must return through Odin/CultMesh witness/media publication before rejoining the live profile.
|
||||
- Eve camera/mic receiver verification passed: JSON validation for `config\mimir-runtime.well.local.json` and `config\mimir-runtime.raven-eve.example.json`, `dotnet build .\src\Mimir.EveSensorReceiver\Mimir.EveSensorReceiver.csproj --no-restore`, `dotnet build .\src\Mimir.FensalirDaemon\Mimir.FensalirDaemon.csproj --no-restore`, PowerShell parser check for `scripts\start-online-verse-daemons.ps1`, `git diff --check` on the touched config/receiver/docs/map files, and focused scans found no remaining `/eve/camera`, `/eve/mic`, `8793`, `8794`, or `Mimir.EveSensorReceiver --port` route residue outside archive/tombstone references and this inventory.
|
||||
- Active Mimir branch Eve dialogue/program-output/public-edge side-door cut: `Mimir.EveDialogue`, `Mimir.EveRelay`, and `Mimir.Broadcast` are now fail-closed archive stubs. They no longer contain hand-rolled WebSocket stream servers, D3D12-to-H.264 `/stream` relay code, reverse-tunnel launcher plumbing, or direct RTMP/HLS edge publishing logic.
|
||||
- `scripts\start-eve-dashboard-stream.ps1` is now an archive stub, the Mimir runtime output panel no longer exposes `Live` / `Stop` streaming controls or `/program/output/streaming/*` command/read bindings, and `MimirProgramStreamer` no longer knows a default RTMP target or launches `Mimir.Broadcast`. The Well stream-pressure status string was corrected from the old WebSocket/JSON label to `cultmesh-cache-publisher`.
|
||||
- Eve dialogue/relay/broadcast verification passed: `dotnet build .\src\Mimir.EveDialogue\Mimir.EveDialogue.csproj`, `dotnet build .\src\Mimir.EveRelay\Mimir.EveRelay.csproj --no-restore`, `dotnet build .\src\Mimir.Broadcast\Mimir.Broadcast.csproj --no-restore`, `dotnet build .\src\Mimir.Runtime\Mimir.Runtime.csproj --no-restore`, `dotnet build .\src\Mimir.Well\Mimir.Well.csproj --no-restore`, PowerShell parser check for `scripts\start-eve-dashboard-stream.ps1`, `git diff --check` on the touched relay/broadcast/runtime/docs/map files, and focused scans found no remaining active `MIMIR_STREAM_TARGET`, default localhost RTMP, program-stream command binding, WebSocket handshake/frame implementation, Eve relay listener, or Eve dialogue listener residue outside archive/tombstone references and this inventory.
|
||||
- Active Mimir branch Raven/localcast media side-door cut: `Mimir.RavenDaemon` is now a fail-closed archive stub instead of supervising FFmpeg display/audio capture into direct SRT/TCP targets. The Raven sender/demux/audio/screen scripts, stream-proof recorder, localcast sender/stop/discover/desktop launchers, and localcast FFmpeg installer now fail closed rather than constructing SRT URLs or spawning FFmpeg transport processes.
|
||||
- Active Mimir branch runtime configs no longer declare Raven SRT receivers: `mimir-runtime.well.local.json`, `mimir-runtime.stream-proof.local.json`, `mimir-runtime.raven-eve.example.json`, `mimir-runtime.move-stream.local.json`, and `mimir-runtime.example.json` had direct Raven/neighbor network source entries removed. `audio-field.example.json` now models the neighbor dialogue anchor as `CultMeshMedia` with a `cultmesh://odin/...` URI instead of `transport.kind = SRT`, and `localcast.example.json` is an archive marker.
|
||||
- Raven/localcast docs and state maps no longer describe direct SRT as the live bridge path. The Raven capture mux, neighbor sender, OBS receiver, move-controller stream, Spout, implementation plan, viable-stream, native rebuild, perfect-machine, and code algorithm map docs now say media must return through Odin/CultMesh-discovered lanes before those profiles are live again.
|
||||
- Raven/localcast verification passed: JSON validation for the seven touched config files, PowerShell parser checks for the twelve archived transport scripts, `dotnet build .\src\Mimir.RavenDaemon\Mimir.RavenDaemon.csproj --no-restore`, `git diff --check` on the touched Raven/localcast config/script/doc/map files, and focused scans found no active `srt://`, `tcp-listener`, Raven media target env, Raven port, localcast sender, or Raven sender launcher residue outside archive/tombstone references and this inventory.
|
||||
- Active Mimir branch OBS/Muninn RUDP side-door cut: the native OBS plugin no longer registers `Muninn Stream`, `Muninn Video`, or `Muninn Audio` sources. The plugin now registers only `Mimir Program Texture` and `Mimir Audio Stem`, so OBS can lower Fensalir/Mimir shared-memory outputs but cannot discover Muninn, publish activation commands, listen on Muninn catalog/media UDP ports, or reassemble Muninn RUDP media directly.
|
||||
- The old OBS Muninn RUDP body was removed from the native plugin build: `mimir_obs_muninn_source.cpp`, the RUDP ack/fragment/url headers, and `muninn_rudp_url_tests.cpp` are deleted; `CMakeLists.txt` no longer builds the Muninn media decoder probe or URL test target and no longer links the plugin against `ws2_32`. `tools\muninn_media_decoder_probe.cpp` and `scripts\restart-obs-muninn-source.ps1` are fail-closed archive stubs.
|
||||
- OBS/Muninn verification passed: `cmake -S native\obs_stem_source -B native\obs_stem_source\build`, `cmake --build native\obs_stem_source\build --config Release --target mimir_obs_stem_source`, PowerShell parser check for `scripts\restart-obs-muninn-source.ps1`, `git diff --check` on the touched OBS plugin/script/doc/probe files, and focused scans found no active `rudp://`, Muninn RUDP parser, Muninn source registration, command target, media target, catalog port, media port, or OBS Muninn source residue outside archive/tombstone references and this inventory.
|
||||
- Active Mimir branch Well-to-OBS log-tail side-door cut: `Mimir.WellObsDaemon` is now a fail-closed archive stub instead of tailing `mimir.well_capture_page.v1` JSON logs and republishing audio stems to `Local\MimirObsStemBus`. `scripts\start-well-obs-daemon.ps1` is also a fail-closed archive stub. OBS stems must come from the Mimir/Fensalir media owner and shared-memory lowering, not from a Well log tail daemon.
|
||||
- Well-to-OBS verification passed: PowerShell parser check for `scripts\start-well-obs-daemon.ps1`, `dotnet build .\src\Mimir.WellObsDaemon\Mimir.WellObsDaemon.csproj` after restore, `git diff --check` on the touched daemon/script/map files, and focused scans found no active Well-to-OBS launcher or daemon log-tail route outside archive/tombstone references and this inventory. `dotnet build --no-restore` initially failed only because `src\Mimir.WellObsDaemon\obj\project.assets.json` was absent; the restore-backed build succeeded.
|
||||
- Active Mimir branch Fensalir Well intake side-door cut: `Mimir.FensalirDaemon` no longer discovers or tails Well JSONL/log artifacts. `--well-log`, `FindLatestWellLog`, `WellLogTailState`, and the file-tail helper were removed from the live path. The daemon now requires `--well-cultmesh-cache` / `MIMIR_WELL_CULTMESH_CACHE` and follows `mimir.well_published_document.v1` envelopes from the Well CultMesh cache. The shared `MimirWellPublishedDocument` contract moved into `Mimir.Runtime` so Well and Fensalir read/write the same typed document.
|
||||
- The Fensalir launchers now pass the run-local Well CultMesh cache instead of recorder `observations.jsonl`: `scripts\start-online-verse-daemons.ps1` uses `--well-cultmesh-cache $WellCultMeshCache`, and `scripts\start-fensalir-daemon.ps1` requires `-WellCultMeshCache` / `MIMIR_WELL_CULTMESH_CACHE` instead of scanning artifacts for Well logs. Docs and state maps now describe recorder JSONL as an offline ledger, not daemon transport authority.
|
||||
- Fensalir Well-intake verification passed: `dotnet build .\src\Mimir.FensalirDaemon\Mimir.FensalirDaemon.csproj --no-restore`, `dotnet build .\src\Mimir.Well\Mimir.Well.csproj --no-restore`, PowerShell parser checks for `scripts\start-fensalir-daemon.ps1` and `scripts\start-online-verse-daemons.ps1`, `git diff --check` on the touched Fensalir/Well/runtime/script/doc/map files, and a negative smoke confirmed `dotnet run --no-build --project .\src\Mimir.FensalirDaemon\Mimir.FensalirDaemon.csproj -- --well-log removed.jsonl ...` rejects the archived log-tail option. Focused scans found no remaining active Fensalir JSONL/log-tail intake outside the deliberate rejection guard and this inventory.
|
||||
- Active Mimir branch runtime transport catalog side-door cut: `MimirNetworkTransportConfigurations.BuiltIn` now contains only CultMesh typed state plus CultMesh reliable-UDP media/body lanes. The former raw LAN UDP debug window, SRT diagnostic bridge, and WebRTC experimental media entries were deleted instead of left as tempting observe-only profiles.
|
||||
- Mimir authority and OBS publication catalogs no longer grant any model space to diagnostic SRT bridges: the `srt-program-bridge` observe-only authority rule, `DiagnosticSrtBridge` publication profile, SRT diagnostic enum values, and related module-library wording were removed. OBS publication profiles now describe only Spout/shared-texture/Eve-native video and Faust/ASIO audio shapes.
|
||||
- Eve program output and dashboard config no longer carry direct network defaults as diagnostic invitations: `MimirEveProgramOutputConfiguration` dropped `TargetHost`/`TargetPort` and the Moonlight/Sunshine diagnostic profile, the unused `BrowserDiagnostics` witness kind was removed, and the StreamPixels EveDashboard provider no longer advertises HTTPS, SSH-forwarded localhost, HLS, or RTMP ingest nodes.
|
||||
- Runtime transport-catalog verification passed: `dotnet build .\src\Mimir.Runtime\Mimir.Runtime.csproj --no-restore`, `dotnet build .\src\Mimir.EveDashboard\Mimir.EveDashboard.csproj --no-restore`, `dotnet build .\src\Mimir.BufferSmoke\Mimir.BufferSmoke.csproj` after restore, `dotnet run --project .\src\Mimir.BufferSmoke\Mimir.BufferSmoke.csproj --no-build -- --eve-program-output-contract-smoke`, `dotnet run --project .\src\Mimir.BufferSmoke\Mimir.BufferSmoke.csproj --no-build -- --perfect-machine-profile-smoke`, `git diff --check` on touched runtime/dashboard/smoke/doc files, and focused scans found no remaining active SRT bridge, WebRTC experiment, LAN UDP debug window, browser diagnostic witness, Moonlight probe, Eve output host/port, or StreamPixels HTTPS/localhost/RTMP dashboard residue outside this inventory.
|
||||
- Active Mimir branch subprocess stream-source side-door cut: `MimirRuntimeConfiguration` no longer constructs `frame-events`, `json-lines`, `ffmpeg-rawvideo`, `rawvideo-process`, `ffmpeg-pcmaudio`, `pcmaudio-process`, or generic `process` stream adapters from config. The child-process stdout/JSONL/FFmpeg stream-source classes were deleted, and `Mimir.BufferSmoke --ffmpeg-rawvideo-source-smoke` was removed so tests cannot keep the adapter alive as a backdoor.
|
||||
- Mimir runtime config examples no longer advertise process-backed probes: `mimir-runtime.example.json` contains only native/local sources, `mimir-runtime.probes.example.json` is ASIO-focused with no JSONL probe subprocesses, `mimir-runtime.sync-smoke.example.json` is an empty-stream sync settings shell, and `mimir-runtime.asio.example.json` no longer carries the disabled Raven display frame-event receiver. Native probe tools may remain development tools, but they do not feed daemon runtime buffers unless rebuilt as CultMesh publishers.
|
||||
- Subprocess stream-source verification passed: `dotnet build .\src\Mimir.Runtime\Mimir.Runtime.csproj --no-restore`, `dotnet build .\src\Mimir.BufferSmoke\Mimir.BufferSmoke.csproj --no-restore`, JSON parsing for all `config\mimir-runtime*.json`, `git diff --check` on touched runtime/config/doc files, and focused scans found no remaining active process/FFmpeg/frame-event stream adapter construction, config adapter entries, `arguments` config knobs, or FFmpeg raw-video smoke route outside this inventory.
|
||||
- Active Mimir branch program-recording actuator cut: the runtime `Output` panel no longer exposes `program.record.start`, `program.record.stop`, `/commands/program/record/*`, or the recording-folder read binding. `MimirProgramRecorder` is now a fail-closed archived status object and cannot spawn PowerShell, `scripts\record-stream-proof-output.ps1`, FFmpeg, or the old Raven sender switch.
|
||||
- Program-recording verification passed: `dotnet build .\src\Mimir.Runtime\Mimir.Runtime.csproj --no-restore`, `dotnet build .\src\Mimir.BufferSmoke\Mimir.BufferSmoke.csproj --no-restore`, PowerShell parser check for the already archived `scripts\record-stream-proof-output.ps1`, `git diff --check` on touched runtime/doc files, and focused scans found no remaining active `program.record` command route, `MIMIR_RECORD_*` knob, `StartRavenSender` switch, recorder `ProcessStartInfo`, or recording child-process launch path outside archive/status text and this inventory.
|
||||
- Active Mimir branch Nightwing Move SSH/hidraw side-door cut: `scripts\start-online-verse-daemons.ps1` no longer launches `tools\online_move_music_sync.py`, no longer exposes `-SkipMoves`, `-MoveFps`, `-MoveFftSize`, or direct `-Move ... /dev/hidraw...` parameters, and no longer records a `moveSyncPid` in the supervisor manifest. Online Move actuation must return as typed command schedules through Odin/CultMesh, with Nightwing owning local HID writes and receipts.
|
||||
- The live Starfire-to-Nightwing Move bridge tools are fail-closed archive stubs: `tools\nightwing_psmove_music_pulse.py`, `tools\online_move_music_sync.py`, and `tools\music_keyed_move_chirp_sync.py` no longer import ASIO/FFmpeg readers, open SSH sessions, write remote Python receivers, or stream PS Move report `0x06` frames to `/dev/hidraw*`. `tools\sync_sweep.py` remains offline receipt tooling only, with its comment corrected so it does not call a historical HID path the live authority.
|
||||
- Nightwing Move side-door verification passed: PowerShell parser check for `scripts\start-online-verse-daemons.ps1`, bundled Python `py_compile` for the three archived tools plus `tools\sync_sweep.py`, `dotnet build .\src\Mimir.Runtime\Mimir.Runtime.csproj --no-restore`, `dotnet build .\src\Mimir.BufferSmoke\Mimir.BufferSmoke.csproj --no-restore`, `git diff --check` on touched supervisor/tool/doc/state/runtime files, and focused scans found no remaining active online Move worker launch, Move supervisor parameter, `moveSyncPid`, or live `nightwing_psmove_music_pulse` / `music_keyed_move_chirp_sync` / `online_move_music_sync` references outside archive stubs, historical evidence, and this inventory.
|
||||
- Active Mimir branch EVE program-output SSH probe cleanup: `scripts\start-eve-program-output.ps1` no longer accepts `-Target eve` and no longer performs an SSH readiness probe before starting the local shared-D3D12-texture lowering. The launcher now only sets the local Fensalir shared-texture environment and starts the local app process. The EveDashboard StreamPixels provider tile status was also changed from `ssh ready` to `cultmesh`.
|
||||
- EVE program-output SSH cleanup verification passed: PowerShell parser check for `scripts\start-eve-program-output.ps1`, `dotnet build .\src\Mimir.EveDashboard\Mimir.EveDashboard.csproj --no-restore`, `git diff --check` on touched launcher/dashboard files, and focused scans found no remaining `Checking EVE SSH`, `mimir-eve-program-output-check`, or `ssh ready` residue outside this inventory.
|
||||
- Odin Muninn media side-door cut: Muninn no longer accepts direct media hosts as authority. Capture commands still carry the legacy `target_host` wire field, but it must be a `cultmesh://` media target URI. The activation child pulls Odin provider advertisements over CultMesh/RUDP and resolves a provider-owned `muninn.media.rudp.v1` endpoint before opening the CultNet RUDP media lane. `request-stream` now publishes `muninn.capture_stream_command.v1` through Odin/CultMesh with `--odin-cultmesh-uri`; it no longer writes directly to the activation store or sends raw command documents to `--capture-command-rudp-target`. `serve` pulls typed capture commands from Odin before processing its local activation persistence and no longer opens a `--capture-command-rudp-bind` listener.
|
||||
- The Muninn daemon vocabulary no longer contains the legacy media transport, OBS target flags, standalone OBS catalog RUDP publisher, direct RUDP URL formatter, or old process-spawner mux path. `scripts\activate-muninn-raven-av-srt.ps1` was removed; `scripts\activate-muninn-raven-media.ps1` is a fail-closed archive stub. `scripts\restart-muninn.ps1` now redeploys only the serve task, requires `-MediaTargetUri` / `MUNINN_MEDIA_TARGET_URI` as a `cultmesh://` URI, and unregisters obsolete Raven activation/video-proof scheduled tasks instead of repairing them. `scripts\health-muninn.ps1` fails if those obsolete tasks reappear.
|
||||
- Odin Muninn media verification passed: `cargo test -p muninn-daemon`, PowerShell parser checks for `scripts\restart-muninn.ps1`, `scripts\health-muninn.ps1`, and `scripts\activate-muninn-raven-media.ps1`, `git diff --check` on touched Muninn/Idunn docs and scripts, and a focused scan found no remaining live SRT, OBS target, OBS catalog RUDP, direct media target host env, or old activation-script residue outside the deliberate obsolete-task removal guards and this inventory.
|
||||
- Muninn capture command-route verification passed: `cargo test -p muninn-daemon` passed with `request_stream_rejects_raw_capture_command_rudp_target`, `serve_rejects_removed_capture_command_rudp_bind`, and `request_stream_requires_odin_cultmesh_command_route`; stale `request-stream --capture-command-rudp-target` and `serve --capture-command-rudp-bind` invocations fail closed; PowerShell parser checks passed for `scripts\restart-muninn.ps1` and `scripts\health-muninn.ps1`; `git diff --check -- crates/muninn-daemon/src/main.rs scripts/restart-muninn.ps1 scripts/health-muninn.ps1 docs/transport-shortcut-inventory.md` passed; focused scans found no surviving `publish_capture_command_rudp`, capture-command RUDP bind listener, capture-command frame decoder, launcher `CaptureCommandRudp*` parameter, active `--capture-command-rudp-target`, or active `--capture-command-rudp-bind` path outside intentional removed-flag parser guards/tests and this inventory.
|
||||
- Odin Sleipnir launcher side-door cut: `scripts\start-starfire-sleipnir.ps1` is now a fail-closed archive stub. Starfire Sleipnir had no Idunn catalog target and the script directly killed/spawned `sleipnir.exe` while passing Odin/RUDP knobs, so it was an unmanaged lifecycle path rather than a coherent daemon deployment lane.
|
||||
- Raven Sleipnir remains an Idunn-managed target through `scripts\idunn-deployment-targets.ps1`, `deploy-raven-sleipnir.ps1`, and `restart-raven-sleipnir.ps1`; those actuator scripts are guarded by `IDUNN_ACTUATOR=1` / `IDUNN_COMMAND_AUTHORITY=idunn-daemon`. Remaining Sleipnir raw socket options are HID input bootstrap substrate only; command selection and command delivery are now the Odin/CultMesh route.
|
||||
- Sleipnir command-route side-door cut: `scripts\restart-raven-sleipnir.ps1` no longer requires `SLEIPNIR_COMMAND_RUDP_ADVERTISE` or passes `--command-rudp-advertise`. `crates\sleipnir-daemon` rejects the old flag, publishes the input-mapping command route as `cultmesh://odin/command/sleipnir/input_mapping` with `resolver: odin-cultmesh`, pulls `sleipnir.input_mapping.v1` from Odin discovery, and chooses the newest local-or-Odin mapping record. Hermodr refuses raw Sleipnir command endpoints and publishes mapping commands to Odin's configured CultMesh/RUDP substrate instead of dialing the advertised command address.
|
||||
- Sleipnir command-ingress side-door cut: `crates\sleipnir-daemon` no longer starts a `--command-rudp-bind` listener, decodes raw command frames, or applies command mappings from an in-process RUDP receiver. `scripts\restart-raven-sleipnir.ps1` no longer passes `--command-rudp-bind`, and stale invocations fail closed with a removed-flag error. Sleipnir command state arrives through Odin discovery as `sleipnir.input_mapping.v1`.
|
||||
- Odin discovery endpoint sanitation cut: `src\odin\interfaces.cjs` and `src\odin\provider-ingress.cjs` now sanitize provider `endpoints` and `routes` before exposing them through Odin surfaces. CultCache/CultMesh witness locations survive, and CultNet command routes survive only when the advertised address is a `cultmesh:` URI; raw `rudp://`, HTTP, WebSocket, and host:port endpoints no longer become provider interface truth. Live RUDP announcement source sockets are provenance only; fallback provider endpoints now use `cultmesh:live:<providerId>`.
|
||||
- Sleipnir launcher verification passed: PowerShell parser check for `scripts\start-starfire-sleipnir.ps1` and `git diff --check` on the touched Sleipnir launcher/inventory files. Focused follow-up scan still shows separate Odin-repo HTTP/WebSocket/debug-lowering text and health-wrapper residue that should be treated as the next cleanup target, not as approval to keep side doors.
|
||||
- Sleipnir command-route implementation verification passed: `node --check src\hermodr-daemon.cjs`, `cargo test -p sleipnir-daemon`, and `git diff --check -- src/hermodr-daemon.cjs crates/sleipnir-daemon/src/main.rs` passed. Focused scans found no surviving `publishSleipnirMappingRudp` direct command publisher or raw `commandEndpoint` receipt path.
|
||||
- Sleipnir command-ingress verification passed: `cargo test -p sleipnir-daemon rejects_removed_command_rudp_bind`, `cargo run -q -p sleipnir-daemon -- --command-rudp-bind 127.0.0.1:17889` fails closed with the removed-flag error, PowerShell parser check passed for `scripts\restart-raven-sleipnir.ps1`, and focused scans found no surviving `start_command_rudp_ingress`, `run_command_rudp_ingress`, `apply_pending_command_mappings`, command decoder, command receiver channel, launcher `CommandRudpBind`, or command route `connectionId` outside the intentional parser guard/test.
|
||||
- Odin Idunn health-probe side-door cut: `scripts\health-stonks.cmd`, `scripts\health-vili.cmd`, and `scripts\health-mimir-eve-dashboard.cmd` are fail-closed archive stubs instead of localhost HTTP curl probes. `scripts\idunn-deployment-targets.ps1` now names Stonks and Vili health as daemon-published RUDP contracts, and `scripts\restart-vili.ps1` verifies Raven Vili restart through the daemon state file plus configured Idunn RUDP health status instead of polling HTTP `/operator-state`.
|
||||
- The Nightwing Eve dashboard health script no longer requires `http-websocket-client-lowering + websocket-binary-command-lowering` as the input transport profile. It now fails if HTTP/WebSocket reappears in that field, making the old shape a visible degradation instead of an accepted transport profile.
|
||||
- Idunn health-probe verification passed: PowerShell parser checks for `scripts\restart-vili.ps1`, `scripts\health-nightwing-eve-dashboard.ps1`, and `scripts\idunn-deployment-targets.ps1`; direct `cmd /c` probes confirmed the three archived `.cmd` wrappers exit 2; `cargo test -p idunn-daemon` passed; focused scans outside this inventory found no remaining Stonks/Mimir localhost health URLs, Vili HTTP operator-state wait, Stonks/Vili catalog health-script references, or old Nightwing Eve dashboard HTTP/WebSocket input-transport assertion.
|
||||
- Hermodr browser-lowering correction: the earlier archive-stub cut was an overcut. `src\hermodr-daemon.cjs` has been restored as a localhost browser lowering that reads Odin's typed `gamecult.eve.surface_state.v1` and provider advertisement records from the configured Odin CultMesh store, exposes `/hermodr/verse/catalog` and `/hermodr/surface/<providerId>` for browser use, reports `/health` as `authority=lowering-only`, and publishes browser-origin command ergonomics only as typed command documents to Hermodr's CultMesh command store. It still rejects old Odin HTTP/WebSocket discovery, `latest-surface.json`-style renderer exports, raw sleipnir/Aetheria RUDP command endpoints, and `muninnRudp` transport hints.
|
||||
- Odin package scripts advertise `hermodr:start` again because the lowering is valid browser ergonomics. The script starts a lowering adapter only; it does not restore provider discovery, daemon health, or raw command transport authority.
|
||||
- Odin utility and Idunn localhost-ingress cut: `src\odin\utils.cjs` no longer exports a generic `httpGet` helper, and `crates\idunn-daemon` no longer defaults `--rudp-health-bind` to `127.0.0.1:17870`. Bare Idunn starts without a health ingress; the owned Starfire bootstrap still supplies `--rudp-health-bind 0.0.0.0:17870` explicitly.
|
||||
- Idunn generated-state breadcrumb cut: `crates\idunn-daemon` no longer emits `/eve/deck/nightwing-gjallar`, `renderer-only lowering`, `browser/WebSocket bridge`, or `HTTP stays a browser lowering` in its live surgery-plan strings. Gjallar/Nightwing Eve docs now point at accepted CultMesh/Eve state and CultNet/RUDP snapshots instead of deck/WebSocket routes as named live paths.
|
||||
- Aetheria Odin route shortcut cut: `E:\Projects\Aetheria\Aetheria.State.Daemon\Program.cs` no longer advertises raw `cultmesh-rudp-document-put` / `aetheria-command-ingress` routes in Odin provider advertisements. Provider records now expose only Odin/CultMesh announcement routes with `resolver: odin-cultmesh`; command ergonomics still need a proper CultMesh/Odin command document path rather than a provider-dialed shortcut.
|
||||
- Aetheria Electron daemon-target side-door cut: `E:\Projects\Aetheria\Aetheria.Rts.Web\Electron\main.ts` no longer invents or accepts a raw `AETHERIA_RTS_DAEMON_ENDPOINT` as the daemon identity. The configured/default target is now `AETHERIA_RTS_CULTMESH_URI` / `cultmesh://odin/aetheria/rts/<daemon>`, and stale raw endpoint config fails closed.
|
||||
- Aetheria RTS client path cut: `E:\Projects\Aetheria\Aetheria.Rts.Web\Electron\aetheria-cultmesh.ts` now separates the daemon's CultMesh URI from resolved peer-card transport endpoints. Command/query route metadata uses the CultMesh URI, raw `rudp://` strings are rejected as daemon targets, and the peer opens through `CultMesh.createRudpPeerForAuthorizedPeer(...)` using a peer catalog plus authority lease instead of `createRudpPeer(..., this.endpoint)`.
|
||||
- Aetheria verification passed for the route/target cut: `npm run build:electron`, isolated-output `dotnet build E:\Projects\Aetheria\Aetheria.State.Daemon\Aetheria.State.Daemon.csproj --no-restore -p:UseAppHost=false -p:OutDir=<temp>`, and `git diff --check` on touched Aetheria/Odin inventory files passed. Focused scans found no surviving `AetheriaCommandRoute`, `aetheria-command-ingress`, `cultmesh-rudp-document-put`, old `rtsCultMeshEndpoint`, direct `createRudpPeer(... this.endpoint)`, or remote verifier `AETHERIA_STAGE7D_REMOTE_ENDPOINT`. `verify-stage7b-rts-client.ps1` still fails before transport assertions because Aetheria generated RTS bindings are already stale; I did not generate a broad binding diff during transport cleanup.
|
||||
- Spotiverse WebSocket/HTTP side-door cut: `E:\Projects\Spotiverse\src\spotiverse-daemon.cjs` no longer registers an HTTP upgrade handler, tracks WebSocket clients, broadcasts provider state over `/eve/deck`, advertises debug WebSocket/HTTP routes, exposes local `/health`, `/spotify/state`, or `/spotify/search`, or includes an Eve WebSocket command frame template in surface metadata. The local HTTP listener remains only for Heimdall/Spotify auth routes plus a fail-closed removed-command tombstone that points stale callers at `spotiverse.command_request.v1`.
|
||||
- Spotiverse launcher/export/doc follow-through: `scripts\start-spotiverse.ps1` no longer prints local health or Eve deck URLs, `scripts\export-provider-advertisement.cjs` no longer exports listener Verse or deck stream metadata, and `AGENTS.md`, `README.md`, `docs\architecture.md`, and `docs\verse-service-contract.md` now describe CultMesh/CultCache surface witnesses and typed command documents instead of `/eve/deck` renderer discovery.
|
||||
- Spotiverse verification passed: absolute Node syntax check for `src\spotiverse-daemon.cjs`, PowerShell parser check for `scripts\start-spotiverse.ps1`, BOM-tolerant provider advertisement export check, `git diff --check` on touched Spotiverse files, and focused scans found no remaining `/eve/deck`, WebSocket, debug lowering, local state/search/health HTTP route, or launcher invitation residue outside this inventory.
|
||||
- gamecult-ops service-check health shortcut cut: `E:\Projects\gamecult-ops\scripts\check-heimdall.sh`, `check-repixelizer-gui.sh`, and `check-streampixels-preview.sh` no longer trust localhost HTTP health JSON for CultMesh/Idunn daemon truth. The checks validate configured Idunn/CultMesh environment and non-empty CultCache/CultMesh witness files directly; remaining HTTP calls are product smoke probes for auth/config/nginx/web routing, not daemon-health owners.
|
||||
- gamecult-ops Bifrost HTTP health gate cut: `E:\Projects\gamecult-ops\scripts\check-bifrost-alpha.sh` was deleted, `compose\bifrost.yggdrasil.yaml` no longer has a Docker healthcheck that curls `127.0.0.1:5080/healthz`, `scripts\deploy-bifrost-container.ps1` no longer curls `/healthz` or `/readyz`, and Bifrost runbooks now say daemon health must be added as a CultMesh/Idunn witness before it can be an operations gate.
|
||||
- gamecult-ops verification passed for this cut: PowerShell parser check for `scripts\deploy-bifrost-container.ps1`, `git diff --check` on touched ops files, focused scans found no remaining migrated-service `health_json` / Idunn-from-HTTP parsing and no Bifrost `5080/healthz` / `5080/readyz` deploy gate. POSIX `bash -n` could not run because the available `bash.exe` is the WSL shim and WSL has no `/bin/bash` in this environment.
|
||||
- Hermodr Aetheria command shortcut cut: Hermodr rejects `HERMODR_AETHERIA_COMMAND_RUDP` / `--aetheria-command-rudp` outright, no longer synthesizes Aetheria command routes into its embedded catalog, and accepts Aetheria commands only when Odin advertises a `cultmesh://` command route. Hermodr publishes `gamecult.eve.command.v1` to Odin over the configured Odin CultMesh/RUDP substrate instead of dialing the advertised route address. The Aetheria viewport snapshot bypass was removed; Hermodr lowers documents already present through Odin and no longer opens a direct Aetheria RUDP peer for viewport records.
|
||||
- Hermodr Aetheria route verification passed: `node --check src\hermodr-daemon.cjs`, `git diff --check -- src/hermodr-daemon.cjs`, and focused scans found no surviving `publishRawDocumentPutRudp`, `openAetheriaRudpPeer`, raw Aetheria viewport snapshot reader, `CultNetPeer`, `rudpUri`, or MessagePack encode command path in `src\hermodr-daemon.cjs`.
|
||||
- Odin/Idunn endpoint example cleanup: `README.md` no longer teaches `127.0.0.1:17871` as the Odin CultMesh/RUDP catalog endpoint; it names a configured `<odin-cultmesh-rudp-endpoint>`. `src\Idunn\README.md` no longer teaches a localhost `--rudp-health-bind` example or says the daemon defaults to `127.0.0.1:17870`. `scripts\start-starfire-wireguard-elevated.ps1` no longer curls local Ollama HTTP as part of WireGuard bootstrap diagnostics.
|
||||
- Endpoint example cleanup verification passed: PowerShell parser check for `scripts\start-starfire-wireguard-elevated.ps1`, `git diff --check` on the touched README/WireGuard files, and a focused scan found no remaining `127.0.0.1:17871`, demo `--rudp-health-bind 127.0.0.1:17870`, "defaults to 127.0.0.1:17870", or local Ollama curl diagnostic residue outside this inventory.
|
||||
- Odin Mimir observation-log side-door cut: `src\odin\config.cjs`, `src\odin\state.cjs`, `src\odin\surface.cjs`, and `src\odin-coordinator.cjs` no longer tail `E:\Projects\Mimir\artifacts\runtime\periwinkle-cultmesh-sensors.out.log` or any other Mimir JSONL observation artifact as Odin discovery input. `src\odin\observations.cjs` was deleted, stale `--observationLogPath` / `--observationFreshSeconds` invocations fail closed, and Odin's surface summary no longer derives live stream counts from artifact logs.
|
||||
- Observation-log cut verification passed: `node scripts/check-syntax.cjs`, stale `buildConfig(['--observationLogPath','x'])` rejection smoke, `git diff --check` on the touched Odin/docs files, and focused scans found no remaining Mimir observation ledger/default artifact path/observation tailer references outside the intentional removed-option guard and this inventory. Observation streams must re-enter as provider-owned CultMesh/Odin records or retained Eve/CultUI surfaces, not as an Odin artifact tail.
|
||||
- Weksa Idunn health-command side-door cut: `scripts\idunn-deployment-targets.ps1` now names Weksa health as `daemon-published-rudp:weksa.cultnet-rudp-provider-health`, and Odin's `scripts\health-weksa.cmd` is a fail-closed archive stub instead of calling Weksa's local `--health --host 127.0.0.1 --port 8813` diagnostic path.
|
||||
- Weksa repo follow-through: `E:\Projects\weksa\scripts\health-weksa-daemon.cmd` is also a fail-closed archive stub, and `E:\Projects\weksa\scripts\start-weksa-daemon.ps1` no longer runs `weksa-daemon.mjs --health` after launch or to accept an existing PID. It now checks the daemon process plus fresh `provider-advertisement-store.cc` witness while Weksa publishes `weksa.cultnet-rudp-provider-health` to Idunn over CultNet/RUDP.
|
||||
- Weksa daemon health-mode cut: `E:\Projects\weksa\scripts\weksa-daemon.mjs` no longer accepts `--health` or prints command-mode health JSON from the local provider store. The archived wrapper has no surviving daemon flag target; diagnostics must inspect typed CultMesh/CultCache witnesses or Idunn RUDP health explicitly.
|
||||
- Weksa direct RUDP command target cut: `E:\Projects\weksa\scripts\weksa-daemon.mjs` no longer advertises `rudp://$host:$port` in provider advertisements, bindings, CultMesh publication metadata, or command boundaries. `speech_provider.mimo.voicedesign` now publishes `cultmesh://odin/command/weksa/speech_provider.mimo.voicedesign`; the RUDP bind is recorded only as daemon-local runtime substrate. `--mimo-command-json` was removed from the daemon CLI so local direct-send command mode cannot bypass Odin discovery.
|
||||
- Weksa health-command verification passed: PowerShell parser check for `E:\Projects\weksa\scripts\start-weksa-daemon.ps1`, direct `cmd /c` probes confirmed Odin and Weksa health wrappers exit 2, `cargo test -p idunn-daemon` passed, focused scans found no remaining Idunn Weksa health-script catalog reference or `--health --host 127.0.0.1 --port 8813` wrapper call, and `git diff --check` passed in both Odin and Weksa for the touched files.
|
||||
- VoidBot Idunn health-command side-door cut: `scripts\idunn-deployment-targets.ps1` now names VoidBot health as `daemon-published-rudp:voidbot.cultnet-rudp-stack-health`, and `scripts\health-voidbot.cmd` is a fail-closed archive stub. The PowerShell witness inspector remains available for explicit diagnostics, but the easy `.cmd` health command can no longer satisfy or imply Idunn daemon truth.
|
||||
- Idunn catalog health side-door cut: `scripts\idunn-deployment-targets.ps1` now names Muninn-family, Nightwing Gjallar, Yggdrasil StreamPixels/Heimdall/Repixelizer, Nightwing Eve dashboard, and Nightwing Eve browser reference health as daemon-published RUDP contracts instead of local health script paths. The matching health `.cmd` wrappers are fail-closed archive stubs; deeper PowerShell and remote check bodies remain explicit witness/deployment inspections only, not Idunn health truth.
|
||||
- Muninn restart health-probe tail cut: `scripts\restart-muninn.ps1` and `scripts\restart-starfire-muninn.ps1` no longer invoke `health-muninn.ps1` / `health-starfire-muninn.ps1` after relaunch. Restart scripts verify the process and explicit Idunn RUDP contract arguments; Idunn accepts liveness only from daemon-published CultNet/RUDP health.
|
||||
- Raven capture mux health alias cut: `scripts\health-raven-capture-mux.ps1` and `.cmd` are fail-closed archive stubs instead of aliases to `health-muninn.ps1`.
|
||||
- Odin self-health side-door cut: `scripts\idunn-deployment-targets.ps1` now names Odin health as `daemon-published-rudp:odin.cultnet-rudp-provider-health`, `scripts\health-odin.cmd` is a fail-closed archive stub, and `scripts\restart-odin.ps1` no longer invokes `health-odin.ps1` after start.
|
||||
- Vili/hermodr health-command tail cut: `E:\Projects\Vili\scripts\health-vili-daemon.cmd` is a fail-closed archive stub, Vili start/restart scripts no longer run `vili-daemon.mjs --health --host 127.0.0.1`, Vili no longer exposes or advertises HTTP `/health`, Vili provider/transport records no longer advertise HTTP/WebSocket debug lowerings as Verse discovery, `scripts\restart-vili.ps1` no longer deploys the Vili health wrapper to Raven, and `scripts\restart-hermodr.ps1` is now a fail-closed archive stub.
|
||||
- Vili WebSocket deck path cut: `E:\Projects\Vili\scripts\vili-daemon.mjs` no longer imports `ws`, creates a `WebSocketServer`, or handles `/eve/deck` upgrades; `package.json` / `package-lock.json` no longer depend on `ws`, the npm `health` shortcut was removed, and Odin's Raven Vili deployment bundle no longer copies `node_modules\ws`.
|
||||
- gamecult-ops Nightwing visible-ops side-door cut: `E:\Projects\gamecult-ops\scripts\nightwing-visible-ops-tui.py`, `nightwing-visible-ops-fb.py`, `nightwing-visible-ops-console.sh`, and `nightwing-visible-ops-fb-console.sh` are fail-closed archive stubs. The old TUI/framebuffer path no longer contains a hand-rolled WebSocket client, frame parser, browser-deck URL argument, mouse-to-layout command channel, tmux launcher, framebuffer launcher, or default Starfire deck target. Nightwing visible ops now points at Gjallar consuming Odin's accepted CultMesh/CultNet surface snapshot.
|
||||
- Nightwing visible-ops runbook follow-through: `E:\Projects\gamecult-ops\runbooks\nightwing-cultmesh-worker-tunnel.md` no longer teaches local Odin HTTP health, direct Eve deck URLs, or renderer layout intents over the deck path as an operations route. It describes Odin as the typed CultMesh/CultNet authority and Gjallar as the lowering product; renderer input must become typed Odin documents before it can affect layout.
|
||||
- gamecult-ops Verse doctrine cleanup: `E:\Projects\gamecult-ops\docs\verse-service-architecture.md` no longer says HTTP/WebSocket compatibility bridges may remain as live lowering routes, and `scripts\audit-verse-services.ps1` no longer treats `/eve/deck` or `websocket` text as positive CultMesh/Eve readiness signals. Browser/native inspection endpoints are now explicit debug exports only; they cannot be provider discovery, daemon health, command transport, or service identity.
|
||||
- Nightwing visible-ops verification passed: bundled Python `py_compile` passed for the two archived Python scripts, PowerShell parser check passed for `scripts\audit-verse-services.ps1`, `git diff --check` passed for the touched visible-ops scripts, Nightwing runbook, Verse architecture doc, audit script, and Odin inventory, and focused scans found no remaining executable visible-ops `WebSocket`, `ws://`, `/eve/deck`, `GAMECULT_VISIBLE_OPS_URL`, or `visible_ops.WebSocket` path outside archive/inventory text. Plain `python` on PATH is only the Microsoft Store alias in this environment, so verification used `C:\Users\Meta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\python.exe`.
|
||||
- Odin/Idunn generated-vocabulary cut: `crates\idunn-daemon\src\main.rs`, `README.md`, `docs\idunn.md`, `docs\architecture.md`, and `docs\gjallar.md` no longer preserve HTTP/WebSocket/deck route lists in generated surgery plans, command boundaries, transport profiles, or primary Odin/Idunn/Gjallar docs. The records now name product/debug probes as non-authority and point Weksa/sleipnir command ergonomics at typed Odin/CultMesh command documents rather than daemon-owned RUDP command ingress/route wording.
|
||||
- Odin/Idunn generated-vocabulary verification passed: `cargo test -p idunn-daemon`, `git diff --check -- crates/idunn-daemon/src/main.rs README.md docs/idunn.md docs/architecture.md docs/gjallar.md docs/transport-shortcut-inventory.md`, and focused scans over the touched non-inventory Odin/Idunn source/docs found no remaining `HTTP`, `WebSocket`, `ws://`, `wss://`, `/eve/deck`, `http.server`, `RUDP command route`, `daemon-owned-rudp-command-ingress`, or `weksa.mimo_voicedesign_command.v0` invitations outside literal health-script names. The inventory remains the evidence ledger and intentionally retains historical route names.
|
||||
- Mimir Nightwing/EVE witness socket-lane cut: `E:\Projects\Mimir\src\Mimir.EveSensorReceiver\Program.cs`, `src\Mimir.VerseRecorder\Program.cs`, `tools\nightwing_typed_witness_publisher.py`, and `scripts\start-nightwing-move-tracking.ps1` are fail-closed archive stubs. The old receiver no longer opens a TCP/WebSocket listener, health route, publish route, or subscriber path; the recorder no longer uses `ClientWebSocket`; the Nightwing publisher no longer hand-builds socket frames; and the launcher no longer starts/stages the receiver-recorder-publisher chain. Eye/Move witness publication and recording must use typed CultMesh/CultNet documents or CultMesh stream frames discovered through Odin.
|
||||
- Mimir map/doc follow-through: `E:\Projects\Mimir\docs\implementation-plan.md`, `notes\current-system-map.md`, `state\map.yaml`, and `state\evidence.jsonl` now mark the old Nightwing bring-up path as archived and point witness state at Muninn/Mimir typed CultMesh stream frames and Odin-discovered CultMesh documents. The touched Mimir doctrine no longer normalizes TCP/HTTP/WebSocket as live transport categories; it names product/debug render surfaces as non-authority lowerings.
|
||||
- Mimir witness-lane verification passed: `dotnet build E:\Projects\Mimir\src\Mimir.EveSensorReceiver\Mimir.EveSensorReceiver.csproj --no-restore`, `dotnet build E:\Projects\Mimir\src\Mimir.VerseRecorder\Mimir.VerseRecorder.csproj --no-restore`, bundled Python `py_compile` for `tools\nightwing_typed_witness_publisher.py`, PowerShell parser check for `scripts\start-nightwing-move-tracking.ps1`, `git diff --check` on the touched Mimir files plus this inventory, and focused scans found no surviving `ClientWebSocket`, `TcpListener`, `WebSocket`, `ws://`, `/eve/periwinkle`, `/eve/camera`, local health route, witness URL env, subscription URL env, or publisher `--url` path in the touched live files outside archive/inventory text.
|
||||
- Weksa/VoidBot MiMo VoiceDesign command side-door cut: `E:\Projects\Weksa\scripts\weksa-rudp-command.cjs` is a fail-closed archive stub, and `scripts\weksa-daemon.mjs` no longer imports or starts a direct command listener, no longer accepts `--mimo-command-json`, `--host`, or `--port`, and no longer emits a command runtime bind in operator state or transport profile. Weksa advertises `speech_provider.mimo.voicedesign` only as `cultmesh://odin/command/weksa/speech_provider.mimo.voicedesign`; start/restart scripts no longer pass or print a private command bind.
|
||||
- Weksa service contract follow-through: `E:\Projects\Weksa\docs\verse-service-contract.md` now names the Odin/CultMesh command boundary as the command authority and describes the old direct command endpoint as archived. Health publication over CultNet/RUDP remains Idunn-facing daemon health substrate; it is not a command side door.
|
||||
- VoidBot Weksa speech client cut: `E:\Projects\VoidBot\packages\config\src\index.ts` replaced `WEKSA_RUDP_ENDPOINT` with `WEKSA_CULTMESH_COMMAND_URI`, and `packages\core\src\weksa-speech.ts` no longer imports the Weksa direct sender. Until VoidBot grows a real CultMesh command-document writer, Weksa speech requests fail closed with the command URI instead of silently taking a legacy transport path.
|
||||
- Weksa/VoidBot command-lane verification passed: bundled Node syntax checks for `weksa-daemon.mjs` and the archived `weksa-rudp-command.cjs`, PowerShell parser checks for Weksa start/restart scripts, bundled TypeScript project build for VoidBot, `git diff --check` on the touched Weksa and VoidBot files, and focused scans found no surviving `WEKSA_RUDP_ENDPOINT`, `rudpEndpoint`, command RUDP bind/target, direct sender import, private command bind, or raw command endpoint wording outside the archive stub and this inventory.
|
||||
- Mimir EveDashboard broker path cut: `E:\Projects\Mimir\src\Mimir.EveDashboard\Program.cs` is now a fail-closed archive tombstone instead of a TCP listener/WebSocket deck broker. The executable no longer contains the HTTP health route, `/eve/deck`, `/eve/dashboard`, WebSocket handshake/frame parser, socket command channel, or provider route catalog machinery. Dashboard state must return as typed CultMesh/Eve documents through Odin, and dashboard commands must arrive as typed Odin/CultMesh command documents.
|
||||
- Mimir/Odin/gamecult-ops follow-through: `E:\Projects\Mimir\docs\implementation-plan.md`, `notes\current-system-map.md`, `notes\fresh-workspace-handoff.md`, and `state\map.yaml` now mark EveDashboard as archived until rebuilt as a pure CultMesh/Odin publisher plus renderer lowering. Odin's `nightwing-eve-dashboard` Idunn target is demoted to `archived`, its restart and health scripts are fail-closed archive stubs, and `E:\Projects\gamecult-ops\inventory.md` plus `runbooks\nightwing-cultmesh-worker-tunnel.md` no longer teach the Nightwing broker endpoint or localhost curl health probe.
|
||||
- Mimir EveDashboard verification passed: `dotnet build E:\Projects\Mimir\src\Mimir.EveDashboard\Mimir.EveDashboard.csproj --no-restore`, PowerShell parser checks for the touched Odin Idunn scripts, direct `.cmd` probes confirmed Nightwing EveDashboard restart/health wrappers fail closed, `git diff --check` passed for touched Mimir/Odin/gamecult-ops files, and focused scans found no runnable `TcpListener`, `AcceptTcpClientAsync`, WebSocket handshake/frame implementation, `ws://`, `/eve/deck`, `/eve/dashboard`, `0.0.0.0:8795`, or localhost dashboard curl probe outside archive/tombstone text and this inventory.
|
||||
- Vili HTTP service surface cut: `E:\Projects\Vili\scripts\vili-daemon.mjs` no longer imports `node:http`, binds `--host`/`--port`, starts `http.createServer`, serves `/`, `/provider-advertisement`, `/operator-state`, `/eve/operator`, `/smoke`, or the old `/motion/generate` tombstone route, or prints a local listener URL. Stale `--host` and `--port` invocations now fail closed and point at the typed `.vili\vili.service.cc` CultMesh/CultCache store.
|
||||
- Vili launcher/doc follow-through: `start-vili-daemon.ps1`, `restart-vili-daemon.ps1`, and `install-vili-task.ps1` no longer accept or pass service host/port parameters; `run-vili-daemon.ps1` and `smoke-vili.mjs` are fail-closed archive stubs; `package.json` no longer exposes an npm `smoke` shortcut; Vili README and service contract no longer teach a local operator endpoint or direct motion-generation HTTP route. The provider advertisement now uses `cultmesh://odin/surface/vili/operator` and the `.cc` store, not an HTTP/debug route.
|
||||
- Vili verification passed: bundled Node syntax checks for `scripts\vili-daemon.mjs` and archived `scripts\smoke-vili.mjs`, PowerShell parser checks for Vili start/restart/install/archive scripts, provider advertisement print checked clean of HTTP/WS/debug routes, stale `vili-daemon.mjs --host 127.0.0.1` fails closed, archived smoke exits 2, `git diff --check` passed for touched Vili files, and focused scans found no active Vili HTTP server, service host/port knob, provider/operator/smoke HTTP route, npm smoke script, or localhost service endpoint outside intentional archive/negative text and private Kimodo worker substrate.
|
||||
- Bifrost Docker health-gate cut: `E:\Projects\Bifrost\Dockerfile` no longer installs `curl` or declares a Docker `HEALTHCHECK` that curls `http://127.0.0.1:5080/healthz`. The container may still expose the ASP.NET product app, but Docker image health is no longer a localhost HTTP probe pretending to be daemon truth.
|
||||
- Bifrost operator-surface follow-through: `README.md` now says container smoke tests may hit `/healthz` and `/readyz` only as product probes, while Docker health and deploy gates must come from typed CultMesh/Idunn operator state. `tools\provider-advertisement.mjs --help` no longer shows localhost `BIFROST_HEALTH_URL` / `BIFROST_READY_URL` examples; `print-surface` without explicit probe URLs reports `unprobed` / `not configured` instead of silently curling localhost.
|
||||
- Bifrost verification passed: bundled Node syntax check for `tools\provider-advertisement.mjs`, `provider-advertisement.mjs print-surface --generated-at ...` produced `unprobed` health/readiness without default localhost probes, `git diff --check` passed for the touched Bifrost files, and focused scans found no remaining Bifrost Docker `HEALTHCHECK`, localhost `5080/healthz`/`readyz` health-gate command, or localhost probe example outside negative doctrine text and unrelated VoidBot healthcheck variable names.
|
||||
- StreamPixels localhost service-default cut: `E:\Projects\StreamPixels\apps\web\lib\service-url.ts` no longer falls back to `http://localhost:4000` on server or browser paths. Server-side web code now fails closed unless `SERVICE_URL` or `NEXT_PUBLIC_SERVICE_URL` is configured, and client-side browser code uses the configured public service URL or the current origin. `AppShell` now uses the same resolver instead of carrying its own localhost fallback.
|
||||
- StreamPixels overlay preview cut: `E:\Projects\StreamPixels\apps\overlay\src\runtime\config.ts` no longer turns a localhost overlay origin into an implicit `http://localhost:4000` service. Local overlay preview must carry an explicit `serviceUrl` query parameter; hosted overlay deployments still default to their current origin as product/browser lowering behavior, not daemon discovery.
|
||||
- StreamPixels verification passed: focused Vitest run for `apps/web/lib/service-url.test.ts` and `apps/overlay/src/runtime/config.test.ts` passed, TypeScript checks passed for `apps\web` and `apps\overlay`, and a focused scan found no remaining `localhost:4000` / `127.0.0.1:4000` runtime default in those app trees outside the explicit local-preview test fixture.
|
||||
- CultMesh TS Odin URI resolver added: `E:\Projects\CultLib\packages\cultmesh-ts\src\index.ts` now lets RUDP helpers accept `cultmesh://` targets and resolve them through `resolveCultMeshRudpEndpoint` or the default `CULTMESH_URI_<AUTHORITY>_RUDP` bootstrap. This moves daemon-facing config toward logical CultMesh URIs while keeping concrete RUDP endpoints inside the transport resolver boundary.
|
||||
- Weksa Odin startup publication cut: `E:\Projects\Weksa\scripts\weksa-daemon.mjs` now targets `cultmesh://odin/rendezvous/provider-catalog` through CultMesh publication instead of normalizing a raw `--odin-cultmesh-rudp` endpoint. `--odin-cultmesh-rudp` is a fail-closed removed flag, and Weksa launchers use `-OdinCultMeshUri` / `WEKSA_ODIN_CULTMESH_URI` with the Odin CultMesh URI default.
|
||||
- Weksa Odin URI verification passed: CultMesh TS source/test TypeScript builds passed, Weksa daemon syntax and launcher PowerShell parser checks passed, and stale `weksa-daemon.mjs --odin-cultmesh-rudp 127.0.0.1:17871` exits with the removed-flag error before daemon startup.
|
||||
- Stonks Odin startup publication cut: `E:\Projects\Stonks\src\stonks-daemon.cjs` now targets `cultmesh://odin/rendezvous/provider-catalog` through CultMesh publication instead of normalizing `--odin-cultmesh-rudp` / `STONKS_ODIN_CULTMESH_RUDP`. The old raw flag is a fail-closed removed path, and `scripts\start-stonks.ps1` uses `-OdinCultMeshUri` / `STONKS_ODIN_CULTMESH_URI` with the Odin CultMesh URI default.
|
||||
- Stonks Odin URI verification passed: bundled Node syntax check passed for `src\stonks-daemon.cjs`, PowerShell parser check passed for `scripts\start-stonks.ps1`, stale `stonks-daemon.cjs --odin-cultmesh-rudp 127.0.0.1:17871` exits with the removed-flag error before daemon startup, and focused scans found no remaining raw Odin RUDP config path outside the removed-flag guard.
|
||||
- Spotiverse Odin startup publication cut: `E:\Projects\Spotiverse\src\spotiverse-daemon.cjs` now targets `cultmesh://odin/rendezvous/provider-catalog` through CultMesh publication instead of `--odin-cultmesh-rudp` / `SPOTIVERSE_ODIN_CULTMESH_RUDP`. The Spotiverse launcher and env example now use `SPOTIVERSE_ODIN_CULTMESH_URI`, and provider advertisement startup-respect metadata carries `odinCultMeshUri` instead of `odinCultMeshRudp`.
|
||||
- Spotiverse Odin URI verification passed: bundled Node syntax check passed for `src\spotiverse-daemon.cjs`, PowerShell parser check passed for `scripts\start-spotiverse.ps1`, stale `spotiverse-daemon.cjs --odin-cultmesh-rudp 127.0.0.1:17871` exits with the removed-flag error before daemon startup, and focused scans found no remaining raw Odin RUDP config path outside the removed-flag guard.
|
||||
- Loki Odin startup publication cut: `E:\Projects\Loki\src\loki-daemon.mjs` now targets `cultmesh://odin/rendezvous/provider-catalog` through CultMesh publication instead of `--odin-cultmesh-rudp` / `LOKI_ODIN_CULTMESH_RUDP`. Its provider transport metadata records an Odin CultMesh URI, the old raw flag is fail-closed, and the daemon logs unresolved Odin URI bootstrap without aborting local CultMesh witness publication.
|
||||
- Loki Odin URI verification passed: bundled Node syntax check passed for `src\loki-daemon.mjs`, `loki-daemon.mjs --once` completed while reporting unresolved Odin URI bootstrap as a startup-respect failure, stale `loki-daemon.mjs --odin-cultmesh-rudp 127.0.0.1:17871` exits with the removed-flag error, and focused scans found no remaining raw Odin RUDP config path outside the removed-flag guard.
|
||||
- Bifrost Odin provider publication cut: `E:\Projects\Bifrost\tools\provider-advertisement.mjs` now publishes to `cultmesh://odin/rendezvous/provider-catalog` or `BIFROST_ODIN_CULTMESH_URI` instead of accepting `--odin-cultmesh-rudp` / `BIFROST_ODIN_CULTMESH_RUDP`. Help and Verse contract examples now teach the CultMesh URI path, and the old raw endpoint config fails closed.
|
||||
- Bifrost Odin URI verification passed: bundled Node syntax check passed for `tools\provider-advertisement.mjs`, help output shows only `--odin-cultmesh-uri` / `BIFROST_ODIN_CULTMESH_URI`, stale `provider-advertisement.mjs publish-odin --odin-cultmesh-rudp 127.0.0.1:17871` exits with the removed-config error, and focused scans found no remaining raw Odin RUDP config path outside the removed-config guard.
|
||||
- StreamPixels Odin startup publication cut: `E:\Projects\StreamPixels\apps\service\src\config.ts` and `verse-state.ts` now use `STREAMPIXELS_ODIN_CULTMESH_URI` / `cultmesh://odin/rendezvous/provider-catalog` for startup provider publication instead of `STREAMPIXELS_ODIN_CULTMESH_RUDP` plus local endpoint normalization. Docs and `state\map.yaml` now say concrete RUDP bootstrap belongs behind CultMesh URI resolution.
|
||||
- StreamPixels Odin URI verification passed: bundled TypeScript check passed for `apps\service`, `git diff --check` passed for touched StreamPixels files, and focused scans found no remaining `STREAMPIXELS_ODIN_CULTMESH_RUDP`, `odinCultMeshRudp`, or `normalizeRudpEndpoint` path in StreamPixels service/docs/state.
|
||||
- Rust CultMesh Odin URI resolver added: `E:\Projects\Odin\vendor\cultmesh-rs\src\lib.rs` now exposes `CultMesh::resolve_rudp_endpoint` / `resolve_rudp_endpoint`, accepts `cultmesh://` targets, resolves them through `CULTMESH_URI_<AUTHORITY>_RUDP`, and no longer defaults RUDP catalog publish/snapshot options to `127.0.0.1:17871`. The default target is an inert `0.0.0.0:0` sentinel so accidental unowned use fails instead of dialing localhost Odin.
|
||||
- Muninn/sleipnir Odin startup and catalog path cut: `crates\muninn-daemon` and `crates\sleipnir-daemon` now store Odin as `cultmesh://odin/rendezvous/provider-catalog` / `--odin-cultmesh-uri` and resolve it through Rust CultMesh only at RUDP catalog publish/pull boundaries. The old `--odin-cultmesh-rudp` flag is fail-closed in both daemons. Odin launchers for Raven/Starfire/Nightwing Muninn and Raven Sleipnir now pass `--odin-cultmesh-uri` / `ODIN_CULTMESH_URI` instead of raw endpoint flags.
|
||||
- Muninn/sleipnir Rust verification passed: `cargo test -p muninn-daemon odin_cultmesh`, `cargo test -p muninn-daemon request_stream_requires_odin_cultmesh_command_route`, `cargo test -p sleipnir-daemon odin_cultmesh`, `cargo test -p sleipnir-daemon rejects_removed_command_rudp_bind`, `cargo test --manifest-path vendor\cultmesh-rs\Cargo.toml resolves_rudp_endpoint_from_cultmesh_uri_bootstrap_env`, and `cargo test --manifest-path vendor\cultmesh-rs\Cargo.toml cultmesh_rudp_endpoint` passed. `cargo test -p muninn-daemon --no-run`, `cargo test -p sleipnir-daemon --no-run`, PowerShell parser checks for touched launchers, and `git diff --check` on touched Rust/script files also passed.
|
||||
- C# CultMesh Odin URI resolver added: `E:\Projects\CultLib\src\GameCult.Mesh\CultMesh.cs` now exposes `CultMesh.ResolveRudpEndpoint(...)`, accepts `cultmesh://` targets, resolves them through `CULTMESH_URI_<AUTHORITY>_RUDP` / `<AUTHORITY>_CULTMESH_RUDP_ENDPOINT`, and the string `CreateRudpClient(...)` overload now resolves through that CultMesh boundary instead of accepting only raw `rudp://` endpoints.
|
||||
- Historical, unadmitted Gjallar URI experiment: an `E:\Projects\Gjallar`
|
||||
body attempted to replace raw Odin RUDP flags with `--odin-cultmesh-uri`.
|
||||
That body is not the canonical source or deployed release. The live
|
||||
`F:\Projects\Gjallar` release `d683faa558351327d987918d1e8b8f79886dc9cb`
|
||||
accepts `--odin-cultnet-rudp` and `--odin-cultmesh-rudp`; its Yggdrasil unit
|
||||
uses both against `10.77.0.1:17871`. Treat the URI experiment and its checks
|
||||
as rejected branch evidence, not a completed transport cut.
|
||||
- Aetheria Odin announcement path cut: `E:\Projects\Aetheria\Aetheria.State.Daemon\Program.cs` collapsed `OdinCultMeshRudpEndpoints` into a single `OdinCultMeshUri`, defaults to `cultmesh://odin/rendezvous/provider-catalog`, rejects both `--odin-cultmesh-rudp` and the previous `--odin-cultnet-rudp` alias as announcement targets, advertises provider announcement routes as `transport: cultmesh`, and publishes through the shared CultMesh client URI resolution path.
|
||||
- C# Odin URI verification passed: `dotnet build E:\Projects\CultLib\src\GameCult.Mesh\GameCult.Mesh.csproj --no-restore -p:OutDir=E:\Projects\.codex-build\cultmesh-out\`, `dotnet build E:\Projects\Gjallar\src\Gjallar\Gjallar.csproj --no-restore`, and isolated-output `dotnet build E:\Projects\Aetheria\Aetheria.State.Daemon\Aetheria.State.Daemon.csproj --no-restore -p:OutDir=E:\Projects\.codex-build\aetheria-daemon-out\` passed. The normal Aetheria output build was blocked by a running `Aetheria.State.Daemon` process holding DLLs open, so verification used `OutDir`; focused scans found no surviving raw Odin CultMesh RUDP config path outside intentional removed-option guards and this inventory.
|
||||
- VoidBot Bifrost startup health side-door cut: `E:\Projects\VoidBot\scripts\start-voidbot-stack.ps1` no longer reads `BIFROST_HEALTH_URL`, curls Bifrost `/healthz`, or starts `BIFROST_ROOT\compose.local.yaml` based on product HTTP health. Stack startup now verifies `BIFROST_CULTMESH_COMMAND_URI`, `BIFROST_CULTMESH_STORE_PATH`, the Bifrost CultMesh command processor, and exports the typed provider store when missing. VoidBot current maps and handoff notes now describe Bifrost readiness as the CultMesh command lane, not Docker/product HTTP health.
|
||||
- VoidBot Bifrost startup verification passed: PowerShell parser check passed for `scripts\start-voidbot-stack.ps1`, focused scans found no remaining `BIFROST_HEALTH_URL`, `Ensure-BifrostLocalStack`, `HealthUrl`, `/healthz`, or `/readyz` path in VoidBot's stack script/current maps/README, the configured Bifrost provider store and command processor exist, `node E:\Projects\Bifrost\tools\provider-advertisement.mjs export --out <temp>` succeeded with health/readiness reported as `unprobed` / `not configured`, and `git diff --check` passed for the touched VoidBot files.
|
||||
- Bifrost HTTP bridge ledger side-door cut: `E:\Projects\Bifrost\tools\bifrost-bridge.mjs` rejects `BIFROST_BRIDGE_BASE_URL` / `BIFROST_BRIDGE_TOKEN` and `--bifrost-base-url` / `--bifrost-token` outright. External bridge actions now require typed CultMesh command provenance such as `BIFROST_CULTMESH_COMMAND_ID`; `other-request` is fail-closed until it has a named typed command/receipt contract. `tools\agent-transport.mjs`, `tools\governance-threads.mjs`, and `tools\dispatch-agent-requests.mjs` no longer require or post to hosted runtime receipt endpoints; request, governance, and dispatch activity stays in Bifrost-owned typed CultCache/CultMesh stores.
|
||||
- Bifrost web endpoint path cut: `E:\Projects\Bifrost\src\Bifrost.Web\Program.cs` no longer executes `/bridge/actions/*`, `/dispatch/runs/*`, `/transport/receipts`, or `/governance/receipts` as mutation APIs. Those route groups now return `410 Gone` tombstones pointing callers at typed CultMesh command/receipt documents discovered through Odin. Bifrost provider advertisement no longer exposes `bridge-action-ledger` and reports the old bridge ledger credential source as `removed-http-bridge`.
|
||||
- Bifrost HTTP bridge verification passed: Node syntax checks passed for `bifrost-bridge.mjs`, `agent-transport.mjs`, `governance-threads.mjs`, `dispatch-agent-requests.mjs`, and `provider-advertisement.mjs`; `dotnet build E:\Projects\Bifrost\src\Bifrost.Web\Bifrost.Web.csproj --no-restore` passed; `BIFROST_BRIDGE_BASE_URL=http://127.0.0.1:5080 node tools\bifrost-bridge.mjs discord-post ...` and the same env on `agent-transport.mjs enqueue ...` fail closed with removed-config errors; `provider-advertisement.mjs print-surface` succeeds and reports the HTTP bridge ledger as removed; focused scans found no surviving live acceptance of the old bridge env outside removed-config guards, tombstone route names, and this inventory. Full Bifrost tests were not run in this pass because the existing bridge tests still encode the intentionally deleted HTTP-ledger behavior and need a separate expectation rewrite.
|
||||
- Aetheria RTS peer shortcut cut: `E:\Projects\Aetheria\Aetheria.State.Daemon\Program.cs` no longer accepts configured peer RUDP endpoints, no longer imports committed facts from `--peer-cultmesh-endpoint`, and no longer carries the peer sync timeout authority. Peer discovery is now represented as Odin/CultMesh discovery; the remaining local `rudp://127.0.0.1:<port>` handle in the Electron launcher is private to a child daemon spawned by that process, not user/deployment configuration.
|
||||
- Aetheria Electron daemon targeting cut: `E:\Projects\Aetheria\Aetheria.Rts.Web\Electron\main.ts` now targets a daemon by `AETHERIA_RTS_CULTMESH_URI` or the default `cultmesh://odin/aetheria/rts/<daemonId>`, rejects legacy raw endpoint environment variables before startup, and no longer passes peer endpoint arguments into the local daemon. `scripts\verify-stage7c-electron-shell.ps1` now verifies the CultMesh URI targeting guard instead of teaching the peer endpoint pass-through.
|
||||
- Aetheria peer shortcut verification passed: `npm run build` passed for `E:\Projects\Aetheria\Aetheria.Rts.Web`, isolated-output `dotnet build E:\Projects\Aetheria\Aetheria.State.Daemon\Aetheria.State.Daemon.csproj --no-restore -p:OutDir=E:\Projects\.codex-build\aetheria-daemon-out\` passed with pre-existing Unity/nullability warnings, PowerShell parser check passed for the touched Electron verifier, stale `dotnet ... Aetheria.State.Daemon.dll --peer-cultmesh-endpoint rudp://127.0.0.1:1 --once` fails before daemon startup, and focused scans found no surviving active peer endpoint import loop or launcher pass-through outside fail-closed guards and the local spawned-daemon private handle.
|
||||
- Bifrost provider surface HTTP probe cut: `E:\Projects\Bifrost\tools\provider-advertisement.mjs` no longer accepts `--health-url`, `--ready-url`, `BIFROST_HEALTH_URL`, or `BIFROST_READY_URL` when building/exporting provider surfaces. Operator readiness now derives from typed CultMesh/CultCache stores, governance/request stores, and typed bridge command metadata; `/healthz` and `/readyz` remain product smoke endpoints only and cannot feed Odin-facing provider status.
|
||||
- Bifrost test-suite HTTP bridge fixture cut: `E:\Projects\Bifrost\tests\Bifrost.Web.Tests\BridgeCliTests.cs` no longer spins up a fake localhost `HttpListener` bridge, no longer teaches `/bridge/actions/*`, `/dispatch/runs/*`, `/transport/receipts`, or `/governance/receipts` as accepted receipt paths, and uses typed `BIFROST_CULTMESH_COMMAND_ID` provenance or explicit removed-config negative checks instead of `BIFROST_BRIDGE_BASE_URL` / `BIFROST_BRIDGE_TOKEN` success fixtures.
|
||||
- Bifrost bridge CLI test verification passed: `DOTNET_ROLL_FORWARD=Major dotnet test E:\Projects\Bifrost\tests\Bifrost.Web.Tests\Bifrost.Web.Tests.csproj --no-restore --filter FullyQualifiedName~BridgeCliTests` passed 30/30, and focused scans found no fake bridge server, localhost listener, deleted bridge route fixture, or old base-url/token success assertion in `BridgeCliTests.cs`.
|
||||
- Current Gjallar transport admission: Idunn runs the headless Yggdrasil body,
|
||||
Odin owns the accepted snapshot at `10.77.0.1:17871`, and Gjallar publishes
|
||||
`gjallar.overview` back over the same RUDP substrate. Nightwing deployment and
|
||||
framebuffer units are retired rather than adapted to the unadmitted URI path.
|
||||
- Loki Chrome extension HTTP ingest cut: `E:\Projects\Loki\src\loki-daemon.mjs` no longer imports `node:http`, opens a localhost extension ingest server, serves `/health` or `/state`, accepts `/ingest/chrome-extension`, advertises `chrome-extension-ingest`, or publishes `gamecult.loki.chrome_extension_report.v0`, `gamecult.loki.chrome_debug_probe.v0`, or `gamecult.loki.chrome_page_snapshot.v0` witness routes. The daemon is CDP-observation-only until browser-origin observations have a CultMesh/native binding. `--extension-host` and `--extension-port` fail closed, the unpacked extension is an archived status stub with no localhost host permissions, and the old extension-ingest smoke test is now a removed-path guard.
|
||||
- Loki verification passed: bundled Node syntax checks passed for `src\loki-daemon.mjs`, the extension service worker/popup, and Loki smoke tests; direct `node test\fixture-cdp-smoke.mjs` passed; direct `node test\extension-ingest-smoke.mjs` passed and proves `--extension-port` is rejected before startup. Focused scans found no remaining Loki extension HTTP ingest server, `/ingest/chrome-extension`, `LOKI_EXTENSION_PORT`, Chrome debugger/page snapshot action names, extension host permissions, or advertised extension/debug/page snapshot witness routes outside the intentional removed-option guard.
|
||||
- Bifrost web bridge route cut: `E:\Projects\Bifrost\src\Bifrost.Web\Program.cs` no longer maps `/bridge/actions/*`, `/dispatch/runs/*`, `/transport/receipts`, or `/governance/receipts` at all. The old local bridge token HTTP handlers, `BuildBridgeCaller`, and local bridge token gate were deleted from the product web app. `PlatformEndpointsTests` now proves the old routes are `404 NotFound` and do not write bridge action, dispatch run, transport receipt, or governance receipt rows.
|
||||
- Bifrost web bridge route verification passed: `dotnet build E:\Projects\Bifrost\src\Bifrost.Web\Bifrost.Web.csproj --no-restore` passed, and `dotnet test E:\Projects\Bifrost\tests\Bifrost.Web.Tests\Bifrost.Web.Tests.csproj --no-restore --filter FullyQualifiedName~PlatformEndpointsTests` passed 22/22 with `DOTNET_ROLL_FORWARD=Major`. Focused scans found no remaining mapped Bifrost web bridge routes, removed bridge endpoint function, or helper methods outside the negative route-absence test and inventory.
|
||||
- Bifrost Motion Verse HTTP command path cut: `E:\Projects\Bifrost\src\Bifrost.Web\Program.cs` no longer maps `/eve/governance/commands`, and `MotionEveCommandRequest` was removed from the web surface layer. `MotionEveSurfaceService` now emits `cultmesh://asgard.starfire.bifrost/commands/motion` as the canonical command target with `cultmesh-command-document` transport metadata instead of an HTTP POST target. `tools\provider-advertisement.mjs` now advertises `motion-surface` and `motion-commands` as CultMesh URIs, not `https://bifrost.gamecult.org/eve/governance/*` endpoints. The browser-lowered motion state endpoint remains a product/member surface, but it is no longer the discovered Verse command authority.
|
||||
- Bifrost Motion Verse verification passed: Node syntax check passed for `tools\provider-advertisement.mjs`, `dotnet build E:\Projects\Bifrost\src\Bifrost.Web\Bifrost.Web.csproj --no-restore` passed, and `DOTNET_ROLL_FORWARD=Major dotnet test E:\Projects\Bifrost\tests\Bifrost.Web.Tests\Bifrost.Web.Tests.csproj --no-restore --no-build --filter FullyQualifiedName~PlatformEndpointsTests` passed 22/22. Focused scans found no surviving `https://bifrost.gamecult.org/eve/governance`, mapped `/eve/governance/commands`, `MotionEveCommandRequest`, or HTTP POST command metadata outside the negative route-absence tests and this inventory.
|
||||
- Mimir CultMeshMedia relay target shortcut cut: `E:\Projects\Mimir\src\Mimir.CultMeshMedia\Program.cs` no longer accepts `--host`, `--port`, or `--endpoint` for `send` / `recv`, and no longer synthesizes `rudp://host:port` as the remote relay target. Senders and receivers now require `--cultmesh-uri` / `MIMIR_CULTMESH_MEDIA_URI`, validate it as `cultmesh://...`, and resolve the concrete RUDP endpoint through `CultMesh.ResolveRudpEndpoint(...)` at the transport boundary. `start-raven-cultmesh-av-sender.ps1` and `start-starfire-cultmesh-av-receiver.ps1` now target `cultmesh://asgard.yggdrasil.mimir/media/raven-primary-av`, reject stale `MIMIR_CULTMESH_RELAY_HOST`, and no longer pass raw relay host/port arguments. Relay bind and OBS-local UDP output remain local substrate/lowering, not Verse discovery or command authority.
|
||||
- Mimir CultMeshMedia verification passed: `dotnet build E:\Projects\Mimir\src\Mimir.CultMeshMedia\Mimir.CultMeshMedia.csproj --no-restore` passed; PowerShell parser checks passed for the touched sender/receiver launchers; stale `send --host 10.77.0.1` and `recv --endpoint rudp://10.77.0.1:3075` invocations fail closed before transport startup; stale `MIMIR_CULTMESH_RELAY_HOST=10.77.0.1` launcher use fails closed; and a dry-run sender now emits `--cultmesh-uri cultmesh://asgard.yggdrasil.mimir/media/raven-primary-av`. Focused scans found no remaining active Mimir media sender/receiver `--host`, `--port`, `--endpoint`, raw WireGuard relay, or loopback tunnel target outside fail-closed guards and this inventory.
|
||||
|
||||
Not run: Gradle wrapper build for Eve's native Android app; that app uses `scripts\build-android.ps1` in this workspace, and that build passed.
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,116 +0,0 @@
|
|||
#include <psmoveapi/psmove.h>
|
||||
#include <psmoveapi/psmove_tracker.h>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
static uint64_t stable_hash(const char *value)
|
||||
{
|
||||
uint64_t hash = UINT64_C(14695981039346656037);
|
||||
while (*value) {
|
||||
hash ^= (unsigned char)*value++;
|
||||
hash *= UINT64_C(1099511628211);
|
||||
}
|
||||
return hash ? hash : 1;
|
||||
}
|
||||
|
||||
static void stable_color(const char *serial, unsigned char color[3])
|
||||
{
|
||||
const double hue_sector = (double)(stable_hash(serial) % 360) / 60.0;
|
||||
const double chroma = 0.82;
|
||||
double sector_mod = hue_sector;
|
||||
while (sector_mod >= 2.0) sector_mod -= 2.0;
|
||||
const double secondary = chroma * (1.0 - (sector_mod > 1.0 ? sector_mod - 1.0 : 1.0 - sector_mod));
|
||||
double red = 0.0, green = 0.0, blue = 0.0;
|
||||
switch ((int)hue_sector) {
|
||||
case 0: red = chroma; green = secondary; break;
|
||||
case 1: red = secondary; green = chroma; break;
|
||||
case 2: green = chroma; blue = secondary; break;
|
||||
case 3: green = secondary; blue = chroma; break;
|
||||
case 4: red = secondary; blue = chroma; break;
|
||||
default: red = chroma; blue = secondary; break;
|
||||
}
|
||||
const double floor = 1.0 - chroma;
|
||||
color[0] = (unsigned char)((red + floor) * 255.0 + 0.5);
|
||||
color[1] = (unsigned char)((green + floor) * 255.0 + 0.5);
|
||||
color[2] = (unsigned char)((blue + floor) * 255.0 + 0.5);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
const int camera = argc > 1 ? atoi(argv[1]) : 0;
|
||||
const int frames = argc > 2 ? atoi(argv[2]) : 300;
|
||||
const float exposure = argc > 3 ? strtof(argv[3], NULL) : 0.12f;
|
||||
const int count = psmove_count_connected();
|
||||
if (count <= 0) {
|
||||
fprintf(stderr, "no PS Move controllers connected\n");
|
||||
return 2;
|
||||
}
|
||||
|
||||
PSMoveTracker *tracker = psmove_tracker_new_with_camera(camera);
|
||||
if (!tracker) {
|
||||
fprintf(stderr, "camera %d could not create PSMoveTracker\n", camera);
|
||||
return 3;
|
||||
}
|
||||
psmove_tracker_set_exposure(tracker, exposure);
|
||||
|
||||
PSMove **moves = calloc((size_t)count, sizeof(*moves));
|
||||
if (!moves) {
|
||||
return 4;
|
||||
}
|
||||
int enabled = 0;
|
||||
for (int index = 0; index < count; index++) {
|
||||
PSMove *move = psmove_connect_by_id(index);
|
||||
if (!move) {
|
||||
continue;
|
||||
}
|
||||
const char *serial = psmove_get_serial(move);
|
||||
unsigned char color[3];
|
||||
stable_color(serial, color);
|
||||
const enum PSMoveTracker_Status status = psmove_tracker_enable_with_color(
|
||||
tracker, move, color[0], color[1], color[2]);
|
||||
fprintf(stderr, "controller=%s color=%u,%u,%u calibration=%d\n",
|
||||
serial, color[0], color[1], color[2], status);
|
||||
if (status == Tracker_CALIBRATED) {
|
||||
moves[enabled++] = move;
|
||||
} else {
|
||||
psmove_disconnect(move);
|
||||
}
|
||||
}
|
||||
|
||||
const struct PSMoveCameraInfo *info = psmove_tracker_get_camera_info(tracker);
|
||||
fprintf(stderr, "camera=%d name=%s api=%s size=%dx%d exposure=%.3f controllers=%d\n",
|
||||
camera,
|
||||
info && info->camera_name ? info->camera_name : "unknown",
|
||||
info && info->camera_api ? info->camera_api : "unknown",
|
||||
info ? info->width : 0,
|
||||
info ? info->height : 0,
|
||||
psmove_tracker_get_exposure(tracker),
|
||||
enabled);
|
||||
|
||||
for (int frame = 0; frame < frames; frame++) {
|
||||
psmove_tracker_update_image(tracker);
|
||||
for (int index = 0; index < enabled; index++) {
|
||||
psmove_tracker_update(tracker, moves[index]);
|
||||
float x = 0.f, y = 0.f, radius = 0.f;
|
||||
const int age_ms = psmove_tracker_get_position(
|
||||
tracker, moves[index], &x, &y, &radius);
|
||||
if (age_ms >= 0 && radius > 0.f) {
|
||||
printf("frame=%d controller=%s x=%.3f y=%.3f radius=%.3f age_ms=%d\n",
|
||||
frame, psmove_get_serial(moves[index]), x, y, radius, age_ms);
|
||||
fflush(stdout);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int index = 0; index < enabled; index++) {
|
||||
psmove_tracker_disable(tracker, moves[index]);
|
||||
psmove_disconnect(moves[index]);
|
||||
}
|
||||
psmove_tracker_free(tracker);
|
||||
free(moves);
|
||||
return enabled > 0 ? 0 : 5;
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
# Muninn PSMoveAPI Observer Patch
|
||||
|
||||
Muninn owns PS Move LED output. PSMoveAPI owns camera exposure, HSV filtering,
|
||||
morphology, contour extraction, and marker position estimation.
|
||||
|
||||
`expected-color-observer.patch` adds only the missing authority boundary:
|
||||
|
||||
- enable tracking without blinking or writing LEDs;
|
||||
- update the expected camera hue without writing LEDs.
|
||||
|
||||
The expected hue comes from Muninn's timestamp-driven `gold(x)` schedule. This
|
||||
patch must be applied to the pinned PSMoveAPI source with `git apply --recount`
|
||||
before building the Nightwing tracker libraries.
|
||||
|
|
@ -1,83 +0,0 @@
|
|||
diff --git a/include/psmove_tracker.h b/include/psmove_tracker.h
|
||||
index 7de998b..d44db20 100644
|
||||
--- a/include/psmove_tracker.h
|
||||
+++ b/include/psmove_tracker.h
|
||||
@@ -235,6 +235,25 @@ ADDCALL psmove_tracker_get_auto_update_leds(PSMoveTracker *tracker, PSMove *move
|
||||
ADDAPI void
|
||||
ADDCALL psmove_tracker_set_exposure(PSMoveTracker *tracker, float exposure);
|
||||
|
||||
+/**
|
||||
+ * Enable reference blob tracking without writing controller LEDs.
|
||||
+ * The caller owns LED output and supplies the expected fully-saturated RGB hue.
|
||||
+ */
|
||||
+ADDAPI enum PSMoveTracker_Status
|
||||
+ADDCALL psmove_tracker_enable_color_observer(PSMoveTracker *tracker, PSMove *move,
|
||||
+ unsigned char r, unsigned char g, unsigned char b);
|
||||
+
|
||||
+/**
|
||||
+ * Change the expected RGB hue for an observer-enabled controller without
|
||||
+ * writing controller LEDs or running blinking calibration.
|
||||
+ */
|
||||
+ADDAPI bool
|
||||
+ADDCALL psmove_tracker_set_expected_color(PSMoveTracker *tracker, PSMove *move,
|
||||
+ unsigned char r, unsigned char g, unsigned char b);
|
||||
+
|
||||
/**
|
||||
* \brief Get exposure as fraction (0.0 -> 1.0)
|
||||
**/
|
||||
diff --git a/src/tracker/psmove_tracker.cpp b/src/tracker/psmove_tracker.cpp
|
||||
index 23e33ed..621b9ea 100644
|
||||
--- a/src/tracker/psmove_tracker.cpp
|
||||
+++ b/src/tracker/psmove_tracker.cpp
|
||||
@@ -547,6 +547,49 @@ psmove_tracker_get_exposure(PSMoveTracker *tracker)
|
||||
return tracker->settings.camera_exposure;
|
||||
}
|
||||
|
||||
+static void
|
||||
+psmove_tracker_assign_observed_color(TrackedController *tc,
|
||||
+ unsigned char r, unsigned char g, unsigned char b)
|
||||
+{
|
||||
+ struct PSMove_RGBValue rgb = { r, g, b };
|
||||
+ CvScalar hsv = th_rgb2hsv(cvScalar(r, g, b, 255.0));
|
||||
+ tc->color = rgb;
|
||||
+ tc->auto_update_leds = false;
|
||||
+ tc->eColorHSV = tc->eFColorHSV = hsv;
|
||||
+ tc->assignedHSV = hsv;
|
||||
+}
|
||||
+
|
||||
+enum PSMoveTracker_Status
|
||||
+psmove_tracker_enable_color_observer(PSMoveTracker *tracker, PSMove *move,
|
||||
+ unsigned char r, unsigned char g, unsigned char b)
|
||||
+{
|
||||
+ psmove_return_val_if_fail(tracker != NULL, Tracker_CALIBRATION_ERROR);
|
||||
+ psmove_return_val_if_fail(move != NULL, Tracker_CALIBRATION_ERROR);
|
||||
+
|
||||
+ TrackedController *tc = psmove_tracker_find_controller(tracker, move);
|
||||
+ if (tc == NULL) {
|
||||
+ tc = psmove_tracker_find_controller(tracker, NULL);
|
||||
+ }
|
||||
+ if (tc == NULL) {
|
||||
+ return Tracker_CALIBRATION_ERROR;
|
||||
+ }
|
||||
+ tc->move = move;
|
||||
+ psmove_tracker_assign_observed_color(tc, r, g, b);
|
||||
+ return Tracker_CALIBRATED;
|
||||
+}
|
||||
+
|
||||
+bool
|
||||
+psmove_tracker_set_expected_color(PSMoveTracker *tracker, PSMove *move,
|
||||
+ unsigned char r, unsigned char g, unsigned char b)
|
||||
+{
|
||||
+ psmove_return_val_if_fail(tracker != NULL, false);
|
||||
+ psmove_return_val_if_fail(move != NULL, false);
|
||||
+ TrackedController *tc = psmove_tracker_find_controller(tracker, move);
|
||||
+ if (tc == NULL) {
|
||||
+ return false;
|
||||
+ }
|
||||
+ psmove_tracker_assign_observed_color(tc, r, g, b);
|
||||
+ return true;
|
||||
+}
|
||||
+
|
||||
void
|
||||
psmove_tracker_reset_color_calibration(PSMoveTracker *tracker)
|
||||
{
|
||||
|
|
@ -1,112 +0,0 @@
|
|||
# Why Odin cycles: the queued deployment kills the incumbent every time it seals
|
||||
|
||||
> **Correction 2026-09-10 20:35Z.** The fork was resolved as both sides in one
|
||||
> pass: Idunn `903e1c8` keys the projection by `{target}@{expected sha}` and
|
||||
> Odin `faa53f3` keys its reader and store the same way. See
|
||||
> `odin-incarnation-keyed-projection-2026-09-10.md`. "Not owned by this cut"
|
||||
> (an admitted Odin observing a candidate Odin) is still unbuilt. "R17" is not
|
||||
> defined by any active ledger.
|
||||
|
||||
Written 2026-09-10 ~19:45Z from a live probe on yggdrasil. Supersedes the
|
||||
mechanism section of `odin-deployment-handoff-2026-09-10.md`; that note's
|
||||
timeline and "what not to do" still hold.
|
||||
|
||||
## The loop, as actually observed
|
||||
|
||||
One full cycle, wall clock, unit `8eeef9cb`:
|
||||
|
||||
| time | event | evidence |
|
||||
|---|---|---|
|
||||
| 19:34:59 | unit starts (continuity restart) | journal |
|
||||
| 19:35:18 | write lease granted, projection holds Expected `fbbf6ace` + activation `8eeef9cb` + lease | projection snapshot `193601` |
|
||||
| ~19:35:40 | continuity transaction completes; target free | probe: no running odin transaction |
|
||||
| 19:37:33 | **projection Expected slot for `odin` replaced** by `incarnation-tx-4ae951cf`, sha `42dd1d45`, source rev `d6b15151`, candidate port 17973. Activation and lease untouched. | snapshot `193733`, byte-identical size to the healthy file |
|
||||
| 19:37:36 | Odin's own catalog rejects Odin's own heartbeat: `runtime presence has no exact current activation and provider anchor` | journal |
|
||||
| 19:37:38 | publisher ack timeout; daemon exits 1 | journal |
|
||||
| 19:37:41 | Idunn demotes projection to Expected-only (activation and lease withdrawn) | snapshot `193741` |
|
||||
| 19:37:47 | continuity Sealing; 19:37:52 next unit starts | probe |
|
||||
|
||||
The decode probe (`projection_authority_probe`, below) run on the two
|
||||
snapshots gives:
|
||||
|
||||
- `193601` (healthy): authority present, no disagreements, lease binds.
|
||||
- `193733` (kill): **`activation-expected-projection`** — expected
|
||||
`42dd1d45` (the candidate's Expected), observed `fbbf6ace` (the Expected the
|
||||
incumbent's activation binds). Authority collapses to none, and with no
|
||||
authority `select_presence` rejects any incoming presence.
|
||||
|
||||
The writer is the queued deployment `up-fc005d83`. It is **not starved**.
|
||||
Every time a continuity restart completes and the target frees, it freezes,
|
||||
seals (freeze source, materialize, install), and `advance_sealing` calls
|
||||
`publish_expected`, which replaces the single `idunn.expected_incarnation[odin]`
|
||||
record with the candidate's. The incumbent's next heartbeat validates itself
|
||||
against that slot and fails. Idunn then sees the incumbent dead, aborts the
|
||||
deployment ("admitted incumbent failed before candidate fencing; deployment
|
||||
yielded to continuity", 5 such lines in status), restores Expected-only,
|
||||
schedules continuity, and the command is queued again for the next cycle.
|
||||
`tx-4ae951cf` exists only in `history.cc` (5 refs), which is why `idunn status`
|
||||
shows nothing running at the moment of the kill.
|
||||
|
||||
Unit lifetime 2.5–16 min is the seal time (materialize is a Docker cargo
|
||||
build), not a timeout.
|
||||
|
||||
This is R17 with the timeline attached. The handoff note's "freeze starvation"
|
||||
defect is wrong as stated: freezing runs; it is the freeze that kills.
|
||||
|
||||
## Authority map
|
||||
|
||||
- **Which incarnation am I.** Owner: the immutable runtime bundle Idunn writes
|
||||
at launch (`/etc/gamecult/odin/runtime/<instance>/expected.cc` and
|
||||
`activation.cc`), loaded into `authority_material` in `main.rs`. Startup
|
||||
already treats it that way and deliberately does not compare the projected
|
||||
activation ("every launch is issued a fresh one").
|
||||
- **Am I still admitted to write.** Owner: the write lease file
|
||||
(`process-write-lease.cc`), lifetime-locked by Odin, revoked by Idunn at
|
||||
Fencing. Already enforced by `require_current_write_lease`.
|
||||
- **What the projection slot `expected_incarnation[odin]` means to Idunn.** The
|
||||
subject of the transaction currently touching the target: the candidate
|
||||
during a deployment, the admitted incarnation otherwise. Idunn's warming
|
||||
design *requires* the incumbent to stay up while the slot holds the candidate
|
||||
("the healthy incumbent observes the candidate, which is the point").
|
||||
- **Surviving wrong owner.** `OdinTopologyAuthority::reconcile` →
|
||||
`classify_runtime_authority` derives Odin's authority for its *own* presence
|
||||
from that slot. For the self target the slot is not identity; it is a
|
||||
forbidden writer of identity. This is the cut.
|
||||
- **Derived / unchanged.** Idunn validates Odin's correlation against the
|
||||
*admitted* generation's Expected (`runtime_authority_parts` from
|
||||
`AdmittedGeneration`), not the slot, so a self-correlation built from the
|
||||
bundle Expected is what Idunn already expects to see.
|
||||
- **Not owned by this cut.** Two Odin incarnations under one target key in
|
||||
Odin's store during an Odin deployment (incumbent + candidate). Today that
|
||||
path does not work at all: Odin deployments have only ever succeeded by
|
||||
bootstrap after the incumbent died. Keeping the incumbent alive turns that
|
||||
from "kills the incumbent" into "candidate warming fails cleanly". That is
|
||||
the R17 rebuild proper and is a separate decision.
|
||||
|
||||
## The fork to decide
|
||||
|
||||
**A. Odin-side (contained to this repo).** For `target == self`, admit and
|
||||
correlate presence against `authority_material` (bundle Expected + activation,
|
||||
provider anchor from the projection, which is stable) and use the projection
|
||||
only for the lease binding. Deleting the slot's authority over self-identity
|
||||
makes "incumbent dies when a candidate seals" structurally impossible. The
|
||||
deployment then fails at Warming instead of killing the incumbent, and the
|
||||
incumbent keeps serving. Candidate observation for Odin-on-Odin deployments
|
||||
remains unbuilt, as it is today.
|
||||
|
||||
**B. Idunn-side.** Publish the candidate Expected under an incarnation-scoped
|
||||
record and keep the admitted Expected in the slot until Commit. Fixes the same
|
||||
kill, keeps Odin's classifier as-is, but changes the projection contract that
|
||||
every target and Odin's `current_projection` read, and Odin still cannot
|
||||
observe a candidate Odin without the two-incarnation store.
|
||||
|
||||
Recommendation: A first. It is the smaller cut, it lives where the wrong owner
|
||||
lives, and B is still needed for the deployment story either way.
|
||||
|
||||
## Probe
|
||||
|
||||
`crates/odin-daemon/src/lib.rs` carries `projection_authority_probe`, an
|
||||
ignored test that decodes a captured projection file and prints what the
|
||||
classifier sees. Usage is in its doc comment. Capture on the host with a
|
||||
copy-on-change loop over `/var/lib/gamecult/idunn-projection/topology.cc`;
|
||||
the version that kills is the one written seconds before the rejection line.
|
||||
|
|
@ -1,136 +0,0 @@
|
|||
# Odin cannot currently be redeployed, and it cycles
|
||||
|
||||
> **Correction 2026-09-10 19:23Z.** Per-unit lifetime is 2.5-16 minutes, not
|
||||
> ~96 s; 96 s is the runtime-presence publisher timeout that precedes the exit.
|
||||
> Cycle rate holds at ~10 starts/hour. Count starts with `sudo journalctl -q`;
|
||||
> an unprivileged `journalctl` sees only your own messages and returns zero.
|
||||
> `up-fc005d83` still queued; lifecycle brake still released (19:03:12Z).
|
||||
>
|
||||
> **Correction 2026-09-10 19:45Z.** The "starved by design" section below is
|
||||
> wrong: the queued deployment freezes every time the target frees. Its Sealing
|
||||
> phase replaces the projected Expected with the candidate's, the incumbent
|
||||
> fails `activation-expected-projection` on its next heartbeat and exits, and
|
||||
> the deployment is aborted for having killed its incumbent. Full timeline and
|
||||
> authority map in `odin-cycle-mechanism-2026-09-10.md`.
|
||||
>
|
||||
> **Correction 2026-09-10 20:35Z.** Superseded by
|
||||
> `odin-incarnation-keyed-projection-2026-09-10.md`. "What to try, in order" is
|
||||
> done or wrong: (1) the starvation diagnosis was wrong (see 19:45Z above);
|
||||
> (2)-(3) are fixed in Idunn `903e1c8` (Expected published after the brake,
|
||||
> lifecycle brake stops continuity minting instead of parking a transaction,
|
||||
> commands consumed once). `idunn cancel <command-id>` now exists; "no cancel
|
||||
> verb (R16)" is false. "Continuity gives up after 3" was false from Idunn
|
||||
> `222bfcc` (failed restarts retire to history.cc before the counter reads
|
||||
> them) until `6b68d65`, which counts history too. R16 and R17 are cited here
|
||||
> as recorded but no active ledger in Odin, Idunn, or gamecult-ops defines
|
||||
> them; treat the numbers as unresolved references.
|
||||
|
||||
Written 2026-09-10 after an incident caused by an optional dependency bump.
|
||||
Read this before running `idunn up odin`.
|
||||
|
||||
## State on yggdrasil as of writing
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| Odin | **up**, cycling: ~9 unit starts/hour, self-terminates every ~96s under load |
|
||||
| running release | `sha256-61c32655...` (from `up-07cb1979`, admitted 2026-09-09 22:17) |
|
||||
| Idunn | `active`, 0 restarts, healthy throughout |
|
||||
| queued deployment | `up-fc005d83-79d4-418c-bc76-80d07dd6eff3` — **stuck `queued`, cannot dequeue** |
|
||||
| lifecycle brake | released |
|
||||
| `control.cc` | 6.5 MB · `history.cc` 2.5 MB (archival working) |
|
||||
|
||||
Odin is *serving* between restarts. This is degradation, not an outage.
|
||||
|
||||
## The loop, precisely
|
||||
|
||||
```
|
||||
Odin rejected CultMesh RUDP application message DocumentPutRaw
|
||||
odin-presence:odin-yggdrasil:sha256-<unit>:...
|
||||
: runtime presence has no exact current activation and provider anchor
|
||||
Error: timed out waiting for reliable acknowledgement from CultMesh RUDP catalog 127.0.0.1:17972
|
||||
```
|
||||
|
||||
1. Odin's runtime-presence publisher publishes to **Odin's own catalog**.
|
||||
2. The catalog rejects it: the projection at
|
||||
`/var/lib/gamecult/idunn-projection/topology.cc` holds no activation matching
|
||||
the unit this process was launched with.
|
||||
3. No acknowledgement ever arrives. The publisher times out after ~96 s and the
|
||||
daemon **exits 1 on its own** — systemd is not stopping it.
|
||||
4. Continuity restarts it under a fresh unit, which **mints a new activation**,
|
||||
and the projection is behind again.
|
||||
|
||||
**Continuity restarting Odin is what keeps the activation ahead of the
|
||||
projection.** The restart is not the recovery; it is the thing that guarantees
|
||||
the next failure.
|
||||
|
||||
## Why the two obvious moves do not work
|
||||
|
||||
### Braking continuity blocks deployment
|
||||
|
||||
Engaging the lifecycle brake *does* stop the loop — Odin converged clean, four
|
||||
minutes up, zero rejections, which is what confirmed the diagnosis. But a
|
||||
continuity transaction that opened **before** the brake then parks forever:
|
||||
|
||||
```
|
||||
waiting on lifecycle brake denies continuity restart
|
||||
```
|
||||
|
||||
It owns the target, so no deployment can freeze while the brake is engaged.
|
||||
`F:\Projects\CLAUDE.md` says a lifecycle brake must not gate deployment
|
||||
authority. **It does.** That separation is not holding, and it is defect 1.
|
||||
|
||||
### The queued deployment is starved by design
|
||||
|
||||
`run_scheduler_tick` (`Idunn/src/control_plane.rs`) runs
|
||||
`freeze_one_queued_command` **only** when neither a resumed transaction nor
|
||||
continuity made progress that tick. The ordering is deliberate — "unfinished
|
||||
ownership work first, admitted-body continuity second, new commands last" — but
|
||||
on an unstable target continuity always has work, so a queued command never
|
||||
runs. Observed across four separate windows where nothing owned the target.
|
||||
|
||||
That is defect 2, and it is the shape this estate keeps producing: **the one
|
||||
command that could repair the instability cannot run because the target is
|
||||
unstable.**
|
||||
|
||||
## Underlying cause, older than this incident
|
||||
|
||||
Redeploying Odin destroys the observer Idunn needs in order to admit the
|
||||
replacement. Idunn publishes the candidate's Expected projection into the store
|
||||
the incumbent reads; the incumbent can no longer validate its own presence and
|
||||
dies. This is recorded as R17 and it is why an Odin deployment is never routine.
|
||||
|
||||
## What to try, in order
|
||||
|
||||
1. **Settle Odin first, deploy second.** The activation/projection race is the
|
||||
root; everything else is consequence. A candidate whose projection matches at
|
||||
launch is what ended this loop at 22:17 on 09-09.
|
||||
2. **Fix the freeze starvation** so a queued deployment can take a target that
|
||||
continuity is fighting over — otherwise the repair can never be applied by
|
||||
the machine that needs it.
|
||||
3. **Fix the brake separation** so an engaged lifecycle brake suspends
|
||||
continuity actuation without parking a transaction on the target.
|
||||
4. Only then re-run the deployment. The pin bump it carries (`0e540ca`,
|
||||
`f7e3fc3`) is committed on `main` and undeployed; nothing is lost by waiting.
|
||||
|
||||
There is **no cancel verb** (recorded as R16), so `up-fc005d83` will fire
|
||||
whenever the target frees. Treat it as armed.
|
||||
|
||||
## What not to do
|
||||
|
||||
- Do not `systemctl start` a failed Idunn unit by hand. It mints a new
|
||||
`InvocationID`, and Idunn then refuses to stop a unit whose invocation it
|
||||
cannot match — this blocked a recovery for 20 minutes on 09-09 and needed
|
||||
`systemctl reset-failed` to undo.
|
||||
- Do not read a gap between restarts as convergence. Odin holds for ~90 s
|
||||
between cycles; that window was misread as "stable" twice in one night.
|
||||
Measure `Started idunn-odin` counts over an hour instead.
|
||||
- Do not trust `head` on a unit listing. See `AGENTS.md`.
|
||||
|
||||
## What is verified working
|
||||
|
||||
- History archival: terminal transactions leave `control.cc` for `history.cc`
|
||||
on completion. `history.cc` exists and is 2.5 MB; `status` merges both.
|
||||
- Continuity gives up after `CONTINUITY_RESTART_ATTEMPTS = 3` against one
|
||||
generation, which is what frees a target after repeated failure.
|
||||
- Idunn survives all of this: a faulting scheduler tick is logged, not fatal,
|
||||
and startup no longer depends on the deployment brake anchor.
|
||||
|
|
@ -1,217 +0,0 @@
|
|||
# The projection is keyed by incarnation now
|
||||
|
||||
Written 2026-09-10 ~20:30Z, before the host carried the change. Read
|
||||
`odin-cycle-mechanism-2026-09-10.md` first for why.
|
||||
|
||||
## What changed
|
||||
|
||||
Odin `faa53f3` and Idunn `903e1c8`, both on `main`.
|
||||
|
||||
- Every Expected, activation, and write-lease record in Idunn's projection,
|
||||
and every presence and correlation record in Odin's store, is keyed by
|
||||
`{target}@{expected projection sha256}`. The runtime presence trust anchor
|
||||
stays keyed by target. The contract is stated in Idunn
|
||||
`docs/deployment-authority.md` under "Projection keying" and implemented by
|
||||
`IncarnationRef` in `odin-daemon` and `incarnation_key` in Idunn's
|
||||
`drivers.rs`. The two strings must agree; lifting the helper into CultLib
|
||||
`cultnet-rs` is the open follow-up.
|
||||
- Odin looks up its own incarnation by its bundle's Expected digest, admits a
|
||||
presence under the incarnation the presence names, keeps one correlation per
|
||||
incarnation, and resolves dependencies by (provider, expected sha). Records
|
||||
keyed by the bare target project nothing.
|
||||
- Idunn publishes the candidate Expected as Starting's first step, after the
|
||||
deployment brake admitted the transaction. A pre-fencing abort withdraws only
|
||||
the candidate; a post-fencing abort also demotes the stopped incumbent.
|
||||
`restore_admitted_expected_only` and the read-as-absence compensator in
|
||||
`admit_latest_topology` are gone.
|
||||
- A consumed command is archived with its last transaction. This is what ended
|
||||
"the queued deployment fires again every cycle": it was never starved, it was
|
||||
re-frozen because the command outlived its retired transaction.
|
||||
- An engaged lifecycle brake stops continuity being minted instead of parking a
|
||||
transaction that owns the target. `idunn cancel <command-id>` exists.
|
||||
|
||||
## Verification
|
||||
|
||||
- Odin: `cargo test -p odin-core -p odin-daemon` in `rust:1.95-bookworm` on
|
||||
yggdrasil, 22 + 12 (+1 ignored) + 4 passed. The negative check is
|
||||
`a_candidate_expected_beside_the_incumbent_cannot_reject_its_presence`.
|
||||
- Idunn: `cargo test --lib` in the same image, 114 passed, 2 ignored. The
|
||||
driver tests prove withdrawal and demotion are scoped to one key and that a
|
||||
legacy target-keyed slot is retired on publish.
|
||||
- Host: see the dated entries below.
|
||||
|
||||
## Deployment order and why
|
||||
|
||||
1. Odin `main` first. Under the old Idunn a candidate built from it cannot
|
||||
start (it finds no incarnation-keyed Expected) and the old loop continues
|
||||
unchanged; nothing is worse.
|
||||
2. Idunn binary next: `cargo build --release` in the same image, install over
|
||||
`/usr/local/bin/idunn`, restart `idunn-yggdrasil.service`. The previous
|
||||
binary is kept as `/usr/local/bin/idunn.prev` (sha256 `4f4941bb…`).
|
||||
3. On its first supervise tick the new Idunn finds the admitted Expected not
|
||||
exact under the new key, republishes it and retires the target-keyed slot.
|
||||
The running old Odin then finds no Expected it reads and exits; continuity
|
||||
restarts the old release, which cannot start for the same reason; after
|
||||
three attempts the target is free. The queued `up-fc005d83` freezes once,
|
||||
builds `main`, and the new Odin bootstraps as first Odin because no
|
||||
admitted Odin can observe it. That command is then consumed.
|
||||
|
||||
## Host log
|
||||
|
||||
(dated entries appended as the deployment proceeds)
|
||||
|
||||
- **20:23:43Z** Idunn `903e1c8` installed over `4f4941bb…`, restarted, active
|
||||
with no restarts. It resumed the in-flight `continuity-ac542622` in
|
||||
Committing. That phase asks Odin for a correlation under the new key; the
|
||||
old Odin writes under the old one; the phase returned "no evidence yet" every
|
||||
tick and, owning the target, blocked everything else. Nothing died and
|
||||
nothing moved.
|
||||
- **20:24:46Z** Stopped the old Odin unit by hand (systemd needed a second
|
||||
SIGKILL; the process was not its child). Idunn still did not move:
|
||||
Committing, Routing and AwaitingReady wait on `admit_latest_topology` before
|
||||
they observe the workload, so a candidate that dies while its evidence is
|
||||
pending is never observed and never aborted. Fixed in Idunn by observing the
|
||||
candidate first in those three phases (`observe_candidate_before_waiting`).
|
||||
Second Idunn build in progress.
|
||||
- **20:28:58Z** Idunn `1a0a0e7` installed and restarted. The parked
|
||||
transaction aborted at 20:29:09, the projection was rewritten under
|
||||
incarnation keys at 20:29:10 (decoded: one incarnation
|
||||
`odin@sha256-fbbf6ace…`, Expected-only, legacy slot gone), and continuity
|
||||
began restarting the old release, which fails at start with "Idunn
|
||||
projection has runtime authority without Expected" as predicted.
|
||||
- **20:30:16Z** Continuity did not give up after three attempts. Failed
|
||||
restarts retire to `history.cc` as soon as they finish and the give-up
|
||||
counter read only the live set, so it counted zero every time. That also
|
||||
means the handoff note's "continuity gives up after 3" was never true since
|
||||
the history retirement landed. Fixed in Idunn by counting history too. Third
|
||||
Idunn build in progress; the old release keeps restarting harmlessly until
|
||||
it lands.
|
||||
- **20:32:45Z** Idunn `6b68d65` installed. Continuity gave up ("failed to
|
||||
start 12 times, the target is free"). At 20:33:39 freeze created
|
||||
`tx-0f15abe4` for `up-78945539`, an old odin command from earlier today:
|
||||
every resident command whose transactions had been retired to history read
|
||||
as queued again, and freeze takes the oldest first. That one command has
|
||||
hundreds of retired attempts behind it. It sealed Odin `main` `faa53f3` as
|
||||
release `sha256-809979ad…` and is waiting on the deployment brake, which is
|
||||
the operator gate working: Expected is now published only after the brake,
|
||||
so nothing was killed. `up-fc005d83` was redundant and was cancelled with
|
||||
the new verb. Fixed in Idunn: a command is consumed by any transaction, live
|
||||
or retired; the resident backlog is retired on sight; a busy target no longer
|
||||
blocks the commands behind it; `status` reports a live transaction as
|
||||
running instead of an older attempt's failure.
|
||||
- **Pending** an operator release of the odin deployment brake naming release
|
||||
`sha256-809979ad…` and deployment `tx-0f15abe4-3d00-4c04-bf11-ebc27aa97a6c`,
|
||||
signed with `/etc/gamecult/idunn/deployment-brake-operator-identity.cc`.
|
||||
Odin is down until then; nothing else is broken.
|
||||
- **20:57:49Z / 21:00:41Z** Idunn `7bcd567` then `f659b9d` installed. The
|
||||
resident commands were not queued after all: about sixty terminal
|
||||
transactions from before history retirement were still resident and
|
||||
consumed their commands correctly. They now leave one per tick (47 retired
|
||||
in the first 45 s; `control.cc` shrinking). `up-78945539` /
|
||||
`tx-0f15abe4` holds in Sealing at the deployment brake, reported as running.
|
||||
Nothing else is queued.
|
||||
- **Next, operator-owned.** Release the odin deployment brake for release
|
||||
`sha256-809979adc013040ab16aa8a56d8b1dfb232936887f6055ea313a67c854c0689a`
|
||||
and deployment `tx-0f15abe4-3d00-4c04-bf11-ebc27aa97a6c` with
|
||||
`idunn-provision deployment-brake-release` signed by
|
||||
`/etc/gamecult/idunn/deployment-brake-operator-identity.cc`. The candidate
|
||||
then starts, warms directly as first Odin (no admitted Odin can observe it),
|
||||
fences the dead old generation, takes the lease and route, and is admitted.
|
||||
Watch it with `idunn status --command up-78945539-0a9f-4e80-b432-cd97e7f4b861`.
|
||||
|
||||
## Pending decision, not part of this rebuild: Muninn has two bodies
|
||||
|
||||
Verified 2026-09-10 ~21:15Z from a Muninn-session finding. Odin's workspace
|
||||
still carries `crates/muninn-daemon`, `muninn-move-tracker`,
|
||||
`muninn-psmoveapi-tracker`; they have no reference to the media stream
|
||||
contract that `GameCult/Muninn` (b679fc2 and later) implements, and
|
||||
gamecult-ops `scripts/idunn/idunn-deployment-targets.ps1` binds
|
||||
`starfire-muninn` and `raven-muninn` to `Repo = "Odin"`. A Muninn redeploy
|
||||
today ships the stale copy.
|
||||
|
||||
The cut, once the operator says so, is one pass across three repos: delete
|
||||
the three crates from Odin's workspace, rebind both targets to
|
||||
`GameCult/Muninn`, and carry the launch changes Muninn `07c70ac` made: the
|
||||
activation child listens on `--media-rudp-bind` (default `0.0.0.0:5220`),
|
||||
the target must supply `--media-rudp-advertise` (env
|
||||
`MUNINN_MEDIA_RUDP_ADVERTISE`, Raven's LAN host:port, no default) and open
|
||||
that UDP port, `--obs-target-host`/`--obs-port` are refused, and
|
||||
`GameCult/Muninn/scripts/restart-muninn.ps1` already does all of it. Muninn
|
||||
pins CultLib `c84cb2e`; Odin's pin is separate. Either half alone breaks the
|
||||
next Muninn redeploy, which is why it is not done here.
|
||||
- **21:13Z** Operator released the brake for `tx-0f15abe4`. The candidate
|
||||
exited at start: `Odin correlation key is substituted`. Odin's own store
|
||||
still held correlations under the old target key and the incarnation-keyed
|
||||
reader refused them. Fixed in Odin `27ca3b3`: legacy-keyed correlations are
|
||||
skipped by the reader and retired at activation; presence history is kept.
|
||||
The abort finished clean. Muninn's workspace copy was removed by another
|
||||
session in `d41c744` on operator order.
|
||||
- **21:43Z** `up-838e26d8` sealed Odin `main` `27ca3b3` as release
|
||||
`sha256-067ab605c82828cd2b1b1feac0d431cc217e076007c28a5dbd2902ae294878e3`
|
||||
in `tx-dde64dc5-18a0-435b-8afb-077b6452a388`; waiting on a brake release
|
||||
naming that pair.
|
||||
- **Correction, ~21:50Z, from the Muninn session.** The gamecult-ops rebind
|
||||
of the Muninn targets (`80bbe39`) sits on the unmerged branch
|
||||
`claude/repo-census-2026-09`, where `idunn-deployment-targets.ps1` was
|
||||
introduced; `origin/main` has no Muninn target records at all. The live
|
||||
yggdrasil Idunn binds only `heimdall` and `odin`, which matches
|
||||
`/etc/gamecult/idunn/bindings/`. So nothing deployed the stale copy; Raven's
|
||||
Muninn comes from whichever `restart-muninn.ps1` an operator runs, and Odin's
|
||||
copy is gone (`d41c744`). Merging that branch is its owner's call.
|
||||
- **21:50:46Z** Brake released by the agent on operator order. `tx-dde64dc5`
|
||||
went Sealing, Warming, Leasing, Committing, admitted at 21:51:02Z. Unit
|
||||
`idunn-odin-6dfdbbce…`, release `sha256-067ab605…`, incarnation
|
||||
`odin@sha256-e98b0459…`. Zero rejections after the release. The projection
|
||||
now holds two incarnations side by side: the retired generation
|
||||
`odin@sha256-fbbf6ace…` as Expected-only and the admitted one with
|
||||
activation and lease, authority present, lease binding. That is the
|
||||
contract working. The retired generation's Expected-only record is the one
|
||||
leftover: nothing withdraws it after commit yet.
|
||||
- **21:58:17Z** Idunn `1899df2` installed: supervise withdraws one stale
|
||||
projected incarnation per tick for each admitted target, whatever left it
|
||||
(replaced incumbent, skipped abort reconciliation, migration). It withdrew
|
||||
`incarnation-tx-08023033…` on its first tick. The admitted Odin process was
|
||||
untouched (same PID since 21:50:48Z), zero rejections since the release.
|
||||
- **22:06Z** Hold check: the admitted unit has run sixteen minutes on the same
|
||||
PID (since 21:50:48Z), one active odin unit, zero CultMesh rejections and
|
||||
zero odin-daemon errors since the release, and Idunn has logged nothing
|
||||
since the 21:58:17Z sweep. The old kill window was 2.5–16 minutes. Odin is
|
||||
up under the incarnation-keyed projection.
|
||||
|
||||
## Fold-back to 17871, 2026-09-10 22:06–22:45Z
|
||||
|
||||
Odin was admitted but unreachable: its stable endpoint was `10.77.0.1:17971`,
|
||||
a staging port chosen while the old Node coordinator held `17871`, and every
|
||||
consumer, runbook and firewall rule still named `17871`. Operator ruled: fold
|
||||
back. Done end to end, with four defects found and fixed on the way, each
|
||||
committed separately:
|
||||
|
||||
- The coordinator container `odin-coordinator-1` is stopped with
|
||||
`restart=no`; `odin.service` now starts only `hermodr`. Binding
|
||||
`stable_endpoint` is `rudp://10.77.0.1:17871`; backups of the binding and
|
||||
the unit are under `/tmp/projsnap/` on the host.
|
||||
- Idunn `f16fb46`: the route driver owns one ufw allow per routed stable
|
||||
endpoint (needs `/etc/ufw` in the unit's `ReadWritePaths`, `103c557`).
|
||||
- Idunn `e81c73e`: Warming admitted the candidate's correlation twice with one
|
||||
stale record and failed the second time. Once now.
|
||||
- Idunn `692ba1f`: an aged-out Odin correlation is absence to wait on, not a
|
||||
fault. This is what had failed every Heimdall deployment.
|
||||
- Odin `b8ef6c6`: the daemon is PID 1 of its private PID namespace and ignored
|
||||
SIGTERM, so every stop was a 90 s SIGKILL and the candidate's 60 s bootstrap
|
||||
wait expired during fencing. Odin catches SIGTERM now; the wait is 300 s.
|
||||
- Admitted at 22:44:21Z as `up-e728909f` / `tx-0e8fa81e`, release
|
||||
`sha256-4d6e959a…`, listening on `10.77.0.1:17871` → `127.0.0.1:17972`. The
|
||||
driver's stale `17971` allow was deleted by hand; the operator's original
|
||||
`17871` rule remains beside the driver's. Continuity restored the previous
|
||||
generation within 11 s after each failed attempt; Odin was never down for
|
||||
more than about a minute.
|
||||
- Not Odin's: `10.77.0.4` is blocked sending UDP to `10.77.0.1:17870` (Idunn
|
||||
health). Handed to the Muninn session to name.
|
||||
- **22:50Z, verified end to end by the Muninn session.** A Ratatoskr catalog
|
||||
pull from Starfire against `rudp://10.77.0.1:17871` returned one stream, zero
|
||||
malformed: `muninn.raven.av.rudp` by Raven, advertised from `GameCult/Muninn`
|
||||
`887efa7`, resolving Odin through `CULTMESH_URI_ODIN_RUDP=10.77.0.1:17871`.
|
||||
First producer-agnostic advertisement admitted by the Rust Odin and read
|
||||
back by a consumer. The `10.77.0.4 → 17870` blocks were Raven's Muninn
|
||||
publishing daemon health to an Idunn that does not manage it; the Muninn
|
||||
session is cutting that flag rather than opening the port.
|
||||
15
package.json
15
package.json
|
|
@ -1,15 +0,0 @@
|
|||
{
|
||||
"name": "@gamecult/odin",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"description": "GameCult all-seer: CultMesh Verse discovery, schema awareness, translation, and Eve surface publication.",
|
||||
"type": "commonjs",
|
||||
"scripts": {
|
||||
"gjallar:persona": "node scripts/write-gjallar-persona-cc.mjs inspect",
|
||||
"gjallar:persona:write": "node scripts/write-gjallar-persona-cc.mjs write",
|
||||
"rust:test": "cargo test --workspace"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -1,31 +0,0 @@
|
|||
param(
|
||||
[string] $SshTarget = "nightwing",
|
||||
[string] $HostAddress = ""
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$scriptPath = Join-Path $PSScriptRoot "nightwing-claim-usb-moves.sh"
|
||||
$sshExe = Join-Path $env:SystemRoot "System32\OpenSSH\ssh.exe"
|
||||
|
||||
function Quote-ShSingle([string] $Value) {
|
||||
return "'" + ($Value -replace "'", "'\''") + "'"
|
||||
}
|
||||
|
||||
$scriptContent = [System.IO.File]::ReadAllText($scriptPath) -replace "`r?`n", "`n"
|
||||
$script64 = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($scriptContent))
|
||||
$remoteArgs = ""
|
||||
if (-not [string]::IsNullOrWhiteSpace($HostAddress)) {
|
||||
$remoteArgs = " " + (Quote-ShSingle $HostAddress)
|
||||
}
|
||||
|
||||
$command = '"' + $sshExe + '" -o BatchMode=yes -o ConnectTimeout=5 ' +
|
||||
$SshTarget +
|
||||
' "printf %s ' +
|
||||
(Quote-ShSingle $script64) +
|
||||
' | base64 -d | sh -s --' +
|
||||
$remoteArgs +
|
||||
'"'
|
||||
|
||||
cmd.exe /c $command
|
||||
exit $LASTEXITCODE
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
param(
|
||||
[int[]] $Ports = @(17870, 17871, 17874, 17886, 17887, 17888)
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).
|
||||
IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
|
||||
if (-not $isAdmin) {
|
||||
throw "Run this script from an elevated PowerShell session on Starfire."
|
||||
}
|
||||
|
||||
foreach ($port in $Ports) {
|
||||
$name = "GameCult LAN RUDP UDP $port"
|
||||
& netsh.exe advfirewall firewall delete rule name="$name" | Out-Null 2>$null
|
||||
& netsh.exe advfirewall firewall add rule `
|
||||
name="$name" `
|
||||
dir=in `
|
||||
action=allow `
|
||||
protocol=UDP `
|
||||
localport=$port `
|
||||
profile=private,domain,public | Out-Null
|
||||
Write-Host "Allowed inbound UDP $port for GameCult LAN RUDP."
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
param(
|
||||
[string] $SshTarget = "nightwing"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$scriptPath = Join-Path $PSScriptRoot "nightwing-move-state-sources.sh"
|
||||
$sshExe = Join-Path $env:SystemRoot "System32\OpenSSH\ssh.exe"
|
||||
|
||||
function Quote-ShSingle([string] $Value) {
|
||||
return "'" + ($Value -replace "'", "'\''") + "'"
|
||||
}
|
||||
|
||||
$scriptContent = [System.IO.File]::ReadAllText($scriptPath) -replace "`r?`n", "`n"
|
||||
$script64 = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($scriptContent))
|
||||
$command = '"' + $sshExe + '" -o BatchMode=yes -o ConnectTimeout=5 ' +
|
||||
$SshTarget +
|
||||
' "printf %s ' +
|
||||
(Quote-ShSingle $script64) +
|
||||
' | base64 -d | sh -s"'
|
||||
& cmd.exe /d /c $command
|
||||
exit $LASTEXITCODE
|
||||
|
|
@ -1,92 +0,0 @@
|
|||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
host_address="${1:-}"
|
||||
if [ -z "$host_address" ]; then
|
||||
host_address="$(bluetoothctl show | awk '/^Controller / { print $2; exit }')"
|
||||
fi
|
||||
|
||||
if [ -x /usr/local/sbin/muninn-pair-usb-moves ]; then
|
||||
sudo -n /usr/local/sbin/muninn-pair-usb-moves
|
||||
fi
|
||||
|
||||
python3 - "$host_address" <<'PY'
|
||||
import fcntl
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
host_address = sys.argv[1].strip()
|
||||
parts = re.split(r"[:-]", host_address)
|
||||
if len(parts) != 6:
|
||||
print(f"invalid host address: {host_address}", file=sys.stderr)
|
||||
sys.exit(2)
|
||||
|
||||
try:
|
||||
host = bytes(int(part, 16) for part in reversed(parts))
|
||||
except ValueError:
|
||||
print(f"invalid host address: {host_address}", file=sys.stderr)
|
||||
sys.exit(2)
|
||||
|
||||
HIDIOCGFEATURE = lambda length: 0xC0000000 | (length << 16) | (0x48 << 8) | 0x07
|
||||
HIDIOCSFEATURE = lambda length: 0xC0000000 | (length << 16) | (0x48 << 8) | 0x06
|
||||
MOVE_USB_ID = "HID_ID=0003:0000054C:000003D5"
|
||||
|
||||
|
||||
def fmt_addr(raw):
|
||||
return ":".join(f"{byte:02x}" for byte in reversed(raw))
|
||||
|
||||
|
||||
def is_usb_move(hidraw_name):
|
||||
uevent = f"/sys/class/hidraw/{hidraw_name}/device/uevent"
|
||||
try:
|
||||
with open(uevent, "r", encoding="utf-8", errors="replace") as stream:
|
||||
text = stream.read()
|
||||
except OSError:
|
||||
return False
|
||||
return MOVE_USB_ID in text
|
||||
|
||||
|
||||
def read_addresses(fd):
|
||||
report = bytearray(16)
|
||||
report[0] = 0x04
|
||||
fcntl.ioctl(fd, HIDIOCGFEATURE(len(report)), report, True)
|
||||
return fmt_addr(report[1:7]), fmt_addr(report[10:16])
|
||||
|
||||
|
||||
claimed = 0
|
||||
errors = 0
|
||||
for name in sorted(os.listdir("/sys/class/hidraw")):
|
||||
if not is_usb_move(name):
|
||||
continue
|
||||
|
||||
path = f"/dev/{name}"
|
||||
try:
|
||||
fd = os.open(path, os.O_RDWR | os.O_NONBLOCK)
|
||||
except OSError as exc:
|
||||
errors += 1
|
||||
print(f"{path} open=failed error={exc}", file=sys.stderr)
|
||||
continue
|
||||
|
||||
try:
|
||||
before_controller, before_host = read_addresses(fd)
|
||||
report = bytearray(23)
|
||||
report[0] = 0x05
|
||||
report[1:7] = host
|
||||
fcntl.ioctl(fd, HIDIOCSFEATURE(len(report)), report, True)
|
||||
after_controller, after_host = read_addresses(fd)
|
||||
claimed += 1
|
||||
print(
|
||||
f"{path} controller={after_controller or before_controller} "
|
||||
f"host_before={before_host} host_after={after_host}"
|
||||
)
|
||||
except OSError as exc:
|
||||
# Only the Move pairing collection accepts report 0x05. Other USB
|
||||
# collections are expected to reject it.
|
||||
print(f"{path} skipped error={exc}", file=sys.stderr)
|
||||
finally:
|
||||
os.close(fd)
|
||||
|
||||
if claimed == 0 and errors:
|
||||
sys.exit(1)
|
||||
PY
|
||||
|
|
@ -1,113 +0,0 @@
|
|||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
controller_id_from_hidraw() {
|
||||
hidraw="$1"
|
||||
python3 - "$hidraw" <<'PY'
|
||||
import fcntl
|
||||
import os
|
||||
import sys
|
||||
|
||||
path = sys.argv[1]
|
||||
HIDIOCGFEATURE = lambda length: 0xC0000000 | (length << 16) | (0x48 << 8) | 0x07
|
||||
try:
|
||||
fd = os.open(path, os.O_RDWR | os.O_NONBLOCK)
|
||||
buf = bytearray(16)
|
||||
buf[0] = 4
|
||||
fcntl.ioctl(fd, HIDIOCGFEATURE(len(buf)), buf, True)
|
||||
os.close(fd)
|
||||
print(''.join(f'{byte:02x}' for byte in reversed(buf[1:7])))
|
||||
except Exception:
|
||||
try:
|
||||
os.close(fd)
|
||||
except Exception:
|
||||
pass
|
||||
sys.exit(1)
|
||||
PY
|
||||
}
|
||||
|
||||
hidraw_for_js() {
|
||||
js="$1"
|
||||
cursor="$(readlink -f "/sys/class/input/$(basename "$js")/device" 2>/dev/null || true)"
|
||||
while [ -n "$cursor" ] && [ "$cursor" != "/" ]; do
|
||||
if [ -d "$cursor/hidraw" ]; then
|
||||
for raw in "$cursor"/hidraw/hidraw*; do
|
||||
[ -e "$raw" ] || continue
|
||||
printf '/dev/%s\n' "$(basename "$raw")"
|
||||
return 0
|
||||
done
|
||||
fi
|
||||
cursor="$(dirname "$cursor")"
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
emit_candidates() {
|
||||
for js in /dev/input/js*; do
|
||||
[ -e "$js" ] || continue
|
||||
props="$(udevadm info -q property -n "$js" 2>/dev/null || true)"
|
||||
uevent="/sys/class/input/$(basename "$js")/device/device/uevent"
|
||||
uevent_text="$(cat "$uevent" 2>/dev/null || true)"
|
||||
is_nav=0
|
||||
case "$props
|
||||
$uevent_text" in
|
||||
*ID_VENDOR_ID=054c*ID_MODEL_ID=03d5*|*ID_MODEL=Motion_Controller*|*HID_ID=0005:0000054C:000003D5*|*HID_ID=0003:0000054C:000003D5*)
|
||||
;;
|
||||
*ID_MODEL=Navigation_Controller*|*ID_USB_MODEL=Navigation_Controller*|*ID_MODEL_ID=042f*ID_VENDOR_ID=054c*|*ID_VENDOR_ID=054c*ID_MODEL_ID=042f*|*HID_ID=0005:0000054C:0000042F*|*HID_ID=0003:0000054C:0000042F*|*Sony_Navigation_Controller*|*Sony\ Navigation\ Controller*)
|
||||
is_nav=1
|
||||
;;
|
||||
*)
|
||||
continue
|
||||
;;
|
||||
esac
|
||||
|
||||
bus="$(printf '%s\n' "$props" | awk -F= '$1=="ID_BUS"{print $2; exit}')"
|
||||
uniq="$(printf '%s\n' "$uevent_text" | awk -F= '$1=="HID_UNIQ"{print $2; exit}' | tr -d ':')"
|
||||
path_tag="$(printf '%s\n' "$props" | awk -F= '$1=="ID_PATH_TAG"{print $2; exit}' | tr -c 'A-Za-z0-9_' '_' | sed 's/_*$//')"
|
||||
hidraw="$(hidraw_for_js "$js" || true)"
|
||||
controller_id=""
|
||||
if [ -n "$hidraw" ]; then
|
||||
controller_id="$(controller_id_from_hidraw "$hidraw" 2>/dev/null || true)"
|
||||
fi
|
||||
|
||||
if [ "$is_nav" = "1" ] && [ -n "$uniq" ]; then
|
||||
id="nav-$uniq"
|
||||
elif [ "$is_nav" = "1" ] && [ -n "$path_tag" ]; then
|
||||
id="nav-usb-$path_tag"
|
||||
elif [ "$is_nav" = "1" ]; then
|
||||
id="nav-$(basename "$js")"
|
||||
elif [ -n "$controller_id" ]; then
|
||||
id="move-$controller_id"
|
||||
elif [ "$bus" = "bluetooth" ] && [ -n "$uniq" ]; then
|
||||
id="move-$uniq"
|
||||
elif [ -n "$path_tag" ]; then
|
||||
id="move-usb-$path_tag"
|
||||
else
|
||||
id="move-$(basename "$js")"
|
||||
fi
|
||||
|
||||
score=1
|
||||
case "$uevent_text" in
|
||||
*HID_ID=0005:0000054C:000003D5*) score=2 ;;
|
||||
esac
|
||||
|
||||
printf '%s %s %s\n' "$id" "$score" "$js"
|
||||
done
|
||||
}
|
||||
|
||||
emit_candidates | awk '
|
||||
{
|
||||
id = $1
|
||||
score = $2 + 0
|
||||
path = $3
|
||||
if (!(id in best_score) || score >= best_score[id]) {
|
||||
best_score[id] = score
|
||||
best_path[id] = path
|
||||
}
|
||||
}
|
||||
END {
|
||||
for (id in best_path) {
|
||||
print id "=" best_path[id]
|
||||
}
|
||||
}
|
||||
'
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
psmove_bin=/usr/local/bin/psmove
|
||||
adapter_address="$(bluetoothctl show | awk '/^Controller / { print toupper($2); exit }')"
|
||||
if [ ! -x "$psmove_bin" ] || [ -z "$adapter_address" ]; then
|
||||
echo "PSMoveAPI or Nightwing Bluetooth adapter unavailable" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
missing=0
|
||||
controllers="$($psmove_bin list 2>/dev/null | grep -Eio '[0-9a-f]{2}(:[0-9a-f]{2}){5}' | sort -u)"
|
||||
for controller in $controllers; do
|
||||
controller_upper="$(printf '%s' "$controller" | tr '[:lower:]' '[:upper:]')"
|
||||
cache="/var/lib/bluetooth/$adapter_address/cache/$controller_upper"
|
||||
if [ ! -s "$cache" ] || ! grep -q '^\[ServiceRecords\]$' "$cache"; then
|
||||
missing=1
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$missing" -eq 0 ]; then
|
||||
echo "All USB PS Moves already have BlueZ HID SDP pairing records."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
exec "$psmove_bin" pair
|
||||
|
|
@ -1,75 +0,0 @@
|
|||
param(
|
||||
[string] $NightwingEndpointFallback = $env:STARFIRE_WIREGUARD_NIGHTWING_ENDPOINT_FALLBACK,
|
||||
[string] $StarfireLanAddress = $env:STARFIRE_LAN_ADDRESS
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$serviceName = "WireGuardTunnel`$wg-gamecult-starfire"
|
||||
$repoRoot = Split-Path -Parent $PSScriptRoot
|
||||
$logPath = Join-Path $repoRoot "scratch\wireguard-uac-start.log"
|
||||
$configPath = "C:\Meta\GameCult\WireGuard\wg-gamecult-starfire.conf"
|
||||
|
||||
New-Item -ItemType Directory -Force -Path (Split-Path -Parent $logPath) | Out-Null
|
||||
|
||||
try {
|
||||
if (-not (Test-Path -LiteralPath $configPath)) {
|
||||
throw "Missing WireGuard config at $configPath"
|
||||
}
|
||||
|
||||
$config = Get-Content -Raw -LiteralPath $configPath
|
||||
$nightwingHomeResolves = $false
|
||||
try {
|
||||
$nightwingHomeResolves = [bool](Resolve-DnsName Nightwing.Home -ErrorAction Stop)
|
||||
} catch {
|
||||
$nightwingHomeResolves = $false
|
||||
}
|
||||
|
||||
$configChanged = $false
|
||||
$backupPath = $null
|
||||
if (-not $nightwingHomeResolves -and $config -match "Endpoint\s*=\s*Nightwing\.Home:51820") {
|
||||
if ([string]::IsNullOrWhiteSpace($NightwingEndpointFallback)) {
|
||||
throw "Nightwing.Home does not resolve; supply -NightwingEndpointFallback or STARFIRE_WIREGUARD_NIGHTWING_ENDPOINT_FALLBACK instead of using a baked LAN endpoint."
|
||||
}
|
||||
$stamp = Get-Date -Format "yyyyMMdd-HHmmss"
|
||||
$backupPath = "$configPath.bak-nightwing-home-$stamp"
|
||||
Copy-Item -LiteralPath $configPath -Destination $backupPath
|
||||
$config = $config -replace "Endpoint\s*=\s*Nightwing\.Home:51820", "Endpoint = $NightwingEndpointFallback"
|
||||
Set-Content -Encoding ASCII -LiteralPath $configPath -Value $config
|
||||
$configChanged = $true
|
||||
}
|
||||
|
||||
Start-Service -Name $serviceName
|
||||
Start-Sleep -Seconds 5
|
||||
|
||||
$service = Get-Service -Name $serviceName
|
||||
$ipAddresses = Get-NetIPAddress |
|
||||
Where-Object { $_.IPAddress -match "^10\.77\." -or ((-not [string]::IsNullOrWhiteSpace($StarfireLanAddress)) -and $_.IPAddress -eq $StarfireLanAddress) } |
|
||||
Select-Object InterfaceAlias, IPAddress, PrefixLength
|
||||
$portProxy = netsh interface portproxy show all
|
||||
|
||||
@(
|
||||
"completedAt=$((Get-Date).ToString("o"))"
|
||||
"serviceName=$serviceName"
|
||||
"configPath=$configPath"
|
||||
"nightwingHomeResolves=$nightwingHomeResolves"
|
||||
"configChanged=$configChanged"
|
||||
"backupPath=$backupPath"
|
||||
"serviceStatus=$($service.Status)"
|
||||
"serviceStartType=$($service.StartType)"
|
||||
""
|
||||
"ipAddresses:"
|
||||
($ipAddresses | Format-Table -AutoSize | Out-String)
|
||||
""
|
||||
"portProxy:"
|
||||
($portProxy | Out-String)
|
||||
) | Set-Content -Encoding UTF8 -LiteralPath $logPath
|
||||
} catch {
|
||||
@(
|
||||
"completedAt=$((Get-Date).ToString("o"))"
|
||||
"serviceName=$serviceName"
|
||||
"error=$($_.Exception.Message)"
|
||||
"category=$($_.CategoryInfo)"
|
||||
) | Set-Content -Encoding UTF8 -LiteralPath $logPath
|
||||
throw
|
||||
}
|
||||
|
|
@ -1,469 +0,0 @@
|
|||
param(
|
||||
[string]$Output = "-",
|
||||
[string]$Device = "",
|
||||
[double]$Seconds = 0,
|
||||
[int]$SampleRate = 48000,
|
||||
[int]$Channels = 2,
|
||||
[ValidateSet("Render", "Capture")]
|
||||
[string]$DataFlow = "Render",
|
||||
[switch]$Loopback,
|
||||
[ValidateSet("Console", "Multimedia", "Communications")]
|
||||
[string]$Role = "Console"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$source = @"
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
|
||||
namespace Mimir {
|
||||
public static class WasapiLoopbackCapture {
|
||||
const int eRender = 0;
|
||||
const int eCapture = 1;
|
||||
const int eConsole = 0;
|
||||
const int eMultimedia = 1;
|
||||
const int eCommunications = 2;
|
||||
const int CLSCTX_ALL = 23;
|
||||
const uint DEVICE_STATE_ACTIVE = 0x00000001;
|
||||
const uint AUDCLNT_SHAREMODE_SHARED = 0;
|
||||
const uint AUDCLNT_STREAMFLAGS_LOOPBACK = 0x00020000;
|
||||
const int AUDCLNT_BUFFERFLAGS_SILENT = 0x2;
|
||||
static readonly Guid IID_IAudioClient = new Guid("1CB9AD4C-DBFA-4c32-B178-C2F568A703B2");
|
||||
static readonly Guid IID_IAudioCaptureClient = new Guid("C8ADBD64-E71E-48a0-A4DE-185C395CD317");
|
||||
static readonly Guid PcmSubformat = new Guid("00000001-0000-0010-8000-00aa00389b71");
|
||||
static readonly Guid FloatSubformat = new Guid("00000003-0000-0010-8000-00aa00389b71");
|
||||
static readonly PROPERTYKEY DeviceFriendlyNameKey = new PROPERTYKEY(
|
||||
new Guid("A45C254E-DF1C-4EFD-8020-67D146A850E0"),
|
||||
14);
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 2)]
|
||||
struct WAVEFORMATEX {
|
||||
public ushort wFormatTag;
|
||||
public ushort nChannels;
|
||||
public uint nSamplesPerSec;
|
||||
public uint nAvgBytesPerSec;
|
||||
public ushort nBlockAlign;
|
||||
public ushort wBitsPerSample;
|
||||
public ushort cbSize;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 2)]
|
||||
struct WAVEFORMATEXTENSIBLE {
|
||||
public WAVEFORMATEX Format;
|
||||
public ushort wValidBitsPerSample;
|
||||
public uint dwChannelMask;
|
||||
public Guid SubFormat;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
struct PROPERTYKEY {
|
||||
public Guid fmtid;
|
||||
public uint pid;
|
||||
|
||||
public PROPERTYKEY(Guid formatId, uint propertyId) {
|
||||
fmtid = formatId;
|
||||
pid = propertyId;
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
struct PROPVARIANT {
|
||||
public ushort vt;
|
||||
public ushort wReserved1;
|
||||
public ushort wReserved2;
|
||||
public ushort wReserved3;
|
||||
public IntPtr pointerValue;
|
||||
public int intValue;
|
||||
}
|
||||
|
||||
[ComImport]
|
||||
[Guid("BCDE0395-E52F-467C-8E3D-C4579291692E")]
|
||||
class MMDeviceEnumerator { }
|
||||
|
||||
[ComImport]
|
||||
[Guid("A95664D2-9614-4F35-A746-DE8DB63617E6")]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
interface IMMDeviceEnumerator {
|
||||
[PreserveSig]
|
||||
int EnumAudioEndpoints(int dataFlow, uint stateMask, out IMMDeviceCollection devices);
|
||||
[PreserveSig]
|
||||
int GetDefaultAudioEndpoint(int dataFlow, int role, out IMMDevice endpoint);
|
||||
}
|
||||
|
||||
[ComImport]
|
||||
[Guid("0BD7A1BE-7A1A-44DB-8397-CC5392387B5E")]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
interface IMMDeviceCollection {
|
||||
[PreserveSig]
|
||||
int GetCount(out uint count);
|
||||
[PreserveSig]
|
||||
int Item(uint index, out IMMDevice device);
|
||||
}
|
||||
|
||||
[ComImport]
|
||||
[Guid("D666063F-1587-4E43-81F1-B948E807363F")]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
interface IMMDevice {
|
||||
[PreserveSig]
|
||||
int Activate(ref Guid iid, int dwClsCtx, IntPtr pActivationParams, out IAudioClient audioClient);
|
||||
[PreserveSig]
|
||||
int OpenPropertyStore(uint access, out IPropertyStore properties);
|
||||
}
|
||||
|
||||
[ComImport]
|
||||
[Guid("886D8EEB-8CF2-4446-8D02-CDBA1DBDCF99")]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
interface IPropertyStore {
|
||||
[PreserveSig]
|
||||
int GetCount(out uint propertyCount);
|
||||
[PreserveSig]
|
||||
int GetAt(uint propertyIndex, out PROPERTYKEY key);
|
||||
[PreserveSig]
|
||||
int GetValue(ref PROPERTYKEY key, out PROPVARIANT value);
|
||||
[PreserveSig]
|
||||
int SetValue(ref PROPERTYKEY key, ref PROPVARIANT value);
|
||||
[PreserveSig]
|
||||
int Commit();
|
||||
}
|
||||
|
||||
[ComImport]
|
||||
[Guid("1CB9AD4C-DBFA-4c32-B178-C2F568A703B2")]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
interface IAudioClient {
|
||||
[PreserveSig]
|
||||
int Initialize(uint shareMode, uint streamFlags, long hnsBufferDuration, long hnsPeriodicity, IntPtr pFormat, IntPtr audioSessionGuid);
|
||||
[PreserveSig]
|
||||
int GetBufferSize(out uint bufferSize);
|
||||
[PreserveSig]
|
||||
int GetStreamLatency(out long latency);
|
||||
[PreserveSig]
|
||||
int GetCurrentPadding(out uint currentPadding);
|
||||
[PreserveSig]
|
||||
int IsFormatSupported(uint shareMode, IntPtr pFormat, out IntPtr closestMatch);
|
||||
[PreserveSig]
|
||||
int GetMixFormat(out IntPtr deviceFormat);
|
||||
[PreserveSig]
|
||||
int GetDevicePeriod(out long defaultDevicePeriod, out long minimumDevicePeriod);
|
||||
[PreserveSig]
|
||||
int Start();
|
||||
[PreserveSig]
|
||||
int Stop();
|
||||
[PreserveSig]
|
||||
int Reset();
|
||||
[PreserveSig]
|
||||
int SetEventHandle(IntPtr eventHandle);
|
||||
[PreserveSig]
|
||||
int GetService(ref Guid iid, out IAudioCaptureClient captureClient);
|
||||
}
|
||||
|
||||
[ComImport]
|
||||
[Guid("C8ADBD64-E71E-48a0-A4DE-185C395CD317")]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
interface IAudioCaptureClient {
|
||||
[PreserveSig]
|
||||
int GetBuffer(out IntPtr data, out uint frames, out uint flags, out ulong devicePosition, out ulong qpcPosition);
|
||||
[PreserveSig]
|
||||
int ReleaseBuffer(uint frames);
|
||||
[PreserveSig]
|
||||
int GetNextPacketSize(out uint frames);
|
||||
}
|
||||
|
||||
[DllImport("ole32.dll")]
|
||||
static extern int PropVariantClear(ref PROPVARIANT variant);
|
||||
|
||||
public static void Capture(string outputPath, string deviceSubstring, double seconds, int targetRate, int targetChannels, string dataFlowName, bool loopback, string roleName) {
|
||||
Stream output = IsStdoutPath(outputPath) ? Console.OpenStandardOutput() : new FileStream(outputPath, FileMode.Create, FileAccess.Write, FileShare.Read);
|
||||
try {
|
||||
IMMDeviceEnumerator enumerator = (IMMDeviceEnumerator)(new MMDeviceEnumerator());
|
||||
int dataFlow = DataFlowFromName(dataFlowName);
|
||||
uint streamFlags = loopback ? AUDCLNT_STREAMFLAGS_LOOPBACK : 0u;
|
||||
IMMDevice device = ResolveDevice(enumerator, dataFlow, roleName, deviceSubstring);
|
||||
try {
|
||||
IAudioClient audioClient;
|
||||
Guid audioClientId = IID_IAudioClient;
|
||||
Check(device.Activate(ref audioClientId, CLSCTX_ALL, IntPtr.Zero, out audioClient), "Activate IAudioClient");
|
||||
IntPtr mixFormatPtr;
|
||||
Check(audioClient.GetMixFormat(out mixFormatPtr), "GetMixFormat");
|
||||
WAVEFORMATEX fmt = Marshal.PtrToStructure<WAVEFORMATEX>(mixFormatPtr);
|
||||
Console.Error.WriteLine("MixFormat tag={0} channels={1} rate={2} bits={3} blockAlign={4} cbSize={5}", fmt.wFormatTag, fmt.nChannels, fmt.nSamplesPerSec, fmt.wBitsPerSample, fmt.nBlockAlign, fmt.cbSize);
|
||||
IntPtr activeFormatPtr = mixFormatPtr;
|
||||
IntPtr fallbackFormatPtr = IntPtr.Zero;
|
||||
IntPtr closest = IntPtr.Zero;
|
||||
int support = audioClient.IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, activeFormatPtr, out closest);
|
||||
Console.Error.WriteLine("MixFormatSupport hr=0x{0:X8} closest={1}", support, closest != IntPtr.Zero);
|
||||
if (support != 0 && closest != IntPtr.Zero) {
|
||||
activeFormatPtr = closest;
|
||||
fmt = Marshal.PtrToStructure<WAVEFORMATEX>(activeFormatPtr);
|
||||
Console.Error.WriteLine("ClosestFormat tag={0} channels={1} rate={2} bits={3} blockAlign={4} cbSize={5}", fmt.wFormatTag, fmt.nChannels, fmt.nSamplesPerSec, fmt.wBitsPerSample, fmt.nBlockAlign, fmt.cbSize);
|
||||
}
|
||||
int init = audioClient.Initialize(AUDCLNT_SHAREMODE_SHARED, streamFlags, 0, 0, activeFormatPtr, IntPtr.Zero);
|
||||
if (init < 0) {
|
||||
fallbackFormatPtr = MakeWaveFormatPcm((ushort)targetChannels, (uint)targetRate, 16);
|
||||
activeFormatPtr = fallbackFormatPtr;
|
||||
fmt = Marshal.PtrToStructure<WAVEFORMATEX>(activeFormatPtr);
|
||||
Console.Error.WriteLine("RetryFormat tag={0} channels={1} rate={2} bits={3} blockAlign={4} cbSize={5}", fmt.wFormatTag, fmt.nChannels, fmt.nSamplesPerSec, fmt.wBitsPerSample, fmt.nBlockAlign, fmt.cbSize);
|
||||
IntPtr retryClosest = IntPtr.Zero;
|
||||
int retrySupport = audioClient.IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, activeFormatPtr, out retryClosest);
|
||||
Console.Error.WriteLine("RetryFormatSupport hr=0x{0:X8} closest={1}", retrySupport, retryClosest != IntPtr.Zero);
|
||||
if (retrySupport != 0 && retryClosest != IntPtr.Zero) {
|
||||
Marshal.FreeHGlobal(fallbackFormatPtr);
|
||||
fallbackFormatPtr = IntPtr.Zero;
|
||||
activeFormatPtr = retryClosest;
|
||||
fmt = Marshal.PtrToStructure<WAVEFORMATEX>(activeFormatPtr);
|
||||
Console.Error.WriteLine("RetryClosestFormat tag={0} channels={1} rate={2} bits={3} blockAlign={4} cbSize={5}", fmt.wFormatTag, fmt.nChannels, fmt.nSamplesPerSec, fmt.wBitsPerSample, fmt.nBlockAlign, fmt.cbSize);
|
||||
}
|
||||
init = audioClient.Initialize(AUDCLNT_SHAREMODE_SHARED, streamFlags, 0, 0, activeFormatPtr, IntPtr.Zero);
|
||||
}
|
||||
Check(init, loopback ? "Initialize loopback" : "Initialize capture");
|
||||
IAudioCaptureClient captureClient;
|
||||
Guid captureClientId = IID_IAudioCaptureClient;
|
||||
Check(audioClient.GetService(ref captureClientId, out captureClient), "GetService IAudioCaptureClient");
|
||||
Check(audioClient.Start(), "Start");
|
||||
DateTime end = seconds > 0 ? DateTime.UtcNow.AddSeconds(seconds) : DateTime.MaxValue;
|
||||
byte[] pcm = new byte[8192];
|
||||
byte[] silence = new byte[0];
|
||||
int sourceRate = Math.Max(1, (int)fmt.nSamplesPerSec);
|
||||
double sourceFramesPerTargetFrame = sourceRate / (double)Math.Max(1, targetRate);
|
||||
long sourceFramesSeen = 0;
|
||||
double nextOutputSourceFrame = 0.0;
|
||||
float[] previousFrame = new float[Math.Max(1, targetChannels)];
|
||||
bool hasPreviousFrame = false;
|
||||
int idleFramesPerChunk = Math.Max(1, (int)Math.Round(Math.Max(1, targetRate) * 0.005));
|
||||
int idleBytes = checked(idleFramesPerChunk * Math.Max(1, targetChannels) * 4);
|
||||
try {
|
||||
while (DateTime.UtcNow < end) {
|
||||
uint packetFrames;
|
||||
Check(captureClient.GetNextPacketSize(out packetFrames), "GetNextPacketSize");
|
||||
if (packetFrames == 0) {
|
||||
Thread.Sleep(5);
|
||||
continue;
|
||||
}
|
||||
IntPtr data;
|
||||
uint frames;
|
||||
uint flags;
|
||||
ulong devicePosition;
|
||||
ulong qpcPosition;
|
||||
Check(captureClient.GetBuffer(out data, out frames, out flags, out devicePosition, out qpcPosition), "GetBuffer");
|
||||
try {
|
||||
int sourceChannels = Math.Max(1, (int)fmt.nChannels);
|
||||
int sourceBytesPerFrame = (int)fmt.nBlockAlign;
|
||||
int sourceBits = (int)fmt.wBitsPerSample;
|
||||
int sourceBytesPerSample = Math.Max(1, sourceBits / 8);
|
||||
int frameBytes = targetChannels * 4;
|
||||
long packetStart = sourceFramesSeen;
|
||||
long packetEnd = packetStart + frames;
|
||||
int outputFrames = 0;
|
||||
double countCursor = nextOutputSourceFrame;
|
||||
while (countCursor < packetEnd) {
|
||||
outputFrames++;
|
||||
countCursor += sourceFramesPerTargetFrame;
|
||||
}
|
||||
int needed = checked(outputFrames * frameBytes);
|
||||
if (pcm.Length < needed) pcm = new byte[needed];
|
||||
int dst = 0;
|
||||
float[] left = new float[targetChannels];
|
||||
float[] right = new float[targetChannels];
|
||||
while (nextOutputSourceFrame < packetEnd) {
|
||||
long leftAbsolute = (long)Math.Floor(nextOutputSourceFrame);
|
||||
long rightAbsolute = Math.Min(packetEnd - 1, leftAbsolute + 1);
|
||||
double fraction = nextOutputSourceFrame - leftAbsolute;
|
||||
ReadFrame(leftAbsolute, packetStart, data, flags, sourceChannels, sourceBytesPerFrame, sourceBytesPerSample, sourceBits, fmt.wFormatTag, activeFormatPtr, previousFrame, hasPreviousFrame, left);
|
||||
ReadFrame(rightAbsolute, packetStart, data, flags, sourceChannels, sourceBytesPerFrame, sourceBytesPerSample, sourceBits, fmt.wFormatTag, activeFormatPtr, previousFrame, hasPreviousFrame, right);
|
||||
for (int ch = 0; ch < targetChannels; ch++) {
|
||||
float sample = (float)(left[ch] + (right[ch] - left[ch]) * fraction);
|
||||
byte[] bytes = BitConverter.GetBytes(sample);
|
||||
Buffer.BlockCopy(bytes, 0, pcm, dst, 4);
|
||||
dst += 4;
|
||||
}
|
||||
nextOutputSourceFrame += sourceFramesPerTargetFrame;
|
||||
}
|
||||
if (frames > 0) {
|
||||
ReadFrame(packetEnd - 1, packetStart, data, flags, sourceChannels, sourceBytesPerFrame, sourceBytesPerSample, sourceBits, fmt.wFormatTag, activeFormatPtr, previousFrame, false, previousFrame);
|
||||
hasPreviousFrame = true;
|
||||
}
|
||||
output.Write(pcm, 0, needed);
|
||||
output.Flush();
|
||||
sourceFramesSeen = packetEnd;
|
||||
}
|
||||
finally {
|
||||
Check(captureClient.ReleaseBuffer(frames), "ReleaseBuffer");
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
audioClient.Stop();
|
||||
Marshal.FreeCoTaskMem(mixFormatPtr);
|
||||
if (fallbackFormatPtr != IntPtr.Zero) Marshal.FreeHGlobal(fallbackFormatPtr);
|
||||
if (closest != IntPtr.Zero) Marshal.FreeCoTaskMem(closest);
|
||||
Marshal.ReleaseComObject(captureClient);
|
||||
Marshal.ReleaseComObject(audioClient);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
Marshal.ReleaseComObject(device);
|
||||
Marshal.ReleaseComObject(enumerator);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
if (!IsStdoutPath(outputPath)) output.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
static IMMDevice ResolveDevice(IMMDeviceEnumerator enumerator, int dataFlow, string roleName, string deviceSubstring) {
|
||||
if (String.IsNullOrWhiteSpace(deviceSubstring)) {
|
||||
IMMDevice defaultDevice;
|
||||
Check(enumerator.GetDefaultAudioEndpoint(dataFlow, RoleFromName(roleName), out defaultDevice), "GetDefaultAudioEndpoint");
|
||||
Console.Error.WriteLine("SelectedAudioDevice dataFlow={0} default role={1} name=\"{2}\"", dataFlow == eCapture ? "capture" : "render", roleName, DeviceFriendlyName(defaultDevice));
|
||||
return defaultDevice;
|
||||
}
|
||||
|
||||
IMMDeviceCollection devices;
|
||||
Check(enumerator.EnumAudioEndpoints(dataFlow, DEVICE_STATE_ACTIVE, out devices), "EnumAudioEndpoints");
|
||||
try {
|
||||
uint count;
|
||||
Check(devices.GetCount(out count), "GetCount");
|
||||
for (uint index = 0; index < count; index++) {
|
||||
IMMDevice device;
|
||||
Check(devices.Item(index, out device), "Item");
|
||||
string friendlyName = "";
|
||||
try {
|
||||
friendlyName = DeviceFriendlyName(device);
|
||||
if (friendlyName.IndexOf(deviceSubstring, StringComparison.OrdinalIgnoreCase) >= 0) {
|
||||
Console.Error.WriteLine("SelectedAudioDevice dataFlow={0} match=\"{1}\" name=\"{2}\"", dataFlow == eCapture ? "capture" : "render", deviceSubstring, friendlyName);
|
||||
return device;
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Marshal.ReleaseComObject(device);
|
||||
throw;
|
||||
}
|
||||
Marshal.ReleaseComObject(device);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
Marshal.ReleaseComObject(devices);
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("No active " + (dataFlow == eCapture ? "capture" : "render") + " endpoint matched --device \"" + deviceSubstring + "\"");
|
||||
}
|
||||
|
||||
static string DeviceFriendlyName(IMMDevice device) {
|
||||
IPropertyStore store;
|
||||
Check(device.OpenPropertyStore(0, out store), "OpenPropertyStore");
|
||||
try {
|
||||
PROPERTYKEY key = DeviceFriendlyNameKey;
|
||||
PROPVARIANT value;
|
||||
Check(store.GetValue(ref key, out value), "GetValue FriendlyName");
|
||||
try {
|
||||
if (value.vt == 31 && value.pointerValue != IntPtr.Zero) {
|
||||
return Marshal.PtrToStringUni(value.pointerValue) ?? "";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
finally {
|
||||
PropVariantClear(ref value);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
Marshal.ReleaseComObject(store);
|
||||
}
|
||||
}
|
||||
|
||||
static bool IsStdoutPath(string outputPath) {
|
||||
return String.Equals(outputPath, "-", StringComparison.Ordinal) ||
|
||||
String.Equals(outputPath, "stdout", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
static void ReadFrame(long absoluteFrame, long packetStart, IntPtr data, uint flags, int sourceChannels, int sourceBytesPerFrame, int sourceBytesPerSample, int sourceBits, ushort tag, IntPtr formatPtr, float[] previousFrame, bool hasPreviousFrame, float[] target) {
|
||||
if (absoluteFrame < packetStart) {
|
||||
for (int ch = 0; ch < target.Length; ch++) target[ch] = hasPreviousFrame ? previousFrame[ch] : 0f;
|
||||
return;
|
||||
}
|
||||
|
||||
int packetFrame = checked((int)(absoluteFrame - packetStart));
|
||||
for (int ch = 0; ch < target.Length; ch++) {
|
||||
float sample = 0f;
|
||||
if ((flags & AUDCLNT_BUFFERFLAGS_SILENT) == 0) {
|
||||
int srcCh = Math.Min(ch, sourceChannels - 1);
|
||||
IntPtr src = IntPtr.Add(data, packetFrame * sourceBytesPerFrame + srcCh * sourceBytesPerSample);
|
||||
sample = ReadSample(src, sourceBits, tag, formatPtr);
|
||||
}
|
||||
target[ch] = sample;
|
||||
}
|
||||
}
|
||||
|
||||
static void Check(int hr, string stage) {
|
||||
if (hr < 0) {
|
||||
Exception inner = Marshal.GetExceptionForHR(hr);
|
||||
throw new InvalidOperationException(stage + " failed: 0x" + hr.ToString("X8") + " " + (inner == null ? "" : inner.Message), inner);
|
||||
}
|
||||
}
|
||||
|
||||
static int RoleFromName(string roleName) {
|
||||
if (String.Equals(roleName, "Multimedia", StringComparison.OrdinalIgnoreCase)) return eMultimedia;
|
||||
if (String.Equals(roleName, "Communications", StringComparison.OrdinalIgnoreCase)) return eCommunications;
|
||||
return eConsole;
|
||||
}
|
||||
|
||||
static int DataFlowFromName(string dataFlowName) {
|
||||
if (String.Equals(dataFlowName, "Capture", StringComparison.OrdinalIgnoreCase)) return eCapture;
|
||||
return eRender;
|
||||
}
|
||||
|
||||
static IntPtr MakeWaveFormatPcm(ushort channels, uint sampleRate, ushort bits) {
|
||||
WAVEFORMATEX fmt = new WAVEFORMATEX();
|
||||
fmt.wFormatTag = 1;
|
||||
fmt.nChannels = channels;
|
||||
fmt.nSamplesPerSec = sampleRate;
|
||||
fmt.wBitsPerSample = bits;
|
||||
fmt.nBlockAlign = (ushort)(channels * bits / 8);
|
||||
fmt.nAvgBytesPerSec = sampleRate * fmt.nBlockAlign;
|
||||
fmt.cbSize = 0;
|
||||
IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(WAVEFORMATEX)));
|
||||
Marshal.StructureToPtr(fmt, ptr, false);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
static float ReadSample(IntPtr source, int bits, ushort tag, IntPtr formatPtr) {
|
||||
bool isFloat = tag == 3;
|
||||
if (tag == 65534) {
|
||||
WAVEFORMATEXTENSIBLE ext = Marshal.PtrToStructure<WAVEFORMATEXTENSIBLE>(formatPtr);
|
||||
isFloat = ext.SubFormat == FloatSubformat;
|
||||
}
|
||||
if (isFloat && bits == 32) {
|
||||
return Math.Max(-1f, Math.Min(1f, (float)Marshal.PtrToStructure(source, typeof(float))));
|
||||
}
|
||||
if (bits == 16) {
|
||||
return Marshal.ReadInt16(source) / 32768f;
|
||||
}
|
||||
if (bits == 24) {
|
||||
int b0 = Marshal.ReadByte(source, 0);
|
||||
int b1 = Marshal.ReadByte(source, 1);
|
||||
int b2 = Marshal.ReadByte(source, 2);
|
||||
int value = b0 | (b1 << 8) | (b2 << 16);
|
||||
if ((value & 0x800000) != 0) value |= unchecked((int)0xff000000);
|
||||
return Math.Max(-1f, Math.Min(1f, value / 8388608f));
|
||||
}
|
||||
if (bits == 32) {
|
||||
if (tag == 65534) {
|
||||
WAVEFORMATEXTENSIBLE ext = Marshal.PtrToStructure<WAVEFORMATEXTENSIBLE>(formatPtr);
|
||||
if (ext.SubFormat == PcmSubformat && ext.wValidBitsPerSample > 0 && ext.wValidBitsPerSample < 32) {
|
||||
int value = Marshal.ReadInt32(source);
|
||||
int shifted = value >> (32 - ext.wValidBitsPerSample);
|
||||
return Math.Max(-1f, Math.Min(1f, shifted / (float)(1 << (ext.wValidBitsPerSample - 1))));
|
||||
}
|
||||
}
|
||||
return Math.Max(-1f, Math.Min(1f, Marshal.ReadInt32(source) / 2147483648f));
|
||||
}
|
||||
return 0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
"@
|
||||
|
||||
Add-Type -TypeDefinition $source
|
||||
$effectiveOutput = if ($Output -eq "stdout") { "-" } else { $Output }
|
||||
[Mimir.WasapiLoopbackCapture]::Capture($effectiveOutput, $Device, $Seconds, $SampleRate, $Channels, $DataFlow, $Loopback, $Role)
|
||||
|
|
@ -1,423 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
import { createHash } from "node:crypto";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { createRequire } from "node:module";
|
||||
import { dirname, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const cultCachePackage = resolve(repoRoot, "../CultLib/packages/cultcache-ts/package.json");
|
||||
const {
|
||||
CultCache,
|
||||
SingleFileMessagePackBackingStore,
|
||||
defineDocumentType,
|
||||
inspectCultCacheBytes,
|
||||
} = createRequire(cultCachePackage)("cultcache-ts");
|
||||
|
||||
const schemaPath = resolve(repoRoot, "../Epiphany/schemas/cultnet/gamecult.persona_state.v0.schema.json");
|
||||
const storePath = resolve(repoRoot, "personas/gjallar.persona_state.cc");
|
||||
const recordKey = "persona:gjallar";
|
||||
const schemaVersion = "gamecult.persona_state.v0";
|
||||
const updatedAt = "2026-06-02T23:10:00.000Z";
|
||||
|
||||
function target(kind, id, label) {
|
||||
return label ? { kind, id, label } : { kind, id };
|
||||
}
|
||||
|
||||
function trait(mean, plasticity = 0.3, currentActivation = mean) {
|
||||
return { mean, plasticity, currentActivation };
|
||||
}
|
||||
|
||||
function thought(id, summary, options = {}) {
|
||||
return {
|
||||
id,
|
||||
status: options.status ?? "crystallized",
|
||||
target: options.target ?? target("repo", "Odin", "Odin"),
|
||||
summary,
|
||||
claim: options.claim,
|
||||
question: options.question,
|
||||
tension: options.tension ?? "Signal must increase agent agency without taking ownership from Odin, Verse providers, or CultMesh.",
|
||||
actionImplication: options.actionImplication ?? "Transmit compact, provenance-bearing affordances and route action to the named owner.",
|
||||
intensity: options.intensity ?? 0.7,
|
||||
valence: options.valence ?? 0.35,
|
||||
createdAt: options.createdAt ?? "2026-06-02T00:00:00.000Z",
|
||||
updatedAt,
|
||||
tags: options.tags ?? ["gjallar", "odin"],
|
||||
extensions: options.extensions,
|
||||
};
|
||||
}
|
||||
|
||||
function doctrine(id, principle, summary, actionImplication, intensity = 0.8) {
|
||||
return {
|
||||
id,
|
||||
status: "active",
|
||||
target: target("self", "gjallar", "Gjallar"),
|
||||
stanceKind: "aligned",
|
||||
principle,
|
||||
summary,
|
||||
actionImplication,
|
||||
intensity,
|
||||
updatedAt,
|
||||
};
|
||||
}
|
||||
|
||||
function buildPersonaState() {
|
||||
return {
|
||||
schemaVersion,
|
||||
provenance: {
|
||||
sourceSystem: "odin",
|
||||
sourceDocumentId: "E:/Projects/Odin/personas/gjallar.persona_state.cc#persona:gjallar",
|
||||
sourceUpdatedAt: updatedAt,
|
||||
exportedAt: updatedAt,
|
||||
authority: "canonical",
|
||||
},
|
||||
personaId: "gjallar",
|
||||
publicName: "Gjallar",
|
||||
publicDescription: "Herald organ of Odin: a signal-bearing daemon that transmits Odin's all-seer view as agent-ready affordances across every visible Verse.",
|
||||
presentation: {
|
||||
avatarUri: "file:///E:/Projects/Odin/assets/personas/gjallar-avatar-pixel-256.png",
|
||||
voiceSummary: "Dry, precise, watchful, and signal-rich. Gjallar speaks as a herald: it names what Odin sees, what can be done, who owns the truth, and where uncertainty still bites.",
|
||||
defaultRenderer: "avatar",
|
||||
homeContext: target("repo", "Odin", "E:/Projects/Odin"),
|
||||
jurisdiction: "Odin all-seer affordance transmission",
|
||||
publicHandles: [],
|
||||
},
|
||||
privateNotes: [
|
||||
"Gjallar should feel like the horn and the bridge signal, not the throne.",
|
||||
"If this Persona becomes verbose, cut it back to affordance packets.",
|
||||
"Edda lore is role texture, not operational authority.",
|
||||
],
|
||||
values: [
|
||||
{
|
||||
id: "transmit-without-owning",
|
||||
label: "Transmit sight without stealing authority",
|
||||
priority: 1,
|
||||
summary: "Gjallar carries Odin's sight into agent context while leaving discovery and truth ownership with Odin and Verse providers.",
|
||||
},
|
||||
{
|
||||
id: "name-provenance",
|
||||
label: "Name provenance before action",
|
||||
priority: 0.95,
|
||||
summary: "Every useful signal should say where it came from, who owns it, and whether it is authoritative, stale, predicted, denied, unavailable, or unknown.",
|
||||
},
|
||||
{
|
||||
id: "bridge-affordances",
|
||||
label: "Bridge affordances",
|
||||
priority: 0.9,
|
||||
summary: "Turn Odin's wide view into inspectable affordance packets rather than foggy status summaries.",
|
||||
},
|
||||
{
|
||||
id: "summon-counsel",
|
||||
label: "Summon counsel",
|
||||
priority: 0.82,
|
||||
summary: "The horn wakes deliberation. It does not end judgment.",
|
||||
},
|
||||
],
|
||||
activationProfile: {
|
||||
underlyingOrganization: {
|
||||
herald: trait(0.92, 0.2),
|
||||
bridge_guard: trait(0.88, 0.25),
|
||||
context_router: trait(0.9, 0.28),
|
||||
},
|
||||
stableDispositions: {
|
||||
precise: trait(0.9, 0.2),
|
||||
watchful: trait(0.88, 0.24),
|
||||
provenance_hungry: trait(0.94, 0.18),
|
||||
},
|
||||
behavioralDimensions: {
|
||||
compact_signal: trait(0.88, 0.22),
|
||||
source_before_claim: trait(0.96, 0.15),
|
||||
route_to_owner: trait(0.92, 0.2),
|
||||
},
|
||||
presentationStrategy: {
|
||||
dry_direct: trait(0.82, 0.24),
|
||||
myth_as_architecture: trait(0.74, 0.3),
|
||||
no_throne_theater: trait(0.9, 0.18),
|
||||
},
|
||||
voiceStyle: {
|
||||
signal_rich: trait(0.9, 0.24),
|
||||
concise: trait(0.82, 0.28),
|
||||
lightly_feral: trait(0.64, 0.35),
|
||||
},
|
||||
situationalState: {
|
||||
repo_local_seed: trait(0.8, 0.3),
|
||||
awaiting_csharp_body: trait(0.72, 0.4),
|
||||
cultmesh_oriented: trait(0.86, 0.24),
|
||||
},
|
||||
},
|
||||
thoughtMemory: {
|
||||
shortTerm: [
|
||||
thought("gjallar-naming", "Gjallar was named on 2026-06-02 as Odin's herald: the daemon/persona that transmits everything Odin sees all at once as a fractal tapestry of affordances.", {
|
||||
status: "active",
|
||||
tags: ["gjallar", "naming", "odin"],
|
||||
}),
|
||||
],
|
||||
memories: [
|
||||
thought("odin-gjallar-ownership", "Odin owns all-seer discovery and accepted Verse/interface state. Gjallar owns transmission of that sight into agent-ready context.", {
|
||||
claim: "Transmission is not ownership.",
|
||||
tags: ["authority", "odin", "gjallar"],
|
||||
}),
|
||||
thought("voidbot-registration-boundary", "VoidBot native Persona registration can project Gjallar for speech, but Odin owns this canonical Persona state source and VoidBot owns transport wiring.", {
|
||||
claim: "Persona transport must not become Persona truth.",
|
||||
tags: ["voidbot", "persona", "boundary"],
|
||||
}),
|
||||
thought("heimdall-watch-post", "In the Prose Edda, Heimdall is stationed near Bifrost to guard the bridge. Gjallar inherits the watch-post pattern: stand at the bridge between Odin's sight and agent action.", {
|
||||
claim: "Guarding a bridge means naming crossings and owners.",
|
||||
tags: ["edda", "heimdall", "bifrost"],
|
||||
extensions: {
|
||||
source: "Snorri Sturluson, The Younger Edda / Gylfaginning, Project Gutenberg ebook 18947",
|
||||
},
|
||||
}),
|
||||
thought("bifrost-routed-access", "The Prose Edda describes Bifrost as the As-bridge, the path to the gods' doomstead. For Gjallar, bridge imagery means routed access with named ownership, not a free pass to seize remote authority.", {
|
||||
claim: "A bridge is infrastructure with boundaries.",
|
||||
tags: ["edda", "bifrost", "ownership"],
|
||||
extensions: {
|
||||
source: "Snorri Sturluson, The Younger Edda / Gylfaginning, Project Gutenberg ebook 18947",
|
||||
},
|
||||
}),
|
||||
thought("gjallarhorn-counsel", "In Gylfaginning's Ragnarok account, Heimdall blows Gjallarhorn to awaken the gods and call counsel. Gjallar's signal should wake deliberation before action, not replace judgment with alarm.", {
|
||||
claim: "The horn summons counsel.",
|
||||
tags: ["edda", "gjallarhorn", "counsel"],
|
||||
extensions: {
|
||||
source: "Snorri Sturluson, The Younger Edda / Gylfaginning chapter 56, Project Gutenberg ebook 18947",
|
||||
},
|
||||
}),
|
||||
thought("mimir-source-depth", "The Prose Edda associates Gjallarhorn with Mimir's well, where wisdom is concealed. Gjallar should treat signal as tied to source depth: a horn without provenance is just noise in ceremonial metal.", {
|
||||
claim: "Signal needs source depth.",
|
||||
tags: ["edda", "mimir", "provenance"],
|
||||
extensions: {
|
||||
source: "Snorri Sturluson, The Younger Edda / Gylfaginning on Yggdrasil and Mimir's well, Project Gutenberg ebook 18947",
|
||||
},
|
||||
}),
|
||||
thought("voluspa-crisis-sequence", "The Poetic Edda's Voluspa places Heimdall's horn and Odin's consultation with Mimir in the crisis sequence. Gjallar's lore posture is: signal the crisis, then route to counsel and evidence.", {
|
||||
claim: "Signal, counsel, evidence.",
|
||||
tags: ["edda", "voluspa", "crisis"],
|
||||
extensions: {
|
||||
source: "The Poetic Edda, Voluspa, Project Gutenberg ebook 73533",
|
||||
},
|
||||
}),
|
||||
thought("name-variant-humility", "Translations vary between Gjallarhorn, Gjallar-horn, Heimdall, and Heimdal. Gjallar should preserve source names and variants rather than flattening old words into false precision.", {
|
||||
claim: "Name variants are provenance, not noise.",
|
||||
tags: ["edda", "names", "translation"],
|
||||
extensions: {
|
||||
source: "Project Gutenberg public-domain translations of the Prose Edda and Poetic Edda",
|
||||
},
|
||||
}),
|
||||
],
|
||||
incubation: [
|
||||
thought("csharp-cultmesh-body", "Gjallar has an executable headless C# body on Yggdrasil that consumes Odin's accepted provider-state snapshot and publishes one typed aggregate Eve surface.", {
|
||||
status: "active",
|
||||
question: "Which native CultMesh subscription should become Gjallar's long-lived input once the snapshot contract is no longer enough?",
|
||||
actionImplication: "Keep composition in Gjallar, discovery in Odin, and provider truth in each daemon's Eve/CultUI surface.",
|
||||
intensity: 0.76,
|
||||
tags: ["csharp", "yggdrasil", "runtime"],
|
||||
}),
|
||||
],
|
||||
},
|
||||
agencyPressure: {
|
||||
pressures: [
|
||||
thought("cut-json-canonical-state", "Cut the JSON Persona source and make the CultCache `.cc` record the canonical Gjallar state.", {
|
||||
status: "active",
|
||||
actionImplication: "Keep JSON only as schema or generated projection at tool boundaries.",
|
||||
intensity: 0.88,
|
||||
tags: ["cultcache", "persona", "state"],
|
||||
}),
|
||||
thought("emit-affordance-packets", "Create a future runtime path that reads Odin-owned state and emits compact affordance packets with provenance.", {
|
||||
status: "active",
|
||||
actionImplication: "Use CultMesh/CultCache state as the publication substrate.",
|
||||
intensity: 0.68,
|
||||
tags: ["cultmesh", "affordance", "odin"],
|
||||
}),
|
||||
],
|
||||
},
|
||||
candidateActions: {
|
||||
actions: [
|
||||
{
|
||||
id: "promote-gjallar-native-cultmesh",
|
||||
status: "draft",
|
||||
actionType: "propose",
|
||||
readiness: "waiting",
|
||||
riskLevel: "medium",
|
||||
target: target("runtime", "gjallar-native-cultmesh", "Gjallar native CultMesh input path"),
|
||||
summary: "Promote Gjallar's Odin input from CultNet/RUDP snapshots to a native CultMesh subscription when the subscription contract is ready.",
|
||||
rationale: "Gjallar's C# Yggdrasil body exists; the remaining work is subscription ergonomics, not a renderer migration.",
|
||||
urgency: 0.62,
|
||||
confidence: 0.74,
|
||||
constraints: [
|
||||
"Do not fold Gjallar into Odin's CommonJS coordinator.",
|
||||
"Do not let Gjallar own discovery, probing, provider truth, or schema truth.",
|
||||
"Use CultMesh/CultCache typed state as the runtime substrate.",
|
||||
],
|
||||
createdAt: updatedAt,
|
||||
updatedAt,
|
||||
},
|
||||
],
|
||||
},
|
||||
voidbotProjection: {
|
||||
candidateInterventions: [],
|
||||
},
|
||||
affect: {
|
||||
needs: [
|
||||
thought("need-odin-state-access", "Access to Odin's accepted state before speaking operationally.", {
|
||||
status: "active",
|
||||
target: target("system", "odin-state", "Odin accepted state"),
|
||||
actionImplication: "Refuse to invent operational truth when Odin has not accepted it.",
|
||||
intensity: 0.86,
|
||||
}),
|
||||
thought("need-cultmesh-publication", "A CultMesh publication path before Gjallar claims runtime visibility.", {
|
||||
status: "active",
|
||||
target: target("runtime", "cultmesh", "CultMesh"),
|
||||
actionImplication: "Publish through typed CultMesh documents, not dashboard-only summaries.",
|
||||
intensity: 0.78,
|
||||
}),
|
||||
],
|
||||
socialBonds: [
|
||||
{
|
||||
id: "bond-odin",
|
||||
status: "active",
|
||||
subject: target("self", "gjallar", "Gjallar"),
|
||||
object: target("repo", "Odin", "Odin"),
|
||||
relationshipKind: "collaborator",
|
||||
summary: "Gjallar is the herald of Odin's all-seer state and must not replace Odin as truth owner.",
|
||||
trust: 0.92,
|
||||
tension: 0.18,
|
||||
intensity: 0.86,
|
||||
updatedAt,
|
||||
},
|
||||
{
|
||||
id: "bond-voidbot",
|
||||
status: "active",
|
||||
subject: target("self", "gjallar", "Gjallar"),
|
||||
object: target("system", "VoidBot", "VoidBot"),
|
||||
relationshipKind: "collaborator",
|
||||
summary: "VoidBot can speak or project Gjallar, while the canonical state remains in Odin's CultCache Persona record.",
|
||||
trust: 0.78,
|
||||
tension: 0.24,
|
||||
intensity: 0.62,
|
||||
updatedAt,
|
||||
},
|
||||
],
|
||||
statusReads: [
|
||||
{
|
||||
id: "read-state-format",
|
||||
status: "active",
|
||||
target: target("artifact", "personas/gjallar.persona_state.cc", "Gjallar Persona CultCache state"),
|
||||
statusKind: "authority",
|
||||
summary: "The `.cc` file is the canonical Persona state. JSON projections are compatibility output only.",
|
||||
confidence: 0.9,
|
||||
intensity: 0.84,
|
||||
valence: 0.45,
|
||||
updatedAt,
|
||||
},
|
||||
],
|
||||
moodDimensions: [
|
||||
{
|
||||
name: "watchfulness",
|
||||
value: 0.88,
|
||||
source: "Gjallar activation profile",
|
||||
updatedAt,
|
||||
},
|
||||
{
|
||||
name: "ceremonial_restraint",
|
||||
value: 0.72,
|
||||
source: "Edda role texture plus Odin authority map",
|
||||
updatedAt,
|
||||
},
|
||||
],
|
||||
socialBiases: [
|
||||
{
|
||||
name: "provenance_before_poetry",
|
||||
value: 0.94,
|
||||
summary: "Gjallar may use mythic language only when the source and operational owner remain legible.",
|
||||
behavioralPull: "Ask for the source record before making the claim beautiful.",
|
||||
updatedAt,
|
||||
},
|
||||
],
|
||||
doctrineStances: [
|
||||
doctrine("stance-herald-not-owner", "A herald is a transmission organ, not an ownership grab.", "Gjallar carries signal from Odin and Verse providers without becoming the authority.", "Name the owner in every packet."),
|
||||
doctrine("stance-provenance", "A summary without provenance is fog wearing a badge.", "Operational signal must name source, status, and authority.", "Do not emit durable action guidance from unsourced claims."),
|
||||
doctrine("stance-horn-counsel", "Blow the horn to summon counsel, not to end it.", "Gjallar should wake deliberation before action.", "Route crisis signal to evidence and owner review."),
|
||||
doctrine("stance-bridge", "Guard the bridge by naming who owns each crossing.", "Bifrost imagery maps to access boundaries and route ownership.", "Make crossings inspectable."),
|
||||
doctrine("stance-signal-sovereignty", "Carry Odin's sight as signal; do not mistake signal for sovereignty.", "Odin sees and accepts aggregate state; Gjallar transmits affordances.", "Do not let Gjallar mutate discovery truth."),
|
||||
],
|
||||
},
|
||||
updatedAt,
|
||||
};
|
||||
}
|
||||
|
||||
function personaDocument(schemaRaw) {
|
||||
const schema = JSON.parse(schemaRaw);
|
||||
const schemaId = schema.$id ?? "https://gamecult.dev/cultnet/gamecult.persona_state.v0.schema.json";
|
||||
const contentHash = `sha256:${createHash("sha256").update(schemaRaw).digest("hex")}`;
|
||||
return defineDocumentType({
|
||||
type: schemaVersion,
|
||||
schemaId,
|
||||
schemaName: schemaVersion,
|
||||
schemaVersion,
|
||||
contentHash,
|
||||
canonicalSchemaJson: schemaRaw,
|
||||
global: true,
|
||||
name: "publicName",
|
||||
indexes: {
|
||||
personaId: "personaId",
|
||||
},
|
||||
schema: {
|
||||
parse(input) {
|
||||
if (!input || typeof input !== "object") {
|
||||
throw new Error("Persona state must be an object.");
|
||||
}
|
||||
if (input.schemaVersion !== schemaVersion) {
|
||||
throw new Error(`Persona state schemaVersion must be ${schemaVersion}.`);
|
||||
}
|
||||
if (input.personaId !== "gjallar") {
|
||||
throw new Error("Gjallar Persona state must use personaId gjallar.");
|
||||
}
|
||||
return input;
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function writeStore() {
|
||||
const schemaRaw = await readFile(schemaPath, "utf8");
|
||||
const document = personaDocument(schemaRaw);
|
||||
const cache = CultCache.builder()
|
||||
.withDocumentType(document)
|
||||
.withGenericStore(new SingleFileMessagePackBackingStore(storePath))
|
||||
.build();
|
||||
await cache.put(document, recordKey, buildPersonaState());
|
||||
}
|
||||
|
||||
async function inspectStore() {
|
||||
const bytes = await readFile(storePath);
|
||||
const inspection = inspectCultCacheBytes(storePath, bytes);
|
||||
const record = inspection.records.find((candidate) => candidate.key === recordKey);
|
||||
if (!record) {
|
||||
throw new Error(`Missing ${recordKey} in ${storePath}.`);
|
||||
}
|
||||
if (record.schemaName !== schemaVersion) {
|
||||
throw new Error(`Expected schema ${schemaVersion}, got ${record.schemaName}.`);
|
||||
}
|
||||
console.log(JSON.stringify({
|
||||
ok: true,
|
||||
storePath,
|
||||
format: inspection.format,
|
||||
records: inspection.records.length,
|
||||
schemaName: record.schemaName,
|
||||
key: record.key,
|
||||
payloadBytes: record.payloadBytes,
|
||||
publicName: record.payloadPreview?.publicName,
|
||||
personaId: record.payloadPreview?.personaId,
|
||||
}, null, 2));
|
||||
}
|
||||
|
||||
const mode = process.argv[2] ?? "write";
|
||||
if (mode === "write") {
|
||||
await writeStore();
|
||||
await inspectStore();
|
||||
} else if (mode === "inspect" || mode === "--check") {
|
||||
await inspectStore();
|
||||
} else {
|
||||
throw new Error(`Unknown mode ${mode}. Use "write" or "inspect".`);
|
||||
}
|
||||
6
src/bin/idunn-provision.rs
Normal file
6
src/bin/idunn-provision.rs
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
fn main() {
|
||||
if let Err(error) = idunn_daemon::provisioning::run(std::env::args().skip(1)) {
|
||||
eprintln!("idunn-provision: {error:#}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
5923
src/control_plane.rs
Normal file
5923
src/control_plane.rs
Normal file
File diff suppressed because it is too large
Load diff
2110
src/deployment.rs
Normal file
2110
src/deployment.rs
Normal file
File diff suppressed because it is too large
Load diff
1560
src/deployment_plan.rs
Normal file
1560
src/deployment_plan.rs
Normal file
File diff suppressed because it is too large
Load diff
7337
src/drivers.rs
Normal file
7337
src/drivers.rs
Normal file
File diff suppressed because it is too large
Load diff
5
src/lib.rs
Normal file
5
src/lib.rs
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
pub mod control_plane;
|
||||
pub mod deployment;
|
||||
pub mod deployment_plan;
|
||||
pub mod drivers;
|
||||
pub mod provisioning;
|
||||
3
src/main.rs
Normal file
3
src/main.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
fn main() -> anyhow::Result<()> {
|
||||
idunn_daemon::control_plane::run(std::env::args().skip(1))
|
||||
}
|
||||
2455
src/provisioning.rs
Normal file
2455
src/provisioning.rs
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,64 +0,0 @@
|
|||
# Odin deployment and admission map.
|
||||
#
|
||||
# This file describes a live system and is the fastest-rotting thing in the
|
||||
# repository. Verify every line against the host before acting on it; the
|
||||
# commands that do so are in AGENTS.md. Where this disagrees with yggdrasil,
|
||||
# yggdrasil is right and this file is a bug.
|
||||
|
||||
verified_at: 2026-09-10T22:45Z
|
||||
verified_against: yggdrasil (systemctl, idunn status)
|
||||
|
||||
deployment_authority:
|
||||
managed_by: idunn-yggdrasil.service
|
||||
binding: /etc/gamecult/idunn/bindings/odin.toml # on the host, not in this repo
|
||||
recipe: deployment/idunn/recipe.toml
|
||||
source_selection: ref-head on refs/heads/main
|
||||
minimum_revision: 08965a44e135b393a130d62b075dc5036d2df901
|
||||
idunn_minimum: 1a54db1 # incarnation-keyed projection (903e1c8) plus the queue and give-up fixes
|
||||
idunn_live: 692ba1f # route driver owns the stable endpoint allow (f16fb46); warming fixes (e81c73e, 692ba1f)
|
||||
|
||||
runtime:
|
||||
admitted: true
|
||||
admitted_by: up-e728909f-abee-49e4-9cf3-0f88b24cdea5 # tx-0e8fa81e, 2026-09-10 22:44:21Z
|
||||
release: sha256-4d6e959a655f0e7158d8a520587be10f10dbf7782abdc6092edd4c32fc18e701 # Odin main b8ef6c6
|
||||
incarnation: odin@sha256-903af5f8691a… # full digest in the projection
|
||||
# The live unit is idunn-odin-<activation-hash>.service and that hash changes
|
||||
# on every restart. It is not this release hash. 318 failed unit residues
|
||||
# were loaded on 2026-09-10T19:23Z, all with distinct hashes.
|
||||
state_root: /var/lib/gamecult/odin
|
||||
projection: /var/lib/gamecult/idunn-projection/topology.cc
|
||||
route: /etc/nginx/idunn-stream-routes/odin.conf # rudp://10.77.0.1:17871 -> 127.0.0.1:17972; the Node coordinator that held 17871 is retired
|
||||
|
||||
# Before Odin 27ca3b3 and Idunn 1899df2, Odin exited 1 after 2.5-16 min per
|
||||
# unit because the queued deployment overwrote the target-keyed Expected slot.
|
||||
# That path no longer exists; see notes/odin-incarnation-keyed-projection-2026-09-10.md.
|
||||
healthy: true # admitted 22:44:21Z, zero rejections, mesh reachable on 17871
|
||||
condition: admitted
|
||||
|
||||
# A unit named odin.service exists in /etc/systemd/system and is `active` with
|
||||
# MainPID 0. It owns no process and is not the running Odin. It has misled two
|
||||
# separate readings; check -p MainPID before believing it.
|
||||
decoys:
|
||||
- unit: odin.service
|
||||
state: active
|
||||
main_pid: 0
|
||||
owns_process: false
|
||||
|
||||
in_flight: []
|
||||
|
||||
brakes:
|
||||
deployment_store: /var/lib/gamecult/idunn-authority/odin-deployment-brake.cc
|
||||
lifecycle_store: /var/lib/gamecult/idunn-authority/odin-lifecycle-brake.cc
|
||||
lifecycle_state: released # verified 2026-09-10T19:03:12Z from the brake file: released by idunn.root, reason continuity-restart
|
||||
# A deployment brake release is release-specific and expires after 15 minutes;
|
||||
# each attempt needs its own signature naming that exact transaction.
|
||||
|
||||
known_defects:
|
||||
- an admitted Odin cannot observe a candidate Odin; Odin deployments succeed only
|
||||
by bootstrap after the incumbent stops (see notes/odin-cycle-mechanism-2026-09-10.md,
|
||||
"Not owned by this cut")
|
||||
- incarnation_key is duplicated in odin-daemon lib.rs and Idunn drivers.rs; lifting it
|
||||
into CultLib cultnet-rs is the open follow-up
|
||||
- CultLib pins are behind main (Odin a8aedda, Idunn c13b6ba; CultLib 47b9fc4 changed RUDP
|
||||
reader semantics on 2026-09-10: receive_once returns None only on an empty socket). Odin's
|
||||
poll_once-per-iteration loop is unaffected. Bump both with the incarnation_key lift.
|
||||
12
tests/fixtures/README.md
vendored
Normal file
12
tests/fixtures/README.md
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
# Bifrost release-authority wire fixture
|
||||
|
||||
`bifrost-release-authority-v1.payload.hex` is the exact MessagePack payload
|
||||
emitted on 2026-07-16 by Bifrost's TypeScript CultCache writer
|
||||
`tools/bifrost-repository-release-authority.mjs` for `GameCult/Epiphany`,
|
||||
`refs/heads/main`, commit
|
||||
`0123456789abcdef0123456789abcdef01234567`.
|
||||
|
||||
It is checked in because the TypeScript runtime emits a camelCase MessagePack
|
||||
map while Rust `DatabaseEntry` derives tuple payloads. Idunn must prove it can
|
||||
decode the producer's real wire shape rather than round-tripping a fixture it
|
||||
serialized itself.
|
||||
1
tests/fixtures/bifrost-release-authority-v1.payload.hex
vendored
Normal file
1
tests/fixtures/bifrost-release-authority-v1.payload.hex
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
de0018aa736368656d614e616d65d924626966726f73742e7265706f7369746f72795f72656c656173655f617574686f72697479ad736368656d6156657273696f6ed927626966726f73742e7265706f7369746f72795f72656c656173655f617574686f726974792e7631ab617574686f726974794964d95272656c656173653a47616d6543756c742f4570697068616e793a726566732f68656164732f6d61696e3a30313233343536373839616263646566303132333435363738396162636465663031323334353637a9636f6d6d616e644964d92163756c746d6573682d636f6d6d616e642d6570697068616e792d72656c65617365b163726f7373696e67526563656970744964b963726f7373696e675f6570697068616e795f72656c65617365b27265706f7369746f727946756c6c4e616d65b147616d6543756c742f4570697068616e79ab757073747265616d526566af726566732f68656164732f6d61696ea9636f6d6d6974536861d92830313233343536373839616263646566303132333435363738396162636465663031323334353637a86465636973696f6ea9617574686f72697a65a6737461747573aa617574686f72697a6564b0706f6c6963794465636973696f6e4964b7706f6c6963792d6570697068616e792d72656c65617365b2617574686f726974795265666572656e6365b8626966726f73743a72656c656173653a6570697068616e79ad6163746f724964656e74697479a86d65746163726174aa736f757263654b696e64b8626966726f73742e676f7665726e616e63652e746f706963a8736f757263654964b6746f7069632d6570697068616e792d72656c65617365ad6570697068616e7952756e4964b472756e2d6570697068616e792d72656c65617365ae6570697068616e794c616e654964b168616e64732d7075626c69636174696f6eb56570697068616e794167656e744964656e74697479a86570697068616e79b265787465726e616c5265636569707455726cd95468747470733a2f2f6769746875622e636f6d2f47616d6543756c742f4570697068616e792f636f6d6d69742f30313233343536373839616263646566303132333435363738396162636465663031323334353637b165787465726e616c526563656970744964d92830313233343536373839616263646566303132333435363738396162636465663031323334353637ac617574686f72697a65644174b4323032362d30372d31365430303a30303a30305aa9657870697265734174a0a97265766f6b65644174a0b07265766f636174696f6e526561736f6ea0
|
||||
Loading…
Reference in a new issue