Commit graph

622 commits

Author SHA1 Message Date
Meta Vens
e2c0c8f626 Odin tests: a later failed attempt, and a removal on its own
Two rules had no test of their own (Soul's mutants survived):
- only_oldest_attempt_tried: every fixture's file held the oldest failed
  attempt. The new test leaves the file holding a later one.
- remove_not_dirty: the heartbeat always dirtied the store, so a removal
  that did not mark it survived. The new test removes a record with nothing
  else changed and checks the write drops it (and that removing what is not
  held writes nothing).

Soul S-8 and the attempted-list gap.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 18:38:30 +02:00
Meta Vens
f9b0926830 Odin: a target being replaced has one presence in the catalog, not a refusal
The catalog refused itself whole ("duplicate public document identities")
whenever two projected incarnations of one target both held an admitted
presence, which every provider has during a deploy window: presences are
served under (schema, target), and the incumbent and the candidate share it.

One is now served and the other skipped, logged once per interval: the
incarnation whose Odin correlation is Ready, and between two that are both
or neither Ready, the later observation. A duplicate identity between
anything but two presences still refuses the query.

Tests: the incumbent (Ready) is served over a newer Warming candidate; with
no correlations to decide, the later presence is served, from either side.

Soul S-4.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 18:38:13 +02:00
Meta Vens
a129da90fe Odin: the activation limit is set where the server is built, from its options
Soul's mutant main_limit_halved survived: main passed the server's
max_snapshot_response_bytes into RuntimeState::open by hand, and the tests
built RuntimeState and the server separately, so nothing tied the two. One
function, serve_with, now builds the server from its options and gives the
state that same limit; main and the test fixture both go through it. The
state has no limit until then, and Odin does not activate without one (the
fixture checks it refuses before serve_with).

Soul S-6.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 18:37:09 +02:00
Meta Vens
726e0f31a1 Odin test: size the small document inside the calibrated range
The small document asked for a 200-byte response, less than the response's
own fixed overhead, so sizing it underflowed. It now asks for 2,000 bytes,
inside the range where the response grows one byte per payload byte.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 17:07:25 +02:00
Meta Vens
bdbecc3308 Odin Cut 3: size a stored document with a message id CultNet admits
CultNet refuses an empty message id, so sizing a document's snapshot
response with one failed for every record, and the failure was read as
"keep": nothing was ever dropped. The response is now sized with a one-byte
message id, the shortest CultNet admits, and a record that cannot be sized
is logged and kept rather than silently kept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 16:58:28 +02:00
Meta Vens
723e74d5d1 Odin test: the unreadable-projection case is a projection that does not decode
The second fault in the test (removing the projection's lock file) did not
make the read fail, so it asserted a fallback that never ran. The test keeps
the fault that does: a projection file that does not decode.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 16:58:28 +02:00
Meta Vens
6786faa430 Odin Cut 3: a stored document the server could never serve is dropped at activation
Odin's store carried two ~1.456 MB Eve records that no snapshot response
could ever return (the server sends at most max_snapshot_response_bytes,
1 MiB), and every write rewrote them. Under Q3 B every put writes the
store, so they cost a ~3 MB write per put.

At activation, each stored peer document is sized as the server would
answer a snapshot of it alone: the same encoder, the shortest message id.
One larger than the server's limit is dropped from the working set and
logged by type, key and size; the first write removes it from the file.
The limit is the server's own option, handed to RuntimeState where main
builds the server, so the two cannot disagree. Presences and records the
catalog does not serve are not checked.

Q1 A (refuse the unservable), Q2 (the two records are dead residue). The
CultLib half, refusing such a put at the server, is its own cut.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 16:50:09 +02:00
Meta Vens
dcd36fccb9 Odin: say what a refused put promises
An acknowledged put is always on disk. A put refused because its write
failed may still become durable and be served: it stays in the working set,
and the next write lands it, or the failed write had already replaced the
file. Puts are latest-value per type and key, so that is sound; the
promise is written down at accept_raw_document.

Soul finding 3.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 16:48:12 +02:00
Meta Vens
749c6b9bbe Odin test: with nothing to write, the next change is written on the next pass
Pins the rule that only an attempted write starts the flush interval: a due
write with nothing to write leaves the next change to land on the first pass
after it. Kills the mutant that starts the interval on every due pass.

Soul finding 5.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 16:48:12 +02:00
Meta Vens
03f01ddba0 Odin: an unreadable projection file skips presences, not the catalog
Reading Idunn's projection once per catalog query (620efb8) made a
projection file that cannot be read refuse the whole catalog, where before
it failed each presence lookup and left the peer documents served. The
query now answers as if Idunn projected nothing, logged once per interval:
peer documents are served and presences skipped. Odin's own projection
check (require_own_projection) is unchanged and still retries or ends Odin
as before.

The new test restores file-level coverage: a projection that does not
decode, and one whose lock cannot be opened.

Soul finding 4.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 16:47:33 +02:00
Meta Vens
08ae183444 Odin: every failed write since the last success is Odin's own
A write that fails does not say whether it replaced the file, so the file
may hold what any failed write since the last success tried to write. The
store kept only the last attempt: a failure after the replace followed by a
failure before it left the file holding an attempt Odin no longer
recognised, and the next write called Odin's own file foreign and ended
it. Every attempt since the last successful write is now an accepted
baseline, cleared when a write lands.

Soul finding 1, with Soul's probe as the test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 16:47:00 +02:00
Meta Vens
f4a1f8900a Odin: drop doc lines that restate the code they sit on
One was also wrong: `records` is in key order, not the file's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:32:41 +02:00
Meta Vens
c94b5c4f85 Odin tests: the real binary; an acknowledged put survives SIGKILL, SIGTERM stops it cleanly
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>
2026-09-30 15:30:32 +02:00
Meta Vens
0a09df9b7c Odin: a peer's put is written before it is acknowledged (Q3 B)
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>
2026-09-30 15:30:28 +02:00
Meta Vens
3afa3fb778 Odin: every way out of the serving loop makes the last write
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>
2026-09-30 15:27:53 +02:00
Meta Vens
a4aa5fcf97 Odin: a failed write is retried as it stands, and its own replaced file is not a foreign write
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>
2026-09-30 15:26:25 +02:00
Meta Vens
620efb85d8 Odin: a catalog query reads Idunn's projection once
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>
2026-09-30 15:24:32 +02:00
Meta Vens
2ad60183a1 Odin: the self presence counts from its own launch; the stored-sequence continuation is gone
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>
2026-09-30 15:14:25 +02:00
soul-probe
cda20870fd Odin tests: the write starts the next interval; unprojected correlations are withdrawn
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>
2026-09-30 14:07:44 +02:00
soul-probe
35ea0d45fc Odin test: with nothing to write, the write step reads nothing, not even the lease
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>
2026-09-30 13:39:47 +02:00
soul-probe
8189623411 Odin: the correlation record type is imported only where it is used
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>
2026-09-30 13:32:11 +02:00
soul-probe
b5f13308e9 Odin Cut 2: one write of the store file per interval, only when it changed
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>
2026-09-30 13:00:53 +02:00
soul-probe
409d92f279 Odin Cut 1: the store file stops being working memory
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>
2026-09-30 12:58:52 +02:00
soul-probe
4b525b463c Map Odin's write pattern: memory working set, one write per interval
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 12:39:32 +02:00
soul-probe
44951a1f07 Merge Odin fixes 2-4: one bad record never stops Odin, peers write only what they own
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>
2026-09-30 06:17:18 +02:00
soul-probe
dc46be205a Odin: the projection-unreadable mark is found through anyhow context layers
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 05:23:54 +02:00
soul-probe
5dd5b4a863 Odin: peer namespaces are the ones a source publishes, and only a failed file read is retried
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>
2026-09-30 05:19:46 +02:00
soul-probe
c0735f17aa Odin: providers write only their own namespaces, own-projection I/O errors are retried, the lingering test no longer races the idle timeout
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>
2026-09-30 03:57:40 +02:00
soul-probe
64e64df3bd Odin test: a refresh before the anchor's binding still records the time disagreement
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>
2026-09-30 03:57:39 +02:00
soul-probe
2f0d1d4bcb Odin test: the lingering-publisher test admits until 64 are held, then refuses one
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>
2026-09-30 02:14:27 +02:00
soul-probe
c81fc38ecc Odin tests: the catalog test expects the watermark record, the lingering test counts from a first pass
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 02:07:22 +02:00
soul-probe
8c33a18729 Odin: repair a mis-spliced edit in the sink handle (the previous commit did not compile)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 02:04:00 +02:00
soul-probe
2e4ccc40ad Odin: a bad record is skipped, own-authority failures are fatal, correlations leave the catalog
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>
2026-09-30 02:01:48 +02:00
soul-probe
59667f4648 Odin tests: Soul's probes P1-P3 for catalog collision, one bad record, and undecodable own anchor
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>
2026-09-30 01:54:48 +02:00
soul-probe
c34d167f74 Merge Odin session-table fix: Odin survives a full session table
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>
2026-09-30 01:21:12 +02:00
soul-probe
68b666c2e5 Merge X1: delete Odin's dead lifecycle residue (-7,037 lines)
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>
2026-09-30 00:48:07 +02:00
soul-probe
cc7e7f9015 Odin: drop the second projection-validity marker; the source that assembles a projection owns it
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 00:33:12 +02:00
soul-probe
8d241efb72 Odin test: a catalog read does not refresh the projection
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 00:27:14 +02:00
soul-probe
106dfb8327 Docs: Idunn owns deployment and daemon survival; Odin does discovery only
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>
2026-09-30 00:25:20 +02:00
soul-probe
779c24ba80 package.json: drop scripts that named the deleted coordinator and a missing crate
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>
2026-09-30 00:23:07 +02:00
soul-probe
99f9a22fa4 odin-core: keep the cultnet-rs dependency (cultmesh_documents! expands to it)
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>
2026-09-30 00:22:45 +02:00
soul-probe
092ff997b4 Delete deploy, restart, health and start/stop scripts for other services
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>
2026-09-30 00:22:19 +02:00
soul-probe
06282c47d8 Delete the retired Node coordinator, its tests and npm entry points
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>
2026-09-30 00:21:30 +02:00
soul-probe
6aba9a8801 Delete odin-core's Idunn lifecycle planner and idunn.* schemas
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>
2026-09-30 00:20:38 +02:00
soul-probe
b6cbf53a54 Odin: an invalid projection of Odin's own incarnation is a refused authority; sibling test uses another target
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 00:17:16 +02:00
soul-probe
24f163cf14 Odin: one bad incarnation no longer stops the catalog; unreadable lease is a lost lease; refused self-presence authority ends Odin
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>
2026-09-30 00:12:07 +02:00
Meta Vens
eeb68124ca Cargo.lock is the CultLib pin; delete deploy/cultlib.commit and dead muninn scripts
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>
2026-09-29 23:28:20 +02:00
Meta Vens
a9ed585263 Odin's liveness is Odin's: self-presence goes straight into its own store
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>
2026-09-29 23:28:19 +02:00
Meta Vens
6eb0bd82e5 Presence ordering: pin the two ways a stored presence is not the same publisher
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>
2026-09-29 23:28:18 +02:00
Meta Vens
5c378609bc Presence ordering: same publisher is an activation identity, not an auth outcome
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>
2026-09-29 22:12:18 +02:00