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:
parent
d8a9517c9d
commit
aeaecb8c06
@ -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 A2:visionA → converter API key 認證(1:1 internal trust)===
|
||||
# 不能給 default — 必須使用者明示設置;未設時 apiKeyMiddleware 對外 API 一律回 503。
|
||||
# 兩端(converter / visionA)值必須完全一致;rotate 時雙端同時更新。
|
||||
- CONVERTER_API_KEY=${CONVERTER_API_KEY}
|
||||
|
||||
# === Phase 0.8b A7:trust proxy(audit log forensic source_ip)===
|
||||
# stage Nginx 1 hop → 1;prod cloud LB + Nginx → 2;明確 CIDR 也支援
|
||||
- TRUST_PROXY=${TRUST_PROXY:-loopback}
|
||||
|
||||
# === Member Center Token Endpoint(converter → 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 concurrency(T10 修 D5)===
|
||||
- MAX_CONCURRENT_UPLOADS=${MAX_CONCURRENT_UPLOADS:-5}
|
||||
- UPLOAD_RETRY_AFTER_SECONDS=${UPLOAD_RETRY_AFTER_SECONDS:-30}
|
||||
|
||||
# === Phase 0.8b Phase B:/result rate limit(per 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) ----------
|
||||
|
||||
@ -1511,3 +1511,102 @@ function createResultStreamConcurrencyLimiter({ maxConcurrent, retryAfterSeconds
|
||||
| R10 | OpenAPI spec 更新含 429 / 503 等新 status code | 看 `docs/openapi.yaml` |
|
||||
| R11 | 6 個新 integration test(IT-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.3);per-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.1);per `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.1);per `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.1);per `token_fingerprint` | `src/routes/v1/index.js` L116 |
|
||||
| `RESULT_BANDWIDTH_QUOTA_PER_DAY_BYTES` | optional | `6442450944`(6 GB)| AC-3 daily bandwidth quota;per `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` | optional(warn-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 URL;prod 強制 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 §15(middleware 預設可被 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.example;Orchestrator 後續派任務以本表為準即可。
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user