Conversation
…queries (#283 PR 2/7) query 層: - getGithubAppLinks() で配列返却 - getGithubAppLinkByInstallationId() / assertInstallationBelongsToOrg() 追加 - getGithubAppLink() は 1 件目を返す互換 shim として deprecated 維持 mutation 層: - disconnectGithubAppLink(orgId, installationId) を追加 - 最後の active link を失った時のみ method=token に戻す - github_app_link_events への audit log writer (table の初回 writer) - disconnectGithubApp() は全 link を順次切る互換 wrapper として deprecated Octokit 解決: - resolveOctokitForRepository() 追加。repository ごとに installation を選び、移行期間 fallback (active 1 件) を含む - resolveOctokitForInstallation() 追加 - resolveOctokitFromOrg() は deprecated 互換 wrapper batch shape: - getOrganization() / listAllOrganizations() が githubAppLinks: [] を返すよう変更 - crawl / backfill ジョブを per-repo 解決に書き換え audit log: - app/services/github-app-link-events.server.ts 新規追加 - logGithubAppLinkEvent() helper を export tests: - resolveOctokitForRepository の 11 ケース追加 (active 1/0/2+/suspended/explicit installation の組み合わせ) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Caution Review failedPull request was closed or merged during review 📝 WalkthroughウォークスルーGitHub Appの複数インストレーション対応を実現するため、監査ログ機能の追加、インストレーション単位でのOctokit解決、マルチリンク対応のクエリ更新、ジョブの内部実装変更が行われました。これらは単一インストレーション前提の既存コードを段階的に置き換え、組織あたり複数のGitHub Appリンクの管理を可能にします。 変更内容
シーケンス図sequenceDiagram
participant Job as バックフィル/クロールジョブ
participant Query as 統合クエリ層
participant Integration as インテグレーション取得
participant OctokitRes as Octokit解決
participant GithubAPI as GitHub API
Job->>Query: 組織と全リポジトリ取得
Query-->>Job: 組織、統合メソッド、リンク配列
Job->>Job: リポジトリをループ
alt github_app メソッド
Job->>Integration: githubAppLinks 参照
Integration-->>Job: アクティブリンク確認
alt 明示的installationId あり
Job->>OctokitRes: resolveOctokitForRepository<br/>(統合, リンク, リポジトリ)
OctokitRes->>OctokitRes: 一致するリンク検索
OctokitRes->>OctokitRes: サスペンド状態チェック
OctokitRes-->>Job: リポジトリ用Octokit
else 明示的installationId なし
Job->>OctokitRes: resolveOctokitForRepository<br/>(統合, リンク, リポジトリ)
OctokitRes->>OctokitRes: アクティブリンク フィルタ
OctokitRes->>OctokitRes: 件数チェック<br/>(0 or >1 なら例外)
OctokitRes-->>Job: リポジトリ用Octokit
end
else token メソッド
Job->>OctokitRes: resolveOctokitForRepository<br/>(統合, リンク, リポジトリ)
OctokitRes->>OctokitRes: privateToken チェック
OctokitRes-->>Job: トークン用Octokit
end
Job->>GithubAPI: Octokit経由でAPI呼び出し
GithubAPI-->>Job: データ取得
Job->>Job: バックフィル/クロール実行
sequenceDiagram
participant User as ユーザー
participant Mutation as GitHubアプリマテーション
participant Query as クエリ層
participant DB as データベース
participant Audit as 監査ログサービス
User->>Mutation: disconnectGithubAppLink<br/>(organizationId, installationId)
Mutation->>Query: インストレーション検証
Query->>DB: 所属確認クエリ
DB-->>Query: インストレーションデータ
Query-->>Mutation: 検証完了
Mutation->>DB: トランザクション開始
Mutation->>DB: ソフトデリート実行<br/>(deletedAt 設定)
DB-->>Mutation: 更新行数
Mutation->>Query: 他アクティブリンク確認
Query->>DB: 残存リンククエリ
DB-->>Query: リンク数
alt 最後のリンクだった場合
Mutation->>DB: integrations.method<br/>を 'token' に変更
Mutation->>DB: appSuspendedAt をクリア
end
Mutation->>Audit: logGithubAppLinkEvent<br/>('link_deleted', ...)
Audit->>DB: githubAppLinkEvents<br/>に挿入
Mutation->>DB: トランザクションコミット
DB-->>Mutation: 完了
Mutation-->>User: 切断成功
推定コードレビュー工数🎯 4 (Complex) | ⏱️ ~75 分 関連する可能性のあるissue
関連する可能性のあるPR
詩
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
This was referenced Apr 8, 2026
Owner
Author
This stack of pull requests is managed by Graphite. Learn more about stacking. |
Owner
Author
coji
added a commit
that referenced
this pull request
Apr 8, 2026
…rship initialization (#283 PR 3/7) (#290) ## Summary Issue #283 の実装 stack **PR 3/7** — webhook handler / canonical reassignment / membership 初期投入の中核実装。stack で最重要 PR。 設計根拠: [`docs/rdd/issue-283-multiple-github-accounts.md`](./docs/rdd/issue-283-multiple-github-accounts.md) 作業計画: [`docs/rdd/issue-283-work-plan.md`](./docs/rdd/issue-283-work-plan.md) 依存: #288 (PR 1: schema), #296 (PR 2: query/octokit) ## 変更内容 ### setup callback (`app/routes/api.github.setup.ts`) - `(organizationId, installationId)` 単位 upsert(複合主キー対応) - `github_account_type` を保存(personal / Organization の UI 分岐用) - membership 初期投入: `fetchInstallationRepositories` → `initializeMembershipsForInstallation` → `membership_initialized_at = now` - GitHub API 失敗時は link のみ保存し、`membership_initialized_at IS NULL` のまま auto-repair に委譲 - audit log: `link_created` / `membership_initialized` (success / failed) ### installation webhook (`app/services/github-webhook-installation.server.ts`) - `findActiveLinkByInstallationOrAccount` 削除 - すべての lookup を `installation_id` で行う - `installation.deleted`: - 該当 link のみ soft-delete - 最後の active link を失った時のみ `integrations.method = 'token'` に戻す - `link_deleted` 監査ログ - tenant 側で canonical reassignment を呼ぶ - `installation.suspend` / `unsuspend`: `github_app_links.suspended_at` を更新(旧 `integrations.app_suspended_at` から移行) - `installation_repositories.added/removed`: - membership upsert / soft-delete - canonical reassignment 呼び出し(removed 時) - bulk owner/repo 解決(1 query で N+1 解消) - `installation.created`: setup callback が正本のため、既存 link が無ければ no-op ### canonical reassignment (`app/services/github-app-membership.server.ts`) 新規 - `reassignCanonicalAfterLinkLoss(orgId, lostInstallationId, source)`: - tenant DB の `repository_installation_memberships` を正本とする - 候補は active / non-suspended / `membership_initialized_at IS NOT NULL` の link のみ - 候補数で判定: - 1 → 自動 reassign + `canonical_reassigned` - 0 → null + `canonical_cleared` (or `assignment_required` if 未初期化 link 残存) - 2+ → null + `assignment_required` - **未初期化 link ガード**: 未初期化 link が残っている org では、候補 0 でも `canonical_cleared` ではなく `assignment_required` に倒す - LEFT JOIN + bulk update で N+1 を回避 - tenant first / shared second の cross-store 順序 - `upsertRepositoryMembership` / `softDeleteRepositoryMembership` / `initializeMembershipsForInstallation` helpers ### installation repos fetcher (`app/services/github-installation-repos.server.ts`) 新規 - `fetchInstallationRepositories(installationId)`: GitHub API でその installation が見える repository を全ページ取得 ### audit log writer (`app/services/github-app-link-events.server.ts`) - `tryLogGithubAppLinkEvent` best-effort wrapper を追加(呼び出し側の `.catch(() => {})` ノイズを排除) ### auto repair (`app/services/jobs/crawl.server.ts`) - crawl 冒頭に独立 step `repair-membership:<installation_id>` を追加 - `membership_initialized_at IS NULL` の active link を検出 → `installation_repositories` を再 fetch → membership upsert → `membership_initialized_at = now` - per-link で独立 step、durably の中断・再開性を維持 - 失敗時は次回 crawl で再試行(idempotent) ### PR webhook (`app/services/github-webhook-pull.server.ts`) - `owner + repo + installation_id` で repository を引く - 移行期間中は `github_installation_id IS NULL` の repository も許可(PR 7 で strict 化) ### tests - **`app/services/github-app-membership.server.test.ts`** 新規 (8 ケース): - 1 候補 → reassign + `canonical_reassigned` - 0 候補 → null + `canonical_cleared` - 2+ 候補 → null + `assignment_required` - 未初期化 link 残存 + 候補 0 → `assignment_required` (cleared じゃない) - suspended link は除外 - 未初期化 link は除外 - soft-deleted membership は除外 - idempotency: 2 回実行しても結果が同じ - **`app/services/github-webhook.server.test.ts`** 既存 12 ケース更新: - 新 schema (`suspended_at`, `membership_initialized_at`, `github_account_type`, `github_app_link_events`) 対応 - tenant DB mock を chain proxy に変更 ## 満たす受入条件 - **#8**: `installation.suspend/unsuspend` が対象 installation row のみ更新 - **#9**: `installation_repositories` が対象 installation のみ更新 - **#10**: `installation.deleted` が対象 installation row のみ `deleted_at` セット - **#11**: 最後の active 切断時のみ method=token + private_token 有無で復帰先を分岐 - **#12**: canonical reassignment が候補 1 件で自動、0/複数で `null` + manual reselect - **#19**: cross-store 更新は tenant first / shared second + audit log - **#22**: setup callback で `membership_initialized_at` をセット、失敗時は repair に委譲 - **#23**: 未初期化 link 残存時は assignment_required に倒れる ## Stack 位置 \`\`\`text PR 1 (#288): schema └ PR 2 (#296): query/octokit └ [PR 3: webhook/membership] ← this PR └ PR 4 (UI) └ PR 5 (repo UI) └ PR 6 (backfill) └ PR 7 (strict) \`\`\` ## 後続 PR への影響 - **PR 4**: integration settings UI が `getGithubAppLinks()` に切替 + installation selector 追加 + `assertInstallationBelongsToOrg` を loader 境界で呼ぶ - **PR 5**: repository list/detail で `assignment required` バッジ + 個別再選択 mutation(同 helper を再利用) - **PR 7**: PR webhook の `OR github_installation_id IS NULL` 削除 + crawl/backfill の移行期間 fallback 削除 ## テスト - [x] \`pnpm validate\` (lint / format / typecheck / build / test 全 339 tests) - [x] canonical reassignment helper を 8 ケースのユニットテストでカバー (cross-store 整合性 / idempotency / 候補 0/1/2+ / 未初期化ガード / suspended 除外 / soft-deleted 除外) - [x] webhook integration test (12 ケース) 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## リリースノート * **新機能** * GitHub Appインストール時にリポジトリメンバーシップを自動初期化・同期する仕組みを追加 * メンバーシップ情報に基づくリポジトリの正規割り当て(再割り当て)処理を導入 * インストールのリポジトリ取得機能を追加 * **バグ修正** * インストール/サスペンド/削除イベント処理のスコープと整合性を強化 * 監査ログ書き込み失敗を影響させない安全措置を追加 * **テスト** * 再割り当て挙動の包括的なテストスイートを追加 <!-- end of auto-generated comment: release notes by coderabbit.ai -->
coji
added a commit
that referenced
this pull request
Apr 12, 2026
## Summary Issue #283 の実装 stack **PR 4/7** — settings UI を multi-installation 対応に。Vercel ライクに installation の存在をユーザーに意識させない UX。 設計根拠: [\`docs/rdd/issue-283-multiple-github-accounts.md\`](./docs/rdd/issue-283-multiple-github-accounts.md) 作業計画: [\`docs/rdd/issue-283-work-plan.md\`](./docs/rdd/issue-283-work-plan.md) 依存: #288 (PR 1: schema), #296 (PR 2: query/octokit), #290 (PR 3: webhook/membership) ## 変更内容 ### integration page (\`app/routes/$orgSlug/settings/integration/index.tsx\`) - loader が \`githubAppLinks[]\` 配列で各 installation の状態 (suspended / membership_initialized) を返す - action を **discriminated union + parseWithZod + match.exhaustive** に refactor (CLAUDE.md 規約準拠、200 行 if-chain → ts-pattern) - 新 INTENTS: \`disconnectGithubAppLink\`, \`confirmDisconnectGithubAppLink\` - per-installation disconnect (\`assertInstallationBelongsToOrg\` 検証) ### GitHub App セクション UI - \`InstallationCard\` で active installation を 1 枚ずつカード表示 - カードごとに fetcher + ConfirmDialog (installationId を保持できる) - \`Add another GitHub account\` ボタン (1 件以上接続済みのとき) - personal account / organization で GitHub 設定 URL を分岐 (\`buildInstallationSettingsUrl\`) ### repositories.add page (Vercel 風 merge UI) - **installation selector を出さない**: 全 active installation の repo を並列 fetch → owner/repo で dedupe → 1 リスト表示 - 各 repo は元 installation を tag (\`TaggedInstallationRepo\`) - ユーザーが Add 押下時、その repo の \`installationId\` が hidden input で submit される - action 側で \`assertInstallationBelongsToOrg\` を server-side 検証 - \`addRepository\` mutation に \`githubInstallationId\` 引数追加 + \`upsertRepositoryMembership\` 呼び出し - \`fetchAllInstallationRepos\` cache key を per-installation に変更 ### github-users page - **installation selector を出さない**: \`search.users\` は global API なので、複数 active link があれば最初を裏で使う - \`searchGithubUsers\` を installationId 引数なしに簡素化 - toolbar / loader / table から installation 関連 UI 削除 ### 共有 helpers - \`app/libs/github-account.ts\`: - \`formatGithubAccountLabel\` (personal は \`@login\`, org はそのまま) - \`isPersonalAccount\` - \`buildInstallationSettingsUrl\` (User → \`/settings/installations/<id>\`, Organization → \`/organizations/<login>/settings/installations\`) - \`app/services/github-integration-queries.server.ts\`: - \`getActiveInstallationOptions\` 共通 helper ## 満たす受入条件 - **#1**: 同一 org で複数 installation を接続できる (UI で確認可能) - **#4**: repository 追加時に installationId が記録される (UI 上は merge されているが裏で installationId が紐付く) - **#5**: github-users で複数 installation 環境でも検索可能 (active link を裏で選択) - **#13/#14/#15**: integration UI の installation 単位表示 / Add another / personal/org URL 分岐 - **#17**: server-side で client 由来 installationId を検証 - **#21**: 1 件目のみは selector なしでデフォルト動作 ## Stack 位置 \`\`\`text PR 1 (#288): schema └ PR 2 (#296): query/octokit └ PR 3 (#290): webhook/membership └ [PR 4: UI] ← this PR └ PR 5 (repo UI) └ PR 6 (backfill) └ PR 7 (strict) \`\`\` ## UX 哲学 最初は installation selector で実装したが、Vercel など先行サービスが「installation を意識させない」UX を取っているのを参考に書き直し。ユーザーは repository を選ぶだけでよく、installation は内部の attribution として透過的に処理される。 ## テスト - [x] \`pnpm validate\` (lint / format / typecheck / build / test 全 339 tests) - [x] \`get-installation-repos.test.ts\` を \`TaggedInstallationRepo\` 対応に更新 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **新機能** * 各GitHub Appインストールを個別カードで表示し、アカウント表示ラベルとインストール設定への直接リンクを追加。 * **改善** * インストール単位の接続解除(確認付き)と専用意図を導入。リポジトリ追加は全アクティブインストールを並列取得・重複排除し、各リポジトリにインストールIDを保持。ユーザー検索はアクティブインストールを順次試行するフォールバックを実装。 * **ドキュメント** * 統合UXと回復フロー案を更新。 <!-- end of auto-generated comment: release notes by coderabbit.ai -->
coji
added a commit
that referenced
this pull request
Apr 12, 2026
## Summary Issue #283 の実装 stack **PR 5/7** — broken state(`github_installation_id IS NULL`)の repository を救済する UI / mutation / batch CLI を追加。 設計根拠: [\`docs/rdd/issue-283-multiple-github-accounts.md\`](./docs/rdd/issue-283-multiple-github-accounts.md) 作業計画: [\`docs/rdd/issue-283-work-plan.md\`](./docs/rdd/issue-283-work-plan.md) 依存: #288 (PR 1: schema), #296 (PR 2: query/octokit), #290 (PR 3: webhook/membership), #291 (PR 4: UI) ## UX 哲学 PR 4 の Vercel 哲学を継承し、**通常時は repository 一覧/詳細に installation 名を出さない**。canonical installation を失った broken state の repository だけを救済導線として可視化する。 ## 変更内容 ### service helper (\`app/services/github-app-membership.server.ts\`) - \`fetchEligibleInstallationIds()\` 内部 helper 抽出 — \`reassignCanonicalAfterLinkLoss\` と \`reassignBrokenRepository\` で eligible link 取得ロジックを共有 - \`reassignBrokenRepository(orgId, repoId, source)\` 追加 - canonical installation を持たない repo に対して membership table から候補を引いて再割当を試みる - 結果を discriminated union で返す: \`reassigned | no_candidates | ambiguous | not_broken\` - 1 候補 → \`canonical_reassigned\` audit log を書く - 0 候補 / 2+ 候補 → 戻り値で表現、audit log は書かない(installationId が無いため) - \`isRepositoryBroken()\` を \`app/libs/github-account.ts\` に追加(client-safe) ### route mutation (\`app/routes/$orgSlug/settings/repositories._index/\`) - loader が \`integrationMethod\` を \`'token' | 'github_app' | null\` で返す - action に \`reassignBroken\` intent 追加(\`source: 'manual_reassign'\`) - discriminated union を \`match.exhaustive\` でハンドリング ### UI (\`+components/repo-columns.tsx\`) - \`NeedsReconnectionBadge\` コンポーネント - \`isRepositoryBroken(repo, integrationMethod)\` で判定 - destructive variant Badge + \`AlertTriangleIcon\` + tooltip - 1-click "Reassign" ボタン (per-row \`useFetcher\`) - 結果別 toast: - 成功 → "Repository reassigned to an active installation." - 候補 0 → "No active installation can see this repository. Reinstall the GitHub App and try again." - 候補 2+ → "Disconnect the unwanted installations to resolve." ### batch CLI (\`batch/commands/reassign-broken-repositories.ts\`) - \`pnpm batch reassign-broken-repositories <orgId> [--repository <id>]\` - broken repository を全て列挙 → 順次 \`reassignBrokenRepository\` (\`source: 'cli_repair'\`) - 結果集計と各 repo の状況を consola で出力 - \`match.exhaustive\` で網羅性確保 ### tests - \`github-app-membership.server.test.ts\` に 6 ケース追加: - 1 候補 → reassigned + canonical_reassigned event - 0 候補 → no_candidates (audit log なし) - 2+ 候補 → ambiguous (audit log なし) - not_broken(既に canonical あり) - suspended link は候補から除外 - 未初期化 link は候補から除外 ## 満たす受入条件 - **#7**: 失われた canonical installation の repository 救済経路 - **#18**: assignment required 状態の表示と再割当 UI ## Stack 位置 \`\`\`text PR 1 (#288): schema └ PR 2 (#296): query/octokit └ PR 3 (#290): webhook/membership └ PR 4 (#291): UI └ [PR 5: repo UI] ← this PR └ PR 6 (backfill) └ PR 7 (strict) \`\`\` ## テスト - [x] \`pnpm validate\` (lint / format / typecheck / build / test 全 345 tests) - [x] \`reassignBrokenRepository\` の 6 ケースをユニットテストでカバー 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **新機能** * GitHub App連携で「接続切れ(broken)」判定を共通化するチェックを追加 * リポジトリ一覧に警告バッジを表示し、バッジから再割り当て(再接続)操作を実行可能に(操作中の状態表示・ツールチップ付き) * 壊れたリポジトリを一括検出・再割り当てするCLIコマンドを追加 * **テスト** * 再割り当ての各結果(再割当て/候補なし/あいまい/初期化待ち/未該当/未発見)を検証する自動テストを追加 <!-- end of auto-generated comment: release notes by coderabbit.ai -->
coji
added a commit
that referenced
this pull request
Apr 12, 2026
#283 PR 6/7) (#293) ## Summary Issue #283 の実装 stack **PR 6/7** — PR 7 (strict lookup 切替) のデプロイ準備を行う one-shot batch CLI。 設計根拠: [\`docs/rdd/issue-283-multiple-github-accounts.md\`](./docs/rdd/issue-283-multiple-github-accounts.md) 作業計画: [\`docs/rdd/issue-283-work-plan.md\`](./docs/rdd/issue-283-work-plan.md) 依存: #288 (PR 1: schema), #296 (PR 2), #290 (PR 3), #291 (PR 4), #292 (PR 5) ## 変更内容 ### batch CLI (\`batch/commands/backfill-installation-membership.ts\`) \`pnpm batch backfill-installation-membership [orgId] [--dry-run]\` 組織ごとに以下の判定: | 組織の状態 | 動作 | 結果 | |---|---|---| | \`integrations.method = 'token'\` | skip | \`skipped_token_method\` | | github_app + 0 active link | skip + warn | \`skipped_no_active_link\` (要 reinstall) | | github_app + 1 active link | bulk backfill | \`backfilled_single_link\` | | github_app + 2+ active links | skip + warn | \`skipped_multi_link_unmapped\` (要 \`reassign-broken-repositories\`) | ### 動作詳細 - **bulk backfill**: \`UPDATE repositories SET github_installation_id = ? WHERE github_installation_id IS NULL\` で一括設定 - **membership seed**: \`fetchInstallationRepositories\` (GitHub API) で installation の見える repo を全取得 → \`initializeMembershipsForInstallation\` で bulk 投入 - **API 失敗時 fallback**: orphan repository に対してのみ \`upsertRepositoryMembership\` を呼ぶ(最低限の membership を確保、本格的な seed は次回 crawl の repair に委譲) - **冪等性**: \`WHERE github_installation_id IS NULL\` で再実行 no-op - **\`--dry-run\`**: 書き込みも GitHub API 呼び出しも行わず判定だけ実行 ### Runbook (本番デプロイ手順) 1. **PR 1-5 がマージ・本番デプロイ済みであることを確認** 2. **本番 DB のスナップショット**: \`pnpm ops pull-db -- --app upflow\` 3. **dry-run で計画確認**: \`\`\`bash pnpm batch backfill-installation-membership -- --dry-run \`\`\` - skip 件数 / backfill 対象件数 / 要注意組織(multi-link / no-link)を確認 4. **実行**: \`\`\`bash pnpm batch backfill-installation-membership \`\`\` 5. **検証**: \`integrations.method = 'github_app'\` の組織に対して以下が 0 件であること: \`\`\`sql SELECT count(*) FROM repositories WHERE github_installation_id IS NULL; \`\`\` 6. **multi-link 組織の対応**: \`pnpm batch reassign-broken-repositories <orgId>\` で各 multi-link 組織を救済 7. **検証 OK 後** PR 7 をマージ ### Stack 位置 \`\`\`text PR 1 (#288): schema └ PR 2 (#296): query/octokit └ PR 3 (#290): webhook/membership └ PR 4 (#291): UI └ PR 5 (#292): repo UI └ [PR 6: backfill] ← this PR └ PR 7 (strict) \`\`\` ## 満たす受入条件 - **#20**: schema → backfill 完了 → strict lookup 切替 (前半) ## テスト - [x] \`pnpm validate\` (lint / format / typecheck / build / test 全 351 tests) - [x] CLI コマンドの 6 ケースを vitest でカバー (single / token / multi / dry-run / idempotent / API failure fallback) 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## リリースノート * **新機能** * `backfill-installation-membership` コマンドを CLI に追加しました。このコマンドは GitHub App インストール連携時に、リポジトリのインストール ID とメンバーシップ情報を一括更新する機能を提供します。`--dryRun` フラグでプレビュー実行も可能です。 <!-- end of auto-generated comment: release notes by coderabbit.ai -->
coji
added a commit
that referenced
this pull request
Apr 12, 2026
…acks (#283 PR 7/7) (#294) ## Summary Issue #283 stack の **最終 PR (7/7)** — 移行期間 fallback を全削除し、strict installation lookup に切替。これでシリーズ完結。 設計根拠: [\`docs/rdd/issue-283-multiple-github-accounts.md\`](./docs/rdd/issue-283-multiple-github-accounts.md) 作業計画: [\`docs/rdd/issue-283-work-plan.md\`](./docs/rdd/issue-283-work-plan.md) 依存: #288, #296, #290, #291, #292, #293 ##⚠️ デプロイ順序 このマージは **PR 6 (#293) の \`backfill-installation-membership\` を本番実行 + 検証完了後** にのみ実行してください。順序を守らないと既存リポジトリが strict lookup で弾かれてエラーになります。 詳細手順は #293 の Runbook 参照。 ## 変更内容 ### schema (\`db/shared.sql\` + migration) - \`integrations.app_suspended_at\` カラム削除(PR 1 で \`github_app_links.suspended_at\` に移行済み) - Atlas が table-rebuild migration を生成 (\`20260408001949.sql\`) - \`app/services/type.ts\` 再生成 ### Octokit 解決 (\`app/services/github-octokit.server.ts\`) - \`resolveOctokitForRepository\` 内の transitional fallback を全削除: - \`github_app + githubInstallationId IS NULL\` → エラー \"Repository has no canonical installation assigned. Run reassign-broken-repositories or reinstall.\" - \"active link 1 件で代用\" のロジック削除 - 削除した legacy export: - \`assertOrgGithubAuthResolvable\` - \`resolveOctokitFromOrg\` - \`OrgGithubAuthInput\` ### query 層 (\`app/services/github-integration-queries.server.ts\`) - \`getGithubAppLink\` (deprecated) を削除(caller ゼロ) ### PR webhook (\`app/services/github-webhook-pull.server.ts\`) - 移行期間の \`OR github_installation_id IS NULL\` を削除し strict 化: \`\`\`sql WHERE owner = ? AND repo = ? AND github_installation_id = ? \`\`\` - broken state webhook を ops が検知できるよう、各 silent return に \`debug()\` ログを追加(\`createDebug('app:github-webhook:pull')\`) ### route (\`app/routes/$orgSlug/settings/repositories/$repository/$pull/\`) - \`index.tsx\`: \`resolveOctokitFromOrg\` から \`resolveOctokitForRepository\` に migration - \`queries.server.ts\`: \`getRepository\` の select に \`githubInstallationId\` 追加 ### cleanup - \`appSuspendedAt\` への参照を全削除 (setup callback / webhook handler / mutation / batch query / test fixture) - \`github-octokit.server.test.ts\` から legacy 関数のテストと transitional fallback ケースを削除 ## 満たす受入条件 - **#16**: PR webhook strict lookup - **#20** (完全達成): schema → backfill → strict lookup 切替 ## デプロイ Rollback 戦略 - migration は destructive (column drop)。本番デプロイ前に staging で apply → rollback → re-apply のリハーサル必須 - デプロイ後 24 時間は \`github_app_link_events\` の異常パターン (連続 \`canonical_reassigned\`, \`webhook_dropped\` debug ログの急増) を監視 - 異常検知時: 1. PR 7 を revert 2. \`app_suspended_at\` を復活させる down migration を当てる (Atlas で生成済み) 3. 必要なら \`reassign-broken-repositories\` で個別 repo 救済 ## Stack 位置 (完成形) \`\`\`text PR 1 (#288): schema └ PR 2 (#296): query/octokit └ PR 3 (#290): webhook/membership └ PR 4 (#291): UI └ PR 5 (#292): repo UI └ PR 6 (#293): backfill └ [PR 7: strict] ← this PR \`\`\` ## テスト - [x] \`pnpm validate\` (lint / format / typecheck / build / test 全 342 tests) - [x] \`resolveOctokitForRepository\` の strict path テスト (canonical なし → throw) - [x] migration \`pnpm db:setup\` で再現可能 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **バグ修正** * リポジトリのGitHub Appインストール割り当てがない場合に厳格にエラーを返すようにし、エラーメッセージをより具体的に表示するよう改善しました。 * **リファクタリング** * 組織スコープの旧ヘルパーを削除し、Webhook処理にデバッグログを追加して追跡性を向上させました。 * 統合の解決フローを整理しました。 * **雑務** * 統合テーブルから未使用フィールドを削除するスキーマ変更と関連マイグレーション、CLI表示文言、テスト修正を行いました。 <!-- end of auto-generated comment: release notes by coderabbit.ai -->
2 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Summary
Issue #283 の実装 stack PR 2/7 — query / mutation / Octokit 解決層を複数 installation 対応にする。アプリケーションの動作はまだ変えない(fallback で互換維持)。
設計根拠:
docs/rdd/issue-283-multiple-github-accounts.md作業計画:
docs/rdd/issue-283-work-plan.md依存: #288 (PR 1: schema)
変更内容
query 層 (
app/services/github-integration-queries.server.ts)getGithubAppLinks(orgId)配列返却(ORDER BY createdAt ASC で決定的順序)getGithubAppLinkByInstallationId(installationId)追加assertInstallationBelongsToOrg(orgId, installationId)追加 — クライアント由来のinstallationIdをサーバ側で検証する境界 guardgetGithubAppLink()は最古の active link を返す互換 shim(@deprecated)mutation 層 (
app/services/github-app-mutations.server.ts)disconnectGithubAppLink(orgId, installationId)追加 — 単一 installation の soft-delete + 最後の active を失った時のみmethod='token'に戻す + audit log 書き込みdisconnectGithubApp()は legacy UI 互換 wrapper として 1 transaction で全 link 一括 soft-delete に書き換え(@deprecated)audit log writer (
app/services/github-app-link-events.server.ts) 新規追加logGithubAppLinkEvent()helperKysely<DB> | Transaction<DB>を受け取り、呼び出し側のトランザクションに乗せられるgithub_app_link_eventstable の 初回 writer(disconnect 経由)Octokit 解決 (
app/services/github-octokit.server.ts)resolveOctokitForInstallation(installationId)追加resolveOctokitForRepository({ integration, githubAppLinks, repository })追加 — repository ごとの解決repository.githubInstallationIdがセットされている場合は厳密にそれを使う(suspended は弾く)github_appモードで未割当の repository に対する移行期間 fallback:tokenモード:privateTokenがあれば PAT、無ければ未接続エラーIntegrationForOctokit.methodを'token' | 'github_app' | (string & {})のユニオンに型を絞るresolveOctokitFromOrg()は legacy 互換 wrapper(@deprecated、PR 4 で削除予定)batch shape 更新 (
batch/db/queries.ts)getGithubAppLinkByOrgId→getGithubAppLinksByOrgId(配列返却)getAllGithubAppLinksをMap.groupByで書き換えgetOrganization()/listAllOrganizations()がgithubAppLinks: []を返すよう変更crawl / backfill ジョブ (
app/services/jobs/{crawl,backfill}.server.ts)load-organizationstep 内でgithub_app + active 0/token + privateToken nullの早期エラー検出resolveOctokitForRepository()を呼び、解決失敗時は warn ログ + skip(crawl 全体は止めない)tsconfig
libをES2024に bump(Map.groupByを使うため)tests
app/services/github-octokit.server.test.tsにresolveOctokitForRepositoryの 11 ケース追加満たす受入条件
crawl.server.ts/backfill.server.tsが repository ごとに対応 installation の Octokit を使うStack 位置
後続 PR への影響
getGithubAppLinkByInstallationId/logGithubAppLinkEvent/ canonical reassignment helper(PR 3 で実装)を使うgetGithubAppLink()をgetGithubAppLinks()に移行 +assertInstallationBelongsToOrgを loader 境界で呼ぶactiveLinks.length === 1分岐) を削除し、github_installation_id IS NULLを strict エラーにするテスト
🤖 Generated with Claude Code
Summary by CodeRabbit
リリースノート
新機能
バグ修正
改善