Commit graph

551 commits

Author SHA1 Message Date
Meta Vens
d41c744b2e Muninn has one body: drop the workspace copy
The three muninn crates, their seventeen actuator scripts, the two Raven
capture-mux wrappers that called one of them, and the two Muninn docs all
leave this workspace. GameCult/Muninn has carried them since 2026-09-09 and
has moved on (media stream advertisement, receivers dialling the producer);
this copy was a fork drifting behind under the same name, and gamecult-ops
now binds every Muninn target to the Muninn repository. Odin keeps the
Muninn records in odin-core until they are moved; that is the remaining
piece of the extraction.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 23:39:16 +02:00
Meta Vens
1ff6a323de Record the pending Muninn two-bodies decision and its rebind checklist
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 23:04:54 +02:00
Meta Vens
bf40a7af99 Note: backlog was resident terminal transactions; brake release is the next operator step
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 23:01:52 +02:00
Meta Vens
768a483ab3 Map: Odin is down awaiting a brake release for the sealed candidate; defects list reflects the rebuild
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 22:55:24 +02:00
Meta Vens
396af5e997 Drop the duplicated correction block
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 22:39:21 +02:00
Meta Vens
c22f6aea40 Correct the cycle-mechanism note now that the fork is resolved
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 22:39:06 +02:00
Meta Vens
7d280dd41b Correct the cycle-mechanism note now that the fork is resolved
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 22:38:48 +02:00
Meta Vens
9fba320b6e Record the incarnation-keyed projection rebuild and correct the superseded notes
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 22:38:33 +02:00
Meta Vens
faa53f3b37 Key Odin's projection reader and store by incarnation, not target
A target being replaced has two incarnations at once. Idunn's projection and
Odin's store keyed every record by target, so the candidate's Expected
overwrote the slot the incumbent validated itself against and the incumbent
died on its next heartbeat. Records now live under {target}@{expected sha}:
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 are the previous single-slot shape and project
nothing.

The negative check the rebuild contract asks for is in
a_candidate_expected_beside_the_incumbent_cannot_reject_its_presence.

Verified with cargo test -p odin-core -p odin-daemon in rust:1.95-bookworm on
yggdrasil: 22 + 12 (+1 ignored) + 4 passed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 22:21:30 +02:00
Meta Vens
118f7503a8 Record why Odin cycles and add a projection authority probe
Live probe on yggdrasil: the queued deployment is not starved. Each time a
continuity restart completes it seals and publish_expected replaces the one
Expected slot for odin with the candidate's; the incumbent's next heartbeat
fails activation-expected-projection against its own catalog and it exits.
notes/odin-cycle-mechanism-2026-09-10.md carries the timeline, snapshots'
decode, the authority map, and the fork to decide. projection_authority_probe
is the ignored dev test that decoded the captured projection versions.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 21:43:35 +02:00
Meta Vens
a9cd5419f3 Note: 2265184 also carried a diagnostic probe I did not write
2265184 claims to be a deploy-script fix and is also 70 lines adding an
ignored `projection_authority_probe` test to crates/odin-daemon/src/lib.rs.
That probe was uncommitted in this working tree and `git add -A` swept it
in under a message that says nothing about it.

Recording rather than rewriting, per this repo's ledger convention: main
is shared and force-pushing it to tidy a message is worse than a message
that needed a correction.

The probe itself is sound and worth keeping -- it decodes a captured
projection and prints what Odin's authority classifier sees for one
target, which is the diagnostic the current cycling incident wants. It is
`#[ignore]` and env-driven, so it changes nothing unless invoked.
Verified green after the fact: cargo test -p odin-daemon ->
10 passed; 0 failed; 1 ignored.

