Files
maestro/docs/maintenance-checklist.md
T
oss-sync 57702b4f34
CI / build-and-test (push) Canceled after 0s
sync: update from private repo (2336949a)
2026-09-17 11:29:31 +00:00

59 KiB
Raw Blame History

メンテナンスチェックリスト

コード変更時に連動して修正が必要な箇所のリスト。 過去に実際に発生した不整合をもとに作成。


0-A. ツールモジュールの自己申告(TOOL_MODULE)— まずここを読む

**2026-08-15(#1126)から、モジュールは自分の属性を自分で申告できる。**新しくツールを足すときは、離れた 4 ファイルを追いかける前に、まずモジュールの中に申告を 1 つ書く。

書き方

自分のモジュール(src/engine/tools/<name>.ts)で、TOOL_DEFS の隣に置く。

import type { ToolModuleManifest } from './module-manifest.js';

export const TOOL_MODULE: ToolModuleManifest = {
  id: 'youtube',          // ファイル名(拡張子なし)と揃える
  category: 'youtube',    // 分類。MODULE_SPECS と同じ語彙
  rawSaveTools: ['GetYouTubeTranscript', 'SearchYouTube'],
};

書ける項目

項目 意味 以前の書き場所
id モジュールの識別子。診断とテストの照合に使う (なし)
category 分類 tool-categories.ts の MODULE_SPECS
metaTools ワークスペース設定に関係なく常時載せる tool-categories.ts と index.ts の 2 か所
rawSaveTools 結果を logs/raw/ に生のまま残す raw-save.ts の RAW_SAVE_TOOLS
rawLogOnlyTools 保存先パスだけ記録する raw-save.ts の RAW_LOG_ONLY_TOOLS
sensitiveTools 分類が既定 ON でも個別に既定 OFF tool-categories.ts の SENSITIVE_TOOLS

**書かない項目もある。**分類そのものを慎重扱いにする指定(SENSITIVE_CATEGORIES)は分類の属性なので申告には載せない。ワークフローの同梱は次の段の話で、まだ入れていない。

守ること

  • **申告に書いた名前は、そのモジュールの TOOL_DEFS に必ずあること。**無い名前は収集時に落とされ、phantoms に記録される(採用されない)
  • **同じ属性を中央の一覧と申告の両方に書かない。**両方にあると、申告を消しても実効値が変わらず、申告が飾りになる。移すときは中央の一覧から必ず消す
  • **読む側は必ず解決関数を通すこと。**中央の一覧を直接読むと、申告した分が落ちる
解決関数(src/engine/tools/module-manifest.ts) 返すもの
resolveMetaToolNames() 常時利用の実効集合
resolveRawSaveToolNames() 生データ保存の実効集合
resolveRawLogOnlyToolNames() パスだけ記録の実効集合
resolveSensitiveToolNames() 既定 OFF の実効集合
resolveDeclaredCategories() 申告された分類(指定 → 分類)

どれも「中央の一覧 ∪ 全モジュールの申告」を返す。**申告を持たないモジュールは、いままでどおり中央の一覧だけで決まる。**一度に全部を移す必要はない。

中央の一覧は残っている

tool-categories.ts の META_TOOLS / SENSITIVE_TOOLS、raw-save.ts の RAW_SAVE_TOOLS / RAW_LOG_ONLY_TOOLS は消していない。申告を持たないモジュールの受け皿として残す。移すときだけ、そのモジュールの分を中央から申告へ動かす。

確認方法

# 申告の収集・実効値・配線をまとめて見る
npx vitest run src/engine/tools/module-manifest.test.ts

この試験には登録済みモジュールが 1 本残らず読み込めることを見る検査が入っている。index.ts の tryLoadModule は読み込み失敗を握り潰すので、モジュールが壊れてもツールが黙って消えるだけで試験は緑のままになる。その穴を塞ぐための検査なので、中に try/catch を足さないこと。

既知の食い違い(未修正・意図的)

workspace-task-search.ts と package-request.ts は index.ts が実行時に読み込んでいるのに MODULE_SPECS に載っていない。載っているツールが全部 META なので実行時には届いてしまい、抜けに気づけない形になっている。/api/tools のツール一覧には出ていない。MODULE_SPECS に足すと設定画面の見た目が変わるため、この段では足さず、module-manifest.ts の EXTRA_MODULE_SPECIFIERS に置いて読み込み検査の対象にだけ入れてある。


0-B. 群(パック)の台帳 — 環境全体で群ごと止める

台帳: src/engine/tools/tool-pack-ledger.ts(データだけ。import を持たない) 関所: src/engine/tools/tool-pack-gate.ts(設定を読んで判定を凍らせる)

config.yaml の tool_packs.disabled に群の名前を書くと、その群のモジュールを読み込む前に断る。ツール定義・分類・申告の収集・/api/tools の一覧が同時に消える。

台帳に足すときの条件(両方満たすこと)

  1. **外の相手をその群だけが持っている。**外部 CLI・外部サーバー・外部 API・外部ライブラリのいずれかが、その群の中からしか呼ばれていない
  2. **代わりを置ける。**同じ用途を別の事業者・別の MCP サーバーで満たせる

**分類が同じという理由だけで束ねないこと。**文字起こし(speech)と読み上げ(speak)は別の群にしてある——接続先のサーバーが別だから。

条件を満たさないものを足すと「外したのに縁が切れていない」群ができて、段 F(実体と依存を捨てる)で必ず詰まる。載せなかったものとその理由は台帳のコメントにある(mermaid / calendar / data / review / app-test)。

守ること

  • **設定の変更通知(ConfigManager の config-changed)に関所を繋がないこと。**走行中にツール一覧が変わると継続ジョブのプロンプトキャッシュが全損する。反映は再起動時のみ
  • モジュール指定を呼び出し側で replace しないこと。normalizeToolModuleSpecifier() を通す。MODULE_SPECS は ../engine/tools/x.js、index.ts は ./x.js の 2 通りがあり、片方だけ素通りするのが一番起きやすい壊れ方
  • **台帳の群のツールを META_TOOLS / SENSITIVE_TOOLS に書かないこと。**中央の一覧は群と無関係に効くので、書くと止めた群の名前が許可一覧に残る。tool-pack-gate.test.ts の歯止めが落ちる
  • 台帳に載っていない群はいままでどおり全部使える。これが既定

関所を通す先(6 か所。1 つでも抜けると片側だけ素通りする)

場所 何が消えるか
tools/index.ts の tryLoadModule エージェントに渡るツール定義と、実行の口
module-manifest.ts の listRegisteredToolModuleSpecifiers 自己申告の収集・分類の地図・生データ保存の対象
tool-categories.ts の listToolCategories 設定画面に並ぶ分類のトグル
bridge/tools-api.ts の loadBuiltinEntries /api/tools のツール一覧
workspace-tool-policy.ts の個別 sensitive ループ ワークスペース側から入れ直す道(層の向き)
worker.ts の ensureKeepaGraphs 呼び出し 成果物に差し込まれる価格グラフ(外部の画像 URL)

最後の 1 行が要る理由は、群の中の関数を群の外から静的に呼んでいるから。import で直に持ってくる経路はモジュール読み込みの関所を通らないので、呼ぶ側が自分で isToolPackDisabled() を見るしかない。ツール定義が消えても差し込みが残ると、成果物を開いた利用者の画面が毎回その外部の相手を叩く——「群を外せば縁が切れる」が嘘になる。群の中の関数を群の外から import したら、必ずこの形で関所を通すこと。

逆に、関所を通してはいけない 2 か所(保存の検査)

保存済みの設定に、いま止めた群の名前が残っていることがある。それを綴り間違いと同じ扱いで弾くと、その名前を送ってくるクライアントの保存が全部落ちる。

送ってくるのは次の 2 つ。画面から普通に開き直した場合は、分類の一覧に無いので送られない。

  • 群を止める前に開かれたままのタブ
  • API を直接叩くクライアント

自動プロンプトの規則だけは事情が違い、画面から編集しても必ず送られる(ツール名は規則そのものの中身で、ツール一覧から組み直されないため)。落ちるのは、その群のツールを名指ししている規則の保存だけ。規則は 1 本ずつ送るので、他の規則は保存できる。

場所 使うもの
space-tool-policy-api.ts の保存検査 listToolCategories({ includeEnvDisabled: true })
space-auto-prompter-api.ts の knownToolNames getToolCategoryMap({ includeEnvDisabled: true })

「知っている名前か」を判定する側だけが includeEnvDisabled を使う。実際にどのツールが使えるかを決める側で使うと、層の向きが崩れる。ツール名やカテゴリ名を allowlist で検査する場所を新しく足すときは、ここに並べること。

画面に出ていない名前を、保存で消さないこと

space-tool-policy-api.ts の保存(PUT)は、保存済みの名前のうちいま画面に出ていないものを、送られてこなくても残す(hiddenStoredNames)。

画面が送る名前は GET が返した分類一覧から組み立てられる。群を止めるとその分類は一覧から消えるので、以後どの保存でも送られない。送られないことを「入れ直した」と読むと、そのワークスペースが明示的に切っていた分類が黙って消え、管理者があとで群を戻したときに既定 ON で復活する。切ったという意思のほうが失われるので、見えない名前は保存済みの値を残す。

残す対象は「既知(環境で止めた分を含む)だが、いまの画面には出ていない」名前だけ。綴り間違いの残骸はこれまでどおり掃除される。ツール名やカテゴリ名を丸ごと置き換える保存 API を新しく足すときは、同じ形にすること。

判定の記録

「いつ・なぜ・誰の設定で」は getToolPackDecisions() に全部載る。起動時に [tool-packs] resolved の 1 行がログに出る。群ごとの記録を足したくなったらここを読む——新しい記録の仕組みを作らないこと。

確認方法

npx vitest run src/engine/tools/tool-pack-gate.test.ts \
               src/engine/tools/tool-pack-gate.wiring.test.ts \
               src/bridge/space-api.tool-policy.pack-gate.test.ts

台帳に群を足したら、config.yaml.example の一覧・ui/src/content/help/19-admin.md の表・更新履歴も合わせて直す。あわせて pieces/*.yaml をその群のツール名で grep すること。既定のワークフローが「必ず〜を使う」の形でそのツールを名指ししていたら、群を止めた環境ではその指示が存在しないツールを指す。名指ししているワークフロー名はヘルプの注意書きに出す。


0. ツールのカテゴリ区分(workspace tool policy)

新しいツールを追加した場合、または既存ツールのカテゴリを変更した場合は、src/engine/tools/tool-categories.ts の MODULE_SPECS に登録し、安全(標準)かセンシティブかの区分を決める。workspace tool policy はこの定義を参照して、ワークスペースごとにツールの利用可否を解決する。

**先に 0-A を読むこと。**新しいモジュールは、分類も既定 OFF も自分の TOOL_MODULE 申告に書ける。以下は申告を持たない既存モジュールの経路。

  • SENSITIVE_CATEGORIES(現在: ssh / browser)に含まれるカテゴリのツールはデフォルト無効
  • SENSITIVE_TOOLS(現在: Bash / SpawnSubTask)に列挙されたツールは、カテゴリが core であっても個別にセンシティブ扱い。SpinOffTask は task-spawn.ts の申告側にある
  • 新カテゴリは安全側(デフォルト有効)が既定。意図的にセンシティブにする場合のみ SENSITIVE_CATEGORIES に追加する

対象ファイル:

  • src/engine/tools/tool-categories.ts — MODULE_SPECS / SENSITIVE_CATEGORIES / SENSITIVE_TOOLS
  • src/engine/tools/index.ts — tryLoadModule でのカテゴリ指定
  • src/bridge/tools-api.ts — カテゴリ情報を返す /api/tools エンドポイント
  • ui/src/content/help/16-tools.md — カテゴリ別の概要テーブルに行を追加

確認方法:

# tool-categories.ts のカテゴリ定義とツール一覧が揃っているか
npx vitest run src/engine/tools/tool-categories.test.ts
# センシティブ区分が resolver に正しく伝わるか(regression guard)
npx vitest run src/engine/tools/resolver.test.ts

1. ツールモジュールを新規追加した場合

対象ファイル:

  • src/engine/tools/<module>.ts — 新モジュール本体
  • src/engine/tools/index.ts — tryLoadModule で動的ロード追加
  • src/bridge/tools-api.ts — /api/tools のモジュール一覧に追加
  • CLAUDE.md — 「ツールモジュール構成」テーブルに行を追加

なぜ必要か: index.ts はエージェント実行時のツール dispatch に使われ、tools-api.ts は Settings UI のピース編集画面でツール候補を返す API。 tools-api.ts への追加を忘れると、エンジンでは使えるのに UI のドロップダウンに表示されないという不整合が起きる(実際に amazon, speech, checklist, knowledge の4モジュールで発生した)。

確認方法:

# index.ts でロードしているモジュール数と tools-api.ts のモジュール数を比較
grep -c 'tryLoadModule' src/engine/tools/index.ts
grep -c "engine/tools/" src/bridge/tools-api.ts
# 数が一致していなければ漏れがある

2. 既存モジュールにツールを追加した場合

対象ファイル:

  • src/engine/tools/tool-categories.ts — 新ツールを適切なカテゴリに割り当てる(sensitive は既定 OFF)。ツールの利用可否は workspace tool policy(設定→ツール)が決めるので、piece 側に追加する必要はない
  • CLAUDE.md — 「ツールモジュール構成」テーブルの該当モジュール行に新ツール名を追加
  • docs/tools/{name}.md — 新ツールの詳細ドキュメント(推奨)
  • ツール description — 1 文 + 「詳細は ReadToolDoc({ name: "XXX" })」を末尾に記述

なぜ必要か: ツールの提示は workspace tool policy が決める(カテゴリ単位。sensitive 以外は既定 ON)。カテゴリ未割り当てだとどのワークスペースでも出ない。 CLAUDE.md のテーブルが古いと、Claude Code 自身が既存ツールを認識せずに新規実装してしまうリスクがある。 ツール description は毎 LLM 呼び出しに乗るため 1 文に絞り、詳細は ReadToolDoc 経由で必要時のみ読み込む(agent-loop が movement 開始時に description サマリを自動カタログ化する)。

確認方法: tool-categories.ts で新ツールがカテゴリに割り当てられているか確認する。 CLAUDE.md のモジュールテーブルに新ツール名が含まれているか確認する。

実例: TestWorkspaceApp(2026-06-24): src/engine/tools/app-test.ts に追加 → tools/index.ts で tryLoadModule 追加 → src/bridge/tools-api.ts のモジュール一覧に追加 → tool-categories.ts でカテゴリ割り当て → src/engine/tools/docs.ts の TOOL_DOC_ALIASES に testworkspaceapp: 'testworkspaceapp' を追加 → docs/tools/testworkspaceapp.md を新規作成。(旧手順の「piece の allowed_tools に追加」は撤去済み)


3. ツールをリネーム・削除した場合

対象ファイル:

  • src/engine/tools/tool-categories.ts — カテゴリ定義・SENSITIVE_TOOLS から旧名を削除/リネーム
  • src/engine/tools/raw-save.ts — RAW_SAVE_TOOLS に旧名が残っていないか
  • ui/src/components/settings/ToolsWebForm.tsx / ToolsMediaForm.tsx / ToolsExternalForm.tsx — ヘルプテキスト等にツール名の言及がないか
  • CLAUDE.md — ツールモジュール構成テーブル

なぜ必要か: BrowserAction を BrowseWeb に統合した際、5つの piece と UI ヘルプテキストに旧名の参照が残っていた。

確認方法:

grep -r '旧ツール名' pieces/ src/ ui/src/ CLAUDE.md

4. config.yaml に新しい設定キーを追加した場合

対象ファイル:

  • src/config.ts — ToolsConfig / AppConfig / LlmConfig / StorageConfig 等のインターフェースにフィールド追加
  • src/config-normalize.ts — v1 → v2 normalizer に新フィールドの mirror が要るか確認 (storage / llm 配下に置く場合)
  • config.yaml.example — YAML キー(スネークケース)でサンプル・コメント追加。config_version: 2 を必ず先頭に保つ
  • ui/src/components/settings/ — 該当セクションの Form コンポーネントに UI フィールド追加
  • CLAUDE.md — 設定セクションに説明追加(必要に応じて)

なぜ必要か: config.ts と config.yaml.example の不一致は「設定が効かない」「ドキュメントと実動作が違う」を招く。 Settings UI への追加を忘れると、YAML を直接編集しないと設定できなくなる。 v2 layout (llm.* / storage.*) では normalizer 側の backfill / migrate path も触らないと、v1 ファイルでの読み取り互換が壊れる。

確認方法:

# config.ts のフィールド数と config.yaml.example のキー数を目視比較
# Settings UI の各 Form ファイルで未対応フィールドがないか確認
# normalize の v1 fixture が落ちていないか
npx vitest run src/config-normalize.test.ts

4-B. 新しい Settings Form コンポーネントを追加した場合

対象ファイル (両方必須):

  • ui/src/components/settings/<Name>Form.tsx — Form 本体
  • ui/src/components/settings/ConfigForm.tsx — renderActiveForm() の switch(section) に新 case 'new-id': return <NewForm {...formProps} />; を追加
  • ui/src/components/settings/SettingsSidebar.tsx — CONFIG_GROUPS[].sections[] に { id: 'new-id', label: '...' } を追加。admin 限定なら親グループに adminOnly: true
  • ui/src/lib/urlState.ts — section id の許可リストにも追加 (URL state からの復元)
  • 旧 id をリネームした場合は LEGACY_SECTION_REDIRECT (SettingsSidebar.tsx) にエントリ追加してブックマーク互換を維持

なぜ必要か: ConfigForm の switch と SettingsSidebar の section リストは独立に管理されており、片方だけ追加すると「サイドバーには項目があるが何も描画されない」「描画はされるがサイドバーから飛べない」状態になる。Step 3 (sidebar 再編) / Step 7-9 (form 分割) で両方を毎回触る前提で設計されている。

禁止事項:

  • 削除した旧 Form (ProviderForm.tsx, WorkersForm.tsx, WorkspaceForm.tsx のような単一巨大 form) を復活させない。LLM Workers / Paths & Storage / Execution / Tools-* の分割は v2 design の前提
  • カンマ区切り入力で配列フィールドを作らない。chip エディタ / textarea / multiselect のいずれかを使うこと (値に , を含めるケースを壊さないため)
  • Secret 入力で '********' の magic string 判定を再導入しない。SecretInput (4-state: unchanged / literal / env_ref / cleared) を再利用すること

確認方法:

# section id が両方のファイルで揃っているか
grep -E "'[a-z-]+'" ui/src/components/settings/SettingsSidebar.tsx | grep "id:"
grep "case '" ui/src/components/settings/ConfigForm.tsx
# legacy redirect が漏れていないか
grep -A 20 'LEGACY_SECTION_REDIRECT' ui/src/components/settings/SettingsSidebar.tsx

4-C. TopBar のトップレベルタブ (page) を追加・削除した場合

PageId は複数ファイルに分散しているので、追加・削除のどちらでも下記をすべて触る。

対象ファイル (すべて必須):

  • ui/src/lib/urlState.ts — PAGES 配列 (PageId の唯一の定義源)
  • ui/src/components/layout/TopBar.tsx — NAV_ITEMS (タブ表示順 + adminOnly / requiresAuth)
  • ui/src/components/layout/NavDrawer.tsx — NAV_ICONS は Record<PageId, ReactNode> の網羅型。PageId を削除したら同名キーも消さないと TS が excess-property で落ちる
  • ui/src/App.tsx — {page === '...' && <... />} のレンダー分岐 (+ 不要になった import の削除)
  • 外部からそのページへ飛ばす導線 (url.searchParams.set('page', '...') 等) の付け替え
  • i18n layout.nav.<id> (ui/src/i18n/locales/{en,ja}/layout.json)

履歴 (2026-06-19): 旧「ユーザーフォルダ」(userfolder) タブは廃止。中身は個人ワークスペースの 設定 タブ (AGENTS.md / メモリ / Pieces / スキル / MCP / SSH / ブラウザ。SpaceSettings.tsx) に集約し、孤立していた Pets はグローバル設定の pets セクション (settings/PetsForm.tsx) へ移設した。userfolder/ 配下のパネル (AgentsMdPanel / MemoryPanel / McpPanel / SshConnectionsPanel / SkillsPanel / AddBrowserSessionDialog / MonacoFileEditor 等) は SpaceSettings / SpaceBrowserPanel / 設定フォームが再利用するため残置。


4-D. タスク spawn に新しいフィールドを追加した場合(二重実装ミラー)

タスク/ジョブの spawn 経路は 2 系統の並行実装 になっている(統合は別課題):

  • 通常作成: src/bridge/local-tasks-crud-api.ts(POST /api/local/tasks → createLocalTask + createJob)
  • スケジュール実行: src/scheduler.ts の executeAgentScheduledTask()(createLocalTask + createJob を独自に呼ぶ。Router Phase 2 は通らない・metadata block はハードコード)

タスクやジョブに新しいフィールド(例: issue #974 の llmWorkerId/requiredWorkerId)を追加したら、両方の経路に配線されているか必ず確認する。片方だけだと「UI から作ると効くがスケジュール実行だと落ちる」型の欠落になる(#974 がまさにこれ)。

5. SSRF 保護に関わる変更をした場合

対象ファイル:

  • src/engine/tools/shared/ssrf.ts — SSRF チェックロジック本体 + ブラウザへの装着(installContextSsrfGuard)
  • src/engine/tools/browser.ts — BrowseWeb(ensureContextHooks / openIsolatedGuardedPage + ssrfCheck)
  • src/engine/tools/web.ts — WebFetch / DownloadFile / 検索・スクショが借りる共有ブラウザ
  • src/engine/tools/x.ts — X の card 画像取得(同じ共有ブラウザを借りる)
  • ui/src/components/settings/ToolsWebForm.tsx — SSRF Allowed Hosts のヘルプテキスト

なぜ必要か: webfetchAllowedHosts は WebFetch と BrowseWeb の両方で使われている。片方だけ修正すると保護に穴が開くか、意図しないブロックが発生する。


6. ログ出力に関わる変更をした場合

対象ファイル:

  • src/engine/tools/raw-save.ts — RAW_SAVE_TOOLS / RAW_LOG_ONLY_TOOLS の更新
  • 各ツールモジュール内の appendXxxHistory() 関数

なぜ必要か: 新ツールを追加したとき、RAW_SAVE_TOOLS への追加を忘れると logs/raw/ に出力が保存されない。 ツール側で独自に raw 保存する場合は RAW_SAVE_TOOLS に含めない(二重保存回避)。

保存先の整理:

ログ種別 パス 内容
生データ logs/raw/{tool}-{timestamp}.txt/.json ツール実行結果の生出力
WebFetch 履歴 logs/webfetch-history.jsonl URL, ステータス, サイズ
ダウンロード履歴 logs/downloads.jsonl 保存パス, サイズ
チェックリスト logs/checklists/{name}.json チェックリスト状態
rawdata インデックス logs/rawdata-history.jsonl raw 保存のメタデータ

7. ツールの詳細ドキュメントを書く場合

対象ファイル:

  • docs/tools/{toolname-lowercase}.md — ツールの詳細な使い方ガイド
  • 該当ツールの description — 概要 + ReadToolDoc({ name: "XXX" }) で参照可能と明記
  • src/engine/tools/docs.ts の TOOL_DOC_ALIASES — 関連ツール名を同じ doc にマップ(例: checkitem: 'checklist')
  • 既存 piece に同じガイダンスが書かれていれば削除する(重複解消)

なぜ必要か: Tool description は毎回 LLM のコンテキストに乗るため肥大化させたくない。詳細手順・ワークフロー例は docs/tools/{name}.md に置き、ReadToolDoc ツールで必要時に取得する設計。 ReadToolDoc は META_TOOLS として常時利用可能(workspace tool policy でも常時 ON)。 関連ツール(CheckItem / CreateChecklist / GetChecklist 等)は1つの doc にまとめてエイリアス経由で引けるようにする。

確認方法:

  • ファイル名は小文字(例: BrowseWeb → browseweb.md)
  • description の末尾に「詳細は ReadToolDoc(...) で取得可能」を明記
  • 全 piece から対応する重複ガイダンスを削除

8. piece の movement 構造を変更した場合

対象ファイル:

  • 対象 pieces/*.yaml — rules に遷移先を明示追加

なぜ必要か: default_next は機械的フォールバック専用。LLM が選べる遷移先は rules[].next に明示されたものだけ。 rules に追加せず default_next だけに頼ると、LLM が遷移先を選択できない。


9. Reflection に新しい memory type を追加した場合

対象ファイル:

  • src/engine/reflection/types.ts — ReflectionMemoryType の union を拡張
  • src/engine/reflection/reflection-schema.ts — LLM tool schema の type enum
  • src/engine/reflection/reflection-prompt.ts — システムプロンプトの type 説明
  • src/engine/reflection/semantic-validator.ts — ALLOWED_TYPES セット
  • src/bridge/memory-api.ts — PUT のバリデーション
  • ui/src/components/settings/MemoryLearningForm.tsx — type ドロップダウン

なぜ必要か: 4 種類 (user / feedback / project / reference) はメモリ意味論の単位。LLM は schema で型強制されるので、schema・prompt・validator のいずれかを忘れると LLM が「未知の type」を吐いて全 reflection が rejected_unknown_type で落ちる。UI ドロップダウンの更新を忘れるとユーザーが手動編集できなくなる。


10. Reflection に新しい rejection code を追加した場合

対象ファイル:

  • src/engine/reflection/types.ts — ReflectionRejectionCode の union を拡張
  • src/engine/reflection/semantic-validator.ts — 新しいケースで返す
  • src/engine/reflection/applier.ts — 必要に応じて applier 側の rejection 経路も追加
  • src/bridge/memory-api.ts — 同じコードを返す PUT バリデーションを更新
  • ui/src/components/settings/MemoryLearningForm.tsx — UI 側でコード → 日本語メッセージ変換
  • src/engine/reflection/applier.fuzz.test.ts — fuzz の "known code list" に追加

なぜ必要か: rejection code は metrics・UI 表示・fuzz invariant の3箇所で参照される。fuzz テストは「decision の code は known list のいずれか」と assertion しているので、追加し忘れると fuzz が落ちる。UI 側で未対応コードはユーザーに「不明なエラー」と表示されてしまう。


11. MCP サーバー追加 / authKind 仕様変更時

対象ファイル:

  • src/mcp/registry.ts — auth_kind の受け入れ・バリデーション
  • src/mcp/token-manager.ts — hasToken / getValidToken の authKind 分岐
  • src/bridge/mcp-api.ts — admin (global server) ルート、/api/mcp/connections
  • src/bridge/user-servers-api.ts — user-owned server CRUD
  • ui/src/components/userfolder/McpServersPanel.tsx — authKind ラジオ + 条件付きフォーム
  • ui/src/components/userfolder/McpConnectionsPanel.tsx — authKind=api_key は Authorize ボタン非表示
  • docs/mcp.md — 運用者向け手順 (OAuth と api_key 両方)

なぜ必要か: authKind は server 行に保存され、token-manager・aggregator・UI のそれぞれで分岐する。Phase 8 で OAuth 専用設計から拡張したため、新しい authKind を入れる場合は schema migration + UI フォーム + docs の 3 点を必ず触る。OAuth start ルートは authKind=oauth でしか動かない (api_key は 400 を返す) ので、route 側のガードも忘れない。

確認方法:

  • grep -rn "authKind" src/mcp/ src/bridge/mcp-api.ts src/bridge/user-servers-api.ts ui/src/components/userfolder/Mcp*.tsx で参照漏れを確認
  • 統合テスト: npx vitest run src/mcp/integration.test.ts (OAuth + api_key 両 path をカバー)

UI フォントサイズスケール (Tailwind class 規約)

UI に新しいパネルやコンポーネントを追加するときは、以下の 6 段階スケール から選ぶ。それ以外の text-[Npx] を導入すると全体の字組が崩れる。

サイズ クラス 用途
10px text-[10px] セクションラベル(uppercase)、極小バッジ
11px text-2xs ⭐ (Tailwind 拡張、tailwind.config.js で定義済み) ヘルプテキスト、キャプション、メタ情報
12px text-xs 本文小、ボタンラベル、フォーム入力、テーブルセル
13px text-[13px] (Tailwind 標準に該当なし) パネル本文、メイン段落
14px text-sm フォームラベル、ナビゲーション項目、見出し小
16px+ text-base / text-lg / text-xl / text-2xl h2 セクション見出し、ページタイトル、ヒーロー

禁止リスト

以下は 絶対に書かないこと。書いた場合は 2026-05 commit 993ef2f の正規化と同様の一括置換でやり直しになる。

書いてしまった 置き換え先
text-[11px] text-2xs
text-[12px] text-xs
text-[14px] text-sm
text-[15px] text-sm
text-[16px] text-base
text-[18px] text-lg
text-[9px] text-[10px] (極小に丸め)
text-[12.5px] text-[13px]
任意の非標準 px 上記スケールに最も近いものに丸める

なぜ必要か

  • 過去に新規パネル (Help, MCP, AGENTS.md 等) を追加するたびに implementer が独自に text-[Npx] を書き、累積で 13 種類のサイズ が混在した
  • 統一したフォントサイズは UI の一貫性 + 認知負荷の低下に直結する
  • Tailwind utility (text-xs/sm/base/lg/xl/2xl) は font-size と line-height がセット定義されているので、行間も自然に揃う
  • 不要な arbitrary value (text-[Npx]) は CSS の出力サイズも増やす

確認方法

cd ui
# 禁止サイズが残っていないか確認 — 何も出なければ OK
grep -rohE 'text-\[(9|11|12|12\.5|14|15|16|17|18)px\]' src --include='*.tsx'

# 全体のサイズ分布を確認
grep -rohE 'text-\[[0-9]+(\.[0-9]+)?px\]|text-(xs|sm|base|lg|xl|2xl|3xl|4xl|2xs)' src --include='*.tsx' | sort | uniq -c | sort -rn

期待される分布 (Tailwind utility が主、arbitrary は 10px / 13px のみ):

text-xs    (~250 ↑)
text-2xs   (~180 ↑)
text-sm    (~100 ↑)
text-[13px] (~100)
text-[10px] (~100)
text-base, text-lg, text-xl, text-2xl, text-4xl ... (少量)

触る場所

  • 新規 UI コンポーネント全般 (ui/src/**/*.tsx)
  • 既存パネルへの追加要素
  • 設定フォーム (ui/src/components/settings/)
  • ダイアログ・モーダル

12. SSH 関連変更時 (Phase 0-8)

12-A. SSH ツール (SshExec / SshUpload / SshDownload) を変更したとき

対象ファイル:

  • src/engine/tools/ssh.ts — TOOL_DEFS + executeTool + 12-step orchestration
  • src/engine/tools/ssh.test.ts — unit tests (23 件)
  • src/engine/tools/ssh.e2e.test.ts — in-process ssh2 server e2e (4 件、SKIP_SSH_E2E=1 で skip)
  • src/engine/tools/index.ts — tryLoadModule('./ssh.js') 経由のロード + dispatch
  • src/engine/tools/core.ts — ToolContext.pieceName (piece grants check に必要)
  • src/engine/piece-runner.ts — ToolContext 構築時に pieceName: piece.name を thread
  • src/engine/tools/docs.ts — TOOL_DOC_ALIASES で 3 ツール名を 'ssh-tools' にマップ
  • docs/tools/ssh-tools.md — LLM 向け詳細ドキュメント
  • src/bridge/server.ts — setSshSubsystem({...}) で DI

新ツールを SSH 系で追加する場合、または既存ツールに引数を増やす場合:

  • ssh.ts の TOOL_DEFS description は 1 文 + 「詳細は ReadToolDoc」 に絞る (毎呼び出しのトークン節約)
  • preflight (steps 1-7) を流用するため runExec/runUpload/runDownload の skeleton を踏襲
  • finally で clearBuffer(pem, passphrase) を必ず実行 — ssh2 の internal retention は防げないが我々の copy は zeroize する
  • 監査 action 名は ssh.<verb> 形式 (例: ssh.exec, ssh.upload)。command 全文は 記録せず SHA-256 16-char hex を detail.command_hash に
  • piece grants check は subsystem.accessResolver.resolve() を必ず通す (piece membership → access decision → state checks → policy の順)

新しい SshSessionError code を追加する場合:

  • src/ssh/session.ts の SshSessionErrorCode union を拡張
  • src/engine/tools/ssh.ts の formatSessionError switch に LLM-actionable message を追加
  • docs/tools/ssh-tools.md の「共通エラーコード一覧」表に追記
  • docs/ssh.md の「Troubleshooting → Symptom → cause table」に追記

確認方法:

# tool が grants check を通っているか
grep -n "accessResolver\.resolve\|preflight" src/engine/tools/ssh.ts
# 監査 action 名が既存と整合
grep -n "action:" src/engine/tools/ssh.ts src/bridge/ssh-api.ts | sort -u
# tests
npx vitest run src/engine/tools/ssh.test.ts
SKIP_SSH_E2E=  npx vitest run src/engine/tools/ssh.e2e.test.ts  # 明示的に run

12-B. SSH HTTP API (src/bridge/ssh-api.ts) を変更したとき

対象ファイル:

  • src/bridge/ssh-api.ts — User router + Admin router (25 endpoints)
  • src/bridge/ssh-api.test.ts — auth / maintenance / reason / CRUD / TOFU / grants / rotation
  • src/ssh/maintenance.ts — in-memory maintenance flag
  • src/ssh/admin-rate-limit.ts — token bucket for force-unlock (10/hr)
  • src/bridge/server.ts — ssh.enabled ゲートで両 router をマウント
  • src/ssh/session.ts — sshTest() (HTTP test endpoint で使用)
  • docs/ssh.md §"HTTP API Reference" — endpoint table

