@rightkit/release 0.6.3 → 0.6.5

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 (48) hide show
  1. package/README.md +67 -5
  2. package/admission-files.mjs +279 -0
  3. package/admission-host.mjs +403 -0
  4. package/admission.mjs +146 -0
  5. package/app-check.mjs +869 -0
  6. package/bin/darwin/rightkit-swap +0 -0
  7. package/bin/rightkit-gate.mjs +68 -29
  8. package/build-release.mjs +81 -21
  9. package/cli/right-release.mjs +39 -5
  10. package/deterministic-scans.mjs +42 -15
  11. package/development.mjs +3 -2
  12. package/hosted-workflow-policy.mjs +111 -0
  13. package/inventory-check.mjs +12 -1
  14. package/ios-dev.mjs +2 -2
  15. package/ios-host.mjs +7 -7
  16. package/ios-package-recipe.mjs +2 -2
  17. package/local-publish.mjs +1 -1
  18. package/local-qualify.mjs +3 -3
  19. package/local-runtime.mjs +1 -1
  20. package/ownership-dependency-allowlist.json +2 -0
  21. package/package.json +5 -3
  22. package/pipeline-admission.mjs +5 -4
  23. package/pipeline-receipts.mjs +302 -302
  24. package/pipeline-scan.mjs +3 -3
  25. package/pipeline-tools.mjs +175 -171
  26. package/prebuild-gates.mjs +191 -0
  27. package/progress-control.mjs +1 -1
  28. package/publish-update.mjs +4 -3
  29. package/qa-admission-policy.json +5 -0
  30. package/release-config.mjs +353 -330
  31. package/release-qa-audit.mjs +37 -11
  32. package/release-qa-command.mjs +46 -5
  33. package/release-qa-fixtures.mjs +127 -0
  34. package/release-qa-floor.json +6 -1
  35. package/release-qa-hardening.mjs +173 -0
  36. package/release-qa-policy.mjs +168 -0
  37. package/release-qa-receipt.mjs +21 -1
  38. package/release-switch-scan.mjs +137 -6
  39. package/release.mjs +333 -231
  40. package/rightapps-register.mjs +3 -1
  41. package/rightkit-drift.mjs +1 -1
  42. package/rightkit-ownership.json +15 -5
  43. package/rightkit-versions.json +50 -37
  44. package/stage-sidecar.mjs +17 -15
  45. package/suite-api-literal.mjs +109 -0
  46. package/suite-api.generated.mjs +78 -0
  47. package/swap-helper-bundle.mjs +2 -2
  48. package/win-package-recipe.mjs +7 -4
package/development.mjs CHANGED
@@ -50,7 +50,7 @@ export function developmentEnvironment(env = process.env) {
50
50
  result[pathKey] = [agentBin, result[pathKey]].filter(Boolean).join(path.delimiter);
51
51
  }
52
52
  // Same default as the agent-bin `rightkit` shim: children that run the managed client directly
53
- // (resolveCommand("rightkit"), build scripts) must not lose the broker endpoint.
53
+ // (resolveCommand("rustright"), build scripts) must not lose the broker endpoint.
54
54
  const socket = discoverBrokerSocket(result);
55
55
  if (!result.RIGHTKIT_BUILD_BROKER_SOCKET && socket && existsSync(socket)) result.RIGHTKIT_BUILD_BROKER_SOCKET = socket;
56
56
  }
