完善(08-09-deployment-contract-single-source-harness): 收敛部署合同并补失败路径

这个提交包含在:
2026-08-09 14:40:28 +08:00
父节点 099f58cf3b
当前提交 621934a35e
共修改 13 个文件,包含 648 行新增和 27 行删除
@@ -259,6 +259,95 @@ SYNC_ENV=0 POSTGRES_DATA_DIR=/srv/hyb-oa/pgdata \
bash deploy/trialrun/deploy.sh
```
## Scenario: Candidate Build Identity Single Source
### 1. Scope / Trigger
- Trigger: changing candidate provenance, `deploy.sh`, production preflight or
Compose build-SHA wiring.
- Purpose: prevent the full commit, its 12-character public prefix and the two
Compose environment keys from drifting between deployment entry points.
### 2. Signatures
- Shared function:
`deriveCandidateBuildEnv(commit) -> Object.freeze({ BUILD_SHA, VITE_BUILD_SHA })`.
- Provenance CLI keeps `commit` and `shortCommit` and adds the same `buildEnv`
object.
- Consumers: `deploy/trialrun/deploy.sh` and
`deploy/trialrun/production-preflight.mjs`.
### 3. Contracts
- The shared function accepts only a lowercase 40–64 character hexadecimal
commit and returns both environment values in one object;
`VITE_BUILD_SHA` is exactly the first 12 characters of `BUILD_SHA`.
- Consumers traverse and inject the complete object. They must not call
`git rev-parse HEAD`, slice the commit again, define an independent key list
or selectively consume one build key.
- `production-preflight-remote.sh` remains an independent consumption guard:
before `docker compose config` and Prisma status, it requires both keys,
validates both formats and checks the prefix relationship. A single local
source does not replace transport and caller validation.
- Preflight writes the stable redacted summary and report reference before it
classifies a remote failure. A non-zero remote exit takes precedence over a
missing Prisma marker.
### 4. Validation & Error Matrix
- Invalid candidate commit -> `CANDIDATE_COMMIT_INVALID` locally; no SSH.
- Missing or malformed remote build value -> `BUILD_SHA_MISSING`,
`PROD_TARGET_BUILD_SHA_INVALID` or `PROD_TARGET_VITE_BUILD_SHA_INVALID`;
no Compose/Prisma call.
- Valid values with a mismatched prefix -> `PROD_TARGET_BUILD_SHA_MISMATCH`;
no Compose/Prisma call.
- Compose config failure -> `PROD_TARGET_COMPOSE_CONFIG_INVALID`; no Prisma
status or migration command.
- Remote non-zero with `code=...` -> propagate that code even without a Prisma
marker or with an unreadable marker body; remote non-zero without a parseable code ->
`PRODUCTION_PREFLIGHT_REMOTE_FAILED`.
- Remote zero without a Prisma marker -> `PRISMA_STATUS_OUTPUT_MISSING`.
### 5. Good/Base/Bad Cases
- Good: provenance derives one frozen object; both entry points inject all of
it; the remote helper independently validates what arrived.
- Base: the existing provenance CLI fields remain compatible and equal the two
object values.
- Bad: preflight uses `candidateCommit.slice(0, 12)`, or deployment reads only
one property and reconstructs the other.
- Bad: check the Prisma marker before a non-zero remote status and thereby hide
the remote safety code.
### 6. Tests Required
- Unit-test fixed keys, immutability, the 12-character relationship and invalid
commit inputs.
- Execute the real production-preflight Node subprocess behind fake SSH for all
four status/marker quadrants: non-zero with code and no marker, non-zero with
code and marker, non-zero without code, and zero without marker. Assert the
stable summary and report reference exist on every failure.
- Run the remote helper behind an allowlisted fake Docker and prove build-value
and Compose failures occur before any Prisma `exec` or status call.
- Mutation-check both historical defects: reversing status/marker precedence
and removing either shared build key must make the relevant tests fail.
### 7. Wrong vs Correct
#### Wrong
```js
const buildSha = candidateCommit;
const viteBuildSha = candidateCommit.slice(0, 12);
```
#### Correct
```js
const candidateBuildEnv = deriveCandidateBuildEnv(candidateCommit);
const assignments = Object.entries(candidateBuildEnv);
```
## Scenario: Public Smoke Navigation Labels
### 1. Scope / Trigger