@ggui-ai/registry-core 0.6.3 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/dist/impls/memory-bundle-storage.d.ts.map +1 -1
  2. package/dist/impls/memory-bundle-storage.js +25 -22
  3. package/dist/impls/memory-registry-storage.d.ts.map +1 -1
  4. package/dist/impls/memory-registry-storage.js +47 -4
  5. package/dist/index.d.ts +8 -3
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +8 -2
  8. package/dist/install-command.d.ts +25 -0
  9. package/dist/install-command.d.ts.map +1 -0
  10. package/dist/install-command.js +15 -0
  11. package/dist/interfaces/bundle-storage.d.ts +55 -31
  12. package/dist/interfaces/bundle-storage.d.ts.map +1 -1
  13. package/dist/interfaces/registry-storage.d.ts +106 -1
  14. package/dist/interfaces/registry-storage.d.ts.map +1 -1
  15. package/dist/mcp-tool-filters.d.ts +23 -0
  16. package/dist/mcp-tool-filters.d.ts.map +1 -0
  17. package/dist/mcp-tool-filters.js +14 -0
  18. package/dist/ops/delete-author-key.d.ts +45 -0
  19. package/dist/ops/delete-author-key.d.ts.map +1 -0
  20. package/dist/ops/delete-author-key.js +31 -0
  21. package/dist/ops/list-author-keys.d.ts +34 -0
  22. package/dist/ops/list-author-keys.d.ts.map +1 -0
  23. package/dist/ops/list-author-keys.js +45 -0
  24. package/dist/ops/list-versions.d.ts +19 -10
  25. package/dist/ops/list-versions.d.ts.map +1 -1
  26. package/dist/ops/list-versions.js +43 -8
  27. package/dist/ops/private-read-authz.d.ts +88 -0
  28. package/dist/ops/private-read-authz.d.ts.map +1 -0
  29. package/dist/ops/private-read-authz.js +58 -0
  30. package/dist/ops/publish.d.ts +68 -1
  31. package/dist/ops/publish.d.ts.map +1 -1
  32. package/dist/ops/publish.js +292 -16
  33. package/dist/ops/read.d.ts +28 -6
  34. package/dist/ops/read.d.ts.map +1 -1
  35. package/dist/ops/read.js +47 -7
  36. package/dist/ops/register-author-key.d.ts +13 -0
  37. package/dist/ops/register-author-key.d.ts.map +1 -1
  38. package/dist/ops/register-author-key.js +42 -0
  39. package/dist/ops/search.d.ts +13 -1
  40. package/dist/ops/search.d.ts.map +1 -1
  41. package/dist/ops/search.js +54 -2
  42. package/dist/testing/bundle-storage-contract.d.ts.map +1 -1
  43. package/dist/testing/bundle-storage-contract.js +61 -27
  44. package/dist/testing/registry-storage-contract.d.ts +1 -1
  45. package/dist/testing/registry-storage-contract.d.ts.map +1 -1
  46. package/dist/testing/registry-storage-contract.js +351 -1
  47. package/dist/types.d.ts +257 -17
  48. package/dist/types.d.ts.map +1 -1
  49. package/dist/types.js +79 -14
  50. package/package.json +20 -5
@@ -40,6 +40,8 @@ import { derivePublicKeyId } from '@ggui-ai/gadget-signing';
40
40
  import { AuthorKeyAlreadyExistsError, } from '../interfaces/registry-storage.js';
41
41
  import { safeBase64Decode } from '../utils/base64.js';
42
42
  const ED25519_PUBLIC_KEY_BYTES = 32;
43
+ /** Display-name budget — a label is a short human hint, not a document. */
44
+ export const MAX_LABEL_LENGTH = 100;
43
45
  export async function registerAuthorKey(input, deps) {
44
46
  if (typeof input.publicKeyBase64 !== 'string' ||
45
47
  input.publicKeyBase64.length === 0) {
@@ -73,12 +75,42 @@ export async function registerAuthorKey(input, deps) {
73
75
  },
74
76
  };
75
77
  }
