From Soul's SIGTERM probe (scratch 5c379dd), made a test. It runs the built
odin-daemon under an Idunn-shaped world with the warming probe and lease
grant made over RUDP. With puts written before they are acknowledged, the
SIGKILL round is the rule's test; the SIGTERM case pins `main`'s signal
wiring, which no in-process test reaches.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Operator ruling Q3 B: `accept_raw_document` returns Ok, and so the server
acknowledges the put, only once the store holding it is on disk. The write
is the one `flush`, under the same lease check, so it also carries every
other unwritten change. A write that is not made refuses the put. Odin's own
bookkeeping stays on the one-second interval.
The interval tests now change Odin's own bookkeeping (its heartbeat) instead
of putting, since a put is no longer an interval change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The loop is `serve`, and it ends in one place: a stop request, a condition
`survive` returns, or a dead socket all reach `stop`, whose write is made
only while the lease is current and the file is still Odin's. Tested for
each exit kind: a stop request, a refused own authority and a dead socket
write what changed; a lost lease and a foreign write write nothing.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The records stay dirty until a write lands, so the next write makes the
failed change without another mutation (tested with no heartbeat to
re-dirty them). A write that fails after it replaced the file (the
directory sync) leaves Odin's own records there; they are remembered as
the write's attempt and accepted as a second baseline until a write lands.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The file-backed projection source no longer answers lookups itself, so no
caller can read the file once per lookup: every lookup is made against one
snapshot. The catalog query takes one per query, as the refresh pass does;
startup, activation and the own-projection check take one each.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every launch is issued a fresh activation (Idunn control_plane.rs, the
continuity demotion), presences are ordered only within one activation
(select_presence), and Idunn compares only the warming presence's digest.
Continuing from a stored sequence protected nothing.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cargo-mutants survivors, both real gaps:
- serving_pass with the timer condition inverted wrote every change on the
next pass after a write; the interval test set the timer by hand on every
pass and never saw the loop set it. A change right after a write now waits.
- correlated_incarnations returning nothing left a correlation whose
incarnation Idunn stopped projecting in place forever; the lib test
refreshed that incarnation by name. The refresh pass now has to find it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The flush step is due on every pass until something changes. Hand mutant M5b
(the daemon skipping its own dirty check) left every write rule intact but read
the lease file on every 2 ms pass; no test saw it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Cut 1 left it imported at the top of main.rs, where only the tests use it;
the release build warned.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every admission, put and refresh that changed something rewrote and fsynced
the whole store; a heartbeat did it twice (watermark, then correlation).
Now the serving loop writes the file at most once per FLUSH_INTERVAL (1 s,
operator default Q3), and only when the working set changed.
Deleted first: the per-mutation writes Cut 1 left in accept_raw_document,
publish_self_presence and refresh_all_correlations. The watermark's own write
went with the file store in Cut 1; its mark now travels in the same atomic
write as the correlations that use it. "Published" means written.
Added: FLUSH_INTERVAL, the flush step in serving_pass, and a final write on
SIGTERM/SIGINT. The dirty flag lives in MemoryOdinTopologyStore. RuntimeState::
flush is the only write of the file: it checks the lease immediately before
writing, and CultLib's snapshot compare-exchange against what Odin last loaded
or wrote refuses a file another process wrote (ForeignStoreWrite). survive
treats ForeignStoreWrite like WriteLeaseLost: both end Odin. An attempt, failed
or not, starts the interval; an interval with nothing to write does not.
Tests: 50 changes inside one interval are one write, not made at 0.9 s and
made at 1.1 s after the last; nothing changed is no write; a lease lost before
the write writes nothing and ends Odin; a foreign write ends Odin and is not
written over; stopping writes only under the lease; a crash and reload never
republishes a written sequence, withdrawal included; a flush writes only
changes and never over a foreign file. The failed-publication test becomes a
failed-write test: the store is no longer read by publication.
Map: docs/write-pattern-cut.md, Cut 2.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Odin read and decoded its whole 3.24 MB store file about 30 times a second:
every refresh pass, every admission, every catalog query and every CAS retry
read it again. The working set now lives in memory under the write lease.
Deleted: CultCacheOdinTopologyStore (every file read and CAS against the
store, and its four CAS_ATTEMPTS retry loops, including the one in
reconcile), persist_generic_document's file CAS loop, the store half of
incarnation_keys, stored_snapshot's file read, and
prior_self_publisher_sequence's file read in RuntimeState::open. With one
writer under the lease an in-memory exchange cannot lose, so nothing retries.
Added: MemoryOdinTopologyStore, keyed (type, key), implementing the
OdinTopologyStore trait (still the mock point). It is loaded once, with the
locked pull_all, in try_activate after the lease is verified; legacy
retirement and the self publisher sequence are operations on the loaded set.
The trait's compare_exchange collapses into commit. Refresh reads Idunn's
projection once per pass (IdunnProjectionSnapshot) instead of once per
incarnation; admission reads it once per admission.
Not in this cut: the write policy. Each mutating operation still writes the
whole file immediately, now from memory (flush after an admission, a put or a
refresh pass that changed something).
Tests: the two tests that corrupted the store while Odin served now put the
bad record in the file at activation. New: rewriting the file behind Odin
changes nothing it serves; refresh passes and catalog reads succeed with the
file replaced by bytes that do not decode; a loaded store never reads its
file again.
Map: docs/write-pattern-cut.md, Cut 1.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A record that does not decode is skipped per record (catalog, startup).
Every failure of Odin's own authority ends it (temporary, until Idunn
cut A3); an early clock and a real file-read error are retried. The
catalog no longer serves correlations or the watermark. Peer puts are an
evidence-based allow-list, closing a peer-reachable presence freeze.
Three Soul passes; deploy gate GO against the live type inventory.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The allow-list drops nine guessed namespaces and gains gamecult.model,
gamecult.aetheria and the two gamecult.media_stream_* types (they are not
under a dotted gamecult.media segment, so they are whole types), each with its
publisher named. The own-projection failure rule now retries only what the
file read itself marked, so a truncated Expected record ends Odin.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Peer writes (Soul P-B, confirmed): persist_generic_document blocked six
authority types, so a peer could put an odin.topology_publisher_watermark keyed
odin holding the largest sequence; every heartbeat then failed to reserve its
sequence, and the record survived restarts. Generic puts are now an allow-list
of provider namespaces (PEER_DOCUMENT_NAMESPACES). Odin's own records and
Idunn's projected authority live in namespaces no provider can name, so they
are refused by construction. The same predicate decides what the catalog
serves back, replacing the correlation special case: the watermark, and any
future Odin-internal type, is no longer public.
Own projection: a plain I/O error reading Odin's own projection (permission
denied, not found, interrupted) or a missing file is survived and retried; an
error the reader decoded or validated and did not accept stays fatal
(PresenceAuthorityRefused).
Log flood: survive() now logs through the RuntimeState LogGate, once per
subject per minute, instead of printing every pass.
Flake: self_presence_lands_while_lingering_publishers_hold_every_session failed
on Yggdrasil because 64 peers connect over about 9 s and the fsync-bound passes
that follow took 4 to 20 s under disk pressure, so the earliest sessions passed
the server's default 30 s idle timeout and expired before the final
session_count assertion (44 of 64, measured). No peer ever failed to connect.
The test's world now serves with a one hour idle timeout, since the property is
a full table at heartbeat time, and it logs any peer that is not admitted.
Tests: provider puts of every Odin-owned kind are refused and presence keeps
advancing; the catalog serves only presence and provider documents; namespaces
match whole segments; I/O versus decode of the own projection, and a missing
projection file through the serving loop; repeated failures are counted, not
printed again.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Removing the incoming-claim condition from the clock exemption in reconcile
survived: only the admission side was pinned. A refresh has no claim to refuse,
so with the clock before the anchor's binding it must still write the
correlation carrying the provider-trust-anchor-time disagreement.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A peer that times out on a loaded host no longer changes the count, and the six refused peers no longer wait out their deadline while the first sessions idle out.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A1 the catalog and startup skip a record that does not decode (logged); startup
reads only presences keyed under Odin's own target.
A2 every failure to establish Odin's own projected authority is
PresenceAuthorityRefused (temporary rule in survive).
A3 a clock that precedes the provider anchor's binding is retried, not refused.
C the catalog no longer serves correlations; the store still writes them.
B test peers connect while the server is polled without timers; 2 s deadline.
6 repeating refresh and catalog-skip lines are logged once per minute per subject.
Soul's probes P1-P3 become assertion tests.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
P1 two incarnations of one target, P2 one undecodable presence, P3 an
undecodable own provider anchor. All three fail at c34d167; the fixes follow.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Odin admits its own presence straight into its store instead of over
RUDP, survives transient failures, and ends only on write-lease loss or
a refused own authority (temporary, until Idunn cut A3). Merged to stop
a live crash loop on Yggdrasil (2026-09-30 00:55 CEST): the deployed
Odin timed out publishing to itself through a full session table and
Idunn stopped restarting it after three failures. Open Soul findings
(A1 startup decode, A2 rule gaps, C catalog collision, flaky harness
test) are pre-existing in the deployed build and are fixed next.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
odin-core idunn.rs and idunn.*/bifrost schemas, the retired JS
coordinator, dead deploy/restart/health scripts, lifecycle claims in
docs. Soul: nothing deleted is reached by the deployed odin-daemon, the
Hermodr container or any live store. Idunn map cut X1.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
README and architecture.md called Idunn "Odin's keepalive organ", listed
crates/idunn-daemon (not in this repo) and the deleted odin-core idunn.rs
as its body, described the Node coordinator's process lifecycle, refresh
loop and Docker/ADB probes as Odin's runtime body, and pointed at the
deleted start-odin.ps1. Rewritten to the live truth: Idunn builds, activates,
restarts and brakes every target, Odin included; odin-daemon reads Idunn's
projection, admits presences, publishes correlation records and answers RUDP
requests; process lifecycle is not Odin's.
The dated ledger (transport-shortcut-inventory.md), notes/ and the Hermodr
extraction plan are history and are left as written.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
start (node src/odin-coordinator.cjs), check and test (JS gates over the
deleted src/ and test/), muninn:build and muninn:start (muninn-daemon is
not in this workspace). gjallar:persona and rust:test stay. This belonged
in the Node coordinator deletion commit and was left unstaged there.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The previous commit dropped cultnet-rs from odin-core's manifest as
unused; cultmesh_documents! emits cultnet_rs paths, so the crate needs it
in scope. Restore the dependency and its lockfile entry.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Odin does not deploy, restart or health-probe anything: Idunn owns
deployment and daemon survival, and every service in this set is either
Idunn-deployed from its own repo recipe or archived. Removed from
scripts/: deploy-yggdrasil-{heimdall,repixelizer,source-app,streampixels}
(.ps1/.cmd, armed behind IDUNN_ACTUATOR=1) and the linux heimdall deploy;
restart-* (odin, stonks, vili, weksa, nightwing eve dashboard and browser
reference; several already exit 2 or throw as archived); health-* (odin,
voidbot, stonks, vili, weksa, mimir, nightwing, raven capture mux,
yggdrasil heimdall/repixelizer/streampixels/source-app; archived stubs and
orphans); start-odin.ps1 and stop-odin.ps1 (launched the deleted Node
coordinator).
Kept: write-gjallar-persona-cc.mjs (persona tooling, package.json
gjallar scripts) and the nightwing usb-move helpers, wasapi loopback,
firewall and wireguard scripts, none of which is a lifecycle actuator.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
src/odin-coordinator.cjs and src/odin/* (probes.cjs docker/adb host
probes, state.cjs, provider-ingress.cjs with its 120 s TTL) were retired
when the Rust odin-daemon became the deployed body (state/map.yaml: the
Node coordinator that held 17871 is retired). The recipe builds only
odin-daemon; nothing in the Rust crate graph loads a .cjs file. The live
Yggdrasil hermodr container runs from the immutable release
/srv/odin/current, not from this tree.
Removes src/, test/ (4 test files for that JS), scripts/check-syntax.cjs
(the JS syntax gate), and the package.json scripts start, check, test,
muninn:build, muninn:start (the last two named a crate that is not in the
workspace). gjallar:persona and rust:test stay.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
crates/odin-core/src/idunn.rs (plan_keepalive: observe, alarm, deploy or
restart from IdunnDaemonHealthRecord) had no caller: odin-daemon does not
depend on odin-core, and the deployed recipe builds only odin-daemon.
Idunn owns deployment and daemon survival; nothing in Idunn reads these
records (its own live in cultnet-rs).
Also removes from documents.rs the idunn.* and bifrost release-authority
schema constants and records, their OdinDocuments registrations, the
cultnet re-exports of Idunn health types, and the tests that only
exercised them. The muninn.* copies stay. cultnet-rs and rmp-serde drop
out of odin-core's manifest and lockfile entry (no remaining user).
Muninn pins odin-core at 3e96c6c and is unaffected until its next bump,
which must drop IdunnDaemonHealthRecord.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Refresh reads only incarnation keys and refreshes each incarnation on its own;
a record that will not decode is that incarnation's logged failure. The
CultCache projection source decodes only the requested incarnation's records.
Catalog reads serve the stored projection instead of refreshing everything.
An unreadable process-write lease maps to WriteLeaseLost. Temporary named
rule: a self-presence Odin's authority refuses (PresenceAuthorityRefused) is
fatal in survive until Idunn proves Odin by its own route challenge.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
deploy/cultlib.commit said 542ddd3 while the built pin is Cargo.lock's 3bf1c0c.
Nothing reads the file (the Idunn recipe builds with `cargo --locked`), so it is
deleted and the README names the lock. package.json's muninn:build/muninn:start
named a muninn-daemon crate that no longer exists in this workspace.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Odin published its own heartbeat over RUDP to its own document server, which
holds 64 sessions for 30 s after each peer's last packet. Ghostlight,
CodexConnector and the presence publishers never send Disconnect, so a burst of
publishes filled the table, Odin's Connect was dropped, the heartbeat failed and
`main` returned. Reproduced with 70 lingering sessions: 4 of 5 self-publishes
failed.
Owner change: Odin is the store, so its heartbeat is signed and admitted through
the same admission path providers use (`admit_presence_document`), with no
socket, no thread and no session slot. The self-publish timeout and the RUDP
publisher are deleted.
Serving-loop failure policy, in one place (`survive`, `settle_poll`): only
`WriteLeaseLost` (the process-write lease withdrawn or replaced, i.e. serving on
would be a second writer) ends Odin. A failed heartbeat, a failed projection
refresh and a failed poll are logged and retried; a socket failing every poll for
30 s is a dead socket and ends Odin so Idunn restarts it. An application
rejection or stray packet never ends Odin. The startup publish/refresh pair is
folded into the loop (a timer that has not fired is due). One serving pass is
`serving_pass`, the unit the tests drive against Odin's real activated state and
production RUDP server.
Tests (Linux, real activation, lease, projection and identities): a full 64-wide
session table of lingering publishers does not stop 5 consecutive heartbeats
landing in the store; a corrupted store fails publication and refresh without
ending Odin and the next pass lands; replacing or removing the lease ends Odin;
an application rejection ends only the offending session and a stray datagram is
dropped while Odin keeps admitting peers; a failing socket is retried until the
failure limit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A stored presence that names the current activation but no longer authenticates
at its own received time is an error (fail-closed): its count is unknowable, so
the claim must not slip past it. A stored presence that cannot be decoded names
no activation, so it is another publisher's and the claim supersedes it
(fail-open). The choice is now written on the branch and each half has a test
that fails when it flips.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The stored presence constrains an incoming claim's sequence only when it names
the current activation (activation_witness_sha256 equals the authority's
activation_sha256). Previously any re-authentication failure meant "different
publisher", so a same-activation presence that stopped authenticating silently
lost its sequence. Now such a failure is an error.
Adds a test pinning that the stored presence is re-authenticated at its own
received-at time: sequence 5 admitted at T, sequence 4 offered at T+40s from
the same activation must be refused as reordered.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Odin crashed on a stray RUDP packet; CultLib 3bf1c0ce hardens stray-packet
handling in all runtimes. Both manifests (odin-core, odin-daemon; Odin has no
workspace.dependencies) move from a8aeddac to 3bf1c0ce. No API breaks: the
workspace compiles and tests unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sleipnir moved into Muninn with its history (Muninn f4f1f85, operator ruling
Q9); the StreamPixels ship that gated this deletion completed 2026-09-29.
Removes the crate, its workspace member, its Cargo.lock entries (including
vigem-client), the sleipnir:build/sleipnir:start package scripts, and the
deploy/restart/start launcher scripts that shipped Odin's sleipnir.exe.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From 2026-09-29 19:20 on yggdrasil every new StreamPixels incarnation's
runtime presence was refused with "runtime presence publisher sequence
was reordered". Odin compared the incoming claim's sequence with the
target's stored presence whoever had published it; the provider counts
from 1 in each process, so once a target had published for a while no
restarted activation could ever be admitted.
A publisher is the activation whose key signs its claims. The stored
presence now constrains the incoming claim only when it still
authenticates under the same current authority. One left by an earlier
activation, or otherwise stale, is superseded whatever its sequence.
Within one activation a lower sequence is still a reorder and an equal
one with different bytes is still a reuse, and a claim that does not
authenticate under the current activation is still refused.
The unconditional decode of the stored presence at the top of
select_presence existed only to feed that comparison and is gone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The StreamPixels deploy archive now carries CultLib's root build files,
contracts/cultnet, cultmesh-ts and cultmesh-browser, so the host can
build CultLib's TS dist/ itself. It previously relied on a June tarball
that happened to include dist/. This was verified on a clean Debian 13
extract and gated by Soul with StreamPixels Cuts E and F.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Odin has no incarnation to observe for a provider Idunn admitted from an
operator binding, so it can never sign evidence for one, and every such
dependency stayed ready: false. A dependent with one (Ghostlight Dungeon's
Heimdall command boundary) could never become Ready, and Idunn waited in
AwaitingReady indefinitely. Idunn and CultLib already accept the kind.
The dependency's own evidence stays ready: false; only its dependent's
Ready no longer waits on it, as with optional. The dependent checks that
provider itself at use. The rule is now one named function with a test
covering managed, optional and external kinds; dropping the external kind
fails it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cultnet-ts's build (generate-swarm-contracts.mjs) mirrors CultLib's
canonical contracts/cultnet into its own package tree at build time, so
`npm run build:ts` fails on cultnet-ts on a host that only has the
packages the previous archive list carried. Verified by a dry run of the
Cut E archive on Yggdrasil: build:ts failed with ENOENT on
CultLib/contracts/cultnet until this path was added.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The archive built only packages/cultnet-ts and packages/cultcache-ts, and
dist/ is gitignored in both, so the current actuator ships CultLib
packages with no dist/ and the service fails at import (P1). Add the
CultLib root package.json/package-lock.json and
scripts/run-typescript-workspaces.mjs so the deploy host can build
CultLib's TS dist/ itself, and carry packages/cultmesh-ts and
packages/cultmesh-browser alongside the two packages already archived.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Deletes crates/odin-core/examples/dump_move_tracker_health.rs and
dump_provider_endpoints.rs (no in-swarm caller: full-corpus rg for both
executable names found only their own usage strings, plus the two sibling
Odin worktrees that carry byte-identical copies). Removes package.json's
gjallar:build/gjallar:start scripts, which run `cargo build/run -p
gjallar-daemon` — no such Cargo package exists in this workspace (members:
sleipnir-daemon, odin-core, odin-daemon) or in the sibling Gjallar repo,
which is a C#/.NET executable (src/Gjallar/Gjallar.csproj), not a Rust
package.
Kill attempts that failed to find a consumer:
- rg -n "dump_move_tracker_health|dump_provider_endpoints" across all of
F:/Projects excluding target/node_modules/.git: only the examples' own
usage strings, the census docs, and the two other Odin worktrees.
- rg -n "gjallar:build|gjallar:start" across all of F:/Projects: only
Odin's own package.json (this repo + 2 worktrees) and census docs.
- Collision check: git diff --stat origin/main...codex/cultmath-cutover and
...codex/ygg-ghostlight-continuity over these paths: empty, no in-flight
work touches them.
- state/map.yaml and notes/*.md: no reference to either example or the
gjallar npm scripts.
Not touched: the root Cargo.toml exclusions for vendor/cultcache-rs and
vendor/cultnet-rs named in REC-014 are already gone — removed by commit
6575de4 ("Drop the workspace excludes for vendor trees that no longer
exist"), prior to this pass. That part of REC-014 is moot.
REC-014
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
On 2026-09-11 a provider's continuity restart (raven-muninn, new
activation) left its previous presence in Odin's store. Odin treated a
stored presence that failed authentication under an authority its own
correlation still named as tampering and exited; every continuity
restart read the same record at startup and died the same way, three
times, and Idunn gave up on odin. The rendezvous was dark for the whole
Verse over one provider's evidence. It is now recorded as a
stored-presence-not-current disagreement and logged.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>