修正CEO报销财务兜底与部署护栏(07-28-ceo-finance-fallback)
这个提交包含在:
@@ -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
|
||||
|
||||
在新工单中引用
屏蔽一个用户