Compare commits

..

254 Commits

Author SHA1 Message Date
uncle6me-web d7a98f53e7 重編出貨成品——kbdb 成品終於裝的是 v7,不是舊模型
出貨管線 preflight 擋下:kbdb 的成品比源碼舊 3 顆 commit
  ceb7638 樹狀 record 模型第一刀(entry_values 拆表,v7 定稿實作)
  aa49b4e 刪 record 不再刪掉別人還指著的 entry(Arcrun#128 配套)
  5b22d56 createRecord 的 slot 可指向既有 entry(Arcrun#128)

🔴 這道閘剛好攔住了今晚整件事的意義被抹掉:成品沒重編就出貨,
   bundle 裡裝的是舊 kbdb ⇒ youlin 依然「資料新、程式舊」,
   而版本號會照跳,看起來像出貨成功。

重編 23/23,實際變動只有 3 個檔(kbdb/cypher-executor/manifest)——
其餘 20 顆內容指紋沒變、不虛增版本,指紋機制運作正常。
arcrun-kbdb 現在 source=ceb7638d、sha256=47b09d60f8d1。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 23:54:09 +08:00
uncle6me-web cacaa33f7d merge: 免金鑰預設路的卡形狀——格式契約收歸 daemon 一份(Arcrun#134)
病徵:portal.ts 的 /portal/daemon/extract 產舊四段格式,而那是免金鑰預設路
=多數用戶實際走的那條 ⇒ daemon 修好了、雲端沒修 ⇒ 對多數用戶等於沒修。

修法比要求的更好:不是把 prompt 複製兩份再靠註解同步,而是
**讓 daemon 擁有格式契約(自帶 prompt),雲端端點退化成「用你自己的 AI 跑一段生成」
的執行器,它不認得也不需要認得卡的形狀**。
⇒ 兩份契約合成一份——與 v7「兩套機制合成一套」同一個手法。

它自己寫下的理由正是今天的主題:
「靠註解叮嚀『兩邊要一起改』同步⋯⋯本端點沒跟上 ⇒ 叮嚀式同步實證失效,這張票就是證據」

總管審過的:
- 舊 daemon(不帶 prompt)走 legacy 路,行為與回應完全不變;legacy 提示詞刻意凍結
  並註明「要改卡的形狀,去 collector/ 改那唯一一份」
- prompt 模式放寬 max_tokens 到 8192,理由寫在碼上(2048 會把 JSON 攔腰截斷)
- 🔴 測試對照:分支 14 failed/447 passed(461 支)vs main 14 failed/445 passed(459 支)
  ⇒ 同樣那 14 個是 main 上的既有紅(Arcrun#131),本分支零新增紅,且多的 2 支都過

⚠️ 併 main ≠ 送達:要部署 cypher worker,用戶才會走到新的那條。

📍 InkStoneCo#44 第 ④ 環|Arcrun#134

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 22:31:04 +08:00
uncle6me-web fda9d169d5 Merge remote-tracking branch 'gitea/main' into work/extract-wiki-json-0815 2026-08-15 22:28:30 +08:00
uncle6me-web 7b478378aa merge: 樹狀 record 模型第一刀——record 有身分、關係是唯一機制、entry_values 拆表
規格=pending-changes「record 要有身分」v7 定稿(leo 2026-08-15 confirm)。
地基(leo):「真身在 pool 的 entry 裡,所有的虛擬表虛擬欄位都是指向這個 entry 的指標。」
⇒ 一個指向就是一條關係 ⇒ entry_values 該死的理由是「它是關係的第二套實作」。

總管審過的(自己跑的,不是聽回報):
- npm test 236/236 綠
- 0007 的形狀對:三根型別化指標欄(src_id/rel_id/dst_id,v7 定案載體)
  + DROP TABLE entry_values,19 處 IF NOT EXISTS/INSERT OR IGNORE
- 遷移測試逐條斷言:每格一條關係列/每 record 一顆身分/value entries 一列不動/
  舊表拆掉/讀端讀得回同樣資料/🔑 第二次逐句重放零錯誤且快照逐字相等(冪等被證明)/
  乾淨後零孤兒、人為斷鏈掃得到並指出斷在哪一端
- relation-orphans.ts 的註解誠實說明它為什麼存在:拆表後同一張表指自己不能建 FK
  ⇒ 斷鏈不再被資料庫擋下,改由巡檢主動找;並列出三種會產生孤兒的情況

⚠️ 這條在 youlin 上動過真的遠端 D1(ALTER/DROP/大量 INSERT)。
   總管事後逐項查證範圍:CF 帳號只出現 youlin 一個、leo21c 真庫 516884ce 零命中。
   而「派工只寫『在 youlin 實跑』沒指定哪一顆」是總管授權寫太寬,已落帳 mistakes.md。

⚠️ 併 main ≠ 送達:youlin 的 worker 仍是舊碼(資料新、程式舊的中間狀態)。
   leo 已裁「當然往前」⇒ 接下來部署。

📍 InkStoneCo#44 第 ⑤ 環|Arcrun#132

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 22:26:02 +08:00
uncle6me-web ceb7638d74 feat(kbdb): 樹狀 record 模型第一刀——record 有身分、關係是唯一機制、entry_values 拆表(v7 定稿實作)
規格:system-dev/docs/3-specs/pending-changes.md「record 要有身分」v7 定稿(leo 2026-08-15 confirm)。
模型一句話(leo):「真身在 pool 的 entry 裡,所有的虛擬表虛擬欄位都是指向這個 entry 的指標。」

- 0007 migration:池上型別化指標欄(src/rel/dst)+一對方向 partial index+啟動常數
  (sys_root/sys_belongs/sys_field_of)+templates 鏡射成 sheet/field entry+
  每筆 record 一顆身分 entry(id=原 record_id,引用不失效)+每格一條關係列
  (id 由舊儲存格列 id 衍生 ⇒ INSERT OR IGNORE 天然冪等)+拆 entry_values
  (0006 墊表→搬→拆手法)。純 INSERT、value entries 一列不動(向量索引不失效)。
- record-crud 整份改寫到關係列(#128 指標語意/共用保護/N+1 批次/租戶過濾全數保留,
  驗收測試 232→236 綠);library-map 四段縱轉橫 SQL、records triplet-stats 改查關係列。
- entry-crud:機制列隔離(未指定 entry_type 的列表/搜尋不回機制節點);deleteEntry
  接手舊 entry_values FK 的不變量(dst 被指著→拒刪)。
- 孤兒偵測重設計(v7 §5 點名):新模型孤兒=指標指向不存在 id 的關係列,
  LEFT JOIN 斷鏈掃描(承接 2026-06-24 清理事故的 FK 形狀),
  GET /maintenance/relation-orphans 唯讀巡檢。
- cli deploy.ts:0007 逐句套用+容錯 duplicate column(SQLite 無欄位級 IF NOT EXISTS,
  整檔送 /query 會在重跑時假紅)。
- 測試:tree-record-migration.test.ts 驗資料零漏/雙跑冪等/孤兒掃描;
  釘死三表的斷言依 confirm 後規格改口(execution-log/credential-legacy 兩處)。

遷移期雙軌(第二刀收):templates 表仍是欄位定義真相源;六種 metadata_json 打包型
與 §7 減法封鎖(拿掉 entry_type/metadata_json 欄)留待第二刀。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 21:34:48 +08:00
uncle6me-web 9fa88065ab feat(portal): /portal/daemon/extract 接受 daemon 自帶提示詞——卡片格式契約回歸單一真相源(Arcrun#134)
免金鑰預設路(Workers AI)一直用雲端自己那份舊格式 prompt(一句話定義/要點/
關鍵實體/關聯),而 daemon 端 2026-08-15 已改成「LLM 只回 JSON 判斷、wikishape.go
機械組卡」(InkStoneCo#44 ④)⇒ 走預設路的用戶拿到次級卡。兩份 prompt 靠註解
叮嚀「兩邊要一起改」同步,這張票就是叮嚀失效的證據。

修法=契約搬回唯一一處:daemon 送 {page_name, text, prompt},雲端在 prompt 模式
只當執行器(用實例自己的 env.AI 跑生成、max_tokens 對齊 gemma 路的 8192),
原文以 {success, output} 回傳,淨化與解析全在 daemon。雲端不再認得卡的形狀。

相容兩個方向都有路:舊 daemon(無 prompt)走 legacy 路,行為與回應完全不變;
新 daemon 撞舊雲端(回 card 無 output)由 daemon fallback 到 legacy 卡處理。

測試:portal-admin +2(prompt 單獨成立=501 探針非 400;legacy 齊欄行為凍結),
全套 447 passed,14 failed 與基準(main)逐一相同=非本改動引入。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 21:21:48 +08:00
uncle6me-web 969acff325 merge: n8n KBDB 節點(薄殼)——對照表 那半
「有效才正式開發」的下一格:紙上考卷過了(Haiku 6/7、Sonnet 7/7),換真的。
範圍限對照表標  的動作;🔴 那半(關係整組、archive 兩個)沒做,等資料模型定案。

總管審過的(自己驗的):
- 每個動作剛好打一支 API,零拼裝——薄殼原則守住
  (POST/GET/PATCH /kbdb/records、by-template、POST/GET /kbdb/templates…)
- 🔴 那半沒有偷混進來;而且它把「禁止在這裡拼裝:對照表標 🔴/◐ 而 API 沒有的能力」
  寫成註解留給下一個人——不只沒犯,還立了牌子
- npm test:20 passed

⚠️ 沒有自己驗的(是它的回報,不是總管的實測):
「八個動作在真 n8n 引擎跑過、KBDB 落庫、獨立 curl 查得回」——
那需要一台真的 n8n,總管沒複跑。**併的是程式碼形狀與單元測試,不是端到端。**

⚠️ 併 main ≠ 用戶拿得到:沒 npm publish、沒裝上任何 n8n。狀態 ◐ 已改未送達。

📍 InkStoneCo#44 第 ⑤ 環的前半|對照表:system-dev/docs/4-guides/kbdb-動作對照表.md

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 20:07:33 +08:00
uncle6me-web 87f6de7c9f feat(n8n): KBDB community node 薄殼——對照表 那半的八個動作
leo 2026-08-15「有效才正式開發」下一格:紙上考卷驗過的動作集做成真的 n8n 節點。
詞彙照定案(sheet/field/record,InkStoneCo system-dev/wiki/kbdb-詞彙表.md)。

- 動作 ↔ API 映射收斂在 nodes/Kbdb/actions.ts(純函式),20 個 vitest 釘死
  ——就是動作對照表  那半的機器版;🔴/◐ 的動作刻意不存在,呼叫丟
  「沒有這個能力」(07-thin-shell §3.1:API 沒有的能力不拼裝)
- 對外走 cypher-executor /kbdb/* proxy(X-Arcrun-API-Key),不直連 kbdb worker
- Add a field 不掛:proxy 無 PATCH /kbdb/templates(實測 404);base 端
  PATCH /templates/:id 語意是 slots 整組覆蓋(本地實測),不是加一欄

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 19:42:53 +08:00
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
uncle6me-web f87d0e92f4 fix(migrations): 0005/0006 從來沒進過版控——被 *.sql 規則吃掉,每個用戶都收到「部署物缺」
leo 更新 leo21c 時撞到(他問「部分失敗?」):
  ✗ D1 migration: 部署物缺 kbdb/migrations/0005_credential_template.sql
  ✗ D1 migration: 部署物缺 kbdb/migrations/0006_drop_credentials_table.sql
(四顆 worker cypher/registry/kbdb/mcp 全部 ✓,失敗的只有這兩個檔)

根因不是誰忘了推:.gitignore:53 的 `*.sql` 是為了擋 D1 匯出備份(整庫全量=機敏),
但它連 migration 一起吃掉。0001-0004 還在,只因為它們在該規則之前就 commit 了
(gitignore 不影響已追蹤檔案)⇒ 0005/0006 從產生那天起就不在任何 clone 裡。

⇒ 這不是 leo 一台的事:更新指令從 Gitea 抓 main,那兩個檔不在那裡
   ⇒ **任何人裝/更新都會收到同一組失敗**,包含全新安裝。

修法照 rules/05-deploy-convention.md「WASM 來源」段已有的慣例
(`.component-builds/**/component.wasm` 就是用否定規則放行的):
  !kbdb/migrations/*.sql

範圍實測(沒開太大):
  kbdb/migrations/0005、0006      → 放行
  backup-2026.sql / kbdb/backup-x.sql / dump.sql / cypher-executor/export.sql → 仍被擋

進版控前確認過無機敏值:grep 命中的 token/secret/api_key 全是欄位名
(api_key、secret_ref)與註解;無 >=20 位英數的疑似真值。

殘項:leo21c 實查 templates 9 個、credential 不在其中 ⇒ 0005 從未套用,
那台仍停在 D38 之前(credentials 走 0002 的獨立表)。要補套需另跑一次更新。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 21:10:12 +08:00
uncle6me-web ba152bc83a chore(builds): 重編 tier2 成品——把 #105 放進執行檔(出貨路徑 A 的第 0 步)
leo 選 A(把出貨線推到能出一版含 #105 的 bundle)。查證後發現最前面還有一層:

  cypher 源碼最後動 10d150a(#105)  / 成品最後動 a24f291(更早)
  mcp    源碼最後動 10d150a(#105)  / 成品最後動 8e10f1d(更早)

⇒ #105 改了 cypher 與 mcp 兩邊源碼,但沒有重編成品。
   這正是 Arcrun#93 那道「源碼比執行檔新就停下來」的閘要擋的狀態。

用官方唯一編譯點 scripts/build-worker-artifacts.mjs(Arcrun#80 的機制,已存在)
重編五顆,全部 5/5:
  arcrun-cypher-executor  571KB  source=10d150ac
  arcrun-kbdb             146KB  source=10d150ac
  arcrun-mcp             1152KB  source=10d150ac
  arcrun-http-request      78KB  source=1e85dfb4(未變)
  arcrun-code             150KB  source=621cb8d9(未變)

交叉驗證(不只信它自記的 commit):/portal/data/ 這條 #105 才有的路徑
在 arcrun-mcp 成品裡出現 8 次。

下一步(等 leo 解閘):把修好的引擎部署到 geek6688 當出貨機
(ARCRUN_SHIP_BASE 可覆寫,預設是 leo21c——arcrun-rag#79 要搬離的正是這個),
再從那台跑出貨線,第 17 站 purge 的 wait 節點才有帶修法的引擎可跑。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 20:03:30 +08:00
Leo 89b80ff90e Merge PR #105: MCP 用登入者的身分查詢,不再去找服務內部金鑰
總管複驗(不聽自述,自己重跑,並與 base a24f291 逐條比對):
  mcp     tsc 乾淨;vitest 113/113 綠
  cypher  14 紅與 base 逐字元相同(diff 無輸出);passed 386→400,新增 14 條全過
  kbdb    5 紅與 base 相同
  安全邊界 11 條全綠(跨租戶 404/越庫 404/owner_id 由 server 定死)

殘項(已記,不擋併):
  · kbdb 的 owner_id 欄位改動沒有新測試守著(213 總數未變)
  · 工作流面仍靠 MCP_OWNER_NAMESPACE || leo 那個巧合,本 PR 刻意沒動(拔了 arcrun_* 會全面失效)
  · 端到端  未驗:要部署到 leo21c,那道閘要 leo 親手解
2026-08-12 11:42:03 +00:00
uncle6me-web 10d150ac2b fix(mcp): MCP 用登入者的身分查詢,不再去找一把服務內部金鑰
leo 2026-08-12:「人類進 Portal 輸入帳密表示你是主人,可以查到你權限所有東西;
AI 透過輸入帳密的 MCP 查詢表示是授權的 AI,可以查到主人允許查的任何東西。」
「掛上 MCP 並輸入帳密,那個動作本身就是授權」⇒ 下游不得再要求第二次認證。

病根(不是金鑰沒同步,是身分沒接住):
  oauth/routes.ts 驗完 Portal 帳密只留下 `loginOk = res.ok` 一個布林值,身分當場丟棄,
  namespace 改從 `MCP_OWNER_NAMESPACE || "leo"` 拿。於是查詢時手上沒有身分可帶,
  只好用 KBDB_INTERNAL_TOKEN 直打 KBDB——那條路繞過 portal 所有庫過濾,
  而且不管誰登入都看到同一格、看到全部。CLI 也從不注入 MCP_OWNER_NAMESPACE,
  所以那個 "leo" 預設值是每台實例的實際行為,不是理論上的邊角。

修法(走既有那條路,不發明新的):
1. 接住身分:/authorize 解析 /portal/login 回應,把 portal session token +
   display_name/role/libraries 存進 authorization code → access token。
   /portal/login 補回 session_expires_in,access_token TTL 夾成
   min(自己的 TTL, portal session TTL)——不讓「MCP 還連著、底下 session 早死」。
   cypher 回 200 但沒給 session_token(舊版)→ 不發碼,不簽一張沒有身分的 token。
2. 攜帶身分:kbdb_* 全部改走 cypher `/portal/data/*`,Authorization 帶登入者的
   session。庫過濾/租戶注入/停用即時生效全在 server 側,與人類走 portal 網頁同一道閘。
   kbdb_graph_neighbors 因此不再需要 kbdb_base(server 自己知道查哪個庫)。
   藏書地圖(含連線時注入 instructions 的那份)同樣只回有權限的庫,快取改 per-session
   分格——地圖本身就是情報,不能讓先連上的人把視野留給下一個。
3. fail-closed:舊 token 沒有身分 → 誠實要求重新連線,不偷偷退回服務金鑰那條老路。
   服務級憑據(static token / partner key)維持既有 KBDB 直連,arcrun_* 零回歸。

新增 cypher portal 資料面端點(能力長在 API,MCP 只暴露;rule 07):
  GET  /portal/data/map、/portal/data/map/:library
  GET  /portal/data/templates、POST /portal/data/templates
  GET  /portal/data/records/by-template/:t、GET /portal/data/records/:id
  POST /portal/data/records
全部:呼叫端自帶 owner_id 一律不生效;越權與不存在同回 404;寫入 owner_id 由 server 定死。

KBDB base:`GET /records/:id` 與 by-template 補回 owner_id 欄位——原本不回,
呼叫端無從判斷「這筆是不是我的」,按 id 直讀等於沒有租戶邊界。

沒動:KBDB fail-closed 閘、任何金鑰、租戶字串仍不下發給呼叫端。

驗證:
  mcp        tsc 綠;vitest 113/113 綠(改前 48 綠 29 紅)
  cypher     vitest 400 綠 / 14 紅,14 紅與 base commit a24f291 逐條相同(既有)
  kbdb       vitest 208 綠 / 5 紅,5 紅同為既有(migrations/*.sql 被 gitignore)
  端到端     ◐ 未驗:需部署到 leo21c,那道閘要 leo 親手解(見 PR)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 19:33:12 +08:00
uncle6me-web a24f2912eb chore(builds): 重編執行檔——把 wait 的修法真的放進引擎成品(Arcrun#93 的閘抓到的)
#101 併進 main 後,.worker-builds/arcrun-cypher-executor 還記著 a5e4caf,
比源碼 HEAD 少了 f1370e2(wait 搬回引擎那筆)。

⇒ 這正是 #93 那道閘存在的理由:**沒有它,這次出貨會送出一個
「引擎裡沒有 wait 修法」的成品**——leo 更新完照樣燒 CPU,而出貨線全綠。
閘寫出來的隔天就抓到一次,抓到的還是我。

arcrun-cypher-executor  source a5e4caff1370e22  sha256=8411ed59b7ad

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 15:30:26 +08:00
Leo 1791ffa497 Merge pull request '#101 等待搬回引擎——WASI 沙箱裡沒有「不花 CPU 地等」這種東西' (#103) from fix/wait-not-in-wasm-101 into main 2026-08-12 07:26:00 +00:00
Leo 296fb01247 Merge pull request 'portal 安裝完成清單拿掉「去 Google 申請 AI 金鑰」那一項(arcrun-rag#81)' (#102) from fix/portal-onboarding-drop-gemini-key-81 into main 2026-08-12 07:24:47 +00:00
uncle6me-web f1370e2275 fix(engine): 等待搬回引擎——WASI 沙箱裡沒有「不花 CPU 地等」這種東西(Arcrun#101)
leo 在 youlin stage 實測(只有 input >> wait 兩個節點):
  ms=3000 → 38.9s 後 503(1102) / ms=20000 → 34.0s / ms=30000 → 34.9s / 寫死 3000 → 34.8s
四個值同一種死法、與 ms 無關 ⇒ 病不是「等待很貴」,是「等待從來沒成功過」。

修法:wait 移進 BUILTIN_COMPONENTS,由引擎 await 一個 timer。
只花 wall-clock、不記 CPU ⇒ 等 30 秒與等 3 秒同價(皆 ≈0)。
I/O 契約沿用 component.contract.yaml,既有 workflow 的 wait 節點定義不必改。

🔴 誠實標明:原本註解斷言「Workers 時鐘在同步執行期間凍結,所以自旋永不結束」。
寫測試去證,反而被打臉——workerd 裡自旋 2553 圈後 Date.now() 就前進了。
那條假斷言已刪除(不是改鬆),完整機制降級為推測。修法不依賴它:
純 WASI 沙箱本來就沒有睡覺這個手段,會等的只有宿主。

實測:
  npx vitest run tests/wait-builtin.test.ts  → 12 passed (12)
  npx vitest run(全套)                      → 386 passed / 14 failed
                                              (14 = 動工前的既有紅燈數,未新增)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 15:15:08 +08:00
uncle6me-web d58a6e152d fix(portal): 安裝完成清單拿掉「去 Google 申請 AI 金鑰」那一項(arcrun-rag#81)
leo 2026-08-12:「已經改用 workers AI,刪掉。」

那一項長在**安裝完成清單裡而且是勾選項** ⇒ 使用者會以為不做這步就沒裝完,
而它要人離開流程、去第三方網站申請帳號、把金鑰貼進表單
——**這是整條安裝路徑上最重的一個動作,而它現在是白做的**。

連帶:步數不再寫死「還差 2 步」,改成由剩下的項目算出來
(少一項卻還寫 2,是另一種說謊)。
2026-08-12 14:44:38 +08:00
uncle6me-web 793a94ecb5 chore(worker-builds): 重編——#100 的修法要進執行檔才會送到用戶那台
cypher-executor source 525faaf → a5e4caf(sha 49d59597 → d1765930)。
今天第三次在同一件事上被提醒:改完源碼沒重編,出貨與安裝送出去的還是舊的。
2026-08-12 13:36:39 +08:00
uncle6me-web cbeddf7535 merge: 讀不到就說讀不到——總圖不再把「讀不到」畫成「你沒有」(Arcrun#100)
總管審過並自己跑了驗證:
  cypher-executor 374 pass(原 357,+17 條),14 個失敗與 main 基準線完全相同
  反向驗證:拿掉帶認證那一行 ⇒ 14 → 17 failed;還原 ⇒ 回到 14

它挖到兩件我沒交辦、而且比原題更嚴重的:
  · /triplets/stats 的 total 是**分頁長度不是總數**(預設 limit=100)
    ⇒ 只修 401 的話畫面會從「0」變成「100」——一樣是假的
  · 數 entries 原本不帶 owner 過濾 ⇒ **會混到別的租戶**(實測 459,137 vs leo 的 458,732)
2026-08-12 13:36:17 +08:00
uncle6me-web d7c6bd0680 merge: 更新不再照名字找資源——已部署 worker 綁著什麼就是什麼(Arcrun#97)
總管審過並自己跑了驗證(那條線被權限閘擋住,沒能執行):
  新測試 20 條全過(含四種「說不準就停手」情境)|CLI 全套 38 pass / 0 fail
  反向驗證:把「照名字 ensure」原語加回去 ⇒ 紅線測試當場變紅(0 pass / 1 fail)
  重編 dist:舊的 ensureKvNamespace/ensureD1Database 在產物裡 0 個

它做的比交辦的多:加了兩條紅線測試(cf-api 不得再提供「找不到同名就順手建一顆」的原語、
只有 resource-resolver 能決定要不要建),以及一條我沒想到的——
**部署出去的 toml 不得殘留官方帳號的資源 id**(自架寫進官方庫=跨租戶外洩)。

誠實標記:驗證是本機模擬(假 CF API),**沒有在真機上跑過**。
2026-08-12 13:36:17 +08:00
uncle6me-web a5e4caf5cb fix(portal): 讀不到就說讀不到——總圖不再把「讀不到」畫成「你沒有」(Arcrun#100)
leo 2026-08-12 打開總圖看到:「0 個實體 · 0 條關聯/知識庫還沒有任何關聯——
上傳文件後 AI 會自動織網」。**而他庫裡有 1854 條三元組。**
那句話會叫他去做一件不需要做的事。

三個獨立的洞疊起來才變成那句謊:
  ① console-dashboard.ts 打 kbdb-graph-plugin 沒帶認證(同段落打 kbdb 的兩支都有帶)
  ② 那顆 worker 不在更新的部署清單裡 ⇒ token 一換它就落單
  ③ **畫面把 null 畫成 0**——後端已經誠實回 null 了,是前端把它變成謊話

為什麼一直沒被發現:graph plugin 原本身上沒有 token ⇒ 門開著 ⇒ 沒帶也進得去。
2026-08-12 輪替後它有了 token,門關上,401 才浮出來。

📍 repo:matrix/arcrun(cypher-executor/src/routes/、console-ui/public/)
📍 票:Leo/Arcrun#100
2026-08-12 13:33:53 +08:00
uncle6me-web 45a546a686 fix(cli): 更新不再照名字找資源——已部署 worker 綁著什麼就是什麼(Arcrun#97)
病根:更新會「確保」它需要的資源存在,而它是**照名字找**的。
使用者的資源是安裝器建的(`arcrun-rag-<x>-kv-webhooks`),更新找的是 `WEBHOOKS`
⇒ 找不到 ⇒ 新建一顆空的並綁上去。

2026-08-12 實撞(leo21c):一次例行更新後
  KV 9 顆 → 18 顆、D1 1 顆 → 2 顆,worker 全綁到新建的空的
  ⇒ 工作流一支都看不到、portal 登出、總圖空的、80 把 recipe 解不出來
  leo 原話:「leo21c 是掛掉的」。資料沒掉,但從他的角度就是東西全不見了。

修法方向:**已經部署上去的 worker 綁著什麼,那就是事實**——
名字是使用者那側的事,不是更新指令可以決定的。

📍 repo:matrix/arcrun(cli/)|📍 票:Leo/Arcrun#97
2026-08-12 13:30:17 +08:00
uncle6me-web e69d6bbc03 chore(worker-builds): 重編——kbdb 的跳脫修法要進執行檔才算數(Arcrun#94)
今天已經在這件事上被咬過一次(8e10f1d):源碼改了、執行檔沒重編,
出貨會全綠地送出舊的。這次併完就重編,不留同一個坑。

kbdb source 525faaf → c497ec4(sha256 7bc66656 → ffb8d434)。
其餘四顆源碼沒動、位元組也沒動。
2026-08-12 12:22:10 +08:00
uncle6me-web b302c03ea8 merge: 搜尋框打的 % 和 _ 是字,不是萬用字元(Arcrun#94)
總管逐筆審過兩筆,並自己跑了測試(那條線被權限閘擋住沒能執行):
  前後對照  查 100% 改前撈回「共有 100 個待辦項目」→ 改後只回真的含 100% 的
            查 owner_id 改前連 ownerXid 也中 → 改後只回字面命中
            單打 % 或 _ 改前整個庫都回來 → 改後只回字面命中
  反向驗證  拿掉跳脫 → 8 failed(紅的正是病徵本身)
            拿掉 ESCAPE → 5 failed(含既有 search-long-query 兩條)
  既有測試  18 檔 197 pass → 19 檔 213 pass

它連帶處理了一個我沒想到的:跳脫會讓 pattern 變長,所以長度上限改用「跳脫後」的
長度算——否則打 48 個 % 會產生 98 bytes 的 pattern,退回 08-03 修掉的那個 HTTP 500。

誠實標記:只在本機真 SQLite 上驗過,沒對任何線上實例跑過。
同款漏洞另有兩處未動(library-map.ts:169、library-backfill.ts:75-76,吃維運端前綴參數)。
2026-08-12 12:21:48 +08:00
uncle6me-web c497ec418e test(kbdb): 用真 SQLite 釘住 #94 的前後對照與跳脫邊界
只驗 SQL 形狀不算數——「% 被當成萬用字元」這件事,只有真的跑一次 LIKE 才看得見。
用 node:sqlite(與 library-map/embed-backfill 同款治具)跑真 SQL,每組驗收同時跑
「舊寫法」與「現行寫法」,讓前後對照直接長在測試裡。

守五件事:
  ① 前後對照   —— 100% / owner_id / 單打符號,改前撈回不相干的、改後只回字面命中
  ② 跳脫邊界   —— `\` 不跳脫會漏掉真正的 `C:\`,也會讓 `_` 的跳脫失效、萬用字元漏回來
  ③ ESCAPE 齊全 —— 沒有任何 `content LIKE ?` 是裸的(漏一個就等於那條路沒修)
  ④ 不退化     —— 不含 % _ \ 的查詢 pattern 逐字不變,最熱路徑仍是單一 LIKE
  ⑤ byte 預算  —— 滿是 % 的長查詢仍在 D1 的 50 bytes 上限內(承 2026-08-03 的 500 修復)

反向驗證(把修法拿掉,兩半分別驗,兩半都是承重的):
  拿掉跳脫      → 8 failed / 204 passed,紅的正是病徵本身
  拿掉 ESCAPE   → 5 failed / 207 passed(含既有 search-long-query 2 條)
  復原後        → 19 檔 213 pass(基線 18 檔 197 pass)
2026-08-12 11:59:36 +08:00
uncle6me-web 6985bf4850 fix(kbdb): 搜尋框打的 % 和 _ 是字,不是萬用字元(Arcrun#94)
使用者在搜尋框打 `%` 或 `_`,搜出來一堆跟他打的字無關的東西。

病根:pattern 一直是 `'%' + 使用者輸入 + '%'` 直接內插,而 SQLite 的 LIKE 有
兩個萬用字元(`%` 任意長度、`_` 任意一字)且**沒有預設跳脫字元**——不寫
ESCAPE 就沒有任何辦法表示「字面上的 %」。所以他打的符號被當成 pattern 語法:

    `100%`     → `%100%%`  → 「100」後面接什麼都算 ⇒ 撈回一堆不相干的
    `owner_id` → `%owner_id%` → `_` 匹配任一字元 ⇒ ownerXid 也中
    單打 `%`/`_` → `%%%`/`%_%` → 整個庫都回來

舊病,不是 08-10 斷詞(#84)引進的:pattern 從來就是這樣拼的。之前關鍵字搜尋
幾乎恆為 0 命中,這個洞被那個洞蓋住;斷詞讓搜尋真的會回東西之後才浮出來。
斷詞那段一個字都沒動。

修法:pattern 產生點全部收斂到兩支 helper——
  · escapeLikeLiteral():跳脫 `%` `_` `\` 三個字元
  · CONTENT_LIKE 常數:每個 `content LIKE ?` 一律帶 `ESCAPE '\'`

為什麼跳脫字元本身(`\`)也要處理:宣告 ESCAPE 之後 `\` 就變成 pattern 裡有
意義的字元,而且它會把後面那個字吃掉、且不報錯——打 `C:\` 會變成去找 `C:%`
(真正的 `C:\` 反而漏掉);只跳脫 %/_ 而漏掉 `\`,打 `C:\_temp` 時 `\\` 先被
讀成字面 `\`、後面的 `_` 變回萬用字元 ⇒ 撈到 `C:\Xtemp`。三個是一組的。
`[` `]` `?` `*` 不需要跳脫(那是 GLOB/別的方言,LIKE 不吃),多跳只會白白吃掉
pattern 的 byte 預算。

連帶:跳脫會變長(`%`→`\%`),所以 50 bytes 上限改用「跳脫後」的長度算
(likeBytes),否則打 48 個 `%` 會產生 98 bytes 的 pattern ⇒ 退回 2026-08-03
修掉的那個 HTTP 500。不含這三個字元的查詢 likeBytes ≡ utf8Len ⇒ 既有查詢逐字不變。

search-long-query.test.ts 兩條「逐字比對謂詞字串」的斷言跟著新字串更新——改的是
比對用的常數、不是放寬檢查(仍逐字相等比對),pattern 本身一個字都沒變。

沒有引進第三種搜尋機制,沒有動資料層(三表不變),沒有動斷詞。
2026-08-12 11:59:36 +08:00
uncle6me-web 8e10f1d83e chore(worker-builds): 重編官方成品——kbdb/cypher-executor 的修法之前只在源碼裡
為什麼要有這一筆:`.worker-builds/` 是**出貨與安裝真正拿去部署的執行檔**,
而它記的 source_commit 在剛併完 #85/#88 之後對不上源碼:

    arcrun-cypher-executor  成品記的 797e7f7  源碼已經是 525faaf  ⚠️
    arcrun-kbdb             成品記的 a7e23ba  源碼已經是 3eb8b31  ⚠️

⇒ 這時候去出貨或 `init`,送出去的是**舊的執行檔**,測試全綠也沒用
(wiki/mistakes.md「修的東西在執行檔裡,改完源碼+測試綠 ≠ 送到用戶手上」)。

🔴 build-bundles.mjs 的「落後閘」抓不到這個:它比的是「.worker-builds 這個目錄有沒有
落後 main 的 commit」,不是「成品記的 source_commit 有沒有落後那顆 worker 的源碼」。
這次是人工比對才發現的。修這道閘另開票。

重編後:cypher-executor source=525faaf、kbdb source=3eb8b31,兩顆的新程式碼都在產物裡
(kbdb 找得到 embed_backfill_usage/backfillEntryLibraryTags,cypher 找得到 builtin 那條路)。

誠實標記:另外三顆(code/http_request/mcp)源碼沒動,位元組卻變了——是 esbuild
版本漂移換了它產生的 helper(`__esm` 多了一段 try/catch)。這正是 #77 記的
「build 不可跨機器重現」那個結構性斷點的實例。
2026-08-12 09:50:50 +08:00
uncle6me-web 3eb8b31f2b merge: 補算向量的節奏、額度、世代核對+標庫共用同一顆閘(Arcrun#85/D68/D69)
總管逐筆審過 1d6dde4/674e1b4/37e13fc:全部落在 kbdb/,沒有新表(額度用量寄生在
既有 entries 表單一列,D38)、沒有動實例。標庫與時間分層共用一套 SelectionCriteria
與同一顆每日 D1 額度計數器——兩者若各記各的,其中一個會把另一個的閘繞過去。
實測:kbdb 全套 196 pass / 0 fail(含反向驗證:拿掉 cap 那個 case 會變紅)。

紅線仍在:這只是併進 main,還沒部署到任何實例。
2026-08-12 09:40:30 +08:00
uncle6me-web 8cee9c9f76 merge: /cypher/search 不再誤報執行期原生零件 not_found(Arcrun#88)
總管逐筆審過 525faaf:改動只在 cypher-executor 的 search-nodes.ts(查 registry 前先比對
執行期白名單)與 component-loader.ts(把既有三份白名單的聯集匯出),沒有新清單、
沒有掃目錄灌死碼。實測基準線:本分支 357 pass / 14 fail,main 350 pass / 14 fail
——同樣的 14 個既有失敗(portal-data 等 5 檔),新增的 7 個全綠。
2026-08-12 09:40:18 +08:00
uncle6me-web 7e3ca4c1a1 Revert "WIP(kbdb): 語意搜尋零命中的排查——⚠️ 被總管中途叫停,未完成驗證"
This reverts commit af3edff856.
2026-08-11 23:18:38 +08:00
uncle6me-web af3edff856 WIP(kbdb): 語意搜尋零命中的排查——⚠️ 被總管中途叫停,未完成驗證
leo 2026-08-11 判斷「如果是 Vectorize 沒完成就不用查了」,總管據此停線。
真因已經寫在 repo 自己的註解裡(kbdb/wrangler.toml:43-51,Arcrun#11):
metadata index 只收「建立後 upsert」的向量,既有向量須 reindex,
否則帶 owner_id filter 一律 0 命中——與實測每一格吻合
(805 筆在、關鍵字搜得到、語意 0、拿自己查自己也 0 ⇒ 不是分數門檻)。

⚠️ 這批改動是排查途中的產物,**沒有走完驗證**,不要當成可用的修法。
保留只是不讓它憑空消失(總管中斷造成,不是它做壞)。
接手的人請先讀 Arcrun#85 上的結論再決定要不要用。

真正的補救是 reindex,而 reindex 要燒 AI 額度
⇒ 卡在 Arcrun#85 的每日額度閘上線之後才能做。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 23:05:20 +08:00
uncle6me-web c5d696556e 保管:#89/#90/#91 卡在人類閘前的產物,從 session 暫存目錄搶進版控
三樣都做完並實測過,但落地的最後一步是終端機裡等人親手打字的互動閘。
它們原本只存在於某個 session 的 scratchpad——那種目錄一關就沒了。

· recipes/gitea_put_file.yaml     出貨線 7 站等它
· recipes/cf_worker_deploy_simple.yaml  ⚠️ 只適用無 bindings 的簡單情形(見 #90)
· hash-component/                 sha256/sha1/md5,已與系統原生指令逐位元核對
  (.wasm 是 1.3MB 編譯產物,不進版控,README 附重編指令)

README 寫了落地指令與各自的注意事項。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 22:38:55 +08:00
uncle6me-web 5919c6b90f fix(kbdb): 藏書地圖讀端每次都觸發重算寫 D1(Arcrun#87 止血)
liveTripletCountsByLibrary 沒有過濾 status,recomputeLibraryMap 只算
COALESCE(status,'active')='active'——兩邊判準不一致,只要庫裡有一筆
superseded triplet 就永遠判定 stale,導致每次讀地圖(GET /map、GET /map/:library、
kbdb_get_map MCP 工具)都觸發重算並新建一筆 library_map record,無止盡寫 D1,
且加劇既有的非原子 supersede 競態(kb 44 筆全 superseded、notes 兩筆同時 active
即 arcrun-rag#50 的共同根因之一)。

修法:liveTripletCountsByLibrary 的 SQL 改成 pivot 出 status 再套用與
recomputeLibraryMap 逐字一致的過濾,兩邊判準對齊。

新增迴歸測試釘住此 bug(反向驗證:跑在修前的 SQL 上會失敗,非空氣測試);
獨立用 leo21c MCP 連線連讀兩次 kbdb_get_map() 重現修前症狀
(general 庫 updated_at 從 1786457080 前進到 1786457114,中間無寫入)。

kbdb/tests/library-map.test.ts 19/19 全綠。既有殘骸(100 筆 library_map record)
未清——目前沒有可用的 DELETE 通道,待部署後另行處理。

kbdb-sql-ok:liveTripletCountsByLibrary 的 .prepare 呼叫是牆內本體
(kbdb/src/actions/),本次 checkout 開在巢狀 worktree
matrix/arcrun/.worktree-fix-87/(避免打斷另一 session 佔用中的
matrix/arcrun 主 checkout),kbdb-api-wall-guard.sh 的字面路徑比對
*matrix/arcrun/kbdb/src/* 吃不到中間多出的 worktree 目錄層,非真的繞牆。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 22:16:30 +08:00
uncle6me-web 525faaf5d0 fix(cypher-executor): /cypher/search 不再誤報執行期原生零件 not_found(Arcrun#88)
病因:/cypher/search 只查 component registry(SUBMISSIONS_KV,經 submitComponent/
index-only 才有記錄);而 component-loader.ts 能直接解析、從不查 registry 的一整類
零件(trigger_workflow/BUILTIN_COMPONENTS/LOGIC_BINDING_MAP/WASM_HTTP_RUNNER_IDS,
如 if_control/http_request/switch)從未被 submit 過。leo21c 實例實測:
GET /components/catalog 回 404「零件 catalog 不存在」,search 因此對這些零件誠實地
回「兩庫都查過沒有」——但它們其實跑得動(leo 08-11 探測工作流已證)。

修法:從 component-loader.ts 匯出 RUNTIME_NATIVE_COMPONENT_IDS(既有三份執行期
解析白名單的聯集,非新清單),search-nodes.ts 在查 registry 之前先比對,
不受 registry 是否可達/是否已 backfill 影響。真正不存在的零件仍誠實回
not_found/unknown,not_found 的分型建議與相似候選機制不變。

刻意不做:不掃描 registry/components/* 目錄當清單來源——那含已標記待刪的死碼
(km_writer/kbdb_upsert_block),07-30 曾把這類死碼誤灌進 registry;也不投資
SUBMISSIONS_KV 的 backfill 腳本——decisions-summary.md D29 已定調 SUBMISSIONS_KV
併入「KV 退休戰」,不宜再加投資。

測試:cypher-executor/tests/search-nodes-runtime-native.test.ts 7 case 全綠,
複現 registry unreachable/registry 可達但目錄空(leo21c 實例的真實症狀)兩種情境。
全 suite 迴歸:357 pass(較修前 350 pass 多 7 個新測試),既有 14 個失敗與修前
數量、內容完全相同(pre-existing,與本次改動無關)。
2026-08-11 21:51:21 +08:00
uncle6me-web 37e13fc9bf merge: 併入 main 最新(b6ef0f0 三元組 library 補標 proxy+測試)保持分支不落後 2026-08-11 18:46:03 +08:00
uncle6me-web 674e1b4fa2 fix(kbdb): D69 節流世代核對+標庫共用 D1 額度,補「挑哪一批」的統一 SelectionCriteria(Arcrun#85)
leo 逐行複核 fix/embed-backfill-d68 後點出的破口+二次裁決(票上全文見 Leo/Arcrun#85):

一、向量化優先序(今天寫的立刻/本週在跑的先跑/有查詢紀錄的庫優先/半年前慢慢跑)表達
不出來——策略要能從外面(工作流)指定,不能焊死在資料層。新增 embed.ts 的
`SelectionCriteria`(owner_id/source/library/since/until),backfillEmbeddings 與
reconcileEmbedGeneration 共用同一套形狀;「按庫」那一層現在有資料可用即可運作(見下)。

二、世代核對(reconcileEmbedGeneration)不打 AI 但逐筆寫 D1,47 萬筆候選 ≈ 4.7 倍 D1
100,000 rows/日免費額度,先前零保護。新增 actions/maintenance-quota.ts(單一 entries
列/日的共用計數器,精神同 execution-log.ts/embed.ts 既有慣例,不新增表)。

三、leo 二度裁決:「標庫」與「時間分層」其實是一件事,判定標準要從第一天同時容納兩者,
不能先做一半再回頭改。新增 actions/library-backfill.ts 的 backfillEntryLibraryTags——
呼叫端(ingest/daemon/Arcrun#87)決定要貼哪個庫、用 page_names(Gitea 原稿卡名精準
點名,leo 定案的正解)或 source_prefix/page_name_prefix 過渡 fallback 篩選候選,base
只負責安全、節流地寫入。owner_id 刻意必填(leo 點出「補錯 owner 等於白做」——實查卡片
掛在 owner_id=bfezv28v,換成 'leo' 查卻是空的)。

D69:reconcile 與標庫 backfill 共用同一顆「今天還剩多少 D1 寫入額度」計數器(不共用的話
其中一個會把另一個的閘繞過去);新增 POST /entries/backfill-library + GET .../status,
擴充 POST /embed/backfill 與 /embed/reconcile 吃 library/since/until 參數。

測試:92 → 39 個新增/擴充案例覆蓋 since/until/library 篩選、reconcile 額度真的擋
(含「拿掉 cap 會變紅」反向驗證)、標庫 backfill 冪等/owner_id 必填/page_names 精準比對、
以及兩個操作共用同一顆額度計數器的跨模組驗證(雙向:先 reconcile 耗盡再標庫、反之亦然)。
kbdb 全套 192 個測試綠燈,tsc --noEmit 除既有 auth.test.ts 舊缺陷外無新增錯誤。

紅線:未併 main、未部署、未動任何實例的 is_embedded 旗標(只在本地 SQLite 測試治具跑過)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 18:39:55 +08:00
uncle6me-web b6ef0f07dc fix(kbdb): 開通 PATCH /kbdb/records/:id proxy + 補三元組 library 補標測試
leo 2026-08-11:貼 wiki 卡「這裏就有 3 個三元組,如果三元組是 0 一定是有錯」——
三元組沒有不見(新式 1,633 筆/舊式 636 筆都還在),問題是地圖看不到:新式三元組
只有 171 筆填了 library slot,其餘因寫入時 template 還沒有該 slot 而被靜默丟棄
(record-crud.ts createRecord 只替「已宣告」的 slot 建值,不是替 caller 傳的
values 建值——順序錯了值就消失,不報錯)。

本次改動:
- cypher-executor/src/routes/kbdb-proxy.ts:補 PATCH /kbdb/records/:recordId。
  基本盤(kbdb/src/routes/records.ts)早有這個端點(mira-dissolve T2.1 的
  updateRecord),但這條 proxy 之前只轉發 GET/POST /kbdb/records,外部(工作流/
  CLI/補標腳本)打不到——能力在,通道沒開,補標三元組只能繞去改表(違 D38)。
  純轉發、無業務邏輯,比照既有 PATCH /kbdb/entries/:id 慣例。
- kbdb/tests/triplet-library-backfill.test.ts:真 node:sqlite 驗三件事——
  ①源頭順序(ensure-slot 必須在 write 之前,事後補救不了已寫的那筆,重現+
  驗證正解)②存量補標(地圖輸出前後對照)③不會重複做(同批跑兩次,第二次
  touch 0 筆)。順手記錄一個相關但不在本次範圍的小落差:liveTripletCountsByLibrary
  的 'general' fallback 桶與 recomputeLibraryMap 的精確比對語意沒對齊。
- cypher-executor/tests/kbdb-records-patch-proxy.test.ts:新端點的租戶閘/
  參數驗證/轉發/404 透傳。

全數綠燈:kbdb 181/181、cypher-executor 新增 8 案全過(既有 14 案失敗為
pre-existing,已用 git stash 比對確認與本次改動無關)。

kbdb-graph-plugin(實際的三元組寫入端 createTriplet/ingestEnvelope)需要同款
修正(library 補進 TRIPLET_SLOTS + 從 source_uri 推導),但該 repo 現行
0 份 active SDD,其 sdd-guard 明確要求人決定要不要開啟——本次不越界代為決定,
留在報告中交回。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 16:48:31 +08:00
uncle6me-web 1d6dde4a01 fix(kbdb/embed): D68 補算向量照新到舊排序+每日額度上限+修復舊世代 is_embedded 誤判
leo 2026-08-11 拍板(D68,system-dev/wiki/decisions-summary.md):補算向量要照時間
由新到舊、且每天有額度上限,不能一次把 Workers AI 每日免費 10,000 neurons 燒光
(與萃取共用同一份額度,見 ops-facts.md)。對應 Leo/Arcrun#85 列出的三個缺口:
① 補算是由舊到新(ORDER BY created_at ASC)② 沒有每日額度上限 ③ 沒有任何自動觸發。

改動:
- backfillEmbeddings:ORDER BY created_at DESC(新到舊),並在打 AI 前依
  env.EMBED_BACKFILL_DAILY_LIMIT(未設用推導出的預設值 1800,算式見 embed.ts 註解)
  截斷候選、額度用完即停手不再打 AI。額度用量存在 entries 表單一列
  (entry_type='embed_backfill_usage',UTC 日期切),不新增表(D38)。
- embedOnWrite / backfillEmbeddings 成功嵌入後在既有 content_hash 欄位蓋上
  現行模型名(世代戳記),修復 leo21c 資料還原案:從備份整批灌回的列帶著對已退役
  768 維索引的 is_embedded=1,現行 1024 維索引永遠不會補到它們。
- 新增 reconcileEmbedGeneration + POST /embed/reconcile:對 is_embedded=1 但
  content_hash 非現行世代的候選,問 Vectorize.getByIds 是否真的在現行 index——
  在→只補 content_hash 不打 AI;不在→重置 is_embedded=0 交回正常 backfill 佇列。
- 新增 kbdb/tests/embed-backfill.test.ts(改走真 SQLite,比舊版手刻假 DB 更硬):
  14 個測試涵蓋新到舊排序、額度真的擋(含「拿掉 cap 會變紅」的反向驗證)、
  世代核對端到端(reconcile → 重置 → backfill 真的補回來)、既有行為不迴歸。

現況誠實回報:目前沒有任何東西會自動觸發補算(無 cron/scheduled handler)——
唯一的「自動」路徑是 entries.ts 的語意搜尋回 0 命中時 fire-and-forget 觸發一次
(既有行為,本次未改動),仍需人或 CC 主動呼叫 /embed/backfill 或掛排程。

紅線:未動 leo 正式實例 leo21c;未動資料層形狀(三表不變,仍走既有 content_hash
bookkeeping 欄);未 push main,本 commit 在獨立分支 fix/embed-backfill-d68。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 16:42:54 +08:00
uncle6me-web 507620e313 merge: Arcrun 統一編譯零件,成品放固定位置 .worker-builds/(Arcrun#80)
總管 2026-08-11 審過後併入:
- 照本 repo 既有慣例(.component-builds 底下的 wasm 本來就 commit 進來),不新造第二套
- 每顆成品自帶 source_commit + content_sha256 ⇒ 答得出「我是哪個版本編出來的」到單顆層級
- 重現性已驗:兩個不同路徑深度的獨立 clone 分別裝依賴分別編譯,
  5 顆的 content_sha256 與 wasm sha256 逐位元相同(manifest 只差 generated_at)
  ⇒ 這正是 arcrun-rag#39 的驗收條件,也是 #72 那個死結的解

待觀察(不擋本次):每次編譯會 commit 數萬行成品進原始碼 repo,長期會脹。
2026-08-11 13:54:19 +08:00
Leo 1e94f8451e Arcrun#80: commit tier2 worker 官方編譯成品到固定位置 .worker-builds/
從本 repo 的 cypher-executor / kbdb / .component-builds/http_request /
registry/components/code / mcp 五個原始碼目錄,用 scripts/build-worker-artifacts.mjs
編出 5 顆成品,commit 進 repo(與既有 .component-builds/*/component.wasm 同一套
「固定位置、任何人直接拿」慣例)。

每顆 manifest 條目自帶 source_commit(該原始碼目錄最後改動的 commit)與
content_sha256,答得出「我是哪個版本的原始碼編出來的」到單顆層級。

重現性已驗證(見 commit 說明外的驗證記錄):同一個 commit 在兩個獨立 clone
(不同磁碟路徑深度,刻意模擬雲端 vs 地端的目錄結構差異)分別裝依賴、分別編譯,
5 顆的 content_sha256 與 wasm module sha256 逐位元相同,manifest.json 唯一差異
是 generated_at 時間戳。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 13:45:33 +08:00
Leo dcb6ad693b fix(build-worker-artifacts): dirty 判斷排除自己的輸出目錄
.worker-builds/ 是本腳本的輸出,在成品寫出、commit 之前永遠是 untracked——
拿它判斷「原始碼乾不乾淨」是自己把自己判成髒的假陽性。git status pathspec
排除 .worker-builds 後才是「原始碼有沒有未 commit 變更」的真實訊號。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 13:45:33 +08:00
Leo 3b0238bb28 Arcrun#80: 新增 tier2 worker 官方編譯腳本(成品固定位置的地基)
背景:安裝器(arcrun-rag)過去自己對 Arcrun 原始碼跑 esbuild,導致同一份原始碼在
不同機器編出不同位元組(見 arcrun-rag changelog 1.4.33 段、arcrun-rag#72):
① esbuild 把入口路徑寫進產物內部註解,該路徑預設相依於執行時 cwd
② 各 worker 目錄用不同套件管理器裝 node_modules,夾帶不同版本間接依賴

本腳本解法:
- absWorkingDir 固定為「本腳本自己算出的 repo 根目錄」,entry 一律用相對路徑餵給
  esbuild——不管 clone 放在磁碟哪個絕對路徑,esbuild 內部產生的相對路徑字串相同
- 不自己跑 install,強制要求呼叫者先用該目錄既有 lockfile 裝好依賴(pnpm frozen /
  npm ci),避免「install 方式不同 → 依賴版本不同 → 位元組不同」
- 每顆 worker 的 manifest entry 自帶 source_commit(該目錄最後改動的 commit),
  答到單顆層級,不是整包一個 source 欄位

涵蓋 5 顆 tier2 worker(與 arcrun-rag bundle-components.mjs CORE_COMPONENTS 對齊):
cypher-executor / kbdb / http_request / code / mcp。

scripts/ 自帶 package.json + pnpm-lock.yaml 鎖 esbuild 版本(0.24.0)——
工具版本本身也是重現性的輸入之一。

驗證:本地跑通 5/5 build;byte-identical 重現性驗證見後續 commit(兩個獨立 clone
比對 sha256)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 13:45:33 +08:00
uncle6me-web 788295ed71 merge: 忘記密碼的錯誤訊息改白話+健康檢查回報寄信有沒有接上(arcrun-rag#38/#69/#25)
總管 2026-08-11 審過後併入:
- 拿掉行話「實例」;拿掉「請管理員直接幫你改密碼」(單人使用者就是管理員=死路,#25 同病)
- 改成給一條他自己走得完的路:重新跑一次安裝/更新
- /health 增回報 mail_relay_configured,讓安裝器判斷得出這台要不要重推
2026-08-11 13:24:34 +08:00
uncle6me-web 797e7f751c fix(portal): 忘記密碼斷點——安裝器從沒注入 PORTAL_MAIL_RELAY_BASE(arcrun-rag#38/#69/#25)
leo 被鎖在自己的系統外面:1.4.35 已出 prod,「忘記密碼」畫面在線上,但按下去回
503「這台實例還沒有設定寄信服務」。根因在 products/arcrun-rag 的安裝器——
`grep -rn PORTAL_MAIL_RELAY installer/` 是零命中,即使 landing 那半(郵差)
D62 已經寫好。這個 repo 這邊改兩件配合修:

- /health 多回 `mail_relay_configured`(布林,不洩漏網址)——安裝器判斷「要不要
  重推」只比 bundle_version,但這個 var 是這次才第一次被注入,跟版本號無關;
  純比版本號的話,已經在最新版的實例(如 leo 那台)永遠不會因為「按更新」而
  重推,這個 var 永遠補不進去。安裝器那邊會讀這個欄位決定要不要強制重推。
- portal.ts 的 503 訊息改白話(不提「實例」)+給一條使用者自己走得完的路
  (重新執行安裝/更新),不再說「請管理員幫你改密碼」——單人使用者的管理員
  就是他自己,那是死路(同源病灶:arcrun-rag#25)。

安裝器那半的修法(PORTAL_MAIL_RELAY_BASE 注入+stale 判斷)在
products/arcrun-rag 同批修(installer/oauth-prototype/worker.js)。

測試:cypher-executor `npx vitest run tests/portal-auth.test.ts tests/health.test.ts`
37/37 綠(35+2,既有測試無回歸;本次未新增測試——改動是訊息文案與健康檢查欄位,
行為已由既有測試覆蓋的路徑保護)。
2026-08-11 12:59:57 +08:00
uncle6me-web d1c44a5878 merge: 忘記密碼與改密碼合成同一個機制(D62,arcrun-rag#69/#25)
總管 2026-08-11 逐筆審過後併入:
- 三筆:8d49d88 機制/9a29eb5 瀏覽器實測抓到的兩個前端缺陷/c76e10d 回歸測試
- #66 的修正 417d69c 在本分支血緣裡,併入不會弄丟已出貨的行為
- 試併零衝突;併入後 cypher-executor 測試 360 項、通過 346
  (14 個失敗與 main 併入前完全相同,是既有紅燈,非本次造成)
- 併入後多出的 8 項全數通過=D62 的回歸測試

⚠️ 併入 main ≠ 出貨。要送到用戶手上仍需走出貨管線並由 leo 解保險。
2026-08-11 11:14:13 +08:00
uncle6me-web a7e23badf2 WIP(kbdb): 關鍵字搜尋改成斷詞——⚠️ 未完成驗證,agent 被中途停止
【為什麼要這個】leo 2026-08-10:「沒有 MCP 你就是瞎的」。
總管實測(有對照組)證明搜尋對 AI 結構上不可用:

  kbdb_search("Gemini 逃生口")  → 0 筆      ← 兩個詞從不相鄰
  kbdb_search("Gemini")         → 864 行    ← 知識明明在
  kbdb_search("local arcrun")   → 5 筆      ← 這兩字在內容裡剛好相鄰

⇒ 現況是拿整個查詢字串去 LIKE,不拆詞。而 AI 問的永遠是問句 ⇒ 永遠回 0。

【這筆做了什麼】CJK/英數分段切詞、CJK 與 ASCII 停用詞、
單詞份量上限(避免長詞獨大)、相對分數截斷;附 search-tokenize.test.ts。

🔴 【誠實聲明】**這筆沒有驗完就被停了**(leo 要求全體停工專心 MCP 進安裝清單)。
未做:前後對照的實際輸出、回歸(原本查得到的不能變查不到)、相關性不崩壞。
**接手的人不要當成已驗證的東西**,先跑那三項再說。

分支保留於 gitea,隨時可接回去。相關:Leo/mira#4 總管留言。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
(cherry picked from commit 763de199b5)
2026-08-11 10:02:09 +08:00
Leo eebb691426 merge: 傳播空窗期不再銷毀 session(arcrun-rag#66)
leo 2026-08-10 本人被這個鎖在 stage 外面,並靠修好後的重設流程走回來——
這是「收的人自己驗到」,不是我們自己測過。

病:認證的家是 Workers Secret,改它會產生 worker 新版本,既有 isolate 讀到舊 env。
舊碼在那個空窗裡讀不到 record 就直接 SESSIONS_KV.delete()
⇒ 帶著完全有效的 token,登入狀態被當場銷毀,secret 鋪開也回不來。
#55 補的加速器重試只加在登入路徑,session 驗證這道門沒有。

實測空窗約 45 秒(不是 #55 記的 ≥15 秒):
改完密碼後舊密碼一路到 t+44.1s 仍登得進去,t+47.1s 才開始 401。

三段修法:
1. 先問加速器再判定(與登入路徑同一支 hydrateFromAccelerator)
2. 永不因「讀不到」刪 session——刪是 best-effort 清潔工,清掉的卻是使用者唯一的憑據
3. 仍讀不到且在空窗 → 503 auth_store_propagating,不是 401
   (前端看到 401 就清 localStorage,後端不刪也沒用)
前端 boot() 收斂成「只有 401 才算被登出」。

驗證:stage 改密碼後同一 token 打 90 秒 → 200x30 / 401x0;瀏覽器實跑仍在站內;
tests/portal-auth.test.ts 27/27 綠。
2026-08-10 14:19:04 +00:00
Claude c76e10d314 test(portal): 補 #66 與 D62 的回歸測試(8 條,35/35 綠)
#66(三條,斷言的是「session 有沒有被刪掉」而不只是狀態碼)
- 傳播空窗(加速器 key 還在)+讀不到 record → 503 auth_store_propagating,**KV 那筆還在**
- 非空窗+讀不到 record → 401 擋下,**KV 那筆仍然還在**(刪是清潔工,清掉的卻是唯一憑據)
- session 內容本身壞掉 → 401 且**該刪**(那是確定的事實,不是暫時讀不到)
  ⇒ 前兩條在舊碼上必紅:舊碼在這兩個情況都會 SESSIONS_KV.delete()

D62(五條)
- 帶 reset_token 呼叫 /portal/password/change:**不必登入、不必現有密碼**;
  且「先刪再回」=同一條連結第二次必定 400 reset_token_invalid,KV 裡也真的沒了
- 先「看一眼」票(GET /portal/password/reset)不會消耗它
- 亂猜 / 格式不對的 token → 400
- 沒 reset_token 又沒登入 → 401(修改密碼那一格仍要身分)
- 沒設代寄服務 → /portal/password/forgot 誠實 503 mail_relay_not_configured(不假裝寄出去了)

擺放順序有註解說明:#66 那組會故意把 per-isolate overlay 灌成空的(模擬空窗),
而 overlay 是模組級全域變數不隨 test 重置,故必須排在檔案最後。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R8vF2zS2XpaZjzkC75Fjss
2026-08-10 13:55:53 +00:00
Claude 9a29eb5af6 fix(portal): 瀏覽器實測抓到的兩個前端缺陷——連結被路由吃掉、.hide 從來沒有在藏東西
兩個都是「HTTP 200 看不出來、真的開瀏覽器才會現形」的(規則五點五③)。

① `#/reset?token=…` 被路由正規化吃掉
   route() 的第一行是「raw 不在 VIEWS 就換成 HOME」,而 reset 不是站內的 view
   ⇒ 使用者點信裡的連結,網址被改寫成 `#/search`、修改密碼畫面根本沒出現,
     **而且 token 一起被丟掉**——連結一次有效,等於這條連結就這樣廢了。
   改:route() 開頭先看有沒有 reset token,有就走 showReset 並 return。
   (boot() 那條只顧得到「整頁重新載入」;hash 變動這條路以前沒人守。)

② `.hide` 一直只有三條有 scope 的規則(#tabbar .tab / #sidenav .nav / .modebtn)
   沒有通用的 `.hide { display:none }` ⇒ 任何其他元素掛上 class="hide" **完全沒被藏起來**,
   看起來有藏、其實沒藏。實測畫面:登入頁還沒按「忘記密碼」就已經露出 email 欄與「寄出連結」;
   連結模式下「現有密碼」那一格也照樣顯示(D62 的「差別只有一格」當場破功)。
   補通用規則,帶 !important 以蓋過 inline display(#forgot-box 有)。
   既有三種用法意圖一致(都是要藏),行為不變。

stage 瀏覽器實測(youlin,playwright):忘記密碼 → 取連結 → 點進去 →
不輸入現有密碼設新密碼 → 自動回登入頁 → 用新密碼登入成功 → 進站 →
設定頁同一份表單(現有密碼那一格回來)改密碼 → **沒有被踢出去、可以繼續操作**。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R8vF2zS2XpaZjzkC75Fjss
2026-08-10 13:51:41 +00:00
Claude 8d49d883c0 feat(portal): 改密碼與忘記密碼合成同一個機制,用連結不用一次性密碼(D62)
leo 2026-08-10 拍板:「這兩個機制其實是一個機制,可以簡化。」
  修改密碼:輸入**現有的** → 輸入新的 → 覆蓋
  忘記密碼:收到**「修改密碼」連結** → **不輸入現有密碼(忽略)** → 輸入新的 → 覆蓋
⇒ 同一個畫面、同一條寫入路徑,差別只有「現有密碼」那一格。

後端(cypher-executor/src/routes/portal.ts)
- POST /portal/password/change =**那一支**。帶 reset_token 就走連結那一格(忽略 current),
  沒帶就要登入 + 正確的 current。兩條路在 writeNewPassword 之後完全相同。
  /portal/me/password 保留成**別名轉呼同一支**(不留第二份實作,兩份必然漂移)。
- POST /portal/password/forgot(公開)/GET /portal/password/reset(看票,不消耗)
- 連結的安全性(承 D50,不可退讓):**一次有效**(用掉即刪,先刪再回)、
  **會過期**(KV TTL 30 分鐘)、**與註冊辨識碼不同源**(現場 crypto 亂數,
  只活在本實例 SESSIONS_KV,與 landing SIGNUPS 那組安裝辨識碼毫無關係)。
  KV 存的是 token 的 sha256,不是 token 本身。
- 🔴 不做一次性密碼(leo:「不要發一次性密碼太麻煩」)。
- 🔴 已否決不准寫回來的三條(D50):console 密碼救援/重裝重設密碼/直接用固定辨識碼。

中央代寄(arcrun-rag landing 那半在該 repo)
leo 給的職責切法:實例產生連結、管一次性/過期;arcrun.dev 只是郵差。
必須這樣切的硬理由:用戶自己的實例**沒有 send_email binding**,根本寄不了信。
🔴 總管紅線:**絕不把整條 URL 交給郵差**——寄件網域帶 DKIM,肯收「任意 URL+任意 email」
就是一台開放的釣魚中繼,燒的是整個網域信譽、不可逆。
故只交出本實例 origin + 一張回呼票,並新增 POST /portal/password/relay-verify:
郵差**回頭打這個 origin** 問「這張票是你發的嗎」,冒用別人網域會被那台實例自己否認
⇒ 主機屬於呼叫方這件事由郵差親自確認,不是相信宣稱。
信裡的連結落點 GET /portal/password/reset-link(主機刻意=被確認過的那個 origin)。

前端(console-ui/public/portal/index.html)
- 登入頁「忘記密碼」入口,**在 portal 不在 console**(leo:「是對 portal 不是對 console,
  這樣 youlin 雖然忘記,我還是可以去 portal 忘記密碼。」)
- 拆掉登入頁原有的「用管理主控台密碼救援自己」連結與「忘記密碼請聯絡管理員」
  ——兩條都是 D50 已否決的做法(console 與 portal 是同一組帳密的兩個鑰匙孔)。
- #v-reset 殼**沒有自己的密碼欄位**:真正的表單是設定頁那唯一一份 #pw-form,
  進入連結模式時被原封不動搬過去,只切換「現有密碼」那一格顯不顯示。
  同一個表單元素、同一支送出函式 —— D62「同一個畫面」的字面落地。

驗證:tsc 與 baseline 同為 23 個既有錯誤(零新增);27/27 既有測試綠;前端 JS node --check 過。
stage 實測見交付回報。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R8vF2zS2XpaZjzkC75Fjss
2026-08-10 13:23:44 +00:00
Claude 417d69ceb3 fix(portal): 傳播空窗期不再銷毀 session——讀不到 ≠ 這個人不存在(arcrun-rag#66)
leo 2026-08-10 在 youlin stage 改完密碼被登出,之後怎麼登都回不去。
病灶是「這一瞬間讀不到」被當成「這個人不存在」,而且做的是**不可逆**的動作。

認證的家是 CF Workers Secret,改它會產生 worker 新版本,既有 isolate 讀到的
還是舊 env(#55 實測 ≥15 秒)。舊的 requirePortalUser 在那個空窗裡直接把
session 從 KV 刪掉——不是擋下讓你重試,是當場銷毀,等 secret 鋪開也回不來。
#55 補的「讀不到就再問一次加速器」只加在登入路徑(findAndVerifyUser),這道門沒有。

三處修法(缺一不可):

1. requirePortalUser 先問一次加速器再判定(與登入路徑同一招、同一支函式)
2. **永不因「讀不到」刪 session**;仍讀不到且正在傳播空窗 → 回 503
   `auth_store_propagating` 而不是 401(讀得到 record 的「已停用」照舊刪,那是確定的事實)
3. 前端 boot() 從「任何非 2xx 都 dropSession」收斂成**只有 401 才算被登出**
   ——後端不刪、前端卻自己丟掉 localStorage 的 token,症狀一模一樣

順帶修掉同一族的一個資料遺失路徑:mutateAuthStore 舊版拿「可能是舊版 env」當底稿做
read-modify-write,而 writeAuthStore 會重切分片並刪掉多出來的舊分片 ⇒ 底稿若是
「某帳號被建立之前」的版本,那個帳號會在這次寫入中被抹掉且無法還原(secret 是唯一真相源)。
改成先問加速器、再把 env 版與 overlay 版取聯集當底稿;刪除仍有效(fn() 在聯集之後才跑)。

驗證:tsc 與 baseline 同為 23 個既有錯誤(零新增);tests/portal-auth.test.ts 27/27 綠。
stage 實測見交付回報。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R8vF2zS2XpaZjzkC75Fjss
2026-08-10 13:22:34 +00:00
Leo 035e8b255b chore(mcp): stage build 標記對齊實際部署的 commit
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 09:51:36 +00:00
Leo 1c630ecfd4 docs(mcp): /health 註解誠實界定——404 只代表比本版舊,不等於舊世代
實測:leo21c 與 geek6688 的 /health 都回 404,但 geek6688 是新世代(email+password)。
原註解會讓下一個人拿 404 去判世代而誤判。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 09:51:36 +00:00
Leo a99d3e5a3e chore(mcp): 補 stage 部署設定(youlin)——升級 arcrun-mcp 終於有測試場
arcrun-mcp 不在安裝器出貨的那批裡,所以「升級某台的 arcrun-mcp」一直沒有地方先驗,
要驗只能拿 leo21c(唯一一份 47.9 萬筆知識的真身)冒險。本檔把 stage 補上。

順帶把「手動直推 arcrun-mcp 到自架帳號」的兩個坑從人的記性搬進檔案:
① 拿掉 mcp.arcrun.dev route(zone 在 uncle6,自架帳號沒有)
② OAUTH_KV 填真 id(出貨 toml 是佔位符,直推不填 → /authorize 503)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 09:51:36 +00:00
Leo 894408181f feat(mcp): GET /health — 一條 curl 看出這台是新舊世代 MCP
判斷一台實例的 arcrun-mcp 是哪一代認證,原本只能打 /authorize 剖 HTML 有幾個欄位
(ops-facts 2026-08-10 的土法)。改成誠實版本面:200+auth=portal-login =新世代;
404 =舊世代(同意頁還要那把沒人拿得到的 MCP_OWNER_SECRET ⇒ 等於接不上)。
順帶 build 標記(MCP_BUILD var)讓「這台跑的是哪一版」看得到。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 09:51:36 +00:00
uncle6me-web d022ca067b merge: kbdb DELETE 誠實回報向量刪除結果(arcrun-rag#46)
總管複核:改法與同檔 /entries/deprecate-by-library 一致(同步 await 再回應),
不動 schema、不加表(守 D38),附 84 行測試。
舊版 fire-and-forget 會把向量刪除失敗靜默吞掉——呼叫端看到「刪除成功」
但語意搜尋還留著殘影,這正是 #46 回報的症狀。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 15:50:36 +08:00
uncle6me-web 6715402bcc fix(kbdb): DELETE /entries/:id 向量刪除改同步+誠實回報(arcrun-rag#46)
舊版把向量刪除包成 fire-and-forget(waitUntil(...).catch(()=>{}))——失敗被靜默
吞掉,呼叫端(rag_takedown_direct workflow/未來的 portal 刪除 UI)永遠不知道
向量沒清乾淨。與同檔 /entries/deprecate-by-library(同步 await + 回報
vectors_deleted)的做法不一致,補齊同款誠實回報:回應加 vector_deleted
(true/false/null),D1 本體刪除不因向量失敗而被擋下。

補上這支端點原本零覆蓋的測試(entries-delete.test.ts,3 案:模組未開/
向量刪除成功/向量刪除失敗)。

隨附 live e2e 驗證(youlin stage,4 筆測試知識,含批次 3 筆):刪除後
keyword/semantic 混合檢索與 rag_chat AI 問答皆不再讀到已刪內容,控制組
(未刪的既有知識)不受影響——確認端到端「刪除即在所有查詢路徑消失」成立,
細節見 Leo/arcrun-rag#46 留言。
2026-08-10 15:41:01 +08:00
uncle6me-web c4cee35adb feat(auth): 認證與資料分離——搬動知識資料時登入不再跟著壞掉(D61 / arcrun-rag#55)
leo 2026-08-10 下令:「登入認證資料要分離⋯⋯就算只有我一個人存在單獨的 json 檔也好,
它不能被改資料庫的連結導致無法登入。」「昨天不能登入 portal,今天不能登入 mcp,
這根本就是一個問題。」

病:portal 帳號住 KBDB(owner_id = {CONSOLE_TENANT}::portal),console 管理員帳密住
SESSIONS_KV。兩者都靠 binding 指過去,重裝/遷移一定會被重新指一次 ⇒ 保險箱的鑰匙
放在保險箱裡。2026-08-09 leo 資料一個位元組都沒動,卻被鎖在門外。

修:認證搬到 CF Workers per-script Secrets(掛在 script 上,與 bindings 兩套資源,
重部不會洗掉;journeys/gemini-key-lost-on-reinstall.md 與 installer worker.js:1148 皆有實證)。
- 新增 lib/portal-auth-store.ts:自足的 JSON,讀取零網路呼叫,>4.6KB 自動溢位分片
- portal.ts 的帳號讀寫全部改走它;KBDB 只留為舊實例的回退讀路徑,登入成功順手搬過去
- console-auth.ts 的第二份認證資料同樣搬離 KV
- 不牴觸 D38:KBDB 三張核心表不增不減,本案是把東西搬出去
- 沿用 credentials.ts 既有的 putWorkerSecret/deleteWorkerSecret,不另造第二套寫入路徑(D36)

明顯失敗(把 #10「寧可明顯失敗,不要靜默錯置」套到門鎖上):
- 「這台實例讀不到任何登入資料」回 503 + code=auth_store_empty,且**不計入 5 次鎖定**
  (08-09 leo 就是被系統自己的誤判鎖了 15 分鐘)
- /console/setup 遇既有帳號改說「你剛才輸入的密碼沒有被採用」,不再只說「已設定過」
- /health 與 /console/auth-status 吐 auth_store 狀態(住哪、寫不寫得進去)

stage 實測撞到並修掉的坑:改 secret 會產生 worker 新版本,既有 isolate 讀到的還是舊 env
⇒ 「建好帳號立刻登入」有 15 秒以上 401,還被算進鎖定。加一層短 TTL 的 KV 加速器
(非真相源,只在 secret 查不到/密碼對不上時問一次),換 KV 不影響不變量。

驗收:stage(youlin)把知識資料庫換成另一顆空的 + 換租戶代號 + SESSIONS_KV 換成空的,
三樣一起換之後 portal / MCP /authorize / console 三條登入路徑仍全綠(複跑 3 次)。
對照組(舊版程式碼同樣換庫):登入回「email 或密碼錯誤」,5 次後鎖 15 分鐘。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 14:13:37 +08:00
uncle6me-web ae81d22775 fix(harness): 世代閘的 ON_TRUE 判準過時,反過來擋下正確教材
引擎自 2026-08-01 起已支援條件邊(cypher-executor/src/graph-executor.ts
case 'ON_TRUE'/'ON_FALSE'/'ON_BRANCH',VALID_EDGE_TYPES 亦已列入;31 個
cypher-executor 測試全過)。registry/skills/write_intent_workflow.md(單一
真相源)也已在同日更正為教 ON_TRUE/ON_FALSE/ON_BRANCH 是合法邊。

但 cli/scripts/check-harness-generation.mjs 的世代閘還停在舊世代判準:
只要 SKILL.md 出現正面示範的 ON_TRUE 就擋——這道閘本身才是落後的一方,
把已經寫對的教材當錯誤攔下,害乾淨 `npm run build` 必敗。

同源的過時內容還藏在三個手動維護的 harness 原始檔(非腳本產物):
CLAUDE.block.md/commands/arcrun.md/hooks/arcrun-guard.sh 都寫著
「引擎沒有條件邊」「沒有 ON_TRUE/ON_FALSE/ON_FAILURE」,一併更正。

真正不存在的邊是 ON_FAILURE(VALID_EDGE_TYPES 只有 ON_FAIL),把 mustNot
判準從 ON_TRUE 換成 ON_FAILURE,並新增 must 規則要求 ON_TRUE 必須出現,
防止教材日後又被改回「條件邊不存在」的舊世代說法。

skills/arcrun-mindset/SKILL.md 是由 registry 於建置期重建的產物
(build-harness-skill.mjs),本次改動只跑 `npm run build:harness`
重建、不手改。

驗證:故意把 SKILL.md 的 ON_FAILURE 改成正面示範,確認閘仍會擋下
(exit 1),還原後 `npm run build` 連跑兩次皆全綠且冪等(SKILL.md
md5 不變)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 11:03:43 +08:00
uncle6me-web 9740794050 docs(3-specs): 立案 Arcrun App ↔ Portal 掛載協定 v0 提案(Leo/Arcrun#82)
設計全文住在票上,本檔只留指針+影響分析,等 leo confirm。
病根實查:Portal「有哪些頁」在單檔 HTML 裡寫了四遍,出貨時整包內嵌成
單檔 worker,安裝器只會整顆換掉、無任何掛載點概念 ⇒ 加一個能力=改核心。

未 confirm 前不開新 SDD、不動功能程式碼(現行 active=workflow-discovery)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 22:02:08 +08:00
uncle6me-web 453dec60b1 fix(scripts): template 來源改指活帳號,404 不再靜默寫入垃圾內容
scripts/install.sh 與 scripts/update.sh 的更新來源指向已被 GitHub suspend 的
uncle6me-web 帳號,實測全部 404 ⇒ 自動更新靜默失效。改成與 system-dev-template
本體、以及本 repo 現行世代 system-dev/scripts/ 一致的正解:預設公開 GitHub
youlinhsieh/system-dev-template,可用 TEMPLATE_SOURCE 環境變數覆寫,不改檔。

同時補上 looks_like_error_page 判斷(沿用 template repo 已修好的寫法):curl
對 404 常回傳非空的錯誤頁內容,只判斷「非空」會把它當成正常檔案寫入且不報錯。
現在錯誤頁會被偵測、檔案不寫入、FAILED 清單於結尾列出、腳本以非零狀態碼結束。

實測:
- 修正後來源多個路徑回真實 HTTP 200(CLAUDE.md/VERSION/self-update 兩支腳本)。
- 刻意把 TEMPLATE_SOURCE 指回死帳號重跑兩支腳本:全部項目進 FAILED 清單、
  無任何檔案被錯誤頁污染、exit code 1。
- 兩支腳本 bash -n 語法檢查通過。

已知缺口(不在本次範圍,留待另決):template repo 內部檔案佈局已從
.claude/wiki/、docs/ 搬到 system-dev/ 前綴,這兩支舊世代腳本仍用舊路徑,
其中一部分檔案(wiki 範本、docs/README、SDD 範本等)在新帳號上仍 404——
現在會清楚回報失敗而不是靜默吞掉,但尚未逐一改點新路徑。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-09 20:54:01 +08:00
uncle6me-web bfc98fe41a 官網與 npm 上的 GitHub 連結指向已停用帳號,全是 404(arcrun-rag#41)
延續同一次盤查(判準:素不相識的人點進來會不會被帶到到不了的地方)。
這批比 README 更顯眼——是 arcrun.dev 官網上那顆「GitHub」鈕,以及 npm 套件頁的
Repository 欄。逐一 curl 實測全部 404:

- landing(官網三個檔五處):github.com/richblack/arcrun → 404(帳號已 suspend)
  → github.com/youlinhsieh/Arcrun(200)
  其中 integrations 頁還指 richblack/arcrun/blob/main/CONTRIBUTING.md =雙重死
  (帳號沒了,而且這個 repo 從來就只有 CONTRIBUTING-components.md)
  → youlinhsieh/Arcrun/blob/main/CONTRIBUTING-components.md(實測 200)
- cli/package.json 的 repository.url:github.com/uncle6me-web/Arcrun.git → 404
  (同樣是 suspend 掉的舊帳號)→ youlinhsieh/Arcrun。這欄會直接顯示在
  npmjs.com/package/arcrun 的 Repository 連結上,裝了 CLI 的人就是從那裡點過來。

只換字串,沒有邏輯變動;package.json 已驗證仍是合法 JSON。

⚠️ 同一批掃出但**本次未動**(behavior-affecting,另案處理):
scripts/install.sh:29 與 scripts/update.sh:26 的 template 來源仍指
raw.githubusercontent.com/uncle6me-web/… =實測 404 ⇒ 自動更新靜默失效。
這正是 agent-memory 記過「uncle6me-web 曾害 template update.sh 自動更新靜默
死亡」的同一顆雷,修在 template repo 卻沒修到 Arcrun 這份 copy。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 20:28:05 +08:00
uncle6me-web be9b92eb28 README:四個對外連結修掉,別再把點進來的人帶到死地方(arcrun-rag#41)
leo 08-09:「整個 github 的 readme 都是過時的,如果用戶真的連過來會被誤導」。
逐一 curl 實測,不靠假設:

- Arcrun RAG 連結指 git.uncle6.me/Leo/arcrun-rag = **實測 404**(那顆是 private;
  同主機的 Leo/Arcrun 反而匿名 200 ⇒ 判準是逐一實測,不是「Gitea 一律 private」)
  → 改指公開鏡像 github.com/youlinhsieh/arcrun-rag(200)
- 致謝的 @richblack → github.com/richblack **實測 404**(帳號已 suspend),
  改 @youlinhsieh(200)
- 結尾 [CONTRIBUTING.md] → 檔案不存在,線上實測 raw 404;
  repo 裡真正有的是 CONTRIBUTING-components.md(線上 200)
- 「給 AI 操盤手:開始前讀 .claude/rules/06-mindset.md」→ `.claude` 整個在公開
  排除清單裡,公開 repo 根本沒這個檔 ⇒ 對外=指了個不存在的路。改指 llms.txt
  (公開、線上 200,第 23-26 行就是那套世界觀),並說明裝 harness 後才會有 Skill。

另把 08-08 留的內部說明 HTML 註解換成對外講得通的一句話:目前沒有公開試玩站,
想看產出去看 arcrun-rag-demo-knowledge 公開鏡像(實測 200)。註解寫給自己人看,
但它躺在對外 README 的原始碼裡。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 20:25:42 +08:00
uncle6me-web 19c82df05f fix(portal): 管理員忘記密碼自救援出口(arcrun-rag#25)
唯一 admin 忘記 portal 密碼就永久卡死:requirePortalAdmin 系列端點全部要
先有 portal session 才進得去,bootstrap 又只能跑一次——登入頁只會叫他
「聯絡管理員」,而他自己就是管理員,沒有下一步。

新增 POST /portal/admin/recover-password,複用 bootstrap 已在用的 console
owner session 當人閘(與 portal 密碼完全獨立存放的另一組帳密)。畫面入口:
/console → 設定 → 「Portal 帳號密碼救援」;/portal 登入頁加一行連結指過去。

本機真瀏覽器 E2E 驗證(wrangler dev 18787/18788 + 本機靜態伺服,真的走一輪
forgot-password 狀態):first-time setup 建帳號 → 故意打錯密碼確認鎖死
(email 或密碼錯誤)→ 點連結進 /console → 用 console 密碼登入 → 設定頁輸入
portal email → 產生新密碼 BPq2Rs4p7dBWMd6d → 回 /portal 用新密碼登入成功。

cypher-executor 4 個新測試 + 既有 59/60 綠(唯一失敗是既有 pre-existing
/portal HTML 殼 404,與本次無關,git stash 驗證過)。
2026-08-09 15:06:17 +08:00
uncle6me-web 23d36b311a portal 設定頁補顯示 MCP 連接網址(arcrun-rag#7)
封測者原話:「說明叫我把 MCP 加進 claude.ai connector,但我找不到網址」。文件一直寫
「登入 portal → 設定頁,那裡可以直接複製」,但畫面上從沒真的顯示過(grep 0 命中),
用戶照文件走一定撲空。

設定頁新增「接上你的 AI(MCP)」面板:純前端把 apiBase 的 worker 名字從
arcrun-cypher-executor 換成 arcrun-mcp(同一顆自架帳號 workers.dev 子網域,對齊
cli/src/lib/deploy.ts:386-392 部署時的組法),不符形狀就誠實顯示「尚未偵測到」,
不亂猜連不到的網址。複製按鈕從既有 copyOriginUrl 拆出共用 copyText(btn, url)。

端到端本機瀏覽器實測:真 wrangler dev(kbdb+cypher-executor local D1)+ 真登入流程
(console setup → bootstrap admin → portal login)走到設定頁,面板正確顯示/隱藏;
用貼近真實自架形狀的假網址驗算,結果與 deploy.ts 的組法逐字相同;複製鈕點擊行為與
既有、未改動的 st-copy-url 按鈕在同一自動化環境下一致(clipboard-write 是瀏覽器自動化
環境限制,非本次改動的迴歸)。詳細記錄見 system-dev/docs/3-specs/portal-auth/tasks.md t218。

未部署(紅線:只 commit+push,未動任何 Cloudflare 實例)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 14:23:56 +08:00
uncle6me-web ebd4bf5d97 wiki: 記執行紀錄保留期 UI 補完(arcrun-rag#21)
status.md 補一段,供下個 session 接關不用重查。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 13:30:00 +08:00
uncle6me-web 889b70b8f9 feat(portal): 執行紀錄保留期 UI(P7 補前端,arcrun-rag#21)
後端(4ca23c2)已提供 GET/PUT /portal/admin/execution-log-retention,
但管理頁完全沒有對應介面——leo 只能自己 curl。本次補管理頁「執行紀錄保留期」
卡片:顯示目前保留天數、可改天數、可勾「不刪除」(企業稽核)。純薄殼,
零業務邏輯,呼叫既有端點(rule 07 薄殼原則)。

驗證(本機真瀏覽器 E2E,非 curl/非讀原始碼):
本機起兩個真 wrangler dev(kbdb:18787/cypher-executor:18788,local D1+KV,
真的套用 migrations 0001-0006)+本機靜態伺服 console-ui/public(18790,
config.js 指向本機 apiBase)+UI_ORIGINS 加白名單解 CORS。瀏覽器走真實
「首次設定」流程建帳號、登入、進管理頁:
- 預設值:保留天數顯示 90(無資料時退回預設,非空白)
- 改 45 天 → 儲存 → 畫面即時更新「目前設定:保留 45 天」→ reload 頁面仍是 45
- 勾「不刪除」→ 儲存 → 天數輸入框停用、清空 → 顯示「不刪除(企業稽核)」
  → reload 仍是不刪除
- 改回 90 天 → 儲存 → reload 仍是 90(雙向都驗過)
- 全程 Network 面板每筆 GET/PUT 皆 200;console 除了測試前置階段的舊
  bootstrap-conflict 雜訊外,無新增紅字

發現但沒動的問題:console-ui 本身沒有本機可跑的 dev/test 腳本
(package.json 只有 deploy/verify),E2E 用的是手工起 wrangler dev +
python http.server 兜出來的,建議之後補一支 `npm run preview:local`
方便下次驗證不用重找路。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 13:28:36 +08:00
uncle6me-web 21be6d19f7 把 .dev.vars 加進 .gitignore——本機密鑰檔原本完全沒被擋
實查:cypher-executor/.dev.vars(3 行)與 kbdb/.dev.vars(1 行)都是 untracked
且 git check-ignore 零命中,任何人一次 git add -A 就會把金鑰推上 Gitea。
歷史上沒有被 commit 過,所以是純預防、不需要清歷史。

對齊 D36「金鑰只有一個家」:真身不落在 repo 上,不是靠自律,是拿不到。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 13:24:24 +08:00
uncle6me-web 6846d6ddae fix(semantic): 故障照實說是故障——不再把壞掉說成「沒開通」(leo 2026-08-09 直令)
一、文案(portal/console/kbdb hint):語意搜尋是一安裝就提供的功能,
   降級=故障。橫幅改「語意搜尋目前故障/我們的問題/你不用做任何事」,
   拿掉「還沒開通、想開通請匯出診斷檔」這種要使用者申請開通的假框架。
   kbdb 降級回應加 degraded_reason(module_off / embed_query_failed)。

二、查詢向量化失敗不再偽裝成空結果(leo 點名的謊):
   semanticSearch 舊行為「AI 額度用完 → 回 []」會讓使用者以為
   自己的知識庫裡沒有這筆資料。改丟 EmbedQueryFailedError,
   route 誠實降級 keyword+照實告知是暫時故障。

三、源頭機制(裝好的實例為什麼會失去語意搜尋):
   - acr update:kbdb_embed 判斷 ===true → !==false。config 缺欄位時
     redeploy 會把 [[vectorize]]+[ai] binding 靜默剝掉(wrangler deploy
     整份覆蓋),一台正常實例就此壞掉。init 預設同步翻成 [Y/n]。
   -(另 repo)deploy-all.mjs ensureVectorizeIndex 失敗改致命中止。

四、順手自癒:孤兒向量/下架殘影搜尋時背景清除;空結果且 pending>0
   背景 backfill;no_index 拆「故障」vs「還沒有資料」兩態。

測試:kbdb 146/146(新增 degraded 6 案+selftest 1 案);cli 10/10;
瀏覽器端到端兩種故障畫面實測(local wrangler dev+portal 真登入)。
無 SDD 對應:leo 直令修故障(同 08-07 檢修孔前例的人閘直接授權路徑)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 02:05:12 +08:00
uncle6me-web 8eb10049b8 P8:節點輸出 KV 寫入只服務 PIPE 讀者——拆掉比 neurons 更短的隱形短板
leo 08-09「不需要換模型,要調整每個 CF 數字搭配」。盤點發現真正最短的板不是
Workers AI neurons(119 檔/日),是 EXEC_CONTEXT KV:每節點(含 FOREACH 每圈)
put 一次、rag 工作流一張卡 15 次,但唯一讀點是 PIPE 邊——rag 系全無 PIPE 邊,
15 次全是白燒 ⇒ KV 1,000/日 ÷ 15 ≈ 66 檔/日,兩條路(免金鑰/Gemini)都被卡。

修法=寫入前檢查「節點有 PIPE 出邊」。PIPE 工作流與 resume 路徑行為不變。
實測(youlin stage):修前同構卡留 6 node key(15 put)→ 修後零 key,
blocks/triplets 照常寫入。單元測試鎖住兩側行為。
另復原 08-08 重部誤拔的 [ai] binding(extract 501→200,KEEP_AI=true)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 01:26:16 +08:00
uncle6me-web 831cb62d2e feat(portal): 拔掉雲端「疑難排解」匯出按鈕,改導向本機小幫手(t213 收尾)
leo 08-08:「雲端那個要刪掉?不刪用戶搞不清楚要去哪裏下載」;08-09 追認「通知
完畢可以刪除」——封測者已被通知去更新到有地端匯出的版本(arcrun-app「版本與
更新」頁的疑難排解卡,見 products/arcrun-rag commit 9f1ca58 起),放行條件已滿足。

三處改動:
1. 設定頁「疑難排解」面板:移除 #st-diag-export 按鈕與 #st-diag-status,
   改成純文字指路到電腦上的 Arcrun App。
2. 對應的前端 JS 點擊處理(打 GET /portal/data/diagnostics 的那段 IIFE)整段刪除
   ——按鈕已不存在,留著是死代碼。
3. 語意搜尋降級橫幅(se-banner):原本也教用戶「到設定→疑難排解匯出診斷檔」,
   同步改成指向本機小幫手,否則使用者會照著走進一個不存在的按鈕。

GET /portal/data/diagnostics 端點本身未刪(無害、已無任何 UI 呼叫),純粹清路標
不動後端;已知例外——同步小幫手完全連不上、且封測者是老版本沒有本機匯出時,
會真的無路可走,判斷為可接受(他們已被個別通知升級,且雲端 portal 仍能用文字
指路,不是把人晾在原地不給說明)。

驗證:本機起 static server(config.js 指向 dummy apiBase)+瀏覽器 JS 強制顯示
setting/search view,實際截圖確認按鈕消失、#st-diag-export/#st-diag-status 在
DOM 裡是 null、文案渲染正確無破版;console 只有 dummy apiBase 連不上的預期錯誤,
無語法/參照錯誤。cypher-executor vitest(portal-data/admin/auth)114 通過、2 個
既有失敗(HTML 殼 404,改動前後一致,非本次影響——用 git stash 前後對照過)。

未部署(紅線:本輪只到本機驗過+commit+push gitea,prod 需 leo 開閘)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 01:09:10 +08:00
uncle6me-web 894d9abeb1 Revert "P8:/portal/daemon/extract 萃取模型 scout → qwen3-30b(真筆記實測:品質不降、額度天花板翻倍)"
This reverts commit aa6b899276.
2026-08-09 00:51:41 +08:00
uncle6me-web 3447efc94e docs(mcp): list_recent_executions 說明文字跟上 P7(保留期),不再寫「無固定保留期」
08-07 事故修復後,這支工具的說明文字曾誠實標「無固定保留期」——那時保留期
確實還沒做。前一個 commit(P7)補上保留期可設定(預設 90 天,可調,可設不刪
除),這支說明文字若不跟著改,會誤導以後讀到它的人以為系統仍然無限保留。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 00:45:19 +08:00
uncle6me-web 4ca23c256a feat(kbdb): 執行紀錄保留期可設定(P7,leo 08-08 confirm)
背景:08-07 事故修復(60688c3)已把執行紀錄從 KV 搬到 KBDB/D1(entries 表,
API-as-Wall),解掉「稽核資料放在會揮發、被額度打斷的地方」這個結構性錯誤,
也順帶把 Evan 撞到的 1,070 次寫入牆退到 D1 額度層級。但那次修復留了一個誠實
的缺口:MCP list_recent_executions 的說明文字寫著「無固定保留期」——保留期
可設定這件事還沒做。本次補上。

P7 規格(system-dev/docs/3-specs/pending-changes.md「P7」,leo 08-08 confirm):
執行紀錄是稽核資料,預設保留 90 天(3 個月)過期即清;租戶可自訂天數,也可
設「不刪除」(企業稽核,leo:「我願意花很多錢保存,不要刪除」)。

實作(kbdb/src/actions/execution-log.ts,牆內):
- getRetentionDays/setRetentionDays:沿用 execution_log_usage 的 upsert 慣例,
  單一 entries 列/租戶(entry_type='execution_log_retention_config'),零建表。
- cleanupExpiredLogs:分兩段掃——有自訂天數的租戶各自 cutoff;其餘(含無租戶)
  套預設 90 天,排除「不刪除」與已處理過的租戶。每次呼叫界限刪除量
  (CLEANUP_BATCH_LIMIT=500),長期多次呼叫可逐步清完累積量。

路由(kbdb/src/routes/execution-log.ts):GET/PUT /execution-log/retention、
POST /execution-log/cleanup,沿用既有的 Bearer token 全域守衛(fail-closed)。

清理觸發(cypher-executor/src/scheduled.ts):不新增排程基礎設施(wrangler.toml
[triggers] 是受保護檔案)——搭 cypher-executor 既有的每分鐘 cron tick 便車,
固定 UTC 02:30 那一分鐘 fire-and-forget 打一次 KBDB 的 cleanup 端點,一天一次,
不是輪詢。

Portal 薄殼(cypher-executor/src/routes/portal.ts):GET/PUT
/portal/admin/execution-log-retention(role=admin 閘),讓本實例的租戶
(portalTenant)能實際設定保留天數,不只是 KBDB 內部端點。

測試(kbdb/tests/execution-log.test.ts):新增 27 個測試(含原有測試共 27 通過
於本檔),真 SQLite 驗證 cutoff 邏輯、自訂天數隔離、「不刪除」永不清、壞資料
容錯、混合租戶情境、路由層 400/200。測試治具需要「插入指定 created_at 的過期
紀錄」這個正式寫入路徑刻意不開放的能力,做成 kbdb/src/actions/execution-log.ts
內匯出的 testInsert*/testCount* 函式(牆內執行 SQL),測試檔本身零原生 SQL。

量測(不是推論):youlin(yuga3bse)實例上,redeploy 後對 graph_neighbors
webhook 發送 1,200 次併發請求(超過 Evan 實測失敗的 1,070 次)——全部 HTTP 200;
ANALYTICS_KV 的 key 數量在請求前後維持 663 不變,證明新寫入路徑完全不碰 KV,
Evan 撞到的那道牆的成因已被物理移除,不只是延後。

讀取端驗證(真呼叫,非 curl):透過綁定 yuga3bse 的 MCP 連線實際呼叫
arcrun_list_recent_executions(回傳含本次量測寫入的 D1 紀錄)與
arcrun_get_execution_trace(正確回 404 not_found,非崩潰);portal 前端
(https://arcrun-rag-ui.youlin-hsieh-dev.workers.dev/portal)瀏覽器實際載入,
無 console 錯誤、無異常紅色橫幅。

舊資料:KV 裡既有的 stats:* 沿用 60688c3 的既有決定——不搬移,任其依現有 90
天 TTL 自然過期(那是統計快取不是真相源);新的 D1 execution_log 保留政策只
管新資料,不回溯處理。

部署:cypher-executor + kbdb 已手動部署到 youlin(yuga3bse,AI 測試場,leo
08-08 令),未動 prod(uncle6)。本次僅程式碼行為變更、無新增/修改 D1 binding、
無新表——三個既有 D1 binding(CREDENTIALS_DB×2+kbdb DB)維持原樣,未新增第四個。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 00:43:55 +08:00
uncle6me-web aa6b899276 P8:/portal/daemon/extract 萃取模型 scout → qwen3-30b(真筆記實測:品質不降、額度天花板翻倍)
免金鑰路短板=Workers AI 免費 10,000 neurons/日。leo 真實筆記 8 篇 × 5 模型實測
(arcrun-rag docs/benchmarks/p8-extractor-quality/,usage.neurons 為 CF 原生計量):
scout 84 n/檔(119 檔/日)→ qwen3-30b 43 n/檔(232 檔/日);格式合規 8/8、
三元組 7.6 條全可解析(scout 5.0)。granite 最便宜但 5/8 缺段=實測否決。
聊天 recipe(workers_ai_chat)不動;只 commit 本 hunk,工作區另有 P7 進行中改動未收。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 00:40:52 +08:00
uncle6me-web 466e56bc2d 落帳:youlin 登入修好(瀏覽器實證)+連線中斷 vs 密碼錯誤是決定性差別
含誠實限制:驗的是程式碼修對了,不是安裝器裝出來的結果。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 22:15:00 +08:00
uncle6me-web 07cc7f51b5 fix(cypher): 同一台實例的 portal 一律自動放行,不再依賴 UI_ORIGINS 被注入
2026-08-08 事故根因:leo 的 youlin 實例登入整個斷掉,瀏覽器實證
  blocked by CORS policy: No 'Access-Control-Allow-Origin' header
真因=該台 UI_ORIGINS 沒被設。

同一天發生兩次同款:這些變數只有安裝器會注入,任何手動 wrangler deploy
就會漏掉,而漏掉時系統看起來完全正常(worker 上線、200、版本號對),
只有真人點下去才會發現。leo:「這麼危險的問題已經發生 2 次,不可以再有一次。」

⇒ 治法不是「記得注入」,是讓它不需要被注入:portal 與 cypher 是同一個
workers.dev 子網域下的兄弟,位址推導得出來。少一個必須注入的變數,
就少一個會被漏掉的東西。UI_ORIGINS 仍有效(自訂網域用),只是不再是
「登得進去」的前提。

對照:改動前後 tsc 錯誤數同為 7(皆為既有、不在本檔)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 20:47:32 +08:00
uncle6me-web 42cb1d7aa9 console-ui:把「線上跑的是不是當代的」變成機械判準,並接進部署鏈
leo 2026-08-08:「已經發生過一次這個錯誤,把舊版界面上到 prod,
你要確定不可再犯。」

實測(repo 對線上,純文字資產比對):
  repo   portal/index.html  343,969 bytes
  線上   mira.uncle6.me      82,911 bytes(Songti 12 處,舊金色 serif 品牌)
  線上   pages.dev           82,911 bytes(同上)
而 e730b3f 那版 verify-live 對這兩站**三項檢查全過**——因為它驗的是
組態(apiBase、profile 的 views/home),不是世代。
⇒ 一個網址可以組態完全正確、卻對外展示一套早就被淘汰的介面,
  而所有機械檢查都說它是綠的。這就是要消滅的狀態。

本次落地:

一、世代指紋(targets.mjs)
  逐一取線上/產物的資產(index / portal / console / favicon.svg),
  遮掉本來就該隨部署目標不同的那兩行(VIEWS/HOME),其餘按位元組比對。
  刻意不用關鍵字清單——清單要人維護,而舊世代能無聲上線正是因為沒人記得維護它。
  誠實 trade-off 寫在檔內:repo 改了沒部署就會判紅,那是正確的(那時線上確實不當代)。

二、宣告值真的寫進產物(收掉 e730b3f 標的 WIP)
  deploy.mjs 改為由 targets.mjs 產出 .staging/<目標> 再推:
  config.js 由宣告值即時產生、console 的 VIEWS/HOME 依 profile 覆寫,
  **覆寫沒命中就中止部署**;推之前回頭讀磁碟上那份驗一次(不看腳本印了什麼)。
  public/config.js 刪除——它是產物不是原始碼。

三、修好一道從 08-03 起就在誤判的閘
  t160 的世代閘比對 portal 全文含「登記新庫」即拒部,而 66f1b59(08-03)
  加了一則**說明「已經把它拿掉了」的 HTML 註解** ⇒ 該閘自那天起每次誤判,
  npm run deploy:personal 連續五天推不出去。改成剝掉註解後只看可見內容,
  並降級為輔助(主判準是指紋)。這正是「手工關鍵字閘會腐爛」的實例。

四、讓它在該跑的時候真的被跑到(不再生出沒人記得執行的腳本)
  · deploy.mjs 推完自動回頭驗線上,不過就算本次部署失敗
  · .deploy-state.json 只在線上實測通過後才寫,且不進版控
    (新 checkout 沒紀錄=狀態未知=該被提醒,而不是繼承別人的綠燈)
  · Stop hook 每回合離線比對「手上這一代 vs 最後一次驗過的部署」,
    在要說「做完了」的那一刻出聲(實測 0.096s,不連網)

五、uncle6 邊界寫進工具本身(leo 08-08:「要看範例只在 youlin 網站,不要去碰 uncle6」)
  deploy.targets.json 的 enterprise 標 frozen:deploy 拒絕部署、verify 連抓都不抓。
  目標本身保留不刪——刪掉就變成下一個 AI 眼中「從來沒有過這個站」的失憶。
  同源清掉兩處還活著的舊記錄:README 的線上 demo 連結、public/index.html 的註解。

驗收證據見 commit 後的實測輸出(舊世代樣本取自 git 歷史 ad367e4,本機起站餵判準,
未碰任何線上資源)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 20:36:18 +08:00
uncle6me-web e730b3f831 console-ui 部署:宣告值只解讀一次,並補上線上實況驗證(WIP)
同源於 2026-08-08 的 apiBase 事故:部署腳本把 apiBase/profile 印在
終端機上,卻沒有寫進推上去的產物 ⇒ 印對的、推錯的。

根因比想像深:deploy.targets.json 原本靠 build.mjs 在 build 期把
profile/apiBase 烤進產物,但 t160(e744ad1)清世代債時把 build.mjs
整支刪掉改成直接託管 public/,沒有人接手「把宣告值寫進產物」這件事
⇒ 前兩次事故的解等於被還原,只剩下「印出來給人看」。

本次落地:
- targets.mjs:宣告值的唯一讀取點,把「一個目標展開成期望的產物長相」
  定死成函式,供部署/驗證共用同一個答案,杜絕三邊各自解讀而漂移。
  缺 apiBase/accountId/verifyUrls/未定義的 profile 一律拒絕部署。
- verify-live.mjs:獨立可跑,驗「線上網址真的在用的組態」=「宣告值」。
- deploy.targets.json:補 _profiles(profile → views/home)與 verifyUrls。

WIP:deploy.mjs 尚未改接 targets.mjs,public/config.js 尚未退役。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 20:14:20 +08:00
uncle6me-web 84471659af docs(portal): 疑難排解按鈕文案改導流(t213,InkStoneCo 總管交辦)
這顆按鈕在封測者瀏覽器裡執行,構不到他電腦上 daemon 的本機資料(檔案總量/
失敗分類/daemon 版本)——那半已改在 arcrun-app 本機端匯出(見
products/arcrun-rag commit 9f1ca58)。按鈕本身保留當退路(daemon 完全掛掉時
唯一還按得到的東西),文案改成誠實講清楚自己只有一半、導去完整版,照總管
給的原話:「這裡只看得到雲端這半,完整診斷請到你電腦上的 Arcrun 匯出。」

純文案改動,未動任何邏輯/端點;跑過 portal-data/portal-admin/portal-auth
三份測試,114 通過、2 個既有失敗(HTML 殼 404,測試環境問題,與本次無關,
改動前後一致)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 13:13:06 +08:00
uncle6me-web 93b1140bf1 feat(portal): 新增 GET /portal/daemon/diagnostics(t213 matrix/arcrun 半部)
InkStoneCo 總管交辦(arcrun-rag repo t213):leo 拿真診斷檔實測四個真實問題,只答得出一題
——其餘三題需要地端資料(daemon 本機 manifest 總量/失敗分類/自我更新狀態),而現有
GET /portal/data/diagnostics 只有 portal session 認證,daemon 背景行程沒有 session
(密碼只在連線精靈當下用過就丟,不落地),構不到這支端點。

本次只做 matrix/arcrun 半部(雲端這半):
- 把 /portal/data/diagnostics 的核心查詢邏輯抽成 buildDiagnostics(env, tenant)
  (portal.ts),薄殼原則:能力只實作一次
- 新增 GET /portal/daemon/diagnostics,認證比照既有 /portal/daemon/extract
  (X-Arcrun-API-Key,非 session);apiKey 當 owner_id 用,不與 portalTenant(env)
  比對(t189 教訓:daemon 的 api_key 不保證等於 worker 的 CONSOLE_TENANT)
- /portal/data/diagnostics 改呼叫共用函式,回應形狀完全不變
- 按 leo 指示刪掉「需在失敗當下由封測者截圖」那句 notes(本機那半資料到位後這句話
  失去意義)

地端那半(arcrun-app 讀 manifest 合併 + 匯出按鈕)在 products/arcrun-rag repo 進行,
待另一隻處理 t210 的 agent 落地 app.go/main.js 改動後再接線,本次不動 arcrun-app。

驗證:
- npx tsc --noEmit:與 stash 前錯誤數相同(4 個),零新增(pre-existing,與本次改動無關)
- npx vitest run:314→315 通過(+3 新測試涵蓋 401/apiKey 當 owner_id 不比對 tenant/
  回應形狀與 session 版一致),14 個既有失敗與改動前完全相同(HTML 殼 404,測試環境
  static asset 問題,與 portal.ts/portal-data.ts 邏輯無關)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 12:32:10 +08:00
uncle6me-web 962d863ef7 fix(kbdb): 藏書地圖 M3 收尾——讀端自動核對重算,不再依賴 ingest 接鏈
真因(總管實測,system-dev/wiki/mistakes.md 08-08 段):design 原訂「ingest 尾端呼
POST /map/recompute」,但 repo 內查無任何呼叫點,三週沒接上,沒手動 backfill 過的租戶
(絕大多數)GET /map 恆回空;MCP 說明文字還宣稱「地圖由 ingest 尾端自動重算(M3)」——假話。

leo 否決「降級成即時聚合、不維護快取」的提案(會丟失 narrative 這類摘要本體,只算得出
count)。改法:GET /map/GET /map/:library 讀端自己核對即時三元組數,落差就地呼叫既有的
recomputeLibraryMap 補算(kbdb/src/actions/library-map.ts ensureFreshLibraryMaps)。聚合
SQL 沒有第二套、narrative/relation_profile/bridges 摘要欄位原封不動,只是觸發時機從「等
外部呼叫」改成「讀的當下順手核對」。同時解掉:全租戶自動 backfill/跟得上新資料/不依賴
跨 repo 的 ingest 接鏈。

附帶修 recomputeLibraryMap 的 narrative 欄位:沒帶值時原本會清空,改成沿用上一版(避免
自動重算把 ingest 端/人工填過的 narrative 靜默洗掉)。

修正三處說謊的說明文字(mcp/src/tools/kbdb_map.ts、console-ui console/index.html):
「地圖由 ingest 尾端自動重算(M3)」不存在,改為誠實描述讀端即時核對機制;404 語意從
「從未 recompute」改為「查無此庫」(已知但空的庫現在會自動補成 triplet_count:0 的 200,
不會落到 404)。

測試:kbdb 新增 6 案(18/18 全綠,覆蓋自動 backfill/跟得上資料/narrative 保留/
404 vs 空庫誠實分辨/owner 隔離/無 triplet template 不報錯);mcp 新增 1 案釘住舊謊言
不再出現。kbdb 125/125、mcp 69/77(同基線 8 個 oauth 既有失敗,非本次引入)全綠;
tsc 兩包乾淨(kbdb 1 個既有 auth.test.ts 錯誤與 stash 前一致,非本次引入)。

SDD:system-dev/docs/3-specs/library-map/tasks.md M3 從「07-19 誤標 」更正為實況;
design.md §3 加 2026-08-08 更正說明。未動 frontmatter status(仍 draft,D35 生命週期
鐵律留給總管/leo 裁)。

殘項:本次修改只在本機驗證(真 SQLite + 假 binding 單元測試),未部署 prod;未在真實
KBDB(如 yuga3bse 租戶)重新實測 kbdb_get_map 非空——需部署後才能貼實測輸出。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 00:44:55 +08:00
uncle6me-web 7dbd4f59e7 fix(portal): 檢修孔 library_count/triplet_count 恆 0 的根因+加統計自我檢查
真因:/portal/data/diagnostics 讀 GET /map,那是 library_map 快取 block,只能
靠 POST /map/recompute 產生;核實過整個 repo 沒有任何呼叫點會打 /map/recompute
(library-map SDD 的 ingest 自動重算 M3 從沒接上,status: draft)。⇒ /map 對任何
租戶恆回空 libraries,與實際資料量無關——2026-08-07 leo 實測抓到:3 庫、大量
triplets,診斷檔卻回 0/0。

修法:改走 GET /portal/admin/libraries 已在用、驗證過的即時查詢組合(不依賴任何
快取):listRecordsByTemplate(portal_library) + /entries/libraries(t52 蓋章即
現身)+ /records/triplet-stats(t142 即時聚合 SQL)。

附帶:加 library_scope_check 統計自我檢查(呼應 embedding.self_test 的精神)。
兩個計數都是 0 時,用完全不同的查詢路徑(不分庫/模板,只問這個租戶底下有沒有
任何 entries)交叉驗證,區分「真的是空」與「查詢方式或 owner_id 對不上」
(2026-08-01 t161 前科同型病:手動補的 record owner_id 存成 None,全量查得到、
按 owner_id 過濾的畫面永遠空)。

三個 diagnostics 測試全綠:即時查對出正確 library_count/triplet_count(且不再
外流庫名/內容,只回數字)、真空情境自我探測誠實回空、t161 同型病情境自我探測抓到
「查得到但統計回 0」的矛盾。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 00:12:54 +08:00
uncle6me-web d779a11958 gitignore 掉 deploy-all.mjs 產的 package.json/lock
名字是 arcrun-deploy-shared,是本機跑 installer/scripts/deploy-all.mjs 時
npm 為了 wrangler 等依賴生出來的,不是 repo 內容。
每次本機部署都會冒出來吵未推警察 => 加 gitignore 而不是刪
(刪了下次部署又會產生)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 00:10:59 +08:00
uncle6me-web c7a0b317cb fix(kbdb): 補 kbdb/src/routes/entries.ts 缺的自癒搬遷 hook(上一commit漏了這支檔)
上一個 commit(046ceba)訊息宣稱改了 entries.ts 但實際只有測試檔進了 git——
entries.ts 的 migrateLegacyCredentialsForOwner 呼叫留在工作區沒進 index
(同一份工作目錄有另一個 session 併行在改這支檔案的語意搜尋空結果診斷功能,
兩邊的 import 改到同一行,第一次 commit 時漏收)。這次補上:

- import migrateLegacyCredentialsForOwner,GET /entries 對 entry_type=
  credential 觸發自癒搬遷(見 046ceba 說明,本檔案是實際掛載點)
- 同時收進另一個 session 已完成且測試通過的變更(search-semantic-empty-reason
  :語意搜尋回空時分辨 no_index/no_match/stale_index 三態,給人話 capability_
  hint + 技術向 admin_hint;非本次任務範圍,因同檔同 import 行交織、且已驗證
  119/119 全過,一併收下不拆散)

kbdb 全測試 119/119 通過(含新增的 credential-legacy-migration.test.ts 5 項
與 search-semantic-empty-reason.test.ts 4 項)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 22:53:51 +08:00
uncle6me-web 046ceba29c fix(kbdb): credential 目錄自癒搬遷(D38 收尾)——修 youlin 20/20 全敗事故
根因(2026-08-07 youlin 測試實例):7ba7855 把 credential 讀寫端從舊表
credentials(0002)改走 KBDB entries(entry_type='credential'),但舊表
資料的搬遷 migration(0006)要人手動觸發部署才會跑。實查 youlin 的 D1:
credentials 表有 1 筆(yuga3bse/kbdb_internal_token),entries 對應筆數
為 0——新讀取端上線、舊資料還沒搬,20 次 workflow 全部找不到 credential。

leo 追加硬要求:credential 資料住在用戶自己的 CF 帳號,換讀取路徑=每個
既有實例都要遷移,但用戶不准做任何手動步驟——搬遷必須內建在既有更新流程
裡、天然無感。

解法(kbdb/src/actions/credential-legacy-migration.ts):把「搬」變成
「讀」的副作用而非獨立步驟。KBDB worker(D38 唯一允許碰 SQL 的牆內)在
每次查詢某租戶的 credential 目錄前,先確認舊表資料是否已搬進 entries
——沒有就搬(per-owner scoped、NOT EXISTS 冪等),有就是零成本的
sqlite_master 短路檢查。呼叫時機掛在 GET /entries?entry_type=credential
(cypher-executor 熱路徑本來就會打的端點),故只要更新 KBDB worker,
下一次任何人跑 workflow 該租戶就自動搬好,不需要用戶或安裝器多做任何事。
刻意不執行退場(DROP TABLE)——多個實例搬遷時間點不同,舊表留著才能讓
「已搬」與「還沒搬」的實例同時安全運作;退場留給之後獨立的清理步驟。

kbdb/tests/credential-legacy-migration.test.ts:反向驗證重建 2026-08-07
事故的確切前置狀態(真 SQLite + 0001/0002/0005 migration 原檔),證明補丁
加入前 entries.length 回 0(事故重現),加入後回 1(修好);另驗冪等
(連呼叫三次不重複搬)、多租戶互不干擾、舊表已清理時的終態安全。

cypher-executor/tests/credentials.test.ts:補齊 7ba7855 留下的刻意紅燈
(原 placeholder 五項清單),涵蓋租戶隔離的讀寫、真刪除(非 deprecated
標記)、零原生 SQL 原始碼掃描、密文本體不落 KBDB。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 22:51:31 +08:00
uncle6me-web ac4fb56c91 portal 前端文案:語意搜尋降級提示改講人話+entry_type/mode 不再吐原始英文值
封測者 Oscar 回報「語義搜尋搜不到」,追查發現 portal 使用者實際會看到的降級提示
其實是後端 kbdb capability_hint 直接透傳(寫給工程師看的:「叫 CC」「vectorize」
「kbdb_embed:true」「redeploy」),比 UI 裡原本的 fallback 文案還難懂。

- se-banner:不再透傳後端 capability_hint,前端固定顯示「發生了什麼+現在怎麼辦」
  (語意搜尋還沒開通→以下是關鍵字結果→到設定頁疑難排解匯出診斷檔給我們)
- 新增 entryTypeLabel():entry_type 原始值(wiki_card/block/execution_log…)
  不再直接印給用戶看——尤其 wiki_card 原樣顯示會打臉上傳頁自己講的「AI 整理後
  會以 wiki 卡形式出現」
- 新增 searchModeLabel():搜尋結果「模式 keyword/semantic」的英文值改顯示
  中文(關鍵字/語意/圖譜),AI 問答來源標籤同步套用

不動 console/index.html(僅供部署擁有者/維運者使用,非客戶介面,根目錄
index.html 明文寫「不該對客戶露出」,同類字眼在此屬合理專業詞彙,故不改)。
不動 cypher-executor/src、kbdb/src(另一 agent 正在改 credential 路徑;
真正的降級文案根因在 kbdb/src/routes/entries.ts 的 capability_hint,
已另行回報總管,非本次改動範圍)。

已用 installer/scripts/build-ui-bundle.mjs 打包驗證:新文案進 bundle、
舊文案消失,entryTypeLabel/searchModeLabel 均實際生效(非死代碼)。
2026-08-07 22:38:07 +08:00
uncle6me-web 1e2ef6806a wiki: 落帳檢修孔第一版(embed selftest + diagnostics 端點 + 匯出按鈕)
記錄 2026-08-07 三個 commit(9344562/83aa1f6/5388f40)的狀態、端到端實測方式、
測試結果,以及未完成項(未 push GitHub 待 D20/Oscar 需自行按「立即更新」)。
一併誠實記錄收工時發現本機既有背景機制會把 commit 鏡到 Gitea(非本次操作觸發)。
2026-08-07 18:52:30 +08:00
uncle6me-web 5388f40c03 feat(portal-ui): 設定頁「匯出診斷檔給我們看」按鈕(檢修孔前端,2026-08-07)
leo 直接指令的簡化版規格:一顆按鈕、按下去下載一個檔案、用戶自己把檔案傳出去——
同意天然內建在「他自己按、自己傳」這個動作裡,不需要額外授權流程或內部概念外露。

按鈕打 GET /portal/data/diagnostics,把回應存成單一 JSON 檔(不是要解壓的一包)
直接觸發瀏覽器下載,檔名帶時間戳。沿用既有 authHeaders/safeJson/guard401/friendlyErr
helper(與同頁其餘按鈕同一套錯誤處理慣例)。

既有前端輕量測試(safejson.test.mjs/os-split.test.mjs)跑過,18/18 全綠,未受影響。
2026-08-07 18:39:30 +08:00
uncle6me-web 83aa1f6bb2 feat(portal): GET /portal/data/diagnostics —— 檢修孔聚合端點
leo 2026-08-07 直接指令:「一顆按鈕在設定裡,按鈕下載一個檔案,把檔案發給我,你看那個
檔」。本端點是那個檔的資料來源:聚合 embed 模組健康狀態(module_enabled/cards_embedded/
cards_pending/self_test)、知識庫規模(library_count/triplet_count)、bundle_version、
instance_url。

紅線落實:
- 只轉發數字/布林/字串狀態,KBDB /map 回應裡的 narrative/top_entities(卡片內容)讀出
  triplet_count 後即丟棄,測試 portal-data.test.ts 新增案專門斷言回應不含內容字樣。
- 認證沿用既有 requirePortalUser session 閘,不對外公開。

3 個新測試全綠(未登入 401/完整聚合含隱私斷言/embed 未開時誠實回 false 不假裝)。
既有 1 個失敗案(/portal HTML 殼 404)為 stash 驗證過的既有失敗,與本次改動無關。
2026-08-07 18:38:03 +08:00
uncle6me-web 9344562258 feat(kbdb): embed 自我檢查端點(檢修孔第一塊,2026-08-07 leo 直接指令)
GET /embed/selftest?owner_id= —— 挑一筆已標記「已嵌入」的卡片,拿它自己的內容做一次
真實語義查詢,檢查「自己是否搜得到自己」。backfillStatus 的 pending/embedded 計數
看不出 Arcrun#11 那種「嵌了但查不到」的故障模式(metadata index 事後才建、既有向量
沒被收錄),本端點是唯一能端到端驗證 index 真的可用的方法。

隱私邊界:只回 {enabled, tested, passed, note} 四個布林/字串欄位,不回卡片內容、
不回 entry id(測試 embed-selftest.test.ts 最後一案專門斷言不洩漏)。

12/12 kbdb vitest 全綠(含既有 embed-backfill 6 案未壞)。
2026-08-07 18:35:06 +08:00
uncle6me-web 7ba78552a4 D38:credential 目錄改走 KBDB API(零原生 SQL)+舊表退場;測試刻意留紅燈
存取層:credentials.ts / auth-dispatcher.ts / portal.ts 全改走 kbdbBase()+fetch
到 /entries(照 execution-logger.ts 既有慣例),.prepare/.exec/.batch 命中 0。
資料層:0005 seed credential template;0006 把舊表資料搬進 entries 後拆表;
0002 標退役、deploy.ts 不再套用(加 kbdb-sql-ok 留痕,純歷史對照)。

總管親驗四項(不聽 agent 自評):
1 三個檔 .prepare/.exec/.batch 命中 0;六個檔全部通過 kbdb-api-wall-guard
2 0006 的 INSERT 欄位(id/entry_type/owner_id/page_name/metadata_json/
  created_at/updated_at)與 0001_base 的 entries 表逐一對得上
3 不可逆風險查官方:D1 batch 是 transaction、任一句失敗整批 rollback;
  exec 出錯則「執行停止、後續不執行」=> 兩種語意下 INSERT 失敗都不會跑到
  DROP TABLE,用戶 credential 目錄不會遺失
4 0006 搬在拆之前、冪等(NOT EXISTS 防重複)、豁免標記有留痕且理由正當
  (拆表是牆內施工,API 不提供也不該提供拆表)

🔴 抓到一個假綠並修正:agent 中途被中斷,把 111 行的 credentials.test.ts
砍成一行「// placeholder — see edit below」,那個 edit 從來沒發生,
且 setup.ts 被刪。vitest 對這種檔案回報「Tests: no tests」,
很容易被讀成「沒失敗=通過」——正是 CP 記過的
「這條 route 曾整條消失過沒人發現」同型。
處置:還原 setup.ts/vitest.config.ts,credentials.test.ts 改成
**刻意會失敗的紅燈**並在檔頭列出要補的五項。空檔會被誤認為綠,紅燈不會。

未部署。本批要先上 stage 驗過才進 prod(leo 08-07 定)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 17:27:18 +08:00
uncle6me-web 60688c3108 fix(kv-quota): workflow 執行紀錄搬離 KV,改走 KBDB template 機制(A1/A2/A7)
事故:cypher-executor/src/actions/execution-logger.ts 舊版每跑完一次 workflow 就
ANALYTICS_KV.put() 一筆新 key(註解寫「避免覆蓋」)= 只增不減,封測者 Evan 處理約 690 個
檔案就把 KV 免費層 1,000 write/日打爆(實測 1,070 write),整個實例 429。

A1 少記:workflow 執行紀錄改走 KBDB template 機制(entries 表 entry_type='execution_log',
kbdb/migrations/0004_execution_log_template.sql 只 seed 一列 template 定義,零建表/改表)。
儲存精神比照既有 recipe_stat(kbdb/src/actions/recipe-stat.ts):template 只負責文件化,
實際一筆執行是 entries 表一列(1 次執行=1 次 D1 寫入,不走 entry_values 全展開)。欄位收斂:
時間/workflow/verdict/duration/錯誤訊息/(可得的)目標;成功記最少,失敗多記(訊息截斷長度
不對稱:200 vs 2000 字)。target 只認 trigger context 的 page_name/path,不整包存 input。

A2 自我降級:D1 額度仍與知識卡共用同一顆 100,000 rows/日,本模組自設 20% 軟上限(可用
EXECUTION_LOG_DAILY_WRITE_LIMIT 覆寫),超過 80% 降成只記失敗、超過 100% 完全停止記錄,
但 workflow 執行永遠照跑(cypher-executor 端 fire-and-forget 永不 throw)。

A7 讀取端:/workflows/:name/executions、/portal/data/workflows 的 last_execution、MCP
list_recent_executions 全部改打 KBDB HTTP API(GET /execution-log、/execution-log/latest),
取代原本的 ANALYTICS_KV list/get(免費層 list 也是 1,000/日)。

架構鐵律修正(本次施工中兩度被抓到走偏,過程留痕於 commit 訊息供後續參考):
- KBDB 三張表打天下(entries/templates/entry_values),永遠不加新 table——新資料類型
  一律用 template + entries,不建表、不 ALTER TABLE。
- KBDB = API-as-Wall,零 SQL:cypher-executor 端一律走 KBDB 的 HTTP API(連法比照既有
  recordRecipeStats/kbdbFetch 慣例),不直連任何 D1、不對 arcrun-kbdb 下任何原生 SQL。

順帶修復:kbdb/src/actions/entry-crud.ts listEntries 的 ORDER BY 補 `, rowid DESC` 二級
排序——entries.created_at 是 unixepoch() 秒級解析度,高頻寫入(execution_log 一秒內多筆)
常同秒,單靠 created_at DESC 不保證「最新一筆」正確,此為本次測試(latestExecutionLog)
發現的既有潛在缺陷,順手補上決定性排序,不改變任何既有查詢在 created_at 不同時的行為。

隔離:portal-data.ts INTERNAL_ENTRY_TYPES 加入 execution_log/execution_log_usage(與既有
value/workflow 同層級排除),避免用戶知識搜尋混進執行 log;本模組從不設 metadata_json.embed,
故永不進 Vectorize 語意搜尋索引。

不動:registry/src/actions/recordAnalytics.ts(零件市場統計,獨立 Worker、獨立 KV 命名空間、
不同資料模型,非本次事故根因所指範圍);cypher-executor/{wrangler.toml,kbdb/wrangler.toml}
未變動(repo 層級 deny 規則保護這兩個生產設定檔不被 AI 編輯)——ANALYTICS_KV binding
因此仍留在 wrangler.toml 宣告中但程式碼零讀寫點(見 PR 說明的完整 grep 佐證)。

KV 裡既有的 stats:* 舊資料不搬移(是統計不是真相源,維持原樣任其依 90 天 TTL 自然過期)。

測試:kbdb/tests/execution-log.test.ts(13 個,含零建表證明/少記/A2 降級/route)、
cypher-executor/tests/execution-logger.test.ts(payload 正確性/永不 throw)、
cypher-executor/tests/executions-route.test.ts(讀取端轉發)、portal-data.test.ts 對應區塊
改寫。kbdb 全測試 104/104 通過;cypher-executor 320 個測試中 9 個失敗為 main 既有(與本次
改動無關,改動前後 stash 對照確認)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 16:13:00 +08:00
uncle6me-web 36a5630c63 portal 顯示同步器版本+下載網址改讀真相源(leo 08-06)
leo:「Portal 和 rag.arcrun.dev 應該顯示同步器的版本⋯⋯因為你說最新 0.18.5
連我都沒辦法確認,所以用戶到底是否最新版他自己也不知道」。

## 病
release(雲端知識庫)與 daemon(桌面同步器)是兩條版本線。
版本卡只顯示前者;下載按鈕的檔名是寫死的固定別名
(DAEMON_MAC/DAEMON_WIN)⇒ 頁面上完全看不出「同步器是哪一版」。

## 改
- 下載網址改向 /api/latest 取 daemon.downloads(真相源=bundles manifest),
  取不到才退回原本的固定別名 ⇒ 按鈕永遠可用。
  ⚠️ 沒有違背 fe0ee82 立的「不要每出一版就改 code」——網址是取來的,
     一樣不必改 code;還順便解掉固定別名的兩個老問題:
     ① 別名指向 @main 會吃 CDN/ref 快取拿到舊檔(08-04 撞過)
     ② 檔名不帶版號 ⇒ 無從顯示「這是哪一版」=leo 這次抱怨的正題
- 版本卡下方加一行「最新的同步器版本是 X(你手上那支在同步器視窗左下角)」
  +連到版本說明頁。取不到就**不顯示**,不編數字。
- 渲染改成「先用退路畫、取到真相源再重畫」,renderDaemonDownload 做成可重複呼叫
  (清掉上一輪插入的節點,否則會疊出兩份「不是這個系統?」)。

## 驗
· node --test os-split.test.mjs safejson.test.mjs → 8 passed, 0 failed
· index.html 三個 script 區塊逐塊 node --check → 全部語法 OK
· 未驗:線上畫面(要等 bundle 出貨後才有 daemon 欄位可讀)⇒ ◐
2026-08-06 13:12:15 +08:00
uncle6me-web 05b215c9f7 語意門檻改相對式+下架連帶刪向量(leo 兩個實測回饋)
## ① 「關懷型 AI 命中 20 筆、只有前 3 筆相關」⇒ 閾值太寬(leo 判斷正確)
固定門檻兩頭都不對,因為每個查詢的分數尺度不同(實測 youlin 實例):
  關懷型 AI          正解 0.645-0.770,雜訊起於 0.547  ← 固定 0.5 放進 6 筆雜訊
  閉環機             正解 0.552-0.638,雜訊起於 0.446  ← 固定 0.6 砍到剩 2/4(=早上的 0 命中)
  人力媒合系統規劃書   正解 0.842,雜訊起於 0.550
⇒ 改**相對門檻** max(0.45, top×0.8)。五組實測:固定 0.5 混入 9 筆雜訊/
  固定 0.6 有兩組正解被砍/相對式四組雜訊 0 且正解全留。

## 🔴 寫測試才發現的真問題:門檻不能在 Vectorize 那層算
Vectorize 的 indexed metadata 沒有 status ⇒ 那層不知道誰已下架。
若最高分是下架殘影(t24 復現案 0.971),拿它算門檻=0.777,
會把 0.6 的正解一起砍光 ⇒ **又變成 0 命中**。
⇒ 相對門檻移到 routes/entries.ts,接在「hydrate+濾下架」之後;
  embed.ts 只留絕對下限。新增測試鎖住這個順序。

## ② leo:「理論上它的向量也要刪掉,就不會有殘影了吧?」——對,補上
單筆真刪已接 deleteByIds(b7af622),但「移除整個庫」走軟刪、向量原地不動。
⇒ deprecate-by-library 同時 deleteByIds + is_embedded 歸零(D1 與 Vectorize 不說兩套話);
  backfill 兩條路徑(含 reindex)都排除 deprecated,否則下次補嵌會把殘影養回來。
不違背 t135「資料保留可還原」:D1 那列原封不動,還原後跑 backfill 重嵌即可。
回應新增 vectors_deleted,刪失敗誠實回 0 不假裝清乾淨。

驗:kbdb 91/91 綠(新增 4 項相對門檻測試,含「下架殘影不得決定門檻」)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 18:47:10 +08:00
uncle6me-web 0ff369f818 修語意搜尋全 0 命中:分數門檻沒跟著 bge-m3 換代(換模型漏掉的第五處)
leo 2026-08-05 實撞:「新上傳的『loop-engine-north-star.md』語義 0 命中,
但舊的『人力媒合系統規劃書』語義 2 命中」。

## 根因不在向量——向量是好的
直打 Vectorize 實測(youlin 實例、bge-m3、1024 維 index):
  搜「閉環機」→ loop-engine-north-star.md 穩坐第 1-4 名(0.638/0.603/0.588/0.552)
D1 與 Vectorize 也對得上:659 筆 embeddable 全 is_embedded=1、index vectorCount 659。

真兇是 **min_score 門檻綁在舊模型的分數尺度上**:
  舊 bge-base-en-v1.5:中文分數全擠 0.65-0.90(沒區辨力)⇒ t183 取 0.75 砍雜訊,對
  新 bge-m3          :尺度整體下移(相關 0.5-0.85、雜訊 0.4 上下)⇒ 0.75 砍掉的是正解
leo 看到的「舊檔中、新檔不中」由此而來——「人力媒合系統規劃書」拿 0.842 僥倖存活,
其餘全被門檻掃掉。08-05 換 bge-m3 的「四處同步」清單(embed.ts/deploy.ts/
deploy-all.mjs/worker.js)**漏了這第五處**,因為它不在 kbdb 而在 portal 呼叫端。

## 改動
· kbdb/src/embed.ts:新增 DEFAULT_MIN_SCORE=0.5,**緊鄰 DEFAULT_EMBED_MODEL**
    ——門檻是模型的性質,放模型旁邊,下次換模型的人一定會看到
· cypher-executor/src/routes/portal-data.ts:拿掉硬寫的 0.75,
    只在使用者顯式指定時才傳 min_score(不再各自持有一份數字=不再漂移)
· console-ui/public/portal/os-split.test.mjs:修好被今天 fe0ee82 弄壞的自測
    (結尾標記寫死文案 ⇒ 改文案就炸「抽不到函式區塊」;改成錨定結構)
    +斷言同步改成 Mac 給 DMG

## 0.5 怎麼來的(實測分布,不是猜的)
「閉環機」            0.638/0.603/0.588/0.552 全是目標檔 ── 斷崖 ── 0.446 才是雜訊
「火星座標 奧林帕斯山」 0.750…0.500 全對,0.475 以下才是雜訊
「人力媒合系統規劃書」   0.842 對,0.550 起是雜訊
誠實 trade-off:0.5 非每個查詢都乾淨(「AI 上課名冊」0.658 的 ax-academy 會擠進來),
但「偶有雜訊」遠優於現況「什麼都搜不到」。

## 驗
· kbdb 87/87 綠(三筆斷言隨新契約更新:預設不再是「不過濾」)
· cypher-executor 9 failed/301 passed=**與改動前逐數相同**(git stash 前後各跑一次)⇒ 既有債
· portal os-split 自測 10/10 綠

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 18:00:43 +08:00
uncle6me-web fe0ee8296a portal 下載 Mac 版改給 DMG(拖進 Applications),不再給 zip
leo 2026-08-05:「Mac 強調要包裝成 application + dmg 的格式,拖進去就會放到
application,**解決之前直接在下載資料夾啟動造成更新問題**」

## 病(實查證實)
manifest 已有 mac_dmg 欄,DMG 也上傳了,**但 portal 下載按鈕仍指 zip**:
  DAEMON_MAC = 'ArcrunRAG-mac-unsigned.zip'
用戶端實抓該 zip → 解開就是 Arcrun.app ⇒ 很可能直接在「下載」資料夾雙擊啟動,
正是 t184(Oscar)那個「更新完又跳回舊版」的病灶。

## 修
· DAEMON_MAC → 'ArcrunRAG-mac.dmg'(**固定檔名**,否則每出一版都要改 code)
· Mac 安裝提示改成 DMG 的步驟:「開啟後把 Arcrun 拖進『應用程式』,再從啟動台開啟」
  (zip 版的話術是「右鍵→打開」,步驟不同不能沿用)

## 說明:App 端的更新邏輯本來就修好了
selfupdate.go 已改成更新「正在跑的那個 .app」(runningAppBundlePath),不寫死
/Applications ⇒ 放哪都更新得了。本次補的是**入口**:讓使用者一開始就放對位置。

## ⚠️ 殘項(未送達)
bundles repo 目前**只有帶版號的 DMG**(ArcrunRAG-v0.18.4.dmg),
**沒有固定檔名 ArcrunRAG-mac.dmg**(實測 404)⇒ 這行改動要等出貨時
一併產生固定檔名副本才會生效。出貨需 D20 開閘。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 16:35:02 +08:00
uncle6me-web b9b5d98d75 併 bge-m3 換代(#59)+補上分支漏做的另一半:Vectorize index 也換
leo 2026-08-05:「換 embed model 當然要合併,當然要換 vectorize,**原本的根本不能用**」
——我把已拍板的事當成待裁決,錯了。

## 為什麼非換不可(08-03 leo 拍板,5 組中文測資實證)
  @cf/baai/bge-base-en-v1.5   768   排序正確 2/5   margin -0.0413   1660ms  ← 舊
  @cf/baai/bge-m3            1024        5/5        +0.1410    959ms  ← 新
舊英文模型嵌中文:分數全擠在 0.65-0.81,區辨力接近沒有=**根本不能用**。

## 🔴 分支只做了一半,另一半我補上
feat/embed-model-m3-t59 只改 kbdb(embed.ts / types.ts / 測試),**完全沒動 deploy.ts**
⇒ 就算合併,安裝器仍會建 **768 維的舊 index**,新向量根本收不進去。
(git show f3af5d3 --stat 實查:只有 3 個檔,全在 kbdb/)

本次補上安裝器那半:
· KBDB_VECTORIZE_INDEX: arcrun-kbdb-embed → **arcrun-kbdb-embed-m3**(換名字,非只換維度)
· ensureVectorizeIndex: dimensions 768 → **1024**
· kbdb/wrangler.toml 說明與 metadata-index 指令同步改新名(照抄不會建錯 index)
· embed.ts docstring「768 維向量」→ 1024(分支漏改,會誤導)

## 為什麼要換「名字」不是只改維度
① 維度 768→1024,舊 index 收不進新向量
② 就算維度相同也不能沿用——不同模型的向量混在同一 index 比對出來是垃圾,
   而 #58(Vectorize vector delete 未接)代表舊向量刪不掉
   ⇒ **開新名字反而順手繞開 #58**,且新舊並存可回滾。

## 查證(歷史警察 Q0-Q3,非假設)
· git log --all -S"arcrun-kbdb-embed-m3" / -S"dimensions: 1024" 皆空 ⇒ 無分支改過,非重造輪子
· **metadata index 用同一個常數**(deploy.ts:426 ensureVectorizeMetadataIndexes)
  ⇒ t36 的四個 metadata index(owner_id/entry_type/source/library,Arcrun#11 根因修復)
  會自動建在新 index 上,**不會因改名而遺失**——這點特地查過,不是假設。

## 驗
· kbdb 測試 87/87 綠(含新增 4 項 embed-model-config)
· cli tsc --noEmit **零錯誤**
· 三處一致性機械確認:embed.ts=bge-m3/deploy.ts=dimensions 1024/index=arcrun-kbdb-embed-m3
· 踩到並記錄:註解寫 `**dimensions=1024**​/metric` 會因 `*/` 提早關掉 block comment(TS1127)

## 既有實例遷移(尚未執行,需對實例操作)
建新 index → 重部署 kbdb(binding 指新 index)
→ POST /embed/backfill {"reindex":true} 到 remaining=0 → 舊 index 可刪。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 13:46:33 +08:00
uncle6me-web 3d21bf0725 移植 t21 溯源 scheme 相容:kb:// 不造死鏈(救自已刪分支)
## 這條與 t195 直接相關
新 ingest 鏈(rag_ingest_card/rag_ingest_direct,就是我今天在修的收卡鏈)
寫的 metadata.source 是 **kb://<path>**,但 main 的 portal 溯源只認 gitea://
⇒ 卡片上傳成功後,**溯源連結一律失效**。

原修補在 fix/portal-source-scheme-t21(07-24),一個月沒併回 main。
該分支的檔案 console-ui/src/portal-ui.ts 已被重構搬走(UI 搬 CF Pages),
⇒ 不能直接 merge,**移植邏輯到現行的 console-ui/public/portal/index.html**。

## 三種 scheme 的正解(照原分支設計)
· gitea://<path>          舊 rag_ingest v2 → {base}/<path>,行為一字不變(不回歸)
· gitea:<org/repo>@<path> km-wiki-ingest 實形 → {base}/<org/repo>/src/branch/main/<path>
· kb://<path>             新 ingest 鏈實形 → **回空字串=維持純文字,不造死鏈**
  (雲端沒有對應網頁;另給 srcLocalPath() 取本地相對路徑顯示)

## 實測四情境(抽出函式跑 node)
gitea://docs/a b.md#3        → https://…/base/docs/a%20b.md           錨點捨棄+編碼
gitea:Leo/kb@notes/x.md#seg01 → https://…/base/Leo/kb/src/branch/main/notes/x.md 
kb://wiki/cards/policy.md    →                                      不造死鏈
srcLocalPath(kb://…#seg02)   → wiki/cards/policy.md                  

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 13:38:19 +08:00
uncle6me-web 5783420fcf 併 feat/harness-content-refresh:install-harness 升現世代+世代閘
leo 2026-08-05:「Arcrun 是它的父層,這裏的問題也影響 Arcrun RAG」。

main 的 cli/harness/ 停在 06-15,分支是 07-31 的升級(676 行實質改動):
· cli/scripts/check-harness-generation.mjs(130 行)=**世代閘**——
  與今天救回的 build-bundles 落後閘同族,都是防「發舊版給用戶」
· cli/scripts/build-harness-skill.mjs(64 行)
· CLAUDE.block.md/commands/arcrun.md/hooks/arcrun-guard.sh 交付內容升級

自動合併零衝突。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 13:35:56 +08:00
Leo 1eefce952b docs: 零件/binding PR 審核規範——結構性防架構錯誤(不靠記規則):A反過度工程(D27)/B binding層級(D28)/C contract/D驗證誠實/E部署資料鐵律/F走PR+機械化CI補強 2026-08-05 13:33:56 +08:00
uncle6me-web 6c5706ba7d 補上合併漏收的修改檔(t189/t181/CIS portal 本體)
上一筆 5d78806 我只 add 了新增檔(favicon 三件套),四個修改檔沒收進去
⇒ 合併內容只推了一半,t189 的 tenant 比對修復其實還躺在工作區。

本次補上(實測與已刪分支 HEAD 23f2fd4 逐檔相同):
· cypher-executor/src/routes/portal.ts      +81(t181 /portal/daemon/extract、t189 tenant)
· cypher-executor/src/routes/portal-data.ts +21
· console-ui/public/portal/index.html      +169(CIS 視覺、版本卡)
· cypher-executor/tests/portal-admin.test.ts +45

教訓:merge --no-commit 後要 git add -A,不能只 add status 裡看到的第一類。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 13:31:57 +08:00
uncle6me-web 5d78806806 併 fix/cis-round3-portal 進 main:CIS 視覺+t181/t183/t189 收口
## 為什麼現在併
leo 2026-08-05:「完工、下班,都要把所有東西整理乾淨,沒推的要推、沒合併要合併」
「至少在 gitea,我不要看到還有沒處理的 PR 或分支」。

這條是 arcrun 的**現役工作分支**(領先 main 15 筆、只落後 3 筆),
含的都是已驗過的東西:
· CIS 第三輪:portal 換色票/真向量 wordmark/favicon 三件套/裁淨版 lockup
· 底色與字形對齊 landing(Paper #FDFCFB/Canvas #F2F1ED,9 處明體→無襯線)
· portal 設定頁版本卡(落後紅點+一鍵更新,t154 免辨識碼)
· t176 套到 CIS 版 portal(刪 AI 設定區塊)
· t181:POST /portal/daemon/extract——daemon 萃取走 Workers AI(免金鑰)
· t183:語意搜尋補 min_score=0.75
· t189:/portal/daemon/extract 拿掉 tenant 等值比對(geek6688 萃取永遠 401)
· 移除誤入版控的 cypher-executor/node_modules 自指 symlink

落後的 3 筆=剛併進 main 的 PR#22(純文件)⇒ 自動合併零衝突。

## 測試對帳(合併前後比對,非只看合併後)
合併後:Test Files 4 failed | 22 passed;Tests 9 failed | 301 passed
合併前(純 main 同樣跑一次):Test Files 4 failed | 22 passed
⇒ **9 fail 是既有債,非本次合併造成**(與 D36 那次 commit 記載的既有債一致)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 13:29:15 +08:00
uncle6me-web 1b6b775c0e Merge commit '5491409003ff' 2026-08-05 13:21:09 +08:00
uncle6me-web 66f1b59f7f t176:雲端不再管地端 LLM 設定(leo 08-03 緊急)
leo 回報四件事:①雲端 Gemini 無法用 ②搞不清楚設定 AI 是設雲端還是地端
③地端不會萃、每個人都死掉因為都去抓 Claude ④雲端還要設置明明就有 Workers AI。
目標:「雲端裝好就能用,地端輸一個 Gemini API Key,然後就開始用。」

根因:extractor_config 的 KV key 由 portalTenant() 組出,而 portalTenant 是
**worker 層級**環境變數(portal.ts:43)⇒ **全租戶共用一把**。任一處設了 claude,
所有人的 daemon 都收到 claude;沒裝 Claude Code 的機器萃取全滅,而 portal 的
Claude 勾選框又恆 disabled(daemon 從未實作 report-capabilities ⇒ daemon_caps 永遠空)
⇒ 用戶自己解不開(awindhon 08-03 實證:雲端同步成功、金鑰有效、零張卡)。

本次(雲端側):
- POST /portal/daemon/config **不再下發** extractor/gemini_api_key/llm_model,
  只回連線欄位。⚠️ route 本身與 daemon/libraries 資料夾管理完全不動(leo 明確劃界:
  「雲端拉地端檔案夾部分不要刪,刪掉從雲端設置地端 LLM 選項部分」)。
- 移除 POST|GET /portal/admin/extractor(t122)——這是「雲端指定地端引擎」的入口,
  且無任何伺服器端驗證,打一下就能把全租戶設成 claude。
- 移除 POST /portal/daemon/report-capabilities(t131)——daemon 端從未實作該呼叫。
- 移除 t131 那組重複註冊的 /portal/admin/ai。**它是重複 route**:後段(arcrun-rag#10)
  另有同路徑一組,Hono 先到先比 ⇒ 舊的一直贏,後段修好的「金鑰真的寫進 credentials」
  形同死碼。保留後段那組(只管 Gemini 金鑰、走 storeCredential),並拿掉 Claude 偏好欄。
- portal 設定頁「AI 設定」整塊移除,改成一句話說明:雲端不需設定(Workers AI 免金鑰),
  地端請在同步小幫手的「AI 設定…」填 Gemini Key。順手清掉 main 上既有的 conflict 標記。
- 清掉隨之孤兒化的 helper(ExtractorConfig/AiConfig/DaemonCapabilities/
  syncExtractorFromAiConfig/readAiPref 等)。

測試:**相對 merge 前 main 基準線,新增失敗 = 0**(基準線本就 9 紅:console 藏書地圖 6/
portal HTML 殼 2/POST execute 1,皆與本次無關)。t122/t131 兩組測試改寫成
**回歸守衛**(斷言那些端點/欄位確實 404、確實不存在),不是刪掉充綠。
順手修 health bundle_version 測試與實作對齊(實作刻意省略該欄,測試卻期待空字串)。

未送達:本 commit 只到 code,尚未部署上線。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 22:11:45 +08:00
uncle6me-web eee3f93815 merge feat/workers-ai-chat-t152:雲端聊天改 Workers AI(免金鑰)
leo 08-03 緊急事件之一:「雲端還要設置明明就有 workers AI」。
本分支帶進 workers_ai_chat 種子(auth: binding,不需要任何金鑰),
讓「雲端裝好就能用」成立——用戶不必再去申請/貼 Gemini key 才能聊天。

測試差異(相對 merge 前的 main 基準線 9 紅):
- 新增 3 紅=/portal/admin/ai 的 Claude 偏好案,正是接下來要刪的功能(t176),隨刪一併處理
- 新增 1 紅=health bundle_version 回 undefined 而非 '',本分支既有小瑕疵,
  與本次緊急事件無關,另記待修
- 原 9 紅為 merge 前既有(console 藏書地圖 6/portal HTML 殼 2/POST execute 1)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 18:20:33 +08:00
Leo f3af5d3631 feat(#59): embed 模型可配置,預設換成 bge-m3——英文模型嵌中文會排錯
leo 2026-08-03 拍板「換 m3」。#59 本體(模型應可配置+index 版本化)。

## 為什麼換:實測,不是憑感覺
5 組中文問答測資(每組 1 問 + 2 段相關 + 3 段無關,無關的刻意放同一知識庫裡的其他主題),
margin = min(相關分數) − max(無關分數),margin ≤ 0 代表排序是錯的:

  @cf/baai/bge-base-en-v1.5      768   排序正確 2/5   平均 margin -0.0413  1660ms ← 舊
  @cf/google/embeddinggemma-300m 768   4/5   +0.1275   1174ms
  @cf/baai/bge-m3               1024   5/5   +0.1410    959ms ← 新(最好且最快)
  @cf/qwen/qwen3-embedding-0.6b 1024   4/5   +0.1381   3238ms

最刺眼的一組:問「知識庫問答為什麼要標出處?」→「會議室預約規則」0.7789
竟然高於真正相關的 0.7306。這正是 leo 2026-07-18 回報的「問 RAG 卻引用會議室規範」,
過去只被描述成「semantic 排名近乎雜訊」,其實是**排序錯誤**,而且五組錯三組。
英文 bge 系列的分數全擠在 0.65-0.81(區辨力接近沒有)——中文對它就是看不懂的 token。

## 改動
- `EMBED_MODEL` 常數 → `DEFAULT_EMBED_MODEL='@cf/baai/bge-m3'` + `embedModel(env)`,
  可由 `env.EMBED_MODEL` 覆寫(#59 要的「可配置」),空字串/空白視為沒設。
- 寫入端(embedOnWrite / backfill)與查詢端(semanticSearch)共用同一個 getter
  ——兩邊不同步是最惡毒的 bug:不報錯、分數全垃圾、外面完全看不出來。已加測試守。

## 換代必須換 index(安裝器那半在 arcrun-rag 同名分支)
① 維度 768→1024,舊 index 收不進新向量
② 就算維度一樣也不能沿用——不同模型的向量混在同一個 index 比對出來是垃圾,
   而 #58(Vectorize vector delete 未接)代表舊向量刪不掉
⇒ 開新 index 反而順手繞開 #58。

既有實例遷移:建新 index → 重部署 kbdb(binding 指新 index)
→ POST /embed/backfill {reindex:true} 重嵌到 remaining=0 → 舊 index 可刪。

驗證:kbdb 全套 87/87 綠(含新增 4 項);tsc 與基線相同(只剩既有的 auth.test.ts 那筆);
打包實跑產物 grep:kbdb bundle 只有 @cf/baai/bge-m3、舊英文模型 0 殘留。
2026-08-03 03:48:28 +00:00
Leo 47c6aaea03 feat(t152): workers_ai_chat 種子(auth: binding,免金鑰)+ 修 /init/seed 吃掉 3.12 欄位+ 修 D1 LIKE 長查詢 500
SDD: workflow-discovery 3.12/3.13(不是新規格;3.12 已 confirmed 並實作完成)

## 1) workers_ai_chat 種子(新)
Cloudflare Workers AI 走 env.AI binding ⇒ 用戶不必填任何 API 金鑰就能問答。
放種子表而非產品安裝器:「裝好後預設有哪些 recipe」是平台能力(rule 07 薄殼原則)。
換模型/換供應商=改這一筆 recipe,workflow 不動。

選型實測(1.4.4 實例,真實長度 RAG prompt,每個模型連跑 2 次):
  llama-4-scout-17b        2373/2173 ms   答案最完整、引用正確 ← 選它
  llama-3.3-70b-fp8-fast   3261/2147 ms   可用但波動較大
  mistral-small-3.1-24b    3560/3631 ms
  qwen2.5-coder-32b        3572/3353 ms
  gpt-oss-120b             1971/2295 ms   回應形狀不同,response 取不到文字
  gemma-3-12b-it            5018 帳號無權限
對照舊路徑 Gemini gemma-4-31b-it:同型提問 16.87 s,且吐整段英文思考草稿。

## 2) 修 /init/seed 靜默吃掉 3.12 欄位
3.12 給 RecipeDefinition 加了 body_template/response_map/auth/binding_name,
但 /init/seed 是**列舉欄位重建** recipe record ⇒ 不在名單上的欄位被丟掉。
最惡劣的地方是「哪裡都不會紅」:recipe 查得到、endpoint 對,只有跑起來像沒設定過。
與 08-02 syncManifest 吃掉 manifest.daemon 欄同型(教訓:東西還在不在也要進機械閘)。
加 tests/init-seed-recipe-fields.test.ts:拿掉修復會紅、補回會綠(已實測會擋)。

## 3) 修 D1 LIKE pattern 50 bytes 上限造成的 500
/entries/search?q=… 只要 q 超過 48 bytes 就回 HTTP 500,沒有錯誤訊息。
逐 byte 二分:48→200/49→500;中文 16 字→200/17 字→500。
判別實驗:q 固定 48 bytes、其他 filter 全塞滿讓 SQL 變很長 → 仍 200
⇒ 爆的是 LIKE 的 pattern('%'+q+'%' = 50),不是 statement 長度。
中文問句超過 16 字是常態,而 rag_chat 用整句問題當 q ⇒ 聊天對正常問句等於不能用。
(=InkStoneCo status.md 待辦第 1 條「KBDB keyword 長查詢會炸」的根因。)
修法:q ≤ 48 bytes 走原路(行為逐字不變),超過才拆詞/切 UTF-8 邊界片段。
kbdb 全套 83 測全綠(含新增 8 項)。

## 4) 順手
- 移除被 commit 進 repo 的 node_modules 壞 symlink(指向 leo Mac 的絕對路徑,
  害任何 fresh clone 裝不起來、切分支還會把裝好的蓋掉——本次撞了兩次)。
- pending-changes.md 加 P2 提案(fan-out 並行執行)+等裁決,未動引擎。

驗證:cypher-executor 新增測試 17/17 綠;tsc 與基線逐字相同;
全套測試失敗集合與基線**逐字相同**(基線 14 個失敗,本分支 t173 既有,非本次引入)。
2026-08-03 02:56:53 +00:00
Leo 5b983c47b8 Merge branch 'main' into fix/merge-main-into-batch-t173
# Conflicts:
#	console-ui/public/portal/index.html
#	cypher-executor/src/routes/health.ts
#	cypher-executor/src/routes/portal.ts
#	registry/components/kbdb_upsert_block/component.contract.yaml
#	registry/examples/km-wiki-ingest/workflow.yaml
2026-08-02 23:43:16 +08:00
uncle6me-web 36cdc8fba6 #10 補測試守衛+console 兩頁同族 fallback 一併拔
① tests/portal-admin-ai.test.ts(6 項綠)=**route 消失就會紅**的迴歸防線。
   這條 route 以前不存在、前端卻一直在打它 ⇒ key 從來沒存進去(藍字=假綠),
   所以最該守的就是「它還在不在」。覆蓋:未登入 401(**不是 404**=route 真的在)/
   非 admin 403/GET 回 has_key 布林且回應不含任何金鑰欄位(D36)/
   空 body 400 不假裝成功/只改偏好不誤報 has_key/同測試內寫→讀一致。
   註:vitest-pool-workers 預設 isolatedStorage=true ⇒ 跨 it 的 KV 會還原,
   驗來回一致必須在同一個 it 內完成(我第一版寫成跨 it,測試如實抓出來了)。

② console/index.html 與 console/dashboard/index.html 也有同一個寫死
   `|| "https://cypher.arcrun.dev"` fallback(與 portal 同族)⇒ 一併拔掉。
   靜態 config.js 保留無妨:build-ui-bundle.mjs 的 SKIP 會跳過它、改由 worker 動態產生。

驗證:cypher tsc 綠、全套 248 passed(9 既有失敗未變);
UI 產物 grep 寫死 fallback 歸 0;stage verify.sh 仍 11/3(無迴歸)。
2026-07-31 20:57:05 +08:00
uncle6me-web 0d49989c19 修 arcrun-rag#10:補 /portal/admin/ai(key 從來沒存進去的真因)+UI 已輸入態
真因(不是重裝洗掉,是從來沒存進去):前端唯一寫入路徑 POST /portal/admin/ai
**後端從來沒有這條 route** ⇒ 用戶填 key → 404 → 什麼都沒存,畫面卻像成功了(藍字=假綠)。
產物層鐵證:bundle tier2/ui grep 'portal/admin/ai'=1、tier2/cypher=0;兩實例實打皆 404。
反證(別改不壞的東西):走正確端點寫入 → 完整重裝 24/24 → credential 仍在、
rag_chat verdict failed→success ⇒ **重裝不洗 credential,資料層本來就是保留式的**。

① cypher 補 GET|POST /portal/admin/ai(routes/portal.ts)
   - POST 內部轉呼 credentials.ts 既有 storeCredential()=**唯一寫入路徑**,不另造第二套
   - GET 只回 has_key 布林,**永不回傳 key 本身**(D36)
   - credential 的 api_key 欄=租戶 slug(portalTenant),與安裝器 seedCredential 寫
     kbdb_internal_token 用的 ns 同值 ⇒ 同租戶分區,彼此查得到
   - use_claude_for_extract 存 KV(單一布林偏好,不為它開 KBDB template)
   - claude_available 目前無訊號來源(該由小幫手回報)⇒ **誠實回 false 不假綠**
   - 寫入失敗回 502 帶原因——本 bug 的教訓就是「不能讓前端以為存好了」
   - 兩者皆未提供 → 400,避免「看起來成功但什麼都沒做」

② 前端(console-ui/public/portal/index.html)
   - 拔掉寫死 `|| "https://cypher.arcrun.dev"` fallback ⇒ 缺 config.js 就顯示紅色橫幅明顯報錯。
     **寧可明顯失敗,不要靜默把用戶金鑰送去中央實例。**
     ⚠️ 更正我先前的錯誤診斷:config.js **有**正確注入(實查 leo 實例指向自己的 cypher),
     跨租戶錯置未發生;這條是未爆彈不是現行災情。
   - leo 規格:已存 → 唯讀「已輸入」+「修改」鈕(不顯示 key);按修改才變回輸入框;
     未存 → 一般輸入框。舊版只改 placeholder=讀起來像臨時提示,是它像 UI bug 的原因。
   - 只有真的送了新 key 才切回「已輸入」(單純改 Claude 勾選不誤報)

tsc 綠;三個 script 區塊 node --check 全過。
卷:system-dev/docs/3-specs/journeys/gemini-key-lost-on-reinstall.md
2026-07-31 19:56:47 +08:00
uncle6me-web c735b911a0 🔴 修教材反向教壞:skill §7 自打臉+補「分支怎麼算成功」+刪 publish 死代碼
端到端 haiku 考 0/3、1/3,**斷點在教材不在引擎**(另一 agent 取證,別重查):
探針零 code 實測 n=10→TRUE、n=1→FALSE 兩次 success;**考生的作品其實會動**
(amount=5000→true、amount=100→false 條件求值全對),但它以為跑不通而放棄改寫 code;
考生第二次自己指認「MCP 說明聲稱不支援 ON_TRUE/ON_FALSE,與實際系統行為不符」。

① skill `write_intent_workflow` **同一份前後打架**(我 08-01 只改了 §2 沒掃全篇):
   §2.1 教用 ON_TRUE,§7 第 1 條卻把 ON_TRUE 列為「不存在的邊」⇒ 教材自我否定。
   改:§7 只留 ON_FAILURE(真的沒有),並明寫「ON_TRUE/ON_FALSE/ON_BRANCH 是存在的,
   見 §2.1,本行舊世代已更正」。全篇 grep 過確認無其他矛盾。

② **補 §2.2「怎麼確認分支真的走對」**——這是「看到對的結果卻以為失敗」的直接解:
   看 verdict,且**走 true 路時 false 路節點不出現=正確行為不是失敗**;
   附 08-01 實撞案例,明講「只有一條路有輸出」不該判定壞掉。
   MCP instructions 同步加這段(比 skill 更前置,AI 一連上就讀到)。

③ #23 殘留清除(leo 08-01:「已經沒有 publish 了,零件等級一律走 PR,這條路封了」):
   `git rm mcp/src/tools/arcrun_publish_component.ts`+拔掉 registry.ts 的 import
   (註冊呼叫本來就已註解掉=純死代碼配活 import)。刪前 grep 全 repo 呼叫方:
   除本檔與 registry.ts 外,其餘命中全是 md/SDD 的歷史記載(不需動)。tsc 綠。
   替代路徑=`Leo/arcrun-components` fork→PR→人審,已寫進 registry.ts 註解。

SDD: workflow-discovery 3.11|CP: arcrun-usable 步驟 5
2026-07-31 18:37:48 +08:00
uncle6me-web eefbed98b6 合流 batch:步驟 3/4/5/6 併成一版出貨(同一顆 cypher worker,bundle 是 worker 粒度)
鐵律(08-01 血淚):CP 可分步驗收,但 bundle 是 worker 粒度——凡改同一顆 worker 的多步,
出貨前必先合流成一版再重生 bundle,否則 stage 只拿到一步(三條分支各自建 bundle 的坑)。

base=feat/step3-missing-guidance(最前緣,已含 step4+step6+t158-t162 prod 修)
merge=feat/step5-conditional-edges(引擎條件邊+recipe 三層+教材世代修)
2026-07-31 16:56:09 +08:00
uncle6me-web a9a47b7c37 🔴 步驟5 補斷點:教材說「引擎不支援條件分支」+意圖語法收不到分支標籤
備考 haiku 真考時自查發現的兩個斷點——**若不補,考試必掛,且掛的是我自己的教材**。
形狀=步驟1 那個「世代脫節」的翻版:能力做好了,但教 AI 的地方還停在舊世代。

斷點①:三處教材主動說「沒有條件分支」,還教了正好造成腹語術的替代法
  - mcp/src/mcp-handler.ts:44「**沒有** ON_TRUE/ON_FALSE——引擎目前不支援條件分支」
  - registry/skills/write_intent_workflow.md:39「不要寫 ON_TRUE/ON_FALSE…
    需要判斷時**寫成一個獨立節點**再接 ON_SUCCESS」← 這正是「判斷退回 code」的入口
  - registry/skills/INDEX.md:44「已知的坑:引擎沒有條件分支」
  ⇒ 三處全部更新成現況(三顆零件都輸出 data.branch、引擎依標籤選路、
    查零件回應附 branch_hint 照著接即可),並保留「不要寫 ON_FAILURE」(那個真的沒有)。

斷點②(更隱蔽,靜默失效):`graph-builder` 只認得 `對每個 X` 的參數化 label,
  `ON_BRANCH(branch_active)` 帶括號會落到 toEdgeType 預設值 **PIPE**
  ⇒ 我在 skill 教的寫法,編圖收不到,而且**不報錯**——AI 以為分支了、實際全走同一條。
  「教了語法但引擎不收」比沒做更糟,故與文件同批補上:比照 FOREACH 抽 iterator 的作法
  抽 branch 標籤(半形/全形括號都收),寫進 edge.branch。

新增 tests/intent-branch-syntax.test.ts(7 項綠):守「文件教的寫法,編圖真的收得到」
——ON_TRUE/ON_FALSE 不退化成 PIPE、中文「成立時/否則」、ON_BRANCH(標籤) 抽得出 branch、
全形括號、try/catch 標籤;零變化:ON_SUCCESS 仍是 ON_SUCCESS、對每個 X 的 iterator 不受干擾。

全套 234 passed(前 227 +7),失敗數維持既有 9 筆;tsc 綠。

SDD: workflow-discovery 3.11|CP: arcrun-usable 步驟 5

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-31 16:51:19 +08:00
uncle6me-web e688d805ea 步驟5 抽驗補證:三型分支用「真零件實跑輸出」端到端驗過(不再是推論)
總管 08-01 抽驗要求(正確):ON_CASE/ON_CATCH grep=0,我用 ON_BRANCH 通用承接,
但既有測試是用 Input 節點**手餵分支形狀**——那只證明「引擎依標籤選邊」,
**沒證明「真零件吐的標籤對得上」**。leo 特別點名 switch/try_catch,故補實測。

真零件實跑(wasmtime 跑 .component-builds/*.wasm,原文抄進測試當 given):
  if_control  active   → {"data":{"branch":"true","result":true},"success":true}
  if_control  inactive → {"data":{"branch":"false","result":false},"success":true}
  switch      case1    → {"data":{"branch":"branch_active"},"success":true}
  switch      case3    → {"data":{"branch":"branch_pending"},"success":true}
  switch      無匹配   → {"data":{"branch":"branch_default"},"success":true}
  try_catch   成功     → {"data":{"branch":"try","result":{"value":42}},"success":true}
  try_catch   失敗     → {"data":{"branch":"catch","error":"boom"},"success":true}
⇒ 三顆的標籤與 readBranch 讀的 data.branch **完全對得上**,非「理論上支援」。

新增 tests/branch-real-components.test.ts(8 項綠):把上列真輸出送進引擎,
驗 if 兩路/switch 多路(含**第 3 條 case** 證明第 N 路走對)+default/
try_catch ok 與 catch 兩路,每項都驗「該走的走、不該走的沒走」。

新增 tests/branch-hint-response.test.ts(7 項綠):驗逐顆查三顆的回應
自帶 branch_field/branches/edge_types/usage/example,並把 AI 實際看到的內容印出來
(總管要求「貼回應」)。另驗不分岔零件(http_request/code)無此欄=不加噪音。

全套 227 passed(前 212 +15),失敗數維持既有 9 筆未變;tsc --noEmit 綠。

SDD: workflow-discovery 3.11|CP: arcrun-usable 步驟 5

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-31 16:43:25 +08:00
uncle6me-web c9feb15a37 步驟5 驗收與 SDD 落帳:判斷骨架重寫實測 1201→350 字元、if×8→0(降 71%)
tests/step5-acceptance.test.ts(3 項綠):
- 多路分流+失敗路三條各自到位、全程零 code 節點、success=true
- 布林兩路(if_control)同樣零 code
- 字元數對照:舊寫法(判斷全塞進一個 code 節點的 JS)1201 字元 if×8
  → 新寫法(分支邊+recipe response_map 宣告)350 字元 if×0

誠實標記(寫進測試檔頭與 tasks,不讓後人誤讀):線上那顆 assemble(5509 字元 if×23)
住在 arcrun-rag 實例、本 repo 無其定義 ⇒ 本次是把它的**判斷骨架**以新能力重建成等價
工作流,證明判斷不必寫在 JS 裡,**不是**直接改寫線上節點。真正改寫與 haiku 逐顆查場景
=stage 端到端(features/09)才算數,故 3.13 標 ◐ 不標 。

tasks.md:3.11/3.12 標 [x] 附證據,3.13 標 [◐] 附待辦界線。
全套 212 passed(基線 179 +33 新),失敗數維持既有 9 筆未變;tsc --noEmit 綠。

SDD: workflow-discovery 3.11/3.12/3.13|CP: arcrun-usable 步驟 5

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-31 16:34:20 +08:00
uncle6me-web 5f5c0a89e2 步驟5 缺口②:recipe 補 payload/回應正規化/binding 三層(leo 三層模型的第③層)
問題:舊 recipe schema 只有 {canonical_id, endpoint, method, auth_service}(body 有但淺)
⇒ ①帶 body 的 API 只能繞過 recipe 把整包寫進 workflow code
  ②回應解析綁死單一供應商(rag_chat 的 finalize 2786 字元全在對付 Gemini 形狀)
  ③Cloudflare binding(env.AI/VECTORIZE/BROWSER/QUEUE)整類被「只認 HTTP+金鑰」的抽象排除
⇒ 換 LLM 供應商=改 workflow,而非換 recipe,違背「外部 API 只有一條一致的路」。

新增 lib/recipe-payload.ts(純函式,好測):
- renderBodyTemplate:遞迴插值,單一 {{x}} 保留原型別、混合文字拼字串、
  支援 dot path、取不到保留原樣(不靜默吞掉,看得見才好 debug)。
  語義刻意與 graph-executor 的 interpolateData 一致,不新造第二種插值行為。
- applyResponseMap:text_path 取值/thinking_model 剔除 thought=true 取最後一個非 thought/
  answer_marker 用 lastIndexOf(自檢清單內文也會提到標記)/strip_prefixes 循環剝殼
  (實撞三型「Draft: 【答】」「* 【答】」「Answer: * 【答】」,單趟剝不乾淨)。

RecipeDefinition 加四個**全選填**欄位:body_template/response_map/auth/binding_name。
- component-loader:body_template 優先於 body,兩者皆無才沿用 ctx 當 body(既有行為)
- response_map 有設才附 text 欄,未設原樣回傳 ⇒ 既有 recipe 行為完全不變
- 新增 makeBindingRecipeRunner+pickRecipeRunner:auth='binding' 走平台 binding(免金鑰、
  開機即可用),其餘一律走既有 HTTP 路徑。binding 缺綁定/無 run() 時回可操作錯誤,不假綠。
  這型不是為 Workers AI 開特例——一次打開 env.AI/VECTORIZE/BROWSER/QUEUE 整排。

payload 用法要「查得到」(同 branch_hint 動機,leo 08-01 的 n8n 式逐顆查):
buildPayloadHint() 讓 recipe 的查詢回應說得出「payload 怎麼填、回應怎麼取值、
認證誰負責」,wire 進 discover 混搜/legacy 逐顆/target=recipe 三條路徑。
金鑰鐵律 D36:hint 只說「走 auth recipe X,金鑰由系統注入、你不必也不該填」,不吐值。

測試:tests/recipe-payload-response.test.ts 14 項全綠(含三家形狀 Gemini/Claude/Workers AI
用不同 path 都取得出文字=換源=換 recipe 的實證;未設 response_map 原樣回傳的相容性)。
全套 209 passed(前 179 +30 新),失敗數維持既有 9 筆未變;tsc --noEmit 綠。

SDD: workflow-discovery task 3.12|CP: arcrun-usable 步驟 5

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-31 16:31:58 +08:00
uncle6me-web 323ccc8475 步驟5 缺口①:引擎通用具名分支邊(ON_TRUE/ON_FALSE/ON_BRANCH)=Arcrun#5 根治
問題(「全變成 code」的根):if_control 回 {result, branch} 卻沒有邊讀得懂它,
圖的邊只有 ON_SUCCESS/IF/FOREACH ⇒ 就算照規矩用零件,仍得寫 code 判斷走哪條。
leo 08-01 追問「你改了 if,有改 switch 嗎?switch 更嚴重」——確認 switch(N 路)
與 try_catch(try/catch) 同病,故一次做成通用機制,不留「為 switch 再改一次」的債。

設計:三顆流程控制零件的 output_schema 本來就都收斂到同一形狀 data.branch: string
(if_control→true/false;switch→case 名或 default_branch;try_catch→try/catch)
⇒ 引擎只需「依標籤選邊」一個機制 ON_BRANCH;ON_TRUE/ON_FALSE 是布林路的語法糖,
底層同一條路(測試已證等價)。讀不出分支=不走(誠實,不亂挑一條)。

- types/schemas/constants:新增三邊型(純新增,既有列舉不動)+ GraphEdge.branch
- graph-executor:readBranch() 依 data.branch → branch → data.result → result 四層相容
- 中文語意詞:成立時/為真時=ON_TRUE,不成立時/為假時/否則=ON_FALSE,BRANCH=ON_BRANCH

分支用法要「查得到」(leo 08-01:AI 可能像 n8n 那樣逐顆查、自己組圖):
新增 lib/branch-hints.ts,讓 if_control/switch/try_catch 的查詢回應自帶 branch_hint
(branch_field/branches/edge_types/usage/example)——只看這一顆的回應就知道怎麼接下一步,
不必回頭讀 skill。四條回應路徑全wire:catalog found/legacy 逐顆/步驟4 substitution/
target=component 名字搜尋(=n8n 式那條)。不分岔的零件不加此欄,避免噪音。

測試(先寫測試再改引擎,紅線要求):tests/conditional-edges.test.ts 16 項全綠
——if 兩路/switch 多路+default/try_catch 成功與失敗路/語法糖等價/
context 傳遞/同分支 fan-out/無匹配不走/混合邊時 PIPE 不受影響/schema 放行。
零變化保證:195 passed(前 179 +16 新),失敗數維持既有 9 筆未變(console/portal
HTML 資產未建置+executor 斷言字串漂移,皆與本次無關);tsc --noEmit 綠。
現存 workflow/example 用到新邊型=0 筆(grep 實查)⇒ 既有行為不可能被改動。

SDD: workflow-discovery task 3.11|CP: arcrun-usable 步驟 5

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-31 16:26:26 +08:00
uncle6me-web 9f777ff43e t161+t162 修 leo prod 實撞兩病:庫清單靜默空白/「需要更新」假警報迴圈
t162(假警報,實錘):daemon cloudVersionStale() 讀 cypher /health 的 bundle_version
比對 minCloudBuilt(2026-07-28),但 **/health 從來只回 {ok:true}、沒有這個欄位**
⇒ 恆讀到空字串 ⇒ 恆判 stale ⇒ 「知識庫需要更新」永遠不消失,重裝也沒用
(leo:「按下就跳到 install,但重新更新後並不會消失,這個訊息是幹嘛的?」)。
安裝器其實一直有注入 ARCRUN_BUNDLE_VERSION(worker.js:819),只是沒人吐出來。
修:/health 讀該 var 誠實回報(未注入則省略欄位=老實例,daemon 判 stale 是對的)。
新 bundle 注入值 2026-07-31+<commit7> ≥ 2026-07-28 ⇒ 重裝後警報自然消失。

t161(庫清單空白):portal 前端 guard401 → dropSession() **靜默**清 token 跳登入頁,
畫面不說任何原因 ⇒ 用戶看到的是「庫不見了」而非「請重新登入」(leo:「實際上根本
沒連上任何庫,清單空白」)。修:dropSession 帶原因寫進 #login-status——
「你的登入已過期,請重新登入(你的資料都還在,不會遺失)。」
資料側無恙(portal_library records=2 已驗),純讀側 session 過期+靜默 UX 缺陷。

驗:cypher tsc 全綠;vitest 9 failed/187 passed=基線不變。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-31 15:34:27 +08:00
uncle6me-web 46afea83c2 步驟1 最後一筆:install-harness 交付內容升級到現世代+世代閘
管道本來就是好的(install-harness 功能完整、冪等),**過時的是內容**:
harness skill(4066B)grep「意圖」「>>」= 0 命中,只講世界觀/別寫 Python
⇒ 新裝的封測者拿不到步驟 1 的核心教材(`>>` 意圖語法)。

■ 單一真相源:harness skill 改為建置期由 registry 複製
  build-harness-skill.mjs=head + registry/skills/write_intent_workflow.md 正文 + tail。
  選「建置期複製」的理由:npm files 只收 harness/,registry 不進套件;
  symlink 在 npm pack 與 Windows 不可靠。產物 commit 進 repo(npm 裝的是產物、不跑 build)。
  head/tail 是 harness 專屬(CLI 語境入口/acr 指令表/暴露同意/誠實鐵律),
  install-harness 的 copyTree 跳過 .head/.tail,不鋪進使用者專案。

■ 其餘三件逐份對照現世代事實後更新(過時的直接刪,不留死代碼)
  - CLAUDE.block.md:補 >> 意圖語法、not_found 兩條路、零件 vs recipe 分型、
    腹語術紅線、金鑰只拿名字
  - commands/arcrun.md:步驟改成「先寫意圖 → 丟去查 → 再寫 YAML」,補 acr search/validate
  - hooks/arcrun-guard.sh:**正路提示改為指向 arcrun-mindset Skill +意圖語法**
    (呼應「hook 沒提 skill 反而把 AI 導向 repo 文件」的教訓);
    新增 code 節點腹語術提醒,settings.fragment 補 Write|Edit|MultiEdit matcher

■ 世代閘(防再度脫節)
  check-harness-generation.mjs 檢查四件交付物的現世代指紋,缺指紋 exit 1,
  掛進 npm run build(prepublishOnly 因此也擋)。
  反向驗證:把 skill/CLAUDE.block 換回上一代 → 兩者都被擋下並逐條點名缺哪個指紋。

■ 驗收(考生 haiku/受測物=環境)
  乾淨臨時目錄 acr install-harness → 四件鋪好;重跑冪等(全檔 md5 不變、
  CLAUDE.md 66 行不變、hooks 條目 2 不變、arcrun 區塊仍 1 個)。
  haiku 只讀該目錄的 CLAUDE.md+SKILL.md(明令禁讀 ~/.claude、禁上網;
  兩份教材 md5 與大小均不同,可證非考本機那支)答十題
  → grade-step1.sh **10 / 10 通過**(判分器同時反向驗證仍會抓
  ON_TRUE/ON_FAILURE/第一節點非 input)。
  npm test 18/18、tsc 綠。

SDD:workflow-discovery/tasks.md 3.11

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-31 15:29:17 +08:00
uncle6me-web 341bcb13c8 t160 測試跟上規格:人工建庫 POST → 404(庫只從 daemon 同步來)
舊測試驗「POST 建庫 200+重複 409」=已刪端點的規格;vitest 回基線 9 failed/187 passed。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-31 14:57:22 +08:00
uncle6me-web 832f270f52 Merge branch 'feat/step6-success-rate' into feat/step3-missing-guidance 2026-07-31 14:54:25 +08:00
uncle6me-web 5c5109cd45 Merge branch 'feat/step4-node-substitution' into feat/step3-missing-guidance
# Conflicts:
#	system-dev/docs/3-specs/workflow-discovery/tasks.md
2026-07-31 14:54:25 +08:00
uncle6me-web e744ad1f96 t160 世代債清除(leo:「如果你會搞不清楚,就把錯的東西刪掉」)——repo 只留一個 UI 世代
事故鏈(07-31 深夜,leo 刷新撞到):t159 重打包 staging bundle 時,build-ui-bundle
吃了 feat/step3 分支的 console-ui/public/——該分支基底早於 main d7fab7c
「拿掉登記新庫」,public 停在舊世代 ⇒ youlin 實例 UI 被打回被淘汰的
「登記新庫」人工表單典範(07-27 mistakes 已記帳的 src/public 雙世代債,
這次由「分支副本停舊代」路徑引爆——債沒還,任何一個部署路徑都會引爆它)。

刪掉的(舊世代,git rm 實體刪除):
- console-ui/src/(console.ts/console-dashboard.ts/portal-ui.ts)=07-22 從
  cypher-executor 搬出的 renderer 快照,自 07-27 起與 public/ 真身脫鉤、只會
  重產舊 UI
- console-ui/scripts/build.mjs=從上述舊 renderer 重產 public 的 build 步驟
  (deploy.mjs 原自動跑它=引爆器本體)
- cypher POST /portal/admin/libraries(人工建庫端點)=死端點(e28e190 起 UI
  零呼叫;leo 規格「要直通 daemon,同步,沒有登記這回事」)。GET 列表/PATCH
  管理/t159 daemon 自動登記照舊

新唯一源:console-ui/public/=手改演進的真身(本 commit 同步到 main e28e190
世代+註解字樣改寫達成「登記新庫」全檔 0 命中);deploy.mjs 改為直接託管
public(無 build 步)+世代閘(缺「不需要人工新增」或含「登記新庫」拒部署)。

驗:tsc 全綠;deploy.mjs node --check 過;重打 ui bundle 242KB 過世代閘,
指紋「登記新庫」=0/「不需要人工新增」=1。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-31 14:36:48 +08:00
uncle6me-web cae0d7be3b 3.9 落帳+pending-changes 提案:workflow export/import 一等公民化(D35 ③,接線待 confirm)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-31 14:15:33 +08:00
uncle6me-web 062757f1b1 t159 修 portal 庫目錄空:補上 daemon 一直在打、但從未存在的登記端點
診斷更正(重要):kbdb 裡 50+ 筆 entry_type=value(content=資料夾名)**不是孤兒**——
那是 91 筆 triplet records 的 library slot 正常構造(records 全部組裝完整,資料同步
一切正常,無失敗無重試)。真病灶=**POST /portal/daemon/libraries 這個 route
從來不存在**:daemon registerLibraries(arcrun-tray main.go:505,t52「用戶可以看到
我有 2 個庫」)連線精靈時打它 → 404 → 被 daemon「失敗不擋連線」設計靜默吞掉
→ portal_library 登記簿永遠 0 筆 → portal「庫目錄管理」空。

- 新 route 契約照 daemon 既有呼叫:{email, password, libraries:[{name, display_name}]}
- 帳密驗證沿用 /portal/session 同一套(isLocked/findUserRecordId/verifyPassword/
  recordLoginFail;daemon 只在精靈那刻拿帳密不存)
- 冪等:listRecordsByTemplate 比 name,已登記跳過(重跑精靈不堆重複)
- 庫名走 isValidLibraryName 同閘(拒 "*" 與非法字元)

驗:tsc 全綠;youlin 實例資料層已手動補登記(探針 record PATCH 成
youlinhsieh-test1+新建 test2,portal_library count=2);route 部署驗證
隨下批 staging bundle(假帳密應回 401 而非 404)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-31 14:07:05 +08:00
uncle6me-web 9c9aff0046 t158 P1 素材:workflow export/import 原語(definition 端點+CLI 命令,未接線)
leo 07-31:「你要做的就是一個叫 export,另一個是 import……現在如果我要把我做的
工作流分享給同事,我要怎麼 export?他要如何 import?是缺了功能用 search 來湊嗎?」

- cypher GET /webhooks/named/:name/definition:吐可攜定義(graph+config+description
  =record 原樣)——export 的引擎端;import 端直接 POST /webhooks/named 即送進任何實例
- cli/commands/workflow.ts:acr workflow export <name>(definition→.workflow.yaml,
  flow 從 graph.edges 反推供人讀)/import <file>(graph 直 POST,零編圖零 search
  零存在性驗證=V2 純複製;手寫 yaml 無 graph → 指去 acr push)
- ⚠️ 未接線:index.ts 尚未掛 workflow 命令組(P1 收尾:接線+分享場景 A export→
  B import 驗收+parser 共用模組化)

安裝器側 P0(rag-installer 5a6539d)已用同形狀(打包期預編 graph+純上傳)上 prod。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-31 13:49:23 +08:00
uncle6me-web d48f83ae6f t159 步驟4 意圖節點→真實零件/recipe 替換+/cypher/search 加 target 指定搜尋對象
CP arcrun-usable 步驟 4(目的:AI 只要填 payload——系統把「傳到 telegram」
翻成 http_request+recipe telegram_send)+leo 07-31 追加:
「難道我不能指定要搜尋工作流或節點或 recipe 嗎?」

替換(只動 discover,t158「部署≠發現」邊界不碰):
- 兩庫 exact 落空後,在 t158 一次抓好的清單記憶體內媒合,零新增 round-trip
- 規則 A 服務詞→recipe:名字全部服務詞命中同一 recipe 且唯一才換
  (google_slides_create 不被 google_sheets 誤吃)
- 規則 B 強欄位斷詞→零件:canonical/display/aliases 強命中×10+弱命中,
  需至少一強命中且分數唯一最高(aes_encrypt 無強命中不換)
- 換到=status resolved+substitution{from,componentId,recipe,reason},
  cypher 圖節點直接帶真實 componentId;換不到照舊 not_found+3.7 指路

target 參數(各走既有機制,不新造第二套搜尋):
- triplets+target=component|recipe=只查該庫
- query+target=名字搜尋:component→registry /components/search(=MCP
  arcrun_search_components 同路);recipe→私庫 RECIPES KV(回應註明公庫走
  arcrun_recipe_search);workflow→新抽 lib/workflow-search.ts,
  GET /workflows/search 與 target=workflow 共用(=arcrun_search_workflows 同路)
- 防呆:compile+target 400/target=workflow 吃 query 不吃 triplets/非法 target 400

驗(本地 wrangler dev,registry 種 20 合約+init/seed 10 recipe):
- 「判斷有沒有新資料 >> ON_SUCCESS >> 傳到 telegram」→ if_control(resolved)
  +telegram_send(substitution.componentId=http_request)=feature 06 驗法過
- 機械考 27/27 全綠(01 組×5+03 組×4 迴歸+06 組×8+target×8+compile 迴歸×2)
- 冷啟第一發 94ms、熱 8–13ms(t158 病史對照:舊 25.7s);compile 39ms unchecked 照舊
- tsc 全綠;vitest 9 failed/179 passed=t158 基線完全相同

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-31 13:49:09 +08:00
uncle6me-web ea1c0571c1 步驟6 執行統計回寫:每顆零件跑完回寫 success_rate(task 29,design.md「執行統計設計」)
- registry 新增 POST /analytics/record:ANALYTICS_KV 計數器(stats:{hash_id}:{version})
  為唯一真相源,衍生值(success_rate/avg_duration_ms/call_count)回填 comp: 記錄
  ——查詢讀取端讀哪就寫哪,不開第二真相源。KV 無 CAS,誠實標註非原子。
- cypher-executor execution-evaluator 從 stub 改真實作:執行收尾(/cypher/execute
  成功與 ExecutionError 路徑+webhook 路徑)對 trace 裡每顆 Component 節點
  fire-and-forget 回寫,waitUntil 包、不增加執行同步延遲(仿 recordRecipeStats 慣例)。
  成敗判定=trace error 或 output.success===false(makeHttpRunner 非 2xx 不 throw)。
- registry 位置沿 search-nodes 慣例:REGISTRY_BASE_URL 覆蓋,未設走 wasmWorkerUrl。
- 新增單測 13 個全綠;本地雙 wrangler dev 端到端實測:http_request 跑 5 次
  (3 成功+2 失敗)→ success_rate 1→0.6、call_count 0→5,/cypher/search 同步可見。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-31 13:45:05 +08:00
uncle6me-web 7e631a890a t158 迴歸修復「部署≠發現」:複製路徑回毫秒級純編圖,誠實化只留在 discover
leo 07-31 定調:「這裡只是複製一些工作流的 data 過去,沒有要在這裡驗證,難怪這麼慢。
就算是我自己寫了錯的工作流,也可以跑跑看,如果錯誤就修改,
沒有說有錯誤還要一個個驗證這回事。」

定性=迴歸非新設計:部署路徑純複製是既有設計(arcrun-rag installer/src/index.js:13
「workflows.json 是既有 workflows/*.local.yaml 的搬運(打包期抽 flow/config)」+
arcrun-rag wiki「workflow 打包期預編成 workflows.json(worker 免帶 parser)」)。
5cadc60 起誠實化漏進 /cypher/search ⇒ 複製路徑也逐節點跑兩庫查詢+相似搜尋
(每 missing 節點 1+9 次 HTTP+recipe KV 掃)⇒ 冷實例 8 節點實測 25.7s、
安裝器 15s timeout 必炸(leo stage 實走 rag_takedown_direct aborted)。

改動:
- /cypher/search 加 mode:compile=純編圖零查詢(安裝器/acr push 複製路徑);
  discover=誠實查詢預設(AI 問「有沒有」的既有契約,not_found+分型指路全保留)
- /cypher/execute 一律 compile(存在性由 component-loader 執行時決定=原權威)
- compile 的節點 status 標 unchecked(誠實「沒查」,不回假 found)
- discover 批次化:registry 新增 GET /components/catalog(一次回全目錄含
  input_schema,補 CP2-B「沒有列表端點」缺口)+recipe 清單一次抓,
  存在判定與相似度全記憶體比對;舊 registry 無 catalog 端點 → 退回逐顆(相容);
  registry 整個查不通 → unknown 照舊(不誤判 not_found)
- cli push 帶 mode:compile+拔 missing 擋(push 不看 missing;要問有沒有走 validate)

驗(本地 wrangler dev 誠實環境,registry 種 20 合約):
- compile 編圖:graph_neighbors 39ms/rag_chat(11節點) 3ms/rag_ingest_card 2ms/
  rag_takedown_direct(8節點) 3ms——回迴歸前毫秒級
- 安裝器 pushWorkflowTo(mode:compile)4/4 ok(44/7/7/5ms)
- 故意引用不存在零件的 workflow:部署 ok=true,trigger 執行時誠實報
  「找不到零件…」+可用零件清單(部署≠發現實證)
- discover 契約:邏輯名 missing=2+not_found+suggestion(21ms);
  頂層 verify.sh 01 組 5/5+03 組 4/4 全綠
- cypher+registry tsc 全綠;vitest 9 failed/179 passed=5cadc60 基線完全相同

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-31 13:31:33 +08:00
uncle6me-web 7e87a3336b 3.8 新增 write_recipe skill——recipe 指路終於有目的地
📋 SDD:workflow-discovery task 3.8(3.7 的 suggestion 指 skill write_recipe,
之前是空地——指路會指到不存在的 skill)。

- registry/skills/write_recipe.md:從真 code 反推,不是憑空教學——
  schema=routes/recipes.ts RecipeDefinition(canonical_id/endpoint/method/auth_service…);
  真範例=api-recipe-seeds.ts 的 telegram_send(URL path 注入)+gmail_send(service account);
  auth recipe 必填欄位(required_secrets 的 help_url 必填)照 POST /auth-recipes 驗證邏輯;
  常犯錯收錄實錄(sheets append PUT→POST、telegram auth recipe 漏種、金鑰只准名字 D36)
- INDEX.md 補 write_recipe 入口;「/cypher/search 假 found」坑改標已修(2026-07-31)
- write_intent_workflow.md §6 status 表改 not_found 契約+suggestion/similar_* 欄說明

安裝器 seed:registry/skills/*.md 由 sync-registry-to-kbdb.py 自動收,新檔即納入。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-31 11:56:30 +08:00
uncle6me-web 1794072179 3.6/3.7 查詢誠實化:兩庫都查、缺件回 not_found+分型指路 suggestion
📋 SDD:workflow-discovery task 3.6/3.7(leo 07-31 步驟 3 考題)
契約以頂層 arcrun-usable/verify.sh 01+03 組為準。

- 3.6 recipe 納入 /cypher/search:零件 registry 落空後查 RECIPES KV
  (resolveRecipe,執行鏈同一套解析);recipe found 附 source/description/endpoint
- 3.7 缺件分型指路:status 契約定 not_found(verify grep),suggestion 欄——
  名字含服務詞(google/telegram/…)=外部 API 樣貌 → 指 skill write_recipe;
  含計算詞(encrypt/hash/…)=計算原語樣貌 → 指 skill add_new_wasm_component 投稿 PR;
  判不出型誠實說判不出、兩條路都給。規則簡單可解釋(不接 LLM),判準在 code 註解
- 相近候選:not_found 附 similar_components/similar_recipes——全名搜不中就斷詞
  (ASCII 詞+中日韓 2-gram)打 registry /components/search,
  「判斷有沒有新資料」媒合到 if_control(leo:AI 不用知道零件存在)
- 修頭節點假 found:resolveNodeRole 把無入邊頭節點判 Input,searchNodes 對 Input
  無條件 found ⇒ 「aes_encrypt >> … >> code」的頭被掩蓋。改為只有字面
  input/trigger/…/output 名才短路(isVirtualIoName),真名字照查兩庫
- registry 查詢層補 input_schema/output_schema 透傳:KV 一直有存
  (indexOnlyComponent),toComponentRecord 丟掉 ⇒ /cypher/search 給不出
  「怎麼填 payload」。順手補 REGISTRY_BASE_URL 可選覆蓋(比照 KBDB_GRAPH_URL 慣例,
  本地 dev/self-hosted registry 掛別處時用)

驗:cypher-executor+registry tsc 全綠;vitest 9 failed/179 passed=與 5cadc60
基線完全相同(既有債非本次造成);本地 wrangler dev(cypher 指本地 registry,
KV 用 index-only 種入 20 份 repo 合約)跑頂層 verify.sh 01+03 組 9/9 全綠,
含 telegram_send 種入後 recipe found 路徑實測。部署到實例後需對真環境重驗。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-31 11:56:15 +08:00
uncle6me-web 747dc3f843 3.7 補指引目的地+3.8 write_recipe skill(recipe 指路現為空地) 2026-07-31 11:38:26 +08:00
uncle6me-web c56863a7e0 3.6/3.7 補設計基調:回覆重點=缺哪些;兩庫都搜;驗收=機械綠→haiku 真考 2026-07-31 11:36:01 +08:00
uncle6me-web fc6c98cd58 workflow-discovery 補 3.6/3.7(頂層交棒):recipe 入查詢+缺件分型指路——leo 07-31 定義步驟 3 考題 2026-07-31 11:26:20 +08:00
uncle6me-web 0a426c4e16 fix(cypher): CORS origin 空值放行——CLI/curl/MCP 無 Origin 標頭時不再 500 2026-07-31 10:50:57 +08:00
Leo b8ca98cb49 MCP 認證改用 Portal 帳密,廢掉 MCP_OWNER_SECRET(leo 指定做法)
leo:「claude 裡有一個直接輸入帳密連線的,為什麼不用那個?跟他輸入 portal 的帳密一樣不就好了?」

為什麼換(三個封測撞出來的實際問題):
① 沒人給得了封測者——安裝器產生後從不顯示(完成頁 grep「Owner 祕密」=0),
   CF secret 又唯寫讀不回 ⇒ 用戶卡在同意頁,只能找 leo 手動 wrangler 覆寫
② 多一把要記的金鑰——違反「拿一把金鑰就很難了」
③ 全實例共用一把,無法分辨誰連上來(企業多人版必要)

風險評估(leo 判斷,總管原本誇大成「繞過帳密的旁路」已認錯):
secret 要貼進 claude.ai(本身有帳密保護)⇒ 洩漏 secret 與洩漏 portal 帳密風險相同。

改動:
- consent.ts:一個「Owner 祕密」欄位 → email + password 兩欄
- routes.ts POST /authorize:constantTimeEqual(MCP_OWNER_SECRET)
  → 走 CYPHER_EXECUTOR binding 打 /portal/login(認證下沉到唯一真相源,
    同樣吃它的節流與停用檢查)
- routes.ts GET /authorize:移除「未設 MCP_OWNER_SECRET → 503」
  (那是「每個封測者都死在這頁」的直接原因)

驗:tsc 零錯誤;已部署 youlin。
2026-07-31 00:44:15 +08:00
Leo cda1c7b261 instructions 補「你配備了 Arcrun、別上網搜」+新增 skills/INDEX(LLM wiki 式導航)
leo 兩個洞察:
① 「整個 arcrun instruction 很像 LLM wiki…它會拿到一個 index 把所有文件說明都塞給它,
   它不會就可以查,範本全部寫在裡面」
   ⇒ 對照 wiki 的 push/pull 分法:我原本只做了 push(instructions 開場),
     漏了 pull(INDEX 導航)。補 registry/skills/INDEX.md:
     什麼症狀查哪支 skill/查現成零件recipe workflow 的九支工具/四個已知的坑/腹語術紅線
② 「它不去查就不知道…如果它去上網搜因為它不知道自己有配備呢?」
   ⇒ 實測確認 instructions 是 **push**(本 session system prompt 內就有藏書地圖那段)
     ⇒ AI 不會去上網搜,但原本的 instructions 沒說「你配備了什麼」
     ⇒ 開場補:Arcrun 是什麼/你現在就有完整能力/不要上網搜(網路上沒有)

驗:tsc 零錯誤。⚠️ 未部署——實測總管 token 只有 account(read),無法部署任何 CF worker。
2026-07-30 22:38:37 +08:00
Leo fac7d6c9bb AI 一連上 MCP 就知道從哪開始(leo 追問揪出的最後一哩)
leo:「arcrun_get_skill 放在 MCP 裡,現在人類說『幫我用 arcrun 寫 xxx』,
Haiku 會說『arcrun 是什麼?我看看』然後發現有個 arcrun mcp 就去執行?
如果不會,要寫什麼在外面讓它一聽到就知道要用這些資源?」

實測答案:**不會**。
① 總管寫的指引沒有任何入口指向它(真實 AI 找不到)
② MCP 五個 skill 沒有「怎麼寫意圖工作流」這支
③ AI 只看到 41 個工具名 → 自己猜 → 很可能直接 push_workflow 瞎編,
   或去讀 registry/examples 那 8/13 引用不存在零件的壞範例

兩件修法:
1. registry/skills/write_intent_workflow.md — 把指引變成 AI 拿得到的 skill
   (範本全取自實跑 verdict=success 的四支 workflow;含「查詢現在回假 found」的已知限制警告)
2. mcp-handler.ts instructions 開場指路 — instructions 是唯一「AI 一連上就必看」的欄位
   ⇒ 開場就寫明五步順序(先讀 write_intent_workflow → whoami → 查零件 → list_skills → 缺件怎麼補)
   +邊只有兩種、第一個節點是 input、不要因查不到就改寫 code
   ⚠️ 靜態常數:KBDB 掛了也必出現(守「不擋連線」鐵律)

驗:tsc 零錯誤。 待部署後由 leo 真的問 haiku「幫我用 arcrun 做 X」驗證它會不會自己讀 skill。
2026-07-30 22:29:28 +08:00
Leo 0686d39fa1 pending-changes:兩個真缺口提案(引擎條件邊/recipe payload+response+binding)
來源=頂層 CP arcrun-usable 逐步查證。依 leo「照 SDD 做事、照 CP 排順序」,
CP 不得自帶任務 ⇒ 這兩件必須進 SDD 才能做,先走 pending-changes 等 confirm(D35)。

缺口①引擎條件邊:if_control 回 {result,branch} 但 cypher-executor grep ON_TRUE|ON_FALSE=0
⇒ 用了零件仍得寫 code 判斷=「全變成 code」的根。Arcrun#5 於 07-04 發現至今未修。
SDD 查證:7 個字面命中全是「部署分支」等別義,條件邊本身完全沒設計過。

缺口②recipe 缺 payload/response 層:schema 只有 {canonical_id,endpoint,method,auth_service}
⇒ telegram_send 自述「body 帶 chat_id+text」但存不住 body ⇒ 只能繞過 recipe 寫進 workflow。
要補 body_template/response_map/auth:binding 三層。SDD 查證 17 命中全是別的 payload。
2026-07-30 22:06:48 +08:00
Leo 156cdcbe7b chore: 刪掉兩個死零件與其壞範例(leo:這裏的每個不要的零件還存在啊)
📋 SDD:workflow-discovery(active)/對應 CP arcrun-usable 步驟 5「死代碼清除」

刪除(git rm 保留歷史):
- registry/components/km_writer      + .component-builds/km_writer
- registry/components/kbdb_upsert_block + .component-builds/kbdb_upsert_block
- registry/examples/km-wiki-ingest(唯一引用 kbdb_upsert_block 的範例,Mira 時代同源產物)

為什麼刪(Arcrun 自己的 06-mindset.md §1 早已載明):
「mira 的 claude_api / km_writer 就是這樣被錯做成零件的(其實是自用服務膠水)」
+ kbdb_upsert_block 是「一個 CRUD 操作一個零件」(leo:總不能每個都建一個零件)
+ 兩者綁的 Mira 已於 2026-06-29 蒸發=死零件。

刪前實測引用數:workflows.json 0/cypher src 0/examples 僅 km-wiki-ingest。
零件數 22 → 20。

 投影模型驗證成功:build-bundles.mjs 掃 .component-builds/ 目錄(readdirSync),
重跑後 manifest 自動 25 顆 → 23 顆,兩顆消失,**不需手動改 manifest**。
⇒ 證明 manifest 這層本來就是投影(leo 的設計),只有 registry KV 那層是獨立登記簿(待改)。

驗:vitest 9 failed/179 passed=與刪除前相同(既有債)。
2026-07-30 21:30:26 +08:00
Leo 5cadc60e36 feat(workflow-discovery): /cypher/search 改為真查 registry——修「查詢回假信號」
📋 SDD:workflow-discovery(本 commit 同時執行 D35 交接:portal-auth 26/26 完成 → closed
   superseded_by workflow-discovery;workflow-discovery paused → active。單一活性已驗=1 份)
🎯 對應 task:3.x 搜尋端誠實化(CP2-B)

病灶(leo 2026-07-30 定性「腹語術」):
search-nodes.ts 無條件回 status:'found'、missingNodes 永遠 []——
型別宣告了 'missing' 但程式碼從不使用。實測「完全不存在的東西xyz」也回 found。
⇒ AI 拿到假信號 → 以為零件存在 → 部署才發現沒有 → 改寫 code
⇒ 正式 workflow 只用 2 個零件、8 個 code 節點含 if×61。

修法:
- 查 registry 判真實存在(走 HTTP,守 D28 禁新增 service binding;
  URL 用既有 wasmWorkerUrl() 慣例組,不自創)
- found 時附 input_schema/success_rate/stability
  ⇒ AI 才填得出 payload、才看得到「測過幾次」(leo:AI 只要填 payload)
- 查不通回 'unknown' 而非 'missing'——**誠實限制**:
  不能因查詢失敗就宣告零件不存在(那會讓 AI 誤判而重寫 code)
- missing 真的回傳出去(原本寫死 [])

⚠️ 實測發現 registry **沒有列表端點**(GET /components → 404,只有 /components/<id>)
⇒ 改為逐個查(節點數通常 <10、5s timeout)。補列表端點後可改抓一次=CP2-B 待辦。

驗:tsc 零錯誤;vitest 9 failed/179 passed=**與改動前 stash 對帳完全相同**(既有債非本次造成)。
 待部署到實例後跑 arcrun-usable/verify.sh 驗 01 那組轉綠。
2026-07-30 20:41:53 +08:00
Leo e28e19069f t142: 雲端子庫顯示同步幾張卡+幾個三元組(政府專案驗收需求)
leo 07-29:「要在雲端子庫顯示同步了幾個 wiki,既然這樣也同步顯示有幾個三元組,
這是為了政府專案驗收。」

kbdb 兩支統計端點:
- entries.ts: COUNT(DISTINCT page_name) AS card_count
  ⚠️ 必須 distinct——算 block 數會膨脹 3-5 倍,政府驗收看到假數字比沒數字更糟
- records.ts: COUNT(*) AS triplet_count(三元組本來就算全部)
portal.ts 聚合兩者掛進庫目錄;前端 index.html 顯示。

驗:卡數確為 COUNT(DISTINCT page_name)/前端內嵌 JS node --check 全通過
(07-29 白畫面事故教訓)/portal-admin 測試 34 passed(改動前 31 passed,
唯一的 1 failed 是既有債:GET /portal 回 404,改動前後相同,非本次造成)。

註:此為子 CC 完成後未 commit 的懸置工作,總管收工檢查時發現並補收。
2026-07-29 19:55:03 +08:00
Leo cdca296044 D36:修正四個零件契約的假資訊敘述(現行沒有發 API key 的機制)
leo 07-29 指正:「現在沒有 partner key,改用 namespace,現在沒有發 API key 的機制」。
這些契約寫著 'KBDB partner key(ak_xxx)'/'租戶識別(ak_ 前綴)'=假資訊源——
總管就是被它騙的(先把範例改成不存在的 {{credential.kbdb_partner_key}}),
不修的話下一個 AI 會再挖同一個坑。

改:kbdb_upsert_block/auth_oauth2/auth_service_account/auth_static_key 的
api_key description,改述為「租戶識別=Arcrun namespace」+註明 examples 裡的
ak_test/ak_nonexistent 是測試假值不代表真實格式。

只動 description,不動 schema/欄位名/gherkin_tests
(api_key 欄位名保留——改名會破壞現有 workflow)。
驗:四檔 YAML 可解析、required 不變、欄位清單不變、gherkin_tests 全保留、
ak_test 等測試值原封不動。

人閘:leo 07-29 跑 scripts/component-arm.sh 解保險授權。
2026-07-29 19:38:14 +08:00
Leo e8bd518efa D36 第0步:範例 workflow 金鑰統一走 credential(止血——範例是用戶照抄的樣板)
改前四種寫法並存、沒一個是 credential,而執行端只認 {{credential.X}}
(graph-executor.ts:247→resolveCredentialRefs)⇒ 用戶照抄必踩坑:
  {{api_key}} 13/{{gitea_token}} 2/{{secret.GITHUB_BOT_TOKEN}} 2/{{kbdb_api_key}} 1

改後(19 處統一):
  {{credential.arcrun_namespace}} 15/{{credential.gitea_token}} 2/{{credential.github_bot_token}} 2

⚠️ 中途修正一次錯誤命名:我先改成 kbdb_partner_key(照零件契約舊敘述 ak_xxx),
leo 當場指正「現在沒有 partner key,改用 namespace,沒有發 API key 的機制」——
實際機制確為 X-Arcrun-API-Key: <namespace>(實例上活的 workflow 為證)。

不動:{{secret.LEO_TELEGRAM_CHAT_ID}} 3 處——chat_id 是聊天室 ID 不是金鑰,
無腦套規則會引進「找不到 credential」的錯誤。

驗:違規寫法歸零/12 個範例 YAML 全可解析。
殘:kbdb_upsert_block 契約仍寫『KBDB partner key(ak_xxx)』=假資訊源(我就是被它騙的),
修它被 component-guard 擋(正確),需 leo 跑 scripts/component-arm.sh。
2026-07-29 19:33:46 +08:00
uncle6me-web 159f0b07dc 🔴🔴 fix: portal 白畫面——t131 誤刪上一個 IIFE 的收尾 })();
leo:「問題是 portal 是白的」。真因:t131 合併 AI 設定時刪掉舊的 chat-key/extractor 兩區,
**連同上一個 IIFE 的收尾 })(); 一起刪掉** ⇒ 整段 JS 語法錯(Unexpected end of file)
⇒ 瀏覽器整支 script 不執行=白畫面。
**總管失職**:t131 驗收只跑了 vitest(測後端)+grep 字串,**從未驗過前端 JS 語法**——
portal/index.html 是純前端檔,vitest 根本測不到它。
修:補回 })();;node --check 通過;os-split/safejson 測試綠。
2026-07-29 17:45:30 +08:00
uncle6me-web 9d38d580f2 feat(t131): AI 設定合併成一把金鑰——聊天與萃取共用,Claude 為選填加強
leo:「拿到一把就很難了,還要拿兩把。一律規定先輸入 gemini api key,
如果想要強化本地萃取,可以選擇 claude……前面那個,聊天和萃都一次設好,後面那把,不填就是 gemini」
+「重點是更好的模型萃取知識更能抓重點,不然他也不知道加強什麼」(文案講感覺得到的差別)
+「本地如果有裝 claude,掃到,只要一個 checkbox 就好」(daemon 偵測回報,沒偵測到就停用選項)。
- POST/GET /portal/admin/ai(一次寫 chat-key 與 extractor)+/portal/daemon/report-capabilities
- 舊 chat-key/extractor 端點保留相容;UI 兩區合併成「AI 設定」
t131 新測 7 條全綠(總體 229 passed/9 紅皆 pre-existing:HTML shell 搬遷×2、library-map 未實作×6、
executor 零件×1)。(實作=子 CC;驗證+commit=總管)
2026-07-29 15:28:10 +08:00
uncle6me-web 0860e84d22 feat(t135): 庫目錄可自主移除+標示還在不在同步
leo:「需要加移除按鈕。因為別人裝錯我沒辦法幫他弄,需要可以自主」
+「你應該要顯示這個庫沒有本地對應的 folder,那就不容易刪錯」
(兩態非三態——leo 二修:「分兩種沒意義」,那是內部狀態不是用戶分類)。
- DELETE /portal/admin/libraries/:id(登記簿)與 by-name/:name(auto 庫需輸入庫名確認)
- kbdb 加 deprecate-by-library(auto 庫移除=標 deprecated,資料保留可還原)
- daemon/libraries 存 active 清單 → 卡片標 🟢同步中/灰目前沒有在同步
- 不自動刪(daemon 可能沒開機);daemon 從未回報時整列不標
vitest 24 passed(1 紅=console HTML 搬遷陳舊測試,非本案)。
(實作=子 CC;驗證+commit=總管。含 t116/t117 先前未 commit 的 graph-executor/wasi-shim 修正)
2026-07-29 14:45:44 +08:00
uncle6me-web 6d4980d3d7 fix(t130 🔴🔴): PORTAL_TEMPLATE_SEEDS 補 triplet——新實例總圖不再永遠空
真兇(總管探針定罪):seeds 只有 portal_user/portal_library,寫三元組回 400
'template not found: triplet' ⇒ 每個新用戶(含封測者)總圖必空。
geek6688 有是舊實例早期流程建過=拿它驗會假綠(已記 mistakes)。
呼叫路徑已驗:daemon/libraries、admin/libraries、init/seed 皆會 ensurePortalTemplates(冪等)。
vitest 24/24 綠(總管親跑)。(實作=子 CC;驗證+commit=總管)
2026-07-29 14:35:36 +08:00
uncle6me-web ccb86481ae fix(t128+t129): 圖搜尋補 template:triplet+AI 問答出處按頁去重
t128 真因(總管實測定罪):t116 只補 kbdb_base,同一行 URL 還吃 {{input.template}}
⇒ /records/by-template/?owner_id=... 查不到;手動補 template 即 count=1
(企業版功能解鎖→控制→授權系統)。**同種病三犯,已記 mistakes。**
t129:一卡切 3-5 block 每段都算一筆命中 ⇒ dedupeSourcesByPage 後端去重+hit_count。
vitest 52 passed(1 紅=console HTML 搬遷陳舊測試,非本案)。
(實作=子 CC;驗證+commit=總管)
2026-07-29 14:11:52 +08:00
uncle6me-web eb9f2db513 feat(t122 🔴🔴): 萃取引擎雲端設定+隨連線下發——封測者終於萃得出東西
真兇(總管查證):daemon/config 寫死 extractor='claude' 且不下發金鑰 ⇒ 封測者 100% 萃取失敗
(leo:「地端沒有 AI 根本不能萃,那它就不能玩」)。
- POST/GET /portal/admin/extractor(admin 閘;GET 只回 has_key 不回明文)
- daemon/config 改讀設定:未設定→gemma 無金鑰;設定後→含 gemini_api_key
- portal 設定頁加「萃取引擎」區(Gemini 推薦+aistudio 連結,存後提示「小幫手點連上知識庫重連即可」)
測試 3 條新綠(vitest 17 passed;1 紅=console HTML 搬遷陳舊測試,非本案)。
(實作=子 CC;驗證+commit=總管)
2026-07-29 13:21:26 +08:00
uncle6me-web 429b2d965f fix(t97+t114): 庫目錄只顯示用戶同步進來的庫、拿掉兩段式登記
leo 07-28 原話:「用戶沒加上的庫,不要自作主張給它加上」/「掃進來的就是要進目錄…
加入目錄這件小事還要分兩段做?…這是在攻打用戶嗎?根本是 attack」。
- portal.ts:859 auto 段濾掉 general(系統未標庫桶,非用戶的庫)
- index.html:bootstrap 不再預埋 kb 庫;移除「登記到目錄」按鈕與 auto/registered 視覺分岔
驗證(總管 grep 真相源):登記到目錄=0、kb 種子=0、general 濾在;
os-split 10/10+safejson 8/8 綠;portal-admin 1 紅=console HTML 搬遷陳舊測試
(stash 基線同紅,與本案無關)。(實作=子 CC;驗證+commit=總管)
2026-07-28 23:54:57 +08:00
uncle6me-web f6728974ea fix(t115 🔴🔴🔴 三修): kbdb 認證完全 fail-closed——沒金鑰一律 401(含讀取)
leo 實證的洞=「知道網址即可讀走全部知識」;一修 fail-open(沒設 secret 就不擋)、
二修仍放行讀取=洞沒補。三修(總管手改):無 token→全部 401(health 豁免),
老實例升級路徑=重跑安裝器(同時注入金鑰與新 workflow),不以繼續外洩換相容。
+結構閘測試:斷言 src/index.ts 的無 token 分支不得有 return next()——
擋「測試複本與真實作漂移」那類假綠(本輪正是它抓到二修的複本沒同步)。
kbdb vitest 60/60 全綠(總管親跑)。
2026-07-28 23:52:43 +08:00
uncle6me-web 2ff36962be feat(t103): /health 回 bundle_version(讀 ARCRUN_BUNDLE_VERSION,無 var 回空=老實例)
daemon 比對用(leo:daemon 和雲端是連動的)。vitest 2/2 綠(總管親跑)。
(實作=子 CC;驗證+commit=總管)
2026-07-28 16:06:10 +08:00
uncle6me-web e36cd2d990 fix(t95+t96): 查詢 CJK 邊界自動補空白+圖譜節點模糊命中
leo 07-28 實測:「AI協作」(無空白)搜不到;圖譜搜「AI 協作」0 鄰居但總圖有
「AI 協作規範書」節點(「這個搜尋詞來自 Graph View 的一部分,居然搜不到?」)。
- normalizeCjkQuery:CJK↔ASCII 邊界插空白,search q 與 graph 節點名都過
- fuzzyFindNode:精確 0 鄰居時 fallback contains 比對(取最短命中)重查
測試 +18 全綠(vitest 197 passed;9 個既有紅=console HTML 搬遷陳舊測試,與本案無關,
stash 基線對照確認)。B5 分支衝突面已查:僅 line 274 一行。
(實作=子 CC;驗證+commit=總管)
2026-07-28 15:06:07 +08:00
uncle6me-web ba92d10a3f fix(t88): 拿掉庫管理頁「圖譜來源」概念——任何庫都能進總圖
leo 裁定(2026-07-28):「任何的庫都要進到圖譜模式」「給他選擇就是客服問題」
「不要給他選,掃到就能進總圖,也不用停用按鈕」。
移除:標為/取消圖譜來源鈕、庫停用/啟用鈕與 dialog、graph_source tag 與過濾、
說明文字「標了圖譜來源的庫決定誰能用圖譜模式」;後端 API 不動;
用戶管理的「停用」(set-status)未動。diff +6/-33。
按鈕來歷(leo 問):5a16484 第一刀拆分引入(當時 B5 不存在,粗閘是唯一保護);
t52(139d4c5) 補 auto 庫說明。拆的是過期鷹架非錯誤設計。
(實作=子 CC;驗證+commit=總管)
2026-07-28 12:41:00 +08:00
uncle6me-web a909072dc1 feat(t87): portal 兩處顯示「去後綴的乾淨網址」+一鍵複製——連線不再要人抄網址列
leo 07-28 拍板:「一律用網址,把後綴拉掉,以免他貼了說我的網址錯了」。
設定頁「同步小幫手」卡+管理頁「庫目錄管理」各一行:location.origin(無 /portal/#/ 後綴)
+複製鈕(成功短暫顯示「已複製」,失敗 fallback 提示手動選取)。共用同一 helper。
os-split 10/10、safejson 8/8 仍綠(總管親跑)。(實作=子 CC;審查+commit=總管)
2026-07-28 12:04:04 +08:00
uncle6me-web 11e772496f fix(t75 ①): portal 不再把 JSON 解析錯誤噴給使用者+404 說人話
leo 同事實測:存 Gemini 金鑰時畫面出現
「Unexpected non-whitespace character after JSON at position 4」。

根因兩層:
① 前端 15 處無條件 r.json(),但伺服器不一定回 JSON——404 頁/CF 錯誤頁都是 HTML。
   JSON.parse 一爆,錯誤沿 .catch 走到 friendlyErr,而 friendlyErr 最後一行是
    =把任何例外訊息原樣顯示 ⇒ 技術英文直接噴到畫面。
② 真正的原因是 /portal/admin/chat-key 回 404(實例的 cypher 是舊版沒這端點),
   但使用者完全看不出來,只看到一句看不懂的英文。

修:
- 新增 safeJson(r):用 r.text() 再 try/catch parse,解析不了回 {} 不拋錯;15 處改用它
- friendlyErr 收斂:JSON 類錯誤→「伺服器回應異常,請稍後再試」;
  純英文技術訊息→「操作失敗」;我們自己寫的中文訊息才原樣顯示
- 兩處金鑰儲存加 404 專屬提示:「你的知識庫版本還沒有這個功能,請先更新知識庫」
  ——講清楚為什麼與怎麼辦,否則他只看到「儲存失敗」會反覆重試同一件事
- 順手:安裝卡片那處原本 x.d.error 在 x.d 為 undefined 時會再爆一次,補 x.d && 防護

測試 safejson.test.mjs 8/8:404 HTML 不拋錯/空回應/正常 JSON 仍解析得出/
JSON 錯誤不外洩原文且說人話/網路錯誤訊息保留/中文訊息原樣/英文技術訊息收斂。

⚠️ 這只解「不噴技術訊息」;金鑰要真的存得進去仍需實例更新 cypher(②層待辦)。
2026-07-28 01:02:12 +08:00
uncle6me-web 53c6334fd9 fix(portal/t72): 下載頁 OS 分流——Windows 客戶不再拿到 Mac 的 .app
leo 07-27:「客戶是用 windows 的」。此前四處寫死「下載 Mac 版」,
Windows 用戶按下去拿到 .app=按了連到錯的東西(leo 判準:那不算友善)。

改法(施工圖 rag-wave1/windows-build-and-os-split.md §3):
- daemonPick() 依 UA 判 OS,四處共用;判不出來=兩個都給,不替用戶猜
- 判對了也附「不是這個系統?」另一版連結(UA 會判錯,用戶要有路走)
- 擋關話術跟著 OS 走:Mac=右鍵打開/Windows=更多資訊→仍要執行
- daemonBase 新 key,保留 daemonDownload 舊 key 相容(由檔名推目錄)
- 一律走 raw:Mac zip 21MB > jsDelivr 20MB 上限(實測回 File size exceeded)

測試 os-split.test.mjs 10/10:
⚠️ 測試抓到真 bug——iPhone 的 UA 含 'Mac OS X' 會被判成 Mac,
讓手機用戶下載裝不起來的桌面 app。已加 isMobile 排除,手機落到「兩個都給」。

未驗:真 Windows 機器的實際下載與安裝行為(需真機)。
2026-07-27 19:28:43 +08:00
Leo d7fab7c6aa fix(portal): 拿掉登記新庫/設定頁補常駐入口(下載小幫手+AI 金鑰)
leo 07-27 走完安裝後三點回饋:

1) 「登記新庫」是設計錯誤 —— leo:「應該是同步小幫手抓到的庫就顯示在上面,
   沒有人工登記的選項。」人工登記只會製造對不上的空庫。
   移除表單+對應 JS;說明改為「裝好同步小幫手並選好資料夾後,每個資料夾會自動
   成為一個庫出現在下面,不需要人工新增。下面還是空的,代表小幫手還沒裝好或還沒選資料夾。」

2) AI 金鑰與下載連結只存在一次性的「還差 N 步」卡片,按了「稍後再說」或裝完就
   再也找不到 —— 換金鑰/換電腦重新下載都沒入口。
   設定頁新增兩個常駐面板:「同步小幫手」(下載)與「AI 問答金鑰」(可隨時更換),
   共用既有 API POST /portal/admin/chat-key。

3) 修 window.ARCRUN_CFG → ARCRUN_CONFIG(與 721 行既有用法一致,原為筆誤)

驗證:ad-nl-create 殘留 0 / st-daemon-dl 2 / st-key-save 2 / ARCRUN_CFG 0
抽出頁面 3 個 script 區塊 node --check 語法通過
2026-07-27 16:26:52 +08:00
uncle6me-web 139d4c5ed1 feat(t52): 資料夾=庫端到端——kbdb 加 /entries/libraries(資料面 distinct 庫);portal 庫目錄合併「登記簿+蓋章自動出現」(auto);auto 庫改安全渲染(無 record_id 不放死按鈕,改給『登記到目錄』)+daemon/libraries 自動登記端點。leo 07-26:地端 2 個資料夾雲端就要 2 個庫 2026-07-26 01:49:43 +08:00
uncle6me-web e7fe83a872 feat(t54): 小幫手憑帳密自取設定——新端點 POST /portal/daemon/config(帳密驗證同 login,回連線設定不含知識內容);portal 清單移除 config.json 下載(3 步→2 步)。leo 07-25:「最好的就是把它的帳密直接輸入」 2026-07-26 00:16:21 +08:00
uncle6me-web 8d19d0b2d7 fix(portal/t53): daemon 下載連結補實——zip 上鏡像(raw 路徑;jsDelivr 20MB 上限擋 21MB 檔)+無下載點時誠實降級不放死連結(交付警察抓到 404 死連結) 2026-07-25 23:44:44 +08:00
uncle6me-web a8d246ebe9 feat(portal/t53): 進站完成安裝清單(leo 07-25:金鑰與 daemon 要在站內做,不然安裝沒完成)——常駐三項清單(下載小幫手/下載 config.json 站內生成/貼 Gemini 金鑰即時啟用)+新端點 POST /portal/admin/chat-key(改寫 tenant rag_chat 的 x-goog-api-key,admin 閘、金鑰不落 log)+session 回 email 供 config 生成 2026-07-25 23:26:44 +08:00
uncle6me-web 11ffd42899 fix(portal/t49): 首登頁補逃生口——『已經有帳號?改用登入』+setup 409 自動切登入帶提示(leo 實撞死路) 2026-07-25 21:12:31 +08:00
uncle6me-web 24f989d818 merge t49: portal 首登建帳+歡迎引導(leo 07-25 令當晚交付;已先行部署 leo 實例驗證) 2026-07-25 20:35:33 +08:00
uncle6me-web 9bbd25fdfc feat(portal/t49): 首登建帳一顆按鈕——auth-status 偵測未初始化→v-firstsetup(console/setup→portal/admin/bootstrap→自動登記預設庫 kb→portal/login 四發連鎖)+進站一次性歡迎卡(下載小幫手/金鑰 → installer /setup)。leo 07-25:『加入的體驗要搞定』——用戶打開專屬網址第一眼就是建帳號,不再回安裝頁等 2026-07-25 20:35:33 +08:00
uncle6me-web 76b89be6d7 merge t36: console 語意搜尋改狀態列(假開關+CLI 指示移除,click handler 一併拆——leo 07-25 口頭核准 merge) 2026-07-25 20:29:40 +08:00
Claude beb0653e15 fix(console/t36): 語意搜尋改狀態列——移除按不動的假開關與 CLI 指示
leo 2026-07-25 定案:語意搜尋預設開啟、不做開關(費用實算後屬雜訊——1 萬張卡規模
下 Vectorize 約 $0.18/月,佔 CF 帳單 3~4%;關掉省幾毛錢卻換來很爛的搜尋體驗)。

設定頁那顆「語意搜尋(vectorize)」開關其實不能按(title 自承「不能遠端改,只如實
顯示狀態」),旁邊還教用戶去部署端改 config.yaml 跑 acr update——對一鍵安裝進來的
用戶那是天書(與 t35 同一種白癡化違規),而且安裝器現在裝機時就把語意索引開好了,
那段指示本身已不成立。

改成單純狀態列:啟用時只說「已啟用,搜尋頁切語意就能用」不給任何操作指示;未啟用
才給一句人話與下一步(重跑安裝流程會補上,已建資料不重來)。

⚠️ 連帶必修:一併移除綁在該開關上的 click handler——留著會讓 $('st-vec-switch')
回 null、addEventListener 當場拋錯,把後面所有綁定(含登出)一起打斷。
驗:三個 script 區塊語法檢查通過、全檔已無 st-vec-switch/st-vec-state 殘留參照。
2026-07-25 08:51:54 +00:00
uncle6me-web 646e689b65 confirm(CP2-F): 二~四刀拆分過裁(leo 授權技術裁決);四題答定(手寫驗證/arcrun-api/排程於 A 線後) 2026-07-24 18:11:10 +08:00
uncle6me-web dd78b770a9 fix(kbdb): /entries/search 服務端濾 deprecated(keyword+semantic),修下架不生效
daemon-beta t24(t11 斷點①②,總管 0.971 親復現):rag_takedown_direct 下架只把
metadata_json.status 標 deprecated(軟刪,append-only),但 /entries/search 兩 mode
都不濾,已下架內容照樣回傳——semantic 甚至最高分照吐。過去唯一的濾層只在
cypher-executor/portal-data.ts 客端(Arcrun#46),沒部署 rag_chat 的實例等於完全沒濾。

- entry-crud.ts:新增 json_extract($.status) NOT_DEPRECATED_PREDICATE(同 source/library
  既有 json_extract 模式,issue #5.1/#18;不用 LIKE 避免 JSON 序列化格式誤判);
  searchEntries 新增 includeDeprecated 參數(尾端新增,向後相容)。
  新增 isDeprecatedEntry:semantic 路徑用(Vectorize metadata 沒存 status,只能 hydrate
  回完整 entry 後 JS 側判斷)。
- entries.ts:/entries/search 新增 include_deprecated 開關(預設 false,濾掉;true 給
  管理面查殘留)。semantic 分支補位——過濾生效時先以 topK×3(封頂 100)向 Vectorize
  多撈,hydrate+濾完再截斷回 caller 要求的量,避免命中大半下架時整頁被吃光
  (t11 ZZ-T10 實測案例)。
- Vectorize 向量殘留本 PR 不動(只做查詢時過濾,not 同步刪向量)——取捨見 PR 描述。

測試:新增 search-deprecated-filter.test.ts 15 案(keyword 濾/include_deprecated 開關/
semantic 濾+補位+封頂+截斷/isDeprecatedEntry 單元);同步更新 search-source-and-score.test.ts
3 案的 topK 期望值(route 補位改變送進 Vectorize 的實際 topK,回應對外契約不變)。
kbdb 全套 60/60 綠、tsc --noEmit 乾淨。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 16:35:17 +08:00
uncle6me-web ab8e07201f proposal(CP2-F): cypher 二~四刀拆分提案——bundle 528KB 實測解剖+引擎留原地方案(待 leo confirm)
偵察實測(wrangler dry-run+sourcemap byte 歸因,非推測):
- 現碼 11,050 行/bundle 528.1KB——頂層 CP2-F 記載(15,446 行/748KB)是第一刀前舊數,提案 §0 更正
- zod 佔 130.6KB(24.7%),只服務兩條入口驗證鏈=最大單一減重點
- 引擎真身僅 ~180KB,其餘 ~350KB 是管理/前端 API 面

方案:引擎留 cypher-executor 原地(webhook 觸發 URL 外部焊死、零改動),
新開 arcrun-api 收管理面;三刀漸進、每刀獨立回退;13 顆 SVC binding 不動。
依 D35 只寫 proposal 停下,不動 code、不部署。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 15:37:51 +08:00
uncle6me-web ad367e450d console-ui: 修好壞了一個月的 build + 具名部署目標(個人版/企業版)
🔴 根因:5a16484 把 UI 搬 CF Pages 時「移出 → console-ui/」實際上只搬了
build 產物(HTML),三支 renderer 原始檔被刪且沒搬 →
`npm run build` 從那天起 ENOENT 死掉,線上 HTML 是刪檔前烤好的,
一個月來無法重建(CONSOLE_PROFILE 改了也不會生效,因為 build 跑不起來)。

修法:
- 從 git 撈回 console.ts(1623)/portal-ui.ts(1390)/console-dashboard.ts(822)
  放進 console-ui/src/——UI 原始碼跟著 UI 專案走,才是那一刀的原意
- build.mjs 加 resolveSource():本地 src/ 優先,找不到才回退 cypher-executor

新增具名部署目標(deploy.targets.json + scripts/deploy.mjs):
一個目標=帳號+profile+apiBase 綁在一起,解決三次帶漏參數:
- demo 站漏 CONSOLE_PROFILE=rag → 顯示個人版 7 頁駕駛艙(leo「進去是 Mira 介面」的真因)
- 兩站漏 ARCRUN_API_BASE → apiBase 空 → 前端打自己 405 → 登不進去
- 兩帳號都有同名 arcrun-console-ui 專案且 wrangler OAuth 登入在 uncle6
  → 不指定帳號 deploy 會部到 demo 站(差點蓋掉,改用 API token 鎖帳號)

  npm run deploy:personal    → leo21c, profile=full(7頁), apiBase 指 leo21c worker
  npm run deploy:enterprise  → uncle6, profile=rag(4頁落地搜尋), apiBase 指 cypher.arcrun.dev

實測(用戶真的會走的路):
- mira.uncle6.me/console/ 200,VIEWS 7 頁,apiBase 對,CORS ACAO 通
- rag-demo.arcrun.dev 4 頁落地搜尋;登入 OK、搜「藍鯨導航儀」5 筆(未受部署影響)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 19:09:29 +08:00
Leo 5a164843ef refactor(cypher): 第一刀拆分——UI 搬 CF Pages,ingest 從必爆變穩定
根因(leo 2026-07-21 三個判斷全被數字證實):
- 「cypher 展開上萬行,當時我就懷疑這樣跑得動嗎」→ 15,446 行、bundle 748KB
- 「不相信 CF 連讀文字檔都會撞牆」→ 不是 CF 的問題,是 cypher 太肥
- 「它違反了樂高化的原則」→ 零件 4KB,調度它們的 cypher 是巨石

定位錯誤(比效能更根本):console.ts:1 自陳「Mira Console」,而 wiki 明載
「Mira 是 Arcrun 的使用者,不是開發者」→ 使用者的前端寫進了框架的執行引擎。
CLI/MCP 都已拆成獨立薄殼,唯獨 UI 沒有。責任誠實記:console.ts 標
「2026-07-04 總管派工」,是總管當初選了「就近寫在 cypher」的方便路。

本刀:
- console.ts(1623行) + portal-ui.ts(1390行) 移出 → console-ui/ CF Pages 專案
  (單檔 HTML + 原生 JS,零 build step,保持原特性)
- index.ts 移除兩個路由掛載、修 implicit any
- console-dashboard.ts 只留 API(UI 部分移出)

實測驗收(leo 指定判準:不看 /health,看真實任務):
| 測試 | 拆分前 | 拆分後 |
|---|---|---|
| 連灌 10 張新卡(無間隔) | 從未成功 | 10 張全 create、73 條三元組 |
| 同卡連跑 6 次 | 前 2 過、後 4 全 1102 | 6 次全過 |
| Bundle | 747.9 KB | 528.0 KB(-29%) |

Pages 已上線:arcrun-console-ui.pages.dev(console/portal 皆 200)
cypher API 正常:/health、/console/dashboard-data 皆 200

未做(第二刀):console-dashboard/portal/portal-data 尚未拆,cypher 仍 528KB。
但 ingest 已穩定,急迫性下降。

註:部署撞到三個既有 self-hosted 陷阱(KV/D1 id 是官方帳號的、arcrun.dev route
leo21c 無該 zone),用 wrangler.leo21c.toml 繞過,未改原始設定檔。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 18:55:31 +08:00
Leo 1b687fedb0 fix(mcp): 解掉三個把人推去寫零件的誤導入口(leo 2026-07-21 拍板)
leo:「刪掉,技術者才會寫 component,在 Arcrun repo 寫一條如何 contribute
指向另一個 repo 就好。」

實測病灶:總管想寫「定期打 API 然後通知」的 workflow(Python 約 10 行),
問 foreach_control 怎麼用 → MCP 回傳 TinyGo 寫 WASM 零件教學(白名單/syscall/
contract schema)=完全另一件事,40 分鐘未完成。

三處修正:
1. search_components 搜不到時的話術——原本建議 publish_component(把「我找不到」
   翻譯成「你去造一個」,方向完全相反)。改為導向正確順序:語意搜尋知識庫→
   auth-recipe list/scaffold→acr parts(http_request 能打任意 API)→acr list,
   並明說 registry 可能是空的(已知問題),搜不到≠沒有這能力。
2. registry.ts 停用 publish_component / get_component_guide 兩個註冊
   (檔案保留,只是不對 AI 暴露)。
3. 新增 CONTRIBUTING-components.md:三層責任分工(平台通用能力/熱門預鋪 recipe/
   冷門誰用到誰開發)、什麼時候才真需要新零件、真要貢獻走 PR 的流程。

typecheck 通過。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 16:42:51 +08:00
Leo 98d87d5d3f principles: 前端人類友善/Arcrun AI 友善,都從終點看(leo 2026-07-21)
判準=AI 覺得 Arcrun 比 Python 簡單,沒有寫 Python 的慾望,絕不可迷路。
使用方式應意圖驅動:不用想有哪些零件,描述目的→機器給建議→用 >> 黏起來。
實證:總管做「定期打 API 然後通知」(Python 10 行)Arcrun 40 分鐘未完成,
全程被 component guide/publish_component 建議推向鑄零件。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 13:00:15 +08:00
Leo 5edb97c49d docs: CLAUDE.md 加「第一鐵律:wiki 是判準,不准跳過」
leo 2026-07-21:「其他 repos 各自都要警覺不能跳過 wiki」。
hook 只是提醒,鐵律要寫在本 repo AI 開工必讀的地方。
含 grep 查法、三條硬規則、動外部系統前先找現成腳本(別自創方法)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 10:55:40 +08:00
Leo efc29b78c2 chore: template 1.16.1——wiki-first-search 補 Bash 破口
1.16.0 只掛 Grep|Glob|Read,但「用 curl/wrangler 亂試方法」走 Bash → hook 不觸發,
上線隔天即被繞過(leo 2026-07-21 實證:wiki 早記著寄信已驗證可用,我沒查又自創)。

matcher 補 Bash,只認高風險指令(wrangler|curl|npx|acr|gh|deploy|push)。
本 repo 亦需警覺:動外部系統前先看 hook 推的 wiki 命中,別自創方法。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 10:54:15 +08:00
Leo 8d8b01d240 docs(sdd): credential-primitives-wasm 封存進 archive(T10 完成,卷已結案)
leo 2026-07-21 明令封存:卷已完成,留主目錄會讓未來 session 誤以為進行中。
最後一項 T10(廢除自管加密金鑰)已於 20c7610 完成(移除約 2500 行)。

- git mv 整卷 → system-dev/docs/3-specs/archive/credential-primitives-wasm/
  design.md status: closed;superseded_by 留空(據實:非被另一卷取代,是機制整個換掉)
- 卷首補「封存時仍未完成的項目」——逐條查 code 核實,不當作完成:
  真缺口=auth_mtls 從未實作、7.6 self-hosted auth 鏈端到端從未驗;
  另有勾選過期(auth_oauth2 其實已完成)與驗收條件已作廢(與現行 rule 07 牴觸)者
- 修好 14 處引用(原盤點 10 處,實際更多):session-start-load-sdd.sh 內容嚴重過期
  (把已完成 Phase 寫成進行中)→ 改為以 frontmatter 為判準;四份 rules、CLAUDE.md、
  BACKLOG、3-specs/README、deploy.ts、credentials.ts、wrangler.toml 逐一按性質處理
- 額外:system-dev/docs/2-architecture/ 有四個規則檔重複副本(06-08 遷移遺留)
  → 同步修好,否則留一份過期真相(今日第三次撞到「同一資訊兩份副本」的債)
- 02-forbidden.md §2.1 列的三個「待刪違規 TS」其實早已不存在
  → 改標刪除線+禁止重新引入(過期文件同時製造假待辦與假進行中)

驗證:sdd-active-check exit 0(active 仍恰一份=portal-auth);
session-start-load-sdd.sh exit 0;cypher-executor 與 cli typecheck 全綠;
測試 187/188(唯一 fail 為 pre-existing,stash 覆驗相同)。

未動:pre-write-guard.sh 的 KNOWN_SDDS 白名單不含 archive 路徑
(放寬 guardrail 需明確授權,留待決定)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 01:57:34 +08:00
Leo df1b953d1f chore: template 1.16.0——wiki 讀取兩支 hook(查詢即搜尋 wiki+subagent 自動注入)
leo 2026-07-20:「花很多力氣去產生 wiki,最重要的就是要可以查詢,
結果要查的時候就跳過,那就白寫了」

- wiki-first-search.sh(Grep|Glob|Read):查 code 的當下用同組關鍵字 grep wiki,只推命中行
- subagent-wiki-guard.sh(Task):查證類任務自動注入「先查 wiki」給 subagent
- update.sh/install.sh 來源改指 Gitea(原指 GitHub uncle6me-web 已 suspend=自動更新早就死了)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 01:40:55 +08:00
Leo 20c7610371 refactor: 移除已廢棄的自管加密金鑰機制(credential 全面託管 CF Workers Secrets)
leo 2026-07-20 明令:「已經改用 cf 自己的 secrets,不要再說它了」
「我希望以後再也看不到這個詞再出現」

背景:credential 早已遷移至 CF Workers per-script Secrets + D1 目錄,
舊的自管金鑰(client 端 AES-GCM + KV 密文 + crypto_decrypt)是遷移期遺留。
本次連根移除,含一併作廢的死 SaaS 碼。

移除:
- 舊 KV 密文解密路徑(credential-injector.ts 整檔、dual-read fallback)
  前置驗證:leo21c / youlin 兩帳號 CREDENTIALS_KV 實測 *:cred:* 皆 0 筆
- migrate-to-workers-secrets 搬家端點(回填已完成,無可回填)
- /register 路由與 generateApiKey(HMAC 產 ak_ key 是 SaaS 遺物;
  self-hosted 走 namespace 明碼 D21,已無人使用)
- platform_crypto component(三帳號實測 404 已退役,無 workflow 引用)

保留(附理由):
- crypto_decrypt 保留為永遠回失敗的 stub——現役三個 auth .wasm 仍宣告該
  import,缺項會讓 WASM instantiate 直接失敗。待零件重編後可真正刪除。

順帶修復(原不在範圍,但會實際壞事):
- /auth/callback 有 `if (!key) redirect(server_error)` 閘,未設該 secret 的
  實例會登入直接失敗 → 已移除
- OAuth 兩處把 provider token 寫進舊加密 KV(租戶鍵與實際 api_key 在 rotate
  後必然分歧,已失效)→ 改導向 Workers Secrets,包 try/catch 不影響登入
- acr init Standard 模式呼叫已刪除的 /register → 改引導 OAuth 取 key
- .claude/rules 與 system-dev/docs 是同一規範的兩份鏡像,先前只改 rules
  導致鏡像仍在教舊做法 → 已同步(此類雙檔同步應納入檢查)

新用戶安裝從此零 secret 前置。
測試 187/188(唯一 fail 為 pre-existing,stash 驗證與本次無關);
cypher-executor 與 cli typecheck 全綠。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 01:32:48 +08:00
雲端總管 b9b94d7852 docs(sdd): library-map tasks M1-M5+M3 銷帳(07-19 雙實例上線)、M7 半驗 2026-07-19 11:21:33 +00:00
Leo 762d2f5141 Merge pull request 'feat(console): library-map M5 — 總庫搜尋頁藏書地圖首頁區塊(R4)' (#74) from feat/console-library-map-home into main 2026-07-19 09:36:04 +00:00
Claude 10dda9e6f5 feat(console): library-map M5 — 總庫搜尋頁藏書地圖首頁區塊(R4)
library-map SDD M5(Arcrun#39):console 首頁從空白搜尋框變全館地圖。

落點裁定:console 是 hash-routing 單頁殼,home 隨 profile(full=駕駛艙、
rag=總庫搜尋)——地圖放總庫搜尋頁搜尋框上方:rag profile 該頁即首頁;
full profile 它是全館入口,「點庫卡片→進該庫搜尋」動線同頁最短,一份實作
兩 profile 全吃。

- console.ts:藏書地圖區塊(lm-section)——每庫一張卡(庫名+narrative+
  top entities 前 5 帶 degree+triplet_count+commit_hash/updated 更新時間感)、
  跨庫 bridges 有資料才列一小區;兩段式渲染(全館列表先出卡,逐庫詳圖補
  degree/bridges,詳圖失敗維持名字版不擋);點庫卡片=設 library filter
  (/kbdb/search 既有 library 參數,不新造)進庫搜尋,附清除鈕搜回全館;
  /map 空陣列或錯誤=誠實空狀態(指引 POST /map/recompute backfill),
  不擋搜尋等其他功能。品牌沿 CONSOLE_BRAND 變數(#21),零外部庫。
- kbdb-proxy.ts:GET /kbdb/map+/kbdb/map/:library 純轉發(rule 07,聚合真身
  在 kbdb base M2)。owner_id 不強制注入只選擇性透傳(同 M4 MCP kbdb_get_map
  語意;現行 backfill map block owner=NULL,強制注入租戶=首頁永遠假空狀態);
  仍要求 X-Arcrun-API-Key 同閘;KBDB 不可達誠實 502。
- 測試:kbdb-map-proxy.test.ts 9 測(租戶閘/轉發/owner 透傳/空陣列/404/502)+
  console-library-map-page.test.ts 6 測(route 200/區塊在搜尋框上方/資料源接線/
  library 參數/誠實空狀態文案/品牌變數)——15/15 過。
- 誠實回報:tsc 16 行 ExecutionContext.tracing 錯誤與 executor.test 1 失敗=
  main 基線既有(git stash 對照實證),非本分支引入。

merge 後需 gated redeploy cypher-executor(leo 閘)。關聯 #39。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JUmjwkHLVBHM3ydhT1WSW3
2026-07-19 09:34:36 +00:00
Leo 289cd495b4 Merge pull request 'feat(mcp): library-map M4 — kbdb_get_map 工具+instructions 注入全館地圖' (#73) from feat/mcp-library-map-inject into main 2026-07-19 09:13:46 +00:00
Claude 343572bf5d feat(mcp): library-map M4 — kbdb_get_map tool + instructions 注入全館地圖
library-map SDD M4(design §4,源頭 #39)。兩件:

1. kbdb_get_map(tools/kbdb_map.ts,與 #68 kbdb_graph_neighbors 同族薄殼):
   - 無參數=全館地圖(每庫一行:library+narrative+top 3 entities+triplet_count)
   - 帶 library=該庫詳圖;top_entities/relation_profile/bridges 若為 JSON 字串形
     容錯 parse 成物件(parse 失敗當空陣列,不 crash)
   - 404/空庫誠實回報+POST /map/recompute backfill 指引
   - 走既有 KBDB service binding(kbdbFetch),不碰 D1、不新增 binding

2. instructions 注入(lib/library-map.ts+mcp-handler.ts):
   - 連線時拉 GET /map,渲染成緊湊文字({library}:{narrative}|核心:{top3}|{n} triplets)
     嵌 server instructions(design §6:session 啟動 push 零查詢)
   - 快取選型:isolate 內 TTL 快取(成功 5min/失敗 1min)——stateless StreamableHTTP
     每個 HTTP request 重建 McpServer,「每次現拉」實際是每個 tool call 都多打一次 /map
   - timeout 1.5s;任何失敗(超時/HTTP錯/空庫/壞JSON)回 null 靜默略過,絕不擋 MCP 連線(鐵律)

測試:假 KBDB binding(比照 kbdb-graph.test.ts)17 新測,pnpm vitest run 76/76 過、tsc --noEmit 乾淨。
merge 後需 gated redeploy arcrun-mcp(leo 閘)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JUmjwkHLVBHM3ydhT1WSW3
2026-07-19 09:11:46 +00:00
Leo bdfc2e3e0b Merge pull request 'feat(kbdb): 藏書地圖 M1+M2 — library_map template+/map 三端點(聚合 SQL 住基本盤)' (#72) from feat/library-map-base into main 2026-07-19 08:42:42 +00:00
Claude 15fe012006 feat(kbdb): 藏書地圖 M1+M2 — library_map template+/map 端點(聚合 SQL 住基本盤)(#39)
M1:library_map template(migration 0003 seed+runtime ensure,D6 零建表零 ALTER);
核實 triplet 按庫定位=不足(prod triplet template 無 library slot、entries
metadata.library 未標記)→ 走 SDD 預案:recompute 冪等補 optional library slot
(改 template 不動表),slot 值由 ingest 端(M3)補寫,核實結果記入 design §1。

M2:kbdb base 三端點(design §2 歸屬裁定:聚合 SQL 只准住基本盤):
- POST /map/recompute?library=X:degree top-N/predicate 分布/跨庫 bridges/
  triplet_count 一段 SQL 聚合;narrative 本輪收 body 傳入(wiki 抽取屬 M3);
  寫入順序安全(先建新 active map block 再標舊 superseded,讀端取最新 active 自癒);
  過渡 source_prefix fallback 讓無 library 值的舊 triplet 靠 source_uri 前綴歸庫。
- GET /map:全館地圖每庫一行(library+narrative+top 3 entities+triplet_count),
  MCP instructions/GUI 共用(數百 token 內)。
- GET /map/:library:該庫詳圖(完整 slots+可嵌人話 content,design §5 M6 直用)。

測試:node:sqlite(零新依賴)當真 SQLite 實跑 migrations+聚合 SQL,
Hono route 行為同款覆蓋;45/45 過、tsc --noEmit 乾淨。

關聯 #39

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JUmjwkHLVBHM3ydhT1WSW3
2026-07-19 08:40:22 +00:00
雲端總管 bad68ef149 docs(sdd): library-map design 標 leo confirmed 可動工 2026-07-19 08:27:53 +00:00
雲端總管 0ba685af9f Merge remote-tracking branch 'origin/feat/mcp-graph-neighbors-tool'
# Conflicts:
#	system-dev/wiki/status.md
2026-07-19 08:05:22 +00:00
Claude 7eeab9f26f feat(mcp): kbdb_graph_neighbors — graph 鄰居查詢薄殼(#68)
MCP 面補齊 D17 KBDB RAG 套餐第三模式(關鍵字/語義/圖):
- 新 tool kbdb_graph_neighbors:subject(必填)/depth(預設 1)/kbdb_base/
  template(預設 graph_triplet)/directed,形狀對齊
  registry/examples/graph-neighbors/workflow.yaml 的 input
- 薄殼調 GET /q/{orgNamespace}/graph_neighbors 同步查詢端點(#28 地基),
  走既有 CYPHER_EXECUTOR service binding,不新增 binding、不碰 D1、
  不動 workflow 引擎本體
- workflow 未部署 → 404 誠實回 workflow_not_installed +安裝指引,不 crash;
  HTTP 200 但 workflow 層 success:false 也不假綠
- graph_traverse 不加:repo 內無該 workflow 定義可對齊 input 形狀,不猜
- 測試:tests/unit/tools/kbdb-graph.test.ts(7 測,全綠);tsc --noEmit 乾淨;
  mcp 全套 vitest 59/59 綠

merge 後需 gated redeploy arcrun-mcp(leo 閘)。

關聯 #68

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JUmjwkHLVBHM3ydhT1WSW3
2026-07-19 07:55:16 +00:00
雲端總管 1ee0dbd2fe docs(sdd): library-map(藏書地圖)SDD 出稿 draft——#39 立案 11 天催辦補課;含 D6 歸屬裁定(SQL 住基本盤)+D30 檢索治本連動 2026-07-19 07:54:23 +00:00
Claude 65d85eb08f fix(kbdb): search keyword 補 source filter(#66)+semantic 曝 top_k/min_score 帶 score(#67)
#66:/entries/search keyword 路徑 source 解析後丟棄(#5.1 只接了 listEntries 那半)——
searchEntries 尾端加 source?(既有 positional caller 全不用改),conds 補與 listEntries
同款 json_extract(metadata_json,'$.source') 謂詞;route keyword 分支與 semantic 降級
分支兩處傳入。

#67:semantic 固定 topK=20、零分數閾值、低分尾硬湊數——route 曝 top_k(預設 20、封頂
100)與 min_score(預設 0=不過濾)query 參數;semanticSearch 依 min_score 截低分尾;
semantic 回應 entry 附 score 欄(加欄不改形)。壞值(非數字/非正)視同沒帶,不 400。

向後相容:不帶新參數時輸出與現況一致(semantic 僅多 score 資訊);不動表(D6)、
不動 D1 結構(API-as-Wall)。測試:新增 search-source-and-score.test.ts 13 條
(source 謂詞形狀/route 下傳/降級不洩 filter/min_score 截斷/topK 透傳封頂/壞值防呆/
不帶參數行為不變),kbdb vitest 33/33 綠、tsc 0。

關聯 #66 #67。merge 後需 gated redeploy kbdb worker(leo 閘)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JUmjwkHLVBHM3ydhT1WSW3
2026-07-19 07:53:46 +00:00
雲端總管 2324537165 docs(sdd): 1.5-3 補機制邊界(分享≠同步、與 acr update 兩軌)+changelog/版本 diff 規格(leo 修正) 2026-07-19 07:00:42 +00:00
雲端總管 b29de1b216 docs(sdd): 1.5-4 補「public 匿名可讀」規格+官方部署目標帳號(youlin.hsieh.dev) 2026-07-19 06:50:31 +00:00
雲端總管 a73de77a7f docs(sdd): P-2026-07-19 confirmed——pending-changes 移入已裁決、artifact-sharing tasks 增補 Phase 1.5(bundle/譜系/訂閱/多源/self-check) 2026-07-19 06:43:38 +00:00
雲端總管 ffd50e7510 docs(sdd): pending-changes 收 P-2026-07-19 proposal——artifact-sharing 增補 bundle/譜系/訂閱/多源(leo 對話定調,正式 confirm 待明示) 2026-07-19 06:28:28 +00:00
uncle6me-web 98869c5abc fix(publish): 匯出樹刪 .gitattributes(同步) 2026-07-18 23:45:44 +08:00
uncle6me-web d6a051dbfc feat(publish): sanitize 鉤子(同步 arcrun-rag 版) 2026-07-18 23:29:32 +08:00
uncle6me-web 14d4d65848 fix(publish): push 憑證改 header 注入(GITHUB_MIRROR_TOKEN env),token 不進 URL/指令行 2026-07-18 22:41:06 +08:00
uncle6me-web ca57fd6a1a chore(gc): GitHub 公開 mirror 管線(publish-github.sh+排除清單)——D22 模型落地:Gitea 私有真相源/GitHub 過濾樹乾淨歷史 2026-07-18 20:21:15 +08:00
uncle6me-web e1071edfe8 chore(gc): Mira 白名單 email 改 env 注入(publish 前 GC,leo 2026-07-18 放行)——公開 repo 不帶個人 email;本機 .env.local 已補 NEXT_PUBLIC_MIRA_ALLOWLIST 行為不變 2026-07-18 20:14:22 +08:00
Leo 5491409003 docs(mcp): README 加「更新 / acr update」段
- acr update:self-hosted 重跑部署、只部署變動 Worker、未變動略過(--force 全部重部)。
- 說明「新裝 vs 更新」差不多同一套流程,拿新零件(如 code)/新版就跑更新。
- 標待核佔位:下載源正從 GitHub codeload 改指 Gitea(Arcrun#4),確切行為待該 PR 定案。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 08:06:28 +00:00
Leo 0617dab70a docs(mcp): README 改為安裝+使用導向,零件投稿拆成 CONTRIBUTING-components.md
- README 補三種前端安裝(claude.ai connector / Claude Code / Claude Desktop),
  MCP URL 以源碼為準 = <origin>/mcp(DEFAULT_MCP_URL、resourceUri)。
- 認證段講 OAuth owner secret 閘 + MCP_STATIC_TOKEN 真祕密路徑,明講明碼-namespace 已移除。
- 工具總覽全用 arcrun_* 現役名(#20 rename)+ kbdb_* 資料層。
- 修正舊 README 兩處連結:.mcp.json url 補 /mcp、inspector 改 /mcp/inspector。
- 零件開發/投稿流程搬到 mcp/CONTRIBUTING-components.md,README 末留連結。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 06:39:04 +00:00
435 changed files with 142993 additions and 9796 deletions
+161
View File
@@ -0,0 +1,161 @@
# /wiki-extract — vault 增量萃取(Logseq / Obsidian → system-dev/wiki
把**筆記 vault**Logseq graph 如 `notes`/`kb`、或 Obsidian)的原始筆記,**增量、冪等**地
萃成 `system-dev/wiki/` 的精耕卡+`[[wikilink]]`。這是知識一庫 ingest 的**前段**
AI 只產卡片檔,下游 Arcrun ingest 再從 wikilink 機械拉三元組進 KBDB。
> **跟 `/wiki-init` 的分工**
> - `/wiki-init` 是**首次**建結構 + 全庫首萃(一次性)。
> - `/wiki-extract` 是**之後每次**的增量重萃——vault 會被 Syncthing/cron 持續灌新筆記,
> 這支負責「只萃變動的、沒變的不碰、不浪費 AI run」。給 Routine / cloud-worker 反覆跑。
> - **跑它的是你(CC / Routine)=LLM 本人,不需任何 token**。
> **邊界(硬規矩,別越界)**
> - 只往 `system-dev/wiki/` 寫。**絕不寫入 KBDB、絕不拉三元組紀錄**——三元組是下游
> Arcrun 從你產的 `[[wikilink]]` + `## 關聯` 機械映射(另一張 issue),不是這支的事。
> - **原始筆記唯讀**`journals/`、`pages/`、Obsidian 根 `.md` 是 leo 的手寫真身,
> 改了會被 Syncthing 推回他手機污染筆記 App。萃取=只讀原文、只寫 wiki。
> - **D16 精耕非 RAG**:萃「知識點」成自包含原子卡 + 建 wikilink,**不地毯灌原文全文**。
---
## 執行流程
### 第一步:確認這是 vault repo,定位 raw source
偵測邏輯**同 install.sh / wiki-init**
| 偵測到 | 型態 | raw source(要掃的原文) |
|--------|------|--------------------------|
| 根目錄有 `logseq/` | Logseq vault | `journals/*.md` + `pages/*.md` |
| 根目錄有 `.obsidian/` | Obsidian vault | vault 根下所有 `.md` |
| 都沒有 | **不是 vault** | → 停手。這支只處理 vault;一般 dev repo 開發時就手寫 `.claude`/`system-dev/wiki`,不需萃取 |
沒有 `system-dev/wiki/`?→ 先跑 `/wiki-init`(首次建結構+首萃),再回來用這支做增量。
### 第二步:content_hash 冪等 —— 決定哪些檔要萃(省 run 的核心)
讀萃取 manifest`system-dev/wiki/.extract-manifest.json`(不存在=首次,視同全部要萃)。
格式:
```json
{
"version": 1,
"algo": "sha256",
"sources": {
"journals/2026_07_01.md": {
"content_hash": "<sha256 of file bytes>",
"extracted_at": "2026-07-06",
"cards": ["Prompt能力即拆解自己邏輯的能力", "程式化邏輯可圖解任何主題不限AI"],
"skipped_reason": null
},
"journals/2026_06_25.md": {
"content_hash": "<sha256>",
"extracted_at": "2026-07-06",
"cards": [],
"skipped_reason": "空檔/訊息量不足,無可萃知識點"
}
}
}
```
對每個 raw source 檔:
1. 算目前 `content_hash``sha256sum <file>`,取檔案 bytes 的 hash)。
2. 跟 manifest 裡該檔的 `content_hash` 比:
- **相同 → skip,不讀不萃、不呼叫任何 AI 推理**(就算它上次 `cards: []` 也 skip——空檔沒變還是空)。
- **不同或不在 manifest → 這檔要(重)萃**。
3. manifest 有、但檔已不存在 → 該檔被刪,把它的 entry 從 manifest 移除(卡片是否連帶處理見第五步)。
> **這一步是「省 run」的重點**:vault 每天可能只動 1~2 個 journal,其餘幾十個檔 hash 沒變
> 就整批跳過,AI 只對真正變動的檔動腦。**重跑一個沒變動的 vault = 零 AI 呼叫、零 diff。**
### 第三步:對「要萃」的檔,抓知識點 + 任務
逐個變動檔讀原文,分兩類抽取:
**(a) 知識點 → 概念原子卡**
判準與卡片格式**完全依 `/wiki-init` 第五步**frontmatter `tags:`/`gloss:`、H1、麵包屑
`← [[<bucket>/00-INDEX]]``**來源**``## 摘要``## 重點``## 實體``## 關聯`
typed-edge 三元組、TAXONOMY 受控標籤、硬自檢等)——**不在這裡重寫格式,一律回去讀那份**。
廢話/訊息量薄的段落略過(在 manifest 記 `skipped_reason`,誠實留痕、不留卡)。
**(b) Logseq 任務 marker → 任務卡(task_status**
解析**完全依** `system-dev/docs/4-guides/logseq-markers.md`(單一真相源,與 template#4
tasks 投影共用同一套;**別自己另寫 mapping**)。摘要:
- 任務行 regex`^\s*- (TODO|DOING|NOW|LATER|WAITING|DONE|CANCELED|CANCELLED)\s+`
- 狀態正規化:TODO/LATER→`todo`、DOING/NOW→`in-progress`、WAITING→`blocked`
DONE→`done`、CANCELED/CANCELLED→`closed`
- 跳過 `:LOGBOOK:…:END:` 區塊與 `key:: value` 屬性行(`collapsed::``id::`
`SCHEDULED::``DEADLINE::`…),**別把 marker 或屬性當任務內文**。
有實質內容的任務 → 產一張任務卡進 `cards/tasks/` bucketfrontmatter 帶 `task_status`
```markdown
---
tags: [<領域標籤,依 TAXONOMY>]
task_status: todo # ← 依上表正規名;這是任務卡才有的欄位
gloss: 一句話定義這個任務要達成什麼(供下游 normalize)
---
# <任務一句話標題(marker 後的內文,去掉 marker>
← [[tasks/00-INDEX]]
**來源**`journals/2026_07_01.md`TODO block
**最後更新**YYYY-MM-DD
## 摘要
[任務要做什麼、脈絡]
## 實體
- **<關鍵實體正規名>**<同義詞>)— <一句描述>
## 關聯
### 內文知識關係(端點=上方 `## 實體` 正規名,一字不差)
- <實體A> >> <謂詞> >> <實體B>
### 卡片關係(卡對卡)
- [[本任務卡]] >> 涉及 >> [[相關概念卡]]
```
> 純瑣事任務(「買菜」這種無知識量)不必成獨立卡——可在 `cards/tasks/00-INDEX.md`
> 列一行帶狀態即可,避免灌垃圾卡。判準同 D16:有沒有知識/專案價值。
### 第四步:更新桶索引與 INDEX
- 每個動到的 bucket(如 `cards/notes/``cards/tasks/`)更新其 `00-INDEX.md`
(容器:只連不重寫,H2/H3 分節)。
- 更新 `system-dev/wiki/INDEX.md` 的標籤視圖與卡片清單。
- 任務卡可在 INDEX 開一個「任務視圖」按 `task_status` 聚類。
### 第五步:寫回 manifest + 驗證原文 0 動
1. 把這次萃過的每個檔的**新 `content_hash`**、`extracted_at`、產出的 `cards`
(或 `skipped_reason`)寫回 `system-dev/wiki/.extract-manifest.json`
**沒動到的檔的 entry 原樣保留**(別整檔重寫掉別人的 hash)。
2. 驗證原文零異動(踩過的坑):
```
git status --short journals/ pages/ # Obsidian 則看根目錄 .md ——須 0 新增 0 修改
```
有任何原文變動 → 你誤寫了 raw source,回滾。
### 第六步:完成報告
```
✅ wiki-extract 完成(增量)
掃描:N 個 raw source 檔
萃取:M 個(content_hash 變動)→ 產出 X 張概念卡 + Y 張任務卡
跳過:K 個(hash 未變,零 AI 呼叫)
任務狀態分布:todo A / in-progress B / done C / …
原文驗證:journals/ pages/ git status 0 異動 ✅
manifestsystem-dev/wiki/.extract-manifest.json 已更新
```
---
## 冪等自檢(Routine 反覆跑必守)
- [ ] 跑之前先讀 manifesthash 相同的檔**完全不進 AI**(不是「讀了才發現一樣」,是靠 hash 先擋)。
- [ ] 對「同一個沒變動的 vault」連跑兩次:第二次應是**零萃取、零卡片 diff、零 manifest 變化**。
- [ ] 只有 `system-dev/wiki/` 有寫入;`journals/`、`pages/` git status 全乾淨。
- [ ] 任務狀態用正規名,marker/屬性沒混進內文(照 `logseq-markers.md` 自檢)。
+5
View File
@@ -228,3 +228,8 @@ git status --short pages/ journals/ # 或一般專案的 docs/ ——須 0
原文驗證:pages/ journals/ git status 0 異動 ✅
下一步:用 /wiki-capture 把重要決策存進 wiki
```
> **vault repo 首萃後的增量重萃**Logseq / Obsidian vault 會被持續灌新筆記。首萃(本命令)
> 之後,改用 **`/wiki-extract`** 做增量——它靠 content_hash 只萃變動的檔(沒變=零 AI 呼叫),
> 並解析 Logseq 大寫任務 markerTODO/DOING/DONE…→ `task_status`,見
> `system-dev/docs/4-guides/logseq-markers.md`)。適合掛給 Routine / cloud-worker 反覆跑。
+80
View File
@@ -0,0 +1,80 @@
#!/bin/bash
# publish-lag-check.sh — SessionStart hook:偵測「公開 mirror 落後工作區」並出聲
#
# 病根(leo 2026-07-21 點名的真實風險):
# Gitea(草稿/工作現場)與 GitHub(正稿/成品櫥窗)是**手動同步**的
# (靠人跑 scripts/publish-github.sh --push,且需 D20 arm)。
# → 改了零件、重編 wasm 後若沒人記得發佈,**用戶抓到舊版且沒有任何錯誤訊息,
# 只是行為不對**——這種靜默失敗只有外部使用者會撞到,我們自己永遠測不到。
#
# 實例:安裝器的懶載會從
# cdn.jsdelivr.net/gh/youlinhsieh/Arcrun@main/.component-builds/<名>/component.wasm
# 抓 wasm。那個位址永遠指向 GitHub 上的**最後一次發佈**,不是我們本機的最新版。
#
# 原理:比對「工作區 HEAD」與「.github-public 最後一個 release commit 記錄的 snapshot」。
# publish-github.sh 的 commit 訊息格式固定為:release: snapshot <短hash> (<日期>)
# → 從中取出 hash,看它是不是工作區 HEAD 的祖先/相同。
#
# 只提醒不阻擋(exit 0):發不發佈是人的決定(且 push GitHub 需 leo 親跑 arm),
# hook 的職責只是消滅「忘了」這個失敗模式。
set -euo pipefail
MIRROR_DIR=".github-public"
# 沒裝發佈管線的 repo 直接安靜退出
[ -d "$MIRROR_DIR/.git" ] || exit 0
[ -f "scripts/publish-github.sh" ] || exit 0
git rev-parse --git-dir >/dev/null 2>&1 || exit 0
HEAD_SHORT="$(git rev-parse --short HEAD 2>/dev/null || echo '')"
[ -z "$HEAD_SHORT" ] && exit 0
# 從 mirror 最後一個 commit 訊息取出它當初發佈的來源 hash
LAST_MSG="$(git -C "$MIRROR_DIR" log -1 --format=%s 2>/dev/null || echo '')"
PUBLISHED="$(printf '%s' "$LAST_MSG" | sed -n 's/.*snapshot \([0-9a-f]\{6,\}\).*/\1/p')"
if [ -z "$PUBLISHED" ]; then
# mirror 存在但沒有可辨識的 release commit(可能還沒發過)
echo "════════════════════════════════════════════════"
echo "📦 這個 repo 有公開發佈管線,但 mirror 還沒發過任何版本"
echo "════════════════════════════════════════════════"
echo " 若已有用戶依賴公開版(例如安裝器從 jsDelivr 抓 wasm),現在是空的。"
echo " 發佈:leo 在頂層跑 scripts/github-arm.sh,再於本 repo 跑"
echo " GITHUB_REMOTE=... bash scripts/publish-github.sh --push"
echo ""
exit 0
fi
# 已發佈的那個 commit 就是現在的 HEAD → 同步,安靜
if [ "$PUBLISHED" = "$HEAD_SHORT" ]; then
exit 0
fi
# 算出落後幾個 commit(發佈點 → HEAD)。取不到就不顯示數字。
BEHIND="$(git rev-list --count "${PUBLISHED}..HEAD" 2>/dev/null || echo '')"
# 落後 0 且 hash 不同 → 可能是 mirror 比工作區新(罕見,例如剛 rebase),一樣提醒
echo "════════════════════════════════════════════════"
if [ -n "$BEHIND" ] && [ "$BEHIND" != "0" ]; then
printf '📤 公開 mirror 落後工作區 %s 個 commit(最後發佈:%s,現在:%s\n' \
"$BEHIND" "$PUBLISHED" "$HEAD_SHORT"
else
printf '📤 公開 mirror 與工作區不一致(最後發佈:%s,現在:%s)\n' "$PUBLISHED" "$HEAD_SHORT"
fi
echo "════════════════════════════════════════════════"
echo "⚠️ 外部使用者拿到的仍是舊版,而且**不會有任何錯誤訊息**——只是行為不對。"
echo " (安裝器的懶載直接從公開位址抓 wasm,落後=裝到舊零件。)"
echo ""
echo " 要發佈:① leo 在頂層跑 bash scripts/github-arm.sh \"<任務描述>\" 30"
echo " ② 本 repo 跑 GITHUB_REMOTE=https://github.com/<帳號>/<repo>.git \\"
echo " bash scripts/publish-github.sh --push"
echo " 不急著發也沒關係——這只是提醒,別讓它靜默漏掉。"
# 若這次落後的內容碰到 wasm,額外警告(那是用戶會直接抓的東西)
if git diff --name-only "${PUBLISHED}..HEAD" 2>/dev/null | grep -q '\.wasm$'; then
echo ""
echo " 🔴 這批改動**包含 .wasm 變更** → 用戶抓到的零件會跟你本機不同,優先發佈。"
fi
echo ""
exit 0
+15 -16
View File
@@ -18,8 +18,10 @@ cat <<'EOF'
→ 只能 TinyGo (main.go) 或 AssemblyScript,編譯成 .wasm
2. cypher-executor TS 裡禁止實作 credential/auth/JWT 業務邏輯
→ crypto.subtle.decrypt / sign 只准出現在 wasi-shim.ts 的 host function
→ crypto.subtle.sign 只准出現在 wasi-shim.ts 的 host function
crypto_decrypt 已廢除成永遠回失敗的 stubcredential 走 CF Workers Secrets
→ {{secret.X}} template 展開屬於 WASM 零件職責
(例外:*-seeds.ts 是資料宣告非呼叫實作,見 rule 07)
→ 禁止 hard-code gmail/telegram/sheets API endpoint
3. 禁止新增 Service Binding 綁零件
@@ -41,23 +43,20 @@ cat <<'EOF'
🚧 執行範圍:修改/建立/刪除 <檔案>
4. 每完成一個 task,立刻更新 tasks.md 的 [x],不批次
🔥 當前進行中 PhaseCredential Primitives TS → WASM
🔥 當前 active SDD:以 frontmatter `status: active` 為唯一判準(不靠本檔硬寫)
SDDdocs/3-specs/arcrun/credential-primitives-wasm/
已完成:Phase 0.1-0.5(核心合併 + u6u-core 刪除
未完成硬前置:
- Phase 0.6 wasi-shim 加 host functionskv_get / crypto_decrypt / crypto_sign_rs256
- Phase 0.7 component-loader WASM runner 路徑
未完成主要任務:
- Phase 1 auth_static_key WASM 零件(TinyGo
- Phase 2 auth_service_account WASM 零件
- Phase 3 刪除 cypher-executor 的三套違規 TS
* src/actions/credential-injector.ts(整檔刪)
* src/lib/jwt-signer.ts(整檔刪)
* src/lib/component-loader.ts 的 BUILTIN_API_RECIPES + BUILTIN_CREDENTIALS_MAP(整段刪)
查法:bash system-dev/scripts/sdd-active-check.sh
規則:system-dev/docs/3-specs/SDD-LIFECYCLE.md(單一活性鐵律,D35
session 開場請回報三個數字:現行規格名稱 + 未完成任務數 + pending-changes 待裁決數
⛔ 封測狀態:推遲(richblack 2026-04-19 決定)
原因:違規 TS 未清,不封測。
📦 已封存(closed,勿當進行中):
- credential-primitives-wasm → system-dev/docs/3-specs/archive/credential-primitives-wasm/
主線已達成:cypher-executor TS 不再有 credential/auth 業務邏輯
credential-injector.ts / jwt-signer.ts / BUILTIN_API_RECIPES 皆已移除)
credential 現行做法=CF Workers per-script Secrets + D1 目錄,arcrun 不自管加密金鑰
→ 見 .claude/rules/01-tech-stack.md「Credential 儲存規範」
⚠️ 殘留缺口(未隨封存視為完成):auth_mtls 從未實作、7.6 self-hosted auth 鏈
端到端未驗;要做需另立新 SDD。細節見該卷 tasks.md「封存時仍未完成的項目」
📚 詳細規範:
.claude/rules/00-sdd-protocol.md — SDD 協議
+27
View File
@@ -32,6 +32,33 @@ SDD 協議要求:code 和 SDD 必須同步更新。
EOF
fi
# ── console-ui:對外網址上是不是還跑著舊世代?(2026-08-08)────────────────
#
# 病(leo:「已經發生過一次這個錯誤,把舊版界面上到 prod,你要確定不可再犯」):
# 前端改完、commit 了、甚至 wiki 都寫了,但**沒有人把它推上去**——
# 而線上不會報錯,只是繼續展示半個月前的介面。08-08 實測:三個對外網址的
# apiBase/profile 全綠,跑的卻是 07-22 那一代。**組態對 ≠ 世代對。**
#
# 為什麼掛在 Stop:這裡正是 CC 要說「做完了」的那一刻。
# 不連網(每回合都跑),只比對「手上這一代」與「最後一次**通過線上實測**的部署紀錄」
# .deploy-state.json 只在 deploy.mjs 驗過線上後才寫,不是跑過指令就寫)。
# 要問線上真實現況:cd console-ui && npm run verify(那支才連網)。
if [ -d console-ui/scripts ] && command -v node >/dev/null 2>&1; then
LAG="$(cd console-ui && node scripts/verify-live.mjs --offline-lag 2>/dev/null)"
if [ -n "$LAG" ]; then
cat >&2 <<EOF
🕰️ console-ui:手上這一代**還沒送出去過**
$(echo "$LAG" | sed 's/^/ · /')
對外網址不會因此報錯——它只會繼續展示舊介面,而所有只驗組態的檢查都會說它是綠的。
要看線上現在真的在跑哪一代: cd console-ui && npm run verify
要送出去(含推完自動回頭驗線上):cd console-ui && npm run deploy:personal
EOF
fi
fi
# 若有暫存的 tasks.md 變動,提醒 commit
TASKS_DIFF=$(git -C "$(pwd)" status --porcelain -- 'docs/3-specs/**/tasks.md' 2>/dev/null | head -5)
if [[ -n "$TASKS_DIFF" ]]; then
+76
View File
@@ -0,0 +1,76 @@
#!/bin/bash
# subagent-wiki-guard.sh — PreToolUse(Task) hooksubagent 聽到「查」就自己先查 wiki
#
# 病根(2026-07-20):總管兩次派 agent 查 ENCRYPTION_KEYprompt 都只叫它「去查 repo 程式碼」。
# agent 於是從**稿子**推論出「這東西還活著、不能動」,總管照單全收去擋 leo 三輪。
#
# 🔑 設計轉向(leo 2026-07-21):
# 第一版是「上游沒交代讀 wiki 就擋下」——但那**還是依賴上游記得寫**,
# 跟「我記得讀 wiki」是同一個病。leo 點破:
# 「subagent 的問題跟你一樣。你叫它去查,就算你沒說要先查 wiki,
# 但它**只要聽到查,就應該主動查 wiki**,因為每個 repo 都有維護自己的 wiki。」
# → 改成 **注入式**:不擋、不要求上游改 prompt,直接把「先查 wiki」這條
# 以 additionalContext 注入給 subagent,讓它自己做。零依賴任何人記得。
#
# 行為:偵測到查證/實作類任務 → exit 0 並用 hookSpecificOutput 注入指示。
# 已含 wiki 指示、或非查證類任務 → 靜默放行(不重複注入)。
set -euo pipefail
INPUT=$(cat)
PROMPT=$(printf '%s' "$INPUT" | python3 -c "
import json,sys
try:
d=json.load(sys.stdin)
print(d.get('tool_input',{}).get('prompt',''))
except Exception: print('')
" 2>/dev/null || echo "")
[ -z "$PROMPT" ] && exit 0
# 上游已經交代了 → 不必重複注入
if printf '%s' "$PROMPT" | grep -qiE "wiki|agent-memory|mistakes\.md|decisions-summary"; then
exit 0
fi
# 只對「查證/實作」類任務注入(純寫作、計算、潤稿等不需要)
if ! printf '%s' "$PROMPT" | grep -qiE "查|盤點|核實|確認|調查|研究|找出|repo|程式碼|原始碼|source|實作|移除|刪除|重構|修|grep|codebase|\.ts|\.go|src/"; then
exit 0
fi
python3 - <<'PY'
import json
guidance = """【自動注入:查任何東西之前,先查 wiki】
你所在的 repo 有維護自己的 wiki(通常在 `system-dev/wiki/`,舊結構在 `.claude/wiki/`)。
**接到「查/盤點/核實/實作」類任務時,第一個動作是搜尋 wiki,不是翻程式碼。**
做法(30 秒,省下大量白工):
grep -rin "<本題關鍵字>" system-dev/wiki/ 2>/dev/null || grep -rin "<關鍵字>" .claude/wiki/
為什麼這是划算的:
• wiki 是前人已經查過、驗證過、被負責人糾正過的結論——**判準**。
• 程式碼與歷史文件是**稿子**:它反映「還沒清乾淨」,不等於「還在用」。
從稿子推論會系統性得出過時結論。
• wiki 沒記載,才值得花力氣翻原文。
三條硬規則:
1. **wiki 與程式碼衝突 → 以 wiki 為準**,並在回報中明確指出衝突,
不要自行用 code 推翻 wiki。
2. wiki 寫「不可動/待廢除/進行中」→ **讀它的解除條件並逐條核對**。
那是當時狀態,不是永久禁令;條件已滿足就是可動。
2026-07-20 實際事故:agent 只看到「不可動」就回報不能動,
實際上解除條件早已滿足,害負責人被擋三輪。)
3. 翻原文後若得到**新結論**,回報時明講「wiki 該更新」——wiki 過時是債,要還。
"""
print(json.dumps({
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"additionalContext": guidance
}
}, ensure_ascii=False))
PY
exit 0
+88
View File
@@ -0,0 +1,88 @@
#!/bin/bash
# wiki-first-search.sh — PreToolUse hook:要去翻原文/程式碼前,先把 wiki 命中結果推到眼前
#
# 病根(2026-07-20 leo 點破,mistakes 第一鐵律):
# 總管 session 開頭讀了 agent-memory 前 50 行就開工,關鍵那條在第 56 行 → 拿過期記憶擋了 leo 三輪。
# leo:「如果你不是讀而是**搜尋** wiki 就不會只讀 50 行就下定論,
# 而是就像我直接在頁面 cmd+F,那些都會高亮。」
#
# 設計要點(為什麼是這個形狀):
# 1. **搜尋 ≠ 通讀**:開場 push 全文(session-start-recall.sh)解決不了這題——量大必然只讀開頭。
# 這支反過來:在「你正要去查 code/原文」的當下,用你自己的關鍵字 grep wiki,只推命中行。
# 2. **時機是關鍵**:不是開場推、不是寫入時擋,而是**查詢動作發生的那一刻**介入。
# 3. **提醒不阻擋**exit 0):wiki 沒記載時本來就該去翻原文,擋下來反而礙事。
# 唯一目的是消滅「不知道 wiki 有寫」這件事。
#
# 觸發:Grep / Glob / Read 打向 code 或 docs 時(見下方 should_check)。
# 輸出:stdout 注入 context(命中的 wiki 行 + 檔名:行號)。
set -euo pipefail
INPUT=$(cat)
TOOL=$(printf '%s' "$INPUT" | python3 -c "import json,sys;print(json.load(sys.stdin).get('tool_name',''))" 2>/dev/null || echo "")
# 取出這次查詢的關鍵字:Grep 用 patternGlob/Read 用路徑的檔名部分
QUERY=$(printf '%s' "$INPUT" | python3 -c "
import json,sys,os,re
try:
d=json.load(sys.stdin); ti=d.get('tool_input',{})
q = ti.get('pattern') or ''
if not q:
p = ti.get('file_path') or ti.get('path') or ''
q = os.path.splitext(os.path.basename(p))[0] if p else ''
if not q:
# Bash2026-07-21 補的破口——原版只掛 Grep|Glob|Read
# 但「用 curl/wrangler 亂試部署方法」走的是 Bash,整支 hook 不觸發。
# leo 當場點破:wiki 早記著「寄信已驗證可用」,我卻沒查又自創方法。
# 只認「會動到外部系統/部署」的高風險指令,避免每個 ls 都洗版。
cmd = ti.get('command') or ''
if re.search(r'\b(wrangler|curl|npx|acr|gh|deploy|push)\b', cmd):
# 取指令中最具識別度的詞(worker 名/資源名/子命令)當搜尋詞
cand = re.findall(r'[A-Za-z_][A-Za-z0-9_-]{4,}', cmd)
skip = {'https','http','client','accounts','workers','scripts',
'application','content','Authorization','Bearer','python3',
'curl','npx','bash','echo','grep','local','branch','origin'}
cand = [c for c in cand if c not in skip and not c.startswith('-')]
q = max(cand, key=len) if cand else ''
# grep pattern 常含 regex 元字元;取最長的英數/底線詞當搜尋詞
words = re.findall(r'[A-Za-z_][A-Za-z0-9_]{3,}', q)
print(max(words, key=len) if words else '')
except Exception:
print('')
" 2>/dev/null || echo "")
[ -z "$QUERY" ] && exit 0
WIKI_DIR="system-dev/wiki"
[ -d "$WIKI_DIR" ] || exit 0
# 只在「查程式碼/文件」時提醒;查 wiki 本身就不用了(已經在讀了)
TARGET=$(printf '%s' "$INPUT" | python3 -c "
import json,sys
try:
d=json.load(sys.stdin); ti=d.get('tool_input',{})
print(ti.get('file_path') or ti.get('path') or '')
except Exception: print('')
" 2>/dev/null || echo "")
case "$TARGET" in
*system-dev/wiki*) exit 0 ;;
esac
# grep wiki(不分大小寫、含行號),最多 12 行避免洗版
HITS=$(grep -rin --include="*.md" -- "$QUERY" "$WIKI_DIR" 2>/dev/null | head -12 || true)
[ -z "$HITS" ] && exit 0
COUNT=$(printf '%s\n' "$HITS" | wc -l | tr -d ' ')
echo "════════════════════════════════════════════════"
printf '📚 wiki 已有「%s」的記載(%s 處,先看這裡再翻原文)\n' "$QUERY" "$COUNT"
echo "════════════════════════════════════════════════"
printf '%s\n' "$HITS" | sed 's|^system-dev/wiki/| |'
echo ""
echo "⚠️ wiki 是判準,程式碼與歷史文件只是稿子(mistakes 第一鐵律)。"
echo " • 上面若與你將要查的原文衝突 → **以 wiki 為準**,別用 code 推翻 wiki。"
echo " • 看到「不可動/待廢除/進行中」→ 先讀它的**解除條件**並逐條核對,"
echo " 那是當時狀態不是永久禁令;條件已滿足就是可動。"
echo " • wiki 沒答案才值得翻原文——翻完若得到新結論,**回頭更新 wiki**。"
echo ""
exit 0
+1 -3
View File
@@ -14,9 +14,7 @@
| 任務類型 | 對應 SDD |
|---------|---------|
| Auth primitive WASM 零件static_key/oauth2/service_account/mtls | `docs/3-specs/arcrun/credential-primitives-wasm/` |
| 清除 cypher-executor 裡的 TS 業務邏輯 | `docs/3-specs/arcrun/credential-primitives-wasm/` |
| WASI shim host functionskv_get / crypto_decrypt / crypto_sign_rs256 | `docs/3-specs/arcrun/credential-primitives-wasm/` |
| Auth primitive WASM 零件 / credential 儲存 / WASI shim host functions | **已封存**`system-dev/docs/3-specs/archive/credential-primitives-wasm/``status: closed`)。現行規範見 `.claude/rules/01-tech-stack.md`「Credential 儲存規範」;**新工作需另立 SDD**(含未實作的 `auth_mtls`),不得掛在已封存的卷上 |
| Auth Recipe 系統(recipe schema、KV 格式) | `docs/3-specs/arcrun/auth-recipe.md` |
| Landing Page | `docs/3-specs/arcrun/landing-page.md` |
| CLI / SDKPython/JS | `docs/3-specs/arcrun/sdk-and-website/` |
+17 -10
View File
@@ -30,7 +30,8 @@
|---|---|
| `u6u.http_request` | 發 HTTP 請求 |
| `u6u.kv_get` | 讀 Cloudflare KVWorker 側依 key 前綴路由到正確 KV) |
| `u6u.crypto_decrypt` | AES-GCM 解密(encryption key 永不暴露給 WASM |
| `u6u.secret_get` | 讀 CF Workers Secrets(只放行 `CRED_` 前綴 |
| `u6u.crypto_decrypt` | ⚠️ 已廢除,保留成永遠回失敗的 stub(現役 wasm 仍宣告此 import |
| `u6u.crypto_sign_rs256` | RSA-SHA256 簽章(PKCS8 bytes 傳入) |
**所有 host function 在 `cypher-executor/src/lib/wasi-shim.ts` 實作**。零件透過 WASI import 使用。
@@ -50,16 +51,22 @@
**警告:R2 不存平台內建零件的 WASM**。平台零件已 bundle 進各自的 Worker binary`[[wasm_modules]]``import ... assert { type: 'webassembly' }`)。
## 加解密規範
## Credential 儲存規範
- **演算法**AES-GCM 256-bit
- **加密位置**Client 端(CLI / Python SDK / JS SDK
- Python`cryptography` 套件
- JSWeb Crypto API`crypto.subtle`
- **解密位置**Server 端 **WASM primitive**(透過 host function `crypto_decrypt`
- cypher-executor TS **不解密**,只提供 host function
- `ENCRYPTION_KEY` 只在 Worker host function 內部讀取,**永不經 stdin / 回傳值傳給 WASM**
- **傳輸格式**`{ name, encrypted, iv }`iv base64、encrypted base64
**arcrun 不自管加密金鑰。** credential 明文由 **Cloudflare Workers Secrets** 託管
per-script secret,掛在用戶自己的 cypher worker 上):
- secret 名稱 = `CRED_{NAME}_{sha256(api_key)[:8]}`(跨租戶命名隔離
- D1 `credentials` 表只存**目錄**api_key / name / service / secret_ref),不存值
- D19:擁有目錄,不擁有內容物——連 owner 都讀不回,只能覆寫/刪除
- 需要 worker 設 `CF_SECRETS_API_TOKEN`(機密,用戶手動 put+ `CF_ACCOUNT_ID`(自動注入)
> 註:`crypto_decrypt` host function 是**永遠回失敗的 stub**,因為現役三個 `auth_*`
> `.wasm` 仍宣告該 import(缺項會讓 WASM instantiate 失敗)。三個零件重編後即可刪除。
- **取用位置**Server 端 **WASM primitive**(透過 host function `secret_get(ref)`
- cypher-executor TS 不碰業務邏輯,只提供 host function
- `secret_get` 只放行 `CRED_` 前綴,WASM 讀不到 worker 本身的其他機密
- **傳輸格式**:明文值走 TLS`POST /credentials`),不做 client 端加密
## 網路部署
+39 -5
View File
@@ -33,10 +33,13 @@ Auth primitive 必須透過 `component-worker-template/` 搭配 WASM binary 部
## 第二類:cypher-executor TS 的禁令
### 2.1 禁止新增任何 credential / auth / jwt 相關的 TS 檔案
**已存在但要刪**(Phase 1-3 範圍):
- `cypher-executor/src/actions/credential-injector.ts` → 刪除(走 WASM auth primitive
- `cypher-executor/src/lib/jwt-signer.ts`刪除(RS256 移入 auth_service_account WASM
- `cypher-executor/src/lib/component-loader.ts``BUILTIN_API_RECIPES``BUILTIN_CREDENTIALS_MAP` → 整段刪除
**清除已完成**2026-07-20credential-primitives-wasm 卷已封存)。下列曾違規的 TS **均已不存在**
列此僅為「禁止重新引入」的清單:
- ~~`cypher-executor/src/actions/credential-injector.ts`~~已刪(auth 走 WASM primitive
- ~~`cypher-executor/src/lib/jwt-signer.ts`~~ → 已刪(RS256 在 auth_service_account WASM
- ~~`component-loader.ts``BUILTIN_API_RECIPES` / `BUILTIN_CREDENTIALS_MAP`~~ → 已整段刪
**重新建立上述任一者 = 違規**
**Hook 會擋**:新增任何路徑含以下關鍵字的 `.ts` 檔案:
- `credential-injector``credential_injector`
@@ -83,7 +86,7 @@ Auth primitive 必須透過 `component-worker-template/` 搭配 WASM binary 部
13 個現有的 `SVC_*` 綁定(`cypher-executor/wrangler.toml`,邏輯零件)是歷史遺產(效能優化),**保留但不新增**。
> **2026-06-06 註credential-primitives-wasm Phase 7**self-hosted 的 cypher 與 auth worker 同在 `{sub}.workers.dev` zonecypher `fetch()` 打 auth 觸發 CF **same-zone 1042**(壓測階段 11)。**未用 service binding 解**(評估後廢:service binding 靜態、加/改要重 deploy cypher)。改用 **`global_fetch_strictly_public` compatibility flag**cypher wrangler.toml)讓 same-zone fetch 走公網前門 → 同 zone 也通,**auth 維持 HTTP fetch、不加 binding**。故本禁令不變。
> **2026-06-06 註**(來源:credential-primitives-wasm Phase 7,該卷已封存於 `system-dev/docs/3-specs/archive/`**本註記述的規則仍現行有效**):self-hosted 的 cypher 與 auth worker 同在 `{sub}.workers.dev` zonecypher `fetch()` 打 auth 觸發 CF **same-zone 1042**(壓測階段 11)。**未用 service binding 解**(評估後廢:service binding 靜態、加/改要重 deploy cypher)。改用 **`global_fetch_strictly_public` compatibility flag**cypher wrangler.toml)讓 same-zone fetch 走公網前門 → 同 zone 也通,**auth 維持 HTTP fetch、不加 binding**。故本禁令不變。
**Hook 會擋**bash 指令含 `wrangler tail` 以外、涉及 `[[services]]` 新增的 patternEdit wrangler.toml 新增 `[[services]]` 區塊時警告確認。
@@ -139,6 +142,37 @@ SDD 屬於架構決策,必須人確認。CC 不可以自行在 `docs/3-specs/`
---
---
## 第六類:租戶字串來源(Arcrun#108#105 同族)
### 6.1 靜態租戶字串不得用於資料面過濾
**知識資料面的 `owner_id`(三元組/entriesrecords/藏書地圖/工作流 KV)必須與寫入端同源。**
寫入端只有一個真相源=使用者 `~/.arcrun/config.yaml``api_key`(=實例 namespace
CLI push/小幫手上傳/MCP 都用它)。讀取端拿另一份手抄的環境變數預設值 → 全被過濾掉。
實害:`portalTenant(env) = env.CONSOLE_TENANT || "leo"` 讓 leo 的 **1854 條三元組被過濾成 0 個庫**
#108);前一天 `ownerNamespace(env) = env.MCP_OWNER_NAMESPACE || "leo"` 是同一句話(#105)。
**規則**
1. `cypher-executor/src/lib/tenant.ts` 是租戶字串的**唯一產地**。
`CONSOLE_TENANT` / `ARCRUN_NAMESPACE` 只能在該檔被讀取。
2. 知識資料面用 `knowledgeOwner(env)`(回 `TenantId`),過濾一律經
`ownerQuery()` / `ownerField()`——它們只吃 `TenantId``tsc` 就擋掉「隨手一個 string」。
3. 帳號層用 `accountTenant(env)`(回 `string`**刻意不是 TenantId**):帳號子 namespace
`{tenant}::portal` 與 cypher 自己寫的設定用它,型別上不可能流進知識資料面。
4. 身分解析路徑上**不准有字面預設值**。解析不到 → 丟 `TenantUnresolvedError`
誠實回「讀不到」(不是「你沒有」,#100 同一條)。
**機械強制**(規則存在但沒機制驗證=它會再犯第三次):
- 出貨閘:`scripts/build-worker-artifacts.mjs` 編 tier2 成品前先掃,違規 → **編不出成品**
- 本機自查:`cd cypher-executor && npm run check:tenant``npm test` 也會先跑它)。
- 規則本體:`cypher-executor/scripts/tenant-source-rules.mjs`(純函式);
閘自己的測試:`cypher-executor/tests/tenant-gate.test.ts`(壞例子會擋+合法寫法零誤攔)。
> 尚未接上 PreToolUse hook`.claude/hooks/` 為受保護檔案,需人類加入)。
> 要加的話:檢查器已備妥 `--stdin <相對路徑>` 模式,可在寫入前擋。
## Hook Block 訊息格式
當 hook 擋住一個操作時,訊息格式統一為:
+1 -1
View File
@@ -79,7 +79,7 @@ Service binding 需要 `wrangler.toml` 裡寫死 `[[services]]`,且要 redeplo
**禁止新增任何 Service Binding**。所有新零件(含 auth primitive)都走 HTTP URL 路徑。
**same-zone 1042 的解credential-primitives-wasm Phase 72026-06-06**self-hosted 的 cypher 與 auth worker 同在 `{sub}.workers.dev` zonecypher `fetch()` 打 auth 觸發 CF **1042**(官方 docs:「fetch from another Worker on the **same zone**」;官方 cypher 在 `cypher.arcrun.dev`、打 `*.workers.dev` 屬跨 zone 故不踩——非官方有 flag)。**解法不是 service binding**(評估後廢:靜態、加/改要重 deploy),而是 cypher wrangler.toml 加 **`global_fetch_strictly_public` flag**——讓 same-zone fetch 走公網前門 → 同 zone 也通。auth 維持 HTTP fetch、不加 binding。官方加此 flag 行為不變(本就跨 zone),self-host 被修好 → **官方與 self-host 共用同一份 toml**
**same-zone 1042 的解**(來源:credential-primitives-wasm Phase 72026-06-06;該卷已封存於 `system-dev/docs/3-specs/archive/`**但本段規則仍現行有效**):self-hosted 的 cypher 與 auth worker 同在 `{sub}.workers.dev` zonecypher `fetch()` 打 auth 觸發 CF **1042**(官方 docs:「fetch from another Worker on the **same zone**」;官方 cypher 在 `cypher.arcrun.dev`、打 `*.workers.dev` 屬跨 zone 故不踩——非官方有 flag)。**解法不是 service binding**(評估後廢:靜態、加/改要重 deploy),而是 cypher wrangler.toml 加 **`global_fetch_strictly_public` flag**——讓 same-zone fetch 走公網前門 → 同 zone 也通。auth 維持 HTTP fetch、不加 binding。官方加此 flag 行為不變(本就跨 zone),self-host 被修好 → **官方與 self-host 共用同一份 toml**
**仍禁止**:為**用戶自製 / 服務專屬零件**(`gmail-worker``notion-worker` 之類)新增 binding——那些是 recipe 的事,不該有 binding。**workflow 層(用戶串零件)一律 HTTP URL 不變。**
+45 -47
View File
@@ -1,77 +1,75 @@
# 當前進度(SessionStart 會注入此檔重點)
> 更新時間:2026-04-19
> 權威來源`docs/3-specs/arcrun/credential-primitives-wasm/tasks.md`
> 此檔僅摘要,詳細狀態以 tasks.md 為準。
> 更新時間:2026-07-21
> **權威來源design.md frontmatter `status: active`**(機器可查),不是本檔。
> 查法:`bash system-dev/scripts/sdd-active-check.sh`
> 本檔只是索引,任何與 frontmatter 衝突之處以 frontmatter 為準。
---
## 封測狀態
## 現行 active SDD
**原定明天封測,richblack 決定推遲**,原因:cypher-executor 有三套 TS 業務邏輯違反「零件一律 WASM」架構原則(Phase 1-3 要清除的程式碼),在清除前不封測。
**RAG Portal 多人授權**`system-dev/docs/3-specs/portal-auth/`
依 SDD 生命週期鐵律(D35,全文 `system-dev/docs/3-specs/SDD-LIFECYCLE.md`):
任何時刻整個 repo 只允許**一份** `status: active`。所有開發任務必須對應它的 tasks,
找不到對應 → 停下來問,不准直接做。
**session 開場請回報三個數字**:現行規格名稱 未完成任務數 `pending-changes.md` 待裁決 proposal 數。
---
## 目前 PhaseCredential Primitives TS → WASM
## 已封存(closed,勿當進行中)
**SDD 位置**`docs/3-specs/arcrun/credential-primitives-wasm/design.md` + `tasks.md`
### Credential Primitives TS → WASM
### 已完成
`system-dev/docs/3-specs/archive/credential-primitives-wasm/`2026-07-21 封存)
- **Phase 0.10.5**:核心合併(u6u-core 併入 arcrun、21 個零件 contract 完整、刪除重複 `credentials/` 目錄、CREDENTIALS_KV binding 確認、刪除 `matrix/u6u-core/`
- `registry/components/` 下 21 個零件(邏輯 + API)都有 `main.go` + `.wasm`
**主線已達成**cypher-executor TS 不再實作任何 credential / auth 業務邏輯。
`credential-injector.ts``jwt-signer.ts``BUILTIN_API_RECIPES` / `BUILTIN_CREDENTIALS_MAP`
全數移除(最後一項 T10 於 2026-07-20 commit `20c7610` 完成)。
### 進行中 / 未完成
**credential 現行做法**(非被另一卷 supersede,是機制整個換掉):
- 密文 → **CF Workers per-script Secrets**`CRED_{NAME}_{hash}`),arcrun 讀不回明文
- 目錄 → **D1** `credentials` 表(name / service / secret_ref,不含值)
- **arcrun 不自管任何加密金鑰**;`crypto_decrypt` host function 已成永遠回失敗的 stub
- 完整規範:`.claude/rules/01-tech-stack.md`「Credential 儲存規範」
| Task | 狀態 | 阻擋關係 |
|-----|------|---------|
| 0.6 wasi-shim 新增 `kv_get` / `crypto_decrypt` / `crypto_sign_rs256` host functions | ⬜ 未開始 | **Phase 1-3 的硬前置** |
| 0.7 component-loader 新增 WASM runner 路徑 | ⬜ 未開始 | **Phase 1-3 的硬前置** |
| 1.1-1.8 `auth_static_key` WASM 零件(TinyGo | ⬜ 未開始 | 涵蓋 80% 服務 |
| 2.1-2.6 `auth_service_account` WASM 零件(JWT signing | ⬜ 未開始 | Google Service Account 等 |
| 3.1-3.5 清除 `component-loader.ts``BUILTIN_API_RECIPES` | ⬜ 未開始 | 要先有 Phase 1-2 的 WASM 零件 |
| 4.1-4.4 `auth_oauth2` + `auth_mtls`(封測後) | ⬜ 未開始 | 非阻擋項 |
| 5.1-5.7 核心穩定驗證(全域搜尋確認無殘餘 TS) | ⬜ 未開始 | 封測啟動門檻 |
⚠️ **封存 ≠ 全部做完**。真實殘留缺口(要做需另立新 SDD,不得掛回已封存的卷):
- **`auth_mtls` 從未實作**`registry/components/auth_mtls/` 不存在,mTLS 認證不支援)
- **7.6 self-hosted auth 鏈端到端未驗**`global_fetch_strictly_public` flag 是否真解
same-zone 1042,在自架帳號上從未實測)
- 數項端到端測試(1.7/1.8/2.6/3.5/3.6/5.1)無驗證記錄
### Phase 1-3 要**徹底刪除**的 TS 檔案(不是搬、不是改,是刪)
| 檔案 | 違反什麼 |
|-----|---------|
| `cypher-executor/src/actions/credential-injector.ts` | AES 解密、template 展開、JWT 邏輯 —— 應在 WASM |
| `cypher-executor/src/lib/jwt-signer.ts` | RS256 JWT 簽章邏輯 —— 應在 `auth_service_account.wasm` |
| `cypher-executor/src/lib/component-loader.ts``BUILTIN_API_RECIPES`~100 行) | gmail/telegram/line/gsheets/http_request/cron 的 TS 實作 —— 應全部走對應 WASM 零件 |
---
## 下一個 session 第一件要做的事
**讀 `docs/3-specs/arcrun/credential-primitives-wasm/tasks.md`**,然後決定從 Phase 0.6 還是 0.7 開始。
0.6host functions)和 0.7WASM runner)是並列的前置工作,哪個先都可以,但都要在 Phase 1 開始之前完成。
逐條核實見該卷 `tasks.md` 的「封存時仍未完成的項目」段。
---
## SDD 索引
> 「進行中」與否一律以 frontmatter 為準,下表僅路徑速查。
| 子系統 | SDD |
|--------|-----|
| **主要(正在動)** Credential Primitives WASM 改寫 | `docs/3-specs/arcrun/credential-primitives-wasm/` |
| **LI (LLM Interface)** — AI 操盤手使用體驗(2026-05-16 新建,mira dogfood 痛點轉化) | `docs/3-specs/llm-interface/` |
| arcrun 總進度 | `docs/3-specs/arcrun/arcrun.md` |
| Auth Recipe 系統(schema、預建 20 個服務) | `docs/3-specs/arcrun/auth-recipe.md` |
| Landing Page | `docs/3-specs/arcrun/landing-page.md` |
| SDK + Website | `docs/3-specs/arcrun/sdk-and-website/design.md` |
| arcrun MVP 整體 | `docs/3-specs/arcrun-core-mvp/design.md` |
| Credential 長期規格(需求源) | `docs/user_requirements/credential_parts.md` |
| Platform Evolution | `docs/3-specs/arcrun-platform-evolution/design.md` |
| Tech Stack 詳細 | `docs/3-specs/tech.md` |
| **現行 active** RAG Portal 多人授權 | `system-dev/docs/3-specs/portal-auth/` |
| ~~Credential Primitives WASM~~closed | `system-dev/docs/3-specs/archive/credential-primitives-wasm/` |
| LLM InterfaceAI 操盤手使用體驗) | `system-dev/docs/3-specs/llm-interface/` |
| arcrun 總進度 | `system-dev/docs/3-specs/arcrun/arcrun.md` |
| Auth Recipe 系統(schema、預建服務) | `system-dev/docs/3-specs/arcrun/auth-recipe.md` |
| SDK + Website | `system-dev/docs/3-specs/arcrun/sdk-and-website/` |
| arcrun MVP 整體 | `system-dev/docs/3-specs/arcrun-core-mvp/` |
| Platform Evolution | `system-dev/docs/3-specs/arcrun-platform-evolution/` |
| Tech Stack 詳細 | `system-dev/docs/3-specs/tech.md` |
| 生命週期鐵律 | `system-dev/docs/3-specs/SDD-LIFECYCLE.md` |
| 待裁決 proposal | `system-dev/docs/3-specs/pending-changes.md` |
---
## 技術備註(CC 常搞錯的點)
1. **每個 WASM 零件 = 獨立 Worker = 公開 URL**(例:`gmail.arcrun.dev`。不是從 R2 動態讀。
1. **每個 WASM 零件 = 獨立 Worker = 獨立 URL**。不是從 R2 動態讀。
2. **Cypher binding = YAML 裡寫 URL 清單**。不是 Cloudflare service binding。
3. **cypher-executor 只做 routing + host functions**。業務邏輯全在 WASM 零件。
4. **TinyGo 有限制**`crypto/rsa` 支援不全 → 用 host function `crypto_sign_rs256` 讓 Worker 代簽。
5. 詳見 `.claude/rules/03-component-architecture.md`
5. **credential 不自管金鑰**:走 `secret_get(ref)` 讀 CF Workers Secrets`crypto_decrypt` 是 stub
6. 詳見 `.claude/rules/03-component-architecture.md``01-tech-stack.md`
+5 -2
View File
@@ -62,10 +62,13 @@ find . -name 'wrangler.toml' -not -path '*/node_modules/*' -not -name 'wrangler.
**CI 只提供 Cloudflare 驗證,不碰 runtime secret**
- GH Actions secrets:`CLOUDFLARE_API_TOKEN``CLOUDFLARE_ACCOUNT_ID`(一次性設好)
- Runtime secret(例:`ENCRYPTION_KEY``OPENAI_KEY``GOOGLE_API_KEY`):
- Runtime secret(例:`CF_SECRETS_API_TOKEN``OPENAI_KEY``GOOGLE_API_KEY`):
- **由 richblack 一次性手動** `wrangler secret put <KEY>` 設進各 Worker
- 不進 CI,不進 `wrangler.toml` `[vars]`
- 需要的 Worker:`auth_static_key``auth_service_account`(兩個都要 `ENCRYPTION_KEY`)
- 需要的 Worker:`arcrun-cypher-executor`(`CF_SECRETS_API_TOKEN`——寫 credential 進
Workers Secrets 用;`CF_ACCOUNT_ID` 非機密由 `acr init` 自動注入)
> `auth_static_key` / `auth_service_account` 不需要任何 secret。
---
+32
View File
@@ -99,6 +99,38 @@ CLI / MCP / Python lib / JS lib 全是薄殼:只做「介面轉換 + 暴露」
---
## 3.6 自舉例外:能力該「只實作一次」,但不一定要是 HTTP API2026-08-12 立)
> 立這條的原因:`Arcrun#97`(更新把使用者的工作流與登入弄不見)的修法一開始寫在
> `cli/src/lib/resource-resolver.ts` ——**能力住在介面層,違反 §0**。
> 後果不是理論:**安裝器(arcrun-rag)拿不到它,於是同一個 bug 只修了一半**,
> 走 `acr` 的人有保護、走 `install.arcrun.dev` 的人沒有——**而所有真實用戶走後者**。
> leo 2026-08-12:「**根本就不應該在 CLI,我要的是一個大家都可以用到的規則。**」
修法(PR #111)把它搬到 **`shared/resource-rule/`:一份零依賴 ESM**
`acr` 與安裝器共用。**它刻意不是 cypher 的 API 端點**,三個理由:
| 為什麼不放 API | 說明 |
|---|---|
| **自舉** | 這條規則要在「決定怎麼裝」的當下用得到,而安裝器的工作正是把 cypher 生出來。放進 cypher = 要先有雞才能有蛋。 |
| **輸入是使用者自己的帳號狀態** | 判斷依據是使用者 CF 帳號上的綁定。送去平台託管的 worker 換答案 ⇒ ①「能不能安裝」綁在平台是否活著 ②使用者的帳號拓撲交給第三方。 |
| **它根本不需要是服務** | 這是**純函式**,唯一的 IO 由呼叫端注入。**§0 要求「能力只實作一次」,不是「能力一定要是 HTTP」。** |
🔴 **所以本檔 §0 的正確讀法是**:能力**只准有一份**,且**不准住在任何單一介面裡**。
「放 API」是達成它的**常見手段**,不是唯一手段。
**判準仍然是那句口訣**:「這段邏輯換一個介面要不要重寫?」要 → 它是能力。
📌 **給下一個人**:看到 `shared/` 底下的純函式**不要「修正」成 API 端點**——
先讀 `shared/resource-rule/README.md §2`,那裡記著評估過並否決的其他形態
(共用 npm 套件=自舉問題換位置;做成零件=要用 TinyGo 重寫一次,那才是第二份實作)。
📌 **打包例外**`acr` 是獨立 npm 套件,`npm pack` 打不進套件目錄外的檔案 ⇒
`cli/` 下必須有一份**逐位元組副本**。那不是第二份實作——
`scripts/sync-resource-rule.mjs --check` 一有漂移就 exit 1,且 `build``test` 都會先跑它
(同 `cli/harness/` 的既有慣例)。**手改副本 = build 紅 publish 擋下。**
---
## 4. 統一帳號來源(薄殼共用同一身份)
所有薄殼讀**同一份**身份設定:
+28 -1
View File
@@ -10,6 +10,15 @@
"timeout": 5
}
]
},
{
"matcher": "startup|resume|clear",
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/publish-lag-check.sh"
}
]
}
],
"PreToolUse": [
@@ -42,6 +51,24 @@
"timeout": 5
}
]
},
{
"matcher": "Grep|Glob|Read|Bash",
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/wiki-first-search.sh"
}
]
},
{
"matcher": "Task",
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/subagent-wiki-guard.sh"
}
]
}
],
"PostToolUse": [
@@ -68,4 +95,4 @@
}
]
}
}
}
@@ -19,4 +19,3 @@ id = "e7f4320f88d343f187e35e3543dd74c9"
binding = "RECIPES"
id = "9cf9db905c6241f78503199e58b2ffe0"
# ENCRYPTION_KEY 透過 wrangler secret put 設定
@@ -11,7 +11,6 @@
*
* 安全邊界:
* - api_key 經 stdin 傳進 WASM,同時綁到 host function 的 kv_get 做越權檢查
* - ENCRYPTION_KEY 只存在於 host function 的 closure 中,不會進入 WASM 記憶體
* - private key 只以 PKCS8 bytes 傳給 crypto_sign_rs256 host function,decrypt 後 plaintext 不離開 WASM
*/
@@ -20,5 +20,3 @@ id = "e7f4320f88d343f187e35e3543dd74c9"
binding = "RECIPES"
id = "9cf9db905c6241f78503199e58b2ffe0"
# ENCRYPTION_KEY 透過 wrangler secret set 設定
# wrangler secret put ENCRYPTION_KEY
@@ -8,7 +8,6 @@
*
* 安全邊界:
* - api_key 經 stdin 傳進 WASM,同時綁到 host function 的 kv_get 做越權檢查
* - ENCRYPTION_KEY 只存在於 host function 的 closure 中,不會進入 WASM 記憶體
*/
import componentWasm from '../component.wasm' assert { type: 'webassembly' };
@@ -20,5 +20,3 @@ id = "e7f4320f88d343f187e35e3543dd74c9"
binding = "RECIPES"
id = "9cf9db905c6241f78503199e58b2ffe0"
# ENCRYPTION_KEY 透過 wrangler secret set 設定
# wrangler secret put ENCRYPTION_KEY
@@ -1,14 +0,0 @@
{
"name": "arcrun-kbdb-upsert-block",
"version": "1.0.0",
"private": true,
"type": "module",
"dependencies": {
"hono": "^4.7.0"
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20250408.0",
"typescript": "^5.4.0",
"wrangler": "^4.0.0"
}
}
-898
View File
@@ -1,898 +0,0 @@
lockfileVersion: '9.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
importers:
.:
dependencies:
hono:
specifier: ^4.7.0
version: 4.12.18
devDependencies:
'@cloudflare/workers-types':
specifier: ^4.20250408.0
version: 4.20260511.1
typescript:
specifier: ^5.4.0
version: 5.9.3
wrangler:
specifier: ^4.0.0
version: 4.90.1(@cloudflare/workers-types@4.20260511.1)
packages:
'@cloudflare/kv-asset-handler@0.5.0':
resolution: {integrity: sha512-jxQYkj8dSIzc0cD6cMMNdOc1UVjqSqu8BZdor5s8cGjW2I8BjODt/kWPVdY+u9zj3ms75Q5qaZgnxUad83+eAg==}
engines: {node: '>=22.0.0'}
'@cloudflare/unenv-preset@2.16.1':
resolution: {integrity: sha512-ECxObrMfyTl5bhQf/lZCXwo5G6xX9IAUo+nDMKK4SZ8m4Jvvxp52vilxyySSWh2YTZz8+HQ07qGH/2rEom1vDw==}
peerDependencies:
unenv: 2.0.0-rc.24
workerd: '>1.20260305.0 <2.0.0-0'
peerDependenciesMeta:
workerd:
optional: true
'@cloudflare/workerd-darwin-64@1.20260508.1':
resolution: {integrity: sha512-IT3r6VgiSwIesL4AJbxjgxvIxwWZqM7BKkhYAzOKHl4GF2M0TxeOahUIXd+CYXVZgHX8ceEg+MXbEehPelJyNg==}
engines: {node: '>=16'}
cpu: [x64]
os: [darwin]
'@cloudflare/workerd-darwin-arm64@1.20260508.1':
resolution: {integrity: sha512-JTVsisOJPcNKw0qovPjqyBWYahfdhUh7/9NICiG5wxaEQ45PYKdoqNq0hOAAIqvqoxsKZBvTgcPTJREPqk7avA==}
engines: {node: '>=16'}
cpu: [arm64]
os: [darwin]
'@cloudflare/workerd-linux-64@1.20260508.1':
resolution: {integrity: sha512-zO38pCc27YlsZiPYcaZnosy0/t7abXrRU3VEO1oKfUvnaCpHgphDG+VsrmHL+kntda6hrtNwg2jLeMAqqIjnjw==}
engines: {node: '>=16'}
cpu: [x64]
os: [linux]
'@cloudflare/workerd-linux-arm64@1.20260508.1':
resolution: {integrity: sha512-XhJa780Ia6MNIrtxn/ruZHS79b9pu5EKPfRNReaUqxy8erPT2fs93axMfFoS9kIkcaRRj/1TOUKcTeAMoywY7w==}
engines: {node: '>=16'}
cpu: [arm64]
os: [linux]
'@cloudflare/workerd-windows-64@1.20260508.1':
resolution: {integrity: sha512-QdDOK3B/Ul1s3QmIwDrFyx9230to6LsNmWcVR8w+TYjNZuRPzqQBgusp78LO7MlqCoEl9dvIcN00jkJnLtBSfw==}
engines: {node: '>=16'}
cpu: [x64]
os: [win32]
'@cloudflare/workers-types@4.20260511.1':
resolution: {integrity: sha512-FA+si7cOq9i/gtCHhIc0XJL0l1F/ApF+m00752Aj7WZFJrj3ZulT2T8/+rT3BabMT0QEnqFEGIqCgrmqhgEfMg==}
'@cspotcode/source-map-support@0.8.1':
resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
engines: {node: '>=12'}
'@emnapi/runtime@1.10.0':
resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==}
'@esbuild/aix-ppc64@0.27.3':
resolution: {integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [aix]
'@esbuild/android-arm64@0.27.3':
resolution: {integrity: sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [android]
'@esbuild/android-arm@0.27.3':
resolution: {integrity: sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==}
engines: {node: '>=18'}
cpu: [arm]
os: [android]
'@esbuild/android-x64@0.27.3':
resolution: {integrity: sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==}
engines: {node: '>=18'}
cpu: [x64]
os: [android]
'@esbuild/darwin-arm64@0.27.3':
resolution: {integrity: sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [darwin]
'@esbuild/darwin-x64@0.27.3':
resolution: {integrity: sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==}
engines: {node: '>=18'}
cpu: [x64]
os: [darwin]
'@esbuild/freebsd-arm64@0.27.3':
resolution: {integrity: sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==}
engines: {node: '>=18'}
cpu: [arm64]
os: [freebsd]
'@esbuild/freebsd-x64@0.27.3':
resolution: {integrity: sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==}
engines: {node: '>=18'}
cpu: [x64]
os: [freebsd]
'@esbuild/linux-arm64@0.27.3':
resolution: {integrity: sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [linux]
'@esbuild/linux-arm@0.27.3':
resolution: {integrity: sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==}
engines: {node: '>=18'}
cpu: [arm]
os: [linux]
'@esbuild/linux-ia32@0.27.3':
resolution: {integrity: sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==}
engines: {node: '>=18'}
cpu: [ia32]
os: [linux]
'@esbuild/linux-loong64@0.27.3':
resolution: {integrity: sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==}
engines: {node: '>=18'}
cpu: [loong64]
os: [linux]
'@esbuild/linux-mips64el@0.27.3':
resolution: {integrity: sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==}
engines: {node: '>=18'}
cpu: [mips64el]
os: [linux]
'@esbuild/linux-ppc64@0.27.3':
resolution: {integrity: sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [linux]
'@esbuild/linux-riscv64@0.27.3':
resolution: {integrity: sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==}
engines: {node: '>=18'}
cpu: [riscv64]
os: [linux]
'@esbuild/linux-s390x@0.27.3':
resolution: {integrity: sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==}
engines: {node: '>=18'}
cpu: [s390x]
os: [linux]
'@esbuild/linux-x64@0.27.3':
resolution: {integrity: sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==}
engines: {node: '>=18'}
cpu: [x64]
os: [linux]
'@esbuild/netbsd-arm64@0.27.3':
resolution: {integrity: sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==}
engines: {node: '>=18'}
cpu: [arm64]
os: [netbsd]
'@esbuild/netbsd-x64@0.27.3':
resolution: {integrity: sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==}
engines: {node: '>=18'}
cpu: [x64]
os: [netbsd]
'@esbuild/openbsd-arm64@0.27.3':
resolution: {integrity: sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==}
engines: {node: '>=18'}
cpu: [arm64]
os: [openbsd]
'@esbuild/openbsd-x64@0.27.3':
resolution: {integrity: sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==}
engines: {node: '>=18'}
cpu: [x64]
os: [openbsd]
'@esbuild/openharmony-arm64@0.27.3':
resolution: {integrity: sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==}
engines: {node: '>=18'}
cpu: [arm64]
os: [openharmony]
'@esbuild/sunos-x64@0.27.3':
resolution: {integrity: sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==}
engines: {node: '>=18'}
cpu: [x64]
os: [sunos]
'@esbuild/win32-arm64@0.27.3':
resolution: {integrity: sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==}
engines: {node: '>=18'}
cpu: [arm64]
os: [win32]
'@esbuild/win32-ia32@0.27.3':
resolution: {integrity: sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==}
engines: {node: '>=18'}
cpu: [ia32]
os: [win32]
'@esbuild/win32-x64@0.27.3':
resolution: {integrity: sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==}
engines: {node: '>=18'}
cpu: [x64]
os: [win32]
'@img/colour@1.1.0':
resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==}
engines: {node: '>=18'}
'@img/sharp-darwin-arm64@0.34.5':
resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm64]
os: [darwin]
'@img/sharp-darwin-x64@0.34.5':
resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64]
os: [darwin]
'@img/sharp-libvips-darwin-arm64@1.2.4':
resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==}
cpu: [arm64]
os: [darwin]
'@img/sharp-libvips-darwin-x64@1.2.4':
resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==}
cpu: [x64]
os: [darwin]
'@img/sharp-libvips-linux-arm64@1.2.4':
resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-arm@1.2.4':
resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==}
cpu: [arm]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-ppc64@1.2.4':
resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-riscv64@1.2.4':
resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-s390x@1.2.4':
resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-x64@1.2.4':
resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==}
cpu: [x64]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linuxmusl-arm64@1.2.4':
resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==}
cpu: [arm64]
os: [linux]
libc: [musl]
'@img/sharp-libvips-linuxmusl-x64@1.2.4':
resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==}
cpu: [x64]
os: [linux]
libc: [musl]
'@img/sharp-linux-arm64@0.34.5':
resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@img/sharp-linux-arm@0.34.5':
resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm]
os: [linux]
libc: [glibc]
'@img/sharp-linux-ppc64@0.34.5':
resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@img/sharp-linux-riscv64@0.34.5':
resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@img/sharp-linux-s390x@0.34.5':
resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@img/sharp-linux-x64@0.34.5':
resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64]
os: [linux]
libc: [glibc]
'@img/sharp-linuxmusl-arm64@0.34.5':
resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm64]
os: [linux]
libc: [musl]
'@img/sharp-linuxmusl-x64@0.34.5':
resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64]
os: [linux]
libc: [musl]
'@img/sharp-wasm32@0.34.5':
resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [wasm32]
'@img/sharp-win32-arm64@0.34.5':
resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm64]
os: [win32]
'@img/sharp-win32-ia32@0.34.5':
resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [ia32]
os: [win32]
'@img/sharp-win32-x64@0.34.5':
resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64]
os: [win32]
'@jridgewell/resolve-uri@3.1.2':
resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
engines: {node: '>=6.0.0'}
'@jridgewell/sourcemap-codec@1.5.5':
resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
'@jridgewell/trace-mapping@0.3.9':
resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==}
'@poppinss/colors@4.1.6':
resolution: {integrity: sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg==}
'@poppinss/dumper@0.6.5':
resolution: {integrity: sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw==}
'@poppinss/exception@1.2.3':
resolution: {integrity: sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==}
'@sindresorhus/is@7.2.0':
resolution: {integrity: sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==}
engines: {node: '>=18'}
'@speed-highlight/core@1.2.15':
resolution: {integrity: sha512-BMq1K3DsElxDWawkX6eLg9+CKJrTVGCBAWVuHXVUV2u0s2711qiChLSId6ikYPfxhdYocLNt3wWwSvDiTvFabw==}
blake3-wasm@2.1.5:
resolution: {integrity: sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==}
cookie@1.1.1:
resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==}
engines: {node: '>=18'}
detect-libc@2.1.2:
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
engines: {node: '>=8'}
error-stack-parser-es@1.0.5:
resolution: {integrity: sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==}
esbuild@0.27.3:
resolution: {integrity: sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==}
engines: {node: '>=18'}
hasBin: true
fsevents@2.3.3:
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin]
hono@4.12.18:
resolution: {integrity: sha512-RWzP96k/yv0PQfyXnWjs6zot20TqfpfsNXhOnev8d1InAxubW93L11/oNUc3tQqn2G0bSdAOBpX+2uDFHV7kdQ==}
engines: {node: '>=16.9.0'}
kleur@4.1.5:
resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==}
engines: {node: '>=6'}
miniflare@4.20260508.0:
resolution: {integrity: sha512-h3aG+PA8jEH76V4ZtBAbs3g7kjMfHJUF8hPvxeeajLTKwir+G+dqfBODg5yF9MT29LqrZKCRQRqzfHPWX4kCIg==}
engines: {node: '>=22.0.0'}
hasBin: true
path-to-regexp@6.3.0:
resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==}
pathe@2.0.3:
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
semver@7.8.0:
resolution: {integrity: sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==}
engines: {node: '>=10'}
hasBin: true
sharp@0.34.5:
resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
supports-color@10.2.2:
resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==}
engines: {node: '>=18'}
tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
typescript@5.9.3:
resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
engines: {node: '>=14.17'}
hasBin: true
undici@7.24.8:
resolution: {integrity: sha512-6KQ/+QxK49Z/p3HO6E5ZCZWNnCasyZLa5ExaVYyvPxUwKtbCPMKELJOqh7EqOle0t9cH/7d2TaaTRRa6Nhs4YQ==}
engines: {node: '>=20.18.1'}
unenv@2.0.0-rc.24:
resolution: {integrity: sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==}
workerd@1.20260508.1:
resolution: {integrity: sha512-VlnjyH3AjVddpSK7J54nsCVgf8i2733pl8GjKttfNi7vN/hEjjAk20d2b1nDToOLKvRQpTewRnVkqaaeGHCaAw==}
engines: {node: '>=16'}
hasBin: true
wrangler@4.90.1:
resolution: {integrity: sha512-u2KrieKSMfRM0toTst/CfDtcRraeoVjmcExcMWgILM/ytq3qcDhuOAULoZSyPHzma43lfLJy1BC544drFyqe1A==}
engines: {node: '>=22.0.0'}
hasBin: true
peerDependencies:
'@cloudflare/workers-types': ^4.20260508.1
peerDependenciesMeta:
'@cloudflare/workers-types':
optional: true
ws@8.18.0:
resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==}
engines: {node: '>=10.0.0'}
peerDependencies:
bufferutil: ^4.0.1
utf-8-validate: '>=5.0.2'
peerDependenciesMeta:
bufferutil:
optional: true
utf-8-validate:
optional: true
youch-core@0.3.3:
resolution: {integrity: sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==}
youch@4.1.0-beta.10:
resolution: {integrity: sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==}
snapshots:
'@cloudflare/kv-asset-handler@0.5.0': {}
'@cloudflare/unenv-preset@2.16.1(unenv@2.0.0-rc.24)(workerd@1.20260508.1)':
dependencies:
unenv: 2.0.0-rc.24
optionalDependencies:
workerd: 1.20260508.1
'@cloudflare/workerd-darwin-64@1.20260508.1':
optional: true
'@cloudflare/workerd-darwin-arm64@1.20260508.1':
optional: true
'@cloudflare/workerd-linux-64@1.20260508.1':
optional: true
'@cloudflare/workerd-linux-arm64@1.20260508.1':
optional: true
'@cloudflare/workerd-windows-64@1.20260508.1':
optional: true
'@cloudflare/workers-types@4.20260511.1': {}
'@cspotcode/source-map-support@0.8.1':
dependencies:
'@jridgewell/trace-mapping': 0.3.9
'@emnapi/runtime@1.10.0':
dependencies:
tslib: 2.8.1
optional: true
'@esbuild/aix-ppc64@0.27.3':
optional: true
'@esbuild/android-arm64@0.27.3':
optional: true
'@esbuild/android-arm@0.27.3':
optional: true
'@esbuild/android-x64@0.27.3':
optional: true
'@esbuild/darwin-arm64@0.27.3':
optional: true
'@esbuild/darwin-x64@0.27.3':
optional: true
'@esbuild/freebsd-arm64@0.27.3':
optional: true
'@esbuild/freebsd-x64@0.27.3':
optional: true
'@esbuild/linux-arm64@0.27.3':
optional: true
'@esbuild/linux-arm@0.27.3':
optional: true
'@esbuild/linux-ia32@0.27.3':
optional: true
'@esbuild/linux-loong64@0.27.3':
optional: true
'@esbuild/linux-mips64el@0.27.3':
optional: true
'@esbuild/linux-ppc64@0.27.3':
optional: true
'@esbuild/linux-riscv64@0.27.3':
optional: true
'@esbuild/linux-s390x@0.27.3':
optional: true
'@esbuild/linux-x64@0.27.3':
optional: true
'@esbuild/netbsd-arm64@0.27.3':
optional: true
'@esbuild/netbsd-x64@0.27.3':
optional: true
'@esbuild/openbsd-arm64@0.27.3':
optional: true
'@esbuild/openbsd-x64@0.27.3':
optional: true
'@esbuild/openharmony-arm64@0.27.3':
optional: true
'@esbuild/sunos-x64@0.27.3':
optional: true
'@esbuild/win32-arm64@0.27.3':
optional: true
'@esbuild/win32-ia32@0.27.3':
optional: true
'@esbuild/win32-x64@0.27.3':
optional: true
'@img/colour@1.1.0': {}
'@img/sharp-darwin-arm64@0.34.5':
optionalDependencies:
'@img/sharp-libvips-darwin-arm64': 1.2.4
optional: true
'@img/sharp-darwin-x64@0.34.5':
optionalDependencies:
'@img/sharp-libvips-darwin-x64': 1.2.4
optional: true
'@img/sharp-libvips-darwin-arm64@1.2.4':
optional: true
'@img/sharp-libvips-darwin-x64@1.2.4':
optional: true
'@img/sharp-libvips-linux-arm64@1.2.4':
optional: true
'@img/sharp-libvips-linux-arm@1.2.4':
optional: true
'@img/sharp-libvips-linux-ppc64@1.2.4':
optional: true
'@img/sharp-libvips-linux-riscv64@1.2.4':
optional: true
'@img/sharp-libvips-linux-s390x@1.2.4':
optional: true
'@img/sharp-libvips-linux-x64@1.2.4':
optional: true
'@img/sharp-libvips-linuxmusl-arm64@1.2.4':
optional: true
'@img/sharp-libvips-linuxmusl-x64@1.2.4':
optional: true
'@img/sharp-linux-arm64@0.34.5':
optionalDependencies:
'@img/sharp-libvips-linux-arm64': 1.2.4
optional: true
'@img/sharp-linux-arm@0.34.5':
optionalDependencies:
'@img/sharp-libvips-linux-arm': 1.2.4
optional: true
'@img/sharp-linux-ppc64@0.34.5':
optionalDependencies:
'@img/sharp-libvips-linux-ppc64': 1.2.4
optional: true
'@img/sharp-linux-riscv64@0.34.5':
optionalDependencies:
'@img/sharp-libvips-linux-riscv64': 1.2.4
optional: true
'@img/sharp-linux-s390x@0.34.5':
optionalDependencies:
'@img/sharp-libvips-linux-s390x': 1.2.4
optional: true
'@img/sharp-linux-x64@0.34.5':
optionalDependencies:
'@img/sharp-libvips-linux-x64': 1.2.4
optional: true
'@img/sharp-linuxmusl-arm64@0.34.5':
optionalDependencies:
'@img/sharp-libvips-linuxmusl-arm64': 1.2.4
optional: true
'@img/sharp-linuxmusl-x64@0.34.5':
optionalDependencies:
'@img/sharp-libvips-linuxmusl-x64': 1.2.4
optional: true
'@img/sharp-wasm32@0.34.5':
dependencies:
'@emnapi/runtime': 1.10.0
optional: true
'@img/sharp-win32-arm64@0.34.5':
optional: true
'@img/sharp-win32-ia32@0.34.5':
optional: true
'@img/sharp-win32-x64@0.34.5':
optional: true
'@jridgewell/resolve-uri@3.1.2': {}
'@jridgewell/sourcemap-codec@1.5.5': {}
'@jridgewell/trace-mapping@0.3.9':
dependencies:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.5
'@poppinss/colors@4.1.6':
dependencies:
kleur: 4.1.5
'@poppinss/dumper@0.6.5':
dependencies:
'@poppinss/colors': 4.1.6
'@sindresorhus/is': 7.2.0
supports-color: 10.2.2
'@poppinss/exception@1.2.3': {}
'@sindresorhus/is@7.2.0': {}
'@speed-highlight/core@1.2.15': {}
blake3-wasm@2.1.5: {}
cookie@1.1.1: {}
detect-libc@2.1.2: {}
error-stack-parser-es@1.0.5: {}
esbuild@0.27.3:
optionalDependencies:
'@esbuild/aix-ppc64': 0.27.3
'@esbuild/android-arm': 0.27.3
'@esbuild/android-arm64': 0.27.3
'@esbuild/android-x64': 0.27.3
'@esbuild/darwin-arm64': 0.27.3
'@esbuild/darwin-x64': 0.27.3
'@esbuild/freebsd-arm64': 0.27.3
'@esbuild/freebsd-x64': 0.27.3
'@esbuild/linux-arm': 0.27.3
'@esbuild/linux-arm64': 0.27.3
'@esbuild/linux-ia32': 0.27.3
'@esbuild/linux-loong64': 0.27.3
'@esbuild/linux-mips64el': 0.27.3
'@esbuild/linux-ppc64': 0.27.3
'@esbuild/linux-riscv64': 0.27.3
'@esbuild/linux-s390x': 0.27.3
'@esbuild/linux-x64': 0.27.3
'@esbuild/netbsd-arm64': 0.27.3
'@esbuild/netbsd-x64': 0.27.3
'@esbuild/openbsd-arm64': 0.27.3
'@esbuild/openbsd-x64': 0.27.3
'@esbuild/openharmony-arm64': 0.27.3
'@esbuild/sunos-x64': 0.27.3
'@esbuild/win32-arm64': 0.27.3
'@esbuild/win32-ia32': 0.27.3
'@esbuild/win32-x64': 0.27.3
fsevents@2.3.3:
optional: true
hono@4.12.18: {}
kleur@4.1.5: {}
miniflare@4.20260508.0:
dependencies:
'@cspotcode/source-map-support': 0.8.1
sharp: 0.34.5
undici: 7.24.8
workerd: 1.20260508.1
ws: 8.18.0
youch: 4.1.0-beta.10
transitivePeerDependencies:
- bufferutil
- utf-8-validate
path-to-regexp@6.3.0: {}
pathe@2.0.3: {}
semver@7.8.0: {}
sharp@0.34.5:
dependencies:
'@img/colour': 1.1.0
detect-libc: 2.1.2
semver: 7.8.0
optionalDependencies:
'@img/sharp-darwin-arm64': 0.34.5
'@img/sharp-darwin-x64': 0.34.5
'@img/sharp-libvips-darwin-arm64': 1.2.4
'@img/sharp-libvips-darwin-x64': 1.2.4
'@img/sharp-libvips-linux-arm': 1.2.4
'@img/sharp-libvips-linux-arm64': 1.2.4
'@img/sharp-libvips-linux-ppc64': 1.2.4
'@img/sharp-libvips-linux-riscv64': 1.2.4
'@img/sharp-libvips-linux-s390x': 1.2.4
'@img/sharp-libvips-linux-x64': 1.2.4
'@img/sharp-libvips-linuxmusl-arm64': 1.2.4
'@img/sharp-libvips-linuxmusl-x64': 1.2.4
'@img/sharp-linux-arm': 0.34.5
'@img/sharp-linux-arm64': 0.34.5
'@img/sharp-linux-ppc64': 0.34.5
'@img/sharp-linux-riscv64': 0.34.5
'@img/sharp-linux-s390x': 0.34.5
'@img/sharp-linux-x64': 0.34.5
'@img/sharp-linuxmusl-arm64': 0.34.5
'@img/sharp-linuxmusl-x64': 0.34.5
'@img/sharp-wasm32': 0.34.5
'@img/sharp-win32-arm64': 0.34.5
'@img/sharp-win32-ia32': 0.34.5
'@img/sharp-win32-x64': 0.34.5
supports-color@10.2.2: {}
tslib@2.8.1:
optional: true
typescript@5.9.3: {}
undici@7.24.8: {}
unenv@2.0.0-rc.24:
dependencies:
pathe: 2.0.3
workerd@1.20260508.1:
optionalDependencies:
'@cloudflare/workerd-darwin-64': 1.20260508.1
'@cloudflare/workerd-darwin-arm64': 1.20260508.1
'@cloudflare/workerd-linux-64': 1.20260508.1
'@cloudflare/workerd-linux-arm64': 1.20260508.1
'@cloudflare/workerd-windows-64': 1.20260508.1
wrangler@4.90.1(@cloudflare/workers-types@4.20260511.1):
dependencies:
'@cloudflare/kv-asset-handler': 0.5.0
'@cloudflare/unenv-preset': 2.16.1(unenv@2.0.0-rc.24)(workerd@1.20260508.1)
blake3-wasm: 2.1.5
esbuild: 0.27.3
miniflare: 4.20260508.0
path-to-regexp: 6.3.0
unenv: 2.0.0-rc.24
workerd: 1.20260508.1
optionalDependencies:
'@cloudflare/workers-types': 4.20260511.1
fsevents: 2.3.3
transitivePeerDependencies:
- bufferutil
- utf-8-validate
ws@8.18.0: {}
youch-core@0.3.3:
dependencies:
'@poppinss/exception': 1.2.3
error-stack-parser-es: 1.0.5
youch@4.1.0-beta.10:
dependencies:
'@poppinss/colors': 4.1.6
'@poppinss/dumper': 0.6.5
'@speed-highlight/core': 1.2.15
cookie: 1.1.1
youch-core: 0.3.3
@@ -1,8 +0,0 @@
allowBuilds:
esbuild: true
sharp: true
workerd: true
onlyBuiltDependencies:
- esbuild
- sharp
- workerd
@@ -1,81 +0,0 @@
/**
* arcrun WASM 零件 Worker (kbdb_upsert_block)
* POST / → JSON input → WASM (WASI preview1) → JSON output
* SDD: polaris/mira/.agents/specs/mira-app/design.md §3.5.12.4.1
* matrix/arcrun/.agents/specs/arcrun/arcrun.md 三-B 新零件加入紀錄
*/
import componentWasm from '../component.wasm' assert { type: 'webassembly' };
import { Hono } from 'hono';
import { cors } from 'hono/cors';
import { createWasiShim, type WasiHostFunctions } from '../../../cypher-executor/src/lib/wasi-shim';
const app = new Hono();
app.use('*', cors());
app.get('/', (c) => c.json({ ok: true, component: 'kbdb_upsert_block' }));
app.post('/', async (c) => {
let input: unknown;
try {
input = await c.req.json();
} catch {
return c.json({ success: false, error: 'request body must be JSON' }, 400);
}
try {
const result = await runWasm(input);
return c.json(result);
} catch (e) {
return c.json(
{ success: false, error: e instanceof Error ? e.message : String(e) },
500,
);
}
});
export default app;
async function runWasm(input: unknown): Promise<unknown> {
const hostFunctions: WasiHostFunctions = {
http_request: async (url, method, headersJson, body) => {
const headers: Record<string, string> = {};
if (headersJson) {
try {
const parsed = JSON.parse(headersJson);
if (parsed && typeof parsed === 'object') {
for (const [k, v] of Object.entries(parsed as Record<string, unknown>)) {
if (typeof v === 'string') headers[k] = v;
}
}
} catch {}
}
const init: RequestInit = { method, headers };
if (body && method.toUpperCase() !== 'GET' && method.toUpperCase() !== 'HEAD') {
init.body = body;
}
const res = await fetch(url, init);
const text = await res.text();
// 修架構債(同 http_request):非 2xx 包成帶 "error" key 的 envelope
// 讓 WASM 端既有的 error 判定正確識別失敗(原本只回 body 丟掉 status → 4xx 被判 success)。
if (!res.ok) {
return JSON.stringify({ error: `HTTP ${res.status}`, status: res.status, body: text });
}
return text;
},
};
const shim = createWasiShim(JSON.stringify(input), hostFunctions);
const instance = await WebAssembly.instantiate(
componentWasm as WebAssembly.Module,
shim.imports,
);
shim.setMemory(instance.exports.memory as WebAssembly.Memory);
await shim.run(instance);
const stdout = shim.getStdout().trim();
const stderr = shim.getStderr().trim();
if (stderr) console.error('[kbdb_upsert_block wasm stderr]', stderr);
if (!stdout) throw new Error('WASM component produced no output');
return JSON.parse(stdout);
}
@@ -1,11 +0,0 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "ES2022",
"moduleResolution": "bundler",
"lib": ["ES2022"],
"types": ["@cloudflare/workers-types"],
"strict": true,
"noEmit": true
}
}
@@ -1,12 +0,0 @@
name = "arcrun-kbdb-upsert-block"
main = "src/index.ts"
compatibility_date = "2025-02-19"
compatibility_flags = ["nodejs_compat"]
workers_dev = true
[vars]
COMPONENT_ID = "kbdb_upsert_block"
[[routes]]
pattern = "kbdb-upsert-block.arcrun.dev/*"
zone_name = "arcrun.dev"
-14
View File
@@ -1,14 +0,0 @@
{
"name": "arcrun-km-writer",
"version": "1.0.0",
"private": true,
"type": "module",
"dependencies": {
"hono": "^4.7.0"
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20250408.0",
"typescript": "^5.4.0",
"wrangler": "^4.0.0"
}
}
-898
View File
@@ -1,898 +0,0 @@
lockfileVersion: '9.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
importers:
.:
dependencies:
hono:
specifier: ^4.7.0
version: 4.12.14
devDependencies:
'@cloudflare/workers-types':
specifier: ^4.20250408.0
version: 4.20260420.1
typescript:
specifier: ^5.4.0
version: 5.9.3
wrangler:
specifier: ^4.0.0
version: 4.83.0(@cloudflare/workers-types@4.20260420.1)
packages:
'@cloudflare/kv-asset-handler@0.4.2':
resolution: {integrity: sha512-SIOD2DxrRRwQ+jgzlXCqoEFiKOFqaPjhnNTGKXSRLvp1HiOvapLaFG2kEr9dYQTYe8rKrd9uvDUzmAITeNyaHQ==}
engines: {node: '>=18.0.0'}
'@cloudflare/unenv-preset@2.16.0':
resolution: {integrity: sha512-8ovsRpwzPoEqPUzoErAYVv8l3FMZNeBVQfJTvtzP4AgLSRGZISRfuChFxHWUQd3n6cnrwkuTGxT+2cGo8EsyYg==}
peerDependencies:
unenv: 2.0.0-rc.24
workerd: 1.20260301.1 || ~1.20260302.1 || ~1.20260303.1 || ~1.20260304.1 || >1.20260305.0 <2.0.0-0
peerDependenciesMeta:
workerd:
optional: true
'@cloudflare/workerd-darwin-64@1.20260415.1':
resolution: {integrity: sha512-dsxaKsQm3LnPGNPEdsRv09QN3Y4DqCw7kX5j6noKqbAtro2jTr95sVlYM1jUxZ5FkOl1f7SXgaKKB9t5H5Nkbg==}
engines: {node: '>=16'}
cpu: [x64]
os: [darwin]
'@cloudflare/workerd-darwin-arm64@1.20260415.1':
resolution: {integrity: sha512-+JgSgVA49KyKteHRA1SnonE4Zn5Ei5zdAp5FQMxFmXI8qulZw4Hl7safXxRyK4i9sTO8gl7TFOKO5Q64VPvSDQ==}
engines: {node: '>=16'}
cpu: [arm64]
os: [darwin]
'@cloudflare/workerd-linux-64@1.20260415.1':
resolution: {integrity: sha512-tU+9pwsqCy8afOVlGtiWrWQc/fedQK4SRm4KPIAt+zOiQWDxWASm6YGBUJis5c648WN80yz47qnmdDi8DQNOcA==}
engines: {node: '>=16'}
cpu: [x64]
os: [linux]
'@cloudflare/workerd-linux-arm64@1.20260415.1':
resolution: {integrity: sha512-bR9uITnV19r5NQ14xnypi2xHXu2iQvfYV8cVgx0JouFUmWwTEEAwFVojDdssGq93VHX9hr/pi2IRUZeegbYBog==}
engines: {node: '>=16'}
cpu: [arm64]
os: [linux]
'@cloudflare/workerd-windows-64@1.20260415.1':
resolution: {integrity: sha512-4NuMLlerI0Ijua3Ir8HXQ+qyNvCUDEG5gDco5Om+sAiK6rnWiz+aGoSlbB8W16yW9QAgzCstbmXLiVknUBflfQ==}
engines: {node: '>=16'}
cpu: [x64]
os: [win32]
'@cloudflare/workers-types@4.20260420.1':
resolution: {integrity: sha512-DHT9JnSn9cIiCSdL76OxW+Xvc1+ml1CWzWvgVwreoHQ+E604aeFxPPHp9X7nE+XRWm2NH4l0OgtxUI5T/nuI3g==}
'@cspotcode/source-map-support@0.8.1':
resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
engines: {node: '>=12'}
'@emnapi/runtime@1.10.0':
resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==}
'@esbuild/aix-ppc64@0.27.3':
resolution: {integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [aix]
'@esbuild/android-arm64@0.27.3':
resolution: {integrity: sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [android]
'@esbuild/android-arm@0.27.3':
resolution: {integrity: sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==}
engines: {node: '>=18'}
cpu: [arm]
os: [android]
'@esbuild/android-x64@0.27.3':
resolution: {integrity: sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==}
engines: {node: '>=18'}
cpu: [x64]
os: [android]
'@esbuild/darwin-arm64@0.27.3':
resolution: {integrity: sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [darwin]
'@esbuild/darwin-x64@0.27.3':
resolution: {integrity: sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==}
engines: {node: '>=18'}
cpu: [x64]
os: [darwin]
'@esbuild/freebsd-arm64@0.27.3':
resolution: {integrity: sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==}
engines: {node: '>=18'}
cpu: [arm64]
os: [freebsd]
'@esbuild/freebsd-x64@0.27.3':
resolution: {integrity: sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==}
engines: {node: '>=18'}
cpu: [x64]
os: [freebsd]
'@esbuild/linux-arm64@0.27.3':
resolution: {integrity: sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [linux]
'@esbuild/linux-arm@0.27.3':
resolution: {integrity: sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==}
engines: {node: '>=18'}
cpu: [arm]
os: [linux]
'@esbuild/linux-ia32@0.27.3':
resolution: {integrity: sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==}
engines: {node: '>=18'}
cpu: [ia32]
os: [linux]
'@esbuild/linux-loong64@0.27.3':
resolution: {integrity: sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==}
engines: {node: '>=18'}
cpu: [loong64]
os: [linux]
'@esbuild/linux-mips64el@0.27.3':
resolution: {integrity: sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==}
engines: {node: '>=18'}
cpu: [mips64el]
os: [linux]
'@esbuild/linux-ppc64@0.27.3':
resolution: {integrity: sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [linux]
'@esbuild/linux-riscv64@0.27.3':
resolution: {integrity: sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==}
engines: {node: '>=18'}
cpu: [riscv64]
os: [linux]
'@esbuild/linux-s390x@0.27.3':
resolution: {integrity: sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==}
engines: {node: '>=18'}
cpu: [s390x]
os: [linux]
'@esbuild/linux-x64@0.27.3':
resolution: {integrity: sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==}
engines: {node: '>=18'}
cpu: [x64]
os: [linux]
'@esbuild/netbsd-arm64@0.27.3':
resolution: {integrity: sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==}
engines: {node: '>=18'}
cpu: [arm64]
os: [netbsd]
'@esbuild/netbsd-x64@0.27.3':
resolution: {integrity: sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==}
engines: {node: '>=18'}
cpu: [x64]
os: [netbsd]
'@esbuild/openbsd-arm64@0.27.3':
resolution: {integrity: sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==}
engines: {node: '>=18'}
cpu: [arm64]
os: [openbsd]
'@esbuild/openbsd-x64@0.27.3':
resolution: {integrity: sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==}
engines: {node: '>=18'}
cpu: [x64]
os: [openbsd]
'@esbuild/openharmony-arm64@0.27.3':
resolution: {integrity: sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==}
engines: {node: '>=18'}
cpu: [arm64]
os: [openharmony]
'@esbuild/sunos-x64@0.27.3':
resolution: {integrity: sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==}
engines: {node: '>=18'}
cpu: [x64]
os: [sunos]
'@esbuild/win32-arm64@0.27.3':
resolution: {integrity: sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==}
engines: {node: '>=18'}
cpu: [arm64]
os: [win32]
'@esbuild/win32-ia32@0.27.3':
resolution: {integrity: sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==}
engines: {node: '>=18'}
cpu: [ia32]
os: [win32]
'@esbuild/win32-x64@0.27.3':
resolution: {integrity: sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==}
engines: {node: '>=18'}
cpu: [x64]
os: [win32]
'@img/colour@1.1.0':
resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==}
engines: {node: '>=18'}
'@img/sharp-darwin-arm64@0.34.5':
resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm64]
os: [darwin]
'@img/sharp-darwin-x64@0.34.5':
resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64]
os: [darwin]
'@img/sharp-libvips-darwin-arm64@1.2.4':
resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==}
cpu: [arm64]
os: [darwin]
'@img/sharp-libvips-darwin-x64@1.2.4':
resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==}
cpu: [x64]
os: [darwin]
'@img/sharp-libvips-linux-arm64@1.2.4':
resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-arm@1.2.4':
resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==}
cpu: [arm]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-ppc64@1.2.4':
resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-riscv64@1.2.4':
resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-s390x@1.2.4':
resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-x64@1.2.4':
resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==}
cpu: [x64]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linuxmusl-arm64@1.2.4':
resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==}
cpu: [arm64]
os: [linux]
libc: [musl]
'@img/sharp-libvips-linuxmusl-x64@1.2.4':
resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==}
cpu: [x64]
os: [linux]
libc: [musl]
'@img/sharp-linux-arm64@0.34.5':
resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@img/sharp-linux-arm@0.34.5':
resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm]
os: [linux]
libc: [glibc]
'@img/sharp-linux-ppc64@0.34.5':
resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@img/sharp-linux-riscv64@0.34.5':
resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@img/sharp-linux-s390x@0.34.5':
resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@img/sharp-linux-x64@0.34.5':
resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64]
os: [linux]
libc: [glibc]
'@img/sharp-linuxmusl-arm64@0.34.5':
resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm64]
os: [linux]
libc: [musl]
'@img/sharp-linuxmusl-x64@0.34.5':
resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64]
os: [linux]
libc: [musl]
'@img/sharp-wasm32@0.34.5':
resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [wasm32]
'@img/sharp-win32-arm64@0.34.5':
resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm64]
os: [win32]
'@img/sharp-win32-ia32@0.34.5':
resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [ia32]
os: [win32]
'@img/sharp-win32-x64@0.34.5':
resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64]
os: [win32]
'@jridgewell/resolve-uri@3.1.2':
resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
engines: {node: '>=6.0.0'}
'@jridgewell/sourcemap-codec@1.5.5':
resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
'@jridgewell/trace-mapping@0.3.9':
resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==}
'@poppinss/colors@4.1.6':
resolution: {integrity: sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg==}
'@poppinss/dumper@0.6.5':
resolution: {integrity: sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw==}
'@poppinss/exception@1.2.3':
resolution: {integrity: sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==}
'@sindresorhus/is@7.2.0':
resolution: {integrity: sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==}
engines: {node: '>=18'}
'@speed-highlight/core@1.2.15':
resolution: {integrity: sha512-BMq1K3DsElxDWawkX6eLg9+CKJrTVGCBAWVuHXVUV2u0s2711qiChLSId6ikYPfxhdYocLNt3wWwSvDiTvFabw==}
blake3-wasm@2.1.5:
resolution: {integrity: sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==}
cookie@1.1.1:
resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==}
engines: {node: '>=18'}
detect-libc@2.1.2:
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
engines: {node: '>=8'}
error-stack-parser-es@1.0.5:
resolution: {integrity: sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==}
esbuild@0.27.3:
resolution: {integrity: sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==}
engines: {node: '>=18'}
hasBin: true
fsevents@2.3.3:
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin]
hono@4.12.14:
resolution: {integrity: sha512-am5zfg3yu6sqn5yjKBNqhnTX7Cv+m00ox+7jbaKkrLMRJ4rAdldd1xPd/JzbBWspqaQv6RSTrgFN95EsfhC+7w==}
engines: {node: '>=16.9.0'}
kleur@4.1.5:
resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==}
engines: {node: '>=6'}
miniflare@4.20260415.0:
resolution: {integrity: sha512-JoExRWN4YBI2luA5BoSMFEgi8rQWXUGzo3mtE+58VXCLV3jj/Xnk5Yeqs/IXWz8Es5GJIaq6BtsixDvAxXSIng==}
engines: {node: '>=18.0.0'}
hasBin: true
path-to-regexp@6.3.0:
resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==}
pathe@2.0.3:
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
semver@7.7.4:
resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==}
engines: {node: '>=10'}
hasBin: true
sharp@0.34.5:
resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
supports-color@10.2.2:
resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==}
engines: {node: '>=18'}
tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
typescript@5.9.3:
resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
engines: {node: '>=14.17'}
hasBin: true
undici@7.24.8:
resolution: {integrity: sha512-6KQ/+QxK49Z/p3HO6E5ZCZWNnCasyZLa5ExaVYyvPxUwKtbCPMKELJOqh7EqOle0t9cH/7d2TaaTRRa6Nhs4YQ==}
engines: {node: '>=20.18.1'}
unenv@2.0.0-rc.24:
resolution: {integrity: sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==}
workerd@1.20260415.1:
resolution: {integrity: sha512-phyPjRnx+mQDfkhN9ENPioL1L0SdhYs4S0YmJK/xF9Oga+ykNfdSy1MHnsOj8yqnOV96zcVQMx32dJ0r3pq0jQ==}
engines: {node: '>=16'}
hasBin: true
wrangler@4.83.0:
resolution: {integrity: sha512-gw5g3LCiuAqVWxaoKY6+quE0HzAUEFb/FV3oAlNkE1ttd4XP3FiV91XDkkzUCcdqxS4WjhQvPhIDBNdhEi8P0A==}
engines: {node: '>=20.3.0'}
hasBin: true
peerDependencies:
'@cloudflare/workers-types': ^4.20260415.1
peerDependenciesMeta:
'@cloudflare/workers-types':
optional: true
ws@8.18.0:
resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==}
engines: {node: '>=10.0.0'}
peerDependencies:
bufferutil: ^4.0.1
utf-8-validate: '>=5.0.2'
peerDependenciesMeta:
bufferutil:
optional: true
utf-8-validate:
optional: true
youch-core@0.3.3:
resolution: {integrity: sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==}
youch@4.1.0-beta.10:
resolution: {integrity: sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==}
snapshots:
'@cloudflare/kv-asset-handler@0.4.2': {}
'@cloudflare/unenv-preset@2.16.0(unenv@2.0.0-rc.24)(workerd@1.20260415.1)':
dependencies:
unenv: 2.0.0-rc.24
optionalDependencies:
workerd: 1.20260415.1
'@cloudflare/workerd-darwin-64@1.20260415.1':
optional: true
'@cloudflare/workerd-darwin-arm64@1.20260415.1':
optional: true
'@cloudflare/workerd-linux-64@1.20260415.1':
optional: true
'@cloudflare/workerd-linux-arm64@1.20260415.1':
optional: true
'@cloudflare/workerd-windows-64@1.20260415.1':
optional: true
'@cloudflare/workers-types@4.20260420.1': {}
'@cspotcode/source-map-support@0.8.1':
dependencies:
'@jridgewell/trace-mapping': 0.3.9
'@emnapi/runtime@1.10.0':
dependencies:
tslib: 2.8.1
optional: true
'@esbuild/aix-ppc64@0.27.3':
optional: true
'@esbuild/android-arm64@0.27.3':
optional: true
'@esbuild/android-arm@0.27.3':
optional: true
'@esbuild/android-x64@0.27.3':
optional: true
'@esbuild/darwin-arm64@0.27.3':
optional: true
'@esbuild/darwin-x64@0.27.3':
optional: true
'@esbuild/freebsd-arm64@0.27.3':
optional: true
'@esbuild/freebsd-x64@0.27.3':
optional: true
'@esbuild/linux-arm64@0.27.3':
optional: true
'@esbuild/linux-arm@0.27.3':
optional: true
'@esbuild/linux-ia32@0.27.3':
optional: true
'@esbuild/linux-loong64@0.27.3':
optional: true
'@esbuild/linux-mips64el@0.27.3':
optional: true
'@esbuild/linux-ppc64@0.27.3':
optional: true
'@esbuild/linux-riscv64@0.27.3':
optional: true
'@esbuild/linux-s390x@0.27.3':
optional: true
'@esbuild/linux-x64@0.27.3':
optional: true
'@esbuild/netbsd-arm64@0.27.3':
optional: true
'@esbuild/netbsd-x64@0.27.3':
optional: true
'@esbuild/openbsd-arm64@0.27.3':
optional: true
'@esbuild/openbsd-x64@0.27.3':
optional: true
'@esbuild/openharmony-arm64@0.27.3':
optional: true
'@esbuild/sunos-x64@0.27.3':
optional: true
'@esbuild/win32-arm64@0.27.3':
optional: true
'@esbuild/win32-ia32@0.27.3':
optional: true
'@esbuild/win32-x64@0.27.3':
optional: true
'@img/colour@1.1.0': {}
'@img/sharp-darwin-arm64@0.34.5':
optionalDependencies:
'@img/sharp-libvips-darwin-arm64': 1.2.4
optional: true
'@img/sharp-darwin-x64@0.34.5':
optionalDependencies:
'@img/sharp-libvips-darwin-x64': 1.2.4
optional: true
'@img/sharp-libvips-darwin-arm64@1.2.4':
optional: true
'@img/sharp-libvips-darwin-x64@1.2.4':
optional: true
'@img/sharp-libvips-linux-arm64@1.2.4':
optional: true
'@img/sharp-libvips-linux-arm@1.2.4':
optional: true
'@img/sharp-libvips-linux-ppc64@1.2.4':
optional: true
'@img/sharp-libvips-linux-riscv64@1.2.4':
optional: true
'@img/sharp-libvips-linux-s390x@1.2.4':
optional: true
'@img/sharp-libvips-linux-x64@1.2.4':
optional: true
'@img/sharp-libvips-linuxmusl-arm64@1.2.4':
optional: true
'@img/sharp-libvips-linuxmusl-x64@1.2.4':
optional: true
'@img/sharp-linux-arm64@0.34.5':
optionalDependencies:
'@img/sharp-libvips-linux-arm64': 1.2.4
optional: true
'@img/sharp-linux-arm@0.34.5':
optionalDependencies:
'@img/sharp-libvips-linux-arm': 1.2.4
optional: true
'@img/sharp-linux-ppc64@0.34.5':
optionalDependencies:
'@img/sharp-libvips-linux-ppc64': 1.2.4
optional: true
'@img/sharp-linux-riscv64@0.34.5':
optionalDependencies:
'@img/sharp-libvips-linux-riscv64': 1.2.4
optional: true
'@img/sharp-linux-s390x@0.34.5':
optionalDependencies:
'@img/sharp-libvips-linux-s390x': 1.2.4
optional: true
'@img/sharp-linux-x64@0.34.5':
optionalDependencies:
'@img/sharp-libvips-linux-x64': 1.2.4
optional: true
'@img/sharp-linuxmusl-arm64@0.34.5':
optionalDependencies:
'@img/sharp-libvips-linuxmusl-arm64': 1.2.4
optional: true
'@img/sharp-linuxmusl-x64@0.34.5':
optionalDependencies:
'@img/sharp-libvips-linuxmusl-x64': 1.2.4
optional: true
'@img/sharp-wasm32@0.34.5':
dependencies:
'@emnapi/runtime': 1.10.0
optional: true
'@img/sharp-win32-arm64@0.34.5':
optional: true
'@img/sharp-win32-ia32@0.34.5':
optional: true
'@img/sharp-win32-x64@0.34.5':
optional: true
'@jridgewell/resolve-uri@3.1.2': {}
'@jridgewell/sourcemap-codec@1.5.5': {}
'@jridgewell/trace-mapping@0.3.9':
dependencies:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.5
'@poppinss/colors@4.1.6':
dependencies:
kleur: 4.1.5
'@poppinss/dumper@0.6.5':
dependencies:
'@poppinss/colors': 4.1.6
'@sindresorhus/is': 7.2.0
supports-color: 10.2.2
'@poppinss/exception@1.2.3': {}
'@sindresorhus/is@7.2.0': {}
'@speed-highlight/core@1.2.15': {}
blake3-wasm@2.1.5: {}
cookie@1.1.1: {}
detect-libc@2.1.2: {}
error-stack-parser-es@1.0.5: {}
esbuild@0.27.3:
optionalDependencies:
'@esbuild/aix-ppc64': 0.27.3
'@esbuild/android-arm': 0.27.3
'@esbuild/android-arm64': 0.27.3
'@esbuild/android-x64': 0.27.3
'@esbuild/darwin-arm64': 0.27.3
'@esbuild/darwin-x64': 0.27.3
'@esbuild/freebsd-arm64': 0.27.3
'@esbuild/freebsd-x64': 0.27.3
'@esbuild/linux-arm': 0.27.3
'@esbuild/linux-arm64': 0.27.3
'@esbuild/linux-ia32': 0.27.3
'@esbuild/linux-loong64': 0.27.3
'@esbuild/linux-mips64el': 0.27.3
'@esbuild/linux-ppc64': 0.27.3
'@esbuild/linux-riscv64': 0.27.3
'@esbuild/linux-s390x': 0.27.3
'@esbuild/linux-x64': 0.27.3
'@esbuild/netbsd-arm64': 0.27.3
'@esbuild/netbsd-x64': 0.27.3
'@esbuild/openbsd-arm64': 0.27.3
'@esbuild/openbsd-x64': 0.27.3
'@esbuild/openharmony-arm64': 0.27.3
'@esbuild/sunos-x64': 0.27.3
'@esbuild/win32-arm64': 0.27.3
'@esbuild/win32-ia32': 0.27.3
'@esbuild/win32-x64': 0.27.3
fsevents@2.3.3:
optional: true
hono@4.12.14: {}
kleur@4.1.5: {}
miniflare@4.20260415.0:
dependencies:
'@cspotcode/source-map-support': 0.8.1
sharp: 0.34.5
undici: 7.24.8
workerd: 1.20260415.1
ws: 8.18.0
youch: 4.1.0-beta.10
transitivePeerDependencies:
- bufferutil
- utf-8-validate
path-to-regexp@6.3.0: {}
pathe@2.0.3: {}
semver@7.7.4: {}
sharp@0.34.5:
dependencies:
'@img/colour': 1.1.0
detect-libc: 2.1.2
semver: 7.7.4
optionalDependencies:
'@img/sharp-darwin-arm64': 0.34.5
'@img/sharp-darwin-x64': 0.34.5
'@img/sharp-libvips-darwin-arm64': 1.2.4
'@img/sharp-libvips-darwin-x64': 1.2.4
'@img/sharp-libvips-linux-arm': 1.2.4
'@img/sharp-libvips-linux-arm64': 1.2.4
'@img/sharp-libvips-linux-ppc64': 1.2.4
'@img/sharp-libvips-linux-riscv64': 1.2.4
'@img/sharp-libvips-linux-s390x': 1.2.4
'@img/sharp-libvips-linux-x64': 1.2.4
'@img/sharp-libvips-linuxmusl-arm64': 1.2.4
'@img/sharp-libvips-linuxmusl-x64': 1.2.4
'@img/sharp-linux-arm': 0.34.5
'@img/sharp-linux-arm64': 0.34.5
'@img/sharp-linux-ppc64': 0.34.5
'@img/sharp-linux-riscv64': 0.34.5
'@img/sharp-linux-s390x': 0.34.5
'@img/sharp-linux-x64': 0.34.5
'@img/sharp-linuxmusl-arm64': 0.34.5
'@img/sharp-linuxmusl-x64': 0.34.5
'@img/sharp-wasm32': 0.34.5
'@img/sharp-win32-arm64': 0.34.5
'@img/sharp-win32-ia32': 0.34.5
'@img/sharp-win32-x64': 0.34.5
supports-color@10.2.2: {}
tslib@2.8.1:
optional: true
typescript@5.9.3: {}
undici@7.24.8: {}
unenv@2.0.0-rc.24:
dependencies:
pathe: 2.0.3
workerd@1.20260415.1:
optionalDependencies:
'@cloudflare/workerd-darwin-64': 1.20260415.1
'@cloudflare/workerd-darwin-arm64': 1.20260415.1
'@cloudflare/workerd-linux-64': 1.20260415.1
'@cloudflare/workerd-linux-arm64': 1.20260415.1
'@cloudflare/workerd-windows-64': 1.20260415.1
wrangler@4.83.0(@cloudflare/workers-types@4.20260420.1):
dependencies:
'@cloudflare/kv-asset-handler': 0.4.2
'@cloudflare/unenv-preset': 2.16.0(unenv@2.0.0-rc.24)(workerd@1.20260415.1)
blake3-wasm: 2.1.5
esbuild: 0.27.3
miniflare: 4.20260415.0
path-to-regexp: 6.3.0
unenv: 2.0.0-rc.24
workerd: 1.20260415.1
optionalDependencies:
'@cloudflare/workers-types': 4.20260420.1
fsevents: 2.3.3
transitivePeerDependencies:
- bufferutil
- utf-8-validate
ws@8.18.0: {}
youch-core@0.3.3:
dependencies:
'@poppinss/exception': 1.2.3
error-stack-parser-es: 1.0.5
youch@4.1.0-beta.10:
dependencies:
'@poppinss/colors': 4.1.6
'@poppinss/dumper': 0.6.5
'@speed-highlight/core': 1.2.15
cookie: 1.1.1
youch-core: 0.3.3
@@ -1,8 +0,0 @@
allowBuilds:
esbuild: true
sharp: true
workerd: true
onlyBuiltDependencies:
- esbuild
- sharp
- workerd
-83
View File
@@ -1,83 +0,0 @@
/**
* arcrun API component Worker (km_writer)
*
* POST / → JSON input → WASM (WASI preview1 stdin/stdout) → JSON output
*
* 提供 http_request host function,讓 WASM 零件呼叫 Mira /km/* API。
*/
import componentWasm from '../component.wasm' assert { type: 'webassembly' };
import { Hono } from 'hono';
import { cors } from 'hono/cors';
import { createWasiShim, type WasiHostFunctions } from '../../../cypher-executor/src/lib/wasi-shim';
const app = new Hono();
app.use('*', cors());
app.get('/', (c) => c.json({ ok: true, component: 'km_writer' }));
app.post('/', async (c) => {
let input: unknown;
try {
input = await c.req.json();
} catch {
return c.json({ success: false, error: 'request body must be JSON' }, 400);
}
try {
const result = await runWasm(input);
return c.json(result);
} catch (e) {
return c.json(
{ success: false, error: e instanceof Error ? e.message : String(e) },
500,
);
}
});
export default app;
async function runWasm(input: unknown): Promise<unknown> {
const hostFunctions: WasiHostFunctions = {
http_request: async (url, method, headersJson, body) => {
const headers: Record<string, string> = {};
if (headersJson) {
try {
const parsed = JSON.parse(headersJson);
if (parsed && typeof parsed === 'object') {
for (const [k, v] of Object.entries(parsed as Record<string, unknown>)) {
if (typeof v === 'string') headers[k] = v;
}
}
} catch {
// ignore header parse errors
}
}
const init: RequestInit = { method, headers };
if (body && method.toUpperCase() !== 'GET' && method.toUpperCase() !== 'HEAD') {
init.body = body;
}
const res = await fetch(url, init);
const text = await res.text();
// 修架構債(同 http_request):非 2xx 包成帶 "error" key 的 envelope
// 讓 WASM 端既有的 error 判定正確識別失敗(原本只回 body 丟掉 status → 4xx 被判 success)。
if (!res.ok) {
return JSON.stringify({ error: `HTTP ${res.status}`, status: res.status, body: text });
}
return text;
},
};
const shim = createWasiShim(JSON.stringify(input), hostFunctions);
const instance = await WebAssembly.instantiate(
componentWasm as WebAssembly.Module,
shim.imports,
);
shim.setMemory(instance.exports.memory as WebAssembly.Memory);
await shim.run(instance);
const stdout = shim.getStdout().trim();
if (!stdout) throw new Error('WASM component produced no output');
return JSON.parse(stdout);
}
-11
View File
@@ -1,11 +0,0 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "ES2022",
"moduleResolution": "bundler",
"lib": ["ES2022"],
"types": ["@cloudflare/workers-types"],
"strict": true,
"noEmit": true
}
}
-12
View File
@@ -1,12 +0,0 @@
name = "arcrun-km-writer"
main = "src/index.ts"
compatibility_date = "2025-02-19"
compatibility_flags = ["nodejs_compat"]
workers_dev = true
[vars]
COMPONENT_ID = "km_writer"
[[routes]]
pattern = "km-writer.arcrun.dev/*"
zone_name = "arcrun.dev"
Binary file not shown.
@@ -1,14 +0,0 @@
{
"name": "arcrun-platform-crypto",
"version": "1.0.0",
"private": true,
"type": "module",
"dependencies": {
"hono": "^4.7.0"
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20250408.0",
"typescript": "^5.4.0",
"wrangler": "^4.0.0"
}
}
-898
View File
@@ -1,898 +0,0 @@
lockfileVersion: '9.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
importers:
.:
dependencies:
hono:
specifier: ^4.7.0
version: 4.12.17
devDependencies:
'@cloudflare/workers-types':
specifier: ^4.20250408.0
version: 4.20260505.1
typescript:
specifier: ^5.4.0
version: 5.9.3
wrangler:
specifier: ^4.0.0
version: 4.87.0(@cloudflare/workers-types@4.20260505.1)
packages:
'@cloudflare/kv-asset-handler@0.5.0':
resolution: {integrity: sha512-jxQYkj8dSIzc0cD6cMMNdOc1UVjqSqu8BZdor5s8cGjW2I8BjODt/kWPVdY+u9zj3ms75Q5qaZgnxUad83+eAg==}
engines: {node: '>=22.0.0'}
'@cloudflare/unenv-preset@2.16.1':
resolution: {integrity: sha512-ECxObrMfyTl5bhQf/lZCXwo5G6xX9IAUo+nDMKK4SZ8m4Jvvxp52vilxyySSWh2YTZz8+HQ07qGH/2rEom1vDw==}
peerDependencies:
unenv: 2.0.0-rc.24
workerd: '>1.20260305.0 <2.0.0-0'
peerDependenciesMeta:
workerd:
optional: true
'@cloudflare/workerd-darwin-64@1.20260430.1':
resolution: {integrity: sha512-ADohZUHf7NBvPp2PdZig2Opxx+hDkk3ve7jrTne3JRx9kDSB73zc4LzcEeEN8LKkbAcqZmvfRJfpChSlusu0lA==}
engines: {node: '>=16'}
cpu: [x64]
os: [darwin]
'@cloudflare/workerd-darwin-arm64@1.20260430.1':
resolution: {integrity: sha512-/DoYC/1wHs+YRZzzqSQg1/EHB4hiv1yV5U8FnmapRRIzVaPtnt+ApeOXeMrIdKidgKOI8TqQzgBU8xbIM7Cl4Q==}
engines: {node: '>=16'}
cpu: [arm64]
os: [darwin]
'@cloudflare/workerd-linux-64@1.20260430.1':
resolution: {integrity: sha512-koJhBWvEVZPKCVFtMLp2iMHlYr+lFCF47wGbnlKdHVlemV0zTxJEyHI8aLlrhPLhBmOmYLp46rXw09/qJkRIhQ==}
engines: {node: '>=16'}
cpu: [x64]
os: [linux]
'@cloudflare/workerd-linux-arm64@1.20260430.1':
resolution: {integrity: sha512-hMdapNAzNQZDXGGkg4Slydc3fRJP5FUZLJVVcZCW/+imhhJro9Z1rv5n/wfR+txKoSWhTYR8eOp8Pyi2bzLzlw==}
engines: {node: '>=16'}
cpu: [arm64]
os: [linux]
'@cloudflare/workerd-windows-64@1.20260430.1':
resolution: {integrity: sha512-jS3ffixjb5USOwz4frw4WzCz0HrjVxkgyU3WiYb06N7hBAfN6eOrveAJ4QRef0+suK4V1vQFoB1oKdRBsXe9Dw==}
engines: {node: '>=16'}
cpu: [x64]
os: [win32]
'@cloudflare/workers-types@4.20260505.1':
resolution: {integrity: sha512-Uz9D2hcwB4/pdnmCU7RsgknY8TQ5st0cQMMN6h/hvWt1TCt99GUkbi6dMgWdP7jXfIfh+S/EI5zQugI9RZn4Bw==}
'@cspotcode/source-map-support@0.8.1':
resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
engines: {node: '>=12'}
'@emnapi/runtime@1.10.0':
resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==}
'@esbuild/aix-ppc64@0.27.3':
resolution: {integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [aix]
'@esbuild/android-arm64@0.27.3':
resolution: {integrity: sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [android]
'@esbuild/android-arm@0.27.3':
resolution: {integrity: sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==}
engines: {node: '>=18'}
cpu: [arm]
os: [android]
'@esbuild/android-x64@0.27.3':
resolution: {integrity: sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==}
engines: {node: '>=18'}
cpu: [x64]
os: [android]
'@esbuild/darwin-arm64@0.27.3':
resolution: {integrity: sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [darwin]
'@esbuild/darwin-x64@0.27.3':
resolution: {integrity: sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==}
engines: {node: '>=18'}
cpu: [x64]
os: [darwin]
'@esbuild/freebsd-arm64@0.27.3':
resolution: {integrity: sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==}
engines: {node: '>=18'}
cpu: [arm64]
os: [freebsd]
'@esbuild/freebsd-x64@0.27.3':
resolution: {integrity: sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==}
engines: {node: '>=18'}
cpu: [x64]
os: [freebsd]
'@esbuild/linux-arm64@0.27.3':
resolution: {integrity: sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [linux]
'@esbuild/linux-arm@0.27.3':
resolution: {integrity: sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==}
engines: {node: '>=18'}
cpu: [arm]
os: [linux]
'@esbuild/linux-ia32@0.27.3':
resolution: {integrity: sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==}
engines: {node: '>=18'}
cpu: [ia32]
os: [linux]
'@esbuild/linux-loong64@0.27.3':
resolution: {integrity: sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==}
engines: {node: '>=18'}
cpu: [loong64]
os: [linux]
'@esbuild/linux-mips64el@0.27.3':
resolution: {integrity: sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==}
engines: {node: '>=18'}
cpu: [mips64el]
os: [linux]
'@esbuild/linux-ppc64@0.27.3':
resolution: {integrity: sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [linux]
'@esbuild/linux-riscv64@0.27.3':
resolution: {integrity: sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==}
engines: {node: '>=18'}
cpu: [riscv64]
os: [linux]
'@esbuild/linux-s390x@0.27.3':
resolution: {integrity: sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==}
engines: {node: '>=18'}
cpu: [s390x]
os: [linux]
'@esbuild/linux-x64@0.27.3':
resolution: {integrity: sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==}
engines: {node: '>=18'}
cpu: [x64]
os: [linux]
'@esbuild/netbsd-arm64@0.27.3':
resolution: {integrity: sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==}
engines: {node: '>=18'}
cpu: [arm64]
os: [netbsd]
'@esbuild/netbsd-x64@0.27.3':
resolution: {integrity: sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==}
engines: {node: '>=18'}
cpu: [x64]
os: [netbsd]
'@esbuild/openbsd-arm64@0.27.3':
resolution: {integrity: sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==}
engines: {node: '>=18'}
cpu: [arm64]
os: [openbsd]
'@esbuild/openbsd-x64@0.27.3':
resolution: {integrity: sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==}
engines: {node: '>=18'}
cpu: [x64]
os: [openbsd]
'@esbuild/openharmony-arm64@0.27.3':
resolution: {integrity: sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==}
engines: {node: '>=18'}
cpu: [arm64]
os: [openharmony]
'@esbuild/sunos-x64@0.27.3':
resolution: {integrity: sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==}
engines: {node: '>=18'}
cpu: [x64]
os: [sunos]
'@esbuild/win32-arm64@0.27.3':
resolution: {integrity: sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==}
engines: {node: '>=18'}
cpu: [arm64]
os: [win32]
'@esbuild/win32-ia32@0.27.3':
resolution: {integrity: sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==}
engines: {node: '>=18'}
cpu: [ia32]
os: [win32]
'@esbuild/win32-x64@0.27.3':
resolution: {integrity: sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==}
engines: {node: '>=18'}
cpu: [x64]
os: [win32]
'@img/colour@1.1.0':
resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==}
engines: {node: '>=18'}
'@img/sharp-darwin-arm64@0.34.5':
resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm64]
os: [darwin]
'@img/sharp-darwin-x64@0.34.5':
resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64]
os: [darwin]
'@img/sharp-libvips-darwin-arm64@1.2.4':
resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==}
cpu: [arm64]
os: [darwin]
'@img/sharp-libvips-darwin-x64@1.2.4':
resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==}
cpu: [x64]
os: [darwin]
'@img/sharp-libvips-linux-arm64@1.2.4':
resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-arm@1.2.4':
resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==}
cpu: [arm]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-ppc64@1.2.4':
resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-riscv64@1.2.4':
resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-s390x@1.2.4':
resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-x64@1.2.4':
resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==}
cpu: [x64]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linuxmusl-arm64@1.2.4':
resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==}
cpu: [arm64]
os: [linux]
libc: [musl]
'@img/sharp-libvips-linuxmusl-x64@1.2.4':
resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==}
cpu: [x64]
os: [linux]
libc: [musl]
'@img/sharp-linux-arm64@0.34.5':
resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@img/sharp-linux-arm@0.34.5':
resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm]
os: [linux]
libc: [glibc]
'@img/sharp-linux-ppc64@0.34.5':
resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@img/sharp-linux-riscv64@0.34.5':
resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@img/sharp-linux-s390x@0.34.5':
resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@img/sharp-linux-x64@0.34.5':
resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64]
os: [linux]
libc: [glibc]
'@img/sharp-linuxmusl-arm64@0.34.5':
resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm64]
os: [linux]
libc: [musl]
'@img/sharp-linuxmusl-x64@0.34.5':
resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64]
os: [linux]
libc: [musl]
'@img/sharp-wasm32@0.34.5':
resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [wasm32]
'@img/sharp-win32-arm64@0.34.5':
resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm64]
os: [win32]
'@img/sharp-win32-ia32@0.34.5':
resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [ia32]
os: [win32]
'@img/sharp-win32-x64@0.34.5':
resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64]
os: [win32]
'@jridgewell/resolve-uri@3.1.2':
resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
engines: {node: '>=6.0.0'}
'@jridgewell/sourcemap-codec@1.5.5':
resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
'@jridgewell/trace-mapping@0.3.9':
resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==}
'@poppinss/colors@4.1.6':
resolution: {integrity: sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg==}
'@poppinss/dumper@0.6.5':
resolution: {integrity: sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw==}
'@poppinss/exception@1.2.3':
resolution: {integrity: sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==}
'@sindresorhus/is@7.2.0':
resolution: {integrity: sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==}
engines: {node: '>=18'}
'@speed-highlight/core@1.2.15':
resolution: {integrity: sha512-BMq1K3DsElxDWawkX6eLg9+CKJrTVGCBAWVuHXVUV2u0s2711qiChLSId6ikYPfxhdYocLNt3wWwSvDiTvFabw==}
blake3-wasm@2.1.5:
resolution: {integrity: sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==}
cookie@1.1.1:
resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==}
engines: {node: '>=18'}
detect-libc@2.1.2:
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
engines: {node: '>=8'}
error-stack-parser-es@1.0.5:
resolution: {integrity: sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==}
esbuild@0.27.3:
resolution: {integrity: sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==}
engines: {node: '>=18'}
hasBin: true
fsevents@2.3.3:
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin]
hono@4.12.17:
resolution: {integrity: sha512-FbJJNb/XgX7YW0hX/V8w5oYLztKEsRLykCMZWt1WdLtsfjzMvmoqWBA4H4t5norinq8/rh20oiZYr+WSl4UzAQ==}
engines: {node: '>=16.9.0'}
kleur@4.1.5:
resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==}
engines: {node: '>=6'}
miniflare@4.20260430.0:
resolution: {integrity: sha512-MWvMm3Siho9Yj7lbJZidLs8hbrRvIcOrif2mnsHQZdvoKfedpea+GaN8XJxbpRcq0B2WzNI1BB1ihdnqes3/ZA==}
engines: {node: '>=22.0.0'}
hasBin: true
path-to-regexp@6.3.0:
resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==}
pathe@2.0.3:
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
semver@7.7.4:
resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==}
engines: {node: '>=10'}
hasBin: true
sharp@0.34.5:
resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
supports-color@10.2.2:
resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==}
engines: {node: '>=18'}
tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
typescript@5.9.3:
resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
engines: {node: '>=14.17'}
hasBin: true
undici@7.24.8:
resolution: {integrity: sha512-6KQ/+QxK49Z/p3HO6E5ZCZWNnCasyZLa5ExaVYyvPxUwKtbCPMKELJOqh7EqOle0t9cH/7d2TaaTRRa6Nhs4YQ==}
engines: {node: '>=20.18.1'}
unenv@2.0.0-rc.24:
resolution: {integrity: sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==}
workerd@1.20260430.1:
resolution: {integrity: sha512-KEgIWyiw3Jmn+DCd/L3ePo5fmiiYb/UcwKvDWPf/nLLOiwShDFzDSsegU5NY/JcwgvO/QsLHVi2FYrbkcXNY5Q==}
engines: {node: '>=16'}
hasBin: true
wrangler@4.87.0:
resolution: {integrity: sha512-lfhfKwLfQlowwgV0xhlYgE9fU3n0I30d4ccGY/rTCEm/n42Mjvlr0Ng3ZPNqlsrsKBcDR531V7dsPkgELvrk/Q==}
engines: {node: '>=22.0.0'}
hasBin: true
peerDependencies:
'@cloudflare/workers-types': ^4.20260430.1
peerDependenciesMeta:
'@cloudflare/workers-types':
optional: true
ws@8.18.0:
resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==}
engines: {node: '>=10.0.0'}
peerDependencies:
bufferutil: ^4.0.1
utf-8-validate: '>=5.0.2'
peerDependenciesMeta:
bufferutil:
optional: true
utf-8-validate:
optional: true
youch-core@0.3.3:
resolution: {integrity: sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==}
youch@4.1.0-beta.10:
resolution: {integrity: sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==}
snapshots:
'@cloudflare/kv-asset-handler@0.5.0': {}
'@cloudflare/unenv-preset@2.16.1(unenv@2.0.0-rc.24)(workerd@1.20260430.1)':
dependencies:
unenv: 2.0.0-rc.24
optionalDependencies:
workerd: 1.20260430.1
'@cloudflare/workerd-darwin-64@1.20260430.1':
optional: true
'@cloudflare/workerd-darwin-arm64@1.20260430.1':
optional: true
'@cloudflare/workerd-linux-64@1.20260430.1':
optional: true
'@cloudflare/workerd-linux-arm64@1.20260430.1':
optional: true
'@cloudflare/workerd-windows-64@1.20260430.1':
optional: true
'@cloudflare/workers-types@4.20260505.1': {}
'@cspotcode/source-map-support@0.8.1':
dependencies:
'@jridgewell/trace-mapping': 0.3.9
'@emnapi/runtime@1.10.0':
dependencies:
tslib: 2.8.1
optional: true
'@esbuild/aix-ppc64@0.27.3':
optional: true
'@esbuild/android-arm64@0.27.3':
optional: true
'@esbuild/android-arm@0.27.3':
optional: true
'@esbuild/android-x64@0.27.3':
optional: true
'@esbuild/darwin-arm64@0.27.3':
optional: true
'@esbuild/darwin-x64@0.27.3':
optional: true
'@esbuild/freebsd-arm64@0.27.3':
optional: true
'@esbuild/freebsd-x64@0.27.3':
optional: true
'@esbuild/linux-arm64@0.27.3':
optional: true
'@esbuild/linux-arm@0.27.3':
optional: true
'@esbuild/linux-ia32@0.27.3':
optional: true
'@esbuild/linux-loong64@0.27.3':
optional: true
'@esbuild/linux-mips64el@0.27.3':
optional: true
'@esbuild/linux-ppc64@0.27.3':
optional: true
'@esbuild/linux-riscv64@0.27.3':
optional: true
'@esbuild/linux-s390x@0.27.3':
optional: true
'@esbuild/linux-x64@0.27.3':
optional: true
'@esbuild/netbsd-arm64@0.27.3':
optional: true
'@esbuild/netbsd-x64@0.27.3':
optional: true
'@esbuild/openbsd-arm64@0.27.3':
optional: true
'@esbuild/openbsd-x64@0.27.3':
optional: true
'@esbuild/openharmony-arm64@0.27.3':
optional: true
'@esbuild/sunos-x64@0.27.3':
optional: true
'@esbuild/win32-arm64@0.27.3':
optional: true
'@esbuild/win32-ia32@0.27.3':
optional: true
'@esbuild/win32-x64@0.27.3':
optional: true
'@img/colour@1.1.0': {}
'@img/sharp-darwin-arm64@0.34.5':
optionalDependencies:
'@img/sharp-libvips-darwin-arm64': 1.2.4
optional: true
'@img/sharp-darwin-x64@0.34.5':
optionalDependencies:
'@img/sharp-libvips-darwin-x64': 1.2.4
optional: true
'@img/sharp-libvips-darwin-arm64@1.2.4':
optional: true
'@img/sharp-libvips-darwin-x64@1.2.4':
optional: true
'@img/sharp-libvips-linux-arm64@1.2.4':
optional: true
'@img/sharp-libvips-linux-arm@1.2.4':
optional: true
'@img/sharp-libvips-linux-ppc64@1.2.4':
optional: true
'@img/sharp-libvips-linux-riscv64@1.2.4':
optional: true
'@img/sharp-libvips-linux-s390x@1.2.4':
optional: true
'@img/sharp-libvips-linux-x64@1.2.4':
optional: true
'@img/sharp-libvips-linuxmusl-arm64@1.2.4':
optional: true
'@img/sharp-libvips-linuxmusl-x64@1.2.4':
optional: true
'@img/sharp-linux-arm64@0.34.5':
optionalDependencies:
'@img/sharp-libvips-linux-arm64': 1.2.4
optional: true
'@img/sharp-linux-arm@0.34.5':
optionalDependencies:
'@img/sharp-libvips-linux-arm': 1.2.4
optional: true
'@img/sharp-linux-ppc64@0.34.5':
optionalDependencies:
'@img/sharp-libvips-linux-ppc64': 1.2.4
optional: true
'@img/sharp-linux-riscv64@0.34.5':
optionalDependencies:
'@img/sharp-libvips-linux-riscv64': 1.2.4
optional: true
'@img/sharp-linux-s390x@0.34.5':
optionalDependencies:
'@img/sharp-libvips-linux-s390x': 1.2.4
optional: true
'@img/sharp-linux-x64@0.34.5':
optionalDependencies:
'@img/sharp-libvips-linux-x64': 1.2.4
optional: true
'@img/sharp-linuxmusl-arm64@0.34.5':
optionalDependencies:
'@img/sharp-libvips-linuxmusl-arm64': 1.2.4
optional: true
'@img/sharp-linuxmusl-x64@0.34.5':
optionalDependencies:
'@img/sharp-libvips-linuxmusl-x64': 1.2.4
optional: true
'@img/sharp-wasm32@0.34.5':
dependencies:
'@emnapi/runtime': 1.10.0
optional: true
'@img/sharp-win32-arm64@0.34.5':
optional: true
'@img/sharp-win32-ia32@0.34.5':
optional: true
'@img/sharp-win32-x64@0.34.5':
optional: true
'@jridgewell/resolve-uri@3.1.2': {}
'@jridgewell/sourcemap-codec@1.5.5': {}
'@jridgewell/trace-mapping@0.3.9':
dependencies:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.5
'@poppinss/colors@4.1.6':
dependencies:
kleur: 4.1.5
'@poppinss/dumper@0.6.5':
dependencies:
'@poppinss/colors': 4.1.6
'@sindresorhus/is': 7.2.0
supports-color: 10.2.2
'@poppinss/exception@1.2.3': {}
'@sindresorhus/is@7.2.0': {}
'@speed-highlight/core@1.2.15': {}
blake3-wasm@2.1.5: {}
cookie@1.1.1: {}
detect-libc@2.1.2: {}
error-stack-parser-es@1.0.5: {}
esbuild@0.27.3:
optionalDependencies:
'@esbuild/aix-ppc64': 0.27.3
'@esbuild/android-arm': 0.27.3
'@esbuild/android-arm64': 0.27.3
'@esbuild/android-x64': 0.27.3
'@esbuild/darwin-arm64': 0.27.3
'@esbuild/darwin-x64': 0.27.3
'@esbuild/freebsd-arm64': 0.27.3
'@esbuild/freebsd-x64': 0.27.3
'@esbuild/linux-arm': 0.27.3
'@esbuild/linux-arm64': 0.27.3
'@esbuild/linux-ia32': 0.27.3
'@esbuild/linux-loong64': 0.27.3
'@esbuild/linux-mips64el': 0.27.3
'@esbuild/linux-ppc64': 0.27.3
'@esbuild/linux-riscv64': 0.27.3
'@esbuild/linux-s390x': 0.27.3
'@esbuild/linux-x64': 0.27.3
'@esbuild/netbsd-arm64': 0.27.3
'@esbuild/netbsd-x64': 0.27.3
'@esbuild/openbsd-arm64': 0.27.3
'@esbuild/openbsd-x64': 0.27.3
'@esbuild/openharmony-arm64': 0.27.3
'@esbuild/sunos-x64': 0.27.3
'@esbuild/win32-arm64': 0.27.3
'@esbuild/win32-ia32': 0.27.3
'@esbuild/win32-x64': 0.27.3
fsevents@2.3.3:
optional: true
hono@4.12.17: {}
kleur@4.1.5: {}
miniflare@4.20260430.0:
dependencies:
'@cspotcode/source-map-support': 0.8.1
sharp: 0.34.5
undici: 7.24.8
workerd: 1.20260430.1
ws: 8.18.0
youch: 4.1.0-beta.10
transitivePeerDependencies:
- bufferutil
- utf-8-validate
path-to-regexp@6.3.0: {}
pathe@2.0.3: {}
semver@7.7.4: {}
sharp@0.34.5:
dependencies:
'@img/colour': 1.1.0
detect-libc: 2.1.2
semver: 7.7.4
optionalDependencies:
'@img/sharp-darwin-arm64': 0.34.5
'@img/sharp-darwin-x64': 0.34.5
'@img/sharp-libvips-darwin-arm64': 1.2.4
'@img/sharp-libvips-darwin-x64': 1.2.4
'@img/sharp-libvips-linux-arm': 1.2.4
'@img/sharp-libvips-linux-arm64': 1.2.4
'@img/sharp-libvips-linux-ppc64': 1.2.4
'@img/sharp-libvips-linux-riscv64': 1.2.4
'@img/sharp-libvips-linux-s390x': 1.2.4
'@img/sharp-libvips-linux-x64': 1.2.4
'@img/sharp-libvips-linuxmusl-arm64': 1.2.4
'@img/sharp-libvips-linuxmusl-x64': 1.2.4
'@img/sharp-linux-arm': 0.34.5
'@img/sharp-linux-arm64': 0.34.5
'@img/sharp-linux-ppc64': 0.34.5
'@img/sharp-linux-riscv64': 0.34.5
'@img/sharp-linux-s390x': 0.34.5
'@img/sharp-linux-x64': 0.34.5
'@img/sharp-linuxmusl-arm64': 0.34.5
'@img/sharp-linuxmusl-x64': 0.34.5
'@img/sharp-wasm32': 0.34.5
'@img/sharp-win32-arm64': 0.34.5
'@img/sharp-win32-ia32': 0.34.5
'@img/sharp-win32-x64': 0.34.5
supports-color@10.2.2: {}
tslib@2.8.1:
optional: true
typescript@5.9.3: {}
undici@7.24.8: {}
unenv@2.0.0-rc.24:
dependencies:
pathe: 2.0.3
workerd@1.20260430.1:
optionalDependencies:
'@cloudflare/workerd-darwin-64': 1.20260430.1
'@cloudflare/workerd-darwin-arm64': 1.20260430.1
'@cloudflare/workerd-linux-64': 1.20260430.1
'@cloudflare/workerd-linux-arm64': 1.20260430.1
'@cloudflare/workerd-windows-64': 1.20260430.1
wrangler@4.87.0(@cloudflare/workers-types@4.20260505.1):
dependencies:
'@cloudflare/kv-asset-handler': 0.5.0
'@cloudflare/unenv-preset': 2.16.1(unenv@2.0.0-rc.24)(workerd@1.20260430.1)
blake3-wasm: 2.1.5
esbuild: 0.27.3
miniflare: 4.20260430.0
path-to-regexp: 6.3.0
unenv: 2.0.0-rc.24
workerd: 1.20260430.1
optionalDependencies:
'@cloudflare/workers-types': 4.20260505.1
fsevents: 2.3.3
transitivePeerDependencies:
- bufferutil
- utf-8-validate
ws@8.18.0: {}
youch-core@0.3.3:
dependencies:
'@poppinss/exception': 1.2.3
error-stack-parser-es: 1.0.5
youch@4.1.0-beta.10:
dependencies:
'@poppinss/colors': 4.1.6
'@poppinss/dumper': 0.6.5
'@speed-highlight/core': 1.2.15
cookie: 1.1.1
youch-core: 0.3.3
@@ -1,8 +0,0 @@
allowBuilds:
esbuild: true
sharp: true
workerd: true
onlyBuiltDependencies:
- esbuild
- sharp
- workerd
@@ -1,108 +0,0 @@
/**
* arcrun platform_crypto Worker
*
* POST / → JSON input {action, ...} → JSON output
*
* Actions:
* generate_api_key — HMAC-SHA256(email, ENCRYPTION_KEY) → ak_{hex[:32]}
* encrypt — AES-GCM(plaintext, ENCRYPTION_KEY) → {encrypted, iv}base64
* random_token — crypto random bytes → hex string
*
* 安全邊界:ENCRYPTION_KEY 只存在於 closure,永不進入外部(rule 02 §2.2)。
* 此 Worker 直接用 crypto.subtle 實作,不走 WASM runner。
* TinyGo WASM async host function 在 Cloudflare Workers 的 u6u namespace 不支援 Promise.
* WASM 零件 (registry/components/platform_crypto/) 保留作為 edge-Go 移植時的參考。
*/
import { Hono } from 'hono';
import { cors } from 'hono/cors';
type Env = {
ENCRYPTION_KEY: string;
};
type Input = {
action: string;
email?: string;
plaintext?: string;
bytes?: number;
};
const app = new Hono<{ Bindings: Env }>();
app.use('*', cors());
app.get('/', (c) => c.json({ ok: true, component: 'platform_crypto' }));
app.post('/', async (c) => {
let input: Input;
try {
input = await c.req.json() as Input;
} catch {
return c.json({ success: false, error: 'request body must be JSON' }, 400);
}
const encryptionKey = c.env.ENCRYPTION_KEY;
if (!encryptionKey) {
return c.json({ success: false, error: 'ENCRYPTION_KEY not configured' }, 503);
}
try {
switch (input.action) {
case 'generate_api_key': {
if (!input.email) return c.json({ success: false, error: 'email 必填' }, 400);
const apiKey = await generateApiKey(input.email, encryptionKey);
return c.json({ success: true, api_key: apiKey });
}
case 'encrypt': {
if (!input.plaintext) return c.json({ success: false, error: 'plaintext 必填' }, 400);
const { encrypted, iv } = await aesEncrypt(input.plaintext, encryptionKey);
return c.json({ success: true, encrypted, iv });
}
case 'random_token': {
const numBytes = (input.bytes ?? 32) > 0 ? (input.bytes ?? 32) : 32;
const token = randomHex(numBytes);
return c.json({ success: true, token });
}
default:
return c.json({ success: false, error: `不支援的 action: ${input.action}` }, 400);
}
} catch (e) {
return c.json(
{ success: false, error: e instanceof Error ? e.message : String(e) },
500,
);
}
});
export default app;
// ── Crypto implementations (rule 02 §2.2: crypto.subtle 只准在 wasi-shim.ts 或 platform_crypto) ──
async function generateApiKey(email: string, encryptionKey: string): Promise<string> {
const keyBytes = new TextEncoder().encode(encryptionKey.slice(0, 32));
const cryptoKey = await crypto.subtle.importKey(
'raw', keyBytes, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign'],
);
const sig = await crypto.subtle.sign('HMAC', cryptoKey, new TextEncoder().encode(email));
const hex = Array.from(new Uint8Array(sig)).map(b => b.toString(16).padStart(2, '0')).join('');
return 'ak_' + hex.slice(0, 32);
}
async function aesEncrypt(plaintext: string, encryptionKey: string): Promise<{ encrypted: string; iv: string }> {
const keyBytes = new TextEncoder().encode(encryptionKey.slice(0, 32));
const cryptoKey = await crypto.subtle.importKey('raw', keyBytes, { name: 'AES-GCM' }, false, ['encrypt']);
const iv = crypto.getRandomValues(new Uint8Array(12));
const enc = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv },
cryptoKey,
new TextEncoder().encode(plaintext),
);
const toB64 = (buf: ArrayBuffer | Uint8Array) =>
btoa(String.fromCharCode(...new Uint8Array(buf instanceof ArrayBuffer ? buf : buf)));
return { encrypted: toB64(enc), iv: toB64(iv) };
}
function randomHex(numBytes: number): string {
const arr = crypto.getRandomValues(new Uint8Array(numBytes));
return Array.from(arr).map(b => b.toString(16).padStart(2, '0')).join('');
}
@@ -1,11 +0,0 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "ES2022",
"moduleResolution": "bundler",
"lib": ["ES2022"],
"types": ["@cloudflare/workers-types"],
"strict": true,
"noEmit": true
}
}
@@ -1,15 +0,0 @@
name = "arcrun-platform-crypto"
main = "src/index.ts"
compatibility_date = "2025-02-19"
compatibility_flags = ["nodejs_compat"]
workers_dev = true
[vars]
COMPONENT_ID = "platform_crypto"
[[routes]]
pattern = "platform-crypto.arcrun.dev/*"
zone_name = "arcrun.dev"
# ENCRYPTION_KEY 透過 wrangler secret set 設定
# wrangler secret put ENCRYPTION_KEY
+3 -7
View File
@@ -30,19 +30,15 @@ CLOUDFLARE_ACCOUNT_ID=
CLOUDFLARE_API_TOKEN=
# ── ② 身份與加密(自架單人用,這兩格你自己決定/保管)──────────────────────────
# ── ② 身份(自架單人用)──────────────────────────────────────────────────────
#
# NAMESPACE:你的資料分區標籤。隨便取個英數小名即可(例:leo、myteam)。
# 這不是密碼,只是用來分隔你的資料。
#
# ENCRYPTION_KEY:你的 credential 加密金鑰,64 個以上的 hex 字元。你自己保管。
# 不會的話,AI 可以幫你產一串:
# node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
# ⚠️ 這串忘了 = 你之前上傳加密的 credential 就解不開了,請留底。
# (安裝完還要把「同一串」設進你的 worker,acr init 會印確切指令給你跟著做。)
# credential 不再需要自管加密金鑰:明文由 CF Workers Secrets 託管,
# `acr creds push` 會直接寫進你自己的 worker。)
#
NAMESPACE=
ENCRYPTION_KEY=
# ── ③ 各服務的 token(要連哪個服務才填哪個;可之後再加)────────────────────────
+32
View File
@@ -6,6 +6,10 @@ dist/
# 例外:放行 .component-builds 的部署物 wasm — self-host 用戶 / acr init 從 repo 直接拿這份部署
# (推翻 rule 05 原「wasm 不 commit」慣例,見 .agents/specs/arcrun/sdk-and-website/self-hosted-init.md §6
!.component-builds/**/component.wasm
# 例外:Arcrun#80 tier2 worker 官方編譯成品(cypher-executor/kbdb/http_request/code/mcp 的
# esbuild bundle + 隨附 wasm part)——commit 進 repo 同一套理由:固定位置、any clone 都拿得到,
# 不必自己再編一次(見 scripts/build-worker-artifacts.mjs)。
!.worker-builds/**/*.wasm
# 例外:code 零件(自足 Worker)的 vendored quickjs.wasm 同屬部署物 —— acr init/update 從
# repo archive 直接部署(同上 .component-builds 放行邏輯)。來源=npm 套件
# @jitl/quickjs-wasmfile-release-sync 的 emscripten-module.wasm,由 postinstall vendor-wasm.mjs
@@ -48,3 +52,31 @@ scripts/__pycache__/
# D1 備份/匯出(wrangler d1 export 產物,含整庫全量資料=機敏,絕不 commit)
*.sql
backup-*.sql
# 🔴 但 migration 不是備份,它是**要出貨的程式碼**(2026-08-12 實撞):
# 上面那條 `*.sql` 的用意是擋 D1 匯出(整庫全量資料=機敏),卻連 migration 一起吃掉。
# 後果:0001-0004 因為在該規則之前就 commit 所以還在,**0005/0006 從此沒進過版控**
# ⇒ 更新指令從 Gitea 抓 main,那兩個檔根本不在那裡 ⇒ 每個用戶都會收到
# 「✗ D1 migration: 部署物缺 kbdb/migrations/0005…」——**不是誰忘了推,是規則吃掉的**。
# ⇒ 與 `.component-builds/**/component.wasm` 同慣例(見 rules/05-deploy-convention.md
# 「WASM 來源」段),用否定規則放行。備份檔仍由 `backup-*.sql` 與目錄位置擋住。
!kbdb/migrations/*.sql
# GitHub 公開 mirror 工作目錄(publish-github.sh 產物)
.github-public/
wrangler.leo21c.toml
# deploy-all.mjs 產的共用依賴(部署時 npm 安裝 wrangler 等,非 repo 內容)
# 2026-08-07:每次本機跑部署都會冒出來吵未推警察,且含不該進版控的鎖檔
/package.json
/package-lock.json
# console-ui 部署產物(deploy.mjs 依 deploy.targets.json 即時產生,不是原始碼)
console-ui/.staging/
# 「上一次通過線上實測的部署」紀錄——本機事實,不隨 repo 走
# (刻意不進版控:新 checkout 沒有紀錄 ⇒ 狀態未知 ⇒ 該被大聲提醒,而不是繼承別人的綠燈)
console-ui/.deploy-state.json
# Wrangler 本機開發用的密鑰檔——絕不進版控(2026-08-09 補:原本沒被擋,
# 而同目錄有 agent 在動工,一次 git add -A 就會把金鑰推上去)
.dev.vars
**/.dev.vars
Binary file not shown.
File diff suppressed because it is too large Load Diff
Binary file not shown.
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
Binary file not shown.
File diff suppressed because it is too large Load Diff
Binary file not shown.
File diff suppressed because it is too large Load Diff
Binary file not shown.
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
Binary file not shown.
File diff suppressed because it is too large Load Diff
Binary file not shown.
File diff suppressed because it is too large Load Diff
Binary file not shown.
File diff suppressed because it is too large Load Diff
Binary file not shown.
File diff suppressed because it is too large Load Diff
Binary file not shown.
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
Binary file not shown.
File diff suppressed because it is too large Load Diff
Binary file not shown.
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because it is too large Load Diff
Binary file not shown.
File diff suppressed because it is too large Load Diff
Binary file not shown.
File diff suppressed because it is too large Load Diff
Binary file not shown.
File diff suppressed because it is too large Load Diff
Binary file not shown.
File diff suppressed because it is too large Load Diff
Binary file not shown.
File diff suppressed because it is too large Load Diff
+713
View File
@@ -0,0 +1,713 @@
{
"schema": 1,
"built_for": "arcrun-tier2-worker-artifacts",
"generated_at": "2026-08-15T15:53:28.612Z",
"repo_head": "cacaa33f7d4e51d0011096250a33bb5107c67074",
"repo_dirty": false,
"workers": [
{
"name": "arcrun-cypher-executor",
"source_dir": "cypher-executor",
"source_commit": "9fa88065abdc5a795d3847711f04101480621af0",
"main_module": "worker.mjs",
"main_file": "arcrun-cypher-executor/worker.mjs",
"js_bytes": 590499,
"content_sha256": "94a21ad012ee0c48d78e9a74752048917ddb4616f1ea089976834acd887a9d14",
"modules": [],
"compat_date": "2025-02-19",
"compat_flags": [
"nodejs_compat",
"global_fetch_strictly_public"
],
"requires": {
"kv": [
"EXEC_CONTEXT",
"WEBHOOKS",
"CREDENTIALS_KV",
"ANALYTICS_KV",
"RECIPES",
"USERS_KV",
"SESSIONS_KV"
],
"d1": [
{
"binding": "CREDENTIALS_DB",
"database_name": "arcrun-kbdb"
}
],
"vectorize": 0,
"ai": true,
"vars": {
"ENVIRONMENT": "production",
"CF_ACCOUNT_ID": "",
"WORKER_SUBDOMAIN": "uncle6-me",
"KBDB_BASE_URL": "https://arcrun-kbdb.uncle6-me.workers.dev",
"CONSOLE_TENANT": "leo",
"PORTAL_SESSION_TTL": "604800",
"PORTAL_SHOW_WORKFLOWS": "admin",
"GITEA_BASE_URL": "https://git.uncle6.me",
"GITEA_SPRINT_REPO": "Leo/InkStoneCo",
"GITEA_SPRINT_DIR": "system-dev/docs/3-specs/autonomy-dispatch"
}
},
"stripped": {
"services": 13
},
"warnings": []
},
{
"name": "arcrun-kbdb",
"source_dir": "kbdb",
"source_commit": "ceb7638d746f0e8847552aef721f6f53ef87ff0b",
"main_module": "worker.mjs",
"main_file": "arcrun-kbdb/worker.mjs",
"js_bytes": 160306,
"content_sha256": "47b09d60f8d122f7ec234d146a99bc68b2661c56ad6a98eca146d99ec5de03ea",
"modules": [],
"compat_date": "2025-02-19",
"compat_flags": [
"nodejs_compat"
],
"requires": {
"kv": [],
"d1": [
{
"binding": "DB",
"database_name": "arcrun-kbdb"
}
],
"vectorize": 0,
"ai": false,
"vars": {
"ENVIRONMENT": "production"
}
},
"warnings": []
},
{
"name": "arcrun-code",
"source_dir": "registry/components/code",
"source_commit": "621cb8d948d61be6202063fd02effb3f538437fe",
"main_module": "worker.mjs",
"main_file": "arcrun-code/worker.mjs",
"js_bytes": 153758,
"content_sha256": "751634a3fc9a99cc2da662026818d754c48f031d10bff3b3be2d3a8ee2311bd6",
"modules": [
{
"name": "quickjs.wasm",
"type": "application/wasm",
"file": "arcrun-code/quickjs.wasm",
"sha256": "105c3bed22d457e43e3d1c3c1c6959fda62a8fe06f0fc8a985303c3a2be72232"
}
],
"compat_date": "2025-02-19",
"compat_flags": [],
"requires": {
"kv": [],
"d1": [],
"vectorize": 0,
"ai": false,
"vars": {
"COMPONENT_ID": "code"
}
},
"warnings": []
},
{
"name": "arcrun-mcp",
"source_dir": "mcp",
"source_commit": "1ccee0055ebdb1f6d74094df3e807f56b849fa6f",
"main_module": "worker.mjs",
"main_file": "arcrun-mcp/worker.mjs",
"js_bytes": 1192612,
"content_sha256": "9bc8dffb34d0f36608a78b2cfeb8ee8b5c793f52b4497f5d4d5f69f70700972e",
"modules": [],
"compat_date": "2024-11-27",
"compat_flags": [
"nodejs_compat"
],
"requires": {
"kv": [
"OAUTH_KV"
],
"d1": [],
"vectorize": 0,
"ai": false,
"vars": {}
},
"warnings": []
},
{
"name": "arcrun-array-ops",
"source_dir": ".component-builds/array_ops",
"source_commit": "922a57fe3458dfda82bcc25d287cef93f8dab838",
"main_module": "worker.mjs",
"main_file": "arcrun-array-ops/worker.mjs",
"js_bytes": 67802,
"content_sha256": "35ad5be6332dde2299efd10be726300c6cace1eb085ab24f08173536ca7fb950",
"modules": [
{
"name": "component.wasm",
"type": "application/wasm",
"file": "arcrun-array-ops/component.wasm",
"sha256": "74dc9d99f8b9ef43628b55c85c05beb1af8f6f90d78d07cd132ffe4b865147ae"
}
],
"compat_date": "2025-02-19",
"compat_flags": [],
"requires": {
"kv": [],
"d1": [],
"vectorize": 0,
"ai": false,
"vars": {
"COMPONENT_ID": "array_ops"
}
},
"warnings": []
},
{
"name": "arcrun-auth-oauth2",
"source_dir": ".component-builds/auth_oauth2",
"source_commit": "20c7610371c401f622ef4e6ea1b9ba812963be95",
"main_module": "worker.mjs",
"main_file": "arcrun-auth-oauth2/worker.mjs",
"js_bytes": 80472,
"content_sha256": "a9b0d45ee6a63e0eebd45554df16d9fa4e6e717e0c31dac13b630e132f1a96db",
"modules": [
{
"name": "component.wasm",
"type": "application/wasm",
"file": "arcrun-auth-oauth2/component.wasm",
"sha256": "3622c20644e6c8c39487109e7353e2590bad90b1207139e34dfb0cf1c2696664"
}
],
"compat_date": "2025-02-19",
"compat_flags": [
"nodejs_compat"
],
"requires": {
"kv": [
"CREDENTIALS_KV",
"RECIPES"
],
"d1": [],
"vectorize": 0,
"ai": false,
"vars": {
"COMPONENT_ID": "auth_oauth2"
}
},
"warnings": []
},
{
"name": "arcrun-auth-service-account",
"source_dir": ".component-builds/auth_service_account",
"source_commit": "20c7610371c401f622ef4e6ea1b9ba812963be95",
"main_module": "worker.mjs",
"main_file": "arcrun-auth-service-account/worker.mjs",
"js_bytes": 81693,
"content_sha256": "68675ec76f3f060309492e55301936a47f4ae0a5e9b00865eb861e0e631e40e5",
"modules": [
{
"name": "component.wasm",
"type": "application/wasm",
"file": "arcrun-auth-service-account/component.wasm",
"sha256": "7d0808b4b9a2df4fbb54977eafe34a5bdb99e0cc941d97462c62e26f3651951e"
}
],
"compat_date": "2025-02-19",
"compat_flags": [
"nodejs_compat"
],
"requires": {
"kv": [
"CREDENTIALS_KV",
"RECIPES"
],
"d1": [],
"vectorize": 0,
"ai": false,
"vars": {
"COMPONENT_ID": "auth_service_account"
}
},
"warnings": []
},
{
"name": "arcrun-auth-static-key",
"source_dir": ".component-builds/auth_static_key",
"source_commit": "20c7610371c401f622ef4e6ea1b9ba812963be95",
"main_module": "worker.mjs",
"main_file": "arcrun-auth-static-key/worker.mjs",
"js_bytes": 81217,
"content_sha256": "140c163d2e8c609eebf281de25d16e521d85004f8437cc842613c249e44aa7ff",
"modules": [
{
"name": "component.wasm",
"type": "application/wasm",
"file": "arcrun-auth-static-key/component.wasm",
"sha256": "cd0f69a80ced0979749e0ec30b1981795a49eaa56bdb2ba102cd4b64f4670369"
}
],
"compat_date": "2025-02-19",
"compat_flags": [
"nodejs_compat"
],
"requires": {
"kv": [
"CREDENTIALS_KV",
"RECIPES"
],
"d1": [],
"vectorize": 0,
"ai": false,
"vars": {
"COMPONENT_ID": "auth_static_key"
}
},
"warnings": []
},
{
"name": "arcrun-cron",
"source_dir": ".component-builds/cron",
"source_commit": "6e92ca03727bf22143499d6c0df06b40cb65c17b",
"main_module": "worker.mjs",
"main_file": "arcrun-cron/worker.mjs",
"js_bytes": 78911,
"content_sha256": "17068f03f686aee6c0aec9d7d748bbb1998edc8918260033f985697bc718aa76",
"modules": [
{
"name": "component.wasm",
"type": "application/wasm",
"file": "arcrun-cron/component.wasm",
"sha256": "afb3a14f8c8f00f4f5573f44153cb1dcf6fc715b2fb47c2afb0aaf95b74d3cde"
}
],
"compat_date": "2025-02-19",
"compat_flags": [
"nodejs_compat"
],
"requires": {
"kv": [],
"d1": [],
"vectorize": 0,
"ai": false,
"vars": {
"COMPONENT_ID": "cron"
}
},
"warnings": []
},
{
"name": "arcrun-date-ops",
"source_dir": ".component-builds/date_ops",
"source_commit": "922a57fe3458dfda82bcc25d287cef93f8dab838",
"main_module": "worker.mjs",
"main_file": "arcrun-date-ops/worker.mjs",
"js_bytes": 67781,
"content_sha256": "60ebc1d17ab0b9d3bb66f1c71ec63eb5d35bc97ed9f8b4cd1c1c95f4eeb171b7",
"modules": [
{
"name": "component.wasm",
"type": "application/wasm",
"file": "arcrun-date-ops/component.wasm",
"sha256": "4e2c1329ce4014aa86a9a6449d8699e67c6c616f5c07088dc21216d4036ca41e"
}
],
"compat_date": "2025-02-19",
"compat_flags": [],
"requires": {
"kv": [],
"d1": [],
"vectorize": 0,
"ai": false,
"vars": {
"COMPONENT_ID": "date_ops"
}
},
"warnings": []
},
{
"name": "arcrun-filter",
"source_dir": ".component-builds/filter",
"source_commit": "922a57fe3458dfda82bcc25d287cef93f8dab838",
"main_module": "worker.mjs",
"main_file": "arcrun-filter/worker.mjs",
"js_bytes": 67739,
"content_sha256": "c7fd5070ff7c0f0341e9dd364805e393f9d68056a846f48e6bc61ef6f4e67187",
"modules": [
{
"name": "component.wasm",
"type": "application/wasm",
"file": "arcrun-filter/component.wasm",
"sha256": "6a0692b386522918b3b5af79cf7877394d1f02c350018dbaef68ef380dad1b9e"
}
],
"compat_date": "2025-02-19",
"compat_flags": [],
"requires": {
"kv": [],
"d1": [],
"vectorize": 0,
"ai": false,
"vars": {
"COMPONENT_ID": "filter"
}
},
"warnings": []
},
{
"name": "arcrun-foreach-control",
"source_dir": ".component-builds/foreach_control",
"source_commit": "922a57fe3458dfda82bcc25d287cef93f8dab838",
"main_module": "worker.mjs",
"main_file": "arcrun-foreach-control/worker.mjs",
"js_bytes": 67928,
"content_sha256": "e52410c9611a93ac7fe38c7b11291cc18711c7da3370492788c3c55bc0fb03d5",
"modules": [
{
"name": "component.wasm",
"type": "application/wasm",
"file": "arcrun-foreach-control/component.wasm",
"sha256": "8ae161ab37cc78ef6fae65364a47f7accb572378b395c4637daa2ffa57f14632"
}
],
"compat_date": "2025-02-19",
"compat_flags": [],
"requires": {
"kv": [],
"d1": [],
"vectorize": 0,
"ai": false,
"vars": {
"COMPONENT_ID": "foreach_control"
}
},
"warnings": []
},
{
"name": "arcrun-http-request",
"source_dir": ".component-builds/http_request",
"source_commit": "1e85dfb49b0e8d81c0854781d93ee4e6a300c7b3",
"main_module": "worker.mjs",
"main_file": "arcrun-http-request/worker.mjs",
"js_bytes": 80079,
"content_sha256": "cdd97364f277587cbade69e09bb40812c68f26a1e8bc9aa632c65b1b962b0b85",
"modules": [
{
"name": "component.wasm",
"type": "application/wasm",
"file": "arcrun-http-request/component.wasm",
"sha256": "cc15cc785703e7bbb8dbff2d38dc84a4ac24e2f44316182730abae0f170ef133"
}
],
"compat_date": "2025-02-19",
"compat_flags": [
"nodejs_compat",
"global_fetch_strictly_public"
],
"requires": {
"kv": [],
"d1": [],
"vectorize": 0,
"ai": false,
"vars": {
"COMPONENT_ID": "http_request"
}
},
"warnings": []
},
{
"name": "arcrun-if-control",
"source_dir": ".component-builds/if_control",
"source_commit": "922a57fe3458dfda82bcc25d287cef93f8dab838",
"main_module": "worker.mjs",
"main_file": "arcrun-if-control/worker.mjs",
"js_bytes": 67823,
"content_sha256": "557ecf4ee955105af07b2227b08365eb9de8c39ad8ffb323b101397704f438bd",
"modules": [
{
"name": "component.wasm",
"type": "application/wasm",
"file": "arcrun-if-control/component.wasm",
"sha256": "87c81e9cf5e664f60c06a364508dd365d49953e1b7afba6a5753fce5a4c82636"
}
],
"compat_date": "2025-02-19",
"compat_flags": [],
"requires": {
"kv": [],
"d1": [],
"vectorize": 0,
"ai": false,
"vars": {
"COMPONENT_ID": "if_control"
}
},
"warnings": []
},
{
"name": "arcrun-merge",
"source_dir": ".component-builds/merge",
"source_commit": "922a57fe3458dfda82bcc25d287cef93f8dab838",
"main_module": "worker.mjs",
"main_file": "arcrun-merge/worker.mjs",
"js_bytes": 67718,
"content_sha256": "e4216249436000bbb4db0318cc9028f936ca6537d3d656228cfdd3cb31df25f8",
"modules": [
{
"name": "component.wasm",
"type": "application/wasm",
"file": "arcrun-merge/component.wasm",
"sha256": "55a4d143246df172f60b5e15e7b26e55279deda9db436e3d65bccc25c86d7cd9"
}
],
"compat_date": "2025-02-19",
"compat_flags": [],
"requires": {
"kv": [],
"d1": [],
"vectorize": 0,
"ai": false,
"vars": {
"COMPONENT_ID": "merge"
}
},
"warnings": []
},
{
"name": "arcrun-number-ops",
"source_dir": ".component-builds/number_ops",
"source_commit": "922a57fe3458dfda82bcc25d287cef93f8dab838",
"main_module": "worker.mjs",
"main_file": "arcrun-number-ops/worker.mjs",
"js_bytes": 67823,
"content_sha256": "91821a291117e033bfa4f013cd88de09479cd4ac43708e37a39df3f31c72aaa6",
"modules": [
{
"name": "component.wasm",
"type": "application/wasm",
"file": "arcrun-number-ops/component.wasm",
"sha256": "452d1940f6e2b2b36dc97c145abcb7b5bacdbfa7631c742827c2f6f6db87b4ea"
}
],
"compat_date": "2025-02-19",
"compat_flags": [],
"requires": {
"kv": [],
"d1": [],
"vectorize": 0,
"ai": false,
"vars": {
"COMPONENT_ID": "number_ops"
}
},
"warnings": []
},
{
"name": "arcrun-set",
"source_dir": ".component-builds/set",
"source_commit": "922a57fe3458dfda82bcc25d287cef93f8dab838",
"main_module": "worker.mjs",
"main_file": "arcrun-set/worker.mjs",
"js_bytes": 67676,
"content_sha256": "6cac587802cae7dc1651d6b461232abafe40dcf0fbfe9e0adc5b1d8e35b41cc1",
"modules": [
{
"name": "component.wasm",
"type": "application/wasm",
"file": "arcrun-set/component.wasm",
"sha256": "f5afcf6e57401d2d83b21fb971ec80ca56d5e8e4bdb74a004f3d118875ed57c4"
}
],
"compat_date": "2025-02-19",
"compat_flags": [],
"requires": {
"kv": [],
"d1": [],
"vectorize": 0,
"ai": false,
"vars": {
"COMPONENT_ID": "set"
}
},
"warnings": []
},
{
"name": "arcrun-string-ops",
"source_dir": ".component-builds/string_ops",
"source_commit": "922a57fe3458dfda82bcc25d287cef93f8dab838",
"main_module": "worker.mjs",
"main_file": "arcrun-string-ops/worker.mjs",
"js_bytes": 67823,
"content_sha256": "c85166e357b76828c7020426d912b591006152ac38f024a095a4d9dcd10dee72",
"modules": [
{
"name": "component.wasm",
"type": "application/wasm",
"file": "arcrun-string-ops/component.wasm",
"sha256": "41f3243ee18bb9f4fcbaa417bd773347128dec2971132e240eddec19ba6a9da2"
}
],
"compat_date": "2025-02-19",
"compat_flags": [],
"requires": {
"kv": [],
"d1": [],
"vectorize": 0,
"ai": false,
"vars": {
"COMPONENT_ID": "string_ops"
}
},
"warnings": []
},
{
"name": "arcrun-switch",
"source_dir": ".component-builds/switch",
"source_commit": "922a57fe3458dfda82bcc25d287cef93f8dab838",
"main_module": "worker.mjs",
"main_file": "arcrun-switch/worker.mjs",
"js_bytes": 67739,
"content_sha256": "e0e7689dbba735a254556272d6d233396c7fa05cc0fba5835a3e20b0ddf00fca",
"modules": [
{
"name": "component.wasm",
"type": "application/wasm",
"file": "arcrun-switch/component.wasm",
"sha256": "6d993dc9b1affe16b600aaa7cae5af977108cc08513bb11d9d9db908285fea8b"
}
],
"compat_date": "2025-02-19",
"compat_flags": [],
"requires": {
"kv": [],
"d1": [],
"vectorize": 0,
"ai": false,
"vars": {
"COMPONENT_ID": "switch"
}
},
"warnings": []
},
{
"name": "arcrun-try-catch",
"source_dir": ".component-builds/try_catch",
"source_commit": "922a57fe3458dfda82bcc25d287cef93f8dab838",
"main_module": "worker.mjs",
"main_file": "arcrun-try-catch/worker.mjs",
"js_bytes": 67802,
"content_sha256": "a5fbcbf452ab00269dc857997df3059bee7eb1f2c1b322d249146ab4e5e5bac9",
"modules": [
{
"name": "component.wasm",
"type": "application/wasm",
"file": "arcrun-try-catch/component.wasm",
"sha256": "10eaddc5ef0d3dc68e3f009970541ac7f5fce30dcdbecec9a333afd5a29e3486"
}
],
"compat_date": "2025-02-19",
"compat_flags": [],
"requires": {
"kv": [],
"d1": [],
"vectorize": 0,
"ai": false,
"vars": {
"COMPONENT_ID": "try_catch"
}
},
"warnings": []
},
{
"name": "arcrun-validate-json",
"source_dir": ".component-builds/validate_json",
"source_commit": "922a57fe3458dfda82bcc25d287cef93f8dab838",
"main_module": "worker.mjs",
"main_file": "arcrun-validate-json/worker.mjs",
"js_bytes": 67886,
"content_sha256": "2d0efa26e9fe38db87eb9585daf72e0f2ba8c859d15d1b65f36798656cc4e83b",
"modules": [
{
"name": "component.wasm",
"type": "application/wasm",
"file": "arcrun-validate-json/component.wasm",
"sha256": "8142ffd3cc57efbb9128faaf450b646111b0f2e2575cc29a7b562a7b185912ab"
}
],
"compat_date": "2025-02-19",
"compat_flags": [],
"requires": {
"kv": [],
"d1": [],
"vectorize": 0,
"ai": false,
"vars": {
"COMPONENT_ID": "validate_json"
}
},
"warnings": []
},
{
"name": "arcrun-wait",
"source_dir": ".component-builds/wait",
"source_commit": "922a57fe3458dfda82bcc25d287cef93f8dab838",
"main_module": "worker.mjs",
"main_file": "arcrun-wait/worker.mjs",
"js_bytes": 67697,
"content_sha256": "3d1b46e7b1ff056abb2c1aeed738d23d58eaa7c8f36b674e4ea2ed1602015f9e",
"modules": [
{
"name": "component.wasm",
"type": "application/wasm",
"file": "arcrun-wait/component.wasm",
"sha256": "52ca2d184bf3dcc646bb94f29f7f3e69b5540276363e83a54cb435ff33ca2f3c"
}
],
"compat_date": "2025-02-19",
"compat_flags": [],
"requires": {
"kv": [],
"d1": [],
"vectorize": 0,
"ai": false,
"vars": {
"COMPONENT_ID": "wait"
}
},
"warnings": []
},
{
"name": "arcrun-rag-ui",
"source_dir": "console-ui/public",
"source_commit": "614fe448124dbd25d8228e37723473f1704f3a7a",
"main_module": "worker.mjs",
"main_file": "arcrun-rag-ui/worker.mjs",
"js_bytes": 524513,
"content_sha256": "5825f427e4211dd4b0eee969c94c8bfa856f65b8a48fc718e137f3b5d074e1ff",
"modules": [],
"compat_date": "2026-07-01",
"compat_flags": [],
"requires": {
"kv": [],
"d1": [],
"vectorize": 0,
"ai": false,
"vars": {}
},
"ui_fingerprint": "82bc636990e60172663f7ce90c75d8b434193b8532b21f1d4b57c5fb330cbe11",
"ui_file_count": 9,
"warnings": []
}
],
"excluded": [
{
"name": "arcrun-claude-api",
"dir": ".component-builds/claude_api",
"reason": "component.wasm 不在版控(.gitignore 明文排除=這顆不是可出貨的零件)"
}
],
"notes": []
}
+11 -4
View File
@@ -19,7 +19,7 @@
- [~] **credential 注入 401 修復**`{{credential.X}}` 注入失敗,用戶被迫把 token 明文寫進 workflow。
**根因**:此語法系統沒實裝(三條 template 路徑都不認 `credential.` namespace)。
**修法**design §8richblack 2026-06-10 確認):auth_static_key 加 `resolve_credentials` actionWASM 解密)
+ graph-executor `resolveCredentialRefs` 偵測回填(不碰 ENCRYPTION_KEYrule 02 §2.2)。
+ graph-executor `resolveCredentialRefs` 偵測回填(不碰金鑰rule 02 §2.2)。
**8.1-8.4 done**tinygo build OK + tsc 0 + §2.2 自檢綠)。**待 8.5 端對端驗收**(部署 + 真 OpenAI key + 全新帳號打 2xx)。
- [ ] **§8 P1/P2 recipe/workflow list 遷 D1** — CF KV list 免費僅 1000/日,不修=用戶用一用就 429,
免費承諾破功。D1 現已可建(依賴解除),架構拍板走 kbdb `/entries` HTTP 雙寫不加 binding。
@@ -27,14 +27,21 @@
### 🟡 P1(封測門檻 / 技術債)
- [ ] credential-primitives-wasm **Phase 0.7**component-loader WASM runner)→ **Phase 1-2**auth_static_key / auth_service_account WASM 零件)。0.6 已完成。
- [ ] **Phase 3** 清除違規 TScredential-injector.ts / jwt-signer.ts / BUILTIN_*)—— 須先有 Phase 1-2 WASM 頂上。
- [x] ~~credential-primitives-wasm Phase 0.7 / Phase 1-2 / Phase 3~~**全數完成**,該卷已封存
`system-dev/docs/3-specs/archive/credential-primitives-wasm/`)。auth_static_key /
auth_service_account / auth_oauth2 WASM 零件到位;credential-injector.ts、jwt-signer.ts、
`BUILTIN_API_RECIPES` / `BUILTIN_CREDENTIALS_MAP` 全數移除(grep 0 筆,最後一項 T10 於
2026-07-20 commit `20c7610`)。credential 現行做法=CF Workers Secrets + D1 目錄。
- [ ] **`auth_mtls` 零件從未實作**(原 Phase 4.3/4.4 遺留,`registry/components/auth_mtls/` 不存在)。
mTLS 認證目前不支援;要做需**另立新 SDD**(封存卷不得復用)。
- [ ] **self-hosted auth 鏈端到端驗收**(原 Phase 7.6 遺留):`global_fetch_strictly_public` flag
是否真解 same-zone 1042,在自架帳號上從未實測。同屬需另立 SDD 的殘留缺口。
- [ ] 4 份 inline http_request host fn 抽共用 helperdedup;假綠修是逐份改的)。
### ⚪ P2(不擋封測)
- [ ] `arcrun.dev/llms.txt` servelanding/public 補檔)
- [ ] ENCRYPTION_KEY 冪等性、MCP account-source、recipe submit uuid 回傳
- [ ] MCP account-source、recipe submit uuid 回傳
- [ ] 文件遷移階段二/三 + wiki modules/
- [ ] 下方第一期殘項:步驟 2acr recipe test)、步驟 5b(資料外流警示 SDD)、步驟 6(搬家拆 matrix
+36 -2
View File
@@ -21,7 +21,7 @@
1. **任何 code 變動前必須先讀對應 SDD**,在回覆開頭宣告已讀清單與對應 task 編號(格式見 `.claude/rules/00-sdd-protocol.md`
2. **零件只能用 TinyGo 或 AssemblyScript 編譯成 WASM**`registry/components/` 下禁止 TypeScript
3. **cypher-executor TS 禁止實作 credential / auth / JWT / template 展開業務邏輯**;這些全在 WASM 零件
4. **Cypher binding = YAML 裡的 URL 清單**,不是 Cloudflare service binding;零件串接走 HTTP URL(含 auth primitive)。self-hosted same-zone 1042 用 `global_fetch_strictly_public` flag 解,不新增 bindingcredential-primitives-wasm Phase 7
4. **Cypher binding = YAML 裡的 URL 清單**,不是 Cloudflare service binding;零件串接走 HTTP URL(含 auth primitive)。self-hosted same-zone 1042 用 `global_fetch_strictly_public` flag 解,不新增 binding來源:credential-primitives-wasm Phase 7,該卷已封存,規則仍有效
5. **每個 WASM 零件 = 獨立 Worker = 公開 URL**;不從 R2 動態讀(R2 只 Phase 5 啟用)
6. **修改現有程式碼,不是新建資料夾重做**
7. **每完成一個 task 立刻更新 tasks.md 的 `[x]`**,不批次
@@ -63,6 +63,40 @@
---
## 🔴 第一鐵律:wiki 是判準,不准跳過(2026-07-20/21 leo 兩度點破)
**要查任何東西之前,先照索引走:`system-dev/wiki/INDEX.md` → 下層 `00-INDEX.md` → 卡片。**
🔴 **2026-08-15 leo 糾正:本條原本寫的是「用 grep」,那是錯的。**
grep 是平面搜尋,它繞過索引,於是**索引永遠不會被驗證、也永遠不會變準**——
而 AI 又用「索引可能不準」當理由繼續 grep。這是自我實現的。
實測那天:`matrix/arcrun` 的 wiki 有 64 張卡,**只有 1 張走得到索引**
(唯一那張是 leo 手寫的;63 張機器產的卡從沒加入任何索引),
而 AI 從沒發現——**因為 grep 找得到,破損就永遠不會浮現。**
**grep 不是禁令,是異常訊號**:
1. 先照索引走
2. 索引走不到 → **那是索引壞了,先講出來**(哪一層缺、缺什麼)
3. 講完才准 fallback 用 `grep -rin "<關鍵字>" system-dev/wiki/`
⇒ 每一次 grep 都該留下一筆「索引缺陷」,而不是變成習慣。
(全面禁止 grep 也是錯的——索引壞掉那天 AI 會瞎掉,而且沒人知道。)
> leo:「花很多力氣去產生 wiki,最重要的就是要可以查詢,**結果要查的時候就跳過,那就白寫了**。」
> 「重點是你自己的記憶對嗎?而你有按照規定去切實讀 wiki 嗎?」
**三條硬規則**
1. **wiki 與程式碼/歷史文件衝突 → 以 wiki 為準**。程式碼反映「還沒清乾淨」,不等於「還在用」。
2. wiki 寫「不可動/待廢除/進行中」→ **讀它的解除條件並逐條核對**。那是當時狀態,不是永久禁令。
3. 翻原文後得到新結論 → **回頭更新 wiki**wiki 過時是債,要還)。
**動外部系統(部署/curlwrangleracrgh)前**:先找 repo 有沒有**現成腳本或 README 部署段**
別自創方法。(實例:2026-07-21 明明有 `npx wrangler deploy` 這條驗過的路,卻自己 curl 硬幹踩坑。)
> hook `wiki-first-search.sh` 會在你查 code/下高風險指令時自動推 wiki 命中行;
> **但機制只是提醒,判斷是你的責任**
## Wiki(每次 session 的讀取順序)
| 檔案 | 時機 | 用途 |
@@ -96,7 +130,7 @@
| 子系統 | 路徑 |
|-------|------|
| **現行 active** RAG Portal 多人授權 | `system-dev/docs/3-specs/portal-auth/` |
| Credential Primitives WASMpaused | `docs/3-specs/arcrun/credential-primitives-wasm/` |
| ~~Credential Primitives WASM~~**closed,已封存** | `system-dev/docs/3-specs/archive/credential-primitives-wasm/` — credential 現行做法見 `.claude/rules/01-tech-stack.md`;殘留缺口(`auth_mtls` 未實作等)要做需另立新 SDD |
| arcrun 總進度 | `docs/3-specs/arcrun/arcrun.md` |
| Auth Recipe 系統 | `docs/3-specs/arcrun/auth-recipe.md` |
| Landing Page | `docs/3-specs/arcrun/landing-page.md` |
+88
View File
@@ -0,0 +1,88 @@
# 想貢獻零件(component)?先確認你真的需要
> **99% 的需求不需要新零件。** 零件是**專業等級**、走 PR 審核;
> **recipe / workflow / app 誰都可以做**,隨建隨用、不必部署。
---
## 先照這個順序找,多半不用寫零件
**1. 語意搜尋知識庫**(最強——它能找到你沒猜中的用詞)
```
kbdb_search(q="我想達成什麼(用一句話描述)", mode="semantic")
kbdb_get_map() # 不確定該查哪個庫,先看藏書地圖
```
**2. 看現成的服務整合**26 個:GitHubNotionGeminiSlack…)
```bash
acr auth-recipe list
acr auth-recipe scaffold github # 直接吐出 credentials 範本 workflow 範例
```
**3. 看零件全集**(21 顆通用零件)
```bash
acr parts
```
特別注意 **`http_request`**:它能打**任意** HTTP API。
「平台沒有 XX 服務的零件」通常不成立——用 `http_request` 一份 recipe 就有了。
**4. 看有沒有現成 workflow 可以直接接**
```bash
acr list
```
---
## 三層責任分工
| 層 | 誰做 | 怎麼做 |
|---|---|---|
| **通用能力** | 平台提供 | `http_request` auth-recipe 機制=**能打任何 API**,這是地基 |
| **熱門服務 recipe** | 平台預鋪 | 減少常見情境的摩擦(現 26 個) |
| **冷門/特殊** | **誰用到誰開發** | recipe 是**設定不是程式**,門檻低 |
> 我們不會包辦全世界所有服務的 API。**用到就自己補一份 recipe**,那是設定檔不是程式碼。
---
## 什麼時候才真的需要新零件
**只有這種情況**:需要**新的原語能力**,而且**無法用既有零件組合出來**。例如——
- 一種新的控制流(現有 `if_control``switch``foreach_control``filter``try_catch` 都表達不了)
- 一種新的資料轉換原語(`code` 零件的沙箱做不到)
- 需要 WASM 層才能做的事(純計算、特殊編解碼)
**不算的情況**(這些都用 recipeworkflow 解):
- 「我要接 XX 服務的 API」→ `http_request` recipe
- 「我要做 XX 業務邏輯」→ workflow 組合既有零件
- 「我要處理某種資料格式」→ `code` 零件(沙箱 JS
---
## 真的要貢獻零件的話
零件是 WASMTinyGoAssemblyScript),有嚴格的沙箱約束
(禁網路 syscall、禁檔案系統、禁 goroutine、體積上限 2MB、
唯一 I/O 模型是 stdin/stdout JSON)。
**流程**:走 Arcrun repo 的 PR,過 `docs/component-pr-review-standard.md` 審核。
撰寫規範與 contract schema 見 `registry/` 底下的既有零件範例。
---
## 為什麼 MCP 不再暴露 `publish_component` / `get_component_guide`
2026-07-21 leo 拍板停用)
那兩個工具對一般使用者是**誤導危機**:搜不到東西時,系統會建議「去提交新零件」,
把人推向最難、最該擋的那條路。
**實測**:總管想寫一支「定期打 API 然後通知」的 workflowPython 約 10 行),
`foreach_control` 怎麼用,MCP 回傳的是**TinyGo 寫 WASM 零件的教學**
(白名單、syscall 限制、contract schema)——完全是另一件事,導致 40 分鐘未完成。
**設計判準**leo):
> 前端界面要**人類友善**Arcrun 要 **AI 友善**——都要**從終點看**。
> Arcrun **讓 AI 輕易建立程式碼**AI 要覺得 **Arcrun 比 Python 還簡單**
> 因此沒有寫 Python 的慾望。**絕不可迷路、搞不懂。**
+10 -13
View File
@@ -2,7 +2,10 @@
**讓 AI 用的工作流軟體(目前只支援 Claude Code**
> 想先看用它做出來的產品?**[Arcrun RAG](https://git.uncle6.me/Leo/arcrun-rag)** —— 企業知識庫(丟檔案自動長出可查詢、可問答的知識庫),有[線上 demo](https://rag-demo.arcrun.dev/portal) 可直接玩
> 想先看用它做出來的產品?**[Arcrun RAG](https://github.com/youlinhsieh/arcrun-rag)** —— 企業知識庫(丟檔案自動長出可查詢、可問答的知識庫)。
>
> 目前**沒有公開試玩站**(早期那個共用示範站已於 2026-08-08 退場)。想直接看產出長什麼樣,
> 可以看示範知識庫的公開鏡像 [arcrun-rag-demo-knowledge](https://github.com/youlinhsieh/arcrun-rag-demo-knowledge)——純靜態、免登入。
AI 很會寫程式,就要除錯,過程浪費很多 Token 及時間,但絕大部分是重複內容,例如登入認證、存取資料庫等。
@@ -161,27 +164,21 @@ acr init --self-hosted
你不需要懂 git、不需要懂 tinygo、不需要手動建任何東西——預編譯好的零件(`.wasm`)直接從 GitHub 下載,用**你自己的** CF token 部署到**你的**帳號。
**最後一步:身份設定(你自己持有,工具不碰)。** self-hosted 是單租戶——你不需要平台發的 API Key,只需要個你自己填的值。在專案建一個 `.env`
**最後一步:身份設定(你自己持有,工具不碰)。** self-hosted 是單租戶——你不需要平台發的 API Key,只需要個你自己填的值。在專案建一個 `.env`
```bash
# .env(已被 gitignoreCLI 會自動讀)
NAMESPACE=leo # 你的資料分區標籤(明碼即可,這不是密碼)
ENCRYPTION_KEY=<64+ hex> # credential 加密金鑰,你自己保管(忘了 = 解不開已上傳的 credential
# 生成 keynode -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
```
> `NAMESPACE` 只是「你的資料放哪個分區」的標籤,不是密碼——要防別人呼叫你的 webhook,請對 webhook 加保護(見下)。
把**同一把** `ENCRYPTION_KEY` 也設進你的 workerruntime 解密要用,CLI 會印確切指令):
credential **不需要**你自管加密金鑰:`acr creds push` 走 TLS 把值送進你自己的 worker,由 **Cloudflare Workers Secrets** 託管(連你自己都讀不回,只能覆寫/刪除)。這需要 cypher worker 有一把能打 Workers Scripts secrets API 的 CF token`acr init` 會印確切指令):
```bash
wrangler secret put ENCRYPTION_KEY --name arcrun-cypher-executor
wrangler secret put ENCRYPTION_KEY --name arcrun-auth-static-key
wrangler secret put ENCRYPTION_KEY --name arcrun-auth-service-account
wrangler secret put CF_SECRETS_API_TOKEN --name arcrun-cypher-executor
```
> 不想自己 put?跑 `acr init` 時明示同意,AI 可代你設——但預設由你自己 put(金鑰是你持有的)。
完成。之後有新版零件,跑 `acr update` 一樣自動拉新、重部署。
> 想先不碰 Cloudflare、純在本機感受語法?`acr init --local` 然後直接跳到下面「寫一個工作流」。
@@ -316,7 +313,7 @@ acr update self-hosted:拉新版零件/引擎並重新
acr update --force 強制重部所有 worker(忽略未變動跳過快取)
```
> 給 AI 操盤手:開始前讀 `.claude/rules/06-mindset.md`(或 arcrun-mindset Skill——它說明 arcrun 的世界觀(工作流是 default、零件稀有且不自製、一切外部 API 皆 recipe),讓你一開始就走在正路上。
> 給 AI 操盤手:開始前讀 [`llms.txt`](llms.txt)——它說明 arcrun 的世界觀(工作流是 default、零件稀有且不自製、一切外部 API 皆 recipe),讓你一開始就走在正路上。跑過 `acr install-harness` 之後,同一套規範會裝進你的專案(arcrun-mindset Skill)。
---
@@ -339,7 +336,7 @@ MIT
arcrun 的核心架構、WASM 零件、CLI 工具鏈與這份文件,由以下貢獻者共同打造:
- **[@richblack](https://github.com/richblack)** — 創始人,產品設計與架構決策
- **[@youlinhsieh](https://github.com/youlinhsieh)** — 創始人,產品設計與架構決策
- **ClaudeAnthropic** — 實作夥伴:零件開發、executor 架構、CLI 實作與程式碼審查
歡迎加入:[CONTRIBUTING.md](CONTRIBUTING.md)
想投稿零件?看 [CONTRIBUTING-components.md](CONTRIBUTING-components.md)。
+49 -25
View File
@@ -1,41 +1,65 @@
<!-- arcrun-harness:start -->
## 這個專案用 arcrun 做自動化
## 這個專案用 Arcrun 做自動化
當需要「串服務 / 排程 / 打 API / 資料處理自動化」時,用 arcrun 工作流,**不要自己寫 Python/Node 一次性腳本**。arcrun 工作流是純文字、可複用、跑在使用者自己的 Cloudflare。
當需要「串服務/排程/打 API資料處理自動化」時,用 Arcrun 工作流,**不要自己寫 Python/Node 一次性腳本**。
Arcrun 工作流是純文字、可複用、跑在使用者自己的 Cloudflare。
arcrun 只有三種東西,先分清楚就不會做歪:
⚠️ **不要上網搜 Arcrun 文件**——網路上沒有,找到的都是錯的。
語法與範本全在 **arcrun-mindset** Skill 裡,`acr` 指令查得到現況。
### 怎麼開始:先寫「意圖」,不要先寫 YAML
你**不需要先知道有哪些零件**。把需求寫成一串「誰接誰」,丟去查,系統會告訴你哪些存在:
```
input >> ON_SUCCESS >> fetch_rows
fetch_rows >> 對每個 row >> notify
```
- 第一個節點固定是 `input`(觸發時帶進來的資料)
- **邊有這些**`ON_SUCCESS``對每個 <變數>`FOREACH)、`ON_TRUE``ON_FALSE`(配 `if_control`)、`ON_BRANCH``branch:`(配 `switch``try_catch`
- **沒有** `ON_FAILURE`——要處理失敗用 `try_catch` `ON_BRANCH(catch)`
完整語法、四份實跑過的範本、節點命名慣例 → 讀 **arcrun-mindset** Skill。
### Arcrun 只有三種東西,先分清楚就不會做歪
| 東西 | 是什麼 | 你能做的 |
|---|---|---|
| **工作流(workflow** | 把零件/recipe 串起來的純文字流程 | **預設就寫這個**,自由寫 |
| **recipe** | 打「一個固定外部 API」的設定(http_request + endpoint/header/body 模板) | 自由寫、**而且該投稿**(見下) |
| **零件(component** | WASM 程式(流程控制 / 資料處理 / http_request / auth),固定一小套 | **你不自製**由維護者管,走 GitHub PR |
| **工作流(workflow** | 把零件recipe 串起來的純文字流程 | **預設就寫這個**,自由寫 |
| **recipe** | 打「一個固定外部 API」的設定(endpointheaderbody 模板) | 自由寫、**而且該投稿** |
| **零件(component** | WASM 程式(流程控制資料處理`http_request`auth),固定一小套 | **你不自製** PR |
> **一句話判準**:打一個固定外部 endpoint → 寫 **recipe**;流程控制 / 資料處理 / 通用 HTTP → 用既有**零件**;其他 → 寫**工作流**串起來。
> **一句話判準**:打一個固定外部 endpoint → 寫 **recipe**;流程控制資料處理通用 HTTP → 用既有**零件**;其他 → 寫**工作流**串起來。
>
> ⚠️ 常見錯誤:把 `telegram_send``gmail_send``kbdb_get` 當**零件**寫。它們是 **recipe**
### 三個最常踩的坑(讀懂這三條,就不會像前人誤解四輪)
### 查詢回 `not_found` 時:兩條路,都不准改寫成 code
**① recipe 是公共資產,發現缺的就自己做一個投稿,不必問人。**
`acr recipe push` = 把 recipe **投稿到公共庫**,不是存私人腳本。公開/私有只是 recipe 的一個屬性(同一身份可有公私版本並存),不是兩條不同的路。
→ 所以「想打某個 API 但沒有現成 recipe」時,**正解是自己寫一個 recipe 並 push 上去**(會 inject credential、push 時幫你檢查打不打得通)。這是被鼓勵的,別誤以為要自用、不上傳。
查詢會誠實回報(兩庫都查過才點名):
**② 缺一個能力 → 去補 APIcypher endpoint),不准用 recipe / 多步工作流拼裝出來。**
判準口訣:**「這段邏輯換一個介面(CLI→MCP)要不要重寫?要重寫 → 它是『能力』,該長在 API。」**
- ❌ 缺 `upsert` → 在工作流裡拼「先查、沒有再建」、或寫個 recipe 假裝補上。
- ✅ 缺 `upsert` → 去 API 加一個 `upsert` endpointCLI/MCP/recipe 都呼叫它。
recipe 只負責「打一個固定外部 API」這件單純事;它不是用來補 arcrun 自己缺的能力的。缺能力就回報 / 補在 API,不要繞。
| status | 意思 | 你該做什麼 |
|---|---|---|
| `found` / `resolved` | 有現成的可用 | **只填 payload** |
| `not_found` | 零件庫與 recipe 庫都沒有 | 照回應的 `suggestion` 走(見下兩條路),並看 `similar_components``similar_recipes` 有沒有能直接用的 |
| `unknown` | 查不到 registry(未部署/網路失敗) | **不代表不存在**,別據此改寫成 code |
**③ 已經有自製零件(例如 mira 的那幾個)→ 讓它退場,別再加新的。**
你不該自製零件;既有的自製零件要往這三條退場:
- `claude_api` 之類「工作流回頭叫 LLM」→ **刪掉**,需要 AI 判斷時是**你(操盤的 CC)自己做**,再叫工作流做確定性的下一步。arcrun 是 AI 用的工具,不是工具回頭用 AI
- `kbdb_*` 之類資料存取 → 改走已備好的 **`acr kbdb` 薄殼 / `kbdb_*` MCP 工具**template + record 模型),不要當零件。
- 純粹打某個固定外部 API 的假零件 → **改寫成 recipe** 投稿(見①)。
- **缺外部 API****自己寫一個 recipe**`acr recipe push`(幾行 YAML,不用部署 Worker、不用寫程式)。
recipe 是公共資產,發現缺的就補一個投稿,不必問人。
- **缺計算能力**(加解密/壓縮這類純運算) → 投稿**零件 PR**(要人類確認,罕見)
🔴 **查不到就改寫成 `code` 節點 =「腹語術」**(表面用 Arcrun、實際全寫 JS)。
`code` 只用於**局部整形**(例:剝掉 LLM 回應的雜訊、切段落),不用來取代零件與流程控制。
> 實錄:每一個寫進 `code``if` 都是沒被測過的新 bug;零件的價值是「被測過 1000 次」,寫進 code 就歸零。
### 其餘鐵律
- **先查能力再動手**`acr parts`(看可用零件)、`acr auth-recipe list`(看支援的認證服務)、`acr kbdb`(資料存取)。
- **暴露資料要人類同意**:部署對外 webhook / push recipe 會讓東西可被外部呼叫 → 停下來讓使用者明示同意,不替他決定公開
- **誠實**:沒打通就誠實說(缺 credential 標「未驗收:缺 X」),不假裝成功;完成以 HTTP 2xx / trace 為證,不口頭宣布
- **先查能力再動手**`acr search <關鍵字>`(一次掃零件/recipeauth-recipeworkflow)、
`acr parts`(零件)、`acr recipe list`recipe)、`acr auth-recipe list`(支援的認證)
- **需要 AI 判斷時你自己做**,不要讓工作流回頭呼叫 LLM。Arcrun 是 AI 用的工具,不是工具回頭用 AI
- **金鑰只拿名字**:定義裡只寫 `{{credential.<名字>}}`,真身絕不寫進 workflowrecipe 檔案。
- **暴露資料要人類同意**`acr push``acr recipe push` 會讓東西可被外部呼叫 → 停下來讓使用者明示同意,不替他決定公開。
- **誠實**:沒打通就誠實說(缺 credential 標「未驗收:缺 X」),不假裝成功;完成以 HTTP 2xx/trace 為證,不口頭宣布。
開始前讀 **arcrun-mindset** Skill(世界觀)。使用者技術細節交給你,CLI 操作你來做。
開始前讀 **arcrun-mindset** Skill意圖語法+範本+世界觀)。使用者技術細節交給你,CLI 操作你來做。
<!-- arcrun-harness:end -->
+49 -15
View File
@@ -1,26 +1,60 @@
# 用 arcrun 完成這個自動化需求
# 用 Arcrun 完成這個自動化需求
使用者想做一個自動化。你的任務:用 arcrun 做出來,全程不要讓使用者自己寫程式。
使用者想做一個自動化。你的任務:用 Arcrun 做出來,全程不要讓使用者自己寫程式。
⚠️ **不要上網搜 Arcrun 文件**(網路上沒有)。先讀 **arcrun-mindset** Skill,再用 `acr` 指令查現況。
## 鐵則
- **用 arcrun 工作流 / recipe,絕不自己寫 Python/Node 腳本。** 使用者選 arcrun 就是不想要一次性腳本。
- 打外部 API → 寫 recipe`acr recipe push`),不自刻 HTTP client。
- 不自製零件(WASM)—— 零件由 arcrun 維護。你能用的是現有零件 + recipe + 工作流。
- 需要 AI 判斷時你自己做,不要讓工作流回頭呼叫 LLM。
- **用 Arcrun 工作流recipe,絕不自己寫 Python/Node 腳本。** 使用者選 Arcrun 就是不想要一次性腳本。
- **打外部 API → 寫 recipe**`acr recipe push`),不自刻 HTTP client。缺 recipe 就自己補一個,不必問人。
- **不自製零件(WASM**——零件由 Arcrun 維護。你能用的是現有零件 recipe 工作流。
- **需要 AI 判斷時你自己做**,不要讓工作流回頭呼叫 LLM。
- 🔴 **查不到零件就改寫成 `code` 節點 = 腹語術**,禁止。缺 API 寫 recipe、缺能力投稿零件。
## 步驟
1. 先讀 **arcrun-mindset** Skill(世界觀 + 資源去哪取)。
2. 跑 `acr parts` 看零件、`acr auth-recipe list` 看支援的認證。**先查再動手。**
3. 把使用者需求拆成工作流(哪些零件、什麼順序、什麼條件),寫成 `.yaml`
4. 需要 credentialAPI key / token)→ 用 `acr auth-recipe scaffold <service>` 看要哪些,
明確告訴使用者去哪取得、怎麼 `acr creds push`
5. `acr validate` 通過後 `acr push` 部署,告訴使用者 webhook URL / 怎麼 `acr run`
6. 完成給客觀證據(HTTP 2xx / trace),不要只說「做好了」。
## 遇到要暴露資料(對外 webhook)
### 1. 先寫「意圖」,不要先寫 YAML
把使用者的需求寫成一串「誰接誰」(**不必是真實零件名**,用你想得到的名字即可):
```
input >> ON_SUCCESS >> fetch_rows
fetch_rows >> 對每個 row >> notify
```
- 第一個節點固定是 `input`
- 邊有 `ON_SUCCESS``對每個 <變數>`FOREACH)、`ON_TRUE``ON_FALSE`(配 `if_control`)、`ON_BRANCH``branch:`(配 `switch``try_catch`);**沒有** `ON_FAILURE`
- 需要判斷 → 用條件邊(`if_control``ON_TRUE``ON_FALSE`),不要寫 code 判斷
語法細節、四份實跑過的範本、節點命名慣例 → **arcrun-mindset** Skill。
### 2. 丟去查,讓系統告訴你有什麼
`acr search <關鍵字>` 一次掃零件/recipeauth-recipeworkflow
或把意圖串丟 `/cypher/search`,逐節點拿 `found` / `resolved` / `not_found` / `unknown`
- `found``resolved` → **只填 payload**
- `not_found` → 照回應的 `suggestion` 走(缺 API 寫 recipe、缺計算能力投稿零件),
並看 `similar_components``similar_recipes` 有沒有現成能用的
- `unknown`**不代表不存在**,別據此改寫成 code
### 3. 把意圖變成 workflow YAML
節點填上查到的真實零件/recipe + payload。
需要 credential 時:`acr auth-recipe scaffold <service>` 看要哪些,明確告訴使用者去哪取得、怎麼 `acr creds push`
🔑 定義裡只寫 `{{credential.<名字>}}`**真身絕不寫進檔案**。
### 4. 驗證 → 部署 → 給證據
```bash
acr validate <workflow>.yaml # 先驗
acr push <workflow>.yaml # 部署(暴露動作,見下)
acr run <workflow> # 觸發一次
acr logs <workflow> # 看執行紀錄
```
完成要給客觀證據(HTTP 2xx/trace),不要只說「做好了」。
## 遇到要暴露資料(對外 webhook/recipe 投稿)
停下來,明確告訴使用者「這會讓 X 可被外部呼叫」,要他同意。不要替他決定公開。
非互動環境下把完整指令印給使用者自己貼上跑。
## 還沒設定好 arcrun
## 還沒設定好 Arcrun
`acr` 指令不存在或還沒 `acr init`:先帶使用者完成前置設定
(裝 CLI → 拿 Cloudflare 帳號的兩串憑證 → `acr init --self-hosted`)。
拿 Cloudflare 憑證時用白話照抄式引導,不要對使用者講 KV / Worker / R2 等術語。
+19 -5
View File
@@ -66,7 +66,7 @@ if echo "$CMD" | grep -qE "acr (push|recipe push)\b"; then
if echo "$EXEC_PART" | grep -qE "(^|[;&|][[:space:]]*)acr[[:space:]]+(push|recipe[[:space:]]+push)\b"; then
if [ ! -t 0 ] && [ "${ARCRUN_HUMAN_CONFIRMED:-}" != "1" ]; then
block "在非互動環境自動執行暴露動作(acr push / recipe push 會讓東西可被外部呼叫)" \
"交人類在終端機執行(真 TTY 會自動放行)。可把指令完整複製給使用者貼上自己跑:\`acr push <你的 workflow.yaml>\`。或使用者先在對話明示同意後親自於終端機執行。不要替使用者決定公開。"
"交人類在終端機執行(真 TTY 會自動放行)。可把指令完整複製給使用者貼上自己跑:\`acr push <你的 workflow.yaml>\`。或使用者先在對話明示同意後親自於終端機執行。不要替使用者決定公開。(部署前的正路見 arcrun-mindset Skill:先 \`acr validate\`"
fi
fi
fi
@@ -76,15 +76,29 @@ fi
if echo "$CMD" | grep -qE "(^|[;&| ])(python3?|node)[ ]+[^ ]+\.(py|js|mjs|ts)\b"; then
# 排除明顯的測試 / 既有工具呼叫(pytest / npm test / jest 等)降低誤判
if ! echo "$CMD" | grep -qE "(pytest|jest|vitest|npm (run )?test|mocha|\btest_)"; then
remind "偵測到用 python/node 跑腳本。這專案用 arcrun,串服務/自動化不要自刻一次性腳本。" \
"先跑 \`acr parts\` 看有哪些零件,把需求寫成 workflow.yaml 用 \`acr run\`。若這確實不是自動化(例如跑測試/別的工具),忽略本提醒。"
remind "偵測到用 python/node 跑腳本。這專案用 Arcrun,串服務/自動化不要自刻一次性腳本。" \
"讀 arcrun-mindset Skill,先把需求寫成「意圖」串(\`input >> ON_SUCCESS >> <下一步>\`,邊只有 ON_SUCCESS 與「對每個 X」),再用 \`acr search <關鍵字>\` 哪些零件/recipe 存在,最後才寫 workflow.yaml → \`acr validate\` → \`acr run\`。若這確實不是自動化(例如跑測試/別的工具),忽略本提醒。"
fi
fi
# ── 提醒(不硬擋):自寫打固定 API 的 script,而非 recipe ──────────────
if echo "$CMD" | grep -qE "(curl|fetch|requests\.(get|post)|axios).*https?://"; then
remind "偵測到自己打外部 API。arcrun 裡「打固定 endpoint」應寫成 recipe,不自刻 HTTP 呼叫。" \
" \`acr recipe push\` 把這個 API 包成 recipeworkflow 裡用 component 引用它。見 arcrun-mindset Skill。"
remind "偵測到自己打外部 API。Arcrun 裡「打固定 endpoint」應寫成 recipe,不自刻 HTTP 呼叫。" \
" \`acr recipe search <服務名>\` 看有沒有現成的;沒有就自己寫幾行 YAMLcanonical_id/endpoint/method/auth_service)用 \`acr recipe push\` 投稿,workflow 裡用 \`http_request\` 該 recipe 引用它。缺 recipe 就自己補,不必問人。寫法見 arcrun-mindset Skill。"
fi
# ── 提醒(不硬擋):把 code 節點當成缺零件的替代品(「腹語術」)──────────────
# 查詢回 not_found 就改寫成 code = 表面用 Arcrun、實際全寫 JS。這是現世代最常見的走歪。
if [ "$TOOL" = "Write" ] || [ "$TOOL" = "Edit" ] || [ "$TOOL" = "MultiEdit" ]; then
FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // ""')
CONTENT=$(echo "$INPUT" | jq -r '.tool_input.content // .tool_input.new_string // ""')
if echo "$FILE" | grep -qE '\.(ya?ml)$' && echo "$CONTENT" | grep -qE 'component:[[:space:]]*["'"'"']?code\b'; then
# 只在 code 內容看起來在做流程控制/取代零件時提醒(含 if/for/fetch),單純整形不吵
if echo "$CONTENT" | grep -qE '\b(if[[:space:]]*\(|for[[:space:]]*\(|fetch\(|await[[:space:]]+fetch)'; then
remind "workflow 裡的 \`code\` 節點含流程控制/HTTP 呼叫——這可能是「腹語術」(表面用 Arcrun、實際全寫 JS)。" \
"\`code\` 只用於局部整形(例:剝掉 LLM 回應的雜訊、切段落)。缺外部 API → 寫 recipe\`acr recipe push\`);缺計算能力 → 投稿零件 PR;要判斷 → 用條件邊(\`if_control\` 配 \`ON_TRUE\`\`ON_FALSE\`,或 \`switch\`\`try_catch\` 配 \`ON_BRANCH\`),不要寫 code 判斷。每個寫進 code 的 if 都是沒被測過的新 bug。見 arcrun-mindset Skill。"
fi
fi
fi
exit 0
+10
View File
@@ -10,6 +10,16 @@
"timeout": 5
}
]
},
{
"matcher": "Write|Edit|MultiEdit",
"hooks": [
{
"type": "command",
"command": ".claude/hooks/arcrun-guard.sh",
"timeout": 5
}
]
}
]
}
+245 -38
View File
@@ -1,78 +1,285 @@
---
name: arcrun-mindset
description: >-
arcrun 的世界觀 — 用 arcrun 開發自動化時的預設心態 + 資源去哪取。當你(AI 操盤手)要在
arcrun 上做任何事(串服務、處理資料、認證、把東西開放給人用)前讀這個。它讓你做出「方向對」
的選擇、知道資源在哪,避免技術上能跑但架構上錯、或自己重刻 arcrun 已有的東西。
在 Arcrun 上做任何事時使用(用戶說「幫我用 Arcrun 做 X」「用 arcrun 寫一個工作流」「把 X 自動化」)。
Arcrun 是跑在 Cloudflare 上的工作流引擎——你用 `>>` 寫「意圖」,系統告訴你有哪些現成零件與 recipe,
你只填 payload,不必自己寫程式。**不要上網搜 Arcrun 文件**(網路上沒有),也不要自己猜 YAML 格式:
先讀本 skill,再用 `acr` 指令(或 MCP 工具)查現成零件。
涵蓋:意圖工作流語法、四份實跑過的範本、零件 vs recipe 的分別、缺件的兩條路、已知的坑。
---
# arcrun mindset(給 AI 操盤手)
# Arcrun:怎麼寫意圖工作流
你在 arcrun 上幫使用者開發自動化。arcrun 很簡單,簡單到你常會把它想複雜、或退回自己熟悉的
Python/Node 自刻。這份幫你在岔路上選對方向,並告訴你資源在哪
> **你已經配備 Arcrun**(此專案裝了 `acr` CLI,可能另有 `arcrun_*` MCP 工具)。
> **別上網找文件**——網路上沒有 Arcrun 的文件,找到的都是錯的。答案都在本 skill 與 `acr` 指令裡
## 先做這三件(照順序)
1. `acr whoami` — 確認連到哪個帳號(**勿自行 curl 猜帳號 URL**
2. 讀本 skill 下面的語法與範本 → 寫出 `>>` 意圖
3. `acr parts``acr recipe list`(或 `acr search <關鍵字>` 一次掃全部)— 確認零件與 recipe 真的存在
**卡住時**`acr search <關鍵字>` 跨類搜尋;有 MCP 就 `arcrun_get_skill('INDEX')` 拿全館導航。
---
## 0. 一句話世界觀
**arcrun 裡幾乎所有東西都是工作流(workflow)。** 工作流 = 一張紙,寫「用哪些零件、什麼順序、什麼條件」。
你大部分時間在寫紙、改紙,不是在造新零件、也不是自己寫腳本。
**Arcrun 裡幾乎所有東西都是工作流(workflow)。** 工作流 一張紙,寫「用哪些零件、什麼順序、什麼條件」。
你大部分時間在**寫紙、改紙**,不是在造新零件、也不是自己寫腳本。
**Arcrun 只有三種東西,先分清楚就不會做歪:**
| 東西 | 是什麼 | 你能做的 |
|---|---|---|
| **工作流(workflow** | 把零件/recipe 串起來的純文字流程 | **預設就寫這個**,自由寫 |
| **recipe** | 打「一個固定外部 API」的設定(endpointheaderbody 模板) | 自由寫、**而且該投稿**(缺就自己補) |
| **零件(component** | WASM 程式(流程控制/資料處理/`http_request`auth),固定一小套 | **你不自製**,走 PR 由維護者管 |
> **一句話判準**:打一個固定外部 endpoint → 寫 **recipe**;流程控制/資料處理/通用 HTTP → 用既有**零件**;其他 → 寫**工作流**串起來。
---
## 1. 工作流是 default,不要退回自己寫 Python
<!-- 以下正文由 registry/skills/write_intent_workflow.md 於建置期複製而來(單一真相源)。
不要直接編輯本段——改 registry 那份,然後跑 `npm run build:harness`。 -->
使用者選 arcrun,就是不要「每次重刻、跑完即丟」的腳本。所以你的預設順序:
## 1. 意圖工作流的語法
1. **先想能不能用工作流做**(串現有零件 / recipe + 流程控制)。99% 可以。
2. 要打的服務有 HTTP API、但沒有對應 recipe → **寫一個 recipe**http_request + 固定設定 YAML,不用部署、不用審核)。
3. **只有**封閉純邏輯(流程控制 / 資料處理)、現有零件不夠、且值得全 arcrun 重用 → 才考慮零件(而零件走 PR,不是你現在做)。
一串「誰接誰」,每行一個關係:
> 典型走歪:「我先用 Python 測一下」。停。使用者要的是 arcrun 工作流。先 `acr parts` 看有什麼,用工作流串。
```
<節點A> >> <邊> >> <節點B>
```
## 2. 資源去哪取(不要自己重造 arcrun 已有的)
- **節點**=一個步驟。用你想得到的名字(中文可以),**不必是真實零件名**
- **邊**=什麼情況下往下走
## 2. 邊有這些
| 邊 | 意思 | 真例 |
|---|---|---|
| `ON_SUCCESS` | 上一步成功就往下 | `input >> ON_SUCCESS >> prep` |
| `對每個 <變數>` | 上一步產出清單,逐項處理(FOREACH)| `parse_card >> 對每個 block >> post_block` |
| `ON_TRUE` / `ON_FALSE` | 條件成立/不成立各走一條(配 `if_control`| `判斷有沒有新資料 >> ON_TRUE >> 傳到 telegram` |
| `ON_BRANCH``branch:` | 依標籤選路(配 `switch` 每個 case、`try_catch` 的 try/catch| `my_switch >> ON_BRANCH(branch_active) >> 處理啟用` |
### 2.1 條件分支怎麼寫(2026-08-01 起引擎支援)
**需要判斷時,用分支邊,不要寫 `code` 判斷。**
三顆流程控制零件都輸出 `data.branch` 標籤,引擎依標籤選路:
| 零件 | 輸出的標籤 | 接法 |
|---|---|---|
| `if_control` | `"true"` / `"false"` | `ON_TRUE``ON_FALSE` 各一條 |
| `switch` | 你在 `cases[].branch` 取的名字(沒中則 `default_branch`| 每條路一條 `ON_BRANCH`,邊上標 `branch` |
| `try_catch` | `"try"`(沒錯)/`"catch"`(有錯)| 兩條 `ON_BRANCH`,標 `try``catch` |
```
判斷有沒有新資料 >> ON_TRUE >> 傳到 telegram
判斷有沒有新資料 >> ON_FALSE >> 結束
```
中文語意詞亦可:「成立時」=`ON_TRUE`、「否則」=`ON_FALSE`
💡 **不必背**:查零件時回應會附 `branch_hint`(有哪些標籤、用哪些邊型、可照抄的範例),
照著接就對了。
⚠️ 仍然**不要寫 `ON_FAILURE`**(沒有這種邊;要處理失敗用 `try_catch` `ON_BRANCH(catch)`)。
### 2.2 怎麼確認分支真的走對了(**別看不懂就以為壞掉**)
分支工作流「有沒有成功」看兩件事,**不是看某條沒走的路沒有輸出**:
1. **`verdict`**`GET /workflows/<name>/executions?limit=1`
`data.executions[0].verdict === "success"` 就是成功了。
2. **`trace` 裡有沒有出現該走的節點**:走 TRUE 路時 FALSE 路的節點**本來就不該出現**
——**那是正確行為,不是失敗**。
```
# 條件成立 → 只有 true 那條的節點在 trace
{"amount": 5000} → if_control 回 branch="true" → 走 ON_TRUE 那條
{"amount": 100} → if_control 回 branch="false" → 走 ON_FALSE 那條
```
🔴 **實撞(2026-08-01 考試)**:有考生的分支工作流**其實完全正常**
`amount=5000`→true、`amount=100`→false 都對),但它以為「跑不通」而放棄改寫成 code。
**看到只有一條路有輸出=分支正在正確運作**,不要因此判定失敗。
## 3. 第一個節點固定是 `input`
所有真範本都以 `input` 起頭——那是「觸發時帶進來的資料」。
---
## 4. 真範本(照抄結構、改內容)
> 以下四份**全部是實際部署且 `verdict=success` 的 workflow**,不是簡化示範。
> 用 `acr logs <name>`(有 MCP 則 `arcrun_get_workflow(<name>)` 可以拿完整定義。
### A. 最短:取資料 → 處理 (`graph_neighbors`
```
input >> ON_SUCCESS >> fetch_triplets
fetch_triplets >> ON_SUCCESS >> bfs_neighbors
```
### B. 長鏈:多次查詢 → 組裝 → 問 AI → 收尾 (`rag_chat`
```
input >> ON_SUCCESS >> prep
prep >> ON_SUCCESS >> kw_search
kw_search >> ON_SUCCESS >> sem_search
sem_search >> ON_SUCCESS >> fetch_triplets
fetch_triplets >> ON_SUCCESS >> fetch_blocks_a
fetch_blocks_a >> ON_SUCCESS >> assemble
assemble >> ON_SUCCESS >> ask_llm
ask_llm >> ON_SUCCESS >> finalize
```
`prep` 前處理/`assemble` 組 prompt`finalize` 收拾回應——三個常見的整形節點。
### C. 一節點分岔兩條 FOREACH `rag_ingest_card`
```
input >> ON_SUCCESS >> parse_card
parse_card >> 對每個 block >> post_block
parse_card >> 對每個 rel >> post_triplet
```
同一節點可有多條出邊,各自處理不同清單。
### D. 混合:直線 兩段 FOREACH (`rag_takedown_direct`
```
input >> ON_SUCCESS >> prep
prep >> ON_SUCCESS >> list_dead_blocks
list_dead_blocks >> ON_SUCCESS >> build_deprecations
build_deprecations >> 對每個 dead_entry >> deprecate_entry
build_deprecations >> ON_SUCCESS >> list_triplets
list_triplets >> ON_SUCCESS >> pick_dead_triplets
pick_dead_triplets >> 對每個 dead_record >> deprecate_triplet
```
`build_deprecations` 同時有 FOREACH 出邊與 `ON_SUCCESS` 出邊——
前者處理清單、後者繼續主線。
---
## 5. 節點怎麼命名(照真範本的模式,查詢較容易媒合)
| 意圖 | 模式 | 真例 |
|---|---|---|
| 前處理/正規化 | `prep` | `rag_chat.prep` |
| 取一批資料 | `fetch_*``list_*` | `fetch_triplets``list_dead_blocks` |
| 搜尋 | `*_search` | `kw_search``sem_search` |
| 解析/切塊 | `parse_*` | `parse_card` |
| 寫入 | `post_*` | `post_block``post_triplet` |
| 組裝 | `assemble``build_*` | `assemble``build_deprecations` |
| 問 AI | `ask_llm` | `rag_chat.ask_llm` |
| 收尾整形 | `finalize` | `rag_chat.finalize` |
---
## 6. 寫完一定要查(**不要直接部署**)
```bash
curl -s -X POST https://arcrun-cypher-executor.<subdomain>.workers.dev/cypher/search \
-H 'content-type: application/json' -H 'X-Arcrun-API-Key: <namespace>' \
-d '{"triplets":["input >> ON_SUCCESS >> fetch_data","fetch_data >> ON_SUCCESS >> notify"]}'
```
回應的每個節點會有:
| status | 意思 | 你該做什麼 |
|---|---|---|
| `found` | 有這個節點。`source: component``input_schema`(怎麼填 payload)與 `success_rate``source: recipe` 附 description/endpoint | **只填 payload** |
| `not_found` | **兩庫(零件 registry+recipe 庫)都查過,確定沒有** | 照 `suggestion` 欄走:缺 API → 寫 recipeskill `write_recipe`);缺計算能力 → 投稿零件 PR(skill `add_new_wasm_component`)。`similar_components`/`similar_recipes` 是相近候選——先看有沒有現成的能直接用 |
| `unknown` | 查不到 registry | **不代表不存在**,別據此改寫成 code |
> 註(2026-07-31):`/cypher/search` 曾對任何節點名都回假 `found`,已修為真查兩庫。
> 舊實例(未更新部署)仍可能假 found——status 可信度以該實例部署版本為準。
---
## 7. 常犯的錯
1. **用不存在的邊**`ON_FAILURE`)→ 沒有這種邊;要處理失敗用 `try_catch` `ON_BRANCH(catch)`
⚠️ `ON_TRUE``ON_FALSE``ON_BRANCH` **是存在的**2026-08-01 起),見 §2.1——
本行以前寫「ON_TRUE 不存在」是舊世代,已更正
2. **第一個節點不是 `input`**
3. **把 recipe 當零件寫**——`telegram_send``gmail``kbdb_get`**recipe** 不是零件
→ 寫成 `http_request` 該 recipe
4. 🔴 **查詢回 `not_found` 就改寫成 `code` 節點**
→ 那叫「腹語術」(表面用 Arcrun、實際全寫 JS)。正解:缺 API 寫 recipe、缺能力投稿零件。
`code` 只用在**局部整形**(例:剝掉 LLM 回應的雜訊),不用來取代零件與流程控制。
---
## 8. 相關
- 完整版指引與十題考卷(含 haiku 實測 10/10):
頂層 repo `system-dev/docs/3-specs/arcrun-usable/`
- 下一步該讀哪支 skill(需 MCP):`arcrun_list_skills()`
- 定期掃資料 → `build_watcher_workflow`
- RAG 檢索問答 → `rag_with_arcrun`
- workflow 卡住不動 → `debug_paused_workflow`
---
## 9. 資源去哪取(不要自己重造 Arcrun 已有的)
| 你想知道 | 跑這個 |
|---|---|
| 有哪些零件可用 | `acr parts` |
| 某零件的設定範本 | `acr parts scaffold <name>` |
| 有哪些 recipe | `acr recipe list``acr recipe search <關鍵字>` |
| 支援哪些服務的認證 | `acr auth-recipe list` |
| 某服務認證要哪些 credential + 範例 | `acr auth-recipe scaffold <service>` |
| 已上傳的 recipe | `acr recipe list` |
| 某服務認證要哪些 credential 範例 | `acr auth-recipe scaffold <service>` |
| **一次掃全部**(零件/recipeauth-recipeworkflow | `acr search <關鍵字>` |
| 已部署的 workflow | `acr list` |
| 某次執行為什麼失敗 | `acr logs <workflow>` |
| 工作流語法、指令 | `acr --help` |
**先查再動手**——arcrun 多半已經有你要的零件 / recipe / 認證,不要自刻。
**先查再動手**——Arcrun 多半已經有你要的零件recipe認證,不要自刻。
## 3. arcrun 是你(AI)用的工具,不是工具回頭呼叫 AI
## 10. 做出來以後:驗證 → 部署
需要智慧判斷 / 自然語言轉換時,**你自己做**,再呼叫工作流執行確定性的下一步。
**不要在工作流中間放零件回頭呼叫 LLM**。arcrun 的大腦就是操盤的你。
```bash
acr validate <workflow>.yaml # 先驗,別直接部署
acr push <workflow>.yaml # 部署(暴露動作,見 §12)
acr run <workflow> # 觸發一次,看實際結果
acr logs <workflow> # 看執行紀錄/失敗原因
```
## 4. arcrun 不替你做授權判斷
需要 credentialAPI keytoken)時:`acr auth-recipe scaffold <service>` 看要哪些,
明確告訴使用者去哪取得、怎麼 `acr creds push`
🔑 **金鑰只拿名字**workflowrecipe 裡只寫 `{{credential.<名字>}}`
**真身絕不寫進定義檔**(執行前才由系統回填)。
API 打不打得通由發 key 的服務決定。401/403 是對方服務在行使授權,**不是 arcrun 的 bug、不是你做錯**。
不要在 arcrun 裡建「允許/禁止某 endpoint」的二次授權清單。
## 11. Arcrun 是你(AI)用的工具,不是工具回頭呼叫 AI
## 5. 把東西開放給別人用 = 要使用者明示同意
需要智慧判斷/自然語言轉換時,**你自己做**,再呼叫工作流執行確定性的下一步。
**不要在工作流中間放零件回頭呼叫 LLM**——Arcrun 的大腦就是操盤的你。
(唯一例外:`ask_llm` 這種「內容生成本身就是流程的一步」,見範本 B。)
部署對外 webhook、push recipe 會讓資料/能力**可被外部呼叫**(暴露面):
## 12. 把東西開放給別人用 = 要使用者明示同意
`acr push`(部署 workflow)與 `acr recipe push`(投稿 recipe)會讓資料/能力**可被外部呼叫**:
- 停下來,明確告訴使用者「這會讓 X 可被外部呼叫」,要他同意。**不替他決定公開。**
- 非互動環境(你直跑)遇到 → 停,要人類確認,絕不自己塞 confirm 假裝同意。
- arcrun 可提供保護(要求呼叫者帶 key / 限流)——提醒使用者。
- 非互動環境(你直跑)遇到 → 停,把完整指令印給使用者自己貼上跑,絕不自己塞 confirm 假裝同意。
- Arcrun 可提供保護(要求呼叫者帶 key限流)——提醒使用者。
## 6. 誠實(最重要)
## 13. Arcrun 不替你做授權判斷
API 打不打得通由發 key 的服務決定。401/403 是對方服務在行使授權,**不是 Arcrun 的 bug、不是你做錯**。
不要在 Arcrun 裡建「允許/禁止某 endpoint」的二次授權清單。
## 14. 誠實(最重要)
- **不假綠**:沒打通就誠實說。缺 credential 打不到 2xx → 標「未驗收:缺 X」,不 mock 充綠燈。
- **不假裝防偽 / 不代替人類確認**有風險的動作(暴露資料)。
- **完成 = 客觀證據**HTTP 2xx + trace),不是口頭「做好了」。
- **不假裝防偽不代替人類確認**有風險的動作(暴露資料)。
- **完成 客觀證據**HTTP 2xx trace),不是口頭「做好了」。
---
## 怎麼用這份 mindset
## 動手前的自檢清單
每次準備動手,先過一遍:
1. 這能用工作流 / recipe 做嗎?(多半能 → 別自己寫 Python、別造零件)
2. 我查過 `acr parts` / `acr auth-recipe` 了嗎?(arcrun 可能已有
3. 我是不是讓工作流回頭呼叫 AI?(是 → 改成我自己做)
4. 這動作會把資料開放給別人嗎?(會 → 要使用者明示同意)
5. 我有沒有假裝(假綠 / 假防偽 / 代替人類確認)?(有 → 停,誠實標明)
1. 我把意圖寫成 `>>` 串了嗎?(還是直接跳去寫 YAML/寫程式)
2. 我查過 `acr search` / `acr parts` / `acr recipe list` 了嗎?
3. 查詢回 `not_found` 時,我走的是 recipe/零件 PR 兩條路,**還是偷偷改寫成 `code`**?(後者=腹語術
4. 我是不是讓工作流回頭呼叫 AI 做判斷?(是 → 改成我自己做)
5. 這動作會把資料開放給別人嗎?(會 → 要使用者明示同意)
6. 我有沒有假裝(假綠/假防偽/代替人類確認)?(有 → 停,誠實標明)
@@ -0,0 +1,41 @@
---
name: arcrun-mindset
description: >-
在 Arcrun 上做任何事時使用(用戶說「幫我用 Arcrun 做 X」「用 arcrun 寫一個工作流」「把 X 自動化」)。
Arcrun 是跑在 Cloudflare 上的工作流引擎——你用 `>>` 寫「意圖」,系統告訴你有哪些現成零件與 recipe,
你只填 payload,不必自己寫程式。**不要上網搜 Arcrun 文件**(網路上沒有),也不要自己猜 YAML 格式:
先讀本 skill,再用 `acr` 指令(或 MCP 工具)查現成零件。
涵蓋:意圖工作流語法、四份實跑過的範本、零件 vs recipe 的分別、缺件的兩條路、已知的坑。
---
# Arcrun:怎麼寫意圖工作流
> **你已經配備 Arcrun**(此專案裝了 `acr` CLI,可能另有 `arcrun_*` MCP 工具)。
> **別上網找文件**——網路上沒有 Arcrun 的文件,找到的都是錯的。答案都在本 skill 與 `acr` 指令裡。
## 先做這三件(照順序)
1. `acr whoami` — 確認連到哪個帳號(**勿自行 curl 猜帳號 URL**
2. 讀本 skill 下面的語法與範本 → 寫出 `>>` 意圖
3. `acr parts``acr recipe list`(或 `acr search <關鍵字>` 一次掃全部)— 確認零件與 recipe 真的存在
**卡住時**`acr search <關鍵字>` 跨類搜尋;有 MCP 就 `arcrun_get_skill('INDEX')` 拿全館導航。
---
## 0. 一句話世界觀
**Arcrun 裡幾乎所有東西都是工作流(workflow)。** 工作流 = 一張紙,寫「用哪些零件、什麼順序、什麼條件」。
你大部分時間在**寫紙、改紙**,不是在造新零件、也不是自己寫腳本。
**Arcrun 只有三種東西,先分清楚就不會做歪:**
| 東西 | 是什麼 | 你能做的 |
|---|---|---|
| **工作流(workflow** | 把零件/recipe 串起來的純文字流程 | **預設就寫這個**,自由寫 |
| **recipe** | 打「一個固定外部 API」的設定(endpointheaderbody 模板) | 自由寫、**而且該投稿**(缺就自己補) |
| **零件(component** | WASM 程式(流程控制/資料處理/`http_request`auth),固定一小套 | **你不自製**,走 PR 由維護者管 |
> **一句話判準**:打一個固定外部 endpoint → 寫 **recipe**;流程控制/資料處理/通用 HTTP → 用既有**零件**;其他 → 寫**工作流**串起來。
---
@@ -0,0 +1,67 @@
---
## 9. 資源去哪取(不要自己重造 Arcrun 已有的)
| 你想知道 | 跑這個 |
|---|---|
| 有哪些零件可用 | `acr parts` |
| 某零件的設定範本 | `acr parts scaffold <name>` |
| 有哪些 recipe | `acr recipe list``acr recipe search <關鍵字>` |
| 支援哪些服務的認證 | `acr auth-recipe list` |
| 某服務認證要哪些 credential 範例 | `acr auth-recipe scaffold <service>` |
| **一次掃全部**(零件/recipeauth-recipeworkflow | `acr search <關鍵字>` |
| 已部署的 workflow | `acr list` |
| 某次執行為什麼失敗 | `acr logs <workflow>` |
| 工作流語法、指令 | `acr --help` |
**先查再動手**——Arcrun 多半已經有你要的零件/recipe/認證,不要自刻。
## 10. 做出來以後:驗證 → 部署
```bash
acr validate <workflow>.yaml # 先驗,別直接部署
acr push <workflow>.yaml # 部署(暴露動作,見 §12)
acr run <workflow> # 觸發一次,看實際結果
acr logs <workflow> # 看執行紀錄/失敗原因
```
需要 credentialAPI keytoken)時:`acr auth-recipe scaffold <service>` 看要哪些,
明確告訴使用者去哪取得、怎麼 `acr creds push`。
🔑 **金鑰只拿名字**workflowrecipe 裡只寫 `{{credential.<名字>}}`
**真身絕不寫進定義檔**(執行前才由系統回填)。
## 11. Arcrun 是你(AI)用的工具,不是工具回頭呼叫 AI
需要智慧判斷/自然語言轉換時,**你自己做**,再呼叫工作流執行確定性的下一步。
**不要在工作流中間放零件回頭呼叫 LLM**——Arcrun 的大腦就是操盤的你。
(唯一例外:`ask_llm` 這種「內容生成本身就是流程的一步」,見範本 B。)
## 12. 把東西開放給別人用 = 要使用者明示同意
`acr push`(部署 workflow)與 `acr recipe push`(投稿 recipe)會讓資料/能力**可被外部呼叫**:
- 停下來,明確告訴使用者「這會讓 X 可被外部呼叫」,要他同意。**不替他決定公開。**
- 非互動環境(你直跑)遇到 → 停,把完整指令印給使用者自己貼上跑,絕不自己塞 confirm 假裝同意。
- Arcrun 可提供保護(要求呼叫者帶 key/限流)——提醒使用者。
## 13. Arcrun 不替你做授權判斷
API 打不打得通由發 key 的服務決定。401/403 是對方服務在行使授權,**不是 Arcrun 的 bug、不是你做錯**。
不要在 Arcrun 裡建「允許/禁止某 endpoint」的二次授權清單。
## 14. 誠實(最重要)
- **不假綠**:沒打通就誠實說。缺 credential 打不到 2xx → 標「未驗收:缺 X」,不 mock 充綠燈。
- **不假裝防偽/不代替人類確認**有風險的動作(暴露資料)。
- **完成 客觀證據**HTTP 2xx + trace),不是口頭「做好了」。
---
## 動手前的自檢清單
1. 我把意圖寫成 `>>` 串了嗎?(還是直接跳去寫 YAML/寫程式)
2. 我查過 `acr search` / `acr parts` / `acr recipe list` 了嗎?
3. 查詢回 `not_found` 時,我走的是 recipe/零件 PR 兩條路,**還是偷偷改寫成 `code`**?(後者=腹語術)
4. 我是不是讓工作流回頭呼叫 AI 做判斷?(是 → 改成我自己做)
5. 這動作會把資料開放給別人嗎?(會 → 要使用者明示同意)
6. 我有沒有假裝(假綠/假防偽/代替人類確認)?(有 → 停,誠實標明)
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "arcrun",
"version": "1.3.13",
"version": "1.3.14",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "arcrun",
"version": "1.3.13",
"version": "1.3.14",
"license": "MIT",
"dependencies": {
"chalk": "^5.3.0",
+6 -3
View File
@@ -8,9 +8,12 @@
"main": "./dist/index.js",
"type": "module",
"scripts": {
"build": "tsc",
"build": "npm run build:harness && npm run check:harness && npm run check:rule && tsc",
"build:harness": "node scripts/build-harness-skill.mjs",
"check:harness": "node scripts/check-harness-generation.mjs",
"check:rule": "node ../scripts/sync-resource-rule.mjs --check",
"dev": "tsc --watch",
"test": "node --test \"tests/**/*.test.ts\"",
"test": "npm run check:rule && node --experimental-transform-types --import ./tests/register-ts-hooks.mjs --test \"tests/**/*.test.ts\"",
"prepublishOnly": "npm run build && chmod +x dist/index.js"
},
"dependencies": {
@@ -42,6 +45,6 @@
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/uncle6me-web/Arcrun.git"
"url": "git+https://github.com/youlinhsieh/Arcrun.git"
}
}
+64
View File
@@ -0,0 +1,64 @@
#!/usr/bin/env node
/**
* build-harness-skill.mjs registry/skills/ 組出 harness arcrun-mindset SKILL.md
*
* 為什麼是建置期複製而不是人工維護兩份
* `registry/skills/write_intent_workflow.md` 是意圖語法的**單一真相源**它同時是
* MCP `arcrun_get_skill()` 回給雲端 AI 的內容harness skill 若人工再抄一份
* 兩份必然漂移2026-07-31 實錄harness 那份停在上一代grep意圖>>= 0 命中
* 只講世界觀害新裝的用戶 AI 學不到 `>>`
*
* 作法harness skill = 三段拼接
* SKILL.md.head harness 專屬frontmatterCLI 入口三種東西的分型
* registry write_intent_workflow.md 正文 單一真相源只此一份被維護
* SKILL.md.tail harness 專屬acr 指令表暴露同意誠實鐵律
*
* 為什麼不用 symlink / npm 打包直接引用npm `files` 只收 `harness/`
* registry/ 不進套件symlink npm pack Windows 上不可靠建置期複製最單純
*
* 產物 `SKILL.md` ** commit repo**npm 套件裝的是它不會跑 build
* check-harness-generation.mjs 驗證它與 registry 沒有漂移
*/
import { readFileSync, writeFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
const here = dirname(fileURLToPath(import.meta.url)); // cli/scripts
const repoRoot = join(here, '..', '..'); // repo 根
const skillDir = join(here, '..', 'harness', 'skills', 'arcrun-mindset');
const registrySkill = join(repoRoot, 'registry', 'skills', 'write_intent_workflow.md');
const head = readFileSync(join(skillDir, 'SKILL.md.head'), 'utf8').trimEnd();
const tail = readFileSync(join(skillDir, 'SKILL.md.tail'), 'utf8').trimEnd();
const body = readFileSync(registrySkill, 'utf8');
// 取 registry skill 的正文:去掉它自己的 H1 標題與「何時用這個 skill」那段
// harness 的 head 已用 CLI 語境寫過入口),從第一個 `## 1.` 章節起收。
const idx = body.indexOf('## 1. 意圖工作流的語法');
if (idx < 0) {
console.error('❌ registry/skills/write_intent_workflow.md 找不到「## 1. 意圖工作流的語法」章節;');
console.error(' registry skill 結構變了 → 請同步更新 cli/scripts/build-harness-skill.mjs 的取段規則。');
process.exit(1);
}
const middle = body
.slice(idx)
// registry 版把 MCP 工具當預設介面;harness 裝在有 acr CLI 的專案 → 補上 CLI 等價指令
.replace(/`arcrun_get_workflow\(<name>\)`/g, '`acr logs <name>`(有 MCP 則 `arcrun_get_workflow(<name>)`')
.replace(/`arcrun_list_components` \/ `arcrun_search_components`/g, '`acr parts` / `acr search`')
.replace(/下一步該讀哪支 skill`arcrun_list_skills\(\)`/g, '下一步該讀哪支 skill(需 MCP):`arcrun_list_skills()`')
.trimEnd();
const out = [
head,
'',
'<!-- 以下正文由 registry/skills/write_intent_workflow.md 於建置期複製而來(單一真相源)。',
' 不要直接編輯本段——改 registry 那份,然後跑 `npm run build:harness`。 -->',
'',
middle,
'',
tail,
'',
].join('\n');
writeFileSync(join(skillDir, 'SKILL.md'), out, 'utf8');
console.log(`✓ harness skill 已由 registry 重建:${out.length} bytes`);
+136
View File
@@ -0,0 +1,136 @@
#!/usr/bin/env node
/**
* check-harness-generation.mjs 世代閘harness 內容脫節就讓 build/publish 失敗
*
* 為什麼要這道閘
* 2026-07-31 實錄`acr install-harness` 的管道一直是好的但它鋪出去的**內容停在上一代**
* harness skill grep意圖>>= 0 命中只講世界觀管道綠燈交付物過時
* 沒有任何機械檢查會抱怨 世代脫節可以無聲存在好幾個月
*
* 這道閘檢查四件交付物的現世代指紋缺指紋 = exit 1擋掉 build npm publish
* 指紋要挑上一代絕不會有現世代一定有的字串不是隨便的關鍵字
*/
import { readFileSync, existsSync, statSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
import { execFileSync } from 'node:child_process';
const here = dirname(fileURLToPath(import.meta.url));
const harness = join(here, '..', 'harness');
const repoRoot = join(here, '..', '..');
/** @type {{file: string, must: [string, string][], mustNot?: [string,string][]}[]} */
const CHECKS = [
{
file: 'skills/arcrun-mindset/SKILL.md',
must: [
['>>', '意圖語法(`A >> 邊 >> B`)——步驟 1 的核心教材'],
['ON_SUCCESS', '合法邊之一'],
['對每個', 'FOREACH 邊(十題裡有四題要用)'],
['input', '第一個節點固定是 input'],
['not_found', '現世代查詢狀態(舊版寫 missing/假 found'],
['腹語術', '缺件不准改寫成 code 的紅線'],
['recipe', '零件 vs recipe 分型'],
// 條件邊自 2026-08-01 起引擎已支援(cypher-executor/src/graph-executor.ts
// case 'ON_TRUE'/'ON_FALSE'/'ON_BRANCH'31 個測試全過)。教材該教會怎麼用,
// 不是教「不存在」——這條 must 同時防「哪天又被改回舊世代說法」的回歸。
['ON_TRUE', '條件邊(配 if_control)自 2026-08-01 起引擎已支援,教材須教會用法'],
],
mustNot: [
// ON_FAILURE 才是真的不存在(VALID_EDGE_TYPES 只有 ON_FAIL,見
// cypher-executor/src/lib/constants.ts)。只准出現在「教它不存在」的脈絡。
// 2026-08-10 修正:這道閘原本擋的是 ON_TRUE——但 ON_TRUE/ON_FALSE/ON_BRANCH
// 已是引擎現世代能力,正確教材反而被這道閘擋下,是閘的判準過時了,不是教材寫錯。
['ON_FAILURE', '引擎沒有這種邊(只有 ON_FAIL);教材不該把它教成可用的邊', /不要寫|不存在|沒有這種|❌|非法/],
],
},
{
file: 'CLAUDE.block.md',
must: [
['>>', '意圖語法要在 CLAUDE.md 就先亮相'],
['not_found', '缺件兩條路的觸發點'],
],
},
{
file: 'commands/arcrun.md',
must: [
['>>', '/arcrun 的第一步就該是寫意圖'],
['acr search', '現世代的跨類搜尋指令'],
],
},
{
file: 'hooks/arcrun-guard.sh',
must: [
['arcrun-mindset', 'hook 被擋下時要把 AI 導向 skill,而不是叫它去翻 repo 文件'],
['>>', 'hook 的正路提示要提到意圖語法'],
],
},
];
let fail = 0;
const say = (s) => console.log(s);
say('\n 世代閘:檢查 harness 交付物是否為現世代內容\n');
for (const c of CHECKS) {
const p = join(harness, c.file);
if (!existsSync(p)) {
say(`${c.file} — 檔案不存在`);
fail++;
continue;
}
const text = readFileSync(p, 'utf8');
const missing = c.must.filter(([needle]) => !text.includes(needle));
const badNot = (c.mustNot ?? []).filter(([needle, , allowIfNear]) => {
if (!text.includes(needle)) return false;
if (!allowIfNear) return true;
// 允許「在教『不要用』的脈絡裡」出現:看該字串所在行是否有豁免詞
return !text
.split('\n')
.filter((l) => l.includes(needle))
.every((l) => allowIfNear.test(l));
});
if (missing.length === 0 && badNot.length === 0) {
say(`${c.file}`);
} else {
fail++;
say(`${c.file}`);
for (const [needle, why] of missing) say(` 缺指紋「${needle}」— ${why}`);
for (const [needle, why] of badNot) say(` 不該出現「${needle}」— ${why}`);
}
}
// harness skill 必須是由 registry 重建的最新版(防「改了 registry 忘了重跑 build」)
const skillPath = join(harness, 'skills', 'arcrun-mindset', 'SKILL.md');
const registrySkill = join(repoRoot, 'registry', 'skills', 'write_intent_workflow.md');
if (existsSync(skillPath) && existsSync(registrySkill)) {
try {
execFileSync(process.execPath, [join(here, 'build-harness-skill.mjs')], { stdio: 'pipe' });
const rebuilt = readFileSync(skillPath, 'utf8');
const before = statSync(skillPath); // 重建後內容即為期望值
void before;
// 重建是冪等的:若重建後與 git 中的版本不同,git diff 會在 CI 顯示;
// 這裡直接比對「重建結果是否含 registry 當前的關鍵段落」
const reg = readFileSync(registrySkill, 'utf8');
const marker = reg.includes('## 7. 常犯的錯') ? '## 7. 常犯的錯' : null;
if (marker && !rebuilt.includes(marker)) {
say(` ❌ harness skill 與 registry 漂移:registry 有「${marker}」但重建產物沒有`);
fail++;
} else {
say(' ✓ harness skill 與 registry/skills/write_intent_workflow.md 同步');
}
} catch (e) {
say(` ❌ 無法由 registry 重建 harness skill${e.message}`);
fail++;
}
}
say('');
if (fail) {
say(` 🔴 世代閘擋下(${fail} 項)。harness 交付的內容落後於現世代。`);
say(' 修法:改 registry/skills/write_intent_workflow.md(單一真相源)或對應的');
say(' cli/harness/ 檔案,然後跑 `npm run build:harness` 重建,再跑本檢查。\n');
process.exit(1);
}
say(' ✅ 世代閘通過:四件交付物都帶現世代指紋\n');
+1 -1
View File
@@ -18,7 +18,7 @@ const SOURCE_LABEL: Record<ConfigSource, string> = {
};
/** 敏感欄位只印前綴,避免把 token 完整印到終端 / log。*/
const SENSITIVE = new Set(['api_key', 'encryption_key', 'cf_api_token']);
const SENSITIVE = new Set(['api_key', 'cf_api_token']);
function mask(field: string, value: string): string {
if (SENSITIVE.has(field) && value.length > 8) return `${value.slice(0, 8)}`;

Some files were not shown because too many files have changed in this diff Show More