Machine identity contract v1
yovision.machine-identity/v1 defines service-to-service identity for YoVision connectors. It is deliberately separate from Sense and Bell users, GoAdmin JWT/Cookie state, database roles and operating-system accounts.
Authentication mechanism
Every request uses HTTPS and one compact Ed25519 JWS in Authorization: Bearer <token>. The protected header is closed and contains alg=EdDSA, typ=YOVISION-MACHINE+JWT, kid and ver=yovision.machine-identity/v1. The closed claims object contains:
mTLS is not the primary v1 identity mechanism. A customer PKI may add mTLS later as transport hardening, but it cannot replace or weaken the v1 principal, audience, scope, request binding, replay and revocation checks.
- one instance-specific
iss/subprincipal; - one exact service
aud; - the minimum required
scopevalues; iat,nbf,expand a single-use randomjti;- uppercase HTTP method
htm, normalized absolute-path referencehtu, and lowercase SHA-256body_sha256.
Tokens live for at most 300 seconds. Consumers allow at most 30 seconds of clock skew, verify the signature and active key/principal before authorization, then atomically consume jti until exp + skew. Retrying transport creates a new token and jti; business idempotency keys remain unchanged.
Production consumers persist the replay key (principal, jti) in their own durable store so a process restart cannot reopen the replay window. The checked-in process-local replay stores are adapter test/default primitives only; connector tasks must inject an atomic durable implementation and test restart behavior without sharing a database across products.
The v1 scopes are:
| Caller | Audience | Scope |
|---|---|---|
| Sense | yovision-brain |
source-config:write |
| Brain | yovision-sense |
runtime-status:write |
| Brain or Sense | yovision-bell |
events:ingest |
| Bell | yovision-sense |
evidence:read |
No wildcard audience or scope exists. A relay authenticates as its own transport principal and never replaces the original event producer identity.
Key lifecycle
Private Ed25519 keys are generated per product instance and stored outside the repository in an OS-protected file or secret store. Runtime configuration contains only the private-key path. Public registries are local consumer configuration, not a shared database.
Rotation first registers a new kid, switches the caller, and removes the old key after an overlap no longer than 24 hours. A disabled principal or revoked kid is rejected on every request, including tokens that have not expired. Emergency rollback disables the connector; it never enables a shared password, browser token, query token, plaintext transport or signature bypass.
Threat boundary
v1 protects against token modification, wrong audience/scope, expired or premature tokens, captured-token replay, key/principal revocation and accidental credential mixing. It does not protect a host after administrator/root compromise, a stolen usable private key before revocation, compromised TLS trust roots, endpoint implementation flaws or denial of service. Rate and body-size limits remain consumer responsibilities.
See ../transport/v1/README.md for HTTPS and request policy. Stable failures are defined in errors.md; callers and logs must expose only the stable code, principal/kid when already authenticated, and correlation ID—never the token, signature, private/public key material or complete Authorization header.
Compatibility
v1 is closed. New optional claims require all consumers to accept them before producers emit them. Any change to signing input, algorithm, claim meaning, replay semantics, maximum lifetime, audience or scope meaning publishes a new major version. Consumers keep the last accepted major during a controlled migration; rollback disables the new producer version without weakening verification.
Reproducible verification
From the repository root:
pwsh -NoProfile -File contracts/tests/machine-identity-v1/run.ps1
cd Sense/server
go test -race ./app/sense/integration/machine_identity
cd ../../Bell/server
go test -race ./app/bell/integration/machine_identity
The isolated contract test validates both JSON Schemas, the fixed Go/Python Ed25519 vector, request binding, exact audience/scope, expiry, replay, rotation overlap, revocation, bearer-only extraction and verified TLS policy. Product connector tasks remain responsible for injecting a durable replay store and testing restart recovery.