@rightkit/release 0.6.7 → 0.6.10

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 (53) hide show
  1. package/README.md +35 -10
  2. package/admission-files.mjs +1 -1
  3. package/app-check.mjs +94 -20
  4. package/assemble-app-command.mjs +151 -0
  5. package/assembled-app.mjs +445 -0
  6. package/bin/darwin/rightkit-swap +0 -0
  7. package/bin/darwin/rightkit-swap.sign.json +6 -0
  8. package/bin/rightkit-gate.mjs +957 -83
  9. package/build-launch.mjs +88 -0
  10. package/build-ownership.mjs +1231 -0
  11. package/build-release.mjs +2 -1
  12. package/cargo-contract.mjs +3 -1
  13. package/cargo-target-shim.mjs +166 -0
  14. package/class-a-launch-guard.mjs +126 -0
  15. package/cli/right-release.mjs +3 -1
  16. package/codesign-shim.mjs +11 -1
  17. package/deterministic-scans.mjs +807 -56
  18. package/development-signing.mjs +4 -1
  19. package/development.mjs +110 -20
  20. package/door-policy.mjs +61 -6
  21. package/door.mjs +23 -0
  22. package/gate-executor.mjs +14 -2
  23. package/gate-stages.mjs +15 -5
  24. package/heavy-command.mjs +3 -2
  25. package/install-app.mjs +11 -2
  26. package/install-assembled-app.mjs +423 -0
  27. package/ios-dev.mjs +70 -3
  28. package/linux-artifacts.mjs +93 -0
  29. package/local-publish.mjs +161 -26
  30. package/managed-cargo.mjs +47 -5
  31. package/managed-root-guard.mjs +34 -0
  32. package/native-helpers.mjs +355 -0
  33. package/package-recipe-targets.mjs +52 -7
  34. package/package.json +5 -3
  35. package/pipeline-receipts.mjs +302 -302
  36. package/project-type.mjs +2 -2
  37. package/provenance-verify.mjs +115 -0
  38. package/publish-cargo.mjs +8 -6
  39. package/qa-admission-policy.json +10 -1
  40. package/recipe-build.mjs +73 -5
  41. package/release-config.mjs +1 -1
  42. package/release.mjs +15 -5
  43. package/rightkit-versions.json +13 -7
  44. package/snapshot-check.mjs +306 -0
  45. package/snapshot-git.mjs +166 -0
  46. package/snapshot-request.mjs +161 -0
  47. package/snapshot-root-config.mjs +76 -0
  48. package/snapshot.mjs +856 -0
  49. package/stage-commands.mjs +60 -0
  50. package/stage-sidecar.mjs +57 -7
  51. package/test-command.mjs +17 -4
  52. package/unit-gate.mjs +259 -0
  53. package/web-build.mjs +38 -6
@@ -0,0 +1,306 @@
1
+ // Snapshot-aware Class A (joint plan step 2; docs/GATE-CONTRACT.md §1 "Broker interface", E17, E18; rightkit#1257).
2
+ //
3
+ // `rightkit-gate check --commit <sha> --full-a --snapshot <dir>` judges the gate-owned snapshot of a selected commit: the checkout
4
+ // is only where the commit and its objects come from (cwd), never what is checked. Its HEAD and status are not read. The snapshot is
5
+ // verified to be exactly the commit's tree (seeded identity, snapshot.mjs) before and after the check; a mismatch or an error is
6
+ // RK_CHECK_ERROR, exit 2.
7
+ //
8
+ // Dependencies for the checks that execute repository code (tests, typecheck, lint) are installed INTO the snapshot from the commit's
9
+ // lockfile (coordinator decision A, recorded in decisions.md with rightkit#1257): the pinned pnpm, `--offline --frozen-lockfile
10
+ // --ignore-scripts --ignore-pnpmfile`, and the store the primary checkout's own install recorded (its node_modules/.modules.yaml
11
+ // storeDir), resolved before any cwd change. No new store, no network, no code of the live checkout. A store miss is RK_CHECK_ERROR
12
+ // naming the package and root, never a skipped check. The next exact sync prunes this install.
13
+ //
14
+ // `rightkit-gate live-scans --repo <root>` re-runs only the live remote scans (pnpm-audit, per pnpm root) and answers the broker's
15
+ // parser (rustright verdict_reuse.rs parse_live_scans): one JSON line, exit 0 when every scan passed, 1 otherwise.
16
+ //
17
+ // A red full Class A whose every failure is deterministic prints `rightkit-gate: verdict deterministic failed=<ids> env=<digest>`;
18
+ // a timeout, a missing tool, a fetch or lease problem, a cancellation, a deferral, or any live scan among the failures prints none.
19
+
20
+ import crypto from "node:crypto";
21
+ import fs from "node:fs";
22
+ import path from "node:path";
23
+ import { LIVE_SCAN_IDS, defaultSpawn, isLiveScan, isRegistryScan, launchGuardEnv, planScans, runScans } from "./deterministic-scans.mjs";
24
+ import { compileLaunch } from "./class-a-launch-guard.mjs";
25
+ import { SnapshotError, isInside, snapshotProblem, snapshotTree, treeEntries } from "./snapshot.mjs";
26
+ import { discoveryRun, sealedRun } from "./snapshot-git.mjs";
27
+
28
+ export const CHECK_ERROR = "RK_CHECK_ERROR";
29
+ export const LIVE_SCANS_SCHEMA = "rightkit.live-scans/1";
30
+ export const DETERMINISTIC_MARKER = "rightkit-gate: verdict deterministic";
31
+
32
+ /** Failure codes a re-run of the same inputs reproduces: an exit of the check itself, its own findings, a test summary problem. */
33
+ const DETERMINISTIC_CODES = new Set(["EXIT", "RUN_SCRIPT_TOO_LARGE", "NO_TESTS", "ALL_SKIPPED", "SUMMARY_FAILED", "NO_TEST_SUMMARY"]);
34
+
35
+ export class CheckError extends Error {
36
+ constructor(message) { super(message); this.code = CHECK_ERROR; }
37
+ }
38
+
39
+ const text = (r) => String(r?.stdout ?? "");
40
+ const failed = (r) => !r || Boolean(r.error) || r.status !== 0;
41
+ const tail = (r) => `${r?.stderr ?? ""}\n${r?.stdout ?? ""}`.trim().split(/\r?\n/).slice(-6).join(" | ").slice(-600);
42
+ // A path is outside only as `..`, `..<sep>…` or absolute: a sibling named `..x` is inside (snapshot.mjs isInside; review item5-r2 #4).
43
+ const inside = (child, parent) => isInside(child, parent, path);
44
+
45
+ /** The `--snapshot <dir>` of a check: absolute, an existing real directory, not inside the checkout and not holding it. */
46
+ export function snapshotOption(args, { checkout }) {
47
+ const i = args.indexOf("--snapshot");
48
+ if (i < 0) return null;
49
+ const dir = args[i + 1];
50
+ if (!dir || dir.startsWith("--")) throw new CheckError("check --snapshot needs a directory");
51
+ if (!path.isAbsolute(dir)) throw new CheckError(`check --snapshot needs an absolute directory, not ${JSON.stringify(dir)}`);
52
+ if (!args.includes("--full-a") || !args.includes("--commit")) throw new CheckError("check --snapshot is full class A of a selected commit: it needs --commit <sha> and --full-a");
53
+ if (args.includes("--worktree")) throw new CheckError("check --snapshot never checks the working tree: drop --worktree");
54
+ let st;
55
+ try { st = fs.lstatSync(dir); } catch { throw new CheckError(`snapshot ${dir} does not exist`); }
56
+ if (!st.isDirectory()) throw new CheckError(`snapshot ${dir} is not a directory (a link is never followed)`);
57
+ const real = fs.realpathSync.native(dir);
58
+ if (inside(real, checkout) || inside(checkout, real)) throw new CheckError(`snapshot ${real} and checkout ${checkout} overlap`);
59
+ return real;
60
+ }
61
+
62
+ /**
63
+ * `{ commit, tree }` of a revision in the checkout's object database. The sealed invocation (snapshot-git.mjs, item 5 D3): the
64
+ * revision is resolved under the sealed environment with replace refs off, so a replace ref cannot change what <sha>^{tree} means.
65
+ * `spawn` is accepted for the callers that still pass one and is not used: the gate's identity git never goes through an injected runner.
66
+ */
67
+ export function commitOf(checkout, rev, spawn = defaultSpawn) { // eslint-disable-line no-unused-vars
68
+ const name = String(rev ?? "");
69
+ if (!name || name.startsWith("-") || name.includes("\0")) throw new CheckError(`commit ${name} does not resolve in ${checkout}`);
70
+ const c = discoveryRun(checkout, ["rev-parse", "--verify", "--quiet", `${name}^{commit}`]);
71
+ if (failed(c)) throw new CheckError(`commit ${name} does not resolve in ${checkout}`);
72
+ const commit = text(c).trim();
73
+ const t = discoveryRun(checkout, ["rev-parse", "--verify", "--quiet", `${commit}^{tree}`]);
74
+ if (failed(t)) throw new CheckError(`commit ${commit} has no tree`);
75
+ return { commit, tree: text(t).trim() };
76
+ }
77
+
78
+ /**
79
+ * The snapshot holds exactly the commit (E6, GATE-CONTRACT §1): no path under it the commit's tree lacks (ignored ones included; the
80
+ * only allowances are snapshot.mjs's named pnpm dependency directories and the gate's config-only .git/config), no link resolving out
81
+ * of it or into the checkout, and the seeded tree id (the broker's method) equal to the commit's tree. Otherwise CheckError.
82
+ */
83
+ export function verifySnapshotDir({ checkout, dir, commit, tree, when }) {
84
+ let extra;
85
+ try { extra = snapshotProblem({ dir, entries: treeEntries(checkout, commit), checkout }); }
86
+ catch (e) { throw new CheckError(`cannot list snapshot ${dir} ${when}: ${e.message}`); }
87
+ if (extra) throw new CheckError(`snapshot ${dir} is not exactly commit ${commit} ${when}: ${extra}`);
88
+ let actual;
89
+ try { actual = snapshotTree({ checkout, dir, baseTree: tree }); }
90
+ catch (e) { throw new CheckError(`cannot compute the tree of snapshot ${dir} ${when}: ${e.message}`); }
91
+ if (actual !== tree) throw new CheckError(`snapshot ${dir} is tree ${actual} ${when}, not tree ${tree} of commit ${commit}`);
92
+ return actual;
93
+ }
94
+
95
+ /** Every file of the commit with its blob id, from the checkout's object database: what the snapshot holds. */
96
+ export function commitFiles(checkout, commit, spawn = defaultSpawn) { // eslint-disable-line no-unused-vars
97
+ const r = sealedRun(checkout, ["ls-tree", "-r", "-z", "--full-tree", commit]);
98
+ if (failed(r)) throw new CheckError(`cannot list commit ${commit}: ${tail(r)}`);
99
+ const ids = new Map();
100
+ for (const record of text(r).split("\0").filter(Boolean)) {
101
+ const tab = record.indexOf("\t");
102
+ ids.set(record.slice(tab + 1), record.slice(0, tab).split(" ")[2]);
103
+ }
104
+ const files = [...ids.keys()].sort();
105
+ const digestOf = (list) => crypto.createHash("sha256").update(list.map((f) => `${f}\t${ids.get(f)}\n`).join("")).digest("hex");
106
+ return { files, ids, digestOf, digest: digestOf(files) };
107
+ }
108
+
109
+ /** `storeDir` from a node_modules/.modules.yaml: pnpm writes `"storeDir": "<JSON string>"` (Windows paths escaped, `D:\\x`) or `storeDir: <abs>`. */
110
+ export function modulesStoreDir(yamlText) {
111
+ const src = String(yamlText ?? "");
112
+ const quoted = /^\s*"?storeDir"?\s*:\s*("(?:[^"\\\n]|\\.)*")\s*,?\s*$/m.exec(src);
113
+ if (quoted) { try { return JSON.parse(quoted[1]); } catch { return null; } }
114
+ const bare = /^\s*storeDir\s*:\s*([^"'\s][^\n]*?)\s*$/m.exec(src);
115
+ return bare ? bare[1].trim() : null;
116
+ }
117
+
118
+ /**
119
+ * The existing warm store a pnpm root of the PRIMARY checkout installs from: the storeDir its own install recorded (the root's
120
+ * node_modules/.modules.yaml, else the repository root's), else what `pnpm store path` answers in that root with the primary
121
+ * environment. It must be an absolute directory that exists; nothing is created.
122
+ */
123
+ export function resolvePrimaryStore({ checkout, root = ".", env = process.env, spawn = defaultSpawn, readFile = (f) => fs.readFileSync(f, "utf8"), exists = fs.existsSync }) {
124
+ const candidates = [...new Set([path.join(checkout, root, "node_modules", ".modules.yaml"), path.join(checkout, "node_modules", ".modules.yaml")])];
125
+ for (const file of candidates) {
126
+ let dir = null;
127
+ try { dir = modulesStoreDir(readFile(file)); } catch { dir = null; }
128
+ if (dir && path.isAbsolute(dir) && exists(dir)) return { storeDir: dir, source: path.relative(checkout, file).split(path.sep).join("/") };
129
+ }
130
+ const r = spawn("pnpm", ["store", "path"], { cwd: path.join(checkout, root), env: { ...env, COREPACK_ENABLE_AUTO_PIN: "0" } });
131
+ const dir = failed(r) ? null : text(r).trim().split(/\r?\n/).pop();
132
+ if (dir && path.isAbsolute(dir) && exists(dir)) return { storeDir: dir, source: "pnpm store path (primary checkout)" };
133
+ throw new CheckError(`no existing pnpm store for root ${root} of ${checkout}: its install recorded none and \`pnpm store path\` answered ${dir ? JSON.stringify(dir) : tail(r) || "nothing"}; install the checkout's dependencies once, then run the check again`);
134
+ }
135
+
136
+ /** The pnpm version a root pins (`packageManager: pnpm@x.y.z`, its own package.json or the repository root's), or null. */
137
+ export function pinnedPnpm(dir, { readFile = (f) => fs.readFileSync(f, "utf8") } = {}) {
138
+ try { const pm = JSON.parse(readFile(path.join(dir, "package.json"))).packageManager; const m = /^pnpm@(\d+\.\d+\.\d+)/.exec(String(pm ?? "")); return m ? m[1] : null; } catch { return null; }
139
+ }
140
+
141
+ /** Dependencies whose install scripts the repository allows (pnpm-workspace.yaml `allowBuilds: { name: true }`, `onlyBuiltDependencies`). */
142
+ export function allowedBuilds(dir, { readFile = (f) => fs.readFileSync(f, "utf8") } = {}) {
143
+ const names = new Set();
144
+ let ws = "";
145
+ try { ws = readFile(path.join(dir, "pnpm-workspace.yaml")); } catch { ws = ""; }
146
+ const block = (key) => { const m = new RegExp(`^${key}:\\s*\\n((?:[ \\t]+.*\\n?|\\s*#.*\\n?|\\s*\\n)*)`, "m").exec(ws); return m ? m[1] : ""; };
147
+ for (const line of block("allowBuilds").split("\n")) { const m = /^\s+['"]?([^'":#\s]+)['"]?\s*:\s*true\b/.exec(line); if (m) names.add(m[1]); }
148
+ for (const line of block("onlyBuiltDependencies").split("\n")) { const m = /^\s+-\s+['"]?([^'"#\s]+)['"]?/.exec(line); if (m) names.add(m[1]); }
149
+ try { for (const n of JSON.parse(readFile(path.join(dir, "package.json"))).pnpm?.onlyBuiltDependencies ?? []) names.add(n); } catch { /* none */ }
150
+ return [...names].sort();
151
+ }
152
+
153
+ /**
154
+ * A dependency whose install script the repository allows but `--ignore-scripts` skipped must be shown usable without it, never
155
+ * assumed: its own bin runs with `--version`, or, with no bin, the package loads in a fresh node. The probe runs before class A is
156
+ * green, so it runs under the class A launch guard (launchGuardEnv): it can never start a compile; a refused launch fails the probe.
157
+ * A package that is itself a compile tool the guard lists (esbuild) is never launched here: class A cannot launch it either, so its
158
+ * usability is proven where it is launched, after class A, and it is recorded as such. A package the root does not have installed is
159
+ * not required. Returns [{ name, version, probe, result }]; throws CheckError naming the package and root when one is installed but
160
+ * not usable.
161
+ */
162
+ export function probeSkippedBuilds({ dir, root, names, spawn = defaultSpawn, env = process.env, node = process.execPath }) {
163
+ const results = [];
164
+ const store = path.join(dir, "node_modules", ".pnpm");
165
+ let entries = [];
166
+ try { entries = fs.readdirSync(store); } catch { entries = []; }
167
+ for (const name of names) {
168
+ const flat = name.replace("/", "+");
169
+ for (const entry of entries.filter((e) => e.startsWith(`${flat}@`)).sort()) {
170
+ const pkgDir = path.join(store, entry, "node_modules", ...name.split("/"));
171
+ let manifest;
172
+ try { manifest = JSON.parse(fs.readFileSync(path.join(pkgDir, "package.json"), "utf8")); } catch { continue; }
173
+ const bins = typeof manifest.bin === "string" ? { [name.split("/").pop()]: manifest.bin } : (manifest.bin ?? {});
174
+ const binName = Object.keys(bins).find((b) => b === name.split("/").pop()) ?? Object.keys(bins)[0];
175
+ const compiler = compileLaunch(name.split("/").pop()) ?? (binName ? compileLaunch(binName) : null);
176
+ if (compiler) { results.push({ name, version: manifest.version, probe: "none", result: `compile tool (${compiler}): class A never launches it; its usability is proven where it is launched, after class A` }); continue; }
177
+ const probe = binName ? [path.join(pkgDir, bins[binName]), "--version"] : ["-e", `require(${JSON.stringify(pkgDir)})`];
178
+ const r = spawn(node, probe, { cwd: dir, env: { ...env, ...launchGuardEnv(env) }, boundedMs: 60_000 });
179
+ const out = text(r).trim().split(/\r?\n/).pop() ?? "";
180
+ if (failed(r)) {
181
+ const refused = `${r?.stderr ?? ""}\n${r?.stdout ?? ""}`.split(/\r?\n/).find((l) => l.includes("class A refuses a compile/emit launch"));
182
+ const why = refused ? `it launched a compile, which the class A launch guard refused: ${refused.replace(/^rightkit-gate: /, "").trim()}` : (tail(r) || r?.error?.message || `exit ${r?.status}`);
183
+ throw new CheckError(`root ${root}: ${name}@${manifest.version} needs its install script (allowed by the repository, not run under --ignore-scripts) and is not usable without it: ${binName ? `${binName} --version` : "require"} failed: ${why}`);
184
+ }
185
+ results.push({ name, version: manifest.version, probe: binName ? `${binName} --version` : "require", result: out || "loaded" });
186
+ }
187
+ }
188
+ return results;
189
+ }
190
+
191
+ /**
192
+ * Installs each pnpm root's dependencies into the snapshot from the commit's lockfile and the primary checkout's existing store.
193
+ * Returns one record per root ({ root, storeDir, storeSource, pnpm, lockfileSha256, ms, allowedBuildsNotRun }); throws CheckError on a
194
+ * store miss, a pnpm that is not the pinned one, or any install failure.
195
+ */
196
+ export function installSnapshotDependencies({ checkout, snapshot, roots, env = process.env, spawn = defaultSpawn, now = () => Date.now(), readFile, exists, probe = probeSkippedBuilds }) {
197
+ const records = [];
198
+ 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(); };
199
+ for (const root of roots) {
200
+ const dir = path.join(snapshot, root);
201
+ // No lockfile: nothing to install from. Not an error here: the pnpm-lockfile scan judges it, and checks that need dependencies fail.
202
+ 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; }
203
+ const { storeDir, source } = resolvePrimaryStore({ checkout, root, env, spawn, ...(readFile ? { readFile } : {}), ...(exists ? { exists } : {}) });
204
+ const pin = pinnedPnpm(dir) ?? pinnedPnpm(snapshot);
205
+ // Asked where the install runs: pnpm switches itself to the root's packageManager version there (Dell 2026-10-29: an installed
206
+ // 11.24.0 answers 11.24.0 in a neutral directory and runs 11.25.0 in the root), so that is the pnpm that will install.
207
+ const version = versionOf(dir);
208
+ if (!version) throw new CheckError("pnpm is not runnable on this host; the snapshot's dependencies cannot be installed");
209
+ if (pin && version !== pin) throw new CheckError(`pnpm ${version} is on PATH, but root ${root} of the commit pins pnpm@${pin} (packageManager); install the pinned pnpm`);
210
+ const started = now();
211
+ // The virtual store is pinned inside the snapshot and the global virtual store off, whatever the host's pnpm config says: the
212
+ // install writes nowhere but the snapshot (links to the existing store's content are the only reads outside it).
213
+ const r = spawn("pnpm", ["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"], {
214
+ cwd: dir, env: { ...env, COREPACK_ENABLE_AUTO_PIN: "0", CI: "true" }, maxBuffer: 1 << 28,
215
+ });
216
+ const ms = now() - started;
217
+ if (failed(r)) {
218
+ const out = `${r?.stdout ?? ""}\n${r?.stderr ?? ""}`;
219
+ const miss = /ERR_PNPM_NO_OFFLINE_TARBALL[^\n]*?(https?:\/\/\S+?\/-\/(\S+?)\.tgz)/.exec(out);
220
+ if (miss) throw new CheckError(`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`);
221
+ throw new CheckError(`root ${root}: offline dependency install failed (store ${storeDir}): ${tail(r) || r?.error?.message || `exit ${r?.status}`}`);
222
+ }
223
+ const lockfileSha256 = crypto.createHash("sha256").update(fs.readFileSync(path.join(dir, "pnpm-lock.yaml"))).digest("hex");
224
+ const skipped = probe ? probe({ dir, root, names: allowedBuilds(dir), spawn, env }) : [];
225
+ records.push({ root, storeDir, storeSource: source, pnpm: version, pinned: pin, lockfileSha256, ms, allowedBuildsNotRun: skipped });
226
+ }
227
+ return records;
228
+ }
229
+
230
+ /** One line per installed root, for the check's output and evidence. */
231
+ export function describeInstall(record) {
232
+ if (record.skipped) return `snapshot dependencies root=${record.root} not installed: ${record.skipped}`;
233
+ return `snapshot dependencies root=${record.root} pnpm=${record.pnpm}${record.pinned ? ` (pinned ${record.pinned})` : ""} store=${record.storeDir} (from ${record.storeSource}) lockfile=sha256:${record.lockfileSha256} ms=${record.ms}${record.allowedBuildsNotRun.length ? ` build-scripts-not-run=${record.allowedBuildsNotRun.map((b) => (b.probe === "none" ? `${b.name}@${b.version}(deferred, not proven here: ${b.result})` : `${b.name}@${b.version}(usable: ${b.probe} -> ${b.result})`)).join(",")}` : ""}`;
234
+ }
235
+
236
+ /** The deterministic-red marker for a failed run, or null when any failure is not one a re-run of the same inputs reproduces. */
237
+ export function deterministicMarker(scanResult, env) {
238
+ const failing = scanResult.scans.filter((r) => r.status !== "pass");
239
+ if (!failing.length || !env) return null;
240
+ // A fail row with no code is a scan's own findings (an internal check's list); every other code names why it could not judge.
241
+ const eligible = failing.every((r) => r.status === "fail" && !isLiveScan(r.id) && !isRegistryScan(r.id) && (r.code === undefined || r.code === null || DETERMINISTIC_CODES.has(r.code)));
242
+ return eligible ? `${DETERMINISTIC_MARKER} failed=${failing.map((r) => r.id).join(",")} env=${env}` : null;
243
+ }
244
+
245
+ /** Repository-relative files under `root`: git's view when `root` is a work tree's top level, else a walk (no node_modules, no .git). */
246
+ export function rootFiles(root, spawn = defaultSpawn) {
247
+ const top = spawn("git", ["rev-parse", "--show-toplevel"], { cwd: root });
248
+ if (!failed(top) && path.resolve(text(top).trim()) === path.resolve(root)) {
249
+ const r = spawn("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], { cwd: root, maxBuffer: 1 << 30 });
250
+ if (!failed(r)) return text(r).split("\0").filter(Boolean).map((f) => f.split(path.sep).join("/"));
251
+ }
252
+ // No git (a gate-owned snapshot): list what git would, a link as one entry (never followed into), and only real directories
253
+ // entered. A link must land, physically, inside the root on a file; one that leaves it (or dangles) stops the listing rather than
254
+ // silently dropping what it names (a committed apps/web/package.json -> ../../data/web.json is a manifest the audit must see).
255
+ const realRoot = fs.realpathSync.native(root);
256
+ const out = [];
257
+ const walk = (d, rel) => {
258
+ for (const e of fs.readdirSync(d, { withFileTypes: true })) {
259
+ if (e.name === "node_modules" || e.name === ".git") continue;
260
+ const r = rel ? `${rel}/${e.name}` : e.name;
261
+ const p = path.join(d, e.name);
262
+ if (e.isSymbolicLink()) {
263
+ let real = null;
264
+ try { real = fs.realpathSync.native(p); } catch { real = null; }
265
+ if (!real || !inside(real, realRoot)) throw new CheckError(`${r} is a link that does not land inside ${root}; the live scans cannot list the tree safely`);
266
+ if (fs.statSync(real).isFile()) out.push(r);
267
+ } else if (e.isDirectory()) walk(p, r);
268
+ else if (e.isFile()) out.push(r);
269
+ }
270
+ };
271
+ walk(root, "");
272
+ return out.sort();
273
+ }
274
+
275
+ /**
276
+ * `rightkit-gate live-scans --repo <root>`: the live remote scans of the tree at <root>, planned exactly as the full check plans them
277
+ * (planScans, all files changed) and run through the same runner. `deps` supplies the gate's unit discovery.
278
+ */
279
+ export async function liveScansCommand(o, args, { discoverPnpmRoots, discoverNpmUnits, plan = planScans, run = runScans }) {
280
+ const i = args.indexOf("--repo");
281
+ if (i < 0 || !args[i + 1] || args[i + 1].startsWith("--") || args.length !== 2) { o.err("rightkit-gate: live-scans takes exactly --repo <checked tree root>"); return 2; }
282
+ const root = path.resolve(o.cwd, args[i + 1]);
283
+ let scans;
284
+ try {
285
+ const files = rootFiles(root, o.spawn);
286
+ const tracked = (name) => files.filter((f) => f === name || f.endsWith(`/${name}`));
287
+ const readFile = (f) => o.readFile(f);
288
+ const npmRoots = discoverPnpmRoots({ root, tracked, exists: o.exists });
289
+ const npmUnits = discoverNpmUnits({ root, tracked, readFile, exists: o.exists });
290
+ const full = plan({ changed: files, contentRoot: root, liveRoot: root, trustedRoot: root, includeTests: false, rustUnits: [], npmUnits, npmRoots, tier: "release", exists: o.exists, readFile: o.readFile, platform: o.platform });
291
+ const live = { ...full, scans: full.scans.filter((s) => isLiveScan(s.id)), deferred: [] };
292
+ // A tree with a pnpm lockfile always owes a live audit: planning none there is a listing or planning failure, never an empty pass.
293
+ const lockfiles = files.filter((f) => /(^|\/)pnpm-lock\.yaml$/.test(f));
294
+ if (lockfiles.length && !live.scans.length) throw new Error(`the tree holds ${lockfiles.join(", ")} but no live scan was planned; refusing to answer an empty refresh`);
295
+ const result = await run(live, { classes: ["A"], jobs: 2 });
296
+ scans = result.scans.map((r) => ({ id: r.id, status: r.status === "pass" ? "passed" : "failed", ...(r.status === "pass" ? {} : { detail: String(r.detail ?? r.status).slice(0, 300) }) }));
297
+ } catch (e) {
298
+ o.err(`rightkit-gate: live-scans could not run: ${e.message}`);
299
+ return 2;
300
+ }
301
+ o.out(JSON.stringify({ schema: LIVE_SCANS_SCHEMA, scans: scans.map(({ id, status }) => ({ id, status })) }));
302
+ for (const s of scans) if (s.status === "failed") o.err(`rightkit-gate: live scan ${s.id} failed: ${s.detail}`);
303
+ return scans.every((s) => s.status === "passed") ? 0 : 1;
304
+ }
305
+
306
+ export { LIVE_SCAN_IDS, SnapshotError };
@@ -0,0 +1,166 @@
1
+ // The sealed git invocation of the gate (item 5 D3, signed/item5-sage-decisions-2.md; docs/GATE-CONTRACT.md §1 "exact snapshot",
2
+ // rightkit#1255). The snapshot's identity and every object read the gate makes depend on the commit's objects and the snapshot's
3
+ // bytes ONLY. The checkout's git configuration cannot reach them: no `filter.*` clean or smudge driver, no core.autocrlf or eol
4
+ // conversion, no attributes from the checkout's `.git/info/attributes` or any attributes file, no hooks, no system, global or user
5
+ // configuration, no replace refs, no inherited GIT_* variable. (Probes P3a to P3d of the decision: today's unsealed invocation ran a
6
+ // checkout filter and hid a byte tamper and a line-ending tamper behind the commit's own id.)
7
+ //
8
+ // ONE runner serves every identity operation: `<sha>^{tree}`, `ls-tree`, `cat-file --batch`, `read-tree`, `add -A`, `write-tree` and
9
+ // the 5.09d ignore oracle (`check-ignore`). Only resolving a revision NAME to a commit runs against the checkout's own git directory
10
+ // (it needs the refs), under the same environment plus GIT_NO_REPLACE_OBJECTS (`discoveryGit`).
11
+ //
12
+ // The private git directory T (mkdtemp, mode 0700, outside the checkout and the snapshot, removed afterwards) holds:
13
+ // HEAD `ref: refs/heads/none`; an empty refs/; objects/info/alternates naming `<checkout git common dir>/objects` (so every read is
14
+ // still against the checkout's object database, and every object `add` and `write-tree` writes lands in T, never in the
15
+ // checkout); `config` with only core.repositoryformatversion and core.bare (plus extensions.objectFormat for SHA-256);
16
+ // info/attributes SEALED_ATTRIBUTES (the highest-precedence attribute source); an empty info/exclude (so the only ignore rules are
17
+ // the commit's own `.gitignore` files); an empty hooks/; an empty file `empty`.
18
+ // The environment is built from nothing (sealedEnv). The `-c` flags are SEALED_CONFIG, in this order (sealedConfigArgs).
19
+ //
20
+ // The exact flags live in SEALED_GIT, one frozen constant: the broker (rustright check_admission.rs `sealed_git()`) implements the
21
+ // identical invocation, and the three-way compare of row 5.11 cites this constant.
22
+
23
+ import fs from "node:fs";
24
+ import os from "node:os";
25
+ import path from "node:path";
26
+ import { spawnSync } from "./background-process.mjs";
27
+
28
+ /** info/attributes of T, the highest-precedence attribute source: nothing converts, rewrites, identifies or re-encodes a byte. */
29
+ export const SEALED_ATTRIBUTES = "* -text -ident -filter -working-tree-encoding !eol\n";
30
+
31
+ /** The `-c key=value` list, in order. `@FILEMODE` is true on darwin and linux and false on win32 (NTFS has no exec bit: a 100755 lives in the seeded index, signed 5.04); `@T/x` is T/x. */
32
+ export const SEALED_CONFIG = Object.freeze([
33
+ ["core.autocrlf", "false"], ["core.safecrlf", "false"], ["core.eol", "lf"],
34
+ ["core.fileMode", "@FILEMODE"], ["core.symlinks", "true"],
35
+ ["core.ignoreCase", "false"], ["core.precomposeUnicode", "false"],
36
+ ["core.attributesFile", "@T/empty"], ["core.excludesFile", "@T/empty"], ["core.hooksPath", "@T/hooks"],
37
+ ["core.fsmonitor", "false"], ["core.untrackedCache", "false"], ["core.splitIndex", "false"],
38
+ ["core.sparseCheckout", "false"], ["index.sparse", "false"], ["core.useReplaceRefs", "false"],
39
+ ].map((pair) => Object.freeze(pair)));
40
+
41
+ /** Environment variables the invocation sets to a fixed value; the others (PATH, HOME, XDG_CONFIG_HOME, LC_ALL, GIT_CONFIG_GLOBAL, GIT_INDEX_FILE, GIT_DIR, GIT_WORK_TREE, and on Windows SystemRoot, WINDIR, PATHEXT, TEMP, TMP) are built by sealedEnv. */
42
+ export const SEALED_ENV_FIXED = Object.freeze({
43
+ GIT_CONFIG_NOSYSTEM: "1", GIT_ATTR_NOSYSTEM: "1", GIT_NO_REPLACE_OBJECTS: "1", GIT_NO_LAZY_FETCH: "1", GIT_TERMINAL_PROMPT: "0", GIT_OPTIONAL_LOCKS: "0",
44
+ });
45
+
46
+ /** The sealed invocation, whole: what the three-way compare (5.11) and the broker's `sealed_git()` cite. */
47
+ export const SEALED_GIT = Object.freeze({
48
+ attributes: SEALED_ATTRIBUTES,
49
+ config: SEALED_CONFIG,
50
+ envFixed: SEALED_ENV_FIXED,
51
+ envBuilt: Object.freeze(["PATH", "HOME", "XDG_CONFIG_HOME", "LC_ALL", "GIT_CONFIG_GLOBAL", "GIT_INDEX_FILE", "GIT_DIR", "GIT_WORK_TREE", "SystemRoot", "WINDIR", "PATHEXT", "TEMP", "TMP"]),
52
+ layout: Object.freeze({ head: "ref: refs/heads/none\n", alternates: "<git common dir>/objects", excludeIsEmpty: true, hooksIsEmpty: true, workingDirectory: "the snapshot" }),
53
+ gitVersionNote: "GIT_NO_LAZY_FETCH needs git 2.45 or later; older git ignores it",
54
+ });
55
+
56
+ const MAX_BUFFER = 1 << 30;
57
+ const WINDOWS_ENV = ["SystemRoot", "WINDIR", "PATHEXT"];
58
+
59
+ /** `-c k=v ...` for T and `platform`, in SEALED_CONFIG's order. */
60
+ export function sealedConfigArgs(T, platform = process.platform) {
61
+ const out = [];
62
+ for (const [key, value] of SEALED_CONFIG) {
63
+ const v = value === "@FILEMODE" ? (platform === "win32" ? "false" : "true") : value.startsWith("@T/") ? path.join(T, value.slice(3)) : value;
64
+ out.push("-c", `${key}=${v}`);
65
+ }
66
+ return out;
67
+ }
68
+
69
+ const fromEnv = (base, name) => { const k = Object.keys(base).find((x) => x.toLowerCase() === name.toLowerCase()); return k === undefined ? undefined : base[k]; };
70
+
71
+ /** The environment for git: built from nothing, never inherited. `T` is the private git directory (or the discovery home), `snapshot` the work tree. */
72
+ export function sealedEnv(T, { snapshot, platform = process.platform, base = process.env, gitDir = T, index = true } = {}) {
73
+ const env = {};
74
+ const p = fromEnv(base, "PATH");
75
+ if (p !== undefined) env.PATH = p;
76
+ if (platform === "win32") {
77
+ for (const name of WINDOWS_ENV) { const v = fromEnv(base, name); if (v !== undefined) env[name] = v; }
78
+ env.TEMP = T; env.TMP = T;
79
+ }
80
+ env.HOME = T; env.XDG_CONFIG_HOME = T; env.LC_ALL = "C";
81
+ env.GIT_CONFIG_GLOBAL = path.join(T, "empty");
82
+ Object.assign(env, SEALED_ENV_FIXED);
83
+ if (index) env.GIT_INDEX_FILE = path.join(T, "index");
84
+ if (gitDir) env.GIT_DIR = gitDir;
85
+ if (snapshot) env.GIT_WORK_TREE = snapshot;
86
+ return env;
87
+ }
88
+
89
+ /** Creates T. `commonDir` (the checkout's git common directory) wires the object database; `objectFormat` is "sha1" or "sha256". */
90
+ export function createSealedGitDir({ commonDir, objectFormat = "sha1" } = {}) {
91
+ const T = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), "rk-sealed-git-")));
92
+ try {
93
+ for (const d of ["refs", path.join("objects", "info"), "info", "hooks"]) fs.mkdirSync(path.join(T, d), { recursive: true });
94
+ fs.writeFileSync(path.join(T, "HEAD"), SEALED_GIT.layout.head);
95
+ fs.writeFileSync(path.join(T, "config"), `[core]\n\trepositoryformatversion = ${objectFormat === "sha256" ? 1 : 0}\n\tbare = false\n${objectFormat === "sha256" ? "[extensions]\n\tobjectFormat = sha256\n" : ""}`);
96
+ fs.writeFileSync(path.join(T, "info", "attributes"), SEALED_ATTRIBUTES);
97
+ fs.writeFileSync(path.join(T, "info", "exclude"), "");
98
+ fs.writeFileSync(path.join(T, "empty"), "");
99
+ if (commonDir) fs.writeFileSync(path.join(T, "objects", "info", "alternates"), `${commonDir}/objects\n`);
100
+ } catch (error) {
101
+ fs.rmSync(T, { recursive: true, force: true });
102
+ throw error;
103
+ }
104
+ return T;
105
+ }
106
+
107
+ const why = (r, args) => `git ${args.filter((a) => a !== "-c" && !/^(core|index)\./.test(a)).join(" ")}: ${r.error ? r.error.message : String(r.stderr ?? "").trim() || `exit ${r.status}`}`;
108
+
109
+ /**
110
+ * Runs `fn(git)` with a fresh T; `git.run(args, { input, binary, cwd })` returns the spawn result, `git.ok(...)` its stdout or throws an
111
+ * Error naming the command. The working directory is `snapshot` when given, else T. T is removed afterwards.
112
+ */
113
+ export function withSealedGit({ commonDir, objectFormat, snapshot, platform = process.platform } = {}, fn) {
114
+ const T = createSealedGitDir({ commonDir, objectFormat });
115
+ const env = sealedEnv(T, { snapshot, platform });
116
+ const prefix = sealedConfigArgs(T, platform);
117
+ const run = (args, { input, binary = false, cwd } = {}) => spawnSync("git", [...prefix, ...args], { cwd: cwd ?? snapshot ?? T, env, input, maxBuffer: MAX_BUFFER, ...(binary ? {} : { encoding: "utf8" }) });
118
+ const ok = (args, options) => { const r = run(args, options); if (r.error || r.status !== 0) throw new Error(why(r, args)); return r.stdout; };
119
+ try { return fn({ run, ok, T, env }); } finally { fs.rmSync(T, { recursive: true, force: true }); }
120
+ }
121
+
122
+ // --- discovery: finding a repository and resolving a revision NAME, the only reads that need the checkout's own git directory -------------
123
+
124
+ let home = null;
125
+ /** One empty home directory per process for discovery runs, removed at exit. */
126
+ function discoveryHome() {
127
+ if (home && fs.existsSync(home)) return home;
128
+ home = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), "rk-git-home-")));
129
+ fs.writeFileSync(path.join(home, "empty"), "");
130
+ const mine = home;
131
+ process.once("exit", () => { try { fs.rmSync(mine, { recursive: true, force: true }); } catch { /* best effort */ } });
132
+ return home;
133
+ }
134
+
135
+ /** git run in `cwd` against the checkout's own repository (found from `cwd`), under the sealed environment (no GIT_DIR, no index) plus GIT_NO_REPLACE_OBJECTS. Returns the spawn result. */
136
+ export function discoveryRun(cwd, args, { platform = process.platform, input } = {}) {
137
+ const env = sealedEnv(discoveryHome(), { platform, gitDir: null, index: false });
138
+ return spawnSync("git", args, { cwd, env, input, encoding: "utf8", maxBuffer: MAX_BUFFER });
139
+ }
140
+ /** As discoveryRun, returning trimmed-by-caller stdout or throwing an Error naming the command. */
141
+ export function discoveryGit(cwd, args, options) {
142
+ const r = discoveryRun(cwd, args, options);
143
+ if (r.error || r.status !== 0) throw new Error(why(r, args));
144
+ return r.stdout;
145
+ }
146
+
147
+ const infoCache = new Map();
148
+ /** `{ commonDir, objectFormat }` of the repository `checkout` belongs to, found by discovery and cached per canonical path. */
149
+ export function sealedRepoInfo(checkout) {
150
+ let key;
151
+ try { key = fs.realpathSync.native(checkout); } catch { key = path.resolve(checkout); }
152
+ const cached = infoCache.get(key);
153
+ if (cached && fs.existsSync(path.join(cached.commonDir, "objects"))) return cached;
154
+ let common;
155
+ try { common = discoveryGit(key, ["rev-parse", "--path-format=absolute", "--git-common-dir"]).trim(); }
156
+ catch { common = path.resolve(key, discoveryGit(key, ["rev-parse", "--git-common-dir"]).trim()); }
157
+ const info = { commonDir: common, objectFormat: discoveryGit(key, ["rev-parse", "--show-object-format"]).trim() || "sha1" };
158
+ infoCache.set(key, info);
159
+ return info;
160
+ }
161
+
162
+ /** A single sealed git command against `checkout`'s object database (T is made and removed around it). Returns the spawn result. */
163
+ export function sealedRun(checkout, args, options = {}) {
164
+ const info = options.repo ?? sealedRepoInfo(checkout);
165
+ return withSealedGit({ ...info, snapshot: options.snapshot }, (git) => git.run(args, options));
166
+ }