Adaptive check cadence: the interval is earned, not assigned
Operator ruling 2026-08-20. Severity no longer sets the review interval. A check that comes back clean climbs one rung — instant, 1h, 8h, 24h, 48h, 96h, 7d, 14d, 1mo, 1q — and anything wrong drops straight back to instant. A quarter is the ceiling. The operator may defer an instant finding to a stated date; that is the only other way off the bottom rung. The rung is the point: it says how stable the estate has been on that matter, which is information severity does not carry. Volatile things get attention automatically; quiet things stop consuming it; neither judgement has to be made by a person who might be busy. Escalation trigger 5 rebased onto the ladder — fourteen days at the bottom rung, whether that is failing checks or no checks. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
8b204d0411
commit
42bbf5d2dc
17 changed files with 429 additions and 148 deletions
|
|
@ -11,21 +11,11 @@ import datetime as dt
|
|||
|
||||
import register_lib as lib
|
||||
|
||||
TODAY = dt.date.today()
|
||||
|
||||
|
||||
def date(value):
|
||||
if isinstance(value, dt.date):
|
||||
return value
|
||||
if isinstance(value, str) and value not in ("", "unset"):
|
||||
return dt.date.fromisoformat(value)
|
||||
return None
|
||||
NOW = lib.now()
|
||||
|
||||
|
||||
def main() -> int:
|
||||
# "accepted" is deliberately carried, not closed: it keeps its review
|
||||
# interval and its production re-score, so the nag must keep watching it.
|
||||
fs = [f for f in lib.findings() if f.get("status") in ("open", "accepted")]
|
||||
fs = [f for f in lib.findings() if lib.watched(f.get("status"))]
|
||||
lines: list[str] = []
|
||||
|
||||
def section(title: str, rows: list[str], quiet: str) -> None:
|
||||
|
|
@ -36,44 +26,67 @@ def main() -> int:
|
|||
lines.append(f" {quiet}")
|
||||
lines.append("")
|
||||
|
||||
unknown = [
|
||||
f"{f['id']} — status '{f.get('status')}' is not one of {', '.join(lib.KNOWN_STATUSES)}; watched anyway"
|
||||
for f in fs
|
||||
if f.get("status") not in lib.KNOWN_STATUSES
|
||||
]
|
||||
if unknown:
|
||||
section("Unrecognised status", unknown, "none")
|
||||
|
||||
ungraded = [
|
||||
f"{f['id']} — {', '.join(k for k in lib.GRADED_FIELDS if str(f.get(k, 'unset')) == 'unset')}"
|
||||
for f in fs
|
||||
if any(str(f.get(k, "unset")) == "unset" for k in lib.GRADED_FIELDS)
|
||||
]
|
||||
section("Ungraded", ungraded, "none — every open finding carries a grade")
|
||||
section("Ungraded", ungraded, "none — every watched finding carries a grade")
|
||||
|
||||
overdue = []
|
||||
due, deferred = [], []
|
||||
for f in fs:
|
||||
due = date(f.get("review_by"))
|
||||
if due and TODAY > due:
|
||||
overdue.append(f"{f['id']} — review was due {due} ({(TODAY - due).days}d ago)")
|
||||
section("Overdue review", overdue, "none")
|
||||
|
||||
stale = []
|
||||
for f in fs:
|
||||
seen = date(f.get("last_reviewed"))
|
||||
interval = lib.REVIEW_INTERVAL_DAYS.get(f.get("severity"))
|
||||
if not seen or not interval or f.get("severity") == "low":
|
||||
continue
|
||||
limit = seen + dt.timedelta(days=interval * lib.STALE_MULTIPLIER)
|
||||
if TODAY > limit:
|
||||
stale.append(
|
||||
f"{f['id']} ({f.get('severity')}) — untouched since {seen}; "
|
||||
f"escalation trigger 5 fires (limit was {limit})"
|
||||
if f.get("deferred_to"):
|
||||
until = lib.moment(f["deferred_to"])
|
||||
if until and NOW < until:
|
||||
deferred.append(f"{f['id']} — deferred by the operator until {f['deferred_to']}")
|
||||
continue
|
||||
when = lib.moment(f.get("next_check"))
|
||||
if when is None:
|
||||
due.append(f"{f['id']} — no next_check set")
|
||||
elif NOW >= when:
|
||||
late = NOW - when
|
||||
hours = int(late.total_seconds() // 3600)
|
||||
rung = f.get("cadence", "instant")
|
||||
due.append(
|
||||
f"{f['id']} ({f.get('severity')}) — due {f['next_check']}"
|
||||
f"{f', {hours}h late' if hours else ''}, cadence {rung}"
|
||||
)
|
||||
section("Stalled — escalation trigger 5", stale, "none")
|
||||
section("Checks due", due, "none")
|
||||
if deferred:
|
||||
section("Deferred by the operator", deferred, "none")
|
||||
|
||||
embargo = []
|
||||
for f in fs:
|
||||
if f.get("disclosure") != "embargoed":
|
||||
continue
|
||||
due = date(f.get("embargo_review"))
|
||||
if due and TODAY > due:
|
||||
embargo.append(
|
||||
f"{f['id']} — embargo not re-decided since {due}; lifts when: {f.get('embargo_condition')}"
|
||||
)
|
||||
section("Embargo overdue for re-decision", embargo, "none")
|
||||
# The rung is the signal: how long this matter has held still.
|
||||
stability = []
|
||||
for f in sorted(fs, key=lambda f: lib.rung_index(f.get("cadence", "instant"))):
|
||||
rung = f.get("cadence", "instant")
|
||||
streak = f.get("clean_streak", 0)
|
||||
note = " — at the ceiling" if rung == lib.TOP_RUNG else ""
|
||||
stability.append(f"{f['id']}: {rung} ({streak} clean in a row){note}")
|
||||
section("Stability — where each finding sits on the ladder", stability, "nothing watched")
|
||||
|
||||
bottom = [
|
||||
f"{f['id']} ({f.get('severity')}) — still at the bottom rung since {f.get('last_checked')}"
|
||||
for f in fs
|
||||
if lib.rung_index(f.get("cadence", "instant")) == 0
|
||||
and (m := lib.moment(f.get("last_checked")))
|
||||
and NOW - m > dt.timedelta(days=14)
|
||||
]
|
||||
section("Stalled — escalation trigger 5", bottom, "none")
|
||||
|
||||
embargo = [
|
||||
f"{f['id']} — lifts when: {f.get('embargo_condition')}"
|
||||
for f in fs
|
||||
if f.get("disclosure") == "embargoed"
|
||||
]
|
||||
section("Embargoed", embargo, "none")
|
||||
|
||||
esc = [
|
||||
f"{f['id']} — trigger {f.get('escalation_trigger')}, {f.get('escalation_status')}"
|
||||
|
|
@ -89,7 +102,7 @@ def main() -> int:
|
|||
]
|
||||
section("Owed at the production transition", rescore, "none — no finding is graded lower for build mode")
|
||||
|
||||
print(f"Register check — {TODAY}\n{len(fs)} open or accepted finding(s)\n")
|
||||
print(f"Register check — {NOW:%Y-%m-%d %H:%MZ}\n{len(fs)} live finding(s)\n")
|
||||
print("\n".join(lines).rstrip())
|
||||
return 0
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue