jim800121chen d8a9517c9d feat(task-scheduler): Phase 0.8b — API key auth + /result endpoint
Auth pillar 從 OAuth 2.0 resource server 改成 pre-shared API key
(visionA ↔ converter 1:1 internal trust)。新增 GET /api/v1/jobs/:id/result
streaming endpoint 給 visionA backend 中轉 NEF 下載。

Phase A(auth 切換):
- 新增 apiKeyMiddleware(constant-time compare、tokenFingerprint、4 audit events)
- 砍 OAuth middleware + JWKS(保留 oauthClient 供 promote → FAA 使用)
- 4 個 endpoint 換掛 requireApiKey
- 加 TRUST_PROXY env + Express trust proxy 設定(forensic source_ip)

Phase B(/result endpoint):
- streaming NEF download with 5min timeout + concurrent cap 10
- Two-tier rate limit(burst 5/10s + sustained 20/min)
- Bandwidth quota(1 GB/hr + 6 GB/24hr)by token_fingerprint
- Range header silently ignored + Accept-Ranges: none
- filename quote-escape + RFC 5987 fallback + sanitize
- 8 個 /result audit events(forensic 完整)

設計演進記錄:docs/TODO-visionA-integration-v2.md(5/2 OAuth → 5/16 API key
→ 5/16 download via converter;對應 visionA repo ADR-015/016)

Tests: 597 → 666 (+69)、29 suites all pass
Security: APPROVE WITH CONDITIONS(單 instance 部署、6 新 env、24hr 監控)
npm audit: 3 vuln → 0(transitive AWS SDK xml chain)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 22:47:28 +08:00

13 KiB
Raw Blame History

TDD 索引 — Kneron Model Converter 對外 API

作者Architect Agent

狀態DraftPhase 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.mdUX 回饋)。


變更歷程

日期 變更 作者
2026-04-25 初版 Draft 1.0OAuth 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 endpointOAuth 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 /jobsGET /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.jsOAuth 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 limit60 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-backendGo 服務Converter 對外 API 的唯一 caller
  • Member CenterMCOAuth authorization server — Phase 0.8b 後給 Converter → FAA promote 用
  • File Access AgentFAANAS 邊界檔案閘道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 keycrypto.timingSafeEqualPhase 0.8b 新)
認證promote OAuth client_credentialsjose / 自寫 fetch
資料庫 Redis 7
物件儲存 MinIOConverter Bucket
Worker Python 3.10+
反向代理 Nginx
測試 Jest

詳見 design-doc.md §3.5。


5. 專案結構

apps/task-scheduler/
├── server.js                 ← Entry
├── src/
│   ├── config.js             ← 集中讀 envPhase 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 schemeOAuth → 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不 throwOAuth resource server env 移除後 server 仍能啟動
A3 src/routes/v1/index.js / jobs.js / promote.jsrequireAuth(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 importsearch code base 沒有 reference 殘留
A5 .env.exampledocs/openapi.yamlREADME.md:移 OAuth resource server 段、加 CONVERTER_API_KEY A4 0.5d docs lint 過OpenAPI security scheme 改 bearer / api_key
A6 Integration testAPI 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(含 extractNefObjectKeybuildFilename、stream handler B1, A1 1.5d unit test 過filename 各情境、雙路徑 key 解析、stream error / client close handling
B3 Wire /resultsrc/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 → BBackend 單人):

  • 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 覆蓋率目標

  • apiKeyMiddleware100%(少量 code、必須全 cover
  • result.js90%
  • 既有 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 驗:/healthPOST /jobsGET /jobs/:idPOST /promoteGET /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 boundaryvisionA 一旦被 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 已定義 410visionA 端處理
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 負擔。