You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Drift check-ins carry no metadata.fault: a change of kind inside one fault waits for the daily reminder #58
post_checkin sends {"status", "variables"} and never metadata, so the drift check's alerts carry no metadata.fault. [code: src/processor/checkin.py:68 @ 540975e]
Since CannObserv/status#28, co-status groups a run of alert check-ins into one fault. A change of metadata.fault opens a new fault and reports at once. Without the key, every alert carries null alike, so co-status can't tell one fault from the next. [code: CannObserv/status src/core/monitors.pyopens_fault / report_due @ 16102ea]
Effect on co-processor-drift
Still works: a fault opens and reports, reminds once a day (renotify_seconds 86400), and has cleared on the next ok. Nothing goes silent.
Doesn't work: a change of kind inside one fault, say lag → off_main or unstamped, is not reported until the next reminder, up to about 23.5 h later. The most urgent verdicts (off_main, unstamped) are the ones that can be held back this way.
A --test-alert sent while a real fault is open doesn't report either.
[measured, read-only on co-status, 2026-10-20] co-processor-drift (01M4BHMGGTXWQ16J3G5WWDQRYQ) has one alert on record, 2026-10-07T16:16:43Z, which predates #28. fault_key is null and the monitor is ok, so nothing has been lost so far.
Suggested fix
Send "metadata": {"fault": kind} on every alert, and no metadata on an ok. That is the contract co-archiver-drift and co-broker-drift follow (CannObserv/status#32, #51). For example, give post_checkin an optional metadata: dict[str, str] | None and have the drift command pass {"fault": verdict.kind} when status == "alert". Broker's tests for the same contract, as models: tests/test_drift.py::TestVerdict::test_every_alert_s_fault_is_its_kind and ::test_an_ok_carries_no_metadata (CannObserv/broker#110).
metadata.fault is compared, never interpreted: any stable string per fault works. No change is needed on co-status's side, or to the monitor row.
The liveness check (src/processor/liveness.py) uses the same post_checkin. If it ever sends an alert with more than one cause, the same applies there.
Hand-off from the burn-down epic #24, 2026-10-21. This is next, ahead of #57, as a deploy-safety item: it is about how quickly a drift alert reaches a person. Size S. It touches src/, so it ends with a deploy. The pre-flight is against origin/main (540975e).
What the coordinator checked:
The gap is as stated.post_checkin (src/processor/checkin.py:68) sends json={"status": status, "variables": variables} and nothing else. Its only drift caller is __main__._drift (__main__.py:117), which passes verdict.status and verdict.variables().
The fault key is verdict.kind. The Kind literal is at drift.py:69 (ok, lag, off_main, unstamped, test, silent). status maps ok → ok, silent → no check-in, and everything else → alert (drift.py:83-87). So the alerting kinds are lag, off_main, unstamped and test.
Liveness needs no change.liveness.py only ever checks in ok: when the loop is stale it skips the check-in, and Status's grace pages. Keep metadata optional with a None default, so liveness's call is unchanged.
The fix (the issue's suggestion, which the coordinator agrees with):
Give post_checkin an optional metadata: dict[str, str] | None = None. Add the key to the JSON body only when it is given.
Have _drift pass {"fault": verdict.kind} when verdict.status == "alert", and nothing on ok. Putting the fault on the verdict (for example a metadata() beside variables()) is your call.
Update checkin.py's module docstring (line 5 describes the body). Add one line to DEPLOYMENT.md's drift section, saying each alert names its kind as metadata.fault, so a change of kind reports at once.
Tests (TDD; model them on broker's tests/test_drift.py::TestVerdict::test_every_alert_s_fault_is_its_kind and ::test_an_ok_carries_no_metadata):
Every alerting kind's check-in carries metadata.fault == kind, and --test-alert's carries "test".
An ok check-in has no metadata key at all: not null, not {}.
post_checkin with no metadata sends exactly today's body. Extend tests/test_checkin.py:19's stub assertion.
Liveness's body is unchanged.
Traps:
"No metadata" means the key is absent. co-status compares metadata.fault. A {"metadata": null} or {} on an ok is harmless there, but it isn't the sibling contract, and the tests should pin absence.
A --test-alert sent while a real fault is open now reports at once, as a separate fault (test). That is the intended behaviour, so say it in DEPLOYMENT.md where the test alert is documented (around line 292).
Don't put anything secret in fault. It is the kind token only, never body or a URL.
Live checks after the deploy:
The next hourly processor-drift run logs kind ok / checkin 202, and the unit runs from the new release.
The alert path (operator gate): one processor drift --test-alert, as in DEPLOYMENT.md § the drift check. It should get 202, and the operator should confirm the alert arrived on the drift channels. The next hourly ok then clears it, which is a second notification. This pages people, so ask the operator before sending it. If they decline, say in the closing comment that the alert path is proven only in tests.
Cross-repo: none needed. A courtesy reply on CannObserv/status#51 is optional and needs approval. The operator's approval of 2026-10-20 covered #37's two posts only.
Done when:
every drift alert carries metadata.fault = its kind, an ok carries no metadata, and liveness is unchanged, all with tests;
the docstring and DEPLOYMENT.md are updated;
the change is deployed through the gate, and the next drift run is ok/202 from the new release;
the test alert has been sent with approval, or its absence is stated.
Ship: issue → branch in a worktree → plan (short) → TDD → PR (Refs #58: the deploy and live checks come after the merge) → FF merge → git pull --ff-only → scripts/deploy.sh → the live checks. The closing comment gives each box with its evidence, the known limits, and the follow-ups (or "none").
Kickoff prompt:
Pick up processor#58. Read its hand-off comment from epic #24 first.
Shipped in #59 (FF merge, a1a9e91), live 2026-10-21 16:14Z.
Done when:
Every drift alert carries metadata.fault = its kind, an ok carries no metadata, and liveness is unchanged, all with tests.Verdict.metadata() is {"fault": kind} on an alert and None otherwise (ok, silent). post_checkin(..., metadata=None) adds the key only when it is given, never as null or {}. Tests went red first (f1a75ac): TestVerdict::test_every_alert_s_fault_is_its_kind[lag|off_main|unstamped|test] and ::test_an_ok_carries_no_metadata[ok|silent]; the command's lag and --test-alert check-ins carry the fault, and its ok check-in has no metadata key; test_no_metadata_is_no_key_at_all[none|empty]. test_liveness.py's body-equality test passes unchanged. CI on the final SHA: lint ✓, test ✓ (run 39632225886).
Docstring and DEPLOYMENT.md. The checkin.py module docstring now describes metadata. DEPLOYMENT.md § The drift check gets a bullet for the fault, and the test-alert paragraph now says it reports at once during an open fault, as its own fault test (trap 2).
Deployed through the gate, and the next drift run is ok/202 from the new release.scripts/deploy.sh: CI passed for a1a9e91ef684, and live is on a1a9e91ef684. A manual run at 16:14:26Z returned ok/202. The hourly timer run at 17:15:04Z returned {"level":"INFO","build":"a1a9e91ef684","kind":"ok","checkin":202}. processor is active, and starting shows build a1a9e91ef684 with liveness on.
Test alert, with the operator's approval. It was sent at 17:50:16Z with kind test, got checkin 202, and exited 0. The operator confirmed it arrived on the drift channels. The next hourly ok (~18:15Z) clears it.
Known limits: the fault is the kind token only. Two alerts of the same kind with different bodies (for example a lag whose CI result changes) are one fault, which is intended. I didn't observe the clearing ok after the test alert before closing.
Follow-ups: none. The optional courtesy reply on CannObserv/status#51 has not been posted.
Activity
gregoryfoster commentedon Oct 21, 2026
Hand-off from the burn-down epic #24, 2026-10-21. This is next, ahead of #57, as a deploy-safety item: it is about how quickly a drift alert reaches a person. Size S. It touches
src/, so it ends with a deploy. The pre-flight is againstorigin/main(540975e).What the coordinator checked:
post_checkin(src/processor/checkin.py:68) sendsjson={"status": status, "variables": variables}and nothing else. Its only drift caller is__main__._drift(__main__.py:117), which passesverdict.statusandverdict.variables().alert, sendmetadata.fault; on anok, send nometadata. Drift monitor for broker (co-broker / co-broker-drift) status#51 (closed 2026-10-20) states it forco-broker-drift, and #22 C6: broker-drift timer - live lagging origin/main alerts through co-status broker#110 (closed 2026-10-20) implements it. The issue also cites status#32 forco-archiver-drift. No change is needed on co-status's side.verdict.kind. TheKindliteral is atdrift.py:69(ok,lag,off_main,unstamped,test,silent).statusmapsok→ok,silent→ no check-in, and everything else →alert(drift.py:83-87). So the alerting kinds arelag,off_main,unstampedandtest.liveness.pyonly ever checks inok: when the loop is stale it skips the check-in, and Status's grace pages. Keepmetadataoptional with aNonedefault, so liveness's call is unchanged.The fix (the issue's suggestion, which the coordinator agrees with):
post_checkinan optionalmetadata: dict[str, str] | None = None. Add the key to the JSON body only when it is given._driftpass{"fault": verdict.kind}whenverdict.status == "alert", and nothing onok. Putting the fault on the verdict (for example ametadata()besidevariables()) is your call.checkin.py's module docstring (line 5 describes the body). Add one line to DEPLOYMENT.md's drift section, saying eachalertnames its kind asmetadata.fault, so a change of kind reports at once.Tests (TDD; model them on broker's
tests/test_drift.py::TestVerdict::test_every_alert_s_fault_is_its_kindand::test_an_ok_carries_no_metadata):metadata.fault == kind, and--test-alert's carries"test".okcheck-in has nometadatakey at all: notnull, not{}.post_checkinwith nometadatasends exactly today's body. Extendtests/test_checkin.py:19's stub assertion.Traps:
metadata.fault. A{"metadata": null}or{}on anokis harmless there, but it isn't the sibling contract, and the tests should pin absence.--test-alertsent while a real fault is open now reports at once, as a separate fault (test). That is the intended behaviour, so say it in DEPLOYMENT.md where the test alert is documented (around line 292).fault. It is the kind token only, neverbodyor a URL.Live checks after the deploy:
processor-driftrun logskind ok/checkin 202, and the unit runs from the new release.processor drift --test-alert, as in DEPLOYMENT.md § the drift check. It should get202, and the operator should confirm the alert arrived on the drift channels. The next hourlyokthen clears it, which is a second notification. This pages people, so ask the operator before sending it. If they decline, say in the closing comment that the alert path is proven only in tests.Cross-repo: none needed. A courtesy reply on CannObserv/status#51 is optional and needs approval. The operator's approval of 2026-10-20 covered #37's two posts only.
Done when:
alertcarriesmetadata.fault= its kind, anokcarries nometadata, and liveness is unchanged, all with tests;ok/202 from the new release;Out of scope:
Ship: issue → branch in a worktree → plan (short) → TDD → PR (
Refs #58: the deploy and live checks come after the merge) → FF merge →git pull --ff-only→scripts/deploy.sh→ the live checks. The closing comment gives each box with its evidence, the known limits, and the follow-ups (or "none").Kickoff prompt:
gregoryfoster commentedon Oct 21, 2026
Shipped in #59 (FF merge,
a1a9e91), live 2026-10-21 16:14Z.Done when:
alertcarriesmetadata.fault= its kind, anokcarries nometadata, and liveness is unchanged, all with tests.Verdict.metadata()is{"fault": kind}on an alert andNoneotherwise (ok,silent).post_checkin(..., metadata=None)adds the key only when it is given, never asnullor{}. Tests went red first (f1a75ac):TestVerdict::test_every_alert_s_fault_is_its_kind[lag|off_main|unstamped|test]and::test_an_ok_carries_no_metadata[ok|silent]; the command'slagand--test-alertcheck-ins carry the fault, and itsokcheck-in has nometadatakey;test_no_metadata_is_no_key_at_all[none|empty].test_liveness.py's body-equality test passes unchanged. CI on the final SHA: lint ✓, test ✓ (run 39632225886).checkin.pymodule docstring now describesmetadata. DEPLOYMENT.md § The drift check gets a bullet for the fault, and the test-alert paragraph now says it reports at once during an open fault, as its own faulttest(trap 2).ok/202 from the new release.scripts/deploy.sh: CI passed fora1a9e91ef684, andlive is on a1a9e91ef684. A manual run at 16:14:26Z returnedok/202. The hourly timer run at 17:15:04Z returned{"level":"INFO","build":"a1a9e91ef684","kind":"ok","checkin":202}.processoris active, andstartingshows builda1a9e91ef684with liveness on.kind test, gotcheckin 202, and exited 0. The operator confirmed it arrived on the drift channels. The next hourlyok(~18:15Z) clears it.Known limits: the fault is the kind token only. Two alerts of the same kind with different bodies (for example a
lagwhose CI result changes) are one fault, which is intended. I didn't observe the clearingokafter the test alert before closing.Follow-ups: none. The optional courtesy reply on CannObserv/status#51 has not been posted.