@rightkit/release 0.2.132 → 0.2.134

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.
@@ -27,9 +27,9 @@
27
27
  "@rightkit/onboarding": "0.2.3",
28
28
  "@rightkit/pdf-ui": "0.1.2",
29
29
  "@rightkit/platform-ui": "0.1.2",
30
- "@rightkit/qa": "0.3.0",
30
+ "@rightkit/qa": "0.3.1",
31
31
  "@rightkit/qa-chrome": "0.1.3",
32
- "@rightkit/release": "0.2.132",
32
+ "@rightkit/release": "0.2.134",
33
33
  "@rightkit/settings-ui": "0.2.7",
34
34
  "@rightkit/shell": "0.1.0",
35
35
  "@rightkit/tauri": "0.1.1",
@@ -155,7 +155,9 @@
155
155
  "0.2.128",
156
156
  "0.2.129",
157
157
  "0.2.130",
158
- "0.2.131"
158
+ "0.2.131",
159
+ "0.2.132",
160
+ "0.2.133"
159
161
  ],
160
162
  "@rightkit/qa": [
161
163
  "0.1.0",
@@ -163,7 +165,8 @@
163
165
  "0.2.1",
164
166
  "0.2.2",
165
167
  "0.2.3",
166
- "0.2.4"
168
+ "0.2.4",
169
+ "0.3.0"
167
170
  ],
168
171
  "@rightkit/app-shell": [
169
172
  "0.1.0",
@@ -237,7 +240,7 @@
237
240
  "rightkit-automation": "0.1.0",
238
241
  "rightkit-browser": "0.2.2",
239
242
  "rightkit-cache": "0.1.0",
240
- "rightkit-control": "0.1.7",
243
+ "rightkit-control": "0.1.8",
241
244
  "rightkit-crash": "0.1.0",
242
245
  "rightkit-db": "0.3.0",
243
246
  "rightkit-embed": "0.2.2",
@@ -247,28 +250,28 @@
247
250
  "rightkit-jobs": "0.2.2",
248
251
  "rightkit-license": "0.1.3",
249
252
  "rightkit-logs": "0.2.0",
250
- "rightkit-mcp": "0.3.0",
253
+ "rightkit-mcp": "0.3.1",
251
254
  "rightkit-media": "0.2.3",
252
255
  "rightkit-models": "0.2.2",
253
256
  "rightkit-oauth": "0.2.1",
254
257
  "rightkit-ort": "0.2.0",
255
258
  "rightkit-pdf": "0.1.0",
256
259
  "rightkit-playback": "0.1.1",
257
- "rightkit-process": "0.3.0",
258
- "rightkit-qa": "0.2.9",
260
+ "rightkit-process": "0.3.1",
261
+ "rightkit-qa": "0.2.11",
259
262
  "rightkit-qts": "0.2.1",
260
263
  "rightkit-qts-ggml": "0.1.0",
261
264
  "rightkit-qts-ggml-sys": "0.1.0",
262
- "rightkit-sandbox": "0.2.2",
265
+ "rightkit-sandbox": "0.2.4",
263
266
  "rightkit-search": "0.1.0",
264
267
  "rightkit-secrets": "0.1.0",
265
268
  "rightkit-service": "0.1.0",
266
269
  "rightkit-settings": "0.1.0",
267
- "rightkit-shell": "0.1.2",
270
+ "rightkit-shell": "0.1.3",
268
271
  "rightkit-speech-client": "0.3.0",
269
272
  "rightkit-sync": "0.1.0",
270
273
  "rightkit-tauri": "0.1.1",
271
- "rightkit-watch": "0.3.0"
274
+ "rightkit-watch": "0.3.1"
272
275
  },
273
276
  "swift": {
274
277
  "rightkit-swift": {
@@ -287,7 +290,7 @@
287
290
  "rightkit-audio": "0.1.1",
288
291
  "rightkit-browser": "0.2.2",
289
292
  "rightkit-cache": "0.1.0",
290
- "rightkit-control": "0.1.7",
293
+ "rightkit-control": "0.1.8",
291
294
  "rightkit-crash": "0.1.0",
292
295
  "rightkit-db": "0.3.0",
293
296
  "rightkit-embed": "0.2.2",
@@ -297,27 +300,27 @@
297
300
  "rightkit-jobs": "0.2.2",
298
301
  "rightkit-license": "0.1.3",
299
302
  "rightkit-logs": "0.2.0",
300
- "rightkit-mcp": "0.3.0",
303
+ "rightkit-mcp": "0.3.1",
301
304
  "rightkit-media": "0.2.3",
302
305
  "rightkit-models": "0.2.2",
303
306
  "rightkit-oauth": "0.2.1",
304
307
  "rightkit-ort": "0.2.0",
305
308
  "rightkit-pdf": "0.1.0",
306
309
  "rightkit-playback": "0.1.1",
307
- "rightkit-process": "0.3.0",
308
- "rightkit-qa": "0.2.9",
310
+ "rightkit-process": "0.3.1",
311
+ "rightkit-qa": "0.2.11",
309
312
  "rightkit-qts": "0.2.1",
310
313
  "rightkit-qts-ggml": "0.1.0",
311
314
  "rightkit-qts-ggml-sys": "0.1.0",
312
- "rightkit-sandbox": "0.2.2",
315
+ "rightkit-sandbox": "0.2.4",
313
316
  "rightkit-search": "0.1.0",
314
317
  "rightkit-secrets": "0.1.0",
315
318
  "rightkit-service": "0.1.0",
316
319
  "rightkit-settings": "0.1.0",
317
- "rightkit-shell": "0.1.2",
320
+ "rightkit-shell": "0.1.3",
318
321
  "rightkit-speech-client": "0.3.0",
319
322
  "rightkit-tauri": "0.1.1",
320
- "rightkit-watch": "0.3.0"
323
+ "rightkit-watch": "0.3.1"
321
324
  },
322
325
  "npm": {
323
326
  "@rightkit/app-shell": "0.2.1",
@@ -334,9 +337,9 @@
334
337
  "@rightkit/onboarding": "0.2.3",
335
338
  "@rightkit/pdf-ui": "0.1.2",
336
339
  "@rightkit/platform-ui": "0.1.2",
337
- "@rightkit/qa": "0.3.0",
340
+ "@rightkit/qa": "0.3.1",
338
341
  "@rightkit/qa-chrome": "0.1.3",
339
- "@rightkit/release": "0.2.131",
342
+ "@rightkit/release": "0.2.133",
340
343
  "@rightkit/settings-ui": "0.2.7",
341
344
  "@rightkit/shell": "0.1.0",
342
345
  "@rightkit/tauri": "0.1.1",
@@ -366,7 +369,8 @@
366
369
  ],
367
370
  "rightkit-mcp": [
368
371
  "0.1.1",
369
- "0.2.0"
372
+ "0.2.0",
373
+ "0.3.0"
370
374
  ],
371
375
  "rightkit-media": [
372
376
  "0.2.2"
@@ -381,30 +385,38 @@
381
385
  "0.1.0"
382
386
  ],
383
387
  "rightkit-process": [
384
- "0.2.1"
388
+ "0.2.1",
389
+ "0.3.0"
385
390
  ],
386
391
  "rightkit-qa": [
387
392
  "0.2.7",
388
- "0.2.8"
393
+ "0.2.8",
394
+ "0.2.9",
395
+ "0.2.10"
389
396
  ],
390
397
  "rightkit-sandbox": [
391
398
  "0.1.0",
392
399
  "0.2.0",
393
- "0.2.1"
400
+ "0.2.1",
401
+ "0.2.2",
402
+ "0.2.3"
394
403
  ],
395
404
  "rightkit-shell": [
396
405
  "0.1.0",
397
- "0.1.1"
406
+ "0.1.1",
407
+ "0.1.2"
398
408
  ],
399
409
  "rightkit-watch": [
400
410
  "0.1.0",
401
- "0.2.0"
411
+ "0.2.0",
412
+ "0.3.0"
402
413
  ],
403
414
  "rightkit-qts": [
404
415
  "0.2.0"
405
416
  ],
406
417
  "rightkit-control": [
407
- "0.1.6"
418
+ "0.1.6",
419
+ "0.1.7"
408
420
  ]
409
421
  }
410
422
  }
@@ -3,6 +3,7 @@ import { sign as edSign, verify as edVerify } from "node:crypto";
3
3
  export const RUNTIME_ARTIFACT_SCHEMA = 1;
