From 6a23a7596d95515bc244f0c712fd4c0ade1ee849 Mon Sep 17 00:00:00 2001 From: Day Dreamer <87640074+HugoXK@users.noreply.github.com> Date: Fri, 30 Oct 2026 13:11:11 +0800 Subject: [PATCH] fix(timeline): the report's roll-up names the Bots it flags (#1780) `team.unhealthy` filtered on `turns`, while the per-Bot flag has counted against `max(turns, errors)` since #1579. A Bot whose failed turns began before the window was flagged in its own section and left out of the `needs a look` line, so one report disagreed with itself. Docs-not-needed: docs/subsystems/timeline.md describes digest.js's inputs and its count/duration formatters; the roll-up's failure threshold is not described there, and this changes no architecture. Fixes #1780 --- changelog.d/1780-digest-needs-a-look.fixed.md | 1 + plugins/timeline/src/digest.js | 4 +++- plugins/timeline/test/digest.test.js | 13 +++++++++++++ 3 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 changelog.d/1780-digest-needs-a-look.fixed.md diff --git a/changelog.d/1780-digest-needs-a-look.fixed.md b/changelog.d/1780-digest-needs-a-look.fixed.md new file mode 100644 index 000000000..d8e8e233d --- /dev/null +++ b/changelog.d/1780-digest-needs-a-look.fixed.md @@ -0,0 +1 @@ +The timeline report's `## Team` roll-up now names the same Bots its own per-Bot sections flag: a Bot whose failed turns began before the window (so it has more failures than turns inside it) appears under `needs a look` again instead of being dropped while its section still reads `3 of 3 turns failed` ([#1780](https://github.com/ThinkFlowLab/FlyBot/issues/1780)). diff --git a/plugins/timeline/src/digest.js b/plugins/timeline/src/digest.js index fa5ce2748..703d9d78a 100644 --- a/plugins/timeline/src/digest.js +++ b/plugins/timeline/src/digest.js @@ -99,7 +99,9 @@ export function buildDigest(window = {}, { title = 'Team report', now = Date.now counted: bots.reduce((sum, bot) => sum + bot.counted, 0), tokens: { input: sumTokens('input'), output: sumTokens('output'), cacheRead: sumTokens('cacheRead'), cacheWrite: sumTokens('cacheWrite'), calls: sumTokens('calls') }, longestWait: longest === undefined ? null : { id: longest.id, from: longest.from, to: longest.to, waitedMs: longest.waitedMs ?? 0 }, - unhealthy: bots.filter(bot => bot.turns >= ERROR_TURNS_MIN && bot.errorRate >= ERROR_RATE_MIN).map(bot => bot.id), + // The same denominator as the per-Bot flag above: a failure that began before the window is + // counted there, so naming against `turns` alone drops a Bot the report itself flags (#1780). + unhealthy: bots.filter(bot => bot.counted >= ERROR_TURNS_MIN && bot.errorRate >= ERROR_RATE_MIN).map(bot => bot.id), } const from = clock(window.from) diff --git a/plugins/timeline/test/digest.test.js b/plugins/timeline/test/digest.test.js index 8e8910378..7e253a2e5 100644 --- a/plugins/timeline/test/digest.test.js +++ b/plugins/timeline/test/digest.test.js @@ -102,6 +102,19 @@ test('#582: a Bot that fails most turns is named as needing a look', () => { assert.match(digest.markdown, /- look: 3 of 4 turns failed/) }) +// The per-Bot flag counts against `max(turns, errors)` (#1579): a failure that began before the +// window still belongs to a turn the window carries. `## Team` must name that Bot too, or the +// report's roll-up disagrees with the section above it (#1780). +test('#1780: needs a look names a Bot whose failures began before the window', () => { + const digest = buildDigest(make({ + lanes: [{ id: 'b1', kind: 'bot', name: 'Writer' }], + utilization: [{ lane: 'b1', windowMs: 100_000, busyMs: 1000, turns: 2, errors: 3, running: false }], + }), { now: 100_000 }) + assert.deepEqual(digest.bots[0].flags, ['3 of 3 turns failed']) + assert.deepEqual(digest.team.unhealthy, ['b1']) + assert.match(digest.markdown, /- needs a look: b1/) +}) + // The rollup is the sum of the per-Bot numbers, never a separate count: a report a person reads // must not disagree with the lines above it. test('#582: the team rollup equals the Bot sections', () => {