Merge pull request #94 from claude-bot-andresmgsl/build/93-retire-default-labels
labels-reconcile: retire the six GitHub default labels at bootstrap (#93)
This commit is contained in:
commit
6b127f1ba7
3 changed files with 194 additions and 1 deletions
|
|
@ -6,6 +6,7 @@ so entries say what changed, cite the issue, and stop.
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
- `labels-reconcile` — the bootstrap now retires the six GitHub defaults `LABELS.md` publishes as deleted, tolerating both an already-absent label and a refused delete (#93).
|
||||||
- `issueflow-reconcile` — a triage-authored issue arrival stands down with exit 0 instead of killing the run before the sweep (#91).
|
- `issueflow-reconcile` — a triage-authored issue arrival stands down with exit 0 instead of killing the run before the sweep (#91).
|
||||||
- FLEET.md — the assignee's `attention` wake: one role-independent trigger ahead of every per-role list, one acked session per demand; a spec on paper until `duty.sh` polls it (#86).
|
- FLEET.md — the assignee's `attention` wake: one role-independent trigger ahead of every per-role list, one acked session per demand; a spec on paper until `duty.sh` polls it (#86).
|
||||||
- `attention` doctrine — define its assignee-owned pickup, ack, queue and clock semantics across labels, triage, and builder roles (#85).
|
- `attention` doctrine — define its assignee-owned pickup, ack, queue and clock semantics across labels, triage, and builder roles (#85).
|
||||||
|
|
|
||||||
|
|
@ -402,8 +402,22 @@ epic|5319E7|Organizes other issues via a dependency-ordered task list — builde
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
|
retired_label_names() { # the GitHub defaults LABELS.md retires — a `question` is a discussion
|
||||||
|
# One registry, kept beside core_label_rows() for the same reason those rows
|
||||||
|
# are not in labels.conf: a rule that must hold in every governed repo
|
||||||
|
# cannot live in a per-repo file. The six names match LABELS.md exactly.
|
||||||
|
cat <<'EOF'
|
||||||
|
duplicate
|
||||||
|
invalid
|
||||||
|
question
|
||||||
|
wontfix
|
||||||
|
help wanted
|
||||||
|
good first issue
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
bootstrap_labels() { # dispatch-only: ~20 upserts is too chatty for every cron tick
|
bootstrap_labels() { # dispatch-only: ~20 upserts is too chatty for every cron tick
|
||||||
local rows
|
local rows name
|
||||||
rows="$(core_label_rows)"
|
rows="$(core_label_rows)"
|
||||||
if [ -f "$LABELS_CONF" ]; then
|
if [ -f "$LABELS_CONF" ]; then
|
||||||
rows="$rows
|
rows="$rows
|
||||||
|
|
@ -413,6 +427,21 @@ $(configured_label_rows "$LABELS_CONF")"
|
||||||
[ -n "$name" ] || continue
|
[ -n "$name" ] || continue
|
||||||
run gh label create "$name" -R "$REPO" --color "$color" --description "$desc" --force
|
run gh label create "$name" -R "$REPO" --color "$color" --description "$desc" --force
|
||||||
done <<<"$rows"
|
done <<<"$rows"
|
||||||
|
|
||||||
|
# LABELS.md publishes the defaults as deleted at bootstrap; until #93
|
||||||
|
# nothing deleted them — incubator's first dispatch ran green and left
|
||||||
|
# `good first issue` standing. Deletion is dispatch-only like the upserts,
|
||||||
|
# and never fatal: `gh label delete` exits non-zero on a label that is
|
||||||
|
# already gone, the NORMAL case from the second dispatch on, and under
|
||||||
|
# set -e an unguarded call aborts the whole run (#91's shape). A 403
|
||||||
|
# refusal gets the same tolerance — the bot bootstrap already 403s on
|
||||||
|
# blocker:drill-pending, and a token that cannot delete must still get
|
||||||
|
# the taxonomy it can create. Either way: log the name, keep going.
|
||||||
|
while IFS= read -r name; do
|
||||||
|
[ -n "$name" ] || continue
|
||||||
|
run gh label delete "$name" -R "$REPO" --yes \
|
||||||
|
|| log "retire: '$name' not deleted (already absent, or refused) — continuing"
|
||||||
|
done <<<"$(retired_label_names)"
|
||||||
}
|
}
|
||||||
|
|
||||||
has_label() { grep -qxF "$1" <<<"$LABELS"; }
|
has_label() { grep -qxF "$1" <<<"$LABELS"; }
|
||||||
|
|
|
||||||
|
|
@ -592,5 +592,168 @@ expect "exactly one nudge across both sweeps" \
|
||||||
expect "no label edit across both sweeps names the ruling flag" \
|
expect "no label edit across both sweeps names the ruling flag" \
|
||||||
no "$(grep -q 'needs-ruling' "$RTMP/edits" 2>/dev/null && echo yes || echo no)"
|
no "$(grep -q 'needs-ruling' "$RTMP/edits" 2>/dev/null && echo yes || echo no)"
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# bootstrap_labels retires the GitHub defaults (#93). LABELS.md published
|
||||||
|
# them as deleted at bootstrap; nothing deleted them — incubator's first
|
||||||
|
# dispatch (run 30041309187) ran green and left `good first issue` standing,
|
||||||
|
# the first honest read of the machine since the older repos were cleaned by
|
||||||
|
# hand. One registry beside the taxonomy, dispatch-only, and never fatal:
|
||||||
|
# absence is the NORMAL case from the second dispatch on (#91's set -e
|
||||||
|
# shape), and a 403 refusal must not cost the taxonomy the token CAN create.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
BOOT="$RTMP/bootstrap"
|
||||||
|
mkdir -p "$BOOT"
|
||||||
|
|
||||||
|
RETIRED_WANT='duplicate
|
||||||
|
invalid
|
||||||
|
question
|
||||||
|
wontfix
|
||||||
|
help wanted
|
||||||
|
good first issue'
|
||||||
|
expect "the retired registry is exactly the six, no seventh" \
|
||||||
|
"$RETIRED_WANT" "$(retired_label_names)"
|
||||||
|
# the sentence and the registry must not drift apart again: parse the names
|
||||||
|
# out of LABELS.md's own parenthetical and demand identity, name for name
|
||||||
|
# shellcheck disable=SC2016 # the backticks are LABELS.md literals, not expansions
|
||||||
|
doctrine="$(sed -n '/Default GitHub labels/,/are deleted at/p' LABELS.md \
|
||||||
|
| tr '\n' ' ' | sed 's/.*(//;s/).*//' | grep -o '`[^`]*`' | tr -d '`')"
|
||||||
|
expect "...and matches LABELS.md name for name" "$doctrine" "$(retired_label_names)"
|
||||||
|
|
||||||
|
expected_upserts="$({ core_label_rows; configured_label_rows .github/labels.conf; } | cut -d'|' -f1)"
|
||||||
|
|
||||||
|
# -- happy path: the deletes ride the same dispatch, after an unchanged upsert set
|
||||||
|
(
|
||||||
|
REPO=owner/repo LABELS_CONF=.github/labels.conf
|
||||||
|
run() { printf '%s\n' "$*" >>"$BOOT/happy"; }
|
||||||
|
bootstrap_labels
|
||||||
|
)
|
||||||
|
expect "a dispatch deletes the six in the same run as the upserts" \
|
||||||
|
"$RETIRED_WANT" \
|
||||||
|
"$(sed -n 's/^gh label delete \(.*\) -R owner\/repo --yes$/\1/p' "$BOOT/happy")"
|
||||||
|
expect "...and the recorded upsert set is unchanged from today's" \
|
||||||
|
"$expected_upserts" \
|
||||||
|
"$(sed -n 's/^gh label create \([^ ]*\) .*/\1/p' "$BOOT/happy")"
|
||||||
|
|
||||||
|
# -- a missing label is success: gh exits non-zero with not-found, and the
|
||||||
|
# guard keeps that from aborting the dispatch. Red without the guard.
|
||||||
|
boot_missing_probe() {
|
||||||
|
(
|
||||||
|
REPO=owner/repo LABELS_CONF=.github/labels.conf
|
||||||
|
run() { "$@"; }
|
||||||
|
# shellcheck disable=SC2317 # reached through run's "$@", opaque to shellcheck
|
||||||
|
gh() {
|
||||||
|
if [ "$1" = label ] && [ "$2" = delete ]; then
|
||||||
|
printf '%s\n' "$3" >>"$BOOT/missing-deletes"
|
||||||
|
echo "could not delete label: HTTP 404: Not Found" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
bootstrap_labels
|
||||||
|
) 2>&1
|
||||||
|
}
|
||||||
|
missing_rc=0
|
||||||
|
missing_out="$(boot_missing_probe)" || missing_rc=$?
|
||||||
|
expect "an already-absent label does not abort the dispatch" 0 "$missing_rc"
|
||||||
|
expect "...every deletion still ran" "$RETIRED_WANT" "$(cat "$BOOT/missing-deletes")"
|
||||||
|
expect "...and each absence is logged at most once per name" \
|
||||||
|
1 "$(grep -c "retire: 'question'" <<<"$missing_out")"
|
||||||
|
|
||||||
|
# -- a refusal is tolerated: the blocker:drill-pending 403 shape, on a delete.
|
||||||
|
# The other five still go, the taxonomy still lands, the log says who.
|
||||||
|
boot_refusal_probe() {
|
||||||
|
(
|
||||||
|
REPO=owner/repo LABELS_CONF=.github/labels.conf
|
||||||
|
run() { "$@"; }
|
||||||
|
# shellcheck disable=SC2317 # reached through run's "$@", opaque to shellcheck
|
||||||
|
gh() {
|
||||||
|
if [ "$1" = label ] && [ "$2" = delete ]; then
|
||||||
|
if [ "$3" = question ]; then
|
||||||
|
echo "HTTP 403: Resource not accessible by integration" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
printf '%s\n' "$3" >>"$BOOT/refusal-deletes"
|
||||||
|
elif [ "$1" = label ] && [ "$2" = create ]; then
|
||||||
|
printf '%s\n' "$3" >>"$BOOT/refusal-creates"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
bootstrap_labels
|
||||||
|
) 2>&1
|
||||||
|
}
|
||||||
|
refusal_rc=0
|
||||||
|
refusal_out="$(boot_refusal_probe)" || refusal_rc=$?
|
||||||
|
expect "a refused delete does not abort the dispatch" 0 "$refusal_rc"
|
||||||
|
expect "...the other five still deleted" "duplicate
|
||||||
|
invalid
|
||||||
|
wontfix
|
||||||
|
help wanted
|
||||||
|
good first issue" "$(cat "$BOOT/refusal-deletes")"
|
||||||
|
expect "...the taxonomy still upserted whole" \
|
||||||
|
"$expected_upserts" "$(cat "$BOOT/refusal-creates")"
|
||||||
|
expect "...and the log names the refused label" \
|
||||||
|
yes "$(grep -q "retire: 'question'" <<<"$refusal_out" && echo yes || echo no)"
|
||||||
|
|
||||||
|
# -- DRY_RUN narrates the deletions like every other mutation, and does none
|
||||||
|
boot_dry_probe() {
|
||||||
|
(
|
||||||
|
REPO=owner/repo LABELS_CONF=.github/labels.conf DRY_RUN=1
|
||||||
|
# shellcheck disable=SC2317 # reached through run's "$@", opaque to shellcheck
|
||||||
|
gh() { printf '%s\n' "$*" >>"$BOOT/dry-real"; }
|
||||||
|
bootstrap_labels
|
||||||
|
)
|
||||||
|
}
|
||||||
|
dry_out="$(boot_dry_probe)"
|
||||||
|
expect "DRY_RUN narrates each deletion" \
|
||||||
|
6 "$(grep -c '^labels: DRY_RUN: gh label delete' <<<"$dry_out")"
|
||||||
|
expect "...and performs none" \
|
||||||
|
no "$(test -f "$BOOT/dry-real" && echo yes || echo no)"
|
||||||
|
|
||||||
|
# -- the case a sourced probe cannot see (#91): the script EXECUTED, set -e
|
||||||
|
# live, every delete failing the way the second dispatch of every repo
|
||||||
|
# fails. The run must end green with the taxonomy created whole.
|
||||||
|
EXEC="$RTMP/bootstrap-exec"
|
||||||
|
mkdir -p "$EXEC/stub"
|
||||||
|
cat >"$EXEC/stub/gh" <<'EOF'
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
case "$1 $2" in
|
||||||
|
"label delete")
|
||||||
|
printf 'delete %s\n' "$3" >>"$GH_RECORD"
|
||||||
|
echo "could not delete label: HTTP 404: Not Found (owner/repo)" >&2
|
||||||
|
exit 1 ;;
|
||||||
|
"label create")
|
||||||
|
printf 'create %s\n' "$3" >>"$GH_RECORD" ;;
|
||||||
|
esac
|
||||||
|
exit 0
|
||||||
|
EOF
|
||||||
|
chmod +x "$EXEC/stub/gh"
|
||||||
|
printf 'panel=bot-a bot-b bot-c\n' >"$EXEC/labels.conf"
|
||||||
|
|
||||||
|
exec_env() { # $1 = event name → the real script, executed under the PATH stub
|
||||||
|
: >"$EXEC/record"
|
||||||
|
env PATH="$EXEC/stub:$PATH" GH_RECORD="$EXEC/record" \
|
||||||
|
REPO=owner/repo LABELS_CONF="$EXEC/labels.conf" GITHUB_EVENT_NAME="$1" \
|
||||||
|
bash actions/labels-reconcile/labels-reconcile.sh
|
||||||
|
}
|
||||||
|
|
||||||
|
exec_rc=0
|
||||||
|
exec_out="$(exec_env workflow_dispatch 2>&1)" || exec_rc=$?
|
||||||
|
expect "an executed dispatch with all six absent completes green" 0 "$exec_rc"
|
||||||
|
expect "...reaching the end of the sweep" \
|
||||||
|
yes "$(grep -q 'reconciled.' <<<"$exec_out" && echo yes || echo no)"
|
||||||
|
expect "...having attempted all six deletions" \
|
||||||
|
6 "$(grep -c '^delete ' "$EXEC/record")"
|
||||||
|
expect "...and created the full taxonomy" \
|
||||||
|
"$(core_label_rows | cut -d'|' -f1)" \
|
||||||
|
"$(sed -n 's/^create //p' "$EXEC/record")"
|
||||||
|
|
||||||
|
# -- bootstrap is dispatch-only, deletes included: the cron and
|
||||||
|
# pull_request_target paths touch no label
|
||||||
|
for ev in schedule pull_request_target; do
|
||||||
|
ev_rc=0
|
||||||
|
exec_env "$ev" >/dev/null 2>&1 || ev_rc=$?
|
||||||
|
expect "the $ev path completes green" 0 "$ev_rc"
|
||||||
|
expect "...and deletes nothing" \
|
||||||
|
no "$(grep -q '^delete ' "$EXEC/record" && echo yes || echo no)"
|
||||||
|
done
|
||||||
|
|
||||||
printf 'labels-reconcile tests: %d passed, %d failed\n' "$pass" "$fail"
|
printf 'labels-reconcile tests: %d passed, %d failed\n' "$pass" "$fail"
|
||||||
[ "$fail" -eq 0 ]
|
[ "$fail" -eq 0 ]
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue