visionA/docs/autoflow/04-architecture/adr/adr-017-model-library-access.md
jim800121chen 88a8ddbd82 docs(architecture): ADR-017 模型庫存取架構(FAA delegated token)— v1.2 stage e2e 實證
決策:模型庫下載走 (a) MC delegated download token + Client 直連 FAA(照 pptx)
- visionA 向 MC POST /file-access/download-tokens 簽 fdt_ token
- Client 帶 Authorization: Bearer 直接打 FAA GET /files/{key}(不經 visionA、不經 AWS)
- FAA 用 MemberCenterDelegatedDownloadTokenValidator 打 MC validate

v1.0→v1.1→v1.2 演進(保留歷史):
- v1.0 推 (c) visionA 自簽(誤判 MC endpoint fictional,只看了 master)
- v1.1 改推 (a)(發現 MC develop 已實作 Issue/Validate、FAA validator 配套)
- v1.2 stage 真環境 + 真 secret + 真 user e2e 實測打通(§10 證據):
  MC Issue HTTP 200 簽出 fdt token → FAA 僅因假 object_key 回 404 file_not_found(認證鏈全綠)

剩餘 visionA 端 blocking(backend 接手):
- B1 object_key 斷層:model.Model 加 FAAObjectKey,第一階段只支援轉檔→promote 類 model
- B2 加打 MC Issue 簽 fdt 的 client code + .env

技術債(正式上線前處理):短期共用 FAA service client 4242ba63 做 PoC,
正式上線須 MC 配發 visionA 專屬 usage=file_api client(MC 規範禁止 client 混用 usage)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 03:42:46 +08:00

492 lines
52 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.

