docs: the uses: line is keyed on location, not on which forge you run #1324

Merged
bosun merged 3 commits from i/1321-uses-location-not-provider into main 2026-09-06 14:56:26 +02:00
Owner

The README's uses: line now resolves for a stranger. The rule is where the toolkit LIVES, not which forge software you run.

Intended-targets: #1321

AC2, run as a test rather than asserted

I took the line out of the README and resolved it the way an adopter's runner would:

README says:  uses: https://gitea.com/FrankenBit/release-toolkit/.forgejo/workflows/reusable-release.yml@v0.61.1
parsed     ->  host=gitea.com  owner=FrankenBit  repo=release-toolkit  tag=v0.61.1
GET contents/.forgejo/workflows/reusable-release.yml?ref=v0.61.1   ->  200

Before publishing that URL I checked the mirror actually serves it, rather than assuming the mirror is current: anonymously readable, v0.61.1 present, and both .forgejo/workflows/reusable-release.yml and .gitea/workflows/reusable-release.yml resolve at that tag. The owner path is case-insensitive there, so a reader copying frankenbit from elsewhere in the docs still lands.

AC1 — the axis, and where the old text put it

The absolute form was already in the docs. It was under ### Running on Gitea — reference the .gitea/ path, which is why this survived: a reader on self-hosted Forgejo reads "on Forgejo… reference .forgejo/", correctly concludes the Gitea section is not theirs, and writes the failing line.

integration.md now opens the two questions separately:

① WHERE is the toolkit?   -> absolute URL, or the short owner/repo form
② WHICH forge are YOU on? -> .forgejo/workflows/ or .gitea/workflows/

⚠️ The sweep found 16 short-form uses: examples and 1 absolute across both files. I did not rewrite the other 15. The short form is correct for an adopter who mirrors the toolkit, and rewriting every example would bury that. Instead the guide states once, at the point the reader meets the first one, that the remaining examples use the short form and what to prefix — the same state-it-once shape as #1248.

AC3 — the .gitea/ guidance is kept and now stands alone

It was always correct; the defect was location-guidance nested inside it. §② keeps it verbatim and adds the case the old text could not express: a Gitea adopter pulling from the public mirror needs the absolute URL and the .gitea/ path. Those are two axes, and the old block let a reader pick one.

The line I edited has an owner — checked before editing it

internal/prep/doc_version_refs.go re-pins doc refs on every cut. atTagRE matches @vX.Y.Z anywhere in the line, not the bare-form prefix, so the new absolute URL is still re-pinned at the next cut. Had it been anchored on the short form, this change would have frozen the README at v0.61.1 silently — the #1279 lesson, applied before rather than after.

Verification

fragment-check 0 with zero warnings on 1321.fixed.md (needle control: check 7 still fires on another fragment) · register-check 0 · gitea-twin --check 0 · contract-paths-check 0 · go test ./... 0 · no inbound links or prose references to the retitled section.

📌 Credit where it belongs: this needed someone who had never read the docs. Our own release.yml uses the short form and is correct, because the toolkit lives here — to us frankenbit/release-toolkit obviously means on our forge, and no amount of running our own pipeline reaches that.

🤖 Generated with Claude Code

https://claude.ai/code/session_013sreVuKQfwS7yvxyGL1Z2i

