Skip to content

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 &lt;file&gt; --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 &lt;did&gt; "]
        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 &lt;file&gt; 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 &lt;phrase file&gt;"] --> 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

Last reviewed September 9, 2026

Suggest an improvement to this page Not for security reports — see disclosure