@peers-app/peers-sdk 0.27.3 → 0.27.5

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 (272) hide show
  1. package/dist/contracts/contract-proxy.d.ts +7 -0
  2. package/dist/contracts/contract-proxy.js +19 -2
  3. package/dist/contracts/contract-transport.d.ts +2 -1
  4. package/dist/contracts/contract-transport.js +24 -2
  5. package/dist/contracts/create-local-consumer.js +16 -1
  6. package/dist/contracts/index.d.ts +2 -0
  7. package/dist/contracts/index.js +2 -0
  8. package/dist/contracts/system/index.d.ts +2 -0
  9. package/dist/contracts/system/index.js +2 -0
  10. package/dist/contracts/system/invites-contract.js +2 -1
  11. package/dist/contracts/system/sync-check-contract.d.ts +97 -0
  12. package/dist/contracts/system/sync-check-contract.js +115 -0
  13. package/dist/contracts/tool-arguments.d.ts +15 -0
  14. package/dist/contracts/tool-arguments.js +41 -0
  15. package/dist/contracts/tool-catalog-service.js +2 -12
  16. package/dist/contracts/tool-catalog.js +6 -2
  17. package/dist/data/change-tracking.d.ts +40 -0
  18. package/dist/data/change-tracking.js +89 -0
  19. package/dist/data/orm/types.d.ts +24 -7
  20. package/dist/data/orm/types.js +97 -24
  21. package/dist/define/manifest.js +6 -34
  22. package/dist/device/binary-peer-connection-v2.d.ts +7 -15
  23. package/dist/device/binary-peer-connection-v2.js +14 -30
  24. package/dist/device/connection.d.ts +34 -43
  25. package/dist/device/connection.js +188 -186
  26. package/dist/device/device.d.ts +1 -8
  27. package/dist/device/device.js +1 -30
  28. package/dist/device/socket.type.d.ts +6 -12
  29. package/dist/package-loader/package-loader.js +27 -22
  30. package/dist/system-ids.d.ts +5 -0
  31. package/dist/system-ids.js +6 -1
  32. package/package.json +4 -2
  33. package/dist/context/user-context-identity.test.d.ts +0 -1
  34. package/dist/context/user-context-identity.test.js +0 -220
  35. package/dist/contracts/__tests__/action-dispatch.test.d.ts +0 -1
  36. package/dist/contracts/__tests__/action-dispatch.test.js +0 -319
  37. package/dist/contracts/__tests__/also-implements.test.d.ts +0 -1
  38. package/dist/contracts/__tests__/also-implements.test.js +0 -110
  39. package/dist/contracts/__tests__/builder.test.d.ts +0 -1
  40. package/dist/contracts/__tests__/builder.test.js +0 -669
  41. package/dist/contracts/__tests__/contract-events.test.d.ts +0 -1
  42. package/dist/contracts/__tests__/contract-events.test.js +0 -199
  43. package/dist/contracts/__tests__/contract-observables.test.d.ts +0 -1
  44. package/dist/contracts/__tests__/contract-observables.test.js +0 -290
  45. package/dist/contracts/__tests__/contract-provider-router.test.d.ts +0 -1
  46. package/dist/contracts/__tests__/contract-provider-router.test.js +0 -315
  47. package/dist/contracts/__tests__/contract-proxy.test.d.ts +0 -1
  48. package/dist/contracts/__tests__/contract-proxy.test.js +0 -657
  49. package/dist/contracts/__tests__/contract-resolvers.test.d.ts +0 -1
  50. package/dist/contracts/__tests__/contract-resolvers.test.js +0 -758
  51. package/dist/contracts/__tests__/contract-subscription-lifecycle.test.d.ts +0 -1
  52. package/dist/contracts/__tests__/contract-subscription-lifecycle.test.js +0 -353
  53. package/dist/contracts/__tests__/contract-table-events.test.d.ts +0 -1
  54. package/dist/contracts/__tests__/contract-table-events.test.js +0 -209
  55. package/dist/contracts/__tests__/contract-transport.test.d.ts +0 -1
  56. package/dist/contracts/__tests__/contract-transport.test.js +0 -163
  57. package/dist/contracts/__tests__/extract.test.d.ts +0 -1
  58. package/dist/contracts/__tests__/extract.test.js +0 -145
  59. package/dist/contracts/__tests__/frozen-contracts.test.d.ts +0 -1
  60. package/dist/contracts/__tests__/frozen-contracts.test.js +0 -191
  61. package/dist/contracts/__tests__/generic-tool-dispatch.test.d.ts +0 -1
  62. package/dist/contracts/__tests__/generic-tool-dispatch.test.js +0 -130
  63. package/dist/contracts/__tests__/host-contract-client.test.d.ts +0 -1
  64. package/dist/contracts/__tests__/host-contract-client.test.js +0 -793
  65. package/dist/contracts/__tests__/host-contract-consumer.test.d.ts +0 -1
  66. package/dist/contracts/__tests__/host-contract-consumer.test.js +0 -163
  67. package/dist/contracts/__tests__/host-contract-transport.test.d.ts +0 -1
  68. package/dist/contracts/__tests__/host-contract-transport.test.js +0 -66
  69. package/dist/contracts/__tests__/integration.test.d.ts +0 -1
  70. package/dist/contracts/__tests__/integration.test.js +0 -348
  71. package/dist/contracts/__tests__/legacy-removal.test.d.ts +0 -1
  72. package/dist/contracts/__tests__/legacy-removal.test.js +0 -128
  73. package/dist/contracts/__tests__/persistent-registry.test.d.ts +0 -1
  74. package/dist/contracts/__tests__/persistent-registry.test.js +0 -109
  75. package/dist/contracts/__tests__/playground.test.d.ts +0 -1
  76. package/dist/contracts/__tests__/playground.test.js +0 -216
  77. package/dist/contracts/__tests__/registry.test.d.ts +0 -1
  78. package/dist/contracts/__tests__/registry.test.js +0 -238
  79. package/dist/contracts/__tests__/remote-contract-transport.test.d.ts +0 -1
  80. package/dist/contracts/__tests__/remote-contract-transport.test.js +0 -119
  81. package/dist/contracts/__tests__/system-contracts.test.d.ts +0 -1
  82. package/dist/contracts/__tests__/system-contracts.test.js +0 -247
  83. package/dist/contracts/__tests__/target-dispatch.d.ts +0 -11
  84. package/dist/contracts/__tests__/target-dispatch.js +0 -24
  85. package/dist/contracts/__tests__/tool-catalog-inventory.test.d.ts +0 -1
  86. package/dist/contracts/__tests__/tool-catalog-inventory.test.js +0 -44
  87. package/dist/contracts/__tests__/tool-catalog-service.test.d.ts +0 -1
  88. package/dist/contracts/__tests__/tool-catalog-service.test.js +0 -482
  89. package/dist/contracts/__tests__/tool-catalog.test.d.ts +0 -1
  90. package/dist/contracts/__tests__/tool-catalog.test.js +0 -1199
  91. package/dist/contracts/__tests__/validate.test.d.ts +0 -1
  92. package/dist/contracts/__tests__/validate.test.js +0 -991
  93. package/dist/contracts/__tests__/workflow-context.test.d.ts +0 -1
  94. package/dist/contracts/__tests__/workflow-context.test.js +0 -50
  95. package/dist/data/access-level.test.d.ts +0 -1
  96. package/dist/data/access-level.test.js +0 -53
  97. package/dist/data/change-tracking.test.d.ts +0 -1
  98. package/dist/data/change-tracking.test.js +0 -62
  99. package/dist/data/files/files.test.d.ts +0 -1
  100. package/dist/data/files/files.test.js +0 -770
  101. package/dist/data/group-members.test.d.ts +0 -1
  102. package/dist/data/group-members.test.js +0 -336
  103. package/dist/data/group-share.test.d.ts +0 -1
  104. package/dist/data/group-share.test.js +0 -110
  105. package/dist/data/groups.test.d.ts +0 -1
  106. package/dist/data/groups.test.js +0 -153
  107. package/dist/data/orm/cursor.test.d.ts +0 -1
  108. package/dist/data/orm/cursor.test.js +0 -314
  109. package/dist/data/orm/data-query.mongo.test.d.ts +0 -1
  110. package/dist/data/orm/data-query.mongo.test.js +0 -367
  111. package/dist/data/orm/data-query.sqlite.test.d.ts +0 -1
  112. package/dist/data/orm/data-query.sqlite.test.js +0 -365
  113. package/dist/data/orm/data-query.test.d.ts +0 -1
  114. package/dist/data/orm/data-query.test.js +0 -545
  115. package/dist/data/orm/dependency-injection.test.d.ts +0 -1
  116. package/dist/data/orm/dependency-injection.test.js +0 -171
  117. package/dist/data/orm/event-registry.test.d.ts +0 -1
  118. package/dist/data/orm/event-registry.test.js +0 -44
  119. package/dist/data/orm/multi-cursors.test.d.ts +0 -1
  120. package/dist/data/orm/multi-cursors.test.js +0 -463
  121. package/dist/data/orm/sql.data-source.test.d.ts +0 -1
  122. package/dist/data/orm/sql.data-source.test.js +0 -731
  123. package/dist/data/orm/table-container-events.test.d.ts +0 -1
  124. package/dist/data/orm/table-container-events.test.js +0 -101
  125. package/dist/data/orm/table-container.test.d.ts +0 -1
  126. package/dist/data/orm/table-container.test.js +0 -273
  127. package/dist/data/orm/table.event-source.test.d.ts +0 -1
  128. package/dist/data/orm/table.event-source.test.js +0 -331
  129. package/dist/data/orm/types.test.d.ts +0 -1
  130. package/dist/data/orm/types.test.js +0 -409
  131. package/dist/data/package-table-identity.test.d.ts +0 -1
  132. package/dist/data/package-table-identity.test.js +0 -46
  133. package/dist/data/package-version-permissions.test.d.ts +0 -1
  134. package/dist/data/package-version-permissions.test.js +0 -107
  135. package/dist/data/package-version-resolver.test.d.ts +0 -4
  136. package/dist/data/package-version-resolver.test.js +0 -543
  137. package/dist/data/package-versions.test.d.ts +0 -1
  138. package/dist/data/package-versions.test.js +0 -227
  139. package/dist/data/persistent-vars.conflict.test.d.ts +0 -1
  140. package/dist/data/persistent-vars.conflict.test.js +0 -126
  141. package/dist/data/persistent-vars.migration.test.d.ts +0 -1
  142. package/dist/data/persistent-vars.migration.test.js +0 -133
  143. package/dist/data/persistent-vars.package-scope.test.d.ts +0 -1
  144. package/dist/data/persistent-vars.package-scope.test.js +0 -27
  145. package/dist/data/sync-recovery-queue.test.d.ts +0 -1
  146. package/dist/data/sync-recovery-queue.test.js +0 -215
  147. package/dist/data/table-signing.test.d.ts +0 -1
  148. package/dist/data/table-signing.test.js +0 -118
  149. package/dist/data/tool-access.test.d.ts +0 -1
  150. package/dist/data/tool-access.test.js +0 -166
  151. package/dist/data/user-permissions.test.d.ts +0 -1
  152. package/dist/data/user-permissions.test.js +0 -305
  153. package/dist/data/user-trust-levels.test.d.ts +0 -1
  154. package/dist/data/user-trust-levels.test.js +0 -37
  155. package/dist/define/also-implements.test.d.ts +0 -1
  156. package/dist/define/also-implements.test.js +0 -123
  157. package/dist/define/declare-contract.test.d.ts +0 -1
  158. package/dist/define/declare-contract.test.js +0 -573
  159. package/dist/define/define.test.d.ts +0 -1
  160. package/dist/define/define.test.js +0 -1010
  161. package/dist/define/empty-fields.test.d.ts +0 -1
  162. package/dist/define/empty-fields.test.js +0 -55
  163. package/dist/define/events-observables.test.d.ts +0 -1
  164. package/dist/define/events-observables.test.js +0 -1013
  165. package/dist/define/serve-members.test.d.ts +0 -1
  166. package/dist/define/serve-members.test.js +0 -630
  167. package/dist/define/vars.test.d.ts +0 -1
  168. package/dist/define/vars.test.js +0 -39
  169. package/dist/device/binary-peer-connection-v2.test.d.ts +0 -1
  170. package/dist/device/binary-peer-connection-v2.test.js +0 -730
  171. package/dist/device/binary-peer-connection.d.ts +0 -58
  172. package/dist/device/binary-peer-connection.js +0 -516
  173. package/dist/device/binary-peer-connection.test.d.ts +0 -1
  174. package/dist/device/binary-peer-connection.test.js +0 -184
  175. package/dist/device/connection.test.d.ts +0 -1
  176. package/dist/device/connection.test.js +0 -613
  177. package/dist/device/device-election.test.d.ts +0 -1
  178. package/dist/device/device-election.test.js +0 -516
  179. package/dist/device/device.test.d.ts +0 -1
  180. package/dist/device/device.test.js +0 -243
  181. package/dist/device/frame-cipher.test.d.ts +0 -1
  182. package/dist/device/frame-cipher.test.js +0 -219
  183. package/dist/device/get-trust-level-fn.test.d.ts +0 -1
  184. package/dist/device/get-trust-level-fn.test.js +0 -685
  185. package/dist/device/streamed-socket.d.ts +0 -23
  186. package/dist/device/streamed-socket.js +0 -255
  187. package/dist/device/streamed-socket.test.d.ts +0 -1
  188. package/dist/device/streamed-socket.test.js +0 -216
  189. package/dist/device/tx-encoding.test.d.ts +0 -1
  190. package/dist/device/tx-encoding.test.js +0 -295
  191. package/dist/device-pairing/cpace.test.d.ts +0 -1
  192. package/dist/device-pairing/cpace.test.js +0 -116
  193. package/dist/device-pairing/device-pairing.test.d.ts +0 -1
  194. package/dist/device-pairing/device-pairing.test.js +0 -211
  195. package/dist/hlc.test.d.ts +0 -1
  196. package/dist/hlc.test.js +0 -45
  197. package/dist/host-credentials.test.d.ts +0 -1
  198. package/dist/host-credentials.test.js +0 -88
  199. package/dist/identity/account-email.test.d.ts +0 -1
  200. package/dist/identity/account-email.test.js +0 -59
  201. package/dist/identity/anchor-document.test.d.ts +0 -1
  202. package/dist/identity/anchor-document.test.js +0 -16
  203. package/dist/identity/anchor-resolution.test.d.ts +0 -1
  204. package/dist/identity/anchor-resolution.test.js +0 -130
  205. package/dist/identity/key-change.test.d.ts +0 -1
  206. package/dist/identity/key-change.test.js +0 -811
  207. package/dist/invites/group-approval.test.d.ts +0 -1
  208. package/dist/invites/group-approval.test.js +0 -223
  209. package/dist/invites/invite-token.test.d.ts +0 -1
  210. package/dist/invites/invite-token.test.js +0 -123
  211. package/dist/keys.test.d.ts +0 -1
  212. package/dist/keys.test.js +0 -336
  213. package/dist/logging/console-logger.test.d.ts +0 -1
  214. package/dist/logging/console-logger.test.js +0 -300
  215. package/dist/logging/console-logs.table.test.d.ts +0 -1
  216. package/dist/logging/console-logs.table.test.js +0 -156
  217. package/dist/observable.test.d.ts +0 -1
  218. package/dist/observable.test.js +0 -309
  219. package/dist/package-build/build-package.test.d.ts +0 -1
  220. package/dist/package-build/build-package.test.js +0 -204
  221. package/dist/package-build/contract-add.test.d.ts +0 -1
  222. package/dist/package-build/contract-add.test.js +0 -467
  223. package/dist/package-build/freeze-contracts.test.d.ts +0 -1
  224. package/dist/package-build/freeze-contracts.test.js +0 -355
  225. package/dist/package-installer/package-author-signing.test.d.ts +0 -1
  226. package/dist/package-installer/package-author-signing.test.js +0 -200
  227. package/dist/package-installer/package-cloner.test.d.ts +0 -1
  228. package/dist/package-installer/package-cloner.test.js +0 -278
  229. package/dist/package-installer/package-creator.test.d.ts +0 -1
  230. package/dist/package-installer/package-creator.test.js +0 -908
  231. package/dist/package-installer/package-dev-tools.test.d.ts +0 -1
  232. package/dist/package-installer/package-dev-tools.test.js +0 -64
  233. package/dist/package-installer/package-installer.test.d.ts +0 -1
  234. package/dist/package-installer/package-installer.test.js +0 -882
  235. package/dist/package-installer/package-propagation.test.d.ts +0 -1
  236. package/dist/package-installer/package-propagation.test.js +0 -1269
  237. package/dist/package-installer/package-publisher.test.d.ts +0 -1
  238. package/dist/package-installer/package-publisher.test.js +0 -153
  239. package/dist/package-installer/package-remote-checker.test.d.ts +0 -1
  240. package/dist/package-installer/package-remote-checker.test.js +0 -334
  241. package/dist/package-installer/package-seed-installer.test.d.ts +0 -1
  242. package/dist/package-installer/package-seed-installer.test.js +0 -123
  243. package/dist/package-installer/package-tarball.test.d.ts +0 -1
  244. package/dist/package-installer/package-tarball.test.js +0 -75
  245. package/dist/package-installer/peers-core-installer.test.d.ts +0 -1
  246. package/dist/package-installer/peers-core-installer.test.js +0 -49
  247. package/dist/package-installer/peers-core-trust-anchor.test.d.ts +0 -1
  248. package/dist/package-installer/peers-core-trust-anchor.test.js +0 -155
  249. package/dist/package-loader/contract-package-loader.test.d.ts +0 -1
  250. package/dist/package-loader/contract-package-loader.test.js +0 -274
  251. package/dist/package-loader/host-authority-loading.test.d.ts +0 -1
  252. package/dist/package-loader/host-authority-loading.test.js +0 -104
  253. package/dist/package-loader/host-dispatch-policy.test.d.ts +0 -1
  254. package/dist/package-loader/host-dispatch-policy.test.js +0 -56
  255. package/dist/package-loader/isolated-artifact.test.d.ts +0 -1
  256. package/dist/package-loader/isolated-artifact.test.js +0 -688
  257. package/dist/package-loader/isolated-persistent-var-gateway.test.d.ts +0 -1
  258. package/dist/package-loader/isolated-persistent-var-gateway.test.js +0 -56
  259. package/dist/package-loader/isolated-table-gateway.test.d.ts +0 -1
  260. package/dist/package-loader/isolated-table-gateway.test.js +0 -633
  261. package/dist/package-loader/package-loader.test.d.ts +0 -1
  262. package/dist/package-loader/package-loader.test.js +0 -1764
  263. package/dist/package-loader/resolve-consumed-contracts.test.d.ts +0 -1
  264. package/dist/package-loader/resolve-consumed-contracts.test.js +0 -166
  265. package/dist/serial-json.compat.test.d.ts +0 -1
  266. package/dist/serial-json.compat.test.js +0 -369
  267. package/dist/serial-json.test.d.ts +0 -1
  268. package/dist/serial-json.test.js +0 -401
  269. package/dist/testing/mock-host-contract.test.d.ts +0 -1
  270. package/dist/testing/mock-host-contract.test.js +0 -782
  271. package/dist/utils.test.d.ts +0 -1
  272. package/dist/utils.test.js +0 -232
