@rightkit/release 0.2.65 → 0.2.67

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.
@@ -27,6 +27,7 @@ import {
27
27
  resolveCacheLayout,
28
28
  resolveSharedCacheIdentity,
29
29
  resolveSharedCacheRoot,
30
+ resolveStableRoot,
30
31
  } from "./cache-policy.mjs";
31
32
 
32
33
  function root() { return path.join(os.tmpdir(), `rightkit-cache-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`); }
@@ -61,11 +62,52 @@ function migrationFixture() {
61
62
  test("cache roots are platform-native and overrides must be absolute", () => {
62
63
  assert.throws(() => resolveSharedCacheRoot({ platform: "mac", home: "/Users/test", env: {} }), /RIGHT_RELEASE_CACHE_ROOT is required/);
63
64
  assert.equal(resolveSharedCacheRoot({ platform: "win", env: { LOCALAPPDATA: "C:/Users/test/AppData/Local" } }), "C:\\Users\\test\\AppData\\Local\\RightSuite\\Cache\\release");
64
- assert.equal(resolveSharedCacheRoot({ platform: "linux", home: "/home/test", xdgCacheHome: "/tmp/xdg", env: {} }), "/tmp/xdg/rightsuite/release");
65
+ assert.equal(resolveSharedCacheRoot({ platform: "linux", home: "/home/test", env: {} }), "/home/test/.cache/rightsuite/release");
65
66
  assert.equal(resolveSharedCacheRoot({ platform: "mac", env: { RIGHT_RELEASE_CACHE_ROOT: "/tmp/cache" } }), "/tmp/cache");
66
67
  assert.throws(() => resolveSharedCacheRoot({ platform: "mac", env: { RIGHT_RELEASE_CACHE_ROOT: "relative" } }), /absolute/i);
67
68
  });
68
69
 
70
+ test("shared cache root ignores XDG_CACHE_HOME regardless of value", () => {
71
+ assert.equal(
72
+ resolveSharedCacheRoot({ platform: "linux", env: { XDG_CACHE_HOME: "/one/path" }, home: "/home/u" }),
73
+ resolveSharedCacheRoot({ platform: "linux", env: {}, home: "/home/u" }),
74
+ );
75
+ assert.equal(resolveSharedCacheRoot({ platform: "linux", env: { XDG_CACHE_HOME: "/one/path" }, home: "/home/u" }), "/home/u/.cache/rightsuite/release");
76
+ // resolveSharedCacheRoot no longer accepts an xdgCacheHome option; passing one has no effect.
77
+ assert.equal(
78
+ resolveSharedCacheRoot({ platform: "linux", env: {}, home: "/home/u", xdgCacheHome: "/should/be/ignored" }),
79
+ "/home/u/.cache/rightsuite/release",
80
+ );
81
+ });
82
+
83
+ test("resolveStableRoot: explicit override wins, win32 requires the env var, other platforms ignore arbitrary env", () => {
84
+ const opts = {
85
+ darwinSegments: ["Library", "Caches", "Example"],
86
+ win32Segments: ["Example"],
87
+ otherSegments: ["example"],
88
+ };
89
+ assert.equal(
90
+ resolveStableRoot({ ...opts, platform: "linux", explicitOverride: "/explicit/path", overrideName: "EXAMPLE_ROOT", env: { EXAMPLE_ROOT: "ignored-by-caller-arg" } }),
91
+ "/explicit/path",
92
+ );
93
+ assert.throws(
94
+ () => resolveStableRoot({ ...opts, platform: "linux", explicitOverride: "relative/path", overrideName: "EXAMPLE_ROOT" }),
95
+ /EXAMPLE_ROOT must be an absolute path/,
96
+ );
97
+ assert.throws(
98
+ () => resolveStableRoot({ ...opts, platform: "win32", env: {} }),
99
+ /LOCALAPPDATA must be an absolute path for RightSuite/,
100
+ );
101
+ assert.equal(
102
+ resolveStableRoot({ ...opts, platform: "win32", env: { LOCALAPPDATA: "C:/Users/test/AppData/Local" } }),
103
+ "C:\\Users\\test\\AppData\\Local\\Example",
104
+ );
105
+ assert.equal(
106
+ resolveStableRoot({ ...opts, platform: "linux", env: { XDG_CACHE_HOME: "/ignored", npm_config_cache: "/ignored" }, home: "/home/u" }),
107
+ "/home/u/.cache/example",
108
+ );
109
+ });
110
+
69
111
  test("layout isolates app, platform, kind, and fingerprint", () => {
70
112
  const base = root();
71
113
  const release = layout(base);
@@ -27,10 +27,14 @@ export function managedCargoShimOnPath(env = process.env, platform = process.pla
27
27
  return false;
28
28
  }
29
29
 
30
+ export function brokerManagedHost(env = process.env, platform = process.platform, exists = existsSync) {
31
+ return Boolean(env.RIGHTKIT_BUILD_BROKER_SOCKET) || managedCargoShimOnPath(env, platform, exists);
32
+ }
33
+
30
34
  export function isolatedCargoMetadataEnv(cargoHome, env = process.env, platform = process.platform, exists = existsSync) {
31
35
  const metadataEnv = { ...env };
32
36
  for (const name of CACHE_V2_ENVIRONMENT) delete metadataEnv[name];
33
- if (metadataEnv.RIGHTKIT_BUILD_BROKER_SOCKET || managedCargoShimOnPath(metadataEnv, platform, exists)) return metadataEnv;
37
+ if (brokerManagedHost(metadataEnv, platform, exists)) return metadataEnv;
34
38
  return { ...metadataEnv, CARGO_HOME: cargoHome };
35
39
  }
36
40
 
package/cargo-guard.mjs CHANGED
@@ -6,7 +6,8 @@ import os from "node:os";
6
6
  import path from "node:path";
7
7
  import { fileURLToPath } from "node:url";
8
8
 
9
- import { resolveSharedCacheRoot } from "./cache-policy.mjs";
9
+ import { resolveSharedCacheRoot, resolveStableRoot } from "./cache-policy.mjs";
10
+ import { brokerManagedHost } from "./cargo-contract.mjs";
10
11
  import { runHeavyCommand } from "./heavy-command.mjs";
11
12
 
12
13
  const LIGHT_COMMANDS = new Set(["fmt", "metadata", "tree", "fetch", "search", "locate-project", "read-manifest", "help", "version", "--version", "-V"]);
@@ -34,7 +35,14 @@ export function computePolicyPath({ env = process.env, platform = process.platfo
34
35
  return path.win32.join(env.LOCALAPPDATA || path.win32.join(home, "AppData", "Local"), "RightSuite", "compute-policy.json");
35
36
  }
36
37
  if (platform === "darwin" || platform === "mac") return path.join(home, "Library", "Application Support", "RightSuite", "compute-policy.json");
37
- return path.join(env.XDG_CONFIG_HOME || path.join(home, ".config"), "rightsuite", "compute-policy.json");
38
+ const base = resolveStableRoot({
39
+ platform,
40
+ env,
41
+ home,
42
+ otherBaseSegments: [".config"],
43
+ otherSegments: ["rightsuite"],
44
+ });
45
+ return path.join(base, "compute-policy.json");
38
46
  }
39
47
 
40
48
  export function loadComputePolicy(options = {}) {
@@ -121,6 +129,24 @@ export function resolveRealRustc({ env = process.env, run = spawnSync } = {}) {
121
129
  return path.resolve(rustc);
122
130
  }
123
131
 
132
+ // Locate the broker's own managed cargo shim on PATH (the agent-bin launcher
133
+ // that routes into the build broker). Used to hand a whole invocation to the
134
+ // broker on a managed host instead of resolving & running the real toolchain
135
+ // ourselves — which would bypass managed build control entirely.
136
+ export function resolveManagedCargoShim({ env = process.env, platform = process.platform, exists = existsSync } = {}) {
137
+ const win = platform === "win32" || platform === "win";
138
+ const delimiter = win ? ";" : ":";
139
+ const agentBin = /(?:^|[\\/])(?:\.rightkit-managed|rightkitmanagedagent)[\\/]agent-bin[\\/]?$/i;
140
+ for (const directory of String(env.PATH ?? "").split(delimiter).filter(Boolean)) {
141
+ if (!agentBin.test(directory)) continue;
142
+ for (const name of win ? ["cargo.exe", "cargo.cmd"] : ["cargo"]) {
143
+ const candidate = path.join(directory, name);
144
+ if (exists(candidate)) return candidate;
145
+ }
146
+ }
147
+ return null;
148
+ }
149
+
124
150
  function pathApi(platform) {
125
151
  return platform === "win32" || platform === "win" ? path.win32 : path.posix;
126
152
  }
@@ -164,10 +190,18 @@ export function cargoCacheEnvironment(args, {
164
190
  const targetOption = args.find((value) => value.startsWith("--target-dir="));
165
191
  const targetIndex = args.findIndex((value) => value === "--target-dir");
166
192
  const explicitTarget = targetOption?.slice("--target-dir=".length) ?? (targetIndex >= 0 ? args[targetIndex + 1] : null);
167
- if (explicitTarget) assertInsideCache(explicitTarget, cacheRoot, "--target-dir", { cwd, platform });
168
- const targetDir = env.CARGO_TARGET_DIR
169
- ? assertInsideCache(env.CARGO_TARGET_DIR, cacheRoot, "CARGO_TARGET_DIR", { cwd, platform })
170
- : defaultTarget;
193
+ // On a broker-managed host, CARGO_TARGET_DIR is not this cache's to police: the
194
+ // build broker unconditionally overrides it to its own per-namespace workspace
195
+ // outside the shared cache root, so asserting it stays inside cacheRoot is a
196
+ // spurious failure, not a real violation. Pass the broker's own value through
197
+ // untouched instead of rejecting it.
198
+ const broker = brokerManagedHost(env, platform, exists);
199
+ if (explicitTarget && !broker) assertInsideCache(explicitTarget, cacheRoot, "--target-dir", { cwd, platform });
200
+ const targetDir = broker
201
+ ? (env.CARGO_TARGET_DIR ?? explicitTarget ?? defaultTarget)
202
+ : env.CARGO_TARGET_DIR
203
+ ? assertInsideCache(env.CARGO_TARGET_DIR, cacheRoot, "CARGO_TARGET_DIR", { cwd, platform })
204
+ : defaultTarget;
171
205
  const sccacheDir = env.SCCACHE_DIR
172
206
  ? assertInsideCache(env.SCCACHE_DIR, cacheRoot, "SCCACHE_DIR", { cwd, platform })
173
207
  : defaultSccache;
@@ -193,8 +227,20 @@ function spawnCargo(command, args, env) {
193
227
  });
194
228
  }
195
229
 
196
- export async function runCargoGuard(args, { env = process.env, policyOptions = {}, resolveCargo = resolveRealCargo, resolveRustc = resolveRealRustc, runHeavy = runHeavyCommand, runLight = spawnCargo } = {}) {
230
+ export async function runCargoGuard(args, { env = process.env, policyOptions = {}, resolveCargo = resolveRealCargo, resolveRustc = resolveRealRustc, runHeavy = runHeavyCommand, runLight = spawnCargo, resolveShim = resolveManagedCargoShim, isBrokerHost = brokerManagedHost } = {}) {
197
231
  assertComputePolicyAllowsCargo(args, { ...policyOptions, env });
232
+ // On a broker-managed host the build broker owns the toolchain, cache, and
233
+ // quotas. Resolving the real Cargo here and running it via runHeavy would
234
+ // bypass the broker (and, on Windows, resolveReal* itself fails: `rustup
235
+ // which` routes through the managed shim into the broker's isolated
236
+ // RUSTUP_HOME, which has no default toolchain). Hand the whole invocation to
237
+ // the broker's own cargo shim instead — everything through the broker.
238
+ if (isBrokerHost(env)) {
239
+ const shim = resolveShim({ env });
240
+ if (shim) return runLight(shim, args, env);
241
+ // No shim found despite a broker signal: fall through to legacy resolution
242
+ // rather than silently dropping the guard.
243
+ }
198
244
  const cargo = resolveCargo({ env });
199
245
  if (shouldGuardCargo(args)) {
200
246
  const guarded = cargoCacheEnvironment(args, { env });
@@ -2,7 +2,15 @@ import assert from "node:assert/strict";
2
2
  import path from "node:path";
3
3
  import test from "node:test";
4
4
 
5
- import { assertComputePolicyAllowsCargo, cargoCacheEnvironment, cargoProjectRoot, cargoSubcommand, checkHookInput, resolveRealCargo, resolveRealRustc, runCargoGuard, shellRunsCargoTest, shouldGuardCargo } from "./cargo-guard.mjs";
5
+ import { assertComputePolicyAllowsCargo, cargoCacheEnvironment, cargoProjectRoot, cargoSubcommand, checkHookInput, computePolicyPath, resolveRealCargo, resolveRealRustc, runCargoGuard, shellRunsCargoTest, shouldGuardCargo } from "./cargo-guard.mjs";
6
+
7
+ test("compute policy path ignores XDG_CONFIG_HOME regardless of value", () => {
8
+ assert.equal(
9
+ computePolicyPath({ platform: "linux", env: { XDG_CONFIG_HOME: "/one/path" }, home: "/home/u" }),
10
+ computePolicyPath({ platform: "linux", env: {}, home: "/home/u" }),
11
+ );
12
+ assert.equal(computePolicyPath({ platform: "linux", env: {}, home: "/home/u" }), path.join("/home/u/.config/rightsuite", "compute-policy.json"));
13
+ });
6
14
 
7
15
  test("Cargo guard serializes compiling commands but bypasses inspection and formatting", () => {
8
16
  for (const args of [["build"], ["test"], ["check"], ["clippy"], ["nextest", "run"], ["clean"], ["+stable", "bench"]]) {
@@ -78,6 +86,37 @@ test("Cargo guard rejects cache escapes and alternate compiler wrappers", async
78
86
  await assert.rejects(run(["build"], { RUSTC_WRAPPER: "rustc-wrapper" }), /RUSTC_WRAPPER/);
79
87
  });
80
88
 
89
+ test("Cargo guard hands the whole invocation to the broker on a managed host", async () => {
90
+ const calls = [];
91
+ let resolvedReal = false;
92
+ const options = {
93
+ env: { TEST: "1" },
94
+ policyOptions: { exists: () => false },
95
+ isBrokerHost: () => true,
96
+ resolveShim: () => "/managed/agent-bin/cargo",
97
+ resolveCargo: () => { resolvedReal = true; return "/real/cargo"; },
98
+ resolveRustc: () => { resolvedReal = true; return "/real/rustc"; },
99
+ runHeavy: async () => { throw new Error("must not run heavy on a broker host"); },
100
+ runLight: async (command, args) => { calls.push([command, args]); return 0; },
101
+ };
102
+ // A guarded command (build) and a light command (--version) both delegate to
103
+ // the broker shim verbatim, and the real-toolchain resolver is never touched.
104
+ assert.equal(await runCargoGuard(["build", "--release"], options), 0);
105
+ assert.equal(await runCargoGuard(["--version"], options), 0);
106
+ assert.deepEqual(calls, [["/managed/agent-bin/cargo", ["build", "--release"]], ["/managed/agent-bin/cargo", ["--version"]]]);
107
+ assert.equal(resolvedReal, false);
108
+ });
109
+
110
+ test("Cargo guard falls back to real resolution when a broker signal has no shim", async () => {
111
+ const calls = [];
112
+ await runCargoGuard(["build"], {
113
+ env: cacheEnv(), isBrokerHost: () => true, resolveShim: () => null,
114
+ resolveCargo: () => "/real/cargo", resolveRustc: () => "/real/rustc",
115
+ runHeavy: async (args) => { calls.push(args); return 0; },
116
+ });
117
+ assert.deepEqual(calls[0].slice(0, 2), ["--", "/real/cargo"]);
118
+ });
119
+
81
120
  test("Cargo guard routes heavy and light commands without recursion", async () => {
82
121
  const calls = [];
83
122
  const options = {
@@ -122,6 +161,30 @@ test("Cargo guard rejects cache bypasses and permits RightKit-owned cache paths"
122
161
  assert.equal(env.CARGO_TARGET_DIR, "/cache/test-targets/app");
123
162
  });
124
163
 
164
+ test("Cargo guard passes a broker-owned CARGO_TARGET_DIR through instead of rejecting it", () => {
165
+ const base = { cwd: "/repo", platform: "mac", exists: () => false };
166
+ // Outside the shared cache root, which would be rejected on a non-broker host
167
+ // (see the previous test) — but the broker owns this value, not Cache V2.
168
+ const env = cargoCacheEnvironment(["test"], {
169
+ ...base,
170
+ env: { RIGHT_RELEASE_CACHE_ROOT: "/cache", RIGHTKIT_BUILD_BROKER_SOCKET: "/managed/broker.sock", CARGO_TARGET_DIR: "/broker/workspace/target" },
171
+ });
172
+ assert.equal(env.CARGO_TARGET_DIR, "/broker/workspace/target");
173
+
174
+ // Same for an explicit --target-dir outside the cache root.
175
+ const explicit = cargoCacheEnvironment(["test", "--target-dir", "/broker/workspace/target"], {
176
+ ...base,
177
+ env: { RIGHT_RELEASE_CACHE_ROOT: "/cache", RIGHTKIT_BUILD_BROKER_SOCKET: "/managed/broker.sock" },
178
+ });
179
+ assert.doesNotThrow(() => explicit);
180
+
181
+ // Non-broker host: unchanged, still rejects the cache bypass.
182
+ assert.throws(
183
+ () => cargoCacheEnvironment(["test"], { ...base, env: { RIGHT_RELEASE_CACHE_ROOT: "/cache", CARGO_TARGET_DIR: "/tmp/target" } }),
184
+ /CARGO_TARGET_DIR must stay inside/,
185
+ );
186
+ });
187
+
125
188
  test("Cargo guard derives native Windows cache paths", () => {
126
189
  const env = cargoCacheEnvironment(["build"], {
127
190
  cwd: "D:\\Claude\\citadel", platform: "win", exists: (file) => file === "D:\\Claude\\citadel\\Cargo.toml",
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env node
2
+ // Resolves the Cargo target directory for a manifest by asking Cargo itself.
3
+ //
4
+ // `cargo metadata` is authoritative on BOTH broker-managed and unmanaged
5
+ // hosts: through a managed shim it reports the broker's own workspace target
6
+ // directory; without one it reports the local `target/` directory. LOCATING
7
+ // the target directory therefore never needs broker detection — the metadata
8
+ // command already reflects whichever host it runs on. `brokerManagedHost()`
9
+ // (see cargo-contract.mjs) remains a separate, cache-policy-only decision:
10
+ // whether Cache V2's lease/slot/prune/floor machinery should run at all, not
11
+ // where the target directory lives.
12
+ import { execFileSync } from "node:child_process";
13
+ import path from "node:path";
14
+ import { fileURLToPath } from "node:url";
15
+
16
+ export function resolveTargetRoot(manifestPath) {
17
+ if (!manifestPath) throw new Error("resolveTargetRoot requires a Cargo.toml manifest path");
18
+ let output;
19
+ try {
20
+ output = execFileSync(
21
+ "cargo",
22
+ ["metadata", "--offline", "--format-version", "1", "--no-deps", "--manifest-path", manifestPath],
23
+ { cwd: path.dirname(manifestPath), encoding: "utf8", maxBuffer: 64 * 1024 * 1024 },
24
+ );
25
+ } catch (error) {
26
+ throw new Error(`metadata failed for ${manifestPath}: ${error.message}`);
27
+ }
28
+ const { target_directory: directory } = JSON.parse(output);
29
+ if (typeof directory !== "string" || directory.length === 0 || !path.isAbsolute(directory)) {
30
+ throw new Error(`metadata for ${manifestPath} did not report an absolute target_directory`);
31
+ }
32
+ return path.resolve(directory);
33
+ }
34
+
35
+ if (process.argv[1] && fileURLToPath(import.meta.url) === path.resolve(process.argv[1])) {
36
+ const manifestPath = process.argv[2];
37
+ if (!manifestPath) {
38
+ console.error("usage: cargo-target.mjs <path-to-Cargo.toml>");
39
+ process.exit(1);
40
+ }
41
+ try {
42
+ process.stdout.write(`${resolveTargetRoot(manifestPath)}\n`);
43
+ } catch (error) {
44
+ console.error(error.message);
45
+ process.exit(1);
46
+ }
47
+ }
@@ -0,0 +1,133 @@
1
+ import assert from "node:assert/strict";
2
+ import { chmodSync, mkdtempSync, mkdirSync, writeFileSync } from "node:fs";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { spawnSync } from "node:child_process";
6
+ import test from "node:test";
7
+ import { fileURLToPath } from "node:url";
8
+
9
+ import { resolveTargetRoot } from "./cargo-target.mjs";
10
+
11
+ const here = path.dirname(fileURLToPath(import.meta.url));
12
+ const cli = path.join(here, "cargo-target.mjs");
13
+
14
+ // A fake `cargo` on PATH, ahead of the real one, so metadata success/failure
15
+ // shapes are deterministic and don't depend on the host's actual crate graph.
16
+ // It branches on a marker embedded in the manifest path so each test controls
17
+ // its own outcome independently.
18
+ function fakeCargoBin() {
19
+ const bin = mkdtempSync(path.join(os.tmpdir(), "rightkit-fake-cargo-"));
20
+ const script = path.join(bin, process.platform === "win32" ? "cargo.cmd" : "cargo");
21
+ const body = process.platform === "win32"
22
+ ? [
23
+ "@echo off",
24
+ "setlocal enabledelayedexpansion",
25
+ "set ARGS=%*",
26
+ "echo %ARGS% | findstr /C:\"fail-manifest\" >nul && (echo boom 1>&2 & exit /b 1)",
27
+ "echo %ARGS% | findstr /C:\"bad-target-manifest\" >nul && (echo {\"target_directory\":\"relative/target\"} & exit /b 0)",
28
+ "echo {\"target_directory\":\"" + path.join(bin, "target").replaceAll("\\", "\\\\") + "\"}",
29
+ ].join("\r\n")
30
+ : [
31
+ "#!/bin/sh",
32
+ 'case "$*" in',
33
+ ' *fail-manifest*) echo boom 1>&2; exit 1 ;;',
34
+ ' *bad-target-manifest*) echo \'{"target_directory":"relative/target"}\'; exit 0 ;;',
35
+ ` *) echo '{"target_directory":"${path.join(bin, "target")}"}'; exit 0 ;;`,
36
+ "esac",
37
+ ].join("\n");
38
+ writeFileSync(script, body);
39
+ if (process.platform !== "win32") chmodSync(script, 0o755);
40
+ return bin;
41
+ }
42
+
43
+ function withFakeCargo(fn) {
44
+ const bin = fakeCargoBin();
45
+ const env = { ...process.env, PATH: `${bin}${path.delimiter}${process.env.PATH ?? ""}` };
46
+ return fn({ bin, env });
47
+ }
48
+
49
+ test("resolveTargetRoot returns the absolute target_directory Cargo metadata reports", () => {
50
+ withFakeCargo(({ bin, env }) => {
51
+ const manifestDir = mkdtempSync(path.join(os.tmpdir(), "rightkit-cargo-target-fixture-"));
52
+ const manifest = path.join(manifestDir, "Cargo.toml");
53
+ writeFileSync(manifest, "[package]\nname = \"fixture\"\nversion = \"0.0.0\"\n");
54
+ const previousPath = process.env.PATH;
55
+ process.env.PATH = env.PATH;
56
+ try {
57
+ const result = resolveTargetRoot(manifest);
58
+ assert.equal(result, path.resolve(path.join(bin, "target")));
59
+ } finally {
60
+ process.env.PATH = previousPath;
61
+ }
62
+ });
63
+ });
64
+
65
+ test("resolveTargetRoot fails closed, naming the manifest, when target_directory is not absolute", () => {
66
+ withFakeCargo(({ env }) => {
67
+ const manifestDir = mkdtempSync(path.join(os.tmpdir(), "rightkit-cargo-target-fixture-"));
68
+ const manifest = path.join(manifestDir, "bad-target-manifest-Cargo.toml");
69
+ writeFileSync(manifest, "[package]\nname = \"fixture\"\nversion = \"0.0.0\"\n");
70
+ const previousPath = process.env.PATH;
71
+ process.env.PATH = env.PATH;
72
+ try {
73
+ assert.throws(
74
+ () => resolveTargetRoot(manifest),
75
+ new RegExp(`metadata for .*bad-target-manifest-Cargo\\.toml did not report an absolute target_directory`),
76
+ );
77
+ } finally {
78
+ process.env.PATH = previousPath;
79
+ }
80
+ });
81
+ });
82
+
83
+ test("resolveTargetRoot fails closed, naming the manifest, when the metadata command exits non-zero", () => {
84
+ withFakeCargo(({ env }) => {
85
+ const manifestDir = mkdtempSync(path.join(os.tmpdir(), "rightkit-cargo-target-fixture-"));
86
+ const manifest = path.join(manifestDir, "fail-manifest-Cargo.toml");
87
+ writeFileSync(manifest, "[package]\nname = \"fixture\"\nversion = \"0.0.0\"\n");
88
+ const previousPath = process.env.PATH;
89
+ process.env.PATH = env.PATH;
90
+ try {
91
+ assert.throws(
92
+ () => resolveTargetRoot(manifest),
93
+ new RegExp(`metadata failed for .*fail-manifest-Cargo\\.toml`),
94
+ );
95
+ } finally {
96
+ process.env.PATH = previousPath;
97
+ }
98
+ });
99
+ });
100
+
101
+ test("resolveTargetRoot throws when no manifest path is given", () => {
102
+ assert.throws(() => resolveTargetRoot(), /resolveTargetRoot requires a Cargo.toml manifest path/);
103
+ assert.throws(() => resolveTargetRoot(""), /resolveTargetRoot requires a Cargo.toml manifest path/);
104
+ });
105
+
106
+ test("CLI mode writes only the resolved path to stdout on success", () => {
107
+ withFakeCargo(({ bin, env }) => {
108
+ const manifestDir = mkdtempSync(path.join(os.tmpdir(), "rightkit-cargo-target-fixture-"));
109
+ const manifest = path.join(manifestDir, "Cargo.toml");
110
+ writeFileSync(manifest, "[package]\nname = \"fixture\"\nversion = \"0.0.0\"\n");
111
+ const result = spawnSync(process.execPath, [cli, manifest], { encoding: "utf8", env });
112
+ assert.equal(result.status, 0, result.stderr);
113
+ assert.equal(result.stdout.trim(), path.resolve(path.join(bin, "target")));
114
+ });
115
+ });
116
+
117
+ test("CLI mode writes a message to stderr and exits 1 on failure", () => {
118
+ withFakeCargo(({ env }) => {
119
+ const manifestDir = mkdtempSync(path.join(os.tmpdir(), "rightkit-cargo-target-fixture-"));
120
+ const manifest = path.join(manifestDir, "fail-manifest-Cargo.toml");
121
+ writeFileSync(manifest, "[package]\nname = \"fixture\"\nversion = \"0.0.0\"\n");
122
+ const result = spawnSync(process.execPath, [cli, manifest], { encoding: "utf8", env });
123
+ assert.equal(result.status, 1);
124
+ assert.equal(result.stdout, "");
125
+ assert.match(result.stderr, /metadata failed for .*fail-manifest-Cargo\.toml/);
126
+ });
127
+ });
128
+
129
+ test("CLI mode requires a manifest argument", () => {
130
+ const result = spawnSync(process.execPath, [cli], { encoding: "utf8" });
131
+ assert.equal(result.status, 1);
132
+ assert.match(result.stderr, /usage: cargo-target\.mjs/);
133
+ });
@@ -20,6 +20,7 @@ const commands = new Map([
20
20
  ["create-mac-updater", "create-mac-updater.mjs"],
21
21
  ["mirror-root-artifact", "mirror-root-artifact.mjs"],
22
22
  ["github", "github-release.mjs"],
23
+ ["cargo-target", "cargo-target.mjs"],
23
24
  ]);
24
25
 
25
26
  const args = process.argv.slice(2);
@@ -27,13 +27,13 @@ function fixture() {
27
27
  function addonFixture() {
28
28
  const root = mkdtempSync(path.join(os.tmpdir(), "right-release-github-addon-"));
29
29
  mkdirSync(path.join(root, "out"));
30
- for (const [name, bytes] of [["membrane", "command"], ["crypt-service", "service"], ["icon.png", "icon"], ["LICENSE", "license"], ["EULA.txt", "eula"], ["PRIVACY.md", "privacy"], ["THIRD-PARTY-NOTICES.txt", "notices"]]) writeFileSync(path.join(root, "out", name), bytes);
30
+ for (const [name, bytes] of [["membrane", "command"], ["membrane-service", "service"], ["icon.png", "icon"], ["LICENSE", "license"], ["EULA.txt", "eula"], ["PRIVACY.md", "privacy"], ["THIRD-PARTY-NOTICES.txt", "notices"]]) writeFileSync(path.join(root, "out", name), bytes);
31
31
  execFileSync("git", ["init", "--initial-branch", "main"], { cwd: root });
32
32
  execFileSync("git", ["config", "user.email", "test@example.com"], { cwd: root });
33
33
  execFileSync("git", ["config", "user.name", "Test"], { cwd: root });
34
34
  writeFileSync(path.join(root, "tracked"), "source"); execFileSync("git", ["add", "."], { cwd: root }); execFileSync("git", ["commit", "-m", "source"], { cwd: root });
35
- const files = [["command", "membrane", true], ["service", "crypt-service", true], ["icon", "icon.png", false], ["license", "LICENSE", false], ["eula", "EULA.txt", false], ["privacy", "PRIVACY.md", false], ["third-party-notices", "THIRD-PARTY-NOTICES.txt", false]].map(([role, name, executable]) => ({ role, name, source: `out/${name}`, executable }));
36
- const config = { schema: 1, kind: "headless-addon", addon: "membrane", version: "0.1.0", packageManager: "pnpm", distribution: { provider: "github-releases", repository: "Orthic-Labs/Membrane" }, checks: [], buildInputs: { include: ["tracked"] }, consumer: { contract: "orthic-product-v1" }, targets: { win: { targetTriple: "x86_64-pc-windows-msvc", build: { cmd: "false", args: [] }, signing: { contract: "azure-artifact-signing-v1" }, files } } };
35
+ const files = [["command", "membrane", true], ["service", "membrane-service", true], ["icon", "icon.png", false], ["license", "LICENSE", false], ["eula", "EULA.txt", false], ["privacy", "PRIVACY.md", false], ["third-party-notices", "THIRD-PARTY-NOTICES.txt", false]].map(([role, name, executable]) => ({ role, name, source: `out/${name}`, executable }));
36
+ const config = { schema: 1, kind: "headless-addon", addon: "membrane", version: "0.1.0", packageManager: "pnpm", distribution: { provider: "github-releases", repository: "Orthic-Labs/Membrane" }, checks: [], buildInputs: { include: ["tracked"] }, consumer: { contract: "membrane-product-v1" }, targets: { win: { targetTriple: "x86_64-pc-windows-msvc", build: { cmd: "false", args: [] }, signing: { contract: "azure-artifact-signing-v1" }, files } } };
37
37
  const commit = execFileSync("git", ["rev-parse", "HEAD"], { cwd: root, encoding: "utf8" }).trim();
38
38
  const manifest = createAddonManifest({ config, root, platform: "win", commit, signing: { command: { contract: "test-fixture-v1", status: "verified" }, service: { contract: "test-fixture-v1", status: "verified" } } });
39
39
  const sealed = path.join(root, ".right-release", "addons", "membrane", "0.1.0", commit.slice(0, 8), "win"); mkdirSync(sealed, { recursive: true });
package/heavy-command.mjs CHANGED
@@ -6,17 +6,23 @@ import { spawn, spawnSync } from "node:child_process";
6
6
  import { fileURLToPath } from "node:url";
7
7
  import { randomUUID } from "node:crypto";
8
8
 
9
+ import { resolveStableRoot } from "./cache-policy.mjs";
10
+
9
11
  const GB = 1024 ** 3;
10
12
  const SELF = fileURLToPath(import.meta.url);
11
13
 
12
14
  export function heavyWorkRoot({ platform = process.platform, env = process.env, home = os.homedir() } = {}) {
13
- if (env.RIGHTSUITE_HEAVY_WORK_ROOT) return path.resolve(env.RIGHTSUITE_HEAVY_WORK_ROOT);
14
- if (platform === "win32" || platform === "win") {
15
- if (!env.LOCALAPPDATA) throw new Error("LOCALAPPDATA is required for the RightSuite heavy-work guard");
16
- return path.win32.resolve(env.LOCALAPPDATA, "RightSuite", "heavy-work");
17
- }
18
- if (platform === "darwin" || platform === "mac") return path.posix.join(home, "Library", "Caches", "RightSuite", "heavy-work");
19
- return path.posix.resolve(env.XDG_CACHE_HOME || path.posix.join(home, ".cache"), "rightsuite", "heavy-work");
15
+ return resolveStableRoot({
16
+ platform,
17
+ env,
18
+ home,
19
+ explicitOverride: env.RIGHTSUITE_HEAVY_WORK_ROOT,
20
+ overrideName: "RIGHTSUITE_HEAVY_WORK_ROOT",
21
+ darwinSegments: ["Library", "Caches", "RightSuite", "heavy-work"],
22
+ win32EnvVar: "LOCALAPPDATA",
23
+ win32Segments: ["RightSuite", "heavy-work"],
24
+ otherSegments: ["rightsuite", "heavy-work"],
25
+ });
20
26
  }
21
27
 
22
28
  export function heavyCommandEnvironment(env = process.env) {
@@ -26,6 +26,14 @@ test("heavy-work root is machine-wide rather than repository-local", () => {
26
26
  assert.equal(heavyWorkRoot({ platform: "win", env: { LOCALAPPDATA: "C:\\Users\\test\\AppData\\Local" } }), "C:\\Users\\test\\AppData\\Local\\RightSuite\\heavy-work");
27
27
  });
28
28
 
29
+ test("heavy-work root ignores XDG_CACHE_HOME regardless of value", () => {
30
+ assert.equal(
31
+ heavyWorkRoot({ platform: "linux", env: { XDG_CACHE_HOME: "/one/path" }, home: "/home/u" }),
32
+ heavyWorkRoot({ platform: "linux", env: {}, home: "/home/u" }),
33
+ );
34
+ assert.equal(heavyWorkRoot({ platform: "linux", env: {}, home: "/home/u" }), "/home/u/.cache/rightsuite/heavy-work");
35
+ });
36
+
29
37
  test("heavy commands default compiler and test concurrency to two", () => {
30
38
  const env = heavyCommandEnvironment({});
31
39
  assert.equal(env.CARGO_BUILD_JOBS, "2");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rightkit/release",
3
- "version": "0.2.65",
3
+ "version": "0.2.67",
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",
@@ -26,6 +26,7 @@
26
26
  },
27
27
  "scripts": {
28
28
  "test": "node --test *.test.mjs",
29
+ "test:registry-parity": "node registry-parity.mjs --allow-unpublished",
29
30
  "doctor:all": "node --test right-suite-contract.test.mjs",
30
31
  "verify:standalone": "node standalone-clone-verify.mjs"
31
32
  }
package/preflight.mjs CHANGED
@@ -21,6 +21,7 @@
21
21
  import { existsSync, lstatSync, readdirSync, readFileSync, statSync } from "node:fs";
22
22
  import path from "node:path";
23
23
  import { spawnSync } from "node:child_process";
24
+ import { brokerManagedHost } from "./cargo-contract.mjs";
24
25
 
25
26
  export const PREFLIGHT_OK = "ok";
26
27
  export const PREFLIGHT_WARN = "warn";
@@ -186,11 +187,18 @@ export function collectPreflight({
186
187
  checks.push(
187
188
  !entry || entry.isSymbolicLink()
188
189
  ? ok("target-bridge", entry ? "src-tauri/target is a symbolic link" : "src-tauri/target is ready for the shared cache bridge")
189
- : fail(
190
- "target-bridge",
191
- `${target} is a real directory; bridge setup will refuse it`,
192
- `run right-release cache migrate --config ${JSON.stringify(configPath ?? path.join(appRoot, "right-release.config.mjs"))} --dry-run, then repeat with --apply before building`,
193
- ),
190
+ // A real directory here is not corruption on a broker-managed host: the
191
+ // build broker owns CARGO_TARGET_DIR and this is its own output (or a
192
+ // leftover from before it took ownership), not Cache V2's bridge target.
193
+ // Recommending `cache migrate` here is how an agent nearly renamed ~5.9 GB
194
+ // of warm broker intermediates out from under it.
195
+ : brokerManagedHost(env)
196
+ ? ok("target-bridge", `${target} is a real directory; broker-managed host, left untouched`)
197
+ : fail(
198
+ "target-bridge",
199
+ `${target} is a real directory; bridge setup will refuse it`,
200
+ `run right-release cache migrate --config ${JSON.stringify(configPath ?? path.join(appRoot, "right-release.config.mjs"))} --dry-run, then repeat with --apply before building`,
201
+ ),
194
202
  );
195
203
  }
196
204
 
@@ -0,0 +1,68 @@
1
+ import { spawn } from "node:child_process";
2
+
3
+ const MANAGED_REQUEST_PATTERN = /\brightkit(?: managed-agent)?: request ([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\b/gi;
4
+
5
+ export function latestManagedRequestId(value, fallback = null) {
6
+ let latest = fallback;
7
+ for (const match of String(value ?? "").matchAll(MANAGED_REQUEST_PATTERN)) latest = match[1].toLowerCase();
8
+ return latest;
9
+ }
10
+
11
+ export function firstManagedRequestId(value) {
12
+ MANAGED_REQUEST_PATTERN.lastIndex = 0;
13
+ return MANAGED_REQUEST_PATTERN.exec(String(value ?? ""))?.[1]?.toLowerCase() ?? null;
14
+ }
15
+
16
+ export function releaseProgressLimits(env = process.env) {
17
+ const inactivityMs = Number(env.RIGHT_RELEASE_STALL_MS ?? 10 * 60 * 1000);
18
+ const absoluteMs = Number(env.RIGHT_RELEASE_ABSOLUTE_MS ?? 4 * 60 * 60 * 1000);
19
+ if (![inactivityMs, absoluteMs].every((value) => Number.isFinite(value) && value > 0)) throw new Error("release progress limits must be positive finite milliseconds");
20
+ if (absoluteMs <= inactivityMs) throw new Error("RIGHT_RELEASE_ABSOLUTE_MS must exceed RIGHT_RELEASE_STALL_MS");
21
+ return Object.freeze({ inactivityMs, absoluteMs });
22
+ }
23
+
24
+ function runRightkitControl(args, { cwd, env, timeoutMs = 30_000 } = {}) {
25
+ return new Promise((resolve) => {
26
+ const control = spawn("rightkit", args, { cwd, env, stdio: ["ignore", "pipe", "pipe"], windowsHide: true, shell: process.platform === "win32" });
27
+ let output = "";
28
+ let timedOut = false;
29
+ const append = (chunk) => { output = `${output}${chunk}`.slice(-64 * 1024); };
30
+ control.stdout.on("data", append);
31
+ control.stderr.on("data", append);
32
+ const timer = setTimeout(() => {
33
+ timedOut = true;
34
+ control.kill("SIGTERM");
35
+ setTimeout(() => control.kill("SIGKILL"), 1_000).unref();
36
+ }, timeoutMs);
37
+ timer.unref();
38
+ control.once("error", (error) => {
39
+ clearTimeout(timer);
40
+ resolve(Object.freeze({ code: 125, output, timedOut, error: error.message }));
41
+ });
42
+ control.once("exit", (code, signal) => {
43
+ clearTimeout(timer);
44
+ resolve(Object.freeze({ code: code ?? 125, signal, output, timedOut }));
45
+ });
46
+ });
47
+ }
48
+
49
+ export function terminalResultFromOutput(output) {
50
+ for (const line of String(output ?? "").split(/\r?\n/).reverse()) {
51
+ try {
52
+ const candidate = JSON.parse(line);
53
+ if (candidate?.type === "RESULT" && typeof candidate.status === "string") return candidate;
54
+ } catch { /* streamed build output is not JSON */ }
55
+ }
56
+ return null;
57
+ }
58
+
59
+ export async function cancelAndReapManagedRequest(id, { cwd = process.cwd(), env = process.env, run = runRightkitControl } = {}) {
60
+ if (!id) return Object.freeze({ status: "NO_MANAGED_REQUEST" });
61
+ const cancel = await run(["cancel", id], { cwd, env });
62
+ const attach = await run(["attach", id], { cwd, env });
63
+ const terminal = terminalResultFromOutput(attach.output);
64
+ if (!terminal) {
65
+ throw new Error(`broker cancellation for ${id} lacked terminal RESULT (cancel=${cancel.code}, attach=${attach.code}${attach.timedOut ? ", attach-timeout" : ""})`);
66
+ }
67
+ return Object.freeze({ status: terminal.status, id, exitCode: terminal.exitCode, receipt: terminal.receipt ?? null, cancelExitCode: cancel.code, attachExitCode: attach.code });
68
+ }