新しい admin write エンドポイントを追加するとき:

  • body.reason 必須 (≥ 8 chars)。validateReason() を必ず呼ぶ
  • 操作直前に maintenance503() ガードを呼ぶ (read 系は不要)
  • 成功・失敗どちらも auditRepo.beginAndComplete() で ssh_audit_log に記録
  • 監査 action は src/ssh/audit-repo.ts の SshAuditOutcome と Phase 5 設計書の enum を踏襲
  • admin-only フラグ (allow_remote_unrestricted, allow_private_addresses per-connection) は user 経路で常に reject

新しい user write エンドポイントを追加するとき:

  • getUserId() で取得した userId をオーナーチェックに使う
  • 他人の所有物に対しては 404 を返す (存在を漏らさない)

確認方法:

# admin endpoint で reason check / maintenance ガードが漏れていないか
grep -n "deps.requireAdmin\|maintenance503\|validateReason" src/bridge/ssh-api.ts
# audit log の action 名が既存と整合しているか
grep -n "action:" src/bridge/ssh-api.ts

12-C. SSH repo / schema を変更したとき

対象ファイル:

  • src/db/schema.sql — ssh_connections, ssh_connection_grants, ssh_audit_log, ssh_abuse_counters, system_deks
  • src/db/migrate.ts — PRAGMA table_info → 列存在チェック → ALTER TABLE ADD COLUMN パターン (バージョン管理テーブル不使用)
  • src/ssh/connection-repo.ts / grants-repo.ts / audit-repo.ts / abuse-repo.ts / access.ts
  • 各 repo に対応する *.test.ts
  • src/ssh/recovery.ts — boot 時 pending → aborted sweep
  • src/ssh/crypto.ts — DEK ラップ / マスター鍵検証
  • docs/ssh.md §"Connection Model" / §"Access Grants" / §"Audit Log"

