@ai-dev-methodologies/rlp-desk 0.18.1 → 0.18.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/package.json +1 -1
- package/src/scripts/run_ralph_desk.zsh +209 -82
package/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,30 @@ For pre-v0.15.4 versions, refer to `git log` and individual GitHub release notes
|
|
|
11
11
|
- Post-v0.15.6: remove `RLP_LIFECYCLE_METRICS` flag entirely (per plan v3 ADR follow-ups).
|
|
12
12
|
- Phase D.1 (handoff documents) + Phase D.2 (per-stage agent role specialization) — both deferred per `docs/plans/v0.15.4-release-runbook.md` §7.6.
|
|
13
13
|
|
|
14
|
+
## [0.18.2] — 2026-06-25
|
|
15
|
+
|
|
16
|
+
Five leader-resilience fixes (per-us, consensus, and config-validation paths), each
|
|
17
|
+
validated by a real-LLM dogfood. Consensus is opt-in; default per-us campaigns benefit
|
|
18
|
+
from the final-verify and config-validation fixes.
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
- Final verification is resilient to verifier non-determinism: a user story that already
|
|
22
|
+
passed its per-US check is re-verified (up to a small bound) on a fail verdict, so a
|
|
23
|
+
flaky false-fail must reproduce before it charges a fix-loop failure. A single
|
|
24
|
+
non-deterministic false-fail can no longer block a complete, correct campaign.
|
|
25
|
+
- The `claude` rate-limit banner ("API Error: … temporarily limiting requests … · Rate
|
|
26
|
+
limited") is now recognized as a transient API condition and routed to the bounded
|
|
27
|
+
backoff, instead of being misclassified as a frozen-pane deadlock.
|
|
28
|
+
- Numeric configuration knobs (e.g. `--max-iter`, `--cb-threshold`, timeouts) are now
|
|
29
|
+
validated: a non-integer or out-of-range value falls back to its default with a warning,
|
|
30
|
+
instead of silently mis-evaluating (a bad `--max-iter` previously could make a campaign
|
|
31
|
+
run zero iterations).
|
|
32
|
+
- Leader auto-commit recovery no longer blocks a campaign whose work the worker already
|
|
33
|
+
committed (a commit-timing race previously surfaced as "nothing to commit" → blocked).
|
|
34
|
+
- `--consensus final-only` now actually runs at the final verification in the default
|
|
35
|
+
per-us verify mode (it was previously bypassed by the sequential final-verify path, so
|
|
36
|
+
the recommended consensus configuration was a silent no-op in the default mode).
|
|
37
|
+
|
|
14
38
|
## [0.18.1] — 2026-06-25
|
|
15
39
|
|
|
16
40
|
### Fixed
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-dev-methodologies/rlp-desk",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.2",
|
|
4
4
|
"description": "Fresh-context iterative loops for Claude Code — autonomous task completion with independent verification",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"postinstall": "node scripts/postinstall.js",
|
|
@@ -3,6 +3,31 @@ set -uo pipefail
|
|
|
3
3
|
# NOTE: We use set -u (undefined var check) and pipefail, but NOT set -e
|
|
4
4
|
# because the main loop uses explicit error checks throughout.
|
|
5
5
|
|
|
6
|
+
# D-19: validate an env-overridable INTEGER knob. A non-integer value (operator
|
|
7
|
+
# typo, or a bad CLI arg like `--max-iter abc` threaded into the env) otherwise
|
|
8
|
+
# mis-evaluates under `set -u` inside (( )) arithmetic — e.g. a non-integer
|
|
9
|
+
# MAX_ITER makes the main-loop bound error so the campaign silently runs ZERO
|
|
10
|
+
# iterations; a non-integer CB_THRESHOLD breaks the circuit breaker. The `<->`
|
|
11
|
+
# integer glob is checked FIRST (and short-circuits) so the arithmetic never runs
|
|
12
|
+
# on a non-integer. A malformed / below-min / above-max value → the default.
|
|
13
|
+
_validate_int_knob() {
|
|
14
|
+
local _name="$1" _default="$2" _min="${3:-0}" _max="${4:-0}"
|
|
15
|
+
local _val="${(P)_name}"
|
|
16
|
+
local _bad=0
|
|
17
|
+
if ! [[ "$_val" == <-> ]]; then
|
|
18
|
+
_bad=1
|
|
19
|
+
elif (( _val < _min )); then
|
|
20
|
+
_bad=1
|
|
21
|
+
elif (( _max > 0 && _val > _max )); then
|
|
22
|
+
_bad=1
|
|
23
|
+
fi
|
|
24
|
+
if (( _bad )); then
|
|
25
|
+
local _range="min=$_min"; (( _max > 0 )) && _range="$_range, max=$_max"
|
|
26
|
+
print -r -- "WARNING: $_name='$_val' is not a valid integer ($_range) — using default $_default" >&2
|
|
27
|
+
eval "$_name=$_default"
|
|
28
|
+
fi
|
|
29
|
+
}
|
|
30
|
+
|
|
6
31
|
# =============================================================================
|
|
7
32
|
# Ralph Desk Tmux Runner
|
|
8
33
|
#
|
|
@@ -56,6 +81,14 @@ HEARTBEAT_STALE_THRESHOLD="${HEARTBEAT_STALE_THRESHOLD:-120}"
|
|
|
56
81
|
MAX_RESTARTS="${MAX_RESTARTS:-3}"
|
|
57
82
|
IDLE_NUDGE_THRESHOLD="${IDLE_NUDGE_THRESHOLD:-30}"
|
|
58
83
|
MAX_NUDGES="${MAX_NUDGES:-3}"
|
|
84
|
+
# D-19: validate the numeric knobs above (set -u + (( )) arithmetic safety).
|
|
85
|
+
_validate_int_knob MAX_ITER 20 1
|
|
86
|
+
_validate_int_knob POLL_INTERVAL 5 1
|
|
87
|
+
_validate_int_knob ITER_TIMEOUT 600 1
|
|
88
|
+
_validate_int_knob HEARTBEAT_STALE_THRESHOLD 120 1
|
|
89
|
+
_validate_int_knob MAX_RESTARTS 3 0
|
|
90
|
+
_validate_int_knob IDLE_NUDGE_THRESHOLD 30 1
|
|
91
|
+
_validate_int_knob MAX_NUDGES 3 0
|
|
59
92
|
WITH_SELF_VERIFICATION="${WITH_SELF_VERIFICATION:-0}"
|
|
60
93
|
WITH_SELF_VERIFICATION_REQUESTED="$WITH_SELF_VERIFICATION" # preserves original user intent for traceability (governance §1f)
|
|
61
94
|
SV_SKIPPED_REASON="" # set when SV is disabled despite user request
|
|
@@ -88,6 +121,7 @@ TEST_DENSITY_MODE="${TEST_DENSITY_MODE:-warn}"
|
|
|
88
121
|
# .sisyphus/mission-abort.json and exits non-zero so contract defects don't
|
|
89
122
|
# silently loop. infra_failure category and the very first iteration are exempt.
|
|
90
123
|
BLOCK_CB_THRESHOLD="${BLOCK_CB_THRESHOLD:-3}"
|
|
124
|
+
_validate_int_knob BLOCK_CB_THRESHOLD 3 1 # D-19
|
|
91
125
|
CONSECUTIVE_BLOCKS=0
|
|
92
126
|
LAST_BLOCK_REASON=""
|
|
93
127
|
|
|
@@ -229,6 +263,18 @@ FINAL_VERIFIER_ENGINE="${FINAL_VERIFIER_ENGINE:-claude}"
|
|
|
229
263
|
WORKER_EFFORT="${WORKER_EFFORT:-}"
|
|
230
264
|
VERIFIER_EFFORT="${VERIFIER_EFFORT:-}"
|
|
231
265
|
FINAL_VERIFIER_EFFORT="${FINAL_VERIFIER_EFFORT:-}"
|
|
266
|
+
# D-18: max final-verify attempts for a US that ALREADY passed per-US. A verifier
|
|
267
|
+
# fail verdict on already-per-US-passed work must REPRODUCE across all attempts
|
|
268
|
+
# (first pass wins) before it charges a fix-loop failure — guards against verifier
|
|
269
|
+
# non-determinism defeating a complete, correct campaign. A genuinely-regressed US
|
|
270
|
+
# (or one never per-US-passed) still fails on the first attempt.
|
|
271
|
+
FINAL_VERIFY_MAX_ATTEMPTS="${FINAL_VERIFY_MAX_ATTEMPTS:-3}"
|
|
272
|
+
# D-18/D-19: a non-integer value ("abc") would mis-evaluate under set -u in the
|
|
273
|
+
# (( )) retry-loop arithmetic — skipping the loop and silently FALSE-FAILING a US
|
|
274
|
+
# — and an unbounded value would be ruinously expensive. Validate to an integer
|
|
275
|
+
# in 1..10 via the shared _validate_int_knob helper (D-19 generalized this
|
|
276
|
+
# per-knob fix into one validator used by every numeric knob).
|
|
277
|
+
_validate_int_knob FINAL_VERIFY_MAX_ATTEMPTS 3 1 10
|
|
232
278
|
|
|
233
279
|
# Auto-detect engine from model format for env var path (CLI path uses parse_model_flag)
|
|
234
280
|
_auto_detect_engine WORKER_MODEL WORKER_ENGINE WORKER_CODEX_MODEL WORKER_CODEX_REASONING WORKER_EFFORT
|
|
@@ -260,6 +306,7 @@ elif [[ "${FINAL_CONSENSUS:-0}" = "1" ]]; then
|
|
|
260
306
|
fi
|
|
261
307
|
CONSENSUS_SCOPE="${CONSENSUS_SCOPE:-${CONSENSUS_MODE}}"
|
|
262
308
|
CB_THRESHOLD="${CB_THRESHOLD:-6}" # consecutive failures before BLOCKED (default: 6)
|
|
309
|
+
_validate_int_knob CB_THRESHOLD 6 1 # D-19: must be valid before the (( *2 )) below
|
|
263
310
|
# Effective CB threshold: doubled when consensus mode active
|
|
264
311
|
if [[ "$CONSENSUS_MODE" != "off" ]]; then
|
|
265
312
|
EFFECTIVE_CB_THRESHOLD=$(( CB_THRESHOLD * 2 ))
|
|
@@ -268,6 +315,8 @@ else
|
|
|
268
315
|
fi
|
|
269
316
|
_API_MAX_RETRIES="${_API_MAX_RETRIES:-5}"
|
|
270
317
|
_API_RETRY_INTERVAL_S="${_API_RETRY_INTERVAL_S:-30}"
|
|
318
|
+
_validate_int_knob _API_MAX_RETRIES 5 1 # D-19
|
|
319
|
+
_validate_int_knob _API_RETRY_INTERVAL_S 30 1 # D-19
|
|
271
320
|
|
|
272
321
|
# --- Derived Paths ---
|
|
273
322
|
DESK="$ROOT/${RLP_DESK_RUNTIME_DIR:-.rlp-desk}"
|
|
@@ -847,7 +896,25 @@ _bug8_check_synth_allowed() {
|
|
|
847
896
|
log " Bug #8 F-8 recovery: done-claim + Worker's uncommitted tracked changes — auto-committing $us_id work (files: $_bug8_first5)."
|
|
848
897
|
log_debug "[GOV] iter=$iter bug8=recover_autocommit us_id=$us_id files='$_bug8_first5'"
|
|
849
898
|
local -a _bug8_add=("${(@f)_bug8_worker_files}")
|
|
850
|
-
|
|
899
|
+
# D-20 (codex LOW): fail-safe on an empty file list. The upstream
|
|
900
|
+
# `[[ -z "$_bug8_worker_files" ]]` guard already makes this unreachable, but
|
|
901
|
+
# never let an empty array turn `git diff --quiet HEAD --` into a whole-tree
|
|
902
|
+
# check (which could falsely read "already committed" → false PASS). BLOCK.
|
|
903
|
+
if (( ${#_bug8_add} == 0 )); then
|
|
904
|
+
log_error " Bug #8: empty worker-file list at auto-commit (unexpected) — refusing synthesis."
|
|
905
|
+
write_blocked_sentinel "worker_incomplete_uncommitted: empty file list at auto-commit" "$us_id" "metric_failure"
|
|
906
|
+
return 1
|
|
907
|
+
fi
|
|
908
|
+
if git -C "$ROOT" diff --quiet HEAD -- "${_bug8_add[@]}" 2>/dev/null; then
|
|
909
|
+
# D-20: the Worker committed these files itself in the window between the
|
|
910
|
+
# dirty-detection above and now (a reap/commit race) — the working tree is
|
|
911
|
+
# already clean vs HEAD for them, i.e. the work IS committed. The old code
|
|
912
|
+
# ran `git add … && git commit`, which exited non-zero ("nothing to commit")
|
|
913
|
+
# and BLOCKED a correct, fully-committed campaign. Treat "already committed"
|
|
914
|
+
# as success: proceed to synthesis (the Verifier still gates correctness).
|
|
915
|
+
log " Bug #8 F-8 (D-20): Worker files already committed (commit race) — nothing to auto-commit; proceeding."
|
|
916
|
+
log_debug "[GOV] iter=$iter bug8=autocommit_noop_already_committed us_id=$us_id files='$_bug8_first5'"
|
|
917
|
+
elif git -C "$ROOT" add -- "${_bug8_add[@]}" && git -C "$ROOT" commit -q -m "chore(leader-recovery): commit Worker's uncommitted $us_id changes (Bug #8 F-8)"; then
|
|
851
918
|
log " Leader-recovery auto-commit OK (Worker files only) — Verifier will gate correctness."
|
|
852
919
|
else
|
|
853
920
|
log_error " Bug #8: leader-recovery auto-commit failed. Refusing synthesis. files: $_bug8_first5"
|
|
@@ -1434,6 +1501,8 @@ typeset -gA PANE_PROMPT_STUCK_SINCE
|
|
|
1434
1501
|
typeset -gA PANE_DISMISS_FAILED_COUNT
|
|
1435
1502
|
PROMPT_STALL_TIMEOUT="${PROMPT_STALL_TIMEOUT:-300}" # 5 min default
|
|
1436
1503
|
PROMPT_DISMISS_FAIL_LIMIT="${PROMPT_DISMISS_FAIL_LIMIT:-20}" # ~100s of fruitless dismiss attempts
|
|
1504
|
+
_validate_int_knob PROMPT_STALL_TIMEOUT 300 1 # D-19
|
|
1505
|
+
_validate_int_knob PROMPT_DISMISS_FAIL_LIMIT 20 1 # D-19
|
|
1437
1506
|
|
|
1438
1507
|
# v5.7 §4.17: generic no-progress timeout (codex Critic HIGH — closes the gap
|
|
1439
1508
|
# where an undetected prompt or alive-but-frozen Worker bypasses Layer 4).
|
|
@@ -1441,6 +1510,7 @@ PROMPT_DISMISS_FAIL_LIMIT="${PROMPT_DISMISS_FAIL_LIMIT:-20}" # ~100s of fruitle
|
|
|
1441
1510
|
# seconds AND signal file still missing, write BLOCKED `infra_failure` reason
|
|
1442
1511
|
# `worker_no_progress` so silent infinite-wait is impossible.
|
|
1443
1512
|
PROGRESS_NO_CHANGE_TIMEOUT="${PROGRESS_NO_CHANGE_TIMEOUT:-600}" # 10 min default
|
|
1513
|
+
_validate_int_knob PROGRESS_NO_CHANGE_TIMEOUT 600 1 # D-19
|
|
1444
1514
|
typeset -gA PANE_LAST_CHANGE_TS # epoch when content last changed
|
|
1445
1515
|
typeset -gA PANE_LAST_CONTENT_FOR_PROGRESS # captured content for diff
|
|
1446
1516
|
|
|
@@ -1449,6 +1519,7 @@ typeset -gA PANE_LAST_CONTENT_FOR_PROGRESS # captured content for diff
|
|
|
1449
1519
|
# CODEX_IDLE_GRACE_S (default 120s) before BLOCK. Per-pane bookkeeping to
|
|
1450
1520
|
# avoid granting it repeatedly. Bug Report #3 (BOS 2026-05-04).
|
|
1451
1521
|
CODEX_IDLE_GRACE_S="${CODEX_IDLE_GRACE_S:-120}"
|
|
1522
|
+
_validate_int_knob CODEX_IDLE_GRACE_S 120 1 # D-19
|
|
1452
1523
|
typeset -gA PANE_CODEX_IDLE_GRACED
|
|
1453
1524
|
# v0.14.2: per-verifier-pane trace flag — log the verdict-lookup outcome
|
|
1454
1525
|
# exactly once per byte-stasis transition. Bug Report #4 (BOS 2026-05-05).
|
|
@@ -2520,9 +2591,22 @@ poll_for_signal() {
|
|
|
2520
2591
|
if [[ -n "$pane_output_for_retry" ]] &&
|
|
2521
2592
|
( echo "$pane_output_for_retry" | grep -qiE '(^|[^[:digit:]])500([^[:digit:]]|$)' \
|
|
2522
2593
|
|| echo "$pane_output_for_retry" | grep -qiE '(^|[^[:digit:]])529([^[:digit:]]|$)' \
|
|
2594
|
+
|| echo "$pane_output_for_retry" | grep -qiE '(^|[^[:digit:]])429([^[:digit:]]|$)' \
|
|
2523
2595
|
|| echo "$pane_output_for_retry" | grep -qi 'overloaded' \
|
|
2524
2596
|
|| echo "$pane_output_for_retry" | grep -qi 'too many requests' \
|
|
2525
|
-
|| echo "$pane_output_for_retry" | grep -qi 'service unavailable'
|
|
2597
|
+
|| echo "$pane_output_for_retry" | grep -qi 'service unavailable' \
|
|
2598
|
+
|| echo "$pane_output_for_retry" | grep -qiE 'api error.*temporarily limiting requests' ); then
|
|
2599
|
+
# D-17a: the last pattern catches the claude TUI rate-limit banner
|
|
2600
|
+
# ("API Error: Server is temporarily limiting requests (not your usage
|
|
2601
|
+
# limit) · Rate limited") that previously fell through to the 600s
|
|
2602
|
+
# frozen-pane BLOCK with a misleading "deadlock" reason. It requires BOTH
|
|
2603
|
+
# the "API Error" banner prefix AND the distinctive multi-word phrase
|
|
2604
|
+
# "temporarily limiting requests" on the SAME line (codex MEDIUM): a Worker
|
|
2605
|
+
# implementing a rate-limiter feature, quoting the phrase, or merely
|
|
2606
|
+
# discussing API rate-limit handling does NOT false-trigger backoff — only
|
|
2607
|
+
# the actual error banner does. Routes to the bounded API backoff below
|
|
2608
|
+
# (5×30s) → recovers a transient limit, else BLOCKs as infra (recoverable),
|
|
2609
|
+
# not as a misleading frozen-pane deadlock.
|
|
2526
2610
|
is_api_text_retry=1
|
|
2527
2611
|
fi
|
|
2528
2612
|
|
|
@@ -2832,6 +2916,94 @@ _all_us_verified() {
|
|
|
2832
2916
|
return 0
|
|
2833
2917
|
}
|
|
2834
2918
|
|
|
2919
|
+
# D-18 helper: one final-verify pass for a single US. Returns 0=pass verdict,
|
|
2920
|
+
# 1=fail verdict, 2=infra-terminal (launch/poll hard fail — sentinel handling
|
|
2921
|
+
# already done by the poll). Reads/updates globals (VERIFIER_PANE, FINAL_VERIFIER_*,
|
|
2922
|
+
# SIGNAL_FILE, VERDICT_FILE, SESSION_CONFIG). Extracted from run_sequential_final_verify
|
|
2923
|
+
# so the caller can re-verify a per-US-passed US on a flake without duplicating the
|
|
2924
|
+
# dispatch/poll logic. Does NOT set FAILED_US (the caller owns that).
|
|
2925
|
+
_final_verify_one_us() {
|
|
2926
|
+
local us="$1" iter="$2"
|
|
2927
|
+
|
|
2928
|
+
# Temporarily override signal file to scope verifier to this US
|
|
2929
|
+
local orig_signal
|
|
2930
|
+
orig_signal=$(cat "$SIGNAL_FILE" 2>/dev/null)
|
|
2931
|
+
echo "{\"status\":\"verify\",\"us_id\":\"$us\",\"summary\":\"sequential final verify\"}" | atomic_write "$SIGNAL_FILE"
|
|
2932
|
+
|
|
2933
|
+
# Write scoped verifier trigger
|
|
2934
|
+
write_verifier_trigger "$iter"
|
|
2935
|
+
local verifier_prompt="$LOGS_DIR/iter-$(printf '%03d' $iter).verifier-prompt.md"
|
|
2936
|
+
|
|
2937
|
+
# Clean verifier pane
|
|
2938
|
+
local verifier_cmd
|
|
2939
|
+
verifier_cmd=$(tmux display-message -p -t "$VERIFIER_PANE" '#{pane_current_command}' 2>/dev/null)
|
|
2940
|
+
if [[ "$verifier_cmd" == "node" || "$verifier_cmd" == "claude" || "$verifier_cmd" == "codex" ]]; then
|
|
2941
|
+
tmux send-keys -t "$VERIFIER_PANE" C-c 2>/dev/null; sleep 0.5
|
|
2942
|
+
tmux send-keys -t "$VERIFIER_PANE" "/exit" C-m 2>/dev/null; sleep 2
|
|
2943
|
+
fi
|
|
2944
|
+
wait_for_pane_ready "$VERIFIER_PANE" 10 2>/dev/null || true
|
|
2945
|
+
|
|
2946
|
+
# Launch verifier. D-1: the FINAL (ALL) verify uses FINAL_VERIFIER_* (the
|
|
2947
|
+
# "final 엄격" knob — a configured stronger model, e.g. opus, for the final
|
|
2948
|
+
# gate), NOT the lighter per-US VERIFIER_*. This is the configured-final-model
|
|
2949
|
+
# distinction, distinct from the removed per-iteration verifier auto-upgrade.
|
|
2950
|
+
local verifier_launch
|
|
2951
|
+
if [[ "$FINAL_VERIFIER_ENGINE" = "codex" ]]; then
|
|
2952
|
+
verifier_launch="${CODEX_BIN:-codex} -m $FINAL_VERIFIER_CODEX_MODEL -c model_reasoning_effort=\"$FINAL_VERIFIER_CODEX_REASONING\" -c mcp_servers='{}' --disable plugins --dangerously-bypass-approvals-and-sandbox"
|
|
2953
|
+
launch_verifier_codex "$VERIFIER_PANE" "$verifier_prompt" "$iter" "$verifier_launch"
|
|
2954
|
+
else
|
|
2955
|
+
verifier_launch="$(build_claude_cmd tui "$FINAL_VERIFIER_MODEL" "" "" "$FINAL_VERIFIER_EFFORT")"
|
|
2956
|
+
launch_verifier_claude "$VERIFIER_PANE" "$verifier_prompt" "$iter" "$verifier_launch" || {
|
|
2957
|
+
log_error "Failed to launch final verifier for $us"
|
|
2958
|
+
return 2
|
|
2959
|
+
}
|
|
2960
|
+
fi
|
|
2961
|
+
|
|
2962
|
+
# Poll for verdict. D-4: distinguish rc==2 (hard-fail, sentinel already
|
|
2963
|
+
# written → terminal) from rc==1 (transient pane race/timeout) and give ONE
|
|
2964
|
+
# replace-pane + re-dispatch retry before failing the US — the F-10 retry
|
|
2965
|
+
# parity the per-US main verifier site has but this final-verify path lacked
|
|
2966
|
+
# (a single transient poll miss falsely failed a US at the most expensive
|
|
2967
|
+
# end-of-campaign moment, charging a bogus consecutive failure).
|
|
2968
|
+
rm -f "$VERDICT_FILE"
|
|
2969
|
+
local poll_rc=0
|
|
2970
|
+
poll_for_signal "$VERDICT_FILE" "$VERIFIER_HEARTBEAT" "$VERIFIER_PANE" "$verifier_launch" "Verifier-final" || poll_rc=$?
|
|
2971
|
+
if (( poll_rc == 2 )); then
|
|
2972
|
+
log_error "Verifier hard-fail (rc=2, sentinel written) for $us in final verify"
|
|
2973
|
+
return 2
|
|
2974
|
+
fi
|
|
2975
|
+
if (( poll_rc == 1 )); then
|
|
2976
|
+
log " Verifier-final transient poll fail for $us — replacing pane + retrying once (D-4)"
|
|
2977
|
+
replace_worker_pane "$VERIFIER_PANE" "verifier"
|
|
2978
|
+
VERIFIER_PANE=$(jq -r '.panes.verifier' "$SESSION_CONFIG")
|
|
2979
|
+
if [[ "$FINAL_VERIFIER_ENGINE" = "codex" ]]; then
|
|
2980
|
+
launch_verifier_codex "$VERIFIER_PANE" "$verifier_prompt" "$iter" "$verifier_launch"
|
|
2981
|
+
else
|
|
2982
|
+
launch_verifier_claude "$VERIFIER_PANE" "$verifier_prompt" "$iter" "$verifier_launch" || return 2
|
|
2983
|
+
fi
|
|
2984
|
+
rm -f "$VERDICT_FILE"; poll_rc=0
|
|
2985
|
+
poll_for_signal "$VERDICT_FILE" "$VERIFIER_HEARTBEAT" "$VERIFIER_PANE" "$verifier_launch" "Verifier-final" || poll_rc=$?
|
|
2986
|
+
if (( poll_rc != 0 )); then
|
|
2987
|
+
log_error "Verifier poll failed for $us after replace+retry (rc=$poll_rc)"
|
|
2988
|
+
return 2
|
|
2989
|
+
fi
|
|
2990
|
+
fi
|
|
2991
|
+
|
|
2992
|
+
# Bug #7 Fix-Q/R: reap verifier pane between per-US final verifications so
|
|
2993
|
+
# the previous codex/claude TUI cannot continue running while the next per-
|
|
2994
|
+
# US verifier dispatch reuses the same pane.
|
|
2995
|
+
_kill_pane_process "$VERIFIER_PANE" "verifier-final"
|
|
2996
|
+
_lock_sentinel "$VERDICT_FILE"
|
|
2997
|
+
# PR-0b-narrow: stamp leader handshake ack on the verdict (audit-only).
|
|
2998
|
+
_stamp_ack_field "$VERDICT_FILE"
|
|
2999
|
+
|
|
3000
|
+
# Read verdict
|
|
3001
|
+
local verdict
|
|
3002
|
+
verdict=$(jq -r '.verdict' "$VERDICT_FILE" 2>/dev/null)
|
|
3003
|
+
[[ "$verdict" == "pass" ]] && return 0
|
|
3004
|
+
return 1
|
|
3005
|
+
}
|
|
3006
|
+
|
|
2835
3007
|
run_sequential_final_verify() {
|
|
2836
3008
|
local iter="$1"
|
|
2837
3009
|
FAILED_US=""
|
|
@@ -2842,91 +3014,38 @@ run_sequential_final_verify() {
|
|
|
2842
3014
|
for us in $(echo "$US_LIST" | tr ',' ' '); do
|
|
2843
3015
|
log " Final verify: checking $us..."
|
|
2844
3016
|
|
|
2845
|
-
#
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
#
|
|
2851
|
-
|
|
2852
|
-
local
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
local
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
fi
|
|
2861
|
-
wait_for_pane_ready "$VERIFIER_PANE" 10 2>/dev/null || true
|
|
2862
|
-
|
|
2863
|
-
# Launch verifier. D-1: the FINAL (ALL) verify uses FINAL_VERIFIER_* (the
|
|
2864
|
-
# "final 엄격" knob — a configured stronger model, e.g. opus, for the final
|
|
2865
|
-
# gate), NOT the lighter per-US VERIFIER_*. This is the configured-final-model
|
|
2866
|
-
# distinction, distinct from the removed per-iteration verifier auto-upgrade.
|
|
2867
|
-
local verifier_launch
|
|
2868
|
-
if [[ "$FINAL_VERIFIER_ENGINE" = "codex" ]]; then
|
|
2869
|
-
verifier_launch="${CODEX_BIN:-codex} -m $FINAL_VERIFIER_CODEX_MODEL -c model_reasoning_effort=\"$FINAL_VERIFIER_CODEX_REASONING\" -c mcp_servers='{}' --disable plugins --dangerously-bypass-approvals-and-sandbox"
|
|
2870
|
-
launch_verifier_codex "$VERIFIER_PANE" "$verifier_prompt" "$iter" "$verifier_launch"
|
|
2871
|
-
else
|
|
2872
|
-
verifier_launch="$(build_claude_cmd tui "$FINAL_VERIFIER_MODEL" "" "" "$FINAL_VERIFIER_EFFORT")"
|
|
2873
|
-
launch_verifier_claude "$VERIFIER_PANE" "$verifier_prompt" "$iter" "$verifier_launch" || {
|
|
2874
|
-
log_error "Failed to launch final verifier for $us"
|
|
3017
|
+
# D-18: a US that already passed per-US gets up to FINAL_VERIFY_MAX_ATTEMPTS
|
|
3018
|
+
# final-verify attempts on a FAIL verdict (first pass wins). A verifier
|
|
3019
|
+
# false-fail (non-determinism) on already-correct, per-US-passed work must
|
|
3020
|
+
# REPRODUCE across all attempts before it charges a fix-loop failure — else a
|
|
3021
|
+
# single flake defeats a complete, correct campaign (D-16 dogfood: codex
|
|
3022
|
+
# false-failed pytest-36/36 work → fix-loop churn → stale BLOCK). A US that
|
|
3023
|
+
# never passed per-US (or a genuine regression) fails on the first attempt.
|
|
3024
|
+
local _fv_max=1
|
|
3025
|
+
# FINAL_VERIFY_MAX_ATTEMPTS is validated to 1..10 at declaration, so no clamp here.
|
|
3026
|
+
if echo ",$VERIFIED_US," | grep -q ",$us,"; then _fv_max=$FINAL_VERIFY_MAX_ATTEMPTS; fi
|
|
3027
|
+
local _fv_attempt=0 _fv_rc=1
|
|
3028
|
+
while (( _fv_attempt < _fv_max )); do
|
|
3029
|
+
(( _fv_attempt++ ))
|
|
3030
|
+
_final_verify_one_us "$us" "$iter"; _fv_rc=$?
|
|
3031
|
+
if (( _fv_rc == 2 )); then # infra-terminal (launch/poll hard fail) — no retry
|
|
2875
3032
|
FAILED_US="$us"
|
|
3033
|
+
log " Sequential final verify FAILED at $us (infra)"
|
|
3034
|
+
log_debug "[FLOW] iter=$iter phase=sequential_final_verify failed_us=$us reason=infra attempts=$_fv_attempt"
|
|
2876
3035
|
return 1
|
|
2877
|
-
}
|
|
2878
|
-
fi
|
|
2879
|
-
|
|
2880
|
-
# Poll for verdict. D-4: distinguish rc==2 (hard-fail, sentinel already
|
|
2881
|
-
# written → terminal) from rc==1 (transient pane race/timeout) and give ONE
|
|
2882
|
-
# replace-pane + re-dispatch retry before failing the US — the F-10 retry
|
|
2883
|
-
# parity the per-US main verifier site has but this final-verify path lacked
|
|
2884
|
-
# (a single transient poll miss falsely failed a US at the most expensive
|
|
2885
|
-
# end-of-campaign moment, charging a bogus consecutive failure).
|
|
2886
|
-
rm -f "$VERDICT_FILE"
|
|
2887
|
-
local poll_rc=0
|
|
2888
|
-
poll_for_signal "$VERDICT_FILE" "$VERIFIER_HEARTBEAT" "$VERIFIER_PANE" "$verifier_launch" "Verifier-final" || poll_rc=$?
|
|
2889
|
-
if (( poll_rc == 2 )); then
|
|
2890
|
-
log_error "Verifier hard-fail (rc=2, sentinel written) for $us in final verify"
|
|
2891
|
-
FAILED_US="$us"
|
|
2892
|
-
return 1
|
|
2893
|
-
fi
|
|
2894
|
-
if (( poll_rc == 1 )); then
|
|
2895
|
-
log " Verifier-final transient poll fail for $us — replacing pane + retrying once (D-4)"
|
|
2896
|
-
replace_worker_pane "$VERIFIER_PANE" "verifier"
|
|
2897
|
-
VERIFIER_PANE=$(jq -r '.panes.verifier' "$SESSION_CONFIG")
|
|
2898
|
-
if [[ "$FINAL_VERIFIER_ENGINE" = "codex" ]]; then
|
|
2899
|
-
launch_verifier_codex "$VERIFIER_PANE" "$verifier_prompt" "$iter" "$verifier_launch"
|
|
2900
|
-
else
|
|
2901
|
-
launch_verifier_claude "$VERIFIER_PANE" "$verifier_prompt" "$iter" "$verifier_launch" || { FAILED_US="$us"; return 1; }
|
|
2902
3036
|
fi
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
fi
|
|
2910
|
-
fi
|
|
2911
|
-
|
|
2912
|
-
# Bug #7 Fix-Q/R: reap verifier pane between per-US final verifications so
|
|
2913
|
-
# the previous codex/claude TUI cannot continue running while the next per-
|
|
2914
|
-
# US verifier dispatch reuses the same pane.
|
|
2915
|
-
_kill_pane_process "$VERIFIER_PANE" "verifier-final"
|
|
2916
|
-
_lock_sentinel "$VERDICT_FILE"
|
|
2917
|
-
# PR-0b-narrow: stamp leader handshake ack on the verdict (audit-only).
|
|
2918
|
-
_stamp_ack_field "$VERDICT_FILE"
|
|
2919
|
-
|
|
2920
|
-
# Check verdict
|
|
2921
|
-
local verdict
|
|
2922
|
-
verdict=$(jq -r '.verdict' "$VERDICT_FILE" 2>/dev/null)
|
|
2923
|
-
if [[ "$verdict" != "pass" ]]; then
|
|
3037
|
+
(( _fv_rc == 0 )) && break # pass verdict
|
|
3038
|
+
log " Sequential final verify: $us verdict=fail (attempt $_fv_attempt/$_fv_max)"
|
|
3039
|
+
log_debug "[FLOW] iter=$iter phase=sequential_final_verify us=$us attempt=$_fv_attempt/$_fv_max verdict=fail"
|
|
3040
|
+
(( _fv_attempt < _fv_max )) && log " D-18: re-verifying $us — a per-US-passed US's fail must reproduce to count (verifier flake guard)."
|
|
3041
|
+
done
|
|
3042
|
+
if (( _fv_rc != 0 )); then
|
|
2924
3043
|
FAILED_US="$us"
|
|
2925
|
-
log " Sequential final verify FAILED at $us"
|
|
2926
|
-
log_debug "[FLOW] iter=$iter phase=sequential_final_verify failed_us=$us verdict=$
|
|
3044
|
+
log " Sequential final verify FAILED at $us (failed all $_fv_attempt/$_fv_max attempt(s))"
|
|
3045
|
+
log_debug "[FLOW] iter=$iter phase=sequential_final_verify failed_us=$us verdict=fail attempts=$_fv_attempt max=$_fv_max"
|
|
2927
3046
|
return 1
|
|
2928
3047
|
fi
|
|
2929
|
-
log " Sequential final verify: $us PASSED"
|
|
3048
|
+
log " Sequential final verify: $us PASSED$([[ $_fv_attempt -gt 1 ]] && echo " (after $_fv_attempt attempts — earlier verdict was a verifier flake)")"
|
|
2930
3049
|
|
|
2931
3050
|
# Archive per-US final verdict
|
|
2932
3051
|
cp "$VERDICT_FILE" "$LOGS_DIR/iter-$(printf '%03d' $iter).final-verdict-${us}.json" 2>/dev/null
|
|
@@ -3760,7 +3879,15 @@ main() {
|
|
|
3760
3879
|
update_status "verifier" "running"
|
|
3761
3880
|
|
|
3762
3881
|
# --- Sequential final verify: per-US scoped checks instead of one big ALL verify ---
|
|
3763
|
-
|
|
3882
|
+
# D-21: do NOT take the sequential single-verifier path when consensus applies to
|
|
3883
|
+
# the final verify — it would BYPASS consensus entirely (run_sequential_final_verify
|
|
3884
|
+
# returns 0 before the consensus check below), so `--consensus final-only` was a
|
|
3885
|
+
# silent no-op in the DEFAULT per-us mode (the recommended consensus config). When
|
|
3886
|
+
# consensus is on, fall through to run_consensus_verification "ALL" (which uses the
|
|
3887
|
+
# stricter FINAL_VERIFIER_MODEL + FINAL_CONSENSUS_MODEL and the designed
|
|
3888
|
+
# claude+codex final path). The per-US timeout-prevention split is the off-consensus
|
|
3889
|
+
# optimization only.
|
|
3890
|
+
if [[ "$signal_us_id" == "ALL" && "$VERIFY_MODE" == "per-us" && -n "$US_LIST" ]] && ! _should_use_consensus "$signal_us_id"; then
|
|
3764
3891
|
log " Final ALL verify: using sequential per-US strategy (timeout prevention)"
|
|
3765
3892
|
local seq_rc=0
|
|
3766
3893
|
run_sequential_final_verify "$ITERATION" || seq_rc=$?
|