owlrunkit 0.13.0 → 0.15.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.
@@ -0,0 +1,510 @@
1
+ import {
2
+ existsSync,
3
+ lstatSync,
4
+ readdirSync,
5
+ realpathSync,
6
+ } from "node:fs";
7
+ import path from "node:path";
8
+
9
+ import {
10
+ readWorkspaceJsonBounded,
11
+ resolveBoundedWorkspaceRoot,
12
+ } from "./onboarding-doctor.mjs";
13
+ import { resolveProfileImpactDetailed } from "./profile-impact.mjs";
14
+ import {
15
+ safeIdentifier,
16
+ safeRelativePath,
17
+ } from "./provenance-common.mjs";
18
+
19
+ function resolveRegularExecutable(name) {
20
+ const pathEntries = (process.env.PATH ?? "").split(path.delimiter);
21
+ const suffixes = process.platform === "win32"
22
+ ? (process.env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";")
23
+ : [""];
24
+ for (const directory of pathEntries) {
25
+ for (const suffix of suffixes) {
26
+ const candidate = path.resolve(directory || ".", `${name}${suffix}`);
27
+ try {
28
+ const resolved = realpathSync(candidate);
29
+ const stat = lstatSync(resolved);
30
+ if (stat.isFile() && !stat.isSymbolicLink()) return resolved;
31
+ } catch {
32
+ // Continue through PATH without treating a missing or unsafe entry as authority.
33
+ }
34
+ }
35
+ }
36
+ return null;
37
+ }
38
+
39
+ function command(id, script, executable) {
40
+ return {
41
+ id,
42
+ cwd: ".",
43
+ executable,
44
+ argv: ["run", script],
45
+ };
46
+ }
47
+
48
+ function workspaceCommand(id, workspacePath, script, executable) {
49
+ return {
50
+ id,
51
+ cwd: ".",
52
+ executable,
53
+ argv: ["run", "--workspace", workspacePath, script],
54
+ };
55
+ }
56
+
57
+ function detectedScripts(manifest) {
58
+ return Object.keys(manifest.scripts ?? {}).sort();
59
+ }
60
+
61
+ function workspacePatterns(manifest) {
62
+ if (Array.isArray(manifest.workspaces)) return manifest.workspaces;
63
+ if (
64
+ manifest.workspaces
65
+ && typeof manifest.workspaces === "object"
66
+ && Array.isArray(manifest.workspaces.packages)
67
+ ) {
68
+ return manifest.workspaces.packages;
69
+ }
70
+ return [];
71
+ }
72
+
73
+ function safeWorkspacePattern(value) {
74
+ if (
75
+ typeof value !== "string"
76
+ || value.length === 0
77
+ || path.isAbsolute(value)
78
+ || value.includes("\\")
79
+ || value.includes("\0")
80
+ ) {
81
+ return null;
82
+ }
83
+ const segments = value.split("/");
84
+ if (segments.some((segment) => segment.length === 0 || segment === "." || segment === "..")) {
85
+ return null;
86
+ }
87
+ const wildcardCount = [...value].filter((character) => character === "*").length;
88
+ if (wildcardCount === 0) return { kind: "literal", path: value };
89
+ if (wildcardCount === 1 && value.endsWith("/*")) {
90
+ return { kind: "children", path: value.slice(0, -2) };
91
+ }
92
+ return null;
93
+ }
94
+
95
+ function discoverWorkspaces(root, manifest) {
96
+ const warnings = [];
97
+ const paths = new Set();
98
+ for (const rawPattern of workspacePatterns(manifest)) {
99
+ const pattern = safeWorkspacePattern(rawPattern);
100
+ if (pattern === null) {
101
+ warnings.push(`unsupported_workspace_pattern:${String(rawPattern)}`);
102
+ continue;
103
+ }
104
+ if (pattern.kind === "literal") {
105
+ paths.add(pattern.path);
106
+ continue;
107
+ }
108
+ const parent = path.join(root, pattern.path);
109
+ try {
110
+ const parentStat = lstatSync(parent);
111
+ if (
112
+ parentStat.isSymbolicLink()
113
+ || !parentStat.isDirectory()
114
+ || realpathSync(parent) !== path.resolve(parent)
115
+ ) {
116
+ warnings.push(`unsafe_workspace_parent:${pattern.path}`);
117
+ continue;
118
+ }
119
+ for (const entry of readdirSync(parent, { withFileTypes: true })
120
+ .sort((left, right) => left.name.localeCompare(right.name))) {
121
+ if (entry.isDirectory() && !entry.isSymbolicLink()) {
122
+ paths.add(`${pattern.path}/${entry.name}`);
123
+ }
124
+ }
125
+ } catch {
126
+ // A declared workspace pattern may legitimately have no current matches.
127
+ }
128
+ }
129
+
130
+ const workspaces = [];
131
+ for (const workspacePath of [...paths].sort()) {
132
+ try {
133
+ const workspaceManifest = readWorkspaceJsonBounded(
134
+ root,
135
+ path.join(workspacePath, "package.json"),
136
+ );
137
+ workspaces.push({
138
+ name: typeof workspaceManifest.name === "string"
139
+ ? workspaceManifest.name
140
+ : workspacePath,
141
+ path: workspacePath,
142
+ scripts: detectedScripts(workspaceManifest),
143
+ });
144
+ } catch {
145
+ warnings.push(`workspace_manifest_unreadable:${workspacePath}`);
146
+ }
147
+ }
148
+ return {
149
+ workspaces,
150
+ warnings: [...new Set(warnings)].sort(),
151
+ };
152
+ }
153
+
154
+ function profileIdForWorkspace(workspacePath) {
155
+ return `workspace-${workspacePath.replace(/[^A-Za-z0-9._-]+/g, "-")}`;
156
+ }
157
+
158
+ function workspaceProfiles(workspaces, npmExecutable) {
159
+ return workspaces
160
+ .map((workspace) => {
161
+ const qualityScripts = workspace.scripts.filter((script) =>
162
+ ["build", "e2e", "lint", "test", "typecheck"].includes(script));
163
+ if (qualityScripts.length === 0) return null;
164
+ const profileId = profileIdForWorkspace(workspace.path);
165
+ return {
166
+ id: profileId,
167
+ paths: [`${workspace.path}/**`],
168
+ role: "primary",
169
+ primary: false,
170
+ requiresProfileIds: [],
171
+ commands: npmExecutable
172
+ ? qualityScripts.map((script) => workspaceCommand(
173
+ `npm-${profileId}-${script}`,
174
+ workspace.path,
175
+ script,
176
+ npmExecutable,
177
+ ))
178
+ : [],
179
+ };
180
+ })
181
+ .filter(Boolean);
182
+ }
183
+
184
+ function profileCandidates(root, manifest, npmExecutable, workspaces) {
185
+ const scripts = new Set(detectedScripts(manifest));
186
+ const candidates = [];
187
+ if (
188
+ existsSync(path.join(root, "prisma"))
189
+ || [...scripts].some((script) => /^(db:|prisma)/.test(script))
190
+ ) {
191
+ const commands = [...scripts]
192
+ .filter((script) => /^(db:|prisma)/.test(script))
193
+ .map((script) => npmExecutable
194
+ ? command(`npm-${script.replaceAll(":", "-")}`, script, npmExecutable)
195
+ : null)
196
+ .filter(Boolean);
197
+ candidates.push({
198
+ id: "database",
199
+ paths: ["prisma/**"],
200
+ role: "primary",
201
+ primary: false,
202
+ requiresProfileIds: [],
203
+ commands,
204
+ });
205
+ }
206
+ if (
207
+ existsSync(path.join(root, "package.json"))
208
+ && [...scripts].some((script) => ["build", "lint", "test", "typecheck"].includes(script))
209
+ ) {
210
+ const commands = ["build", "lint", "test", "typecheck"]
211
+ .filter((script) => scripts.has(script))
212
+ .map((script) => npmExecutable ? command(`npm-${script}`, script, npmExecutable) : null)
213
+ .filter(Boolean);
214
+ candidates.push({
215
+ id: "node-quality",
216
+ paths: ["src/**", "tests/**"],
217
+ role: "primary",
218
+ primary: false,
219
+ requiresProfileIds: [],
220
+ commands,
221
+ });
222
+ }
223
+ if (
224
+ existsSync(path.join(root, "playwright.config.ts"))
225
+ || existsSync(path.join(root, "playwright.config.js"))
226
+ || scripts.has("e2e")
227
+ ) {
228
+ const commands = scripts.has("e2e") && npmExecutable
229
+ ? [command("npm-e2e", "e2e", npmExecutable)]
230
+ : [];
231
+ candidates.push({
232
+ id: "web-e2e",
233
+ paths: ["playwright.config.ts", "tests/e2e/**"],
234
+ role: "primary",
235
+ primary: false,
236
+ requiresProfileIds: [],
237
+ commands,
238
+ });
239
+ }
240
+ candidates.push(...workspaceProfiles(workspaces, npmExecutable));
241
+ return candidates.sort((left, right) => left.id.localeCompare(right.id));
242
+ }
243
+
244
+ export function detectProfiles({ workspaceRoot } = {}) {
245
+ const root = resolveBoundedWorkspaceRoot(workspaceRoot);
246
+ const manifest = readWorkspaceJsonBounded(root, "package.json");
247
+ const npmExecutable = resolveRegularExecutable("npm");
248
+ const discovered = discoverWorkspaces(root, manifest);
249
+ const candidates = profileCandidates(root, manifest, npmExecutable, discovered.workspaces);
250
+ const primaryCandidates = candidates.filter((profile) => profile.role === "primary");
251
+ const warnings = primaryCandidates.length > 1
252
+ ? [`ambiguous_primary_profile:${primaryCandidates.map((profile) => profile.id).join(",")}`]
253
+ : [];
254
+ warnings.push(...discovered.warnings);
255
+ if (
256
+ npmExecutable === null
257
+ && (
258
+ detectedScripts(manifest).some((script) =>
259
+ /^(build|db:|e2e|lint|prisma|test|typecheck)/.test(script))
260
+ || discovered.workspaces.some((workspace) => workspace.scripts.some((script) =>
261
+ /^(build|e2e|lint|test|typecheck)$/.test(script)))
262
+ )
263
+ ) {
264
+ warnings.push("npm_regular_executable_unavailable");
265
+ }
266
+ return {
267
+ schemaVersion: "OwlCodaRunKitProfileDetectionV1",
268
+ status: "profiles_detected",
269
+ dryRun: true,
270
+ writesPerformed: 0,
271
+ candidates,
272
+ primaryProfileId: primaryCandidates.length === 1 ? primaryCandidates[0].id : null,
273
+ warnings,
274
+ detectedScripts: detectedScripts(manifest),
275
+ detectedWorkspaces: discovered.workspaces,
276
+ authorizationGranted: false,
277
+ };
278
+ }
279
+
280
+ function exactKeys(value, allowed) {
281
+ return value
282
+ && typeof value === "object"
283
+ && !Array.isArray(value)
284
+ && Object.keys(value).every((key) => allowed.includes(key));
285
+ }
286
+
287
+ function representativePath(rule) {
288
+ return rule.endsWith("/**")
289
+ ? `${rule.slice(0, -3)}/__owlrunkit_profile_surface__`
290
+ : rule;
291
+ }
292
+
293
+ function validateProfileDocument(config) {
294
+ const issues = [];
295
+ if (
296
+ !exactKeys(config, ["schemaVersion", "profiles"])
297
+ || config.schemaVersion !== "OwlCodaRunKitProfilesV1"
298
+ || !Array.isArray(config.profiles)
299
+ || config.profiles.length === 0
300
+ ) {
301
+ return ["profiles_document_invalid"];
302
+ }
303
+
304
+ const ids = new Set();
305
+ const commandIds = new Set();
306
+ for (const profile of config.profiles) {
307
+ if (!exactKeys(profile, [
308
+ "commands",
309
+ "id",
310
+ "paths",
311
+ "primary",
312
+ "requiresProfileIds",
313
+ "role",
314
+ ])) {
315
+ issues.push("profile_shape_invalid");
316
+ continue;
317
+ }
318
+ if (typeof profile.id !== "string" || profile.id.length === 0) {
319
+ issues.push("profile_id_invalid");
320
+ } else if (ids.has(profile.id)) {
321
+ issues.push(`duplicate_profile_id:${profile.id}`);
322
+ } else {
323
+ ids.add(profile.id);
324
+ }
325
+ if (!Array.isArray(profile.paths) || profile.paths.length === 0) {
326
+ issues.push(`profile_paths_missing:${profile.id ?? "unknown"}`);
327
+ }
328
+ if (profile.role !== undefined && !["primary", "supporting"].includes(profile.role)) {
329
+ issues.push(`profile_role_invalid:${profile.id ?? "unknown"}`);
330
+ }
331
+ if (profile.primary !== undefined && typeof profile.primary !== "boolean") {
332
+ issues.push(`profile_primary_invalid:${profile.id ?? "unknown"}`);
333
+ }
334
+ if (
335
+ profile.requiresProfileIds !== undefined
336
+ && (
337
+ !Array.isArray(profile.requiresProfileIds)
338
+ || profile.requiresProfileIds.some((id) => typeof id !== "string" || id.length === 0)
339
+ || new Set(profile.requiresProfileIds).size !== profile.requiresProfileIds.length
340
+ )
341
+ ) {
342
+ issues.push(`profile_requirements_invalid:${profile.id ?? "unknown"}`);
343
+ }
344
+ if (profile.commands !== undefined) {
345
+ if (!Array.isArray(profile.commands)) {
346
+ issues.push(`profile_commands_invalid:${profile.id ?? "unknown"}`);
347
+ } else {
348
+ for (const entry of profile.commands) {
349
+ if (
350
+ !exactKeys(entry, ["argv", "cwd", "executable", "id"])
351
+ || typeof entry.id !== "string"
352
+ || entry.id.length === 0
353
+ || typeof entry.cwd !== "string"
354
+ || entry.cwd.length === 0
355
+ || typeof entry.executable !== "string"
356
+ || entry.executable.length === 0
357
+ || !Array.isArray(entry.argv)
358
+ || entry.argv.some((argument) => typeof argument !== "string" || argument.length === 0)
359
+ ) {
360
+ issues.push(`profile_command_invalid:${profile.id ?? "unknown"}`);
361
+ break;
362
+ }
363
+ try {
364
+ safeIdentifier(entry.id, "profile command id");
365
+ } catch {
366
+ issues.push(`profile_command_id_invalid:${profile.id}:${entry.id}`);
367
+ }
368
+ try {
369
+ safeRelativePath(entry.cwd, "profile command cwd", { allowDot: true });
370
+ } catch {
371
+ issues.push(`profile_command_cwd_invalid:${profile.id}:${entry.id}`);
372
+ }
373
+ if (commandIds.has(entry.id)) {
374
+ issues.push(`duplicate_profile_command_id:${entry.id}`);
375
+ } else {
376
+ commandIds.add(entry.id);
377
+ }
378
+ }
379
+ }
380
+ }
381
+ }
382
+ if (issues.length > 0) return [...new Set(issues)].sort();
383
+
384
+ try {
385
+ const changedPaths = config.profiles.flatMap((profile) =>
386
+ profile.paths.map(representativePath));
387
+ resolveProfileImpactDetailed({ changedPaths, profiles: config.profiles });
388
+ } catch (error) {
389
+ issues.push(error instanceof Error ? error.message : String(error));
390
+ }
391
+
392
+ for (const profile of config.profiles) {
393
+ for (const requiredId of profile.requiresProfileIds ?? []) {
394
+ if (!ids.has(requiredId)) {
395
+ issues.push(`required_profile_missing:${profile.id}:${requiredId}`);
396
+ }
397
+ }
398
+ }
399
+ const actionable = config.profiles.filter((profile) =>
400
+ (profile.role ?? "primary") === "primary"
401
+ && Array.isArray(profile.commands)
402
+ && profile.commands.length > 0);
403
+ if (actionable.length === 0) issues.push("actionable_primary_profile_missing");
404
+ const explicitPrimaryIds = actionable
405
+ .filter((profile) => profile.primary === true)
406
+ .map((profile) => profile.id)
407
+ .sort();
408
+ if (explicitPrimaryIds.length > 1) {
409
+ issues.push(`ambiguous_primary_profile:${explicitPrimaryIds.join(",")}`);
410
+ } else if (explicitPrimaryIds.length === 0 && actionable.length > 1) {
411
+ issues.push(`ambiguous_primary_profile:${actionable.map((profile) => profile.id).sort().join(",")}`);
412
+ }
413
+ return [...new Set(issues)].sort();
414
+ }
415
+
416
+ export function validateProfiles({ workspaceRoot } = {}) {
417
+ const root = resolveBoundedWorkspaceRoot(workspaceRoot);
418
+ const issues = [];
419
+ let profileIds = [];
420
+ try {
421
+ const config = readWorkspaceJsonBounded(
422
+ root,
423
+ path.join(".owlcoda", "runkit", "profiles.json"),
424
+ );
425
+ profileIds = Array.isArray(config.profiles)
426
+ ? config.profiles
427
+ .map((profile) => profile?.id)
428
+ .filter((id) => typeof id === "string")
429
+ .sort()
430
+ : [];
431
+ issues.push(...validateProfileDocument(config));
432
+ if (issues.length === 0) {
433
+ const detected = detectProfiles({ workspaceRoot: root });
434
+ const detectedPaths = detected.candidates.flatMap((profile) =>
435
+ profile.paths.map(representativePath));
436
+ if (detectedPaths.length > 0) {
437
+ const impact = resolveProfileImpactDetailed({
438
+ changedPaths: detectedPaths,
439
+ profiles: config.profiles,
440
+ });
441
+ issues.push(...impact.uncoveredPaths.map((value) =>
442
+ `uncovered_detected_surface:${value}`));
443
+ issues.push(...impact.warnings
444
+ .filter((warning) => warning.startsWith("supporting_only_match:"))
445
+ .map((warning) => `detected_surface_${warning}`));
446
+ }
447
+ }
448
+ } catch (error) {
449
+ issues.push(error instanceof Error ? error.message : String(error));
450
+ }
451
+ const valid = issues.length === 0;
452
+ return {
453
+ schemaVersion: "OwlCodaRunKitProfilesValidationV1",
454
+ status: valid ? "valid" : "invalid",
455
+ valid,
456
+ exitCode: valid ? 0 : 2,
457
+ profileIds,
458
+ issues: [...new Set(issues)].sort(),
459
+ authorizationGranted: false,
460
+ };
461
+ }
462
+
463
+ export function resolveProfilesImpact({ workspaceRoot, changedPaths } = {}) {
464
+ const root = resolveBoundedWorkspaceRoot(workspaceRoot);
465
+ const config = readWorkspaceJsonBounded(
466
+ root,
467
+ path.join(".owlcoda", "runkit", "profiles.json"),
468
+ );
469
+ const documentIssues = validateProfileDocument(config);
470
+ if (documentIssues.length > 0) {
471
+ return {
472
+ schemaVersion: "OwlCodaRunKitProfileImpactV1",
473
+ status: "profile_impact_blocked",
474
+ valid: false,
475
+ exitCode: 2,
476
+ changedPaths: [...new Set(changedPaths)].sort(),
477
+ decision: "invalid_profiles",
478
+ primaryProfileId: null,
479
+ directProfileIds: [],
480
+ transitiveProfileIds: [],
481
+ supportingProfileIds: [],
482
+ selectedProfileIds: [],
483
+ uncoveredPaths: [],
484
+ warnings: [],
485
+ issues: documentIssues,
486
+ authorizationGranted: false,
487
+ };
488
+ }
489
+ const impact = resolveProfileImpactDetailed({
490
+ changedPaths,
491
+ profiles: config.profiles,
492
+ });
493
+ const issues = [
494
+ ...impact.uncoveredPaths.map((value) => `uncovered_changed_path:${value}`),
495
+ ...impact.warnings.filter((warning) =>
496
+ warning.startsWith("supporting_only_match:")
497
+ || warning.startsWith("ambiguous_primary_profile:")),
498
+ ];
499
+ const valid = impact.decision === "targeted_profiles" && issues.length === 0;
500
+ return {
501
+ schemaVersion: "OwlCodaRunKitProfileImpactV1",
502
+ status: valid ? "profile_impact_resolved" : "profile_impact_blocked",
503
+ valid,
504
+ exitCode: valid ? 0 : 2,
505
+ changedPaths: [...new Set(changedPaths)].sort(),
506
+ ...impact,
507
+ issues: [...new Set(issues)].sort(),
508
+ authorizationGranted: false,
509
+ };
510
+ }
@@ -21,6 +21,45 @@ const RELEASE_EVIDENCE_FIELDS = Object.freeze([
21
21
  "version",
22
22
  ]);
