@peers-app/peers-sdk 0.22.0 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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 +13 -0
  44. package/dist/system-ids.js +14 -1
  45. package/package.json +1 -1
@@ -0,0 +1,72 @@
1
+ import { z } from "zod";
2
+ import { GroupMemberRole } from "./group-member-roles";
3
+ /**
4
+ * Named access-level constants on the same 0–100 scale as {@link GroupMemberRole}.
5
+ *
6
+ * Tools and Assistants store an integer in this range. Most callers use these
7
+ * named values; custom policies may use any integer from 0 through 100.
8
+ *
9
+ * `Self` (100) is operational authority in a personal context (or a group
10
+ * Founder). It is not connection {@link TrustLevel}.
11
+ */
12
+ export declare const AccessLevel: {
13
+ readonly None: 0;
14
+ readonly Reader: 20;
15
+ readonly Writer: 40;
16
+ readonly Admin: 60;
17
+ readonly Owner: 80;
18
+ readonly Self: 100;
19
+ };
20
+ /** One of the named {@link AccessLevel} constants. */
21
+ export type NamedAccessLevel = (typeof AccessLevel)[keyof typeof AccessLevel];
22
+ /**
23
+ * Fail-closed default for unclassified tools and Assistants.
24
+ *
25
+ * Missing metadata is treated as {@link AccessLevel.Self} so a new package
26
+ * tool cannot run until someone explicitly lowers its requirement.
27
+ */
28
+ export declare const DEFAULT_ACCESS_LEVEL: 100;
29
+ /** Inclusive minimum stored access level. */
30
+ export declare const MIN_ACCESS_LEVEL = 0;
31
+ /** Inclusive maximum stored access level. */
32
+ export declare const MAX_ACCESS_LEVEL = 100;
33
+ /**
34
+ * Zod schema for a stored access level: an integer from 0 through 100.
35
+ * Unspecified values default to {@link DEFAULT_ACCESS_LEVEL}.
36
+ */
37
+ export declare const accessLevelSchema: z.ZodDefault<z.ZodNumber>;
38
+ /**
39
+ * Return a human-readable label for an access-level integer.
40
+ *
41
+ * Exact named values return `Self`, `Owner`, `Admin`, `Writer`, `Reader`, or
42
+ * `None`. Values between named tiers append `+` to the next-lower name
43
+ * (for example `61` → `Admin+`). Out-of-range numbers return `None`.
44
+ *
45
+ * @param value - Access level to label.
46
+ */
47
+ export declare function getAccessLevelLabel(value: number): string;
48
+ /**
49
+ * Normalize a stored or missing access level to a safe integer.
50
+ *
51
+ * `undefined`, `null`, `NaN`, and non-integers become {@link DEFAULT_ACCESS_LEVEL}.
52
+ * Values outside 0–100 are clamped.
53
+ *
54
+ * @param value - Raw access level from a record or config.
55
+ */
56
+ export declare function resolveAccessLevel(value: unknown): number;
57
+ /**
58
+ * Map a {@link GroupMemberRole} onto the access-level scale.
59
+ *
60
+ * Numeric values already match except the top label: group `Founder` (100)
61
+ * is access {@link AccessLevel.Self}.
62
+ *
63
+ * @param role - Group membership role.
64
+ */
65
+ export declare function accessLevelFromGroupRole(role: GroupMemberRole): number;
66
+ /**
67
+ * Return the named access-level presets used by configuration UIs.
68
+ */
69
+ export declare function listAccessLevelPresets(): ReadonlyArray<{
70
+ value: number;
71
+ label: string;
72
+ }>;
@@ -0,0 +1,109 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.accessLevelSchema = exports.MAX_ACCESS_LEVEL = exports.MIN_ACCESS_LEVEL = exports.DEFAULT_ACCESS_LEVEL = exports.AccessLevel = void 0;
4
+ exports.getAccessLevelLabel = getAccessLevelLabel;
5
+ exports.resolveAccessLevel = resolveAccessLevel;
6
+ exports.accessLevelFromGroupRole = accessLevelFromGroupRole;
7
+ exports.listAccessLevelPresets = listAccessLevelPresets;
8
+ const zod_1 = require("zod");
9
+ const group_member_roles_1 = require("./group-member-roles");
10
+ /**
11
+ * Named access-level constants on the same 0–100 scale as {@link GroupMemberRole}.
12
+ *
13
+ * Tools and Assistants store an integer in this range. Most callers use these
14
+ * named values; custom policies may use any integer from 0 through 100.
15
+ *
16
+ * `Self` (100) is operational authority in a personal context (or a group
17
+ * Founder). It is not connection {@link TrustLevel}.
18
+ */
19
+ exports.AccessLevel = {
20
+ None: 0,
21
+ Reader: 20,
22
+ Writer: 40,
23
+ Admin: 60,
24
+ Owner: 80,
25
+ Self: 100,
26
+ };
27
+ /**
28
+ * Fail-closed default for unclassified tools and Assistants.
29
+ *
30
+ * Missing metadata is treated as {@link AccessLevel.Self} so a new package
31
+ * tool cannot run until someone explicitly lowers its requirement.
32
+ */
33
+ exports.DEFAULT_ACCESS_LEVEL = exports.AccessLevel.Self;
34
+ /** Inclusive minimum stored access level. */
35
+ exports.MIN_ACCESS_LEVEL = 0;
36
+ /** Inclusive maximum stored access level. */
37
+ exports.MAX_ACCESS_LEVEL = 100;
38
+ /**
39
+ * Zod schema for a stored access level: an integer from 0 through 100.
40
+ * Unspecified values default to {@link DEFAULT_ACCESS_LEVEL}.
41
+ */
42
+ exports.accessLevelSchema = zod_1.z
43
+ .number()
44
+ .int()
45
+ .min(exports.MIN_ACCESS_LEVEL)
46
+ .max(exports.MAX_ACCESS_LEVEL)
47
+ .default(exports.DEFAULT_ACCESS_LEVEL);
48
+ const ACCESS_LEVEL_LABELS = [
49
+ { level: exports.AccessLevel.Self, label: "Self" },
50
+ { level: exports.AccessLevel.Owner, label: "Owner" },
51
+ { level: exports.AccessLevel.Admin, label: "Admin" },
52
+ { level: exports.AccessLevel.Writer, label: "Writer" },
53
+ { level: exports.AccessLevel.Reader, label: "Reader" },
54
+ { level: exports.AccessLevel.None, label: "None" },
55
+ ];
56
+ /**
57
+ * Return a human-readable label for an access-level integer.
58
+ *
59
+ * Exact named values return `Self`, `Owner`, `Admin`, `Writer`, `Reader`, or
60
+ * `None`. Values between named tiers append `+` to the next-lower name
61
+ * (for example `61` → `Admin+`). Out-of-range numbers return `None`.
62
+ *
63
+ * @param value - Access level to label.
64
+ */
65
+ function getAccessLevelLabel(value) {
66
+ for (const entry of ACCESS_LEVEL_LABELS) {
67
+ if (value >= entry.level) {
68
+ return value === entry.level ? entry.label : `${entry.label}+`;
69
+ }
70
+ }
71
+ return "None";
72
+ }
73
+ /**
74
+ * Normalize a stored or missing access level to a safe integer.
75
+ *
76
+ * `undefined`, `null`, `NaN`, and non-integers become {@link DEFAULT_ACCESS_LEVEL}.
77
+ * Values outside 0–100 are clamped.
78
+ *
79
+ * @param value - Raw access level from a record or config.
80
+ */
81
+ function resolveAccessLevel(value) {
82
+ if (typeof value !== "number" || !Number.isFinite(value) || !Number.isInteger(value)) {
83
+ return exports.DEFAULT_ACCESS_LEVEL;
84
+ }
85
+ if (value < exports.MIN_ACCESS_LEVEL)
86
+ return exports.MIN_ACCESS_LEVEL;
87
+ if (value > exports.MAX_ACCESS_LEVEL)
88
+ return exports.MAX_ACCESS_LEVEL;
89
+ return value;
90
+ }
91
+ /**
92
+ * Map a {@link GroupMemberRole} onto the access-level scale.
93
+ *
94
+ * Numeric values already match except the top label: group `Founder` (100)
95
+ * is access {@link AccessLevel.Self}.
96
+ *
97
+ * @param role - Group membership role.
98
+ */
99
+ function accessLevelFromGroupRole(role) {
100
+ return role === group_member_roles_1.GroupMemberRole.Founder ? exports.AccessLevel.Self : role;
101
+ }
102
+ /**
103
+ * Return the named access-level presets used by configuration UIs.
104
+ */
105
+ function listAccessLevelPresets() {
106
+ return ACCESS_LEVEL_LABELS.slice()
107
+ .reverse()
108
+ .map(({ level, label }) => ({ value: level, label }));
109
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const group_member_roles_1 = require("./group-member-roles");
4
+ const access_level_1 = require("./access-level");
5
+ describe("accessLevelSchema", () => {
6
+ it("defaults missing values to Self (100)", () => {
7
+ expect(access_level_1.accessLevelSchema.parse(undefined)).toBe(access_level_1.AccessLevel.Self);
8
+ expect(access_level_1.DEFAULT_ACCESS_LEVEL).toBe(100);
9
+ });
10
+ it("accepts custom integers between named tiers", () => {
11
+ expect(access_level_1.accessLevelSchema.parse(45)).toBe(45);
12
+ });
13
+ it("rejects values outside 0–100", () => {
14
+ expect(access_level_1.accessLevelSchema.safeParse(-1).success).toBe(false);
15
+ expect(access_level_1.accessLevelSchema.safeParse(101).success).toBe(false);
16
+ expect(access_level_1.accessLevelSchema.safeParse(40.5).success).toBe(false);
17
+ });
18
+ });
19
+ describe("getAccessLevelLabel", () => {
20
+ it("labels named tiers", () => {
21
+ expect((0, access_level_1.getAccessLevelLabel)(0)).toBe("None");
22
+ expect((0, access_level_1.getAccessLevelLabel)(20)).toBe("Reader");
23
+ expect((0, access_level_1.getAccessLevelLabel)(40)).toBe("Writer");
24
+ expect((0, access_level_1.getAccessLevelLabel)(60)).toBe("Admin");
25
+ expect((0, access_level_1.getAccessLevelLabel)(80)).toBe("Owner");
26
+ expect((0, access_level_1.getAccessLevelLabel)(100)).toBe("Self");
27
+ });
28
+ it("labels in-between values with +", () => {
29
+ expect((0, access_level_1.getAccessLevelLabel)(61)).toBe("Admin+");
30
+ });
31
+ });
32
+ describe("resolveAccessLevel", () => {
33
+ it("fail-closes invalid values to 100", () => {
34
+ expect((0, access_level_1.resolveAccessLevel)(undefined)).toBe(100);
35
+ expect((0, access_level_1.resolveAccessLevel)("40")).toBe(100);
36
+ expect((0, access_level_1.resolveAccessLevel)(Number.NaN)).toBe(100);
37
+ });
38
+ it("clamps out-of-range integers", () => {
39
+ expect((0, access_level_1.resolveAccessLevel)(-5)).toBe(0);
40
+ expect((0, access_level_1.resolveAccessLevel)(200)).toBe(100);
41
+ });
42
+ });
43
+ describe("accessLevelFromGroupRole", () => {
44
+ it("maps Founder to Self 100", () => {
45
+ expect((0, access_level_1.accessLevelFromGroupRole)(group_member_roles_1.GroupMemberRole.Founder)).toBe(access_level_1.AccessLevel.Self);
46
+ expect((0, access_level_1.accessLevelFromGroupRole)(group_member_roles_1.GroupMemberRole.Admin)).toBe(access_level_1.AccessLevel.Admin);
47
+ });
48
+ });
49
+ describe("listAccessLevelPresets", () => {
50
+ it("returns named presets in ascending order", () => {
51
+ expect((0, access_level_1.listAccessLevelPresets)().map((p) => p.value)).toEqual([0, 20, 40, 60, 80, 100]);
52
+ });
53
+ });
@@ -1,10 +1,12 @@
1
1
  import { z } from "zod";
2
2
  import type { DataContext } from "../context/data-context";
3
+ /** How an Assistant selects tools for each run. */
3
4
  export declare enum ToolInclusionStrategy {
4
5
  Fixed = "Fixed",
5
6
  Linked = "Linked",
6
7
  Relevant = "Relevant"
7
8
  }
9
+ /** Zod schema for a persisted {@link IAssistant} row. */
8
10
  export declare const assistantSchema: z.ZodObject<{
9
11
  assistantId: z.ZodEffects<z.ZodString, string, string>;
10
12
  name: z.ZodString;
@@ -16,8 +18,10 @@ export declare const assistantSchema: z.ZodObject<{
16
18
  toolsToInclude: z.ZodDefault<z.ZodString>;
17
19
  toolInclusionStrategy: z.ZodDefault<z.ZodNativeEnum<typeof ToolInclusionStrategy>>;
18
20
  packageId: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
21
+ accessLevel: z.ZodDefault<z.ZodNumber>;
19
22
  }, "strip", z.ZodTypeAny, {
20
23
  name: string;
24
+ accessLevel: number;
21
25
  assistantId: string;
22
26
  createdAt: Date;
23
27
  assistantRunnerToolId: string;
@@ -34,6 +38,7 @@ export declare const assistantSchema: z.ZodObject<{
34
38
  assistantId: string;
35
39
  description?: string | undefined;
36
40
  packageId?: string | undefined;
41
+ accessLevel?: number | undefined;
37
42
  createdAt?: Date | undefined;
38
43
  updatedAt?: Date | undefined;
39
44
  assistantRunnerToolId?: string | undefined;
@@ -41,9 +46,16 @@ export declare const assistantSchema: z.ZodObject<{
41
46
  toolsToInclude?: string | undefined;
42
47
  toolInclusionStrategy?: ToolInclusionStrategy | undefined;
43
48
  }>;
49
+ /** An Assistant definition stored in the Assistants table. */
44
50
  export type IAssistant = z.infer<typeof assistantSchema>;
51
+ /**
52
+ * Accessor for the Assistants table.
53
+ *
54
+ * @param dataContext - Optional data context; defaults to the current singleton.
55
+ */
45
56
  export declare function Assistants(dataContext?: DataContext): import("..").Table<{
46
57
  name: string;
58
+ accessLevel: number;
47
59
  assistantId: string;
48
60
  createdAt: Date;
49
61
  assistantRunnerToolId: string;
@@ -66,8 +78,15 @@ export declare const userPrimaryAssistantVar: import("./persistent-vars").Persis
66
78
  * their own preference. Can be set by group admins. Synced to all group members.
67
79
  */
68
80
  export declare const groupPrimaryAssistantVar: import("./persistent-vars").PersistentVar<string | undefined>;
81
+ /**
82
+ * Resolve the current user's primary Assistant, falling back to the group
83
+ * default and then the built-in Shell Assistant.
84
+ *
85
+ * @param dataContext - Optional data context for the lookup.
86
+ */
69
87
  export declare const getPrimaryAssistant: (dataContext?: DataContext) => Promise<{
70
88
  name: string;
89
+ accessLevel: number;
71
90
  assistantId: string;
72
91
  createdAt: Date;
73
92
  assistantRunnerToolId: string;
@@ -80,4 +99,9 @@ export declare const getPrimaryAssistant: (dataContext?: DataContext) => Promise
80
99
  packageId?: string | undefined;
81
100
  updatedAt?: Date | undefined;
82
101
  }>;
102
+ /**
103
+ * Return Assistant ids mentioned in markdown (`<@assistant:name:id>`).
104
+ *
105
+ * @param message - Message markdown that may contain mentions.
106
+ */
83
107
  export declare function getAllAssistantIdsMentioned(message: string): Promise<string[]>;
@@ -8,15 +8,18 @@ const user_context_singleton_1 = require("../context/user-context-singleton");
8
8
  const mentions_1 = require("../mentions");
9
9
  const system_ids_1 = require("../system-ids");
10
10
  const zod_types_1 = require("../types/zod-types");
11
+ const access_level_1 = require("./access-level");
11
12
  const table_definitions_system_1 = require("./orm/table-definitions.system");
12
13
  const types_1 = require("./orm/types");
13
14
  const persistent_vars_1 = require("./persistent-vars");
15
+ /** How an Assistant selects tools for each run. */
14
16
  var ToolInclusionStrategy;
15
17
  (function (ToolInclusionStrategy) {
16
18
  ToolInclusionStrategy["Fixed"] = "Fixed";
17
19
  ToolInclusionStrategy["Linked"] = "Linked";
18
20
  ToolInclusionStrategy["Relevant"] = "Relevant";
19
21
  })(ToolInclusionStrategy || (exports.ToolInclusionStrategy = ToolInclusionStrategy = {}));
22
+ /** Zod schema for a persisted {@link IAssistant} row. */
20
23
  exports.assistantSchema = zod_1.z.object({
21
24
  assistantId: zod_types_1.zodPeerId,
22
25
  name: zod_1.z.string(),
@@ -47,6 +50,7 @@ exports.assistantSchema = zod_1.z.object({
47
50
  "- **Relevant** - `Linked` plus tools matching the context of the conversation determined via vector search (embeddings) of the tools descriptions.",
48
51
  ].join("\n")),
49
52
  packageId: zod_types_1.zodPeerId.optional().describe("The package that installed this assistant, if any"),
53
+ accessLevel: access_level_1.accessLevelSchema.describe("Minimum user access to invoke this assistant, and the level it may run automatically. Unclassified assistants default to 100 (Self)."),
50
54
  });
51
55
  const metaData = {
52
56
  name: "Assistants",
@@ -55,8 +59,14 @@ const metaData = {
55
59
  fields: (0, types_1.schemaToFields)(exports.assistantSchema),
56
60
  iconClassName: "bi bi-person-fill-gear",
57
61
  indexes: [{ fields: ["name"] }],
62
+ versionNumber: 1,
58
63
  };
59
64
  (0, table_definitions_system_1.registerSystemTableDefinition)(metaData, exports.assistantSchema);
65
+ /**
66
+ * Accessor for the Assistants table.
67
+ *
68
+ * @param dataContext - Optional data context; defaults to the current singleton.
69
+ */
60
70
  function Assistants(dataContext) {
61
71
  return (0, user_context_singleton_1.getTableContainer)(dataContext).getTable(metaData, exports.assistantSchema);
62
72
  }
@@ -70,6 +80,12 @@ exports.userPrimaryAssistantVar = (0, persistent_vars_1.groupUserVar)("userPrima
70
80
  * their own preference. Can be set by group admins. Synced to all group members.
71
81
  */
72
82
  exports.groupPrimaryAssistantVar = (0, persistent_vars_1.groupVar)("groupPrimaryAssistantId");
83
+ /**
84
+ * Resolve the current user's primary Assistant, falling back to the group
85
+ * default and then the built-in Shell Assistant.
86
+ *
87
+ * @param dataContext - Optional data context for the lookup.
88
+ */
73
89
  const getPrimaryAssistant = async (dataContext) => {
74
90
  await exports.userPrimaryAssistantVar.loadingPromise;
75
91
  const userAssistantId = (0, exports.userPrimaryAssistantVar)();
@@ -91,6 +107,11 @@ const getPrimaryAssistant = async (dataContext) => {
91
107
  return fallback;
92
108
  };
93
109
  exports.getPrimaryAssistant = getPrimaryAssistant;
110
+ /**
111
+ * Return Assistant ids mentioned in markdown (`<@assistant:name:id>`).
112
+ *
113
+ * @param message - Message markdown that may contain mentions.
114
+ */
94
115
  async function getAllAssistantIdsMentioned(message) {
95
116
  const mentions = (0, mentions_1.getAllMentions)(message);
96
117
  return mentions.filter((m) => m.kind === "assistant").map((m) => m.id);
@@ -1,3 +1,7 @@
1
+ /**
2
+ * Numeric group membership roles. The same 0–100 scale is used for tool and
3
+ * Assistant {@link AccessLevel} values (`Founder` maps to access `Self`).
4
+ */
1
5
  export declare enum GroupMemberRole {
2
6
  None = 0,
3
7
  Reader = 20,
@@ -6,4 +10,9 @@ export declare enum GroupMemberRole {
6
10
  Owner = 80,
7
11
  Founder = 100
8
12
  }
13
+ /**
14
+ * Return a human-readable group-role label, using `+` for in-between values.
15
+ *
16
+ * @param value - Numeric role or access-like integer.
17
+ */
9
18
  export declare function getRoleLabel(value: number): string;
@@ -2,6 +2,10 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.GroupMemberRole = void 0;
4
4
  exports.getRoleLabel = getRoleLabel;
5
+ /**
6
+ * Numeric group membership roles. The same 0–100 scale is used for tool and
7
+ * Assistant {@link AccessLevel} values (`Founder` maps to access `Self`).
8
+ */
5
9
  var GroupMemberRole;
6
10
  (function (GroupMemberRole) {
7
11
  GroupMemberRole[GroupMemberRole["None"] = 0] = "None";
@@ -11,6 +15,11 @@ var GroupMemberRole;
11
15
  GroupMemberRole[GroupMemberRole["Owner"] = 80] = "Owner";
12
16
  GroupMemberRole[GroupMemberRole["Founder"] = 100] = "Founder";
13
17
  })(GroupMemberRole || (exports.GroupMemberRole = GroupMemberRole = {}));
18
+ /**
19
+ * Return a human-readable group-role label, using `+` for in-between values.
20
+ *
21
+ * @param value - Numeric role or access-like integer.
22
+ */
14
23
  function getRoleLabel(value) {
15
24
  const roleEntries = Object.entries(GroupMemberRole)
16
25
  .filter(([_key, val]) => typeof val === "number")
@@ -1,8 +1,62 @@
1
+ import type { DataContext } from "../context/data-context";
2
+ import type { UserContext } from "../context/user-context";
1
3
  import { GroupMemberRole } from "./group-member-roles";
2
4
  import type { IGroupMember } from "./group-members";
3
5
  import type { IGroup } from "./groups";
4
6
  export { GroupMemberRole };
5
- export declare function getUserRole(groupId: string, userId: string): Promise<GroupMemberRole>;
7
+ /**
8
+ * Return a user's {@link GroupMemberRole} in `groupId`.
9
+ *
10
+ * Missing groups and the group's founder resolve to {@link GroupMemberRole.Founder},
11
+ * which is how a personal context (no Groups row) becomes access 100.
12
+ *
13
+ * Prefer passing `userContext` from the provider that owns the data. The
14
+ * process singleton is only a fallback and is not awaited — multi-device
15
+ * tests never set it, and hanging here would stall contract calls.
16
+ *
17
+ * @param groupId - Group id, or the user's id for their personal context.
18
+ * @param userId - User whose role is requested.
19
+ * @param userContext - Provider-owned context. Falls back to {@link peekUserContext}.
20
+ * @throws If neither an explicit context nor the process singleton is available.
21
+ */
22
+ export declare function getUserRole(groupId: string, userId: string, userContext?: UserContext): Promise<GroupMemberRole>;
23
+ /**
24
+ * Resolve a group role from the signing public key of a Users record.
25
+ *
26
+ * Uses {@link peekUserContext}; throws if the process singleton is unset.
27
+ *
28
+ * @param groupId - Group or personal context id.
29
+ * @param publicKey - User public key that signed a record.
30
+ */
6
31
  export declare function getUserRoleFromPublicKey(groupId: string, publicKey: string): Promise<GroupMemberRole>;
32
+ /**
33
+ * Return the current user's access level in `dataContext`.
34
+ *
35
+ * Personal contexts (no `groupId`) use the user's id and resolve to 100.
36
+ *
37
+ * @param dataContext - Context whose group/personal role is evaluated.
38
+ */
39
+ export declare function getCurrentUserAccessLevel(dataContext: DataContext): Promise<number>;
40
+ /**
41
+ * Return `userId`'s access level in `dataContext`.
42
+ *
43
+ * @param dataContext - Context whose group/personal role is evaluated.
44
+ * @param userId - User to resolve.
45
+ */
46
+ export declare function getUserAccessLevel(dataContext: DataContext, userId: string): Promise<number>;
47
+ /**
48
+ * Verify that a group record was signed by an Admin+ member (Founder to change founder).
49
+ *
50
+ * @param group - Proposed group record.
51
+ * @param oldGroup - Previous record when this is an update.
52
+ */
7
53
  export declare function verifyGroupSignature(group: IGroup, oldGroup?: IGroup): Promise<void>;
54
+ /**
55
+ * Return whether a group-member write is signed by someone allowed to make it.
56
+ *
57
+ * @param groupId - Group the membership belongs to.
58
+ * @param newGroupMember - Proposed membership row.
59
+ * @param oldGroupMember - Previous row when this is an update.
60
+ * @param signerRole - Optional precomputed signer role.
61
+ */
8
62
  export declare function isGroupMemberSignatureValid(groupId: string, newGroupMember: IGroupMember, oldGroupMember?: IGroupMember, signerRole?: GroupMemberRole): Promise<boolean>;
@@ -3,19 +3,41 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.GroupMemberRole = void 0;
4
4
  exports.getUserRole = getUserRole;
5
5
  exports.getUserRoleFromPublicKey = getUserRoleFromPublicKey;
6
+ exports.getCurrentUserAccessLevel = getCurrentUserAccessLevel;
7
+ exports.getUserAccessLevel = getUserAccessLevel;
6
8
  exports.verifyGroupSignature = verifyGroupSignature;
7
9
  exports.isGroupMemberSignatureValid = isGroupMemberSignatureValid;
8
10
  const context_1 = require("../context");
9
11
  const keys_1 = require("../keys");
12
+ const access_level_1 = require("./access-level");
10
13
  const group_member_roles_1 = require("./group-member-roles");
11
14
  Object.defineProperty(exports, "GroupMemberRole", { enumerable: true, get: function () { return group_member_roles_1.GroupMemberRole; } });
12
- async function getUserRole(groupId, userId) {
15
+ /**
16
+ * Return a user's {@link GroupMemberRole} in `groupId`.
17
+ *
18
+ * Missing groups and the group's founder resolve to {@link GroupMemberRole.Founder},
19
+ * which is how a personal context (no Groups row) becomes access 100.
20
+ *
21
+ * Prefer passing `userContext` from the provider that owns the data. The
22
+ * process singleton is only a fallback and is not awaited — multi-device
23
+ * tests never set it, and hanging here would stall contract calls.
24
+ *
25
+ * @param groupId - Group id, or the user's id for their personal context.
26
+ * @param userId - User whose role is requested.
27
+ * @param userContext - Provider-owned context. Falls back to {@link peekUserContext}.
28
+ * @throws If neither an explicit context nor the process singleton is available.
29
+ */
30
+ async function getUserRole(groupId, userId, userContext) {
13
31
  const { Groups } = await Promise.resolve().then(() => require("./groups"));
14
32
  const { GroupMembers } = await Promise.resolve().then(() => require("./group-members"));
15
- const userContext = await (0, context_1.getUserContext)();
16
- const groupDataContext = userContext.getDataContext(groupId);
33
+ const ctx = userContext ?? (0, context_1.peekUserContext)();
34
+ if (!ctx) {
35
+ throw new Error("User context is not available");
36
+ }
37
+ await ctx.loadingPromise;
38
+ const groupDataContext = ctx.getDataContext(groupId);
17
39
  const group = (await Groups(groupDataContext).get(groupId)) ||
18
- (await Groups(userContext.userDataContext).get(groupId));
40
+ (await Groups(ctx.userDataContext).get(groupId));
19
41
  // If the group doesn't exist or the current user is explicitly the founder, return Founder role
20
42
  if (!group || group.founderUserId === userId) {
21
43
  return group_member_roles_1.GroupMemberRole.Founder;
@@ -26,17 +48,57 @@ async function getUserRole(groupId, userId) {
26
48
  }
27
49
  return group.publicRole || group_member_roles_1.GroupMemberRole.None;
28
50
  }
51
+ /**
52
+ * Resolve a group role from the signing public key of a Users record.
53
+ *
54
+ * Uses {@link peekUserContext}; throws if the process singleton is unset.
55
+ *
56
+ * @param groupId - Group or personal context id.
57
+ * @param publicKey - User public key that signed a record.
58
+ */
29
59
  async function getUserRoleFromPublicKey(groupId, publicKey) {
30
60
  const { Users } = await Promise.resolve().then(() => require("./users"));
31
- const userContext = await (0, context_1.getUserContext)();
61
+ const userContext = (0, context_1.peekUserContext)();
62
+ if (!userContext) {
63
+ throw new Error("User context is not available");
64
+ }
65
+ await userContext.loadingPromise;
32
66
  const groupDataContext = userContext.getDataContext(groupId);
33
67
  const user = (await Users(groupDataContext).findOne({ publicKey })) ||
34
68
  (await Users(userContext.userDataContext).findOne({ publicKey }));
35
69
  if (user) {
36
- return getUserRole(groupId, user?.userId);
70
+ return getUserRole(groupId, user?.userId, userContext);
37
71
  }
38
72
  return group_member_roles_1.GroupMemberRole.None;
39
73
  }
74
+ /**
75
+ * Return the current user's access level in `dataContext`.
76
+ *
77
+ * Personal contexts (no `groupId`) use the user's id and resolve to 100.
78
+ *
79
+ * @param dataContext - Context whose group/personal role is evaluated.
80
+ */
81
+ async function getCurrentUserAccessLevel(dataContext) {
82
+ const userId = dataContext.userContext.userId;
83
+ return getUserAccessLevel(dataContext, userId);
84
+ }
85
+ /**
86
+ * Return `userId`'s access level in `dataContext`.
87
+ *
88
+ * @param dataContext - Context whose group/personal role is evaluated.
89
+ * @param userId - User to resolve.
90
+ */
91
+ async function getUserAccessLevel(dataContext, userId) {
92
+ const contextKey = dataContext.groupId ?? dataContext.userContext.userId;
93
+ const role = await getUserRole(contextKey, userId, dataContext.userContext);
94
+ return (0, access_level_1.accessLevelFromGroupRole)(role);
95
+ }
96
+ /**
97
+ * Verify that a group record was signed by an Admin+ member (Founder to change founder).
98
+ *
99
+ * @param group - Proposed group record.
100
+ * @param oldGroup - Previous record when this is an update.
101
+ */
40
102
  async function verifyGroupSignature(group, oldGroup) {
41
103
  (0, keys_1.verifyObjectSignature)(group);
42
104
  const publicKey = (0, keys_1.getPublicKeyFromObjectSignature)(group) ?? "";
@@ -50,6 +112,14 @@ async function verifyGroupSignature(group, oldGroup) {
50
112
  throw new Error("Only group admins can modify group settings");
51
113
  }
52
114
  }
115
+ /**
116
+ * Return whether a group-member write is signed by someone allowed to make it.
117
+ *
118
+ * @param groupId - Group the membership belongs to.
119
+ * @param newGroupMember - Proposed membership row.
120
+ * @param oldGroupMember - Previous row when this is an update.
121
+ * @param signerRole - Optional precomputed signer role.
122
+ */
53
123
  async function isGroupMemberSignatureValid(groupId, newGroupMember, oldGroupMember, signerRole) {
54
124
  oldGroupMember ??= newGroupMember;
55
125
  // Basic signature verification
@@ -1,3 +1,4 @@
1
+ export * from "./access-level";
1
2
  export * from "./assistants";
2
3
  export * from "./change-tracking";
3
4
  export * from "./channels";
@@ -20,6 +21,8 @@ export * from "./peer-types";
20
21
  export * from "./persistent-vars";
21
22
  export * from "./sync-recovery-queue";
22
23
  export * from "./table-definitions-table";
24
+ export * from "./tool-access";
25
+ export * from "./tool-access-approvals";
23
26
  export * from "./tool-tests";
24
27
  export * from "./tools";
25
28
  export * from "./user-permissions";
@@ -14,6 +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
+ __exportStar(require("./access-level"), exports);
17
18
  __exportStar(require("./assistants"), exports);
18
19
  __exportStar(require("./change-tracking"), exports);
19
20
  __exportStar(require("./channels"), exports);
@@ -36,6 +37,8 @@ __exportStar(require("./peer-types"), exports);
36
37
  __exportStar(require("./persistent-vars"), exports);
37
38
  __exportStar(require("./sync-recovery-queue"), exports);
38
39
  __exportStar(require("./table-definitions-table"), exports);
40
+ __exportStar(require("./tool-access"), exports);
41
+ __exportStar(require("./tool-access-approvals"), exports);
39
42
  __exportStar(require("./tool-tests"), exports);
40
43
  __exportStar(require("./tools"), exports);
41
44
  __exportStar(require("./user-permissions"), exports);
@@ -34,6 +34,16 @@ export declare class SQLDataSource<T extends {
34
34
  delete(idOrRecord: string | T): Promise<void>;
35
35
  dropTableIfExists(): Promise<void>;
36
36
  clearTable(): Promise<void>;
37
+ /**
38
+ * Validate `record` and copy Zod defaults onto missing keys so NOT NULL
39
+ * columns (for example `accessLevel`) are written on insert.
40
+ */
41
+ private applySchemaDefaults;
42
+ /**
43
+ * Columns to SET on update. Required fields that are missing are omitted so
44
+ * older peers without a new column do not overwrite a local value with NULL.
45
+ */
46
+ private fieldsForUpdate;
37
47
  initRecord(data?: Partial<T>): T;
38
48
  /**
39
49
  * Synchronous insert for use within transactions.