feat(ql3): make release evidence replay deterministic

This commit is contained in:
whyour
2026-08-18 02:17:26 +08:00
parent 3e7bfa8087
commit cc6d6b7b48
13 changed files with 196 additions and 31 deletions
@@ -99,6 +99,7 @@ test('accepts the reviewed native CI and digest release contracts', () => {
sourceDerived: true,
sameRunRecords: true,
sameRunPrivateEvidenceReceipts: true,
deterministicPrivateEvidenceReceipts: true,
exactScopeClosure: true,
standaloneInspection: true,
tagPromotionAuthority: 'complete_verified_release_set',
@@ -108,7 +109,7 @@ test('accepts the reviewed native CI and digest release contracts', () => {
},
durableCatalog: {
repository: 'qinglong3-release-catalog',
artifactType: 'application/vnd.qinglong.release-set.v2+json',
artifactType: 'application/vnd.qinglong.release-set.v3+json',
basenameOnly: true,
crossRunnerDeterministic: true,
byteExactRoundTrip: true,
@@ -119,6 +119,70 @@ test('rejects publishing private material in an otherwise re-digested receipt',
);
});
test('omits the ephemeral validation clock and replays identical source evidence byte-for-byte', (t) => {
const directory = temporaryDirectory(t);
const reportPath = path.join(directory, 'private-report.json');
fs.writeFileSync(
reportPath,
`${JSON.stringify({ observedAt: '2026-08-18T00:00:00.000Z' })}\n`,
{ mode: 0o600 },
);
const createAt = (validationTime, outputFile) =>
createWorkerReceipt(
{
root: path.resolve(__dirname, '../..'),
version: RELEASE.version,
sourceRevision: RELEASE.sourceRevision,
sourceRef: RELEASE.sourceRef,
releaseScope: RELEASE.scope,
reportFile: reportPath,
ceremonyReportFile: path.join(directory, 'private-ceremony.json'),
durableAuditReportFile: path.join(directory, 'private-durable.json'),
pkiRotationReportFile: path.join(directory, 'private-pki.json'),
caRolloverReportFile: path.join(directory, 'private-ca.json'),
outputFile,
},
{
now: () => Date.parse(validationTime),
auditGate(_options, dependencies) {
assert.equal(dependencies.now(), Date.parse(validationTime));
return {
fixture: 'qinglong/worker-credential-management-release-gate@v1',
evidenceReportSha256: `sha256:${'5'.repeat(64)}`,
};
},
},
);
const firstPath = path.join(directory, 'first.json');
const secondPath = path.join(directory, 'second.json');
const first = createAt('2026-08-18T00:05:00.000Z', firstPath);
const second = createAt('2026-08-18T00:15:00.000Z', secondPath);
assert.deepEqual(second, first);
assert.equal(
fs.readFileSync(secondPath, 'utf8'),
fs.readFileSync(firstPath, 'utf8'),
);
assert.equal(Object.hasOwn(first.evidence, 'validatedAt'), false);
assert.equal(first.verification.freshnessValidatedAtCreation, true);
assert.equal(first.verification.durableValidationClockPublished, false);
assert.throws(
() =>
createAt('2026-08-19T00:00:01.000Z', path.join(directory, 'stale.json')),
/freshness window/,
);
const widened = JSON.parse(JSON.stringify(first));
widened.evidence.validatedAt = '2026-08-18T00:05:00.000Z';
assert.throws(
() =>
inspectPrivateReleaseEvidenceReceipt(
widened,
options('worker-management'),
),
/receipt shape/,
);
});
test('audit CLI accepts a canonical receipt and rejects open argument shapes', (t) => {
const directory = temporaryDirectory(t);
const receiptPath = path.join(directory, 'worker-management.json');
@@ -79,7 +79,7 @@ function releaseSet(scope) {
const names = images.map((entry) => entry.name);
const unsigned = {
schemaVersion: 1,
schema: 'qinglong/release-set@v2',
schema: 'qinglong/release-set@v3',
release,
candidate: {
schema: 'qinglong/release-candidate-contract@v1',
@@ -121,6 +121,10 @@ test('plans deterministic Local, Cluster and All OCI catalog entries', () => {
`ghcr.io/qinglong-release/qinglong3-release-catalog:v${version}-${scope}`,
);
assert.equal(plan.catalog.artifactType, ARTIFACT_TYPE);
assert.equal(
plan.publicationPolicy.recovery,
'republish_deterministic_content_then_verify_digest',
);
assert.equal(
plan.releaseSet.bytes,
Buffer.byteLength(`${JSON.stringify(set)}\n`),
+4 -3
View File
@@ -2,7 +2,7 @@
const crypto = require('node:crypto');
const RECEIPT_SCHEMA = 'qinglong/private-release-evidence-receipt@v1';
const RECEIPT_SCHEMA = 'qinglong/private-release-evidence-receipt@v2';
const FIXTURES = Object.freeze({
'worker-management': 'qinglong/worker-credential-management-release-gate@v1',
'cloudnativepg-disaster-recovery':
@@ -28,14 +28,13 @@ function privateReleaseEvidenceReceipt(release, evidenceKind) {
}))
: [];
const unsigned = {
schemaVersion: 1,
schemaVersion: 2,
schema: RECEIPT_SCHEMA,
release: { ...release },
evidenceKind,
evidence: {
fixture: FIXTURES[evidenceKind],
observedAt: '2026-08-18T00:00:00.000Z',
validatedAt: '2026-08-18T00:05:00.000Z',
maximumAgeSeconds: 86_400,
reportDigest: `sha256:${
evidenceKind === 'worker-management' ? '5' : '6'
@@ -46,6 +45,8 @@ function privateReleaseEvidenceReceipt(release, evidenceKind) {
verification: {
sourceAwareAudit: true,
privateEvidenceReplayed: true,
freshnessValidatedAtCreation: true,
durableValidationClockPublished: false,
publicConsumerReplay: 'not_possible_without_private_reports',
privateReportContentPublished: false,
compatible: true,