Concepts
Proposal, grant, quorum, receipt, anchor, policy bundle, epoch, tenant - the objects ZIFFER moves, and what each one is not.
Eight objects. Each definition ends with what the object is not, because most of the ways this goes wrong are a reader assuming one of them is the other.
Proposal
A request from an AI agent for one action: what to do, to which resource, with which arguments.
A proposal carries facts about the world. It does not carry the values the decision depends on: the risk grade, the reversibility class, who owns the resource. Those are recomputed from signed policy on the deciding side.
A compromised AI agent writes the whole proposal. So the proposal is treated as what an untrusted party said, and every value that decides anything is derived somewhere else.
A proposal is not a permission. Holding one grants nothing.
Grant
The decision that an action may run, produced by grading the proposal against signed policy and comparing the grade to a floor.
Three defaults fail closed:
| Missing from policy | ZIFFER answers |
|---|---|
| The resource | Highest tier |
| The reversibility of the action | Irreversible |
| A risk function for the action | Refused |
A default that changes no outcome is documentation, not a control. Each of these three is read by a branch that can stop an action.
The API has its own three names for the answer, and this is the only place on
this page they appear: granted: the API answers ALLOW; held for a quorum:
ATTEST; refused: DENY. The prose uses the plain words. The code blocks use
the API names, because that is what your if compares against.
A grant is not an execution. ZIFFER never performs the action.
Quorum
The rule for a held action: how many distinct approvers must confirm before a grant is issued. Two named humans, in the policy this documentation uses as its example.
held - quorum 2-of-2 requiredDistinct means distinct signing identities. Two confirmations from one approver are one approver, and the check is on the identity that signed, not on the name in the message. If the quorum is not met before the hold expires:
quorum not met - nothing ranNothing ran is stronger than nothing persisted. Your code never acted, because it had no receipt to act on. The refusal leaves a decision record, and no receipt.
A quorum is not an approval workflow. It is a condition on a signature count, and it is checked on the signatures before a receipt is issued, not at the button.
Receipt
The signed record of a grant: the hash of the proposal it answers, the grade, the policy version it was decided under, the digests of the approvals that met the quorum, and when. Only a granted action gets one.
It is signed with two independent signature algorithms, and verification requires both. One algorithm breaking does not make a receipt forgeable.
A receipt becomes readable after the decision, once the Executor's part is done, not in the answer to the proposal. Your code waits until it is present and verifies it before acting. That is the load-bearing step: a receipt that is transmitted but never checked is a claim, and a claim is what an attacker sends.
A receipt is not a log line. A log records what a system says happened. A receipt is checkable by someone who was not there and does not trust you.
Anchor
A commitment to the receipt chain, published where more than one party can see it.
Receipts are appended to a chain, so a receipt cannot be removed later without breaking every receipt after it. The anchor makes that chain checkable by someone outside the system that wrote it.
An anchor is not a backup. A record only you hold is a record only you can edit. The point of anchoring is the second party.
Policy bundle
The signed files that decide. Ten of them carry the rules: how sensitive each of your systems is, how risky each action is, what can be undone, who is told before an irreversible action runs, who receives each class of critical alert, the fidelity class of each front door your AI agent may use, the windows the deployment declares, who may approve and how many must, the key that signs your receipts, and the keys of the approval and notification services. A manifest names the tenant, the epoch and when the bundle expires, and a signature file covers the whole set.
You sign it with your own key. ZIFFER holds the public half and verifies the bundle on every read. Every value the decision depends on comes from these files, which is what makes the proposal safe to distrust.
A policy bundle is not configuration. Configuration is a thing an operator edits in place. This is signed material: change one byte after signing and the signature no longer verifies.
Epoch
The version number of a policy bundle. An integer, and every publish must raise it.
The same epoch twice is refused. That is what stops a rollback to an older, more permissive set of rules being presented as the current one.
An epoch is not a timestamp. It orders publications, and it only increases.
Tenant and sandbox
A tenant is the customer one signed bundle governs. Your API key determines which tenant you are; you never put a tenant name in a request, and a proposal body naming a different tenant is refused rather than rewritten.
A sandbox is a separate tenant. Its held actions are answered by a robot
approver, so the quorum path runs end to end with nobody in it. A tenant counts
as a sandbox when two things hold together: its tenant id ends in -sandbox,
and the fingerprint of its receipt signing identity is on an allowlist the
robot approver reads. Two halves, and neither is the other's backstop: drop the
name check and any production tenant whose receipt key was listed becomes a
sandbox; drop the allowlist and any tenant becomes one by being renamed.
What the marker guarantees: your production verification refuses a sandbox receipt. A sandbox tenant signs under its own identity, and a receipt is verified under the key your own bundle names, so the refusal is a checked fact rather than a flag somebody could forget.
The marker decides which bundles the robot may approve for, and nothing in it
is consulted on a production tenant's path. A production bundle that enrols a
robot key is refused at publish (SandboxRobotInProductionBundle), for any
robot on the list the deployment holds.
A sandbox is not a dry run. ZIFFER decides; your handler acts. A sandbox grant handed to a handler that transfers money transfers money.
How they connect
Your code is on your side of the line. That is the boundary the rest of the drawing hangs on.
your environment │ ZIFFER
AI agent ── proposal ───────────────┼─▶ grade against signed policy,
│ compare to the floor
│ │
◀── refused (DENY) ───────────┼────────┤ outside policy, or no rule
◀── granted (ALLOW) ──────────┼────────┤ below the floor: receipt later
◀── held (ATTEST) ────────────┼────────┘ at or above: quorum first
│ │
│ ├── quorum met ─▶ receipt
│ └── not met ────▶ nothing ran
│
your code verifies the receipt, │ every receipt joins the append-only
then acts with your credential │ ledger, and the ledger is anchored
│ where a second party can read itWhat ZIFFER is
The grant layer for AI agents. An AI agent proposes an action, ZIFFER grants or refuses it, your code verifies the receipt before anything runs.
How ZIFFER fits your stack
Four zones, drawn from your side - your environment, ZIFFER, the approvers, the anchor - and what crosses each boundary.