Evidence

What the green checks hid

For twelve days a team wiki's backup reported success while it saved none of the files the team had uploaded. Every check was green. This page is three of those moments, what was actually true, a restore measured on a machine that had never run the stack, and the system that catches the difference, including the mistakes of the AI agent that writes most of this code.

repositories under one standard
91
restore scripts, all run by CI
73of 73
clean-machine restores measured
45
releases tagged
1231

Counted by fleet-ops, which rewrites the numbers when one changes. Last changed 2026-10-13 20:26 UTC.

Case one · a backup of nothing

12 days, 8 releases, and not one attachment in any backup

The dashboard said
Data backup OK
CI: data archive opens    ✓
What was true

Release 2.0.0 moved Outline's file storage from MinIO to Garage. The backup kept archiving the old MinIO volume, which nothing writes to any more. The restore script looked for a service that no longer existed and would have stopped before doing anything.

Exposure

A team restoring after a lost disk gets its documents back with every image and attachment missing. Every log line in those twelve days would have said OK.

An archive that opens proves the archive. CI now proves the backup the way a user would notice it missing: it stores a file through S3, takes a backup, stores a second file, restores, and requires the first to read back and the second to be gone. The restore uses Garage's own consistent metadata snapshot rather than a copy of a live database.

Case two · the restore nobody ran

72 restore scripts, some never run by CI since May 2021

The dashboard said
test_restore_roundtrip   PASS
(every push, 47 templates)
What was true

Each test restored with its own copy of the commands. The scripts a person would run were never executed. Four were written for a different vendor's image, and one of those refused to run at all. One signed in with the wrong account. Several merged the backup into live data instead of replacing it.

Exposure

The day you need a restore would have been the first day the script ran. In 16 of the 47 templates that script was more than three years old.

A decade of production infrastructure taught me one pattern above the rest: a backup that has never been restored is a belief. The tests now run the shipped script, and a fleet rule fails any repository whose restore script no test invokes. A comment or an echo naming the script does not count. Its list of exceptions can only shrink, and it has been empty since the day it was written.

Case three · the check that could not fail

17 hours healthy over a crashed server

The dashboard said
docker inspect → healthy
What was true

A game server had crashed and dropped two players mid-round. The health check searched for the process with pgrep -f, which matches its own command line. It found itself, every time.

Exposure

Time to detect was never. Docker does not restart an unhealthy container on its own, and this one was never reported unhealthy.

The rule it produced: a check whose pattern can match the check itself has never failed, so prove the red case against a real container before believing the green one. The fix shipped to every game-server template with that test, and every fleet rule since has been tested against a planted violation.

The question a CISO asks first

The host is gone. How long until it works again?

Every week, for each template below, one runner starts the previous release, writes a marker row into the database and a marker file into the data, and takes a backup. Only what an operator keeps off the host leaves it: the backup files and the environment file. A second runner that has never run the stack then brings the current release back from those files alone, with the scripts the template ships, and the clock stops when the application answers over HTTPS with both markers back. So the number below is an older backup restored into a newer release, on an empty machine, image downloads included.

Fastest 17 s · slowest 6 min 12 s

TemplateBackup taken on → restored intoEmpty machine to answeringOf that, restoringBackup everyLast run
AFFiNEv1.9.3 → v1.9.41 min 1 s22 s24 h2026-10-11
Audiobookshelfv1.1.4 → v1.1.535 s11 s24 h2026-10-11
Autheliav1.8.3 → v1.8.437 s11 s24 h2026-10-11
Beszelv1.1.3 → v1.1.432 s11 s24 h2026-10-11
Bitbucketv1.9.4 → v1.9.51 min 3 s24 s24 h2026-10-11
Confluencev1.9.6 → main1 min 41 s43 s24 h2026-10-11
Difyv1.1.1 → v1.1.25 min 3 s2 min 48 s24 h2026-10-11
Diunv1.0.3 → v1.0.524 s10 s24 h2026-10-11
Docmostv1.9.3 → v1.9.41 min 25 s28 s24 h2026-10-11
Dozzlev2.1.6 → v2.1.730 s11 s24 h2026-10-11
Forgejov1.1.2 → v1.1.340 s12 s24 h2026-10-11
Gaseousv1.3.2 → v1.3.31 min 43 s1 min 3 s24 h2026-10-11
Ghostv1.9.4 → v1.9.51 min 37 s25 s24 h2026-10-11
Giteav2.0.3 → v2.0.448 s22 s24 h2026-10-11
GLPIv1.9.2 → v1.9.31 min 21 s42 s24 h2026-10-11
Grafanav1.8.2 → v1.8.31 min 47 s58 s24 h2026-10-11
Home Assistantv1.7.3 → v1.7.41 min 20 s15 s24 h2026-10-11
Homeboxv1.7.1 → v1.7.231 s10 s24 h2026-10-11
Homepagev1.1.2 → v1.1.334 s11 s24 h2026-10-11
Immichv2.1.3 → v2.1.41 min 52 s44 s24 h2026-10-11
Jellyfinv1.2.5 → v1.2.654 s11 s24 h2026-10-11
Jirav1.8.5 → v1.8.66 min 12 s2 min 1 s24 h2026-10-11
Joomlav1.9.5 → v1.9.61 min 8 s34 s24 h2026-10-11
Keycloakv1.8.5 → v1.8.61 min 29 s23 s24 h2026-10-11
Mailuv1.8.5 → v1.8.62 min 4 s42 s24 h2026-10-11
Mattermostv1.9.3 → v1.9.41 min 58 s1 min 2 s24 h2026-10-11
SQL Serverv1.7.1 → v1.7.236 s1 s24 h2026-10-11
Navidromev1.1.2 → v1.1.336 s11 s24 h2026-10-11
Nextcloud + ONLYOFFICEv2.1.6 → v2.1.72 min 51 s1 min 33 s24 h2026-10-11
Nextcloudv2.1.4 → v2.1.52 min 32 s1 min 29 s24 h2026-10-11
Open WebUI + Ollamathe last drill failed, so no time is quoted2026-10-11
OTRS (Znuny)the last drill failed, so no time is quoted2026-10-11
Outline + Keycloakv2.2.5 → v2.2.62 min 36 s41 s—2026-10-11
ownCloudv1.9.9 → v1.9.101 min 34 s43 s24 h2026-10-11
Portainerv1.7.2 → v1.7.322 s6 s24 h2026-10-11
Rocket.Chatv1.8.3 → v1.8.41 min 35 s24 s24 h2026-10-11
RomMv1.1.2 → v1.1.33 min 45 s2 min 12 s24 h2026-10-11
Seafilev1.9.3 → v1.9.41 min 23 s29 s24 h2026-10-11
SiYuanv1.6.4 → v1.6.517 s8 s24 h2026-10-11
SonarQubev1.9.2 → v1.9.33 min 14 s1 min 40 s24 h2026-10-11
Uptime Kumav1.1.2 → v1.1.353 s10 s24 h2026-10-11
Vaultwardenv1.7.2 → v1.7.335 s11 s24 h2026-10-11
Wiki.jsv2.1.3 → v2.1.451 s11 s24 h2026-10-11
WordPressv1.10.4 → v1.10.51 min 19 s35 s24 h2026-10-11
XWikiv1.9.4 → v1.9.52 min 14 s58 s24 h2026-10-11
Zabbix (no proxy)v1.9.3 → v1.9.459 s14 s24 h2026-10-11
Zabbixv1.10.2 → v1.10.354 s14 s24 h2026-10-11

A drill that fails is listed as failing and carries no time. A restore time from a restore that did not work is not a time.

In the words of a vendor questionnaire. The recovery time objective is the measured column, not a target somebody wrote down. The recovery point is the backup interval each template ships with, read from the file an operator copies; shortening it is one variable. The date of the last recovery test is the last column, and while the drill stays green it is never more than a week old.

Scored by someone else

OpenSSF Scorecard, across the whole fleet, low marks included

Scorecard runs its own checks against every public repository here and publishes the result. The fleet reads that result each morning and repeats it, rather than awarding itself a number. Median 7.3 of 10 across 100 repositories; the lowest is 6.5, fleet-ops. Below, every check that is under ten somewhere, with how many repositories and what that means here. A check Scorecard marks as not applicable is not counted.

CheckBelow tenWhat it means here
Branch-Protection100Until 24 September no rule stopped main being rewritten. Every default branch now blocks force-push and deletion; required reviews are refused on purpose. Scorecard re-scores weekly, so this count lags the rollout.
CII-Best-Practices100Filled in on 25 September for every repository that is not documentation alone, with the answers public; the section below says which are short of passing and why. Scorecard re-reads the badge site weekly, so this count lags.
Code-Review100One maintainer and an agent. Nothing merges on a second person's review; the review is CI against planted violations, and this row will stay at zero for as long as that is true.
Contributors100No reading yet. Listed, not excused.
Fuzzing100There is no code here to fuzz: compose files, shell, and workflows.
Pinned-Dependencies100One line: the SLSA generator that signs every release is referenced by tag, because it halts when referenced by commit. Every other action in the fleet is pinned by commit SHA. Scorecard scores that line as 8 of 10 on every repository that carries the release workflow, and the trade was made knowingly; see decision 17.
Maintained37Scorecard scores zero for any repository younger than ninety days whatever it does, and this summer's wave created most of these. It resolves itself.
SAST31The templates upload Trivy results as SARIF, which counts; the tools and documents around them run no scanner.
CI-Tests9Repositories with nothing to run on a pull request, mostly documents. Listed, not excused.
Binary-Artifacts3No reading yet. Listed, not excused.
Signed-Releases3Since 24 September every release carries a git archive, a keyless Sigstore signature and SLSA provenance, and the last five of each repository were given the same. Scorecard re-scores weekly; a repository still here has releases it has not re-read yet.
License2No reading yet. Listed, not excused.
Token-Permissions1No reading yet. Listed, not excused.

