@rightkit/release 0.2.69 → 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 (45) hide show
  1. package/package.json +4 -2
  2. package/rightkit-versions.json +3 -2
  3. package/addon-command.test.mjs +0 -54
  4. package/addon-contract.test.mjs +0 -48
  5. package/asr-artifact-adoption.test.mjs +0 -122
  6. package/build-invocation-contract.test.mjs +0 -124
  7. package/build-release.test.mjs +0 -242
  8. package/cache-command.test.mjs +0 -118
  9. package/cache-policy.test.mjs +0 -346
  10. package/cargo-guard.test.mjs +0 -195
  11. package/cargo-target.test.mjs +0 -82
  12. package/create-mac-updater.test.mjs +0 -14
  13. package/github-release.test.mjs +0 -103
  14. package/heavy-command.test.mjs +0 -221
  15. package/legal-contract.test.mjs +0 -151
  16. package/mirror-root-artifact.test.mjs +0 -58
  17. package/model-promote.test.mjs +0 -284
  18. package/notary-auth.test.mjs +0 -31
  19. package/nsis-payload.test.mjs +0 -139
  20. package/nsis-upgrade-contract.test.mjs +0 -57
  21. package/pipeline-normalization-contract.test.mjs +0 -140
  22. package/preflight.test.mjs +0 -123
  23. package/progress-control.test.mjs +0 -56
  24. package/prune-r2.test.mjs +0 -12
  25. package/publish-cargo.test.mjs +0 -43
  26. package/publish-swift.test.mjs +0 -44
  27. package/publish-update.test.mjs +0 -207
  28. package/qa-contract.test.mjs +0 -47
  29. package/registry-parity.test.mjs +0 -30
  30. package/release-cli-contract.test.mjs +0 -119
  31. package/release-invocation.test.mjs +0 -22
  32. package/release-state.test.mjs +0 -395
  33. package/release-token.test.mjs +0 -21
  34. package/release.test.mjs +0 -533
  35. package/right-suite-contract.test.mjs +0 -1011
  36. package/rightapps-register.test.mjs +0 -28
  37. package/runtime-artifact-manifest.test.mjs +0 -128
  38. package/sign-updater.test.mjs +0 -12
  39. package/source-gate.test.mjs +0 -25
  40. package/standalone-clone-evidence.json +0 -32
  41. package/standalone-clone-verify.test.mjs +0 -76
  42. package/suite-doctor.test.mjs +0 -19
  43. package/target-bridge.test.mjs +0 -269
  44. package/tauri-bundle-marker.test.mjs +0 -81
  45. package/upload-large.test.mjs +0 -70
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rightkit/release",
3
- "version": "0.2.69",
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,7 +12,9 @@
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,
18
20
  "scripts": {
@@ -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.69",
25
+ "@rightkit/release": "0.2.70",
26
26
  "@rightkit/tauri": "0.1.1",
27
27
  "@rightkit/updates": "0.2.4"
28
28
  },
@@ -64,7 +64,8 @@
64
64
  "0.2.65",
65
65
  "0.2.66",
66
66
  "0.2.67",
67
- "0.2.68"
67
+ "0.2.68",
68
+ "0.2.69"
68
69
  ],