スキーマ列を追加するとき:

  • schema.sql の CREATE TABLE に列追加 (初期スキーマ)
  • migrate.ts に冪等 ALTER TABLE 追加 (既存 DB 用)
  • 既存テストが影響を受けないか npm test で検証
  • 列をエンドポイントから書く場合、POST/PATCH ハンドラのバリデーションに追加し、admin-only フラグなら user 経路で reject

新しい監査 action を追加するとき:

  • src/ssh/audit-repo.ts の SshAuditOutcome は触らない (固定 5 値: pending|success|failed|denied|aborted)
  • action 名は ssh.<entity>.<verb> 形式 (例: ssh.connection.host_key.replace)
  • docs/ssh.md §"Audit Log → Actions" 表に追加
  • docs/tools/ssh-tools.md の「監査ログ」セクションは tool 経由 action のみなので、tool 起因なら追記

確認方法:

# 監査 action 一覧 (重複/typo 検出)
grep -rn "action: 'ssh\." src/ | awk -F"'" '{print $2}' | sort -u
# repo CAS / transaction が崩れていないか
grep -n "BEGIN\|COMMIT\|prepare(" src/ssh/*.ts | head -30

12-D. SSH UI を変更したとき

対象ファイル:

  • ユーザー向け:
    • ui/src/components/userfolder/SshConnectionsPanel.tsx — 一覧 + actions
    • ui/src/components/userfolder/SshConnectionForm.tsx — 作成・編集フォーム
    • ui/src/components/userfolder/SshHostKeyDialog.tsx — TOFU verify ダイアログ
  • 管理者向け:
    • ui/src/components/settings/SshForm.tsx — Settings → SSH のルート
    • ui/src/components/settings/SshGlobalConnectionsForm.tsx — グローバル接続 CRUD
    • ui/src/components/settings/SshGrantsForm.tsx — grants CRUD
    • ui/src/components/settings/SshAuditLog.tsx — 全テナント監査ログ
    • ui/src/components/settings/SshMasterKeyRotationForm.tsx — 鍵 rotation
  • 型定義: ui/src/lib/ssh-types.ts
  • ui/src/components/settings/ConfigForm.tsx / SettingsSidebar.tsx — SshForm の登録 (新規 Form 追加時)

確認方法:

  • 新 form を追加した場合 ConfigForm.tsx の section リストに登録、SettingsSidebar.tsx のナビ項目に追加
  • ssh-types.ts と API レスポンス shape (SshConnection, SshGrant, SshAuditRow) が一致しているか
  • 禁止フォントサイズ (text-[11px] 等) を導入していないか — 既存セクションの「UI フォントサイズスケール」参照

12-E. SSH 接続の認可 (workspace connection scoping) を変更したとき

SSH 接続の認可は piece ではなくワークスペースで決まる(設定→SSH の登録接続)。 piece の allowed_ssh_connections は撤去済み。接続スコープは worker が解決して workspaceSshConnections として piece-runner に渡す。

対象ファイル:

  • src/worker.ts — parsedPolicy.enabledSensitive に ssh があるとき listBySpace(spaceId) で接続 ID を解決(workspaceSshConnections)
  • src/engine/piece-runner.ts — workspaceSshConnections ?? [] を ctx.allowedSshConnections / movement.allowedSshConnections に流す
  • src/engine/tools/ssh.ts — preflight で ctx.allowedSshConnections に対して接続 ID を検証
  • docs/ssh.md §"接続の認可"

規約:

  • 接続が 1 つも登録されていない(空配列)→ SSH ツールはクリーンに reject
  • '*' ワイルドカードは worker からは渡さない(常に明示 ID リスト = クロススペース分離保証)

確認方法:

# worker が ssh 接続を解決している経路
grep -n 'workspaceSshConnections' src/worker.ts src/engine/piece-runner.ts

12-F. config.yaml の SSH セクション (SshRuntimeConfig) を変更したとき

対象ファイル:

  • src/ssh/config.ts — SshRuntimeConfig interface + SSH_DEFAULTS + mergeSshConfig
  • src/config.ts — transformKeys でスネーク→キャメル変換、AppConfig に統合
  • config.yaml.example — コメント付きデフォルト値
  • ui/src/components/settings/SshForm.tsx — UI で編集可能にする場合
  • docs/ssh.md §"config.yaml Reference"

確認方法:

# config.ts と config.yaml.example の SSH キー数が一致するか
grep -E '^\s+[a-z_]+:' config.yaml.example | grep -A 30 '^# ssh:' | head -40

12-G. SSH Console (interactive PTY) を変更したとき

SSH Console は SshExec/Upload/Download とは別系統の対話的 PTY 経路。Phase 8 で新規追加。

  • 新ツール SshConsoleEnsure / SshConsoleSend / SshConsoleSnapshot を追加した時:
    • src/engine/tools/ssh-console.ts の TOOL_DEFS に追加
    • src/engine/tools/index.ts の dispatch + aggregation に追加
    • src/engine/tools/docs.ts の TOOL_DOC_ALIASES にエイリアス
    • docs/tools/ssh-console-tools.md を更新
  • 新 WS endpoint を追加した時:
    • src/bridge/server.ts に upgrade handler 配線
    • auth / visibility / access の 4 段階チェックを記述
  • 新 SessionCloseReason を追加した時:
    • src/ssh/console-protocol.ts (server) と ui/src/lib/ssh-console-types.ts (UI) の両方に追加
    • ConsoleHeader.tsx の disconnected reason 表示に文言を追加

13. Scheduler から呼ばない手動オペレーション endpoint

以下の endpoint は UI からの手動操作専用 で、scheduler / Routine / 自動化経路から起動できない設計になっている。Routine 側の payload schema にこれらの override を追加してはいけない (scheduled task のツール境界〈ワークスペースのツールポリシーで決まる〉が想定外に拡大するリスクのため)。

  • POST /api/local/tasks/:id/continue — 別 piece で task を続ける (handoff)
    • 実装: src/bridge/local-tasks-api.ts の /continue ハンドラ
    • terminal 状態 (succeeded / failed / waiting_human / cancelled) のときのみ有効
    • 新カテゴリを追加する場合: scheduled-tasks-api.ts / scheduler.ts 側に「実行する piece を override する」機能を持たせない。実行する piece は task 作成時に固定し、人間が UI から明示的に切り替える経路だけ残す

確認方法:

# scheduler 経路から /continue を叩いている箇所が無いか
grep -rn "/continue\b\|continueTaskWithPiece" src/scheduler.ts src/bridge/scheduled-tasks-api.ts
# 無いことが期待値

14. Knowledge Notes 機能 (削除済み)

共有 Markdown ノート + 購読 + SearchNotes / ReadNote / WriteNote ツールからなる Knowledge Notes 機能は 2026-06 に削除された (共有ワークスペースと役割が重複したため)。 src/notes/・src/bridge/notes-api.ts・src/engine/notes-inject.ts・NotesForm.tsx・ note_index / note_subscriptions / pending_reindex テーブルはすべて撤去済み。 既存 DB に残る孤立テーブルは無害なので DROP していない。再導入の予定はない。


Bash Unrestricted モード (safety.bash_unrestricted) の変更時

対象ファイル:

  • src/engine/tools/sandbox.ts — bwrap サンドボックスのマウント構成・実行ロジック
  • src/engine/tools/core.ts — executeBash の分岐 (ctx.bashUnrestricted)、ToolContext インターフェース
  • src/engine/piece-runner.ts — safetyConfig.bashUnrestricted → ToolContext への伝播
  • src/worker-bootstrap.ts — 起動時 bwrap 可用性チェック
  • src/config.ts — SafetyConfig.bashUnrestricted 定義・バリデーション

なぜ必要か: bwrap のマウント構成を変えた場合、セキュリティ境界が変わる。テスト (sandbox.test.ts, core.test.ts の bashUnrestricted mode セクション) を必ず更新すること。


15. A2A 認可サーバー (src/bridge/a2a/)

外部エージェント連携(A2A)の OAuth2 認可サーバー関連を変更するときに連動が必要な箇所。

DB テーブル・スキーマを変更するとき

  • src/db/schema.sql の CREATE TABLE(oidc_models / a2a_clients)を更新(初期スキーマ)
  • src/db/migrate.ts に冪等 ALTER TABLE ADD COLUMN を追加(既存 DB 用)。dual-path 必須(どちらか片方だけ更新するとテストが大量に落ちる)
  • src/db/repository.a2a.ts / src/bridge/a2a/oidc-adapter.ts のクエリを合わせて修正

config.yaml の a2a セクションを変更するとき

  • YAML キーはスネークケース (client_ttl_seconds)、コード内はキャメルケース (clientTtlSeconds)
  • src/config.ts の transformKeys が自動変換するため、読み取り側では キャメルケースのみ使う
  • config.yaml.example にコメント付きで追記する
  • A2aConfig interface(src/config.ts)に対応フィールドを追加

A2A ルーターを変更するとき

  • createConsentRouter / createA2aClientsAdminRouter / mountA2aOidc はいずれも src/bridge/server.ts でマウントされる。新しい router を追加した場合は server.ts の配線を忘れずに
  • 管理者専用エンドポイントは requireAdmin ミドルウェアを必ず通すこと
  • ユーザー向け同意エンドポイントは requireAuth を通し、操作対象の sub(subject)をセッションの userId と照合する

A2A 公開スキル(スペース許可リスト)を変更するとき

  • 公開スキルは spaces.a2a_skills(JSON 配列カラム)に保存される。スキーマ変更時は schema.sql と migrate.ts の両方(dual-path 必須)を更新すること
  • 委任は a2a_delegations テーブルで管理し、grant_id で OAuth grant(oidc_models)にリンクする。grant_id の整合性は外部キーではなくアプリ層で保証しているため、grant 削除時は対応する delegation も削除すること
  • 委任フィルタは buildVisibilityWhere の OR 句に追加しない。タスク/ジョブの公開スコープ(private/org/public)とは独立したフィルタとして AND 交差で適用すること(OR に混ぜると cross-space IDOR の原因になる)
  • Agent Card の公開エンドポイント(GET /.well-known/agent.json)とスペースオーナー向け API(公開スキル設定 PATCH)は、いずれも a2a.enabled ゲートを通過した場合のみ有効。ゲートを外したルートを追加しないこと

A2A スキル実行(executor)を変更するとき

  • a2a_tasks テーブルは schema.sql と migrate.ts の両方(dual-path 必須)を更新すること
  • executor は createLocalTask → createJob の順に呼ぶ。createLocalTask 単体はジョブをエンキューしない(createJob で初めて Worker がポーリングで拾える状態になる)
  • ジョブ完了の検知はポーリング(a2a_tasks の status カラムを定期参照)で行い、進捗は SSE で外部エージェントに返す
  • 実行完了後、output/ 以下のファイルは A2A Artifact として返却する
  • 委任スコープは executor でも computeEffectiveScope を通じて再強制する。トークンの委任スコープがスペースの公開スキル設定を上回ることは許容しない(fail-closed)
  • buildVisibilityWhere の OR 句に委任条件を追加しないこと(AND 交差で適用する)

非ブロッキング送信 + reconciler(reconciler.ts / task-finalize.ts)を変更するとき

  • 非ブロッキング契約: message/send の params.configuration.blocking: false は SDK ネイティブ機能。executor が初期 Task(submitted)を publish した時点で SDK が即座にレスポンスを返す(完了を待たない)。外部エージェントは後から tasks/get(params.id)で最新状態を取得する。executor 自体を非ブロッキング化する改造は不要(SDK 側で完結)。
  • reconciler は単一起動: A2aTaskReconciler は server.ts で a2a.enabled ゲート内から 1 インスタンスだけ start() する(多重起動は state churn の原因)。テストでは start() の interval を使わず reconcileOnce() を明示的に呼んで決定論化すること。
  • DB が source of truth: reconciler は a2a_tasks(非 terminal 行)を listNonTerminalA2aTasks で走査し、リンク先の MAESTRO ジョブ状態(getJob)と突き合わせて収束させる。live executor が切断・再起動で消えても、reconciler だけでタスクを terminal 化できる(再起動耐性)。in-memory の per-task detached promise には依存しない。
  • 終端化ロジックは共有: enumerateOutputArtifacts / finalizeStatusFromJob / TERMINAL_JOB_STATUSES(task-finalize.ts)を executor と reconciler の両方が使う。ジョブ状態 → A2A state の写像や artifact 列挙を変えるときは、この共有ヘルパだけを直せば両経路に反映される(ロジックを片方に複製しないこと)。
  • 冪等な状態一致スキップ: reconciler は payload.status.state が算出後の state と一致する場合は再書き込みしない(waiting_human → input-required の churn 防止)。saveA2aTask は task id の upsert なので executor と reconciler が競合しても二重書きは安全。
  • 委任カラムの保持: reconciler が saveA2aTask を呼ぶ際は payload.metadata の a2aDelegationId / a2aGrantId / a2aActingUserId を a2a_tasks の delegation_id / grant_id / acting_user_id 列へ写す(task-store.ts の save 規約と同一)。列を増やすときは executor の初期 Task metadata・SqliteA2aTaskStore.save・reconciler.persistTask の 3 箇所を揃えること。

A2A 成果物ダウンロード(artifact-download-api.ts / artifact-parts.ts)を変更するとき

  • artifactId は決定的: deriveArtifactId(a2aTaskId, fileName) から作る。ランダム UUID に戻すと、executor が流した ID と reconciler が組み直した ID が食い違い、tasks/get のたびに成果物の識別子が変わる(受け入れ条件「ID の安定性」が崩れる)。
  • URI は識別子だけ: {baseUrl}/a2a/artifacts/{taskId}/{artifactId}。ワークスペースの実パス・worktree 名・output/ 相対パスのいずれも URI へ入れないこと。
  • baseUrl は 1 か所から導出: resolveA2aBaseUrl(a2a-subsystem.ts)を executor(request-handler.ts 経由)と reconciler の両方へ渡す。片方だけ変えると経路によって URI が変わる。
  • 認可は書き写さない: 可視性は getLocalTask(id, { viewer })(=buildVisibilityWhere)にそのまま乗せ、委任のスペース範囲は principal.delegation.grantedSpaceIds との AND 交差で重ねる。可視性のルール(持ち主・組織・公開・スペースメンバー)をこのルータへ再実装しないこと。
  • タスクと委任の突き合わせは grant_id ではなく当事者で: resolveTaskDelegationParty が返す acting_user_id × 委任行の client_id で一致を見る。grant_id の一致に戻すと、アクセストークン(寿命 10 分・更新トークン無し)を取り直すための再承認が新しい grant を作り、それ以前に配ったリンクが一斉に 404 になる(PR #1111 レビュー P2-1)。引いた委任行の生死は見ないこと——提示されたトークン側の生死・期限は authenticateA2aRequest が引き直している。
  • deriveArtifactId の区切りは生バイトで書かない: NUL はエスケープ表記(\u0000)で書く。生の 1 バイトをソースへ置くと git がファイル全体を二値と判定し、以後この中核モジュールの差分がどこでも読めなくなる。
  • どのファイルを返すかは記録済みメタ情報から引く: クライアントが送れるのは taskId と artifactId だけ。パスを受け取るクエリ引数を足さないこと。パス解決は必ず ensurePathWithin(workspace/output, name) を通す(字句チェックと realpath チェックの両方がここに入っている)。
  • チャットコネクタ経路には baseUrl を渡さない: chat-connector-service.ts は SDK の TaskStore を通さず a2a_tasks 行が残らないため、URI を載せると開けないリンクになる。

確認方法:

# A2A 関連テスト一括実行
npx vitest run src/bridge/a2a/ src/db/migrate.test.ts src/db/repository.a2a.test.ts src/db/repository.a2a-deleg.test.ts
# router のマウント漏れを確認
grep -n "mountA2aOidc\|createA2aClients\|createConsent" src/bridge/server.ts
# buildVisibilityWhere に委任条件が混入していないか確認
grep -n "a2a_delegations\|a2a_skills" src/db/repository.ts

16. OSS リリース(scripts/oss-sync.sh --push)を実行するとき

  • スクリーンショットの目視確認(必須): docs/screenshots/*.png はバイナリのため、release gate(oss/forbidden.txt の正規表現スキャン)では描画内容を検査できない。スクリーンショットを更新した場合は、push 前に各画像を開いて以下が写り込んでいないか目視で確認する
    • 内部ホスト名・URL(Gitea / 社内サービスのアドレスバー等)
    • 実在のタスクタイトル・会話内容・ファイル名
    • ユーザー名・メールアドレス・組織名
    • 資格情報やトークンの断片(設定画面のマスク漏れ)
  • 撮り直しの手順は README スクリーンショット刷新時のレシピ(隔離 worktree + シードデータ)に従い、実データ環境のキャプチャをそのまま使わない

17. 承認パーク型 META ツール(RequestTool / RequestPackage)を追加・変更する場合

エージェントが「これが欲しい」と申告 → 人が承認 → ジョブ再開、という停車型ツールの配線点。RequestPackage(PR3)は RequestTool を全面ミラーしている。片方だけ直すと不整合になるので、両方を並べて確認すること。

対象ファイル(RequestPackage を例に):

  • src/engine/tools/package-request.ts — ツール本体(TOOL_DEFS + executeTool)。バリデーションは engine/python-packages.ts の parseAndValidateSpec / assertNotShadowing を再利用
  • src/engine/tools/core.ts — ToolContext に park フラグ(pendingPackageApproval)+ recorder(recordPackageRequest)+ 補助状態(installedPackages / declinedPackages)
  • src/engine/tools/index.ts — module loader / Object.assign(allDefs, ...) / dispatch 分岐(META の直書き配列は撤去済み。#1126)
  • 常時利用の登録先は 1 か所になった。モジュールの TOOL_MODULE.metaTools に書く(0-A 参照)。申告を持たない既存モジュールの分だけ tool-categories.ts の META_TOOLS Set に残っている
  • src/engine/agent-loop/pending-states.ts — buildPackageApprovalWaitResult(park → next:'WAITING_HUMAN_PACKAGE_REQUEST')
  • src/engine/agent-loop.ts — import + buildToolApprovalWaitResult の隣で呼ぶ
  • src/engine/piece-runner.ts — WAITING_HUMAN_PACKAGE_REQUEST sentinel 分岐(waiting_human park)+ 2 つの PieceRunOptions interface に recorder/補助状態を追加 + ctx 構築で piece/movement を注入
  • src/worker.ts — recorder を task/job/space にバインド(feature enabled かつ space 解決可のときのみ)+ waiting_human 分岐で wait_reason='package_request' を永続化(忘れると resume がマッチせず永久停止)
  • DB(三重ミラー): src/db/schema.sql / src/db/migrate.ts / src/db/repositories/schema.ts(package_requests テーブル)+ src/db/repositories/tool-requests.ts(record/get/list/decide/revert/listDeclined + 型 + rowMapper)+ src/db/repositories/jobs.ts(resumePackageRequestJob + createJobIfNoPending の承認ポーズ判定)。実装は repositories/* に置き、src/db/repository.ts ファサードに委譲メソッドを足す(repository.ts は肥大分割済みの薄いファサード)
  • src/bridge/local-tasks-package-requests-api.ts — GET 一覧(view 権限)+ POST decide(task write 権限= owner/admin/space editor のみ、エージェント自己承認不可)。承認時のインストールは space-python-packages-service.ts の共有パスを通す
  • src/bridge/local-tasks-api.ts — ルート登録 + LocalTasksApiOptions に config アクセサ / server.ts で配線
  • ui/src/components/chat/PackageRequestApproval.tsx + ChatPane.tsx + ui/src/api/tasks.ts + i18n chat.json(ja/en)
  • docs/tools/requestpackage.md(ReadToolDoc)+ ヘルプ + changelog

なぜ必要か: 停車系は「ジョブ停車=ワーカー解放」で成り立つ(ツール内同期ブロックはデッドロック)。sentinel(WAITING_HUMAN_*)は tool → pending-states → agent-loop → piece-runner → worker(wait_reason 永続化) の全経路が揃って初めて機能する。承認 API は必ず write 権限ゲートを通し、インストールは共有サービス(bwrap 必須 fail-closed / wheels のみ / 固定 index / shadowing 拒否)経由に限定する。


自動検知の可能性

  • ツールモジュール登録漏れ: index.ts と tools-api.ts のモジュール一覧を比較するスクリプトで CI チェック可能
  • code-review-graph: importers_of で各ツールモジュールの参照元を列挙できるが、「tools-api.ts にも登録すべき」というルールの自動適用は困難。変更時の detect_changes + get_impact_radius で影響範囲の見落としを防ぐ用途が現実的