fix(release): make August 1 handoff reproducible

This commit is contained in:
key
2026-07-29 12:25:20 +08:00
parent 6e2b6025c1
commit fee80877de
5 changed files with 161 additions and 11 deletions
@@ -0,0 +1,89 @@
import assert from 'node:assert/strict'
import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
import os from 'node:os'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import { spawnSync } from 'node:child_process'
import test from 'node:test'
const testDir = path.dirname(fileURLToPath(import.meta.url))
const projectRoot = path.resolve(testDir, '..', '..')
const verifierPath = path.join(projectRoot, 'scripts', 'verify-aug1-content-candidates.mjs')
const candidatePath = path.join(
projectRoot,
'docs',
'content-candidates',
'aug1-life-advisor-content-candidates-v0.1.json',
)
const baseCandidate = JSON.parse(await readFile(candidatePath, 'utf8'))
const cloneCandidate = () => structuredClone(baseCandidate)
async function verifyCandidate(candidate) {
const tempDir = await mkdtemp(path.join(os.tmpdir(), 'aug1-content-'))
const fixturePath = path.join(tempDir, 'candidate.json')
try {
await writeFile(fixturePath, JSON.stringify(candidate), 'utf8')
return spawnSync(process.execPath, [verifierPath, fixturePath], {
cwd: projectRoot,
encoding: 'utf8',
})
} finally {
await rm(tempDir, { recursive: true, force: true })
}
}
test('accepts the reviewed disabled candidate structure through an explicit package path', async () => {
const result = await verifyCandidate(cloneCandidate())
assert.equal(result.status, 0, result.stderr)
assert.match(result.stdout, /verification passed/)
})
test('rejects sensitive locators, credentials and mobile numbers', async (t) => {
const cases = [
['precise room', (candidate) => {
candidate.scenarios[0].scenarioDraft.openingText = '住户住在3栋2单元501室。'
}],
['access credential', (candidate) => {
candidate.scenarios[0].scenarioDraft.openingText = '门禁口令 ABCD。'
}],
['mobile phone', (candidate) => {
candidate.policyQuestionCandidates[0].question = ['联系号码', '13', '9000', '00000'].join('')
}],
]
for (const [name, mutate] of cases) {
await t.test(name, async () => {
const candidate = cloneCandidate()
mutate(candidate)
const result = await verifyCandidate(candidate)
assert.notEqual(result.status, 0)
})
}
})
test('rejects publishable content, answers and duplicate IDs before formal sign-off', async (t) => {
const cases = [
['publishable root', (candidate) => {
candidate.publishable = true
}],
['embedded answer', (candidate) => {
candidate.policyQuestionCandidates[0].answer = 'not allowed before formal sourcing'
}],
['duplicate scenario ID', (candidate) => {
candidate.scenarios[1].candidateId = candidate.scenarios[0].candidateId
}],
['duplicate question ID', (candidate) => {
candidate.policyQuestionCandidates[1].id = candidate.policyQuestionCandidates[0].id
}],
]
for (const [name, mutate] of cases) {
await t.test(name, async () => {
const candidate = cloneCandidate()
mutate(candidate)
const result = await verifyCandidate(candidate)
assert.notEqual(result.status, 0)
})
}
})
+5 -1
View File
@@ -15,10 +15,14 @@ $requiredFragments = @(
"'operations/release-backend.sh'",
"'operations/release-preflight.sh'",
"'operations/verify-aug1-production-api-readonly.sh'",
"'operations/verify-aug1-authenticated-production.sh'",
"'operations/verify-aug1-content-candidates.mjs'",
"'operations/capture-android-acceptance.ps1'",
"'operations/go-no-go.md'",
"'operations/business-content-and-five-channel-signoff.md'",
'Release package already exists and will not be overwritten',
'Release package expected 14 files',
'Release package expected 17 files',
'ZIP verification expected 17 entries',
'ZIP entry hash mismatch',
'package_verified=true',
'authenticatedFixedCodeSmokePassed = $true',