23
23
 
24
+ const STORED_EVIDENCE_FIELDS = Object.freeze([
25
+ "authorizationGranted",
26
+ "authority",
27
+ "decision",
28
+ "exactSpec",
29
+ "installedBinding",
30
+ "packageName",
31
+ "registryRelease",
32
+ "schemaVersion",
33
+ "status",
34
+ "version",
35
+ ]);
36
+
37
+ const STORED_RELEASE_FIELDS = Object.freeze([
38
+ "integrity",
39
+ "packageName",
40
+ "registry",
41
+ "shasum",
42
+ "tarballUrl",
43
+ "version",
44
+ ]);
45
+
46
+ const STORED_INSTALL_FIELDS = Object.freeze([
47
+ "integrity",
48
+ "packageName",
49
+ "requestedSpec",
50
+ "resolved",
51
+ "version",
52
+ ]);
53
+
54
+ function isRecord(value) {
55
+ return typeof value === "object" && value !== null && !Array.isArray(value);
56
+ }
57
+
58
+ function hasExactFields(value, fields) {
59
+ return isRecord(value)
60
+ && Object.keys(value).sort().join("\n") === [...fields].sort().join("\n");
61
+ }
62
+
24
63
  function decision(policy, issueCodes) {
25
64
  return {
26
65
  schemaVersion: "OwlCodaRunKitRegistryAdoptionDecisionV1",
@@ -32,12 +71,12 @@ function decision(policy, issueCodes) {
32
71
  };
33
72
  }
34
73
 
35
- function expectedTarballUrl(policy) {
36
- const tarballName = policy.packageName.split("/").at(-1);
37
- return `${OFFICIAL_NPM_REGISTRY}/${policy.packageName}/-/${tarballName}-${policy.candidateVersion}.tgz`;
74
+ export function expectedRegistryTarballUrl(packageName, version) {
75
+ const tarballName = packageName.split("/").at(-1);
76
+ return `${OFFICIAL_NPM_REGISTRY}/${packageName}/-/${tarballName}-${version}.tgz`;
38
77
  }
39
78
 
40
- function validSha512Integrity(value) {
79
+ export function isCanonicalSha512Integrity(value) {
41
80
  if (typeof value !== "string") return false;
42
81
  const match = /^sha512-([A-Za-z0-9+/]+={0,2})$/.exec(value);
43
82
  if (!match) return false;
@@ -45,13 +84,88 @@ function validSha512Integrity(value) {
45
84
  return bytes.length === 64 && bytes.toString("base64") === match[1];
46
85
  }
47
86
 
87
+ export function validateStoredRegistryAdoptionEvidence({
88
+ evidence,
89
+ packageName,
90
+ version,
91
+ localInstall,
92
+ }) {
93
+ const issues = [];
94
+ const exactSpec = `${packageName}@${version}`;
95
+ const expectedTarball = expectedRegistryTarballUrl(packageName, version);
96
+ if (
97
+ !hasExactFields(evidence, STORED_EVIDENCE_FIELDS)
98
+ || evidence.schemaVersion !== "OwlCodaRunKitRegistryAdoptionEvidenceV1"
99
+ || evidence.status !== "adopted"
100
+ || evidence.decision !== "ELIGIBLE"
101
+ || evidence.packageName !== packageName
102
+ || evidence.version !== version
103
+ || evidence.exactSpec !== exactSpec
104
+ || evidence.authorizationGranted !== false
105
+ ) {
106
+ issues.push("adoption_evidence_invalid");
107
+ }
108
+ const release = evidence?.registryRelease;
109
+ if (
110
+ !hasExactFields(release, STORED_RELEASE_FIELDS)
111
+ || release.registry !== OFFICIAL_NPM_REGISTRY
112
+ || release.packageName !== packageName
113
+ || release.version !== version
114
+ || !/^[0-9a-f]{40}$/i.test(release.shasum)
115
+ || !isCanonicalSha512Integrity(release.integrity)
116
+ || release.tarballUrl !== expectedTarball
117
+ ) {
118
+ issues.push("adoption_registry_binding_invalid");
119
+ }
120
+ const installed = evidence?.installedBinding;
121
+ if (
122
+ !hasExactFields(installed, STORED_INSTALL_FIELDS)
123
+ || installed.requestedSpec !== exactSpec
124
+ || installed.packageName !== packageName
125
+ || installed.version !== version
126
+ || installed.integrity !== release?.integrity
127
+ || installed.resolved !== expectedTarball
128
+ ) {
129
+ issues.push("adoption_installed_binding_invalid");
130
+ }
131
+ if (
132
+ !hasExactFields(evidence?.authority, ["foreignProjectWrite", "git", "release"])
133
+ || evidence.authority.git !== false
134
+ || evidence.authority.release !== false
135
+ || evidence.authority.foreignProjectWrite !== false
136
+ ) {
137
+ issues.push("adoption_authority_invalid");
138
+ }
139
+ if (
140
+ localInstall !== undefined
141
+ && (
142
+ localInstall.status !== "bound"
143
+ || localInstall.requestedSpec !== exactSpec
144
+ || localInstall.packageName !== packageName
145
+ || localInstall.version !== version
146
+ || localInstall.integrity !== release?.integrity
147
+ || localInstall.resolved !== expectedTarball
148
+ )
149
+ ) {
150
+ issues.push("adoption_local_install_drift");
151
+ }
152
+ const issueCodes = [...new Set(issues)].sort();
153
+ return {
154
+ valid: issueCodes.length === 0,
155
+ issueCodes,
156
+ };
157
+ }
158
+
48
159
  function validReleaseEvidence(policy, release) {
49
160
  if (
50
161
  policy.registry !== OFFICIAL_NPM_REGISTRY
51
162
  || 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)
163
+ || !/^[0-9a-f]{40}$/i.test(release.shasum)
164
+ || !isCanonicalSha512Integrity(release.integrity)
165
+ || release.tarballUrl !== expectedRegistryTarballUrl(
166
+ policy.packageName,
167
+ policy.candidateVersion,
168
+ )
55
169
  ) {
56
170
  return false;
57
171
  }