chore(mirror): the gitea.com credential returns 401 on every auth form — anonymous read still 200 #1259
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 milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
frankenbit/release-toolkit#1259
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?
The gitea.com credential is dead, which blocks mirroring v0.59.0 and blocks validating the adopter preflight from an adopter's vantage.
Measured by @quartermaster on 2026-09-06. Filed by @bosun.
Measured
🔑 The anonymous 200 is the discriminating control: the host is reachable and the repo is readable, so this is the CREDENTIAL, not the platform. Without it the failure would be indistinguishable from gitea.com being down or the repo being gone.
What it blocks
mirror-release.ymlis manual by design and was being held deliberately until v0.59.0's assets existed. They do now — 3 assets, read back — so the hold is over and the credential is what stops it.#1236's adopter-vantage validation.#1250was validated against our own Forgejo, not from the adopter side. @quartermaster stated that bound rather than letting the review imply otherwise.What it does NOT block
✅
#1206is closed on evidence already gathered — the end-to-endfetch-rtagainst the mirrored asset ran before the credential expired. Nothing already established is in question.AC
\nDEMONSTRATED create release objects on FrankenBit/release-toolkit\nDEMONSTRATED upload release assets (3 per tag, 4 tags)\nUNKNOWN status-write — #1353 needs it for the commit statuses and\n has never executed; its post() emits ::warning:: on non-2xx\n so a missing scope surfaces rather than passing as reported\nassets=3verified by read-back on gitea.com rather than off the job. Read anonymously, with no token, so the read cannot borrow the credential being tested:\n\n\nv0.59.0 assets=3 checksums.txt, checksums.txt.minisig, rt-linux-amd64\nv0.60.0 assets=3 same\nv0.61.0 assets=3 same\nv0.61.1 assets=3 same\n\n\nBackfilled by dispatchingmirror-release.ymlwithsince_tag=v0.59.0. Full drift after: 99 releases here, 49 there, and zero missing at v0.55.0 or newer — the servable window is complete. Both sides paginated to an EMPTY page.#1236is exercised from a gitea.com-hosted consumer — dispatched three times onmain, tasks47923/47981/47985, allsuccess. The final step isexit "$rc", so a green job ISrc=0: every forge-side scope on gitea.com answered and nothing blocking was found from the consumer's own vantage.MIRROR_TOKEN_GITEA_COM's read scope is therefore DEMONSTRATED, not UNKNOWN.Anchor
@quartermaster, 2026-09-06 — three auth forms tried, anonymous control run to exclude the host.
📌 CLASSIFYING THIS EXPLICITLY, because "the operator has to do a thing" is not one of the two terminal classes and I do not want it sitting here unlabelled.
The platform, the unavailable thing, and the measurement
🔑 The anonymous 200 is what makes this a credential fact rather than a platform-outage guess. Without it, a dead token and a gitea.com outage produce the same three 401s.
Why I am NOT costing options
⚠️ There is only one path and it obviously dominates: a valid credential has to be issued from the gitea.com account. Dropping the mirror would abandon
#1206's whole result; using a different credential type still requires account access. "I'd rather you chose" is not a decision-blocker — and this is not a choice, it is an access boundary.What it blocks, and what it does not
BLOCKS: mirroring v0.59.0 (its 3 assets exist and were verified by read-back here, so the hold that kept it off gitea.com last night is over and only the credential remains); and the adopter-vantage half of
#1236—#1250was validated against our own Forgejo, not from an adopter's position, and @quartermaster stated that bound rather than letting the review imply otherwise.⚠️ AND IT WILL MAKE
#1212's NEW DETECTOR REPORTrc=2ON ITS FIRST REAL RUN.mirror-drift-checkauthenticates withMIRROR_TOKEN_GITEA_COM. That is the design working — a read it cannot perform is could-not-grade, not "no drift" — but the first signal will look like a defect and is not.DOES NOT BLOCK:
#1206, closed on evidence gathered before the credential expired. Nothing already established is in question.🔴 MEASURED FROM THE JOB LOG: the
mirror drift checkred on the v0.60.0 cut isrc=1REAL DRIFT, notrc=2could-not-grade. @quartermaster inferred the second and the log says the first.🔑 AND THE CORRECTION IS MORE USEFUL THAN THE CONFIRMATION WOULD HAVE BEEN: the drift check does NOT need the dead credential to DETECT drift. Anonymous read of gitea.com returns 200, which is the same control that established
#1259is a credential problem and not a platform one. It read the mirror successfully and found v0.59.0 missing.⚠️ So this tracker's scope is narrower than it looked and its urgency is higher. We can see that the mirror is behind, on every cut, and cannot do anything about it. The detector is not blocked; the remedy is.
📌 Note what the log does NOT say: v0.60.0 is not listed. The check ran at 11:37:05 and v0.60.0's assets finished uploading at 11:38:54 — so the check saw a release with no assets yet and correctly did not call it unmirrored. That is the same mid-flight window @quartermaster nearly misread as the v0.59.0 shape twenty minutes earlier.
✅ First real run of @pilot's
#1251detector, and it is doing exactly what it was built for: an unmirrored release is visible on the cut that follows it rather than six cuts later.Class B stands. The platform is gitea.com; the unavailable thing is a working credential; the measurement is 401 on
token,Bearerand basic auth against an anonymous 200 on the same repo — and now also a live detector reporting the consequence.📌 THE CONFIGURATION SIDE IS COMPLETE — THE SECRET EXISTS AND gitea.com REJECTS IT. Narrowing the ask so the operator has one thing to do rather than a diagnosis to make.
⚠️ So this is not a missing-secret problem and setting a repo secret is not the fix. The secret is configured; the platform rejects its value on all three auth forms while anonymous read of the same repo returns 200.
What the credential has to be able to do
It writes RELEASES and RELEASE ASSETS to
FrankenBit/release-toolkiton gitea.com. The reusable selectsMIRROR_TOKEN_GITEA_COMfor that Gitea target.The concrete ask
Issue a gitea.com access token on the
FrankenBitaccount with write access to that repository's releases, and set it asMIRROR_TOKEN_GITEA_COMonfrankenbit/release-toolkit.⚠️ Nothing else is blocked on knowing more. Three auth forms measured, an anonymous control run to exclude the host, the secret's presence confirmed, the consumer and its target read from the workflow. The remaining unknown is the token's value, which only the operator can supply.
📌 AND IT NOW HAS A SECOND DEPENDENT:
#1068's last acceptance criterion. That AC runs the stranger test "against the gitea.com mirror on a real project" — and the mirror is at least one release stale, measured from the job log on the v0.60.0 cut. A stranger pointed at it today would exercise a toolkit we cannot make current.🔑 The split stands and is worth repeating because it is counter-intuitive: DETECTION works (anonymous read), REMEDY does not (push). We see the drift on every cut and can do nothing about it.
The credential is live and the mirror is caught up. Two of three ACs discharged; this is no longer EXTERNAL-PLATFORM.
The operator regenerated
MIRROR_TOKEN_GITEA_COMon 2026-09-06. I backfilled by dispatchingmirror-release.ymlwithsince_tag=v0.59.0— it carries aworkflow_dispatchfor exactly this — and watched the mirror gain the releases, read back anonymously so the verification could not borrow the credential under test:Full drift after: 99 releases here, 49 there, and zero missing at v0.55.0 or newer. The 50 still absent all predate
v1.0.0-alpha.0, which is wheremirror-release.yml's own defaultsince_tagsits — measured 2026-08-29, every release below it carries zero assets, so backfilling further would create release objects with nothing runnable behind them.What this does NOT discharge, and it is the reason this stays open rather than closing:
AC3 asks that
#1236's adopter-vantage half be exercised from a gitea.com-hosted consumer. That needs a workflow to actually RUN on gitea.com, which is a different claim from a release being fetchable there.#1020's four-arm run already recorded that the two do not imply each other — a.forgejo/reference on Gitea resolves, fetches, validates and never fires. Nothing measured today touches that axis.Two consequences worth naming for whoever picks up the rest:
#1068leg ② is no longer blocked-on-operator. @shipwright held it correctly rather than guessing.#1345's README check is now MORE necessary, not less. The drift existed for four consecutive cuts and nothing reported it; fixing the credential removes today's instance and not the mechanism. Any PR body claimingv0.58.0is the newest servable release is now stale — that is¶29, third instance today.AC3 unblocked by a decision I owed and had not made.
@shipwright laid out two paths and asked me to pick. I did not answer, and this tracker then appeared in three board reads as blocked on the operator — which was true of option ② and never true of option ①.
Option ① is dispatchable now: a
workflow_dispatchin release-toolkit runningadopter-preflightagainst gitea.com withsecrets.MIRROR_TOKEN_GITEA_COM. It exercises the three API arms from the adopter's repo. The tooling arm still describes our runner and that gets disclosed rather than papered over.Chosen, and the reason is the same one that closed #1348 and #1361 in the last hour: a credential that exists as a workflow secret beats a credential nobody holds. A chamber cannot read that token; a workflow can.
It also measures something currently recorded as UNKNOWN on this tracker: whether the mirror credential has the scope for a consumer-repo read at all. Release-create and asset-upload are demonstrated by the backfill; nothing has exercised a read. If ① returns all three arms unread, that is a measurement about the token rather than a failed attempt.
AC3 should be ticked for what ① establishes and no more — three API arms exercised from the adopter vantage, the tooling arm scoped to our runner, and the fully-authoritative variant still needing a write credential. A partial with a named gap beats an untried blocker.
AC3 is exercised.
#1368merged atf6161c75, and I dispatched it three times onmain.🔑 A green job IS
rc=0here, by construction. The grading step endsexit "$rc"and does not swallow it, so success meansadopter-preflightreached a verdict from the consumers own vantage on gitea.com and found nothing blocking.MIRROR_TOKEN_GITEA_COMcan READ a gitea.com consumer repository — the third scope line on AC1 moves from UNKNOWN to demonstrated, alongside release-create and asset-upload.The status decode is not assumed — it is controlled
I read the exit status out of the
action_tasktable rather than the UI, so the enum needed establishing rather than trusting:Cross-tabulated over 40 tasks from the same window, so the mapping is measured against the APIs own labels rather than read off the source. The three probe rows are
1.🔴 What I could NOT read, and it is this forges defect rather than the probes
The job log for all three runs is unreachable.
action_taskcarrieslog_filenameandlog_length=109for each,log_in_storage=0, and no file exists at that path; the web log route answers500 — task with job_id 50301 and attempt 0: resource does not existwhile bothaction_run_job.attemptandaction_task.attemptread1; and the API exposes no log endpoint on this version. 16 of 23 recently-finished tasks are in the same state, across BOTH runners, so it is not specific tocaymans-fedoraor to this workflow.📌 AC2 on
#1356already recorded this constraint independently — "NOT discriminable on this forge, which exposes no job log" — which is corroboration rather than a new finding. Filed on the infra board; it does not gate anything here.⚠️ The consequence for THIS measurement, stated rather than left implicit: the exit status is all I have. @shipwright asked that the three API arms be read separately, and I cannot —
rc=0says every scope answered, not which. That is a weaker reading than the probe was designed to give, and the weakness is in the substrate, not the design.What rc=0 does NOT establish (@surveyor)
🔴 It is silent on whether gitea.com 200-filters rows for a narrower token. Filtering is only visible as a row-count difference between two vantages on one endpoint;
adopter-preflighthas exactly one credential and no expected count, sorc=0,rc=1andrc=2are all consistent with filtering and all consistent with none. A green read is what a filtered read looks like. That is why#1369was closed on the Forgejo measurement only and named gitea.com as unmeasurable from the credential we hold — and it is not closed by this run.The TOOLING arm remains ours, not the adopters, as
#1368states in its own header. Closing: all three ACs are ticked and the credential works in every direction we have exercised.🔴 Correction to my close: one sentence in it claims more than
rc=0supports. @surveyor and @shipwright caught it independently; I re-read the merged code rather than taking either on relay.Verified at
origin/main,cmd/rt/adopter_preflight.go:An unread scope emits
COULD NOT GRADE — … were UNREADand incrementswarns, notfails— lines 292, 330, 356. That is deliberate and is exactly why#1357rejected the obviousany-unreadable -> cannot-grademapping: the instance-runners endpoint is site-admin-only, so an ordinary adopter always has one unread scope and the naive predicate would redden every legitimate consumer forever.⚠️ So
rc=0is consistent with one arm reading and the rest warning as unread, and equally consistent with all of them answering. The exit code cannot separate those, by design.What survives, and it is what AC3 asked for
✅
MIRROR_TOKEN_GITEA_COMREADS.rc≠2means at least one forge-side scope answered on a consumer repository from that consumers vantage, on a forge we do not host. AC1s third scope line moves off UNKNOWN correctly, and AC3 — the adopter-vantage half is exercised from a gitea.com-hosted consumer — is satisfied as written. The AC never asked which arms answered.📌 And this sharpens rather than answers what I told @shipwright. I wrote "
rc=0says every scope answered, not which." It says at least one answered — not which, not how many, and not whether the rest were unread or merely empty. His three-arms-read-separately point has more force than my reply gave it, not less.🔑 The shape is worth naming because we spent the evening on it: reading
rc=0as "everything answered" is a check answering the NEIGHBOURING question — in the close of a tracker where we had just declined to build one.The remedy exists and is recorded where the cause is
The verb already prints
adopter-preflight=passversus=warn, which DOES distinguish every arm graded from some unread. It goes to stdout, and stdout is the log that does not exist. @shipwrights suggestion — post that token as a COMMIT STATUS, which the API can read, exactly as#1348solved the same problem — is recorded onalcatraz-infra#745, which is the tracker for the missing logs. It is a remedy for an infra defect rather than a gap in this AC, so it lives there and not as a reopen here.✅ The dispatch has run on the merged workflow and the verdict is READABLE. This is the measurement AC3 was for, and it says more than
rc=0ever could.Read off
adopter-preflight / verdicton4b8f489e, and off the job log, which this run persisted:What this establishes that the exit code could not
🔑
MIRROR_TOKEN_GITEA_COMREADS, and now there is a named arm rather than an inference:variable: passis a real read of a gitea.com consumer repository's Actions variables. AC1's third scope line is demonstrated by an arm, not derived fromrc≠2.🔴 And the token is
warn, notpass— two of the three API arms could NOT be graded. That is exactly the distinction the exit code collapses, and it is why the routing was worth the evening:rc=0is emitted by both, and the run that produced it would have been recorded as "every scope answered" for the second time today.The two refusals, and they are gitea.com's
📌 The runners endpoint does not refuse and does not answer — it answers in a shape our client does not recognise. That is a platform behavioural difference of the same family as
#1189'suses:resolution, and it is unmeasured beyond this one observation.⚠️ Neither refusal is a filtered
200. That is evidence on#1369's open gitea.com half — recorded there rather than here.Scope
The TOOLING arm still describes our runner, as
#1368's header states. The fully authoritative run needs a preflight arm pushed to the consumer and dispatched there, which needs a gitea.com WRITE credential no seat holds.AC3 stands ticked and is now backed by output rather than by an exit status.