uncle6me-web
576afa2364
fix(wiki): 修一條四項的「三元組」——述詞裡不能再有 >>
...
範例產生器把述詞寫成「實作 >> 結構性閘」,產出
「零件 PR 審核閘 >> 實作 >> 結構性閘 >> [[規則記不住]]」四項,不是三元組。
InkStoneCo 的 wiki-lint 已加第十六項檢查(一行 >> 超過兩個就報)。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
2026-08-15 15:19:16 +08:00
uncle6me-web
730c2af245
merge: Arcrun#128 外鍵接上去——slot 可以指向既有 entry,不再複製
...
總管逐筆審過三筆,並自己跑過測試(不是採信交件):
· kbdb 218 → 232 綠(新增 14 支),零回歸
· cypher-executor 445/459;那 14 支紅在 gitea/main 上就是紅的(main 439/453)
⇒ 零回歸。順帶記下:那套測試已經不是閘了,另案
三筆各是什麼:
5b22d56 createRecord 新增 entry_ids 通道,與 values 並存(舊呼叫端一字不改)
aa49b4e 刪 record 不再刪掉別人還指著的 entry —— **我的派工單沒提,它自己想到的必然配套**
9ba6ba7 cypher-executor 的 POST /kbdb/records 開通同一條通道
它自己補了兩件我沒指定的:
① 🔴 租戶邊界——這條新路徑讓呼叫端可以自己指定 entry_id,不檢查歸屬的話
任何人都能把別人的 entry 掛進自己的 record 再讀回內容 ⇒ 繞過租戶邊界的門
(它引 .claude/rules/02-forbidden.md §6.1 當依據)
② 對照組測試——「同樣五個 slot 給字串 → entries 增加五筆」
只證修法有效沒人知道原本壞在哪;它同時證明了問題存在
驗收五條全部有對應測試:水池列數不增加/一條 entry 被多筆 record 多個 slot 指到/
改內容兩邊都變(副本做不到)/舊呼叫端行為不變/指不到的外鍵當場報錯且一列不寫。
未做(刻意):.worker-builds/ 未重編 ⇒ 修法只在源碼,還沒進任何執行檔。
leo 2026-08-15 裁定先不出貨,重編屬於出貨動作。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
2026-08-15 14:56:42 +08:00
uncle6me-web
9ba6ba75fc
feat(cypher): POST /kbdb/records 開通 entry_ids 通道(Arcrun#128 的對外門)
...
基本盤補好而通道不開=能力在、沒人打得到——同 b6ef0f0 那次 PATCH 的形狀。
#129(wiki template)與 #130(三元組正規化)的寫入端走的就是這扇門。
改了兩件事(維持純轉發,判斷全在基本盤,薄殼鐵律見檔頭):
· 舊版寫死 `!body.values → 400`:只給 entry_ids 的合法請求會被自己的 proxy 擋掉
⇒ 改成「values 與 entry_ids 至少要有一個」
· 轉發 body 帶上 entry_ids;沒給的那個 key 就不塞(不憑空給基本盤一個空物件)
租戶隔離沿用本檔既有做法:**忽略 caller 自帶的 owner_id,一律注入 header 身份**。
配合基本盤這次加的檢查(被參照 entry 的 owner_id 必須與注入的租戶相符),
「呼叫端可以自己指定 entry_id」這條新路徑不會變成跨租戶的門。
驗(tests/kbdb-records-entry-ids-proxy.test.ts,6 條,fetchMock 假 host+斷網)
· 無 X-Arcrun-API-Key → 401 不碰 KBDB
· 只給 entry_ids → 轉發成功,body 為 {template, entry_ids, owner_id:'leo'}(無 values key),
且 caller 自帶的 owner_id:'alice' 被忽略
· values 與 entry_ids 混用 → 兩個都轉過去
· 舊呼叫端只給 values → 轉發 body 與過去逐字相同,不夾帶 entry_ids
· 兩個都沒給 → 400 不轉發;base 擋跨租戶(400)→ 原樣透傳不假裝成功
cypher-executor 全套 445 綠 / 14 紅——那 14 條是**既有**紅燈
(auth-dispatcher/console-library-map-page/executor/portal-admin/portal-data),
已用 `git stash` 拿掉本次改動複跑同五個檔複驗:同樣 14 紅,與本次無關。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
2026-08-15 14:48:01 +08:00
uncle6me-web
aa49b4eeb2
fix(kbdb): 刪 record 不再刪掉別人還指著的 entry(Arcrun#128 的必然配套)
...
上一筆讓 slot 可以指向既有 entry 之後,同一條 entry 會同時被別的 record 指著。
deleteRecord 舊寫法是「這筆 record 的每個 entry_id 都刪掉」,在共用的情況下會:
· 把**別人還在用**的那條水池資料一起刪掉(他的 slot 從此指向不存在的列),或
· 撞上 `entry_values.entry_id REFERENCES entries(id)` 的 FK 而整個刪除失敗
⇒ 條件改成「已經沒有任何 entry_values 指著它」才刪
(`DELETE FROM entries WHERE id = ? AND NOT EXISTS (SELECT 1 FROM entry_values WHERE entry_id = ?)`)
**沒有共用時行為與舊版完全相同**:這筆 record 的關聯列已先刪掉,若沒有別人指著,
NOT EXISTS 恆為真 ⇒ 照樣刪。差別只出現在真的被共用的那一條上。
驗(同檔 +2 條,真 SQLite 且 `PRAGMA foreign_keys = ON`)
· 兩筆 record 共用一段、刪掉其中一筆:entries 2 → 1(只少掉那筆自己專屬的 title),
共用那條還在,另一筆 record 讀回來的 slot 值不受影響
· 沒有共用時:3 個 slot 的 entry 全刪、entry_values 歸零、getRecord 回 null(同舊版)
kbdb 全套 232 綠
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
2026-08-15 14:44:32 +08:00
uncle6me-web
5b22d569c9
fix(kbdb): createRecord 的 slot 可以指向既有 entry——外鍵不再被實作成複製(Arcrun#128)
...
leo 2026-08-15 的心智模型:「blocks 是一個大水池,template/slots 組成虛擬表和
fields,最後都指向水池的一條 entry⋯⋯連到三元組就是外鍵」。
而 `entry_values` 的約束只有 `UNIQUE(record_id, slot_name)`、`entry_id` 上沒有任何
unique ⇒ 一條 entry 本來就能被無限多筆 record 的無限多個 slot 參照,
**儲存層早就是外鍵語意,壞的只有寫入路徑**:createRecord 對每個 slot 值都無條件
createEntry ⇒ 每設一次外鍵就把被參照的資料複製一份。那不是外鍵,那是複製。
為什麼是地基而不是省空間:複製讓資料量隨「有幾個 App 參照它」線性膨脹,而且兩份
從此各自漂移 ⇒ 遲早要有人來清 ⇒ 直接違背這個模型的產品承諾「加一個 App 只要建一份
template,不必遷移、不需要工程師」(llm-wiki-schema.md)。#129(wiki template)、
#130(三元組正規化)、#60(alias 餵了沒用)三票都卡在它後面。
做了什麼(不動 schema、不動舊資料、不加表)
· CreateRecordInput 多一個 entry_ids:{slot: 既有 entry id},與 values 並存
—— 給字串照舊新建(舊呼叫端一個字不用改),給 id 就只插一列 entry_values
· POST /records 接受只給 entry_ids(舊版這裡回 400),並驗兩個 map 的型別
· 寫入前先把被參照的 entry 讀出來,一次擋掉三種錯,且**檢查全在第一筆 INSERT 之前**
⇒ 失敗=一列都沒寫(base 沒有交易,這是唯一保證得了的原子性)
① id 不存在(FK 只會回一句 SQLITE_CONSTRAINT,說不出是哪個 slot)
② 🔴 跨租戶:新路徑讓呼叫端能自己指定 entry_id,不擋就等於開一扇
「把別人的 entry 掛進自己的 record 再讀回內容」的門(rules 02 §6.1 同精神)
③ 指到 template 沒有的 slot → 報錯,不學 values 那條靜默略過
(外鍵無聲消失是最難查的失敗:呼叫端以為建好了,template= 查詢卻永遠撈不到)
驗(tests/record-entry-ref.test.ts,17 條,真 SQLite 套 0001_base.sql 原檔——
「列數變不變」是 capture-DB 驗不到的東西,必須有真的表在數)
· 五個 slot 全指既有 entry:entries 5 → 5(差 0),entry_values 0 → 5
對照組同樣五段給字串:entries 5 → 10(差 5)=原本的複製行為
· 一條 entry 同時被兩筆 record、且被同一筆 record 的兩個 slot 指到 → 讀回都正確
· 改水池那一份 → 兩筆 record 都看到新內容(副本做不到,證明真的是同一條)
· searchByTemplate('wiki','leo') 撈得到,五個 slot 值正確
· 舊路徑逐項不變:3 筆新 entry、entry_type='value'、owner_id 帶歸屬、
template 沒有的 slot 照舊只 echo 不存;POST 舊 body 仍 200
kbdb 全套 215 → 230 綠(新增 15 條,既有一條都沒動)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
2026-08-15 14:43:42 +08:00
uncle6me-web
30c9312c57
wiki: 拿真實的 docs/ 套新規範跑一次(27 張卡,兩個節點)
...
leo 2026-08-15:「我是說不是要用真實的資料夾套新規範做一次?」
對 matrix/arcrun/docs 的兩個節點做了真的萃取(不是模擬):
· docs/ 5 篇原稿 → 5 文件卡 + 9 概念卡 + 1 主題(結構性閘)
· user_requirements/ 4 篇 → 2 文件卡 + 7 概念卡 + 1 主題(讓生態自己長)
踩到並如實處理的四個邊緣案例:
① arcrun-landing-page/ 只有 .jsx/.html/.css ⇒ 依規範不是節點,不建 .wiki(index 說明有寫)
② credential_parts.md 29KB、12 個 H2,超過 20KB 分段門檻
⇒ 只萃 §0–§2,其餘八段在文件卡上如實標明未做——一張卡蓋不住 29KB
③ wishlist.md 是「空」的第三種:指針檔(不是沒東西、不是紀錄)
⇒ 標 no_concept 不產卡。我第一版給它產了卡,wiki-lint 當場報孤島卡抓到
④ 三個子節點未萃,在 index 標「未萃」——標出來使用者才分得出「沒東西」和「還沒做」
並依 leo 的判準拿掉 type 欄位:
「在這個類似 Logseq 的結構裡根本不分,你要想的是在寫函式時是否不同」
逐一檢查 place_card/patch_card/promote_hub/split_hub/渲染/三個信號/lint
——沒有任何函式需要類型欄位,需要的全是結構屬性(有無父/有無子/有無判斷),
而那三個都是現算的。⇒ type: hub 與 hub_kind 拿掉,wiki-lint 改成用算的。
promote_hub 因此不是改型別,是在有子的卡上補一段判斷,型別自己就變了。
實測:docs 樹 節點 2|卡片 27|卡外邊 62,十五項全 0,退出碼 0
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
2026-08-15 13:47:27 +08:00
uncle6me-web
1616517ace
rules: 第一鐵律從「用 grep」改成「照索引走,grep 是異常訊號」(leo 2026-08-15)
...
leo 當面點破:規則寫的是平面搜尋,而設計寫的是索引鏈(INDEX → cards/<bucket>/00-INDEX
→ 卡),兩者矛盾,AI 服從了寫得比較具體的那一份。
實測本 repo:system-dev/wiki/cards/ 有 64 張卡,只有 1 張走得到索引——
那 1 張是 leo 手寫的 decisions/,其餘 63 張機器產的從沒加入任何索引。
而沒有人發現,因為 grep 找得到 ⇒ 破損永遠不會浮現。
不全面禁止 grep(索引壞掉那天 AI 會瞎掉且沒人知道),改成:
先照索引走 → 走不到就先把缺陷講出來 → 才准 fallback。
每次 grep 都該留下一筆索引缺陷,而不是變成習慣。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
2026-08-15 11:01:39 +08:00
uncle6me-web
7dc3e615e9
merge: 內部 uninstaller 進 main(Arcrun#119/#120/#122)
...
它已經在 youlin 上真的用過,卻只活在分支上——8/14 就因此被總管誤判成「不存在」。
留在分支=下一次還會再消失一次。
內容:scripts/teardown-instance.mjs(535 行,唯一新增檔)
· plan/apply --yes 兩段式
· leo21c 在 deny-list
· central KV 操作前機械核對 wrangler 身分(D88)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
2026-08-15 01:37:40 +08:00
uncle6me-web
ccd531863a
merge: 公庫 5 顆 → 23 顆,Arcrun 統一編所有零件(D91/D92/Arcrun#125)
...
總管複驗才併(第二次審——第一次因基底過舊被攔下):
· cypher sha = 1da6a46e6a17 ← 含 Arcrun#124 修法那顆(第一版帶的是修法前的 8e648747)
· 23 顆逐位元等於 Arcrun 官方成品;五顆既有引擎逐顆對得上 main 的 committed 值
· repo_dirty false、repo_head 45169ab
· excluded 有寫明理由(arcrun-claude-api:wasm 不在版控=不是可出貨零件),不是安靜消失
· auth_static_key 進首裝,靠 {{credential.X}} 隱含依賴抓到
🔴 第一版被攔下的教訓(值得留):它的 commit 訊息寫「位元組完全重現 committed 值」——
那句屬實,但重現的是**舊的** committed 值。
⇒「與版控一致」在基底過舊時,正好是最像正確的錯誤。
環境驗證(我算得穩不穩)≠ 內容驗證(我帶的是不是最新那顆)。
⚠️ 未驗最後一哩:全新安裝實測要 leo 按 CF 授權。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
2026-08-15 01:04:35 +08:00
uncle6me-web
537b520143
build(worker-artifacts): 公庫從 5 顆變 23 顆(基底重釘到含 Arcrun#124 修法的 main)
...
由乾淨工作區重編(repo_head 45169ab、dirty=false)。
🔴 這一版是**重編**不是重併:前一版是從 94d5452(#124 修法併進 main 之前)長出來的,
直接併會把 .worker-builds 的 cypher **換回沒有修法的那顆**(8e648747),
而且是安靜發生的——manifest 從 5 顆變 23 顆看起來全是進步,沒有人會去看那顆 sha 退回去。
複驗(五顆既有引擎逐顆比對 main 的 committed 值,全中):
cypher-executor 1da6a46e6a17 ← **含 #124 修法**(source 340b461f)
kbdb f15e16cca6ce
code 751634a3fc9a
mcp 9bc8dffb34d0
http-request cdd97364f277
新增 18 顆零件 worker + arcrun-rag-ui;claude_api 依 .gitignore 判定不出貨,
落在 manifest.excluded。成品裡沒有任何本機路徑洩漏。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
2026-08-15 01:00:45 +08:00
uncle6me-web
45169ab25e
feat(build): 一個實例會用到的每一顆 worker 都在 Arcrun 編(D91/Arcrun#125)
...
官方編譯點從 5 顆擴到 23 顆,並把 portal 前端的產生器從 arcrun-rag 搬回來。
為什麼不是「順手多編幾顆」:
- 只編 5 顆 ⇒ 下游 bundle 裡就只有 5 顆 ⇒「零件用到才下載」沒有貨可以下載。
而 auth_static_key 是每一支產品工作流解 {{credential.X}} 都會打的那一顆
⇒ 走網頁安裝器裝出來的實例,工作流一跑就 500(Arcrun#124 的 error code: 1042)。
- portal 前端(arcrun-rag-ui)當時仍由 arcrun-rag 自己拼裝原始碼
⇒ 使用者拿到的那顆 worker,沒有任何 Arcrun commit 說得出它的來源。
leo:「今天開始出貨一律不准在 arcrun rag 或任何別的地方 build,這就是 arcrun 的專屬工作。」
做法:
- 零件 worker 改用**掃描** .component-builds/(不是手寫清單——手寫清單就是
arcrun-rag#27/D48 那個病的形狀),worker 名一律讀該零件自己的 wrangler.toml。
- 可出貨的判準=**部署位元組在版控裡**。.gitignore 明文排除的三顆
(claude_api/km_writer/kbdb_upsert_block,DECISIONS §1「錯做成零件」)
因此自動落在公庫外,而且是**寫進 manifest.excluded 的**,不是安靜跳過。
- 輸出目錄改成每次只留這一輪該有的,清掉舊成品(拔掉棘輪)。
- UI 產生器原樣搬過來,兩道閘(世代閘、前端 JS 語法閘)一起搬——
它們驗的是本 repo 的 console-ui/public,本來就該長在來源這一側。
實測(本機 worktree,依 lockfile 重裝依賴後):
- 23/23 編成功;四顆既有引擎成品**位元組完全重現** committed 值
(cypher 8e6487478bc8/kbdb f15e16cca6ce/code 751634a3fc9a/mcp 9bc8dffb34d0/
http-request cdd97364f277)⇒ 這次擴充沒有動到既有零件。
- arcrun-rag-ui 的產出與搬家前逐位元比對,**只差第一行註解**
(那行必須改:它原本指向一支不再產生它的腳本)。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
2026-08-15 00:59:59 +08:00
uncle6me-web
5dd1d1e7a9
merge: credential 全取到時不再打 auth_static_key(Arcrun#124)——Mira 恢復可用
...
總管獨立複驗才併(不是採信自述):
· youlin 實測,用戶真正會走的那條路:
改前 GET /q/yuga3bse/rag_chat → 500 credential resolve 回傳 404: error code: 1042
改後 GET /q/yuga3bse/rag_chat → success: true
/kbdb/map libraries 0 → 1(testkb,entry_count 4、triplet_count 1)
· 部署前後 binding 21→21,消失/多出/值變 各 0;secret 都在
· 修法保留 WASM 路徑(任一 name 沒取到就照舊走 T7 雙讀)⇒ 不縮減既有能力
· 註解明確警告「這個 1042 跟前兩輪的 1042 不是同一個病」——同碼兩因,值得留
根因:auth_static_key 不在首裝清單,而 cypher 遇 {{credential.X}} 就無條件 fetch 它。
影響:凡走網頁安裝器裝出來的實例,ingest 與查詢全死(youlin/leo21c 實測同死,
geek6688 因手工維護零件齊全而測不出來)。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
# Conflicts:
# .worker-builds/manifest.json
2026-08-15 00:06:51 +08:00
uncle6me-web
b8a5201e8a
build(worker-artifacts): 重打 cypher-executor 成品(含 Arcrun#124 修法)
...
sha256 8e6487478bc8 → 1da6a46e6a17(工作區乾淨時重編,repo_head 標記可信)。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
2026-08-14 23:26:28 +08:00
uncle6me-web
340b461f58
fix(cypher): credential 全部從新家取到時不再打 auth_static_key worker(Arcrun#124)
...
病:cypher 看到 {{credential.X}} 就無條件 fetch
arcrun-auth-static-key.{WORKER_SUBDOMAIN}.workers.dev。那顆 worker
**不在網頁安裝器的首裝清單裡**(只裝 cypher-executor / kbdb / http_request / code
四顆),也沒有「用到才長」的機制 ⇒ fetch 撞 CF 邊緣 404 `error code: 1042`
⇒ 凡是用到 {{credential.X}} 的節點全部 500。
實測兩台都一字不差地同死(唯讀證據):
leo21c GET /q/bfezv28v/rag_chat → 500 Node kw_search failed: ... 1042
youlin GET /q/yuga3bse/rag_chat → 500 Node kw_search failed: ... 1042
geek6688(手工維護、那顆在) → 過得了這關
leo 的 8 支 RAG 工作流每一支都用 {{credential.}}(rag_chat 6 處、
rag_ingest_card 7 處)⇒ 問答與寫入同時死,不只 ingest。
修法:resolveCredentialRefs 在 resolveSecretsFromNewHome 之後,若 nameList
每一個都已取到明文,就直接 replaceCredentialRefs 回傳,不打那顆 worker。
為什麼是零行為變化:回填本來就在本檔做(replaceCredentialRefs);那顆 worker
在這條路徑上唯一多做的事是幫「沒取到」的 name fallback 舊 KV(T7 雙讀)。
全取到時它收到 resolved_secrets 就是原樣回傳。只要有任一 name 沒取到,
仍照舊全量交給 WASM 走 T7——不縮減既有能力,只在等價時省掉那一跳。
證據那把金鑰早就在手上:leo21c 的 credential 目錄 last_used_at=1786715805,
與最後一次失敗執行的時間戳同一秒(touchLastUsed 只在真的從新家取到值時才寫)。
🔴 這個 1042 與 d2048e2/95a1462 那兩輪不是同一個病:那兩輪是 same-zone fetch
被擋(解=global_fetch_strictly_public flag,service binding 的解法已被 revert
不要拿回來);本案是目標 worker 根本不存在。同一個錯誤碼、兩種原因。
邊界:rule 02 §2.2 不變(不解密/不展開模板/不組 JWT);rule 3.1 不變(不加
service binding)。明文來源仍是 secret_get host function,與改動前同一條路。
驗證:tsc 對本檔零錯誤;auth-dispatcher+credentials 測試 5 紅 8 綠,
與改動前基準線逐項相同(零回歸;那 5 紅是 7ba7855 註明的「刻意留紅燈」)。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
2026-08-14 23:25:26 +08:00
uncle6me-web
5318398849
merge: 成品 manifest 的 metadata 修正(repo_dirty:true→false)
...
只改 .worker-builds/manifest.json 的 metadata,五顆 worker 內容位元組完全沒變
(總管逐行看過 diff:generated_at/repo_head/repo_dirty/source_commit 四個欄位)。
為什麼要併:不併的話 main 上的成品清單一直記著 repo_dirty:true,
下次任何人從 main 出貨都會被 build-bundles 的新鮮度閘擋下。
⚠️ 併了之後 repo_head 仍記著 94d5452(前一版 main),出 prod 前 ship.mjs
的 build 站會重編一次——這一顆解的是「髒」,不是「舊」。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
2026-08-14 20:18:33 +08:00
uncle6me-web
9609924818
提案補勾連:辨識碼的「更新者免碼」是靠那張紙,方向與直覺相反
...
不是用辨識碼找那張紙,是用那張紙免掉辨識碼(t154)。
紙搬到帳號上後,免碼判準自然變成去帳號看一眼,並讓 #120 情境變誠實:
帳號清空=本來就是新裝,請填辨識碼是對的答案,不是把人關在門外。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
2026-08-14 20:08:29 +08:00
uncle6me-web
043e9acc31
chore(builds): 重編 tier2 成品 manifest——repo_dirty:true 修正為乾淨 HEAD(94d5452)
...
出貨 D87(Arcrun#119)到 stage 時卡在 build-bundles.mjs 的落後閘:main 上現有的
.worker-builds/manifest.json 記著 repo_head=8286c8a(4b6cc15 那個 commit 的父提交)
且 repo_dirty:true——是那次 commit 在完成前先跑了建置腳本,manifest 跟著沒完成的
工作樹一起被 commit 進去。
在乾淨的 main HEAD(94d5452)重跑 scripts/build-worker-artifacts.mjs:五顆
worker 內容位元組完全相同(cypher-executor sha256 8e6487478bc8... 不變,
證明前一份的實際內容其實是對的,只是 metadata 記錯),這次只更新
generated_at/repo_head/repo_dirty/source_commit 四個欄位為正確值。
2026-08-14 20:06:35 +08:00
uncle6me-web
cfb759a774
提案收斂:那張紙搬到用戶自己的帳號上,中心不再保管(leo 2026-08-14 第三次)
...
比「降級」更徹底——紙跟東西在同一個地方,一起消失
⇒ #120 不是被修好,是沒有地方可以發生。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
2026-08-14 20:05:29 +08:00
uncle6me-web
73bdb90780
pending-changes:安裝器狀態判斷改看帳號、中心帳本降級(leo 2026-08-14 的檢修孔比喻)
...
同一天三次撞牆同一形狀:帳本與現場分岔時,安裝器相信帳本。
鐵律:帳本永遠不得產生停手;不合時以現場為準並順手改對帳本。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
2026-08-14 20:03:05 +08:00
uncle6me-web
9ad95ee3a6
merge: 上次裝到一半死掉的帳號要能再裝一次(Arcrun#123)+清單分頁
...
總管逐筆審過才併:
· 接管同名資源的唯一依據是呼叫端聲明 createNameIsOurs,預設 false(fail-closed)
· 安裝器有資格聲明——複驗過推導:baseName=arcrun-rag-<SHA256('arcrun-rag:'+email) 前8碼>
⇒ 同一 email 每次算出同一組名字,別人算不到
· acr CLI 那條路沒有聲明(createName 是裸 binding 名,證明不了是誰的)——正確
· 2b(已部署 worker 的綁定優先)沒被放寬,#97 的保護還在
· 分頁修法同批:三支清單端點形狀不同(D1 沒有 total_pages),終止條件刻意只用
result_info 在不在+total_count 對不對得上;看不完整就 throw ⇒ 停手,
「我不知道」不准被當成「它沒有」
真帳號實測(geek6688):正向接得回、反向 fail-closed 擋得住、兩個時間窗都通。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
2026-08-14 19:58:02 +08:00
richblack
68f042cfd0
fix(resource-rule): 帳號上資源超過一頁時,規則看到的必須是全部(Arcrun#123 續集)
...
三支清單方法只打 `?per_page=100`,也就是**只看第一頁**。這個洞在 #123 的修法
前後嚴重度不同,這才是它必須跟那張票一起修的理由:
· 修法前:被截掉的是「worker 綁著的那顆」→ 2b 判「綁著的資源不見了」
→ blocker → 停手。誣告使用者,但安全。
· 修法後:被截掉的是「同名殘骸」→ 2c 判「這個名字沒被佔走」
→ 去建 → CF 回 title already exists → #123 的死路原樣回來。
⇒ 修法把它從「叫得太大聲」變成「安靜地復發」。分開出貨等於把 #123 的災情
延後到「資源比較多的帳號」再爆。
做法:`cfListAll()` 翻到底;翻不完、或數量對不上 CF 回報的 `total_count`,
一律 throw ⇒ 變 blocker ⇒ 整趟停手(README 規則第 3 條)。
「我不知道」不准被當成「它沒有」。
三支端點的分頁行為不一樣(2026-08-14 在 geek6688 帳號實測,唯讀):
/storage/kv/namespaces result_info 有 total_pages
/d1/database result_info **沒有** total_pages ⇒ 不能拿它當終止條件
/vectorize/v2/indexes result_info 是 null,不分頁(分頁參數被忽略)
所以終止條件只用「三支都有或都沒有」的兩件事:result_info 在不在、total_count 對不對得上。
fixture 的清單端點同步照真 CF 的形狀分頁(三支各自不同)——假資料失真就會養出
「拿 total_pages 當終止條件」這種在 D1 上必壞的實作,而測試全綠。
新增 tests/list-pagination.mjs(在舊碼上實測會紅,且第 ③ 段直接重現
「無 blocker → 排 10 顆新建 → CF 回 title already exists」的 #123 死路)。
cli 73 項全綠、demo 與 half-finished-install 全綠。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
2026-08-14 19:52:24 +08:00
uncle6me-web
5d441aa7d3
fix(teardown): 中心側補上帳號白名單——原本 central-* 碰得到 leo21c
...
實測破口(2026-08-14,修補前):
central-plan --account-id 51a01bfa…(leo21c)
→ 一路暢通,列出 deployed:51a01bfa…:leo21c
加 --yes 就會刪掉 leo 的安裝紀錄 ⇒ 他下次重裝撞 Arcrun#120 死結。
根因是兩個問題被當成同一個:
① 動的是「誰的」紀錄 → assertAccountAllowed(帳號側有,中心側沒有)
② 以「誰的身分」動手 → assertWranglerIsCentralAccount(D88 補的那道)
c543aba 的註解寫「這個不對稱現在補齊」,但只補了②。註解說補齊、程式補一半,
後來讀的人會相信註解——所以順手把那段註解改成實測講法。
另修 listAllScripts 沒翻頁(ops-facts 記過的同款坑)。在這支工具裡漏看一顆
worker 有兩層傷害:拆不乾淨,以及「共用資源保護」看不到那個 owner,
把還在用的資源判成沒人用而刪掉。
並補一條已知限制:資源清單是從 worker binding 反推、不掃帳號,
所以沒人綁的孤兒殘骸不會被列也不會被刪(這正是 drill-a/drill-b 不受影響的原因,
但代價是拆完重裝可能撞 Arcrun#123,要自己再列一次帳號)。
驗證(youlin,全唯讀,未執行 apply):
- plan:6 顆 worker + 10 個資源(8 顆 yuga3bse KV/yuga3bse-db/embed-m3),
drill-a、drill-b 不在清單
- leo21c 帳號側、中心側 兩條路徑皆 exit 1 拒絕
- youlin 中心側仍正常:staging 通道有 deployed:1129efd7…:youlin-hsieh-dev 一筆
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
2026-08-14 19:51:31 +08:00
claude-code
43323b1a78
test(resource-rule): 加一支「在真帳號上驗 #123」的腳本(離線測不出 CF 的拒絕)
...
離線 fixture 證明的是**判斷**對,證明不了「真的 Cloudflare 會不會照我們
以為的方式回應」——而 #123 的病根正是「我們以為 CF 會讓我們重建,
實際上它拒絕」。那種錯只有真帳號驗得出來。
用法:
CF_API_TOKEN=<token> CF_ACCOUNT_ID=<id> \
node shared/resource-rule/tests/verify-against-real-account.mjs
它對帳號做的事:
· 讀:列 KV/D1/Vectorize、讀 worker 綁定
· 寫:只建一顆名字帶 -zz123tst- 的一次性 KV,finally 保證刪掉
· 不碰任何既有資源(測試用的 worker script 名刻意不存在,
所以規則看到的是「沒有人綁著它」= #123 那條路)
安全開關:DRY_RUN=true 只盤點、KEEP=true 不清理(除錯用)
驗三件:① 接回殘骸且一顆都不新建 ② apply 前後帳號顆數不變
③ 沒聲明 createNameIsOurs 時 fail-closed 且訊息不叫人開 CF 後台
⚠️ 雲端 session 跑不了寫入那半(權限分類器擋 CF 寫入),
DRY_RUN 已對真 youlin 帳號試跑通過(讀到 8 顆 KV)。
完整那半要在有寫入權限的地方跑。
Refs: inkstone/Arcrun#123
2026-08-14 09:57:43 +00:00
claude-code
2b807da324
test(resource-rule): 半殘情境的 D1 名字改用安裝器真正會取的那個(-db,非 -kbdb)
...
上一筆 commit 之後才發現的:fixture 的 half-finished 情境沿用了
installed/renamed 的歷史 D1 名(-kbdb),但那顆殘骸是安裝器留下的,
安裝器真正會取的名字是 `${baseName}-db`。名字不對,這個情境就測不到
真的那條路(D1 會被判成「不同名 ⇒ 照舊新建」而不是接回來)。
把 d1Title 加進情境定義、installerRequirements 收一個 d1CreateName 參數,
half-finished 用 -db、其餘照舊 -kbdb。
實跑:node shared/resource-rule/tests/half-finished-install.mjs → 全部通過
Refs: inkstone/Arcrun#123
2026-08-14 08:18:46 +00:00
claude-code
3f2e45f5dc
fix(resource-rule): 上次裝到一半死掉的帳號要能再裝一次(Arcrun#123)
...
封測者 1.4.45 實撞:
a namespace with this account ID and title already exists
⇒ 那個帳號從此永遠裝不起來,而錯誤訊息對用戶完全無法行動。
根因:rule.mjs 第 2c 段只問「有沒有已部署的 worker 綁著它」,
不問「這個名字在帳號上是不是已經存在」。註解裡「本來就沒有東西可丟」
漏掉一種狀態——資源已建、worker 還沒部署就中斷(逾時/關掉分頁/斷網)。
拆除 youlin 時親眼看到的 8 顆空殼 KV 是同一個形狀。
修法:2c 在 create 之前先查同名。找到同名資源時:
· 呼叫端聲明了 createNameIsOurs → 接回那一顆(adopt + reclaimed 標記)
· 沒聲明 → 停手,訊息帶 RES-NAME-TAKEN 錯誤碼讓用戶回報
createNameIsOurs 是接管的唯一依據,預設 false(fail-closed)。只有名字
推導自使用者自己的身分時才准聲明——安裝器的
arcrun-rag-<slugFromEmail(email)>-kv-<binding> 合格;acr 從 toml 讀到的
裸 binding 名(WEBHOOKS)不合格,因為用戶自己也可能用那個名字。
這不是把 #97 刪掉的「照名字 ensure」搬回來:
① #97 找不到就新建一顆頂上去(會弄丟資料);這裡找到才沿用,
找不到照舊新建,永遠不拿新的空資源頂替既有的
② 排在「已部署綁定=事實」之後,名字只在沒有任何綁定可看時才有發言權
③ #97 無條件相信名字;這裡要呼叫端先證明名字推導自用戶身分
順手補上假帳號的保真度:FakeCloudflare.createKvNamespace 原本不擋同名,
所以半殘帳號在測試裡看起來只是「多幾顆孤兒」,實際上是裝不起來——
少了那一行,這個 bug 測不出來。fixture-account.mjs 也把
resourcesExist 與 deployed 拆開,才表達得出這個狀態。
驗證(皆為實跑):
node shared/resource-rule/tests/half-finished-install.mjs → 全部通過(零依賴)
cd cli && npm test → 73/73 pass
sync-resource-rule --check → 三份副本皆與原稿一致
⚠️ 只有規則這一半。安裝器要在 manifestRequirements 聲明 createNameIsOurs
才會生效,那一半在 arcrun-rag(D85)。
Refs: inkstone/Arcrun#123
2026-08-14 08:08:37 +00:00
uncle6me-web
c543abaa8a
fix(teardown): central KV 操作前機械核對 wrangler 身分(D88,leo 審核裁示①,必修項)
...
leo 審核意見:「這條正好違反 D88——閘要長在機器上,不是長在誰的記性上。而且失敗模式
是最壞的那種:靜默打錯帳號的中心 KV,沒有任何東西會叫。帳號側有白名單擋、中心側完全
沒有——這個不對稱本身就是設計缺口。」
修法:assertWranglerIsCentralAccount() 在 planCentralCleanup()(central-plan/
central-apply 共用的入口)最前面跑 `wrangler whoami --json`,核對登入態的帳號清單裡
有 uncle6(58309bb90fd93ad6d0fe0aae99170e9d)才放行,沒有就丟錯拒絕整個操作。
已用模擬錯誤帳號 id 驗證比對邏輯正確拒絕;正常路徑(真的登入 uncle6)central-plan
照常運作,貼在同一次 commit 的測試輸出裡。
同時把 leo 裁示②(apply 的依賴序失敗不擋併、但要記實測輪數)補進「已知限制」段:
兩次實測收斂輪數分別是 2 輪(有外部依賴殘留時)與 3 輪(純內部 service binding 鏈時)。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
2026-08-14 14:29:03 +08:00
uncle6me-web
49248b18c5
docs(teardown): 記下兩層咬合的已知限制(跨 worker 依賴鏈,2026-08-14 實撞)
...
今天實跑撞到、也解掉的真事:kbdb-graph-plugin(不屬於這台實例,正確被排除不動)
身上有 service binding 指著 arcrun-kbdb(要刪的)→ CF 拒絕刪除,本工具只回報那次
DELETE 失敗,不會主動點出「有外部依賴卡住」。若選擇半殘留(清資源留殼),下一步
acr init/update 的 resource-rule fail-closed 保護會正確拒絕重裝——這是它該有的行為,
但代表本工具留下的半殘留會讓下一步卡住,不是「拆一半也沒關係」。
本工具目前無法自動偵測跨 worker 依賴鏈,寫進「已知限制」而不是假裝判準完備。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
2026-08-14 14:20:56 +08:00
uncle6me-web
05e879faa7
fix(teardown): 拿掉 wrangler kv key delete 的 --force(這版 wrangler 沒這個 flag)
...
實跑撞到:這台裝的 wrangler 4.100.0 的 `kv key delete` 沒有 --force,帶了就直接
「Unknown argument: force」失敗。central-apply 兩個通道各撞一次才發現。
拿掉 --force 後在真實 youlin 帳號實測:central-apply 兩個通道(prod INSTALLER_KV
+ staging PEER_INSTALLER_KV)的 deployed:<accountId>: 紀錄都刪除成功,central-plan
複驗兩邊皆印「沒有殘留紀錄」。這是 Arcrun#120 死結修復鏈路的必要一步。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
2026-08-14 14:13:39 +08:00
uncle6me-web
0b72b818a0
chore(builds): 重編 tier2 成品 manifest——repo_dirty:true 修正為乾淨 HEAD(94d5452)
...
出貨 D87(Arcrun#119)到 stage 時卡在 build-bundles.mjs 的落後閘:main 上現有的
.worker-builds/manifest.json 記著 repo_head=8286c8a(4b6cc15 那個 commit 的父提交)
且 repo_dirty:true——是那次 commit 在完成前先跑了建置腳本,manifest 跟著沒完成的
工作樹一起被 commit 進去。
在乾淨的 main HEAD(94d5452)重跑 scripts/build-worker-artifacts.mjs:五顆
worker 內容位元組完全相同(cypher-executor sha256 8e6487478bc8... 不變,
證明前一份的實際內容其實是對的,只是 metadata 記錯),這次只更新
generated_at/repo_head/repo_dirty/source_commit 四個欄位為正確值。
2026-08-14 13:27:45 +08:00
uncle6me-web
7498911856
WIP: 內部 uninstaller 工具(拆帳號側 + 中心 KV),尚未跑過 apply(Arcrun#119/#120/#122)
...
leo 交辦:先做出「拆乾淨→重裝」的內部工具,在 youlin 帳號上自己能用即可,
不是產品(對外版另計)。D82 三步驗收(全新安裝→更新→解除安裝)需要拆的能力才測得到,
沒有它就永遠測不到 #119/#120 這類「只有全新用戶才撞得到」的坑。
目前做到:
- scripts/teardown-instance.mjs:
- plan/apply 兩階段(預設唯讀只印清單,真要刪要 apply --yes)
- 帳號白名單(只認 youlin,明確擋 leo21c)
- 歸屬判準:worker 名字要出現在本 repo wrangler*.toml 或官方 bundle manifest.core[]
才算「已知」,不認得的一律列出不動(例如 kbdb-graph-plugin,不屬於這台實例)
- 共用資源保護:同一顆 KV/D1/Vectorize 若也被「保留中的 worker」綁著,排除不刪
- central-plan/central-apply:清中心 INSTALLER_KV/PEER_INSTALLER_KV 的
deployed:<accountId>: 紀錄(#120 死結的另一半,帳號側清了但這裡沒清會卡死重裝)
- 已用 `plan` 在 youlin 帳號實跑過(唯讀),清單看起來合理,貼在 issue/回報裡
還沒做:
- apply(真的刪)、central-apply、以及刪完後的重裝驗證,全部還沒跑
- 已知限制列在檔頭註解:判準是「repo 認不認得」不是「屬於哪一台實例」,
同帳號多台同名慣例實例分不出來(今天 youlin 上短暫同時有正式實例與
ship-stage-119 的 freshtest 測試實例,此時就是靠人工 --keep 排除)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
2026-08-14 13:25:39 +08:00
uncle6me-web
94d5452425
merge: 認證儲存搬回 binding(D87 走 C,leo confirm)——全新安裝建得出第一個帳號
...
總管審過六項驗收,自己實測非採信自述:
- 測試 14 個失敗與 clean main 逐檔完全相同(既存,非本次引入)
- 成品 bundle 裡 x-arcrun-install-token 0 次
- writeAuthStore(Secrets 寫入)0 個外部呼叫者,已離開關鍵路徑
- auth-status 的 writable 改為型別上的 true,不再說謊
- 舊實例(D61 帳密在 Secrets)走純讀 env 的 legacy 路接回並 best-effort 搬進 KV
Arcrun#119 / InkStoneCo D87
2026-08-14 12:57:48 +08:00
uncle6me-web
4b6cc159b8
feat(auth): 認證儲存搬回 D1/KV——落實方案C(leo confirm「走C」,arcrun-rag#99)
...
實作 pending-changes.md「認證儲存要不要搬回 D1/KV」提案(commit 8286c8a):
D61 的病根「重裝時 binding 被安裝器照名字重新指到新建空資源」已被更通用的
shared/resource-rule(Arcrun#97,2026-08-13)解掉,故不再需要繞開 binding
去躲這個病——而繞開的代價正是這次要收的債:Workers Secrets 寫入需要外部
CF_SECRETS_API_TOKEN,這把 token 從安裝那天起就沒被種過,止血版只解掉
「建第一個帳號」這一格,之後的每一次寫入(換密碼/加帳號/改權限)仍卡死。
改動:
- console 管理員帳密:家改回 SESSIONS_KV(binding,console-auth.ts)
- portal 多人帳號:家改回 KBDB(binding,走 base HTTP API,D38 零 SQL,portal.ts)
- D61 認證儲存(CF Workers Secrets)留為舊實例的唯讀回退路徑:讀取零成本、
零外部憑證需求(只有寫入才要 token);登入成功即 best-effort 自動搬進新家,
且**這次登入發出的 session 就直接指向新 record_id**(不必等下一次登入)
- D61 的三項「明顯失敗」語意全部保留:auth_store_empty(讀不到不算密碼錯、
不計入鎖定)、/console/setup 遇既有帳號說清楚密碼沒被採用、/health 與
/console/auth-status 吐儲存狀態
- 移除止血版的 x-arcrun-install-token 表頭傳遞機制(installToken 參數)——
帳號寫入從此不需要任何外部 CF token,這個結構性缺口已從根拔除
測試:cypher-executor 全套 vitest 439/453(14 個既存失敗與本改動無關,已用
git stash 對照 clean checkout 逐一比對檔名確認完全相同);tsc --noEmit
無新增錯誤(3 個既存錯誤同上核實無關)。已跑 build-worker-artifacts.mjs
重打 tier2 bundle,grep 複驗 createKbdbUserRecord/promoteToKbdb 進了成品、
promoteLegacyUser/x-arcrun-install-token 完全從成品消失。
未覆蓋:POST /credentials(一般 workflow API 金鑰儲存)仍依賴
CF_SECRETS_API_TOKEN——這是 01-tech-stack.md 既有的、獨立於 D61 之外的
credential 儲存架構(D19「擁有目錄不擁有內容物」),本提案範圍只涵蓋「認證」
(登入帳密),不涵蓋一般 credential 儲存;07-29 已知缺口仍待另案處理。
不准 merge 進 main(SDD 鐵律③,等總管審過再併);不准部署(D20 出貨閘)。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
2026-08-14 12:52:07 +08:00
uncle6me-web
8286c8afec
docs(pending-changes): 提案——認證儲存搬回 D1/KV(撤 D61 的儲存層決定,留其明顯失敗語意)
...
arcrun-rag#99 止血修法(安裝精靈傳 OAuth token 給 cypher 建第一個帳號)只解一格,
之後的寫入(換密碼/加使用者/存金鑰)仍永久 writable:false,是留債。
查證(讀碼+讀本機重打的 .worker-builds/manifest.json,非猜測):D61 想解的病根
(重裝時 binding 被照名字重新指到新建空資源)已在 2026-08-13 被更通用的
shared/resource-rule(Arcrun#97)解掉——SESSIONS_KV、KBDB 的 D1 都在 cypher 的
requires 清單裡,且 runInstall:1762 每次安裝/更新都會走這條規則。技術前提成立。
提案:認證資料搬回 D1/KV(走 binding,零外部 CF token、零過期、零新 OAuth scope、
零手動步驟),保留 D61 加的三個明顯失敗機制。附影響分析、遷移考量、信心水位
(靜態推論,未動態實測)、D84 三段式驗法(③ uninstall 目前做不到,如實標記)。
⏸ 等 leo/總管 confirm,本 commit 不動任何架構程式碼。
順手修:writeAuthStore 的錯誤訊息移除誤導的「/ CF_ACCOUNT_ID」(已查證全新實例
一定有這個 var,installer worker.js:1354 無條件注入,列兩項只會讓下一個人白工)。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
2026-08-14 12:06:50 +08:00
uncle6me-web
ca1ed2aaf6
fix(auth): 錯誤訊息不再叫用戶做做不到的事(arcrun-rag#99,leo 傳實測畫面點破)
...
leo 傳了封測者實際看到的畫面:撞牆訊息叫他「重新執行安裝/更新讓它就緒」,
但安裝/更新從來不會把 CF_SECRETS_API_TOKEN 種成一個常駐的值——這句話在
訊息會出現的所有情況下都不保證成立(尤其是安裝精靈本身之外的路徑,如事後
新增使用者、改密碼)。使用者照做 → 回到同一畫面 → 以為是自己操作錯誤 →
無限迴圈。這比「訊息不夠白話」嚴重一個級數:不是難懂,是死路。
改:不再承諾一個我們自己都不確定會生效的動作,改成誠實描述現況+給得出去
的下一步(回報支援並附上下情境)。訊息集中在 writeAuthStore 單一出處,
console-auth.ts/portal.ts 的 502 回應共用同一份文字,不會各說各話。
測試:全套 vitest 443/457(14 個既存失敗與本檔無關,已用 git stash 對照
clean checkout 確認);tsc --noEmit 無新增錯誤。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
2026-08-14 11:56:59 +08:00
uncle6me-web
88f308642e
fix(auth): 全新安裝補上 CF_SECRETS_API_TOKEN 的缺口——第一個帳號建得起來了(arcrun-rag#99)
...
根因:D61(認證與資料分離,c4cee35)把 /console/setup、/portal/admin/bootstrap
的寫入全部改走 CF Workers Secrets(env.CF_SECRETS_API_TOKEN),但這把 token
從安裝那天起就沒被種過——07-29 已知缺口(pending-changes.md「credential 走
n8n 模式」),當時只降級某個功能;D61 之後升級成「連第一個帳號都建不起來」
的硬斷點,每台全新安裝必中(leo 本人+封測者實撞:裝得起來但卡在註冊)。
修法:putWorkerSecret/deleteWorkerSecret/authStoreWritable/writeAuthStore/
mutateAuthStore 新增可選的 tokenOverride 參數(呼叫端提供 > worker 自身 env)。
/console/setup、/portal/admin/bootstrap 讀取 x-arcrun-install-token 表頭,
只有安裝精靈(裝機當下手上有一把自己還有效的 OAuth token,workers-scripts.write
scope,同一把已用於 putWorkerSecretDirect/seedCredential)會帶這個表頭;
一般使用者自己在瀏覽器操作不受影響。沿用既有「D36 安裝器代寫」precedent,
不是新開一條路;bootstrap 本身已被「已有 admin → 409」擋成只能成功一次,
不會被拿來反覆濫用。
測試:cypher-executor vitest 443/457(14 個既存失敗與本改動無關,已用
git stash 對照確認);新增 2 則直接證明「缺 token→502 auth_store_not_writable/
帶 token→200」。tsc --noEmit 無新增錯誤。已跑 build-worker-artifacts.mjs
重打 tier2 bundle 供驗證(工作區未 commit 前提下的本地驗證版)。
未完成:安裝器(products/arcrun-rag/installer/oauth-prototype/worker.js)
端的 x-arcrun-install-token 表頭傳遞已另外修好,但兩邊都還沒部署——需要
①重打正式 worker artifact ②install.arcrun.dev 的安裝器 wrangler deploy
③已卡住的封測者要再走一次安裝精靈讓他的 cypher worker 拿到新 bundle。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
2026-08-14 11:46:56 +08:00
uncle6me-web
c3856470ad
chore(builds): 重編成品——把 #117 地圖 entry_count 與 #118 讀不到≠不存在 放進執行檔
2026-08-13 18:28:25 +08:00
claude-code
1ccee0055e
Merge PR #118 : 「讀不到」不准長得像「不存在」+開場就講得出有知識庫(總管審過)
2026-08-13 10:02:27 +00:00
claude-code
302e024804
Merge PR #117 : 藏書地圖分開講「有幾筆內容」與「萃出幾條關係」(總管審過)
2026-08-13 10:01:34 +00:00
uncle6me-web
c2897ba68b
fix(mcp): AI 一連上就知道「這裡有主人的知識庫」+「讀不到」不再講成「不存在」
...
leo 2026-08-13:「它應該是**你的知識來源**⋯⋯**沒有它你是瞎的**,
從你對 Arcrun 的認知就知道你的內建 Memory 是沒用的。」
## 病灶一:指路的話被綁在一個會無聲消失的段落上
instructions 裡唯一提到「這裡有知識可查」的,是 buildLibraryMapInstructions
拼上去的【藏書地圖】——而那段是**選配的**(1500ms 逾時、catch 回 null、失敗無聲)。
必定出現的那段靜態文字(startHere)從頭到尾只講工作流、零件、recipe、邊的語法,
`kbdb_search`/`kbdb_get_map` 一個字都沒有。
實害:同一天,一條 portal 連線的 session 為了回答「Arcrun 是什麼」去讀了 682 行
原始碼——而 `kbdb_search(q="Arcrun 是什麼")` 當下回得出 33 筆真答案。
修法:
- 新增 `KNOWLEDGE_FIRST` 靜態段(與 startHere 同級,KBDB 掛了照樣出現),
排在最前面:這條連線後面有主人的知識庫、第一個動作是 kbdb_search、
工具怎麼呼叫、以及「沒查到/沒查/地圖沒取到」三件事不可以講成同一句。
- 地圖失敗**不再靜默**:拿不到就印【藏書地圖:這次沒取到】,
stale token 另印身分版(要使用者重新連線)——「沒取到」與「這裡沒有知識」
不可以長得一樣(Arcrun#109 同族)。鐵律不變:失敗仍然不擋連線。
- 抽出 `buildServerInstructions(env, identity)` 供測試印出完整字串。
## 病灶二:一個 401 被逐字翻譯成「不存在」
`arcrun_get_skill('write_intent_workflow')` 回「skill ... 不存在」,
但 `kbdb_search` 撈得到 `page_name: "skill-write_intent_workflow"`
(entry_type: agent-skill、source: installer-seed)——**與本工具查的鍵逐字相符**。
真兇是 `kbdbGetByPageName` 的 `if (!resp.ok) return null;`。
而 401 的來源是該實例的 arcrun-mcp 沒有 KBDB_INTERNAL_TOKEN
(kbdb-client 沒 token 就匿名送出)=部署面的事,但這支 code 把它講成了假話。
修法:
- 非 2xx 一律拋 KbdbAccessError(帶真 status)→ kbdb_unauthorized/kbdb_unreachable,
訊息明說「這是讀不到,不是不存在」,並交出還走得通的那條路(kbdb_search)。
- `not_found` 只留給「KBDB 正常回應但真的沒這張卡」,並說明是「這台實例沒 seed」。
- 五支工具吃 identity:stale → identity_missing;portal 撞 401 時額外說明
「是這批工具還走服務憑據,不是你的帳號讀不到」。
- instructions 步驟 4 不再指名 `arcrun_get_skill('INDEX')`(leo21c 實測根本沒 seed
這支)——改成先 `arcrun_list_skills()` 看這台實例真的有哪幾支。
## 驗
- `mcp` 測試 130 passed(新增 mcp-instructions 7 項+skills-examples-honesty 10 項),tsc exit 0。
- 三種情境(地圖抓得到/抓不到/舊 token)各印一份完整 instructions 實測,
三份的開場都指向 kbdb_search。
⚠️ 未部署(部署是 leo 的手動閘)。上線後才會到用戶手上。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
2026-08-13 14:09:35 +08:00
uncle6me-web
614fe44812
fix(kbdb): 藏書地圖加 entry_count——triplet_count=0 不再被誤讀成「沒有知識」(Arcrun#87 三次收尾)
...
leo21c 實測:kbdb_get_map() 9 庫裡 8 庫 triplet_count:0/top_entities:[](kb 以外全部),
一個全新 session 讀到這份地圖會合理但錯誤地判定「這些庫沒有知識」而放棄查詢——但
kbdb_search(mode=keyword) 找得到 42 筆真內容(來源 gitea:Leo/Arcrun@…/gitea:Leo/mira@…等),
其中 15 筆 entries 的 metadata_json.library 已正確標成 arcrun/mira/arcrun-harness/arcrun-rag。
查證(唯讀,未動 leo21c 任何寫入):
- entries(原始 ingest 內容)與 triplet(從 entries 萃取出的三元組)是兩個不同存放處。
- kb 以外 7 庫:entries 有、library 標記正確;triplet 一筆都沒有——不是「三元組沒貼標」,
是「三元組從沒被萃取」(另一條偵察線在查斷在哪一段,跨 repo,本 PR 不處理)。
- general 庫(未標 library 的三元組兜底分類)即時計數也是 0 ⇒ 沒有任何「有三元組但沒標庫」
的候選 ⇒ Leo/Arcrun#87 先前那條批次補標通道(PR #114)在目前資料現況下會補到 0 筆,
它解的是另一個問題,不是這次「地圖看起來是空的」的真因。
本次修法(純讀端加欄位,不碰任何寫入/部署/線上資源):
- kbdb/src/actions/library-map.ts:新增 liveEntryCountsByLibrary(),依 entries 自己的
metadata_json.$.library 分組即時計數(排除 entry_type='value' 儲存碎片與地圖自己的歷史
摘要 block,避免自我膨脹)。listLibraryMaps/getLibraryMapDetail/recomputeLibraryMap
的回傳都加上 entry_count,與 triplet_count 並排、互不覆蓋。
- mcp/src/lib/library-map.ts:renderLibraryMapLines(MCP 連線開場注入的那份地圖原文)
triplet_count=0 但 entry_count>0 時改印「0 triplets/N 筆原始內容(尚未萃取關係,
kbdb_search 查得到)」,不再只印「0 triplets」。
- mcp/src/tools/kbdb_map.ts:kbdb_get_map 工具的全館/單庫回應都帶 entry_count,並在符合
條件時附加提示,明講「triplet_count=0 不代表沒有知識」。
- console-ui/public/console/index.html:藏書地圖看板卡片同步顯示,人類看的畫面同一件事。
順手修掉一個真 SQL bug:entry_count 排除條件原寫
`NOT (entry_type='block' AND json_extract(...)='library_map')`,SQL 三值邏輯下
metadata_json 沒有 $.kind 欄位時 json_extract 回 NULL、`NULL = 'library_map'` 為 NULL
(非 false),整條 WHERE 判定 NULL 而把所有列濾掉——改用 COALESCE(...,'') 修正
(新增測試以 sqlite 實跑驗證抓到並鎖住這個修法)。
測試:kbdb 21/21(新增 2 案,全套 215/215);mcp kbdb-map 33/33(新增 7 案,全套 120/120)。
cypher-executor 既有 map/portal-data 相關測試(library-map-scope-108/kbdb-map-proxy/
portal-data)103/104 綠,唯一失敗(/portal HTML 殼 404)在未動過的 main checkout 上同樣
失敗,環境既有問題、與本次改動無關。
CP:◐ 半通。程式碼在此分支,測試綠燈,未併 main、未部署 leo21c——entry_count 要讓
leo21c 的真實使用者看到,需部署 kbdb+mcp(cypher-executor 未改動,portal-data.ts
是透明轉發不需重部署)。三元組萃取斷在哪一段(真因)與 23/42 entries 未 embed
(語意搜尋覆蓋率)兩件不在本 PR 範圍,分別交給另一條偵察線與 embed reconcile 管線。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
2026-08-13 12:36:03 +08:00
uncle6me-web
9ff933ba98
chore(builds): 重編成品——把 #108 的租戶修法放進執行檔
...
cypher source 10d150a → b223a698(#108 併入)。
重編時新的租戶來源閘第一行就報:
✔ 租戶來源檢查通過:cypher-executor 資料面 owner_id 全部來自 src/lib/tenant.ts
⇒ 那道閘現在長在建置線上:以後有人再寫 env.XXX || 'leo',成品編不出來。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
2026-08-13 00:25:46 +08:00
Leo
2fcae722e7
Merge PR #112 : 藏書地圖看得到自己的知識——租戶字串改從寫入端來(Arcrun#108)
...
總管複驗(不聽自述,自己重跑並與 base 逐條比對):
cypher 14 failed / 441 passed(base 14 / 400)⇒ 多 41 條新測試全過
失敗清單 diff 無輸出=逐字元相同、零回歸
閘實測 照 #105/#108 的原句形狀種一個違規(env.CONSOLE_TENANT || leo)
→ 掃描器當場抓到並指名道姓;移除後恢復綠 ⇒ 不是假綠
這道閘的價值超過本票:它擋的是「身分來自環境變數」整族,
#105 那句話今天再寫一次也會被擋。符合 leo 08-12 立的
「做平台要減少 hotfix」——修掉 bug 不算完成,要留下下次再犯會被擋住的機制。
agent 誠實標的殘項(不擋併):
· kbdb_get_map 回 1851、Portal 總圖畫面、部分授權帳號實測——都要部署 leo21c,紅線沒碰
· 想把閘也接進 .claude/hooks/pre-write-guard.sh(寫入前就擋),該檔受保護改不動;
檢查器已備妥 --stdin 模式,總管代接
2026-08-12 16:24:45 +00:00
uncle6me-web
cac874601f
chore(builds): 重編 tier2 成品——把 #108 放進執行檔(出貨路徑 A 的第 0 步)
...
原始碼修好不等於使用者拿得到:`.worker-builds/` 是安裝器與 `acr update`
實際部署的那份執行檔。本次一併重編,repo_dirty=false、repo_head=b223a698。
arcrun-cypher-executor sha256=e0026a23792f source=b223a698 ← #108
arcrun-kbdb sha256=9c6d41895d78 source=f87d0e92(未變)
arcrun-http-request sha256=9a9dcb71879a source=1e85dfb4(未變)
arcrun-code sha256=285a7406ec69 source=621cb8d9(未變)
arcrun-mcp sha256=3ebc0d441bc0 source=10d150ac(未變)
其餘四顆的 js 位元組有變是 esbuild 版本/相依重裝造成的重編,source commit 未動。
本次起這條路徑多一道閘:編成品前先跑租戶來源檢查,違規直接「建置中止」
(見同 PR 的 scripts/build-worker-artifacts.mjs)——實跑輸出貼在 PR。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-08-13 00:16:55 +08:00
uncle6me-web
b223a69884
fix(portal): 藏書地圖看得到自己的知識——租戶字串改從「寫入端」來,不再拿環境變數預設值(Arcrun#108)
...
leo 2026-08-12 實撞:藏書地圖回 0 個庫,同一分鐘 KBDB 裡有 1854 條三元組,
`arcrun_whoami` 顯示 admin/全部知識庫、`kbdb_search` 也查得到——只有地圖那格是空的。
病根(不是資料掉了,是讀寫兩端各拿一個來源):
寫入端 owner_id = `~/.arcrun/config.yaml` 的 `api_key`(CLI push/小幫手上傳/MCP,
leo = `bfezv28v`)
讀取端過濾 = `portalTenant(env) = env.CONSOLE_TENANT || "leo"`
——repo toml 帶的**官方 prod 值**,而 `acr` 從來不注入 CONSOLE_TENANT
⇒ 那個 `"leo"` 不是理論邊角,是每台 self-hosted 實例的實際行為,1854 條全被濾掉。
與 #105(`env.MCP_OWNER_NAMESPACE || "leo"`)同一句話,換一個檔案。
租戶字串該從哪裡來(本票的核心判斷):
**從「寫入這批知識的那一方」來,不是從一份手抄的環境變數預設值來。**
不是「掛到每個帳號上」——portal 帳號共用同一台實例的知識庫(design D-2),
帳號之間的差別是 libraries 權限不是 owner_id;複製一份到帳號上只是多一個會過期的副本。
#105 真正的教訓是:過濾用的租戶字串要有單一權威來源、解析不到要誠實失敗、且要能機械驗證。
修法:
1. 唯一產地 `cypher-executor/src/lib/tenant.ts`
- `knowledgeOwner(env)` → branded `TenantId`:`ARCRUN_NAMESPACE` → `CONSOLE_TENANT` →
丟 `TenantUnresolvedError`。**沒有字面預設值**——`|| 'leo'` 正是把「這台機器沒設定」
偽裝成「你沒有資料」的元凶。
- `accountTenant(env)` → 普通 `string`(帳號子 namespace `{tenant}::portal` 與 cypher
自己寫的設定用它)。**回 string 是刻意的**:型別上就不可能流進知識資料面。
- 資料面過濾一律經 `ownerQuery()` / `ownerField()`,只吃 `TenantId`。
2. 值的正解由 CLI 從真相源導出:`acr update` 把 config 的 `api_key` 注入成 `ARCRUN_NAMESPACE`,
但**先驗再寫**(`GET /kbdb/map?owner_id=<api_key>` 查得到庫才寫;查不到/問不到就一個字
都不動)。無條件覆蓋會把「知識本來就在 CONSOLE_TENANT 底下」的一鍵安裝實例指向空的那一格
——那是 #97/#106 那類「更新一次把人家的東西弄不見」,比原本的 bug 更糟。
未注入時回退 CONSOLE_TENANT ⇒ 對官方 prod 與未更新的實例,這次改動是惰性的。
3. 空地圖分四態(沿 #100「讀不到就說讀不到」):no_library_grant/filtered_out/
scope_mismatch/confirmed_empty。scope_mismatch 以前不存在,所以設定錯誤被畫成
「你沒有資料」。回應仍不含租戶字串(design §3.3 紅線)。
4. 同族一起修(同一道閘一次抓到):console-dashboard 4 處、console-auth 1 處
——console 首頁的規模數字與藏書地圖對 leo 也一直是空的。
留下的閘(規則存在但沒機制驗證=會再犯第三次):
· 型別閘:TenantId 只能由 tenant.ts 產出 → 拿隨手一個 string 去過濾,tsc 當場不給過。
· 出貨閘:scripts/build-worker-artifacts.mjs 編 tier2 成品前先掃,違規 → 編不出成品。
· 閘自己可測:規則是純函式(tenant-source-rules.mjs),tests/tenant-gate.test.ts
逐條驗「5 種壞例子會擋」+「11 種合法寫法零誤攔」;掃描範圍只有 src/,擋不到自己。
規範寫入 .claude/rules/02-forbidden.md 第六類、system-dev/wiki/mistakes.md #26。
沒動:庫權限過濾(一字未改,回歸測試釘住)、帳號資料落點、任何金鑰、租戶字串仍不下發前端。
驗證:
cypher vitest 441 綠 / 14 紅,14 紅與 base commit e05518a 逐字相同(既有)
tsc 5 個既有錯誤,零新增
cli node:test 60/60 綠(含本次新增 12 條);tsc 零錯誤
閘 壞例子實跑 exit 1;build 實跑「建置中止」;乾淨時實跑通過
端到端 ◐ 未驗:需部署到 leo21c,那道閘要 leo 親手解(見 PR ③)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-08-13 00:15:58 +08:00
uncle6me-web
fad5da0e17
rules/07 §3.6:自舉例外——能力只實作一次,但不一定要是 HTTP API
...
代補 PR #111 的 agent 想加但改不動的段落(該檔受保護)。
立這條的原因不是理論:#97 的修法一開始寫在 cli/src/lib/resource-resolver.ts
(能力住在介面層,違反 §0)⇒ 安裝器拿不到它 ⇒ 同一個 bug 只修了一半,
走 acr 的人有保護、走 install.arcrun.dev 的人沒有——而所有真實用戶走後者。
leo 2026-08-12:「根本就不應該在 CLI,我要的是一個大家都可以用到的規則。」
記三件,都是為了不讓下一個人「修正」回去:
① 為什麼不放 cypher API(自舉/輸入是使用者自己的帳號狀態/它是純函式)
② §0 的正確讀法是「只准有一份、不准住在單一介面裡」,放 API 只是常見手段
③ npm 打包例外:cli/ 下的逐位元組副本由 sync --check 機械擋漂移,不是第二份實作
📍 repo:matrix/arcrun(shared/resource-rule/、cli/、.claude/rules/07)
+ products/arcrun-rag(安裝器待接上,已派工)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
2026-08-12 23:50:53 +08:00
Leo
13155a1d7b
Merge PR #111 : 把「該用哪些資源」搬出 CLI——一份實作,兩條路吃同一條規則
...
leo 2026-08-12:「根本就不應該在 CLI,我要的是一個大家都可以用到的規則。」
總管複驗(不聽自述,自己重跑):
兩份 rule.mjs 逐位元組相同;--check 綠
故意加一行製造漂移 → --check 當場擋下;還原後恢復綠 ⇒ 閘是真的,不是假綠
cli 測試 58 pass / 0 fail(main 上是 49 ⇒ 多 9 條全過)
邊界乾淨:沒動 cypher/kbdb/mcp;resource-resolver.ts 減 461 行變薄殼
兩條新測試比我寫的收工標準更嚴:
two-paths-agree 斷言「一邊停手、一邊照做 = 最危險的分歧」與「停手的理由也要一樣」
single-implementation 帶 assert.ok(files.length > 0) 防「檢查 0 個卻通過」
——後者正是總管當晚剛撞過的假綠形狀
殘項(agent 誠實標的,不擋併):
· youlin 上走安裝器那條路的實測沒跑成——安裝器在 arcrun-rag,不在它的工作副本裡,
且沙箱無 CF 憑證。它依紅線「不確定時寧可中止」沒有猜著跑。
· 安裝器那一行 import 尚未接上(規格在 shared/resource-rule/README.md §4)
· rules/07 想補「自舉例外」正例,該檔受保護,內容留在 README §2 待總管代補
2026-08-12 15:44:50 +00:00
uncle6me-web
bb548b6fdf
refactor(shared): 「該用哪些資源」搬出 CLI——一份實作,acr 與安裝器吃同一條規則
...
leo 2026-08-12:「根本就不應該在 CLI,我要的是一個大家都可以用到的規則。」
「這個實例該用哪些資源」換到安裝器就要重寫一次 ⇒ 依 rules/07-thin-shell.md 的判準
它是**能力**,而它原本住在 cli/src/lib/resource-resolver.ts ⇒ 那本身就是違規。
後果已經真的發生:acr 那條有 Arcrun#97 的修法、安裝器那條沒有,於是安裝器照名字
找、找不到就建一顆空的綁上去 ⇒「我按了更新,工作流和登入全不見了」。
規則搬到 shared/resource-rule/(零依賴 ESM,Node 與 Workers runtime 都直接跑):
· rule.mjs 規則本體+把 CF 回應讀成事實的 normalizeLive*
· cf-resource-api.mjs ResourceApi 的 CF REST 實作——**眼睛也共用**:
兩條路各自解讀 CF 回應,只要一邊看不到既有綁定就會去新建,
#97 不需要規則寫錯就能重演
· installer-entry.mjs 安裝器唯一該碰的入口 resolveInstanceResources()
不是做成 cypher 端點的理由(自舉):這條規則要在「決定怎麼裝」的當下就用得到,
而那時 cypher 可能還不存在(安裝器的工作正是把它生出來);且輸入是使用者自己帳號的
綁定狀態,不該送去平台換答案。它是純函式,用不著變成服務。
只有一份,機械看守:
· 安裝器直接 import repo archive 裡的原稿,**不需要副本**
· acr 因為 npm pack 打不進套件目錄外的檔案,帶一份逐位元組鏡射
(scripts/sync-resource-rule.mjs 產生;build/test 先跑 --check,差一位元組就紅)
——同 cli/harness/ 產生物+世代閘的既有慣例
· cli/tests/single-implementation.test.ts 掃全 repo:7 支規則函式的實作只有一處
CLI 淨 -496 行(邏輯是搬走,不是複製)。cf-api.ts 的 CfAccountClient 保留公開介面,
ResourceApi 那七個方法全部委派共用 client。
驗證:cli 58/58 綠(含新增的兩條路一致性 fixture + 三種情境),tsc --noEmit 乾淨。
2026-08-12 23:37:01 +08:00
uncle6me-web
e05518a2b4
chore(builds): 重編成品——把 #107 的版本標籤修法放進執行檔
...
出貨線第 1 站的 #93 新鮮度閘擋下:cypher-executor 成品記 10d150a,
而 HEAD 已是 2129356(#107 改了 routes/health.ts 與 types.ts)。
這是該閘今晚第三次擋對——沒有它,1.4.42 會送出一個「版本標籤修法只在源碼裡」的成品。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
2026-08-12 22:22:57 +08:00
Leo
21293568d5
Merge PR #107 : 更新完還看得到版本號——CLI 重部署不再把版本標籤洗掉(Arcrun#106)
...
總管複驗(不聽自述,自己重跑並與 base 逐條比對):
cli main 是 0 pass / 3 fail(測試根本跑不起來)→ PR 49 pass / 0 fail
⇒ agent 那句「#97 的守衛測試一直沒在跑」是真的,又一個假綠
cypher 14 failed / 402 passed(base 14 / 400);失敗清單 diff 無輸出=零回歸
設計比我建議的對:版本標籤每趟重烙、其他 vars 才沿用。
我原本建議「一律沿用」會讓版本號永遠停在安裝當時。
殘項(agent 誠實標的,不擋併):沒在真實例上跑過、沒有真 Portal 截圖
(它的環境指向 leo21c 紅線、無 youlin 憑證、瀏覽器未授權)。總管接手補這段。
2026-08-12 14:05:57 +00:00
uncle6me-web
53b05c6d3d
fix(cli): 更新完還看得到版本號——CLI 重部署不再把版本標籤(和你的設定)洗掉
...
leo 08-12 實撞:更新完 leo21c,Portal 設定頁的「版本」變成
「無法讀取目前版本(知識庫服務可能正在啟動)」。版本號是 leo 唯一的驗收介面,
看不到就等於他無法自己確認任何一次更新有沒有生效。
根因(Arcrun#106):`bundle_version` 來自部署時注入的 plain_text var
`ARCRUN_BUNDLE_VERSION`,而**只有安裝器會注入**。wrangler deploy 是整份覆蓋,
toml 沒寫的 var 直接消失 ⇒ CLI 更新那條路每跑一次就把標籤洗掉一次。
#97 修好了「櫃子」(KV/D1/Vectorize 沿用既有),沒修「櫃子上的標籤」。
修法(兩種 var 走相反的規則,這是本次的判斷):
· 設定類 var = 使用者實例的事實 → **沿用**(讀綁定時同一份回應就帶回來,不多打 API)
——把 #97「已部署的 worker 上綁著什麼就是事實」原封不動套用到 plain_text var。
· 版本標籤 = 這份成品的屬性 → **每趟重烙,絕不沿用舊值**。
沿用舊值會得到一個永遠停在安裝當天的假標籤——比沒有標籤更糟,
因為它會讓人以為驗收過了。
版號取部署當下發行頻道公告的 release(Portal/daemon 就是拿它當「最新版」比),
另外把**真正部署的 commit** 一起烙上去(/health 多吐 `bundle_commit`)→ 漂掉查得出來。
查不到 release 就誠實退成 `YYYY-MM-DD+<commit7>`,不掰一個 semver 假裝已是最新。
順帶(都是同一條路上的東西):
· ref 先解析成 commit sha 再用 sha 下載 archive——不可變,順手解掉 branch tarball 被快取的老病
· Portal 版本行接受帶 build metadata 的 semver(`1.4.41+d61` 這種先前一律被當成「較舊版本」)
· cli 測試在 node 22 上本來一支都跑不起來(.js→.ts 解析 + parameter property),補上 resolve hook
——#97 那份「使用者的東西還在不在」的迴歸守衛也在其中,跑不起來的守衛等於沒有守衛
· types.ts 的 ARCRUN_BUNDLE_VERSION 重複宣告(TS2300)併回一處
驗證見 PR:cli 49/49 綠、cypher health 4/4 綠、Portal 版本行原始碼實跑五種情境、
對真實已部署 worker 的唯讀 dry-run。**未做**:真實實例上的 acr update 端到端
(本機唯一有憑證的帳號是 leo21c=紅線禁碰,youlin 無憑證)。
Refs: Leo/Arcrun#106 , #97 , #95
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-08-12 21:58:43 +08:00