release 0.6.1 #226
20 changed files with 536 additions and 467 deletions
2
.github/workflows/labels-sweep.yml
vendored
2
.github/workflows/labels-sweep.yml
vendored
|
|
@ -63,7 +63,7 @@ on:
|
||||||
env:
|
env:
|
||||||
# A called workflow arrives without its repository. Keep this literal pin
|
# A called workflow arrives without its repository. Keep this literal pin
|
||||||
# aligned with the ceremony release consumed by callers (issue #9 D3).
|
# aligned with the ceremony release consumed by callers (issue #9 D3).
|
||||||
CEREMONY_SELF_REF: "0.6.0"
|
CEREMONY_SELF_REF: "0.6.1"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
reconcile:
|
reconcile:
|
||||||
|
|
|
||||||
2
.github/workflows/labels.yml
vendored
2
.github/workflows/labels.yml
vendored
|
|
@ -48,7 +48,7 @@ on:
|
||||||
env:
|
env:
|
||||||
# A called workflow arrives without its repository. Keep this literal pin
|
# A called workflow arrives without its repository. Keep this literal pin
|
||||||
# aligned with the ceremony release consumed by callers (issue #9 D3).
|
# aligned with the ceremony release consumed by callers (issue #9 D3).
|
||||||
CEREMONY_SELF_REF: "0.6.0"
|
CEREMONY_SELF_REF: "0.6.1"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
scope:
|
scope:
|
||||||
|
|
|
||||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
|
|
@ -129,7 +129,7 @@ env:
|
||||||
# `ref:` accepts ${{ env }}; `uses:` strings do not — which is why the
|
# `ref:` accepts ${{ env }}; `uses:` strings do not — which is why the
|
||||||
# shared logic arrives as script files via checkout, not as inner `uses:`
|
# shared logic arrives as script files via checkout, not as inner `uses:`
|
||||||
# references.
|
# references.
|
||||||
CEREMONY_SELF_REF: "0.6.0"
|
CEREMONY_SELF_REF: "0.6.1"
|
||||||
VERSION_SOURCE: ${{ inputs.version-source }}
|
VERSION_SOURCE: ${{ inputs.version-source }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
|
||||||
416
CHANGELOG.md
416
CHANGELOG.md
|
|
@ -22,6 +22,422 @@ section is deliberately not carried — the tag published here is the one this
|
||||||
section is the body of. Each sync updates this line
|
section is the body of. Each sync updates this line
|
||||||
(docs/UPSTREAM-SYNC.md, #200).
|
(docs/UPSTREAM-SYNC.md, #200).
|
||||||
|
|
||||||
|
## 0.6.1 — 2026-08-09
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- `test/labels-bootstrap.test.sh` pins 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 `BOOTSTRAP` input, never the event name.
|
||||||
|
It tested `GITHUB_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 `no` perfectly,
|
||||||
|
drill runs 16/17 still bootstrapped, because the script never read the
|
||||||
|
input the whole chain existed to deliver (#215).
|
||||||
|
|
||||||
|
- `test/labels-reconcile.test.sh` pins the regression pair exactly: a
|
||||||
|
`workflow_dispatch` event with `BOOTSTRAP=no` (or unset) creates and
|
||||||
|
deletes nothing; only `BOOTSTRAP=yes` bootstraps (#215).
|
||||||
|
- A gather-level case drives the real board read against a Forgejo-shaped
|
||||||
|
fixture — every entry carrying the key. The existing discriminator cases
|
||||||
|
assert `jq` expressions 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.sh` pins each backend's path **and** field, because
|
||||||
|
a stubbed `forge_api` cannot catch a wrong path — which is how this shipped
|
||||||
|
and why a live sweep was what found it (#209).
|
||||||
|
- `test/labels-dispatch.test.sh` extracts the shipped step and executes it
|
||||||
|
against a recording stub, asserting the method, endpoint, ref and
|
||||||
|
`inputs.bootstrap` actually 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_URL` refuses before any request instead of defaulting
|
||||||
|
to `api.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.md` and both caller comments describe the REST dispatch, and
|
||||||
|
the manual bootstrap command carries a forge-neutral form beside the `gh`
|
||||||
|
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.md` documents 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.md` cross-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: every `repository:` equals the fork, every `CEREMONY_SELF_REF` value
|
||||||
|
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/checkout` is 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.sh` drives the fetch path, which had no coverage at all:
|
||||||
|
every existing row passes `--source`, which overrides the fetch entirely
|
||||||
|
(#201).
|
||||||
|
|
||||||
|
- A stubbed `curl` records 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 merge` asks 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
|
||||||
|
pinned `jq` whose 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-ref` records the upstream commit this tree carries, in
|
||||||
|
machine-readable form beside the CHANGELOG's prose (#200).
|
||||||
|
|
||||||
|
- `test/upstream-delta.test.sh` fails the PR that scatters a forge decision
|
||||||
|
into a file the inventory does not name. Discovery is derived from the tree,
|
||||||
|
so a composite `action.yml` or a `.yaml` workflow is seen without anyone
|
||||||
|
remembering to add a glob (#200).
|
||||||
|
|
||||||
|
- Discovery is git's, not the filesystem's: `ls-files`, so the tarballs `ci.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 with `return 0` reds five of them (#200).
|
||||||
|
|
||||||
|
- `docs/CONSUMERS.md` states 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.sh` drives 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` (upstream `0.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 runtime `gh`
|
||||||
|
outside `lib/forge-github.sh` unless the file declares
|
||||||
|
`CEREMONY_FORGE_CLIENT=gh` (#198).
|
||||||
|
|
||||||
|
- `CHANGELOG.md` names the upstream commit this tree carries, so a drill
|
||||||
|
record can say which `0.6.0` it exercised (#197, #198).
|
||||||
|
- `test/labels.test.sh` holds the conf's roster and `CONTRIBUTING.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.sh` pins 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, and `forge_labels_add`
|
||||||
|
still `POST`-only, per ceremony#128 (#192).
|
||||||
|
|
||||||
|
- `test/labels-reconcile.test.sh` drives a failing write through `main()` — the
|
||||||
|
swallow was in the loop, where a fixture-level probe cannot reach (#192).
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- The review panel restores `kimi-reviewer-andresmgsl` alongside GLM. The
|
||||||
|
four-identity panel now requires three cross-vendor approvals after the PR
|
||||||
|
author recuses (#224).
|
||||||
|
- The review panel names `glm-reviewer-andresmgsl` in place of
|
||||||
|
`kimi-reviewer-andresmgsl` while 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.0` section 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.md` records 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 `bootstrap` value crosses the `workflow_call` boundary 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.inputs` was 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 `no` explicitly, 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-reconcile` unchanged, so an
|
||||||
|
invalid value meets the action's own `yes|no` refusal 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-reconcile` sees 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 printing `reconciled.` (#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-merge` transitions can fire again: they could not, because the sweep
|
||||||
|
saw no issues to transition (#210).
|
||||||
|
- `blocker:unrequested` is judged on this forge again. The head-commit date was
|
||||||
|
read from `repos/{o}/{r}/commits/{sha}`, which Forgejo answers **404** — so
|
||||||
|
every sweep degraded and left the blocker unjudged (#209).
|
||||||
|
|
||||||
|
- `forge_commit_at` is a verb on both backends: GitHub serves a single commit at
|
||||||
|
the bare path with the date nested, Forgejo at `git/commits/{sha}` with it
|
||||||
|
under `.created`. The caller asks for one timestamp and knows neither shape
|
||||||
|
(#209).
|
||||||
|
- `.github/workflows/labels.yml` wakes 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: the `CEREMONY_FORGE_CLIENT=gh` declaration and
|
||||||
|
both inline refusals are gone rather than ported (#205).
|
||||||
|
|
||||||
|
- The dispatch supplies its `ref` explicitly, because REST has no default
|
||||||
|
branch where `gh workflow run` had one, and refuses without it (#205).
|
||||||
|
|
||||||
|
- It takes that ref from the repository, never from `GITHUB_REF_NAME` — on a
|
||||||
|
`pull_request_target` run 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 empty `500` body 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-sync` fetches the doctrine mirror from the forge named by
|
||||||
|
`GITHUB_SERVER_URL` instead of a hard-coded `github.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_URL` and no `--source` exits 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-closing` gathers 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.sh` over 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 `hasNextPage` refusal is carried onto the paginated commit read: an
|
||||||
|
incomplete gather refuses instead of returning a partial verdict, reusing
|
||||||
|
the backend's `x-total-count` completeness 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.yml` no 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=gh` declaration is gone, and the contract test
|
||||||
|
asserts its absence: an opt-out with no `gh` behind it is a standing
|
||||||
|
permission slip (#199).
|
||||||
|
- Eight runtime `gh` call 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 for `Refs` is what released a live claim in
|
||||||
|
crew#321, and this tree carried that shape (#198).
|
||||||
|
|
||||||
|
- The merged record gains `merged_at`, so `post_merge_pr_for_issue` answers
|
||||||
|
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_issues` one 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-coded `github.com` (#198).
|
||||||
|
|
||||||
|
- `actions/refs-not-closing` reports 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 ship `gh` cannot dispatch against a forge that cannot serve it.
|
||||||
|
#205 ports it to REST (#198).
|
||||||
|
|
||||||
|
- `actions/refs-not-closing` fails closed on a forge it cannot speak, and
|
||||||
|
`.github/workflows/refs-guard.yml` carries the scheduling decision — the
|
||||||
|
action never reports a success it did not earn (#198).
|
||||||
|
|
||||||
|
- `issue_payload_valid` refuses an empty payload on jq 1.6 as well as 1.7.
|
||||||
|
`jq -e` exits 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.sh` names `lib/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.conf` names identities that exist on the forge this repo
|
||||||
|
lives on. All five it named before were absent, so `panel=` could never
|
||||||
|
converge a review round and `triage-actors=` made every issue a stray mint
|
||||||
|
nobody could normalize (#195).
|
||||||
|
|
||||||
|
- `CONTRIBUTING.md`'s roster table matches the conf: three identities, the
|
||||||
|
human row is `andres`, 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-label `DELETE`. The
|
||||||
|
workflow token gets HTTP 500 on every `DELETE .../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
|
||||||
|
no `reconciled.` token at all (#192).
|
||||||
|
|
||||||
|
- Every label mutation goes through one checked helper, so clearing
|
||||||
|
`merge-next` or either `stale` edit 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 say `HTTP 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
|
||||||
|
replacement `PUT` can never drop a label nobody asked to remove (#192).
|
||||||
|
|
||||||
## 0.6.0 — 2026-08-05
|
## 0.6.0 — 2026-08-05
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
|
||||||
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
||||||
0.6.1-dev
|
0.6.1
|
||||||
|
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Label removal on Forgejo is a full-set `PUT`, not a per-label `DELETE`. The
|
|
||||||
workflow token gets HTTP 500 on every `DELETE .../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
|
|
||||||
no `reconciled.` token at all (#192).
|
|
||||||
|
|
||||||
- Every label mutation goes through one checked helper, so clearing
|
|
||||||
`merge-next` or either `stale` edit 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 say `HTTP 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
|
|
||||||
replacement `PUT` can never drop a label nobody asked to remove (#192).
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- `test/forge-backends.test.sh` pins 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, and `forge_labels_add`
|
|
||||||
still `POST`-only, per ceremony#128 (#192).
|
|
||||||
|
|
||||||
- `test/labels-reconcile.test.sh` drives a failing write through `main()` — the
|
|
||||||
swallow was in the loop, where a fixture-level probe cannot reach (#192).
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- `.github/labels.conf` names identities that exist on the forge this repo
|
|
||||||
lives on. All five it named before were absent, so `panel=` could never
|
|
||||||
converge a review round and `triage-actors=` made every issue a stray mint
|
|
||||||
nobody could normalize (#195).
|
|
||||||
|
|
||||||
- `CONTRIBUTING.md`'s roster table matches the conf: three identities, the
|
|
||||||
human row is `andres`, and the approval count states what panel-minus-author
|
|
||||||
actually resolves to on this roster rather than a stale three (#195).
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- `test/labels.test.sh` holds the conf's roster and `CONTRIBUTING.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).
|
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
### Added
|
|
||||||
|
|
||||||
- This tree carries upstream ceremony through `8c3a4d1` (upstream `0.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 runtime `gh`
|
|
||||||
outside `lib/forge-github.sh` unless the file declares
|
|
||||||
`CEREMONY_FORGE_CLIENT=gh` (#198).
|
|
||||||
|
|
||||||
- `CHANGELOG.md` names the upstream commit this tree carries, so a drill
|
|
||||||
record can say which `0.6.0` it exercised (#197, #198).
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Eight runtime `gh` call 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 for `Refs` is what released a live claim in
|
|
||||||
crew#321, and this tree carried that shape (#198).
|
|
||||||
|
|
||||||
- The merged record gains `merged_at`, so `post_merge_pr_for_issue` answers
|
|
||||||
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_issues` one 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-coded `github.com` (#198).
|
|
||||||
|
|
||||||
- `actions/refs-not-closing` reports 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 ship `gh` cannot dispatch against a forge that cannot serve it.
|
|
||||||
#205 ports it to REST (#198).
|
|
||||||
|
|
||||||
- `actions/refs-not-closing` fails closed on a forge it cannot speak, and
|
|
||||||
`.github/workflows/refs-guard.yml` carries the scheduling decision — the
|
|
||||||
action never reports a success it did not earn (#198).
|
|
||||||
|
|
||||||
- `issue_payload_valid` refuses an empty payload on jq 1.6 as well as 1.7.
|
|
||||||
`jq -e` exits 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.sh` names `lib/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).
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- `actions/refs-not-closing` gathers 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.sh` over 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 `hasNextPage` refusal is carried onto the paginated commit read: an
|
|
||||||
incomplete gather refuses instead of returning a partial verdict, reusing
|
|
||||||
the backend's `x-total-count` completeness 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.yml` no 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=gh` declaration is gone, and the contract test
|
|
||||||
asserts its absence: an opt-out with no `gh` behind it is a standing
|
|
||||||
permission slip (#199).
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- `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.sh` drives 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).
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
### Added
|
|
||||||
|
|
||||||
- `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 merge` asks 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
|
|
||||||
pinned `jq` whose 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-ref` records the upstream commit this tree carries, in
|
|
||||||
machine-readable form beside the CHANGELOG's prose (#200).
|
|
||||||
|
|
||||||
- `test/upstream-delta.test.sh` fails the PR that scatters a forge decision
|
|
||||||
into a file the inventory does not name. Discovery is derived from the tree,
|
|
||||||
so a composite `action.yml` or a `.yaml` workflow is seen without anyone
|
|
||||||
remembering to add a glob (#200).
|
|
||||||
|
|
||||||
- Discovery is git's, not the filesystem's: `ls-files`, so the tarballs `ci.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 with `return 0` reds five of them (#200).
|
|
||||||
|
|
||||||
- `docs/CONSUMERS.md` states that two ceremonies answer to the same version
|
|
||||||
number, and how a consumer says which one it pinned (#200).
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- `actions/docs-sync` fetches the doctrine mirror from the forge named by
|
|
||||||
`GITHUB_SERVER_URL` instead of a hard-coded `github.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_URL` and no `--source` exits 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).
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- `test/docs-sync.test.sh` drives the fetch path, which had no coverage at all:
|
|
||||||
every existing row passes `--source`, which overrides the fetch entirely
|
|
||||||
(#201).
|
|
||||||
|
|
||||||
- A stubbed `curl` records the requested URL and serves a tarball, so which
|
|
||||||
forge a pin resolves against is a tested decision rather than plumbing (#201).
|
|
||||||
|
|
@ -1,76 +0,0 @@
|
||||||
### Added
|
|
||||||
|
|
||||||
- `docs/RUNNER-PROBES.md` documents 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.md` cross-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: every `repository:` equals the fork, every `CEREMONY_SELF_REF` value
|
|
||||||
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/checkout` is 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).
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- `docs/RUNNER-PROBES.md` records 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).
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- `.github/workflows/labels.yml` wakes 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: the `CEREMONY_FORGE_CLIENT=gh` declaration and
|
|
||||||
both inline refusals are gone rather than ported (#205).
|
|
||||||
|
|
||||||
- The dispatch supplies its `ref` explicitly, because REST has no default
|
|
||||||
branch where `gh workflow run` had one, and refuses without it (#205).
|
|
||||||
|
|
||||||
- It takes that ref from the repository, never from `GITHUB_REF_NAME` — on a
|
|
||||||
`pull_request_target` run 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 empty `500` body 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).
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- `test/labels-dispatch.test.sh` extracts the shipped step and executes it
|
|
||||||
against a recording stub, asserting the method, endpoint, ref and
|
|
||||||
`inputs.bootstrap` actually 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_URL` refuses before any request instead of defaulting
|
|
||||||
to `api.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.md` and both caller comments describe the REST dispatch, and
|
|
||||||
the manual bootstrap command carries a forge-neutral form beside the `gh`
|
|
||||||
one — a cross-forge runbook that directs this forge to a missing binary is
|
|
||||||
wrong even where the surrounding prose is right (#205).
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- `blocker:unrequested` is judged on this forge again. The head-commit date was
|
|
||||||
read from `repos/{o}/{r}/commits/{sha}`, which Forgejo answers **404** — so
|
|
||||||
every sweep degraded and left the blocker unjudged (#209).
|
|
||||||
|
|
||||||
- `forge_commit_at` is a verb on both backends: GitHub serves a single commit at
|
|
||||||
the bare path with the date nested, Forgejo at `git/commits/{sha}` with it
|
|
||||||
under `.created`. The caller asks for one timestamp and knows neither shape
|
|
||||||
(#209).
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- `test/forge-backends.test.sh` pins each backend's path **and** field, because
|
|
||||||
a stubbed `forge_api` cannot catch a wrong path — which is how this shipped
|
|
||||||
and why a live sweep was what found it (#209).
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- `issueflow-reconcile` sees 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 printing `reconciled.` (#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-merge` transitions can fire again: they could not, because the sweep
|
|
||||||
saw no issues to transition (#210).
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- A gather-level case drives the real board read against a Forgejo-shaped
|
|
||||||
fixture — every entry carrying the key. The existing discriminator cases
|
|
||||||
assert `jq` expressions 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).
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- The sweep's `bootstrap` value crosses the `workflow_call` boundary 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.inputs` was 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 `no` explicitly, 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-reconcile` unchanged, so an
|
|
||||||
invalid value meets the action's own `yes|no` refusal 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).
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- `test/labels-bootstrap.test.sh` pins 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 `BOOTSTRAP` input, never the event name.
|
|
||||||
It tested `GITHUB_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 `no` perfectly,
|
|
||||||
drill runs 16/17 still bootstrapped, because the script never read the
|
|
||||||
input the whole chain existed to deliver (#215).
|
|
||||||
|
|
||||||
- `test/labels-reconcile.test.sh` pins the regression pair exactly: a
|
|
||||||
`workflow_dispatch` event with `BOOTSTRAP=no` (or unset) creates and
|
|
||||||
deletes nothing; only `BOOTSTRAP=yes` bootstraps (#215).
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
### Changed
|
|
||||||
|
|
||||||
- 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.0` section 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).
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
### Changed
|
|
||||||
|
|
||||||
- The review panel names `glm-reviewer-andresmgsl` in place of
|
|
||||||
`kimi-reviewer-andresmgsl` while 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).
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
### Changed
|
|
||||||
|
|
||||||
- The review panel restores `kimi-reviewer-andresmgsl` alongside GLM. The
|
|
||||||
four-identity panel now requires three cross-vendor approvals after the PR
|
|
||||||
author recuses (#224).
|
|
||||||
116
drills/0.6.1.md
Normal file
116
drills/0.6.1.md
Normal file
|
|
@ -0,0 +1,116 @@
|
||||||
|
# 0.6.1 — drill record
|
||||||
|
|
||||||
|
Run 2026-08-09 by `codex-reviewer-andresmgsl` against release PR !226,
|
||||||
|
candidate branch `release-0.6.1`, canonical candidate SHA
|
||||||
|
`ba3b17af88e0fe1ccae9eefd4b37bf2666f6cdad`. All six probes ran on this
|
||||||
|
Forgejo instance. Each result below was read from its own run and from the
|
||||||
|
repository state after that run.
|
||||||
|
|
||||||
|
## Scope ruling — a full rehearsal was owed
|
||||||
|
|
||||||
|
The last rehearsed tag was `0.6.0`. The release path at this candidate is the
|
||||||
|
exact output of `.github/scripts/release-path.sh`. Its measured diff from
|
||||||
|
`0.6.0` is not pin-only: `.github/workflows/release.yml`, `lib/facts.sh`, and
|
||||||
|
the new `lib/forge.sh` changed. These are release-door bytes, so the
|
||||||
|
doors-unchanged record shape is refused and this release carries a full live
|
||||||
|
rehearsal.
|
||||||
|
|
||||||
|
## Where
|
||||||
|
|
||||||
|
Disposable **private** repository `heavy-duty/ceremony-drill-0.6.1`, created
|
||||||
|
by the operator on 2026-08-09 after the fleet identity's personal repository
|
||||||
|
could not see the organization runner. It carries the `docs/CONSUMERS.md`
|
||||||
|
release caller with `version-source: file`, a fragment-mode fixture armed at
|
||||||
|
`0.6.1-dev`, and a non-blank `drills/0.6.1.md`. The `release` label existed
|
||||||
|
before the first probe PR.
|
||||||
|
|
||||||
|
**Disposal as observed when this record was written:** the repository is
|
||||||
|
private and **not yet archived**. This builder's archive PATCH returned 403
|
||||||
|
because it has push but not admin permission. The operator was asked to
|
||||||
|
archive it; delete remains the operator's later step. No archive or delete is
|
||||||
|
claimed, and cleanup gates neither review nor merge.
|
||||||
|
|
||||||
|
## Candidate-ref deviation
|
||||||
|
|
||||||
|
The candidate tree pins `CEREMONY_SELF_REF` to `0.6.1`, a tag that did not
|
||||||
|
exist before this release. No branch named `0.6.1` was created on
|
||||||
|
`heavy-duty/ceremony`.
|
||||||
|
|
||||||
|
The scratch caller instead uses
|
||||||
|
`codex-reviewer-andresmgsl/ceremony/.github/workflows/release.yml@drill/0.6.1`.
|
||||||
|
That ref is commit `f766752090429241c20a0d86aba6f679d713fc2c`; its parent is the canonical
|
||||||
|
candidate SHA above, and its only change rewrites all three
|
||||||
|
`CEREMONY_SELF_REF` carriers to that canonical SHA. The release path is
|
||||||
|
therefore byte-identical to the candidate except for the required candidate
|
||||||
|
pin rewrite.
|
||||||
|
|
||||||
|
## Probes
|
||||||
|
|
||||||
|
| # | probe | run | result |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 1 | merge-door ceremony | 5 | ✅ one `0.6.1` release; tag points to merge commit `438097fc75b1d294b448830a3a79c00b0ee7b83f`; main re-armed to `0.6.2-dev` |
|
||||||
|
| 2 | release-labelled ordinary PR | 2 | ✅ green no-op; zero tags and zero releases |
|
||||||
|
| 3 | bare-version PR without `release` | 3 | ✅ refused; zero tags and zero releases |
|
||||||
|
| 4 | completed ceremony repeated | 7 | ✅ refused; release count stayed one and tag `0.6.1` stayed on the original merge commit |
|
||||||
|
| 5 | manual matching tag | 8 | ✅ `0.6.2` published from its own changelog section; main was unchanged |
|
||||||
|
| 6 | mismatched tag | 9 | ✅ refused before publication; no `9.9.9` release, and the operator-created probe tag was removed afterwards |
|
||||||
|
|
||||||
|
### Probe 1 — merge door
|
||||||
|
|
||||||
|
PR #4 transitioned `VERSION` from `0.6.1-dev` to `0.6.1`, assembled both
|
||||||
|
fixture fragments, and carried the `release` label. Run 5 succeeded. The
|
||||||
|
published body is exactly the assembled `0.6.1` section, tag `0.6.1` points
|
||||||
|
to PR #4's merge commit, and the workflow pushed `0.6.2-dev` to main at
|
||||||
|
`1c74d76f035da4a13f7af0d2a43d611092061204`.
|
||||||
|
|
||||||
|
### Probe 2 — labelled ordinary work
|
||||||
|
|
||||||
|
PR #1 carried the `release` label and added only `changelog.d/2.md`.
|
||||||
|
`VERSION` stayed `0.6.1-dev`. Run 2 succeeded as a no-op; the tag and release
|
||||||
|
lists were both empty afterwards.
|
||||||
|
|
||||||
|
### Probe 3 — bare version without declared intent
|
||||||
|
|
||||||
|
PR #2 assembled the two fragments and transitioned to bare `0.6.1`, but had
|
||||||
|
no `release` label. Run 3 failed. Both tag and release lists remained empty.
|
||||||
|
Setup PR #3 then reverted that probe and restored the armed fixture; run 4
|
||||||
|
was green before the ceremony probe began.
|
||||||
|
|
||||||
|
### Probe 4 — completed ceremony repeated
|
||||||
|
|
||||||
|
Forgejo exposes no run-rerun API, so this probe reproduced the completed
|
||||||
|
door's input. Setup PR #5 restored `0.6.1-dev` and run 6 was green. Release-
|
||||||
|
labelled PR #6 transitioned to bare `0.6.1` again. Run 7 failed at the
|
||||||
|
already-existing tag guard. The release count stayed one and tag `0.6.1`
|
||||||
|
still pointed to the original ceremony merge commit, not PR #6.
|
||||||
|
|
||||||
|
### Probe 5 — matching manual tag
|
||||||
|
|
||||||
|
Side branch `probe5-tag` carried bare `VERSION 0.6.2` and a publishable
|
||||||
|
`0.6.2` changelog section. Tag `0.6.2` was created at
|
||||||
|
`a4ba62aa83fdd2e259ed3c3e906b13eaab1abcd0`. Run 8 succeeded and published
|
||||||
|
only that section. Main stayed at the state left by probe 4; the tag door did
|
||||||
|
not push a version change to it.
|
||||||
|
|
||||||
|
### Probe 6 — mismatched tag
|
||||||
|
|
||||||
|
Tag `9.9.9` was created at the same `0.6.2` tree. Run 9 failed before
|
||||||
|
publication. The release list remained exactly `0.6.1` and `0.6.2`. The
|
||||||
|
operator-created `9.9.9` ref was then deleted; the remaining tag list was
|
||||||
|
exactly `0.6.1` and `0.6.2`.
|
||||||
|
|
||||||
|
## Runs that are setup, not probes
|
||||||
|
|
||||||
|
- Run 1: the initial `0.6.1-dev` fixture push; green no-op.
|
||||||
|
- Run 4: PR #3 restored the armed fixture after probe 3; green.
|
||||||
|
- Run 6: PR #5 restored `0.6.1-dev` to reproduce the completed ceremony
|
||||||
|
input for probe 4; green.
|
||||||
|
|
||||||
|
## What this rehearsal establishes
|
||||||
|
|
||||||
|
Both release doors ran against the 0.6.1 candidate's Forgejo implementation.
|
||||||
|
The merge door published once, tagged the reviewed merge commit, and re-armed
|
||||||
|
main. It stayed a green no-op for labelled ordinary work, refused a bare
|
||||||
|
unlabelled transition, and refused a repeated ceremony. The tag door
|
||||||
|
published a matching tag without changing main and refused a mismatched tag
|
||||||
|
without publishing. Every refusal created no tag or release of its own.
|
||||||
Loading…
Reference in a new issue