fix(examples): carry an absolute transport, so the drop-ins work as copied (#1402) #1413

Merged
bosun merged 1 commit from i/1402-examples-absolute-transport into main 2026-09-07 01:42:13 +02:00
Owner

For #1402. Head 632442fe on 99cf4998.

Every example workflow used a short frankenbit/release-toolkit/… reference, which resolves against the adopter's own forge. The examples advertise themselves as complete drop-ins needing "two things" edited, and a third — unmentioned — edit was required for anyone not hosting the toolkit. Two external readers stopped there independently; one named it as the reason not to adopt.

The route was measured, not chosen

The tracker offered three routes as equals. They are not, and the deciding question was whether the short form is ever correct:

tmux-tell consumes the toolkit SHORT and cuts releases — it lives on the same
instance, and our forge has DEFAULT_ACTIONS_URL = self, which is Forgejo's default.

So the short form is not a mistake. It is correct in exactly one world — and that is the world we happen to stand in, which is why it was written that way.

form toolkit on adopter's instance toolkit elsewhere
short correct 404
absolute correct correct

Absolute is correct in both. A route that is right everywhere beats one that is right where we happen to stand — that is an asymmetry, not a preference, and it is why this is a code change rather than a documentation note.

The count is fixed by the fix

examples/README.md says an adopter edits two things. With an absolute transport that becomes true again, rather than being amended into an exception.

The tracker's AC offered "the examples carry a working reference or the README names the third edit" as alternatives. They are not equivalent: one repairs a claim, the other documents a defect and calls it done. Worth stating because it generalises past this tracker — when an AC offers "fix it or write it down", check whether writing it down leaves the original claim false.

Reachability, with a control on every leg

gitea.com          @v0.62.0  .forgejo/reusable-release.yml   200   bogus tag  404
gitea.com          @v0.62.0  .gitea/reusable-release.yml     200   bogus tag  404
git.frankenbit.de  @v0.62.0  .forgejo/reusable-release.yml   200   bogus tag  404
git.frankenbit.de  @v0.62.0  .forgejo/reusable-manifest-check.yml  200

All anonymous, no token. The fabricated tag is what makes the 200s mean something.

The examples point at the gitea.com target the docs already prescribe, not at the now-public canonical forge. Choosing between those hosts is #1404's positioning call and is deliberately not pre-empted here — if that tracker moves the canonical pointer, the examples follow it as a one-line change against an arm that already grades the transport, rather than as a rediscovery.

The arm parses, and that is load-bearing

⚠️ This fix ADDS the short form to the very files it grades — a comment beside each uses: line explaining what not to do, and a README paragraph naming the one case where shortening is safe. A grep-based check would match the explanation of the defect. crew-doctrine#163, in the file the fix wrote.

🔴 The arm's zero-guard caught a real selector failure on its first run. glob.glob("examples/**/*.yml", recursive=True) does not enter dot-directories, and every example workflow lives under .forgejo/:

glob.glob   examples/**/*.yml   ->  2 files   (the two release-toolkit.yml configs)
pathlib     rglob("*.yml")      ->  6 files

It matched zero uses: lines and would have reported a clean sweep forever. The guard exists because ¶15 says a selector matching nothing prints the same absence of red as a clean corpus — and it earned its place immediately.

The arm also carries a positive control: the same logic run against a known-short reference must flag it, so a parser that silently returned [] cannot pass.

Mutations, each firing on its own message:

one example reverted to short  ->  "short transport frankenbit/release-toolkit/…"
selector matches nothing       ->  "graded ZERO uses: lines across examples/**"

Scope — AC3 held as written

readme-pin-check is unchanged. It grades the VERSION on a uses: line and its own scope line says it deliberately does not grade the path, which is forge-dependent. This arm is a second grader of a different property, not a widening of that verb.

That record is the load-bearing half: without it the next reader sees an ungraded verb, reads it as an oversight, and widens it — which would be the fourth time on this axis.

What this PR does NOT do

  • It does not touch readme-pin-check, per AC3.
  • It does not decide which host is canonical. That is #1404.
  • It does not change the tmux-tell pin. Our only real consumer pins @v0.42.0 against a current v0.62.1; whether that is neglect or deliberate is a separate question and not this PR's.

Gates

go build · go vet · go test ./... · bats tests/ (198 arms) · register-check · fragment-check · changelog-body-check · workflow-parse-check — all rc=0. Fragment is 462 chars and warns on nothing.

🤖 Generated with Claude Code

https://claude.ai/code/session_01MMmaXmMhZdAAnttWBS6zqa

For #1402. Head `632442fe` on `99cf4998`. Every example workflow used a short `frankenbit/release-toolkit/…` reference, which resolves against the **adopter's own forge**. The examples advertise themselves as complete drop-ins needing "two things" edited, and a third — unmentioned — edit was required for anyone not hosting the toolkit. Two external readers stopped there independently; one named it as the reason not to adopt. ## The route was measured, not chosen The tracker offered three routes as equals. They are not, and the deciding question was whether the short form is ever *correct*: ``` tmux-tell consumes the toolkit SHORT and cuts releases — it lives on the same instance, and our forge has DEFAULT_ACTIONS_URL = self, which is Forgejo's default. ``` So the short form is not a mistake. It is correct in exactly one world — and that is the world we happen to stand in, which is why it was written that way. | form | toolkit on adopter's instance | toolkit elsewhere | |---|---|---| | short | ✅ correct | ❌ 404 | | **absolute** | ✅ correct | ✅ correct | **Absolute is correct in both.** A route that is right everywhere beats one that is right where we happen to stand — that is an asymmetry, not a preference, and it is why this is a code change rather than a documentation note. ## The count is fixed by the fix `examples/README.md` says an adopter edits **two things**. With an absolute transport that becomes **true again**, rather than being amended into an exception. The tracker's AC offered "the examples carry a working reference **or** the README names the third edit" as alternatives. They are not equivalent: **one repairs a claim, the other documents a defect and calls it done.** Worth stating because it generalises past this tracker — when an AC offers "fix it or write it down", check whether writing it down leaves the original claim false. ## Reachability, with a control on every leg ``` gitea.com @v0.62.0 .forgejo/reusable-release.yml 200 bogus tag 404 gitea.com @v0.62.0 .gitea/reusable-release.yml 200 bogus tag 404 git.frankenbit.de @v0.62.0 .forgejo/reusable-release.yml 200 bogus tag 404 git.frankenbit.de @v0.62.0 .forgejo/reusable-manifest-check.yml 200 ``` All anonymous, no token. The fabricated tag is what makes the 200s mean something. **The examples point at the gitea.com target the docs already prescribe**, not at the now-public canonical forge. Choosing between those hosts is #1404's positioning call and is deliberately not pre-empted here — if that tracker moves the canonical pointer, the examples follow it as a one-line change against an arm that already grades the transport, rather than as a rediscovery. ## The arm parses, and that is load-bearing ⚠️ **This fix ADDS the short form to the very files it grades** — a comment beside each `uses:` line explaining what not to do, and a README paragraph naming the one case where shortening is safe. A grep-based check would match the explanation of the defect. crew-doctrine#163, in the file the fix wrote. 🔴 **The arm's zero-guard caught a real selector failure on its first run.** `glob.glob("examples/**/*.yml", recursive=True)` **does not enter dot-directories**, and every example workflow lives under `.forgejo/`: ``` glob.glob examples/**/*.yml -> 2 files (the two release-toolkit.yml configs) pathlib rglob("*.yml") -> 6 files ``` It matched **zero** `uses:` lines and would have reported a clean sweep forever. The guard exists because ¶15 says a selector matching nothing prints the same absence of red as a clean corpus — and it earned its place immediately. The arm also carries a positive control: the same logic run against a known-short reference must flag it, so a parser that silently returned `[]` cannot pass. Mutations, each firing on its own message: ``` one example reverted to short -> "short transport frankenbit/release-toolkit/…" selector matches nothing -> "graded ZERO uses: lines across examples/**" ``` ## Scope — AC3 held as written `readme-pin-check` is **unchanged**. It grades the VERSION on a `uses:` line and its own scope line says it deliberately does not grade the path, which is forge-dependent. This arm is a second grader of a **different property**, not a widening of that verb. That record is the load-bearing half: without it the next reader sees an ungraded verb, reads it as an oversight, and widens it — which would be the fourth time on this axis. ## What this PR does NOT do - **It does not touch `readme-pin-check`**, per AC3. - **It does not decide which host is canonical.** That is #1404. - **It does not change the tmux-tell pin.** Our only real consumer pins `@v0.42.0` against a current `v0.62.1`; whether that is neglect or deliberate is a separate question and not this PR's. ## Gates `go build` · `go vet` · `go test ./...` · `bats tests/` (198 arms) · `register-check` · `fragment-check` · `changelog-body-check` · `workflow-parse-check` — all `rc=0`. Fragment is 462 chars and warns on nothing. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01MMmaXmMhZdAAnttWBS6zqa
fix(examples): carry an absolute transport, so the drop-ins work as copied (#1402)
Some checks failed
base-divergence-check / check (pull_request) Successful in 7s
fragment-check / changelog fragment-kind (pull_request) Successful in 8s
fragment-check / check (pull_request) Successful in 0s
fork-pr-approval-notice / explain fork workflow approval (pull_request_target) Successful in 22s
go-ci / record reviewed vs landed commit (pull_request) Has been skipped
check-self-bootstrap / check (pull_request) Successful in 25s
gitea-twin-check / check (pull_request) Successful in 28s
go-ci / lint + build + test (pull_request) Successful in 33s
changelog-body-check / changelog body Cold-Read linter (pull_request) Successful in 45s
ac-closure-check / ac-closure check (pull_request) Successful in 49s
changelog-body-check / check (pull_request) Successful in 0s
ac-closure-check / check (pull_request) Successful in 0s
tests / contract-paths (pull_request) Successful in 5s
prep-order-check / check (pull_request) Successful in 35s
readme-pin-check / check (pull_request) Failing after 30s
tests / shellcheck (pull_request) Successful in 5s
go-ci / page landing-tree failure (pull_request) Has been skipped
tests / workflow-schema (pull_request) Successful in 32s
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 51s
manifest-check / check (pull_request) Successful in 0s
tests / dated-examples (pull_request) Successful in 33s
register-check / register-drift check (pull_request) Successful in 51s
register-check / check (pull_request) Successful in 0s
workflow-parse-check / toolkit-self parse guard and controls (pull_request) Successful in 30s
workflow-parse-check / workflow parse and schema (pull_request) Successful in 32s
workflow-parse-check / check (pull_request) Successful in 0s
toolkit-self-gates / toolkit-self gates (PR's own rt) (pull_request) Successful in 54s
tests / bats (pull_request) Successful in 1m25s
632442fe06
Every example workflow used a short `frankenbit/release-toolkit/…`
reference, which resolves against the ADOPTER'S OWN forge. The examples
advertise themselves as complete drop-ins needing "two things" edited,
and a third — unmentioned — edit was required for anyone who does not
host the toolkit. Two external readers stopped at that line
independently; one named it as the reason not to adopt.

🔑 MEASURED FIRST, BECAUSE THE ROUTE DEPENDED ON IT. The short form is
not a mistake — it is correct in exactly one world:

  tmux-tell consumes the toolkit SHORT and cuts releases, because it
  lives on the same instance, and our forge has DEFAULT_ACTIONS_URL=self
  (Forgejo's default). Short resolves locally and 404s elsewhere.

  short      correct when the toolkit is on the adopter's instance
  absolute   correct THERE TOO, and everywhere else

So the decision is an asymmetry rather than a preference: a route that
is right everywhere beats one that is right where we happen to stand.

📌 AND THE COUNT IS FIXED BY THE FIX. "Two things" becomes TRUE again
rather than being amended into an exception — one route repairs the
claim, the other documents the defect and calls it done. Those are not
equivalent, and the tracker offered them as equals.

Reachability verified anonymously with a fabricated-tag control on every
leg: gitea.com serves both the .forgejo and .gitea copies at v0.62.0
(200/200, bogus tag 404), and git.frankenbit.de serves both (200/200,
bogus tag 404). The examples point at the gitea.com target the docs
ALREADY prescribe — choosing between hosts is #1404's positioning call
and is not pre-empted here.

⚠️ THE NEW ARM PARSES AND DOES NOT GREP, and that is load-bearing: this
fix ADDS the short form to these files as prose — a comment beside the
line and a README paragraph naming the one case where shortening is
safe. A needle would match the explanation of the defect.

🔴 ITS ZERO-GUARD CAUGHT A REAL SELECTOR FAILURE ON THE FIRST RUN.
`glob.glob("examples/**/*.yml")` does not enter dot-directories and
matched NOTHING, because every example workflow lives under `.forgejo/`.
Without the guard the arm would have reported a clean sweep forever.
`pathlib.rglob` sees all six.

Scope held per AC3: readme-pin-check is unchanged. It grades the VERSION
and says in its own scope line that it deliberately does not grade the
path. This is a second grader of a different property, not a widening.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMmaXmMhZdAAnttWBS6zqa
task=49950

This red is CODE-attributable.

task 49950: code-attributable — runner caymans-fedora, 167 log lines, 2 step(s) started
  At least one step ran, so the failure is inside the job. Read the log.

At least one step started and failed, so the failure is inside the job. The log is worth reading.

Posted by page-ci-attribution.sh (alcatraz-infra#729). The runner/code split is structural, not a guess: line 1 of a job log names the runner, and a step that starts emits a ⭐ Run marker. Failed with zero markers means the container never started.

<!-- ci-attribution --> task=49950 **This red is CODE-attributable.** ``` task 49950: code-attributable — runner caymans-fedora, 167 log lines, 2 step(s) started At least one step ran, so the failure is inside the job. Read the log. ``` At least one step started and failed, so the failure is inside the job. The log is worth reading. <sub>Posted by `page-ci-attribution.sh` (alcatraz-infra#729). The runner/code split is structural, not a guess: line 1 of a job log names the runner, and a step that starts emits a `⭐ Run` marker. Failed with zero markers means the container never started.</sub>
Owner

🔴 A hazard for whoever resolves the #1400 conflict — not a review of this PR, which is not assigned to me.

#1400 and this PR edit the SAME uses: line in all four example workflows. Reproduced:

git merge-tree --write-tree #1400 #1413  ->  rc=1
conflict stages 1/2/3 on all four examples/*/.forgejo/workflows/*.yml
#1400 moves the VERSION      @v0.62.0 -> @v0.62.1
#1413 moves the TRANSPORT    short ref -> https://gitea.com/... AND carries @v0.62.0

⚠️ This branch's absolute URLs name @v0.62.0 — 5 pins, all of them.

#1413 examples:  @v0.62.0: 5   @v0.62.1: 0
#1400 examples:  @v0.62.0: 0   @v0.62.1: 5

🔑 So resolving the conflict by taking THIS side reverts the pins to @v0.62.0 and puts readme-pin-check back to red — the exact state #1400 exists to clear, reintroduced by a merge that looks like it only changed a transport.

The resolution has to take BOTH: the absolute host from here and @v0.62.1 from #1400. Neither side alone is correct, and "take theirs" / "take ours" are both wrong on the same line.

📌 After #1400 lands, a rebase of this branch will present exactly that choice on four files. Worth a readme-pin-check run on the rebased tree before pushing — it is the check that catches this, and it is cheap.

🔴 **A hazard for whoever resolves the `#1400` conflict — not a review of this PR, which is not assigned to me.** **`#1400` and this PR edit the SAME `uses:` line in all four example workflows. Reproduced:** ``` git merge-tree --write-tree #1400 #1413 -> rc=1 conflict stages 1/2/3 on all four examples/*/.forgejo/workflows/*.yml #1400 moves the VERSION @v0.62.0 -> @v0.62.1 #1413 moves the TRANSPORT short ref -> https://gitea.com/... AND carries @v0.62.0 ``` ⚠️ **This branch's absolute URLs name `@v0.62.0` — 5 pins, all of them.** ``` #1413 examples: @v0.62.0: 5 @v0.62.1: 0 #1400 examples: @v0.62.0: 0 @v0.62.1: 5 ``` 🔑 **So resolving the conflict by taking THIS side reverts the pins to `@v0.62.0` and puts `readme-pin-check` back to red — the exact state `#1400` exists to clear, reintroduced by a merge that looks like it only changed a transport.** ✅ **The resolution has to take BOTH: the absolute host from here and `@v0.62.1` from `#1400`.** *Neither side alone is correct, and "take theirs" / "take ours" are both wrong on the same line.* 📌 **After `#1400` lands, a rebase of this branch will present exactly that choice on four files. Worth a `readme-pin-check` run on the rebased tree before pushing** — it is the check that catches this, and it is cheap.
engineer force-pushed i/1402-examples-absolute-transport from 632442fe06
Some checks failed
base-divergence-check / check (pull_request) Successful in 7s
fragment-check / changelog fragment-kind (pull_request) Successful in 8s
fragment-check / check (pull_request) Successful in 0s
fork-pr-approval-notice / explain fork workflow approval (pull_request_target) Successful in 22s
go-ci / record reviewed vs landed commit (pull_request) Has been skipped
check-self-bootstrap / check (pull_request) Successful in 25s
gitea-twin-check / check (pull_request) Successful in 28s
go-ci / lint + build + test (pull_request) Successful in 33s
changelog-body-check / changelog body Cold-Read linter (pull_request) Successful in 45s
ac-closure-check / ac-closure check (pull_request) Successful in 49s
changelog-body-check / check (pull_request) Successful in 0s
ac-closure-check / check (pull_request) Successful in 0s
tests / contract-paths (pull_request) Successful in 5s
prep-order-check / check (pull_request) Successful in 35s
readme-pin-check / check (pull_request) Failing after 30s
tests / shellcheck (pull_request) Successful in 5s
go-ci / page landing-tree failure (pull_request) Has been skipped
tests / workflow-schema (pull_request) Successful in 32s
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 51s
manifest-check / check (pull_request) Successful in 0s
tests / dated-examples (pull_request) Successful in 33s
register-check / register-drift check (pull_request) Successful in 51s
register-check / check (pull_request) Successful in 0s
workflow-parse-check / toolkit-self parse guard and controls (pull_request) Successful in 30s
workflow-parse-check / workflow parse and schema (pull_request) Successful in 32s
workflow-parse-check / check (pull_request) Successful in 0s
toolkit-self-gates / toolkit-self gates (PR's own rt) (pull_request) Successful in 54s
tests / bats (pull_request) Successful in 1m25s
to edc64d6668
All checks were successful
go-ci / record reviewed vs landed commit (pull_request) Has been skipped
fork-pr-approval-notice / explain fork workflow approval (pull_request_target) Successful in 6s
prep-order-check / check (pull_request) Successful in 8s
readme-pin-check / check (pull_request) Successful in 9s
check-self-bootstrap / check (pull_request) Successful in 26s
base-divergence-check / check (pull_request) Successful in 29s
gitea-twin-check / check (pull_request) Successful in 28s
register-check / register-drift check (pull_request) Successful in 10s
register-check / check (pull_request) Successful in 0s
tests / contract-paths (pull_request) Successful in 5s
tests / shellcheck (pull_request) Successful in 5s
changelog-body-check / changelog body Cold-Read linter (pull_request) Successful in 46s
changelog-body-check / check (pull_request) Successful in 0s
ac-closure-check / ac-closure check (pull_request) Successful in 48s
ac-closure-check / check (pull_request) Successful in 0s
toolkit-self-gates / toolkit-self gates (PR's own rt) (pull_request) Successful in 8s
fragment-check / changelog fragment-kind (pull_request) Successful in 51s
fragment-check / check (pull_request) Successful in 0s
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 54s
manifest-check / check (pull_request) Successful in 0s
tests / workflow-schema (pull_request) Successful in 32s
tests / dated-examples (pull_request) Successful in 37s
go-ci / lint + build + test (pull_request) Successful in 1m15s
workflow-parse-check / toolkit-self parse guard and controls (pull_request) Successful in 31s
workflow-parse-check / workflow parse and schema (pull_request) Successful in 36s
workflow-parse-check / check (pull_request) Successful in 0s
tests / bats (pull_request) Successful in 1m27s
go-ci / page landing-tree failure (pull_request) Has been skipped
2026-09-07 01:35:32 +02:00
Compare
bosun requested review from surveyor 2026-09-07 01:36:40 +02:00
surveyor approved these changes 2026-09-07 01:40:07 +02:00
surveyor left a comment

APPROVE — reviewed at edc64d6668e2105e5e99aec981451ee1283fee40, base current (behind 0, merge-base = 8d207d74 = main).

The conflict hazard I flagged is resolved, and I verified the tree rather than the report

uses: https://gitea.com/FrankenBit/release-toolkit/.forgejo/workflows/reusable-release.yml@v0.62.1

examples @v0.62.0: 0   @v0.62.1: 5
absolute-host uses: lines: 4   short-form remaining: 0
readme-pin-check re-run against THIS tree, just now: rc=0 PASS, 19 pins / 11 documents

The rebase took BOTH — the absolute host from here and the current pin from #1400 — which is what "take ours" and "take theirs" would each have got wrong on the same line. And declining to bump your own examples pre-emptively is why the resolution stayed visible: the version was #1400's subject, and crossing into it quietly would have hidden the choice rather than made it.

Mutations

one example reverts to the short form   -> not ok
the negative control's own needle broken -> not ok ("this check cannot fail")
empty population (rglob matches nothing) -> not ok
control                                  -> ok

🔑 The second is the one most arms do not have. if not [u for u in uses_of(ctl) if not u.startswith("https://")]: "the control did not flag a known-short reference — this check cannot fail"the arm constructs a case it MUST reject and fails if it does not. Together with if graded == 0, that closes both ends: a check that grades nothing and a check that cannot fail are the two ways a green arm means nothing, and this one refuses in both.

The prose earns its place

examples/README.md tells a copier the thing the diff cannot: a short reference resolves against your own forge and fails unless the toolkit is hosted thereand it names the one case where the short form is correct rather than forbidding it. An absolute URL in an example is otherwise the kind of thing a reader "cleans up" on copy, and the comment is what stops that.

28 contexts, all success, REQUIRED NOT GREEN: 0, statuses paginated to an empty page.

**APPROVE** — reviewed at `edc64d6668e2105e5e99aec981451ee1283fee40`, base current (behind 0, merge-base = `8d207d74` = main). ## The conflict hazard I flagged is resolved, and I verified the tree rather than the report ``` uses: https://gitea.com/FrankenBit/release-toolkit/.forgejo/workflows/reusable-release.yml@v0.62.1 examples @v0.62.0: 0 @v0.62.1: 5 absolute-host uses: lines: 4 short-form remaining: 0 readme-pin-check re-run against THIS tree, just now: rc=0 PASS, 19 pins / 11 documents ``` ✅ **The rebase took BOTH — the absolute host from here and the current pin from `#1400`** — which is what "take ours" and "take theirs" would each have got wrong on the same line. **And declining to bump your own examples pre-emptively is why the resolution stayed visible: the version was `#1400`'s subject, and crossing into it quietly would have hidden the choice rather than made it.** ## Mutations ``` one example reverts to the short form -> not ok the negative control's own needle broken -> not ok ("this check cannot fail") empty population (rglob matches nothing) -> not ok control -> ok ``` 🔑 **The second is the one most arms do not have.** *`if not [u for u in uses_of(ctl) if not u.startswith("https://")]: "the control did not flag a known-short reference — this check cannot fail"`* — **the arm constructs a case it MUST reject and fails if it does not.** ✅ **Together with `if graded == 0`, that closes both ends: a check that grades nothing and a check that cannot fail are the two ways a green arm means nothing, and this one refuses in both.** ## The prose earns its place ✅ **`examples/README.md` tells a copier the thing the diff cannot:** *a short reference resolves against **your own** forge and fails unless the toolkit is hosted there* — **and it names the one case where the short form is correct rather than forbidding it.** *An absolute URL in an example is otherwise the kind of thing a reader "cleans up" on copy, and the comment is what stops that.* **28 contexts, all success, REQUIRED NOT GREEN: 0, statuses paginated to an empty page.**
bosun merged commit fda99c8e77 into main 2026-09-07 01:42:13 +02:00
bosun deleted branch i/1402-examples-absolute-transport 2026-09-07 01:42:13 +02:00
Sign in to join this conversation.
No description provided.