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>
This commit is contained in:
parent
89afe723bb
commit
814e08e0c4
8 changed files with 345 additions and 39 deletions
|
|
@ -1,8 +1,10 @@
|
|||
[Unit]
|
||||
Description=Idunn Yggdrasil-local lifecycle coordinator
|
||||
Description=Idunn deployment admission and daemon-survival authority
|
||||
After=network-online.target docker.service
|
||||
Wants=network-online.target
|
||||
# Deployment runners use Docker, but continuity actuation does not. A hard
|
||||
# Deployment runners use Docker, but continuity actuation does not. Warm,
|
||||
# fence, promote, drain and restart all run on systemd; only the build step
|
||||
# is containerized, and worktree-tree recipes skip even that. A hard
|
||||
# Requires= would let a Docker outage stop Idunn itself, killing daemon
|
||||
# survival for every target. Want it, order after it, and let individual
|
||||
# container-backed deploys fail on their own if it is absent.
|
||||
|
|
@ -12,9 +14,19 @@ Wants=docker.service
|
|||
Type=simple
|
||||
User=idunn
|
||||
Group=idunn
|
||||
# Operational cancellation ceiling only. Target daemons own readiness through
|
||||
# typed health; this duration must never be interpreted as deployment success.
|
||||
ExecStart=/usr/local/bin/idunn --swarm-profile yggdrasil-local --repo-root /var/lib/gamecult/idunn --store /var/lib/gamecult/idunn/yggdrasil.keepalive.cc --release-authority-store /srv/bifrost/state/repository-release-authority.cc --deployment-brake-store /var/lib/gamecult/idunn-authority/deployment-brake.cc --deployment-brake-operator-anchor /etc/gamecult/idunn/deployment-brake-operator-anchor.cc --daemon-health-trust-store /etc/gamecult/idunn/daemon-health-trust.cc --rudp-health-bind 10.77.0.1:17870 --service-identity-store /var/lib/gamecult/idunn/idunn-service-identity.cc --public-health-store /var/lib/gamecult/idunn/authenticated-provider-health.cc --public-health-query-bind 127.0.0.1:17871 --command-timeout-seconds 21600 --execute
|
||||
# Idunn discovers no authority implicitly. Every store is named here, and
|
||||
# every one of them is Idunn's own -- no path below belongs to a managed
|
||||
# target. That is the invariant, expressed as a command line.
|
||||
ExecStart=/usr/local/bin/idunn serve \
|
||||
--state-store /var/lib/gamecult/idunn/control.cc \
|
||||
--bindings-dir /etc/gamecult/idunn/bindings \
|
||||
--source-root /var/lib/gamecult/idunn/sources \
|
||||
--staging-root /var/lib/gamecult/idunn/staging \
|
||||
--topology-store /var/lib/gamecult/idunn/topology.cc \
|
||||
--idunn-identity-store /var/lib/gamecult/idunn/idunn-service-identity.cc \
|
||||
--odin-correlation-store /var/lib/gamecult/odin/idunn-runtime-topology.cc \
|
||||
--odin-trust-anchor /etc/gamecult/idunn/odin-topology-anchor.cc \
|
||||
--deployment-brake-operator-anchor /etc/gamecult/idunn/deployment-brake-operator-anchor.cc
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
TimeoutStopSec=20
|
||||
|
|
@ -23,8 +35,18 @@ UMask=027
|
|||
PrivateTmp=yes
|
||||
ProtectHome=yes
|
||||
ProtectSystem=full
|
||||
ReadWritePaths=/etc/gamecult/idunn /etc/gamecult/codex-connector /etc/gamecult/ghostlight-dungeon /etc/systemd/system/codex-connector.service /etc/systemd/system/epiphany-swarm.service /etc/systemd/system/ghostlight-dungeon.service /var/lib/gamecult/idunn /srv/build /srv/voidbot
|
||||
ReadOnlyPaths=/var/lib/gamecult/idunn-authority
|
||||
# Idunn's own state, and the build root it materializes releases in.
|
||||
# Enumerated rather than broadened: this list is the blast radius.
|
||||
ReadWritePaths=/var/lib/gamecult/idunn /var/lib/gamecult/idunn-authority /srv/build
|
||||
# Everything Idunn only reads. The operator anchor and target bindings are
|
||||
# operator-authored: Idunn admits against them, it does not write them.
|
||||
# /var/lib/gamecult/odin is a managed target's directory and is read-only on
|
||||
# purpose -- Idunn consumes Odin's published topology correlation to gate
|
||||
# *deployment* promotion, and nothing else. Continuity never consults it
|
||||
# (see stale_odin_provider_receipt_cannot_gate_continuity_restart), and an
|
||||
# absent store returns None rather than failing, so Odin being down blocks
|
||||
# promotions and leaves daemon survival untouched.
|
||||
ReadOnlyPaths=/etc/gamecult/idunn /var/lib/gamecult/odin
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
|
|
|||
62
deploy/legacy/README.md
Normal file
62
deploy/legacy/README.md
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
# 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.
|
||||
Loading…
Reference in a new issue