@rightkit/release 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +59 -7
  2. package/adhoc-entitlements.mjs +8 -0
  3. package/adoption-evidence.mjs +650 -0
  4. package/advisory-exceptions.mjs +371 -0
  5. package/apple-profile-create.mjs +113 -10
  6. package/bin/darwin/rightkit-swap +0 -0
  7. package/bin/rightkit-gate.mjs +1079 -0
  8. package/binary-license-notices.mjs +161 -0
  9. package/cargo-license-notices.mjs +111 -0
  10. package/cli/right-release.mjs +39 -0
  11. package/deterministic-scans.mjs +966 -0
  12. package/development-signing.mjs +2 -1
  13. package/host-upload-refusal.mjs +90 -0
  14. package/local-publish.mjs +142 -19
  15. package/local-qualify.mjs +728 -0
  16. package/local-runtime.mjs +319 -0
  17. package/lock-graph.mjs +51 -0
  18. package/managed-cargo.mjs +4 -1
  19. package/ownership-approval.mjs +23 -6
  20. package/ownership-dependency-allowlist.json +2 -0
  21. package/ownership-mandatory-set.json +158 -68
  22. package/ownership-mandatory-set.mjs +82 -6
  23. package/package.json +6 -2
  24. package/pipeline-admission.mjs +375 -0
  25. package/pipeline-config.mjs +100 -0
  26. package/pipeline-generators.json +4 -0
  27. package/pipeline-generators.mjs +137 -0
  28. package/pipeline-receipts.mjs +302 -0
  29. package/pipeline-scan.mjs +285 -0
  30. package/pipeline-tools.mjs +171 -0
  31. package/production-mode.mjs +42 -0
  32. package/publication-gate.mjs +94 -32
  33. package/publish-cargo.mjs +24 -7
  34. package/publish-swift.mjs +12 -3
  35. package/qa-control-scan.mjs +4 -0
  36. package/release-config.mjs +330 -0
  37. package/release-switch-scan.mjs +44 -5
  38. package/release.mjs +14 -4
  39. package/rightkit-ownership.json +57 -0
  40. package/rightkit-sync.mjs +8 -0
  41. package/rightkit-versions.json +191 -131
  42. package/runtime-budget-classes.json +8 -0
  43. package/runtime-budget-crates.mjs +14 -0
  44. package/runtime-budgets.mjs +128 -31
  45. package/scan-digest.mjs +107 -0
  46. package/sign-qa-bundle.mjs +210 -0
  47. package/stage-sidecar.mjs +99 -15
  48. package/swap-helper-bundle.mjs +7 -3
  49. package/workflow-yaml.mjs +177 -0
  50. package/bin/darwin/rightkit-swap.sign.json +0 -6
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "schema": 1,
3
3
  "source": {
4
- "table": "private-audits/TABLE1-MANDATORY.md:56-78 (M1-M23 requirement catalog)",
5
- "versions": "private-audits/inventory/REQUIRED-VERSIONS.md (source versions at rightkit HEAD a15e62fd, registry readback 2026-10-13)",
6
- "note": "min = required version (source version at P1.7 HEAD). registryMax = highest registry version at readback; where registryMax < min the required version is not yet published, so every consumer fails until it ships."
4
+ "table": "private-audits/TABLE1-MANDATORY.md:56-78 (M1-M23 requirement catalog; M24-M25 added 2026-10-21, pending)",
5
+ "versions": "registry readback 2026-10-19 after the mandatory-set publish (#790): min = registryMax = the live version",
6
+ "note": "min = registryMax = the required version: the live registry version of the published mandatory set (#790). Raise both with each mandatory-set publish."
7
7
  },
8
8
  "defaultProfile": "desktop",
