@rightkit/release 0.6.8 → 0.6.11

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 (96) hide show
  1. package/README.md +30 -12
  2. package/admission-files.mjs +4 -1
  3. package/admission.mjs +2 -2
  4. package/advisory-exceptions.mjs +41 -5
  5. package/android-play-upload.mjs +16 -15
  6. package/app-check.mjs +105 -27
  7. package/assemble-app-command.mjs +151 -0
  8. package/assembled-app.mjs +445 -0
  9. package/bin/darwin/rightkit-swap +0 -0
  10. package/bin/darwin/rightkit-swap.sign.json +6 -0
  11. package/bin/rightkit-gate.mjs +1608 -345
  12. package/broker-pins.mjs +152 -0
  13. package/build-launch.mjs +88 -0
  14. package/build-ownership.mjs +1231 -0
  15. package/build-release.mjs +82 -28
  16. package/cargo-contract.mjs +3 -1
  17. package/cargo-ledger.mjs +62 -0
  18. package/cargo-target-shim.mjs +238 -0
  19. package/class-a-launch-guard.mjs +126 -0
  20. package/cli/right-release.mjs +31 -5
  21. package/clippy-equivalence.mjs +202 -0
  22. package/clippy-route.mjs +543 -0
  23. package/codesign-shim.mjs +11 -1
  24. package/committed-source.mjs +39 -5
  25. package/crate-workspaces.mjs +87 -0
  26. package/create-mac-updater.mjs +14 -2
  27. package/deterministic-scans.mjs +997 -71
  28. package/development-signing.mjs +4 -1
  29. package/development.mjs +120 -21
  30. package/direct-receipt.mjs +135 -0
  31. package/door-policy.mjs +71 -6
  32. package/door.mjs +6 -6
  33. package/freshness-census.mjs +257 -0
  34. package/gate-executor.mjs +14 -2
  35. package/gate-stages.mjs +91 -15
  36. package/heavy-command.mjs +3 -2
  37. package/hook-retirement.mjs +65 -0
  38. package/init-command.mjs +1 -1
  39. package/install-app.mjs +11 -2
  40. package/install-assembled-app.mjs +423 -0
  41. package/inventory-check.mjs +11 -8
  42. package/ios-dev.mjs +70 -3
  43. package/ios-upload.mjs +20 -15
  44. package/linux-artifacts.mjs +93 -0
  45. package/local-publish.mjs +224 -53
  46. package/local-qualify.mjs +5 -3
  47. package/local-runtime.mjs +5 -4
  48. package/mac-package-recipe.mjs +4 -1
  49. package/managed-cargo.mjs +47 -5
  50. package/managed-root-guard.mjs +34 -0
  51. package/mirror-root-artifact.mjs +18 -5
  52. package/native-helpers.mjs +355 -0
  53. package/ownership-class-rules.mjs +1 -1
  54. package/ownership-evidence.mjs +37 -13
  55. package/ownership-hooks.mjs +20 -59
  56. package/ownership-lockfiles.mjs +14 -1
  57. package/ownership-policy-bundle.mjs +3 -2
  58. package/ownership-staged.mjs +2 -1
  59. package/ownership.mjs +24 -0
  60. package/package-recipe-targets.mjs +84 -10
  61. package/package.json +4 -3
  62. package/pipeline-admission.mjs +4 -4
  63. package/pipeline-receipts.mjs +302 -302
  64. package/preflight.mjs +27 -4
  65. package/project-type.mjs +2 -2
  66. package/provenance-verify.mjs +115 -0
  67. package/publish-cargo.mjs +12 -9
  68. package/qa-admission-policy.json +10 -1
  69. package/recipe-build.mjs +106 -14
  70. package/release-artifact-path.mjs +341 -0
  71. package/release-config.mjs +1 -1
  72. package/release-invocation.mjs +8 -1
  73. package/release-qa-command.mjs +25 -9
  74. package/release-source-brand.mjs +17 -0
  75. package/release-source-identity.mjs +171 -0
  76. package/release.mjs +59 -22
  77. package/rightkit-versions.json +73 -47
  78. package/runtime-budgets.mjs +6 -2
  79. package/scan-digest.mjs +71 -5
  80. package/sign-updater.mjs +6 -3
  81. package/snapshot-broker.mjs +172 -0
  82. package/snapshot-check.mjs +312 -0
  83. package/snapshot-git.mjs +166 -0
  84. package/snapshot-request.mjs +363 -0
  85. package/snapshot-root-config.mjs +76 -0
  86. package/snapshot.mjs +969 -0
  87. package/source-split.mjs +73 -0
  88. package/stage-commands.mjs +60 -0
  89. package/stage-ledger.mjs +204 -0
  90. package/stage-sidecar-command.mjs +5 -1
  91. package/stage-sidecar.mjs +157 -27
  92. package/swap-helper-bundle.mjs +5 -2
  93. package/test-command.mjs +17 -4
  94. package/unit-gate.mjs +301 -0
  95. package/web-build.mjs +40 -6
  96. package/win-package-recipe.mjs +20 -7
@@ -0,0 +1,543 @@
1
+ // The Clippy route: `rightkit-gate clippy [<cargo clippy args>]` (GATE-CONTRACT §1 "Clippy route & batching", E13 to E15; item 8 of
2
+ // the joint plan, rows 8.1 to 8.16 of the signed map). A gate-owned destination for a request such as
3
+ // `cargo clippy --workspace --all-targets -- -D warnings`: it builds HEAD's commit (never the checkout) from the gate-owned
4
+ // snapshot, under the host's build ownership, after FULL class A (D8-2), and stops after diagnostics. It has no stage chain, runs no
5
+ // package, sign, publish or release stage, and writes nothing a door receipt, qualification or the full gate could take as stage
6
+ // evidence (row 8.5).
7
+ //
8
+ // argv cargo's own arguments, verbatim. The gate adds only `--message-format=json`, `--keep-going` (unless present) and
9
+ // `--locked` (unless --locked, --frozen or --offline is present), each recorded as a transformation. Refused with
10
+ // exit 2, before class A, the snapshot sync or any compile: a second --message-format, --target-dir, --config,
11
+ // +toolchain, --fix, --allow-dirty, -Z (and -C, which moves the working directory), an absolute --manifest-path
12
+ // (a relative one resolves inside the snapshot and may not leave it) and any word that is not an option of
13
+ // `cargo clippy` (a subcommand).
14
+ // sidecars a requested package that declares sidecars (Tauri `bundle.externalBin`) refuses the whole request, BLOCKED with
15
+ // RK_CLIPPY_SIDECAR_BOUND, before ownership, class A, the sync or any compile, until item 7 row 7.05 stages sidecars
16
+ // from a snapshot. The request is never rewritten to leave such a package out (signed D8-1: three additions only).
17
+ // Linux direct, through the item 7 cargo wrapper (cargo-target-shim.mjs): every cargo launch is bound to the checkout's own
18
+ // target directory or refused, so nothing is written under the snapshot (row 8.12).
19
+ // Mac and Dell the broker carrier (item 7 row 7.12) is not wired into the gate yet. The route refuses BEFORE class A, the sync and
20
+ // any compile with a BLOCKED code naming 7.12, and never falls back to direct cargo on a broker-managed host
21
+ // (rows 8.3, 8.10, 8.13). The gate's half of the broker receipt (attribution and toolchain, rows 8.9 and 8.16) is
22
+ // `routeFromBrokerReceipt` below, unwired until the carrier is.
23
+ // result FAILED when any unit failed; diagnostics by package and target from cargo's JSON messages; a unit that never ran
24
+ // because a dependency failed is reported blocked and the result claims no completeness (row 8.4).
25
+ // exit 0 PASSED, 1 FAILED or BLOCKED, 2 refused or could not run (RK_CHECK_ERROR: the snapshot moved).
26
+ import crypto from "node:crypto";
27
+ import fs from "node:fs";
28
+ import os from "node:os";
29
+ import path from "node:path";
30
+ import { resolveExecutor as defaultResolveExecutor } from "./gate-executor.mjs";
31
+ import { directPlatform } from "./managed-cargo.mjs";
32
+ import { defaultSpawnAsync, sidecarBoundCrate } from "./deterministic-scans.mjs";
33
+ import { gateStateDir } from "./gate-stages.mjs";
34
+
35
+ export const ROUTE_RECEIPT_SCHEMA = "rightkit.gate-clippy-route-receipt/1";
36
+ export const ROUTE_DIAGNOSTICS_SCHEMA = "rightkit.gate-clippy-route-diagnostics/1";
37
+ export const CLIPPY_USAGE = "usage: rightkit-gate clippy [<cargo clippy args>] (HEAD's commit; the arguments are cargo's, verbatim; the gate adds only --message-format=json, --keep-going and --locked)";
38
+ /** The BLOCKED and refusal codes the route prints (each as `rightkit-gate: <code>: ...`). */
39
+ export const ROUTE_CODES = Object.freeze({
40
+ notClippy: "RK_CLIPPY_NOT_CLIPPY", messageFormat: "RK_CLIPPY_MESSAGE_FORMAT", targetDir: "RK_CLIPPY_TARGET_DIR", config: "RK_CLIPPY_CONFIG", toolchain: "RK_CLIPPY_TOOLCHAIN_OVERRIDE",
41
+ fix: "RK_CLIPPY_FIX", unstable: "RK_CLIPPY_UNSTABLE", chdir: "RK_CLIPPY_CHDIR", manifestAbsolute: "RK_CLIPPY_MANIFEST_ABSOLUTE", manifestEscapes: "RK_CLIPPY_MANIFEST_ESCAPES",
42
+ outputInSnapshot: "RK_CLIPPY_OUTPUT_IN_SNAPSHOT", brokerCarrier: "RK_CLIPPY_BROKER_CARRIER_NOT_WIRED", brokerUnavailable: "RK_CLIPPY_BROKER_UNAVAILABLE", hostUnsupported: "RK_CLIPPY_HOST_UNSUPPORTED",
43
+ sidecarBound: "RK_CLIPPY_SIDECAR_BOUND", receiptEvidence: "RK_CLIPPY_RECEIPT_EVIDENCE", toolchainUnreadable: "RK_CLIPPY_TOOLCHAIN_UNREADABLE",
44
+ });
45
+
46
+ const sha256 = (v) => crypto.createHash("sha256").update(v).digest("hex");
47
+ const posix = (p) => String(p).replaceAll("\\", "/");
48
+ const text = (r) => String(r?.stdout ?? "");
49
+ const failed = (r) => !r || Boolean(r.error) || r.status !== 0;
50
+ const why = (r) => String(r?.error?.message ?? r?.stderr ?? "").trim().split(/\r?\n/).slice(-3).join(" | ");
51
+ const inside = (child, parent) => { const rel = path.relative(parent, child); return rel === "" || (!!rel && !rel.startsWith("..") && !path.isAbsolute(rel)); };
52
+
53
+ // ---- argv (rows 8.1, 8.2) ------------------------------------------------------------------------------------------------------------
54
+
55
+ // `cargo clippy` options that take a value as the next word (or after `=`), and the short letters that do.
56
+ const LONG_WITH_VALUE = new Set(["--package", "--exclude", "--jobs", "--bin", "--example", "--test", "--bench", "--features", "--profile", "--target", "--manifest-path", "--color", "--lockfile-path", "--artifact-dir", "--out-dir", "--build-dir"]);
57
+ const SHORT_WITH_VALUE = new Set(["p", "j", "F"]);
58
+ const REFUSED = new Map([
59
+ ["--message-format", [ROUTE_CODES.messageFormat, "the gate requests --message-format=json itself, exactly once; a request that carries its own is refused, never rewritten"]],
60
+ ["--target-dir", [ROUTE_CODES.targetDir, "the gate passes no output path of its own: outputs go to the checkout's existing target directory (GATE-CONTRACT §1 output directory & cache)"]],
61
+ ["--config", [ROUTE_CODES.config, "a cargo configuration override can move tools, outputs or flags the gate pins"]],
62
+ ["--fix", [ROUTE_CODES.fix, "the route reports diagnostics only; the snapshot is never written"]],
63
+ ["--allow-dirty", [ROUTE_CODES.fix, "the route reports diagnostics only; the snapshot is never written"]],
64
+ ]);
65
+ const refusal = (code, message) => ({ refusal: { code, message } });
66
+ const absolutePath = (p) => path.posix.isAbsolute(p) || path.win32.isAbsolute(p) || /^[A-Za-z]:/.test(p);
67
+
68
+ /**
69
+ * The route's cargo argv for a request: `{ refusal }`, `{ help: true }` or
70
+ * `{ request, before, after, final, additions, transformations, already, manifestPath }`. `before` and `after` are the request's words
71
+ * on either side of its `--`. The final argv is `before`, the additions, then `after`, so removing the additions gives the request
72
+ * back, word for word, and lint arguments after `--` are untouched.
73
+ */
74
+ export function parseClippyArgv(args) {
75
+ const request = [...args];
76
+ const sep = request.indexOf("--");
77
+ const before = sep < 0 ? request : request.slice(0, sep);
78
+ const after = sep < 0 ? [] : request.slice(sep);
79
+ let keepGoing = false; let lockLike = false; let help = false; let manifestPath = null;
80
+ for (let i = 0; i < before.length; i += 1) {
81
+ const w = before[i];
82
+ if (w.startsWith("+")) return refusal(ROUTE_CODES.toolchain, `${w}: the toolchain is pinned by the gate (BROKER-CONTRACT B5), never chosen by the request`);
83
+ if (!w.startsWith("-") || w === "-") return refusal(ROUTE_CODES.notClippy, `${JSON.stringify(w)} is not an option of \`cargo clippy\`: the route runs Clippy only (a subcommand such as build, check, test or fmt is refused)`);
84
+ if (w.startsWith("--")) {
85
+ const eq = w.indexOf("=");
86
+ const name = eq < 0 ? w : w.slice(0, eq);
87
+ const refused = REFUSED.get(name);
88
+ if (refused) return refusal(refused[0], `${name}: ${refused[1]}`);
89
+ if (name === "--help" || name === "--version") help = true;
90
+ else if (name === "--keep-going") keepGoing = true;
91
+ else if (name === "--locked" || name === "--frozen" || name === "--offline") lockLike = true;
92
+ if (LONG_WITH_VALUE.has(name)) {
93
+ const value = eq >= 0 ? w.slice(eq + 1) : (before[i + 1] ?? null);
94
+ if (eq < 0) i += 1;
95
+ if (name === "--manifest-path" && value !== null) {
96
+ if (absolutePath(value)) return refusal(ROUTE_CODES.manifestAbsolute, `--manifest-path ${value} is absolute: it would name a file outside the snapshot; give a path relative to the working directory (it resolves inside the snapshot)`);
97
+ manifestPath = value;
98
+ }
99
+ }
100
+ continue;
101
+ }
102
+ // A short cluster: the first value-taking letter takes the rest of the word (or the next word); Z and C are refused wherever they stand.
103
+ for (let j = 1; j < w.length; j += 1) {
104
+ const letter = w[j];
105
+ if (letter === "Z") return refusal(ROUTE_CODES.unstable, `${w}: -Z unstable flags change what cargo does in ways the gate pins`);
106
+ if (letter === "C") return refusal(ROUTE_CODES.chdir, `${w}: -C moves cargo's working directory, which must stay inside the snapshot`);
107
+ if (letter === "h" || letter === "V") help = true;
108
+ if (SHORT_WITH_VALUE.has(letter)) { if (j === w.length - 1) i += 1; break; }
109
+ }
110
+ }
111
+ if (help) return { help: true };
112
+ const additions = []; const transformations = []; const already = [];
113
+ const add = (arg, reason) => { additions.push(arg); transformations.push({ op: "add", arg, reason }); };
114
+ add("--message-format=json", "structured diagnostics: package and target attribution (E15)");
115
+ if (keepGoing) already.push("--keep-going"); else add("--keep-going", "collect every feasible independent diagnostic when a unit fails (E15)");
116
+ if (lockLike) already.push("--locked|--frozen|--offline"); else add("--locked", "the commit-only build: a lockfile rewrite inside the snapshot would surface only as a late tree mismatch (GATE-CONTRACT §1)");
117
+ return { request, before, after, final: [...before, ...additions, ...after], additions, transformations, already, manifestPath };
118
+ }
119
+
120
+ // ---- cargo messages, units and the aggregate (rows 8.4, 8.9) ---------------------------------------------------------------------------
121
+
122
+ /** A package's name from its id, in either of cargo's spellings (`name 1.2.3 (source)`, `source#name@1.2.3`, `path+file:///x/name#1.2.3`). */
123
+ export function packageNameOf(packageId) {
124
+ const id = String(packageId ?? "");
125
+ const hash = id.lastIndexOf("#");
126
+ if (hash >= 0) {
127
+ const fragment = id.slice(hash + 1);
128
+ const at = fragment.indexOf("@");
129
+ if (at >= 0) return fragment.slice(0, at);
130
+ return id.slice(0, hash).replace(/\/+$/, "").split(/[\\/]/).pop() || id;
131
+ }
132
+ return id.split(" ")[0] || id;
133
+ }
134
+
135
+ /** `{ messages, nonJson }`: the JSON objects of cargo's `--message-format=json` stream, one per line; other lines are counted. */
136
+ export function parseCargoMessages(body) {
137
+ const messages = []; let nonJson = 0;
138
+ for (const line of String(body ?? "").split(/\r?\n/)) {
139
+ const t = line.trim();
140
+ if (!t) continue;
141
+ if (t.startsWith("{")) { try { messages.push(JSON.parse(t)); continue; } catch { /* counted below */ } }
142
+ nonJson += 1;
143
+ }
144
+ return { messages, nonJson };
145
+ }
146
+
147
+ const SUMMARY_MESSAGE = /^(aborting due to|could not compile|\d+ warnings? emitted|build failed)/i;
148
+ const targetKind = (t) => (Array.isArray(t?.kind) ? t.kind.join(",") : String(t?.kind ?? ""));
149
+ const primarySpan = (spans) => (spans ?? []).find((s) => s.is_primary) ?? spans?.[0] ?? null;
150
+
151
+ /**
152
+ * What a cargo message stream says. `units`: one row per package and target (package, packageId, target, kind, errors, warnings,
153
+ * artifact, fresh); `diagnostics`: every error and warning with its package, target, code and primary span, plus cargo's own
154
+ * rendering; `artifacts`: every compiler-artifact with the feature set it was built with; `buildFinished`: `{ success }` or null.
155
+ */
156
+ export function summarizeMessages(messages) {
157
+ const units = new Map(); const diagnostics = []; const artifacts = []; let buildFinished = null;
158
+ const unitOf = (m) => {
159
+ const packageId = String(m.package_id ?? "unknown");
160
+ const key = `${packageNameOf(packageId)}\0${m.target?.name ?? "unknown"}\0${targetKind(m.target)}`;
161
+ if (!units.has(key)) units.set(key, { package: packageNameOf(packageId), packageId, target: m.target?.name ?? "unknown", kind: targetKind(m.target), errors: 0, warnings: 0, artifact: false, fresh: null });
162
+ return units.get(key);
163
+ };
164
+ for (const m of messages) {
165
+ if (m.reason === "build-finished") buildFinished = { success: m.success === true };
166
+ else if (m.reason === "compiler-artifact") {
167
+ const u = unitOf(m); u.artifact = true; u.fresh = m.fresh === true;
168
+ artifacts.push({ package: u.package, packageId: u.packageId, target: u.target, kind: u.kind, test: m.profile?.test === true, features: [...(m.features ?? [])].sort(), fresh: m.fresh === true });
169
+ } else if (m.reason === "compiler-message") {
170
+ const u = unitOf(m); const msg = m.message ?? {};
171
+ const level = String(msg.level ?? "");
172
+ if (SUMMARY_MESSAGE.test(String(msg.message ?? ""))) continue;
173
+ if (level === "error" || level === "error: internal compiler error") u.errors += 1;
174
+ else if (level === "warning") u.warnings += 1;
175
+ else continue;
176
+ const span = primarySpan(msg.spans);
177
+ diagnostics.push({ package: u.package, packageId: u.packageId, target: u.target, kind: u.kind, level, code: msg.code?.code ?? null, message: String(msg.message ?? ""), span: span ? { file: span.file_name, line: span.line_start, column: span.column_start, lineEnd: span.line_end, columnEnd: span.column_end } : null, rendered: String(msg.rendered ?? "") });
178
+ }
179
+ }
180
+ return { units: [...units.values()], diagnostics, artifacts, buildFinished };
181
+ }
182
+
183
+ /** Packages cargo's stderr names as failed to build (`could not compile` and a failed build script), by name. */
184
+ export function failedPackagesFromStderr(stderr) {
185
+ const names = new Set();
186
+ for (const m of String(stderr ?? "").matchAll(/could not compile `([^`]+)`/g)) names.add(m[1]);
187
+ for (const m of String(stderr ?? "").matchAll(/failed to run custom build command for `([^`\s]+)/g)) names.add(m[1]);
188
+ return [...names].sort();
189
+ }
190
+
191
+ /**
192
+ * The packages a request selects, from `cargo metadata --no-deps` of the snapshot: `-p`/`--package` (name, `name@version`, glob),
193
+ * `--workspace`/`--all` less `--exclude`, else the package at the working directory, else the workspace's default members.
194
+ * `{ members: [pkg], all: [pkg], unresolved: [spec] }`; a spec that matches nothing is unresolved and the scope is then not fully known.
195
+ */
196
+ export function resolveScope({ before, metadata, directory }) {
197
+ const specs = []; const excludes = []; let workspace = false;
198
+ for (let i = 0; i < before.length; i += 1) {
199
+ const w = before[i];
200
+ if (w === "--workspace" || w === "--all") { workspace = true; continue; }
201
+ if (w === "-p" || w === "--package") { specs.push(before[i + 1] ?? ""); i += 1; continue; }
202
+ if (w.startsWith("--package=")) { specs.push(w.slice(10)); continue; }
203
+ if (w === "--exclude") { excludes.push(before[i + 1] ?? ""); i += 1; continue; }
204
+ if (w.startsWith("--exclude=")) { excludes.push(w.slice(10)); continue; }
205
+ if (/^-p./.test(w) && !w.startsWith("--")) specs.push(w.slice(2));
206
+ }
207
+ const packages = metadata?.packages ?? [];
208
+ const byId = new Map(packages.map((p) => [p.id, p]));
209
+ const all = (metadata?.workspace_members ?? []).map((id) => byId.get(id)).filter(Boolean);
210
+ const globOf = (spec) => new RegExp(`^${spec.replace(/[.+^${}()|\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".")}$`);
211
+ const matches = (p, spec) => p.name === spec || `${p.name}@${p.version}` === spec || p.id === spec || (/[*?]/.test(spec) && globOf(spec).test(p.name));
212
+ const unresolved = [];
213
+ let members;
214
+ if (workspace) members = all.filter((p) => !excludes.some((x) => matches(p, x)));
215
+ else if (specs.length) {
216
+ members = [];
217
+ for (const spec of specs) { const hit = all.filter((p) => matches(p, spec)); if (!hit.length) unresolved.push(spec); for (const p of hit) if (!members.includes(p)) members.push(p); }
218
+ } else {
219
+ const here = all.find((p) => path.dirname(p.manifest_path) === directory);
220
+ members = here ? [here] : (metadata?.workspace_default_members ?? metadata?.workspace_members ?? []).map((id) => byId.get(id)).filter(Boolean);
221
+ }
222
+ return { members, all, unresolved };
223
+ }
224
+
225
+ /**
226
+ * The route's result for one cargo run. Aggregate FAILED on any failed unit or a build that did not finish successfully (E15). A
227
+ * requested package that built nothing because a dependency failed is `blocked`, never clean, and `complete` is false then (row 8.4).
228
+ */
229
+ export function summarizeRun({ messages, stderr = "", exit = null, signal = null, startError = null, scope = null }) {
230
+ const run = summarizeMessages(messages);
231
+ const memberNames = scope ? new Set(scope.members.map((p) => p.name)) : null;
232
+ const failedNames = new Set(failedPackagesFromStderr(stderr));
233
+ const units = run.units.filter((u) => !memberNames || memberNames.has(u.package)).map((u) => ({ package: u.package, packageId: u.packageId, target: u.target, kind: u.kind, errors: u.errors, warnings: u.warnings, status: u.errors > 0 ? "failed" : u.artifact ? "clean" : "unbuilt", fresh: u.fresh }));
234
+ for (const u of run.units) if (u.errors > 0) failedNames.add(u.package);
235
+ const success = exit === 0 && !signal && !startError && run.buildFinished?.success === true;
236
+ const blocked = [];
237
+ if (scope && !success && run.buildFinished !== null) {
238
+ const deps = new Map(scope.all.map((p) => [p.name, (p.dependencies ?? []).map((d) => d.name)]));
239
+ const reachable = (name) => { const seen = new Set(); const stack = [...(deps.get(name) ?? [])]; while (stack.length) { const n = stack.pop(); if (seen.has(n) || !deps.has(n)) continue; seen.add(n); stack.push(...deps.get(n)); } return seen; };
240
+ for (const p of scope.members) {
241
+ const mine = units.filter((u) => u.package === p.name);
242
+ if (failedNames.has(p.name) || mine.some((u) => u.status === "failed" || u.status === "clean")) continue;
243
+ const blockedBy = [...reachable(p.name)].filter((n) => failedNames.has(n)).sort();
244
+ blocked.push({ package: p.name, blockedBy, ...(blockedBy.length ? {} : { failedElsewhere: [...failedNames].filter((n) => !memberNames.has(n)).sort() }) });
245
+ }
246
+ }
247
+ const aggregate = success ? "PASSED" : "FAILED";
248
+ const scopeKnown = Boolean(scope) && scope.unresolved.length === 0;
249
+ const complete = success || (blocked.length === 0 && scopeKnown && run.buildFinished !== null && !startError && !signal);
250
+ const completeness = success ? "every requested unit was built"
251
+ : startError ? `not claimed: cargo did not run (${startError})`
252
+ : signal ? `not claimed: cargo was stopped by ${signal}`
253
+ : !scope ? "not claimed: the requested package set could not be read"
254
+ : scope.unresolved.length ? `not claimed: the request names packages the snapshot does not hold (${scope.unresolved.join(", ")})`
255
+ : blocked.length ? `not claimed: ${blocked.length} requested package(s) were blocked by a failed dependency and built nothing`
256
+ : run.buildFinished === null ? "not claimed: cargo reported no build-finished message"
257
+ : "every requested unit ran; some failed";
258
+ return { aggregate, complete, completeness, units, blocked, failedPackages: [...failedNames].sort(), diagnostics: run.diagnostics, artifacts: run.artifacts, buildFinished: run.buildFinished };
259
+ }
260
+
261
+ // ---- the broker's half of the receipt: attribution and toolchain (rows 8.9, 8.16) ------------------------------------------------------
262
+
263
+ /**
264
+ * The gate's reading of a broker receipt for a Clippy request (BROKER-CONTRACT B4 to B6; the gate side only, wired when the carrier
265
+ * is). Missing attribution, a truncated or unavailable one, or a missing toolchain identity makes the route result FAILED, never a
266
+ * partial success: `{ aggregate, code, units, problems, toolchain, complete }`. The units are the receipt's per package and per
267
+ * target `attribution` rows.
268
+ */
269
+ export function routeFromBrokerReceipt(receipt) {
270
+ const problems = [];
271
+ const attribution = receipt?.attribution;
272
+ const toolchain = typeof receipt?.toolchain === "string" && receipt.toolchain.trim() ? receipt.toolchain : null;
273
+ if (!attribution || typeof attribution !== "object") problems.push("the receipt carries no attribution");
274
+ else if (attribution.available !== true) problems.push(`the receipt's attribution is unavailable (${attribution.reason ?? "no reason"})`);
275
+ else if (!Array.isArray(attribution.units)) problems.push("the receipt's attribution lists no units");
276
+ else if (Number(attribution.truncated ?? 0) > 0) problems.push(`the receipt's attribution is truncated (${attribution.truncated} unit(s) not listed)`);
277
+ if (toolchain === null) problems.push("the receipt carries no toolchain identity");
278
+ const units = Array.isArray(attribution?.units) ? attribution.units.map((u) => ({ package: u.package, packageId: u.packageId ?? null, target: u.target, kind: u.kind ?? "", errors: Number(u.errors ?? 0), warnings: Number(u.warnings ?? 0), status: Number(u.errors ?? 0) > 0 ? "failed" : "clean", fresh: u.fresh ?? null })) : [];
279
+ const exit = receipt?.exit;
280
+ const exitCode = typeof exit === "number" ? exit : typeof exit?.code === "number" ? exit.code : typeof exit?.status === "number" ? exit.status : null;
281
+ if (exitCode === null) problems.push("the receipt carries no exit status");
282
+ const aggregate = problems.length || exitCode !== 0 || units.some((u) => u.status === "failed") ? "FAILED" : "PASSED";
283
+ return { aggregate, code: problems.length ? ROUTE_CODES.receiptEvidence : null, units, problems, toolchain, complete: aggregate === "PASSED" };
284
+ }
285
+
286
+ // ---- sidecar-bound scope (row 8.8) -----------------------------------------------------------------------------------------------------
287
+
288
+ /**
289
+ * The requested packages that declare sidecars (a Tauri `bundle.externalBin`): their build needs the sidecars staged first, which the
290
+ * snapshot route cannot do until item 7 row 7.05 lands. The route never rewrites the request to leave them out (signed D8-1 allows
291
+ * only its three additions): it refuses the whole request, BLOCKED with RK_CLIPPY_SIDECAR_BOUND, before class A, the sync or any
292
+ * compile. `dirOf(pkg)` is the package's directory as `exists` and `readFile` read it (the commit's tree, or the snapshot).
293
+ */
294
+ export function sidecarBoundMembers({ scope, dirOf, exists, readFile }) {
295
+ if (!scope) return [];
296
+ return scope.members.filter((p) => sidecarBoundCrate({ dir: dirOf(p), exists, readFile }).bound).map((p) => p.name).sort();
297
+ }
298
+
299
+ const sidecarRefusal = (tag, bound, where) => `${tag} BLOCKED: ${ROUTE_CODES.sidecarBound}: the requested scope holds sidecar-bound package(s) ${bound.join(", ")} (${where}); they need their sidecars staged before they build, which a commit snapshot cannot do until item 7 row 7.05; the route does not rewrite the request to leave them out; nothing compiled`;
300
+
301
+ // ---- the route ----------------------------------------------------------------------------------------------------------------------
302
+
303
+ const BLOCKED = 1; const REFUSED_EXIT = 2;
304
+ const resultLine = (o, tag, value) => o.out(`${tag} clippy route result ${JSON.stringify(value)}`);
305
+
306
+ /**
307
+ * `rightkit-gate clippy`: the entry. `args` are the words after `clippy`. `deps` (from the gate's main): `main` (the gate entry, for
308
+ * the door's own full class A), `io` (its overrides), `absoluteEnv` (the inherited locations a snapshot compile requires absolute),
309
+ * and, for tests, overrides of `resolveExecutor`, `snapshot` (snapshot.mjs), `snapshotCheck` (snapshot-check.mjs), `ownership`
310
+ * (build-ownership.mjs), `acquireOwnership`, `syncSnapshot`, `runClassA`, `runCargo` and `shim` (cargo-target-shim.mjs).
311
+ */
312
+ export async function clippyCommand(o, args, deps = {}) {
313
+ const tag = "rightkit-gate:";
314
+ const parsed = parseClippyArgv(args);
315
+ if (parsed.help) { o.out(CLIPPY_USAGE); return 0; }
316
+ if (parsed.refusal) { o.err(`${tag} ${parsed.refusal.code}: ${parsed.refusal.message}; nothing ran`); return REFUSED_EXIT; }
317
+
318
+ // The checkout: where HEAD's commit and the object database come from. Its working files are never read.
319
+ const top = o.spawn("git", ["rev-parse", "--show-toplevel"], { cwd: o.cwd });
320
+ if (failed(top) || !text(top).trim()) { o.err(`${tag} not inside a git repository; nothing ran`); return REFUSED_EXIT; }
321
+ let root; let cwdReal;
322
+ try { root = fs.realpathSync.native(path.resolve(text(top).trim())); cwdReal = fs.realpathSync.native(path.resolve(o.cwd)); } catch (e) { o.err(`${tag} cannot resolve the checkout: ${e.message}; nothing ran`); return REFUSED_EXIT; }
323
+ const relCwd = inside(cwdReal, root) ? posix(path.relative(root, cwdReal)) : "";
324
+ // A relative --manifest-path resolves inside the snapshot and may not leave it (resolved lexically against the working directory).
325
+ if (parsed.manifestPath !== null) {
326
+ const resolved = path.posix.normalize(path.posix.join(relCwd || ".", posix(parsed.manifestPath)));
327
+ if (resolved === ".." || resolved.startsWith("../")) { o.err(`${tag} ${ROUTE_CODES.manifestEscapes}: --manifest-path ${parsed.manifestPath} resolves outside the checkout's tree, so outside the snapshot; nothing ran`); return REFUSED_EXIT; }
328
+ }
329
+
330
+ // The executor is the gate's, never the request's: Mac and Dell are broker hosts, Linux is always direct (E2, E3).
331
+ const which = (cmd) => { const r = o.spawn(o.platform === "win32" ? "where" : "which", [cmd]); return failed(r) ? [] : text(r).split(/\r?\n/).map((l) => l.trim()).filter(Boolean); };
332
+ const executor = (deps.resolveExecutor ?? defaultResolveExecutor)({ env: o.env, platform: o.platform, exists: o.exists, which, readFile: o.readFile });
333
+ if (executor?.env && o.env) Object.assign(o.env, executor.env);
334
+ if (executor?.note) o.out(`${tag} ${executor.note}`);
335
+ if (executor.kind === "refuse") {
336
+ o.err(`${tag} BLOCKED: ${ROUTE_CODES.brokerUnavailable}: ${executor.problem}; the route never compiles around the broker, and no launch was attempted (no class A, no sync, no compile)`);
337
+ resultLine(o, tag, { result: "BLOCKED", code: ROUTE_CODES.brokerUnavailable, launched: false });
338
+ return BLOCKED;
339
+ }
340
+ if (executor.kind === "broker") {
341
+ o.err(`${tag} BLOCKED: ${ROUTE_CODES.brokerCarrier}: on this broker host a Clippy route compiles the commit snapshot only through the RustRight snapshot carrier (item 7 row 7.12), which the gate does not wire yet; nothing ran and cargo is never run directly here (rows 8.3, 8.10, 8.13)`);
342
+ resultLine(o, tag, { result: "BLOCKED", code: ROUTE_CODES.brokerCarrier, blockedOn: ["item7:7.12"], launched: false });
343
+ return BLOCKED;
344
+ }
345
+ if (!directPlatform(o.platform) || o.env.GITHUB_ACTIONS === "true") {
346
+ o.err(`${tag} BLOCKED: ${ROUTE_CODES.hostUnsupported}: the route builds a gate-owned commit snapshot on a Linux execution host; hosted CI runs Clippy through its own workflow; nothing ran`);
347
+ return BLOCKED;
348
+ }
349
+
350
+ const snap = deps.snapshot ?? await import("./snapshot.mjs");
351
+ const sc = deps.snapshotCheck ?? await import("./snapshot-check.mjs");
352
+ const ownershipApi = deps.ownership ?? await import("./build-ownership.mjs");
353
+ let selected; let snapshotsAt; let snapshotDir;
354
+ try {
355
+ selected = snap.resolveCommit(root, "HEAD");
356
+ snapshotsAt = o.snapshotRoot ?? snap.snapshotRoot({ platform: o.platform, env: o.env });
357
+ snapshotDir = path.join(snapshotsAt, snap.repositoryOf(root).key);
358
+ } catch (e) { o.err(`${tag} cannot select HEAD's commit: ${e.message}; nothing ran`); return REFUSED_EXIT; }
359
+
360
+ // The output directory is the checkout's, resolved by the gate (row 8.12): a caller's CARGO_TARGET_DIR that points into the snapshot would
361
+ // be a second output directory, and CARGO_HOME, sccache and temp are inherited only as absolute values.
362
+ for (const name of ["CARGO_TARGET_DIR", "CARGO_BUILD_TARGET_DIR"]) {
363
+ const v = o.env[name];
364
+ if (typeof v === "string" && v !== "" && inside(path.resolve(cwdReal, v), snapshotDir)) { o.err(`${tag} ${ROUTE_CODES.outputInSnapshot}: ${name}=${v} points into the gate-owned snapshot ${snapshotDir}; outputs go to the checkout's target directory, never under the snapshot; nothing ran`); return REFUSED_EXIT; }
365
+ }
366
+ const relative = (deps.absoluteEnv ?? []).filter((n) => typeof o.env[n] === "string" && o.env[n] !== "" && !path.isAbsolute(o.env[n]));
367
+ if (relative.length) { o.err(`${tag} BLOCKED: ${relative.map((n) => `${n}=${o.env[n]}`).join(", ")} ${relative.length > 1 ? "are" : "is"} relative; a snapshot compile inherits CARGO_HOME, sccache and temp only as absolute paths; nothing ran`); return BLOCKED; }
368
+
369
+ // Sidecar-bound scope, refused BEFORE ownership, the sync, class A or any compile (row 8.8, lead decision 2026-10-29). Each Tauri
370
+ // configuration is read from HEAD's commit, never from the checkout's files. A commit that declares no sidecar anywhere needs no
371
+ // further look (no cargo process starts before class A). Otherwise the requested packages come from the checkout's
372
+ // `cargo metadata --no-deps --offline --locked` (no compile, nothing written) and the request is refused when one of them declares
373
+ // sidecars. The snapshot's own metadata re-checks it below, so a checkout whose workspace differs from the commit cannot slip a
374
+ // sidecar-bound package through.
375
+ const commitExists = (f) => !failed(o.spawn("git", ["cat-file", "-e", `${selected.commit}:${posix(f)}`], { cwd: root }));
376
+ const commitRead = (f) => { const r = o.spawn("git", ["cat-file", "-p", `${selected.commit}:${posix(f)}`], { cwd: root }); if (failed(r)) throw new Error(`cannot read ${f} in ${selected.commit}`); return text(r); };
377
+ const listed = o.spawn("git", ["ls-tree", "-r", "--name-only", selected.commit], { cwd: root });
378
+ const confDirs = failed(listed) ? [""] : [...new Set(text(listed).split(/\r?\n/).filter((f) => /(^|\/)(tauri\.conf\.json5?|Tauri\.toml)$/.test(f)).map((f) => path.posix.dirname(f)).map((d) => (path.posix.basename(d) === "src-tauri" ? path.posix.dirname(d) : d)))];
379
+ const commitDeclaresSidecars = confDirs.some((d) => d === "" || sidecarBoundCrate({ dir: d, exists: commitExists, readFile: commitRead }).bound);
380
+ let preBound = [];
381
+ if (commitDeclaresSidecars) {
382
+ const preMeta = o.spawn("cargo", ["metadata", "--no-deps", "--offline", "--locked", "--format-version", "1", ...(parsed.manifestPath ? ["--manifest-path", parsed.manifestPath] : [])], { cwd: cwdReal, env: o.env });
383
+ let preScope = null;
384
+ try { if (!failed(preMeta)) preScope = resolveScope({ before: parsed.before, metadata: JSON.parse(text(preMeta)), directory: parsed.manifestPath ? path.dirname(path.resolve(cwdReal, parsed.manifestPath)) : cwdReal }); } catch { preScope = null; }
385
+ preBound = sidecarBoundMembers({ scope: preScope, dirOf: (p) => posix(path.relative(root, path.dirname(p.manifest_path))) || ".", exists: commitExists, readFile: commitRead });
386
+ }
387
+ if (preBound.length) { o.err(sidecarRefusal(tag, preBound, "read from the commit")); resultLine(o, tag, { result: "BLOCKED", code: ROUTE_CODES.sidecarBound, blockedOn: ["item7:7.05"], bound: preBound, launched: false }); return BLOCKED; }
388
+
389
+ const timings = {}; const lap = (name, since) => { timings[name] = Date.now() - since; };
390
+ // Build ownership first (E6): held from before the sync until the diagnostics are written. A busy host runs nothing and syncs nothing.
391
+ let own;
392
+ try { own = (deps.acquireOwnership ?? ownershipApi.acquireBuildOwnership)({ file: ownershipApi.ownershipFile({ platform: o.platform, beside: o.snapshotRoot }), stage: "clippy", snapshot: snapshotDir, commit: selected.commit, platform: o.platform, env: o.env }); }
393
+ catch (e) { if (e.code !== "busy" && e.code !== "refused") throw e; o.err(`${tag} BLOCKED: ${e.message}; ${e.code === "refused" ? "nothing ran, nothing created" : "the route never re-syncs a snapshot another request holds; nothing ran"}`); return BLOCKED; }
394
+ o.out(`${tag} build ownership ${own.joined ? "joined" : "held"} ${own.handle}`);
395
+ const abort = o.abort ?? new AbortController();
396
+ let shimDir = null; let messagesFile = null;
397
+ const stopped = []; const onSignal = (sig) => () => { stopped.push(sig); abort.abort(); };
398
+ const handlers = o.abort ? [] : [["SIGINT", onSignal("SIGINT")], ["SIGTERM", onSignal("SIGTERM")]];
399
+ for (const [sig, h] of handlers) process.on(sig, h);
400
+ try {
401
+ let t = Date.now();
402
+ let synced;
403
+ try { synced = (deps.syncSnapshot ?? snap.syncSnapshot)({ checkout: root, rev: selected.commit, root: snapshotsAt }); }
404
+ catch (e) { o.err(`${tag} ${e.code === "error" ? "RK_CHECK_ERROR" : "BLOCKED"}: snapshot ${e.message}; nothing ran`); return e.code === "error" ? REFUSED_EXIT : BLOCKED; }
405
+ lap("sync", t);
406
+ o.out(`${tag} snapshot ${synced.path} is commit ${synced.commit} tree ${synced.tree} (written=${synced.written} removed=${synced.removed} unchanged=${synced.unchanged})`);
407
+
408
+ // FULL class A first (D8-2, E1): the door's own check of the snapshot. A red class A starts no Clippy.
409
+ t = Date.now();
410
+ const classA = await (deps.runClassA ?? (() => deps.main(["check", "--commit", synced.commit, "--full-a", "--snapshot", synced.path, "--rule-tier", "release"], { ...(deps.io ?? {}), cwd: root, buildOwnership: own })))();
411
+ lap("classA", t);
412
+ if (classA !== 0) { o.err(`${tag} FAILED: class A is red for ${synced.commit.slice(0, 12)}; Clippy was not started`); resultLine(o, tag, { result: "FAILED", reason: "class-a", exit: classA, commit: synced.commit, clippyStarted: false }); return classA === REFUSED_EXIT ? REFUSED_EXIT : BLOCKED; }
413
+
414
+ const realCheckout = fs.realpathSync.native(root);
415
+ const realSnapshot = fs.realpathSync.native(synced.path);
416
+ let treeAtStart;
417
+ try { treeAtStart = sc.verifySnapshotDir({ checkout: realCheckout, dir: synced.path, commit: synced.commit, tree: synced.tree, when: "before Clippy" }); }
418
+ catch (e) { o.err(`${tag} RK_CHECK_ERROR: ${e.message}; nothing was compiled`); return REFUSED_EXIT; }
419
+
420
+ // The snapshot's working directory for the request, and the Cargo workspace it names, resolved from the commit's own manifests.
421
+ const snapCwd = relCwd ? path.join(synced.path, ...relCwd.split("/")) : synced.path;
422
+ if (!fs.existsSync(snapCwd)) { o.err(`${tag} BLOCKED: the working directory ${relCwd} is not in commit ${synced.commit.slice(0, 12)}; run from a directory the commit holds; nothing compiled`); return BLOCKED; }
423
+ const realCargo = which("cargo").find((c) => path.isAbsolute(c));
424
+ if (!realCargo) { o.err(`${tag} BLOCKED: no cargo on PATH to wrap for the snapshot compile; nothing compiled`); return BLOCKED; }
425
+ const located = o.spawn("cargo", ["locate-project", "--workspace", "--message-format", "plain", ...(parsed.manifestPath ? ["--manifest-path", parsed.manifestPath] : [])], { cwd: snapCwd, env: o.env });
426
+ if (failed(located) || !path.isAbsolute(text(located).trim())) { o.err(`${tag} BLOCKED: the request's Cargo workspace cannot be resolved in the snapshot (${why(located) || "no manifest"}); nothing compiled`); return BLOCKED; }
427
+ const workspaceDir = fs.realpathSync.native(path.dirname(text(located).trim()));
428
+ if (!inside(workspaceDir, realSnapshot)) { o.err(`${tag} BLOCKED: the Cargo workspace ${workspaceDir} is outside the snapshot; nothing compiled`); return BLOCKED; }
429
+ const cargoDir = posix(path.relative(realSnapshot, workspaceDir)) || ".";
430
+ const checkoutManifest = path.join(root, ...(cargoDir === "." ? [] : cargoDir.split("/")), "Cargo.toml");
431
+ if (!(o.exists ?? fs.existsSync)(checkoutManifest)) { o.err(`${tag} BLOCKED: the checkout has no ${posix(path.relative(root, checkoutManifest))}, so the output directory of workspace ${cargoDir} cannot be resolved; nothing compiled`); return BLOCKED; }
432
+ // The same resolution the door's snapshot compile makes (bin/rightkit-gate.mjs, "output directory for workspace").
433
+ const metaOut = o.spawn("cargo", ["metadata", "--offline", "--format-version", "1", "--no-deps", "--manifest-path", checkoutManifest], { cwd: path.dirname(checkoutManifest), env: o.env });
434
+ let outputDir = null;
435
+ try { outputDir = failed(metaOut) ? null : JSON.parse(text(metaOut)).target_directory; } catch { outputDir = null; }
436
+ if (typeof outputDir !== "string" || !path.isAbsolute(outputDir)) { o.err(`${tag} BLOCKED: cargo metadata in the checkout reported no absolute target_directory for ${cargoDir}${failed(metaOut) ? ` (${why(metaOut)})` : ""}; nothing compiled`); return BLOCKED; }
437
+ outputDir = path.resolve(outputDir);
438
+ if (inside(outputDir, realSnapshot)) { o.err(`${tag} ${ROUTE_CODES.outputInSnapshot}: the resolved output directory ${outputDir} is inside the snapshot; nothing compiled`); return REFUSED_EXIT; }
439
+ o.out(`${tag} output directory for workspace ${cargoDir}: ${outputDir} (the checkout's)`);
440
+
441
+ // The item 7 cargo wrapper binds every cargo launch to that directory or refuses it.
442
+ const shimApi = deps.shim ?? await import("./cargo-target-shim.mjs");
443
+ let shim;
444
+ try { shim = shimApi.installCargoShim({ base: os.tmpdir(), snapshotRoot: synced.path, targetDirs: { [cargoDir]: outputDir }, realCargo, platform: o.platform, onDir: (dir) => { shimDir = dir; } }); }
445
+ catch (e) { o.err(`${tag} BLOCKED: the runtime cargo wrapper cannot be installed (${e.message}); nothing compiled`); return BLOCKED; }
446
+ const scanEnv = shimApi.shimScanEnv(o.env, shim);
447
+ o.out(`${tag} runtime cargo wrapper ${shim.shim} (identity ${shim.identity.slice(0, 16)}) -> ${realCargo}`);
448
+
449
+ // The toolchain identity this run compiled with (BROKER-CONTRACT B5: the same engine and toolchain for batch and per-crate runs).
450
+ const toolchain = toolchainIdentity({ spawn: o.spawn, env: o.env, cwd: snapCwd });
451
+ if (!toolchain) { o.err(`${tag} BLOCKED: ${ROUTE_CODES.toolchainUnreadable}: \`cargo -vV\` or \`rustc -vV\` printed nothing the gate can read; nothing compiled`); return BLOCKED; }
452
+
453
+ // The requested package set (blocked-unit detection and the sidecar re-check), read from the snapshot's own metadata.
454
+ let scope = null;
455
+ const metaScope = o.spawn(shim.shim, ["metadata", "--no-deps", "--format-version", "1", "--locked", ...(parsed.manifestPath ? ["--manifest-path", parsed.manifestPath] : [])], { cwd: snapCwd, env: scanEnv });
456
+ try { if (!failed(metaScope)) scope = resolveScope({ before: parsed.before, metadata: JSON.parse(text(metaScope)), directory: parsed.manifestPath ? path.dirname(path.resolve(snapCwd, parsed.manifestPath)) : snapCwd }); } catch { scope = null; }
457
+
458
+ // Sidecar-bound scope (row 8.8): refused before the sync above; this re-check on the snapshot's own metadata is the backstop.
459
+ const bound = sidecarBoundMembers({ scope, dirOf: (p) => path.dirname(p.manifest_path) });
460
+ if (bound.length) { o.err(sidecarRefusal(tag, bound, "found in the snapshot, after the checkout's own metadata showed none")); resultLine(o, tag, { result: "BLOCKED", code: ROUTE_CODES.sidecarBound, blockedOn: ["item7:7.05"], bound, launched: false }); return BLOCKED; }
461
+ const invocations = [];
462
+
463
+ t = Date.now();
464
+ let run = { messages: [], stderr: "", exit: null, signal: null, startError: null };
465
+ const stateDir = path.join(gateStateDir(commonDirOf(o, root)), "clippy-route");
466
+ fs.mkdirSync(stateDir, { recursive: true });
467
+ const requestId = crypto.randomUUID();
468
+ messagesFile = path.join(stateDir, `${requestId}.messages.jsonl`);
469
+ {
470
+ const r = await (deps.runCargo ?? defaultSpawnAsync)(shim.shim, ["clippy", ...parsed.final], { cwd: snapCwd, env: scanEnv, signal: abort.signal, stdoutFile: messagesFile });
471
+ invocations.push({ role: "requested", argv: ["clippy", ...parsed.final], exit: r.status ?? null });
472
+ let body = "";
473
+ try { body = fs.readFileSync(messagesFile, "utf8"); } catch { body = String(r.stdout ?? ""); }
474
+ run = { messages: parseCargoMessages(body).messages, stderr: String(r.stderr ?? ""), exit: r.status ?? null, signal: stopped[0] ?? null, startError: r.error && !r.cancelled ? r.error.message : null };
475
+ }
476
+ lap("cargo", t);
477
+ if (stopped.length) { fs.rmSync(messagesFile, { force: true }); o.err(`${tag} stopped by ${stopped[0]}; no result`); return stopped[0] === "SIGINT" ? 130 : 143; }
478
+
479
+ // The result covers the commit only if the snapshot is still its tree after cargo exits (E6): otherwise the diagnostics are discarded.
480
+ t = Date.now();
481
+ let treeAtExit;
482
+ try { treeAtExit = sc.verifySnapshotDir({ checkout: realCheckout, dir: synced.path, commit: synced.commit, tree: synced.tree, when: "after Clippy" }); }
483
+ catch (e) { fs.rmSync(messagesFile, { force: true }); o.err(`${tag} RK_CHECK_ERROR: ${e.message}; the diagnostics are discarded, they cover no known bytes`); resultLine(o, tag, { result: "FAILED", code: "RK_CHECK_ERROR", discarded: true, commit: synced.commit }); return REFUSED_EXIT; }
484
+ lap("afterExitBind", t);
485
+ if (!own.held()) { fs.rmSync(messagesFile, { force: true }); o.err(`${tag} RK_CHECK_ERROR: the build ownership ${own.handle} is no longer held; the diagnostics are discarded`); return REFUSED_EXIT; }
486
+
487
+ t = Date.now();
488
+ const summary = summarizeRun({ ...run, scope });
489
+ const diagnosticsPath = path.join(stateDir, `${requestId}.diagnostics.json`);
490
+ fs.writeFileSync(diagnosticsPath, `${JSON.stringify({ schema: ROUTE_DIAGNOSTICS_SCHEMA, requestId, commit: { sha: synced.commit, tree: synced.tree }, units: summary.units, blocked: summary.blocked, diagnostics: summary.diagnostics }, null, 2)}\n`);
491
+ fs.rmSync(messagesFile, { force: true });
492
+ lap("diagnostics", t);
493
+ const receipt = {
494
+ schema: ROUTE_RECEIPT_SCHEMA, requestId, executor: "direct", issuedAt: new Date().toISOString(),
495
+ commit: { sha: synced.commit, tree: synced.tree },
496
+ snapshot: { path: synced.path, treeAtStart, treeAtExit },
497
+ ownership: { handle: own.handle },
498
+ outputDir, outputDirs: { [cargoDir]: outputDir },
499
+ cargoWrapper: { identity: shim.identity, workspaces: shim.workspaces },
500
+ toolchain,
501
+ argv: { request: parsed.request, final: parsed.final, transformations: parsed.transformations, already: parsed.already },
502
+ invocations,
503
+ exit: { status: run.exit, signal: run.signal }, diagnosticsPath, timingsMs: timings,
504
+ result: { aggregate: summary.aggregate, complete: summary.complete, completeness: summary.completeness, units: summary.units, blocked: summary.blocked, failedPackages: summary.failedPackages },
505
+ rebuilt: null,
506
+ };
507
+ const receiptPath = path.join(stateDir, `${requestId}.json`);
508
+ fs.writeFileSync(receiptPath, `${JSON.stringify(receipt, null, 2)}\n`);
509
+ reportRoute(o, tag, receipt, summary, receiptPath);
510
+ return summary.aggregate === "PASSED" ? 0 : BLOCKED;
511
+ } finally {
512
+ for (const [sig, h] of handlers) process.off(sig, h);
513
+ if (shimDir) fs.rmSync(shimDir, { recursive: true, force: true });
514
+ const r = own.release();
515
+ if (r?.released === false && !own.joined) o.err(`${tag} build ownership ${own.handle} kept: ${r.why}`);
516
+ }
517
+ }
518
+
519
+ function commonDirOf(o, root) {
520
+ const r = o.spawn("git", ["rev-parse", "--path-format=absolute", "--git-common-dir"], { cwd: root });
521
+ return failed(r) ? path.join(root, ".git") : text(r).trim();
522
+ }
523
+
524
+ /** The toolchain identity in the broker's shape (`name|cargo:<sha256>|rustc:<sha256>`, over `-vV`), or null when unreadable. */
525
+ export function toolchainIdentity({ spawn, env, cwd }) {
526
+ const cargo = spawn("cargo", ["-vV"], { cwd, env });
527
+ const rustc = spawn("rustc", ["-vV"], { cwd, env });
528
+ if (failed(cargo) || failed(rustc) || !text(cargo).trim() || !text(rustc).trim()) return null;
529
+ return `${env?.RUSTUP_TOOLCHAIN ?? "direct"}|cargo:${sha256(text(cargo).trim())}|rustc:${sha256(text(rustc).trim())}`;
530
+ }
531
+
532
+ const SHOWN_DIAGNOSTICS = 400;
533
+ function reportRoute(o, tag, receipt, summary, receiptPath) {
534
+ const shown = summary.diagnostics.slice(0, SHOWN_DIAGNOSTICS);
535
+ for (const d of shown) if (d.rendered) o.err(d.rendered.trimEnd());
536
+ if (summary.diagnostics.length > shown.length) o.err(`${tag} ${summary.diagnostics.length - shown.length} more diagnostic(s) are in ${receipt.diagnosticsPath}`);
537
+ const failedUnits = summary.units.filter((u) => u.status === "failed");
538
+ o.out(`${tag} clippy route ${summary.aggregate} for ${receipt.commit.sha.slice(0, 12)}: ${summary.units.length} unit(s), ${failedUnits.length} failed, ${summary.blocked.length} blocked; ${summary.completeness}`);
539
+ for (const u of failedUnits) o.out(` failed ${u.package} (${u.target}${u.kind ? `, ${u.kind}` : ""}): ${u.errors} error(s), ${u.warnings} warning(s)`);
540
+ for (const b of summary.blocked) o.out(` blocked ${b.package}${b.blockedBy?.length ? `: dependency ${b.blockedBy.join(", ")} failed` : b.reason ? `: ${b.reason}` : ": a dependency failed to build"}`);
541
+ o.out(`${tag} clippy route receipt ${receiptPath}`);
542
+ resultLine(o, tag, { result: summary.aggregate, complete: summary.complete, commit: receipt.commit.sha, failed: failedUnits.length, blocked: summary.blocked.length, receipt: receiptPath });
543
+ }
package/codesign-shim.mjs CHANGED
@@ -23,7 +23,17 @@ export async function runCodesignShim(argv, { cwd = process.cwd(), env = process
23
23
  privatePath(paths.config, 0o600, { io });
24
24
  roots = JSON.parse(io.readFileSync(paths.config, "utf8")).roots;
25
25
  }
26
- const { targets } = parseCodesignArgv(argv, cwd);
26
+ const { targets, mode } = parseCodesignArgv(argv, cwd);
27
+ if (mode !== "sign") {
28
+ // Verify & display read the code they are given and need no identity, so
29
+ // they run the system codesign here. The agent's roots are the signing
30
+ // policy (where the identity may be applied), not a read policy: a dev
31
+ // install verifies its installed copy under /Applications, which no
32
+ // signing root contains.
33
+ const direct = run("/usr/bin/codesign", argv, { cwd, env, encoding: "utf8", shell: false, windowsHide: true });
34
+ if (direct.error) throw direct.error;
35
+ return { status: direct.status ?? 1, stdout: String(direct.stdout ?? ""), stderr: String(direct.stderr ?? "") };
36
+ }
27
37
  const report = await request({ schema: 1, type: "codesign-argv", argv, cwd, targets,
28
38
  allowedRoots: roots.filter((root) => io.existsSync(root)), timeoutMs: 180000 }, { paths });
29
39
  const result = report.results?.[0];