依 autoflow-agent workspace v2 設計把 PRD / 設計 / 架構 / 交付類 共享文件從個人層 .autoflow/(ignored)搬到 docs/autoflow/(進 git), 讓團隊可共享產品與架構文件,個人層只留 progress / review / testing 等 per-branch 筆記。 - 02-prd/ 21 個檔(PRD、features、market-analysis 等) - 03-design/ 18 個檔(design-spec、wireframes、flows 等) - 04-architecture/ 31 個檔(TDD、design-doc、ADR×14、API 規格等) - 07-delivery/ 3 個檔(project-summary、phase-0.6-handover、stage-deployment-setup) 合計 73 檔。原檔已從 .autoflow/ 移除(migration 工具執行 git mv, 但因 .autoflow/ 在 .gitignore 中、git 將此操作視為新增、無 rename history)。
8.3 KiB
8.3 KiB
ADR-011:推翻 ADR-005 — 雛形階段升級接 Innovedus Member Center
狀態
Accepted — 2026-04-26
推翻
ADR-005 — Auth 部分(DB 部分仍有效)
背景 (Context)
ADR-005(2026-04-21)在 Phase 0 雛形階段決定不接 auth:
- 用
StaticAuthProvider任何帳密都通過、永遠回demo-user StaticAuthServicemiddleware 永遠注入 demo-user UserContext- 雛形 PRD / TDD 中所有 user-bound 資源(Device / Model / PairingToken)都綁到 demo-user
當時的決策邏輯:
- 雛形階段目標是驗證「雲端端對端連通」技術可行性
- 把時間花在 Auth、user 系統、DB schema 上會延誤核心驗證
- 介面(
AuthProvider/AuthService)已切乾淨,未來換實作零業務邏輯改動
到了 Phase 0.6(2026-04-26),情況變了:
- 雛形已交付(Phase 0 + Phase 0.5 全綠),核心架構通過驗證
- 需要進入 Phase 1(多用戶、上線給 FAE 試用)— 沒真實 user 寸步難行
- 同期間 Innovedus 集團另一條線 Member Center 已可用(C# .NET Core + OpenIddict + PostgreSQL,70% 完成度,OAuth Authorization Code + PKCE + JWKS 都能跑)
- 跨產品 SSO 是 Innovedus 集團方向(visionA / kneron_model_converter / 未來其他產品線)
繼續用 StaticAuth 的代價:
- 無法做多用戶測試(內部 FAE 試用無法區分使用者)
- 無法把雛形交給內部使用者(資料會混在一起)
- 之後要接 OIDC 還是要做 → 不如現在做完,後面少一輪改動
決策 (Decision)
推翻 ADR-005 的 Auth 部分:Phase 0.6 起 visionA-backend 的唯一認證路徑改為 OIDC,接 Innovedus Member Center。
具體做法
-
拔除 StaticAuthProvider / StaticAuthService
- 刪除
internal/auth/static.go+static_test.go internal/api/middleware.go的AuthMiddleware拿掉雙模式分支,只剩 OIDCinternal/api/api.go的Deps拿掉AuthService/AuthProvider欄位;validate()強制OIDCProvider+SessionManager必填(缺則啟動 panic)cmd/api-server/main.go拿掉cfg.OIDC.Enabled旗標分支,OIDC 變成必須路徑
- 刪除
-
OIDC 實作(OB1-OB4 已完成;本 ADR 只記錄決策)
- 採 OAuth 2.0 Authorization Code + PKCE + BFF Pattern(詳見 ADR-010)
- visionA-backend 是 OIDC confidential client(持有 client_secret)
- frontend 完全不接觸 token,只看到
visiona_sessioncookie - In-memory session store(重啟即消失,雛形階段可接受)
-
保留 AuthProvider / AuthService interface
- 雖然 Static 實作已刪,interface 本身仍保留在
internal/auth/auth.go - 給未來新增備援 provider(Phase 1 backup local auth、service-to-service token)使用
- 介面代表 contract — 提早設計、提早被驗證的介面比 ad-hoc 重新發明便宜
- 雖然 Static 實作已刪,interface 本身仍保留在
-
Pairing Token 流程不動(ADR-005 的另一個面向)
- PairingStore / SessionTokenStore 仍是 in-memory + interface 抽象
- 唯一改變:
UserContext.UserID從固定的"demo-user"變成 OIDCsub(UUID),自然穿透到PairingStore.Create的 user_id 參數 - Agent 端不知道 user 是誰、不接 OIDC
-
dev 環境策略
- 不寫 mock OIDC server(測試例外 — 見
internal/oidctest) - 開發者要登入就得起 Member Center(docker-compose 一鍵化)
- 為了讓 demo-user 流程繼續可用,Member Center 端會 seed
demo@visionA.local / demo123帳號
- 不寫 mock OIDC server(測試例外 — 見
仍維持 ADR-005 規範的部分
- 不接真實 DB:Device / Model / Pairing 仍用 InMemoryRepository
- Repository interface 抽象:Phase 1 換 PostgresRepository 仍然零業務邏輯改動
- 資料重啟會遺失:雛形 process 重啟 → user session、device、model、pairing token 全消失
考慮過的替代方案
| 方案 | 評估 | 排除原因 |
|---|---|---|
| 繼續用 StaticAuth 到 Phase 1 | 不用做事 | 無法多用戶測試;FAE 試用會撞牆;之後還是要接 |
| 保留 dev fallback 切換 | dev 環境不需起 Member Center | 各環境行為分歧難排查;且 Member Center docker-compose 一鍵起,成本不高 |
| 接 Auth0 / Clerk / Cognito | 現成 | 跨 Innovedus 產品 SSO 需企業方案;vendor lock-in;資料外流 |
| 自刻 email + password + JWT | 完全掌控 | 要做密碼重設、2FA、暴力破解防禦;維運成本太高 |
詳細替代方案分析見 ADR-010。
後果 (Consequences)
正面影響
- 跨 Innovedus 產品 SSO:使用者一組帳號用所有 Innovedus 產品線
- 真實 user binding for Pairing token:多使用者上線時不再混淆
- 進 Phase 1 的前置就緒:FAE 內部試用、多用戶測試、上線都有 Auth 基礎
- frontend 安全性提升:Token 在 backend,不存 localStorage(順便清掉 security.md §14.1 / §14.2 / §14.3 三筆雛形安全債)
- demo-user 流程繼續可用:Member Center seed 同名帳號,雛形 demo 體驗不變
- 程式碼更乾淨:拔除「雙模式 if/else」之後 auth middleware 邏輯線性、容易追蹤
負面影響(接受的取捨)
- dev 環境多一個依賴:起 visionA 要先起 Member Center + Postgres(docker-compose 緩解)
- 對 Member Center dev 環境穩定性有依賴:MC 掛掉 visionA 也無法登入
- 緩解:docker-compose 固定版本;Member Center 團隊維持基礎可用性
- session 重啟即消失:in-memory store;雛形 Phase 0.6 可接受,內部測試者
- Phase 1 換 Redis/DB(同 interface)
風險
| 風險 | 緩解 |
|---|---|
| Member Center API 改動 → visionA 跟著爆 | 用 OIDC 標準介面(discovery + JWKS) — Member Center 改實作不改介面就 OK |
| Member Center webhook(user 刪除 / 停用)未實作 | 雛形不接收;Phase 1 補(記入 oidc-tdd.md TODO) |
| OIDC client_secret 外洩 | env / Secrets Manager + 不 commit + log 不印 secret + 可隨時 rotate |
| 既有 Pairing flow 對 user_id 變動的破壞 | OB5 整合測試 TestOIDCE2E_PairingTokenBindsToOIDCUser + TestOIDCE2E_MultiUserIsolation 驗證 user binding 正確 |
合規性
- Architect 確認
- 使用者裁決 Q1(OIDC + PKCE redirect)— 見 ADR-010
- 使用者裁決 Q4(完全取代 StaticAuth,不保留 dev fallback)— 見 ADR-010
- OB5 任務完成:StaticAuth 已從 codebase 移除
- OB5 任務完成:所有測試(含原 build-tag 隔離的 oidc_e2e_test.go)皆通過
- Phase 1 Kick-off:補 user 表 + 接 DB(取代 in-memory session store)
相關文件
- 上位:ADR-005(被本 ADR 推翻 Auth 部分;DB 部分仍有效)
- 同層:ADR-010(OIDC 接入策略 — 為什麼選 BFF + Authorization Code + PKCE + Member Center)
- 詳細實作:oidc-tdd.md(Phase 0.6 OIDC TDD 增補)
- 安全:security.md §2、§14(前端安全債清單)
後記(2026-05-01)
Phase 0.7 stage 部署時發現 Innovedus stage Member Center 配給 visionA 的 login OAuth client 是 public PKCE-only(無 client_secret),與本 ADR §32 中「visionA-backend 是 OIDC confidential client(持有 client_secret)」的假設不符。
具體狀況:
- Stage MC 端為 redirect flow 配的是 public client(
b8093fea1a504a5d8f0e04bee9f78f2e,無 secret) - 另配一組 confidential client(
<see stage .env.stage>+ secret)給未來 client_credentials grant 用,login flow 不用此組 - 表示 ADR-010 §「MC 強制 confidential」前提對 redirect flow 不再成立(對 server-to-server flow 仍成立)
處理:
- visionA-backend 擴充為兩種 mode 都支援(ClientSecret 從必填變選填,runtime 判斷走哪條路)
- 詳細決策見 ADR-013
- 本 ADR 的核心結論(OIDC 取代 StaticAuth、保留 AuthProvider/AuthService interface、Pairing 流程不動、in-memory session)全部仍有效,只是 client 配置層多了一個維度
版本記錄
| 日期 | 版本 | 變更 |
|---|---|---|
| 2026-04-26 | 1.0 | 初版 — OB5 完成後正式記錄推翻 ADR-005 Auth 部分 |
| 2026-05-01 | 1.1 | 新增「後記」段:stage 部署發現 MC login client 為 public PKCE-only,擴充由 ADR-013 處理 |