Commit Graph

104 Commits

Author SHA1 Message Date
Leo 3689f30409 fix(mira): [pageName] h1 顯示 entity 名 + listing dedupe by entity
兩個 leo 反饋的 UI bug:

1. wiki/[pageName] 對 index-entry 渲染時,h1 用 block.content(整篇 markdown)
   會把整個內容塞進 h1。改:wiki-page 用 content 當 entity 名;其他類型
   (index-entry/schema/log)用 page_name 剝 `wiki-` / `index-` prefix。

2. listing「Wiki Pages (21)」累積式設計造成同 entity 多版顯示為多張卡,雜亂。
   改:用 useMemo dedupe by entity(content)— 每 entity 一張卡顯示最新版,
   標題顯示「N 版累積」當 N>1。原始 21 筆 → 現在約 6-7 個 unique entity。
2026-05-16 09:03:45 +08:00
Leo 64193f2aa5 feat(mira): wiki listing 加 Index Entries section(CC navigation 入口)
leo 反饋:原本只看到 wiki-page 列表沒看到 per-entity index-entry,
不知道 CC 從哪入口。新增 section 列出所有 type=index-entry blocks,
標題用 entity 名稱(剝 `index-` prefix),點進去看完整 markdown 摘要。

對應 design.md §3.5.12.4.2 雙層 outliner(v1.6):
- 概覽層:index-entry markdown(含「段落 outline」/「涵蓋面向」等)
- 完整 outliner:wiki page 自身(7B.3g 已實現的樹狀渲染)

部署:arcrun-landing.pages.dev(手動 wrangler pages deploy)。
2026-05-14 18:01:55 +08:00
Leo bc6360ccfc feat(arcrun): http_request body_json + error heuristic; mira feed fire-and-forget
http_request 零件擴展(registry/components/http_request):
- 加 body_json 物件欄位(內部 JSON.stringify),yaml 端不用手組 JSON 字串
- 新增 JSON 回應的 error 欄位偵測 → 若 body 含 `{"error":"..."}` 則零件回 success=false
  解 cascade bug:mira_feed_watcher 用 http_request trigger wiki_synthesis,
  原本 4xx response 也被當 success,ON_SUCCESS 鏈會誤觸發
  根因架構債:host fn 沒回 HTTP status code(arcrun.md 列為 P1 follow-up)

landing 河道 feed (landing/app/mira/feed/page.tsx):
- 加回 triggerWikiSynthesis fire-and-forget 對 cypher.arcrun.dev/webhooks/named/
  wiki_synthesis/trigger 公開觸發 endpoint(arcrun-native,非 mira-specific route)
- 不走 watcher 是因為 cypher-executor 自己 fetch 自己 workers.dev URL = CF 1042
  self-fetch 擋

