visionA/docs/autoflow/04-architecture/api/api-model-sharing.md
jim800121chen f6d15b7b14 docs(arch): B 設備管理 + C 模型共享 + camera ADR-020 規劃文件
- feature-device-mgmt-tdd.md + api-device-mgmt.md(B 設備管理 TDD)
- feature-model-sharing-tdd.md + api-model-sharing.md + PRD feature + 設計規格(C 模型共享三方規劃)
- adr-020-ffmpeg-camera-indev.md(camera 三平台 indev)
- PRD.md / TDD.md 索引增補

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-02 16:31:38 +08:00

187 lines
8.6 KiB
Markdown
Raw Permalink 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.

# API Spec — 模型共享Model Sharing
> 對齊 `feature-model-sharing-tdd.md`。base URL / envelope / 認證同 `api-spec.md`§0
> 通用回應:`{ "success": true, "data": {...} }` / `{ "success": false, "error": { "code", "message" } }`。
> 認證cookie sessionBFF同 `api-spec.md`handler 從 `UserContext` 取 `userID` / `roles` / `orgId`。
>
> **狀態**Draft待三方互審。標 `[需 PM 確認]` / `[需 Design 確認]` / `[需 security 審]` 的點見各節。
---
## 0. 名詞與前提
| 名詞 | 定義 |
|------|------|
| **visibility** | 模型的「公開對象」維度(廣播式):`private`(僅擁有者)/ `tenant`(同租戶可見)/ `public`(全平台可見)。存在 `models.visibility` 欄。 |
| **model_shares** | 「點對點分享給特定人」維度ADR-017 決策 3 已定義,本功能沿用、不重造)。與 visibility 正交。 |
| **共享模型庫** | 「依身份權限的可見模型列表」= 我的 公開(public) 同租戶(tenant 且同 org) 分享給我(model_shares) preset。 |
| **tenant / org** | 沿用既有 `users.org_id`migration 0001 已有欄位、domain 尚未使用,見 TDD §2`tenant` visibility = 同 `org_id` 可見。`[需 PM 確認]` org 邊界語意。 |
> **與既有 `/api/models` 的分工**:既有 `GET /api/models``api-spec.md §4`)維持「**我的模型 + preset**」語意不變owner dashboard 用),**不動**。共享模型庫是**新端點** `GET /api/models/library`(跨擁有者、依權限可見)。兩者刻意分開,避免破壞既有 owner dashboard 行為。`[需 Design 確認]` UI 是否分兩個入口。
---
## 1. GET `/api/models/library` — 共享模型庫列表(依身份權限可見)
依當前 user 身份,回「可見模型」的分頁列表。可見範圍 = 我的 public (tenant 且同 org) 分享給我 preset。
### Query 參數
| 參數 | 型別 | 預設 | 說明 |
|------|------|------|------|
| `cursor` | string | — | 分頁游標(不透明 base64。首頁不帶。見 §1.3 分頁契約。 |
| `limit` | int | 20 | 每頁筆數1100超出 clamp。 |
| `sort` | enum | `created_at` | 排序欄位:`created_at` / `name` / `file_size`。 |
| `order` | enum | `desc` | `asc` / `desc`。 |
| `q` | string | — | 搜尋關鍵字(比對 `name` + `description`ILIKE 前綴/包含,見 TDD §5 效能)。 |
| `target_chip` | enum | — | filter`kl520`/`kl720`/`kl630`/`kl730`。 |
| `source` | enum | — | filter`uploaded`/`converted`/`preset`。 |
| `visibility` | enum | — | filter`public`/`tenant`(僅過濾廣播類;`private` 不在共享庫語意內、傳了視為忽略)。 |
| `owned` | bool | — | `true`=只看我的、`false`=只看別人分享/公開給我的;不帶=全部可見。 |
### Response 200envelope `data`
```json
{
"items": [
{
"id": "uuid",
"name": "yolov5s-kl520",
"description": "...",
"target_chip": "kl520",
"file_size": 10485760,
"source": "converted",
"status": "ready",
"visibility": "public",
"owner": { "id": "uuid", "name": "Alice", "is_me": false },
"shared_with_me": false,
"my_access": "viewer",
"created_at": "2026-07-01T00:00:00Z",
"updated_at": "2026-07-02T00:00:00Z"
}
],
"next_cursor": "eyJ...",
"has_more": true
}
```
**欄位說明(權限相關,重要)**
- `owner`:只揭露 `id` / `name` / `is_me`**不揭露 owner email**——非擁有者不該看到他人 email見 §4 安全)。`[需 security 審]`
- `my_access`:當前 user 對此模型的有效權限(`owner`/`editor`/`viewer`。由「owner_user_id 命中 / model_shares.role / visibility 落 viewer」計算取最高。
- `shared_with_me`:是否因 model_shares 命中(供 UI 標「已分享給我」)。`[需 Design 確認]` 標示方式。
- `status`:沿用既有 DTO`pending`/`ready``uploaded_at` 決定,見 `models.go toModelResponse`)。**共享庫只列 `ready`**(未 finalize 的不進共享庫,見 TDD §5
### 錯誤碼
| HTTP | code | 情境 |
|------|------|------|
| 400 | `VALIDATION_FAILED` | limit / cursor / sort 非法。 |
| 401 | middleware | 未登入。 |
| 500/503 | `INTERNAL_ERROR` | DB 錯誤(經 `WriteDBError` 映射,不洩漏 raw。 |
---
## 2. GET `/api/models/:id/profile` — 模型 profile 頁(公開版詳情)
非擁有者也可看的模型詳情頁。**權限檢查為核心**:只有「對此 model 有可見性」的 user 能看,且回傳欄位依身份裁剪。
### 可見性判斷handler 第一步)
user 能看此 profile ⟺ 命中任一:
1. `owner_user_id == userID`(我的)
2. `visibility == public`
3. `visibility == tenant` 且 model.owner.org_id == user.org_id
4. `model_shares` 命中grantee == userID
5. preset 模型(公用)
不命中 → **404**(不是 403不揭露「這個 id 存在但你沒權限」,避免 enumeration 洩漏存在性。`[需 security 審]` 404 vs 403 策略。
### Response 200envelope `data`
```json
{
"id": "uuid",
"name": "...",
"description": "...",
"target_chip": "kl520",
"file_size": 10485760,
"source": "converted",
"status": "ready",
"visibility": "public",
"input_shape": [1, 3, 224, 224],
"classes": ["cat", "dog"],
"framework": "onnx",
"owner": { "id": "uuid", "name": "Alice", "is_me": false },
"my_access": "viewer",
"can_download": true,
"created_at": "...",
"updated_at": "...",
"uploaded_at": "..."
}
```
**依身份裁剪的欄位(重要,`[需 security 審]`**
- **絕不揭露**(無論身份):`storage_key``faa_object_key`FAA 內部 keyADR-017 決策 2 防曝露、owner email、`file_checksum``[需 PM 確認]` checksum 是否對非 owner 公開)。
- `can_download``my_access != none` 時 true。前端據此決定是否顯示下載鈕。實際下載仍走既有 `GET /api/models/:id/download`(該端點需同步加共享權限檢查,見 §3
### 錯誤碼
| HTTP | code | 情境 |
|------|------|------|
| 404 | `NOT_FOUND` | model 不存在 **或** 無可見性(合併,防 enumeration。 |
---
## 3. PATCH `/api/models/:id/visibility` — 設定公開對象
擁有者設定模型的公開對象。**只有 owner或 editor`[需 PM 確認]`)能改**。
### Request
```json
{ "visibility": "public" }
```
- `visibility`required`private` / `tenant` / `public`
- `tenant` 但 user 無 `org_id` → 400無租戶歸屬不能設 tenant 可見)。`[需 PM 確認]` demo/無 org user 的行為。
### Response 200envelope `data`
```json
{ "id": "uuid", "visibility": "public", "updated_at": "..." }
```
### 錯誤碼
| HTTP | code | 情境 |
|------|------|------|
| 400 | `VALIDATION_FAILED` | visibility 非法 / 設 tenant 但無 org。 |
| 403 | `FORBIDDEN` | 非 owner。 |
| 404 | `NOT_FOUND` | model 不存在。 |
| 409 | `CONFLICT` | model 未 `ready`(未 finalize 不允許公開,`[需 PM 確認]`)。 |
> **既有 `GET /api/models/:id/download` 的連帶變更(見 TDD §5**:目前是 owner-only`m.OwnerUserID != userID → 403`)。共享後需改為「有可見性且 `can_download` → 放行」。此變更**涉及 auth 繞過風險**`[需 security 審]`。
---
## 4. 安全考量摘要(詳見 TDD §6
| 風險 | 對策 |
|------|------|
| IDOR / enumeration猜 id 看他人 model | profile / download 的可見性檢查在 handler 第一步;不命中回 404不回 403 揭露存在性)。`[需 security 審]` |
| 越權改 visibility | PATCH visibility 強制 owner 檢查。 |
| 資訊洩漏email / storage_key / faa_object_key | DTO 白名單序列化,內部 key 用 `json:"-"`(沿用 `model.Model.FAAObjectKey` 既有做法)。 |
| tenant 邊界誤放 | tenant 可見性需 `model.owner.org_id == user.org_id` 且兩者皆非空;空 org 不落 tenant 可見。 |
| 下載端點權限繞過 | `GET /api/models/:id/download` 的 owner-only 改共享權限檢查,須與 profile 可見性邏輯**共用同一函式**single source of truth避免兩處判斷不一致`[需 security 審]` |
---
## 5. 待三方確認清單(本檔)
| # | 項目 | 待誰 |
|---|------|------|
| S1 | org/tenant 邊界語意org_id 是否等於租戶;跨 org 是否可能) | PM |
| S2 | editor 是否能改 visibility / 未 ready 能否公開 / checksum 是否對非 owner 公開 | PM |
| S3 | 共享庫 UI 入口(與既有「我的模型」分頁 or 合併 tab`shared_with_me` 標示、profile 頁欄位 | Design |
| S4 | 404 vs 403 防 enumeration、download 端點權限改動、owner email 不揭露 | security |