@rightkit/release 0.2.160 → 0.2.162

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.
@@ -181,11 +181,19 @@ function readToml(file, label) {
181
181
  return JSON.parse(result.stdout);
182
182
  }
183
183
 
184
- function findCargoManifests(root) {
184
+ // Not product code: SwiftPM `.build` (dependency checkouts and build products), throwaway probe dirs (`*-probe`, `pdfprobe2`), `tmp`, and
185
+ // `tasks/handoffs` (handoff probes and their manifests). They are never built by the product, so they are not gated.
186
+ export function isExcludedScanDirectory(name, parentName = "") {
187
+ if (SKIP_DIRECTORIES.has(name) || name === ".build") return true;
188
+ if (/probe\d*$/i.test(name) || name === "tmp") return true;
189
+ return name === "handoffs" && parentName === "tasks";
190
+ }
191
+
192
+ export function findCargoManifests(root) {
185
193
  const found = [];
186
194
  const visit = (dir) => {
187
195
  for (const entry of readdirSync(dir, { withFileTypes: true })) {
188
- if (entry.isDirectory() && SKIP_DIRECTORIES.has(entry.name)) continue;
196
+ if (entry.isDirectory() && isExcludedScanDirectory(entry.name, path.basename(dir))) continue;
189
197
  const full = path.join(dir, entry.name);
190
198
  if (entry.isDirectory()) visit(full);
191
199
  else if (entry.isFile() && entry.name === "Cargo.toml") found.push(full);
package/disk-policy.json CHANGED
@@ -47,7 +47,9 @@
47
47
  {"id": "leftover-node-modules-medium-packages", "platform": "mac", "path": "/Volumes/D/claude/rightkit/packages/*/node_modules.medium-*", "capBytes": 1073741824, "action": "sweep", "unit": "self"},
48
48
  {"id": "leftover-legion-snap", "platform": "mac", "path": "/Volumes/D/claude/legion-snap-*", "capBytes": 1073741824, "action": "sweep", "unit": "self"},
49
49
  {"id": "leftover-legion-final", "platform": "mac", "path": "/Volumes/D/claude/legion-final-*", "capBytes": 1073741824, "action": "sweep", "unit": "self"},
50
- {"id": "leftover-pulse-bak", "platform": "win", "path": "${LOCALAPPDATA}\\Programs\\Pulse.bak-*", "capBytes": 1073741824, "action": "sweep", "unit": "self"}
50
+ {"id": "leftover-pulse-bak", "platform": "win", "path": "${LOCALAPPDATA}\\Programs\\Pulse.bak-*", "capBytes": 1073741824, "action": "sweep", "unit": "self"},
51
+ {"id": "qa-evidence-runs", "platform": "mac", "path": "/Volumes/D/claude/*/.cache/rightkit-qa/*", "capBytes": 5368709120, "action": "sweep", "unit": "self", "staleDays": 3, "skipTracked": true},
52
+ {"id": "qa-evidence-runs", "platform": "win", "path": "D:\\claude\\*\\.cache\\rightkit-qa\\*", "capBytes": 5368709120, "action": "sweep", "unit": "self", "staleDays": 3, "skipTracked": true}
51
53
  ],
52
54
  "protectedSegments": ["Application Support", "Keychains", "Keychain", "HeardRightNext", "Credentials", ".ssh", ".rightkit-managed", ".rightkit-swept"]
53
55
  }
package/disk-policy.mjs CHANGED
@@ -187,14 +187,25 @@ export function windowsLockProbe(candidate) {
187
187
  return false;
188
188
  }
189
189
 
190
- export function planSweep({ policy, platform = process.platform, env = process.env, home = os.homedir(), now = Date.now(), staleDays = policy.staleDays, openPaths, inUse, size = (p) => pathSize(p, { platform }) } = {}) {
190
+ // True when git tracks anything at or under `target` (a tracked file is never swept). Not in a repository: not tracked.
191
+ export function gitTracksPath(target) {
192
+ const top = spawnSync("git", ["-C", path.dirname(target), "rev-parse", "--show-toplevel"], { encoding: "utf8" });
193
+ if (top.status !== 0) return false;
194
+ const listed = spawnSync("git", ["-C", String(top.stdout).trim(), "ls-files", "-z", "--", target], { encoding: "utf8" });
195
+ if (listed.status !== 0) return true; // cannot prove untracked: keep it
196
+ return String(listed.stdout).length > 0;
197
+ }
198
+
199
+ export function planSweep({ policy, platform = process.platform, env = process.env, home = os.homedir(), now = Date.now(), staleDays, openPaths, inUse, tracked = gitTracksPath, size = (p) => pathSize(p, { platform }) } = {}) {
191
200
  const key = platformKey(platform);
192
- const cutoff = now - staleDays * 86400_000;
201
+ const effectiveDays = staleDays ?? policy.staleDays;
193
202
  const candidates = [];
194
203
  const skipped = [];
195
204
  const seen = new Set();
196
205
  const lockCheck = inUse ?? (key === "win" ? windowsLockProbe : (p) => inUseByOpenPaths(p, openPaths));
197
206
  for (const writer of policy.writers.filter((w) => w.platform === key && w.action === "sweep")) {
207
+ // `--days N` overrides every writer; otherwise a writer may carry its own window (QA evidence: 3 days).
208
+ const cutoff = now - (staleDays ?? writer.staleDays ?? policy.staleDays) * 86400_000;
198
209
  let roots; let literal;
199
210
  try { roots = writerMatches(writer, { env, home }); literal = literalRoot(expandPolicyPath(writer.path, { env, home })); } catch { continue; }
200
211
  const units = [];
@@ -216,12 +227,13 @@ export function planSweep({ policy, platform = process.platform, env = process.e
216
227
  if (st.isSymbolicLink()) { skipped.push({ ...base, reason: "symlink" }); continue; }
217
228
  if (!isInsideRoot(unit, root)) { skipped.push({ ...base, reason: "outside-root" }); continue; }
218
229
  if (isProtectedPath(unit, policy)) { skipped.push({ ...base, reason: "protected" }); continue; }
230
+ if (writer.skipTracked && tracked(unit)) { skipped.push({ ...base, reason: "tracked" }); continue; }
219
231
  if (hasFreshContent(unit, cutoff)) { skipped.push({ ...base, reason: "fresh" }); continue; }
220
232
  if (lockCheck(unit)) { skipped.push({ ...base, reason: "in-use" }); continue; }
221
233
  candidates.push({ ...base, root, bytes: size(unit), mtime: new Date(st.mtimeMs).toISOString() });
222
234
  }
223
235
  }
224
- return { schema: 1, platform: key, staleDays, candidates, skipped, totalBytes: candidates.reduce((s, c) => s + c.bytes, 0) };
236
+ return { schema: 1, platform: key, staleDays: effectiveDays, candidates, skipped, totalBytes: candidates.reduce((s, c) => s + c.bytes, 0) };
225
237
  }
226
238
 
227
239
  export const HOLDING_DIR = ".rightkit-swept";
package/local-publish.mjs CHANGED
@@ -79,6 +79,17 @@ async function defaultLoadWorkspaceModules(workspaceRoot) {
79
79
  return { results, checkNpmTarball: upload.checkNpmTarball };
80
80
  }
81
81
 
82
+ /**
83
+ * Feature sets that must at least compile, beyond the tested ones. A crate whose tested sets already
84
+ * include `--all-features` needs none. `checkFeatureSets` (ci/gate.config.json) lists them explicitly for
85
+ * crates with mutually exclusive features; otherwise one `--all-features` check runs. Mirrored in scripts/ci/gate.mjs.
86
+ */
87
+ export function allFeatureCheckSets(entry = {}) {
88
+ const tested = entry.featureSets ?? [[]];
89
+ if (tested.some((features) => features.includes("--all-features"))) return [];
90
+ return entry.checkFeatureSets ?? [["--all-features"]];
91
+ }
92
+
82
93
  /** The CI gate's commands for one unit (scripts/ci/gate.mjs npmGate/rustGate), mirrored exactly. */
83
94
  export function localTestPlan({ kind, unit, dir, config, manifest }) {
84
95
  if (kind === "npm") {
@@ -100,6 +111,7 @@ export function localTestPlan({ kind, unit, dir, config, manifest }) {
100
111
  if (entry.alsoSerial) steps.push({ command: "cargo", args: ["test", "-p", unit, ...features, "--no-fail-fast", "--", "--test-threads=1", "--show-output"], test: true });
101
112
  }
102
113
  steps.push({ command: "cargo", args: ["clippy", "-p", unit, "--no-deps", "--all-targets", ...featureSets[0], "--", "-D", "warnings"], test: false });
114
+ for (const features of allFeatureCheckSets(entry)) steps.push({ command: "cargo", args: ["check", "-p", unit, "--all-targets", ...features], test: false });
103
115
  return { id: `rust:${unit}`, featureLabels: featureSets.map((features) => features.join(" ") || "default"), steps };
104
116
  }
105
117
 
@@ -285,15 +285,22 @@ function versionAtLeast(actual, minimum) {
285
285
  }
286
286
  return true;
287
287
  }
288
+ // The protected policy bundle (scanner source, catalog, registry, dependency allowlist) is still fingerprinted and a pin
289
+ // that differs from the running bundle is reported, but only as a warning: trust-sync, the human-approved way to
290
+ // re-pin it, is retired (Adrian, decisions.md), so no check refuses on a missing or stale pinned bundle.
291
+ export const TRUST_SYNC_RETIRED = "right-release ownership trust-sync is retired: no release, ownership or QA check needs a human-approved policy bundle any more. The bundle is still fingerprinted; `right-release ownership trust-compare` reports drift as a warning with a semantic diff.";
292
+
293
+ export const POLICY_BUNDLE_DRIFT_WARNING = "ownership policy bundle (scanner source, catalog, registry or dependency allowlist) differs from the bundle pinned in protected trust (warning only; trust-sync is retired)";
288
294
  export function validateOwnershipFloor(trust, { scannerVersion, catalogVersion, catalogDigest, policyBundleDigest } = {}) {
289
295
  const problems = [];
290
- if (!trust) return { ok: false, problems: ["external ownership trust unavailable"] };
296
+ const warnings = [];
297
+ if (!trust) return { ok: false, problems: ["external ownership trust unavailable"], warnings };
291
298
  if (!versionAtLeast(scannerVersion, trust.minimumScannerVersion)) problems.push("scanner version missing, malformed, or below protected minimum");
292
299
  if (!versionAtLeast(catalogVersion, trust.minimumCatalogVersion)) problems.push("catalog version missing, malformed, or below protected minimum");
293
300
  if (trust.minimumCatalogDigest && trust.minimumCatalogDigest !== catalogDigest) problems.push("catalog digest below protected pinned digest");
294
- // Trust written before the bundle existed has no policyBundle and stays unpinned until the next trust-sync.
295
- if (trust.policyBundle?.digest && policyBundleDigest !== undefined && trust.policyBundle.digest !== policyBundleDigest) problems.push("ownership policy bundle (scanner source, catalog, registry or dependency allowlist) differs from the protected pinned bundle; run trust-sync");
296
- return { ok: problems.length === 0, problems };
301
+ // Trust written before the bundle existed has no policyBundle: nothing to compare.
302
+ if (trust.policyBundle?.digest && policyBundleDigest !== undefined && trust.policyBundle.digest !== policyBundleDigest) warnings.push(POLICY_BUNDLE_DRIFT_WARNING);
303
+ return { ok: problems.length === 0, problems, warnings };
297
304
  }
298
305
  function protectedLocation(file, platform, { runner = spawnSync, inspect = lstatSync, stat = statSync, requireNonElevated = true } = {}) {
299
306
  const problems = [];
@@ -4,20 +4,22 @@
4
4
  //
5
5
  // right-release ownership [--app <dir>] [--json]
6
6
  // right-release ownership [--app <dir>] --audit-json (Legion governance.capability-ownership provider)
7
- // right-release ownership trust-compare [--json] (print this host's protected-trust digest)
7
+ // right-release ownership trust-compare [--json] (print this host's protected-trust digest; policy-bundle drift is a warning, never a block)
8
+ // right-release ownership trust-sync (retired: prints why and exits 0)
8
9
  // right-release ownership --write-manifest [--rightkit <rightkit>] [--approve-new-capabilities]
9
10
  // --approve-new-capabilities permits new ids during local generation only.
10
11
  // Existing owner changes are always refused; installation needs protected authorization.
11
12
  import { realpathSync } from "node:fs";
12
13
  import path from "node:path";
13
14
  import { fileURLToPath } from "node:url";
14
- import { formatTable } from "./ownership.mjs";
15
+ import { SCANNER_VERSION, formatTable } from "./ownership.mjs";
15
16
  import { writeOwnershipCatalog } from "./ownership-catalog.mjs";
16
17
  import { createOwnershipEvidence } from "./ownership-evidence.mjs";
17
18
  import { spawnSync } from "./background-process.mjs";
18
19
  import { main as stagedMain } from "./ownership-staged.mjs";
19
20
  import { loadTrustedExternal } from "./ownership-approval.mjs";
20
- import { trustComparison } from "./ownership-policy-bundle.mjs";
21
+ import { diffPolicyBundles, runningPolicyBundle, trustComparison } from "./ownership-policy-bundle.mjs";
22
+ import { POLICY_BUNDLE_DRIFT_WARNING, TRUST_SYNC_RETIRED } from "./ownership-approval.mjs";
21
23
  import { isBlockingFinding } from "./ownership-verdict.mjs";
22
24
 
23
25
  const packageRoot = path.dirname(fileURLToPath(import.meta.url));
@@ -27,14 +29,41 @@ function flag(args, name) {
27
29
  return i >= 0 ? args[i + 1] : undefined;
28
30
  }
29
31
 
32
+ /** Semantic drift of the bundle pinned in protected trust against the bundle this checkout runs; null when none is pinned or they match. */
33
+ export function policyBundleDrift(trust, { runningBundle = () => runningPolicyBundle({ scannerVersion: SCANNER_VERSION }) } = {}) {
34
+ const pinned = trust?.policyBundle;
35
+ if (!pinned?.digest) return null;
36
+ let running;
37
+ try { running = runningBundle(); } catch (error) { return { warning: `policy bundle could not be computed: ${error.message}`, diff: null }; }
38
+ if (running.digest === pinned.digest) return null;
39
+ return { warning: POLICY_BUNDLE_DRIFT_WARNING, pinnedDigest: pinned.digest, runningDigest: running.digest, diff: diffPolicyBundles(pinned, running) };
40
+ }
41
+
42
+ function driftLines(drift) {
43
+ const lines = [`WARNING: ${drift.warning}`];
44
+ if (drift.diff) {
45
+ const { filesAdded, filesRemoved, filesChanged, scannerVersion, catalogChanged, registryChanged, allowlistChanged, qaFloorChanged } = drift.diff;
46
+ if (scannerVersion) lines.push(` scanner version: ${scannerVersion.from} -> ${scannerVersion.to}`);
47
+ for (const [label, list] of [["files added", filesAdded], ["files removed", filesRemoved], ["files changed", filesChanged]]) if (list.length) lines.push(` ${label}: ${list.join(", ")}`);
48
+ const moved = Object.entries({ catalog: catalogChanged, registry: registryChanged, allowlist: allowlistChanged, "qa floor": qaFloorChanged }).filter(([, changed]) => changed).map(([name]) => name);
49
+ if (moved.length) lines.push(` changed digests: ${moved.join(", ")}`);
50
+ }
51
+ return lines;
52
+ }
53
+
30
54
  /** Print this host's protected-trust digest. The Mac's digest is the reference; see docs/OWNERSHIP-ENFORCEMENT.md. */
31
- export function trustCompare(args, { print, trustLoader = loadTrustedExternal } = {}) {
55
+ export function trustCompare(args, { print, trustLoader = loadTrustedExternal, runningBundle } = {}) {
32
56
  let state;
33
57
  try { state = trustLoader(); } catch (error) { print(`[right-release] trust-compare: protected trust unreadable (${error.message})`); return 1; }
34
58
  if (!state?.trust) { print("[right-release] trust-compare: no protected trust on this host (not enrolled)"); return 1; }
35
59
  const { digest, components } = trustComparison(state.trust);
36
- if (args.includes("--json")) print(JSON.stringify({ platform: process.platform, digest, components }, null, 2));
37
- else { print(`trust digest (${process.platform}): ${digest}`); for (const [name, value] of Object.entries(components)) print(` ${name}: ${typeof value === "object" && value !== null ? JSON.stringify(value) : value ?? "none"}`); }
60
+ const drift = policyBundleDrift(state.trust, runningBundle ? { runningBundle } : undefined);
61
+ if (args.includes("--json")) print(JSON.stringify({ platform: process.platform, digest, components, ...(drift ? { warnings: [drift] } : {}) }, null, 2));
62
+ else {
63
+ print(`trust digest (${process.platform}): ${digest}`);
64
+ for (const [name, value] of Object.entries(components)) print(` ${name}: ${typeof value === "object" && value !== null ? JSON.stringify(value) : value ?? "none"}`);
65
+ if (drift) for (const line of driftLines(drift)) print(line);
66
+ }
38
67
  return 0;
39
68
  }
40
69
 
@@ -45,7 +74,8 @@ export async function main(args, { evidenceFactory = createOwnershipEvidence, gi
45
74
  }
46
75
  if (args[0] === "trust-compare") return trustCompare(args, { print, trustLoader });
47
76
  if (args.includes("--staged")) return stagedMain(args, { print });
48
- if (["enroll", "approve-key", "revoke-key", "trust-sync"].includes(args[0])) {
77
+ if (args[0] === "trust-sync") { print(`[right-release] ${TRUST_SYNC_RETIRED}`); return 0; }
78
+ if (["enroll", "approve-key", "revoke-key"].includes(args[0])) {
49
79
  const { main: keyMain } = await import("./ownership-key-command.mjs");
50
80
  return keyMain(args, { runner: gitRunner, print });
51
81
  }
@@ -79,7 +109,7 @@ export async function main(args, { evidenceFactory = createOwnershipEvidence, gi
79
109
  : result.coverage.length
80
110
  ? { type: "ownership-coverage", message: `${result.coverage.length} ownership coverage gap(s)` }
81
111
  : undefined;
82
- print(JSON.stringify({ ok: checked.ok, ...(checked.enrollmentPending ? { enrollmentPending: true, warning: checked.warning } : {}), ...(error ? { error } : {}), ...(blocked ? { blocked } : {}), rows: result.rows, violations: result.violations, covered: result.covered, problems: [...result.problems, ...checked.floor.problems], coverage: result.coverage, metadata: result.metadata }, null, 2));
112
+ print(JSON.stringify({ ok: checked.ok, ...(checked.enrollmentPending ? { enrollmentPending: true, warning: checked.warning } : {}), ...(error ? { error } : {}), ...(blocked ? { blocked } : {}), rows: result.rows, violations: result.violations, covered: result.covered, problems: [...result.problems, ...checked.floor.problems], ...(checked.warnings ? { warnings: checked.warnings } : {}), coverage: result.coverage, metadata: result.metadata }, null, 2));
83
113
  } else {
84
114
  print(`RightKit ownership: ${path.basename(app)}`);
85
115
  print(formatTable(result.rows));
@@ -88,6 +118,7 @@ export async function main(args, { evidenceFactory = createOwnershipEvidence, gi
88
118
  }
89
119
  for (const p of result.problems) print(` ${checked.enrollmentPending ? "DIAGNOSTIC" : "EXCEPTION"} ${p}`);
90
120
  print(verdictLine(checked, result, blocked));
121
+ for (const warning of checked.warnings ?? []) print(` WARNING ${warning}`);
91
122
  }
92
123
  // CI lanes may pass `--advisory-when-pending`: before approval keys are enrolled the scan asserts
93
124
  // nothing, so findings print as diagnostics and the step exits 0. Release lanes never pass it.
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  // Propose or regenerate dependency-allowlist baselines from each registered repository's CURRENT direct
3
- // dependencies. This only edits the committed proposal file; it takes effect solely through Adrian's trust-sync.
3
+ // dependencies. This only edits the committed proposal file; it is advisory until a protected trust copy carries it (trust-sync is retired).
4
4
  // node ownership-dependency-allowlist-seed.mjs --workspace <dir> [--write]
5
5
  // Existing approvals are never removed (union with the current file); block-tier catalog dependencies
6
6
  // and RightKit packages are never recorded as approvals.
@@ -78,7 +78,8 @@ function createEvidence(repoRoot, {
78
78
  // When a protected registry exists, key enrollment controls signing
79
79
  // authority; scanner and catalog floors still apply during pending enrollment.
80
80
  // Step 2: the protected trust pins the policy bundle (catalog + registry + dependency allowlist + scanner version +
81
- // scanner source digests). Trust without a pinned bundle (written before it existed) is unpinned until trust-sync.
81
+ // scanner source digests). It is fingerprinted and compared for drift only: a pin that differs, or a bundle that cannot be
82
+ // computed, is a warning, never a block (trust-sync is retired).
82
83
  let policyBundleDigest;
83
84
  let bundleProblem = null;
84
85
  if (trustState.trust?.policyBundle?.digest) {
@@ -88,7 +89,7 @@ function createEvidence(repoRoot, {
88
89
  const floor = trustState.trust
89
90
  ? validateOwnershipFloor(trustState.trust, { scannerVersion, catalogVersion, catalogDigest, ...(policyBundleDigest !== undefined ? { policyBundleDigest } : {}) })
90
91
  : { ok: false, problems: ["protected ownership floor is unavailable until trust registry enrollment"] };
91
- if (bundleProblem) { floor.ok = false; floor.problems = [bundleProblem, ...floor.problems.filter((problem) => !problem.startsWith("ownership policy bundle"))]; }
92
+ floor.warnings = [...(floor.warnings ?? []), ...(bundleProblem ? [bundleProblem] : [])];
92
93
  // Real paths on both sides: Git's root is resolved (macOS /private/var), callers may pass /var or /tmp.
93
94
  const real = (p) => { try { return realpathSync.native(p); } catch { return path.resolve(p); } };
94
95
  const requestedRoot = path.relative(real(root), real(scanRoot)).replaceAll("\\", "/") || ".";
@@ -152,7 +153,7 @@ function createEvidence(repoRoot, {
152
153
  ok: scans.every(({ scanned }) => scanned.ok),
153
154
  findings: scans.flatMap(({ scanned, prefix }) => (scanned.findings ?? []).map((entry) => rebase(entry, prefix))),
154
155
  violations: scans.flatMap(({ scanned, prefix }) => (scanned.violations ?? []).map((entry) => rebase(entry, prefix))),
155
- problems: [...scans.flatMap(({ scanned }) => scanned.problems ?? []), ...evidenceDiagnostics, ...(enrollmentPending ? floor.problems : floor.problems.filter((problem) => problem.startsWith("ownership policy bundle")))],
156
+ problems: [...scans.flatMap(({ scanned }) => scanned.problems ?? []), ...evidenceDiagnostics, ...(enrollmentPending ? floor.problems : [])],
156
157
  rows: scans.flatMap(({ scanned, root: registered }) => (scanned.rows ?? []).map((row) => ({ ...row, root: registered }))),
157
158
  covered: scans.flatMap(({ scanned, prefix }) => (scanned.covered ?? []).map((entry) => rebase(entry, prefix))),
158
159
  coverage: contentCoverage,
@@ -182,7 +183,7 @@ function createEvidence(repoRoot, {
182
183
  checkedAt: new Date().toISOString(),
183
184
  };
184
185
  const warning = enrollmentPending ? "WARNING: ownership approval enrollment is pending; findings remain backlog diagnostics and no protected evidence or approvals are asserted" : undefined;
185
- return { ok: !enrollmentPending && result.ok && floor.ok && contentCoverage.length === 0, ...(enrollmentPending ? { enrollmentPending: true, warning } : {}), evidence, result, floor, contentCoverage };
186
+ return { ok: !enrollmentPending && result.ok && floor.ok && contentCoverage.length === 0, ...(enrollmentPending ? { enrollmentPending: true, warning } : {}), ...(floor.warnings.length ? { warnings: floor.warnings } : {}), evidence, result, floor, contentCoverage };
186
187
  }
187
188
 
188
189
  /** Create scan evidence bound to committed source, catalog, and signed exceptions. */
@@ -5,10 +5,9 @@ import os from "node:os";
5
5
  import { fileURLToPath } from "node:url";
6
6
  import { spawnSync, spawnSyncInteractive, windowsPowerShellEnvironment } from "./background-process.mjs";
7
7
  import { createKeyEvent, keyFingerprint, verifyKeyLog, verifyKeyRequest } from "./ownership-key-log.mjs";
8
- import { canonicalRepositoryIdentity, DEFAULT_TRUST_PATH, loadTrustedExternal, resolveKeyLogTrust, __testOnly as approvalTestOnly } from "./ownership-approval.mjs";
8
+ import { canonicalRepositoryIdentity, DEFAULT_TRUST_PATH, TRUST_SYNC_RETIRED, loadTrustedExternal, resolveKeyLogTrust, __testOnly as approvalTestOnly } from "./ownership-approval.mjs";
9
9
  import { readCommittedKeyAuthority } from "./ownership-key-authority.mjs";
10
- import { buildMacInvocation, confirmTrustChange, delegateToDesktop, hasTerminal, needsDesktopDelegation } from "./ownership-key-elevation.mjs";
11
- import { summarizeRequestFile } from "./ownership-trust-summary.mjs";
10
+ import { buildMacInvocation, hasTerminal } from "./ownership-key-elevation.mjs";
12
11
 
13
12
  const here = path.dirname(fileURLToPath(import.meta.url));
14
13
  const LOG = ".rightkit/ownership-keys.json";
@@ -321,7 +320,7 @@ function buildWindowsElevationInvocation(nodeExe, trustScript, requestPath, dige
321
320
  const parentScript = `$ErrorActionPreference='Stop'; try { $env:NODE_OPTIONS=$null; $env:NODE_PATH=$null; $p=Start-Process -FilePath ${quote(powershell)} -ArgumentList ${quote(`-NoProfile -EncodedCommand ${childEncoded}`)} -Verb RunAs -WindowStyle Hidden -Wait -PassThru; exit $p.ExitCode } catch { exit 1 }`;
322
321
  return { parentExecutable: powershell, parentArgs: ["-NoProfile", "-EncodedCommand", Buffer.from(parentScript, "utf16le").toString("base64")], childScript };
323
322
  }
324
- export const __testOnly = Object.freeze({ elevate, buildWindowsElevationInvocation, assertSafeMetadataParents, detectMinimizeHook, trustSyncGate, installCheckpoint });
323
+ export const __testOnly = Object.freeze({ elevate, buildWindowsElevationInvocation, assertSafeMetadataParents, detectMinimizeHook, installCheckpoint });
325
324
  function trustState() { return loadTrustedExternal(); }
326
325
  function currentLogTrust(root, log, trust = trustState(), authority, injected = false) {
327
326
  if (!trust.trust?.rootFingerprint) throw new Error("protected root trust is not enrolled on this host");
@@ -353,10 +352,10 @@ export async function main(args, deps = {}) {
353
352
  const print = deps.print ?? console.log;
354
353
  const app = path.resolve(flag(args, "--app") ?? process.cwd());
355
354
  const root = deps.root ?? repoRoot(app, run);
355
+ if (args[0] === "trust-sync") { print(`[right-release] ${TRUST_SYNC_RETIRED}`); return 0; }
356
356
  try {
357
357
  if (!platform) throw new Error(`ownership key management unsupported platform: ${process.platform}`);
358
358
  // Delegate BEFORE taking the command lock: the desktop-session child needs that same lock.
359
- if (args[0] === "trust-sync" && !args.includes("--check") && needsDesktopDelegation({ hostPlatform: deps.hostPlatform, env: deps.env, run, elevatedCheck: deps.elevatedCheck })) return delegateTrustSync(deps, run, print);
360
359
  const helper = deps.helper ?? helperFor(run);
361
360
  const lockPath = deps.lockPath ?? path.join(os.tmpdir(), "rightkit-key-command.lock");
362
361
  let lockFd;
@@ -371,8 +370,7 @@ export async function main(args, deps = {}) {
371
370
  if (verb === "enroll") return await enroll(args, { ...deps, run, print, root, helper, headSnapshot, transport, authority, hook });
372
371
  if (verb === "approve-key") return await approve(args, { ...deps, run, print, root, helper, headSnapshot, transport, authority, hook });
373
372
  if (verb === "revoke-key") return await revoke(args, { ...deps, run, print, root, helper, headSnapshot, transport, authority, hook });
374
- if (verb === "trust-sync") return await trustSync(args, { ...deps, run, print, root, helper, headSnapshot, transport, authority, hook });
375
- throw new Error("expected enroll, approve-key, revoke-key, or trust-sync");
373
+ throw new Error("expected enroll, approve-key, or revoke-key (trust-sync is retired)");
376
374
  } finally {
377
375
  closeSync(lockFd);
378
376
  rmSync(lockPath, { force: true });
@@ -508,53 +506,6 @@ async function resumeRootEnrollment(log, d) {
508
506
  print(`Ownership root enrollment recovery completed: ${pin}; active keys: ${state.keys.length}`);
509
507
  return 0;
510
508
  }
511
- function delegateTrustSync(deps, run, print) {
512
- const result = delegateToDesktop({ run, command: deps.desktopCommand ?? [process.execPath, ...(process.argv[1] ? [path.resolve(process.argv[1])] : []), ...process.argv.slice(2)], ...(deps.desktopOptions ?? {}) });
513
- if (result.output) print(result.output.trimEnd());
514
- if (result.status !== 0) throw new Error(`desktop-session trust-sync failed (exit ${result.status}); see output above`);
515
- return 0;
516
- }
517
- /** Advance this host's protected checkpoint to the committed key log (after another host approved or revoked a key). */
518
- async function trustSync(_args, d) {
519
- d = { ...d, label: "RightKit ownership trust-sync", gate: trustSyncGate(d) };
520
- const log = readLog(d.root);
521
- const pin = log.events[0]?.payload?.rootFingerprint;
522
- const state = verifyKeyLog(log, { rootFingerprint: pin });
523
- if (!state.ok) throw new Error(`committed key log is invalid: ${state.problems.join("; ")}`);
524
- const trust = (d.trustLoader ?? trustState)();
525
- if (!trust?.trust?.rootFingerprint) throw new Error("protected root trust is not enrolled on this host; run ownership enroll");
526
- if (trust.trust.rootFingerprint !== pin) throw new Error("committed key log root does not match this host's protected trust");
527
- const baseline = d.baseline ?? parse(readFileSync(path.join(here, "ownership-default-policy.json"), "utf8"), "ownership default policy");
528
- if (_args.includes("--check")) {
529
- // Read-only: say whether a sync would ask for approval, and for what. Asks nothing, elevates nothing, writes no trust.
530
- const request = makeTrustRequest(log, pin, baseline, d.authority);
531
- try {
532
- const summary = (d.summarize ?? summarizeRequestFile)({ file: request.file, digest: request.digest, existing: trust.trust, ...(d.summaryInputs ?? {}) });
533
- if (!summary.changed) { d.print("trust already current: no approval needed"); return 0; }
534
- d.print(summary.lines.join("\n"));
535
- d.print("approval needed: trust-sync on this host would ask for the change above");
536
- return 3;
537
- } finally { await import("node:fs").then(({ unlinkSync }) => unlinkSync(request.file)).catch(() => {}); }
538
- }
539
- const installed = await installCheckpoint(log, pin, baseline, d);
540
- if (installed === false) { d.print("trust already current"); return 0; }
541
- postVerify(d.root, log, d);
542
- d.print(`Ownership trust synced to key-log sequence ${log.events.length - 1}; active keys: ${state.keys.length}`);
543
- return 0;
544
- }
545
- /**
546
- * Informed-consent gate: summarize the exact request bytes (digest-checked) against the installed trust,
547
- * skip when nothing changes, otherwise require explicit approval before any elevation prompt.
548
- */
549
- function trustSyncGate(d) {
550
- return (request) => {
551
- const existing = (d.trustLoader ?? trustState)()?.trust ?? null;
552
- const summary = (d.summarize ?? summarizeRequestFile)({ file: request.file, digest: request.digest, existing, ...(d.summaryInputs ?? {}) });
553
- if (!summary.changed) return false;
554
- (d.confirm ?? confirmTrustChange)({ lines: summary.lines, run: d.run, interactiveRun: d.run.interactive, print: d.print, hostPlatform: d.hostPlatform, ...(d.confirmOptions ?? {}) });
555
- return true;
556
- };
557
- }
558
509
  async function installCheckpoint(log, rootFingerprint, baseline, d) {
559
510
  const request = makeTrustRequest(log, rootFingerprint, baseline, d.authority);
560
511
  try {
@@ -375,10 +375,10 @@
375
375
  "profile": "desktop",
376
376
  "owners": [
377
377
  {
378
- "name": "rightkit-mcp",
378
+ "name": "rightkit-agent-tools",
379
379
  "ecosystem": "cargo",
380
- "min": "0.6.0",
381
- "registryMax": "0.5.3"
380
+ "min": "0.1.2",
381
+ "registryMax": "0.1.2"
382
382
  }
383
383
  ]
384
384
  }
package/ownership.mjs CHANGED
@@ -562,7 +562,7 @@ function scanInternal(appRoot, manifest, { inventory = fileInventory(appRoot), t
562
562
  if (blocked.has(`${d.ecosystem}:${d.name}`) || evaluateDependency(allowlistPolicy.allowlist, repositoryIdentity, { ecosystem: d.ecosystem, name: d.name, kind: sourceKind(d.source) }, { owners }).allowed) continue;
563
563
  findings.push({ fingerprint: ALLOWLIST_FINDING_ID, capability: "dependency-allowlist", package: "RightKit dependency allowlist", tier: allowlistPolicy.tier, file: d.file, line: 0, kind: "dependency", ecosystem: d.ecosystem, name: d.name,
564
564
  alias: d.alias, section: d.section, requirement: d.requirement, features: d.features, defaultFeatures: d.defaultFeatures, local: d.local, source: d.source, resolvedVersion: d.resolvedVersion,
565
- text: `${d.ecosystem} dependency ${d.name} is not in ${ALLOWLIST_FILE}; propose it there and Adrian activates it via trust-sync`, provenance: d.source });
565
+ text: `${d.ecosystem} dependency ${d.name} is not in ${ALLOWLIST_FILE}; propose it there (advisory until protected trust carries the allowlist; trust-sync is retired)`, provenance: d.source });
566
566
  }
567
567
  }
568
568
  const blockRules = manifest.capabilities.flatMap((c) => c.fingerprints.filter((f) => f.type === "dependency" && findingPolicy(c, f).tier === "block").map((f) => ({ c, f })));
@@ -598,8 +598,8 @@ function shippedAllowlist() {
598
598
  try { const parsed = JSON.parse(readFileSync(shippedAllowlistFile, "utf8")); return validateAllowlist(parsed).ok ? parsed : null; } catch { return null; }
599
599
  }
600
600
  /**
601
- * Which allowlist applies and how hard. The protected trust copy (activated by trust-sync) governs every repository
602
- * registered in trust and blocks. Before trust-sync, or while approval enrollment is pending, the shipped proposal
601
+ * Which allowlist applies and how hard. The protected trust copy governs every repository
602
+ * registered in trust and blocks. When no trust copy applies, or while approval enrollment is pending, the shipped proposal
603
603
  * is advisory and only for repositories it has a baseline for.
604
604
  */
605
605
  export function dependencyAllowlistPolicy(trust, repositoryIdentity, { pending = false, shipped } = {}) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rightkit/release",
3
- "version": "0.2.160",
3
+ "version": "0.2.162",
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",
@@ -2193,7 +2193,7 @@
2193
2193
  "Declaration-only: the lifecycle, bundle writer, redaction, protocol and reader are not an ownership fingerprint.",
2194
2194
  "Windows capture lives in this crate (src/windows, cfg(windows)) and is served out of process by rightkit-record-helper in serve mode.",
2195
2195
  "rightkit-child-process owns helper spawning, supervision and restart; this crate only speaks the protocol over the streams it is given.",
2196
- "The macOS helper is not in this crate. It will implement Backend in its own helper and serve the same protocol."
2196
+ "The macOS ScreenCaptureKit helper (helpers/macos, Swift) ships with this crate sources and implements the same protocol; the crate never sends input. Capturing needs the OS Screen Recording permission on macOS."
2197
2197
  ]
2198
2198
  },
2199
2199
  {
@@ -15,7 +15,7 @@
15
15
  "stagedNpm": {
16
16
  "@rightkit/app-update": "0.2.5",
17
17
  "@rightkit/ax": "0.3.2",
18
- "@rightkit/ci": "0.2.44",
18
+ "@rightkit/ci": "0.2.45",
19
19
  "@rightkit/doc-render": "0.1.6",
20
20
  "@rightkit/hooks": "0.1.2",
21
21
  "@rightkit/i18n": "0.1.0",
@@ -31,7 +31,7 @@
31
31
  "@rightkit/qa": "0.4.8",
32
32
  "@rightkit/qa-chrome": "0.1.6",
33
33
  "@rightkit/react-shell": "0.6.4",
34
- "@rightkit/release": "0.2.160",
34
+ "@rightkit/release": "0.2.162",
35
35
  "@rightkit/settings-ui": "0.4.4",
36
36
  "@rightkit/tauri": "0.2.1",
37
37
  "@rightkit/tauri-shell": "0.1.1",
@@ -201,7 +201,9 @@
201
201
  "0.2.155",
202
202
  "0.2.156",
203
203
  "0.2.158",
204
- "0.2.159"
204
+ "0.2.159",
205
+ "0.2.160",
206
+ "0.2.161"
205
207
  ],
206
208
  "@rightkit/qa": [
207
209
  "0.1.0",
@@ -330,7 +332,8 @@
330
332
  "0.1.0"
331
333
  ],
332
334
  "@rightkit/ci": [
333
- "0.2.43"
335
+ "0.2.43",
336
+ "0.2.44"
334
337
  ]
335
338
  },
336
339
  "cargo": {
@@ -365,7 +368,7 @@
365
368
  "rightkit-contracts": "0.1.5",
366
369
  "rightkit-crash": "0.3.0",
367
370
  "rightkit-credentials": "0.2.0",
368
- "rightkit-daemon": "0.2.2",
371
+ "rightkit-daemon": "0.2.3",
369
372
  "rightkit-db": "0.4.0",
370
373
  "rightkit-device": "0.1.3",
371
374
  "rightkit-embed": "0.4.0",
@@ -388,8 +391,8 @@
388
391
  "rightkit-ownership": "0.2.1",
389
392
  "rightkit-pdf": "0.3.0",
390
393
  "rightkit-playback": "0.2.1",
391
- "rightkit-qa": "0.3.0",
392
- "rightkit-qa-driver": "0.3.1",
394
+ "rightkit-qa": "0.4.0",
395
+ "rightkit-qa-driver": "0.4.0",
393
396
  "rightkit-review": "0.2.0",
394
397
  "rightkit-sandbox": "0.4.0",
395
398
  "rightkit-search": "0.2.1",
@@ -435,7 +438,7 @@
435
438
  "rightkit-control": "0.3.0",
436
439
  "rightkit-crash": "0.3.0",
437
440
  "rightkit-credentials": "0.2.0",
438
- "rightkit-daemon": "0.2.2",
441
+ "rightkit-daemon": "0.2.3",
439
442
  "rightkit-db": "0.4.0",
440
443
  "rightkit-device": "0.1.3",
441
444
  "rightkit-embed": "0.4.0",
@@ -463,8 +466,8 @@
463
466
  "rightkit-pdf": "0.3.0",
464
467
  "rightkit-playback": "0.2.1",
465
468
  "rightkit-process": "0.4.0",
466
- "rightkit-qa": "0.3.0",
467
- "rightkit-qa-driver": "0.3.1",
469
+ "rightkit-qa": "0.4.0",
470
+ "rightkit-qa-driver": "0.4.0",
468
471
  "rightkit-qts": "0.2.6",
469
472
  "rightkit-qts-ggml": "0.1.2",
470
473
  "rightkit-qts-ggml-sys": "0.1.2",
@@ -493,7 +496,7 @@
493
496
  "@rightkit/app-shell": "0.6.2",
494
497
  "@rightkit/app-update": "0.2.5",
495
498
  "@rightkit/ax": "0.3.2",
496
- "@rightkit/ci": "0.2.44",
499
+ "@rightkit/ci": "0.2.45",
497
500
  "@rightkit/doc-render": "0.1.6",
498
501
  "@rightkit/git": "0.2.42",
499
502
  "@rightkit/hooks": "0.1.2",
@@ -510,7 +513,7 @@
510
513
  "@rightkit/qa": "0.4.8",
511
514
  "@rightkit/qa-chrome": "0.1.6",
512
515
  "@rightkit/react-shell": "0.6.4",
513
- "@rightkit/release": "0.2.159",
516
+ "@rightkit/release": "0.2.161",
514
517
  "@rightkit/settings-ui": "0.4.4",
515
518
  "@rightkit/shell": "0.1.1",
516
519
  "@rightkit/tauri": "0.2.1",
@@ -610,7 +613,8 @@
610
613
  "0.2.11",
611
614
  "0.2.12",
612
615
  "0.2.14",
613
- "0.2.16"
616
+ "0.2.16",
617
+ "0.3.0"
614
618
  ],
615
619
  "rightkit-sandbox": [
616
620
  "0.1.0",
@@ -743,13 +747,15 @@
743
747
  ],
744
748
  "rightkit-daemon": [
745
749
  "0.2.0",
746
- "0.2.1"
750
+ "0.2.1",
751
+ "0.2.2"
747
752
  ],
748
753
  "rightkit-job-queue": [
749
754
  "0.3.0"
750
755
  ],
751
756
  "rightkit-qa-driver": [
752
- "0.3.0"
757
+ "0.3.0",
758
+ "0.3.1"
753
759
  ]
754
760
  }
755
761
  }