@x12i/catalox 3.4.5 → 3.5.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.
@@ -1,5 +1,5 @@
1
1
  import { compactCatalogFilter } from "../contracts/catalogs.js";
2
- import { CatalogAccessDeniedError, CatalogAdapterError, CatalogNotFoundError } from "../contracts/errors.js";
2
+ import { CatalogAccessDeniedError, CatalogAdapterError, CatalogBindingError, CatalogNotFoundError, } from "../contracts/errors.js";
3
3
  import { validateMappingSpec } from "../mapping/validate-mapping.js";
4
4
  import { executeMapping } from "../mapping/execute-mapping.js";
5
5
  import { ApiCatalogAdapter } from "../adapters/api/api-adapter.js";
@@ -110,8 +110,41 @@ export class Catalox {
110
110
  if (active.length)
111
111
  return { storeId, appIds: active };
112
112
  }
113
+ if (!context.appId) {
114
+ throw new CatalogBindingError({
115
+ reason: "missing_appId",
116
+ message: "App id is required unless storeId/appIds are provided.",
117
+ });
118
+ }
113
119
  return { ...(storeId ? { storeId } : {}), appIds: [context.appId] };
114
120
  }
121
+ async resolveAppIdForCatalogAccess(params) {
122
+ const { context, catalogId, required } = params;
123
+ if (context.appId)
124
+ return context.appId;
125
+ // Super-admin callers may omit appId; access is still evaluated with explicit elevation.
126
+ if (context.superAdmin)
127
+ return "_super";
128
+ // CatalogId-first: infer appId via active bindings if there is exactly one viable binding.
129
+ const bindings = await this.deps.bindings.listByCatalog(catalogId);
130
+ const active = bindings.filter((b) => b.status === "active");
131
+ const viable = active.filter((b) => {
132
+ if (required === "read")
133
+ return Boolean(b.access.canRead);
134
+ if (required === "write")
135
+ return Boolean(b.access.canWrite);
136
+ return Boolean(b.access.canAdmin);
137
+ });
138
+ const distinctApps = [...new Set(viable.map((b) => String(b.appId)))];
139
+ if (distinctApps.length === 1)
140
+ return viable[0].appId;
141
+ throw new CatalogBindingError({
142
+ reason: "cannot_resolve_appId_for_catalog",
143
+ catalogId: String(catalogId),
144
+ required,
145
+ matchingAppIds: distinctApps,
146
+ });
147
+ }
115
148
  readPath(obj, path) {
116
149
  if (!path)
117
150
  return undefined;
@@ -162,7 +195,12 @@ export class Catalox {
162
195
  }
163
196
  if (source.type === "catalog") {
164
197
  // Enforce authz: safest default is to require binding (unless god-mode).
165
- await this.deps.authz.requireBindingAccess(params.context, params.context.appId, source.catalogId, "read");
198
+ const appId = await this.resolveAppIdForCatalogAccess({
199
+ context: params.context,
200
+ catalogId: source.catalogId,
201
+ required: "read",
202
+ });
203
+ await this.deps.authz.requireBindingAccess(params.context, appId, source.catalogId, "read");
166
204
  const filterEq = resolveFilterBy(source.filterBy, params.currentItemData, (o, p) => this.readPath(o, p));
167
205
  const sort = source.sortBy
168
206
  ? { [source.sortBy.field]: source.sortBy.direction === "asc" ? 1 : -1 }
@@ -421,6 +459,9 @@ export class Catalox {
421
459
  }
422
460
  async listAppCatalogs(context, input) {
423
461
  const appId = input?.appId ?? context.appId;
462
+ if (!appId) {
463
+ throw new CatalogBindingError({ reason: "missing_appId", message: "listAppCatalogs requires an appId." });
464
+ }
424
465
  const bindings = await this.deps.bindings.listByApp(appId);
425
466
  const catalogs = await this.deps.catalogs.listAll();
426
467
  const byId = new Map(catalogs.map((c) => [c.catalogId, c]));
@@ -547,12 +588,14 @@ export class Catalox {
547
588
  return findCatalogsByAiHelper({ query: input.query, catalogs: all, input });
548
589
  }
549
590
  async getCatalogDescriptor(context, catalogId) {
550
- await this.deps.authz.requireBindingAccess(context, context.appId, catalogId, "read");
591
+ const appId = await this.resolveAppIdForCatalogAccess({ context, catalogId, required: "read" });
592
+ await this.deps.authz.requireBindingAccess(context, appId, catalogId, "read");
551
593
  const rec = await this.deps.descriptors.get(catalogId);
552
594
  return rec?.descriptor ?? null;
553
595
  }
554
596
  async getCatalogRendererSnippet(context, catalogId, role, mode) {
555
- await this.deps.authz.requireBindingAccess(context, context.appId, catalogId, "read");
597
+ const appId = await this.resolveAppIdForCatalogAccess({ context, catalogId, required: "read" });
598
+ await this.deps.authz.requireBindingAccess(context, appId, catalogId, "read");
556
599
  if (!this.deps.rendererSnippets) {
557
600
  throw new Error("rendererSnippets dependency is not configured");
558
601
  }
@@ -560,6 +603,9 @@ export class Catalox {
560
603
  }
561
604
  async getAppCatalogBootstrap(context, appId) {
562
605
  const resolved = appId ?? context.appId;
606
+ if (!resolved) {
607
+ throw new CatalogBindingError({ reason: "missing_appId", message: "getAppCatalogBootstrap requires an appId." });
608
+ }
563
609
  const entries = await this.listAppCatalogs(context, { appId: resolved });
564
610
  const descriptors = [];
565
611
  for (const e of entries) {
@@ -582,7 +628,9 @@ export class Catalox {
582
628
  return out;
583
629
  }
584
630
  async listCatalogItems(context, catalogId, options) {
585
- await this.deps.authz.requireBindingAccess(context, context.appId, catalogId, "read");
631
+ const appId = await this.resolveAppIdForCatalogAccess({ context, catalogId, required: "read" });
632
+ await this.deps.authz.requireBindingAccess(context, appId, catalogId, "read");
633
+ const appCtx = context.appId ? context : { ...context, appId };
586
634
  const catalog = await this.deps.catalogs.get(catalogId);
587
635
  if (!catalog)
588
636
  throw new CatalogNotFoundError({ catalogId });
@@ -619,7 +667,7 @@ export class Catalox {
619
667
  else {
620
668
  physical = await this.fetchScopedPhysicalRows(catalogId, rawScope, baseListOpts);
621
669
  }
622
- const merged = mergeNativePhysicalRows(physical, descRec.descriptor.identity, String(context.appId), wantsSuperList);
670
+ const merged = mergeNativePhysicalRows(physical, descRec.descriptor.identity, String(appId), wantsSuperList);
623
671
  const off = listQueryOptions?.offset ?? 0;
624
672
  const lim = listQueryOptions?.limit ?? 100;
625
673
  const sliced = merged.slice(off, off + lim);
@@ -652,7 +700,7 @@ export class Catalox {
652
700
  const adapterConfig = await this.deps.adapters.get(def.adapterId);
653
701
  if (!adapterConfig)
654
702
  throw new CatalogAdapterError({ catalogId, reason: "missing_adapter" });
655
- const result = await this.deps.mongoAdapter.listItems(context, catalogId, adapterConfig, { mapping: mapping.mapping, ...(mapping.options ? { options: mapping.options } : {}) }, mappedListQueryOptions);
703
+ const result = await this.deps.mongoAdapter.listItems(appCtx, catalogId, adapterConfig, { mapping: mapping.mapping, ...(mapping.options ? { options: mapping.options } : {}) }, mappedListQueryOptions);
656
704
  return result.issues?.length
657
705
  ? { listOutcome: "ok", items: result.items, issues: result.issues }
658
706
  : { listOutcome: "ok", items: result.items };
@@ -663,7 +711,7 @@ export class Catalox {
663
711
  const adapterConfig = await this.deps.adapters.get(def.adapterId);
664
712
  if (!adapterConfig)
665
713
  throw new CatalogAdapterError({ catalogId, reason: "missing_adapter" });
666
- const result = await this.deps.apiAdapter.listItems(context, catalogId, adapterConfig, { responseMapping: mapping.mapping, ...(mapping.options ? { options: mapping.options } : {}) }, mappedListQueryOptions);
714
+ const result = await this.deps.apiAdapter.listItems(appCtx, catalogId, adapterConfig, { responseMapping: mapping.mapping, ...(mapping.options ? { options: mapping.options } : {}) }, mappedListQueryOptions);
667
715
  return {
668
716
  listOutcome: "ok",
669
717
  items: result.items,
@@ -697,7 +745,8 @@ export class Catalox {
697
745
  }
698
746
  }
699
747
  async getCatalogItem(context, catalogId, itemId, options) {
700
- await this.deps.authz.requireBindingAccess(context, context.appId, catalogId, "read");
748
+ const appId = await this.resolveAppIdForCatalogAccess({ context, catalogId, required: "read" });
749
+ await this.deps.authz.requireBindingAccess(context, appId, catalogId, "read");
701
750
  const catalog = await this.deps.catalogs.get(catalogId);
702
751
  if (!catalog)
703
752
  throw new CatalogNotFoundError({ catalogId });
@@ -713,7 +762,7 @@ export class Catalox {
713
762
  const base = {
714
763
  itemId: rec.itemId,
715
764
  catalogId: rec.catalogId,
716
- appId: context.appId,
765
+ appId,
717
766
  sourceMode: "native",
718
767
  sourceType: "firebase",
719
768
  data: rec.data,
@@ -738,7 +787,7 @@ export class Catalox {
738
787
  const filtered = scope?.accountId || scope?.groupId || scope?.userId || scope?.channelId || scope?.visitorId || scope?.domainId || scope?.agentId
739
788
  ? filterPhysicalForTenantFetch(rows, scope)
740
789
  : rows.filter((r) => isGlobalPhysicalRow(r));
741
- const merged = mergeNativePhysicalRows(filtered, descRec.descriptor.identity, String(context.appId), false);
790
+ const merged = mergeNativePhysicalRows(filtered, descRec.descriptor.identity, String(appId), false);
742
791
  const hit = merged.find((m) => m.itemId === itemId) ?? merged[0];
743
792
  if (!hit)
744
793
  return { outcome: "not_found" };
@@ -784,18 +833,23 @@ export class Catalox {
784
833
  return { isValid: true, issues: [] };
785
834
  }
786
835
  async getCatalogItemReferences(context, catalogId, itemId) {
787
- await this.deps.authz.requireBindingAccess(context, context.appId, catalogId, "read");
836
+ const appId = await this.resolveAppIdForCatalogAccess({ context, catalogId, required: "read" });
837
+ await this.deps.authz.requireBindingAccess(context, appId, catalogId, "read");
788
838
  const refs = await this.deps.references.listByItem(catalogId, itemId);
789
839
  return refs;
790
840
  }
791
841
  async listCatalogReferences(context, catalogId) {
792
- await this.deps.authz.requireBindingAccess(context, context.appId, catalogId, "read");
842
+ const appId = await this.resolveAppIdForCatalogAccess({ context, catalogId, required: "read" });
843
+ await this.deps.authz.requireBindingAccess(context, appId, catalogId, "read");
793
844
  const refs = await this.deps.references.listByCatalog(catalogId);
794
845
  return refs;
795
846
  }
796
847
  // Spec methods below are stubs for now; filled in by later todos.
797
848
  async getApp(_context, appId) {
798
- return appId ? this.deps.apps.get(appId) : this.deps.apps.get(_context.appId);
849
+ const resolved = appId ?? _context.appId;
850
+ if (!resolved)
851
+ throw new CatalogBindingError({ reason: "missing_appId" });
852
+ return this.deps.apps.get(resolved);
799
853
  }
800
854
  async createCatalog(_context, _input) {
801
855
  const now = new Date().toISOString();
@@ -1004,7 +1058,8 @@ export class Catalox {
1004
1058
  return { query: input.query, hits, noMatch: Boolean(res.noMatch) };
1005
1059
  }
1006
1060
  async createCatalogItemByAi(context, catalogId, input) {
1007
- await this.deps.authz.requireBindingAccess(context, context.appId, catalogId, "write");
1061
+ const appId = await this.resolveAppIdForCatalogAccess({ context, catalogId, required: "write" });
1062
+ await this.deps.authz.requireBindingAccess(context, appId, catalogId, "write");
1008
1063
  const descRec = await this.deps.descriptors.get(catalogId);
1009
1064
  if (!descRec)
1010
1065
  throw new CatalogAdapterError({ catalogId, reason: "missing_descriptor" });
@@ -1056,7 +1111,8 @@ export class Catalox {
1056
1111
  return out;
1057
1112
  }
1058
1113
  async modifyCatalogItemByAi(context, catalogId, itemId, input) {
1059
- await this.deps.authz.requireBindingAccess(context, context.appId, catalogId, "write");
1114
+ const appId = await this.resolveAppIdForCatalogAccess({ context, catalogId, required: "write" });
1115
+ await this.deps.authz.requireBindingAccess(context, appId, catalogId, "write");
1060
1116
  const descRec = await this.deps.descriptors.get(catalogId);
1061
1117
  if (!descRec)
1062
1118
  throw new CatalogAdapterError({ catalogId, reason: "missing_descriptor" });
@@ -1146,7 +1202,7 @@ export class Catalox {
1146
1202
  async bindAppToStore(context, input) {
1147
1203
  if (!this.deps.storeAppBindings)
1148
1204
  throw new Error("storeAppBindings dependency is not configured");
1149
- if (!context.superAdmin && input.appId !== context.appId) {
1205
+ if (!context.superAdmin && (!context.appId || input.appId !== context.appId)) {
1150
1206
  throw new CatalogAccessDeniedError({ reason: "not_super_admin" });
1151
1207
  }
1152
1208
  const existing = await this.deps.storeAppBindings.findByStoreApp(input.storeId, input.appId);
@@ -1170,7 +1226,7 @@ export class Catalox {
1170
1226
  async unbindAppFromStore(context, storeId, appId) {
1171
1227
  if (!this.deps.storeAppBindings)
1172
1228
  throw new Error("storeAppBindings dependency is not configured");
1173
- if (!context.superAdmin && appId !== context.appId) {
1229
+ if (!context.superAdmin && (!context.appId || appId !== context.appId)) {
1174
1230
  throw new CatalogAccessDeniedError({ reason: "not_super_admin" });
1175
1231
  }
1176
1232
  const existing = await this.deps.storeAppBindings.findByStoreApp(storeId, appId);
@@ -1192,10 +1248,15 @@ export class Catalox {
1192
1248
  if (context.superAdmin)
1193
1249
  return records;
1194
1250
  // non-god: only reveal memberships that include the caller's own appId
1195
- return records.filter((r) => r.appId === context.appId);
1251
+ return context.appId ? records.filter((r) => r.appId === context.appId) : [];
1196
1252
  }
1197
1253
  async ensureCatalog(context, catalog) {
1198
- await this.deps.authz.requireBindingAccess(context, context.appId, catalog.catalogId, "admin");
1254
+ const appId = await this.resolveAppIdForCatalogAccess({
1255
+ context,
1256
+ catalogId: catalog.catalogId,
1257
+ required: "admin",
1258
+ });
1259
+ await this.deps.authz.requireBindingAccess(context, appId, catalog.catalogId, "admin");
1199
1260
  const existing = await this.deps.catalogs.get(catalog.catalogId);
1200
1261
  const now = new Date().toISOString();
1201
1262
  if (existing)
@@ -1211,7 +1272,7 @@ export class Catalox {
1211
1272
  }
1212
1273
  async ensureBinding(context, input) {
1213
1274
  // only super-admin apps can provision cross-app bindings.
1214
- if (!context.superAdmin && input.appId !== context.appId) {
1275
+ if (!context.superAdmin && (!context.appId || input.appId !== context.appId)) {
1215
1276
  throw new CatalogAccessDeniedError({ reason: "not_super_admin" });
1216
1277
  }
1217
1278
  const existing = await this.deps.bindings.findByAppCatalog(input.appId, input.catalogId);
@@ -1236,7 +1297,8 @@ export class Catalox {
1236
1297
  return this.upsertNativeCatalogItem(_context, _catalogId, _input);
1237
1298
  }
1238
1299
  async updateNativeCatalogItem(_context, _catalogId, _itemId, _patch, _options) {
1239
- await this.deps.authz.requireBindingAccess(_context, _context.appId, _catalogId, "write");
1300
+ const appId = await this.resolveAppIdForCatalogAccess({ context: _context, catalogId: _catalogId, required: "write" });
1301
+ await this.deps.authz.requireBindingAccess(_context, appId, _catalogId, "write");
1240
1302
  let existing = null;
1241
1303
  if (_options?.storageDocId) {
1242
1304
  existing = await this.deps.nativeItems.get(_catalogId, _options.storageDocId);
@@ -1292,7 +1354,7 @@ export class Catalox {
1292
1354
  const out = {
1293
1355
  itemId: nextRec.itemId,
1294
1356
  catalogId: _catalogId,
1295
- appId: _context.appId,
1357
+ appId,
1296
1358
  sourceMode: "native",
1297
1359
  sourceType: "firebase",
1298
1360
  data: mergedData,
@@ -1302,7 +1364,8 @@ export class Catalox {
1302
1364
  return this.decorateItem(_catalogId, out);
1303
1365
  }
1304
1366
  async deleteNativeCatalogItem(_context, _catalogId, _itemId, _options) {
1305
- await this.deps.authz.requireBindingAccess(_context, _context.appId, _catalogId, "write");
1367
+ const appId = await this.resolveAppIdForCatalogAccess({ context: _context, catalogId: _catalogId, required: "write" });
1368
+ await this.deps.authz.requireBindingAccess(_context, appId, _catalogId, "write");
1306
1369
  let docId = _options?.storageDocId;
1307
1370
  if (!docId) {
1308
1371
  let rows = await this.deps.nativeItems.findByLogicalItemId(_catalogId, _itemId);
@@ -1333,7 +1396,8 @@ export class Catalox {
1333
1396
  if (!context.superAdmin) {
1334
1397
  throw new CatalogAccessDeniedError({ reason: "super_admin_required" });
1335
1398
  }
1336
- await this.deps.authz.requireBindingAccess(context, context.appId, catalogId, "write");
1399
+ const appId = await this.resolveAppIdForCatalogAccess({ context, catalogId, required: "write" });
1400
+ await this.deps.authz.requireBindingAccess(context, appId, catalogId, "write");
1337
1401
  const fromDoc = input.fromStorageDocId ??
1338
1402
  encodeNativeItemStorageDocId(String(itemId), normalizeStoredScope(input.fromScope ?? { kind: "global" }));
1339
1403
  const rec = await this.deps.nativeItems.get(catalogId, fromDoc);
@@ -1362,7 +1426,8 @@ export class Catalox {
1362
1426
  });
1363
1427
  }
1364
1428
  async upsertNativeCatalogItem(context, catalogId, input) {
1365
- await this.deps.authz.requireBindingAccess(context, context.appId, catalogId, "write");
1429
+ const appId = await this.resolveAppIdForCatalogAccess({ context, catalogId, required: "write" });
1430
+ await this.deps.authz.requireBindingAccess(context, appId, catalogId, "write");
1366
1431
  const descriptor = await this.deps.descriptors.get(catalogId);
1367
1432
  if (!descriptor)
1368
1433
  throw new CatalogAdapterError({ catalogId, reason: "missing_descriptor" });
@@ -1381,7 +1446,7 @@ export class Catalox {
1381
1446
  itemId: logicalItemId,
1382
1447
  catalogId,
1383
1448
  ...(storedScope.kind !== "global" ? { scope: storedScope } : {}),
1384
- appScopedOwnerId: context.appId,
1449
+ appScopedOwnerId: appId,
1385
1450
  ...(indexed != null ? { indexed } : {}),
1386
1451
  data,
1387
1452
  version: (existing?.version ?? 0) + 1,
@@ -1402,7 +1467,7 @@ export class Catalox {
1402
1467
  return {
1403
1468
  itemId: logicalItemId,
1404
1469
  catalogId,
1405
- appId: context.appId,
1470
+ appId,
1406
1471
  sourceMode: "native",
1407
1472
  sourceType: "firebase",
1408
1473
  data,
@@ -1411,7 +1476,8 @@ export class Catalox {
1411
1476
  };
1412
1477
  }
1413
1478
  async batchUpsertNativeCatalogItems(context, catalogId, items) {
1414
- await this.deps.authz.requireBindingAccess(context, context.appId, catalogId, "write");
1479
+ const appId = await this.resolveAppIdForCatalogAccess({ context, catalogId, required: "write" });
1480
+ await this.deps.authz.requireBindingAccess(context, appId, catalogId, "write");
1415
1481
  const descriptor = await this.deps.descriptors.get(catalogId);
1416
1482
  if (!descriptor)
1417
1483
  throw new CatalogAdapterError({ catalogId, reason: "missing_descriptor" });
@@ -1432,7 +1498,7 @@ export class Catalox {
1432
1498
  itemId: logicalItemId,
1433
1499
  catalogId,
1434
1500
  ...(storedScope.kind !== "global" ? { scope: storedScope } : {}),
1435
- appScopedOwnerId: context.appId,
1501
+ appScopedOwnerId: appId,
1436
1502
  ...(indexed != null ? { indexed } : {}),
1437
1503
  data: stripped.data,
1438
1504
  createdAt: now,
@@ -1803,7 +1869,8 @@ export class Catalox {
1803
1869
  });
1804
1870
  }
1805
1871
  async listCatalogItemHistory(context, catalogId, input) {
1806
- await this.deps.authz.requireBindingAccess(context, context.appId, catalogId, "read");
1872
+ const appId = await this.resolveAppIdForCatalogAccess({ context, catalogId, required: "read" });
1873
+ await this.deps.authz.requireBindingAccess(context, appId, catalogId, "read");
1807
1874
  const rh = this.deps.recordHistory;
1808
1875
  if (!rh)
1809
1876
  return { events: [] };
@@ -1816,7 +1883,8 @@ export class Catalox {
1816
1883
  const row = await rh.store.get(eventId);
1817
1884
  if (!row)
1818
1885
  return null;
1819
- await this.deps.authz.requireBindingAccess(context, context.appId, row.catalogId, "read");
1886
+ const appId = await this.resolveAppIdForCatalogAccess({ context, catalogId: row.catalogId, required: "read" });
1887
+ await this.deps.authz.requireBindingAccess(context, appId, row.catalogId, "read");
1820
1888
  const payload = await readRecordHistoryEventPayload(rh, row);
1821
1889
  return { index: row, payload };
1822
1890
  }
@@ -1827,7 +1895,8 @@ export class Catalox {
1827
1895
  const row = await rh.store.get(eventId);
1828
1896
  if (!row)
1829
1897
  throw new Error(`catalogItemHistory event not found: ${eventId}`);
1830
- await this.deps.authz.requireBindingAccess(context, context.appId, row.catalogId, "write");
1898
+ const appId = await this.resolveAppIdForCatalogAccess({ context, catalogId: row.catalogId, required: "write" });
1899
+ await this.deps.authz.requireBindingAccess(context, appId, row.catalogId, "write");
1831
1900
  const payload = await readRecordHistoryEventPayload(rh, row);
1832
1901
  const rec = input.mode === "before" ? payload.before ?? undefined : payload.after ?? undefined;
1833
1902
  if (!rec)
@@ -1847,7 +1916,7 @@ export class Catalox {
1847
1916
  return this.decorateItem(row.catalogId, {
1848
1917
  itemId: rec.itemId,
1849
1918
  catalogId: row.catalogId,
1850
- appId: context.appId,
1919
+ appId,
1851
1920
  sourceMode: "native",
1852
1921
  sourceType: "firebase",
1853
1922
  data: liveAfter.data,
@@ -1856,7 +1925,8 @@ export class Catalox {
1856
1925
  });
1857
1926
  }
1858
1927
  async replayCatalogToPointInTime(context, catalogId, input) {
1859
- await this.deps.authz.requireBindingAccess(context, context.appId, catalogId, "write");
1928
+ const appId = await this.resolveAppIdForCatalogAccess({ context, catalogId, required: "write" });
1929
+ await this.deps.authz.requireBindingAccess(context, appId, catalogId, "write");
1860
1930
  const rh = this.deps.recordHistory;
1861
1931
  if (!rh)
1862
1932
  throw new Error("recordHistory is not configured on Catalox");
@@ -1908,8 +1978,9 @@ export class Catalox {
1908
1978
  return { upserted, deleted };
1909
1979
  }
1910
1980
  async deleteCatalog(context, catalogId, input) {
1911
- await this.deps.authz.requireBindingAccess(context, context.appId, catalogId, "admin");
1912
- return runDeleteCatalog(this.catalogLifecycleDeps(), this.deps.recordHistory, context, catalogId, input);
1981
+ const appId = await this.resolveAppIdForCatalogAccess({ context, catalogId, required: "admin" });
1982
+ await this.deps.authz.requireBindingAccess(context, appId, catalogId, "admin");
1983
+ return runDeleteCatalog(this.catalogLifecycleDeps(), this.deps.recordHistory, { ...context, appId }, catalogId, input);
1913
1984
  }
1914
1985
  async restoreDeletedCatalog(context, input) {
1915
1986
  if (!context.superAdmin) {
@@ -1926,8 +1997,9 @@ export class Catalox {
1926
1997
  return runRestoreDeletedCatalog(this.catalogLifecycleDeps(), rh, input);
1927
1998
  }
1928
1999
  async renameCatalog(context, fromCatalogId, toCatalogId, input = {}) {
1929
- await this.deps.authz.requireBindingAccess(context, context.appId, fromCatalogId, "admin");
1930
- return runRenameCatalog(this.catalogLifecycleDeps(), this.deps.recordHistory, context, fromCatalogId, toCatalogId, input);
2000
+ const appId = await this.resolveAppIdForCatalogAccess({ context, catalogId: fromCatalogId, required: "admin" });
2001
+ await this.deps.authz.requireBindingAccess(context, appId, fromCatalogId, "admin");
2002
+ return runRenameCatalog(this.catalogLifecycleDeps(), this.deps.recordHistory, { ...context, appId }, fromCatalogId, toCatalogId, input);
1931
2003
  }
1932
2004
  /**
1933
2005
  * Fix {@link CataloxContext} for subsequent calls so embedders omit it on each method (no globals).