> ## Documentation Index
> Fetch the complete documentation index at: https://usenaive.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Claim & provisioning

> How a headless Tower becomes an owned, provisioned operator: the firstboot TPM keypair + claim code/QR, the announce → claim → poll → attest → provision flow, the durable SSE relay with a kv-pg cursor, and the KYC gate (Footprint; loud dev-only bypass, otherwise BLOCKED).

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

<Steps>
  <Step title="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`.
  </Step>

  <Step title="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`.
  </Step>

  <Step title="poll — device → CP">
    The headless device polls `GET /v1/devices/claim/:code` until `claimed` is true,
    then holds its self-minted token to proceed.
  </Step>

  <Step title="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.
  </Step>

  <Step title="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**.
  </Step>
</Steps>

The runtime token is the identity the [egress gateway](/docs/self-hosting/security-model)
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`**.

<Warning>
  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`](/docs/self-hosting/conformance)).
</Warning>