Third time this session that `git add -A` in a filesystem shared with
other agent sessions has committed work that was not mine. `git status`
before `git commit`, every time, is the cheap habit that would have caught
all three.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 21:42:42 +02:00
Meta Vens
2265184da3 Ship all three Python packages to Repixelizer
CultLib split packages/cultcache-py into cultcache-py, cultnet-py and
cultmesh-py. Module names did not change, so archiving only the first
still succeeds and produces a tarball missing two thirds of the library --
a failure that surfaces later as an ImportError with nothing pointing back
to this line.

The two sibling deploy scripts ship cultnet-ts and cultcache-ts and are
unaffected by a Python split.

Not exercised: repixelizer.service is inactive, so this is a trap defused
rather than a break repaired.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 21:41:42 +02:00
Meta Vens
d6b151515d Correct the Odin unit lifetime and journal-read rakes in the re-entry surfaces
Verified on yggdrasil 2026-09-10T19:23Z: units exit after 2.5-16 min, not
~96 s; that number is the RUDP ack timeout. Unit hashes are per-activation,
not the release hash. Unprivileged journalctl returns a false zero.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 21:25:42 +02:00
Meta Vens
52dae94024 Record the Odin deployment map that AGENTS.md points at
AGENTS.md named state/map.yaml as the current deployment and admission
map; leaving it absent would have been a dangling reference in the file
that tells a cold session where to look.

Carries the decoy odin.service unit -- active, MainPID 0, owning no
process -- and the armed queued deployment, because both are things a
reader will otherwise discover the hard way.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 21:20:00 +02:00
Meta Vens
1f78867993 Give Odin its own doctrine, re-entry surfaces, and a deployment handoff
Odin had no AGENTS.md, no CLAUDE.md and no notes/ or state/, so every
session started cold in the repo whose deployment is the least routine in
the estate.

AGENTS.md carries what is specific to Odin rather than repeating estate
doctrine: that the host is authoritative, that a local compile says
nothing about the Linux release, that /etc/machine-id must be mounted or
identity tests fail in a way that looks like broken code, and the two
listing traps that have each misled a reading -- an alphabetically
truncated unit list, and odin.service being active while owning no
process.

notes/odin-deployment-handoff-2026-09-10.md records why Odin currently
cycles and why the two obvious interventions fail: an engaged lifecycle
brake parks a continuity transaction on the target and blocks deployment,
which the operator doctrine says it must not; and freeze_one_queued_command
only runs when continuity made no progress, so a queued deployment is
starved on exactly the unstable target it exists to repair.

Both defects are named with the state that proves them rather than left
as a description of a bad night.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 21:19:29 +02:00
Meta Vens
f7e3fc341a Update Cargo.lock for the CultLib bump
0e540ca moved the rev in four Cargo.toml files and left the lock naming
c13b6ba. Idunn's build runner passes --locked, precisely so a sealed
source builds the dependency set it was sealed with, and refused:

  error: cannot update the lock file /workspace/Cargo.lock because
  --locked was passed to prevent this

Which is the runner doing its job. The failure landed in Sealing, before
fencing, so the admitted incumbent was never touched -- Odin stayed up on
the same pid throughout.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 12:10:43 +02:00
Meta Vens
0e540cae18 Bump CultLib to a8aedda for bounded fragment reassembly
cultnet-rs removed a fragment buffer only on successful reassembly and
refused new fragmented payloads once 64 sets were stranded, so any
sustained loss on a fragmenting channel ended with the receiver returning
"RUDP pending fragment-set limit reached" for every subsequent fragmented
payload. Odin is the rendezvous every other target is observed through;
a receiver that stops accepting fragmented traffic after a bounded number
of losses is the wrong failure mode for that role.

Measured on Raven at 3% loss, 12 Mbps: the old receiver dies at 3 and 6
fragments per payload, the new one delivers 91.0% and 82.7%, and the
loss amplification matches 1-(1-p)^n exactly.

The pin also carries the media stream contracts and the media delivery
option, neither of which Odin uses, and the TypeScript and Python halves
of the same eviction fix.

