WARDEN-WP-0033-T05: split the stale cadences, and record how a blocker was verified
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:
parent
a565e62b2f
commit
55f0f47a02
8 changed files with 335 additions and 24 deletions
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue