@peers-app/peers-sdk 0.23.9 → 0.23.11

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 (35) hide show
  1. package/dist/context/user-context-singleton.js +19 -4
  2. package/dist/contracts/__tests__/builder.test.js +25 -0
  3. package/dist/contracts/__tests__/contract-provider-router.test.js +45 -0
  4. package/dist/contracts/__tests__/contract-proxy.test.js +67 -0
  5. package/dist/contracts/__tests__/contract-resolvers.test.js +133 -0
  6. package/dist/contracts/__tests__/remote-contract-transport.test.d.ts +1 -0
  7. package/dist/contracts/__tests__/remote-contract-transport.test.js +89 -0
  8. package/dist/contracts/builder.d.ts +47 -1
  9. package/dist/contracts/builder.js +80 -22
  10. package/dist/contracts/contract-provider-router.d.ts +6 -0
  11. package/dist/contracts/contract-provider-router.js +25 -3
  12. package/dist/contracts/contract-proxy.d.ts +41 -1
  13. package/dist/contracts/contract-proxy.js +103 -0
  14. package/dist/contracts/contract-resolvers.d.ts +47 -13
  15. package/dist/contracts/contract-resolvers.js +125 -35
  16. package/dist/contracts/extract.d.ts +4 -1
  17. package/dist/contracts/extract.js +3 -1
  18. package/dist/contracts/index.d.ts +4 -3
  19. package/dist/contracts/index.js +10 -2
  20. package/dist/contracts/remote-contract-transport.d.ts +69 -0
  21. package/dist/contracts/remote-contract-transport.js +95 -0
  22. package/dist/contracts/types.d.ts +57 -0
  23. package/dist/data/persistent-vars.d.ts +8 -0
  24. package/dist/data/persistent-vars.js +56 -11
  25. package/dist/data/tool-access-approvals.d.ts +2 -2
  26. package/dist/data/tools.d.ts +4 -0
  27. package/dist/data/tools.js +7 -0
  28. package/dist/package-loader/isolated-artifact.js +16 -1
  29. package/dist/package-loader/isolated-runtime.d.ts +5 -2
  30. package/dist/package-loader/package-loader.d.ts +15 -1
  31. package/dist/package-loader/package-loader.js +48 -0
  32. package/dist/package-loader/resolve-consumed-contracts.d.ts +6 -2
  33. package/dist/package-loader/resolve-consumed-contracts.js +30 -2
  34. package/dist/package-loader/resolve-consumed-contracts.test.js +43 -0
  35. package/package.json +1 -1
@@ -1,7 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.PackageBuilder = exports.ContractBuilder = void 0;
4
+ exports.createDeferredContractHandle = createDeferredContractHandle;
4
5
  exports.createConsumedContractHandle = createConsumedContractHandle;
6
+ exports.settleContractHandle = settleContractHandle;
7
+ exports.rejectContractHandle = rejectContractHandle;
5
8
  exports.settleConsumedContractHandle = settleConsumedContractHandle;
6
9
  exports.rejectConsumedContractHandle = rejectConsumedContractHandle;
7
10
  exports.definePackage = definePackage;
@@ -106,14 +109,14 @@ class ContractBuilder {
106
109
  }
107
110
  }
108
111
  exports.ContractBuilder = ContractBuilder;
109
- const consumedContractDeferreds = new WeakMap();
112
+ const contractHandleDeferreds = new WeakMap();
110
113
  /**
111
- * Create a deferred consumed-contract handle. Settlement is SDK-owned via
112
- * {@link settleConsumedContractHandle} / {@link rejectConsumedContractHandle}.
114
+ * Create a deferred contract handle. Settlement is SDK-owned via
115
+ * {@link settleContractHandle} / {@link rejectContractHandle}.
113
116
  *
114
117
  * @internal
115
118
  */
