This commit is contained in:
@@ -6,6 +6,7 @@ import test from 'node:test';
|
||||
|
||||
import {
|
||||
assessProviderEvidence,
|
||||
deriveHistoryBoundary,
|
||||
listProspectiveCommits,
|
||||
readManifestAtCommit,
|
||||
replayCommit,
|
||||
@@ -285,7 +286,7 @@ test('PR verification states the RM-60 boundary without executing an intermediat
|
||||
try {
|
||||
const result = await verifyHistory({
|
||||
root,
|
||||
manifest: { schemaVersion: 1, activationCommit: activation },
|
||||
manifest: { schemaVersion: 1 },
|
||||
});
|
||||
assert.deepEqual(result.failures, []);
|
||||
assert.ok(
|
||||
@@ -305,6 +306,169 @@ test('PR verification states the RM-60 boundary without executing an intermediat
|
||||
}
|
||||
});
|
||||
|
||||
test('derived history boundary includes the registry-introduction commit', async () => {
|
||||
const root = `${fixtureRoot}-derived-boundary`;
|
||||
await rm(root, { recursive: true, force: true });
|
||||
await mkdir(root, { recursive: true });
|
||||
git(root, 'init', '-q');
|
||||
git(root, 'config', 'user.name', 'gate-test');
|
||||
git(root, 'config', 'user.email', '[email protected]');
|
||||
await writeFile(path.join(root, 'baseline.txt'), 'baseline\n');
|
||||
git(root, 'add', '.');
|
||||
git(root, 'commit', '-m', 'baseline');
|
||||
const baseline = git(root, 'rev-parse', 'HEAD');
|
||||
await mkdir(path.join(root, 'gates'), { recursive: true });
|
||||
await writeFile(path.join(root, 'gates', 'gates.manifest.json'), '{"schemaVersion":1}\n');
|
||||
git(root, 'add', '.');
|
||||
git(root, 'commit', '-m', 'registry introduction');
|
||||
const introduction = git(root, 'rev-parse', 'HEAD');
|
||||
await writeFile(path.join(root, 'later.txt'), 'later\n');
|
||||
git(root, 'add', '.');
|
||||
git(root, 'commit', '-m', 'later');
|
||||
const head = git(root, 'rev-parse', 'HEAD');
|
||||
|
||||
assert.deepEqual(deriveHistoryBoundary(root, head), {
|
||||
activationCommit: baseline,
|
||||
introductionCommit: introduction,
|
||||
});
|
||||
assert.deepEqual(await listProspectiveCommits(root, baseline, head), [introduction, head]);
|
||||
});
|
||||
|
||||
test('author-controlled activation seams cannot omit registry-era history', async () => {
|
||||
const root = `${fixtureRoot}-activation-seam`;
|
||||
await rm(root, { recursive: true, force: true });
|
||||
await mkdir(path.join(root, 'gates'), { recursive: true });
|
||||
git(root, 'init', '-q');
|
||||
git(root, 'config', 'user.name', 'gate-test');
|
||||
git(root, 'config', 'user.email', '[email protected]');
|
||||
await writeFile(path.join(root, 'gates', 'gates.manifest.json'), '{"schemaVersion":1}\n');
|
||||
git(root, 'add', '.');
|
||||
git(root, 'commit', '-m', 'registry introduction');
|
||||
const introduction = git(root, 'rev-parse', 'HEAD');
|
||||
await writeFile(path.join(root, 'one.txt'), 'one\n');
|
||||
git(root, 'add', '.');
|
||||
git(root, 'commit', '-m', 'one');
|
||||
await writeFile(path.join(root, 'two.txt'), 'two\n');
|
||||
git(root, 'add', '.');
|
||||
git(root, 'commit', '-m', 'two');
|
||||
const head = git(root, 'rev-parse', 'HEAD');
|
||||
const parent = git(root, 'rev-parse', 'HEAD^');
|
||||
|
||||
const previousBranch = process.env.CI_COMMIT_BRANCH;
|
||||
process.env.CI_COMMIT_BRANCH = 'feature/activation-seam';
|
||||
try {
|
||||
for (const candidate of [head, parent, introduction]) {
|
||||
const result = await verifyHistory({
|
||||
root,
|
||||
manifest: { schemaVersion: 1, activationCommit: candidate },
|
||||
});
|
||||
assert.match(result.failures.join('\n'), /author-controlled activationCommit.*forbidden/i);
|
||||
}
|
||||
} finally {
|
||||
if (previousBranch === undefined) delete process.env.CI_COMMIT_BRANCH;
|
||||
else process.env.CI_COMMIT_BRANCH = previousBranch;
|
||||
}
|
||||
});
|
||||
|
||||
test('globally invalid provider evidence fails when HEAD is the only prospective commit', async () => {
|
||||
const root = `${fixtureRoot}-head-only-evidence`;
|
||||
await rm(root, { recursive: true, force: true });
|
||||
await mkdir(root, { recursive: true });
|
||||
git(root, 'init', '-q');
|
||||
git(root, 'config', 'user.name', 'gate-test');
|
||||
git(root, 'config', 'user.email', '[email protected]');
|
||||
await writeFile(path.join(root, 'baseline.txt'), 'baseline\n');
|
||||
git(root, 'add', '.');
|
||||
git(root, 'commit', '-m', 'baseline');
|
||||
await mkdir(path.join(root, 'gates'), { recursive: true });
|
||||
await writeFile(path.join(root, 'gates', 'gates.manifest.json'), '{"schemaVersion":1}\n');
|
||||
git(root, 'add', '.');
|
||||
git(root, 'commit', '-m', 'registry introduction');
|
||||
const head = git(root, 'rev-parse', 'HEAD');
|
||||
const evidenceFile = path.join(root, 'provider-evidence.json');
|
||||
await writeFile(
|
||||
evidenceFile,
|
||||
JSON.stringify([
|
||||
{
|
||||
commit: head,
|
||||
number: 7,
|
||||
status: 'success',
|
||||
steps: [{ name: 'gate-verify', status: 'success' }],
|
||||
},
|
||||
{
|
||||
commit: 'other-subject',
|
||||
number: 7,
|
||||
status: 'success',
|
||||
steps: [{ name: 'gate-verify', status: 'success' }],
|
||||
},
|
||||
]),
|
||||
);
|
||||
const previousBranch = process.env.CI_COMMIT_BRANCH;
|
||||
const previousEvidence = process.env.GATE_PROVIDER_EVIDENCE_FILE;
|
||||
process.env.CI_COMMIT_BRANCH = 'main';
|
||||
process.env.GATE_PROVIDER_EVIDENCE_FILE = evidenceFile;
|
||||
try {
|
||||
const result = await verifyHistory({ root, manifest: { schemaVersion: 1 } });
|
||||
assert.match(result.failures.join('\n'), /duplicate pipeline identity across commits/i);
|
||||
} finally {
|
||||
if (previousBranch === undefined) delete process.env.CI_COMMIT_BRANCH;
|
||||
else process.env.CI_COMMIT_BRANCH = previousBranch;
|
||||
if (previousEvidence === undefined) delete process.env.GATE_PROVIDER_EVIDENCE_FILE;
|
||||
else process.env.GATE_PROVIDER_EVIDENCE_FILE = previousEvidence;
|
||||
}
|
||||
});
|
||||
|
||||
test('collection-wide validation rejects ambiguous gate steps on unrelated commits', () => {
|
||||
const records = [
|
||||
{
|
||||
commit: 'target',
|
||||
number: 7,
|
||||
status: 'success',
|
||||
steps: [{ name: 'gate-verify', status: 'success' }],
|
||||
},
|
||||
{
|
||||
commit: 'unrelated',
|
||||
number: 8,
|
||||
status: 'success',
|
||||
steps: [
|
||||
{ name: 'gate-verify', status: 'success' },
|
||||
{ name: 'gate-verify', status: 'failure' },
|
||||
],
|
||||
},
|
||||
];
|
||||
const result = assessProviderEvidence('target', records);
|
||||
assert.equal(result.state, 'terminal-failure');
|
||||
assert.match(result.detail, /ambiguous gate-verify step count/i);
|
||||
});
|
||||
|
||||
test('provider evidence rejects non-object collection entries without crashing', () => {
|
||||
for (const record of [null, [], 'text', 42]) {
|
||||
const result = assessProviderEvidence('aaa', [record]);
|
||||
assert.equal(result.state, 'terminal-failure');
|
||||
assert.match(result.detail, /malformed/i);
|
||||
}
|
||||
});
|
||||
|
||||
test('provider evidence rejects duplicate pipeline identity across commits', () => {
|
||||
const records = [
|
||||
{
|
||||
commit: 'aaa',
|
||||
number: 7,
|
||||
status: 'success',
|
||||
steps: [{ name: 'gate-verify', status: 'success' }],
|
||||
},
|
||||
{
|
||||
commit: 'bbb',
|
||||
number: 7,
|
||||
status: 'success',
|
||||
steps: [{ name: 'gate-verify', status: 'success' }],
|
||||
},
|
||||
];
|
||||
const result = assessProviderEvidence('aaa', records);
|
||||
assert.equal(result.state, 'terminal-failure');
|
||||
assert.match(result.detail, /duplicate pipeline.*across.*commit|global.*pipeline.*identity/i);
|
||||
});
|
||||
|
||||
test('provider evidence distinguishes retained success, failure, and absent history', () => {
|
||||
const pipelines = [
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user