feat(legacy-meter): scale retirement quiet period to call volume
A clean review window only proves an interface was silent for that window. For a heavily used interface that is weak evidence -- an infrequent caller can fall outside the window entirely, so the interface reads as retirable while still having consumers. _retirement_state now also requires silence since last_seen_at, scaled by all-time volume (RETIREMENT_QUIET_LADDER): 7d under 100 calls, 30d under 10k, 60d above. Existing guards (retired / manual hold / replacement ref / replacement verified / in-window traffic) still take precedence. On the 2026-08-20 capture this makes 15 of 19 legacy interfaces retirable and holds 4: the three six-figure /workstreams/ read paths, and GET /tasks/?workstream_id which still has live traffic. Unblocks the bulk of Wave E2 in the cutover slice plan. 7 new tests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
be7f2632c3
commit
93e95f93e7
4 changed files with 229 additions and 19 deletions
|
|
@ -486,3 +486,137 @@ class TestWorkplanAliasesAndLegacyMeter:
|
|||
assert items["rest_api:DELETE /workstreams/{workstream_id}/dependencies/{dep_id}"]["window"]["components"] == {
|
||||
"old-dep-archiver": 1
|
||||
}
|
||||
|
||||
|
||||
# --- retirement quiet-period ladder (STATE-WP-0079-T05) ---------------------
|
||||
#
|
||||
# A clean review window only proves silence for that window. For an interface
|
||||
# that carried heavy traffic, that is weak evidence: an infrequent caller can
|
||||
# fall outside the window entirely. The ladder requires silence proportional to
|
||||
# all-time volume before an interface becomes a retirement candidate.
|
||||
|
||||
|
||||
def _iface(**kw):
|
||||
from types import SimpleNamespace
|
||||
|
||||
defaults = dict(
|
||||
status="legacy",
|
||||
manual_hold=False,
|
||||
hold_reason=None,
|
||||
replacement_ref="replacement",
|
||||
replacement_verified=True,
|
||||
)
|
||||
defaults.update(kw)
|
||||
return SimpleNamespace(**defaults)
|
||||
|
||||
|
||||
def test_required_quiet_days_scales_with_volume():
|
||||
from api.services.legacy_meter import _required_quiet_days
|
||||
|
||||
assert _required_quiet_days(0) == 0
|
||||
assert _required_quiet_days(1) == 7
|
||||
assert _required_quiet_days(99) == 7
|
||||
assert _required_quiet_days(100) == 30
|
||||
assert _required_quiet_days(9_999) == 30
|
||||
assert _required_quiet_days(10_000) == 60
|
||||
assert _required_quiet_days(511_406) == 60
|
||||
|
||||
|
||||
def test_high_volume_interface_held_until_quiet_period_elapses():
|
||||
"""A six-figure interface quiet for 9 days is not yet retirable."""
|
||||
from api.services.legacy_meter import _retirement_state
|
||||
|
||||
window_end = datetime(2026, 8, 20, tzinfo=timezone.utc)
|
||||
candidate, reason = _retirement_state(
|
||||
_iface(),
|
||||
0,
|
||||
all_time_calls=144_086,
|
||||
last_seen_at=datetime(2026, 8, 11, tzinfo=timezone.utc),
|
||||
window_end=window_end,
|
||||
)
|
||||
assert candidate is False
|
||||
assert "9d of 60d required" in reason
|
||||
|
||||
|
||||
def test_high_volume_interface_retirable_once_quiet_long_enough():
|
||||
from api.services.legacy_meter import _retirement_state
|
||||
|
||||
window_end = datetime(2026, 8, 20, tzinfo=timezone.utc)
|
||||
candidate, reason = _retirement_state(
|
||||
_iface(),
|
||||
0,
|
||||
all_time_calls=144_086,
|
||||
last_seen_at=datetime(2026, 5, 1, tzinfo=timezone.utc),
|
||||
window_end=window_end,
|
||||
)
|
||||
assert candidate is True
|
||||
assert "60d required" in reason
|
||||
|
||||
|
||||
def test_low_volume_interface_uses_short_quiet_period():
|
||||
from api.services.legacy_meter import _retirement_state
|
||||
|
||||
window_end = datetime(2026, 8, 20, tzinfo=timezone.utc)
|
||||
candidate, _ = _retirement_state(
|
||||
_iface(),
|
||||
0,
|
||||
all_time_calls=2,
|
||||
last_seen_at=datetime(2026, 8, 12, tzinfo=timezone.utc),
|
||||
window_end=window_end,
|
||||
)
|
||||
assert candidate is True
|
||||
|
||||
|
||||
def test_never_used_interface_needs_no_quiet_period():
|
||||
from api.services.legacy_meter import _retirement_state
|
||||
|
||||
candidate, reason = _retirement_state(
|
||||
_iface(),
|
||||
0,
|
||||
all_time_calls=0,
|
||||
last_seen_at=None,
|
||||
window_end=datetime(2026, 8, 20, tzinfo=timezone.utc),
|
||||
)
|
||||
assert candidate is True
|
||||
assert reason == "no measured usage in review window"
|
||||
|
||||
|
||||
def test_in_window_traffic_still_wins_over_quiet_ladder():
|
||||
"""Traffic inside the window blocks retirement regardless of volume."""
|
||||
from api.services.legacy_meter import _retirement_state
|
||||
|
||||
candidate, reason = _retirement_state(
|
||||
_iface(),
|
||||
3,
|
||||
all_time_calls=5,
|
||||
last_seen_at=datetime(2026, 8, 19, tzinfo=timezone.utc),
|
||||
window_end=datetime(2026, 8, 20, tzinfo=timezone.utc),
|
||||
)
|
||||
assert candidate is False
|
||||
assert "3 call(s) in review window" in reason
|
||||
|
||||
|
||||
def test_existing_guards_precede_quiet_ladder():
|
||||
from api.services.legacy_meter import _retirement_state
|
||||
|
||||
window_end = datetime(2026, 8, 20, tzinfo=timezone.utc)
|
||||
long_quiet = dict(
|
||||
all_time_calls=1,
|
||||
last_seen_at=datetime(2026, 1, 1, tzinfo=timezone.utc),
|
||||
window_end=window_end,
|
||||
)
|
||||
assert _retirement_state(_iface(status="retired"), 0, **long_quiet) == (
|
||||
False,
|
||||
"already retired",
|
||||
)
|
||||
assert _retirement_state(
|
||||
_iface(manual_hold=True, hold_reason="pinned"), 0, **long_quiet
|
||||
) == (False, "pinned")
|
||||
assert _retirement_state(_iface(replacement_ref=" "), 0, **long_quiet) == (
|
||||
False,
|
||||
"missing replacement reference",
|
||||
)
|
||||
assert _retirement_state(_iface(replacement_verified=False), 0, **long_quiet) == (
|
||||
False,
|
||||
"replacement not verified",
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue