commit efe8e165cf942027e84073a8b85e05fd77e3916e Author: richblack Date: Sun Jun 14 20:59:41 2026 +0800 feat: KBDB-graph 插件獨立 — 全面改寫成走基本盤 API(API-as-Wall) 按 leo 鐵律(2026-06-14)把插件從「直接 SQL 操作基本盤表」改寫成 「只透過基本盤 arcrun/kbdb HTTP API 讀寫」。零建表、零 migration、零 SQL。 - 新增 src/lib/kbdb-client.ts:唯一對外通道,封裝 entries/templates/records API - 新增 src/lib/templates.ts:triplet/entity template 定義(替代建表) - 改寫 21 個違規 action(triplet/graph/entity/search)→ 走 client,圖在插件層記憶體組裝 - 移除所有 migrations、D1/Vectorize/AI 綁定;embedding/語意搜尋歸基本盤 optional 模組 - index.ts 只掛 triplets/graph/entities/search 路由;基本盤路由歸 arcrun/kbdb - 測試改走 mock client(純 node);裁剪 CLAUDE.md 只留 graph 插件 + 鐵律 - 修正 SDD design.md「讀現狀推翻鐵律」的錯誤判斷(共用 D1 → API-as-Wall) Co-Authored-By: Claude Opus 4.8 (1M context) diff --git a/.claude/commands/sdd-check.md b/.claude/commands/sdd-check.md new file mode 100644 index 0000000..54c97d2 --- /dev/null +++ b/.claude/commands/sdd-check.md @@ -0,0 +1,55 @@ +# /sdd-check — 確認當前任務有沒有對應 SDD + +動手前執行。確保 CC 有全局觀,不會在沒有設計文件的情況下猛衝。 + +--- + +## 執行流程 + +### 第一步:理解任務 + +確認使用者要做什麼: +- 涉及哪個子系統? +- 是新功能還是修改現有功能? +- 影響範圍? + +### 第二步:尋找對應 SDD + +在 `docs/3-specs/` 下尋找對應的子系統目錄,確認有沒有: +- `design.md`(設計文件) +- `tasks.md`(任務清單) + +### 第三步:根據結果回應 + +**情況 A:找到對應 SDD** +``` +✅ 找到 SDD:docs/3-specs/[子系統]/ +📋 design.md:[確認] +📋 tasks.md:[確認,列出相關 task] +🎯 對應 task:[編號和描述] +繼續嗎? +``` + +**情況 B:找不到 SDD,任務明確** +``` +⚠️ 找不到對應 SDD +任務:[描述] +建議在 docs/3-specs/[建議子系統名]/ 建立 SDD + +要我幫你起草 design.md 嗎?(需要你確認後才動手) +``` + +**情況 C:找不到 SDD,任務模糊** +``` +⚠️ 找不到對應 SDD,而且任務範圍不夠清楚 +請先回答: +1. 這個功能屬於哪個子系統? +2. 完成的標準是什麼? +3. 有沒有不能動的邊界? +``` + +### 注意 + +- 找不到 SDD **不等於可以直接動手** +- 小修改(修 bug、改文字)可以豁免,但要明確說「這是小修改,範圍是 X」 +- 新功能、架構變動、跨模組的修改 → 一定要有 SDD diff --git a/.claude/commands/wiki-capture.md b/.claude/commands/wiki-capture.md new file mode 100644 index 0000000..5b00230 --- /dev/null +++ b/.claude/commands/wiki-capture.md @@ -0,0 +1,61 @@ +# /wiki-capture — 把對話結論存進 wiki + +把這次對話中產生的決策、誤解釐清、或重要結論存入 wiki。 +解決「討論過了但知識消失」的問題。 + +--- + +## 執行流程 + +### 第一步:辨識對話中的可記錄內容 + +掃描當前對話,找出: + +| 類型 | 判斷標準 | 存到哪 | +|------|---------|-------| +| 架構決策 | 「為什麼選A不選B」「我們決定用X」 | `decisions-summary.md` + `docs/2-architecture/decisions/` | +| CC 的誤解被糾正 | CC 說了某件事,使用者說「不是,是...」 | `mistakes.md` | +| 重要狀態更新 | 完成了某件事、阻擋了某件事 | `status.md` | +| 技術發現 | 踩到坑、找到解法、重要行為確認 | `mistakes.md` 或對應 SDD | + +### 第二步:列出清單給使用者確認 + +格式: +``` +這次對話我整理了以下內容要存入 wiki: + +1. [MISTAKE] CC 誤解了 X,正確是 Y +2. [DECISION] 決定用 A 不用 B,原因是 C +3. [STATUS] 完成了 task 2.3,下一步是 2.4 + +確認後存入,有需要修改的嗎? +``` + +**停下來等確認。** + +### 第三步:寫入 + +確認後,依照格式寫入對應檔案: + +**mistakes.md 格式:** +``` +⚠️ MISTAKE: [錯誤描述] + 症狀: [CC 的表現] + 正確做法: [應該怎麼做] + 原因: [背景] + 日期: [YYYY-MM-DD] +``` + +**decisions-summary.md 格式:** +``` +## [主題] — [YYYY-MM-DD] +**結論**:[一句話] +**原因**:[簡短說明] +**詳細**:docs/2-architecture/decisions/[檔名] +``` + +重大決策同時在 `docs/2-architecture/decisions/` 建立 ADR 檔案。 + +### 第四步:確認 + +告知存到哪些檔案,共幾條記錄。 diff --git a/.claude/commands/wiki-init.md b/.claude/commands/wiki-init.md new file mode 100644 index 0000000..4cb7bfd --- /dev/null +++ b/.claude/commands/wiki-init.md @@ -0,0 +1,77 @@ +# /wiki-init — 初始化或接入 LLM Wiki 系統 + +初始化這個專案的 LLM Wiki 記憶系統。 +新專案建立空白結構,已有專案掃描現有文件並建立 wiki。 + +--- + +## 執行流程 + +### 第一步:偵測專案狀態 + +檢查以下項目,判斷是新專案還是已有專案: +- 根目錄有沒有 `.claude/wiki/` +- 根目錄有沒有 `docs/` +- 有沒有散落的 `.md` 檔案 + +**新專案**(幾乎空的)→ 直接建立結構,跳到第三步 +**已有專案**(有文件)→ 執行第二步 + +### 第二步:已有專案的掃描(已有專案才執行) + +1. 遞迴找出所有 `.md` 檔案 +2. 對每個檔案標注建議位置和信心度 +3. 列出清單給使用者確認,**停下來等確認** + +分類規則: +``` +有明確子系統 + 設計內容 → docs/3-specs/[子系統]/ +解釋為什麼做某個決定 → docs/2-architecture/decisions/ +說明怎麼操作 → docs/4-guides/ +記錄發生過的事 → docs/5-records/ +給外部使用者看的 → docs/6-user/ +不確定 → 列為「待確認」,問使用者 +``` + +### 第三步:建立缺少的結構 + +只建立不存在的目錄和檔案,**已有的一律不動**: + +目錄: +``` +docs/{1-vision,2-architecture/decisions,3-specs,4-guides,5-records/{incidents,test-reports},6-user} +.claude/wiki/ +``` + +檔案(不存在才建): +- `.claude/wiki/INDEX.md` +- `.claude/wiki/status.md` +- `.claude/wiki/mistakes.md` +- `.claude/wiki/decisions-summary.md` +- `docs/README.md` + +### 第四步:訪談(每次一個問題) + +依序問: +1. 這個專案做什麼?(一句話) +2. 有哪些絕對不能違反的限制?(技術棧、架構原則等) +3. 現在進行到哪個階段? +4. 有沒有 CC 曾經犯過的錯要先記下來? + +把答案填進 `CLAUDE.md`(如果存在)或建立新的。 + +### 第五步:已有專案的文件歸檔 + +(第二步確認後執行) + +按照確認好的分類移動檔案,完成後更新 `CLAUDE.md` 的路徑引用。 + +### 第六步:完成報告 + +告知: +``` +✅ wiki-init 完成 +建立了:[列出新建的目錄和檔案] +跳過了:[列出已有因此不動的] +下一步:用 /wiki-capture 把重要決策存進 wiki +``` diff --git a/.claude/commands/wiki-update.md b/.claude/commands/wiki-update.md new file mode 100644 index 0000000..1d5ecfe --- /dev/null +++ b/.claude/commands/wiki-update.md @@ -0,0 +1,50 @@ +# /wiki-update — Session 結束,更新狀態 + +每次 session 結束時執行。更新 status.md,確保下次 session 能無縫接上。 + +--- + +## 執行流程 + +### 第一步:整理這次 session 的結果 + +從對話中提取: +- 完成了哪些 tasks(標記為 [x]) +- 進行中但未完成的(標記為 [🔄]) +- 遇到什麼問題或阻擋 +- 下次應該從哪裡開始 + +### 第二步:更新 tasks.md + +把對應 SDD 的 tasks.md 狀態更新(如果這次有動到的話)。 + +### 第三步:更新 status.md + +用以下格式覆蓋 status.md: + +```markdown +# 當前狀態 +> 更新時間:[YYYY-MM-DD] + +## 正在做 +- [🔄] [task 描述] — 阻擋點:[如果有] + +## 下次 session 第一件事 +[具體的第一個動作,越具體越好] + +## 待負責人確認 +- [描述] — 等待:[什麼決定] + +## 已知問題 +| 問題 | 優先級 | 狀態 | +|------|--------|------| +| [問題] | 🔴/🟡/⚪ | [狀態] | +``` + +### 第四步:如果有新的誤解或決策 + +順帶執行 `/wiki-capture` 的邏輯,把這次的誤解和決策也存進去。 + +### 第五步:確認 + +告知 status.md 更新完成,下次 session 從哪裡開始。 diff --git a/.claude/hooks/pre-bash-guard-no-table.sh b/.claude/hooks/pre-bash-guard-no-table.sh new file mode 100755 index 0000000..a405c4c --- /dev/null +++ b/.claude/hooks/pre-bash-guard-no-table.sh @@ -0,0 +1,37 @@ +#!/bin/bash +# .claude/hooks/pre-bash-guard-no-table.sh +# KBDB-graph PreToolUse guard for Bash +# +# 鐵律:任何人都不准動表。擋命令列層的動表(wrangler d1 execute CREATE TABLE 等)。 +# 退出 code:0 = 允許 / 2 = 擋下 +# 依賴:jq + +set -o pipefail +INPUT=$(cat) +CMD=$(echo "$INPUT" | jq -r '.tool_input.command // ""') + +block() { + cat >&2 <&2 < 新 session 開始時從這裡導航。 +> 目的:讓 CC 不需要重新學習已知的事。 +> 維護者:CC(人不手動編輯這裡) + +--- + +## 核心檔案 + +| 檔案 | 何時讀 | 內容 | +|------|-------|------| +| `status.md` | session 開始第一件事 | 當前進度、下一步 | +| `mistakes.md` | 做新功能前 | 已知誤解、快速檢查清單 | +| `decisions-summary.md` | 遇到設計判斷時 | 架構決策摘要 | + +--- + +## 維護規則 + +1. 只增不刪——記錄 append,決策改了加新條目說明「舊決策已更新」 +2. status.md 每次 session 結束更新 +3. mistakes.md 每次被糾正後 append +4. 發現新的重要決策 → 同時更新 decisions-summary.md 和 docs/2-architecture/decisions/ + +--- + +## 快速導航 + +**這個專案是什麼**:KBDB-graph —— KBDB 的 graph 插件(triplet 採集 + graph 查詢),類比 Apache AGE 之於 Postgres。要抽成獨立 repo(leo 產權)。基本盤(block CRUD)在 `arcrun/kbdb`,不在這。 + +**動工前必讀**: +- `docs/HANDOFF-kbdb-plugin.md` —— 本目錄專屬交棒(待辦:確認邊界 / 改名+git init+推 GitHub / 定義掛載介面)。 +- 上游約束見 `CLAUDE.md` 最頂 + `github.com/uncle6me-web/InkStoneCo`。 + +**文件去哪找**: +- SDD(design+tasks)→ `docs/3-specs/`(現有:arcrun-key-auth、blocks-edit-api) +- 歷史記錄 / bug 復盤 → `docs/5-records/`(現有:PATCH 403 bug、upsert feature request) +- 分類規則全表 → `docs/README.md` + +**絕對限制**:本目錄只做 graph 插件 / 萬物皆 Block(禁 CREATE/ALTER TABLE)/ API-as-Wall / 部署繞開 GitHub、禁跨 repo Actions / 樂高法(actions < 100 行)。 diff --git a/.claude/wiki/decisions-summary.md b/.claude/wiki/decisions-summary.md new file mode 100644 index 0000000..c914318 --- /dev/null +++ b/.claude/wiki/decisions-summary.md @@ -0,0 +1,34 @@ +# 架構決策摘要 + +> 遇到設計判斷時查這裡。 +> 完整脈絡在 docs/2-architecture/decisions/。 + +--- + +## KBDB-graph 定位 — 2026-06-13 +**結論**:本目錄 = KBDB 的 graph 插件(triplet 採集 + graph 查詢),獨立成 repo,類比 Apache AGE 之於 Postgres。 +**原因**:graph 能力較龐大、非基本儲存功能、leo 產權、較複雜 → 不留 arcrun。基本盤(block CRUD,D1 三表)= `arcrun/kbdb`,不在本目錄。 +**詳細**:`docs/HANDOFF-kbdb-plugin.md`;來源 InkStoneCo 頂層 `matrix-rearrange` Phase 2 (R2)。 + +## 獨立 repo 名 + 邊界乾淨度 — 2026-06-14 +**結論**:獨立 repo = 新 repo `uncle6me-web/kbdb-graph-plugin`(leo 拍板,沿用目錄名)。grep 證實插件與基本盤 action 層零耦合,耦合只在 DB 層(讀 blocks/entry_values + triplets VIEW)。 +**原因**:乾淨的 AGE-on-Postgres,插件抽出無程式碼牽連;唯一要設計的是 DB 掛載介面。 +**詳細**:`docs/3-specs/kbdb-graph-extraction/design.md`(灰色地帶結論 + 掛載介面)。 + +## 萬物皆 Block(KBDB v3)— 2026-02-28 +**結論**:一張 Block 表 + type 欄位,禁 CREATE/ALTER TABLE,新資料類型用 template;API-as-Wall。 +**原因**:零 migration 擴充;所有讀寫經 API 不繞過直打 D1。 +**詳細**:`CLAUDE.md` 下半(v3 全規範,含基本盤,待裁剪只留 graph 插件相關)。 + +## 避免再被 GitHub flag(上游鐵律)— 沿用 +**結論**:禁跨 repo 自動同步 Actions;部署繞開 GitHub(wrangler / scp);新 repo 預設不開 Actions。 +**原因**:當初 monorepo→多 worker 的 Actions 自動同步 + 高頻 API 害帳號被 flag。 +**詳細**:InkStoneCo 頂層 CLAUDE.md。 + +--- + +格式: +## [主題] — [YYYY-MM-DD] +**結論**:[一句話] +**原因**:[簡短說明] +**詳細**:docs/2-architecture/decisions/[對應檔案] diff --git a/.claude/wiki/mistakes.md b/.claude/wiki/mistakes.md new file mode 100644 index 0000000..cbc9fe5 --- /dev/null +++ b/.claude/wiki/mistakes.md @@ -0,0 +1,49 @@ +# CC 已知誤解 + 避坑方法 + +> 做新功能前讀一遍。 +> 格式:每條必須有症狀 + 正確做法 + 原因。 + +--- + +## 快速檢查清單(做任何事前) + +- [ ] 有對應 SDD 嗎?沒有 → 停手 +- [ ] 這次修改會影響哪些模組?有沒有連帶破壞? +- [ ] 驗收標準是什麼?有客觀證據嗎? + +--- + +## 誤解記錄 + +⚠️ MISTAKE: 把基本盤 block CRUD 當成本目錄的職責 + 症狀: 在本目錄改/實作 block CRUD、migration 0001/0002、整套 v3 規範。 + 正確做法: 本目錄只做 graph 插件(triplet/graph/entity)。基本盤歸 arcrun/kbdb。動手前先讀 docs/HANDOFF-kbdb-plugin.md 確認邊界。 + 原因: 2026-06-13 定調 KBDB-graph = 插件(AGE-on-Postgres 模式),但舊 CLAUDE.md 下半仍是整套 v3 規範,易誤導。 + 日期: 2026-06-13 + +⚠️ MISTAKE: 以為本目錄在 git 版控內、用 git mv 搬檔 + 症狀: git mv 報 "not under version control" / "source directory is empty"。 + 正確做法: 本目錄無獨立 git(matrix 降級後脫離,且被頂層 gitignore)→ 用普通 mv。改名 KBDB-graph 後才 git init。 + 原因: git repo 是 InkStoneCo 頂層,本目錄被 gitignore。 + 日期: 2026-06-14 + +⚠️ MISTAKE: 部署或同步走 GitHub Actions + 症狀: 設計依賴 GitHub API 列檔、push 觸發跨 repo 自動同步。 + 正確做法: 部署繞開 GitHub(wrangler 直推 / scp);讀檔走本地 fs;新 repo 不開 Actions。 + 原因: 上游鐵律——當初正是這模式害帳號被 flag。 + 日期: 2026-06-14 + +⚠️ MISTAKE: 假設「核心已在 arcrun」是既成事實 + 症狀: 照 HANDOFF 字面以為 arcrun/kbdb 已是 v3 基本盤、插件直接掛上去、共用同一 D1。 + 正確做法: 讀真身——arcrun/kbdb 其實還是 v2(entries,無 blocks/0005/0007/block-crud),與本插件是不同 D1 庫(arcrun-kbdb vs inkstone-kbdb)。v3 基本盤真身其實在本目錄。動工前用 ls/grep 對真身,不信 HANDOFF 字面。 + 原因: HANDOFF 寫的是「意圖/計劃」,未必已落地;跨 repo 重整時尤其要核對現況。 + 日期: 2026-06-14 + +--- + +格式: +⚠️ MISTAKE: [錯誤描述,一句話] + 症狀: [CC 通常怎麼表現這個錯] + 正確做法: [應該怎麼做] + 原因: [為什麼會錯] + 日期: [YYYY-MM-DD] diff --git a/.claude/wiki/status.md b/.claude/wiki/status.md new file mode 100644 index 0000000..774d49c --- /dev/null +++ b/.claude/wiki/status.md @@ -0,0 +1,31 @@ +# 當前狀態 + +> 更新時間:2026-06-14 +> 每次 session 結束必須更新此檔(用 /wiki-update)。 + +--- + +## 正在做 + +- 知識庫骨架建立完成(system-dev-template 接入:SDD + LLM Wiki)。 +- **HANDOFF 已讀完並整進 SDD**:`docs/3-specs/kbdb-graph-extraction/`(requirements + design + tasks)。 +- R-EXT-1 邊界初步分類完成(插件 / 基本盤 / 灰色地帶),見該 design.md。 + +## 下次 session 第一件事 + +讀 `docs/3-specs/kbdb-graph-extraction/tasks.md`。R-EXT-1 邊界 + grep + 讀 arcrun 全局核對都做完。**主阻擋 = 前置議題定案(task 1.4b)**,等 leo/arcrun 答覆三問(見下)。定案後:升 arcrun v3 / 定掛載形態 → 移交 → 裁剪 CLAUDE.md → git init 推 `uncle6me-web/kbdb-graph-plugin`。 + +**讀 arcrun 後的關鍵翻盤**:兩份 HANDOFF 假設「核心已在 arcrun」其實**尚未落地**——`arcrun/kbdb` 還是 v2(entries,無 blocks/0005/0007/block-crud),且與本插件是**不同 D1 庫**(arcrun-kbdb vs inkstone-kbdb)。**v3 基本盤真身其實在本目錄**。詳見 design.md「全局核對發現」。 + +## 待總管決策(三問已上呈,不是本子 repo 能拍板) + +leo 指示「整理成 wiki、由總管決策」。已寫成頂層決策文件: +📍 `InkStoneCo/.agents/specs/matrix-rearrange/DECISION-kbdb-v3-baseplane.md`(頂層 tasks Phase 2 task 2.5 已連結) + +三問:(1) v3 基本盤怎麼進 arcrun (2) 掛載形態共用 D1 vs 走 API (3) 0005 歸屬。總管答覆後本 repo 才能解除 1.4b / R-EXT-3 阻擋。 + +## 已知問題 + +- 本目錄目前**無獨立 git**(matrix 降級後脫離),改名後需 `git init`。 +- `CLAUDE.md` 下半部仍是整套 KBDB v3 規範(含基本盤 Block CRUD),與「只做 graph 插件」新定位不符,待裁剪(HANDOFF 待辦 1 的一部分)。 +- 上游 bug 未解:`PATCH /blocks/:id` 回 403(見 `docs/5-records/incidents/BUG-2026-05-29-...`)——屬 arcrun/kbdb 基本盤端,非本插件。 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d1889ae --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +node_modules/ +dist/ +.wrangler/ +.swarm/ +.env +.dev.vars +*.bak +*.db +ruvector.db +finally.click +.DS_Store +/tmp/ diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..43593a4 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,89 @@ +# CLAUDE.md — KBDB-graph 插件開發規範 + +> **上游約束(InkStoneCo 總管)**:此目錄(原 matrix/kbdb,已改名 kbdb-graph-plugin)是 InkStoneCo 子專案,受頂層知識庫約束。 +> 動工前讀 `github.com/uncle6me-web/InkStoneCo` 的 CLAUDE.md + `docs/3-specs/matrix-rearrange/`。 +> **定位(leo 2026-06-13)**:此 repo = **KBDB-graph 插件**(triplet 採集 + graph 查詢),類比 **Apache AGE 之於 Postgres**。基本盤 = `arcrun/kbdb`(D1 三表 + CRUD API),**不在這裡、不動它**。 +> **本目錄專屬交棒見 `docs/HANDOFF-kbdb-plugin.md`**,SDD 見 `docs/3-specs/kbdb-graph-extraction/`。 + +> 本檔案由 Claude Code 自動讀取。所有在此目錄下的開發必須遵守以下規則。 + +--- + +## 🔒 KBDB 鐵律(leo 2026-06-14 拍板,最高原則,違反會被 hook exit 2 擋下) + +決策全文:`InkStoneCo/docs/3-specs/matrix-rearrange/DECISION-kbdb-v3-baseplane.md`。 + +1. **任何人不准動表** — 禁 `CREATE/ALTER/DROP TABLE`。那 3 表只有基本盤維護者(leo)能改。 +2. **插件不准直接接觸表** — 禁 `SELECT/INSERT/UPDATE/DELETE`、禁 `JOIN`、禁 `.prepare(...sql...)`、禁綁 D1/Vectorize/AI。 +3. **讀寫全走基本盤 API/CLI/MCP** — 插件與 AI/人同一條路(薄殼原則)。base URL = `KBDB_BASE_URL` env var。 +4. **新資料類型 = 建 template + 填 slot,永不建表** — triplet=`template='triplet'`、entity=`template='entity'`,走 `POST /templates`+`POST /records`。 +5. **零 migration、零 SQL** — 插件目錄無 `migrations/`。SQL 只存在於基本盤 worker 內部。 + +> 比 AGE-on-Postgres 更嚴:AGE 能讀 Postgres 表,KBDB 插件連表都不許碰,必須透過基本盤 API。真正的 API-as-Wall。 +> **誠實限制**:hook 擋語法層明顯 SQL;藏在 helper 裡的繞道擋不了 → 文檔(本檔)+ hook 都不可省。想到「建表」時只准 template/slot。 + +### 掛載架構 + +``` +基本盤 arcrun/kbdb(不動) KBDB-graph 插件(本 repo) +─ entries / templates / entry_values ─ triplet template 定義 + graph 查詢函式 +─ CRUD API: ─ 寫 triplet → POST /records (template=triplet) + POST /entries POST /templates ─ 查圖 → GET /records/by-template/triplet + POST /records GET /entries/search → 插件層【記憶體】組鄰接表跑圖演算法 + GET /records/by-template/:tpl ─ entity 正規化 → template='entity'(exact match) + ─ 唯一對外通道 = src/lib/kbdb-client.ts +``` +基本盤 API 契約詳見 `docs/3-specs/kbdb-graph-extraction/design.md`。 +**基本盤缺口**(base 無 `PUT/DELETE /records/:id`、無 vectorize 語意搜尋)標 `[→arcrun]`,不得為此自建表。embedding/語意搜尋屬基本盤 optional embed 模組,**不是插件職責**。 + +--- + +## Wiki 讀取順序(LLM 記憶系統,CC 維護) + +| 檔案 | 時機 | 用途 | +|------|------|------| +| `.claude/wiki/status.md` | session 開始第一件事 | 當前進度、下一步 | +| `.claude/wiki/mistakes.md` | 做新功能前 | 已知誤解、避坑清單 | +| `.claude/wiki/decisions-summary.md` | 設計判斷時 | 架構決策摘要 | + +文件分類規則見 `docs/README.md`;SDD 在 `docs/3-specs/[子系統]/`(design.md + tasks.md),動手前必須有這兩個檔案。 +session 結束用 `/wiki-update` 更新 status.md,被糾正後 `/wiki-capture` 寫進 mistakes.md。 + +--- + +## 樂高法硬性限制(Layer 1) + +- `src/actions/` 目錄下的檔案**嚴禁超過 100 行**,建議 50-80 行 +- **一檔一事**:每個 action 只做一件具體的事 +- **無狀態**:Action 不保存記憶體狀態,所有狀態透過參數傳遞或走基本盤 API 持久化(**不碰 D1/Vectorize**) +- Route 檔案(`src/routes/`)**不含業務邏輯**,僅驗證參數 + `makeKbdbClient(c.env)` + 呼叫 action + +## 目錄結構 + +``` +src/ +├── lib/ ← kbdb-client.ts(唯一對外 API 通道)、templates.ts(插件 template 定義) +├── actions/ ← 核心業務邏輯(純函數,< 100 行,第一參數收 KbdbClient) +├── routes/ ← HTTP 入口(Hono route,只做驗證 + 呼叫 action) +├── types.ts ← 型別定義(= contracts) +└── index.ts ← Worker 進入點(只掛 triplets/graph/entities/search 路由) +``` + +## 開發流程 + +1. 在 `types.ts` 定義輸入/輸出型別 +2. 在 `tests/` 寫測試(走 `tests/mock-client.ts` 的 mock KbdbClient,不打真網路) +3. 在 `actions/` 實作邏輯(透過 `KbdbClient` 讀寫,零 SQL) +4. 在 `routes/` 建立 HTTP 入口 + +--- + +## 技術棧 + +- **Framework**: Hono(OpenAPIHono) +- **資料層**: 全走基本盤 arcrun/kbdb HTTP API(`KBDB_BASE_URL`)。**插件本身無 D1/Vectorize/AI 綁定。** +- **Validation**: Zod +- **Testing**: Vitest(純 node + mock client) +- **部署**: wrangler 直推 Cloudflare,**不開 Actions**(避免再被 flag,見頂層鐵律) + +> 2026-06-14:按 leo 鐵律改寫完成。21 個違規直接 SQL action 全改走基本盤 API;刪除所有 migrations(插件零建表);移除 D1/Vectorize/AI 綁定。基本盤規範歸 `arcrun/kbdb`。 diff --git a/contracts/triplet.json b/contracts/triplet.json new file mode 100644 index 0000000..138310f --- /dev/null +++ b/contracts/triplet.json @@ -0,0 +1,35 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Triplet", + "description": "知識圖譜三元組:Subject > Predicate > Object", + "type": "object", + "required": ["subject", "predicate", "object"], + "properties": { + "subject": { + "type": "string", + "minLength": 1, + "description": "主詞(實體名稱)" + }, + "predicate": { + "type": "string", + "minLength": 1, + "description": "謂詞(關係類型)" + }, + "object": { + "type": "string", + "minLength": 1, + "description": "受詞(目標實體或值)" + }, + "source_block_id": { + "type": "string", + "description": "來源 Block ID(Logseq 對應)" + }, + "confidence": { + "type": "number", + "minimum": 0, + "maximum": 1, + "default": 1.0, + "description": "可信度(0-1)" + } + } +} diff --git a/docs/3-specs/arcrun-key-auth/design.md b/docs/3-specs/arcrun-key-auth/design.md new file mode 100644 index 0000000..30a1214 --- /dev/null +++ b/docs/3-specs/arcrun-key-auth/design.md @@ -0,0 +1,117 @@ +# KBDB — Arcrun Key Auth + +> 建立:2026-05-05 +> 狀態:草稿,待 richblack review + +--- + +## 背景 + +KBDB 是 Arcrun 平台的子服務(對外統稱 Arcrun)。目前 KBDB 有兩種身份: + +- **Internal**:後端機器使用 `KBDB_INTERNAL_TOKEN`(hex secret) +- **Partner**:外部系統使用 `pk_live_xxx` API Key,需先由 internal 建立 partner 記錄 + +問題:Arcrun 用戶(人類或 AI agent)登入 arcrun.dev 取得的 `ak_xxx` Key 無法直接存取 KBDB。要存 KBDB 目前沒有自助路徑。 + +## 目標 + +讓 Arcrun 用戶的 `ak_xxx` Key **直接可用於 KBDB**,不需要額外申請第二把 Key。 + +--- + +## 設計決策 + +### Key 格式不變 + +Arcrun Key 格式維持 `ak_` 前綴(32 char hex),KBDB 新增對這個前綴的識別邏輯。 +不引入新的 Key 格式,不改 Arcrun 的 Key 產生邏輯。 + +### KBDB 驗證路徑新增 `ak_` 支援 + +現有 `index.ts` auth middleware 的 Partner 驗證區塊(line 103)僅接受 `pk_` 前綴。 +改為同時接受 `pk_` 和 `ak_`,查詢同一張 partner 表(tpl-partner)。 + +``` +if (effectiveToken.startsWith('pk_') || effectiveToken.startsWith('ak_')) { + const partner = await lookupPartner(c.env.DB, tokenHash); + ... +} +``` + +### Arcrun 登入時寫入 KBDB partner 記錄 + +Arcrun `cypher-executor/src/routes/auth.ts` 的 OAuth callback(`/auth/callback`) +在建立 UserRecord 後,呼叫 KBDB `POST /partners` 建立對應的 partner 記錄。 + +寫入時機: +- 新用戶首次登入 → 建立 partner 記錄(`upsert` 語意:若已存在則跳過) +- Key rotate(`PUT /me/api-key/rotate`)→ 舊 partner revoke,建新 partner 記錄 +- Key revoke(`DELETE /me/api-key`)→ KBDB partner 設為 revoked + +寫入內容: +```json +{ + "name": "arcrun:{email}", + "org_namespace": "arcrun:{email}", + "api_key_hash": "SHA-256(ak_xxx)", + "status": "active" +} +``` + +`org_namespace` 用 `arcrun:{email}` 格式,與 KBDB 原有的 partner namespace 不衝突。 + +### Namespace 隔離 + +用戶只能存取自己 `org_namespace` 下的 KBDB 資料,與其他用戶完全隔離。 +`org_namespace = 'arcrun:{email}'`,不是 admin,不會被提升為 internal。 + +### 服務啟用 UI(Dashboard /keys) + +初期:Arcrun 用戶登入即自動建立 KBDB partner 記錄(無需勾選), +因為「KBDB 是 Arcrun 的捆綁服務」,就像 n8n 登進去 Data Table 就在那裡。 + +未來(有計費需求時):Dashboard `/keys` 頁面可加 toggle 控制,切換時 +呼叫 `PATCH /me/kbdb` → cypher-executor 再去 KBDB 啟用/停用 partner。 + +--- + +## 實作範圍(本 SDD) + +### KBDB 側(`matrix/kbdb/`) + +**修改** `src/index.ts`: +- auth middleware Partner 驗證區塊:將 `effectiveToken.startsWith('pk_')` 改為同時接受 `ak_` + +**不改**: +- `src/actions/partner-auth.ts`(`hashToken` / `lookupPartner` 邏輯不變) +- `src/routes/partners.ts`(`POST /partners` 介面不變,Arcrun 呼叫此 endpoint 建記錄) +- D1 schema(`tpl-partner` template 不變) + +### Arcrun 側(`matrix/arcrun/cypher-executor/`) + +屬於 `frontend-redesign` SDD 範圍內的後端補充,見該 SDD tasks.md。 +本 SDD **不**負責 Arcrun 側實作,僅說明預期行為: + +1. OAuth callback 成功後,以 `KBDB_INTERNAL_TOKEN` 呼叫 KBDB `POST /partners` +2. Key rotate 時:先 KBDB `DELETE /admin/partners/{id}` (revoke),再建新記錄 +3. Key revoke 時:KBDB `DELETE /admin/partners/{id}` + +--- + +## 不做的事 + +- 不改 Key 格式(`ak_` 保留) +- 不合併 Arcrun USERS_KV 和 KBDB partner 表(兩邊各自維護) +- 不做跨 namespace 的資料共享 +- 不做 KBDB 側的 OAuth 驗證(KBDB 永遠只驗 token hash) + +--- + +## 風險 + +| 風險 | 緩解 | +|------|------| +| Arcrun 寫 KBDB 失敗(KBDB 暫時不可用) | 登入仍成功;寫 KBDB 失敗靜默 log,用戶下次 rotate key 時重建 | +| `ak_` Key 被猜測 | 32 char hex,entropy 足夠;與 `pk_` 同等安全 | +| email 含特殊字元破壞 namespace | `org_namespace` 用 `arcrun:` + raw email,D1 存 TEXT 無問題 | diff --git a/docs/3-specs/arcrun-key-auth/tasks.md b/docs/3-specs/arcrun-key-auth/tasks.md new file mode 100644 index 0000000..92b0450 --- /dev/null +++ b/docs/3-specs/arcrun-key-auth/tasks.md @@ -0,0 +1,60 @@ +# KBDB Arcrun Key Auth — Tasks + +> 建立:2026-05-05 +> 權威進度來源:本檔。完成一項立刻 `[x]`,不批次。 + +--- + +## Phase 0 — SDD 建立 + +- [x] 撰寫 `design.md` +- [x] 撰寫 `tasks.md`(本檔) +- [ ] richblack review + 認可 → 開 Phase 1 + +--- + +## Phase 1 — KBDB auth middleware 接受 `ak_` Key + +**修改檔案**:`matrix/kbdb/src/index.ts` + +- [x] 1.1 將 line 103 的 `effectiveToken.startsWith('pk_')` 改為 + `effectiveToken.startsWith('pk_') || effectiveToken.startsWith('ak_')` +- [ ] 1.2 本地跑現有測試確認不 break:`pnpm test` + +--- + +## Phase 2 — Arcrun OAuth callback 寫入 KBDB partner 記錄 + +**修改檔案**:`matrix/arcrun/cypher-executor/src/routes/auth.ts` + +> 注意:此 Phase 需要 Arcrun 側有 `KBDB_INTERNAL_TOKEN` 和 `KBDB_BASE_URL` 兩個 env binding。 + +- [x] 2.1 在 `Bindings` type(`types.ts`)加入 `KBDB_INTERNAL_TOKEN?: string` 和 `KBDB_BASE_URL?: string` +- [x] 2.2 建立 helper `src/lib/kbdb-partner.ts`: + - `ensureKbdbPartner(env, email, apiKey)` → PUT /admin/partners/by-key-hash,失敗靜默 log + - `revokeKbdbPartner(env, oldApiKey)` → DELETE /admin/partners/{id},失敗靜默 log +- [x] 2.3 在 OAuth callback(UserRecord 建立/取得後)呼叫 `ensureKbdbPartner`(fire-and-forget) +- [x] 2.4 在 `PUT /me/api-key/rotate` 呼叫:`revokeKbdbPartner(oldKey)` + `ensureKbdbPartner(newKey)` +- [x] 2.5 在 `DELETE /me/api-key` 呼叫 `revokeKbdbPartner` +- [ ] 2.6 `wrangler secret put KBDB_INTERNAL_TOKEN`(cypher-executor Worker)← 需要人工執行 +- [x] 2.7 在 `wrangler.toml` 加 `KBDB_BASE_URL = "https://kbdb.finally.click"` + +另外:KBDB `admin.ts` 新增 `PUT /admin/partners/by-key-hash` endpoint(upsert by hash,不產生新 key)。 +KBDB `types.ts` 加入 `KBDB_INTERNAL_TOKEN` 到 Bindings。 +KBDB `admin.ts` 放寬 `org_namespace` regex(允許 `arcrun:email@domain` 格式)。 + +--- + +## Phase 3 — 驗證 + +- [ ] 3.1 新用戶 OAuth 登入 → 確認 KBDB partner 記錄建立(`GET /admin/partners` 查詢) +- [ ] 3.2 用 `ak_xxx` Key 直接打 KBDB `GET /blocks` → 確認 200(非 401) +- [ ] 3.3 Key rotate → 確認舊 Key 401,新 Key 200 +- [ ] 3.4 Key revoke → 確認舊 Key 401 + +--- + +## 目前狀態 + +- Phase 0 已完成(等 richblack 認可) +- Phase 1–3 全部 `[ ]`,等認可後動工 diff --git a/docs/3-specs/blocks-edit-api/design.md b/docs/3-specs/blocks-edit-api/design.md new file mode 100644 index 0000000..c04bddf --- /dev/null +++ b/docs/3-specs/blocks-edit-api/design.md @@ -0,0 +1,227 @@ +# KBDB — Blocks Edit API + +> **建立**:2026-05-06 +> **狀態**:草稿,待 richblack review +> **依賴**:`matrix/kbdb/CLAUDE.md`(萬物皆 Block 架構,2026-02-28 鎖定) +> **驅動需求**:`polaris/mira/.agents/specs/mira-app/design.md`(前端 inline edit 直寫 KBDB 的需求) + +--- + +## 0. 背景 + +KBDB v3「萬物皆 Block」架構鎖定後,現役 routes 涵蓋 Block CRUD 多數操作,但**缺少編輯既有 block 的 PATCH endpoints**。具體缺口: + +| 操作 | 現役狀態 | +|---|---| +| `POST /blocks/ingest`(建立) | ✅ 已有 | +| `GET /blocks/{id}`(讀取單筆) | ✅ 已有 | +| `GET /blocks/`(列表 / 查詢) | ✅ 已有 | +| `DELETE /blocks/{id}`(刪除) | ✅ 已有 | +| **`PATCH /blocks/{id}`(部分更新)** | ❌ **缺** | +| `POST /triplets/`(建立) | ✅ 已有 | +| **`PATCH /triplets/{id}` / `DELETE /triplets/{id}`** | ❌ **缺** | +| `PUT /templates/{name}` | ✅ 已有 | +| `PATCH /tasks/{id}/status` | ✅ 已有 | + +→ 沒有 PATCH/UPDATE block 內容的 API,前端「inline edit 寫回 KBDB」做不了。 + +> **註**:`src/routes/blocks.ts.bak` 有 PUT /:id 的舊實作可參考,但已被 v3 取代並停用。**不直接複用 .bak 檔案**,按 v3 規範重寫。 + +--- + +## 1. 範圍 + +本 SDD 涵蓋兩件事: + +1. **補三組 endpoint**:`PATCH /blocks/{id}`, `PATCH /triplets/{id}`, `DELETE /triplets/{id}` +2. **建三個 templates**:`data-source-config`, `source-skill`, `wiki-page`(為 mira-app 準備) + +**不在範圍內**: +- 改 schema(v3 鎖定,禁止 ALTER TABLE) +- 改既有 endpoint 行為(純加新 endpoint) +- 多用戶權限細分(partner key 已能做 org 隔離,沿用即可) +- 編輯歷史 / undo(未來 SDD) + +--- + +## 2. PATCH /blocks/{id} + +### 2.1 用途 + +部分更新一個既有 block。前端 inline edit 場景:使用者點 edit icon,改 content / refs / tags,按儲存 → 送 PATCH。 + +### 2.2 規格 + +``` +PATCH /blocks/{id} +Authorization: Bearer +Content-Type: application/json + +Body (所有欄位皆 optional,至少要有一個): +{ + "content": "新的內容", + "tags": ["tag1", "tag2"], // 完整覆寫 tags 陣列 + "refs": ["block-id-1", "block-id-2"], // 完整覆寫 refs 陣列 + "slots": { "key": "value" }, // 完整覆寫 slots 物件 + "source": "...", // 通常不改,但允許 + "metadata_json": { ... } // 完整覆寫 +} + +Response 200: +{ + "id": "...", + "content": "...", + "updated_at": "2026-05-06T...", + ...完整 block 欄位 +} + +Response 400: +{ "error": "no fields to update" } + +Response 403: +{ "error": "block belongs to different org" } + +Response 404: +{ "error": "block not found" } +``` + +### 2.3 行為 + +- 只更新 body 內提供的欄位 +- 自動更新 `updated_at` +- 自動重新計算 `content_hash`(如 content 變動) +- 自動觸發 embedding 重算(如 content 變動,async) +- **權限**:partner key 只能改自己 org 內的 block(透過 user_id 對應),internal token 可改任何 block +- **content_hash 衝突**:partner key 不可修改 v3「`source` 為 `system` 的 admin 標記資料」(沿用既有 admin-preservation 規則) + +### 2.4 實作位置 + +- Action: `src/actions/update-block.ts`(< 100 行,按 KBDB CLAUDE.md 樂高法) +- Route: `src/routes/blocks.ts` 加新 OpenAPI route +- Test: `tests/blocks-update.test.ts` + +--- + +## 3. Triplet 編輯:使用既有 `PUT /records/:id` + `DELETE /records/:id` + +**設計修正(2026-05-06 實作時發現)**: + +v3 萬物皆 Block 架構下,triplet 是 `tpl-triplet` template 的 record(用 entry_values 存 subject/predicate/object slots)。**既有 `PUT /records/:id` 跟 `DELETE /records/:id` 已涵蓋編輯/刪除需求**,無需新增 `PATCH /triplets/:id`。 + +→ 前端編輯異見牆上的 triplet: +- 編輯:`PUT /records/{triplet_record_id}` body `{ values: { subject, predicate, object } }` +- 刪除:`DELETE /records/{triplet_record_id}` + +→ **本 SDD 不再規劃新 triplet endpoint**。 + +--- + +## 5. 三個新 Templates + +按 KBDB v3 規範,新資料類型透過 template 定義,**不動 schema**。 + +### 5.1 template: `data-source-config` + +每個資料源實例對應一個此 template 的 block。Mira 的「來源篩選」、cron workflow 的「每天去抓什麼」都讀這個。 + +```yaml +template_name: data-source-config +slots: + - name: string # "電子時報"、"我的 Logseq" + - channel: string # rss / telegram / km-writer / voice-stt / ai-comment / ai-canon + - config: object # channel-specific (e.g., rss: {url, schedule}) + - skill_id: string? # 連到 source-skill block 的 id + - enabled: bool + - ai_comment: bool # 是否需要 AI 加註解 + - ai_comment_style: string? # 提示給 claude_api 的風格 +``` + +### 5.2 template: `source-skill` + +每個 source 累積的「分析配方」(prompt + few-shot)。可在前端編輯、版本化。 + +```yaml +template_name: source-skill +slots: + - name: string # 例 "電子時報科技類分析" + - prompt: text # system_prompt 內容 + - examples: text? # few-shot examples(markdown) + - version: int + - based_on: string? # 上一版的 block id +``` + +### 5.3 template: `wiki-page` + +AI 從河道對話合成的定稿。 + +```yaml +template_name: wiki-page +slots: + - entity_name: string + - summary: text # markdown + - key_blocks: array # 引用的 source block ids + - conflicts: array? # 標記為矛盾的 block ids + - generated_at: timestamp + - version: int + - based_on: string? +``` + +### 5.4 建立方式 + +不寫 SQL migration(v3 規範禁止)。改用 KBDB 既有的 `POST /templates`: + +```bash +curl -X POST https://kbdb.finally.click/templates \ + -H "Authorization: Bearer " \ + -d '{"name": "data-source-config", "slots": [...]}' +``` + +→ tasks.md 列為 P0 任務(用 internal token 一次性建好)。 + +--- + +## 6. 實作步驟 + +### Phase 1:補 endpoints + +1. 寫 `src/actions/update-block.ts`(純函數,< 100 行,含權限檢查) +2. 寫 `tests/blocks-update.test.ts`(含 happy path、403、404、no-fields 三案) +3. 寫 `src/routes/blocks.ts` 的 PATCH route(OpenAPI 定義 + 呼叫 action) +4. 補 `src/routes/triplets.ts` PATCH(wrapper)+ DELETE(alias) +5. 部署 + smoke test + +### Phase 2:建 templates + +6. 用 internal token 呼叫 `POST /templates` 建三個 template +7. 驗證:用 partner key (mira 用的) 創建一個 `data-source-config` block 看能否寫成功 + +### Phase 3:補 OpenAPI spec + +8. 確認新 routes 自動進 swagger.json(OpenAPIHono 應該自動,需驗證) + +--- + +## 7. 風險 + +- **embedding 重算成本**:PATCH content 會觸發 vectorize 重算,頻繁 inline edit 可能拖慢。**對策**:embedding 改為 async(行為已是 async,需確認)。 +- **content_hash 計算遺漏**:忘記重算會讓查重失效。**對策**:在 action 內統一處理,不讓 route 層管。 +- **partner key 越權**:必須驗 user_id 對應,不能讓 partner A 改 partner B 的 block。**對策**:write tests 涵蓋此案。 +- **三個 templates 命名衝突**:若 KBDB 已有同名 template 會 fail。**對策**:建立前先 GET /templates/{name} 檢查。 + +--- + +## 8. 不在範圍內 + +- 編輯歷史 / undo / version diff(未來 SDD) +- Block soft delete(v3 已有 hard delete,softdelete 是 enhancement) +- Bulk PATCH(一次改多個 block,未來看需求) +- Field-level permissions(特定欄位只能某些 user 改) +- WebSocket 通知 block 改了(即時協作) + +--- + +## 9. 變更紀錄 + +| 版本 | 日期 | 內容 | +|---|---|---| +| v0 | 2026-05-06 | 初稿。對應 mira-app 的 inline edit 需求。 | diff --git a/docs/3-specs/blocks-edit-api/tasks.md b/docs/3-specs/blocks-edit-api/tasks.md new file mode 100644 index 0000000..5bf4b1a --- /dev/null +++ b/docs/3-specs/blocks-edit-api/tasks.md @@ -0,0 +1,68 @@ +# KBDB Blocks Edit API — Tasks + +> 對應 SDD:[design.md](design.md) +> 上次更新:2026-05-06(Phase 1 + Phase 2 完成) + +--- + +## Phase 1:補 PATCH endpoint ✅ 完成 + +### 1. PATCH /blocks/{id} + +- [x] 1.1 zod schema 直接放在 route 檔(既有 pattern) +- [x] 1.2 寫 `src/actions/block-update.ts`(96 行,符合樂高法 < 100) + - 取既有 block + getBlock fallback(id 或 logseq_uuid) + - 權限檢查:partner key 比對 user_id 前綴 + - 自動重算 content_hash(如 content 變) + - 觸發 embedding async 重算(不阻塞 PATCH 回應) + - 寫回 D1 + - 回傳更新後的 block +- [x] 1.3 寫 `tests/blocks-update.test.ts`(**7 case 全通過**) + - happy: content + content_hash 重算 + - happy: tags + refs 同改 + - 400: 無欄位 + - 404: 不存在 + - 403: partner 越權 + - 200: partner 改自己 namespace + - content_hash 在只改 tags 時不變 +- [x] 1.4 在 `src/routes/blocks.ts` 加 PATCH route(OpenAPI) +- [x] 1.5 部署到 prod(kbdb.finally.click)+ smoke test 4 case 通過 + +### 2. Triplet 編輯:使用既有 `PUT/DELETE /records/:id` + +- [x] 2.1 設計修正:v3 萬物皆 Block,triplet 是 record,既有 endpoints 已涵蓋。本任務組無需新增 endpoint。 +- [ ] 2.2 在 mira-app 前端「異見牆」實作呼叫 `PUT /records/:id`(待 mira 階段 3) + +### 3. OpenAPI spec 同步 + +- [x] 3.1 OpenAPIHono 自動產 swagger.json(route 用 createRoute 已自動納入) +- [ ] 3.2 部署後驗證 swagger UI 顯示新 route(待手動驗證) + +--- + +## Phase 2:建三個 templates ✅ 完成 + +- [x] 4.1 確認 KBDB 內無同名 template(透過 GET /templates 確認) +- [x] 4.2 用 internal token POST /templates 建 `data-source-config`(id: `tpl-data-source-config`) +- [x] 4.3 用 internal token POST /templates 建 `source-skill`(id: `tpl-source-skill`) +- [x] 4.4 用 internal token POST /templates 建 `wiki-page`(id: `tpl-wiki-page`) +- [x] 4.5 驗證:3/3 templates 在 GET /templates 列表內 + +--- + +## 風險追蹤 + +- ~~風險 1:partner key 跨 org 越權~~ — ✅ unit test 已涵蓋(403 partner 越權) +- ~~風險 2:embedding 重算造成 D1 寫入 spike~~ — ✅ 改成 fire-and-forget(不 await),不阻塞 PATCH +- ~~風險 3:content_hash 不一致~~ — ✅ unit test 驗證 hash 重算對應內容 + +## Known Issues(不在本 SDD 範圍,待另開) + +- **`POST /blocks/ingest` 不寫入 `source` 欄位**:input 接受 `source` 參數但僅用於 id slug 生成,未寫進 block 的 source 欄位(block-ingest.ts:84 的 INSERT 缺欄位)。對 mira 影響:所有 source 區分目前無效,需等 KBDB 修復或直接走 `POST /blocks` + slots。建議下一份 KBDB SDD `block-ingest-source-fix` 處理。 + +--- + +## 部署紀錄 + +- 2026-05-06: Worker version `b7df3c38-e138-41fb-a16c-cc9d2dfeebea` 部署上線 +- Smoke test 通過:content 改寫 + hash 重算、tags 改寫、400 空 body、404 不存在 diff --git a/docs/3-specs/kbdb-graph-extraction/design.md b/docs/3-specs/kbdb-graph-extraction/design.md new file mode 100644 index 0000000..8c3f54a --- /dev/null +++ b/docs/3-specs/kbdb-graph-extraction/design.md @@ -0,0 +1,145 @@ +# KBDB-graph 抽出 — Design + +> 建立:2026-06-14 +> 大改:2026-06-14(leo 拍板鐵律後,推翻原「共用 D1 / 直接 SQL」判斷) +> 對應 requirements.md 的 R-EXT-1/2/3 + +--- + +## ⚠️ 修正:原 design 的錯誤判斷(2026-06-14) + +**本檔原版犯了「讀現狀推翻鐵律」的錯**,必須記下來避免重犯: + +- 原版看到插件現狀「直接 SQL 讀 `blocks`/`entry_values`」(28×/31× SQL 引用),**把它當成「AGE-on-Postgres 訊號」當設計依據**,跑去問「要不要共用同一個 D1、直接 SQL 掛在基本盤表上」。 +- **這是錯的**:現狀那 21 個直接 SQL 的 action 是**違規的歷史產物**(違反本 repo CLAUDE.md「禁止繞過 API 直接 D1 操作 — API-as-Wall」),不是設計依據。讀違規現狀去推翻規則,正是 leo 點名的反例。 +- leo 2026-06-14 拍板 KBDB 鐵律(見 `InkStoneCo/.../DECISION-kbdb-v3-baseplane.md`):**插件絕不碰表,讀寫全走基本盤 API。零建表、零 migration、零 SQL。** 比 AGE-on-Postgres 更嚴——AGE 能讀 Postgres 表,KBDB 插件連表都不許碰。 + +### 連帶修正的次要誤判 + +- **「v3 基本盤真身在本目錄」**:原版以為 arcrun/kbdb 是「v2 落後版」、本目錄的 blocks 表才是 v3 真身。讀 `arcrun/kbdb/0001_base.sql` 註釋確認**前提倒反**:arcrun 的 3 表(`entries`/`templates`/`entry_values`,`entry_type='block'`)**是刻意設計的基本盤**,明寫 plugin model(core + AGE)、"Table never changes"。本目錄帶獨立 `blocks` 表 + 0001/0005 CREATE TABLE 的那套「v3」**才是長歪的違規殘留,要刪**。 +- **「共用 D1 vs 走 API」**:選走 API(鐵律 3)。兩 repo 不同 D1 庫不是問題——插件本來就不該碰基本盤的 D1。 +- **「0005 歸屬」**:問題消解。插件不該有任何 migration。`entry_values` 屬基本盤、基本盤已有。 + +--- + +## 基本盤 API 契約(arcrun/kbdb,已存在,不動) + +讀 `arcrun/kbdb/src/` 確認的真實端點(插件改寫的目標介面): + +| 端點 | 用途 | 備註 | +|---|---|---| +| `POST /entries` | 建 atomic entry | body 用 **`entry_type`**(block/value/...)、**`owner_id`**;回 `{success, entry}` | +| `GET /entries` | list(filter: entry_type/owner_id/parent_id/page_name/limit/offset) | | +| `GET /entries/search?q=&owner_id=` | D1 LIKE keyword search | base 只有 keyword;語意搜尋是 optional embed module | +| `GET/PATCH/DELETE /entries/:id` | 單筆 CRUD | | +| `POST /templates` | 建 template(name+slots[])= **替代建表** | | +| `GET /templates`、`GET /templates/:name`、`PUT /templates/:name` | template CRUD | | +| `POST /records` | 建 record(`{template, values:{slot:content}, owner_id?}`)= 填 slot | 回 `{success, record}` | +| `GET /records/by-template/:template?owner_id=` | 列某 template 的所有 record | | +| `GET /records/:recordId` | 取單筆 record 的 slot values | | + +> ⚠️ **與插件本地舊 copy 的差異**(改寫時務必對齊):欄位是 `entry_type`/`owner_id`(不是本地的 `type`/`user_id`);回應包在 `{success, ...}`;基本盤**無** `PUT/DELETE /records/:id`、**無** `entity_type` 欄位、**無** vectorize 綁定(語意搜尋與 embedding 屬 optional 模組,不在 base)。 + +### 插件專屬狀態怎麼存(leo 2026-06-14 釘正:不准建表) + +triplet 的 `clusters`/`bridge_score`/`confidence`/`source_block_id`、entity 正規化的 `canonical`/`alias`、`entity_type`——**全部是「新資料類型 = 建 template + 填 slot」**,不是建表: + +| 插件狀態 | 存法(純 API) | +|---|---| +| triplet | `template='triplet'`,slots: subject/predicate/object/source_block_id/confidence/clusters_json/bridge_score → `POST /records` | +| entity 正規化 | `template='entity'`,slots: canonical/aliases_json/entity_type/owner → `POST /records`;查重靠 `GET /records/by-template/entity` + (語意比對走 optional embed,base 沒有就降級 exact match) | +| entity_type | 不再是 blocks 欄位(基本盤無此欄)→ 收進 entity record 的 slot | + +「插件自建獨立 D1(triplet_clusters 等)」**不是選項**——那仍是建表,違反鐵律 1。問「狀態存哪」時若想到建表,只准 template/slot。 + +## 邊界分類(R-EXT-1) + +依 HANDOFF 資產清單 + 實際 `ls` 比對(2026-06-14)。三類:**插件留** / **基本盤走** / **灰色地帶待確認**。 +(註:下表「基本盤走」的前提是 arcrun 升 v3——見上「前置議題」未解前不要實際搬。) + +### 插件(graph)— 留本目錄 + +| 類型 | 檔案 | +|---|---| +| actions | `triplet-{crud,embed,entities,extract,stats,syntax,update}` (7)、`graph-{nodes,path,traverse}` (3)、`entity-{crud,graph-embed,normalize}` (3)、`predicate-normalize`、`search-{embed,query,suggest}` (3) | +| routes | `triplets.ts`、`graph.ts`、`entities.ts`、`search.ts` | +| migrations | `0003_triplet_user_id`、`0005_universal_table`、`0006_triplet_clusters`、`0008_entity_type` | +| contracts | `triplet.json` | + +> 註:HANDOFF 同時列 `search-*` 與 `entity-graph-embed` 為插件資產,與此處一致。`0005_universal_table` 雖名「universal」(看似基本盤),但 HANDOFF 明列為 triplet/graph 相關 → 暫歸插件,待 R-EXT-3 釐清(見灰色地帶)。 + +### 基本盤(block CRUD)— 走 arcrun/kbdb + +| 類型 | 檔案 | +|---|---| +| actions | `block-{crud,embed,import,ingest,process,update}` (6)、`tag-crud`、`profile-crud` | +| routes | `blocks.ts`(+ `blocks.ts.bak` 清掉)、`tags.ts`、`templates.ts`、`profiles.ts` | +| migrations | `0001_init`、`0002_block_indexing` | + +### 灰色地帶 — grep 調查結論(2026-06-14) + +調查方法:`src/index.ts` route 掛載、route↔action import、plugin action 的相依、DB 表引用。 + +**關鍵發現(耦合面)**: +- **插件與基本盤在 action 層完全解耦**:所有 plugin action(triplet/graph/entity/search)**不 import 任何**基本盤或灰色地帶 action;plugin route 也只 import plugin action。`src/lib`、`src/models` 都是空的,無共用程式碼耦合。 +- **耦合只在 DB 層**:plugin action 直接以 SQL 讀 `blocks`(28×)、`entry_values`(31×)、`triplets`(23×)。→ 這正是 AGE-on-Postgres 訊號:插件靠共用 D1 掛在基本盤表之上。 +- `triplets` 演進:0001 是 TABLE → 0005/0006/0007 改成 VIEW(疊在 universal table 上)。`entry_values` 是 0005 定義的 universal 儲存**表**。 + +**逐檔歸屬建議(附證據)**: + +| 檔案 | 證據 | 建議歸屬 | +|---|---|---| +| `0005_universal_table` | 定義 `entry_values`(v3 slots 儲存表)= 基本盤核心基礎設施,非純插件 | **基本盤(arcrun)**。⚠️ 與 HANDOFF 列為插件相反——需與 arcrun 對齊 | +| `0007_v3_rename_and_cleanup` | 同時 rename 基本盤 `blocks` 表 + 重建 `triplets`/`user_profiles` VIEW | **基本盤(arcrun)**做 rename;插件只依賴「基本盤已是 v3 schema + 有 triplets VIEW」 | +| `entry-crud` | **無人 import**(route/index 都沒引用)= dead code | **刪**(v2 legacy) | +| `record-crud` + `records.ts` | 被 admin/templates/records 用,非任何 graph route | **基本盤/arcrun**(非 graph) | +| `0004_task_status` + `tasks.ts` | `/tasks` 掛載,與 block 儲存/graph 都無關 | **arcrun 其他子系統**(非本插件) | +| `block-documents`、`convertPdf` + `convert.ts` | 被 `blocks.ts` 用 / `/convert` 掛載;PDF 轉換 | **基本盤/arcrun**(非 graph) | +| `partner-auth` + `partners.ts` | 被 admin/partners/index 用;partner API key 認證 | **基本盤/arcrun 認證層** | +| `admin.ts`、`personality.ts` | `/admin`、`/personality` 掛載;與 graph 無關 | **arcrun 其他子系統**(非本插件) | + +**結論**:graph 插件邊界乾淨(action 層零耦合)。耦合**只在 DB 層**——而那層正是要拆掉的違規。掛載介面不是「DB 掛載」,是 **API 掛載**(見下)。 + +## 掛載介面(R-EXT-3)= 基本盤 API(API-as-Wall,非共用 D1) + +> 推翻原「AGE-on-Postgres 共用 D1 + triplets VIEW」設計。leo 鐵律:插件不碰表。 + +- **掛載 = HTTP API**:插件不共用 D1、不自建表、不建 VIEW。插件**只能用基本盤 API / CLI / MCP 去建與讀**(leo 2026-06-14 釘正),與 AI/人同一條路。連 `triplets` VIEW 都不做——「圖」在**插件層的記憶體裡**從 record 組裝,不靠 DB VIEW。 +- **插件依賴的基本盤介面** = 上節「基本盤 API 契約」那張表(已存在於 arcrun/kbdb,不需 arcrun 升級、不需合庫)。 +- **base URL** = `KBDB_BASE_URL` env var(leo 2026-06-14:做成可設定,先留空)。插件透過 `src/lib/kbdb-client.ts` 打它。本地測試用 mock / 本地 base worker,部署時填真網址。 +- **禁止繞道**:不准把 SQL 藏在 helper 裡假裝走 API。client 只發 HTTP,零 `.prepare`。hook 擋語法層,這條是設計層補強。 + +### 改寫對照(21 個違規 action → API) + +| 現狀(違規 SQL) | 改寫成(基本盤 API) | +|---|---| +| `triplet-crud` `INSERT INTO entry_values` + value blocks | `POST /templates`(確保 triplet template 存在) + `POST /records`(template=triplet, 填 slot) | +| `triplet-crud` `queryTriplets` 大 JOIN | `GET /records/by-template/triplet?owner_id=` → 插件層 filter/組裝 | +| `triplet-crud` `getTriplet`/`updateTriplet`/`deleteTriplet` | `GET /records/:id`;update/delete 受限於 base 無 PUT/DELETE record(見「缺口」) | +| `triplet-stats` 聚合 SQL | `GET /records/by-template/triplet` 後在插件層 reduce 統計 | +| `triplet-extract`/`triplet-entities` 讀 blocks | `GET /entries`/`GET /entries/search` | +| `graph-{nodes,path,traverse}` 讀 `triplets` 表 | 先 `GET /records/by-template/triplet` 取全部 triplet → 插件層建鄰接表跑圖演算法 | +| `entity-crud`/`entity-normalize` 讀寫 entity 表 | `template='entity'` + `POST/GET records`;語意比對降級 exact(base 無 vectorize) | +| `search-query` SQL | `GET /entries/search?q=`(keyword);語意搜尋待 optional embed 模組 | +| `predicate-normalize` | 純函式(若有 SQL 一併改 API) | + +### 基本盤缺口(改寫時誠實標記,不偷建表補) + +base 目前**無** `PUT/DELETE /records/:id`、**無** entity_type 欄位、**無** vectorize。影響: +- triplet/entity 的 **update/delete** → base 缺端點。對策:(a) 標記為 `[→arcrun]` 缺口待基本盤補端點;(b) 暫以「建新 record + 標記舊 record 作廢」soft-delete,**仍走 API**。不得為此自建表或直連 D1。 +- **語意搜尋 / entity embedding 比對** → 屬 optional embed 模組(不在 base)。base 沒有時降級成 exact match / keyword。embedding 不是插件職責,不在插件建 vectorize。 + +## 改寫 task(落到 tasks.md R-EXT-4) + +見 tasks.md 新增的 R-EXT-4「改寫成走 API」區塊。 + +## 獨立成 repo(R-EXT-2) + +1. 確認 R-EXT-1 邊界、清掉基本盤檔案(移交 arcrun)後,本目錄只剩插件。 +2. 改名 KBDB-graph、`git init`、設 remote(帳號問 leo)。 +3. 部署繞開 GitHub:wrangler 直推 CF;不開 Actions。 +4. 推 GitHub(由本 CC 自己推)。 + +## CLAUDE.md 裁剪 + +移除整套 KBDB v3 基本盤規範(萬物皆 Block 全文、50 endpoints、Block CRUD 細節),保留:樂高法、graph 插件定位、掛載介面、上游約束、wiki 讀取順序。基本盤規範移交 arcrun/kbdb 的 CLAUDE.md。 diff --git a/docs/3-specs/kbdb-graph-extraction/requirements.md b/docs/3-specs/kbdb-graph-extraction/requirements.md new file mode 100644 index 0000000..4eab6b5 --- /dev/null +++ b/docs/3-specs/kbdb-graph-extraction/requirements.md @@ -0,0 +1,38 @@ +# KBDB-graph 抽出 — Requirements + +> 建立:2026-06-14 +> 來源:InkStoneCo 頂層 `matrix-rearrange` R2 + 本目錄 `docs/HANDOFF-kbdb-plugin.md` +> 定調:leo 2026-06-13 + +--- + +## 背景 + +本目錄(`matrix/kbdb-graph-plugin`,原 `matrix/kbdb`)原是「整套 KBDB」。leo 2026-06-13 拍板拆分: +- **基本盤** = `arcrun/kbdb`:D1 三表(blocks/templates/slots)的基本存儲讀寫,已併進 arcrun。 +- **本目錄(KBDB-graph)** = 掛在基本盤之上的 triplet 採集 + graph 查詢插件,**類比 Apache AGE 之於 Postgres**。 + +為何獨立:graph 能力較龐大、非基本存儲、leo 產權較複雜 → 獨立成 repo 不留 arcrun。 + +## 需求 + +1. **R-EXT-1 確認邊界**:把現有 `src/actions/`、`src/routes/`、`migrations/`、`contracts/` 逐一分類為「插件(triplet/graph/entity/search)」或「基本盤(block CRUD/template/tag/profile)」。基本盤的歸 arcrun/kbdb,插件的留本目錄。順便裁剪 CLAUDE.md(移除基本盤規範,只留 graph 插件相關)。 +2. **R-EXT-2 獨立成 repo**:改名 KBDB-graph,`git init` + 設 remote(帳號問 leo)+ 推 GitHub。由本 CC 自己推,不經總管。 +3. **R-EXT-3 定義掛載介面**:KBDB-graph 如何掛在 arcrun/kbdb 基本盤上(AGE-on-Postgres 模式)——插件怎麼讀基本盤的 blocks 表、怎麼宣告自己的 triplet/entity/graph schema。 + +## 約束(硬性) + +- **修改不是重建**:在現有實作上改,不重寫。 +- **部署繞開 GitHub**:wrangler 直推 Cloudflare,禁跨 repo 同步 Actions(當初害帳號被 flag 的模式)。新 repo 預設不開 Actions。 +- **本目錄現無獨立 git**(matrix 降級後脫離、被 InkStoneCo 頂層 gitignore)→ R-EXT-2 才 git init。在那之前用普通 mv 不是 git mv。 +- **API-as-Wall / 萬物皆 Block** 仍適用於基本盤;插件對 graph 資料同樣經 API。 +- **樂高法**:`src/actions/` 單檔 < 100 行、一檔一事、無狀態。 + +## leo 已拍板(2026-06-14) + +- 獨立 repo = **新 repo `uncle6me-web/kbdb-graph-plugin`**(沿用現目錄名,非「KBDB-graph」字面)。 +- 灰色地帶處理方式 = 先 grep 查引用再提建議(已完成,見 design.md)。 + +## 仍待確認(與 arcrun 對齊,非 leo) + +- 0005/0007 等基本盤 migration 歸屬與 HANDOFF 清單有出入,移交前對齊 arcrun。 diff --git a/docs/3-specs/kbdb-graph-extraction/tasks.md b/docs/3-specs/kbdb-graph-extraction/tasks.md new file mode 100644 index 0000000..f9a912b --- /dev/null +++ b/docs/3-specs/kbdb-graph-extraction/tasks.md @@ -0,0 +1,60 @@ +# KBDB-graph 抽出 — Tasks + +> 唯一進度來源,不靠對話記憶。完成即時更新。 +> 狀態:[ ] 未開始 [🔄] 進行中 [x] 完成 [⏸] 卡住/待確認 + +--- + +## R-EXT-1 確認邊界 + +- [x] 1.1 inventory:列出現有 actions/routes/migrations/contracts(2026-06-14 完成,見 design.md) +- [x] 1.2 初步分類:插件 / 基本盤 / 灰色地帶(2026-06-14,見 design.md 邊界分類表) +- [x] 1.3 灰色地帶 grep 調查:證實插件 action 層零耦合、耦合只在 DB 層;逐檔附證據歸屬(2026-06-14,見 design.md 灰色地帶結論)。⚠️ 0005/0007 歸屬與 HANDOFF 有出入,仍需與 arcrun 對齊(屬 1.4) +- [x] 1.4a 讀 arcrun 端真身對齊(2026-06-14):**發現 arcrun/kbdb 還是 v2(entries,無 blocks/0005/0007/block-crud),且兩 repo 是不同 D1 庫**。v3 基本盤真身其實在本目錄。見 design.md「全局核對發現」 +- [x] 1.4b 前置議題**總管已答覆**(leo 2026-06-14):→ `InkStoneCo/docs/3-specs/matrix-rearrange/DECISION-kbdb-v3-baseplane.md`。三問消解:基本盤已在 arcrun/kbdb 且設計正確、掛載走 API(非共用 D1)、插件零 migration。**阻擋解除。** +- [x] 1.4c 不需移交/升級 arcrun——基本盤已正確。插件改寫成走 API 即可(見 R-EXT-4) +- [ ] 1.5 裁剪 CLAUDE.md:移除基本盤規範,只留 graph 插件相關 +- [ ] 1.6 清掉殘留:`blocks.ts.bak`、誤入 repo 的 `ruvector.db`(根 + src/routes/ 各一份)、`finally.click` 空檔、`.swarm` + +## R-EXT-3 定義掛載介面(已定案 2026-06-14) + +- [x] 3.1 確認基本盤 API 契約(讀 arcrun/kbdb src,見 design.md「基本盤 API 契約」表) +- [x] 3.2 掛載方式定案:**API-as-Wall**(HTTP API,非共用 D1、非 VIEW、非附加表)。圖在插件層記憶體組裝 +- [x] 3.3 寫進 design.md 定稿(「掛載介面 = 基本盤 API」節) + +## R-EXT-4 改寫成走 API(核心,2026-06-14 新增) + +> 鐵律:插件零建表、零 migration、零 SQL,只用 API/CLI/MCP。 + +- [ ] 4.1 寫 `src/lib/kbdb-client.ts`:封裝基本盤 HTTP API(entries/templates/records),指向 `KBDB_BASE_URL` env var。零 `.prepare` +- [ ] 4.2 wrangler.toml:移除 D1/Vectorize 綁定(插件不碰 DB),加 `KBDB_BASE_URL` var(先留空) +- [ ] 4.3 改寫 `triplet-crud`(拆 < 100 行):create/query/get/update/delete/stats → API +- [ ] 4.4 改寫 `triplet-extract`/`triplet-entities`/`triplet-stats`/`triplet-update` → API +- [ ] 4.5 改寫 `graph-{nodes,path,traverse}`:取 triplet records → 插件層組圖 +- [ ] 4.6 改寫 `entity-{crud,normalize,graph-embed}`:template='entity' + records API;無 vectorize 時降級 exact +- [ ] 4.7 改寫 `search-query`/`search-*`:`GET /entries/search`(keyword);語意搜尋標記待 embed 模組 +- [ ] 4.8 刪違規 migrations(0001/0002/0005/0007 等含 CREATE TABLE)+ 清基本盤 action/route(block-*/entry-crud/record-crud/tag/profile/admin/...) +- [ ] 4.9 改測試走 mock client;標記 base 缺口(PUT/DELETE record、vectorize)為 `[→arcrun]` + +## R-EXT-2 獨立成 repo(最後做,依賴 1.4/1.5 完成) + +- [x] 2.1 GitHub 帳號 + repo 名:**leo 拍板 = 新 repo `uncle6me-web/kbdb-graph-plugin`**(2026-06-14) +- [ ] 2.2 `git init` + `.gitignore`(排除 ruvector.db、.env、node_modules、.wrangler、*.bak) +- [ ] 2.3 設 remote、首次 commit、推 GitHub(本 CC 自己推,不經總管) +- [ ] 2.4 部署驗證:wrangler 直推 CF,確認不開 Actions + +--- + +## 阻擋項彙整(更新 2026-06-14) + +1. ✅ repo 已定:`uncle6me-web/kbdb-graph-plugin`(解除 2.1) +2. ✅ 灰色地帶已 grep 調查完,附證據建議(解除 1.3) +3. ⏸ **前置議題(讀 arcrun 後升級為主阻擋)**:arcrun/kbdb 還是 v2、與插件不同 D1 庫。三問待 leo/arcrun 定案: + - (1) v3 基本盤(blocks/0005/0007/block-crud)由誰、怎麼進 arcrun?(arcrun 升 v3 vs 本目錄整理好再交) + - (2) 掛載形態:共用同一 D1(需合庫)還是插件透過基本盤 **API** 取 block(不共用 D1)? + - (3) `0005_universal_table` 歸基本盤(它定義 entry_values)——與 HANDOFF 列為插件矛盾,需 arcrun 確認。 + +## 注意 + +- arcrun 端對應交棒:`arcrun docs/HANDOFF-matrix-rearrange.md §2`,移交基本盤前先與其對齊。 +- 在 2.2 git init 前,本目錄無版控 → 搬檔用 `mv` 不是 `git mv`。 diff --git a/docs/5-records/FEATURE-REQUEST-2026-05-29-upsert-block-endpoint.md b/docs/5-records/FEATURE-REQUEST-2026-05-29-upsert-block-endpoint.md new file mode 100644 index 0000000..9cb8a75 --- /dev/null +++ b/docs/5-records/FEATURE-REQUEST-2026-05-29-upsert-block-endpoint.md @@ -0,0 +1,57 @@ +# FEATURE REQUEST: KBDB 應提供 upsert block endpoint(目前只能 client 端拼 GET+PATCH/POST) + +> 日期:2026-05-29 +> 來源:arcrun Phase 2(降級假零件成 recipe) +> 類型:API 缺口 —— upsert 語義目前不存在於 KBDB,被迫由 client 端拼湊 +> 關聯:[BUG-2026-05-29-patch-blocks-403-different-org.md](./BUG-2026-05-29-patch-blocks-403-different-org.md)(拼湊路徑的 PATCH 那段還壞著) + +--- + +## 問題 + +arcrun 原本有一個 `kbdb_upsert_block` 零件,行為是「依 page_name + user_id 查找,有就更新、沒有就新建」。但它**完全是 client 端拼湊**: + +``` +GET /blocks?page_name=X&limit=10 # 查找 + → client 端 filter user_id 找第一筆 + → 找到:PATCH /blocks/:id # 更新 + → 沒找到:POST /blocks # 新建 +``` + +KBDB **沒有** upsert 語義的 endpoint。實測(同一把 key): + +| 探測 | HTTP | +|---|---| +| `PUT /blocks` | 404 | +| `POST /blocks/upsert` | 404 | +| `PUT /blocks/upsert` | 404 | + +## 為什麼這是 KBDB 該補的、不是 arcrun 該拼的 + +arcrun 的設計原則是**薄殼 / 薄 API**:arcrun 只幫既有 API 套一層 recipe(endpoint + auth),**不無中生有功能**。 + +「先查再分支寫」這套 upsert 邏輯,是在 client 端**變出 KBDB 沒有的功能**。這有幾個壞處: + +1. **競態(race condition)**:GET 和後續 PATCH/POST 之間,別人可能插入同 page_name 的 block,造成重複或覆寫。只有 KBDB server 端用單一交易(upsert / `ON CONFLICT`)才能正確。 +2. **語義碎裂**:每個 client(arcrun / 其他 SDK)各自拼一套 upsert,filter 規則(怎麼算「同一筆」)可能不一致。 +3. **拼湊路徑現在還壞著**:它依賴 PATCH /blocks/:id,而那個 endpoint 目前回 403(見關聯 bug)。 + +## 建議 + +KBDB 提供一個原生 upsert endpoint,例如: + +``` +POST /blocks/upsert +Body: { page_name, user_id, content, type, source, tags, ... } +語義:依 (page_name, user_id) 找唯一 block —— 存在則更新、不存在則建立(單一交易,server 端 ON CONFLICT) +回應:{ id, action: "created" | "updated" } +``` + +有了它之後: +- arcrun 端只要建一個 `recipe:kbdb_upsert` 指向 `POST /blocks/upsert`,套殼即可,跟其他 5 個 KBDB recipe 一致。 +- 競態由 KBDB server 端交易保證,client 不再拼湊。 + +## arcrun 端現狀(等 KBDB) + +- 其餘 5 個 KBDB 操作已降級成 recipe 並驗收:`kbdb_get`(200) / `kbdb_create_block`(201) / `kbdb_ingest`(201) / `kbdb_delete`(200) 綠;`kbdb_patch_block` 因上述 403 bug 待驗。 +- `kbdb_upsert_block` **暫不降級、源碼暫留**,等 KBDB 出 `POST /blocks/upsert` 後改建 `recipe:kbdb_upsert` 套殼。 diff --git a/docs/5-records/incidents/BUG-2026-05-29-patch-blocks-403-different-org.md b/docs/5-records/incidents/BUG-2026-05-29-patch-blocks-403-different-org.md new file mode 100644 index 0000000..e71fc40 --- /dev/null +++ b/docs/5-records/incidents/BUG-2026-05-29-patch-blocks-403-different-org.md @@ -0,0 +1,68 @@ +# BUG: PATCH /blocks/:id 回 403 "block belongs to different org"(同一把 key 能 create/get/delete 卻不能 patch) + +> 回報日期:2026-05-29 +> 回報來源:arcrun Phase 2(把 kbdb_* 零件降級成 recipe,逐個驗收時發現) +> 嚴重度:中高 —— PATCH endpoint 對「自己剛建、且能刪的 block」拒絕更新,等於 update 能力全壞 +> 影響:任何「查→改」或 upsert 流程(先 GET 找到 block,再 PATCH 更新)都無法完成 + +--- + +## 症狀 + +用**同一把 API key**、對**同一個 block**,四個操作的結果不一致: + +| 操作 | endpoint | HTTP | 結果 | +|---|---|---|---| +| 建立 | `POST /blocks` | **201** | ✅ 建出 block,回 id | +| 讀取 | `GET /blocks/:id` | **200** | ✅ 讀得到 | +| **更新** | **`PATCH /blocks/:id`** | **403** | ❌ `{"error":"block belongs to different org"}` | +| 刪除 | `DELETE /blocks/:id` | **200** | ✅ `{"deleted":true}` | + +**矛盾點**:同一把 key 能 create / get / delete 這個 block —— 代表 KBDB 認定我擁有它(org 一致)。但 PATCH 卻說「belongs to different org」。**create 寫進去的 org 判定,和 patch 讀出來比對的 org 判定不一致**,這是 KBDB 內部 org 歸屬邏輯的 bug。 + +## 重現(裸 curl,不經 arcrun) + +為排除是 arcrun 注入問題,直接用裸 curl + Bearer token 打 `https://kbdb.finally.click`: + +```bash +TOKEN="Bearer ak_402d…" # 同一把 key,全程不變 +BASE=https://kbdb.finally.click + +# 1. 建立 → 201 +curl -X POST $BASE/blocks -H "Authorization: $TOKEN" -H 'Content-Type: application/json' \ + -d '{"content":"...","type":"note","page_name":"kbdb_bug_repro","source":"...","user_id":"arcrun_phase2"}' +# → {"id":"f39ea877-...","action":"created"} HTTP 201 + +# 2. 讀取 → 200 +curl $BASE/blocks/f39ea877-... -H "Authorization: $TOKEN" +# → HTTP 200 + +# 3. 更新 → 403 ★ BUG +curl -X PATCH $BASE/blocks/f39ea877-... -H "Authorization: $TOKEN" -H 'Content-Type: application/json' \ + -d '{"content":"patched"}' +# → {"error":"block belongs to different org"} HTTP 403 + +# 4. 刪除 → 200(證明我擁有此 block) +curl -X DELETE $BASE/blocks/f39ea877-... -H "Authorization: $TOKEN" +# → {"deleted":true} HTTP 200 +``` + +經 arcrun(cypher-executor → auth_static_key 注入同一把 token → recipe 轉發)也是完全相同結果,所以**確定是 KBDB server 端 PATCH 路徑的問題,不是 client / arcrun 的問題**。 + +## 推測方向(給 KBDB 排查) + +create / get / delete 的 org 判定路徑,和 PATCH 的 org 判定路徑不一致。可能: + +1. **PATCH 用了不同的 org 解析來源**:例如 create 用 token → org_id 的某種映射寫入 block,但 PATCH 的 org-check 從另一個欄位 / 另一張表讀,兩邊算出的 org 不同。 +2. **block 落地時的 org_id 與 token 的 org_id 不一致**:create 時可能用了 default org 或 null org 寫入,PATCH 的 ownership 檢查卻嚴格比對 token org,導致「自己建的卻不是自己 org」。 +3. **org-check 是 PATCH 獨有、其他三個 verb 沒做**:所以只有 PATCH 露出這個不一致。 + +建議從「create 時 block 實際寫入的 org_id」對比「PATCH org-check 讀的 org_id」兩個值下手,它們應該相等卻不等。 + +## 對 arcrun 的影響(已隔離,不阻擋 arcrun Phase 2) + +- arcrun 已把 `kbdb_patch_block` 降級成 recipe,recipe 的轉發 + auth 注入經驗證**正確無誤**(請求成功打到 KBDB 的 PATCH handler,非 401)。 +- 403 屬 KBDB 端行為,依 arcrun 原則「能不能打通由發 key 的服務裁決」,這不是 recipe 的 bug。 +- 但 arcrun 的 `kbdb_upsert_block`(GET 查找 → 分支 PATCH/POST)會用到 PATCH,**此 bug 未解前,upsert 的 PATCH 分支無法驗收 2xx**。arcrun 端會把該分支標「未驗收:阻擋於 KBDB PATCH 403」。 + +KBDB 修好後請通知 arcrun,重跑 `kbdb_patch_block` recipe 驗收即可。 diff --git a/docs/HANDOFF-kbdb-plugin.md b/docs/HANDOFF-kbdb-plugin.md new file mode 100644 index 0000000..91870cf --- /dev/null +++ b/docs/HANDOFF-kbdb-plugin.md @@ -0,0 +1,40 @@ +# HANDOFF: KBDB triplet/graph → 改名 **KBDB-graph**,作為 KBDB 插件獨立(2026-06-13) + +來源:InkStoneCo 頂層 `.agents/specs/matrix-rearrange/` Phase 2(R2)。 +**指針式考古**:相關歷史素材在 InkStoneCo `_archive/`,照路徑挖,不複製。 + +## 定位(leo 2026-06-13 澄清) + +此目錄 `matrix/kbdb-graph-plugin`(原 matrix/kbdb,已改名)作為 **KBDB-graph**,是**原有 KBDB 的插件**獨立: +- **原有 KBDB(基本盤)** = `arcrun/kbdb`:D1 三表(blocks/templates/slots)的基本儲存讀寫,已併進 arcrun。 +- **KBDB-graph(本目錄→獨立 repo)** = 掛在基本盤之上的 triplet 採集 + graph 查詢能力。**類比 Apache AGE 之於 Postgres**——插件掛在基本儲存上,不取代它。 +- **為何獨立**:它比較龐大、不是基本存儲功能;且 leo 產權、較複雜。故獨立成 repo,不留在 arcrun。 +- **由此目錄的 CC 自行 `git init` + 推 GitHub**(命名 `KBDB-graph`,帳號 leo 定/uncle6me-web)。 + +## ⚠️ 重大修正(leo 2026-06-14):插件現狀違反 KBDB 鐵律,要改寫 + +**先讀總管決策 `InkStoneCo/.agents/specs/matrix-rearrange/DECISION-kbdb-v3-baseplane.md`。** + +你之前的 design.md 把「現狀代碼直接 SQL 讀 entry_values/blocks」當成「AGE-on-Postgres 訊號」、跑去問「要不要共用 D1 直接 SQL」——**這是讀現狀推翻鐵律的錯**。你自己的 CLAUDE.md 第 83 行就寫了「禁止繞過 API 直接 D1 操作 — API-as-Wall」。現狀代碼是違規的歷史產物,不是設計依據。 + +### KBDB 鐵律(leo 拍板,最高原則) +1. **任何人都不准動表**(CREATE/ALTER/DROP)。只有基本盤維護者能改那 3 表,你不行。 +2. 新資料類型=建 template,永不建表。triplet=`template='triplet'` 寫進 entry_values。 +3. 插件**不准直接接觸表**(禁 SELECT/INSERT/JOIN),讀寫**全走基本盤 API**。 +4. 插件層全程禁 SQL。 + +### 正確形態 +- 基本盤=arcrun/kbdb(3 表 + CRUD API:templates/entries/records/search 端點,已存在),**不動**。它用 `entry_type='block'` 表達 block,無獨立 blocks 表。 +- 你=`triplet` template 定義(`contracts/triplet.json`)+ graph 查詢函式。寫 triplet→調基本盤 records/entries API;查圖→調基本盤 search API 取 entry_values 在插件層組裝。**零建表、零 migration、零 SQL。** + +## 待辦(由本目錄 CC 執行) + +1. **改寫成走 API/薄殼**(核心):`triplet-crud` 的 `INSERT INTO entry_values`、graph/entity 的 `SELECT FROM ...` → 改調基本盤 API(或 MCP/CLI 薄殼,與 AI/人同一條路)。寫 triplet=建 `template='triplet'` + 填 slot(調 `POST /templates`、`POST /records`);查圖=調 search/records API 取回再組裝。**注意**:arcrun 的 MCP/CLI 目前還沒 KBDB tool(要 arcrun 端先補,見 arcrun HANDOFF §2),在那之前先直調基本盤 HTTP API。 +2. **刪除違規 migrations**:`0001_init`/`0005_universal_table` 等含 `CREATE TABLE` 的全刪(插件不建表)。triplets VIEW 改用 API 查 + 插件層組裝。 +3. **改名 `KBDB-graph` + `git init` + 推 GitHub**(帳號 leo 定)。由本 CC 自己推。 +4. **裁剪 CLAUDE.md**:移除整套 KBDB v3 基本盤規範(那屬 arcrun/kbdb),只留 graph 插件相關 + 上面鐵律。 +5. 部署繞開 GitHub(wrangler 直推),禁跨 repo 同步 Actions([[github-flagged-risk]])。 + +## 注意 +- 本目錄現**無獨立 git**。改名後 `git init` + remote(帳號 leo 定),由本 CC 推。 +- arcrun 端基本盤已正確(3 表 + API),不需升 v3、不需搬基本盤。對應交棒見 arcrun `docs/HANDOFF-matrix-rearrange.md` §2。 diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..6a37c58 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,56 @@ +# 文件分類索引 + +> CC 整理文件時的分類依據。找不到分類就問,不要猜。 + +--- + +## 分類規則 + +| 目錄 | 放什麼 | 判斷標準 | +|------|--------|---------| +| **1-vision/** | 為什麼做這個 | 產品願景、北極星、設計哲學 | +| **2-architecture/** | 系統怎麼設計的 | 架構圖、技術棧、元件關係 | +| **2-architecture/decisions/** | 為什麼這樣設計 | ADR,選A不選B的原因 | +| **3-specs/** | 要做什麼 | SDD,每個子系統一個目錄 | +| **4-guides/** | 怎麼做 | 部署、開發流程、CLI 用法 | +| **5-records/** | 發生過什麼 | 歷史記錄,不修改只增加 | +| **5-records/incidents/** | 生產問題復盤 | 故障原因、時間線、改進方案 | +| **5-records/test-reports/** | 測試結果 | 壓測報告、驗收記錄 | +| **6-user/** | 給使用者看的 | README、安裝教學、FAQ | + +--- + +## CC 整理文件時的判斷流程 + +``` +這個文件是... +├── 有明確子系統 + 設計內容? → docs/3-specs/[子系統]/ +├── 解釋為什麼做某個決定? → docs/2-architecture/decisions/ +├── 說明怎麼操作? → docs/4-guides/ +├── 記錄發生過的事? → docs/5-records/ +├── 給外部使用者看的? → docs/6-user/ +└── 以上都不確定? → 列為「待確認」,問負責人 +``` + +--- + +## SDD 結構(docs/3-specs/ 下每個子系統) + +``` +docs/3-specs/[子系統名]/ +├── design.md ← 設計文件(要做什麼、怎麼做、邊界在哪) +└── tasks.md ← 任務清單([ ] 未開始 [🔄] 進行中 [x] 完成) +``` + +CC 動手前必須有這兩個檔案。找不到就停手。 + +--- + +## .claude/wiki/ — CC 的記憶空間(CC 維護,人不手動編輯) + +| 檔案 | 用途 | 更新時機 | +|------|------|---------| +| `INDEX.md` | wiki 導引 | 新增 wiki 檔案時 | +| `mistakes.md` | CC 已知誤解 + 避坑 | 每次被糾正後 | +| `status.md` | 當前進度 + 下一步 | 每次 session 結束 | +| `decisions-summary.md` | 架構決策摘要 | 重大決策後 | diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..77ed501 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1522 @@ +{ + "name": "@inkstone/kbdb-worker", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@inkstone/kbdb-worker", + "version": "0.1.0", + "dependencies": { + "@hono/swagger-ui": "^0.6.1", + "@hono/zod-openapi": "^1.2.4", + "@modelcontextprotocol/sdk": "^1.29.0", + "hono": "^4.7.0", + "unpdf": "^1.4.0", + "zod": "^4.3.6" + }, + "devDependencies": { + "@cloudflare/vitest-pool-workers": "^0.8.0", + "@cloudflare/workers-types": "^4.20250219.0", + "fast-check": "^4.6.0", + "typescript": "^5.7.0", + "vitest": "^3.1.0" + } + }, + "../../node_modules/.pnpm/@cloudflare+vitest-pool-workers@0.8.71_@cloudflare+workers-types@4.20260219.0_@vitest+r_2aaee54dd2f4bccf6bcf93df495dec39/node_modules/@cloudflare/vitest-pool-workers": { + "version": "0.8.71", + "dev": true, + "license": "MIT", + "dependencies": { + "birpc": "0.2.14", + "cjs-module-lexer": "^1.2.3", + "devalue": "^5.3.2", + "miniflare": "4.20250906.0", + "semver": "^7.7.1", + "wrangler": "4.35.0", + "zod": "^3.22.3" + }, + "devDependencies": { + "@cloudflare/eslint-config-worker": "1.1.0", + "@cloudflare/mock-npm-registry": "0.0.0", + "@cloudflare/workers-tsconfig": "0.0.0", + "@cloudflare/workers-types": "^4.20250906.0", + "@types/node": "^20.19.9", + "@types/semver": "^7.5.1", + "@vitest/runner": "~3.2.0", + "@vitest/snapshot": "~3.2.0", + "capnp-es": "^0.0.11", + "ts-dedent": "^2.2.0", + "typescript": "^5.8.3", + "undici": "^7.10.0", + "vitest": "~3.2.0" + }, + "peerDependencies": { + "@vitest/runner": "2.0.x - 3.2.x", + "@vitest/snapshot": "2.0.x - 3.2.x", + "vitest": "2.0.x - 3.2.x" + } + }, + "../../node_modules/.pnpm/@cloudflare+workers-types@4.20260219.0/node_modules/@cloudflare/workers-types": { + "version": "4.20260219.0", + "dev": true, + "license": "MIT OR Apache-2.0" + }, + "../../node_modules/.pnpm/hono@4.11.7/node_modules/hono": { + "version": "4.11.7", + "license": "MIT", + "devDependencies": { + "@hono/eslint-config": "^2.0.5", + "@hono/node-server": "^1.13.5", + "@types/glob": "^9.0.0", + "@types/jsdom": "^21.1.7", + "@types/node": "^24.3.0", + "@typescript/native-preview": "7.0.0-dev.20251220.1", + "@vitest/coverage-v8": "^3.2.4", + "arg": "^5.0.2", + "bun-types": "^1.2.20", + "editorconfig-checker": "6.1.1", + "esbuild": "^0.27.1", + "eslint": "9.39.1", + "glob": "^11.0.0", + "jsdom": "22.1.0", + "msw": "^2.6.0", + "np": "10.2.0", + "oxc-parser": "^0.96.0", + "pkg-pr-new": "^0.0.53", + "prettier": "3.7.4", + "publint": "0.3.15", + "typescript": "^5.9.2", + "undici": "^6.21.3", + "vite-plugin-fastly-js-compute": "^0.4.2", + "vitest": "^3.2.4", + "wrangler": "4.12.0", + "ws": "^8.18.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=16.9.0" + } + }, + "../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript": { + "version": "5.9.3", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "devDependencies": { + "@dprint/formatter": "^0.4.1", + "@dprint/typescript": "0.93.4", + "@esfx/canceltoken": "^1.0.0", + "@eslint/js": "^9.20.0", + "@octokit/rest": "^21.1.1", + "@types/chai": "^4.3.20", + "@types/diff": "^7.0.1", + "@types/minimist": "^1.2.5", + "@types/mocha": "^10.0.10", + "@types/ms": "^0.7.34", + "@types/node": "latest", + "@types/source-map-support": "^0.5.10", + "@types/which": "^3.0.4", + "@typescript-eslint/rule-tester": "^8.24.1", + "@typescript-eslint/type-utils": "^8.24.1", + "@typescript-eslint/utils": "^8.24.1", + "azure-devops-node-api": "^14.1.0", + "c8": "^10.1.3", + "chai": "^4.5.0", + "chokidar": "^4.0.3", + "diff": "^7.0.0", + "dprint": "^0.49.0", + "esbuild": "^0.25.0", + "eslint": "^9.20.1", + "eslint-formatter-autolinkable-stylish": "^1.4.0", + "eslint-plugin-regexp": "^2.7.0", + "fast-xml-parser": "^4.5.2", + "glob": "^10.4.5", + "globals": "^15.15.0", + "hereby": "^1.10.0", + "jsonc-parser": "^3.3.1", + "knip": "^5.44.4", + "minimist": "^1.2.8", + "mocha": "^10.8.2", + "mocha-fivemat-progress-reporter": "^0.1.0", + "monocart-coverage-reports": "^2.12.1", + "ms": "^2.1.3", + "picocolors": "^1.1.1", + "playwright": "^1.50.1", + "source-map-support": "^0.5.21", + "tslib": "^2.8.1", + "typescript": "^5.7.3", + "typescript-eslint": "^8.24.1", + "which": "^3.0.1" + }, + "engines": { + "node": ">=14.17" + } + }, + "../../node_modules/.pnpm/vitest@3.2.4_@types+debug@4.1.12_@types+node@25.3.0_lightningcss@1.31.1/node_modules/vitest": { + "version": "3.2.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/expect": "3.2.4", + "@vitest/mocker": "3.2.4", + "@vitest/pretty-format": "^3.2.4", + "@vitest/runner": "3.2.4", + "@vitest/snapshot": "3.2.4", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "debug": "^4.4.1", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.14", + "tinypool": "^1.1.1", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", + "vite-node": "3.2.4", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "devDependencies": { + "@ampproject/remapping": "^2.3.0", + "@antfu/install-pkg": "^1.1.0", + "@edge-runtime/vm": "^5.0.0", + "@sinonjs/fake-timers": "14.0.0", + "@types/debug": "^4.1.12", + "@types/estree": "^1.0.8", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/jsdom": "^21.1.7", + "@types/mime": "^4.0.0", + "@types/node": "^22.15.32", + "@types/picomatch": "^4.0.0", + "@types/prompts": "^2.4.9", + "@types/sinonjs__fake-timers": "^8.1.5", + "acorn-walk": "^8.3.4", + "birpc": "2.4.0", + "cac": "^6.7.14", + "chai-subset": "^1.6.0", + "find-up": "^6.3.0", + "flatted": "^3.3.3", + "happy-dom": "^17.6.3", + "jsdom": "^26.1.0", + "local-pkg": "^1.1.1", + "mime": "^4.0.7", + "pretty-format": "^29.7.0", + "prompts": "^2.4.2", + "strip-literal": "^3.0.0", + "ws": "^8.18.2" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.2.4", + "@vitest/ui": "3.2.4", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/@asteasolutions/zod-to-openapi": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/@asteasolutions/zod-to-openapi/-/zod-to-openapi-8.5.0.tgz", + "integrity": "sha512-SABbKiObg5dLRiTFnqiW1WWwGcg1BJfmHtT2asIBnBHg6Smy/Ms2KHc650+JI4Hw7lSkdiNebEGXpwoxfben8Q==", + "license": "MIT", + "dependencies": { + "openapi3-ts": "^4.1.2" + }, + "peerDependencies": { + "zod": "^4.0.0" + } + }, + "node_modules/@cloudflare/vitest-pool-workers": { + "resolved": "../../node_modules/.pnpm/@cloudflare+vitest-pool-workers@0.8.71_@cloudflare+workers-types@4.20260219.0_@vitest+r_2aaee54dd2f4bccf6bcf93df495dec39/node_modules/@cloudflare/vitest-pool-workers", + "link": true + }, + "node_modules/@cloudflare/workers-types": { + "resolved": "../../node_modules/.pnpm/@cloudflare+workers-types@4.20260219.0/node_modules/@cloudflare/workers-types", + "link": true + }, + "node_modules/@hono/node-server": { + "version": "1.19.13", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.13.tgz", + "integrity": "sha512-TsQLe4i2gvoTtrHje625ngThGBySOgSK3Xo2XRYOdqGN1teR8+I7vchQC46uLJi8OF62YTYA3AhSpumtkhsaKQ==", + "license": "MIT", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, + "node_modules/@hono/swagger-ui": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@hono/swagger-ui/-/swagger-ui-0.6.1.tgz", + "integrity": "sha512-sJTvldu1GPeEPfyeLG7gRj+W4vEuD+JDi+JjJ3TJs/DvMUtBLs0KJO5yokGegWWdy5qrbdnQGekbhgNRmPmYKQ==", + "license": "MIT", + "peerDependencies": { + "hono": ">=4.0.0" + } + }, + "node_modules/@hono/zod-openapi": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@hono/zod-openapi/-/zod-openapi-1.2.4.tgz", + "integrity": "sha512-cZu71bpODTbtIDoUsIIYPrs58wJ565Tbg6FE+JshU0irBAd6KxrP+k62Amm/mjA7tTOQ3+ingODHKGFOnv+Ibw==", + "license": "MIT", + "dependencies": { + "@asteasolutions/zod-to-openapi": "^8.5.0", + "@hono/zod-validator": "^0.7.6", + "openapi3-ts": "^4.5.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "hono": ">=4.3.6", + "zod": "^4.0.0" + } + }, + "node_modules/@hono/zod-validator": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/@hono/zod-validator/-/zod-validator-0.7.6.tgz", + "integrity": "sha512-Io1B6d011Gj1KknV4rXYz4le5+5EubcWEU/speUjuw9XMMIaP3n78yXLhjd2A3PXaXaUwEAluOiAyLqhBEJgsw==", + "license": "MIT", + "peerDependencies": { + "hono": ">=3.9.0", + "zod": "^3.25.0 || ^4.0.0" + } + }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", + "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", + "license": "MIT", + "dependencies": { + "@hono/node-server": "^1.19.9", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", + "json-schema-typed": "^8.0.2", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/body-parser": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/content-disposition": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", + "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.3.2.tgz", + "integrity": "sha512-77VmFeJkO0/rvimEDuUC5H30oqUC4EyOhyGccfqoLebB0oiEYfM7nwPrsDsBL1gsTpwfzX8SFy2MT3TDyRq+bg==", + "license": "MIT", + "dependencies": { + "ip-address": "10.1.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/fast-check": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-4.6.0.tgz", + "integrity": "sha512-h7H6Dm0Fy+H4ciQYFxFjXnXkzR2kr9Fb22c0UBpHnm59K2zpr2t13aPTHlltFiNT6zuxp6HMPAVVvgur4BLdpA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT", + "dependencies": { + "pure-rand": "^8.0.0" + }, + "engines": { + "node": ">=12.17.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hono": { + "resolved": "../../node_modules/.pnpm/hono@4.11.7/node_modules/hono", + "link": true + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ip-address": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/jose": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.2.tgz", + "integrity": "sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "license": "BSD-2-Clause" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/openapi3-ts": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/openapi3-ts/-/openapi3-ts-4.5.0.tgz", + "integrity": "sha512-jaL+HgTq2Gj5jRcfdutgRGLosCy/hT8sQf6VOy+P+g36cZOjI1iukdPnijC+4CmeRzg/jEllJUboEic2FhxhtQ==", + "license": "MIT", + "dependencies": { + "yaml": "^2.8.0" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-to-regexp": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/pkce-challenge": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pure-rand": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-8.4.0.tgz", + "integrity": "sha512-IoM8YF/jY0hiugFo/wOWqfmarlE6J0wc6fDK1PhftMk7MGhVZl88sZimmqBBFomLOCSmcCCpsfj7wXASCpvK9A==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, + "node_modules/qs": { + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "resolved": "../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript", + "link": true + }, + "node_modules/unpdf": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/unpdf/-/unpdf-1.4.0.tgz", + "integrity": "sha512-TahIk0xdH/4jh/MxfclzU79g40OyxtP00VnEUZdEkJoYtXAHWLiir6t3FC6z3vDqQTzc2ZHcla6uEiVTNjejuA==", + "license": "MIT", + "peerDependencies": { + "@napi-rs/canvas": "^0.1.69" + }, + "peerDependenciesMeta": { + "@napi-rs/canvas": { + "optional": true + } + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vitest": { + "resolved": "../../node_modules/.pnpm/vitest@3.2.4_@types+debug@4.1.12_@types+node@25.3.0_lightningcss@1.31.1/node_modules/vitest", + "link": true + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", + "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/zod": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", + "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.25.28 || ^4" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..1b0cfae --- /dev/null +++ b/package.json @@ -0,0 +1,23 @@ +{ + "name": "kbdb-graph-plugin", + "version": "0.6.0", + "private": true, + "description": "KBDB-graph 插件:triplet 採集 + graph 查詢,掛在基本盤 arcrun/kbdb 之上(API-as-Wall,零建表/零 SQL)", + "scripts": { + "dev": "wrangler dev", + "deploy": "wrangler deploy", + "test": "vitest run", + "test:watch": "vitest" + }, + "dependencies": { + "@hono/swagger-ui": "^0.6.1", + "@hono/zod-openapi": "^1.2.4", + "hono": "^4.7.0", + "zod": "^4.3.6" + }, + "devDependencies": { + "@cloudflare/workers-types": "^4.20250219.0", + "typescript": "^5.7.0", + "vitest": "^3.1.0" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..a995bf9 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,2784 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@hono/swagger-ui': + specifier: ^0.6.1 + version: 0.6.1(hono@4.12.10) + '@hono/zod-openapi': + specifier: ^1.2.4 + version: 1.2.4(hono@4.12.10)(zod@4.3.6) + '@modelcontextprotocol/sdk': + specifier: ^1.29.0 + version: 1.29.0(zod@4.3.6) + hono: + specifier: ^4.7.0 + version: 4.12.10 + unpdf: + specifier: ^1.4.0 + version: 1.4.0 + zod: + specifier: ^4.3.6 + version: 4.3.6 + devDependencies: + '@cloudflare/vitest-pool-workers': + specifier: ^0.8.0 + version: 0.8.71(@cloudflare/workers-types@4.20260404.1)(@vitest/runner@3.2.4)(@vitest/snapshot@3.2.4)(vitest@3.2.4(yaml@2.8.3)) + '@cloudflare/workers-types': + specifier: ^4.20250219.0 + version: 4.20260404.1 + fast-check: + specifier: ^4.6.0 + version: 4.6.0 + typescript: + specifier: ^5.7.0 + version: 5.9.3 + vitest: + specifier: ^3.1.0 + version: 3.2.4(yaml@2.8.3) + +packages: + + '@asteasolutions/zod-to-openapi@8.5.0': + resolution: {integrity: sha512-SABbKiObg5dLRiTFnqiW1WWwGcg1BJfmHtT2asIBnBHg6Smy/Ms2KHc650+JI4Hw7lSkdiNebEGXpwoxfben8Q==} + peerDependencies: + zod: ^4.0.0 + + '@cloudflare/kv-asset-handler@0.4.0': + resolution: {integrity: sha512-+tv3z+SPp+gqTIcImN9o0hqE9xyfQjI1XD9pL6NuKjua9B1y7mNYv0S9cP+QEbA4ppVgGZEmKOvHX5G5Ei1CVA==} + engines: {node: '>=18.0.0'} + + '@cloudflare/unenv-preset@2.7.3': + resolution: {integrity: sha512-tsQQagBKjvpd9baa6nWVIv399ejiqcrUBBW6SZx6Z22+ymm+Odv5+cFimyuCsD/fC1fQTwfRmwXBNpzvHSeGCw==} + peerDependencies: + unenv: 2.0.0-rc.21 + workerd: ^1.20250828.1 + peerDependenciesMeta: + workerd: + optional: true + + '@cloudflare/vitest-pool-workers@0.8.71': + resolution: {integrity: sha512-keu2HCLQfRNwbmLBCDXJgCFpANTaYnQpE01fBOo4CNwiWHUT7SZGN7w64RKiSWRHyYppStXBuE5Ng7F42+flpg==} + peerDependencies: + '@vitest/runner': 2.0.x - 3.2.x + '@vitest/snapshot': 2.0.x - 3.2.x + vitest: 2.0.x - 3.2.x + + '@cloudflare/workerd-darwin-64@1.20250906.0': + resolution: {integrity: sha512-E+X/YYH9BmX0ew2j/mAWFif2z05NMNuhCTlNYEGLkqMe99K15UewBqajL9pMcMUKxylnlrEoK3VNxl33DkbnPA==} + engines: {node: '>=16'} + cpu: [x64] + os: [darwin] + + '@cloudflare/workerd-darwin-arm64@1.20250906.0': + resolution: {integrity: sha512-X5apsZ1SFW4FYTM19ISHf8005FJMPfrcf4U5rO0tdj+TeJgQgXuZ57IG0WeW7SpLVeBo8hM6WC8CovZh41AfnA==} + engines: {node: '>=16'} + cpu: [arm64] + os: [darwin] + + '@cloudflare/workerd-linux-64@1.20250906.0': + resolution: {integrity: sha512-rlKzWgsLnlQ5Nt9W69YBJKcmTmZbOGu0edUsenXPmc6wzULUxoQpi7ZE9k3TfTonJx4WoQsQlzCUamRYFsX+0Q==} + engines: {node: '>=16'} + cpu: [x64] + os: [linux] + + '@cloudflare/workerd-linux-arm64@1.20250906.0': + resolution: {integrity: sha512-DdedhiQ+SeLzpg7BpcLrIPEZ33QKioJQ1wvL4X7nuLzEB9rWzS37NNNahQzc1+44rhG4fyiHbXBPOeox4B9XVA==} + engines: {node: '>=16'} + cpu: [arm64] + os: [linux] + + '@cloudflare/workerd-windows-64@1.20250906.0': + resolution: {integrity: sha512-Q8Qjfs8jGVILnZL6vUpQ90q/8MTCYaGR3d1LGxZMBqte8Vr7xF3KFHPEy7tFs0j0mMjnqCYzlofmPNY+9ZaDRg==} + engines: {node: '>=16'} + cpu: [x64] + os: [win32] + + '@cloudflare/workers-types@4.20260404.1': + resolution: {integrity: sha512-jfZfktdn3D0ceA59i4lkMgPUR9p5Wd6trtNLQR1RTgF59iVt9/yegkiEZv0TQvJPXlmQOU1D0pAYz7cMztqErg==} + + '@cspotcode/source-map-support@0.8.1': + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} + + '@emnapi/runtime@1.9.2': + resolution: {integrity: sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==} + + '@esbuild/aix-ppc64@0.25.4': + resolution: {integrity: sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/aix-ppc64@0.27.7': + resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.4': + resolution: {integrity: sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm64@0.27.7': + resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.4': + resolution: {integrity: sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-arm@0.27.7': + resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.4': + resolution: {integrity: sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/android-x64@0.27.7': + resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.4': + resolution: {integrity: sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-arm64@0.27.7': + resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.4': + resolution: {integrity: sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/darwin-x64@0.27.7': + resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.4': + resolution: {integrity: sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-arm64@0.27.7': + resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.4': + resolution: {integrity: sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.27.7': + resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.4': + resolution: {integrity: sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm64@0.27.7': + resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.4': + resolution: {integrity: sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-arm@0.27.7': + resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.4': + resolution: {integrity: sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-ia32@0.27.7': + resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.4': + resolution: {integrity: sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-loong64@0.27.7': + resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.4': + resolution: {integrity: sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-mips64el@0.27.7': + resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.4': + resolution: {integrity: sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-ppc64@0.27.7': + resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.4': + resolution: {integrity: sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-riscv64@0.27.7': + resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.4': + resolution: {integrity: sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-s390x@0.27.7': + resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.4': + resolution: {integrity: sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/linux-x64@0.27.7': + resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.4': + resolution: {integrity: sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-arm64@0.27.7': + resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.4': + resolution: {integrity: sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.27.7': + resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.4': + resolution: {integrity: sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-arm64@0.27.7': + resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.4': + resolution: {integrity: sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.27.7': + resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.27.7': + resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.25.4': + resolution: {integrity: sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/sunos-x64@0.27.7': + resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.4': + resolution: {integrity: sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-arm64@0.27.7': + resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.4': + resolution: {integrity: sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-ia32@0.27.7': + resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.4': + resolution: {integrity: sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@esbuild/win32-x64@0.27.7': + resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@hono/node-server@1.19.12': + resolution: {integrity: sha512-txsUW4SQ1iilgE0l9/e9VQWmELXifEFvmdA1j6WFh/aFPj99hIntrSsq/if0UWyGVkmrRPKA1wCeP+UCr1B9Uw==} + engines: {node: '>=18.14.1'} + peerDependencies: + hono: ^4 + + '@hono/swagger-ui@0.6.1': + resolution: {integrity: sha512-sJTvldu1GPeEPfyeLG7gRj+W4vEuD+JDi+JjJ3TJs/DvMUtBLs0KJO5yokGegWWdy5qrbdnQGekbhgNRmPmYKQ==} + peerDependencies: + hono: '>=4.0.0' + + '@hono/zod-openapi@1.2.4': + resolution: {integrity: sha512-cZu71bpODTbtIDoUsIIYPrs58wJ565Tbg6FE+JshU0irBAd6KxrP+k62Amm/mjA7tTOQ3+ingODHKGFOnv+Ibw==} + engines: {node: '>=16.0.0'} + peerDependencies: + hono: '>=4.3.6' + zod: ^4.0.0 + + '@hono/zod-validator@0.7.6': + resolution: {integrity: sha512-Io1B6d011Gj1KknV4rXYz4le5+5EubcWEU/speUjuw9XMMIaP3n78yXLhjd2A3PXaXaUwEAluOiAyLqhBEJgsw==} + peerDependencies: + hono: '>=3.9.0' + zod: ^3.25.0 || ^4.0.0 + + '@img/sharp-darwin-arm64@0.33.5': + resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.33.5': + resolution: {integrity: sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-darwin-arm64@1.0.4': + resolution: {integrity: sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.0.4': + resolution: {integrity: sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.0.4': + resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-arm@1.0.5': + resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-s390x@1.0.4': + resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-x64@1.0.4': + resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linuxmusl-arm64@1.0.4': + resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-libvips-linuxmusl-x64@1.0.4': + resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-linux-arm64@0.33.5': + resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-arm@0.33.5': + resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-s390x@0.33.5': + resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-x64@0.33.5': + resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-linuxmusl-arm64@0.33.5': + resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-linuxmusl-x64@0.33.5': + resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-wasm32@0.33.5': + resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + + '@img/sharp-win32-ia32@0.33.5': + resolution: {integrity: sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.33.5': + resolution: {integrity: sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==} + 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==} + + '@modelcontextprotocol/sdk@1.29.0': + resolution: {integrity: sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==} + engines: {node: '>=18'} + peerDependencies: + '@cfworker/json-schema': ^4.1.1 + zod: ^3.25 || ^4.0 + peerDependenciesMeta: + '@cfworker/json-schema': + optional: true + + '@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==} + + '@rollup/rollup-android-arm-eabi@4.60.1': + resolution: {integrity: sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.60.1': + resolution: {integrity: sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.60.1': + resolution: {integrity: sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.60.1': + resolution: {integrity: sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.60.1': + resolution: {integrity: sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.60.1': + resolution: {integrity: sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.60.1': + resolution: {integrity: sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.60.1': + resolution: {integrity: sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.60.1': + resolution: {integrity: sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.60.1': + resolution: {integrity: sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loong64-gnu@4.60.1': + resolution: {integrity: sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-loong64-musl@4.60.1': + resolution: {integrity: sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==} + cpu: [loong64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-ppc64-gnu@4.60.1': + resolution: {integrity: sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-ppc64-musl@4.60.1': + resolution: {integrity: sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==} + cpu: [ppc64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-riscv64-gnu@4.60.1': + resolution: {integrity: sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-musl@4.60.1': + resolution: {integrity: sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-s390x-gnu@4.60.1': + resolution: {integrity: sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.60.1': + resolution: {integrity: sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.60.1': + resolution: {integrity: sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-openbsd-x64@4.60.1': + resolution: {integrity: sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.60.1': + resolution: {integrity: sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.60.1': + resolution: {integrity: sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.60.1': + resolution: {integrity: sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.60.1': + resolution: {integrity: sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.60.1': + resolution: {integrity: sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==} + cpu: [x64] + os: [win32] + + '@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==} + + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@vitest/expect@3.2.4': + resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} + + '@vitest/mocker@3.2.4': + resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@3.2.4': + resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} + + '@vitest/runner@3.2.4': + resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==} + + '@vitest/snapshot@3.2.4': + resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==} + + '@vitest/spy@3.2.4': + resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} + + '@vitest/utils@3.2.4': + resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} + + accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} + engines: {node: '>= 0.6'} + + acorn-walk@8.3.2: + resolution: {integrity: sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==} + engines: {node: '>=0.4.0'} + + acorn@8.14.0: + resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv-formats@3.0.1: + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv@8.18.0: + resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + birpc@0.2.14: + resolution: {integrity: sha512-37FHE8rqsYM5JEKCnXFyHpBCzvgHEExwVVTq+nUmloInU7l8ezD1TpOhKpS8oe1DTYFqEK27rFZVKG43oTqXRA==} + + blake3-wasm@2.1.5: + resolution: {integrity: sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==} + + body-parser@2.2.2: + resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==} + engines: {node: '>=18'} + + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + chai@5.3.3: + resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} + engines: {node: '>=18'} + + check-error@2.1.3: + resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==} + engines: {node: '>= 16'} + + cjs-module-lexer@1.4.3: + resolution: {integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + color-string@1.9.1: + resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} + + color@4.2.3: + resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} + engines: {node: '>=12.5.0'} + + content-disposition@1.0.1: + resolution: {integrity: sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==} + engines: {node: '>=18'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + cookie-signature@1.2.2: + resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} + engines: {node: '>=6.6.0'} + + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + + cookie@1.1.1: + resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} + engines: {node: '>=18'} + + cors@2.8.6: + resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} + engines: {node: '>= 0.10'} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + + defu@6.1.6: + resolution: {integrity: sha512-f8mefEW4WIVg4LckePx3mALjQSPQgFlg9U8yaPdlsbdYcHQyj9n2zL2LJEA52smeYxOvmd/nB7TpMtHGMTHcug==} + + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + devalue@5.6.4: + resolution: {integrity: sha512-Gp6rDldRsFh/7XuouDbxMH3Mx8GMCcgzIb1pDTvNyn8pZGQ22u+Wa+lGV9dQCltFQ7uVw0MhRyb8XDskNFOReA==} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + + error-stack-parser-es@1.0.5: + resolution: {integrity: sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + esbuild@0.25.4: + resolution: {integrity: sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q==} + engines: {node: '>=18'} + hasBin: true + + esbuild@0.27.7: + resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} + engines: {node: '>=18'} + hasBin: true + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + eventsource-parser@3.0.6: + resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==} + engines: {node: '>=18.0.0'} + + eventsource@3.0.7: + resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} + engines: {node: '>=18.0.0'} + + exit-hook@2.2.1: + resolution: {integrity: sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==} + engines: {node: '>=6'} + + expect-type@1.3.0: + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + engines: {node: '>=12.0.0'} + + express-rate-limit@8.3.2: + resolution: {integrity: sha512-77VmFeJkO0/rvimEDuUC5H30oqUC4EyOhyGccfqoLebB0oiEYfM7nwPrsDsBL1gsTpwfzX8SFy2MT3TDyRq+bg==} + engines: {node: '>= 16'} + peerDependencies: + express: '>= 4.11' + + express@5.2.1: + resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} + engines: {node: '>= 18'} + + exsolve@1.0.8: + resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} + + fast-check@4.6.0: + resolution: {integrity: sha512-h7H6Dm0Fy+H4ciQYFxFjXnXkzR2kr9Fb22c0UBpHnm59K2zpr2t13aPTHlltFiNT6zuxp6HMPAVVvgur4BLdpA==} + engines: {node: '>=12.17.0'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-uri@3.1.0: + resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + finalhandler@2.1.1: + resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} + engines: {node: '>= 18.0.0'} + + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + + fresh@2.0.0: + resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} + engines: {node: '>= 0.8'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + glob-to-regexp@0.4.1: + resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + hono@4.12.10: + resolution: {integrity: sha512-mx/p18PLy5og9ufies2GOSUqep98Td9q4i/EF6X7yJgAiIopxqdfIO3jbqsi3jRgTgw88jMDEzVKi+V2EF+27w==} + engines: {node: '>=16.9.0'} + + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + engines: {node: '>= 0.8'} + + iconv-lite@0.7.2: + resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} + engines: {node: '>=0.10.0'} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ip-address@10.1.0: + resolution: {integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==} + engines: {node: '>= 12'} + + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + + is-arrayish@0.3.4: + resolution: {integrity: sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==} + + is-promise@4.0.0: + resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + jose@6.2.2: + resolution: {integrity: sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ==} + + js-tokens@9.0.1: + resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + json-schema-typed@8.0.2: + resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==} + + kleur@4.1.5: + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} + engines: {node: '>=6'} + + loupe@3.2.1: + resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + media-typer@1.1.0: + resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} + engines: {node: '>= 0.8'} + + merge-descriptors@2.0.0: + resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} + engines: {node: '>=18'} + + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + + mime-types@3.0.2: + resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} + engines: {node: '>=18'} + + mime@3.0.0: + resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} + engines: {node: '>=10.0.0'} + hasBin: true + + miniflare@4.20250906.0: + resolution: {integrity: sha512-T/RWn1sa0ien80s6NjU+Un/tj12gR6wqScZoiLeMJDD4/fK0UXfnbWXJDubnUED8Xjm7RPQ5ESYdE+mhPmMtuQ==} + engines: {node: '>=18.0.0'} + hasBin: true + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + negotiator@1.0.0: + resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} + engines: {node: '>= 0.6'} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + ohash@2.0.11: + resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} + + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + openapi3-ts@4.5.0: + resolution: {integrity: sha512-jaL+HgTq2Gj5jRcfdutgRGLosCy/hT8sQf6VOy+P+g36cZOjI1iukdPnijC+4CmeRzg/jEllJUboEic2FhxhtQ==} + + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-to-regexp@6.3.0: + resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} + + path-to-regexp@8.4.2: + resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + pathval@2.0.1: + resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} + engines: {node: '>= 14.16'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + + pkce-challenge@5.0.1: + resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==} + engines: {node: '>=16.20.0'} + + postcss@8.5.8: + resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==} + engines: {node: ^10 || ^12 || >=14} + + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + + pure-rand@8.4.0: + resolution: {integrity: sha512-IoM8YF/jY0hiugFo/wOWqfmarlE6J0wc6fDK1PhftMk7MGhVZl88sZimmqBBFomLOCSmcCCpsfj7wXASCpvK9A==} + + qs@6.15.0: + resolution: {integrity: sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==} + engines: {node: '>=0.6'} + + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + + raw-body@3.0.2: + resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} + engines: {node: '>= 0.10'} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + rollup@4.60.1: + resolution: {integrity: sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + router@2.2.0: + resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} + engines: {node: '>= 18'} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} + engines: {node: '>=10'} + hasBin: true + + send@1.2.1: + resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} + engines: {node: '>= 18'} + + serve-static@2.2.1: + resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} + engines: {node: '>= 18'} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + sharp@0.33.5: + resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + simple-swizzle@0.2.4: + resolution: {integrity: sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + + std-env@3.10.0: + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + + stoppable@1.1.0: + resolution: {integrity: sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==} + engines: {node: '>=4', npm: '>=6'} + + strip-literal@3.1.0: + resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==} + + supports-color@10.2.2: + resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==} + engines: {node: '>=18'} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + + tinypool@1.1.1: + resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@2.0.0: + resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} + engines: {node: '>=14.0.0'} + + tinyspy@4.0.4: + resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==} + engines: {node: '>=14.0.0'} + + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + type-is@2.0.1: + resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} + engines: {node: '>= 0.6'} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + ufo@1.6.3: + resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==} + + undici@7.24.7: + resolution: {integrity: sha512-H/nlJ/h0ggGC+uRL3ovD+G0i4bqhvsDOpbDv7At5eFLlj2b41L8QliGbnl2H7SnDiYhENphh1tQFJZf+MyfLsQ==} + engines: {node: '>=20.18.1'} + + unenv@2.0.0-rc.21: + resolution: {integrity: sha512-Wj7/AMtE9MRnAXa6Su3Lk0LNCfqDYgfwVjwRFVum9U7wsto1imuHqk4kTm7Jni+5A0Hn7dttL6O/zjvUvoo+8A==} + + unpdf@1.4.0: + resolution: {integrity: sha512-TahIk0xdH/4jh/MxfclzU79g40OyxtP00VnEUZdEkJoYtXAHWLiir6t3FC6z3vDqQTzc2ZHcla6uEiVTNjejuA==} + peerDependencies: + '@napi-rs/canvas': ^0.1.69 + peerDependenciesMeta: + '@napi-rs/canvas': + optional: true + + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + + vite-node@3.2.4: + resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + + vite@7.3.1: + resolution: {integrity: sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@3.2.4: + resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/debug': ^4.1.12 + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + '@vitest/browser': 3.2.4 + '@vitest/ui': 3.2.4 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/debug': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + + workerd@1.20250906.0: + resolution: {integrity: sha512-ryVyEaqXPPsr/AxccRmYZZmDAkfQVjhfRqrNTlEeN8aftBk6Ca1u7/VqmfOayjCXrA+O547TauebU+J3IpvFXw==} + engines: {node: '>=16'} + hasBin: true + + wrangler@4.35.0: + resolution: {integrity: sha512-HbyXtbrh4Fi3mU8ussY85tVdQ74qpVS1vctUgaPc+bPrXBTqfDLkZ6VRtHAVF/eBhz4SFmhJtCQpN1caY2Ak8A==} + engines: {node: '>=18.0.0'} + hasBin: true + peerDependencies: + '@cloudflare/workers-types': ^4.20250906.0 + peerDependenciesMeta: + '@cloudflare/workers-types': + optional: true + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + 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 + + yaml@2.8.3: + resolution: {integrity: sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==} + engines: {node: '>= 14.6'} + hasBin: true + + youch-core@0.3.3: + resolution: {integrity: sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==} + + youch@4.1.0-beta.10: + resolution: {integrity: sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==} + + zod-to-json-schema@3.25.2: + resolution: {integrity: sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==} + peerDependencies: + zod: ^3.25.28 || ^4 + + zod@3.22.3: + resolution: {integrity: sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==} + + zod@3.23.8: + resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} + + zod@4.3.6: + resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} + +snapshots: + + '@asteasolutions/zod-to-openapi@8.5.0(zod@4.3.6)': + dependencies: + openapi3-ts: 4.5.0 + zod: 4.3.6 + + '@cloudflare/kv-asset-handler@0.4.0': + dependencies: + mime: 3.0.0 + + '@cloudflare/unenv-preset@2.7.3(unenv@2.0.0-rc.21)(workerd@1.20250906.0)': + dependencies: + unenv: 2.0.0-rc.21 + optionalDependencies: + workerd: 1.20250906.0 + + '@cloudflare/vitest-pool-workers@0.8.71(@cloudflare/workers-types@4.20260404.1)(@vitest/runner@3.2.4)(@vitest/snapshot@3.2.4)(vitest@3.2.4(yaml@2.8.3))': + dependencies: + '@vitest/runner': 3.2.4 + '@vitest/snapshot': 3.2.4 + birpc: 0.2.14 + cjs-module-lexer: 1.4.3 + devalue: 5.6.4 + miniflare: 4.20250906.0 + semver: 7.7.4 + vitest: 3.2.4(yaml@2.8.3) + wrangler: 4.35.0(@cloudflare/workers-types@4.20260404.1) + zod: 3.23.8 + transitivePeerDependencies: + - '@cloudflare/workers-types' + - bufferutil + - utf-8-validate + + '@cloudflare/workerd-darwin-64@1.20250906.0': + optional: true + + '@cloudflare/workerd-darwin-arm64@1.20250906.0': + optional: true + + '@cloudflare/workerd-linux-64@1.20250906.0': + optional: true + + '@cloudflare/workerd-linux-arm64@1.20250906.0': + optional: true + + '@cloudflare/workerd-windows-64@1.20250906.0': + optional: true + + '@cloudflare/workers-types@4.20260404.1': {} + + '@cspotcode/source-map-support@0.8.1': + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + + '@emnapi/runtime@1.9.2': + dependencies: + tslib: 2.8.1 + optional: true + + '@esbuild/aix-ppc64@0.25.4': + optional: true + + '@esbuild/aix-ppc64@0.27.7': + optional: true + + '@esbuild/android-arm64@0.25.4': + optional: true + + '@esbuild/android-arm64@0.27.7': + optional: true + + '@esbuild/android-arm@0.25.4': + optional: true + + '@esbuild/android-arm@0.27.7': + optional: true + + '@esbuild/android-x64@0.25.4': + optional: true + + '@esbuild/android-x64@0.27.7': + optional: true + + '@esbuild/darwin-arm64@0.25.4': + optional: true + + '@esbuild/darwin-arm64@0.27.7': + optional: true + + '@esbuild/darwin-x64@0.25.4': + optional: true + + '@esbuild/darwin-x64@0.27.7': + optional: true + + '@esbuild/freebsd-arm64@0.25.4': + optional: true + + '@esbuild/freebsd-arm64@0.27.7': + optional: true + + '@esbuild/freebsd-x64@0.25.4': + optional: true + + '@esbuild/freebsd-x64@0.27.7': + optional: true + + '@esbuild/linux-arm64@0.25.4': + optional: true + + '@esbuild/linux-arm64@0.27.7': + optional: true + + '@esbuild/linux-arm@0.25.4': + optional: true + + '@esbuild/linux-arm@0.27.7': + optional: true + + '@esbuild/linux-ia32@0.25.4': + optional: true + + '@esbuild/linux-ia32@0.27.7': + optional: true + + '@esbuild/linux-loong64@0.25.4': + optional: true + + '@esbuild/linux-loong64@0.27.7': + optional: true + + '@esbuild/linux-mips64el@0.25.4': + optional: true + + '@esbuild/linux-mips64el@0.27.7': + optional: true + + '@esbuild/linux-ppc64@0.25.4': + optional: true + + '@esbuild/linux-ppc64@0.27.7': + optional: true + + '@esbuild/linux-riscv64@0.25.4': + optional: true + + '@esbuild/linux-riscv64@0.27.7': + optional: true + + '@esbuild/linux-s390x@0.25.4': + optional: true + + '@esbuild/linux-s390x@0.27.7': + optional: true + + '@esbuild/linux-x64@0.25.4': + optional: true + + '@esbuild/linux-x64@0.27.7': + optional: true + + '@esbuild/netbsd-arm64@0.25.4': + optional: true + + '@esbuild/netbsd-arm64@0.27.7': + optional: true + + '@esbuild/netbsd-x64@0.25.4': + optional: true + + '@esbuild/netbsd-x64@0.27.7': + optional: true + + '@esbuild/openbsd-arm64@0.25.4': + optional: true + + '@esbuild/openbsd-arm64@0.27.7': + optional: true + + '@esbuild/openbsd-x64@0.25.4': + optional: true + + '@esbuild/openbsd-x64@0.27.7': + optional: true + + '@esbuild/openharmony-arm64@0.27.7': + optional: true + + '@esbuild/sunos-x64@0.25.4': + optional: true + + '@esbuild/sunos-x64@0.27.7': + optional: true + + '@esbuild/win32-arm64@0.25.4': + optional: true + + '@esbuild/win32-arm64@0.27.7': + optional: true + + '@esbuild/win32-ia32@0.25.4': + optional: true + + '@esbuild/win32-ia32@0.27.7': + optional: true + + '@esbuild/win32-x64@0.25.4': + optional: true + + '@esbuild/win32-x64@0.27.7': + optional: true + + '@hono/node-server@1.19.12(hono@4.12.10)': + dependencies: + hono: 4.12.10 + + '@hono/swagger-ui@0.6.1(hono@4.12.10)': + dependencies: + hono: 4.12.10 + + '@hono/zod-openapi@1.2.4(hono@4.12.10)(zod@4.3.6)': + dependencies: + '@asteasolutions/zod-to-openapi': 8.5.0(zod@4.3.6) + '@hono/zod-validator': 0.7.6(hono@4.12.10)(zod@4.3.6) + hono: 4.12.10 + openapi3-ts: 4.5.0 + zod: 4.3.6 + + '@hono/zod-validator@0.7.6(hono@4.12.10)(zod@4.3.6)': + dependencies: + hono: 4.12.10 + zod: 4.3.6 + + '@img/sharp-darwin-arm64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.0.4 + optional: true + + '@img/sharp-darwin-x64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.0.4 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.0.4': + optional: true + + '@img/sharp-libvips-darwin-x64@1.0.4': + optional: true + + '@img/sharp-libvips-linux-arm64@1.0.4': + optional: true + + '@img/sharp-libvips-linux-arm@1.0.5': + optional: true + + '@img/sharp-libvips-linux-s390x@1.0.4': + optional: true + + '@img/sharp-libvips-linux-x64@1.0.4': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.0.4': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.0.4': + optional: true + + '@img/sharp-linux-arm64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.0.4 + optional: true + + '@img/sharp-linux-arm@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.0.5 + optional: true + + '@img/sharp-linux-s390x@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.0.4 + optional: true + + '@img/sharp-linux-x64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.0.4 + optional: true + + '@img/sharp-linuxmusl-arm64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 + optional: true + + '@img/sharp-linuxmusl-x64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.0.4 + optional: true + + '@img/sharp-wasm32@0.33.5': + dependencies: + '@emnapi/runtime': 1.9.2 + optional: true + + '@img/sharp-win32-ia32@0.33.5': + optional: true + + '@img/sharp-win32-x64@0.33.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 + + '@modelcontextprotocol/sdk@1.29.0(zod@4.3.6)': + dependencies: + '@hono/node-server': 1.19.12(hono@4.12.10) + ajv: 8.18.0 + ajv-formats: 3.0.1(ajv@8.18.0) + content-type: 1.0.5 + cors: 2.8.6 + cross-spawn: 7.0.6 + eventsource: 3.0.7 + eventsource-parser: 3.0.6 + express: 5.2.1 + express-rate-limit: 8.3.2(express@5.2.1) + hono: 4.12.10 + jose: 6.2.2 + json-schema-typed: 8.0.2 + pkce-challenge: 5.0.1 + raw-body: 3.0.2 + zod: 4.3.6 + zod-to-json-schema: 3.25.2(zod@4.3.6) + transitivePeerDependencies: + - supports-color + + '@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': {} + + '@rollup/rollup-android-arm-eabi@4.60.1': + optional: true + + '@rollup/rollup-android-arm64@4.60.1': + optional: true + + '@rollup/rollup-darwin-arm64@4.60.1': + optional: true + + '@rollup/rollup-darwin-x64@4.60.1': + optional: true + + '@rollup/rollup-freebsd-arm64@4.60.1': + optional: true + + '@rollup/rollup-freebsd-x64@4.60.1': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.60.1': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.60.1': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.60.1': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.60.1': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.60.1': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.60.1': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.60.1': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.60.1': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.60.1': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.60.1': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.60.1': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.60.1': + optional: true + + '@rollup/rollup-linux-x64-musl@4.60.1': + optional: true + + '@rollup/rollup-openbsd-x64@4.60.1': + optional: true + + '@rollup/rollup-openharmony-arm64@4.60.1': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.60.1': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.60.1': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.60.1': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.60.1': + optional: true + + '@sindresorhus/is@7.2.0': {} + + '@speed-highlight/core@1.2.15': {} + + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/deep-eql@4.0.2': {} + + '@types/estree@1.0.8': {} + + '@vitest/expect@3.2.4': + dependencies: + '@types/chai': 5.2.3 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.3.3 + tinyrainbow: 2.0.0 + + '@vitest/mocker@3.2.4(vite@7.3.1(yaml@2.8.3))': + dependencies: + '@vitest/spy': 3.2.4 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 7.3.1(yaml@2.8.3) + + '@vitest/pretty-format@3.2.4': + dependencies: + tinyrainbow: 2.0.0 + + '@vitest/runner@3.2.4': + dependencies: + '@vitest/utils': 3.2.4 + pathe: 2.0.3 + strip-literal: 3.1.0 + + '@vitest/snapshot@3.2.4': + dependencies: + '@vitest/pretty-format': 3.2.4 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@3.2.4': + dependencies: + tinyspy: 4.0.4 + + '@vitest/utils@3.2.4': + dependencies: + '@vitest/pretty-format': 3.2.4 + loupe: 3.2.1 + tinyrainbow: 2.0.0 + + accepts@2.0.0: + dependencies: + mime-types: 3.0.2 + negotiator: 1.0.0 + + acorn-walk@8.3.2: {} + + acorn@8.14.0: {} + + ajv-formats@3.0.1(ajv@8.18.0): + optionalDependencies: + ajv: 8.18.0 + + ajv@8.18.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.0 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + assertion-error@2.0.1: {} + + birpc@0.2.14: {} + + blake3-wasm@2.1.5: {} + + body-parser@2.2.2: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 4.4.3 + http-errors: 2.0.1 + iconv-lite: 0.7.2 + on-finished: 2.4.1 + qs: 6.15.0 + raw-body: 3.0.2 + type-is: 2.0.1 + transitivePeerDependencies: + - supports-color + + bytes@3.1.2: {} + + cac@6.7.14: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + chai@5.3.3: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.3 + deep-eql: 5.0.2 + loupe: 3.2.1 + pathval: 2.0.1 + + check-error@2.1.3: {} + + cjs-module-lexer@1.4.3: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + color-string@1.9.1: + dependencies: + color-name: 1.1.4 + simple-swizzle: 0.2.4 + + color@4.2.3: + dependencies: + color-convert: 2.0.1 + color-string: 1.9.1 + + content-disposition@1.0.1: {} + + content-type@1.0.5: {} + + cookie-signature@1.2.2: {} + + cookie@0.7.2: {} + + cookie@1.1.1: {} + + cors@2.8.6: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + deep-eql@5.0.2: {} + + defu@6.1.6: {} + + depd@2.0.0: {} + + detect-libc@2.1.2: {} + + devalue@5.6.4: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + ee-first@1.1.1: {} + + encodeurl@2.0.0: {} + + error-stack-parser-es@1.0.5: {} + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-module-lexer@1.7.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + esbuild@0.25.4: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.4 + '@esbuild/android-arm': 0.25.4 + '@esbuild/android-arm64': 0.25.4 + '@esbuild/android-x64': 0.25.4 + '@esbuild/darwin-arm64': 0.25.4 + '@esbuild/darwin-x64': 0.25.4 + '@esbuild/freebsd-arm64': 0.25.4 + '@esbuild/freebsd-x64': 0.25.4 + '@esbuild/linux-arm': 0.25.4 + '@esbuild/linux-arm64': 0.25.4 + '@esbuild/linux-ia32': 0.25.4 + '@esbuild/linux-loong64': 0.25.4 + '@esbuild/linux-mips64el': 0.25.4 + '@esbuild/linux-ppc64': 0.25.4 + '@esbuild/linux-riscv64': 0.25.4 + '@esbuild/linux-s390x': 0.25.4 + '@esbuild/linux-x64': 0.25.4 + '@esbuild/netbsd-arm64': 0.25.4 + '@esbuild/netbsd-x64': 0.25.4 + '@esbuild/openbsd-arm64': 0.25.4 + '@esbuild/openbsd-x64': 0.25.4 + '@esbuild/sunos-x64': 0.25.4 + '@esbuild/win32-arm64': 0.25.4 + '@esbuild/win32-ia32': 0.25.4 + '@esbuild/win32-x64': 0.25.4 + + esbuild@0.27.7: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.7 + '@esbuild/android-arm': 0.27.7 + '@esbuild/android-arm64': 0.27.7 + '@esbuild/android-x64': 0.27.7 + '@esbuild/darwin-arm64': 0.27.7 + '@esbuild/darwin-x64': 0.27.7 + '@esbuild/freebsd-arm64': 0.27.7 + '@esbuild/freebsd-x64': 0.27.7 + '@esbuild/linux-arm': 0.27.7 + '@esbuild/linux-arm64': 0.27.7 + '@esbuild/linux-ia32': 0.27.7 + '@esbuild/linux-loong64': 0.27.7 + '@esbuild/linux-mips64el': 0.27.7 + '@esbuild/linux-ppc64': 0.27.7 + '@esbuild/linux-riscv64': 0.27.7 + '@esbuild/linux-s390x': 0.27.7 + '@esbuild/linux-x64': 0.27.7 + '@esbuild/netbsd-arm64': 0.27.7 + '@esbuild/netbsd-x64': 0.27.7 + '@esbuild/openbsd-arm64': 0.27.7 + '@esbuild/openbsd-x64': 0.27.7 + '@esbuild/openharmony-arm64': 0.27.7 + '@esbuild/sunos-x64': 0.27.7 + '@esbuild/win32-arm64': 0.27.7 + '@esbuild/win32-ia32': 0.27.7 + '@esbuild/win32-x64': 0.27.7 + + escape-html@1.0.3: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + + etag@1.8.1: {} + + eventsource-parser@3.0.6: {} + + eventsource@3.0.7: + dependencies: + eventsource-parser: 3.0.6 + + exit-hook@2.2.1: {} + + expect-type@1.3.0: {} + + express-rate-limit@8.3.2(express@5.2.1): + dependencies: + express: 5.2.1 + ip-address: 10.1.0 + + express@5.2.1: + dependencies: + accepts: 2.0.0 + body-parser: 2.2.2 + content-disposition: 1.0.1 + content-type: 1.0.5 + cookie: 0.7.2 + cookie-signature: 1.2.2 + debug: 4.4.3 + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 2.1.1 + fresh: 2.0.0 + http-errors: 2.0.1 + merge-descriptors: 2.0.0 + mime-types: 3.0.2 + on-finished: 2.4.1 + once: 1.4.0 + parseurl: 1.3.3 + proxy-addr: 2.0.7 + qs: 6.15.0 + range-parser: 1.2.1 + router: 2.2.0 + send: 1.2.1 + serve-static: 2.2.1 + statuses: 2.0.2 + type-is: 2.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + exsolve@1.0.8: {} + + fast-check@4.6.0: + dependencies: + pure-rand: 8.4.0 + + fast-deep-equal@3.1.3: {} + + fast-uri@3.1.0: {} + + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + + finalhandler@2.1.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + forwarded@0.2.0: {} + + fresh@2.0.0: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + glob-to-regexp@0.4.1: {} + + gopd@1.2.0: {} + + has-symbols@1.1.0: {} + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + hono@4.12.10: {} + + http-errors@2.0.1: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 + + iconv-lite@0.7.2: + dependencies: + safer-buffer: 2.1.2 + + inherits@2.0.4: {} + + ip-address@10.1.0: {} + + ipaddr.js@1.9.1: {} + + is-arrayish@0.3.4: {} + + is-promise@4.0.0: {} + + isexe@2.0.0: {} + + jose@6.2.2: {} + + js-tokens@9.0.1: {} + + json-schema-traverse@1.0.0: {} + + json-schema-typed@8.0.2: {} + + kleur@4.1.5: {} + + loupe@3.2.1: {} + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + math-intrinsics@1.1.0: {} + + media-typer@1.1.0: {} + + merge-descriptors@2.0.0: {} + + mime-db@1.54.0: {} + + mime-types@3.0.2: + dependencies: + mime-db: 1.54.0 + + mime@3.0.0: {} + + miniflare@4.20250906.0: + dependencies: + '@cspotcode/source-map-support': 0.8.1 + acorn: 8.14.0 + acorn-walk: 8.3.2 + exit-hook: 2.2.1 + glob-to-regexp: 0.4.1 + sharp: 0.33.5 + stoppable: 1.1.0 + undici: 7.24.7 + workerd: 1.20250906.0 + ws: 8.18.0 + youch: 4.1.0-beta.10 + zod: 3.22.3 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + ms@2.1.3: {} + + nanoid@3.3.11: {} + + negotiator@1.0.0: {} + + object-assign@4.1.1: {} + + object-inspect@1.13.4: {} + + ohash@2.0.11: {} + + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + openapi3-ts@4.5.0: + dependencies: + yaml: 2.8.3 + + parseurl@1.3.3: {} + + path-key@3.1.1: {} + + path-to-regexp@6.3.0: {} + + path-to-regexp@8.4.2: {} + + pathe@2.0.3: {} + + pathval@2.0.1: {} + + picocolors@1.1.1: {} + + picomatch@4.0.4: {} + + pkce-challenge@5.0.1: {} + + postcss@8.5.8: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + + pure-rand@8.4.0: {} + + qs@6.15.0: + dependencies: + side-channel: 1.1.0 + + range-parser@1.2.1: {} + + raw-body@3.0.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.7.2 + unpipe: 1.0.0 + + require-from-string@2.0.2: {} + + rollup@4.60.1: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.60.1 + '@rollup/rollup-android-arm64': 4.60.1 + '@rollup/rollup-darwin-arm64': 4.60.1 + '@rollup/rollup-darwin-x64': 4.60.1 + '@rollup/rollup-freebsd-arm64': 4.60.1 + '@rollup/rollup-freebsd-x64': 4.60.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.60.1 + '@rollup/rollup-linux-arm-musleabihf': 4.60.1 + '@rollup/rollup-linux-arm64-gnu': 4.60.1 + '@rollup/rollup-linux-arm64-musl': 4.60.1 + '@rollup/rollup-linux-loong64-gnu': 4.60.1 + '@rollup/rollup-linux-loong64-musl': 4.60.1 + '@rollup/rollup-linux-ppc64-gnu': 4.60.1 + '@rollup/rollup-linux-ppc64-musl': 4.60.1 + '@rollup/rollup-linux-riscv64-gnu': 4.60.1 + '@rollup/rollup-linux-riscv64-musl': 4.60.1 + '@rollup/rollup-linux-s390x-gnu': 4.60.1 + '@rollup/rollup-linux-x64-gnu': 4.60.1 + '@rollup/rollup-linux-x64-musl': 4.60.1 + '@rollup/rollup-openbsd-x64': 4.60.1 + '@rollup/rollup-openharmony-arm64': 4.60.1 + '@rollup/rollup-win32-arm64-msvc': 4.60.1 + '@rollup/rollup-win32-ia32-msvc': 4.60.1 + '@rollup/rollup-win32-x64-gnu': 4.60.1 + '@rollup/rollup-win32-x64-msvc': 4.60.1 + fsevents: 2.3.3 + + router@2.2.0: + dependencies: + debug: 4.4.3 + depd: 2.0.0 + is-promise: 4.0.0 + parseurl: 1.3.3 + path-to-regexp: 8.4.2 + transitivePeerDependencies: + - supports-color + + safer-buffer@2.1.2: {} + + semver@7.7.4: {} + + send@1.2.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 2.0.0 + http-errors: 2.0.1 + mime-types: 3.0.2 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + serve-static@2.2.1: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 1.2.1 + transitivePeerDependencies: + - supports-color + + setprototypeof@1.2.0: {} + + sharp@0.33.5: + dependencies: + color: 4.2.3 + detect-libc: 2.1.2 + semver: 7.7.4 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.33.5 + '@img/sharp-darwin-x64': 0.33.5 + '@img/sharp-libvips-darwin-arm64': 1.0.4 + '@img/sharp-libvips-darwin-x64': 1.0.4 + '@img/sharp-libvips-linux-arm': 1.0.5 + '@img/sharp-libvips-linux-arm64': 1.0.4 + '@img/sharp-libvips-linux-s390x': 1.0.4 + '@img/sharp-libvips-linux-x64': 1.0.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 + '@img/sharp-libvips-linuxmusl-x64': 1.0.4 + '@img/sharp-linux-arm': 0.33.5 + '@img/sharp-linux-arm64': 0.33.5 + '@img/sharp-linux-s390x': 0.33.5 + '@img/sharp-linux-x64': 0.33.5 + '@img/sharp-linuxmusl-arm64': 0.33.5 + '@img/sharp-linuxmusl-x64': 0.33.5 + '@img/sharp-wasm32': 0.33.5 + '@img/sharp-win32-ia32': 0.33.5 + '@img/sharp-win32-x64': 0.33.5 + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + side-channel-list@1.0.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + siginfo@2.0.0: {} + + simple-swizzle@0.2.4: + dependencies: + is-arrayish: 0.3.4 + + source-map-js@1.2.1: {} + + stackback@0.0.2: {} + + statuses@2.0.2: {} + + std-env@3.10.0: {} + + stoppable@1.1.0: {} + + strip-literal@3.1.0: + dependencies: + js-tokens: 9.0.1 + + supports-color@10.2.2: {} + + tinybench@2.9.0: {} + + tinyexec@0.3.2: {} + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + + tinypool@1.1.1: {} + + tinyrainbow@2.0.0: {} + + tinyspy@4.0.4: {} + + toidentifier@1.0.1: {} + + tslib@2.8.1: + optional: true + + type-is@2.0.1: + dependencies: + content-type: 1.0.5 + media-typer: 1.1.0 + mime-types: 3.0.2 + + typescript@5.9.3: {} + + ufo@1.6.3: {} + + undici@7.24.7: {} + + unenv@2.0.0-rc.21: + dependencies: + defu: 6.1.6 + exsolve: 1.0.8 + ohash: 2.0.11 + pathe: 2.0.3 + ufo: 1.6.3 + + unpdf@1.4.0: {} + + unpipe@1.0.0: {} + + vary@1.1.2: {} + + vite-node@3.2.4(yaml@2.8.3): + dependencies: + cac: 6.7.14 + debug: 4.4.3 + es-module-lexer: 1.7.0 + pathe: 2.0.3 + vite: 7.3.1(yaml@2.8.3) + transitivePeerDependencies: + - '@types/node' + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + vite@7.3.1(yaml@2.8.3): + dependencies: + esbuild: 0.27.7 + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + postcss: 8.5.8 + rollup: 4.60.1 + tinyglobby: 0.2.15 + optionalDependencies: + fsevents: 2.3.3 + yaml: 2.8.3 + + vitest@3.2.4(yaml@2.8.3): + dependencies: + '@types/chai': 5.2.3 + '@vitest/expect': 3.2.4 + '@vitest/mocker': 3.2.4(vite@7.3.1(yaml@2.8.3)) + '@vitest/pretty-format': 3.2.4 + '@vitest/runner': 3.2.4 + '@vitest/snapshot': 3.2.4 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.3.3 + debug: 4.4.3 + expect-type: 1.3.0 + magic-string: 0.30.21 + pathe: 2.0.3 + picomatch: 4.0.4 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.15 + tinypool: 1.1.1 + tinyrainbow: 2.0.0 + vite: 7.3.1(yaml@2.8.3) + vite-node: 3.2.4(yaml@2.8.3) + why-is-node-running: 2.3.0 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + workerd@1.20250906.0: + optionalDependencies: + '@cloudflare/workerd-darwin-64': 1.20250906.0 + '@cloudflare/workerd-darwin-arm64': 1.20250906.0 + '@cloudflare/workerd-linux-64': 1.20250906.0 + '@cloudflare/workerd-linux-arm64': 1.20250906.0 + '@cloudflare/workerd-windows-64': 1.20250906.0 + + wrangler@4.35.0(@cloudflare/workers-types@4.20260404.1): + dependencies: + '@cloudflare/kv-asset-handler': 0.4.0 + '@cloudflare/unenv-preset': 2.7.3(unenv@2.0.0-rc.21)(workerd@1.20250906.0) + blake3-wasm: 2.1.5 + esbuild: 0.25.4 + miniflare: 4.20250906.0 + path-to-regexp: 6.3.0 + unenv: 2.0.0-rc.21 + workerd: 1.20250906.0 + optionalDependencies: + '@cloudflare/workers-types': 4.20260404.1 + fsevents: 2.3.3 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + wrappy@1.0.2: {} + + ws@8.18.0: {} + + yaml@2.8.3: {} + + 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 + + zod-to-json-schema@3.25.2(zod@4.3.6): + dependencies: + zod: 4.3.6 + + zod@3.22.3: {} + + zod@3.23.8: {} + + zod@4.3.6: {} diff --git a/src/actions/entity-crud.ts b/src/actions/entity-crud.ts new file mode 100644 index 0000000..be62471 --- /dev/null +++ b/src/actions/entity-crud.ts @@ -0,0 +1,65 @@ +// Entity CRUD — 零 SQL / 零 D1 版。全走基本盤 API(KbdbClient)。 +// Entity = 基本盤 record(template=entity),填 slot 不建表。Pending alias 拆到 entity-pending.ts。 +// +// base 缺口 [→arcrun]:無 PUT /records/:id → record 無法原地更新; +// addAlias 改「重建一筆新 entity record」覆寫(補上 PUT 後可改原地 patch aliases_json)。 + +import type { Entity } from '../types'; +import type { KbdbClient } from '../lib/kbdb-client'; +import { TPL_ENTITY, ensurePluginTemplates, recordToEntity } from '../lib/templates'; + +const norm = (s: string): string => s.toLowerCase().trim(); + +// ─── Entity ────────────────────────────────────────────────────────────────── + +/** 建立 Entity(canonical name)。底層 = 一筆 entity template record。 */ +export async function createEntity(client: KbdbClient, canonical: string, owner?: string): Promise { + await ensurePluginTemplates(client); + const id = await client.createRecord( + TPL_ENTITY, + { canonical, aliases_json: '[]', entity_type: '', owner: owner ?? '' }, + owner, + ); + return { id, canonical, aliases: [] }; +} + +/** exact match 查找 Entity(小寫去空白比對 canonical 與 aliases)。語意相似度屬基本盤 embed 模組,不在此。 */ +export async function findEntityByName(client: KbdbClient, name: string, owner?: string): Promise { + const target = norm(name); + const records = await client.listRecordsByTemplate(TPL_ENTITY, owner); + for (const rec of records) { + const ent = recordToEntity(rec); + if (norm(ent.canonical) === target) return ent; + if (ent.aliases.some(a => norm(a) === target)) return ent; + } + return null; +} + +/** 列出所有 Entity。 */ +export async function listEntities(client: KbdbClient, limit = 100, owner?: string): Promise { + const records = await client.listRecordsByTemplate(TPL_ENTITY, owner); + return records.map(recordToEntity).filter(e => e.canonical).slice(0, limit); +} + +/** + * 新增 alias。base 無 PUT /records/:id → 改「重建一筆新 entity record」覆寫(含舊 canonical + 既有 aliases + 新 alias)。 + * [→arcrun] base 缺 PUT /records/:id:補上後改為原地 patch aliases_json,省一次重建。 + */ +export async function addAlias(client: KbdbClient, entityId: string, alias: string, owner?: string): Promise { + const rec = await client.getRecord(entityId); + if (!rec) throw new Error(`Entity ${entityId} not found`); + const ent = recordToEntity(rec); + if (ent.aliases.includes(alias)) return; + const aliases = [...ent.aliases, alias]; + await ensurePluginTemplates(client); + await client.createRecord( + TPL_ENTITY, + { + canonical: ent.canonical, + aliases_json: JSON.stringify(aliases), + entity_type: rec.values.entity_type ?? '', + owner: rec.values.owner ?? owner ?? '', + }, + owner, + ); +} diff --git a/src/actions/entity-graph-embed.ts b/src/actions/entity-graph-embed.ts new file mode 100644 index 0000000..8e720a8 --- /dev/null +++ b/src/actions/entity-graph-embed.ts @@ -0,0 +1,23 @@ +// Entity Graph Embedding — [→arcrun embed 模組] +// +// 處置決定(2026-06-14):整檔原本是 embedding 邏輯(聚合 entity 所有 triplet 的 +// bge-m3 向量、加權平均成認知向量、upsert 到 Vectorize namespace 'entity-graph')。 +// 依鐵律 4:embedding / 語意搜尋【不是插件職責】,屬基本盤 optional embed 模組。 +// 插件不綁 AI/Vectorize,故移除全部 D1 + Vectorize 實作,僅留薄殼標記去向。 +// +// 若日後需要 entity graph embedding:在基本盤 embed 模組實作(讀 triplet record 走 API + +// base 內部 AI/Vectorize),插件這層不碰。此函式維持簽名相容,永遠回 updated:false。 + +import type { KbdbClient } from '../lib/kbdb-client'; + +/** + * [→arcrun embed 模組] no-op 薄殼。 + * graph embedding 已移出插件職責;保留簽名供 caller 不需改動,永遠回 { updated: false }。 + */ +export async function recalcEntityGraph( + _client: KbdbClient, + _entityName: string, +): Promise<{ updated: boolean; triplet_count: number }> { + // 插件不做 embedding。實作搬到基本盤 optional embed 模組。 + return { updated: false, triplet_count: 0 }; +} diff --git a/src/actions/entity-normalize.ts b/src/actions/entity-normalize.ts new file mode 100644 index 0000000..54b964b --- /dev/null +++ b/src/actions/entity-normalize.ts @@ -0,0 +1,33 @@ +// Entity 正規化 — 純 exact match 版(零 AI / 零 Vectorize)。 +// +// 鐵律:embedding / 語意相似度合併【不是插件職責】,屬基本盤 optional embed 模組。 +// 插件只做 exact match(小寫去空白比對 canonical / aliases): +// 命中 → 回 canonical;未命中 → 建新 entity 後回 rawName。 +// 原本的 cosine 門檻(0.92 merge / 0.75 pending)與 pending 流程, +// 待基本盤 embed 模組上線後在 base 層處理;插件不綁 AI/Vectorize。 + +import { findEntityByName, createEntity } from './entity-crud'; +import type { KbdbClient } from '../lib/kbdb-client'; + +/** + * 正規化 rawName(純 exact): + * 1. exact match 命中 → 回傳 canonical + * 2. 未命中 → 建新 entity → 回傳 rawName + * 任何錯誤靜默降級,回傳 rawName。 + */ +export async function normalizeEntity( + client: KbdbClient, + rawName: string, + owner?: string, +): Promise { + try { + const exact = await findEntityByName(client, rawName, owner); + if (exact) return exact.canonical; + + await createEntity(client, rawName, owner); + return rawName; + } catch (err) { + console.error('[normalizeEntity] error, fallback to rawName:', err); + return rawName; + } +} diff --git a/src/actions/entity-pending.ts b/src/actions/entity-pending.ts new file mode 100644 index 0000000..23ab412 --- /dev/null +++ b/src/actions/entity-pending.ts @@ -0,0 +1,70 @@ +// Pending Alias — 零 SQL / 零 D1。全走基本盤 API(template=entity_pending record)。 +// +// base 缺口 [→arcrun]:無 DELETE /records/:id → pending record 無法硬刪。 +// confirm/reject 採 soft:執行動作但不刪 pending(待 base 補 DELETE)。 +// 故 getPendingAliases 須由 caller 自行過濾已處理者,或待 DELETE 補上後在此硬刪。 + +import type { Entity, PendingAlias } from '../types'; +import type { KbdbClient } from '../lib/kbdb-client'; +import { TPL_ENTITY_PENDING, ensurePluginTemplates } from '../lib/templates'; +import { createEntity, addAlias } from './entity-crud'; + +/** 建立 Pending Alias 記錄(一筆 entity_pending record)。 */ +export async function createPendingAlias( + client: KbdbClient, + rawName: string, + candidateEntityId: string, + candidateCanonical: string, + similarity: number, + owner?: string, +): Promise { + await ensurePluginTemplates(client); + const id = await client.createRecord( + TPL_ENTITY_PENDING, + { + raw_name: rawName, + candidate_entity_id: candidateEntityId, + candidate_canonical: candidateCanonical, + similarity: String(similarity), + }, + owner, + ); + return { + id, + raw_name: rawName, + candidate_entity_id: candidateEntityId, + candidate_canonical: candidateCanonical, + similarity, + created_at: Math.floor(Date.now() / 1000), + }; +} + +/** 列出所有 Pending Aliases。 */ +export async function getPendingAliases(client: KbdbClient, limit = 100, owner?: string): Promise { + const records = await client.listRecordsByTemplate(TPL_ENTITY_PENDING, owner); + return records + .filter((r) => r.values.raw_name) + .map((r) => ({ + id: r.record_id, + raw_name: r.values.raw_name, + candidate_entity_id: r.values.candidate_entity_id ?? '', + candidate_canonical: r.values.candidate_canonical ?? '', + similarity: parseFloat(r.values.similarity ?? '0'), + created_at: 0, + })) + .slice(0, limit); +} + +/** 確認 → addAlias 到候選 entity。pending soft 保留([→arcrun] base 缺 DELETE record)。 */ +export async function confirmPendingAlias(client: KbdbClient, pendingId: string, owner?: string): Promise { + const rec = await client.getRecord(pendingId); + if (!rec || !rec.values.raw_name) throw new Error(`Pending alias ${pendingId} not found`); + await addAlias(client, rec.values.candidate_entity_id, rec.values.raw_name, owner); +} + +/** 拒絕 → 以 raw_name 建新 entity。pending soft 保留([→arcrun] base 缺 DELETE record)。 */ +export async function rejectPendingAlias(client: KbdbClient, pendingId: string, owner?: string): Promise { + const rec = await client.getRecord(pendingId); + if (!rec || !rec.values.raw_name) throw new Error(`Pending alias ${pendingId} not found`); + return createEntity(client, rec.values.raw_name, owner); +} diff --git a/src/actions/graph-nodes.ts b/src/actions/graph-nodes.ts new file mode 100644 index 0000000..c13464e --- /dev/null +++ b/src/actions/graph-nodes.ts @@ -0,0 +1,56 @@ +// 圖節點操作 — 零 SQL、零 D1,全走基本盤 API +// 取全部 triplet 一次(queryTriplets),在插件層記憶體 group/filter。 + +import type { Triplet } from '../types'; +import type { KbdbClient } from '../lib/kbdb-client'; +import { queryTriplets } from './triplet-crud'; + +/** 取全部 triplet(一次 API 呼叫,limit 拉滿上限)。 */ +async function loadAllTriplets(client: KbdbClient): Promise { + const { triplets } = await queryTriplets(client, { limit: 2000 }); + return triplets; +} + +export async function listNodes( + client: KbdbClient, + options: { search?: string; limit?: number }, +): Promise> { + const limit = Math.min(options.limit ?? 100, 500); + const triplets = await loadAllTriplets(client); + + // subject ∪ object,記憶體 group 計 edge_count + const counts = new Map(); + for (const t of triplets) { + counts.set(t.subject, (counts.get(t.subject) ?? 0) + 1); + counts.set(t.object, (counts.get(t.object) ?? 0) + 1); + } + + let nodes = Array.from(counts.entries()).map(([node, edge_count]) => ({ node, edge_count })); + if (options.search) { + const q = options.search.toLowerCase(); + nodes = nodes.filter((n) => n.node.toLowerCase().includes(q)); + } + nodes.sort((a, b) => b.edge_count - a.edge_count); + return nodes.slice(0, limit); +} + +export async function getNodeEdges( + client: KbdbClient, + name: string, +): Promise { + const triplets = await loadAllTriplets(client); + return triplets.filter((t) => t.subject === name || t.object === name); +} + +export async function getNeighbors( + client: KbdbClient, + name: string, +): Promise { + const edges = await getNodeEdges(client, name); + const neighbors = new Set(); + for (const t of edges) { + if (t.subject !== name) neighbors.add(t.subject); + if (t.object !== name) neighbors.add(t.object); + } + return Array.from(neighbors); +} diff --git a/src/actions/graph-path.ts b/src/actions/graph-path.ts new file mode 100644 index 0000000..1e67dc5 --- /dev/null +++ b/src/actions/graph-path.ts @@ -0,0 +1,74 @@ +// 最短路徑(BFS)— 零 SQL、零 D1。 +// 取全部 triplet 一次建無向鄰接表,記憶體 BFS 求最短路。 + +import type { Triplet } from '../types'; +import type { KbdbClient } from '../lib/kbdb-client'; +import { queryTriplets } from './triplet-crud'; + +type PathResult = { + path: string[] | null; + edges: Triplet[]; + hops: number; +}; + +/** node → 與之相連的所有 triplet(無向鄰接表)。 */ +function buildAdjacency(triplets: Triplet[]): Map { + const adj = new Map(); + const push = (node: string, t: Triplet) => { + const list = adj.get(node); + if (list) list.push(t); + else adj.set(node, [t]); + }; + for (const t of triplets) { + push(t.subject, t); + if (t.object !== t.subject) push(t.object, t); + } + return adj; +} + +export async function findShortestPath( + client: KbdbClient, + from: string, + to: string, +): Promise { + if (from === to) return { path: [from], edges: [], hops: 0 }; + + const maxDepth = 6; + const { triplets } = await queryTriplets(client, { limit: 2000 }); + const adj = buildAdjacency(triplets); + + const visited = new Set([from]); + const parent = new Map(); + const queue: Array<{ node: string; depth: number }> = [{ node: from, depth: 0 }]; + let found = false; + + while (queue.length > 0 && !found) { + const { node: current, depth } = queue.shift()!; + if (depth >= maxDepth) continue; + + for (const t of adj.get(current) ?? []) { + const next = t.subject === current ? t.object : t.subject; + if (!visited.has(next)) { + visited.add(next); + parent.set(next, { node: current, edge: t }); + queue.push({ node: next, depth: depth + 1 }); + if (next === to) { found = true; break; } + } + } + } + + if (!found) return { path: null, edges: [], hops: -1 }; + + // 回溯路徑 + const path: string[] = [to]; + const pathEdges: Triplet[] = []; + let current = to; + while (parent.has(current)) { + const { node, edge } = parent.get(current)!; + path.unshift(node); + pathEdges.unshift(edge); + current = node; + } + + return { path, edges: pathEdges, hops: path.length - 1 }; +} diff --git a/src/actions/graph-traverse.ts b/src/actions/graph-traverse.ts new file mode 100644 index 0000000..295c26e --- /dev/null +++ b/src/actions/graph-traverse.ts @@ -0,0 +1,64 @@ +// 圖遍歷 + 關係查詢 — 零 SQL、零 D1。 +// 取全部 triplet 一次建鄰接表,BFS / filter 在記憶體跑。 + +import type { Triplet, GraphNode } from '../types'; +import type { KbdbClient } from '../lib/kbdb-client'; +import { queryTriplets } from './triplet-crud'; + +/** node → 與之相連的所有 triplet(無向鄰接表)。 */ +function buildAdjacency(triplets: Triplet[]): Map { + const adj = new Map(); + const push = (node: string, t: Triplet) => { + const list = adj.get(node); + if (list) list.push(t); + else adj.set(node, [t]); + }; + for (const t of triplets) { + push(t.subject, t); + if (t.object !== t.subject) push(t.object, t); + } + return adj; +} + +export async function traverseGraph( + client: KbdbClient, + start: string, + maxDepth: number, +): Promise { + const depth = Math.min(maxDepth, 5); + const { triplets } = await queryTriplets(client, { limit: 2000 }); + const adj = buildAdjacency(triplets); + + const visited = new Set(); + const queue: Array<{ node: string; level: number }> = [{ node: start, level: 0 }]; + const results: GraphNode[] = []; + + while (queue.length > 0) { + const { node, level } = queue.shift()!; + if (visited.has(node) || level > depth) continue; + visited.add(node); + + const edges = adj.get(node) ?? []; + results.push({ node, level, edges }); + + for (const t of edges) { + const next = t.subject === node ? t.object : t.subject; + if (!visited.has(next)) queue.push({ node: next, level: level + 1 }); + } + } + + return results; +} + +export async function queryRelation( + client: KbdbClient, + from: string, + to: string, +): Promise { + const { triplets } = await queryTriplets(client, { limit: 2000 }); + return triplets.filter( + (t) => + (t.subject === from && t.object === to) || + (t.subject === to && t.object === from), + ); +} diff --git a/src/actions/predicate-normalize.ts b/src/actions/predicate-normalize.ts new file mode 100644 index 0000000..7ab98b2 --- /dev/null +++ b/src/actions/predicate-normalize.ts @@ -0,0 +1,27 @@ +// Predicate 正規化(插件層):純字串正規化,零 embedding。 +// +// 鐵律:插件零 SQL / 零 D1 / 零 Vectorize。 +// [→arcrun embed 模組] 原本用 bge-m3 embedding cosine 比對近義 predicate(>0.90 use_existing / +// 0.85~0.90 pending / <0.85 new)。語意比對屬基本盤 embed 模組,不是 graph 插件職責。 +// 此處降級為純字串正規化(trim + 小寫 + 收斂空白),任何近義收斂交給基本盤 embed 模組。 +// 任何錯誤靜默降級,不擋寫入。 + +export type PredicateNormalizeResult = + | { action: 'use_existing'; canonical: string; score: number } + | { action: 'pending'; similar: Array<{ canonical: string; score: number }> } + | { action: 'new'; predicate: string }; + +/** + * 正規化 predicate(純字串):trim + 收斂連續空白。回傳 { action: 'new', predicate }。 + * [→arcrun embed 模組] 近義收斂(use_existing / pending)交由基本盤 embed 模組處理,插件不做向量比對。 + * 任何錯誤靜默降級,回傳原始 predicate。 + */ +export function normalizePredicateOnWrite(predicate: string): PredicateNormalizeResult { + try { + const canonical = predicate.trim().replace(/\s+/g, ' '); + return { action: 'new', predicate: canonical }; + } catch (err) { + console.error('[normalizePredicateOnWrite] error, fallback to raw:', err); + return { action: 'new', predicate }; + } +} diff --git a/src/actions/search-embed.ts b/src/actions/search-embed.ts new file mode 100644 index 0000000..eb348bf --- /dev/null +++ b/src/actions/search-embed.ts @@ -0,0 +1,12 @@ +// [→arcrun embed 模組] 手動 embedding 管理(embedAndStore / getVector / deleteVector) +// 已移出插件:embedding / Vectorize 屬基本盤的 optional embed 模組,不是 graph 插件職責。 +// +// 鐵律:插件零 SQL / 零 D1 / 零 Vectorize。此檔不再持有任何向量邏輯。 +// 保留薄殼 stub 以維持匯出相容;呼叫端(route)應改打基本盤 embed 模組,或停用此端點。 + +const NOT_IN_PLUGIN = + 'embedding 屬基本盤 optional embed 模組([→arcrun embed 模組]),不在 graph 插件實作'; + +export function embedNotSupported(): never { + throw new Error(NOT_IN_PLUGIN); +} diff --git a/src/actions/search-query.ts b/src/actions/search-query.ts new file mode 100644 index 0000000..3435a22 --- /dev/null +++ b/src/actions/search-query.ts @@ -0,0 +1,43 @@ +// 搜尋(插件層):只做基本盤 keyword 搜尋(D1 LIKE,走 GET /entries/search)。 +// +// 鐵律:插件零 SQL / 零 D1 / 零 Vectorize。讀寫只透過 KbdbClient。 +// [→arcrun embed 模組] 語意搜尋 / embedding 是基本盤的 optional embed 模組,不是插件職責。 +// 插件不綁 AI/Vectorize;「語意搜尋」在此降級為 keyword 搜尋。 + +import type { KbdbClient, BaseEntry } from '../lib/kbdb-client'; + +export type SearchMatch = { + score: number; + metadata: Record; + type: 'block' | 'triplet'; + triplet: Record | null; + block: Record | null; +}; + +export type KeywordSearchOptions = { + limit?: number; + owner_id?: string; +}; + +/** + * 基本盤 keyword 搜尋。打 GET /entries/search(D1 LIKE),把 BaseEntry 包成相容的 SearchMatch。 + * [→arcrun embed 模組] 若日後要真語意搜尋,由基本盤 embed 模組提供,不在插件實作。 + */ +export async function keywordSearch( + client: KbdbClient, + query: string, + options: KeywordSearchOptions = {}, +): Promise { + const { limit = 10, owner_id } = options; + const entries = await client.searchEntries(query, owner_id); + + const matches: SearchMatch[] = entries.map((e: BaseEntry) => ({ + score: 0, // keyword 搜尋無相似度分數;語意分數待 embed 模組 + metadata: {}, + type: 'block' as const, + triplet: null, + block: e as unknown as Record, + })); + + return matches.slice(0, limit); +} diff --git a/src/actions/search-suggest.ts b/src/actions/search-suggest.ts new file mode 100644 index 0000000..e2fcdc0 --- /dev/null +++ b/src/actions/search-suggest.ts @@ -0,0 +1,68 @@ +// 搜尋推薦(插件層):用基本盤 keyword 搜尋結果組模板。 +// +// 鐵律:插件零 SQL / 零 D1 / 零 Vectorize。讀寫只透過 KbdbClient。 +// [→arcrun embed 模組] 語意分數 / 相似度排序屬基本盤 embed 模組;此處只做 keyword 搜尋 + 零幻覺模板。 +// 零幻覺:只用知識庫資料組模板,不經 LLM。 + +import { keywordSearch } from './search-query'; +import type { SearchMatch } from './search-query'; +import type { KbdbClient } from '../lib/kbdb-client'; + +type SuggestResult = { + suggestion: string; + matches: SearchMatch[]; + count: number; +}; + +export async function suggestKnowledge( + client: KbdbClient, + query: string, + limit: number = 10, + owner_id?: string, +): Promise { + const allMatches = await keywordSearch(client, query, { limit, owner_id }); + + const goodMatches = allMatches.filter(m => m.triplet !== null || m.block !== null).filter(m => { + // 過濾純 ref 類 block:((uuid))、{{embed ((uuid))}} 等對用戶無意義 + if (m.type === 'block' && m.block) { + const content = (m.block as Record).content || ''; + if (/^\s*\(\([a-f0-9-]+\)\)\s*$/.test(content)) return false; + if (/^\s*\{\{embed\s+\(\([a-f0-9-]+\)\)\}\}\s*$/.test(content)) return false; + } + return true; + }); + + if (goodMatches.length === 0) { + return { suggestion: '', matches: [], count: 0 }; + } + + const suggestion = buildFallback(goodMatches); + + return { suggestion, matches: goodMatches, count: goodMatches.length }; +} + +// 親切口語化回應(零幻覺,只引用知識庫資料) +function buildFallback(matches: SearchMatch[]): string { + const blockMatches = matches.filter(m => m.type === 'block'); + const tripletMatches = matches.filter(m => m.type === 'triplet'); + + if (blockMatches.length > 0 && tripletMatches.length === 0) { + const first = blockMatches[0].block as Record | null; + const pageName = first?.page_name || '筆記'; + if (blockMatches.length === 1) { + return `你之前在「${pageName}」寫過相關的內容,幫你找出來了~`; + } + return `嘿,你之前寫過 ${blockMatches.length} 筆相關筆記,幫你撈出來了~`; + } + + if (tripletMatches.length > 0 && blockMatches.length === 0) { + const first = tripletMatches[0].triplet as Record | null; + if (!first) return `欸,找到 ${matches.length} 筆你之前寫過的東西,看看有沒有幫助?`; + if (tripletMatches.length === 1) { + return `你之前有寫到「${first.subject} ${first.predicate} ${first.object}」,是不是跟這個有關?`; + } + return `嘿,你之前寫過 ${tripletMatches.length} 筆跟「${first.subject}」相關的筆記,幫你撈出來了~`; + } + + return `嘿,找到 ${matches.length} 筆相關資料(${blockMatches.length} 筆筆記、${tripletMatches.length} 筆知識關聯),幫你整理好了~`; +} diff --git a/src/actions/triplet-cluster.ts b/src/actions/triplet-cluster.ts new file mode 100644 index 0000000..7b1b72b --- /dev/null +++ b/src/actions/triplet-cluster.ts @@ -0,0 +1,16 @@ +// Triplet 知識分群 — 純函式 +// +// 原版用 Workers AI 自動判斷 cluster。插件已不綁 AI(embedding/LLM 屬基本盤 optional 模組)。 +// 分群改為「由呼叫端(基本盤 embed 模組 / 上游 ingest)算好後傳入」;這裡只做正規化與 bridge_score 計算。 + +/** 正規化 cluster 標籤:小寫、去空白、最多 3 個、去重。 */ +export function classifyClusters(clusters: string[] | undefined): { clusters: string[]; bridgeScore: number } { + const norm = Array.from( + new Set( + (clusters ?? []) + .filter((c) => typeof c === 'string' && c.trim()) + .map((c) => c.trim().toLowerCase()), + ), + ).slice(0, 3); + return { clusters: norm, bridgeScore: Math.max(0, norm.length - 1) }; +} diff --git a/src/actions/triplet-crud.ts b/src/actions/triplet-crud.ts new file mode 100644 index 0000000..744cc16 --- /dev/null +++ b/src/actions/triplet-crud.ts @@ -0,0 +1,90 @@ +// 三元組 CRUD — 走基本盤 API(API-as-Wall,零 SQL) +// 寫 triplet = 確保 template='triplet' + POST /records 填 slot。 +// 查 triplet = GET /records/by-template/triplet → 插件層 filter/組裝。 + +import type { Triplet } from '../types'; +import type { KbdbClient } from '../lib/kbdb-client'; +import { TPL_TRIPLET, ensurePluginTemplates, recordToTriplet } from '../lib/templates'; +import { classifyClusters } from './triplet-cluster'; + +export type CreateTripletData = { + subject: string; + predicate: string; + object: string; + source_block_id?: string; + confidence?: number; + owner_id?: string; + clusters?: string[]; + bridge_score?: number; + subject_entity_type?: string; + object_entity_type?: string; +}; + +/** 建立三元組 → POST /records(template=triplet)。 */ +export async function createTriplet( + client: KbdbClient, + data: CreateTripletData, +): Promise<{ id: string; subject: string; predicate: string; object: string }> { + await ensurePluginTemplates(client); + + const clusters = data.clusters ?? []; + const bridgeScore = data.bridge_score ?? Math.max(0, clusters.length - 1); + + const values: Record = { + subject: data.subject, + predicate: data.predicate, + object: data.object, + confidence: String(data.confidence ?? 1.0), + clusters_json: JSON.stringify(clusters), + bridge_score: String(bridgeScore), + }; + if (data.source_block_id) values.source_block_id = data.source_block_id; + if (data.subject_entity_type) values.subject_entity_type = data.subject_entity_type; + if (data.object_entity_type) values.object_entity_type = data.object_entity_type; + + const id = await client.createRecord(TPL_TRIPLET, values, data.owner_id); + return { id, subject: data.subject, predicate: data.predicate, object: data.object }; +} + +export type TripletFilters = { + subject?: string; + predicate?: string; + object?: string; + limit?: number; + offset?: number; + owner_id?: string; + entity_type?: string; +}; + +/** 查三元組 → 取 template 全部 record,插件層 filter(base 無複合 slot 查詢)。 */ +export async function queryTriplets( + client: KbdbClient, + filters: TripletFilters, +): Promise<{ triplets: Triplet[]; count: number }> { + const records = await client.listRecordsByTemplate(TPL_TRIPLET, filters.owner_id); + let triplets = records.map(recordToTriplet); + + if (filters.subject) triplets = triplets.filter((t) => t.subject === filters.subject); + if (filters.predicate) triplets = triplets.filter((t) => t.predicate === filters.predicate); + if (filters.object) triplets = triplets.filter((t) => t.object === filters.object); + if (filters.entity_type) { + triplets = triplets.filter( + (t) => t.subject_entity_type === filters.entity_type || t.object_entity_type === filters.entity_type, + ); + } + + const offset = filters.offset ?? 0; + const limit = Math.min(filters.limit ?? 50, 2000); + const page = triplets.slice(offset, offset + limit); + return { triplets: page, count: page.length }; +} + +/** 取單一三元組 → GET /records/:id。 */ +export async function getTriplet(client: KbdbClient, id: string): Promise { + const rec = await client.getRecord(id); + if (!rec) return null; + return recordToTriplet({ ...rec, template: TPL_TRIPLET }); +} + +// re-export clusters helper(AI 分群,純函式 + 走 client 無關) +export { classifyClusters }; diff --git a/src/actions/triplet-embed.ts b/src/actions/triplet-embed.ts new file mode 100644 index 0000000..bcce105 --- /dev/null +++ b/src/actions/triplet-embed.ts @@ -0,0 +1,10 @@ +// 三元組 embedding — [→arcrun embed] +// +// 鐵律:embedding / Vectorize 屬基本盤/上游(arcrun embed 模組),插件不綁 Vectorize、不碰向量索引。 +// 本檔精簡為薄殼:只保留純字串組裝 helper(tripletToText,供別處組 query 文字用)。 +// 真正的向量生成/upsert/delete 由基本盤負責;插件層不再持有 Ai 或向量索引繫結。 + +/** 把 S-P-O 組成一段可餵 embedding 的文字(純函式,無 DB / 無 Vectorize)。 */ +export function tripletToText(subject: string, predicate: string, object: string): string { + return `${subject} ${predicate} ${object}`; +} diff --git a/src/actions/triplet-entities.ts b/src/actions/triplet-entities.ts new file mode 100644 index 0000000..b25c23d --- /dev/null +++ b/src/actions/triplet-entities.ts @@ -0,0 +1,57 @@ +// 列出所有唯一實體(subject ∪ object)— 零 SQL / 零 D1 +// 取全部 triplet record(走基本盤 API),在記憶體統計 as_subject/as_object/total。 +// GET /entities + +import type { KbdbClient } from '../lib/kbdb-client'; +import { queryTriplets } from './triplet-crud'; + +export interface EntityStat { + name: string; + as_subject: number; + as_object: number; + total: number; +} + +export interface EntityListResult { + entities: EntityStat[]; + total: number; + limit: number; + offset: number; +} + +export async function listTripletEntities( + client: KbdbClient, + { limit = 200, offset = 0, q }: { limit?: number; offset?: number; q?: string }, +): Promise { + const safeLimit = Math.min(limit, 500); + const { triplets } = await queryTriplets(client, { limit: 2000 }); + + const stats = new Map(); + const bump = (name: string, role: 'as_subject' | 'as_object') => { + if (!name) return; + let s = stats.get(name); + if (!s) { + s = { name, as_subject: 0, as_object: 0, total: 0 }; + stats.set(name, s); + } + s[role] += 1; + s.total += 1; + }; + + for (const t of triplets) { + bump(t.subject, 'as_subject'); + bump(t.object, 'as_object'); + } + + let entities = [...stats.values()]; + if (q) { + const needle = q.toLowerCase(); + entities = entities.filter((e) => e.name.toLowerCase().includes(needle)); + } + entities.sort((a, b) => b.total - a.total); + + const total = entities.length; + const page = entities.slice(offset, offset + safeLimit); + + return { entities: page, total, limit: safeLimit, offset }; +} diff --git a/src/actions/triplet-extract.ts b/src/actions/triplet-extract.ts new file mode 100644 index 0000000..fd26845 --- /dev/null +++ b/src/actions/triplet-extract.ts @@ -0,0 +1,77 @@ +// triplet-extract.ts — LLM 三元組萃取 + 寫入工具函數 +// 萃取=純 LLM(不碰 DB);寫入=走基本盤 API(零 SQL / 零 D1)。 +// 供 block-ingest.ts 和 block-process.ts 共用 + +import type { KbdbClient } from '../lib/kbdb-client'; +import { createTriplet, queryTriplets } from './triplet-crud'; + +const EXTRACT_PROMPT = `你是知識萃取助手,從文章中萃取知識三元組。 +只萃取:性格特質、關鍵經歷精華、核心觀點信念、說話方式與風格。 +禁止:具體年份日期、純事實統計、流水帳年表。 +格式:[{"subject":"...","predicate":"2-6字","object":"15-50字","confidence":0.8}] +直接輸出 JSON Array,第一字元 [,最後字元 ]。不要其他文字。`; + +export interface LLMTriplet { + subject: string; + predicate: string; + object: string; + confidence: number; +} + +/** Workers AI 萃取三元組,每段獨立呼叫,單段失敗不中斷 */ +export async function extractTripletsViaLLM(ai: Ai, chunks: string[]): Promise { + const results: LLMTriplet[] = []; + for (const chunk of chunks) { + if (!chunk.trim()) continue; + try { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const raw = await (ai as any).run('@cf/meta/llama-3.3-70b-instruct-fp8-fast', { + messages: [ + { role: 'system', content: EXTRACT_PROMPT }, + { role: 'user', content: `萃取三元組:\n${chunk}` }, + ], + max_tokens: 512, + temperature: 0.1, + }); + const text = (typeof raw === 'string' ? raw : (raw?.response ?? '')) + .replace(/[\s\S]*?<\/think>/g, '').trim(); + const m = text.match(/\[[\s\S]*\]/); + if (!m) continue; + const parsed = JSON.parse(m[0]); + if (!Array.isArray(parsed)) continue; + for (const t of parsed) { + const s = String(t?.subject ?? '').trim(); + const p = String(t?.predicate ?? '').trim(); + const o = String(t?.object ?? '').trim(); + if (s && p && o) results.push({ subject: s, predicate: p, object: o, confidence: Number(t?.confidence) || 0.8 }); + } + } catch { /* 單段失敗跳過 */ } + } + return results; +} + +/** 寫入一條三元組(走基本盤 API),已存在回傳 false,新寫入回傳 true。 + * 查重 + 寫入全走 KbdbClient → triplet-crud,零 SQL / 零 D1。 */ +export async function writeTripletToDb( + client: KbdbClient, + t: { subject: string; predicate: string; object: string; confidence?: number }, + owner: string | null, +): Promise { + // 查重:以 S-P-O 三欄精確比對(queryTriplets 取 template record 後在插件層 filter) + const { count } = await queryTriplets(client, { + subject: t.subject, + predicate: t.predicate, + object: t.object, + limit: 1, + }); + if (count > 0) return false; + + await createTriplet(client, { + subject: t.subject, + predicate: t.predicate, + object: t.object, + confidence: t.confidence ?? 0.8, + owner_id: owner ?? undefined, + }); + return true; +} diff --git a/src/actions/triplet-stats.ts b/src/actions/triplet-stats.ts new file mode 100644 index 0000000..cebfe45 --- /dev/null +++ b/src/actions/triplet-stats.ts @@ -0,0 +1,41 @@ +// Triplet 統計 action — 知識圖譜規模統計 +// 零 SQL / 零 D1:取全部 triplet record(走基本盤 API),在記憶體 reduce 出統計。 +// GET /triplets/stats + +import type { KbdbClient } from '../lib/kbdb-client'; +import { queryTriplets } from './triplet-crud'; + +export interface TripletStats { + total: number; + // 保留 by_owner_id(原 by_user_id 改名,對齊基本盤 owner_id 欄位)。 + // 注:基本盤 record 不回傳 owner_id / 時間戳,故 by_owner_id/recent 在純插件層無法填, + // 待 [→arcrun] base 於 record 回應帶上 owner_id + created_at 後補。 + by_owner_id: Record; + recent: { today: number; this_week: number }; + top_subjects: { subject: string; count: number }[]; + top_predicates: { predicate: string; count: number }[]; +} + +export async function getTripletStats(client: KbdbClient): Promise { + const { triplets } = await queryTriplets(client, { limit: 2000 }); + + const subjectCounts = new Map(); + const predicateCounts = new Map(); + const ownerCounts: Record = {}; + + for (const t of triplets) { + subjectCounts.set(t.subject, (subjectCounts.get(t.subject) ?? 0) + 1); + predicateCounts.set(t.predicate, (predicateCounts.get(t.predicate) ?? 0) + 1); + } + + const top = (m: Map) => + [...m.entries()].sort((a, b) => b[1] - a[1]).slice(0, 10); + + return { + total: triplets.length, + by_owner_id: ownerCounts, // base record 暫無 owner_id,待上游補 + recent: { today: 0, this_week: 0 }, // base record 暫無 created_at,待上游補 + top_subjects: top(subjectCounts).map(([subject, count]) => ({ subject, count })), + top_predicates: top(predicateCounts).map(([predicate, count]) => ({ predicate, count })), + }; +} diff --git a/src/actions/triplet-syntax.ts b/src/actions/triplet-syntax.ts new file mode 100644 index 0000000..7afd4de --- /dev/null +++ b/src/actions/triplet-syntax.ts @@ -0,0 +1,28 @@ +// 三元組 >> 語法解析器 +// 掃描 block content 裡的「A >> B >> C」格式,回傳解析結果 + +export type ParsedTriplet = { + subject: string; + predicate: string; + object: string; +}; + +/** + * 掃描 content 裡所有符合「詞 >> 詞 >> 詞」的行 + * 一個 block 可包含多行 >> 語法 + * 前後空白 trim,空的部分跳過 + * + * 範例: + * parseTripletSyntax('書僮採集 >> 指向北極星 >> 異見三元組累積') + * // → [{ subject: '書僮採集', predicate: '指向北極星', object: '異見三元組累積' }] + */ +export function parseTripletSyntax(content: string): ParsedTriplet[] { + const results: ParsedTriplet[] = []; + for (const line of content.split('\n')) { + const parts = line.split('>>').map(p => p.trim()); + if (parts.length === 3 && parts.every(p => p.length > 0)) { + results.push({ subject: parts[0], predicate: parts[1], object: parts[2] }); + } + } + return results; +} diff --git a/src/actions/triplet-update.ts b/src/actions/triplet-update.ts new file mode 100644 index 0000000..6e8462d --- /dev/null +++ b/src/actions/triplet-update.ts @@ -0,0 +1,23 @@ +// 三元組 partial update action +// +// [→arcrun] base 缺 PUT /records/:id(也無 DELETE):基本盤目前只支援建/查 record, +// 不支援就地更新一筆 record 的 slot 值。插件不准用 SQL 直改表,故更新暫不支援。 +// +// 暫時策略:回 not-supported(null),由 route 轉成 4xx/501。 +// 待上游補 PUT /records/:id 後,這裡改成 client.req('PUT', ...) 或薄殼呼叫。 +// 不得以 SQL/D1 繞過。 + +import type { Triplet } from '../types'; +import type { KbdbClient } from '../lib/kbdb-client'; + +export async function patchTriplet( + _client: KbdbClient, + _id: string, + _data: { + subject?: string; predicate?: string; object?: string; confidence?: number; + subject_alias?: string; predicate_alias?: string; object_alias?: string; + }, +): Promise<{ ok: true; triplet: Triplet } | null> { + // base 無 PUT /records/:id → 無法就地更新;回 null(not-supported)。 + return null; +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..f26298c --- /dev/null +++ b/src/index.ts @@ -0,0 +1,82 @@ +// KBDB-graph 插件 Worker 進入點 +// +// 插件 = triplet 採集 + graph 查詢,掛在基本盤 arcrun/kbdb 之上(類比 AGE 之於 Postgres)。 +// 鐵律:插件不碰表、零 SQL、零 migration。讀寫全走基本盤 API(KBDB_BASE_URL)。 +// 因此本 worker 無 D1/Vectorize/AI 綁定;語意搜尋/embedding 屬基本盤 optional 模組。 + +import { OpenAPIHono } from '@hono/zod-openapi'; +import { swaggerUI } from '@hono/swagger-ui'; +import { cors } from 'hono/cors'; +import type { Bindings, Variables } from './types'; +import { tripletRoutes } from './routes/triplets'; +import { searchRoutes } from './routes/search'; +import { entityRoutes } from './routes/entities'; +import { graphRoutes } from './routes/graph'; + +const app = new OpenAPIHono<{ Bindings: Bindings; Variables: Variables }>(); + +app.onError((err, c) => { + console.error(err); + return c.json( + { + error: 'Internal Server Error', + message: err.message, + stack: c.env.ENVIRONMENT === 'development' ? err.stack : undefined, + }, + 500, + ); +}); + +app.use('*', cors({ + origin: '*', + allowHeaders: ['Content-Type', 'Authorization'], + allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], +})); + +// Auth:僅 bearer token 比對 KBDB_INTERNAL_TOKEN(partner/DB 認證屬基本盤,插件不做)。 +// namespace 由 owner_id query/body 帶;插件本身不持有用戶資料。 +const apiPaths = ['/triplets', '/graph', '/search', '/entities']; + +app.use('*', async (c, next) => { + const path = c.req.path; + const isApiPath = apiPaths.some((p) => path === p || path.startsWith(p + '/')); + c.set('namespace', null); + c.set('partner_id', 'internal'); + if (!isApiPath) return next(); + + const secret = (c.env.KBDB_INTERNAL_TOKEN || c.env.API_KEY)?.trim(); + if (!secret) return next(); // 未設 secret = 開放(本地/內部) + + const auth = c.req.header('Authorization'); + const token = auth?.startsWith('Bearer ') ? auth.slice(7).trim() : c.req.query('key')?.trim(); + if (token && token === secret) return next(); + return c.json({ error: 'Unauthorized', path }, 401); +}); + +// Swagger UI + OpenAPI spec +app.get('/ui', (c) => swaggerUI({ url: '/doc' })(c)); +app.get('/doc', (c) => { + const doc = app.getOpenAPIDocument({ + openapi: '3.0.0', + info: { title: 'KBDB-graph Plugin API', version: '0.6.0' }, + }); + if (!doc.openapi) doc.openapi = '3.0.0'; + return c.json(doc); +}); + +app.get('/health', (c) => + c.json({ + service: 'kbdb-graph', + status: 'ok', + base_url_set: Boolean(c.env.KBDB_BASE_URL), + }), +); +app.get('/', (c) => c.json({ service: 'kbdb-graph', tier: 'plugin', status: 'ok' })); + +// 插件路由(只有 graph 相關;基本盤路由屬 arcrun/kbdb) +app.route('/triplets', tripletRoutes); +app.route('/graph', graphRoutes); +app.route('/search', searchRoutes); +app.route('/entities', entityRoutes); + +export default app; diff --git a/src/lib/kbdb-client.ts b/src/lib/kbdb-client.ts new file mode 100644 index 0000000..4e4dd2f --- /dev/null +++ b/src/lib/kbdb-client.ts @@ -0,0 +1,163 @@ +// KBDB 基本盤 API client — 唯一對外通道(API-as-Wall) +// +// 鐵律:插件不碰表、零 SQL。所有讀寫只透過基本盤 HTTP API(arcrun/kbdb)。 +// 這裡只發 fetch,絕無 .prepare / D1。base URL 由 KBDB_BASE_URL env var 注入(可留空於本地測試以 mock 替代)。 +// +// 對齊基本盤真實契約(讀 arcrun/kbdb/src 2026-06-14): +// 欄位用 entry_type / owner_id(非 type / user_id);回應包在 { success, ... }。 + +export type BaseEntry = { + id: string; + content: string | null; + entry_type: string; + owner_id: string | null; + parent_id?: string | null; + page_name?: string | null; + created_at?: number; + updated_at?: number; +}; + +export type BaseRecord = { + record_id: string; + template: string; + values: Record; +}; + +export type CreateEntryInput = { + content: string | null; + entry_type: string; + owner_id?: string; + parent_id?: string; + page_name?: string; +}; + +/** 基本盤 API client。所有方法 = 一個 HTTP 呼叫,零 SQL。 */ +export class KbdbClient { + constructor( + private readonly baseUrl: string, + private readonly token?: string, + ) { + if (!baseUrl) { + throw new Error('KBDB_BASE_URL 未設定:插件需指向基本盤 API(不可直連 D1)'); + } + } + + private async req(method: string, path: string, body?: unknown): Promise { + const headers: Record = { 'Content-Type': 'application/json' }; + if (this.token) headers['Authorization'] = `Bearer ${this.token}`; + + const res = await fetch(this.baseUrl.replace(/\/$/, '') + path, { + method, + headers, + body: body === undefined ? undefined : JSON.stringify(body), + }); + + const json = (await res.json().catch(() => null)) as any; + if (!res.ok || (json && json.success === false)) { + const msg = json?.error ?? `${res.status} ${res.statusText}`; + throw new Error(`[kbdb-base] ${method} ${path}: ${msg}`); + } + return json as T; + } + + // --- entries --- + + async createEntry(input: CreateEntryInput): Promise { + const { entry } = await this.req<{ entry: BaseEntry }>('POST', '/entries', input); + return entry; + } + + async getEntry(id: string): Promise { + try { + const { entry } = await this.req<{ entry: BaseEntry }>('GET', `/entries/${encodeURIComponent(id)}`); + return entry; + } catch { + return null; + } + } + + async listEntries(filters: { + entry_type?: string; + owner_id?: string; + parent_id?: string; + page_name?: string; + limit?: number; + offset?: number; + } = {}): Promise { + const { entries } = await this.req<{ entries: BaseEntry[] }>('GET', '/entries' + qs(filters)); + return entries ?? []; + } + + /** 基本盤 D1 LIKE keyword 搜尋(語意搜尋屬 optional embed 模組,base 沒有)。 */ + async searchEntries(q: string, owner_id?: string): Promise { + const { entries } = await this.req<{ entries: BaseEntry[] }>( + 'GET', + '/entries/search' + qs({ q, owner_id }), + ); + return entries ?? []; + } + + async updateEntry(id: string, patch: Partial): Promise { + try { + const { entry } = await this.req<{ entry: BaseEntry }>('PATCH', `/entries/${encodeURIComponent(id)}`, patch); + return entry; + } catch { + return null; + } + } + + async deleteEntry(id: string): Promise { + await this.req('DELETE', `/entries/${encodeURIComponent(id)}`); + } + + // --- templates(= 替代建表;插件要新類型只能建 template) --- + + async ensureTemplate(name: string, slots: string[], description?: string): Promise { + const existing = await this.req<{ id?: string } | { error: string }>( + 'GET', + `/templates/${encodeURIComponent(name)}`, + ).catch(() => null); + if (existing && (existing as any).id) return; + await this.req('POST', '/templates', { name, slots, description, created_by: 'kbdb-graph' }); + } + + // --- records(= template 實例,填 slot) --- + + async createRecord(template: string, values: Record, owner_id?: string): Promise { + const { record } = await this.req<{ record: { record_id: string } }>('POST', '/records', { + template, + values, + owner_id, + }); + return record.record_id; + } + + async getRecord(recordId: string): Promise { + try { + const { record } = await this.req<{ record: BaseRecord }>('GET', `/records/${encodeURIComponent(recordId)}`); + return record; + } catch { + return null; + } + } + + async listRecordsByTemplate(template: string, owner_id?: string): Promise { + const { records } = await this.req<{ records: BaseRecord[] }>( + 'GET', + `/records/by-template/${encodeURIComponent(template)}` + qs({ owner_id }), + ); + return records ?? []; + } +} + +function qs(params: Record): string { + const parts = Object.entries(params) + .filter(([, v]) => v !== undefined && v !== '') + .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`); + return parts.length ? `?${parts.join('&')}` : ''; +} + +/** 從 Bindings 建 client。KBDB_BASE_URL 未設時拋錯(不准 fallback 直連 D1)。 */ +export function makeKbdbClient(env: { KBDB_BASE_URL?: string; KBDB_INTERNAL_TOKEN?: string }): KbdbClient { + return new KbdbClient(env.KBDB_BASE_URL ?? '', env.KBDB_INTERNAL_TOKEN); +} diff --git a/src/lib/templates.ts b/src/lib/templates.ts new file mode 100644 index 0000000..42b5b2d --- /dev/null +++ b/src/lib/templates.ts @@ -0,0 +1,65 @@ +// 插件用到的基本盤 template 定義(= 替代建表)。 +// 鐵律:插件新類型只能建 template,不建表。這裡集中宣告 slot schema, +// 任何寫入前先 client.ensureTemplate 確保存在。 + +import type { KbdbClient } from './kbdb-client'; +import type { Triplet, Entity } from '../types'; +import type { BaseRecord } from './kbdb-client'; + +export const TPL_TRIPLET = 'triplet'; +export const TPL_ENTITY = 'entity'; +export const TPL_ENTITY_PENDING = 'entity_pending'; + +export const TRIPLET_SLOTS = [ + 'subject', 'predicate', 'object', 'source_block_id', + 'confidence', 'clusters_json', 'bridge_score', + 'subject_entity_type', 'object_entity_type', +]; +export const ENTITY_SLOTS = ['canonical', 'aliases_json', 'entity_type', 'owner']; +export const ENTITY_PENDING_SLOTS = [ + 'raw_name', 'candidate_entity_id', 'candidate_canonical', 'similarity', +]; + +/** 確保插件三個 template 存在(idempotent,走 API)。 */ +export async function ensurePluginTemplates(client: KbdbClient): Promise { + await client.ensureTemplate(TPL_TRIPLET, TRIPLET_SLOTS, 'knowledge graph triplet (S-P-O)'); + await client.ensureTemplate(TPL_ENTITY, ENTITY_SLOTS, 'normalized entity (canonical + aliases)'); + await client.ensureTemplate(TPL_ENTITY_PENDING, ENTITY_PENDING_SLOTS, 'pending entity alias for review'); +} + +/** 基本盤 record → 插件 Triplet 型別。 */ +export function recordToTriplet(rec: BaseRecord): Triplet { + const v = rec.values; + return { + id: rec.record_id, + subject: v.subject ?? '', + predicate: v.predicate ?? '', + object: v.object ?? '', + source_block_id: v.source_block_id ?? null, + confidence: parseFloat(v.confidence ?? '1.0'), + clusters: safeArr(v.clusters_json), + bridge_score: parseInt(v.bridge_score ?? '0', 10), + subject_entity_type: (v.subject_entity_type as Triplet['subject_entity_type']) || null, + object_entity_type: (v.object_entity_type as Triplet['object_entity_type']) || null, + created_at: 0, + updated_at: 0, + }; +} + +/** 基本盤 record → 插件 Entity 型別。 */ +export function recordToEntity(rec: BaseRecord): Entity { + return { + id: rec.record_id, + canonical: rec.values.canonical ?? '', + aliases: safeArr(rec.values.aliases_json), + }; +} + +function safeArr(json?: string): string[] { + try { + const p = JSON.parse(json || '[]'); + return Array.isArray(p) ? p : []; + } catch { + return []; + } +} diff --git a/src/routes/entities.ts b/src/routes/entities.ts new file mode 100644 index 0000000..53234ad --- /dev/null +++ b/src/routes/entities.ts @@ -0,0 +1,67 @@ +import { createRoute, OpenAPIHono, z } from '@hono/zod-openapi'; +import type { Bindings } from '../types'; +import { + getPendingAliases, + confirmPendingAlias, + rejectPendingAlias, +} from '../actions/entity-pending'; +import { listTripletEntities } from '../actions/triplet-entities'; +import { makeKbdbClient } from '../lib/kbdb-client'; + +const entityRoutes = new OpenAPIHono<{ Bindings: Bindings }>(); + +// GET / (列出 entities) +const listEntitiesRoute = createRoute({ + method: 'get', + path: '/', + request: { + query: z.object({ + limit: z.string().optional(), + offset: z.string().optional(), + q: z.string().optional(), + }), + }, + responses: { + 200: { description: 'List of entities' }, + }, + tags: ['Entities'], +}); + +entityRoutes.openapi(listEntitiesRoute, async (c) => { + const limit = parseInt(c.req.query('limit') || '100'); + const offset = parseInt(c.req.query('offset') || '0'); + const q = c.req.query('q') || ''; + + const result = await listTripletEntities(makeKbdbClient(c.env), { limit, offset, q: q || undefined }); + return c.json(result); +}); + +// GET /pending (列出待確認) +const listPendingRoute = createRoute({ + method: 'get', + path: '/pending', + responses: { + 200: { description: 'List of pending aliases' }, + }, + tags: ['Entities'], +}); + +entityRoutes.openapi(listPendingRoute, async (c) => { + const limit = parseInt(c.req.query('limit') || '100'); + const pending = await getPendingAliases(makeKbdbClient(c.env), limit); + return c.json(pending); +}); + +entityRoutes.post('/pending/:id/confirm', async (c) => { + const id = c.req.param('id'); + await confirmPendingAlias(makeKbdbClient(c.env), id); + return c.json({ success: true, action: 'confirmed', id }); +}); + +entityRoutes.post('/pending/:id/reject', async (c) => { + const id = c.req.param('id'); + const newEntity = await rejectPendingAlias(makeKbdbClient(c.env), id); + return c.json({ success: true, action: 'rejected', newEntity }); +}); + +export { entityRoutes }; diff --git a/src/routes/graph.ts b/src/routes/graph.ts new file mode 100644 index 0000000..d368eb7 --- /dev/null +++ b/src/routes/graph.ts @@ -0,0 +1,49 @@ +// 圖遍歷路由入口 +// 僅驗證參數,呼叫 actions(actions 走基本盤 API,圖在插件層組裝) + +import { Hono } from 'hono'; +import type { Bindings } from '../types'; +import { traverseGraph, queryRelation } from '../actions/graph-traverse'; +import { getNodeEdges, getNeighbors } from '../actions/graph-nodes'; +import { findShortestPath } from '../actions/graph-path'; +import { makeKbdbClient } from '../lib/kbdb-client'; + +const graphRoutes = new Hono<{ Bindings: Bindings }>(); + +graphRoutes.get('/traverse', async (c) => { + const start = c.req.query('start'); + if (!start) return c.json({ error: 'start parameter required' }, 400); + const depth = Number(c.req.query('depth') ?? 2); + const results = await traverseGraph(makeKbdbClient(c.env), start, depth); + return c.json({ start, depth, nodes: results, nodeCount: results.length }); +}); + +graphRoutes.get('/relation', async (c) => { + const from = c.req.query('from'); + const to = c.req.query('to'); + if (!from || !to) return c.json({ error: 'from and to parameters required' }, 400); + const relations = await queryRelation(makeKbdbClient(c.env), from, to); + return c.json({ from, to, relations, count: relations.length }); +}); + +// GET /graph/neighbors/:name — 合併原 nodes/:name 的 edges + neighbors +graphRoutes.get('/neighbors/:name', async (c) => { + const name = decodeURIComponent(c.req.param('name')); + const client = makeKbdbClient(c.env); + const [edges, neighbors] = await Promise.all([ + getNodeEdges(client, name), + getNeighbors(client, name), + ]); + return c.json({ node: name, edges, neighbors, edgeCount: edges.length, neighborCount: neighbors.length }); +}); + +// GET /graph/path — 取代 /shortest-path,行為不變 +graphRoutes.get('/path', async (c) => { + const from = c.req.query('from'); + const to = c.req.query('to'); + if (!from || !to) return c.json({ error: 'from and to parameters required' }, 400); + const result = await findShortestPath(makeKbdbClient(c.env), from, to); + return c.json({ from, to, ...result }); +}); + +export { graphRoutes }; diff --git a/src/routes/search.ts b/src/routes/search.ts new file mode 100644 index 0000000..c90650e --- /dev/null +++ b/src/routes/search.ts @@ -0,0 +1,43 @@ +// 搜尋路由入口 — 僅驗證參數,呼叫 actions +// +// 插件層只做基本盤 keyword 搜尋(D1 LIKE,走 GET /entries/search)。 +// 語意搜尋 / embedding 屬基本盤 optional embed 模組,不在插件 → 已移除 /search/embed。 + +import { Hono } from 'hono'; +import { z } from 'zod'; +import type { Bindings, Variables } from '../types'; +import { keywordSearch } from '../actions/search-query'; +import { suggestKnowledge } from '../actions/search-suggest'; +import { makeKbdbClient } from '../lib/kbdb-client'; + +const searchRoutes = new Hono<{ Bindings: Bindings; Variables: Variables }>(); + +const UnifiedSearchSchema = z.object({ + query: z.string().min(1), + type: z.enum(['keyword', 'suggest']).optional().default('keyword'), + topK: z.number().min(1).max(20).optional(), + owner_id: z.string().optional(), +}); + +// 統一搜尋入口:POST /search +searchRoutes.post('/', async (c) => { + const parsed = UnifiedSearchSchema.safeParse(await c.req.json()); + if (!parsed.success) return c.json({ error: parsed.error.flatten() }, 400); + const { query, type, topK, owner_id } = parsed.data; + + // Namespace 讀取過濾:partner token 只能搜到自己 namespace 的資料 + const namespace = c.get('namespace'); + const effectiveOwner = namespace ?? owner_id; + + const client = makeKbdbClient(c.env); + + if (type === 'suggest') { + const result = await suggestKnowledge(client, query, topK, effectiveOwner); + return c.json(result); + } + + const matches = await keywordSearch(client, query, { limit: topK, owner_id: effectiveOwner }); + return c.json({ matches, count: matches.length, mode: 'keyword' }); +}); + +export { searchRoutes }; diff --git a/src/routes/triplets.ts b/src/routes/triplets.ts new file mode 100644 index 0000000..1bf5c2c --- /dev/null +++ b/src/routes/triplets.ts @@ -0,0 +1,100 @@ +import { createRoute, OpenAPIHono, z } from '@hono/zod-openapi'; +import type { Bindings, Variables } from '../types'; +import { createTriplet, queryTriplets } from '../actions/triplet-crud'; +import { getTripletStats } from '../actions/triplet-stats'; +import { makeKbdbClient } from '../lib/kbdb-client'; + +const tripletRoutes = new OpenAPIHono<{ Bindings: Bindings; Variables: Variables }>(); + +const TripletSchema = z.object({ + id: z.string(), + subject: z.string(), + predicate: z.string(), + object: z.string(), + owner_id: z.string().optional(), +}); + +// GET /triplets/stats (統計) +const statsRoute = createRoute({ + method: 'get', + path: '/stats', + responses: { + 200: { description: 'Statistics of the knowledge graph' }, + }, + tags: ['Triplets'], +}); + +tripletRoutes.openapi(statsRoute, async (c) => { + const stats = await getTripletStats(makeKbdbClient(c.env)); + return c.json(stats, 200); +}); + +// GET /triplets (查詢) +const queryRoute = createRoute({ + method: 'get', + path: '/', + request: { + query: z.object({ + subject: z.string().optional(), + predicate: z.string().optional(), + object: z.string().optional(), + limit: z.string().optional().describe('Maximum number of results (default 50)'), + offset: z.string().optional().describe('Pagination offset'), + }), + }, + responses: { + 200: { + content: { + 'application/json': { + schema: z.object({ triplets: z.array(TripletSchema), count: z.number() }), + }, + }, + description: 'Query results from KBDB Graph', + }, + }, + tags: ['Triplets'], +}); + +tripletRoutes.openapi(queryRoute, async (c) => { + const query = c.req.valid('query'); + const limit = query.limit ? parseInt(query.limit, 10) : undefined; + const offset = query.offset ? parseInt(query.offset, 10) : undefined; + + const results = await queryTriplets(makeKbdbClient(c.env), { ...query, limit, offset }); + return c.json(results as any, 200); +}); + +// POST /triplets (建立) +const createRouteDefinition = createRoute({ + method: 'post', + path: '/', + request: { + body: { + content: { + 'application/json': { + schema: z.object({ + subject: z.string().min(1), + predicate: z.string().min(1), + object: z.string().min(1), + owner_id: z.string().optional(), + source_block_id: z.string().optional(), + confidence: z.number().optional(), + clusters: z.array(z.string()).optional(), + }), + }, + }, + }, + }, + responses: { + 201: { description: 'Triplet created successfully' }, + }, + tags: ['Triplets'], +}); + +tripletRoutes.openapi(createRouteDefinition, async (c) => { + const data = c.req.valid('json'); + await createTriplet(makeKbdbClient(c.env), data); + return c.json({ ok: true }, 201); +}); + +export { tripletRoutes }; diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..3755e94 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,57 @@ +// KBDB Worker 型別定義 + +export type Bindings = { + // 插件不碰 DB/Vectorize/AI — 全走基本盤 API(API-as-Wall)。 + // 語意搜尋/embedding 屬基本盤 optional embed 模組,不在插件。 + KBDB_BASE_URL?: string; // 基本盤 arcrun/kbdb API 網址(leo: 可設定,先留空) + KBDB_INTERNAL_TOKEN?: string; + ENVIRONMENT: string; + API_KEY?: string; +}; + +// Hono Context 變數:auth middleware 設定,route handler 使用 +export type Variables = { + namespace: string | null; // null = internal(無限制),string = partner 的 org_namespace + partner_id: string; // 'internal' 或 partner record_id +}; + +export type EntityType = 'person' | 'event' | 'product' | 'market' | 'org'; + +export type Triplet = { + id: string; + subject: string; + predicate: string; + object: string; + source_block_id: string | null; + confidence: number; + clusters: string[]; // 所屬的知識群集,用於跨領域偵測 + bridge_score: number; // 跨越的 cluster 數量,Scout 發現指標 + subject_entity_type: EntityType | null; // 主體 entity 類型(人格疊加局勢分析用) + object_entity_type: EntityType | null; // 客體 entity 類型 + created_at: number; + updated_at: number; +}; + +// 圖遍歷結果 +export type GraphNode = { + node: string; + level: number; + edges: Triplet[]; +}; + +// --- Entity Normalization 型別 --- + +export type Entity = { + id: string; + canonical: string; + aliases: string[]; +}; + +export type PendingAlias = { + id: string; + raw_name: string; + candidate_entity_id: string; + candidate_canonical: string; + similarity: number; + created_at: number; +}; diff --git a/tests/entity.test.ts b/tests/entity.test.ts new file mode 100644 index 0000000..028ca1d --- /dev/null +++ b/tests/entity.test.ts @@ -0,0 +1,31 @@ +// entity 正規化 — exact match(語意合併屬基本盤 embed 模組),走 mock client。 +import { describe, it, expect } from 'vitest'; +import { createEntity, findEntityByName, listEntities } from '../src/actions/entity-crud'; +import { normalizeEntity } from '../src/actions/entity-normalize'; +import { mockClient } from './mock-client'; + +describe('entity-crud', () => { + it('建立後可 exact 查回(大小寫不敏感)', async () => { + const c = mockClient(); + await createEntity(c, 'InkStone'); + const found = await findEntityByName(c, 'inkstone'); + expect(found?.canonical).toBe('InkStone'); + }); + + it('listEntities 列出', async () => { + const c = mockClient(); + await createEntity(c, 'A'); + await createEntity(c, 'B'); + const all = await listEntities(c); + expect(all.map((e) => e.canonical).sort()).toEqual(['A', 'B']); + }); +}); + +describe('normalizeEntity', () => { + it('已存在回 canonical,不存在建新回原值', async () => { + const c = mockClient(); + await createEntity(c, 'InkStone'); + expect(await normalizeEntity(c, 'INKSTONE')).toBe('InkStone'); + expect(await normalizeEntity(c, '新公司')).toBe('新公司'); + }); +}); diff --git a/tests/graph.test.ts b/tests/graph.test.ts new file mode 100644 index 0000000..7b44933 --- /dev/null +++ b/tests/graph.test.ts @@ -0,0 +1,47 @@ +// graph 查詢 — 圖在插件層記憶體組裝(不靠 DB VIEW),走 mock client。 +import { describe, it, expect } from 'vitest'; +import { createTriplet } from '../src/actions/triplet-crud'; +import { listNodes, getNodeEdges, getNeighbors } from '../src/actions/graph-nodes'; +import { findShortestPath } from '../src/actions/graph-path'; +import { mockClient } from './mock-client'; +import type { KbdbClient } from '../src/lib/kbdb-client'; + +async function seed(c: KbdbClient) { + // A — B — C,D 孤立連 A + await createTriplet(c, { subject: 'A', predicate: 'r', object: 'B' }); + await createTriplet(c, { subject: 'B', predicate: 'r', object: 'C' }); + await createTriplet(c, { subject: 'A', predicate: 'r', object: 'D' }); +} + +describe('graph-nodes', () => { + it('listNodes 統計 edge_count', async () => { + const c = mockClient(); + await seed(c); + const nodes = await listNodes(c, {}); + const a = nodes.find((n) => n.node === 'A'); + expect(a?.edge_count).toBe(2); + }); + + it('getNeighbors 收鄰居', async () => { + const c = mockClient(); + await seed(c); + const neighbors = await getNeighbors(c, 'A'); + expect(neighbors.sort()).toEqual(['B', 'D']); + }); + + it('getNodeEdges 取邊', async () => { + const c = mockClient(); + await seed(c); + const edges = await getNodeEdges(c, 'B'); + expect(edges.length).toBe(2); + }); +}); + +describe('graph-path', () => { + it('A→C 最短路經過 B', async () => { + const c = mockClient(); + await seed(c); + const res = await findShortestPath(c, 'A', 'C'); + expect(res.path).toEqual(['A', 'B', 'C']); + }); +}); diff --git a/tests/mock-client.ts b/tests/mock-client.ts new file mode 100644 index 0000000..f213954 --- /dev/null +++ b/tests/mock-client.ts @@ -0,0 +1,78 @@ +// 記憶體版 KbdbClient — 模擬基本盤 API,供插件單元測試(不打真網路、不碰 D1)。 +// 只實作插件實際用到的方法,行為對齊 arcrun/kbdb 契約。 + +import type { KbdbClient, BaseEntry, BaseRecord } from '../src/lib/kbdb-client'; + +export class MockKbdbClient { + private entries = new Map(); + private templates = new Map(); + private records = new Map; owner_id?: string }>(); + private seq = 0; + + private id(prefix: string): string { + this.seq += 1; + return `${prefix}-${this.seq}`; + } + + async createEntry(input: any): Promise { + const id = this.id('entry'); + const entry: BaseEntry = { id, content: input.content ?? null, entry_type: input.entry_type, owner_id: input.owner_id ?? null }; + this.entries.set(id, entry); + return entry; + } + + async getEntry(id: string): Promise { + return this.entries.get(id) ?? null; + } + + async listEntries(filters: any = {}): Promise { + return [...this.entries.values()].filter( + (e) => (!filters.entry_type || e.entry_type === filters.entry_type) && (!filters.owner_id || e.owner_id === filters.owner_id), + ); + } + + async searchEntries(q: string, owner_id?: string): Promise { + const needle = q.toLowerCase(); + return [...this.entries.values()].filter( + (e) => (e.content ?? '').toLowerCase().includes(needle) && (!owner_id || e.owner_id === owner_id), + ); + } + + async updateEntry(id: string, patch: any): Promise { + const e = this.entries.get(id); + if (!e) return null; + Object.assign(e, patch); + return e; + } + + async deleteEntry(id: string): Promise { + this.entries.delete(id); + } + + async ensureTemplate(name: string, slots: string[]): Promise { + if (!this.templates.has(name)) this.templates.set(name, slots); + } + + async createRecord(template: string, values: Record, owner_id?: string): Promise { + const id = this.id('rec'); + this.records.set(id, { template, values, owner_id }); + return id; + } + + async getRecord(recordId: string): Promise { + const r = this.records.get(recordId); + if (!r) return null; + return { record_id: recordId, template: r.template, values: r.values }; + } + + async listRecordsByTemplate(template: string, owner_id?: string): Promise { + return [...this.records.entries()] + .filter(([, r]) => r.template === template && (!owner_id || r.owner_id === owner_id)) + .map(([record_id, r]) => ({ record_id, template: r.template, values: r.values })); + } +} + +/** 轉成 KbdbClient 型別供 action 接受。 */ +export function mockClient(): KbdbClient { + return new MockKbdbClient() as unknown as KbdbClient; +} diff --git a/tests/triplet-crud.test.ts b/tests/triplet-crud.test.ts new file mode 100644 index 0000000..a70cf8a --- /dev/null +++ b/tests/triplet-crud.test.ts @@ -0,0 +1,36 @@ +// triplet CRUD — 走 mock KbdbClient(API-as-Wall),零 SQL。 +import { describe, it, expect } from 'vitest'; +import { createTriplet, queryTriplets, getTriplet } from '../src/actions/triplet-crud'; +import { mockClient } from './mock-client'; + +describe('createTriplet → records API', () => { + it('建立後可由 id 取回', async () => { + const c = mockClient(); + const r = await createTriplet(c, { subject: 'InkStone', predicate: '是', object: '創業 OS' }); + expect(r.id).toBeDefined(); + expect(r.subject).toBe('InkStone'); + + const got = await getTriplet(c, r.id); + expect(got?.predicate).toBe('是'); + expect(got?.object).toBe('創業 OS'); + }); +}); + +describe('queryTriplets → 插件層 filter', () => { + it('by subject 過濾', async () => { + const c = mockClient(); + await createTriplet(c, { subject: 'KBDB', predicate: '使用', object: 'D1' }); + await createTriplet(c, { subject: 'Other', predicate: '使用', object: 'X' }); + + const { triplets, count } = await queryTriplets(c, { subject: 'KBDB' }); + expect(count).toBe(1); + expect(triplets[0].object).toBe('D1'); + }); + + it('limit/offset 分頁', async () => { + const c = mockClient(); + for (let i = 0; i < 5; i++) await createTriplet(c, { subject: `s${i}`, predicate: 'p', object: 'o' }); + const { triplets } = await queryTriplets(c, { limit: 2, offset: 1 }); + expect(triplets.length).toBe(2); + }); +}); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..e2926b0 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "bundler", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "outDir": "dist", + "rootDir": "src", + "types": ["@cloudflare/workers-types"] + }, + "include": ["src/**/*.ts"], + "exclude": ["node_modules", "dist", "tests"] +} diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..4f79661 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from 'vitest/config'; + +// 插件不綁 D1/Vectorize/Workers,測試走純 node + mock KbdbClient(API-as-Wall)。 +export default defineConfig({ + test: { + environment: 'node', + include: ['tests/**/*.test.ts'], + }, +}); diff --git a/wrangler.toml b/wrangler.toml new file mode 100644 index 0000000..ad0a509 --- /dev/null +++ b/wrangler.toml @@ -0,0 +1,24 @@ +name = "kbdb-graph-plugin" +main = "src/index.ts" +compatibility_date = "2025-02-19" +compatibility_flags = ["nodejs_compat"] +workers_dev = true + +# KBDB-graph 插件 = triplet 採集 + graph 查詢,掛在基本盤 arcrun/kbdb 之上。 +# 鐵律:插件不碰表、零 SQL、零 migration。讀寫全走基本盤 API(API-as-Wall)。 +# 因此這裡【無】D1 / Vectorize 綁定 — 那些屬基本盤,插件不直連。 + +[vars] +ENVIRONMENT = "development" +# 基本盤 arcrun/kbdb API 網址(leo 2026-06-14:做成可設定,先留空)。 +# 部署前用 `wrangler secret put` 或在此填入,例如 https://arcrun-kbdb..workers.dev +KBDB_BASE_URL = "" + +[alias] +"zod/v3" = "zod" +"zod/v4" = "zod" +"zod/v4-mini" = "zod" + +[[routes]] +pattern = "kbdb-graph.finally.click" +custom_domain = true