Scale the blocker window by lane risk, converging with risk-nexus
risk-nexus accepted the offer to match their convention rather than grow a second one, and published it: 14d critical/high, 30d medium, 60d low, nothing auto-closing on staleness alone. Their preference — point warden route gaps at those windows and the two registers agree without a shared mechanism — is better than a joint tool. blocker_stale_days() now maps lane risk onto those windows. A flat 14 would have been wrong in both directions: too aggressive for a low-risk pointer, and it treated an admin PAT lane the same as one. ungraded takes the shortest window, not the longest. ADR-0007 makes an absent grade a defect and ADR-0008 makes a grade cover the whole path, so a lane nobody graded is the one whose blocker deserves least trust. Encoding that as 60 days would have been the fail-open default this repo already fixed once. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
55f0f47a02
commit
e18894ee5b
7 changed files with 96 additions and 24 deletions
|
|
@ -87,6 +87,29 @@ DEFAULT_STALE_DAYS = 90
|
|||
# ~15 interim lanes, surfaces about one lane a day rather than a wall of them.
|
||||
DEFAULT_BLOCKER_STALE_DAYS = 14
|
||||
|
||||
# Scaled by the lane's own risk grade, matching risk-nexus's stall windows
|
||||
# (14d critical/high, 30d medium, 60d low — docs/method/check-procedure.md).
|
||||
# They offered the convention rather than a joint tool: point `warden route gaps`
|
||||
# at the same windows and the two registers agree without a shared mechanism.
|
||||
#
|
||||
# `ungraded` gets the shortest window, not the longest. ADR-0007 already decided
|
||||
# an absent grade is a defect and ADR-0008 that a grade covers the whole path;
|
||||
# a lane nobody has graded is exactly the one whose blocker is least trustworthy.
|
||||
BLOCKER_STALE_DAYS_BY_RISK = {
|
||||
"high": 14,
|
||||
"ungraded": 14,
|
||||
"standard": 30,
|
||||
"accepted": 60,
|
||||
"low": 60,
|
||||
}
|
||||
|
||||
|
||||
def blocker_stale_days(risk: Optional[str], override: Optional[int] = None) -> int:
|
||||
"""Days a lane's blocker may go unverified, scaled by what the lane holds."""
|
||||
if override is not None:
|
||||
return override
|
||||
return BLOCKER_STALE_DAYS_BY_RISK.get(risk or "ungraded", DEFAULT_BLOCKER_STALE_DAYS)
|
||||
|
||||
|
||||
def days_since_review(reviewed: str, *, today: Optional[date] = None) -> int:
|
||||
"""Calendar days between reviewed date (YYYY-MM-DD) and today."""
|
||||
|
|
@ -229,12 +252,16 @@ class Catalog:
|
|||
def stale_gaps(
|
||||
self,
|
||||
include_draft: bool = False,
|
||||
threshold_days: int = DEFAULT_BLOCKER_STALE_DAYS,
|
||||
threshold_days: Optional[int] = None,
|
||||
*,
|
||||
today: Optional[date] = None,
|
||||
) -> List[RouteEntry]:
|
||||
"""Interim lanes whose blocker is due a re-check.
|
||||
|
||||
The window scales with the lane's risk grade unless `threshold_days`
|
||||
overrides it -- a blocker on a lane holding an admin PAT should not go
|
||||
unverified as long as one on a low-risk pointer.
|
||||
|
||||
A lane counts as stale when its review date is past the threshold **or**
|
||||
when the review was never a verification at all. An `asked-and-waiting`
|
||||
entry is the case that motivated this: it looks freshly reviewed on the
|
||||
|
|
@ -244,7 +271,8 @@ class Catalog:
|
|||
for e in self.gaps(include_draft=include_draft):
|
||||
d = e.effective_delegation
|
||||
reviewed = d.reviewed or e.reviewed
|
||||
if is_review_stale(reviewed, threshold_days=threshold_days, today=today):
|
||||
window = blocker_stale_days(e.risk, threshold_days)
|
||||
if is_review_stale(reviewed, threshold_days=window, today=today):
|
||||
out.append(e)
|
||||
elif d.verified is not None and not d.is_verified:
|
||||
out.append(e)
|
||||
|
|
@ -287,14 +315,12 @@ class Catalog:
|
|||
)
|
||||
# Interim blockers run on their own, much shorter cadence -- a stale
|
||||
# pointer and an unanswered blocker are not the same kind of drift.
|
||||
stale_interim = len(self.stale_gaps(
|
||||
include_draft=True, threshold_days=DEFAULT_BLOCKER_STALE_DAYS, today=today
|
||||
))
|
||||
stale_interim = len(self.stale_gaps(include_draft=True, today=today))
|
||||
if stale_interim:
|
||||
warnings.append(
|
||||
f"{stale_interim} interim delegation"
|
||||
f"{'' if stale_interim == 1 else 's'} need re-verifying "
|
||||
f"({DEFAULT_BLOCKER_STALE_DAYS}d blocker cadence) — see `warden route gaps`"
|
||||
f"(risk-scaled blocker cadence) — see `warden route gaps`"
|
||||
)
|
||||
|
||||
return CatalogFreshness(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue