Skip to content

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_policy describes whom the Key Authority will wrap a key for (see the crypto envelope).
  • Write access is relay-checked. A write_policy is 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=secrets is 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:

  1. Token validity — version, signature, authorized_by is human, unexpired, not revoked, and (if delegated) a valid chain.
  2. Path scope — the target must match path_scope and not denied_path_scope (and branch_scope for lane ops). A miss is rejected before any policy is consulted.
  3. Operation allow/deny — the op must be allow-listed, not deny-listed, and satisfy the relevant capability flags.
  4. Policy / clearance / key availability — evaluate the effective read_policy against 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 revoke drops 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 by expires_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 = ["**"] with denied_path_scope set to every path whose effective read_policy or write_policy is 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_branches and can_push_to_relay on, can_issue_sub_tokens off unless you pass --can-delegate;
  • a required expiry — --expires-in <hours>, 24 hours when omitted — and lane work namespaced under agent/<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