zespan policy apply, or authored here and applied from the dashboard, it reaches your project’s guardrails through the exact same computation: the same parser, the same plan → apply pipeline, the same refusals. There is no second, dashboard-only way to write a guardrail rule from a policy — this page is a second entrypoint into the pipeline documented in Policy as code, not a second implementation of it.
Policies is available on the Pro plan and above, the same gate as Guardrails — see Plan requirement.
One writer per policy
Every policy has exactly one owner at a time: Code (a file in your repository, applied by CI) or Dashboard (authored here). The owner decides who may change or remove it — the other side is refused, not silently overwritten. See Ownership below; this is the same ownership model Policy as code already describes for Guardrails, extended to cover policies authored here instead of hand-built guardrail rules.The inventory
The main Policies page lists every policy enforcing (or once enforced) in the selected environment — the same environment switcher Traces, Guardrails, and Evaluations use. One row per policy:
Status values:
Tested values:
A filter box searches by name or id, and an owner toggle narrows to Code or Dashboard. Click a row to open that policy’s detail page.
If a project has no policies yet, the inventory explains what a policy is and offers both starting points side by side: Author a policy here, or run
zespan policy apply ./policies --env prod from CI.
Authoring
1
Open New policy
From the Policies page, click New policy.
2
Name it
The Name and id fields write straight into the document’s
metadata.name and metadata.id as you leave each field — the fields and the YAML never disagree, because one is the source for the other. If the id or name line in the YAML has been hand-edited into something the fields can’t unambiguously update (duplicated, removed, or re-indented), authoring refuses to guess and asks you to fix it directly in the editor instead.3
Set the enforcement posture
Dry run, Warn, or Deny, matching the enforcement ladder. Start new policies at dry run.4
Write the rules
The YAML editor is the source of truth — full syntax highlighting and inline error squiggles, powered by the same server-side validator
zespan policy validate uses, so an error shown here is the same error the CLI would report. Alongside it, the rule builder inserts a generated rule fragment at the end of the document’s rules: list for the common types (regex, keyword, PII, toxicity, LLM classifier). It only ever inserts — it never reads the document back into itself, so nothing you’ve hand-written (comments, key order, an unusual structure) is ever silently rewritten. If it can’t find a single unambiguous rules: list to insert into, it says so and leaves the fragment for you to paste in yourself.5
Save
Save draft commits the document’s first revision. Nothing is enforced yet — saving only creates the record. Review and apply it from the policy’s own page.
Saving is disabled until the YAML validates and both Name and id are filled in. That’s deliberate: nothing invalid or unidentified reaches a revision.
Reviewing a policy
A saved policy’s detail page has four tabs:- Rules — how many compiled rules this policy contributes in the current environment, with a link to their runtime view in Guardrails.
- Source — the applied revision’s YAML, read-only, with a revision picker. A policy authored in git shows its repository path instead of an editor — Zespan holds no document for it to render.
- Tests — the current test status in plain language: tested, stale, or untested, and what to do about it.
- History — when this policy was last applied in this environment and by whom, with a link to the full apply history.
Review and apply
Review renders the exact planzespan policy plan would print for the same change — creates, updates, adopts, deletes, and any conflicts — because it’s computed by the same server endpoint. Reviewing needs only policy:read, which every role has; Apply needs policy:apply and is disabled while any conflict is unresolved.
Applying goes through the identical set of refusals zespan policy apply has always had. Each one opens a dialog naming what happened and offering the action that resolves it, rather than a generic error:
The
UNTESTED_DENY treatment is the one worth using deliberately: it’s a speed bump, not a wall, because someone mid-incident has to be able to ship — but the dialog makes the safe path (see what the backtest would have caught) as fast as the override.
A successful apply lands you on apply history at the entry it just created.
Backtest evidence
The Tested badge and theUNTESTED_DENY gate read the same fact: whether a PolicyTest row exists for this policy’s exact current text, keyed by content hash. A test run from the dashboard and one run with zespan policy test write to the same table, so whichever surface tested a given revision satisfies the deny gate on both.
The one place the dashboard triggers a backtest today is inside the UNTESTED_DENY dialog — Run backtest now runs it against the last 7 days of traffic and reports what it would have caught and broken, the same report Policy testing describes for the CLI. To backtest against a different corpus (issues, or a specific dataset) before you’re at the deny gate, use zespan policy test --against <corpus>.
Apply history
Activity (linked from the Policies page header) lists every apply in the selected environment, newest first:
Every dashboard apply records the exact revision it deployed, so an apply made here always names a specific, immutable piece of text — not just “whatever the document held at the time.”
Ownership and handing a policy over
The Owner badge is the whole mechanism that lets git and the dashboard share a project without fighting over it:
To move a policy from code to the dashboard — during an incident, or because ownership is genuinely changing hands — use Detach on its guardrail card or detail page in Guardrails, exactly as Policy as code describes. The policy becomes dashboard-owned immediately; the next
zespan policy apply reports it as a conflict and refuses to overwrite it without --force.
There’s no dashboard-side equivalent for taking a policy the other direction — from dashboard to code — beyond what already exists: point a file at the same id and run zespan policy apply --adopt, the same adoption flow Policy as code describes for hand-built guardrails.
Organisation view
Everything above is one project’s inventory. The top-level Policies entry in the org admin sidebar (/[org]/admin/policies) rolls the same facts up across every project in the organisation, for the question a security lead actually asks: “is this policy deployed everywhere it should be?”
Four cards summarise the current scope:
Below the cards, one row per policy id, not per project — every project carrying that id is grouped underneath it. A row shows how many projects carry the policy, a posture breakdown (dry run / warn / deny), how many occurrences need attention (
out of band, detached, untested, or stale), and the owner split (Code vs Dashboard). Expand a row to see every occurrence: which project, which environment, posture, rule count, owner, test status, and sync status, each linking to that project’s own policy detail page. If projects use different display names for the same policy id, the row flags it — that disagreement is itself a governance signal, not noise to hide.
Present in 9 of 12 projects is coverage, not compliance: it means 9 of the 12 projects measurable in the current scope carry at least one occurrence of that policy id. Under a narrowed scope (Production only, or one named environment), a project with no matching environment at all was never measurable and is excluded from both the numerator and the denominator, rather than counted as absent for a reason that has nothing to do with policy coverage.
This page reports what is deployed. It does not, and cannot, declare a project non-compliant — no standard exists yet in Zespan for a project to be measured against; that’s a later capability. “Present in 9 of 12 projects” and “Not present in: <project>” are facts about what’s deployed, never a verdict on the projects that don’t carry a policy.
lastAppliedAt on the rows further up: a policy’s row can still show Not applied even while this section shows recent activity for the same project, because activity outside that window, or on a different environment, doesn’t move lastAppliedAt on the row.
What is not in this release
- Editing a policy again after it’s been saved. The dashboard supports authoring, reviewing, backtesting, and applying a policy today; changing an already-saved policy’s text from here is not yet available.
- Restoring or diffing a specific past apply from the Activity page. Both exist as API endpoints today — restore resolves an old apply back to a plan you confirm through the ordinary apply flow — but neither has a dashboard button yet.
- A cross-environment view on a policy’s detail page. The detail page shows one environment at a time, whichever the switcher has selected; switch environments to see the same policy’s posture elsewhere.
Next steps
Guardrails
The runtime side: execution history, code-managed badges, and Detach.
Policy as code
The authoring loop and ownership model from the repository side.
zespan policy
Every verb and flag, and the same six refusals from the CLI’s perspective.
Policy file reference
Every field a policy document supports, and the supported YAML subset.
Policy testing
How a backtest is scored, and what the deny gate is protecting against.
SDK guardrails
Handle a guardrail verdict in your application code.

