docs: the additive-scope contract in CONSUMERS/LABELS; changelog fragment

Part of #130.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
claude-bot-andresmgsl 2026-07-24 11:57:10 +00:00
parent 16dfdb9a4f
commit d0b857eb10
5 changed files with 25 additions and 4 deletions

View file

@ -170,8 +170,10 @@ re-arms mentions, but only the writer can declare that a move is owed (#83).
## Scope — which surface? (PRs and issues, any number)
All scopes share one calm color, `#C5DEF5` — scopes locate, states alert. The
set is per-repo: PRs get theirs from changed paths via actions/labeler, issues
get theirs from triage. This file never enumerates a set — it is mirrored
set is per-repo: PRs get theirs from changed paths via the labels workflow's
scope job — an additive write only, so a label applied by hand or by an agent
while the machine runs always survives it (#130) — and issues get theirs from
triage. This file never enumerates a set — it is mirrored
byte-identically into every governed repo, and any list it carried would be
true in one repo and false in the rest (#104). The set for the repo you are
standing in lives in the two places that are true wherever you read them: its

View file

@ -56,9 +56,9 @@ glob_to_regex() { # $1 = glob (the subset above) → anchored ERE, one line
i=$((i + 2))
continue
fi
out="$out[^/]*"
out="${out}[^/]*"
;;
\?) out="$out[^/]" ;;
\?) out="${out}[^/]" ;;
[a-zA-Z0-9_/-]) out="$out$c" ;;
*) out="$out\\$c" ;; # every other byte is literal — ., +, {, (, …
esac

1
changelog.d/130.md Normal file
View file

@ -0,0 +1 @@
- `actions/labels-scope` replaces `actions/labeler@v5` in the labels workflow's scope job: labeler wrote the whole label set (`PUT`) even under `sync-labels: false`, silently removing any label applied while it ran — #128 lost its `release` that way — so the scope job now derives from the same `.github/labeler.yml` mapping (the `changed-files`/`any-glob-to-any-file` shape, block or flow; anything else refuses loudly) and its only write is an additive `POST`. The reconcile sweep also warns — never sets — when a non-draft PR is release-shaped (bare version differing from its base) but carries no `release` label (#130).

View file

@ -276,6 +276,21 @@ never writes it). The consumer keeps its path mapping in
`.github/labeler.yml` and its review panel plus scope taxonomy in
`.github/labels.conf`.
**Additive means additive** (unreleased — #130): the scope job's only label
write is `POST /issues/{n}/labels`, which adds the derived scopes and removes
nothing, so a label applied while the job runs survives it. Earlier tags used
`actions/labeler@v5`, which — even under `sync-labels: false` — replaces the
whole label set and silently drops a label written mid-job (ceremony#128 lost
its `release` that way). With the same pin bump, `.github/labeler.yml` keeps
its format but the accepted shape becomes exactly the one this guide has
always shown: label → `changed-files``any-glob-to-any-file`, block or flow
style, globs over `**`, `*` and `?` (`**` crosses `/`, the others do not; the
whole path must match). Any other labeler key — `all-globs-to-all-files`,
branch matchers, negations — fails the run loudly instead of being
half-honoured. The reconcile sweep also warns (never sets) when a non-draft
PR carries a bare `X.Y.Z` version differing from its base but no `release`
label — the merge door would refuse that merge, and the sweep says so first.
The complete caller is:
```yaml

View file

@ -31,12 +31,15 @@ scope:docs${TAB}README"
check "derive: ** matches nested paths" 0 "scope:release-flow" \
derive_labels "$cfg" 'lib/deep/facts.sh'
# shellcheck disable=SC2016 # expansion belongs to the nested bash
check "derive: ** does not match the bare directory" 1 "" \
bash -c 'source "$1"; [ -n "$(derive_labels "$2" lib)" ]' _ \
"$ROOT/actions/labels-scope/labels-scope.sh" "$cfg"
# shellcheck disable=SC2016 # expansion belongs to the nested bash
check "derive: literal glob does not match a nested twin" 1 "" \
bash -c 'source "$1"; [ -n "$(derive_labels "$2" docs2/README)" ]' _ \
"$ROOT/actions/labels-scope/labels-scope.sh" "$cfg"
# shellcheck disable=SC2016 # expansion belongs to the nested bash
check "derive: one label per line, config order, deduped" 0 "" \
bash -c 'source "$1"; got="$(derive_labels "$2" "$(printf "%s\n" README VERSION lib/x docs/a.md)")"
[ "$got" = "$(printf "%s\n" scope:release-flow scope:docs)" ] || { printf "%s\n" "$got"; exit 1; }' _ \