forked from heavy-duty/ceremony
Merge pull request #234 from dan-claude-bot/build/226-best-shaped-escalation
fix: select the best-shaped escalation, not the earliest
This commit is contained in:
commit
0ac3a6ff7e
3 changed files with 124 additions and 13 deletions
9
changelog.d/226.md
Normal file
9
changelog.d/226.md
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- `ruling_escalation_row` selects the setter's best-shaped in-window comment,
|
||||||
|
ties broken to the earliest, instead of the earliest outright — a whole-round
|
||||||
|
reply landing seconds before the escalation is no longer graded in its place
|
||||||
|
(crew#293).
|
||||||
|
- The escalation selector and `ruling_shape_decision` share one field-presence
|
||||||
|
matcher, and an undecodable body column scores 0 instead of erroring the
|
||||||
|
sweep.
|
||||||
|
|
@ -99,18 +99,40 @@ ruling_bare_comment_needed() { # $1 labeled epoch, $2 newest marked-comment epoc
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ruling_shape_field_present() { # $1 field label; escalation body on stdin → 0 iff present
|
||||||
|
# THE one spelling of the field-presence test — the escalation selector
|
||||||
|
# scores by it and the shape check grades by it, on purpose in one place:
|
||||||
|
# a selector scoring by one grep while the check grades by another is how
|
||||||
|
# the crew#293 misgrade would come back from the other side (#226).
|
||||||
|
# Line-anchored, allowing leading whitespace and Markdown bold
|
||||||
|
# (`**Options:**` is how the live escalations write them): the labels
|
||||||
|
# appearing only mid-sentence is not the template.
|
||||||
|
local field="$1"
|
||||||
|
grep -Eq "^[[:space:]]*(\*\*)?$field"
|
||||||
|
}
|
||||||
|
|
||||||
|
ruling_shape_score() { # escalation body on stdin → 0–4, one point per field present
|
||||||
|
# The selection rule's metric (#226). An empty body scores 0 through the
|
||||||
|
# same loop — no special case, and never an error.
|
||||||
|
local body field score=0
|
||||||
|
body="$(cat)"
|
||||||
|
for field in "${RULING_SHAPE_FIELDS[@]}"; do
|
||||||
|
if ruling_shape_field_present "$field" <<<"$body"; then score=$((score + 1)); fi
|
||||||
|
done
|
||||||
|
echo "$score"
|
||||||
|
}
|
||||||
|
|
||||||
ruling_shape_decision() { # escalation body on stdin → SHAPED | MALFORMED <missing labels>
|
ruling_shape_decision() { # escalation body on stdin → SHAPED | MALFORMED <missing labels>
|
||||||
# Presence only (#50 D4): that `Recommend:` exists is checkable, that the
|
# Presence only (#50 D4): that `Recommend:` exists is checkable, that the
|
||||||
# recommendation is any good is not — no counting options, no parsing the
|
# recommendation is any good is not — no counting options, no parsing the
|
||||||
# prose. Line-anchored, allowing leading whitespace and Markdown bold
|
# prose. The per-field test is ruling_shape_field_present, shared with the
|
||||||
# (`**Options:**` is how the live escalations write them): the labels
|
# selector (#226). The `🧭 needs-ruling` header line is deliberately
|
||||||
# appearing only mid-sentence is not the template. The `🧭 needs-ruling`
|
# unchecked — it is prose, and an emoji grep on an LC_ALL=C runner is a
|
||||||
# header line is deliberately unchecked — it is prose, and an emoji grep
|
# portability trap for zero enforcement value.
|
||||||
# on an LC_ALL=C runner is a portability trap for zero enforcement value.
|
|
||||||
local body field missing=""
|
local body field missing=""
|
||||||
body="$(cat)"
|
body="$(cat)"
|
||||||
for field in "${RULING_SHAPE_FIELDS[@]}"; do
|
for field in "${RULING_SHAPE_FIELDS[@]}"; do
|
||||||
grep -Eq "^[[:space:]]*(\*\*)?$field" <<<"$body" || missing="$missing $field"
|
ruling_shape_field_present "$field" <<<"$body" || missing="$missing $field"
|
||||||
done
|
done
|
||||||
if [ -z "$missing" ]; then echo SHAPED; else echo "MALFORMED$missing"; fi
|
if [ -z "$missing" ]; then echo SHAPED; else echo "MALFORMED$missing"; fi
|
||||||
}
|
}
|
||||||
|
|
@ -170,17 +192,32 @@ ruling_newest_flag() { # "login<TAB>iso8601" lines on stdin → the newest line
|
||||||
}
|
}
|
||||||
|
|
||||||
ruling_escalation_row() { # $1 setter, $2 labeled epoch; "login epoch url [b64]" lines on stdin
|
ruling_escalation_row() { # $1 setter, $2 labeled epoch; "login epoch url [b64]" lines on stdin
|
||||||
# → "url b64" of the EARLIEST in-window comment by the setter, or nothing.
|
# → "url b64" of the BEST-SHAPED in-window comment by the setter, or
|
||||||
# Earliest, because the natural shape is escalation-then-flag: the first
|
# nothing: highest ruling_shape_score wins, equal scores break to the
|
||||||
# qualifying comment is the escalation itself, later ones are follow-ups.
|
# earliest epoch. Earliest-wins outright was the rule until crew#293
|
||||||
# The body rides along base64-encoded (#73's shape check reads it); rows
|
# (2026-08-02): a builder answered its round whole and escalated 33
|
||||||
# without the column still resolve, with an empty body.
|
# seconds later — both in one window, the reply earlier — and the sweep
|
||||||
local setter="$1" labeled="$2" login epoch url b64 best_epoch="" best=""
|
# graded the round reply, told a correct escalation it was malformed, and
|
||||||
|
# the setter re-posted a shape it had already met. Score resolves both
|
||||||
|
# orderings; the earliest tiebreak keeps escalation-then-follow-ups
|
||||||
|
# wherever the scores cannot tell candidates apart, including all-zero.
|
||||||
|
# An undecodable or absent body scores 0 and stays a legal candidate —
|
||||||
|
# an unreadable fact never invents a verdict, and never errors the sweep.
|
||||||
|
# The window and the setter gate candidacy before any score is taken.
|
||||||
|
local setter="$1" labeled="$2" login epoch url b64 body score
|
||||||
|
local best_score=-1 best_epoch="" best=""
|
||||||
while read -r login epoch url b64; do
|
while read -r login epoch url b64; do
|
||||||
[ -n "$login" ] || continue
|
[ -n "$login" ] || continue
|
||||||
[ "$login" = "$setter" ] || continue
|
[ "$login" = "$setter" ] || continue
|
||||||
ruling_accompanies "$epoch" "$labeled" || continue
|
ruling_accompanies "$epoch" "$labeled" || continue
|
||||||
if [ -z "$best_epoch" ] || [ "$epoch" -lt "$best_epoch" ]; then
|
if body="$(base64 -d <<<"${b64:-}" 2>/dev/null)"; then
|
||||||
|
score="$(ruling_shape_score <<<"$body")"
|
||||||
|
else
|
||||||
|
score=0
|
||||||
|
fi
|
||||||
|
if [ "$score" -gt "$best_score" ] \
|
||||||
|
|| { [ "$score" -eq "$best_score" ] && [ "$epoch" -lt "$best_epoch" ]; }; then
|
||||||
|
best_score="$score"
|
||||||
best_epoch="$epoch"
|
best_epoch="$epoch"
|
||||||
best="$url ${b64:-}"
|
best="$url ${b64:-}"
|
||||||
fi
|
fi
|
||||||
|
|
|
||||||
|
|
@ -109,6 +109,71 @@ check "labels only mid-sentence are malformed — line-anchoring is the rule" 0
|
||||||
check "an empty body is missing everything" 0 "MALFORMED Options: Recommend: Blocked: Default:" \
|
check "an empty body is missing everything" 0 "MALFORMED Options: Recommend: Blocked: Default:" \
|
||||||
ruling_shape_decision </dev/null
|
ruling_shape_decision </dev/null
|
||||||
|
|
||||||
|
# -- escalation selection: best-shaped wins, earliest breaks ties (#226) ----
|
||||||
|
# The crew#293 incident: a whole-round reply and the escalation land seconds
|
||||||
|
# apart inside one window, the reply earlier. Earliest-wins graded the reply.
|
||||||
|
# b64 here mirrors jq's @base64 — unwrapped, or the TSV rows would split.
|
||||||
|
|
||||||
|
b64enc() { printf '%s' "$1" | base64 | tr -d '\n'; }
|
||||||
|
ROUND_REPLY=$'🔧 addressing round on head 86c35f14 — every point answered below'
|
||||||
|
PARTIAL=$'Options: A — x B — y\nBlocked: z'
|
||||||
|
|
||||||
|
replay="$(printf 'setter %s https://x/reply %s\nsetter %s https://x/escalation %s\n' \
|
||||||
|
"$((L - 40))" "$(b64enc "$ROUND_REPLY")" "$((L - 7))" "$(b64enc "$TPL")")"
|
||||||
|
check "crew#293 replay: the complete escalation is selected over the earlier round reply" 0 \
|
||||||
|
"https://x/escalation $(b64enc "$TPL")" ruling_escalation_row setter "$L" <<<"$replay"
|
||||||
|
sel="$(ruling_escalation_row setter "$L" <<<"$replay")"
|
||||||
|
check "crew#293 replay: the selected body grades SHAPED" 0 "SHAPED" \
|
||||||
|
ruling_shape_decision <<<"$(base64 -d <<<"${sel#* }")"
|
||||||
|
check "the nudge's link follows the same selection" 0 "https://x/escalation" \
|
||||||
|
ruling_escalation_url setter "$L" <<<"$replay"
|
||||||
|
check "the rung wording reads Default: from the selected body" 0 "DEADLINE 2026-07-23T21:00Z" \
|
||||||
|
ruling_default_decision <<<"$(base64 -d <<<"${sel#* }")"
|
||||||
|
|
||||||
|
check "escalation-then-follow-up still selects the escalation" 0 \
|
||||||
|
"https://x/escalation $(b64enc "$TPL")" ruling_escalation_row setter "$L" <<<"$(
|
||||||
|
printf 'setter %s https://x/escalation %s\nsetter %s https://x/followup %s\n' \
|
||||||
|
"$((L - 300))" "$(b64enc "$TPL")" "$((L - 60))" "$(b64enc 'thanks — clarified above')")"
|
||||||
|
|
||||||
|
check "a complete escalation beats an earlier partial" 0 \
|
||||||
|
"https://x/complete $(b64enc "$TPL")" ruling_escalation_row setter "$L" <<<"$(
|
||||||
|
printf 'setter %s https://x/partial %s\nsetter %s https://x/complete %s\n' \
|
||||||
|
"$((L - 300))" "$(b64enc "$PARTIAL")" "$((L - 60))" "$(b64enc "$TPL")")"
|
||||||
|
check "a complete escalation beats a later partial" 0 \
|
||||||
|
"https://x/complete $(b64enc "$TPL")" ruling_escalation_row setter "$L" <<<"$(
|
||||||
|
printf 'setter %s https://x/complete %s\nsetter %s https://x/partial %s\n' \
|
||||||
|
"$((L - 300))" "$(b64enc "$TPL")" "$((L - 60))" "$(b64enc "$PARTIAL")")"
|
||||||
|
|
||||||
|
check "equal full scores break to the earliest" 0 \
|
||||||
|
"https://x/one $(b64enc "$TPL")" ruling_escalation_row setter "$L" <<<"$(
|
||||||
|
printf 'setter %s https://x/one %s\nsetter %s https://x/two %s\n' \
|
||||||
|
"$((L - 300))" "$(b64enc "$TPL")" "$((L - 60))" "$(b64enc "$TPL_BOLD")")"
|
||||||
|
check "all-zero scores still break to the earliest" 0 "https://x/first" \
|
||||||
|
ruling_escalation_url setter "$L" <<<"$(
|
||||||
|
printf 'setter %s https://x/first\nsetter %s https://x/second\n' \
|
||||||
|
"$((L - 300))" "$((L - 60))")"
|
||||||
|
|
||||||
|
check "an out-of-window all-four row is never selected" 0 \
|
||||||
|
"https://x/in $(b64enc "$PARTIAL")" ruling_escalation_row setter "$L" <<<"$(
|
||||||
|
printf 'setter %s https://x/out %s\nsetter %s https://x/in %s\n' \
|
||||||
|
"$((L - 5000))" "$(b64enc "$TPL")" "$((L - 60))" "$(b64enc "$PARTIAL")")"
|
||||||
|
check "an out-of-window all-four row cannot turn an empty result non-empty" 0 "" \
|
||||||
|
ruling_escalation_row setter "$L" <<<"setter $((L - 5000)) https://x/out $(b64enc "$TPL")"
|
||||||
|
check "another actor's all-four row is never selected" 0 "" \
|
||||||
|
ruling_escalation_row setter "$L" <<<"bystander $((L - 60)) https://x/other $(b64enc "$TPL")"
|
||||||
|
|
||||||
|
check "a garbage body column scores 0 and never errors" 0 \
|
||||||
|
"https://x/good $(b64enc "$PARTIAL")" ruling_escalation_row setter "$L" <<<"$(
|
||||||
|
printf 'setter %s https://x/garbage !!!not-base64!!!\nsetter %s https://x/good %s\n' \
|
||||||
|
"$((L - 300))" "$((L - 60))" "$(b64enc "$PARTIAL")")"
|
||||||
|
check "a garbage-only candidate is still a legal selection" 0 \
|
||||||
|
"https://x/garbage !!!not-base64!!!" \
|
||||||
|
ruling_escalation_row setter "$L" <<<"setter $((L - 300)) https://x/garbage !!!not-base64!!!"
|
||||||
|
|
||||||
|
# shellcheck disable=SC2016 # the literal $field is the assertion — one spelling, unexpanded
|
||||||
|
check "the field matcher has exactly one spelling in lib/ruling.sh" 0 "1" \
|
||||||
|
grep -cF '(\*\*)?$field' "$ROOT/lib/ruling.sh"
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# The orchestrator, against a recording gh stub. The stub serves fixture JSON
|
# The orchestrator, against a recording gh stub. The stub serves fixture JSON
|
||||||
# per endpoint (missing file = unreadable read), applies the caller's --jq
|
# per endpoint (missing file = unreadable read), applies the caller's --jq
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue