@rightkit/release 0.2.68 → 0.2.70

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/cli/right-release.mjs +0 -0
  2. package/package.json +13 -9
  3. package/release.mjs +47 -14
  4. package/rightkit-versions.json +4 -2
  5. package/addon-command.test.mjs +0 -54
  6. package/addon-contract.test.mjs +0 -48
  7. package/asr-artifact-adoption.test.mjs +0 -122
  8. package/build-invocation-contract.test.mjs +0 -124
  9. package/build-release.test.mjs +0 -242
  10. package/cache-command.test.mjs +0 -118
  11. package/cache-policy.test.mjs +0 -346
  12. package/cargo-guard.test.mjs +0 -195
  13. package/cargo-target.test.mjs +0 -82
  14. package/create-mac-updater.test.mjs +0 -14
  15. package/github-release.test.mjs +0 -103
  16. package/heavy-command.test.mjs +0 -221
  17. package/legal-contract.test.mjs +0 -151
  18. package/mirror-root-artifact.test.mjs +0 -58
  19. package/model-promote.test.mjs +0 -284
  20. package/notary-auth.test.mjs +0 -31
  21. package/nsis-payload.test.mjs +0 -139
  22. package/nsis-upgrade-contract.test.mjs +0 -57
  23. package/preflight.test.mjs +0 -123
  24. package/progress-control.test.mjs +0 -56
  25. package/prune-r2.test.mjs +0 -12
  26. package/publish-cargo.test.mjs +0 -43
  27. package/publish-swift.test.mjs +0 -44
  28. package/publish-update.test.mjs +0 -207
  29. package/qa-contract.test.mjs +0 -47
  30. package/registry-parity.test.mjs +0 -30
  31. package/release-cli-contract.test.mjs +0 -119
  32. package/release-invocation.test.mjs +0 -22
  33. package/release-state.test.mjs +0 -395
  34. package/release-token.test.mjs +0 -21
  35. package/release.test.mjs +0 -477
  36. package/right-suite-contract.test.mjs +0 -1007
  37. package/rightapps-register.test.mjs +0 -28
  38. package/runtime-artifact-manifest.test.mjs +0 -128
  39. package/sign-updater.test.mjs +0 -12
  40. package/source-gate.test.mjs +0 -25
  41. package/standalone-clone-evidence.json +0 -32
  42. package/standalone-clone-verify.test.mjs +0 -76
  43. package/suite-doctor.test.mjs +0 -19
  44. package/target-bridge.test.mjs +0 -269
  45. package/tauri-bundle-marker.test.mjs +0 -81
  46. package/upload-large.test.mjs +0 -70
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rightkit/release",
3
- "version": "0.2.68",
3
+ "version": "0.2.70",
4
4
  "description": "Portable Right Suite release CLI/SDK: native-host signed installers, updater artifacts, hardening, immutable GitHub Release upload, and add-on adoption.",
5
5
  "license": "MIT OR Apache-2.0",
6
6
  "type": "module",
@@ -12,9 +12,18 @@
12
12
  "*.mjs",
13
13
  "*.json",
14
14
  "*.sh",
15
- "*.py"
15
+ "*.py",
16
+ "!*.test.mjs",
17
+ "!standalone-clone-evidence.json"
16
18
  ],
17
19
  "sideEffects": false,
20
+ "scripts": {
21
+ "test": "node --test --test-concurrency=1 --test-force-exit *.test.mjs",
22
+ "test:registry-parity": "node registry-parity.mjs --allow-unpublished",
23
+ "prepublishOnly": "pnpm test && pnpm test:registry-parity",
24
+ "doctor:all": "node --test right-suite-contract.test.mjs",
25
+ "verify:standalone": "node standalone-clone-verify.mjs"
26
+ },
18
27
  "publishConfig": {
19
28
  "registry": "https://registry.npmjs.org/",
20
29
  "access": "public"
@@ -24,10 +33,5 @@
24
33
  "url": "git+https://github.com/bogusyogi/claude.git",
25
34
  "directory": "tools/rightkit/packages/release"
26
35
  },
27
- "scripts": {
28
- "test": "node --test --test-concurrency=1 --test-force-exit *.test.mjs",
29
- "test:registry-parity": "node registry-parity.mjs --allow-unpublished",
30
- "doctor:all": "node --test right-suite-contract.test.mjs",
31
- "verify:standalone": "node standalone-clone-verify.mjs"
32
- }
33
- }
36
+ "packageManager": "pnpm@11.24.0"
37
+ }
package/release.mjs CHANGED
@@ -29,7 +29,8 @@ const RIGHTKIT_ALLOWED = buildAllowedVersions(
29
29
  );
30
30
  const RIGHTKIT_CARGO_ALLOWED = buildAllowedVersions(RIGHTKIT_VERSIONS.cargo, RIGHTKIT_VERSIONS.stagedCargo);
31
31
  const FORBIDDEN_RIGHTKIT_SPEC = /^(?:git|file|link|workspace):|github|github\.com/i;
32
- const WINDOWS_SIGNING_CONTRACT = "windows-raw-exe-authenticode-before-nsis-v1";
32
+ const WINDOWS_NSIS_SIGNING_CONTRACT = "windows-raw-exe-authenticode-before-nsis-v1";
33
+ const WINDOWS_PORTABLE_SIGNING_CONTRACT = "windows-raw-exe-authenticode-before-portable-v1";
33
34
  const DEFAULT_NOTARIZATION_TIMEOUT_MS = 30 * 60 * 1000;
34
35
 
35
36
  const args = process.argv.slice(2);
@@ -91,7 +92,7 @@ if (config.schema !== 1) fail(`unsupported config schema: ${config.schema ?? "<m
91
92
 
92
93
  const root = path.dirname(configPath);
93
94
  const workdir = path.resolve(root, config.workdir ?? ".");
94
- await validateRightKitPackageContract(root, config.app);
95
+ await validateRightKitPackageContract(root, config.app, config.hostedWorkflows);
95
96
  validateRightKitCargoContract(root, RIGHTKIT_CARGO_ALLOWED, config.app ?? path.basename(root));
96
97
  const target = config.targets?.[opts.platform];
97
98
  if (!target) fail(`${config.app ?? "app"} has no ${opts.platform} release target`);
@@ -101,14 +102,16 @@ const legalContract = config.legal
101
102
  if (target.signed !== true) {
102
103
  fail(`${config.app ?? "app"} ${opts.platform} must declare signed: true; unsigned release targets are forbidden`);
103
104
  }
104
- if (opts.platform === "win" && !target.sign?.files?.length) {
105
- fail(`${config.app ?? "app"} win must declare sign.files for the signed release pipeline`);
106
- }
107
105
  if (opts.platform === "win") {
108
- if (target.signingContract !== WINDOWS_SIGNING_CONTRACT) fail(`${config.app ?? "app"} win must declare signingContract: ${WINDOWS_SIGNING_CONTRACT}`);
106
+ const isNsis = target.signingContract === WINDOWS_NSIS_SIGNING_CONTRACT;
107
+ const isPortable = target.signingContract === WINDOWS_PORTABLE_SIGNING_CONTRACT;
108
+ if (!isNsis && !isPortable) fail(`${config.app ?? "app"} win must declare signingContract: ${WINDOWS_NSIS_SIGNING_CONTRACT} or ${WINDOWS_PORTABLE_SIGNING_CONTRACT}`);
109
109
  if (!target.prePackage?.cmd) fail(`${config.app ?? "app"} win must declare prePackage for the raw EXE build`);
110
- if (target.sign?.prePackageFiles?.length !== 1) fail(`${config.app ?? "app"} win must declare exactly one sign.prePackageFiles raw EXE`);
111
- if (target.sign.prePackageFiles.some((file) => target.sign.files.includes(file))) fail(`${config.app ?? "app"} win raw EXE and installer signing files must be distinct`);
110
+ if (!target.sign?.prePackageFiles?.length) fail(`${config.app ?? "app"} win must declare at least one sign.prePackageFiles raw EXE`);
111
+ if (isNsis && target.sign.prePackageFiles.length !== 1) fail(`${config.app ?? "app"} win NSIS must declare exactly one sign.prePackageFiles raw EXE`);
112
+ if (isNsis && !target.sign?.files?.length) fail(`${config.app ?? "app"} win NSIS must declare sign.files for installer signing`);
113
+ if (isNsis && target.sign.prePackageFiles.some((file) => target.sign.files.includes(file))) fail(`${config.app ?? "app"} win raw EXE and installer signing files must be distinct`);
114
+ if (isPortable && target.sign?.files?.length) fail(`${config.app ?? "app"} win portable release must sign every executable through sign.prePackageFiles and omit sign.files`);
112
115
  }
113
116
  if (opts.upload && target.publishBlocked) fail(`${config.app ?? "app"} ${opts.platform} publish blocked: ${target.publishBlocked}`);
114
117
  /**
@@ -206,7 +209,8 @@ if (opts.platform === "win") {
206
209
  // bundling, and doing that to signed bytes invalidates Authenticode — the exact
207
210
  // defect that shipped invalid binaries. Idempotent, so an app that still patches
208
211
  // in its own prePackage script simply reports alreadyPatched here.
209
- bundleMarkerReceipts = opts.dryRun ? [] : rawFiles.map((file) => patchTauriBundleType(file, { bundle: target.bundleMarker ?? "nsis" }));
212
+ const isNsis = target.signingContract === WINDOWS_NSIS_SIGNING_CONTRACT;
213
+ bundleMarkerReceipts = !isNsis || opts.dryRun ? [] : rawFiles.map((file) => patchTauriBundleType(file, { bundle: target.bundleMarker ?? "nsis" }));
210
214
  for (const receipt of bundleMarkerReceipts) {
211
215
  console.log(`right-release: bundle marker ${receipt.bundle} ${receipt.alreadyPatched ? "already applied" : "applied"} at offset ${receipt.offset} in ${path.basename(receipt.file)}`);
212
216
  }
@@ -219,7 +223,7 @@ for (const rel of target.artifacts ?? []) {
219
223
  await mustExist(path.resolve(root, rel), `missing release artifact: ${rel}`);
220
224
  }
221
225
 
222
- if (opts.platform === "win" && target.sign?.files?.length) {
226
+ if (opts.platform === "win" && target.signingContract === WINDOWS_NSIS_SIGNING_CONTRACT) {
223
227
  const files = target.sign.files.map((p) => path.resolve(root, p));
224
228
  for (const file of files) await mustExist(file, `missing signing artifact: ${file}`);
225
229
  // Before the installer earns its own signature, prove it carries the bytes we
@@ -360,16 +364,16 @@ function commandTimeoutMs(command) {
360
364
  : undefined;
361
365
  }
362
366
 
363
- async function validateRightKitPackageContract(root, appName) {
367
+ async function validateRightKitPackageContract(root, appName, hostedWorkflows) {
364
368
  const packageJsonPath = path.join(root, "package.json");
365
369
  const pkg = JSON.parse(await readFile(packageJsonPath, "utf8"));
366
370
  const scripts = pkg.scripts ?? {};
367
371
  assertQaBackdoorContract(root, scripts);
368
372
  const workflowDir = path.join(root, ".github", "workflows");
369
373
  if (existsSync(workflowDir)) {
370
- const workflows = await readdir(workflowDir);
374
+ const workflows = (await readdir(workflowDir)).sort();
371
375
  if (workflows.length) {
372
- fail(`${appName ?? pkg.name ?? "app"} hosted workflow files are forbidden: ${workflows.join(", ")}`);
376
+ await validateHostedWorkflows(root, appName ?? pkg.name ?? "app", workflows, hostedWorkflows);
373
377
  }
374
378
  }
375
379
  if (pkg.packageManager !== RIGHTKIT_VERSIONS.packageManager) {
@@ -394,6 +398,33 @@ async function validateRightKitPackageContract(root, appName) {
394
398
  }
395
399
  }
396
400
 
401
+ async function validateHostedWorkflows(root, appName, workflows, policy) {
402
+ if (policy !== "right-git-ci-only") {
403
+ fail(`${appName} hosted workflow files are forbidden: ${workflows.join(", ")}`);
404
+ }
405
+ if (workflows.length !== 1 || workflows[0] !== "ci.yml") {
406
+ fail(`${appName} right-git-ci-only policy permits only ci.yml, got: ${workflows.join(", ")}`);
407
+ }
408
+ const manifestPath = path.join(root, ".rightgit.json");
409
+ let manifest;
410
+ try {
411
+ manifest = JSON.parse(await readFile(manifestPath, "utf8"));
412
+ } catch {
413
+ fail(`${appName} right-git-ci-only policy requires a valid .rightgit.json`);
414
+ }
415
+ if (manifest?.schemaVersion !== 1 || JSON.stringify(manifest?.lanes) !== JSON.stringify(["ci"])) {
416
+ fail(`${appName} right-git-ci-only policy requires .rightgit.json lanes=["ci"]`);
417
+ }
418
+ const source = await readFile(path.join(root, ".github", "workflows", "ci.yml"), "utf8");
419
+ if (!source.startsWith("# Managed by right-git")) {
420
+ fail(`${appName} ci.yml is not marked as right-git managed`);
421
+ }
422
+ if (/^\s*(?:actions|attestations|contents|id-token|packages|security-events):\s*write\s*$/mi.test(source)
423
+ || /\bsecrets\s*\./i.test(source)) {
424
+ fail(`${appName} right-git managed ci.yml requests release-capable permissions or secrets`);
425
+ }
426
+ }
427
+
397
428
  function buildAllowedVersions(published = {}, staged = {}, legacy = {}) {
398
429
  const allowed = new Map();
399
430
  for (const [name, value] of [
@@ -423,7 +454,7 @@ function expandEnv(value) {
423
454
 
424
455
  async function signWindows(files, phase, root) {
425
456
  const args = [SIGN_WINDOWS];
426
- const receipt = path.join(process.env.RIGHT_RELEASE_STATE_ROOT ?? path.join(root, ".right-release", "receipts"), `windows-${phase}.json`);
457
+ const receipt = windowsReceiptPath(root, phase);
427
458
  if (opts.dryRun) args.push("--dry-run");
428
459
  else {
429
460
  if (existsSync(receipt)) unlinkSync(receipt);
@@ -435,6 +466,8 @@ async function signWindows(files, phase, root) {
435
466
  }
436
467
 
437
468
  function windowsReceiptPath(root, phase) {
469
+ const configured = phase === "raw-exe" ? target.sign?.receipt : target.sign?.installerReceipt;
470
+ if (configured) return path.resolve(root, configured);
438
471
  return path.join(process.env.RIGHT_RELEASE_STATE_ROOT ?? path.join(root, ".right-release", "receipts"), `windows-${phase}.json`);
439
472
  }
440
473
 
@@ -22,7 +22,7 @@
22
22
  "@rightkit/logs": "0.1.4",
23
23
  "@rightkit/platform-ui": "0.1.1",
24
24
  "@rightkit/qa": "0.2.1",
25
- "@rightkit/release": "0.2.68",
25
+ "@rightkit/release": "0.2.70",
26
26
  "@rightkit/tauri": "0.1.1",
27
27
  "@rightkit/updates": "0.2.4"
28
28
  },
@@ -63,7 +63,9 @@
63
63
  "0.2.64",
64
64
  "0.2.65",
65
65
  "0.2.66",
66
- "0.2.67"
66
+ "0.2.67",
67
+ "0.2.68",
68
+ "0.2.69"
67
69
  ],
68
70
  "@rightkit/qa": [
69
71
  "0.1.0",
@@ -1,54 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { existsSync, mkdtempSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
- import os from "node:os";
4
- import path from "node:path";
5
- import { execFileSync, spawnSync } from "node:child_process";
6
- import { fileURLToPath, pathToFileURL } from "node:url";
7
- import test from "node:test";
8
- import { createAddonManifest, addonManifestSha256, canonicalAddonManifest, downloadImmutableAddOn, immutableAddonManifestUrl } from "./addon-contract.mjs";
9
-
10
- const command = fileURLToPath(new URL("./addon-command.mjs", import.meta.url));
11
- function fixture() {
12
- const root = mkdtempSync(path.join(os.tmpdir(), "right-addon-command-"));
13
- mkdirSync(path.join(root, "legal")); mkdirSync(path.join(root, "assets"));
14
- for (const name of ["LICENSE", "EULA.txt", "PRIVACY.md", "THIRD-PARTY-NOTICES.txt"]) writeFileSync(path.join(root, "legal", name), name);
15
- writeFileSync(path.join(root, "assets", "tab.png"), "icon");
16
- const files = [["command", "membrane", "out/membrane", true], ["service", "membrane-service", "out/membrane-service", true], ["icon", "membrane-tab-icon.png", "assets/tab.png", false], ["license", "LICENSE", "legal/LICENSE", false], ["eula", "EULA.txt", "legal/EULA.txt", false], ["privacy", "PRIVACY.md", "legal/PRIVACY.md", false], ["third-party-notices", "THIRD-PARTY-NOTICES.txt", "legal/THIRD-PARTY-NOTICES.txt", false]].map(([role, name, source, executable]) => ({ role, name, source, executable }));
17
- writeFileSync(path.join(root, "right-addon.config.mjs"), `export default ${JSON.stringify({ schema: 1, kind: "headless-addon", addon: "membrane", version: "0.1.0", packageManager: "pnpm", checks: [], buildInputs: { include: ["right-addon.config.mjs"] }, consumer: { contract: "membrane-product-v1" }, targets: { mac: { targetTriple: "aarch64-apple-darwin", build: { cmd: "false", args: [] }, signing: { contract: "apple-developer-id-executable-v1", teamId: "6KLGD3LLKF" }, files }, win: { targetTriple: "x86_64-pc-windows-msvc", build: { cmd: "false", args: [] }, signing: { contract: "azure-artifact-signing-v1" }, files } } }, null, 2)};\n`);
18
- execFileSync("git", ["init", "--initial-branch", "main"], { cwd: root }); execFileSync("git", ["config", "user.email", "test@example.com"], { cwd: root }); execFileSync("git", ["config", "user.name", "Test"], { cwd: root }); execFileSync("git", ["add", "."], { cwd: root }); execFileSync("git", ["commit", "-m", "init"], { cwd: root });
19
- return root;
20
- }
21
- test("add-on dry-run creates no release state and never executes build", () => {
22
- const root = fixture(); const result = spawnSync(process.execPath, [command, "build", "--config", "right-addon.config.mjs", "--platform", "mac", "--dry-run"], { cwd: root, encoding: "utf8" });
23
- assert.equal(result.status, 0, result.stderr); assert.match(result.stdout, /would run: false/); assert.equal(existsSync(path.join(root, ".right-release")), false);
24
- });
25
-
26
- test("local adoption verifies lock and stages portable plus Tauri-qualified binaries", async () => {
27
- const root = fixture(); const config = (await import(`${pathToFileURL(path.join(root, "right-addon.config.mjs")).href}?test=${Date.now()}`)).default;
28
- mkdirSync(path.join(root, "out")); writeFileSync(path.join(root, "out", "membrane"), "command"); writeFileSync(path.join(root, "out", "membrane-service"), "service");
29
- const manifest = createAddonManifest({ config, root, platform: "mac", commit: "d".repeat(40), signing: { command: { contract: "test-fixture-v1", status: "verified" }, service: { contract: "test-fixture-v1", status: "verified" } } });
30
- const sealed = path.join(root, "sealed"); mkdirSync(sealed);
31
- for (const file of manifest.files) writeFileSync(path.join(sealed, file.name), readFileSync(path.join(root, config.targets.mac.files.find((entry) => entry.role === file.role).source)));
32
- writeFileSync(path.join(sealed, "addon-manifest.json"), canonicalAddonManifest(manifest));
33
- const digest = addonManifestSha256(manifest); const lock = path.join(root, "membrane.lock.json"); writeFileSync(lock, JSON.stringify({ targets: { mac: { manifestUrl: `https://example.test/membrane/addons/mac/sha256/${digest}/addon-manifest.json`, manifestSha256: digest } } }));
34
- const output = path.join(root, "addons"); const unsigned = spawnSync(process.execPath, [command, "adopt", "--platform", "mac", "--lock", lock, "--output", `${output}-unsigned`, "--source", sealed], { cwd: root, encoding: "utf8", env: { ...process.env, RIGHT_RELEASE_TEST_FIXTURE: "" } });
35
- assert.notEqual(unsigned.status, 0); assert.match(unsigned.stderr, /unexpected mac signing contract/);
36
- const result = spawnSync(process.execPath, [command, "adopt", "--platform", "mac", "--lock", lock, "--output", output, "--source", sealed], { cwd: root, encoding: "utf8", env: { ...process.env, RIGHT_RELEASE_TEST_FIXTURE: "1" } });
37
- assert.equal(result.status, 0, result.stderr);
38
- assert.equal(readFileSync(path.join(output, "membrane", "bin", "membrane-aarch64-apple-darwin"), "utf8"), "command");
39
- assert.equal(readFileSync(path.join(output, "membrane", "resources", "LICENSE"), "utf8"), "LICENSE");
40
- const receipt = JSON.parse(readFileSync(path.join(root, ".right-release", "addons", "adoptions", "membrane-mac.json"), "utf8"));
41
- assert.equal(receipt.manifestSha256, digest);
42
- assert.deepEqual(receipt.files.map((file) => [file.role, file.sealedSha256, file.adoptedSha256]), [["command", manifest.files.find((file) => file.role === "command").sha256, manifest.files.find((file) => file.role === "command").sha256], ["service", manifest.files.find((file) => file.role === "service").sha256, manifest.files.find((file) => file.role === "service").sha256]]);
43
- });
44
-
45
- test("remote downloader accepts only immutable manifest plus file routes", async () => {
46
- const root = fixture(); const config = (await import(`${pathToFileURL(path.join(root, "right-addon.config.mjs")).href}?remote=${Date.now()}`)).default;
47
- mkdirSync(path.join(root, "out")); writeFileSync(path.join(root, "out", "membrane"), "command"); writeFileSync(path.join(root, "out", "membrane-service"), "service");
48
- const manifest = createAddonManifest({ config, root, platform: "mac", commit: "e".repeat(40), signing: { command: { contract: "test-fixture-v1", status: "verified" }, service: { contract: "test-fixture-v1", status: "verified" } } });
49
- const digest = addonManifestSha256(manifest); const base = `https://example.test/membrane/addons/mac/sha256/${digest}/`; const bytes = new Map([[`${base}addon-manifest.json`, Buffer.from(canonicalAddonManifest(manifest))], ...manifest.files.map((file) => [base + encodeURIComponent(file.name), readFileSync(path.join(root, config.targets.mac.files.find((entry) => entry.role === file.role).source))])]);
50
- const destination = path.join(root, "remote"); mkdirSync(destination);
51
- await downloadImmutableAddOn(`${base}addon-manifest.json`, destination, { fetchImpl: async (url) => ({ ok: bytes.has(url), status: bytes.has(url) ? 200 : 404, arrayBuffer: async () => bytes.get(url) }) });
52
- assert.equal(readFileSync(path.join(destination, "membrane-service"), "utf8"), "service");
53
- assert.equal(immutableAddonManifestUrl(manifest, `https://github.com/Orthic-Labs/Membrane/releases/download/addon-membrane-v0.1.0-mac-sha256-${digest}/addon-manifest.json`, digest), true);
54
- });
@@ -1,48 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { mkdtempSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
- import os from "node:os";
4
- import path from "node:path";
5
- import test from "node:test";
6
- import { addonManifestSha256, addonRoutes, createAddonManifest, validateAddonConfig, validateAddonManifest } from "./addon-contract.mjs";
7
-
8
- function fixture() {
9
- const root = mkdtempSync(path.join(os.tmpdir(), "right-addon-contract-"));
10
- mkdirSync(path.join(root, "bin")); mkdirSync(path.join(root, "assets")); mkdirSync(path.join(root, "legal"));
11
- for (const [file, bytes] of [["bin/membrane", "membrane"], ["bin/membrane-service", "service"], ["assets/tab.png", "icon"], ["legal/LICENSE", "license"], ["legal/EULA.txt", "eula"], ["legal/PRIVACY.md", "privacy"], ["legal/THIRD-PARTY-NOTICES.txt", "notice"]]) writeFileSync(path.join(root, file), bytes);
12
- const files = [
13
- ["command", "membrane", "bin/membrane", true], ["service", "membrane-service", "bin/membrane-service", true], ["icon", "membrane-tab-icon.png", "assets/tab.png", false],
14
- ["license", "LICENSE", "legal/LICENSE", false], ["eula", "EULA.txt", "legal/EULA.txt", false], ["privacy", "PRIVACY.md", "legal/PRIVACY.md", false], ["third-party-notices", "THIRD-PARTY-NOTICES.txt", "legal/THIRD-PARTY-NOTICES.txt", false],
15
- ].map(([role, name, source, executable]) => ({ role, name, source, executable }));
16
- return { root, config: { schema: 1, kind: "headless-addon", addon: "membrane", version: "0.1.0", packageManager: "pnpm", checks: ["test"], buildInputs: { include: ["right-addon.config.mjs"] }, consumer: { contract: "membrane-product-v1" }, targets: { mac: { targetTriple: "aarch64-apple-darwin", build: { cmd: "cargo", args: ["build"] }, signing: { contract: "apple-developer-id-executable-v1", teamId: "6KLGD3LLKF" }, files }, win: { targetTriple: "x86_64-pc-windows-msvc", build: { cmd: "cargo", args: ["build"] }, signing: { contract: "azure-artifact-signing-v1" }, files } } } };
17
- }
18
-
19
- test("add-on manifest has portable immutable identity and routes", () => {
20
- const { root, config } = fixture();
21
- const signing = { command: { contract: "test", status: "verified" }, service: { contract: "test", status: "verified" } };
22
- const manifest = createAddonManifest({ config, root, platform: "mac", commit: "a".repeat(40), signedAt: "2026-08-11T00:00:00.000Z", signing });
23
- assert.deepEqual(Object.keys(manifest), ["schema", "kind", "addon", "version", "commit", "platform", "targetTriple", "consumer", "files", "signedAt"]);
24
- const digest = addonManifestSha256(manifest); const routes = addonRoutes(manifest);
25
- assert.equal(routes.at(-1).manifest, true);
26
- assert.match(routes[0].key, new RegExp(`^membrane/addons/mac/sha256/${digest}/`));
27
- assert.ok(routes.slice(0, -1).every((route) => !route.manifest));
28
- });
29
-
30
- test("add-on contract rejects unsafe, incomplete, duplicate, and unsigned inputs", () => {
31
- const { root, config } = fixture();
32
- const broken = structuredClone(config); broken.targets.mac.files[0].source = "../escape";
33
- assert.throws(() => validateAddonConfig(broken, { root, platform: "mac" }), /unsafe add-on source/);
34
- const duplicate = structuredClone(config); duplicate.targets.mac.files[1].role = "command";
35
- assert.throws(() => validateAddonConfig(duplicate, { root, platform: "mac" }), /duplicate/);
36
- const signing = { command: { contract: "test", status: "verified" }, service: { contract: "test", status: "verified" } };
37
- const manifest = createAddonManifest({ config, root, platform: "mac", commit: "b".repeat(40), signing });
38
- manifest.files.find((file) => file.role === "command").signing = null;
39
- assert.throws(() => validateAddonManifest(manifest), /unsigned executable/);
40
- });
41
-
42
- test("manifest excludes source and mutable transport fields", () => {
43
- const { root, config } = fixture();
44
- const manifest = createAddonManifest({ config, root, platform: "win", commit: "c".repeat(40), signing: { command: { contract: "test", status: "verified" }, service: { contract: "test", status: "verified" } } });
45
- const text = JSON.stringify(manifest);
46
- assert.doesNotMatch(text, /source|path|url|token/i);
47
- assert.equal(readFileSync(path.join(root, "bin", "membrane"), "utf8"), "membrane");
48
- });
@@ -1,122 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { generateKeyPairSync } from "node:crypto";
3
- import test from "node:test";
4
-
5
- import { assertAsrAdapterPair, assertAsrArtifactAdoption } from "./asr-artifact-adoption.mjs";
6
- import { buildRuntimeArtifactManifest, signRuntimeArtifactManifest } from "./runtime-artifact-manifest.mjs";
7
-
8
- const digest = "a".repeat(64);
9
- const notice = "b".repeat(64);
10
- const evidence = "c".repeat(64);
11
- const { privateKey, publicKey } = generateKeyPairSync("ed25519");
12
-
13
- function envelope(appKey, overrides = {}) {
14
- const manifest = buildRuntimeArtifactManifest({
15
- artifactKind: "asr-model",
16
- entitlement: { appKey, tier: "pro" },
17
- distribution: { delivery: "private-r2", bucket: "rightapps-updates" },
18
- target: { os: "windows", arch: "x86_64" },
19
- filename: "parakeet-unified-en-0.6b.tar.zst",
20
- sha256: digest,
21
- sizeBytes: 1234,
22
- versions: {
23
- runtime: "rightkit-asr@0.1.0",
24
- model: "parakeet-unified-en-0.6b",
25
- tokenizer: "sha256:tokenizer-v1",
26
- preprocessing: "heardright-frontend-v1",
27
- license: "CC-BY-4.0",
28
- provenance: "nvidia/parakeet-tdt-0.6b-v3",
29
- },
30
- provenance: {
31
- source: "https://huggingface.co/nvidia/parakeet-tdt-0.6b-v3",
32
- sourceRevision: "revision-1",
33
- licenseId: "CC-BY-4.0",
34
- noticeSha256: notice,
35
- },
36
- promotion: {
37
- authorityAppKey: "heardright",
38
- promotionId: "hr-asr-2026-07-14",
39
- promotedAt: "2026-07-14T00:00:00.000Z",
40
- evidenceSha256: evidence,
41
- },
42
- ...overrides,
43
- });
44
- return signRuntimeArtifactManifest(manifest, privateKey, "rightkit-runtime-artifacts-2026-07");
45
- }
46
-
47
- test("accepts app-scoped pointers with one exact HeardRight-certified ASR identity", () => {
48
- const result = assertAsrArtifactAdoption({
49
- authorityEnvelope: envelope("heardright"),
50
- consumerEnvelope: envelope("scraperight"),
51
- publicKey,
52
- trustedKeyId: "rightkit-runtime-artifacts-2026-07",
53
- });
54
-
55
- assert.equal(result.sha256, digest);
56
- assert.equal(result.promotionId, "hr-asr-2026-07-14");
57
- });
58
-
59
- test("rejects a validly signed ScrapeRight manifest with ASR drift", () => {
60
- const drifted = envelope("scraperight", {
61
- versions: {
62
- runtime: "rightkit-asr@0.1.1",
63
- model: "parakeet-unified-en-0.6b",
64
- tokenizer: "sha256:tokenizer-v1",
65
- preprocessing: "heardright-frontend-v1",
66
- license: "CC-BY-4.0",
67
- provenance: "nvidia/parakeet-tdt-0.6b-v3",
68
- },
69
- });
70
-
71
- assert.throws(() => assertAsrArtifactAdoption({
72
- authorityEnvelope: envelope("heardright"),
73
- consumerEnvelope: drifted,
74
- publicKey,
75
- trustedKeyId: "rightkit-runtime-artifacts-2026-07",
76
- }), /ASR drift: versions\.runtime/);
77
- });
78
-
79
- test("rejects wrong roles, authority, target, signature key, and artifact kind", () => {
80
- const base = {
81
- authorityEnvelope: envelope("heardright"),
82
- consumerEnvelope: envelope("scraperight"),
83
- publicKey,
84
- trustedKeyId: "rightkit-runtime-artifacts-2026-07",
85
- };
86
- assert.throws(() => assertAsrArtifactAdoption({ ...base, authorityEnvelope: envelope("scraperight") }), /authority manifest must belong to heardright/);
87
- assert.throws(() => assertAsrArtifactAdoption({ ...base, consumerEnvelope: envelope("heardright") }), /consumer manifest must belong to scraperight/);
88
- assert.throws(() => assertAsrArtifactAdoption({ ...base, trustedKeyId: "rotated-without-app-update" }), /untrusted runtime artifact key id/);
89
- assert.throws(() => assertAsrArtifactAdoption({
90
- ...base,
91
- consumerEnvelope: envelope("scraperight", { target: { os: "darwin", arch: "aarch64" } }),
92
- }), /ASR drift: target/);
93
- assert.throws(() => assertAsrArtifactAdoption({
94
- ...base,
95
- consumerEnvelope: envelope("scraperight", { artifactKind: "ocr-model" }),
96
- }), /must be an asr-model/);
97
- });
98
-
99
- test("rejects a consumer signed by a different key", () => {
100
- const other = generateKeyPairSync("ed25519");
101
- const consumer = signRuntimeArtifactManifest(
102
- envelope("scraperight").manifest,
103
- other.privateKey,
104
- "rightkit-runtime-artifacts-2026-07",
105
- );
106
- assert.throws(() => assertAsrArtifactAdoption({
107
- authorityEnvelope: envelope("heardright"),
108
- consumerEnvelope: consumer,
109
- publicKey,
110
- trustedKeyId: "rightkit-runtime-artifacts-2026-07",
111
- }), /signature verification failed/);
112
- });
113
-
114
- test("locks the thin app adapters to HeardRight authority and private Pro delivery", () => {
115
- const authority = {
116
- schema: 1, appKey: "heardright", role: "authority", authorityAppKey: "heardright",
117
- artifactKind: "asr-model", delivery: "private-r2", entitlementTier: "pro",
118
- };
119
- const consumer = { ...authority, appKey: "scraperight", role: "consumer" };
120
- assert.equal(assertAsrAdapterPair(authority, consumer), true);
121
- assert.throws(() => assertAsrAdapterPair(authority, { ...consumer, authorityAppKey: "scraperight" }), /ASR drift/);
122
- });
@@ -1,124 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { execFileSync } from "node:child_process";
3
- import { mkdtempSync, mkdirSync, writeFileSync } from "node:fs";
4
- import os from "node:os";
5
- import path from "node:path";
6
- import test from "node:test";
7
-
8
- import { assertPrimaryReleaseCheckout, dirtyBuildInputs } from "./release-invocation.mjs";
9
-
10
- function git(cwd, ...args) {
11
- return execFileSync("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
12
- }
13
-
14
- function makeRepo() {
15
- const root = mkdtempSync(path.join(os.tmpdir(), "right-release-invocation-"));
16
- git(root, "init", "--initial-branch", "main");
17
- git(root, "config", "user.email", "release-test@example.com");
18
- git(root, "config", "user.name", "Release Test");
19
- mkdirSync(path.join(root, "src"));
20
- mkdirSync(path.join(root, "tests"));
21
- mkdirSync(path.join(root, "docs"));
22
- writeFileSync(path.join(root, "package.json"), "{}\n");
23
- writeFileSync(path.join(root, "src", "app.js"), "export const app = true;\n");
24
- writeFileSync(path.join(root, "tests", "app.test.js"), "// test\n");
25
- writeFileSync(path.join(root, "docs", "notes.md"), "notes\n");
26
- git(root, "add", ".");
27
- git(root, "commit", "-m", "initial");
28
- return root;
29
- }
30
-
31
- const buildInputs = {
32
- include: ["package.json", "src/**"],
33
- exclude: ["**/*.test.*", "**/tests/**"],
34
- json: {
35
- "package.json": [["dependencies"], ["scripts", "build"]],
36
- },
37
- };
38
-
39
- test("rejects release invocation from a linked Git worktree", () => {
40
- const primary = makeRepo();
41
- const linked = path.join(path.dirname(primary), `${path.basename(primary)}-linked`);
42
- git(primary, "worktree", "add", "--detach", linked);
43
-
44
- assert.throws(
45
- () => assertPrimaryReleaseCheckout(linked),
46
- /must be invoked from the primary Git worktree/i,
47
- );
48
- });
49
-
50
- test("allows release invocation from the primary Git checkout", () => {
51
- const primary = makeRepo();
52
-
53
- assert.doesNotThrow(() => assertPrimaryReleaseCheckout(primary));
54
- });
55
-
56
- test("rejects a detached HEAD even in the primary Git checkout", () => {
57
- const primary = makeRepo();
58
- git(primary, "checkout", "--detach");
59
-
60
- assert.throws(
61
- () => assertPrimaryReleaseCheckout(primary),
62
- /detached HEAD is forbidden/i,
63
- );
64
- });
65
-
66
- test("reports a dirty tracked file declared as a build input", () => {
67
- const primary = makeRepo();
68
- writeFileSync(path.join(primary, "src", "app.js"), "export const app = false;\n");
69
-
70
- assert.deepEqual(dirtyBuildInputs(primary, buildInputs), ["src/app.js"]);
71
- });
72
-
73
- test("reports an untracked file declared as a build input", () => {
74
- const primary = makeRepo();
75
- writeFileSync(path.join(primary, "src", "new-runtime.js"), "export {};\n");
76
-
77
- assert.deepEqual(dirtyBuildInputs(primary, buildInputs), ["src/new-runtime.js"]);
78
- });
79
-
80
- test("ignores dirty files outside the packaged app input set", () => {
81
- const primary = makeRepo();
82
- writeFileSync(path.join(primary, "docs", "notes.md"), "changed notes\n");
83
- writeFileSync(path.join(primary, "tests", "app.test.js"), "// changed test\n");
84
- writeFileSync(path.join(primary, "scratch.txt"), "evaluation output\n");
85
-
86
- assert.deepEqual(dirtyBuildInputs(primary, buildInputs), []);
87
- });
88
-
89
- test("required release metadata cannot be hidden by build-input exclusions", () => {
90
- const primary = makeRepo();
91
- writeFileSync(path.join(primary, "package.json"), "{\"version\":\"2.0.0\"}\n");
92
-
93
- assert.deepEqual(dirtyBuildInputs(primary, {
94
- ...buildInputs,
95
- exclude: [...buildInputs.exclude, "package.json"],
96
- required: ["package.json"],
97
- }), ["package.json"]);
98
- assert.deepEqual(dirtyBuildInputs(primary, {
99
- include: ["package.json"],
100
- required: ["package.json"],
101
- }), ["package.json"]);
102
- });
103
-
104
- test("checks only build-relevant fields in mixed-purpose JSON manifests", () => {
105
- const primary = makeRepo();
106
- writeFileSync(path.join(primary, "package.json"), `${JSON.stringify({ scripts: { bakeoff: "node eval.mjs" } })}\n`);
107
- assert.deepEqual(dirtyBuildInputs(primary, buildInputs), []);
108
-
109
- writeFileSync(path.join(primary, "package.json"), `${JSON.stringify({ dependencies: { tauri: "2.0.0" } })}\n`);
110
- assert.deepEqual(dirtyBuildInputs(primary, buildInputs), ["package.json"]);
111
- });
112
-
113
- test("does not report a locally dirty file whose bytes already equal the release commit", () => {
114
- const primary = makeRepo();
115
- const localHead = git(primary, "rev-parse", "HEAD");
116
- writeFileSync(path.join(primary, "src", "app.js"), "export const app = false;\n");
117
- git(primary, "add", "src/app.js");
118
- git(primary, "commit", "-m", "release change");
119
- const releaseCommit = git(primary, "rev-parse", "HEAD");
120
- git(primary, "checkout", localHead);
121
- writeFileSync(path.join(primary, "src", "app.js"), "export const app = false;\n");
122
-
123
- assert.deepEqual(dirtyBuildInputs(primary, buildInputs, releaseCommit), []);
124
- });