Read from Scorecard's own API, results dated 2026-10-13. The decision to leave Code-Review at zero, and why, is decision 16.

Answered by me, published by them

OpenSSF Best Practices, with every answer public and the short ones named

The badge is a questionnaire, not a scan: sixty-seven criteria, each answered Met, Unmet or N/A with a sentence saying why, and OpenSSF publishes the answers beside the badge. A badge is only as honest as its answers, so each one here names the workflow or file that makes it true, and a repository with no test suite says Unmet where it is unmet. Passing on 90 of 90 registered repositories.

RepositoryAnsweredWhy it is short
Nothing registered is short of passing today.

Read from the badge site's own API, answers last changed 2026-09-27; 0 could not be read at the last count and are in neither number. The 10 public repositories that hold no code to rate are not registered. Why a criterion that is unmet is answered Unmet rather than Met is decision 18.

Threat model

What an attacker, or an ordinary Tuesday, would try, and what answers

A control that is not tied to a threat is a feature. Each row names the failure first, then the control, then where the control is proven to work, which for a public template is a file a reader can open.

The failureWhat answers itProven where
Upstream publishes a different image under the same tagEvery image is pinned by digest; a daily job re-resolves each pin and goes red when upstream movedfreshness.yml
A GitHub Action's tag is moved to hostile codeActions pinned by commit SHA; write permissions on the one job that writes; Dependabot moves the SHA through a pull request that merges only after the repository's own verification is greendependabot-automerge.yml, Scorecard Token-Permissions and Pinned-Dependencies
A vulnerable image ships and nobody looksTrivy scans every image on every push and daily, results to the Security tab; the digest bump arrives from Dependabot within the weekdeployment-verification.yml
A credential lands in git.env is ignored, the compose file refuses to render without its required variables, and a repository whose history ever carried a value says so in its README with the rotation procedureSECURITY.md, and the pre-rotation advisories in the ledger
The host is goneA weekly restore of the previous release's backup into the current release on a machine that has never run the stack, timedthe table above, dr-drill.yml
The backup is running and is a backup of nothingCI stores a file, backs up, stores a second, restores, and requires the first back and the second gone; the shipped restore script is what runscase one, case two
A database major reaches end of vendor support while every check stays greenA weekly job reads the vendors' own support calendars and names the pin that runs out, and whenfleet-ops, private; the issues it opens are the record
A watcher stops, and nothing watches the watcherA daily heartbeat reads every schedule's own cron and reports one that has not fired; its own run pings a switch outside GitHub that alarms on silencefleet-ops, private; two of its false alarms are in the ledger
Somebody rewrites mainA ruleset on every default branch blocks force-push and deletion; the heartbeat asks for both rules dailyScorecard Branch-Protection, above; decision 16
The agent that writes most of this gets the keysModel credentials come from workload identity federation, never a stored key; the agent never reads a decrypted secret and never changes a production serverhow the AI is run, decision 7
The tag you deploy is not the tree that was testedEvery release carries a git archive of the tag, a keyless Sigstore signature over it, verified on the runner before upload, and SLSA build provenance; the README says how to check all three with nothing from the repository trustedrelease-assets.yml, decision 17
Somebody finds a hole and has nowhere private to say soA SECURITY.md in every repository with an address, a PGP key, and a seven-day acknowledgmentSECURITY.md

How the AI is run

The agent is fast, and it is wrong on a schedule. The operating model assumes both.

An AI agent does most of the typing across this fleet, and nothing it says about its own work counts as evidence. The typing is the part anyone can buy now. What cannot be bought is the set of rules that decide what the agent's work is allowed to become.

Nothing ships on a claim
A release is cut only after the release commit itself passes a full deploy, backup and restore in CI. The agent's report that something works is not an input.
A check that cannot fail is not a check
Every fleet rule is exercised against a planted violation. A rule that stays quiet over its own violation fails the build.
Exceptions only shrink
Anything excused from a rule sits on a written list. An item that has since been fixed but is still listed is itself a failure, so the list cannot quietly become permanent.
The agent does not hold the keys
It never reads decrypted secrets and never changes a production server. A server is changed only by the agent that lives on it, from a written brief, and it reports back what it did.
Its mistakes are published
The public record of findings includes the agent's own, among them a cause it published before measuring it, and the correction.

Caught before release on 23 September 2026. Five mistakes by the agent in one day of work across 47 repositories. None of them reached a release.

What the agent got wrongWhat caught itProof
An apostrophe in a comment closed a shell quote, so Outline's backup loop never started.CI waits for a "Data backup OK" line, not for a running containerfix
A bulk rename also renamed an unrelated variable in five test suites.strict mode stopped every one of those runsfix
A new restore script was committed without its executable bit, and the same test exported the environment file, so the stack rendered without one.the test runs the shipped file, and the check that the stack must refuse to renderfix
A generated restore called a database client the image does not ship.the first CI run of the shipped scriptfix
Tests passed locally against a stale copy of the reference repository.CI, which checks against the current oneprivate repository

Green is a claim. A restore is evidence.