Commit graph

541 commits

Author SHA1 Message Date
Meta Vens
82e0edfaf7 Land the second Cut 1 fix batch: F2/F3 write-outside-root regression, F4 hardening, F5 chain-aware symlink check
Soul's second pass on the Cut 1 fix batch found a new regression (a
duplicate/aliased tree name lets the raw freeze write outside its root) and
three pre-existing gaps: two redundant hardening passes with no pin, a
lexical (not chain-resolving) symlink escape check, and unpinned Docker argv
combinations. F1's perf finding (40-80x slower than the old git archive path)
is addressed here too.

F2/F3 (write-outside-root, two required layers):
- git_tree_entries now calls refuse_conflicting_tree_entries, which refuses
  a tree where two leaf paths collide case-insensitively, or where one leaf
  path is a case-folded ancestor of another (the exact shape
  `git hash-object --literally` lets through: a symlink and a subtree
  sharing one name). The exact-revision fetch and both clone paths
  (ensure_checkout, materialize_gitlink_raw) enable transfer.fsckObjects.
- The writer itself never trusts that check alone. ensure_frozen_directory
  is the only way materialize_tree_raw obtains a directory to write into: a
  path must already be a directory this freeze created (tracked in
  created_dirs, shared across the superproject and every Gitlink), or must
  not exist at all; anything else is refused rather than traversed.
  Symlinks are written in a dedicated last pass, after every directory and
  regular file the tree needs already exists.

F4: freeze_exact now calls harden_frozen_source once, last, instead of
harden then a separate validate_frozen_source pass over the same tree.
validate_frozen_source stays as the read-only re-check observe_frozen uses
on an already-hardened tree it did not just write.

F5: validate_frozen_source_symlink resolves the whole symlink chain with
fs::canonicalize instead of lexically counting ".." components, which
undercounts a chain that loops back through another symlink (D -> .,
L -> D/D/.../../../../etc/passwd). copy_artifact (the one place besides
freeze that reads through a possibly-attacker-controlled tree) gets the same
whole-chain resolution: it now takes the artifact's containment root and
refuses a source that resolves outside it, closing the gap where an
intermediate symlink in a runner's own build output could be followed.

F1: materialize_tree_raw bulk-fetches every blob a tree needs in one
`git fetch --stdin` round trip (Git 2.36+, no argv-length limit) instead of
letting a blob:none partial clone lazily fetch each one as `cat-file --batch`
reads it. Blobs stream straight to their destination file instead of being
held in a BTreeMap for the whole tree. read_blobs is replaced by
stream_blobs, which drains stderr on a dedicated thread and waits on the
child on every return path, including an error from the per-object
callback (F9).

F6: pins four previously-unpinned Docker argv/environment combinations
(N1: --read-only survives a Named network plus a cache mount; N2: a secret
mount stays read-only alongside a plain environment entry; N3: the source
stamp is never dropped alongside a secret and a plain environment entry;
N5: an LFS pointer file sets contains_lfs_pointers).

Tests: 142 to 152. cut1.psd1 grows by 12 entries (5 for F1-F5, 4 for F6's
N1/N2/N3/N5, and one recording the F2 fsck flag as not yet reached -- see
below); all 28 non-comment entries in the suite now kill.

Discrepancy: cut1-fix-f2-no-fsck-on-fetch could not be pinned. Every tree
this crate's own tools can build that fsck's duplicateEntries check rejects
also has a literal duplicate path or a case-folded ancestor collision that
refuse_conflicting_tree_entries rejects first, unconditionally, before the
fetch's fsck result can matter. The flag stays as defense in depth (fsck's
other structural checks are broader than this crate's own parser), recorded
as not yet reached in cut1.psd1 rather than removed or falsely claimed
unreachable.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 18:29:36 +02:00
Meta Vens
f7be933de3 Record Q-V9 and Q-V10 ruled A by Self; every verify question is now settled
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 17:59:21 +02:00
Meta Vens
e26a0992ea debug: four-entry isolation 2026-09-22 17:58:51 +02:00
Meta Vens
02a81fa1ae debug: isolate f1 mutant 2026-09-22 17:57:10 +02:00
Meta Vens
3f6d690f92 wip6 2026-09-22 17:51:05 +02:00
Meta Vens
43c7a6e46d wip5 fix spy log perms 2026-09-22 17:47:29 +02:00
Meta Vens
47912bc6b5 wip4 remove scratch mutator 2026-09-22 17:46:08 +02:00
Meta Vens
f8baa93dac wip3 tests+psd1 2026-09-22 17:46:02 +02:00
Meta Vens
2ee7c225e2 wip2 2026-09-22 17:41:06 +02:00
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