1import { execFileSync } from "node:child_process";2import { createHash } from "node:crypto";3import { existsSync, readFileSync } from "node:fs";4import { fileURLToPath } from "node:url";5import { dirname, join, resolve } from "node:path";6import { describe, expect, it } from "vitest";78import {9 buildSourceManifest,10 collectSourceFiles,11 packageSourceArchive,12 REVIEWED_THIRD_PARTY_FILE,13 type SourceManifest,14} from "./package-source";1516const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");1718const REQUIRED_FILES = [19 "src/shared/noise-core-wasm/peach_noise_core_bg.wasm",20 "fixtures/golden/peach-payload-v4.json",21 "fixtures/golden/sync-entry-envelope-v1.json",22 "crypto-core/Cargo.toml",23 "crypto-core/Cargo.lock",24 "crypto-core/src/lib.rs",25 "crypto-core/src/jni_bridge.rs",26 "crypto-core/.cargo/config.toml",27 "scripts/build-noise-core.mjs",28 "scripts/check-noise-core.mjs",29 "manifest.json",30 "manifest.firefox.json",31 "package.json",32 "bun.lock",33 "tsconfig.json",34 "vite.config.ts",35];3637const EXCLUDE_PATTERNS = [38 /node_modules\//,39 /dist-[^/]+\//,40 /\.zip$/,41 /crypto-core\/target\//,42 /crypto-core\/generated\//,43 /\.DS_Store$/,44 /__MACOSX\//,45];4647function listArchive(archivePath: string): string[] {48 return execFileSync("unzip", ["-Z1", archivePath], { encoding: "utf8" })49 .split("\n")50 .filter(Boolean);51}5253describe("PB-26-212: source package — required files", () => {54 it("collects all security-critical files including crypto-core", () => {55 const files = collectSourceFiles();56 for (const required of REQUIRED_FILES) {57 expect(files).toContain(required);58 }59 });6061 it("includes crypto-core/Cargo.toml, Cargo.lock, and src/lib.rs (the core regression)", () => {62 const files = collectSourceFiles();63 expect(files).toContain("crypto-core/Cargo.toml");64 expect(files).toContain("crypto-core/Cargo.lock");65 expect(files).toContain("crypto-core/src/lib.rs");66 });6768 it("excludes forbidden directories and files", () => {69 const files = collectSourceFiles();70 for (const file of files) {71 if (file === REVIEWED_THIRD_PARTY_FILE) continue;72 for (const pattern of EXCLUDE_PATTERNS) {73 expect(pattern.test(file)).toBe(false);74 }75 }76 });7778 it("collects files sorted and without duplicates", () => {79 const files = collectSourceFiles();80 const sorted = [...files].sort();81 expect(files).toEqual(sorted);82 expect(new Set(files).size).toBe(files.length);83 });84});8586describe("PB-26-212: SOURCE-MANIFEST.json", () => {87 it("has correct structure with version, noise WASM SHA-256, toolchain, and file hashes", () => {88 const files = collectSourceFiles();89 const manifest = buildSourceManifest(files, "0.0.0-test");9091 expect(manifest.peachVersion).toBe("0.0.0-test");92 expect(manifest.noiseWasmSha256).toMatch(/^[0-9a-f]{64}$/);93 expect(manifest.toolchain).toBeDefined();94 expect(manifest.toolchain.bun).toBeDefined();95 expect(manifest.toolchain.node).toBeDefined();96 expect(manifest.toolchain.rust).toBeDefined();9798 expect(manifest.files).toHaveLength(files.length);99 for (const entry of manifest.files) {100 expect(entry.path).toBeDefined();101 expect(entry.sha256).toMatch(/^[0-9a-f]{64}$/);102 }103 });104105 it("noiseWasmSha256 matches the actual shipped WASM", () => {106 const wasmPath = join(107 ROOT,108 "src/shared/noise-core-wasm/peach_noise_core_bg.wasm",109 );110 const expected = createHash("sha256")111 .update(readFileSync(wasmPath))112 .digest("hex");113 const files = collectSourceFiles();114 const manifest = buildSourceManifest(files, "0.0.0-test");115 expect(manifest.noiseWasmSha256).toBe(expected);116 });117118 it("file hashes match actual file contents", () => {119 const files = collectSourceFiles();120 const manifest = buildSourceManifest(files, "0.0.0-test");121 for (const entry of manifest.files) {122 const actual = createHash("sha256")123 .update(readFileSync(join(ROOT, entry.path)))124 .digest("hex");125 expect(entry.sha256).toBe(actual);126 }127 });128});129130describe("PB-26-212: source archive contents", () => {131 it("creates an archive containing every required file exactly once", () => {132 const archivePath = packageSourceArchive("0.0.0-test");133 expect(existsSync(archivePath)).toBe(true);134135 const contents = listArchive(archivePath);136 for (const required of REQUIRED_FILES) {137 const matches = contents.filter((c) => c === required);138 expect(matches).toHaveLength(1);139 }140141 expect(contents).toContain("SOURCE-MANIFEST.json");142 });143144 it("excludes forbidden paths from the archive", () => {145 const archivePath = packageSourceArchive("0.0.0-test");146 const contents = listArchive(archivePath);147 148 149 for (const file of contents) {150 if (file === REVIEWED_THIRD_PARTY_FILE) continue;151 for (const pattern of EXCLUDE_PATTERNS) {152 expect(pattern.test(file)).toBe(false);153 }154 }155 });156157 it("archive SOURCE-MANIFEST.json contains the noise WASM SHA-256 and version", () => {158 const archivePath = packageSourceArchive("0.0.0-test");159 const manifestJson = execFileSync(160 "unzip",161 ["-p", archivePath, "SOURCE-MANIFEST.json"],162 { encoding: "utf8" },163 );164 const manifest: SourceManifest = JSON.parse(manifestJson);165166 expect(manifest.peachVersion).toBe("0.0.0-test");167 expect(manifest.noiseWasmSha256).toMatch(/^[0-9a-f]{64}$/);168 expect(Array.isArray(manifest.files)).toBe(true);169 expect(manifest.files.length).toBeGreaterThan(0);170 });171172 it("archive contains the same crypto-core source as the repo (rebuild provenance)", () => {173 const archivePath = packageSourceArchive("0.0.0-test");174175 const archiveLibRs = execFileSync(176 "unzip",177 ["-p", archivePath, "crypto-core/src/lib.rs"],178 { encoding: "utf8" },179 );180 const repoLibRs = readFileSync(181 join(ROOT, "crypto-core/src/lib.rs"),182 "utf8",183 );184 expect(archiveLibRs).toBe(repoLibRs);185186 const archiveBuildScript = execFileSync(187 "unzip",188 ["-p", archivePath, "scripts/build-noise-core.mjs"],189 { encoding: "utf8" },190 );191 const repoBuildScript = readFileSync(192 join(ROOT, "scripts/build-noise-core.mjs"),193 "utf8",194 );195 expect(archiveBuildScript).toBe(repoBuildScript);196197 const manifestJson = execFileSync(198 "unzip",199 ["-p", archivePath, "SOURCE-MANIFEST.json"],200 { encoding: "utf8" },201 );202 const manifest: SourceManifest = JSON.parse(manifestJson);203 const wasmPath = join(204 ROOT,205 "src/shared/noise-core-wasm/peach_noise_core_bg.wasm",206 );207 const expectedWasmHash = createHash("sha256")208 .update(readFileSync(wasmPath))209 .digest("hex");210 expect(manifest.noiseWasmSha256).toBe(expectedWasmHash);211 });212});213214describe("PB-26-212: reproducibility", () => {215 it("produces byte-identical archives from the same baseline", () => {216 const archive1 = packageSourceArchive("0.0.0-repro");217 const hash1 = createHash("sha256")218 .update(readFileSync(archive1))219 .digest("hex");220221 const archive2 = packageSourceArchive("0.0.0-repro");222 const hash2 = createHash("sha256")223 .update(readFileSync(archive2))224 .digest("hex");225226 expect(hash1).toBe(hash2);227 });228});229230describe("PB-26-212: all entry points use the shared packager", () => {231 it("package.json package:source calls scripts/package-source.ts", () => {232 const pkg = JSON.parse(readFileSync(join(ROOT, "package.json"), "utf8"));233 expect(pkg.scripts["package:source"]).toContain("package-source.ts");234 });235236 it("package.json package:source does not contain a duplicate file list", () => {237 const pkg = JSON.parse(readFileSync(join(ROOT, "package.json"), "utf8"));238 const script = pkg.scripts["package:source"];239 expect(script).not.toContain("manifest.json manifest.firefox.json");240 expect(script).not.toMatch(/^zip\s/);241 });242243 it("release.ts imports and calls packageSourceArchive", () => {244 const source = readFileSync(join(ROOT, "scripts/release.ts"), "utf8");245 expect(source).toContain("packageSourceArchive");246 });247248 it("release-firefox.ts imports and calls packageSourceArchive", () => {249 const source = readFileSync(250 join(ROOT, "scripts/release-firefox.ts"),251 "utf8",252 );253 expect(source).toContain("packageSourceArchive");254 });255256 it("release-lib.ts does not contain a duplicate zip file list", () => {257 const source = readFileSync(join(ROOT, "scripts/release-lib.ts"), "utf8");258 expect(source).not.toContain("packageFirefoxSourceZip");259 });260});