Policy & tokens¶
Two contracts govern who may do what in a TOVIO repository: the policy expression language that declares which identities may read and write which paths, and the capability token that bounds what an AI agent may do on the human's behalf. Both are public, deterministic, and offline-evaluable — so any implementation reaches the same authorization decision from the same inputs.
This page summarizes the policy language and the tovio-capability-v1 token.
Two enforcement planes¶
Read and write access are enforced by different mechanisms, and the specs never conflate them:
- Read access is cryptographic. An identity reads a protected object only by obtaining
a usable content key. A
read_policydescribes whom the Key Authority will wrap a key for (see the crypto envelope). - Write access is relay-checked. A
write_policyis satisfied by an Attribute-Based Signature proof the relay verifies on push (see the wire protocol).
Capability tokens layer over both planes: a token can only ever narrow what its authorizing human could already do; it never grants access the human lacks.
The policy expression language¶
A policy expression is a boolean expression over attribute claims. It appears as the
read_policy and write_policy of a declaration in the policy manifest; both sides use
the same grammar.
Predicates and operators¶
- The only leaf is a predicate
attr-name = value, true for an identity that presents a valid, unexpired, KA-signed claim whose name and value match exactly (case-sensitive UTF-8). An identity may hold several claims for one name; the predicate is true if any matches. - There is no ordering, range, or substring operator —
clearance=secretsis an exact match, not a level comparison. - Operators are
!/NOT,&/AND,|/OR(symbolic and keyword forms are equivalent), with precedence NOT → AND → OR, AND/OR left-associative, and parentheses to override. - Reserved attribute names with defined meaning:
entity(human|agent),role,team,clearance. Everything else is an opaque, KA-defined attribute.
ANY # clear path — plaintext, all readers
role=admin # single attribute
role=senior & team=payments # AND
role=admin | role=engineer # OR
(role=engineer | role=senior) & clearance=secrets
entity=agent & clearance=deployment # only cleared agents
!role=contractor & team=backend # NOT (safe in a write_policy)
ANY and the clear/encrypt decision¶
read_policy = "ANY" declares a clear path: stored in plaintext, never encrypted.
ANY is a whole-expression keyword — it can never appear as a sub-term. Whether an object
is written clear or encrypted is decided solely by the effective read_policy of its
path.
A write_policy = "ANY" means the path is write-open: any identity with repository write
access may write it with no proof required.
Determinism and offline evaluation¶
Policy evaluation is a pure function of the expression text and the identity's claim set. Expiry is checked against a caller-supplied operation instant, never a wall-clock read inside the evaluator, so a replay at the same logical instant is reproducible. The evaluator consults no network and no mutable state — two conforming implementations, given byte-identical inputs, return identical results offline.
Path matching and the effective policy¶
Policies bind to paths by a glob in each declaration. The glob subset is well defined:
? matches one non-/ character, * matches within a single segment, ** matches whole
segments (including none), and [...] is a character class. ** is the only token that
crosses /.
When several declarations match one path, the most-specific (longest-match) wins, by a
total, deterministic ranking: more literal characters first, then fewer wildcards, then
** ranked less specific than not, with a final lexicographic tiebreak. This makes
"carve a clear hole in an encrypted tree" expressible — a more specific ANY on
config/README.md stays clear even under an encrypting config/**.
.tovioignore is a tracking filter, not an access-control mechanism, and policy takes
precedence over it: a path that is both ignored and governed by a non-ANY read_policy is
still snapshotted, and it is sealed — an ignore rule can never silently suppress encryption.
An ignored path with no protective policy simply stays untracked. A short list of hard
exclusions sits outside that rule — .tovio/ itself, any tovio-recovery-key.txt, and the
materialization markers TOVIO writes for an unreadable, conflicted, or submodule entry are
never snapshotted, whatever a manifest claims. The correct way to keep a secret safe is a
non-ANY read_policy, never an ignore rule.
The policy manifest¶
Policies live in a tracked, content-addressed policy-manifest object:
policy-manifest {
version: u32 // monotonically increasing
policies: [ PolicyDecl ]
// additive, omitted when empty: protected_branches, change_control, chunking,
// signing, audit, and the owner `signature` that makes the manifest authority
}
PolicyDecl {
policy_id: string // stable id, referenced by policy objects
path: string // glob
read_policy: string // policy expression or "ANY"
write_policy: string // policy expression or "ANY"
}
The manifest is always a clear object — implementations must refuse to encrypt it. This is what makes offline policy evaluation possible: the expressions are public; only the content keys are gated. Any reader can see that a path is protected and by which policy, even without read access to the content.
Changing policy is a normal commit producing a higher version. What stops an identity
from granting itself access by editing the manifest is owner attestation: a manifest is
authority only if it carries a signature verifying under the repository owner root
established at tovio init, so a non-owner cannot produce a manifest any peer or Forge
will select, whatever it pushes. The policy in force for any historical commit is
recoverable: it is the manifest reachable from that commit.
The capability token¶
A capability token authorizes an agent identity to perform bounded operations on
bounded paths for a bounded time. It is the capability object, version
"tovio-capability-v1", signed by the human who issued it.
Key fields:
| Field | Meaning |
|---|---|
token_id |
Unique id, cap_ followed by a random suffix; appears in provenance and audit. |
agent / authorized_by |
The agent the token authorizes, and the human who signed it. A token's authority is upper-bounded by that human's own access. |
model / model_hash / task_id |
The model powering the agent (with its cryptographic pin) and the bounded task — recorded into commit provenance, not evaluated in policy. |
path_scope / denied_path_scope |
Globs the token may read and write, minus an explicit exclusion set. Exclusion overrides inclusion. |
branch_scope |
Lane-name globs the token may push to. |
secret_clearance |
If false, the token cannot obtain keys for any clearance-gated object. |
allowed_ops / denied_ops |
Operation allow/deny lists; denied_ops overrides allowed_ops, and an op not allow-listed is denied by default. |
can_create_branches / can_push_to_relay / can_issue_sub_tokens |
Capability flags gating specific operations. |
issued_at / expires_at |
Validity window. expires_at is required — tokens are never indefinite. |
revoked / revocation_timestamp |
Revocation state. |
signature |
Ed25519 by authorized_by. |
The enforcement order¶
Every operation under a token passes four checks in this exact order. The order matters because an earlier check must not leak information gated by a later one — notably, path scope is checked before any policy is read, so an out-of-scope read cannot even learn a path's policy:
- Token validity — version, signature,
authorized_byis human, unexpired, not revoked, and (if delegated) a valid chain. - Path scope — the target must match
path_scopeand notdenied_path_scope(andbranch_scopefor lane ops). A miss is rejected before any policy is consulted. - Operation allow/deny — the op must be allow-listed, not deny-listed, and satisfy the relevant capability flags.
- Policy / clearance / key availability — evaluate the effective
read_policyagainst the agent's claims and obtain the content key; or, for a write, require a satisfying ABS proof.
Checks 1–3 are offline and cryptographically local. A denial returns a structured error
(TokenExpired, TokenRevoked, TokenInvalid, PathScopeViolation,
OperationNotPermitted, PermissionDenied, WritePolicyUnsatisfied) carrying only the
fields relevant to its stage.
Expiry, revocation, renewal¶
- Expiry is mandatory and has no grace period: an operation under an expired token is
rejected with
TokenExpired. There is no implicit extension. - Revocation is by the issuing identity before expiry, is audited, and revokes the
entire delegated sub-tree. What makes a relay refuse a revoked token is roster removal —
tovio agent revokedrops the agent's registration from the access registry, so a copied grant stops authenticating on its next connection; there is no relay-side revocation list, and a still-registered bearer is bounded byexpires_at. An offline client honors any revocation it already knows — which is why short expiries matter for high-trust scopes. - Renewal (
tovio agent renew <token-id>) is a deliberate re-issue of a fresh token with equal-or-narrower scope, never an automatic extension. Clients should renew proactively (e.g. at 80% of TTL) so a long-running session never fails mid-operation. A delegated sub-token is not renewed in place — renew its root, then re-delegate.
Delegation: monotonic narrowing¶
A token holder may mint sub-tokens for sub-agents, enabling hierarchical
orchestration. A sub-token must be monotonically narrower than its parent on every
dimension — its path_scope a subset, its denied_path_scope a superset, its
allowed_ops a subset, its expires_at no later, clearance and capability flags only
dropped — and the root of every chain must be a human-authorized token. An
implementation conservatively rejects any sub-scope it cannot prove is contained. The full
root→leaf chain is recorded in commit provenance, so every agent action traces back to a
human.
Safe defaults¶
tovio agent new <name> --model <model> --task <task> issues a token with defaults tuned
for "let an agent work on the codebase, not the secrets":
- everything except policy-protected paths —
path_scope = ["**"]withdenied_path_scopeset to every path whose effectiveread_policyorwrite_policyis non-ANY, so the agent is structurally excluded from protected paths at the scope stage (no leak); secret_clearance = false;- ordinary working ops allowed (
read,commit,amend,branch:create,relay:fetch,relay:push,conflict:resolve); - escalation ops denied (
obliterate,policy:modify,tag:create,tag:force,sub-token:issue); can_create_branchesandcan_push_to_relayon,can_issue_sub_tokensoff unless you pass--can-delegate;- a required expiry —
--expires-in <hours>, 24 hours when omitted — and lane work namespaced underagent/<name>/**.
Promotion to broader authority — clearance, specific protected paths, delegation — is always an explicit, separate, audited action.
Lane protection¶
A protected lane is one a repository requires to stay conflict-free (buildable),
declared in the policy manifest as a BranchProtection with an optional landing
write_policy, named required checks, an opt-in semantic_check = required gate that
refuses a breaking or semantically conflicting land, and an optional require_review that
makes the Forge the sole writer of the lane tip. The conflict-free rule is enforced both
client-side (at land) and at the relay (on push), so it cannot be bypassed by pushing
directly. Changing protection is a write to the manifest, so it needs the owner attestation
above — protection cannot be silently removed to slip a conflicted change in.
Related pages
The cryptographic constructions this contract relies on (envelope wrap/unwrap, ABS, signing payloads) are summarized in the crypto envelope.
Last reviewed September 9, 2026
Suggest an improvement to this page Not for security reports — see disclosure