All 11 CultLib revs across odin-core, odin-daemon, muninn-daemon and
sleipnir-daemon move together; leaving any behind gives two cultcache-rs
copies and a DatabaseEntry that is not DatabaseEntry.

  cargo test -p odin-core -p odin-daemon -> 22 passed, 10 passed, 4 passed,
  0 failed

muninn-psmoveapi-tracker still fails to link in the build image for want
of libpsmoveapi. It declares no CultLib dependency, so the pin cannot
reach it; that is a missing native library on the builder.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 12:04:12 +02:00
Meta Vens
3a6368f93d Reserve a publisher sequence only once the facts have changed
reconcile reserved a sequence from the watermark before comparing the
new correlation against the prior one, so every refresh -- Odin's idle
timer, a restart, a dependency re-read -- advanced the mark and fsynced
topology.cc while returning the prior bytes unchanged. The sequence Idunn
receives then jumps by however long Odin sat idle rather than by the
number of observations that actually differed, which is the opposite of
what a monotonic publisher sequence is for.

same_correlation_facts normalizes publisher_sequence to the prior's
before comparing, so the reserve has no bearing on the dedupe decision
and moves below it wholesale.

Verified against the old ordering: three idle refreshes then one real
observation yields sequence 5 instead of 2.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 14:14:29 +02:00
Meta Vens
35a6f31df6 Stop comparing the projected activation at startup
Idunn publishes an activation only after observing the process that owns it,
and every launch -- deployment or continuity restart -- is issued a fresh one.
So at startup the projection either carries no activation yet or still names
the incarnation being replaced. Neither is this process's, and both are normal.

Requiring one broke the first start of a target. Requiring a match broke every
start after it, which between them is every start there is: Odin could be
deployed exactly once, and any restart or redeploy left it refusing to start
while continuity restarted it into the same wall.

Nothing is taken on trust for dropping it. The bundle's own activation is
verified against the Idunn anchor, and the write lease -- the thing that
actually authorises writing state -- is still checked against the live
projection before Odin writes anything.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 23:06:09 +02:00
Meta Vens
fbfc2e9172 Keep a correlation's publisher sequence across withdrawal
The sequence was derived from the stored correlation, so withdrawing one took
the count with it and the next started at 1. Idunn remembers the highest
sequence it has admitted per target and refuses anything at or below it, so
every later correlation was refused as a replay -- and since that refusal is
what withdraws the correlation, the count never climbed past the old mark. A
deadlock that tightened with each attempt, and it cost Heimdall every
deployment after its first two.

It was also the exact weakness the check exists to prevent: while the count
was back at 1, a replayed correlation carrying the old sequence 2 outranked
the genuine one.

The mark now lives in its own record, which withdrawal does not remove, and is
reserved before the correlation that uses it is published -- so a crash in
between leaves it ahead. Sequences may skip; they may never repeat. A stored
correlation ahead of the mark still wins, which carries an existing store onto
the new scheme without a migration.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 22:37:29 +02:00
Meta Vens
08965a44e1 Do not require an activation Idunn cannot have published yet
Odin's startup demanded that the live Idunn projection already carry the
runtime activation matching its bundle. Idunn publishes that record only
after it has observed the started process -- the activation carries that
process's executable digest -- so at the moment Odin checks, it cannot exist.
Every first start failed with "Idunn live projection differs from Odin's
immutable runtime bundle".

The Expected incarnation is still required to match, and a projected
activation, once present, must be this one, so a stale or foreign activation
is still refused. The bundle's own activation is verified against the Idunn
anchor regardless, and the write lease is checked against the live projection
before Odin writes anything.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 01:16:10 +02:00
Meta Vens
8e16f56f33 Stop comparing LISTEN_PID to our own pid
Idunn launches Odin with PrivatePIDs=yes -- the private PID namespace is part
of the isolation it proves between a candidate and the incumbent -- and
systemd sets LISTEN_PID to the pid it knows, which is in the outer namespace.
Inside, /proc/self/status reports only the namespace-local pid (measured: pid
3, LISTEN_PID 2994180). The value does not merely differ, it names something
this process cannot observe, so the equality could never hold and every
candidate exited immediately with "systemd signer descriptors belong to
another process".

