feat(release): add standard and fast 103 portal release modes

Formalize fast release as --mode fast so verified changes can skip canary promotion and full Gate reruns while keeping backups and health checks.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
john
2026-08-15 08:03:44 +08:00
parent a9fe3d01a7
commit 835677a285
9 changed files with 222 additions and 76 deletions
+42 -6
View File
@@ -38,7 +38,7 @@ test('production release verifies gate report before 103 preflight and upload',
assert.ok(gateIndex > 0, 'missing gate verifier');
assert.ok(preflightIndex > gateIndex, '103 preflight must run after gate verification');
assert.ok(uploadIndex > preflightIndex, 'upload must run after preflight');
assert.match(source, /生产发布守门员禁止 --skip-tests/);
assert.match(source, /标准发布禁止 --skip-tests/);
assert.match(source, /禁止 ALLOW_PORTAL_RELEASE_SCOPE_BYPASS/);
});
@@ -47,11 +47,11 @@ test('production stable release verifies canary promotion evidence before gate c
path.join(ROOT, 'scripts', 'release-portal-runtime-prod.sh'),
'utf8',
);
const promotionIndex = source.indexOf('verify-canary-promotion-evidence.mjs');
const gateIndex = source.indexOf('verify-release-gate-report.mjs');
const promotionIndex = source.indexOf('say "验证 103 灰度晋升证据"');
const gateCallIndex = source.indexOf('say "验证与当前 main 和 runtime artifact 绑定的 Gate report"');
const impactIndex = source.indexOf('run-release-gate-impact.mjs');
assert.ok(promotionIndex > 0, 'missing canary promotion evidence verifier');
assert.ok(gateIndex > promotionIndex, 'gate verification must follow promotion evidence');
assert.ok(promotionIndex > 0, 'missing canary promotion evidence step');
assert.ok(gateCallIndex > promotionIndex, 'gate verification must follow promotion evidence in standard flow');
assert.ok(impactIndex > 0, 'missing risk-based impact gate fallback');
assert.doesNotMatch(source, /在同一候选完成 103 灰度验收且晋升证据校验落地前,禁止非 dry-run/);
assert.match(source, /read_agent_run_status_json/);
@@ -125,10 +125,46 @@ test('production release rejects --skip-tests before repository or network prefl
{ cwd: ROOT, encoding: 'utf8' },
);
assert.notEqual(result.status, 0);
assert.match(result.stderr, /禁止 --skip-tests/);
assert.match(result.stderr, /标准发布禁止 --skip-tests/);
assert.doesNotMatch(`${result.stdout}\n${result.stderr}`, /103 只读预检/);
});
test('fast production release allows --skip-tests but still requires check-release-ready', () => {
const result = spawnSync(
'bash',
[
path.join(ROOT, 'scripts', 'release-portal-runtime-prod.sh'),
'--mode',
'fast',
'--skip-tests',
'--skip-build',
'--dry-run',
],
{ cwd: ROOT, encoding: 'utf8' },
);
assert.notEqual(result.status, 0);
assert.doesNotMatch(result.stderr, /标准发布禁止 --skip-tests/);
assert.match(`${result.stdout}\n${result.stderr}`, /快速发布:跳过额外本地测试/);
});
test('fast release wrapper delegates to runtime prod script with --mode fast', async () => {
const source = await fs.readFile(
path.join(ROOT, 'scripts', 'release-portal-fast-prod.sh'),
'utf8',
);
assert.match(source, /release-portal-runtime-prod\.sh.*--mode fast/);
});
test('fast production release skips canary promotion evidence in script', async () => {
const source = await fs.readFile(
path.join(ROOT, 'scripts', 'release-portal-runtime-prod.sh'),
'utf8',
);
assert.match(source, /快速发布:跳过 103 灰度晋升证据/);
assert.match(source, /verify-canary-promotion-evidence\.mjs/);
assert.match(source, /release_mode=\$\{RELEASE_MODE\}/);
});
test('production canary verifies the exact Gate artifact before any 103 preflight or upload', async () => {
const source = await fs.readFile(CANARY_RELEASE, 'utf8');
const gateIndex = source.indexOf('verify-release-gate-report.mjs');