The RUDP connection id 0x1d0d0001 appeared nowhere in this repository. It lived only in Odin's publisher implementation, which meant the constant defining how to reach Idunn was owned by a consumer. The three constants are now stated here: connection id, signed statement schema, and the unsigned diagnostic schema. The publishing transport moves to CultLib as a generic module that takes them as a contract, so consumers declare them against this document rather than each carrying their own copy of the transport. Recorded a discrepancy while writing it down: the wire value for the unsigned schema is idunn.daemon_health, with no version suffix, while prose in guide.md and earlier in this file says idunn.daemon_health.v1. The table gives the wire value. Reconciling the prose is a separate question about whether the wire should carry the suffix. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
255 lines
13 KiB
Markdown
255 lines
13 KiB
Markdown
# 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.
|
|
|
|
## 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.
|
|
|
|
## 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.
|