@rightkit/release 0.2.115 → 0.2.117

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.
@@ -22,6 +22,7 @@ const commands = new Map([
22
22
  ["mirror-root-artifact", "mirror-root-artifact.mjs"],
23
23
  ["github", "github-release.mjs"],
24
24
  ["cargo-target", "cargo-target.mjs"],
25
+ ["ownership", "ownership-command.mjs"],
25
26
  ]);
26
27
 
27
28
  const args = process.argv.slice(2);
@@ -157,8 +158,10 @@ Commands:
157
158
  Upload one sealed release; never builds
158
159
  release Compatibility alias for build
159
160
  publish Compatibility alias for upload
160
- publish npm --package @rightkit/<name> [--dry-run] Gate preflight for npm publish (no publish)
161
- publish npm --package @rightkit/<name> [--dry-run] Gate preflight for npm publish (never publishes)
161
+ publish npm --package @rightkit/<name> [--dry-run] Gate preflight for npm publish
162
+ publish npm --package @rightkit/<name> --publish --tarball <gated.tgz>
163
+ Publish the gated tarball with the Keychain token
164
+ (npm-rightkit-publish) and verify registry bytes
162
165
  publish cargo --crate <name> [--dry-run] Test, inspect, scan, and publish one crates.io package
163
166
  publish swift [--repo <git-url>] [--dry-run] Test, tag, resolve a fresh exact-pin consumer, and publish RightKitSwift to its git repo
164
167
  model promote --authority heardright --config <file> [--dry-run]
@@ -167,6 +170,7 @@ Commands:
167
170
  doctor [--platform mac|win] Inspect one app's release config
168
171
  doctor --all Verify all Right Suite app release contracts
169
172
  suite-doctor Verify all local Right Suite repositories
173
+ ownership [--app <dir>] [--json] Fail on app copies of RightKit-owned capabilities
170
174
  cache status [--json] Inspect the shared local build cache
171
175
  cache prune [--dry-run|--apply] [--json] Plan or apply safe shared-target pruning