watcher 仍存在當 cron backup,但目前因 self-fetch 1042 不會真正觸發下游
wiki_synthesis(arcrun.md 列為 P1 follow-up)。
2026-05-14 16:06:46 +08:00
Leo 8ab6f8a66b fix(cypher): interpolateString single-ref array/object pass-through (P0 #11)
mira_feed_watcher 第一輪 cron tick 跑 264ms 完成但 0 raw 處理 — 挖到 root cause:

interpolateString 看到模板就 string.replace,非 string 值(如 kbdb_get 回的
blocks 陣列)一律 JSON.stringify。所以 `items: "{{list_raws.blocks}}"` 把
陣列轉成字串給 filter 零件,filter 收到字串 != array → items 被忽略 →
FOREACH 跑 0 次 → watcher 看似成功實則空跑。

修:interpolateString 加 single-ref pass-through —— 若整個值是純單一 `{{x}}`
引用,回 raw value(保留 array / object 型別)。多 ref / 混合文字仍 stringify。

對應 SDD: arcrun.md 三-A P0 #11。

下一輪 cron tick 應該真正處理 raws,加 wiki-processed tag。
2026-05-14 14:54:26 +08:00
Leo 711af5dbf2 feat(arcrun): kbdb_get 加 type/source/user_id filter
之前只支援 block_id / page_name,撈「source=km-writer-direct 的 note」這類
跨 page 查詢做不到。Wiki UI 7B.3g 跟 mira_feed_watcher 都要用 client-side
filter 繞,違反「邊用 arcrun 邊修」原則。

擴 contract:保留既有 block_id (mode A) + page_name (mode B),新增純 filter
mode C:type / source / user_id 任意組合。同時 page_name + filter 也允許組合。

驗證:source=km-writer-direct&type=note&limit=5 撈到 leo 5 筆未處理河道貼文。

對應 SDD: arcrun.md 三-B 新零件 checklist + tasks.md 7B.3h(mira_feed_watcher
正在組)。
2026-05-14 14:18:43 +08:00
Leo 9560485937 feat(cypher): add scheduled() handler — arcrun-native cron 排程基建
對應 arcrun.md 三-A P1 #3。

緣由:cron 零件存在但只做 expression validation,沒有真正的排程跑。leo 指出
「邊用 arcrun 邊修,不要 workaround」— 撤回前一輪的 /mira/wiki-from-raw
mira-specific route(違反 mira CLAUDE.md §1.5 一律 arcrun-native),改補
真正的 cron infra。

加入:
- src/lib/cron-match.ts — 5 欄位 cron matcher(* / N / */N / a-b / a,b 組合)
- src/scheduled.ts — handler:掃 KV cron-idx: prefix,比對 controller.scheduledTime
  → executeWebhookGraph 背景跑
- routes/webhooks-named.ts — acr push 時偵測首節點 cron → 存 cron_expr 到 record
  + 額外寫 cron-idx:{api_key}:{name} 輕量索引;DELETE 一併清理
- src/index.ts — export default 改 { fetch, scheduled }
- wrangler.toml — [triggers] crons = ["* * * * *"](每分鐘 tick)
- wrangler.toml — workers_dev = true 供 self-fetch self-trigger 用
- tests/arcrun-test/cron_heartbeat.yaml — 健康監控 workflow(每分鐘 fire + set 節點)

撤回:
- 刪 src/routes/mira.ts(mira-specific workaround)
- types.ts 拿掉 MIRA_CONFIG
- index.ts 拿掉 miraRouter wire
- landing/app/mira/feed/page.tsx 拿掉 triggerWikiSynthesis 呼叫

下一輪:mira_feed_watcher.yaml(mira side),可能要先補 kbdb_get filter +
CALLS_SUBFLOW wire(arcrun.md 列為跟進)。
2026-05-14 14:04:57 +08:00
Leo 660b32eafd feat(mira): 河道 → wiki 自動化(fire-and-forget 觸發 wiki_synthesis)
對應 polaris/mira/.agents/specs/mira-app/tasks.md 7B.3h(簡化版)。

原計畫用 arcrun cron 零件 → cypher-executor scheduled() handler,但發現
cron 零件只是 validator,cypher-executor 還沒實作 scheduled()。為了不擋
「河道書寫 → 自動產 wiki」這條 UX,先做 fire-and-forget 版本:

- 新 cypher-executor route POST /mira/wiki-from-raw
  - body: { raw_block_id }
  - server 端從 MIRA_CONFIG secret 補 partner key / mira_token / 三個 block IDs
  - waitUntil 背景跑 executeWebhookGraph,立刻回 202
- landing 河道 post composer 成功寫 raw 後 fire-and-forget triggerWikiSynthesis()
  跟既有 triggerAiReply() 同範式
- types.ts 加 MIRA_CONFIG?: string

部署後需手動:
  echo '{"service_api_key":"ak_...","data_api_key":"ak_...","schema_block_id":"...","skill_block_id":"...","entities_block_id":"...","mira_token":"..."}' \
    | wrangler secret put MIRA_CONFIG

UX:河道貼一則 → AI reply 30s 內 → wiki 60-90s 內出現在 /mira/wiki。

arcrun.md 記 P1 #3:cypher-executor 加 scheduled() handler,那是真正的
cron 路線,封測前不擋。
2026-05-14 13:50:13 +08:00
Leo 933ae6cb13 feat(mira): 7B.3g wiki UI 樹狀渲染 + 跨 wiki 連結
對應 polaris/mira/.agents/specs/mira-app/design.md §5.2 + §3.5.12。

`/mira/wiki/[pageName]`:
- 抓 wiki-page 後平行撈所有 wiki-paragraph / triplet / wiki-page,client-side 用 parent_id filter
  (KBDB 沒 parent_id server filter,且 tag filter 還有 KI-3 bug)
- 按 facet 分區渲染:facet 標題 + paragraph markdown + 該段的 triplets
- facet 預設展開(看一篇要看內容)/ triplets 預設折疊(leo Logseq outliner 習慣)
- **triplet A/B 拆字串 「A >> 關係 >> B」**,若 A 或 B 對得上既有 wiki entity → render 成 <Link>
  跨 wiki 跳轉,是 Wikipedia-like 體驗的關鍵
- fallback:非 wiki-page block(schema/index/log 等)直接 render content

`/mira/wiki`:列表用 wiki-page 的 content (= entity 名稱) 當標題,不是 page_name slug。

mira.css 加 `.mira-wiki-detail` 不破版 + h2 底線,避免長 cypher 字串撐爆右邊界。

TS check pass。
2026-05-14 13:35:54 +08:00
Leo d6d2cecfb5 fix(cypher): resumeFromPaused 漏 node-id namespace 導致下游模板找不到 paused 結果
mira 7B.3f PATCH 測試踩到:classify 跟 compose 都是 claude_api(兩次 paused/resumed),
upsert_index_entry config 寫 `{{compose_index_entry.data.text}}`,但 PATCH 跑出的
block content 是字面 `{{compose_index_entry.data.text}}` —— 模板沒被替換。

根因:resumeFromPaused 把 callback_result spread 到 top-level,但漏了
`[paused_node_id]: callback_result` 的 namespace 包裝。同步路徑的 propagateCtx
有做這件事,resume 路徑沒做,行為不一致。

修:mergedContext 加 [paused_node_id]: callback_result 一行,跟 propagateCtx 對齊。

arcrun.md 同步補三-B「新零件 checklist」+ 三-C「workers_dev=true 全 component
自動化」收尾紀錄。
2026-05-14 12:06:59 +08:00
Leo c830897988 fix(cypher): register kbdb_upsert_block + workers_dev=true on all 32 components
兩件事一起補:

1. cypher-executor 的 WASM_HTTP_RUNNER_IDS 白名單漏加 kbdb_upsert_block。
   mira 7B.3f 端對端 debug 才發現 upsert 節點直接拋「找不到零件」。
   建零件時要記得同步註冊到 loader。

2. .component-builds/*/wrangler.toml (×32) 全部加 workers_dev = true。
   解決每次新部署 component 都要去 CF Dashboard 手動啟用 workers.dev URL
   的痛點(leo 今天踩這個踩了好幾次)。zero-touch deploy + free tier 友好,
   符合 arcrun 「fork 後 self-host 用 free tier 跑得起來」的核心目標。

對 mira 端:wiki_synthesis.yaml 還原回完整鏈
(lookup → compose → upsert + 平行的 FOREACH paragraphs/triplets)。
2026-05-14 11:37:28 +08:00
Leo 6f6e31dbee fix(cypher): deploy P0 #9/#10/#10衍生 fixes (workers.dev URL + nested FOREACH + propagateCtx)
arcrun.md 一直標  已解決但 fix 在 working tree 沒推。今天 mira 7B.3f 端對端
跑不通才發現 production 還是舊版(fetch *.arcrun.dev 同 zone 自循環 → 522)。

涵蓋:
- P0 #9: wasmWorkerUrl() 從 *.arcrun.dev 改 arcrun-{kebab}.{WORKER_SUBDOMAIN}.workers.dev
  + types.ts/wrangler.toml 加 WORKER_SUBDOMAIN binding (uncle6-me)
  + auth-dispatcher.ts 用新 signature
- P0 #10A: interpolateData() 拆 interpolateString + interpolateValue 遞迴 nested
- P0 #10B: propagateCtx() helper 把上游 output spread + 用 node id namespace 存
  讓下游能 {{node_id.data.text}} 永不被覆蓋。5 個 edge type 全用此 helper
- P0 #10C: FOREACH 找 iterable 先看 result 沒有再看 ctx + 掃 nested object 一層
  解雙重 FOREACH(paragraph→triplets)內層跑 0 次

rules/01-tech-stack.md + rules/03-component-architecture.md 同步補 workers.dev 慣例說明。

未推 5 個 worker 改動,今晚才發現實際沒部署過。
2026-05-14 11:02:44 +08:00
Leo 4e746986b4 feat(arcrun): add kbdb_upsert_block component for idempotent block writes
對應 mira 7B.3f:per-entity index-entry 維護需要「找有則 PATCH 沒找到 POST」,
arcrun workflow 沒 IF/branch 能力(已知限制 #1 + 新 P1 #1),用 kbdb_upsert_block
零件把分支邏輯封進零件內:GET /blocks?page_name=X → user_id filter → 找到 PATCH 沒找到 POST。
page_name 當 idempotency key,未來其他「找有則改沒則建」場景共用。

SDD:polaris/mira/.agents/specs/mira-app/design.md §3.5.12.4.1
     matrix/arcrun/.agents/specs/arcrun/arcrun.md 三-A P1 #1 + 三-B 新零件加入紀錄
2026-05-14 10:18:21 +08:00
Leo 519423cb0d feat(arcrun): mira wiki page with tag filter + accumulated WIP
- landing/app/mira/wiki: tag=mira-wiki list now shows all wiki paragraphs
  (depends on KBDB tag filter exposed in matrix/kbdb commit, separate repo)
- landing: app/mira hub + feed split + various WIP from prior sessions
- registry/components: claude_api / kbdb_create_block / kbdb_get / km_writer /
  platform_crypto / auth_oauth2 contracts + main.go (accumulated)
- .component-builds: pkg-lock updates + index.ts adjustments (WIP)
- .agents/specs/arcrun/frontend-redesign: design notes
- docs/test_credentials, docs/user_requirements/arcrun-landing-page: WIP docs
- cypher-executor: auth-dispatcher / wasi-shim adjustments (WIP)

Includes accumulated work from prior sessions plus the wiki UI tag-filter
update that surfaces the AI-generated wiki paragraphs at /mira/wiki.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 16:52:01 +08:00
Leo e8fca33f80 feat(cypher): 3-node wiki workflow end-to-end (FOREACH + nested interp + unified parsing)
Three platform-level improvements that together enable the full
"草稿 → LLM 整理 → KBDB" wiki ingest workflow via cypher binding:

## 1. Nested interpolation in node.data
Previously {{var}} only supported top-level keys, so {{item.content}}
literal-passed through. Now supports dot-path:
  {{paragraph.content}} → ctx.paragraph.content
  {{paragraphs.0.entity}} → ctx.paragraphs[0].entity
Non-string values (object/array) JSON.stringify automatically.

## 2. 對每個 X cypher binding syntax
'A >> 對每個 paragraph >> B' parses into FOREACH edge with
iterator='paragraph'. graph-builder.ts strips the iterator from label
before edge type resolution. Backwards compatible: bare '對每個' still
defaults to item.

## 3. FOREACH preserves outer context
itemContext was previously {...result, [iter]: item}, dropping
top-level api_key etc. Now {...outerCtx, ...result, [iter]: item} so
{{api_key}} interpolation works in foreach body.

## 4. Unified recipe output parsing (sync + resume)
Extracted parseRecipeOutput() helper used by both sync claude_api
result + workflow resume callback. Strips ```json fence, parses,
spreads parsed top-level fields into result so downstream FOREACH
finds 'paragraphs' (not buried in data.paragraphs).

paused state now stores recipe_output_format + required_fields so
resume route can apply same parsing as sync path.

End-to-end verified:
- input(草稿+api_key) → synth(claude_api+recipe) → 對每個 paragraph → write_wiki(kbdb_create_block)
- Real Claude synthesis on Mira daemon: 3 triplets + 2 paragraphs
- Both paragraphs written to KBDB as wiki-page blocks (verified GET)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 16:23:02 +08:00
Leo 497f92a268 feat(arcrun): recipe system + resumable workflow + component registry canon
Three new platform capabilities + one component (kbdb_get) to enable
real AI workflow execution through cypher binding YAML.

## Recipe System (容器 + Recipe 模式)
SDD: .agents/specs/recipe-system/

- prompt_recipe schema (Zod): fragments + inputs + assembly + output
- recipe-expander.ts: expand recipe ref → real prompt by fetching KBDB blocks
  + pulling context fields with transforms (pluck_content / extract_field / etc)
- 7 transform whitelist: json_array / to_string / join / markdown_list /
  extract_field / first / pluck_content
- graph-executor hooks: detect node.data.recipe → expand → inject into ctx
- output JSON parser (with markdown fence stripping for Claude-wrapped JSON)
- Stored in RECIPES KV under prompt_recipe:{name}

## Resumable Workflow (webhook callback resume)
SDD: .agents/specs/resumable-workflow/

- WorkflowPaused class + paused-runs.ts (persist/load/consume in EXEC_CONTEXT KV, 24h TTL)
- graph-executor: detect {pending:true, task_id} → persist state → throw WorkflowPaused
- cypher-handlers: catch → return {success:true, paused:true, task_id, run_id}
- POST /workflows/resume route: consume KV state → resumeFromPaused()
- Auto-inject callback_url for claude_api nodes (PUBLIC_BASE_URL or default cypher.arcrun.dev)
- claude_api/main.go: forward callback_url to Mira daemon, default timeout 25s→120s
- Idempotent (consume = load+delete)

## Component Registry Canon
SDD: .agents/specs/component-registry-canon/

- Add POST /components/index-only endpoint (metadata-only, no wasm/sandbox)
- Backfill script (mjs): scan registry/components/*/contract.yaml → submit to KV
- register-component.sh: SSOT for local + CI hook (deploy.yml change in next commit)
- Drop R2 dead storage from submitComponent + types + wrangler
- Schema relaxed: category enum + auth/ai/platform; cold_start 50→500ms; size 2→8MB

## kbdb_get component
- registry/components/kbdb_get/: TinyGo WASM, two modes (block_id / page_name list)
- .component-builds/kbdb_get/: WASI shim worker (kbdb-get.arcrun.dev)

End-to-end validation: AI uses MCP execute_workflow with recipe ref →
cypher-executor expands prompt from KBDB schema/skill blocks + drafts →
claude_api calls Mira daemon → daemon callback fires resume route →
workflow continues. Verified with real 2KB+ Karpathy LLM Wiki draft.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 15:52:19 +08:00
Leo e2221161a8 fix(wasi-shim): re-read memory.buffer after await in all host functions
WebAssembly memory can grow (and return a new ArrayBuffer) during an
async host function call. Reading memory.buffer before await and using
it after the await causes host functions (kv_get / crypto_decrypt /
crypto_sign_rs256 / http_request) to write into a detached buffer,
so the WASM side reads zero bytes → empty string → JSON parse failure.

Fix: read inputs before await using the current buffer snapshot,
then call memory.buffer again after the await to write the result.
For crypto_sign_rs256 and http_request, input arrays are copied
before await so the snapshot can be released.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-24 17:13:03 +08:00
Leo 83a01fe028 feat(auth_static_key): auto-encode Basic Auth; seed gemini/trello/mailgun recipes
- auth_static_key WASM: 偵測 Authorization header "Basic <x>:<y>" (含冒號
  的 user:pass 原文), 自動 base64 編碼; 無冒號則維持原樣 (向後相容
  已 base64 過的值).
  這涵蓋 twilio / jira / mailgun 三個 Basic Auth recipe, 用戶 recipe
  只需寫 'Basic {{secret.user}}:{{secret.key}}' 直覺語法.

- 新增 3 個 recipe (auth-recipe-seeds.ts):
  • gemini    — static_key / header x-goog-api-key (單 secret)
  • trello    — static_key / QUERY key+token (雙 secret, 第一個 query
                injection 測試覆蓋)
  • mailgun   — static_key / HEADER Basic api:<key> (雙 secret Basic Auth)

- hook fix (pre-write-guard.sh): 放行 auth-recipe-seeds.ts 的 {{secret.X}}
  字面值. 該檔是 RECIPES KV 的 seed 資料, 不是 TS 展開邏輯;
  真正展開仍在 WASM 完成.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 08:29:02 +08:00
Leo 258ef38f7a fix(ci): regenerate tier2 pnpm-lock.yaml after adding wrangler
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-20 21:21:18 +08:00
Leo 200a8e14dc fix(ci): add wrangler to tier2 devDependencies
tier2 deploy failed with ERR_PNPM_RECURSIVE_EXEC_FIRST_FAIL
because cypher-executor / registry / builtins package.json
didn't list wrangler; local dev worked via global install only.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-20 21:05:55 +08:00
Leo 2bfe16ba61 ci(deploy): drop setup-node pnpm cache (fails on legacy Workers)
setup-node's cache: 'pnpm' requires the cache-dependency-path to
resolve, but several legacy .component-builds/* Workers only ship
package-lock.json (historical mixed state — see rule
05-deploy-convention.md). With pnpm-lock.yaml missing, setup-node
fails fast with "Some specified paths were not resolved" before
the install step's fallback logic even gets a chance to run.

Since each Worker deploy takes ~30s on its own runner, skipping
the per-job cache costs nothing in practice.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-20 20:52:39 +08:00
Leo 38b42a2662 ci(deploy): fetch full history + fallback when base sha unreachable
fetch-depth: 2 is too shallow — a batch push of 10 commits (like
Phase 1-6 commit chain) leaves github.event.before outside the
fetched range, so git diff returns empty and nothing deploys.

- Set fetch-depth: 0 (full history) so diff always has a reachable
  base.
- Added git cat-file -e check: even with full history, force-pushes
  or orphan base SHAs trigger a "deploy all" fallback instead of
  silently skipping.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-20 20:39:54 +08:00
Leo 4516cdee4b feat: add landing page + builtins Worker + BETA_TEST guide + README
- landing/: Next.js 15 app for arcrun.dev (dashboard, integrations,
  API docs, login). Deploys via Cloudflare Pages — CI scan skips
  this via pages_build_output_dir marker.
- builtins/: minimal Hono Worker at arcrun-builtins (/init for
  one-shot component registry seeding). initComponents logic is
  flagged stale in src/index.ts for future rewrite.
- BETA_TEST.md: pre-launch validation playbook.
- README.md: updated to match current arcrun.dev / acr CLI flow.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-20 17:52:41 +08:00
Leo 13b01328c1 docs: add SDD specs + user requirements + tests
- .agents/specs/: spec-driven-dev docs for arcrun MVP, auth-recipe,
  credential-primitives-wasm (active refactor), landing-page,
  sdk-and-website, u6u-core-mvp, u6u-platform-evolution.
- .agents/steerings/tech.md: detailed tech stack rationale.
- docs/user_requirements/: long-form requirements incl. credential
  primitives, pages spec, py strategy analysis.
- tests/: end-to-end harness scaffolding.

These are the durable context backing CLAUDE.md's SDD protocol.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-20 17:48:24 +08:00
Leo cadcaef3b0 ci: generic scan-based deploy workflow for all Workers
Rewrites deploy.yml to auto-discover every wrangler.toml in the repo
(excluding node_modules and Pages projects) rather than hardcoding
each Worker. Adding a new Worker only requires the new directory —
no workflow changes.

- Two-tier fanout: tier1 = .component-builds/* (WASM rebuild +
  deploy in parallel), tier2 = orchestration Workers (cypher-executor,
  registry, builtins) that depend on tier1 via service bindings.
- Diff-aware on push: only changed Worker dirs deploy; changes under
  registry/components/{name}/ cascade to .component-builds/{name}/.
- workflow_dispatch inputs: force_all (deploy everything) and only
  (comma-separated allow-list).
- TinyGo 0.40.1 rebuilds WASM from registry/components/{name}/main.go
  so deployed binaries always match source.
- max-parallel: 5 to stay under Workers API rate limit.

Adds .claude/rules/05-deploy-convention.md documenting the
"new Worker = new dir + wrangler.toml" invariant.

Per .agents/specs/arcrun/credential-primitives-wasm Phase 6.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-20 17:42:14 +08:00
Leo 066652f6e8 feat(cli): add recipe / auth-recipe commands + update push/creds/init
- New: acr recipe (push/list/get a user recipe to RECIPES KV)
- New: acr auth-recipe (inspect platform-seeded auth recipes)
- push/creds/init/parts/config updated to match the new cypher-executor
  routing (/auth, /credentials, webhooks-named).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-20 17:40:57 +08:00
Leo 500d796573 feat: 15 logic component Workers + cypher-executor auth/credentials routing
Component Workers:
- Deploys if_control, switch, filter, merge, try_catch, wait, set,
  array_ops, string_ops, number_ops, date_ops, validate_json,
  ai_transform_compile, ai_transform_run, foreach_control as
  independent Workers, backing cypher-executor's SVC_* service
  bindings (fast internal RPC for logic components).

cypher-executor routing:
- New routes: /auth (recipe resolution), /credentials (CRUD),
  /webhooks/named (user-friendly alias for cmp_/rec_ hashes).
- auth-recipe-seeds.ts: 20 pre-built platform auth recipes
  (Google Sheets, Gmail, Telegram, etc.) seeded into RECIPES KV.
- graph-executor + cypher-handlers + search-nodes updated for
  the new resolution chain.
- scripts/seed-auth-recipes.ts: one-shot tool to push seeds to KV.
- wrangler.toml: 15 SVC_* bindings wired to the new logic Workers.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-20 17:40:02 +08:00
Leo 6a3219e51b feat(components): move 6 API components to independent WASM Workers
Deploys gmail, telegram, line_notify, google_sheets, http_request, cron
as independent Cloudflare Workers at {name-kebab}.arcrun.dev. Each
wraps the TinyGo WASM from registry/components/{name}/main.go via
wasi-shim cross-import (Method A).

component-loader no longer carries BUILTIN_API_RECIPES — those
hardcoded gmail.googleapis.com / api.telegram.org / sheets / line-notify
endpoints all lived in TS, violating "all business logic in WASM".
Resolution chain now routes the 6 canonical IDs straight to their
{name}.arcrun.dev Worker URLs via WASM_HTTP_RUNNER_IDS.

Per .agents/specs/arcrun/credential-primitives-wasm Phase 3.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-20 17:36:06 +08:00
Leo 8c14562a2f feat(auth): auth_service_account WASM primitive + remove TS JWT signer
- registry/components/auth_service_account: TinyGo impl for Google
  Service Account (JWT-bearer → token exchange) and base structure
  for AWS SigV4.
- .component-builds/auth_service_account: independent Worker at
  auth-service-account.arcrun.dev, extends wasi-shim with an
  http_request host function for the token exchange step.
- Delete cypher-executor/src/lib/wasm-executor.ts (legacy, replaced
  by component-loader WASM HTTP runner path).
- credential-injector.ts service_account branch now throws — all
  service_account recipes must route through auth-dispatcher.

Per .agents/specs/arcrun/credential-primitives-wasm Phase 2.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-20 17:34:42 +08:00
Leo 18f04448ce feat(auth): auth_static_key WASM primitive + host functions
- wasi-shim gains kv_get / crypto_decrypt / crypto_sign_rs256 host
  functions with strict boundary (ENCRYPTION_KEY never exits Worker).
- registry/components/auth_static_key: TinyGo impl for API-key /
  Bearer / Basic Auth recipes (80% of supported services).
- .component-builds/auth_static_key: independent Worker at
  auth-static-key.arcrun.dev, imports wasi-shim cross-directory.
- cypher-executor/auth-dispatcher routes static_key recipes to the
  new Worker instead of credential-injector TS.

Replaces TS credential injection per
.agents/specs/arcrun/credential-primitives-wasm Phase 1.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-20 16:54:18 +08:00
Leo 6ee6fee8b9 chore: remove duplicate credentials/ dir + add CLAUDE.md + .claude rules
credentials/ was a leftover duplicate — all credential routes already live
in cypher-executor/src/routes/credentials.ts. Adds the SDD protocol,
tech-stack, forbidden-list, component-architecture, and progress rules
that guide Phase 1-6 refactors.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-20 16:53:26 +08:00
Leo 40df4025a6 chore: gitignore .DS_Store
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-20 16:52:52 +08:00
Leo 7b18387113 feat: config field in /cypher/execute — node-level component override
- /cypher/execute now accepts separate `config` field:
  {node_name: {component: "cmp_19e62efd", ...staticParams}}
- graph-builder reads config[node].component to override componentId
  (supports cmp_ hash, rec_ hash, or canonical_id)
- config[node] other fields become node.data (static params merged at runtime)
- acr run now sends workflow.config as separate `config` (not flattened into context)
- context is now only --input dynamic params

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 18:42:26 +08:00
Leo 60d3e41905 feat: component hash IDs + dynamic KV recipe system
Hash system:
- cmp_xxxxxxxx: stable ID for logic components (SHA-256 of canonical_id)
- rec_xxxxxxxx: stable ID for API recipe components
- Pre-seeded 15 cmp_ + 6 rec_ hash indexes in KV

RECIPES KV (id: 9cf9db905c6241f78503199e58b2ffe0):
- POST/GET/DELETE /recipes — CRUD for API recipe definitions
- recipe stored as: recipe:{canonical_id} + idx:{rec_hash}
- template interpolation: {{key}} replaced from context

component-loader resolution order:
  builtin → external URL → cmp_ hash → rec_ hash →
  logic canonical_id → KV recipe → builtin API fallback → error

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 18:36:51 +08:00
Leo d8e6964088 feat: use CF Service Bindings for logic components (no public network)
- Add 15 [[services]] bindings in cypher-executor wrangler.toml
- component-loader now calls logic Workers via Service Binding (svc.fetch)
  instead of public URL fetch (which caused 522 timeout within same zone)
- Fallback to public URL if binding not available (dev/testing)
- Add ServiceBinding type to Bindings

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 17:01:42 +08:00
Leo 2b89ea8825 feat: component execution via Worker fetch + API recipes
- Logic components (15): each deployed as Worker at {name}.arcrun.dev,
  cypher-executor fetches them via HTTP POST
- API components (6): gmail, telegram, line_notify, google_sheets,
  http_request, cron executed inline via fetch recipes in component-loader
- External URL support: any https:// componentId is fetched directly
  (n8n webhooks, MCP endpoints, etc.)
- Add deploy-logic-components.sh script for building/deploying WASM Workers
- Add component-worker-template with inline WASI shim

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 16:59:12 +08:00
Leo 5534d60b60 fix: component-loader was calling wasm-executor with wrong signature
Rewrote createComponentLoader to directly use createWasiShim inline
instead of calling executeWasm(componentId, buffer, ctx) which doesn't
match wasm-executor's actual signature of executeWasm(input, options).
Also adds Module caching to avoid recompiling WASM on every request.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 16:18:50 +08:00
Leo 9590083851 fix: sink nodes should be Component not Output unless named output/result/end
Previously, the last node in any triplet chain was classified as Output type
and skipped by the executor (passthrough only). Now only nodes explicitly named
output/result/end/done are Output; all other sink nodes are Component and
will have their WASM executed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 16:16:28 +08:00
Leo 65769fc0dd fix: graphSchema missing ON_SUCCESS edge type + label field
- Add all valid EdgeTypes to graphSchema.edges.type enum
- Add label field to graphSchema.nodes (graph-builder passes it)
- Was causing 圖定義產生失敗 for all /cypher/execute calls

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 16:07:51 +08:00
Leo 2594f8371d feat: add /register endpoint + fix acr run Mode 1 (inline YAML execution)
- POST /register on cypher.arcrun.dev: HMAC-SHA256(email, ENCRYPTION_KEY) → ak_{32hex}, no DB needed
- acr run: Mode 1 (standard/local) now finds local YAML and POSTs to /cypher/execute inline
- acr init: fix register URL → cypher.arcrun.dev/register; fix local mode description
- acr init --local: creates hello.yaml example workflow
- cli v1.0.3 published

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 16:04:14 +08:00
Leo ea54aa378b ci: upgrade actions to v5 (Node.js 24) 2026-04-16 15:43:10 +08:00
Leo fcc7fdcf59 ci: fix workflow_dispatch trigger + simplify job structure 2026-04-16 15:41:14 +08:00
Leo 9b648fc5fb ci: trigger deploy workflow test 2026-04-16 15:39:36 +08:00
Leo 2d557a0382 ci: add GitHub Actions deploy workflow for 3 Workers
Auto-deploys on push to main when files in cypher-executor/, registry/,
or credentials/ change. Manual dispatch deploys all three.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 15:37:06 +08:00
Leo 9168253357 chore: fill wrangler.toml KV/R2 IDs and routes for production deploy
cypher-executor: EXEC_CONTEXT, WEBHOOKS, CREDENTIALS_KV, ANALYTICS_KV KV IDs,
  arcrun-wasm R2, route cypher.arcrun.dev/*
registry: SUBMISSIONS_KV, ANALYTICS_KV KV IDs, arcrun-wasm R2,
  route registry.arcrun.dev/*
credentials: CREDENTIALS_KV KV ID

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 15:36:26 +08:00
Leo a4ddf68621 chore(cli): bump to v1.0.2 2026-04-16 14:54:08 +08:00
Leo 7bd4ab0a6e fix(cli): address Gemini test report — local mode, validate bug, offline flag
A. acr init --local: new local mode, no Cloudflare account required;
   config defaults to mode:local when ~/.arcrun/config.yaml missing

B. validate node-count bug: removed faulty input/output node heuristic
   that dropped start/end nodes from config check; now all nodes except
   reserved 'input' keyword must have config entries

C. acr validate --offline: skip remote component-existence and credentials
   checks; local mode also auto-skips these checks

D. parts.ts: replace require('node:fs') with static import (ES module fix)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 14:53:30 +08:00
Leo 8e2c32e466 feat(registry): component_hash_id — stable id system for workflow references
Problem: canonical_id is readable but mutable; if a component is renamed,
all workflows referencing it by canonical_id break.

Solution: dual-id system
- component_hash_id: cmp_{sha256(canonical_id).slice(0,8)}, derived deterministically,
  never changes, safe for workflow references
- canonical_id: human-readable name, used for search and display
- idx:{canonical_id} KV key: reverse-lookup index for resolving canonical_id → hash_id

Changes:
- types.ts: SandboxResult.component_id → component_hash_id + canonical_id,
  added 'data' to category enum
- submitComponent.ts: deriveHashId(), writes idx: reverse-lookup on submit
- queryComponents.ts: full rewrite — removed KBDB dependency, uses SUBMISSIONS_KV;
  supports both cmp_* and canonical_id as query id; Phase 0 keyword search
  with note to upgrade to Vectorize in Phase 2
- sandboxAcceptance.ts: updated field names, fixed TextDecoder TS type
- ensureTemplate.ts: removed KBDB dependency, now a KV health check
- tests: updated component_id → canonical_id
- CONTRIBUTING.md: explain hash_id derivation and dual-id workflow reference syntax

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 14:41:22 +08:00
Leo d8028eabe0 feat(registry): aliases.yaml scope synonym table + contract aliases field
- registry/aliases.yaml: scope-level synonym table for 21 built-in components
  covers api (gmail/google_sheets/telegram/line_notify/http_request),
  data (string/array/date/number/json), logic (if/foreach/switch/try_catch/wait),
  ai scopes; includes zh/en/abbrev variants
- types.ts: add optional aliases[] field to ComponentContractSchema
- CONTRIBUTING.md: explain aliases auto-merge from aliases.yaml vs manual contract aliases

Note: manual maintenance for now; aliases.yaml becomes KBDB synonym graph seed data
when KBDB is introduced.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 14:34:16 +08:00
Leo 875ecd2265 docs(contributing): canonical_id naming rules + description semantics guide
- canonical_id naming table by category (api/data/logic/ai)
- display_name vs canonical_id distinction (display_name is free-form)
- description writing guide for semantic search indexing
- contract.yaml example updated to show the difference

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 14:25:41 +08:00
Leo 67e1e03d0b chore(cli): fix bin path, add files field, bump to v1.0.1
- npm pkg fix: bin path "dist/index.js" (no leading ./)
- files: ["dist/"] to only publish compiled output
- prepublishOnly: chmod +x dist/index.js to preserve executable bit
- version: 1.0.0 → 1.0.1

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 14:17:37 +08:00