
How an agent running in Daevix gets an identity, and how that identity is
authenticated. This is the design that the enclave data plane (broker, network
gateway, LLM gateway, IMDS) is built to enforce.

Read [`architecture.md`](architecture.md) first for the controlplane/enclave
split and the sovereignty invariants this builds on.

## Threat model

An agent is **arbitrary, untrusted code**. We schedule a customer's workload and
must assume it is hostile: it can read its own filesystem and environment,
inspect its own memory, make syscalls, and try to reach the network. The
platform exists to *observe and govern* that workload, so the workload has a
direct incentive to escape observation.

The single consequence that shapes everything below:

> **An agent must never hold a credential, because a credential an agent holds
> is a credential an attacker who owns the agent can exfiltrate and replay.**

## The core inversion: attribute, don't authenticate

The obvious approach is to **authenticate** the agent: hand it a single-use
bootstrap token (delivered in an env var), exchange that for a rotating refresh
token plus short-lived JWT/mTLS credentials, and make the root of trust
*something the agent presents*. That is exactly the thing that does not survive
the threat model — the bootstrap token, the refresh token on disk, and the JWT
are all extractable by the code they are meant to authenticate, and a
compromised agent simply *is* that identity until the TTL lapses.

Daevix does not authenticate the agent at all. It **attributes** every action
to an agent using properties of *where the agent runs*, enforced by a layer
**below** the agent that its code cannot reach — the kernel, the CNI datapath,
the hypervisor/TEE. The agent presents **nothing**.

This is the AWS instance-metadata analogy: a VM does not authenticate to
`169.254.169.254`; the hypervisor knows which VM is calling because of the path
the request took, not because the VM presented a secret. The network fabric
attributes a VM's traffic to it because the fabric stamps and validates the
source beneath the guest. We reproduce that with three enforcement layers.

## Invariants

1. **Zero standing agent secrets.** No long-lived credential is ever placed in
   an agent's environment, filesystem, or memory. Identity is ambient.
2. **Attribution below the agent.** Every "who is this" decision is made from a
   signal the agent's code cannot forge: the socket it is mounted, the Cilium
   identity on its packets, or the TEE measurement of its guest.
3. **Total mediation — no uninspected egress.** Every outbound connection an
   agent makes is terminated, inspected, and policy-applied at an enclave
   gateway, or it is denied. This is protocol-complete: a protocol the gateway
   cannot inspect is refused, never forwarded opaquely. TLS-over-TCP is
   MITM-terminated (leaf forged from the injected CA); **UDP/QUIC (HTTP-3) is
   likewise only permitted by terminating it** — a QUIC-terminating MITM using
   the same forged-leaf trust — and anything we cannot terminate (arbitrary UDP,
   other L4) is default-denied and audited. Direct or uninspected egress is not
   expressible from inside the namespace; it is dropped by the datapath, not by
   agent cooperation. This "inspect everything or deny it" rule is the platform's
   core containment guarantee.
4. **Bring your own image.** The agent's application image is arbitrary and
   never modified. All trust machinery is supplied by the Daevix-controlled
   runtime and substrate, never by requiring a specific base image or an
   embedded SDK.
5. **Validity equals liveness.** An agent's identity exists exactly as long as
   its pod is provisioned. There is no secret to rotate and none to revoke;
   tearing down the pod removes the identity in the same operation.
6. **Mediation runs outside the agent's VM.** Every identity and mediation
   function (IMDS, gateways, broker) is a trusted Daevix instance in the
   enclave's trusted plane, never a sidecar inside the agent's Kata VM - the
   boundary it exists to enforce.

## The three enforcement planes

