fix: 隔离既有发布的数据库容器生命周期(09-05-existing-deploy-db-lifecycle-isolation)
ci-e2e / e2e (push) Canceled after 0s
ci-minimal / check-minimal (push) Canceled after 0s

这个提交包含在:
2026-09-05 21:02:58 +08:00
父节点 05d2a3f78a
当前提交 f223bfb72f
共修改 11 个文件,包含 822 行新增和 7 行删除
@@ -19,6 +19,96 @@ operations require explicit approval and evidence.
- Do not execute real payments, import bank statements, connect payment
providers or write ERP without a separate approved Trellis task.
## Scenario: Existing Application Release Must Not Manage Database Containers
### 1. Scope / Trigger
- Applies to `deploy-existing.sh` → `deploy.sh` and direct `deploy.sh` execution.
- Separate application publication from database container lifecycle management;
this does not make migrations or seed operations read-only.
### 2. Signatures
- `DEPLOY_DATA_MODE=existing|initialize`; the existing wrapper pins `existing`.
- Only `initialize` may explicitly run Compose `up -d db`.
- One-shot init: `run --rm --no-deps -T --entrypoint sh init ...`.
- Application: explicit `build server`, then `up -d --no-build --no-deps server`;
optional worker and explicitly selected MinIO services also isolate dependencies.
- Database readiness: `timeout --signal=KILL 120 bash -s -- <compose arguments>`;
poll every 2 seconds with no environment override for the 120-second deadline.
### 3. Contracts
- Existing releases may query database state but must not start, stop, restart,
recreate, replace or remove db, explicitly or via dependency propagation.
A project-wide lifecycle operation is not safe merely because it omits `db`.
- Resolve exactly one container through the approved Compose project/service.
Inspect its running state and Docker health, replacing the `service_healthy`
startup guarantee otherwise lost when dependency startup is disabled.
- The deadline encloses both polling and potentially stuck Compose/inspect calls.
Missing, stopped, unhealthy, ambiguous or uninspectable db must fail; only
`running|starting` waits, and only `running|healthy` proceeds. No automatic
initialize fallback, restart, permission repair or health-bypass option.
- Initialize starts db explicitly, then passes the same health gate before init.
Reuse the gate after init build, after init and after server build, before each
application switch; a healthy snapshot before a long build is not sufficient.
Prepare worker before switching server: resolve its existing container through
Compose, inspect Config.Image and verify that image reference, not just a retained
image ID whose tag may be gone. Build explicitly when no container/reference is
available, then check health and start with `--no-build --no-deps`. A cached image
without a container may incur an extra cached build. This assumes the current
build-only worker definition; future image overrides need their own compatibility
review, rather than treating a previous container reference as new configuration.
Preserve migration/seed command content and order, safe logs, identity/path
preflights, rollback window, build markers and backup-contract refresh.
- Missing timeout tooling or unsupported isolation options fail closed. Do not
retry a command without isolation to accommodate an older Compose executable.
### 4. Validation & Error Matrix
- Missing/invalid mode → nonzero before container operations.
- Missing timeout tool → `db_health_timeout_unavailable`, no db start or init.
- Missing/non-unique db → `db_container_missing_or_ambiguous`.
- Query failure → `db_health_query_failed`; invalid/unhealthy state → `db_not_healthy`.
- Deadline → `db_health_timeout`, nonzero; never continue init/application switch.
- Init failure → preserve failure status and stop before application switch.
### 5. Good / Base / Bad Cases
- Good: existing healthy db → isolated init → isolated application switch;
captured effective lifecycle targets never include db.
- Base: initialize → explicit db start → bounded healthy check → isolated init.
- Bad: remove only the explicit `up db` but leave init `run` or application `up`
free to start dependencies; grep for one `--no-deps` cannot prove safety.
### 6. Tests Required
- Execute the actual remote shell from current source in a private temporary
fixture; capture argv and resolve effective service targets from the real
Compose dependency graph. Assert execution reaches init and expected services.
- Cover V1/V2, both modes, worker/MinIO profiles and failure cases; mutate explicit
db startup and each init/server/worker dependency isolation independently.
- Change db health after build/init/server-switch stages and prove the next
guarded action never runs; verify image builds cannot follow the last health
check implicitly inside application `up`.
- Cover a retained worker container/image ID with a missing launch tag; rebuild
must happen before the server switch, not fail later at isolated worker startup.
- Test the real deadline mechanism with a shortened deadline only in a temporary
source copy, including a hung inspect; never shorten the production constant.
- Fake binaries and whitelisted PATH/env exist only in test subprocesses. Unknown
commands fail, real Docker/SSH/production credentials remain inaccessible.
- Run `server/tests/unit/scripts/deployment-db-lifecycle.test.js` in static gates
and retain existing `deployment-safety-contract.test.js` coverage.
### 7. Wrong vs Correct
- Wrong: database unavailable → application deploy silently runs `up db`.
- Correct: database unavailable → stop and request separate fault recovery.
- First authorized real release needs an operator, pre/post db container identity
comparison and separate application-image/tool recovery references. Keep the
old tool source as evidence, not a ready-to-run command sequence that reintroduces
unapproved db lifecycle operations or replays migrations/seeds.
## Scenario: Exact Production Identity Bootstrap Before Migration Status
### 1. Scope / Trigger