actions/* + lib/* + CHANGELOG — merge upstream 0.6.0 onto the forge tree, and port every gh call site it brought (#198) #204
8 changed files with 229 additions and 9 deletions
|
|
@ -212,11 +212,12 @@ triage bug, and the move is to say so on the issue, not to guess.
|
||||||
repo-specific facts such as the panel roster live in that repo's own
|
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.)
|
CONTRIBUTING; the shared flow lives here and is not restated there.)
|
||||||
|
|
||||||
1. Mark ready-for-review; request **the whole panel**. The panel is the roster
|
1. Mark ready-for-review; request **the whole panel**. The panel is the PR
|
||||||
of the repo the **PR** is in, minus you — never the roster of the repo the
|
repo's `panel[<your-login>]=` line if it defines one, else its `panel=`
|
||||||
issue is in. The PR repo's `.github/labels.conf` `panel=` line is the
|
line; minus the author in either case (#224) — and never the roster of
|
||||||
|
the repo the issue is in. The PR repo's `.github/labels.conf` is the
|
||||||
machine's answer; its CONTRIBUTING roster is the human-readable answer,
|
machine's answer; its CONTRIBUTING roster is the human-readable answer,
|
||||||
and `panel=` governs if they disagree because that is what the state
|
and the conf governs if they disagree because that is what the state
|
||||||
machine reads. If the PR repo names no roster, ask triage on the
|
machine reads. If the PR repo names no roster, ask triage on the
|
||||||
authorizing issue before marking ready-for-review; do not guess. You may
|
authorizing issue before marking ready-for-review; do not guess. You may
|
||||||
request an off-panel reviewer, but say that their verdict is advisory and
|
request an off-panel reviewer, but say that their verdict is advisory and
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,9 @@ saw Y" outranks one that says "this looks like it might".
|
||||||
wait for the repo to appear on a list: review is reversible
|
wait for the repo to appear on a list: review is reversible
|
||||||
read-plus-comment work, and the requester already decided it should happen.
|
read-plus-comment work, and the requester already decided it should happen.
|
||||||
- **A request is authorization, not panel membership.** Convergence is
|
- **A request is authorization, not panel membership.** Convergence is
|
||||||
measured against the target repo's `panel=` roster minus the author. If you
|
measured against the target repo's `panel[<author>]=` line if its
|
||||||
|
`labels.conf` defines one for the PR author, else its `panel=` line; minus
|
||||||
|
the author in either case (#224). If you
|
||||||
are requested off-panel, post the verdict anyway and say in its body that
|
are requested off-panel, post the verdict anyway and say in its body that
|
||||||
it is advisory; neither your silence nor your request-changes is a gate the
|
it is advisory; neither your silence nor your request-changes is a gate the
|
||||||
reconciler enforces. The nine-hour wait for kimi's off-panel verdict on
|
reconciler enforces. The nine-hour wait for kimi's off-panel verdict on
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,12 @@ fi
|
||||||
|
|
||||||
HUMAN="${HUMAN_REVIEWER:-danmt}"
|
HUMAN="${HUMAN_REVIEWER:-danmt}"
|
||||||
BOTS=()
|
BOTS=()
|
||||||
|
# Per-author panels (#224): parallel arrays because the conf is tiny and an
|
||||||
|
# associative array buys nothing but a bash-4 dependency statement. One entry
|
||||||
|
# per panel[<login>]= row — PANEL_AUTHORS holds the login, PANEL_ROWS the
|
||||||
|
# space-joined reviewer set at the same index.
|
||||||
|
PANEL_AUTHORS=()
|
||||||
|
PANEL_ROWS=()
|
||||||
REQUIRED_BOTS=()
|
REQUIRED_BOTS=()
|
||||||
STATES=(state:building state:bots-reviewing state:addressing state:needs-human)
|
STATES=(state:building state:bots-reviewing state:addressing state:needs-human)
|
||||||
BLOCKERS=(blocker:conflict blocker:ci-red blocker:unrequested)
|
BLOCKERS=(blocker:conflict blocker:ci-red blocker:unrequested)
|
||||||
|
|
@ -127,8 +133,16 @@ load_config() { # $1 = consumer labels.conf; panel is mandatory, scopes optional
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
BOTS=()
|
BOTS=()
|
||||||
|
PANEL_AUTHORS=()
|
||||||
|
PANEL_ROWS=()
|
||||||
|
# shellcheck disable=SC2094 # parse_panel_author_row takes $conf for its
|
||||||
|
# error messages only — nothing in this loop writes the file it reads
|
||||||
while IFS= read -r line || [ -n "$line" ]; do
|
while IFS= read -r line || [ -n "$line" ]; do
|
||||||
[ -n "$line" ] || continue
|
[ -n "$line" ] || continue
|
||||||
|
# The panel[ prefix is matched QUOTED (#224 D7): in a case pattern an
|
||||||
|
# unquoted panel[abc]=* is a bracket expression that matches panela=…,
|
||||||
|
# panelb=…, panelc=… — silently rerouting ordinary settings. The
|
||||||
|
# panela= tripwire in test/labels.test.sh goes red if this regresses.
|
||||||
case "$line" in
|
case "$line" in
|
||||||
panel=*)
|
panel=*)
|
||||||
[ "$panel_seen" = false ] || {
|
[ "$panel_seen" = false ] || {
|
||||||
|
|
@ -142,6 +156,7 @@ load_config() { # $1 = consumer labels.conf; panel is mandatory, scopes optional
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
;;
|
;;
|
||||||
|
"panel["*) parse_panel_author_row "$line" "$conf" || return ;;
|
||||||
triage-actors=*) ;;
|
triage-actors=*) ;;
|
||||||
*) parse_label_row "$line" >/dev/null || return ;;
|
*) parse_label_row "$line" >/dev/null || return ;;
|
||||||
esac
|
esac
|
||||||
|
|
@ -152,6 +167,43 @@ load_config() { # $1 = consumer labels.conf; panel is mandatory, scopes optional
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
parse_panel_author_row() { # panel[<login>]=<space-separated logins> (#224)
|
||||||
|
# Every failure here is a hard one that names the offending line (D3): a
|
||||||
|
# conf error takes the whole board down, and the run log is the only place
|
||||||
|
# the operator can read why. A malformed bracket is refused AS a bracket
|
||||||
|
# (D4) — falling through to parse_label_row would report it as a
|
||||||
|
# "malformed label row", the misleading diagnostic #224 was filed over.
|
||||||
|
local line="$1" conf="$2" login rest existing
|
||||||
|
case "$line" in
|
||||||
|
"panel["*"]="*) ;;
|
||||||
|
*)
|
||||||
|
echo "labels: malformed panel[<login>]= row (expected panel[<login>]=<reviewers>): $line in $conf" >&2
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
login="${line#panel[}"
|
||||||
|
login="${login%%]=*}"
|
||||||
|
[ -n "$login" ] || {
|
||||||
|
echo "labels: empty login in panel row: $line in $conf" >&2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
for existing in ${PANEL_AUTHORS[@]+"${PANEL_AUTHORS[@]}"}; do
|
||||||
|
[ "$existing" != "$login" ] || {
|
||||||
|
echo "labels: duplicate panel[$login]= row in $conf: $line" >&2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
done
|
||||||
|
local -a row=()
|
||||||
|
rest="${line#*]=}"
|
||||||
|
read -r -a row <<<"$rest"
|
||||||
|
[ "${#row[@]}" -gt 0 ] || {
|
||||||
|
echo "labels: panel[$login]= must name at least one reviewer in $conf: $line" >&2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
PANEL_AUTHORS+=("$login")
|
||||||
|
PANEL_ROWS+=("${row[*]}")
|
||||||
|
}
|
||||||
|
|
||||||
parse_label_row() { # exact name|color|description; pipes in descriptions are refused
|
parse_label_row() { # exact name|color|description; pipes in descriptions are refused
|
||||||
local line="$1" name color desc extra
|
local line="$1" name color desc extra
|
||||||
IFS='|' read -r name color desc extra <<<"$line"
|
IFS='|' read -r name color desc extra <<<"$line"
|
||||||
|
|
@ -167,15 +219,38 @@ configured_label_rows() { # validated scope rows, excluding the panel setting
|
||||||
[ -f "$conf" ] || return 0
|
[ -f "$conf" ] || return 0
|
||||||
while IFS= read -r line || [ -n "$line" ]; do
|
while IFS= read -r line || [ -n "$line" ]; do
|
||||||
[ -n "$line" ] || continue
|
[ -n "$line" ] || continue
|
||||||
case "$line" in panel=* | triage-actors=*) continue ;; esac
|
# "panel["* quoted for the same D7 reason as load_config's case; skipping
|
||||||
|
# the bracketed rows (D5) keeps a dispatch bootstrap from trying to
|
||||||
|
# create a label named panel[<login>].
|
||||||
|
case "$line" in panel=* | "panel["* | triage-actors=*) continue ;; esac
|
||||||
parse_label_row "$line" || return
|
parse_label_row "$line" || return
|
||||||
done <"$conf"
|
done <"$conf"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
panel_for_author() { # $1 = author → the effective panel, space-joined (#224 D2)
|
||||||
|
# THE resolution point: the author's panel[<login>]= row when the conf
|
||||||
|
# defines one, the base panel= otherwise. Everything that computes a
|
||||||
|
# required set goes through here, because two places computing the panel
|
||||||
|
# is how the engine and the reconciler came to disagree in the first place.
|
||||||
|
local author="$1" i
|
||||||
|
for i in ${PANEL_AUTHORS[@]+"${!PANEL_AUTHORS[@]}"}; do
|
||||||
|
if [ "${PANEL_AUTHORS[i]}" = "$author" ]; then
|
||||||
|
printf '%s\n' "${PANEL_ROWS[i]}"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
printf '%s\n' "${BOTS[*]}"
|
||||||
|
}
|
||||||
|
|
||||||
set_required_bots() { # the PR author is recused by construction
|
set_required_bots() { # the PR author is recused by construction
|
||||||
|
# Minus-the-author applies to WHICHEVER set panel_for_author returns (#224
|
||||||
|
# D2's safety net): an author who mistakenly appears inside its own
|
||||||
|
# bracketed row is still recused.
|
||||||
local author="$1" bot
|
local author="$1" bot
|
||||||
|
local -a effective=()
|
||||||
|
read -r -a effective <<<"$(panel_for_author "$author")"
|
||||||
REQUIRED_BOTS=()
|
REQUIRED_BOTS=()
|
||||||
for bot in "${BOTS[@]}"; do
|
for bot in ${effective[@]+"${effective[@]}"}; do
|
||||||
[ "$bot" = "$author" ] || REQUIRED_BOTS+=("$bot")
|
[ "$bot" = "$author" ] || REQUIRED_BOTS+=("$bot")
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
|
||||||
6
changelog.d/224.md
Normal file
6
changelog.d/224.md
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- `labels.conf` accepts optional `panel[<login>]=` rows: the required set for
|
||||||
|
a PR authored by that login is the row minus the author; other authors keep
|
||||||
|
`panel=`. Consumers gain the row at their next pin bump — adding it before
|
||||||
|
that bump is a parse failure that takes the label board down (#224).
|
||||||
|
|
@ -482,10 +482,12 @@ 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.
|
by pinning `0.3.0` or later, never through mixed refs.
|
||||||
|
|
||||||
`.github/labels.conf` has one mandatory panel setting, one mandatory
|
`.github/labels.conf` has one mandatory panel setting, one mandatory
|
||||||
`triage-actors` setting, and then zero or more scope rows:
|
`triage-actors` setting, zero or more optional per-author panel rows, and
|
||||||
|
then zero or more scope rows:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
panel=claude-bot example-codex-bot example-grok-bot
|
panel=claude-bot example-codex-bot example-grok-bot
|
||||||
|
panel[example-builder]=example-codex-bot example-grok-bot
|
||||||
triage-actors=example-triage-bot
|
triage-actors=example-triage-bot
|
||||||
scope:cli|C5DEF5|The command-line surface
|
scope:cli|C5DEF5|The command-line surface
|
||||||
scope:docs|C5DEF5|Documentation
|
scope:docs|C5DEF5|Documentation
|
||||||
|
|
@ -497,11 +499,24 @@ rows only; adding `triage-actors=` is a parse failure, not an ignored setting.
|
||||||
Add it at the same pin bump as the `issues:` trigger — `0.2.0` or later —
|
Add it at the same pin bump as the `issues:` trigger — `0.2.0` or later —
|
||||||
never before it and never through mixed refs.
|
never before it and never through mixed refs.
|
||||||
|
|
||||||
|
The optional `panel[<login>]=` rows are **unreleased** (#224). A row names
|
||||||
|
the effective panel for PRs authored by exactly that login — the reconciler
|
||||||
|
computes that PR's required set from the row, minus the author as always —
|
||||||
|
and every other author keeps the base `panel=`, which stays mandatory. The
|
||||||
|
panel is configured or it is the base one: ceremony never infers a reviewer
|
||||||
|
set from the model behind a login. On any earlier pin a bracketed row is a
|
||||||
|
**parse failure, not an ignored setting** — the same shape `triage-actors=`
|
||||||
|
bought at `0.2.0`, but harsher in practice: the reconcile job dies on every
|
||||||
|
PR event and every sweep until the row is removed, so the whole label board
|
||||||
|
goes down. Add the row only at or after the pin bump that carries it, never
|
||||||
|
before it and never through mixed refs.
|
||||||
|
|
||||||
Both actor lists are whitespace-separated. `triage-actors` names the identities
|
Both actor lists are whitespace-separated. `triage-actors` names the identities
|
||||||
allowed to mint issues without the sweep applying `needs-triage`. Label rows use exactly
|
allowed to mint issues without the sweep applying `needs-triage`. Label rows use exactly
|
||||||
`name|color|description`; blank lines are ignored and extra pipes are refused.
|
`name|color|description`; blank lines are ignored and extra pipes are refused.
|
||||||
There are no comment lines: every non-blank line must be the `panel=`
|
There are no comment lines: every non-blank line must be the `panel=`
|
||||||
setting, the `triage-actors=` setting, or a label row, so `#`-prefixed prose
|
setting, a `panel[<login>]=` row, the `triage-actors=` setting, or a label
|
||||||
|
row, so `#`-prefixed prose
|
||||||
is a parse failure, not a comment (rig #13's conversion found this the hard
|
is a parse failure, not a comment (rig #13's conversion found this the hard
|
||||||
way — keep the file data only).
|
way — keep the file data only).
|
||||||
Core state, blocker, work-queue, and release labels come from ceremony. Scope
|
Core state, blocker, work-queue, and release labels come from ceremony. Scope
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,16 @@ printf '%s\n' 'panel=one' >"$TMP/missing.conf"
|
||||||
check "missing triage actors fails loudly" 1 "missing triage-actors=" load_issueflow_config "$TMP/missing.conf"
|
check "missing triage actors fails loudly" 1 "missing triage-actors=" load_issueflow_config "$TMP/missing.conf"
|
||||||
printf '%s\n' 'triage-actors=one' 'triage-actors=two' >"$TMP/duplicate.conf"
|
printf '%s\n' 'triage-actors=one' 'triage-actors=two' >"$TMP/duplicate.conf"
|
||||||
check "duplicate triage actors fails loudly" 1 "duplicate triage-actors" load_issueflow_config "$TMP/duplicate.conf"
|
check "duplicate triage actors fails loudly" 1 "duplicate triage-actors" load_issueflow_config "$TMP/duplicate.conf"
|
||||||
|
# A panel[<login>]= row (#224 D8) must not take the issue board down: this
|
||||||
|
# loader ignores every line that is not triage-actors=. That tolerance was
|
||||||
|
# incidental; this row makes it deliberate, so a future tightening cannot
|
||||||
|
# break the sweep as a side effect.
|
||||||
|
printf '%s\n' \
|
||||||
|
'panel=one two' \
|
||||||
|
'panel[builder-z]=two' \
|
||||||
|
'triage-actors=triage-one' >"$TMP/bracketed.conf"
|
||||||
|
check "a per-author panel row is tolerated by the issue-flow loader" 0 "" \
|
||||||
|
load_issueflow_config "$TMP/bracketed.conf"
|
||||||
|
|
||||||
# The dogfood caller and reusable workflow must expose the same runtime facts
|
# The dogfood caller and reusable workflow must expose the same runtime facts
|
||||||
# as the documented consumer stub. Static pins catch YAML blocks drifting to
|
# as the documented consumer stub. Static pins catch YAML blocks drifting to
|
||||||
|
|
|
||||||
|
|
@ -986,5 +986,48 @@ for ev in schedule pull_request_target; do
|
||||||
expect "...and deletes nothing" \
|
expect "...and deletes nothing" \
|
||||||
no "$(grep -q '^delete ' "$EXEC/record" && echo yes || echo no)"
|
no "$(grep -q '^delete ' "$EXEC/record" && echo yes || echo no)"
|
||||||
done
|
done
|
||||||
|
# -- per-author panels (#224): the required set flows from the one ----------
|
||||||
|
# resolution point, and convergence counts the effective set — never the
|
||||||
|
# base panel beside a reduced request set (the must-fail the issue names)
|
||||||
|
PANEL_DIR="$RTMP/panel-author"
|
||||||
|
mkdir -p "$PANEL_DIR"
|
||||||
|
printf '%s\n' 'panel=bot-a bot-b bot-c bot-d' \
|
||||||
|
'panel[builder-z]=bot-b bot-c bot-d' >"$PANEL_DIR/labels.conf"
|
||||||
|
load_config "$PANEL_DIR/labels.conf"
|
||||||
|
set_required_bots builder-z
|
||||||
|
expect "a bracketed author requires exactly its configured row" \
|
||||||
|
"bot-b bot-c bot-d" "${REQUIRED_BOTS[*]}"
|
||||||
|
set_required_bots bot-a
|
||||||
|
expect "an unbracketed author beside a bracketed row requires panel minus self" \
|
||||||
|
"bot-b bot-c bot-d" "${REQUIRED_BOTS[*]}"
|
||||||
|
set_required_bots outsider
|
||||||
|
expect "an unbracketed non-panelist author requires the whole base panel" \
|
||||||
|
"bot-a bot-b bot-c bot-d" "${REQUIRED_BOTS[*]}"
|
||||||
|
|
||||||
|
# The engine shape: the three configured reviewers approving the head IS the
|
||||||
|
# whole round for a bracketed author — bot-a's absent verdict must not hold
|
||||||
|
# convergence, or the request side and the convergence side disagree forever
|
||||||
|
# (the deadlock crew#285 was filed over).
|
||||||
|
set_required_bots builder-z
|
||||||
|
THREE_APPROVE="$(reviews \
|
||||||
|
"$(rev bot-b APPROVED head1 ok 2026-08-02T10:00:00Z)" \
|
||||||
|
"$(rev bot-c APPROVED head1 ok 2026-08-02T10:01:00Z)" \
|
||||||
|
"$(rev bot-d APPROVED head1 ok 2026-08-02T10:02:00Z)")"
|
||||||
|
DRAFT=false HEAD_SHA=head1 REQUESTED="" REVIEWS_JSON="$THREE_APPROVE" \
|
||||||
|
MERGEABLE=MERGEABLE CHECKS=SUCCESS LABELS=""
|
||||||
|
expect "the bracketed author's round converges on its three approvals" \
|
||||||
|
state:needs-human "$(decide_state)"
|
||||||
|
expect "...with no blocker standing" "" "$(blockers)"
|
||||||
|
# The control: the same three approvals under the base panel are NOT a full
|
||||||
|
# round — the fourth verdict is owed and unrequested. If this pair ever
|
||||||
|
# reads the same, one side stopped consulting the resolution point.
|
||||||
|
printf '%s\n' 'panel=bot-a bot-b bot-c bot-d' >"$PANEL_DIR/labels.conf"
|
||||||
|
load_config "$PANEL_DIR/labels.conf"
|
||||||
|
set_required_bots builder-z
|
||||||
|
expect "without the row the same approvals leave the round incomplete" \
|
||||||
|
state:addressing "$(decide_state)"
|
||||||
|
expect "...and the owed, unasked verdict is named" \
|
||||||
|
blocker:unrequested "$(blockers)"
|
||||||
|
|
||||||
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 ]
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,74 @@ load_config "$TMP/good.conf"
|
||||||
set_required_bots two
|
set_required_bots two
|
||||||
check "PR author is recused from the required panel" 0 "one three" printf '%s\n' "${REQUIRED_BOTS[*]}"
|
check "PR author is recused from the required panel" 0 "one three" printf '%s\n' "${REQUIRED_BOTS[*]}"
|
||||||
|
|
||||||
|
# -- per-author panel rows (#224): the config-parse matrix -------------------
|
||||||
|
# required_for loads a conf fresh in a subshell and prints the required set
|
||||||
|
# behind a RESULT: anchor, so substring matching cannot confuse "b c" with
|
||||||
|
# "a b c".
|
||||||
|
# shellcheck disable=SC2016 # expansion belongs to the nested bash
|
||||||
|
required_for() { # $1 = conf, $2 = author → RESULT:<required set>
|
||||||
|
bash -c 'source "$1"; load_config "$2" || exit 1
|
||||||
|
set_required_bots "$3"; printf "RESULT:%s\n" "${REQUIRED_BOTS[*]}"' _ \
|
||||||
|
"$ROOT/actions/labels-reconcile/labels-reconcile.sh" "$1" "$2"
|
||||||
|
}
|
||||||
|
printf '%s\n' 'panel=a b c' >"$TMP/plain.conf"
|
||||||
|
check "no bracketed row: panelist author gets panel minus self" 0 "RESULT:b c" \
|
||||||
|
required_for "$TMP/plain.conf" a
|
||||||
|
check "no bracketed row: outside author gets the whole panel" 0 "RESULT:a b c" \
|
||||||
|
required_for "$TMP/plain.conf" z
|
||||||
|
printf '%s\n' 'panel=a b c' 'panel[z]=b c' >"$TMP/author.conf"
|
||||||
|
check "bracketed author gets exactly its row" 0 "RESULT:b c" \
|
||||||
|
required_for "$TMP/author.conf" z
|
||||||
|
check "unbracketed author beside a bracketed row is unchanged" 0 "RESULT:b c" \
|
||||||
|
required_for "$TMP/author.conf" a
|
||||||
|
printf '%s\n' 'panel[z]=b c' 'panel=a b c' >"$TMP/reversed.conf"
|
||||||
|
check "row order is irrelevant: bracketed row before panel=" 0 "RESULT:b c" \
|
||||||
|
required_for "$TMP/reversed.conf" z
|
||||||
|
check "row order is irrelevant for the base panel too" 0 "RESULT:b c" \
|
||||||
|
required_for "$TMP/reversed.conf" a
|
||||||
|
printf '%s\n' 'panel=a b c' 'panel[a]=a b' >"$TMP/self.conf"
|
||||||
|
check "author inside its own bracketed row is still recused" 0 "RESULT:b" \
|
||||||
|
required_for "$TMP/self.conf" a
|
||||||
|
# shellcheck disable=SC2016 # expansion belongs to the nested bash
|
||||||
|
check "base panel is byte-identical with the bracketed rows deleted" 0 "SAME" \
|
||||||
|
bash -c 'source "$1"; load_config "$2"; with="${BOTS[*]}"
|
||||||
|
load_config "$3"; [ "$with" = "${BOTS[*]}" ] && echo SAME' _ \
|
||||||
|
"$ROOT/actions/labels-reconcile/labels-reconcile.sh" \
|
||||||
|
"$TMP/author.conf" "$TMP/plain.conf"
|
||||||
|
printf '%s\n' 'panel=a b c' 'panel[z]=b' 'panel[z]=c' >"$TMP/dup-author.conf"
|
||||||
|
check "duplicate rows for one login fail naming the line" 1 \
|
||||||
|
"duplicate panel[z]= row" load_config "$TMP/dup-author.conf"
|
||||||
|
printf '%s\n' 'panel=a b c' 'panel[z]=' >"$TMP/empty-set.conf"
|
||||||
|
check "a bracketed row naming zero reviewers fails loudly" 1 \
|
||||||
|
"panel[z]= must name at least one reviewer" load_config "$TMP/empty-set.conf"
|
||||||
|
printf '%s\n' 'panel=a b c' 'panel[]=b c' >"$TMP/empty-login.conf"
|
||||||
|
check "an empty login fails loudly" 1 "empty login in panel row" \
|
||||||
|
load_config "$TMP/empty-login.conf"
|
||||||
|
printf '%s\n' 'panel=a b c' 'panel[z=b c' >"$TMP/broken-bracket.conf"
|
||||||
|
check "a malformed bracket is refused as a bracket (D4)" 1 \
|
||||||
|
"malformed panel[<login>]= row" load_config "$TMP/broken-bracket.conf"
|
||||||
|
# shellcheck disable=SC2016 # expansion belongs to the nested bash
|
||||||
|
check "...and never as a label row" 1 "" bash -c \
|
||||||
|
'source "$1"; load_config "$2" 2>&1 | grep -F "malformed label row"' _ \
|
||||||
|
"$ROOT/actions/labels-reconcile/labels-reconcile.sh" "$TMP/broken-bracket.conf"
|
||||||
|
# The D7 tripwire: in a case pattern an unquoted panel[abc]=* is a bracket
|
||||||
|
# expression matching panela=… — this row going green as a panel setting is
|
||||||
|
# exactly the silent mis-route the quoted prefix exists to prevent.
|
||||||
|
printf '%s\n' 'panel=a b c' 'panela=b c' >"$TMP/glob-guard.conf"
|
||||||
|
check "panela= is still a malformed label row, never a panel setting (D7)" 1 \
|
||||||
|
"malformed label row" load_config "$TMP/glob-guard.conf"
|
||||||
|
printf '%s\n' 'panel[z]=b c' >"$TMP/bracket-only.conf"
|
||||||
|
check "a bracketed row does not satisfy the mandatory panel=" 1 \
|
||||||
|
"missing panel= line" load_config "$TMP/bracket-only.conf"
|
||||||
|
printf '%s\n' 'panel=a b c' 'panel[z]=b c' \
|
||||||
|
'scope:one|C5DEF5|First scope' >"$TMP/mixed.conf"
|
||||||
|
check "configured_label_rows returns the scope rows alone" 0 \
|
||||||
|
"scope:one|C5DEF5|First scope" configured_label_rows "$TMP/mixed.conf"
|
||||||
|
# shellcheck disable=SC2016 # expansion belongs to the nested bash
|
||||||
|
check "no panel[...] row reaches the bootstrap" 1 "" bash -c \
|
||||||
|
'source "$1"; configured_label_rows "$2" | grep -F "panel["' _ \
|
||||||
|
"$ROOT/actions/labels-reconcile/labels-reconcile.sh" "$TMP/mixed.conf"
|
||||||
|
|
||||||
# LABELS.md is mirrored byte-identically into every governed repo, so any
|
# LABELS.md is mirrored byte-identically into every governed repo, so any
|
||||||
# scope enumeration it carries is true at home and false everywhere else —
|
# scope enumeration it carries is true at home and false everywhere else —
|
||||||
# 14 of 16 vendored rows were false across the family when this fired (#104).
|
# 14 of 16 vendored rows were false across the family when this fired (#104).
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue