check_history has two producers. sysmon-go writes `summary` at the top level
beside `status`; this module wrote it under `metrics`. So a reader had to know
which producer wrote a row before it could find out what the row said, and a
query written the obvious way found one and silently missed the other.
That is the T-36 failure repeating. There, per-domain queries returned rows from
August and looked like a system that had stopped reporting, because the data was
nested under a composite row nobody had mentioned. Nothing was missing; the
query was asking the wrong shape. verify.sh had already grown a coalesce over
both spellings, which is the tell: a compatibility shim that hides a schema
disagreement rather than resolving it.
D-33 made this table a contract between producers, and a contract needs one
spelling.
Summary is now a required parameter with no default. sysmon-go enforces the same
thing through Domain.Run's signature, and the reason is identical: a row whose
substance is missing looks exactly like a row whose check found nothing to say.
Both call sites pass it; the failure path passes the exception rather than
leaving the field to the metrics blob.
Old rows keep the nested spelling and verify.sh keeps reading both, because
rewriting history to match a new convention is a worse trade than a fallback
with a reason attached.
Also drops "Three consequences" from the module docstring, which by then listed
five. A hardcoded count beside the thing it counts is the same defect as
install.sh printing "wrote 8 keys" while writing ten — this morning's bug, in
prose instead of code.
Co-Authored-By: Claude <noreply@anthropic.com>
`source` names the code that wrote a row. It cannot name the schedule that
invoked it, and two tasks may share one executor -- so a row could not answer
the question a health record mostly exists to answer: which job broke?
Concretely, on 2026-08-11 a 425 MB probe task and the 5 GB nightly backup both
ran through config_backup_executor. The probe failed and wrote
source: scheduler/config_backup_executor
status: critical
error: Backup file was not created
which is byte-for-byte what a nightly backup failure would have written. The
row was true and unattributable, and the reflex it invited -- delete the
inconvenient row -- was correctly refused. Attribution is the actual fix: the
record stays intact and starts saying who it is about.
Carried in a ContextVar rather than an argument. Executors are invoked as
execute(config, settings) and there are ten of them, several dormant -- existing
only as a string in a database row and becoming live the moment someone inserts
a task naming them. A signature change would leave those broken in a way nothing
imports, greps or tests would reveal. Injecting the name into `config` was the
other option and is worse: `config` is what a human wrote in the task
definition, and an executor is entitled to reject keys it does not recognise.
Two properties make the ContextVar safe, both verified in the deployed runtime
rather than reasoned about:
- asyncio.to_thread propagates the context, so reporting still sees the task
after T-74 moved executor bodies into worker threads. Had it not, every row
from a real executor would have quietly lost its task while unit tests kept
passing -- so there is a test that specifically goes through report_async.
- Each asyncio Task gets its own copy, so the five concurrent executions
MAX_CONCURRENT_TASKS permits cannot read each other's value. The isolation
test yields mid-execution to force interleaving; without that it would pass
even against a shared global.
A plain await does NOT get its own copy and leaks the value to the caller, which
the runtime check showed. Both real entry points go through create_task, but
task_scope resets via token rather than depending on that.
The field is omitted, not nulled, when there is no task: report() is callable
from a script, and a null would claim a task existed with no name.
Mutation-checked: removing the scope from the engine fails both isolation tests.
Co-Authored-By: Claude <noreply@anthropic.com>