Firstboot identity
On first boot the device-agent generates a keypair — TPM-backed on a Tower, software Ed25519 in dev — derives a short claim code, and renders it as a QR + text to the console (also shown in the panel). It self-mints an opaque device token and sends only the sha256 hash to the control plane in the announce; the CP never holds the raw token, which stays in the device’s sealed store.The claim flow
1
announce — device → CP
POST /v1/devices/announce with { claim_code, device_pubkey, device_token_hash }. Idempotent on the claim code (a reboot before claim
re-announces). The device row starts unclaimed.2
owner claim — owner → CP
The owner scans the QR / opens the claim URL and claims as an authenticated
company user (
POST /v1/devices/claim/:code). This is where KYC runs (see
below). On success the CP binds the device to the company, creates a dedicated
sandbox kit for the Tower, mints the opaque device token, and flips the row
to claimed.3
poll — device → CP
The headless device polls
GET /v1/devices/claim/:code until claimed is true,
then holds its self-minted token to proceed.4
attest — device → CP
POST /v1/devices/self/attest records a PCR-7 quote (a real TPM2 quote on a
Tower; a software placeholder in dev). CP verification is a documented beta —
the quote is recorded, not yet cryptographically verified.5
provision — device → CP
POST /v1/devices/self/provision creates the operator (a tenant_user) under
the device’s kit and returns the bundle: company_id, account_kit_id,
tenant_user_id, environment: "sandbox", and a fresh per-operator
runtime_token (+ its runtime identity). Credentials are sealed into vaultd on
the box once and never re-fetched. The Tower comes up live in sandbox.{ company, tenant_user, agent_profile, environment } before asking the governor. It is opaque, rotated on
re-provision, and never leaves the box.
The relay (durable SSE)
Provider webhooks (SMS/email) terminate at the control plane, which enqueues onedevice_events row per device (a bigserial monotonic cursor). The device-agent
holds a single outbound SSE to GET /v1/devices/self/events and replays from
its persisted Last-Event-ID cursor — durable catch-up first, then live,
with a 25s heartbeat.
The device-agent persists that cursor with the kv-pg driver, so a
power-pull loses nothing: on reconnect it resumes exactly after the last id —
no gap, no duplicate. (The conformance relay test proves this: stream synthetic
events, disconnect, enqueue more, reconnect, resume clean.)
KYC
Identity verification at claim time is Footprint. It is a real gate, wired into the existing provisioning path:- When a Footprint key is configured (
FOOTPRINT_SECRET_KEY), real KYC runs. This is the only supported path on a Tower or in production. - When no Footprint sandbox key is present, the claim flow stays
structurally testable via a loud, logged, dev-only bypass:
NAIVE_DEV_KYC_BYPASS=1on the control plane. It emits a warning on every use and is refused whenNAIVE_BUILD=towerorNODE_ENV=production.