修正CEO报销财务兜底与部署护栏(07-28-ceo-finance-fallback)
ci-e2e / e2e (push) Canceled after 0s
ci-minimal / check-minimal (push) Canceled after 0s

这个提交包含在:
2026-07-28 13:42:48 +08:00
父节点 37b625c5dc
当前提交 35948729cf
共修改 31 个文件,包含 980 行新增和 74 行删除
+49 -4
查看文件
@@ -608,9 +608,10 @@ curl -fsS "http://127.0.0.1:${SERVER_HOST_PORT}/ready"
### 1. Scope / Trigger
- Trigger: changing Compose identity, host data mounts, database publication,
deployment entrypoints or operational timers.
deployment entrypoints, deployment-time seeds or operational timers.
- Purpose: ensure one approved Compose project owns one verified DB/storage pair
without fixed global container names or accidental shared PGDATA.
without fixed global container names, source synchronization touching runtime
data, or deployment seeds overwriting approved production rules.
### 2. Signatures
@@ -618,7 +619,10 @@ curl -fsS "http://127.0.0.1:${SERVER_HOST_PORT}/ready"
- 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`.
`STORAGE_HOST_DIR`, absolute `REMOTE_DIR`, absolute `REMOTE_ENV_FILE`,
`DEPLOY_DATA_MODE=existing|initialize`.
- Deployment init includes `npm run seed:config`; approval-rule defaults are
inserted only when `approval_rules` is empty.
- Timer install inputs: `COMPOSE_PROJECT_NAME`, absolute
`POSTGRES_DATA_DIR`, absolute `STORAGE_HOST_DIR`, full 40–64 hex
`BUILD_SHA` and its exact 12-hex `VITE_BUILD_SHA` prefix.
@@ -637,6 +641,15 @@ curl -fsS "http://127.0.0.1:${SERVER_HOST_PORT}/ready"
- Relative defaults preserve local trialrun compatibility, but production must
use explicit absolute command inputs and verify them against active container
mounts before any start, backup or migration.
- `REMOTE_DIR` must not equal, contain or be contained by either production
data path. Validate this locally before provenance, SSH or `rsync --delete`.
If `REMOTE_ENV_FILE` is inside the synchronization tree, derive its actual
root-relative path and add explicit rsync protect/exclude filters; a fixed
default `.env` exclusion is insufficient.
- Deployment-time `seed:config` may create the three default approval rules for
an empty database. If any approval rule exists, preserve all existing rules;
production rule changes require their own approved transaction, drift checks
and audit rows.
- `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.
@@ -673,6 +686,12 @@ curl -fsS "http://127.0.0.1:${SERVER_HOST_PORT}/ready"
valid absolute paths remain installable.
- Formal deploy omits paths/mode, uses a relative/broad/overlapping path, or an
`existing` PGDATA lacks `PG_VERSION` -> fail before DB start.
- `REMOTE_DIR` overlaps PostgreSQL or attachment storage in either direction ->
fail before provenance, SSH and rsync.
- A custom `REMOTE_ENV_FILE` lies inside the sync tree without dynamic
protect/exclude filters -> deployment contract fails.
- `approval_rules` contains one or more rows -> `seed:config` reports them as
preserved and performs no approval-rule create, update or delete.
- 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
@@ -682,8 +701,11 @@ curl -fsS "http://127.0.0.1:${SERVER_HOST_PORT}/ready"
- `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.
name, invokes formal deploy with each data path nested below `REMOTE_DIR`, and
proves every unsafe shape is rejected before network access.
- `bash -n` covers deploy, timer, backup and restore scripts.
- Seed unit tests assert: an empty table receives exactly three defaults; any
non-empty table receives zero approval-rule writes and no delete call.
- Timer contract assertions cover all four explicit inputs, path isolation,
PGDATA marker, SHA/prefix validation and the four systemd `Environment=`
entries. On the deployment host, assert timer enabled/active, daily service
@@ -693,6 +715,9 @@ 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.
- Good: source lives under one absolute root, data lives under separate absolute
roots, a custom env file is protected, and existing approval rules survive
deployment init byte-for-byte until an approved rule transaction runs.
- Good: the timer unit contains the current project, active mount sources and
deployed full/short SHA pair; Compose config and a non-mutating server exec
resolve under those exact values while the daily service stays inactive.
@@ -704,6 +729,9 @@ curl -fsS "http://127.0.0.1:${SERVER_HOST_PORT}/ready"
the old container name alone is a rollback point.
- Bad: reinstall the timer with only `COMPOSE_PROJECT_NAME`, allowing systemd
Compose interpolation to fall back to repository-relative data directories.
- Bad: place PGDATA or attachments below `REMOTE_DIR` and rely on one hard-coded
rsync exclusion, or delete/recreate configurable approval rules in a routine
deployment seed.
### 7. Wrong vs Correct
@@ -742,6 +770,23 @@ VITE_BUILD_SHA=<verified-12-char-prefix> \
bash deploy/trialrun/install-warch3-ops-timer.sh install
```
For source synchronization and approval-rule defaults:
```bash
# Wrong: runtime data sits below the --delete synchronization root, and seed
# replaces operator-managed rules during every deployment.
REMOTE_DIR=/srv/oa POSTGRES_DATA_DIR=/srv/oa/data/postgres \
bash deploy/trialrun/deploy.sh
# seedApprovalRules: deleteMany -> createMany
# Correct: data roots are disjoint; an in-tree env file is dynamically
# protected; seed creates defaults only for an empty approval_rules table.
REMOTE_DIR=/srv/oa-app POSTGRES_DATA_DIR=/srv/oa-data/postgres \
STORAGE_HOST_DIR=/srv/oa-data/storage DEPLOY_DATA_MODE=existing \
bash deploy/trialrun/deploy.sh
# seedApprovalRules: count > 0 -> preserve; count = 0 -> create defaults
```
## Scenario: Paired Backup And Shadow-Only Restore
### 1. Scope / Trigger