From 39355adb8e1196e5398695126f6a61e4fec42683 Mon Sep 17 00:00:00 2001 From: "vanillagreen-fleet-lanes[bot]" <328550061+vanillagreen-fleet-lanes[bot]@users.noreply.github.com> Date: Tue, 13 Oct 2026 15:49:36 +0000 Subject: [PATCH 1/2] test(KEN-3997): await a successful resume poll [no-changelog] --- skills/slack/tests/hold.test.sh | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/skills/slack/tests/hold.test.sh b/skills/slack/tests/hold.test.sh index 4537b68a6b..c9598c8086 100755 --- a/skills/slack/tests/hold.test.sh +++ b/skills/slack/tests/hold.test.sh @@ -44,10 +44,11 @@ row() { sk_run -- listen --status --root "$1"; LINE="$(printf '%s' "$OUT" | sed refuse_next_post() { sk_ctl /_test/fault '{"method": "chat.postMessage", "error": "not_in_channel", "times": 1}' >/dev/null; } # A status timestamp acknowledges a completed poll. The parent suite bounds # this wait even if the relay or the settings reader stops making progress. -polled_after() { # ROOT LAST_POLL +polled_after() { # ROOT LAST_POLL [RESUMED] local tries=0 value while [ "$tries" -lt 100 ]; do - value="$(jq -r --argjson after "$2" '.last_poll > $after' "$1/tmp/slack/status.json" 2>/dev/null)" + value="$(jq -r --argjson after "$2" --argjson resumed "${3:-false}" \ + '.last_poll > $after and ($resumed == false or (.last_poll_ok and .held_by == ""))' "$1/tmp/slack/status.json" 2>/dev/null)" [ "$value" != true ] || return 0 tries=$((tries + 1)); sleep 0.1 done @@ -638,7 +639,9 @@ while read -r name source caller control; do named) printf 'SLACK_JARVIS_SESSION_FILE=\n' > "$R/private.env" ;; esac AFTER="$(jq -r .last_poll "$R/tmp/slack/status.json")" - polled_after "$R" "$AFTER" || exit 1 + # A refused poll can finish after removal. Its timestamp does not + # acknowledge the new setting; wait for a successful, unheld poll. + polled_after "$R" "$AFTER" true || exit 1 row "$R"; assert_eq "$(field "$LINE" state)=$(field "$LINE" held-by)" "ok=" "$name: removing or emptying the setting resumes" assert_eq "$(count "$CHANNEL" "Changed hold $name.")=$(count "$CHANNEL" "Invalid hold $name.")" "1=1" \ "$name: resume posts the held notices once" From a9c29f96dfebd73a97258a9e2bc42f911b3e181f Mon Sep 17 00:00:00 2001 From: "vanillagreen-fleet-lanes[bot]" <328550061+vanillagreen-fleet-lanes[bot]@users.noreply.github.com> Date: Tue, 13 Oct 2026 16:08:49 +0000 Subject: [PATCH 2/2] test(KEN-3997): force the refused-poll resume order [no-changelog] A refused poll can read an invalid setting before removal and write its status afterward. Pause that real poll in each live-settings relay copy so the existing resume assertions must wait for successful release. Keep every existing row, control and bound. A disposable timestamp-only mutation of this fixture failed both resume assertions for all four settings variants: 53 passed, 8 failed. The corrected whole hold suite passed 262 rows in 222 seconds. Evidence: tmp/waiter.ttdLWh/old-wait.log and tmp/waiter.aBbd2w/hold.log. --- skills/slack/tests/hold.test.sh | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/skills/slack/tests/hold.test.sh b/skills/slack/tests/hold.test.sh index c9598c8086..0d493e6df4 100755 --- a/skills/slack/tests/hold.test.sh +++ b/skills/slack/tests/hold.test.sh @@ -595,6 +595,29 @@ while read -r name source caller control; do if [ "$control" = mutant ]; then sk_mutant no-poll-presence relay.py 'presence = load_presence\(self.path\)' 'presence = self.presence' fi + if [ "$control:$caller" = real:none ]; then + sk_copy "resume-$name" + # Pause the real refused poll before it records its completion time. + # Removal then precedes that old reading's status write on every run. + python3 - "${SK_BIN%/*}/lib/relay.py" <<'PY' +from pathlib import Path +import sys + +path = Path(sys.argv[1]) +text = path.read_text() +needle = ' delivered = max(self.state.delivered, key=float, default="")' +assert text.count(needle) == 1 +barrier = ''' arm = self.path / "tmp/resume-arm" + if not ok and "setting-invalid" in error and arm.exists(): + arm.unlink() + (self.path / "tmp/resume-entered").touch() + while not (self.path / "tmp/resume-release").exists(): + time.sleep(0.01) +''' +path.write_text(text.replace(needle, barrier + needle)) +PY + [ "$?" -eq 0 ] || exit 1 + fi case "$caller" in empty) sk_relay_start "$R" --root "$S" SLACK_JARVIS_SESSION_FILE= ;; *) sk_relay_start "$R" --root "$S" ;; @@ -632,6 +655,12 @@ while read -r name source caller control; do assert_has "$(jq -r .last_error "$R/tmp/slack/status.json")" "setting-invalid=SLACK_JARVIS_SESSION_MAX_AGE=invalid root=$R" \ "$name: the refused reading names its root" row "$S"; assert_eq "$(field "$LINE" state)" ok "$name: a refused reading leaves its peer ok" + touch "$R/tmp/resume-arm" || exit 1 + TRIES=0 + while [ ! -f "$R/tmp/resume-entered" ] && [ "$TRIES" -lt 100 ]; do + TRIES=$((TRIES + 1)); sleep 0.1 + done + [ -f "$R/tmp/resume-entered" ] || { bad "refused poll did not reach the resume barrier" "$R"; exit 1; } case "$source" in toml) rm -- "$R/kendex.settings.toml" ;; local) rm -- "$R/.kendex/settings.toml" ;; @@ -639,6 +668,7 @@ while read -r name source caller control; do named) printf 'SLACK_JARVIS_SESSION_FILE=\n' > "$R/private.env" ;; esac AFTER="$(jq -r .last_poll "$R/tmp/slack/status.json")" + touch "$R/tmp/resume-release" || exit 1 # A refused poll can finish after removal. Its timestamp does not # acknowledge the new setting; wait for a successful, unheld poll. polled_after "$R" "$AFTER" true || exit 1