WARDEN-WP-0033-T05: split the stale cadences, and record how a blocker was verified
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

The 90-day --stale-days default on `warden route gaps` was not a loose threshold,
it was an inert one: the delegation register was created 2026-08-15, so it could
not have fired before November. It was inherited from the catalog pointer cadence
and applied to a claim with a completely different half-life.

Two changes. DEFAULT_BLOCKER_STALE_DAYS = 14 now governs interim blockers, while
DEFAULT_STALE_DAYS = 90 keeps governing pointer freshness -- "is this the right
owner and page" is quarterly, "has the owner answered" is not. 14 is calibrated
on blockers that actually cost something: ten days for the secrets-engine lanes,
one for RISK-F-0001, roughly fifty for FLEX-WP-0007.

The second change matters more. `reviewed` records when someone touched an entry,
which is indistinguishable from re-checking it -- six lanes read as freshly
reviewed today because I typed in them. `verified:` now says how the claim was
established, and asked-and-waiting explicitly does NOT count: that is the state
the secrets-engine blocker sat in for ten days while looking current. A lane in
that state is stale at zero days old, and key-cape-oidc-login proves it works.

8 of 14 interim lanes are honestly marked unverified rather than given a fresh
date they did not earn.

--fail-on-stale exits 3 for a cron or gate. No CI test on age: a date-triggered
failure breaks the build for whoever commits next instead of whoever owns the
blocker. The CI test is structural -- every interim lane must record how it was
verified -- so it fails on the commit that introduces the omission.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
tegwick 2026-08-21 13:26:10 +02:00
parent a565e62b2f
commit 55f0f47a02
8 changed files with 335 additions and 24 deletions

View file

@ -69,9 +69,24 @@ _VALID_STATUS = ("active", "draft")
_VALID_LANES = ("secret", "login")
_VALID_ROTATION_METHODS = ("rotate", "re-establish")
# Default review cadence — see wiki/AccessRouting.md#drift-review-cadence
# Default review cadence for a catalog pointer — "is this still the right owner
# and page?" That is a genuinely quarterly question, so 90 days is right for it.
# See wiki/AccessRouting.md#drift-review-cadence
DEFAULT_STALE_DAYS = 90
# Cadence for an interim lane's *blocker*, which is a different kind of claim
# with a much shorter half-life: "has the intended owner answered / can they
# front this yet?" (WARDEN-WP-0033-T05).
#
# 14 rather than 90 because 90 was never a loose default, it was an inert one --
# the delegation register was created 2026-08-15, so a 90-day threshold could not
# fire before November and never had. Calibrated instead against blockers that
# actually went stale: the secrets-engine lanes cost ten days, RISK-F-0001
# invalidated an ops-warden blocker in one, and the FLEX-WP-0007 claim was
# repeated by two repos for roughly fifty. 14 catches the ten-day cases and, at
# ~15 interim lanes, surfaces about one lane a day rather than a wall of them.
DEFAULT_BLOCKER_STALE_DAYS = 14
def days_since_review(reviewed: str, *, today: Optional[date] = None) -> int:
"""Calendar days between reviewed date (YYYY-MM-DD) and today."""
@ -214,16 +229,25 @@ class Catalog:
def stale_gaps(
self,
include_draft: bool = False,
threshold_days: int = DEFAULT_STALE_DAYS,
threshold_days: int = DEFAULT_BLOCKER_STALE_DAYS,
*,
today: Optional[date] = None,
) -> List[RouteEntry]:
"""Interim lanes whose delegation review is past the cadence threshold."""
"""Interim lanes whose blocker is due a re-check.
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
day the question is asked and stays that way while nobody answers.
"""
out: List[RouteEntry] = []
for e in self.gaps(include_draft=include_draft):
reviewed = e.effective_delegation.reviewed or e.reviewed
d = e.effective_delegation
reviewed = d.reviewed or e.reviewed
if is_review_stale(reviewed, threshold_days=threshold_days, today=today):
out.append(e)
elif d.verified is not None and not d.is_verified:
out.append(e)
return out
def freshness(
@ -261,14 +285,16 @@ class Catalog:
f"{stale_count} catalog entr{'y' if stale_count == 1 else 'ies'} "
f"past {stale_threshold_days}d review cadence"
)
# 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=stale_threshold_days, today=today
include_draft=True, threshold_days=DEFAULT_BLOCKER_STALE_DAYS, today=today
))
if stale_interim:
warnings.append(
f"{stale_interim} interim delegation"
f"{'' if stale_interim == 1 else 's'} past "
f"{stale_threshold_days}d review — see `warden route gaps`"
f"{'' if stale_interim == 1 else 's'} need re-verifying "
f"({DEFAULT_BLOCKER_STALE_DAYS}d blocker cadence) — see `warden route gaps`"
)
return CatalogFreshness(
@ -447,11 +473,19 @@ def _parse_delegation(entry_id: str, raw: Optional[dict]) -> Optional[Delegation
entry_id, "delegation.blocked_on", blocked_on, prose=True
)
verified = str(raw.get("verified", "")).strip() or None
if verified is not None and verified not in Delegation.VERIFICATION_METHODS:
raise CatalogError(
f"entry {entry_id!r} delegation.verified {verified!r} invalid "
f"(expected one of {Delegation.VERIFICATION_METHODS})"
)
return Delegation(
mode=mode,
intended_owner=intended_owner,
blocked_on=blocked_on,
reviewed=reviewed,
verified=verified,
implicit=False,
)