zespan policy manages policy-as-code files: the YAML
policies in your repository that compile into guardrails.
Quick start
You must be signed in
pull, test, plan and apply check your role in the organization that
owns the project. That is a question about a person, so they need a person’s
session:
zespan doctor. See the two
credentials.
Verbs
validate and init make no network call and need no credential of any
kind. That is deliberate: validate is meant to run as a pre-commit or
pre-push hook, and a hook that needs the network is a hook people disable. The
parser and the full schema are compiled into the binary.Flags
Configuration precedence is the same as every other
zespan subcommand — see
CLI overview. As there, the API key and API URL are never read
from .zespan.yaml, so a committed config file cannot redirect where your key
is sent.
zespan policy init
policies/pii-egress.yaml with a starter policy in dryrun mode. It
refuses rather than overwriting an existing file.
zespan policy pull
--all also re-emits ones already managed
in code, which is useful if a file was lost.
Each generated file pins its rule to the existing guardrail with a slug:, so
the plan straight afterwards reports no changes to make — only pending
adoptions. Files you have already edited are skipped unless you pass --force.
Generation is not lossless and prints a warning for every case it could not
express exactly; see what pull cannot express.
zespan policy validate
.yaml/.yml file under policies/ and validates all of them,
reporting every problem at once with file, line and a message that says what to
write instead. Exits 0 when everything is valid, 1 otherwise.
As a pre-commit hook:
.git/hooks/pre-commit
zespan policy test
deny requires a test result or --untested.
zespan policy plan
plan against unchanged
state prints No changes. — reformatting a file, reordering its keys, or adding
comments is not a change, because the comparison runs over a canonical form.
zespan policy apply
apply computes a plan first and sends its hash with the apply, so you cannot
apply a plan you never saw. It refuses in the cases below, each naming the flag
that resolves it:
The policy does not list this environment (ENVIRONMENT_NOT_ALLOWED)
The policy does not list this environment (ENVIRONMENT_NOT_ALLOWED)
appliesTo.environments is a guard, not a router — it never sends a policy
somewhere you did not name on the command line, it only refuses to write it
where the policy says it does not belong. Either point --env at an
environment the policy lists, or add this one to the file. Every offending
policy is named, not just the first. Checked before every other refusal
below, so a misdirected apply is reported before anything else — see
scoping to environments.The state changed since the plan (STALE_PLAN)
The state changed since the plan (STALE_PLAN)
--force overrides.A policy was detached in the dashboard (CONFLICTS)
A policy was detached in the dashboard (CONFLICTS)
--force takes
ownership back into code.A policy would enforce at deny with no backtest (UNTESTED_DENY)
A policy would enforce at deny with no backtest (UNTESTED_DENY)
zespan policy test --against issues and look at the false positives.
--untested exists because a platform engineer mid-incident has to be able
to ship — see the deny gate.The apply would take over a dashboard guardrail (ADOPTION_NOT_ALLOWED)
The apply would take over a dashboard guardrail (ADOPTION_NOT_ALLOWED)
zespan policy pull. Adopting makes those guardrails
read-only for whoever built them, so it is never implicit. The row is
updated in place and keeps its execution history.The apply would remove a policy (REMOVAL_NOT_ALLOWED)
The apply would remove a policy (REMOVAL_NOT_ALLOWED)
--allow-remove makes it
deliberate.Every apply names a person
Because these commands run as you rather than as a project key, an apply is attributed to a real identity. Thepolicy.applied audit entry records the
user, their organization, the session the request came from, the
device — the machine registered by zespan auth login —
plus the IP address and user agent the server observed. The recorded apply itself
names the acting user alongside each file’s SHA-256 content hash.
An apply made from the dashboard records no device, because there is no CLI
machine behind it. That is an honest blank rather than a guess.
A control-plane mutation the server cannot attribute to a real user is
refused with
401, not recorded against a placeholder. There is no
unattributed apply to find in the audit log, because one can no longer be made.In CI
.github/workflows/policies.yml
policy:apply, which is granted to owner and admin only —
editor deliberately does not have it, matching how guardrails are already
treated as a high-risk resource. A project you cannot reach is reported by name:
Next steps
Policy as code
The authoring loop and the ownership model.
File reference
Every field and the supported YAML subset.
zespan auth
Sign in —
pull, test, plan and apply all need it.zespan link
Link a project so you never pass
--project.CLI overview
Configuration precedence and install options.
zespan doctor
Diagnose SDK setup problems.