The protocol check exists so a child does not mistake inherited LISTEN_FDS
for its own. What remains is stronger: the exact descriptor count and name
order, and then the content, which must verify against the trust anchor Idunn
published. Descriptors that were not Idunn's fail that verification.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 00:58:15 +02:00
Meta Vens
ec6dec42e8 Name the runner environment each build step needs
Only a step's declared `required_environment` is forwarded to the runner
container. Odin's steps declared none, so CARGO_HOME and HOME never arrived
and cargo fell back to the image's read-only /usr/local/cargo, failing to
fetch the rev-pinned CultLib dependency.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 00:20:45 +02:00
Meta Vens
946d28a4c7 Take the state root, not the store path
Odin's recipe declared `[state]` with a `topology` slot but its launch
contract passed `--store` as an absolute path. Idunn refuses that pairing:
a workload whose recipe declares state must bind a state root, and binding a
state root requires the launch contract to carry a `state_root` argument. No
operator binding for Odin could have been admitted. This never surfaced
because `idunn validate` was run recipe-only, so `admit` never saw it.

The rule is Idunn's on purpose -- it is what makes Idunn the owner of where a
target's state lives, so it can create and harden the root. Odin naming its
own absolute store path routed around that: a binding could have placed the
store outside the root Idunn prepared.

So the daemon now takes `--state-root` and derives the store from the slot
the recipe already declares. The store path is no longer supplied by anyone;
`--store` is rejected, and a test pins that.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 00:11:42 +02:00
Meta Vens
a60c4b08f9 Record Phase 5 as done
Static lowering landed in GameCult/Hermodr: a library and CLI rendering a
surface graph to standalone HTML through the same renderer the browser uses,
with command controls inert because a static page has no back-channel.

All five phases of the Hermodr extraction are complete. This document stays as
the record of what left Odin and why.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 20:19:38 +02:00
Meta Vens
3f22fd1d76 Hermodr leaves Odin
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>
2026-09-06 19:46:06 +02:00
Meta Vens
20bacabf7c Reduce Odin's document catalog to a CultLib adapter
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>
2026-09-06 19:41:28 +02:00
Meta Vens
907db01329 Record Phase 1 as done by splitting rather than moving
The plan said move idunn-rudp.cjs to Idunn. Reading it first showed that would
relocate 311 lines of generic CultNet transport into a Rust repository and hand
Odin a fragile sibling-path dependency. The split was taken instead: transport
to CultLib beside the RUDP session it builds on, constants documented by Idunn,
a 65-line adapter left in Odin declaring that contract.

Phases 1 and 2 are both complete. documents.cjs is now the only genuinely shared
dependency standing between Odin and a future Hermodr repository, which makes it
Phase 3's single remaining question.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 19:30:34 +02:00
Meta Vens
ac3336adeb Reduce Odin's Idunn health publisher to Idunn's contract
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>
2026-09-06 19:29:53 +02:00
Meta Vens
4c2b022df3 Record Phase 2 as done and why Phase 1 was not
Phase 2 landed: Hermodr reads providers through Odin-advertised routes instead
of accepting publications into a private store.

Phase 1 did not, and the plan now says why instead of leaving a specification I
had already found to be wrong. Reading idunn-rudp.cjs before moving it changed
the answer: 13 of its 324 lines mention Idunn and most are error strings. The
Idunn-owned content is two constants. The other 311 lines are generic CultNet
transport, and moving them into a Rust repository with no Node packaging would
also hand Odin a cross-repo sibling-path dependency of exactly the kind that
broke Erycina's provider publisher when that repository moved. It buys nothing
until Hermodr leaves in Phase 4, because both consumers still live here.