9
9
  "defaultApps": [
@@ -37,18 +37,20 @@
37
37
  {
38
38
  "name": "rightkit-tauri-shell",
39
39
  "ecosystem": "cargo",
40
- "min": "0.3.0"
40
+ "min": "0.4.0",
41
+ "registryMax": "0.4.0"
41
42
  },
42
43
  {
43
44
  "name": "rightkit-device",
44
45
  "ecosystem": "cargo",
45
- "min": "0.1.3"
46
+ "min": "0.3.0",
47
+ "registryMax": "0.3.0"
46
48
  },
47
49
  {
48
50
  "name": "@rightkit/tauri",
49
51
  "ecosystem": "npm",
50
- "min": "0.2.0",
51
- "registryMax": "0.2.0"
52
+ "min": "0.2.3",
53
+ "registryMax": "0.2.3"
52
54
  }
53
55
  ]
54
56
  },
@@ -60,19 +62,20 @@
60
62
  {
61
63
  "name": "@rightkit/react-shell",
62
64
  "ecosystem": "npm",
63
- "min": "0.6.4"
65
+ "min": "0.7.1",
66
+ "registryMax": "0.7.1"
64
67
  },
65
68
  {
66
69
  "name": "@rightkit/platform-ui",
67
70
  "ecosystem": "npm",
68
- "min": "0.2.8",
69
- "registryMax": "0.2.8"
71
+ "min": "0.4.0",
72
+ "registryMax": "0.4.0"
70
73
  },
71
74
  {
72
75
  "name": "@rightkit/theme",
73
76
  "ecosystem": "npm",
74
- "min": "0.2.3",
75
- "registryMax": "0.2.3"
77
+ "min": "0.3.1",
78
+ "registryMax": "0.3.1"
76
79
  }
77
80
  ]
78
81
  },
@@ -84,12 +87,14 @@
84
87
  {
85
88
  "name": "rightkit-job-queue",
86
89
  "ecosystem": "cargo",
87
- "min": "0.3.0"
90
+ "min": "0.4.0",
91
+ "registryMax": "0.4.0"
88
92
  },
89
93
  {
90
94
  "name": "rightkit-child-process",
91
95
  "ecosystem": "cargo",
92
- "min": "0.4.0"
96
+ "min": "0.5.2",
97
+ "registryMax": "0.5.2"
93
98
  }
94
99
  ]
95
100
  },
@@ -101,12 +106,14 @@
101
106
  {
102
107
  "name": "rightkit-job-queue",
103
108
  "ecosystem": "cargo",
104
- "min": "0.3.0"
109
+ "min": "0.4.0",
110
+ "registryMax": "0.4.0"
105
111
  },
106
112
  {
107
113
  "name": "rightkit-sidecar",
108
114
  "ecosystem": "cargo",
109
- "min": "0.2.0"
115
+ "min": "0.3.0",
116
+ "registryMax": "0.3.0"
110
117
  }
111
118
  ]
112
119
  },
@@ -118,7 +125,8 @@
118
125
  {
119
126
  "name": "rightkit-child-process",
120
127
  "ecosystem": "cargo",
121
- "min": "0.4.0"
128
+ "min": "0.5.2",
129
+ "registryMax": "0.5.2"
122
130
  }
123
131
  ]
124
132
  },
@@ -130,7 +138,8 @@
130
138
  {
131
139
  "name": "rightkit-daemon",
132
140
  "ecosystem": "cargo",
133
- "min": "0.2.0"
141
+ "min": "0.3.0",
142
+ "registryMax": "0.3.0"
134
143
  }
135
144
  ]
136
145
  },
@@ -142,7 +151,8 @@
142
151
  {
143
152
  "name": "rightkit-daemon",
144
153
  "ecosystem": "cargo",
145
- "min": "0.2.0"
154
+ "min": "0.3.0",
155
+ "registryMax": "0.3.0"
146
156
  }
147
157
  ]
148
158
  },
@@ -154,8 +164,8 @@
154
164
  {
155
165
  "name": "rightkit-fs",
156
166
  "ecosystem": "cargo",
157
- "min": "0.4.0",
158
- "registryMax": "0.4.1"
167
+ "min": "0.4.4",
168
+ "registryMax": "0.4.4"
159
169
  }
160
170
  ]
161
171
  },
