import { generateKeyPairSync, sign } from "node:crypto"; import { describe, expect, test } from "vitest"; import { canonicalJsonDigest, canonicalValueBytes, } from "../../src/security/canonical-digest.ts"; import { computePreparedAssetSetDigest, computeProductionInputRoot, } from "../../src/security/production-input-root.ts"; import { validateReleaseManifest } from "../../src/security/release-manifest.ts"; // @ts-expect-error The executable JavaScript verifier intentionally has no browser-facing declaration. import { verifyApprovalRequestForTest } from "../../scripts/release/verify-approvals.mjs"; const h = (c: string) => c.repeat(64); describe("production input and private-source release manifest", () => { test("hashes an exact manifest-bound PreparedAssetSet and rejects missing or extra members", () => { const bytes = new TextEncoder().encode("image"); const member = { assetId: "asset-one", publicPath: "asset-one.webp", mediaType: "image/webp", width: 10, height: 20, durationMs: null, bytes, }; const manifest = [ { id: "asset-one", fileName: "asset-one.webp", sha256: "6105d6cc76af400325e94d588ce511be5bfdbb73b437dc51eca43917d7a43e3d", mediaType: "image/webp", width: 10, height: 20, durationMs: null, }, ]; expect(computePreparedAssetSetDigest([member], manifest)).toMatch( /^[a-f0-9]{64}$/, ); expect(() => computePreparedAssetSetDigest([], manifest)).toThrow(); expect(() => computePreparedAssetSetDigest( [ member, { ...member, assetId: "asset-two", publicPath: "asset-two.webp" }, ], manifest, ), ).toThrow(); }); test("matches the fixed four-label input-root vector", () => { expect( computeProductionInputRoot({ portfolioContentDigest: h("a"), publicAssetManifestDigest: h("b"), preparedAssetSetDigest: h("c"), siteConfigDigest: h("d"), }), ).toBe("38707dd72ded582a346048ff66a1625cdaca91f6536dd27c935bc3b8af262ae8"); }); test("validates an exact initial-launch private-source manifest", () => { const inputDigests = { PortfolioContent: h("a"), PublicAssetManifest: h("b"), PreparedAssetSet: h("c"), SiteConfig: h("d"), }; const manifest = { schema: "portfolio-release-manifest/v1", launchMode: "private-source-initial-launch", buildId: "release-2026-09-02", inputDigests, productionInputRoot: computeProductionInputRoot({ portfolioContentDigest: h("a"), publicAssetManifestDigest: h("b"), preparedAssetSetDigest: h("c"), siteConfigDigest: h("d"), }), artifacts: [ { path: "index.html", sha256: h("e"), bytes: 42, encoding: "identity" }, ], deployFiles: ["index.html"], sourceCommit: h("f"), sourceTree: h("0"), approvedRecordRoot: h("1"), approvedAssetRoot: h("2"), approvalProofSetDigest: h("3"), approvalProjectionAttestationDigest: h("4"), scanEvidenceDigest: h("5"), policyVersions: { approval: "1", privacy: "1", release: "1" }, toolVersions: { node: "22.22.1" }, predecessorManifestDigest: null, currentManifestDigest: null, issuedAt: "2026-09-02T00:00:00Z", expiresAt: "2026-09-03T00:00:00Z", minimumBoundExpiry: "2026-09-03T00:00:00Z", }; expect( validateReleaseManifest(manifest, { now: "2026-09-02T01:00:00Z" }), ).toEqual(manifest); expect( canonicalJsonDigest("portfolio-release-manifest/v1", manifest), ).toMatch(/^[a-f0-9]{64}$/); }); test.each([ ["public Git target", { publicGitTarget: "https://example.com/repo.git" }], ["extra deploy file", { deployFiles: ["index.html", "extra.txt"] }], [ "case-fold collision", { artifacts: [ { path: "A.js", sha256: h("e"), bytes: 1, encoding: "identity" }, { path: "a.js", sha256: h("f"), bytes: 1, encoding: "identity" }, ], deployFiles: ["A.js", "a.js"], }, ], ["expired manifest", { expiresAt: "2026-09-02T00:30:00Z" }], ])("rejects %s", (_label, override) => { const inputDigests = { PortfolioContent: h("a"), PublicAssetManifest: h("b"), PreparedAssetSet: h("c"), SiteConfig: h("d"), }; const baseline = { schema: "portfolio-release-manifest/v1", launchMode: "private-source-initial-launch", buildId: "release", inputDigests, productionInputRoot: computeProductionInputRoot({ portfolioContentDigest: h("a"), publicAssetManifestDigest: h("b"), preparedAssetSetDigest: h("c"), siteConfigDigest: h("d"), }), artifacts: [ { path: "index.html", sha256: h("e"), bytes: 42, encoding: "identity" }, ], deployFiles: ["index.html"], sourceCommit: h("f"), sourceTree: h("0"), approvedRecordRoot: h("1"), approvedAssetRoot: h("2"), approvalProofSetDigest: h("3"), approvalProjectionAttestationDigest: h("4"), scanEvidenceDigest: h("5"), policyVersions: { approval: "1", privacy: "1", release: "1" }, toolVersions: { node: "22" }, predecessorManifestDigest: null, currentManifestDigest: null, issuedAt: "2026-09-02T00:00:00Z", expiresAt: "2026-09-03T00:00:00Z", minimumBoundExpiry: "2026-09-03T00:00:00Z", }; expect(() => validateReleaseManifest( { ...baseline, ...override }, { now: "2026-09-02T01:00:00Z" }, ), ).toThrow(); }); test("approval verifier requires an authenticated exact ledger and rejects replay after revoke", () => { const keys = generateKeyPairSync("ed25519"); const approvalPayload = { schema: "portfolio-approval-payload/v1", keyId: "approval-key", issuerEpoch: 1, authorizedRole: "approver", evidenceFingerprints: [h("1")], recordClass: "content", recordType: "profile", publicRecordIdOrAssetId: "profile-main", approvedPublicRecordHashOrManifestRecordHash: h("2"), sanitizedOutputHashOrNull: null, decision: "approve", issuedAt: "2026-09-01T00:00:00Z", expiresAt: "2026-09-04T00:00:00Z", previousLedgerHeadHash: h("0"), ledgerRevision: 1, policyVersion: "policy-1", }; const envelope = { schema: "portfolio-approval-envelope/v1", payload: approvalPayload, signature: { algorithm: "Ed25519", encoding: "base64url-nopad", value: sign( null, canonicalValueBytes(approvalPayload), keys.privateKey, ).toString("base64url"), }, }; const envelopeDigest = canonicalJsonDigest( "portfolio-approval-envelope-digest/v1", envelope, ); const head = canonicalJsonDigest("portfolio-approval-ledger-entry/v1", { previousLedgerHeadHash: h("0"), ledgerRevision: 1, envelopeDigest, }); const request = { schema: "portfolio-approval-verification-request/v1", latestLedgerHeadHash: head, latestLedgerRevision: 1, trustedKeys: [ { keyId: "approval-key", issuerEpoch: 1, scope: "portfolio-approval-decision/v1", authorizedRoles: ["approver"], publicKeyPem: keys.publicKey.export({ type: "spki", format: "pem" }), }, ], ledgerProof: { schema: "portfolio-approval-ledger-proof/v1", genesisHeadHash: h("0"), entries: [envelope], }, contentRecords: [ { recordType: "profile", publicRecordId: "profile-main", approvedPublicRecordHash: h("2"), envelope, }, ], assetRecords: [], inputDigests: { PortfolioContent: h("a"), PublicAssetManifest: h("b"), PreparedAssetSet: h("c"), SiteConfig: h("d"), }, }; const result = verifyApprovalRequestForTest(canonicalValueBytes(request), { now: "2026-09-02T00:00:00Z", }); expect(Object.keys(result).sort()).toEqual([ "approvalProofSetDigest", "approvedAssetRoot", "approvedRecordRoot", "earliestApprovalExpiry", "latestLedgerHeadHash", "latestLedgerRevision", "productionInputRoot", "projectionSchema", "projectionScope", "schema", ]); const serialized = new TextDecoder().decode(canonicalValueBytes(result)); expect(serialized).not.toContain("evidenceFingerprints"); const revokePayload = { ...approvalPayload, decision: "revoke", issuedAt: "2026-09-02T01:00:00Z", ledgerRevision: 2, previousLedgerHeadHash: head, }; const revoke = { schema: "portfolio-approval-envelope/v1", payload: revokePayload, signature: { algorithm: "Ed25519", encoding: "base64url-nopad", value: sign( null, canonicalValueBytes(revokePayload), keys.privateKey, ).toString("base64url"), }, }; const revokeDigest = canonicalJsonDigest( "portfolio-approval-envelope-digest/v1", revoke, ); const revokedHead = canonicalJsonDigest( "portfolio-approval-ledger-entry/v1", { previousLedgerHeadHash: head, ledgerRevision: 2, envelopeDigest: revokeDigest, }, ); expect(() => verifyApprovalRequestForTest( canonicalValueBytes({ ...request, latestLedgerHeadHash: revokedHead, latestLedgerRevision: 2, ledgerProof: { schema: "portfolio-approval-ledger-proof/v1", genesisHeadHash: h("0"), entries: [envelope, revoke], }, }), { now: "2026-09-02T02:00:00Z" }, ), ).toThrow(/latest|revoke|supersed/i); }); });