Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 22 additions & 16 deletions .github/scripts/compare-independent-builds.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,22 @@ import {
import { isAbsolute, relative, resolve, sep } from 'node:path';

const [standaloneOneManifest, standaloneTwoManifest,
localOneManifest, localTwoManifest,
standaloneOneRoot, standaloneTwoRoot, localOneRoot, localTwoRoot,
standaloneOneGradle, standaloneTwoGradle, localOneGradle, localTwoGradle,
standaloneThreeManifest, standaloneFourManifest,
standaloneOneRoot, standaloneTwoRoot, standaloneThreeRoot,
standaloneFourRoot, standaloneOneGradle, standaloneTwoGradle,
standaloneThreeGradle, standaloneFourGradle,
bexCommit, outputPath] = process.argv.slice(2);
if (!standaloneOneManifest || !standaloneTwoManifest ||
!localOneManifest || !localTwoManifest ||
!standaloneThreeManifest || !standaloneFourManifest ||
!standaloneOneRoot || !standaloneTwoRoot ||
!localOneRoot || !localTwoRoot ||
!standaloneThreeRoot || !standaloneFourRoot ||
!standaloneOneGradle || !standaloneTwoGradle ||
!localOneGradle || !localTwoGradle || !bexCommit || !outputPath) {
!standaloneThreeGradle || !standaloneFourGradle ||
!bexCommit || !outputPath) {
throw new Error(
'usage: compare-independent-builds.mjs S1 S2 L1 L2 ' +
'S1_ROOT S2_ROOT L1_ROOT L2_ROOT S1_GRADLE S2_GRADLE ' +
'L1_GRADLE L2_GRADLE COMMIT OUTPUT'
'usage: compare-independent-builds.mjs S1 S2 S3 S4 ' +
'S1_ROOT S2_ROOT S3_ROOT S4_ROOT S1_GRADLE S2_GRADLE ' +
'S3_GRADLE S4_GRADLE COMMIT OUTPUT'
);
}

Expand Down Expand Up @@ -219,6 +221,7 @@ function pair(first, second) {
const passed = exactManifestBytesMatch && exactArtifactBytesMatch &&
requiredArtifactsPresent;
return {
mode: 'standalone-published',
status: passed ? 'passed' : 'failed',
exactManifestBytesMatch,
exactArtifactBytesMatch,
Expand All @@ -239,8 +242,10 @@ const builds = [
standaloneOneRoot, standaloneOneGradle),
buildEvidence(standaloneTwoManifest,
standaloneTwoRoot, standaloneTwoGradle),
buildEvidence(localOneManifest, localOneRoot, localOneGradle),
buildEvidence(localTwoManifest, localTwoRoot, localTwoGradle)
buildEvidence(standaloneThreeManifest,
standaloneThreeRoot, standaloneThreeGradle),
buildEvidence(standaloneFourManifest,
standaloneFourRoot, standaloneFourGradle)
];
const checkoutRoots = builds.map((build) => build.report.checkoutRoot);
const gitDirectories = builds.map((build) => build.report.gitDirectory);
Expand All @@ -251,14 +256,15 @@ const distinctGitDirectories = new Set(gitDirectories).size === 4;
const distinctGradleHomes = new Set(gradleHomes).size === 4;
const distinctInputManifestFiles = new Set(manifestPaths).size === 4;
const standalonePublished = pair(builds[0], builds[1]);
const localComposite = pair(builds[2], builds[3]);
const standalonePublishedReplica = pair(builds[2], builds[3]);
const passed = distinctCheckoutRoots && distinctGitDirectories &&
distinctGradleHomes && distinctInputManifestFiles &&
standalonePublished.status === 'passed' &&
localComposite.status === 'passed';
standalonePublishedReplica.status === 'passed';
const report = {
schema: 'blue-bex-independent-clean-builds/2.1',
schema: 'blue-bex-independent-clean-builds/3.0',
status: passed ? 'passed' : 'failed',
dependencyPolicy: 'published-only',
bexCommit,
checkoutCount: checkoutRoots.length,
gitDirectoryCount: gitDirectories.length,
Expand All @@ -269,14 +275,14 @@ const report = {
distinctGradleHomes,
distinctInputManifestFiles,
standalonePublished,
localComposite
standalonePublishedReplica
};
writeFileSync(outputPath, `${JSON.stringify(report, null, 2)}\n`);
if (!passed) {
console.error(`Independent clean-build comparison failed: ${outputPath}`);
for (const [label, comparison] of [
['standalone-published', standalonePublished],
['local-composite', localComposite]
['standalone-published-replica', standalonePublishedReplica]
]) {
if (comparison.status === 'passed') {
continue;
Expand Down
99 changes: 0 additions & 99 deletions .github/scripts/compare-local-published-evidence.mjs

This file was deleted.

134 changes: 134 additions & 0 deletions .github/scripts/compare-published-conformance-evidence.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
#!/usr/bin/env node

import { createHash } from 'node:crypto';
import { readFileSync, writeFileSync } from 'node:fs';

const [firstPath, secondPath, thirdPath, fourthPath,
bexCommit, outputPath] = process.argv.slice(2);
if (!firstPath || !secondPath || !thirdPath || !fourthPath ||
!bexCommit || !outputPath) {
throw new Error(
'usage: compare-published-conformance-evidence.mjs ' +
'REPORT1 REPORT2 REPORT3 REPORT4 COMMIT OUTPUT'
);
}

const reports = [firstPath, secondPath, thirdPath, fourthPath]
.map((path) => JSON.parse(readFileSync(path, 'utf8')));
if (!/^[0-9a-f]{40}$/.test(bexCommit)) {
throw new Error(`invalid BEX commit: ${bexCommit}`);
}
const semanticKeys = [
'identities',
'finalTotals',
'normativeVectorCoverage',
'operatorCoverage',
'representationMatrix',
'representationMatrixResult',
'cacheMatrix',
'recursionEvidence',
'finiteLoopEvidence',
'semanticBoundaryInvocationEvidence',
'cyclicProofEvidence',
'cyclicProofUnavailabilityCapability',
'intrinsicEvidence',
'referenceEvidenceClassificationEvidence',
'hostedLocalLimitCapability',
'ledgerLifecycleEvidence'
];
const gasKeys = [
'identities',
'finalTotals',
'counterCoverage',
'gasExhaustionEvidence',
'gasExhaustionTraceExamples',
'maximumObservedOrderedTraceEntries'
];

function selected(report, keys) {
return Object.fromEntries(keys.map((key) => [key, report[key] ?? null]));
}

function canonical(value) {
if (Array.isArray(value)) {
return `[${value.map(canonical).join(',')}]`;
}
if (value && typeof value === 'object') {
return `{${Object.keys(value).sort().map(
(key) => `${JSON.stringify(key)}:${canonical(value[key])}`
).join(',')}}`;
}
return JSON.stringify(value);
}

function digest(value) {
return createHash('sha256').update(canonical(value)).digest('hex');
}

function repeated(values) {
return values.length === 4 && values.every((value) => value === values[0]);
}

const modes = reports.map((report) => report.dependency?.mode ?? 'missing');
const coordinates = reports.map(
(report) => report.dependency?.declaredCoordinate ?? 'missing'
);
const artifactHashes = reports.map(
(report) => report.dependency?.resolution?.artifact?.sha256 ?? 'missing'
);
Comment on lines +76 to +78

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Compare all focused dependency hashes across repeatability runs

If the repository serves different focused module JARs to the four isolated builds while the aggregate blue-language-java JAR remains unchanged, this repeatability check still reports dependencyIdentityRepeated because it compares only resolution.artifact.sha256 for the aggregate. Each report already contains dependency.resolution.artifacts, so the complete name/hash set should be compared; otherwise the published-repeatability receipt can claim identical dependencies for builds that actually compiled and ran against different Language module bytes.

Useful? React with 👍 / 👎.

const semanticHashes = reports.map(
(report) => digest(selected(report, semanticKeys))
);
const gasHashes = reports.map(
(report) => digest(selected(report, gasKeys))
);
const allowedIncompleteGate =
'independent-clean-build-reproducibility-gate-not-passing';
const sourceBound = reports.every((report) => report.commit === bexCommit);
const publishedModes = modes.every((mode) => mode === 'standalone-published');
const dependencyResolutionPassed = reports.every((report) =>
report.dependency?.resolution?.status === 'passed' &&
report.languageReleaseIdentity
?.currentDependencyExactFinalArtifactProven === true
);
const noUnexpectedFailures = reports.every((report) =>
Array.isArray(report.currentModeFailures) &&
report.currentModeFailures.every(
(failure) => failure === allowedIncompleteGate
)
);
const dependencyIdentityRepeated = repeated(coordinates) &&
repeated(artifactHashes) && /^[0-9a-f]{64}$/.test(artifactHashes[0]);
const semanticAndGasRepeatability = repeated(semanticHashes);
const exactGasTraceRepeatability = repeated(gasHashes);
const passed = sourceBound && publishedModes && dependencyResolutionPassed &&
noUnexpectedFailures && dependencyIdentityRepeated &&
semanticAndGasRepeatability && exactGasTraceRepeatability;

const report = {
schema: 'blue-bex-published-conformance-repeatability/1.0',
status: passed ? 'passed' : 'failed',
dependencyPolicy: 'published-only',
bexCommit,
runCount: reports.length,
modes,
coordinates,
sourceBound,
dependencyResolutionPassed,
noUnexpectedFailures,
dependencyIdentityRepeated,
semanticAndGasRepeatability: semanticAndGasRepeatability
? 'passed' : 'failed',
exactGasTraceRepeatability: exactGasTraceRepeatability
? 'passed' : 'failed',
semanticEvidenceSha256: semanticHashes,
gasEvidenceSha256: gasHashes,
dependencyArtifactSha256: artifactHashes
};
writeFileSync(outputPath, `${JSON.stringify(report, null, 2)}\n`);
if (!passed) {
console.error(
`Published conformance repeatability failed: ${outputPath}`
);
process.exitCode = 1;
}
Loading
Loading