Skip to content

feat(ai-engine): ADR-0011 PR-E3a — OAuthFlowOrchestrator#31

Merged
shomatan merged 2 commits into
mainfrom
feat/oauth-api-ui-pr-e3
May 2, 2026
Merged

feat(ai-engine): ADR-0011 PR-E3a — OAuthFlowOrchestrator#31
shomatan merged 2 commits into
mainfrom
feat/oauth-api-ui-pr-e3

Conversation

@shomatan
Copy link
Copy Markdown
Contributor

@shomatan shomatan commented May 2, 2026

Summary

ADR-0011 (Tally 側で OAuth 2.1 フローを管理する) の実装段階 3a。issue #28 への対応として、PR-E1 (schema + token store) と PR-E2 (OAuthClient + LoopbackCallbackServer) を組み合わせる OAuthFlowOrchestrator を実装する。

本 PR でも UI / API は接続されない (PR-E3b で Route Handler + UI 統合)。

設計判断

  • process scope の singleton state (flows Map) で in-progress 状態を保持。Next.js Route Handler が module scope で共有
  • 1 mcpServerId につき同時に 1 フロー (pending 中の二重 start は throw)
  • state verify (CSRF 対策) は orchestrator が持つ
  • completed / failed 状態は次 start まで残す (UI が status を pull)

公開 API

関数 用途
startOAuthFlow(input) PKCE/state 生成 → loopback server → authorization URL → bg で callback 受領 → token 交換 → store 保存
getOAuthFlowStatus(mcpServerId) polling 用の status 取得 (pending / completed / failed / null)
awaitOAuthFlowSettled(mcpServerId) test helper
clearOAuthFlow(mcpServerId) pending 中なら callbackHandle.close() で bg 中断
__resetAllFlowsForTest /* @internal */、test isolation 用

codex セカンドオピニオン対応 (push 直前 review)

重要度 対応
HIGH 2 スロット予約 race / clearOAuthFlow で bg 中断
MEDIUM 4 concurrent race test / scope split 改善 / no-entry warn / @internal
LOW 2 TODO コメント / close 失敗 warn

Test plan

  • pnpm typecheck (4/4 PASS)
  • pnpm test (core 99 + storage 97 + ai-engine 244 + frontend 273 = 713 PASS)
  • pnpm lint (exit 0)

orchestrator test 7 件: start で pending / 成功で completed + token 保存 / state mismatch で failed / 二重 start で reject / 未開始 null / concurrent race / clearOAuthFlow で bg 中断 + 再 start 即可能

Related

Summary by CodeRabbit

リリースノート

  • New Features

    • OAuth認証フローの管理機能を追加しました。ユーザーは認証の開始、ステータス確認、フローのクリアが可能になります。
  • Tests

    • OAuth フローのオーケストレーション動作を検証するテストスイートを追加しました。

ADR-0011 の実装段階 3a。issue #28 への対応として、PR-E1 (schema + token store)
と PR-E2 (OAuthClient + LoopbackCallbackServer) を組み合わせる単一エントリの
オーケストレータを実装する。本 PR でも UI / API は接続されない (PR-E3b で
Route Handler + UI 統合)。

## 設計判断

- process scope の singleton state (flows Map) で in-progress 状態を保持。
  Next.js Route Handler が module scope で共有
- 1 mcpServerId につき同時に 1 フロー (pending 中の二重 start は throw)
- state verify (CSRF 対策) は orchestrator が持つ
- completed / failed 状態は次 start まで残す (UI が status を pull)

## 新規ファイル

- packages/ai-engine/src/oauth/oauth-flow-orchestrator.ts:
  - startOAuthFlow: PKCE / state 生成 → loopback server 起動 →
    authorization URL 構築 → bg で callback 受領 → token 交換 → token store 保存
  - getOAuthFlowStatus: polling 用の status 取得 (pending / completed / failed)
  - awaitOAuthFlowSettled: test helper
  - clearOAuthFlow: pending 中なら callbackHandle.close で bg 中断
  - __resetAllFlowsForTest: @internal、test isolation 用
