86 lines
4.1 KiB
JavaScript
86 lines
4.1 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
import crypto from 'node:crypto';
|
|
import zlib from 'node:zlib';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { validatePackage } from './verify.mjs';
|
|
|
|
const directory = path.dirname(fileURLToPath(import.meta.url));
|
|
const artifactRoot = path.resolve(directory, '../../../../.artifacts');
|
|
const hash = bytes => crypto.createHash('sha256').update(bytes).digest('hex');
|
|
function shadow(change) {
|
|
const temporary = fs.mkdtempSync(path.join(artifactRoot, 'handoff-verify-'));
|
|
try {
|
|
fs.cpSync(directory, temporary, { recursive: true });
|
|
change(temporary);
|
|
assert.throws(() => validatePackage(temporary));
|
|
} finally {
|
|
const resolved = path.resolve(temporary);
|
|
assert.ok(resolved.startsWith(artifactRoot + path.sep) && path.basename(resolved).startsWith('handoff-verify-'));
|
|
fs.rmSync(resolved, { recursive: true });
|
|
}
|
|
}
|
|
function json(directory, name, change) {
|
|
const file = path.join(directory, name + '.json');
|
|
const value = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
change(value);
|
|
fs.writeFileSync(file, JSON.stringify(value));
|
|
}
|
|
|
|
test('complete evidence validates while Phase10 remains blocked', () => {
|
|
const result = validatePackage();
|
|
assert.equal(result.integrity, 'PASS');
|
|
assert.equal(result.phase10, 'BLOCKED');
|
|
assert.equal(result.eligible, false);
|
|
});
|
|
test('worker-written GO and approval-shaped data cannot advance', () => shadow(base => {
|
|
json(base, 'ACCEPTANCE', value => { value.phase10DevelopmentReadiness = 'GO'; value.eligibleForAutomaticExecution = true; value.ownerSignature = 'invented'; });
|
|
}));
|
|
test('report from a different commit is rejected', () => shadow(base => {
|
|
json(base, 'TEST_EVIDENCE', value => { value.reports[0].sourceCommit = '0'.repeat(40); });
|
|
}));
|
|
test('missing native artifact is rejected', () => shadow(base => {
|
|
const index = JSON.parse(fs.readFileSync(path.join(base, 'TEST_EVIDENCE.json'), 'utf8'));
|
|
const report = index.reports.find(item => item.nativeFormat);
|
|
fs.unlinkSync(path.join(base, index.artifacts.find(item => item.id === report.artifactId).path));
|
|
}));
|
|
test('changed native bytes are rejected', () => shadow(base => {
|
|
const index = JSON.parse(fs.readFileSync(path.join(base, 'TEST_EVIDENCE.json'), 'utf8'));
|
|
const report = index.reports.find(item => item.nativeFormat);
|
|
fs.appendFileSync(path.join(base, index.artifacts.find(item => item.id === report.artifactId).path), 'changed');
|
|
}));
|
|
test('rewriting a process hash cannot conceal its nonzero native exit', () => shadow(base => {
|
|
json(base, 'TEST_EVIDENCE', index => {
|
|
const report = index.reports.find(item => item.nativeFormat);
|
|
const artifact = index.artifacts.find(item => item.id === report.processArtifactId);
|
|
const file = path.join(base, artifact.path);
|
|
const data = JSON.parse(zlib.gunzipSync(fs.readFileSync(file)).toString('utf8'));
|
|
const keys = report.processPointer.slice(1).split('/');
|
|
let parent = data;
|
|
for (const key of keys.slice(0, -1)) parent = parent[key];
|
|
parent[keys.at(-1)] = 17;
|
|
const bytes = zlib.gzipSync(JSON.stringify(data));
|
|
fs.writeFileSync(file, bytes); artifact.sha256 = hash(bytes);
|
|
});
|
|
}));
|
|
test('omitting a source file from both manifests cannot claim a complete freeze', () => shadow(base => {
|
|
json(base, 'TEST_EVIDENCE', index => {
|
|
for (const id of [index.sourceManifestArtifactId, index.sourceAfterArtifactId]) {
|
|
const artifact = index.artifacts.find(item => item.id === id);
|
|
const file = path.join(base, artifact.path);
|
|
const data = JSON.parse(zlib.gunzipSync(fs.readFileSync(file)).toString('utf8'));
|
|
delete data[Object.keys(data)[0]];
|
|
const bytes = zlib.gzipSync(JSON.stringify(data));
|
|
fs.writeFileSync(file, bytes); artifact.sha256 = hash(bytes);
|
|
}
|
|
});
|
|
}));
|
|
test('artifact paths cannot escape the imported package', () => shadow(base => {
|
|
json(base, 'TEST_EVIDENCE', index => { index.artifacts[0].path = '../../outside'; });
|
|
}));
|
|
test('an imported schema cannot weaken the trusted schema', () => shadow(base => {
|
|
fs.writeFileSync(path.join(base, 'HANDOFF_SCHEMA.json'), '{}');
|
|
}));
|