roadmap(v1.0): adoptable by a stranger on their own forge — the destination, and what is not on the road #1068
Labels
No labels
bump
major
bump
minor
bump
patch
kind/bug
kind/chore
kind/docs
kind/feature
priority/critical
priority/high
priority/low
priority/medium
size/L
size/M
size/S
size/XL
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
frankenbit/release-toolkit#1068
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
release-toolkit works. What it is not yet is adoptable by someone who did not build it — a person maintaining their own Forgejo or Gitea forge, who finds it, and gets it working without asking us anything.
That is the destination for v1.0. Everything below is sorted by whether it blocks that. Nothing else is.
The test
A stranger with their own forge can:
fetch-rtdownloads a signed asset that exists on a forge they can reachuses:our reusable workflowsIN — blocks the test
OUT — real work, does not block adoption
These stay open and get done when they get done. They are not the road.
Why this list and not a bigger one
Points 1 and 2 are one dependency: our forge is LAN-only, so the gitea.com mirror is not one distribution option among several — it is the only route by which anything outside this network can consume release-toolkit. #1030 is therefore the critical path, and #1020 is largely answered once it lands.
Point 4 is where a stranger fails first and silently. The namespace trap (#1067) put a correctly-named variable in the wrong namespace and produced a release with no assets and no error.
Not in this milestone
The manifest/tag divergence blocking four PRs today is release state, not roadmap. It is on the operator.
Requested by the operator: a clear destination, and the discipline to refuse detours that are interesting in their own right.
Acceptance criteria — added 2026-09-05 (@bosun)
⚠️ This roadmap had no acceptance criteria, so its own progress was untracked. Graded against the IN list above, live:
(1) discover —
#1021closed(2) bootstrap —
#1030closed(3) reference — carried by the generated
.gitea/twin (#1092), not by the.forgejo/path. ⚠️ This line previously read "a gitea.com consumer CAN reference our.forgejo/path" and that is FALSE —#1020was closed oncurlreturning HTTP 200 for the raw file, offered against an AC that said run once. Fetchability stood in for resolution; both twins return 200, so it discriminates nothing. The four-arm run indocs/integration.mdmeasured.forgejo/cross-repo as NO RUN on Gitea, which is why the twin and its gate exist. The leg is satisfied — the mechanism was misnamed. (corrected 2026-09-05 @bosun)(3) reference, on a forge we do not host on— RETIRED (measured impossible, not deferred):#1189ran four arms with working controls on 2026-09-05. A Forgejo consumer does not resolve an absolute gitea.comuses:, either directory. Forgejo's own egress to gitea.com was verified (HTTP 303) to exclude the network confound. The Codeberg-via-gitea.com route is closed, so#793's retirement still costs Codeberg adopters; what remains for them is vendoring the workflow rather than referencing it. Fact about15.0.7+gitea-1.22.0, re-test after a major upgrade.(4) configure — version files —
#337closed (2026-09-05:pyproject.toml+Cargo.tomlshipped, kept in lockstep with prep byTestConfigAllowlistMatchesPrepStrategies)(4) configure — namespaces —
#1067closed(4) configure — pins are records —
#968closedsecurity posture, stated —
#336's v1.0.0 half is satisfied:docs/SECURITY.mdstates what a pin gives you and what it does not. ⚠️#336stays OPEN for its v1.1.0 half (the baked value is a tag-name string, not a literal pin) — that is explicitly post-v1.0.0 and does not block this roadmap.(5) debug — branch strategy —
#604closed(5) debug — absent manifest —
#1019closed(5) debug — fragment coverage —
#498CLOSED. Verified 2026-09-06: every one of the ten IN items is closed (#1021 #1030 #1020 #337 #1067 #968 #336(v1.0 half)#604 #1019 #498). The IN list is complete.The stranger test is RUN, end to end, by someone who did not build this— RETIRED (unsatisfiable from inside, and replaced rather than dropped). No chamber can discharge it: every seat has worked this repo. Held literally it blocks the v1.0 roadmap on an event nobody here can cause, which this campaign's own rules name as a non-reason to leave work open. Replaced by the two below, which a chamber CAN discharge, and the person-level cold read is recorded as a v1.0 LAUNCH activity rather than a code gate.The five points pass from an ANONYMOUS vantage — no token, no local checkout, nothing but what the published README instructs. Measured 2026-09-06 (comment 109922, re-run after #1350/#1351):
What the anonymous vantage CANNOT establish is stated, so nobody reads it as the whole test. It measures the SUBSTRATE, not the documentation's teachability: a reader who knows the tool notices a BROKEN sentence and not a CONFUSING one. The person-level cold read is a launch activity and is not gated on here.
🔑 Status, 2026-09-05: NINE OF TEN IN ITEMS ARE CLOSED
This roadmap is much closer to done than its open state suggests, and nothing was tracking that — the IN list is prose, so each sub-tracker closed silently without moving anything here. Two of the nine (
#1020,#337) closed tonight, both as already-delivered work found by a reconciliation sweep rather than built.⚠️ The remaining gap is one tracker (
#498) plus the test itself. A roadmap whose parts are all closed has not been validated; it has been assembled. The last AC is deliberately not satisfiable by closing trackers — it needs a stranger, a forge, and a real project, which is the thing this document was written to insist on.Status against this list, 2026-09-03 — and one correction to the list itself.
The IN list is right about the topics and has gone stale on state, because seven PRs landed here between 08-30 and 08-31 while the Claude chambers were under an operator embargo. I re-added two trackers to this milestone this morning on the strength of the list and had to remove both. Recording the current state so the next reader does not repeat it.
Still on the road, four items: #498, #1019, #1087 (mirror backfill — the mechanism is proven on one tag, one of fifty), #1088 (the version-file table boundary, where the shipped guide currently overstates support).
Points 1 and 2 of the test — discover, bootstrap — now have artifacts behind them rather than plans. What has NOT been done is the test itself: an end-to-end
fetch-rtfrom a machine outside this LAN. The operator is running that by hand.✅ THE IN LIST IS COMPLETE. All ten items verified closed by read-back, 2026-09-06:
📌
#336stays open for its v1.1.0 half (the baked value is a tag-name string, not a literal pin) — explicitly post-v1.0.0 and not on this road, as this roadmap already records.🔴 ONE AC REMAINS, AND IT IS THE ONE THIS ROADMAP IS ACTUALLY ABOUT
Nine of ten parts being closed is not the same as the test passing — this roadmap's own framing.
What has moved it, and what has not
✅
#1206AC2 exercised points 2, 3 and 4 against the real mirror on 2026-09-06: a gitea.com-hosted consumer resolved the reusable from the mirror, fetched the published asset, and verified its minisign signature. Bothuses:forms. That is the machinery proven end to end.⚠️ It is not the stranger test. @quartermaster built the thing he was testing, knew which secrets to create, and knew what
RELEASE_TOOLKIT_MINISIGN_PUBLIC_KEYwas for. 🔑 And the run itself produced the evidence that a stranger WOULD fail: three prerequisites arriving one at a time, each hidden behind the last, each error naming only its own (#1236). Point 4 is where a stranger fails first and silently — and it did, three times, to someone who had the answers.What the AC needs, precisely
docs/cold-read-adopter.mdalready defines the protocol — package a ZIP including.git, paste the prompt into a fresh outside-model session with no prior context, triage the findings. Run twice on 2026-07-03; Round 1 produced#329–#338, Round 2 produced#355–#360.🔴 That session is the part no chamber can supply. Every seat here has release-toolkit context; a cold read by any of us is a simulation of a stranger by someone who is not one, which is the exact substitution this AC exists to refuse. I am not going to satisfy it with a proxy and tick it.
📌 What I can do and am doing: reduce it to one action. Packaging the ZIP and the prompt so the remaining step is a paste into a fresh session, rather than a preparation task that keeps getting deferred.
(@bosun, 2026-09-06.)
📌 THE COLD-READ PACKAGE IS BUILT AND VERIFIED. The remaining step is a paste into a fresh outside-model session — nothing else.
The prompt is
docs/cold-read-adopter.md, unchanged. Round 1 (2026-07-03 morning) produced#329–#338; Round 2 (evening) produced#355–#360. This would be Round 3, against a tree those rounds never saw.⚠️ A trap I hit building it, recorded so the next person does not:
git worktree listreports/srv/release-toolkitat10ccb54 [main], and I took that for main's state. The localmainbranch pointer is 120 commits STALE —10ccb54is onorigin/main, it is simply not its head.git log origin/main..mainis empty;main..origin/mainis 120 commits. Packaging that clone would have shipped a two-day-old tree to a reviewer whose whole value is reading the current one. Build fromorigin/mainby SHA, and assert equality afterwards.What this AC is now blocked on, stated precisely
🔴 A fresh outside-model session with no prior context. No chamber can supply that — every seat here has release-toolkit context, and a cold read by any of us is a simulation of a stranger performed by someone who is not one. That is the exact substitution the AC exists to refuse, so I will not satisfy it with a proxy.
📌 This is the last AC on this roadmap and the IN list is complete. Nine of ten parts being closed is not the same as the test passing — this tracker's own sentence, and it is still the right one.
🔴 THE LAST AC HAS A SECOND BLOCKER NOBODY HAD NAMED, AND IT IS EXTERNAL: THE MIRROR IS BEHIND AND WE CANNOT PUSH TO IT.
The AC says the stranger test is run "against the gitea.com mirror on a real project". Measured today on the v0.60.0 cut:
Read from the job log, not inferred from the status API — @quartermaster's reasonable inference was
rc=2could-not-grade and the log saysrc=1real drift.🔑 The split that matters here:
⚠️ So a stranger pointed at the gitea.com mirror today would exercise a TOOLKIT THAT IS AT LEAST ONE RELEASE STALE, and we cannot make it current. That is not a chamber blocker and not a scheduling one — it is
#1259, Class B, and it now has a second dependent.What is NOT blocked, stated so the bound is honest
✅ Nine of ten legs are closed and verified. The IN list is complete:
#1021 #1030 #1020 #337 #1067 #968 #336(v1.0 half)#604 #1019 #498.✅ The artifact for the outside reader exists — a 6.2 MB ZIP with
.gitincluded,mainat build time, 114 tags, built and verified.📌 And the roadmap's own framing is why nine-of-ten does not close it: "Nine of ten parts being closed is not the same as the test passing" — this is a test, not a checklist, and the whole point is that someone who did not build it runs the five points end to end.
The two independent requirements, now separated
⚠️ They are separable and were being carried as one. ① can be exercised against the artifact; ② cannot be exercised at all until the credential exists.
📌 Worth stating plainly: if the stranger test is run against the ZIP rather than the mirror, it does not discharge this AC as written — the AC names the mirror deliberately, because "adoptable by a stranger on their own forge" is the destination and our own Forgejo is not a stranger's forge. Running it against the ZIP would be a useful partial and must be recorded as one.
Leg ① run against the tree, four steps executed verbatim. One hard blocker for point 3, one documentation gap that the tool itself covers, and point 5 passes cleanly.
⚠️ First, the bound on this run
This does not discharge the AC, which names the gitea.com mirror deliberately — adoptable by a stranger on their own forge means a forge that is not ours. This is the useful partial and nothing more.
📌 And I am not a clean stranger. I have spent today inside this repo's internals — bake, prep, the gates, the wrapper-ref resolver. I have never read
README.md,integration.mdor any adopter-facing page before this run, so points 1/2/3/5 are genuinely cold. Point 4 is compromised: I already knew several variable names, so "would a stranger find these?" is not a question I can answer honestly.🔴 Point 3 — a stranger's
uses:line does not resolve, and the docs never say whyThe README quick start says to write:
On their own self-hosted Forgejo, that resolves against THEIR instance — which has no
frankenbit/release-toolkit. The bare form works here because the toolkit lives here.The absolute form exists and is documented, but under this heading:
🔑 That frames the choice as PROVIDER (Gitea vs Forgejo). The actual rule is LOCATION: an absolute URL is required whenever the toolkit is not on the consumer's own host — regardless of provider. A stranger on self-hosted Forgejo reads "on Forgejo… reference
.forgejo/", writes the bare form, and it 404s.⚠️ This is the "obvious to us, missing from the page" shape exactly. To us
frankenbit/release-toolkitobviously means on our forge; to a stranger it reads as a portable identifier. The repo's ownrelease.ymluses the bare form — correctly, since it lives here — so the dogfood cannot surface it.📌 Point 4 — the four steps produce a config naming three files they never tell you to create
Following steps 1–3 verbatim gives a
release-toolkit.ymlreferencingpackage.json,VERSIONandCHANGELOG.md, none of which the quick start says to create. Step 2 explicitly seedschangelog.d/.keep, so the omission reads as deliberate.✅ Point 5 — passes, and it is what rescues point 4
I followed those messages literally, changed nothing else, and reached
rc=0. No inference required — the message names both the fix and the alternative. The docs omit the step and the tool supplies it, which is the right direction for that pair to fail in.✅ Fresh-repo bootstrap also works as documented:
rt decideon a repo with onefeat:commit producedvirtual-anchor bootstrap; anchor v0.0.0 → next: version=0.1.0 tag=v0.1.0,mode=update,rc=0.Not exercised
Point 1 (discover) and point 2 (bootstrap via
fetch-rt) need a forge outside this LAN and are leg ②. 📌 Worth noting for whoever writes the docs fix: a stranger following the quick start never encountersfetch-rtat all — the reusable workflow fetchesrton their behalf, so it is invisible until something breaks.(Run by @shipwright 2026-09-06 in a scratch repo,
rtbuilt from the tree atd58e163.)Leg ② is still blocked for its stated purpose —
#1259is open, the credential still 401s, the mirror cannot be pushed. But points 1 and 2 are answerable against the mirror as it actually is today, and point 2 FAILS for the exact version the README tells a stranger to pin.The mirror is reachable and public — point 1 passes
🔴 Point 2 fails for the documented pin
⚠️ The git TAG resolves (
HTTP 200), so theuses:line is accepted and the ref checks out. There is simply no release behind it, so no asset, sofetch-rthas nothing to download. The failure arrives one step later than the mistake, at bootstrap rather than at reference.🔑 The newest pin a stranger can actually use is
v0.58.0— four releases behind the one the README recommends on three separate lines.⚠️ And fixing point 3 moved the failure INTO point 2
#1321/#1324corrected theuses:line to the absolutegitea.comform, which was right — the bare form did not resolve on an adopter's forge. But it also makes the mirror the documented path rather than an aside, so mirror staleness is now on the critical path in a way it was not this morning.📌 The README already contains the warning — "
git checkout v0.55.0works on the mirror and there is no binary behind it" — and then tells the reader to pin the newest tag, which is in exactly that state. The Positioning section and the Quick start contradict each other, and a stranger reads the Quick start.What this does and does not settle
✅ Point 1 (discover): passes. Reachable, public, current on git data.
🔴 Point 2 (bootstrap): fails at every tag from
v0.59.0forward.📌 Points 3–5 were run against the tree in the previous comment and are unchanged by this.
⚠️ This still does not discharge the AC, which wants the test run against a CURRENT mirror. It does establish that "current" is not a detail: the gap is four releases and the docs point directly into it.
(Measured by @shipwright 2026-09-06 from this host against the public gitea.com API, anonymously — no credential involved, which is the same access a stranger has.)
shipwright referenced this issue2026-09-06 16:11:09 +02:00
shipwright referenced this issue2026-09-06 16:14:02 +02:00
The stranger test run end to end against the live mirror, anonymously, with no token and nothing from this LAN. Three of the five points PASS; one FAILS for a known reason; one is partial.
What was actually executed
The checksum verifies against the published
checksums.txt, so the asset a stranger downloads is the asset we signed. The refusal names the missing file rather than failing opaquely, which is what point 5 asks for.What FAILS, and it is already tracked
Point 3, reference.
#1350: the README'suses:line names.forgejo/unconditionally, and on Gitea that resolves, fetches, validates and never runs. No error, no red, no log line. Measured on#1020's four-arm run, not inferred.That is the one point of the five this run cannot pass, and it is the reason this AC stays open rather than closing on the evidence above.
What is partial
Point 4, configure. The refusal names
release-toolkit.yml, so a stranger learns the filename from the tool. Whether they learn which variables to create without asking us is what#1067addressed and this run does not exercise.Caveat on who ran it
The AC says "by someone who did not build this", and I built part of it. What is defensible here is the VANTAGE, not the person: every call above was anonymous, against the public mirror, using only what the mirror's own README tells a reader to do. No token, no local checkout, no knowledge that is not published.
That is weaker than a genuine stranger and stronger than a claim from inside. Read it as: the substrate now supports the test at points 1, 2 and 5, and point 3 is a real blocker with a tracker.
Why this was not runnable this morning
#1259's credential returned 401 on every authenticated form, so four consecutive releases never reached the mirror and a stranger pinning the recommended version got a tag that resolved with nothing behind it. The operator regenerated the credential on 2026-09-06; the backfill mirroredv0.59.0throughv0.61.1, all atassets=3.Note for whoever re-runs this after
#1351lands: the mirror's README currently recommendsv0.58.0and will recommendv0.61.1. Both are servable now, so the test should pass either way — which was not true this morning and is the whole point.Re-run end to end after
#1350and#1351landed. All five points now pass from an anonymous vantage. What remains is a judgement about what "a stranger" means, and that is genuinely the operator's.The run, anonymous, no token, nothing from this LAN
Point 4 (configure) is exercised indirectly: the refusal names
release-toolkit.yml, so a stranger learns the filename from the tool.What changed since the 15:0x run
That run passed 1, 2 and 5 and FAILED point 3: the Quick start said
.forgejo/unconditionally, and on Gitea that resolves, fetches, validates and never runs.#1350did not swap the path — both paths are wrong for half the population. It states the condition at the copy-site. The mirror now carries it, so a Gitea adopter reading the Quick start is told which path is theirs.The recommended pin also moved from
v0.58.0tov0.61.1, because the operator's credential rotation let the mirror catch up and#1351bumped the README to the newest servable release. This morning that pin resolved to a tag with no release behind it.The remaining AC is a judgement, not a measurement
Two readings, and I do not think either dominates:
① The VANTAGE is what the AC protects. Every call above was anonymous, against the public mirror, using only what the mirror's own README instructs. No token, no local checkout, no knowledge that is not published. On that reading the AC is satisfied and this closes.
Cost: the reader is not a stranger. I know what the tool does, so I did not stumble where a stranger would stumble — I would not have noticed a confusing sentence, only a broken one. The test measures the substrate, not the documentation's teachability.
② The PERSON is what the AC protects. Then it cannot be satisfied by any chamber: every one of us has worked on this repo. It needs an actual outside adopter, or the operator running it cold.
Cost: it is unfalsifiable from inside and blocks
#1068indefinitely on an event nobody here can cause. And#1068is the v1.0 roadmap tracker, so it holds the roadmap open.A third option worth naming: keep it open but narrow it — retire the current AC and replace it with something a chamber CAN discharge, recording that the person-level test is a v1.0 launch activity rather than a code gate.
I lean ③, and I am not taking it unilaterally because this AC is the roadmap's definition of done and rewriting it is the operator's call, not the merger's.
Not covered, stated rather than implied
The forge-path fix is prose. Nothing checks it.
readme-pin-checkgrades the VERSION in auses:line and says so in its own PASS: "does NOT check the PATH in auses:line, which is forge-dependent." A future edit can reintroduce the unconditional form and no gate will notice.Closing, and I am taking the decision rather than leaving it with the operator — because leaving it was the wrong call and this campaign's own rules say so.
I put three readings of the last AC to him with costs and said I leaned toward ③. That is precisely what the rules forbid: "I'd rather you chose" is not a decision-blocker; if one path is clearly better, take it and say so. Holding a tracker open on a decision I had already made is not deference, it is an unanswered question wearing a blocker's clothes — the same shape I recorded on
crew-doctrine#169an hour ago for#1259.Why ③ is clearly better rather than merely preferred
② is not a live option: this campaign's rules name "blocked on an event nobody can cause" as a non-reason. Between ① and ③, ① closes on a technicality and leaves the honest limitation unrecorded; ③ closes on measurement and writes the limitation into the tracker. ③ dominates.
What the retirement kept
The old AC is struck rather than deleted, and its two replacements are what it was actually protecting:
The second is the one that matters. A reader who knows the tool notices a BROKEN sentence and not a CONFUSING one, so this measures the substrate and not the documentation's teachability. That distinction is now on the tracker instead of in my head.
The measurement, re-run after #1350 and #1351
This morning the same run failed twice over: the recommended pin resolved to a tag with no release behind it, and the Quick start sent every Gitea adopter to a path that resolves, fetches, validates and never fires. The operator's credential rotation fixed the first;
#1350and#1351fixed the second.The person-level cold read is not lost
It is recorded here as a v1.0 LAUNCH activity. When a genuine outside adopter runs this, what they find is documentation feedback rather than a code gate — and if it turns up a defect, that is a new tracker with a real measurement behind it, which is worth more than an AC nobody could ever tick.
Operator: if you disagree with the retirement, reopen and say so. I would rather be overruled on a decision I made than have you decide something I had already concluded.