- packages/ai-engine/src/oauth/oauth-flow-orchestrator.test.ts (7 件)

## codex セカンドオピニオン対応 (push 直前 review)

[HIGH] スロット予約レース: await より前に flows.set で sentinel 予約 (同期で
  check-and-set 完結)、callback server 起動失敗時は sentinel を片付け
[HIGH] clearOAuthFlow が bg を停止しない: flows entry に callbackHandle を
  保持し、clear 時に close() で bg を中断
[MEDIUM] concurrent race の test 不足: Promise.all で並走 start を呼んで
  fulfilled 1 / rejected 1 を確認するテスト追加
[MEDIUM] scope split が空文字混入: result.scope.split(/\\s+/).filter(Boolean) と
  scopesParsed.length > 0 の conditional spread に変更
[MEDIUM] flows.get の no entry 時に silent: console.warn を追加
[MEDIUM] __resetAllFlowsForTest が public: @internal JSDoc 追加
[LOW] completed の authorizationUrl 保持: TODO コメントで PR-E3b の整理予告
[LOW] callbackHandle.close 失敗の swallow: console.warn を追加

## テスト

7 件: start で pending / 成功で completed + token 保存 / state mismatch で
failed / 二重 start で reject / 未開始 null / concurrent race / clearOAuthFlow
で bg 中断 + 再 start 即可能。

pnpm typecheck 4/4 PASS / pnpm test core 99 + storage 97 + ai-engine 244 +
frontend 273 = 713 PASS / pnpm lint exit 0。
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 2, 2026

Warning

Rate limit exceeded

@shomatan has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 28 minutes and 41 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6be8ae6a-a239-4282-97b6-85e64dc25bdb

📥 Commits

Reviewing files that changed from the base of the PR and between 474463d and d258c15.

📒 Files selected for processing (2)
  • packages/ai-engine/src/oauth/oauth-flow-orchestrator.test.ts
  • packages/ai-engine/src/oauth/oauth-flow-orchestrator.ts
📝 Walkthrough

ウォークスルー

OAuth フロー オーケストレーション機能の新規実装と統合により、プロセスローカルの OAuth 2.1 フロー制御機能が導入されました。startOAuthFlow 関数と関連するステータス取得・管理ヘルパー関数が実装され、既存の OAuth クライアント、PKCE/state 生成、ループバック コールバック サーバーが統合されました。ステータス追跡、CSRF 保護、並行呼び出し防止、トークン永続化が含まれます。

変更内容

OAuth フロー オーケストレーション実装

レイヤー / ファイル(s) 概要
ステータス定義とデータ型
packages/ai-engine/src/oauth/oauth-flow-orchestrator.ts (30-74 行)
OAuthFlowStatus 判別型(pendingcompletedfailed)を定義。StartOAuthFlowInputStartOAuthFlowResult インターフェースで入出力型を確立。内部 FlowEntry と process-local flows Map で mcpServerId ごとのステート追跡を実装。
フロー開始と制御ロジック
packages/ai-engine/src/oauth/oauth-flow-orchestrator.ts (80-236 行)
startOAuthFlow で重複呼び出し防止の同期的センチネル予約を実装。ループバック サーバー起動、PKCE/state 生成、認可 URL 構築、background async プロセスによる callback 待機・state 検証・code-to-token 交換・FileSystemOAuthStore への永続化を行い、ステータスを completed または failed に更新。getOAuthFlowStatusawaitOAuthFlowSettledclearOAuthFlow__resetAllFlowsForTest で状態管理と テスト支援。
モジュール再エクスポート
packages/ai-engine/src/oauth/index.ts (19-28 行)
startOAuthFlow、ステータス・クリア・リセット関数、および関連型を ./oauth-flow-orchestrator から公開 API として再エクスポート。
包括的テストスイート
packages/ai-engine/src/oauth/oauth-flow-orchestrator.test.ts (16-239 行)
Vitest スイートで 8 つのテストケースを実装:フロー開始と pending ステータス検証、callback エンドツーエンド動作とトークン永続化、state ミスマッチによる failed ステータス、double-start 時のエラー防止、並行呼び出し競合、clearOAuthFlow による再開可能性、未開始 mcpServerId に対する null 応答。test lifecycle でフロー リセットと cleanup を確保。

