@rightkit/release 0.6.13 → 0.6.15
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.
- package/README.md +49 -1
- package/adapter-env.mjs +165 -0
- package/android-ndk.mjs +5 -1
- package/android-release.mjs +73 -8
- package/app-check.mjs +22 -8
- package/app-closure.mjs +214 -0
- package/assemble-app-command.mjs +24 -7
- package/atomic-write.mjs +57 -0
- package/bin/rightkit-gate.mjs +1193 -337
- package/broker-client.mjs +106 -0
- package/broker-exception.mjs +211 -0
- package/broker-handshake.mjs +160 -0
- package/broker-pins.mjs +27 -8
- package/broker-wire.mjs +262 -0
- package/build-launch.mjs +364 -12
- package/build-ownership.mjs +117 -49
- package/build-release.mjs +3 -2
- package/cache-command.mjs +7 -2
- package/candidate-build-receipt.mjs +96 -0
- package/candidate-receipt-command.mjs +29 -0
- package/cargo-contract.mjs +4 -3
- package/cargo-ledger.mjs +1 -1
- package/cargo-license-notices.mjs +2 -2
- package/cargo-shapes.mjs +332 -0
- package/cargo-target-shim.mjs +44 -5
- package/cargo-target.mjs +5 -0
- package/carried-adapter-run.mjs +31 -0
- package/carried-cargo-run.mjs +9 -0
- package/carried-cargo.mjs +237 -0
- package/carrier-census.mjs +124 -0
- package/class-a-launch-guard.mjs +266 -33
- package/cli/right-release.mjs +2 -0
- package/clippy-route.mjs +23 -11
- package/compile-inputs.mjs +9 -12
- package/control-plane.mjs +18 -0
- package/deps-cargo-lock.mjs +208 -0
- package/deps.mjs +5 -0
- package/deterministic-scans.mjs +273 -44
- package/development-signing.mjs +14 -1
- package/development.mjs +112 -26
- package/direct-origin.mjs +107 -0
- package/disk-command.mjs +1 -0
- package/disk-policy.mjs +18 -3
- package/freshness-census.mjs +206 -58
- package/gate-executor.mjs +30 -12
- package/gate-stages.mjs +93 -16
- package/github-release.mjs +2 -1
- package/github-upload-gate.mjs +2 -1
- package/install-tauri-authenticode.mjs +346 -0
- package/ios-dev.mjs +77 -23
- package/ios-package-recipe.mjs +100 -41
- package/linux-artifacts.mjs +17 -2
- package/linux-qualifier.mjs +83 -0
- package/local-publish.mjs +36 -14
- package/local-runtime.mjs +2 -2
- package/lock-identity.mjs +138 -0
- package/lsclean.sh +0 -0
- package/managed-cargo.mjs +161 -44
- package/managed-roots.mjs +130 -12
- package/media-sidecar-catalog.mjs +1 -1
- package/native-adapters.mjs +411 -0
- package/native-helpers.mjs +26 -18
- package/native-tool-planning.mjs +94 -0
- package/ownership-mark.mjs +182 -0
- package/pack-digest.mjs +122 -0
- package/package.json +12 -12
- package/pipeline-receipts.mjs +302 -302
- package/platform-dir.mjs +14 -0
- package/pnpm-adapter-grammar.mjs +226 -0
- package/progress-control.mjs +7 -2
- package/provenance-real-receipt.pins.json +7 -0
- package/provenance-verify.mjs +262 -1
- package/publication-outcomes.mjs +21 -0
- package/publish-update.mjs +2 -1
- package/qa-journeys-prebuilt-run.mjs +18 -0
- package/qa-journeys-prebuilt.mjs +148 -0
- package/qa-snapshot-run.mjs +53 -0
- package/qa-snapshot.mjs +164 -0
- package/qualification-verify.mjs +144 -0
- package/qualify-reuse.mjs +38 -0
- package/recipe-build.mjs +16 -3
- package/release-qa-audit.mjs +2 -2
- package/release-qa-command.mjs +2 -1
- package/release-qa-receipt.mjs +3 -2
- package/release-state.mjs +3 -2
- package/release.mjs +14 -3
- package/rightkit-drift.mjs +27 -5
- package/rightkit-versions.json +15 -11
- package/rustright-names.mjs +32 -0
- package/scan-tool-enrolment.mjs +167 -0
- package/sign-windows.mjs +212 -112
- package/signing-files.mjs +35 -0
- package/signing-identity.mjs +7 -0
- package/snapshot-broker.mjs +528 -57
- package/snapshot-check.mjs +95 -11
- package/snapshot-metadata.mjs +123 -0
- package/snapshot-request.mjs +389 -96
- package/stage-binary.mjs +22 -4
- package/stage-commands.mjs +56 -13
- package/stage-sidecar.mjs +93 -21
- package/suite-signing-policy.mjs +657 -0
- package/suite-signing.mjs +223 -0
- package/swift-scratch.mjs +25 -0
- package/tauri-locked.mjs +25 -0
- package/test-command.mjs +164 -29
- package/unit-gate.mjs +201 -31
- package/upload-large.mjs +0 -0
- package/web-build.mjs +2 -2
- package/win-package-recipe.mjs +26 -2
- package/bin/darwin/rightkit-swap +0 -0
- package/bin/darwin/rightkit-swap.sign.json +0 -6
package/cargo-shapes.mjs
ADDED
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
// The ONE versioned command-shape table of the gate's Cargo calls (GB-01, GB-02; audit gate-broker-2026-10-30).
|
|
2
|
+
//
|
|
3
|
+
// RustRight owns the admitted grammar (rustright-policy command_policy.rs `ThinCommandPolicy::classify`, `snapshot_read_only`,
|
|
4
|
+
// `grant_command`, `carried_refusal`, and `normalize_build_request` for the standard aliases). RightKit owns the callers. Both read one
|
|
5
|
+
// vector: docs/contracts/cargo-shapes.fixtures.json of RustRight (schema `rustright.cargo-shapes.fixtures.v1`), copied byte for byte into
|
|
6
|
+
// test-support/vectors/ with its source identity pinned (vectors-source.json). Every row of it is judged here by `classifyCargoArgv`, so
|
|
7
|
+
// the wrapper (cargo-target-shim.mjs), the carried call (snapshot-request.mjs) and the planners (stage-commands.mjs) decide from the same
|
|
8
|
+
// function and cannot drift from the broker: an accepted row reaches the broker's parser, a refused row launches zero processes.
|
|
9
|
+
//
|
|
10
|
+
// `argv` is cargo's argument list without the `cargo` word. Nothing here rewrites a caller's arguments except the five standard aliases at
|
|
11
|
+
// the subcommand position (`b c d r t`), which the broker expands before it classifies (a committed `.cargo/config.toml` `[alias]` can
|
|
12
|
+
// otherwise redefine what was classified). A `fmt` argument is never changed and a plugin restriction is never weakened.
|
|
13
|
+
|
|
14
|
+
export const COMMAND_SHAPES_SCHEMA = "rightkit.command-shapes/1";
|
|
15
|
+
export const COMMAND_SHAPES_VERSION = 1;
|
|
16
|
+
/** The RustRight vector this table is exercised against (its `schema`); a change of either side is a failing test, not a silent drift. */
|
|
17
|
+
export const COMMAND_SHAPES_VECTOR_SCHEMA = "rustright.cargo-shapes.fixtures.v1";
|
|
18
|
+
|
|
19
|
+
/** Cargo's standard aliases (rustright cargo_argv.rs CARGO_ALIASES), expanded at the subcommand position before classification. */
|
|
20
|
+
export const CARGO_COMMAND_ALIASES = Object.freeze({ b: "build", c: "check", d: "doc", r: "run", t: "test" });
|
|
21
|
+
export const canonicalCargoCommand = (name) => (typeof name === "string" && Object.hasOwn(CARGO_COMMAND_ALIASES, name) ? CARGO_COMMAND_ALIASES[name] : name);
|
|
22
|
+
|
|
23
|
+
const VALUE_GLOBALS = new Set(["--manifest-path", "--config", "-C", "-Z", "--color", "--target-dir"]);
|
|
24
|
+
const PLAIN_GLOBALS = new Set(["--locked", "--frozen", "--offline", "--quiet", "-q", "--verbose", "-v"]);
|
|
25
|
+
const VERSION_FLAGS = new Set(["--version", "-V", "-vV"]);
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Where the subcommand sits in `argv` (rustright command_policy.rs `command_index`): after an optional `+toolchain` and the global options (the
|
|
29
|
+
* ones that take a value are skipped with it), before any `--`. `--version`, `-V` and `-vV` are the command. null when there is none.
|
|
30
|
+
*/
|
|
31
|
+
export function cargoCommandIndex(argv) {
|
|
32
|
+
let i = 0;
|
|
33
|
+
while (i < argv.length) {
|
|
34
|
+
const v = argv[i];
|
|
35
|
+
i += 1;
|
|
36
|
+
if (v === "--") break;
|
|
37
|
+
if (v.startsWith("+") && v.length > 1) continue;
|
|
38
|
+
if (VERSION_FLAGS.has(v)) return i - 1;
|
|
39
|
+
if (VALUE_GLOBALS.has(v)) { i += 1; continue; }
|
|
40
|
+
if (PLAIN_GLOBALS.has(v)) continue;
|
|
41
|
+
if (v.startsWith("--manifest-path=") || v.startsWith("--config=") || v.startsWith("--color=") || v.startsWith("-Z")) continue;
|
|
42
|
+
if (v.startsWith("-")) continue;
|
|
43
|
+
return i - 1;
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The cargo subcommand of an argv as sent (an alias stays an alias), read the way Cargo reads it: the broker's reading (`cargoCommandIndex`) plus
|
|
50
|
+
* Cargo's own `--explain <code>` global, whose value is skipped here (the broker reads that value as the command and refuses it, so this
|
|
51
|
+
* only ever makes the gate demand MORE evidence, never less). null when there is none.
|
|
52
|
+
*/
|
|
53
|
+
export function cargoSubcommandOf(argv) {
|
|
54
|
+
const words = [...argv];
|
|
55
|
+
const end = words.indexOf("--");
|
|
56
|
+
const own = [];
|
|
57
|
+
for (let i = 0; i < words.length; i += 1) {
|
|
58
|
+
if ((end < 0 || i < end) && words[i] === "--explain") { i += 1; continue; }
|
|
59
|
+
own.push(words[i]);
|
|
60
|
+
}
|
|
61
|
+
const i = cargoCommandIndex(own);
|
|
62
|
+
return i === null ? null : own[i];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** `argv` with the standard alias at the subcommand position replaced by its canonical command; every other word is kept as sent. */
|
|
66
|
+
export function expandCargoAliases(argv) {
|
|
67
|
+
const out = [...argv];
|
|
68
|
+
const i = cargoCommandIndex(out);
|
|
69
|
+
if (i !== null) out[i] = canonicalCargoCommand(out[i]);
|
|
70
|
+
return out;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const hasFlag = (argv, flag) => { const end = argv.indexOf("--"); return (end < 0 ? argv : argv.slice(0, end)).includes(flag); };
|
|
74
|
+
const lockPinnedOffline = (argv) => hasFlag(argv, "--frozen") || (hasFlag(argv, "--locked") && hasFlag(argv, "--offline"));
|
|
75
|
+
|
|
76
|
+
/** Whether a short-flag cluster reaches `-Z` or `-C` (`-Zx`, `-vZx`, `-qvC/dir`); `-j`, `-p`, `-F` take the rest as their value. */
|
|
77
|
+
function shortClusterCarriesZorC(arg) {
|
|
78
|
+
if (!arg.startsWith("-") || arg.startsWith("--")) return false;
|
|
79
|
+
for (const c of arg.slice(1)) {
|
|
80
|
+
if (c === "Z" || c === "C") return true;
|
|
81
|
+
if (c === "j" || c === "p" || c === "F") return false;
|
|
82
|
+
}
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
function hasConfigOrUnstable(argv) {
|
|
86
|
+
const end = argv.indexOf("--");
|
|
87
|
+
return (end < 0 ? argv : argv.slice(0, end)).some((v) => v === "--config" || v.startsWith("--config=") || shortClusterCarriesZorC(v));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ---- external subcommands the gate sends (only when the host registers them) ----
|
|
91
|
+
|
|
92
|
+
const DENY_FLAGS = new Set(["--metadata-path", "--config"]);
|
|
93
|
+
/** `deny [--frozen] [--metadata-path <file>] [--config <file>] check`: each option once, spaced, nothing after `check`. Returns the file options or null. */
|
|
94
|
+
function denyCheckFiles(argv) {
|
|
95
|
+
const index = cargoCommandIndex(argv);
|
|
96
|
+
if (index === null || argv[index] !== "deny") return null;
|
|
97
|
+
const files = []; let frozen = false;
|
|
98
|
+
const rest = argv.slice(index + 1);
|
|
99
|
+
for (let i = 0; i < rest.length; i += 1) {
|
|
100
|
+
const arg = rest[i];
|
|
101
|
+
if (arg === "check") return i === rest.length - 1 ? files : null;
|
|
102
|
+
if (arg === "--frozen" && !frozen) { frozen = true; continue; }
|
|
103
|
+
if (DENY_FLAGS.has(arg)) {
|
|
104
|
+
if (files.some(([seen]) => seen === arg)) return null;
|
|
105
|
+
const value = rest[i + 1];
|
|
106
|
+
if (typeof value !== "string" || value === "" || value.startsWith("-")) return null;
|
|
107
|
+
i += 1; files.push([arg, value]); continue;
|
|
108
|
+
}
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
/** `audit --db <dir> --no-fetch`, either order, nothing else. Returns the database directory or null. */
|
|
114
|
+
function auditOfflineDb(argv) {
|
|
115
|
+
const index = cargoCommandIndex(argv);
|
|
116
|
+
if (index === null || argv[index] !== "audit") return null;
|
|
117
|
+
let db = null; let noFetch = false;
|
|
118
|
+
const rest = argv.slice(index + 1);
|
|
119
|
+
for (let i = 0; i < rest.length; i += 1) {
|
|
120
|
+
const arg = rest[i];
|
|
121
|
+
if (arg === "--no-fetch" && !noFetch) { noFetch = true; continue; }
|
|
122
|
+
if (arg === "--db" && db === null) {
|
|
123
|
+
const value = rest[i + 1];
|
|
124
|
+
if (typeof value !== "string" || value === "" || value.startsWith("-")) return null;
|
|
125
|
+
i += 1; db = value; continue;
|
|
126
|
+
}
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
return db !== null && noFetch ? db : null;
|
|
130
|
+
}
|
|
131
|
+
/** `about generate --frozen --fail [--config f] [--manifest-path f] [--all-features | --features l] <template>` exactly as the licence notices send it. */
|
|
132
|
+
function aboutNoticesShape(argv) {
|
|
133
|
+
const index = cargoCommandIndex(argv);
|
|
134
|
+
if (index === null) return false;
|
|
135
|
+
const tail = argv.slice(index + 1);
|
|
136
|
+
if (argv[index] !== "about" || tail.length < 4 || tail[0] !== "generate" || tail[1] !== "--frozen" || tail[2] !== "--fail") return false;
|
|
137
|
+
const seen = []; let template = false; let features = false;
|
|
138
|
+
const rest = tail.slice(3);
|
|
139
|
+
for (let i = 0; i < rest.length; i += 1) {
|
|
140
|
+
const arg = rest[i];
|
|
141
|
+
if (arg === "--config" || arg === "--manifest-path" || arg === "--features") {
|
|
142
|
+
const isFeature = arg === "--features";
|
|
143
|
+
const value = rest[i + 1];
|
|
144
|
+
if (seen.includes(arg) || (isFeature && features) || typeof value !== "string" || value === "" || value.startsWith("-")) return false;
|
|
145
|
+
i += 1; features ||= isFeature; seen.push(arg); continue;
|
|
146
|
+
}
|
|
147
|
+
if (arg === "--all-features" && !features) { features = true; continue; }
|
|
148
|
+
if (!arg.startsWith("-") && !template) { template = true; continue; }
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
return template;
|
|
152
|
+
}
|
|
153
|
+
/** The part of `argv` cargo itself parses: before the subcommand for the deny and about shapes (the rest is the tool's own), all of it otherwise. */
|
|
154
|
+
function cargoOwnArgs(argv) {
|
|
155
|
+
const index = cargoCommandIndex(argv);
|
|
156
|
+
return index !== null && (denyCheckFiles(argv) !== null || aboutNoticesShape(argv)) ? argv.slice(0, index) : argv;
|
|
157
|
+
}
|
|
158
|
+
function fmtCheckShape(tail) {
|
|
159
|
+
if (tail.length === 3 && tail[0] === "-p" && tail[1] !== "" && !tail[1].startsWith("-") && tail[2] === "--check") return true;
|
|
160
|
+
return tail.length === 2 && tail[0] === "--all" && tail[1] === "--check";
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ---- the package observation (rustright build_rules.rs observation_only_cargo, package arm) ----
|
|
164
|
+
|
|
165
|
+
const PACKAGE_PLAIN_FLAGS = new Set(["--list", "--locked", "--allow-dirty"]);
|
|
166
|
+
/**
|
|
167
|
+
* Whether `argv` is the one package form the broker admits as an observation that compiles nothing: no `--`, no `+toolchain`, the subcommand as
|
|
168
|
+
* the one positional, `-p`/`--package` once with a value that is not a flag (`--package=x` is read, `-p=x` is not), and only `--list`,
|
|
169
|
+
* `--locked` and `--allow-dirty` besides; it must carry `--list`, `--locked` and one package. Anything else is a compiling package.
|
|
170
|
+
*/
|
|
171
|
+
export function isPackageObservation(argv) {
|
|
172
|
+
const words = [...argv];
|
|
173
|
+
if (words.includes("--") || words.some((w) => w.startsWith("+"))) return false;
|
|
174
|
+
const first = words.find((w) => !w.startsWith("-"));
|
|
175
|
+
if (first !== "package") return false;
|
|
176
|
+
let seen = false; let list = false; let locked = false; let packages = 0;
|
|
177
|
+
for (let i = 0; i < words.length; i += 1) {
|
|
178
|
+
const w = words[i];
|
|
179
|
+
if (!w.startsWith("-")) { if (seen || w !== "package") return false; seen = true; continue; }
|
|
180
|
+
const eq = w.startsWith("--") ? w.indexOf("=") : -1;
|
|
181
|
+
const name = eq >= 0 ? w.slice(0, eq) : w; const inline = eq >= 0 ? w.slice(eq + 1) : null;
|
|
182
|
+
if (name === "-p" || name === "--package") {
|
|
183
|
+
const value = inline ?? words[++i];
|
|
184
|
+
if (typeof value !== "string" || value === "" || value.startsWith("-")) return false;
|
|
185
|
+
packages += 1; if (packages > 1) return false;
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
if (!PACKAGE_PLAIN_FLAGS.has(name) || inline !== null) return false;
|
|
189
|
+
if (name === "--list") list = true; else if (name === "--locked") locked = true;
|
|
190
|
+
}
|
|
191
|
+
return seen && list && locked && packages === 1;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// ---- classification (command_policy.rs cargo_capability) ----
|
|
195
|
+
|
|
196
|
+
const CONTROL_COMMANDS = new Set(["--version", "-V", "-vV", "version", "help", "locate-project", "read-manifest", "logout", "owner", "yank", "login"]);
|
|
197
|
+
const ANALYSIS_COMMANDS = new Set(["search", "info", "pkgid", "verify-project", "report", "config"]);
|
|
198
|
+
const WORKSPACE_WRITE_COMMANDS = new Set(["new", "init", "update", "vendor", "generate-lockfile", "add", "remove"]);
|
|
199
|
+
/** The built-in commands the broker classifies as compiling (a Capability with `compile: true`): `package` only when it is not the observation. */
|
|
200
|
+
export const BROKER_COMPILE_COMMANDS = Object.freeze(["fix", "run", "package", "publish", "build", "check", "test", "bench", "clippy", "doc", "rustc", "rustdoc"]);
|
|
201
|
+
const CARGO_BUILTINS = new Set(["build", "check", "test", "bench", "clippy", "doc", "run", "install", "rustc", "rustdoc", "clean", "update", "vendor", "generate-lockfile", "add", "remove", "metadata", "tree", "fetch", "fix", "publish", "package", "login", "logout", "owner", "yank", "uninstall", "init", "new", "info", "config", "report", "pkgid", "verify-project", "locate-project", "read-manifest", "search", "help", "version", "fmt", "--version", "-V"]);
|
|
202
|
+
/** External subcommands and their classification once the host registers them (command_policy.rs external_policy). */
|
|
203
|
+
const EXTERNAL = Object.freeze({ about: { compile: false }, outdated: { compile: false }, audit: { compile: false }, deny: { compile: false }, machete: { compile: false }, upgrade: { compile: false }, "set-version": { compile: false }, nextest: { compile: true }, ndk: { compile: true } });
|
|
204
|
+
/** The external subcommands the gate itself sends through the broker: the host registers each (rustright adapter/external registrations). */
|
|
205
|
+
export const GATE_EXTERNAL_COMMANDS = Object.freeze(["deny", "audit", "about", "ndk", "nextest"]);
|
|
206
|
+
|
|
207
|
+
const MANAGED_TOOL_NAMES = new Set(["cargo", "rustup", "rustc", "rustdoc", "swift", "cmake", "make", "cc", "clang", "gcc", "node", "node-gyp", "pnpm", "npm"]);
|
|
208
|
+
const REDIRECT_LONG = ["--target-dir", "--config", "--cache-dir", "--build-dir", "--toolchain", "--temp-dir", "-Zbuild-std", "-Zconfig"];
|
|
209
|
+
const isRedirectArgument = (a) => REDIRECT_LONG.some((flag) => a === flag || a.startsWith(`${flag}=`));
|
|
210
|
+
const isUnmanagedToolPath = (a) => {
|
|
211
|
+
const abs = a.startsWith("/") || /^[A-Za-z]:[\\/]/.test(a) || a.startsWith("\\\\");
|
|
212
|
+
const name = a.split(/[\\/]/).at(-1).toLowerCase().replace(/\.exe$/, "");
|
|
213
|
+
return abs && MANAGED_TOOL_NAMES.has(name);
|
|
214
|
+
};
|
|
215
|
+
const OWN_FLAG_SUBCOMMANDS = new Set(["about", "outdated", "audit", "deny", "machete"]);
|
|
216
|
+
|
|
217
|
+
/** rustright request.rs `reject_managed_agent_redirects` for an argv: a redirecting option, a `+toolchain` or an unmanaged tool path before any `--`. */
|
|
218
|
+
function redirectRefused(argv) {
|
|
219
|
+
const at = argv.findIndex((a) => !a.startsWith("-"));
|
|
220
|
+
const ownFrom = at >= 0 && OWN_FLAG_SUBCOMMANDS.has(argv[at]) ? at : null;
|
|
221
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
222
|
+
if (argv[i] === "--") break;
|
|
223
|
+
const cargoFlag = ownFrom === null || i < ownFrom;
|
|
224
|
+
if ((cargoFlag && isRedirectArgument(argv[i])) || argv[i].startsWith("+") || isUnmanagedToolPath(argv[i])) return true;
|
|
225
|
+
}
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/** The refusal rows: code is the broker's own typed code. */
|
|
230
|
+
const refused = (code, message) => ({ refused: { code, message } });
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* What the broker's production parser decides for `argv` (cargo's arguments without `cargo`), in rustright's order: normalisation (the standard
|
|
234
|
+
* aliases, redirect refusals), classification (command, compile) and the carried, grant and carried-refusal predicates.
|
|
235
|
+
* - `{ refused: { code, message } }`: the broker answers the typed code and runs zero processes;
|
|
236
|
+
* - `{ normalizedArgv, command, compile, carriedReadOnly, grant, carriedRefusal }` (the vector's columns).
|
|
237
|
+
* `registered` lists the external subcommands the host registers (default none, as in the vector); `deny`, `audit`, `about`, `ndk` and
|
|
238
|
+
* `nextest` are classified only when registered, as on the broker.
|
|
239
|
+
*/
|
|
240
|
+
export function classifyCargoArgv(argv, { registered = [] } = {}) {
|
|
241
|
+
const words = [...argv];
|
|
242
|
+
if (!words.every((w) => typeof w === "string" && w !== "")) return refused("RK_ARGV_INVALID", "Cargo argv must contain non-empty strings");
|
|
243
|
+
if (redirectRefused(words)) return refused("RK_REDIRECT_REFUSED", "managed agent request cannot redirect cache, build, toolchain, temp, or tool path");
|
|
244
|
+
const normalizedArgv = expandCargoAliases(words);
|
|
245
|
+
const index = cargoCommandIndex(normalizedArgv);
|
|
246
|
+
const command = index === null ? null : normalizedArgv[index];
|
|
247
|
+
if (command === null) return refused("RK_UNKNOWN_COMMAND", `unregistered managed command: cargo ${normalizedArgv.join(" ")}`.trim());
|
|
248
|
+
if (command === "install" || command === "uninstall") return refused("RK_COMMAND_DENIED", `agent tool ${command === "install" ? "installation" : "removal"} is denied; managed tools are digest-registered by the operator`);
|
|
249
|
+
if (command === "clean") return refused("RR_CLEAN_REFUSED", "denied managed command: cargo clean");
|
|
250
|
+
let compile;
|
|
251
|
+
if (CONTROL_COMMANDS.has(command) || ANALYSIS_COMMANDS.has(command) || WORKSPACE_WRITE_COMMANDS.has(command) || command === "metadata" || command === "tree" || command === "fetch" || command === "fmt") compile = false;
|
|
252
|
+
else if (command === "package") compile = !isPackageObservation(normalizedArgv);
|
|
253
|
+
else if (BROKER_COMPILE_COMMANDS.includes(command)) compile = true;
|
|
254
|
+
else if (registered.includes(command) && Object.hasOwn(EXTERNAL, command)) compile = EXTERNAL[command].compile;
|
|
255
|
+
else return refused("RK_UNKNOWN_COMMAND", `unregistered managed command: cargo ${normalizedArgv.join(" ")}`.trim());
|
|
256
|
+
const carriedRefusalText = hasConfigOrUnstable(cargoOwnArgs(normalizedArgv)) ? "a snapshot-bound request does not carry cargo --config or -Z" : null;
|
|
257
|
+
return { normalizedArgv, command, compile, carriedReadOnly: snapshotReadOnly(normalizedArgv), grant: grantCommand(normalizedArgv).ok, carriedRefusal: carriedRefusalText !== null, carriedRefusalText };
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function offlineReadOnly(argv) {
|
|
261
|
+
if (hasConfigOrUnstable(cargoOwnArgs(argv))) return false;
|
|
262
|
+
const index = cargoCommandIndex(argv);
|
|
263
|
+
if (index === null) return false;
|
|
264
|
+
switch (canonicalCargoCommand(argv[index])) {
|
|
265
|
+
case "--version": case "-V": case "version": case "locate-project": case "read-manifest": return true;
|
|
266
|
+
case "metadata": return hasFlag(argv, "--no-deps") || lockPinnedOffline(argv);
|
|
267
|
+
case "about": {
|
|
268
|
+
const end = argv.indexOf("--");
|
|
269
|
+
const after = argv.slice(index + 1, end < 0 ? undefined : end);
|
|
270
|
+
const at = after.findIndex((v) => !v.startsWith("-"));
|
|
271
|
+
return at >= 0 && after[at] === "generate" && lockPinnedOffline(after.slice(at + 1));
|
|
272
|
+
}
|
|
273
|
+
default: return false;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
function classAScan(argv) {
|
|
277
|
+
if (hasConfigOrUnstable(cargoOwnArgs(argv))) return null;
|
|
278
|
+
const index = cargoCommandIndex(argv);
|
|
279
|
+
if (index === null) return null;
|
|
280
|
+
switch (canonicalCargoCommand(argv[index])) {
|
|
281
|
+
case "deny": return denyCheckFiles(argv);
|
|
282
|
+
case "audit": { const db = auditOfflineDb(argv); return db === null ? null : [["--db", db]]; }
|
|
283
|
+
case "fetch": case "metadata": return hasFlag(argv, "--locked") || hasFlag(argv, "--frozen") ? [] : null;
|
|
284
|
+
case "fmt": return fmtCheckShape(argv.slice(index + 1)) ? [] : null;
|
|
285
|
+
case "package": return isPackageObservation(argv) ? [] : null;
|
|
286
|
+
default: return null;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
/** What a snapshot-bound (carried) cargo request that compiles nothing may be (command_policy.rs `snapshot_read_only`). */
|
|
290
|
+
export const snapshotReadOnly = (argv) => offlineReadOnly(argv) || classAScan(argv) !== null;
|
|
291
|
+
|
|
292
|
+
/** What a stage grant's allowlist accepts (command_policy.rs `grant_command`): `{ ok: true }` or `{ ok: false, reason }`. */
|
|
293
|
+
export function grantCommand(argv) {
|
|
294
|
+
if (hasConfigOrUnstable(argv)) return { ok: false, reason: "a stage grant does not carry cargo --config or -Z" };
|
|
295
|
+
const index = cargoCommandIndex(argv);
|
|
296
|
+
if (index === null) return { ok: false, reason: "a stage grant needs a cargo subcommand" };
|
|
297
|
+
const command = canonicalCargoCommand(argv[index]);
|
|
298
|
+
if (command === "build" || command === "run") return hasFlag(argv, "--locked") || hasFlag(argv, "--frozen") ? { ok: true } : { ok: false, reason: `a stage grant carries cargo ${argv[index]} only with --locked or --frozen` };
|
|
299
|
+
if (command === "about") return { ok: false, reason: "a stage grant does not carry cargo about" };
|
|
300
|
+
if (offlineReadOnly(argv)) return { ok: true };
|
|
301
|
+
return { ok: false, reason: `a stage grant does not carry cargo ${command} with these arguments` };
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/** The version spellings the broker answers uncarried inside a snapshot (command_policy.rs VERSION_ARGUMENTS and `version`), matched as the WHOLE argv. */
|
|
305
|
+
export const VERSION_PROBES = Object.freeze([["-V"], ["--version"], ["-vV"], ["-Vv"], ["--version", "--verbose"], ["-V", "--verbose"], ["-v", "-V"], ["version"]]);
|
|
306
|
+
export const isVersionProbe = (argv) => VERSION_PROBES.some((probe) => probe.length === argv.length && probe.every((w, i) => w === argv[i]));
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* The decision for a CARRIED call (a snapshot-bound request through the broker): the shape the wrapper, the carried seam and the planners
|
|
310
|
+
* share. `{ ok: true, argv, compile }` (`argv` with the aliases expanded, the one rewrite) or `{ ok: false, code, message }`, decided
|
|
311
|
+
* before anything is submitted, so a refused shape launches no process. A carried call must compile or be a carried read-only shape; the
|
|
312
|
+
* broker refuses everything else (`tree`, `pkgid`, `verify-project`, a bare `metadata`, a `fmt` write form, `help`), so it is never sent.
|
|
313
|
+
*/
|
|
314
|
+
export function carriedShapeDecision(argv, options = {}) {
|
|
315
|
+
const row = classifyCargoArgv(argv, { registered: GATE_EXTERNAL_COMMANDS, ...options });
|
|
316
|
+
if (row.refused) return { ok: false, code: row.refused.code, message: `the broker refuses \`cargo ${[...argv].join(" ")}\` (${row.refused.code}: ${row.refused.message}); it is not sent` };
|
|
317
|
+
if (row.carriedRefusal) return { ok: false, code: "RK_CARGO_BROKER_CARRIED_REFUSED", message: `\`cargo ${[...argv].join(" ")}\`: ${row.carriedRefusalText}; it is not sent` };
|
|
318
|
+
if (!row.compile && !row.carriedReadOnly) return { ok: false, code: "RK_CARGO_BROKER_READ_ONLY_SHAPE", message: `\`cargo ${[...argv].join(" ")}\` is neither a compile nor a carried read-only shape (metadata --no-deps or --locked/--frozen, fetch --locked, fmt --check, locate-project, read-manifest, deny, audit, package --list); the broker refuses it inside a snapshot, so it is not sent` };
|
|
319
|
+
return { ok: true, argv: row.normalizedArgv, compile: row.compile };
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Whether the broker classifies this argv (cargo's arguments) as a compile: the receipt then carries freshness, attribution and toolchain
|
|
324
|
+
* evidence (or typed unavailable evidence), and absence is never a success. An argv the broker refuses outright never produces a receipt; if one
|
|
325
|
+
* is presented anyway it still owes the evidence its compile command names (more evidence is demanded, never less).
|
|
326
|
+
*/
|
|
327
|
+
export function isCompileCommand(argv) {
|
|
328
|
+
const row = classifyCargoArgv(argv, { registered: GATE_EXTERNAL_COMMANDS });
|
|
329
|
+
if (!row.refused) return row.compile === true;
|
|
330
|
+
const sub = canonicalCargoCommand(cargoSubcommandOf(argv));
|
|
331
|
+
return BROKER_COMPILE_COMMANDS.includes(sub) && !(sub === "package" && isPackageObservation(argv));
|
|
332
|
+
}
|
package/cargo-target-shim.mjs
CHANGED
|
@@ -30,9 +30,12 @@ import fs from "node:fs";
|
|
|
30
30
|
import path from "node:path";
|
|
31
31
|
import { createHash } from "node:crypto";
|
|
32
32
|
import { spawnSync } from "./background-process.mjs";
|
|
33
|
+
import { spawnBrokerClient } from "./broker-client.mjs";
|
|
33
34
|
import { fileURLToPath } from "node:url";
|
|
34
35
|
import { SCAN_TOKEN_ENV, appendLedger } from "./cargo-ledger.mjs";
|
|
35
36
|
import { BROKER_REFUSAL_STATUS, runBrokerCargo } from "./snapshot-broker.mjs";
|
|
37
|
+
import { carriedScanRefusal } from "./snapshot-metadata.mjs";
|
|
38
|
+
import { carriedShapeDecision } from "./cargo-shapes.mjs";
|
|
36
39
|
|
|
37
40
|
export const SHIM_MAP_SCHEMA = "rightkit.cargo-target-shim/3";
|
|
38
41
|
// Exit status of a refusal: cargo's own error status, so every caller reads it as a failed cargo.
|
|
@@ -48,6 +51,16 @@ const PLUGIN = new Set(["deny", "audit"]);
|
|
|
48
51
|
const BOUND = new Set([...COMPILE, ...LOCKED, ...LOCKFILE]);
|
|
49
52
|
// Commands that read no workspace at all: run unchanged.
|
|
50
53
|
const PASSTHROUGH = new Set(["version", "help"]);
|
|
54
|
+
/** The subcommands the wrapper binds or passes through (the contract test pins each to the broker's classification). */
|
|
55
|
+
export const WRAPPER_COMMANDS = Object.freeze({ get bound() { return [...BOUND]; }, get passthrough() { return [...PASSTHROUGH]; } });
|
|
56
|
+
// Cargo's built-in command aliases. The broker classifies only the full names (command_policy.rs: b, c, t, r, d are RK_UNKNOWN_COMMAND), so a
|
|
57
|
+
// broker-mode call is expanded to the full name BEFORE it is classified and sent (the signed argv digest is over the argv as sent).
|
|
58
|
+
const ALIASES = Object.freeze({ b: "build", c: "check", t: "test", r: "run", d: "doc" });
|
|
59
|
+
// The only requests the broker lets run uncarried inside a snapshot (command_policy.rs uncarried_snapshot_probe: VERSION_ARGUMENTS and `version`).
|
|
60
|
+
const VERSION_PROBES = Object.freeze([["-V"], ["--version"], ["-vV"], ["-Vv"], ["--version", "--verbose"], ["-V", "--verbose"], ["-v", "-V"], ["version"]]);
|
|
61
|
+
// The read-only commands the broker admits to a carried request only in carriedScanRefusal's exact shapes (snapshot-metadata.mjs).
|
|
62
|
+
const CARRIED_SHAPE = new Set(["metadata", "deny", "audit", "fmt"]);
|
|
63
|
+
const insideOf = (child, parent) => { const rel = path.relative(parent, child); return rel === "" || !(rel === ".." || rel.startsWith(`..${path.sep}`) || path.isAbsolute(rel)); };
|
|
51
64
|
// Global options that take a value (`--color always`, `--explain E0001`); the subcommand is the first word after them.
|
|
52
65
|
const GLOBAL_VALUE = new Set(["--color", "--explain"]);
|
|
53
66
|
// Options that move the working directory, the configuration or the output (a short cluster holding C or Z too).
|
|
@@ -76,12 +89,29 @@ export function decideCargoInvocation({ args, cwd, env, map, locate, realpath =
|
|
|
76
89
|
sub = w;
|
|
77
90
|
break;
|
|
78
91
|
}
|
|
92
|
+
const broker = map.mode === "broker";
|
|
93
|
+
if (broker && sub !== null && Object.hasOwn(ALIASES, sub)) { words[i] = ALIASES[sub]; sub = ALIASES[sub]; }
|
|
79
94
|
const end = sub === null ? -1 : words.indexOf("--", i + 1);
|
|
80
95
|
const own = sub === null ? [] : words.slice(i + 1, end < 0 ? undefined : end);
|
|
81
96
|
const globals = words.slice(toolchain ? 1 : 0, sub === null ? undefined : i);
|
|
82
97
|
if (sub === "fmt" && !own.includes("--check")) return refusal("RK_CARGO_UNBOUND_COMMAND", "`cargo fmt` without --check rewrites tracked files; a snapshot compile changes no file of the commit");
|
|
83
|
-
if (sub === null || PASSTHROUGH.has(sub))
|
|
98
|
+
if (sub === null || PASSTHROUGH.has(sub)) {
|
|
99
|
+
// Inside the snapshot the broker runs only a version probe uncarried; `help`, `--list` and anything else would be refused there (and is
|
|
100
|
+
// never sent as a root request from the snapshot), so the wrapper refuses it first. A snapshot it cannot place is treated as inside.
|
|
101
|
+
if (broker && !VERSION_PROBES.some((probe) => probe.length === words.length && probe.every((w, k) => w === words[k]))) {
|
|
102
|
+
let inside = true;
|
|
103
|
+
try { if (map.broker?.snapshot?.path) inside = insideOf(realpath(cwd), realpath(map.broker.snapshot.path)); } catch { inside = true; }
|
|
104
|
+
if (inside) return refusal("RK_CARGO_BROKER_UNCARRIED", `\`cargo ${words.join(" ")}\` reads no workspace but is not a version probe; the broker refuses an uncarried call inside the snapshot (only ${VERSION_PROBES.map((p) => p.join(" ")).join(", ")}), so it is never sent`);
|
|
105
|
+
}
|
|
106
|
+
return { exec: { args: words, env: withCargo({ ...env }) } };
|
|
107
|
+
}
|
|
84
108
|
if (!BOUND.has(sub)) return refusal("RK_CARGO_UNBOUND_COMMAND", `\`cargo ${sub}\` is not a command a snapshot compile binds (${[...BOUND].join(", ")}; or ${[...PASSTHROUGH].join(", ")}, which read no workspace): an alias, plugin, clean, install, publish or lock rewrite is refused`);
|
|
109
|
+
// Broker mode: metadata, deny, audit and fmt ride only in the exact shapes the broker admits as carried read-only requests (the one rule,
|
|
110
|
+
// snapshot-metadata.mjs carriedScanRefusal). Nothing is added to the argv and nothing is dropped: a call of another shape is refused.
|
|
111
|
+
if (broker && CARRIED_SHAPE.has(sub)) {
|
|
112
|
+
const shaped = i === 0 ? carriedScanRefusal(words, { checkout: map.broker?.checkout ?? null }) : `a +toolchain or global option before \`${sub}\` is not carried`;
|
|
113
|
+
if (shaped !== null) return refusal("RK_CARGO_BROKER_READ_ONLY_SHAPE", `\`cargo ${words.join(" ")}\` is not a shape the broker carries (${shaped})`);
|
|
114
|
+
}
|
|
85
115
|
if (LOCKED.has(sub) && ![...globals, ...own].some((w) => w === "--locked" || w === "--frozen")) return refusal("RK_CARGO_UNBOUND_COMMAND", `\`cargo ${sub}\` without --locked or --frozen may write a Cargo.lock; a snapshot compile changes no file of the commit and writes nothing outside its bound outputs`);
|
|
86
116
|
if (!PLUGIN.has(sub)) for (const w of own) if (REDIRECT.test(w)) return refusal("RK_CARGO_REDIRECT", `\`cargo ${sub}\` passes ${w}, which moves its working directory, configuration or output; a snapshot compile's outputs must be the checkout's`);
|
|
87
117
|
const valueOf = (name) => {
|
|
@@ -90,11 +120,16 @@ export function decideCargoInvocation({ args, cwd, env, map, locate, realpath =
|
|
|
90
120
|
return own[at].includes("=") ? own[at].slice(name.length + 1) : (own[at + 1] ?? "");
|
|
91
121
|
};
|
|
92
122
|
const manifestPath = valueOf("--manifest-path");
|
|
93
|
-
const broker = map.mode === "broker";
|
|
94
123
|
// The RustRight client submits the call from the working directory and refuses to rewrite it (attestation), and the broker pins the
|
|
95
124
|
// output: a manifest path or a target directory of the caller's has no place in a broker call.
|
|
96
125
|
if (broker && manifestPath !== undefined) return refusal("RK_CARGO_BROKER_MANIFEST_PATH", `\`cargo ${sub}\` passes --manifest-path; a broker call runs from the workspace's own directory (the client refuses to rewrite the invocation), so run it there`);
|
|
97
126
|
if (broker && valueOf("--target-dir") !== undefined) return refusal("RK_CARGO_REDIRECT", `\`cargo ${sub}\` passes --target-dir; the broker pins a snapshot compile's output directory`);
|
|
127
|
+
// GB-01: in broker mode the one command-shape table (cargo-shapes.mjs) decides what the broker would admit inside a snapshot, before anything is
|
|
128
|
+
// sent: tree, pkgid and verify-project (no non-writing policy at RustRight), a bare metadata and the other shapes it refuses are refused here, typed.
|
|
129
|
+
if (broker) {
|
|
130
|
+
const decision = carriedShapeDecision(words);
|
|
131
|
+
if (!decision.ok) return refusal(decision.code, decision.message);
|
|
132
|
+
}
|
|
98
133
|
let workspace;
|
|
99
134
|
try {
|
|
100
135
|
const manifest = locate(manifestPath === undefined ? null : path.resolve(cwd, manifestPath), cwd, toolchain);
|
|
@@ -135,7 +170,7 @@ export function installCargoShim({ base, snapshotRoot, targetDirs, broker = null
|
|
|
135
170
|
const sorted = Object.fromEntries(Object.entries(workspaces).sort(([a], [b]) => (a < b ? -1 : 1)));
|
|
136
171
|
const identityHash = createHash("sha256").update(JSON.stringify({ realCargo: realCargo ?? null, node, workspaces: sorted, ...(broker ? { mode, section: broker.section } : {}) })).update(fs.readFileSync(self));
|
|
137
172
|
// The broker half of the wrapper is part of its identity: any change to the code that carries and verifies a call is another wrapper.
|
|
138
|
-
if (broker) for (const name of ["snapshot-broker.mjs", "snapshot-request.mjs", "provenance-verify.mjs", "cargo-ledger.mjs"]) identityHash.update(fs.readFileSync(new URL(`./${name}`, import.meta.url)));
|
|
173
|
+
if (broker) for (const name of ["snapshot-broker.mjs", "snapshot-request.mjs", "snapshot-metadata.mjs", "provenance-verify.mjs", "cargo-ledger.mjs"]) identityHash.update(fs.readFileSync(new URL(`./${name}`, import.meta.url)));
|
|
139
174
|
const identity = identityHash.digest("hex");
|
|
140
175
|
const dir = path.join(base, `rk-cargo-shim-${identity.slice(0, 16)}`);
|
|
141
176
|
const bin = path.join(dir, "bin");
|
|
@@ -229,10 +264,14 @@ export function runShim(argv, { env = process.env, cwd = process.cwd(), spawn =
|
|
|
229
264
|
// A call that reads no workspace (version, help) runs as it is: the real cargo in direct mode, the host's cargo or the client in broker mode.
|
|
230
265
|
const viaClient = broker && !map.realCargo;
|
|
231
266
|
const command = viaClient ? map.broker.client : map.realCargo;
|
|
232
|
-
const
|
|
267
|
+
const spawnOpts = { cwd, env: decision.exec.env, stdio: "inherit" };
|
|
268
|
+
const r = viaClient ? spawnBrokerClient(spawn, command, ["cargo", ...decision.exec.args], spawnOpts, { platform: map.broker.platform }) : spawn(command, decision.exec.args, spawnOpts);
|
|
233
269
|
if (r.error) return refuse({ code: "RK_CARGO_SHIM_ERROR", message: `${command} did not start (${r.error.message})` });
|
|
234
270
|
record({ ...base, status: "ran", exit: r.status ?? null });
|
|
235
271
|
return r.status ?? (r.signal ? 128 : SHIM_REFUSAL_STATUS);
|
|
236
272
|
}
|
|
237
273
|
|
|
238
|
-
|
|
274
|
+
// Run as the wrapper only when this file is the entry. Imported by another module (carried-cargo.mjs, through managed-cargo.mjs), the entry
|
|
275
|
+
// may be anything, `node -e` arguments included, so a path that cannot be resolved is no wrapper run.
|
|
276
|
+
const invokedAsWrapper = (() => { try { return Boolean(process.argv[1]) && fs.realpathSync(process.argv[1]) === fs.realpathSync(fileURLToPath(import.meta.url)); } catch { return false; } })();
|
|
277
|
+
if (invokedAsWrapper) process.exit(runShim(process.argv.slice(2)));
|
package/cargo-target.mjs
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
// `.rightkit-qa-bin` stash) and run from there.
|
|
20
20
|
import { realpathSync } from "node:fs";
|
|
21
21
|
import { execFileSync } from "./background-process.mjs";
|
|
22
|
+
import { carriedOutputDir } from "./carried-cargo.mjs";
|
|
22
23
|
import { runCargoSync } from "./managed-cargo.mjs";
|
|
23
24
|
import path from "node:path";
|
|
24
25
|
import { fileURLToPath } from "node:url";
|
|
@@ -26,6 +27,8 @@ import { fileURLToPath } from "node:url";
|
|
|
26
27
|
export function resolveTargetRoot(manifestPath, options = {}) {
|
|
27
28
|
if (!manifestPath) throw new Error("resolveTargetRoot requires a Cargo.toml manifest path");
|
|
28
29
|
const execute = options.execFileSync ?? execFileSync;
|
|
30
|
+
// A stage's carrier names the output directory from a signed receipt it already holds (a read-only metadata call is not a snapshot request the broker accepts yet).
|
|
31
|
+
if (!options.execFileSync) { const receipt = carriedOutputDir(options.env ?? process.env, manifestPath); if (receipt) return path.resolve(receipt); }
|
|
29
32
|
let output;
|
|
30
33
|
try {
|
|
31
34
|
const args = ["metadata", "--offline", "--format-version", "1", "--no-deps", "--manifest-path", manifestPath];
|
|
@@ -35,6 +38,8 @@ export function resolveTargetRoot(manifestPath, options = {}) {
|
|
|
35
38
|
const result = runCargoSync(args, { cwd: path.dirname(manifestPath), encoding: "utf8", maxBuffer: 64 * 1024 * 1024 });
|
|
36
39
|
if (result.error) throw result.error;
|
|
37
40
|
if (result.status !== 0) throw new Error(String(result.stderr ?? "").trim() || `exited ${result.status}`);
|
|
41
|
+
// A carried call (a snapshot stage's, carried-cargo.mjs): the output directory is the signed receipt's, not what the call prints.
|
|
42
|
+
if (typeof result.outputDir === "string") return path.resolve(result.outputDir);
|
|
38
43
|
output = result.stdout;
|
|
39
44
|
}
|
|
40
45
|
} catch (error) {
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// `node carried-adapter-run.mjs <adapter> -- <args>`: one pnpm, node or QA step of a snapshot stage on a broker host, carried
|
|
3
|
+
// (carried-cargo.mjs runCarriedAdapter) from this process's working directory. build-launch.mjs hands such a step to this entry instead of
|
|
4
|
+
// to the RustRight client, so the step is one signed, ledgered adapter request (rustright#190) with its compiles nested under it, the
|
|
5
|
+
// counterpart of carried-cargo-run.mjs for Cargo.
|
|
6
|
+
//
|
|
7
|
+
// The step's own environment arrives in this process's environment: build-launch.mjs sets RUSTRIGHT_ADAPTER_ENV to exactly the names the step
|
|
8
|
+
// declares (adapter-env.mjs adapterEnvLaunch) and those variables' values. runCarriedAdapter reads the same list back (forwardedAdapterEnv),
|
|
9
|
+
// refuses a reserved or unset name with its code (a ledgered refusal, status 101), and binds the forwarded values into the request core the
|
|
10
|
+
// gate recomputes. A step that declares no env has no RUSTRIGHT_ADAPTER_ENV, so the request carries an empty environment.
|
|
11
|
+
//
|
|
12
|
+
// `node carried-adapter-run.mjs --skip-install <pnpm args>` runs nothing: a snapshot stage's dependencies were installed by the gate, so a
|
|
13
|
+
// step that only installs them is skipped with a log line (build-launch.mjs snapshotInstallSkip).
|
|
14
|
+
import { carriedCargoMapOf, runCarriedAdapter } from "./carried-cargo.mjs";
|
|
15
|
+
|
|
16
|
+
const [first, ...rest] = process.argv.slice(2);
|
|
17
|
+
if (first === "--skip-install") {
|
|
18
|
+
process.stderr.write(`rightkit-gate: snapshot route: skipped \`pnpm ${rest.join(" ")}\`: the gate already installed this snapshot's dependencies (a stage never installs into the commit's snapshot)\n`);
|
|
19
|
+
process.exit(0);
|
|
20
|
+
}
|
|
21
|
+
const separator = rest.indexOf("--");
|
|
22
|
+
const adapter = first;
|
|
23
|
+
const args = separator >= 0 ? rest.slice(separator + 1) : [];
|
|
24
|
+
const carried = carriedCargoMapOf(process.env);
|
|
25
|
+
if (!adapter || separator !== 0 || !carried?.map) {
|
|
26
|
+
process.stderr.write(`rightkit-gate: RK_ADAPTER_CARRIER_MISSING: ${!adapter || separator !== 0 ? "usage: carried-adapter-run.mjs <adapter> -- <args>" : (carried?.error ?? "no carried map in the environment")}; a snapshot stage's step is never sent to the client around its carrier\n`);
|
|
27
|
+
process.exit(101);
|
|
28
|
+
}
|
|
29
|
+
const result = runCarriedAdapter(adapter, args, { cwd: process.cwd(), env: process.env, stdio: "inherit" }, { map: carried.map });
|
|
30
|
+
if (result.error) process.stderr.write(`rightkit-gate: ${result.error.message}\n`);
|
|
31
|
+
process.exit(result.status ?? 1);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// `node carried-cargo-run.mjs <cargo args>`: one Cargo call of a snapshot stage on a broker host, carried (carried-cargo.mjs) from
|
|
3
|
+
// this process's working directory. build-launch.mjs hands a configured `cargo` or `rustright cargo` step to this entry instead of
|
|
4
|
+
// to the RustRight client, so a config step is carried the same way on every platform (Windows included: no .cmd or shim is involved).
|
|
5
|
+
import { runCargoSync } from "./managed-cargo.mjs";
|
|
6
|
+
|
|
7
|
+
const result = runCargoSync(process.argv.slice(2), { cwd: process.cwd(), env: process.env, stdio: "inherit", windowsHide: true });
|
|
8
|
+
if (result.error) process.stderr.write(`rightkit-gate: ${result.error.message}\n`);
|
|
9
|
+
process.exit(result.status ?? 1);
|