Skip to content

fix: a paused repository stops naming a next run on the page, dashboard and digest - #755

Merged
HugoXK merged 1 commit into
mainfrom
dev3/723-disabled-next-run
Nov 15, 2026
Merged

HugoXK merged 1 commit into
mainfrom
dev3/723-disabled-next-run

Conversation

@HugoXK

@HugoXK HugoXK commented Nov 15, 2026

Copy link
Copy Markdown
Owner

Written-by: HugoXK
Author: Fledge Platform Dev-3

Fixes #723

What a user sees differently

A paused repository stops promising a run on all three surfaces.

  • /repos/:id — a repository with enabled = 0 and a saved schedule row used to print the planner's instant (2026-11-16 01:00:00 UTC in the fixture) tagged data-next-source="schedule", while the worker only considers enabled = 1 AND auto_iterate = 1. It now prints the page's existing sentence 仓库已停用,自动运行不会开始 (Repository disabled; no automatic run starts), tagged interval, with or without a row.
  • /dashboard — the all-clear card used to be able to name a paused repository — 下次自动运行:acme/paused · 2026-11-15 05:00:00 UTC in the red run below — because the candidate list and the runnable flag consulted auto_iterate only. It now names the enabled repository that does have a next run, and when every auto-iterating repository is paused it says 自动运行暂停:<repo> · 仓库已停用,自动运行不会开始 and prints no instant.
  • Daily digest — the email named the same paused repository with its row's instant (下次自动迭代:o/disabled-scheduled · 11/16 09:00 in the red run below) after fix: print the scheduled next run in the daily digest #678 put the digest on the shared next-run helper. It now prints 下次自动迭代:o/disabled-scheduled · 仓库已停用,自动运行不会开始 and no instant, so the email agrees with the page.

What changed

