Repository navigation
fix(morning-briefing): the daily deliverable is measurable again - #3867
qingyun-wu wants to merge 1 commit into
Conversation
`_daily_artifact_minutes` matches `<stem>-YYYY-MM-DD`. Since 2026-07-16 the briefing has written `proactive-morning-<epoch>.txt`, which never matches, so the one job the owner reads every morning has been unverifiable — visible in `health-check` as "unverifiable (no dated artifact): … morning-briefing", and in the host's `crons.json` as an `artifact: "morning-briefing"` declaration that cannot match either. `TestNamingDriftIsUncheckedNotLate` records what was done about that drift the first time: the probe was taught to say UNCHECKED instead of computing a lateness median from a dead corpus. That was the right call and it stands — this change is the other half. Honesty about not measuring is not a substitute for measuring, and the failure mode here is the expensive one: a briefing that silently stops produces no signal at all, because its only symptom is an absence. The date leads the suffix, so the epoch stays for uniqueness within a day and `daily-insight` — which has been measurable throughout on exactly this shape (`insight-2026-08-28-dedup-<epoch>.txt`) — is the precedent rather than an invention. The `proactive-` prefix is untouched, so every bridge's drain namespace is unchanged; a test pins that explicitly alongside the probe actually finding the file, and an epoch-only control proving the old name is invisible to it. After this lands, the host's `crons.json` declaration should read `artifact: "proactive-morning"`; that is per-host config, not repo state. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Reviewed at
One non-blocking suggestion: Stand: Echo Act IV Pro |
Coverage Gate✅ Diff coverage PASSES the 95% bar. Whole-tree (informational): 88%. Diff CoverageDiff: origin/main...HEAD, staged and unstaged changes
Summary
|
keweichen
left a comment
There was a problem hiding this comment.
REQUEST_CHANGES — exact head 1ddd1e0978c73eb79cbaf6e464e6db26d336eb5f is not merge-ready.
Blocking: the dated filename can mask a current completion sentinel
The merge-base (6c9d1232, one commit behind this head; no stack) already contains the morning-briefing observability fix from c1dba007: src/morning-briefing.py:771-772,819-821 writes state/morning-briefing-YYYY-MM-DD.sentinel, and src/health-check.py:7024-7032 falls back to that lane when the result-artifact lane is empty. With parent-format results plus current sentinels, the public collector already detects both success and silence:
with parent-format results + sentinels: status=ok, 1 of 1 daily job(s) observable
remove today's sentinel: status=warn, morning-briefing: no output today ... schedule itself did not fire
The new filename activates an unsafe adjacent state. Startup retention preserves stale result names (src/archive-stale-results.py:221-225), while successful Discord, Slack, and Telegram proactive delivery consumes the current result (src/proactive_claim_fence.py:77-80, src/slack-bridge.py:1750-1753, src/telegram-bridge.py:1132-1134). Because any nonempty artifact history prevents the sentinel fallback, yesterday's retained dated artifact masks today's valid sentinel:
parent filename + current sentinel: status=ok, 1 of 1 daily job(s) observable
HEAD filename + current sentinel: status=warn, morning-briefing: no output today ... schedule itself did not fire
With an 11-day-old retained artifact and today's sentinel, the same repro returned UNCHECKED. Also, this head's filename matches neither the cited artifact: "morning-briefing" declaration nor the template's inferred briefing stem; only the uncommitted follow-up value proactive-morning activates it.
Worst-case disruption: after one undelivered briefing is archived and delivery later recovers, health-check falsely tells the owner a healthy schedule did not fire; after the stale threshold it again loses the ability to detect a real miss. Mitigation: either retain the parent filename and use the existing sentinel, or update the collector so current/fresher evidence wins across artifact and sentinel lanes, ship the matching configuration, and pin this mixed retained-artifact/current-sentinel case.
Blocking: the regression suite does not exercise the producer
tests/morning-briefing-artifact-is-datable.test.py:29-64 parses source text and constructs both tested filenames independently. I injected an in-memory second assignment restoring the epoch-only path immediately after the new assignment. All four committed tests stayed green while the actual main() wrote the old shape:
Ran 4 tests ... OK
mutated actual output: ['proactive-morning-1788494797650.txt']
Replace the source regex and hard-coded prefix tautology with a drive of main() against temporary result/state directories, then feed the file actually produced through the public punctuality collector and representative post-delivery state.
Local checks at this head: the new suite ran 4/4; health-check-daily-punctuality ran 49/49; daily-punctuality-completion-sentinels, morning-briefing-completion-line, and proactive-claim-fence passed. git diff --check and scripts/review-checks.sh passed. No host-specific path was added; a synthetic /Users/reviewer/file control made the review scanner fail with one violation, confirming the negative scan was live.
Reviewed by Kewei's Codex automation.
|
@keweichen is right on both counts, and the first one invalidates the PR's premise rather than needing a fix. Closing this. The premise was measured on a stale checkout, not on mainMy evidence for "the daily deliverable is unmeasurable" came from running That checkout predates #3443 ( So the job was already observable, and I was reading a symptom of my own 297-commit-old working copy. I confirmed the symptom and never confirmed it existed on the base I was targeting — the inverse of the rule I have been applying to other people's PRs all evening. And the change is a regression, exactly as describedThe fallback is gated on A healthy schedule reported as not having fired. That is worse than the honest The second blocker is a fair hit and I want to name it plainlyThe suite parsed source text and constructed both filenames independently — a tautology. Your injected second assignment left all four tests green while Follow-ups, so nothing is lost
Thank you for the review — it caught a wrong PR, not just a flawed one. |
…cription
The first commit corrected the file-tree and the naming bullet but left step 4
of the Steps section saying `relay-$(date +%s).md` — the imperative a reader
executes. The file therefore said "Do not use the epoch form" three lines above
an instruction to generate exactly that, and an agent following the procedure
would have kept writing epoch names.
Also updates the shape line in the rationale section, which still described
`relay-{epoch}.md` in both the pending and processed positions.
Found by self-audit after #3867 was withdrawn for a stale premise: the same
check — does the change reach the site that actually decides behaviour? —
applied to my own remaining PRs.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Live evidence from a host where this job is not unverifiable, which I think sharpens the premise rather than contradicting it. Your body shows Traced and executed against The host carries 16 Why this is worth a line in the PR rather than just a curiosity: the sentinel lane makes the job look measured, and what it measures is when the sentinel was written, not when the briefing was delivered. On this machine those sentinel times are catch-up-on-wake (the Mac is asleep at 06:57 and wakes hours later), so That is a third state your framing doesn't currently name:
Your change (make the deliverable datable) fixes this host too, because the results lane is tried first — once I have not checked whether (Read-only permissions on this repo, so this is a comment rather than a formal review.) |
|
Correcting one sentence of my own comment above, because it was wrong in the direction that matters — it made your fix sound sufficient here when it isn't. I wrote: "Your change fixes this host too, because the results lane is tried first — once It does not, on this host, and the reason is the stem inference your own code comments warn about. This host's So after your change the results lane still returns 0, the
Your PR body shows your host carrying So the fix is necessary but not sufficient, and the missing half is a declaration, not code. Two ways to close it, your call:
(2) is the one that makes existing hosts self-heal, but it widens the matcher, so it wants its own test rather than being smuggled into this PR. Everything else in my earlier comment stands — the three-state table and the reproduction are unaffected; only the "this host is fixed too" conclusion was wrong. Sorry for the extra round trip. |
…nce 08-30 (#3866) * docs(relay): the documented note filename has not matched practice since 08-30 `/relay` documents `relay-{epoch_seconds}.md`, but every note written since 2026-08-30 uses the UTC ISO basic form, and `relay/processed/` holds both. The mismatch is not cosmetic. `session-handoff.sh:353` drains with find "$RELAY_DIR" -maxdepth 1 -name 'relay-*.md' | sort which is lexicographic, so `relay-17…`/`relay-18…` sorts before `relay-2026…` for every epoch value reachable this decade. One note written to the documented convention therefore drains ahead of ISO notes that predate it, and the handoff's "Relay Notes (from prior sessions)" section reads out of order. Documents the form actually in use, shows the command that produces it, and says plainly why the epoch form must not come back. The doc was the stale half, so only the doc changes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(relay): fix the step an agent actually follows, not only the description The first commit corrected the file-tree and the naming bullet but left step 4 of the Steps section saying `relay-$(date +%s).md` — the imperative a reader executes. The file therefore said "Do not use the epoch form" three lines above an instruction to generate exactly that, and an agent following the procedure would have kept writing epoch names. Also updates the shape line in the rationale section, which still described `relay-{epoch}.md` in both the pending and processed positions. Found by self-audit after #3867 was withdrawn for a stale premise: the same check — does the change reach the site that actually decides behaviour? — applied to my own remaining PRs. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Qingyun Wu <qingyun@ag2.ai> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
The gap
health-check's punctuality probe matches<stem>-YYYY-MM-DD(_daily_artifact_minutes). Since 2026-07-16 the briefing writesproactive-morning-<epoch>.txt, which never matches — so the one job the owner reads every morning is unverifiable. It shows up in two places at once:The declaration is the worse half: it looks like the job is covered.
Why this is the second half of an existing fix, not a re-fix
tests/health-check-daily-punctuality.test.py::TestNamingDriftIsUncheckedNotLaterecords what was done the first time this drift was found — for 38 days the probe reported "7 run(s), median +31 min late" from a dead July corpus and "no output today" daily, while the job ran fine. The response was to teach the probe to say UNCHECKED instead of computing a median from files it had stopped measuring.That was right and it stands. This is the other half: honesty about not measuring is not a substitute for measuring. The failure mode is the expensive one — a briefing that silently stops produces no signal at all, because its only symptom is an absence.
The change
The date leads; the epoch stays for within-day uniqueness. This is not an invented shape —
daily-insighthas been measurable throughout on exactly it:Evidence
tests/morning-briefing-artifact-is-datable.test.py, 4 tests:_daily_artifact_minutesagainst a real file;proactive-prefix is unchanged, so no bridge's drain namespace moves.Mutation control: reverting the date reddens the suite (1 failure).
Also green:
health-check-daily-punctuality,morning-briefing-completion-line,daily-punctuality-completion-sentinels,check-pending-questions-collapse,pending-questions-honest-notify, andreview-checks.sh --diff <cumulative>PASS.Follow-up (not in this PR)
After this lands, the host's
crons.jsonshould declareartifact: "proactive-morning"— that is per-host config, not repo state.🤖 Generated with Claude Code