Repository navigation
feat(cron): persist incident reopen_count with a truthful reopen flag - #132839
Astraea-Hermes wants to merge 1 commit into
Conversation
Re-opened incidents were invisible: upsert_incident's second return slot
mixed creation and re-open under one name ("is_new" True for both), its
only caller discarded it, and the durable incident row — the record that
outlives the pruned executions ledger — flattened chronic recurrence
into one shape.
reopen_count tracks cycles on the same signature. Pre-counter rows
migrate to NULL, never a silent 0: add_column_if_missing is a raw ALTER
TABLE pass-through where a DEFAULT backfills unmeasured history with the
default's claim. The helper docstring now says so at its own layer. The
reopen flag means one thing (a resolved incident re-opened) and is named
for it; the scheduler bind follows. hermes cron incidents renders the
distinction: "pre-counter" vs measured counts.
|
Compatibility note for maintainers (no rebase dependency between PRs): I independently ran a trial three-way merge of this PR's head Beyond textual: #132835's author also merged both heads and ran the combined suite — 107 passed / 3 failed, the 3 being pre-existing failures on the common base, unchanged by the combination (their run, reported here with attribution, not mine). Net: the two PRs are order-independent — either can land first on the merits, and neither degrades the other. Cross-posted for the record on #132835. |
|
Verified at 6fa7c6f: test_cron_incidents.py passes 20/20. |
|
Update to the compatibility note (head moved since it was posted): #132835's second commit ( |
What does this PR do?
Makes incident recurrence visible instead of flattened. Today a job that dies once and a job that dies every tick leave the same durable mark:
upsert_incident's re-open path refreshes one row and its re-open result is discarded by the only caller (cron/scheduler.pybinds it to_is_newand nothing incron/reads it). Since incidents sharecron/executions.dbbut the retention prune never trimscron_incidents, incidents are the evidence that outlives the rolling executions ledger — which makes their recurrence signal load-bearing, not cosmetic.Changes Made
cron/incidents.pyreopen_countcolumn. The migration is deliberately NULLABLE with NO DEFAULT:add_column_if_missingis a rawALTER TABLEpass-through, and aDEFAULT 0would backfill every pre-existing row and silently attest "never reopened" for history that was never counted. Pre-counter rows readNULL= not measured; the write path sets0explicitly on INSERT.upsert_incidentnow returns(incident_id, reopened)— True only when aresolvedincident re-opens under the same signature. The old second slot was ambiguous by construction: True on INSERT and True on re-open, False otherwise — one name (is_new) for two facts, asserting the inverse of the measurement on the re-open branch.COALESCE(reopen_count, 0) + 1, so a pre-counter (NULL) row starts its counted history at 1 instead of staying NULL forever. Non-reopen refreshes never touch the counter.cron/scheduler.py— bind renamed to_reopened(still unused; the helper's public(withheld, incident_id)contract is unchanged).hermes_cli/cron.py—hermes cron incidentsrenders aReopened:line:pre-counterfor NULL,0x/2x… for measured rows. A measured zero and an unmeasured absence are visibly different.hermes_cli/sqlite_util.py—add_column_if_missingnow documents what it actually is: the only guard is swallowing the duplicate-column error; aDEFAULTin theddlapplies to every existing row, so "make old rows read unmeasured" requires a nullable ddl with no default, and the review gate is the ddl string at the call site. This knowledge previously lived only in a downstream comment (kanban_db_connect.py— which that line now supersedes).Related
Context chain, not dependencies: #128509 (terminalize dead-owner with observed reason), #132835 (per-site honest reason strings at the sweep), #108806 (alerting reclaimed executions). None of those change the re-open path; this makes their incidents chronicle recurrence rather than a single flattened row. No reason-string edits here — the incident
error_sigstays stable.How to Test
scripts/run_tests.sh tests/cron/test_cron_incidents.py— three new tests: recurrence tracking (0 → refresh → resolved → 1 → 2), old-shape ledger migrating toNULL(not0), COALESCE path starting at 1 from NULL. Four existing assertions adapted from the ambiguous slot to the single-meaning flag (creation is now proven by the row, not the return value).git stash, re-run, sortedFAILEDsets): identical — 3 pre-existing failures on8d5e3e41unchanged, zero introduced.tests/cron/patched vs baseline on the same commit: failure sets identical (this base has pre-existing failures in my local environment independent of this diff; none added or masked by it).Evidence
cron_incidentshas no recurrence field;hermes cron incidentscannot tell "died once, acked" from "died every cadence for a week".reopen_counttracks cycles on the durable row that outlives the ledger; the flag means one thing and is named for it; unmeasured history is honest about being unmeasured.