feat(engine): TAG-SIGNED — a tag that names a signer, not just write access

The check sat parked as "blocked on an operator decision" on the strength of
0 of 18 repos signing. That zero was never evidence against it: it is what a
practice nobody has adopted yet looks like, and reasoning from it makes the
gate ratify the status quo it exists to move. The rule that settled it asks
something else — would a public AAA+ repository do this? Yes, and unlike the
two conventions that killed VERSION-DRIFT, there is no second convention under
which release tags are better left unattributable.

The measurement then decided the SCOPE, not the yes. Across 20 clones: 158 v*
tags, 144 annotated, 14 lightweight, 0 signed, and not one dated after
2026-08-12. A boolean "this org signs" would have failed 20 correct
repositories on day one with force-moving 144 published refs as its only
remedy — the mechanism that gets gates switched off. So the policy is a DATE in
the register (never in the engine, the rule that keeps the org-profile
exemption a flag): nothing fires today, teeth at each repo's next release.

Verified both directions, because a sweep returning zero proves nothing on its
own — a dead check returns zero too. 21 repos: 18 PREPOLICY + 3 NONE, 0 ERROR,
0 WARN; then against real git objects an unsigned post-policy newest tag does
fire ERROR.

Two exclusions keep one defect from being counted twice: a lightweight tag has
no object to carry a signature (byDesign skip naming TAG-ANNOTATED as owner),
and a pre-policy tag was correct when cut (OK, said out loud, never silence).
It answers "is it signed", never "does the forge vouch for it".

The README row ships in this commit, not after it, and the stale test count in
CLAUDE.md (230 → 241) is corrected here rather than left for the next reader.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XsPsVsvhrSaejK3cLPmnN2
This commit is contained in:
Kjell Tore Guttormsen 2026-08-13 10:51:36 +02:00
commit e6cd8983ca
9 changed files with 415 additions and 17 deletions

View file

@ -175,6 +175,37 @@ never *passing*.
domain packs owes no command, and adding a hollow test script to clear a finding
that was never against it is the anti-pattern in its purest form.
## Tag signing — forward only, and never by rewriting history
Since 0.11.0 the gate judges whether a release tag is signed. The org's policy
date lives in the register (`tags_signed_from`); tags cut before it are reported
as `TAG-SIGNED-PREPOLICY` (`OK`) and are **not** findings. If the key is absent
the check is a `notRun` SKIP — the gate never assumes a policy nobody recorded.
`TAG-SIGNED` (`ERROR`) is the newest tag cut under the policy, unsigned. The
remedy is to re-cut that one tag signed. `TAG-SIGNED-HISTORY` (`WARN`) is a
count of older ones, and it is a `WARN` precisely because its only remedy is
force-moving an already published ref — the risk the check exists to name. Do
not clear a WARN by doing the dangerous thing.
Two moves look like fixes and are not:
- **Backdating `tags_signed_from` to "catch up".** Every tag below the date was
correct when it was cut. Backdating manufactures findings whose only remedy is
re-signing published history.
- **Re-signing the whole tag history.** Same act, one step further. The org
adopted signing forward for the same reason public projects do.
`TAG-SIGNED-LIGHTWEIGHT` is a `byDesign` skip, not work. A lightweight tag has
no tag object to hold a signature at all, so signing is not a remedy it has —
`TAG-ANNOTATED` owns that finding, and fixing it there is what puts the tag in
this check's scope.
The check reads the signature from the clone, so a green line means *this tag
carries a signature*. It does **not** mean the forge verified it — the forge's
"Verified" badge needs a key registered there, which is a different subject on a
different surface. Do not upgrade the claim when reporting.
## Diagrams — capability is not justification
Mermaid renders in a README on this forge. That was verified on the live