Commit graph

232 commits

Author SHA1 Message Date
Meta Vens
4aa8d3b3b7 Record Epiphany's measured build memory under Q-V6
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 17:34:00 +02:00
Meta Vens
d2a98edbaa Record Soul on the Cut 1 fix batch: the raw freeze is slow and can write outside its root; rule the fix
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 17:26:23 +02:00
Meta Vens
9fb6c1a615 Record Q-V6: 2 jobs, 4 CPU and 12 GiB each, from measured build memory
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 17:12:02 +02:00
Meta Vens
686f3d0839 Record the Cut 1 fix batch and the eol=lf ruling
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 16:51:06 +02:00
Meta Vens
95016e817d Land F2: freeze writes blobs raw, per Self's ruling on the Eve survey finding
Self reviewed the F2 survey finding (Eve's gitlink under Ghostlight carries
`packages/*/dist/** text eol=lf`) and ruled it is not a real dependency on an
archive transform: Git stores text blobs as LF, `eol=lf` checks them out as
LF, so no conversion happens. Self verified this directly at Ghostlight's
pinned Eve gitlink revision `672c0c1e9fc828b63752345385727b06c50491d0` — all
26 files under `packages/*/dist/` hash identically between `git archive` and
`git hash-object --no-filters`, and none contains a CR. Stopping for the
survey was correct; the ruling is to proceed.

Survey table (repo, attributes found, verdict), recorded here as ruled:

| Repo (deploy target) | Binding source | .gitattributes findings | Verdict |
|---|---|---|---|
| Ghostlight | idunn/yggdrasil/bindings/ghostlight.toml.in | none | clean |
| Ghostlight's gitlink vendor/eve -> Eve | same binding | packages/*/dist/** text eol=lf | transform present, but a no-op on stored LF content (Self's evidence above) |
| Odin | idunn/yggdrasil/bindings/odin.toml.in | none | clean |
| CodexConnector | idunn/yggdrasil/bindings/codex-connector.toml.in | none | clean |
| Muninn (raven-muninn) | idunn/yggdrasil/bindings/raven-muninn.toml.in | none | clean |
| Heimdall | deployment/idunn-bindings/heimdall.toml.template (a second, previously unlisted binding directory, installed live 2026-09-06) | none | clean |
| Heimdall's gitlink -> CultLib | same binding | * text=auto only (F12) | clean |

`freeze_exact` now writes every tree entry raw from the object store:
`git ls-tree -r -z` for the manifest, `git cat-file --batch` for content, with
no `git archive` anywhere in the path and so no attribute-driven transform of
any kind. Modes and symlinks are preserved exactly as the tree records them.
Gitlinks are frozen the same raw way, through the same primitive, at their
own path. LFS content is never fetched; `freeze_exact` now also returns
whether any materialized file is an LFS pointer (recorded in the frozen
result's own return value — not yet plumbed into `FrozenSourceReceipt`, which
has no field for it; that is follow-up scope, not this fix).

Deleted: `git_archive_into`, `materialize_gitlink_archive`, and the
now-unused `tar_program` field — the archive-and-extract path they built has
no remaining caller. `GitSourceDriver::new` no longer takes a tar program.

Tests:
- `freeze_exact_is_byte_exact_across_every_attribute_transform`: a fixture
  with `eol=crlf`, `export-ignore`, `export-subst`, `ident`, a symlink, the
  executable bit, and a Gitlink. Every frozen file's bytes are asserted equal
  to `git cat-file blob` at the selected revision.
- `freeze_exact_matches_archive_when_untransformed_and_diverges_when_transformed`:
  the deploy-equivalence test Self's ruling asked for. An `eol=lf` file over
  already-LF content matches today's independently-reconstructed `git archive`
  tree exactly; a second file with `eol=crlf` over the same LF content
  diverges from it, so the equivalence check has teeth rather than trivially
  passing for every fixture.
- `exact_git_blobs_become_root_owned_immutable_source_without_git_metadata`
  (renamed from the git-archive-era test) now exercises `materialize_tree_raw`.
