#!/usr/bin/env bash set -u ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" # shellcheck source=test/harness.sh source "$ROOT/test/harness.sh" # shellcheck source=lib/ruling.sh source "$ROOT/lib/ruling.sh" TMP="$(mktemp -d)" trap 'rm -rf "$TMP"' EXIT # --------------------------------------------------------------------------- # Pure decisions. Epochs are arbitrary fixed numbers — no wall clock anywhere. # --------------------------------------------------------------------------- check "needs-ruling is stale-exempt" 0 "EXEMPT" ruling_stale_exempt <<< $'claimed\nneeds-ruling' check "a flag-free issue sweeps normally" 0 "SWEEP" ruling_stale_exempt <<< $'claimed' L=100000 # the labeled event, in every window case below check "escalation 14 minutes before the flag accompanies it" 0 "ACCOMPANIED" \ ruling_bare_decision setter "$L" <<<"setter $((L - 840))" check "escalation exactly 15 minutes before still accompanies (no earlier than)" 0 "ACCOMPANIED" \ ruling_bare_decision setter "$L" <<<"setter $((L - 900))" check "escalation after the flag accompanies it" 0 "ACCOMPANIED" \ ruling_bare_decision setter "$L" <<<"setter $((L + 60))" check "escalation 16 minutes before is bare" 0 "BARE" \ ruling_bare_decision setter "$L" <<<"setter $((L - 960))" check "somebody else's comment does not satisfy the contract" 0 "BARE" \ ruling_bare_decision setter "$L" <<<"bystander $((L - 60))" check "no comment at all is bare" 0 "BARE" \ ruling_bare_decision setter "$L" >"$TMP/posted-$n" file="$TMP/repos_${REPO%%/*}_${REPO#*/}_issues_${n}_comments.json" [ -f "$file" ] || printf '[]\n' >"$file" jq --arg b "$body" --arg at "$(iso "$NOW")" \ '. + [{"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/edits" fi } posts() { [ -f "$TMP/posted-$1" ] && grep -c '^----$' "$TMP/posted-$1" || echo 0; } timeline_file() { printf '%s/repos_owner_repo_issues_%s_timeline.json' "$TMP" "$1"; } comments_file() { printf '%s/repos_owner_repo_issues_%s_comments.json' "$TMP" "$1"; } # -- bare flag, swept twice: exactly one comment ----------------------------- T=$((NOW - 3600)) jq -n --arg at "$(iso "$T")" \ '[{"event":"labeled","label":{"name":"needs-ruling"},"actor":{"login":"setter"},"created_at":$at}]' \ >"$(timeline_file 9)" printf '[]\n' >"$(comments_file 9)" check "a bare flag is commented on" 0 "ruling flag is bare" reconcile_ruling 9 "$T" "$NOW" check "...and the sweep never posted twice" 0 "" reconcile_ruling 9 "$T" "$NOW" check "one bare comment across two sweeps" 0 "1" posts 9 check "the bare comment carries its marker" 0 "" \ grep -qF '' "$TMP/posted-9" check "the bare comment names the missing contract" 0 "" \ grep -q 'question' "$TMP/posted-9" # -- accompanied flag: silence ---------------------------------------------- jq -n --arg at "$(iso "$T")" \ '[{"event":"labeled","label":{"name":"needs-ruling"},"actor":{"login":"setter"},"created_at":$at}]' \ >"$(timeline_file 10)" jq -n --arg at "$(iso "$((T - 840))")" \ '[{"user":{"login":"setter"},"created_at":$at,"html_url":"https://x/esc","body":"question, options, recommendation"}]' \ >"$(comments_file 10)" reconcile_ruling 10 "$T" "$NOW" >/dev/null check "an accompanied flag posts nothing" 0 "0" posts 10 # -- re-flag: judged on its own escalation, marker scoped per event ---------- T1=$((NOW - 86400)) T2=$((NOW - 7200)) jq -n --arg t1 "$(iso "$T1")" --arg t2 "$(iso "$T2")" \ '[{"event":"labeled","label":{"name":"needs-ruling"},"actor":{"login":"setter"},"created_at":$t1}, {"event":"unlabeled","label":{"name":"needs-ruling"},"actor":{"login":"setter"},"created_at":$t2}, {"event":"labeled","label":{"name":"needs-ruling"},"actor":{"login":"setter"},"created_at":$t2}]' \ >"$(timeline_file 11)" jq -n --arg esc "$(iso "$((T1 - 60))")" --arg marked "$(iso "$((T1 + 300))")" \ '[{"user":{"login":"setter"},"created_at":$esc,"html_url":"https://x/esc1","body":"the first escalation"}, {"user":{"login":"sweep-bot"},"created_at":$marked,"html_url":"https://x/bare1","body":"\nolder episode"}]' \ >"$(comments_file 11)" reconcile_ruling 11 "$T2" "$NOW" >/dev/null check "a re-flag is re-checked against its own escalation" 0 "1" posts 11 # -- nudge: fires past 7 quiet days, links the escalation, resets itself ----- T0=$((NOW - 8 * 86400)) jq -n --arg at "$(iso "$T0")" \ '[{"event":"labeled","label":{"name":"needs-ruling"},"actor":{"login":"setter"},"created_at":$at}]' \ >"$(timeline_file 12)" jq -n --arg at "$(iso "$((T0 - 60))")" \ '[{"user":{"login":"setter"},"created_at":$at,"html_url":"https://x/esc12","body":"question, options, recommendation"}]' \ >"$(comments_file 12)" check "8 quiet days nudge" 0 "ruling nudge" reconcile_ruling 12 "$T0" "$NOW" check "one nudge posted" 0 "1" posts 12 check "the nudge links the escalation comment" 0 "" grep -qF 'https://x/esc12' "$TMP/posted-12" check "the nudge addresses the decider" 0 "" grep -qF '@danmt' "$TMP/posted-12" check "the nudge does not tag the flag-setter" 1 "" grep -qF '@setter' "$TMP/posted-12" check "the nudge carries no marker — the comment itself resets the window" 1 "" \ grep -qF "$RULING_BARE_MARKER" "$TMP/posted-12" # The reset, through the surfaces' own activity computation: the nudge the # stub appended is the newest comment, so the recomputed last-activity is NOW. newest_at="$(jq -r 'map(.created_at) | max' "$(comments_file 12)")" check "the posted nudge is now the newest activity" 0 "" \ test "$(date -d "$newest_at" +%s)" = "$NOW" reconcile_ruling 12 "$(date -d "$newest_at" +%s)" "$NOW" >/dev/null check "a sweep right after the nudge holds its silence" 0 "1" posts 12 # -- 6 quiet days: silence --------------------------------------------------- jq -n --arg at "$(iso "$((NOW - 6 * 86400))")" \ '[{"event":"labeled","label":{"name":"needs-ruling"},"actor":{"login":"setter"},"created_at":$at}]' \ >"$(timeline_file 13)" jq -n --arg at "$(iso "$((NOW - 6 * 86400))")" \ '[{"user":{"login":"setter"},"created_at":$at,"html_url":"https://x/esc13","body":"question, options, recommendation"}]' \ >"$(comments_file 13)" reconcile_ruling 13 $((NOW - 6 * 86400)) "$NOW" >/dev/null check "6 quiet days do not nudge" 0 "0" posts 13 # -- unreadable timeline: nothing happens ------------------------------------ check "an unreadable timeline invents no verdict" 0 "timeline unreadable" \ reconcile_ruling 14 "$T" "$NOW" check "...and posts nothing" 0 "0" posts 14 # -- across every scenario above: not one label write ------------------------ check "the ruling sweep never wrote a label" 1 "" test -f "$TMP/edits" # Grep-level pin for #50 D9: no mutation call in the sweep code names the # flag. The only writes reconcile_ruling makes are comments. check "no add/remove-label mutation names the ruling flag" 1 "" \ grep -rEn -- '(add|remove)-label[^"]*needs-ruling' "$ROOT/actions" "$ROOT/lib" summary