Three ranked options recorded. The one I would take splits the transport into
CultLib beside cultnet-ts and leaves Idunn owning its connection ID and schema
name, which removes the sibling-path bug rather than relocating it. That is
larger than this phase assumed and touches CultLib, so it wants a decision
rather than being folded in quietly.

Also dropped idunn:build and idunn:start from package.json. They invoke
cargo -p idunn-daemon, and that crate left Odin with the extraction.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 18:43:09 +02:00
Meta Vens
b6f3296484 Hermodr reads providers instead of accepting from them
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>
2026-09-06 18:41:39 +02:00
Meta Vens
9282ea9dd6 Correct the Hermodr plan: Odin is a directory, not a state owner
The previous revision assigned provider ingress and live-publication acceptance
to Odin, and described Hermodr as a second Odin holding a parallel instance of
the same store. Both claims were wrong, and the second was asserted without
checking.

Odin is a rendezvous organ. It publishes what state is available, what schema
speaks it, and where to reach it. It is not in the data path, and it never
carried this state: neither odin-coordinator.cjs nor any Odin test references
OdinLivePublicationSource or provider-session-ingress.cjs. Those modules are
Hermodr's own state machinery wearing Odin's name and filed under src/odin/,
which is what made it read as sanctioned.

So there is no second Odin. There was never a first one for this state. The cut
is not to hand acceptance back to Odin, which does not want it, but to delete it
and have Hermodr resolve providers through the catalog and subscribe to them
directly.

The command path already models this correctly. findProviderCommandRoute looks a
route up in the catalog and publishes to the provider's own endpoint, and
Erycina's surface names Odin as discoveryOwner while pointing canonicalSurfaceUri
at Erycina's own address. The retrofitted leg got the architecture right; the
original state leg did not.

Dependency picture corrected by actual reference counts. Only documents.cjs and
idunn-rudp.cjs are genuinely shared with the coordinator. utils.cjs,
live-publication-source.cjs, and provider-session-ingress.cjs are Hermodr-only
and leave with it or are deleted, which shrinks src/odin/ from fourteen modules
to ten and removes the documents.cjs decision from the critical path of
everything else.

Negative checks now include the one that matters for the corrected model: with
Odin stopped, existing provider subscriptions must keep delivering. If they do
not, Odin is in the data path and the plan failed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 18:29:27 +02:00
Meta Vens
baed89bbd8 Plan the Hermodr extraction and authority cut
Hermodr is described as the Eve-to-browser lowering bridge. It is also a second
provider ingress and a second acceptance authority: main() constructs its own
OdinLivePublicationSource, binds its own provider session with its own token,
accepts publications into that private Map, and serves them to browsers. Odin's
coordinator holds a separate instance over the same providers with no
reconciliation. A surface reaches a browser only if its provider binds to
Hermodr, which makes a required ingress out of something named a bridge.

The intent path is not the problem and is kept as-is. POST /hermodr/commands/eve
is generic across providers, wraps intent as gamecult.eve.command.v1, stamps
itself as the relay rather than the author, publishes to the provider's
advertised route, and returns a receipt. Eve's client dispatches controls
through it and refuses anything that is not a correlated receipt in state
reconciled.

The defect is that the loop does not close. Intent leaves by CultMesh to the
owner of the consequence; the consequence returns only through Hermodr's private
ingress. A provider must publish twice for the Verse to be correct and for the
operator to see their own action land. Cutting the acceptance authority is what
closes it: with the state leg subscribed to Odin-accepted state, a command's
effect returns by the same route as every other change.

The plan orders the work so the authority cut happens in place, before any file
moves, because an extraction that carries the split brain into a new repository
has only changed its address. Static lowering lands last and as a library with a
CLI, since emitting bytes from a graph earns no daemon, and requiring one would
reintroduce the dependency static lowering exists to remove.

