@ontrails/trails 1.0.0-beta.43 → 1.0.0-beta.45

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # trails
2
2
 
3
+ ## 1.0.0-beta.45
4
+
5
+ ### Patch Changes
6
+
7
+ - [`f1bd093`](https://github.com/outfitter-dev/trails/commit/f1bd09395fcf81db0bcb8657030288877c2e26e6): Recognize conditional, aliased, and parenthesized Result provenance while invalidating provenance after reassignment across the implementation-return and redundant-error-wrap rules.
8
+ - [`872a815`](https://github.com/outfitter-dev/trails/commit/872a815243cae63fae5b16022f102d125bc78ac5): Require strict registry checks to prove exact-version consumer availability instead of trusting package access or dist-tags alone.
9
+
10
+ ## 1.0.0-beta.44
11
+
12
+ ### Patch Changes
13
+
14
+ - [`b1fbe57`](https://github.com/outfitter-dev/trails/commit/b1fbe574e6f44d1fecb5e3a000270955c0a77b7b): Publish Bun-validated package tarballs through an npm trusted-publishing adapter
15
+ binding, add exact repository metadata for each public workspace package, and
16
+ correct the native Bun release descriptor to its pack-only runtime boundary.
17
+
3
18
  ## 1.0.0-beta.43
4
19
 
5
20
  ### Minor Changes
package/package.json CHANGED
@@ -1,6 +1,11 @@
1
1
  {
2
2
  "name": "@ontrails/trails",
3
- "version": "1.0.0-beta.43",
3
+ "version": "1.0.0-beta.45",
4
+ "repository": {
5
+ "type": "git",
6
+ "url": "git+https://github.com/outfitter-dev/trails.git",
7
+ "directory": "apps/trails"
8
+ },
4
9
  "bin": {
5
10
  "trails": "./bin/trails.ts"
6
11
  },
@@ -27,25 +32,25 @@
27
32
  },
28
33
  "dependencies": {
29
34
  "@clack/prompts": "^1.1.0",
30
- "@ontrails/adapter-kit": "^1.0.0-beta.43",
31
- "@ontrails/cli": "^1.0.0-beta.43",
32
- "@ontrails/commander": "^1.0.0-beta.43",
33
- "@ontrails/config": "^1.0.0-beta.43",
34
- "@ontrails/core": "^1.0.0-beta.43",
35
- "@ontrails/http": "^1.0.0-beta.43",
36
- "@ontrails/mcp": "^1.0.0-beta.43",
37
- "@ontrails/observability": "^1.0.0-beta.43",
38
- "@ontrails/permits": "^1.0.0-beta.43",
39
- "@ontrails/regrade": "^1.0.0-beta.43",
40
- "@ontrails/source": "^1.0.0-beta.43",
41
- "@ontrails/topography": "^1.0.0-beta.43",
42
- "@ontrails/warden": "^1.0.0-beta.43",
35
+ "@ontrails/adapter-kit": "^1.0.0-beta.45",
36
+ "@ontrails/cli": "^1.0.0-beta.45",
37
+ "@ontrails/commander": "^1.0.0-beta.45",
38
+ "@ontrails/config": "^1.0.0-beta.45",
39
+ "@ontrails/core": "^1.0.0-beta.45",
40
+ "@ontrails/http": "^1.0.0-beta.45",
41
+ "@ontrails/mcp": "^1.0.0-beta.45",
42
+ "@ontrails/observability": "^1.0.0-beta.45",
43
+ "@ontrails/permits": "^1.0.0-beta.45",
44
+ "@ontrails/regrade": "^1.0.0-beta.45",
45
+ "@ontrails/source": "^1.0.0-beta.45",
46
+ "@ontrails/topography": "^1.0.0-beta.45",
47
+ "@ontrails/warden": "^1.0.0-beta.45",
43
48
  "commander": "^14.0.3",
44
49
  "typescript": "^5.9.3",
45
50
  "zod": "^4.3.5"
46
51
  },
47
52
  "devDependencies": {
48
- "@ontrails/cloudflare": "^1.0.0-beta.43",
49
- "@ontrails/testing": "^1.0.0-beta.43"
53
+ "@ontrails/cloudflare": "^1.0.0-beta.45",
54
+ "@ontrails/testing": "^1.0.0-beta.45"
50
55
  }
51
56
  }
@@ -27,13 +27,32 @@ export interface ReleaseBindingDescriptor {
27
27
  readonly runtime: string;
28
28
  }
29
29
 
30
- export const nativeBunReleaseBinding = {
30
+ export const nativeBunPackBinding = {
31
31
  boundary: 'trails-owned',
32
- capabilities: ['pack-check', 'publish', 'registry-preflight'],
32
+ capabilities: ['pack-check'],
33
33
  description:
34
- 'Built-in Bun release binding for Trails-owned package pack checks, npm registry preflight, and lockstep package publication.',
34
+ 'Native Bun release binding for Trails-owned package packing and validation.',
35
35
  id: 'release.binding.native-bun',
36
36
  kind: 'native',
37
37
  placement: 'same-package',
38
38
  runtime: 'bun',
39
39
  } satisfies ReleaseBindingDescriptor;
40
+
41
+ /**
42
+ * @deprecated Use `nativeBunPackBinding`. This alias preserves the exported
43
+ * name while correcting its descriptor to the Bun-owned pack boundary.
44
+ */
45
+ export const nativeBunReleaseBinding = Object.freeze({
46
+ ...nativeBunPackBinding,
47
+ });
48
+
49
+ export const npmReleaseAdapterBinding = {
50
+ boundary: 'foreign',
51
+ capabilities: ['publish', 'registry-preflight'],
52
+ description:
53
+ 'Same-package npm adapter binding for trusted publication, registry preflight, and lockstep recovery.',
54
+ id: 'release.binding.npm',
55
+ kind: 'adapter',
56
+ placement: 'same-package',
57
+ runtime: 'npm',
58
+ } satisfies ReleaseBindingDescriptor;
@@ -1,5 +1,7 @@
1
1
  export {
2
+ nativeBunPackBinding,
2
3
  nativeBunReleaseBinding,
4
+ npmReleaseAdapterBinding,
3
5
  releaseBindingCapabilityValues,
4
6
  releaseBindingKindValues,
5
7
  releaseBindingPlacementValues,
@@ -55,8 +57,12 @@ export {
55
57
  type ReleaseRuleInput,
56
58
  } from './config.js';
57
59
  export {
60
+ createNpmPublishCommand,
58
61
  findPackedFirstPartyDependencyMismatches,
62
+ publicationActionForRegistryState,
59
63
  runNativeBunPublishCli,
64
+ trustedPublishingPreflightErrors,
65
+ unsupportedPublishLifecycleScripts,
60
66
  type NativeBunPublishOptions,
61
67
  type NativeBunPublishPackageJson,
62
68
  type NativeBunPublishWorkspace,
@@ -79,6 +85,7 @@ export {
79
85
  checkRegistryPosture,
80
86
  classifyPackageRegistryState,
81
87
  discoverRegistryWorkspaces,
88
+ factsFromRegistryResult,
82
89
  formatDistTagSummary,
83
90
  npmRegistryVersionView,
84
91
  npmRegistryView,
@@ -1,6 +1,6 @@
1
1
  /* oxlint-disable eslint-plugin-jest/require-hook, max-statements, func-style -- release script with module-level flow */
2
2
  /**
3
- * Native Bun release binding for public `@ontrails/*` workspace publication.
3
+ * Built-in release flow for public `@ontrails/*` workspace publication.
4
4
  *
5
5
  * Auto-discovers workspaces from the root `package.json` `workspaces` field,
6
6
  * topo-sorts them by `workspace:` dependency edges, enforces manifest-range
@@ -14,6 +14,15 @@ import { mkdtemp, readdir, rm } from 'node:fs/promises';
14
14
  import { tmpdir } from 'node:os';
15
15
  import { join, relative, resolve } from 'node:path';
16
16
 
17
+ import {
18
+ checkRegistryPosture,
19
+ classifyPackageRegistryState,
20
+ factsFromRegistryResult,
21
+ npmRegistryVersionProofView,
22
+ npmRegistryView,
23
+ } from './native-bun-registry.js';
24
+ import type { PackageRegistryState } from './native-bun-registry.js';
25
+
17
26
  const REPO_ROOT = resolve(process.cwd());
18
27
 
19
28
  /** ANSI color helpers, disabled when stdout is not a TTY or `NO_COLOR` is set. */
@@ -34,6 +43,7 @@ export interface NativeBunPublishOptions {
34
43
  readonly tag: string | undefined;
35
44
  readonly otp: string | undefined;
36
45
  readonly only: readonly string[] | undefined;
46
+ readonly trustedPublishing?: boolean;
37
47
  }
38
48
 
39
49
  /** Minimal shape of a workspace `package.json` we care about. */
@@ -45,6 +55,14 @@ export interface NativeBunPublishPackageJson {
45
55
  devDependencies?: Record<string, string>;
46
56
  peerDependencies?: Record<string, string>;
47
57
  optionalDependencies?: Record<string, string>;
58
+ scripts?: Record<string, string>;
59
+ repository?:
60
+ | string
61
+ | {
62
+ directory?: string;
63
+ type?: string;
64
+ url?: string;
65
+ };
48
66
  }
49
67
 
50
68
  type DependencyField =
@@ -60,6 +78,8 @@ export interface NativeBunPublishWorkspace {
60
78
  readonly path: string;
61
79
  readonly isPrivate: boolean;
62
80
  readonly workspaceDeps: readonly string[];
81
+ readonly repository?: NativeBunPublishPackageJson['repository'];
82
+ readonly publishLifecycleScripts?: readonly string[];
63
83
  }
64
84
 
65
85
  const DEPENDENCY_FIELDS: readonly DependencyField[] = [
@@ -68,19 +88,32 @@ const DEPENDENCY_FIELDS: readonly DependencyField[] = [
68
88
  'peerDependencies',
69
89
  'optionalDependencies',
70
90
  ];
91
+ const TRUSTED_REPOSITORY_URL =
92
+ 'git+https://github.com/outfitter-dev/trails.git';
93
+ const MINIMUM_TRUSTED_NODE = [22, 14, 0] as const;
94
+ const MINIMUM_TRUSTED_NPM = [11, 5, 1] as const;
95
+ const PUBLISH_ONLY_LIFECYCLE_SCRIPTS = [
96
+ 'prepublishOnly',
97
+ 'publish',
98
+ 'postpublish',
99
+ ] as const;
71
100
 
72
101
  const USAGE = `Usage: bun scripts/publish.ts [options]
73
102
 
74
- Publish all public @ontrails/* workspaces in dep order using \`bun publish\`.
103
+ Publish all public @ontrails/* workspaces in dependency order. Bun packs and
104
+ validates each tarball; npm publishes the resolved tarball to the registry.
75
105
 
76
106
  Options:
77
107
  --check Pre-publish verification only. Runs \`bun pm pack --dry-run\`
78
108
  (required so \`catalog:\` resolves) and asserts the packed
79
- manifest has no \`workspace:\` or \`catalog:\` ranges. No publishing.
109
+ manifest has no \`workspace:\` or \`catalog:\` ranges and
110
+ validates trusted-publishing repository metadata. No publishing.
80
111
  --dry-run Alias for --check.
81
112
  --tag <tag> npm dist-tag. Defaults to .changeset/pre.json tag when in
82
113
  prerelease mode, otherwise "latest".
83
114
  --otp <code> Two-factor code. Also read from BUN_PUBLISH_OTP.
115
+ --trusted-publishing Require GitHub Actions OIDC and npm trusted-publishing
116
+ prerequisites before attempting the first package.
84
117
  --only <name[,name]> Restrict to the named packages (repeatable). Useful for
85
118
  partial reruns after a mid-matrix failure.
86
119
  -h, --help Show this help and exit.
@@ -96,6 +129,7 @@ const parseArgs = (argv: readonly string[]): NativeBunPublishOptions => {
96
129
  let mode: NativeBunPublishOptions['mode'] = 'publish';
97
130
  let tag: string | undefined;
98
131
  let otp: string | undefined = process.env['BUN_PUBLISH_OTP'] || undefined;
132
+ let trustedPublishing = false;
99
133
  const only: string[] = [];
100
134
 
101
135
  const needsValue = (flag: string, value: string | undefined): string => {
@@ -118,6 +152,8 @@ const parseArgs = (argv: readonly string[]): NativeBunPublishOptions => {
118
152
  } else if (arg === '--otp') {
119
153
  i += 1;
120
154
  otp = needsValue('--otp', argv[i]);
155
+ } else if (arg === '--trusted-publishing') {
156
+ trustedPublishing = true;
121
157
  } else if (arg === '--only') {
122
158
  i += 1;
123
159
  const value = needsValue('--only', argv[i]);
@@ -143,6 +179,7 @@ const parseArgs = (argv: readonly string[]): NativeBunPublishOptions => {
143
179
  only: only.length > 0 ? only : undefined,
144
180
  otp,
145
181
  tag,
182
+ trustedPublishing,
146
183
  };
147
184
  };
148
185
 
@@ -334,6 +371,10 @@ const discoverWorkspaces = async (): Promise<NativeBunPublishWorkspace[]> => {
334
371
  isPrivate: pkg.private === true,
335
372
  name: pkg.name,
336
373
  path: dir,
374
+ publishLifecycleScripts: PUBLISH_ONLY_LIFECYCLE_SCRIPTS.filter(
375
+ (name) => pkg.scripts?.[name] !== undefined
376
+ ),
377
+ repository: pkg.repository,
337
378
  version: pkg.version ?? '0.0.0',
338
379
  workspaceDeps: collectWorkspaceDeps(pkg),
339
380
  });
@@ -433,23 +474,19 @@ const spawnCapture = async (
433
474
  return { exitCode, stdout };
434
475
  };
435
476
 
436
- /**
437
- * Pack a package to a temp dir and assert the resulting tarball's
438
- * `package/package.json` contains no `workspace:` or `catalog:` ranges.
439
- *
440
- * @throws When packing fails or forbidden ranges are found.
441
- */
442
- const assertManifestClean = async (
443
- ws: NativeBunPublishWorkspace,
444
- workspacesByName: ReadonlyMap<string, NativeBunPublishWorkspace>
445
- ): Promise<void> => {
446
- const tmp = await mkdtemp(join(tmpdir(), 'trails-publish-'));
477
+ interface PackedWorkspaceTarball {
478
+ readonly directory: string;
479
+ readonly path: string;
480
+ }
481
+
482
+ /** Pack through Bun so workspace and catalog ranges resolve before npm sees the manifest. */
483
+ const packWorkspaceTarball = async (
484
+ ws: NativeBunPublishWorkspace
485
+ ): Promise<PackedWorkspaceTarball> => {
486
+ const directory = await mkdtemp(join(tmpdir(), 'trails-publish-'));
447
487
  try {
448
- // Use `bun pm pack` so the packed manifest reflects what `bun publish`
449
- // will upload: workspace: and catalog: ranges are resolved the same way.
450
- // npm pack does not resolve `catalog:` and would produce false positives.
451
488
  const pack = await spawnCapture(
452
- ['bun', 'pm', 'pack', '--destination', tmp],
489
+ ['bun', 'pm', 'pack', '--destination', directory],
453
490
  ws.path
454
491
  );
455
492
  if (pack.exitCode !== 0) {
@@ -457,78 +494,133 @@ const assertManifestClean = async (
457
494
  `bun pm pack failed for ${ws.name} (exit ${pack.exitCode})`
458
495
  );
459
496
  }
460
- const tarEntries = await readdir(tmp);
461
- const tarName = tarEntries.find((n) =>
462
- typeof n === 'string' ? n.endsWith('.tgz') : String(n).endsWith('.tgz')
497
+ const entries = await readdir(directory);
498
+ const tarName = entries.find((name) =>
499
+ typeof name === 'string'
500
+ ? name.endsWith('.tgz')
501
+ : String(name).endsWith('.tgz')
463
502
  );
464
503
  if (!tarName) {
465
504
  throw new Error(`bun pm pack produced no tarball for ${ws.name}`);
466
505
  }
467
- const tarPath = join(tmp, String(tarName));
506
+ return { directory, path: join(directory, String(tarName)) };
507
+ } catch (error) {
508
+ await rm(directory, { force: true, recursive: true });
509
+ throw error;
510
+ }
511
+ };
468
512
 
469
- const extract = Bun.spawn(
470
- ['tar', '-xOf', tarPath, 'package/package.json'],
471
- { stderr: 'pipe', stdin: 'ignore', stdout: 'pipe' }
513
+ /** Assert that a Bun-packed tarball contains publishable dependency ranges. */
514
+ const assertPackedManifestClean = async (
515
+ ws: NativeBunPublishWorkspace,
516
+ workspacesByName: ReadonlyMap<string, NativeBunPublishWorkspace>,
517
+ tarPath: string
518
+ ): Promise<void> => {
519
+ const extract = Bun.spawn(['tar', '-xOf', tarPath, 'package/package.json'], {
520
+ stderr: 'pipe',
521
+ stdin: 'ignore',
522
+ stdout: 'pipe',
523
+ });
524
+ const [manifestText, tarStderr, extractExit] = await Promise.all([
525
+ new Response(extract.stdout).text(),
526
+ new Response(extract.stderr).text(),
527
+ extract.exited,
528
+ ]);
529
+ if (extractExit !== 0) {
530
+ const detail = tarStderr.trim() || '(no stderr output)';
531
+ throw new Error(
532
+ `tar extraction failed for ${ws.name} (exit ${extractExit}): ${detail}`
472
533
  );
473
- const [manifestText, tarStderr, extractExit] = await Promise.all([
474
- new Response(extract.stdout).text(),
475
- new Response(extract.stderr).text(),
476
- extract.exited,
477
- ]);
478
- if (extractExit !== 0) {
479
- const detail = tarStderr.trim() || '(no stderr output)';
480
- throw new Error(
481
- `tar extraction failed for ${ws.name} (exit ${extractExit}): ${detail}`
482
- );
483
- }
534
+ }
484
535
 
485
- let packedPackage: NativeBunPublishPackageJson;
486
- try {
487
- packedPackage = JSON.parse(manifestText) as NativeBunPublishPackageJson;
488
- } catch (error) {
489
- throw new Error(
490
- `Invalid packed package.json for ${ws.name}: ${(error as Error).message}`,
491
- { cause: error }
492
- );
493
- }
536
+ let packedPackage: NativeBunPublishPackageJson;
537
+ try {
538
+ packedPackage = JSON.parse(manifestText) as NativeBunPublishPackageJson;
539
+ } catch (error) {
540
+ throw new Error(
541
+ `Invalid packed package.json for ${ws.name}: ${(error as Error).message}`,
542
+ { cause: error }
543
+ );
544
+ }
494
545
 
495
- const offenders: string[] = [];
496
- for (const [lineNo, line] of manifestText.split('\n').entries()) {
497
- if (line.includes('"workspace:') || line.includes('"catalog:')) {
498
- offenders.push(` line ${lineNo + 1}: ${line.trim()}`);
499
- }
500
- }
501
- if (offenders.length > 0) {
502
- const relPath = relative(REPO_ROOT, ws.path);
503
- const hint =
504
- ' Hint: `bun publish` rewrites these at pack time. Verify the package was packed via bun, not npm.';
505
- throw new Error(
506
- `Packed manifest for ${ws.name} (${relPath}) contains forbidden ranges:\n${offenders.join('\n')}\n${hint}`
507
- );
546
+ const offenders: string[] = [];
547
+ for (const [lineNo, line] of manifestText.split('\n').entries()) {
548
+ if (line.includes('"workspace:') || line.includes('"catalog:')) {
549
+ offenders.push(` line ${lineNo + 1}: ${line.trim()}`);
508
550
  }
509
- const sourcePackage = await readJson<NativeBunPublishPackageJson>(
510
- join(ws.path, 'package.json')
551
+ }
552
+ if (offenders.length > 0) {
553
+ const relPath = relative(REPO_ROOT, ws.path);
554
+ throw new Error(
555
+ `Packed manifest for ${ws.name} (${relPath}) contains forbidden ranges:\n${offenders.join('\n')}\n Hint: publish the Bun-packed tarball instead of packing through npm.`
511
556
  );
512
- const mismatches = findPackedFirstPartyDependencyMismatches({
513
- packageName: ws.name,
514
- packagePath: ws.path,
515
- packedPackage,
516
- sourcePackage,
517
- workspacesByName,
518
- });
519
- if (mismatches.length > 0) {
520
- throw new Error(mismatches.join('\n'));
521
- }
522
- } finally {
523
- await rm(tmp, { force: true, recursive: true });
557
+ }
558
+ const sourcePackage = await readJson<NativeBunPublishPackageJson>(
559
+ join(ws.path, 'package.json')
560
+ );
561
+ const mismatches = findPackedFirstPartyDependencyMismatches({
562
+ packageName: ws.name,
563
+ packagePath: ws.path,
564
+ packedPackage,
565
+ sourcePackage,
566
+ workspacesByName,
567
+ });
568
+ if (mismatches.length > 0) {
569
+ throw new Error(mismatches.join('\n'));
524
570
  }
525
571
  };
526
572
 
573
+ export const unsupportedPublishLifecycleScripts = (
574
+ workspaces: readonly NativeBunPublishWorkspace[]
575
+ ): string[] =>
576
+ workspaces.flatMap((workspace) =>
577
+ workspace.isPrivate
578
+ ? []
579
+ : (workspace.publishLifecycleScripts ?? []).map(
580
+ (script) => `${workspace.name} defines unsupported ${script}`
581
+ )
582
+ );
583
+
584
+ /** Validate repository metadata required by npm trusted publishing. */
585
+ export const publishRepositoryMetadataErrors = (
586
+ workspaces: readonly NativeBunPublishWorkspace[]
587
+ ): string[] =>
588
+ workspaces.flatMap((workspace) => {
589
+ if (workspace.isPrivate) {
590
+ return [];
591
+ }
592
+ const { repository } = workspace;
593
+ const expectedDirectory = relative(REPO_ROOT, workspace.path);
594
+ if (
595
+ typeof repository !== 'string' &&
596
+ repository?.type === 'git' &&
597
+ repository.url === TRUSTED_REPOSITORY_URL &&
598
+ repository.directory === expectedDirectory
599
+ ) {
600
+ return [];
601
+ }
602
+ return [
603
+ `${workspace.name} must declare repository ${TRUSTED_REPOSITORY_URL} with directory ${expectedDirectory}`,
604
+ ];
605
+ });
606
+
527
607
  /** Run `--check` flow: pack dry-run plus manifest-range assertion per package. */
528
608
  const runCheck = async (
529
609
  workspaces: readonly NativeBunPublishWorkspace[],
530
610
  allWorkspaces: readonly NativeBunPublishWorkspace[]
531
611
  ): Promise<number> => {
612
+ const repositoryErrors = publishRepositoryMetadataErrors(workspaces);
613
+ if (repositoryErrors.length > 0) {
614
+ throw new Error(
615
+ `Package repository metadata check failed:\n${repositoryErrors.map((error) => `- ${error}`).join('\n')}`
616
+ );
617
+ }
618
+ const unsupportedLifecycle = unsupportedPublishLifecycleScripts(workspaces);
619
+ if (unsupportedLifecycle.length > 0) {
620
+ throw new Error(
621
+ `Tarball publication cannot honor publish-only lifecycle scripts:\n${unsupportedLifecycle.map((error) => `- ${error}`).join('\n')}`
622
+ );
623
+ }
532
624
  const workspacesByName = new Map(allWorkspaces.map((ws) => [ws.name, ws]));
533
625
  for (const ws of workspaces) {
534
626
  if (ws.isPrivate) {
@@ -545,7 +637,12 @@ const runCheck = async (
545
637
  return 1;
546
638
  }
547
639
  try {
548
- await assertManifestClean(ws, workspacesByName);
640
+ const packed = await packWorkspaceTarball(ws);
641
+ try {
642
+ await assertPackedManifestClean(ws, workspacesByName, packed.path);
643
+ } finally {
644
+ await rm(packed.directory, { force: true, recursive: true });
645
+ }
549
646
  } catch (error) {
550
647
  fail((error as Error).message);
551
648
  return 1;
@@ -557,36 +654,246 @@ const runCheck = async (
557
654
  return 0;
558
655
  };
559
656
 
657
+ const compareNumericVersion = (
658
+ version: string,
659
+ minimum: readonly [number, number, number]
660
+ ): number => {
661
+ const actual = version
662
+ .split('.')
663
+ .slice(0, 3)
664
+ .map((part) => Number.parseInt(part, 10));
665
+ if (actual.length !== 3 || actual.some((part) => Number.isNaN(part))) {
666
+ return -1;
667
+ }
668
+ for (let index = 0; index < minimum.length; index += 1) {
669
+ const delta = (actual[index] ?? 0) - (minimum[index] ?? 0);
670
+ if (delta !== 0) {
671
+ return delta;
672
+ }
673
+ }
674
+ return 0;
675
+ };
676
+
677
+ export const trustedPublishingPreflightErrors = ({
678
+ env,
679
+ nodeVersion,
680
+ npmVersion,
681
+ workspaces,
682
+ }: {
683
+ readonly env: Readonly<Record<string, string | undefined>>;
684
+ readonly nodeVersion: string;
685
+ readonly npmVersion: string;
686
+ readonly workspaces: readonly NativeBunPublishWorkspace[];
687
+ }): string[] => {
688
+ const errors: string[] = [];
689
+ if (env['GITHUB_ACTIONS'] !== 'true') {
690
+ errors.push('trusted publishing requires a GitHub Actions runner');
691
+ }
692
+ if (!env['ACTIONS_ID_TOKEN_REQUEST_URL']) {
693
+ errors.push('GitHub OIDC request URL is unavailable (id-token: write)');
694
+ }
695
+ if (!env['ACTIONS_ID_TOKEN_REQUEST_TOKEN']) {
696
+ errors.push('GitHub OIDC request token is unavailable (id-token: write)');
697
+ }
698
+ if (compareNumericVersion(nodeVersion, MINIMUM_TRUSTED_NODE) < 0) {
699
+ errors.push(
700
+ `trusted publishing requires Node >= ${MINIMUM_TRUSTED_NODE.join('.')}; found ${nodeVersion}`
701
+ );
702
+ }
703
+ if (compareNumericVersion(npmVersion, MINIMUM_TRUSTED_NPM) < 0) {
704
+ errors.push(
705
+ `trusted publishing requires npm >= ${MINIMUM_TRUSTED_NPM.join('.')}; found ${npmVersion}`
706
+ );
707
+ }
708
+ errors.push(...publishRepositoryMetadataErrors(workspaces));
709
+ return errors;
710
+ };
711
+
712
+ export const createNpmPublishCommand = ({
713
+ otp,
714
+ tag,
715
+ tarballPath,
716
+ }: {
717
+ readonly otp: string | undefined;
718
+ readonly tag: string;
719
+ readonly tarballPath: string;
720
+ }): string[] => {
721
+ const command = [
722
+ 'npm',
723
+ 'publish',
724
+ tarballPath,
725
+ '--access',
726
+ 'public',
727
+ '--tag',
728
+ tag,
729
+ ];
730
+ if (otp) {
731
+ command.push('--otp', otp);
732
+ }
733
+ return command;
734
+ };
735
+
736
+ export const publicationActionForRegistryState = (
737
+ state: PackageRegistryState,
738
+ trustedPublishing = false
739
+ ): 'block' | 'publish' | 'skip' => {
740
+ if (state.kind === 'complete') {
741
+ return 'skip';
742
+ }
743
+ if (state.kind === 'first-time-package') {
744
+ return trustedPublishing ? 'block' : 'publish';
745
+ }
746
+ if (state.kind === 'needs-publish') {
747
+ return 'publish';
748
+ }
749
+ return 'block';
750
+ };
751
+
752
+ const registryPublicationBlocker = (
753
+ workspace: NativeBunPublishWorkspace,
754
+ state: PackageRegistryState,
755
+ trustedPublishing: boolean
756
+ ): string => {
757
+ if (state.kind === 'first-time-package' && trustedPublishing) {
758
+ return `${workspace.name} is not published yet; bootstrap it with credentialed local tooling, configure its npm trusted publisher, then retry`;
759
+ }
760
+ if (state.kind === 'needs-tag-repair') {
761
+ return `${workspace.name}@${workspace.version} is published but the requested dist-tag points at ${state.currentTagVersion ?? '(missing)'}; repair it with credentialed npm tooling before retrying`;
762
+ }
763
+ if (state.kind === 'tag-points-ahead') {
764
+ return `${workspace.name} dist-tag points ahead at ${state.currentTagVersion}; refusing to publish ${workspace.version}`;
765
+ }
766
+ if (state.kind === 'registry-inaccessible') {
767
+ return `Could not inspect ${workspace.name}: ${state.error}`;
768
+ }
769
+ return `Could not determine whether ${workspace.name}@${workspace.version} is ready`;
770
+ };
771
+
772
+ const assertTrustedPublishingReady = async (
773
+ workspaces: readonly NativeBunPublishWorkspace[]
774
+ ): Promise<void> => {
775
+ const npmVersionResult = await spawnCapture(['npm', '--version'], REPO_ROOT);
776
+ if (npmVersionResult.exitCode !== 0) {
777
+ throw new Error('Could not read npm version for trusted publishing');
778
+ }
779
+ const nodeVersionResult = await spawnCapture(
780
+ ['node', '--version'],
781
+ REPO_ROOT
782
+ );
783
+ if (nodeVersionResult.exitCode !== 0) {
784
+ throw new Error('Could not read Node version for trusted publishing');
785
+ }
786
+ const errors = trustedPublishingPreflightErrors({
787
+ env: process.env,
788
+ nodeVersion: nodeVersionResult.stdout.trim().replace(/^v/, ''),
789
+ npmVersion: npmVersionResult.stdout.trim(),
790
+ workspaces,
791
+ });
792
+ if (errors.length > 0) {
793
+ throw new Error(
794
+ `Trusted publishing preflight failed:\n${errors.map((error) => `- ${error}`).join('\n')}`
795
+ );
796
+ }
797
+ success('Trusted publishing prerequisites are available');
798
+ };
799
+
560
800
  /** Run the actual publish flow sequentially. Aborts on first failure. */
561
801
  const runPublish = async (
562
802
  workspaces: readonly NativeBunPublishWorkspace[],
803
+ allWorkspaces: readonly NativeBunPublishWorkspace[],
563
804
  tag: string,
564
- otp: string | undefined
805
+ otp: string | undefined,
806
+ trustedPublishing: boolean
565
807
  ): Promise<number> => {
808
+ if (trustedPublishing) {
809
+ await assertTrustedPublishingReady(workspaces);
810
+ }
811
+ const unsupportedLifecycle = unsupportedPublishLifecycleScripts(workspaces);
812
+ if (unsupportedLifecycle.length > 0) {
813
+ throw new Error(
814
+ `Tarball publication cannot honor publish-only lifecycle scripts:\n${unsupportedLifecycle.map((error) => `- ${error}`).join('\n')}`
815
+ );
816
+ }
817
+ const publicWorkspaces = workspaces.filter(
818
+ (workspace) => !workspace.isPrivate
819
+ );
820
+ const registryResults = await checkRegistryPosture(
821
+ publicWorkspaces,
822
+ npmRegistryView,
823
+ npmRegistryVersionProofView,
824
+ tag
825
+ );
826
+ const registryStates = new Map<string, PackageRegistryState>();
827
+ const registryBlockers: string[] = [];
828
+ for (const workspace of publicWorkspaces) {
829
+ const registryResult = registryResults.find(
830
+ (result) => result.name === workspace.name
831
+ );
832
+ if (!registryResult) {
833
+ registryBlockers.push(
834
+ `Could not read registry posture for ${workspace.name}`
835
+ );
836
+ continue;
837
+ }
838
+ const state = classifyPackageRegistryState(
839
+ factsFromRegistryResult(registryResult)
840
+ );
841
+ registryStates.set(workspace.name, state);
842
+ if (
843
+ publicationActionForRegistryState(state, trustedPublishing) === 'block'
844
+ ) {
845
+ registryBlockers.push(
846
+ registryPublicationBlocker(workspace, state, trustedPublishing)
847
+ );
848
+ }
849
+ }
850
+ if (registryBlockers.length > 0) {
851
+ throw new Error(
852
+ `Registry preflight blocked publication before any package was published:\n${registryBlockers.map((error) => `- ${error}`).join('\n')}`
853
+ );
854
+ }
855
+ const workspacesByName = new Map(allWorkspaces.map((ws) => [ws.name, ws]));
566
856
  for (const ws of workspaces) {
567
857
  if (ws.isPrivate) {
568
858
  info(`Skipping ${ws.name} (private)`);
569
859
  continue;
570
860
  }
571
- info(`Publishing ${ws.name}@${ws.version}... (tag=${tag})`);
572
- const cmd: string[] = [
573
- 'bun',
574
- 'publish',
575
- '--access',
576
- 'public',
577
- '--tag',
578
- tag,
579
- ];
580
- if (otp) {
581
- cmd.push('--otp', otp);
861
+ const registryState = registryStates.get(ws.name);
862
+ if (!registryState) {
863
+ fail(`Registry preflight did not produce a state for ${ws.name}`);
864
+ return 1;
582
865
  }
583
- const code = await spawnInherit(cmd, ws.path);
584
- if (code !== 0) {
585
- fail(
586
- `Failed to publish ${ws.name} (exit ${code}); aborting remaining publishes`
866
+ const publicationAction = publicationActionForRegistryState(
867
+ registryState,
868
+ trustedPublishing
869
+ );
870
+ if (publicationAction === 'skip') {
871
+ success(
872
+ `${ws.name}@${ws.version} already published with ${tag} at target; continuing`
587
873
  );
874
+ continue;
875
+ }
876
+ if (publicationAction === 'block') {
877
+ fail(registryPublicationBlocker(ws, registryState, trustedPublishing));
588
878
  return 1;
589
879
  }
880
+ info(`Publishing ${ws.name}@${ws.version}... (tag=${tag})`);
881
+ const packed = await packWorkspaceTarball(ws);
882
+ try {
883
+ await assertPackedManifestClean(ws, workspacesByName, packed.path);
884
+ const code = await spawnInherit(
885
+ createNpmPublishCommand({ otp, tag, tarballPath: packed.path }),
886
+ ws.path
887
+ );
888
+ if (code !== 0) {
889
+ fail(
890
+ `Failed to publish ${ws.name} (exit ${code}); aborting remaining publishes`
891
+ );
892
+ return 1;
893
+ }
894
+ } finally {
895
+ await rm(packed.directory, { force: true, recursive: true });
896
+ }
590
897
  success(`${ws.name}@${ws.version} published`);
591
898
  }
592
899
  console.log('');
@@ -635,7 +942,13 @@ export const runNativeBunPublishCli = async (
635
942
  fail('Could not resolve a dist-tag. Pass --tag <tag> explicitly.');
636
943
  return 1;
637
944
  }
638
- return await runPublish(selected, tag, opts.otp);
945
+ return await runPublish(
946
+ selected,
947
+ all,
948
+ tag,
949
+ opts.otp,
950
+ opts.trustedPublishing ?? false
951
+ );
639
952
  } catch (error) {
640
953
  const msg = error instanceof Error ? error.message : String(error);
641
954
  fail(msg);
@@ -43,6 +43,7 @@ export type RegistryResult =
43
43
  readonly status: 'published';
44
44
  readonly version: string;
45
45
  readonly versionPublished: boolean | undefined;
46
+ readonly versionProof?: RegistryVersionProof | undefined;
46
47
  readonly workspaceVersion: string;
47
48
  }
48
49
  | {
@@ -82,18 +83,20 @@ export interface PackageRegistryFacts {
82
83
  readonly error?: string | undefined;
83
84
  }
84
85
 
86
+ /** Consumer-facing evidence for an exact package version. */
87
+ export type RegistryVersionProof =
88
+ | { readonly kind: 'consumer-pack'; readonly published: true }
89
+ | { readonly kind: 'exact-metadata'; readonly published: true }
90
+ | { readonly kind: 'unavailable'; readonly published: false };
91
+
85
92
  /**
86
93
  * Classify a package's registry state from two orthogonal facts — whether the
87
94
  * target version is published, and where the dist-tag points relative to it —
88
95
  * plus reachability. Members are mutually exclusive by construction.
89
96
  *
90
- * `versionPublished` is read strictly only in the behind-tag case: a behind tag
91
- * becomes a `needs-tag-repair` only when the target is known published (`true`);
92
- * `undefined` or `false` route to `needs-publish`, preserving the conservative
93
- * release-policy behavior. When the tag already points at the target, an
94
- * unprobed (`undefined`) state counts as published and yields `complete`, which
95
- * matches the policy `versionPublished ?? true` default. `undefined` means the
96
- * exact-version probe was not run, including policy inputs and compatibility
97
+ * `complete` requires affirmative consumer proof. A matching dist-tag cannot
98
+ * substitute for exact-version metadata or an equivalent package fetch.
99
+ * `undefined` means the consumer probe was not run, including compatibility
97
100
  * callers that supply an injected registry view without a version probe.
98
101
  */
99
102
  export const classifyPackageRegistryState = (
@@ -120,9 +123,9 @@ export const classifyPackageRegistryState = (
120
123
  return { currentTagVersion: expectedTagVersion, kind: 'tag-points-ahead' };
121
124
  }
122
125
  if (tagAtTarget) {
123
- return versionPublished === false
124
- ? { kind: 'needs-publish' }
125
- : { kind: 'complete' };
126
+ return versionPublished === true
127
+ ? { kind: 'complete' }
128
+ : { kind: 'needs-publish' };
126
129
  }
127
130
  if (versionPublished === true) {
128
131
  return { currentTagVersion: expectedTagVersion, kind: 'needs-tag-repair' };
@@ -131,13 +134,16 @@ export const classifyPackageRegistryState = (
131
134
  };
132
135
 
133
136
  /** Map a registry probe result into the classifier's fact shape. */
134
- const factsFromResult = (result: RegistryResult): PackageRegistryFacts => {
137
+ export const factsFromRegistryResult = (
138
+ result: RegistryResult
139
+ ): PackageRegistryFacts => {
135
140
  if (result.status === 'published') {
136
141
  return {
137
142
  expectedTagVersion: result.expectedTagVersion,
138
143
  status: 'published',
139
144
  targetVersion: result.workspaceVersion,
140
- versionPublished: result.versionPublished,
145
+ versionPublished:
146
+ result.versionProof?.published ?? result.versionPublished,
141
147
  };
142
148
  }
143
149
  if (result.status === 'inaccessible') {
@@ -165,7 +171,8 @@ Options:
165
171
  --tag <tag> Expected npm dist-tag. Defaults to .changeset/pre.json
166
172
  tag while in prerelease mode, otherwise "latest".
167
173
  --require-published Fail when any workspace package is missing from npm.
168
- Use after publication to verify every package exists.
174
+ Use after publication to require exact metadata or
175
+ equivalent consumer package-fetch proof.
169
176
  -h, --help Show this help and exit.
170
177
 
171
178
  Exit codes: 0 success, 1 registry posture failure, 2 arg-parse error.`;
@@ -459,12 +466,23 @@ export type RegistryVersionView = (
459
466
  version: string
460
467
  ) => Promise<boolean | undefined>;
461
468
 
469
+ /** Probe with evidence that distinguishes metadata from package fetch proof. */
470
+ export type RegistryVersionProofView = (
471
+ name: string,
472
+ version: string
473
+ ) => Promise<RegistryVersionProof>;
474
+
475
+ export type RegistryVersionProbeView = (
476
+ name: string,
477
+ version: string
478
+ ) => Promise<boolean | RegistryVersionProof | undefined>;
479
+
462
480
  const UNKNOWN_REGISTRY_VERSION_STATE: { readonly published?: boolean } = {};
463
481
  const unknownRegistryVersionView: RegistryVersionView = async () =>
464
482
  UNKNOWN_REGISTRY_VERSION_STATE.published;
465
483
 
466
- export const createNpmRegistryVersionView =
467
- (runNpm: NpmCommandRunner = runNpmCommand): RegistryVersionView =>
484
+ export const createNpmRegistryVersionProofView =
485
+ (runNpm: NpmCommandRunner = runNpmCommand): RegistryVersionProofView =>
468
486
  async (name, version) => {
469
487
  const { exitCode, stderr, stdout } = await runNpm([
470
488
  'view',
@@ -474,12 +492,14 @@ export const createNpmRegistryVersionView =
474
492
  ]);
475
493
 
476
494
  if (exitCode === 0) {
477
- return JSON.parse(stdout.trim()) === version;
495
+ return JSON.parse(stdout.trim()) === version
496
+ ? { kind: 'exact-metadata', published: true }
497
+ : { kind: 'unavailable', published: false };
478
498
  }
479
- if (isNpmExactVersionMissingOutput(stdout, stderr)) {
480
- return false;
481
- }
482
- if (!isNpmNotFoundOutput(stdout, stderr)) {
499
+ if (
500
+ !isNpmExactVersionMissingOutput(stdout, stderr) &&
501
+ !isNpmNotFoundOutput(stdout, stderr)
502
+ ) {
483
503
  throw new Error(
484
504
  stderr.trim() || `npm view failed for ${name}@${version}`
485
505
  );
@@ -496,21 +516,35 @@ export const createNpmRegistryVersionView =
496
516
  packResult.stdout,
497
517
  name,
498
518
  version
499
- );
519
+ )
520
+ ? { kind: 'consumer-pack', published: true }
521
+ : { kind: 'unavailable', published: false };
500
522
  }
501
523
  if (isNpmExactVersionMissingOutput(packResult.stdout, packResult.stderr)) {
502
- return false;
524
+ return { kind: 'unavailable', published: false };
503
525
  }
504
526
  if (isNpmNotFoundOutput(packResult.stdout, packResult.stderr)) {
505
- return false;
527
+ return { kind: 'unavailable', published: false };
506
528
  }
507
529
  throw new Error(
508
530
  packResult.stderr.trim() || `npm pack failed for ${name}@${version}`
509
531
  );
510
532
  };
511
533
 
534
+ export const createNpmRegistryVersionView = (
535
+ runNpm: NpmCommandRunner = runNpmCommand
536
+ ): RegistryVersionView => {
537
+ const proofView = createNpmRegistryVersionProofView(runNpm);
538
+ return async (name, version) => {
539
+ const proof = await proofView(name, version);
540
+ return proof.published;
541
+ };
542
+ };
543
+
512
544
  export const npmRegistryVersionView: RegistryVersionView =
513
545
  createNpmRegistryVersionView();
546
+ export const npmRegistryVersionProofView: RegistryVersionProofView =
547
+ createNpmRegistryVersionProofView();
514
548
 
515
549
  /** Run async tasks with a bounded number in flight, preserving input order. */
516
550
  const mapBounded = async <T, R>(
@@ -536,7 +570,7 @@ const mapBounded = async <T, R>(
536
570
  const checkWorkspaceRegistryPosture = async (
537
571
  workspace: RegistryWorkspace,
538
572
  view: RegistryView,
539
- versionView: RegistryVersionView,
573
+ versionView: RegistryVersionProbeView,
540
574
  expectedTag: string
541
575
  ): Promise<RegistryResult> => {
542
576
  try {
@@ -549,13 +583,19 @@ const checkWorkspaceRegistryPosture = async (
549
583
  };
550
584
  }
551
585
  const distTags = registry['dist-tags'] ?? {};
586
+ const versionProbe = await versionView(workspace.name, workspace.version);
587
+ const versionProof =
588
+ typeof versionProbe === 'object' ? versionProbe : undefined;
589
+ const versionPublished =
590
+ typeof versionProbe === 'object' ? versionProbe.published : versionProbe;
552
591
  return {
553
592
  distTags,
554
593
  expectedTagVersion: distTags[expectedTag],
555
594
  name: workspace.name,
556
595
  status: 'published',
557
596
  version: registry.version ?? '(unknown)',
558
- versionPublished: await versionView(workspace.name, workspace.version),
597
+ ...(versionProof === undefined ? {} : { versionProof }),
598
+ versionPublished,
559
599
  workspaceVersion: workspace.version,
560
600
  };
561
601
  } catch (error) {
@@ -569,14 +609,14 @@ const checkWorkspaceRegistryPosture = async (
569
609
  };
570
610
 
571
611
  type CheckRegistryPostureArgs =
572
- | readonly [versionView: RegistryVersionView, expectedTag: string]
612
+ | readonly [versionView: RegistryVersionProbeView, expectedTag: string]
573
613
  | readonly [expectedTag: string];
574
614
 
575
615
  const normalizeCheckRegistryPostureArgs = (
576
616
  args: CheckRegistryPostureArgs
577
617
  ): {
578
618
  readonly expectedTag: string;
579
- readonly versionView: RegistryVersionView;
619
+ readonly versionView: RegistryVersionProbeView;
580
620
  } => {
581
621
  if (args.length === 1) {
582
622
  return { expectedTag: args[0], versionView: unknownRegistryVersionView };
@@ -592,7 +632,7 @@ export function checkRegistryPosture(
592
632
  export function checkRegistryPosture(
593
633
  workspaces: readonly RegistryWorkspace[],
594
634
  view: RegistryView,
595
- versionView: RegistryVersionView,
635
+ versionView: RegistryVersionProbeView,
596
636
  expectedTag: string
597
637
  ): Promise<RegistryResult[]>;
598
638
  export async function checkRegistryPosture(
@@ -624,6 +664,19 @@ const normalizeRegistryCheckPhase = (
624
664
  return phaseOrRequirePublished ? 'published' : 'ready';
625
665
  };
626
666
 
667
+ const targetVersionFailure = (result: RegistryResult): string => {
668
+ if (result.status !== 'published') {
669
+ return 'is not published';
670
+ }
671
+ if (result.versionProof?.kind === 'unavailable') {
672
+ return 'lacks exact-version metadata and consumer pack proof';
673
+ }
674
+ if (result.versionPublished === undefined) {
675
+ return 'publish state was not probed';
676
+ }
677
+ return 'is not published';
678
+ };
679
+
627
680
  export const registryPostureErrors = (
628
681
  results: readonly RegistryResult[],
629
682
  expectedTag: string,
@@ -632,7 +685,7 @@ export const registryPostureErrors = (
632
685
  const phase = normalizeRegistryCheckPhase(phaseOrRequirePublished);
633
686
  const errors: string[] = [];
634
687
  for (const result of results) {
635
- const state = classifyPackageRegistryState(factsFromResult(result));
688
+ const state = classifyPackageRegistryState(factsFromRegistryResult(result));
636
689
  if (state.kind === 'registry-inaccessible') {
637
690
  errors.push(`${result.name}: registry probe failed: ${state.error}`);
638
691
  continue;
@@ -649,12 +702,8 @@ export const registryPostureErrors = (
649
702
  if (state.kind === 'first-time-package') {
650
703
  errors.push(`${result.name}: package is missing from the registry`);
651
704
  } else if (state.kind === 'needs-publish') {
652
- const targetState =
653
- result.status === 'published' && result.versionPublished === undefined
654
- ? 'publish state was not probed'
655
- : 'is not published';
656
705
  errors.push(
657
- `${result.name}: target version ${result.workspaceVersion} ${targetState}`
706
+ `${result.name}: target version ${result.workspaceVersion} ${targetVersionFailure(result)}`
658
707
  );
659
708
  } else if (state.kind === 'needs-tag-repair') {
660
709
  errors.push(
@@ -673,8 +722,18 @@ export const formatDistTagSummary = (
673
722
  );
674
723
 
675
724
  const formatTargetVersionStatus = (
725
+ proof: RegistryVersionProof | undefined,
676
726
  versionPublished: boolean | undefined
677
727
  ): string => {
728
+ if (proof?.kind === 'exact-metadata') {
729
+ return 'exact-version metadata available';
730
+ }
731
+ if (proof?.kind === 'consumer-pack') {
732
+ return 'exact-version metadata unavailable, consumer pack available';
733
+ }
734
+ if (proof?.kind === 'unavailable') {
735
+ return 'exact-version metadata and consumer pack unavailable';
736
+ }
678
737
  if (versionPublished === true) {
679
738
  return 'target version published';
680
739
  }
@@ -691,7 +750,10 @@ const printResults = (
691
750
  console.log(`Registry preflight for dist-tag "${expectedTag}"`);
692
751
  for (const result of results) {
693
752
  if (result.status === 'published') {
694
- const targetStatus = formatTargetVersionStatus(result.versionPublished);
753
+ const targetStatus = formatTargetVersionStatus(
754
+ result.versionProof,
755
+ result.versionPublished
756
+ );
695
757
  console.log(
696
758
  `✓ ${result.name}@${result.workspaceVersion}: package exists, ${targetStatus} (registry version ${result.version}, expected ${expectedTag}=${result.expectedTagVersion ?? 'missing'}, tags ${formatDistTagSummary(result.distTags)})`
697
759
  );
@@ -709,11 +771,14 @@ const normalizeRegistryPreflightViews = (
709
771
  view: RegistryView | undefined,
710
772
  versionView: RegistryVersionView | undefined
711
773
  ): {
712
- readonly versionView: RegistryVersionView;
774
+ readonly versionView: RegistryVersionProbeView;
713
775
  readonly view: RegistryView;
714
776
  } => {
715
777
  if (view === undefined) {
716
- return { versionView: npmRegistryVersionView, view: npmRegistryView };
778
+ return {
779
+ versionView: npmRegistryVersionProofView,
780
+ view: npmRegistryView,
781
+ };
717
782
  }
718
783
  return { versionView: versionView ?? unknownRegistryVersionView, view };
719
784
  };
@@ -999,13 +999,13 @@ const registryPackagesFromResults = (
999
999
  const readRegistryPackages = async (
1000
1000
  distTag: string
1001
1001
  ): Promise<readonly ReleasePolicyRegistryPackage[]> => {
1002
- const { checkRegistryPosture, npmRegistryView, npmRegistryVersionView } =
1002
+ const { checkRegistryPosture, npmRegistryView, npmRegistryVersionProofView } =
1003
1003
  await import('./native-bun-registry.js');
1004
1004
  const workspaces = await discoverRegistryWorkspaces(repoRoot);
1005
1005
  const results = await checkRegistryPosture(
1006
1006
  workspaces,
1007
1007
  npmRegistryView,
1008
- npmRegistryVersionView,
1008
+ npmRegistryVersionProofView,
1009
1009
  distTag
1010
1010
  );
1011
1011
  return registryPackagesFromResults(results);
@@ -3520,7 +3520,7 @@ export const regradeTrail = trail('regrade', {
3520
3520
  )
3521
3521
  : await runClassModeRegrade(input, rootDirResult.value, configScope);
3522
3522
  if (reportResult.isErr()) {
3523
- return Result.err(reportResult.error);
3523
+ return reportResult;
3524
3524
  }
3525
3525
  const outputResult = validateOutput(
3526
3526
  regradeReportOutput,