@peers-app/peers-sdk 0.25.20 → 0.26.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 (94) hide show
  1. package/dist/contracts/__tests__/builder.test.js +0 -2
  2. package/dist/contracts/__tests__/contract-provider-router.test.js +51 -0
  3. package/dist/contracts/__tests__/contract-proxy.test.js +21 -0
  4. package/dist/contracts/__tests__/contract-resolvers.test.js +84 -0
  5. package/dist/contracts/__tests__/legacy-removal.test.d.ts +1 -0
  6. package/dist/contracts/__tests__/legacy-removal.test.js +90 -0
  7. package/dist/contracts/__tests__/system-contracts.test.js +88 -0
  8. package/dist/contracts/__tests__/tool-catalog-inventory.test.d.ts +1 -0
  9. package/dist/contracts/__tests__/tool-catalog-inventory.test.js +44 -0
  10. package/dist/contracts/__tests__/tool-catalog-service.test.d.ts +1 -0
  11. package/dist/contracts/__tests__/tool-catalog-service.test.js +446 -0
  12. package/dist/contracts/__tests__/tool-catalog.test.d.ts +1 -0
  13. package/dist/contracts/__tests__/tool-catalog.test.js +649 -0
  14. package/dist/contracts/__tests__/workflow-context.test.d.ts +1 -0
  15. package/dist/contracts/__tests__/workflow-context.test.js +61 -0
  16. package/dist/contracts/builder.d.ts +9 -8
  17. package/dist/contracts/builder.js +34 -16
  18. package/dist/contracts/contract-provider-router.d.ts +12 -1
  19. package/dist/contracts/contract-provider-router.js +40 -2
  20. package/dist/contracts/contract-proxy.d.ts +20 -6
  21. package/dist/contracts/contract-proxy.js +23 -12
  22. package/dist/contracts/contract-resolvers.d.ts +22 -3
  23. package/dist/contracts/contract-resolvers.js +86 -7
  24. package/dist/contracts/extract.d.ts +7 -0
  25. package/dist/contracts/extract.js +3 -0
  26. package/dist/contracts/index.d.ts +11 -4
  27. package/dist/contracts/index.js +14 -2
  28. package/dist/contracts/registry.d.ts +3 -0
  29. package/dist/contracts/registry.js +2 -0
  30. package/dist/contracts/system/desktop-tools-contract.d.ts +5 -0
  31. package/dist/contracts/system/desktop-tools-contract.js +88 -0
  32. package/dist/contracts/system/host-tools-contract.d.ts +5 -0
  33. package/dist/contracts/system/host-tools-contract.js +76 -0
  34. package/dist/contracts/system/index.d.ts +3 -0
  35. package/dist/contracts/system/index.js +3 -0
  36. package/dist/contracts/system/tool-catalog-contract.d.ts +73 -0
  37. package/dist/contracts/system/tool-catalog-contract.js +70 -0
  38. package/dist/contracts/system-contracts.d.ts +3 -1
  39. package/dist/contracts/system-contracts.js +20 -6
  40. package/dist/contracts/tool-catalog-identity.d.ts +35 -0
  41. package/dist/contracts/tool-catalog-identity.js +88 -0
  42. package/dist/contracts/tool-catalog-migrations.d.ts +12 -0
  43. package/dist/contracts/tool-catalog-migrations.js +179 -0
  44. package/dist/contracts/tool-catalog-service.d.ts +30 -0
  45. package/dist/contracts/tool-catalog-service.js +287 -0
  46. package/dist/contracts/tool-catalog.d.ts +91 -0
  47. package/dist/contracts/tool-catalog.js +490 -0
  48. package/dist/contracts/types.d.ts +7 -5
  49. package/dist/data/messages.d.ts +3 -3
  50. package/dist/data/orm/sql.data-source.d.ts +5 -0
  51. package/dist/data/orm/sql.data-source.js +28 -10
  52. package/dist/data/orm/sql.data-source.test.d.ts +1 -1
  53. package/dist/data/orm/sql.data-source.test.js +160 -0
  54. package/dist/data/orm/table-container.d.ts +14 -0
  55. package/dist/data/orm/table-container.js +20 -0
  56. package/dist/data/tool-access-approvals.d.ts +6 -6
  57. package/dist/data/tool-catalog-fields.d.ts +31 -0
  58. package/dist/data/tool-catalog-fields.js +67 -0
  59. package/dist/data/tools.d.ts +38 -10
  60. package/dist/data/tools.js +31 -8
  61. package/dist/data/workflow-logs.d.ts +6 -2
  62. package/dist/data/workflow-logs.js +7 -5
  63. package/dist/data/workflow-runs.d.ts +6 -6
  64. package/dist/index.d.ts +0 -2
  65. package/dist/index.js +1 -3
  66. package/dist/package-installer/package-creator.d.ts +14 -5
  67. package/dist/package-installer/package-creator.js +92 -37
  68. package/dist/package-installer/package-creator.test.js +308 -23
  69. package/dist/package-installer/types.d.ts +8 -0
  70. package/dist/package-loader/contract-package-loader.d.ts +12 -3
  71. package/dist/package-loader/contract-package-loader.js +54 -50
  72. package/dist/package-loader/contract-package-loader.test.d.ts +1 -1
  73. package/dist/package-loader/contract-package-loader.test.js +172 -13
  74. package/dist/package-loader/host-authority-loading.test.js +16 -3
  75. package/dist/package-loader/isolated-artifact.d.ts +14 -2
  76. package/dist/package-loader/isolated-artifact.js +6 -0
  77. package/dist/package-loader/isolated-artifact.test.js +34 -0
  78. package/dist/package-loader/isolated-runtime.d.ts +14 -2
  79. package/dist/package-loader/isolated-table-gateway.d.ts +3 -1
  80. package/dist/package-loader/isolated-table-gateway.js +54 -5
  81. package/dist/package-loader/isolated-table-gateway.test.js +123 -0
  82. package/dist/package-loader/package-loader.d.ts +14 -2
  83. package/dist/package-loader/package-loader.js +184 -62
  84. package/dist/package-loader/package-loader.test.js +695 -2
  85. package/dist/package-loader/resolve-consumed-contracts.js +1 -1
  86. package/dist/system-ids.d.ts +6 -0
  87. package/dist/system-ids.js +7 -1
  88. package/dist/types/peers-package.d.ts +3 -2
  89. package/dist/types/workflow-run-context.d.ts +12 -2
  90. package/package.json +1 -1
  91. package/dist/tools/index.d.ts +0 -1
  92. package/dist/tools/index.js +0 -17
  93. package/dist/tools/tools-factory.d.ts +0 -5
  94. package/dist/tools/tools-factory.js +0 -34
