@rightkit/release 0.6.13 → 0.6.14

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 (109) hide show
  1. package/README.md +49 -1
  2. package/adapter-env.mjs +165 -0
  3. package/android-ndk.mjs +5 -1
  4. package/android-release.mjs +73 -8
  5. package/app-check.mjs +22 -8
  6. package/app-closure.mjs +214 -0
  7. package/assemble-app-command.mjs +24 -7
  8. package/atomic-write.mjs +57 -0
  9. package/bin/darwin/rightkit-swap +0 -0
  10. package/bin/rightkit-gate.mjs +1190 -337
  11. package/broker-client.mjs +106 -0
  12. package/broker-exception.mjs +211 -0
  13. package/broker-handshake.mjs +160 -0
  14. package/broker-pins.mjs +27 -8
  15. package/broker-wire.mjs +262 -0
  16. package/build-launch.mjs +364 -12
  17. package/build-ownership.mjs +117 -49
  18. package/build-release.mjs +3 -2
  19. package/cache-command.mjs +7 -2
  20. package/candidate-build-receipt.mjs +96 -0
  21. package/candidate-receipt-command.mjs +29 -0
  22. package/cargo-contract.mjs +4 -3
  23. package/cargo-ledger.mjs +1 -1
  24. package/cargo-license-notices.mjs +2 -2
  25. package/cargo-shapes.mjs +332 -0
  26. package/cargo-target-shim.mjs +44 -5
  27. package/cargo-target.mjs +5 -0
  28. package/carried-adapter-run.mjs +31 -0
  29. package/carried-cargo-run.mjs +9 -0
  30. package/carried-cargo.mjs +237 -0
  31. package/carrier-census.mjs +124 -0
  32. package/class-a-launch-guard.mjs +266 -33
  33. package/cli/right-release.mjs +2 -0
  34. package/clippy-route.mjs +23 -11
  35. package/compile-inputs.mjs +9 -12
  36. package/control-plane.mjs +18 -0
  37. package/deps-cargo-lock.mjs +208 -0
  38. package/deps.mjs +5 -0
  39. package/deterministic-scans.mjs +273 -44
  40. package/development-signing.mjs +14 -1
  41. package/development.mjs +109 -24
  42. package/direct-origin.mjs +107 -0
  43. package/disk-command.mjs +1 -0
  44. package/disk-policy.mjs +18 -3
  45. package/freshness-census.mjs +206 -58
  46. package/gate-executor.mjs +30 -12
  47. package/gate-stages.mjs +93 -16
  48. package/github-release.mjs +2 -1
  49. package/github-upload-gate.mjs +2 -1
  50. package/install-tauri-authenticode.mjs +346 -0
  51. package/ios-dev.mjs +77 -23
  52. package/ios-package-recipe.mjs +100 -41
  53. package/linux-artifacts.mjs +17 -2
  54. package/linux-qualifier.mjs +83 -0
  55. package/local-publish.mjs +36 -14
  56. package/local-runtime.mjs +2 -2
  57. package/lock-identity.mjs +138 -0
  58. package/lsclean.sh +0 -0
  59. package/managed-cargo.mjs +151 -44
  60. package/managed-roots.mjs +130 -12
  61. package/media-sidecar-catalog.mjs +1 -1
  62. package/native-adapters.mjs +411 -0
  63. package/native-helpers.mjs +26 -18
  64. package/native-tool-planning.mjs +94 -0
  65. package/ownership-mark.mjs +182 -0
  66. package/pack-digest.mjs +122 -0
  67. package/package.json +12 -12
  68. package/platform-dir.mjs +14 -0
  69. package/pnpm-adapter-grammar.mjs +226 -0
  70. package/progress-control.mjs +7 -2
  71. package/provenance-real-receipt.pins.json +7 -0
  72. package/provenance-verify.mjs +262 -1
  73. package/publication-outcomes.mjs +21 -0
  74. package/publish-update.mjs +2 -1
  75. package/qa-journeys-prebuilt-run.mjs +18 -0
  76. package/qa-journeys-prebuilt.mjs +148 -0
  77. package/qa-snapshot-run.mjs +53 -0
  78. package/qa-snapshot.mjs +164 -0
  79. package/qualification-verify.mjs +144 -0
  80. package/qualify-reuse.mjs +38 -0
  81. package/recipe-build.mjs +16 -3
  82. package/release-qa-audit.mjs +2 -2
  83. package/release-qa-command.mjs +2 -1
  84. package/release-qa-receipt.mjs +3 -2
  85. package/release-state.mjs +3 -2
  86. package/release.mjs +14 -3
  87. package/rightkit-drift.mjs +27 -5
  88. package/rightkit-versions.json +14 -10
  89. package/rustright-names.mjs +32 -0
  90. package/scan-tool-enrolment.mjs +167 -0
  91. package/sign-windows.mjs +212 -112
  92. package/signing-files.mjs +35 -0
  93. package/signing-identity.mjs +7 -0
  94. package/snapshot-broker.mjs +528 -57
  95. package/snapshot-check.mjs +95 -11
  96. package/snapshot-metadata.mjs +123 -0
  97. package/snapshot-request.mjs +389 -96
  98. package/stage-binary.mjs +22 -4
  99. package/stage-commands.mjs +56 -13
  100. package/stage-sidecar.mjs +93 -21
  101. package/suite-signing-policy.mjs +657 -0
  102. package/suite-signing.mjs +223 -0
  103. package/swift-scratch.mjs +25 -0
  104. package/tauri-locked.mjs +25 -0
  105. package/test-command.mjs +164 -29
  106. package/unit-gate.mjs +201 -31
  107. package/upload-large.mjs +0 -0
  108. package/web-build.mjs +2 -2
  109. package/win-package-recipe.mjs +26 -2
