63 lines
3.1 KiB
JavaScript
63 lines
3.1 KiB
JavaScript
const assert = require('node:assert/strict');
|
|
const crypto = require('node:crypto');
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
|
|
const root = path.resolve(__dirname, '..');
|
|
const read = relative => fs.readFileSync(path.join(root, relative), 'utf8');
|
|
const hash = relative => crypto.createHash('sha256').update(fs.readFileSync(path.join(root, relative))).digest('hex');
|
|
const packageJson = JSON.parse(read('package.json'));
|
|
const baseline = JSON.parse(read('verification/approved-r4.json'));
|
|
const html = read('dist/index.html');
|
|
const css = read('dist/styles.css');
|
|
const app = read('dist/app.js');
|
|
|
|
assert.equal(packageJson.private, true, 'the app must remain a private deployable package');
|
|
assert.equal(packageJson.dependencies, undefined, 'runtime must remain dependency-free');
|
|
assert.equal(packageJson.devDependencies, undefined, 'local verification must not require npm installation');
|
|
for (const script of ['dev', 'start', 'test', 'test:static', 'test:e2e', 'verify']) {
|
|
assert.equal(typeof packageJson.scripts[script], 'string', `missing npm script: ${script}`);
|
|
}
|
|
|
|
const actualHashes = {};
|
|
for (const [relative, expected] of Object.entries(baseline.files)) {
|
|
actualHashes[relative] = hash(relative);
|
|
assert.equal(actualHashes[relative], expected, `${relative} diverged from the declared production baseline`);
|
|
}
|
|
|
|
assert.match(html, /^<!doctype html>/i);
|
|
assert.match(html, /<html lang="ko">/);
|
|
assert.match(html, /<meta name="viewport"/);
|
|
assert.match(html, /<meta name="description"/);
|
|
assert.match(html, /<a class="skip-link" href="#main">본문으로 건너뛰기<\/a>/);
|
|
assert.match(html, /id="announcer"[^>]*aria-live="polite"/);
|
|
assert.match(html, /<script type="module" src="\.\/app\.js"><\/script>/);
|
|
assert.doesNotMatch(html, /<script(?![^>]*\bsrc=)[^>]*>/i, 'inline scripts are not allowed');
|
|
assert.doesNotMatch(`${html}\n${css}\n${app}`, /https?:\/\//i, 'the product must not make remote requests');
|
|
|
|
for (const marker of [
|
|
"'tx-lost-update-01'",
|
|
"'tx-lost-update-inventory-fixture'",
|
|
"href=\"#/orient/concept\"",
|
|
"href=\"#/orient/symptom\"",
|
|
'Predict → Observe → Compare → Explain → Transfer',
|
|
'실제 장애 원인을 확정하지 않습니다',
|
|
"window.addEventListener('hashchange'",
|
|
]) assert.ok(app.includes(marker), `missing approved behavior marker: ${marker}`);
|
|
|
|
for (const marker of [
|
|
'--coral: #ad4031',
|
|
'--accent-on-deep: #ff8873',
|
|
'--pending: #535c58',
|
|
'--control-border: #88877e',
|
|
'.skip-link',
|
|
'@media (max-width: 700px)',
|
|
]) assert.ok(css.includes(marker), `missing approved visual/accessibility marker: ${marker}`);
|
|
|
|
assert.equal(baseline.winnerPrototypeId, 'ENG-FE-20260718T144700Z');
|
|
assert.equal(baseline.winnerReportSha256, '8b208279e12f9aea0fa6d7a3c320811b0e72ddb74c6afac64cddadf4b157cf76');
|
|
assert.ok(Array.isArray(baseline.postApprovalChanges), 'production hardening provenance must be explicit');
|
|
|
|
console.log(`PASS static integrity ${JSON.stringify(actualHashes)}`);
|
|
console.log('PASS zero-runtime-dependency, local-only assets, semantic shell, approved scenario/token contracts and production-hardening provenance');
|