@@ -298,7 +298,8 @@ function execute(command, { root, env, dryRun, run, label }) {
298
298
  export function resolveCommand(command, args, { root, env = process.env } = {}) {
299
299
  if (command === "node") return { command: process.execPath, args };
300
300
  if (command === "pnpm") return { command: process.execPath, args: [resolvePnpmCli({ root, env }), ...args] };
301
- if (command === "rightkit") {
301
+ if (command === "rightkit") throw new Error("`rightkit` is the retired name of the RustRight client (rustright#91); use rustright (cmd: \"rustright\")");
302
+ if (command === "rustright") {
302
303
  const client = managedRuntimeClient(env);
303
304
  if (!client) throw new Error("installed RightKit runtime client is unavailable for development command");
304
305
  const socket = discoverBrokerSocket(env);
@@ -0,0 +1,111 @@
1
+ // hostedWorkflows: { policy: "declared-unrelated", files: { "<file>.yml": "<reason>" | { reason, idToken } } }
2
+ //
3
+ // For a repository that ships more than the released app (a monorepo whose web or server pipeline is a hand-written
4
+ // GitHub workflow). The released app still builds, signs and uploads only through right-release on its native host;
5
+ // every hosted workflow must be declared with the reason it exists, and none may be able to build, sign or upload a
6
+ // release. What makes that hold is credentials: an admitted workflow reaches no secret but GITHUB_TOKEN (no secrets
7
+ // context, no `secrets: inherit`, no reusable workflow, no escape that could spell one), mints no OIDC token unless the
8
+ // file declares why (`idToken`), and runs only commit-pinned actions. Without an Apple, Azure or store credential it
9
+ // cannot sign or upload as Orthic Labs. The command and artifact patterns are a denylist backstop, not the guarantee:
10
+ // a script file the workflow runs is not read.
11
+ export const DECLARED_UNRELATED_POLICY = "declared-unrelated";
12
+
13
+ const FILE_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]{0,60}\.ya?ml$/;
14
+ const MIN_REASON = 12;
15
+
16
+ // Release machinery a declared-unrelated workflow must never run. Matched on the whole file, comments included: a comment cannot hide a command.
17
+ const RELEASE_MACHINERY = [
18
+ [/\bright-release\b/, "right-release"],
19
+ [/\bxcodebuild\b[^\n]*(?:\barchive\b|-exportArchive\b|-exportOptionsPlist\b)/, "xcodebuild archive/export"],
20
+ [/\bcodesign\b/, "codesign"],
21
+ [/\bnotarytool\b|\bstapler\b/, "notarization"],
22
+ [/\baltool\b|\biTMSTransporter\b|\bupload-testflight\b|\bapp-store-connect\b/i, "App Store upload"],
23
+ [/\bfastlane\b/, "fastlane"],
24
+ [/\bsecurity\s+import\b|\bapple-actions\//, "Apple signing keychain import"],
25
+ [/\bproductbuild\b|\bpkgbuild\b/, "installer package build"],
26
+ [/\bsigntool\b|\bAzureSignTool\b|\bazure\/(?:trusted|artifact)-signing/i, "Windows code signing"],
27
+ [/\btauri(?:-apps\/tauri-action|\s+build\b)/, "Tauri app build"],
28
+ ];
29
+
30
+ // A `uses:` must be a local action (./...), a docker image by digest, or owner/repo[/path]@<40-hex commit>.
31
+ const PINNED_USES = /^(?:\.\/[^\s]*|docker:\/\/[^\s@]+@sha256:[0-9a-f]{64}|[A-Za-z0-9_.-]+\/[A-Za-z0-9_./-]+@[0-9a-f]{40})$/;
32
+
33
+ const normalize = (source) => source.replace(/\r\n?/g, "\n");
34
+ const isObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
35
+ const entryReason = (entry) => (isObject(entry) ? entry.reason : entry);
36
+
37
+ /** Shape problems of a declared-unrelated hostedWorkflows value (an empty list when it is well formed). */
38
+ export function declaredUnrelatedShapeProblems(value) {
39
+ if (!isObject(value)) return ["hostedWorkflows must be a policy string or an object"];
40
+ const problems = [];
41
+ for (const key of Object.keys(value)) if (key !== "policy" && key !== "files") problems.push(`hostedWorkflows.${key} is not a known field (policy, files)`);
42
+ if (value.policy !== DECLARED_UNRELATED_POLICY) problems.push(`hostedWorkflows.policy must be "${DECLARED_UNRELATED_POLICY}"`);
43
+ const files = value.files;
44
+ if (!isObject(files) || Object.keys(files).length === 0) {
45
+ problems.push("hostedWorkflows.files must map each workflow file name to the reason it exists");
46
+ return problems;
47
+ }
48
+ for (const [file, entry] of Object.entries(files)) {
49
+ if (!FILE_NAME.test(file)) problems.push(`hostedWorkflows.files has an invalid workflow file name ${JSON.stringify(file)}`);
50
+ if (isObject(entry)) {
51
+ for (const key of Object.keys(entry)) if (key !== "reason" && key !== "idToken") problems.push(`hostedWorkflows.files["${file}"].${key} is not a known field (reason, idToken)`);
52
+ if (entry.idToken !== undefined && (typeof entry.idToken !== "string" || entry.idToken.trim().length < MIN_REASON)) problems.push(`hostedWorkflows.files["${file}"].idToken must say why the workflow mints an OIDC token (at least ${MIN_REASON} characters)`);
53
+ }
54
+ const reason = entryReason(entry);
55
+ if (typeof reason !== "string" || reason.trim().length < MIN_REASON) problems.push(`hostedWorkflows.files["${file}"] must say why the workflow exists (at least ${MIN_REASON} characters)`);
56
+ }
57
+ return problems;
58
+ }
59
+
60
+ function credentialRefusal(file, source, entry) {
61
+ // GitHub decodes YAML escapes before it evaluates expressions, so an escape could spell `secrets` or a command.
62
+ if (/\\[xuU]/.test(source)) return `${file} contains a \\x, \\u or \\U escape; a declared-unrelated workflow must spell every name literally`;
63
+ // The only secret is GITHUB_TOKEN; any other use of the word (secrets.X, secrets['X'], secrets: inherit, toJSON(secrets),
64
+ // join(secrets)) is refused. A path segment such as /run/secrets/ is not the context.
65
+ const rest = source.replace(/\bsecrets\.GITHUB_TOKEN\b/g, "");
66
+ if (/(?<![\w/-])secrets\b(?![\w/-])/i.test(rest)) return `${file} references secrets other than GITHUB_TOKEN (or passes the secrets context on); a declared-unrelated workflow can hold no signing or store credential`;
67
+ for (const [, ref] of source.matchAll(/^\s*(?:-\s+)?uses:\s*["']?([^\s"'#]+)/gm)) {
68
+ if (/\.github\/workflows\//.test(ref)) return `${file} calls the reusable workflow ${ref}; a called workflow is not checked`;
69
+ if (!PINNED_USES.test(ref)) return `${file} uses ${ref}, which is not pinned to a full commit sha (or a local action, or a docker image by digest)`;
70
+ }
71
+ if (/^\s*id-token\s*:\s*write\b/m.test(source) && !(isObject(entry) && entry.idToken)) return `${file} mints an OIDC token (id-token: write) without declaring why; set hostedWorkflows.files["${file}"] to { reason, idToken: "<why>" }`;
72
+ return null;
73
+ }
74
+
75
+ /**
76
+ * Why a declared-unrelated workflow set is refused, or null when it is admitted.
77
+ * workflows: the file names present in .github/workflows; read(file): its text; artifacts: release artifact paths
78
+ * from the config's targets (a workflow that names one could produce or ship it).
79
+ */
80
+ export function declaredUnrelatedRefusal({ declaration, workflows, read, artifacts = [] }) {
81
+ const shape = declaredUnrelatedShapeProblems(declaration);
82
+ if (shape.length) return shape.join("; ");
83
+ const declared = Object.keys(declaration.files).sort();
84
+ const present = [...workflows].sort();
85
+ const undeclared = present.filter((file) => !declared.includes(file));
86
+ if (undeclared.length) return `undeclared hosted workflow file(s): ${undeclared.join(", ")} (declare each in hostedWorkflows.files with its reason, or remove it)`;
87
+ const missing = declared.filter((file) => !present.includes(file));
88
+ if (missing.length) return `hostedWorkflows.files declares workflow file(s) that do not exist: ${missing.join(", ")}`;
89
+ for (const file of present) {
90
+ const source = normalize(read(file));
91
+ const credential = credentialRefusal(file, source, declaration.files[file]);
92
+ if (credential) return credential;
93
+ for (const [pattern, label] of RELEASE_MACHINERY) {
94
+ if (pattern.test(source)) return `${file} runs release machinery (${label}); the released app builds, signs and uploads only through right-release on its native host`;
95
+ }
96
+ for (const artifact of artifacts) {
97
+ if (artifact && source.includes(artifact)) return `${file} names the release artifact ${artifact}`;
98
+ }
99
+ }
100
+ return null;
101
+ }
102
+
103
+ /** Release artifact paths a config's targets declare (artifacts, iosPackage archive and ipa). */
104
+ export function releaseArtifactPaths(config) {
105
+ const paths = new Set();
106
+ for (const target of Object.values(config?.targets ?? {})) {
107
+ for (const artifact of target?.artifacts ?? []) if (typeof artifact === "string") paths.add(artifact);
108
+ for (const key of ["archive", "ipa"]) if (typeof target?.iosPackage?.[key] === "string") paths.add(target.iosPackage[key]);
109
+ }
110
+ return [...paths];
111
+ }
@@ -16,6 +16,7 @@ import {
16
16
  generateAdoption,
17
17
  generateLocal,
18
18
  } from "../../scripts/generate-index.mjs";
19
+ import { PORT_MATRIX_OUTPUTS, generatePortMatrix } from "../../scripts/generate-port-matrix.mjs";
19
20
 
20
21
  const HERE = dirname(fileURLToPath(import.meta.url));
21
22
  export const DEFAULT_REPO_ROOT = resolve(HERE, "..", "..");
@@ -781,6 +782,16 @@ function checkGenerated({ repoRoot, appsRoot, apps, findings }) {
781
782
  for (const rel of driftOf(repoRoot, adoption)) {
782
783
  findings.push(issue("generated", rel, "generated-doc-drift", "committed adoption report differs from the app repos; run generate-index.mjs"));
783
784
  }
785
+ // The port matrix reads the app repos like the adoption report. Its stamp, app directories and HEADs, and per-app check counts
786
+ // are carried over from the committed docs/port-matrix.json, so only a change in the data itself reads as drift.
787
+ try {
788
+ const matrix = generatePortMatrix({ root: repoRoot, workspace: appsRoot, carry: true });
789
+ for (const rel of driftOf(repoRoot, matrix.outputs)) {
790
+ findings.push(issue("generated", rel, "generated-doc-drift", "committed port matrix differs from the app repos; run generate-index.mjs"));
791
+ }
792
+ } catch (error) {
793
+ findings.push(issue("generated", PORT_MATRIX_OUTPUTS[2], "generated-doc-drift", `the port matrix could not be regenerated: ${error.message}`));
794
+ }
784
795
  return { local, adoption: Object.values(adoption)[0] };
785
796
  }
786
797
 
@@ -842,7 +853,7 @@ export async function runInventoryCheck(options = {}) {
842
853
  account: { crates: account.crates, npm: account.npm },
843
854
  thirdParty: { declarations: thirdParty.declarations, uniquePackages: thirdParty.uniquePackages, lockDuplicates: thirdParty.lockDuplicates },
844
855
  apps: { rows: appRows },
845
- generated: { checked: ["INDEX.md", "docs/CAPABILITY-MAP.md", "docs/ADOPTION.md"] },
856
+ generated: { checked: ["INDEX.md", "docs/CAPABILITY-MAP.md", "docs/ADOPTION.md", ...PORT_MATRIX_OUTPUTS] },
846
857
  hygiene: { checked: records.length },
847
858
  },
848
859
  };
package/ios-dev.mjs CHANGED
@@ -13,7 +13,7 @@
13
13
  // | { kind: "device", device: "<devicectl identifier>", bundleId, productName?, launch?: true, launchArgs?: [] },
14
14
  // }
15
15
  // The build commands are generated from the config (a hand-written `build` is refused). The device is chosen by id only, never
16
- // "the only device": `install.device`, `--serial` or RIGHTKIT_IOS_DEVICE. DerivedData comes from `rightkit storage xcode-args`.
16
+ // "the only device": `install.device`, `--serial` or RIGHTKIT_IOS_DEVICE. DerivedData comes from `rustright storage xcode-args`.
17
17
  import * as nodeFs from "node:fs";
18
18
  import os from "node:os";
19
19
  import path from "node:path";
@@ -102,7 +102,7 @@ export function validateIosDevelopment(target, { root, env = process.env } = {})
102
102
  install = { ...descriptor, productName: descriptor.productName ?? target.scheme, launchArgs: [...(descriptor.launchArgs ?? [])] };
103
103
  }
104
104
  const normalized = { project, projectSpec, scheme: target.scheme, configuration, teamId: target.teamId, extraBuildSettings, prepare, install };
105
- return { ...normalized, build: iosDevelopmentBuildCommands(normalized, { root, storage: { derivedData: "<rightkit storage xcode-args -derivedDataPath>" }, destinationId: "<device or simulator id>" }) };
105
+ return { ...normalized, build: iosDevelopmentBuildCommands(normalized, { root, storage: { derivedData: "<rustright storage xcode-args -derivedDataPath>" }, destinationId: "<device or simulator id>" }) };
106
106
  }
107
107
 
108
108
  const destinationFor = (install, id) => (install?.kind === "device" ? `id=${id}` : `platform=iOS Simulator,id=${id}`);
package/ios-host.mjs CHANGED
@@ -34,29 +34,29 @@ export function assertGuiSigningSession({ operation, env = process.env, run = sp
34
34
  }
35
35
 
36
36
  /**
37
- * DerivedData and result-bundle paths. With RIGHTKIT_RUN_ROOT set they come from `rightkit storage xcode-args --run-root <root>`
37
+ * DerivedData and result-bundle paths. With RIGHTKIT_RUN_ROOT set they come from `rustright storage xcode-args --run-root <root>`
38
38
  * (a JSON argv list holding -derivedDataPath and -resultBundlePath), the same source CodeRight and HeardRight use. Without a
39
39
  * managed run the fallback is a directory under the right-release state root, never an in-repo DerivedData.
40
40
  */
41
41
  export function resolveXcodeStorage({ env = process.env, run = spawnSync, cwd = process.cwd(), fallbackRoot }) {
42
42
  const runRoot = typeof env.RIGHTKIT_RUN_ROOT === "string" ? env.RIGHTKIT_RUN_ROOT.trim() : "";
43
43
  if (!runRoot) {
44
- if (!fallbackRoot) throw new IosHostError("IOS_STORAGE_UNAVAILABLE", "no RIGHTKIT_RUN_ROOT and no fallback storage root; run via: rightkit storage run --app <app> -- right-release ...");
44
+ if (!fallbackRoot) throw new IosHostError("IOS_STORAGE_UNAVAILABLE", "no RIGHTKIT_RUN_ROOT and no fallback storage root; run via: rustright storage run --app <app> -- right-release ...");
45
45
  return { derivedData: path.join(fallbackRoot, "DerivedData"), resultBundle: undefined, source: "fallback" };
46
46
  }
47
- const result = run("rightkit", ["storage", "xcode-args", "--run-root", runRoot], { cwd, env, encoding: "utf8", windowsHide: true, shell: false, timeout: 30_000 });
48
- if (result.error || result.status !== 0) throw new IosHostError("IOS_STORAGE_FAILED", `rightkit storage xcode-args failed (${result.status ?? "unavailable"})`);
47
+ const result = run("rustright", ["storage", "xcode-args", "--run-root", runRoot], { cwd, env, encoding: "utf8", windowsHide: true, shell: false, timeout: 30_000 });
48
+ if (result.error || result.status !== 0) throw new IosHostError("IOS_STORAGE_FAILED", `rustright storage xcode-args failed (${result.status ?? "unavailable"})`);
49
49
  let args;
50
50
  try { args = JSON.parse(String(result.stdout ?? "")); } catch { args = null; }
51
- if (!Array.isArray(args) || !args.length || args.some((arg) => typeof arg !== "string")) throw new IosHostError("IOS_STORAGE_FAILED", "rightkit storage xcode-args returned no argv list");
51
+ if (!Array.isArray(args) || !args.length || args.some((arg) => typeof arg !== "string")) throw new IosHostError("IOS_STORAGE_FAILED", "rustright storage xcode-args returned no argv list");
52
52
  const value = (flag, required) => {
53
53
  const index = args.indexOf(flag);
54
54
  if (index < 0 || index + 1 >= args.length) {
55
- if (required) throw new IosHostError("IOS_STORAGE_FAILED", `rightkit storage xcode-args is missing ${flag}`);
55
+ if (required) throw new IosHostError("IOS_STORAGE_FAILED", `rustright storage xcode-args is missing ${flag}`);
56
56
  return undefined;
57
57
  }
58
58
  const found = args[index + 1];
59
- if (!path.isAbsolute(found)) throw new IosHostError("IOS_STORAGE_FAILED", `rightkit storage xcode-args ${flag} is not an absolute path`);
59
+ if (!path.isAbsolute(found)) throw new IosHostError("IOS_STORAGE_FAILED", `rustright storage xcode-args ${flag} is not an absolute path`);
60
60
  return found;
61
61
  };
62
62
  return { derivedData: value("-derivedDataPath", true), resultBundle: value("-resultBundlePath", false), source: "rightkit-storage" };
@@ -378,7 +378,7 @@ const plannedTrust = (spec) => {
378
378
  };
379
379
 
380
380
  /** The ordered plan (pure; no secret is part of any argument). Used by --dry-run and by the run itself. */
381
- export function iosPackagePlan(spec, { root, buildNumber = "<YYYYMMDDNN>", storage = { derivedData: "<rightkit storage xcode-args -derivedDataPath>" }, receiptDir }) {
381
+ export function iosPackagePlan(spec, { root, buildNumber = "<YYYYMMDDNN>", storage = { derivedData: "<rustright storage xcode-args -derivedDataPath>" }, receiptDir }) {
382
382
  const optionsFile = path.join(receiptDir, "ios-export-options.plist");
383
383
  return {
384
384
  steps: [
@@ -504,7 +504,7 @@ export function scanForbiddenStrings(dir, strings, fs = nodeFs) {
504
504
  * scanFiles(label, files, o) release.mjs's assertNoQaControlBinaries (QA rules plus the release-switch binary layer)
505
505
  * detectSession / route GUI signing session seams (ios-host.mjs)
506
506
  * gitHead() the source commit; it must not change during the build
507
- * storage() DerivedData and result bundle (rightkit storage xcode-args)
507
+ * storage() DerivedData and result bundle (rustright storage xcode-args)
508
508
  * scanForbidden(dir, strings) forbidden-string scan
509
509
  * counterDir where the per-app build number counter lives (the vault state root under right-release build)
510
510
  */
package/local-publish.mjs CHANGED
@@ -529,7 +529,7 @@ async function publishUnit(parsed, deps, batch) {
529
529
  // #258: the package ships a Developer-ID-signed bin/darwin/rightkit-swap; build, sign and verify it before packing (fails closed).
530
530
  try { (deps.prepareSwapHelper ?? prepareSwapHelper)({ workspaceRoot, packageDir: unitPath, platform, run, env, log }); }
531
531
  catch (error) { refuse(`${error.code ? `[${error.code}] ` : ""}${error.message}`); }
532
- packCommands.push(`rightkit cargo ${SWAP_HELPER_CARGO_ARGS.join(" ")} && codesign --verify --strict bin/darwin/rightkit-swap`);
532
+ packCommands.push(`rustright cargo ${SWAP_HELPER_CARGO_ARGS.join(" ")} && codesign --verify --strict bin/darwin/rightkit-swap`);
533
533
  }
534
534
  if (kind === "npm") {
535
535
  // The feed a tarball ships must list the version it ships with (#319): stage it before packing.
package/local-qualify.mjs CHANGED
@@ -370,16 +370,16 @@ function brokerIdentity(deps) {
370
370
  return { name: "rustright", client: String(client).replaceAll("\\", "/"), sha256: sha256((deps.readFile ?? fs.readFileSync)(client)) };
371
371
  }
372
372
 
373
- // Cargo only through the broker: `rightkit cargo`, or a `cargo` on PATH that is the managed shim (scripts/local-gate.mjs).
373
+ // Cargo only through the broker: `rustright cargo`, or a `cargo` on PATH that is the managed shim (scripts/local-gate.mjs).
374
374
  function detectCargoCommand(deps) {
375
375
  const which = (command) => {
376
376
  const result = (deps.run ?? spawnSync)((deps.platform ?? process.platform) === "win32" ? "where" : "which", [command], { encoding: "utf8", windowsHide: true });
377
377
  return result.status === 0 ? String(result.stdout).split(/\r?\n/).map((line) => line.trim()).filter(Boolean) : [];
378
378
  };
379
- if (which("rightkit").length) return ["rightkit", "cargo"];
379
+ if (which("rustright").length) return ["rustright", "cargo"];
380
380
  const first = which("cargo")[0];
381
381
  if (first) { try { if (fs.readFileSync(first, "utf8").includes("cargo-guard.mjs")) return ["cargo"]; } catch { /* not the managed shim */ } }
382
- return refuse("neither `rightkit` nor the managed cargo shim (cargo-guard.mjs) is first on PATH; plain cargo is never used");
382
+ return refuse("neither `rustright` nor the managed cargo shim (cargo-guard.mjs) is first on PATH; plain cargo is never used");
383
383
  }
384
384
 
385
385
  const loadFromRoot = (root, file) => {
package/local-runtime.mjs CHANGED
@@ -176,7 +176,7 @@ export async function measureRuntimeCrate(options, deps = {}) {
176
176
  const run = deps.run ?? ((command, args, settings) => spawnCollect(command, args, { maxBuffer: 64 * 1024 * 1024, ...settings }));
177
177
  const invoke = (args, settings) => run(cargoCommand[0], [...cargoCommand.slice(1), ...args], { cwd: root, windowsHide: true, ...settings });
178
178
  const toolchainOf = async () => {
179
- const rustc = await run(cargoCommand[0] === "rightkit" ? "rightkit" : "rustc", cargoCommand[0] === "rightkit" ? ["rustc", "-Vv"] : ["-Vv"], { cwd: root, env: { ...baseEnv, ...brokerEnv }, windowsHide: true, timeout: 60_000 });
179
+ const rustc = await run(cargoCommand[0] === "rustright" ? "rustright" : "rustc", cargoCommand[0] === "rustright" ? ["rustc", "-Vv"] : ["-Vv"], { cwd: root, env: { ...baseEnv, ...brokerEnv }, windowsHide: true, timeout: 60_000 });
180
180
  const cargo = await invoke(["-V"], { env: { ...baseEnv, ...brokerEnv }, timeout: 60_000 });
181
181
  if (rustc.error || rustc.status !== 0 || cargo.error || cargo.status !== 0) throw new Error("cannot read the toolchain through the broker");
182
182
  return { rustc: String(rustc.stdout).trim(), cargo: String(cargo.stdout).trim() };
@@ -399,10 +399,12 @@
399
399
  "cargo:base64",
400
400
  "cargo:block2",
401
401
  "cargo:chrono",
402
+ "cargo:clap",
402
403
  "cargo:dirs",
403
404
  "cargo:futures",
404
405
  "cargo:getrandom",
405
406
  "cargo:image",
407
+ "cargo:libc",
406
408
  "cargo:objc2",
407
409
  "cargo:objc2-foundation",
408
410
  "cargo:reqwest",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rightkit/release",
3
- "version": "0.6.3",
3
+ "version": "0.6.5",
4
4
  "description": "Portable release CLI/SDK with fail-closed public GitHub Releases and private R2 + RightApps distribution.",
5
5
  "license": "MIT OR Apache-2.0",
6
6
  "type": "module",
@@ -33,7 +33,9 @@
33
33
  "LICENSE-MIT",
34
34
  "LICENSE-APACHE",
35
35
  "!*.test.mjs",
36
- "!standalone-clone-evidence.json"
36
+ "!cli/*.test.mjs",
37
+ "!standalone-clone-evidence.json",
38
+ "!refusals.json"
37
39
  ],
38
40
  "sideEffects": false,
39
41
  "publishConfig": {
@@ -49,7 +51,7 @@
49
51
  "smol-toml": "1.9.1"
50
52
  },
51
53
  "scripts": {
52
- "test": "node --import ./test-support/broker-guard.mjs --test --test-concurrency=1 --test-force-exit *.test.mjs",
54
+ "test": "node --import ./test-support/broker-guard.mjs --test --test-concurrency=1 --test-force-exit *.test.mjs cli/*.test.mjs",
53
55
  "test:registry-parity": "node registry-parity.mjs --allow-unpublished",
54
56
  "test:signer:windows": "node sign-release-manifest.integration.mjs",
55
57
  "doctor:all": "node --test right-suite-contract.test.mjs",
@@ -352,11 +352,12 @@ export function admitInvocation(step, options = {}) {
352
352
  if (name === "right-release") return admitRightRelease(args, label);
353
353
  if (name === "pnpm") return admitPnpm(args, inner, label, chain);
354
354
  if (name === "node") return admitNode(args, inner, label);
355
- if (name === "rightkit") {
356
- if (args[0] !== "cargo" || !CARGO_SUBCOMMANDS.has(args[1])) return refuse("R1-managed-client", `${label}: only rightkit cargo ${[...CARGO_SUBCOMMANDS].join("|")} is admitted`);
357
- return shellSafeArgs(args, label) ?? admit(`rightkit-cargo-${args[1]}`, "managed-cargo");
355
+ if (name === "rightkit") return refuse("R1-retired-client", `${label}: rightkit is the retired name of the RustRight client (rustright#91); use rustright ${args.slice(0, 2).join(" ")}`);
356
+ if (name === "rustright") {
357
+ if (args[0] !== "cargo" || !CARGO_SUBCOMMANDS.has(args[1])) return refuse("R1-managed-client", `${label}: only rustright cargo ${[...CARGO_SUBCOMMANDS].join("|")} is admitted`);
358
+ return shellSafeArgs(args, label) ?? admit(`rustright-cargo-${args[1]}`, "managed-cargo");
358
359
  }
359
- if (name === "cargo" || name === "rustc" || name === "rustdoc") return refuse("R1-bare-cargo", `${label}: run Cargo through the managed client (rightkit cargo ...), never bare ${name}`);
360
+ if (name === "cargo" || name === "rustc" || name === "rustdoc") return refuse("R1-bare-cargo", `${label}: run Cargo through the managed client (rustright cargo ...), never bare ${name}`);
360
361
  if (name === "tauri") return refuse("R1-direct-tauri", `${label}: run the Tauri CLI as pnpm exec tauri ..., or through right-release; a bare tauri is not an approved installed runtime`);
361
362
  if (PACKAGE_MANAGERS.has(name)) return refuse("R1-package-manager", `${label}: ${name} is not allowed; the workspace uses pnpm`);
362
363
  if (INTERPRETERS.has(name)) return refuse("R1-interpreter", `${label}: ${name} runs script text or a script file the app owns; only an admitted shared generator may do that`);