```
                          enclave (data plane)
   ┌───────────────────────────────────────────────────────────────┐
   │  broker ── provisions ──▶ per-agent namespace (Kata pod)        │
   │    │                        ┌───────────────────────────────┐   │
   │    │  Plane 3: TEE          │  arbitrary agent image (BYO)  │   │
   │    │  attest guest at       │                               │   │
   │    │  provisioning          │   ┌── mounts ──┐               │   │
   │    ▼                        │   ▼            │ egress only   │   │
   │  socket→agent map           │  IMDS unix   Cilium default-  │   │
   │  Cilium id→agent map        │  socket      deny → gateways   │   │
   │                             └───┼────────────────┼──────────┘   │
   │              Plane 1: attribute │   Plane 2: attribute          │
   │              by which socket    │   by Cilium identity          │
   │              ▼                  │   ▼                           │
   │           IMDS service        network gateway / LLM gateway     │
   │           (metadata,          (TLS-terminate, inspect, govern,  │
   │            per-req tokens)     inject egress credentials)       │
   └───────────────────────────────────────────────────────────────┘
```

### Plane 1 — Local metadata: attribution by the mount

The broker creates **one IMDS unix socket per agent** on the host
(`/run/dvx/agents/<agent-uid>/imds.sock`) and bind-mounts *only that one* into
*that one* pod at the well-known path (`/run/dvx/imds.sock`). Identity is
**which listener the connection arrived on**: the broker owns the
socket→agent mapping, and the agent cannot see or reach any other agent's socket
(pod/VM isolation, plus only its own socket is mounted). The agent asks "who am
I?" and the IMDS answers from the mapping — the agent presented nothing.

`SO_PEERCRED` is *not* the primary signal. Across the Kata VM boundary
(virtio-fs / vsock) host-side peer pids and uids are virtualized and largely
meaningless. Peercred is kept only as an optional defense-in-depth sanity check;
**the socket identity is the mount.**

The IMDS unix socket is in-namespace local IPC, not network egress, so the
default-deny egress policy (Plane 2) does not apply to it. Local metadata and
network traffic are deliberately governed by different mechanisms.

### Plane 2 — Network: attribution by Cilium identity

Every agent runs in **its own namespace** with **Cilium default-deny egress**.
The only permitted route out is to the enclave network gateway (and the LLM
gateway for model traffic). The agent's arbitrary code cannot route a packet
anywhere else — the eBPF datapath drops it. There is no "forgot to proxy"
failure mode because direct egress is not expressible from inside the namespace.

The same eBPF datapath that forces the path also carries the pod's Cilium
**security identity**, derived from its k8s identity and validated in-kernel,
unforgeable by the workload. So the gateway attributes every connection to an
agent *ambiently* — no token, no per-request auth from the agent. **The layer
that forces the path is the layer that names the agent**: mediation and identity
are one mechanism, not two.

**How attribution is wired (implemented).** East-west traffic is not
masqueraded, so the gateway sees each connection's real pod IP as the TCP
source. `internal/enclave/attribution` lists **CiliumEndpoint** objects (stdlib
REST over the in-cluster ServiceAccount - no client-go) and maintains pod-IP ->
{agent labels, numeric security identity}. The gateway joins the source IP to
that index and audits `actor="agent:<id>"` with the org and the Cilium
`security_id`. Today one node-local gateway serves many agents, so this join is
on the path; per-agent gateways would make it implicit.

**The enforcement seam (bd_2_0-336, implemented).** Every terminated flow - TCP
and QUIC alike - is evaluated by a single `PolicyEngine.Evaluate(EgressRequest)
-> Decision` seam (`internal/enclave/gateway/policyengine.go`) using the
*attributed identity*, not just the source IP, and the verdict is audited as a
structured decision (`policy_scope`, `policy_reason` code, matched
`policy_rule`). Because mediation and identity are one mechanism, the default
engine **fails closed on identity**: when attribution is in force, an egress
that resolves to no agent is *denied* with reason `no_identity` (not merely
audited by IP) - we will not govern a flow we cannot name. The default
`staticEngine` is the degenerate case (the `DVX_ALLOW_DESTS` default-deny
hostname allowlist); the layered governance engine (below) plugs into the same
seam without touching the request path.