シーケンス図

sequenceDiagram
    actor User
    participant Client as Client Code
    participant Orchestrator as OAuth Flow<br/>Orchestrator
    participant LoopbackServer as Loopback<br/>Callback Server
    participant Provider as OAuth Provider
    participant Store as FileSystem<br/>OAuth Store

    User->>Client: startOAuthFlow(config)
    Client->>Orchestrator: startOAuthFlow(input)
    
    Orchestrator->>Orchestrator: Reserve pending sentinel<br/>for mcpServerId
    Orchestrator->>LoopbackServer: Start server
    LoopbackServer-->>Orchestrator: callbackHandle + port
    
    Orchestrator->>Orchestrator: Generate PKCE + state<br/>Build authorization URL
    Orchestrator-->>Client: {authorizationUrl}
    
    Note over Orchestrator: Background async process starts
    
    User->>Provider: Click authorize link
    Provider->>LoopbackServer: Redirect to localhost<br/>with code + state
    LoopbackServer->>Orchestrator: Callback received
    
    Orchestrator->>Orchestrator: Validate state (CSRF check)
    Orchestrator->>Provider: Exchange code for tokens
    Provider-->>Orchestrator: Access token + (optional refresh)
    
    Orchestrator->>Store: Write normalized tokens
    Store-->>Orchestrator: Stored
    
    Orchestrator->>Orchestrator: Update status to completed
    Orchestrator->>LoopbackServer: Close server
    LoopbackServer-->>Orchestrator: Closed
    
    Orchestrator-->>Client: Flow settled
Loading

見積もり コード レビュー工数

🎯 4 (Complex) | ⏱️ ~45 分

関連する可能性のある PR

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: implementing the OAuthFlowOrchestrator according to ADR-0011 stage PR-E3a, which matches the primary objective of combining prior OAuth components into a unified orchestrator.
Docstring Coverage ✅ Passed Docstring coverage is 83.33% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/oauth-api-ui-pr-e3

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.

❤️ Share
Review rate limit: 0/1 reviews remaining, refill in 28 minutes and 41 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

@shomatan shomatan self-assigned this May 2, 2026
@shomatan
Copy link
Copy Markdown
Contributor Author

shomatan commented May 2, 2026

@CodeRabbit review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 2, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
packages/ai-engine/src/oauth/index.ts (1)

19-28: ⚡ Quick win

テスト専用ヘルパーは public barrel から外した方がよいです。

