owlrunkit 0.13.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 (128) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/LICENSE +674 -0
  3. package/NOTICE.md +137 -0
  4. package/README.md +44 -0
  5. package/SOURCE.md +10 -0
  6. package/docs/architecture/OWLCODA_RUN_KIT_CONTRACT_V0_1.md +160 -0
  7. package/docs/architecture/OWLCODA_RUN_KIT_CONTRACT_V0_2.md +459 -0
  8. package/integrations/codex/skills/owlcoda-runkit/SKILL.md +408 -0
  9. package/integrations/codex/skills/owlcoda-runkit/agents/openai.yaml +4 -0
  10. package/integrations/codex/skills/owlcoda-runkit/assets/templates/agents-snippet.md +7 -0
  11. package/integrations/codex/skills/owlcoda-runkit/assets/templates/config.json +10 -0
  12. package/integrations/codex/skills/owlcoda-runkit/assets/templates/coverage-adopt-request.json +26 -0
  13. package/integrations/codex/skills/owlcoda-runkit/assets/templates/execution-delivery-packet.json +13 -0
  14. package/integrations/codex/skills/owlcoda-runkit/assets/templates/finalize-request.json +18 -0
  15. package/integrations/codex/skills/owlcoda-runkit/assets/templates/goal-contract.json +11 -0
  16. package/integrations/codex/skills/owlcoda-runkit/assets/templates/profiles.json +9 -0
  17. package/integrations/codex/skills/owlcoda-runkit/assets/templates/ready-for-commit-request.json +11 -0
  18. package/integrations/codex/skills/owlcoda-runkit/assets/templates/resource-preflight-request.json +56 -0
  19. package/integrations/codex/skills/owlcoda-runkit/assets/templates/resume-request.json +10 -0
  20. package/integrations/codex/skills/owlcoda-runkit/assets/templates/snapshot-request.json +23 -0
  21. package/integrations/codex/skills/owlcoda-runkit/assets/templates/verification-context.json +15 -0
  22. package/integrations/codex/skills/owlcoda-runkit/assets/templates/verification-receipt.json +11 -0
  23. package/integrations/codex/skills/owlcoda-runkit/assets/templates/verify-plan-request.json +31 -0
  24. package/integrations/codex/skills/owlcoda-runkit/assets/templates/visual-smoke-request.json +32 -0
  25. package/integrations/codex/skills/owlcoda-runkit/assets/templates/worker-lease.json +7 -0
  26. package/package.json +24 -0
  27. package/packages/attest/LICENSE +674 -0
  28. package/packages/attest/README.md +63 -0
  29. package/packages/attest/cli/owlcoda-attest.mjs +155 -0
  30. package/packages/attest/consumer-surface-v1.json +255 -0
  31. package/packages/attest/distribution-manifest-v1.json +104 -0
  32. package/packages/attest/fixtures/bad/accepted-authority-leak.json +7 -0
  33. package/packages/attest/fixtures/bad/accepted-failed-receipt.json +7 -0
  34. package/packages/attest/fixtures/bad/active-context-binding-mismatch.json +7 -0
  35. package/packages/attest/fixtures/bad/active-source-binding-mismatch.json +7 -0
  36. package/packages/attest/fixtures/bad/context-fingerprint-mismatch.json +7 -0
  37. package/packages/attest/fixtures/bad/duplicate-object-key.json +8 -0
  38. package/packages/attest/fixtures/bad/multiple-active-leaves.json +10 -0
  39. package/packages/attest/fixtures/bad/orphan-replacement.json +7 -0
  40. package/packages/attest/fixtures/bad/root-shape-extra-property.json +8 -0
  41. package/packages/attest/fixtures/bad/source-fingerprint-mismatch.json +7 -0
  42. package/packages/attest/fixtures/good/accepted-replacement-chain.json +59 -0
  43. package/packages/attest/fixtures/good/blocked-single-leaf.json +9 -0
  44. package/packages/attest/fixtures/index.json +90 -0
  45. package/packages/attest/sbom.spdx.json +33 -0
  46. package/packages/attest/schemas/attestation-ref-v1.schema.json +44 -0
  47. package/packages/attest/schemas/attestation-result-v1.schema.json +163 -0
  48. package/packages/attest/schemas/offline-attestation-bundle-v1.schema.json +30 -0
  49. package/packages/attest/schemas/public-verification-bundle-v1.schema.json +74 -0
  50. package/packages/attest/schemas/quick-verification-receipt-v1.schema.json +219 -0
  51. package/packages/attest/schemas/workspace-snapshot-v1.schema.json +135 -0
  52. package/packages/attest/src/canonical.mjs +12 -0
  53. package/packages/attest/src/formal.mjs +762 -0
  54. package/packages/attest/src/index.mjs +113 -0
  55. package/packages/attest/src/offline-bundle.mjs +108 -0
  56. package/packages/attest/src/quick-receipt-contract.mjs +156 -0
  57. package/packages/attest/src/quick.mjs +274 -0
  58. package/packages/attest/src/reference-contract.mjs +64 -0
  59. package/packages/attest/src/reference.mjs +131 -0
  60. package/packages/attest/src/workspace-snapshot.mjs +171 -0
  61. package/registry-release-policy-v1.json +34 -0
  62. package/sbom.spdx.json +39 -0
  63. package/scripts/runkit-contract/acceptance-evidence.mjs +103 -0
  64. package/scripts/runkit-contract/core-contract.mjs +392 -0
  65. package/scripts/runkit-contract/coverage-adoption.mjs +414 -0
  66. package/scripts/runkit-contract/delivery-create.mjs +252 -0
  67. package/scripts/runkit-contract/delivery-selection.mjs +31 -0
  68. package/scripts/runkit-contract/finalize.mjs +790 -0
  69. package/scripts/runkit-contract/inspect-presentation.mjs +183 -0
  70. package/scripts/runkit-contract/install-codex-skill.mjs +239 -0
  71. package/scripts/runkit-contract/lease-lifecycle.mjs +324 -0
  72. package/scripts/runkit-contract/lifecycle-orchestration.mjs +415 -0
  73. package/scripts/runkit-contract/offline-store.mjs +248 -0
  74. package/scripts/runkit-contract/profile-impact.mjs +223 -0
  75. package/scripts/runkit-contract/provenance-common.mjs +153 -0
  76. package/scripts/runkit-contract/quick-attest.mjs +4 -0
  77. package/scripts/runkit-contract/quick-canonical.mjs +41 -0
  78. package/scripts/runkit-contract/quick-metrics.mjs +86 -0
  79. package/scripts/runkit-contract/quick-receipt.mjs +139 -0
  80. package/scripts/runkit-contract/quick-verify.mjs +201 -0
  81. package/scripts/runkit-contract/quick-workspace-snapshot.mjs +3 -0
  82. package/scripts/runkit-contract/ready-for-commit.mjs +188 -0
  83. package/scripts/runkit-contract/receipt-lineage.mjs +332 -0
  84. package/scripts/runkit-contract/registry-adoption-gate.mjs +117 -0
  85. package/scripts/runkit-contract/repair-execution.mjs +997 -0
  86. package/scripts/runkit-contract/resource-preflight.mjs +829 -0
  87. package/scripts/runkit-contract/resume-execution.mjs +569 -0
  88. package/scripts/runkit-contract/runkit-cli.mjs +1214 -0
  89. package/scripts/runkit-contract/schemas/closeout-receipt-v2.schema.json +42 -0
  90. package/scripts/runkit-contract/schemas/core-artifact-v2.schema.json +23 -0
  91. package/scripts/runkit-contract/schemas/core-artifact.schema.json +23 -0
  92. package/scripts/runkit-contract/schemas/coverage-adopt-request-v1.schema.json +66 -0
  93. package/scripts/runkit-contract/schemas/engine-pin-v2.schema.json +14 -0
  94. package/scripts/runkit-contract/schemas/engine-pin.schema.json +14 -0
  95. package/scripts/runkit-contract/schemas/evidence-coverage-index-v1.schema.json +131 -0
  96. package/scripts/runkit-contract/schemas/finalize-request-v1.schema.json +131 -0
  97. package/scripts/runkit-contract/schemas/profiles.schema.json +47 -0
  98. package/scripts/runkit-contract/schemas/project-config-v2.schema.json +13 -0
  99. package/scripts/runkit-contract/schemas/project-config.schema.json +13 -0
  100. package/scripts/runkit-contract/schemas/ready-for-commit-v1.schema.json +26 -0
  101. package/scripts/runkit-contract/schemas/replayable-evidence-v1.schema.json +82 -0
  102. package/scripts/runkit-contract/schemas/resource-preflight-request-v1.schema.json +178 -0
  103. package/scripts/runkit-contract/schemas/resource-preflight-v1.schema.json +153 -0
  104. package/scripts/runkit-contract/schemas/resume-attempt-v1.schema.json +80 -0
  105. package/scripts/runkit-contract/schemas/resume-request-v1.schema.json +77 -0
  106. package/scripts/runkit-contract/schemas/snapshot-v1.schema.json +71 -0
  107. package/scripts/runkit-contract/schemas/verification-context-v1.schema.json +92 -0
  108. package/scripts/runkit-contract/schemas/verification-plan-v1.schema.json +150 -0
  109. package/scripts/runkit-contract/schemas/verification-receipt-v2.schema.json +118 -0
  110. package/scripts/runkit-contract/schemas/verify-plan-request-v1.schema.json +56 -0
  111. package/scripts/runkit-contract/schemas/visual-smoke-request-v1.schema.json +64 -0
  112. package/scripts/runkit-contract/schemas/visual-smoke-result-v1.schema.json +68 -0
  113. package/scripts/runkit-contract/snapshot.mjs +340 -0
  114. package/scripts/runkit-contract/source-fingerprint.mjs +404 -0
  115. package/scripts/runkit-contract/templates/coverage-adopt-request.json +26 -0
  116. package/scripts/runkit-contract/templates/finalize-request.json +18 -0
  117. package/scripts/runkit-contract/templates/goal-contract.json +11 -0
  118. package/scripts/runkit-contract/templates/ready-for-commit-request.json +11 -0
  119. package/scripts/runkit-contract/templates/resource-preflight-request.json +56 -0
  120. package/scripts/runkit-contract/templates/resume-request.json +10 -0
  121. package/scripts/runkit-contract/templates/snapshot-request.json +23 -0
  122. package/scripts/runkit-contract/templates/verify-plan-request.json +31 -0
  123. package/scripts/runkit-contract/templates/visual-smoke-request.json +32 -0
  124. package/scripts/runkit-contract/templates/worker-lease.json +7 -0
  125. package/scripts/runkit-contract/verification-context.mjs +141 -0
  126. package/scripts/runkit-contract/verification-plan.mjs +608 -0
  127. package/scripts/runkit-contract/verification-receipt-gate.mjs +566 -0
  128. package/scripts/runkit-contract/visual-smoke.mjs +410 -0
