ci: аттестовать локальную WSL-приёмку golden (#641)

Issue: #641
User-Visible: no
This commit is contained in:
Claude
2026-09-23 19:16:09 +03:00
parent 284644acc8
commit ee6ca4f3c9
59 changed files with 1199 additions and 431 deletions
+9 -1
View File
@@ -265,7 +265,10 @@ test('#541: uploaded deflated artifact is read without an external ZIP dependenc
const KEYS = Object.fromEntries(REUSE_JOBS.map((job, index) => [job, String(index + 1).repeat(64)]));
const evidenceOf = (over = {}) => ({
product: { tree: 'p'.repeat(64) },
baselines: { tree: 'c'.repeat(40), manifestSha256: 'd'.repeat(64), reviewedRun: 34853080375 },
baselines: {
tree: 'c'.repeat(40), manifestSha256: 'd'.repeat(64),
reviewedRun: 34853080375, reviewedLocal: null,
},
keys: { ...KEYS },
...over,
});
@@ -334,6 +337,10 @@ test('#573 AC4: подмена content-ключа, product tree, overlay, инд
assert.match(withExpected({ baselines: { tree: 'x'.repeat(40), manifestSha256: 'd'.repeat(64), reviewedRun: 34853080375 } }).note, /baselines\.tree/);
assert.match(withExpected({ baselines: { tree: 'c'.repeat(40), manifestSha256: 'y'.repeat(64), reviewedRun: 34853080375 } }).note, /manifestSha256/);
assert.match(withExpected({ baselines: { tree: 'c'.repeat(40), manifestSha256: 'd'.repeat(64), reviewedRun: 1 } }).note, /reviewedRun/);
assert.match(withExpected({ baselines: {
tree: 'c'.repeat(40), manifestSha256: 'd'.repeat(64),
reviewedRun: 34853080375, reviewedLocal: 'a'.repeat(64),
} }).note, /reviewedLocal/);
// маркер реюза ссылается на ключ, отличный от ключа кандидата — внутренняя несогласованность
const tampered = structuredClone(fixture.proof);
tampered.checks.smoke.reuse.key = 'a'.repeat(64);
@@ -432,6 +439,7 @@ test('#573: evidence живого дерева считается детерми
assert.match(first.product.tree, /^[0-9a-f]{64}$/);
assert.match(first.baselines.tree, /^[0-9a-f]{40}$/);
assert.match(first.baselines.manifestSha256, /^[0-9a-f]{64}$/);
assert.equal(first.baselines.reviewedLocal, null);
for (const job of REUSE_JOBS) assert.equal(first.keys[job], reuseKey(root, job));
assert.throws(() => localEvidence(root, { keys: { ...first.keys, smoke: 'f'.repeat(64) } }), /smoke: reuse job key/);
});
+18
View File
@@ -79,6 +79,24 @@ test('golden files require exact release-review provenance', () => {
), []);
});
test('#641: a local WSL review trailer is exclusive and bound to the accepted index', () => {
const changed = ['demo/golden/baselines/example.png'];
const digest = 'a'.repeat(64);
const base = 'Update baseline\n\nIssue: #641\nUser-Visible: no\nRelease: v1.2.3-beta.1';
const local = `${base}\nBaseline-Reviewed-Local: sha256:${digest}`;
const index = { localAttestation: { sha256: digest } };
assert.deepEqual(validateCommitMessage(local, changed, { baselineIndex: index }), []);
assert.match(validateCommitMessage(local, changed, {
baselineIndex: { localAttestation: { sha256: 'b'.repeat(64) } },
}).join('\n'), /does not match/);
assert.match(validateCommitMessage(
`${local}\nBaseline-Reviewed: https:\/\/example.test\/run`, changed, { baselineIndex: index },
).join('\n'), /requires one Baseline-Reviewed or Baseline-Reviewed-Local/);
assert.match(validateCommitMessage(
`${base}\nBaseline-Reviewed-Local: sha256:ABC`, changed, { baselineIndex: index },
).join('\n'), /64 lowercase hex/);
});
test('the audited beta.2 baseline exception is exact and golden-only', () => {
const changed = ['demo/golden/baselines/example.png'];
const message = 'Update baseline\n\nIssue: #426\nUser-Visible: no';
+9 -1
View File
@@ -65,7 +65,14 @@ function fixture({ platform = 'linux', schema = CAPTURE_PROVENANCE_SCHEMA, captu
};
if (schema >= CAPTURE_PROVENANCE_SCHEMA) {
report.capture = capture === undefined
? { ...captureProvenance({ chromium: index.chromium, buildFingerprint: report.buildFingerprint, env: {} }), platform }
? { ...captureProvenance({
chromium: index.chromium,
buildFingerprint: report.buildFingerprint,
env: {
GITHUB_RUN_ID: '1', GITHUB_RUN_ATTEMPT: '1',
GITHUB_REPOSITORY: 'Matysh/houseplan-card', GITHUB_SHA: 'a'.repeat(40),
},
}), platform }
: capture;
}
writeFileSync(resolve(dir, 'golden-report.json'), `${JSON.stringify(report, null, 2)}\n`);
@@ -112,6 +119,7 @@ test('#571 AC1: артефакт Linux принимается, обе сторо
assert.equal(index.acceptedOn, process.platform, 'платформа приёмки — своя');
assert.equal(indexCapturedOn(index), 'linux');
assert.equal(index.capture.chromium, index.chromium);
assert.equal(index.localAttestation, null, 'CI source is not presented as local WSL');
assert.deepEqual(index.foreignCapture, HOST_TEST_ALLOWANCE ? { reason: HOST_TEST_ALLOWANCE } : null,
'не-Linux хост теста оставляет явный след осознанного обхода');
rmSync(from, { recursive: true, force: true });
+206
View File
@@ -0,0 +1,206 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import { createHash } from 'node:crypto';
import {
copyFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync,
} from 'node:fs';
import { tmpdir } from 'node:os';
import { resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import { CAPTURE_PROVENANCE_SCHEMA } from '../scripts/capture-environment.mjs';
import {
WSL_ATTESTATION_FILE, createWslAttestation, environmentRefusal,
repositoryRefusal, verifyWslAttestation,
} from '../scripts/golden-wsl-artifact.mjs';
import { GOLDEN_MATRIX_VERSION, GOLDEN_SCENARIOS } from '../demo/golden/matrix.mjs';
import { sourceFingerprint } from '../scripts/source-fingerprint.mjs';
import { pinsFromSources } from '../scripts/toolchain-pins.mjs';
const ROOT = resolve(fileURLToPath(new URL('../', import.meta.url)));
const BASELINES = resolve(ROOT, 'demo/golden/baselines');
const digest = (bytes) => createHash('sha256').update(bytes).digest('hex');
const source = Object.freeze({
repository: 'Matysh/houseplan-card', branch: 'issue/641-wsl-golden-attestation',
commit: 'a'.repeat(40), tree: 'b'.repeat(40), remoteSha: 'a'.repeat(40),
clean: true, status: '',
});
const environment = Object.freeze({
platform: 'linux', arch: 'x64', kernel: '6.6.0-microsoft-standard-WSL2',
wsl: true, distro: 'Ubuntu', filesystem: 'ext2/ext3',
});
const toolchain = () => {
const pins = pinsFromSources();
return {
pins,
node: `${pins.node}.0.0`,
npm: '10.9.0',
playwright: pins.playwright,
chromiumExecutable: '/home/test/chromium',
chromiumExecutableSha256: 'c'.repeat(64),
};
};
function artifact() {
const dir = mkdtempSync(resolve(tmpdir(), 'hp-golden-wsl-'));
const actualRoot = resolve(dir, 'actual');
mkdirSync(actualRoot, { recursive: true });
const index = JSON.parse(readFileSync(resolve(BASELINES, 'baselines-index.json'), 'utf8'));
const results = GOLDEN_SCENARIOS.map((scenario) => {
const baseline = resolve(BASELINES, `${scenario.id}.png`);
assert.equal(existsSync(baseline), true, `test fixture needs reviewed baseline ${scenario.id}`);
const actual = resolve(actualRoot, `${scenario.id}.png`);
copyFileSync(baseline, actual);
const sha = digest(readFileSync(actual));
return { id: scenario.id, status: 'passed', actualSha256: sha, baselineSha256: sha };
});
const report = {
schema: CAPTURE_PROVENANCE_SCHEMA,
mode: 'capture',
capture: {
platform: 'linux', arch: 'x64', chromium: index.chromium,
buildFingerprint: sourceFingerprint(ROOT), ci: null,
},
generatedAt: '2026-09-23T00:00:00.000Z',
matrixVersion: GOLDEN_MATRIX_VERSION,
buildFingerprint: sourceFingerprint(ROOT),
chromium: index.chromium,
results,
};
writeFileSync(resolve(dir, 'golden-report.json'), `${JSON.stringify(report, null, 2)}\n`);
return dir;
}
const intent = Object.freeze({ expectChange: [], expectNew: [], noWitnesses: false, reason: '' });
test('#641: repository and WSL/ext4 preconditions fail closed', () => {
assert.equal(repositoryRefusal(source), null);
assert.match(repositoryRefusal({ ...source, repository: '' }), /repository/);
assert.match(repositoryRefusal({ ...source, clean: false, status: ' M src/x.ts' }), /чистое/);
assert.match(repositoryRefusal({ ...source, remoteSha: 'd'.repeat(40) }), /не совпадает/);
assert.equal(environmentRefusal(environment), null);
assert.match(environmentRefusal({ ...environment, platform: 'win32', wsl: false }), /только внутри WSL/);
assert.match(environmentRefusal({ ...environment, filesystem: '9p' }), /ext4/);
assert.match(environmentRefusal({ ...environment, distro: null }), /distro/);
});
test('#641: complete WSL artifact is self-hashed and can be verified before acceptance', async () => {
const dir = artifact();
try {
const tc = toolchain();
const attestation = await createWslAttestation({
root: ROOT, artifactRoot: dir, intent, source, environment, toolchain: tc,
createdAt: '2026-09-23T00:00:01.000Z',
});
assert.match(attestation.sha256, /^[0-9a-f]{64}$/);
assert.equal(attestation.command, 'npm run golden:wsl:capture');
assert.equal(attestation.frames.length, GOLDEN_SCENARIOS.length);
assert.ok(attestation.witnesses.count >= attestation.witnesses.floor);
writeFileSync(resolve(dir, WSL_ATTESTATION_FILE), `${JSON.stringify(attestation, null, 2)}\n`);
const verified = await verifyWslAttestation({
root: ROOT, artifactRoot: dir, intent,
currentSource: source, currentEnvironment: environment, currentToolchain: tc,
});
assert.equal(verified.sha256, attestation.sha256);
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test('#641: tampered intent, source, toolchain and PNG are rejected', async () => {
const dir = artifact();
try {
const tc = toolchain();
const attestation = await createWslAttestation({
root: ROOT, artifactRoot: dir, intent, source, environment, toolchain: tc,
});
writeFileSync(resolve(dir, WSL_ATTESTATION_FILE), `${JSON.stringify(attestation, null, 2)}\n`);
await assert.rejects(() => verifyWslAttestation({
root: ROOT, artifactRoot: dir,
intent: { ...intent, expectChange: [GOLDEN_SCENARIOS[0].id] },
currentSource: source, currentEnvironment: environment, currentToolchain: tc,
}), /intent differs/);
await assert.rejects(() => verifyWslAttestation({
root: ROOT, artifactRoot: dir, intent,
currentSource: { ...source, commit: 'd'.repeat(40), remoteSha: 'd'.repeat(40) },
currentEnvironment: environment, currentToolchain: tc,
}), /another repository, branch, commit or tree/);
await assert.rejects(() => verifyWslAttestation({
root: ROOT, artifactRoot: dir, intent, currentSource: source,
currentEnvironment: environment, currentToolchain: { ...tc, npm: '11.0.0' },
}), /toolchain changed/);
const victim = resolve(dir, 'actual', `${GOLDEN_SCENARIOS[0].id}.png`);
writeFileSync(victim, Buffer.concat([readFileSync(victim), Buffer.from([0])]));
await assert.rejects(() => verifyWslAttestation({
root: ROOT, artifactRoot: dir, intent,
currentSource: source, currentEnvironment: environment, currentToolchain: tc,
}), /candidate changed after capture/);
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test('#641: incomplete matrix cannot be attested', async () => {
const dir = artifact();
try {
rmSync(resolve(dir, 'actual', `${GOLDEN_SCENARIOS[0].id}.png`));
await assert.rejects(() => createWslAttestation({
root: ROOT, artifactRoot: dir, intent,
source, environment, toolchain: toolchain(),
}), /complete current golden matrix/);
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test('#641: duplicate result rows are not a complete matrix', async () => {
const dir = artifact();
try {
const reportPath = resolve(dir, 'golden-report.json');
const report = JSON.parse(readFileSync(reportPath, 'utf8'));
report.results.push({ ...report.results[0] });
writeFileSync(reportPath, `${JSON.stringify(report, null, 2)}\n`);
await assert.rejects(() => createWslAttestation({
root: ROOT, artifactRoot: dir, intent,
source, environment, toolchain: toolchain(),
}), /complete current golden matrix/);
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test('#641: stale fingerprints, toolchain drift and undeclared diffs cannot be attested', async () => {
const stale = artifact();
const drifted = artifact();
const unexpected = artifact();
try {
const staleReportPath = resolve(stale, 'golden-report.json');
const staleReport = JSON.parse(readFileSync(staleReportPath, 'utf8'));
staleReport.buildFingerprint = 'd'.repeat(64);
writeFileSync(staleReportPath, `${JSON.stringify(staleReport, null, 2)}\n`);
await assert.rejects(() => createWslAttestation({
root: ROOT, artifactRoot: stale, intent,
source, environment, toolchain: toolchain(),
}), /current frontend source/);
await assert.rejects(() => createWslAttestation({
root: ROOT, artifactRoot: drifted, intent,
source, environment,
toolchain: { ...toolchain(), playwright: '0.0.0' },
}), /toolchain расходится/);
const reportPath = resolve(unexpected, 'golden-report.json');
const report = JSON.parse(readFileSync(reportPath, 'utf8'));
report.results[0].status = 'different';
writeFileSync(reportPath, `${JSON.stringify(report, null, 2)}\n`);
await assert.rejects(() => createWslAttestation({
root: ROOT, artifactRoot: unexpected, intent,
source, environment, toolchain: toolchain(),
}), /менять не собирались/);
} finally {
rmSync(stale, { recursive: true, force: true });
rmSync(drifted, { recursive: true, force: true });
rmSync(unexpected, { recursive: true, force: true });
}
});
+4
View File
@@ -98,6 +98,10 @@ test('a class D only commit needs a release or a reviewed baseline', () => {
'demo/golden/baselines/a.png',
]);
assert.deepEqual(rules(evaluateCommit(reviewed)), []);
const reviewedLocally = commit('Accept baselines', `Issue: #1\nBaseline-Reviewed-Local: sha256:${'a'.repeat(64)}`, [
'demo/golden/baselines/a.png',
]);
assert.deepEqual(rules(evaluateCommit(reviewedLocally)), []);
});
test('beta candidates are ordinary commits, stable releases are not', () => {