# ADR-017: 模型庫存取架構File Access Agent 重設計)
## 狀態
Proposed待使用者裁決**v1.2 修訂2026-06-07(a) 已用 stage 真實環境 + 真 secret + 真 user e2e 實測打通,跨團隊 blocking 歸零,剩餘全是 visionA 端純實作。** 保留 v1.0(推 (c)/ v1.1(改推 (a)、待跨團隊驗證)歷史。
## 日期
2026-06-06v1.0/ 2026-06-06v1.1 修訂)/ 2026-06-07v1.2 修訂)
## 作者
Architect Agent
---
## 0. v1.1 修訂註記(重要 — 推翻 v1.0 前提)
> **v1.0(同日稍早)推薦決策 1 = (c) visionA 自簽 token**,核心理由是「(a) 回到 MC token 那條鏈一年沒生出來、是 fictional見 ADR-016 致命發現)」。
>
> **這個前提在 v1.1 被推翻。** 重新驗證 source 後發現上週ADR-016判 (a) fictional 時**只 grep 了 MC master 分支**,沒看 develop。實際上
>
> 1. **MC `develop` 分支已實作** `POST /file-access/download-tokens`Issue+ `POST /file-access/download-tokens/validate`Validate兩個 endpointcommit `e77fdec`master 尚未 merge。
> 2. **FAA 端 `MemberCenterDelegatedDownloadTokenValidator.cs` 本來就是配套**:它打 MC validate 的 payload`token/tenant_id/file_id/object_key/method`)與 response`active/...`)形狀,**與 MC develop 的 validate endpoint 契約吻合**(見本 ADR §9 驗證證據)。
> 3. 所以 ADR-014 那條 `visionA → MC → FAA` delegated token 鏈,在 **source level 是通的**,缺的只是「部署 + scope 註冊 + 跨 repo 設定」,不是「程式碼不存在」。
>
> **使用者原本就想 follow pptx 設計(走 MC token = (a))。** 既然 (a) 不再是「賭 MC 一年生不出來的 endpoint」、而是「已寫好、待部署」本 ADR v1.1 **改推 (a)**,並把 (c) 降為「跨團隊部署若卡住的 fallback」。
>
> **但 v1.1 必須誠實標記**source 就緒 ≠ 零風險。(a) 仍有**跨團隊部署依賴**warrenchen 要 merge develop→master + 部署 MC stage + 設 FAA stage MemberCenterOptions + MC 端註冊 visionA service client 的 `files:download.read/delegate` scope。這條鏈過去 5/9 就因「scope 沒在 MC 註冊」撞 `invalid_scope`——同類風險仍在。詳見 §4 決策 1改寫與 §9可行性驗證
>
> **被 supersede 的內容**v1.0 §4 決策 1 的「推薦 (c)」結論、決策 2 的「visionA 自簽 JWT」具體流程、§5 落地順序 P0、§6 Q1、§7 R1。原文保留於下方並標 ~~刪除線~~ / 修訂框,保留歷史脈絡。
---
## 0.1 v1.2 修訂註記(重要 — (a) 已 stage 真實環境 e2e 實證打通)
> **v1.1 時 (a) 還是「source 就緒、但跨團隊部署 + scope 註冊是未知風險」**P0 標「warrenchen 主導、不可控」、Q1.5 標「待跟 warrenchen 確認」、R1 標「5/9 撞過 invalid_scope 的同類最高風險」)。
>
> **v1.2 的事實MC stage 已部署、scope 機制已驗證、整條 (a) e2e 認證鏈用真 secret + 真 user_id 跑通了HTTP 200 簽出 `fdt_` token、FAA 只因測試用假 object_key 回 404 file_not_found即認證鏈全綠。跨團隊 blocking 歸零,剩餘全是 visionA 端純實作。** 完整證據見**新增的 §10(a) 端到端 stage 實測證據)**。
>
> **v1.1 → v1.2 的核心轉變**
> 1. **MC 部署確認**stage `POST /file-access/download-tokens` 與 `/validate` 回 401非 404→ endpoint **已部署**(推翻 v1.0/v1.1「只看 git master 判 fictional」stage 實際版本已含 develop 功能)。
> 2. **scope 機制已驗證**MC 用 usage→scope 模型(`file_api` usage 自動帶 5 個 files scopeFAA 既有 service client `4242ba63...` 實測能拿 `files:download.delegate` token。R1 的「scope 沒在 MC 註冊」風險**已實證可繞過**(短期共用 FAA client、正式上線換 visionA 專屬)。
> 3. **🏆 e2e 全綠**:使用者提供真實 OIDC user_id整條 Issue→簽 fdt→FAA validate 鏈 stage 實測通過B3 tenant 一致、B4 真實 user、FAA validate 鏈全部實證)。
>
> **被 v1.2 supersede 的內容**v1.1 §4 決策 1 的「(a) 待跨團隊部署驗證」、§5 P0「跨團隊部署鏈warrenchen 主導不可控」、§6 Q1/Q1.5/Q9「待確認」狀態、§7 R1「最高風險」定性。原文保留並標 v1.2 更新框,保留歷史脈絡。
>
> **剩餘 visionA 端 blockingv1.2 標清楚、為 backend agent 接下來的工作)**B1 object_key 斷層(最關鍵,第一階段 (a) 只支援轉檔→promote 類 model+ B2 visionA 加「打 MC /oauth/token + 打 MC Issue 簽 fdt」的 client code + .env 設定。詳見 §10.4。
---
## 1. 背景與範圍
visionA Cloud 的「模型庫」要處理的是**大檔案**(轉檔/訓練好的模型、APP 套件),這類資產的存取設計有兩個本質問題綁在一起:
1. **流量費用** — S3 容量便宜但流量貴,大檔反覆下載不該每次都經過 AWS egress。
2. **權限保護** — Bucket 位址不能曝露;就算有人拿到連結也不能直接下載,每次存取都必須先做身份/權限查驗。
本 ADR 收斂三個面向,並把它們放進使用者目標架構(`模型庫存取架構.pptx`,引入 File Access Agent + NAS Bucket的框架下一起決策而不是各自打 patch
- **B 模型存取權限/分享**:現況 owner-only → 目標 share/workspace/role。
- **C 模型下載/檔案存取**:現況**沒有 model download endpoint**,要設計 pptx 的「Client 直連 FAA 下載」。
- **D promote-to-models 修補**stage 環境 promote 按鈕卡 OAuth 401要收斂進新架構而非孤立修補。
### 1.1 pptx 目標 vs 現況的根本衝突(本 ADR 存在的理由)
pptx 的上傳/下載流程都假設一條 token 鏈:**「visionA 向 MC 取 Token → 帶 Token 給 FAA → FAA 向 MC 驗 Token」**。
~~但歷史盤點(見 §2顯示MC 沒有簽發 delegated download token 的 endpoint也沒有 FAA 驗 token 所需的 introspection endpoint。ADR-014 §2 描述的這條鏈從 2026-05-02 起就是 fictional、從未 e2e 通過,上週 ADR-016 才改走「converter 中轉」繞開。~~
> **v1.1 修正**上述「MC 沒有 endpoint」的判斷**只成立於 MC master 分支**。MC `develop` 分支commit `e77fdec`**已實作** Issue + Validate 兩個 endpointFAA 端 validator 本來就是配套設計。所以這條鏈在 source level 是通的、只差部署。詳見 §0 修訂註記與 §9 可行性驗證。**本 ADR 改在「使用者想 follow pptx走 MC token」+「MC develop 已就緒、待部署」的新事實下,決定推動 (a) 並列出跨團隊部署 checklist。**
---
## 2. 現況盤點
| 面向 | 現況事實 | Source |
|------|---------|--------|
| **A. FAA 端能力** | `GET /files/{key}`(下載)**沒掛 RequireAuthorization()**,改用 `IDelegatedDownloadTokenValidator.ValidateAsync()` 驗 delegated download token。`PUT`/`GET metadata`/`HEAD`/`DELETE` 走 JWT Bearer + `EnsureJwtScopeAndTenant`scope: files:upload.write / files:metadata.read / files:delete。即 FAA 是 **dual-auth**download=delegated token、其他 op=service JWT。 | `file_access_agent/.../Program.cs:184-254` |
| **A'. FAA download 原生支援** | FAA download 本來就是為「帶 delegated token 直接下載」設計 → pptx 的「Client 直連 FAA 下載」**FAA 端原生支援**FAA 不是阻礙。 | 同上 |
| **B-新事實. MC develop 已有 token 簽發** | MC `develop`commit `e77fdec`**已實作** `POST /file-access/download-tokens`Issue, `[Authorize(Policy="FilesDownloadDelegate")]`,回 opaque `fdt_<base64url>` token + scope `files:download.read` + 自訂 TTL。**master 尚未 merge**——上週只 grep master 才誤判不存在。 | MC develop `FileAccessController.cs`Orchestrator 已驗)|
| **B-新事實'. MC develop 已有 token 驗證** | MC develop **已實作** `POST /file-access/download-tokens/validate`Validate, `[Authorize(Policy="FilesDownloadRead")]`,驗 TokenHash + RevokedAt + ExpiresAt + boundary(tenant/file/object_key/method),回 `{active, scope, expires_at}`。FAA validator 的 payload/response 形狀**與此契約吻合**。 | MC develop / FAA validator本 ADR §9|
| **B-結論修正. token 鏈 source 已就緒、待部署** | ADR-014 §2 的「visionA→MC→FAA delegated token 鏈」在 **source level 是通的**MC develop + FAA validator 配套。缺的是MC merge develop→master + 部署 stage + 註冊 visionA service client 的 download scope + FAA stage `MemberCenterOptions` 設定。**不再是 fictional而是「跨團隊部署待辦」。** | MC develop / FAA / ADR-014 |
| **C. promoteconverter→FAAprod work** | converter promote → FAA PUT NEF 走 **OAuth client_credentials + scope `files:upload.write`****prod 已上線可用**。 | `apps/task-scheduler/.../fileAccessAgent/client.js` |
| **C'. promote stage 卡 401** | stage 上 promote 按鈕卡 OAuth 401 → 研判是 stage converter 的 OAuth client 設定 / FAA accepted scope 問題(**設定問題,非設計問題**)。 | progress.md |
| **C''. NEF 雙存** | converter MinIO 存 NEFpromote 後 NEF 同時在 converter MinIOexpires_at=7天+ FAA。 | ADR-016 |
| **D. visionA download 現況** | visionA → converterinit/poll/promote/result download**全走 API key**ADR-015 §1。visionA backend 有 stream proxy 結構(`flow.go` DownloadStreamio.CopyN size cap + Content-Disposition + context cancellation現況 stream 來源是 converter `/result`。 | ADR-015 / `internal/conversion/flow.go` |
| **D'. 無 model download endpoint** | `internal/api/models.go`/api/models/* 有 list/get/init/finalize/delete**load-to-device 是 stub無 model download endpoint**。 | `internal/api/models.go` |
| **E. 資料模型現況** | DB `models` table 是 `owner_user_id UUID NOT NULL`(單一擁有者)+ `CREATE INDEX ON models (owner_user_id) WHERE deleted_at IS NULL``model.go` Model struct 只有 OwnerUserIDRepository.List 用 OwnerUserID 過濾,**無 share/workspace/role/ACL**。 | `internal/model/model.go` / `database.md:344-363` |
**現況一句話總結**FAA 設計上 readydownload 收 delegated token、op 收 service JWTconverter→FAA 上傳 prod work但「誰簽發/驗證 download token」這一環MC 從未提供visionA 也還沒自建所以模型下載至今走不通、model download endpoint 根本還沒長出來。
---
## 3. 目標架構pptx解讀
### 3.1 核心設計
引入 **File Access Agent (FAA) + NAS Bucket搭配 AWS S3**。大檔存取統一收口到 FAA
- Bucket 位址不曝露Client 只跟 FAA 互動,不知道也碰不到底層 Bucket
- 每次存取都過 FAA 的權限查驗。
- 未來可在 FAA 層加密。
- **省流量**:除了「上傳那一次」大檔流量不經 AWS下載走 FAANAS/公司網路),不吃 AWS egress。
- 轉檔/訓練好的模型可以**只傳網址**讓 FAA 自己去抓converter→FAA 已經是這個模式的雛形)。
### 3.2 pptx 上傳流程
1. POST visionA endpoint
2. visionA 向 MC 取 Token
3. 檔案(連結) + Token 送 FAA
4. FAA 向 MC 驗 Token
5. FAA 回檔案 path 給 visionA
### 3.3 pptx 下載流程
1. visionA 驗存取權限
2. visionA 向 MC 取 Token
3. **Client 帶 Token 直接向 FAA 發 GET**
4. FAA 向 MC 驗 Token
5. Response 檔案到 Client**不經 visionA、不經 AWS**
### 3.4 pptx 灰色地帶(必須在 §4 解掉)
| # | 灰色地帶 | 問題 |
|---|---------|------|
| G1 | **「visionA 向 MC 取 Token」** | ~~MC 沒有簽發 download token 的 endpoint~~ **v1.1MC develop 已有 Issue endpoint事實 B-新事實)**。可做到,但 visionA 需「復活 MC service token client」+ MC 端註冊 download scope見 §9 缺口)。 |
| G2 | **「FAA 向 MC 驗 Token」** | ~~MC 沒有 introspection endpoint~~ **v1.1MC develop 已有 Validate endpoint、FAA validator 本來就是配套(事實 B-新事實'**。可做到,但需 FAA stage 設好 `MemberCenterOptions`(見 §9 缺口)。 |
| G3 | **Client 直連 FAA 的憑證怎麼到 Client 手上** | pptx 說「Client 帶 Token」但沒講 token 範圍(單檔/全庫)、有效期、防盜用(被截走能不能重放)。 |
| G4 | **Bucket 不曝露但 FAA 對外** | FAA 要對 Client含桌面 local-tool / 瀏覽器開放CORS、FAA 對外網段、HA 都得想。 |
| G5 | **pptx 自列待解** | ①HANAS/公司網路掛掉就全掛)②頻寬(下載走公司網路,先 router 限流,未來移轉便宜雲端空間)。 |
**G1 + G2 是本 ADR 決策 1/決策 2 的核心**使用者已拍板「Client 直連 FAA」+「要驗權限」,但簽發/驗證 token 的 MC 能力不存在。所以必須決定 **delegated token 由誰簽、由誰驗**
---
## 4. 關鍵決策點
### 決策 1認證方向download token 誰簽發、誰驗證)— **v1.2 改寫((a) 已 stage e2e 實證)**
> **v1.2 重大更新**(a) 已用 stage 真實環境 + 真 secret + 真 user_id **e2e 實測打通**HTTP 200 簽出 `fdt_` token、FAA validate 鏈全綠,僅因測試用假 object_key 回 404 file_not_found見新增 §10。**(a) 從 v1.1 的「已寫好、待跨團隊部署驗證」升級為「已實證可行、跨團隊 blocking 歸零」。** 剩餘全是 visionA 端純實作object_key 斷層 + 打 MC Issue 的 client code見 §10.4)。**(c) fallback 實測後已無需動用**——除非未來改用正式專屬 file_api client 時受阻,否則 (a) 直接落地。
>
> **v1.1 原文(保留供對照)**v1.0 在「(a) 是 fictional」的錯誤前提下推 (c)。新事實MC develop 已實作 Issue+Validate、FAA validator 配套,見 §0/§9(a) 從「賭一年沒生出來的 endpoint」變成「已寫好、待跨團隊部署」。**使用者想 follow pptx= (a)**,故 v1.1 改推 (a)(c) 降為 fallback。
pptx 預設 MC 簽發 + MC 驗證。以下三案v1.1 重新評估):
| 維度 | (a) MC delegated tokenpptx 原圖★v1.1 改推 | (b) 延續 API key / pre-shared secret | (c) visionA 簽短期 token + FAA 本地驗v1.0 原推v1.1 降 fallback |
|------|---------------------------------------|------------------------------------|--------------------------------------------------------|
| **做法** | visionA 打 MC `POST /file-access/download-tokens` 拿 opaque `fdt_` token → Client 帶 token 打 FAA `GET /files/{key}` → FAA `IDelegatedDownloadTokenValidator` 打 MC `validate` 驗 | Client 帶固定 pre-shared secret 直連 FAAFAA 比對 secret | visionA 自簽短期 JWT含 file key + exp + 權限 claimFAA 用 visionA JWKS 本地驗簽 |
| **source 現況v1.1 新增)** | **MC develop + FAA validator 都已寫好**commit `e77fdec`),缺部署/scope 註冊/FAA stage 設定 | 無現成 | **FAA validator 目前 impl 是「打 MC validate」、不是「本地驗 visionA JWKS」** — 要 (c) 反而得叫 warrenchen **改寫 FAA validator**(比 (a) 重!)|
| **與 pptx 相容** | 完全相容(就是 pptx 原圖、使用者想要的) | 半相容 | 相容token 由 visionA 取代 MC 角色) |
| **對 MC / FAA team 依賴** | **中** — 不需「新寫」endpoint已寫好但需 warrenchen merge develop→master + 部署 MC stage + 設 FAA stage `MemberCenterOptions` + MC 註冊 visionA service client 的 download scope | 無 | **中-高v1.1 重估)** — FAA 現成 validator 是打 MC 的;改成本地驗 visionA JWKS 要 warrenchen **改 validator impl**,這也是動 FAA、不比 (a) 輕 |
| **與現況落差** | 中 — visionA 端要**復活 MC service token client**ADR-016 剛砍掉 mc_token_client.go要逆轉+ 新增「打 MC Issue」邏輯 | 中 | 中 — visionA 要建簽發+JWKSFAA 要改 validator |
| **防盜用(被截走)** | 佳 — MC 簽 opaque token + 短 TTL可自訂 `expires_in_seconds`+ boundary(tenant/file/object_key/method) + 可 revokeRevokedAt| **差** — secret 外洩=全庫淪陷 | 佳 — 短 exp + 綁 file key + 綁 user |
| **HA / 延遲** | 每次下載 FAA→MC 同步 validate多一跳 + MC 成驗證單點(但 FAA validator 有 service token cachetoken validate 是輕量呼叫)| 無外部依賴 | 無外部依賴FAA 本地驗簽,延遲最低 |
| **成本/工時** | 中 — visionA 復活 MC client逆轉 ADR-016+ 跨團隊部署協調(工期受 warrenchen 排程影響) | 低(但留安全債) | 中 — visionA 簽發+JWKS + **FAA validator 改寫(跨團隊)** |
| **opaque token 限制v1.1 新增)** | token 是 `fdt_<random>` 不是 JWT → FAA **無法本地驗簽**,每次必 call MC validate這是 MC 設計,符合 FAA 現成 validator| — | JWT 可本地驗(但 FAA 現成 validator 不是這樣做的)|
**v1.2 推薦:(a) MC delegated tokenpptx 原圖)— 已 stage e2e 實證可行。** (c) 仍保留為 **fallback**,但實測後已無需動用(除非未來改正式專屬 client 受阻)。
**改推 (a) 的理由**
1. **(a) 不再是 fictional** — MC develop + FAA validator 是配套的、已寫好§9 驗證。v1.0 推 (c) 的唯一核心理由「MC 那條鏈一年沒生出來」)已不成立。
2. **(c) 在新事實下反而更重** — FAA 現成的 `MemberCenterDelegatedDownloadTokenValidator` 是「打 MC validate」的 impl。要走 (c) 必須請 warrenchen **改寫 validator 成本地驗 visionA JWKS**——這同樣是動 FAA、且是「改既有正確配套」比「部署既有 (a)」更不划算。
3. **符合使用者意圖** — 使用者原本就想 follow pptx 設計(走 MC token。(a) = pptx 原圖。
4. **FAA / MC 端零程式碼改動** — (a) 在 source level 已就緒FAA / MC 不需寫新 code只需「部署 + 設定 + scope 註冊」。
5. **token 安全性最強** — MC 簽 opaque token支援 boundary 檢查 + revoke + 自訂 TTL比 visionA 自簽 JWT無法 revoke更完整。
> **(a) 的關鍵前置(跨團隊部署 checklist非程式碼**
> 1. **[MC / warrenchen]** merge `develop`(含 `e77fdec`+`5f32452`)→ `master` + 部署 MC stage。
> 2. **[MC / warrenchen]** 在 MC 端把 visionA service client`23605e14...`)的 `files:download.read` + `files:download.delegate` scope **註冊到 AuthResourceRegistry**(這是 5/9 撞 `invalid_scope` 的同類風險點——ADR-014 line 35 雖列了 4 scope但「列在 visionA 文件」≠「在 MC 端註冊」)。
> 3. **[FAA / warrenchen]** FAA stage 部署 + 設好 `MemberCenterOptions``BaseUrl` / `ClientId` / `ClientSecret` / `Scope` / `DownloadTokenValidationPath` 指向 MC develop 的 `/file-access/download-tokens/validate`)。
> 4. **[visionA / jimchen]** **復活 MC service token client**(逆轉 ADR-016 §5 砍除的 mc_token_client.go+ 新增「打 MC Issue endpoint 拿 fdt_ token」邏輯。
> 5. **[三方]** 對齊 boundaryvisionA Issue 時帶的 `object_key` 必須 = FAA `GET /files/{key}` 的 key = MC validate 的 `object_key`(見決策 2 與 §9 的 object_key 斷層分析)。
>
> **✅ v1.2 更新:此前置清單 step 13、5 已 stage 實測驗證完成。** MC stage 已部署step 1§10.1scope 機制已驗證——FAA service client `4242ba63...` 實測能拿 `files:download.delegate` token、boundarytenant/object_key/methode2e 通過step 2/5§10.2§10.3FAA stage 已部署且 `MemberCenterOptions` 已設好step 3§10.3 FAA validate 鏈全綠)。**原「step 2 是最高風險、5/9 撞 invalid_scope」已實證可繞過**(短期共用 FAA client 拿 token正式上線換 visionA 專屬 usage=file_api client見 §7 R1 改寫)。**剩 step 4visionA 復活 MC client + 打 Issue= 唯一剩餘工作、純 visionA 端、自己可控。**
> **~~何時 fallback 回 (c)~~v1.2:實測後已無需動用)**v1.1 設想「若跨團隊部署卡住則 fallback (c)」。v1.2 跨團隊部署已 e2e 通、無卡點,**(c) 不再需要動用**。僅保留為「未來 visionA 改用正式專屬 file_api client、若 MC 端無法配發專屬 client 時」的理論退路。**(b) pre-shared secret 仍不採用**。
#### v1.0 原推薦((c))保留供對照
> ~~**推薦:(c) visionA 簽短期 download token + FAA 本地驗。** 理由:(1) 解除對 MC 的歷史依賴——MC 那兩個 endpoint 一年沒生出來;(2) FAA 本來就是 dual-auth、有 validator 抽象,換 impl 即可;(3) 滿足 pptx 兩大訴求;(4) 未來不排斥 (a)。~~
>
> v1.1 supersede 原因:理由 (1) 的前提「MC endpoint 一年沒生出來」是錯的(只看了 master理由 (2) 「FAA 換 impl 即可」低估了——FAA 現成 validator 正是 (a) 要的、(c) 才需要改寫它。
---
### 決策 2Client↔FAA 憑證傳遞與 Bucket 保護 — **v1.2 改寫(流程已 stage 實測確認)**
> **✅ v1.2 實測確認**:以下流程已用 stage 真實環境 + 真 secret + 真 OIDC user_id 跑通(見 §10.3 e2e。token 確認為 MC 簽的 opaque `fdt_<base64url>`120s、scope `files:download.read`、token_type `file_download`)。**FAA download endpoint 確切用法**(給 backend 實作):`GET {FAA}/files/{objectKey}`token 放 **`Authorization: Bearer {fdt_token}`**FAA `TryReadAccessToken` 只認 Authorization Bearer**不認 query / 自訂 header**)。`objectKey` 必須與簽 token 時的 `object_key` **完全一致**FAA validate boundary 檢查、不一致回 `object_key_mismatch`)。
採用決策 1**(a)** 後下載流程具體化token 由 **MC** 簽,不是 visionA 自簽):
1. Clientlocal-tool / browser**visionA** 請求下載某 model帶 visionA 使用者 session
2. visionA **先驗存取權限**owner / share / workspace / role見決策 3
3. 通過 → visionA **用 MC service token 打 MC `POST /file-access/download-tokens`**Issue`tenant_id` / `user_id` / `file_id` / `object_key` / `method=GET` / `expires_in_seconds`MC 回 opaque `fdt_<base64url>` token。
4. visionA 回給 Client「FAA 下載 URL + `fdt_` token」。
5. Client 帶 token 直接 `GET FAA /files/{object_key}`**`Authorization: Bearer fdt_...`**——v1.2 實測FAA 只認 Authorization Bearer、不認 `?access_token=` query不經 visionA、不經 AWS
6. FAA `MemberCenterDelegatedDownloadTokenValidator` **打 MC `POST /file-access/download-tokens/validate`**(帶 FAA 自己的 MC service token、`instanceOptions.TenantId` + 從 URL path 取的 `ObjectKey` + `Method=GET`)驗 token + boundarytenant/object_key/method→ MC 回 `{active:true}` → FAA 回檔。
> 注意:`fdt_` 是 **opaque random token、不是 JWT** → FAA **無法本地驗簽**,每次下載一定要 call MC validate這是 MC 刻意設計:可即時 revoke。延遲多一跳 FAA→MC但 FAA validator 有 service token cache見 FAA validator source line 74-129validate 本身是輕量 POST。
**防盜用 / 不曝露設計**
| 風險 | 對策 |
|------|------|
| token 被截走重放 | 短 TTLvisionA Issue 時帶 `expires_in_seconds`,建議 60300s待裁決 Q2MC 端 boundary 綁 `object_key` + `method`(截走也只能下載那一個檔、那個 methodMC 端可 `RevokedAt` 即時撤銷 |
| Bucket 位址曝露 | Client 只拿到 FAA URL`/files/{object_key}`object_key 是 FAA 內部 storage key、不是 S3/NAS 真實路徑 |
| CORSbrowser 直連 FAA | FAA 需設允許 visionA 前端 origin 的 CORS待裁決 Q3是否需瀏覽器直連只 local-tool 直連無 CORS 問題)|
| FAA 對外網段 / HA | 見決策 5 |
| token 簽發濫用 | visionA 端對「打 MC Issue」做 rate limit + audit log誰、何時、要下載哪個 model|
#### ⚠️ object_key 三方對映斷層v1.1 新增 — (a) 最大實作風險)
(a) 要求 visionA Issue 時帶的 `object_key` = FAA `GET /files/{key}` 的 key = MC validate 的 `object_key`,三方必須一致。但驗證現有 source 發現**斷層**
| key 空間 | 值 | source |
|----------|-----|--------|
| **visionA model storage key** | `models/{userID}/{modelID}.nef` | `internal/api/models.go:235`visionA 自己的 S3/LocalFS key|
| **converter promote → FAA 的 object_key** | 由 `flow.go` 按命名規則組的 `TargetObjectKey`FAA 內部 key | `converter_client.go` PromoteReq.TargetObjectKey |
| **FAA 實際存放的 object_key** | converter PUT 進去的 key | FAA `PUT /files/{objectKey}` |
**斷層本質**
- visionA model store 的檔案是走 `/api/models/init+finalize` 上傳到 **visionA 自己的 storage**`models/{userID}/{modelID}.nef`**從未經過 FAA**。
- 只有「轉檔結果 NEF」會經 converter promote 進 FAA用另一套 `TargetObjectKey`)。
- **所以「model download (a) 直連 FAA」的前提——model 必須在 FAA 上、且 visionA 知道它的 FAA object_key——目前不成立**
- 上傳類 modelSourceUploaded根本不在 FAA 上,無法走 (a) 直連 FAA。
- 轉檔類 modelSourceConverted若有 promote 進 FAAvisionA 需**記錄該 model 的 FAA object_key**(目前 `model.Model` struct 沒有 `faa_object_key` 欄位,只有 visionA 自己的 `StorageKey`)。
**(a) 要可行,必須補的 visionA 端工作(決策方向,細節由 backend agent 落地)**
1. `model.Model``FAAObjectKey`nullable欄位記錄 model 在 FAA 上的 keypromote 時寫入)。
2. 釐清「哪些 model 在 FAA 上」:只有經 converter promote 的才有 FAAObjectKey純上傳的 model 要 (a) 直連 FAA 需先有「把上傳檔也 PUT 進 FAA」的路徑目前沒有
3. download flow 用 `FAAObjectKey`(不是 `StorageKey`)去 Issue MC token + 組 FAA URL。
> **這個斷層是 (a) 比 (c) 多出的實作成本**,且影響「上傳類 model 能不能走 (a)」——須在 Q1 裁決時一併考慮(見待裁決 Q7
#### v1.0 原決策 2visionA 自簽 JWT保留供對照
> ~~採用決策 1(c)visionA 簽發短期 JWTclaim: file_key/sub/exp/scope、FAA 用 visionA JWKS 本地驗簽。~~ v1.1 改為 MC 簽 opaque `fdt_` token + FAA 打 MC validate。
---
### 決策 3B — 權限/分享資料模型owner-only → share/workspace/role
現況 `models.owner_user_id NOT NULL` 單一擁有者(事實 E。最小可行變更**不一次做到完整 RBAC**,只加「能分享 + 能用 workspace 群組 + 三種 role」
**方案比較(最小可行 vs 完整):**
| 維度 | (B1) 只加 model_sharesper-model 直接分享)★第一階段推薦 | (B2) 加 workspaces + memberships + model 歸屬 workspace | (B3) 完整 ABAC/RBAC policy engine |
|------|--------------------------------------------------|----------------------------------------------|--------------------------------|
| 變更面 | 加 1 張 `model_shares(model_id, grantee_user_id, role, ...)` | 加 `workspaces` / `workspace_members` / `models.workspace_id` | 引入 policy 表 + evaluator |
| 滿足 pptx | 「分享給特定人」✓ | 「團隊/組織共用」✓ | 過度設計 |
| 工時 | 小 | 中 | 大 |
| 與現況落差 | 小owner_user_id 保留share 疊加) | 中 | 大 |
**推薦:分兩步 — 先 (B1),後 (B2)(B3) 不做。**
最小資料模型(對齊 `database.md §2.3` + `models` DDL`owner_user_id` 保留不動):
```
-- 第一階段 (B1)per-model 分享
CREATE TABLE model_shares (
model_id UUID NOT NULL REFERENCES models(id),
grantee_user_id UUID NOT NULL REFERENCES users(id),
role TEXT NOT NULL, -- 'viewer' | 'editor'owner 仍記在 models.owner_user_id
granted_by UUID NOT NULL REFERENCES users(id),
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
PRIMARY KEY (model_id, grantee_user_id)
);
CREATE INDEX ON model_shares (grantee_user_id);
-- 第二階段 (B2)workspace 群組共用(待第一階段穩定後)
-- workspaces(id, owner_user_id, name, ...)
-- workspace_members(workspace_id, user_id, role)
-- models 加 nullable workspace_idNULL=個人資產,非 NULL=workspace 資產)
```
**role 語意(三種,最小集)**
- `owner``models.owner_user_id`,可刪除/改權限/分享。
- `editor`:可更新 model metadata、可 promote/覆蓋。
- `viewer`:只能 list/get/download。
**權限查驗點**:決策 2 第 2 步「visionA 先驗存取權限」就是查 `owner_user_id == user OR model_shares 命中 ORB2 後workspace_members 命中`,通過才簽 download token。`Repository.List` 要從「只用 owner_user_id 過濾」改成「owner shared workspace」。
> 此為**決策方向**schema 細節由後續實作backend agent落地 migration本 ADR 只定模型形狀。
---
### 決策 4D — promote 收斂stage OAuth 401
事實 C/C'promoteconverter→FAA PUTOAuth client_credentials + scope `files:upload.write`**prod 已 work**,只有 **stage 卡 401**,研判是**設定問題stage OAuth client 設定 / FAA accepted scope非設計問題**。
| 方案 | 說明 | 評估 |
|------|------|------|
| (D1) 修 stage OAuth 設定 ★推薦 | 對齊 stage converter 的 OAuth client_id/secret/scope 與 FAA 在 stage 接受的 scope`files:upload.write`),讓 stage 跟 prod 一致 | promote 設計本身是對的prod 已證明),不該為了 stage 設定問題去改架構 |
| (D2) 改走新存取路徑 | 把 promote 也改成決策 1(c) 的 token 模式 | **不推薦** — promote 是 server-to-serverconverter→FAA用 service JWT/OAuth 才對decision 1(c) 的短期 download token 是給 Client 下載用,兩者用途不同,不該混 |
**推薦:(D1) 修 stage 設定,不動 promote 架構。** promote上傳/server-to-server與 downloadClient 直連)是兩條不同的 auth 路徑,**正好對應 FAA 的 dual-auth 設計**op=service JWT、download=delegated token不要強行統一。
> promote 走 OAuth service token、download 走 visionA 簽的 delegated token — 這個分離是刻意的,符合事實 A 的 FAA dual-auth。
---
### 決策 4.5:與 ADR-016 converter 中轉的關係v1.1 新增 — 並存,非取代)
ADR-016 讓 **轉檔結果 NEF download**`converter GET /api/v1/jobs/{id}/result` + visionA stream 中轉visionA 不直接打 FAA。本 ADR (a) 是 **model library download** 走 Client 直連 FAA。兩者**是兩條不同用途的路徑、並存**
| 路徑 | 用途 | 觸發點 | 來源 | 認證 |
|------|------|--------|------|------|
| **ADR-016 converter 中轉** | 下載**轉檔結果 NEF**job-scoped、剛轉完還沒進模型庫| `GET /api/conversion/{job_id}/download` | converter MinIO7 天 expires_at| visionA→converter API key |
| **ADR-017 (a) 直連 FAA** | 下載**模型庫 model**(持久資產、已在模型庫)| model download endpoint待建| FAA持久| MC `fdt_` token |
**為什麼並存而非取代**
- ADR-016 的來源是 **converter MinIO7 天就 GC**——只適合「剛轉完、demand-trigger download」的短期場景**不適合模型庫**模型庫是持久資產7 天後 converter MinIO 沒了)。
- ADR-017 (a) 的來源是 **FAA持久**——適合模型庫長期下載。
- 兩條路徑的「檔案在哪」不同converter MinIO vs FAA、生命週期不同7 天 vs 持久),故並存。
**但有交集需釐清(待裁決 Q8**
- 「轉檔結果」可以「加到模型庫」promote 進 FAA。一旦加到模型庫該 model 的 download 應走 (a)FAA 持久),而非 ADR-016converter MinIO 會過期)。
- **建議分流**model 在模型庫內(有 FAAObjectKey→ 走 (a)job 結果還沒進模型庫 → 走 ADR-016。兩者不互相取代依「是否已是模型庫資產」分流。
- ADR-016 的 converter 中轉**保留不動**(轉檔結果 demand download 仍需要它);(a) 是**新增**模型庫的持久 download 路徑。
### 決策 5HA / 頻寬pptx 自列待解 G5
| 問題 | 短期(本架構落地時) | 長期(標 Phase 後續,本 ADR 不深入) |
|------|---------------------|----------------------------------|
| HANAS / 公司網路掛掉全掛) | 接受單點風險 + 監控告警FAA health check關鍵 model 可在 converter MinIO 保留副本(事實 C'' NEF 雙存已是雛形)作為降級來源 | 移轉便宜雲端空間做 FAA 後端 / 多節點 FAA |
| 頻寬(下載走公司網路) | router 限流pptx 已提visionA 端對 download token 簽發做 rate limit | 移轉便宜雲端空間,下載改走雲端 egress屆時重評「省流量」前提 |
**標記為後續 Phase**,本 ADR 先讓核心存取路徑(決策 14跑通HA/頻寬不阻擋第一階段,但須在 progress 留風險登記。
---
## 5. 建議落地順序
依賴關係:~~決策 1(a) 的跨團隊部署是地基~~**v1.2:跨團隊部署已 stage e2e 實測完成、不再是地基/不確定性**C下載依賴 visionA 端實作B權限是下載前的查驗點D 與 B/C 無依賴可平行。
> **v1.2 改寫 P0**v1.1 的 P0 是「MC merge+部署 + scope 註冊 + FAA stage 設定」這條跨團隊部署鏈、標為 (a) 最大不確定性。**v1.2:這條鏈已用 stage 真實環境 e2e 實測打通§10、跨團隊 blocking 歸零。** P0 縮減為「visionA 端實作」+「(正式上線前)請 MC 給 visionA 專屬 file_api client」。短期 PoC 直接共用 FAA service client `4242ba63...`(已實測可拿 token
| 階段 | 內容 | 依賴 | 可平行? |
|------|------|------|---------|
| **P0跨團隊部署✅ v1.2 已驗證完成** | ~~① MC merge+部署 stage ② scope 註冊 ③ FAA stage 設定~~ **三項已 stage e2e 實測通過§10**。剩餘 P0 = 僅「正式上線前請 MC 配發 visionA 專屬 usage=file_api client」短期 PoC 共用 `4242ba63...`,非 blocking| 無 | — |
| **P0.5visionA 端,現為起點)** | **B2**:復活 MC client打 MC `/oauth/token` + 打 MC Issue 簽 fdt逆轉 ADR-016 §5比原版單純——token 由 MC 簽、不自簽)+ .env 設定client `4242ba63...` / MC base url / FAA base url / tenant `732270c0...`)。**B1**`model.Model``FAAObjectKey` 欄位(解 object_key 斷層,見決策 2 + §10.4| 無P0 已完成)| 與 P1a/P1b 平行 |
| **P1a** | D — 修 stage OAuth 設定(決策 4 D1。**v1.2 線索**`4242ba63...``files:upload.write`、promote 卡的 401 很可能同類問題converter 用的 client 沒 upload.write→ 可一併驗證 | 無 | **與 P0.5/P1b 平行** |
| **P1b** | B 第一階段 — model_shares 表 + 權限查驗(決策 3 B1| 無 | **與 P1a 平行** |
| **P2** | C — visionA 打 MC Issue + model download flow決策 1a + 決策 2FAA 用法見決策 2 v1.2 確認框);第一階段 (a) 只支援轉檔→promote 類 modelobject_key 斷層§10.4 B1| P0.5 + P1b | 接 P0.5/P1b |
| **P3** | B 第二階段 — workspaces/workspace_members決策 3 B2| P1b 穩定後 | 獨立 |
| **P4後續 Phase** | HA / 頻寬(決策 5 長期方向)| 不阻擋前面 | 獨立 |
> **~~fallback 觸發點~~v1.2跨團隊部署已通、fallback 不再需要)**v1.1 設想「P0 卡住則 fallback (c)」。v1.2 P0 已 stage e2e 通、無卡點,**(c) 不動用**。僅保留「正式上線改用 visionA 專屬 file_api client、若 MC 端無法配發時」的理論退路。
不寫 task 級細節;各階段的實作 task 拆分由 backend agent 在進開發時做。
---
## 6. 待使用者裁決清單
| # | 待裁決項 | 推薦選項 | 理由 |
|---|---------|---------|------|
| **Q1v1.2:已確認走 (a)** | 決策 1 認證方向:**(a) MC delegated token** vs (c) visionA 自簽fallback | **✅ 已確認走 (a)** — stage e2e 實測打通§10(c) 無需動用 | MC stage 已部署 + scope 機制 + FAA validate 鏈全部 e2e 實證;符合使用者 follow pptx 意圖。(c) 僅留理論退路 |
| **Q1.5v1.2:已實測 P0 可行)** | (a) 的 P0 跨團隊前置 checklist 能否打通 | **✅ 已實測通過** — MC/FAA stage 已部署、scope 用 FAA client `4242ba63...` 實測可拿 token、e2e 全綠§10| 原「5/9 撞 invalid_scope」風險已實證可繞過共用 FAA client。跨團隊 blocking 歸零 |
| **Q2** | download token 有效期visionA Issue 時帶的 `expires_in_seconds`| **60300 秒**(傾向 120s| 夠 Client 發起下載、又短到截走也很快失效 |
| **Q3** | 是否需支援**瀏覽器**直連 FAA牽涉 CORS或只 local-tool 直連 | 先**只 local-tool**(無 CORS瀏覽器需求出現再加 FAA CORS | 縮小第一階段範圍 |
| **Q4** | B 權限模型做到哪 | **先 B1model_sharesB2 排後續B3 不做** | 最小可行先上 |
| **Q5** | promote stage 401 修法 | **D1 修 stage 設定,不動 promote 架構** | prod 已證明設計對 |
| **Q6** | HA/頻寬是否阻擋第一階段 | **不阻擋,標後續 Phase + 風險登記** | 先讓核心存取路徑跑通 |
| **Q7v1.1 新增)** | object_key 斷層:上傳類 model不在 FAA 上)能否走 (a) 直連 FAA | **第一階段 (a) 只支援「轉檔→promote 進 FAA 的 model」有 FAAObjectKey純上傳 model 的 FAA download 排後續**(需先有「上傳檔也 PUT 進 FAA」的路徑| 純上傳 model 目前只在 visionA 自己 storage、不在 FAA無法直連避免第一階段範圍爆炸 |
| **Q8v1.1 新增)** | model download 走 (a) 直連 FAA vs 沿用 ADR-016 converter 中轉的分流 | **已在模型庫(有 FAAObjectKey→ (a)job 結果還沒進模型庫 → ADR-016**(兩者並存)| ADR-016 來源 converter MinIO 7 天就 GC、不適合模型庫持久資產(a) 來源 FAA 持久 |
| **Q9v1.2:已同意復活 MC client** | 是否接受「復活 MC service token client」逆轉 ADR-016 §5 剛砍的 mc_token_client.go| **✅ 已同意接受**(a) 必要成本;邏輯比原版單純:打 MC `/oauth/token` 拿 service token + 打 MC Issue 簽 fdttoken 由 MC 簽、不需自簽)| (a) 要 visionA 打 MC Issue必須有 MC service token使用者已同意 |
| **Q10v1.2 新增)** | 是否接受「短期共用 FAA service client `4242ba63...` 做 PoC、正式上線前換 visionA 專屬 usage=file_api client」| **接受(使用者已傾向)** — 短期解 blocking、正式上線換專屬降技術債 | `4242ba63...` 是現成 usage=file_api client實測可拿 download.delegate token共用違反 MC「secret 不共用」規範(技術債,見 R1正式上線換專屬 client。詳見 §7 R1 + §10.2 |
---
## 7. 後果
### 正面 — **v1.1 改寫**
- **模型下載有可行路徑、且符合 pptx 原圖((a)**:不需 visionA 自建簽發 + JWKSMC/FAA source 已就緒。
- 滿足 pptx 兩大訴求Bucket 不曝露 + 每次驗權限 + 下載不經 AWS。
- **FAA/MC 端零程式碼改動**(只需部署 + 設定 + scope 註冊)——比 (c)「請 warrenchen 改寫 FAA validator」輕。
- **token 安全性最強**MC 簽 opaque `fdt_` token支援 boundary + revoke + 自訂 TTL比 visionA 自簽 JWT無法 revoke完整。
- B 權限分階段,先解 owner-only 痛點,不過度設計。
### 負面(接受的取捨)— **v1.1 改寫**
- **visionA 要復活 MC service token client**(逆轉 ADR-016 §5 剛砍的 mc_token_client.go——但邏輯比原版單純只需 service token cache + 打 Issue不需自簽 delegated token
- **(a) 依賴 MC 線上 validate**:每次 FAA download 多一跳 FAA→MCFAA validator 有 token cache 緩解,但 MC 成驗證單點)。
- **object_key 斷層成本**`model.Model` 要加 `FAAObjectKey`、釐清「哪些 model 在 FAA 上」(見決策 2
- **跨團隊部署不可控**(a) 的 P0 依賴 warrenchen 排程merge/部署/scope 註冊)。
- HA/頻寬留後續 Phase第一階段 NAS/公司網路是單點。
### 風險 — **v1.1 改寫**
- **R1v1.2 改寫——原「scope 註冊最高風險」已 e2e 實證解除,改為共用 client 的技術債)**v1.1 的 R1「scope 沒在 MC 註冊、5/9 撞 invalid_scope」已用 stage e2e 實測解除§10MC scope 機制驗證通過、FAA client `4242ba63...` 實測可拿 `files:download.delegate` token。**v1.2 新的 R1 = 短期共用 FAA service client `4242ba63...` 的技術債**(i) 違反 MC source 明訂「OAuth client 禁止混用 usage、secret 不共用」;(ii) FAA 的 client secret 進 visionA `.env` → 擴大 secret 洩漏面(一份 secret 同時被 FAA 與 visionA 持有,任一邊洩漏波及兩個服務)。**緩解:正式上線前請 MC 配發 visionA 專屬 usage=file_api client換掉 `4242ba63...`),把 secret 邊界收回 visionA 自己**(待裁決 Q10、合規清單追蹤。實測共用實際上 e2e 可跑通是因 `4242ba63...` 本就是 usage=file_api client、自動帶 5 個 files scope。
- **R2v1.1 新增)**object_key 三方斷層(決策 2——上傳類 model 不在 FAA 上,第一階段 (a) 只能支援轉檔→promote 的 model。若使用者期待「所有 model 都能直連 FAA」範圍大幅擴張。
- **R3v1.1 改寫)**(a) 跨團隊部署任一卡住 → 整條 (a) 不通。緩解:保留 (c) 為 fallbackP0 設可接受期限,逾期 fallback。
- **R4opaque token 限制)**`fdt_` 是 opaque、FAA 無法本地驗,每次 download 必 call MC validate。MC validate 慢/掛 → 所有 model download 失敗。緩解FAA validator service token cache + MC validate SLA 監控。
- **R5pptx 矛盾,原 R3**pptx「只傳網址讓 FAA 去抓」上傳省流量但來源converter MinIO也在公司網路HA/頻寬風險與下載同源。
- **R6原 R4**NEF 在 converter MinIO7天+ FAA 雙存,長期雙存一致性與清理策略未定,須在 P2 釐清。
- **R1'(僅 fallback 回 (c) 才適用)**(c) 把 token 簽發收回 visionA**JWKS 私鑰外洩 = 全模型庫可被簽 token 下載**,須 secret 管理 + key rotation。(a) 無此風險token 由 MC 簽、可 revoke
---
## 8. 合規性 — **v1.2 改寫**
- [x] **(a) 的 P0 跨團隊部署 checklist** — ✅ v1.2 已 stage e2e 實測完成MC/FAA stage 已部署、scope 機制驗證、Issue→fdt→FAA validate 鏈全綠§10跨團隊 blocking 歸零
- [ ] **(正式上線前)請 MC 配發 visionA 專屬 usage=file_api client** 換掉短期共用的 FAA client `4242ba63...`R1 技術債、Q10
- [x] 與使用者確認 Q1 / Q1.5 / Q9 裁決 — ✅ 已確認走 (a) + 同意復活 MC client新增 Q10共用 client PoC使用者已傾向接受
- [ ] 與 backend agent 確認 object_key 斷層解法(`model.Model``FAAObjectKey`、上傳類 model 範圍)
- [ ] 與 security agent 評估 R4MC validate 單點 / SLA若 fallback (c) 則評估 R1'JWKS 私鑰)
- [ ] 成本影響:第一階段無新雲端資源(沿用現有 FAA + NAS + MC主要是 visionA 開發工時 + 跨團隊部署協調
---
## 9. (a) 端到端可行性驗證v1.1 新增)
本節記錄改推 (a) 前所做的 source 驗證證據與三端缺口清單。
### 9.1 source 證據(已就緒的部分)
| 端 | 證據 | 結論 |
|----|------|------|
| **MC develop** | commit `e77fdec`FileAccessDownloadToken entity + migration`POST /file-access/download-tokens`Issue, `[Authorize(Policy="FilesDownloadDelegate")]`,回 `fdt_<base64url>` + scope `files:download.read` + 自訂 `expires_in_seconds`token hash 存 DB`POST /file-access/download-tokens/validate`Validate, `[Authorize(Policy="FilesDownloadRead")]`,驗 TokenHash + RevokedAt + ExpiresAt + boundary。commit `5f32452` AuthResourceRegistry 做 audience/scope mapping。**master 尚未 merge。** | Issue + Validate **已實作**token 為 opaque非 JWT→ 必 call MC validate |
| **FAAmasterworking tree 已驗)** | `MemberCenterDelegatedDownloadTokenValidator.cs``ValidateAsync``GetServiceAccessTokenAsync`FAA 自己拿 MC service token`client_credentials` + `_options.Scope`)→ `PostAsJsonAsync(_options.DownloadTokenValidationPath, payload)` 打 MC。payload = `{token, tenant_id, file_id, object_key, method}`、response = `{active, tenant_id, user_id, file_id, object_key, method, expires_at}``Program.cs:33` 註冊此 validator`Program.cs:184` `GET /files/{**objectKey}` 用它驗 + boundary 檢查tenant_mismatch / object_key_mismatch / method_mismatch。 | FAA validator 的 payload/response 形狀**與 MC develop validate 契約吻合** → 配套設計FAA 端**不需重寫** |
| **visionA已驗** | `internal/conversion/`mc_token_client.go **確實已不存在**ADR-016 §5 砍除生效。download 現走 converter `GetResult``converter_client.go:998`+ API key。model 上傳走 `/api/models/init+finalize` 進 visionA 自己 storage`models.go:235` `StorageKey=models/{userID}/{modelID}.nef`)。 | visionA 端要 (a) 須**復活 MC service token client** + 新增打 MC Issue 邏輯 |
### 9.2 三端缺口清單((a) 端到端「還缺什麼」)
| 端 | 缺口 | 誰做 | 風險 |
|----|------|------|------|
| **MC** | ① develop 尚未 merge master + 部署 stage | warrenchen | 排程不可控 |
| **MC** | ② visionA service client`23605e14...`)的 `files:download.read/delegate` scope **是否已在 MC AuthResourceRegistry 註冊**未確認ADR-014 line 35 只證明「visionA 文件列了」非「MC 端註冊了」Issue 的 `[Authorize(Policy="FilesDownloadDelegate")]` 需 visionA token 帶對應 claim | warrenchen | **最高5/9 撞過 invalid_scope** |
| **FAA** | ③ FAA stage 是否部署 + `MemberCenterOptions`BaseUrl/ClientId/ClientSecret/Scope/DownloadTokenValidationPath是否設好未確認FAA 自己也要有 MC service tokenscope 待確認)| warrenchen | 中 |
| **visionA** | ④ 復活 MC service token client逆轉 ADR-016 §5+ 打 MC Issue | jimchen / backend agent | 低(自己可控)|
| **visionA** | ⑤ object_key 斷層:`model.Model``FAAObjectKey`、釐清上傳類 model 不在 FAA 上的範圍(見決策 2| jimchen / backend agent | 中(範圍影響)|
| **三方** | ⑥ boundary 對齊visionA Issue 帶的 object_key = FAA URL key = MC validate object_key見決策 2 斷層分析)| 三方協調 | 中 |
### 9.3 驗證結論
- **source level(a) 通**MC develop Issue+Validate 已寫、FAA validator 配套、契約吻合)。**v1.0 判 fictional 是因只 grep master。**
- **部署 level(a) 未通**,缺 §9.2 六項,其中 ② scope 註冊是最高風險warrenchen 端、5/9 同類事故)。
- **visionA 端額外成本**:復活 MC service token client逆轉 ADR-016+ 解 object_key 斷層(加 FAAObjectKey
- **(c) 在新事實下反而更重**FAA 現成 validator 正是 (a) 要的;(c) 要請 warrenchen 改寫成本地驗 JWKS是「改既有正確配套」。
- **最終建議:推 (a),但 P0 step ②③ 須先跟 warrenchen 確認可行Q1.5),逾期 fallback (c)。**
> **⚠️ v1.2 後記**§9 是 v1.1 時的「source level 驗證 + 待跨團隊驗證」紀錄。**§9.2 六項缺口中的 ①②③MC merge/部署、scope 註冊、FAA stage 設定)已於 v1.2 用 stage 真實環境 e2e 實測全部通過**(見 §10。§9.3「部署 level 未通」結論已被 §10 推翻。④⑤⑥visionA 端實作 + object_key 斷層 + boundary 對齊)轉為 §10.4 的 visionA 端 checklist。
---
## 10. (a) 端到端 stage 實測證據v1.2 新增 — 本 ADR 最有價值的部分)
本節記錄 v1.1 → v1.2 之間,用 **stage 真實環境 + 真 secret + 真 OIDC user_id** 把整條 (a) 鏈路打通的實證。**這把 (a) 從「待驗證的可行性」升級為「已實證可行」。**
### 10.0 環境與憑證(使用者由 MC team 取得)
| 項目 | 值 |
|------|-----|
| MC stage Web | `https://stage-9527.innovedus.com:7880/` |
| MC stage API | `https://stage-9527.innovedus.com:7850/` |
| FAA stage | `https://stage-9527.innovedus.com:5081/` |
| visionA Login clientweb_login | `b8093fea1a504a5d8f0e04bee9f78f2e`callback `https://stage-9527.innovedus.com:9527/api/auth/callback`**注意MC team 訊息少寫 `/api`,以 visionA 現用含 `/api` 的為準、勿改**|
| visionA「Login 以外 API call」共用 client | `23605e14a2c64660abd97e29963d8d58`client_credentials|
| **FAA service clientPoC 共用)** | `4242ba63099d4f318dd3f143d27ef4c5`tenant `732270c0-449c-489c-bfad-321e9bf89b3d`scopes `files:upload.write files:metadata.read files:delete files:download.delegate` |
| 真實 OIDC user_id`/api/auth/me` | `b5332e51-c394-45c7-a28a-83e6da127ba9` |
### 10.1 MC 部署確認(推翻「只看 git master 判 fictional」
- `POST /file-access/download-tokens`Issue`/validate` 在 stage 回 **401非 404→ endpoint 已部署**。對照亂打路徑回 404。
-**推翻 v1.0/v1.1「只看 git master 判 fictional」**stage 實際版本已含 develop 的 download token 功能(即 §9.2 缺口 ①「develop 尚未 merge master」對 stage 不成立——stage 跑的版本已有此功能)。
### 10.2 scope 模型診斷MC usage→scope 模型)
- MC discovery `scopes_supported``files:download.read/delegate/upload.write/metadata.read/delete`
- **MC 用 usage→scope 模型**`AuthResourceRegistryService``file_api` 這個 **usage 類型**(不是現成 client自動帶 5 個 files scope。`file_api` 強制 **confidential + client_credentials + 綁 tenant_id**。source 明訂「**OAuth client 禁止混用 usage、secret 不共用**」(→ R1 技術債的根據)。
- **visionA 共用 client `23605e14...` 實測**:所有 files scope 全部 `not allowed``converter:job.*` 是 invalid**MC 根本沒這 scope**,呼應 converter 已改 API key。→ **共用 client 對 file_api 行不通**(規範禁止混用 usage
- **FAA client `4242ba63...` 實測**:能拿 `files:download.delegate` token`aud=file_access_api` → 它就是個 **usage=file_api 的 client**(所以 PoC 直接共用它可行)。
### 10.3 🏆 (a) 端到端 e2e 全綠stage 真實環境、真 secret、真 user_id
| Step | 動作 | 結果 |
|------|------|------|
| **Step 1** | `4242ba63...` 打 MC `/oauth/token`client_credentials`files:download.delegate` token | ✅ 拿到 |
| **Step 2** | 帶該 token + 真 user_id 打 MC `POST /file-access/download-tokens`Issue| ✅ **HTTP 200簽出 `fdt_...` token**token_type=`file_download`、120s、scope=`files:download.read`、tenant/user/object_key 正確回填)|
| **Step 3** | 拿 `fdt_` token 以 `Authorization: Bearer` 打 FAA `GET /files/{objectKey}` | ✅ **HTTP 404 `file_not_found`**= 認證 + MC validate 全過、FAA 去 bucket 找檔、**只因 `test/probe.nef` 是假 key 不存在**;換真實檔就回檔案內容)|
**B3tenant 一致、B4user 是 MC 真實 user、FAA validate 鏈,全部實證通過。** 404 是「檔案不存在」而非「認證失敗」——這正是認證鏈全綠的證明。
### 10.4 剩餘 visionA 端 blockingbackend agent 接下來的工作)
| # | 項目 | 說明 |
|---|------|------|
| **B1最關鍵** | **object_key 斷層** | visionA model `StorageKey=models/{userID}/{modelID}.nef`visionA 自己 storage≠ FAA object_key。**上傳類 modelSourceUploaded根本不在 FAA → 第一階段 (a) 只支援轉檔→promote 類 model**。`model.Model` 要加 `FAAObjectKey` 欄位 + promote 時寫入。download flow 用 `FAAObjectKey`(非 `StorageKey`)組 FAA URL + Issue token |
| **B2** | **visionA 加 MC client code** | 加「打 MC `/oauth/token` + 打 MC Issue 簽 fdt token」的 client code**逆轉 ADR-016 砍除的 mc_token_client但更單純不自簽、token 由 MC 簽**+ .env 設定client `4242ba63...` / MC base url / FAA base url / tenant `732270c0...`|
| **D連帶** | **promote 401 可一併驗證** | `4242ba63...``files:upload.write` → promote 卡的 401 **很可能同類問題**converter 用的 client 沒 `upload.write`)。標為「可一併驗證」 |
**FAA download endpoint 確切用法(給 backend 實作)**
- `GET {FAA}/files/{objectKey}`token 放 `Authorization: Bearer {fdt_token}`FAA `TryReadAccessToken` **只認 Authorization Bearer**,不認 query / 自訂 header
- FAA 自己用 `IDelegatedDownloadTokenValidator`=`MemberCenterDelegatedDownloadTokenValidator`)拿 token 打 MC validate`instanceOptions.TenantId` + `ObjectKey`(從 URL path 取)+ `Method=GET`
- `objectKey` 必須與簽 token 時的 `object_key` **完全一致**FAA validate boundary 檢查、不一致回 `object_key_mismatch`)。