fix(compose): Phase 0.8b deploy blocker — env 透傳 + 命名規格

d8a9517 commit 漏改 docker-compose.yml:scheduler service environment block
沒透傳 Phase 0.8b 新 env、即使 stage .env 設了 container 也讀不到、
deploy 後 CONVERTER_API_KEY undefined 會啟動 503 reject all requests。

docker-compose.yml:
- 新增 10 個 Phase 0.8b env 透傳(CONVERTER_API_KEY 無 default fail-secure、
  其他用 ${VAR:-default} fail-soft)
- 砍 9 個已廢 OAuth resource-server env(MEMBER_CENTER_ISSUER / JWKS_URL /
  AUDIENCE / CONVERTER_TENANT_ID / SCOPE_* / JWKS_* / JWT_*)
- 保留 8 個 promote → FAA 用 env(MEMBER_CENTER_TOKEN_URL /
  KNERON_CONVERTER_CLIENT_ID/SECRET / FILE_ACCESS_AGENT_* /
  OAUTH_TOKEN_* / PROMOTE_TIMEOUT_MS)

docs/autoflow/04-architecture/api/api-result.md §16:
- 新增 Env Naming Reference Table(30 個 canonical env names)
- 拍板 source code 為 single source of truth、env.example 對齊
- 確認 /result 8 個 env + 其他 22 個的命名規格
- 留歷史記錄:Orchestrator 之前用過想像中縮寫名(_MAX / _HOURLY_QUOTA /
  RESULT_CONCURRENT_STREAM_MAX)造成命名混亂、§16 為未來 prompt 引用標準

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
jim800121chen 2026-05-18 01:01:59 +08:00
parent d8a9517c9d
commit aeaecb8c06
2 changed files with 128 additions and 15 deletions

View File