@@ -2,6 +2,7 @@ import type { DataContext } from "../context/data-context";
2
2
  import { type IContractProviderEndpointRequest } from "../contracts/contract-provider-router";
3
3
  import { type IContractConsumer, type IContractProviderEndpoint } from "../contracts/contract-proxy";
4
4
  import { ContractRegistry } from "../contracts/registry";
5
+ import { ToolCatalog } from "../contracts/tool-catalog";
5
6
  import type { IConsumedContract, IRemoteContract, IRemoteContractConsumer } from "../contracts/types";
6
7
  import { type IPackage } from "../data/packages";
7
8
  import type { IPeersPackage } from "../types/peers-package";
@@ -29,17 +30,25 @@ export declare class PackageLoader {
29
30
  /** Host-injected Zod module used as the `zod` require. */
30
31
  static Zod: any;
31
32
  private hostConsumes;
33
+ private loadGenerations;
34
+ private hostActivationGenerations;
35
+ private consumedContractRouters;
36
+ private disposed;
32
37
  private packageInstances;
33
- /** Packages that were installed via the contract-based path (tools/assistants/tables already saved with packageId). */
38
+ /** Packages installed through definePackage or isolation; providers own their executable members. */
34
39
  private contractInstalledPackages;
35
40
  /** Packages installed through the isolated runtime (no host eval). */
36
41
  private isolatedPackageIds;
37
42
  /**
38
43
  * In-memory contract providers for this data context. Seeded with system
39
- * contracts at construction; package providers are added via
44
+ * contracts after catalog readiness; package providers are added via
40
45
  * {@link installContractPackage} or the isolated runtime.
41
46
  */
42
47
  readonly contractRegistry: ContractRegistry;
48
+ /** Permanent tool metadata owner for this context. */
49
+ readonly toolCatalog: ToolCatalog;
50
+ /** Lazy, retryable readiness boundary; performs no constructor-time database work. */
51
+ get catalogReady(): Promise<void>;
43
52
  /** Optional isolated-provider supervisor created from the host factory. */
44
53
  readonly isolatedRuntime?: IIsolatedPackageRuntime;
45
54
  /** Optional host `require` used when reading local bundles or evaluating legacy packages. */
@@ -87,6 +96,7 @@ export declare class PackageLoader {
87
96
  * @throws If no provider or matching endpoint implementation is available.
88
97
  */
89
98
  resolveContractEndpoint(request: IContractProviderEndpointRequest, opts?: IPackageContractEndpointOptions): Promise<IContractProviderEndpoint>;
99
+ private guardResolutionTools;
90
100
  /**
91
101
  * Bind a legacy package's declared consume to its selected provider.
92
102
  *
@@ -129,6 +139,7 @@ export declare class PackageLoader {
129
139
  * Dispose the isolated runtime for this data context.
130
140
  */
131
141
  dispose(): Promise<void>;
142
+ private retireHostEndpoints;
132
143
  /**
133
144
  * Resolve and load every non-disabled package in this data context.
134
145
  * Disabled packages that were previously loaded are unloaded.
@@ -156,6 +167,7 @@ export declare class PackageLoader {
156
167
  packageVersionId?: string;
157
168
  }): Promise<IPeersPackage | undefined>;
158
169
  private _readLocalBundle;
170
+ private guardLoad;
159
171
  /**
160
172
  * Post-correction: if the package definition carries a version string,
161
173
  * update the selected IPackageVersion record to match — but only when that
@@ -8,13 +8,11 @@ const contract_resolvers_1 = require("../contracts/contract-resolvers");
8
8
  const contract_transport_1 = require("../contracts/contract-transport");
9
9
  const registry_1 = require("../contracts/registry");
10
10
  const remote_contract_transport_1 = require("../contracts/remote-contract-transport");
11
- const system_contracts_1 = require("../contracts/system-contracts");
11
+ const tool_catalog_1 = require("../contracts/tool-catalog");
12
12
  const files_1 = require("../data/files");
13
13
  const package_version_resolver_1 = require("../data/package-version-resolver");
14
14
  const package_versions_1 = require("../data/package-versions");
15
15
  const packages_1 = require("../data/packages");
16
- const tools_1 = require("../data/tools");
17
- const tools_2 = require("../tools");
18
16
  const contract_package_loader_1 = require("./contract-package-loader");
19
17
  const isolated_artifact_1 = require("./isolated-artifact");
20
18
  const isolated_persistent_var_gateway_1 = require("./isolated-persistent-var-gateway");
@@ -46,17 +44,27 @@ class PackageLoader {
46
44
  /** Host-injected Zod module used as the `zod` require. */
47
45
  static Zod;
48
46
  hostConsumes = new Map();
47
+ loadGenerations = new Map();
48
+ hostActivationGenerations = new Map();
49
+ consumedContractRouters = new Set();
50
+ disposed = false;
49
51
  packageInstances = {};
50
- /** Packages that were installed via the contract-based path (tools/assistants/tables already saved with packageId). */
52
+ /** Packages installed through definePackage or isolation; providers own their executable members. */
51
53
  contractInstalledPackages = new Set();
52
54
  /** Packages installed through the isolated runtime (no host eval). */
53
55
  isolatedPackageIds = new Set();
54
56
  /**
55
57
  * In-memory contract providers for this data context. Seeded with system
56
- * contracts at construction; package providers are added via
58
+ * contracts after catalog readiness; package providers are added via
57
59
  * {@link installContractPackage} or the isolated runtime.
58
60
  */
59
61
  contractRegistry;
62
+ /** Permanent tool metadata owner for this context. */
63
+ toolCatalog;
64
+ /** Lazy, retryable readiness boundary; performs no constructor-time database work. */
65
+ get catalogReady() {
66
+ return this.toolCatalog.ready();
67
+ }
60
68
  /** Optional isolated-provider supervisor created from the host factory. */
61
69
  isolatedRuntime;
62
70
  /** Optional host `require` used when reading local bundles or evaluating legacy packages. */
@@ -69,7 +77,7 @@ class PackageLoader {
69
77
  constructor(dataContext) {
70
78
  this.dataContext = dataContext;
71
79
  this.contractRegistry = new registry_1.ContractRegistry();
72
- (0, system_contracts_1.installSystemContracts)(this.contractRegistry);
80
+ this.toolCatalog = new tool_catalog_1.ToolCatalog(dataContext, this.contractRegistry);
73
81
  const factory = (0, isolated_runtime_1.getIsolatedPackageRuntimeFactory)();
74
82
  if (factory) {
75
83
  this.isolatedRuntime = factory({
@@ -194,6 +202,7 @@ class PackageLoader {
194
202
  * @throws If no provider or matching endpoint implementation is available.
195
203
  */
196
204
  async resolveContractEndpoint(request, opts = {}) {
205
+ await this.catalogReady;
197
206
  // A route that names another device is forwarded over the mesh instead of being
198
207
  // resolved against a local provider. Only routers serving local principals set
199
208
  // targetDeviceId; remote-caller routers reject it before reaching this point.
@@ -208,7 +217,7 @@ class PackageLoader {
208
217
  request.providerPackageId ??
209
218
  this.contractRegistry.getProviderPackageId(request.contractId, request.version);
210
219
  if (!providerPackageId) {
211
- const legacyResolver = (0, contract_resolvers_1.getContractResolver)(request.contractId, request.version);
220
+ const legacyResolver = (0, contract_resolvers_1.getContractResolver)(request.contractId, request.version, undefined, this.dataContext);
212
221
  if (!legacyResolver) {
213
222
  throw new Error(`No contract provider registered for ${request.contractId} v${request.version}`);
214
223
  }
@@ -222,10 +231,28 @@ class PackageLoader {
222
231
  },
223
232
  }
224
233
  : resolution;
225
- return (0, contract_proxy_1.createContractProviderEndpoint)(localResolution, {
226
- context: request.context,
227
- notify: request.notify,
228
- });
234
+ const assertProviderCurrent = () => {
235
+ if (this.disposed ||
236
+ this.contractRegistry.getProviderPackageId(request.contractId, request.version) ||
237
+ (0, contract_resolvers_1.getContractResolver)(request.contractId, request.version, undefined, this.dataContext) !==
238
+ legacyResolver)
239
+ throw new contract_provider_router_1.RetiredContractProviderEndpointError(guardedEndpoint);
240
+ };
241
+ const endpoint = (0, contract_proxy_1.createContractProviderEndpoint)(this.guardResolutionTools(localResolution, request, assertProviderCurrent), { context: request.context, notify: request.notify });
242
+ const guardedEndpoint = {
243
+ handleCall: async (call, context) => {
244
+ if (call.member === "tool")
245
+ await this.toolCatalog.assertTargetUsable({
246
+ contractId: request.contractId,
247
+ version: request.version,
248
+ toolName: call.name,
249
+ });
250
+ assertProviderCurrent();
251
+ return endpoint.handleCall(call, context);
252
+ },
253
+ dispose: () => endpoint.dispose(),
254
+ };
255
+ return guardedEndpoint;
229
256
  }
230
257
  if (!this.contractRegistry
231
258
  .getRegisteredProviderIds(request.contractId, request.version)
@@ -247,10 +274,11 @@ class PackageLoader {
247
274
  contractId: request.contractId,
248
275
  version: request.version,
249
276
  };
250
- const resolver = (0, contract_resolvers_1.getContractResolver)(implementation.contractId, implementation.version, providerPackageId);
277
+ const resolver = (0, contract_resolvers_1.getContractResolver)(implementation.contractId, implementation.version, providerPackageId, this.dataContext);
251
278
  if (!resolver) {
252
279
  throw new Error(`Provider ${providerPackageId} has no live resolver for ${request.contractId} v${request.version}`);
253
280
  }
281
+ const generation = this.hostActivationGenerations.get(providerPackageId);
254
282
  const resolution = await resolver({ dataContext: this.dataContext });
255
283
  const localResolution = opts.resolveVariable
256
284
  ? {
@@ -261,7 +289,16 @@ class PackageLoader {
261
289
  },
262
290
  }
263
291
  : resolution;
264
- const endpoint = (0, contract_proxy_1.createContractProviderEndpoint)(localResolution, {
292
+ const assertProviderCurrent = () => {
293
+ const current = this.contractRegistry.getProviderImplementation(request.contractId, request.version, providerPackageId);
294
+ if (this.disposed ||
295
+ this.hostActivationGenerations.get(providerPackageId) !== generation ||
296
+ current?.contractId !== implementation.contractId ||
297
+ current?.version !== implementation.version ||
298
+ (0, contract_resolvers_1.getContractResolver)(implementation.contractId, implementation.version, providerPackageId, this.dataContext) !== resolver)
299
+ throw new contract_provider_router_1.RetiredContractProviderEndpointError(guardedEndpoint);
300
+ };
301
+ const endpoint = (0, contract_proxy_1.createContractProviderEndpoint)(this.guardResolutionTools(localResolution, request, assertProviderCurrent), {
265
302
  dispatchTool: (target, args, context) => this.dispatchToolFromProvider(providerPackageId, target, args, context),
266
303
  context: request.context,
267
304
  notify: (notification) => request.notify({
@@ -269,18 +306,45 @@ class PackageLoader {
269
306
  contractId: request.contractId,
270
307
  }),
271
308
  });
272
- if (implementation.contractId === request.contractId &&
273
- implementation.version === request.version) {
274
- return endpoint;
275
- }
276
- return {
277
- handleCall: (call, context) => endpoint.handleCall({
278
- ...call,
279
- contractId: implementation.contractId,
280
- version: implementation.version,
281
- }, context),
309
+ const guardedEndpoint = {
310
+ handleCall: async (call, context) => {
311
+ if (call.member === "tool")
312
+ await this.toolCatalog.assertTargetUsable({
313
+ contractId: request.contractId,
314
+ version: request.version,
315
+ toolName: call.name,
316
+ });
317
+ assertProviderCurrent();
318
+ return endpoint.handleCall({ ...call, contractId: implementation.contractId, version: implementation.version }, context);
319
+ },
282
320
  dispose: () => endpoint.dispose(),
283
321
  };
322
+ return guardedEndpoint;
323
+ }
324
+ guardResolutionTools(resolution, request, assertProviderCurrent) {
325
+ const guard = (instances) => instances &&
326
+ Object.fromEntries(Object.entries(instances).map(([name, instance]) => [
327
+ name,
328
+ {
329
+ ...instance,
330
+ // The dispatcher awaits authorization before calling toolFn. Check again
331
+ // here so those awaits cannot retain old code. Once provider code has
332
+ // started, its nested action errors must not request a router replay.
333
+ toolFn: async (...args) => {
334
+ await this.toolCatalog.assertTargetUsable({
335
+ contractId: request.contractId,
336
+ version: request.version,
337
+ toolName: name,
338
+ });
339
+ assertProviderCurrent();
340
+ return instance.toolFn(...args);
341
+ },
342
+ },
343
+ ]));
344
+ return {
345
+ ...resolution,
346
+ tools: guard(resolution.tools),
347
+ };
284
348
  }
285
349
  /**
286
350
  * Bind a legacy package's declared consume to its selected provider.
@@ -293,6 +357,7 @@ class PackageLoader {
293
357
  * @returns A live consumer proxy, or `undefined` when no provider matches.
294
358
  */
295
359
  async resolveConsumedContract(dependency) {
360
+ await this.catalogReady;
296
361
  const selected = this.contractRegistry.selectProvider(dependency);
297
362
  if (!selected) {
298
363
  return undefined;
@@ -310,10 +375,12 @@ class PackageLoader {
310
375
  const consumer = (0, contract_proxy_1.createContractConsumer)(selected.definition, consumerTransport, {
311
376
  dataContextId: this.dataContext.dataContextId,
312
377
  });
378
+ this.consumedContractRouters.add(router);
313
379
  const dispose = consumer.dispose.bind(consumer);
314
380
  consumer.dispose = () => {
315
381
  dispose();
316
382
  router.dispose();
383
+ this.consumedContractRouters.delete(router);
317
384
  };
318
385
  return consumer;
319
386
  }
@@ -331,6 +398,7 @@ class PackageLoader {
331
398
  * @returns A per-device consumer factory, or `undefined` when the contract is unknown.
332
399
  */
333
400
  async resolveRemoteContract(declaration) {
401
+ await this.catalogReady;
334
402
  const definition = this.contractRegistry.getDefinition(declaration.contractId, declaration.version);
335
403
  if (!definition) {
336
404
  return undefined;
@@ -376,26 +444,50 @@ class PackageLoader {
376
444
  * @param packageId - Package to unload.
377
445
  */
378
446
  async unloadPackage(packageId) {
379
- this.hostConsumes.delete(packageId);
380
- if (this.isolatedRuntime?.isInstalled(packageId)) {
381
- await this.isolatedRuntime.uninstall(packageId);
382
- }
383
- this.contractRegistry.unregister(packageId);
384
- this.isolatedPackageIds.delete(packageId);
385
- this.contractInstalledPackages.delete(packageId);
386
- delete this.packageInstances[packageId];
447
+ // Invalidate loads before the first await, including those still resolving
448
+ // bundles or consumed contracts outside the catalog publication queue.
449
+ this.loadGenerations.set(packageId, (this.loadGenerations.get(packageId) ?? 0) + 1);
450
+ // Uninstall also queues behind an install that has not published yet.
451
+ await this.isolatedRuntime?.uninstall(packageId);
452
+ await this.toolCatalog.removeProvider(packageId, () => {
453
+ this.retireHostEndpoints(packageId);
454
+ (0, contract_resolvers_1.unregisterPackageContractResolvers)(this.dataContext, packageId);
455
+ this.hostConsumes.delete(packageId);
456
+ this.isolatedPackageIds.delete(packageId);
457
+ this.contractInstalledPackages.delete(packageId);
458
+ delete this.packageInstances[packageId];
459
+ });
387
460
  }
388
461
  /**
389
462
  * Dispose the isolated runtime for this data context.
390
463
  */
391
464
  async dispose() {
465
+ this.disposed = true;
466
+ for (const router of this.consumedContractRouters)
467
+ router.dispose();
468
+ this.consumedContractRouters.clear();
469
+ await this.toolCatalog.dispose();
392
470
  if (this.isolatedRuntime) {
393
471
  await this.isolatedRuntime.dispose();
394
472
  }
473
+ for (const packageId of new Set([
474
+ ...Object.keys(this.packageInstances),
475
+ ...this.contractInstalledPackages,
476
+ ...this.hostConsumes.keys(),
477
+ ])) {
478
+ this.contractRegistry.unregister(packageId);
479
+ (0, contract_resolvers_1.unregisterPackageContractResolvers)(this.dataContext, packageId);
480
+ }
481
+ this.hostConsumes.clear();
395
482
  this.isolatedPackageIds.clear();
396
483
  this.contractInstalledPackages.clear();
397
484
  this.packageInstances = {};
398
485
  }
486
+ retireHostEndpoints(packageId) {
487
+ this.hostActivationGenerations.set(packageId, (this.hostActivationGenerations.get(packageId) ?? 0) + 1);
488
+ for (const router of this.consumedContractRouters)
489
+ router.invalidateProvider(packageId);
490
+ }
399
491
  /**
400
492
  * Resolve and load every non-disabled package in this data context.
401
493
  * Disabled packages that were previously loaded are unloaded.
@@ -403,6 +495,7 @@ class PackageLoader {
403
495
  * @param opts - Forwarded to {@link resolveDevicePackageVersion}.
404
496
  */
405
497
  async loadAllPackages(opts) {
498
+ await this.catalogReady;
406
499
  const packages = await (0, packages_1.Packages)(this.dataContext).list();
407
500
  await Promise.all(packages.map(async (pkg) => {
408
501
  if (pkg.disabled) {
@@ -434,6 +527,7 @@ class PackageLoader {
434
527
  await this.unloadPackage(pkg.packageId);
435
528
  return undefined;
436
529
  }
530
+ const assertCurrent = this.guardLoad(pkg.packageId);
437
531
  if (this.packageInstances[pkg.packageId] && !opts?.force) {
438
532
  return this.packageInstances[pkg.packageId];
439
533
  }
@@ -478,7 +572,7 @@ class PackageLoader {
478
572
  }
479
573
  if (!bundleCode)
480
574
  return;
481
- return await this._loadBundle(pkg, bundleCode, selectedPackageVersion, usingLocalBundle, opts?.force === true);
575
+ return await this._loadBundle(pkg, bundleCode, selectedPackageVersion, usingLocalBundle, opts?.force === true, assertCurrent);
482
576
  }
483
577
  catch (err) {
484
578
  if (isIsolatedArtifactError(err)) {
@@ -489,7 +583,7 @@ class PackageLoader {
489
583
  try {
490
584
  const localBundleCode = this._readLocalBundle(localPath);
491
585
  if (localBundleCode) {
492
- return await this._loadBundle(pkg, localBundleCode, selectedPackageVersion, true, opts?.force === true);
586
+ return await this._loadBundle(pkg, localBundleCode, selectedPackageVersion, true, opts?.force === true, assertCurrent);
493
587
  }
494
588
  }
495
589
  catch (localErr) {
@@ -516,6 +610,13 @@ class PackageLoader {
516
610
  return;
517
611
  }
518
612
  }
613
+ guardLoad(packageId) {
614
+ const generation = this.loadGenerations.get(packageId) ?? 0;
615
+ return () => {
616
+ if (this.disposed || generation !== (this.loadGenerations.get(packageId) ?? 0))
617
+ throw new Error(`Package load invalidated: ${packageId}`);
618
+ };
619
+ }
519
620
  /**
520
621
  * Post-correction: if the package definition carries a version string,
521
622
  * update the selected IPackageVersion record to match — but only when that
@@ -546,18 +647,21 @@ class PackageLoader {
546
647
  console.warn(`[PackageLoader] Failed to correct version for ${pkg.name}:`, error instanceof Error ? error.message : String(error));
547
648
  }
548
649
  }
549
- async _loadBundle(pkg, bundleCode, selectedPackageVersion, usingLocalBundle, force) {
650
+ async _loadBundle(pkg, bundleCode, selectedPackageVersion, usingLocalBundle, force, assertCurrent = this.guardLoad(pkg.packageId)) {
651
+ assertCurrent();
550
652
  if ((0, isolated_artifact_1.looksLikeIsolatedPackageArtifact)(bundleCode)) {
551
653
  try {
552
654
  const artifact = (0, isolated_artifact_1.parseIsolatedPackageArtifact)(bundleCode);
553
- return await this._installIsolated(pkg, artifact, selectedPackageVersion, usingLocalBundle, force);
655
+ return await this._installIsolated(pkg, artifact, selectedPackageVersion, usingLocalBundle, force, assertCurrent);
554
656
  }
555
657
  catch (err) {
556
658
  throw wrapIsolatedArtifactError(err);
557
659
  }
558
660
  }
661
+ await this.catalogReady;
662
+ assertCurrent();
559
663
  const replacingIsolated = this.isolatedRuntime?.isInstalled(pkg.packageId) ?? false;
560
- const instance = await this._evaluateBundle(pkg, bundleCode, selectedPackageVersion, force);
664
+ const instance = await this._evaluateBundle(pkg, bundleCode, selectedPackageVersion, force, assertCurrent);
561
665
  if (!instance) {
562
666
  return undefined;
563
667
  }
@@ -569,7 +673,8 @@ class PackageLoader {
569
673
  }
570
674
  return instance;
571
675
  }
572
- async _installIsolated(pkg, artifact, selectedPackageVersion, usingLocalBundle, force) {
676
+ async _installIsolated(pkg, artifact, selectedPackageVersion, usingLocalBundle, force, assertCurrent) {
677
+ assertCurrent();
573
678
  if (artifact.manifest.packageId !== pkg.packageId) {
574
679
  throw wrapIsolatedArtifactError(new Error(`Isolated packageId mismatch: artifact has "${artifact.manifest.packageId}" but DB record has "${pkg.packageId}"`));
575
680
  }
@@ -579,6 +684,7 @@ class PackageLoader {
579
684
  if (artifact.manifest.version !== selectedPackageVersion.version) {
580
685
  if (usingLocalBundle && selectedPackageVersion.versionTag === "dev") {
581
686
  await this.correctPackageVersion(pkg, selectedPackageVersion, artifact.manifest.version);
687
+ assertCurrent();
582
688
  if (artifact.manifest.version !== selectedPackageVersion.version) {
583
689
  throw wrapIsolatedArtifactError(new Error(`Could not correct selected dev PackageVersion to isolated artifact version "${artifact.manifest.version}"`));
584
690
  }
@@ -592,7 +698,8 @@ class PackageLoader {
592
698
  console.warn(`[PackageLoader] Skipping isolated package "${pkg.name}" because this host does not provide an isolated runtime.`);
593
699
  return undefined;
594
700
  }
595
- const installOptions = isolatedContractLifecycle(selectedPackageVersion);
701
+ assertCurrent();
702
+ const installOptions = { ...isolatedContractLifecycle(selectedPackageVersion), assertCurrent };
596
703
  if (this.isolatedRuntime.isInstalled(pkg.packageId)) {
597
704
  if (!force) {
598
705
  return this.packageInstances[pkg.packageId];
@@ -602,6 +709,9 @@ class PackageLoader {
602
709
  else {
603
710
  await this.isolatedRuntime.install(artifact, installOptions);
604
711
  }
712
+ assertCurrent();
713
+ this.retireHostEndpoints(pkg.packageId);
714
+ (0, contract_resolvers_1.unregisterPackageContractResolvers)(this.dataContext, pkg.packageId);
605
715
  this.isolatedPackageIds.add(pkg.packageId);
606
716
  this.contractInstalledPackages.add(pkg.packageId);
607
717
  const instance = {
@@ -611,7 +721,7 @@ class PackageLoader {
611
721
  this.packageInstances[pkg.packageId] = instance;
612
722
  return instance;
613
723
  }
614
- async _evaluateBundle(pkg, bundleCode, selectedPackageVersion, force) {
724
+ async _evaluateBundle(pkg, bundleCode, selectedPackageVersion, force, assertCurrent = this.guardLoad(pkg.packageId)) {
615
725
  // Node.js built-in modules that do not exist in React Native (and would cause
616
726
  // a fatal native error if passed through to RN's require). We block them here
617
727
  // and throw a descriptive JS Error instead, which the surrounding try/catch
@@ -675,8 +785,9 @@ class PackageLoader {
675
785
  }
676
786
  };
677
787
  try {
788
+ assertCurrent();
678
789
  const bundleFunction = new Function("module", "exports", "require", bundleCode);
679
- bundleFunction(module, moduleExports, customRequire);
790
+ const activateResolvers = (0, contract_resolvers_1.stagePackageContractResolvers)(this.dataContext, pkg.packageId, () => bundleFunction(module, moduleExports, customRequire));
680
791
  const bundleExports = module.exports;
681
792
  // Check for contract-based package definition (new path)
682
793
  const packageDefinition = (0, contract_package_loader_1.extractPackageDefinition)(bundleExports);
@@ -684,42 +795,53 @@ class PackageLoader {
684
795
  if (packageDefinition.packageId !== pkg.packageId) {
685
796
  console.warn(`[PackageLoader] packageId mismatch: definition has "${packageDefinition.packageId}" but DB record has "${pkg.packageId}"`);
686
797
  }
687
- await (0, resolve_consumed_contracts_1.resolveConsumedContracts)(this.dataContext, this.contractRegistry, packageDefinition);
688
- await (0, contract_package_loader_1.installContractPackage)(this.dataContext, packageDefinition, {
689
- registry: this.contractRegistry,
690
- });
691
- this.hostConsumes.set(packageDefinition.packageId, packageDefinition.consumes);
692
- this.contractInstalledPackages.add(packageDefinition.packageId);
693
- await this.correctPackageVersion(pkg, selectedPackageVersion, packageDefinition.version);
694
798
  // Build a proper IPeersPackage from the contract definition so callers
695
799
  // (e.g. package-installer) can read appNavs, assistants, etc.
696
800
  const contractPackageInstance = {
697
801
  packageId: packageDefinition.packageId,
698
802
  appNavs: packageDefinition.appNavs,
699
803
  assistants: packageDefinition.assistants,
700
- toolInstances: packageDefinition.toolInstances,
701
804
  tableDefinitions: packageDefinition.tableDefinitions,
702
805
  };
703
- this.packageInstances[pkg.packageId] = contractPackageInstance;
806
+ await (0, contract_package_loader_1.installContractPackage)(this.dataContext, packageDefinition, {
807
+ registry: this.contractRegistry,
808
+ assertCurrent,
809
+ prepare: () => this.correctPackageVersion(pkg, selectedPackageVersion, packageDefinition.version),
810
+ onActivated: () => {
811
+ activateResolvers();
812
+ this.retireHostEndpoints(pkg.packageId);
813
+ this.hostConsumes.set(packageDefinition.packageId, packageDefinition.consumes);
814
+ this.contractInstalledPackages.add(packageDefinition.packageId);
815
+ this.packageInstances[pkg.packageId] = contractPackageInstance;
816
+ },
817
+ });
818
+ // A package may declare remote/consume handles for its own contracts.
819
+ // Settle only after guarded activation makes those definitions and
820
+ // resolvers visible, outside the catalog's serialized publication turn.
821
+ await (0, resolve_consumed_contracts_1.resolveConsumedContracts)(this.dataContext, this.contractRegistry, packageDefinition);
822
+ assertCurrent();
704
823
  return contractPackageInstance;
705
824
  }
706
825
  // Legacy path: extract IPeersPackage for backward compat
707
826
  const packageInstance = bundleExports?.exports || bundleExports?.package || bundleExports?.default || bundleExports;
708
827
  if (packageInstance && typeof packageInstance === "object") {
709
- packageInstance.toolInstances?.forEach((toolInstance) => {
710
- (0, tools_2.registerTool)(toolInstance);
711
- (0, tools_1.Tools)(this.dataContext).save(toolInstance.tool);
712
- });
713
- packageInstance.tableDefinitions?.forEach((tableDefinition) => {
714
- this.dataContext.tableContainer.registerTableDefinition(tableDefinition, {
715
- overwrite: true,
716
- });
828
+ const tools = packageInstance.toolInstances;
829
+ if (Array.isArray(tools) && tools.length)
830
+ throw new Error(`Package '${pkg.name}' (${pkg.packageId}) ships legacy executable tools, which this runtime no longer runs; upgrade it to definePackage() contract tools (docs/Packages/contracts.md#migrating-from-toolinstances)`);
831
+ await this.toolCatalog.publish(pkg.packageId, [], async () => assertCurrent(), () => {
832
+ assertCurrent();
833
+ // Final catalog writes must succeed before changing active host tables.
834
+ if (packageInstance.tableDefinitions?.length) {
835
+ this.dataContext.tableContainer.registerTableDefinitions(packageInstance.tableDefinitions, { overwrite: true });
836
+ }
837
+ activateResolvers();
838
+ this.retireHostEndpoints(pkg.packageId);
839
+ if (force && this.contractInstalledPackages.has(pkg.packageId)) {
840
+ this.contractRegistry.unregister(pkg.packageId);
841
+ this.contractInstalledPackages.delete(pkg.packageId);
842
+ }
843
+ this.packageInstances[pkg.packageId] = packageInstance;
717
844
  });
718
- if (force && this.contractInstalledPackages.has(pkg.packageId)) {
719
- this.contractRegistry.unregister(pkg.packageId);
720
- this.contractInstalledPackages.delete(pkg.packageId);
721
- }
722
- this.packageInstances[pkg.packageId] = packageInstance;
723
845
  return packageInstance;
724
846
  }
725
847
  return;