Hermodr is a browser lowering, not an all-seer organ. It lived here as Node
inside a Rust repository, sharing a module directory with the coordinator, and
until this week it ran its own provider ingress and accepted publications into a
private store. The ingress went first on purpose: extracting it beforehand would
have relocated a second acceptance authority and called that progress.
Removed: the daemon, its provider subscription source and state stream, both
test files, four lifecycle scripts, the persona avatar, the hermodr:start
script, and src/odin/utils.cjs, whose only remaining consumer was Hermodr and
which it used for one of six functions.
src/odin/ falls from fourteen modules to eleven. The CommonJS surface falls from
sixteen files to twelve. What is left is the coordinator, the Rust organs, and
two adapters declaring contracts other repositories own.
The plan document stays as the record of what left and why. Phase 5, static
lowering, is Hermodr's business now.
5 tests pass, syntax check clean over twelve files.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
defineOdinDocuments was 536 lines defining 45 CultCache document types for Eve,
Ghostlight, Heimdall, Idunn, Muninn, Sleipnir, Aetheria, VoidBot and Stonks.
The name was the only part of it that was Odin's.
The catalog is now cultcache-ts's defineSwarmDocuments, verified byte-identical
to what this file produced. What remains here is 26 lines resolving CultLib and
re-exporting under the name Odin's consumers already call, so odin-coordinator
and the two discovery tests are untouched.
This was the last shared dependency between Odin and Hermodr. utils.cjs now has
exactly one consumer, hermodr-daemon, using exactly one of its six functions, so
it leaves with Hermodr rather than staying as a library nobody here reads.
CULTLIB_ROOT is honoured with the sibling walk as fallback, matching the health
adapter and hermodr-daemon.
22 tests pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
idunn-rudp.cjs was 324 lines of which almost none were about Idunn. The
transport moved to CultLib as cultnet-ts's signed-daemon-health module, beside
the RUDP session it was already built on. What is left here is 65 lines: the
three constants naming Idunn's wire contract, and the call shapes Odin's
consumers already use.
The constants are declared, not decided. Idunn defines them in
docs/signed-daemon-health-authority.md, which until today did not state the
connection id at all -- the value describing how to reach Idunn existed only in
this consumer. Declaring two constants against a documented contract is not
duplicated authority; duplicating 311 lines of transport across Odin and a
future Hermodr repository would have been.
The module also stops resolving CultLib by walking up three directories from its
own location. It now honours CULTLIB_ROOT with that walk as a fallback, matching
what hermodr-daemon.cjs already did. The old assumption held only while Odin sat
beside CultLib, and Erycina broke on exactly that when it moved.
The signed-health test now supplies IDUNN_HEALTH_CONTRACT to the publisher it
builds by hand, and still asserts the literal wire values, so a change to the
contract constants fails loudly rather than being absorbed.
Verified: 22 tests pass across six files, including signature verification
against the relocated implementation, and the syntax check is clean.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Hermodr constructed its own OdinLivePublicationSource, opened its own
provider-session ingress on its own bind address with its own token, and served
that private store to browsers. A surface reached a browser only if its provider
bound to Hermodr specifically, which made a required ingress out of something
named a bridge, and meant a provider had to publish twice: once where the Verse
expects it, and once into Hermodr so an operator could watch their own command
land.
Providers own their state. Odin advertises where it lives. Hermodr now resolves
a provider through the catalog and reads that provider directly, which is what
the command path has always done: findProviderCommandRoute looks a route up in
the catalog and publishes intent to the provider's own endpoint. The state leg
now matches the intent leg, and the round trip closes without the provider
publishing anywhere twice.
ProviderSubscriptionSource presents the same narrow port the store did, so
HermodrStateStreamRegistry is untouched and browsers keep their sequence numbers
and stale/reconnected transitions. Its read is injected, which is what makes the
lifecycle semantics testable without a live Verse.
One invariant is worth naming because getting it wrong is silent: a failed read
is not a withdrawal. A provider restart or a dropped packet must not render as
the provider retracting state it still holds, so read errors are reported and
the previous value stands, while genuine absence withdraws. Both are tested.
Route resolution is cached for thirty seconds and the state is not cached at
all, since routes change slowly and state is the thing being watched. A wedged
peer is dropped so the next poll reconnects.
Deleted: the ingress construction, --provider-session-bind and
--provider-session-token, live-publication-source.cjs,
provider-session-ingress.cjs, their test, parseBind, and three imports left
unused. src/odin/ falls from fourteen modules to twelve. The removed options and
env vars now fail loudly through the existing rejectRemovedOptions path in both
spellings parseArgs can produce.
Polling is a first cut, not the destination. CultMesh ships
cultMeshPollingDocumentWatcher for exactly this shape, and the cadence is
--provider-poll-interval-ms. A push subscription belongs here later; it does not
change the authority, which is the point of this commit.
Verified: 22 tests pass across six files, syntax check clean over sixteen
CommonJS files. Not verified: behaviour against a live provider over RUDP, which
needs a running Verse and is an operator step.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Completes the extraction. Unit files and documentation went to the Idunn
repository, where a service's own deployment surface belongs. Operator scripts
went to gamecult-ops, which is where fleet tooling lives.
To Idunn: scripts/linux/idunn-yggdrasil{,.service,.sudoers}, docs/idunn.md,
docs/idunn-deployment-authority.md,
docs/idunn-signed-daemon-health-authority.md, src/Idunn/README.md.
To gamecult-ops/scripts/idunn/: the eight PowerShell and cmd operator tools --
deployment targets, startup install, swarm coverage health, operator alarm
notification, local start.
Staying in Odin, deliberately: crates/odin-core/src/idunn.rs, Odin's own
projection of Idunn state for aggregation; src/odin/idunn-rudp.cjs, its client
for talking to Idunn; test/idunn-signed-health.test.cjs, its own integration
test; and deployment/idunn/recipe.toml, which despite the path builds
odin-daemon and is Odin's recipe expressed in Idunn's format.
docs/transport-shortcut-inventory.md is a dated evidence ledger citing several
of the moved script paths. Its entries are left as written and a path note
added at the top -- rewriting a ledger to match a later reorganisation would
destroy the record it exists to keep.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The CultNet submodule and the in-tree CultCache copy are gone. Both now come
from CultLib, and vendor/cultmesh-rs is the only fork left -- it too consumes
CultLib, so exactly one version of each crate spans the graph. That mattered:
mixing sources would have put two cultnet-rs crates in one build, and the types
would not have unified.
The in-flight CultMesh RUDP document server comes with it because it is a
prerequisite, not a coincidence. The previously committed CultMesh calls
assume_connected(), a CultNet method Odin's fork carried and CultLib does not,
so the old code cannot build against CultLib at all. The newer code no longer
calls it. Verified by reverting the file and watching it fail.
Three API differences needed adapting: CultLib's transport options carry a
reconnect_policy the fork lacked (7 sites), its create_ack builds the packet
straight from ack state and so cannot fail, and its transport profile owns each
channel's reliable expiry.
Everything but odin-daemon builds; odin-daemon is Unix-only and does not compile
on this machine either way. muninn and sleipnir pass 162 tests. The one failure,
rudp_media_transport_options_follow_low_latency_profile, expects an Unreliable
media channel while every CultNet lineage including both revisions Odin had
pinned declares it Reliable, so it failed before this change too.
The in-flight idunn-daemon work is deliberately left uncommitted; the committed
tree was verified to build without it. Also left alone: the untracked
odin-daemon crate and deployment recipe, and the root manifest, whose now-dead
vendor excludes are inert. That recipe still requires the vendor/cultnet-rs
gitlink and will refuse admission until that line is dropped -- the fix is in
the working tree, waiting on that directory being tracked.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>