Skip to content

Qualify the release before it exists rather than after - #495

Merged
MongLong0214 merged 1 commit into
devfrom
release-gate-fail-closed
Aug 8, 2026
Merged

Qualify the release before it exists rather than after#495
MongLong0214 merged 1 commit into
devfrom
release-gate-fail-closed

Conversation

@MongLong0214

Copy link
Copy Markdown
Owner

Closes #493.

Held: dev is frozen for the v0.7.1 promotion. This is opened for CI evidence, not to merge.

publish depended on version-consistency and nothing else, so pushing a tag created the GitHub Release and the six checks that decide whether it deserved to exist ran afterwards, if the operator remembered. That is how 0.7.0 was published with its headline feature broken: the checks were a written procedure, and a procedure is not a dependency.

Section 4 is now the install-gate job and publish needs it. All six rows are automated and every one is blocking; none was dropped or made advisory, because a gate claiming six checks while running four is worse than one honestly running five.

The stale-hook fixture also produces a hook-runtime fail of its own and doctor exits 1 on it. The row being qualified is commit-msg-hook, whose required verdict is warn, so the check reads that row out of the JSON rather than the process exit — reading the exit would have qualified the wrong thing.

Evidence. The six rows were run end to end against a fresh clone: exit 0. The gate is also not vacuous — breaking the doctor row exits 1, and comparing the plugin version against a wrong expectation exits 1.

`publish` depended on `version-consistency` and nothing else, so pushing a tag
created the GitHub Release and the six checks that decide whether it deserved
to exist ran afterwards, if the operator remembered. That is how 0.7.0 was
published with its headline feature broken: the checks were a written
procedure, and a procedure is not a dependency.

Section 4 is now the `install-gate` job and `publish` needs it. All six rows
are automated and every one of them is blocking; none was dropped or made
advisory, because a gate claiming six checks while running four is worse than
one honestly running five.

Two rows needed real fixtures rather than approximations. The PATH-less commit
runs under `env -i` so node is genuinely absent, which is what makes a
rejection evidence that the stub used its recorded interpreter instead of an
ambient one. The stale-hook row installs a healthy stub first and then
replaces only its body, so the fixture is a stale stub rather than a missing
one.

That second fixture also produces a hook-runtime fail of its own, and doctor
exits 1 on it. The row being qualified is `commit-msg-hook`, whose required
verdict is `warn`, so the check reads that row out of the JSON rather than the
process exit -- reading the exit here would have qualified the wrong thing.

The clone is of the pushed tag from the remote and runs the shipped bundle
with no build and no install, because building in this job would qualify an
artefact the tag does not carry.

Limit: the job runs on ubuntu-latest only, so it qualifies the POSIX install; the Windows path is still covered by `install-ps1` in CI against the branch rather than against the tag
Ruled-out: reusing the workflow's checkout instead of cloning | the row asks whether a fresh clone works, and a checkout is not the thing users get
Ruled-out: making the stale-hook row non-blocking because its fixture also fails another check | the confounding finding is separable by reading the row, and a row excused for being awkward is the fail-open being repaired
Warn: `publish` now waits on a job that clones over the network, so a transient clone failure withholds a release for a tag that is already pushed -- the tag is the durable thing and re-running the job is the recovery
Blast: system
Undo: easy
Certainty: firm
Verified: the six rows run end to end against a fresh clone and exit 0; the workflow loads as YAML with publish declaring both dependencies; and the gate is not vacuous -- breaking the doctor row exits 1, and comparing the plugin version against a wrong expectation exits 1
Provenance: authored
Record-Id: r-failclosed493
@github-actions

github-actions Bot commented Aug 8, 2026

Copy link
Copy Markdown

CommitLore — record lint

Trailers: clean — 1 commit in origin/dev..3216a54c0180f837c5d05c3a6f243c0aaa218905
Active constraints: 3 limits · 10 ruled-out · 5 warnings — from 7 records over 2 changed paths

Active constraints for the paths this PR touches

