File formats
Vidi keeps state in exactly two places: .vidi/ inside the repository, and
~/.vidi/ on your machine. The split is the important part: the repo
directory is meant to be committed, the home directory never is.
Source of truth: crates/vidi-core/src/ledger/layout.rs, which declares every
repo-relative path as a constant.
What lives where
| Path | Contents | Committed? |
|---|---|---|
.vidi/reviews.jsonl | review claims, approved and rejected | yes |
.vidi/revocations.jsonl | revoke records | yes |
.vidi/authorship.jsonl | advisory model provenance | yes |
.vidi/policy.toml | the trust policy | yes |
.vidi/notary.pub | cached notary public key | yes, once it exists |
~/.vidi/identity | your offline identity | never |
~/.vidi/token | hosted bearer token | never |
~/.vidi/trust-root.json | trust-root manifest | never |
.review-cache/ | derived scan cache | never, git-ignored |
The rule of thumb: anything that is a claim is committed, anything that is a credential or derived is not.
.vidi/reviews.jsonl
One in-toto Statement per line, a review claim about one unit. Both verdicts live here; a rejection is a review, not a separate file.
{
"_type": "https://in-toto.io/Statement/v1",
"subject": [{
"name": "crates/vidi-core/src/digest.rs::struct:Blake3Digest",
"digest": { "blake3": "76b53b0c9cfd57d07d2a4802263e6856" },
"repo": { "host": "github.com", "owner": "graze-ai", "name": "vidi" }
}],
"predicateType": "https://vidivouch.com/vidi/human-review/v1",
"predicate": {
"criteria": ["equivalent"],
"rigor": "read-fully",
"attesterIdentity": { "kind": "person", "id": "you@example.com" },
"roleAtReview": "maintainer",
"verdict": "approved",
"claimTimestamp": "2026-07-30T20:52:58Z",
"profileVersion": "rust-1",
"scopeModelVersion": "vidi-scope-4"
}
}
The fields that carry weight:
| Field | Why it matters |
|---|---|
subject[].digest.blake3 | the unit’s fingerprint as it was when read. When the code changes this stops matching and the review goes stale |
attesterIdentity.id | matched as a literal string against your [reviewers] keys; see policy.toml |
roleAtReview | your standing at vouch time; the effective rank is the weaker of this and your current one |
verdict | approved or rejected |
criteria | equivalent is the core review claim; other values are weaker attestations |
profileVersion / scopeModelVersion | the analysis rules in force. A mismatch reads as migration-needed rather than fresh |
Two records for the same unit are two independent claims. Nothing is overwritten.
.vidi/revocations.jsonl
Revoke records, and a much smaller shape:
{
"_type": "https://in-toto.io/Statement/v1",
"subject": [{ "name": "revoke", "digest": { "blake3": "28d7043ed80ea10df8a78cd4b6f32f3f" } }],
"predicateType": "https://vidivouch.com/vidi/revoke/v1",
"predicate": {
"reason": "revoked via vidi revoke",
"attesterIdentity": { "kind": "person", "id": "you@example.com" },
"claimTimestamp": "2026-07-31T15:26:24Z"
}
}
The subject name is the reserved word revoke, not a unit address. The digest
names the content_id of the review being withdrawn. A revocation targets a
record, not a location, which is why re-vouching afterwards works: the new review
has a different content_id the revocation does not name. See
Revoke an approval.
The file lane is itself a constraint: reviews.jsonl accepts review records
only, revocations.jsonl revocations only. A record in the wrong file is
rejected rather than honoured.
.vidi/authorship.jsonl
Advisory model provenance (which model or models wrote a unit) recorded by
vidi attribute from git-ai’s refs/notes/ai attestations.
{
"address": "crates/vidi-core/src/digest.rs::struct:Blake3Digest",
"contentId": "76b53b0c9cfd57d07d2a4802263e6856",
"contributions": [{ "modelId": "claude-opus-4-8", "lineCount": 12 }],
"agentContext": true
}
This file is never read by the gate, and that guarantee is structural rather
than documentary: the gate path does not import the authorship module, the
ledger exposes no method accepting an authorship record, and no type there can
be converted into a review or a gate state. A malformed line here is dropped as
advisory and can never affect an exit code, unlike a malformed line in
reviews.jsonl, which fails closed.
It stays empty until you run vidi attribute.
.vidi/policy.toml
The trust policy: reviewer ladders, the reviewer directory, and the coverage
obligations that turn review into a gate. It is the one file in .vidi/ that is
not append-only, and the ledger never reads it.
Every key is documented in the policy.toml reference.
.vidi/notary.pub
A cached copy of the notary’s Ed25519 public key (standard-base64, raw 32
bytes). Its only job is to make vidi verify-vouches work offline: once
the key is cached, signature verification needs neither network nor token.
It does not exist until you verify signed vouches, and repos using only self-asserted reviews never grow one.
~/.vidi/: machine-level, never committed
Credentials, scoped to your machine rather than to a repository, the way
git config --global is:
| File | Contents |
|---|---|
identity | the offline identity from vidi login --local, one line, e.g. you@example.com |
token | the hosted bearer token from vidi login. Revocable, and not a signing key |
trust-root.json | the trust-root manifest mapping key ids to epochs |
Two overrides exist for non-default setups: $VIDI_IDENTITY_PATH relocates the
identity file, and $VIDI_REMOTE_TOKEN supplies a bearer directly, which is
how CI authenticates without an interactive login.
.review-cache/: derived, git-ignored
Per-file scan facts, cached so repeated scans do not re-hash unchanged files.
Never a source of truth: delete it and the next command rebuilds it. vidi init
adds .review-cache/ to .gitignore for you.
Why the ledgers are JSONL
One claim per line, append-only, and never rewritten in place. Three properties follow from that, and they are the reason the format is what it is:
Diffs are readable. A new review is one added line in a pull request, not an opaque binary delta.
Merges cannot conflict. vidi init writes .vidi/*.jsonl merge=union into
.gitattributes, so git resolves two branches by keeping every line from both
sides. Order does not matter because the merge algebra is monotone:
union, dedupe by content_id, revoke-wins.
History survives. Revoking appends a record rather than deleting one, so “approved Tuesday, withdrawn Thursday” stays visible. See Why review files never conflict.
This is also why writers append rather than rewrite the file: a whole-file rewrite would clobber lines contributed on another branch, breaking the union algebra that makes the conflict-freedom work.
The two lines vidi init writes
.gitattributes: .vidi/*.jsonl merge=union
.gitignore: .review-cache/
Both are declared as constants in layout.rs. Commit them along with .vidi/:
the merge rule is what stops two people’s reviews from colliding, and it only
works if everyone has it.