78
+ // Label ([E] enrichment) — defend with a typeof check even though the
79
+ // input type says string (transports hand over parsed JSON); trim;
80
+ // whitespace-only collapses to absent.
81
+ if (input.label !== undefined && typeof input.label !== 'string') {
82
+ return {
83
+ ok: false,
84
+ status: 400,
85
+ body: {
86
+ error: 'invalid_request',
87
+ message: '`label` must be a string',
88
+ },
89
+ };
90
+ }
91
+ const trimmedLabel = input.label?.trim();
92
+ const label = trimmedLabel !== undefined && trimmedLabel.length > 0
93
+ ? trimmedLabel
94
+ : undefined;
95
+ if (label !== undefined && label.length > MAX_LABEL_LENGTH) {
96
+ return {
97
+ ok: false,
98
+ status: 400,
99
+ body: {
100
+ error: 'invalid_request',
101
+ message: `\`label\` must be at most ${MAX_LABEL_LENGTH} characters (got ${label.length})`,
102
+ },
103
+ };
104
+ }
76
105
  const keyId = derivePublicKeyId(decoded);
77
106
  const subject = deps.authn.subject;
107
+ const clock = deps.clock ?? (() => new Date());
78
108
  const row = {
79
109
  subject,
80
110
  keyId,
81
111
  publicKeyBase64: input.publicKeyBase64,
112
+ createdAt: clock().toISOString(),
113
+ ...(label !== undefined ? { label } : {}),
82
114
  };
83
115
  // Atomic first-write attempt. The `ifNotExists` flag tells the
84
116
  // storage adapter to use a conditional put (a DDB
@@ -103,6 +135,8 @@ export async function registerAuthorKey(input, deps) {
103
135
  subject,
104
136
  keyId,
105
137
  publicKeyBase64: input.publicKeyBase64,
138
+ ...(row.createdAt !== undefined ? { createdAt: row.createdAt } : {}),
139
+ ...(row.label !== undefined ? { label: row.label } : {}),
106
140
  },
107
141
  };
108
142
  }
@@ -150,6 +184,10 @@ export async function registerAuthorKey(input, deps) {
150
184
  };
151
185
  }
152
186
  if (existing.publicKeyBase64 === input.publicKeyBase64) {
187
+ // Idempotent re-register echoes the EXISTING row untouched — a
188
+ // different label on a retry does NOT rewrite the stored one
189
+ // (relabeling is a deliberate future op, not a register side
190
+ // effect).
153
191
  return {
154
192
  ok: true,
155
193
  status: 200,
@@ -157,6 +195,10 @@ export async function registerAuthorKey(input, deps) {
157
195
  subject: existing.subject,
158
196
  keyId: existing.keyId,
159
197
  publicKeyBase64: existing.publicKeyBase64,
198
+ ...(existing.createdAt !== undefined
199
+ ? { createdAt: existing.createdAt }
200
+ : {}),
201
+ ...(existing.label !== undefined ? { label: existing.label } : {}),
160
202
  },
161
203
  };
162
204
  }
@@ -2,7 +2,7 @@
2
2
  * `searchArtifacts` — pure op for `GET /search?…`. Delegates the
3
3
  * post-fetch filter + cursor to {@link RegistryStorage.scanArtifacts} so
4
4
  * every storage impl shares the single semantic — AND-composition over
5
- * q/kind/hook/tag/author.
5
+ * q/kind/hook/tag/author/tool/server.
6
6
  *
7
7
  * Visibility: only `visibility: "public"` rows are exposed. Without that
8
8
  * filter the public `/search` route would leak private artifact IDs.
@@ -31,6 +31,18 @@ export interface SearchArtifactsInput {
31
31
  * globally-correct newest-first order across the full cursor chain.
32
32
  */
33
33
  readonly sort?: string;
34
+ /**
35
+ * Exact MCP tool-name filter — matches artifacts whose bindings
36
+ * include this tool (declared under any server, or none). Must match
37
+ * `^[A-Za-z0-9_.-]{1,128}$`; anything else is a 400.
38
+ */
39
+ readonly tool?: string;
40
+ /**
41
+ * Exact MCP server-name filter — matches artifacts whose bindings
42
+ * declare this server; bindings without a server never match. Same
43
+ * charset rule as `tool`.
44
+ */
45
+ readonly server?: string;
34
46
  }
