Idunn/deploy/legacy/README.md
Meta Vens 814e08e0c4 Make the public entry point describe the binary that actually ships
The README and unit file I wrote at extraction documented the previous
generation's CLI. Running the deployed ExecStart against a build of this
repository returns `unknown Idunn command "--swarm-profile"`: commit 330e4cd
rebuilt Idunn around typed authority ports and a test now asserts that
--deploy-command, --restart-command and --swarm-profile are rejected. Anyone
following the README would have installed a unit that crashloops on exec
every 5 seconds under Restart=always.

Corrected against the binary, not against the old docs:

- deploy/idunn-yggdrasil.service now invokes `idunn serve` with the real
  flags. systemd-analyze verify passes on yggdrasil. /var/lib/gamecult/odin
  moved from ReadWritePaths to ReadOnlyPaths -- Idunn only reads Odin's
  topology correlation (pull_all_read_only_snapshot, absent store yields
  None), and it gates deployment promotion only: continuity never consults
  it, per stale_odin_provider_receipt_cannot_gate_continuity_restart.
- README documents the three declarative commands, and adds a section on the
  recipe/binding split, which is the security model: capability lives in the
  target's repo, privilege lives in the host binding, and a binding that
  omits an affordance the recipe derives fails admit().
- Corrected two inherited claims. The distinct-brake check is per-binding
  validation ("deployment and lifecycle brakes are identical"), not a
  startup check. And the "inputs: nothing else" claim was false -- the Odin
  correlation read is a real, deliberate exception and now says so.

The legacy actuator moves to deploy/legacy/ rather than being deleted: it is
what is installed and running, so it is the before-side of the migration.
Its README maps each shell mechanism to the contract that replaced it, and
preserves the eleven-target inventory read out of deployment_target_policy().
Seven of the eleven are pinned to a codex/ branch rather than main.

docs/guide.md is marked as the previous generation rather than silently
carrying dead commands, and docs/migration.md is new: per-target order,
what to fix before carrying a ref forward, and why the authority cuts over
last. Also fixes a broken cross-link in authority-map.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 23:15:46 +02:00

3.6 KiB

Legacy actuator — the generation currently installed on yggdrasil

These files are superseded. Do not use them for a new installation. They are kept because they are what is running, and a migration needs both sides.

idunn-yggdrasil is a root shell actuator reached through the narrow sudo grant in idunn-yggdrasil.sudoers. It hardcodes eleven targets, their repositories and their admitted refs, then delegates to executable manifests under /srv/odin/deploy-manifests/<target>. It calls idunn validate-release-authority and idunn validate-minimum-source-revision — subcommands the current binary does not have.

What replaced it

Everything this script does by convention, the current Idunn does by contract:

Legacy mechanism Current owner
deployment_target_policy() — a case over eleven target names one operator binding per target, in --bindings-dir
upstream_ref=refs/heads/... in shell [repository] admitted_ref
minimum_source_revision_required [repository] minimum_revision
requires_bifrost_authority + --release-authority-store [repository] selection = "signed-release", per target
/srv/odin/deploy-manifests/<target> shell scripts recipe [[steps]] in the target's own repository
run_manifest_while_holding_authority_locks the deployment transaction and process write-lease
flock on a brake store [brakes] deployment_store and lifecycle_store
root shell with IDUNN_ACTUATOR=1 systemd-transient workload driver under DynamicUser

The structural difference is not tidiness. The legacy path admits a deployment by validating arguments to a root shell script, and everything after that validation is unverified shell running as root. The current path seals exact source and artifacts, admits one incarnation against typed operator authority, and hands execution to systemd — with no place to pass a command string, enforced by cli_exposes_only_declarative_commands.

The target inventory, which is still useful

This is the real migration scope, read out of deployment_target_policy():

Target Repository Admitted ref Notes
odin GameCult/Odin codex/ygg-idunn-independent-bootstrap ref is a branch, not main
codex-connector GameCult/CodexConnector codex/ghostlight-release-binding minimum revision enforced
ghostlight GameCult/Ghostlight codex/ghostlight-dungeon-mvp minimum revision enforced
voidbot GameCult/VoidBot main
heimdall GameCult/Heimdall main restart is docker compose restart
epiphany GameCult/Epiphany codex/epiphany-shakedown-live
epiphany-capstone-17 GameCult/Epiphany codex/epiphany-shakedown-live second target, same repo
bifrost-persona-feedback GameCult/Bifrost main only target requiring Bifrost release authority
repixelizer GameCult/repixelizer main
streampixels GameCult/StreamPixels main two units, one target
gjallar GameCult/Gjallar codex/yggdrasil-aggregate-daemon

Seven of eleven are pinned to a non-main branch. That is its own problem — the swarm goal is everyone on main and current with each other — and migrating a target is the moment to fix its ref, not to copy it forward.

When these files die

When every target above has a binding and a recipe, and idunn serve owns actuation on yggdrasil. At that point delete the sudoers grant first, then the script, then /srv/odin/deploy-manifests/. The grant is the privileged half; it should not outlive its consumer.