Files
company-haness/hyeonworks/app/scripts/verify_static.cjs
T

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');