mercury-agent 0.5.0 → 0.5.2

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 (125) hide show
  1. package/README.md +452 -451
  2. package/container/Dockerfile +127 -127
  3. package/container/Dockerfile.base +109 -109
  4. package/container/Dockerfile.power +17 -17
  5. package/container/agent-package.json +8 -8
  6. package/container/build.sh +54 -54
  7. package/docs/ROADMAP.md +3 -4
  8. package/docs/archive/summarization/2026-07-02-recent-dev-summary.md +71 -0
  9. package/docs/auth/dashboard.md +28 -28
  10. package/docs/auth/overview.md +109 -109
  11. package/docs/auth/whatsapp.md +173 -173
  12. package/docs/authoring-profiles.md +174 -174
  13. package/docs/configuration.md +54 -54
  14. package/docs/container-lifecycle.md +349 -349
  15. package/docs/{bugs/bwrap-privileged-linux-docker.md → debug/major/2026-07-02-bwrap-privileged-linux-docker.md} +21 -1
  16. package/docs/{bugs/e2big-prompt-delivery-fix.md → debug/major/2026-07-02-e2big-prompt-delivery-fix.md} +27 -1
  17. package/docs/{bugs/registry-pull-no-local-fallback.md → debug/major/2026-07-02-registry-pull-no-local-fallback.md} +27 -1
  18. package/docs/debug/summarization/2026-07-02-common-bug-patterns.md +72 -0
  19. package/docs/deployment.md +199 -199
  20. package/docs/extensions.md +375 -375
  21. package/docs/graceful-shutdown.md +62 -62
  22. package/docs/kb-distillation.md +77 -77
  23. package/docs/media/overview.md +140 -140
  24. package/docs/media/whatsapp.md +171 -171
  25. package/docs/memory.md +137 -137
  26. package/docs/permissions.md +217 -217
  27. package/docs/pipeline.md +228 -228
  28. package/docs/prd-chat-memory.md +76 -76
  29. package/docs/prd-config-load.md +82 -82
  30. package/docs/rate-limiting.md +229 -229
  31. package/docs/runbooks/publish-checklist.md +9 -1
  32. package/docs/scheduler.md +288 -288
  33. package/docs/setup-discord.md +100 -100
  34. package/docs/setup-slack.md +119 -119
  35. package/docs/setup-whatsapp.md +94 -94
  36. package/docs/subagents.md +166 -166
  37. package/docs/web-search.md +62 -62
  38. package/examples/extensions/README.md +12 -12
  39. package/examples/extensions/charts/index.ts +13 -13
  40. package/examples/extensions/charts/skill/SKILL.md +98 -98
  41. package/examples/extensions/gws/README.md +52 -52
  42. package/examples/extensions/gws/skill/SKILL.md +57 -57
  43. package/examples/extensions/gws/skill/references/calendar.md +101 -101
  44. package/examples/extensions/gws/skill/references/docs.md +65 -65
  45. package/examples/extensions/gws/skill/references/drive.md +79 -79
  46. package/examples/extensions/gws/skill/references/gmail.md +85 -85
  47. package/examples/extensions/gws/skill/references/sheets.md +60 -60
  48. package/examples/extensions/napkin/skill/SKILL.md +728 -728
  49. package/examples/extensions/pdf/skill/LICENSE.txt +30 -30
  50. package/examples/extensions/pdf/skill/SKILL.md +314 -314
  51. package/examples/extensions/pdf/skill/forms.md +294 -294
  52. package/examples/extensions/pdf/skill/reference.md +611 -611
  53. package/examples/extensions/pdf/skill/scripts/check_bounding_boxes.py +65 -65
  54. package/examples/extensions/pdf/skill/scripts/check_fillable_fields.py +11 -11
  55. package/examples/extensions/pdf/skill/scripts/convert_pdf_to_images.py +33 -33
  56. package/examples/extensions/pdf/skill/scripts/create_validation_image.py +37 -37
  57. package/examples/extensions/pdf/skill/scripts/extract_form_field_info.py +122 -122
  58. package/examples/extensions/pdf/skill/scripts/extract_form_structure.py +115 -115
  59. package/examples/extensions/pdf/skill/scripts/fill_fillable_fields.py +98 -98
  60. package/examples/extensions/pdf/skill/scripts/fill_pdf_form_with_annotations.py +107 -107
  61. package/examples/extensions/permission-guard/index.ts +65 -65
  62. package/examples/extensions/pinchtab/skill/SKILL.md +224 -224
  63. package/examples/extensions/pinchtab/skill/TRUST.md +69 -69
  64. package/examples/extensions/pinchtab/skill/references/api.md +297 -297
  65. package/examples/extensions/pinchtab/skill/references/env.md +45 -45
  66. package/examples/extensions/pinchtab/skill/references/profiles.md +107 -107
  67. package/examples/extensions/tradestation/host/refresh.ts +102 -102
  68. package/examples/extensions/tradestation/index.ts +153 -153
  69. package/examples/extensions/tradestation/skill/SKILL.md +67 -67
  70. package/examples/extensions/voice-synth/index.ts +94 -94
  71. package/examples/extensions/voice-synth/skill/SKILL.md +38 -38
  72. package/examples/extensions/voice-transcribe/requirements.txt +8 -8
  73. package/examples/extensions/voice-transcribe/scripts/transcribe.py +179 -179
  74. package/examples/extensions/voice-transcribe/skill/SKILL.md +53 -53
  75. package/examples/extensions/yahoo-mail/cli/package.json +13 -13
  76. package/examples/extensions/yahoo-mail/skill/SKILL.md +78 -78
  77. package/package.json +106 -106
  78. package/resources/agents/explore.md +50 -50
  79. package/resources/agents/worker.md +24 -24
  80. package/resources/connection-env-vars.json +25 -25
  81. package/resources/pi-extensions/subagent/agents.ts +126 -126
  82. package/resources/pi-extensions/subagent/index.ts +964 -964
  83. package/resources/profiles/coding/AGENTS.md +43 -43
  84. package/resources/profiles/coding/mercury-profile.yaml +15 -15
  85. package/resources/profiles/general/AGENTS.md +31 -31
  86. package/resources/profiles/general/mercury-profile.yaml +15 -15
  87. package/resources/profiles/research/AGENTS.md +40 -40
  88. package/resources/profiles/research/mercury-profile.yaml +15 -15
  89. package/resources/skills/config/SKILL.md +25 -25
  90. package/resources/skills/context/SKILL.md +33 -33
  91. package/resources/skills/conversation-recap/SKILL.md +19 -19
  92. package/resources/skills/mutes/SKILL.md +31 -31
  93. package/resources/skills/permissions/SKILL.md +19 -19
  94. package/resources/skills/preferences/SKILL.md +31 -31
  95. package/resources/skills/recall/SKILL.md +24 -24
  96. package/resources/skills/roles/SKILL.md +18 -18
  97. package/resources/skills/spaces/SKILL.md +18 -18
  98. package/resources/skills/tasks/SKILL.md +45 -45
  99. package/resources/templates/AGENTS.md +157 -157
  100. package/resources/templates/env.template +38 -38
  101. package/resources/templates/mercury.example.yaml +99 -99
  102. package/src/agent/container-entry.ts +1 -1
  103. package/src/agent/container-runner.ts +1345 -1346
  104. package/src/cli/mercury.ts +39 -7
  105. package/src/cli/mrctl.ts +636 -636
  106. package/src/config-file.ts +540 -540
  107. package/src/config.ts +339 -339
  108. package/src/core/api.ts +125 -125
  109. package/src/core/caller-token.ts +101 -101
  110. package/src/core/permissions.ts +228 -228
  111. package/src/core/profiles.ts +271 -271
  112. package/src/core/routes/capability.ts +70 -70
  113. package/src/core/routes/index.ts +15 -15
  114. package/src/core/runtime.ts +1530 -1530
  115. package/src/dashboard/index.html +729 -729
  116. package/src/extensions/api.ts +273 -273
  117. package/src/extensions/loader.ts +286 -286
  118. package/src/extensions/types.ts +517 -517
  119. package/src/main.ts +605 -605
  120. package/docs/pending-updates/applicative-profiles.md +0 -15
  121. package/docs/pending-updates/caller-bound-capability-token.md +0 -15
  122. package/docs/pending-updates/dm-auto-space.md +0 -15
  123. /package/docs/archive/{2026-07-01-applicative-profiles.md → applicative-profiles/2026-07-01-applicative-profiles.md} +0 -0
  124. /package/docs/archive/{2026-07-01-caller-bound-capability-token.md → applicative-profiles/2026-07-01-caller-bound-capability-token.md} +0 -0
  125. /package/docs/archive/{2026-07-01-dm-auto-space.md → applicative-profiles/2026-07-01-dm-auto-space.md} +0 -0
@@ -1,217 +1,217 @@
1
- # Permissions
2
-
3
- Mercury uses role-based access control (RBAC) per space. Each user has a role, and each role has a set of permissions.
4
-
5
- ## How It Works
6
-
7
- ```
8
- Message arrives
9
-
10
- ├─► Resolve caller's role
11
- │ • System caller? → role = "system"
12
- │ • Seeded admin? → grant admin, store in DB
13
- │ • Existing role in DB? → use it
14
- │ • Otherwise → "member"
15
-
16
- ├─► Load role's permissions
17
- │ • Check space_config for override
18
- │ • Fall back to built-in defaults
19
-
20
- └─► Check permission for action
21
- • Has permission → proceed
22
- • Denied → return error
23
- ```
24
-
25
- ## Roles
26
-
27
- | Role | Default Permissions | Description |
28
- |------|---------------------|-------------|
29
- | `system` | All | Internal system caller (scheduler, etc.) — not assignable |
30
- | `admin` | All | Full control over the space |
31
- | `member` | `prompt`, `prefs.get` | Can chat and read space preferences (default for new users) |
32
-
33
- Custom roles can be created by assigning permissions to any role name.
34
-
35
- ## Permissions
36
-
37
- | Permission | Description |
38
- |------------|-------------|
39
- | `prompt` | Send messages to the assistant |
40
- | `stop` | Abort running agent and clear queue |
41
- | `compact` | Reset session boundary (fresh context) |
42
- | `tasks.list` | View scheduled tasks |
43
- | `tasks.create` | Create new scheduled tasks |
44
- | `tasks.pause` | Pause scheduled tasks |
45
- | `tasks.resume` | Resume paused tasks |
46
- | `tasks.delete` | Delete scheduled tasks |
47
- | `config.get` | Read space configuration |
48
- | `config.set` | Modify space configuration |
49
- | `prefs.get` | Read space preferences (`mrctl prefs list/get`) |
50
- | `prefs.set` | Create, update, or delete space preferences (`mrctl prefs set/delete`) |
51
- | `roles.list` | View roles in the space |
52
- | `roles.grant` | Assign roles to users |
53
- | `roles.revoke` | Remove roles from users |
54
- | `permissions.get` | View role permissions |
55
- | `permissions.set` | Modify role permissions |
56
- | `spaces.list` | View all spaces |
57
- | `spaces.rename` | Rename a space and link/unlink conversations |
58
- | `spaces.delete` | Delete current space and all related DB data |
59
-
60
- ## Mutes
61
-
62
- Muted users’ messages are dropped for the space until the mute expires or is cleared. The agent can mute via `mrctl mute` (with API confirmation). **Operators** with dashboard access (`MERCURY_API_SECRET`) can list active mutes, unmute, or add a timed mute from **Spaces → (space) → Muted users**.
63
-
64
- ## Managing Roles
65
-
66
- The agent uses `mrctl` to manage roles:
67
-
68
- ```bash
69
- # List all roles in the current space
70
- mrctl roles list
71
-
72
- # Grant admin role to a user
73
- mrctl roles grant 1234567890@s.whatsapp.net --role admin
74
-
75
- # Grant a custom role
76
- mrctl roles grant 1234567890@s.whatsapp.net --role moderator
77
-
78
- # Revoke role (user becomes member)
79
- mrctl roles revoke 1234567890@s.whatsapp.net
80
- ```
81
-
82
- ## Managing Permissions
83
-
84
- Permissions are per-role, per-space:
85
-
86
- ```bash
87
- # Show permissions for all roles
88
- mrctl permissions show
89
-
90
- # Show permissions for a specific role
91
- mrctl permissions show --role member
92
-
93
- # Give members ability to stop the agent
94
- mrctl permissions set member prompt,stop
95
-
96
- # Create a moderator role with task management
97
- mrctl permissions set moderator prompt,stop,tasks.list,tasks.pause,tasks.resume
98
-
99
- # Give a role full task control
100
- mrctl permissions set taskmaster prompt,tasks.list,tasks.create,tasks.pause,tasks.resume,tasks.delete
101
- ```
102
-
103
- ## Managing Spaces
104
-
105
- Spaces can be listed and managed via `mrctl`:
106
-
107
- ```bash
108
- # List all spaces with their names
109
- mrctl spaces list
110
-
111
- # Get current space's display name
112
- mrctl spaces name
113
-
114
- # Set current space's display name
115
- mrctl spaces name "Startup Buddies"
116
-
117
- # Delete current space (tasks, messages, roles, config)
118
- mrctl spaces delete
119
-
120
- # List discovered conversations
121
- mrctl conversations list
122
-
123
- # Show only conversations that are not yet linked
124
- mrctl conversations list --unlinked
125
- ```
126
-
127
- Space names are stored in the database and shown in logs/dashboard for easier identification. Conversations remain platform-native and are linked into spaces. Conversation listing uses `spaces.list`; linking and unlinking use `spaces.rename`.
128
-
129
- ## Seeding Admins
130
-
131
- Pre-configure admin users via environment variable. They're granted admin on first interaction with each space:
132
-
133
- ```bash
134
- MERCURY_ADMINS=1234567890@s.whatsapp.net,0987654321@s.whatsapp.net
135
- ```
136
-
137
- This is useful for bootstrapping — the first admin can then grant roles to others.
138
-
139
- ## Storage
140
-
141
- Roles and permissions are stored in SQLite:
142
-
143
- | Table | Purpose |
144
- |-------|---------|
145
- | `space_roles` | Maps `(space_id, platform_user_id)` → `role` |
146
- | `space_config` | Stores `role.<name>.permissions` overrides |
147
-
148
- Built-in defaults (`admin` = all, `member` = `prompt` + `prefs.get`) are not stored — they're applied when no override exists.
149
-
150
- ## System Caller
151
-
152
- The `system` role is special:
153
-
154
- - Always has all permissions
155
- - Cannot be modified or assigned
156
- - Used for internal callers: scheduled tasks, system triggers
157
-
158
- ```typescript
159
- if (isSystemCaller(callerId)) return "system";
160
- ```
161
-
162
- ## Extension Permissions
163
-
164
- Extensions register additional permissions at runtime via the extension API:
165
-
166
- ```typescript
167
- mercury.permission({ defaultRoles: ["admin", "member"] });
168
- ```
169
-
170
- This registers a permission named after the extension (e.g., `napkin`). The behavior:
171
-
172
- - **Admin** always gets all permissions (built-in + extension)
173
- - **Extension `defaultRoles`** are respected — if `["member"]` is specified, members get that permission by default
174
- - **Per-space overrides** still take precedence over defaults
175
- - **Built-in permission names** cannot be overridden by extensions
176
-
177
- Extension CLIs are called directly by the agent in bash. Permission enforcement is handled by a pi extension that blocks denied CLIs at the bash tool level, based on the caller's role and the `MERCURY_DENIED_CLIS` environment variable set by Mercury's runtime.
178
-
179
- Extensions that declare env vars via `mercury.env()` also have those vars gated by permission — they are only injected into containers when the caller has the extension's permission. This prevents credential leakage (e.g., a blocked `gh` CLI's `GH_TOKEN` being used via `curl`).
180
-
181
- ### API
182
-
183
- ```typescript
184
- registerPermission(name, { defaultRoles }) // Register (called by extension loader)
185
- getAllPermissions() // Built-in + extension permissions
186
- isValidPermission(name) // Check if name is valid
187
- resetPermissions() // Clear registered (test isolation)
188
- ```
189
-
190
- ## Scope
191
-
192
- Permissions are **per-space**:
193
-
194
- - A user can be `admin` in one space and `member` in another
195
- - Custom role permissions are space-specific
196
- - No global roles (except seeded admins, which apply on first interaction per space)
197
-
198
- ## API
199
-
200
- ### `resolveRole(db, spaceId, platformUserId, seededAdmins)`
201
-
202
- Determines a caller's role:
203
- 1. System caller → `"system"`
204
- 2. Seed admins if needed
205
- 3. Upsert member record
206
- 4. Return stored role or `"member"`
207
-
208
- ### `getRolePermissions(db, spaceId, role)`
209
-
210
- Returns the permission set for a role:
211
- 1. System role → all permissions
212
- 2. Check `space_config` for `role.<name>.permissions`
213
- 3. Fall back to built-in defaults
214
-
215
- ### `hasPermission(db, spaceId, role, permission)`
216
-
217
- Returns `true` if the role has the specified permission.
1
+ # Permissions
2
+
3
+ Mercury uses role-based access control (RBAC) per space. Each user has a role, and each role has a set of permissions.
4
+
5
+ ## How It Works
6
+
7
+ ```
8
+ Message arrives
9
+
10
+ ├─► Resolve caller's role
11
+ │ • System caller? → role = "system"
12
+ │ • Seeded admin? → grant admin, store in DB
13
+ │ • Existing role in DB? → use it
14
+ │ • Otherwise → "member"
15
+
16
+ ├─► Load role's permissions
17
+ │ • Check space_config for override
18
+ │ • Fall back to built-in defaults
19
+
20
+ └─► Check permission for action
21
+ • Has permission → proceed
22
+ • Denied → return error
23
+ ```
24
+
25
+ ## Roles
26
+
27
+ | Role | Default Permissions | Description |
28
+ |------|---------------------|-------------|
29
+ | `system` | All | Internal system caller (scheduler, etc.) — not assignable |
30
+ | `admin` | All | Full control over the space |
31
+ | `member` | `prompt`, `prefs.get` | Can chat and read space preferences (default for new users) |
32
+
33
+ Custom roles can be created by assigning permissions to any role name.
34
+
35
+ ## Permissions
36
+
37
+ | Permission | Description |
38
+ |------------|-------------|
39
+ | `prompt` | Send messages to the assistant |
40
+ | `stop` | Abort running agent and clear queue |
41
+ | `compact` | Reset session boundary (fresh context) |
42
+ | `tasks.list` | View scheduled tasks |
43
+ | `tasks.create` | Create new scheduled tasks |
44
+ | `tasks.pause` | Pause scheduled tasks |
45
+ | `tasks.resume` | Resume paused tasks |
46
+ | `tasks.delete` | Delete scheduled tasks |
47
+ | `config.get` | Read space configuration |
48
+ | `config.set` | Modify space configuration |
49
+ | `prefs.get` | Read space preferences (`mrctl prefs list/get`) |
50
+ | `prefs.set` | Create, update, or delete space preferences (`mrctl prefs set/delete`) |
51
+ | `roles.list` | View roles in the space |
52
+ | `roles.grant` | Assign roles to users |
53
+ | `roles.revoke` | Remove roles from users |
54
+ | `permissions.get` | View role permissions |
55
+ | `permissions.set` | Modify role permissions |
56
+ | `spaces.list` | View all spaces |
57
+ | `spaces.rename` | Rename a space and link/unlink conversations |
58
+ | `spaces.delete` | Delete current space and all related DB data |
59
+
60
+ ## Mutes
61
+
62
+ Muted users’ messages are dropped for the space until the mute expires or is cleared. The agent can mute via `mrctl mute` (with API confirmation). **Operators** with dashboard access (`MERCURY_API_SECRET`) can list active mutes, unmute, or add a timed mute from **Spaces → (space) → Muted users**.
63
+
64
+ ## Managing Roles
65
+
66
+ The agent uses `mrctl` to manage roles:
67
+
68
+ ```bash
69
+ # List all roles in the current space
70
+ mrctl roles list
71
+
72
+ # Grant admin role to a user
73
+ mrctl roles grant 1234567890@s.whatsapp.net --role admin
74
+
75
+ # Grant a custom role
76
+ mrctl roles grant 1234567890@s.whatsapp.net --role moderator
77
+
78
+ # Revoke role (user becomes member)
79
+ mrctl roles revoke 1234567890@s.whatsapp.net
80
+ ```
81
+
82
+ ## Managing Permissions
83
+
84
+ Permissions are per-role, per-space:
85
+
86
+ ```bash
87
+ # Show permissions for all roles
88
+ mrctl permissions show
89
+
90
+ # Show permissions for a specific role
91
+ mrctl permissions show --role member
92
+
93
+ # Give members ability to stop the agent
94
+ mrctl permissions set member prompt,stop
95
+
96
+ # Create a moderator role with task management
97
+ mrctl permissions set moderator prompt,stop,tasks.list,tasks.pause,tasks.resume
98
+
99
+ # Give a role full task control
100
+ mrctl permissions set taskmaster prompt,tasks.list,tasks.create,tasks.pause,tasks.resume,tasks.delete
101
+ ```
102
+
103
+ ## Managing Spaces
104
+
105
+ Spaces can be listed and managed via `mrctl`:
106
+
107
+ ```bash
108
+ # List all spaces with their names
109
+ mrctl spaces list
110
+
111
+ # Get current space's display name
112
+ mrctl spaces name
113
+
114
+ # Set current space's display name
115
+ mrctl spaces name "Startup Buddies"
116
+
117
+ # Delete current space (tasks, messages, roles, config)
118
+ mrctl spaces delete
119
+
120
+ # List discovered conversations
121
+ mrctl conversations list
122
+
123
+ # Show only conversations that are not yet linked
124
+ mrctl conversations list --unlinked
125
+ ```
126
+
127
+ Space names are stored in the database and shown in logs/dashboard for easier identification. Conversations remain platform-native and are linked into spaces. Conversation listing uses `spaces.list`; linking and unlinking use `spaces.rename`.
128
+
129
+ ## Seeding Admins
130
+
131
+ Pre-configure admin users via environment variable. They're granted admin on first interaction with each space:
132
+
133
+ ```bash
134
+ MERCURY_ADMINS=1234567890@s.whatsapp.net,0987654321@s.whatsapp.net
135
+ ```
136
+
137
+ This is useful for bootstrapping — the first admin can then grant roles to others.
138
+
139
+ ## Storage
140
+
141
+ Roles and permissions are stored in SQLite:
142
+
143
+ | Table | Purpose |
144
+ |-------|---------|
145
+ | `space_roles` | Maps `(space_id, platform_user_id)` → `role` |
146
+ | `space_config` | Stores `role.<name>.permissions` overrides |
147
+
148
+ Built-in defaults (`admin` = all, `member` = `prompt` + `prefs.get`) are not stored — they're applied when no override exists.
149
+
150
+ ## System Caller
151
+
152
+ The `system` role is special:
153
+
154
+ - Always has all permissions
155
+ - Cannot be modified or assigned
156
+ - Used for internal callers: scheduled tasks, system triggers
157
+
158
+ ```typescript
159
+ if (isSystemCaller(callerId)) return "system";
160
+ ```
161
+
162
+ ## Extension Permissions
163
+
164
+ Extensions register additional permissions at runtime via the extension API:
165
+
166
+ ```typescript
167
+ mercury.permission({ defaultRoles: ["admin", "member"] });
168
+ ```
169
+
170
+ This registers a permission named after the extension (e.g., `napkin`). The behavior:
171
+
172
+ - **Admin** always gets all permissions (built-in + extension)
173
+ - **Extension `defaultRoles`** are respected — if `["member"]` is specified, members get that permission by default
174
+ - **Per-space overrides** still take precedence over defaults
175
+ - **Built-in permission names** cannot be overridden by extensions
176
+
177
+ Extension CLIs are called directly by the agent in bash. Permission enforcement is handled by a pi extension that blocks denied CLIs at the bash tool level, based on the caller's role and the `MERCURY_DENIED_CLIS` environment variable set by Mercury's runtime.
178
+
179
+ Extensions that declare env vars via `mercury.env()` also have those vars gated by permission — they are only injected into containers when the caller has the extension's permission. This prevents credential leakage (e.g., a blocked `gh` CLI's `GH_TOKEN` being used via `curl`).
180
+
181
+ ### API
182
+
183
+ ```typescript
184
+ registerPermission(name, { defaultRoles }) // Register (called by extension loader)
185
+ getAllPermissions() // Built-in + extension permissions
186
+ isValidPermission(name) // Check if name is valid
187
+ resetPermissions() // Clear registered (test isolation)
188
+ ```
189
+
190
+ ## Scope
191
+
192
+ Permissions are **per-space**:
193
+
194
+ - A user can be `admin` in one space and `member` in another
195
+ - Custom role permissions are space-specific
196
+ - No global roles (except seeded admins, which apply on first interaction per space)
197
+
198
+ ## API
199
+
200
+ ### `resolveRole(db, spaceId, platformUserId, seededAdmins)`
201
+
202
+ Determines a caller's role:
203
+ 1. System caller → `"system"`
204
+ 2. Seed admins if needed
205
+ 3. Upsert member record
206
+ 4. Return stored role or `"member"`
207
+
208
+ ### `getRolePermissions(db, spaceId, role)`
209
+
210
+ Returns the permission set for a role:
211
+ 1. System role → all permissions
212
+ 2. Check `space_config` for `role.<name>.permissions`
213
+ 3. Fall back to built-in defaults
214
+
215
+ ### `hasPermission(db, spaceId, role, permission)`
216
+
217
+ Returns `true` if the role has the specified permission.