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

@ -779,3 +779,78 @@ def test_low_risk_vocabulary_is_explicit():
entry = _bare_entry(risk=grade)
assert entry.is_high_risk is False, grade
assert entry.is_graded is True, grade
# ---------------------------------------------------------------------------
# Blocker staleness cadence + verification (WARDEN-WP-0033-T05)
# ---------------------------------------------------------------------------
def test_blocker_cadence_is_separate_from_pointer_cadence():
"""Two claims with different half-lives must not share one threshold.
"Is this still the right owner and page?" is quarterly. "Has the owner
answered yet?" is not. Sharing 90 days made the second one inert -- the
register was six days old, so it could not have fired for months.
"""
from warden.routing.catalog import DEFAULT_BLOCKER_STALE_DAYS, DEFAULT_STALE_DAYS
assert DEFAULT_STALE_DAYS == 90
assert DEFAULT_BLOCKER_STALE_DAYS == 14
assert DEFAULT_BLOCKER_STALE_DAYS < DEFAULT_STALE_DAYS
def test_asked_and_waiting_is_not_verification():
"""The failure this whole change exists to catch.
A lane asked today reads as reviewed today. The secrets-engine blocker sat
in exactly that state for ten days while looking current.
"""
from warden.routing.models import Delegation
asked = Delegation(mode="interim", intended_owner="x", blocked_on="y",
reviewed="2026-08-21", verified="asked-and-waiting")
assert asked.is_verified is False
for method in ("owner-confirmed", "source-read"):
d = Delegation(mode="interim", intended_owner="x", blocked_on="y",
reviewed="2026-08-21", verified=method)
assert d.is_verified is True, method
def test_stale_gaps_flags_unverified_even_when_the_date_is_today():
catalog = load_catalog(_repo_catalog())
stale = {e.id for e in catalog.stale_gaps(include_draft=True, today=date(2026, 8, 21))}
# Asked of key-cape on 2026-08-21 and unanswered -- zero days old, still stale.
assert "key-cape-oidc-login" in stale
# Confirmed by the owner the same day -- fresh.
assert "issue-core-ingestion-api-key" not in stale
def test_invalid_verification_method_rejected(tmp_path):
entry = dict(ROUTED_ENTRY)
entry["delegation"] = {
"mode": "interim", "intended_owner": "secrets-engine",
"blocked_on": "pending", "reviewed": "2026-08-21", "verified": "probably-fine",
}
with pytest.raises(CatalogError, match="verified"):
load_catalog(_write_catalog(tmp_path, [SSH_ENTRY, entry]))
def test_every_interim_lane_records_how_it_was_verified():
"""Structural, not time-based, so it never fails on a calendar day alone."""
catalog = load_catalog(_repo_catalog())
missing = [
e.id for e in catalog.gaps(include_draft=True)
if e.effective_delegation.verified is None
]
assert not missing, f"interim lanes with no `verified`: {missing}"
def test_cli_route_gaps_fail_on_stale_exits_3(repo_catalog_env):
result = runner.invoke(app, ["route", "gaps", "--fail-on-stale", "--json"])
assert result.exit_code == 3
rows = json.loads(result.stdout)
assert any(r["stale"] for r in rows)
# A lane can be stale on age or on never having been verified; both must be
# expressible, or asked-and-waiting silently passes the gate.
assert any(r["stale"] and r["days_since_review"] == 0 for r in rows)