补强生产数据路径与备份来源护栏(07-26-internal-ops-recovery-baseline)
ci-e2e / e2e (push) Waiting to run
ci-minimal / check-minimal (push) Waiting to run

这个提交包含在:
2026-07-27 06:37:47 +08:00
父节点 23342035ee
当前提交 64ed844bfe
共修改 14 个文件,包含 256 行新增和 34 行删除
+59 -12
查看文件
@@ -7,8 +7,7 @@ operations require explicit approval and evidence.
## Rules
- Use `SYNC_ENV=0 bash deploy/trialrun/deploy.sh` only when deployment is
approved; do not overwrite remote `.env`.
- Use `SYNC_ENV=0 POSTGRES_DATA_DIR=<absolute> STORAGE_HOST_DIR=<absolute> DEPLOY_DATA_MODE=<existing|initialize> bash deploy/trialrun/deploy.sh` only when deployment is approved; do not overwrite remote `.env`.
- When `server/src/config.js` reads a new environment key, add the same key to
`deploy/trialrun/.env.example` before deployment; `deploy/trialrun/deploy.sh`
runs `node deploy/trialrun/validate-env-contract.js` at step 0 and must fail
@@ -31,7 +30,8 @@ operations require explicit approval and evidence.
### 2. Signatures
- Command: `node deploy/trialrun/validate-env-contract.js`
- Deployment entry: `SYNC_ENV=0 bash deploy/trialrun/deploy.sh`
- Deployment entry requires `SYNC_ENV=0`, absolute `POSTGRES_DATA_DIR`, absolute
`STORAGE_HOST_DIR` and `DEPLOY_DATA_MODE=existing|initialize`.
- Contract sources:
- `server/src/config.js`
- `deploy/trialrun/.env.example`
@@ -47,6 +47,10 @@ operations require explicit approval and evidence.
allowlist in `validate-env-contract.js`.
- `SYNC_ENV=0` is the default public deploy mode; it does not overwrite remote
`.env`.
- Relative data-path defaults are allowed only for direct local Compose.
`deploy.sh` requires explicit absolute host paths and a data mode; `existing`
verifies the PGDATA directory contains `PG_VERSION`, while `initialize` is
the only mode allowed to create missing directories.
### 4. Validation & Error Matrix
@@ -54,6 +58,10 @@ operations require explicit approval and evidence.
- Service omits `env_file: ./.env` -> contract exits non-zero.
- Unauthorized compose override -> contract exits non-zero.
- Remote `.env` missing -> deploy step 4 exits before migration.
- Missing/relative/unsafe/overlapping data paths or missing data mode -> deploy
exits locally before provenance, synchronization or SSH.
- `existing` target missing `PG_VERSION` or storage directory -> remote deploy
exits before Compose starts DB.
### 5. Good/Base/Bad Cases
@@ -83,7 +91,9 @@ SYNC_ENV=0 bash deploy/trialrun/deploy.sh
```bash
# Add the key to deploy/trialrun/.env.example first.
node deploy/trialrun/validate-env-contract.js
SYNC_ENV=0 bash deploy/trialrun/deploy.sh
SYNC_ENV=0 POSTGRES_DATA_DIR=/srv/hyb-oa/pgdata \
STORAGE_HOST_DIR=/srv/hyb-oa/storage DEPLOY_DATA_MODE=existing \
bash deploy/trialrun/deploy.sh
```
## Scenario: Public Smoke Navigation Labels
@@ -279,7 +289,8 @@ const REQUIRED_ADMIN_NAV_KEYS = ["systemSettings"];
### 2. Signatures
- Provenance gate: `node deploy/trialrun/check-source-provenance.mjs`.
- Standard release: `SYNC_ENV=0 bash deploy/trialrun/deploy.sh`.
- Standard release additionally supplies the explicit absolute data-path and
data-mode contract required by `deploy.sh`.
- Build/runtime inputs: `BUILD_SHA=<full-commit>` and
`VITE_BUILD_SHA=<12-char-commit>`.
- Runtime outputs: `/health.data.buildSha`, `/ready.data.buildSha` and the
@@ -606,6 +617,8 @@ curl -fsS "http://127.0.0.1:${SERVER_HOST_PORT}/ready"
- 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}`.
- Formal deploy inputs: absolute `POSTGRES_DATA_DIR`, absolute
`STORAGE_HOST_DIR`, `DEPLOY_DATA_MODE=existing|initialize`.
- DB has no host `ports`; server remains loopback-only.
### 3. Contracts
@@ -615,11 +628,22 @@ curl -fsS "http://127.0.0.1:${SERVER_HOST_PORT}/ready"
- 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.
- Freeze the old project name from `docker compose ls`, not from a directory
guess. Record immutable image IDs/digests, successful migration count, fixed
business-table counts, resolved mounts and Caddy target before any write.
- 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.
use explicit absolute command inputs and verify them against active container
mounts before any start, backup or migration.
- `existing` is the normal migration mode and refuses a missing PGDATA marker
or storage directory. `initialize` is only for an approved empty first start;
only that mode may create data directories.
- 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.
- For an existing cutover, stop the old PostgreSQL completely, copy PGDATA and
storage into pre-verified empty absolute targets while preserving ownership,
permissions, links and extended attributes, and retain the old directories
untouched and unmounted for the observation period. Do not rename away the
only pre-migration rollback data and do not copy a running PGDATA.
### 4. Validation & Error Matrix
@@ -627,8 +651,12 @@ curl -fsS "http://127.0.0.1:${SERVER_HOST_PORT}/ready"
- Any fixed `container_name` appears -> environment contract fails.
- Required data mount loses its parameter -> environment contract fails.
- Deploy/timer omits `-p` -> environment contract fails.
- Formal deploy omits paths/mode, uses a relative/broad/overlapping path, or an
`existing` PGDATA lacks `PG_VERSION` -> fail before DB start.
- New project resolves the same PGDATA while the old DB runs -> stop before
Compose start; do not use service health as permission to continue.
- Old project name was inferred, immutable image ID or baseline counts are
missing, or target copy is non-empty -> stop the cutover.
### 5. Tests Required
@@ -641,9 +669,12 @@ curl -fsS "http://127.0.0.1:${SERVER_HOST_PORT}/ready"
- 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.
- Base: direct local Compose uses the documented relative defaults under one
project; those defaults are never accepted by formal `deploy.sh`.
- 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.
- Bad: move the only PGDATA into the new path, migrate it in place, then assume
the old container name alone is a rollback point.
### 7. Wrong vs Correct
@@ -677,7 +708,7 @@ DB_TOOL_MODE=docker DB_CONTAINER="${DB_CONTAINERS[0]}" \
### 2. Signatures
- Paired backup: `DB_TOOL_MODE=docker DB_CONTAINER=<compose-resolved-id> STORAGE_DIR_HOST=<verified-active-directory> ./scripts/backup-all.sh`.
- Paired backup: `DB_TOOL_MODE=docker DB_CONTAINER=<compose-resolved-id> STORAGE_DIR_HOST=<verified-active-directory> BUILD_SHA=<actual-build-sha> SOURCE_IMAGE_DIGEST=<actual-sha256-image-id> ./scripts/backup-all.sh`; at least one source identity is required.
- 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.
@@ -693,7 +724,11 @@ DB_TOOL_MODE=docker DB_CONTAINER="${DB_CONTAINERS[0]}" \
- A complete recovery point is one batch directory containing `database.dump`,
`storage.tar.gz` and a final `manifest.json`. The manifest contains only safe
relative names, byte sizes, SHA-256 hashes, batch time and optional build SHA.
relative names, byte sizes, SHA-256 hashes, batch time, actual build SHA when
known and actual source image digest when supplied.
- `BUILD_SHA` supplied by the caller takes precedence over local Git fallback.
On a non-Git runtime host, at least `SOURCE_IMAGE_DIGEST=sha256:<64-hex>` is
mandatory. Never label a legacy backup with the commit planned for deployment.
- Manifest generation uses exclusive creation and preserves the same schema
under Node and Python 3; if neither runtime exists, fail before publishing the
final batch directory.
@@ -718,6 +753,10 @@ DB_TOOL_MODE=docker DB_CONTAINER="${DB_CONTAINERS[0]}" \
people, rollback point and post-restore `/ready`/permission checks.
- Copy the whole completed batch to approved off-host/off-disk storage; a batch
left only on the application host is not accepted recovery evidence.
- A real-format shadow restore compares successful migration count and a fixed
set of business-table counts with the pre-backup baseline, compares extracted
regular-file count with the active storage source, and runs `/ready` using a
schema-compatible image. `/ready` alone is not recovery evidence.
### 4. Validation & Error Matrix
@@ -735,9 +774,14 @@ DB_TOOL_MODE=docker DB_CONTAINER="${DB_CONTAINERS[0]}" \
only when the explicit target container and required DB variables exist.
- Neither Node nor Python 3 is available -> fail manifest generation and remove
the temporary batch; do not publish a partial recovery point.
- Invalid build SHA/image digest, or neither identity available on a non-Git
host -> fail before DB or storage backup starts.
- Deployment `.env`, PostgreSQL container and active application connection do
not identify the same database/user -> stop the live backup; never guess
credentials or expose values in diagnostics.
- Shadow migration count, any fixed table count, attachment regular-file count
or compatible-image `/ready` differs from baseline -> reject the recovery
point and do not enter production cutover.
### 5. Good/Base/Bad Cases
@@ -745,6 +789,8 @@ DB_TOOL_MODE=docker DB_CONTAINER="${DB_CONTAINERS[0]}" \
host, and a disposable shadow drill restores the DB marker and attachment hash.
- Good: a Docker-only host uses Python 3 to write the same manifest contract
after the active application connection is matched to the database container.
- Good: a legacy container without `/ready.data.buildSha` records its inspected
`sha256:` image ID and leaves `buildSha` null rather than inventing provenance.
- Base: backup succeeds locally but off-host copy and deployed `/ready` evidence
remain explicitly pending; do not claim formal recovery readiness.
- Bad: source a stale deployment `.env` and treat a failed dump as a prompt to
@@ -759,7 +805,7 @@ DB_TOOL_MODE=docker DB_CONTAINER="${DB_CONTAINERS[0]}" \
- Negative assertions for wrong DB confirmation, corrupt DB/tar, non-empty
target and dangerous tar path.
- Validate manifest relative paths, absence of connection secrets and matching
SHA-256 values.
SHA-256 values, explicit build-SHA precedence and image-digest preservation.
- Run the synthetic drill with Node available and with a `PATH` that excludes
Node but includes Python 3; both paths must complete the same round trip.
- Real-instance restore and `/ready` remain separately approved evidence and
@@ -783,6 +829,7 @@ source deploy/trialrun/.env
# 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> \
STORAGE_DIR_HOST=<verified-active-storage> ./scripts/backup-all.sh
STORAGE_DIR_HOST=<verified-active-storage> \
SOURCE_IMAGE_DIGEST=<verified-sha256-image-id> ./scripts/backup-all.sh
# Real restore remains an approved incident operation after shadow evidence.
```