**Layered governance policy (bd_2_0-apa, implemented).** Policy is authored at
four scopes, broad -> specific: **Platform -> Org -> Group -> Agent**. Platform
is the operator running the controlplane - in a multi-tenant SaaS, a *different*
principal from any tenant - so platform policy is the safety authority a tenant
cannot escape. Group is a subdivision within a tenant (a team, environment, or
project) that the old design lacked. A more-specific scope OVERRIDES a broader
one unless the broader one LOCKS the setting; agents can never lock (a new
`dvx.io/group-id` pod label carries the group into `AgentIdentity`). Two
composition laws (`internal/policy`, a pure resolver shared by both planes):
scalar settings resolve by lock-aware most-specific-wins (broadest lock pins);
destination policy **unions DENY across every scope always** (a hard floor no
lower scope can remove - a platform deny binds with no lock needed) while ALLOW
follows most-specific-wins/lock, and the decision is deny-trumps-allow then
default-deny. The controlplane stores this policy, builds a `policy.Snapshot`,
and **publishes it out-of-band** (`internal/controlplane/govpolicy`, mirroring
attestation reference-values): only policy metadata crosses, in the CP→enclave
direction, never sovereign data (invariant 2). The gateway's `layeredEngine` runs the *same* resolver
over that snapshot locally (hot-reloaded), so a QUIC decision and a TCP decision
are governed identically. Authoring is `dvx policy destination --org/--group/
--agent [--lock]`, which refuses an agent lock and refuses to author a key a
higher scope has locked.

*One controlplane, many enclaves (bd_2_0-apa.9).* A single controlplane serves
many enclaves, so the snapshot is scoped **per enclave**: each enclave gets the
platform floor plus only the org/group/agent policy for the single org it serves,
so no org's rules ever land in another org's sovereign enclave. An enclave serves
exactly one org (`bd_2_0-j9r`): `enclave.owning_org_id` is required and authored
at registration (`dvx internal register --owning-org`, or `dvx enclaves
register-broker --owning-org`); an org may own many enclaves. It is deliberately
*not* derived from agent placement: the controlplane stays agent-agnostic
(enforcement and provisioning are enclave-local), which avoids pulling agent
inventory up into the management plane. `dvx serve controlplane
--policy-publish-dir` fans a full re-publish out to `<dir>/<enclave_id>.json`, one
scoped file per enclave; because every file carries the platform floor and an
org's rows land only in the files of enclaves owned by that org, a platform change
reaches every enclave and an org change reaches only that org's — no incremental
event routing. Those per-enclave files back the controlplane's authenticated
**pull** endpoint: each enclave's gateway runs a `policy-pull` sidecar that holds
an SSE stream for a `refresh` nudge and pulls its own scoped file (bd_2_0-apa.10)
— the sole delivery path, since enclaves run in the customer's cluster where a
ConfigMap could never reach them (bd_2_0-apa.15). This is permitted because a
snapshot is platform-owned metadata (not sovereign data): invariant 2 forbids
sovereign data leaving the enclave, not the enclave fetching platform metadata.
For air-gapped enclaves the same file may be delivered at rest by any out-of-band
channel. Either way the controlplane never dials the enclave and never sees
sovereign data. There is no request-time served-org gate (the `bd_2_0-apa.12`
guard was retired with shared enclaves): a foreign-org agent simply finds no allow
rows and hits default-deny — no cross-org reach, enforced by the same resolver.

### Plane 3 — Provisioning root: the Kata TEE measurement

Planes 1 and 2 both rest on a mapping — *this socket / this Cilium identity ==
agent N of org M* — established at the one moment the workload is trustworthy:
when the enclave **provisions** the pod. The agent never participates in its own
enrollment, so there is no bootstrap secret to steal.