Records five open dependencies, three phases of subtraction, and negative checks
that prove the old path can no longer accept, no longer override, and no longer
paper over Odin being absent.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 18:24:30 +02:00
Meta Vens
6575de4367 Drop the workspace excludes for vendor trees that no longer exist
The extraction removed vendor/cultmesh-rs and left crates/idunn-daemon as an
empty directory shell. Git does not track empty directories, so `git status`
was clean while the tree still differed from HEAD -- the shells are gone now.

Cargo.toml still excluded vendor/cultcache-rs and vendor/cultnet-rs, both
absent since Odin started consuming CultLib. An exclude for a path that does
not exist is a compensator with nothing left to compensate for.

cargo metadata resolves; odin-core still 22/22.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 23:16:28 +02:00
Meta Vens
df10b554a9 Merge the Idunn extraction: Odin no longer owns deployment authority
Idunn lived as a crate inside the repository of a service it manages, which
inverted the authority it exists to hold. It is now GameCult/Idunn with its
full 136-commit history; this merge removes the old body from Odin.

Cut: crates/idunn-daemon and vendor/cultmesh-rs. Odin keeps its own Idunn
projection (crates/odin-core/src/idunn.rs), its RUDP client, and its recipe
under deployment/idunn -- those are Odin's view of a managed relationship,
not Idunn's authority.

Conflicts resolved:
- crates/idunn-daemon/src/main.rs, scripts/linux/idunn-yggdrasil: deleted,
  per the extraction. Both live in GameCult/Idunn.
- docs/architecture.md: took the branch. Main still described Gjallar as
  Nightwing-resident under E:\Projects\Gjallar; it runs on Yggdrasil from
  /srv/gjallar/current, confirmed against the host.
- src/odin/documents.cjs: union. All three hunks were pure branch-side
  additions of the ghostlight.schema_catalog type; markers stripped rather
  than taking --theirs, to preserve auto-merged HEAD changes elsewhere in
  the file. node --check passes.

Verified: odin-core 22/22 on Windows; odin-daemon type-checks clean for
x86_64-unknown-linux-gnu, which is its real target -- it is unconditionally
Unix (systemd socket activation via File::from_raw_fd) and does not build
on Windows by design.

main is tagged pre-idunn-extraction-main: the last tree containing the
swarm keepalive supervision and the July Bifrost Persona feedback target,
neither of which exists on the branch or in GameCult/Idunn. See the merge
notes for why that matters.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 22:33:39 +02:00
Meta Vens
b31340417c Add MIT license pre-idunn-extraction-main
The repository was public but carried no license, so default copyright applied
and all rights were reserved. Readers could look at the code and had no right to
run, copy, modify, or self-host it. GitHub's terms grant forking within GitHub
and nothing past that.

This matters now because Erycina is being prepared for founding creators to
self-host, and its intended one-click deployment installs Idunn to bring up
Odin, Heimdall, and Erycina together. Erycina is MIT. A creator accepting that
deploy would otherwise be handed dependencies they have no license to run, which
makes "self-hosted" true only for us.

MIT to match CultLib, VoidBot, and Eve, and the stated intent for GameCult
projects.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 21:52:55 +02:00
Meta Vens
222fe03896 Move Idunn's unit files, docs and operator scripts out of Odin
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>
2026-09-05 18:16:38 +02:00
Meta Vens
7ea146dd36 Cut idunn-daemon and the vendored cultmesh fork out of Odin
Idunn now lives at https://github.com/GameCult/Idunn with its full 136-commit
history. Odin no longer contains the deployment authority that manages it.

Also removes vendor/cultmesh-rs, the private fork that had diverged from
CultLib in both directions. Its unique capability -- the RUDP document server
and the message_id correlation rework -- was reconciled into CultLib first, so
this deletes a duplicate rather than a capability. The four crates that
consumed it by path (muninn-daemon, odin-core, odin-daemon, sleipnir-daemon)
now consume CultLib at c13b6ba like everything else.

