import test from 'node:test'; import assert from 'node:assert/strict'; import fs from 'node:fs'; import path from 'node:path'; import os from 'node:os'; import { createHash } from 'node:crypto'; import { sourceFingerprint, combineEvidence, linkCssEvidence } from './guard-evidence.mjs'; function evidence(callback) { const parent = fs.realpathSync(os.tmpdir()); const dir = fs.mkdtempSync(path.join(parent, 'hm-evidence-')); try { fs.writeFileSync(path.join(dir, 'main.tsx'), 'original'); fs.writeFileSync(path.join(dir, 'contract.ts'), 'public contract'); const guard = { scope: 'FULL_SCREEN', runtimeRequired: true, target: dir, contractSource: dir, fingerprintFiles: ['main.tsx'], contractFiles: ['contract.ts'], sourceFingerprint: sourceFingerprint(dir, ['main.tsx']), contractFingerprint: sourceFingerprint(dir, ['contract.ts']), staticStatus: 'PASS', status: 'NOT VERIFIED', issues: [{ code: 'RENDER-002', severity: 'NOT VERIFIED' }] }; const runtime = { sourceFingerprint: guard.sourceFingerprint, contractFingerprint: guard.contractFingerprint, viewports: [390, 768, 1280, 1440].map(width => ({ width, status: 'PASS' })), interactions: [{ name: 'Submit', status: 'PASS' }] }; callback(guard, runtime, dir); } finally { assert.equal(path.dirname(dir), parent); assert.ok(path.basename(dir).startsWith('hm-evidence-')); fs.rmSync(dir, { recursive: true, force: true }); } } test('matching independent browser evidence leaves original Guard untouched', () => evidence((g, r) => { const before = JSON.stringify(g); assert.equal(combineEvidence(g, r).status, 'PASS'); assert.equal(JSON.stringify(g), before); })); test('changed source and changed public contract invalidate previous browser evidence', () => evidence((g, r, dir) => { fs.writeFileSync(path.join(dir, 'main.tsx'), 'modified'); assert.equal(combineEvidence(g, r).status, 'NOT VERIFIED'); fs.writeFileSync(path.join(dir, 'main.tsx'), 'original'); fs.writeFileSync(path.join(dir, 'contract.ts'), 'modified'); assert.equal(combineEvidence(g, r).status, 'NOT VERIFIED'); })); test('missing viewport or interaction cannot claim runtime PASS', () => evidence((g, r) => { assert.equal(combineEvidence(g, { ...r, viewports: r.viewports.slice(1) }).status, 'NOT VERIFIED'); assert.equal(combineEvidence(g, { ...r, interactions: [] }).status, 'NOT VERIFIED'); })); test('browser evidence cannot clear static FAIL or CSS intent uncertainty', () => evidence((g, r) => { assert.equal(combineEvidence({ ...g, staticStatus: 'FAIL', issues: [{ code: 'TOKEN-001' }] }, r).status, 'FAIL'); assert.equal(combineEvidence({ ...g, staticStatus: 'NOT VERIFIED', issues: [{ code: 'REUSE-003' }] }, r).status, 'NOT VERIFIED'); assert.equal(combineEvidence(g, { ...r, interactions: [{ status: 'FAIL' }] }).status, 'FAIL'); })); test('unproven React mount cannot become PASS and PARTIAL coverage stays PARTIAL', () => evidence((g, r) => { assert.equal(combineEvidence({ ...g, issues: [{ code: 'RENDER-001' }] }, r).status, 'NOT VERIFIED'); assert.equal(combineEvidence({ ...g, runtimeRequired: false }, r).status, 'NOT VERIFIED'); assert.equal(combineEvidence({ ...g, scope: 'PARTIAL' }, r).scope, 'PARTIAL'); })); const cssCheck = { name: 'Badge text', kind: 'COMPUTED_STYLE', width: 390, selector: '[data-slot="badge"]', property: 'color', actual: 'rgb(255, 255, 255)', expected: 'rgb(255, 255, 255)' }; const styleSite = { id: 'main.tsx:1:color', ruleId: 'STYLE-002', file: 'main.tsx', line: 1, component: 'Badge', selector: '[data-slot="badge"]', property: 'color', staticStatus: 'PASS', designIntentStatus: 'NOT VERIFIED' }; function siteChecks(site = styleSite) { return [390, 768, 1280, 1440].map(width => ({ ...cssCheck, width, height: 900, siteId: site.id, ruleId: site.ruleId, file: site.file, component: 'Badge', state: 'default', browser: { name: 'Chrome', version: 'test' } })); } test('site-specific CSS has independent STATIC RUNTIME DESIGN_INTENT FINAL verdicts', () => evidence((g, r) => { g.styleEvidence = { checks: [styleSite] }; const result = combineEvidence(g, { ...r, cssEvidence: { checks: siteChecks() } }); assert.equal(result.styleVerification.staticStatus, 'PASS'); assert.equal(result.styleVerification.runtimeStatus, 'PASS'); assert.equal(result.styleVerification.designIntentStatus, 'NOT VERIFIED'); assert.equal(result.status, 'NOT VERIFIED'); })); test('wrong sites, source, rule, browser and state cannot cover CSS uncertainty', () => evidence((g, r) => { g.styleEvidence = { checks: [styleSite] }; for (const change of [{ siteId: 'other' }, { file: 'other.tsx' }, { ruleId: 'OTHER' }, { browser: null }, { state: '' }, { property: 'backgroundColor' }]) { const result = combineEvidence(g, { ...r, cssEvidence: { checks: siteChecks().map(c => ({ ...c, ...change })) } }); assert.equal(result.styleVerification.runtimeStatus, 'NOT VERIFIED'); } })); test('observed dynamic Token validates declarations but never approves unobserved branches', () => evidence((g, r) => { const site = { id: 'dynamic:1', file: 'main.tsx', line: 1, ruleId: 'TOKEN-002', status: 'NOT VERIFIED' }; g.tokenEvidence = [{ definitions: ['--hm-valid'], dynamic: [site] }]; g.staticStatus = 'NOT VERIFIED'; const checks = siteChecks(site).map(c => ({ ...c, actualExpression: 'var(--hm-valid)', tokens: ['--hm-valid'] })); const result = combineEvidence(g, { ...r, cssEvidence: { checks } }); assert.equal(result.styleVerification.checks[0].runtimeStatus, 'PASS'); assert.equal(result.styleVerification.checks[0].finalStatus, 'NOT VERIFIED'); assert.equal(result.status, 'NOT VERIFIED'); })); test('undefined dynamic Token is a runtime FAIL despite computed fallback equality', () => evidence((g, r) => { const site = { id: 'dynamic:1', file: 'main.tsx', line: 1, ruleId: 'TOKEN-002', status: 'NOT VERIFIED' }; g.tokenEvidence = [{ definitions: ['--hm-valid'], dynamic: [site] }]; const checks = siteChecks(site).map(c => ({ ...c, status: 'PASS', actualExpression: 'var(--hm-missing, #fff)', tokens: ['--hm-missing'] })); assert.equal(combineEvidence(g, { ...r, cssEvidence: { checks } }).status, 'FAIL'); })); test('unknown dynamic expressions, incomplete declarations and missing width stay unverified', () => evidence((g, r) => { const site = { id: 'dynamic:1', file: 'main.tsx', line: 1, ruleId: 'TOKEN-002', status: 'NOT VERIFIED' }; g.tokenEvidence = [{ definitions: ['--hm-valid'], dynamic: [site] }]; for (const checks of [siteChecks(site), siteChecks(site).map(c => ({ ...c, actualExpression: 'var(--hm-valid)', tokens: [] })), siteChecks(site).slice(1)]) { assert.equal(combineEvidence(g, { ...r, cssEvidence: { checks } }).styleVerification.runtimeStatus, 'NOT VERIFIED'); } })); test('static issue FAIL dominates inconsistent summary PASS and runtime PASS', () => evidence((g, r) => { g.issues.push({ severity: 'FAIL', code: 'TOKEN-001' }); assert.equal(combineEvidence(g, r).status, 'FAIL'); })); test('document references with impossible line numbers are not accepted', () => evidence((g, r, dir) => { g.issues.push({ code: 'REUSE-003', file: 'main.tsx', line: 1 }); const reference = { file: 'contract.ts', line: 1000, sha256: createHash('sha256').update(fs.readFileSync(path.join(dir, 'contract.ts'))).digest('hex') }; const result = combineEvidence(g, { ...r, cssEvidence: { checks: [cssCheck], intentReviews: [{ issue: { code: 'REUSE-003', file: 'main.tsx', line: 1 }, rationale: 'Recipe', reference }] } }); assert.equal(result.cssEvidence.intentReviews.length, 0); })); test('unnamed interactions and duplicate CSS names cannot borrow a successful result', () => evidence((g, r) => { assert.equal(combineEvidence(g, { ...r, interactions: [{ status: 'PASS' }] }).status, 'NOT VERIFIED'); g.styleEvidence = { checks: [styleSite] }; const checks = siteChecks(); checks.push({ ...checks[0], actual: 'rgb(0, 0, 0)', status: 'PASS' }); assert.equal(combineEvidence(g, { ...r, cssEvidence: { checks } }).styleVerification.runtimeStatus, 'FAIL'); })); test('CSS evidence must address the source selector and Component; static uncertainty dominates', () => evidence((g, r) => { g.styleEvidence = { checks: [styleSite] }; for (const change of [{ selector: 'body' }, { component: 'Button' }]) assert.equal(combineEvidence(g, { ...r, cssEvidence: { checks: siteChecks().map(c => ({ ...c, ...change })) } }).styleVerification.runtimeStatus, 'NOT VERIFIED'); g.issues.push({ code: 'CSS-005', severity: 'NOT VERIFIED' }); assert.equal(combineEvidence(g, r).status, 'NOT VERIFIED'); })); test('CSS measurements and exact documentation references never approve intent or mutate raw issues', () => evidence((g, r, dir) => { g.staticStatus = 'NOT VERIFIED'; g.issues.push({ code: 'REUSE-003', file: 'main.tsx', line: 1 }); const before = JSON.stringify(g); const review = { issue: { code: 'REUSE-003', file: 'main.tsx', line: 1 }, rationale: 'Documented composition', reference: { file: 'contract.ts', line: 1, sha256: createHash('sha256').update(fs.readFileSync(path.join(dir, 'contract.ts'))).digest('hex') } }; const result = combineEvidence(g, { ...r, cssEvidence: { checks: [cssCheck], intentReviews: [review] } }); assert.equal(result.cssEvidence.status, 'PASS'); assert.equal(result.runtimeStatus, 'PASS'); assert.equal(result.status, 'NOT VERIFIED'); assert.equal(result.cssEvidence.intentReviews[0].status, 'NOT VERIFIED'); assert.equal(JSON.stringify(g), before); })); test('CSS mismatch remains a runtime FAIL even when a supplied status claims PASS', () => evidence((g, r) => { const result = combineEvidence(g, { ...r, cssEvidence: { checks: [{ ...cssCheck, actual: 'rgb(0, 0, 0)', status: 'PASS' }] } }); assert.equal(result.cssEvidence.status, 'FAIL'); assert.equal(result.runtimeStatus, 'FAIL'); assert.equal(result.status, 'FAIL'); })); test('missing, dynamic or invalid style measurements cannot claim CSS verification', () => evidence((g, r) => { for (const check of [{ ...cssCheck, actual: '' }, { ...cssCheck, kind: 'DESIGN_INTENT' }, { ...cssCheck, width: 320 }, { ...cssCheck, kind: 'CSS_VARIABLE', property: 'color' }]) { assert.equal(combineEvidence(g, { ...r, cssEvidence: { checks: [check] } }).cssEvidence.status, 'NOT VERIFIED'); } assert.equal(combineEvidence(g, { ...r, cssEvidence: null }).cssEvidence.status, 'NOT VERIFIED'); assert.equal(combineEvidence(g, r).cssEvidence.status, 'NOT VERIFIED'); })); test('changed source rejects supplied CSS checks and stale references do not become intent evidence', () => evidence((g, r, dir) => { const runtime = { ...r, cssEvidence: { checks: [cssCheck], intentReviews: [{ issue: { code: 'REUSE-003', file: 'main.tsx', line: 1 }, rationale: 'Example', reference: { file: 'contract.ts', line: 1, sha256: 'stale' } }] } }; assert.equal(combineEvidence(g, runtime).cssEvidence.intentReviews.length, 0); assert.equal(combineEvidence(g, runtime).cssEvidence.status, 'NOT VERIFIED'); fs.writeFileSync(path.join(dir, 'main.tsx'), 'changed'); const result = combineEvidence(g, runtime); assert.equal(result.cssEvidence.checks.length, 0); assert.equal(result.runtimeStatus, 'NOT VERIFIED'); })); test('resolved CSS variables are measured values and static violations still dominate', () => evidence((g, r) => { const runtime = { ...r, cssEvidence: { checks: [{ ...cssCheck, kind: 'CSS_VARIABLE', property: '--hm-badge-outline-text', actual: '#fff', expected: '#fff' }] } }; assert.equal(combineEvidence(g, runtime).cssEvidence.status, 'PASS'); assert.equal(combineEvidence({ ...g, staticStatus: 'FAIL', issues: [{ code: 'TOKEN-001' }] }, runtime).status, 'FAIL'); })); test('CSS review cannot link an outside path even with a correct hash; missing references stay unverified', () => evidence((g, r, dir) => { fs.mkdirSync(path.join(dir, 'references')); g.contractSource = path.join(dir, 'references'); g.issues.push({ code: 'REUSE-003', file: 'main.tsx', line: 1 }); const sha256 = createHash('sha256').update(fs.readFileSync(path.join(dir, 'main.tsx'))).digest('hex'); for (const reference of [{ file: '../main.tsx', line: 1, sha256 }, { file: path.join(dir, 'main.tsx'), line: 1, sha256 }, { file: 'missing.md', line: 1, sha256 }]) { const result = linkCssEvidence(g, { ...r, cssEvidence: { checks: [cssCheck], intentReviews: [{ issue: { code: 'REUSE-003', file: 'main.tsx', line: 1 }, rationale: 'Recipe', reference }] } }, true); assert.equal(result.status, 'NOT VERIFIED'); assert.equal(result.intentReviews.length, 0); } }));