This commit is contained in:
+78
-27
@@ -22,6 +22,33 @@ export async function listProspectiveCommits(root, activationCommit, head = 'HEA
|
||||
return result.stdout.trim() ? result.stdout.trim().split('\n') : [];
|
||||
}
|
||||
|
||||
export function deriveHistoryBoundary(root, head = 'HEAD') {
|
||||
const introductions = git(root, [
|
||||
'log',
|
||||
'--first-parent',
|
||||
'--diff-filter=A',
|
||||
'--format=%H',
|
||||
'--reverse',
|
||||
head,
|
||||
'--',
|
||||
'gates/gates.manifest.json',
|
||||
])
|
||||
.stdout.trim()
|
||||
.split('\n')
|
||||
.filter(Boolean);
|
||||
if (introductions.length === 0) {
|
||||
throw new Error('history boundary cannot be derived: registry introduction is absent');
|
||||
}
|
||||
const introductionCommit = introductions[0];
|
||||
const parent = git(root, ['rev-parse', `${introductionCommit}^`], { allowFailure: true });
|
||||
if (parent.status !== 0 || !parent.stdout.trim()) {
|
||||
throw new Error(
|
||||
`history boundary cannot be derived: registry introduction ${introductionCommit} has no parent`,
|
||||
);
|
||||
}
|
||||
return { activationCommit: parent.stdout.trim(), introductionCommit };
|
||||
}
|
||||
|
||||
export async function readManifestAtCommit(root, commit) {
|
||||
const result = git(root, ['show', `${commit}:gates/gates.manifest.json`]);
|
||||
return JSON.parse(result.stdout);
|
||||
@@ -198,15 +225,8 @@ export async function replayCommit(root, commit) {
|
||||
}
|
||||
}
|
||||
|
||||
export function assessProviderEvidence(commit, pipelines) {
|
||||
const matches = pipelines.filter((candidate) => candidate.commit === commit);
|
||||
if (matches.length === 0) {
|
||||
return {
|
||||
state: 'absent',
|
||||
detail:
|
||||
'no retained provider record was supplied; retention expiry and never-ran are not inferred',
|
||||
};
|
||||
}
|
||||
export function validateProviderEvidenceCollection(pipelines) {
|
||||
if (!Array.isArray(pipelines)) return 'provider evidence collection is malformed';
|
||||
const pipelineStates = new Set(['success', 'failure', 'error', 'pending', 'running', 'queued']);
|
||||
const stepStates = new Set([
|
||||
'success',
|
||||
@@ -217,9 +237,11 @@ export function assessProviderEvidence(commit, pipelines) {
|
||||
'queued',
|
||||
'skipped',
|
||||
]);
|
||||
const numbers = matches.map((candidate) => candidate.number);
|
||||
const malformed = matches.some(
|
||||
const malformed = pipelines.some(
|
||||
(candidate) =>
|
||||
!candidate ||
|
||||
typeof candidate !== 'object' ||
|
||||
Array.isArray(candidate) ||
|
||||
typeof candidate.commit !== 'string' ||
|
||||
candidate.commit.length === 0 ||
|
||||
!Number.isInteger(candidate.number) ||
|
||||
@@ -227,15 +249,43 @@ export function assessProviderEvidence(commit, pipelines) {
|
||||
!Array.isArray(candidate.steps) ||
|
||||
candidate.steps.some(
|
||||
(step) =>
|
||||
typeof step?.name !== 'string' ||
|
||||
typeof step?.status !== 'string' ||
|
||||
!step ||
|
||||
typeof step !== 'object' ||
|
||||
Array.isArray(step) ||
|
||||
typeof step.name !== 'string' ||
|
||||
typeof step.status !== 'string' ||
|
||||
!stepStates.has(step.status),
|
||||
),
|
||||
);
|
||||
if (malformed || new Set(numbers).size !== numbers.length) {
|
||||
if (malformed) return 'provider records are malformed';
|
||||
const ambiguousGateRecord = pipelines.find(
|
||||
(candidate) => candidate.steps.filter((step) => step.name === 'gate-verify').length !== 1,
|
||||
);
|
||||
if (ambiguousGateRecord) {
|
||||
const count = ambiguousGateRecord.steps.filter((step) => step.name === 'gate-verify').length;
|
||||
return `provider record ${ambiguousGateRecord.number} has ambiguous gate-verify step count ${count}`;
|
||||
}
|
||||
const numbers = pipelines.map((candidate) => candidate.number);
|
||||
if (new Set(numbers).size !== numbers.length) {
|
||||
return 'duplicate pipeline identity across commits in provider evidence collection';
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function assessProviderEvidence(commit, pipelines) {
|
||||
const collectionFailure = validateProviderEvidenceCollection(pipelines);
|
||||
if (collectionFailure) {
|
||||
return {
|
||||
state: 'terminal-failure',
|
||||
detail: 'provider records are malformed or have ambiguous pipeline numbers',
|
||||
detail: collectionFailure,
|
||||
};
|
||||
}
|
||||
const matches = pipelines.filter((candidate) => candidate.commit === commit);
|
||||
if (matches.length === 0) {
|
||||
return {
|
||||
state: 'absent',
|
||||
detail:
|
||||
'no retained provider record was supplied; retention expiry and never-ran are not inferred',
|
||||
};
|
||||
}
|
||||
const pipeline = [...matches].sort((left, right) => right.number - left.number)[0];
|
||||
@@ -279,19 +329,16 @@ export async function verifyHistory({ root, manifest }) {
|
||||
const failures = [];
|
||||
const observations = [];
|
||||
const head = git(root, ['rev-parse', 'HEAD']).stdout.trim();
|
||||
if (!manifest.activationCommit) {
|
||||
failures.push('history activationCommit is missing');
|
||||
return { failures, observations };
|
||||
}
|
||||
const activationCheck = git(
|
||||
root,
|
||||
['merge-base', '--is-ancestor', manifest.activationCommit, head],
|
||||
{ allowFailure: true },
|
||||
);
|
||||
if (activationCheck.status !== 0) {
|
||||
if (Object.hasOwn(manifest, 'activationCommit')) {
|
||||
failures.push(
|
||||
`history activation commit ${manifest.activationCommit} is not an ancestor of ${head}`,
|
||||
'author-controlled activationCommit is forbidden; history boundary is derived from the registry introduction',
|
||||
);
|
||||
}
|
||||
let boundary;
|
||||
try {
|
||||
boundary = deriveHistoryBoundary(root, head);
|
||||
} catch (error) {
|
||||
failures.push(error.message);
|
||||
return { failures, observations };
|
||||
}
|
||||
const onMain = isMainCommit(root, head);
|
||||
@@ -311,7 +358,11 @@ export async function verifyHistory({ root, manifest }) {
|
||||
}
|
||||
|
||||
const pipelines = onMain ? await loadProviderEvidence() : [];
|
||||
const commits = await listProspectiveCommits(root, manifest.activationCommit, head);
|
||||
const collectionFailure = validateProviderEvidenceCollection(pipelines);
|
||||
if (collectionFailure) {
|
||||
failures.push(`provider evidence collection invalid: ${collectionFailure}`);
|
||||
}
|
||||
const commits = await listProspectiveCommits(root, boundary.activationCommit, head);
|
||||
for (const commit of commits) {
|
||||
let commitManifest;
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user