Skip to main content
A freshly-booted Tower is headless and unowned. Claiming binds it to a company, runs identity verification, provisions an operator, and hands the box a runtime token — all driven by the on-box device-agent (OSS) talking to a control plane (the region API in cloud mode).

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.
The runtime token is the identity the egress gateway resolves on every request to { 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 one device_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=1 on the control plane. It emits a warning on every use and is refused when NAIVE_BUILD=tower or NODE_ENV=production.
The bypass is never allowed in a Tower/prod build. Absent both a real key and the dev bypass, the claim endpoint returns feature_not_configured. The KYC leg is reported BLOCKED(footprint-sandbox), never faked — the never-stub invariant covers the claim flow’s structure and enforcement, not a third-party sandbox we lack a key for (see KEYS.md).