From 1a507ff8a6cbe7a51f3777e27c2a2d438c7ca562 Mon Sep 17 00:00:00 2001 From: saagpatel Date: Sat, 20 Jun 2026 00:02:34 -0700 Subject: [PATCH] fix(operator-trend): floor rererestore persistence magnitude (T3-2 phase 5) The rererestore persistence builder decremented per-event `magnitude` without the `max(0.0, ..)` floor its rebuild sibling applies. `magnitude` is per-event evidence strength (non-negative by concept; `sign` carries the direction), so an over-penalized confirmation event went negative and -- after `sign` -- counted against its own side, producing a spurious clearance-leaning persistence_score. Floor the 4 decrements to match rebuild. On the same structural input the two tiers now agree (persistence_score 0.31 == 0.31, was 0.31 vs 0.24), and the rerererestore wrapper that delegates to it no longer yields a spurious negative (-0.08 -> 0.09). The phase-4 composer golden caught this exactly: the regenerated golden diff is 3 score lines, and the anchor test flips from pinning the divergence to pinning the convergence (test_magnitude_floor_unifies_rebuild_and_rererestore_tiers). No existing test pinned the buggy value -- the negative-magnitude path was uncharacterized, which is why the bug survived. --- ...r_trend_closure_forecast_reset_controls.py | 8 ++-- tests/golden/composer_contract.golden.json | 6 +-- tests/golden/enumerate_composer_contract.py | 38 ++++++++++--------- tests/test_composer_golden_contract.py | 29 +++++++------- 4 files changed, 42 insertions(+), 39 deletions(-) diff --git a/src/operator_trend_closure_forecast_reset_controls.py b/src/operator_trend_closure_forecast_reset_controls.py index 8fc4e29..d4dd7c7 100644 --- a/src/operator_trend_closure_forecast_reset_controls.py +++ b/src/operator_trend_closure_forecast_reset_controls.py @@ -5773,11 +5773,11 @@ def closure_forecast_reset_reentry_rebuild_reentry_restore_rererestore_persisten if freshness_status == "fresh": magnitude += 0.10 elif freshness_status == "mixed-age": - magnitude -= 0.10 + magnitude = max(0.0, magnitude - 0.10) if momentum_status in {"reversing", "unstable"}: - magnitude -= 0.15 + magnitude = max(0.0, magnitude - 0.15) if stability_status == "oscillating": - magnitude -= 0.15 + magnitude = max(0.0, magnitude - 0.15) if ( event.get( "closure_forecast_reset_reentry_rebuild_reentry_restore_rerestore_reset_status", @@ -5785,7 +5785,7 @@ def closure_forecast_reset_reentry_rebuild_reentry_restore_rererestore_persisten ) != "none" ): - magnitude -= 0.15 + magnitude = max(0.0, magnitude - 0.15) weighted_total += sign * magnitude * weight weight_sum += weight diff --git a/tests/golden/composer_contract.golden.json b/tests/golden/composer_contract.golden.json index e7f2e07..4c6c298 100644 --- a/tests/golden/composer_contract.golden.json +++ b/tests/golden/composer_contract.golden.json @@ -637,7 +637,7 @@ "reversing": { "closure_forecast_reset_reentry_rebuild_reentry_restore_rerererestore_age_runs": 2, "closure_forecast_reset_reentry_rebuild_reentry_restore_rerererestore_persistence_reason": "The re-re-re-restored rebuilt re-entry posture is already weakening, so it is being softened again.", - "closure_forecast_reset_reentry_rebuild_reentry_restore_rerererestore_persistence_score": -0.08, + "closure_forecast_reset_reentry_rebuild_reentry_restore_rerererestore_persistence_score": 0.09, "closure_forecast_reset_reentry_rebuild_reentry_restore_rerererestore_persistence_status": "reversing", "recent_reset_reentry_rebuild_reentry_restore_rerererestore_persistence_path": "rebuilt-confirmation-reentry -> strong-conf" }, @@ -894,7 +894,7 @@ "clamp-divergence": { "closure_forecast_reset_reentry_rebuild_reentry_restore_rererestore_age_runs": 2, "closure_forecast_reset_reentry_rebuild_reentry_restore_rererestore_persistence_reason": "Confirmation-side re-re-restored posture has stayed aligned long enough to keep the stronger rerestored forecast in place.", - "closure_forecast_reset_reentry_rebuild_reentry_restore_rererestore_persistence_score": 0.24, + "closure_forecast_reset_reentry_rebuild_reentry_restore_rererestore_persistence_score": 0.31, "closure_forecast_reset_reentry_rebuild_reentry_restore_rererestore_persistence_status": "holding-confirmation-rebuild-reentry-rererestore", "recent_reset_reentry_rebuild_reentry_restore_rererestore_persistence_path": "strong-conf -> decrement-conf" }, @@ -929,7 +929,7 @@ "sparse": { "closure_forecast_reset_reentry_rebuild_reentry_restore_rererestore_age_runs": 0, "closure_forecast_reset_reentry_rebuild_reentry_restore_rererestore_persistence_reason": "", - "closure_forecast_reset_reentry_rebuild_reentry_restore_rererestore_persistence_score": 0.24, + "closure_forecast_reset_reentry_rebuild_reentry_restore_rererestore_persistence_score": 0.31, "closure_forecast_reset_reentry_rebuild_reentry_restore_rererestore_persistence_status": "none", "recent_reset_reentry_rebuild_reentry_restore_rererestore_persistence_path": "sparse-none -> strong-conf -> decrement-conf" }, diff --git a/tests/golden/enumerate_composer_contract.py b/tests/golden/enumerate_composer_contract.py index 32848be..a7f33f3 100644 --- a/tests/golden/enumerate_composer_contract.py +++ b/tests/golden/enumerate_composer_contract.py @@ -8,23 +8,26 @@ outputs feed the operator decision-quality string contract. This enumerator captures those composers across a status-spanning corpus into a frozen golden. -Why it must exist BEFORE any ``recovery_state(target, *, depth)`` collapse: the -per-tier composer families (rebuild / rererestore / rerererestore) are near-clones -with a real, OBSERVABLE logic delta. Concretely, the rebuild persistence builder -floors per-event magnitude at zero:: +Why it exists BEFORE any ``recovery_state(target, *, depth)`` collapse: the per-tier +composer families (rebuild / rererestore / rerererestore) are near-clones, and the +net both pins all 44 composers for the collapse and guards a real correctness fix. +``magnitude`` is per-event evidence strength (non-negative by concept; ``sign`` +carries the direction). The rebuild persistence builder floors it at zero on every +decrement:: magnitude = max(0.0, magnitude - 0.10) # rebuild -while the rererestore builder does not:: - - magnitude -= 0.10 # rererestore - -That unclamped negative survives the symmetric final ``clamp_round(.., -0.95, -0.95)`` and reaches ``persistence_score`` -- so the SAME structural input yields a -different score per tier (the pinned "clamp-divergence" scenario records 0.31 for -rebuild vs 0.24 for rererestore). The str->str golden cannot -see this. Any collapse of these families must reproduce THIS golden byte-for-byte; -an intentional unification shows up here as a reviewed diff, never a silent one. +The rererestore builder originally omitted that floor (``magnitude -= 0.10``), so an +over-penalized confirmation event could go NEGATIVE and -- after ``sign`` -- count +against its own side, producing a spurious clearance-leaning ``persistence_score``. +That divergence was invisible to the str->str golden. T3-2 phase 5 fixed it: the +rererestore builder now floors exactly like rebuild, so the SAME structural input +yields the SAME score across the tiers. The "clamp-divergence" scenario (the input +that drives a decrement event's magnitude below zero) now records 0.31 for BOTH +rebuild and rererestore, and ``test_magnitude_floor_unifies_rebuild_and_rererestore_tiers`` +pins that convergence -- a regression that re-removed the floor would re-diverge and +fail. Any future change to these families must reproduce THIS golden byte-for-byte; +an intentional behavior change shows up here as a reviewed diff, never a silent one. Fixed-harness validity: the injected callables below are deterministic stand-ins, not the production wiring (which threads ~15 helpers per composer). The golden's @@ -32,7 +35,7 @@ behavior under a FIXED harness, and the collapse must reproduce it under the SAME harness. The harness only has to be (a) held constant between generation and check (it is -- one enumerator) and (b) rich enough to exercise the real branches (the -companion test guards a non-degenerate branch count and the clamp divergence). +companion test guards a non-degenerate branch count and the magnitude-floor fix). Regenerate only with an intentional, reviewed behavior change:: @@ -373,8 +376,9 @@ def _for_target_scenarios() -> list[ } reversing_target = {**conf_target, "closure_forecast_momentum_status": "reversing"} return [ - # The divergence fixture: confirmation side, one strong event + one decrement - # event -> rebuild floors the decrement to 0, rererestore keeps it negative. + # The floor-path fixture: confirmation side, one strong event + one decrement + # event whose magnitude crosses below zero. Both rebuild and the (now-fixed) + # rererestore floor it at 0, so they agree; the scenario name is historical. ("clamp-divergence", conf_target, [strong, decrement], {}), ("sustained-confirmation", conf_target, [strong, strong, strong], {}), ( diff --git a/tests/test_composer_golden_contract.py b/tests/test_composer_golden_contract.py index c44b0cf..242cbb1 100644 --- a/tests/test_composer_golden_contract.py +++ b/tests/test_composer_golden_contract.py @@ -83,24 +83,23 @@ def test_composer_reproduces_golden(name: str) -> None: assert LIVE[name] == GOLDEN[name] -def test_clamp_divergence_between_tiers_is_pinned() -> None: - # The whole reason this net exists: the rebuild and rererestore persistence - # builders are near-clones, but the rebuild per-event magnitude floor makes the - # SAME structural input ("clamp-divergence" scenario) yield a different score. - # A naive collapse that translated one tier into the other would erase this and - # this assertion would fail -- which is the point. +def test_magnitude_floor_unifies_rebuild_and_rererestore_tiers() -> None: + # T3-2 phase 5 fix: the rererestore persistence builder now floors per-event + # magnitude at 0 (max(0.0, ..)) exactly like rebuild -- previously it omitted the + # floor (magnitude -= X), letting an over-penalized confirmation event go negative + # and count against its own side. With the floor, the SAME structural input (the + # "clamp-divergence" floor-path scenario) yields the SAME score across the tiers. + # This pins the fix: a regression that re-removed the floor would re-diverge here. rebuild = GOLDEN[_REBUILD_PERSISTENCE]["clamp-divergence"] rererestore = GOLDEN[_RERERESTORE_PERSISTENCE]["clamp-divergence"] rebuild_score = rebuild[_REBUILD_SCORE_KEY] rererestore_score = rererestore[_RERERESTORE_SCORE_KEY] - assert rebuild_score != rererestore_score, (rebuild_score, rererestore_score) - # Rebuild floors the negative decrement term at 0, so it stays the higher score. - assert rebuild_score > rererestore_score, (rebuild_score, rererestore_score) - - # The rerererestore tier is a thin wrapper that translates keys and delegates to - # the rererestore builder, so it also does NOT floor the magnitude. Pin it as a - # named three-way invariant: it diverges from the flooring rebuild tier (F7). A - # collapse that accidentally gave it rebuild's floor would break this. + assert rebuild_score == rererestore_score, (rebuild_score, rererestore_score) + + # The rerererestore tier wraps + delegates to the now-fixed rererestore builder, + # so this confirmation-side scenario no longer yields a spurious NEGATIVE score + # (the bug symptom). It need not equal rebuild -- the wrapper feeds translated + # inputs -- but it must reflect the floor (be non-negative here). rerererestore = GOLDEN[_RERERESTORE_WRAP_PERSISTENCE]["clamp-divergence"] rerererestore_score = rerererestore[_RERERESTORE_WRAP_SCORE_KEY] - assert rerererestore_score != rebuild_score, (rerererestore_score, rebuild_score) + assert rerererestore_score >= 0.0, rerererestore_score