Idunn/README.md

271 lines
11 KiB
Markdown
Raw Normal View History

# Idunn
**Deployment admission and daemon-survival authority for the GameCult swarm.**
Idunn decides two things and nothing else: whether an artifact may be
installed, and whether an already-installed body may be restarted. It keeps
daemons alive across a fleet, admits releases against operator-signed
authority, and reports typed health — while depending on none of the services
it manages.
Extracted from [Odin](https://github.com/GameCult/Odin) on 2026-09-05 with its
full 136-commit history. Idunn had lived as a crate inside the repository of a
service it manages, which inverted the authority it exists to hold.
---
## Quick start
Requires Rust 1.95+. Idunn is a **Linux daemon**; see [Platform](#platform).
```bash
git clone https://github.com/GameCult/Idunn.git
cd Idunn
cargo build --release
```
Two binaries land in `target/release/`:
| Binary | Purpose |
|---|---|
| `idunn` | the daemon — keepalive supervision, deployment actuation, health |
| `idunn-provision` | operator tooling — brake status, identity enrolment, provisioning |
Idunn's CLI is **declarative**. There are five commands and no imperative
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
escape hatch — you cannot hand it a shell string to run:
```bash
idunn serve [runtime options] # the control plane
idunn up <service|profile:name> [--no-wait] # request a deployment
idunn status [--command ID] # read what happened
idunn cancel <command-id> # cancel a pending command
idunn validate --recipe PATH [--binding PATH] # offline admission check
```
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
A test, `cli_exposes_only_declarative_commands`, asserts that
`--deploy-command`, `--restart-command` and `--swarm-profile` are **rejected**.
What a target is, how it is built, and what it is allowed to touch live in two
documents, never in a command line — see [Recipes and bindings](#recipes-and-bindings).
Request a deployment and watch it:
```bash
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
idunn up ghostlight --requested-by "$USER"
idunn status --command <id>
```
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
`idunn up` accepts a service name or `profile:<name>`; profiles are declared by
the bindings, so `idunn up profile:full-gamecult` brings up everything bound
into that profile.
## Authority
**Owner.** Deployment admission, and the continuity of installed bodies.
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
**Inputs.** Its own typed state stores, its own credentials, and
operator-authored trust anchors and bindings. It does not read a managed
target's binaries, health, or availability to decide anything about itself.
The one deliberate exception, stated plainly rather than hidden: Idunn reads
Odin's published topology correlation to gate **deployment** promotion, so that
a promotion waits for semantic discovery. It is read-only, an absent store
yields `None` rather than an error, and it is structurally unreachable from the
continuity path — `validate_live_providers_for_deploy` never invokes the
provider check for `CommandKind::Continuity`. Odin being down blocks
promotions. It cannot stop daemon survival.
**The invariant.**
> Idunn must be able to start, recover, observe and report while any managed
> target is absent, broken, or deliberately braked.
A target's brake gates mutation *of that target only*. It can neither authorize
nor block Idunn's own startup, nor any other target's lifecycle.
**Two brakes, not one.** Deployment and continuity are separate authorities
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
with separate stores, declared per target in that target's binding. A binding
naming one store for both is rejected outright — *"deployment and lifecycle
brakes are identical"*.
- *Continuity* is restarting an already-admitted installed release. Physiology.
- *Deployment* is changing an artifact, revision, configuration, schema, unit,
or authority binding.
A deployment brake does not own the former. Suspending restart actuation
requires the separately named lifecycle brake.
**Forbidden.** Idunn does not depend on a managed target, and does not repair
one merely to satisfy a deploy. If recovery would require that, the target
dependency is the thing to cut.
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
## Recipes and bindings
Two documents describe a target, and the split is the security model.
The **recipe** lives in the target's own repository at
`deployment/idunn/recipe.toml` (`gamecult.idunn.target_declaration.v1`). It
declares *capability*: build and test steps, the artifacts they produce, the
service's argument shape, its health contract, its state slots, and what it
provides. It names no host paths, no images, no privileges.
The **operator binding** lives on the host under `--bindings-dir`
(`gamecult.idunn.operator_binding.v2`). It supplies everything privileged:
which origin and ref are admitted, the pinned runner image, the workload's
roots and hardening, the runtime trust anchor, the route, the two brake
stores, rollout strategy and placement. It also declares which profiles the
target belongs to.
```toml
# recipe — in the target's repo, capability only
[[steps]]
id = "build"
phase = "build"
runner = "rust-build"
argv = ["cargo", "build", "--locked", "--release", "-p", "thing"]
[service]
executable_artifact = "thing"
arguments = [
{ kind = "literal", value = "--store" },
{ kind = "binding", name = "state_root" },
]
```
```toml
# binding — on the host, privilege only
[repository]
origin = "https://github.com/GameCult/Thing.git"
admitted_ref = "refs/heads/main"
selection = "ref-head"
recipe_path = "deployment/idunn/recipe.toml"
[brakes]
deployment_store = "/var/lib/gamecult/idunn-authority/thing-deployment-brake.cc"
lifecycle_store = "/var/lib/gamecult/idunn-authority/thing-lifecycle-brake.cc"
```
A recipe cannot grant itself an affordance: the binding must name every
affordance the recipe's runners derive, and `admit()` rejects a binding that
omits one. Unknown fields are rejected on both sides. A repository that gets
compromised can therefore change what is built, but not what it is allowed to
touch on the host.
An upstream daemon that does not speak Idunn's runtime contract is deployed
with `idunn-launch` as its executable artifact and the upstream tree as a
required adjacent artifact; the launcher finds the child inside the release that
holds it. The launcher is the unit's process: it binds the
candidate endpoint and answers Idunn's challenge there, renders the child's
config, starts a stateful child only once it holds the write lease, and signs
presence. Members never pass through it: the binding's `[route.member]` makes
Idunn route a second listener straight to the child, which binds the address
Idunn gives it in `GAMECULT_IDUNN_MEMBER_BIND`.
```toml
# recipe fragment — the upstream tree is built or fetched as an artifact
[service]
executable_artifact = "idunn-launch"
arguments = [
{ kind = "literal", value = "--child" },
{ kind = "literal", value = "upstream/bin/daemon" },
{ kind = "literal", value = "--template" },
{ kind = "literal", value = "config.tmpl" },
{ kind = "literal", value = "--health-path" },
{ kind = "literal", value = "/health" },
{ kind = "literal", value = "--health-contract" },
{ kind = "literal", value = "example.presence-launcher.v1" },
]
required_environment = [
"GAMECULT_IDUNN_RUNTIME_BUNDLE",
"GAMECULT_IDUNN_CANDIDATE_BIND",
"GAMECULT_IDUNN_MEMBER_BIND",
]
```
```toml
# binding fragment <20> members connect to the member endpoint, which Idunn
# forwards to the private port derived from the candidate: member
# private_port_start + (candidate port - route private_port_start)
[route]
stable_endpoint = "tcp://10.77.0.1:8008"
private_port_start = 18831
private_port_end = 18839
# ...
[route.member]
stable_endpoint = "tcp://10.77.0.1:8448"
private_port_start = 18841
# max_connections = 128 # nginx limit_conn on the member listener (1-65535)
```
## Contracts
Idunn's typed contracts are published by
[CultLib](https://github.com/GameCult/CultLib) in `cultnet-rs` — deployment and
lifecycle brakes, service identity, runtime activation, expected incarnation,
and the provider-health family. Idunn consumes them and defines no private
copies.
State is CultCache (`.cc` typed documents) throughout. Health is published over
CultNet RUDP.
Odin keeps its own separate projection of Idunn state for aggregation. That is
the all-seer's view, not Idunn's contract, and the two sets do not overlap.
## Running as a service
[`deploy/`](deploy/) carries a worked systemd installation:
| File | |
|---|---|
| `idunn-yggdrasil.service` | the unit |
| `idunn-yggdrasil` | operator wrapper |
| `idunn-yggdrasil.sudoers` | the narrow sudo grant the wrapper needs |
The unit runs as root under `ProtectSystem=full`,
`ProtectHome=yes`, `PrivateTmp=yes`. `ReadWritePaths` contains Idunn's own state,
the build root, and only the runtime, state, and release roots explicitly
granted to each managed target. The operator anchor, bindings, and Odin's
correlation store remain read-only. Idunn admits against operator authority —
it does not author it.
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
Note `Wants=docker.service`, not `Requires=`. Only the build step is
containerized; warm, fence, promote, drain and restart run on systemd, and
worktree-tree recipes skip Docker entirely. A hard requirement would let a
Docker outage stop Idunn itself and take daemon survival down with it.
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
Adapt the store paths for your host. Every path in `ExecStart` is explicit on
purpose; Idunn discovers no authority implicitly.
## Documentation
| | |
|---|---|
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
| [`docs/migration.md`](docs/migration.md) | **start here if you run a swarm today** — moving a target from the previous generation, in order, and cutting over the authority itself |
| [`docs/authority-map.md`](docs/authority-map.md) | authority map, repository declaration, operator binding, release foundation, promotion and continuity |
| [`docs/deployment-authority.md`](docs/deployment-authority.md) | the deployment transaction in depth |
| [`docs/signed-daemon-health-authority.md`](docs/signed-daemon-health-authority.md) | signed health admission and trust bindings |
| [`docs/guide.md`](docs/guide.md) | the **previous** generation's keepalive daemon — no longer runs anywhere, kept for migration history; its commands no longer work |
| [`deploy/legacy/`](deploy/legacy/) | the shell actuator that generation used, no longer running, and what replaced each part of it |
## Platform
Idunn targets Linux and does not compile on Windows (`drivers.rs` uses
`std::os::unix` unconditionally). A local `cargo check` on a Windows
workstation proves nothing. `scripts/verify.sh` is the verification command:
the library tests plus a Windows-target check of `idunn-host`. Run it on Linux
with the `x86_64-pc-windows-gnu` std installed. Test and build where it runs, in the same image
Odin uses, with the working tree shipped to the host's build root:
```bash
git ls-files -z | tar --null -T - -cf - | ssh ygg 'sudo rm -rf /srv/build/idunn && sudo mkdir -p /srv/build/idunn && sudo tar -x -C /srv/build/idunn && sudo chown -R gamecultadmin:gamecultadmin /srv/build/idunn'
ssh ygg 'sudo docker run --rm -v /srv/build/idunn:/w -v /etc/machine-id:/etc/machine-id:ro -v /srv/build/cargo-registry:/usr/local/cargo/registry -v /srv/build/cargo-git:/usr/local/cargo/git -w /w rust:1.95-bookworm sh -c "cargo test --lib && cargo build --release"'
```
`/etc/machine-id` must be mounted or every identity-enrolling test fails.
`--lib` because the tests under `tests/` assume the host layout. The release
binary lands in `/srv/build/idunn/target/release/idunn`; install it over
`/usr/local/bin/idunn` and restart `idunn-yggdrasil.service`.
## Licence
MIT.