From f5ca1c2cbda173308c4d5bd3980e098a14eb196f Mon Sep 17 00:00:00 2001 From: Sollan Systems Date: Thu, 9 Jul 2026 22:47:23 -0400 Subject: [PATCH] fix(metrics): recognize approval_requested/replanned as honest-red outcome tokens (#36) --- scripts/metrics.py | 4 ++-- scripts/test_metrics.py | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/scripts/metrics.py b/scripts/metrics.py index 9d124c9..046d6bf 100644 --- a/scripts/metrics.py +++ b/scripts/metrics.py @@ -95,8 +95,8 @@ # (so they are not surfaced as an "unrecognized" synonym). Any outcome token in # neither set is surfaced under provenance.unrecognized_outcomes. _HONEST_RED_OUTCOME_TOKENS = ( - "repair_triggered", "task_failed", "replan", "reverted", "revert", - "blocked", "terminated", "aborted", "failed", + "repair_triggered", "task_failed", "approval_requested", "replanned", + "replan", "reverted", "revert", "blocked", "terminated", "aborted", "failed", ) _KNOWN_OUTCOME_TOKENS = frozenset(_SUCCESS_OUTCOME_TOKENS) | frozenset(_HONEST_RED_OUTCOME_TOKENS) diff --git a/scripts/test_metrics.py b/scripts/test_metrics.py index 6a57a25..10bef76 100644 --- a/scripts/test_metrics.py +++ b/scripts/test_metrics.py @@ -9,6 +9,8 @@ import metrics +from loop import emit + _REPO = Path(__file__).resolve().parent.parent _EXAMPLE = _REPO / "examples" / "coverage-repair" @@ -687,3 +689,18 @@ def test_baseline_refuses_when_a_counted_rp_record_is_unanchored(tmp_path): ok, _sc, reasons = metrics.build_baseline(ws, "ws") assert ok is False assert any("anchor" in r.lower() for r in reasons) + + +def test_every_emitted_outcome_token_is_recognized(tmp_path): + # Round-trip: every token emit.append_iteration will write must be a token + # metrics.py recognizes — none may leak into provenance.unrecognized_outcomes. + # Iterate the real tuple so a new emit outcome can't silently drift unrecognized. + ws = tmp_path / "ws" + emit.open_contract(ws) + # Drop the scaffold-seeded RUNLOG (it carries the {{ITERATION_OUTCOME}} placeholder, + # issue #40) so append_iteration writes a clean header and only real outcome tokens. + (ws / "RUNLOG.md").unlink() + for iteration_id, outcome in enumerate(emit._ITERATION_OUTCOMES, start=1): + emit.append_iteration(ws, iteration_id=iteration_id, outcome=outcome) + sc = metrics.compute_metrics(ws) + assert sc["provenance"]["unrecognized_outcomes"] == []