While this branch was being built, live main moved: 82dc3c6 (#678, closing #713) added the daily digest as the third surface through a new src/lib/nextRunFor.ts, and 9582ffb (#751) touched unrelated view files. The branch is forward-merged onto 9582ffb (no rebase), and the card's one condition — enabled answers before the row does — is applied where the merged code now needs it:

  • src/app.ts — both routes withhold scheduledNext for a repository with enabled !== 1, so the page falls to the interval rule, whose disabled state already exists (src/lib/nextRun.ts → nextRunLine, src/views/pages.ts:730).
  • src/views/pages.ts — the dashboard's schedule-row branch (nextScheduledRun) marks a row runnable only when the repository is enabled, so a facts list that somehow carried a row for a paused repository still cannot make it the chosen candidate.
  • src/lib/nextRunFor.ts (new in main) — returns the interval rule's disabled answer before it reads the row, so buildDigest follows the same rule. No member is added to NextRunState and the wording is not duplicated.
  • test/nextScheduledRun.test.ts — the three red-first view cases stay; the merged helper's disabled+row case flips from scheduled/instant to disabled/no instant (this is bug: the daily digest must print the repository's scheduled next run, not the interval answer #713's case 2b for the helper).
  • test/jobs.test.ts — bug: the daily digest must print the repository's scheduled next run, not the interval answer #713's digest case 2b flips: a disabled repository with a row now says 仓库已停用,自动运行不会开始 and prints no instant.
  • docs/architecture.md — the one-next-run paragraph states that enabled is answered before either rule on the page, the dashboard and the digest, naming listAutoRepos and REPO_DISABLED as the worker's truth; docs/decisions/0017-one-next-run-decision-three-surfaces.md carries an amendment for the same correction.
  • changelog.d/723-disabled-next-run.fixed.md.

The worker stays the truth this matches: listAutoRepos() is WHERE enabled = 1 AND auto_iterate = 1 and a manual start on a paused repository is refused with REPO_DISABLED. data-next-source stays honest: the line for a paused repository is the interval rule's disabled answer, not the planner's. Nothing else moves: disabling still keeps the schedule row, listRepos is untouched, and an enabled repository with a row still prints the planner's instant on all three surfaces (the existing #564/#688/#713 cases in the same files pass unchanged).

Evidence

Red at live main e5397e1 (the unmodified views), in test/nextScheduledRun.test.ts — # pass 6 / # fail 3, exactly the new view cases:

  • page, disabled + row: actual 2026-11-16 01:00:00 UTC, expected 仓库已停用,自动运行不会开始
  • dashboard, paused vs enabled: the card was 下次自动运行:acme/paused · 2026-11-15 05:00:00 UTC and never named acme/enabled
  • dashboard, paused only: the card named an instant instead of the paused sentence

Red at the forward-merged tree 3f69e74, with the flipped assertions and the helper still row-first — test/nextScheduledRun.test.ts helper case not ok (10 pass / 1 fail), test/jobs.test.ts digest case 2b not ok (4 pass / 1 fail, actual 下次自动迭代:o/disabled-scheduled · 11/16 09:00).

Green at this tip: test/nextScheduledRun.test.ts 11/11, the digest cases 5/5, npm run check exit 0 with 1860/1860 (lint, typecheck, tests and build), changelog: 25 fragment(s) ok, version: 0.3.6 ok.

Risk

T2, computed from the diff: src/app.ts, src/views/pages.ts, src/lib/nextRunFor.ts. The failure mode if this is wrong is a page, card or email that hides a next run for a repository that is in fact enabled — which the unchanged #564/#688/#713 "enabled repository keeps its planner instant" assertions guard.

Ran:

  • npx tsx --test test/nextScheduledRun.test.ts — 11/11 at this tip; 6 pass / 3 fail at e5397e1; 10 pass / 1 fail at the merged tree before the helper change
  • npx tsx --test --test-name-pattern "digest" test/jobs.test.ts — 5/5 at this tip; 4 pass / 1 fail at the merged tree before the helper change
  • npm run check — exit 0, 1860/1860 at this tip
  • node scripts/changelog.mjs check — 25 fragment(s) ok
  • node scripts/version.mjs check — 0.3.6 ok
  • node scripts/risk-gate.mjs --base 9582ffb --head HEAD — risk-tier: T2; basis: src/app.ts

Not run: the Node 24 half of the CI matrix (CI runs it), and a browser pass — the surfaces are asserted through app.inject() with the rendered HTML the browser receives, including the data-next-action and data-next-source attributes.

Published tip: 84f535539f03e7bc2e59355461824de0ab430577 (API-built on 9582ffb, single parent)
Tree: 30d1895042572dd7e266b99c722085a4248a0e21
Base: 9582ffbeb1930c05f21d5eb13acd40680cd60eb1 (live main, forward-merged into the branch)
Diff: 8 files, +161/−18 (1 added, 7 modified, no deletions, no renames, every mode 100644)

Transport: the seats that built and published this commit have no smart-HTTP git route, so the published commit is rebuilt through the Git Data API from the eight files at the verified tree. notes/723/723-files.tar (sha256 bf2d232ec60a476f8183df4a6dea71fa8ab40f10270c78611d6352766260086a) carries those files; applying them onto 9582ffb rebuilds tree 30d18950 exactly, and that equality is the accept condition — a different tree would be reported rather than pushed. The diff against 9582ffb is the one below, and the green run above is at this tree, so the published commit and the verified commit are the same content. Prerequisite commits (9582ffb) are live main.

The helper answers `enabled` before a saved schedule row, so the page, the dashboard and the
digest all stop naming a run the worker will never do (listAutoRepos is enabled=1 AND
auto_iterate=1, and a manual start refuses REPO_DISABLED).

@HugoXK-bot HugoXK-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: PASS at 84f5355
Written-by: HugoXK-bot
Author: Fledge Platform Reviewer
Posted-by: HugoXK-bot
Tip SHA: 84f5355

T2 (#723): a paused repository stops naming a next run on the page, the dashboard and the digest.

Judged at this tip against the worker truth: listAutoRepos() is WHERE enabled = 1 AND auto_iterate = 1 (src/db.ts:4240) and the scheduled path is the loop at src/jobs/service.ts:3454-3470, so a row alone never buys a run.

What I checked in the tree:

  • src/lib/nextRunFor.ts:40-65 — enabled !== 1 now answers through nextRun, which returns {at:null, state:'disabled'} (src/lib/nextRun.ts:44) before the row is read; the row branch is unchanged for an enabled repository. Both callers are covered by that one rule.
  • src/app.ts:1277-1285 (dashboard) and :1912-1920 (repository page) withhold scheduledNext when repo.enabled !== 1, so the page prints its existing disabled sentence and keeps data-next-source="interval"; src/views/pages.ts:780-790 states the same condition where the dashboard's candidate is chosen, so a paused repository cannot shadow an enabled one with an earlier cron.
  • The dashboard's all-paused answer and the digest wording follow from those two changes, not from a fourth code path; test/nextScheduledRun.test.ts:300-410 pins the page line, the enabled-candidate dashboard and the all-paused dashboard, and test/jobs.test.ts:488-513 rewrites the digest expectation.
  • Docs match the behaviour: docs/architecture.md and the ADR amendment record that enabled answers before the row, cite listAutoRepos and REPO_DISABLED, and supersede the old "a disabled repository with a row follows the row" consequence.

Ran:

  • npx tsc --noEmit at this tip — clean
  • npx tsx --test test/nextScheduledRun.test.ts test/jobs.test.ts at this tip — 112/112
  • test-merge of this tip onto live main 0d6f8935 (the PR base b0253923 has since been joined by #699): the API diff applied cleanly in a scratch worktree, nothing pushed; npx tsc --noEmit clean; npx tsx --test test/*.test.ts — 1886/1886
  • node scripts/risk-gate.mjs --base 0d6f89357db76255caeeaf98b9670fbfc87b2c37 on the applied tree — risk-tier: T2; basis: src/app.ts
  • check-runs at 84f53553 — 27 green, review-gate red; that is this verdict
    Not run: a browser pass over /repos/:id and /dashboard, a real SMTP digest send, Docker.

Two notes, neither blocking:

  1. The worker truth is a conjunction but only enabled is consulted. enabled = 1, auto_iterate = 0 with a saved schedule row still prints the planner instant on /repos/:id, beside the same line's "仅手动运行 / Manual runs only", while the tick loop only walks listAutoRepos() and nextRun already has the honest state 'auto-off' (src/lib/nextRun.ts:44-50 for the interval answer). That state is reachable — the schedule save route (src/app.ts:1492-1522) does not require auto_iterate, and the settings form writes the two flags independently. The dashboard and the digest are safe (the dashboard filters auto_iterate === 1; the digest computes a next run only inside if (repo.auto_iterate === 1), src/jobs/service.ts:2659). Suggested follow-up card: withhold scheduledNext when repo.auto_iterate !== 1 as well, plus one page test for that state. I am not blocking this tip on it: #723's Expected is enabled = 0, and the fix answers that on all three surfaces.
  2. In the dashboard row builder the repo.enabled === 1 term is redundant with the route withholding the row — deliberate belt and braces, and it is the right place for the condition; no change wanted.

Conclusion: PASS at 84f535539f03e7bc2e59355461824de0ab430577. The three surfaces now say what the worker does for a paused repository, the tests pin each surface, and the merged tree with live main is green. Do not merge a later tip under this PASS.

@HugoXK-bot HugoXK-bot added the review:approved Issue reviewed and approved by the Fledge Platform Reviewer label Nov 15, 2026
@HugoXK
HugoXK merged commit 4b8065f into main Nov 15, 2026
35 of 36 checks passed
@HugoXK
HugoXK deleted the dev3/723-disabled-next-run branch November 15, 2026 04:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review:approved Issue reviewed and approved by the Fledge Platform Reviewer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: a disabled repository with a schedule row still names a next run on the page and the dashboard

2 participants