124d3990b2
Keep async job and chart-cache files across API recreates, freeze jyotish_api_server.py growth, and fail fast with 429 when rectification or high-rigor compute is saturated. Co-authored-by: Cursor <cursoragent@cursor.com>
129 lines
6.3 KiB
Markdown
129 lines
6.3 KiB
Markdown
# PROGRESS · Python 引擎运行时治理(2026-09-01)
|
||
|
||
工作树:`.worktrees/engine-runtime-hygiene-20260901`
|
||
分支:`codex/engine-runtime-hygiene-20260901`
|
||
HEAD:`80e7736163be684119c03013a07980031468c634`(仍等于 `origin/staging`)
|
||
|
||
未提交、未推送。未改 `frontend/src/app/page.tsx`。未改 `.gitea/workflows/**`。未切分支、未 reset。迁移 squash 按任务书明确延后。
|
||
|
||
## 任务 1 · api 命名卷
|
||
|
||
- 容器 `WORKDIR`(`deploy/railway-api.Dockerfile`):`/app`
|
||
- 代码路径:`Path(REPO_ROOT) / 'scratch' / 'local' / ...` → 容器内 **`/app/scratch/local`**
|
||
- Compose 卷名:`api_scratch`
|
||
- 挂载:`api_scratch:/app/scratch/local`(`deploy/docker-compose.server.yml` 的 api 服务)
|
||
- 项目前缀后的 Docker 卷名:`jyotisha-staging_api_scratch` / `jyotisha-production_api_scratch`
|
||
- 向后兼容:首次 `docker compose up` 创建命名卷;当前 overlay 数据本就是易失的,无迁移
|
||
|
||
**备份结论(已写入 `deploy/README.md`)**
|
||
|
||
- Chart cache(`/app/scratch/local/api_chart_cache`,默认 TTL 900s)不值得备份;重建比归档便宜。
|
||
- Async / high-rigor job 态(`async_jobs` / `async_jobs.sqlite3` / `high_rigor_jobs`,默认 TTL 3600s)是短生命周期进行中工作,不是恢复单元。
|
||
- Staging 加密备份继续只走 `deploy/backup-staging-postgres.sh`。**不要**把 `api_scratch` 加进该脚本。命名卷的唯一目标是容器重建后缓存与进行中任务还在。
|
||
|
||
**Deploy 脚本:** `run-staging-deploy.sh` / `run-production-deploy.sh` **未改**。`docker compose up` 会创建并重新挂上命名卷;现有 `up -d --remove-orphans` 不会 `down -v`。
|
||
|
||
**Staging 实机三条验收(blocked)**:需要真正 dispatch 部署后才能 `docker volume ls`、重启 api 证明 `/app/scratch/local` 存活、核对 `/api/health`。Compose 挂载合同已锁在 `tests/test_railway_deployment.py`。
|
||
|
||
## 任务 2 · 冻结 `jyotish_api_server.py`
|
||
|
||
- 开工 live `wc -l`:**11063**(任务书 11035 已过时;出生精度合入后又涨了约 28 行)
|
||
- 本轮薄注册后:**11088**
|
||
- 合同上限:**11363**(11063 + 300 bugfix slack)
|
||
- 测试:`tests/test_api_server_growth_contract.py`(注释写明新功能必须开模块)
|
||
- 已列入 `scripts/run_quality_gate.py` `CORE_PYTEST_TARGETS`
|
||
- 根 `AGENTS.md` 第 8 节:`scripts/jyotish_api_server.py` must not grow;新端点/功能落 `scripts/` 模块,主文件只薄注册
|
||
|
||
## 任务 3 · 重计算并发闸
|
||
|
||
新模块 `scripts/api_heavy_compute_gate.py`。主文件只 import,并在 `do_POST` 里 acquire/release。`GET`(含 `/api/health` 与 job 轮询)不经过闸门。
|
||
|
||
- 环境变量 `JYOTISH_HEAVY_COMPUTE_CONCURRENCY`,默认 **2**(对齐 2 vCPU)
|
||
- `JYOTISH_HEAVY_COMPUTE_RETRY_AFTER_SECONDS`,默认 **2**
|
||
- 饱和:fail-fast,不排队。HTTP **429** + `Retry-After` + `error_code=ERR_COMPUTE_BUSY`
|
||
|
||
**入闸端点**
|
||
|
||
- `POST /api/rectification/sensitivity_scan`
|
||
- `POST /api/active_rectification_events`
|
||
- `POST /api/active_rectification_events_v4`
|
||
- `POST /api/rectification/v5/candidate-features`
|
||
- `POST /api/rectification/v5/score`
|
||
- `POST /api/rectification/v5/diagnostics`
|
||
- `POST /api/rectification/v5/vedastro-validate`
|
||
- `POST /api/dynamic_rectification_opportunities`
|
||
- `POST /api/dynamic_rectification_score`
|
||
- `POST /api/high_rigor_workflow`
|
||
- `POST /api/consultation_workflow`
|
||
- `POST /api/professional_reading`
|
||
- `POST /api/vedastro/range_scan`
|
||
- `POST /api/vedastro_gateway/run`
|
||
- `POST /api/vedastro_gateway/jobs/{id}/run`(前缀匹配)
|
||
- `POST /api/thematic_report`
|
||
|
||
**不入闸:** `GET /api/health`、城市/位置解析、job 轮询、`/api/chart`(已有 TTL 缓存 + `JYOTISH_ASYNC_JOB_*` 有界队列;再叠 429 会误伤常规排盘)、问卷、v5 versions、capability/catalog 等轻量路由。
|
||
|
||
## 验证命令与输出
|
||
|
||
```text
|
||
/opt/anaconda3/bin/python3.12 -m pytest \
|
||
tests/test_api_server_growth_contract.py \
|
||
tests/test_api_heavy_compute_gate.py -v
|
||
```
|
||
|
||
```text
|
||
collected 7 items
|
||
tests/test_api_server_growth_contract.py ... [ 42%]
|
||
tests/test_api_heavy_compute_gate.py .... [100%]
|
||
============================== 7 passed in 0.28s ===============================
|
||
```
|
||
|
||
饱和用例(limit=1):第二个重请求 **429** + `Retry-After: 2` + `ERR_COMPUTE_BUSY`;释放后 200;同期 `GET /api/health` 与 `POST /api/location/resolve` 仍 200。
|
||
|
||
```text
|
||
/opt/anaconda3/bin/python3.12 -m pytest tests/test_api_server_security.py -q
|
||
# 129 passed, ~90s(仅 datetime.utcnow DeprecationWarning)
|
||
|
||
/opt/anaconda3/bin/python3.12 -m pytest \
|
||
tests/test_runtime_security_p0.py \
|
||
tests/test_api_server_script_entrypoint.py -q
|
||
# 11 passed
|
||
|
||
/opt/anaconda3/bin/python3.12 -m pytest \
|
||
tests/test_api_async_job_contract.py \
|
||
tests/test_railway_deployment.py \
|
||
tests/test_api_server_security.py::test_health_endpoint_exposes_runtime_accuracy_metadata \
|
||
tests/test_api_server_security.py::test_vedastro_range_scan_endpoint_uses_user_birth_and_returns_controlled_blocked_state \
|
||
tests/test_api_server_security.py::test_professional_reading_composes_high_rigor_and_gateway \
|
||
tests/test_api_server_growth_contract.py \
|
||
tests/test_api_heavy_compute_gate.py -v
|
||
# 22 passed in 0.38s
|
||
```
|
||
|
||
`python3 scripts/pre_work_check.py --remote-timeout 8 --command-timeout 45` → `status=pass`。
|
||
|
||
未改前端,故未跑 `npm test` / tsc。未跑完整 `scripts/run_quality_gate.py --profile quick`(quick 仍会跑 Next.js test/lint/build;本轮前端豁免)。
|
||
|
||
## 改动文件
|
||
|
||
- `deploy/docker-compose.server.yml`
|
||
- `deploy/README.md`
|
||
- `AGENTS.md`
|
||
- `scripts/jyotish_api_server.py`(薄注册;+25 行)
|
||
- `scripts/api_heavy_compute_gate.py`(新)
|
||
- `scripts/run_quality_gate.py`
|
||
- `tests/test_api_server_growth_contract.py`(新)
|
||
- `tests/test_api_heavy_compute_gate.py`(新)
|
||
- `tests/test_railway_deployment.py`
|
||
- `PROGRESS-engine-runtime-hygiene-20260901.md`(本文件)
|
||
|
||
未改:`deploy/run-staging-deploy.sh`、`deploy/run-production-deploy.sh`、`deploy/backup-staging-postgres.sh`。
|
||
|
||
## 明确未做 / blocked
|
||
|
||
- Staging/production 实机挂卷、重启存活、`/api/health` 部署 SHA(需部署窗口)。
|
||
- 完整 quick gate(含 frontend runtime)。
|
||
- 迁移 squash。
|
||
- 把 `/api/chart` 同步路径送进这道闸。
|
||
- 提交 / 推送 / 提升 `main`。
|