@lesliechan721/aw-plugin-core 0.1.0-beta.8 → 0.1.0-beta.9

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/dist/types.d.ts CHANGED
@@ -55,6 +55,11 @@ export interface BuildConfigManifestOverrides {
55
55
  export interface BuildConfigRuntimeExport {
56
56
  name: string;
57
57
  source: string;
58
+ requires: {
59
+ aw: {
60
+ capabilities: string[];
61
+ };
62
+ };
58
63
  }
59
64
  export interface BuildConfigImportReference {
60
65
  plugin: string;
@@ -250,6 +255,8 @@ export interface ArtifactRuntimeImportDefinition extends BuildConfigImportRefere
250
255
  providerPluginId: PluginId;
251
256
  providerVersion: PluginVersion;
252
257
  source: string;
258
+ requiredCapabilities: string[];
259
+ capabilitySetDigest: string;
253
260
  };
254
261
  }
255
262
  export interface PluginArtifactTargetDefinition {
@@ -331,7 +338,7 @@ export interface CatalogManifestEntry {
331
338
  artifactDigest: string;
332
339
  manifestDigest: string;
333
340
  }
334
- export interface CatalogManifestV5Entry extends CatalogManifestEntry {
341
+ export interface CatalogManifestV6Entry extends CatalogManifestEntry {
335
342
  name: PluginId;
336
343
  contentDigest: string;
337
344
  }
@@ -339,23 +346,20 @@ export interface CatalogMarketplaceIdentity {
339
346
  owner: MarketplaceOwner;
340
347
  interface: MarketplaceInterfaceMeta;
341
348
  }
342
- export type CatalogManifestV5Release = {
349
+ export type CatalogManifestV6Release = {
343
350
  state: 'development';
344
351
  } | {
345
352
  state: 'published';
346
- targetVersion: string;
347
- planId: string;
348
353
  sourceHead: string;
349
- contentDigest: string;
350
354
  };
351
- export interface CatalogManifestV5 {
352
- schemaVersion: 5;
355
+ export interface CatalogManifestV6 {
356
+ schemaVersion: 6;
353
357
  catalogVersion: string;
354
358
  channel: CatalogChannel;
355
359
  requires: PluginRequirements;
356
360
  marketplace: CatalogMarketplaceIdentity;
357
- release: CatalogManifestV5Release;
358
- entries: CatalogManifestV5Entry[];
361
+ release: CatalogManifestV6Release;
362
+ entries: CatalogManifestV6Entry[];
359
363
  }
360
364
  export interface PlatformPluginManifest {
361
365
  name: string;
@@ -1,6 +1,5 @@
1
- import { type PluginReleaseLedger } from './plugin-release.js';
2
1
  import type { ReservedImportRegistry } from './manifest-contract.js';
3
- import type { CatalogChannel, CatalogManifestV5, MarketplaceSourceMeta, PluginMeta } from './types.js';
2
+ import type { CatalogChannel, CatalogManifestV6, MarketplaceSourceMeta, PluginMeta } from './types.js';
4
3
  export declare const DEFAULT_CATALOG_WORKSPACE_VERSION = "0.1.0";
5
4
  export declare const DEFAULT_CATALOG_OUTPUT_PATH: string;
6
5
  export declare const DEFAULT_PUBLISHED_CATALOG_OUTPUT_PATH: string;
@@ -50,6 +49,10 @@ export declare function resolveCatalogWorkspaceOutputPath(options: {
50
49
  workspaceRoot: string;
51
50
  outputRoot: string;
52
51
  }>;
52
+ export declare function readCatalogWorkspaceMarker(workspaceRoot: string): Promise<{
53
+ markerPath: string;
54
+ workspaceIdentity: string;
55
+ }>;
53
56
  export declare function initializeCatalogWorkspace(options: CatalogWorkspaceInitOptions): Promise<{
54
57
  workspaceRoot: string;
55
58
  markerPath: string;
@@ -76,8 +79,7 @@ export declare function buildCatalogWorkspace(options: {
76
79
  }>;
77
80
  export declare function validatePublishedCatalogSnapshot(snapshotDirectory: string): Promise<{
78
81
  rootDir: string;
79
- manifest: CatalogManifestV5;
80
- ledger: PluginReleaseLedger;
82
+ manifest: CatalogManifestV6;
81
83
  snapshotDigest: string;
82
84
  }>;
83
85
  export declare function preparePublishedCatalogWorkspace(options: {
@@ -93,8 +95,6 @@ export declare function preparePublishedCatalogWorkspace(options: {
93
95
  channel: CatalogChannel;
94
96
  pluginNames: string[];
95
97
  snapshotDigest: string;
96
- contentDigest: string;
97
- planId: string;
98
98
  }>;
99
99
  export declare function buildCatalogWorkspacePlugin(options: {
100
100
  workspaceRoot: string;
@@ -111,20 +111,12 @@ export declare function buildCatalogWorkspacePlugin(options: {
111
111
  }>;
112
112
  export declare function validateCatalogSnapshotTransport(snapshotDirectory: string): Promise<{
113
113
  rootDir: string;
114
- manifest: CatalogManifestV5;
115
- ledger?: PluginReleaseLedger;
116
- snapshotDigest: string;
117
- }>;
118
- export declare function validatePublishedCatalogPublicationParent(snapshotDirectory: string): Promise<{
119
- rootDir: string;
120
- catalogVersion: string;
121
- ledger: PluginReleaseLedger;
114
+ manifest: CatalogManifestV6;
122
115
  snapshotDigest: string;
123
116
  }>;
124
117
  type CatalogSnapshotValidationResult = {
125
118
  rootDir: string;
126
- manifest: CatalogManifestV5;
127
- ledger?: PluginReleaseLedger;
119
+ manifest: CatalogManifestV6;
128
120
  snapshotDigest: string;
129
121
  };
130
122
  export declare function validateCatalogSnapshotPluginEntriesComplete(snapshotDirectory: string, pluginNames: readonly string[]): Promise<CatalogSnapshotValidationResult>;
package/dist/workspace.js CHANGED
@@ -2,10 +2,9 @@ import crypto from 'node:crypto';
2
2
  import path from 'node:path';
3
3
  import { isDeepStrictEqual } from 'node:util';
4
4
  import fs from 'fs-extra';
5
- import { assertCatalogVersion, assertStrictSemVer, compareStrictSemVer, MARKETPLACE_SOURCE_PATH, SOURCE_MANIFEST_PATH, createMarketplaceManifest, createMarketplaceManifestEntry, normalizeCatalogManifestBase, normalizeCatalogMarketplaceIdentity, normalizeCatalogManifestEntries, normalizeManifestDigestInput, readMarketplaceSourceMeta, readPluginManifest, validatePlatformManifestProjection, } from './manifest-contract.js';
5
+ import { assertCatalogVersion, assertStrictSemVer, compareStrictSemVer, parseStrictSemVer, MARKETPLACE_SOURCE_PATH, SOURCE_MANIFEST_PATH, createMarketplaceManifest, createMarketplaceManifestEntry, normalizeCatalogManifestBase, normalizeCatalogMarketplaceIdentity, normalizeCatalogManifestEntries, normalizeManifestDigestInput, readMarketplaceSourceMeta, readPluginManifest, validatePlatformManifestProjection, } from './manifest-contract.js';
6
6
  import { resolveSupportedPlatforms } from './platform-support.js';
7
7
  import { createAwPluginRequirementsReceipt, normalizeAwCapabilities, parseAwPluginRequirementsReceipt, } from './aw-capabilities.js';
8
- import { appendCatalogToPluginReleaseLedger, assertStablePluginVersion, PLUGIN_RELEASE_LEDGER_FILENAME, PLUGIN_RELEASE_LEDGER_SCHEMA_VERSION, resolveBetaPluginVersionOverrides, validatePluginReleaseLedgerAgainstManifest, } from './plugin-release.js';
9
8
  export const DEFAULT_CATALOG_WORKSPACE_VERSION = '0.1.0';
10
9
  export const DEFAULT_CATALOG_OUTPUT_PATH = path.join('.aw', 'catalog');
11
10
  export const DEFAULT_PUBLISHED_CATALOG_OUTPUT_PATH = path.join('.aw', 'publish', 'catalog');
@@ -140,9 +139,18 @@ function validateCatalogWorkspaceMarker(value) {
140
139
  }
141
140
  return { workspaceIdentity: marker.workspaceIdentity };
142
141
  }
143
- async function readCatalogWorkspaceMarker(workspaceRoot) {
142
+ export async function readCatalogWorkspaceMarker(workspaceRoot) {
144
143
  const markerPath = path.join(workspaceRoot, CATALOG_WORKSPACE_MARKER_FILENAME);
145
- const marker = validateCatalogWorkspaceMarker(await readRegularJson(markerPath, 'Catalog Workspace marker'));
144
+ let value;
145
+ try {
146
+ value = await readRegularJson(markerPath, 'Catalog Workspace marker');
147
+ }
148
+ catch (error) {
149
+ if (error instanceof SyntaxError)
150
+ throw new Error('Invalid Catalog Workspace marker.');
151
+ throw error;
152
+ }
153
+ const marker = validateCatalogWorkspaceMarker(value);
146
154
  return { markerPath, workspaceIdentity: marker.workspaceIdentity };
147
155
  }
148
156
  async function readRegularJson(filePath, label) {
@@ -400,22 +408,6 @@ function stableStringify(value) {
400
408
  function sha256(value) {
401
409
  return `sha256:${crypto.createHash('sha256').update(value).digest('hex')}`;
402
410
  }
403
- function publishedContentDigest(manifest) {
404
- return sha256(stableStringify({
405
- schemaIdentity: { schemaVersion: manifest.schemaVersion },
406
- marketplaceMetadata: {
407
- name: 'aw',
408
- owner: manifest.marketplace.owner,
409
- interface: manifest.marketplace.interface,
410
- },
411
- requires: manifest.requires,
412
- plugins: manifest.entries.map((entry) => ({
413
- name: entry.name,
414
- targetVersion: entry.version.replace(/-beta\.[1-9]\d*$/u, ''),
415
- contentDigest: entry.contentDigest,
416
- })).sort((left, right) => left.name.localeCompare(right.name)),
417
- }));
418
- }
419
411
  const SOURCE_IDENTITY_EXCLUDED_DIRECTORIES = new Set([
420
412
  '.git', '.aw', 'node_modules', 'evals', 'reports',
421
413
  ]);
@@ -558,7 +550,7 @@ async function validateBundleRuntimeRequirementsReceipts(plugin, pluginRoot) {
558
550
  }
559
551
  }
560
552
  }
561
- function normalizeCatalogRelease(raw, catalogVersion) {
553
+ function normalizeCatalogRelease(raw) {
562
554
  if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
563
555
  throw new Error('Invalid catalog manifest release: expected an object.');
564
556
  }
@@ -569,33 +561,16 @@ function normalizeCatalogRelease(raw, catalogVersion) {
569
561
  }
570
562
  return { state: 'development' };
571
563
  }
572
- const allowed = new Set(['state', 'targetVersion', 'planId', 'sourceHead', 'contentDigest']);
564
+ const allowed = new Set(['state', 'sourceHead']);
573
565
  if (release.state !== 'published' || Object.keys(release).some((key) => !allowed.has(key))) {
574
566
  throw new Error('Invalid published catalog release.');
575
567
  }
576
- const targetVersion = assertStrictSemVer(release.targetVersion, 'catalog release targetVersion');
577
- if (targetVersion.includes('-') || targetVersion.includes('+')) {
578
- throw new Error('Invalid catalog release targetVersion: expected a stable version.');
579
- }
580
- if (targetVersion !== catalogVersion.replace(/-beta\.[1-9]\d*$/u, '')) {
581
- throw new Error('Invalid catalog release targetVersion: must match catalogVersion.');
582
- }
583
- const digestPattern = /^sha256:[0-9a-f]{64}$/u;
584
- if (typeof release.planId !== 'string' || !digestPattern.test(release.planId)) {
585
- throw new Error('Invalid catalog release planId.');
586
- }
587
- if (typeof release.contentDigest !== 'string' || !digestPattern.test(release.contentDigest)) {
588
- throw new Error('Invalid catalog release contentDigest.');
589
- }
590
568
  if (typeof release.sourceHead !== 'string' || !/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/u.test(release.sourceHead)) {
591
569
  throw new Error('Invalid catalog release sourceHead.');
592
570
  }
593
571
  return {
594
572
  state: 'published',
595
- targetVersion,
596
- planId: release.planId,
597
573
  sourceHead: release.sourceHead,
598
- contentDigest: release.contentDigest,
599
574
  };
600
575
  }
601
576
  export async function buildCatalogWorkspace(options) {
@@ -643,10 +618,10 @@ export async function buildCatalogWorkspace(options) {
643
618
  .sort((left, right) => left.manifest.name.localeCompare(right.manifest.name))
644
619
  .map((plugin) => expectedCatalogEntry(plugin, plugin.rootDir)));
645
620
  const manifest = {
646
- schemaVersion: 5,
621
+ schemaVersion: 6,
647
622
  catalogVersion,
648
623
  channel,
649
- requires: { aw: { capabilities: ['aw.catalog-snapshot.v5'] } },
624
+ requires: { aw: { capabilities: ['aw.catalog-snapshot.v6'] } },
650
625
  marketplace: {
651
626
  owner: catalogWorkspace.metadata.owner,
652
627
  interface: catalogWorkspace.metadata.interface,
@@ -692,14 +667,77 @@ async function rewritePublishedPluginVersions(snapshotRoot, overrides) {
692
667
  }
693
668
  }
694
669
  }
670
+ function assertStablePluginVersion(version, fieldName) {
671
+ const parsed = parseStrictSemVer(version, fieldName);
672
+ if (parsed.prerelease.length > 0 || parsed.build.length > 0) {
673
+ throw new Error(`Invalid ${fieldName}: expected a stable SemVer without prerelease or build metadata.`);
674
+ }
675
+ return version;
676
+ }
677
+ function stableVersionBase(version, fieldName) {
678
+ const parsed = parseStrictSemVer(version, fieldName);
679
+ return `${parsed.major}.${parsed.minor}.${parsed.patch}`;
680
+ }
681
+ function resolvePublishedPluginVersions(options) {
682
+ const previousEntries = new Map(options.previous?.entries.map((entry) => [entry.name, entry]) ?? []);
683
+ const versions = {};
684
+ for (const plugin of options.plugins) {
685
+ const name = plugin.manifest.name;
686
+ const sourceVersion = assertStablePluginVersion(plugin.manifest.version, `${name} source version`);
687
+ const contentDigest = options.contentDigests.get(name);
688
+ if (!contentDigest)
689
+ throw new Error(`Missing release content digest for plugin ${JSON.stringify(name)}.`);
690
+ const previous = previousEntries.get(name);
691
+ if (!previous) {
692
+ versions[name] = options.channel === 'beta' ? `${sourceVersion}-beta.1` : sourceVersion;
693
+ continue;
694
+ }
695
+ const previousVersion = assertStrictSemVer(previous.version, `${name} previous published version`);
696
+ if (options.channel === 'latest') {
697
+ const comparison = compareStrictSemVer(sourceVersion, previousVersion);
698
+ if (comparison < 0) {
699
+ throw new Error(`Plugin ${name} source version ${sourceVersion} must not be lower than previous ${previousVersion}.`);
700
+ }
701
+ if (comparison === 0 && previous.contentDigest !== contentDigest) {
702
+ throw new Error(`Plugin ${name} version ${sourceVersion} maps to different content. Increase the source plugin version.`);
703
+ }
704
+ versions[name] = sourceVersion;
705
+ continue;
706
+ }
707
+ const previousBase = stableVersionBase(previousVersion, `${name} previous published version`);
708
+ const baseComparison = compareStrictSemVer(sourceVersion, previousBase);
709
+ if (baseComparison < 0) {
710
+ throw new Error(`Plugin ${name} source version ${sourceVersion} must not be lower than previous base ${previousBase}.`);
711
+ }
712
+ if (baseComparison > 0) {
713
+ versions[name] = `${sourceVersion}-beta.1`;
714
+ continue;
715
+ }
716
+ const parsedPrevious = parseStrictSemVer(previousVersion, `${name} previous published version`);
717
+ if (parsedPrevious.prerelease.length === 0) {
718
+ if (previous.contentDigest !== contentDigest) {
719
+ throw new Error(`Plugin ${name} stable version ${sourceVersion} maps to different content. Increase the source plugin version.`);
720
+ }
721
+ versions[name] = previousVersion;
722
+ continue;
723
+ }
724
+ if (parsedPrevious.prerelease.length !== 2
725
+ || parsedPrevious.prerelease[0] !== 'beta'
726
+ || !/^[1-9]\d*$/u.test(parsedPrevious.prerelease[1])) {
727
+ throw new Error(`Plugin ${name} previous version ${previousVersion} is not a stable or beta publication version.`);
728
+ }
729
+ versions[name] = previous.contentDigest === contentDigest
730
+ ? previousVersion
731
+ : `${sourceVersion}-beta.${Number(parsedPrevious.prerelease[1]) + 1}`;
732
+ }
733
+ return versions;
734
+ }
695
735
  export async function validatePublishedCatalogSnapshot(snapshotDirectory) {
696
736
  const validation = await validateCatalogSnapshotComplete(snapshotDirectory);
697
737
  if (validation.manifest.release.state !== 'published') {
698
738
  throw new Error('Published Catalog snapshot must have release.state published.');
699
739
  }
700
- if (!validation.ledger)
701
- throw new Error('Published Catalog snapshot must include a plugin release ledger.');
702
- return { ...validation, ledger: validation.ledger };
740
+ return validation;
703
741
  }
704
742
  export async function preparePublishedCatalogWorkspace(options) {
705
743
  if (!/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/u.test(options.sourceHead)) {
@@ -712,15 +750,11 @@ export async function preparePublishedCatalogWorkspace(options) {
712
750
  const catalogVersion = catalogWorkspace.metadata.catalog.version;
713
751
  const channel = deriveCatalogChannel(catalogVersion);
714
752
  const previous = options.previousSnapshotRoot
715
- ? await validatePublishedCatalogPublicationParent(options.previousSnapshotRoot)
753
+ ? await validatePublishedCatalogSnapshot(options.previousSnapshotRoot)
716
754
  : null;
717
- if (previous && compareStrictSemVer(catalogVersion, previous.catalogVersion) <= 0) {
718
- throw new Error(`Catalog version ${catalogVersion} must be higher than published ${previous.catalogVersion}.`);
755
+ if (previous && compareStrictSemVer(catalogVersion, previous.manifest.catalogVersion) <= 0) {
756
+ throw new Error(`Catalog version ${catalogVersion} must be higher than published ${previous.manifest.catalogVersion}.`);
719
757
  }
720
- const previousLedger = previous?.ledger ?? {
721
- schemaVersion: PLUGIN_RELEASE_LEDGER_SCHEMA_VERSION,
722
- records: [],
723
- };
724
758
  const temporaryRelative = path.join('.aw', 'publish', `.catalog-preparation-${crypto.randomUUID()}`);
725
759
  const finalRelative = options.outputPath
726
760
  ?? `${DEFAULT_PUBLISHED_CATALOG_OUTPUT_PATH}-v${catalogVersion}`;
@@ -735,13 +769,12 @@ export async function preparePublishedCatalogWorkspace(options) {
735
769
  temporaryRoot = built.outputRoot;
736
770
  const development = await validateCatalogSnapshotComplete(temporaryRoot);
737
771
  const contentDigests = new Map(development.manifest.entries.map((entry) => [entry.name, entry.contentDigest]));
738
- const overrides = channel === 'beta'
739
- ? resolveBetaPluginVersionOverrides({
740
- plugins: catalogWorkspace.plugins,
741
- contentDigests,
742
- ledger: previousLedger,
743
- })
744
- : Object.fromEntries(catalogWorkspace.plugins.map((plugin) => [plugin.manifest.name, plugin.manifest.version]));
772
+ const overrides = resolvePublishedPluginVersions({
773
+ channel,
774
+ plugins: catalogWorkspace.plugins,
775
+ contentDigests,
776
+ previous: previous?.manifest ?? null,
777
+ });
745
778
  await rewritePublishedPluginVersions(temporaryRoot, overrides);
746
779
  const bundledPlugins = await Promise.all(catalogWorkspace.plugins.map((plugin) => readPluginManifest(path.join(temporaryRoot, 'plugins', plugin.manifest.name), { mode: 'bundle' })));
747
780
  const entries = await Promise.all(bundledPlugins.sort((left, right) => left.manifest.name.localeCompare(right.manifest.name))
@@ -752,26 +785,14 @@ export async function preparePublishedCatalogWorkspace(options) {
752
785
  channel,
753
786
  entries,
754
787
  };
755
- const contentDigest = publishedContentDigest(manifestWithoutRelease);
756
788
  const manifest = {
757
789
  ...manifestWithoutRelease,
758
790
  release: {
759
791
  state: 'published',
760
- targetVersion: catalogVersion.replace(/-beta\.[1-9]\d*$/u, ''),
761
- planId: contentDigest,
762
792
  sourceHead: options.sourceHead,
763
- contentDigest,
764
793
  },
765
794
  };
766
- const ledger = appendCatalogToPluginReleaseLedger({
767
- previous: previousLedger,
768
- manifest,
769
- contentDigests,
770
- });
771
- await Promise.all([
772
- fs.writeFile(path.join(temporaryRoot, CATALOG_MANIFEST_FILENAME), `${JSON.stringify(manifest, null, 2)}\n`),
773
- fs.writeFile(path.join(temporaryRoot, PLUGIN_RELEASE_LEDGER_FILENAME), `${JSON.stringify(ledger, null, 2)}\n`),
774
- ]);
795
+ await fs.writeFile(path.join(temporaryRoot, CATALOG_MANIFEST_FILENAME), `${JSON.stringify(manifest, null, 2)}\n`);
775
796
  const prepared = await validatePublishedCatalogSnapshot(temporaryRoot);
776
797
  const resolved = await resolveCatalogWorkspaceOutputPath({
777
798
  workspaceRoot: catalogWorkspace.workspaceRoot,
@@ -795,8 +816,6 @@ export async function preparePublishedCatalogWorkspace(options) {
795
816
  channel,
796
817
  pluginNames: built.pluginNames,
797
818
  snapshotDigest: prepared.snapshotDigest,
798
- contentDigest,
799
- planId: contentDigest,
800
819
  };
801
820
  }
802
821
  finally {
@@ -922,7 +941,7 @@ async function digestDirectory(root) {
922
941
  await walk(root);
923
942
  return `sha256:${crypto.createHash('sha256').update(entries.join('\n')).digest('hex')}`;
924
943
  }
925
- async function validateCatalogSnapshotTransportInternal(snapshotDirectory, options = {}) {
944
+ async function validateCatalogSnapshotTransportInternal(snapshotDirectory) {
926
945
  const rootDir = await assertDirectoryWithoutSymlinks(snapshotDirectory, 'Catalog snapshot');
927
946
  const allowed = new Set([
928
947
  CATALOG_MANIFEST_FILENAME,
@@ -930,7 +949,6 @@ async function validateCatalogSnapshotTransportInternal(snapshotDirectory, optio
930
949
  '.agents',
931
950
  '.claude-plugin',
932
951
  'plugins',
933
- 'plugin-release-ledger.json',
934
952
  ]);
935
953
  const entries = await fs.readdir(rootDir);
936
954
  const unexpected = entries.filter((entry) => !allowed.has(entry));
@@ -970,19 +988,18 @@ async function validateCatalogSnapshotTransportInternal(snapshotDirectory, optio
970
988
  await readRegularJson(path.join(rootDir, '.agents', 'plugins', 'marketplace.json'), 'Codex marketplace');
971
989
  await readRegularJson(path.join(rootDir, '.claude-plugin', 'marketplace.json'), 'Claude marketplace');
972
990
  const snapshotDigest = await digestDirectory(rootDir);
973
- const raw = options.rawManifest
974
- ?? await fs.readJson(path.join(rootDir, CATALOG_MANIFEST_FILENAME));
991
+ const raw = await fs.readJson(path.join(rootDir, CATALOG_MANIFEST_FILENAME));
975
992
  const allowedManifestKeys = new Set([
976
993
  'schemaVersion', 'catalogVersion', 'channel', 'requires', 'marketplace', 'release', 'entries',
977
994
  ]);
978
995
  for (const key of Object.keys(raw))
979
996
  if (!allowedManifestKeys.has(key))
980
997
  throw new Error(`Invalid catalog manifest: unknown field ${JSON.stringify(key)}.`);
981
- if (raw.schemaVersion !== 5)
982
- throw new Error('Invalid catalog manifest schemaVersion: expected 5.');
983
- const base = normalizeCatalogManifestBase(raw, 'catalog manifest v5');
984
- if (!(base.requires.aw.capabilities ?? []).includes('aw.catalog-snapshot.v5')) {
985
- throw new Error('Invalid catalog manifest requires.aw.capabilities: expected aw.catalog-snapshot.v5.');
998
+ if (raw.schemaVersion !== 6)
999
+ throw new Error('Invalid catalog manifest schemaVersion: expected 6.');
1000
+ const base = normalizeCatalogManifestBase(raw, 'catalog manifest v6');
1001
+ if (!(base.requires.aw.capabilities ?? []).includes('aw.catalog-snapshot.v6')) {
1002
+ throw new Error('Invalid catalog manifest requires.aw.capabilities: expected aw.catalog-snapshot.v6.');
986
1003
  }
987
1004
  const marketplaceIdentity = normalizeCatalogMarketplaceIdentity(raw.marketplace);
988
1005
  if (!Array.isArray(raw.entries))
@@ -999,7 +1016,7 @@ async function validateCatalogSnapshotTransportInternal(snapshotDirectory, optio
999
1016
  delete copy.contentDigest;
1000
1017
  return copy;
1001
1018
  });
1002
- const normalizedEntries = normalizeCatalogManifestEntries(entriesWithoutContentDigest, base, 'catalog manifest v5')
1019
+ const normalizedEntries = normalizeCatalogManifestEntries(entriesWithoutContentDigest, base, 'catalog manifest v6')
1003
1020
  .map((entry, index) => ({ ...entry, contentDigest: contentDigests[index] }));
1004
1021
  const pluginNames = normalizedEntries.map((entry, index) => {
1005
1022
  if (typeof entry.name !== 'string')
@@ -1031,10 +1048,6 @@ async function validateCatalogSnapshotTransportInternal(snapshotDirectory, optio
1031
1048
  contentDigest: await calculateEntryDigest(pluginRoot, true),
1032
1049
  };
1033
1050
  for (const field of Object.keys(expectedDigests)) {
1034
- // V4 used a normalized Manifest v1 digest. The artifact digest still binds the raw manifest bytes,
1035
- // and the release ledger must still match the preserved v4 manifestDigest.
1036
- if (field === 'manifestDigest' && options.legacyV4PublicationParent)
1037
- continue;
1038
1051
  if (entry[field] !== expectedDigests[field]) {
1039
1052
  throw new Error(`Catalog entry ${field} for ${JSON.stringify(name)} does not match its plugin bundle.`);
1040
1053
  }
@@ -1083,9 +1096,9 @@ async function validateCatalogSnapshotTransportInternal(snapshotDirectory, optio
1083
1096
  if (!isDeepStrictEqual(normalizeMarketplaceOrder(claudeMarketplace), normalizeMarketplaceOrder(expectedClaude))) {
1084
1097
  throw new Error('Claude marketplace metadata or plugin inventory does not match the snapshot bundles.');
1085
1098
  }
1086
- const release = normalizeCatalogRelease(raw.release, base.catalogVersion);
1099
+ const release = normalizeCatalogRelease(raw.release);
1087
1100
  const manifest = {
1088
- schemaVersion: 5,
1101
+ schemaVersion: 6,
1089
1102
  catalogVersion: base.catalogVersion,
1090
1103
  channel: base.channel,
1091
1104
  requires: base.requires,
@@ -1093,157 +1106,11 @@ async function validateCatalogSnapshotTransportInternal(snapshotDirectory, optio
1093
1106
  release,
1094
1107
  entries: normalizedEntries,
1095
1108
  };
1096
- if (release.state === 'published') {
1097
- const contentDigest = publishedContentDigest(manifest);
1098
- if (release.contentDigest !== contentDigest) {
1099
- throw new Error('Published catalog release contentDigest does not match the snapshot.');
1100
- }
1101
- }
1102
- const hasLedger = entries.includes(PLUGIN_RELEASE_LEDGER_FILENAME);
1103
- if (release.state === 'published' && !hasLedger) {
1104
- throw new Error('Published Catalog snapshot must include plugin-release-ledger.json.');
1105
- }
1106
- if (release.state === 'development' && hasLedger) {
1107
- throw new Error('Development Catalog snapshot must not include plugin-release-ledger.json.');
1108
- }
1109
- const ledger = hasLedger
1110
- ? validatePluginReleaseLedgerAgainstManifest(await readRegularJson(path.join(rootDir, PLUGIN_RELEASE_LEDGER_FILENAME), 'Plugin release ledger'), manifest)
1111
- : undefined;
1112
- return { rootDir, manifest, ...(ledger ? { ledger } : {}), snapshotDigest };
1109
+ return { rootDir, manifest, snapshotDigest };
1113
1110
  }
1114
1111
  export async function validateCatalogSnapshotTransport(snapshotDirectory) {
1115
1112
  return validateCatalogSnapshotTransportInternal(snapshotDirectory);
1116
1113
  }
1117
- function normalizeLegacyCatalogCompatibility(value, fieldName) {
1118
- if (!value || typeof value !== 'object' || Array.isArray(value)) {
1119
- throw new Error(`Invalid ${fieldName}: expected an object.`);
1120
- }
1121
- const compatibility = value;
1122
- if (!isDeepStrictEqual(Object.keys(compatibility), ['aw'])) {
1123
- throw new Error(`Invalid ${fieldName}: unknown field.`);
1124
- }
1125
- if (!compatibility.aw || typeof compatibility.aw !== 'object' || Array.isArray(compatibility.aw)) {
1126
- throw new Error(`Invalid ${fieldName}.aw: expected an object.`);
1127
- }
1128
- const aw = compatibility.aw;
1129
- if (!isDeepStrictEqual(Object.keys(aw), ['minVersion'])) {
1130
- throw new Error(`Invalid ${fieldName}.aw: unknown field.`);
1131
- }
1132
- return { aw: { minVersion: assertStrictSemVer(aw.minVersion, `${fieldName}.aw.minVersion`) } };
1133
- }
1134
- function normalizeLegacyPublishedCatalogManifestV4(raw) {
1135
- const allowedManifestKeys = new Set([
1136
- 'schemaVersion', 'catalogVersion', 'channel', 'compatibility', 'marketplace', 'release', 'entries',
1137
- ]);
1138
- for (const key of Object.keys(raw)) {
1139
- if (!allowedManifestKeys.has(key))
1140
- throw new Error(`Invalid legacy catalog manifest: unknown field ${JSON.stringify(key)}.`);
1141
- }
1142
- if (raw.schemaVersion !== 4)
1143
- throw new Error('Invalid legacy catalog manifest schemaVersion: expected 4.');
1144
- const catalogVersion = assertCatalogVersion(raw.catalogVersion, 'legacy catalog manifest v4.catalogVersion');
1145
- if (raw.channel !== 'latest' && raw.channel !== 'beta') {
1146
- throw new Error('Invalid legacy catalog manifest v4.channel: expected "latest" or "beta".');
1147
- }
1148
- const channel = raw.channel;
1149
- const isBetaVersion = catalogVersion.includes('-beta.');
1150
- if ((channel === 'latest' && isBetaVersion) || (channel === 'beta' && !isBetaVersion)) {
1151
- throw new Error('Invalid legacy catalog manifest v4 catalogVersion channel.');
1152
- }
1153
- const compatibility = normalizeLegacyCatalogCompatibility(raw.compatibility, 'legacy catalog manifest v4.compatibility');
1154
- const marketplace = normalizeCatalogMarketplaceIdentity(raw.marketplace, 'legacy catalog manifest v4.marketplace');
1155
- if (!Array.isArray(raw.entries))
1156
- throw new Error('Invalid legacy catalog manifest v4.entries: expected an array.');
1157
- const allowedEntryKeys = new Set([
1158
- 'name', 'version', 'description', 'interface', 'category', 'tags', 'strict', 'policy',
1159
- 'compatibility', 'source', 'artifactDigest', 'manifestDigest', 'contentDigest',
1160
- ]);
1161
- const translatedEntryInputs = raw.entries.map((value, index) => {
1162
- if (!value || typeof value !== 'object' || Array.isArray(value)) {
1163
- throw new Error(`Invalid legacy catalog manifest v4.entries[${index}]: expected an object.`);
1164
- }
1165
- const entry = value;
1166
- for (const key of Object.keys(entry)) {
1167
- if (!allowedEntryKeys.has(key)) {
1168
- throw new Error(`Invalid legacy catalog manifest v4.entries[${index}]: unknown field ${JSON.stringify(key)}.`);
1169
- }
1170
- }
1171
- const entryCompatibility = normalizeLegacyCatalogCompatibility(entry.compatibility, `legacy catalog manifest v4.entries[${index}].compatibility`);
1172
- if (compareStrictSemVer(entryCompatibility.aw.minVersion, compatibility.aw.minVersion) < 0) {
1173
- throw new Error(`Invalid legacy catalog manifest v4.entries[${index}].compatibility.aw.minVersion.`);
1174
- }
1175
- const translated = { ...entry };
1176
- delete translated.compatibility;
1177
- delete translated.contentDigest;
1178
- return {
1179
- ...translated,
1180
- dependencies: [],
1181
- requires: { aw: { capabilities: [] } },
1182
- };
1183
- });
1184
- const translatedBase = normalizeCatalogManifestBase({
1185
- catalogVersion,
1186
- channel,
1187
- requires: { aw: { capabilities: ['aw.catalog-snapshot.v5'] } },
1188
- }, 'legacy catalog manifest v4 translation');
1189
- const normalizedEntries = normalizeCatalogManifestEntries(translatedEntryInputs, translatedBase, 'legacy catalog manifest v4 translation').map((entry, index) => {
1190
- const contentDigest = raw.entries[index].contentDigest;
1191
- if (typeof contentDigest !== 'string' || !/^sha256:[0-9a-f]{64}$/u.test(contentDigest)) {
1192
- throw new Error(`Invalid legacy catalog manifest v4.entries[${index}].contentDigest.`);
1193
- }
1194
- return { ...entry, contentDigest };
1195
- });
1196
- const release = normalizeCatalogRelease(raw.release, catalogVersion);
1197
- if (release.state !== 'published') {
1198
- throw new Error('Legacy Catalog publication parent must have release.state published.');
1199
- }
1200
- const legacyContentDigest = sha256(stableStringify({
1201
- schemaIdentity: { schemaVersion: 4 },
1202
- marketplaceMetadata: { name: 'aw', owner: marketplace.owner, interface: marketplace.interface },
1203
- compatibility,
1204
- plugins: normalizedEntries.map((entry) => ({
1205
- name: entry.name,
1206
- targetVersion: entry.version.replace(/-beta\.[1-9]\d*$/u, ''),
1207
- contentDigest: entry.contentDigest,
1208
- })).sort((left, right) => left.name.localeCompare(right.name)),
1209
- }));
1210
- if (release.contentDigest !== legacyContentDigest) {
1211
- throw new Error('Published legacy catalog release contentDigest does not match the snapshot.');
1212
- }
1213
- const translatedManifest = {
1214
- schemaVersion: 5,
1215
- catalogVersion,
1216
- channel,
1217
- requires: translatedBase.requires,
1218
- marketplace,
1219
- release,
1220
- entries: normalizedEntries,
1221
- };
1222
- return {
1223
- ...translatedManifest,
1224
- release: { ...release, contentDigest: publishedContentDigest(translatedManifest) },
1225
- };
1226
- }
1227
- export async function validatePublishedCatalogPublicationParent(snapshotDirectory) {
1228
- const raw = await readRegularJson(path.join(snapshotDirectory, CATALOG_MANIFEST_FILENAME), 'Catalog publication parent manifest');
1229
- const validation = raw.schemaVersion === 4
1230
- ? await validateCatalogSnapshotTransportInternal(snapshotDirectory, {
1231
- rawManifest: normalizeLegacyPublishedCatalogManifestV4(raw),
1232
- legacyV4PublicationParent: true,
1233
- })
1234
- : await validatePublishedCatalogSnapshot(snapshotDirectory);
1235
- if (validation.manifest.release.state !== 'published') {
1236
- throw new Error('Catalog publication parent must have release.state published.');
1237
- }
1238
- if (!validation.ledger)
1239
- throw new Error('Catalog publication parent must include a plugin release ledger.');
1240
- return {
1241
- rootDir: validation.rootDir,
1242
- catalogVersion: validation.manifest.catalogVersion,
1243
- ledger: validation.ledger,
1244
- snapshotDigest: validation.snapshotDigest,
1245
- };
1246
- }
1247
1114
  async function validateCatalogPluginEntriesComplete(validation, pluginNames) {
1248
1115
  const entriesByName = new Map(validation.manifest.entries.map((entry) => [entry.name, entry]));
1249
1116
  const plugins = [];