When provenance capture refuses

Reading a TVO-PROV error, and why almost all of them keep your commit.

Written By Dustin

Last updated About 3 hours ago

Provenance capture has its own error family, TVO-PROV-*. Before the individual codes, there is one rule that decides what a capture failure actually costs you:

If nobody asked for the transcript, a failure drops the session and your commit lands. If you named a transcript, a failure refuses the command.

You count as having named one if you passed --session-from, set TOVIO_OBSERVED_TRANSCRIPT, or configured provenance.observed_from. That last one matters: a durable setting makes every subsequent capture failure a commit failure, without you typing a flag.

Both halves are deliberate. Failing a commit over a capture nobody requested would make the feature radioactive, and a gate people disable protects nothing. But silently skipping a capture you explicitly asked for would leave you believing provenance was recorded when it was not, which is worse than an error.

In the refusing case, nothing partial is left behind — the capture is prepared before any object is written, so the repository is untouched. The commit simply did not happen. --no-session re-runs it without the capture; commit, resolve, and land all accept the flag.

Refusals when a run record is captured

Every row below drops the session on the discovered path and refuses the command on the explicit one, unless the row says otherwise.

CodeWhat happenedWhat to do
TVO-PROV-007The content contained a credential-shaped value. The scan is mandatory on both capture tiers.If the value is real, rotate it — it is sitting in a plaintext transcript on your machine regardless of TOVIO. If it is a false positive, re-run with --no-session.
TVO-PROV-008You explicitly asked for a transcript while the policy is off.Enable capture locally, unless the policy manifest pins off — that is an organization mandate, not a local setting.
TVO-PROV-009The transcript could not be read, no adapter recognised it, no prompt could be identified, the source file is too large, or it belongs to a different project.Name the intended file with --session-from, or skip with --no-session.
TVO-PROV-009The assembled record is over the object-size ceiling. Always a warning: the session is dropped and the commit lands, asked for or not.Capture the prompt only, or re-run with --no-session. A sealed record is measured after its spills are folded inline, so it reaches the bound much sooner.
TVO-PROV-003An attested record is malformed, or too large to keep even after bulky content spilled out by reference. Over the ceiling or the budget, the commit lands with a warning; malformed at source refuses.Raise the capture budget, or reduce what is captured. A sealed record cannot spill, so splitting protected work from transcript-heavy work also resolves it.
TVO-PROV-006A hosted Forge refused the push because the repository pins session capture off. Your local commit is fine; the objects batch is rejected.Re-push without the session, or set the repository's session-capture policy to prompt or full. Leaving it alone means the push keeps failing.
TVO-PROV-002The change touched two or more protected policies with no reader in common, so the record could not be sealed to anyone. Refuses either way — discovered or explicit.Normally unreachable, since the owner is a recipient of every protected policy. Check the policies the change touched.

TVO-PROV-008 also has a Forge-side form: a push carrying an observed record into a repository that pins observed capture off is rejected the same way TVO-PROV-006 rejects an attested one. The two policies are independent on purpose — "mandate attested sessions, forbid host-scraped ones" is a coherent posture, and one shared refusal would misreport which mandate you crossed.

Discovery quietly finding nothing usable is the normal case and stays silent.

Why a rejected transcript is not a false alarm

TVO-PROV-009's different-project case is the one worth understanding, because it looks pedantic and is not. Agent runtimes name their per-project directories from the working-directory path, and the encoding is lossy — several genuinely different paths collapse onto the same name. Attaching the wrong project's transcript would record another team's session as this commit's provenance, and nobody notices provenance that merely looks plausible. So the transcript's self-reported directory is checked, and a mismatch is refused rather than guessed at.

Refusals when a record is read or exported

CodeWhat happenedWhat to do
TVO-PROV-004An attested session's prompt hashes to something the commit did not attest — it is not provably the run record the commit was authorized under.Do not trust that session. The commit and its attested prompt hash remain the source of truth. Producers bind them by construction, so a mismatch means tampering or a producer bug.
TVO-PROV-005A PROV or in-toto export could not resolve a required lineage node under your scope, and refused rather than emit a partial graph that reads as complete.Backfill the missing attachments and retry, export from a scope that can read them, or run tovio audit graph instead — it renders locked markers and never refuses.
TVO-PERM-001The clearance gate refused a sealed record read.Expected. You are not a recipient of the policy that record is sealed to.

TVO-PROV-004 applies to the attested tier only. An observed record has no prompt bind at all, so the check does not exist for it.

Two you should report rather than work around

  • TVO-PROV-010 — an observed, self-reported record was offered where an attested one is required. No configuration makes an observed session attestable; that is precisely the property the two-class design guarantees. It is a producer bug.
  • TVO-PROV-011 — a commit carries more than one run record. Also a producer bug. tovio fsck finds any already-stored commits in this state; a stored conflict resolves toward the attested record, so no reader is ever shown self-reported data labelled as attested.

Backfill refusals

  • TVO-PROV-012 — the change already carries a commit-time record, whose link is covered by the commit's own address. A backfilled link is only asserted, so attaching one would replace a stronger claim with a weaker one without saying so. Read the existing record first.
  • TVO-PROV-013 — no signing identity, so the assertion could not be signed. The signature is the feature: an unsigned association records neither who claimed it nor whether it has changed since. Run tovio identity init and retry.
  • TVO-PROV-014 — the change touched a protected path, and a backfilled record has no sealed form. Capture at commit time instead, where the record seals to the touched policies' recipients.

And one from the rationale side

TVO-PROV-001 is the same shape as TVO-PROV-003, one object over: a rationale supplied to a resolve is malformed or oversized.

Before you ask for help

The full entry for every code — cause, remediation, and exit code — is in the error catalog in the developer documentation. If you need to share a failure, share the code and the one-line title. Do not paste the transcript, the prompt, the turn content, or repository paths: a capture that failed the secret scan by definition contains something credential-shaped, and a transcript is the last file you want in a support thread.

Never through the portal

If what you have found is a way to make an unattested record read as attested, or to read a sealed record you are not cleared for, that is a security report. Send it privately to security@tovio.dev — never on a public board and never through the feedback portal.