mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-20 16:07:11 +08:00
feat(ql3): revalidate release evidence at closure
This commit is contained in:
@@ -100,6 +100,7 @@ test('accepts the reviewed native CI and digest release contracts', () => {
|
||||
sameRunRecords: true,
|
||||
sameRunPrivateEvidenceReceipts: true,
|
||||
deterministicPrivateEvidenceReceipts: true,
|
||||
privateEvidenceFreshnessRevalidatedAtClosure: true,
|
||||
exactScopeClosure: true,
|
||||
standaloneInspection: true,
|
||||
tagPromotionAuthority: 'complete_verified_release_set',
|
||||
|
||||
@@ -102,6 +102,7 @@ function releaseSet(scope) {
|
||||
records,
|
||||
evidenceReceipts: privateReleaseEvidenceReceipts(candidate.release),
|
||||
...identity,
|
||||
validationClockMs: Date.parse('2026-08-18T00:05:00.000Z'),
|
||||
releaseScope: scope,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -70,6 +70,7 @@ function releaseSet(scope) {
|
||||
records,
|
||||
evidenceReceipts: privateReleaseEvidenceReceipts(candidate.release),
|
||||
...identity,
|
||||
validationClockMs: Date.parse('2026-08-18T00:05:00.000Z'),
|
||||
releaseScope: scope,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ const identity = Object.freeze({
|
||||
sourceRevision: 'b'.repeat(40),
|
||||
sourceRef: `refs/tags/v${version}`,
|
||||
repositoryOwner: 'qinglong-release',
|
||||
validationClockMs: Date.parse('2026-08-18T00:05:00.000Z'),
|
||||
});
|
||||
|
||||
function candidate(scope) {
|
||||
@@ -74,12 +75,13 @@ function writeCanonical(filePath, value) {
|
||||
test('aggregates the independent Local image into one immutable release set', () => {
|
||||
const releaseCandidate = candidate('local');
|
||||
const records = recordsFor(releaseCandidate);
|
||||
const { validationClockMs: _unused, ...localIdentity } = identity;
|
||||
const releaseSet = createReleaseSet({
|
||||
root,
|
||||
candidate: releaseCandidate,
|
||||
records,
|
||||
evidenceReceipts: evidenceFor(releaseCandidate),
|
||||
...identity,
|
||||
...localIdentity,
|
||||
releaseScope: 'local',
|
||||
});
|
||||
assert.deepEqual(
|
||||
@@ -97,7 +99,7 @@ test('aggregates the independent Local image into one immutable release set', ()
|
||||
candidate: releaseCandidate,
|
||||
records,
|
||||
evidenceReceipts: evidenceFor(releaseCandidate),
|
||||
...identity,
|
||||
...localIdentity,
|
||||
releaseScope: 'local',
|
||||
}).compatible,
|
||||
true,
|
||||
@@ -187,6 +189,66 @@ test('requires exact private evidence receipts only for Cluster-capable scopes',
|
||||
);
|
||||
});
|
||||
|
||||
test('revalidates private evidence freshness at closure without changing release-set bytes', () => {
|
||||
const releaseCandidate = candidate('cluster');
|
||||
const records = recordsFor(releaseCandidate);
|
||||
const evidenceReceipts = evidenceFor(releaseCandidate);
|
||||
const createAt = (validationClockMs) =>
|
||||
createReleaseSet({
|
||||
root,
|
||||
candidate: releaseCandidate,
|
||||
records,
|
||||
evidenceReceipts,
|
||||
...identity,
|
||||
validationClockMs,
|
||||
releaseScope: 'cluster',
|
||||
});
|
||||
const first = createAt(Date.parse('2026-08-18T00:05:00.000Z'));
|
||||
const second = createAt(Date.parse('2026-08-18T00:15:00.000Z'));
|
||||
assert.deepEqual(second, first);
|
||||
assert.throws(
|
||||
() => createAt(Date.parse('2026-08-19T00:00:01.000Z')),
|
||||
/release freshness window/,
|
||||
);
|
||||
assert.throws(
|
||||
() => createAt(Date.parse('2026-08-17T23:54:59.000Z')),
|
||||
/release freshness window/,
|
||||
);
|
||||
assert.throws(
|
||||
() =>
|
||||
createReleaseSet({
|
||||
root,
|
||||
candidate: releaseCandidate,
|
||||
records,
|
||||
evidenceReceipts,
|
||||
version: identity.version,
|
||||
sourceRevision: identity.sourceRevision,
|
||||
sourceRef: identity.sourceRef,
|
||||
repositoryOwner: identity.repositoryOwner,
|
||||
releaseScope: 'cluster',
|
||||
}),
|
||||
/closure validation clock/,
|
||||
);
|
||||
assert.equal(
|
||||
auditReleaseSet(first, {
|
||||
root,
|
||||
candidate: releaseCandidate,
|
||||
records,
|
||||
evidenceReceipts,
|
||||
...identity,
|
||||
releaseScope: 'cluster',
|
||||
}).privateEvidenceFreshnessRevalidatedAtClosure,
|
||||
true,
|
||||
);
|
||||
assert.equal(
|
||||
inspectReleaseSet(first, {
|
||||
...identity,
|
||||
releaseScope: 'cluster',
|
||||
}).privateEvidenceReplayed,
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
test('rejects missing, duplicate and cross-candidate image evidence', () => {
|
||||
const releaseCandidate = candidate('cluster');
|
||||
const records = recordsFor(releaseCandidate);
|
||||
@@ -440,6 +502,70 @@ test('CLI records, aggregates and audits exact no-replace files', (t) => {
|
||||
);
|
||||
});
|
||||
|
||||
test('CLI rejects stale private receipts at closure and keeps valid retries byte-identical', (t) => {
|
||||
const directory = temporaryDirectory(t);
|
||||
const recordsDirectory = path.join(directory, 'records');
|
||||
const evidenceDirectory = path.join(directory, 'evidence');
|
||||
fs.mkdirSync(recordsDirectory);
|
||||
fs.mkdirSync(evidenceDirectory);
|
||||
const releaseCandidate = candidate('cluster');
|
||||
const candidatePath = path.join(directory, 'candidate.json');
|
||||
writeCanonical(candidatePath, releaseCandidate);
|
||||
for (const record of recordsFor(releaseCandidate)) {
|
||||
writeCanonical(
|
||||
path.join(recordsDirectory, `${record.image.name}.json`),
|
||||
record,
|
||||
);
|
||||
}
|
||||
for (const receipt of evidenceFor(releaseCandidate)) {
|
||||
writeCanonical(
|
||||
path.join(evidenceDirectory, `${receipt.evidenceKind}.json`),
|
||||
receipt,
|
||||
);
|
||||
}
|
||||
const common = [
|
||||
'--mode=aggregate',
|
||||
`--version=${version}`,
|
||||
`--source-revision=${identity.sourceRevision}`,
|
||||
`--source-ref=${identity.sourceRef}`,
|
||||
'--release-scope=cluster',
|
||||
`--repository-owner=${identity.repositoryOwner}`,
|
||||
`--candidate=${candidatePath}`,
|
||||
`--records=${recordsDirectory}`,
|
||||
`--evidence-receipts=${evidenceDirectory}`,
|
||||
];
|
||||
const firstPath = path.join(directory, 'first.json');
|
||||
const secondPath = path.join(directory, 'second.json');
|
||||
runCli(
|
||||
[...common, `--output=${firstPath}`],
|
||||
root,
|
||||
{ write() {} },
|
||||
{ now: () => Date.parse('2026-08-18T00:05:00.000Z') },
|
||||
);
|
||||
runCli(
|
||||
[...common, `--output=${secondPath}`],
|
||||
root,
|
||||
{ write() {} },
|
||||
{ now: () => Date.parse('2026-08-18T00:15:00.000Z') },
|
||||
);
|
||||
assert.equal(
|
||||
fs.readFileSync(secondPath, 'utf8'),
|
||||
fs.readFileSync(firstPath, 'utf8'),
|
||||
);
|
||||
const stalePath = path.join(directory, 'stale.json');
|
||||
assert.throws(
|
||||
() =>
|
||||
runCli(
|
||||
[...common, `--output=${stalePath}`],
|
||||
root,
|
||||
{ write() {} },
|
||||
{ now: () => Date.parse('2026-08-19T00:00:01.000Z') },
|
||||
),
|
||||
/release freshness window/,
|
||||
);
|
||||
assert.equal(fs.existsSync(stalePath), false);
|
||||
});
|
||||
|
||||
test('CLI rejects extra records, symlinks and open argument shapes', (t) => {
|
||||
const directory = temporaryDirectory(t);
|
||||
const recordsDirectory = path.join(directory, 'records');
|
||||
|
||||
Reference in New Issue
Block a user