Limits (3)

  • r-failclosed493 3216a54 — the job runs on ubuntu-latest only, so it qualifies the POSIX install; the Windows path is still covered by install-ps1 in CI against the branch rather than against the tag
  • r-plugres483 7dab3e3 — the gate now catches the mismatch at release time, and doctor's inject-version check catches it for a user, but nothing tells a user who has never run doctor
  • r-exit065 e545dee — any new command's exit codes must be drawn from SPEC §10, not invented locally

Ruled out (10)

  • r-failclosed493 3216a54 — reusing the workflow's checkout instead of cloning | the row asks whether a fresh clone works, and a checkout is not the thing users get
  • r-failclosed493 3216a54 — making the stale-hook row non-blocking because its fixture also fails another check | the confounding finding is separable by reading the row, and a row excused for being awkward is the fail-open being repaired
  • r-plugres483 7dab3e3 — preferring CLAUDE_PLUGIN_ROOT over PATH | it reopens how ADR-0011's ship-dist-in-the-repo interacts with the installer's wrapper, and the node-resolution advantage that motivated the current order would need re-examining rather than reversing
  • r-plugres483 7dab3e3 — comparing versions inside the run script | it sits on the hot path of every edit and would spend a second process launch there to report a condition doctor already reports
  • r-nobin284 ab2f08f — Keeping the binary classification arm for the installer's wrapper | the wrapper is a shell script that execs node, so trusting it by name would re-admit any extensionless executable called commitlore while the bundle it runs is the thing with an interpreter to check
  • r-nobin284 ab2f08f — Relaxing the dogfood Evidence check to allow a missing path | it exists to catch a citation nobody can follow, and dropping the check to accommodate a deletion would retire the guarantee instead of correcting the question it asked
  • r-nobin284 ab2f08f — Leaving src/core/paths.ts alone because ADR-0026's inventory did not name it | it was the only remaining code that read files out of a compiled artifact, so leaving it would have left the invariant false while the test asserting it passed
  • r-nobin284 ab2f08f — Keeping /dist/commitlore in gitignore in case a binary returns | an ignore rule for an artifact nothing produces is a claim that something might, and the ADR says otherwise
  • r-exit065 e545dee — leaving guard's inversion in place and only documenting it | guard and inject are the two commands wired into hooks and CI, so a script written against guard today is already trusting the number; a documented inversion is still an inversion, and it gets load-bearing the longer it sits. The surface is one boolean per command right now — cheaper to fix once than to explain forever.
  • r-7a48c3 b85d847 — a CONFLICTED lifecycle state for divergent declarations | see above — it would block work on records that have a correct answer

Warnings (5)

  • r-failclosed493 3216a54 (claim)publish now waits on a job that clones over the network, so a transient clone failure withholds a release for a tag that is already pushed -- the tag is the durable thing and re-running the job is the recovery
  • r-plugres483 7dab3e3 (claim) — the narrowed PATH in the gate includes the interpreter's directory, so a machine whose node lives beside a commitlore wrapper still shadows the plugin and the check will pass for the wrong reason
  • r-nobin284 ab2f08f (claim) — an extensionless COMMITLORE_BIN is now ignored rather than honoured -- the hook falls through to the recorded install and to PATH, so a caller who pointed it at the wrapper still gets a working hook, by a different route
  • r-exit065 e545dee (claim) — guard's swap is a breaking change for anything scripted against the old exit 2 for "matched" — see CHANGELOG Unreleased/Breaking
  • r-7a48c3 b85d847 (claim) — these two changes were developed concurrently in one worktree and share a built dist/. Splitting them would leave one commit whose dist/ did not match its src/, so they land together and are described together

git log --follow accepts exactly one pathspec, so renames are not followed for 2 paths; query one path at a time to follow its rename chain

withheld the content of 1 record(s) graded blocked: a Verified trailer matching an injection pattern is reported, never quoted (SPEC §7)

Trailer violations fail this check. Active constraints are informational — they are what the repository already decided, not a verdict on this PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant