@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
@@ -494,7 +494,6 @@ describe("definePackage — error cases", () => {
494
494
  });
495
495
  expect(result.contracts).toHaveLength(0);
496
496
  expect(result.consumes).toHaveLength(0);
497
- expect(result.toolInstances).toHaveLength(0);
498
497
  expect(result.tableDefinitions).toHaveLength(0);
499
498
  expect(result.appNavs).toEqual([nav]);
500
499
  });
@@ -505,7 +504,6 @@ describe("definePackage — error cases", () => {
505
504
  });
506
505
  expect(result.contracts).toHaveLength(0);
507
506
  expect(result.consumes).toHaveLength(1);
508
- expect(result.toolInstances).toHaveLength(0);
509
507
  expect(result.tableDefinitions).toHaveLength(0);
510
508
  });
511
509
  it("throws on duplicate contract ID + version", () => {
@@ -16,6 +16,57 @@ const makeEndpoint = (label) => ({
16
16
  dispose: jest.fn(),
17
17
  });
18
18
  describe("contract provider router", () => {
19
+ it("refreshes a retired cached endpoint once and shares the new route across concurrent calls", async () => {
20
+ const [consumerTransport, providerTransport] = (0, index_1.inProcessTransportPair)();
21
+ const old = makeEndpoint("old");
22
+ const current = makeEndpoint("current");
23
+ const resolveEndpoint = jest.fn().mockResolvedValueOnce(old).mockResolvedValue(current);
24
+ const router = (0, index_1.createContractProviderRouter)(providerTransport, {
25
+ authorizeCall: () => ({}),
26
+ resolveEndpoint,
27
+ });
28
+ try {
29
+ expect(await consumerTransport.emit(index_1.CONTRACT_REQUEST_CHANNEL, makeCall())).toBe("old:list");
30
+ old.handleCall.mockRejectedValue(new index_1.RetiredContractProviderEndpointError(old));
31
+ expect(await Promise.all([
32
+ consumerTransport.emit(index_1.CONTRACT_REQUEST_CHANNEL, makeCall()),
33
+ consumerTransport.emit(index_1.CONTRACT_REQUEST_CHANNEL, makeCall()),
34
+ ])).toEqual(["current:list", "current:list"]);
35
+ expect(resolveEndpoint).toHaveBeenCalledTimes(2);
36
+ expect(old.dispose).toHaveBeenCalledTimes(1);
37
+ // Another replacement in the same call fails closed instead of looping.
38
+ current.handleCall.mockRejectedValue(new index_1.RetiredContractProviderEndpointError(current));
39
+ await expect(consumerTransport.emit(index_1.CONTRACT_REQUEST_CHANNEL, makeCall())).rejects.toThrow(/retired/);
40
+ expect(resolveEndpoint).toHaveBeenCalledTimes(3);
41
+ }
42
+ finally {
43
+ router.dispose();
44
+ }
45
+ });
46
+ it("does not replay provider code when a downstream endpoint retires", async () => {
47
+ const [consumerTransport, providerTransport] = (0, index_1.inProcessTransportPair)();
48
+ const downstream = makeEndpoint("downstream");
49
+ const endpoint = makeEndpoint("caller");
50
+ const effects = jest.fn();
51
+ const failure = new index_1.RetiredContractProviderEndpointError(downstream);
52
+ endpoint.handleCall.mockImplementation(async () => {
53
+ effects();
54
+ throw failure;
55
+ });
56
+ const resolveEndpoint = jest.fn().mockResolvedValue(endpoint);
57
+ const router = (0, index_1.createContractProviderRouter)(providerTransport, {
58
+ authorizeCall: () => ({}),
59
+ resolveEndpoint,
60
+ });
61
+ try {
62
+ await expect(consumerTransport.emit(index_1.CONTRACT_REQUEST_CHANNEL, makeCall())).rejects.toBe(failure);
63
+ expect(resolveEndpoint).toHaveBeenCalledTimes(1);
64
+ expect(effects).toHaveBeenCalledTimes(1);
65
+ }
66
+ finally {
67
+ router.dispose();
68
+ }
69
+ });
19
70
  it("multiplexes routes and caches normalized contract/version/data-context endpoints", async () => {
20
71
  const [consumerTransport, providerTransport] = (0, index_1.inProcessTransportPair)();
21
72
  const endpoints = new Map();
@@ -634,3 +634,24 @@ describe("connectionContractTransport", () => {
634
634
  }));
635
635
  });
636
636
  });
637
+ test("a provider-local implementation needs no toolId and still enforces access", async () => {
638
+ const toolFn = jest.fn(() => "bound");
639
+ const dispatch = (0, contract_proxy_1.createContractProvider)({
640
+ contractId: CONTRACT_ID,
641
+ version: 1,
642
+ tools: { run: { tool: { name: "run", usageDescription: "run", accessLevel: 40 }, toolFn } },
643
+ });
644
+ const call = {
645
+ contractId: CONTRACT_ID,
646
+ version: 1,
647
+ member: "tool",
648
+ name: "run",
649
+ operation: "run",
650
+ args: [{}],
651
+ };
652
+ jest.spyOn(groupPermissions, "getUserRole").mockResolvedValue(20);
653
+ await expect(dispatch(call, { callerUserId: (0, utils_1.newid)() })).rejects.toThrow(/Permission denied/);
654
+ jest.spyOn(groupPermissions, "getUserRole").mockResolvedValue(40);
655
+ await expect(dispatch(call, { callerUserId: (0, utils_1.newid)() })).resolves.toBe("bound");
656
+ expect(toolFn).toHaveBeenCalledTimes(1);
657
+ });
@@ -167,10 +167,13 @@ describe("Self-trusted contract caller authorization", () => {
167
167
  it.each([
168
168
  ["Tool Access Control", system_ids_1.toolAccessControlContractId],
169
169
  ["Identity", system_ids_1.identityContractId],
170
+ ["Host Tools", system_ids_1.hostToolsContractId],
171
+ ["Desktop Tools", system_ids_1.desktopToolsContractId],
170
172
  ["Invites", system_ids_1.invitesContractId],
171
173
  ["HTTP", system_ids_1.httpContractId],
172
174
  ["Scheduler", system_ids_1.schedulerContractId],
173
175
  ["Contract Catalog", system_ids_1.contractCatalogContractId],
176
+ ["Tool Catalog", system_ids_1.toolCatalogContractId],
174
177
  ])("rejects remote %s as local-only", async (_name, contractId) => {
175
178
  const { remoteIdentity, user } = makeIdentity();
176
179
  const records = new Map();
@@ -260,6 +263,42 @@ describe("remote contract call authorization (group members)", () => {
260
263
  expect(route.context?.dataContext).toBe(groupDataContext);
261
264
  expect(accessLevelMock.calls).toEqual([[groupDataContext, remoteIdentity.userId]]);
262
265
  });
266
+ it("waits for catalog persistence before authorizing a cold group's first call", async () => {
267
+ const { remoteIdentity, userContext, groupDataContext } = setupGroupMember({
268
+ remoteAccessLevel: access_level_1.AccessLevel.Writer,
269
+ callerLevel: access_level_1.AccessLevel.Writer,
270
+ });
271
+ let release;
272
+ const catalogReady = new Promise((resolve) => {
273
+ release = resolve;
274
+ });
275
+ Object.assign(groupDataContext.packageLoader, { catalogReady });
276
+ const pending = (0, index_1.authorizeRemoteContractCall)(toolCall, userContext, remoteIdentity);
277
+ try {
278
+ await new Promise((resolve) => setImmediate(resolve));
279
+ expect(groupDataContext.packageLoader.contractRegistry.getDefinition).not.toHaveBeenCalled();
280
+ expect(accessLevelMock.calls).toEqual([]);
281
+ }
282
+ finally {
283
+ release();
284
+ }
285
+ await expect(pending).resolves.toMatchObject({ dataContextId: groupId });
286
+ });
287
+ it("fails closed on catalog initialization failure and permits a later retry", async () => {
288
+ const { remoteIdentity, userContext, groupDataContext } = setupGroupMember({
289
+ remoteAccessLevel: access_level_1.AccessLevel.Writer,
290
+ callerLevel: access_level_1.AccessLevel.Writer,
291
+ });
292
+ let fail = true;
293
+ Object.defineProperty(groupDataContext.packageLoader, "catalogReady", {
294
+ get: () => (fail ? Promise.reject(new Error("catalog unavailable")) : Promise.resolve()),
295
+ });
296
+ await expect((0, index_1.authorizeRemoteContractCall)(toolCall, userContext, remoteIdentity)).rejects.toThrow("catalog unavailable");
297
+ expect(groupDataContext.packageLoader.contractRegistry.getDefinition).not.toHaveBeenCalled();
298
+ expect(accessLevelMock.calls).toEqual([]);
299
+ fail = false;
300
+ await expect((0, index_1.authorizeRemoteContractCall)(toolCall, userContext, remoteIdentity)).resolves.toMatchObject({ dataContextId: groupId });
301
+ });
263
302
  it("denies a group member below the tool's remoteAccessLevel", async () => {
264
303
  const { remoteIdentity, userContext } = setupGroupMember({
265
304
  remoteAccessLevel: access_level_1.AccessLevel.Admin,
@@ -388,6 +427,51 @@ describe("local user-context contract routing", () => {
388
427
  await expect(result).resolves.toBe(setup.userId);
389
428
  router.dispose();
390
429
  });
430
+ it.each([
431
+ false,
432
+ true,
433
+ ])("waits for catalog readiness before provider selection (explicit=%s)", async (explicit) => {
434
+ const setup = setupLocalRouter();
435
+ const providerPackageId = (0, utils_1.newid)();
436
+ let release;
437
+ const catalogReady = new Promise((resolve) => {
438
+ release = resolve;
439
+ });
440
+ const getRegisteredProviderIds = jest.fn(() => [providerPackageId]);
441
+ const getActiveContractProviderPackageId = jest.fn(() => providerPackageId);
442
+ const handleCall = jest.fn(async () => "ready");
443
+ const resolveContractEndpoint = jest.fn(async () => ({ handleCall, dispose() { } }));
444
+ setup.userContext.userDataContext.packageLoader = {
445
+ catalogReady,
446
+ contractRegistry: { getRegisteredProviderIds },
447
+ getActiveContractProviderPackageId,
448
+ resolveContractEndpoint,
449
+ };
450
+ const router = (0, index_1.createLocalUserContextContractProviderRouter)(setup.providerTransport, {
451
+ getUserContext: async () => setup.userContext,
452
+ });
453
+ try {
454
+ const pending = setup.consumerTransport.emit(index_1.CONTRACT_REQUEST_CHANNEL, {
455
+ ...makeToolCall(setup.contractId),
456
+ ...(explicit ? { providerPackageId } : {}),
457
+ });
458
+ try {
459
+ await new Promise((resolve) => setImmediate(resolve));
460
+ expect(getRegisteredProviderIds).not.toHaveBeenCalled();
461
+ expect(getActiveContractProviderPackageId).not.toHaveBeenCalled();
462
+ expect(resolveContractEndpoint).not.toHaveBeenCalled();
463
+ }
464
+ finally {
465
+ release();
466
+ }
467
+ await expect(pending).resolves.toBe("ready");
468
+ expect(resolveContractEndpoint).toHaveBeenCalledWith(expect.objectContaining({ providerPackageId }), expect.anything());
469
+ expect(handleCall).toHaveBeenCalledTimes(1);
470
+ }
471
+ finally {
472
+ router.dispose();
473
+ }
474
+ });
391
475
  it("resolves a signed-out contract without waiting for a user context", async () => {
392
476
  const contractId = (0, utils_1.newid)();
393
477
  (0, index_1.registerContractResolver)(contractId, 1, () => ({
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,90 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const sdk = require("../../index");
4
+ const utils_1 = require("../../utils");
5
+ const builder_1 = require("../builder");
6
+ const registry_1 = require("../registry");
7
+ const tool_catalog_1 = require("../tool-catalog");
8
+ const tool_catalog_migrations_1 = require("../tool-catalog-migrations");
9
+ const removedRegistryExports = [
10
+ "registerTool",
11
+ "getRegisteredTool",
12
+ "unregisterTools",
13
+ "getAllRegisteredTools",
14
+ "clearRegisteredTools",
15
+ "tools",
16
+ ];
17
+ test("the SDK exports no executable registry", () => {
18
+ for (const name of removedRegistryExports)
19
+ expect(sdk).not.toHaveProperty(name);
20
+ });
21
+ test("old SDK assignments warn once and never produce executables", () => {
22
+ const builder = new builder_1.ContractBuilder((0, utils_1.newid)(), 1, "Old package");
23
+ const warn = jest.spyOn(console, "warn").mockImplementation(() => { });
24
+ try {
25
+ builder.toolInstances = [{ toolFn: jest.fn() }];
26
+ builder.toolInstances = [];
27
+ expect(warn).toHaveBeenCalledTimes(1);
28
+ expect(warn).toHaveBeenCalledWith(expect.stringContaining(`Contract Old package (${builder.contractId}@1) assigned toolInstances, which no longer run`));
29
+ expect(builder._build()).not.toHaveProperty("toolInstances");
30
+ }
31
+ finally {
32
+ warn.mockRestore();
33
+ }
34
+ });
35
+ test("runner metadata uses the shape target and ignores arbitrary tool IDs", () => {
36
+ const contractId = (0, utils_1.newid)();
37
+ const shape = {
38
+ tool: {
39
+ toolId: (0, utils_1.newid)(),
40
+ name: "runner",
41
+ usageDescription: "run",
42
+ isAssistantRunner: true,
43
+ configSchema: { type: sdk.IOSchemaType.none, fields: [] },
44
+ detailedDescription: "details",
45
+ },
46
+ };
47
+ const result = (0, builder_1.definePackage)((pkg) => {
48
+ pkg.packageId = (0, utils_1.newid)();
49
+ pkg.contract(contractId, 1, "Runner").tools = [shape];
50
+ });
51
+ const target = { contractId, version: 1, toolName: "runner" };
52
+ expect(result.toolMetadata).toEqual([
53
+ {
54
+ ...target,
55
+ isAssistantRunner: true,
56
+ configSchema: shape.tool.configSchema,
57
+ detailedDescription: "details",
58
+ },
59
+ ]);
60
+ expect(result).not.toHaveProperty("toolInstances");
61
+ expect((0, tool_catalog_migrations_1.toolCatalogIds)(target)).not.toContain(shape.tool.toolId);
62
+ shape.tool.toolId = (0, utils_1.newid)();
63
+ expect((0, tool_catalog_migrations_1.toolCatalogIds)(target)).toEqual([sdk.deriveToolCatalogId(target)]);
64
+ });
65
+ test("metadata targeting another publication is rejected before any catalog write", async () => {
66
+ const getTable = jest.fn();
67
+ const registry = new registry_1.ContractRegistry();
68
+ const catalog = new tool_catalog_1.ToolCatalog({ tableContainer: { getTable } }, registry);
69
+ const definition = (0, builder_1.definePackage)((pkg) => {
70
+ pkg.packageId = (0, utils_1.newid)();
71
+ pkg.contract((0, utils_1.newid)(), 1, "Own").tools = [{ tool: { name: "own", usageDescription: "own" } }];
72
+ });
73
+ const foreign = {
74
+ ...definition.contracts[0],
75
+ contractId: (0, utils_1.newid)(),
76
+ tools: [{ ...definition.contracts[0].tools[0], name: "foreign" }],
77
+ };
78
+ expect(registry.register((0, utils_1.newid)(), foreign).valid).toBe(true);
79
+ const prepare = jest.fn();
80
+ const activate = jest.fn();
81
+ try {
82
+ await expect(catalog.publish(definition.packageId, definition.contracts.map((definition) => ({ definition })), prepare, activate, [{ contractId: foreign.contractId, version: 1, toolName: "foreign" }])).rejects.toThrow(/member of this publication/);
83
+ expect(getTable).not.toHaveBeenCalled();
84
+ expect(prepare).not.toHaveBeenCalled();
85
+ expect(activate).not.toHaveBeenCalled();
86
+ }
87
+ finally {
88
+ await catalog.dispose();
89
+ }
90
+ });
@@ -6,6 +6,7 @@ const contract_resolvers_1 = require("../contract-resolvers");
6
6
  const registry_1 = require("../registry");
7
7
  const identity_contract_1 = require("../system/identity-contract");
8
8
  const system_contracts_1 = require("../system-contracts");
9
+ const tool_catalog_1 = require("../tool-catalog");
9
10
  // Populate the catalog with built-in system contracts.
10
11
  require("../system");
11
12
  describe("system contract catalog", () => {
@@ -17,9 +18,12 @@ describe("system contract catalog", () => {
17
18
  system_ids_1.logsContractId,
18
19
  system_ids_1.deviceOperationsContractId,
19
20
  system_ids_1.identityContractId,
21
+ system_ids_1.hostToolsContractId,
22
+ system_ids_1.desktopToolsContractId,
20
23
  system_ids_1.networkDiagnosticsContractId,
21
24
  system_ids_1.networkControlContractId,
22
25
  system_ids_1.toolAccessControlContractId,
26
+ system_ids_1.toolCatalogContractId,
23
27
  ]));
24
28
  });
25
29
  it("registers the Logs live resolver", async () => {
@@ -76,3 +80,87 @@ describe("system contract catalog", () => {
76
80
  expect(registry.getProviderPackageId(fakeContractId, 1)).toBe(providerPackageId);
77
81
  });
78
82
  });
83
+ test("system catalog initialization retries without publishing on failed persistence", async () => {
84
+ const rows = new Map();
85
+ let fail = true;
86
+ const context = {
87
+ tableContainer: {
88
+ getTable: () => ({
89
+ list: async () => [...rows.values()],
90
+ get: async (toolId) => rows.get(toolId),
91
+ save: async (row) => {
92
+ if (fail)
93
+ throw new Error("storage unavailable");
94
+ rows.set(row.toolId, row);
95
+ return row;
96
+ },
97
+ dataChanged: { subscribe: () => ({ unsubscribe: () => true }) },
98
+ }),
99
+ },
100
+ };
101
+ const registry = new registry_1.ContractRegistry();
102
+ const catalog = new tool_catalog_1.ToolCatalog(context, registry);
103
+ await expect((0, system_contracts_1.installSystemContracts)(registry)).rejects.toThrow("storage unavailable");
104
+ expect(registry.getProviderPackageId(system_ids_1.deviceOperationsContractId, 1)).toBeUndefined();
105
+ fail = false;
106
+ await (0, system_contracts_1.installSystemContracts)(registry);
107
+ expect(registry.getProviderPackageId(system_ids_1.deviceOperationsContractId, 1)).toBe(system_ids_1.peersSystemPackageId);
108
+ expect(registry.getProviderPackageId(system_ids_1.identityContractId, 1)).toBe(system_ids_1.peersSystemPackageId);
109
+ expect([...rows.values()].some((row) => row.contractId === system_ids_1.toolAccessControlContractId)).toBe(true);
110
+ await catalog.dispose();
111
+ });
112
+ test("a later system provider's persistence failure does not publish an earlier provider", async () => {
113
+ const contractId = (0, utils_1.newid)();
114
+ const providerPackageId = (0, utils_1.newid)();
115
+ (0, system_contracts_1.registerSystemContract)({
116
+ contractId,
117
+ version: 1,
118
+ providerPackageId,
119
+ getDefinition: () => ({
120
+ contractId,
121
+ version: 1,
122
+ name: "Later system provider",
123
+ description: "",
124
+ tables: [],
125
+ observables: [],
126
+ tools: [{ name: "read", usageDescription: "", inputFields: [], outputFields: [] }],
127
+ }),
128
+ });
129
+ const rows = new Map();
130
+ const registry = new registry_1.ContractRegistry();
131
+ const context = {
132
+ tableContainer: {
133
+ getTable: () => ({
134
+ list: async () => [...rows.values()],
135
+ save: async (row) => {
136
+ if (row.contractId === contractId)
137
+ throw new Error("late persistence failure");
138
+ rows.set(row.toolId, row);
139
+ return row;
140
+ },
141
+ }),
142
+ },
143
+ };
144
+ const catalog = new tool_catalog_1.ToolCatalog(context, registry);
145
+ try {
146
+ await expect(catalog.ready()).rejects.toThrow(/late persistence failure/);
147
+ expect(rows.size).toBeGreaterThan(0);
148
+ expect(registry.getProviderPackageId(system_ids_1.deviceOperationsContractId, 1)).toBeUndefined();
149
+ expect(registry.getProviderPackageId(contractId, 1)).toBeUndefined();
150
+ }
151
+ finally {
152
+ await catalog.dispose();
153
+ }
154
+ });
155
+ test("host and desktop workflow members are present with workflow-only policy", () => {
156
+ const registry = new registry_1.ContractRegistry();
157
+ (0, system_contracts_1.installSystemContracts)(registry);
158
+ for (const [id, names] of [
159
+ [system_ids_1.hostToolsContractId, ["default_assistant_runner", "tool-search"]],
160
+ [system_ids_1.desktopToolsContractId, ["cd", "send-notification"]],
161
+ ]) {
162
+ const definition = registry.resolve(id, 1);
163
+ for (const name of names)
164
+ expect(definition?.tools.find((tool) => tool.name === name)?.requiresWorkflowRun).toBe(true);
165
+ }
166
+ });
@@ -0,0 +1 @@
1
+ import "../../index";
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ require("../../index");
4
+ const systemIds = require("../../system-ids");
5
+ const system_ids_1 = require("../../system-ids");
6
+ const system_contracts_1 = require("../system-contracts");
7
+ const tool_catalog_identity_1 = require("../tool-catalog-identity");
8
+ const tool_catalog_migrations_1 = require("../tool-catalog-migrations");
9
+ test("every reviewed alias is valid and the workflow migration leaves no unresolved tools", () => {
10
+ for (const entry of tool_catalog_migrations_1.toolCatalogMigrations) {
11
+ expect((0, tool_catalog_identity_1.isCatalogToolId)(entry.toolId)).toBe(true);
12
+ expect((0, tool_catalog_migrations_1.resolveLegacyToolId)(entry.toolId)).toEqual({
13
+ contractId: entry.contractId,
14
+ version: entry.version,
15
+ toolName: entry.toolName,
16
+ });
17
+ }
18
+ for (const [id, contractId, toolName] of [
19
+ ["000peers0tool00000runner1", system_ids_1.hostToolsContractId, "default_assistant_runner"],
20
+ ["000peers0tool00toolsearch", system_ids_1.hostToolsContractId, "tool-search"],
21
+ ["000peers0tool0000000dircd", system_ids_1.desktopToolsContractId, "cd"],
22
+ ["000peers0tool000000notify", system_ids_1.desktopToolsContractId, "send-notification"],
23
+ ])
24
+ expect((0, tool_catalog_migrations_1.resolveLegacyToolId)(id)).toEqual({ contractId, version: 1, toolName });
25
+ });
26
+ test("all system aliases name declared members and every system ID map is reviewed", () => {
27
+ for (const alias of tool_catalog_migrations_1.toolCatalogMigrations) {
28
+ if (!(0, system_contracts_1.isSystemContractId)(alias.contractId))
29
+ continue;
30
+ const system = (0, system_contracts_1.listSystemContracts)().find((entry) => {
31
+ const definition = entry.getDefinition();
32
+ return definition.contractId === alias.contractId && definition.version === alias.version;
33
+ });
34
+ expect(system).toBeDefined();
35
+ expect(system?.getDefinition().tools.some((tool) => tool.name === alias.toolName)).toBe(true);
36
+ }
37
+ for (const [name, ids] of Object.entries(systemIds)) {
38
+ if (!name.endsWith("ToolIds"))
39
+ continue;
40
+ for (const toolId of Object.values(ids))
41
+ expect((0, tool_catalog_migrations_1.resolveLegacyToolId)(toolId)).toBeDefined();
42
+ }
43
+ expect((0, tool_catalog_migrations_1.resolveLegacyToolId)("00mh0wlipkdbeaw8imptsk001")).toBeUndefined();
44
+ });
@@ -0,0 +1 @@
1
+ import "../../index";