t195:燈號不再說謊+加診斷 log(leo:「燈號是真的還是假的?」→ 是假的)

## leo 實撞
丟 PDF 進 youlinhsieh-test1 沒反應;按「立刻同步」後畫面顯示
「正在整理知識卡」,但**卡從來沒產出**。

## 燈號為什麼是假的
舊版 describeStatus 只看「sync-now 訊號檔存不存在」就顯示「同步中…」。
那只代表**排隊了**,不代表有人處理:leo 的 collector 在 11:25 跑完最後一輪,
他 11:38 按同步 ⇒ 訊號檔沒人消化 ⇒ 畫面一直說在整理,實際 13 分鐘沒跑過。
**這比沒有燈號更糟——它在說謊。**

修:燈號要有憑有據——
  · collector 沒在跑 → 明說「同步引擎沒有在跑」並告訴他怎麼辦
  · 有排隊 **且** 引擎活著 → 才是「同步中」
  · 其餘 → 看守中

## 順手補上診斷 log(~/.arcrun-rag/app.log)
startSupervisor 以前只 Stat(config) 就靜默 return,出問題完全查不到
(我這次也是繞了很久才確定它有啟動)。現在每個 return 點都留痕。

## 🔴 但真正擋住產卡的是另一個 401(未修,需 leo 裁)
實測鏈路:掃到 PDF  → /portal/daemon/extract  200 → 寫 kbdb  401
  curl -X POST https://arcrun-kbdb.youlin-hsieh-dev.workers.dev/entries(無 token)→ 401
真兇:workflows/rag-ingest-card.local.yaml 打 __KBDB_BASE__/entries
**完全沒帶 Authorization**,但 kbdb 要 Bearer KBDB_INTERNAL_TOKEN。
⚠️ 與今天修的 t189 不同:那是 extract 端點入口(現已 200),這是 workflow 內部寫 kbdb。

修法命中 D36 金鑰鐵律(「只能拿 key,送出時自動拉 value」)
⇒ 應寫 {{credential.kbdb_internal_token}} 由 resolve_credentials 回填,
   而非讓安裝器 sed 把 token 值塞進定義。**已停下等 leo 裁**。

