Add preview pack smoke gate

This commit is contained in:
Affaan Mustafa
2026-05-17 15:35:23 -04:00
parent 1a384dc533
commit 3215e655ef
9 changed files with 724 additions and 12 deletions

View File

@@ -171,6 +171,7 @@ test('preview pack manifest assembles release, Hermes, and publication gates', (
'docs/HERMES-SETUP.md',
'skills/hermes-imports/SKILL.md',
'docs/architecture/harness-adapter-compliance.md',
'scripts/preview-pack-smoke.js',
'docs/releases/2.0.0-rc.1/publication-readiness.md',
'docs/releases/2.0.0-rc.1/naming-and-publication-matrix.md',
]) {
@@ -189,6 +190,7 @@ test('preview pack manifest assembles release, Hermes, and publication gates', (
assert.ok(manifest.includes('no raw workspace exports'));
assert.ok(manifest.includes('Final Verification Commands'));
assert.ok(manifest.includes('npm run preview-pack:smoke'));
assert.ok(manifest.includes('Reference-Inspired Adapter Direction'));
});
@@ -263,6 +265,8 @@ test('publication readiness checklist gates public release actions on evidence',
}
assert.ok(source.includes('publication-evidence-2026-05-15.md'));
assert.ok(source.includes('Preview-pack smoke'));
assert.ok(source.includes('npm run preview-pack:smoke'));
assert.ok(may15Evidence.includes('PR #1921'));
assert.ok(may15Evidence.includes('PR #1933'));
assert.ok(may15Evidence.includes('PR #1934'));

View File

@@ -59,6 +59,7 @@ function buildExpectedPublishPaths(repoRoot) {
"scripts/observability-readiness.js",
"scripts/operator-readiness-dashboard.js",
"scripts/platform-audit.js",
"scripts/preview-pack-smoke.js",
"scripts/skill-create-output.js",
"scripts/repair.js",
"scripts/harness-adapter-compliance.js",
@@ -129,6 +130,7 @@ function main() {
"scripts/consult.js",
"scripts/discussion-audit.js",
"scripts/operator-readiness-dashboard.js",
"scripts/preview-pack-smoke.js",
"scripts/work-items.js",
"scripts/platform-audit.js",
".gemini/GEMINI.md",

View File

@@ -32,18 +32,26 @@ function seedRepo(rootDir, overrides = {}) {
files: [
'scripts/observability-readiness.js',
'scripts/operator-readiness-dashboard.js',
'scripts/platform-audit.js'
'scripts/platform-audit.js',
'scripts/preview-pack-smoke.js'
],
scripts: {
'discussion:audit': 'node scripts/discussion-audit.js',
'observability:ready': 'node scripts/observability-readiness.js',
'operator:dashboard': 'node scripts/operator-readiness-dashboard.js',
'platform:audit': 'node scripts/platform-audit.js',
'preview-pack:smoke': 'node scripts/preview-pack-smoke.js',
'security:ioc-scan': 'node scripts/ci/scan-supply-chain-iocs.js',
'security:advisory-sources': 'node scripts/ci/supply-chain-advisory-sources.js'
}
}, null, 2),
'scripts/operator-readiness-dashboard.js': 'operator dashboard generator',
'scripts/preview-pack-smoke.js': [
'ecc.preview-pack-smoke.v1',
'preview-pack-artifacts-present',
'hermes-boundary-sanitized',
'publication-blockers-preserved'
].join('\n'),
'docs/ECC-2.0-GA-ROADMAP.md': [
'https://linear.app/itomarkets/project/ecc-platform-roadmap-52b328ee03e1',
'Linear ITO-44 ITO-59',
@@ -63,7 +71,11 @@ function seedRepo(rootDir, overrides = {}) {
].join('\n'),
'docs/releases/2.0.0-rc.1/publication-readiness.md': 'Claude plugin Codex plugin',
'docs/releases/2.0.0-rc.1/naming-and-publication-matrix.md': 'Claude plugin Codex plugin npm package Publication Paths',
'docs/releases/2.0.0-rc.1/preview-pack-manifest.md': 'publication-readiness.md release-notes.md quickstart.md',
'docs/releases/2.0.0-rc.1/preview-pack-manifest.md': [
'publication-readiness.md release-notes.md quickstart.md',
'`scripts/preview-pack-smoke.js`',
'npm run preview-pack:smoke'
].join('\n'),
'docs/releases/2.0.0-rc.1/release-notes.md': 'release notes',
'docs/releases/2.0.0-rc.1/x-thread.md': 'x thread',
'docs/releases/2.0.0-rc.1/linkedin-post.md': 'linkedin post',
@@ -77,8 +89,8 @@ function seedRepo(rootDir, overrides = {}) {
'PR queue',
'Not complete'
].join('\n'),
'docs/HERMES-SETUP.md': 'Hermes setup',
'skills/hermes-imports/SKILL.md': 'Hermes imports',
'docs/HERMES-SETUP.md': 'Hermes setup Public Release Candidate Scope',
'skills/hermes-imports/SKILL.md': 'Hermes imports Sanitization Checklist Do not ship raw workspace exports Output Contract',
'docs/stale-pr-salvage-ledger.md': [
'Remaining Manual-Review Backlog',
'Linear ITO-55',
@@ -218,6 +230,18 @@ function runTests() {
assert.strictEqual(report.ready, false);
assert.strictEqual(report.publicationReady, false);
assert.ok(report.requirements.some(item => item.id === 'completion-dashboard' && item.status === 'complete'));
assert.ok(report.requirements.some(item => (
item.id === 'ecc-preview-pack'
&& item.status === 'current'
&& item.evidence.includes('deterministic smoke gate')
&& item.gap === 'repeat clean-checkout preview-pack smoke before publication'
)));
assert.ok(report.requirements.some(item => (
item.id === 'hermes-specialized-skills'
&& item.status === 'current'
&& item.evidence.includes('covered by preview-pack smoke')
&& item.gap === 'repeat preview-pack smoke before release review'
)));
assert.ok(report.requirements.some(item => item.id === 'ecc-tools-next-level' && item.status === 'in_progress'));
assert.ok(report.requirements.some(item => (
item.id === 'agentshield-enterprise-iteration'
@@ -253,6 +277,8 @@ function runTests() {
&& item.gap === 'repeat Linear/project status update and local work-items sync after each significant merge batch'
)));
assert.ok(report.top_actions.some(item => item.id === 'naming-and-plugin-publication'));
assert.ok(!report.top_actions.some(item => item.id === 'ecc-preview-pack'));
assert.ok(!report.top_actions.some(item => item.id === 'hermes-specialized-skills'));
assert.ok(!report.top_actions.some(item => item.id === 'legacy-salvage'));
assert.ok(!report.top_actions.some(item => item.id === 'linear-roadmap-and-progress'));
} finally {
@@ -287,6 +313,45 @@ function runTests() {
}
})) passed++; else failed++;
if (test('preview pack and Hermes gates stay in progress until smoke gate is wired', () => {
const rootDir = createTempDir('operator-dashboard-preview-smoke-');
try {
seedRepo(rootDir, {
'package.json': JSON.stringify({
files: [
'scripts/observability-readiness.js',
'scripts/operator-readiness-dashboard.js',
'scripts/platform-audit.js'
],
scripts: {
'discussion:audit': 'node scripts/discussion-audit.js',
'observability:ready': 'node scripts/observability-readiness.js',
'operator:dashboard': 'node scripts/operator-readiness-dashboard.js',
'platform:audit': 'node scripts/platform-audit.js',
'security:ioc-scan': 'node scripts/ci/scan-supply-chain-iocs.js',
'security:advisory-sources': 'node scripts/ci/supply-chain-advisory-sources.js'
}
}, null, 2),
'scripts/preview-pack-smoke.js': null,
'docs/releases/2.0.0-rc.1/preview-pack-manifest.md': 'publication-readiness.md release-notes.md quickstart.md'
});
const report = buildSeededReport(rootDir);
const previewPack = report.requirements.find(item => item.id === 'ecc-preview-pack');
const hermes = report.requirements.find(item => item.id === 'hermes-specialized-skills');
assert.strictEqual(previewPack.status, 'in_progress');
assert.strictEqual(previewPack.gap, 'final clean-checkout release approval and publish evidence still pending');
assert.strictEqual(hermes.status, 'in_progress');
assert.strictEqual(hermes.gap, 'final preview-pack smoke and release review pending');
assert.ok(report.top_actions.some(item => item.id === 'ecc-preview-pack'));
assert.ok(report.top_actions.some(item => item.id === 'hermes-specialized-skills'));
} finally {
cleanup(rootDir);
}
})) passed++; else failed++;
if (test('AgentShield enterprise evidence covers export and policy promotion markers', () => {
const cases = [
{

View File

@@ -0,0 +1,260 @@
'use strict';
const assert = require('assert');
const fs = require('fs');
const os = require('os');
const path = require('path');
const { execFileSync, spawnSync } = require('child_process');
const SCRIPT = path.join(__dirname, '..', '..', 'scripts', 'preview-pack-smoke.js');
const {
REQUIRED_ARTIFACTS,
REQUIRED_PUBLICATION_BLOCKERS,
REQUIRED_VERIFICATION_COMMANDS,
buildReport,
parseArgs,
renderText,
} = require(SCRIPT);
const RELEASE_DIR = 'docs/releases/2.0.0-rc.1';
function createTempDir(prefix) {
return fs.mkdtempSync(path.join(os.tmpdir(), prefix));
}
function cleanup(dirPath) {
fs.rmSync(dirPath, { recursive: true, force: true });
}
function writeFile(rootDir, relativePath, content) {
const targetPath = path.join(rootDir, relativePath);
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
fs.writeFileSync(targetPath, content);
}
function manifestContent() {
return [
'# ECC v2.0.0-rc.1 Preview Pack Manifest',
'',
'## Pack Contents',
'',
'| Artifact | Role | Gate |',
'| --- | --- | --- |',
...REQUIRED_ARTIFACTS.map(artifact => `| \`${artifact}\` | release artifact | checked |`),
'',
'## Hermes Skill Boundary',
'',
'- no raw workspace exports;',
'',
'## Final Verification Commands',
'',
'```bash',
...REQUIRED_VERIFICATION_COMMANDS,
'```',
'',
'## Publication Blockers',
'',
...REQUIRED_PUBLICATION_BLOCKERS.map(blocker => `- ${blocker}`),
'',
'The preview pack is not public without approval-gated release, package, plugin, and announcement steps.',
].join('\n');
}
function seedRepo(rootDir, overrides = {}) {
const files = {
'package.json': JSON.stringify({
files: ['scripts/preview-pack-smoke.js'],
scripts: {
'preview-pack:smoke': 'node scripts/preview-pack-smoke.js',
},
}, null, 2),
'scripts/preview-pack-smoke.js': 'preview pack smoke script',
[`${RELEASE_DIR}/preview-pack-manifest.md`]: manifestContent(),
'docs/HERMES-SETUP.md': [
'# Hermes Setup',
'Public Release Candidate Scope',
'ECC v2.0.0-rc.1 documents the Hermes surface',
'No raw workspace export is included.',
].join('\n'),
'skills/hermes-imports/SKILL.md': [
'---',
'name: hermes-imports',
'---',
'Sanitization Checklist',
'Do not ship raw workspace exports',
'Output Contract',
].join('\n'),
};
for (const artifact of REQUIRED_ARTIFACTS) {
if (!Object.prototype.hasOwnProperty.call(files, artifact)) {
files[artifact] = `${artifact} public preview-pack content`;
}
}
for (const [relativePath, content] of Object.entries({ ...files, ...overrides })) {
if (content === null) {
continue;
}
writeFile(rootDir, relativePath, content);
}
}
function run(args = [], options = {}) {
return execFileSync('node', [SCRIPT, ...args], {
cwd: options.cwd || path.join(__dirname, '..', '..'),
encoding: 'utf8',
stdio: ['pipe', 'pipe', 'pipe'],
timeout: 10000,
});
}
function runProcess(args = [], options = {}) {
return spawnSync('node', [SCRIPT, ...args], {
cwd: options.cwd || path.join(__dirname, '..', '..'),
encoding: 'utf8',
stdio: ['pipe', 'pipe', 'pipe'],
timeout: 10000,
});
}
function test(name, fn) {
try {
fn();
console.log(` PASS ${name}`);
return true;
} catch (error) {
console.log(` FAIL ${name}`);
console.log(` Error: ${error.message}`);
return false;
}
}
function runTests() {
console.log('\n=== Testing preview-pack-smoke.js ===\n');
let passed = 0;
let failed = 0;
if (test('parseArgs accepts smoke flags and rejects invalid values', () => {
const rootDir = createTempDir('preview-pack-smoke-args-');
try {
const parsed = parseArgs([
'node',
'script',
'--format=json',
`--root=${rootDir}`,
]);
assert.strictEqual(parsed.format, 'json');
assert.strictEqual(parsed.root, path.resolve(rootDir));
assert.throws(() => parseArgs(['node', 'script', '--format', 'xml']), /Invalid format/);
assert.throws(() => parseArgs(['node', 'script', '--root']), /--root requires a value/);
assert.throws(() => parseArgs(['node', 'script', '--unknown']), /Unknown argument/);
} finally {
cleanup(rootDir);
}
})) passed++; else failed++;
if (test('seeded release pack passes every smoke check', () => {
const rootDir = createTempDir('preview-pack-smoke-pass-');
try {
seedRepo(rootDir);
const report = buildReport({ root: rootDir });
assert.strictEqual(report.schema_version, 'ecc.preview-pack-smoke.v1');
assert.strictEqual(report.ready, true);
assert.strictEqual(report.summary.failed, 0);
assert.ok(report.checks.every(check => check.status === 'pass'));
const text = renderText(report);
assert.ok(text.includes('Ready: yes'));
assert.ok(text.includes('Passed: 5'));
assert.ok(text.includes('Failed: 0'));
} finally {
cleanup(rootDir);
}
})) passed++; else failed++;
if (test('script registration fails closed without package wiring', () => {
const rootDir = createTempDir('preview-pack-smoke-package-');
try {
seedRepo(rootDir, {
'package.json': JSON.stringify({ files: [], scripts: {} }, null, 2),
});
const report = buildReport({ root: rootDir });
const registration = report.checks.find(check => check.id === 'preview-pack-script-registered');
assert.strictEqual(report.ready, false);
assert.strictEqual(registration.status, 'fail');
assert.ok(registration.fix.includes('preview-pack:smoke'));
} finally {
cleanup(rootDir);
}
})) passed++; else failed++;
if (test('Hermes boundary fails closed on private local paths', () => {
const rootDir = createTempDir('preview-pack-smoke-private-path-');
try {
seedRepo(rootDir, {
[`${RELEASE_DIR}/quickstart.md`]: 'Do not ship /Users/affoon/private-state in public docs.',
});
const report = buildReport({ root: rootDir });
const boundary = report.checks.find(check => check.id === 'hermes-boundary-sanitized');
assert.strictEqual(report.ready, false);
assert.strictEqual(boundary.status, 'fail');
assert.ok(boundary.evidence.includes(`${RELEASE_DIR}/quickstart.md:1`));
} finally {
cleanup(rootDir);
}
})) passed++; else failed++;
if (test('CLI emits json and uses status 2 for failed smoke reports', () => {
const rootDir = createTempDir('preview-pack-smoke-cli-');
try {
seedRepo(rootDir);
const stdout = run(['--format=json', `--root=${rootDir}`], { cwd: rootDir });
const parsed = JSON.parse(stdout);
assert.strictEqual(parsed.ready, true);
writeFile(rootDir, 'package.json', JSON.stringify({ files: [], scripts: {} }, null, 2));
const failedRun = runProcess(['--format=json', `--root=${rootDir}`], { cwd: rootDir });
assert.strictEqual(failedRun.status, 2);
assert.strictEqual(failedRun.stderr, '');
assert.ok(failedRun.stdout.includes('"ready": false'));
} finally {
cleanup(rootDir);
}
})) passed++; else failed++;
if (test('CLI help exits successfully and invalid flags fail before reporting', () => {
const help = runProcess(['--help']);
assert.strictEqual(help.status, 0);
assert.strictEqual(help.stderr, '');
assert.ok(help.stdout.includes('Usage: node scripts/preview-pack-smoke.js'));
const invalid = runProcess(['--format=xml']);
assert.strictEqual(invalid.status, 1);
assert.strictEqual(invalid.stdout, '');
assert.match(invalid.stderr, /Error: Invalid format/);
})) passed++; else failed++;
console.log(`\nPassed: ${passed}`);
console.log(`Failed: ${failed}`);
if (failed > 0) {
process.exit(1);
}
}
if (require.main === module) {
runTests();
}