@ -72,30 +72,28 @@ services:
- MINIO_REGION=${MINIO_REGION:-us-east-1}
- MINIO_LIFECYCLE_DAYS=${MINIO_LIFECYCLE_DAYS:-7}
# === OAuth / Member Center必填缺漏 fail-fast===
- MEMBER_CENTER_ISSUER=${MEMBER_CENTER_ISSUER}
- MEMBER_CENTER_JWKS_URL=${MEMBER_CENTER_JWKS_URL}
# === Phase 0.8b A2visionA → converter API key 認證1:1 internal trust===
# 不能給 default — 必須使用者明示設置;未設時 apiKeyMiddleware 對外 API 一律回 503。
# 兩端converter / visionA值必須完全一致rotate 時雙端同時更新。
- CONVERTER_API_KEY=${CONVERTER_API_KEY}
# === Phase 0.8b A7trust proxyaudit log forensic source_ip===
# stage Nginx 1 hop → 1prod cloud LB + Nginx → 2明確 CIDR 也支援
- TRUST_PROXY=${TRUST_PROXY:-loopback}
# === Member Center Token Endpointconverter → FAA OAuth client 用,保留)===
# visionA → converter 已改 API key 不再走 OAuth/JWKS但 converter promote
# 階段仍以 client_credentials grant 跟 Member Center 取 token、再 PUT 到 FAA。
- MEMBER_CENTER_TOKEN_URL=${MEMBER_CENTER_TOKEN_URL}
# === Converter 身份(必填)===
- KNERON_CONVERTER_AUDIENCE=${KNERON_CONVERTER_AUDIENCE}
# === Converter OAuth Client 身份converter → FAA 用,必填)===
- KNERON_CONVERTER_CLIENT_ID=${KNERON_CONVERTER_CLIENT_ID}
- KNERON_CONVERTER_CLIENT_SECRET=${KNERON_CONVERTER_CLIENT_SECRET}
- CONVERTER_TENANT_ID=${CONVERTER_TENANT_ID:-}
# === File Access Agent必填===
- FILE_ACCESS_AGENT_BASE_URL=${FILE_ACCESS_AGENT_BASE_URL}
- FILE_ACCESS_AGENT_AUDIENCE=${FILE_ACCESS_AGENT_AUDIENCE}
# === Scope可選預設 TDD §8===
- CONVERTER_SCOPE_WRITE=${CONVERTER_SCOPE_WRITE:-converter:job.write}
- CONVERTER_SCOPE_READ=${CONVERTER_SCOPE_READ:-converter:job.read}
# === JWKS / JWT cache 行為(可選)===
- JWKS_CACHE_MAX_AGE_MS=${JWKS_CACHE_MAX_AGE_MS:-600000}
- JWKS_COOLDOWN_MS=${JWKS_COOLDOWN_MS:-30000}
- JWT_CLOCK_TOLERANCE_SEC=${JWT_CLOCK_TOLERANCE_SEC:-60}
# === OAuth Client cache可選===
- OAUTH_TOKEN_REFRESH_SKEW_MS=${OAUTH_TOKEN_REFRESH_SKEW_MS:-60000}
- OAUTH_TOKEN_TIMEOUT_MS=${OAUTH_TOKEN_TIMEOUT_MS:-10000}
@ -111,6 +109,22 @@ services:
# === Upload concurrencyT10 修 D5===
- MAX_CONCURRENT_UPLOADS=${MAX_CONCURRENT_UPLOADS:-5}
- UPLOAD_RETRY_AFTER_SECONDS=${UPLOAD_RETRY_AFTER_SECONDS:-30}
# === Phase 0.8b Phase B/result rate limitper token_fingerprint===
# 命名以 §16.2.1 canonical names 為準_PER_MIN + _WINDOW_MS 配對、不用 _MAX
- RESULT_RATE_LIMIT_BURST_PER_10S=${RESULT_RATE_LIMIT_BURST_PER_10S:-5}
- RESULT_RATE_LIMIT_BURST_WINDOW_MS=${RESULT_RATE_LIMIT_BURST_WINDOW_MS:-10000}
- RESULT_RATE_LIMIT_SUSTAINED_PER_MIN=${RESULT_RATE_LIMIT_SUSTAINED_PER_MIN:-20}
- RESULT_RATE_LIMIT_SUSTAINED_WINDOW_MS=${RESULT_RATE_LIMIT_SUSTAINED_WINDOW_MS:-60000}
# === Phase 0.8b Phase B/result bandwidth quota保護 mass download===
# 1 GiB = 1073741824 / 6 GiB = 6442450944
- RESULT_BANDWIDTH_QUOTA_PER_HOUR_BYTES=${RESULT_BANDWIDTH_QUOTA_PER_HOUR_BYTES:-1073741824}
- RESULT_BANDWIDTH_QUOTA_PER_DAY_BYTES=${RESULT_BANDWIDTH_QUOTA_PER_DAY_BYTES:-6442450944}
# === Phase 0.8b Phase B/result concurrent stream cap + stream timeout ===
- MAX_CONCURRENT_RESULT_STREAMS=${MAX_CONCURRENT_RESULT_STREAMS:-10}
- RESULT_STREAM_TIMEOUT_MS=${RESULT_STREAM_TIMEOUT_MS:-300000}
restart: unless-stopped
# ---------- Workers (stub mode) ----------

View File

