-
0.6.1
StableAll checks were successfulCI / test (push) Successful in 3m18sCI / release-exercise (push) Has been skippedCI / self-guards (push) Successful in 10sCI / action-exercise (push) Successful in 9sCI / docs-sync-exercise (push) Successful in 9srelease / release (push) Successful in 14sreleased this
2026-08-09 19:46:56 +00:00 | 109 commits to main since this releaseAdded
-
test/labels-bootstrap.test.shpins the bridge at every hop: the declared
boundary, both gate sites as the identity, no expression reading
github.event.inputs, and the caller and stub pass-throughs
byte-exact (#215). -
The same test drives the four value paths — schedule-empty,
no,yes,
invalid — through the shipped expressions into the action's real
validator (#215). -
The taxonomy bootstrap keys on the
BOOTSTRAPinput, never the event name.
It testedGITHUB_EVENT_NAME = workflow_dispatch— correct while an
operator's manual dispatch was the only dispatch there was, inert-by-
construction from #209 on, when every machine wake became a dispatch
event (#215). -
The venue drill caught that: with the bridge delivering
noperfectly,
drill runs 16/17 still bootstrapped, because the script never read the
input the whole chain existed to deliver (#215). -
test/labels-reconcile.test.shpins the regression pair exactly: a
workflow_dispatchevent withBOOTSTRAP=no(or unset) creates and
deletes nothing; onlyBOOTSTRAP=yesbootstraps (#215). -
A gather-level case drives the real board read against a Forgejo-shaped
fixture — every entry carrying the key. The existing discriminator cases
assertjqexpressions in isolation and passed throughout this regression
(#210). -
A source pin forbids
has("pull_request")on this surface, because the rule
was stated in a comment and violated forty lines below it. It strips comments,
so the #188 warning that explains the trap is allowed to stay (#210). -
All three sites are covered behaviourally, not only by the pin: the board
gather, the release-body gather through an observable window flag, and the
per-issue payload check (#210). -
test/forge-backends.test.shpins each backend's path and field, because
a stubbedforge_apicannot catch a wrong path — which is how this shipped
and why a live sweep was what found it (#209). -
test/labels-dispatch.test.shextracts the shipped step and executes it
against a recording stub, asserting the method, endpoint, ref and
inputs.bootstrapactually sent (#205). -
That test also drives the failure path: any non-204 still fails the job, so
the misconfiguration alarm the trigger exists to be cannot decay into a
warning (#205). -
An unset
GITHUB_API_URLrefuses before any request instead of defaulting
toapi.github.com. Guessing sent this forge's dispatch to GitHub and
reported success — the "Never 'probably github'" rule, and the same
unset-environment refusal #201 established for docs-sync (#205). -
A dispatch that never reaches the forge names the failure. Letting
set -e
carry curl's exit code out did fail the job, but with a bare status and no
sentence (#205). -
docs/CONSUMERS.mdand both caller comments describe the REST dispatch, and
the manual bootstrap command carries a forge-neutral form beside thegh
one — a cross-forge runbook that directs this forge to a missing binary is
wrong even where the surrounding prose is right (#205). -
docs/RUNNER-PROBES.mddocuments the standing runner-probe venue,
heavy-duty/ceremony-runner-probe— the place runner-only facts are measured
on demand, ruled as option A by the operator (#202). -
drills/README.mdcross-links it beside the disposal rule, so the exception
is visible where the dangerous habit lives (#202). -
The runbook states that the drill disposal rule does not apply to it.
Archiving it defeats its purpose, and that is exactly how the three existing
drill repos each became unavailable (#202). -
It records that a probe must run as an Actions job under the workflow token:
the same call answers 500 there and 204 under a PAT, so a probe run any other
way produces a confident wrong answer (#202). -
Creating the repo is recorded as the operator's step, measured rather than
assumed: a fleet identity gets 403 on org repo creation and 201 in its own
namespace (#202). -
It carries an executable two-layer arming procedure: an immutable candidate
code SHA and an armed workflow commit on top of it. A single layer is
self-referential — rewriting a workflow makes a new commit, and a commit
cannot contain its own object ID (#202). -
Callers are pinned by layer: composite actions to the candidate code SHA,
reusable workflows to the armed SHA, which is the only revision whose inner
checkout points at the fork (#202). -
The arming gate asserts what each carrier IS, not only that the old literal
is gone: everyrepository:equals the fork, everyCEREMONY_SELF_REFvalue
equal the candidate code SHA, and callers match the layer they belong to
(#202). -
It enumerates the carriers from the tree rather than encoding a count, and
distinguishes ceremony's internal self-checkouts from the consumer checkouts
that must stay${{ github.repository }}(#202). -
Both published snippets are ShellCheck-clean when extracted and linted
directly, not merely as part of the repository sweep (#202). -
The checker validates the MANIFEST against the target it was given, so a
manifest that describes a wrong arming consistently — wrong fork, or the
armed SHA where the candidate belongs — refuses instead of matching a tree
rewritten to the same wrong value (#202). -
The manifest is generated from the PRE-arming tree, which is the only order
that enumerates the carriers that must change (#202). -
Both published snippets were driven against a constructed candidate/probe
pair: deletion, both role swaps, wrong owner, wrong
SHA, wrong path, a deleted caller class and an extra carrier all refuse, and
the armed control passes (#202). -
The manifest records complete caller coordinates, so a path swapped under the
right owner and SHA is caught (#202). -
Generator and checker share one domain — ceremony callers — so a third-party
actions/checkoutis neither manifested nor reported as unrecognised (#202). -
Probe results are written to an issue in the probe repo and carried to the
ceremony issue by a human, so the probe holds no path that can write to the
live board (#202). -
test/docs-sync.test.shdrives the fetch path, which had no coverage at all:
every existing row passes--source, which overrides the fetch entirely
(#201). -
A stubbed
curlrecords the requested URL and serves a tarball, so which
forge a pin resolves against is a tested decision rather than plumbing (#201). -
docs/UPSTREAM-SYNC.md— the recurring upstream sync as a runbook: the
standing resolutions, which side wins each and the issue that decided it
(#200). -
It names the step the 0.6.0 sync nearly shipped without: auditing what the
merge brought in that did not conflict.git mergeasks no question
about a function upstream added to a file this tree owns (#200). -
It records that the same mechanic applies to state, not just to call sites: a
resolved region can silently remove a producer whose consumers auto-merged,
and every one of those consumers degrades to empty rather than erroring
(#200). -
It says to verify with the runner's tooling, because "green locally" was
wrong three times in one sync — untracked files, a pinned linter, and a
pinnedjqwhose empty-input exit code differs (#200). -
It says every branch open across a sync is stale afterwards — Forgejo never
re-tests an open PR when main moves, so a prior approval is evidence about a
tree that no longer exists (#200). -
It says to audit post-merge runs by executed steps rather than colour, and to
inventory what the sync changed about workflow triggers and jobs first (#200). -
.upstream-refrecords the upstream commit this tree carries, in
machine-readable form beside the CHANGELOG's prose (#200). -
test/upstream-delta.test.shfails the PR that scatters a forge decision
into a file the inventory does not name. Discovery is derived from the tree,
so a compositeaction.ymlor a.yamlworkflow is seen without anyone
remembering to add a glob (#200). -
Discovery is git's, not the filesystem's:
ls-files, so the tarballsci.yml
extracts into the checkout and any developer cache are not parsed as source
(#200). -
It refuses when the recorded commit is missing, absent from the object store,
or not an ancestor — three distinct refusals, none of them a skip.ci.yml
fetches that exact object so the test reads local evidence without CI
omitting it (#200). -
Its mutation cases drive the real check against a constructed tree, so
replacing the guard withreturn 0reds five of them (#200). -
docs/CONSUMERS.mdstates that two ceremonies answer to the same version
number, and how a consumer says which one it pinned (#200). -
lib/issue_references.sh— the LOCAL/CROSS classifier, moved out of
actions/issueflow-reconcile's executable so a second caller can use it
without sourcing a reconciler, which would run one (#199, #61). -
test/refs-not-closing.test.shdrives the action's boundary on both
backends with stubs at the transport, proving one fixture yields the same
verdict on each — including a closing keyword that appears only in a commit
message (#199). -
This tree carries upstream ceremony through
8c3a4d1(upstream0.6.0):
lib/attention.sh,lib/read.sh,actions/refs-not-closing, the guarded
reads, and the ruling and window rules (#198). -
test/no-runtime-gh.test.sh— the forge-portability guard: no runtimegh
outsidelib/forge-github.shunless the file declares
CEREMONY_FORGE_CLIENT=gh(#198). -
CHANGELOG.mdnames the upstream commit this tree carries, so a drill
record can say which0.6.0it exercised (#197, #198). -
test/labels.test.shholds the conf's roster andCONTRIBUTING.md's roster
table to the same set, in both directions, so a roster edit that touches one
file and not the other goes red instead of drifting quietly (#195). -
test/forge-backends.test.shpins the replacement contract: preserve
unrelated labels across a combined add+remove, an absent removal as a
successful no-op, the empty set as a full clear, andforge_labels_add
stillPOST-only, per ceremony#128 (#192). -
test/labels-reconcile.test.shdrives a failing write throughmain()— the
swallow was in the loop, where a fixture-level probe cannot reach (#192).
Changed
-
The review panel restores
kimi-reviewer-andresmgslalongside GLM. The
four-identity panel now requires three cross-vendor approvals after the PR
author recuses (#224). -
The review panel names
glm-reviewer-andresmgslin place of
kimi-reviewer-andresmgslwhile that identity is unavailable. The panel
stays three, so a PR still requires two cross-vendor approvals (#222). -
test/labels.test.sh's table-side roster mutation names an identity the
table actually carries. Naming a departed one would mutate nothing and the
case would pass while proving nothing — #195's rot class one layer down
(#222). -
This forge's release line runs
0.4.1 → 0.6.1: versions 0.5.0 and 0.6.0
arrived here by merge from the read-only upstream and were never released
on this forge (#220). -
The
## 0.6.0section this changelog carries is upstream's — its entries
describe upstream's work under upstream's issue numbers. The forge port's
own work ships first in 0.6.1 (#220). -
docs/RUNNER-PROBES.mdrecords the venue's first delivered drills — the
#192 asymmetry re-observed on demand under the workflow token, the dispatch
route's 204 under both identities, and #215's boundary finding — each with
the probe-issue URL it is recorded in (#202). -
Two venue lessons join the runbook where the next probe author will look:
findings must be written to issues because the venue's log route 404s for
non-admin reads, and report content sent to the forge must never contain a
credential expression or value (#202).
Fixed
-
The sweep's
bootstrapvalue crosses theworkflow_callboundary as a
declared input, explicitly passed by the caller — the one channel measured
to work; the called workflow did not see the caller's event inputs as an
implicit substitute on this instance (#215). -
Before the bridge,
github.event.inputswas empty inside the called
workflow, so every dispatch-woken sweep bootstrapped: ~20 label upserts on
each board event (#215). -
The caller maps an empty top-level value to
noexplicitly, so a
cron-woken sweep can never bootstrap; the declared input also defaults to
no, so a consumer that passes nothing gets the safe path (#215). -
The gate feeds the declared input to
labels-reconcileunchanged, so an
invalid value meets the action's ownyes|norefusal instead of being
silently coerced (#215). -
docs/CONSUMERS.md's published sweep stub carries the same pass-through —
without it every consumer inherits the defect ceremony fixed for
itself (#215). -
issueflow-reconcilesees this forge's issues again. The board gather used
has("pull_request"), and every Forgejo entry carries that key — so it
selected zero rows on every sweep while printingreconciled.(#210). -
Three sites take
.pull_request == null, the discriminator the file's own
comment already specified and that one of its four call sites already used
(#210). -
post-mergetransitions can fire again: they could not, because the sweep
saw no issues to transition (#210). -
blocker:unrequestedis judged on this forge again. The head-commit date was
read fromrepos/{o}/{r}/commits/{sha}, which Forgejo answers 404 — so
every sweep degraded and left the blocker unjudged (#209). -
forge_commit_atis a verb on both backends: GitHub serves a single commit at
the bare path with the date nested, Forgejo atgit/commits/{sha}with it
under.created. The caller asks for one timestamp and knows neither shape
(#209). -
.github/workflows/labels.ymlwakes the sweep over REST instead of
gh workflow run, so a board event reconciles within seconds on any forge
rather than waiting up to an hour for the scheduled sweep (#205). -
The workflow-dispatch endpoint has the same shape on both forges, so that
step no longer decides one: theCEREMONY_FORGE_CLIENT=ghdeclaration and
both inline refusals are gone rather than ported (#205). -
The dispatch supplies its
refexplicitly, because REST has no default
branch wheregh workflow runhad one, and refuses without it (#205). -
It takes that ref from the repository, never from
GITHUB_REF_NAME— on a
pull_request_targetrun that is<n>/merge, which is not a branch (#205). -
A failed dispatch names the endpoint, the ref and the status, and says that
an empty500body from Forgejo means the workflow name or the ref did not
resolve — a bare status sends the reader after a server fault that is not
there (#205). -
actions/docs-syncfetches the doctrine mirror from the forge named by
GITHUB_SERVER_URLinstead of a hard-codedgithub.com(#201). -
The same pin ref names a different tree on each forge, so a consumer's mirror
was verified against a tree it never pinned — and with HTTP 200, so--check
reported drift that could not be fixed (#201). -
A fetch that cannot name its forge now refuses instead of guessing: no
GITHUB_SERVER_URLand no--sourceexits naming the variable, having
reached for no network (#201). -
A failed fetch names the URL it actually tried, and asks whether the ref
exists on that forge rather than in the abstract (#201). -
actions/refs-not-closinggathers over REST through the forge shim instead
of one GraphQL query, so it produces a real verdict on Forgejo — which
serves no GraphQL surface at all — rather than refusing (#199). -
The closing set is parsed by
lib/closes_references.shover the PR body
and every commit message, unioned. Forgejo honours closing keywords in
commit messages, so a body-only port would miss a PR that closes an issue
from a commit subject (#199). -
The
hasNextPagerefusal is carried onto the paginated commit read: an
incomplete gather refuses instead of returning a partial verdict, reusing
the backend'sx-total-countcompleteness proof (#199). -
A failed read never reaches the parser. An unread body parses to an empty
closing set, which is a passing verdict the action never earned (#199). -
.github/workflows/refs-guard.ymlno longer gates the job on the forge.
A portable action behind a github-only gate is a guard that passes by never
running (#199, #198). -
The
CEREMONY_FORGE_CLIENT=ghdeclaration is gone, and the contract test
asserts its absence: an opt-out with noghbehind it is a standing
permission slip (#199). -
Eight runtime
ghcall sites arrived with the merge outside every conflict
hunk, in functions upstream added to files this tree already owned. Seven
are ported onto the shim; the eighth is named with its reason (#198). -
The open-PR gather reads
Refs, not only closing keywords. Reading one side
for closing links and the other forRefsis what released a live claim in
crew#321, and this tree carried that shape (#198). -
The merged record gains
merged_at, sopost_merge_pr_for_issueanswers
the PR that merged last rather than the highest-numbered one. Without the
column every sort key ties and the old order returns silently (#198). -
The open gather feeds
open_pr_issuesone record per physical body line. A
whole decoded body as one record loses every declaration including the
first, and reclaims a claim a live PR was holding (#198). -
The post-merge nudge links the issue on the forge in play rather than a
hard-codedgithub.com(#198). -
actions/refs-not-closingreports and skips on a forge it cannot speak,
naming the client and #199, instead of standing red on every PR. It reaches
the forge zero times, so no verdict is produced either way (#198). -
.github/workflows/labels.yml's sweep dispatch declares the client it
speaks and decides the FORGE before the binary, so a Forgejo runner that
happens to shipghcannot dispatch against a forge that cannot serve it.
#205 ports it to REST (#198). -
actions/refs-not-closingfails closed on a forge it cannot speak, and
.github/workflows/refs-guard.ymlcarries the scheduling decision — the
action never reports a success it did not earn (#198). -
issue_payload_validrefuses an empty payload on jq 1.6 as well as 1.7.
jq -eexits 4 on empty input under 1.7 and 0 under 1.6, and this
instance's runner carries 1.6 — so the guard #247 D3 added to refuse an
unreadable read was accepting one here (#198). -
The post-merge nudge strips a trailing slash from the server URL, so a forge
URL carrying one does not render//owner/repo(#198). -
.github/scripts/release-path.shnameslib/forge.sh: #191 put the shim on
the release doors' executable path here, so a doors-unchanged record that
omitted it was measuring the wrong set (#198). -
.github/labels.confnames identities that exist on the forge this repo
lives on. All five it named before were absent, sopanel=could never
converge a review round andtriage-actors=made every issue a stray mint
nobody could normalize (#195). -
CONTRIBUTING.md's roster table matches the conf: three identities, the
human row isandres, and the approval count states what panel-minus-author
actually resolves to on this roster rather than a stale three (#195). -
Label removal on Forgejo is a full-set
PUT, not a per-labelDELETE. The
workflow token gets HTTP 500 on everyDELETE .../labels/{id}on this
instance, so the state machine could only ever ADD labels (#192). -
Every
state:*transition that needs the previous state cleared, and every
blocker:*that should lift, can now actually clear. They were inert (#192). -
A label edit that fails is fatal to
labels-reconcile, matching
issueflow-reconcile. One cause had two contradictory policies (#192). -
A failed write reaches the sweep's exit code: per-PR tolerance is kept for
READS, but a sweep that could not write exits non-zero and its output carries
noreconciled.token at all (#192). -
Every label mutation goes through one checked helper, so clearing
merge-nextor eitherstaleedit fails the sweep too — not only the
primary state edit (#192). -
A preserved label keeps the id the issue payload already carried, so
preservation does not depend on a repository-wide list that has nothing to do
with the issue (#192). -
A removal that changes nothing writes nothing, rather than replacing the set
with itself and opening a race for no state change (#192). -
Every failure diagnostic on the forgejo backend names the verb as well as the
path and the status. A read used to sayHTTP 500 from 'repos/…', which
cannot be told from a failed write of the same path (#192). -
The diagnostic names what was attempted and that it did not happen, instead
of blaming a missing label and telling the operator to bootstrap — a cause it
had not established (#192, #101). -
An add-label the repo does not carry refuses before any write, so a
replacementPUTcan never drop a label nobody asked to remove (#192).
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
440 downloads
-