That mapping is anchored in hardware. Before the broker mints the per-agent
socket or installs the Cilium-identity→agent mapping, it requires a **Kata TEE
remote-attestation report** (SEV-SNP / TDX / vTPM) and verifies the guest's
measurement against expected reference values. Only a pod whose measured guest
matches gets a socket and a mapping. The ambient "who" is rooted in a hardware
signature over the actual running guest, not merely in "the broker asserts it."

**What is measured (and why BYO image still works).** The reference values cover
the **Daevix-controlled Kata guest and injected runtime** — the guest kernel,
the guest agent, and the Daevix runtime layer that installs the CA, mounts the
socket, and wires egress. They do **not** cover the customer's application image,
which is arbitrary and unmeasured *by design*. We do not attest the payload; we
attest the **envelope** the payload runs inside. That envelope is fixed and
Daevix-built, so stable reference values exist even though the agent image is
unknown.

**Boundary note.** Attestation *policy* (acceptable reference values / allowed
guest measurements) is an identity concern the controlplane owns, but sovereign
data never leaves the enclave and the controlplane never sees it. Attestation
evidence is a measurement, not sovereign data. So: the controlplane
**publishes** reference values and the enclave **verifies** the report locally at
provisioning time. Reference values reach the enclave over the same CP→enclave
**metadata channel** governance policy uses — a `refvalues-pull` sidecar next to
`serve imds` holds an SSE stream and pulls this enclave's reference-values
policy over the authenticated endpoint (`internal/enclave/cpsync`, bd_2_0-4ef),
writing the file the attestation gate re-reads per provision. Verification stays
enclave-local; only policy crosses, and it crosses the allowed direction (per
architecture invariant 2).

**Requested isolation class vs discovered assurance tier (bd_2_0-cwnz.6).**
Plane 3's assurance tier is *discovered*: what the guest actually PROVED at
attestation, minted into the credential and gateable by floors. It is distinct
from the enclave's **requested isolation class** (`internal/isoclass`): the
operator-authored, provision-time, structural envelope for the whole enclave
(`plain < vm < node-cvm < per-agent-tee`; class describes what boundary
CONTAINS the agent, tier what the guest PROVED). The broker enforces the two
honestly:

- The broker-side **IsolationGate is exact-class-or-refuse** (fail-closed): a
  driver provisions only a class it can satisfy exactly — no silent downgrade
  and no silent upgrade; ordering is used only by assurance floors. A missing
  (never authored) or unknown class is refused, never guessed to a weaker
  default.
- Because the class is per-enclave, an unsatisfiable class stalls ALL
  provisioning for the enclave — so the refusal is **operator-queryable
  status** on the broker's operator API (`GET /api/v1/status/isolation`)
  naming the mismatch (requested=X, driver-capability=Y), never just a log
  line.
- The invariant **assurance ≤ class** holds always: a guest cannot have proven
  a stronger boundary than the envelope it was provisioned into; a violation
  fails loudly (a bug, never absorbed). A class stronger than the attested
  tier is fine by design (a TEE present but unverified). Deriving assurance
  FROM the class record is forbidden — what was requested is never evidence of
  what was proved. A `vm`-class enclave (e.g. the local micro-VM driver)
  honestly carries assurance `none` under the SAMPLE attestation gate.
- **The class travels as its OWN claim (never an overwrite of the tier):** the
  broker authors the enclave's class onto each AgentMapping
  (`spec.identity.isolationClass`), and the node-local SVID mint carries it as
  a DISTINCT dvx-extension claim beside the attested tier
  (`agentid.CredClaims.Class` / `agentcert.CredClaims.Class`; the SVID verify
  paths on both planes require BOTH claims well-formed). The agent-operator
  JWT likewise carries `class` beside the attested `tier`, and the
  echoverify/llmproxy surfaces report both. On the dev cluster a kata-class
  agent therefore shows `class=node-cvm, tier=none` — what was asked, and
  what was proved, in two separate claims. Minting fails closed on a
  missing/unknown class and loudly on the assurance > class violation.