35
47
  export interface SearchArtifactsDeps {
36
48
  readonly storage: RegistryStorage;
@@ -1 +1 @@
1
- {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/ops/search.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAIV,eAAe,EACf,cAAc,EAGf,MAAM,aAAa,CAAC;AAErB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AAKzE,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACjC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;CACnC;AAED,MAAM,MAAM,qBAAqB,GAC7B;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,GAAG,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAA;CAAE,GAC1E;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,GAAG,GAAG,GAAG,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAA;CAAE,CAAC;AAEvF,wBAAsB,eAAe,CACnC,KAAK,EAAE,oBAAoB,EAC3B,IAAI,EAAE,mBAAmB,GACxB,OAAO,CAAC,qBAAqB,CAAC,CAsFhC"}
1
+ {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/ops/search.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAKV,eAAe,EACf,cAAc,EAGf,MAAM,aAAa,CAAC;AAErB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AAOzE,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACjC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;CACnC;AAED,MAAM,MAAM,qBAAqB,GAC7B;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,GAAG,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAA;CAAE,GAC1E;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,GAAG,GAAG,GAAG,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAA;CAAE,CAAC;AAEvF,wBAAsB,eAAe,CACnC,KAAK,EAAE,oBAAoB,EAC3B,IAAI,EAAE,mBAAmB,GACxB,OAAO,CAAC,qBAAqB,CAAC,CAwHhC"}
@@ -1,4 +1,6 @@
1
1
  import { SEARCH_SORT_OPTIONS } from '../types.js';
2
+ import { artifactScope } from './private-read-authz.js';
3
+ import { MCP_TOOL_BINDING_NAME_RE } from '@ggui-ai/artifact-manifest';
2
4
  const DEFAULT_LIMIT = 50;
3
5
  const MAX_LIMIT = 200;
4
6
  export async function searchArtifacts(input, deps) {
@@ -45,12 +47,36 @@ export async function searchArtifacts(input, deps) {
45
47
  }
46
48
  sort = input.sort;
47
49
  }
50
+ const tool = nonEmpty(input.tool);
51
+ if (tool !== undefined && !MCP_TOOL_BINDING_NAME_RE.test(tool)) {
52
+ return {
53
+ ok: false,
54
+ status: 400,
55
+ body: {
56
+ error: 'invalid_request',
57
+ message: '`tool` must match ^[A-Za-z0-9_.-]{1,128}$',
58
+ },
59
+ };
60
+ }
61
+ const server = nonEmpty(input.server);
62
+ if (server !== undefined && !MCP_TOOL_BINDING_NAME_RE.test(server)) {
63
+ return {
64
+ ok: false,
65
+ status: 400,
66
+ body: {
67
+ error: 'invalid_request',
68
+ message: '`server` must match ^[A-Za-z0-9_.-]{1,128}$',
69
+ },
70
+ };
71
+ }
48
72
  const filter = {
49
73
  q: nonEmpty(input.q),
50
74
  kind,
51
75
  hook: nonEmpty(input.hook),
52
76
  tag: nonEmpty(input.tag),
53
77
  author: nonEmpty(input.author),
78
+ tool,
79
+ server,
54
80
  limit,
55
81
  cursor: nonEmpty(input.cursor),
56
82
  order: sort,
@@ -72,11 +98,16 @@ export async function searchArtifacts(input, deps) {
72
98
  continue;
73
99
  visibleRows.push(row);
74
100
  }
101
+ // Scope-verification surfacing — ONE owner read per unique scope on
102
+ // the page, batched. A failed or empty read leaves the fields ABSENT
103
+ // (state unknown is not 'unverified'): the wire only carries a label
104
+ // the ownership row actually asserted.
105
+ const ownersByScope = await readScopeOwners(deps.storage, visibleRows);
75
106
  // `sort=recent` ordering is served by the storage layer (the filter
76
107
  // carries `order: 'recent'`); rows arrive newest-first and are
77
108
  // emitted verbatim — no re-sort here, so page order and cursor order
78
109
  // cannot diverge.
79
- const results = visibleRows.map(rowToEntry);
110
+ const results = visibleRows.map((row) => rowToEntry(row, ownersByScope.get(artifactScope(row.artifactId)) ?? null));
80
111
  return {
81
112
  ok: true,
82
113
  status: 200,
@@ -86,7 +117,24 @@ export async function searchArtifacts(input, deps) {
86
117
  function isSearchSort(value) {
87
118
  return SEARCH_SORT_OPTIONS.includes(value);
88
119
  }
89
- function rowToEntry(row) {
120
+ async function readScopeOwners(storage, rows) {
121
+ const scopes = [...new Set(rows.map((row) => artifactScope(row.artifactId)))];
122
+ const entries = await Promise.all(scopes.map(async (scope) => {
123
+ try {
124
+ return [scope, await storage.getScopeOwner(scope)];
125
+ }
126
+ catch (err) {
127
+ // eslint-disable-next-line no-console -- server-side operator signal; the wire simply omits the fields
128
+ console.error('registry search: scope-owner lookup failed; omitting verification fields', {
129
+ scope,
130
+ error: err instanceof Error ? err.message : String(err),
131
+ });
132
+ return [scope, null];
133
+ }
134
+ }));
135
+ return new Map(entries);
136
+ }
137
+ function rowToEntry(row, scopeOwner) {
90
138
  return {
91
139
  artifactId: row.artifactId,
92
140
  latestVersion: row.latestVersion,
@@ -94,6 +142,10 @@ function rowToEntry(row) {
94
142
  description: row.description,
95
143
  tags: row.tags,
96
144
  publishedAt: row.publishedAt,
145
+ mcpTools: row.mcpTools,
146
+ mcpToolsSource: row.mcpToolsSource,
147
+ scopeVerification: scopeOwner?.verification,
148
+ verifiedDomain: scopeOwner?.verification === 'verified' ? scopeOwner.verifiedDomain : undefined,
97
149
  };
98
150
  }
99
151
  function nonEmpty(s) {
@@ -1 +1 @@
1
- {"version":3,"file":"bundle-storage-contract.d.ts","sourceRoot":"","sources":["../../src/testing/bundle-storage-contract.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AA0DrE,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,MAAM,aAAa,GAAG,IAAI,CA6H5E"}
1
+ {"version":3,"file":"bundle-storage-contract.d.ts","sourceRoot":"","sources":["../../src/testing/bundle-storage-contract.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AA0DrE,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,MAAM,aAAa,GAAG,IAAI,CA8K5E"}
@@ -69,35 +69,69 @@ export function bundleStorageContract(makeStorage) {
69
69
  describe('bundle', () => {
70
70
  it('returns null on miss', async () => {
71
71
  const s = makeStorage();
72
- expect(await s.getBundle('@test', 'foo', '0.1.0')).toBe(null);
72
+ expect(await s.getBundle('@test', 'foo', '0.1.0', 'public')).toBe(null);
73
73
  });
74
74
  it('round-trips bundle bytes', async () => {
75
75
  const s = makeStorage();
76
76
  const bytes = new Uint8Array([0xff, 0x00, 0x10, 0x20]);
77
- await s.putBundle('@test', 'foo', '0.1.0', bytes);
78
- const fetched = await s.getBundle('@test', 'foo', '0.1.0');
77
+ await s.putBundle('@test', 'foo', '0.1.0', 'public', bytes);
78
+ const fetched = await s.getBundle('@test', 'foo', '0.1.0', 'public');
79
79
  expect(fetched).toEqual(bytes);
80
80
  });
81
81
  it('isolates bundles by (scope, name, version)', async () => {
82
82
  const s = makeStorage();
83
- await s.putBundle('@a', 'pkg', '0.1.0', new Uint8Array([1]));
84
- await s.putBundle('@a', 'pkg', '0.2.0', new Uint8Array([2]));
85
- await s.putBundle('@b', 'pkg', '0.1.0', new Uint8Array([3]));
86
- expect((await s.getBundle('@a', 'pkg', '0.1.0'))?.[0]).toBe(1);
87
- expect((await s.getBundle('@a', 'pkg', '0.2.0'))?.[0]).toBe(2);
88
- expect((await s.getBundle('@b', 'pkg', '0.1.0'))?.[0]).toBe(3);
83
+ await s.putBundle('@a', 'pkg', '0.1.0', 'public', new Uint8Array([1]));
84
+ await s.putBundle('@a', 'pkg', '0.2.0', 'public', new Uint8Array([2]));
85
+ await s.putBundle('@b', 'pkg', '0.1.0', 'public', new Uint8Array([3]));
86
+ expect((await s.getBundle('@a', 'pkg', '0.1.0', 'public'))?.[0]).toBe(1);
87
+ expect((await s.getBundle('@a', 'pkg', '0.2.0', 'public'))?.[0]).toBe(2);
88
+ expect((await s.getBundle('@b', 'pkg', '0.1.0', 'public'))?.[0]).toBe(3);
89
+ });
90
+ });
91
+ describe('visibility key split (H1)', () => {
92
+ it('a private-written bundle is invisible to a public read of the same triple', async () => {
93
+ const s = makeStorage();
94
+ await s.putBundle('@acme', 'secret', '1.0.0', 'private', new Uint8Array([7]));
95
+ expect(await s.getBundle('@acme', 'secret', '1.0.0', 'public')).toBe(null);
96
+ expect((await s.getBundle('@acme', 'secret', '1.0.0', 'private'))?.[0]).toBe(7);
97
+ });
98
+ it('a public-written bundle is invisible to a private read of the same triple', async () => {
99
+ const s = makeStorage();
100
+ await s.putBundle('@acme', 'open', '1.0.0', 'public', new Uint8Array([9]));
101
+ expect(await s.getBundle('@acme', 'open', '1.0.0', 'private')).toBe(null);
102
+ expect((await s.getBundle('@acme', 'open', '1.0.0', 'public'))?.[0]).toBe(9);
103
+ });
104
+ it('splits signatures and manifests by visibility too', async () => {
105
+ const s = makeStorage();
106
+ await s.putSignature('@acme', 'secret', '1.0.0', 'private', stubSignature());
107
+ await s.putManifest('@acme', 'secret', '1.0.0', 'private', stubManifest());
108
+ expect(await s.getSignature('@acme', 'secret', '1.0.0', 'public')).toBe(null);
109
+ expect(await s.getManifest('@acme', 'secret', '1.0.0', 'public')).toBe(null);
110
+ expect(await s.getSignature('@acme', 'secret', '1.0.0', 'private')).not.toBe(null);
111
+ expect(await s.getManifest('@acme', 'secret', '1.0.0', 'private')).not.toBe(null);
112
+ });
113
+ it('embeds the visibility segment in every composed URL', () => {
114
+ const s = makeStorage();
115
+ for (const compose of [
116
+ s.bundleUrl.bind(s),
117
+ s.signatureUrl.bind(s),
118
+ s.manifestUrl.bind(s),
119
+ ]) {
120
+ expect(compose('@test', 'foo', '0.1.0', 'public')).toContain('/bundles/public/');
121
+ expect(compose('@test', 'foo', '0.1.0', 'private')).toContain('/bundles/private/');
122
+ }
89
123
  });
90
124
  });
91
125
  describe('signature', () => {
92
126
  it('returns null on miss', async () => {
93
127
  const s = makeStorage();
94
- expect(await s.getSignature('@test', 'foo', '0.1.0')).toBe(null);
128
+ expect(await s.getSignature('@test', 'foo', '0.1.0', 'public')).toBe(null);
95
129
  });
96
130
  it('round-trips an Ed25519 signature envelope', async () => {
97
131
  const s = makeStorage();
98
132
  const sig = stubSignature();
99
- await s.putSignature('@test', 'foo', '0.1.0', sig);
100
- const fetched = await s.getSignature('@test', 'foo', '0.1.0');
133
+ await s.putSignature('@test', 'foo', '0.1.0', 'private', sig);
134
+ const fetched = await s.getSignature('@test', 'foo', '0.1.0', 'private');
101
135
  expect(fetched).toEqual(sig);
102
136
  });
103
137
  // Pin the sigstore round-trip through the storage layer. Guards
@@ -110,8 +144,8 @@ export function bundleStorageContract(makeStorage) {
110
144
  it('round-trips a sigstore signature envelope including the embedded cosign bundle', async () => {
111
145
  const s = makeStorage();
112
146
  const sig = stubSigstoreSignature();
113
- await s.putSignature('@test', 'foo', '0.1.0', sig);
114
- const fetched = await s.getSignature('@test', 'foo', '0.1.0');
147
+ await s.putSignature('@test', 'foo', '0.1.0', 'public', sig);
148
+ const fetched = await s.getSignature('@test', 'foo', '0.1.0', 'public');
115
149
  expect(fetched).toEqual(sig);
116
150
  // Specifically pin the `algorithm` discriminator + the
117
151
  // `bundle` field — these are sigstore-only fields the prior
@@ -135,41 +169,41 @@ export function bundleStorageContract(makeStorage) {
135
169
  // case is covered by the round-trip tests; this test pins the
136
170
  // null-on-miss contract specifically — without it, a regression
137
171
  // that silently returns a stub object would slip through.
138
- const fetched = await s.getSignature('@no-such-scope', 'no-such-name', '0.0.0');
172
+ const fetched = await s.getSignature('@no-such-scope', 'no-such-name', '0.0.0', 'public');
139
173
  expect(fetched).toBe(null);
140
174
  });
141
175
  });
142
176
  describe('manifest', () => {
143
177
  it('returns null on miss', async () => {
144
178
  const s = makeStorage();
145
- expect(await s.getManifest('@test', 'foo', '0.1.0')).toBe(null);
179
+ expect(await s.getManifest('@test', 'foo', '0.1.0', 'public')).toBe(null);
146
180
  });
147
181
  it('round-trips the manifest', async () => {
148
182
  const s = makeStorage();
149
183
  const m = stubManifest();
150
- await s.putManifest('@test', 'foo', '0.1.0', m);
151
- const fetched = await s.getManifest('@test', 'foo', '0.1.0');
184
+ await s.putManifest('@test', 'foo', '0.1.0', 'public', m);
185
+ const fetched = await s.getManifest('@test', 'foo', '0.1.0', 'public');
152
186
  expect(fetched).toEqual(m);
153
187
  });
154
188
  });
155
189
  describe('URL composition', () => {
156
190
  it('produces distinct URLs for bundle / sig / manifest', async () => {
157
191
  const s = makeStorage();
158
- const b = s.bundleUrl('@test', 'foo', '0.1.0');
159
- const sig = s.signatureUrl('@test', 'foo', '0.1.0');
160
- const man = s.manifestUrl('@test', 'foo', '0.1.0');
192
+ const b = s.bundleUrl('@test', 'foo', '0.1.0', 'public');
193
+ const sig = s.signatureUrl('@test', 'foo', '0.1.0', 'public');
194
+ const man = s.manifestUrl('@test', 'foo', '0.1.0', 'public');
161
195
  expect(b).not.toBe(sig);
162
196
  expect(b).not.toBe(man);
163
197
  expect(sig).not.toBe(man);
164
198
  });
165
199
  it('matches the URL returned from put*', async () => {
166
200
  const s = makeStorage();
167
- const putBundleUrl = await s.putBundle('@test', 'foo', '0.1.0', new Uint8Array([1]));
168
- expect(putBundleUrl).toBe(s.bundleUrl('@test', 'foo', '0.1.0'));
169
- const putSigUrl = await s.putSignature('@test', 'foo', '0.1.0', stubSignature());
170
- expect(putSigUrl).toBe(s.signatureUrl('@test', 'foo', '0.1.0'));
171
- const putManUrl = await s.putManifest('@test', 'foo', '0.1.0', stubManifest());
172
- expect(putManUrl).toBe(s.manifestUrl('@test', 'foo', '0.1.0'));
201
+ const putBundleUrl = await s.putBundle('@test', 'foo', '0.1.0', 'private', new Uint8Array([1]));
202
+ expect(putBundleUrl).toBe(s.bundleUrl('@test', 'foo', '0.1.0', 'private'));
203
+ const putSigUrl = await s.putSignature('@test', 'foo', '0.1.0', 'private', stubSignature());
204
+ expect(putSigUrl).toBe(s.signatureUrl('@test', 'foo', '0.1.0', 'private'));
205
+ const putManUrl = await s.putManifest('@test', 'foo', '0.1.0', 'private', stubManifest());
206
+ expect(putManUrl).toBe(s.manifestUrl('@test', 'foo', '0.1.0', 'private'));
173
207
  });
174
208
  });
175
209
  });
@@ -1,3 +1,3 @@
1
- import type { RegistryStorage } from '../interfaces/registry-storage.js';
1
+ import { type RegistryStorage } from '../interfaces/registry-storage.js';
2
2
  export declare function registryStorageContract(makeStorage: () => RegistryStorage): void;
3
3
  //# sourceMappingURL=registry-storage-contract.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"registry-storage-contract.d.ts","sourceRoot":"","sources":["../../src/testing/registry-storage-contract.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AAsHzE,wBAAgB,uBAAuB,CAAC,WAAW,EAAE,MAAM,eAAe,GAAG,IAAI,CA+ZhF"}
1
+ {"version":3,"file":"registry-storage-contract.d.ts","sourceRoot":"","sources":["../../src/testing/registry-storage-contract.ts"],"names":[],"mappings":"AAkBA,OAAO,EAEL,KAAK,eAAe,EACrB,MAAM,mCAAmC,CAAC;AAqI3C,wBAAgB,uBAAuB,CAAC,WAAW,EAAE,MAAM,eAAe,GAAG,IAAI,CAgzBhF"}