Documentation · v0.6.70
Prove the tests ran before you merge.
Relay does not read your code and judge it. It answers one question: is there signed evidence that the tests ran, and did they cover the lines that changed?
This is the reference. If you have not run Relay before, start here instead — two lines of shell, about a minute, and everything after happens in the session.
Security advisory · 2026-08-08 · fixed in 0.6.0
A receipt could be bypassed by an in-place edit
Every version before 0.6.0 was affected, 0.2.0 through 0.5.0. A receipt is documented to go stale once the worktree moves. It did not: an edit that changed a file’s contents without changing which files changed left the binding intact, so code introduced after the evidence was signed still returned PASS. No policy setting closed it.
Fixed in 0.6.0. Receipts now record a content hash for every changed surface at signing time, and the verdict compares them. Upgrade with the install command below and check relayevals --version.
Upgrading invalidates existing receipts, and that is the fix rather than a side effect of it — every earlier receipt certifies a command ran against a set of changed paths, not against the file contents in front of you. You will see RECEIPT_PREDATES_SNAPSHOT_BINDING, deliberately not RECEIPT_SNAPSHOT_STALE: that code means “you edited after running”, and you did not. One relayevals next per repository clears it.
§ 01
Install
macOS and Linux. No sudo, nothing outside your home directory. On Windows, use WSL2 and follow the Linux path — the installer is a bash script and there is no native Windows build.
If Node is missing the installer downloads a pinned, sha256-verified Node 22 and uses it — you do not need to install anything first. git ≥ 2.30 is the only prerequisite.
Check the bytes first
Piping a URL to bash executes whatever that URL returns. If that bothers you — it reasonably should — verify before running:
For 0.6.70 that must be exactly:
Check the byte count as well as the hash. A truncated download hashes to a stable, plausible, wrong value — the count tells you why immediately instead of leaving you suspecting a bad mirror.
Where it goes
Set RELAY_HOME to relocate the root. Reinstalling is idempotent and upgrading is the same command. Nothing is written into your repositories.
Confirm it worked
Alpine and other musl systems are refused up front with exit 78. TypeScript 7 ships a native compiler and publishes no musl build, so a source install cannot work there — saying so in two seconds beats failing after a 40 MB download.
§ 02
See the problem first
Before configuring anything. try detects your test runner and coverage tool, runs your suite once, and reports which of your changed lines no test executed. It needs no setup and writes nothing.
Exit 1 means it found changed lines nothing executes. If your project has no coverage tool, try says exactly which one to install for your stack rather than guessing a number.
§ 03
Set up a repository
Once per repo.
relay.policy.json is committed — it is shared team configuration and CI cannot read it otherwise. .relay/ is not; setup adds it to your .gitignore, because receipt run writes there and an untracked .relay/ would make every receipt stale the instant it was signed.
Then confirm you are ready:
check reports every remaining gap as next_actions[] with a runnable command — the same shape verdict uses.
§ 03
The loop
Hand this to your agent. It is the entire integration — one command, and the agent never has to choose between them.
next resolves the repository into exactly one state and returns exactly one allowed action — including on a repository it has never seen, where the action is relayevals setup. The agent needs no other knowledge of Relay.
Exit codes are the contract an agent branches on: 0 PASS / READY / SETUP_NEEDED, 10 BLOCK, 20 UNRESOLVED, 30–39 environment, 40 NEEDS_HUMAN.
If the same finding survives three identical attempts — or ten in total — Relay stops the loop and hands control to you rather than letting an agent burn tokens on it. relayevals explain says why, in plain language, with no agent involved.
Hand it back: relayevals fix
On a BLOCK, Relay prints the evidence behind the verdict and the exact condition that clears it — ready to paste into a fresh agent session. The same text is on the fix_prompt field of the JSON.
No model is involved — this is templated from the measurement Relay already made. Your agent has the code; Relay contributes the two things only it has: what the evidence actually showed, and what would clear the finding. Reading it never advances the attempt counters.
By hand
The whole product works without an agent. guard runs your suite, signs the receipt, and decides, in one step:
The two-step form still works and is exactly what guard does internally:
verdict reads the newest receipt of each kind from .relay/receipts on its own. Do not pass a tests-*.json glob — it takes one positional task argument, so a glob matching two receipts fails with too many arguments.
The exit code is the answer: 0 PASS, 1 BLOCK, 2 UNRESOLVED.
The receipt binds to the worktree as it was during the run. Edit a file afterwards and it stops counting. Evidence last, always.
Every state next can return
Branch on state and code, never on prose. Each state also has a page of its own at relayevals.com/docs/states/<code>.
| state | exit | what it means | what to do |
|---|---|---|---|
PASS | 0 | evidence exists and covers the change | report it verbatim; you are done |
READY | 0 | configured, nothing changed yet | stop — terminal for the loop |
SETUP_NEEDED | 0 | never configured | run relayevals setup |
BLOCK | 10 | a finding needs work | do the returned action, or relayevals fix |
NO_TEST_RUNNER | 10 | no runner Relay can drive | add a test script, then retry |
UNRESOLVED | 20 | not enough evidence to decide | run the returned receipt run |
NOT_A_REPO | 30 | not inside a git repository | stop; tell your human |
SETUP_PARTIAL | 31 | configured, but not finished | run relayevals setup again |
SESSION_LOCKED | 32 | another Relay run holds the lock | wait and retry |
GRAMMAR_MISSING | 33 | no grammar for this language | relayevals grammar install |
UNSUPPORTED_PLATFORM | 34 | Relay cannot run here | stop; tell your human |
NEEDS_HUMAN | 40 | only a person can clear this | stop and say why |
PASS, READY and NEEDS_HUMAN are the three states that end a loop. A missing signing key arrives as NEEDS_HUMAN carrying the code IDENTITY_MISSING — it is a code, never a state, so do not branch on it as one.
Two commands, two schemes. The numbers above belong to next, which reports the state of the loop. verdict and guard report the decision instead: 0 PASS, 1 BLOCK, 2 UNRESOLVED. Their failure codes are not the same as each other — verdict distinguishes usage (64), evidence and IO (74) and a malformed policy (78), while guard reports 70 for every runtime failure without exception. 70 is the catch-all in both and the one you will see most. An agent in the loop branches on next; CI gates on whichever command it runs.
The same thing, for a person
next answers an agent. explain answers you — the same state, walked through in plain language, with no JSON and nothing to parse.
§ 04
Produce evidence
receipt run executes your command verbatim, hashes both output streams, binds the result to the current git snapshot, and signs the envelope. Everything after -- is yours.
| Flag | Meaning |
|---|---|
--name | Label, used in the receipt filename. |
--producer | Which signing identity to use. |
--kind | command, test, typecheck, lint, coverage, build, security, inventory, custom. Policy’s mandatory_kinds matches on this. |
--surface | Glob this run vouches for. Repeatable. |
--coverage | Attach a real coverage report. See § 05. |
--timeout | Wall clock in ms. Default 300000. |
--set-env, --inherit-env | Child environment. Everything else is sanitized. |
Without --surface the receipt still proves the command ran — it just vouches for no particular file, so every changed file comes back SURFACE_UNCOVERED.
§ 05
Measured coverage
This is the feature that makes the gate real. Without it, --surface src/api/upload.ts is simply believed — you could run a suite that never touches the file, declare it, and get a PASS.
The report is read after your command exits and folded into the receipt before signing, so you cannot edit it afterwards. Two things follow:
- Your test command must write the report during this run. A report left from an earlier run is recorded with
generated_after_command: falseand does not count. Stale evidence that looks fresh is worse than none. - Only the changed lines must be hit. Not a percentage, not the whole file.
What setup writes. A new repository gets coverage_evidence: "measured" — a declared surface is checked against a real coverage report. setup never rewrites an existing relay.policy.json, so a repository that predates this setting keeps what it had, which for a policy written before 0.5 means declared. That is the upgrade-safety rule, and it applies to existing repositories only. Check yours with relayevals check, which names the mode every time it runs.
Under declared a --surface claim is believed rather than checked, so a change no test exercises can still reach PASS. A repository with no policy file at all is treated as declared for the same reason — Relay will not invent a stricter gate than you asked for. If check tells you the repository is in declared, that is the one field worth changing — see § 10.
And coverage_enforcement. The default is "warn", which makes a coverage gap UNRESOLVED rather than BLOCK: Relay is saying it could not prove the change ran, not that the change is wrong. Set it to "block" when you want the gate to stop merges.
This matters in CI. Under the default, a coverage gap exits 2, so a job written to fail only on exit 1 lets it through. Fail on anything other than 0.
§ 06
Read the verdict
In a loop, ask only for what you will act on — you pay tokens per iteration:
--only accepts next_actions, why, requirements, evidence, changed_surfaces, verdict, decision_hash.
Branch on code, never on reason
code is contract. reason is prose for a human reading your transcript and may be reworded in any release. commands are runnable, with <…> marking what you substitute.
An error is not a verdict
If the output’s schema is relay:error:v1, no decision was reached. Your work may be perfect and your invocation wrong. Fix the invocation — do not rewrite code in response to an error envelope.
Exit codes
| 0 | PASS — evidence exists and covers the change |
| 1 | BLOCK — something about the change needs work |
| 2 | UNRESOLVED — not enough information to decide |
| 64 | Bad invocation |
| 65 | Malformed input data |
| 70 | Internal error |
| 74 | I/O error |
| 77 | Signing key is protected — see § 11 |
| 78 | Configuration or platform is unsupported |
Verdicts are deterministic. Identical inputs give an identical decision_hash, so re-running without editing cannot produce a different answer.
§ 07
Verdict codes
Every code carries its own remediation record. You never have to hardcode this — ask at runtime:
It returns meaning, clears_when (the condition, not a ritual), commands[], and blocks_the_change. Branch on that last field: false means the finding is about your invocation or is advisory, and rewriting working code in response is a wasted loop.
Your change needs work
SYMBOL_UNTESTED | Write a test exercising the named symbol. The reason says how many of its changed lines already run — partial progress is real progress. |
SURFACE_UNTESTED | Same, at file granularity. Your tests ran but never reached the changed lines. |
SURFACE_UNCOVERED | No receipt declares this file. Add --surface — only if your tests genuinely cover it. |
RECEIPT_COMMAND_FAILED | Your tests failed. Reproduce with commands[0], fix, re-run. Re-signing does not help; the receipt is accurate. |
RECEIPT_SNAPSHOT_STALE | You edited after running. Re-run receipt run. |
RECEIPT_KIND_MISSING | Produce a receipt of the kind policy requires. |
RECEIPT_SIGNATURE_INVALID | Altered, or signed by an unknown key. Do not touch the code. |
RECEIPT_NOT_SUPERVISED | Re-run under relayevals supervise. See § 11. |
Your invocation needs work
COVERAGE_MISSING | Add --coverage. Do not touch your code — nothing is known to be wrong with it. |
SEMANTIC_UNAVAILABLE | relayevals grammar install <language> |
CHANGE_TEST_ONLY | You changed tests and no source. Often fine — but check you did not weaken an assertion to clear a failure. |
Stop
ATTEMPT_THRASHING | Three changes have failed to move the same finding. Report what you tried and what still fails, and ask for human review. This is not a rate limit — it means the approach is not working. |
§ 08
Semantic analysis
Without it, every changed file demands evidence — including one where you only rewrote a comment or reran the formatter. That is noise, and noise in the loop is a work order for something that cannot break.
Once per machine. Optional: skipping it costs precision, not safety.
| Change | Demands evidence? |
|---|---|
| Comment or docstring only | no |
| Formatting only | no |
| Import reordering, no new module | no |
| Statement, expression, control flow | yes |
| Signature or exported symbol | yes, flagged as a contract change |
| Anything unrecognised | yes — fail safe |
Do not read an exemption as approval. It proves the change cannot alter behaviour, not that it is correct.
With a grammar installed, a BLOCK names the symbol rather than the file:
§ 09
Attempt chains
A single verdict cannot tell you two things you need: did my last change help, and did it break something else? Every verdict carries an attempt block that does.
resolved_since_previous— your last change worked. Continue.introduced_since_previous— you broke something else. Weigh that before piling on.- Both empty and
attemptclimbing — you are not moving. Escalate before Relay tells you to.
Chains are keyed by task plus repository, so keep the task string identical across attempts. Changing it starts a new chain and discards the history. Re-running verdict without editing is polling, not thrashing — it does not count against you.
§ 10
Policy
relay.policy.json is the gate, committed to the repo. This is exactly what setup writes for a new repository — an existing policy is never rewritten, so upgrading keeps whatever you had. What the two coverage fields mean, and what they do to a first run, is in § 05.
coverage_evidence | declared believes --surface. measured checks it against the report and produces SURFACE_UNTESTED naming the exact lines. This is the setting that makes Relay worth running. |
mandatory_kinds | Receipt kinds that must be present. |
coverage_enforcement | What a coverage gap costs. block makes it BLOCK (exit 1), warn makes it UNRESOLVED (exit 2), off disables the check. Distinct from coverage_evidence, which decides whether coverage is checked at all. |
require_semantic | An array of languages whose grammar must be installed, else UNRESOLVED — not a boolean. Empty means fall back to file granularity rather than refusing to decide. |
test_globs | What counts as a test file. Empty falls back to built-in defaults (**/*.test.*, tests/**, and similar), so an empty array is not the same as “no test files”. |
flag_test_only_changes | Raise CHANGE_TEST_ONLY when no source moved. |
thrash_threshold | Attempts against one unmoved finding before ATTEMPT_THRASHING. |
require_supervised | Only accept receipts signed by a supervisor. |
Do not loosen the policy to make a verdict pass. Weakening the gate you are being measured by is not completing the task.
§ 11
Accounts
The verification path needs no account and no network: relayevals try, setup, receipt and verdict all run offline, free and unlimited, permanently. A verdict makes no network call on any plan. The relayevals session is the exception — it signs in on first run.
Relay prints a code, opens your browser, and confirms back in the terminal. Nothing is typed into the terminal: the code appears here, you enter it in the browser, and the browser shows you which machine is asking before you approve anything.
What signing in buys. The receipts and verdicts already on this machine are attributed to your account retroactively — the count on that last line is the whole argument. History follows you across machines, and a team can verify each other’s receipts without exchanging keys. On a first machine with no history that line reads Receipts will attribute as you produce them instead.
What it does not change. Every verdict is still produced locally and offline. If login fails, or you never run it, the gate works exactly the same.
If your signing key is protected
Under producer protect (§ 12) the key is encrypted at rest. Login does not prompt for the passphrase — unlock once and it is cached for this machine.
This is the only command that reads a passphrase.
If login cannot reach the server
A network problem and a key problem are reported separately, so the message always names the one thing that actually went wrong rather than listing everything it could have been.
§ 11b
Teams
A team is what turns the gate from something you run into something your repositories enforce. Everything below lives in the console at relayevals.com/teams — the nav links it as Console.
What the plan actually gates
One thing, and it is worth being exact about: the CI signing credential. A repository registered to a team on the Team plan can exchange a GitHub OIDC token for a short-lived signing key, which is what puts a signed check on every pull request and fills the analytics and audit history. Everything else — the whole local loop, the drive standard in relay.policy.json, shared memory through your own git, verifying a teammate’s receipt — works on every plan, deliberately.
Inviting people
An owner mints an invitation on the members tab and sends it themselves. There is no email system in Relay and the console says so rather than implying a message is on its way: you get a link, a copy button, and a hand-off to your own mail client. Links expire in seven days.
Whoever accepts signs in with GitHub and lands in the team. They then run relayevals login on each machine they work from — that is what registers the machine and lets the rest of the team verify its receipts.
Seats and billing
Seats are developers, not dashboards. The count that bills is the one on the subscription, and the members tab shows it beside the number of people who have accepted so the two never drift silently. Billing is centralised with the owner: an owner opens the Polar portal from the members tab for cards, invoices, seat changes and cancellation; members see the plan and its renewal date and nothing they could accidentally change.
The org baseline
An owner publishes one policy on the policy tab, versioned and append-only. It is enforced at the gate. ci auth writes it beside the credential, and ci report compares your repository policy against it on every run: a repository that TIGHTENS the baseline is fine, one that weakens it fails with POLICY_DRIFT naming the field and both values. The check is a file comparison, so it keeps working during our outage.
Machines
Every signed-in machine appears on the members tab, and an owner can revoke one. Revocation is honest about its limits: it takes effect at the next credential refresh, and a certificate already cached on a machine stays acceptable to an offline verifier until it expires.
From the terminal
whoami names your teams, your role and your plan; /team answers the same question inside a session. If someone buys a plan while you are signed in, your machine keeps its cached claim until you ask for a new one:
§ 12
Privacy
Your code never leaves your machine. Relay reads your repository locally, runs your test command locally, and decides locally. There is no code upload, no diff upload and no remote analysis — a verdict makes no network call at all, on any plan.
Relay does send anonymous usage counts, so we can tell whether the product works: a per-machine identifier, the event name, the version and the platform. That is the whole payload. File names, paths, task strings, branch names and repository contents are not fields in the schema, so they cannot be sent by accident. Repository, when it appears, is a salted hash computed on your machine — it distinguishes two repositories without naming either.
Turn it off and nothing is sent:
0, false, off and no all work, and CI is treated as opted out on its own. Relay prints a notice the first time before anything is sent, rather than after.
Signed in changes who those counts belong to, not what they contain: the same events are attributed to your account so your history follows you between machines.
On a team plan, the gate’s decision for repositories you register is recorded so the dashboard and audit log can show it: the repository, the verdict, how many changed lines there were and how many were covered, and the finding CODES.
Codes and nothing else — SURFACE_UNTESTED, not the file it was about. The endpoint validates every finding against ^[A-Z][A-Z0-9_]{2,47}$, which cannot express a path: no lowercase, no slashes, no dots. The CLI drops the human-readable explanation before sending for the same reason, because that sentence may quote a filename and a database is the wrong place for it. So a team plan records what happened, never where.
§ 13
Protecting the key
Under a plain install the signing key is a file the agent can read, so a receipt proves only that something with the key made a claim. Encrypting it moves signing out of the agent’s reach.
The key is encrypted at rest with scrypt and AES-256-GCM. The passphrase is read from stdin only — never an argument, never an environment variable, because both are readable from the process table by anything running as you.
After that, receipt run exits 77 outside a session. That is not a failure. Start the agent inside a supervisor:
Inside it, receipt run is invoked exactly as normal — it routes to the supervisor over a unix socket automatically. Nothing about the commands changes. An agent that sees exit 77 should report that it needs to be started under supervise, not go looking for the key.
§ 14
Signed verdicts
A verdict artifact records a decision. Signing it with the authority key records who accepted that decision — an unsigned artifact proves nothing about approval.
Verification runs three independent checks:
- The Ed25519 signature over the canonical bytes.
- The
decision_hash, over the decision core — so timestamps and rendering options cannot change it. - Re-derivation of the verdict from the artifact’s own embedded evidence. This one needs no key, and it is what makes an artifact checkable by someone who does not trust you.
§ 15
CI
Do not use the interactive installer in a pipeline. There are two ways to give CI a signing identity.
Self-managed key — any CI, no account
The key and its authority both come from your secret store:
This means a long-lived private key sits in your CI secrets, shared across the team and rotated by hand. It works, and for a solo repository it is fine. The section below removes that key entirely.
No stored key — GitHub Actions
On GitHub Actions nothing needs to be stored. GitHub proves the workflow’s identity with an OIDC token, Relay verifies it against GitHub’s published keys, and issues a signing credential that expires in fifteen minutes and is bound to that one run.
ci auth writes the credential to $RUNNER_TEMP/relay-ci, which the runner deletes between jobs, and exposes the path as a step output. Nothing goes in repository secrets — not long-lived ones, not short-lived ones. Credentials are revocable from your team dashboard, and relayevals ci report --verdict-file <path> sends the decision back as counts and codes.
Three things must be true first, or ci auth answers 403 and the message will not tell you which — the exchange keeps its failures undifferentiated on purpose, so a stranger cannot map your estate by probing it.
- The repository is registered to a team — an owner does that in the console, at relayevals.com/teams → repositories.
- That team is on the Team plan, and the plan has not lapsed.
- The workflow requests the token for this server. The audience must match the origin you are exchanging against —
https://relayevals.comunless you are running your own.
Do the changed tests depend on the change?
A verdict says every changed line executed under signed evidence. It does not say anything checked the result. A test that calls the changed function and asserts nothing satisfies the gate completely — and it is the first shape an agent optimising for green will find.
red-first is the measurement that separates the two. It rebuilds your repository as it was before the branch, keeps your new tests, and runs them. Tests that genuinely cover a change fail there. Tests that pass there proved nothing, and the coverage they produced was execution without verification.
It exits 0 for genuine, 21 for vacuous, and 0 for inconclusive. Passing the report to ci report --red-first-file <path> attaches a VACUOUS_TESTS finding to the check.
It needs history. “Before the branch” is the merge base with your target branch, so fetch-depth: 0 on actions/checkout is required — the default shallow clone has no common ancestor to compare against. Without it the check reports inconclusive and says why. It does not fall back to HEAD: on a branch whose change is already committed, HEAD contains the change, so that counterfactual would be identical to the change and every honest test would be called vacuous.
Advisory, deliberately. red-first runs a fresh counterfactual, and an execution is not a signed receipt — so it never moves a verdict. It reports, and your workflow decides. Relay still never says a change is safe to merge.
Pinning the version
Every release publishes its own installer, and that installer carries its version, its tarball URL and its tarball hash as literals. Fetching it pins the whole chain:
/install.sh is whatever is current, which is what you want on a laptop and not what you want in CI — a build that pins nothing installs something new the morning after a release, and the first you hear of it is a job that behaved differently. A version that was never published answers 404 rather than quietly giving you a different one.
Or use the action
The steps above, packaged. It installs the pinned CLI, exchanges the OIDC token, verifies, checks that your changed tests depend on your change, and exits with the verdict.
A composite action, not a JavaScript one. A JS action ships a bundled dist/index.js — tens of thousands of lines of vendored dependencies that every consumer runs with their OIDC token in scope and almost nobody reads. This runs the published CLI and nothing else, so what executes in your job is the artifact you can download and checksum yourself.
Installing it cannot turn a passing build red on day one: set fail-on-verdict: false to observe the gate first, and fail-on-vacuous-tests is already false. The GitHub org is relay-evals (hyphenated); the CLI and this site remain relayevals.
The gate, in either mode
The exit code is the gate. Fail the job on anything other than 0, and treat UNRESOLVED as a configuration problem rather than a code problem — it means Relay could not decide, not that your change is bad. Which numbers those are depends on which command you run; see § 17.
§ 16
Agents
Relay ships as a skill so a coding agent loads the protocol itself instead of you pasting instructions into a prompt. It travels with the install, not with your project — setup writes nothing into your repository — so point your coding agent at the installed copy:
Three rules make an agent’s loop terminate:
- Branch on
code, never onreason. - Stop on
ATTEMPT_THRASHING. Do not try a fourth time. - An error envelope is not a verdict. Check
schemafirst.
The important consequence of the design: there is no prompt to persuade. A BLOCK clears when the evidence exists and not before.
Run Relay when your agent stops
Installs a hook so Relay runs the moment your coding agent reports it is done, instead of you remembering to ask. The verdict lands in the same place the agent says “finished”, which is the point.
claude is the only coding agent wired up today. Others follow the same pattern when they land.
What Relay has caught for you
report reads the receipts and verdicts already on this machine and totals them over the last 30 days. It runs offline and covers this machine only.
The number most people quote back to us is the ratio — how often a change arrived with no test touching the lines that moved.
§ 17
Command reference
§ 18
Troubleshooting
relayevals: command not found | ~/.local/bin is not on your PATH, or your shell cached the old lookup. Open a new shell, or run hash -r. |
--version prints 0.0.0-dev | The install did not complete its version step. Reinstall rather than continuing — that build is not one you can identify later. |
| An old install shadows the new one | Versions before 0.4.4 wrote to /usr/local, which takes priority on PATH. Remove /usr/local/bin/relayevals and /usr/local/libexec/relay. |
this installer was not finalised | You have an installer whose pin was never filled. It is not publishable and refuses to run. Re-download from relayevals.com. |
Exit 77 on receipt run | Not a failure. The key is encrypted — see § 11. |
Exit 2 with COVERAGE_MISSING | Policy asks for measured coverage and no receipt carried a report. Add --coverage. Nothing is wrong with your code, which is why this is UNRESOLVED and not BLOCK. |
npm ci fails during install | The installer prints the head of npm’s output, which names the cause on the first line. Read that line before anything else. |
| A verdict you did not expect | relayevals verdict --explain <CODE> --format json returns what it means, what clears it, and whether it is about your change at all. |
Uninstalling
Per-repository state is .relay/ and relay.policy.json. Your data home — signing keys and grammars — is separate; find it with relayevals config path --format json before deleting anything. Deleting it destroys your producer key. Receipts already signed stay verifiable by anyone holding the public key, but you cannot sign as that producer again.
§ 19
What it does not prove
The limits are part of the product. Keep them straight when you summarise your work.
- A PASS means the evidence exists and covers the change. It does not mean the change is correct.
- Coverage proves a line ran, not that anything was asserted about it.
- An unsigned verdict artifact is not approval by anyone.
- A semantic exemption proves a change cannot alter behaviour, not that it is a good change.
A tool that overstates what it knows is worse than no tool, because you stop checking.
Reference for relayevals 0.6.70. Every code and schema is also available offline: relayevals verdict --explain <CODE> --format json.