@@ -167,8 +177,8 @@
167
177
  {
168
178
  "name": "rightkit-db",
169
179
  "ecosystem": "cargo",
170
- "min": "0.4.0",
171
- "registryMax": "0.4.0"
180
+ "min": "0.5.0",
181
+ "registryMax": "0.5.0"
172
182
  }
173
183
  ]
174
184
  },
@@ -180,13 +190,14 @@
180
190
  {
181
191
  "name": "rightkit-app-settings",
182
192
  "ecosystem": "cargo",
183
- "min": "0.2.0"
193
+ "min": "0.2.4",
194
+ "registryMax": "0.2.4"
184
195
  },
185
196
  {
186
197
  "name": "@rightkit/settings-ui",
187
198
  "ecosystem": "npm",
188
- "min": "0.4.4",
189
- "registryMax": "0.4.4"
199
+ "min": "0.7.0",
200
+ "registryMax": "0.7.0"
190
201
  }
191
202
  ]
192
203
  },
@@ -198,7 +209,8 @@
198
209
  {
199
210
  "name": "rightkit-credentials",
200
211
  "ecosystem": "cargo",
201
- "min": "0.2.0"
212
+ "min": "0.4.2",
213
+ "registryMax": "0.4.2"
202
214
  }
203
215
  ]
204
216
  },
@@ -210,19 +222,20 @@
210
222
  {
211
223
  "name": "rightkit-license",
212
224
  "ecosystem": "cargo",
213
- "min": "0.2.0",
214
- "registryMax": "0.2.0"
225
+ "min": "0.2.3",
226
+ "registryMax": "0.2.3"
215
227
  },
216
228
  {
217
229
  "name": "rightkit-device",
218
230
  "ecosystem": "cargo",
219
- "min": "0.1.3"
231
+ "min": "0.3.0",
232
+ "registryMax": "0.3.0"
220
233
  },
221
234
  {
222
235
  "name": "@rightkit/license",
223
236
  "ecosystem": "npm",
224
- "min": "0.2.0",
225
- "registryMax": "0.2.0"
237
+ "min": "0.2.1",
238
+ "registryMax": "0.2.1"
226
239
  }
227
240
  ]
228
241
  },
@@ -234,8 +247,8 @@
234
247
  {
235
248
  "name": "rightkit-license",
236
249
  "ecosystem": "cargo",
237
- "min": "0.2.0",
238
- "registryMax": "0.2.0"
250
+ "min": "0.2.3",
251
+ "registryMax": "0.2.3"
239
252
  }
240
253
  ]
241
254
  },
@@ -247,20 +260,20 @@
247
260
  {
248
261
  "name": "@rightkit/legal",
249
262
  "ecosystem": "npm",
250
- "min": "0.3.1",
251
- "registryMax": "0.3.1"
263
+ "min": "0.6.1",
264
+ "registryMax": "0.6.1"
252
265
  },
253
266
  {
254
267
  "name": "@rightkit/legal-ui",
255
268
  "ecosystem": "npm",
256
- "min": "0.2.2",
257
- "registryMax": "0.2.2"
269
+ "min": "0.4.0",
270
+ "registryMax": "0.4.0"
258
271
  },
259
272
  {
260
273
  "name": "@rightkit/onboarding",
261
274
  "ecosystem": "npm",
262
- "min": "0.3.3",
263
- "registryMax": "0.3.3"
275
+ "min": "0.6.1",
276
+ "registryMax": "0.6.1"
264
277
  }
265
278
  ]
266
279
  },
@@ -272,14 +285,14 @@
272
285
  {
273
286
  "name": "rightkit-logs",
274
287
  "ecosystem": "cargo",
275
- "min": "0.3.0",
276
- "registryMax": "0.3.0"
288
+ "min": "0.3.3",
289
+ "registryMax": "0.3.3"
277
290
  },
278
291
  {
279
292
  "name": "@rightkit/logs",
280
293
  "ecosystem": "npm",
281
- "min": "0.1.4",
282
- "registryMax": "0.1.4"
294
+ "min": "0.1.6",
295
+ "registryMax": "0.1.6"
283
296
  }
284
297
  ]
285
298
  },
