待辦簿存得住 trigger 與 recur,但沒有東西判「現在到期了沒有」。存放那一支的檔頭明寫它不判定、算的邏輯在別的地方,這一輪就是補那個別的地方。 另開一支而不併進存放那一支。那一支的檔頭把「我不判定」寫成合約,而合約是呼叫端唯一的依據;併進去就要推翻那整段話,而讀過舊版的人會以為那一支不判、自己再判一次,於是漂移從註解裡長出來。界線做成單向:判定這一支只讀待辦檔與印值,要改狀態一律回頭叫存放那一支的完成與失敗。 事件偵測靠比對狀態快照,不改任何產生者。每一輪讀那幾支產生者留下的狀態,跟上一輪存下的快照比,有差別就算事件發生。六個事件名各自接上本機狀態來源:工作包鎖檔轉態、階段檔換值、錯誤紀錄行數變多、工作階段開始與結束、日誌暫存區清空。 這個選擇有一個明確的代價,寫進檔頭:兩輪之間發生又消失的事件會漏掉。往後誰假設「事件不會漏」就會出錯,而那種錯是無聲的。不為了補這個洞去改那幾支產生者——要它們各自在事件發生那一刻多寫一筆,任何一支忘了寫就讓對應的事件從此不再發生,而那同樣無聲,還散在五個 domain 裡查不出是誰漏的。快照比對至少壞掉時看得出來。 第一次跑一律只建快照、不發事件,否則機器上所有既有狀態都會被當成剛發生,讓每一筆事件型待辦一次全到期。同一條理由逐個來源再套一次:某個來源上一輪不存在、這一輪才出現,那個來源這一輪也只建快照。所以快照要留一列記每個來源上一輪是看得到還是不存在——少了它就分不出「上一輪沒有這個來源」與「上一輪這個來源是空的」,而只有後者的差別才算事件。來源目錄從有變成不見時也不發事件,只記警示:整個目錄被砍掉不是每一支工作包都合併了。 四種組合一個都不壓。時鐘型的排定點從那個時間往後數、一次加一個間隔,不從上次執行起算——每次拿上次執行加間隔,執行慢幾秒就往後挪幾秒,一天下來就偏掉。漏掉的排定點只補一次不逐個追補,否則機器關一天再開會連續跑幾十輪同一件事。事件型配間隔時,那個間隔照存放那一支的定義當「重新武裝的最短間隔」。 事件型的下次執行時間一律留空。事件型沒有預定時間,唯一算得出來的是重新武裝時刻,但那個值填進去會被狀態表與提醒讀成「那個時間會跑」,它其實只是「那個時間之前不會跑」,差別在畫面上看不出來。那個值改印成另一個欄位,要看的人看得到。 規格有兩條在同一個欄位上打架。同一件事不重複觸發的判準是「事件發生時間比上次執行晚」,但失敗也會寫上次執行,於是失敗過的事件型待辦再也等不到同一次事件,也就永遠不重試——而規格明確要求失敗照重試。改用連續失敗次數當第三個判準把兩者分開:狀態還是待辦而次數大於零,代表上一次執行失敗,一律算到期,不再比事件也不看排定點。完成會把次數歸零,所以成功那一次不會被誤判成要重試。 兩項刻意標成未接線並吵出來,不假裝算得出來。分析完成那一個事件的狀態來源在 wiki 上、要連網要金鑰,混進來之後金鑰一過期就讓事件偵測整項每輪失敗;更糟的是判不出來時那幾筆會靜靜地永遠不到期,跟填一個不存在的事件名一樣看不出壞在哪。cron 式子同理,五個欄位的萬用字元與步進算錯一格就是差一小時或差一天,而那種錯要等真的跑錯才看得出來。兩者的處置都是印一行未接線、列進要人接手那一節,明說「這一筆現在判不出到期,不是還沒發生」。 間隔格式規格從來沒定義過,這一輪定成正整數加單位,刻意不收裸數字——排程週期以分鐘算、心跳門檻以秒算,裸數字兩種都讀得通,讀錯就差六十倍。 巡檢那一路一併接上:快照要有人推進才活得起來。刻意不把它算進既有五項的成敗,避免動到摘要表的欄名與本輪項目那一列的文字,舊頁那些列的欄名不會跟著改,同一張表就會有兩種寫法。失敗時改成記警示與待人處理。 順手修掉一個同類的坑:用冒號當空指令去清空檔案時,重導向失敗會讓整個殼直接結束,後面的分流一次都跑不到。這一支一律改用 true 並把理由寫進註解。
25 KiB
25 KiB
jsc-assist 技能行為清單
本頁記錄 jsc-assist 每支技能的行為基準,供技能驗證比對。技能異動時,在同一個 PR 內一起更新這一頁。
assistant
| 項目 | 內容 |
|---|---|
| 觸發時機 | 要啟動助理、要停止助理、要跑一輪巡檢,或要問助理現在還在不在跑、待辦簿剩下哪幾筆時用。四個操作 start、status、patrol、stop 都走這一支。排程每一輪叫起來的也是這一支的 patrol。執行環境健檢不走這支,走 jsc-cli:doctor。技能使用次數不走這支,走 jsc-log:stats |
| 關鍵步驟 | 四個操作都先跑同一個前置步驟,取得工具根目錄(本頁記成 {CURRENT}),根目錄一律由外面餵進來:排程那一輪從叫用文字裡的「工具根目錄=」那一段取字面絕對路徑,一個指令都不跑;人在現場叫用時,叫用文字帶那一段就取那一段,沒帶才跑一次 readlink -f "${JSC_HOME:-$HOME/.jsc}/current" 自己解,那一次會跳一次權限詢問,人按一下就過。無人值守那一輪取不到根目錄就停下回報:說明條目是舊版 schedule.sh 裝的、沒有把根目錄寫進提示文字,叫人重跑一次 start 或 schedule.sh install patrol 把條目重寫,收尾狀態取 aborted;一律不跑 readlink、不跑 ls、不退回帶變數的路徑、不拿技能提示或上一次轉錄裡的路徑、也不猜。整次叫用只取這一次,之後每一次腳本呼叫都填那一個字面絕對路徑,不是每一次呼叫各取一次,也不另外加印路徑的工具,更不另外跑指令去驗那一個路徑。取到的是空的、不是絕對路徑、或那條路徑不是存在的目錄,就回報根目錄不見了、叫人跑 jsc-cli:deploy,收尾狀態取 aborted;第四項要單獨查,JSC_HOME 沒設時 readlink -f "$JSC_HOME/current" 印的是 /current、結束碼 0,非空又是絕對路徑,前三項全過得了關,之後每一條字面路徑都指向不存在的地方,所以人在現場那一次要在同一步再跑 [ -d "{剛印出來的路徑}" ],目錄存在才算取到根目錄;排程那一輪不查,它的根目錄是裝排程的人寫進條目的,根目錄不對就會在第一支腳本呼叫上失敗。除了人在現場那一次 readlink,任何指令列都不得出現 $JSC_HOME、${JSC_HOME} 或 ~:權限層比對的是還沒展開的指令字面。實測歸納出兩條判準:一、無人值守時只有允許清單上的完整字面指令跑得動,沒有「預設安全的唯讀指令」這回事,連 readlink -f "$JSC_HOME/current"、ls -d "$JSC_HOME/current" 與沒有規則的 ls -d /root/.jsc/current 都被擋;二、路徑中段的萬用字元不匹配,版本號寫成 * 的快取路徑規則一樣擋,規則與指令都必須是完整字面。排程那一輪沒有人可以按同意,被擋就是停在第一支腳本,什麼都不記,心跳也寫不出來。接著認出使用者要的是哪一個操作,patrol 那一路全程不問人。start:先照 patrol 的每一步跑完一輪巡檢,第一次心跳由那一輪寫、不另外寫、跑不完就不算啟動、跑 heartbeat.sh report 確認 state=fresh、跑 tools/schedule.sh install patrol 裝巡檢那一筆排程、把它印的 allow_rule= 每一行、patrol_root=(條目寫進去的字面根目錄,之後每一輪都從那裡讀)、環境快照提醒與 current 連結缺漏的警告原樣轉給人、依結束碼選一段收尾訊息印出——排程接上、排程寫進去了但 cron 沒在跑、排程沒接上三種各一段。心跳那一筆不裝了,install heartbeat 一律回 6。patrol:跑 tools/patrol.sh collect 取鎖並讀五項來源(那一輪另外會自己叫一次 tools/due.sh scan,把待辦簿的事件偵測與到期判定寫成「待辦簿到期與逾期」那一節,技能本文一律不自己再叫一次——scan 會推進事件快照,同一輪叫第二次就比不出任何事件,而那一次會回報零事件、看起來完全正常;要看下一輪會判出什麼就叫 due.sh events,那個子命令一律唯讀)(第五項是執行狀態事件:collect 自己叫 jsc-hooks/tools/report-status.sh drain 排空,緊接著跑 rotate,再把非 ok 的事件與「有 start 沒有配對 end」的技能彙整成監控頁那一節;技能本文一律不自己再跑一次 drain)、結束碼 4 就讓開不寫任何東西、結束碼 1 與 3 照樣把這一輪寫上監控頁、hash 是空的就 abort、經 jsc-gitea:wiki 讀回 MONITOR_{HASH} 舊頁、基本資料原樣留著、最新一輪那一塊整塊換成 latest_file、summary_file 的本輪那一列擺最上面(五欄:巡檢時間、本輪判定、各項成敗、待人處理、警示來源)、舊的資料列接在下面並截到 24 列、三塊重組成整頁、寫回之前先把這一頁要放進去的每一個連結交給 jsc-gitea/tools/link-check.sh(結束碼 0 才整頁寫回,結束碼 1 就把 DEAD 那幾筆原樣回報並 abort,2、3、7 同樣 abort,一個連結都沒有就跳過這一次驗證並照實說明)、頁不存在(唯有結束碼 4)才用 newpage_file 建頁、讀不回舊頁就不寫、監控頁寫成之後跑 gitea.sh wiki-url 取那一頁的絕對網址並依結束碼分流(4 回步驟三重寫、5 沒有 html_url、7 與 8 走 abort,其餘非 0 也走 abort,網址取不到就不寫那一個區塊)、換掉 contents_file 那個 H2 區塊裡 {監控頁絕對網址} 那個佔位、換完再用 link-check.sh 驗那一個網址(結束碼 0 才寫那一個區塊;非 0 一律不寫,比照目錄頁結束碼 3 當成那一個區塊沒更新、這一輪照樣往下寫心跳,並把連不到的那一筆列進待人處理)、用 jsc-gitea/tools/wiki-contents.sh upsert MONITOR 1 "MONITOR_{HASH}" {區塊檔} 以 H2 標題(也就是內容頁頁名,取 collect 印的 page=)當鍵更新 MONITOR_CONTENTS 自己那一個區塊並一律帶上 templates/monitor-contents.md 當範本(第三個參數 1 是 key-col,只在舊頁還是 markdown 表格時用得到:舊表格第 1 欄「監控頁」持有身分,那一格是 [MONITOR_{HASH}](網址),轉檔時只取文字當標題;頁面已經是條列格式時這個參數被忽略,照樣固定給 1)、目錄頁回 3(CONTENTS 存取庫沒設定)不中止這一輪,照樣往下寫心跳,並把「設 JSC_WIKI_REPO_CONTENTS 或 JSC_WIKI_REPO」列進待人處理、監控頁任一失敗或目錄頁其餘非 0 才 abort 且不寫心跳、跑 tools/patrol.sh finish 寫心跳、最後印出各項結果、本輪事件數與非 ok 事件數、非 ok 事件的明細(kind、name、status、exit、detail)、以及有 start 沒有配對 end 的那幾支技能(單獨列,那代表那一輪中止了)、兩次寫入各自的連結驗證結果(通過、無連結而跳過、或被擋下並附結束碼與 DEAD 明細)、判成警示時的警示來源與待人處理列。status:跑 heartbeat.sh report 取心跳現況、把 state 對映成新鮮、過期、心跳檔損壞、不存在、不自己解析心跳檔也不自己判定、從 file= 解出助理目錄後列出 tasks/ 底下每一個檔案並解析 state、title、next_run、fail_count、跑 tools/schedule.sh status 取排程現況與週期、印成心跳、排程、待辦三塊、fail_count 大於 0 的列標上「已連續失敗 N 次」、心跳與排程兜起來會誤讀的四種組合各補一句話。stop:先跑 heartbeat.sh report 留下原本的狀態、再跑 tools/schedule.sh remove all 移除排程與舊版遺留的心跳條目、最後才跑 heartbeat.sh clear 清掉心跳、印出停止訊息並說明心跳清掉之後閘門會擋人、同時說明閘門還沒接線所以現在擋不到人。四個操作最後都一樣:回報印完之後跑一次 jsc-hooks/tools/report-status.sh skill-end jsc-assist:assistant {status} {結束碼},start 由 hook 記、end 由這裡寫,不寫就等於這一次自己看起來中止了 |
| 外部呼叫 | 工具一律走前置步驟取得的根目錄底下那一組不帶版本的路徑(本頁記成 {CURRENT},實際填的是像 /root/.jsc/current 這種字面絕對路徑):{CURRENT}/jsc-assist/tools/patrol.sh、{CURRENT}/jsc-assist/tools/schedule.sh、{CURRENT}/jsc-assist/tools/due.sh、{CURRENT}/jsc-hooks/hooks/heartbeat.sh,wiki 那一支是 {CURRENT}/jsc-gitea/tools/gitea.sh,目錄頁那一支是 {CURRENT}/jsc-gitea/tools/wiki-contents.sh,連結驗證那一支是 {CURRENT}/jsc-gitea/tools/link-check.sh,執行狀態事件那一支是 {CURRENT}/jsc-hooks/tools/report-status.sh,範本是 {CURRENT}/jsc-assist/templates/monitor-contents.md;JSC_HOME 沒設時,人在現場那一次 readlink 自己退回 ~/.jsc 再解,排程那一輪則直接用條目餵進來的值,指令列上不留變數也不留波浪號;不拿技能提示給的快取基底目錄組工具路徑——權限只放行 current 那一組,快取路徑帶版本號,規則寫成萬用字元也對不上,用錯路徑會被靜靜擋掉。jsc-hooks/hooks/heartbeat.sh 的 write、report、clear 三個子命令,六個結束碼各有處置:0 往下走、1 與 3 印「助理未運行」、2 回報判不出狀態並停下、4 當成不新鮮並回報心跳檔損壞、5 是嚴重狀況要吵出來且不得回報成功、6 是呼叫寫錯要更正後重跑。write 只由 tools/patrol.sh finish 呼叫,技能自己不呼叫。本 domain 的 tools/schedule.sh 的 install、remove、status 三個子命令:install 會查 {CURRENT}/jsc-assist 與 {CURRENT}/jsc-gitea 兩個連結在不在、不在就警告且不代建,會把巡檢的 CLI 用 command -v 解成絕對路徑、把 GITEA_HOST、GITEA_TOKEN、JSC_HOME、JSC_ASSISTANT_HEARTBEAT_TTL 與所有已設定的 JSC_WIKI_REPO 系列快照進條目(含內容頁的 JSC_WIKI_REPO_MONITOR 與目錄頁的 JSC_WIKI_REPO_CONTENTS,名單當下從環境撈、不寫死,新頁型自動涵蓋)、條目自帶 JSC_GITEA_CONFIRM=yes、把自己解好的字面根目錄寫進條目的提示文字(固定格式 工具根目錄={字面絕對路徑},那一輪就是從這裡讀根目錄)並印成 patrol_root=、--patrol-cmd 或 JSC_ASSIST_PATROL_CMD 給的自訂指令沒帶那一段時只警告不中止、並印出這一輪要開的 allow_rule= 規則(七支腳本各三種呼叫形式,含 gitea.sh、wiki-contents.sh、link-check.sh 與 jsc-hooks/tools/report-status.sh——Skill(jsc-gitea:wiki) 只放行叫用技能,技能內部的 Bash 呼叫仍各自受檢;路徑是 current 那一組確切路徑,不用萬用字元);七個結束碼各有處置:0 往下走、1 是條目裝了但 cron 沒在跑要照實講不會執行、2 是缺 jsc-hooks 導致門檻讀不到、3 是這台機器沒有排程機制、4 是排程操作失敗要原樣引用 stderr、5 是回讀驗證失敗要叫人自己去看 crontab -l、6 是呼叫寫錯,含 install heartbeat、週期塞不進門檻、判不出 CLI、那一支 CLI 的執行檔不在 PATH 上,以及 JSC_HOME 解不出絕對路徑(條目寫不出字面根目錄)。本 domain 的 tools/patrol.sh 的 collect、finish、abort 三個子命令,七個結束碼各有處置:0 往下走、1 部分失敗照樣寫頁、2 是 finish 找不到 heartbeat.sh 要回報「記下來了但沒有心跳」、3 是各項全失敗照樣寫頁且判定異常、4 是讓開或鎖被搶走一律不寫心跳、5 是檔案系統失敗要吵出來、6 是呼叫寫錯。巡檢那五項讀 jsc-log/tools/usage-stats.sh、jsc-hooks/hooks/version-guard.sh report、jsc-hooks/hooks/restart-gate.sh report、$JSC_HOME/sessions/*.stage、$JSC_HOME/wp/*.pr、heartbeat.sh report、jsc-hooks/tools/report-status.sh drain 與 rotate,除了排空會把事件流的位移往前推之外全部只讀,任一項失敗不影響其餘各項。report-status.sh 三個結束碼各有處置:0 是排空到新事件、3 是沒有新事件(正常狀態,不是失敗)、2 是呼叫寫錯;找不到這一支、drain 回 0 與 3 以外的碼、或 rotate 回非 0,都只讓這一項標成失敗或記一筆警示,一律不中止那一輪——回報鏈自己壞掉不可以把被回報的那一輪拖下去。rotate 只在 drain 成功時緊接著跑:中間隔越久,那段時間新寫進來的事件被搬進備份檔而從此排不到的機會越大;排空失敗時位移狀態未知,這時候輪替會直接吃掉還沒排空的那一批。配對以 session 加 name 為鍵,不只看 name:五支 CLI 併發時同一支技能會有好幾個工作階段同時在跑。沒配對到的 start 留在 $JSC_HOME/assistant/events-open.tsv 跨輪繼續配對,開超過心跳門檻才算疑似中止,未達門檻的算還在跑,超過一天沒配對到就丟掉。wiki 讀寫一律經 jsc-gitea:wiki,技能自己不拼 API 呼叫;只有目錄頁那一個 H2 區塊例外,走 jsc-gitea/tools/wiki-contents.sh upsert,它自己解 CONTENTS 存取庫、自己讀回整頁比對標題,舊頁還是 markdown 表格時自己先整頁轉成 H2 區塊再寫,七個結束碼各有處置:0 已更新或已新增、1 組不出頁面內容或寫入失敗要 abort(找不到同名標題不算錯,那是附加)、2 參數錯就改正重跑(範本路徑不存在也回這一碼,代表 plugin 沒裝齊)、3 是 CONTENTS 存取庫未設定且不中止這一輪、4 是頁不存在又沒給範本,本技能一律帶第五個參數所以不會出現、7 金鑰失效要 abort、8 其他 API 失敗要 abort。比對鍵取 H2 標題,也就是內容頁頁名 MONITOR_{HASH},不取「監控頁」那一條的連結:連結含 GITEA_HOST 與頁名的網址編碼,那三樣一變鍵就對不上,同一台機器每輪多附一個區塊;頁名只由 {主機名}/{登入帳號} 決定,那三樣都動不到它。連結一律寫成 [{文字}]({絕對網址}),網址只取 gitea.sh wiki-url 印的那一個、不自己組路徑,那一支的結束碼 4、5、7、8 與其餘非 0 各有處置;每一個要放進頁面的連結在寫入前先過 jsc-gitea/tools/link-check.sh,它每個網址印一行 `{OK |
| 完成條件 | 四個操作都要先取得工具根目錄,之後每一支腳本都拿那一個字面絕對路徑呼叫;排程那一輪只從叫用文字取,取不到就回報條目沒帶根目錄並中止,收尾狀態取 aborted,不得改跑 readlink 或任何解析指令,也不得改用帶變數的路徑硬跑;人在現場叫用時取不到才自己解一次,解出來的要是一條存在的絕對路徑(同一步用 [ -d ] 查過),解不出來或目錄不存在就回報缺 current 並中止,同樣取 aborted。start 要那一輪巡檢的 finish 回 0 且 report 回 state=fresh,才算啟動成功;巡檢沒寫成心跳一律回報失敗並停下,不得宣稱啟動;schedule.sh install patrol 回 1 要講明條目不會被執行與 sudo service cron start,不得宣稱排程會定時執行;回 0 或 1 都要把 allow_rule= 各行、「條目含金鑰快照、變數改了要重裝」這句提醒,以及 current 連結缺漏的警告轉出去。patrol 要五項各自有 status、「待辦簿到期與逾期」那一節要有逐筆判定表(due.sh 回 0、1、2、3、4 都算判過,其中 1、2、3 各記一筆警示與一列待人處理;回別的碼就照實寫「這一輪判不出到期」並說明那不代表沒有任何一筆到期,不留空白也不寫成「都沒到期」)、執行狀態事件那一項要印出本輪事件數、非 ok 事件數與未配對的 start(drain 回 3 是沒有新事件,照樣算這一項讀到底)、監控頁那一頁要放的連結全部通過 link-check.sh(或整頁本來就沒有連結)、監控頁三塊重組寫成、目錄頁那一個 H2 區塊的網址通過 link-check.sh 後更新成功,或以目錄頁結束碼 3、或以連結驗證非 0 回報成沒更新、finish 回 0,才算一輪跑完;collect 回 4 是讓開,不算失敗也不寫任何東西;舊頁讀不回來就不寫,回報「這一輪沒有結果」;連結驗證沒過就不寫那一頁,監控頁沒寫成就 abort,心跳一定不寫;目錄頁除了結束碼 3 之外的非 0 也一樣 abort,結束碼 3 只少一筆索引,那一輪的結果已經在監控頁上,照樣寫心跳並把缺的變數列進待人處理;目錄頁那一個區塊的連結驗不過同樣只少一筆索引,照樣寫心跳並把那一筆列進待人處理。status 要印出現況表,或印出「助理未運行」並說明原因;心跳不存在、待辦簿目錄不存在、待辦簿零筆、排程沒裝,四種都算正常結束。stop 要 schedule.sh remove all 先回 0、clear 再回 0,並印出帶三段話的停止訊息;remove 非 0 就回報排程還在、助理停不掉,不清心跳也不印停止訊息;clear 回 5 就回報心跳檔還在、助理沒有確實停掉,不印停止訊息。四個操作都要在回報之後寫一筆 skill-end,status 取 ok、blocked、failed、degraded、aborted 五選一,要與回報出去的結果一致;那一支回非 0 只回報成回報鏈的缺陷,不改寫這一次操作的成敗 |
| 可驗證跡象 | 四個操作的轉錄裡,每一條指令列都是字面絕對路徑,開頭是 /,沒有 $JSC_HOME、${JSC_HOME} 或 ~,也沒有任何一次因為路徑帶變數而跳出來的權限詢問;排程那一輪從頭到尾一次 readlink、一次 ls 都沒有,根目錄直接取自叫用文字;人在現場那一路才可能有 readlink,而且同一次叫用只出現一次。start 之後 $JSC_HOME/assistant/heartbeat 存在,ts 是剛才那一輪的時間,crontab -l 找得到一筆帶 # jsc-assist:assistant patrol 的條目,而且只有一筆,帶 # jsc-assist:assistant heartbeat 的舊條目一筆都不剩;那一筆條目裡的 CLI 是絕對路徑,前面帶著 JSC_GITEA_CONFIRM=yes 與環境變數快照,提示文字裡有「工具根目錄=」接一個字面絕對路徑,那個值與 install 印的 patrol_root= 和 allow_rule= 用的根目錄完全相同,不是變數也不是快取實體路徑;install 印出的 allow_rule= 都是 current 那一組展開後的字面絕對路徑,沒有變數、沒有波浪號、沒有萬用字元,也沒有 Write(...),而且 jsc-gitea/tools/link-check.sh 與 jsc-hooks/tools/report-status.sh 那三種呼叫形式都在裡面。patrol 跑完之後 wiki 的 MONITOR_{HASH} 只有三塊:基本資料一字未改、最新一輪換成本輪、摘要表最上面一列是本輪且總列數不超過 24,頁名的 {HASH} 是 40 碼大寫十六進位,雜湊來源那一列寫的是不含網域的短主機名;CONTENTS 存取庫裡的 MONITOR_CONTENTS 只有自己那一個 H2 區塊變動,同一台機器從頭到尾只有一個區塊,標題是 MONITOR_ 接 40 碼大寫十六進位、標題上不帶連結也不帶網址,區塊裡「監控頁」那一條是 [{頁名}]({絕對網址}) 這種連結、點下去開得起那一頁,「HASH」那一條是裸 HASH、40 碼大寫十六進位、不帶連結,八條欄位一條都不缺、格式是 - {欄位名}:{值},頁上一個 markdown 表格都不剩,兩頁上點得到的連結沒有一個是死的——把頁上的網址抓出來重跑一次 link-check.sh,應該全部是 OK、結束碼 0,別台機器的區塊一字不動,$JSC_HOME/assistant/patrol/ 底下有本輪的 latest.md、summary.md、summary-row.md、newpage.md、contents-entry.md,摘要列是五欄、警示來源那一欄有值或寫「無」;兩支腳本不是從 current 那一組路徑跑起來時,stderr 會有一行 [WARN] 點出實際路徑與應該用的路徑,$JSC_HOME/assistant/usage-prev.tsv 換成本輪的累計數,$JSC_HOME/assistant/events-prev.tsv 換成本輪的狀態快照(三欄定位字元分隔,每一個來源另有一列 meta)、$JSC_HOME/assistant/events-seen.tsv 是每一個事件名的累計次數與最後發生時間,$JSC_HOME/assistant/patrol/due/ 底下有本輪的 due.md 與 rows.txt(rows.txt 是十一欄定位字元分隔,欄位順序印在 rows_columns=;要逐筆取值就讀它,不要切 task= 那幾行,那幾行的四個欄位都可能帶空白),第一次跑那一輪的 due.sh 印 first_run=1、事件數為 0,且 tasks/ 底下一個檔案都沒被改動,$JSC_HOME/assistant/patrol.lock 已經放掉;監控頁的最新一輪有「執行狀態事件」那一節,節裡有本輪事件數、非 ok 事件數,以及非 ok 明細與未配對 start 兩張表(一筆都沒有時寫明「沒有」,不留空表格);$JSC_HOME/usage/scan-state/events.offset 的數字往前推到本輪排空的位置,$JSC_HOME/assistant/events-open.tsv 只剩下還沒配對到 end 的那幾筆。讓開的那一輪沒有任何寫入跡象。stop 之後心跳路徑不存在,crontab -l 找不到任何 # jsc-assist:assistant 條目。以上都不動別人的排程條目,條目數量前後相同。status 無寫入跡象,只有回報內容。四個操作跑完,$JSC_HOME/usage/events.jsonl 最後都多一筆 name 是 jsc-assist:assistant、phase 是 end 的事件,status 與回報出去的結果一致,而且同一個 session 下它與 hook 記的那一筆 phase=start 配得起來。四個操作都不動 tasks/ 底下的檔案,也不動 worktree 與程式碼存取庫。排程的 log 一律在 $JSC_HOME/assistant/schedule.log,不落在任何存取庫 |