@@ -375,6 +375,13 @@ export interface IContractResolution {
375
375
  version: number;
376
376
  tables?: Record<string, Table<any>>;
377
377
  tools?: Record<string, IContractToolImplementation>;
378
+ /**
379
+ * Trusted runtime invocation after access checks, with host context already appended.
380
+ * It validates the requested version's input and checks endpoint liveness immediately
381
+ * before running provider code. Without it, dispatch validates the system definition or
382
+ * the implementation's declared input fields and calls the tool function directly.
383
+ */
384
+ invokeTool?: (name: string, args: any[]) => any;
378
385
  /**
379
386
  * Host-only capabilities appended as the final argument to provider tool functions.
380
387
  * Omit this for remote or otherwise untrusted provider routes.
@@ -15,6 +15,8 @@ const observable_1 = require("../observable");
15
15
  const utils_1 = require("../utils");
16
16
  const action_dispatch_1 = require("./action-dispatch");
17
17
  const contract_transport_1 = require("./contract-transport");
18
+ const system_contracts_1 = require("./system-contracts");
19
+ const tool_arguments_1 = require("./tool-arguments");
18
20
  const STANDARD_REMOTE_TABLE_METHODS = new Set([
19
21
  "get",
20
22
  "list",
@@ -594,6 +596,21 @@ function createContractProvider(resolution, opts = {}) {
594
596
  }
595
597
  const { workflow, ...caller } = context;
596
598
  const authority = await assertContractToolAccess(tool, call, caller);
599
+ const invoke = (...args) => {
600
+ if (resolution.invokeTool)
601
+ return resolution.invokeTool(call.name, args);
602
+ const definition = (0, system_contracts_1.listSystemContracts)()
603
+ .find((entry) => (entry.contractId ?? entry.getDefinition().contractId) === call.contractId &&
604
+ (entry.version ?? entry.getDefinition().version) === call.version)
605
+ ?.getDefinition();
606
+ const member = definition?.tools.find((entry) => entry.name === call.name);
607
+ if (definition && !member)
608
+ throw new Error(`Contract ${call.contractId} does not declare tool '${call.name}'`);
609
+ const fields = member?.inputFields ?? tool.tool.inputSchema?.fields;
610
+ if (fields?.length)
611
+ (0, tool_arguments_1.checkToolArguments)(fields, call.args[0], call.name);
612
+ return tool.toolFn(...args);
613
+ };
597
614
  if (resolution.toolContext ||
598
615
  resolution.forwardCallContext ||
599
616
  authority.strictActionScope) {
@@ -621,7 +638,7 @@ function createContractProvider(resolution, opts = {}) {
621
638
  authority: (0, action_dispatch_1.narrowActionPrincipal)(frame, true),
622
639
  });
623
640
  };
624
- return tool.toolFn(call.args[0], {
641
+ return invoke(call.args[0], {
625
642
  ...base,
626
643
  workflow: caller.callerDeviceId || caller.callerPackageId ? undefined : workflow,
627
644
  callerPackageId: caller.callerPackageId,
@@ -632,7 +649,7 @@ function createContractProvider(resolution, opts = {}) {
632
649
  authority,
633
650
  });
634
651
  }
635
- return tool.toolFn(...call.args);
652
+ return invoke(...call.args);
636
653
  }
637
654
  case "observable": {
638
655
  const obs = resolution.observables?.[call.name];
@@ -63,7 +63,8 @@ export declare const CONTRACT_REQUEST_CHANNEL = "contractCall";
63
63
  export declare const CONTRACT_NOTIFY_CHANNEL = "contractNotify";
64
64
  /**
65
65
  * Wrap a duplex RPC emitter (e.g. a device {@link Connection}) as an
66
- * {@link IContractTransport}. `emit` delegates directly. `on` uses one adapter-owned
66
+ * {@link IContractTransport}. `emit` delegates directly and rejects with an `Error`
67
+ * carrying the remote message, whatever the emitter rejected with. `on` uses one adapter-owned
67
68
  * dispatcher per emitter/channel and returns an unsubscribe that removes only the caller's
68
69
  * handler from that dispatcher; it never uses a coarse `removeAllListeners`.
69
70
  *
@@ -33,9 +33,24 @@ exports.CONTRACT_REQUEST_CHANNEL = "contractCall";
33
33
  */
34
34
  exports.CONTRACT_NOTIFY_CHANNEL = "contractNotify";
35
35
  const connectionDispatcherStates = new WeakMap();
36
+ /**
37
+ * Turn a failed emit into an `Error`. A device connection rejects with the remote handler's
38
+ * `{ error, errorType }` envelope, or with a string when the call times out.
39
+ */
40
+ function toError(error) {
41
+ if (error instanceof Error)
42
+ return error;
43
+ if (error &&
44
+ typeof error === "object" &&
45
+ typeof error.error === "string") {
46
+ return new Error(error.error);
47
+ }
48
+ return new Error(typeof error === "string" ? error : JSON.stringify(error));
49
+ }
36
50
  /**
37
51
  * Wrap a duplex RPC emitter (e.g. a device {@link Connection}) as an
38
- * {@link IContractTransport}. `emit` delegates directly. `on` uses one adapter-owned
52
+ * {@link IContractTransport}. `emit` delegates directly and rejects with an `Error`
53
+ * carrying the remote message, whatever the emitter rejected with. `on` uses one adapter-owned
39
54
  * dispatcher per emitter/channel and returns an unsubscribe that removes only the caller's
40
55
  * handler from that dispatcher; it never uses a coarse `removeAllListeners`.
41
56
  *
@@ -53,7 +68,14 @@ const connectionDispatcherStates = new WeakMap();
53
68
  */
54
69
  function connectionContractTransport(emitter) {
55
70
  return {
56
- emit: (channel, ...args) => emitter.emit(channel, ...args),
71
+ emit: async (channel, ...args) => {
72
+ try {
73
+ return await emitter.emit(channel, ...args);
74
+ }
75
+ catch (error) {
76
+ throw toError(error);
77
+ }
78
+ },
57
79
  on: (channel, handler) => {
58
80
  let channelStates = connectionDispatcherStates.get(emitter);
59
81
  if (!channelStates) {
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createLocalContractConsumer = createLocalContractConsumer;
4
4
  const contract_proxy_1 = require("./contract-proxy");
5
5
  const contract_transport_1 = require("./contract-transport");
6
+ const tool_arguments_1 = require("./tool-arguments");
6
7
  /**
7
8
  * Build a same-process contract consumer wired to a live provider resolution.
8
9
  *
@@ -12,7 +13,21 @@ const contract_transport_1 = require("./contract-transport");
12
13
  */
13
14
  function createLocalContractConsumer(definition, resolution, opts) {
14
15
  const [consumerTransport, providerTransport] = (0, contract_transport_1.inProcessTransportPair)();
15
- (0, contract_proxy_1.createContractProviderSession)(resolution, providerTransport, { context: opts?.context });
16
+ (0, contract_proxy_1.createContractProviderSession)({
17
+ ...resolution,
18
+ invokeTool: resolution.invokeTool ??
19
+ ((name, args) => {
20
+ const tool = resolution.tools?.[name];
21
+ if (!tool)
22
+ throw new Error(`Contract ${definition.contractId} does not provide tool '${name}'`);
23
+ const member = definition.tools.find((tool) => tool.name === name);
24
+ if (!member)
25
+ throw new Error(`Contract ${definition.contractId} does not declare tool '${name}'`);
26
+ if (member.inputFields.length)
27
+ (0, tool_arguments_1.checkToolArguments)(member.inputFields, args[0], name);
28
+ return tool.toolFn(...args);
29
+ }),
30
+ }, providerTransport, { context: opts?.context });
16
31
  return (0, contract_proxy_1.createContractConsumer)(definition, consumerTransport, {
17
32
  dataContextId: opts?.dataContextId,
18
33
  });
@@ -29,9 +29,11 @@ export { getLogsContractDefinition, type ILogsConsumer, LOGS_CONTRACT_VERSION, r
29
29
  export * from "./system/network-contracts";
30
30
  export * from "./system/packages-contract";
31
31
  export { getSchedulerContractDefinition, type IScheduleInfo, type IScheduleInput, type ISchedulerConsumer, SCHEDULER_CONTRACT_VERSION, SCHEDULER_MAX_DELAY_MS, schedulerProviderPackageId, } from "./system/scheduler-contract";
32
+ export * from "./system/sync-check-contract";
32
33
  export { getToolAccessControlContractDefinition, type IToolAccessControlConsumer, TOOL_ACCESS_CONTROL_CONTRACT_VERSION, } from "./system/tool-access-control-contract";
33
34
  export * from "./system/tool-catalog-contract";
34
35
  export { type ISystemContractRegistration, installSystemContracts, isSystemContractId, listSystemContracts, registerSystemContract, } from "./system-contracts";
36
+ export * from "./tool-arguments";
35
37
  export * from "./tool-catalog";
36
38
  export * from "./tool-catalog-identity";
37
39
  export * from "./tool-catalog-migrations";
@@ -143,6 +143,7 @@ Object.defineProperty(exports, "getSchedulerContractDefinition", { enumerable: t
143
143
  Object.defineProperty(exports, "SCHEDULER_CONTRACT_VERSION", { enumerable: true, get: function () { return scheduler_contract_1.SCHEDULER_CONTRACT_VERSION; } });
144
144
  Object.defineProperty(exports, "SCHEDULER_MAX_DELAY_MS", { enumerable: true, get: function () { return scheduler_contract_1.SCHEDULER_MAX_DELAY_MS; } });
145
145
  Object.defineProperty(exports, "schedulerProviderPackageId", { enumerable: true, get: function () { return scheduler_contract_1.schedulerProviderPackageId; } });
146
+ __exportStar(require("./system/sync-check-contract"), exports);
146
147
  var tool_access_control_contract_1 = require("./system/tool-access-control-contract");
147
148
  Object.defineProperty(exports, "getToolAccessControlContractDefinition", { enumerable: true, get: function () { return tool_access_control_contract_1.getToolAccessControlContractDefinition; } });
148
149
  Object.defineProperty(exports, "TOOL_ACCESS_CONTROL_CONTRACT_VERSION", { enumerable: true, get: function () { return tool_access_control_contract_1.TOOL_ACCESS_CONTROL_CONTRACT_VERSION; } });
@@ -152,6 +153,7 @@ Object.defineProperty(exports, "installSystemContracts", { enumerable: true, get
152
153
  Object.defineProperty(exports, "isSystemContractId", { enumerable: true, get: function () { return system_contracts_1.isSystemContractId; } });
153
154
  Object.defineProperty(exports, "listSystemContracts", { enumerable: true, get: function () { return system_contracts_1.listSystemContracts; } });
154
155
  Object.defineProperty(exports, "registerSystemContract", { enumerable: true, get: function () { return system_contracts_1.registerSystemContract; } });
156
+ __exportStar(require("./tool-arguments"), exports);
155
157
  __exportStar(require("./tool-catalog"), exports);
156
158
  __exportStar(require("./tool-catalog-identity"), exports);
157
159
  __exportStar(require("./tool-catalog-migrations"), exports);
@@ -13,6 +13,7 @@ import "./logs-contract";
13
13
  import "./network-contracts";
14
14
  import "./packages-contract";
15
15
  import "./scheduler-contract";
16
+ import "./sync-check-contract";
16
17
  import "./tool-access-control-contract";
17
18
  export * from "./app-updates-contract";
18
19
  export { DEVICE_OPERATIONS_CONTRACT_VERSION, DEVICE_OPERATIONS_MAX_QUERY_LENGTH, DEVICE_OPERATIONS_MAX_RESULT_BYTES, DEVICE_OPERATIONS_MAX_ROWS, DEVICE_OPERATIONS_MAX_TOOLS, type DeviceOperationCapability, deviceOperationsProviderPackageId, deviceOperationsToolIds, getDeviceOperationsContractDefinition, type IDeviceDatabaseQueryInput, type IDeviceDatabaseQueryResult, type IDeviceOperationStatus, type IDeviceToolListInput, type IDeviceToolLookupInput, type IDeviceToolLookupResult, type IDeviceToolRunInput, } from "./device-operations-contract";
@@ -22,6 +23,7 @@ export { getInvitesContractDefinition, type IAcceptInviteResult, type ICreatedIn
22
23
  export { getLogsContractDefinition, type ILogsConsumer, LOGS_CONTRACT_VERSION, resolveLogsContract, } from "./logs-contract";
23
24
  export * from "./network-contracts";
24
25
  export * from "./packages-contract";
26
+ export * from "./sync-check-contract";
25
27
  export { getToolAccessControlContractDefinition, type IToolAccessControlConsumer, TOOL_ACCESS_CONTROL_CONTRACT_VERSION, } from "./tool-access-control-contract";
26
28
  import "./tool-catalog-contract";
27
29
  export * from "./desktop-tools-contract";
@@ -30,6 +30,7 @@ require("./logs-contract");
30
30
  require("./network-contracts");
31
31
  require("./packages-contract");
32
32
  require("./scheduler-contract");
33
+ require("./sync-check-contract");
33
34
  require("./tool-access-control-contract");
34
35
  __exportStar(require("./app-updates-contract"), exports);
35
36
  var device_operations_contract_1 = require("./device-operations-contract");
@@ -54,6 +55,7 @@ Object.defineProperty(exports, "LOGS_CONTRACT_VERSION", { enumerable: true, get:
54
55
  Object.defineProperty(exports, "resolveLogsContract", { enumerable: true, get: function () { return logs_contract_1.resolveLogsContract; } });
55
56
  __exportStar(require("./network-contracts"), exports);
56
57
  __exportStar(require("./packages-contract"), exports);
58
+ __exportStar(require("./sync-check-contract"), exports);
57
59
  var tool_access_control_contract_1 = require("./tool-access-control-contract");
58
60
  Object.defineProperty(exports, "getToolAccessControlContractDefinition", { enumerable: true, get: function () { return tool_access_control_contract_1.getToolAccessControlContractDefinition; } });
59
61
  Object.defineProperty(exports, "TOOL_ACCESS_CONTROL_CONTRACT_VERSION", { enumerable: true, get: function () { return tool_access_control_contract_1.TOOL_ACCESS_CONTROL_CONTRACT_VERSION; } });
@@ -47,8 +47,9 @@ function getInvitesContractDefinition() {
47
47
  field("expiresInMs", field_type_1.FieldType.number, "Lifetime override in milliseconds.", {
48
48
  optional: true,
49
49
  }),
50
- field("uses", field_type_1.FieldType.string, "1 or unlimited (default unlimited).", {
50
+ field("uses", field_type_1.FieldType.any, "1 or unlimited (default unlimited).", {
51
51
  optional: true,
52
+ enumValues: [1, "unlimited"],
52
53
  }),
53
54
  ],
54
55
  outputFields: [field("invite", field_type_1.FieldType.object, "The issued invite and encodings.")],
@@ -0,0 +1,97 @@
1
+ import type { IChangeCount, ICountChangesRequest } from "../../data/change-tracking";
2
+ import type { Observable } from "../../observable";
3
+ import type { IContractConsumerBase } from "../contract-proxy";
4
+ import type { IContractDefinition } from "../types";
5
+ /** Version of the built-in System Sync Check contract. */
6
+ export declare const SYNC_CHECK_CONTRACT_VERSION = 1;
7
+ /** A device's answer to `countChanges`. */
8
+ export interface ICountChangesResult {
9
+ /** Tables this device never syncs for the context; the asking device leaves them out too. */
10
+ excludedTableNames: string[];
11
+ counts: IChangeCount[];
12
+ }
13
+ /** One change another device has that this device is missing. */
14
+ export interface IMissingChange {
15
+ changeId: string;
16
+ tableName: string;
17
+ recordId: string;
18
+ op: string;
19
+ path: string;
20
+ /** Author time. Hybrid-logical-clock milliseconds. */
21
+ createdAt: number;
22
+ }
23
+ /** What the check found when it compared this device with one other device in one context. */
24
+ export interface ISyncGap {
25
+ dataContextId: string;
26
+ dataContextName: string;
27
+ /** The device that has the changes. */
28
+ deviceId: string;
29
+ deviceName?: string;
30
+ checkedAt: number;
31
+ /** Changes the other device has that this device is missing. */
32
+ missingCount: number;
33
+ /** Up to 100 of the missing changes, oldest first. */
34
+ missing: IMissingChange[];
35
+ /** True when the check stopped before comparing everything; there may be more. */
36
+ truncated: boolean;
37
+ }
38
+ /** Live summary of the latest checks on this device. */
39
+ export interface ISyncCheckStatus {
40
+ /** Missing changes found, each counted once across contexts and devices. */
41
+ missingChangeCount: number;
42
+ /** When the latest check finished. */
43
+ lastCheckedAt?: number;
44
+ }
45
+ /** Result of `checkSyncGaps`. */
46
+ export interface ISyncCheckResult {
47
+ /** Device comparisons that completed. Devices without the check are skipped. */
48
+ compared: number;
49
+ /** Missing changes found. */
50
+ missingCount: number;
51
+ /** At least one comparison stopped at its bound; another check continues it. */
52
+ truncated?: boolean;
53
+ message: string;
54
+ }
55
+ /** Result of `repairSyncGaps`. */
56
+ export interface ISyncRepairResult {
57
+ /** Missing changes this device now holds. */
58
+ repaired: number;
59
+ /** Missing changes still found after the repair. */
60
+ remaining: number;
61
+ message: string;
62
+ }
63
+ /** Typed consumer proxy for System Sync Check v1. */
64
+ export interface ISyncCheckConsumer extends IContractConsumerBase {
65
+ tools: {
66
+ /** Count this device's active changes in the call's context. Group Readers may call it. */
67
+ countChanges(input: ICountChangesRequest): Promise<ICountChangesResult>;
68
+ /** What the latest checks found, for one context or every context. Local callers only. */
69
+ getSyncGaps(input?: {
70
+ dataContextId?: string;
71
+ }): Promise<{
72
+ gaps: ISyncGap[];
73
+ }>;
74
+ /** Check now against each connected device. Local callers only. */
75
+ checkSyncGaps(input?: {
76
+ dataContextId?: string;
77
+ }): Promise<ISyncCheckResult>;
78
+ /** Fetch and apply the missing changes found in one context. Local callers only. */
79
+ repairSyncGaps(input: {
80
+ dataContextId: string;
81
+ deviceId?: string;
82
+ }): Promise<ISyncRepairResult>;
83
+ };
84
+ observables: {
85
+ /** Live summary; available over the local host transport only. */
86
+ status: Observable<ISyncCheckStatus>;
87
+ };
88
+ }
89
+ /**
90
+ * Return the System Sync Check contract shape.
91
+ *
92
+ * Once a day each device compares its change history for each personal and group context with
93
+ * each connected device's, and reports changes the other device has that this one is missing.
94
+ * `countChanges` is how devices compare: other devices in a group may call it with Reader
95
+ * access. The other tools serve this device's UI and CLI and refuse remote callers.
96
+ */
97
+ export declare function getSyncCheckContractDefinition(): IContractDefinition;
@@ -0,0 +1,115 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SYNC_CHECK_CONTRACT_VERSION = void 0;
4
+ exports.getSyncCheckContractDefinition = getSyncCheckContractDefinition;
5
+ const access_level_1 = require("../../data/access-level");
6
+ const system_ids_1 = require("../../system-ids");
7
+ const field_type_1 = require("../../types/field-type");
8
+ const system_contracts_1 = require("../system-contracts");
9
+ /** Version of the built-in System Sync Check contract. */
10
+ exports.SYNC_CHECK_CONTRACT_VERSION = 1;
11
+ const field = (name, type, description, opts = {}) => ({ name, type, description, ...opts });
12
+ const contextInput = field("dataContextId", field_type_1.FieldType.id, "Personal or group context ID.", {
13
+ optional: true,
14
+ });
15
+ let definition;
16
+ /**
17
+ * Return the System Sync Check contract shape.
18
+ *
19
+ * Once a day each device compares its change history for each personal and group context with
20
+ * each connected device's, and reports changes the other device has that this one is missing.
21
+ * `countChanges` is how devices compare: other devices in a group may call it with Reader
22
+ * access. The other tools serve this device's UI and CLI and refuse remote callers.
23
+ */
24
+ function getSyncCheckContractDefinition() {
25
+ definition ??= {
26
+ contractId: system_ids_1.syncCheckContractId,
27
+ version: exports.SYNC_CHECK_CONTRACT_VERSION,
28
+ devTag: "dev",
29
+ name: "System Sync Check",
30
+ description: "Find changes another device has that this device is missing, and repair them.",
31
+ tables: [],
32
+ tools: [
33
+ {
34
+ name: "countChanges",
35
+ usageDescription: "Count this device's active changes per table, or per table and change-id prefix, with a digest of their ids.",
36
+ inputFields: [
37
+ field("before", field_type_1.FieldType.string, "Count only change ids below this one."),
38
+ field("excludedTableNames", field_type_1.FieldType.string, "Tables to leave out.", {
39
+ optional: true,
40
+ isArray: true,
41
+ }),
42
+ field("tableName", field_type_1.FieldType.string, "Count only this table.", { optional: true }),
43
+ field("prefix", field_type_1.FieldType.string, "Count only ids that start with this.", {
44
+ optional: true,
45
+ }),
46
+ field("prefixLength", field_type_1.FieldType.number, "Also group by this many id characters.", {
47
+ optional: true,
48
+ }),
49
+ field("afterPrefix", field_type_1.FieldType.string, "Resume after this prefix; grouped pages contain at most 500 rows.", {
50
+ optional: true,
51
+ }),
52
+ ],
53
+ outputFields: [
54
+ field("excludedTableNames", field_type_1.FieldType.string, "Tables this device never syncs.", {
55
+ isArray: true,
56
+ }),
57
+ field("counts", field_type_1.FieldType.object, "Counts and digests.", { isArray: true }),
58
+ ],
59
+ remoteAccessLevel: access_level_1.AccessLevel.Reader,
60
+ },
61
+ {
62
+ name: "getSyncGaps",
63
+ usageDescription: "List changes the latest checks found on another device but not here.",
64
+ inputFields: [contextInput],
65
+ outputFields: [
66
+ field("gaps", field_type_1.FieldType.object, "Findings, one per context and device.", {
67
+ isArray: true,
68
+ }),
69
+ ],
70
+ },
71
+ {
72
+ name: "checkSyncGaps",
73
+ usageDescription: "Compare one context, or every context, with each connected device now. Only reports.",
74
+ inputFields: [contextInput],
75
+ outputFields: [
76
+ field("compared", field_type_1.FieldType.number, "Device comparisons that completed."),
77
+ field("missingCount", field_type_1.FieldType.number, "Missing changes found."),
78
+ field("truncated", field_type_1.FieldType.boolean, "Another check is needed to finish a bounded comparison.", { optional: true }),
79
+ field("message", field_type_1.FieldType.string, "Human-readable result."),
80
+ ],
81
+ },
82
+ {
83
+ name: "repairSyncGaps",
84
+ usageDescription: "Fetch the missing changes found in one context from the devices that have them and apply them as sync would.",
85
+ inputFields: [
86
+ field("dataContextId", field_type_1.FieldType.id, "Personal or group context ID."),
87
+ field("deviceId", field_type_1.FieldType.id, "Repair only what this device has.", {
88
+ optional: true,
89
+ }),
90
+ ],
91
+ outputFields: [
92
+ field("repaired", field_type_1.FieldType.number, "Missing changes this device now holds."),
93
+ field("remaining", field_type_1.FieldType.number, "Missing changes still found."),
94
+ field("message", field_type_1.FieldType.string, "Human-readable result."),
95
+ ],
96
+ },
97
+ ],
98
+ observables: [
99
+ {
100
+ name: "status",
101
+ description: "Live summary of the latest checks on this device.",
102
+ valueType: field_type_1.FieldType.object,
103
+ writable: false,
104
+ },
105
+ ],
106
+ events: [],
107
+ };
108
+ return definition;
109
+ }
110
+ (0, system_contracts_1.registerSystemContract)({
111
+ providerPackageId: system_ids_1.peersSystemPackageId,
112
+ contractId: system_ids_1.syncCheckContractId,
113
+ version: exports.SYNC_CHECK_CONTRACT_VERSION,
114
+ getDefinition: getSyncCheckContractDefinition,
115
+ });
@@ -0,0 +1,15 @@
1
+ import { type IField } from "../types/field-type";
2
+ /**
3
+ * Check a contract tool's arguments against its declared input fields. The Runtime does this
4
+ * before every tool run, whoever calls: the UI, the CLI, an assistant, a package, or another
5
+ * device. A missing or `null` input counts as `{}`. Optional fields that are `null` are left
6
+ * out first, and an ISO string in a `Date` field is read as a date.
7
+ *
8
+ * @param fields - The tool's input fields, from its contract definition.
9
+ * @param input - The tool's argument as the caller sent it.
10
+ * @param toolName - The tool's name, for the error message.
11
+ * @returns The arguments without empty optional fields and with `Date` fields as dates.
12
+ * @throws If the input isn't an object, or a field is missing, of the wrong type, or not one
13
+ * of its allowed values.
14
+ */
15
+ export declare function checkToolArguments(fields: IField[], input: unknown, toolName: string): Record<string, unknown>;
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.checkToolArguments = checkToolArguments;
4
+ const types_1 = require("../data/orm/types");
5
+ const field_type_1 = require("../types/field-type");
6
+ /**
7
+ * Check a contract tool's arguments against its declared input fields. The Runtime does this
8
+ * before every tool run, whoever calls: the UI, the CLI, an assistant, a package, or another
9
+ * device. A missing or `null` input counts as `{}`. Optional fields that are `null` are left
10
+ * out first, and an ISO string in a `Date` field is read as a date.
11
+ *
12
+ * @param fields - The tool's input fields, from its contract definition.
13
+ * @param input - The tool's argument as the caller sent it.
14
+ * @param toolName - The tool's name, for the error message.
15
+ * @returns The arguments without empty optional fields and with `Date` fields as dates.
16
+ * @throws If the input isn't an object, or a field is missing, of the wrong type, or not one
17
+ * of its allowed values.
18
+ */
19
+ function checkToolArguments(fields, input, toolName) {
20
+ const source = input ?? {};
21
+ if (typeof source !== "object" ||
22
+ Array.isArray(source) ||
23
+ (Object.getPrototypeOf(source) !== Object.prototype && Object.getPrototypeOf(source) !== null)) {
24
+ throw new Error(`Tool argument validation failed for ${toolName}: input must be an object`);
25
+ }
26
+ // An optional argument that is null (as models often send) or undefined is left out.
27
+ const args = (0, types_1.withoutEmptyFields)(fields, { ...source });
28
+ for (const field of fields) {
29
+ if (field.type === field_type_1.FieldType.Date && !field.isArray && typeof args[field.name] === "string") {
30
+ args[field.name] = new Date(args[field.name]);
31
+ }
32
+ }
33
+ // Zod reads named properties, including inherited ones. Object.prototype must not supply
34
+ // a missing argument such as `constructor`, or invalidate an omitted optional one.
35
+ const result = (0, types_1.fieldsToArgumentsSchema)(fields).safeParse(Object.assign(Object.create(null), args));
36
+ if (!result.success) {
37
+ const problems = result.error.issues.map((issue) => `${issue.path.join(".") || "input"}: ${issue.message}`);
38
+ throw new Error(`Tool argument validation failed for ${toolName}: ${problems.join("; ")}`);
39
+ }
40
+ return args;
41
+ }
@@ -8,15 +8,14 @@ exports.findToolCatalogEntry = findToolCatalogEntry;
8
8
  exports.projectToolCatalogAccess = projectToolCatalogAccess;
9
9
  exports.invokeToolCatalogTool = invokeToolCatalogTool;
10
10
  const group_permissions_1 = require("../data/group-permissions");
11
- const types_1 = require("../data/orm/types");
12
11
  const tool_access_1 = require("../data/tool-access");
13
12
  const tools_1 = require("../data/tools");
14
13
  const system_ids_1 = require("../system-ids");
15
- const field_type_1 = require("../types/field-type");
16
14
  const utils_1 = require("../utils");
17
15
  const contract_provider_router_1 = require("./contract-provider-router");
18
16
  const contract_resolvers_1 = require("./contract-resolvers");
19
17
  const system_contracts_1 = require("./system-contracts");
18
+ const tool_arguments_1 = require("./tool-arguments");
20
19
  const tool_catalog_1 = require("./tool-catalog");
21
20
  const tool_catalog_identity_1 = require("./tool-catalog-identity");
22
21
  const tool_catalog_migrations_1 = require("./tool-catalog-migrations");
@@ -258,16 +257,7 @@ async function invokeToolCatalogTool(dataContext, input, context, opts = {}) {
258
257
  if (liveLevel === undefined || liveLevel > opts.maxAccessLevel)
259
258
  throw new Error("Tool provider access level exceeds the authorized workflow call");
260
259
  }
261
- const source = input.args === undefined ? {} : input.args;
262
- requirePlainObject(source, "Tool args");
263
- // An optional argument that is null (as models often send) or undefined is left out.
264
- const args = (0, types_1.withoutEmptyFields)(tool.inputFields, { ...source });
265
- for (const field of tool.inputFields)
266
- if (field.type === field_type_1.FieldType.Date && !field.isArray && typeof args[field.name] === "string")
267
- args[field.name] = new Date(args[field.name]);
268
- const validated = (0, types_1.fieldsToSchema)(tool.inputFields).passthrough().safeParse(args);
269
- if (!validated.success)
270
- throw new Error(`Tool argument validation failed: ${validated.error.message}`);
260
+ const args = (0, tool_arguments_1.checkToolArguments)(tool.inputFields, input.args, target.toolName);
271
261
  const dataContextId = dataContext.dataContextId;
272
262
  const { workflow: _workflow, ...caller } = context;
273
263
  const endpointContext = loader.isolatedRuntime?.isInstalled(providerPackageId)
@@ -179,13 +179,17 @@ function validateToolCatalogWrite(existing, candidate, recordId = candidate?.too
179
179
  !isMissingTargetQuarantine(existing) &&
180
180
  isMissingTargetQuarantine(candidate))
181
181
  throw new Error(`Quarantined tool identity: ${candidate.toolId}`);
182
- // Quarantine preserves the original evidence byte-for-byte; it cannot retarget it.
182
+ // Quarantine preserves the original evidence byte-for-byte; it cannot retarget it. That
183
+ // includes an existing quarantine's reason: a peer's resolve patch whose `catalogReason`
184
+ // half arrives first must park, not erase the missing-target reason its other half needs.
183
185
  if (existing &&
184
186
  candidate.catalogState === "quarantined" &&
185
187
  existing.contractId === candidate.contractId &&
186
188
  existing.contractVersion === candidate.contractVersion &&
187
189
  existing.toolName === candidate.toolName &&
188
- existing.catalogIdentity === candidate.catalogIdentity)
190
+ existing.catalogIdentity === candidate.catalogIdentity &&
191
+ (existing.catalogState !== "quarantined" ||
192
+ (existing.catalogReason ?? undefined) === (candidate.catalogReason ?? undefined)))
189
193
  return;
190
194
  // Changing quarantined identity evidence also requires an approved repair, even
191
195
  // when the candidate stays quarantined and would be resolved by reconciliation.
@@ -51,6 +51,37 @@ export declare const changeRecordSchema: z.ZodObject<{
51
51
  * One JSON-patch operation (or root snapshot / delete) stored for synchronization.
52
52
  */
53
53
  export type IChangeRecord = z.infer<typeof changeRecordSchema>;
54
+ /** Which active changes {@link ChangeTrackingTable.countChanges} counts, and how it groups them. */
55
+ export interface ICountChangesRequest {
56
+ /**
57
+ * Count only change ids below this one. The first 10 characters of a change id are its author
58
+ * time in base36 milliseconds, so a 10-character prefix counts changes written before then.
59
+ */
60
+ before: string;
61
+ /** Tables to leave out. */
62
+ excludedTableNames?: string[];
63
+ /** Count only this table. */
64
+ tableName?: string;
65
+ /** Count only change ids that start with this. */
66
+ prefix?: string;
67
+ /** Group by the first this-many characters of the change id as well as by table. */
68
+ prefixLength?: number;
69
+ /** Count ids after this entire prefix; also resumes grouped pages (at most 500 rows each). */
70
+ afterPrefix?: string;
71
+ }
72
+ /** Number of active changes in one table (and one id prefix), with a digest of their ids. */
73
+ export interface IChangeCount {
74
+ tableName: string;
75
+ /** The id prefix counted, when the request set `prefixLength`. */
76
+ prefix?: string;
77
+ count: number;
78
+ /**
79
+ * Two sums of numbers taken from the last 10 characters of each change id, as decimal text.
80
+ * Equal counts with different sums mean different changes, such as one device missing an edit
81
+ * that replaced an earlier change.
82
+ */
83
+ sums: string;
84
+ }
54
85
  /**
55
86
  * Local-only change log used for peer sync.
56
87
  *
@@ -134,6 +165,15 @@ export declare class ChangeTrackingTable extends SQLDataSource<IChangeRecord> {
134
165
  * @returns Array of changeIds with no change records
135
166
  */
136
167
  filterToMissingChangeIds(changeIds: string[]): Promise<string[]>;
168
+ /**
169
+ * Count active changes per table, or per table and change-id prefix, with a digest of their ids.
170
+ * Two devices holding the same active changes get the same results. One scan of the
171
+ * `(tableName, supersededAt)` index, which holds every change id.
172
+ *
173
+ * @param request - Which changes to count and how to group them
174
+ * @returns One row per table (and prefix) that has any active changes
175
+ */
176
+ countChanges(request: ICountChangesRequest): Promise<IChangeCount[]>;
137
177
  /**
138
178
  * Mark every still-active change for `recordId` whose `createdAt` is older
139
179
  * than `supersededAt`. Used after a root snapshot, delete, or restore.