Technology
Implemented12 minThe signed order protocol
The control plane never executes — it mints signed authority. A look at the self-sufficient, Ed25519-signed envelope that authorizes an assessment, how it is canonicalized, how it expires, and the fail-closed gate that checks it.
The signed envelope
An authority event travels in a three-part envelope: a protected header, the canonical event, and a signature.
SignedEnvelopeSignedEnvelope ├── header { alg: "EdDSA", typ: "rootveil-authority-event", kid: <key id> } ├── event AuthorityEvent { ...canonical signed content... } └── signature base64( Ed25519_sign(signing_input) )
- ▸alg is always EdDSA — never HMAC, never none. The verifier rejects anything else.
- ▸kid identifies the issuer’s public key, which is what lets keys rotate without downtime.
- ▸typ is fixed. The event itself carries event_id, event_type, issuer, audience, purpose, tenant, assessment, versions, issued/expires, a correlation id, and a reference-only string map as payload — never secrets.
Canonicalization: signing exact bytes
The signature covers exact bytes, so serialization must be deterministic — a JSON Canonicalization Scheme: sorted keys, no insignificant whitespace, UTF-8.
deterministic serializationcanonical(x) = json(x, sort_keys=True, separators=(",",":"), ensure_ascii=False) signing_input = canonical(header) + "." + canonical(event) signature = base64( Ed25519_sign(privateKey_in_KMS, signing_input) )
A detail that breaks signatures if omitted: non-ASCII characters are emitted as themselves, not escaped. Any non-ASCII byte in a target reference or a tenant name changes the bytes, and the engine rejects the signature — fail-closed, by design.
A self-sufficient request
The request payload carries an execution plan, the family code, and a signed execution context — a snapshot of the connection and the authorization grant, by value. Because the whole snapshot is inside the signed bytes, the engine never has to ask the control plane anything; the decision is frozen.
- ▸The plan names a target by reference — a repository or an endpoint — never a credential. The secret is a provider plus a reference, never the material.
- ▸The family code is re-validated against the catalog before signing: a family that is experimental or planned is refused as "not available to run". The front end is never trusted to have filtered.
- ▸The two dangerous opt-ins — act actively, and touch production — default to false and must be turned on by a human.
The photo never outlives the permit
A signature does not expire; the snapshot does. The event and the snapshot carry two separate windows. The event has a time-to-live; the snapshot expires at the earlier of its own time-to-live and the authorization grant’s end. So a frozen context can never outlive the permission it portrays, and the engine revalidates both when it dispatches — verifying the signature is not enough.
Authority binding: one signature, one purpose
Every event is bound to an audience and a purpose, so a signature minted for one channel cannot be replayed as another.
| Event | audience | purpose | Signed by |
|---|---|---|---|
| assessment.requested | scheduler | request | the control plane |
| assessment.approved | worker | execution | the control plane (human approval) |
| assessment.execute | worker | execution | the engine’s dispatcher — never the control plane |
Because a request is bound to the scheduler for the purpose "request", it can never be reused as an execution order to the worker. The approval and the execution order share a binding; what separates them is the event type, and the engine rejects an order that arrives on the approval channel as "not an approval".
The cryptographic gate
Before executing anything, the engine validates the event in a fixed order. Any failure rejects the event and nothing runs:
- 1Schema is valid.
- 2The algorithm is on the allow-list (never none, never the one the message chose).
- 3The issuer and key id are in the trust store.
- 4The signature verifies.
- 5The signer is neither revoked nor expired.
- 6The audience matches.
- 7The purpose matches.
- 8The event is within its freshness window — and is not a replay of an event id already seen.
The event id is claimed atomically — an insert-on-conflict, race-safe — only after every check passes. On the verified event the caller then applies business checks: tenant, assessment state, and Rules of Engagement. A valid but unschedulable plan is not rejected; it is recorded as unschedulable, and the engine never invents a slot for it (nor does the interface paint it as "clean"). Keys rotate without downtime: active, then verify-only, then revoked.
Outbox, relay and consumer
Three separate processes, each with the minimum privilege it needs and no more.
- Transactional outbox
- The signed envelope is written in the same transaction as the state change, so the database and the bus can never diverge: never published without being recorded, never lost, never duplicated.
- Relay
- A separate process with no signing-key access and its own database role. It drains the queue with a skip-locked select (so it scales to many replicas), publishes, then marks — at-least-once on purpose, with producer idempotency — and verifies the signature before publishing, so a tampered envelope is never retried.
- Consumer
- A third process with no outbox and no signing-key access. It verifies the engine’s signature against its own trust store — trusting neither the channel nor any producer — and sends anything it cannot verify, understand or apply to a dead-letter queue with a reason, while the batch continues.
Only references and numbers travel the bus, never bodies: a report reference says where the report lives, not the report. A null summary means "the engine has not reported yet" — not zero.
The state machine
assessment lifecycleDRAFT → REQUESTED → SCHEDULED → RUNNING → HITL_PENDING → COMPLETED | FAILED └─ also: UNVERIFIABLE · UNSCHEDULABLE · CANCELLED · REJECTED
The worker emits only three signed lifecycle events — started, awaiting-approval, failed. The step to a non-terminal RUNNING is bridged over SCHEDULED, which no event asserts, and the bridge is marked "deduced" in the audit. Crucially, COMPLETED is not reachable from the bus: it is minted by the control plane with a human behind it, because if the consumer accepted it as an inbound event, any producer on the topic could close an unreviewed assessment as done.
Human approval is the second and last point where authority is signed. It is bound to the report digest the engine computed, so if the analysis is re-run the digest changes and the approval no longer describes what would be delivered — and the use case refuses to sign without a digest. Segregation of duties still holds: whoever requested the assessment cannot approve it.
References
- [1]RFC 8032 — Ed25519 (EdDSA)
- [2]RFC 8785 — JSON Canonicalization Scheme
- [3]RFC 8725 — JWT Best Current Practices (algorithm confusion)
- [4]The transactional outbox pattern; Kafka at-least-once delivery
References point to public, well-known technical sources for further study.
Related articles