@ -1511,3 +1511,102 @@ function createResultStreamConcurrencyLimiter({ maxConcurrent, retryAfterSeconds
| R10 | OpenAPI spec 更新含 429 / 503 等新 status code | 看 `docs/openapi.yaml` |
| R11 | 6 個新 integration testIT-2 到 IT-7全寫 + 4xx 系列原 test 保留 | grep test 檔 |
| R12 | 6 個新 env 在 README / deploy doc 有文件化 | 看 README + `.env.example` |
---
## 16. Env Naming Reference Table**Architect 2026-05-18 拍板、避免三方命名漂移**
### 16.1 為什麼這節存在
Phase 0.8b Phase B deploy 時 DevOps 發現 Orchestrator prompt、source code、`env.example` §17 三方 env 命名不一致Orchestrator 寫 `RESULT_RATE_LIMIT_SUSTAINED_MAX`、source code 讀 `RESULT_RATE_LIMIT_SUSTAINED_PER_MIN` + 額外 `_WINDOW_MS`)。此節提供**權威 canonical 命名清單**,後續任何 deployment 設定、docker-compose env 注入、`.env*` 模板生成、Orchestrator 派任務、文件互引,全部以本節為準。
**拍板原則**:以 source code 實際讀的命名為準(改 code 風險 > 改 deployment。本表已對照 `apps/task-scheduler/src/routes/v1/index.js` L116-169、`apps/task-scheduler/src/routes/v1/result.js` L60、`apps/task-scheduler/src/config.js` L112-296、`apps/task-scheduler/src/services/jobService.js` L69、`apps/task-scheduler/src/storage/{local,minio}.js``apps/task-scheduler/src/auth/apiKeyMiddleware.js``apps/task-scheduler/env.example` 三方確認。
### 16.2 Canonical 命名清單(全部 task-scheduler 讀的 env
#### 16.2.1 `/result` 端點專屬Phase 0.8b Phase B 新增、本檔 spec 範圍)
| Canonical env name | Required? | Default | Purpose | Source code reads |
|--------------------|-----------|---------|---------|-------------------|
| `RESULT_STREAM_TIMEOUT_MS` | optional | `300000`5 min| AC-7 stream response timeout§14.3 / §15.2| `src/routes/v1/result.js` L60`getStreamTimeoutMs` lazy 讀)|
| `MAX_CONCURRENT_RESULT_STREAMS` | optional | `10` | AC-4 concurrent stream cap§14.1 / §15.3per-instance counter超限 503 + `Retry-After: 30` | `src/routes/v1/index.js` L128`parseEnvInt` 解析後傳給 `createResultStreamConcurrencyLimiter`|
| `RESULT_RATE_LIMIT_BURST_PER_10S` | optional | `5` | AC-2 burst rate limit `max`§9.2 / §14.1per `token_fingerprint` | `src/routes/v1/index.js` L160 |
| `RESULT_RATE_LIMIT_BURST_WINDOW_MS` | optional | `10000`10 s| AC-2 burst rate limit `windowMs`;與 `_PER_10S` 成對 | `src/routes/v1/index.js` L159 |
| `RESULT_RATE_LIMIT_SUSTAINED_PER_MIN` | optional | `20` | AC-2 sustained rate limit `max`§9.2 / §14.1per `token_fingerprint` | `src/routes/v1/index.js` L169 |
| `RESULT_RATE_LIMIT_SUSTAINED_WINDOW_MS` | optional | `60000`1 min| AC-2 sustained rate limit `windowMs`;與 `_PER_MIN` 成對 | `src/routes/v1/index.js` L167 |
| `RESULT_BANDWIDTH_QUOTA_PER_HOUR_BYTES` | optional | `1073741824`1 GB| AC-3 hourly bandwidth quota§9.4 / §14.1per `token_fingerprint` | `src/routes/v1/index.js` L116 |
| `RESULT_BANDWIDTH_QUOTA_PER_DAY_BYTES` | optional | `6442450944`6 GB| AC-3 daily bandwidth quotaper `token_fingerprint` | `src/routes/v1/index.js` L119 |
**共 8 個 `RESULT_*` / `MAX_CONCURRENT_RESULT_STREAMS`。所有 8 個皆 optional無設值會走 source code 內 fallback default。**
#### 16.2.2 其他 task-scheduler 讀的 env`/result` 專屬、列出避免後續混淆)
| Canonical env name | Required? | Default | Purpose | Source code reads |
|--------------------|-----------|---------|---------|-------------------|
| `CONVERTER_API_KEY` | optionalwarn-only| `''`(空字串)| visionA → converter 對外 API 認證 pre-shared key未設時 `apiKeyMiddleware` 一律回 503 `service_unavailable` | `src/config.js` L167 + `src/auth/apiKeyMiddleware.js` L234 |
| `TRUST_PROXY` | optional | `'loopback'` | Express `app.set('trust proxy', ...)`;影響 `req.ip` 與 audit log `source_ip`接受值boolean / integer / 字串 keyword / CIDR| `src/config.js` L145 + `src/app.js` |
| `PORT` | optional | `4000` | HTTP listen port | server entry未經 config.js|
| `NODE_ENV` | optional | `'development'` | 影響 FAA URL 強制 HTTPS 等行為 | `src/config.js` L212 |
| `LOG_LEVEL` | optional | `'info'` | log 等級 | server entry未經 config.js|
| `REDIS_URL` | optional | `'redis://localhost:6379'` | Redis 連線字串 | `src/redis.js` L24 |
| `FRONTEND_URL` | optional | `'http://localhost:3000'` | CORS origin | `src/app.js` L59 |
| `JOB_DATA_DIR` | optional | `'/data/jobs'` | local storage 與 worker 共用 volume 路徑 | `src/services/jobService.js` L69、`src/storage/local.js` L23 |
| `STORAGE_BACKEND` | optional | `'local'` | `'local'` / `'minio'` | `src/app.js` L170、`src/storage/minio.js` L39、`src/routes/v1/jobs.js` L945 |
| `MINIO_ENDPOINT_URL` | conditional`STORAGE_BACKEND=minio` 必填)| `'http://192.168.0.130:9000'` | MinIO endpoint | `src/storage/minio.js` L40 |
| `MINIO_BUCKET` | conditional | `'convertet-working-space'` | MinIO bucket name | `src/storage/minio.js` L41 |
| `MINIO_ACCESS_KEY` | conditional | `'convuser'` | MinIO access key | `src/storage/minio.js` L42 |
| `MINIO_SECRET_KEY` | conditional | `''`(空字串)| MinIO secret key | `src/storage/minio.js` L43 |
| `MINIO_REGION` | conditional | `'us-east-1'` | MinIO region | `src/storage/minio.js` L44 |
| `MINIO_LIFECYCLE_DAYS` | optional | TBD 由 bucket lifecycle policy 設)| bucket lifecycle 天數orphan 清除 | env.example §6非 task-scheduler 讀、由 init script 用)|
| `MEMBER_CENTER_TOKEN_URL` | **required** | — | converter → FAA OAuth client token endpoint缺漏 → fail-fast| `src/config.js` L112 |
| `KNERON_CONVERTER_CLIENT_ID` | **required** | — | converter OAuth client_id缺漏 → fail-fast| `src/config.js` L116 |
| `KNERON_CONVERTER_CLIENT_SECRET` | **required** | — | converter OAuth client_secret缺漏 → fail-fast| `src/config.js` L117 |
| `FILE_ACCESS_AGENT_BASE_URL` | **required** | — | FAA base URLprod 強制 https | `src/config.js` L197 |
| `FILE_ACCESS_AGENT_AUDIENCE` | **required** | — | FAA OAuth audience | `src/config.js` L198 |
| `PROMOTE_TIMEOUT_MS` | optional | `300000`300 s| FAA PUT timeout | `src/config.js` L220 + `src/app.js` L125 |
| `OAUTH_TOKEN_REFRESH_SKEW_MS` | optional | `60000`60 s| OAuth token 距 expiresAt 還剩多少 ms 主動 refresh | `src/config.js` L225 |
| `OAUTH_TOKEN_TIMEOUT_MS` | optional | `10000`10 s| OAuth token endpoint timeout | `src/config.js` L228 |
| `MULTIPART_MODEL_MAX_BYTES` | optional | `524288000`500 MB| multer model file size 上限 | `src/config.js` L243 |
| `MULTIPART_REF_IMAGE_MAX_BYTES` | optional | `10485760`10 MB| 單張 ref_image 上限 | `src/config.js` L252 |
| `MULTIPART_REF_IMAGES_MAX_COUNT` | optional | `100` | ref_images 張數上限 | `src/config.js` L261 |
| `MAX_CONCURRENT_UPLOADS` | optional | `5` | 同時間最多進行幾個 upload超限 503 + `Retry-After` | `src/config.js` L285 |
| `UPLOAD_RETRY_AFTER_SECONDS` | optional | `30` | upload 超限的 `Retry-After` 秒數 | `src/config.js` L291 |
| `API_V1_RATE_LIMIT_WINDOW_MS` | optional | `300000`5 min| per-clientId rate limit window當前 clientId 寫死 `'visionA-service'` | env.example §15middleware 預設可被 deps 覆寫)|
| `API_V1_RATE_LIMIT_MAX` | optional | `300` | per-clientId rate limit max | env.example §15 |
### 16.3 命名規則約定(後續新增 env 必遵)
- **scope prefix**:與 `/result` 端點相關用 `RESULT_*`、與 upload 相關用 `UPLOAD_*` / `MULTIPART_*`、與 OAuth 相關用 `OAUTH_*` / `MEMBER_CENTER_*` / `KNERON_CONVERTER_*`、與 FAA 相關用 `FILE_ACCESS_AGENT_*`、與 MinIO 相關用 `MINIO_*`、與 API key 認證相關用 `CONVERTER_API_KEY`
- **單位後綴**:時間 = `_MS` / `_SECONDS`;空間 = `_BYTES`count = `_PER_<時間單位>` / `_MAX_COUNT`(不混用)
- **rate limit 命名**`_PER_<period>` 表單位時間最大次數;`_WINDOW_MS` 表 sliding window 大小;兩者成對出現(`_PER_10S` + `_BURST_WINDOW_MS=10000` 不算違規、單位來自 window
- **bandwidth quota 命名**`_QUOTA_PER_<period>_BYTES`period 用單字(`HOUR` / `DAY`、非 `1H` / `24H`
- **concurrent cap 命名**`MAX_CONCURRENT_<scope>``<scope>_CONCURRENT_MAX`(本檔已存在 `MAX_CONCURRENT_UPLOADS` + `MAX_CONCURRENT_RESULT_STREAMS`、不再變動)
### 16.4 已知非標準命名(接受、不要 rename
以下命名與 §16.3 規則不完全一致、但已散佈在 source code + tests + docs、rename 成本高、且語意清楚、**保留不變**
- `MAX_CONCURRENT_RESULT_STREAMS`(理論上應為 `RESULT_CONCURRENT_STREAM_MAX`、但 `MAX_CONCURRENT_UPLOADS` 已用此 pattern、保持一致
- `MAX_CONCURRENT_UPLOADS`(同上)
- `KNERON_CONVERTER_CLIENT_ID` / `_SECRET`(理論上應為 `OAUTH_CONVERTER_*`、但這是 converter 在 OAuth 體系中的身份名、有歷史脈絡)
- `MEMBER_CENTER_TOKEN_URL`service-name prefix 而非 scope prefix、但語意明確
未來新增 env 必依 §16.3、不再增加例外。
### 16.5 此節維護者責任
- **Architect**:拍板命名 + 維護本表Backend 想新增 env 必先過此表PR 改 `env.example` 時同步改本表)
- **Backend**source code 增減 `process.env.*` 讀取點時PR 必同步更新本表的 "Source code reads" 欄
- **DevOps**deployment / docker-compose env 注入以本表為唯一權威;不接受 source code 與本表不一致的部署設定
- **Orchestrator**:派任務給 Backend / DevOps 時env 命名必引用本表(不憑記憶寫)
### 16.6 三方對齊現況2026-05-18 deploy 時發現的不一致 + 解決狀態)
| 不一致案例deploy 時)| Orchestrator prompt 曾用過 | Source code 實際讀 | env.example §17 | 拍板 |
|----------------------|--------------------------|-------------------|-----------------|------|
| Sustained rate limit | `RESULT_RATE_LIMIT_SUSTAINED_MAX` | `RESULT_RATE_LIMIT_SUSTAINED_PER_MIN` + `_WINDOW_MS` | `RESULT_RATE_LIMIT_SUSTAINED_PER_MIN` + `_WINDOW_MS` | source code 為準(已對齊)|
| Bandwidth quota | `RESULT_BANDWIDTH_HOURLY_QUOTA` | `RESULT_BANDWIDTH_QUOTA_PER_HOUR_BYTES` + `_PER_DAY_BYTES` | `RESULT_BANDWIDTH_QUOTA_PER_HOUR_BYTES` + `_PER_DAY_BYTES` | source code 為準(已對齊)|
| Concurrent cap | `RESULT_CONCURRENT_STREAM_MAX` | `MAX_CONCURRENT_RESULT_STREAMS` | `MAX_CONCURRENT_RESULT_STREAMS` | source code 為準(已對齊)|
| Stream timeout | `RESULT_STREAM_TIMEOUT_MS` | `RESULT_STREAM_TIMEOUT_MS` | `RESULT_STREAM_TIMEOUT_MS` | 三方原本就一致 |
**結論**:實際 source code 與 `env.example` §17 已完全對齊(差異僅在 Orchestrator 派任務的 prompt 描述。Backend 與 DevOps 不需動 source code / docker-compose / env.exampleOrchestrator 後續派任務以本表為準即可。