@peers-app/peers-sdk 0.22.0 → 0.23.1

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 (45) hide show
  1. package/dist/context/user-context-singleton.d.ts +42 -0
  2. package/dist/context/user-context-singleton.js +45 -0
  3. package/dist/contracts/__tests__/contract-proxy.test.js +51 -1
  4. package/dist/contracts/__tests__/contract-resolvers.test.js +30 -0
  5. package/dist/contracts/__tests__/playground.test.js +5 -2
  6. package/dist/contracts/__tests__/system-contracts.test.js +3 -0
  7. package/dist/contracts/contract-proxy.d.ts +6 -0
  8. package/dist/contracts/contract-proxy.js +34 -0
  9. package/dist/contracts/contract-resolvers.js +9 -1
  10. package/dist/contracts/index.d.ts +1 -0
  11. package/dist/contracts/index.js +5 -2
  12. package/dist/contracts/system/index.d.ts +2 -0
  13. package/dist/contracts/system/index.js +5 -1
  14. package/dist/contracts/system/tool-access-control-contract.d.ts +35 -0
  15. package/dist/contracts/system/tool-access-control-contract.js +70 -0
  16. package/dist/data/access-level.d.ts +72 -0
  17. package/dist/data/access-level.js +109 -0
  18. package/dist/data/access-level.test.d.ts +1 -0
  19. package/dist/data/access-level.test.js +53 -0
  20. package/dist/data/assistants.d.ts +24 -0
  21. package/dist/data/assistants.js +21 -0
  22. package/dist/data/group-member-roles.d.ts +9 -0
  23. package/dist/data/group-member-roles.js +9 -0
  24. package/dist/data/group-permissions.d.ts +55 -1
  25. package/dist/data/group-permissions.js +76 -6
  26. package/dist/data/index.d.ts +3 -0
  27. package/dist/data/index.js +3 -0
  28. package/dist/data/orm/sql.data-source.d.ts +10 -0
  29. package/dist/data/orm/sql.data-source.js +48 -30
  30. package/dist/data/orm/sql.data-source.test.js +13 -0
  31. package/dist/data/package-version-resolver.test.js +2 -0
  32. package/dist/data/tool-access-approvals.d.ts +126 -0
  33. package/dist/data/tool-access-approvals.js +86 -0
  34. package/dist/data/tool-access.d.ts +162 -0
  35. package/dist/data/tool-access.js +235 -0
  36. package/dist/data/tool-access.test.d.ts +1 -0
  37. package/dist/data/tool-access.test.js +159 -0
  38. package/dist/data/tools.d.ts +15 -0
  39. package/dist/data/tools.js +11 -0
  40. package/dist/data/workflow-logs.d.ts +5 -5
  41. package/dist/data/workflow-runs.d.ts +2 -2
  42. package/dist/device-pairing/types.d.ts +6 -6
  43. package/dist/system-ids.d.ts +18 -0
  44. package/dist/system-ids.js +19 -1
  45. package/package.json +1 -1
@@ -1,12 +1,54 @@
1
1
  import type { Table } from "../data/orm";
2
2
  import type { DataContext } from "./data-context";
3
3
  import { UserContext } from "./user-context";
4
+ /**
5
+ * Return the process user context, waiting until {@link setUserContext} if needed.
6
+ *
7
+ * Prefer an explicit `UserContext` when one is already in hand (multi-device
8
+ * tests and connection providers). Use {@link peekUserContext} to fail closed
9
+ * instead of waiting.
10
+ */
4
11
  export declare function getUserContext(): Promise<UserContext>;
12
+ /**
13
+ * Return the process user context if {@link setUserContext} has already run.
14
+ *
15
+ * Does not wait. Callers that must not hang (role checks, contract tools)
16
+ * should treat `undefined` as fail-closed.
17
+ */
18
+ export declare function peekUserContext(): UserContext | undefined;
19
+ /**
20
+ * Install the process-wide user context. Throws if one is already set, except
21
+ * in a browser/PWA where HMR and navigations may replace it.
22
+ *
23
+ * @param userContext - Context that owns local personal and group data.
24
+ */
5
25
  export declare function setUserContext(userContext: UserContext): void;
26
+ /**
27
+ * Return the current user's default data context after the singleton is ready.
28
+ */
6
29
  export declare function getDefaultDataContext(): Promise<DataContext>;
30
+ /**
31
+ * Return the table container for `dataContext`, or the singleton default.
32
+ *
33
+ * @param dataContext - Context to read tables from. Falls back to the singleton.
34
+ */
7
35
  export declare function getTableContainer(dataContext?: DataContext | undefined): import("../data/orm").TableContainer;
36
+ /**
37
+ * Install a client-proxy user context for renderer/RPC processes.
38
+ *
39
+ * No-ops when a real server-side context is already set in-process.
40
+ *
41
+ * @param userId - Local user id used as the personal data-context id.
42
+ */
8
43
  export declare function setDefaultClientUserContext(userId: string): void;
44
+ /**
45
+ * Return every table currently loaded in the default table container, keyed by
46
+ * full table name.
47
+ */
9
48
  export declare function getAllTables(): {
10
49
  [tableName: string]: Table<any>;
11
50
  };