## Enclave decomposition: the trusted plane

The enclave is not one process; it is a set of Daevix-owned components, and the
line that organizes them is the **agent's Kata VM boundary**:

- **Inside the agent's Kata VM:** only the untrusted, bring-your-own agent image
  plus a **minimal, measured** Daevix runtime shim (CA injection, egress
  redirect setup). Nothing here sees plaintext, holds a key, or makes a policy
  decision. This is the only code the TEE measurement (Plane 3) needs to cover.
- **Outside the VM, in the enclave's trusted plane:** IMDS serving, the network
  gateway, the LLM gateway, and the broker. These see plaintext, hold the
  gateway CA private key, mint tokens, and write the audit stream.

The realization that decides placement: the Kata VM exists to contain untrusted
**customer** code; the gateways are trusted **Daevix** code. They need isolation
*from the agent*, not *from Daevix* - so they do not run in a Kata VM and,
crucially, **never as a sidecar inside the agent's pod.** A mediation component
inside the boundary it is meant to mediate is no boundary at all: a compromised
agent sharing its VM could read the CA key, forge its own audit, or disable its
own governance.

### Per-agent, out-of-VM instances

Identity and mediation functions are provisioned **per agent, as dedicated
instances outside the agent's VM** (a separate trusted pod with its own network
namespace, to which the agent's egress is routed). This makes the whole platform
one pattern:

> Every identity and mediation function is a dedicated instance the enclave
> owns; *which instance* handled the request **is** the identity, and the agent
> presents nothing.

That is the same trick as the per-agent IMDS socket (Plane 1), now applied to
the gateways: a gateway that serves exactly one agent needs no attribution
lookup - identity is implicit in the pairing, so Planes 1 and 2 collapse into
one idea. It also bounds blast radius: an instance only ever sees one agent's
traffic, so no shared component holds every agent's plaintext.