@@ -291,8 +304,8 @@
291
304
  {
292
305
  "name": "rightkit-crash",
293
306
  "ecosystem": "cargo",
294
- "min": "0.2.2",
295
- "registryMax": "0.3.0"
307
+ "min": "0.3.3",
308
+ "registryMax": "0.3.3"
296
309
  }
297
310
  ]
298
311
  },
@@ -304,8 +317,8 @@
304
317
  {
305
318
  "name": "rightkit-http",
306
319
  "ecosystem": "cargo",
307
- "min": "0.3.0",
308
- "registryMax": "0.3.0"
320
+ "min": "0.3.6",
321
+ "registryMax": "0.3.6"
309
322
  }
310
323
  ]
311
324
  },
@@ -317,7 +330,8 @@
317
330
  {
318
331
  "name": "@rightkit/app-update",
319
332
  "ecosystem": "npm",
320
- "min": "0.2.5"
333
+ "min": "0.4.1",
334
+ "registryMax": "0.4.1"
321
335
  }
322
336
  ]
323
337
  },
@@ -329,8 +343,8 @@
329
343
  {
330
344
  "name": "@rightkit/release",
331
345
  "ecosystem": "npm",
332
- "min": "0.2.153",
333
- "registryMax": "0.2.165",
346
+ "min": "0.4.1",
347
+ "registryMax": "0.4.1",
334
348
  "allowDev": true
335
349
  }
336
350
  ]
@@ -343,29 +357,22 @@
343
357
  {
344
358
  "name": "rightkit-qa",
345
359
  "ecosystem": "cargo",
346
- "min": "0.2.16",
347
- "registryMax": "0.6.1",
348
- "allowDev": true
349
- },
350
- {
351
- "name": "rightkit-qa-driver",
352
- "ecosystem": "cargo",
353
- "min": "0.3.0",
354
- "note": "deprecated shim; the QA owner is rightkit-qa with the `control` feature (Ember #280)",
360
+ "min": "0.7.0",
361
+ "registryMax": "0.7.0",
355
362
  "allowDev": true
356
363
  },
357
364
  {
358
365
  "name": "@rightkit/qa",
359
366
  "ecosystem": "npm",
360
- "min": "0.4.8",
361
- "registryMax": "0.4.8",
367
+ "min": "0.5.1",
368
+ "registryMax": "0.5.1",
362
369
  "allowDev": true
363
370
  },
364
371
  {
365
372
  "name": "@rightkit/qa-chrome",
366
373
  "ecosystem": "npm",
367
- "min": "0.1.6",
368
- "registryMax": "0.1.6",
374
+ "min": "0.1.8",
375
+ "registryMax": "0.1.8",
369
376
  "allowDev": true
370
377
  }
371
378
  ]
@@ -378,8 +385,8 @@
378
385
  {
379
386
  "name": "rightkit-agent-tools",
380
387
  "ecosystem": "cargo",
381
- "min": "0.1.2",
382
- "registryMax": "0.1.4"
388
+ "min": "0.2.0",
389
+ "registryMax": "0.2.0"
383
390
  }
384
391
  ]
385
392
  }
@@ -390,16 +397,99 @@
390
397
  "name": "Test mode",
391
398
  "profile": "desktop",
392
399
  "owners": [],
393
- "ownerPending": "No selected owner (D4 unratified); never fails."
400
+ "ownerPending": "No selected owner (D4 unratified); never fails.",
401
+ "pendingOwners": [
402
+ {
403
+ "name": "rightkit-fs",
404
+ "ecosystem": "cargo",
405
+ "min": "0.4.4"
406
+ },
407
+ {
408
+ "name": "rightkit-daemon",
409
+ "ecosystem": "cargo",
410
+ "min": "0.3.0"
411
+ },
412
+ {
413
+ "name": "rightkit-qa",
414
+ "ecosystem": "cargo",
415
+ "min": "0.7.0",
416
+ "allowDev": true
417
+ }
418
+ ]
394
419
  },