package/stage-binary.mjs CHANGED
@@ -32,8 +32,26 @@ export function stageDevelopmentBinary({ root, source, name = path.basename(sour
32
32
  const profileDirectory = (profile) => (profile === "dev" || profile === "test" ? "debug" : profile === "bench" ? "release" : profile);
33
33
 
34
34
  /** `{ source }` or `{ manifestPath, bin, targetTriple?, profile? }` (resolved through Cargo's own target directory, broker or not). */
35
- export function resolveStageSource(entry, { root, resolveTarget = resolveTargetRoot, platform = process.platform } = {}) {
36
- if (entry.source) return path.resolve(root, entry.source);
35
+ export function resolveStageSource(entry, { root, resolveTarget = resolveTargetRoot, platform = process.platform, managedOutputDirs = [] } = {}) {
36
+ if (entry.source) {
37
+ const from = path.resolve(root, entry.source);
38
+ // GB-60: on a receipt-managed build (managedOutputDirs: the output directories signed receipts of this run named) a source path under a `target`
39
+ // directory is the Cargo target directory's, which the broker keeps elsewhere: it is migrated onto the receipt's directory when exactly one is known,
40
+ // and refused when the receipts name several (the entry must then say which: manifestPath and bin). A source path outside any target directory is a
41
+ // source-tree file and is used as written.
42
+ const unique = [...new Set(managedOutputDirs)];
43
+ const segments = path.relative(path.resolve(root), from).split(path.sep).filter(Boolean);
44
+ const at = segments.indexOf("target");
45
+ if (unique.length && at >= 0 && !segments.includes("..")) {
46
+ if (unique.length > 1) {
47
+ const error = new Error(`development stage source ${JSON.stringify(entry.source)} is under a Cargo target directory, but this build's receipts name ${unique.length} output directories; name the workspace with manifestPath and bin instead`);
48
+ error.code = "DEV_OUTPUT_AMBIGUOUS";
49
+ throw error;
50
+ }
51
+ return path.join(unique[0], ...segments.slice(at + 1));
52
+ }
53
+ return from;
54
+ }
37
55
  if (!entry.manifestPath || !entry.bin) throw new Error("development stage entry needs source, or manifestPath and bin");
38
56
  const target = resolveTarget(path.resolve(root, entry.manifestPath));
39
57
  const file = platform === "win32" ? `${entry.bin}.exe` : entry.bin;
@@ -51,9 +69,9 @@ export function validateStageEntries(entries, label) {
51
69
  return entries;
52
70
  }
53
71
 
54
- export function stageDevelopmentBinaries(entries, { root, platform = process.platform, resolveTarget } = {}) {
72
+ export function stageDevelopmentBinaries(entries, { root, platform = process.platform, resolveTarget, managedOutputDirs = [] } = {}) {
55
73
  return entries.map((entry) => {
56
- const source = resolveStageSource(entry, { root, platform, resolveTarget });
74
+ const source = resolveStageSource(entry, { root, platform, resolveTarget, managedOutputDirs });
57
75
  const name = entry.name ?? path.basename(source);
58
76
  return stageDevelopmentBinary({ root, source, name, triple: entry.targetTriple ?? null });
59
77
  });
@@ -2,14 +2,26 @@
2
2
  // localTestPlan and the pack argv builders), so the gate (which chooses the receipts to carry) and the hosted admission (which checks
3
3
  // the signed requestedArgv of each) share one definition and cannot drift from the stages. EVERY command of a stage must be covered by
4
4
  // a signed receipt of its own job; a missing command is a refusal that names it.
5
+ import fs from "node:fs";
5
6
  import path from "node:path";
6
7
  import { CARGO_METADATA_ARGS, cargoPackageArgs, localTestPlan, npmPackArgs, qualifyFmtArgs } from "./local-publish.mjs";
7
8
  import { SWAP_HELPER_CARGO_ARGS, SWAP_HELPER_METADATA_ARGS, SWAP_HELPER_PACKAGE } from "./swap-helper-bundle.mjs";
9
+ import { carriedRequestOf } from "./snapshot-request.mjs";
8
10
 
9
11
  /** Stands for the one argument that is a fresh absolute scratch path (`pnpm pack --pack-destination <mkdtemp>`). */
10
12
  export const ANY_ABSOLUTE_PATH = Object.freeze({ anyAbsolutePath: true });
11
13
  const PNPM = (...argvs) => ({ kind: "adapter", adapter: "rightkit-pnpm", argvs });
12
- const CARGO = (...argvs) => ({ kind: "cargo", argvs });
14
+ /**
15
+ * A cargo command as a broker host receives it. Every Cargo call of a snapshot stage is carried (carried-cargo.mjs): `--manifest-path` is
16
+ * not sent (the call runs from the manifest's directory) and a build or run is sent with `--locked`, so the signed `requestedArgv` is
17
+ * that argv and the signed `workspace` is that directory of the checkout, never the spelling the stage's code wrote. `cwd` is the
18
+ * directory the stage runs the command from, relative to the repository root; the row keeps the directory the call runs in.
19
+ */
20
+ const CARGO = (cwd, ...argvs) => {
21
+ const carried = argvs.map((argv) => carriedRequestOf(argv, cwd) ?? { argv, cwd });
22
+ if (new Set(carried.map((c) => c.cwd)).size !== 1) throw new Error(`the spellings of one cargo command run in different directories: ${argvs.map((a) => a.join(" ")).join(" | ")}`);
23
+ return { kind: "cargo", argvs: carried.map((c) => c.argv), cwd: carried[0].cwd };
24
+ };
13
25
 
14
26
  /** Readable name of an expected command (scratch paths shown as <scratch>). */
15
27
  export const describeCommand = (row) => `${row.kind === "adapter" ? "pnpm" : "cargo"} ${row.argvs[0].map((a) => (typeof a === "string" ? a : "<scratch>")).join(" ")}`;
@@ -22,16 +34,18 @@ export const describeCommand = (row) => `${row.kind === "adapter" ? "pnpm" : "ca
22
34
  export function expectedStageCommands({ kind, unit, config, manifest, platform }) {
23
35
  const dir = kind === "npm" ? `packages/${unit.slice("@rightkit/".length)}` : `crates/${unit}`;
24
36
  const plan = localTestPlan({ kind, unit, dir, config, manifest, batch: false, platform });
25
- const compile = plan.steps.map((step) => (step.command === "pnpm" ? PNPM(step.args) : CARGO(step.args)));
37
+ // The stage's own working directories (local-publish.mjs): a crate's cargo steps run in its workspace; a consumer row's (named by
38
+ // --manifest-path from the repository root) and the swap helper's run from the repository root.
39
+ const crateDir = `crates/${unit}`;
40
+ const compile = plan.steps.map((step) => (step.command === "pnpm" ? PNPM(step.args) : CARGO(step.args.includes("--manifest-path") ? "." : crateDir, step.args)));
26
41
  const pack = [];
27
42
  if (kind === "npm") {
28
- if (unit === SWAP_HELPER_PACKAGE) {
29
- // The swap helper builds and signs a binary, then packs from a scratch copy of its directory.
30
- pack.push(CARGO(SWAP_HELPER_CARGO_ARGS), CARGO(SWAP_HELPER_METADATA_ARGS), PNPM(npmPackArgs(ANY_ABSOLUTE_PATH, ANY_ABSOLUTE_PATH)));
31
- } else pack.push(PNPM(npmPackArgs(dir, ANY_ABSOLUTE_PATH)));
43
+ // The swap helper builds and signs a binary into its own directory of the snapshot (a declared stage output), then packs from there like any unit.
44
+ if (unit === SWAP_HELPER_PACKAGE) pack.push(CARGO(".", SWAP_HELPER_CARGO_ARGS), CARGO(".", SWAP_HELPER_METADATA_ARGS));
45
+ pack.push(PNPM(npmPackArgs(dir, ANY_ABSOLUTE_PATH)));
32
46
  } else {
33
- compile.unshift(CARGO(qualifyFmtArgs(unit)));
34
- pack.push(CARGO(cargoPackageArgs(unit, true)), CARGO([...CARGO_METADATA_ARGS], CARGO_METADATA_ARGS.filter((arg) => arg !== "--offline")));
47
+ compile.unshift(CARGO(crateDir, qualifyFmtArgs(unit)));
48
+ pack.push(CARGO(crateDir, cargoPackageArgs(unit, true)), CARGO(crateDir, [...CARGO_METADATA_ARGS], CARGO_METADATA_ARGS.filter((arg) => arg !== "--offline")));
35
49
  }
36
50
  return { compile, package: pack };
37
51
  }
@@ -39,21 +53,50 @@ export function expectedStageCommands({ kind, unit, config, manifest, platform }
39
53
  const argvMatches = (expected, actual) => Array.isArray(actual) && expected.length === actual.length
40
54
  && expected.every((want, i) => (want === ANY_ABSOLUTE_PATH ? typeof actual[i] === "string" && (path.posix.isAbsolute(actual[i]) || /^[A-Za-z]:[\\/]/.test(actual[i])) : want === actual[i]));
41
55
 
42
- /** Whether a signed payload's request is this command: request.kind, adapter (when any) and requestedArgv. */
43
- export function requestIs(payload, row) {
56
+ const posixOf = (p) => String(p ?? "").replaceAll("\\", "/").replace(/\/+$/, "");
57
+ const realOf = (p) => { try { return fs.realpathSync.native(p); } catch { return path.resolve(p); } };
58
+
59
+ /**
60
+ * The repository root a job ran under, given the directory its row runs in: the signed `workspace` of a carried cargo call is that
61
+ * directory of the checkout (`crates/<x>`), so the root is the workspace with the row's `cwd` removed. Null when the workspace is not that
62
+ * directory. A row with no `cwd` (a pnpm job) ran at the root itself.
63
+ */
64
+ export function rootOfWorkspace(workspace, cwd) {
65
+ const where = posixOf(workspace);
66
+ if (cwd === undefined || cwd === ".") return where;
67
+ return where.endsWith(`/${cwd}`) ? where.slice(0, -(cwd.length + 1)) : null;
68
+ }
69
+
70
+ /** Whether the signed workspace is the directory the row runs in. With `checkout` (the gate's own primary checkout) it must be exactly that directory of it. */
71
+ function workspaceIs(workspace, row, checkout) {
72
+ if (row.cwd === undefined) return true;
73
+ if (typeof workspace !== "string" || !workspace) return false;
74
+ if (checkout) {
75
+ const want = path.join(checkout, ...(row.cwd === "." ? [] : row.cwd.split("/")));
76
+ return posixOf(workspace) === posixOf(want) || posixOf(realOf(workspace)) === posixOf(realOf(want));
77
+ }
78
+ return rootOfWorkspace(workspace, row.cwd) !== null;
79
+ }
80
+
81
+ /**
82
+ * Whether a signed payload's request is this command: request.kind, adapter (when any), requestedArgv, and for a cargo command the
83
+ * directory it ran in (`row.cwd`: a different crate's call cannot satisfy it). `checkout` pins that directory exactly (the gate knows its
84
+ * checkout; the hosted admission does not and checks the suffix).
85
+ */
86
+ export function requestIs(payload, row, { checkout = null } = {}) {
44
87
  const request = payload?.request;
45
- return Boolean(request && typeof request === "object" && request.kind === row.kind && (row.adapter === undefined || request.adapter === row.adapter) && row.argvs.some((argv) => argvMatches(argv, request.requestedArgv)));
88
+ return Boolean(request && typeof request === "object" && request.kind === row.kind && (row.adapter === undefined || request.adapter === row.adapter) && row.argvs.some((argv) => argvMatches(argv, request.requestedArgv)) && workspaceIs(request.workspace, row, checkout));
46
89
  }
47
90
 
48
91
  /**
49
92
  * Assigns each job (`{ id, payload }`) to a distinct command of the stage. Returns { assigned: Map(id -> row), unmatched: [id...],
50
93
  * missing: [row...] }: a job that is none of the commands is unmatched; a command no job covers is missing.
51
94
  */
52
- export function assignJobs(rows, jobs) {
95
+ export function assignJobs(rows, jobs, options = {}) {
53
96
  const free = rows.map((row) => ({ row, taken: false }));
54
97
  const assigned = new Map(), unmatched = [];
55
98
  for (const job of jobs) {
56
- const slot = free.find((s) => !s.taken && requestIs(job.payload, s.row));
99
+ const slot = free.find((s) => !s.taken && requestIs(job.payload, s.row, options));
57
100
  if (slot) { slot.taken = true; assigned.set(job.id, slot.row); } else unmatched.push(job.id);
58
101
  }
59
102
  return { assigned, unmatched, missing: free.filter((s) => !s.taken).map((s) => s.row) };
package/stage-sidecar.mjs CHANGED
@@ -38,7 +38,9 @@ import os from "node:os";
38
38
  import path from "node:path";
39
39
  import { spawnSync as nodeSpawnSync } from "./background-process.mjs";
40
40
  import { resolveCommand } from "./development.mjs";
41
- import { directPlatform, managedHost } from "./managed-cargo.mjs";
41
+ import { carriedCargoMapOf } from "./carried-cargo.mjs";
42
+ import { BUILD_LAUNCH_REFUSED, buildLaunch } from "./build-launch.mjs";
43
+ import { directHost, managedHost, runCargoSync } from "./managed-cargo.mjs";
42
44
  import { APPROVAL_MACHO_STRIP_LINKEDIT_V1, APPROVAL_RAW_SHA256, approvalDigest } from "./macho-approval-hash.mjs";
43
45
  import {
44
46
  bundledFileName, planMediaSidecar, sourceOfferText, stagedFileName, validateMediaSidecarConfig, verifyStagedSidecars, writeMediaSidecarNotices,
@@ -583,6 +585,9 @@ const tail = (text, lines = 12) => String(text ?? "").trim().split("\n").slice(-
583
585
  /** Default process runner: `rustright` resolves through the installed runtime client (its Windows shim is rustright.cmd). */
584
586
  export function defaultRun(command, args, { cwd, env = process.env } = {}) {
585
587
  if (command === "rightkit") throw new Error("`rightkit` is the retired name of the RustRight client (rustright#91); use rustright (stage: { cmd: \"rustright\" })");
588
+ // Every `rustright cargo` of this module is a right-release Cargo call: the one carried seam (managed-cargo.mjs runCargoSync) takes it,
589
+ // so a snapshot stage's call is carried and verified, and any other host runs it as before.
590
+ if (command === "rustright" && args[0] === "cargo") return runCargoSync(args.slice(1), { cwd, env, encoding: "utf8", maxBuffer: 512 * 1024 * 1024, shell: false, windowsHide: true, stdio: ["inherit", "pipe", "inherit"] });
586
591
  const launch = command === "rustright" ? resolveCommand(command, args, { root: cwd, env }) : { command, args };
587
592
  return nodeSpawnSync(launch.command, launch.args, {
588
593
  cwd, env: { ...env, ...(launch.env ?? {}) }, encoding: "utf8", maxBuffer: 512 * 1024 * 1024, shell: false, windowsHide: true, stdio: ["inherit", "pipe", "inherit"],
@@ -668,7 +673,7 @@ function resolveCatalogFile(spec, ctx, kind) {
668
673
  if (spec.via === "absolute") return requireSnapshotSource(ctx, spec.file, `the ${label} catalog`);
669
674
  if (spec.via === "path") return requireSnapshotSource(ctx, path.resolve(ctx.appRoot, spec.file), `the ${label} catalog`);
670
675
  if (spec.via === "crate") {
671
- const meta = ctx.run("rustright", ["cargo", "metadata", "--format-version", "1"], { cwd: ctx.appRoot, env: ctx.env });
676
+ const meta = ctx.run("rustright", ["cargo", "metadata", "--frozen", "--format-version", "1"], { cwd: ctx.appRoot, env: ctx.env });
672
677
  let packages;
673
678
  try { packages = JSON.parse(meta.stdout).packages; } catch { /* refused below */ }
674
679
  if (meta?.status !== 0 || !Array.isArray(packages)) fail("STAGE_SIDECAR_CATALOG_UNREADABLE", `the ${label} catalog crate ${spec.crate} cannot be located: the workspace metadata query failed`, { crate: spec.crate });
@@ -760,12 +765,16 @@ function cleanDest(ctx, entry, dest, { directory = false } = {}) {
760
765
  function carryMtime(ctx, dest, sources) {
761
766
  const { fs } = ctx;
762
767
  if (typeof fs.utimesSync !== "function") return;
768
+ // Millisecond precision (a Date), FLOORED: Node's `stat().mtime` rounds the file's sub-millisecond part to the nearest millisecond, so a source whose mtime is x.6 ms would be carried
769
+ // as x+1 ms, newer than the source it was copied from; floored, the carried mtime is never newer than the source and less than a millisecond older. `mtimeMs` carries the
770
+ // fraction; a file system double that gives only a `mtime` Date is carried as that Date.
763
771
  let newest = null;
764
772
  for (const source of sources) {
765
773
  let st; try { st = fs.statSync(source); } catch { continue; }
766
- if (st?.mtime instanceof Date && (!newest || st.mtime > newest)) newest = st.mtime;
774
+ const ms = Number.isFinite(st?.mtimeMs) ? Math.floor(st.mtimeMs) : st?.mtime instanceof Date ? st.mtime.getTime() : null;
775
+ if (ms !== null && (newest === null || ms > newest)) newest = ms;
767
776
  }
768
- if (newest) { try { fs.utimesSync(dest, newest, newest); } catch { /* the mtime is a cache hint, never a binding */ } }
777
+ if (newest !== null) { const carried = new Date(newest); try { fs.utimesSync(dest, carried, carried); } catch { /* the mtime is a cache hint, never a binding */ } }
769
778
  }
770
779
 
771
780
  function copyInto(ctx, source, dest, { executable }) {
@@ -799,6 +808,38 @@ function requireSnapshotSource(ctx, file, label) {
799
808
  if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) fail("STAGE_SIDECAR_SOURCE_OUTSIDE_SNAPSHOT", `${label} resolves to ${file}, outside the snapshot's app root ${ctx.appRoot}; a split run reads sources only from the snapshot (checkout bytes enter only as digest-pinned cache entries)`, { file });
800
809
  return file;
801
810
  }
811
+ /**
812
+ * The Cargo output directory of the workspace a split run builds in, as the checkout's absolute path, or `{ error }`. Resolved from the SNAPSHOT: `cargo
813
+ * metadata --offline --no-deps` runs in the snapshot's app root (with `--manifest-path` the snapshot's manifest when the entry names one), so the
814
+ * committed manifests and Cargo configuration decide, and a path it reports under the snapshot is mapped lexically to the same relative path under the
815
+ * checkout (the output directory is the checkout's, GATE-CONTRACT "Output directory & cache"; cargoOutputPin does the same for the stage's own calls). The
816
+ * checkout is never asked: a newer HEAD that deleted the crate's directory, a dirty manifest or another build.target-dir there changes nothing (audit GB-16).
817
+ * A path outside the snapshot (an absolute CARGO_TARGET_DIR, a target-dir set in the user's Cargo home) stands as reported.
818
+ */
819
+ function snapshotTargetDirectory(ctx, env, manifest) {
820
+ const resolved = ctx.run("rustright", ["cargo", "metadata", "--offline", "--format-version", "1", "--no-deps", ...(manifest ? ["--manifest-path", manifest] : [])], { cwd: ctx.appRoot, env });
821
+ let dir;
822
+ try { dir = JSON.parse(resolved?.stdout).target_directory; } catch { /* reported below */ }
823
+ if (resolved?.error || resolved?.status !== 0 || typeof dir !== "string" || !path.isAbsolute(dir)) return { error: resolved?.error?.message ?? `exit ${resolved?.status}` };
824
+ const roots = splitRoots(ctx);
825
+ for (const snapshot of roots.snapshots) {
826
+ const rel = path.relative(snapshot, dir);
827
+ if (rel === "" || (!rel.startsWith("..") && !path.isAbsolute(rel))) return { dir: path.join(roots.checkout, rel) };
828
+ }
829
+ return { dir: path.resolve(dir) };
830
+ }
831
+
832
+ /** The snapshot's and the checkout's roots of a split run: `appRoot` and `cacheRoot` are the same app directory in each, so what their paths do not share is the root. */
833
+ function splitRoots(ctx) {
834
+ const a = path.resolve(ctx.appRoot).split(path.sep); const c = path.resolve(ctx.cacheRoot).split(path.sep);
835
+ let n = 0;
836
+ while (n < a.length - 1 && n < c.length - 1 && a[a.length - 1 - n] === c[c.length - 1 - n]) n += 1;
837
+ const snapshot = a.slice(0, a.length - n).join(path.sep) || path.sep;
838
+ const snapshots = [snapshot];
839
+ try { snapshots.push(nodeFs.realpathSync.native(snapshot)); } catch { /* the path as given */ }
840
+ return { snapshots, checkout: c.slice(0, c.length - n).join(path.sep) || path.sep };
841
+ }
842
+
802
843
  /**
803
844
  * The environment a split run hands to the commands it starts (a `command` entry, an ffmpeg `stage` command): Cargo's output directory is
804
845
  * the checkout's, resolved there once and passed absolute, so a command that reaches Cargo (`rustright cargo run -p ...`) never writes
@@ -806,15 +847,40 @@ function requireSnapshotSource(ctx, file, label) {
806
847
  */
807
848
  function splitCommandEnv(ctx) {
808
849
  if (!ctx.split) return ctx.env;
850
+ // A carried stage (carried-cargo.mjs): the broker pins every Cargo output, so nothing is pinned here and the commands inherit the carrier.
851
+ if (carriedCargoMapOf(ctx.env)?.map) return ctx.env;
809
852
  if (ctx.splitEnv === undefined) {
810
- const resolved = ctx.run("rustright", ["cargo", "metadata", "--format-version", "1", "--no-deps"], { cwd: ctx.cacheRoot, env: ctx.env });
811
- let dir;
812
- try { dir = JSON.parse(resolved?.stdout).target_directory; } catch { /* no workspace in the checkout: nothing to pin */ }
813
- ctx.splitEnv = !resolved?.error && resolved?.status === 0 && typeof dir === "string" && path.isAbsolute(dir) ? { ...ctx.env, CARGO_TARGET_DIR: path.resolve(dir) } : ctx.env;
853
+ const pinned = snapshotTargetDirectory(ctx, ctx.env, null); // no Cargo workspace in the snapshot: nothing to pin
854
+ ctx.splitEnv = pinned.dir ? { ...ctx.env, CARGO_TARGET_DIR: pinned.dir } : ctx.env;
814
855
  }
815
856
  return ctx.splitEnv;
816
857
  }
817
858
 
859
+ /**
860
+ * One step command of a config (`stage.cmd`, a `command` entry's `cmd`, the cmake tool). In a snapshot stage with a carrier (the gate's
861
+ * carrier map in the environment) every compile is carried or nested under a carried adapter job, so such a step goes through the door
862
+ * (build-launch.mjs): `pnpm`, `node` and `swift` become carried adapter jobs, `rightkit-qa` runs direct (it compiles nothing), `rustright cargo` keeps the carried seam
863
+ * (defaultRun), this package's own `right-release` runs as it is, and anything else (`cmake`, a bare tool, `rustright <not cargo>`) is
864
+ * refused with STAGE_SIDECAR_STEP_REFUSED, naming the step: it would otherwise run uncarried around the broker. Without a carrier the
865
+ * step runs exactly as before. Utility commands that compile nothing (`tar`, `codesign`) never come through here.
866
+ */
867
+ export function runStep(ctx, command, args, options = {}) {
868
+ const env = options.env ?? ctx.env;
869
+ const carried = carriedCargoMapOf(env);
870
+ if (!carried || (command === "rustright" && String(args[0]) === "cargo")) return ctx.run(command, args, options);
871
+ const describe = `${command} ${args.join(" ")}`.trim();
872
+ if (carried.error) fail("STAGE_SIDECAR_STEP_REFUSED", `step \`${describe}\`: ${carried.error}; a snapshot stage's step is never run around its carrier`, { command });
873
+ const own = command === "right-release" || (command === "pnpm" && ((args[0] === "exec" && args[1] === "right-release") || args[0] === "right-release"));
874
+ let launch;
875
+ try {
876
+ launch = own ? resolveCommand(command, args, { root: options.cwd, env }) : buildLaunch(command, args, { env, cwd: options.cwd, exists: ctx.fs?.existsSync });
877
+ } catch (error) {
878
+ if (error?.code === BUILD_LAUNCH_REFUSED) fail("STAGE_SIDECAR_STEP_REFUSED", `step \`${describe}\` of a snapshot stage is refused: ${error.message}`, { command });
879
+ throw error;
880
+ }
881
+ return ctx.run(launch.command, launch.args, { ...options, env: launch.env ? { ...env, ...launch.env } : env });
882
+ }
883
+
818
884
  /** A cache directory the config names (`cacheDir`): the checkout's `.right-release` under a split, the app root otherwise. */
819
885
  const cacheDirFor = (ctx, dir) => (ctx.split ? path.resolve(ctx.cacheRoot, ".right-release", dir) : path.resolve(ctx.appRoot, dir));
820
886
  /** The per-arch slice directory of a universal staging: absolute under the checkout's cache root on a split, app-relative otherwise. */
@@ -907,18 +973,24 @@ function stageCargoBuild(entry, ctx, triple) {
907
973
  // resolved in the checkout instead and passed as an absolute CARGO_TARGET_DIR, so the warm outputs stay the checkout's and the
908
974
  // snapshot holds no target/ (docs/GATE-CONTRACT.md "Output directory & cache").
909
975
  let buildEnv = env;
910
- if (ctx.split) {
911
- const checkoutManifest = entry.manifestPath ? ["--manifest-path", path.resolve(ctx.cacheRoot, entry.manifestPath)] : [];
912
- const resolved = run("rustright", ["cargo", "metadata", "--format-version", "1", "--no-deps", ...checkoutManifest], { cwd: ctx.cacheRoot, env });
913
- let pinned;
914
- try { pinned = JSON.parse(resolved?.stdout).target_directory; } catch { /* refused below */ }
915
- if (resolved?.error || resolved?.status !== 0 || typeof pinned !== "string" || !path.isAbsolute(pinned)) fail("STAGE_SIDECAR_BUILD_FAILED", `the checkout's cargo output directory for ${entry.package} cannot be resolved (${resolved?.error?.message ?? `exit ${resolved?.status}`}); nothing was built`, { package: entry.package });
916
- buildEnv = { ...env, CARGO_TARGET_DIR: path.resolve(pinned) };
976
+ // A carried stage (carried-cargo.mjs, a broker host): the broker pins the output directory and the call passes no CARGO_TARGET_DIR;
977
+ // the directory the binary is read from is the signed receipt's `outputDir`, never a carrier-less `cargo metadata`.
978
+ const carried = Boolean(carriedCargoMapOf(env)?.map);
979
+ if (ctx.split && !carried) {
980
+ const pinned = snapshotTargetDirectory(ctx, env, entry.manifestPath ? path.resolve(appRoot, entry.manifestPath) : null);
981
+ if (!pinned.dir) fail("STAGE_SIDECAR_BUILD_FAILED", `the cargo output directory for ${entry.package} cannot be resolved from the snapshot (${pinned.error}); nothing was built`, { package: entry.package });
982
+ buildEnv = { ...env, CARGO_TARGET_DIR: pinned.dir };
917
983
  }
918
984
  const built = run("rustright", args, { cwd: appRoot, env: buildEnv });
919
985
  if (built?.error || built?.status !== 0) fail("STAGE_SIDECAR_BUILD_FAILED", `rustright cargo build of ${entry.package} failed (${built?.error?.message ?? `exit ${built?.status}`})`, { package: entry.package, stderr: tail(built?.stderr) });
920
986
  let source = artifactFromStream(built.stdout, entry.bin);
921
- if (!source || !fs.existsSync(source)) {
987
+ if (carried && (!source || !fs.existsSync(source))) {
988
+ const targetDir = typeof built.outputDir === "string" && path.isAbsolute(built.outputDir) ? built.outputDir : undefined;
989
+ if (!targetDir) fail("STAGE_SIDECAR_BINARY_NOT_FOUND", `cargo reported no ${entry.bin} executable and the receipt names no output directory`, { bin: entry.bin });
990
+ const candidates = builtBinaryCandidates({ targetDir, targetDirs: [], triple, profile: entry.profile, bin: entry.bin, windows: ctx.windows });
991
+ source = candidates.find((file) => fs.existsSync(file));
992
+ if (!source) fail("STAGE_SIDECAR_BINARY_NOT_FOUND", `the build did not produce ${entry.bin} (looked in ${candidates.join(", ")})`, { bin: entry.bin, candidates });
993
+ } else if (!source || !fs.existsSync(source)) {
922
994
  const meta = run("rustright", ["cargo", "metadata", "--format-version", "1", "--no-deps", ...(entry.manifestPath ? ["--manifest-path", path.resolve(appRoot, entry.manifestPath)] : [])], { cwd: appRoot, env: buildEnv });
923
995
  let targetDir;
924
996
  try { targetDir = JSON.parse(meta.stdout).target_directory; } catch { /* refused below */ }
@@ -1029,7 +1101,7 @@ async function stageFfmpegSlice(entry, ctx, resolved, { config, t, writeNotices,
1029
1101
  // A universal entry stages once per slice: `{arch}`, `{triple}` and `{stageDir}` in the stage command's arguments name the slice.
1030
1102
  const args = (config.stage.args ?? []).map((arg) => (universal ? String(arg).replaceAll("{arch}", t.arch).replaceAll("{triple}", t.triple).replaceAll("{stageDir}", sliceRoot === appRoot ? config.stageDir : path.resolve(sliceRoot, config.stageDir)) : arg));
1031
1103
  ctx.log(`stage-sidecar: ${config.stage.cmd} ${args.join(" ")}`);
1032
- const staged = run(config.stage.cmd, args, { cwd: appRoot, env: splitCommandEnv(ctx) });
1104
+ const staged = runStep(ctx, config.stage.cmd, args, { cwd: appRoot, env: splitCommandEnv(ctx) });
1033
1105
  if (staged?.error || staged?.status !== 0) fail("STAGE_SIDECAR_BUILD_FAILED", `ffmpeg stage command failed (${staged?.error?.message ?? `exit ${staged?.status}`})`);
1034
1106
  }
1035
1107
  const noticeFiles = writeNotices ? writeMediaSidecarNotices(plan, { exists: fs.existsSync, mkdir: fs.mkdirSync, copy: fs.copyFileSync, write: fs.writeFileSync }) : [];
@@ -1519,7 +1591,7 @@ function stageCommand(entry, ctx) {
1519
1591
  const cwd = entry.cwd ? path.resolve(appRoot, entry.cwd) : appRoot;
1520
1592
  cleanDest(ctx, entry, destFor(entry, ctx));
1521
1593
  ctx.log(`stage-sidecar: ${entry.cmd} ${entry.args.join(" ")}`);
1522
- const built = run(entry.cmd, entry.args, { cwd, env: splitCommandEnv(ctx) });
1594
+ const built = runStep(ctx, entry.cmd, entry.args, { cwd, env: splitCommandEnv(ctx) });
1523
1595
  if (built?.error || built?.status !== 0) fail("STAGE_SIDECAR_BUILD_FAILED", `command ${entry.name} (${entry.cmd}) failed (${built?.error?.message ?? `exit ${built?.status}`})`, { name: entry.name, stderr: tail(built?.stderr) });
1524
1596
  const source = path.resolve(appRoot, entry.output);
1525
1597
  if (!fs.existsSync(source) || !fs.statSync(source).isFile()) fail("STAGE_SIDECAR_BINARY_NOT_FOUND", `command ${entry.name} did not produce ${entry.output}`, { name: entry.name, output: entry.output });
@@ -1681,7 +1753,7 @@ function stageCmake(entry, ctx) {
1681
1753
  const configure = ["-S", srcDir, "-B", buildDir, "-DCMAKE_BUILD_TYPE=Release", ...(generator ? ["-G", generator] : []), ...(entry.architecture ? ["-A", entry.architecture] : []), ...(archFlag ? [archFlag] : []), ...(entry.platformArgs[target.platform] ?? []), ...entry.cmakeArgs];
1682
1754
  const step = (args, label) => {
1683
1755
  ctx.log(`stage-sidecar: ${cmake} ${args.join(" ")}`);
1684
- const result = run(cmake, args, { cwd: appRoot, env });
1756
+ const result = runStep(ctx, cmake, args, { cwd: appRoot, env });
1685
1757
  if (result?.error || result?.status !== 0) fail("STAGE_SIDECAR_BUILD_FAILED", `cmake ${label} of ${entry.name} failed (${result?.error?.message ?? `exit ${result?.status}`})`, { name: entry.name, stderr: tail(result?.stderr) });
1686
1758
  };
1687
1759
  step(configure, "configure");
@@ -1808,8 +1880,8 @@ function wipeStaleStageDir(ctx, plan, produced) {
1808
1880
  */
1809
1881
  export function isNestedBrokerContext(env = process.env, platform = process.platform) {
1810
1882
  // A direct (Linux) host has no broker, so no outer broker build to nest in, whatever flag or socket the env carries (heavy-command sets
1811
- // RIGHTSUITE_HEAVY_WORK_OWNER on every platform).
1812
- if (directPlatform(platform)) return false;
1883
+ // RIGHTSUITE_HEAVY_WORK_OWNER on every platform). A Linux host that declared the broker route (managed-cargo linuxBrokerDeclared) is a broker host.
1884
+ if (directHost(platform, env ?? {})) return false;
1813
1885
  if (env?.RIGHTKIT_STAGE_SIDECAR_ALLOW_NESTED === "1") return false;
1814
1886
  const insideTauri = Boolean(env?.TAURI_ENV_PLATFORM || env?.TAURI_ENV_TARGET_TRIPLE);
1815
1887
  return insideTauri && (managedHost(env ?? {}, platform) || env?.RIGHTSUITE_HEAVY_WORK_OWNER === "1");