- `tools/verify-mutations/cut1.psd1`: `cut1-fix-freeze-skips-gitlinks`
  retargeted at the new call, and a new `cut1-fix-f2-revert-to-git-archive`
  entry that reverts `materialize_tree_raw` to a `git archive | tar` pipeline;
  it must die against the byte-exact test.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 16:48:17 +02:00
Meta Vens
7a5d32f02d Land the Cut 1 fix batch: pin every argv branch, one fetch, dead StepPort gone
Soul's pass on Cuts 0-1 found the argv-lowering test pinned only the
default runner path (13 of 14 mutants survived), freeze fetching the
same revision twice, an untested Gitlink loop, dead StepPort/StepOutcome
code, and a stale guide.md line. This lands Self's rulings for all of
it except F2 (the byte-exact freeze): the F2 survey found that Eve, a
Gitlink of the Ghostlight deploy target, depends on an archive
transform (`.gitattributes` eol=lf under packages/*/dist/**), so per
ruling 1 the byte-exact rewrite is withheld and reported instead.

- F1: `docker_run_args`/`ContainerSpec::for_step` are now pinned across
  every network branch (none/bridge/named), the full CPU quota range
  (100/150/250/333/800), cache root present/absent, and secret mounts,
  with dedicated fixtures for the cache-root and secret-file validation
  calls so a mutant that skips either is caught.
- F3: `freeze` fetches the exact revision once, through `freeze_exact`,
  instead of fetching before it and again inside it.
- F5: `freeze_exact_materializes_gitlinks` pins that the Gitlink loop
  actually runs.
- F7: deletes the dead `StepPort`/`StepOutcome` types; `run_step` never
  had a caller, and `docker()` is the one Docker spawn path.
- Stamp override: `ContainerSpec::for_step` now refuses a step
  environment name that collides with the Idunn source stamp, so the
  invariant holds at the one place every runner's environment is built,
  not only at deploy's separate `admit` check.
- docs/guide.md:11 no longer claims the retired generation still runs
  on Yggdrasil.

F8 (the `--cap-drop` negative check) is a reporting fix, not a code
change: `rg -n '"--cap-drop"' src/` now returns 4 hits (1 real site
plus 3 test fixtures, one of which this batch adds), never the "1 hit"
the map's verification text describes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 16:15:38 +02:00
Meta Vens
09ccfcb3e0 Record Soul on verify Cuts 0-1 and rule the fix: a byte-exact freeze, every argv branch pinned
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 16:03:42 +02:00
Meta Vens
31f72a5449 Record Idunn verify Cuts 0 and 1
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 15:48:19 +02:00
Meta Vens
95e15efca3 Cut 1: one step primitive and one freeze primitive (behaviour-preserving)
Deletes the driver-private Docker argv assembly in run_in_workspace and
base_run_args and the OperatorBinding/CompiledDeploymentPlan coupling inside
GitSourceDriver::freeze's fetch-and-archive core. Both become one shared
primitive apiece, the shape Cut 4's verify worker will reuse instead of
growing a second copy:

- ContainerSpec + docker_run_args(spec, workspace, working_directory, argv):
  a plain, pure lowering to the exact `docker run` argv. ContainerSpec::for_step
  is the one constructor (a DockerRunnerBinding, a step's required
  environment, and the source stamp); it does the cache-root and secret
  validation against the filesystem before docker_run_args ever runs.
  run_in_workspace and materialize_external_input both build a ContainerSpec
  and call docker_run_args now; base_run_args is gone (rg '"--cap-drop"'
  src/ has exactly one hit).
- ExactSource {origin, checkout, gitlinks, recipe_path} plus
  GitSourceDriver::freeze_exact(&ExactSource, revision, transaction_id, root),
  which fetches, archives and hardens an exact revision with no
  OperatorBinding or compiled plan. freeze() now derives an ExactSource from
  the binding, keeps its own verify_exact_source pre-check against the
  durable resolution, and delegates the archive to freeze_exact.
- A StepPort trait (DockerRunnerDriver::run_step) that runs a lowered step
  and returns exit status plus a bounded stderr tail instead of bailing.
  Not yet reached from materialize (Cut 4 wires it into the verify worker);
  deploy keeps DockerRunnerDriver::docker's existing bail-on-failure error
  text unchanged.

materialize_external_input's curl step now goes through the same lowering,
which adds a harmless --workdir /workspace and one inert IDUNN_EXTERNAL_INPUT_ID
env var to its argv; curl's behavior (an absolute --output path) is
unaffected. materialize_gitlink_archive's cache-directory name changed from
the operator binding's target label to a hash of the checkout path, since
ExactSource carries no target field; this is bookkeeping only; a UUID
already made every gitlink checkout unique.

Adds three unit tests: container_spec_lowers_to_exact_docker_argv pins the
full docker run argv; required_environment_is_the_only_environment pins F6
(a binding environment entry the step does not require never leaks in);
freeze_exact_is_byte_exact_and_recipe_checked and its negative twin
freeze_exact_rejects_a_recipe_the_archive_transforms (an export-subst
recipe, which git archive substitutes and git cat-file does not) exercise
freeze_exact directly, with no OperatorBinding in sight.

tools/verify-mutations/cut1.psd1 covers all five rules with a revert or
loosening mutant apiece, run through the Eureka harness in the stopgap
container. Verified on Yggdrasil at this commit: cargo test --lib is 130
passed, 0 failed, 2 ignored (the P1 baseline of 126/2 plus these four new
tests), and every mutation entry was killed by its own test.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 15:46:57 +02:00
Meta Vens
4845e989aa Record Q-V5 ruled A: no Windows verify host in this campaign
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 15:31:35 +02:00
Meta Vens
effe40b7a4 Record Q-V3 ruled A: requests name step ids, the binding admits programs
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 15:29:03 +02:00
Meta Vens
e01ce88e61 Record Q-V8 ruled B: verify runs as its own unit; Cut 2 is unblocked
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 15:25:38 +02:00
Meta Vens
e0cfd0dcf5 Record Q-V7 ruled B: a no-sudo eureka account that can only request and read
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 15:24:30 +02:00
Meta Vens
c65a88500c Record Q-V4 ruled B via Add-Type: the harness writes its report as CultCache
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 15:23:22 +02:00
Meta Vens
2eb25b6a85 Cut 0: kill the stale docs, verify transaction campaign
Deletes the two stale "Current implementation boundary" sections in
docs/deployment-authority.md and docs/authority-map.md; both described a
pre-drivers.rs era and were wrong (drivers.rs has driven Git, runners,
systemd, nginx, brakes, CultCache and CultMesh for weeks). The still-true
second paragraph of the deployment-authority.md section (private plan state
is never published as Odin topology) moves to the end of "Typed deployment
state", reworded present tense.

Fixes README.md's stale claims: the CLI has five commands (cancel and
validate were missing), and docs/guide.md plus deploy/legacy/ describe a
generation that no longer runs anywhere, not "still what runs on yggdrasil".

Documents present truth in "Candidate promotion": advance_sealing resolves,
freezes, runs every recipe step and installs before the deployment brake is
consulted at all. The brake only gates the move into Starting, the first
Verse-visible change.

Docs only, no build required.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 15:21:11 +02:00
Meta Vens
37f1a1dfd9 Record Q-V2 ruled A: a request is an exact pushed commit and step ids
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 15:18:59 +02:00
Meta Vens
3020c7c56f Record Q-V1 ruled A: verify recipes live in the repo
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 15:18:05 +02:00
Meta Vens
52620ac699 Map the verify transaction: Idunn owns verification
Operator ruling 2026-09-22 after Starfire froze under agent build load. A
verify transaction takes an exact revision and declared steps, runs them in
capped runners in its own worker, returns a typed verdict, seals nothing and
sits outside the deployment brake. Imagination's map; ten operator questions
open, five of them blocking Cut 2.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 15:14:53 +02:00
Meta Vens
5b3f646bf1 Bump CultLib pin to 61549aa: anchor-export permission fix
Idunn's cultmesh-rs/cultcache-rs/cultnet-rs pin moves from c13b6ba to
CultLib origin/main 61549aa, picking up e9b91c1 ("Preserve public
trust-anchor parent permissions"). That fix matters here: Idunn calls
export_service_identity_trust_anchor from provisioning.rs:129 and
control_plane.rs:6633 to publish a public anchor into a directory another
service reads from. Before the fix, export routed through the same
prepare_parent helper as private enrollment and chmod'd that directory
0700 on every export, which would lock the legitimate reader out instead
of protecting anything. CultLib now splits ensure_parent (export, no
narrowing) from prepare_private_parent (enrollment only).

Encoding audit (c13b6ba..61549aa in cultcache-rs/cultnet-rs/cultmesh-rs/
contracts, 21 commits): none touch the eight record types Idunn persists
or verifies (GameCultServiceTrustAnchorRecord, IdunnDaemonHealthTrust
BindingRecord, IdunnDeploymentBrakeRecord, IdunnExpectedIncarnationRecord,
IdunnLifecycleBrakeRecord, IdunnProcessWriteLeaseRecord, IdunnRuntime
ActivationRecord, OdinRuntimeTopologyCorrelationRecord).
- 9e6027e adds only a test (byte_slot_migration.rs); no production flip
  lands in this range.
- 2ba404c/96138ff/a3e6877/etc. touch MediaStreamContracts and
  media_stream_wire record types (Muninn/Ratatoskr's media path) that
  Idunn never derives, persists or reads.
- c2a9a6e changes cultcache-rs's Windows-only atomic-replace retry
  (cfg(windows) MoveFileExW), not record encoding, and Idunn targets
  Linux.
- 47b9fc4/d3bc6e6/66f05e1 are cultnet-rs rudp/transport changes with no
  record-shape effect.
No re-encode guard in Idunn (drivers.rs, control_plane.rs, provisioning.rs
canonical-bytes checks) is at risk; this is a pin bump, not a store
migration.

Schema-id audit: fcc49cf ("Consolidate CultNet schema contracts") moves
JSON Schema files and changes their $id to
https://github.com/GameCult/CultLib/contracts/cultnet/... inside
packages/cultnet-ts and the C# reference implementation. Idunn's Rust
source and tests contain no reference to cultnet-ts/contracts,
schema_catalog, content_hash, CultNetSchemaRegistry or
builtin_schema_registry. Not a consumer.

Live-branch audit: origin/claude/windows-host-actuator (the branch the
live yggdrasil binary was built from, last commit 9dc80f8) is fully
contained in origin/main (git merge-base --is-ancestor confirms it) and
still carries the old c13b6ba pin. This bump is based on origin/main only;
the live branch is not touched here and would need its own follow-up.

Cargo.lock: cargo update -p cultmesh-rs -p cultcache-rs -p cultnet-rs
moved exactly four git-sourced packages (cultmesh-rs, cultcache-rs,
cultcache-rs-derive, cultnet-rs) to the new rev; the lockfile diff is only
their `source = git+...?rev=...` lines, no checksums, versions, or
unrelated packages changed.

No Idunn source changes were needed: CultLib made no API-breaking rename
or reshape Idunn depends on. cargo check --locked (Windows, compile gate
only) and cargo check --locked --tests both pass clean.

Verification ran on yggdrasil (Linux, the deploy target), not the
Windows workstation. Built a throwaway unprivileged toolchain (no sudo,
no docker) under ~/idunn-pin-61549aa since /srv/build is root:idunn
0755 and gamecultadmin has no docker-group access; rustc/cargo 1.95.0
matches the recipe's rust:1.95-bookworm image exactly. `cargo test --lib`:
117 passed, 9 failed, 2 ignored. All 9 failures require root privilege or
ufw (frozen-source root ownership, sealed-release root ownership, runner
cache/secret chown, service-credential 0400 root inode, write-lease
actuation, host-actuator-hub ufw allow) -- capabilities this unprivileged
non-containerized session does not have, not CultLib regressions. Proved
by re-running the identical suite at the old c13b6ba pin in the same
unprivileged environment: identical 117 passed / 9 failed, same 9 test
names, same errors. All 16 provisioning:: tests pass at the new pin,
including identity_enrollment_and_export_are_immutable_and_paths_cannot_alias
and provider_and_odin_anchor_exports_are_profile_exact_immutable_and_non_aliasing,
which cover the trust-anchor export path this bump fixes. `cargo build
--release --locked` also succeeds. Both throwaway directories
(~/idunn-pin-61549aa, ~/idunn-baseline-c13b6ba) were removed from
yggdrasil afterward.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-13 19:25:50 +02:00
Meta Vens
2500529a08 Doc: what the Raven cut taught, measured
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-11 14:59:54 +02:00
Meta Vens
9dc80f8f5e An unobservable host is silence, not a death; stop-then-start for fixed-resource hosts
Continuity counted 'host actuator raven is not attached' as a workload
death and burned all three restart attempts inside the reattach window
after an Idunn restart, demoting a healthy Muninn. The hub now returns a
typed HostUnobservable for not-attached, detached, reconnected and
unanswered, and supervision skips the tick instead of restarting.

A route-less host workload that owns fixed host resources (one desktop,
one capture, fixed advertised ports) cannot overlap two incarnations. A
binding may now declare rollout strategy stop-then-start: the admitted
incumbent is stopped at Starting, before the candidate starts, and
supervision leaves the target alone from Starting on. A systemd-transient
workload still rolls out candidate-then-promote only.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-11 14:42:35 +02:00
Meta Vens
3269ffde74 The host actuator hub is serviced on its own thread
A scheduler tick that blocks on a build (odin's docker build took a
minute) left every attached host unserviced; the actuator timed out,
redialled, and three continuity restarts of raven-muninn failed with
'host actuator raven is not attached' inside one minute. The hub now
lives behind a mutex with a service thread; a request holds the lock and
services the socket itself for its whole exchange.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-11 14:35:39 +02:00
Meta Vens
2d73608c4e The actuator's git never rewrites line endings; the frozen tree is byte-exact
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-11 14:05:22 +02:00
Meta Vens
2d4f9840a3 A binding path is absolute by its own shape, not the judging platform
The host actuator re-parses a Linux binding on Windows to reach its own
section; the Idunn host's /var paths must not fail there. This also makes
the Linux-shaped test fixtures pass on a Windows checkout.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-11 14:02:54 +02:00
Meta Vens
fb82dce265 A host-actuator recipe declares the presence identity it is handed as environment
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-11 13:59:17 +02:00
Meta Vens
ffaeae3829 idunn-provision prints the derived identity id of an enrolled store
provider-health-identity-id and host-actuator-identity-id are read-only.
The id is what a binding names as expected_signer_identity_id and what
a host is keyed by; deriving it here closes the gap where an operator
had to decode an anchor by hand to fill a binding.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-11 13:51:50 +02:00
Meta Vens
144581f9c7 Host actuator: Idunn manages a workload on a Windows host it cannot touch
A managed host runs idunn-host in the user session. It dials Idunn's hub
over CultNet RUDP on the mesh, proves itself with a host actuator service
identity, and executes the runner and workload ports for its host: fetch
the exact frozen revision, build with the host's cargo under the binding's
allowed programs, install the sealed release, start the workload as a
child, report pid, creation time and executable digest, stop on request.
It keeps no transaction state and decides nothing.

On the Idunn side the engine chooses the runner and workload driver per
plan from the binding's driver tag; the yggdrasil daemon is still the
single deployment and continuity authority, and a binding of the old kind
takes the same path it always did. Every request is signed by Idunn's
identity and every report by the host's; both ends refuse the other's
message unless signature, session nonce and sequence hold. The hub admits
its own firewall allow the way the route driver does.

idunn-provision learns to enroll and export the host actuator identity.
The unit gains --host-actuator-bind on the mesh address.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-11 13:49:25 +02:00
Meta Vens
c848459471 Bindings are driver-tagged and the observation is a variant per host kind
RunnerBinding and WorkloadBinding become enums tagged by driver, so a
host-native runner and a host-actuator workload can be declared without
counterfeiting docker images or systemd units. The single-variant
RunnerDriver and WorkloadDriver enums are gone; the driver is the tag.

WorkloadObservation is now Systemd or Host, untagged on the wire so every
transaction the previous Idunn persisted decodes unchanged (proved under
both MessagePack encodings CultCache uses). IsolationEvidence follows:
Linux proves uid, pid and mount namespaces; a host proves two live
processes distinct by pid and creation time. Mixed kinds are refused.

The build machine-id seal is unix-only so the library compiles on the
Windows host that will run the actuator.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-11 13:25:50 +02:00
Meta Vens
c3d1b7a614 Doc: authority map for the Windows host actuator
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-11 13:13:43 +02:00
Meta Vens
692ba1fe4d An aged-out Odin correlation is absence to wait on, not evidence to refuse
Odin re-stamps a correlation only when its facts change, and a live presence
changes them every heartbeat, so a correlation older than the trusted window
means nothing about that incarnation has arrived lately. Admission and
supervise now read that one refusal as nothing-new. It had failed every
Heimdall deployment and the first observed Odin deployment at Warming.

Verified with cargo test --lib in rust:1.95-bookworm on yggdrasil: 118 passed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-11 00:35:30 +02:00
Meta Vens
e81c73efad Warming asks the admitted Odin for the candidate's correlation once
Admitting the latest topology persists the sequence cursor; asking twice
with the same record failed the second time as "transaction changed before
topology admission". Only reachable now that an admitted Odin can observe a
candidate Odin under incarnation keys; it failed the first fold-back
deployment on yggdrasil at Warming.

Verified with cargo test --lib in rust:1.95-bookworm on yggdrasil: 118 passed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-11 00:31:06 +02:00
Meta Vens
103c5572df Unit: drop the duplicated /etc/ufw line
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-11 00:27:38 +02:00
Meta Vens
844e3246c3 Unit: one ReadWritePaths line for /etc/ufw, not one per line
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-11 00:27:27 +02:00
Meta Vens
ddff209372 Unit: /etc/ufw is writable so the route driver can admit its endpoint
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-11 00:27:11 +02:00
Meta Vens
5ae006ca8b Doc: the route driver owns the stable endpoint's firewall allow
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-11 00:23:41 +02:00
Meta Vens
f16fb46c95 Route driver owns the host firewall allow for its stable endpoint
A route that is admitted must be reachable without an operator opening a
port by hand. NginxRouteDriver admits one ufw allow for the binding's stable
address, port and transport, tagged with the route id, before it reloads a
fragment, and withdraws that one rule when the last membership is removed.
A rule already gone reads as the state asked for. Odin on yggdrasil is
admitted on 10.77.0.1:17971 with ufw still admitting only the previous
generation's 17871; this closes that class.

Verified with cargo test --lib in rust:1.95-bookworm on yggdrasil: 118 passed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-11 00:23:28 +02:00
Meta Vens
1899df2244 Withdraw stale incarnations from the projection in supervise
A replaced incumbent, an aborted candidate whose reconciliation was skipped,
and a record left by the projection migration all leave an Expected-only
incarnation in the projection that nothing owns. Supervise now withdraws one
per tick for each admitted target: any projected incarnation that is neither
the admitted generation nor a live transaction's. One owner for all three
sources, instead of a per-commit field.

Verified with cargo test --lib in rust:1.95-bookworm on yggdrasil: 117 passed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 23:58:05 +02:00
Meta Vens
f659b9d7ca Retire one resident terminal transaction per tick
Transactions that finished before finished transactions travelled to history
are still resident with their commands, gating nothing but growing the store
every decision reads. They leave the same way a transaction finishing today
does, one per tick.

Verified with cargo test --lib in rust:1.95-bookworm on yggdrasil: 116 passed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 23:00:29 +02:00
Meta Vens
7bcd567ef0 Prove a resident command with only historical transactions is retired, not re-frozen; log retirements
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 22:57:39 +02:00
Meta Vens
1a54db1831 Consume commands by any transaction, retire the resident backlog, do not let a busy target block the queue
Commands whose transactions were retired to history before commands travelled
with them were still resident and read as queued; freeze took the oldest
first, so one stale odin command was re-frozen hundreds of times today and
the newest command never got a turn. freeze_one_queued_command now reads
history, retires such commands on sight, and skips a busy target instead of
returning. status reports a live transaction as running rather than an older
attempt's failure, and prints completion and cleanup evidence for --command.
README carries the Linux build recipe; Windows does not compile.

Verified with cargo test --lib in rust:1.95-bookworm on yggdrasil: 114 passed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 22:46:54 +02:00
Meta Vens
6b68d65880 Count refused continuity restarts from history too
Failed restarts retire to history.cc as soon as they finish, so the give-up
counter that read only the live set found zero every time and restarted a
release that could not start forever. Seen on yggdrasil during the
projection-key migration.

Verified with cargo test --lib in rust:1.95-bookworm on yggdrasil: 114 passed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 22:32:30 +02:00
Meta Vens
1a0a0e7958 Observe the candidate before waiting on Odin's evidence in AwaitingReady, Routing, and Committing
A candidate that died while its correlation was pending was never observed
again: each phase returned on no-evidence-yet every tick, the error that
triggers the post-fencing abort never happened, and the transaction owned the
target forever behind a process that was gone. Seen on yggdrasil with a
continuity transaction parked in Committing across the projection-key
migration.

Verified with cargo test --lib in rust:1.95-bookworm on yggdrasil: 114 passed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 22:28:48 +02:00
Meta Vens
903e1c8e63 Key the projection by incarnation; publish Expected after the brake; consume commands once; add cancel
The projection kept one Expected/activation/lease slot per target. A
deployment's Sealing overwrote that slot with the candidate's Expected, the
incumbent failed activation-expected-projection on its next heartbeat and
exited, the deployment was aborted for having killed its incumbent, and the
command -- left resident after its transaction was retired to history --
read as queued and was frozen again. Odin cycled on that every few minutes.

- Topology driver records are keyed {target}@{expected sha}; the incumbent and
  the sealed candidate are projected side by side. One CAS mutation replaces
  the six bespoke loops. withdraw_incarnation removes only one incarnation;
  demote_to_expected_only replaces restore_admitted_expected_only and is used
  only for an incarnation whose process is gone. Legacy target-keyed records
  are retired on the first publish for that target.
- receive() takes the incarnation; the read-as-absence compensator for a
  correlation about another incarnation is gone.
- Candidate Expected is published as Starting's first step, after the brake
  admitted the transaction, not in Sealing before it.
- A pre-fencing abort withdraws only the candidate; a post-fencing abort also
  demotes the stopped incumbent.
- A consumed command is archived with its last transaction; status still
  answers for it from history.
- An engaged lifecycle brake stops continuity being minted instead of parking
  a transaction on the target.
- idunn cancel <command-id> retires a queued command as an operator refusal.

Verified with cargo test --lib in rust:1.95-bookworm on yggdrasil: 114 passed,
2 ignored.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 22:22:46 +02:00
Meta Vens
222bfcc133 Retire finished transactions to history.cc
The control store held every transaction Idunn had ever run -- 260 on
yggdrasil, all terminal, 2.6 MB re-read and byte-exact checked on every
scheduler tick. A finished transaction gates nothing; it only makes the
live set slower to read and gives history more ways to refuse one.

replace_transaction now moves a transaction out of control.cc the moment
it becomes terminal. History first, then the live copy: a crash between
the two leaves the record in both, which reads as still-resident and is
archived again next time. The other order can lose it, and a completion
nobody can observe is what R11 refused to trade away for a smaller live
set. insert_entry_if_absent makes the repeat a no-op and the delete is a
compare-and-swap on the exact envelope archived, so a transaction that
changed underneath is left alone rather than dropped.

history.cc is read leniently and for display only; control.cc keeps the
byte-exact canonical check because its records still gate decisions. That
asymmetry is the whole reason the two files are separate. status merges
both, so a completed command still reports what it did rather than
appearing never to have run.

No sweep. The earlier attempt archived resident terminal records from
inside the scheduler tick's propagating prefix, so an unwritable history
starved every live transaction on every other target. One primitive, one
caller, at the moment of completion.

Verified against a copy of the live store: status output is byte-identical
between the installed binary and this one, so the 260 existing records are
unaffected. They stay resident -- moving them is a separate explicit
operation, not something to bolt onto a scheduler.

  cargo test --locked -> test result: ok. 113 passed; 0 failed; 2 ignored

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 02:06:25 +02:00
Meta Vens
64cc78d748 Stop letting terminal history and a missing brake refuse startup
validate_durable_authority read the deployment-brake operator anchor
unconditionally and then re-proved every transaction in the store. Two
consequences, both live until now:

An absent brake artifact refused startup against an empty store. That is
a deployment brake gating Idunn itself, which F:\Projects\CLAUDE.md
forbids in as many words -- a target brake "may never gate Idunn itself
or unrelated service lifecycle". The anchor is now read on demand, when
a record actually carries an authorization to verify.

Terminal transactions were re-proved on every boot. They describe what
already happened and authorize nothing further, so the only thing that
re-proving them can do is invent new ways for history to refuse a boot --
a rotated signer on a record from last week making the daemon
unstartable. They are skipped. On yggdrasil that is 259 of 260 records,
and this is what R4 meant by terminal history not being live authority.

Live records keep every check unchanged.

Known gap, deliberately not closed here: a live record whose signed
evidence no longer verifies still refuses startup. The right answer is to
abort that transaction rather than refuse the boot, which needs the
post-fencing abort path and is its own cut.

  cargo test --locked -> test result: ok. 110 passed; 0 failed; 2 ignored

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 01:41:14 +02:00
Meta Vens
5b6a650482 Let one bad transaction fault its tick instead of the daemon
serve's loop propagated every scheduler-body error with `?`. Idunn runs
under Restart=always, so a single unreadable record turned into a
crashloop that took every unrelated target's continuity down with it --
the daemon-survival organ killed by exactly the thing it exists to
survive. This is the second half of the store-history work; the first
half landed as the admission gates.

run_scheduler_tick extracts one pass with the loop's ordering preserved
exactly, including that Ok(true) means "go round again immediately",
which is what the caller's `continue` did. The loop logs a faulting tick
and keeps polling. The fault recurs every poll until someone fixes it,
which is louder than a restart loop and cheaper than an outage.

Adds EngineFixture, the first Engine-level test harness this crate has
had -- enrolled Idunn and Odin identities, an exported anchor, tempdir
roots. Its absence is why "does one bad record kill the daemon" was
arguable rather than measurable.

The test proves the tick is a clean unit of failure: a 0xc1 payload (the
one byte msgpack never emits) makes three consecutive ticks report an
error and leaves the control store byte-identical, so a tick that cannot
read does not rewrite, truncate or silently repair. The loop's own match
arm is not covered -- serve never returns, so no unit test reaches it --
and that part rests on inspection.

  cargo test --locked -> test result: ok. 109 passed; 0 failed; 2 ignored

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 01:37:30 +02:00
Meta Vens
da927ea30e Name the workload, not the candidate, in the restart-policy refusal
is_permanently_stopped is called on the incumbent as well as the
candidate since 8448f83, so a message naming only the candidate would
misdirect the first person who hits it. Unreachable today -- observe_unit
guarantees every recorded workload is Restart=no -- which is exactly why
the wording would be read cold.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 01:05:42 +02:00
Meta Vens
8445c6b45a Pin the schema refusal for a Present record without an instance id
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 01:04:05 +02:00
Meta Vens
db5c21a558 Correct two audit test expectations
A Present correlation with no activation is refused by the schema before
authentication, and a foreign Odin signature only names a different
identity when the record carries that identity's id.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 01:03:13 +02:00
Meta Vens
ffa5926d0d Audit tests for the admission gating rebuild (c09993c..8448f83)
Soul pass on the five gate changes. Negative checks only:
- another incarnation's perfect correlation, another activation of the same
  Expected, and a foreign target beside our projection are all refused by
  authentication alone (the deleted identity gate was a subset of it)
- the signing fixture refuses at every link: activation/Expected binding,
  Idunn anchor, provider key, foreign Odin signer, forged signature
- a durable receipt replays at its own admitted_at and not at now
- is_permanently_stopped is true only for failed or not-found units; every
  transitional ActiveState reads as running, and a systemctl failure errors
- an ignored live-store inventory probe

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 01:01:31 +02:00