fix(t108 🔴🔴): 逐帳號同步遺失萃取設定→原文直送——三層修+契約保險絲

事故(leo 機 07-28 17:3x 總管現場抓到):t104 config 重寫吃掉機器層 extractor/key、
per-account DirectConfig 不繼承 ⇒ extractor 空 ⇒ 6 筆走 rag_ingest_direct 原文出機。
修:①config 讀改寫全程保留既有欄位(帶測試)②帳號層無值垂直繼承機器層
extractor/claude_bin/gemini_api_key/llm_model/CardIngestWF/RemovedWF(帶 fake server 測試:
必須收到 rag_ingest_card 非 direct)③契約保險絲:extractor 空時非 .md/.txt 禁直送、
標 failed「萃取器未設定,已跳過(不直送原文)」——同類 bug 永不再成外洩。
三模組 go test 全綠(總管親跑)。(實作=子 CC;驗證+commit=總管)
This commit is contained in:
2026-07-28 17:59:42 +08:00
parent bf1f0d9991
commit 1738acaca7
4 changed files with 176 additions and 7 deletions
+10 -7
View File
@@ -83,13 +83,16 @@ type directConfig struct {
Email string `json:"email,omitempty"`
InstanceName string `json:"instance_name,omitempty"`
Library string `json:"library,omitempty"`
Extractor string `json:"extractor,omitempty"` // 機器層級:claude/gemma
ClaudeBin string `json:"claude_bin,omitempty"` // t92collector fallback 找到後回寫
Libraries map[string]string `json:"libraries,omitempty"` // t52 舊制:資料夾→庫對映
IngestWF string `json:"ingest_workflow,omitempty"`
RemovedWF string `json:"removed_workflow,omitempty"`
PollSec int `json:"poll_interval_sec,omitempty"`
MaxRemoved float64 `json:"max_removed_ratio,omitempty"`
Extractor string `json:"extractor,omitempty"` // 機器層級:claude/gemma
ClaudeBin string `json:"claude_bin,omitempty"` // t92collector fallback 找到後回寫
GeminiAPIKey string `json:"gemini_api_key,omitempty"` // t108gemma 路 key
LLMModel string `json:"llm_model,omitempty"` // gemma 路模型
CardIngestWF string `json:"card_ingest_workflow,omitempty"` // 收卡 workflow
Libraries map[string]string `json:"libraries,omitempty"` // t52 舊制:資料夾→庫對映
IngestWF string `json:"ingest_workflow,omitempty"`
RemovedWF string `json:"removed_workflow,omitempty"`
PollSec int `json:"poll_interval_sec,omitempty"`
MaxRemoved float64 `json:"max_removed_ratio,omitempty"`
}
// ── t91 萃取狀態可見性 ──────────────────────────────────────────────────────────
+62
View File
@@ -441,3 +441,65 @@ func TestDirectConfigFolders_MultiAccount(t *testing.T) {
}
}
}
// t108directConfig JSON round-trip 必須保留 GeminiAPIKey/LLMModel/CardIngestWF——
// 這三欄是 t104 前缺失的;若托盤 loadConfig→saveConfig 吃掉它們,collector 讀到空值就走直送路。
func TestDirectConfigGeminiFieldsPreserved(t *testing.T) {
original := &directConfig{
Extractor: "gemma",
GeminiAPIKey: "AIzaSy-test-key",
LLMModel: "gemma-4-31b-it",
CardIngestWF: "rag_ingest_card",
}
data, err := json.Marshal(original)
if err != nil {
t.Fatalf("marshal 失敗:%v", err)
}
var back directConfig
if err := json.Unmarshal(data, &back); err != nil {
t.Fatalf("unmarshal 失敗:%v", err)
}
if back.GeminiAPIKey != original.GeminiAPIKey {
t.Errorf("GeminiAPIKey 消失:got %q", back.GeminiAPIKey)
}
if back.LLMModel != original.LLMModel {
t.Errorf("LLMModel 消失:got %q", back.LLMModel)
}
if back.CardIngestWF != original.CardIngestWF {
t.Errorf("CardIngestWF 消失:got %q", back.CardIngestWF)
}
}
// t108addOrUpdateAccount 後機器層 GeminiAPIKey 仍存在(遷移/新增帳號不吃掉機器層欄位)。
func TestAddOrUpdateAccountPreservesMachineLayer(t *testing.T) {
cfg := &directConfig{
Extractor: "gemma",
GeminiAPIKey: "AIzaSy-existing-key",
LLMModel: "gemma-4-31b-it",
CardIngestWF: "rag_ingest_card",
}
resp := &daemonConfigResp{
Success: true,
Config: struct {
CypherURL string `json:"cypher_url"`
Namespace string `json:"namespace"`
Library string `json:"library"`
Extractor string `json:"extractor"`
Email string `json:"email"`
InstanceName string `json:"instance_name"`
}{
CypherURL: "https://new.workers.dev",
Namespace: "newns",
},
}
addOrUpdateAccount(cfg, resp)
if cfg.GeminiAPIKey != "AIzaSy-existing-key" {
t.Errorf("GeminiAPIKey 被清空:got %q", cfg.GeminiAPIKey)
}
if cfg.LLMModel != "gemma-4-31b-it" {
t.Errorf("LLMModel 被清空:got %q", cfg.LLMModel)
}
if cfg.CardIngestWF != "rag_ingest_card" {
t.Errorf("CardIngestWF 被清空:got %q", cfg.CardIngestWF)
}
}