ADR-0030: An assertion declared to fail on a recording is reported as failing as declared
Rendered from spec at76d46f3 (2026-10-25) · its source · Edit this page
Status: Accepted
Date: 2026-09-26
Decided: 2026-09-27, by the maintainer, Wessel Verheij: with every declaration naming the constraint it expects to fail, and the successors of F10-R11 and F5-R2 locked in 0.17.0 in their place.
Context
A plugin’s proofs and contributed checks are proved against recordings of the
image it pins, so that neither its author nor any CI needs a live instance
(F10-R4). A recording names the image it was taken from by digest, and that
digest is the one the manifest pins (ARCH-R121).
plugin-plex declares the doctor check plex:claimed, which expects
/MediaContainer/claimed to be true on GET /identity. An unclaimed Plex
server hands ownership to whoever reaches it first, so the check is right to
exist. Claiming a server needs a plex.tv account, and the plugin’s CI holds
none. The one recording of /identity is of an unclaimed server, and the check
fails on it. The recording is honest, and so is the failure.
plugin-plex’s proofs job reports every assertion that is not passed as a
failure, so the job is red on every run. A permanently red job teaches its
readers to ignore it, and it hides the day something else in it fails.
F10-R11 answered part of this with fires_on: a check names a recording it
must fail on, failing there is reported as passed, and passing there is
reported as refuted. It covers checks and not proofs, carries no reason, and
reports a failure as a pass. It is in lemonfiber’s main and in no release.
Decision
A declared proof and a contributed check may each declare, per recording, the
verdict it is expected to reach there, the constraint of its expectation that
reaches it, and a reason. The only verdict it may declare is failing, and the
constraint must be one the assertion makes (F10-R12, ARCH-R130). For
plex:claimed the constraint is json at /MediaContainer/claimed.
- Failing there on the declared constraint, with every other constraint holding,
is reported as failing as declared, apart from passed and from failed,
naming the declared constraint, what the answer held there, and the reason
(
F10-R13). - Failing there on any constraint the declaration does not name is reported as
failed, naming the declared constraint and every constraint that failed, and
fails the run (
F10-R14). A declaration excuses the failure it describes and no other. - The declared constraint holding there is reported as failed, naming the
declaration as stale, and fails the run (
F10-R14). The declaration is removed in the change that made the recording pass. - The declaration applies to the recording it names and nothing else. The live
service, and every other recording, hold the assertion to its own expectation
(
F10-R15). - An assertion that could not be run on the named recording is unproven, as any
other is. A declaration never stands in for a run (
F10-R16). - The catalogue’s CI (
F5-R13) and the release train’s plugin gate (OPS-R72) each name a proof failing as declared, do not refuse on it, and do not count it as passed.
F10-R11 is superseded by F10-R12 to F10-R16, F5-R2 by F5-R13, and
OPS-R69 by OPS-R72.
fires_on is replaced by expected.
Alternatives considered
| Option | Why it lost |
|---|---|
Report failing where declared as passed (F10-R11 as written) |
A pass says the assertion holds. Here it does not, and a reader of the report would learn the opposite of what the recording shows. |
| Skip the assertion against that recording | A skipped assertion reads as one that ran. Nothing would notice the recording starting to pass, and nothing would say why the assertion was not run. |
| Record a claimed server in CI | Needs a plex.tv account held for CI, which is ruled out. |
| Hand-write a recording of a claimed server | It would carry the pinned digest and an answer that image never gave: a claim about the software rather than a recording of it. |
| Drop the check, or its fixture | The check is the finding the plugin exists to make about an unclaimed server. Without a recording it is not proved at all. |
Make the proofs job advisory |
Every other assertion in it stops being enforced with the one that is expected to fail. |
| Allow a declaration of unproven, or of any verdict | Unproven is the verdict that says nothing ran. Excusing it is the hiding this decision exists to prevent. |
| Declare the recording, not the constraint | A recording that started failing for another reason — a truncated fixture, a 500 recorded by mistake — would still read as failing as declared. The declaration would excuse a failure nobody had looked at. |
Consequences
Positive
- A plugin whose honest recordings include the state a check exists to find has a green proving run that still says so, every run, with the reason.
- A declaration cannot outlive the fact it records: the day the recording passes, the run fails and names it.
- A declaration cannot excuse a failure it does not describe: a recording that fails on anything but the named constraint fails the run.
- Proofs get the same treatment as checks. A proof’s passing state is sometimes as unrecordable as a check’s.
Negative
- The report has a fourth outcome, and every reader of it changes:
lemonfiber’s verdicts, the plugin template’s reader, the catalogue’s CI, and the release train’s plugin gate. fires_onis removed before any release carried it, solemonfiber’s unreleased implementation ofF10-R11is reworked rather than shipped.
Neutral
- A probe carries no declaration. A claim is what other services are wired on,
and a claim whose probe fails is not wired to (
F4-R6).
Affected repositories
- lemonfiber — the manifest types and generated schema (
expectedreplacingfires_on), the verdictlemonfiber plugin claimsreports, and the published contract artefacts. - sdk-ts, sdk-php — regenerated from the contract that carries the field.
- plugin-template —
.github/reader/reader.pyreports the new outcome apart from passed and failed, writes it toproofs.json, and fails only on the rest.plugin-komga,plugin-uptime-kumaandplugin-plexcarry the same file. - plugin-plex —
plex:claimeddeclares that it fails onfixtures/identity-anonymous.jsononjsonat/MediaContainer/claimed, with the reason, oncetargets.tomlnames a release that readsexpected. - lemonfiber-plugins — its CI reads the new outcome under
F5-R13. - spec —
scripts/check_plugins.pyand the two workflows that run it implementOPS-R72.
Revisit if
- A plugin needs to declare a verdict other than failing against a recording.
- Recordings of a service’s passing state become obtainable without an account, so declarations of this kind stop being needed.