395
420
  {
396
421
  "id": "M23",
397
422
  "name": "Release-time switches",
398
423
  "profile": "desktop",
399
424
  "owners": [],
400
- "ownerPending": "No selected owner (D4 unratified); never fails."
425
+ "ownerPending": "No selected owner (D4 unratified); never fails.",
426
+ "pendingOwners": [
427
+ {
428
+ "name": "@rightkit/release",
429
+ "ecosystem": "npm",
430
+ "min": "0.4.1",
431
+ "allowDev": true,
432
+ "registryMax": "0.4.1"
433
+ },
434
+ {
435
+ "name": "rightkit-daemon",
436
+ "ecosystem": "cargo",
437
+ "min": "0.3.0"
438
+ }
439
+ ]
440
+ },
441
+ {
442
+ "id": "M24",
443
+ "name": "Issue tracker",
444
+ "profile": "all",
445
+ "owners": [],
446
+ "ownerPending": "@rightkit/issues-tracker 0.1.0 is not yet published (rightkit#875); never fails.",
447
+ "pendingOwners": [
448
+ {
449
+ "name": "@rightkit/issues-tracker",
450
+ "ecosystem": "npm",
451
+ "min": "0.1.0",
452
+ "allowDev": true
453
+ }
454
+ ]
455
+ },
456
+ {
457
+ "id": "M25",
458
+ "name": "End-to-end journeys",
459
+ "profile": [
460
+ "desktop",
461
+ "apple"
462
+ ],
463
+ "owners": [],
464
+ "ownerPending": "Journeys capability (E2E-TESTING-STANDARD, rightkit#878) is not built or published in rightkit-qa / @rightkit/qa; never fails.",
465
+ "pendingOwners": [
466
+ {
467
+ "name": "rightkit-qa",
468
+ "ecosystem": "cargo",
469
+ "min": "0.7.0",
470
+ "allowDev": true
471
+ },
472
+ {
473
+ "name": "@rightkit/qa",
474
+ "ecosystem": "npm",
475
+ "min": "0.5.1",
476
+ "allowDev": true
477
+ }
478
+ ]
401
479
  }
402
480
  ],
