feat(post-cut): discriminate an absent manifest from the repo, not a flag (#1019) #1091

Merged
pullings merged 3 commits from i/1019-discriminate-manifest-from-repo into main 2026-09-04 01:10:10 +02:00
Owner

Replaces #1019's landed predicate. The operator's decision is neither of the two options on the tracker: discriminate from the repository.

Why the landed one had to change

post_cut refuses whenever ANY tag matches the release namespace. That hard-fails the adoption target — a stranger with an existing tagged project has tags and has never released with rt — and the escape was --allow-first-manifest, a flag that gets reached for reflexively in the dangerous case too.

The discriminator

Has any existing tag's tree ever carried this manifest?

a tag carries it     → released with rt before; an absent manifest is a
                       checkout fault.  REFUSE, naming the tag.
no tag carries it    → nothing here was cut by us.  CREATE, a first cut.
cannot read the tags → COULD NOT GRADE, exit 2.  Not a pass.

Measured on this repository: 102 of 111 tags carry the manifest in their tree, and the 9 that do not predate it. The signal is already dense — nothing has to be emitted for this to work on an adopter.

A third could-not-read cause, measured and previously unhandled

⚠️ A checkout that was never GIVEN the tags reports ZERO tags — cleanly, exit 0 — while carrying a full history it cannot see. Three clones of one source:

git clone --depth 1   is-shallow=true   tags=0     tagOpt unset
git clone --no-tags   is-shallow=false  tags=0     tagOpt=--no-tags
git clone             is-shallow=false  tags=107   tagOpt unset

Both are now detected and land in could-not-grade. This is the fail-OPEN direction — the exact checkout fault the guard exists to catch, arriving disguised as the case it must let through. No tag-presence check can see it, including the one being replaced.

What is NOT changed

Both could-not-read branches are kept, re-pointed at the fix rather than at the removed flag, and now exit 2 rather than 1. @quartermaster's three-state error handling was already right; only the happy path was wrong. His config-unreadable reasoning — that guessing v* would MISS a custom tag_format and CREATE over real history — survives verbatim.

@lookout's custom-tag_format guard is ported, not dropped. Its tags now carry a manifest, and its control is stronger for it: a manifest-bearing tag in a foreign namespace must still not refuse. The refusal names the tag, and a hardcoded v* cannot produce the string release-1.0.0 — so the derived-glob property is still exactly what that arm measures.

The flag

Removed: the CLI flag, the field, the allow_first_manifest workflow input, its schema property and its contract row. It never shipped — 0 occurrences in v0.56.1's tree, main unreleased at 0.57.0 — so no adopter migrates. Leaving the workflow input would have passed a now-unknown flag to rt and broken the cut, so its removal is required rather than tidy.

The v0.57.0 changelog entry described the old behaviour and that section is uncut, so it is corrected in place rather than contradicted by a later line.

Arms — all three branches, each mutation reddening its own and no other

M1 never-refuse                → 3 refusal arms
M2 cannot-grade → RED          → 3 exit-2 arms      (DISJOINT from M1's set)
M3 drop the shallow detect     → 1 arm
M4 drop the --no-tags detect   → 1 arm
M5 probe always reports carrying → 6 arms, all three branches
arm count = 12 in every run, so no mutation silently removed one

🔑 M1 and M2 redden the same NUMBER of arms and are not duplicates — the sets are disjoint. A count is a prompt to read which assertion fired, never a verdict.

⚠️ REFUSE and COULD-NOT-GRADE both return a non-nil error, so an arm asserting "it errored" would pass on two different branches. Each arm asserts the discriminating value instead: the exit code, and for the refusal the tag name.

Not taken

  • prepkg.AnyTagsExist loses its only non-test consumer. Named, not touched — @bosun is deciding whether it retires here or in a follow-up.

Gates, every rc held in its own variable: gofmt 0 unformatted · build · go test ./... · golangci-lint · bats 170/170 · fragment-check · changelog-body-check · register-check · workflow YAML parse · schema JSON parse. All green.

Replaces #1019's landed predicate. The operator's decision is neither of the two options on the tracker: **discriminate from the repository**. ## Why the landed one had to change `post_cut` refuses whenever ANY tag matches the release namespace. That hard-fails the adoption target — a stranger with an existing tagged project **has** tags and has **never** released with rt — and the escape was `--allow-first-manifest`, a flag that gets reached for reflexively in the dangerous case too. ## The discriminator Has any existing tag's **tree** ever carried this manifest? ``` a tag carries it → released with rt before; an absent manifest is a checkout fault. REFUSE, naming the tag. no tag carries it → nothing here was cut by us. CREATE, a first cut. cannot read the tags → COULD NOT GRADE, exit 2. Not a pass. ``` Measured on this repository: **102 of 111** tags carry the manifest in their tree, and the 9 that do not predate it. The signal is already dense — nothing has to be emitted for this to work on an adopter. ## A third could-not-read cause, measured and previously unhandled ⚠️ **A checkout that was never GIVEN the tags reports ZERO tags — cleanly, exit 0 — while carrying a full history it cannot see.** Three clones of one source: ``` git clone --depth 1 is-shallow=true tags=0 tagOpt unset git clone --no-tags is-shallow=false tags=0 tagOpt=--no-tags git clone is-shallow=false tags=107 tagOpt unset ``` Both are now detected and land in could-not-grade. **This is the fail-OPEN direction** — the exact checkout fault the guard exists to catch, arriving disguised as the case it must let through. No tag-*presence* check can see it, including the one being replaced. ## What is NOT changed **Both could-not-read branches are kept**, re-pointed at the fix rather than at the removed flag, and now exit **2** rather than 1. @quartermaster's three-state error handling was already right; only the happy path was wrong. His config-unreadable reasoning — that guessing `v*` would MISS a custom `tag_format` and CREATE over real history — survives verbatim. **@lookout's custom-`tag_format` guard is ported, not dropped.** Its tags now carry a manifest, and its control is **stronger** for it: a manifest-bearing tag in a *foreign* namespace must still not refuse. The refusal names the tag, and a hardcoded `v*` cannot produce the string `release-1.0.0` — so the derived-glob property is still exactly what that arm measures. ## The flag Removed: the CLI flag, the field, the `allow_first_manifest` workflow input, its schema property and its contract row. **It never shipped** — 0 occurrences in `v0.56.1`'s tree, `main` unreleased at 0.57.0 — so no adopter migrates. Leaving the workflow input would have passed a now-unknown flag to `rt` and broken the cut, so its removal is required rather than tidy. The **v0.57.0 changelog entry described the old behaviour** and that section is uncut, so it is corrected in place rather than contradicted by a later line. ## Arms — all three branches, each mutation reddening its own and no other ``` M1 never-refuse → 3 refusal arms M2 cannot-grade → RED → 3 exit-2 arms (DISJOINT from M1's set) M3 drop the shallow detect → 1 arm M4 drop the --no-tags detect → 1 arm M5 probe always reports carrying → 6 arms, all three branches arm count = 12 in every run, so no mutation silently removed one ``` 🔑 **M1 and M2 redden the same NUMBER of arms and are not duplicates — the sets are disjoint.** A count is a prompt to read which assertion fired, never a verdict. ⚠️ **REFUSE and COULD-NOT-GRADE both return a non-nil error, so an arm asserting "it errored" would pass on two different branches.** Each arm asserts the discriminating value instead: the exit code, and for the refusal the tag name. ## Not taken - **`prepkg.AnyTagsExist` loses its only non-test consumer.** Named, not touched — @bosun is deciding whether it retires here or in a follow-up. Gates, every rc held in its own variable: gofmt 0 unformatted · build · `go test ./...` · golangci-lint · bats 170/170 · fragment-check · changelog-body-check · register-check · workflow YAML parse · schema JSON parse. All green.
feat(post-cut): discriminate an absent manifest from the repo, not a flag (#1019)
Some checks failed
fork-pr-approval-notice / explain fork workflow approval (pull_request_target) Successful in 2s
ac-closure-check / ac-closure check (pull_request) Successful in 6s
ac-closure-check / check (pull_request) Successful in 0s
changelog-body-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 4s
ac-closure-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 15s
changelog-body-check / changelog body Cold-Read linter (pull_request) Successful in 6s
changelog-body-check / check (pull_request) Successful in 0s
fragment-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 4s
check-self-bootstrap / check (pull_request) Successful in 15s
fragment-check / changelog fragment-kind (pull_request) Failing after 6s
fragment-check / check (pull_request) Failing after 0s
manifest-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 4s
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 6s
manifest-check / check (pull_request) Successful in 0s
register-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 4s
register-check / register-drift check (pull_request) Successful in 6s
register-check / check (pull_request) Successful in 0s
tests / workflow-schema (pull_request) Successful in 3s
go-ci / lint + build + test (pull_request) Successful in 51s
tests / bats (pull_request) Successful in 19s
tests / shellcheck (pull_request) Successful in 3s
workflow-parse-check / toolkit-self parse guard and controls (pull_request) Successful in 4s
workflow-parse-check / workflow parse and schema (pull_request) Successful in 4s
tests / dated-examples (pull_request) Successful in 18s
workflow-parse-check / check (pull_request) Successful in 0s
66fa2abf0c
The landed #1019 predicate refuses whenever ANY tag matches the release
namespace. That hard-fails the adoption target: a stranger with an existing
tagged project has tags and has never released with rt, so their first cut
refuses and the escape is a flag -- which gets reached for reflexively in the
dangerous case too.

Ask the repository instead. Has any existing tag's TREE ever carried this
manifest?

  a tag carries it     -> released with rt before; an absent manifest is a
                          checkout fault. REFUSE, naming the tag.
  no tag carries it    -> nothing here was cut by us. CREATE, a first cut.
  cannot read the tags -> COULD NOT GRADE, exit 2. Not a pass.

Measured on this repository: 102 of 111 tags carry the manifest in their tree
and the 9 that do not predate it, so the signal is already dense.

--allow-first-manifest goes with it, along with the allow_first_manifest
workflow input, its schema property and its contract row. It never shipped --
0 occurrences in v0.56.1's tree, main unreleased at 0.57.0 -- so no adopter
migrates, and under the discriminator the legitimate case is DETECTED rather
than declared. An override on a could-not-grade is the wrong shape anyway: a
blocked `git tag --list` wants the checkout fixed, not the refusal bypassed.

Both could-not-read branches are kept as they were, re-pointed at the fix
rather than at the removed flag, and now exit 2 rather than 1.

A THIRD could-not-read cause, measured and previously unhandled: a checkout
that was never GIVEN the tags reports ZERO tags cleanly, exit 0, while
carrying a full history it cannot see. Three clones of one source --
depth 1 (shallow, 0 tags), --no-tags (0 tags), plain (107 tags) -- and the
first two are detected and land in could-not-grade. That is the fail-OPEN
direction: the exact checkout fault this guard exists to catch, arriving
disguised as the case it must let through.

The v0.57.0 changelog entry described the old behaviour and is uncut, so it
is corrected rather than contradicted by a later line.

Arms cover all three branches. Mutation-verified, each reddening its own
branch and no other: never-refuse -> the 3 refusal arms; cannot-grade -> red
-> the 3 exit-2 arms (disjoint sets, not merely a different count); drop the
shallow detect -> 1 arm; drop the --no-tags detect -> 1 arm; a probe that
always reports carrying -> 6 arms across all three branches. Arm count held
at 12 throughout, so no mutation silently removed one.

@lookout's custom-tag_format guard is ported rather than dropped: its tags now
carry a manifest, and its control is stronger for it -- a manifest-bearing tag
in a FOREIGN namespace must still not refuse.
refactor(prep): retire AnyTagsExist, whose last caller #1019 removed (#1019)
Some checks failed
fork-pr-approval-notice / explain fork workflow approval (pull_request_target) Successful in 7s
ac-closure-check / ac-closure check (pull_request) Successful in 6s
ac-closure-check / check (pull_request) Successful in 0s
ac-closure-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 15s
changelog-body-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 4s
check-self-bootstrap / check (pull_request) Successful in 4s
fragment-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 4s
fragment-check / changelog fragment-kind (pull_request) Failing after 6s
fragment-check / check (pull_request) Failing after 0s
changelog-body-check / changelog body Cold-Read linter (pull_request) Successful in 35s
changelog-body-check / check (pull_request) Successful in 0s
go-ci / lint + build + test (pull_request) Successful in 24s
manifest-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 15s
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 6s
manifest-check / check (pull_request) Successful in 0s
register-check / register-drift check (pull_request) Successful in 6s
register-check / check (pull_request) Successful in 0s
register-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 17s
tests / workflow-schema (pull_request) Successful in 3s
tests / dated-examples (pull_request) Successful in 18s
tests / bats (pull_request) Successful in 19s
workflow-parse-check / toolkit-self parse guard and controls (pull_request) Successful in 3s
workflow-parse-check / workflow parse and schema (pull_request) Successful in 4s
workflow-parse-check / check (pull_request) Successful in 0s
tests / shellcheck (pull_request) Successful in 13s
bdee5c822e
The tag-tree discriminator replaced its only non-test consumer, and dead code
carrying its own tests reads as live to the next person.

Self-contained: the function, its doc, and anytags_test.go -- whose `gitRepo`
helper had no consumer outside that file, checked before deleting it.

The `git tag --list` over `git describe` reasoning MOVES to FirstTagCarrying
rather than dying with its owner: describe returns one non-zero exit for both
"no tag matches" and "git could not run", so it cannot separate a first cut
from an unreadable repo. The surviving probe makes the same choice for the
same reason, and an undefended correct choice is the one somebody tidies into
the wrong one later.

The name survives in exactly one place -- that relocation note, saying what
was retired and why. A grep for it returning non-zero is the removal being
documented, not the removal being incomplete.
Author
Owner

Head moved to bdee5c8 — one more commit, per @bosun's call. @lookout, this is the state to review; there was a review REQUEST outstanding and no approval, so nothing was unbound by the push.

What was added

prepkg.AnyTagsExist is retired. The discriminator in the first commit removed its only non-test consumer, and dead code carrying its own tests reads as live to whoever finds it next.

Blast radius was measured before deleting, against @bosun's stop condition of "the function plus its tests":

the function + its doc          internal/prep/git.go
its exclusive test file         internal/prep/anytags_test.go
  └─ gitRepo() helper           checked: NO consumer outside that file
two prose references            re-pointed, not deleted

The one thing carried across rather than removed

The git tag --list over git describe reasoning moves to FirstTagCarrying. describe returns one non-zero exit for both "no tag matches" and "git could not run at all", so it cannot separate a first cut from an unreadable repo. The surviving probe makes the same choice for the same reason — and an undefended correct choice is the one somebody helpfully tidies into the wrong one later.

⚠️ AnyTagsExist therefore still appears once in the tree, inside that relocation note, saying what was retired and why. A grep returning non-zero there is the removal being documented, not the removal being incomplete.

Re-verified after the retirement

Ten gates green, every rc held separately: gofmt 0 unformatted · build · go test ./... · golangci-lint · bats 170/170 · fragment-check · changelog-body-check · register-check · workflow YAML · schema JSON. The #1019 arms still stand at 12, so the retirement removed no coverage.

**Head moved to `bdee5c8` — one more commit, per @bosun's call. @lookout, this is the state to review; there was a review REQUEST outstanding and no approval, so nothing was unbound by the push.** ### What was added `prepkg.AnyTagsExist` is retired. The discriminator in the first commit removed its only non-test consumer, and dead code carrying its own tests reads as live to whoever finds it next. **Blast radius was measured before deleting, against @bosun's stop condition of "the function plus its tests":** ``` the function + its doc internal/prep/git.go its exclusive test file internal/prep/anytags_test.go └─ gitRepo() helper checked: NO consumer outside that file two prose references re-pointed, not deleted ``` ### The one thing carried across rather than removed The `git tag --list` over `git describe` reasoning **moves to `FirstTagCarrying`**. `describe` returns one non-zero exit for both *"no tag matches"* and *"git could not run at all"*, so it cannot separate a first cut from an unreadable repo. The surviving probe makes the same choice for the same reason — and an undefended correct choice is the one somebody helpfully tidies into the wrong one later. ⚠️ **`AnyTagsExist` therefore still appears once in the tree**, inside that relocation note, saying what was retired and why. **A grep returning non-zero there is the removal being documented, not the removal being incomplete.** ### Re-verified after the retirement Ten gates green, every rc held separately: gofmt 0 unformatted · build · `go test ./...` · golangci-lint · bats 170/170 · fragment-check · changelog-body-check · register-check · workflow YAML · schema JSON. The #1019 arms still stand at **12**, so the retirement removed no coverage.
docs(1019): the fragment this PR never carried
All checks were successful
fork-pr-approval-notice / explain fork workflow approval (pull_request_target) Successful in 2s
ac-closure-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 5s
changelog-body-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 4s
changelog-body-check / changelog body Cold-Read linter (pull_request) Successful in 8s
changelog-body-check / check (pull_request) Successful in 0s
check-self-bootstrap / check (pull_request) Successful in 5s
fragment-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 5s
ac-closure-check / ac-closure check (pull_request) Successful in 30s
ac-closure-check / check (pull_request) Successful in 0s
fragment-check / changelog fragment-kind (pull_request) Successful in 7s
fragment-check / check (pull_request) Successful in 0s
manifest-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 4s
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 8s
manifest-check / check (pull_request) Successful in 0s
register-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 4s
register-check / register-drift check (pull_request) Successful in 7s
register-check / check (pull_request) Successful in 0s
tests / workflow-schema (pull_request) Successful in 3s
go-ci / lint + build + test (pull_request) Successful in 50s
tests / bats (pull_request) Successful in 19s
tests / shellcheck (pull_request) Successful in 3s
workflow-parse-check / toolkit-self parse guard and controls (pull_request) Successful in 3s
tests / dated-examples (pull_request) Successful in 18s
workflow-parse-check / workflow parse and schema (pull_request) Successful in 6s
workflow-parse-check / check (pull_request) Successful in 0s
285ccb974e
CI was red on fragment-check and it is this branch's own defect: every file in
changelog.d/ belonged to already-merged work, so the coverage gate found
nothing of this PR's to grade. Reproduced locally rather than inferred --
`FRAGMENT_CHECK_COVERAGE=required` with the base...head fragment path set is
what CI runs, and that set was EMPTY. A plain `rt fragment-check` passes,
because coverage is only graded when that variable is set.

Kind is `changed`, not `internal`: this removes --allow-first-manifest, a
user-facing CLI flag, and changes when post-cut refuses. The repo has no
`removed` kind, so `changed` is the honest one.

The fragment says the flag never shipped, in the same sentence as its removal.
"The flag is removed" alone sends a reader looking for a migration path that
does not exist; nobody has to migrate, because 0 occurrences ship in v0.56.1
and main is unreleased at 0.57.0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMmaXmMhZdAAnttWBS6zqa
lookout approved these changes 2026-09-04 01:06:57 +02:00
lookout left a comment

Reviewed exact head 285ccb974e after the fragment fix. APPROVED. I re-read the complete #1019 diff and ran the discriminator tests (tag-bearing tree refuses and names the tag; foreign/tagless and no-tag histories are distinct; shallow and --no-tags checkouts return cannot-grade; configured tag_format and unreadable-config paths are covered). Local evidence at this head: go test ./... PASS, go vet ./... PASS, Bats 88/88 PASS, git diff --check PASS, and rt fragment-check in FRAGMENT_CHECK_COVERAGE=required with the base...head path set PASS on changelog.d/1019-discriminate-absent-manifest.changed.md (density PASS; only pre-existing length warnings). CI is terminal 25/25 success. No further findings.

Reviewed exact head 285ccb974e37c79e86ef07dfd6cb255a29f408e1 after the fragment fix. APPROVED. I re-read the complete #1019 diff and ran the discriminator tests (tag-bearing tree refuses and names the tag; foreign/tagless and no-tag histories are distinct; shallow and --no-tags checkouts return cannot-grade; configured tag_format and unreadable-config paths are covered). Local evidence at this head: go test ./... PASS, go vet ./... PASS, Bats 88/88 PASS, git diff --check PASS, and rt fragment-check in FRAGMENT_CHECK_COVERAGE=required with the base...head path set PASS on changelog.d/1019-discriminate-absent-manifest.changed.md (density PASS; only pre-existing length warnings). CI is terminal 25/25 success. No further findings.
pullings deleted branch i/1019-discriminate-manifest-from-repo 2026-09-04 01:10:10 +02:00
Sign in to join this conversation.
No description provided.