import { mkdtemp, readFile, unlink, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { describe, expect, test } from 'vitest'; import { byteDigest, canonicalValueBytes } from '../../src/security/canonical-digest.ts'; import { buildReleasePackage } from '../../scripts/build-release.mjs'; import { parseDeterministicTar } from '../../scripts/deploy/lib/release-common.mjs'; import { createReleaseFixture } from './release-test-fixture.ts'; function buildInput(fixture: Awaited>, outputPath: string) { return { distDir: fixture.distDir, signedManifestPath: fixture.signedManifestPath, sbomPath: fixture.sbomPath, verifierModulePath: fixture.verifierModulePath, trustPolicyPath: fixture.trustPolicyPath, verifySignedReleaseEnvelope: fixture.verifySignedReleaseEnvelope, protectedState: fixture.protectedState, now: '2026-09-02T01:00:00Z', outputPath, }; } describe('deterministic exact-manifest release package', () => { test('builds byte-identical archives containing only the signed closure and release metadata', async () => { const root = await mkdtemp(join(tmpdir(), 'portfolio-package-')); const fixture = await createReleaseFixture(root); const first = join(root, 'release-one.tar'); const second = join(root, 'release-two.tar'); await buildReleasePackage(buildInput(fixture, first)); await buildReleasePackage(buildInput(fixture, second)); expect(byteDigest(await readFile(first))).toBe(byteDigest(await readFile(second))); expect(parseDeterministicTar(await readFile(first)).map((file) => file.path)).toEqual([ '.release/package-index.json', '.release/sbom.cdx.json', '.release/signed-release-manifest.json', 'assets/app.js', 'index.html', ]); }); test('fails closed on extra, substituted, or invalidly signed inputs', async () => { const root = await mkdtemp(join(tmpdir(), 'portfolio-package-invalid-')); const fixture = await createReleaseFixture(root); await writeFile(join(fixture.distDir, 'extra.txt'), 'not in manifest'); await expect(buildReleasePackage(buildInput(fixture, join(root, 'extra.tar')))).rejects.toThrow('exactly match'); await unlink(join(fixture.distDir, 'extra.txt')); await writeFile(join(fixture.distDir, 'index.html'), 'substituted'); await expect(buildReleasePackage(buildInput(fixture, join(root, 'substituted.tar')))).rejects.toThrow('hash'); await writeFile(join(fixture.distDir, 'index.html'), 'Portfolio\n'); const envelope = JSON.parse(await readFile(fixture.signedManifestPath, 'utf8')); envelope.signature.value = `${envelope.signature.value[0] === 'A' ? 'B' : 'A'}${envelope.signature.value.slice(1)}`; await writeFile(fixture.signedManifestPath, canonicalValueBytes(envelope)); await expect(buildReleasePackage(buildInput(fixture, join(root, 'invalid.tar')))).rejects.toThrow('signature'); }); });