The density fallback, if one instance-set per agent proves too costly, is a
**per-node daemon** (one gateway DaemonSet per node, serving all agent pods on
that node, still outside every agent's VM). That trades implicit attribution for
the Cilium-identity join (Plane 2) - which is why that join stays in the design
even though the per-agent placement would not strictly need it.

### Node-local data path vs. HA control

The components scale differently, so they deploy differently:

- **Node-local (DaemonSet):** IMDS socket serving is node-local *by physics* - a
  hostPath unix socket can only be served by a process on the same node as the
  agent pod (exactly as a cloud IMDS is answered by the local host, never a
  central service). This tier is never centralized; it is present on every node.
- **HA control:** the broker's provisioning, attestation verification, and
  identity issuance are stateless-over-shared-state and run as multiple replicas
  (leader election, or optimistic writes keyed by agent UID).

For HA to be safe, the socket->identity **registry must be durable and watched,
not in-memory**: the authoritative mapping lives in shared enclave state (the
enclave database, or a Kubernetes CRD / pod annotation written at provision
time), and each node's IMDS daemon *watches* it to serve its local sockets. A
broker replica failing then changes nothing on the data path. (The in-memory
`internal/enclave/registry` is the node-local cache tier of this; its source of
truth is external.)

## Total mediation and TLS termination

Governance and observability require the gateway to see plaintext, so it
**TLS-terminates** agent egress:

1. A **per-agent gateway CA** is trusted by the agent's TLS stack (see "CA
   injection" below).
2. Egress is transparently redirected to the network gateway (belt: Cilium
   default-deny; suspenders: redirect, not reliance on `HTTPS_PROXY`, which
   arbitrary code can unset).
3. The gateway forges a leaf certificate per destination, decrypts, **inspects
   and applies governance policy** (allow/deny/redact on destination and
   payload), then **re-originates TLS upstream** to the real destination.

The **LLM gateway** is the same pattern specialized for model APIs — it
terminates and inspects prompts and completions for the same observability and
governance reasons.

**How the redirect is wired (implemented).** Step 2 is not `HTTPS_PROXY`; it is
an eBPF-datapath redirect the agent cannot observe or bypass. A
`CiliumNetworkPolicy` on `app=agent` (`deploy/policy/agent-egress-redirect.yaml`)
redirects **all** agent TCP (`port "0"`) into Cilium's embedded Envoy
(`CiliumClusterwideEnvoyConfig`, `deploy/enclave/gateway-envoy-config.yaml`),
which forwards each connection to the node-local `dvx-gateway`
(`internal/enclave/gateway`, a DaemonSet outside the agent's VM) over a **Unix
socket** carrying a **PROXY-protocol v2** header. The Unix socket is mandatory:
Cilium's Envoy spoofs the upstream source to the agent's pod IP
(`IP_TRANSPARENT`), which would misroute a TCP gateway's return path. The PROXY
header is how the agent's **original destination** and its **real pod IP** (the
unforgeable "who", joined to the Cilium identity in the attribution provider)
survive the redirect. Any egress rule flips the endpoint to Cilium default-deny,
so DNS and the redirect are the *only* reachable paths — lateral and direct
egress are dropped by elimination. Step 3 (TLS termination + destination policy)
is implemented on top of this datapath: the gateway forges a leaf per SNI,
governs by a default-deny hostname allowlist, and re-originates validated TLS
upstream (see "CA injection" below). See `deploy/kind/README.md` for the live
proof.

## CA injection without owning the image

Daevix lets customers run **any** container image: the Daevix runtime injects
the CA at runtime rather than requiring a Daevix base image (invariant 4).

The CA certificate is the one piece of trust machinery that must live *inside*
the agent's filesystem view (its TLS libraries have to trust the gateway leaf).
It is placed there by the **Daevix runtime layer**, not by the customer:

- The runtime overlays / injects the CA into the standard trust locations and
  points TLS stacks at it (e.g. a read-only mount plus the conventional
  environment such as `SSL_CERT_FILE`, `REQUESTS_CA_BUNDLE`,
  `NODE_EXTRA_CA_CERTS`), from a source the agent cannot swap out.
- Because the CA distribution rides the Daevix runtime, it is part of the
  **measured envelope** (Plane 3), which ties trust distribution back to the
  attestation anchor.
- Only the CA *certificate* (public) is ever in the agent's view; the CA
  *private key* never leaves the gateway.

An agent that removes or ignores the CA only breaks its own TLS — it still
cannot reach anything directly, because egress is default-denied to everything
but the gateway (invariant 3). The CA is what makes mediated egress *usable*,
not what makes it *enforced*.

**How injection is wired (implemented).** An **init container running the
agent's own image** (so it copies that image's own trust roots) merges the
gateway CA into a trust bundle in a shared volume, which is mounted back **over
`/etc/ssl/certs`** in the main container; the belt-and-suspenders env vars above
point runtimes that read them at the same bundle. The agent image is unmodified.
See `deploy/agents/demo-agents.yaml` and, on the gateway side, the leaf-forging
`internal/enclave/gateway` (`certstore.go` forges a per-SNI leaf; `mitm.go` /
`gateway.go` terminate agent TLS, apply the destination allowlist, and
re-originate a validated TLS connection upstream). Proven live: an unmodified
`curl` agent reaches an allowlisted host over a gateway-forged leaf (HTTP 200),
while a non-allowlisted host is cut after the handshake — see
`deploy/kind/README.md`.

## Credentials for external systems

Some calls genuinely need a real bearer credential at the external boundary
(you cannot ambient-auth Google). The agent still never holds one:

- **Preferred — inject at egress.** Because all egress is already terminated at
  the gateway, the gateway attaches the short-lived, minimally-scoped cloud
  credential on the way out. The agent may never fetch or hold one.