## CP 對帳
「丟檔→產卡」=  斷(卡產不出來)。本次只修好「燈號誠實」與「可診斷」,
**未送達用戶**(daemon 新版尚未出貨、401 未修)。
This commit is contained in:
2026-08-05 11:45:07 +08:00
parent 5860ef72df
commit d787bfc47d
3 changed files with 89 additions and 6 deletions
+18 -3
View File
@@ -221,10 +221,25 @@ func loadSyncStatus() syncStatus {
}
// describeStatus 產生狀態文案。
// 🔴 issue #17:正在跑就要**看得出來在跑**——按「立刻同步」會寫 sync-now 訊號檔,
// collector 開工時才把它吃掉;訊號檔還在=已排隊未完成 ⇒ 顯示「同步中…」。
// 🔴 t195leo 08-05 實撞:「燈號是真的還是假的?」——**是假的**):
//
// 舊版只看「sync-now 訊號檔存不存在」就顯示「同步中…」。
// 但那只代表**排隊了**,不代表有人在處理:leo 的 collector 在 11:25 死掉,
// 他 11:38 按同步 ⇒ 訊號檔沒人消化 ⇒ 畫面一直說「正在整理知識卡」,
// 實際上 13 分鐘沒跑過任何一輪、也不會產卡。**這比沒有燈號更糟——它在說謊。**
//
// ⇒ 燈號改成有憑有據:
// · collector 沒在跑 → 明說「同步引擎沒有在跑」,不要假裝在整理
// · 有訊號檔且引擎活著 → 才是真的「同步中」
// · 其餘 → 看守中
func describeStatus(s syncStatus) (syncing bool, big, sub string) {
if _, err := os.Stat(syncNowSignal()); err == nil {
alive := collectorAlive()
_, queued := os.Stat(syncNowSignal())
if !alive {
return false, "同步引擎沒有在跑", "資料夾不會被自動整理 ⇒ 請結束 Arcrun 再重新開啟"
}
if queued == nil {
return true, "同步中… 正在讀檔並整理成知識卡", "請稍候,完成後會顯示整理了幾份"
}
if s.ExtractorError != "" && !s.ExtractorOK {
+33
View File
@@ -0,0 +1,33 @@
#!/usr/bin/env bash
# build-dmg.sh — 打包成 Mac 標準安裝檔(t194,leo:「你應該包成 Mac 安裝檔,我完整做一次」)
#
# DMG 裡放 Arcrun.app Applications 捷徑 ⇒ 開啟就是「把 App 拖進去」的標準畫面。
# 為什麼一定要:使用者若直接在下載資料夾裡跑,自更新會蓋錯位置(t184 Oscar 的病);
# 而且 macOS 對「不在 /Applications 的常駐程式」權限行為也不一致。
set -euo pipefail
cd "$(dirname "$0")"
VERSION="${VERSION:-$(git describe --tags --always --dirty 2>/dev/null || echo dev)}"
echo "① 先確保 .app 是最新的"
VERSION="$VERSION" bash build-mac.sh >/dev/null
APP="build/bin/Arcrun.app"
[ -d "$APP" ] || { echo "❌ 找不到 $APP" >&2; exit 1; }
OUT="dist"; DMG="$OUT/Arcrun-${VERSION}.dmg"
STAGE="$(mktemp -d)/Arcrun"
mkdir -p "$OUT" "$STAGE"
echo "② 放 app Applications 捷徑"
ditto "$APP" "$STAGE/Arcrun.app"
ln -s /Applications "$STAGE/Applications"
echo "③ 產 DMG-fs HFS+:否則含空格/中文的名稱會被截斷)"
rm -f "$DMG"
hdiutil create -volname "Arcrun" -srcfolder "$STAGE" -fs HFS+ -ov -format UDZO "$DMG" >/dev/null
rm -rf "$(dirname "$STAGE")"
SIZE=$(ls -lh "$DMG" | awk '{print $5}')
echo "✅ 完成:${DMG}${SIZE}"
echo
echo "🔬 使用者流程:開 DMG → 把 Arcrun 拖到 Applications → 從啟動台開啟"
+38 -3
View File
@@ -9,6 +9,7 @@ package main
// 邊界:**複用既有的 supervisor 套件**(重起退避、狀態機、round 解析都在裡面,
// 含 t191 的 phase:start/done ⇒ 「同步中…」)。這裡只做「找到執行檔、拉起來」。
import (
"fmt"
"os"
"os/exec"
"os/signal"
@@ -43,10 +44,34 @@ func collectorBinPath() string {
// startSupervisor 在 App 啟動時拉起 collector;沒有 config 就先不啟動
// (使用者還沒連知識庫,啟動只會一直失敗刷 log)。
func startSupervisor() {
sup = supervisor.New(collectorBinPath(), configPath())
if _, err := os.Stat(configPath()); err == nil {
sup.Start()
bin := collectorBinPath()
cfg := configPath()
// 🔴 t195:這裡以前只 Stat(config) 就靜默 return,出問題完全查不到。
// leo 實撞:App 活著、collector 從沒啟動、畫面卻顯示「正在整理知識卡」。
// ⇒ 每個 return 點都要留下痕跡(寫進 collector.log 旁邊的 app.log)。
if _, err := os.Stat(bin); err != nil {
appLog("啟動同步引擎失敗:找不到 %s(%v)", bin, err)
return
}
if _, err := os.Stat(cfg); err != nil {
appLog("尚未連上知識庫(沒有 %s),同步引擎先不啟動", cfg)
return
}
sup = supervisor.New(bin, cfg)
sup.Start()
appLog("同步引擎已啟動:%s", bin)
}
// appLog 把 App 層的關鍵事件寫進 ~/.arcrun-rag/app.log。
// 沒有這個,「為什麼沒同步」就只能用猜的(leo 這次就卡在這)。
func appLog(format string, args ...any) {
f, err := os.OpenFile(filepath.Join(appDir(), "app.log"),
os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
if err != nil {
return
}
defer f.Close()
fmt.Fprintf(f, "%s %s\n", time.Now().Format(time.RFC3339), fmt.Sprintf(format, args...))
}
// restartWatch 在設定變更後重起看守,讓新設定立刻生效。
@@ -109,3 +134,13 @@ func processRunning(bin string) bool {
out, _ := exec.Command("pgrep", "-f", bin).Output()
return len(strings.TrimSpace(string(out))) > 0
}
// collectorAlive 回報「同步引擎是不是真的在跑」。
// 🔴 t195:燈號不可以只憑「訊號檔存在」就說在同步——leo 實撞過
// collector 已死、畫面卻一直顯示「正在整理知識卡」13 分鐘。
func collectorAlive() bool {
if sup == nil {
return false
}
return processRunning(collectorBinPath())
}