@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/snapshot-check.mjs
CHANGED
|
@@ -22,7 +22,7 @@ import fs from "node:fs";
|
|
|
22
22
|
import path from "node:path";
|
|
23
23
|
import { LIVE_SCAN_IDS, defaultSpawn, isLiveScan, isRegistryScan, launchGuardEnv, planScans, runScans } from "./deterministic-scans.mjs";
|
|
24
24
|
import { compileLaunch } from "./class-a-launch-guard.mjs";
|
|
25
|
-
import { SnapshotError, isInside, snapshotProblem, snapshotTree, treeEntries } from "./snapshot.mjs";
|
|
25
|
+
import { SnapshotError, dependencyDirs, isInside, snapshotProblem, snapshotTree, treeEntries } from "./snapshot.mjs";
|
|
26
26
|
import { discoveryRun, sealedRun } from "./snapshot-git.mjs";
|
|
27
27
|
|
|
28
28
|
export const CHECK_ERROR = "RK_CHECK_ERROR";
|
|
@@ -49,7 +49,8 @@ export function snapshotOption(args, { checkout }) {
|
|
|
49
49
|
const dir = args[i + 1];
|
|
50
50
|
if (!dir || dir.startsWith("--")) throw new CheckError("check --snapshot needs a directory");
|
|
51
51
|
if (!path.isAbsolute(dir)) throw new CheckError(`check --snapshot needs an absolute directory, not ${JSON.stringify(dir)}`);
|
|
52
|
-
|
|
52
|
+
// Full class A (`--full-a`, the door's) or the content checks alone (plain `check --commit`): either way the snapshot is the commit's bytes.
|
|
53
|
+
if (!args.includes("--commit")) throw new CheckError("check --snapshot judges the snapshot of a selected commit: it needs --commit <sha>");
|
|
53
54
|
if (args.includes("--worktree")) throw new CheckError("check --snapshot never checks the working tree: drop --worktree");
|
|
54
55
|
let st;
|
|
55
56
|
try { st = fs.lstatSync(dir); } catch { throw new CheckError(`snapshot ${dir} does not exist`); }
|
|
@@ -197,15 +198,92 @@ export function probeSkippedBuilds({ dir, root, names, spawn = defaultSpawn, env
|
|
|
197
198
|
/**
|
|
198
199
|
* Installs each pnpm root's dependencies into the snapshot from the commit's lockfile and the primary checkout's existing store.
|
|
199
200
|
* Returns one record per root ({ root, storeDir, storeSource, pnpm, lockfileSha256, ms, allowedBuildsNotRun }); throws CheckError on a
|
|
200
|
-
* store miss, a pnpm that is not the pinned one, or any install failure.
|
|
201
|
+
* store miss, a pnpm that is not the pinned one, or any install failure. A failed install invalidates only that root's generated
|
|
202
|
+
* dependency directories: remove them without following links before reporting the original error. Other roots and stores stay warm.
|
|
201
203
|
*/
|
|
202
|
-
export function installSnapshotDependencies({ checkout, snapshot, roots, env = process.env, spawn = defaultSpawn, now = () => Date.now(), readFile, exists, probe = probeSkippedBuilds }) {
|
|
204
|
+
export function installSnapshotDependencies({ checkout, snapshot, commit, tree, ledger, roots, packageDirs, env = process.env, spawn = defaultSpawn, now = () => Date.now(), readFile, exists, probe = probeSkippedBuilds }) {
|
|
203
205
|
const records = [];
|
|
206
|
+
// Cleanup authority comes from the selected commit, never a path supplied by pnpm or an untracked manifest. Verify before any
|
|
207
|
+
// installer runs; a preflight failure has generated nothing and must not remove existing dependencies.
|
|
208
|
+
if (!commit || !tree) throw new CheckError("snapshot dependency install needs the selected commit and tree");
|
|
209
|
+
const base = path.resolve(snapshot);
|
|
210
|
+
try {
|
|
211
|
+
const st = fs.lstatSync(base);
|
|
212
|
+
if (!st.isDirectory() || fs.realpathSync.native(base) !== base) throw new Error("not a real directory");
|
|
213
|
+
} catch (error) { throw new CheckError(`snapshot dependency install needs a real snapshot directory: ${snapshot}: ${error.message}`); }
|
|
214
|
+
if (inside(base, checkout) || inside(checkout, base)) throw new CheckError(`snapshot ${base} and checkout ${checkout} overlap`);
|
|
215
|
+
verifySnapshotDir({ checkout, dir: base, commit, tree, when: "before dependency installation", ...(ledger !== undefined ? { ledger } : {}) });
|
|
216
|
+
const entries = treeEntries(checkout, commit);
|
|
217
|
+
const fold = (s) => process.platform === "darwin" || process.platform === "win32" ? s.toLowerCase() : s;
|
|
218
|
+
const dirOf = (p) => p.includes("/") ? p.slice(0, p.lastIndexOf("/")) : "";
|
|
219
|
+
const lockRoots = entries.filter((e) => e.path === "pnpm-lock.yaml" || e.path.endsWith("/pnpm-lock.yaml")).map((e) => fold(dirOf(e.path))).filter((p) => !p.split("/").includes("node_modules"));
|
|
220
|
+
// The verifier folds names on Mac/Windows. Filesystem operations retain committed spelling even on a case-sensitive Mac volume.
|
|
221
|
+
const generated = [...dependencyDirs(entries, "linux")];
|
|
222
|
+
const generatedFolded = new Set(generated.map(fold));
|
|
223
|
+
const nearestLock = (member) => lockRoots.filter((r) => !r || member === r || member.startsWith(`${r}/`)).sort((a, b) => b.length - a.length)[0];
|
|
224
|
+
const selectedByRoot = new Map();
|
|
225
|
+
if (packageDirs !== undefined) {
|
|
226
|
+
if (!Array.isArray(packageDirs)) throw new CheckError("snapshot dependency packageDirs must be an array of committed manifest directories");
|
|
227
|
+
const manifests = new Map(entries.filter((e) => e.path === "package.json" || e.path.endsWith("/package.json")).map((e) => [fold(dirOf(e.path)), dirOf(e.path)]));
|
|
228
|
+
const installRoots = new Set(roots.map((r) => fold(path.relative(base, path.resolve(base, r)).split(path.sep).join("/"))));
|
|
229
|
+
for (const selected of packageDirs) {
|
|
230
|
+
if (typeof selected !== "string" || path.isAbsolute(selected) || !inside(path.resolve(base, selected), base)) throw new CheckError(`dependency package ${String(selected)} is not relative to the snapshot`);
|
|
231
|
+
const relative = path.relative(base, path.resolve(base, selected)).split(path.sep).join("/");
|
|
232
|
+
const actual = manifests.get(fold(relative));
|
|
233
|
+
const owner = nearestLock(fold(relative));
|
|
234
|
+
if (actual === undefined || owner === undefined || !installRoots.has(owner) || /[*?\[\]{}!^()\\]/.test(actual) || actual.includes("...")) throw new CheckError(`dependency package ${selected} is not an exact committed manifest directory owned by an install root`);
|
|
235
|
+
if (!selectedByRoot.has(owner)) selectedByRoot.set(owner, new Set());
|
|
236
|
+
selectedByRoot.get(owner).add(actual);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
const cleanupFor = (root) => {
|
|
240
|
+
const relative = path.relative(base, path.resolve(base, root)).split(path.sep).join("/");
|
|
241
|
+
const owner = fold(relative);
|
|
242
|
+
if (!inside(path.resolve(base, root), base) || !lockRoots.includes(owner) || !generatedFolded.has(owner ? `${owner}/node_modules` : "node_modules")) throw new CheckError(`root ${root}: not a committed pnpm install root inside the snapshot`);
|
|
243
|
+
const dirs = generated.filter((d) => {
|
|
244
|
+
const member = fold(dirOf(d));
|
|
245
|
+
return nearestLock(member) === owner;
|
|
246
|
+
});
|
|
247
|
+
for (const d of dirs) {
|
|
248
|
+
if (entries.some((e) => fold(e.path) === fold(d) || fold(e.path).startsWith(`${fold(d)}/`))) throw new CheckError(`root ${root}: cannot install over committed dependency path ${d}`);
|
|
249
|
+
}
|
|
250
|
+
// Capture physical source parents before pnpm starts. During cleanup check them from the snapshot down, never traversing a
|
|
251
|
+
// replaced parent or a junction. rmSync unlinks a dependency-directory symlink itself and never follows links within a tree.
|
|
252
|
+
const parents = new Map();
|
|
253
|
+
for (const d of dirs) {
|
|
254
|
+
const parts = d.split("/").slice(0, -1);
|
|
255
|
+
for (let i = 0; i <= parts.length; i++) {
|
|
256
|
+
const p = path.join(base, ...parts.slice(0, i));
|
|
257
|
+
if (!parents.has(p)) {
|
|
258
|
+
let stat;
|
|
259
|
+
try { stat = fs.lstatSync(p); }
|
|
260
|
+
catch (error) { throw new CheckError(`root ${root}: cannot inspect dependency parent ${p}: ${error.message}`); }
|
|
261
|
+
if (!stat.isDirectory()) throw new CheckError(`root ${root}: dependency parent is not a real directory: ${p}`);
|
|
262
|
+
parents.set(p, stat);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return () => {
|
|
267
|
+
for (const [p, before] of parents) {
|
|
268
|
+
const after = fs.lstatSync(p);
|
|
269
|
+
if (!after.isDirectory() || after.dev !== before.dev || after.ino !== before.ino) throw new Error(`dependency parent changed: ${p}`);
|
|
270
|
+
}
|
|
271
|
+
for (const d of dirs) fs.rmSync(path.join(base, ...d.split("/")), { recursive: true, force: true });
|
|
272
|
+
verifySnapshotDir({ checkout, dir: base, commit, tree, when: "after failed dependency installation cleanup", ...(ledger !== undefined ? { ledger } : {}) });
|
|
273
|
+
};
|
|
274
|
+
};
|
|
204
275
|
const versionOf = (cwd) => { const r = spawn("pnpm", ["--version"], { cwd, env: { ...env, COREPACK_ENABLE_AUTO_PIN: "0" } }); return failed(r) ? null : text(r).trim().split(/\r?\n/).pop(); };
|
|
205
276
|
for (const root of roots) {
|
|
206
|
-
const dir = path.
|
|
277
|
+
const dir = path.resolve(base, root);
|
|
278
|
+
if (!inside(dir, base)) throw new CheckError(`root ${root}: dependency install is outside the snapshot`);
|
|
207
279
|
// No lockfile: nothing to install from. Not an error here: the pnpm-lockfile scan judges it, and checks that need dependencies fail.
|
|
208
280
|
if (!fs.existsSync(path.join(dir, "pnpm-lock.yaml"))) { records.push({ root, skipped: "no pnpm-lock.yaml in the commit: nothing to install from (the pnpm-lockfile scan judges it)" }); continue; }
|
|
281
|
+
const cleanup = cleanupFor(root);
|
|
282
|
+
const owner = fold(path.relative(base, dir).split(path.sep).join("/"));
|
|
283
|
+
const selected = selectedByRoot.get(owner);
|
|
284
|
+
if (packageDirs !== undefined && !selected?.size) throw new CheckError(`root ${root}: no dependency packages selected; refusing an unfiltered install`);
|
|
285
|
+
const filters = packageDirs === undefined ? [] : [...selected].sort().flatMap((p) => ["--filter", `./${path.relative(dir, path.join(base, p)).split(path.sep).join("/")}`]);
|
|
286
|
+
if (packageDirs !== undefined) filters.push("--fail-if-no-match", "--config.legacy-dir-filtering=false", "--config.include-workspace-root=false");
|
|
209
287
|
const { storeDir, source } = resolvePrimaryStore({ checkout, root, env, spawn, ...(readFile ? { readFile } : {}), ...(exists ? { exists } : {}) });
|
|
210
288
|
const pin = pinnedPnpm(dir) ?? pinnedPnpm(snapshot);
|
|
211
289
|
// Asked where the install runs: pnpm switches itself to the root's packageManager version there (Dell 2026-10-29: an installed
|
|
@@ -216,15 +294,21 @@ export function installSnapshotDependencies({ checkout, snapshot, roots, env = p
|
|
|
216
294
|
const started = now();
|
|
217
295
|
// The virtual store is pinned inside the snapshot and the global virtual store off, whatever the host's pnpm config says: the
|
|
218
296
|
// install writes nowhere but the snapshot (links to the existing store's content are the only reads outside it).
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
297
|
+
let r;
|
|
298
|
+
try {
|
|
299
|
+
r = spawn("pnpm", [...filters, "install", "--offline", "--frozen-lockfile", "--ignore-scripts", "--ignore-pnpmfile", "--store-dir", storeDir, "--virtual-store-dir", "node_modules/.pnpm", "--config.enable-global-virtual-store=false", "--reporter", "append-only"], {
|
|
300
|
+
cwd: dir, env: { ...env, COREPACK_ENABLE_AUTO_PIN: "0", CI: "true" }, maxBuffer: 1 << 28,
|
|
301
|
+
});
|
|
302
|
+
} catch (error) { r = { error }; }
|
|
222
303
|
const ms = now() - started;
|
|
223
|
-
if (failed(r)) {
|
|
304
|
+
if (failed(r) || /No projects matched the filters/.test(`${r?.stdout ?? ""}\n${r?.stderr ?? ""}`)) {
|
|
224
305
|
const out = `${r?.stdout ?? ""}\n${r?.stderr ?? ""}`;
|
|
225
306
|
const miss = /ERR_PNPM_NO_OFFLINE_TARBALL[^\n]*?(https?:\/\/\S+?\/-\/(\S+?)\.tgz)/.exec(out);
|
|
226
|
-
|
|
227
|
-
|
|
307
|
+
const message = miss
|
|
308
|
+
? `root ${root}: ${miss[2]} is missing from the store ${storeDir} (offline install, no network fallback); install the checkout's dependencies so the store holds it, then run the check again`
|
|
309
|
+
: `root ${root}: offline dependency install failed (store ${storeDir}): ${tail(r) || r?.error?.message || `exit ${r?.status}`}`;
|
|
310
|
+
try { cleanup(); } catch (error) { throw new CheckError(`${message}; dependency cleanup failed: ${error.message}`); }
|
|
311
|
+
throw new CheckError(`${message}; invalidated generated dependency output for owning lockroot ${root}`);
|
|
228
312
|
}
|
|
229
313
|
const lockfileSha256 = crypto.createHash("sha256").update(fs.readFileSync(path.join(dir, "pnpm-lock.yaml"))).digest("hex");
|
|
230
314
|
const skipped = probe ? probe({ dir, root, names: allowedBuilds(dir), spawn, env }) : [];
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// The gate's own read-only Cargo calls inside a managed snapshot on a broker host (rightkit#1311).
|
|
2
|
+
//
|
|
3
|
+
// RustRight refuses a ROOT request whose workspace is inside a managed snapshot ("a root request cannot run inside a managed snapshot",
|
|
4
|
+
// rustright job_service/service.rs). The gate reads the snapshot's Cargo metadata to discover units, and every Class A Cargo scan of a
|
|
5
|
+
// snapshot (metadata, fetch, cargo-deny, cargo-audit) reads the snapshot too, never the primary checkout (docs/GATE-CONTRACT.md: no
|
|
6
|
+
// dirty-tree condition; class A for a build reads the snapshot). So on a broker host each such call is a CARRIED read-only request: the
|
|
7
|
+
// same carrier, nonce, signed-receipt verification and ownership mark as every other carried call (snapshot-broker.mjs carryBrokerCargo).
|
|
8
|
+
// The broker admits a carried call that compiles nothing only for the exact shapes below (rustright_policy::command_policy::
|
|
9
|
+
// snapshot_read_only). This module carries exactly those and refuses every other call inside the snapshot with RK_SNAPSHOT_CARRIED_REFUSED,
|
|
10
|
+
// never running it as a root request.
|
|
11
|
+
//
|
|
12
|
+
// cargo metadata [--no-deps] [--locked] [--frozen] [--offline] [--format-version 1] (with dependencies: --locked or --frozen)
|
|
13
|
+
// cargo fetch --locked
|
|
14
|
+
// cargo deny [--frozen] [--metadata-path <file>] [--config <file>] check (each flag at most once, spaced form, nothing after `check`)
|
|
15
|
+
// cargo audit --db <dir> --no-fetch
|
|
16
|
+
// cargo fmt -p <unit> --check | cargo fmt --all --check (command_policy.rs fmt_check_shape; check only, nothing after)
|
|
17
|
+
//
|
|
18
|
+
// Cargo's own `--config` and `-Z`, a `+toolchain`, `--manifest-path`, a `--` and every other word never ride along. The files deny reads and
|
|
19
|
+
// the database directory audit reads are absolute, exist (a file; a directory) and lie outside the live checkout.
|
|
20
|
+
import fs from "node:fs";
|
|
21
|
+
import path from "node:path";
|
|
22
|
+
import { BROKER_REFUSAL_STATUS, brokerSection, carryBrokerCargo } from "./snapshot-broker.mjs";
|
|
23
|
+
|
|
24
|
+
export const CARRIED_METADATA_REFUSED = "RK_SNAPSHOT_CARRIED_REFUSED";
|
|
25
|
+
|
|
26
|
+
const real = (p) => { try { return fs.realpathSync.native(p); } catch { return path.resolve(p); } };
|
|
27
|
+
const within = (child, parent) => { const rel = path.relative(parent, child); return rel === "" || !(rel === ".." || rel.startsWith(`..${path.sep}`) || path.isAbsolute(rel)); };
|
|
28
|
+
|
|
29
|
+
/** The flags of `rest` as a Map (flag to value, or true); null when one repeats, is unknown (`--flag=value` included) or lacks its value. */
|
|
30
|
+
function flagsOf(rest, spec) {
|
|
31
|
+
const found = new Map();
|
|
32
|
+
for (let i = 0; i < rest.length; i += 1) {
|
|
33
|
+
const w = rest[i];
|
|
34
|
+
if (!Object.hasOwn(spec, w) || found.has(w)) return null;
|
|
35
|
+
if (spec[w] === "value") {
|
|
36
|
+
const v = rest[i + 1];
|
|
37
|
+
if (typeof v !== "string" || v === "" || v.startsWith("-")) return null;
|
|
38
|
+
found.set(w, v); i += 1;
|
|
39
|
+
} else found.set(w, true);
|
|
40
|
+
}
|
|
41
|
+
return found;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Why the broker would not admit `words` (cargo's argv without `cargo`) as a carried read-only request, or null when it does. Without
|
|
46
|
+
* `checkout` only the shape (and that the named paths are absolute) is judged; with it (the live primary checkout) the files the call
|
|
47
|
+
* names must also exist, be of the right kind, and lie outside that checkout.
|
|
48
|
+
*/
|
|
49
|
+
export function carriedScanRefusal(words, { checkout = null } = {}) {
|
|
50
|
+
if (!Array.isArray(words) || !words.every((w) => typeof w === "string")) return "the argv is not a list of words";
|
|
51
|
+
const [sub, ...rest] = words;
|
|
52
|
+
const pathProblem = (flag, value, kind) => {
|
|
53
|
+
if (!path.isAbsolute(value)) return `${flag} ${value} is not absolute`;
|
|
54
|
+
if (checkout === null) return null;
|
|
55
|
+
let stat; try { stat = fs.statSync(value); } catch { return `${flag} ${value} does not exist`; }
|
|
56
|
+
if (kind === "file" ? !stat.isFile() : !stat.isDirectory()) return `${flag} ${value} is not ${kind === "file" ? "a file" : "a directory"}`;
|
|
57
|
+
return within(real(value), real(checkout)) ? `${flag} ${value} is inside the live checkout ${checkout}` : null;
|
|
58
|
+
};
|
|
59
|
+
if (sub === "metadata") {
|
|
60
|
+
const f = flagsOf(rest, { "--no-deps": "flag", "--locked": "flag", "--frozen": "flag", "--offline": "flag", "--format-version": "value" });
|
|
61
|
+
if (f === null) return "`metadata` takes only --no-deps, --locked, --frozen, --offline and --format-version 1, each once";
|
|
62
|
+
if (f.has("--format-version") && f.get("--format-version") !== "1") return "`metadata` takes --format-version 1 only";
|
|
63
|
+
if (!f.has("--no-deps") && !f.has("--locked") && !f.has("--frozen")) return "`metadata` with dependencies must be --locked or --frozen";
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
if (sub === "fetch") return rest.length === 1 && rest[0] === "--locked" ? null : "`fetch` is admitted as `fetch --locked` only";
|
|
67
|
+
if (sub === "deny") {
|
|
68
|
+
if (rest.at(-1) !== "check") return "`deny` must end with `check`, with nothing after it";
|
|
69
|
+
const f = flagsOf(rest.slice(0, -1), { "--frozen": "flag", "--metadata-path": "value", "--config": "value" });
|
|
70
|
+
if (f === null) return "`deny` takes only --frozen, --metadata-path <file> and --config <file> before `check`, each once, spaced form";
|
|
71
|
+
for (const flag of ["--metadata-path", "--config"]) if (f.has(flag)) { const problem = pathProblem(flag, f.get(flag), "file"); if (problem) return problem; }
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
// rustright command_policy.rs fmt_check_shape: exactly `-p <unit> --check` or `--all --check`; no `--`, so no rustfmt argument reaches it.
|
|
75
|
+
if (sub === "fmt") {
|
|
76
|
+
const shapeOk = (rest.length === 3 && rest[0] === "-p" && rest[1] !== "" && !rest[1].startsWith("-") && rest[2] === "--check") || (rest.length === 2 && rest[0] === "--all" && rest[1] === "--check");
|
|
77
|
+
return shapeOk ? null : "`fmt` is admitted as `fmt -p <unit> --check` or `fmt --all --check` only";
|
|
78
|
+
}
|
|
79
|
+
if (sub === "audit") {
|
|
80
|
+
const f = flagsOf(rest, { "--db": "value", "--no-fetch": "flag" });
|
|
81
|
+
if (f === null || f.size !== 2 || !f.has("--db") || !f.has("--no-fetch")) return "`audit` is admitted as `audit --db <dir> --no-fetch` only";
|
|
82
|
+
return pathProblem("--db", f.get("--db"), "directory");
|
|
83
|
+
}
|
|
84
|
+
// rustright command_policy.rs:407-409: class_a_scan "package" -> observation_only_cargo. Exactly: -p <unit> --list --locked [--allow-dirty].
|
|
85
|
+
if (sub === "package") {
|
|
86
|
+
const unit = rest[1];
|
|
87
|
+
// `-p <unit>` first, then --list and --locked and optionally --allow-dirty, each once in either order (the gate plans `--allow-dirty --locked`).
|
|
88
|
+
const tail = rest.slice(2);
|
|
89
|
+
const shapeOk = rest[0] === "-p" && typeof unit === "string" && unit !== "" && !unit.startsWith("-") && new Set(tail).size === tail.length && tail.every((w) => ["--list", "--locked", "--allow-dirty"].includes(w)) && tail.includes("--list") && tail.includes("--locked");
|
|
90
|
+
return shapeOk ? null : "`package` is admitted as `package -p <unit> --list --locked [--allow-dirty]` only";
|
|
91
|
+
}
|
|
92
|
+
return `\`cargo ${words.join(" ")}\` is not one of the carried read-only shapes (metadata, fetch --locked, deny, audit --db --no-fetch, fmt --check, package -p <unit> --list --locked)`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Whether the broker admits `words` as a carried read-only request (see carriedScanRefusal). */
|
|
96
|
+
export const carriedReadOnlyScan = (words, options) => carriedScanRefusal(words, options) === null;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* A `spawn(cmd, args, opts)` for the gate's Cargo calls in `snapshotRoot` on a broker host. A call to `cargoCommand` whose cwd is inside the
|
|
100
|
+
* snapshot becomes one carried read-only request (capture mode, a spawnSync-shaped result; `status` is non-zero when the call was refused or
|
|
101
|
+
* its signed receipt did not verify). Any other call passes to `spawn` unchanged (git, and Cargo in the primary checkout).
|
|
102
|
+
* `broker()` returns the gate's snapshot broker (built once); `records` collects each call's ledger record. `carrySpawn` (default `spawn`) is
|
|
103
|
+
* the synchronous spawn that starts the RustRight client and reads its receipt; `spawn` may be asynchronous for the pass-through calls.
|
|
104
|
+
*/
|
|
105
|
+
export function snapshotCargoSpawn({ spawn, carrySpawn = spawn, cargoCommand, snapshotRoot, broker, env, err = () => {}, records = [] }) {
|
|
106
|
+
const prefix = cargoCommand.slice(1);
|
|
107
|
+
const refused = (code, message) => { err(`rightkit-gate: ${code}: ${message}`); return { status: BROKER_REFUSAL_STATUS, signal: null, error: null, stdout: "", stderr: `rightkit-gate: ${code}: ${message}\n` }; };
|
|
108
|
+
return (cmd, args, opts = {}) => {
|
|
109
|
+
const cargo = cmd === cargoCommand[0] && prefix.every((w, i) => args[i] === w);
|
|
110
|
+
if (!cargo || typeof opts.cwd !== "string" || !within(real(opts.cwd), real(snapshotRoot))) return spawn(cmd, args, opts);
|
|
111
|
+
const words = args.slice(prefix.length);
|
|
112
|
+
const shape = carriedScanRefusal(words);
|
|
113
|
+
if (shape !== null) return refused(CARRIED_METADATA_REFUSED, `\`cargo ${words.join(" ")}\` would run inside the managed snapshot ${snapshotRoot}, and the broker admits only the carried read-only shapes there (${shape}); it is never sent as a root request`);
|
|
114
|
+
try {
|
|
115
|
+
const sb = broker();
|
|
116
|
+
// The named files are checked against the live checkout before anything is sent (absolute, existing, of the right kind, outside it).
|
|
117
|
+
const placed = carriedScanRefusal(words, { checkout: sb.checkout });
|
|
118
|
+
if (placed !== null) return refused(CARRIED_METADATA_REFUSED, `\`cargo ${words.join(" ")}\` names a path the broker would not admit (${placed}); it is not sent`);
|
|
119
|
+
const r = carryBrokerCargo({ words, cwd: opts.cwd, env: { ...env, ...(opts.env ?? {}) }, section: brokerSection(sb), workspace: real(snapshotRoot), checkoutWorkspace: sb.checkout, record: (e) => records.push(e), spawn: carrySpawn, err, stdio: "capture" });
|
|
120
|
+
return { status: r.status, signal: null, error: null, stdout: r.stdout, stderr: r.stderr };
|
|
121
|
+
} catch (e) { return refused(e.code ?? "RK_SNAPSHOT_BROKER", e.message); }
|
|
122
|
+
};
|
|
123
|
+
}
|