沉淀生产收口规约与清理证据(07-15-production-trust-closeout-cleanup)
这个提交包含在:
@@ -169,3 +169,128 @@ if (acknowledgementCount > 0) {
|
||||
throw new AppError({ code: "VALID_001", message: "参数校验失败", statusCode: 409 });
|
||||
}
|
||||
```
|
||||
|
||||
## Scenario: Safe Development Seed
|
||||
|
||||
### 1. Scope / Trigger
|
||||
|
||||
- Trigger: changing or running `server/src/scripts/seed-dev.js`, its safety
|
||||
guard, development account defaults or the `seed:dev` npm command.
|
||||
|
||||
### 2. Signatures
|
||||
|
||||
- Command: `SEED_DEV_ALLOW=1 SEED_DEV_DATABASE_CONFIRM=<db> npm run seed:dev`.
|
||||
- Optional reset: `SEED_DEV_RESET_PASSWORDS=1`.
|
||||
- Target: `DATABASE_URL` parsed with the same `pg` semantics used by Prisma's
|
||||
PostgreSQL adapter.
|
||||
|
||||
### 3. Contracts
|
||||
|
||||
- `NODE_ENV=production` is permanently refused.
|
||||
- The explicit allow switch and exact database-name confirmation are both
|
||||
required.
|
||||
- The effective PostgreSQL host, including query-string overrides, must be
|
||||
loopback; checking only the URI authority is insufficient.
|
||||
- Existing passwords are preserved by default. Newly created accounts and
|
||||
explicitly rotated accounts set `must_change_password=true`.
|
||||
- The guard and seed logs never print credentials, complete targets or account
|
||||
passwords.
|
||||
|
||||
### 4. Validation & Error Matrix
|
||||
|
||||
- Production -> `production_forbidden`.
|
||||
- Missing allow switch -> `explicit_allow_required`.
|
||||
- Invalid/non-PostgreSQL URL -> `database_url_invalid` or
|
||||
`database_protocol_invalid`.
|
||||
- Effective non-loopback host, including `?host=...` ->
|
||||
`database_host_not_local`.
|
||||
- Missing/mismatched confirmation -> `database_confirmation_mismatch`.
|
||||
|
||||
### 5. Good/Base/Bad Cases
|
||||
|
||||
- Good: loopback test database, exact confirmation, new accounts require a
|
||||
password change.
|
||||
- Base: rerun without reset preserves existing password hashes.
|
||||
- Bad: accept `postgresql://[email protected]/db?host=remote.internal` because only
|
||||
`URL.hostname` was checked.
|
||||
|
||||
### 6. Tests Required
|
||||
|
||||
- Pure guard tests for production, missing allow, remote authority, host-query
|
||||
override, exact confirmation and redacted errors.
|
||||
- Unit tests for existing-password preservation, explicit rotation and new
|
||||
account first-login change.
|
||||
- Do not validate refusal paths by connecting to a real database.
|
||||
|
||||
### 7. Wrong vs Correct
|
||||
|
||||
#### Wrong
|
||||
|
||||
```js
|
||||
if (new URL(env.DATABASE_URL).hostname === "127.0.0.1") allowSeed();
|
||||
```
|
||||
|
||||
#### Correct
|
||||
|
||||
```js
|
||||
const client = new Client({ connectionString: env.DATABASE_URL });
|
||||
if (!LOOPBACK_HOSTS.has(client.host)) refuse("database_host_not_local");
|
||||
```
|
||||
|
||||
## Scenario: Database-Free App Contract Tests
|
||||
|
||||
### 1. Scope / Trigger
|
||||
|
||||
- Trigger: unit tests call the production `buildApp()` factory to verify health,
|
||||
readiness, static serving or other app-level contracts in CI without a DB.
|
||||
|
||||
### 2. Signatures
|
||||
|
||||
- Production: `buildApp()`.
|
||||
- Test seam: `buildApp({ prismaPlugin, startJobs })`.
|
||||
|
||||
### 3. Contracts
|
||||
|
||||
- Omitted dependencies always use the production Prisma plugin and job starter.
|
||||
- Unit tests inject a `fastify-plugin`-wrapped Prisma decorator and a no-op job
|
||||
starter; they must not rely on ESM `vi.mock` intercepting a CJS `require`.
|
||||
- App-level test doubles expose only methods used by the tested requests.
|
||||
- `DATABASE_URL='' JWT_SECRET=''` must not make isolated app tests access a
|
||||
developer `.env` or local PostgreSQL instance.
|
||||
|
||||
### 4. Validation & Error Matrix
|
||||
|
||||
- Production call with missing DB target -> normal startup refusal.
|
||||
- Test call with injected Prisma plugin -> no production client construction.
|
||||
- Test double missing a method exercised by the request -> focused unit failure.
|
||||
- Injected dependencies leaking into `buildApp()` callers without options ->
|
||||
contract regression.
|
||||
|
||||
### 5. Good/Base/Bad Cases
|
||||
|
||||
- Good: health/static tests inject Prisma and pass with empty DB/JWT process
|
||||
variables after setting their own test JWT.
|
||||
- Base: API/Worker call `buildApp()` without options and retain production wiring.
|
||||
- Bad: a hoisted `vi.mock("../prismaClient")` appears green only because a local
|
||||
`.env` database is reachable.
|
||||
|
||||
### 6. Tests Required
|
||||
|
||||
- Run health and static tests with `DATABASE_URL='' JWT_SECRET=''`.
|
||||
- Run the full `scripts/check-minimal.sh` under the same empty environment.
|
||||
- Keep the production default path covered by normal startup/smoke checks.
|
||||
|
||||
### 7. Wrong vs Correct
|
||||
|
||||
#### Wrong
|
||||
|
||||
```js
|
||||
vi.mock("../../src/prismaClient", () => ({ createPrismaClient: fake }));
|
||||
await buildApp(); // CJS plugin may still construct the real client
|
||||
```
|
||||
|
||||
#### Correct
|
||||
|
||||
```js
|
||||
await buildApp({ prismaPlugin: testPrismaPlugin, startJobs: noOpStartJobs });
|
||||
```
|
||||
|
||||
@@ -263,3 +263,87 @@ const REQUIRED_MAIN_NAV_KEYS = [
|
||||
const REQUIRED_BACKUP_NAV_KEYS = ["weeklyReports", "announcements"];
|
||||
const REQUIRED_ADMIN_NAV_KEYS = ["systemSettings"];
|
||||
```
|
||||
|
||||
## Scenario: Immutable Deployment Provenance And Dual Build SHA
|
||||
|
||||
### 1. Scope / Trigger
|
||||
|
||||
- Trigger: changing the trialrun deployment entry, Docker build, Compose
|
||||
wiring, `/health` or `/ready`, public bundle scanning or live readiness.
|
||||
|
||||
### 2. Signatures
|
||||
|
||||
- Provenance gate: `node deploy/trialrun/check-source-provenance.mjs`.
|
||||
- Standard release: `SYNC_ENV=0 bash deploy/trialrun/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
|
||||
frontend `html[data-build-sha]` marker.
|
||||
|
||||
### 3. Contracts
|
||||
|
||||
- Deployment rejects tracked and untracked dirty files before synchronization.
|
||||
- By default HEAD must be contained in its upstream. The explicit unpublished
|
||||
override is for controlled offline recovery and never relaxes clean-tree.
|
||||
- Standard deployment refreshes the configured upstream remote before the
|
||||
ancestor check; a stale local remote-tracking ref is not publication proof.
|
||||
- Unpublished override requires an allowlisted `DEPLOY_UNPUBLISHED_REASON` and
|
||||
must emit an `UNPUBLISHED-OVERRIDE` audit line.
|
||||
- Synchronization comes from `git archive <verified-commit>`, not the working
|
||||
tree.
|
||||
- Full `BUILD_SHA` is injected at build and runtime; `VITE_BUILD_SHA` is
|
||||
normalized before bundling so only 12 characters become public.
|
||||
- Neither build key is persisted in `deploy/trialrun/.env`; direct Compose use
|
||||
must explicitly provide both and must not publish `unknown`.
|
||||
- Deployment compares public `/ready` and the public bundle marker with the
|
||||
verified commit before reporting success.
|
||||
|
||||
### 4. Validation & Error Matrix
|
||||
|
||||
- Dirty tree -> `dirty_worktree`, deployment stops.
|
||||
- No upstream or commit absent from upstream -> stop unless the explicit
|
||||
unpublished override is approved.
|
||||
- Upstream refresh fails -> stop in standard mode; override mode requires an
|
||||
allowlisted reason and records `remoteVerified=false`.
|
||||
- Missing runtime/build SHA input -> Compose interpolation fails.
|
||||
- `/ready` SHA mismatch, bundle marker missing or full SHA exposed -> deployment
|
||||
fails.
|
||||
- Standalone readiness missing/mismatching observed SHA -> non-clean finding.
|
||||
|
||||
### 5. Good/Base/Bad Cases
|
||||
|
||||
- Good: clean published commit -> Git archive -> dual-SHA build -> matching
|
||||
backend and public bundle checks.
|
||||
- Base: local development build uses `unknown` but is never represented as a
|
||||
release.
|
||||
- Bad: rsync the current working tree or let `.env` override the image's SHA
|
||||
with `unknown`.
|
||||
|
||||
### 6. Tests Required
|
||||
|
||||
- Temp-repo provenance tests for dirty, missing upstream, published and
|
||||
unpublished-override cases, including a stale remote-tracking ref after a
|
||||
forced remote update.
|
||||
- `node deploy/trialrun/validate-env-contract.js` and Compose config with both
|
||||
SHA inputs.
|
||||
- Synthetic 40-character build: bundle contains the matching 12-character SHA
|
||||
and not the full value.
|
||||
- Local-stub readiness test for expected/observed SHA propagation.
|
||||
- Public deployment still requires separate approval and post-deploy checks.
|
||||
|
||||
### 7. Wrong vs Correct
|
||||
|
||||
#### Wrong
|
||||
|
||||
```bash
|
||||
rsync -az --delete ./ remote:/app
|
||||
docker compose up -d --build server
|
||||
```
|
||||
|
||||
#### Correct
|
||||
|
||||
```bash
|
||||
node deploy/trialrun/check-source-provenance.mjs
|
||||
BUILD_SHA=<verified-full-sha> VITE_BUILD_SHA=<matching-short-sha> \
|
||||
docker compose -f deploy/trialrun/docker-compose.yml build server
|
||||
```
|
||||
|
||||
@@ -36,6 +36,9 @@ Start from `docs/文档索引.md` and `docs/文档事实源状态矩阵.md`.
|
||||
- Use repo-relative `reportRef` / `logRef` when a path is inside the repo.
|
||||
- Do not write absolute local workspace paths into `summary.json`.
|
||||
- Parent summaries must include child report status and child finding counts.
|
||||
- Live-readiness summaries must distinguish the local `sourceCommit` from the
|
||||
public `observedBuildSha`; a missing or mismatched public build marker cannot
|
||||
produce a clean pass.
|
||||
- Parent `findingCount` must become non-zero when any child summary reports
|
||||
`PASS_WITH_FINDINGS`, `SKIP`, `PARTIAL`, `PAUSED`, `MISSING`, blocker-like
|
||||
status or child `findingCount > 0`.
|
||||
@@ -47,6 +50,8 @@ Start from `docs/文档索引.md` and `docs/文档事实源状态矩阵.md`.
|
||||
- Credential/readiness presence reports must use safe business labels, not raw
|
||||
env key names. For example, emit `adminCredentialReady: true` instead of a
|
||||
field named after a credential-value environment variable.
|
||||
- `check-pending-ocr.mjs` must reuse the adapter-backed Prisma client factory
|
||||
and close both Prisma and its external PostgreSQL pool.
|
||||
|
||||
### 4. Validation & Error Matrix
|
||||
|
||||
@@ -60,11 +65,15 @@ Start from `docs/文档索引.md` and `docs/文档事实源状态矩阵.md`.
|
||||
summary reflect the failure.
|
||||
- Secret-like field names in generated reports -> privacy finding, even when
|
||||
the value is only a boolean presence flag.
|
||||
- Public `/ready` has no usable build SHA -> `manual_or_config` finding.
|
||||
- Expected and observed public SHA differ -> `manual_or_config` finding or a
|
||||
deployment blocker; never a clean pass attributed to local HEAD.
|
||||
|
||||
### 5. Good/Base/Bad Cases
|
||||
|
||||
- Good: `summary.json` contains `reportRef`, `childReports.<name>.status`,
|
||||
`childReports.<name>.findingCount` and no absolute local path.
|
||||
`childReports.<name>.findingCount`, `sourceCommit`, `observedBuildSha` and no
|
||||
absolute local path.
|
||||
- Base: Missing runtime credentials produce `PASS_WITH_FINDINGS` or
|
||||
`manual_or_config`, not a clean pass.
|
||||
- Bad: Parent command exits 0 for a child `PASS_WITH_FINDINGS` and the parent
|
||||
@@ -81,6 +90,9 @@ Start from `docs/文档索引.md` and `docs/文档事实源状态矩阵.md`.
|
||||
- For live/readiness env reports, assert the JSON contains safe readiness
|
||||
labels and does not contain raw secret-like env key names.
|
||||
- Run the matching launch/readiness aggregator when it consumes the report.
|
||||
- Build a synthetic 40-character SHA and assert the public bundle contains only
|
||||
its 12-character marker; use a local HTTP stub to assert readiness records and
|
||||
matches the observed marker.
|
||||
|
||||
### 7. Wrong vs Correct
|
||||
|
||||
@@ -107,6 +119,9 @@ Start from `docs/文档索引.md` and `docs/文档事实源状态矩阵.md`.
|
||||
```json
|
||||
{
|
||||
"status": "PASS_WITH_FINDINGS",
|
||||
"sourceCommit": "abcdef1234567890",
|
||||
"observedBuildSha": "abcdef123456",
|
||||
"buildShaMatches": true,
|
||||
"reportRef": "artifacts/qa-flow/run",
|
||||
"childReports": {
|
||||
"oaOnlySmokeReadiness": {
|
||||
|
||||
@@ -13,8 +13,9 @@ tasks after TRELLIS2.
|
||||
- Put goal, scope, out-of-scope, acceptance and test plan in `prd.md`.
|
||||
- Add `design.md` or `implement.md` for complex process, API, migration,
|
||||
permission, deployment or workflow-governance changes.
|
||||
- Curate `implement.jsonl` and `check.jsonl` with stable spec or research
|
||||
context only.
|
||||
- In sub-agent dispatch mode, curate `implement.jsonl` and `check.jsonl` with
|
||||
stable spec or research context only. In Codex inline mode they may contain
|
||||
no real entries because the main session reads specs directly.
|
||||
- Give a concrete implementation plan before coding and wait for approval.
|
||||
- Keep edits scoped to the current Trellis task.
|
||||
- Commit in Chinese and include the Trellis task name or task ID.
|
||||
@@ -60,3 +61,62 @@ rollback and future self-maintenance.
|
||||
- Do not delete, bulk rewrite or move historical `issues/*.csv` files.
|
||||
- New work may mention an old Issue CSV for traceability, but must not require
|
||||
a new CSV unless the user explicitly requests legacy/export evidence.
|
||||
|
||||
## Scenario: Codex Inline Context Manifests
|
||||
|
||||
### 1. Scope / Trigger
|
||||
|
||||
- Trigger: creating, validating or finishing a Trellis task while
|
||||
`.trellis/config.yaml` sets `codex.dispatch_mode: inline`.
|
||||
|
||||
### 2. Signatures
|
||||
|
||||
- Config: `codex.dispatch_mode: inline | sub-agent`.
|
||||
- Context files: `<task>/implement.jsonl`, `<task>/check.jsonl`.
|
||||
- Gates: `node scripts/qa/check-trellis-governance.mjs` and
|
||||
`python3 ./.trellis/scripts/task.py validate <task>`.
|
||||
|
||||
### 3. Contracts
|
||||
|
||||
- Inline mode may keep both JSONL files at zero real entries; the seed
|
||||
`_example` row does not count as injected context.
|
||||
- If an inline task contains real entries, each entry must still be valid JSON
|
||||
with non-empty `file` and `reason`; normal path and context warnings apply.
|
||||
- Sub-agent mode requires at least one real entry in each manifest.
|
||||
- Switching dispatch mode changes the validation policy, not historical task
|
||||
evidence and not the requirement to read specs before implementation.
|
||||
|
||||
### 4. Validation & Error Matrix
|
||||
|
||||
- Inline + zero real entries -> pass with `*_inline_optional`.
|
||||
- Inline + malformed real entry -> fail.
|
||||
- Sub-agent + zero real entries -> fail with `*_empty`.
|
||||
- Either mode + non-spec/non-research context -> warning for review.
|
||||
|
||||
### 5. Good/Base/Bad Cases
|
||||
|
||||
- Good: inline task uses the seed-only files and the main agent reads specs.
|
||||
- Base: inline task keeps curated entries for audit; they remain validated.
|
||||
- Bad: governance fabricates placeholder entries only to satisfy a rule meant
|
||||
for sub-agent context injection.
|
||||
|
||||
### 6. Tests Required
|
||||
|
||||
- Fixture test for inline seed-only pass.
|
||||
- Fixture test for sub-agent seed-only failure.
|
||||
- Run both Trellis governance and `task.py validate` for the active task.
|
||||
|
||||
### 7. Wrong vs Correct
|
||||
|
||||
#### Wrong
|
||||
|
||||
```text
|
||||
dispatch_mode=inline -> require at least one injected JSONL entry
|
||||
```
|
||||
|
||||
#### Correct
|
||||
|
||||
```text
|
||||
dispatch_mode=inline -> allow zero real entries; validate any real entries
|
||||
dispatch_mode=sub-agent -> require curated real entries
|
||||
```
|
||||
|
||||
@@ -155,3 +155,123 @@ return reply.send(stream);
|
||||
originals in normal OA attachment areas.
|
||||
- Customer original material, sensitive quotes, contract bodies or signed
|
||||
packages outside authorized business flows.
|
||||
|
||||
## Scenario: External Delivery Error Logging
|
||||
|
||||
### 1. Scope / Trigger
|
||||
|
||||
- Trigger: logging SMTP, webhook, object-storage or other provider delivery
|
||||
failures whose native error objects may contain recipients or payloads.
|
||||
|
||||
### 2. Signatures
|
||||
|
||||
- Email log metadata: `errorCode`, optional `providerCode`, optional
|
||||
`smtpCommand`, optional numeric `smtpResponseCode`, request/business IDs and
|
||||
recipient count.
|
||||
- Forbidden input: raw provider `Error` objects in structured logs.
|
||||
|
||||
### 3. Contracts
|
||||
|
||||
- Log only explicitly allowlisted scalar metadata.
|
||||
- Do not log `response`, `rejected`, `rejectedErrors`, `envelope`, addresses,
|
||||
raw messages, headers, credentials or payloads.
|
||||
- Provider codes must match a bounded token format; SMTP commands must come
|
||||
from a fixed safe set; response codes must be bounded integers.
|
||||
- Delivery failure counts remain observable without exposing recipient data.
|
||||
|
||||
### 4. Validation & Error Matrix
|
||||
|
||||
- Known safe provider code/command/response code -> include normalized fields.
|
||||
- Unknown or free-form field -> omit it.
|
||||
- Error with rejected address or SMTP response text -> log generic category and
|
||||
safe numeric/token fields only.
|
||||
- Missing logger -> return the delivery result without throwing a logging
|
||||
error.
|
||||
|
||||
### 5. Good/Base/Bad Cases
|
||||
|
||||
- Good: record `EENVELOPE`, `RCPT TO`, `550`, request ID and failed count.
|
||||
- Base: plain `Error` becomes only `errorCode=email_delivery_failed`.
|
||||
- Bad: pass Nodemailer's raw `err` to Pino, which serializes rejected addresses.
|
||||
|
||||
### 6. Tests Required
|
||||
|
||||
- Mock a provider error containing a rejected full address, response and
|
||||
envelope.
|
||||
- Assert serialized log context contains none of those values and retains only
|
||||
allowlisted metadata.
|
||||
- Cover disabled, configured, success and failure delivery paths.
|
||||
|
||||
### 7. Wrong vs Correct
|
||||
|
||||
#### Wrong
|
||||
|
||||
```js
|
||||
logger.error({ err: providerError, recipient }, "delivery failed");
|
||||
```
|
||||
|
||||
#### Correct
|
||||
|
||||
```js
|
||||
logger.error({ ...buildSafeErrorLog(providerError), requestId, failed }, "delivery failed");
|
||||
```
|
||||
|
||||
## Scenario: Current Fact Source Sensitive Gate
|
||||
|
||||
### 1. Scope / Trigger
|
||||
|
||||
- Trigger: adding, registering or changing a CURRENT fact source, README,
|
||||
deployment entry or environment template.
|
||||
|
||||
### 2. Signatures
|
||||
|
||||
- Gate: `node scripts/qa/check-sensitive-content.mjs`.
|
||||
- Registry: `docs/文档事实源状态矩阵.md` rows whose status is `CURRENT` and
|
||||
contract path is under `docs/`.
|
||||
- Output: `fileRef`, `line`, `category`; never the matched text.
|
||||
|
||||
### 3. Contracts
|
||||
|
||||
- Scan fixed execution/deployment entrypoints plus every CURRENT docs contract
|
||||
derived from the status matrix; do not maintain a partial duplicate docs list.
|
||||
- Detect secret assignments, credential URLs, full accounts, personal workspace
|
||||
paths, private keys and public IPv4 values.
|
||||
- Allow explicit placeholders, environment interpolation and loopback addresses.
|
||||
- Missing registry files remain a docs-audit concern; existing scanned files
|
||||
must not be silently excluded.
|
||||
|
||||
### 4. Validation & Error Matrix
|
||||
|
||||
- Any sensitive match -> `FAIL` with non-zero `findingCount`.
|
||||
- CURRENT doc added to the matrix -> automatically enters the scan set.
|
||||
- EVIDENCE/HISTORICAL issue row -> does not enter the CURRENT docs scan set.
|
||||
- Placeholder such as `<repo-root>` -> pass.
|
||||
|
||||
### 5. Good/Base/Bad Cases
|
||||
|
||||
- Good: all current docs and deploy entrypoints scan clean with metadata-only
|
||||
output.
|
||||
- Base: historical evidence stays outside this current-source blocking gate.
|
||||
- Bad: hard-code six docs while the matrix contains eighteen CURRENT docs.
|
||||
|
||||
### 6. Tests Required
|
||||
|
||||
- Parser fixture with CURRENT doc, historical doc and CURRENT Issue row.
|
||||
- Assertion that representative current manuals/specs appear in the resolved
|
||||
default set.
|
||||
- Repository gate must report `findingCount=0` and the expected expanded file
|
||||
count after registry changes.
|
||||
|
||||
### 7. Wrong vs Correct
|
||||
|
||||
#### Wrong
|
||||
|
||||
```js
|
||||
const files = ["docs/文档索引.md", "docs/产品设计规则.md"];
|
||||
```
|
||||
|
||||
#### Correct
|
||||
|
||||
```js
|
||||
const files = [...fixedEntrypoints, ...extractCurrentDocumentFiles(statusMatrix)];
|
||||
```
|
||||
|
||||
在新工单中引用
屏蔽一个用户