Permission & review workflows¶
Visual references for how TOVIO's cryptographic permission system and the review system it plugs into actually flow — from writing a policy through grant / revoke, key recovery, redacted review, the audit log, and how agents fit in. Prose for each step lives on the sibling pages (write a policy, grant and revoke, check access, manage keys, recover a lost key, audit log) plus concepts / Permissions and the collaboration pages (propose and review, lane protection, team workflows). This page is the map.
How to read these
Rectangles are objects or states, rounded shapes are actions, diamonds are decisions, and dashed lines are network / cross-identity boundaries. Two ideas recur throughout: read access is cryptographic (encryption, not a server check), and write / review access is checked by the relay and the land gate (proof of attributes, not decryption).
1. Clear files vs policy objects¶
Every path is one of two things, decided by the effective read policy. The policy manifest itself is always clear-readable — anyone can see that a path is protected and by which expression; they just cannot read the protected content.
flowchart LR
F[[File under path P]] --> Q{"effective read policy for P"}
Q -- "ANY (or none)" --> CO["clear object<br/>(plaintext, content-addressed)"]
Q -- "non-ANY expression" --> PO["policy object<br/>(encrypted at snapshot time)"]
subgraph MANIFEST[" always clear-readable "]
M[("policy manifest<br/>path -> read/write expressions")]
end
M -.-> Q
classDef enc fill:#eef,stroke:#557;
class PO enc;
Prose: Write a policy — clear vs policy objects · Concepts / Permissions.
2. Encryption happens on the way into the snapshot¶
Because the working copy is continuously snapshotted with no
staging area, plaintext must never land in a tracked tree. TOVIO matches the policy and encrypts
before the content is hashed into the tree — so the object that is written at commit time is
already ciphertext, and there is no window in which a plaintext object exists in the store. This is
why tovio policy set tells you the path will be encrypted "on the next commit": that snapshot is
the moment.
sequenceDiagram
participant Dev as Developer
participant WC as Working copy
participant Snap as Snapshotter
participant Store as Object store
Dev->>WC: save config/production/api-keys.env
WC->>Snap: change detected
Snap->>Snap: match path against policy manifest
alt path has non-ANY read policy
Snap->>Snap: generate per-file DEK, encrypt content (AES-256-GCM)
Snap->>Snap: wrap DEK once per authorized recipient (X25519)
Snap->>Store: write ciphertext object + wrapped DEK copies
else clear path
Snap->>Store: write plaintext object
end
Prose: Write a policy · Concepts / Permissions — Tier 1.
3. The three cryptographic tiers — one envelope¶
All tiers share one envelope format, so moving up a tier adds a header type rather than re-encrypting your world.
flowchart LR
T0["Tier 0 — Solo<br/>no KA<br/>age-style, own key"] --> ENV[("shared envelope format")]
T1["Tier 1 — Team (built)<br/>hybrid: DEK + wraps<br/>KA = authorization oracle"] --> ENV
T2["Tier 2 — Enterprise<br/>CP-ABE, policy in ciphertext<br/>private, release-gated"] --> ENV
ENV --> OBJ["policy object<br/>(one on-disk shape,<br/>tier chosen by header)"]
classDef tgt fill:#efe,stroke:#575;
class T1 tgt;
Tier 2 is the CP-ABE read-control engine, which has passed its enterprise KA conformance and internal-review gate but is private and release-gated. A threshold (k-of-n) or KMS/HSM-backed KA root is a separate, later profile — not the Tier-2 KA form, and not a prerequisite for CP-ABE.
Prose: Concepts / Permissions — three tiers.
4. Grant vs revoke — additive vs rotate¶
Granting is additive and takes effect on your next commit. Revoking is re-encryption and happens now, in a commit of its own: rotate the DEK and re-wrap it only for the identities that remain.
flowchart TB
subgraph GRANT[" tovio access grant --identity <file> --attr … "]
G1["enroll recipient + issue attribute claims"] --> G2["confirm the printed pairing code (SAS)"]
G2 --> G3["next commit of a matching path<br/>wraps the DEK to their public key"]
G3 --> G4([✓ additive, prospective — no re-encryption])
end
subgraph REVOKE[" tovio access revoke <did> "]
R1["drop recipient from the roster"] --> R2["generate fresh DEK'"]
R2 --> R3["re-encrypt HEAD's protected content under DEK'"]
R3 --> R4["wrap DEK' only for remaining identities"]
R4 --> R5([✓ published as its own commit — current + future closed])
R5 --> R6[["past plaintext they legitimately read<br/>is outside TOVIO's control<br/>(rotate the secret itself if leaked)"]]
end
Prose: Grant and revoke access.
5. Reading a policy-protected file — the decrypt path¶
Decryption happens on your machine with your key. The Key Authority is not in the read path in Tier 1; it was only consulted when the wrapped DEK copies were assembled.
sequenceDiagram
participant Dev as Developer
participant CLI as tovio-cli
participant Obj as Policy object
participant Ring as OS keychain
Dev->>CLI: open config/production/api-keys.env
CLI->>Obj: fetch ciphertext + wrapped DEK copies
CLI->>CLI: find wrapped copy addressed to my public key
alt wrapped copy present
CLI->>Ring: use my identity private key
Ring-->>CLI: unwrap DEK
CLI->>CLI: decrypt content (AES-256-GCM)
CLI-->>Dev: plaintext (never written to disk unless requested)
else no wrapped copy for me
CLI-->>Dev: DENIED, run: tovio access check <path>
end
Prose: Check access · Manage keys.
6. tovio access check — never a bare denial¶
Every permission message points here. The checklist marks each predicate the expression requires, so a denial always names what you are short of.
flowchart TD
A["tovio access check <path><br/>(--identity <file> for anyone but the local identity)"] --> P{"policy on path?"}
P -- no --> CL([✓ clear object — access granted])
P -- yes --> O{"subject is the repository owner?<br/>(always true without --identity)"}
O -- yes --> OW([✓ authorized — owner short-circuit])
O -- no --> E["evaluate policy expression<br/>against my attribute claims (offline)"]
E --> D{"all predicates satisfied?"}
D -- yes --> OK([✓ authorized])
D -- no --> M[["✗ denied<br/>mark each unsatisfied predicate<br/>(e.g. ✗ clearance=secrets)"]]
M --> R["then: tovio access request <path> --attr <missing>"]
classDef stop fill:#fdd,stroke:#c33,color:#600;
class M stop;
The verdict line reads "DENIED ✗ — obtain the missing attribute(s) above, or have an authorized
identity grant you". It does not print the access request command for you; run it yourself with
the attributes the checklist marked.
Prose: Check access · Write a policy — policy test.
7. Key Authority modes — chosen at tovio init¶
The KA is progressive: it only exists when your collaboration model needs it. It is never the thing that decrypts your files.
flowchart LR
INIT["tovio init --mode …"] --> Q{"mode"}
Q -- simple --> S["no identity, no KA<br/>no policies"]
Q -- team --> T["KA = authorization oracle<br/>identity to public-key registry<br/>decides wrapped-DEK recipients"]
Q -- agentic --> A["KA = oracle + agent token issuer<br/>issues & validates capability tokens"]
S -.->|"tovio identity init"| T
T -.->|"tovio agent new — no promotion needed"| A
classDef v1 fill:#efe,stroke:#575;
class T v1;
tovio init offers simple, team, and agentic. Enterprise is not an init choice — the
CP-ABE KA is private and release-gated, and there is no command that promotes into it. A Simple repo
is promoted to Team later with tovio identity init, and a Team repo
already issues capability tokens, so the agentic step is a framing choice rather than a gate.
Prose: Concepts / Permissions — Key Authority.
8. Read vs write — two enforcement planes¶
Read is enforced by encryption (offline, no server in the loop). Write is enforced by the relay at sync time via an attribute proof.
flowchart LR
subgraph READ[" READ plane — enforced by cryptography "]
R1["clone / fetch"] --> R2["policy object arrives as ciphertext"]
R2 --> R3{"do I hold a wrapped DEK for me?"}
R3 -- yes --> R4([✓ decrypt locally])
R3 -- no --> R5[["✗ opaque ciphertext (TVO-PERM-001)"]]
end
subgraph WRITE[" WRITE plane — enforced by the relay "]
W1["tovio sync (push change to write-protected path)"] --> W2["relay checks attribute proof"]
W2 --> W3{"satisfies path's write expression?"}
W3 -- yes --> W4([✓ accepted])
W3 -- no --> W5[["✗ rejected at sync (TVO-PERM-002)"]]
end
Prose: Grant and revoke — a note on write access · Lane protection.
9. Recovery phrase — the one thing that prevents data loss¶
At a terminal tovio init prints it once and asks you to acknowledge it; unattended it writes the
phrase to .tovio/tovio-recovery-key.txt instead. Either way you perform the escrow. It is the
only thing that can bring you back if you lose your identity key, and losing both is unrecoverable by
design.
flowchart TB
INIT["tovio init / tovio identity init"] --> GEN["generate recovery keypair"]
GEN --> WRAP["add recovery pub as an extra recipient<br/>on your wrapped key material"]
WRAP --> SHOW{"interactive terminal?"}
SHOW -- yes --> P["print the phrase once<br/>+ prompt to acknowledge"]
SHOW -- no --> FILE["write the phrase to<br/>.tovio/tovio-recovery-key.txt"]
P --> ESCROW
FILE --> ESCROW["YOU escrow it out of band"]
ESCROW --> USE
subgraph USE[" if identity key is lost "]
L["tovio key recover --from <phrase file>"] --> L2["install a fresh identity keypair"]
L2 --> L3["re-seal HEAD's protected content<br/>to the new keypair"]
L3 --> L4([✓ HEAD readable again])
L4 --> L5[["✗ cannot recover paths whose DEK<br/>was rotated away while I was locked out"]]
L4 --> L6[["! audit chain resets — pre-recovery entries<br/>were signed by the lost key"]]
L4 --> L7[["! teammates/devices whose claims that key signed<br/>are dropped — re-grant and re-approve"]]
end
classDef warn fill:#fdd,stroke:#c33,color:#600;
class L5,L6,L7 warn;
Prose: Recover a lost key · Manage keys.
10. Redacted review — the review system meets encryption¶
A reviewer with no clearance sees that a protected path changed, never its contents. The change still ships once a cleared reviewer covers the protected paths. This is what makes the review system compatible with policy-protected files.
sequenceDiagram
participant R as Reviewer (no clearance)
participant Fg as Forge
participant CR as Cleared reviewer
R->>Fg: tovio review chg:a3f7b2…
Fg-->>R: readable files -> full diff
Fg-->>R: protected files -> 🔒 not accessible to you + coarse change indicator
R->>Fg: --approve --proposal prop-009 --remote …
CR->>Fg: tovio review chg:a3f7b2…
Fg-->>CR: decrypts protected files locally with their wrapped DEKs
CR->>Fg: --approve --proposal prop-009 --remote …
Fg-->>Fg: cleared-reviewer coverage satisfied
tovio review takes a Change ID, not a proposal id, and renders locally by default; submitting a
decision needs --approve/--request-changes plus --proposal <id> and --remote <addr>. The
reviewer is always your signing identity, never a field you set. A protected path you cannot decrypt
shows a 🔒 not accessible to you marker and a coarse change indicator — never the plaintext
or anything derived from it — and the render ends with a coverage summary. That coverage is
advisory: the enforceable requirement that a protected path be approved by a recipient lives on
the Forge side, not in the client that submits the decision.
Prose: Propose and review — redacted files · Lane protection — cleared reviewer.
11. The land gate — permissions inside the review system¶
Permissions compose into the same refuse-or-land gate the rest of the team sees, side by side with reviews, required checks, and plugins. Every branch is a structured reason plus a next step.
flowchart TD
L["tovio land my-lane --into main"] --> G1{"conflict-free?"}
G1 -- no --> B1[["✗ resolve first"]]
G1 -- yes --> G2{"required_checks pass?"}
G2 -- no --> B2[["✗ check failed"]]
G2 -- yes --> G3{"proposal approved?<br/>no open request-changes?"}
G3 -- no --> B3[["✗ review pending / blocked"]]
G3 -- yes --> G4{"cleared-reviewer coverage<br/>for protected paths?"}
G4 -- no --> B4[["✗ needs cleared reviewer<br/>(path shown, contents never)"]]
G4 -- yes --> G5{"write_policy satisfied?<br/>(attribute proof at relay)"}
G5 -- no --> B5[["✗ not authorized to land here"]]
G5 -- yes --> G6{"required plugins (pre-land) pass?"}
G6 -- no --> B6[["✗ plugin blocked"]]
G6 -- yes --> OK([✓ landed])
classDef stop fill:#fdd,stroke:#c33,color:#600;
class B1,B2,B3,B4,B5,B6 stop;
Prose: Lane protection · Team workflows — land gate.
12. Agents get scoped read access via capability tokens¶
An AI agent never becomes you. The KA issues a capability token bounding what the agent may read; TOVIO enforces that scope at open time, and every action lands in the audit log. The default is already the safe one: broad access to clear code, excluded from every policy-protected path, and a required expiry.
sequenceDiagram
participant Dev as Developer
participant KA as Key Authority
participant Ag as Agent
participant CLI as tovio-cli
Dev->>KA: tovio agent new reviewer --model … --task … --expires-in 1
KA-->>Ag: capability token cap_<32 hex>, scope ** excluding protected paths
Ag->>CLI: open src/app/main.ts (token attached)
CLI->>CLI: check token scope + expiry
CLI-->>Ag: allowed -> decrypt & return
Ag->>CLI: open config/production/api-keys.env (token attached)
CLI->>CLI: path is excluded from the token's scope
CLI-->>Ag: DENIED (TVO-TOKEN-001)
Note over CLI: allow + deny both recorded in audit log
tovio agent new <name> requires --model and --task, defaults --expires-in to 24 hours, and
mints the agent's DID as did:tovio:agent/<name> with a branch scope of agent/<name>/**. It works
in a Team repository — no mode promotion is involved.
Prose: Concepts / Agents & capabilities · Audit log.
13. The audit log — append-only, signed, verifiable¶
Every successful protected plaintext release through the CLI or Node/MCP read edges is recorded before
release; token/scope/clearance and final decrypt denials are recorded best-effort. Policy changes,
token issuance, obliteration, and the key/device lifecycle also use the signed chain, which survives
key rotations. Note that grants and revokes have no event of their own — both are recorded as
PolicyChange carrying the affected DID.
flowchart LR
subgraph EV[" recorded events "]
E1["PolicyObjectDecrypted<br/>(read of policy object)"]
E2["ReadDenied<br/>(blocked attempt)"]
E3["PolicyChange<br/>(policy set/remove, grant, revoke)"]
E5["TokenIssue / TokenRevoke"]
E6["KeyRotate / KeyRecover / KeyImport"]
E7["Obliterate · DeviceEnroll / DeviceRevoke"]
end
EV --> APP["append signed entry<br/>(links prev hash)"]
APP --> CHAIN[("audit log<br/>chained + signed")]
CHAIN --> V["tovio audit verify"]
V --> OK([✓ N signed entries intact])
V --> BAD[["✗ errors (TVO-CRYPTO-002)"]]
CHAIN --> S["tovio audit summary --last 7d"]
CHAIN --> Q["tovio audit log · audit show --object/--token"]
classDef stop fill:#fdd,stroke:#c33,color:#600;
class BAD stop;
Prose: Read the audit log.
14. End-to-end: protect a path, grant a teammate, review, and land¶
The full permission story on one page — from writing the first policy through a redacted review to a successful land, with the audit log recording every step.
sequenceDiagram
participant Dev as Author (Dustin)
participant KA as Key Authority
participant Fg as Forge
participant Rev as Reviewer (no clearance)
participant CR as Cleared reviewer (Alice)
Dev->>KA: tovio access grant --identity alice-identity.pub --attr role=senior --attr clearance=secrets
KA-->>Dev: enrolled — confirm the pairing code out of band
Dev->>Dev: tovio policy set config/production/** --read role=senior and clearance=secrets
Dev->>Dev: edit + save, then tovio commit -m protect prod config
Note over Dev: this commit is where the paths encrypt, and where alice's wrap lands
Dev->>Fg: tovio sync, then tovio change propose --remote forge.example.com
Fg-->>Rev: prop-011 open (protected files show a locked marker only)
Fg-->>CR: prop-011 open (protected files decrypt locally)
Rev->>Fg: tovio review chg:... --approve --proposal prop-011 --remote ...
CR->>Fg: tovio review chg:... --approve --proposal prop-011 --remote ...
Dev->>Fg: tovio land my-lane --into main
Fg-->>Fg: gate: conflict-free, checks, review, cleared coverage, write_policy, plugins
Fg-->>Dev: ✓ landed
Fg-->>Fg: audit log appends: PolicyChange, reads, review approvals, land
Grant before you commit: a grant is prospective, so if you commit the protected path first,
Alice's wrapped key does not appear until the next commit that touches it. tovio change propose
requires --remote; reviewers are not named on the command line.
Where to go next¶
- Write a policy — declare who can read a path.
- Grant and revoke access — the additive-vs-rotate model.
- Check access — diagnose denials with a named missing attribute.
- Manage keys · Recover a lost key — key hygiene.
- Read the audit log — the signed, chained record.
- Team workflows · Lane protection · Propose and review — where permissions meet review.
Last reviewed September 9, 2026
Suggest an improvement to this page Not for security reports — see disclosure