116
- function createConsumedContractHandle() {
119
+ function createDeferredContractHandle() {
117
120
  let resolve;
118
121
  let reject;
119
122
  const consumer = new Promise((res, rej) => {
@@ -124,25 +127,53 @@ function createConsumedContractHandle() {
124
127
  // error before any package code awaits the handle.
125
128
  consumer.catch(() => { });
126
129
  const handle = { consumer };
127
- consumedContractDeferreds.set(handle, { resolve, reject, settled: false });
130
+ contractHandleDeferreds.set(handle, { resolve, reject, settled: false });
128
131
  return handle;
129
132
  }
130
133
  /**
131
- * Resolve a consumed-contract handle with a live proxy, or `undefined` for an
132
- * unmatched optional dependency. One-shot — throws if already settled.
134
+ * Create a deferred consumed-contract handle. Settlement is SDK-owned via
135
+ * {@link settleConsumedContractHandle} / {@link rejectConsumedContractHandle}.
133
136
  *
134
- * @internal Used by the package-loader when settling `consumes` handles.
137
+ * @internal
135
138
  */
136
- function settleConsumedContractHandle(handle, consumer) {
137
- const deferred = consumedContractDeferreds.get(handle);
139
+ function createConsumedContractHandle() {
140
+ return createDeferredContractHandle();
141
+ }
142
+ function takeDeferred(handle) {
143
+ const deferred = contractHandleDeferreds.get(handle);
138
144
  if (!deferred) {
139
- throw new Error("Unknown consumed-contract handle");
145
+ throw new Error("Unknown contract handle");
140
146
  }
141
147
  if (deferred.settled) {
142
- throw new Error("Consumed-contract handle has already been settled");
148
+ throw new Error("Contract handle has already been settled");
143
149
  }
144
150
  deferred.settled = true;
145
- deferred.resolve(consumer);
151
+ return deferred;
152
+ }
153
+ /**
154
+ * Resolve a deferred contract handle. One-shot — throws if already settled.
155
+ *
156
+ * @internal Used by the package-loader when settling `consumes` / `remote` handles.
157
+ */
158
+ function settleContractHandle(handle, value) {
159
+ takeDeferred(handle).resolve(value);
160
+ }
161
+ /**
162
+ * Reject a deferred contract handle. One-shot — throws if already settled.
163
+ *
164
+ * @internal Used by the package-loader when settling `consumes` / `remote` handles.
165
+ */
166
+ function rejectContractHandle(handle, reason) {
167
+ takeDeferred(handle).reject(reason);
168
+ }
169
+ /**
170
+ * Resolve a consumed-contract handle with a live proxy, or `undefined` for an
171
+ * unmatched optional dependency. One-shot — throws if already settled.
172
+ *
173
+ * @internal Used by the package-loader when settling `consumes` handles.
174
+ */
175
+ function settleConsumedContractHandle(handle, consumer) {
176
+ settleContractHandle(handle, consumer);
146
177
  }
147
178
  /**
148
179
  * Reject a consumed-contract handle (e.g. required dependency missing).
@@ -151,15 +182,7 @@ function settleConsumedContractHandle(handle, consumer) {
151
182
  * @internal Used by the package-loader when settling `consumes` handles.
152
183
  */
153
184
  function rejectConsumedContractHandle(handle, reason) {
154
- const deferred = consumedContractDeferreds.get(handle);
155
- if (!deferred) {
156
- throw new Error("Unknown consumed-contract handle");
157
- }
158
- if (deferred.settled) {
159
- throw new Error("Consumed-contract handle has already been settled");
160
- }
161
- deferred.settled = true;
162
- deferred.reject(reason);
185
+ rejectContractHandle(handle, reason);
163
186
  }
164
187
  /**
165
188
  * Top-level builder passed to the `definePackage` callback.
@@ -175,6 +198,7 @@ class PackageBuilder {
175
198
  _version;
176
199
  _contracts = [];
177
200
  _consumes = [];
201
+ _remotes = [];
178
202
  /** Package-level assistants (not part of any contract). */
179
203
  assistants = [];
180
204
  /** Package-level app navigation items (not part of any contract). */
@@ -248,6 +272,39 @@ class PackageBuilder {
248
272
  });
249
273
  return handle;
250
274
  }
275
+ /**
276
+ * Declare that this package calls a contract's tools on **other devices**.
277
+ *
278
+ * A sibling of {@link consumes} for the mesh: the resolved
279
+ * {@link IRemoteContractConsumer} hands out a tools-only consumer per target device
280
+ * (`remote.device(deviceId).tools.x(...)`). No local provider is needed — a package
281
+ * commonly targets its *own* contract on a peer's device — and there is no `optional`
282
+ * flag because whether the remote device can serve the call is only known per call.
283
+ * The loader settles the handle once the contract definition is registered locally
284
+ * (from this package's `provides` or any installed package) and rejects it otherwise.
285
+ *
286
+ * Await `handle.consumer` from runtime code after package definition, not inside
287
+ * this callback.
288
+ *
289
+ * @typeParam T - Consumer proxy type (defaults to {@link IContractConsumer}).
290
+ * @param contractId - Must be a valid peer ID (25-char alphanumeric from `newid()`).
291
+ * @param version - Positive integer version number.
292
+ */
293
+ remote(contractId, version) {
294
+ if (!(0, utils_1.isid)(contractId)) {
295
+ throw new Error(`remote contractId must be a valid peer ID (25-char alphanumeric), got "${contractId}"`);
296
+ }
297
+ if (version < 1 || !Number.isInteger(version)) {
298
+ throw new Error(`remote version must be a positive integer, got ${version}`);
299
+ }
300
+ const handle = createDeferredContractHandle();
301
+ this._remotes.push({
302
+ contractId,
303
+ version,
304
+ handle: handle,
305
+ });
306
+ return handle;
307
+ }
251
308
  /** @internal Finalize and return the package definition result. */
252
309
  _build() {
253
310
  if (!this._packageId) {
@@ -274,6 +331,7 @@ class PackageBuilder {
274
331
  version: this._version,
275
332
  contracts,
276
333
  consumes: [...this._consumes],
334
+ remotes: [...this._remotes],
277
335
  alsoImplements: alsoImplementsMap,
278
336
  assistants: [...this.assistants],
279
337
  appNavs: [...this.appNavs],
@@ -33,6 +33,12 @@ export interface IContractProviderEndpointRequest {
33
33
  providerPackageId?: string;
34
34
  /** Canonical data context returned by the authorization hook. */
35
35
  dataContextId?: string;
36
+ /**
37
+ * Device that should execute calls on this route when it differs from the local
38
+ * device. Copied from {@link IContractCall.targetDeviceId} by the router; the endpoint
39
+ * resolver decides whether forwarding is permitted for this router's principal.
40
+ */
41
+ targetDeviceId?: string;
36
42
  /** Authorized caller context for this route. */
37
43
  context: IContractCallContext;
38
44
  /** Sink for provider-to-consumer subscription notifications. */
@@ -2,7 +2,22 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createContractProviderRouter = createContractProviderRouter;
4
4
  const contract_transport_1 = require("./contract-transport");
5
- const routeKey = (contractId, version, providerPackageId, dataContextId) => JSON.stringify([contractId, version, providerPackageId ?? null, dataContextId ?? null]);
5
+ const routeKey = (contractId, version, providerPackageId, dataContextId, targetDeviceId) => JSON.stringify([
6
+ contractId,
7
+ version,
8
+ providerPackageId ?? null,
9
+ dataContextId ?? null,
10
+ targetDeviceId ?? null,
11
+ ]);
12
+ /**
13
+ * Reject remote-target operations the mesh path does not support yet. Only tool runs
14
+ * cross devices; subscriptions, tables, observables, and events stay local.
15
+ */
16
+ function assertRemoteTargetCallSupported(call) {
17
+ if (call.member !== "tool" || call.operation !== "run") {
18
+ throw new Error(`Contract ${call.member} '${call.name}'.${call.operation} cannot target a remote device; only tool runs are remotely callable`);
19
+ }
20
+ }
6
21
  /**
7
22
  * Own the sole {@link CONTRACT_REQUEST_CHANNEL} handler for a shared connection.
8
23
  *
@@ -91,7 +106,11 @@ function createContractProviderRouter(transport, opts) {
91
106
  if (isSubscribe && subscriptionId) {
92
107
  pendingSubscriptionIds.add(subscriptionId);
93
108
  }
109
+ const targetDeviceId = call.targetDeviceId || undefined;
94
110
  try {
111
+ if (targetDeviceId) {
112
+ assertRemoteTargetCallSupported(call);
113
+ }
95
114
  const inboundContext = opts.context ?? {};
96
115
  const authorization = await opts.authorizeCall(call, inboundContext);
97
116
  if (disposed) {
@@ -106,14 +125,17 @@ function createContractProviderRouter(transport, opts) {
106
125
  contractId: call.contractId,
107
126
  version: call.version,
108
127
  dataContextId: authorization.dataContextId,
128
+ targetDeviceId,
109
129
  context,
110
130
  notify,
111
131
  };
112
- const providerPackageId = opts.resolveProviderPackageId
132
+ // Provider selection happens on the executing device, so a remote route is
133
+ // cached by target device rather than by a locally selected provider package.
134
+ const providerPackageId = !targetDeviceId && opts.resolveProviderPackageId
113
135
  ? await opts.resolveProviderPackageId(endpointRequest)
114
136
  : undefined;
115
137
  endpointRequest.providerPackageId = providerPackageId;
116
- const endpointKey = routeKey(call.contractId, call.version, providerPackageId, authorization.dataContextId);
138
+ const endpointKey = routeKey(call.contractId, call.version, providerPackageId, authorization.dataContextId, targetDeviceId);
117
139
  const endpoint = await getEndpoint(endpointKey, endpointRequest);
118
140
  const result = await endpoint.handleCall(routedCall, context);
119
141
  if (isSubscribe && subscriptionId) {
@@ -4,7 +4,7 @@ import type { IToolInstance } from "../data/tools";
4
4
  import type { Event, ISubscriptionResult } from "../events";
5
5
  import { type Observable } from "../observable";
6
6
  import { type IContractTransport } from "./contract-transport";
7
- import type { IContractDefinition } from "./types";
7
+ import type { IContractDefinition, IRemoteContractConsumer } from "./types";
8
8
  /**
9
9
  * The RPC event name used to proxy a single {@link IContractCall} over a
10
10
  * {@link Connection} (`connection.emit(CONTRACT_CALL_RPC, call)` on the consumer
@@ -53,6 +53,15 @@ export interface IContractCall {
53
53
  * id to the local provider user's personal context.
54
54
  */
55
55
  dataContextId?: string;
56
+ /**
57
+ * Device that should execute this call when it is not the local device. Set by a
58
+ * {@link createRemoteContractConsumer} device consumer and honored only by the caller's own
59
+ * trusted host, which forwards the call over the device mesh and strips this field
60
+ * before it leaves the device. A provider router serving *remote* callers rejects any
61
+ * call that still carries it, so one device can never relay another device's call
62
+ * under its own identity.
63
+ */
64
+ targetDeviceId?: string;
56
65
  /**
57
66
  * For any subscription call (a generic `event`, a table `dataChanged`, or an observable
58
67
  * value stream): the consumer-chosen id that ties a `"subscribe"` (and its later
@@ -236,6 +245,8 @@ export interface IContractToolContext {
236
245
  * consumer and provider boundaries. Disposal is idempotent and prevents new remote operations;
237
246
  * cached observable reads remain available afterward.
238
247
  *
248
+ * To run tools on another device use {@link createRemoteContractConsumer} instead.
249
+ *
239
250
  * @param contract The contract definition (shape) being consumed.
240
251
  * @param transport Bidirectional channel to the provider; requests go out on
241
252
  * {@link CONTRACT_REQUEST_CHANNEL}, notifications arrive on {@link CONTRACT_NOTIFY_CHANNEL}.
@@ -244,6 +255,29 @@ export interface IContractToolContext {
244
255
  export declare function createContractConsumer(contract: IContractDefinition, transport: IContractTransport, opts?: {
245
256
  dataContextId?: string;
246
257
  }): IContractConsumer;
258
+ /**
259
+ * Build the **remote** consumer side of a contract: a factory of tools-only consumers whose
260
+ * calls execute on other devices (the runtime behind {@link PackageBuilder.remote}).
261
+ *
262
+ * `device(deviceId)` returns a consumer of the contract that stamps
263
+ * {@link IContractCall.targetDeviceId} on every tool call. The trusted local host router
264
+ * behind `transport` recognizes the target, forwards the call over the device mesh, and the
265
+ * remote device authorizes it against the caller's group role and the tool's
266
+ * {@link IContractTool.remoteAccessLevel}. Consumers are cached per device; passing this
267
+ * device's own id resolves locally. No notify listener or observable mirror is created
268
+ * because subscriptions do not cross devices yet, so `tables`, `observables`, and `events`
269
+ * throw on access and `loadingPromise` resolves immediately. Disposing the factory disposes
270
+ * every device consumer; a device consumer may also be disposed on its own.
271
+ *
272
+ * @param contract The contract definition (shape) whose tools are called remotely.
273
+ * @param transport Channel to the caller's own trusted host router (never to the remote
274
+ * device directly).
275
+ * @param opts.dataContextId Data context every call is scoped to. Cross-account calls must
276
+ * name the shared group whose membership the remote device checks.
277
+ */
278
+ export declare function createRemoteContractConsumer(contract: IContractDefinition, transport: IContractTransport, opts?: {
279
+ dataContextId?: string;
280
+ }): IRemoteContractConsumer<IContractConsumer>;
247
281
  /**
248
282
  * The live implementation a **provider** dispatches contract calls to. This is the
249
283
  * runtime-callable counterpart to {@link IResolvedContract} (which additionally tracks
@@ -279,6 +313,12 @@ export interface IContractCallContext {
279
313
  callerUserId?: string;
280
314
  /** User id of the local device executing the call. */
281
315
  localUserId?: string;
316
+ /**
317
+ * Device the call arrived from when it was made by another device. Absent for
318
+ * same-device callers. Informational for providers; authorization is decided by the
319
+ * host router before dispatch.
320
+ */
321
+ callerDeviceId?: string;
282
322
  /**
283
323
  * Provider-side data context used to resolve the caller's group/personal role.
284
324
  * In-process only — never serialized across a connection.
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.sameUserContractPermissionCheck = exports.CONTRACT_CALL_RPC = void 0;
4
4
  exports.createContractConsumer = createContractConsumer;
5
+ exports.createRemoteContractConsumer = createRemoteContractConsumer;
5
6
  exports.createContractProvider = createContractProvider;
6
7
  exports.createContractProviderEndpoint = createContractProviderEndpoint;
7
8
  exports.createContractProviderSession = createContractProviderSession;
@@ -66,6 +67,8 @@ exports.CONTRACT_CALL_RPC = contract_transport_1.CONTRACT_REQUEST_CHANNEL;
66
67
  * consumer and provider boundaries. Disposal is idempotent and prevents new remote operations;
67
68
  * cached observable reads remain available afterward.
68
69
  *
70
+ * To run tools on another device use {@link createRemoteContractConsumer} instead.
71
+ *
69
72
  * @param contract The contract definition (shape) being consumed.
70
73
  * @param transport Bidirectional channel to the provider; requests go out on
71
74
  * {@link CONTRACT_REQUEST_CHANNEL}, notifications arrive on {@link CONTRACT_NOTIFY_CHANNEL}.
@@ -334,6 +337,106 @@ function createContractConsumer(contract, transport, opts = {}) {
334
337
  consumer.loadingPromise = Promise.all(observableLoads).then(() => undefined);
335
338
  return consumer;
336
339
  }
340
+ /**
341
+ * Build a bag whose every string property access throws, used for the contract members a
342
+ * remote consumer cannot reach yet. Symbol access (inspection, `then` probing) stays inert.
343
+ */
344
+ function unreachableRemoteMembers(member) {
345
+ return new Proxy({}, {
346
+ get(_target, prop) {
347
+ if (typeof prop !== "string" || prop === "then" || prop === "toJSON") {
348
+ return undefined;
349
+ }
350
+ throw new Error(`Contract ${member} '${prop}' is not remotely accessible yet; only tools can be called on a remote device`);
351
+ },
352
+ });
353
+ }
354
+ /**
355
+ * Build the **remote** consumer side of a contract: a factory of tools-only consumers whose
356
+ * calls execute on other devices (the runtime behind {@link PackageBuilder.remote}).
357
+ *
358
+ * `device(deviceId)` returns a consumer of the contract that stamps
359
+ * {@link IContractCall.targetDeviceId} on every tool call. The trusted local host router
360
+ * behind `transport` recognizes the target, forwards the call over the device mesh, and the
361
+ * remote device authorizes it against the caller's group role and the tool's
362
+ * {@link IContractTool.remoteAccessLevel}. Consumers are cached per device; passing this
363
+ * device's own id resolves locally. No notify listener or observable mirror is created
364
+ * because subscriptions do not cross devices yet, so `tables`, `observables`, and `events`
365
+ * throw on access and `loadingPromise` resolves immediately. Disposing the factory disposes
366
+ * every device consumer; a device consumer may also be disposed on its own.
367
+ *
368
+ * @param contract The contract definition (shape) whose tools are called remotely.
369
+ * @param transport Channel to the caller's own trusted host router (never to the remote
370
+ * device directly).
371
+ * @param opts.dataContextId Data context every call is scoped to. Cross-account calls must
372
+ * name the shared group whose membership the remote device checks.
373
+ */
374
+ function createRemoteContractConsumer(contract, transport, opts = {}) {
375
+ const { contractId, version } = contract;
376
+ const { dataContextId } = opts;
377
+ const consumers = new Map();
378
+ let disposed = false;
379
+ const createDeviceConsumer = (targetDeviceId) => {
380
+ let deviceDisposed = false;
381
+ const tools = {};
382
+ for (const tool of contract.tools) {
383
+ const name = tool.name;
384
+ tools[name] = (...args) => deviceDisposed
385
+ ? Promise.reject(new Error(`Remote contract consumer ${contractId} v${version} for device ${targetDeviceId} is disposed`))
386
+ : transport.emit(contract_transport_1.CONTRACT_REQUEST_CHANNEL, {
387
+ contractId,
388
+ version,
389
+ member: "tool",
390
+ name,
391
+ operation: "run",
392
+ args,
393
+ dataContextId,
394
+ targetDeviceId,
395
+ });
396
+ }
397
+ return {
398
+ contractId,
399
+ version,
400
+ tables: unreachableRemoteMembers("table"),
401
+ tools,
402
+ observables: unreachableRemoteMembers("observable"),
403
+ events: unreachableRemoteMembers("event"),
404
+ loadingPromise: Promise.resolve(),
405
+ dispose: () => {
406
+ deviceDisposed = true;
407
+ consumers.delete(targetDeviceId);
408
+ },
409
+ };
410
+ };
411
+ return {
412
+ contractId,
413
+ version,
414
+ device(deviceId) {
415
+ if (disposed) {
416
+ throw new Error(`Remote contract consumer ${contractId} v${version} is disposed`);
417
+ }
418
+ if (typeof deviceId !== "string" || deviceId.trim() === "") {
419
+ throw new Error("device(deviceId) requires a non-empty device id");
420
+ }
421
+ let consumer = consumers.get(deviceId);
422
+ if (!consumer) {
423
+ consumer = createDeviceConsumer(deviceId);
424
+ consumers.set(deviceId, consumer);
425
+ }
426
+ return consumer;
427
+ },
428
+ dispose() {
429
+ if (disposed) {
430
+ return;
431
+ }
432
+ disposed = true;
433
+ for (const consumer of [...consumers.values()]) {
434
+ consumer.dispose();
435
+ }
436
+ consumers.clear();
437
+ },
438
+ };
439
+ }
337
440
  /**
338
441
  * Legacy gate that allows calls only when caller and provider user ids match.
339
442
  *
@@ -1,7 +1,7 @@
1
1
  import type { DataContext } from "../context/data-context";
2
2
  import type { UserContext } from "../context/user-context";
3
3
  import type { IDeviceInfo } from "../data/devices";
4
- import { type IContractProviderRouter } from "./contract-provider-router";
4
+ import { type IAuthorizedContractRoute, type IContractProviderRouter } from "./contract-provider-router";
5
5
  import { type IContractCall, type IContractResolution } from "./contract-proxy";
6
6
  import type { IContractTransport } from "./contract-transport";
7
7
  /**
@@ -48,6 +48,17 @@ export declare function registerContractResolver(contractId: string, version: nu
48
48
  * yet adopted provider selection; multiple scoped providers remain ambiguous.
49
49
  */
50
50
  export declare function getContractResolver(contractId: string, version: number, providerPackageId?: string): ContractResolver | undefined;
51
+ /**
52
+ * Verify that a connection- or message-authenticated remote identity matches a valid
53
+ * self-signed Users record in the provider's personal or shared-group data.
54
+ *
55
+ * Unsigned discovery stubs, malformed signatures, and key mismatches fail closed. This is
56
+ * the identity half of remote authorization; trust and role decisions build on it.
57
+ *
58
+ * @param userContext Provider's user context whose data is searched.
59
+ * @param remoteIdentity Identity derived from the verified transport.
60
+ */
61
+ export declare function verifyRemoteContractIdentity(userContext: UserContext, remoteIdentity: ContractRemoteIdentity): Promise<boolean>;
51
62
  /**
52
63
  * Check whether a connection-authenticated remote identity has full contract authority.
53
64
  *
@@ -56,6 +67,29 @@ export declare function getContractResolver(contractId: string, version: number,
56
67
  * {@link TrustLevel.Self}. Unsigned discovery stubs and key mismatches fail closed.
57
68
  */
58
69
  export declare function isSelfTrustedContractCaller(userContext: UserContext, remoteIdentity: ContractRemoteIdentity): Promise<boolean>;
70
+ /**
71
+ * Authorize one inbound contract call from **another device**.
72
+ *
73
+ * Layered policy, evaluated in order:
74
+ * 1. Calls that still carry a `targetDeviceId` are denied: a remote caller may not use
75
+ * this device as a relay under its identity. The tool-access-control contract is
76
+ * local-only.
77
+ * 2. The caller's keys must match a valid signed Users record known to the provider.
78
+ * 3. A Self-trusted caller (the provider's own account, or an account granted exact
79
+ * `TrustLevel.Self`) keeps full access; an omitted data context selects the provider's
80
+ * personal context.
81
+ * 4. Any other verified caller is treated as a **group member**: the call must name a
82
+ * group the provider belongs to, must be a tool run, the contract definition registered
83
+ * for that group must mark the tool with {@link IContractTool.remoteAccessLevel}, and
84
+ * the caller's role in that group must meet it. Missing loader, definition, or level
85
+ * fails closed.
86
+ *
87
+ * @param call Inbound call.
88
+ * @param userContext Provider's user context.
89
+ * @param remoteIdentity Identity derived from the verified transport (never from the call).
90
+ * @returns The canonical route and caller context for the router.
91
+ */
92
+ export declare function authorizeRemoteContractCall(call: IContractCall, userContext: UserContext, remoteIdentity: ContractRemoteIdentity): Promise<IAuthorizedContractRoute>;
59
93
  /**
60
94
  * Create a provider router for one authenticated UI-to-local-host session.
61
95
  *
@@ -79,27 +113,27 @@ export declare function createLocalUserContextContractProviderRouter(transport:
79
113
  resolveVariable?: LocalContractVariableResolver;
80
114
  }): IContractProviderRouter;
81
115
  /**
82
- * Create the local-object provider router for one verified device connection.
116
+ * Create the local-object provider router for one verified remote device, whether it is
117
+ * reached over a direct {@link Connection} or through device messages.
83
118
  *
84
- * The router derives caller identity from the connection owner supplied by the host,
85
- * authorizes every call before invoking a registered resolver, normalizes omitted data
86
- * contexts to the local user's personal context, and caches stateful endpoints per
87
- * contract/version/data-context route. The caller must match a valid signed Users record
88
- * and have exact Self trust in the provider's personal context. Explicit data-context ids
89
- * retain their existing behavior after authorization.
119
+ * The router derives caller identity from the transport owner supplied by the host,
120
+ * authorizes every call with {@link authorizeRemoteContractCall} before invoking a
121
+ * registered resolver, normalizes omitted data contexts to the local user's personal
122
+ * context for Self callers, and caches stateful endpoints per contract/version/data-context
123
+ * route. Group members reach only tools marked with `remoteAccessLevel`.
90
124
  *
91
- * @param transport Contract transport backed by one verified connection.
125
+ * @param transport Contract transport backed by one verified remote device.
92
126
  * @param opts.userContext Local user context that owns provider data.
93
- * @param opts.remoteIdentity Identity derived from the verified remote connection.
127
+ * @param opts.remoteIdentity Identity derived from the verified remote device.
94
128
  */
95
129
  export declare function createUserContextContractProviderRouter(transport: IContractTransport, opts: {
96
130
  userContext: UserContext;
97
131
  remoteIdentity: ContractRemoteIdentity;
98
132
  }): IContractProviderRouter;
99
133
  /**
100
- * Provider-side request/response glue: apply the signed-identity Self-trust host gate,
101
- * resolve the contract for the authorized data context, build a stateless provider, and
102
- * execute the call.
134
+ * Provider-side request/response glue: apply {@link authorizeRemoteContractCall}, resolve
135
+ * the contract for the authorized data context, build a stateless provider, and execute
136
+ * the call.
103
137
  *
104
138
  * Used by transports (e.g. the connection `CONTRACT_CALL_RPC` handler) to run an inbound
105
139
  * {@link IContractCall} against local state.