Make Idunn independently usable: unit files, docs, and a public entry point
Brings across everything an operator needs to run Idunn without reference to
Odin. deploy/ carries the worked systemd installation -- unit, operator
wrapper, and the narrow sudoers grant. docs/ carries the four documents that
were scattered across Odin's tree.
The README is new and is the public entry point the repo lacked: what Idunn
decides, how to build and run it, the authority model in the form doctrine
asks for, how to install it as a service, and where the deeper documents are.
Two corrections made while moving:
Requires=docker.service becomes Wants=. Deployment runners use Docker;
continuity actuation does not. A hard requirement lets a Docker outage stop
Idunn itself and take daemon survival down with it, which is precisely the
invariant Idunn exists to hold. This is live on yggdrasil today and should be
changed there too.
The guide still told operators to run from E:\Projects\Odin -- a drive that no
longer exists -- via npm wrappers that belonged to Odin. Paths generalised,
commands moved to direct cargo invocations, and a note added that the document
predates the extraction.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 18:15:40 +02:00
|
|
|
# Idunn signed daemon-health authority
|
|
|
|
|
|
|
|
|
|
## Objective
|
|
|
|
|
|
|
|
|
|
Idunn must know which daemon authored a health statement before that statement
|
|
|
|
|
can affect lifecycle judgment or operator status. A typed packet is a shape,
|
|
|
|
|
not an identity. The current generic `idunn.daemon_health.v1` RUDP path checks
|
|
|
|
|
shape, daemon id, contract, self-declared publication source, and freshness but
|
|
|
|
|
does not authenticate the publisher. It is diagnostic input until replaced.
|
|
|
|
|
|
|
|
|
|
## Owner
|
|
|
|
|
|
|
|
|
|
- Each daemon owns its health statement and signs it with its pinned service or
|
|
|
|
|
host identity.
|
|
|
|
|
- Idunn owns admission, freshness, monotonic lineage, and the outward projection
|
|
|
|
|
of current authenticated provider health. Lifecycle judgment remains private
|
|
|
|
|
Idunn policy and is not smuggled into that provider-health statement.
|
|
|
|
|
- Root deployment configuration owns the trust binding between daemon id,
|
|
|
|
|
health contract, source runtime, and signer identity.
|
|
|
|
|
|
|
|
|
|
No renderer, transport peer, systemd unit, PID probe, caller boolean, or typed
|
|
|
|
|
but unsigned packet owns health.
|
|
|
|
|
|
|
|
|
|
Authenticated daemon health is selected before release-source inspection.
|
|
|
|
|
Release state may request a rollout of an otherwise healthy body, but a missing
|
|
|
|
|
checkout or upstream cannot replace the daemon's liveness statement or block
|
|
|
|
|
restart of an unhealthy admitted body. Continuity restart and deployment are
|
|
|
|
|
separate authorities: only the latter is gated by the deployment brake.
|
|
|
|
|
|
2026-09-06 19:27:00 +02:00
|
|
|
## Wire contract
|
|
|
|
|
|
|
|
|
|
Idunn owns these constants. A publisher supplies them to a generic transport;
|
|
|
|
|
they are not the transport's to choose, and this document is where they are
|
|
|
|
|
defined.
|
|
|
|
|
|
|
|
|
|
| Constant | Value |
|
|
|
|
|
| --- | --- |
|
|
|
|
|
| RUDP connection id | `0x1d0d0001` |
|
|
|
|
|
| Signed statement schema | `idunn.signed_daemon_health.v1` |
|
|
|
|
|
| Unsigned diagnostic schema | `idunn.daemon_health` |
|
|
|
|
|
|
|
|
|
|
The signing purpose is the signed statement schema id, so a statement signed for
|
|
|
|
|
Idunn cannot verify as another service's health. The signature and identity
|
|
|
|
|
domains — `gamecult.provider-health.signature.v1` and
|
|
|
|
|
`gamecult.provider-health.identity.v1` — are shared GameCult domains rather than
|
|
|
|
|
Idunn's, and a publisher may not vary them.
|
|
|
|
|
|
|
|
|
|
The publishing transport lives in CultLib as
|
|
|
|
|
`cultnet-ts`'s `signed-daemon-health` module. It is generic: it binds, shakes
|
|
|
|
|
hands, signs, puts the document, and waits for the ack, taking the three
|
|
|
|
|
constants above as a contract. Consumers declare that contract against this
|
|
|
|
|
document; they do not each reimplement the transport.
|
|
|
|
|
|
|
|
|
|
Note that the unsigned schema is `idunn.daemon_health`, without a version
|
|
|
|
|
suffix, while prose elsewhere in this repository says `idunn.daemon_health.v1`.
|
|
|
|
|
The wire value is the one in the table.
|
|
|
|
|
|
Make Idunn independently usable: unit files, docs, and a public entry point
Brings across everything an operator needs to run Idunn without reference to
Odin. deploy/ carries the worked systemd installation -- unit, operator
wrapper, and the narrow sudoers grant. docs/ carries the four documents that
were scattered across Odin's tree.
The README is new and is the public entry point the repo lacked: what Idunn
decides, how to build and run it, the authority model in the form doctrine
asks for, how to install it as a service, and where the deeper documents are.
Two corrections made while moving:
Requires=docker.service becomes Wants=. Deployment runners use Docker;
continuity actuation does not. A hard requirement lets a Docker outage stop
Idunn itself and take daemon survival down with it, which is precisely the
invariant Idunn exists to hold. This is live on yggdrasil today and should be
changed there too.
The guide still told operators to run from E:\Projects\Odin -- a drive that no
longer exists -- via npm wrappers that belonged to Odin. Paths generalised,
commands moved to direct cargo invocations, and a note added that the document
predates the extraction.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 18:15:40 +02:00
|
|
|
## Inputs
|
|
|
|
|
|
|
|
|
|
The daemon-authored statement binds:
|
|
|
|
|
|
|
|
|
|
- exact daemon id and health contract;
|
|
|
|
|
- source runtime id and signer identity;
|
|
|
|
|
- observation time, publisher incarnation, and monotonic sequence;
|
|
|
|
|
- bounded health state and detail;
|
|
|
|
|
- release id, release witness, source revision, and deployment request when the
|
|
|
|
|
managed target is release-bound;
|
|
|
|
|
- `private_state_exposed=false`;
|
|
|
|
|
- an Ed25519 signature over the complete unsigned statement under a
|
|
|
|
|
domain-separated signing purpose.
|
|
|
|
|
|
|
|
|
|
The Idunn trust binding names the only public key allowed to speak for that
|
|
|
|
|
daemon/contract/runtime tuple and whether release identity is required.
|
|
|
|
|
|
|
|
|
|
Yggdrasil Odin uses the generic signed contract with daemon
|
|
|
|
|
`yggdrasil-odin`, source runtime `odin-yggdrasil`, and a root-provisioned
|
|
|
|
|
Ed25519 key readable only by the Odin runtime group. Its health signature owns
|
|
|
|
|
liveness. Exact Odin and CultLib revisions remain deployment authority in the
|
|
|
|
|
immutable release and `/srv/odin/deploy/deployment.env`; neither Docker health
|
|
|
|
|
nor the signed liveness statement may rewrite that witness.
|
|
|
|
|
|
|
|
|
|
## Outputs
|
|
|
|
|
|
|
|
|
|
Idunn persists two separate current facts per daemon:
|
|
|
|
|
|
|
|
|
|
1. one admitted daemon-authored statement, keyed by daemon id, plus its exact
|
|
|
|
|
digest in the admission;
|
|
|
|
|
2. an Idunn-authored `idunn.authenticated_provider_health_projection.v1`
|
|
|
|
|
record binding the trust record, admission, exact provider lineage, and
|
|
|
|
|
freshness interval.
|
|
|
|
|
|
|
|
|
|
Signed heartbeats are current liveness state, not an append-only event ledger.
|
|
|
|
|
Advancing a daemon's publisher sequence atomically replaces that daemon's prior
|
|
|
|
|
statement, admission, and compatibility health row. Startup atomically compacts
|
|
|
|
|
legacy digest-keyed statement history to the one exact statement named by each
|
|
|
|
|
current admission. A missing or digest-mismatched witness fails closed; it is
|
|
|
|
|
never reconstructed from process probes or a later heartbeat.
|
|
|
|
|
|
|
|
|
|
The surrounding keepalive store follows a separate bounded-retention policy.
|
|
|
|
|
It keeps exact current authority plus small per-daemon diagnostic windows;
|
|
|
|
|
historical pulse decisions, repeated alarms, and superseded actuation receipts
|
|
|
|
|
cannot make authentication fsync cost grow with host age. The current
|
|
|
|
|
deployment head and every record required to rejoin active signed health remain
|
|
|
|
|
pinned through compaction.
|
|
|
|
|
|
|
|
|
|
The outward record exists only while a generic provider-authored admission is
|
|
|
|
|
current and still matches its root trust and optional release binding. Missing
|
|
|
|
|
publication, release drift, dependency failure, target absence, and recovery
|
|
|
|
|
policy are absence of this projection. They are not synthetic provider states.
|
|
|
|
|
The projected state is exactly the authenticated provider state. Its only
|
|
|
|
|
explanation is a closed Idunn-generated reason code paired to that state:
|
|
|
|
|
`authenticated_provider_active`, `_warming`, `_degraded`, or `_failed`.
|
|
|
|
|
There is no free-text detail field.
|
|
|
|
|
|
|
|
|
|
Consumers such as Epiphany Discord Status receive only the outward projection
|
|
|
|
|
through CultNet/CultMesh and verify Idunn's pinned identity. Reading Idunn's
|
|
|
|
|
private store is not a consumer protocol.
|
|
|
|
|
|
|
|
|
|
The query transport is an explicit, read-only CultNet/RUDP listener enabled by
|
|
|
|
|
`--public-health-query-bind <addr>`. It has no default and is valid only with
|
|
|
|
|
`--swarm-profile`, `--service-identity-store`, and `--public-health-store`.
|
|
|
|
|
The listener opens only the dedicated public store and derives its allowlist
|
|
|
|
|
from the configured target catalog's daemon/health-contract pairs. It returns
|
|
|
|
|
the exact stored projection payload bytes with fixed source runtime
|
|
|
|
|
`idunn-daemon` and role `authenticated-provider-health-projector`. It has no
|
|
|
|
|
handle to Idunn's private stores and accepts no mutation message.
|
|
|
|
|
|
|
|
|
|
## Derived state
|
|
|
|
|
|
|
|
|
|
- Process and systemd observations are diagnostic evidence only.
|
|
|
|
|
- RUDP source endpoints and connection ids are transport routing only.
|
|
|
|
|
- `publication_source`, `transport`, `ready`, and `healthy` strings are display
|
|
|
|
|
data unless contained inside a valid signed statement.
|
|
|
|
|
- An unsigned legacy health packet may be retained temporarily for diagnostics
|
|
|
|
|
but cannot satisfy managed health, prevent recovery, or produce a healthy
|
|
|
|
|
outward projection.
|
|
|
|
|
|
|
|
|
|
## Forbidden writers
|
|
|
|
|
|
|
|
|
|
- The inbound packet cannot choose its own trust anchor.
|
|
|
|
|
- A daemon cannot change its configured daemon id, contract, or source runtime
|
|
|
|
|
by signing different values.
|
|
|
|
|
- Idunn cannot manufacture daemon-authored health from PID, executable,
|
|
|
|
|
container, HTTP, systemd, or command exit observations.
|
|
|
|
|
- Epiphany, Bifrost, VoidBot, Eve, and Discord cannot infer Idunn acceptance
|
|
|
|
|
from a sent packet.
|
|
|
|
|
- A generic typed `idunn.daemon_health.v1` packet cannot fall back into
|
|
|
|
|
lifecycle authority when signature verification fails or trust is absent.
|
|
|
|
|
|
|
|
|
|
## Shared paths
|
|
|
|
|
|
|
|
|
|
Every managed daemon uses the same verification primitive:
|
|
|
|
|
|
|
|
|
|
`signed statement -> root trust binding -> signature and tuple verification ->
|
|
|
|
|
monotonic admission -> target/deployment join -> Idunn outward projection`
|
|
|
|
|
|
|
|
|
|
Epiphany's deployed `epiphany.idunn_signed_runtime_health.v0` path is migration
|
|
|
|
|
evidence and the first working implementation of the signature half. It is not
|
|
|
|
|
the permanent excuse for a service-specific verifier. The generic contract
|
|
|
|
|
must absorb its invariants before the special path is deleted.
|
|
|
|
|
|
|
|
|
|
## Cut line
|
|
|
|
|
|
|
|
|
|
1. Add generic signed-health and trust-binding contracts.
|
|
|
|
|
2. Make generic Idunn admission verify the pinned tuple, signature, sequence,
|
|
|
|
|
incarnation, freshness, and release requirements.
|
|
|
|
|
3. Migrate Epiphany and Bifrost publishers and install their root-owned trust
|
|
|
|
|
bindings.
|
|
|
|
|
4. Publish and verify Idunn's outward signed authenticated-provider-health
|
|
|
|
|
projection using the fixed CultNet signature profile
|
|
|
|
|
`idunn.authenticated-provider-health-projection.v1`.
|
|
|
|
|
5. Point Discord Status at the outward projections.
|
|
|
|
|
6. Delete generic unsigned health from managed-health authority and delete the
|
|
|
|
|
Epiphany-only verifier after its last publisher migrates.
|
|
|
|
|
|
|
|
|
|
The rebuild is incomplete while an unsigned packet can keep a managed daemon
|
|
|
|
|
healthy or while a status consumer must inspect Idunn's private store.
|
|
|
|
|
|
|
|
|
|
## Implemented cut (2026-07-19)
|
|
|
|
|
|
|
|
|
|
The deployed-lineage source now has the first executable generic admission
|
|
|
|
|
path. `idunn.signed_daemon_health.v1` is verified against an exact root-store
|
|
|
|
|
daemon/contract/runtime binding, the signer identity is derived from the bound
|
|
|
|
|
public key, the Ed25519 signature covers the positional empty-signature
|
|
|
|
|
statement under the domain-separated document purpose, and Idunn atomically
|
|
|
|
|
persists the signed statement, its generic admission, and the compatibility
|
|
|
|
|
health row. Managed-health selection rejoins all three with the current root
|
|
|
|
|
binding on every read; trust rotation invalidates old health. Sequence,
|
|
|
|
|
incarnation, observation, freshness, and optional deployment lineage advance
|
|
|
|
|
under CAS.
|
|
|
|
|
|
|
|
|
|
Unsigned `idunn.daemon_health.v1` packets now persist only as
|
|
|
|
|
`idunn.unsigned_daemon_health_diagnostic.v1` under `diagnostic:<daemon>`.
|
|
|
|
|
They cannot overwrite the daemon health key, create an admission, reset the
|
|
|
|
|
missing-publication clock, suppress recovery, or produce authenticated health.
|
|
|
|
|
The evaluation clock is passed through the whole target cycle instead of being
|
|
|
|
|
resampled inside the health selector.
|
|
|
|
|
|
|
|
|
|
Idunn now also has the local outward projection writer. One publisher opens an
|
|
|
|
|
already-enrolled Idunn service identity before workers start, captures an
|
|
|
|
|
incarnation-stable root trust snapshot, and writes only
|
|
|
|
|
`idunn.authenticated_provider_health_projection.v1` envelopes to a distinct
|
|
|
|
|
public store. Each write rejoins the exact health, admission, signed statement,
|
|
|
|
|
root binding, and optional current deployment lineage. The signed positional
|
|
|
|
|
record has a fixed hash-derived key, a store-monotonic sequence, a process UUID,
|
|
|
|
|
and an expiry capped by the provider observation's managed-health silence
|
|
|
|
|
window. Missing, legacy, unsigned, drifted, or mutated input cannot write,
|
|
|
|
|
refresh, or delete a projection; an old row simply expires for consumers.
|
|
|
|
|
|
|
|
|
|
The public projection has a dedicated multi-peer CultNet/RUDP snapshot
|
|
|
|
|
listener. Startup synchronously binds the explicit address and validates the
|
|
|
|
|
public store before target workers start. Bind failure or public-store
|
|
|
|
|
contamination is fatal startup evidence. Each request rereads only that store
|
|
|
|
|
and passes exact records through CultNet's read-only snapshot server with the
|
|
|
|
|
target-derived `(schema,key)` allowlist. Unknown schema/key requests return no
|
|
|
|
|
record. Malformed datagrams or messages are refused per peer without killing
|
|
|
|
|
the daemon. No request can write, delete, import, or inspect private Idunn
|
|
|
|
|
state.
|
|
|
|
|
|
|
|
|
|
This source is deliberately not deployable yet: Bifrost and the remaining
|
|
|
|
|
generic publishers still emit the unsigned diagnostic contract, production
|
|
|
|
|
root trust and Idunn identity stores have not been installed, and Epiphany
|
|
|
|
|
still uses its signed v0 migration path. The query transport and Epiphany
|
|
|
|
|
consumer verifier now exist in source, but neither makes an unmigrated
|
|
|
|
|
publisher authoritative. Deploying this intermediate source would correctly
|
|
|
|
|
classify unmigrated daemons as missing health and could therefore actuate
|
|
|
|
|
recovery. Publisher migration and trust enrollment must land before promotion.
|
|
|
|
|
|
|
|
|
|
## Verification layer
|
|
|
|
|
|
|
|
|
|
Hostile tests must prove:
|
|
|
|
|
|
|
|
|
|
- valid shape with no signature is not admitted;
|
|
|
|
|
- wrong key, daemon, contract, runtime, release, deployment, or trust record is
|
|
|
|
|
not admitted;
|
|
|
|
|
- replay, sequence rollback, time rollback, incarnation substitution, and
|
|
|
|
|
future/stale observations are not admitted;
|
|
|
|
|
- a live PID, existing executable, green HTTP response, or generic `ready`
|
|
|
|
|
string cannot repair failed authentication;
|
|
|
|
|
- one daemon's trust record cannot authenticate another daemon;
|
|
|
|
|
- unsigned legacy health cannot suppress recovery or yield outward healthy;
|
|
|
|
|
- outward provider health is signed by Idunn under the fixed, typed purpose and
|
|
|
|
|
binds the exact admitted statement;
|
|
|
|
|
- missing health, release drift, and dependency failure cannot write or refresh
|
|
|
|
|
the projection; any prior row expires instead of being rewritten as an
|
|
|
|
|
Idunn-authored provider state;
|
|
|
|
|
- caller-selected signing purposes, signer ids, reason strings, and synthetic
|
|
|
|
|
states fail closed;
|
|
|
|
|
- private detail and filesystem/process internals never enter the operator
|
|
|
|
|
projection.
|
|
|
|
|
- repeated signed heartbeats keep exactly one signed statement per admitted
|
|
|
|
|
daemon rather than growing the monolithic keepalive store;
|
|
|
|
|
- startup compaction keeps only the exact statement named by each admission,
|
|
|
|
|
is idempotent, and loses cleanly to a concurrent whole-store mutation rather
|
|
|
|
|
than overwriting it.
|