加固部署隔离与备份来源契约(07-26-internal-ops-recovery-baseline)

这个提交包含在:
2026-07-27 06:13:02 +08:00
父节点 130518f471
当前提交 23342035ee
共修改 16 个文件,包含 304 行新增和 80 行删除
+81 -2
查看文件
@@ -592,6 +592,80 @@ SERVER_HOST_PORT="${SERVER_HOST_PORT##*:}"
curl -fsS "http://127.0.0.1:${SERVER_HOST_PORT}/ready"
```
## Scenario: Compose Project And Data-Path Isolation
### 1. Scope / Trigger
- Trigger: changing Compose identity, host data mounts, database publication,
deployment entrypoints or operational timers.
- Purpose: ensure one approved Compose project owns one verified DB/storage pair
without fixed global container names or accidental shared PGDATA.
### 2. Signatures
- Every deploy/timer Compose command uses `-p "$COMPOSE_PROJECT_NAME"`.
- PostgreSQL mount: `${POSTGRES_DATA_DIR:-./data/postgres}`.
- Application storage mount: `${STORAGE_HOST_DIR:-./data/storage}`.
- DB has no host `ports`; server remains loopback-only.
### 3. Contracts
- Compose files must not set `container_name`. Operational commands resolve
services through the approved project, never a historical global name.
- A project-name change and its DB/storage path selection are one atomic,
separately approved downtime operation. Never start two PostgreSQL instances
against one PGDATA.
- Relative defaults preserve local trialrun compatibility, but production must
verify the resolved host paths against the active container mounts before
any start, backup or migration.
- Remote readiness may use host curl, but build-SHA JSON validation runs with
Node inside the server container; the Docker host does not need Node.
### 4. Validation & Error Matrix
- DB `ports` appears -> environment contract fails.
- Any fixed `container_name` appears -> environment contract fails.
- Required data mount loses its parameter -> environment contract fails.
- Deploy/timer omits `-p` -> environment contract fails.
- New project resolves the same PGDATA while the old DB runs -> stop before
Compose start; do not use service health as permission to continue.
### 5. Tests Required
- `node deploy/trialrun/validate-env-contract.js`.
- The isolated backup/restore drill mutates Compose with a DB port and fixed
name and proves the contract rejects it.
- `bash -n` covers deploy, timer, backup and restore scripts.
### 6. Good/Base/Bad Cases
- Good: one approved project resolves one stopped-and-migrated PGDATA and the
matching active storage path; deploy, timer and backup all use that identity.
- Base: local trialrun uses the documented relative defaults under one project.
- Bad: start a renamed project before the old DB stops, or let it create a new
empty directory and accept a green health check as proof of data continuity.
### 7. Wrong vs Correct
#### Wrong
```bash
docker-compose up -d db
DB_CONTAINER=hyb-oa-trialrun-db ./scripts/backup-all.sh
```
#### Correct
```bash
docker-compose -p "$COMPOSE_PROJECT_NAME" -f deploy/trialrun/docker-compose.yml \
--env-file deploy/trialrun/.env up -d db
mapfile -t DB_CONTAINERS < <(docker-compose -p "$COMPOSE_PROJECT_NAME" \
-f deploy/trialrun/docker-compose.yml --env-file deploy/trialrun/.env ps -q db)
test "${#DB_CONTAINERS[@]}" -eq 1
DB_TOOL_MODE=docker DB_CONTAINER="${DB_CONTAINERS[0]}" \
STORAGE_DIR_HOST=<verified-active-storage> ./scripts/backup-all.sh
```
## Scenario: Paired Backup And Shadow-Only Restore
### 1. Scope / Trigger
@@ -603,7 +677,7 @@ curl -fsS "http://127.0.0.1:${SERVER_HOST_PORT}/ready"
### 2. Signatures
- Paired backup: `DB_TOOL_MODE=docker DB_CONTAINER=<name> ./scripts/backup-all.sh`.
- Paired backup: `DB_TOOL_MODE=docker DB_CONTAINER=<compose-resolved-id> STORAGE_DIR_HOST=<verified-active-directory> ./scripts/backup-all.sh`.
- Optional safe batch identifier: `BACKUP_RUN_ID=<safe-id>`.
- Manifest writer: host `node` is preferred; host `python3` is an equivalent
fallback for Docker-only runtime machines.
@@ -627,6 +701,10 @@ curl -fsS "http://127.0.0.1:${SERVER_HOST_PORT}/ready"
the running database. Confirm that the selected PostgreSQL container's database
and user match at least one active application `DATABASE_URL`, then pass the
verified runtime values without logging their contents.
- Docker backup/restore has no default container name. Resolve the unique DB
container with `compose -p <approved-project> ... ps -q db` and fail when the
result is empty or ambiguous. Storage backup likewise requires an explicit
`STORAGE_DIR_HOST`; it must never infer `STORAGE_DIR` or a repository default.
- The DB dump must pass `pg_restore --list` and the storage archive must pass
`tar -tzf` before the manifest may say `complete`.
- DB restore queries `current_database()` and compares it exactly with
@@ -704,6 +782,7 @@ source deploy/trialrun/.env
./scripts/qa/run-backup-restore-drill.sh
# Before an approved live backup, compare the selected DB container identity
# with an active app connection using redacted boolean diagnostics.
DB_TOOL_MODE=docker DB_CONTAINER=<verified-container> ./scripts/backup-all.sh
DB_TOOL_MODE=docker DB_CONTAINER=<verified-container> \
STORAGE_DIR_HOST=<verified-active-storage> ./scripts/backup-all.sh
# Real restore remains an approved incident operation after shadow evidence.
```