VacantAn accountability layer for AIGitHub

Core concepts · Concept / five-level vocabulary ·

Receipts

A receipt is a "tamper-evident" signed record, but it comes in five levels, and every receipt must declare for itself which level it reaches. An audit on 2026-09-20 found that "refused delivery" was only a flag — since then we have stopped saying "stopped at the exit" at all.

#The five-level vocabulary

On 2026-09-20, an internal audit found that in one batch of experiments, "refused delivery" was only a flag and an exit code — the outer script did not read the exit code, and copied the rejected files out anyway. Of 391 audited cells, 7 of the 59 refused-delivery cells had their files copied out and scored.

After that audit, the project switched to a five-level vocabulary to describe "what the system has proven," and requires each level to declare its own coverage.

LevelWhat the system has provenStatus as of 2026-09
ObservedIt saw certain behavior, traffic or artifactsHolds: the conversation seen by mediation has no gaps (all 2,755 rounds match up, 195/195 chains verify)
DecidedIt independently judged whether this delivery passes or notHolds: when the agent claims it is done, the system can still rule refused delivery and sign it
WithheldAn artifact that did not pass was not sent outDoes not hold by default; filled in by separating the release / publish capabilities
MediatedThe agent cannot find an alternative path that bypasses the systemDoes not hold by default; holds experimentally with the bwrap sandbox wall
AttestedThe facts above are bound tamper-proof to the artifact, the policy and the identityPartly holds: expressed as receipt levels A / B / B′ / C

The benefit of this vocabulary is that it breaks up binary statements like "is there a gate or not." It's not that we have no gate; it's that we cannot assume the gate holds the line.

#Event chain format

Each resident (participant) maintains an append-only event chain. Every event contains exactly eight fields:

{
  "stream_id": "z…",
  "branch_id": "…",
  "seq": 42,
  "prev_hash": "…",
  "ts_ms": 1758…,
  "type": "DELIVERED",
  "payload": { },
  "sig": "…"
}

Verification checks three things: sequence numbers are contiguous, the previous-entry hash matches, and the Ed25519 signature is valid.

#What a signature can prove, and what it can't

Every signature covers the bytes of canonical JSON (sorted keys, no extra whitespace, UTF-8), not a programming language's data structure — otherwise the bytes would not match when signatures are verified across machines.

#Three things chain verification cannot catch

These three are structural limits of the chain, not configuration problems.

Cannot catchWhy
TruncationThe chain has no trusted chain head. Cut off the tail and it still passes.
ForksIf the same party signs two chains with different contents for the same event, both verify.
OmissionsFor an event that was never written down, no receipt can claim to be one of its entries.

So the term for receipts is "tamper-evident," not "impossible to change afterwards."

#Measured: chain verification passes

BatchChainsEntriesResult
R460R (five replications)—6,674All passed
R529 (four sets across task banks)—3,167All passed
Total1949,841All passed independent signature verification
R454 (multi-party execution, real run)—5,519 attestations0 signature verification failures
E10 (after the channel-separation revision)—180/180 reviewer envelopesStill verifiable
ABPI audit195—All passed

Lying keys named: 273/273; honest participants falsely accused: 0/1,840

#Receipt levels

The level is decided by probe results, not by the inference "we installed it."

LevelConditionWhat the receipt can say
ASandbox wall in place, framework-hook probe triggered, 0 unexplained callsEvery model call went through Vacant, and each one matches a tool event
BSandbox wall in place, no hookEvery byte that left is recorded, but it can't tell which call was made by the model
B′The model channel is not (or not fully) in Vacant's view, but the tool layer is recordedIt can only account for tool-layer events
CNeitherRefuses to issue a receipt

Every coverage field is three-state (true / false / not measured). A value that was not measured must not be written as zero or false. The first A-level receipt for a real agent was signed on 2026-09-20 (pi 0.85.1, 0 unexplained calls).

#The exit: capability separation

"Ruled a refusal" is not the same as "impossible to publish." The exit problem is handled by capability separation:

  • The release module mints a release credential only on a pass (its structure is taken from SLSA's verification summary attestation).
  • The publish module is the only component with the ability to write outward. It verifies the credential's signature, the artifact hash, the verdict, the validity period and the coverage.
  • publish does not read exit codes, and deliberately has no force-publish option.

From this the project wrote down a design principle:

A gate that can be bypassed will be bypassed on the day it is needed.

#The offline viewer's structural boundary

Our own single-file offline viewer (exhibit/receipts/receipt_viewer.html) embeds the receipts in the file, with zero external resources and zero network requests; even opened via file://, it runs the full chain verification.

But the R455 experiment found its limit:

If an attacker re-mints the whole chain with their own key and edits the embedded receipts to match, it still verifies.

This is a structural limit of the offline viewer, not a bug — a signature identifies a key, not a subject. The mitigation is to print the three vacant_ids on a physical label, so that there is an anchor outside that cannot be re-minted along with the chain.

#Next

Last updated: · about 4 min read · this page cites 1 adjudicated claim

Shih Hsin UniversityDepartment of Information and Communications, Shih Hsin UniversityVacant

Department of Information and Communications, Shih Hsin University · 2026 capstone project · on-site exhibition