What deliberately stays in Odin: crates/odin-core/src/idunn.rs, which is Odin's
own projection of Idunn state for aggregation and is Odin's to own;
src/odin/idunn-rudp.cjs, its client for talking to Idunn; and
deployment/idunn/recipe.toml, which despite the path is Odin's own deployment
recipe expressed in Idunn's format -- it builds odin-daemon.

odin-core: 22 passed, 0 failed, unchanged across the cut. odin-daemon still
fails to compile on Windows for the pre-existing Unix-only std::os::fd reason.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 18:09:11 +02:00
Meta Vens
0995222c17 Sever idunn-daemon's last import from odin-core
IdunnDaemonHealthTrustBindingRecord and its schema constant moved to
cultnet-rs (GameCult/CultLib@c13b6ba). odin-core now imports them from
cultnet_rs instead of defining them, and idunn-daemon/provisioning.rs
pulls all six provider-health symbols from cultnet_rs directly, merged
into its existing cultnet_rs use block. Dropped the odin-core path
dependency from idunn-daemon/Cargo.toml — nothing in the crate reaches
odin_core:: anymore. Bumped the CultLib git rev across all six
consuming manifests to the new commit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 17:43:24 +02:00
Meta Vens
ba8b4afbb6 Bump CultLib to ae0ac0d and give odin-core its missing rmp-serde dev-dep
Moves all six CultLib pins from cbde940 to ae0ac0d, which carries the
reconciled cultmesh-rs. The delta is three commits and near-nothing in the
packages Odin consumes: a license line, two lines in cultnet-rs replication,
and one additive pub fn.

The dev-dependency is a real pre-existing defect, not fallout from the bump.
odin-core's tests use rmp_serde in crates/odin-core/src/idunn.rs but never
declared it, so the lib-test target has never compiled. Verified pre-existing:
9 errors at the old pin, 8 at the new. With the dep declared, odin-core tests
compile and 22 pass -- including idunn_records_round_trip_through_cultmesh,
which matters for the extraction this branch is preparing.

Note for anyone checking this on Windows: odin-daemon does not build here and
that is expected, not a regression. It imports std::os::fd and from_raw_fd,
which are Unix-only. Identical failure at both pins.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 17:35:54 +02:00
Meta Vens
3d939a8523 Apply rustfmt
The drift was 94 hunks across 4 files, not 94 files as I described it earlier.
muninn-daemon carries almost all of it; sleipnir-daemon needed four lines.

Checked rather than assumed. Ignoring whitespace, the only remaining deltas are
trailing commas rustfmt adds when it wraps an argument list, and braces removed
from single-expression closure bodies. Both are semantics-preserving.
sleipnir-daemon's change is whitespace and nothing else.

Everything except the Unix-only odin-daemon builds; muninn and sleipnir pass 162
tests, with the same pre-existing media-channel failure.

vendor/cultmesh-rs is its own workspace so `cargo fmt` does not reach it, but it
reports no drift of its own.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 15:28:32 +02:00
Meta Vens
eedce4f3de Land the Idunn projection, UDP route actuation, and the Odin daemon
This had been sitting uncommitted in the worktree. It is real work rather than
stray edits: RouteBinding gains stable_socket and an NginxStreamUdp driver so a
route can be actuated over UDP, control_plane and drivers grow the Idunn-owned
projection and bounded RUDP transport, and crates/odin-daemon arrives as a new
member of the workspace.

The deployment recipe comes with it, including the required_gitlinks fix the
core-stack repoint made necessary: it named vendor/cultnet-rs, which no longer
exists now that CultNet comes from CultLib, so Idunn would have refused to admit
the target. The list is empty because Odin has no submodules left; the core
stack is pinned by revision in Cargo.lock inside the frozen source tree.