69
70
  "@rightkit/qa": [
70
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
- });
@@ -1,242 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { mkdtempSync, readFileSync, readdirSync, 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 test from "node:test";
7
- import { fileURLToPath } from "node:url";
8
- import { parseCpuSeconds, processGroupCpuSeconds } from "./process-liveness.mjs";
9
- import { brokerManagedHost } from "./cargo-contract.mjs";
10
-
11
- const source = readFileSync(path.join(path.dirname(fileURLToPath(import.meta.url)), "build-release.mjs"), "utf8");
12
- const build = path.join(path.dirname(fileURLToPath(import.meta.url)), "build-release.mjs");
13
-
14
- test("general build dry-run is filesystem-pure and never starts its package command", () => {
15
- const root = mkdtempSync(path.join(os.tmpdir(), "right-build-dry-run-"));
16
- writeFileSync(path.join(root, "right-release.config.mjs"), "export default { app: 'fixture', version: '1.0.0', targets: { mac: { package: { cmd: 'false' } } } };\n");
17
- for (const args of [["init", "--initial-branch", "main"], ["config", "user.email", "test@example.com"], ["config", "user.name", "Test"], ["add", "."], ["commit", "-m", "init"]]) execFileSync("git", args, { cwd: root });
18
- const before = readdirSync(root).sort();
19
- const result = spawnSync(process.execPath, [build, "--config", "right-release.config.mjs", "--platform", "mac", "--dry-run"], { cwd: root, encoding: "utf8" });
20
- assert.equal(result.status, 0, result.stderr);
21
- assert.match(result.stdout, /build dry-run/);
22
- assert.deepEqual(readdirSync(root).sort(), before);
23
- });
24
-
25
- test("macOS and Windows builds default to Cache V2 while explicit legacy mode remains available", () => {
26
- assert.match(source, /assertCleanSource/);
27
- assert.match(source, /right-release build/);
28
- assert.match(source, /RIGHT_RELEASE_CACHE_MODE/);
29
- assert.match(source, /platform === "mac" \|\| platform === "win" \? "shared" : "legacy"/);
30
- assert.match(source, /cacheModeRequested !== "legacy" && cacheModeRequested !== "shared"/);
31
- assert.match(source, /resolveSharedCacheRoot/);
32
- assert.match(source, /acquireSuiteBuildSlot/);
33
- assert.match(source, /acquireHeavyWorkSlot/);
34
- assert.match(source, /heavyCommandEnvironment/);
35
- assert.match(source, /heavySlot\.trackChild/);
36
- assert.match(source, /detached: process\.platform !== "win32"/);
37
- assert.match(source, /acquireCacheLease/);
38
- assert.match(source, /markCacheEntrySuccessful/);
39
- assert.match(source, /RIGHT_RELEASE_CACHE_OWNER/);
40
- assert.match(source, /suiteSlot\?\.release\(\)/);
41
- assert.match(source, /heavySlot\?\.release\(\)/);
42
- assert.match(source, /cacheLease\?\.release\(\)/);
43
- assert.doesNotMatch(source, /process\.exit\(signal/);
44
- assert.match(source, /throwIfInterrupted/);
45
- assert.match(source, /delete env\.RIGHT_RELEASE_CACHE_OWNER/);
46
- assert.match(source, /assertPrimaryReleaseCheckout\(repoRoot\)/);
47
- assert.match(source, /dirtyBuildInputs\(repoRoot/);
48
- });
49
-
50
- test("production builds package from the real primary app checkout", () => {
51
- assert.match(source, /const configPath = path\.resolve\(configName\)/);
52
- assert.match(source, /const appRoot = path\.dirname\(configPath\)/);
53
- assert.match(source, /\[WORKER, "--config", configPath, "--platform", platform, "--no-upload"\]/);
54
- assert.match(source, /sealRelease\(\{ configRoot: appRoot,/);
55
- });
56
-
57
- test("Windows native executables preserve arguments containing spaces", () => {
58
- assert.match(source, /const useShell = process\.platform === "win32" && !\/\\\.\(\?:exe\|com\)\$\/i\.test\(cmd\);/);
59
- assert.match(source, /shell: useShell/);
60
- assert.doesNotMatch(source, /shell: process\.platform === "win32"/);
61
- });
62
-
63
- test("sealing resolves installer and updater artifacts against the broker target on a managed host", () => {
64
- assert.match(source, /sealRelease\(\{ configRoot: appRoot, managedCargoTarget,/);
65
- assert.match(source, /function sealRelease\(\{ configRoot, managedCargoTarget,/);
66
- assert.match(source, /const targetPrefix = "src-tauri\/target\/";/);
67
- assert.match(source, /if \(managedCargoTarget && normalized\.startsWith\(targetPrefix\)\) \{/);
68
- // Every configRoot-relative artifact lookup inside sealRelease must route
69
- // through the resolver, or a managed build fails at seal after a full
70
- // compile, sign, and notarize because src-tauri/target is never populated
71
- // locally on a broker-managed host (target-bridge.mjs leaves it alone).
72
- assert.doesNotMatch(source, /addSource\(path\.resolve\(configRoot, artifact\.file\)/);
73
- assert.doesNotMatch(source, /addSource\(path\.resolve\(configRoot, artifact\.signature\)/);
74
- assert.match(source, /addSource\(resolveArtifactPath\(artifact\.file\), "installer"\)/);
75
- assert.match(source, /addSource\(resolveArtifactPath\(artifact\.file\), "updater"\)/);
76
- assert.match(source, /addSource\(resolveArtifactPath\(artifact\.signature\), "updater-signature"\)/);
77
- assert.match(source, /const source = resolveArtifactPath\(artifact\.file\);/);
78
- assert.match(source, /const signatureSource = resolveArtifactPath\(artifact\.signature\);/);
79
- });
80
-
81
- test("production builds never create or use an internal Git worktree", () => {
82
- assert.doesNotMatch(source, /git", \["worktree", "add"/);
83
- assert.doesNotMatch(source, /path\.join\(vaultRoot, "worktrees"/);
84
- assert.doesNotMatch(source, /worktreeConfigPath/);
85
- });
86
-
87
- test("dirty release config is rejected before the config module is imported", () => {
88
- const guard = source.indexOf("dirtyBuildInputs(repoRoot, { include: [relativeConfig], required: [relativeConfig] }, commit)");
89
- const imported = source.indexOf("await import(");
90
- assert.ok(guard >= 0, "config cleanliness guard must exist");
91
- assert.ok(guard < imported, "config cleanliness guard must run before module import");
92
- });
93
-
94
- test("shared Cache V2 target bridge is owned by a finally-cleaned lifecycle", () => {
95
- assert.match(source, /createTargetBridge\(\{\s*link: targetLink,\s*target: managedCargoTarget \?\? env\.CARGO_TARGET_DIR,/);
96
- // ownedRoot must stay wired: without it the bridge refuses every link left by
97
- // an earlier fingerprint, so any Cargo.lock or version bump hard-stops the
98
- // next build until someone deletes the link by hand.
99
- assert.match(source, /ownedRoot: path\.dirname\(managedCargoTarget \?\? env\.CARGO_TARGET_DIR\)/);
100
- assert.match(source, /targetBridge\.run\(.*runBuildStateMachine/s);
101
- // A broker-managed host also routes through the bridge even though cacheMode
102
- // is forced to "broker" there — its brokerManaged flag is what keeps a real
103
- // (non-symlink) target directory from being treated as corruption.
104
- assert.match(source, /if \(cacheMode === "shared" \|\| managedCargoTarget\) targetBridge\.ensure\(\)/);
105
- });
106
-
107
- test("a broker-managed host forces broker cacheMode so Cache V2's lease/prune/floor chain never runs against an unrelated volume", () => {
108
- assert.match(
109
- source,
110
- /const cacheMode = managedCargoTarget \? "broker" : cacheModeRequested;/,
111
- );
112
- // Every Cache V2 lease/slot/prune/floor/mark call stays gated on cacheMode,
113
- // never on cacheModeRequested directly, so forcing cacheMode to "broker" on a
114
- // broker host is sufficient to skip all of them without a second gate.
115
- for (const guarded of [
116
- /acquireSuiteBuildSlot\(\{ layout: sharedLayout/,
117
- /cacheLease = acquireCacheLease\(\{ layout: sharedLayout/,
118
- /planCachePrune\(\{ snapshot, policy, protectedEntryIds: new Set\(\[sharedLayout\.id\]\) \}\)/,
119
- /assertWriteVolumeFloors\(\{ volumes, policy,/,
120
- /markCacheEntrySuccessful\(\{ layout: sharedLayout \}\)/,
121
- ]) {
122
- assert.match(source, guarded);
123
- }
124
- assert.doesNotMatch(source, /if \(cacheModeRequested === "shared"\) \{\s*\n\s*suiteSlot/);
125
- });
126
-
127
- test("Windows seal and cache identity bind the signing contract, config, and receipts", () => {
128
- assert.match(source, /signingIdentity = platform === "win"/);
129
- assert.match(source, /contract: target\.signingContract/);
130
- assert.match(source, /configSha256: hashFile\(configPath\)/);
131
- assert.match(source, /windows-\$\{phase\}\.json/);
132
- assert.match(source, /inputHashes\["\.right-release\/signing-identity\.json"\]/);
133
- assert.match(source, /cache: cacheIdentity\.fingerprint, signingIdentity/);
134
- assert.match(source, /receipts: signingIdentity\.receiptInputs\.map/);
135
- assert.match(readFileSync(new URL("./release.mjs", import.meta.url), "utf8"), /item\.after\?\.sha256 !== currentHashes/);
136
- });
137
-
138
- test("CPU sampling separates a silent working step from a hang", () => {
139
- // Real `ps -o cputime=` shapes, including a multi-process group.
140
- assert.equal(parseCpuSeconds(" 0:03.21\n"), 3.21);
141
- assert.equal(parseCpuSeconds(" 1:02:03\n"), 3723);
142
- assert.equal(parseCpuSeconds(" 2-01:00:00\n"), 176400);
143
- // A process group sums every member, which is how a linker under cargo shows up.
144
- assert.equal(parseCpuSeconds(" 0:10.00\n 0:05.00\n"), 15);
145
- // A vanished process yields no rows: null, not zero, so the caller falls back
146
- // to output and mtime rather than treating it as "no CPU used".
147
- assert.equal(parseCpuSeconds(""), null);
148
- assert.equal(parseCpuSeconds(" \n"), null);
149
- assert.equal(parseCpuSeconds("garbage"), null);
150
- assert.equal(parseCpuSeconds(undefined), null);
151
- });
152
-
153
- test("CPU sampling degrades to the old behaviour rather than inventing a verdict", () => {
154
- const ok = (stdout) => () => ({ status: 0, stdout });
155
- // A working process group reports advancing CPU.
156
- assert.equal(processGroupCpuSeconds(123, { platform: "darwin", run: ok(" 0:42.50\n") }), 42.5);
157
- // Windows has no `ps`; sampling is skipped, not guessed.
158
- assert.equal(processGroupCpuSeconds(123, { platform: "win32", run: ok(" 0:42.50\n") }), null);
159
- // No pid, a failed probe, or an empty result must all be "cannot measure",
160
- // never 0 — 0 would look like a hang and kill a healthy build.
161
- assert.equal(processGroupCpuSeconds(0, { platform: "darwin", run: ok(" 0:42.50\n") }), null);
162
- assert.equal(processGroupCpuSeconds(123, { platform: "darwin", run: () => ({ status: 1, stdout: "" }) }), null);
163
- assert.equal(processGroupCpuSeconds(123, { platform: "darwin", run: ok("") }), null);
164
- });
165
-
166
- test("package liveness owner timeout floors outer watchdog through shared release limits", () => {
167
- assert.match(source, /livenessOwnerTimeoutMs: target\.package\.livenessOwnerTimeoutMs \?\? null/);
168
- assert.match(source, /releaseProgressLimits\(env, \{ livenessOwnerTimeoutMs \}\)/);
169
- });
170
-
171
- // Regression: the managed-host gate must not depend on RIGHTKIT_BUILD_BROKER_SOCKET
172
- // alone. That variable is exported by login shells only, while the managed cargo
173
- // shim is on PATH in every shell — so an agent shell took the Cache V2 branch,
174
- // bridged src-tauri/target into the release cache, and then lost CARGO_TARGET_DIR
175
- // to the broker's unconditional override. The app built into the broker workspace
176
- // and packaging failed on an empty cache before notarize/staple.
177
- const managedHostDetected = (env, platform, exists) => brokerManagedHost(env, platform, exists);
178
-
179
- test("managed-host gate fires from the managed shim on PATH when no broker socket is exported", () => {
180
- // The build gate is exactly the shared brokerManagedHost predicate cargo-contract.mjs exports,
181
- // so there is exactly one definition of "is this a broker-managed host" in the codebase.
182
- assert.match(
183
- source,
184
- /const managedCargoTarget = brokerManagedHost\(process\.env\) \? resolveTargetRoot\(cargoToml\) : null;/,
185
- );
186
- assert.match(source, /import \{ brokerManagedHost \} from "\.\/cargo-contract\.mjs";/);
187
- assert.match(source, /import \{ resolveTargetRoot \} from "\.\/cargo-target\.mjs";/);
188
-
189
- const shim = "/Volumes/D/.rightkit-managed/agent-bin/cargo";
190
- const exists = (candidate) => candidate === shim;
191
-
192
- // Agent (non-login) shell: no socket variable, managed shim first on PATH.
193
- assert.equal(managedHostDetected({ PATH: `${path.dirname(shim)}:/usr/bin` }, "darwin", exists), true);
194
- // Login shell: unchanged behaviour, with or without the shim resolvable.
195
- assert.equal(
196
- managedHostDetected({ PATH: `${path.dirname(shim)}:/usr/bin`, RIGHTKIT_BUILD_BROKER_SOCKET: "/managed/broker.sock" }, "darwin", exists),
197
- true,
198
- );
199
- assert.equal(managedHostDetected({ PATH: "/usr/bin", RIGHTKIT_BUILD_BROKER_SOCKET: "/managed/broker.sock" }, "darwin", () => false), true);
200
- // Unmanaged host: neither signal, so the Cache V2 branch still owns the build.
201
- assert.equal(managedHostDetected({ PATH: "/usr/bin:/usr/local/bin" }, "darwin", () => false), false);
202
- // PATH precedence still decides: an unmanaged toolchain ahead of the shim is unmanaged.
203
- const both = new Set(["/usr/local/bin/cargo", shim]);
204
- assert.equal(managedHostDetected({ PATH: `/usr/local/bin:${path.dirname(shim)}` }, "darwin", (c) => both.has(c)), false);
205
- });
206
-
207
- test("brokerManagedHost is the single shared predicate: env var alone, shim alone, both, or neither", () => {
208
- const shim = "/Volumes/D/.rightkit-managed/agent-bin/cargo";
209
- const exists = (candidate) => candidate === shim;
210
-
211
- // Broker socket alone (login shell), no shim resolvable.
212
- assert.equal(brokerManagedHost({ RIGHTKIT_BUILD_BROKER_SOCKET: "/managed/broker.sock", PATH: "/usr/bin" }, "darwin", () => false), true);
213
- // Shim on PATH alone (agent/non-login shell), no socket exported.
214
- assert.equal(brokerManagedHost({ PATH: `${path.dirname(shim)}:/usr/bin` }, "darwin", exists), true);
215
- // Both signals present.
216
- assert.equal(
217
- brokerManagedHost({ RIGHTKIT_BUILD_BROKER_SOCKET: "/managed/broker.sock", PATH: `${path.dirname(shim)}:/usr/bin` }, "darwin", exists),
218
- true,
219
- );
220
- // Neither signal: unmanaged host.
221
- assert.equal(brokerManagedHost({ PATH: "/usr/bin:/usr/local/bin" }, "darwin", () => false), false);
222
- });
223
-
224
- test("the managed branch hands the Rust target directory to the broker while the unmanaged branch keeps Cache V2", () => {
225
- // Managed: CARGO_TARGET_DIR (and the rest of the Cache V2 environment) is deleted
226
- // from the child environment, so the broker's own override is the only one.
227
- assert.match(
228
- source,
229
- /if \(managedCargoTarget\) \{\n\s*for \(const name of \["CARGO_HOME", "CARGO_TARGET_DIR", "RUSTC_WRAPPER", "SCCACHE_DIR", "SCCACHE_BASEDIRS", "SCCACHE_CACHE_SIZE", "RUSTFLAGS"\]\) \{\n\s*delete env\[name\];/,
230
- );
231
- // Managed: no vault build root, and the bridge points at the metadata-reported target.
232
- assert.match(source, /const buildRoot = managedCargoTarget \? null :/);
233
- assert.match(source, /target: managedCargoTarget \?\? env\.CARGO_TARGET_DIR,/);
234
- assert.match(source, /ownedRoot: path\.dirname\(managedCargoTarget \?\? env\.CARGO_TARGET_DIR\),/);
235
- // The managed target is read from crate metadata through the shim, which is what
236
- // makes the bridge target equal to the directory the broker actually builds into.
237
- // resolveTargetRoot (cargo-target.mjs) is the single shared implementation —
238
- // build-release.mjs no longer defines its own copy.
239
- assert.doesNotMatch(source, /function resolveManagedCargoTarget/);
240
- // Unmanaged (managedCargoTarget === null): Cache V2 still supplies CARGO_TARGET_DIR.
241
- assert.match(source, /\.\.\.releaseEnvironment\(\{ root: vaultRoot, cacheRoot: sharedCacheRoot,/);
242
- });