The README's `uses:` line now resolves for a stranger. The rule is where the toolkit LIVES, not which forge software you run. Intended-targets: #1321 ## AC2, run as a test rather than asserted I took the line out of the README and resolved it the way an adopter's runner would: ``` README says: uses: https://gitea.com/FrankenBit/release-toolkit/.forgejo/workflows/reusable-release.yml@v0.61.1 parsed -> host=gitea.com owner=FrankenBit repo=release-toolkit tag=v0.61.1 GET contents/.forgejo/workflows/reusable-release.yml?ref=v0.61.1 -> 200 ``` **Before publishing that URL I checked the mirror actually serves it**, rather than assuming the mirror is current: anonymously readable, `v0.61.1` present, and **both** `.forgejo/workflows/reusable-release.yml` and `.gitea/workflows/reusable-release.yml` resolve at that tag. The owner path is case-insensitive there, so a reader copying `frankenbit` from elsewhere in the docs still lands. ## AC1 — the axis, and where the old text put it The absolute form was already in the docs. It was under **`### Running on Gitea — reference the `.gitea/` path`**, which is why this survived: a reader on self-hosted Forgejo reads *"on Forgejo… reference `.forgejo/`"*, correctly concludes the Gitea section is not theirs, and writes the failing line. `integration.md` now opens the two questions separately: ``` ① WHERE is the toolkit? -> absolute URL, or the short owner/repo form ② WHICH forge are YOU on? -> .forgejo/workflows/ or .gitea/workflows/ ``` ⚠️ **The sweep found 16 short-form `uses:` examples and 1 absolute** across both files. **I did not rewrite the other 15.** The short form is correct for an adopter who mirrors the toolkit, and rewriting every example would bury that. Instead the guide states once, at the point the reader meets the first one, that the remaining examples use the short form and what to prefix — the same *state-it-once* shape as `#1248`. ## AC3 — the `.gitea/` guidance is kept and now stands alone It was always correct; the defect was location-guidance nested inside it. §② keeps it verbatim and adds the case the old text could not express: **a Gitea adopter pulling from the public mirror needs the absolute URL *and* the `.gitea/` path.** Those are two axes, and the old block let a reader pick one. ## The line I edited has an owner — checked before editing it `internal/prep/doc_version_refs.go` re-pins doc refs on every cut. **`atTagRE` matches `@vX.Y.Z` anywhere in the line, not the bare-form prefix**, so the new absolute URL is still re-pinned at the next cut. Had it been anchored on the short form, this change would have frozen the README at `v0.61.1` silently — the `#1279` lesson, applied before rather than after. ## Verification `fragment-check` 0 with zero warnings on `1321.fixed.md` (needle control: check 7 still fires on another fragment) · `register-check` 0 · `gitea-twin --check` 0 · `contract-paths-check` 0 · `go test ./...` 0 · no inbound links or prose references to the retitled section. 📌 Credit where it belongs: this needed someone who had never read the docs. **Our own `release.yml` uses the short form and is correct, because the toolkit lives here** — to us `frankenbit/release-toolkit` obviously means *on our forge*, and no amount of running our own pipeline reaches that. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_013sreVuKQfwS7yvxyGL1Z2i
docs: the uses: line is keyed on LOCATION, not on which forge you run
Some checks failed
base-divergence-check / check (pull_request) Successful in 6s
prep-order-check / check (pull_request) Successful in 5s
register-check / register-drift check (pull_request) Successful in 8s
register-check / check (pull_request) Successful in 0s
tests / workflow-schema (pull_request) Successful in 4s
fork-pr-approval-notice / explain fork workflow approval (pull_request_target) Successful in 27s
check-self-bootstrap / check (pull_request) Successful in 27s
gitea-twin-check / check (pull_request) Successful in 26s
tests / bats (pull_request) Failing after 23s
toolkit-self-gates / toolkit-self gates (PR's own rt) (pull_request) Successful in 7s
tests / shellcheck (pull_request) Successful in 27s
changelog-body-check / changelog body Cold-Read linter (pull_request) Successful in 55s
fragment-check / changelog fragment-kind (pull_request) Successful in 54s
ac-closure-check / ac-closure check (pull_request) Successful in 55s
changelog-body-check / check (pull_request) Successful in 0s
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 54s
fragment-check / check (pull_request) Successful in 0s
ac-closure-check / check (pull_request) Successful in 0s
manifest-check / check (pull_request) Successful in 0s
tests / contract-paths (pull_request) Successful in 31s
tests / dated-examples (pull_request) Successful in 38s
go-ci / lint + build + test (pull_request) Successful in 1m15s
go-ci / page landing-tree failure (pull_request) Has been skipped
workflow-parse-check / toolkit-self parse guard and controls (pull_request) Successful in 23s
workflow-parse-check / workflow parse and schema (pull_request) Successful in 29s
workflow-parse-check / check (pull_request) Successful in 0s
ac34f0ce4c
A short `frankenbit/release-toolkit/...` reference resolves against the
CONSUMER's instance, which does not have the toolkit. The README shipped
exactly that form and no absolute example at all, so a stranger
following it got a 404.

The absolute form did exist — under "Running on Gitea". So a reader on
self-hosted Forgejo correctly concluded that section was not about them
and wrote the failing line. The docs encoded "is the toolkit on your
instance?" as if it were "which forge software do you run?".

README now leads with the absolute form against the public mirror.
integration.md splits the two axes explicitly: location decides
absolute-vs-short, your forge decides .forgejo/ vs .gitea/, and a Gitea
adopter pulling from the mirror needs both. The .gitea/ path guidance is
kept intact — that axis was always correct.

Verified the mirror actually serves what the example points at:
gitea.com/FrankenBit/release-toolkit is anonymously readable, carries
v0.61.1, and resolves both .forgejo/ and .gitea/ reusable-release.yml at
that tag. The owner path is case-insensitive there.

`atTagRE` matches `@vX.Y.Z` anywhere, so the cut still re-pins the new
absolute URL — checked before editing a line rt owns.

Found by Shipwright's stranger test. Our own release.yml uses the bare
form and is correct, because the toolkit lives here — which is why no
amount of dogfooding reaches this.

Intended-targets: #1321

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013sreVuKQfwS7yvxyGL1Z2i
test(workflows): the guide's Gitea example is matched host-agnostically
All checks were successful
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 8s
manifest-check / check (pull_request) Successful in 0s
prep-order-check / check (pull_request) Successful in 6s
register-check / register-drift check (pull_request) Successful in 8s
register-check / check (pull_request) Successful in 0s
base-divergence-check / check (pull_request) Successful in 26s
fork-pr-approval-notice / explain fork workflow approval (pull_request_target) Successful in 26s
gitea-twin-check / check (pull_request) Successful in 25s
check-self-bootstrap / check (pull_request) Successful in 26s
tests / workflow-schema (pull_request) Successful in 4s
toolkit-self-gates / toolkit-self gates (PR's own rt) (pull_request) Successful in 7s
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 5s
workflow-parse-check / check (pull_request) Successful in 0s
tests / shellcheck (pull_request) Successful in 22s
ac-closure-check / ac-closure check (pull_request) Successful in 54s
ac-closure-check / check (pull_request) Successful in 0s
changelog-body-check / changelog body Cold-Read linter (pull_request) Successful in 55s
changelog-body-check / check (pull_request) Successful in 0s
tests / contract-paths (pull_request) Successful in 32s
fragment-check / changelog fragment-kind (pull_request) Successful in 59s
tests / dated-examples (pull_request) Successful in 34s
fragment-check / check (pull_request) Successful in 0s
go-ci / lint + build + test (pull_request) Successful in 1m11s
tests / bats (pull_request) Successful in 1m6s
go-ci / page landing-tree failure (pull_request) Has been skipped
2eead5c527
#1222's arm feeds the guide's own absolute Gitea `uses:` line through
the real resolver rather than an invented fixture, which is the right
design. It pinned the host as `gitea.example.com`, so pointing the guide
at the actual public mirror turned it red for a reason that has nothing
to do with the resolver.

Loosened to any `uses: https://…/.gitea/workflows/reusable-release.yml`.

Two things checked before loosening a predicate:

- the resolver ACCEPTS the new host, so the guide now documents a form
  that genuinely resolves — the arm passes against gitea.com unchanged
- the arm still FAILS when the guide has no absolute Gitea example at
  all (mutation: removed the line, "the documented absolute Gitea
  wrapper form is missing", rc=1)

So the assertion is weaker on the host and unchanged on what it exists
to catch.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013sreVuKQfwS7yvxyGL1Z2i
Author
Owner

tests / bats went red and it was a real coupling I had missed. Fixed at 2eead5c.

The guide's example is an INPUT to a gate

#1222's arm does not use a fixture — it reads the absolute Gitea uses: line out of integration.md and feeds it through the real resolver. That is the right design, and it is exactly why pointing the guide at the actual public mirror broke it: the extraction pinned the host as gitea.example.com.

not ok 32  workflows: wrapper-ref fallback accepts documented URL and display casing (#1222)
# the documented absolute Gitea wrapper form is missing

📌 I checked doc_version_refs.go before editing these lines and found atTagRE safe. I did not think to ask whether anything ELSE owned them. The second owner was a bats arm in a different directory, and CI told me rather than my sweep. One owner found by looking, one found by being caught.

What I changed, and the two checks before loosening a predicate

The extraction now matches any uses: https://…/.gitea/workflows/reusable-release.yml. Loosening an assertion needs more than a green run, so:

① does the resolver ACCEPT the new host?
   arm passes unchanged against gitea.com  ->  the guide now documents a form
                                               that genuinely resolves
② can the loosened arm still FAIL?
   mutation: removed the absolute example from the guide entirely
   -> "the documented absolute Gitea wrapper form is missing", rc=1

So it is weaker on the host and unchanged on the thing it exists to catch. ① is the one that mattered: had the resolver been host-specific, the honest fix would have been to revert my doc change, not to relax the test.

Full local run

bats tests/*.bats 174/174, and separately: fragment-check 0 · register-check 0 · gitea-twin --check 0 · go test ./... 0.

⚠️ I ran go test before pushing the first time and not bats. That is the gap — the Go suite has nothing to say about a doc line, and the arm that did was in the suite I skipped.

`tests / bats` went red and it was a real coupling I had missed. Fixed at `2eead5c`. ## The guide's example is an INPUT to a gate `#1222`'s arm does not use a fixture — **it reads the absolute Gitea `uses:` line out of `integration.md` and feeds it through the real resolver.** That is the right design, and it is exactly why pointing the guide at the actual public mirror broke it: the extraction pinned the host as `gitea.example.com`. ``` not ok 32 workflows: wrapper-ref fallback accepts documented URL and display casing (#1222) # the documented absolute Gitea wrapper form is missing ``` 📌 **I checked `doc_version_refs.go` before editing these lines and found `atTagRE` safe. I did not think to ask whether anything ELSE owned them.** The second owner was a bats arm in a different directory, and CI told me rather than my sweep. *One owner found by looking, one found by being caught.* ## What I changed, and the two checks before loosening a predicate The extraction now matches any `uses: https://…/.gitea/workflows/reusable-release.yml`. **Loosening an assertion needs more than a green run**, so: ``` ① does the resolver ACCEPT the new host? arm passes unchanged against gitea.com -> the guide now documents a form that genuinely resolves ② can the loosened arm still FAIL? mutation: removed the absolute example from the guide entirely -> "the documented absolute Gitea wrapper form is missing", rc=1 ``` **So it is weaker on the host and unchanged on the thing it exists to catch.** ① is the one that mattered: had the resolver been host-specific, the honest fix would have been to revert my doc change, not to relax the test. ## Full local run `bats tests/*.bats` **174/174**, and separately: `fragment-check` 0 · `register-check` 0 · `gitea-twin --check` 0 · `go test ./...` 0. ⚠️ I ran `go test` before pushing the first time and not `bats`. That is the gap — the Go suite has nothing to say about a doc line, and the arm that did was in the suite I skipped.
bosun requested review from sentry 2026-09-06 14:12:29 +02:00
test(workflows): keep the host pinned; move the pin to the documented one
All checks were successful
ac-closure-check / ac-closure check (pull_request) Successful in 9s
ac-closure-check / check (pull_request) Successful in 0s
prep-order-check / check (pull_request) Successful in 6s
register-check / register-drift check (pull_request) Successful in 8s
register-check / check (pull_request) Successful in 0s
fork-pr-approval-notice / explain fork workflow approval (pull_request_target) Successful in 27s
gitea-twin-check / check (pull_request) Successful in 26s
base-divergence-check / check (pull_request) Successful in 27s
check-self-bootstrap / check (pull_request) Successful in 26s
tests / workflow-schema (pull_request) Successful in 4s
tests / contract-paths (pull_request) Successful in 4s
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 5s
workflow-parse-check / check (pull_request) Successful in 0s
tests / shellcheck (pull_request) Successful in 22s
fragment-check / changelog fragment-kind (pull_request) Successful in 53s
changelog-body-check / changelog body Cold-Read linter (pull_request) Successful in 54s
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 54s
fragment-check / check (pull_request) Successful in 0s
changelog-body-check / check (pull_request) Successful in 0s
manifest-check / check (pull_request) Successful in 0s
tests / dated-examples (pull_request) Successful in 30s
go-ci / lint + build + test (pull_request) Successful in 1m9s
go-ci / page landing-tree failure (pull_request) Has been skipped
toolkit-self-gates / toolkit-self gates (PR's own rt) (pull_request) Successful in 50s
tests / bats (pull_request) Successful in 1m5s
8927e0789d
Reverting my own loosening on Bosun's call, and his reasoning holds: this
is the public entry point, so a change of documented host should stop a
human rather than pass quietly. The arm went red on exactly that and was
doing its job.

Both dispositions prove the documented form RESOLVES — the line is fed
through the real resolver either way — so the only thing at stake was
whether a host change reddens. For an adopter's first copy-paste, it
should.

Mutation-verified in both directions after the change:

  guide points at a different host  -> not ok, rc=1
  guide has no absolute example     -> not ok, rc=1

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013sreVuKQfwS7yvxyGL1Z2i
Author
Owner

Reverted my own loosening at 8927e07@bosun's call, and his reasoning holds.

I had made the extraction host-agnostic. Both dispositions prove the documented form RESOLVES — the line is fed through the real resolver either way — so the only thing at stake was whether a change of documented host reddens. For the public entry point an adopter copy-pastes first, it should. The arm went red on exactly that and was doing its job.

Now startswith("uses: https://gitea.com/"), exact, with the pin moved to the host the guide documents.

Mutation-verified in both directions after the change, not just re-run green:

guide points at a different host   ->  not ok, rc=1
guide has no absolute example      ->  not ok, rc=1
restored                           ->  ok,    rc=0

bats tests/*.bats 174/174 · fragment-check 0 · gitea-twin --check 0 · go test ./... 0.


📌 What I want on the record, because it is the reusable part rather than the fix. My earlier comment justified loosening with "the host is incidental". That was true of the resolver and false of the reader:

The doc is not only an input to the resolver. It is the line a stranger pastes, and #1321 exists because a stranger pasted the wrong one. A test guarding what a stranger copies should be exact about what they copy.

⚠️ I reached for the change that made my PR green and found a principle for it afterwards. The principle was real — a fixture-free test should not pin incidentals — and it was aimed at the wrong field. The tell was available before the argument: I was weakening a guard that had just caught something in my own diff.

**Reverted my own loosening at `8927e07`** — @bosun's call, and his reasoning holds. I had made the extraction host-agnostic. **Both dispositions prove the documented form RESOLVES** — the line is fed through the real resolver either way — so the only thing at stake was whether a change of documented host reddens. ✅ **For the public entry point an adopter copy-pastes first, it should.** The arm went red on exactly that and was doing its job. Now `startswith("uses: https://gitea.com/")`, exact, with the pin moved to the host the guide documents. **Mutation-verified in both directions after the change, not just re-run green:** ``` guide points at a different host -> not ok, rc=1 guide has no absolute example -> not ok, rc=1 restored -> ok, rc=0 ``` `bats tests/*.bats` 174/174 · `fragment-check` 0 · `gitea-twin --check` 0 · `go test ./...` 0. --- 📌 **What I want on the record, because it is the reusable part rather than the fix.** My earlier comment justified loosening with *"the host is incidental"*. That was true of the resolver and false of the reader: > **The doc is not only an input to the resolver. It is the line a stranger pastes**, and `#1321` exists because a stranger pasted the wrong one. **A test guarding what a stranger copies should be exact about what they copy.** ⚠️ I reached for the change that made my PR green and found a principle for it afterwards. **The principle was real — a fixture-free test should not pin incidentals — and it was aimed at the wrong field.** The tell was available before the argument: I was weakening a guard that had just caught something in my own diff.
bosun removed review request for shipwright 2026-09-06 14:43:34 +02:00
sentry approved these changes 2026-09-06 14:55:13 +02:00
sentry left a comment

APPROVED at exact head 8927e0789d.

The README and integration guide now separate the toolkit location question (absolute URL versus short owner/repo) from the adopter forge path (.forgejo versus .gitea), while retaining the Gitea guidance. The public mirror and both v0.61.1 workflow paths were independently read live, including lower- and display-cased owner paths.

The workflow control exercises source and generated-twin resolver paths, fallback forms, baked-marker precedence, the actual guide form, absolute/casefold mutation controls, and unknown-wrapper fail-closed behavior. The old pinned URL assertion was moved to the actual documented fixture rather than weakened. Targeted and root-invoked full Bats checks pass 174/174; Forgejo CI is terminal 26/26 green.

APPROVED at exact head 8927e0789d0c6f27183ad811cc68cc251a3e7bbe. The README and integration guide now separate the toolkit location question (absolute URL versus short owner/repo) from the adopter forge path (.forgejo versus .gitea), while retaining the Gitea guidance. The public mirror and both v0.61.1 workflow paths were independently read live, including lower- and display-cased owner paths. The workflow control exercises source and generated-twin resolver paths, fallback forms, baked-marker precedence, the actual guide form, absolute/casefold mutation controls, and unknown-wrapper fail-closed behavior. The old pinned URL assertion was moved to the actual documented fixture rather than weakened. Targeted and root-invoked full Bats checks pass 174/174; Forgejo CI is terminal 26/26 green.
bosun merged commit 98b71840ae into main 2026-09-06 14:56:26 +02:00
bosun deleted branch i/1321-uses-location-not-provider 2026-09-06 14:56:26 +02:00
Sign in to join this conversation.
No description provided.