Everything except odin-daemon builds here. odin-daemon reads systemd listen
file descriptors through std::os::fd and cannot compile on Windows by design, so
Linux CI is its first real witness. muninn and sleipnir pass 162 tests; the one
failure expects an Unreliable media channel that no CultNet lineage has ever
declared, and predates this.

Committed rather than left in flight. rustfmt reports 94 files across the repo,
which predates this work, so formatting is left for its own pass instead of
being buried in a 4,000-line diff.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 15:13:38 +02:00
Meta Vens
bc2fdedcb0 Refuse agent scratch
.codex-tmp had grown to 50MB of build tarballs and lockfile snapshots.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 15:08:44 +02:00
Meta Vens
27cc8156bc Consume CultCache and CultNet from CultLib instead of forking them
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>
2026-09-04 14:54:58 +02:00
Meta Vens
515a864ef0 Rebase Idunn actuator scripts off the projects root
The previous commit fixed the target catalog and the operator alarm, but the
actuators the catalog invokes carried the same E: paths. Idunn would have
resolved every target and then failed on the first restart or deploy it ran,
for exactly the reason the alarm failed.

Rebased 41 references across 27 scripts:

- .cmd bodies invoke their sibling .ps1 through %~dp0 instead of an absolute
  drive path, and restart-weksa.cmd reaches its sibling repo the same way.
- .ps1 parameter defaults and assignments derive from $PSScriptRoot: one level
  up for paths inside Odin, two for sibling repos under the projects root.

Left deliberately alone: $RemoteViliRoot and $StateRoot in restart-vili.ps1.
Both are consumed inside the EncodedCommand block that restart-vili ssh's to
raven, so they describe raven's filesystem, not starfire's. Whether raven also
moved off E: is an operator question, not something to infer from here.

Verified: all 46 scripts under scripts/ parse clean, and the derivation
resolves to F:\Projects\Odin and F:\Projects on this host.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 01:05:40 +02:00
Meta Vens
70ad92c8e3 Restore Idunn starfire paths after the E: drive loss
The projects root moved from E: to F:, but Idunn's starfire-local surfaces
still named E:\Projects. Local Idunn died on 2026-08-16 with "fatal: cannot
change to 'E:\Projects\Heimdall'" and every one of its 16 operator alarms
also failed (0xfffd0000), because notify-idunn-operator-alarm.cmd shelled to
an E: path that no longer existed. The supervisor was down for 18 days and
could not say so.

Rather than swap one hardcoded drive letter for another, derive the projects
root the way this catalog already derives its repo root:

- notify-idunn-operator-alarm.cmd invokes its sibling via %~dp0.
- notify-idunn-operator-alarm.ps1 resolves Bifrost from the projects root.
- idunn-deployment-targets.ps1 gains $projectsRoot and rebases 19 LocalPath
  values onto it. The remote /srv/build/VoidBot path is left alone.

Verified: the catalog resolves 15 of 20 targets to real directories, the
remote target is untouched, and the alarm chain now runs to exit 0 emitting
a valid gamecult.operator_dm_request.v1 under IDUNN_OPERATOR_ALARM_DRY_RUN.

Still broken, not addressed here: stonks, vili, StreamPixels and repixelizer
have no directory under F:\Projects, and the Bifrost publisher's non-dry path
throws "definition.schema.parse is not a function" against the current
CultMesh.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 23:14:48 +02:00
Meta Vens
65cf2b2f5c Reject pre-OpenFile control records 2026-09-03 06:05:23 +02:00
Meta Vens
8692553908 Fence parent-only runtime signing credentials 2026-09-03 06:02:54 +02:00
Meta Vens
412978aeb1 Rebuild Idunn deployment transaction authority 2026-09-03 05:23:06 +02:00
Meta Vens
d527d72700 Map Idunn source actuation authority 2026-09-03 00:36:41 +02:00