__resetAllFlowsForTestawaitOAuthFlowSettled をここで再 export すると、本番コードが内部同期手段や全消去 API に依存できてしまいます。テストは実装ファイルを直接 import できるので、公開面からは外して test-only な入口に閉じるのが無難です。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ai-engine/src/oauth/index.ts` around lines 19 - 28, Remove the
test-only helpers from the public barrel: stop re-exporting
__resetAllFlowsForTest and awaitOAuthFlowSettled from this file so production
consumers cannot depend on test-only APIs; keep exports for the public APIs
(e.g., startOAuthFlow, getOAuthFlowStatus, OAuthFlowStatus,
StartOAuthFlowInput/Result) and update tests to import __resetAllFlowsForTest
and awaitOAuthFlowSettled directly from './oauth-flow-orchestrator' (or a
dedicated test-only entry) instead of from this barrel.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/ai-engine/src/oauth/oauth-flow-orchestrator.ts`:
- Around line 168-176: Replace the direct inclusion of raw exception text into
the public failureMessage stored in flows (the object set for input.mcpServerId)
with a generic, non-sensitive message or error code; keep the detailed
err.message/stack for server-side logging only (e.g., log via processLogger or a
dedicated logger inside the catch). Update all catch blocks that set
failureMessage (including the block around FileSystemOAuthStore.write and the
token exchange paths referenced) to populate failureMessage with a fixed string
like "OAuth flow failed" or a short error code, and ensure the original err is
logged via a secure logger so internal paths and filesystem errors are not
exposed to the UI/polling response.
- Around line 89-93: clearOAuthFlow can fail to cancel an in-progress
startOAuthFlow because flows.set(...) at start only places a sentinel and the
actual callbackHandle is stored later (callbackHandle, startOAuthFlow, flows,
mcpServerId). Fix by making the flow entry immutable for that run or by
validating ownership before resuming: when creating the initial entry in
startOAuthFlow, store a stable run id or the callbackHandle immediately in the
flows map (instead of a temporary empty object) and when the async continuation
(where callbackHandle is assigned) resumes, check that the current
flows.get(mcpServerId) still matches this run id/callbackHandle before
registering or continuing; alternatively update clearOAuthFlow to mark the entry
as cancelled and ensure startOAuthFlow aborts if it detects that cancelled
marker. Ensure all reads/writes use the same unique token so old start tasks do
not re-register stale callbackHandle.
- Around line 238-240: __resetAllFlowsForTest currently just calls flows.clear()
and leaves pending awaitCallback entries and loopback servers open; change it
into an async helper that iterates over the existing flow entries (the same
structure referenced by flows and the pending awaitCallback logic), call each
entry.close() (or the flow's cleanup/abort method) and await Promise.all of
those close promises before clearing flows, then export/rename it so tests can
await it (update oauth-flow-orchestrator.__resetAllFlowsForTest to return a
Promise and ensure tests call/await it to avoid leaked open handles and pending
loopback servers).

---

Nitpick comments:
In `@packages/ai-engine/src/oauth/index.ts`:
- Around line 19-28: Remove the test-only helpers from the public barrel: stop
re-exporting __resetAllFlowsForTest and awaitOAuthFlowSettled from this file so
production consumers cannot depend on test-only APIs; keep exports for the
public APIs (e.g., startOAuthFlow, getOAuthFlowStatus, OAuthFlowStatus,
StartOAuthFlowInput/Result) and update tests to import __resetAllFlowsForTest
and awaitOAuthFlowSettled directly from './oauth-flow-orchestrator' (or a
dedicated test-only entry) instead of from this barrel.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 59907c9b-1e80-4123-98f8-2f8b8f7af655

📥 Commits

Reviewing files that changed from the base of the PR and between 55d58b7 and 474463d.

📒 Files selected for processing (3)
  • packages/ai-engine/src/oauth/index.ts
  • packages/ai-engine/src/oauth/oauth-flow-orchestrator.test.ts
  • packages/ai-engine/src/oauth/oauth-flow-orchestrator.ts

Comment thread packages/ai-engine/src/oauth/oauth-flow-orchestrator.ts
Comment thread packages/ai-engine/src/oauth/oauth-flow-orchestrator.ts
Comment thread packages/ai-engine/src/oauth/oauth-flow-orchestrator.ts Outdated
CR 1 周目 Major:
- runId pattern 導入: 各 startOAuthFlow 呼び出しに UUID を発行し、bg IIFE が
  flows.set する前に「自分が現在の run か」を確認。clearOAuthFlow → 即 start race
  で旧 bg が新 pending を踏みつぶすバグを防ぐ。await 後の各分岐 (startListen 後 /
  completed / failed) で runId guard。
- failureMessage は固定文字列に正規化 (raw err.message を UI 経由で漏らさない)。
  詳細は console.warn で server log に出す。
- __resetAllFlowsForTest を async 化、pending flow の callbackHandle.close() を
  await してから関数 return。テスト間の loopback fd リークを防ぐ。

codex セカンドオピニオン Major:
- store.write 直前にも runId guard を追加。旧実装は write 後にしか guard が無く、
  preempted な旧 run が storage にトークンを書き込むウィンドウがあった。

テスト追加:
- 「store.write 直前に preempt されたら旧 run はトークンを書き込まない」
- 「clearOAuthFlow → 即 start で旧 bg が新 pending を踏まない (runId guard)」
@shomatan
Copy link
Copy Markdown
Contributor Author

shomatan commented May 2, 2026

@CodeRabbit review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 2, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant