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

627 lines
39 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# メンテナンスチェックリスト
コード変更時に連動して修正が必要な箇所のリスト。
過去に実際に発生した不整合をもとに作成。
---
## 0. ツールのカテゴリ区分workspace tool policy
新しいツールを追加した場合、または既存ツールのカテゴリを変更した場合は、**`src/engine/tools/tool-categories.ts`** の `MODULE_SPECS` に登録し、安全標準かセンシティブかの区分を決める。workspace tool policy はこの定義を参照して、ワークスペースごとにツールの利用可否を解決する。
- `SENSITIVE_CATEGORIES`(現在: `ssh` / `browser`)に含まれるカテゴリのツールはデフォルト無効
- `SENSITIVE_TOOLS`(現在: `Bash`)に列挙されたツールは、カテゴリが `core` であっても個別にセンシティブ扱い
- 新カテゴリは安全側(デフォルト有効)が既定。意図的にセンシティブにする場合のみ `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` — カテゴリ別の概要テーブルに行を追加
**確認方法:**
```bash
# 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モジュールで発生した
**確認方法:**
```bash
# 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.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/ToolsForm.tsx` — ヘルプテキスト等にツール名の言及がないか
- `CLAUDE.md` — ツールモジュール構成テーブル
**なぜ必要か:**
BrowserAction を BrowseWeb に統合した際、5つの piece と UI ヘルプテキストに旧名の参照が残っていた。
**確認方法:**
```bash
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 ファイルでの読み取り互換が壊れる。
**確認方法:**
```bash
# 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`) を再利用すること
**確認方法:**
```bash
# 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 / 設定フォームが再利用するため残置。
---
## 5. SSRF 保護に関わる変更をした場合
**対象ファイル:**
- `src/engine/tools/shared/ssrf.ts` — SSRF チェックロジック本体
- `src/engine/tools/browser.ts` — BrowseWeb`setupRouteInterception` + `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.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 の出力サイズも増やす
### 確認方法
```bash
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」に追記
**確認方法:**
```bash
# 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` を返す (存在を漏らさない)
**確認方法:**
```bash
# 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 起因なら追記
**確認方法:**
```bash
# 監査 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 リスト = クロススペース分離保証)
**確認方法:**
```bash
# 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"
**確認方法:**
```bash
# 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 から明示的に切り替える経路だけ残す
**確認方法:**
```bash
# 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 箇所を揃えること。
**確認方法:**
```bash
# 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.ts``tools-api.ts` のモジュール一覧を比較するスクリプトで CI チェック可能
- **code-review-graph**: `importers_of` で各ツールモジュールの参照元を列挙できるが、「tools-api.ts にも登録すべき」というルールの自動適用は困難。変更時の `detect_changes` + `get_impact_radius` で影響範囲の見落としを防ぐ用途が現実的