# TDD 索引 — Kneron Model Converter 對外 API ## 作者:Architect Agent ## 狀態:Draft(Phase 0.8b 重寫 + 模組化) ## 最後更新:2026-05-16 > **auth 設計演進**:本 TDD 反映 Phase 0.8b 拍板後的「目標狀態」。完整歷史見 visionA repo `docs/autoflow/04-architecture/adr/adr-015-server-to-server-api-key.md` v2.1 + `adr-016-download-via-converter.md` v1.0。 > > **配套**:`design-doc.md`(架構決策)、`../02-prd/PRD.md`(需求)、`../03-design/design-review.md`(UX 回饋)。 --- ## 變更歷程 | 日期 | 變更 | 作者 | |------|------|------| | 2026-04-25 | 初版 Draft 1.0(OAuth resource server + promote) | Architect Agent | | 2026-04-25 | Multipart 上傳路徑改 visionA → converter 直傳;移除 FAA GET/HEAD | Architect Agent | | 2026-05-16 | **Phase 0.8b 重寫**:visionA → converter 改 API key;新增 `/result` endpoint;OAuth resource server 章節砍除;模組化拆分為索引 + 子檔案 | Architect Agent | --- ## 1. 文件結構 本 TDD 在 Phase 0.8b 重寫時拆分為模組化結構: | 檔案 | 內容 | 目標讀者 | |------|------|---------| | `TDD.md`(本檔,索引)| 各章節摘要 + 子檔案連結 | 全部 | | `auth.md` | API key middleware 設計 + 砍除 OAuth resource server 清單 + 保留 OAuth client | Backend | | `api/api-jobs.md` | `POST/GET /jobs`、`GET /jobs/:id` 規格 | Backend、Reviewer、Testing | | `api/api-promote.md` | `POST /jobs/:id/promote` 規格 | Backend、Reviewer、Testing | | `api/api-result.md` | **新增** `GET /jobs/:id/result` 規格 | Backend、Reviewer、Testing | | `database.md` | Redis schema + 索引 + Lua script | Backend | | `infra.md` | Nginx / docker-compose / .env 變動 + 部署順序 | Backend、DevOps | | `performance.md` | SLO + 延遲預算 + 負載測試 | Backend、Testing | | `observability.md` | Log 格式 + 敏感資料保護 + 告警 | Backend | | `security.md` | Trust boundary + Input validation + Auth security | 全部 | | `design-doc.md` | 架構決策 + ADR | 全部 | --- ## 2. Phase 0.8b 改動摘要 ### 2.1 對外 auth 改 API key - 砍 `auth/middleware.js`(OAuth resource server)+ `auth/jwks.js` - 加 `auth/apiKeyMiddleware.js` - 4 個既有 endpoint 改掛 `requireApiKey()` - 新加 `/result` endpoint 也用 `requireApiKey()` 詳見 `auth.md` §1 + §3(砍除清單)。 ### 2.2 新增 `/result` endpoint - `GET /api/v1/jobs/:id/result` - Streaming proxy NEF from MinIO → caller - 4 種 4xx + 2 種 5xx 情境 - 雙路徑 NEF key 解析(新格式 + 舊格式向後相容) - **2026-05-17 補充**:rate limit(60 req/min,獨立 bucket)、Range header 防護(silently ignore)、audit log 8 個 action、Backend `source_filename` 寫入 acceptance criteria(§9-§14) 詳見 `api/api-result.md`。 ### 2.3 保留不動 - Promote 流程(converter → FAA 仍走 OAuth client_credentials) - Redis schema(除確認 `source_filename` 欄位存在) - Worker、MinIO bucket、Nginx 結構 詳見 `auth.md` §2 + `api/api-promote.md`。 ### 2.4 Config 變動 - 移除:`MEMBER_CENTER_ISSUER` / `MEMBER_CENTER_JWKS_URL` / `KNERON_CONVERTER_AUDIENCE` / `JWKS_*` / `JWT_CLOCK_TOLERANCE_SEC` - 新增:`CONVERTER_API_KEY` - 保留:`MEMBER_CENTER_TOKEN_URL` / `KNERON_CONVERTER_CLIENT_*` / `FILE_ACCESS_AGENT_*` / `OAUTH_*` 詳見 `infra.md` §3。 --- ## 3. 系統概述 ### 3.1 角色 - **Converter(本專案)**:Node.js Task Scheduler + Python Worker - **visionA-backend**:Go 服務,Converter 對外 API 的**唯一** caller - **Member Center(MC)**:OAuth authorization server — Phase 0.8b 後**只**給 Converter → FAA promote 用 - **File Access Agent(FAA)**:NAS 邊界檔案閘道,single-tenant per instance ### 3.2 API 端點清單(Phase 0.8b 後) | 方法 | 路徑 | Auth | 說明 | 規格 | |------|------|------|------|------| | GET | `/health` | — | 健康檢查 | `api/api-jobs.md` §3 | | POST | `/api/v1/jobs` | API key | 建立 job | `api/api-jobs.md` §4 | | GET | `/api/v1/jobs` | API key | 列表 / Recovery | `api/api-jobs.md` §6 | | GET | `/api/v1/jobs/:id` | API key | 單一 job 狀態 | `api/api-jobs.md` §5 | | POST | `/api/v1/jobs/:id/promote` | API key | 搬檔到 FAA | `api/api-promote.md` | | GET | `/api/v1/jobs/:id/result` | API key | **NEW** stream NEF | `api/api-result.md` | | POST | `/api/v1/jobs/:id/download-tokens` | API key | Phase 2,回 501 | `api/api-jobs.md` §7 | | DELETE | `/api/v1/jobs/:id` | API key | Phase 2,回 501 | `api/api-jobs.md` §7 | ### 3.3 既有路徑(Phase 0.8b 不動) | 方法 | 路徑 | 用途 | |------|------|------| | POST | `/jobs` (multipart) | Web UI 既有上傳 | | GET | `/jobs/:id` | Web UI 狀態查詢 | | GET | `/jobs/:id/events` (SSE) | Web UI 進度 push | | GET | `/jobs/:id/download/:filename` | Web UI 下載 | | GET | `/queues/stats` | 內部監控 | 這些走 internal vhost,不對外、不加 auth。 --- ## 4. 技術堆疊(不變) | 層級 | 選擇 | |------|------| | 後端框架 | Node.js 18 + Express 4 | | 認證(對外)| API key(`crypto.timingSafeEqual`,Phase 0.8b 新)| | 認證(promote)| OAuth client_credentials(jose / 自寫 fetch)| | 資料庫 | Redis 7 | | 物件儲存 | MinIO(Converter Bucket) | | Worker | Python 3.10+ | | 反向代理 | Nginx | | 測試 | Jest | 詳見 `design-doc.md` §3.5。 --- ## 5. 專案結構 ``` apps/task-scheduler/ ├── server.js ← Entry ├── src/ │ ├── config.js ← 集中讀 env(Phase 0.8b 改) │ ├── redis.js ← Redis client │ ├── auth/ │ │ ├── apiKeyMiddleware.js ← 【新】Phase 0.8b │ │ ├── oauthClient.js ← 【保留】promote 用 │ │ ├── middleware.js ← 【砍】OAuth resource server │ │ └── jwks.js ← 【砍】 │ ├── fileAccessAgent/ │ │ ├── client.js ← FAA HTTP client(保留) │ │ └── errors.js ← 錯誤翻譯(保留) │ ├── routes/ │ │ ├── legacy.js ← 既有 /jobs/* 路由 │ │ └── v1/ │ │ ├── index.js ← v1 router 組裝(要改 wire result + 換 auth middleware) │ │ ├── jobs.js ← POST/GET(要改換 requireApiKey) │ │ ├── promote.js ← POST promote(要改換 requireApiKey) │ │ └── result.js ← 【新】Phase 0.8b │ ├── services/ │ │ ├── jobService.js ← Job CRUD │ │ └── doneListener.js ← Worker done event │ ├── middleware/ │ │ ├── errorHandler.js ← 統一錯誤 │ │ └── requestId.js │ └── utils/ │ └── logger.js ├── docs/openapi.yaml ← 要改 security scheme(OAuth → bearer/api_key) ├── .env.example ← 要改(見 infra.md §4) ├── README.md ← 要改 auth 章節 └── package.json ``` --- ## 6. 實作任務拆分(給 Backend) 按 Autoflow 增量式開發規範,每個任務 = 一個可獨立 review 的單位。 ### Phase A — API key middleware + auth 切換(取代 OAuth) | # | 任務 | 依賴 | 預估 | 驗收標準 | |---|------|------|------|---------| | A1 | 新建 `src/auth/apiKeyMiddleware.js` | — | 1d | unit test 全過:happy path、missing header、wrong key、constant-time、destroy socket、env 未設定 fail-fast | | A2 | 修 `src/config.js`:新增 `converter.apiKey`、移除 OAuth resource server 相關 env、保留 promote 相關 | — | 0.5d | config.test.js 過;啟動時 `CONVERTER_API_KEY` 未設只 warn(不 throw);OAuth resource server env 移除後 server 仍能啟動 | | A3 | 修 `src/routes/v1/index.js` / `jobs.js` / `promote.js`:`requireAuth(scope)` → `requireApiKey()` | A1, A2 | 0.5d | 既有 integration test 全過(401 行為改成 API key 模式驗);server 啟動正常 | | A4 | 砍 `src/auth/middleware.js` + `src/auth/jwks.js` + 相關 test | A3 | 0.5d | `git rm` + test runner 沒 broken import;search code base 沒有 reference 殘留 | | A5 | 修 `.env.example`、`docs/openapi.yaml`、`README.md`:移 OAuth resource server 段、加 `CONVERTER_API_KEY` | A4 | 0.5d | docs lint 過;OpenAPI security scheme 改 bearer / api_key | | A6 | Integration test:API key 驗證 4 個情境(happy / missing / wrong / 503) | A1-A5 | 1d | 全部過;既有 jobs / promote integration test 仍過 | **Phase A 總工時**:~4d ### Phase B — `/result` endpoint | # | 任務 | 依賴 | 預估 | 驗收標準 | |---|------|------|------|---------| | B1 | 確認 `jobService.createJob` 寫入 `source_filename` 欄位(檢查既有 code、補上若缺)| — | 0.5d | unit test 過;既有 job record 結構不破壞 | | B2 | 新建 `src/routes/v1/result.js`(含 `extractNefObjectKey`、`buildFilename`、stream handler)| B1, A1 | 1.5d | unit test 過:filename 各情境、雙路徑 key 解析、stream error / client close handling | | B3 | Wire `/result` 到 `src/routes/v1/index.js`(含 `requireApiKey` + per-client rate limiter)| B2 | 0.5d | server 啟動 + route table 正確;mergeParams 取 :id 通 | | B4 | Integration test:`/result` 8 個情境(200 happy / 401 / 404 job / 404 result / 409 / 410 expired / 410 minio miss / 502)| B2, B3 | 1d | 全部過 | **Phase B 總工時**:~3.5d ### 任務排程建議 **順序執行 A → B**(Backend 單人): - A1 + A2 可平行 - A3 等 A1 + A2 - A4 等 A3 - A5 等 A4 - A6 等 A5(整體 verify) - B1 + B2 可平行(B1 簡單,B2 是主要工作) - B3 等 B2 - B4 等 B3 預估總工時:~7.5 工作日(單人)。若可雙人並行,A 和 B 可分工,壓到 ~5d。 ### 與 visionA 端的 dependency | Backend 任務狀態 | visionA 端可以做什麼 | |---------------|------------------| | Phase A 完成、deploy stage | visionA 可以打 stage converter 的既有 endpoint 驗 API key 流程 | | Phase B 完成、deploy stage | visionA 可以打 `/result` endpoint 驗 streaming | | Phase A + B 都 deploy 完 | e2e 驗證(visionA repo commit 9e29ebf 已 ready) | --- ## 7. 測試策略 詳見 `performance.md` §7 + 各 `api/*.md` 的 test 章節。 ### 7.1 Unit test 覆蓋率目標 - `apiKeyMiddleware`:100%(少量 code、必須全 cover) - `result.js`:90% - 既有 OAuth-related 改動:維持 ≥ 85% ### 7.2 Integration test 必跑 - API key 4 情境(happy / missing / wrong / 503) - 既有 jobs / promote 在 API key 模式下仍過 - `/result` 8 情境(見 `api/api-result.md` §7.1) ### 7.3 Manual stage e2e(部署後) - curl 驗:`/health`、`POST /jobs`、`GET /jobs/:id`、`POST /promote`、`GET /result` - visionA 端 e2e:完整 upload → poll → promote → download --- ## 8. 安全注意事項 詳見 `security.md`。重點: - `CONVERTER_API_KEY` 不進 git / log / Slack - `constant-time compare`(防 timing attack) - Sec C1 暫緩(`.env` history rewrite + secret rotation 在 Phase 1 ready 後做、含 CONVERTER_API_KEY) - Trust boundary:visionA 一旦被 compromise 可冒充任意 user_id(接受、與 OAuth 模型一致) --- ## 9. 風險與待確認 | # | 風險 | 影響 | 行動 | |---|------|------|------| | R1 | CONVERTER_API_KEY rotation 流程未自動化 | 低 | Phase 1 接受手動 | | R2 | `/result` 高並發 stream 壓力 | 低 | NEF 通常小、visionA 是唯一 caller、QPS 可控 | | R3 | Sec C1 暫緩(.env 進 git history) | 中 | Phase 1 ready 收尾後 rewrite | | R4 | NEF 7 天過期後 client 重新轉檔 | 低 | API spec 已定義 410,visionA 端處理 | | R5 | Phase 0.8b 部署期間「OAuth → API key」短暫不可用 | 低 | 既有 stage OAuth 從未跑通、不會有 regression | --- ## 10. 後續步驟 1. 本 TDD 索引 + 子檔案送 PM / Design 三方互審 2. 使用者審核 3. Backend Agent 依 §6 的任務拆分增量開發 4. Reviewer 每個任務把關 5. Testing 整合測試 + e2e 6. DevOps 部署(converter 先 + visionA 後) --- ## 11. 變更記錄 | 日期 | 版本 | 變更 | 作者 | |------|------|------|------| | 2026-04-25 | Draft 1.0 | 初版,Phase 1 完整規格(單檔 1390 行) | Architect Agent | | 2026-04-25 | Draft 1.1 | Multipart 上傳路徑改 | Architect Agent | | 2026-05-16 | Draft 2.0 | **Phase 0.8b 重寫**:API key + /result + 模組化拆分為索引 + 8 個子檔案 | Architect Agent | --- **附註**:本 TDD 從 1390 行單檔重組為 ~180 行索引 + 8 個子檔案。每個子檔案 < 500 行(單一職責),可獨立給 Backend / Reviewer / Testing 不同角色讀對應檔案、減少 context 負擔。