'use strict'; const assert = require('node:assert/strict'); const crypto = require('node:crypto'); const fs = require('node:fs'); const os = require('node:os'); const path = require('node:path'); const test = require('node:test'); const { CLOSURE_RECEIPT_SCHEMA, PUBLICATION_PLAN_SCHEMA, TAG_OBSERVATION_SCHEMA, auditClosureReceipt, createClosureReceipt, createPublicationPlan, createPublicationTagObservation, parseArguments, runCli, } = require('../../scripts/ql3-release-publication-closure-contract.cjs'); const { ARTIFACT_TYPE, OCI_EMPTY_CONFIG_DIGEST, OCI_EMPTY_CONFIG_MEDIA_TYPE, OCI_MANIFEST_MEDIA_TYPE, createCatalogPlan, createCatalogReceipt, } = require('../../scripts/ql3-release-catalog-contract.cjs'); const { createReleaseSet, createVerifiedImageRecord, } = require('../../scripts/ql3-release-set-contract.cjs'); const { createReleaseCandidateContract, } = require('../../scripts/ql3-release-candidate-contract.cjs'); const { readReleaseIdentity, } = require('../../scripts/lib/ql3-release-identity.cjs'); const { createDeploymentReadinessReceipt, } = require('../../scripts/ql3-release-deployment-readiness-contract.cjs'); const { privateReleaseEvidenceReceipts, } = require('./ql3ReleaseEvidenceFixture.cjs'); const root = path.resolve(__dirname, '../..'); const version = readReleaseIdentity(root).version; const identity = Object.freeze({ version, sourceRevision: 'e'.repeat(40), sourceRef: `refs/tags/v${version}`, repositoryOwner: 'qinglong-release', sourceRepository: 'qinglong-release/qinglong', }); function sha256(value) { return `sha256:${crypto.createHash('sha256').update(value).digest('hex')}`; } function releaseSet(scope) { const candidate = createReleaseCandidateContract({ root, version, sourceRevision: identity.sourceRevision, sourceRef: identity.sourceRef, releaseScope: scope, }); const records = candidate.images.map((entry, index) => createVerifiedImageRecord({ root, candidate, ...identity, releaseScope: scope, image: entry.image, digest: `sha256:${String(index + 1).repeat(64)}`, }), ); return createReleaseSet({ root, candidate, records, evidenceReceipts: privateReleaseEvidenceReceipts(candidate.release), ...identity, validationClockMs: Date.parse('2026-08-18T00:05:00.000Z'), releaseScope: scope, }); } function manifestFor(plan) { return JSON.stringify({ schemaVersion: 2, mediaType: OCI_MANIFEST_MEDIA_TYPE, artifactType: ARTIFACT_TYPE, config: { mediaType: OCI_EMPTY_CONFIG_MEDIA_TYPE, digest: OCI_EMPTY_CONFIG_DIGEST, size: 2, }, layers: [ { mediaType: ARTIFACT_TYPE, digest: plan.releaseSet.contentDigest, size: plan.releaseSet.bytes, annotations: { 'org.opencontainers.image.title': plan.releaseSet.fileName, }, }, ], annotations: { ...plan.catalog.annotations }, }); } function catalogConsumption(set, manifestDigest, marker) { return { compatible: true, releaseScope: set.release.scope, sourceRepository: identity.sourceRepository, workflowIdentity: `https://github.com/${identity.sourceRepository}/.github/workflows/ql3-image-release.yml@${identity.sourceRef}`, releaseSetDigest: set.releaseSetDigest, catalogManifestDigest: manifestDigest, immutableReference: `ghcr.io/${identity.repositoryOwner}/qinglong3-release-catalog@${manifestDigest}`, imageCount: set.images.length, discoveryTagAuthority: 'none', externalToolResultsReplayed: false, deploymentMutation: false, contentDigest: sha256(marker), releaseSet: set, }; } function localReport(profile, consumption, marker) { return { value: { schemaVersion: 1, profile, generation: 2, exactRepoDigest: true, composeMerge: true, rolloutActive: true, durableReceipt: true, sqliteWriteContract: 37, sqliteBackup: true, sqliteWriteObservation: { rows: 1 }, sqliteRestorePrepared: true, sqliteRestoreCommitted: true, sqliteRestoreRolloutRecovered: true, sqliteRestoreReplayUnchanged: true, sqliteEvidenceCollected: true, sqliteCollectedRolloutReplayUnchanged: true, gracefulCleanup: true, releaseAuthority: { mode: 'verified_release_catalog', sourceRevision: identity.sourceRevision, sourceRef: identity.sourceRef, scope: consumption.releaseScope, releaseSetDigest: consumption.releaseSetDigest, catalogManifestDigest: consumption.catalogManifestDigest, catalogConsumptionDigest: consumption.contentDigest, selectionDigest: sha256('local-selection'), }, compatible: true, }, digest: sha256(marker), }; } function clusterReport(consumption) { return { value: { schemaVersion: 1, schema: 'qinglong/kubernetes-deployment-live-evidence@v1', kubernetes: { serverVersion: 'v1.34.3+k3s1', architecture: 'linux/amd64', nodeCount: 3, clusterUid: 'cluster-fixture', }, deployment: { replicas: 0, immutableImages: true, headPhase: 'committed', headGeneration: 2, deploymentDigest: sha256('deployment'), }, releaseAuthority: { mode: 'verified_release_catalog', version, sourceRevision: identity.sourceRevision, sourceRef: identity.sourceRef, scope: consumption.releaseScope, releaseSetDigest: consumption.releaseSetDigest, catalogManifestDigest: consumption.catalogManifestDigest, catalogConsumptionDigest: consumption.contentDigest, immutableReference: consumption.immutableReference, }, preflightDigest: sha256('preflight'), receiptDigest: sha256('deployment-receipt'), receiptAuditCompatible: true, retirement: { receiptAuditCompatible: true, targetAbsent: true, uidResourceVersionDeletePreconditions: true, deploymentHeadCas: true, unixSocketProxy: true, }, serverSideDryRun: true, serverSideApply: true, convergenceRead: true, deploymentHeadCas: true, resourceInventoryClosed: true, crossResourceAtomicity: false, cleanupComplete: true, }, digest: sha256('cluster-report'), }; } function deploymentReadiness(set, manifestDigest) { const scope = set.release.scope; const finalizerConsumption = catalogConsumption( set, manifestDigest, 'finalizer-consumption', ); const input = { identity: { ...identity, releaseScope: scope }, finalizerConsumption, }; if (scope !== 'cluster') { const consumption = catalogConsumption( set, manifestDigest, 'local-consumption', ); input.local = { consumption, edge: localReport('edge', consumption, 'edge-report'), standalone: localReport('standalone', consumption, 'standalone-report'), }; } if (scope !== 'local') { const consumption = catalogConsumption( set, manifestDigest, 'cluster-consumption', ); input.cluster = { consumption, report: clusterReport(consumption) }; } return createDeploymentReadinessReceipt(input); } function fixture(scope = 'all') { const set = releaseSet(scope); const options = { ...identity, releaseScope: scope }; const catalogPlan = createCatalogPlan(set, options); const manifest = manifestFor(catalogPlan); const manifestDigest = sha256(manifest); const catalogReceipt = createCatalogReceipt( catalogPlan, manifest, manifestDigest, ); const readiness = deploymentReadiness(set, manifestDigest); const publicationPlan = createPublicationPlan( set, catalogPlan, catalogReceipt, readiness, manifest, manifestDigest, options, ); const tags = publicationPlan.images.flatMap((image) => image.tags.map((tag) => ({ image: image.name, kind: tag.kind, reference: tag.reference, digest: image.digest, })), ); const observation = createPublicationTagObservation(publicationPlan, tags); return { set, options, catalogPlan, manifest, manifestDigest, catalogReceipt, readiness, publicationPlan, observation, }; } function temporaryDirectory(t) { const directory = fs.realpathSync( fs.mkdtempSync(path.join(os.tmpdir(), 'ql3-release-closure-')), ); t.after(() => fs.rmSync(directory, { recursive: true, force: true })); return directory; } function writeCanonical(filePath, value) { fs.writeFileSync(filePath, `${JSON.stringify(value)}\n`, { mode: 0o600 }); } test('plans tag publication only from a verified immutable catalog', () => { for (const scope of ['local', 'cluster', 'all']) { const { publicationPlan, set } = fixture(scope); assert.equal(publicationPlan.schema, PUBLICATION_PLAN_SCHEMA); assert.equal( publicationPlan.promotionPolicy.authority, 'verified_catalog_bound_deployments', ); assert.equal( publicationPlan.requiredPrerequisites.deploymentReadiness, 'scope_exact_receipt_attested_before_tag_promotion', ); assert.equal(publicationPlan.promotionPolicy.registryTagCas, false); assert.equal(publicationPlan.images.length, set.images.length); assert.equal( publicationPlan.images.every((image) => image.tags.length === 2), true, ); } }); test('creates a deterministic final closure receipt for every exact tag', () => { const { publicationPlan, observation } = fixture(); const first = createClosureReceipt(publicationPlan, observation); const replay = createClosureReceipt(publicationPlan, observation); assert.equal(first.schema, CLOSURE_RECEIPT_SCHEMA); assert.equal(observation.schema, TAG_OBSERVATION_SCHEMA); assert.deepEqual(first, replay); assert.equal(first.publishedTags.length, publicationPlan.images.length * 2); assert.equal(first.verification.catalogReadyBeforeTagMutation, true); assert.equal(first.verification.deploymentReadyBeforeTagMutation, true); assert.equal(first.verification.allTagsExactDigest, true); assert.equal(first.verification.registryTagCas, false); assert.equal( auditClosureReceipt(first, publicationPlan, observation).compatible, true, ); }); test('rejects missing, reordered, extra or digest-drifted tag observations', () => { const { publicationPlan, observation } = fixture('cluster'); const variants = [ observation.tags.slice(1), [...observation.tags].reverse(), [...observation.tags, observation.tags[0]], observation.tags.map((entry, index) => index === 0 ? { ...entry, digest: `sha256:${'f'.repeat(64)}` } : entry, ), ]; for (const tags of variants) { assert.throws( () => createPublicationTagObservation(publicationPlan, tags), /observations differ from the exact plan/, ); } }); test('rejects a publication plan detached from release-set or catalog evidence', () => { const current = fixture('local'); const other = fixture('cluster'); assert.throws( () => createPublicationPlan( current.set, other.catalogPlan, other.catalogReceipt, current.readiness, other.manifest, other.manifestDigest, current.options, ), /catalog plan differs from the standalone release set/, ); const weakenedReceipt = structuredClone(current.catalogReceipt); weakenedReceipt.verification.keylessSignature = 'not_verified'; assert.throws( () => createPublicationPlan( current.set, current.catalogPlan, weakenedReceipt, current.readiness, current.manifest, current.manifestDigest, current.options, ), /catalog receipt differs from the verified OCI manifest/, ); }); test('rejects tampered plan, observation and closure self-digests', () => { const { publicationPlan, observation } = fixture('local'); const badPlan = structuredClone(publicationPlan); badPlan.promotionPolicy.registryTagCas = true; assert.throws( () => createPublicationTagObservation(badPlan, observation.tags), /publication plan (?:shape|digest) is invalid/, ); const recomputedWeakenedPlan = structuredClone(publicationPlan); recomputedWeakenedPlan.promotionPolicy.registryTagCas = true; delete recomputedWeakenedPlan.planDigest; recomputedWeakenedPlan.planDigest = sha256( JSON.stringify(recomputedWeakenedPlan), ); assert.throws( () => createPublicationTagObservation(recomputedWeakenedPlan, observation.tags), /publication plan shape is invalid/, ); const badObservation = structuredClone(observation); badObservation.observationDigest = `sha256:${'a'.repeat(64)}`; assert.throws( () => createClosureReceipt(publicationPlan, badObservation), /observation digest is invalid/, ); const receipt = createClosureReceipt(publicationPlan, observation); const badReceipt = structuredClone(receipt); badReceipt.verification.registryTagCas = true; assert.throws( () => auditClosureReceipt(badReceipt, publicationPlan, observation), /closure receipt differs/, ); }); test('runs plan, close and audit as canonical no-replace CLI stages', (t) => { const value = fixture('cluster'); const directory = temporaryDirectory(t); const files = { releaseSet: path.join(directory, 'release-set.json'), catalogPlan: path.join(directory, 'catalog-plan.json'), catalogReceipt: path.join(directory, 'catalog-receipt.json'), readiness: path.join(directory, 'deployment-readiness.json'), manifest: path.join(directory, 'manifest.json'), publicationPlan: path.join(directory, 'publication-plan.json'), observation: path.join(directory, 'observation.json'), receipt: path.join(directory, 'receipt.json'), }; writeCanonical(files.releaseSet, value.set); writeCanonical(files.catalogPlan, value.catalogPlan); writeCanonical(files.catalogReceipt, value.catalogReceipt); writeCanonical(files.readiness, value.readiness); fs.writeFileSync(files.manifest, value.manifest, { mode: 0o600 }); writeCanonical(files.observation, value.observation); const planArgs = [ '--mode=plan', `--version=${version}`, `--source-revision=${identity.sourceRevision}`, `--source-ref=${identity.sourceRef}`, '--release-scope=cluster', `--repository-owner=${identity.repositoryOwner}`, `--source-repository=${identity.sourceRepository}`, `--release-set=${files.releaseSet}`, `--catalog-plan=${files.catalogPlan}`, `--catalog-receipt=${files.catalogReceipt}`, `--deployment-readiness=${files.readiness}`, `--catalog-manifest=${files.manifest}`, `--catalog-manifest-digest=${value.manifestDigest}`, `--output=${files.publicationPlan}`, ]; const output = { write() {} }; assert.equal(runCli(planArgs, output).schema, PUBLICATION_PLAN_SCHEMA); assert.throws(() => runCli(planArgs, output), /EEXIST/); assert.equal( runCli( [ '--mode=close', `--plan=${files.publicationPlan}`, `--observations=${files.observation}`, `--output=${files.receipt}`, ], output, ).schema, CLOSURE_RECEIPT_SCHEMA, ); assert.equal( runCli( [ '--mode=audit', `--plan=${files.publicationPlan}`, `--observations=${files.observation}`, `--receipt=${files.receipt}`, ], output, ).compatible, true, ); }); test('rejects ambiguous CLI modes and extra arguments', () => { assert.throws( () => parseArguments(['--mode=close']), /arguments are invalid/, ); assert.throws( () => parseArguments([ '--mode=audit', '--plan=/tmp/plan', '--observations=/tmp/observations', '--receipt=/tmp/receipt', '--extra=true', ]), /arguments are invalid/, ); });