- **Fallback — per-use via IMDS.** For SDKs that must sign client-side, the
  agent fetches a fresh, minimally-scoped, seconds-to-minutes token from its
  IMDS socket (ambiently identified, per-use, no refresh loop). No bootstrap
  token, refresh family, or rotation loop is involved at all.
- **Implemented — the operator API (agent-as-operator, bd_2_0-bvw).** Egress
  toward the controlplane's operator API gets a 60s, dvx-signed
  **agent-operator token** injected the same way: the gateway relays the agent's
  held SVID *cert* (never the key) through the broker to the CP mint endpoint,
  caches the token per-agent (≤60s, drop-on-failure), and presents the bearer
  itself on the re-originated hop. The agent's own TLS to the CP host is
  terminated at the gateway exactly like any other egress (the per-agent
  egress-redirect carries an explicit east-west rule for the controlplane), so
  the token and the SVID key both stay in the trusted plane. Blast radius is the
  own-actor audit read (the `auditor` role, kind-gated on every other route);
  the outbound cloud federation of the same token class is the WIF proof
  (`docs/wif.md`). Live-verified on daevix-dev (bd_2_0-bvw.7).

## Lifetime equals liveness

There is no secret to rotate, so there is no rotation loop and no refresh-token
family to protect. The identity **is** the socket, the Cilium identity, and the
measured guest. Tearing down the pod removes the socket, the mapping, and the
Cilium identity in the same operation — a token-revocation kill-switch (revoke
the refresh family + blacklist the JTI, eventually consistent) becomes "unmap
and unmount," immediate and local.

## Containment is not teardown

Teardown (the Lifetime equals liveness lever) destroys the VM — and with it the
evidence. The composed **contain agent** action (`bd_2_0-0qp`,
`POST /api/v1/orgs/{org}/agents/{agent}/contain`, released by `.../release`)
freezes a suspect agent IN PLACE instead, escalating without destroying:

1. **Sever external reach.** The controlplane flips `agent.contained` in the
   governance snapshot; the enclave gateway's layered engine resolves it FIRST
   and denies the agent's every egress — ahead of allow rules and ahead of the
   assert-identity floor — which also kills LLM inference (classification only
   runs on destination-allowed flows). The broker additionally stamps a
   deny-all `egressDeny` CiliumNetworkPolicy into the agent's namespace:
   defence in depth below the redirect.
2. **Revoke agent-operator authority.** The CP flag feeds the existing gates:
   the mint endpoint refuses contained agents (`bd_2_0-bvw.3`) and the broker's
   relay gate refuses them before the CP (`bd_2_0-bvw.5`), so worst-case
   post-containment CP access is the ≤60s tail of already-minted tokens.
3. **Preserve evidence.** The Kata VM is NOT torn down: memory and disk keep
   running for inspection. Operator forensics (ssh/exec/tunnel into the VM)
   deliberately stay reachable — inspect, then release or delete.

The action is a single operator verb, reversible, audited at authority tier
(`agent.contain` / `agent.release`), and touches only the target agent. It
composes existing primitives (the policy snapshot + layered engine, the
mint/relay gates, the broker reconciler) rather than adding a new mechanism.

## Open questions

- **Attestation freshness.** Is a one-time attestation at provisioning
  sufficient, or should the gateway/IMDS periodically re-challenge? For Kata
  VM-isolated pods the boot-time measurement is usually the meaningful one, but
  it is a real choice.
- **Attestation on nodes without a TEE.** Define the fallback posture for
  clusters whose nodes cannot produce a hardware measurement (dev/test, or
  non-confidential hardware). Likely a clearly-labelled reduced-assurance mode
  where the anchor degrades to enclave-asserted, never silently.
- **Cilium identity granularity.** Confirm one Cilium security identity per
  agent (not per workload class), since Plane 2 attribution depends on it being
  agent-unique.
- **CA injection across runtimes.** Enumerate the TLS stacks we must cover
  (OpenSSL, Go, Node, Python, JVM) and how the runtime points each at the
  injected CA without touching the customer image.
