From ba3b17af88e0fe1ccae9eefd4b37bf2666f6cdad Mon Sep 17 00:00:00 2001 From: Codex Review Date: Sun, 9 Aug 2026 16:12:18 +0000 Subject: [PATCH] release: stage 0.6.1 ceremony --- .github/workflows/labels-sweep.yml | 2 +- .github/workflows/labels.yml | 2 +- .github/workflows/release.yml | 2 +- CHANGELOG.md | 416 +++++++++++++++++++++++++++++ VERSION | 2 +- changelog.d/192.md | 47 ---- changelog.d/195.md | 16 -- changelog.d/198.md | 58 ---- changelog.d/199.md | 36 --- changelog.d/200.md | 48 ---- changelog.d/201.md | 24 -- changelog.d/202.md | 76 ------ changelog.d/205.md | 44 --- changelog.d/209.md | 16 -- changelog.d/210.md | 27 -- changelog.d/215.md | 47 ---- changelog.d/220.md | 9 - changelog.d/222.md | 10 - changelog.d/224.md | 5 - 19 files changed, 420 insertions(+), 467 deletions(-) delete mode 100644 changelog.d/192.md delete mode 100644 changelog.d/195.md delete mode 100644 changelog.d/198.md delete mode 100644 changelog.d/199.md delete mode 100644 changelog.d/200.md delete mode 100644 changelog.d/201.md delete mode 100644 changelog.d/202.md delete mode 100644 changelog.d/205.md delete mode 100644 changelog.d/209.md delete mode 100644 changelog.d/210.md delete mode 100644 changelog.d/215.md delete mode 100644 changelog.d/220.md delete mode 100644 changelog.d/222.md delete mode 100644 changelog.d/224.md diff --git a/.github/workflows/labels-sweep.yml b/.github/workflows/labels-sweep.yml index 705f4bf..557ca81 100644 --- a/.github/workflows/labels-sweep.yml +++ b/.github/workflows/labels-sweep.yml @@ -63,7 +63,7 @@ on: env: # A called workflow arrives without its repository. Keep this literal pin # aligned with the ceremony release consumed by callers (issue #9 D3). - CEREMONY_SELF_REF: "0.6.0" + CEREMONY_SELF_REF: "0.6.1" jobs: reconcile: diff --git a/.github/workflows/labels.yml b/.github/workflows/labels.yml index 8cdf02c..baebe76 100644 --- a/.github/workflows/labels.yml +++ b/.github/workflows/labels.yml @@ -48,7 +48,7 @@ on: env: # A called workflow arrives without its repository. Keep this literal pin # aligned with the ceremony release consumed by callers (issue #9 D3). - CEREMONY_SELF_REF: "0.6.0" + CEREMONY_SELF_REF: "0.6.1" jobs: scope: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cdb1133..8e4575f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -129,7 +129,7 @@ env: # `ref:` accepts ${{ env }}; `uses:` strings do not — which is why the # shared logic arrives as script files via checkout, not as inner `uses:` # references. - CEREMONY_SELF_REF: "0.6.0" + CEREMONY_SELF_REF: "0.6.1" VERSION_SOURCE: ${{ inputs.version-source }} jobs: diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d9a9c2..1f8b246 100644 --- a/CHANGELOG.md +++ b/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 (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 `/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 ### Added diff --git a/VERSION b/VERSION index 2feed2f..ee6cdce 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.6.1-dev +0.6.1 diff --git a/changelog.d/192.md b/changelog.d/192.md deleted file mode 100644 index 8851646..0000000 --- a/changelog.d/192.md +++ /dev/null @@ -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). diff --git a/changelog.d/195.md b/changelog.d/195.md deleted file mode 100644 index 91451e8..0000000 --- a/changelog.d/195.md +++ /dev/null @@ -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). diff --git a/changelog.d/198.md b/changelog.d/198.md deleted file mode 100644 index 4fa510f..0000000 --- a/changelog.d/198.md +++ /dev/null @@ -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). diff --git a/changelog.d/199.md b/changelog.d/199.md deleted file mode 100644 index 7f881d0..0000000 --- a/changelog.d/199.md +++ /dev/null @@ -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). diff --git a/changelog.d/200.md b/changelog.d/200.md deleted file mode 100644 index acfada8..0000000 --- a/changelog.d/200.md +++ /dev/null @@ -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). diff --git a/changelog.d/201.md b/changelog.d/201.md deleted file mode 100644 index c859f87..0000000 --- a/changelog.d/201.md +++ /dev/null @@ -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). diff --git a/changelog.d/202.md b/changelog.d/202.md deleted file mode 100644 index 7ff9cc3..0000000 --- a/changelog.d/202.md +++ /dev/null @@ -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). diff --git a/changelog.d/205.md b/changelog.d/205.md deleted file mode 100644 index 50ed6f6..0000000 --- a/changelog.d/205.md +++ /dev/null @@ -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 `/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). diff --git a/changelog.d/209.md b/changelog.d/209.md deleted file mode 100644 index 320b984..0000000 --- a/changelog.d/209.md +++ /dev/null @@ -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). diff --git a/changelog.d/210.md b/changelog.d/210.md deleted file mode 100644 index ed46cf2..0000000 --- a/changelog.d/210.md +++ /dev/null @@ -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). diff --git a/changelog.d/215.md b/changelog.d/215.md deleted file mode 100644 index 160ee13..0000000 --- a/changelog.d/215.md +++ /dev/null @@ -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). diff --git a/changelog.d/220.md b/changelog.d/220.md deleted file mode 100644 index 3901222..0000000 --- a/changelog.d/220.md +++ /dev/null @@ -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). diff --git a/changelog.d/222.md b/changelog.d/222.md deleted file mode 100644 index 6fdf537..0000000 --- a/changelog.d/222.md +++ /dev/null @@ -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). diff --git a/changelog.d/224.md b/changelog.d/224.md deleted file mode 100644 index d1bef5a..0000000 --- a/changelog.d/224.md +++ /dev/null @@ -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).