diff --git a/issues/issue-2199-design.md b/issues/issue-2199-design.md
index fbd7a17e6..b707e342d 100644
--- a/issues/issue-2199-design.md
+++ b/issues/issue-2199-design.md
@@ -2,26 +2,27 @@
## 目的
-- **何が壊れているか**: #2170 の cross-refactoring(PR #2192)が `factory/` のリファクタリングの提案 20 件(R01〜R20)を時間(`budget`)で見送り、どの課題にも残っていない。その後に重複が広がり、2026-10-31 の `develop`(ee32e92f)では `requireEnv` が 4 か所、ログインの確認が 6 画面にある
-- **誰が困るか**: 同じ `factory/api/`・`factory/web/` に足していく子課題(#2171・#2172・#2173・#2174・#2189)の実装者と、その cross-refactoring。重複を写して広げ、同じ提案を毎回出し直しては時間で見送る
-- **直すと何が成り立つか**: 20 件のそれぞれが「適用した」か「採らない(理由つき)」に決まり、同じ役割の関数の定義が `factory/` の中で 1 か所になる。HTTP の応答・OpenAPI の契約・DynamoDB の行の形・画面の表示は変わらない
+- **今どうなっているか**: #2170 の cross-refactoring(PR #2192)は、`factory/` のリファクタリングの提案 20 件(R01〜R20)を時間(`budget`)で見送った。20 件はどの課題にも残っていない。その後に重複が広がった。2026-11-05 の `develop`(86436dace)では、`requireEnv` が 5 か所、ログインの確認が 12 画面、失敗を画面の文へ変える `onFailure(err)` が 23 行にある
+- **誰に効くか**: 子課題(#2171・#2172・#2173・#2174・#2189)の実装者と、その cross-refactoring。子課題は同じ `factory/api/`・`factory/web/` に足していく。重複を写して広げ、同じ提案を毎回出し直しては時間で見送る
+- **変えると何が成り立つか**: 20 件のそれぞれが「適用した」か「採らない(理由つき)」に決まり、同じ役割の関数の定義が `factory/` の中で 1 か所になる。HTTP の応答・OpenAPI の契約・DynamoDB の行の形・画面の表示は変わらない
## 適用範囲
- **働く範囲**: このリポジトリの `factory/api/src`・`factory/web/src` と `scripts/tests/test_check_markdown_links.py` だけ。配布物(`plugins/`)には届かない
- **プロジェクトごとに違うもの**: 無し(`factory/` の内部の関数の置き場と引数の形だけを変える)
-- **当たるモード**: `standard`。要求と受け入れ条件があり、対象には既存のテスト(`factory/` の vitest 19 ファイル・pytest 46 件)がある
+- **当たるモード**: `standard`。要求と受け入れ条件があり、対象には既存のテストがある。vitest は `factory/api/src`・`factory/web/src` の 37 ファイル、pytest は `scripts/tests/test_check_markdown_links.py` の 46 件である
## あるべき姿の根拠
| 根拠 | 種類 | 何を示すか |
| --- | --- | --- |
| PR #2192 のリファクタリング計画のコメント「見送った提案」の 20 行(理由はすべて `budget`)と、提案の結果ファイル(`/tmp/ndf-worktrees/devbasex--ai-plugins/rf2192/work/.cross_refactoring/{claude,codex,kiro}-propose-rf2192-result.json`)の `rationale` と `plan` | 実測(記録の読み取り) | 各件の兆候と、提案した手順。この設計の判定表の出所 |
-| 2026-10-31 の `develop`(ee32e92f)で `grep -rn "function requireEnv" factory/api/src` が 4 行(`handlers/api.ts`・`handlers/github-oidc.ts`・`handlers/ingest.ts`・`runs/deps.ts`) | 実測 | PR #2192 の後に写しが 2 つ増えた(要求の前提 1) |
-| 同じ日の `grep -rn 'Navigate to="/login"' factory/web/src/pages` が 6 行、`grep -rn "onFailure(err)" factory/web/src` が 13 行 | 実測 | 画面の重複も PR #2192 の後に足された 3 画面(`RepositoriesPage`・`ModelCredentialPage`・`NewSprintPage`)へ写っている |
-| 同じ日の `factory/api/src/github-oidc/app.ts` に `clientCredentials`・`exchangeGithubCode`・`issueIdToken` があり、POST /token の本体は 24 行 | 実測 | R07 の兆候は PR #2192 の I-001 で消えている(要求の前提 2) |
-| `factory/api/src/index.ts` が `./handlers/api.ts` と `./handlers/ingest.ts` を import し、どちらも読み込みの時点で `export const handler = handle(...)` を組み立てる | 実測(コードの読み取り) | 公開の入口が Lambda の入口を読む向き(R02)。`runs/deps.ts` も `handlers/ingest.ts` を読む |
-| 着手前の `uv run --frozen --project . --all-extras pytest scripts/tests/test_check_markdown_links.py -q` が `46 passed` | 実測(2026-10-31) | 受け入れ条件 5 の「変更の前の件数」 |
+| 2026-11-05 の `develop`(86436dace)で `grep -rn "function requireEnv" factory/api/src` が 5 行(`handlers/api.ts`・`handlers/github-oidc.ts`・`handlers/ingest.ts`・`handlers/issue-requests.ts`・`runs/deps.ts`) | 実測 | PR #2192 の後に写しが 3 つ増えた(要求の前提 1) |
+| 同じ日の `grep -rn 'Navigate to="/login"' factory/web/src/pages` が 12 行、`factory/web/src` の `onFailure(err)` が 13 ファイルで 23 行 | 実測 | 画面の重複が PR #2192 の後に足された画面(スプリント・ループ・委任・バックログなど)へ写っている |
+| 同じ日の `factory/web/src/App.tsx` が、ログインの後の画面をすべて 1 つの枠(`AppShell` の layout route)の下に置く | 実測(コードの読み取り) | ログインの確認を 1 か所に置ける場所が既にある(R14) |
+| 同じ日の `factory/api/src/github-oidc/app.ts` に `clientCredentials`・`exchangeGithubCode`・`issueIdToken` があり、POST /token の本体は 21 行 | 実測 | R07 の兆候は PR #2192 の I-001 で消えている(要求の前提 2) |
+| `factory/api/src/index.ts` が `./handlers/api.ts`(`depsFromEnv`・`createApiApp`)と `./handlers/ingest.ts`(`progressStoreFromEnv`)を読み、どちらも読み込みの時点で `export const handler = ...` を組み立てる。`runs/deps.ts` も `handlers/ingest.ts` を読む | 実測(コードの読み取り) | 公開のエントリポイントが Lambda のエントリポイントを読む向き(R02) |
+| 着手前の `uv run --frozen --project . --all-extras pytest scripts/tests/test_check_markdown_links.py -q` が `46 passed`(2026-11-05) | 実測 | 受け入れ条件 5 の「変更の前の件数」 |
要求と受け入れ条件は #2199 の本文にある(コピーは [issue-2199-requirements.md](issue-2199-requirements.md))。この文書は「どう作るか」だけを扱う。
@@ -34,7 +35,7 @@
| Neural Development Factory(`ndf-factory`) | アカウント・席・履歴・実行の形態・プラン(`factory/api/src/accounts/`)、GitHub の仲立ち(`github-oidc/`)、画面(`factory/web/`) |
| NDF の cross-refactoring(`ndf-cross-refactoring`) | 見送った提案・リファクタリング計画(R01〜R20 の出所) |
-関係は**順応者**である。この変更は `ndf-cross-refactoring` が残した見送った提案を、その語と兆候の分類(duplication など)のまま受け取り、`ndf-factory` の構造を直す。`ndf-cross-refactoring` の側は何も変えない。
+関係は**順応者(Conformist)**である。この変更は `ndf-cross-refactoring` が残した見送った提案を、その語と兆候の分類(duplication など)のまま受け取り、`ndf-factory` の構造を直す。`ndf-cross-refactoring` の側は何も変えない。
### 集約
@@ -43,7 +44,7 @@
| 集約 | 持ち主(書き換えてよいもの) | 根 | エンティティ | 値オブジェクト |
| --- | --- | --- | --- | --- |
| 利用者 | `AccountService`(`accounts/service.ts`)。保存は `AccountStore` の契約を通す | 利用者(`UserRecord`) | — | 利用者の ID(Cognito の `sub`)・IdP の種類 |
-| アカウント | `AccountService`(`accounts/service.ts`)。保存は `AccountStore` の契約を通す | アカウント(`AccountRecord`) | 席(`SeatRecord`) | プラン・実行の形態・席の種類・履歴の行(`HistoryRecord`) |
+| アカウント | `AccountService`(`accounts/service.ts`)。保存は `AccountStore` の契約を通す | アカウント(`AccountRecord`) | 席(`SeatRecord`) | プラン・実行の形態・席の種類・履歴の行(`HistoryRecord`)・作成の冪等の鍵の行 |
利用者とアカウントは別の集約である(確定仕様 `docs/specifications/ndf-factory-accounts-and-plans.md` の「集約」)。2 つを 1 つのトランザクションで書くのは初めてのログインで両方を作るときだけで、それ以外の利用者の読み書きはアカウントの操作に含めない。保存の契約(`AccountStore`)が両方の集約の保存を受け持つのは今のままで、この変更は契約の置き場(`accounts/port.ts`)だけを変える。
@@ -54,11 +55,11 @@
| # | 集約 | 条件 | 破れたときの扱い |
| --- | --- | --- | --- |
| I1 | — | HTTP の応答(状態コード・本文・エラーの文言)と画面の表示が変更の前と同じ | その件を戻し、表の扱いを「採らない(適用すると振る舞いが変わる)」にする |
-| I2 | アカウント | DynamoDB へ送る `TransactItems` の項目の並び・キー・属性・条件式・プレースホルダの名前(`:plan`・`:mode` など)が変更の前と同じ | その件を戻す(C3 に当たる変更を入れない) |
+| I2 | アカウント | DynamoDB へ送る `TransactItems` の項目の並び(業務の項目 → 履歴 → 作成の冪等の鍵の行)・キー・属性・条件式・プレースホルダの名前(`:plan`・`:mode` など)が変更の前と同じ | その件を戻す(C3 に当たる変更を入れない) |
| I3 | アカウント | どの書き込みも履歴の行を同じトランザクションで足す(既存の I20)。条件の失敗は `ConditionFailedError`、それ以外の失敗は今と同じ型で呼ぶ側へ届く | その件を戻す |
| I4 | — | 認証(`auth/verify.ts`・`github-oidc/`)と認可(`auth/resolve.ts`)の判定が変更の前と同じ | その件を戻す(C2 に当たる変更を入れない) |
| I5 | — | 「適用した」件の同じ役割の関数・定数の定義が `factory/` の中で 1 か所にある | 残った写しを寄せるまで実装の Pull Request を出さない |
-| I6 | — | `factory/api/src` の `handlers/` の外のコード(`*.test.ts` を除く。テストの補助の `testing/app.ts` は除かない)が `handlers/` を import しない | import を移した先へ替えるまで出さない |
+| I6 | — | `factory/api/src` の `handlers/` の外のコード(`*.test.ts` を除く。テストの補助の `testing/app.ts` は除かない)が `handlers/` を import しない。`handlers/` の中のファイルどうしも互いを import しない | import を移した先へ替えるまで出さない |
### ドメインイベント
@@ -78,8 +79,8 @@
| 用語 | 意味 | 用語集への反映 |
| --- | --- | --- |
| 見送った提案 | cross-refactoring のリファクタリング計画の「見送った提案」の表の 1 行。ここでは PR #2192 の R01〜R20 | 変えない(`ndf-cross-refactoring` の既存の語のまま) |
-| 同じ役割の写し | R01〜R20 の対象と同じ処理・同じ判断を、別の場所に書き写したもの。要求の S3 が対象に含める | 追加(`ndf-factory`) |
-| 保存の契約 | `AccountStore` の interface と、その引数・失敗の型(`AccountPatch`・`AccountUpdate`・`ConditionFailedError`)。DynamoDB とメモリの 2 つの実装が満たす | 追加(`ndf-factory`) |
+| 同じ役割の写し | R01〜R20 の対象と同じ処理・同じ判断を、別の場所に書き写したもの。要求の S3 が対象に含める | 登録済み(`ndf-factory`) |
+| 保存の契約 | `AccountStore` の interface と、その引数・失敗の型(`AccountPatch`・`AccountUpdate`・`ConditionFailedError`)。DynamoDB とメモリの 2 つの実装が満たす | 登録済み(`ndf-factory`) |
## 機能一覧
@@ -88,47 +89,51 @@
| # | 機能 | 誰が使うか |
| --- | --- | --- |
| F1 | 20 件の扱い(適用した・採らない)と写しの扱いを 1 つの表で読む | 承認ゲート 2 で見る人 |
-| F2 | 環境変数の読み取り・API の入口の組み立て・保存の契約・再試行を、1 か所の定義から使う | 子課題(#2171・#2172・#2173・#2174・#2189)の実装者 |
+| F2 | 環境変数の読み取り・API のエントリポイントの組み立て・保存の契約・再試行・ログインの確認・失敗の表示を、1 か所の定義から使う | 子課題(#2171・#2172・#2173・#2174・#2189)の実装者 |
| F3 | 後の cross-refactoring が R01〜R20 と同じ提案を出さない | cross-refactoring の提案の担当 |
## 20 件の判定表
-**この表が実装の Pull Request の本文の表(受け入れ条件 1)の下書きになる。** 「今の状態」は 2026-10-31 の `develop`(ee32e92f)で確かめた。実装の開始時に `develop` が進んでいれば、E1 として確かめ直す。
+**この表が実装の Pull Request の本文の表(受け入れ条件 1)の下書きになる。** 「今の状態」は 2026-11-05 の `develop`(86436dace)で確かめた。実装の開始時に `develop` が進んでいれば、E1 として確かめ直す。
| # | 対象 | 今の状態 | 扱い | 直し方(1 か所にまとめる場所) | 同じ役割の写し(S3) |
| --- | --- | --- | --- | --- | --- |
-| R01 | `routes/teams.ts#registerTeams` | 席の種類の検証と文言が `teams.ts` に直書き、`accounts.ts` に定数 | 適用する | `routes/body.ts` の `readSeatType(value)`。`SEAT_TYPE_MESSAGE` も移す | `routes/accounts.ts` の 2 か所 |
-| R02 | `index.ts#depsFromEnv` | `index.ts` が `handlers/api.ts`・`handlers/ingest.ts` を読む | 適用する | 決定 1 | `runs/deps.ts` → `handlers/ingest.ts` |
+| R01 | `routes/teams.ts#registerTeams` | 席の種類の検証と文言が `teams.ts` に直書き、`accounts.ts` に定数 `SEAT_TYPE_MESSAGE` | 適用する | `accounts/service.ts` の `validateSeatType(value)`(既存の `validateTeamName` の隣)。文言の定数も移す | `routes/accounts.ts` の `addSeat`・`changeSeat` |
+| R02 | `index.ts#depsFromEnv` | `index.ts` が `handlers/api.ts`・`handlers/ingest.ts` を読み、`createApiApp` も `handlers/api.ts` から再 export する | 適用する | 決定 1 | `runs/deps.ts` → `handlers/ingest.ts`、`handlers/issue-requests.ts` → `handlers/ingest.ts` |
| R03 | `github-oidc/app.ts#githubJson` | `/token` と `/userinfo` が `/user` の取得と ID の検証を 2 回書く | 適用する | `createGithubOidcApp` の中の `githubUser(accessToken)`(取れなければ `null`) | 無し |
-| R04 | `accounts/store.ts#DynamoAccountStore.transact` | 6 つの書き込みが履歴の行の展開を末尾に書く | 適用する | 決定 3(R09 と同じ直し方) | 無し |
-| R05 | `web/pages/TeamPage.tsx#TeamPage` | 席の種類の `