maestro/docs/maintenance-checklist.md
oss-sync b1292e34b2
Some checks failed
CI / build-and-test (push) Has been cancelled
sync: update from private repo (edc775f2)
2026-07-06 01:04:12 +00:00

39 KiB
Raw Blame History

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

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


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

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

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

対象ファイル:

  • src/engine/tools/tool-categories.tsMODULE_SPECS / SENSITIVE_CATEGORIES / SENSITIVE_TOOLS
  • src/engine/tools/index.tstryLoadModule でのカテゴリ指定
  • 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.tstryLoadModule で動的ロード追加
  • 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 のモジュールテーブルに新ツール名が含まれているか確認する。

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


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

対象ファイル:

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

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

確認方法:

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

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

対象ファイル:

  • src/config.tsToolsConfig / 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.tsxrenderActiveForm()switch(section) に新 case 'new-id': return <NewForm {...formProps} />; を追加
  • ui/src/components/settings/SettingsSidebar.tsxCONFIG_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.tsPAGES 配列 (PageId の唯一の定義源)
  • ui/src/components/layout/TopBar.tsxNAV_ITEMS (タブ表示順 + adminOnly / requiresAuth)
  • ui/src/components/layout/NavDrawer.tsxNAV_ICONSRecord<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 / 設定フォームが再利用するため残置。


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

対象ファイル:

  • src/engine/tools/shared/ssrf.ts — SSRF チェックロジック本体
  • src/engine/tools/browser.ts — BrowseWebsetupRouteInterception + ssrfCheck
  • src/engine/tools/web.ts — WebFetch / DownloadFile
  • ui/src/components/settings/ToolsForm.tsx — SSRF Allowed Hosts のヘルプテキスト

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


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

対象ファイル:

  • src/engine/tools/raw-save.tsRAW_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.tsTOOL_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 にまとめてエイリアス経由で引けるようにする。

確認方法:

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

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

対象ファイル:

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

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


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

対象ファイル:

  • src/engine/reflection/types.tsReflectionMemoryType の union を拡張
  • src/engine/reflection/reflection-schema.ts — LLM tool schema の type enum
  • src/engine/reflection/reflection-prompt.ts — システムプロンプトの type 説明
  • src/engine/reflection/semantic-validator.tsALLOWED_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.tsReflectionRejectionCode の 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.tsauth_kind の受け入れ・バリデーション
  • src/mcp/token-manager.tshasToken / 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.tstryLoadModule('./ssh.js') 経由のロード + dispatch
  • src/engine/tools/core.tsToolContext.pieceName (piece grants check に必要)
  • src/engine/piece-runner.tsToolContext 構築時に pieceName: piece.name を thread
  • src/engine/tools/docs.tsTOOL_DOC_ALIASES で 3 ツール名を 'ssh-tools' にマップ
  • docs/tools/ssh-tools.md — LLM 向け詳細ドキュメント
  • src/bridge/server.tssetSshSubsystem({...}) で DI

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

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

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

  • src/ssh/session.tsSshSessionErrorCode union を拡張
  • src/engine/tools/ssh.tsformatSessionError 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.tsssh.enabled ゲートで両 router をマウント
  • src/ssh/session.tssshTest() (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.tsSshAuditOutcome と 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.sqlssh_connections, ssh_connection_grants, ssh_audit_log, ssh_abuse_counters, system_deks
  • src/db/migrate.tsPRAGMA 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.sqlCREATE TABLE に列追加 (初期スキーマ)
  • migrate.ts に冪等 ALTER TABLE 追加 (既存 DB 用)
  • 既存テストが影響を受けないか npm test で検証
  • 列をエンドポイントから書く場合、POST/PATCH ハンドラのバリデーションに追加し、admin-only フラグなら user 経路で reject

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

  • src/ssh/audit-repo.tsSshAuditOutcome は触らない (固定 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.tsxSshForm の登録 (新規 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.tsparsedPolicy.enabledSensitivessh があるとき listBySpace(spaceId) で接続 ID を解決(workspaceSshConnections
  • src/engine/piece-runner.tsworkspaceSshConnections ?? []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.tsSshRuntimeConfig interface + SSH_DEFAULTS + mergeSshConfig
  • src/config.tstransformKeys でスネーク→キャメル変換、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.tsTOOL_DEFS に追加
    • src/engine/tools/index.ts の dispatch + aggregation に追加
    • src/engine/tools/docs.tsTOOL_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.tssrc/engine/notes-inject.tsNotesForm.tsxnote_index / note_subscriptions / pending_reindex テーブルはすべて撤去済み。 既存 DB に残る孤立テーブルは無害なので DROP していない。再導入の予定はない。


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

対象ファイル:

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

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


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

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

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

  • src/db/schema.sqlCREATE TABLEoidc_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.yamla2a セクションを変更するとき

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

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

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

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

  • 公開スキルは spaces.a2a_skillsJSON 配列カラム)に保存される。スキーマ変更時は schema.sqlmigrate.ts両方dual-path 必須)を更新すること
  • 委任は a2a_delegations テーブルで管理し、grant_id で OAuth grantoidc_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.sqlmigrate.ts両方dual-path 必須)を更新すること
  • executor は createLocalTaskcreateJob の順に呼ぶ。createLocalTask 単体はジョブをエンキューしない(createJob で初めて Worker がポーリングで拾える状態になる)
  • ジョブ完了の検知はポーリング(a2a_tasksstatus カラムを定期参照)で行い、進捗は SSE で外部エージェントに返す
  • 実行完了後、output/ 以下のファイルは A2A Artifact として返却する
  • 委任スコープは executor でも computeEffectiveScope を通じて再強制する。トークンの委任スコープがスペースの公開スキル設定を上回ることは許容しないfail-closed
  • buildVisibilityWhere の OR 句に委任条件を追加しないことAND 交差で適用する)

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

  • 非ブロッキング契約: message/sendparams.configuration.blocking: false は SDK ネイティブ機能。executor が初期 Tasksubmitted)を publish した時点で SDK が即座にレスポンスを返す(完了を待たない)。外部エージェントは後から tasks/getparams.idで最新状態を取得する。executor 自体を非ブロッキング化する改造は不要SDK 側で完結)。
  • reconciler は単一起動: A2aTaskReconcilerserver.tsa2a.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_STATUSEStask-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.metadataa2aDelegationId / a2aGrantId / a2aActingUserIda2a_tasksdelegation_id / grant_id / acting_user_id 列へ写す(task-store.ts の save 規約と同一)。列を増やすときは executor の初期 Task metadata・SqliteA2aTaskStore.savereconciler.persistTask の 3 箇所を揃えること。

確認方法:

# 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

自動検知の可能性

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