forked from heavy-duty/ceremony
test(labels): surface-level ruling contracts on both reconcilers
Issue side: invariant-1 composition, the reclaim clock stopping under a pending ruling (with a flag-free reclaim control), the stale heal, label churn invisible to the activity clock, the surface-level nudge reset, and no edit anywhere naming the flag. PR side: the wired nudge riding the stale sweep's activity computation, one nudge across two sweeps, #51's stale skip intact. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
a40e42544e
commit
c4079ea37c
2 changed files with 217 additions and 0 deletions
|
|
@ -149,4 +149,133 @@ check "completed epic is nudged" 0 "NUDGE" epic_decision "$epic_refs" $'CLOSED\n
|
|||
check "open epic child suppresses nudge" 0 "KEEP" epic_decision "$epic_refs" $'CLOSED\nOPEN'
|
||||
check "epic without parseable children is stable" 0 "KEEP" epic_decision "" ""
|
||||
|
||||
# Invariant 1 keeps ignoring the ruling flag (#50 D8): it composes with the
|
||||
# queue labels and is not one of them.
|
||||
check "claimed plus a pending ruling is a healthy issue" 0 "KEEP" \
|
||||
queue_decision <<< $'claimed\nneeds-ruling'
|
||||
check "a ruling flag alone is still invariant 1's violation" 0 "ADD_NEEDS_TRIAGE" \
|
||||
queue_decision <<< $'needs-ruling'
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# The ruling pass on the issue surface (#52), against a recording stub: the
|
||||
# reclaim clock stops under a pending ruling, an applied stale heals off,
|
||||
# label churn is not activity, the nudge resets on its own comment, and no
|
||||
# edit anywhere names the flag (#50 D9). The stub serves fixture JSON per
|
||||
# endpoint with the caller's --jq applied by real jq, appends posted comments
|
||||
# back into the fixture (a second sweep sees the first one's writes), and
|
||||
# records every label edit.
|
||||
# ---------------------------------------------------------------------------
|
||||
INOW=2000000000
|
||||
iso_at() { date -u -d "@$1" +%Y-%m-%dT%H:%M:%SZ; }
|
||||
|
||||
issue_stub_gh() {
|
||||
if [ "$1" = api ]; then
|
||||
shift
|
||||
local jqexpr="" endpoint="" file
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--jq) jqexpr="$2"; shift ;;
|
||||
-*) ;;
|
||||
*) [ -n "$endpoint" ] || endpoint="$1" ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
file="$TMP/$(printf '%s' "$endpoint" | tr '/' '_').json"
|
||||
[ -f "$file" ] || { printf '[]\n'; return 0; }
|
||||
if [ -n "$jqexpr" ]; then jq -r "$jqexpr" "$file"; else cat "$file"; fi
|
||||
elif [ "$1" = issue ] && [ "$2" = comment ]; then
|
||||
local n="$3" body="" file
|
||||
shift 3
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in --body) body="$2"; shift ;; esac
|
||||
shift
|
||||
done
|
||||
printf '%s\n----\n' "$body" >>"$TMP/posted-$n"
|
||||
file="$TMP/repos_owner_repo_issues_${n}_comments.json"
|
||||
[ -f "$file" ] || printf '[]\n' >"$file"
|
||||
jq --arg b "$body" --arg at "$(iso_at "$INOW")" \
|
||||
'. + [{"user":{"login":"sweep-bot"},"created_at":$at,"html_url":"https://x/posted","body":$b}]' \
|
||||
"$file" >"$file.tmp" && mv "$file.tmp" "$file"
|
||||
elif [ "$1" = issue ] && [ "$2" = edit ]; then
|
||||
printf '%s\n' "$*" >>"$TMP/issue-edits"
|
||||
fi
|
||||
}
|
||||
|
||||
issue_probe() { # $1 = issue number, $2 = labels → reconcile_issue's log lines
|
||||
(
|
||||
REPO=owner/repo NOW="$INOW"
|
||||
ISSUE_LABELS="$2"
|
||||
ISSUE_JSON="$(jq -n --arg at "$(iso_at $((INOW - 10 * 86400)))" \
|
||||
'{created_at: $at, assignees: [{login: "owner-bot"}], body: ""}')"
|
||||
OPEN_PR_ISSUES=""
|
||||
run() { "$@"; }
|
||||
gh() { issue_stub_gh "$@"; }
|
||||
reconcile_issue "$1" 2>&1
|
||||
)
|
||||
}
|
||||
|
||||
tfix() { printf '%s/repos_owner_repo_issues_%s_timeline.json' "$TMP" "$1"; }
|
||||
cfix() { printf '%s/repos_owner_repo_issues_%s_comments.json' "$TMP" "$1"; }
|
||||
|
||||
# -- the reclaim clock stops under a pending ruling (48h quiet, no PR) -------
|
||||
jq -n --arg l "$(iso_at $((INOW - 10 * 86400)))" \
|
||||
'[{"event":"labeled","label":{"name":"needs-ruling"},"actor":{"login":"setter"},"created_at":$l},
|
||||
{"event":"assigned","created_at":$l}]' >"$(tfix 21)"
|
||||
jq -n --arg at "$(iso_at $((INOW - 10 * 86400 - 60)))" \
|
||||
'[{"user":{"login":"setter"},"created_at":$at,"html_url":"https://x/esc21","body":"question, options, recommendation"}]' \
|
||||
>"$(cfix 21)"
|
||||
exempt="$(issue_probe 21 $'claimed\nneeds-ruling')"
|
||||
check "a 10-day-quiet claim under a ruling is not reclaimed" 1 "" \
|
||||
grep -q 'reclaimed' <<<"$exempt"
|
||||
check "...the same silence still nudges the pending ruling" 0 "" \
|
||||
grep -q 'ruling nudge' <<<"$exempt"
|
||||
# shellcheck disable=SC2016 # expansions belong to the isolated bash -c process
|
||||
check "...and the nudge went to the decider with the escalation linked" 0 "" \
|
||||
bash -c 'grep -qF "@danmt" "$1" && grep -qF "https://x/esc21" "$1"' _ "$TMP/posted-21"
|
||||
again="$(issue_probe 21 $'claimed\nneeds-ruling')"
|
||||
check "the sweep right after the nudge holds its silence" 1 "" \
|
||||
grep -q 'ruling nudge' <<<"$again"
|
||||
check "exactly one nudge across both sweeps" 0 "1" \
|
||||
grep -c -- '^----$' "$TMP/posted-21"
|
||||
|
||||
# -- control: the same silence without the flag is reclaimed -----------------
|
||||
jq -n --arg l "$(iso_at $((INOW - 10 * 86400)))" \
|
||||
'[{"event":"assigned","created_at":$l}]' >"$(tfix 22)"
|
||||
printf '[]\n' >"$(cfix 22)"
|
||||
control="$(issue_probe 22 claimed)"
|
||||
check "the flag-free control is reclaimed (the clock still runs elsewhere)" 0 "" \
|
||||
grep -q 'stale claim reclaimed -> ready' <<<"$control"
|
||||
|
||||
# -- an already-applied stale heals off, and no edit names the flag ----------
|
||||
jq -n --arg l "$(iso_at $((INOW - 3600)))" \
|
||||
'[{"event":"labeled","label":{"name":"needs-ruling"},"actor":{"login":"setter"},"created_at":$l}]' >"$(tfix 23)"
|
||||
jq -n --arg at "$(iso_at $((INOW - 3660)))" \
|
||||
'[{"user":{"login":"setter"},"created_at":$at,"html_url":"https://x/esc23","body":"question, options, recommendation"}]' \
|
||||
>"$(cfix 23)"
|
||||
healed="$(issue_probe 23 $'claimed\nneeds-ruling\nstale')"
|
||||
check "an applied stale comes off under a pending ruling" 0 "" \
|
||||
grep -q 'unstale (a ruling is pending)' <<<"$healed"
|
||||
check "...via an edit that removes exactly stale" 0 "" \
|
||||
grep -q -- '--remove-label stale' "$TMP/issue-edits"
|
||||
check "no issue edit across every probe names the ruling flag (#50 D9)" 1 "" \
|
||||
grep -q 'needs-ruling' "$TMP/issue-edits"
|
||||
|
||||
# -- label churn is not activity: the nudge clock reads comments, not labels --
|
||||
jq -n --arg flag "$(iso_at $((INOW - 8 * 86400)))" \
|
||||
--arg churn "$(iso_at $((INOW - 2 * 86400)))" \
|
||||
--arg assigned "$(iso_at $((INOW - 9 * 86400)))" \
|
||||
'[{"event":"labeled","label":{"name":"needs-ruling"},"actor":{"login":"setter"},"created_at":$flag},
|
||||
{"event":"labeled","label":{"name":"priority"},"actor":{"login":"anyone"},"created_at":$churn},
|
||||
{"event":"assigned","created_at":$assigned}]' >"$(tfix 24)"
|
||||
jq -n --arg at "$(iso_at $((INOW - 8 * 86400 - 60)))" \
|
||||
'[{"user":{"login":"setter"},"created_at":$at,"html_url":"https://x/esc24","body":"question, options, recommendation"}]' \
|
||||
>"$(cfix 24)"
|
||||
churn_last="$( (REPO=owner/repo; gh() { issue_stub_gh "$@"; }
|
||||
last_issue_activity 24 "$(iso_at $((INOW - 10 * 86400)))") )"
|
||||
check "last activity ignores the 2-day-old label churn" 0 "" \
|
||||
test "$churn_last" = "$((INOW - 8 * 86400 - 60))"
|
||||
churned="$(issue_probe 24 $'claimed\nneeds-ruling')"
|
||||
check "8 real-quiet days nudge through a 2-day-old label churn" 0 "" \
|
||||
grep -q 'ruling nudge' <<<"$churned"
|
||||
|
||||
summary
|
||||
|
|
|
|||
|
|
@ -489,5 +489,93 @@ unstale="$(ruling_probe "$(printf 'needs-ruling\nstale')")"
|
|||
expect "...and an already-applied stale comes off" \
|
||||
yes "$(grep -q 'unstale' <<<"$unstale" && echo yes || echo no)"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# The ruling pass on the PR surface (#52): the bare-flag check and the 7-day
|
||||
# nudge ride reconcile_pr behind the flag, on the same real-activity
|
||||
# computation the stale sweep reads. A recording stub serves the API facts:
|
||||
# fixture JSON per endpoint (with the caller's --jq applied by real jq),
|
||||
# posted comments appended back into the fixture so a second sweep sees the
|
||||
# first one's writes, and every label edit recorded.
|
||||
# ---------------------------------------------------------------------------
|
||||
RTMP="$(mktemp -d)"
|
||||
trap 'rm -rf "$RTMP"' EXIT
|
||||
iso_at() { date -u -d "@$1" +%Y-%m-%dT%H:%M:%SZ; }
|
||||
RNOW=2000000000
|
||||
|
||||
ruling_sweep_probe() { # $1 = the PR's labels → reconcile_pr's log lines
|
||||
(
|
||||
REPO_LABELS="$(printf 'state:addressing\nstate:needs-human\nmerge-next\nstale\nneeds-ruling')"
|
||||
REPO=owner/repo NOW="$RNOW"
|
||||
LABELS="$1"
|
||||
DRAFT=false HEAD_SHA=head1 REQUESTED=""
|
||||
# Approvals submitted 8 days ago — the newest real activity anywhere.
|
||||
REVIEWS_JSON="$(reviews \
|
||||
"$(rev "$BOT1" APPROVED head1 "" "$(iso_at $((RNOW - 8 * 86400)))")" \
|
||||
"$(rev "$BOT2" APPROVED head1 "" "$(iso_at $((RNOW - 8 * 86400)))")" \
|
||||
"$(rev "$BOT3" APPROVED head1 "" "$(iso_at $((RNOW - 8 * 86400)))")")"
|
||||
MERGEABLE=MERGEABLE CHECKS=SUCCESS
|
||||
PR_JSON="$(jq -n --arg at "$(iso_at $((RNOW - 10 * 86400)))" '{created_at: $at}')"
|
||||
run() { "$@"; } # mutations reach the stub and are recorded, not swallowed
|
||||
gh() {
|
||||
if [ "$1" = api ]; then
|
||||
shift
|
||||
local jqexpr="" endpoint="" file
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--jq) jqexpr="$2"; shift ;;
|
||||
-*) ;;
|
||||
*) [ -n "$endpoint" ] || endpoint="$1" ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
file="$RTMP/$(printf '%s' "$endpoint" | tr '/' '_').json"
|
||||
[ -f "$file" ] || { printf '[]\n'; return 0; }
|
||||
if [ -n "$jqexpr" ]; then jq -r "$jqexpr" "$file"; else cat "$file"; fi
|
||||
elif [ "$1" = issue ] && [ "$2" = comment ]; then
|
||||
local n="$3" body="" file
|
||||
shift 3
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in --body) body="$2"; shift ;; esac
|
||||
shift
|
||||
done
|
||||
printf '%s\n----\n' "$body" >>"$RTMP/posted-$n"
|
||||
file="$RTMP/repos_owner_repo_issues_${n}_comments.json"
|
||||
[ -f "$file" ] || printf '[]\n' >"$file"
|
||||
jq --arg b "$body" --arg at "$(iso_at "$RNOW")" \
|
||||
'. + [{"user":{"login":"sweep-bot"},"created_at":$at,"html_url":"https://x/posted","body":$b}]' \
|
||||
"$file" >"$file.tmp" && mv "$file.tmp" "$file"
|
||||
elif [ "$1" = issue ] && [ "$2" = edit ]; then
|
||||
printf '%s\n' "$*" >>"$RTMP/edits"
|
||||
fi
|
||||
}
|
||||
reconcile_pr 77 2>&1
|
||||
)
|
||||
}
|
||||
|
||||
# The flag went up 8 days ago with its escalation posted seconds earlier.
|
||||
jq -n --arg at "$(iso_at $((RNOW - 8 * 86400)))" \
|
||||
'[{"event":"labeled","label":{"name":"needs-ruling"},"actor":{"login":"setter"},"created_at":$at}]' \
|
||||
>"$RTMP/repos_owner_repo_issues_77_timeline.json"
|
||||
jq -n --arg at "$(iso_at $((RNOW - 8 * 86400 - 60)))" \
|
||||
'[{"user":{"login":"setter"},"created_at":$at,"html_url":"https://x/esc77","body":"question, options, recommendation"}]' \
|
||||
>"$RTMP/repos_owner_repo_issues_77_comments.json"
|
||||
|
||||
wired="$(ruling_sweep_probe "needs-ruling")"
|
||||
expect "8 quiet days under a ruling nudges on the PR surface" \
|
||||
yes "$(grep -q 'ruling nudge' <<<"$wired" && echo yes || echo no)"
|
||||
expect "...while the quiet stays stale-free (#51's skip intact)" \
|
||||
no "$(grep -q 'stale (' <<<"$wired" && echo yes || echo no)"
|
||||
expect "...the accompanied flag is not called bare" \
|
||||
no "$(grep -q 'ruling flag is bare' <<<"$wired" && echo yes || echo no)"
|
||||
expect "the nudge addressed the decider and linked the escalation" \
|
||||
yes "$(grep -qF '@danmt' "$RTMP/posted-77" && grep -qF 'https://x/esc77' "$RTMP/posted-77" && echo yes || echo no)"
|
||||
again="$(ruling_sweep_probe "needs-ruling")"
|
||||
expect "the sweep right after the nudge holds its silence — the comment reset the window" \
|
||||
no "$(grep -q 'ruling nudge' <<<"$again" && echo yes || echo no)"
|
||||
expect "exactly one nudge across both sweeps" \
|
||||
1 "$(grep -c '^----$' "$RTMP/posted-77")"
|
||||
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)"
|
||||
|
||||
printf 'labels-reconcile tests: %d passed, %d failed\n' "$pass" "$fail"
|
||||
[ "$fail" -eq 0 ]
|
||||
|
|
|
|||
Loading…
Reference in a new issue