172
176
  cache migrate --config <file> [--dry-run|--apply] [--json]
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env node
2
+ // right-release ownership: check an app against RightKit's capability ownership, or rebuild
3
+ // the shipped manifest from each package's declaration.
4
+ //
5
+ // right-release ownership [--app <dir>] [--json]
6
+ // right-release ownership --write-manifest [--rightkit <tools/rightkit>]
7
+ import { readFileSync, writeFileSync } from "node:fs";
8
+ import path from "node:path";
9
+ import { fileURLToPath } from "node:url";
10
+ import { aggregateOwnership, checkApp, formatTable, MANIFEST_FILE } from "./ownership.mjs";
11
+
12
+ const packageRoot = path.dirname(fileURLToPath(import.meta.url));
13
+ const shipped = path.join(packageRoot, MANIFEST_FILE);
14
+
15
+ function flag(args, name) {
16
+ const i = args.indexOf(name);
17
+ return i >= 0 ? args[i + 1] : undefined;
18
+ }
19
+
20
+ export function main(args) {
21
+ if (args.includes("--write-manifest")) {
22
+ const root = path.resolve(flag(args, "--rightkit") ?? path.join(packageRoot, "..", ".."));
23
+ const manifest = aggregateOwnership(root);
24
+ writeFileSync(shipped, `${JSON.stringify(manifest, null, 2)}\n`);
25
+ console.log(`[right-release] ownership manifest: ${manifest.capabilities.length} capabilities -> ${shipped}`);
26
+ return 0;
27
+ }
28
+ const app = path.resolve(flag(args, "--app") ?? process.cwd());
29
+ const manifest = JSON.parse(readFileSync(shipped, "utf8"));
30
+ const result = checkApp(app, manifest);
31
+ if (args.includes("--json")) {
32
+ console.log(JSON.stringify({ ok: result.ok, rows: result.rows, violations: result.violations, problems: result.problems }, null, 2));
33
+ } else {
34
+ console.log(`RightKit ownership: ${path.basename(app)}`);
35
+ console.log(formatTable(result.rows));
36
+ for (const v of result.violations) {
37
+ console.log(` OWN COPY ${v.fingerprint} (${v.package}) ${v.file}${v.line ? `:${v.line}` : ""} ${v.text}`);
38
+ }
39
+ for (const p of result.problems) console.log(` EXCEPTION ${p}`);
40
+ console.log(result.ok ? "[right-release] ownership passed" : "[right-release] ownership FAILED: move these to RightKit or add a reviewed, dated exception");
41
+ }
42
+ return result.ok ? 0 : 1;
43
+ }
44
+
45
+ if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
46
+ process.exitCode = main(process.argv.slice(2));
47
+ }
package/ownership.mjs ADDED
@@ -0,0 +1,273 @@
1
+ // RightKit ownership gate: no Right Suite app keeps its own copy of a capability RightKit owns.
2
+ //
3
+ // Each RightKit package declares what it owns in `rightkit-ownership.json` beside its manifest
4
+ // (capability + fingerprints of a reimplementation). `aggregateOwnership` folds those into the
5
+ // shipped `rightkit-ownership.json`, so an app is always checked against the data of the
6
+ // RightKit version it pins. `scanApp` reports fingerprint matches in an app repository; each
7
+ // must be covered by a live, specific entry in the app's `.rightkit/ownership-exceptions.json`
8
+ // (the migration backlog) or the gate fails.
9
+ //
10
+ // Fingerprint types:
11
+ // dependency direct dependency by name (cargo | npm | swiftpm). Transitive use through a
12
+ // RightKit package is never flagged.
13
+ // source regex over code files (comments-only lines, docs, tests fixtures, vendored and
14
+ // generated trees are skipped).
15
+ import { spawnSync } from "./background-process.mjs";
16
+ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
17
+ import path from "node:path";
18
+
19
+ export const MANIFEST_FILE = "rightkit-ownership.json";
20
+ export const EXCEPTIONS_FILE = path.join(".rightkit", "ownership-exceptions.json");
21
+
22
+ const SKIP_DIRS = new Set([
23
+ ".git", "node_modules", "target", "dist", "build", "out", ".next", ".turbo", ".cache",
24
+ "vendor", "third_party", "Pods", "DerivedData", ".build", "generated", "fixtures",
25
+ "test-fixtures", "__fixtures__", ".rightkit", ".audit",
26
+ ]);
27
+ const CODE_EXT = new Set([".rs", ".ts", ".tsx", ".js", ".mjs", ".cjs", ".jsx", ".swift", ".m", ".mm", ".c", ".cc", ".cpp", ".h", ".py", ".go", ".kt", ".java", ".cs"]);
28
+ const MAX_FILE_BYTES = 2 * 1024 * 1024;
29
+
30
+ /** Collect every package's ownership declaration under a RightKit checkout. */
31
+ export function aggregateOwnership(rightkitRoot) {
32
+ const entries = [];
33
+ for (const group of ["crates", "packages"]) {
34
+ const dir = path.join(rightkitRoot, group);
35
+ if (!existsSync(dir)) continue;
36
+ for (const name of readdirSync(dir).sort()) {
37
+ const file = path.join(dir, name, MANIFEST_FILE);
38
+ if (!existsSync(file)) continue;
39
+ const declared = JSON.parse(readFileSync(file, "utf8"));
40
+ if (declared.schema !== undefined) continue; // the shipped aggregate itself
41
+ for (const capability of declared.capabilities ?? []) {
42
+ entries.push({ package: declared.package, ...capability });
43
+ }
44
+ }
45
+ }
46
+ const manifest = { schema: 1, capabilities: entries };
47
+ validateManifest(manifest);
48
+ return manifest;
49
+ }
50
+
51
+ export function validateManifest(manifest) {
52
+ const ids = new Set();
53
+ for (const c of manifest.capabilities) {
54
+ if (!c.package || !c.capability || !Array.isArray(c.fingerprints) || c.fingerprints.length === 0) {
55
+ throw new Error(`ownership: incomplete capability ${JSON.stringify(c).slice(0, 120)}`);
56
+ }
57
+ for (const f of c.fingerprints) {
58
+ if (!f.id || ids.has(f.id)) throw new Error(`ownership: missing or duplicate fingerprint id ${f.id}`);
59
+ ids.add(f.id);
60
+ if (f.type === "dependency") {
61
+ if (!["cargo", "npm", "swiftpm"].includes(f.ecosystem) || !f.name) throw new Error(`ownership: bad dependency fingerprint ${f.id}`);
62
+ } else if (f.type === "source") {
63
+ new RegExp(f.pattern); // throws on a bad pattern
64
+ } else {
65
+ throw new Error(`ownership: unknown fingerprint type ${f.type} (${f.id})`);
66
+ }
67
+ }
68
+ }
69
+ }
70
+
71
+ function skippedDir(name) {
72
+ return SKIP_DIRS.has(name) || name.startsWith(".") || /^(node_modules|target)/.test(name);
73
+ }
74
+
75
+ const RIGHTKIT_MARKER = `packages/release/${MANIFEST_FILE}`;
76
+
77
+ // A nested repository is its own app with its own exceptions file, and RightKit's own sources
78
+ // are the owner, never a copy.
79
+ function walkTree(root, visit, rel = "") {
80
+ let entries;
81
+ try { entries = readdirSync(path.join(root, rel), { withFileTypes: true }); } catch { return; }
82
+ for (const e of entries) {
83
+ const r = rel ? `${rel}/${e.name}` : e.name;
84
+ if (e.isDirectory()) {
85
+ if (!skippedDir(e.name) && !existsSync(path.join(root, r, ".git"))
86
+ && !existsSync(path.join(root, r, RIGHTKIT_MARKER))) walkTree(root, visit, r);
87
+ } else if (e.isFile()) {
88
+ visit(r);
89
+ }
90
+ }
91
+ }
92
+
93
+ // Tracked plus untracked-but-not-ignored files. Git never descends into nested repositories,
94
+ // and honoring .gitignore keeps model, data and build trees out of the scan.
95
+ function gitFiles(root) {
96
+ if (!existsSync(path.join(root, ".git"))) return null;
97
+ const r = spawnSync("git", ["-C", root, "ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
98
+ encoding: "utf8", maxBuffer: 256 * 1024 * 1024, windowsHide: true,
99
+ });
100
+ if (r.status !== 0) return null;
101
+ const files = r.stdout.split("\0").filter((f) => f && !f.endsWith("/"));
102
+ const rightkitRoots = files.filter((f) => f === RIGHTKIT_MARKER || f.endsWith(`/${RIGHTKIT_MARKER}`))
103
+ .map((f) => f.slice(0, f.length - RIGHTKIT_MARKER.length));
104
+ return files.filter((f) => !rightkitRoots.some((p) => f.startsWith(p))
105
+ && !f.split("/").slice(0, -1).some(skippedDir));
106
+ }
107
+
108
+ function walk(root, visit) {
109
+ const files = gitFiles(root);
110
+ if (files) files.forEach((f) => visit(f));
111
+ else walkTree(root, visit);
112
+ }
113
+
114
+ /** Direct dependencies declared by an app repository: [{ecosystem, name, file}]. */
115
+ export function directDependencies(appRoot) {
116
+ const deps = [];
117
+ walk(appRoot, (rel) => {
118
+ const base = path.posix.basename(rel);
119
+ const abs = path.join(appRoot, rel);
120
+ if (base === "Cargo.toml") {
121
+ let section = "";
122
+ for (const line of readFileSync(abs, "utf8").split(/\r?\n/)) {
123
+ const header = line.match(/^\s*\[([^\]]+)\]/);
124
+ if (header) { section = header[1]; continue; }
125
+ const dotted = section.match(/(^|\.)(dev-|build-)?dependencies\.([A-Za-z0-9_-]+)$/);
126
+ if (dotted) { deps.push({ ecosystem: "cargo", name: dotted[3], file: rel }); section = "~"; continue; }
127
+ if (!/(^|\.)((dev|build)-)?dependencies$/.test(section)) continue;
128
+ const m = line.match(/^\s*([A-Za-z0-9_-]+)\s*=/);
129
+ if (m) deps.push({ ecosystem: "cargo", name: m[1], file: rel });
130
+ }
131
+ } else if (base === "package.json") {
132
+ let pkg;
133
+ try { pkg = JSON.parse(readFileSync(abs, "utf8")); } catch { return; }
134
+ for (const field of ["dependencies", "devDependencies", "optionalDependencies", "peerDependencies"]) {
135
+ for (const name of Object.keys(pkg[field] ?? {})) deps.push({ ecosystem: "npm", name, file: rel });
136
+ }
137
+ } else if (base === "Package.swift") {
138
+ for (const m of readFileSync(abs, "utf8").matchAll(/\.package\([^)]*url:\s*"([^"]+)"/g)) {
139
+ const name = m[1].replace(/\.git$/, "").split("/").pop();
140
+ deps.push({ ecosystem: "swiftpm", name, file: rel });
141
+ }
142
+ }
143
+ });
144
+ return deps;
145
+ }
146
+
147
+ function isCommentOnly(line) {
148
+ const t = line.trim();
149
+ return t.startsWith("//") || t.startsWith("#") || t.startsWith("*") || t.startsWith("/*") || t.startsWith("--");
150
+ }
151
+
152
+ /** All fingerprint matches in an app: [{fingerprint, capability, package, file, line, text}]. */
153
+ export function scanApp(appRoot, manifest) {
154
+ const findings = [];
155
+ const deps = directDependencies(appRoot);
156
+ const sources = [];
157
+ for (const c of manifest.capabilities) {
158
+ for (const f of c.fingerprints) {
159
+ if (f.type === "dependency") {
160
+ for (const d of deps) {
161
+ if (d.ecosystem === f.ecosystem && d.name === f.name) {
162
+ findings.push({ fingerprint: f.id, capability: c.capability, package: c.package, file: d.file, line: 0, text: `${d.ecosystem} dependency ${d.name}` });
163
+ }
164
+ }
165
+ } else {
166
+ sources.push({ c, f, re: new RegExp(f.pattern, f.flags ?? "") });
167
+ }
168
+ }
169
+ }
170
+ if (sources.length) {
171
+ // One combined pass rejects almost every file and line before the per-fingerprint tests.
172
+ const any = new RegExp(sources.map(({ f }) => `(?:${f.pattern})`).join("|"));
173
+ walk(appRoot, (rel) => {
174
+ if (!CODE_EXT.has(path.posix.extname(rel))) return;
175
+ const abs = path.join(appRoot, rel);
176
+ try { if (statSync(abs).size > MAX_FILE_BYTES) return; } catch { return; }
177
+ const text = readFileSync(abs, "utf8");
178
+ if (!any.test(text)) return;
179
+ const lines = text.split(/\r?\n/);
180
+ lines.forEach((line, i) => {
181
+ if (!any.test(line) || isCommentOnly(line)) return;
182
+ for (const { c, f, re } of sources) {
183
+ if (re.test(line)) findings.push({ fingerprint: f.id, capability: c.capability, package: c.package, file: rel, line: i + 1, text: line.trim().slice(0, 160) });
184
+ }
185
+ });
186
+ });
187
+ }
188
+ return findings;
189
+ }
190
+
191
+ function globToRegExp(glob) {
192
+ let re = "";
193
+ for (let i = 0; i < glob.length; i++) {
194
+ const ch = glob[i];
195
+ if (ch === "*" && glob[i + 1] === "*") { re += ".*"; i++; if (glob[i + 1] === "/") i++; }
196
+ else if (ch === "*") re += "[^/]*";
197
+ else if (ch === "?") re += "[^/]";
198
+ else re += ch.replace(/[.+^${}()|[\]\\]/g, "\\$&");
199
+ }
200
+ return new RegExp(`^${re}$`);
201
+ }
202
+
203
+ /** Load and validate an app's exceptions (the migration backlog). */
204
+ export function loadExceptions(appRoot) {
205
+ const file = path.join(appRoot, EXCEPTIONS_FILE);
206
+ if (!existsSync(file)) return [];
207
+ const parsed = JSON.parse(readFileSync(file, "utf8"));
208
+ return (parsed.exceptions ?? []).map((e, i) => ({ ...e, index: i }));
209
+ }
210
+
211
+ /**
212
+ * Apply exceptions. Returns {violations, covered, problems}: uncovered findings, findings an
213
+ * exception covers, and exception defects (expired, stale, wildcard-only, incomplete).
214
+ */
215
+ export function evaluate(findings, exceptions, { today = new Date().toISOString().slice(0, 10) } = {}) {
216
+ const problems = [];
217
+ const live = [];
218
+ for (const e of exceptions) {
219
+ const where = `exception #${e.index} (${e.fingerprint ?? "?"})`;
220
+ const missing = ["fingerprint", "paths", "reason", "owner", "approvedBy"].filter((k) => !e[k] || (Array.isArray(e[k]) && e[k].length === 0));
221
+ if (missing.length) { problems.push(`${where}: missing ${missing.join(", ")}`); continue; }
222
+ if (!e.expires && !e.tracking) { problems.push(`${where}: needs an expires date or a tracking item`); continue; }
223
+ if (e.paths.some((p) => /^\*+(\/\*+)*$/.test(p))) { problems.push(`${where}: wildcard-only paths are not allowed`); continue; }
224
+ if (e.expires && e.expires < today) { problems.push(`${where}: expired ${e.expires}`); continue; }
225
+ live.push({ ...e, matchers: e.paths.map(globToRegExp), used: false });
226
+ }
227
+ const violations = [];
228
+ const covered = [];
229
+ for (const f of findings) {
230
+ const e = live.find((x) => x.fingerprint === f.fingerprint && x.matchers.some((m) => m.test(f.file)));
231
+ if (e) { e.used = true; covered.push({ ...f, exception: e }); } else violations.push(f);
232
+ }
233
+ for (const e of live) if (!e.used) problems.push(`exception #${e.index} (${e.fingerprint}): stale, matches nothing; delete it`);
234
+ return { violations, covered, problems };
235
+ }
236
+
237
+ /** One row per capability: uses RightKit | own copy (exception, expiry) | not needed. */
238
+ export function capabilityTable(manifest, deps, result) {
239
+ const rows = [];
240
+ const byCapability = new Map();
241
+ for (const c of manifest.capabilities) {
242
+ if (!byCapability.has(c.capability)) byCapability.set(c.capability, c.package);
243
+ }
244
+ for (const [capability, owner] of byCapability) {
245
+ const uses = deps.some((d) => d.name === owner || d.name === owner.replace(/^rightkit-/, "@rightkit/"));
246
+ const own = result.violations.filter((v) => v.capability === capability);
247
+ const excepted = result.covered.filter((v) => v.capability === capability);
248
+ let status;
249
+ if (own.length) status = `OWN COPY, NOT EXCEPTED (${own.length})`;
250
+ else if (excepted.length) {
251
+ const e = excepted[0].exception;
252
+ status = `own copy, excepted until ${e.expires ?? e.tracking} (${excepted.length})`;
253
+ } else status = uses ? "uses RightKit" : "not needed";
254
+ rows.push({ capability, owner, uses, status });
255
+ }
256
+ return rows;
257
+ }
258
+
259
+ export function formatTable(rows) {
260
+ const w1 = Math.max(10, ...rows.map((r) => r.capability.length));
261
+ const w2 = Math.max(7, ...rows.map((r) => r.owner.length));
262
+ const lines = [`${"capability".padEnd(w1)} ${"owner".padEnd(w2)} status`];
263
+ for (const r of rows) lines.push(`${r.capability.padEnd(w1)} ${r.owner.padEnd(w2)} ${r.status}`);
264
+ return lines.join("\n");
265
+ }
266
+
267
+ /** Full check for one app. `ok` is false on any uncovered copy or exception defect. */
268
+ export function checkApp(appRoot, manifest, options = {}) {
269
+ const findings = scanApp(appRoot, manifest);
270
+ const result = evaluate(findings, loadExceptions(appRoot), options);
271
+ const rows = capabilityTable(manifest, directDependencies(appRoot), result);
272
+ return { ok: result.violations.length === 0 && result.problems.length === 0, rows, ...result };
273
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rightkit/release",
3
- "version": "0.2.115",
3
+ "version": "0.2.117",
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",
package/preflight.mjs CHANGED
@@ -20,8 +20,10 @@
20
20
 
21
21
  import { existsSync, lstatSync, readdirSync, readFileSync, statSync } from "node:fs";
22
22
  import path from "node:path";
23
+ import { fileURLToPath } from "node:url";
23
24
  import { spawnSync } from "./background-process.mjs";
24
25
  import { brokerManagedHost } from "./cargo-contract.mjs";
26
+ import { checkApp, MANIFEST_FILE } from "./ownership.mjs";
25
27
 
26
28
  export const PREFLIGHT_OK = "ok";
27
29
  export const PREFLIGHT_WARN = "warn";
@@ -176,6 +178,7 @@ export function collectPreflight({
176
178
  env = process.env,
177
179
  versionProbe = commandVersion,
178
180
  isBrokerManagedHost = brokerManagedHost,
181
+ ownership = true,
179
182
  } = {}) {
180
183
  const checks = [];
181
184
  const windows = platform === "win" || platform === "win32";
@@ -287,9 +290,35 @@ export function collectPreflight({
287
290
  }
288
291
  }
289
292
 
293
+ if (repoRoot && ownership) {
294
+ // An app living inside the RightKit workspace repository is scoped to its own directory.
295
+ const workspace = existsSync(path.join(repoRoot, "tools", "rightkit", "packages", "release"));
296
+ checks.push(ownershipCheck(workspace && appRoot ? appRoot : repoRoot));
297
+ }
298
+
290
299
  return checks;
291
300
  }
292
301
 
302
+ /** Release-blocking: the app keeps no unexcepted copy of a RightKit-owned capability. */
303
+ export function ownershipCheck(repoRoot, manifestPath = path.join(path.dirname(fileURLToPath(import.meta.url)), MANIFEST_FILE)) {
304
+ let manifest;
305
+ try {
306
+ manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
307
+ } catch (error) {
308
+ return fail("ownership", `RightKit ownership manifest unreadable: ${error.message}`, "reinstall @rightkit/release");
309
+ }
310
+ const result = checkApp(repoRoot, manifest);
311
+ if (result.ok) {
312
+ const excepted = result.covered.length;
313
+ return ok("ownership", excepted ? `no unexcepted copies (${excepted} findings under dated exceptions)` : "no copies of RightKit-owned capabilities");
314
+ }
315
+ const first = result.violations[0];
316
+ const detail = first
317
+ ? `${result.violations.length} own cop${result.violations.length === 1 ? "y" : "ies"} of RightKit capabilities, e.g. ${first.fingerprint} at ${first.file}${first.line ? `:${first.line}` : ""}`
318
+ : result.problems[0];
319
+ return fail("ownership", detail, "run: right-release ownership (move to RightKit, or add a reviewed, dated exception)");
320
+ }
321
+
293
322
  export function formatPreflight(checks) {
294
323
  const symbol = { [PREFLIGHT_OK]: "ok ", [PREFLIGHT_WARN]: "WARN", [PREFLIGHT_FAIL]: "FAIL" };
295
324
  const lines = checks.map((check) => {
@@ -3,7 +3,9 @@
3
3
  // evidence file names the exact current commit SHA (clean tree for the package path) and carries
4
4
  // passing host run receipts from that same SHA: macOS and Windows, or macOS only for Swift.
5
5
  // Format: docs/RIGHTKIT-SHARED-PACKAGES.md, "Publication gate" -> "Evidence file format".
6
+ import crypto from "node:crypto";
6
7
  import fs from "node:fs";
8
+ import os from "node:os";
7
9
  import path from "node:path";
8
10
  import { spawnSync } from "./background-process.mjs";
9
11
  import { fileURLToPath } from "node:url";
@@ -43,7 +45,11 @@ export function checkPublicationGate({ kind, name, version, packagePath, evidenc
43
45
  if (!SHA.test(String(evidence.commit ?? ""))) missing.push("evidence commit is not a full 40-character SHA");
44
46
  else if (head && evidence.commit !== head) missing.push(`evidence commit ${evidence.commit} != current HEAD ${head}`);
45
47
  const runs = Array.isArray(evidence.runs) ? evidence.runs : [];
46
- for (const host of requiredHosts(kind)) {
48
+ // npm UI packages may ship on macOS evidence first when Adrian decides so; the decision must be
49
+ // recorded in the evidence (hostPolicy "darwin-only" + a non-empty hostPolicyReason).
50
+ const darwinOnly = kind === "npm" && evidence.hostPolicy === "darwin-only"
51
+ && typeof evidence.hostPolicyReason === "string" && evidence.hostPolicyReason.trim() !== "";
52
+ for (const host of darwinOnly ? ["darwin"] : requiredHosts(kind)) {
47
53
  const label = host === "darwin" ? "macOS" : "Windows";
48
54
  const matching = runs.filter((run) => run?.host?.os === host);
49
55
  if (!matching.length) { missing.push(`no ${label} host run receipt`); continue; }
@@ -68,21 +74,76 @@ export function enforcePublicationGate(options, { dryRun = false, write = (line)
68
74
  throw new Error(report);
69
75
  }
70
76
 
71
- // `right-release publish npm --package <name> [--evidence <file>] [--dry-run]`: preflight only.
72
- export function runNpmPreflight(argv, workspaceRoot = process.cwd()) {
73
- let pkg; let evidence; const dryRun = argv.includes("--dry-run");
77
+ // Keychain item holding a granular npm token scoped to @rightkit with 2FA bypass (created by Adrian).
78
+ export const NPM_KEYCHAIN_SERVICE = "npm-rightkit-publish";
79
+
80
+ // `right-release publish npm --package <name> [--evidence <file>] [--dry-run]` runs the gate only.
81
+ // Add `--publish --tarball <file>` to publish that exact gated tarball: its sha256 must equal the
82
+ // evidence artifactSha256, the token is read from the macOS Keychain inside this process (never
83
+ // printed or passed on a command line), and the registry shasum is verified after publication.
84
+ export function runNpmPreflight(argv, workspaceRoot = process.cwd(), deps = {}) {
85
+ let pkg; let evidence; let tarball; const dryRun = argv.includes("--dry-run"); const publish = argv.includes("--publish");
74
86
  for (let i = 0; i < argv.length; i += 1) {
75
87
  if (argv[i] === "--package") pkg = argv[++i];
76
88
  else if (argv[i] === "--evidence") evidence = argv[++i];
89
+ else if (argv[i] === "--tarball") tarball = path.resolve(argv[++i]);
77
90
  else if (argv[i] === "--workspace") workspaceRoot = path.resolve(argv[++i]);
78
- else if (argv[i] !== "--dry-run") throw new Error(`unknown publish npm option: ${argv[i]}`);
91
+ else if (argv[i] !== "--dry-run" && argv[i] !== "--publish") throw new Error(`unknown publish npm option: ${argv[i]}`);
79
92
  }
80
93
  if (!pkg || !/^@rightkit\/[a-z0-9-]+$/.test(pkg)) throw new Error("publish npm requires --package @rightkit/<name>");
81
94
  const packagePath = path.join(workspaceRoot, "packages", pkg.slice("@rightkit/".length));
82
95
  const manifest = JSON.parse(fs.readFileSync(path.join(packagePath, "package.json"), "utf8"));
83
- const options = { kind: "npm", name: pkg, version: manifest.version, packagePath, evidencePath: path.resolve(evidence ?? defaultEvidencePath(workspaceRoot, "npm", pkg)) };
84
- enforcePublicationGate(options, { dryRun });
85
- process.stdout.write("[right-release] npm preflight only: publish stays on the authenticated Windows primary checkout (pnpm publish)\n");
96
+ const evidencePath = path.resolve(evidence ?? defaultEvidencePath(workspaceRoot, "npm", pkg));
97
+ const options = { kind: "npm", name: pkg, version: manifest.version, packagePath, evidencePath };
98
+ if (!publish) {
99
+ enforcePublicationGate(options, { dryRun });
100
+ process.stdout.write("[right-release] npm preflight only; add --publish --tarball <file> to publish\n");
101
+ return;
102
+ }
103
+ if (dryRun) throw new Error("--publish and --dry-run are mutually exclusive");
104
+ if (!tarball) throw new Error("--publish requires --tarball <gated .tgz>");
105
+ enforcePublicationGate(options);
106
+ publishGatedTarball({ name: pkg, version: manifest.version, tarball, evidencePath }, deps);
107
+ }
108
+
109
+ export function publishGatedTarball({ name, version, tarball, evidencePath }, deps = {}) {
110
+ const run = deps.spawnSync ?? spawnSync;
111
+ const bytes = fs.readFileSync(tarball);
112
+ const evidence = JSON.parse(fs.readFileSync(evidencePath, "utf8"));
113
+ const sha256 = crypto.createHash("sha256").update(bytes).digest("hex");
114
+ if (sha256 !== evidence.artifactSha256) throw new Error(`tarball sha256 ${sha256} != evidence artifactSha256 ${evidence.artifactSha256}`);
115
+ const token = deps.readToken ? deps.readToken() : readKeychainToken(run);
116
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "rk-npm-publish-"));
117
+ const userconfig = path.join(dir, "npmrc");
118
+ const redact = (text) => String(text ?? "").split(token).join("<redacted>");
119
+ try {
120
+ fs.writeFileSync(userconfig, "//registry.npmjs.org/:_authToken=${RIGHTKIT_NPM_TOKEN}\n", { mode: 0o600 });
121
+ const result = run("npm", ["publish", tarball, "--access", "public", "--registry", "https://registry.npmjs.org/", "--userconfig", userconfig],
122
+ { encoding: "utf8", env: { ...process.env, RIGHTKIT_NPM_TOKEN: token }, windowsHide: true });
123
+ const out = redact(`${result.stdout ?? ""}${result.stderr ?? ""}`);
124
+ if (result.error || result.status !== 0) throw new Error(`npm publish failed for ${name}@${version}:\n${out.trim()}`);
125
+ process.stdout.write(`[right-release] published ${name}@${version}\n`);
126
+ } finally {
127
+ fs.rmSync(dir, { recursive: true, force: true });
128
+ }
129
+ const sha1 = crypto.createHash("sha1").update(bytes).digest("hex");
130
+ const deadline = Date.now() + (deps.verifyTimeoutMs ?? 10 * 60 * 1000);
131
+ for (;;) {
132
+ const view = run("npm", ["view", `${name}@${version}`, "dist.shasum", "--prefer-online", "--registry", "https://registry.npmjs.org/"], { encoding: "utf8", windowsHide: true });
133
+ const registry = (view.stdout ?? "").trim();
134
+ if (registry === sha1) { process.stdout.write(`[right-release] registry bytes verified for ${name}@${version} (shasum ${sha1})\n`); return; }
135
+ if (registry && registry !== sha1) throw new Error(`registry shasum ${registry} != gated tarball ${sha1} for ${name}@${version}`);
136
+ if (Date.now() > deadline) throw new Error(`${name}@${version} not visible on the registry after publish; verify later with npm view`);
137
+ (deps.sleep ?? ((ms) => Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms)))(15000);
138
+ }
139
+ }
140
+
141
+ function readKeychainToken(run) {
142
+ if (process.platform !== "darwin") throw new Error(`npm publish token lookup is implemented for the macOS Keychain (${NPM_KEYCHAIN_SERVICE}) only`);
143
+ const result = run("security", ["find-generic-password", "-s", NPM_KEYCHAIN_SERVICE, "-w"], { encoding: "utf8", windowsHide: true });
144
+ const token = (result.stdout ?? "").trim();
145
+ if (result.status !== 0 || !token) throw new Error(`Keychain item ${NPM_KEYCHAIN_SERVICE} not found; create it with: security add-generic-password -a "$USER" -s ${NPM_KEYCHAIN_SERVICE} -w`);
146
+ return token;
86
147
  }
87
148
 
88
149
  if (process.argv[1] && path.resolve(process.argv[1]) === path.resolve(fileURLToPath(import.meta.url))) {
@@ -0,0 +1,363 @@
1
+ {
2
+ "schema": 1,
3
+ "capabilities": [
4
+ {
5
+ "package": "rightkit-browser",
6
+ "capability": "browser-automation",
7
+ "summary": "Chromium over CDP with admission, SSRF policy, profiles and owned processes",
8
+ "fingerprints": [
9
+ {
10
+ "id": "browser.cargo.chromiumoxide",
11
+ "type": "dependency",
12
+ "ecosystem": "cargo",
13
+ "name": "chromiumoxide"
14
+ },
15
+ {
16
+ "id": "browser.cargo.headless_chrome",
17
+ "type": "dependency",
18
+ "ecosystem": "cargo",
19
+ "name": "headless_chrome"
20
+ },
21
+ {
22
+ "id": "browser.cargo.fantoccini",
23
+ "type": "dependency",
24
+ "ecosystem": "cargo",
25
+ "name": "fantoccini"
26
+ },
27
+ {
28
+ "id": "browser.cargo.thirtyfour",
29
+ "type": "dependency",
30
+ "ecosystem": "cargo",
31
+ "name": "thirtyfour"
32
+ },
33
+ {
34
+ "id": "browser.npm.puppeteer",
35
+ "type": "dependency",
36
+ "ecosystem": "npm",
37
+ "name": "puppeteer"
38
+ },
39
+ {
40
+ "id": "browser.npm.puppeteer-core",
41
+ "type": "dependency",
42
+ "ecosystem": "npm",
43
+ "name": "puppeteer-core"
44
+ },
45
+ {
46
+ "id": "browser.npm.playwright-core",
47
+ "type": "dependency",
48
+ "ecosystem": "npm",
49
+ "name": "playwright-core"
50
+ }
51
+ ]
52
+ },
53
+ {
54
+ "package": "rightkit-db",
55
+ "capability": "sqlite-storage",
56
+ "summary": "SQLite open profiles, WAL with verified fallback, keys, migrations, backup, recovery",
57
+ "fingerprints": [
58
+ {
59
+ "id": "db.cargo.rusqlite",
60
+ "type": "dependency",
61
+ "ecosystem": "cargo",
62
+ "name": "rusqlite"
63
+ },
64
+ {
65
+ "id": "db.cargo.sqlx",
66
+ "type": "dependency",
67
+ "ecosystem": "cargo",
68
+ "name": "sqlx"
69
+ },
70
+ {
71
+ "id": "db.npm.better-sqlite3",
72
+ "type": "dependency",
73
+ "ecosystem": "npm",
74
+ "name": "better-sqlite3"
75
+ }
76
+ ]
77
+ },
78
+ {
79
+ "package": "rightkit-embed",
80
+ "capability": "embeddings",
81
+ "summary": "Local text embeddings",
82
+ "fingerprints": [
83
+ {
84
+ "id": "embed.cargo.fastembed",
85
+ "type": "dependency",
86
+ "ecosystem": "cargo",
87
+ "name": "fastembed"
88
+ }
89
+ ]
90
+ },
91
+ {
92
+ "package": "rightkit-mcp",
93
+ "capability": "mcp",
94
+ "summary": "MCP client/server transports, trust gate, server pool",
95
+ "fingerprints": [
96
+ {
97
+ "id": "mcp.cargo.rmcp",
98
+ "type": "dependency",
99
+ "ecosystem": "cargo",
100
+ "name": "rmcp"
101
+ },
102
+ {
103
+ "id": "mcp.npm.sdk",
104
+ "type": "dependency",
105
+ "ecosystem": "npm",
106
+ "name": "@modelcontextprotocol/sdk"
107
+ },
108
+ {
109
+ "id": "mcp.protocol.initialized",
110
+ "type": "source",
111
+ "pattern": "[\"']notifications/initialized[\"']"
112
+ }
113
+ ]
114
+ },
115
+ {
116
+ "package": "rightkit-oauth",
117
+ "capability": "oauth-sign-in",
118
+ "summary": "OAuth sign-in: loopback PKCE, device flow, provider endpoints, refresh",
119
+ "fingerprints": [
120
+ {
121
+ "id": "oauth.cargo.oauth2",
122
+ "type": "dependency",
123
+ "ecosystem": "cargo",
124
+ "name": "oauth2"
125
+ },
126
+ {
127
+ "id": "oauth.cargo.openidconnect",
128
+ "type": "dependency",
129
+ "ecosystem": "cargo",
130
+ "name": "openidconnect"
131
+ },
132
+ {
133
+ "id": "oauth.cargo.yup-oauth2",
134
+ "type": "dependency",
135
+ "ecosystem": "cargo",
136
+ "name": "yup-oauth2"
137
+ },
138
+ {
139
+ "id": "oauth.npm.simple-oauth2",
140
+ "type": "dependency",
141
+ "ecosystem": "npm",
142
+ "name": "simple-oauth2"
143
+ },
144
+ {
145
+ "id": "oauth.npm.oauth4webapi",
146
+ "type": "dependency",
147
+ "ecosystem": "npm",
148
+ "name": "oauth4webapi"
149
+ },
150
+ {
151
+ "id": "oauth.endpoint.github",
152
+ "type": "source",
153
+ "pattern": "github\\.com/login/(oauth/(authorize|access_token)|device/code)"
154
+ },
155
+ {
156
+ "id": "oauth.endpoint.google",
157
+ "type": "source",
158
+ "pattern": "accounts\\.google\\.com/o/oauth2|oauth2\\.googleapis\\.com/(token|device/code)"
159
+ },
160
+ {
161
+ "id": "oauth.endpoint.openai",
162
+ "type": "source",
163
+ "pattern": "auth\\.openai\\.com/oauth/(authorize|token)"
164
+ },
165
+ {
166
+ "id": "oauth.endpoint.apple",
167
+ "type": "source",
168
+ "pattern": "appleid\\.apple\\.com/auth/(authorize|token)"
169
+ },
170
+ {
171
+ "id": "oauth.endpoint.cloudflare",
172
+ "type": "source",
173
+ "pattern": "dash\\.cloudflare\\.com/oauth2/(auth|token)"
174
+ },
175
+ {
176
+ "id": "oauth.device-grant",
177
+ "type": "source",
178
+ "pattern": "urn:ietf:params:oauth:grant-type:device_code"
179
+ }
180
+ ]
181
+ },
182
+ {
183
+ "package": "rightkit-ort",
184
+ "capability": "onnx-runtime",
185
+ "summary": "ONNX Runtime selection, execution providers, dylib resolution and global pools",
186
+ "fingerprints": [
187
+ {
188
+ "id": "ort.cargo.ort",
189
+ "type": "dependency",
190
+ "ecosystem": "cargo",
191
+ "name": "ort"
192
+ },
193
+ {
194
+ "id": "ort.npm.onnxruntime-node",
195
+ "type": "dependency",
196
+ "ecosystem": "npm",
197
+ "name": "onnxruntime-node"
198
+ },
199
+ {
200
+ "id": "ort.env.dylib",
201
+ "type": "source",
202
+ "pattern": "ORT_DYLIB_PATH"
203
+ }
204
+ ]
205
+ },
206
+ {
207
+ "package": "rightkit-process",
208
+ "capability": "owned-processes",
209
+ "summary": "Child processes owned as a tree: process groups / kill-on-close jobs, hidden windows",
210
+ "fingerprints": [
211
+ {
212
+ "id": "process.cargo.process-wrap",
213
+ "type": "dependency",
214
+ "ecosystem": "cargo",
215
+ "name": "process-wrap"
216
+ },
217
+ {
218
+ "id": "process.cargo.command-group",
219
+ "type": "dependency",
220
+ "ecosystem": "cargo",
221
+ "name": "command-group"
222
+ },
223
+ {
224
+ "id": "process.api.kill-on-job-close",
225
+ "type": "source",
226
+ "pattern": "JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE"
227
+ }
228
+ ]
229
+ },
230
+ {
231
+ "package": "rightkit-sandbox",
232
+ "capability": "os-sandbox",
233
+ "summary": "Seatbelt / Landlock+seccomp / AppContainer confinement, PTY leases, resource authority",
234
+ "fingerprints": [
235
+ {
236
+ "id": "sandbox.cargo.landlock",
237
+ "type": "dependency",
238
+ "ecosystem": "cargo",
239
+ "name": "landlock"
240
+ },
241
+ {
242
+ "id": "sandbox.cargo.seccompiler",
243
+ "type": "dependency",
244
+ "ecosystem": "cargo",
245
+ "name": "seccompiler"
246
+ },
247
+ {
248
+ "id": "sandbox.api.appcontainer",
249
+ "type": "source",
250
+ "pattern": "\\b(CreateAppContainerProfile|DeriveAppContainerSidFromAppContainerName|PROC_THREAD_ATTRIBUTE_SECURITY_CAPABILITIES)\\b"
251
+ },
252
+ {
253
+ "id": "sandbox.api.seatbelt",
254
+ "type": "source",
255
+ "pattern": "\\bsandbox_init\\b|sandbox-exec\\b"
256
+ }
257
+ ]
258
+ },
259
+ {
260
+ "package": "rightkit-secrets",
261
+ "capability": "os-credential-store",
262
+ "summary": "Keychain / Credential Manager access with per-app namespaces and test guards",
263
+ "fingerprints": [
264
+ {
265
+ "id": "secrets.cargo.keyring",
266
+ "type": "dependency",
267
+ "ecosystem": "cargo",
268
+ "name": "keyring"
269
+ },
270
+ {
271
+ "id": "secrets.cargo.security-framework",
272
+ "type": "dependency",
273
+ "ecosystem": "cargo",
274
+ "name": "security-framework"
275
+ },
276
+ {
277
+ "id": "secrets.npm.keytar",
278
+ "type": "dependency",
279
+ "ecosystem": "npm",
280
+ "name": "keytar"
281
+ },
282
+ {
283
+ "id": "secrets.api.wincred",
284
+ "type": "source",
285
+ "pattern": "\\bCred(Write|Read|Delete|Enumerate)W?\\b"
286
+ },
287
+ {
288
+ "id": "secrets.api.secitem",
289
+ "type": "source",
290
+ "pattern": "\\bSecItem(Add|CopyMatching|Update|Delete)\\b"
291
+ }
292
+ ]
293
+ },
294
+ {
295
+ "package": "rightkit-shell",
296
+ "capability": "desktop-shell",
297
+ "summary": "Tauri shell: tray, autostart, deep links, single instance, window state, notifications",
298
+ "fingerprints": [
299
+ {
300
+ "id": "shell.cargo.tauri-plugin-autostart",
301
+ "type": "dependency",
302
+ "ecosystem": "cargo",
303
+ "name": "tauri-plugin-autostart"
304
+ },
305
+ {
306
+ "id": "shell.cargo.tauri-plugin-deep-link",
307
+ "type": "dependency",
308
+ "ecosystem": "cargo",
309
+ "name": "tauri-plugin-deep-link"
310
+ },
311
+ {
312
+ "id": "shell.cargo.tauri-plugin-single-instance",
313
+ "type": "dependency",
314
+ "ecosystem": "cargo",
315
+ "name": "tauri-plugin-single-instance"
316
+ },
317
+ {
318
+ "id": "shell.cargo.tauri-plugin-window-state",
319
+ "type": "dependency",
320
+ "ecosystem": "cargo",
321
+ "name": "tauri-plugin-window-state"
322
+ },
323
+ {
324
+ "id": "shell.cargo.tauri-plugin-notification",
325
+ "type": "dependency",
326
+ "ecosystem": "cargo",
327
+ "name": "tauri-plugin-notification"
328
+ },
329
+ {
330
+ "id": "shell.cargo.auto-launch",
331
+ "type": "dependency",
332
+ "ecosystem": "cargo",
333
+ "name": "auto-launch"
334
+ }
335
+ ]
336
+ },
337
+ {
338
+ "package": "rightkit-watch",
339
+ "capability": "file-watching",
340
+ "summary": "Coalesced, ignore-aware file watching with rescan recovery",
341
+ "fingerprints": [
342
+ {
343
+ "id": "watch.cargo.notify",
344
+ "type": "dependency",
345
+ "ecosystem": "cargo",
346
+ "name": "notify"
347
+ },
348
+ {
349
+ "id": "watch.cargo.notify-debouncer-full",
350
+ "type": "dependency",
351
+ "ecosystem": "cargo",
352
+ "name": "notify-debouncer-full"
353
+ },
354
+ {
355
+ "id": "watch.npm.chokidar",
356
+ "type": "dependency",
357
+ "ecosystem": "npm",
358
+ "name": "chokidar"
359
+ }
360
+ ]
361
+ }
362
+ ]
363
+ }
@@ -20,16 +20,16 @@
20
20
  "@rightkit/hooks": "0.1.1",
21
21
  "@rightkit/i18n": "0.1.0",
22
22
  "@rightkit/legal": "0.3.1",
23
- "@rightkit/legal-ui": "0.1.2",
23
+ "@rightkit/legal-ui": "0.1.3",
24
24
  "@rightkit/license": "0.1.8",
25
25
  "@rightkit/logs": "0.1.4",
26
- "@rightkit/media-ui": "0.2.3",
27
- "@rightkit/onboarding": "0.2.0",
26
+ "@rightkit/media-ui": "0.2.5",
27
+ "@rightkit/onboarding": "0.2.1",
28
28
  "@rightkit/pdf-ui": "0.1.0",
29
29
  "@rightkit/platform-ui": "0.1.2",
30
30
  "@rightkit/qa": "0.2.1",
31
- "@rightkit/release": "0.2.115",
32
- "@rightkit/settings-ui": "0.2.0",
31
+ "@rightkit/release": "0.2.117",
32
+ "@rightkit/settings-ui": "0.2.2",
33
33
  "@rightkit/shell": "0.1.0",
34
34
  "@rightkit/tauri": "0.1.1",
35
35
  "@rightkit/theme": "0.2.0",
@@ -69,7 +69,8 @@
69
69
  ],
70
70
  "@rightkit/legal-ui": [
71
71
  "0.1.0",
72
- "0.1.1"
72
+ "0.1.1",
73
+ "0.1.2"
73
74
  ],
74
75
  "@rightkit/hooks": [
75
76
  "0.1.0"
@@ -135,7 +136,9 @@
135
136
  "0.2.111",
136
137
  "0.2.112",
137
138
  "0.2.113",
138
- "0.2.114"
139
+ "0.2.114",
140
+ "0.2.115",
141
+ "0.2.116"
139
142
  ],
140
143
  "@rightkit/qa": [
141
144
  "0.1.0",
@@ -148,16 +151,20 @@
148
151
  "0.1.0",
149
152
  "0.2.0",
150
153
  "0.2.1",
151
- "0.2.2"
154
+ "0.2.2",
155
+ "0.2.3",
156
+ "0.2.4"
152
157
  ],
153
158
  "@rightkit/theme": [
154
159
  "0.1.0"
155
160
  ],
156
161
  "@rightkit/settings-ui": [
157
- "0.1.0"
162
+ "0.1.0",
163
+ "0.2.0"
158
164
  ],
159
165
  "@rightkit/onboarding": [
160
- "0.1.0"
166
+ "0.1.0",
167
+ "0.2.0"
161
168
  ],
162
169
  "@rightkit/doc-render": [
163
170
  "0.1.0"
@@ -167,19 +174,30 @@
167
174
  ]
168
175
  },
169
176
  "cargo": {
177
+ "rightkit-audio": "0.1.0",
178
+ "rightkit-browser": "0.1.0",
179
+ "rightkit-cache": "0.1.0",
180
+ "rightkit-crash": "0.1.0",
181
+ "rightkit-db": "0.1.0",
182
+ "rightkit-framed-sidecar": "0.1.0",
183
+ "rightkit-fs": "0.1.0",
170
184
  "rightkit-license": "0.1.2",
171
185
  "rightkit-logs": "0.1.0",
186
+ "rightkit-media": "0.1.0",
187
+ "rightkit-pdf": "0.1.0",
188
+ "rightkit-playback": "0.1.0",
172
189
  "rightkit-process": "0.1.0",
190
+ "rightkit-search": "0.1.0",
173
191
  "rightkit-tauri": "0.1.0"
174
192
  },
175
193
  "stagedCargo": {
176
194
  "rightkit-automation": "0.1.0",
177
195
  "rightkit-audio": "0.1.0",
178
- "rightkit-browser": "0.1.0",
196
+ "rightkit-browser": "0.1.1",
179
197
  "rightkit-cache": "0.1.0",
180
- "rightkit-control": "0.1.0",
198
+ "rightkit-control": "0.1.1",
181
199
  "rightkit-crash": "0.1.0",
182
- "rightkit-db": "0.1.0",
200
+ "rightkit-db": "0.2.0",
183
201
  "rightkit-framed-sidecar": "0.1.0",
184
202
  "rightkit-fs": "0.1.0",
185
203
  "rightkit-jobs": "0.1.0",
@@ -188,7 +206,7 @@
188
206
  "rightkit-models": "0.1.0",
189
207
  "rightkit-pdf": "0.1.0",
190
208
  "rightkit-playback": "0.1.0",
191
- "rightkit-process": "0.2.0",
209
+ "rightkit-process": "0.2.1",
192
210
  "rightkit-qa": "0.1.0",
193
211
  "rightkit-sandbox": "0.1.0",
194
212
  "rightkit-search": "0.1.0",