4
4
  export const RUNTIME_ARTIFACT_KINDS = Object.freeze([
5
5
  "asr-model",
6
+ "tts-model",
6
7
  "ocr-model",
7
8
  "ort-runtime",
8
9
  "media-runtime",
@@ -15,6 +16,9 @@ export const RIGHT_SUITE_APP_KEYS = Object.freeze([
15
16
  "mailright",
16
17
  "scraperight",
17
18
  "coderight",
19
+ "screenright",
20
+ "genright",
21
+ "voiceright",
18
22
  ]);
19
23
 
20
24
  const TARGET_OSES = new Set(["windows", "darwin", "linux", "ios"]);
@@ -107,7 +111,7 @@ export function validateRuntimeArtifactManifest(value) {
107
111
  if (value.entitlement.tier !== lane.tier || value.distribution.bucket !== lane.bucket) {
108
112
  fail("distribution lane contradicts entitlement tier or bucket");
109
113
  }
110
- if (["asr-model", "ocr-model", "tokenizer"].includes(value.artifactKind)
114
+ if (["asr-model", "tts-model", "ocr-model", "tokenizer"].includes(value.artifactKind)
111
115
  && (value.distribution.delivery !== "private-r2" || value.entitlement.tier !== "pro")) {
112
116
  fail("model artifacts must use private-r2 with Pro entitlement");
113
117
  }
package/sign-macos.mjs CHANGED
@@ -1,20 +1,26 @@
1
1
  #!/usr/bin/env node
2
2
  import { createHash } from "node:crypto";
3
- import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
3
+ import { existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, readlinkSync, statSync, writeFileSync } from "node:fs";
4
4
  import path from "node:path";
5
5
  import { spawnSync } from "./background-process.mjs";
6
6
  import { MACOS_PORTABLE_SIGNING_CONTRACT } from "./macos-signing-contract.mjs";
7
7
  import { resolveMacosDeveloperIdIdentity } from "./macos-signing-identity.mjs";
8
+ import { runMacCodesignCommands } from "./macos-signing-session.mjs";
8
9
 
9
10
  const args = process.argv.slice(2);
10
11
  const dryRun = args.includes("--dry-run");
11
12
  const receiptIndex = args.indexOf("--receipt");
12
13
  const receiptPath = receiptIndex >= 0 ? args[receiptIndex + 1] : null;
14
+ const entitlementsIndex = args.indexOf("--entitlements");
15
+ const entitlementsPath = entitlementsIndex >= 0 ? args[entitlementsIndex + 1] : null;
16
+ if (entitlementsIndex >= 0 && (!entitlementsPath || entitlementsPath.startsWith("--"))) fail("--entitlements requires a path");
13
17
  const files = args
14
- .filter((arg, index) => arg !== "--dry-run" && arg !== "--receipt" && (receiptIndex < 0 || index !== receiptIndex + 1))
18
+ .filter((arg, index) => arg !== "--dry-run" && arg !== "--receipt" && arg !== "--entitlements"
19
+ && (receiptIndex < 0 || index !== receiptIndex + 1)
20
+ && (entitlementsIndex < 0 || index !== entitlementsIndex + 1))
15
21
  .map((arg) => path.resolve(arg));
16
22
 
17
- if (!files.length) fail("usage: node sign-macos.mjs [--dry-run] --receipt <path> <file>...");
23
+ if (!files.length) fail("usage: node sign-macos.mjs [--dry-run] [--entitlements <path>] --receipt <path> <file>...");
18
24
  if (!receiptPath) fail("--receipt requires a path");
19
25
  if (dryRun) {
20
26
  console.log(`dry-run: Developer ID signing ${files.join(", ")}`);
@@ -24,11 +30,16 @@ if (process.platform !== "darwin") fail("macOS signing must run on macOS");
24
30
  let identity;
25
31
  try { identity = resolveMacosDeveloperIdIdentity(); } catch (error) { fail(error.message); }
26
32
  for (const file of files) if (!existsSync(file)) fail(`missing file: ${file}`);
33
+ if (entitlementsPath && !existsSync(entitlementsPath)) fail(`missing entitlements: ${entitlementsPath}`);
27
34
 
28
35
  const evidence = [];
29
36
  for (const file of files) {
30
37
  const before = fileEvidence(file);
31
- run("codesign", ["--force", "--options", "runtime", "--timestamp", "--sign", identity, file]);
38
+ try {
39
+ const [signed] = runMacCodesignCommands([["--force", "--options", "runtime", "--timestamp", "--sign", identity,
40
+ ...(entitlementsPath ? ["--entitlements", path.resolve(entitlementsPath)] : []), file]]);
41
+ if (signed.error || signed.status !== 0) fail(`codesign failed: ${signed.stderr ?? signed.error?.message ?? signed.status}`);
42
+ } catch (error) { fail(`${error.code ? `[${error.code}] ` : ""}${error.message}`); }
32
43
  const verification = run("codesign", ["--verify", "--strict", "--verbose=4", file]);
33
44
  const details = run("codesign", ["-dvv", file]);
34
45
  if (!/valid on disk/i.test(verification) || !/satisfies its designated requirement/i.test(verification)) {
@@ -46,6 +57,23 @@ mkdirSync(path.dirname(absoluteReceipt), { recursive: true });
46
57
  writeFileSync(absoluteReceipt, `${JSON.stringify({ schema: 1, signingContract: MACOS_PORTABLE_SIGNING_CONTRACT, identity, files: evidence }, null, 2)}\n`);
47
58
 
48
59
  function fileEvidence(file) {
60
+ if (statSync(file).isDirectory()) {
61
+ const digest = createHash("sha256");
62
+ let sizeBytes = 0;
63
+ function visit(directory) {
64
+ for (const name of readdirSync(directory).sort()) {
65
+ const entry = path.join(directory, name);
66
+ const info = lstatSync(entry);
67
+ const type = info.isSymbolicLink() ? "link" : info.isDirectory() ? "directory" : "file";
68
+ const bytes = type === "link" ? Buffer.from(readlinkSync(entry)) : type === "file" ? readFileSync(entry) : Buffer.alloc(0);
69
+ digest.update(`${path.relative(file, entry).split(path.sep).join("/")}\0${type}\0${bytes.length}\0`).update(bytes);
70
+ sizeBytes += bytes.length;
71
+ if (type === "directory") visit(entry);
72
+ }
73
+ }
74
+ visit(file);
75
+ return { sha256: digest.digest("hex"), sizeBytes, kind: "bundle-tree-v1" };
76
+ }
49
77
  const bytes = readFileSync(file);
50
78
  return { sha256: createHash("sha256").update(bytes).digest("hex"), sizeBytes: statSync(file).size };
51
79
  }
package/sign-updater.mjs CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
- import { execFileSync, spawnSync } from "./background-process.mjs";
2
+ import { spawnSync } from "./background-process.mjs";
3
3
  import { existsSync, readFileSync, rmSync } from "node:fs";
4
- import os from "node:os";
5
4
  import path from "node:path";
5
+ import { signMacUpdaterFiles } from "./macos-signing-session.mjs";
6
6
 
7
7
  const args = process.argv.slice(2);
8
8
  const files = [];
@@ -20,17 +20,7 @@ if (!dryRun && !env.TAURI_SIGNING_PRIVATE_KEY) {
20
20
  const key = path.join(env.APPDATA || "", "ViewRight", "updater.key");
21
21
  if (!existsSync(key)) fail(`shared updater key not found: ${key}`);
22
22
  env.TAURI_SIGNING_PRIVATE_KEY = readFileSync(key, "utf8");
23
- } else if (process.platform === "darwin") {
24
- try {
25
- env.TAURI_SIGNING_PRIVATE_KEY = execFileSync(
26
- "security",
27
- ["find-generic-password", "-a", os.userInfo().username, "-s", "rightsuite-updater-key", "-w"],
28
- { encoding: "utf8" },
29
- ).trim();
30
- } catch {
31
- fail("shared updater key is missing from Keychain service rightsuite-updater-key");
32
- }
33
- } else {
23
+ } else if (process.platform !== "darwin") {
34
24
  fail("TAURI_SIGNING_PRIVATE_KEY is required on this platform");
35
25
  }
36
26
  }
@@ -41,6 +31,7 @@ for (const file of files) {
41
31
  rmSync(`${file}.sig`, { force: true });
42
32
  console.log(`${dryRun ? "[dry-run] " : ""}pnpm exec tauri signer sign ${file}`);
43
33
  if (dryRun) continue;
34
+ if (process.platform === "darwin") continue;
44
35
  const result = process.platform === "win32"
45
36
  ? spawnSync(
46
37
  process.env.ComSpec || "cmd.exe",
@@ -58,6 +49,10 @@ for (const file of files) {
58
49
  });
59
50
  if (result.status !== 0) process.exit(result.status ?? 1);
60
51
  }
52
+ if (!dryRun && process.platform === "darwin") {
53
+ try { signMacUpdaterFiles(files, { env }); }
54
+ catch (error) { fail(`${error.code ? `[${error.code}] ` : ""}${error.message}`); }
55
+ }
61
56
 
62
57
  function fail(message) {
63
58
  console.error(`sign-updater: ${message}`);
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/env node
2
+ import * as fs from "node:fs";
3
+ import net from "node:net";
4
+ import path from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { spawnSync } from "./background-process.mjs";
7
+ import { signingAgentPaths, privatePath, SigningAgentError, MAX_FRAME } from "./rightkit-signing-agent.mjs";
8
+
9
+ export async function requestSigningAgent(request, { paths = signingAgentPaths(), timeoutMs = 185000,
10
+ io = fs, connect = net.createConnection } = {}) {
11
+ try {
12
+ privatePath(paths.directory, 0o700, { io, kind: "directory" });
13
+ privatePath(paths.plist, 0o600, { io });
14
+ privatePath(paths.socket, 0o600, { io, kind: "socket" });
15
+ privatePath(paths.token, 0o600, { io });
16
+ } catch (error) {
17
+ if (error.code === "ENOENT") throw new SigningAgentError("MAC_SIGNING_AGENT_UNAVAILABLE", "Signing agent installation, socket, or token is missing");
18
+ throw error;
19
+ }
20
+ const token = io.readFileSync(paths.token, "utf8");
21
+ if (!/^[a-f0-9]{64}$/.test(token)) throw new SigningAgentError("MAC_SIGNING_AGENT_AUTH", "Invalid signing agent token");
22
+ const frame = Buffer.from(`${JSON.stringify({ ...request, token })}\n`);
23
+ if (frame.length > MAX_FRAME) throw new SigningAgentError("MAC_SIGNING_AGENT_PROTOCOL", "Signing request is too large");
24
+ return new Promise((resolve, reject) => {
25
+ const socket = connect({ path: paths.socket });
26
+ let input = Buffer.alloc(0), settled = false;
27
+ const finish = (error, report) => {
28
+ if (settled) return;
29
+ settled = true; clearTimeout(timer); socket.destroy();
30
+ if (error) reject(error); else resolve(report);
31
+ };
32
+ // Wall deadline, rather than an activity timeout which a stalled peer can extend.
33
+ const timer = setTimeout(() => finish(new SigningAgentError("MAC_SIGNING_AGENT_TIMEOUT", "Signing agent response timed out")), timeoutMs);
34
+ socket.on("connect", () => socket.write(frame));
35
+ socket.on("error", (error) => finish(new SigningAgentError(["ENOENT", "ECONNREFUSED"].includes(error.code)
36
+ ? "MAC_SIGNING_AGENT_UNAVAILABLE" : "MAC_SIGNING_AGENT_CONNECTION", "Signing agent connection failed")));
37
+ socket.on("end", () => { if (!settled) finish(new SigningAgentError("MAC_SIGNING_AGENT_PROTOCOL", "Signing agent returned no complete response")); });
38
+ socket.on("data", (chunk) => {
39
+ input = Buffer.concat([input, chunk]);
40
+ if (input.length > MAX_FRAME) { finish(new SigningAgentError("MAC_SIGNING_AGENT_PROTOCOL", "Signing agent response is too large")); return; }
41
+ const end = input.indexOf(10);
42
+ if (end < 0) return;
43
+ try {
44
+ const report = JSON.parse(input.subarray(0, end).toString("utf8"));
45
+ if (end !== input.length - 1 || report.schema !== 1 || typeof report.ok !== "boolean") throw new Error();
46
+ if (!report.ok) {
47
+ const code = typeof report.code === "string" && report.code.startsWith("MAC_SIGNING_") ? report.code : "MAC_SIGNING_AGENT_JOB_FAILED";
48
+ finish(new SigningAgentError(code, request.type === "updater" ? "Updater signing failed inside signing agent" : String(report.message ?? "Signing agent rejected request")));
49
+ return;
50
+ }
51
+ if (request.type === "health") {
52
+ if (report.service !== "dev.rightkit.signing-agent" || !Number.isInteger(report.pid)) throw new Error();
53
+ } else if (!Array.isArray(report.results) || report.results.length !== (request.items?.length ?? request.files?.length)
54
+ || report.results.some((r) => r.status !== 0 || typeof r.stdout !== "string" || typeof r.stderr !== "string")
55
+ || (request.type === "updater" && report.results.some((r) => r.stdout || r.stderr))) throw new Error();
56
+ finish(null, report);
57
+ } catch { finish(new SigningAgentError("MAC_SIGNING_AGENT_PROTOCOL", "Invalid signing agent response")); }
58
+ });
59
+ });
60
+ }
61
+ export function requestSigningAgentSync(request, { runClient = spawnSync, home, timeoutMs = 185000 } = {}) {
62
+ // Existing signing call sites are synchronous. Only typed JSON goes to this
63
+ // child's stdin; credentials & authentication token never enter argv/env.
64
+ const result = runClient(process.execPath, [fileURLToPath(import.meta.url)], { encoding: "utf8", stdio: "pipe", shell: false,
65
+ input: JSON.stringify({ request, home, timeoutMs }), timeout: timeoutMs + 1000, maxBuffer: MAX_FRAME,
66
+ env: { PATH: process.env.PATH, HOME: process.env.HOME } });
67
+ let report;
68
+ try { report = JSON.parse(result.stdout); } catch {
69
+ throw new SigningAgentError(result.error?.code === "ETIMEDOUT" ? "MAC_SIGNING_AGENT_TIMEOUT" : "MAC_SIGNING_AGENT_CONNECTION", "Signing agent client failed");
70
+ }
71
+ if (report.schema !== 1 || typeof report.ok !== "boolean") throw new SigningAgentError("MAC_SIGNING_AGENT_PROTOCOL", "Invalid signing agent client report");
72
+ if (!report.ok) throw new SigningAgentError(report.code ?? "MAC_SIGNING_AGENT_JOB_FAILED", report.message ?? "Signing agent failed");
73
+ if (result.error || result.status !== 0) throw new SigningAgentError("MAC_SIGNING_AGENT_CONNECTION", "Signing agent client failed");
74
+ return report;
75
+ }
76
+ if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
77
+ try {
78
+ const { request, home, timeoutMs } = JSON.parse(fs.readFileSync(0, "utf8"));
79
+ const report = await requestSigningAgent(request, { paths: signingAgentPaths(home), timeoutMs });
80
+ process.stdout.write(JSON.stringify(report));
81
+ } catch (error) {
82
+ process.stdout.write(JSON.stringify({ schema: 1, ok: false, code: error.code ?? "MAC_SIGNING_AGENT_PROTOCOL", message: error.message }));
83
+ process.exitCode = 1;
84
+ }
85
+ }
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env node
2
+ import { installSigningAgent, uninstallSigningAgent, signingAgentStatus } from "./signing-agent-install.mjs";
3
+
4
+ const [command, ...args] = process.argv.slice(2);
5
+ try {
6
+ let result;
7
+ if (command === "install") {
8
+ const options = {};
9
+ if (args.length) {
10
+ if (args.length !== 2 || args[0] !== "--managed-root" || !args[1]) throw new TypeError("Expected install [--managed-root <absolute-directory>]");
11
+ options.managedRoot = args[1];
12
+ }
13
+ result = installSigningAgent(options);
14
+ } else if (command === "uninstall" && !args.length) result = uninstallSigningAgent();
15
+ else if (command === "status" && !args.length) result = signingAgentStatus();
16
+ else throw new TypeError("Expected signing-agent install|uninstall|status");
17
+ console.log(JSON.stringify(result));
18
+ } catch (error) {
19
+ console.error(`right-release signing-agent: ${error.code ? `[${error.code}] ` : ""}${error.message}`);
20
+ process.exitCode = 1;
21
+ }
@@ -0,0 +1,100 @@
1
+ import * as fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { spawnSync } from "./background-process.mjs";
5
+ import { AGENT_LABEL, signingAgentPaths, defaultSigningRoots, ensurePrivateDirectory, privatePath, SigningAgentError } from "./rightkit-signing-agent.mjs";
6
+ import { requestSigningAgentSync } from "./signing-agent-client.mjs";
7
+
8
+ const capture = { encoding: "utf8", stdio: "pipe", shell: false, timeout: 10000 };
9
+ export function requireMacGuiSession({ run = spawnSync, uid = process.getuid?.() } = {}) {
10
+ const session = run("/bin/launchctl", ["print", `gui/${uid}`], capture);
11
+ if (session.error || session.status !== 0) throw new SigningAgentError("MAC_SIGNING_NO_GUI_SESSION", `No logged-in Aqua session for uid ${uid}`);
12
+ }
13
+ const xml = (value) => String(value).replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&apos;");
14
+ export function signingAgentPlist({ paths, node = process.execPath, home }) {
15
+ return `<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n<plist version="1.0"><dict>
16
+ <key>Label</key><string>${AGENT_LABEL}</string>
17
+ <key>ProgramArguments</key><array><string>${xml(node)}</string><string>${xml(path.join(paths.runtime, "rightkit-signing-agent.mjs"))}</string></array>
18
+ <key>LimitLoadToSessionType</key><string>Aqua</string>
19
+ <key>RunAtLoad</key><true/><key>KeepAlive</key><true/>
20
+ <key>ProcessType</key><string>Background</string>
21
+ <key>ThrottleInterval</key><integer>10</integer>
22
+ <key>Umask</key><integer>63</integer>
23
+ <key>EnvironmentVariables</key><dict><key>HOME</key><string>${xml(home)}</string></dict>
24
+ <key>StandardOutPath</key><string>/dev/null</string>
25
+ <key>StandardErrorPath</key><string>/dev/null</string>
26
+ </dict></plist>\n`;
27
+ }
28
+ export function installSigningAgent({ home = os.userInfo().homedir, username = os.userInfo().username,
29
+ uid = process.getuid?.(), platform = process.platform, run = spawnSync, io = fs, node = process.execPath,
30
+ managedRoot = "/Volumes/D/.rightkit-managed", health = requestSigningAgentSync,
31
+ now = Date.now, sleep = (ms) => Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms) } = {}) {
32
+ if (platform !== "darwin") throw new SigningAgentError("MAC_SIGNING_AGENT_PLATFORM", "Signing agent requires macOS");
33
+ requireMacGuiSession({ run, uid });
34
+ const paths = signingAgentPaths(home);
35
+ ensurePrivateDirectory(paths.directory, { io, uid });
36
+ ensurePrivateDirectory(paths.runtime, { io, uid });
37
+ ensurePrivateDirectory(path.join(paths.directory, "requests"), { io, uid });
38
+ ensurePrivateDirectory(path.join(paths.directory, "tmp"), { io, uid });
39
+ if (!path.isAbsolute(managedRoot) || ["/", home, "/Volumes", "/Volumes/D"].includes(path.resolve(managedRoot))) throw new SigningAgentError("MAC_SIGNING_AGENT_PATH_DENIED", "Managed signing root must be a dedicated absolute directory");
40
+ const found = run("/usr/bin/which", ["pnpm"], capture);
41
+ const pnpm = String(found.stdout ?? "").trim();
42
+ if (found.error || found.status !== 0 || !path.isAbsolute(pnpm) || pnpm.includes("\n")) throw new SigningAgentError("MAC_SIGNING_AGENT_INSTALL_FAILED", "Provisioned pnpm executable is unavailable");
43
+ // Resident runtime is self-contained; no package/checkout imports after install.
44
+ for (const name of ["rightkit-signing-agent.mjs", "background-process.mjs"]) {
45
+ const target = path.join(paths.runtime, name);
46
+ if (io.existsSync(target)) privatePath(target, 0o600, { io, uid });
47
+ io.writeFileSync(target, io.readFileSync(new URL(`./${name}`, import.meta.url)), { mode: 0o600 });
48
+ }
49
+ if (io.existsSync(paths.config)) privatePath(paths.config, 0o600, { io, uid });
50
+ io.writeFileSync(paths.config, JSON.stringify({ schema: 1, home, username, pnpm,
51
+ roots: defaultSigningRoots(home, path.resolve(managedRoot)),
52
+ environmentPath: [...new Set([path.dirname(node), path.dirname(pnpm), "/opt/homebrew/bin", "/usr/local/bin", "/usr/bin", "/bin", "/usr/sbin", "/sbin"])].join(":") }), { mode: 0o600 });
53
+ ensurePrivateDirectory(path.dirname(paths.plist), { io, uid });
54
+ if (io.existsSync(paths.plist)) privatePath(paths.plist, 0o600, { io, uid });
55
+ io.writeFileSync(paths.plist, signingAgentPlist({ paths, node, home }), { mode: 0o600 });
56
+ const loaded = run("/bin/launchctl", ["print", `gui/${uid}/${AGENT_LABEL}`], capture);
57
+ if (!loaded.error && loaded.status === 0) {
58
+ const stopped = run("/bin/launchctl", ["bootout", `gui/${uid}/${AGENT_LABEL}`], capture);
59
+ if (stopped.error || stopped.status !== 0) throw new SigningAgentError("MAC_SIGNING_AGENT_INSTALL_FAILED", "Could not stop existing signing agent");
60
+ }
61
+ const boot = run("/bin/launchctl", ["bootstrap", `gui/${uid}`, paths.plist], capture);
62
+ if (boot.error || boot.status !== 0) {
63
+ requireMacGuiSession({ run, uid });
64
+ throw new SigningAgentError("MAC_SIGNING_AGENT_INSTALL_FAILED", `Signing agent bootstrap failed (exit ${boot.status ?? "unavailable"})`);
65
+ }
66
+ const deadline = now() + 10000;
67
+ do {
68
+ try {
69
+ const report = health({ schema: 1, type: "health" }, { home, timeoutMs: 1000 });
70
+ return { schema: 1, installed: true, running: true, pid: report.pid, socket: paths.socket, plist: paths.plist };
71
+ } catch (error) {
72
+ if (!["MAC_SIGNING_AGENT_UNAVAILABLE", "MAC_SIGNING_AGENT_CONNECTION", "MAC_SIGNING_AGENT_TIMEOUT"].includes(error.code)) throw error;
73
+ sleep(100);
74
+ }
75
+ } while (now() < deadline);
76
+ throw new SigningAgentError("MAC_SIGNING_AGENT_INSTALL_FAILED", "Signing agent bootstrapped but health did not become ready");
77
+ }
78
+ export function signingAgentStatus({ home = os.userInfo().homedir, uid = process.getuid?.(), run = spawnSync,
79
+ io = fs, health = requestSigningAgentSync } = {}) {
80
+ const paths = signingAgentPaths(home);
81
+ const installed = io.existsSync(paths.plist);
82
+ let guiSession = false;
83
+ try { requireMacGuiSession({ run, uid }); guiSession = true; } catch {}
84
+ try {
85
+ const report = health({ schema: 1, type: "health" }, { home, timeoutMs: 1500 });
86
+ return { schema: 1, installed, guiSession, running: true, pid: report.pid, socket: paths.socket };
87
+ } catch (error) { return { schema: 1, installed, guiSession, running: false, code: error.code, socket: paths.socket }; }
88
+ }
89
+ export function uninstallSigningAgent({ home = os.userInfo().homedir, uid = process.getuid?.(), platform = process.platform, run = spawnSync, io = fs } = {}) {
90
+ if (platform !== "darwin") throw new SigningAgentError("MAC_SIGNING_AGENT_PLATFORM", "Signing agent requires macOS");
91
+ const paths = signingAgentPaths(home);
92
+ const loaded = run("/bin/launchctl", ["print", `gui/${uid}/${AGENT_LABEL}`], capture);
93
+ if (!loaded.error && loaded.status === 0) {
94
+ const result = run("/bin/launchctl", ["bootout", `gui/${uid}/${AGENT_LABEL}`], capture);
95
+ if (result.error || result.status !== 0) throw new SigningAgentError("MAC_SIGNING_AGENT_UNINSTALL_FAILED", "Could not stop signing agent");
96
+ }
97
+ if (io.existsSync(paths.plist)) { privatePath(paths.plist, 0o600, { io, uid }); io.unlinkSync(paths.plist); }
98
+ if (io.existsSync(paths.directory)) { privatePath(paths.directory, 0o700, { io, uid, kind: "directory" }); io.rmSync(paths.directory, { recursive: true, force: true }); }
99
+ return { schema: 1, installed: false, running: false };
100
+ }
@@ -0,0 +1,10 @@
1
+ import path from "node:path";
2
+
3
+ // One resolver for the worker that writes receipts & the build that seals them.
4
+ export function signingReceiptPath({ appRoot, target, platform, phase, stateRoot }) {
5
+ const configured = platform === "mac"
6
+ ? phase === "signing" ? target.sign?.receipt : target.notarize?.receipt
7
+ : phase === "raw-exe" ? target.sign?.receipt : phase === "installer" ? target.sign?.installerReceipt : undefined;
8
+ if (configured) return path.resolve(appRoot, configured);
9
+ return path.resolve(stateRoot ?? path.join(appRoot, ".right-release", "receipts"), `${platform === "mac" ? "macos" : "windows"}-${phase}.json`);
10
+ }
@@ -8,6 +8,7 @@ import { assertPrimaryReleaseCheckout } from "./release-invocation.mjs";
8
8
  import { verifySealedRelease } from "./release-state.mjs";
9
9
  import { assertCleanSource } from "./source-gate.mjs";
10
10
  import { verifyOwnershipEvidence } from "./ownership-evidence.mjs";
11
+ import { recordPublishedVersion } from "./artifact-storage.mjs";
11
12
 
12
13
  const args = process.argv.slice(2);
13
14
  let platform = process.platform === "win32" ? "win" : process.platform === "darwin" ? "mac" : process.platform;
@@ -75,6 +76,9 @@ if (!dryRun) writeJson(verifiedMarker, {
75
76
  tag: result.tag,
76
77
  verifiedAt: new Date().toISOString(),
77
78
  });
79
+ // This runs only after GitHub has returned successful remote byte verification.
80
+ // Candidate builds never enter published.json.
81
+ if (!dryRun) recordPublishedVersion({ app: sealed.manifest.app, version: sealed.manifest.version, platform });
78
82
  console.log(`right-release upload: ${dryRun ? "dry-run " : ""}${result.status} ${releaseId} tier=${tier} repo=${repo}`);
79
83
 
80
84
  function git(cwd, runArgs) {