fix(assistant): 提醒佇列把內建項標成另一種列

實測踩到:這台機器的佇列八筆全是委派清單種入的內建項,動作是只提醒、
還沒有執行入口,所以每一輪都到期。於是每一個工作階段開頭固定吐八行
一模一樣的東西。

那不是提醒,是噪音——而讀佇列那一支自己的註解裡就寫著「對著一個刻意的
決定每個工作階段催一次,那是噪音不是提醒」。這一批就犯了那一條,只是
換成另一種來源。

分法照該有的處置切:使用者自己登錄的那一筆,人看到就做得了;內建項那
幾筆等的是接線,不是人。所以佇列的第一欄多一種值 builtin,讀的那一端
把前兩種逐筆點名、第三種收成一行總數。

種類在這一邊判,不在讀的那一邊:spec_key 只有這裡讀得到,而讀的那一端
的規則是只印不判。收尾多印一個 reminders_builtin=。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-07 13:39:45 +08:00
co-authored by Claude Opus 5
parent 404cf8f4fd
commit 61f4a2dbc7
6 changed files with 37 additions and 10 deletions
+2 -2
View File
@@ -348,9 +348,9 @@ That property holds only while nothing fakes a heartbeat. **`write` is called by
One round: read five sources, record the result, then beat. Everything before the heartbeat is read-only except the round's own scratch files. Ask nobody anything.
1. **Collect.** Run `{CURRENT}/jsc-assist/tools/patrol.sh collect --trigger 排程` (use `--trigger 手動` when a person asked for this round). That is the same split step 0 branched on: 排程 is the unattended round that read its root out of the invocation text, 手動 the round somebody asked for. Judge the exit code by the patrol.sh table. Exit 4 stands the round down — report the holder and its age from the printed `lock=busy` line, and stop; write no page and no heartbeat. Exit 5 and 6 stop the round the same way, with the code and the stderr text. Exit 0, 1 and 3 all carry on to step 2. Record `round=`, `lock_broken=`, `hash=`, `page=`, `verdict=`, `failed_sources=`, `warn_sources=`, `pending=`, `tasks_total=`, `tasks_failing=`, `tasks_due=`, `tasks_overdue=`, `reminders=`, every `item=` line, and the file paths `latest_file=`, `summary_file=`, `summary_row_file=`, `newpage_file=`, `contents_file=` and `due_rows_file=` — that last one is what step 5 has to be given, and an empty value there means the judging step produced no list, so step 5 has nothing to act on and says so rather than falling back to anything. Completion condition: the round id, the page name and the five file paths are recorded, or the stand-down or the failure was reported and the round stopped.
1. **Collect.** Run `{CURRENT}/jsc-assist/tools/patrol.sh collect --trigger 排程` (use `--trigger 手動` when a person asked for this round). That is the same split step 0 branched on: 排程 is the unattended round that read its root out of the invocation text, 手動 the round somebody asked for. Judge the exit code by the patrol.sh table. Exit 4 stands the round down — report the holder and its age from the printed `lock=busy` line, and stop; write no page and no heartbeat. Exit 5 and 6 stop the round the same way, with the code and the stderr text. Exit 0, 1 and 3 all carry on to step 2. Record `round=`, `lock_broken=`, `hash=`, `page=`, `verdict=`, `failed_sources=`, `warn_sources=`, `pending=`, `tasks_total=`, `tasks_failing=`, `tasks_due=`, `tasks_overdue=`, `reminders=`, `reminders_builtin=`, every `item=` line, and the file paths `latest_file=`, `summary_file=`, `summary_row_file=`, `newpage_file=`, `contents_file=` and `due_rows_file=` — that last one is what step 5 has to be given, and an empty value there means the judging step produced no list, so step 5 has nothing to act on and says so rather than falling back to anything. Completion condition: the round id, the page name and the five file paths are recorded, or the stand-down or the failure was reported and the round stopped.
**The round also writes the reminder queue.** `collect` leaves `$JSC_HOME/assistant/reminders.tsv` behind — a tab-separated file whose first line is `round`, the round id, the round's UTC timestamp, the failing count and the same moment in epoch seconds — the ISO string is for a person to read and the epoch is what a reader does arithmetic on, so no reader has to carry its own date parser — followed by one `remind` row per reminder-type entry that came due and one `overdue` row per entry past its deadline, each carrying the id, the reason or the how-long-overdue wording, and the title. It is printed as `reminders=` and `reminders_file=`. **The judgement stays here and the queue is only its output**: whatever reads it later prints and nothing more, because a reader that compared `due` and `next_run` against the clock itself would be a second judgement of the same thing, and the day the two disagreed both would look right. The timestamp on that first line is what the design turns on — a queue nobody refreshed reads exactly like a fresh one, so the reader has to be able to say how old it is. **"No reminders" and "nobody computed the reminders" must never look the same.**
**The round also writes the reminder queue.** `collect` leaves `$JSC_HOME/assistant/reminders.tsv` behind — a tab-separated file whose first line is `round`, the round id, the round's UTC timestamp, the failing count and the same moment in epoch seconds — the ISO string is for a person to read and the epoch is what a reader does arithmetic on, so no reader has to carry its own date parser — followed by one row per item to surface, each carrying its kind, the id, the reason or the how-long-overdue wording, and the title. There are three kinds, and the split is the point: `overdue` for an entry past its deadline, `remind` for a reminder-type entry a person registered, and `builtin` for one the delegation list seeded. **A built-in reminder waits on plumbing, not on a person** — its action is `remind`, nothing executes it yet, so it comes due every round and reads identically every round; eight of them on this machine turned every session's opening into eight lines nobody could act on. Whatever prints the queue names the first two kinds and gives the third a single count line, which is why the kind is decided here, where `spec_key` is readable, and not there. The counts are printed as `reminders=` for the named kinds and `reminders_builtin=` for the third, alongside `reminders_file=`. **The judgement stays here and the queue is only its output**: whatever reads it later prints and nothing more, because a reader that compared `due` and `next_run` against the clock itself would be a second judgement of the same thing, and the day the two disagreed both would look right. The timestamp on that first line is what the design turns on — a queue nobody refreshed reads exactly like a fresh one, so the reader has to be able to say how old it is. **"No reminders" and "nobody computed the reminders" must never look the same.**
**`tasks_overdue=` and `tasks_failing=` earn a 待人處理 row every round, and `collect` writes both of them itself.** Neither condition heals on its own: a deadline that has passed does not become un-passed, and an entry that fails retries next round and fails again. So the row is repeated every round rather than suppressed after the first — **"already reported" is not "already handled"**, and the assistant does not pause an entry on anybody's behalf; `paused` is a state a person sets and only a person clears. The overdue count comes from the judging step and is a dash when that step could not judge, which is not the same as zero. The monitor page carries the two named tables under 待辦簿到期與逾期: which entries are overdue and by how long, and which ones have been failing and how many times. **The directory page gains no field for either.** That page holds one block per machine, each written by that machine's own round, so a new field would only appear for machines already on the new version, and a reader could not tell "nothing overdue here" from "this machine has not written that field yet" — the same reason the 本輪非 ok 事件數 field was kept off it.