From b5667b9ccb9dc5efbabeb676e8e46c5420ba82f3 Mon Sep 17 00:00:00 2001 From: codejeet Date: Wed, 1 Apr 2026 13:49:42 +0200 Subject: [PATCH] Fix Ralph acceptance drift handling (#379) --- SGT_CONTEXT.md | 3 ++ sgt | 25 +++++++++++-- test_ralph_mode_config_and_state.sh | 57 +++++++++++++++++++++++++++++ 3 files changed, 82 insertions(+), 3 deletions(-) diff --git a/SGT_CONTEXT.md b/SGT_CONTEXT.md index 037014c..a78e25c 100644 --- a/SGT_CONTEXT.md +++ b/SGT_CONTEXT.md @@ -1670,3 +1670,6 @@ Required follow-up: ``` - 2026-04-01T13:28:48+02:00 — 2026-04-01 issue #392: regression coverage now pins Ralph lane issue-number accounting, the non-underfilled anti-false-completion override (declared acceptance waived/verified still forced back to pending while condition is unmet), and President's ralph-idle branch as refresh-not-wake distinct from ralph-underfilled refill wakes. +- 2026-04-01T13:34:17+02:00 — 2026-04-01 issue #394: README now includes a realistic PMKB-style Ralph-mode walkthrough (target=3, active=2, backlog=1, support-only excluded, duplicate excluded) and test_ralph_mode_latest_main_proof.sh now runs test_ralph_mode_realistic_rig_example.sh so latest-main proof covers documented operator-plausible Ralph behavior, anti-false-completion, and refillable underfill state. +- 2026-04-01T13:42:59+02:00 — Acceptance blocker sgt-acceptance-1775041349-4cbdb7d1 resolved. +- 2026-04-01T13:42:59+02:00 — Acceptance blocker sgt-acceptance-1775039964-1c53354b resolved. diff --git a/sgt b/sgt index 097c802..99ca435 100755 --- a/sgt +++ b/sgt @@ -6586,6 +6586,8 @@ completion_condition = completion_condition.strip() acceptance = plan.get("acceptance") if not isinstance(acceptance, dict): acceptance = {} +else: + acceptance = dict(acceptance) allowed_completion_status = {"pending", "completed", "verified", "blocked", "waived"} plan_completion_status = str(acceptance.get("status") or "").strip().lower() @@ -6710,12 +6712,26 @@ completion_doc = { "not_declared" if not (completion_condition or acceptance) else "pending" ), } +ralph_completion_blocked = bool(ralph_state.get("completion_blocked_by_condition")) +completion_rollup = str(completion_state.get("rollup") or "").strip().lower() +if ralph_completion_blocked or completion_rollup.startswith("ralph-"): + effective_acceptance = completion_state.get("acceptance") + if isinstance(effective_acceptance, dict): + completion_doc["acceptance"] = dict(effective_acceptance) + effective_status = str(completion_state.get("status") or "").strip().lower() + if effective_status: + completion_doc["status"] = effective_status + effective_rollup = str(completion_state.get("rollup") or "").strip() + if effective_rollup: + completion_doc["rollup"] = effective_rollup + completion_doc["details"] = one_line(completion_state.get("details") or completion_doc["details"]) + completion_doc["blocked_reason"] = one_line(completion_state.get("blocked_reason") or completion_doc["blocked_reason"]) timestamp_field = { "completed": "completed_at", "verified": "verified_at", "blocked": "blocked_at", "waived": "waived_at", -}.get(completion_status) +}.get(str(completion_doc.get("status") or "").strip().lower()) if timestamp_field: value = acceptance.get(timestamp_field) or completion_state.get(timestamp_field) if value: @@ -6858,9 +6874,10 @@ except Exception: acceptance = dict(previous_acceptance) acceptance = dict(acceptance) -for key in list(acceptance): +preserved_declared = {} +for key, value in list(acceptance.items()): if str(key).startswith("declared_"): - acceptance.pop(key, None) + preserved_declared[str(key)] = value declared_status = str(acceptance.get("status") or "").strip().lower() declared_terminal_fields = ("completed_at", "verified_at", "blocked_at", "blocked_reason", "waived_at") @@ -6881,6 +6898,8 @@ if status != "blocked": acceptance.pop("blocked_reason", None) if status != "waived": acceptance.pop("waived_at", None) +for key, value in preserved_declared.items(): + acceptance[key] = value now = datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z") completion["declared"] = bool(condition.strip() or acceptance) diff --git a/test_ralph_mode_config_and_state.sh b/test_ralph_mode_config_and_state.sh index 8fe8c99..0366867 100644 --- a/test_ralph_mode_config_and_state.sh +++ b/test_ralph_mode_config_and_state.sh @@ -370,6 +370,63 @@ assert acceptance.get("declared_status") == "waived", acceptance assert acceptance.get("declared_waived_at") == "2026-03-31T09:30:00Z", acceptance assert "waived_at" not in acceptance, acceptance PY + +export SGT_TEST_ISSUES_JSON="$TMP_ROOT/issues-third.json" +cat > "$SGT_ROOT/.sgt/rigs/demo3" <<'STATE' +https://github.com/acme/demo3 +STATE +mkdir -p "$SGT_ROOT/rigs/demo3" +cat > "$SGT_ROOT/.sgt/polecats/demo3-worker" <<'STATE' +RIG=demo3 +REPO=https://github.com/acme/demo3 +ISSUE=31 +BRANCH=sgt/demo3-worker +WORKTREE=/tmp/demo3-worker +SESSION=sgt-demo-worker +STATE + +cat > "$SGT_ROOT/rigs/demo3/SGT_PLAN.json" <<'JSON' +{ + "version": 1, + "rig": "demo3", + "policy": { "max_in_flight": 1 }, + "completion_condition": "Fresh-state proof passes.", + "acceptance": { + "status": "blocked", + "details": "Prior proof run is blocked on a stale operator note.", + "blocked_at": "2026-03-31T10:45:00Z", + "blocked_reason": "waiting for operator signoff" + }, + "tasks": [] +} +JSON + +sgt config ralph demo3 --enable --condition "Keep a live lane running until the new proof lands" --target 1 >/dev/null +sgt plan tick demo3 >/dev/null 2>&1 +sgt status --json > "$SGT_ROOT/status-third.json" +sgt plan tick demo3 >/dev/null 2>&1 + +python3 - "$SGT_ROOT/.sgt/plan-state/demo3.json" <<'PY' +import json +import sys + +with open(sys.argv[1], "r", encoding="utf-8") as fh: + plan_state = json.load(fh) + +completion = plan_state.get("completion") or {} +assert completion.get("status") == "pending", completion +assert completion.get("rollup") == "ralph-underfilled", completion +assert completion.get("blocked_reason") == "ralph condition unmet: Keep a live lane running until the new proof lands", completion +assert "Ralph mode remains active" in completion.get("details", ""), completion +acceptance = completion.get("acceptance") or {} +assert acceptance.get("status") == "pending", acceptance +assert acceptance.get("details") == "Prior proof run is blocked on a stale operator note.", acceptance +assert acceptance.get("declared_status") == "blocked", acceptance +assert acceptance.get("declared_blocked_at") == "2026-03-31T10:45:00Z", acceptance +assert acceptance.get("declared_blocked_reason") == "waiting for operator signoff", acceptance +assert "blocked_at" not in acceptance, acceptance +assert "blocked_reason" not in acceptance, acceptance +PY BASH echo "ALL TESTS PASSED"