Compare commits
No commits in common. "84bb1a424dfe467358e99d4c6ab4e76ac89cc328" and "0e3d8de2a6adba37eaead9706cd4c9541fe19199" have entirely different histories.
84bb1a424d
...
0e3d8de2a6
24 changed files with 196 additions and 388 deletions
15
.github/workflows/labels.yml
vendored
15
.github/workflows/labels.yml
vendored
|
|
@ -10,14 +10,11 @@ name: labels
|
|||
# and reconcile checks out the BASE branch only. Keep it that way.
|
||||
#
|
||||
# There is no pull_request_review_target, so a review landing cannot wake this
|
||||
# workflow directly — which is why the caller's cron is load-bearing, not a
|
||||
# safety net (#199 relaxed it from */15 to hourly, but did NOT drop it). The
|
||||
# cron is the sweep's only discovery path for every transition no subscribed
|
||||
# event carries: a verdict landing, blocker:ci-red set/cleared, a
|
||||
# blocker:conflict when another PR merges under this one, and the time-based
|
||||
# stale / 48h claim-reclaim. Where an event IS subscribed the wake is direct —
|
||||
# the handoff sets state:needs-human and the caller's `labeled` event confirms
|
||||
# or corrects that optimistic write within seconds.
|
||||
# workflow directly — and the */15 cron is advisory: GitHub deprioritises
|
||||
# short intervals hard enough that a quiet repo goes hours between ticks. The
|
||||
# handoff wakes the sweep itself: the author sets state:needs-human, and the
|
||||
# caller's `labeled` event confirms or corrects that optimistic write within
|
||||
# seconds. The cron stays as the last resort for a forgotten handoff.
|
||||
#
|
||||
# This cannot loop: reconciler writes use GITHUB_TOKEN, and GitHub does not
|
||||
# create workflow runs from GITHUB_TOKEN-triggered events. Agent writes use a
|
||||
|
|
@ -28,7 +25,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.4.0"
|
||||
CEREMONY_SELF_REF: "0.3.0"
|
||||
|
||||
jobs:
|
||||
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
|
||||
# shared logic arrives as script files via checkout, not as inner `uses:`
|
||||
# references.
|
||||
CEREMONY_SELF_REF: "0.4.0"
|
||||
CEREMONY_SELF_REF: "0.3.0"
|
||||
VERSION_SOURCE: ${{ inputs.version-source }}
|
||||
|
||||
jobs:
|
||||
|
|
|
|||
35
.github/workflows/self-labels.yml
vendored
35
.github/workflows/self-labels.yml
vendored
|
|
@ -5,40 +5,11 @@ name: labels
|
|||
# unpinned — correct only for the repo that IS the source). Consumers write:
|
||||
# uses: heavy-duty/ceremony/.github/workflows/labels.yml@<pinned-tag>
|
||||
on:
|
||||
# The consumer owns this cadence (#203). Hourly is the recommended default
|
||||
# when no other engine drives board state: the cron is then the sweep's ONLY
|
||||
# wake for four transition classes — a review verdict landing (there is no
|
||||
# pull_request_review trigger here), blocker:ci-red set or cleared (no
|
||||
# check_suite/check_run/workflow_run), a blocker:conflict when ANOTHER PR
|
||||
# merges under this one, and the time-based stale / 48h claim-reclaim. The
|
||||
# events below carry the rest in seconds. Hourly trades ≤1h of latency on
|
||||
# those four while cutting nominal scheduled sweeps from four an hour to one
|
||||
# at GitHub's 1-minute billing floor. Do not delete the cron: it is their
|
||||
# discovery path. If another engine writes some of those transitions, only
|
||||
# the classes with no other writer bound the cadence; relax it only as that
|
||||
# list shrinks.
|
||||
schedule: [{cron: "0 * * * *"}]
|
||||
# A manual full-board sweep, including taxonomy bootstrap on a fresh repo.
|
||||
workflow_dispatch:
|
||||
# Narrowed (#199) to the actions that carry a queue-state change the hourly
|
||||
# cron cannot wait one cadence for — dropping only labeled/unlabeled/assigned/
|
||||
# unassigned, which feed validation and the 48h claim clock (caught within one
|
||||
# cadence) and were the dominant issues-churn source. Kept: `opened` (the
|
||||
# mint→needs-triage check, issueflow's opened-only path), `closed` (the
|
||||
# blocker-closes→ready self-heal, crew#96/#98), `edited` (a body rewrite of the
|
||||
# `Blocked by #N` declaration the sweep parses — issueflow-reconcile.sh:179),
|
||||
# `reopened` (a closed issue re-entering the queue wearing labels derived when
|
||||
# it closed). The must-fail in #199 is exactly "a queue-state transition waits
|
||||
# on the schedule when an event could have carried it", so edited/reopened stay
|
||||
# on events. The PR handoff wake is pull_request_target:labeled, NOT issues, so
|
||||
# this does not touch the handoff.
|
||||
schedule: [{cron: "*/15 * * * *"}] # advisory; the handoff label is the real wake
|
||||
workflow_dispatch: # bootstraps missing labels on a fresh repo
|
||||
issues:
|
||||
types: [opened, closed, edited, reopened]
|
||||
types: [opened, edited, assigned, unassigned, labeled, unlabeled, closed, reopened]
|
||||
pull_request_target:
|
||||
# Every PR arrives from a fork, so these carry the head/draft/review facts
|
||||
# the sweep derives state:* from. labeled/unlabeled are the handoff wake —
|
||||
# the author's optimistic state:needs-human write, confirmed or corrected
|
||||
# here in seconds (#11); synchronize re-derives on every push;
|
||||
# review_requested/review_request_removed wake the sweep that clears (or
|
||||
# restores) blocker:unrequested — without them the one event that makes
|
||||
# the label false could not clear it, and a quiet repo wore the red flag
|
||||
|
|
|
|||
|
|
@ -208,9 +208,9 @@ triage bug, and the move is to say so on the issue, not to guess.
|
|||
|
||||
## The review round
|
||||
|
||||
(If you are reading this as `.ceremony/BUILDER.md` in a governed repo:
|
||||
repo-specific facts such as the panel roster live in that repo's own
|
||||
CONTRIBUTING; the shared flow lives here and is not restated there.)
|
||||
(If you are reading this as `.ceremony/BUILDER.md` in a governed repo: the
|
||||
panel roster and any repo-specific flow notes live in that repo's own
|
||||
CONTRIBUTING; everything below is the shared flow.)
|
||||
|
||||
1. Mark ready-for-review; request **the whole panel**. The panel is the roster
|
||||
of the repo the **PR** is in, minus you — never the roster of the repo the
|
||||
|
|
|
|||
96
CHANGELOG.md
96
CHANGELOG.md
|
|
@ -9,102 +9,6 @@ Entries arrive as fragments — one `changelog.d/<issue>.md` per PR, never
|
|||
an edit to this file — and the release PR assembles them into the next
|
||||
section here (`bin/changelog-assemble`, #112).
|
||||
|
||||
## 0.4.0 — 2026-07-29
|
||||
|
||||
### Added
|
||||
|
||||
- `changelog.d/shape` — an optional one-line sentinel, `flat` or `grouped`,
|
||||
that pins the fragment set's shape and outranks the newest-published-section
|
||||
inference; absent, the inference binds unchanged (#182).
|
||||
- Add `post-merge` issue state for merged `Refs` work awaiting triage-owned verification.
|
||||
- `changelog_fragment_problem` bounds every entry at 300 normalized
|
||||
characters, red on the PR that writes the fragment; the armed guard and
|
||||
the assembler inherit the one definition (#167).
|
||||
- BUILDER.md and CHANGELOG.md state the bound and the split rule: a long
|
||||
change ships several short entries, never one long one (#167).
|
||||
|
||||
### Changed
|
||||
|
||||
- Labels automation docs now make sweep cadence a consumer-owned tradeoff,
|
||||
retain hourly as the engine-less default, and document manual dispatch as
|
||||
the operator's immediate full-board sweep (#203).
|
||||
- `labels` — the reconcile cron relaxes from `*/15` to hourly (#199), cutting a
|
||||
private consumer's schedule-triggered full-board sweeps ~4× at GitHub's
|
||||
1-minute billing floor.
|
||||
- `labels` — the hourly cron is the sweep's only wake for transitions no
|
||||
subscribed event carries — a verdict landing, blocker:ci-red, a
|
||||
blocker:conflict when another PR merges, the time-based stale/reclaim — so it
|
||||
bounds their latency to ≤1h, delaying no event-carried transition (#199).
|
||||
- `labels` — the caller's `issues:` trigger narrows to
|
||||
`[opened, closed, edited, reopened]` (#199), the actions that carry a
|
||||
queue-state change the cron cannot wait a cadence for. The churn/validation
|
||||
actions — labeled/unlabeled/assigned/unassigned — come off; the PR handoff
|
||||
wake is unaffected.
|
||||
- `labels` — each caller trigger now carries a comment saying why it is
|
||||
subscribed, and reconcile keeps `cancel-in-progress: false` (#199) —
|
||||
cancelling a sweep mid-board is the race that guard exists to prevent.
|
||||
- `CONTRIBUTING.md` now points to `BUILDER.md` for the shared PR flow instead
|
||||
of restating doctrine that can drift, while retaining ceremony's roster and
|
||||
other repo-specific facts (#198).
|
||||
- Builder doctrine makes each whole-round reply the durable Round log record
|
||||
mirrored by the engine, leaving handoff as a mechanical facts-only step
|
||||
instead of a newly composed summary (#196).
|
||||
- `FLEET.md` removes its duplicate bench roster, records crew as a general
|
||||
operator-configured tool, and advances its whole-file audit stamp to
|
||||
`crew@eaeb302` with every surviving crew link re-pinned (#193).
|
||||
- `FLEET.md` keeps the registry's authorization rule and its crew#16/crew#66
|
||||
provenance, while replacing duplicated mechanism and path claims with a
|
||||
pinned pointer to crew's registry header (#192).
|
||||
- `BUILDER.md` gates both review-request points on a green check at the
|
||||
head, carries crew#45's argued exception for failures outside the PR,
|
||||
and states the ruled classification: cancelled and stale are not a
|
||||
green head; skipped and neutral are (#189).
|
||||
- `BUILDER.md` documents CI-red recovery in pickup precedence: a red head
|
||||
of your own PR is picked up before claiming another issue, is never a
|
||||
parked claim, and follows crew#17's recovery path (#189).
|
||||
- `FLEET.md` writes the ci-red wake into the duty order between resume
|
||||
and build, now as deployed engine rather than on paper: the
|
||||
reconciliation stamp advances to the crew SHA carrying crew#64 (#189).
|
||||
- `FLEET.md` describes the build wake's check gate as the engine
|
||||
implements it: a green head, or one with no checks configured, opens a
|
||||
round; a red head and an unfinished one are held and reported
|
||||
separately (#189).
|
||||
- `FLEET.md` corrects the attention wake to the crew#66 ruling: the query
|
||||
is cross-repo, the action is registry-bounded, and an out-of-scope
|
||||
demand is reported and escalated to the operator rather than worked. It
|
||||
no longer claims attention is exempt from the registry (#189).
|
||||
- `FLEET.md` distinguishes an attention session that dies before acking,
|
||||
which relaunches, from one that completes without acking, which is a
|
||||
decline a ledger keeps from re-firing (#189).
|
||||
- `BUILDER.md` re-requests by head, not by verdict: a push while
|
||||
answering a round stales every approval, so every panelist is
|
||||
re-requested; only an unchanged head re-requests the non-approvers
|
||||
alone (#190).
|
||||
- FLEET.md's duty-loop mechanism is a pointer to crew's shared engine; the
|
||||
wake lists follow the engine's duty order, the roster keeps the as-built
|
||||
bench beside `fleet.roster`'s target, and the reconciliation stamp names
|
||||
crew@`01fb49c` (#187).
|
||||
- Ceremony's changelog is grouped from this release forward: the pending
|
||||
fragments carry `### ` headings under a `grouped` sentinel (#182).
|
||||
- BUILDER.md: a park declaration stands until its facts change — a
|
||||
nothing-changed resumption posts nothing; only a no-open-PR park owes a
|
||||
refresh, inside the 48-hour reclaim window (#178).
|
||||
- Private-repository label callers document `actions: read` alongside checks and statuses for workflow-run check-rollup nodes (#173).
|
||||
|
||||
### Fixed
|
||||
|
||||
- FLEET.md no longer says a review request outside the registry is
|
||||
authorization: `repos.txt` is the scope for the review queue, out-of-scope
|
||||
requests are logged and never acted on, and the attention wake is stated
|
||||
as the one registry-independent exception, by design (#187).
|
||||
- `blocked_reference_records` unions every `Blocked by` clause in the body
|
||||
instead of binding to the first marker occurrence — a repeated declaration
|
||||
no longer promotes on its first sentence alone, and earlier prose that
|
||||
merely mentions being blocked no longer hijacks the parse (#184).
|
||||
- `decide_state()` refuses `state:needs-human` while the hand-set `blocked`
|
||||
label stands — the PR falls to `state:addressing`, exactly parallel to the
|
||||
`needs-ruling` exclusion; never emitted by `blockers()` (#180).
|
||||
|
||||
## 0.3.0 — 2026-07-24
|
||||
|
||||
- Make `changelog-armed` reject fragment shape drift on the PR that introduces it.
|
||||
|
|
|
|||
|
|
@ -35,9 +35,45 @@ Who may set which label is [LABELS.md](LABELS.md)'s contract.
|
|||
|
||||
## The PR flow
|
||||
|
||||
PRs move through review rounds that builders answer whole, and only a human
|
||||
merges. [BUILDER.md](BUILDER.md) is the shared flow contract; this file names
|
||||
only ceremony-specific facts such as the roster and code conventions.
|
||||
The same flow the sibling repos run, and the part of this pipeline that is
|
||||
already proven:
|
||||
|
||||
1. **One issue, one PR**, opened as a **draft** while building, with
|
||||
`Closes #N` in the body — its exceptions (cross-repo work, a post-merge
|
||||
criterion) live in [BUILDER.md](BUILDER.md). Drafts are invisible to the
|
||||
reviewer panel on purpose. Every behavior change writes one fragment,
|
||||
`changelog.d/<issue>.md` — the exact prose to publish, nothing else
|
||||
(cross-repo work names it `<repo>-<issue>.md`; a grouped repo puts its
|
||||
`### Added` / `### Changed` / `### Fixed` headings inside the fragment).
|
||||
Never edit `CHANGELOG.md` for an entry — the release PR assembles the
|
||||
section from the fragments (#112).
|
||||
|
||||
The sole exception is the release PR: it writes no fragment. It consumes
|
||||
the directory and stamps the section, so a fragment it created would be
|
||||
absent from
|
||||
[`changelog-assembled`](https://github.com/heavy-duty/ceremony/blob/a602fd0/actions/changelog-assembled/changelog-assembled.sh)'s
|
||||
merge-base replay if consumed, or refused by
|
||||
[`changelog-armed`](https://github.com/heavy-duty/ceremony/blob/a602fd0/actions/changelog-armed/changelog-armed.sh)
|
||||
if left to survive into the next release. A change that must ship inside
|
||||
the release PR therefore ships without an entry. If it can wait and wants
|
||||
an entry, land it as an ordinary PR before the release PR, then rebase and
|
||||
re-assemble the release.
|
||||
2. **When it's ready**: mark ready-for-review and request the whole panel.
|
||||
3. **Rounds are answered whole.** Wait until every reviewer has a verdict in,
|
||||
then answer the entire round in a **single reply**, push the fixes, and
|
||||
re-request the reviewers that didn't approve. Prefer verification over
|
||||
argument: a test settles what a comment thread can't.
|
||||
4. **Reviews end in a verdict** — approve or request-changes, never a bare
|
||||
comment. The verdict carries blockingness only; the body carries the
|
||||
feedback. ([REVIEWER.md](REVIEWER.md) for why a comment-only review stalls
|
||||
the machine.)
|
||||
5. **Handoff**: when the round passes — every panel verdict is an approval of
|
||||
the current head and no `blocker:*` label stands — the engine requests the
|
||||
human's review, sets `state:needs-human`, and posts the handoff facts
|
||||
(approvals at the current head, head SHA, and Round-log pointer) on the
|
||||
author's behalf. The label write is optimistic; the reconciler validates
|
||||
it within seconds.
|
||||
6. **A human merges.** Nothing else merges.
|
||||
|
||||
### Roster
|
||||
|
||||
|
|
|
|||
14
LABELS.md
14
LABELS.md
|
|
@ -3,8 +3,7 @@
|
|||
The taxonomy shared across the heavy-duty repos. Only the `scope:` set
|
||||
differs per repo (each repo's `.github/labels.conf` names its actual
|
||||
surfaces); everything else below is core and identical everywhere, created by
|
||||
the labels workflow's dispatch (which is also the operator's manual
|
||||
full-board reconcile sweep; issue #10).
|
||||
the labels workflow's bootstrap dispatch (issue #10).
|
||||
|
||||
Two state machines share the taxonomy: the **PR machine** (proven in
|
||||
box/rig/cast, reconciled by machinery) and the **issue flow** (the
|
||||
|
|
@ -212,13 +211,10 @@ on a PR would say the same thing twice and drift.
|
|||
|
||||
## Maintenance
|
||||
|
||||
The labels workflow (issue #10) recomputes PR state statelessly on subscribed
|
||||
events plus a consumer-owned scheduled discovery sweep. Hourly is the
|
||||
recommended default when no other engine drives board state; relax it only as
|
||||
the transition classes with no other writer shrink. Manual dispatch both
|
||||
bootstraps this taxonomy idempotently and runs the operator's on-demand
|
||||
full-board reconcile. The sweep warns when the core taxonomy declares a label
|
||||
the repository lacks. The same workflow reconciles issue-flow labels on issue
|
||||
The labels workflow (issue #10) recomputes PR state statelessly on PR events
|
||||
plus a 15-minute advisory cron, and bootstraps this taxonomy idempotently on
|
||||
manual dispatch. The sweep warns when the core taxonomy declares a label the
|
||||
repository lacks. The same workflow reconciles issue-flow labels on issue
|
||||
events and during the scheduled sweep. Default GitHub labels (`duplicate`,
|
||||
`invalid`, `question`, `wontfix`, `help wanted`, `good first issue`) are
|
||||
deleted at bootstrap — a `question` is a discussion, not an issue.
|
||||
|
|
|
|||
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
|||
0.4.1-dev
|
||||
0.3.1-dev
|
||||
|
|
|
|||
7
changelog.d/167.md
Normal file
7
changelog.d/167.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
### Added
|
||||
|
||||
- `changelog_fragment_problem` bounds every entry at 300 normalized
|
||||
characters, red on the PR that writes the fragment; the armed guard and
|
||||
the assembler inherit the one definition (#167).
|
||||
- BUILDER.md and CHANGELOG.md state the bound and the split rule: a long
|
||||
change ships several short entries, never one long one (#167).
|
||||
3
changelog.d/173.md
Normal file
3
changelog.d/173.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
### Changed
|
||||
|
||||
- Private-repository label callers document `actions: read` alongside checks and statuses for workflow-run check-rollup nodes (#173).
|
||||
3
changelog.d/175.md
Normal file
3
changelog.d/175.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
### Added
|
||||
|
||||
- Add `post-merge` issue state for merged `Refs` work awaiting triage-owned verification.
|
||||
5
changelog.d/178.md
Normal file
5
changelog.d/178.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
### Changed
|
||||
|
||||
- BUILDER.md: a park declaration stands until its facts change — a
|
||||
nothing-changed resumption posts nothing; only a no-open-PR park owes a
|
||||
refresh, inside the 48-hour reclaim window (#178).
|
||||
5
changelog.d/180.md
Normal file
5
changelog.d/180.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
### Fixed
|
||||
|
||||
- `decide_state()` refuses `state:needs-human` while the hand-set `blocked`
|
||||
label stands — the PR falls to `state:addressing`, exactly parallel to the
|
||||
`needs-ruling` exclusion; never emitted by `blockers()` (#180).
|
||||
10
changelog.d/182.md
Normal file
10
changelog.d/182.md
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
### Added
|
||||
|
||||
- `changelog.d/shape` — an optional one-line sentinel, `flat` or `grouped`,
|
||||
that pins the fragment set's shape and outranks the newest-published-section
|
||||
inference; absent, the inference binds unchanged (#182).
|
||||
|
||||
### Changed
|
||||
|
||||
- Ceremony's changelog is grouped from this release forward: the pending
|
||||
fragments carry `### ` headings under a `grouped` sentinel (#182).
|
||||
6
changelog.d/184.md
Normal file
6
changelog.d/184.md
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
### Fixed
|
||||
|
||||
- `blocked_reference_records` unions every `Blocked by` clause in the body
|
||||
instead of binding to the first marker occurrence — a repeated declaration
|
||||
no longer promotes on its first sentence alone, and earlier prose that
|
||||
merely mentions being blocked no longer hijacks the parse (#184).
|
||||
13
changelog.d/187.md
Normal file
13
changelog.d/187.md
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
### Fixed
|
||||
|
||||
- FLEET.md no longer says a review request outside the registry is
|
||||
authorization: `repos.txt` is the scope for the review queue, out-of-scope
|
||||
requests are logged and never acted on, and the attention wake is stated
|
||||
as the one registry-independent exception, by design (#187).
|
||||
|
||||
### Changed
|
||||
|
||||
- FLEET.md's duty-loop mechanism is a pointer to crew's shared engine; the
|
||||
wake lists follow the engine's duty order, the roster keeps the as-built
|
||||
bench beside `fleet.roster`'s target, and the reconciliation stamp names
|
||||
crew@`01fb49c` (#187).
|
||||
27
changelog.d/189.md
Normal file
27
changelog.d/189.md
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
### Changed
|
||||
|
||||
- `BUILDER.md` gates both review-request points on a green check at the
|
||||
head, carries crew#45's argued exception for failures outside the PR,
|
||||
and states the ruled classification: cancelled and stale are not a
|
||||
green head; skipped and neutral are (#189).
|
||||
- `BUILDER.md` documents CI-red recovery in pickup precedence: a red head
|
||||
of your own PR is picked up before claiming another issue, is never a
|
||||
parked claim, and follows crew#17's recovery path (#189).
|
||||
- `FLEET.md` writes the ci-red wake into the duty order between resume
|
||||
and build, now as deployed engine rather than on paper: the
|
||||
reconciliation stamp advances to the crew SHA carrying crew#64 (#189).
|
||||
- `FLEET.md` describes the build wake's check gate as the engine
|
||||
implements it: a green head, or one with no checks configured, opens a
|
||||
round; a red head and an unfinished one are held and reported
|
||||
separately (#189).
|
||||
- `FLEET.md` corrects the attention wake to the crew#66 ruling: the query
|
||||
is cross-repo, the action is registry-bounded, and an out-of-scope
|
||||
demand is reported and escalated to the operator rather than worked. It
|
||||
no longer claims attention is exempt from the registry (#189).
|
||||
- `FLEET.md` distinguishes an attention session that dies before acking,
|
||||
which relaunches, from one that completes without acking, which is a
|
||||
decline a ledger keeps from re-firing (#189).
|
||||
- `BUILDER.md` re-requests by head, not by verdict: a push while
|
||||
answering a round stales every approval, so every panelist is
|
||||
re-requested; only an unchanged head re-requests the non-approvers
|
||||
alone (#190).
|
||||
5
changelog.d/192.md
Normal file
5
changelog.d/192.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
### Changed
|
||||
|
||||
- `FLEET.md` keeps the registry's authorization rule and its crew#16/crew#66
|
||||
provenance, while replacing duplicated mechanism and path claims with a
|
||||
pinned pointer to crew's registry header (#192).
|
||||
5
changelog.d/193.md
Normal file
5
changelog.d/193.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
### Changed
|
||||
|
||||
- `FLEET.md` removes its duplicate bench roster, records crew as a general
|
||||
operator-configured tool, and advances its whole-file audit stamp to
|
||||
`crew@eaeb302` with every surviving crew link re-pinned (#193).
|
||||
5
changelog.d/196.md
Normal file
5
changelog.d/196.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
### Changed
|
||||
|
||||
- Builder doctrine makes each whole-round reply the durable Round log record
|
||||
mirrored by the engine, leaving handoff as a mechanical facts-only step
|
||||
instead of a newly composed summary (#196).
|
||||
|
|
@ -122,8 +122,7 @@ the machinery at all:
|
|||
[Labels automation](#labels-automation), plus `.github/labels.conf`
|
||||
(panel + the repo's `scope:*` rows) and `.github/labeler.yml` (the
|
||||
path→scope globs). Run `workflow_dispatch` once — **this bootstraps
|
||||
the taxonomy, `release` label included** — and use it again whenever an
|
||||
operator needs a full-board sweep immediately.
|
||||
the taxonomy, `release` label included**.
|
||||
7. **The artifact hook** (optional): `.github/actions/release-artifact/`
|
||||
per [The artifact hook](#the-artifact-hook). No hook → the source
|
||||
tarball is the package.
|
||||
|
|
@ -297,39 +296,20 @@ The complete caller is:
|
|||
```yaml
|
||||
name: labels
|
||||
on:
|
||||
# The consumer owns this cadence (#203). Hourly is the recommended default
|
||||
# when no other engine drives board state: the cron is then the sweep's only
|
||||
# wake for four transition classes — a review verdict landing (no
|
||||
# pull_request_review trigger), blocker:ci-red set/cleared, blocker:conflict
|
||||
# when another PR merges under this one, and time-based stale / 48h
|
||||
# claim-reclaim. Events below carry the rest in seconds. Hourly trades ≤1h of
|
||||
# latency on those four while cutting nominal scheduled sweeps from four an
|
||||
# hour to one at GitHub's 1-minute floor. Do not delete the cron: it is their
|
||||
# discovery path. If another engine writes some of those transitions, only
|
||||
# the classes with no other writer bound the cadence; relax it only as that
|
||||
# list shrinks.
|
||||
schedule: [{cron: "0 * * * *"}]
|
||||
# A manual full-board sweep, including taxonomy bootstrap on a fresh repo.
|
||||
workflow_dispatch:
|
||||
schedule: [{cron: "*/15 * * * *"}] # advisory; the handoff label is the real wake
|
||||
workflow_dispatch: # bootstraps missing labels on a fresh repo
|
||||
pull_request_target:
|
||||
# Fork PRs; these carry the head/draft/review facts state:* derives from.
|
||||
# labeled/unlabeled are the handoff wake (state:needs-human confirmed here);
|
||||
# synchronize re-derives on every push. review_requested/review_request_removed
|
||||
# (shipped in 0.3.0, ceremony#137) wake the sweep that clears
|
||||
# blocker:unrequested when the panel is asked.
|
||||
# review_requested/review_request_removed are unreleased — not in 0.2.0;
|
||||
# add them with the pin bump to the first tag carrying ceremony#137. They
|
||||
# wake the sweep that clears blocker:unrequested when the panel is asked.
|
||||
types: [opened, reopened, ready_for_review, converted_to_draft, synchronize, labeled, unlabeled, review_requested, review_request_removed]
|
||||
# Available at 0.2.0 and later (the first tag carrying ceremony#32); a
|
||||
# consumer pinned to 0.1.0 omits this block.
|
||||
issues:
|
||||
# Narrowed (#199) to the actions carrying a queue-state change the hourly
|
||||
# cron cannot wait one cadence for: opened → the mint→needs-triage check,
|
||||
# closed → the blocker-closes→ready self-heal, edited → a body rewrite of the
|
||||
# `Blocked by #N` declaration the sweep parses, reopened → a closed issue
|
||||
# re-entering the queue. Dropped: labeled/unlabeled/assigned/unassigned —
|
||||
# validation + the 48h claim clock, caught within one cadence, and
|
||||
# labeled/unlabeled were the issues-churn source. The handoff wake is
|
||||
# pull_request_target:labeled, not issues, so this leaves it intact.
|
||||
types: [opened, closed, edited, reopened]
|
||||
# edited/reopened are unreleased — not in 0.2.0; add them with the pin
|
||||
# bump to the first tag carrying ceremony#144, the same bump as the
|
||||
# pull_request_target additions above.
|
||||
types: [opened, edited, assigned, unassigned, labeled, unlabeled, closed, reopened]
|
||||
permissions:
|
||||
contents: read
|
||||
checks: read # mergeability/check-rollup read for PR state
|
||||
|
|
@ -350,32 +330,23 @@ needs all three explicit reads above; without them the failure appears as an emp
|
|||
The `issues:` trigger is available at `0.2.0` and later — `0.2.0` is the
|
||||
first tag carrying ceremony#32. A consumer pinned to `0.1.0` omits it. Adopt
|
||||
it only by bumping every ceremony reference to `0.2.0` or later; never mix
|
||||
refs to adopt it early. The type list has grown then narrowed across tags:
|
||||
`0.2.0` (ceremony#32) shipped `[opened, labeled, unlabeled, assigned,
|
||||
unassigned, closed]`; `0.3.0` (ceremony#144) added `edited` and `reopened`;
|
||||
ceremony#199 narrows it to `[opened, closed, edited, reopened]` and relaxes the
|
||||
cron to hourly, so a consumer picks up the smaller trigger surface at the pin
|
||||
bump to the first tag carrying ceremony#199. The narrowing drops
|
||||
`labeled`/`unlabeled`/`assigned`/`unassigned` — validation and the 48h claim
|
||||
clock, which the hourly cron catches within one cadence, and `labeled`/
|
||||
`unlabeled` were the issues-churn source — while **keeping** #144's `edited`/
|
||||
`reopened`: those carry a queue-state change an event uniquely carries (a body
|
||||
rewrite of `Blocked by #N`, and a closed issue re-entering the queue), so the
|
||||
must-fail in ceremony#199 keeps them on events. `opened` drives the
|
||||
mint→`needs-triage` check and `closed` the blocker-closes→`ready` self-heal;
|
||||
the stub and ceremony's own caller stay byte-for-byte identical, the parity
|
||||
#144 established.
|
||||
refs to adopt it early. At `0.2.0` the type list stops at `closed`: `edited`
|
||||
and `reopened` are newer, part of the pending stub edit below.
|
||||
|
||||
`pull_request_target` is intentional: fork PRs need the base repository's
|
||||
token to write labels. The reusable workflow executes no PR code. It checks
|
||||
out only the consumer's base branch and the pinned ceremony implementation.
|
||||
The #52 ruling invariants ride exactly these triggers — but the caller above
|
||||
is no longer the #18 shape, so adopting current triggers is a stub edit, not
|
||||
a bare pin bump. `review_requested` and `review_request_removed` on
|
||||
`pull_request_target:` shipped in `0.3.0` (ceremony#137) — the wake that
|
||||
a bare pin bump. The pending edits are `review_requested` and
|
||||
`review_request_removed` on `pull_request_target:` (#137) — the wake that
|
||||
clears `blocker:unrequested` the moment the panel is asked, without which a
|
||||
quiet repo wears that flag until the backstop cron; a consumer picks them up
|
||||
by pinning `0.3.0` or later, never through mixed refs.
|
||||
quiet repo wears that flag until the advisory cron — and `edited` and
|
||||
`reopened` on `issues:` (#144): the wakes for a body edit rewriting the
|
||||
`Blocked by #N` declaration the reconcile sweep parses, and for a closed
|
||||
issue re-entering the queue wearing labels derived when it closed. Make both
|
||||
edits together, with the pin bump to the first tag carrying ceremony#137 and
|
||||
ceremony#144 — never before it and never through mixed refs.
|
||||
|
||||
`.github/labels.conf` has one mandatory panel setting, one mandatory
|
||||
`triage-actors` setting, and then zero or more scope rows:
|
||||
|
|
@ -404,20 +375,11 @@ Core state, blocker, work-queue, and release labels come from ceremony. Scope
|
|||
rows remain consumer-owned because paths and surfaces differ by repository.
|
||||
|
||||
After adding the caller and configuration, run `workflow_dispatch` once to
|
||||
bootstrap labels on a fresh repository. It is also the operator's general
|
||||
manual full-board sweep — the answer when the board looks wrong now rather
|
||||
than after the next scheduled cadence:
|
||||
|
||||
```sh
|
||||
gh workflow run labels.yml -R <owner>/<repo>
|
||||
```
|
||||
|
||||
Ceremony dogfoods the caller under the filename `self-labels.yml`, so the
|
||||
equivalent command in this repository substitutes that filename. Scheduled
|
||||
and PR-triggered runs only reconcile; they do not repeatedly upsert the
|
||||
taxonomy. When a ceremony pin bump adds a core label, bump the pin first and
|
||||
then re-dispatch `workflow_dispatch`; the scheduled sweep warns when the
|
||||
pinned taxonomy declares a core label the repository lacks.
|
||||
bootstrap labels on a fresh repository. Scheduled and PR-triggered runs only
|
||||
reconcile; they do not repeatedly upsert the taxonomy. When a ceremony pin
|
||||
bump adds a core label, bump the pin first and then re-dispatch
|
||||
`workflow_dispatch`; the scheduled sweep warns when the pinned taxonomy
|
||||
declares a core label the repository lacks.
|
||||
|
||||
## Doctrine mirror
|
||||
|
||||
|
|
|
|||
|
|
@ -1,50 +0,0 @@
|
|||
# 0.4.0 — drill record
|
||||
|
||||
Run 2026-07-29 by `codex-bot-andresmgsl` against release PR #207 head
|
||||
`7c755bcd402ba7f9a38ecd406a025c149c77aa57`.
|
||||
|
||||
Where: disposable private repo
|
||||
`codex-bot-andresmgsl/ceremony-drill-0.4.0`, carrying the
|
||||
`docs/CONSUMERS.md` release caller and a fragment-mode fixture armed at
|
||||
`0.4.0-dev`. The fixture had `changelog.d/README.md`, one release fragment,
|
||||
and a non-blank drill record. The repository is archived, pending the
|
||||
operator's delete.
|
||||
|
||||
## Candidate-ref deviation
|
||||
|
||||
The pure consumer path cannot resolve `CEREMONY_SELF_REF: "0.4.0"` before
|
||||
the candidate creates that tag. No `0.4.0` branch was created in
|
||||
`heavy-duty/ceremony`. The scratch caller instead used
|
||||
`codex-bot-andresmgsl/ceremony@drill/0.4.0`, whose parent is PR #207 head
|
||||
`7c755bcd402ba7f9a38ecd406a025c149c77aa57` and whose only additional
|
||||
commit rewrites both `CEREMONY_SELF_REF` carriers to that same canonical
|
||||
candidate SHA. All runtime machinery was therefore fetched from the 0.4.0
|
||||
candidate tree.
|
||||
|
||||
## Probes
|
||||
|
||||
| # | probe | run | result |
|
||||
|---|---|---|---|
|
||||
| 1 | merge-door ceremony | 30445585532 (attempt 1) | ✅ one `0.4.0` release; tag equals merge commit; main re-armed to `0.4.1-dev` with only `changelog.d/README.md` |
|
||||
| 2 | mislabeled ordinary PR | 30445473977 | ✅ green NOTICE no-op; no tag or release |
|
||||
| 3 | bare-version PR without `release` | 30445513860 | ✅ refused at decide; no tag or release |
|
||||
| 4 | re-run completed ceremony | 30445585532 (attempt 2) | ✅ refused because the tag/release already existed; release count stayed one |
|
||||
| 5 | manual matching tag | 30445658952 | ✅ `0.5.0` published with its changelog section; main untouched |
|
||||
| 6 | mismatched tag | 30445684268 | ✅ refused before publication; no `9.9.9` release, and the probe tag was removed afterward |
|
||||
|
||||
The merge-door `0.4.0` tag and PR #3 merge commit were both
|
||||
`2b2d592ffcc5c376e55bd5fcf2dd5ffbdd692d64`. Its release body was exactly:
|
||||
|
||||
```text
|
||||
- Fragment mode is exercised by the 0.4.0 drill.
|
||||
```
|
||||
|
||||
## Setup
|
||||
|
||||
The armed fixture was committed before the caller so the first workflow run
|
||||
had a real parent version to inspect. Installing the caller then produced
|
||||
green baseline run 30445432039.
|
||||
|
||||
After the unlabeled bare-version refusal, its merge commit was reverted to
|
||||
restore the armed fixture. That setup correction produced green no-op run
|
||||
30445544068 before the merge-door probe began.
|
||||
|
|
@ -1,98 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
set -u
|
||||
|
||||
# The labels TRIGGER SURFACE is a cost lever (#199): a full-board sweep is
|
||||
# billed a 1-minute minimum every time a trigger fires, so how OFTEN it fires
|
||||
# is what exhausted the fleet's shared Actions allotment. These assertions
|
||||
# pin the reductions #199 made and the guard it must not trade away — none of
|
||||
# them touch the reconciler's LOGIC, which its own fixtures cover.
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
# shellcheck source=test/harness.sh
|
||||
source "$ROOT/test/harness.sh"
|
||||
|
||||
REUSABLE="$ROOT/.github/workflows/labels.yml"
|
||||
SELF="$ROOT/.github/workflows/self-labels.yml"
|
||||
STUB="$ROOT/docs/CONSUMERS.md" # the published caller stub, a fenced yaml block
|
||||
|
||||
# The `cancel-in-progress:` value of a named top-level job, read from the first
|
||||
# such line inside that job's block. Job keys sit at two-space indent.
|
||||
job_cancel_in_progress() { # $1 = file, $2 = job name
|
||||
awk -v job="^ $2:\$" '
|
||||
$0 ~ job { f = 1; next }
|
||||
f && /^ [a-z]/ { exit } # next job — stop before leaking into it
|
||||
f && /cancel-in-progress:/ { sub(/.*cancel-in-progress:[[:space:]]*/, ""); print; exit }
|
||||
' "$1"
|
||||
}
|
||||
|
||||
# The `types:` list of a trigger key (issues:, pull_request_target:), read from
|
||||
# the first `types:` line after the bare key. The key is bare (nothing after
|
||||
# the colon) so it never collides with `issues: write` in the permissions block.
|
||||
trigger_types() { # $1 = file, $2 = trigger key
|
||||
awk -v key="^ $2:\$" '
|
||||
$0 ~ key { f = 1; next }
|
||||
f && /^ types:/ { sub(/^ types:[[:space:]]*/, ""); print; exit }
|
||||
f && /^ [a-z]/ { exit }
|
||||
' "$1"
|
||||
}
|
||||
|
||||
# ---- the guard the cost fix must never trade away (#199 test plan must-fail) --
|
||||
# cancel-in-progress: true on reconcile kills a sweep mid-board, the exact race
|
||||
# the shared concurrency group exists to prevent. It WOULD cut run count — by
|
||||
# trading correctness for minutes — so it stays false, forever.
|
||||
check "reconcile serializes, never cancels mid-board" 0 "false" \
|
||||
job_cancel_in_progress "$REUSABLE" reconcile
|
||||
# shellcheck disable=SC2016 # the awk program runs in the nested bash, not here
|
||||
check "reconcile is never cancel-in-progress: true" 1 "" \
|
||||
bash -c 'job_cancel_in_progress() {
|
||||
awk -v job="^ reconcile:\$" "\$0 ~ job{f=1;next} f&&/^ [a-z]/{exit} f&&/cancel-in-progress:/{sub(/.*cancel-in-progress:[[:space:]]*/,\"\");print;exit}" "$1"
|
||||
}; [ "$(job_cancel_in_progress "$1")" = true ]' _ "$REUSABLE"
|
||||
# scope MAY cancel — it is per-PR and additive, so a superseded run is waste,
|
||||
# not a lost sweep. This asserts the must-fail above is scoped to reconcile.
|
||||
check "scope stays cancel-in-progress: true (per-PR, additive)" 0 "true" \
|
||||
job_cancel_in_progress "$REUSABLE" scope
|
||||
|
||||
# ---- the cron is a backstop, relaxed to hourly (#199 candidate 1) -----------
|
||||
# Scope the */15 assertion to the cron LINE — the prose comments cite */15 by
|
||||
# name to explain the change, and must not re-red their own documentation.
|
||||
check "self caller cron is hourly" 0 '0 * * * *' grep -F 'cron:' "$SELF"
|
||||
# shellcheck disable=SC2016 # $1 expands in the nested bash, not here
|
||||
check "self caller cron line no longer fires */15" 1 "" \
|
||||
bash -c 'grep -F "cron:" "$1" | grep -qF "*/15"' _ "$SELF"
|
||||
check "stub cron is hourly" 0 '0 * * * *' grep -F 'cron:' "$STUB"
|
||||
# shellcheck disable=SC2016 # $1 expands in the nested bash, not here
|
||||
check "stub cron line no longer fires */15" 1 "" \
|
||||
bash -c 'grep -F "cron:" "$1" | grep -qF "*/15"' _ "$STUB"
|
||||
|
||||
# ---- issues: is narrowed to the queue-state-changing actions (#199) ----------
|
||||
# Kept because each carries a queue-state change an event uniquely carries, so
|
||||
# dropping it would trip #199's must-fail (a transition waiting on the schedule
|
||||
# when an event could have carried it): opened → mint→needs-triage; closed →
|
||||
# blocker-closes→ready self-heal; edited → a body rewrite of the `Blocked by #N`
|
||||
# declaration the sweep parses; reopened → a closed issue re-entering the queue.
|
||||
# (labels.test.sh owns the exact-list and caller<->stub parity assertions.)
|
||||
for keep in opened closed edited reopened; do
|
||||
# shellcheck disable=SC2016 # the awk program runs in the nested bash, not here
|
||||
check "self caller issues surface keeps '$keep'" 0 "" \
|
||||
bash -c 'trigger_types() {
|
||||
awk -v key="^ issues:\$" "\$0 ~ key{f=1;next} f&&/^ types:/{sub(/^ types:[[:space:]]*/,\"\");print;exit} f&&/^ [a-z]/{exit}" "$1"
|
||||
}; trigger_types "$1" | grep -qw "$2"' _ "$SELF" "$keep"
|
||||
done
|
||||
# The churn actions must not reappear on the issues surface without a fresh why.
|
||||
# labeled/unlabeled were the dominant issues-churn source; assigned/unassigned
|
||||
# only feed validation and the 48h claim clock, caught within one cadence.
|
||||
for churn in labeled unlabeled assigned unassigned; do
|
||||
# shellcheck disable=SC2016 # the awk program runs in the nested bash, not here
|
||||
check "self caller issues surface drops '$churn'" 1 "" \
|
||||
bash -c 'trigger_types() {
|
||||
awk -v key="^ issues:\$" "\$0 ~ key{f=1;next} f&&/^ types:/{sub(/^ types:[[:space:]]*/,\"\");print;exit} f&&/^ [a-z]/{exit}" "$1"
|
||||
}; trigger_types "$1" | grep -qw "$2"' _ "$SELF" "$churn"
|
||||
done
|
||||
|
||||
# ---- the PR handoff wake is NOT collateral of the issues narrowing ----------
|
||||
# The handoff (state:needs-human, confirmed by the caller's labeled event) rides
|
||||
# pull_request_target, not issues. A future edit that strips it there re-reds.
|
||||
check "pull_request_target keeps the labeled handoff wake" 0 "labeled" \
|
||||
trigger_types "$SELF" pull_request_target
|
||||
|
||||
summary
|
||||
|
|
@ -69,16 +69,15 @@ check "LABELS.md enumerates no repo's scope labels" 1 "0" \
|
|||
# same lists. review_requested/review_request_removed are the wake that
|
||||
# clears blocker:unrequested — the label sat false for as long as a quiet
|
||||
# repo stayed quiet because the one event that falsifies it was never
|
||||
# listed (#137). The issues list narrowed to [opened, closed, edited, reopened]
|
||||
# (#199): each carries a queue-state change the hourly cron cannot wait one
|
||||
# cadence for — opened drives mint→needs-triage, closed the blocker-closes→ready
|
||||
# self-heal, edited a body rewrite of the `Blocked by #N` line the sweep parses,
|
||||
# reopened a closed issue re-entering the queue — while the churn/validation
|
||||
# actions (labeled/unlabeled/assigned/unassigned) came off. The stub is prose, so nothing but these rows
|
||||
# keeps the lists from drifting: a type in one file only is a wake that fires
|
||||
# at home and nowhere in the fleet, or the reverse — the drift #144 caught.
|
||||
# The NF guard keeps `issues: write` under permissions: from matching the
|
||||
# issues: trigger key.
|
||||
# listed (#137). edited/reopened are the wakes for the two events that
|
||||
# falsify issue labels silently — an edited body rewrites the `Blocked by
|
||||
# #N` declaration the reconcile sweep parses, and a reopened issue
|
||||
# re-enters the queue wearing labels derived at close; PR #32 widened the
|
||||
# caller by both and the stub never followed (#144). The stub is prose, so
|
||||
# nothing but these rows keeps the lists from drifting: a type in one file
|
||||
# only is a wake that fires at home and nowhere in the fleet, or the
|
||||
# reverse. The NF guard keeps `issues: write` under permissions: from
|
||||
# matching the issues: trigger key.
|
||||
event_types() { # $1 = file, $2 = trigger key → that trigger's types line, unindented
|
||||
awk -v key="$2:" '$1 == key && NF == 1 {f=1; next} f && /types: /{sub(/^ */,""); print; exit}' "$1"
|
||||
}
|
||||
|
|
@ -89,20 +88,17 @@ types_in_sync() { # $1 = trigger key, $2 = caller, $3 = stub → 0 when both lis
|
|||
}
|
||||
CALLER="$ROOT/.github/workflows/self-labels.yml"
|
||||
STUB="$ROOT/docs/CONSUMERS.md"
|
||||
# event_types anchors on the bare trigger key (NF == 1), so it reads the real
|
||||
# types line even though the #199 comments name pull_request_target: and
|
||||
# issues: in prose above the keys — an inline /pull_request_target:/ scan would
|
||||
# latch onto the first mention and read the wrong list.
|
||||
pr_has_both_review_wakes() {
|
||||
event_types "$CALLER" pull_request_target | grep -F review_requested | grep -qF review_request_removed
|
||||
}
|
||||
check "caller and stub pull_request_target lists are identical" 0 "" \
|
||||
types_in_sync pull_request_target "$CALLER" "$STUB"
|
||||
check "the caller lists both review-request wakes" 0 "" pr_has_both_review_wakes
|
||||
# shellcheck disable=SC2016 # expansion belongs to the nested bash
|
||||
check "the caller lists both review-request wakes" 0 "" bash -c \
|
||||
'awk "/pull_request_target:/{f=1; next} f && /types: /{print; exit}" "$1" |
|
||||
grep -F review_requested | grep -qF review_request_removed' _ "$CALLER"
|
||||
check "caller and stub issues lists are identical" 0 "" \
|
||||
types_in_sync issues "$CALLER" "$STUB"
|
||||
check "the caller lists exactly the queue-state-changing issue types" 0 \
|
||||
"types: [opened, closed, edited, reopened]" event_types "$CALLER" issues
|
||||
check "the caller still lists all eight issue types" 0 \
|
||||
"types: [opened, edited, assigned, unassigned, labeled, unlabeled, closed, reopened]" \
|
||||
event_types "$CALLER" issues
|
||||
# the failing cases: drop a type from either file, or reorder one list only,
|
||||
# and the identity rows above go red — exercised here on mutated copies
|
||||
mut_caller="$TMP/mut-caller.yml" mut_stub="$TMP/mut-stub.md"
|
||||
|
|
@ -116,13 +112,13 @@ sed 's/review_requested, review_request_removed/review_request_removed, review_r
|
|||
"$STUB" >"$mut_stub"
|
||||
check "a reorder in one list only goes red" 1 "" \
|
||||
types_in_sync pull_request_target "$CALLER" "$mut_stub"
|
||||
sed 's/, closed//' "$CALLER" >"$mut_caller"
|
||||
sed 's/, edited//' "$CALLER" >"$mut_caller"
|
||||
check "an issue type dropped from the caller goes red" 1 "" \
|
||||
types_in_sync issues "$mut_caller" "$STUB"
|
||||
sed 's/, closed//' "$STUB" >"$mut_stub"
|
||||
sed 's/, edited//' "$STUB" >"$mut_stub"
|
||||
check "an issue type dropped from the stub goes red" 1 "" \
|
||||
types_in_sync issues "$CALLER" "$mut_stub"
|
||||
sed 's/opened, closed/closed, opened/' "$STUB" >"$mut_stub"
|
||||
sed 's/closed, reopened/reopened, closed/' "$STUB" >"$mut_stub"
|
||||
check "an issue-list reorder in one file only goes red" 1 "" \
|
||||
types_in_sync issues "$CALLER" "$mut_stub"
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue