
## What policy is and where it lives

Governance policy is a set of typed keys (destinations, LLM governance,
interactive-access toggles) stored at hierarchical scopes: **platform** (no
scope flags), **org** (`--org`), **group** (`--org --group`), and **agent**
(`--org --agent`). The enclave gateway resolves the chain for each agent and
layers the results: a `deny` anywhere unions down, each `allow` is an
independent gate, and a **locked** key wins over every more-specific scope.

One typed key, `llm.inspection`, drives response inspection: when it resolves
for an agent, tool calls in the model's output are held for operator approval
per your rules. Authoring it works exactly as this guide describes — see
[Inspect LLM responses and approve tool calls](/guides/inspect-and-approve-tool-calls/)
for the key's shape and the approval loop.

You author through the controlplane's authenticated operator API, never the
database directly, so every write flows the real path: capability
authorization, the Author invariants (agents never lock; no scope may author
under a higher lock), an immediate re-publish, and an SSE nudge that tells the
affected enclave to re-pull.

{{< planenote plane="controlplane" >}}
Authoring commands take the common operator-API flags: `--controlplane` (or
`DVX_CONTROLPLANE_URL`) plus caller authentication — a service-account key that
mints its own token (`--zitadel-key` + `--issuer`) or a static `--token-file` —
and `--ca-cert` to trust the API's TLS certificate. The controlplane never
receives sovereign data; it holds policy metadata only.
{{< /planenote >}}

## Author a destination list

Destination policy is an allow/deny list of host **suffixes** the gateway
matches against egress destinations. Enforcement is **default-deny**: a host
matching no allow rule is denied, deny rules always win over allows, and
without a resolved allow the flow falls to `default_deny`. A list left unset at
a scope simply defers to the next scope up — resolution walks from agent to
platform and the most-specific scope contributing an allow wins. There is no
fail-open: the gateway refuses to start without a policy snapshot, and a failed
reload keeps the last good one in force.

Start with a platform baseline:

{{< terminal title="dvx" prompt="$" >}}
$ dvx policy destination \
    --controlplane https://cp.example.com \
    --allow example.com --allow example.org
{{< /terminal >}}

Then scope to an org — the flags select the scope, so the same command targets
platform, org, group, or agent:

{{< terminal title="dvx" prompt="$" >}}
$ dvx policy destination \
    --controlplane https://cp.example.com \
    --org org_acme \
    --allow api.partner.example.com \
    --deny evil.example --deny-reason "known exfil host"
{{< /terminal >}}

`--deny` rules take a `--deny-reason`, which is recorded with the rules and
surfaced in the audit trail when the gateway denies on them. `--lock` pins the
key at this scope so more-specific scopes cannot override it (refused at agent
scope — agents never lock).

{{< terminal title="dvx" prompt="$" >}}
$ dvx policy destination \
    --controlplane https://cp.example.com \
    --org org_acme --group team-blue \
    --allow api.anthropic.com --lock
{{< /terminal >}}

## The one footgun: a write replaces the list

Each `dvx policy destination` call **replaces the entire allow/deny list at
that scope** — it is a write of the whole value, not a patch. Run the org
command again with a single `--allow` and every host you allowed before at that
scope is gone. This caught a live operator during verification: adding one new
host to an org list silently dropped the existing entries until the call was
re-issued with the full set.

So the rule is: **one call, complete list**. To add a host, re-issue the
command with everything the scope should hold:

{{< terminal title="dvx" prompt="$" >}}
$ # WRONG — replaces the org list with just api.partner.example.com:
$ dvx policy destination --controlplane https://cp.example.com \
    --org org_acme --allow api.partner.example.com
$
$ # RIGHT — restate the complete list:
$ dvx policy destination --controlplane https://cp.example.com \
    --org org_acme \
    --allow api.partner.example.com --allow api.example.com
{{< /terminal >}}

For bundles you reuse across scopes, define a **pack** once and install it at
any org, group, or agent scope — installs compose with the scope's own key
instead of replacing your typed call:

{{< terminal title="dvx" prompt="$" >}}
$ dvx policy pack define --controlplane https://cp.example.com \
    --name partner-apis --allow api.partner.example.com
$ dvx policy pack install --controlplane https://cp.example.com \
    --name partner-apis --org org_acme
{{< /terminal >}}

Packs can also be installed **inline on agent create**, so one call yields a
governed, usable agent (the caller must hold both `agents:manage` and
`policy:write` at the org):

{{< terminal title="dvx" prompt="$" >}}
$ dvx agent create --controlplane https://cp.example.com \
    --org org_acme --enclave eu-sov-1 --image ghcr.io/acme/bot \
    --policy-pack partner-apis
{{< /terminal >}}

## How the enclave pulls it

Nothing is pushed into the enclave. Each gateway runs a `dvx policy-pull`
sidecar that holds an authenticated, mutually-TLS'd stream to the **broker**
(the enclave's sole controlplane egress), re-pulls the enclave's resolved
snapshot on every SSE nudge (and on `--interval` as a fallback), and writes it
to a shared volume the gateway hot-reloads. A failed pull keeps the last-known
snapshot, so a controlplane blip never leaves the gateway ungoverned.

In the in-repo dev deployment (`deploy/enclave/gateway-daemonset.yaml`) the
sidecar is configured entirely by environment variables:

```yaml
DVX_POLICY_PULL_URL:      https://dvx-broker.dvx-enclave.svc.cluster.local:8450
DVX_ENCLAVE_ID:           (from the dvx-enclave-identity ConfigMap)
DVX_POLICY_SNAPSHOT:      /etc/dvx/policy/snapshot.json
DVX_POLICY_PULL_CA:       /etc/dvx/broker-ca/ca.crt
DVX_POLICY_PULL_CLIENT_CERT: /etc/dvx/broker-client/tls.crt
DVX_POLICY_PULL_CLIENT_KEY:  /etc/dvx/broker-client/tls.key
```

The equivalent CLI flags (all with `DVX_`-prefixed env sources) are
`--controlplane`, `--enclave-id`, `--snapshot`, `--interval`, `--no-events`,
plus the auth flags (`--zitadel-key`/`--issuer` or `--token-file`,
`--ca-cert`, or client-cert mTLS with `--client-cert`/`--client-key`).

## Verify convergence

The snapshot is the ground truth the gateway reads. After a write (the SSE
nudge fires within moments), read it back from the gateway pod:

{{< terminal title="sh" prompt="$" >}}
$ kubectl exec -n dvx-enclave ds/dvx-gateway -c gateway -- \
    cat /etc/dvx/policy/snapshot.json
{
  "enclave_id": "enc_7f3a...",
  "orgs": { "org_acme": { "destination": { "allow": [...], "deny": [...] } } },
  ...
}
{{< /terminal >}}

Then exercise it: an agent egressing to an allowed host is audited as a
`gateway.egress` **allow**; a host covered by a `--deny` rule is denied with
your reason attached, visible in the enclave audit (`dvx audit events --action
gateway.egress --outcome deny`).