@@ -0,0 +1,332 @@
1
+ import { createHash } from "node:crypto";
2
+ import { readFile } from "node:fs/promises";
3
+
4
+ import { isDirectExecution } from "./core-contract.mjs";
5
+
6
+ const SHA256_PATTERN = /^[a-f0-9]{64}$/i;
7
+
8
+ function isRecord(value) {
9
+ return typeof value === "object" && value !== null && !Array.isArray(value);
10
+ }
11
+
12
+ function canonicalize(value) {
13
+ if (Array.isArray(value)) {
14
+ return value.map(canonicalize);
15
+ }
16
+
17
+ if (isRecord(value)) {
18
+ return Object.fromEntries(
19
+ Object.keys(value)
20
+ .sort()
21
+ .map((key) => [key, canonicalize(value[key])]),
22
+ );
23
+ }
24
+
25
+ return value;
26
+ }
27
+
28
+ function issue(code, message, extra = {}) {
29
+ return { code, ...extra, message };
30
+ }
31
+
32
+ function malformedResult(issues) {
33
+ return {
34
+ valid: false,
35
+ malformed: true,
36
+ active: null,
37
+ superseded: [],
38
+ issues,
39
+ };
40
+ }
41
+
42
+ function normalizedSha256(value) {
43
+ return typeof value === "string" && SHA256_PATTERN.test(value)
44
+ ? value.toLowerCase()
45
+ : null;
46
+ }
47
+
48
+ export function receiptSha256(receipt) {
49
+ return createHash("sha256")
50
+ .update(JSON.stringify(canonicalize(receipt)))
51
+ .digest("hex");
52
+ }
53
+
54
+ function validateEntryShapes(entries) {
55
+ if (!Array.isArray(entries)) {
56
+ return [issue("entries_not_array", "Receipt lineage input must be an entries array.")];
57
+ }
58
+
59
+ const issues = [];
60
+ for (const [index, entry] of entries.entries()) {
61
+ if (!isRecord(entry)) {
62
+ issues.push(issue(
63
+ "entry_not_object",
64
+ "Each receipt lineage entry must be an object.",
65
+ { index },
66
+ ));
67
+ continue;
68
+ }
69
+
70
+ if (!normalizedSha256(entry.receiptSha256)) {
71
+ issues.push(issue(
72
+ "malformed_receipt_sha256",
73
+ "receiptSha256 must be a 64-character hexadecimal SHA-256.",
74
+ { index },
75
+ ));
76
+ }
77
+ if (!isRecord(entry.receipt)) {
78
+ issues.push(issue(
79
+ "receipt_not_object",
80
+ "Each receipt lineage entry must contain a receipt object.",
81
+ { index },
82
+ ));
83
+ } else if (
84
+ typeof entry.receipt.status !== "string"
85
+ || entry.receipt.status.trim().length === 0
86
+ ) {
87
+ issues.push(issue(
88
+ "malformed_receipt_status",
89
+ "Each receipt object must contain a non-empty status string.",
90
+ { index },
91
+ ));
92
+ }
93
+ if (
94
+ "parentReceiptSha256" in entry
95
+ && entry.parentReceiptSha256 !== undefined
96
+ && !normalizedSha256(entry.parentReceiptSha256)
97
+ ) {
98
+ issues.push(issue(
99
+ "malformed_parent_receipt_sha256",
100
+ "parentReceiptSha256 must be a 64-character hexadecimal SHA-256 when present.",
101
+ { index },
102
+ ));
103
+ }
104
+ if (
105
+ isRecord(entry.receipt)
106
+ && "supersedesReceiptSha256" in entry.receipt
107
+ && entry.receipt.supersedesReceiptSha256 !== undefined
108
+ && !normalizedSha256(entry.receipt.supersedesReceiptSha256)
109
+ ) {
110
+ issues.push(issue(
111
+ "malformed_supersedes_receipt_sha256",
112
+ "supersedesReceiptSha256 must be a 64-character hexadecimal SHA-256 when present.",
113
+ { index },
114
+ ));
115
+ }
116
+ }
117
+ return issues;
118
+ }
119
+
120
+ export function validateReceiptLineage(entries) {
121
+ const shapeIssues = validateEntryShapes(entries);
122
+ if (shapeIssues.length > 0) return malformedResult(shapeIssues);
123
+
124
+ const childrenByParent = new Map();
125
+ const issues = [];
126
+ const entriesByHash = new Map();
127
+ const indexByHash = new Map();
128
+
129
+ for (const [index, entry] of entries.entries()) {
130
+ const receiptHash = normalizedSha256(entry.receiptSha256);
131
+ if (entriesByHash.has(receiptHash)) {
132
+ issues.push(issue(
133
+ "duplicate_receipt_sha256",
134
+ "Each receipt SHA-256 must be globally unique in the lineage.",
135
+ { receiptSha256: receiptHash },
136
+ ));
137
+ continue;
138
+ }
139
+ entriesByHash.set(receiptHash, entry);
140
+ indexByHash.set(receiptHash, index);
141
+ }
142
+
143
+ for (const entry of entries) {
144
+ const receiptHash = normalizedSha256(entry.receiptSha256);
145
+ if (receiptSha256(entry.receipt) !== receiptHash) {
146
+ issues.push(issue(
147
+ "receipt_hash_mismatch",
148
+ "The declared receipt SHA-256 does not match the receipt content.",
149
+ { receiptSha256: receiptHash },
150
+ ));
151
+ }
152
+ }
153
+
154
+ for (const [index, entry] of entries.entries()) {
155
+ if (!entry.parentReceiptSha256) continue;
156
+
157
+ const receiptHash = normalizedSha256(entry.receiptSha256);
158
+ const parentHash = normalizedSha256(entry.parentReceiptSha256);
159
+ const children = childrenByParent.get(parentHash) ?? [];
160
+ children.push(receiptHash);
161
+ childrenByParent.set(parentHash, children);
162
+
163
+ const parent = entriesByHash.get(parentHash);
164
+ if (!parent) {
165
+ issues.push(issue(
166
+ "parent_not_found",
167
+ "The replacement references a receipt SHA-256 that is not present.",
168
+ { receiptSha256: receiptHash, parentReceiptSha256: parentHash },
169
+ ));
170
+ } else if (parent.receipt.status !== "invalidated_by_concurrent_write") {
171
+ const declaredSupersedes = normalizedSha256(entry.receipt.supersedesReceiptSha256);
172
+ if (declaredSupersedes !== parentHash) {
173
+ issues.push(issue(
174
+ declaredSupersedes === null
175
+ ? "parent_not_invalidated_by_concurrent_write"
176
+ : "supersedes_receipt_sha256_mismatch",
177
+ declaredSupersedes === null
178
+ ? "A passed parent requires an explicit repair supersedes binding."
179
+ : "The replacement receipt supersedes hash must match its lineage parent.",
180
+ {
181
+ receiptSha256: receiptHash,
182
+ parentReceiptSha256: parentHash,
183
+ supersedesReceiptSha256: declaredSupersedes,
184
+ },
185
+ ));
186
+ } else if (
187
+ parent.receipt.status !== "passed"
188
+ || entry.receipt.status !== "passed"
189
+ || typeof parent.receipt.sourceFingerprint !== "string"
190
+ || typeof entry.receipt.sourceFingerprint !== "string"
191
+ || parent.receipt.sourceFingerprint === entry.receipt.sourceFingerprint
192
+ ) {
193
+ issues.push(issue(
194
+ "invalid_repair_supersedes_edge",
195
+ "An explicit repair supersedes edge requires passed receipts bound to different source fingerprints.",
196
+ { receiptSha256: receiptHash, parentReceiptSha256: parentHash },
197
+ ));
198
+ }
199
+ } else if (
200
+ entry.receipt.supersedesReceiptSha256 !== undefined
201
+ && normalizedSha256(entry.receipt.supersedesReceiptSha256) !== parentHash
202
+ ) {
203
+ issues.push(issue(
204
+ "supersedes_receipt_sha256_mismatch",
205
+ "The replacement receipt supersedes hash must match its lineage parent.",
206
+ {
207
+ receiptSha256: receiptHash,
208
+ parentReceiptSha256: parentHash,
209
+ supersedesReceiptSha256: normalizedSha256(entry.receipt.supersedesReceiptSha256),
210
+ },
211
+ ));
212
+ }
213
+
214
+ const parentIndex = indexByHash.get(parentHash);
215
+ if (parentIndex !== undefined && parentIndex >= index) {
216
+ issues.push(issue(
217
+ "parent_not_prior",
218
+ "An append-only replacement must reference an earlier receipt entry.",
219
+ { receiptSha256: receiptHash, parentReceiptSha256: parentHash },
220
+ ));
221
+ }
222
+ }
223
+
224
+ for (const [parentReceiptSha256, children] of childrenByParent) {
225
+ if (children.length > 1) {
226
+ issues.push(issue(
227
+ "lineage_branch_detected",
228
+ "An append-only receipt may have at most one direct replacement.",
229
+ { parentReceiptSha256, childReceiptSha256Values: children },
230
+ ));
231
+ }
232
+ }
233
+
234
+ const visitState = new Map();
235
+ let cycleDetected = false;
236
+
237
+ function visit(receiptHash) {
238
+ const state = visitState.get(receiptHash);
239
+ if (state === "visiting") {
240
+ cycleDetected = true;
241
+ return;
242
+ }
243
+ if (state === "visited") return;
244
+
245
+ visitState.set(receiptHash, "visiting");
246
+ const parentHash = normalizedSha256(
247
+ entriesByHash.get(receiptHash)?.parentReceiptSha256,
248
+ );
249
+ if (parentHash && entriesByHash.has(parentHash)) visit(parentHash);
250
+ visitState.set(receiptHash, "visited");
251
+ }
252
+
253
+ for (const receiptHash of entriesByHash.keys()) visit(receiptHash);
254
+
255
+ if (cycleDetected) {
256
+ issues.push(issue(
257
+ "cycle_detected",
258
+ "Receipt replacement references must form an acyclic lineage.",
259
+ ));
260
+ }
261
+
262
+ const activeEntries = entries.filter(
263
+ (entry) => !childrenByParent.has(normalizedSha256(entry.receiptSha256)),
264
+ );
265
+ const active = activeEntries.length === 1 ? activeEntries[0] : null;
266
+ const superseded = entries.filter((entry) =>
267
+ childrenByParent.has(normalizedSha256(entry.receiptSha256)),
268
+ );
269
+
270
+ if (activeEntries.length > 1) {
271
+ issues.push(issue(
272
+ "multiple_active_leaves",
273
+ "A valid receipt lineage must have exactly one active leaf.",
274
+ {
275
+ receiptSha256Values: activeEntries.map((entry) =>
276
+ normalizedSha256(entry.receiptSha256)),
277
+ },
278
+ ));
279
+ } else if (activeEntries.length === 0 && !cycleDetected) {
280
+ issues.push(issue(
281
+ "no_active_leaf",
282
+ "A valid receipt lineage must have one active leaf.",
283
+ ));
284
+ }
285
+
286
+ return {
287
+ valid: active !== null && issues.length === 0,
288
+ malformed: false,
289
+ active,
290
+ superseded,
291
+ issues,
292
+ };
293
+ }
294
+
295
+ function cliFailure(code, message) {
296
+ return malformedResult([issue(code, message)]);
297
+ }
298
+
299
+ export async function runCli(args = process.argv.slice(2)) {
300
+ if (args.length !== 1) {
301
+ return {
302
+ exitCode: 3,
303
+ output: cliFailure(
304
+ "usage_error",
305
+ "Usage: receipt-lineage.mjs <receipt-array.json>",
306
+ ),
307
+ };
308
+ }
309
+
310
+ let document;
311
+ try {
312
+ document = JSON.parse(await readFile(args[0], "utf8"));
313
+ } catch (error) {
314
+ return {
315
+ exitCode: 3,
316
+ output: cliFailure("input_error", error.message),
317
+ };
318
+ }
319
+
320
+ const entries = Array.isArray(document) ? document : document?.receipts;
321
+ const output = validateReceiptLineage(entries);
322
+ return {
323
+ exitCode: output.malformed ? 3 : output.valid ? 0 : 2,
324
+ output,
325
+ };
326
+ }
327
+
328
+ if (isDirectExecution(import.meta.url)) {
329
+ const { exitCode, output } = await runCli();
330
+ process.stdout.write(`${JSON.stringify(output)}\n`);
331
+ process.exitCode = exitCode;
332
+ }
@@ -0,0 +1,117 @@
1
+ export const OFFICIAL_NPM_REGISTRY = "https://registry.npmjs.org";
2
+
3
+ const REQUIRED_INSTALL_FIELDS = Object.freeze([
4
+ "requestedSpec",
5
+ "packageName",
6
+ "version",
7
+ "shasum",
8
+ "integrity",
9
+ "resolved",
10
+ ]);
11
+
12
+ const RELEASE_EVIDENCE_FIELDS = Object.freeze([
13
+ "authorizationGranted",
14
+ "integrity",
15
+ "packageName",
16
+ "registry",
17
+ "schemaVersion",
18
+ "shasum",
19
+ "status",
20
+ "tarballUrl",
21
+ "version",
22
+ ]);
23
+
24
+ function decision(policy, issueCodes) {
25
+ return {
26
+ schemaVersion: "OwlCodaRunKitRegistryAdoptionDecisionV1",
27
+ decision: issueCodes.length === 0 ? "ELIGIBLE" : "INELIGIBLE",
28
+ issueCodes,
29
+ packageName: policy.packageName,
30
+ version: policy.candidateVersion,
31
+ authorizationGranted: false,
32
+ };
33
+ }
34
+
35
+ function expectedTarballUrl(policy) {
36
+ const tarballName = policy.packageName.split("/").at(-1);
37
+ return `${OFFICIAL_NPM_REGISTRY}/${policy.packageName}/-/${tarballName}-${policy.candidateVersion}.tgz`;
38
+ }
39
+
40
+ function validSha512Integrity(value) {
41
+ if (typeof value !== "string") return false;
42
+ const match = /^sha512-([A-Za-z0-9+/]+={0,2})$/.exec(value);
43
+ if (!match) return false;
44
+ const bytes = Buffer.from(match[1], "base64");
45
+ return bytes.length === 64 && bytes.toString("base64") === match[1];
46
+ }
47
+
48
+ function validReleaseEvidence(policy, release) {
49
+ if (
50
+ policy.registry !== OFFICIAL_NPM_REGISTRY
51
+ || Object.keys(release).sort().join("\n") !== RELEASE_EVIDENCE_FIELDS.join("\n")
52
+ || !/^[0-9a-f]{40}$/.test(release.shasum)
53
+ || !validSha512Integrity(release.integrity)
54
+ || release.tarballUrl !== expectedTarballUrl(policy)
55
+ ) {
56
+ return false;
57
+ }
58
+ return true;
59
+ }
60
+
61
+ export function evaluateRegistryAdoption({ policy, releaseEvidence, install }) {
62
+ if (!policy || typeof policy !== "object" || Array.isArray(policy)) {
63
+ throw new TypeError("Registry release policy must be an object.");
64
+ }
65
+ if (!install || typeof install !== "object" || Array.isArray(install)) {
66
+ return decision(policy, ["registry_install_binding_missing"]);
67
+ }
68
+ if (install.kind !== "registry") {
69
+ return decision(policy, ["registry_source_required"]);
70
+ }
71
+ if (REQUIRED_INSTALL_FIELDS.some((field) =>
72
+ typeof install[field] !== "string" || install[field].length === 0)) {
73
+ return decision(policy, ["registry_install_binding_missing"]);
74
+ }
75
+ if (
76
+ !releaseEvidence
77
+ || typeof releaseEvidence !== "object"
78
+ || Array.isArray(releaseEvidence)
79
+ || releaseEvidence.schemaVersion !== policy.registryEvidenceSchemaVersion
80
+ || releaseEvidence.status !== "registry_verified"
81
+ || releaseEvidence.authorizationGranted !== false
82
+ ) {
83
+ return decision(policy, ["registry_release_not_verified"]);
84
+ }
85
+
86
+ const release = releaseEvidence;
87
+ if (!validReleaseEvidence(policy, release)) {
88
+ return decision(policy, ["registry_release_evidence_invalid"]);
89
+ }
90
+ if (
91
+ release.registry !== policy.registry
92
+ || release.packageName !== policy.packageName
93
+ || release.version !== policy.candidateVersion
94
+ ) {
95
+ return decision(policy, ["registry_release_evidence_mismatch"]);
96
+ }
97
+ const expectedSpec = `${policy.packageName}@${release.version}`;
98
+ if (install.requestedSpec !== expectedSpec) {
99
+ return decision(policy, ["registry_exact_version_required"]);
100
+ }
101
+ if (install.packageName !== release.packageName) {
102
+ return decision(policy, ["registry_package_name_mismatch"]);
103
+ }
104
+ if (install.version !== release.version) {
105
+ return decision(policy, ["registry_version_mismatch"]);
106
+ }
107
+ if (install.shasum !== release.shasum) {
108
+ return decision(policy, ["registry_shasum_mismatch"]);
109
+ }
110
+ if (install.integrity !== release.integrity) {
111
+ return decision(policy, ["registry_integrity_mismatch"]);
112
+ }
113
+ if (install.resolved !== release.tarballUrl) {
114
+ return decision(policy, ["registry_tarball_url_mismatch"]);
115
+ }
116
+ return decision(policy, []);
117
+ }