481
+ "applicabilityRecords": {
482
+ "M22": {
483
+ "name": "Agent surface",
484
+ "roles": [
485
+ "client",
486
+ "host",
487
+ "both",
488
+ "absent"
489
+ ],
490
+ "note": "Declared in <app>/rightkit-mandatory.json as M22: { role, entrypoints }. Report-only: a record never removes or adds a violation; only notApplicable (with a reason) removes a row."
491
+ }
492
+ },
403
493
  "appAmendments": {
404
494
  "ember": {
405
495
  "issue": "Orthic-Labs/rightkit#280",
@@ -2,7 +2,7 @@
2
2
  // right-release ownership-mandatory: Table 1 mandatory-owner-set check (plan task 5.1).
3
3
  //
4
4
  // This evaluates declarations only; it does not establish active target/features, lock resolution or production adoption.
5
- // A registered app must declare every Table 1 owner (rows M1-M23) at or above the required version,
5
+ // A registered app must declare every Table 1 owner (enforced rows M1-M20, M22; pending rows M21, M23-M25 never fail) at or above the required version,
6
6
  // from the registry. Report mode (default) prints per-app findings and exits 0; --enforce exits 1
7
7
  // on any missing owner, below-required version, or path/git/patch override ("not-on-registry").
8
8
  // Mode switching of the ownership gate itself (Report -> Enforce) is a separate step; this check
@@ -12,7 +12,15 @@
12
12
  // [--amendment v2.1] [--enforce] [--json]
13
13
  //
14
14
  // App-side declaration (optional): <app>/rightkit-mandatory.json
15
- // { "profile": "desktop", "notApplicable": { "M16": "reason text" } }
15
+ // { "profile": "desktop", "notApplicable": { "M16": "reason text" },
16
+ // "M22": { "role": "client|host|both|absent", "entrypoints": ["src-tauri/src/agent.rs#register_tools"] } }
17
+ //
18
+ // PK17.scanner (report only, never enforcement): rows M21, M23, M24 and M25 stay `ownerPending` and never fail, but each lists its
19
+ // `pendingOwners` (M21: rightkit-fs, rightkit-daemon, rightkit-qa; M23: @rightkit/release, rightkit-daemon; M24: @rightkit/issues-tracker,
20
+ // unpublished; M25: rightkit-qa, @rightkit/qa, journeys capability unbuilt) and the report shows
21
+ // how each app's declarations measure up (`pending[].candidateOwners`, `enforced: false`). Moving those owners into `rows` and
22
+ // raising minimums is the separate activation step (PK17.activation). The M22 applicability record (PK15-2) is validated and
23
+ // reported under `applicability.M22`; an invalid or contradictory record is a `problem`, which never changes the exit code.
16
24
  import { existsSync, readFileSync, readdirSync, realpathSync, statSync } from "node:fs";
17
25
  import path from "node:path";
18
26
  import { fileURLToPath } from "node:url";
@@ -231,9 +239,50 @@ function judge(req, facts) {
231
239
  return { ...base, status: "ok", declared: declaredAt, evidence: [] };
232
240
  }
233
241
 
242
+ // An entrypoint is `<relative file>`, optionally `#<symbol>` or `::<symbol>`; a bare token with no path separator or extension
243
+ // is a symbol or command name and is recorded unverified. A file part must stay inside the app root and exist.
244
+ const entrypointFile = (entry) => entry.split(/#|::/)[0];
245
+ const looksLikePath = (text) => /[\\/]/.test(text) || /\.[A-Za-z0-9]+$/.test(text);
246
+
247
+ /** Validate one applicability record (`M22: { role, entrypoints }`); report-only, never throws. */
248
+ export function validateApplicabilityRecord(id, raw, { spec, root }) {
249
+ const result = { row: id, status: "unrecorded", role: null, entrypoints: [], unverifiedEntrypoints: 0, problems: [] };
250
+ if (raw === undefined) return result;
251
+ const bad = (reason) => result.problems.push(`${id}: ${reason}`);
252
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) { bad("applicability record must be an object { role, entrypoints }"); result.status = "invalid"; return result; }
253
+ const extra = Object.keys(raw).filter((key) => key !== "role" && key !== "entrypoints");
254
+ if (extra.length) bad(`unknown applicability record key(s): ${extra.join(", ")}`);
255
+ const roles = spec?.roles ?? [];
256
+ if (!roles.includes(raw.role)) bad(`role must be one of ${roles.join("|")}, got ${JSON.stringify(raw.role)}`);
257
+ else result.role = raw.role;
258
+ if (!Array.isArray(raw.entrypoints)) bad("entrypoints must be an array");
259
+ else {
260
+ const seen = new Set();
261
+ for (const entry of raw.entrypoints) {
262
+ if (typeof entry !== "string" || !entry.trim()) { bad("every entrypoint must be a non-empty string"); continue; }
263
+ const text = entry.trim();
264
+ if (seen.has(text)) { bad(`entrypoint ${JSON.stringify(text)} is listed twice`); continue; }
265
+ seen.add(text);
266
+ result.entrypoints.push(text);
267
+ const file = entrypointFile(text);
268
+ if (!file) { bad(`entrypoint ${JSON.stringify(text)} names no file or symbol`); continue; }
269
+ if (!looksLikePath(file)) { result.unverifiedEntrypoints += 1; continue; }
270
+ const normalized = file.replace(/\\/g, "/");
271
+ if (path.isAbsolute(file) || /^[A-Za-z]:/.test(file) || normalized.split("/").includes("..")) { bad(`entrypoint ${JSON.stringify(text)} must be a relative path inside the app`); continue; }
272
+ let isFile = false;
273
+ try { isFile = statSync(path.join(root, normalized)).isFile(); } catch { /* missing */ }
274
+ if (!isFile) bad(`entrypoint ${JSON.stringify(text)}: file not found under the app root`);
275
+ }
276
+ if (result.role === "absent" && raw.entrypoints.length) bad("role absent must list no entrypoints");
277
+ if (result.role && result.role !== "absent" && !raw.entrypoints.length) bad(`role ${result.role} needs at least one real entrypoint`);
278
+ }
279
+ result.status = result.problems.length ? "invalid" : "recorded";
280
+ return result;
281
+ }
282
+
234
283
  export function loadAppDeclaration(root, data) {
235
284
  const file = path.join(root, data.appDeclarationFile);
236
- if (!existsSync(file)) return { profile: data.defaultProfile, notApplicable: {}, problems: [] };
285
+ if (!existsSync(file)) return { profile: data.defaultProfile, notApplicable: {}, records: {}, problems: [] };
237
286
  const problems = [];
238
287
  let doc = {};
239
288
  try { doc = JSON.parse(readFileSync(file, "utf8")); } catch (error) { problems.push(`${data.appDeclarationFile} unparseable: ${error.message}`); }
@@ -242,7 +291,14 @@ export function loadAppDeclaration(root, data) {
242
291
  if (typeof reason === "string" && reason.trim().length >= 10) notApplicable[id] = reason.trim();
243
292
  else problems.push(`${id}: N/A declaration needs a reason (>= 10 chars); ignored`);
244
293
  }
245
- return { profile: doc.profile ?? data.defaultProfile, notApplicable, problems };
294
+ const records = {};
295
+ for (const id of Object.keys(data.applicabilityRecords ?? {})) if (doc && typeof doc === "object" && Object.hasOwn(doc, id)) records[id] = doc[id];
296
+ return { profile: doc.profile ?? data.defaultProfile, notApplicable, records, problems };
297
+ }
298
+
299
+ // A pending row's `profile` is one profile name, a list of profile names, or "all" (repository-wide: every profile, including future ones).
300
+ export function pendingAppliesTo(row, active) {
301
+ return row.profile === "all" || (Array.isArray(row.profile) ? row.profile.includes(active) : row.profile === active);
246
302
  }
247
303
 
248
304
  export function checkApp(root, { data = loadData(), profile, amendments = [] } = {}) {
@@ -263,11 +319,29 @@ export function checkApp(root, { data = loadData(), profile, amendments = [] } =
263
319
  }
264
320
  const notApplicable = Object.entries(declaration.notApplicable).map(([id, reason]) => ({ row: id, reason }));
265
321
  const applicable = rows.filter((r) => !(r.id in declaration.notApplicable));
266
- const pending = data.pendingRows.filter((r) => r.profile === active).map((r) => ({ row: r.id, name: r.name, status: "owner-pending", note: r.ownerPending }));
267
322
  const manifests = findManifests(root).map((f) => readManifest(f, root));
268
323
  resolveWorkspaceInheritance(manifests);
269
324
  for (const m of manifests) if (m.error) problems.push(`${m.rel}: ${m.error}`);
270
325
  const facts = manifests.flatMap((m) => m.facts);
326
+ // Pending rows never fail. Their `pendingOwners` are judged like any owner and listed as `candidateOwners` (`enforced: false`)
327
+ // so the report shows what activation would find; the result is never counted in `violations`.
328
+ const pending = data.pendingRows.filter((r) => pendingAppliesTo(r, active)).map((r) => ({
329
+ row: r.id, name: r.name, status: "owner-pending", note: r.ownerPending,
330
+ candidateOwners: r.id in declaration.notApplicable ? [] : (r.pendingOwners ?? []).map((o) => {
331
+ const judged = judge({ ...o, rows: [r.id] }, facts);
332
+ return { owner: judged.owner, ecosystem: judged.ecosystem, rows: judged.rows, required: judged.required, declared: judged.declared, declarationStatus: judged.status, evidence: judged.evidence, enforced: false };
333
+ }),
334
+ }));
335
+ // Applicability records (PK15-2): validated and reported, never enforced.
336
+ const applicability = {};
337
+ for (const [id, spec] of Object.entries(data.applicabilityRecords ?? {})) {
338
+ const record = validateApplicabilityRecord(id, declaration.records[id], { spec, root });
339
+ applicability[id] = record;
340
+ problems.push(...record.problems);
341
+ const declaredNa = id in declaration.notApplicable;
342
+ if (record.role === "absent" && !declaredNa) problems.push(`${id}: applicability role is absent but notApplicable.${id} is not declared; the ${id} row still applies until the app declares it not applicable with a reason`);
343
+ if (record.role && record.role !== "absent" && declaredNa) problems.push(`${id}: notApplicable.${id} is declared but the applicability record says role ${record.role}`);
344
+ }
271
345
  const owners = [...requirements(applicable).values()].map((req) => { const result = judge(req, facts); return { ...result, declarationStatus: result.status }; });
272
346
  // A profile that is unknown, marked unratified, or has no rows checks nothing; an app may not select it, so it fails rather than passing empty.
273
347
  const profileStatus = !data.profiles[active] ? "unknown-profile" : (data.profiles[active].ratified === false || !data.rows.some((r) => r.profile === active)) ? "unratified" : null;
@@ -276,7 +350,7 @@ export function checkApp(root, { data = loadData(), profile, amendments = [] } =
276
350
  owners.push({ owner: `profile:${active}`, ecosystem: "profile", rows: [], required: "ratified profile", status: profileStatus, declared: null, evidence: ["an app may not select a profile that is not ratified"] });
277
351
  }
278
352
  const violations = owners.filter((o) => o.status !== "ok").length;
279
- return { app: name, root, scanned: true, profile: active, manifests: manifests.length, owners, pending, notApplicable, problems, violations };
353
+ return { app: name, root, scanned: true, profile: active, manifests: manifests.length, owners, pending, applicability, notApplicable, problems, violations };
280
354
  }
281
355
 
282
356
  export function checkApps(roots, options = {}) {
@@ -292,6 +366,8 @@ export function renderText(report) {
292
366
  out.push("", `${app.app} [${app.profile}] ${app.scanned ? `ok=${count("ok")} missing=${count("missing")} below-required=${count("below-required")} not-on-registry=${count("not-on-registry")}` : "UNSCANNED"}`
293
367
  + (app.pending.length ? ` pending=${app.pending.map((p) => p.row).join(",")}` : "") + (app.notApplicable.length ? ` n/a=${app.notApplicable.map((n) => n.row).join(",")}` : ""));
294
368
  for (const o of app.owners.filter((x) => x.status !== "ok")) out.push(` ${o.status.padEnd(15)} ${o.owner} (${o.rows.join(",")}) requires ${o.required}${o.declared ? `, declared ${o.declared}` : ""}${o.evidence.length ? ` — ${o.evidence.join("; ")}` : ""}`);
369
+ for (const p of app.pending) for (const c of p.candidateOwners ?? []) if (c.declarationStatus !== "ok") out.push(` not-enforced ${p.row} ${c.declarationStatus} ${c.owner} requires ${c.required}${c.declared ? `, declared ${c.declared}` : ""} (owner-pending: report only)`);
370
+ for (const record of Object.values(app.applicability ?? {})) if (record.status !== "unrecorded") out.push(` applicability ${record.row} ${record.status}${record.role ? ` role=${record.role}` : ""} entrypoints=${record.entrypoints.length}${record.unverifiedEntrypoints ? ` (${record.unverifiedEntrypoints} unverified symbol)` : ""}`);
295
371
  for (const n of app.notApplicable) out.push(` n/a ${n.row}: ${n.reason}`);
296
372
  for (const p of app.problems) out.push(` problem ${p}`);
297
373
  }
package/package.json CHANGED
@@ -1,17 +1,21 @@
1
1
  {
2
2
  "name": "@rightkit/release",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
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",
7
7
  "bin": {
8
- "right-release": "cli/right-release.mjs"
8
+ "right-release": "cli/right-release.mjs",
9
+ "rightkit-gate": "bin/rightkit-gate.mjs"
9
10
  },
10
11
  "rightkit": {
11
12
  "publishCheck": {
12
13
  "binSmoke": {
13
14
  "right-release": [
14
15
  "--version"
16
+ ],
17
+ "rightkit-gate": [
18
+ "--version"
15
19
  ]
16
20
  }
17
21
  }