/ concepts / How it works

This page walks through the mechanics: how an audit comes into existence, how it gets distributed, how a consumer subscribes to it, and how a project’s dependencies get gated against a policy. It is the “understand-the-machine” companion to What is OpenVet.

The audit #

An OpenVet audit is the atomic unit. Each audit is a signed statement, by one or more authors, about a specific package version identified by the subject tuple (registry, name, version, variant, hash). The audit’s content — summary, report, source-anchored annotations, structured findings, and machine-readable claims; see What is OpenVet for the full breakdown — is bundled, hashed, and signed by the author(s).

Two properties follow from “audit = signed, content-addressed object”: audits are immutable (editing an audit produces a different hash, and therefore a different audit), and they are standalone — anyone with the audit bytes and the author’s public key can verify it without consulting any server.

The log #

Every author or organisation owns one log: an append-only chain of commits, where each commit records one change to the log’s state. The most common change is adding or updating an audit, but commits also carry keyset changes (e.g. adding a new signing key). A log lives at a URL — https://openvet.org/alice on the public registry, or any URL for a self-hosted log.

Three properties define the trust shape of a log:

  • Append-only. Older commits stay; new commits get appended. Nothing in the log is rewritten after the fact.
  • Content-addressed. Every commit and every audit is referenced by its content hash. Tampering with any byte changes the hash; downstream references stop matching.
  • Owner-signed. Every commit is signed by a key from the log’s keyset, so each commit is cryptographically tied to the log’s owner.
Alice's log
┌──────────┐   ┌──────────┐   ┌──────────┐   ┌──────────┐
│ commit 0 │ ← │ commit 1 │ ← │ commit 2 │ ← │ commit 3 │  ← head
└──────────┘   └──────────┘   └──────────┘   └──────────┘
  +audit         +audit         +keyset        +audit
  libfoo@1.0     libbar@2.0     (new key)      libbaz@0.1
  sig: alice     sig: alice     sig: alice     sig: alice

Under the hood, audits within a log are indexed by a content-addressable Merkle Search Tree, which makes audit lookup efficient. Commits are linked with a skipchain, which makes verifying a new head against your pinned one fast — O(log n) instead of O(n). And because everything in a log is content-addressed, the data is highly cacheable. The net result: openvet check finishes in milliseconds, even on projects with a thousand-plus dependencies. The wire details live in the specification; for day-to-day use you do not need them.

Publishing an audit #

To publish, the author drafts the audit, signs it with their key, and appends it to their log as a new commit. The CLI handles the publishing handshake; the server validates the commit (signature shape, keyset authority, audit well-formedness, tree-state transitions) and persists it. Once accepted, the new commit’s hash becomes the log’s head.

From a consumer’s perspective, nothing happens automatically. The consumer pulls in the new commit (and the new audit it carries) the next time they fetch the log.

Subscribing to a log #

On the consumer side, you declare which logs you trust in your project’s openvet.toml:

[log.alice]
url = "https://openvet.org/alice"

The first time the CLI fetches the log, it pulls the current head, verifies it independently (signature + commit shape), and pins the hash in openvet.lock. This first fetch is trust-on-first-use — there is no prior state to compare against. Subsequent fetches pull the new head and verify that it is a descendant of the pinned head, walking the skipchain back to confirm. If the verification succeeds, the pin advances. If it fails — a commit you previously pinned is no longer present, suggesting the log was rewritten — the fetch aborts and surfaces the problem.

This pin-and-verify pattern is what makes the server hosting the log unnecessary to trust. The server’s job is to serve bytes; the cryptographic verification happens on the consumer’s machine.

The policy #

A policy is a set of named requirements, declared in openvet.toml:

[requirement]
safe-to-deploy = "is-benign and (not uses-unsafe or unsafe-safe)"

Each requirement is a boolean expression over claim names. When the expression is evaluated against an audit, each atom is looked up in the audit’s claims map and resolves to one of three values:

  • True — the audit asserts the claim is true.
  • False — the audit asserts the claim is false.
  • Unknown — the audit doesn’t mention the claim.

The expression then composes under Kleene’s three-valued logic with and, or, not, and implies.

For a given subject and requirement, the verdict collapses across all relevant audits:

A requirement passes for a subject iff at least one audit evaluates to True and no audit evaluates to False.

The “no False” rule means a single contradicting audit can sink a requirement that would otherwise pass. This is intentional: if you do not want a particular auditor’s veto, you stop subscribing to their log (or alias the disagreeing claim). Per-subject overrides ([[override]]) let you patch the effective requirement set for specific packages.

The check #

openvet check composes everything above into a verdict.

For each lockfile listed in openvet.toml, the CLI extracts the project’s dependencies as subject tuples. For each subject, it looks up the matching audits across every subscribed log. Each requirement is then evaluated against those audits using the rules above. A subject passes iff every applicable requirement passes; the project passes iff every subject passes. A failure means a non-zero exit and per-subject failure reasons printed to stderr.

  Lockfile          Subscribed logs            Policy           Verdict
  --------          ---------------            ------           -------
  libfoo@1.0  ──→   alice: audit(libfoo)
                    bob:   audit(libfoo)       safe-to-deploy   PASS

  libbar@2.0  ──→   alice: audit(libbar)       safe-to-deploy   PASS

  libbaz@0.1  ──→   (no audit)                 safe-to-deploy   FAIL
                                                                (no audit)

The verdict is deterministic: given the same openvet.toml, the same openvet.lock, and the same project lockfile, every machine produces the same result. That is what makes openvet check safe to run in CI.

Further reading #

This page covered the mechanics. Deeper questions — who you have to trust (and who you don’t), what happens if a signing key is compromised, what guarantees mirroring gives — live in Trust model. The exact wire formats and field-level details live in the Specification.