51
+ /**
52
+ * Return the singleton user's id. Throws if {@link setUserContext} has not run.
53
+ */
12
54
  export declare const myUserId: () => string;
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.myUserId = void 0;
4
4
  exports.getUserContext = getUserContext;
5
+ exports.peekUserContext = peekUserContext;
5
6
  exports.setUserContext = setUserContext;
6
7
  exports.getDefaultDataContext = getDefaultDataContext;
7
8
  exports.getTableContainer = getTableContainer;
@@ -16,6 +17,13 @@ const user_context_1 = require("./user-context");
16
17
  var userContextInstance;
17
18
  var userContextResolved;
18
19
  var userContextPromise;
20
+ /**
21
+ * Return the process user context, waiting until {@link setUserContext} if needed.
22
+ *
23
+ * Prefer an explicit `UserContext` when one is already in hand (multi-device
24
+ * tests and connection providers). Use {@link peekUserContext} to fail closed
25
+ * instead of waiting.
26
+ */
19
27
  async function getUserContext() {
20
28
  if (userContextInstance) {
21
29
  return userContextInstance.loadingPromise;
@@ -30,6 +38,21 @@ async function getUserContext() {
30
38
  }
31
39
  return userContextPromise;
32
40
  }
41
+ /**
42
+ * Return the process user context if {@link setUserContext} has already run.
43
+ *
44
+ * Does not wait. Callers that must not hang (role checks, contract tools)
45
+ * should treat `undefined` as fail-closed.
46
+ */
47
+ function peekUserContext() {
48
+ return userContextInstance;
49
+ }
50
+ /**
51
+ * Install the process-wide user context. Throws if one is already set, except
52
+ * in a browser/PWA where HMR and navigations may replace it.
53
+ *
54
+ * @param userContext - Context that owns local personal and group data.
55
+ */
33
56
  function setUserContext(userContext) {
34
57
  if (userContextInstance) {
35
58
  if (typeof window !== "undefined") {
@@ -51,10 +74,18 @@ function setUserContext(userContext) {
51
74
  window.userContext = userContext;
52
75
  }
53
76
  }
77
+ /**
78
+ * Return the current user's default data context after the singleton is ready.
79
+ */
54
80
  async function getDefaultDataContext() {
55
81
  const userContext = await getUserContext();
56
82
  return userContext.defaultDataContext();
57
83
  }
84
+ /**
85
+ * Return the table container for `dataContext`, or the singleton default.
86
+ *
87
+ * @param dataContext - Context to read tables from. Falls back to the singleton.
88
+ */
58
89
  function getTableContainer(dataContext) {
59
90
  dataContext ??= userContextInstance?.defaultDataContext();
60
91
  if (!dataContext) {
@@ -62,6 +93,13 @@ function getTableContainer(dataContext) {
62
93
  }
63
94
  return dataContext.tableContainer;
64
95
  }
96
+ /**
97
+ * Install a client-proxy user context for renderer/RPC processes.
98
+ *
99
+ * No-ops when a real server-side context is already set in-process.
100
+ *
101
+ * @param userId - Local user id used as the personal data-context id.
102
+ */
65
103
  function setDefaultClientUserContext(userId) {
66
104
  // If a real (server-side) user context is already set in the same process
67
105
  // (e.g. PWA where peers-device runs in the browser), don't overwrite it
@@ -83,6 +121,10 @@ function setDefaultClientUserContext(userId) {
83
121
  })();
84
122
  setUserContext(userContext);
85
123
  }
124
+ /**
125
+ * Return every table currently loaded in the default table container, keyed by
126
+ * full table name.
127
+ */
86
128
  function getAllTables() {
87
129
  const tables = getTableContainer().getAllTables();
88
130
  const tableMap = {};
@@ -117,6 +159,9 @@ if (!rpc_types_1.isClient) {
117
159
  return table[methodName](...args);
118
160
  };
119
161
  }
162
+ /**
163
+ * Return the singleton user's id. Throws if {@link setUserContext} has not run.
164
+ */
120
165
  const myUserId = () => {
121
166
  if (!userContextInstance) {
122
167
  throw new Error(`myUserId called before userContext singleton was initialized`);
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ const groupPermissions = require("../../data/group-permissions");
3
4
  const decorators_1 = require("../../data/orm/decorators");
4
5
  const observable_1 = require("../../observable");
5
6
  const field_type_1 = require("../../types/field-type");
@@ -76,7 +77,7 @@ function makeResolution() {
76
77
  tables: { Widgets: table },
77
78
  tools: {
78
79
  "make-widget": {
79
- tool: { toolId: (0, utils_1.newid)(), name: "make-widget" },
80
+ tool: { toolId: (0, utils_1.newid)(), name: "make-widget", accessLevel: 0 },
80
81
  toolFn: makeWidget,
81
82
  },
82
83
  },
@@ -147,6 +148,55 @@ describe("contract proxy — consumer/provider round-trip", () => {
147
148
  expect(makeWidget).toHaveBeenCalledWith({ color: "blue" });
148
149
  expect(result.color).toBe("blue");
149
150
  });
151
+ it("allows a classified tool when the provider data context grants access", async () => {
152
+ const getUserAccessLevel = jest
153
+ .spyOn(groupPermissions, "getUserAccessLevel")
154
+ .mockResolvedValue(100);
155
+ const contract = makeContract();
156
+ const { resolution, makeWidget } = makeResolution();
157
+ const makeWidgetTool = resolution.tools?.["make-widget"];
158
+ if (!makeWidgetTool) {
159
+ throw new Error("expected make-widget tool");
160
+ }
161
+ makeWidgetTool.tool.accessLevel = 80;
162
+ const provider = (0, contract_proxy_1.createContractProvider)(resolution);
163
+ const dataContext = { userContext: { userId: "u1" } };
164
+ const consumer = consumerVia(contract, provider, {
165
+ callerUserId: "u1",
166
+ localUserId: "u1",
167
+ dataContext,
168
+ });
169
+ await expect(consumer.tools["make-widget"]({ color: "blue" })).resolves.toEqual(expect.objectContaining({ color: "blue" }));
170
+ expect(makeWidget).toHaveBeenCalledWith({ color: "blue" });
171
+ expect(getUserAccessLevel).toHaveBeenCalledWith(dataContext, "u1");
172
+ });
173
+ it("fails closed quickly when a classified tool has no user context", async () => {
174
+ const contract = makeContract();
175
+ const { resolution } = makeResolution();
176
+ const makeWidgetTool = resolution.tools?.["make-widget"];
177
+ if (!makeWidgetTool) {
178
+ throw new Error("expected make-widget tool");
179
+ }
180
+ makeWidgetTool.tool.accessLevel = 20;
181
+ const provider = (0, contract_proxy_1.createContractProvider)(resolution);
182
+ const consumer = consumerVia(contract, provider, {
183
+ callerUserId: (0, utils_1.newid)(),
184
+ localUserId: (0, utils_1.newid)(),
185
+ });
186
+ await expect(consumer.tools["make-widget"]({ color: "blue" })).rejects.toThrow("User context is not available");
187
+ });
188
+ it("denies unclassified tools when the caller identity is missing", async () => {
189
+ const contract = makeContract();
190
+ const { resolution } = makeResolution();
191
+ const makeWidgetTool = resolution.tools?.["make-widget"];
192
+ if (!makeWidgetTool) {
193
+ throw new Error("expected make-widget tool");
194
+ }
195
+ makeWidgetTool.tool.accessLevel = undefined;
196
+ const provider = (0, contract_proxy_1.createContractProvider)(resolution);
197
+ const consumer = consumerVia(contract, provider, {});
198
+ await expect(consumer.tools["make-widget"]({ color: "blue" })).rejects.toThrow("missing caller identity");
199
+ });
150
200
  it("forwards dataContextId on methods, tools, observables, and subscriptions", async () => {
151
201
  const dataContextId = (0, utils_1.newid)();
152
202
  const calls = [];
@@ -5,8 +5,16 @@ const user_trust_levels_1 = require("../../data/user-trust-levels");
5
5
  const users_1 = require("../../data/users");
6
6
  const socket_type_1 = require("../../device/socket.type");
7
7
  const keys_1 = require("../../keys");
8
+ const system_ids_1 = require("../../system-ids");
8
9
  const utils_1 = require("../../utils");
9
10
  const index_1 = require("../index");
11
+ jest.mock("../../data/group-permissions", () => {
12
+ const actual = jest.requireActual("../../data/group-permissions");
13
+ return {
14
+ ...actual,
15
+ getUserRole: jest.fn(async () => actual.GroupMemberRole.Founder),
16
+ };
17
+ });
10
18
  jest.mock("../../data/user-trust-levels", () => ({
11
19
  UserTrustLevels: jest.fn(),
12
20
  }));
@@ -145,6 +153,26 @@ describe("Self-trusted contract caller authorization", () => {
145
153
  })).rejects.toThrow("Permission denied");
146
154
  router.dispose();
147
155
  });
156
+ it("rejects remote Tool Access Control as local-only", async () => {
157
+ const { remoteIdentity, user } = makeIdentity();
158
+ const records = new Map();
159
+ const { personalContext, userContext } = setupUserContext(remoteIdentity.userId, records);
160
+ records.set(personalContext, user);
161
+ mockTrustGet.mockResolvedValue({ trustLevel: socket_type_1.TrustLevel.Self });
162
+ const [consumerTransport, providerTransport] = (0, index_1.inProcessTransportPair)();
163
+ const router = (0, index_1.createUserContextContractProviderRouter)(providerTransport, {
164
+ userContext,
165
+ remoteIdentity,
166
+ });
167
+ await expect(consumerTransport.emit(index_1.CONTRACT_REQUEST_CHANNEL, {
168
+ ...call,
169
+ contractId: system_ids_1.toolAccessControlContractId,
170
+ member: "tool",
171
+ name: "approve",
172
+ operation: "run",
173
+ })).rejects.toThrow("local-only");
174
+ router.dispose();
175
+ });
148
176
  });
149
177
  describe("local user-context contract routing", () => {
150
178
  function setupLocalRouter() {
@@ -180,6 +208,7 @@ describe("local user-context contract routing", () => {
180
208
  outputSchema: { type: "simple", fields: [] },
181
209
  code: "",
182
210
  packageId: (0, utils_1.newid)(),
211
+ accessLevel: 0,
183
212
  },
184
213
  toolFn: () => dataContext.dataContextId,
185
214
  },
@@ -254,6 +283,7 @@ describe("local user-context contract routing", () => {
254
283
  inputSchema: { type: "simple", fields: [] },
255
284
  outputSchema: { type: "simple", fields: [] },
256
285
  code: "",
286
+ accessLevel: 0,
257
287
  },
258
288
  toolFn: async (variableName, context) => context.getVariable(variableName, "00localcontractsecrettool"),
259
289
  },
@@ -154,7 +154,10 @@ describe("playground", () => {
154
154
  contractId: CONTRACT_ID,
155
155
  version: 1,
156
156
  tools: {
157
- logText: { tool: { toolId: "", name: "logText" }, toolFn: logTextFn },
157
+ logText: {
158
+ tool: { toolId: "", name: "logText", accessLevel: 0 },
159
+ toolFn: logTextFn,
160
+ },
158
161
  },
159
162
  observables: { logCount: logCountObs },
160
163
  observableWritability: { logCount: false },
@@ -208,6 +211,6 @@ describe("playground", () => {
208
211
  expect(logs).toEqual(["log1", "log2"]);
209
212
  expect(observedLogCount).toBe(2);
210
213
  // TODO test tables so we can see what this looks like and how it works for both providers and consumers
211
- // TODO consider renaming ContractResolution to ContractProviderInstance or something like that. ContractResolution is awkward.
214
+ // TODO consider renaming ContractResolution to ContractProviderInstance or something like that. ContractResolution is awkward.
212
215
  });
213
216
  });
@@ -17,6 +17,7 @@ describe("system contract catalog", () => {
17
17
  system_ids_1.deviceOperationsContractId,
18
18
  system_ids_1.networkDiagnosticsContractId,
19
19
  system_ids_1.networkControlContractId,
20
+ system_ids_1.toolAccessControlContractId,
20
21
  ]));
21
22
  });
22
23
  it("registers the Logs live resolver", async () => {
@@ -34,6 +35,8 @@ describe("system contract catalog", () => {
34
35
  expect(registry.getProviderPackageId(system_ids_1.networkDiagnosticsContractId, 1)).toBe(system_ids_1.peersSystemPackageId);
35
36
  expect(registry.resolve(system_ids_1.networkControlContractId, 1)?.name).toBe("System Network Control");
36
37
  expect(registry.getProviderPackageId(system_ids_1.networkControlContractId, 1)).toBe(system_ids_1.peersSystemPackageId);
38
+ expect(registry.resolve(system_ids_1.toolAccessControlContractId, 1)?.name).toBe("System Tool Access Control");
39
+ expect(registry.getProviderPackageId(system_ids_1.toolAccessControlContractId, 1)).toBe(system_ids_1.peersSystemPackageId);
37
40
  });
38
41
  it("registerSystemContract makes new contracts installable without loader changes", () => {
39
42
  const fakeContractId = (0, utils_1.newid)();
@@ -1,3 +1,4 @@
1
+ import type { DataContext } from "../context/data-context";
1
2
  import type { Table } from "../data/orm/table";
2
3
  import type { IToolInstance } from "../data/tools";
3
4
  import type { Event, ISubscriptionResult } from "../events";
@@ -275,6 +276,11 @@ export interface IContractCallContext {
275
276
  callerUserId?: string;
276
277
  /** User id of the local device executing the call. */
277
278
  localUserId?: string;
279
+ /**
280
+ * Provider-side data context used to resolve the caller's group/personal role.
281
+ * In-process only — never serialized across a connection.
282
+ */
283
+ dataContext?: DataContext;
278
284
  }
279
285
  /**
280
286
  * A gate that decides whether a given call is allowed. Runs before dispatch on the
@@ -5,6 +5,8 @@ exports.createContractConsumer = createContractConsumer;
5
5
  exports.createContractProvider = createContractProvider;
6
6
  exports.createContractProviderEndpoint = createContractProviderEndpoint;
7
7
  exports.createContractProviderSession = createContractProviderSession;
8
+ const access_level_1 = require("../data/access-level");
9
+ const group_permissions_1 = require("../data/group-permissions");
8
10
  const decorators_1 = require("../data/orm/decorators");
9
11
  const observable_1 = require("../observable");
10
12
  const utils_1 = require("../utils");
@@ -340,6 +342,37 @@ const sameUserContractPermissionCheck = (_call, context) => {
340
342
  return Boolean(context.localUserId) && context.callerUserId === context.localUserId;
341
343
  };
342
344
  exports.sameUserContractPermissionCheck = sameUserContractPermissionCheck;
345
+ /**
346
+ * Deny a contract tool when the caller’s group/personal access is below the tool.
347
+ *
348
+ * Tools at {@link AccessLevel.None} are unrestricted and do not need a caller
349
+ * identity. Higher levels fail closed if the transport omitted `callerUserId`
350
+ * and `localUserId`. Role lookup uses {@link IContractCallContext.dataContext}
351
+ * when the host attached one, so multi-device processes do not wait on the
352
+ * user-context singleton.
353
+ *
354
+ * Remote Self-trust is a separate, stricter gate applied by the connection router.
355
+ *
356
+ * @param tool - Live tool instance being dispatched.
357
+ * @param call - Inbound contract call (provides dataContextId).
358
+ * @param context - Caller identity from the transport.
359
+ */
360
+ async function assertContractToolAccess(tool, call, context) {
361
+ const required = (0, access_level_1.resolveAccessLevel)(tool.tool.accessLevel);
362
+ if (required <= access_level_1.AccessLevel.None) {
363
+ return;
364
+ }
365
+ const userId = context.callerUserId ?? context.localUserId;
366
+ if (!userId) {
367
+ throw new Error(`Permission denied: missing caller identity for tool '${tool.tool.name}'`);
368
+ }
369
+ const userLevel = context.dataContext
370
+ ? await (0, group_permissions_1.getUserAccessLevel)(context.dataContext, userId)
371
+ : (0, access_level_1.accessLevelFromGroupRole)(await (0, group_permissions_1.getUserRole)(call.dataContextId || context.localUserId || userId, userId));
372
+ if (userLevel < required) {
373
+ throw new Error(`Permission denied: ${tool.tool.name} requires ${(0, access_level_1.getAccessLevelLabel)(required)} (${required})`);
374
+ }
375
+ }
343
376
  /**
344
377
  * Build the **provider** side of a contract proxy: an async handler that permission-checks
345
378
  * an {@link IContractCall} and dispatches it to the live table method / tool fn / observable
@@ -394,6 +427,7 @@ function createContractProvider(resolution, opts = {}) {
394
427
  if (!tool) {
395
428
  throw new Error(`Contract ${call.contractId} does not provide tool '${call.name}'`);
396
429
  }
430
+ await assertContractToolAccess(tool, call, context);
397
431
  return resolution.toolContext
398
432
  ? tool.toolFn(...call.args, resolution.toolContext)
399
433
  : tool.toolFn(...call.args);
@@ -10,6 +10,7 @@ const user_permissions_1 = require("../data/user-permissions");
10
10
  const user_trust_levels_1 = require("../data/user-trust-levels");
11
11
  const users_1 = require("../data/users");
12
12
  const socket_type_1 = require("../device/socket.type");
13
+ const system_ids_1 = require("../system-ids");
13
14
  const contract_provider_router_1 = require("./contract-provider-router");
14
15
  const contract_proxy_1 = require("./contract-proxy");
15
16
  const types_1 = require("./types");
@@ -63,15 +64,20 @@ async function isSelfTrustedContractCaller(userContext, remoteIdentity) {
63
64
  return trustLevel === socket_type_1.TrustLevel.Self;
64
65
  }
65
66
  async function authorizeUserContextCall(call, userContext, remoteIdentity) {
67
+ if (call.contractId === system_ids_1.toolAccessControlContractId) {
68
+ throw new Error("Permission denied: tool access control is local-only");
69
+ }
70
+ const dataContextId = call.dataContextId || userContext.userId;
66
71
  const context = {
67
72
  callerUserId: remoteIdentity.userId,
68
73
  localUserId: userContext.userId,
74
+ dataContext: userContext.getDataContext(dataContextId),
69
75
  };
70
76
  if (!(await isSelfTrustedContractCaller(userContext, remoteIdentity))) {
71
77
  throw new Error(`Permission denied: ${call.member} '${call.name}'.${call.operation} on contract ${call.contractId}`);
72
78
  }
73
79
  return {
74
- dataContextId: call.dataContextId || userContext.userId,
80
+ dataContextId,
75
81
  context,
76
82
  };
77
83
  }
@@ -114,6 +120,7 @@ function createLocalUserContextContractProviderRouter(transport, opts) {
114
120
  const context = {
115
121
  callerUserId: userContext.userId,
116
122
  localUserId: userContext.userId,
123
+ dataContext,
117
124
  };
118
125
  return {
119
126
  dataContextId: dataContext.dataContextId,
@@ -160,6 +167,7 @@ function createUserContextContractProviderRouter(transport, opts) {
160
167
  const context = {
161
168
  callerUserId: remoteIdentity.userId,
162
169
  localUserId: userContext.userId,
170
+ dataContext: userContext.userDataContext,
163
171
  };
164
172
  return (0, contract_provider_router_1.createContractProviderRouter)(transport, {
165
173
  context,
@@ -13,6 +13,7 @@ export { ContractRegistry } from "./registry";
13
13
  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 "./system/device-operations-contract";
14
14
  export { getLogsContractDefinition, type ILogsConsumer, LOGS_CONTRACT_VERSION, resolveLogsContract, } from "./system/logs-contract";
15
15
  export * from "./system/network-contracts";
16
+ export { getToolAccessControlContractDefinition, type IToolAccessControlConsumer, TOOL_ACCESS_CONTROL_CONTRACT_VERSION, } from "./system/tool-access-control-contract";
16
17
  export { type ISystemContractRegistration, installSystemContracts, listSystemContracts, registerSystemContract, } from "./system-contracts";
17
18
  export type { IAlsoImplementsDeclaration, IConsumedContract, IConsumedContractHandle, IConsumedContractOptions, IContractDefinition, IContractEvent, IContractObservable, IContractTable, IContractTool, IPackageDefinitionResult, IResolvedContract, IValidationError, IValidationResult, } from "./types";
18
19
  export { contractKey } from "./types";
@@ -14,8 +14,8 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.installSystemContracts = exports.resolveLogsContract = exports.LOGS_CONTRACT_VERSION = exports.getLogsContractDefinition = exports.getDeviceOperationsContractDefinition = exports.deviceOperationsToolIds = exports.deviceOperationsProviderPackageId = exports.DEVICE_OPERATIONS_MAX_TOOLS = exports.DEVICE_OPERATIONS_MAX_ROWS = exports.DEVICE_OPERATIONS_MAX_RESULT_BYTES = exports.DEVICE_OPERATIONS_MAX_QUERY_LENGTH = exports.DEVICE_OPERATIONS_CONTRACT_VERSION = exports.ContractRegistry = exports.PersistentContractRegistry = exports.waitForHostContractTransport = exports.subscribeHostContractTransport = exports.registerHostContractTransport = exports.getHostContractTransport = exports.setSchemaToFieldsFn = exports.extractToolShape = exports.extractTableShape = exports.createLocalContractConsumer = exports.contractsSchema = exports.contractsMetaData = exports.ContractsTable = exports.Contracts = exports.inProcessTransportPair = exports.connectionContractTransport = exports.CONTRACT_REQUEST_CHANNEL = exports.CONTRACT_NOTIFY_CHANNEL = exports.registerContractResolver = exports.isSelfTrustedContractCaller = exports.handleContractCall = exports.getContractResolver = exports.createUserContextContractProviderRouter = exports.createLocalUserContextContractProviderRouter = exports.sameUserContractPermissionCheck = exports.createContractProviderSession = exports.createContractProviderEndpoint = exports.createContractProvider = exports.createContractConsumer = exports.CONTRACT_CALL_RPC = exports.contractProvidersSchema = exports.contractProvidersMetaData = exports.ContractProvidersTable = exports.ContractProviders = exports.createContractProviderRouter = exports.PackageBuilder = exports.definePackage = exports.ContractBuilder = void 0;
18
- exports.validateProviderSatisfiesContract = exports.validateImmutability = exports.validateAlsoImplements = exports.contractKey = exports.registerSystemContract = exports.listSystemContracts = void 0;
17
+ exports.getToolAccessControlContractDefinition = exports.resolveLogsContract = exports.LOGS_CONTRACT_VERSION = exports.getLogsContractDefinition = exports.getDeviceOperationsContractDefinition = exports.deviceOperationsToolIds = exports.deviceOperationsProviderPackageId = exports.DEVICE_OPERATIONS_MAX_TOOLS = exports.DEVICE_OPERATIONS_MAX_ROWS = exports.DEVICE_OPERATIONS_MAX_RESULT_BYTES = exports.DEVICE_OPERATIONS_MAX_QUERY_LENGTH = exports.DEVICE_OPERATIONS_CONTRACT_VERSION = exports.ContractRegistry = exports.PersistentContractRegistry = exports.waitForHostContractTransport = exports.subscribeHostContractTransport = exports.registerHostContractTransport = exports.getHostContractTransport = exports.setSchemaToFieldsFn = exports.extractToolShape = exports.extractTableShape = exports.createLocalContractConsumer = exports.contractsSchema = exports.contractsMetaData = exports.ContractsTable = exports.Contracts = exports.inProcessTransportPair = exports.connectionContractTransport = exports.CONTRACT_REQUEST_CHANNEL = exports.CONTRACT_NOTIFY_CHANNEL = exports.registerContractResolver = exports.isSelfTrustedContractCaller = exports.handleContractCall = exports.getContractResolver = exports.createUserContextContractProviderRouter = exports.createLocalUserContextContractProviderRouter = exports.sameUserContractPermissionCheck = exports.createContractProviderSession = exports.createContractProviderEndpoint = exports.createContractProvider = exports.createContractConsumer = exports.CONTRACT_CALL_RPC = exports.contractProvidersSchema = exports.contractProvidersMetaData = exports.ContractProvidersTable = exports.ContractProviders = exports.createContractProviderRouter = exports.PackageBuilder = exports.definePackage = exports.ContractBuilder = void 0;
18
+ exports.validateProviderSatisfiesContract = exports.validateImmutability = exports.validateAlsoImplements = exports.contractKey = exports.registerSystemContract = exports.listSystemContracts = exports.installSystemContracts = exports.TOOL_ACCESS_CONTROL_CONTRACT_VERSION = void 0;
19
19
  var builder_1 = require("./builder");
20
20
  Object.defineProperty(exports, "ContractBuilder", { enumerable: true, get: function () { return builder_1.ContractBuilder; } });
21
21
  Object.defineProperty(exports, "definePackage", { enumerable: true, get: function () { return builder_1.definePackage; } });
@@ -80,6 +80,9 @@ Object.defineProperty(exports, "getLogsContractDefinition", { enumerable: true,
80
80
  Object.defineProperty(exports, "LOGS_CONTRACT_VERSION", { enumerable: true, get: function () { return logs_contract_1.LOGS_CONTRACT_VERSION; } });
81
81
  Object.defineProperty(exports, "resolveLogsContract", { enumerable: true, get: function () { return logs_contract_1.resolveLogsContract; } });
82
82
  __exportStar(require("./system/network-contracts"), exports);
83
+ var tool_access_control_contract_1 = require("./system/tool-access-control-contract");
84
+ Object.defineProperty(exports, "getToolAccessControlContractDefinition", { enumerable: true, get: function () { return tool_access_control_contract_1.getToolAccessControlContractDefinition; } });
85
+ Object.defineProperty(exports, "TOOL_ACCESS_CONTROL_CONTRACT_VERSION", { enumerable: true, get: function () { return tool_access_control_contract_1.TOOL_ACCESS_CONTROL_CONTRACT_VERSION; } });
83
86
  var system_contracts_1 = require("./system-contracts");
84
87
  Object.defineProperty(exports, "installSystemContracts", { enumerable: true, get: function () { return system_contracts_1.installSystemContracts; } });
85
88
  Object.defineProperty(exports, "listSystemContracts", { enumerable: true, get: function () { return system_contracts_1.listSystemContracts; } });
@@ -5,6 +5,8 @@
5
5
  import "./device-operations-contract";
6
6
  import "./logs-contract";
7
7
  import "./network-contracts";
8
+ import "./tool-access-control-contract";
8
9
  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";
9
10
  export { getLogsContractDefinition, type ILogsConsumer, LOGS_CONTRACT_VERSION, resolveLogsContract, } from "./logs-contract";
10
11
  export * from "./network-contracts";
12
+ export { getToolAccessControlContractDefinition, type IToolAccessControlConsumer, TOOL_ACCESS_CONTROL_CONTRACT_VERSION, } from "./tool-access-control-contract";
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.resolveLogsContract = exports.LOGS_CONTRACT_VERSION = exports.getLogsContractDefinition = exports.getDeviceOperationsContractDefinition = exports.deviceOperationsToolIds = exports.deviceOperationsProviderPackageId = exports.DEVICE_OPERATIONS_MAX_TOOLS = exports.DEVICE_OPERATIONS_MAX_ROWS = exports.DEVICE_OPERATIONS_MAX_RESULT_BYTES = exports.DEVICE_OPERATIONS_MAX_QUERY_LENGTH = exports.DEVICE_OPERATIONS_CONTRACT_VERSION = void 0;
17
+ exports.TOOL_ACCESS_CONTROL_CONTRACT_VERSION = exports.getToolAccessControlContractDefinition = exports.resolveLogsContract = exports.LOGS_CONTRACT_VERSION = exports.getLogsContractDefinition = exports.getDeviceOperationsContractDefinition = exports.deviceOperationsToolIds = exports.deviceOperationsProviderPackageId = exports.DEVICE_OPERATIONS_MAX_TOOLS = exports.DEVICE_OPERATIONS_MAX_ROWS = exports.DEVICE_OPERATIONS_MAX_RESULT_BYTES = exports.DEVICE_OPERATIONS_MAX_QUERY_LENGTH = exports.DEVICE_OPERATIONS_CONTRACT_VERSION = void 0;
18
18
  /**
19
19
  * Built-in system contracts. Importing this barrel runs each module's
20
20
  * {@link registerSystemContract} side effect so the catalog is populated.
@@ -22,6 +22,7 @@ exports.resolveLogsContract = exports.LOGS_CONTRACT_VERSION = exports.getLogsCon
22
22
  require("./device-operations-contract");
23
23
  require("./logs-contract");
24
24
  require("./network-contracts");
25
+ require("./tool-access-control-contract");
25
26
  var device_operations_contract_1 = require("./device-operations-contract");
26
27
  Object.defineProperty(exports, "DEVICE_OPERATIONS_CONTRACT_VERSION", { enumerable: true, get: function () { return device_operations_contract_1.DEVICE_OPERATIONS_CONTRACT_VERSION; } });
27
28
  Object.defineProperty(exports, "DEVICE_OPERATIONS_MAX_QUERY_LENGTH", { enumerable: true, get: function () { return device_operations_contract_1.DEVICE_OPERATIONS_MAX_QUERY_LENGTH; } });
@@ -36,3 +37,6 @@ Object.defineProperty(exports, "getLogsContractDefinition", { enumerable: true,
36
37
  Object.defineProperty(exports, "LOGS_CONTRACT_VERSION", { enumerable: true, get: function () { return logs_contract_1.LOGS_CONTRACT_VERSION; } });
37
38
  Object.defineProperty(exports, "resolveLogsContract", { enumerable: true, get: function () { return logs_contract_1.resolveLogsContract; } });
38
39
  __exportStar(require("./network-contracts"), exports);
40
+ var tool_access_control_contract_1 = require("./tool-access-control-contract");
41
+ Object.defineProperty(exports, "getToolAccessControlContractDefinition", { enumerable: true, get: function () { return tool_access_control_contract_1.getToolAccessControlContractDefinition; } });
42
+ Object.defineProperty(exports, "TOOL_ACCESS_CONTROL_CONTRACT_VERSION", { enumerable: true, get: function () { return tool_access_control_contract_1.TOOL_ACCESS_CONTROL_CONTRACT_VERSION; } });
@@ -0,0 +1,35 @@
1
+ import type { IContractConsumerBase } from "../contract-proxy";
2
+ import type { IContractDefinition } from "../types";
3
+ /** Version of the built-in System Tool Access Control contract. */
4
+ export declare const TOOL_ACCESS_CONTROL_CONTRACT_VERSION = 1;
5
+ /**
6
+ * Local-only host contract for approving or rejecting step-up tool access.
7
+ *
8
+ * Assistants never receive these tools. Remote device connections must not
9
+ * resolve this contract.
10
+ */
11
+ export declare function getToolAccessControlContractDefinition(): IContractDefinition;
12
+ /** Consumer-side TypeScript shape for System Tool Access Control. */
13
+ export interface IToolAccessControlConsumer extends IContractConsumerBase {
14
+ tools: {
15
+ approve(input: {
16
+ approvalId: string;
17
+ nonce: string;
18
+ scope?: "once" | "thread" | "context";
19
+ }): Promise<{
20
+ result: unknown;
21
+ }>;
22
+ reject(input: {
23
+ approvalId: string;
24
+ nonce: string;
25
+ }): Promise<{
26
+ result: unknown;
27
+ }>;
28
+ listPending(input?: {
29
+ threadMessageId?: string;
30
+ workflowRunId?: string;
31
+ }): Promise<{
32
+ approvals: unknown[];
33
+ }>;
34
+ };
35
+ }
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TOOL_ACCESS_CONTROL_CONTRACT_VERSION = void 0;
4
+ exports.getToolAccessControlContractDefinition = getToolAccessControlContractDefinition;
5
+ const system_ids_1 = require("../../system-ids");
6
+ const field_type_1 = require("../../types/field-type");
7
+ const system_contracts_1 = require("../system-contracts");
8
+ /** Version of the built-in System Tool Access Control contract. */
9
+ exports.TOOL_ACCESS_CONTROL_CONTRACT_VERSION = 1;
10
+ const field = (name, type, description, opts = {}) => ({ name, type, description, ...opts });
11
+ let definition;
12
+ /**
13
+ * Local-only host contract for approving or rejecting step-up tool access.
14
+ *
15
+ * Assistants never receive these tools. Remote device connections must not
16
+ * resolve this contract.
17
+ */
18
+ function getToolAccessControlContractDefinition() {
19
+ definition ??= {
20
+ contractId: system_ids_1.toolAccessControlContractId,
21
+ version: exports.TOOL_ACCESS_CONTROL_CONTRACT_VERSION,
22
+ devTag: "dev",
23
+ name: "System Tool Access Control",
24
+ description: "Approve or reject privileged tool calls requested by Assistants.",
25
+ tables: [],
26
+ tools: [
27
+ {
28
+ name: "approve",
29
+ usageDescription: "Approve a pending tool-access request and execute the frozen call.",
30
+ inputFields: [
31
+ field("approvalId", field_type_1.FieldType.id, "Pending approval id."),
32
+ field("nonce", field_type_1.FieldType.string, "Single-use nonce from the pending row."),
33
+ field("scope", field_type_1.FieldType.string, "once, thread, or context.", { optional: true }),
34
+ ],
35
+ outputFields: [field("result", field_type_1.FieldType.object, "Approval and execution result.")],
36
+ },
37
+ {
38
+ name: "reject",
39
+ usageDescription: "Reject a pending tool-access request.",
40
+ inputFields: [
41
+ field("approvalId", field_type_1.FieldType.id, "Pending approval id."),
42
+ field("nonce", field_type_1.FieldType.string, "Single-use nonce from the pending row."),
43
+ ],
44
+ outputFields: [field("result", field_type_1.FieldType.object, "Rejection result.")],
45
+ },
46
+ {
47
+ name: "listPending",
48
+ usageDescription: "List pending tool-access approvals for a thread or workflow.",
49
+ inputFields: [
50
+ field("threadMessageId", field_type_1.FieldType.id, "Thread root or parent user message.", {
51
+ optional: true,
52
+ }),
53
+ field("workflowRunId", field_type_1.FieldType.id, "Workflow run id.", { optional: true }),
54
+ ],
55
+ outputFields: [
56
+ field("approvals", field_type_1.FieldType.object, "Pending approval rows.", { isArray: true }),
57
+ ],
58
+ },
59
+ ],
60
+ observables: [],
61
+ events: [],
62
+ };
63
+ return definition;
64
+ }
65
+ (0, system_contracts_1.registerSystemContract)({
66
+ providerPackageId: system_ids_1.peersSystemPackageId,
67
+ contractId: system_ids_1.toolAccessControlContractId,
68
+ version: exports.TOOL_ACCESS_CONTROL_CONTRACT_VERSION,
69
+ getDefinition: getToolAccessControlContractDefinition,
70
+ });