@bevel-software/platform-core-backend 0.10.0 → 0.11.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 (188) hide show
  1. package/dist/core/create-core-server.d.ts.map +1 -1
  2. package/dist/core/create-core-server.js +12 -1
  3. package/dist/core/create-core-server.js.map +1 -1
  4. package/dist/core/create-core-services.d.ts +22 -0
  5. package/dist/core/create-core-services.d.ts.map +1 -1
  6. package/dist/core/create-core-services.js +34 -1
  7. package/dist/core/create-core-services.js.map +1 -1
  8. package/dist/index.d.ts +2 -0
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +4 -0
  11. package/dist/index.js.map +1 -1
  12. package/dist/modules/access/access-control.interface.d.ts +54 -28
  13. package/dist/modules/access/access-control.interface.d.ts.map +1 -1
  14. package/dist/modules/access/access-control.service.d.ts +129 -14
  15. package/dist/modules/access/access-control.service.d.ts.map +1 -1
  16. package/dist/modules/access/access-control.service.js +435 -66
  17. package/dist/modules/access/access-control.service.js.map +1 -1
  18. package/dist/modules/access/access-declarations.d.ts.map +1 -1
  19. package/dist/modules/access/access-declarations.js +5 -3
  20. package/dist/modules/access/access-declarations.js.map +1 -1
  21. package/dist/modules/access/access-mutation.service.d.ts +39 -6
  22. package/dist/modules/access/access-mutation.service.d.ts.map +1 -1
  23. package/dist/modules/access/access-mutation.service.js +78 -18
  24. package/dist/modules/access/access-mutation.service.js.map +1 -1
  25. package/dist/modules/access/access-splice.d.ts +31 -4
  26. package/dist/modules/access/access-splice.d.ts.map +1 -1
  27. package/dist/modules/access/access-splice.js +40 -16
  28. package/dist/modules/access/access-splice.js.map +1 -1
  29. package/dist/modules/access/access.routes.d.ts.map +1 -1
  30. package/dist/modules/access/access.routes.js +204 -82
  31. package/dist/modules/access/access.routes.js.map +1 -1
  32. package/dist/modules/access/admin-locked-commit.d.ts +134 -0
  33. package/dist/modules/access/admin-locked-commit.d.ts.map +1 -0
  34. package/dist/modules/access/admin-locked-commit.js +277 -0
  35. package/dist/modules/access/admin-locked-commit.js.map +1 -0
  36. package/dist/modules/access/admin-route-helpers.d.ts +32 -0
  37. package/dist/modules/access/admin-route-helpers.d.ts.map +1 -0
  38. package/dist/modules/access/admin-route-helpers.js +44 -0
  39. package/dist/modules/access/admin-route-helpers.js.map +1 -0
  40. package/dist/modules/access/capability-registry.d.ts +41 -0
  41. package/dist/modules/access/capability-registry.d.ts.map +1 -0
  42. package/dist/modules/access/capability-registry.js +46 -0
  43. package/dist/modules/access/capability-registry.js.map +1 -0
  44. package/dist/modules/access/directory-sync-bot.d.ts +13 -0
  45. package/dist/modules/access/directory-sync-bot.d.ts.map +1 -0
  46. package/dist/modules/access/directory-sync-bot.js +64 -0
  47. package/dist/modules/access/directory-sync-bot.js.map +1 -0
  48. package/dist/modules/access/group-files.d.ts +83 -0
  49. package/dist/modules/access/group-files.d.ts.map +1 -0
  50. package/dist/modules/access/group-files.js +167 -0
  51. package/dist/modules/access/group-files.js.map +1 -0
  52. package/dist/modules/access/groups-admin.routes.d.ts +19 -0
  53. package/dist/modules/access/groups-admin.routes.d.ts.map +1 -0
  54. package/dist/modules/access/groups-admin.routes.js +98 -0
  55. package/dist/modules/access/groups-admin.routes.js.map +1 -0
  56. package/dist/modules/access/groups-admin.service.d.ts +166 -0
  57. package/dist/modules/access/groups-admin.service.d.ts.map +1 -0
  58. package/dist/modules/access/groups-admin.service.js +442 -0
  59. package/dist/modules/access/groups-admin.service.js.map +1 -0
  60. package/dist/modules/access/groups-edit.d.ts +58 -0
  61. package/dist/modules/access/groups-edit.d.ts.map +1 -0
  62. package/dist/modules/access/groups-edit.js +162 -0
  63. package/dist/modules/access/groups-edit.js.map +1 -0
  64. package/dist/modules/access/reference-scan.d.ts +141 -0
  65. package/dist/modules/access/reference-scan.d.ts.map +1 -0
  66. package/dist/modules/access/reference-scan.js +440 -0
  67. package/dist/modules/access/reference-scan.js.map +1 -0
  68. package/dist/modules/access/roles-admin.service.d.ts +88 -119
  69. package/dist/modules/access/roles-admin.service.d.ts.map +1 -1
  70. package/dist/modules/access/roles-admin.service.js +230 -384
  71. package/dist/modules/access/roles-admin.service.js.map +1 -1
  72. package/dist/modules/access/roles-edit.d.ts +51 -25
  73. package/dist/modules/access/roles-edit.d.ts.map +1 -1
  74. package/dist/modules/access/roles-edit.js +133 -59
  75. package/dist/modules/access/roles-edit.js.map +1 -1
  76. package/dist/modules/access/synced-groups-committer.d.ts +28 -0
  77. package/dist/modules/access/synced-groups-committer.d.ts.map +1 -0
  78. package/dist/modules/access/synced-groups-committer.js +139 -0
  79. package/dist/modules/access/synced-groups-committer.js.map +1 -0
  80. package/dist/modules/access/synced-groups-writer.d.ts +78 -0
  81. package/dist/modules/access/synced-groups-writer.d.ts.map +1 -0
  82. package/dist/modules/access/synced-groups-writer.js +219 -0
  83. package/dist/modules/access/synced-groups-writer.js.map +1 -0
  84. package/dist/modules/database/core-schema.d.ts +17 -0
  85. package/dist/modules/database/core-schema.d.ts.map +1 -1
  86. package/dist/modules/database/core-schema.js +9 -0
  87. package/dist/modules/database/core-schema.js.map +1 -1
  88. package/dist/modules/mcp/mcp-auth.middleware.d.ts +13 -2
  89. package/dist/modules/mcp/mcp-auth.middleware.d.ts.map +1 -1
  90. package/dist/modules/mcp/mcp-auth.middleware.js +61 -2
  91. package/dist/modules/mcp/mcp-auth.middleware.js.map +1 -1
  92. package/dist/modules/mcp/mcp.routes.d.ts +9 -3
  93. package/dist/modules/mcp/mcp.routes.d.ts.map +1 -1
  94. package/dist/modules/mcp/mcp.routes.js +126 -2
  95. package/dist/modules/mcp/mcp.routes.js.map +1 -1
  96. package/dist/modules/mcp/mcp.service.d.ts +14 -0
  97. package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
  98. package/dist/modules/mcp/mcp.service.js +6 -1
  99. package/dist/modules/mcp/mcp.service.js.map +1 -1
  100. package/dist/modules/workflow/file-lock.service.d.ts +11 -1
  101. package/dist/modules/workflow/file-lock.service.d.ts.map +1 -1
  102. package/dist/modules/workflow/file-lock.service.js +15 -1
  103. package/dist/modules/workflow/file-lock.service.js.map +1 -1
  104. package/dist/modules/workflow/git/git.service.d.ts +34 -11
  105. package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
  106. package/dist/modules/workflow/git/git.service.js +165 -37
  107. package/dist/modules/workflow/git/git.service.js.map +1 -1
  108. package/dist/modules/workflow/locking-filesystem.d.ts +4 -0
  109. package/dist/modules/workflow/locking-filesystem.d.ts.map +1 -1
  110. package/dist/modules/workflow/locking-filesystem.js +181 -28
  111. package/dist/modules/workflow/locking-filesystem.js.map +1 -1
  112. package/dist/modules/workflow/pending-commits.service.d.ts +10 -0
  113. package/dist/modules/workflow/pending-commits.service.d.ts.map +1 -1
  114. package/dist/modules/workflow/pending-commits.service.js +19 -1
  115. package/dist/modules/workflow/pending-commits.service.js.map +1 -1
  116. package/dist/modules/workflow/workflow.service.d.ts +17 -2
  117. package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
  118. package/dist/modules/workflow/workflow.service.js +113 -19
  119. package/dist/modules/workflow/workflow.service.js.map +1 -1
  120. package/kb-template/AGENTS.md +4 -1
  121. package/migrations/0004_file_lock_mode.sql +2 -0
  122. package/migrations/meta/0004_snapshot.json +1564 -0
  123. package/migrations/meta/_journal.json +7 -0
  124. package/package.json +3 -3
  125. package/src/core/create-core-server.ts +13 -0
  126. package/src/core/create-core-services.ts +68 -0
  127. package/src/index.ts +10 -0
  128. package/src/modules/access/__tests__/access-control.service.test.ts +9 -3
  129. package/src/modules/access/__tests__/access-groups.test.ts +427 -0
  130. package/src/modules/access/__tests__/access-mutation.service.test.ts +171 -5
  131. package/src/modules/access/__tests__/access-splice.test.ts +65 -0
  132. package/src/modules/access/__tests__/access.routes.group-grant.test.ts +337 -0
  133. package/src/modules/access/__tests__/access.routes.revoke.test.ts +48 -0
  134. package/src/modules/access/__tests__/admin-locked-commit.test.ts +221 -0
  135. package/src/modules/access/__tests__/admin-route-helpers.test.ts +61 -0
  136. package/src/modules/access/__tests__/directory-sync-bot.test.ts +106 -0
  137. package/src/modules/access/__tests__/grant-sources.test.ts +67 -0
  138. package/src/modules/access/__tests__/groups-admin.service.test.ts +440 -0
  139. package/src/modules/access/__tests__/reference-scan.test.ts +288 -0
  140. package/src/modules/access/__tests__/roles-admin.service.test.ts +161 -83
  141. package/src/modules/access/__tests__/roles-capabilities.test.ts +325 -0
  142. package/src/modules/access/__tests__/roles-edit.test.ts +104 -28
  143. package/src/modules/access/__tests__/roles.routes.test.ts +63 -40
  144. package/src/modules/access/__tests__/synced-groups-committer.test.ts +238 -0
  145. package/src/modules/access/__tests__/synced-groups-writer.test.ts +249 -0
  146. package/src/modules/access/access-control.interface.ts +66 -32
  147. package/src/modules/access/access-control.service.ts +536 -73
  148. package/src/modules/access/access-declarations.ts +5 -2
  149. package/src/modules/access/access-mutation.service.ts +88 -14
  150. package/src/modules/access/access-splice.ts +55 -17
  151. package/src/modules/access/access.routes.ts +227 -93
  152. package/src/modules/access/admin-locked-commit.ts +331 -0
  153. package/src/modules/access/admin-route-helpers.ts +55 -0
  154. package/src/modules/access/capability-registry.ts +74 -0
  155. package/src/modules/access/directory-sync-bot.ts +76 -0
  156. package/src/modules/access/group-files.ts +212 -0
  157. package/src/modules/access/groups-admin.routes.ts +113 -0
  158. package/src/modules/access/groups-admin.service.ts +551 -0
  159. package/src/modules/access/groups-edit.ts +187 -0
  160. package/src/modules/access/reference-scan.ts +513 -0
  161. package/src/modules/access/roles-admin.service.ts +290 -418
  162. package/src/modules/access/roles-edit.ts +134 -61
  163. package/src/modules/access/synced-groups-committer.ts +177 -0
  164. package/src/modules/access/synced-groups-writer.ts +303 -0
  165. package/src/modules/database/core-schema.ts +9 -0
  166. package/src/modules/mcp/__tests__/mcp-auth.middleware.test.ts +116 -0
  167. package/src/modules/mcp/__tests__/mcp.e2e.test.ts +3 -1
  168. package/src/modules/mcp/__tests__/mcp.routes.delete.test.ts +6 -1
  169. package/src/modules/mcp/__tests__/mcp.routes.local-token.test.ts +301 -0
  170. package/src/modules/mcp/mcp-auth.middleware.ts +61 -1
  171. package/src/modules/mcp/mcp.routes.ts +137 -2
  172. package/src/modules/mcp/mcp.service.ts +6 -1
  173. package/src/modules/workflow/__tests__/locking-filesystem.test.ts +336 -0
  174. package/src/modules/workflow/__tests__/preserve-roles-yaml.test.ts +1 -1
  175. package/src/modules/workflow/__tests__/workflow.service.commitFileWhileLocked.test.ts +32 -0
  176. package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +13 -2
  177. package/src/modules/workflow/__tests__/workflow.service.releaseLock.test.ts +139 -1
  178. package/src/modules/workflow/file-lock.service.ts +15 -0
  179. package/src/modules/workflow/git/__tests__/git.service.accessGating.test.ts +0 -1
  180. package/src/modules/workflow/git/__tests__/git.service.commitChanges.test.ts +132 -0
  181. package/src/modules/workflow/git/__tests__/git.service.deleteBranch.test.ts +0 -1
  182. package/src/modules/workflow/git/__tests__/pull-request.service.test.ts +0 -1
  183. package/src/modules/workflow/git/git.service.ts +174 -35
  184. package/src/modules/workflow/locking-filesystem.ts +188 -26
  185. package/src/modules/workflow/pending-commits.service.ts +27 -1
  186. package/src/modules/workflow/review-workflow/__tests__/approval-states.test.ts +0 -1
  187. package/src/modules/workflow/review-workflow/__tests__/cancel-pr.test.ts +0 -1
  188. package/src/modules/workflow/workflow.service.ts +140 -20
@@ -0,0 +1,442 @@
1
+ /**
2
+ * Admin Groups service — CRUD on the default-branch `groups.yaml` (manual
3
+ * mode), the mode probe, and the connect-time retirement of manual groups.
4
+ *
5
+ * Shares `RolesAdminService`'s write pipeline via the SHARED
6
+ * `AdminLockedCommits` helper (one lock/commit implementation — the twin
7
+ * copies diverged once into a real bug) with the policy differences the
8
+ * roles/groups split defines:
9
+ *
10
+ * - MODE GATE: every mutation refuses in IdP mode (`synced-groups.yaml`
11
+ * exists on the default branch). Groups are managed in the identity
12
+ * provider then; a second write surface would fragment org management —
13
+ * which is the thing the mode model exists to prevent.
14
+ * - COLLISION GATE: create/rename refuse a name whose canonical form is a
15
+ * roles.yaml role. Bare-name precedence would let the group win, but a
16
+ * deliberately-created shadow of a capability role is far more likely a
17
+ * mistake than an intent — the IdP can still produce collisions, which
18
+ * precedence then resolves (group first, `role/<name>` for the role).
19
+ * - DEGRADE LOUDLY: a broken groups source never fails access resolution
20
+ * closed — groups contribute nothing and the model carries a
21
+ * `groupsHealth` marker, which the roster exposes so the Groups page can
22
+ * banner it. A broken MANUAL groups.yaml additionally makes the roster
23
+ * answer 422 with the parse message (the operator must see what to fix —
24
+ * never a dead Groups page, never a silent empty roster they might
25
+ * "repair" by re-creating groups over live bytes).
26
+ */
27
+ import { workspaceIdForBranch } from '../workspace/workspace.service.js';
28
+ import { WorkflowDomainError } from '../workflow/workflow.errors.js';
29
+ import { GROUP_REF_PREFIX, canonicalRoleName, hasAccessFrontmatterExtension, loadActiveGroups, } from './access-control.service.js';
30
+ import { GROUPS_YAML, SYNCED_GROUPS_YAML, validateGroupsFile } from './group-files.js';
31
+ import { parseRolesModel, removeGroupRefsEverywhere, renameGroupRefs, isGroupRefMember } from './roles-edit.js';
32
+ import { makeRolesYamlWriteValidator } from './roles-yaml-guard.js';
33
+ import { GroupsEditError, parseGroupsModel, createGroup as editCreateGroup, deleteGroup as editDeleteGroup, addGroupMember as editAddMember, removeGroupMember as editRemoveMember, renameGroupDisplay as editRenameDisplay, } from './groups-edit.js';
34
+ import { AdminLockedCommits } from './admin-locked-commit.js';
35
+ import { KbReferenceScanner } from './reference-scan.js';
36
+ /** Where role→group assignments live (`group:<canonical>` member entries). */
37
+ const ROLES_YAML = 'roles.yaml';
38
+ /** A group mutation that cannot proceed. `payload.kind` distinguishes the
39
+ * mode refusal (`'idp-mode'`) for the UI. */
40
+ export class GroupsAdminError extends WorkflowDomainError {
41
+ constructor(message, status = 422, payload) {
42
+ super(message, status, payload);
43
+ this.name = 'GroupsAdminError';
44
+ }
45
+ }
46
+ export class GroupsAdminService {
47
+ workspaceService;
48
+ workflowService;
49
+ accessControl;
50
+ kbDirName;
51
+ defaultBranchOf;
52
+ eventBus;
53
+ /** Shared lock/commit plumbing — see module doc. */
54
+ locked;
55
+ /** Shared cached reference scanner. */
56
+ references;
57
+ constructor(workspaceService, workflowService, accessControl, kbDirName,
58
+ /** Live-binding thunk — see RolesAdminService's identical note. */
59
+ defaultBranchOf, eventBus) {
60
+ this.workspaceService = workspaceService;
61
+ this.workflowService = workflowService;
62
+ this.accessControl = accessControl;
63
+ this.kbDirName = kbDirName;
64
+ this.defaultBranchOf = defaultBranchOf;
65
+ this.eventBus = eventBus;
66
+ this.locked = new AdminLockedCommits({
67
+ workspaceService,
68
+ workflowService,
69
+ kbDirName,
70
+ defaultBranchOf,
71
+ makeError: (message, status, payload) => new GroupsAdminError(message, status, payload),
72
+ logTag: 'groups-admin',
73
+ contendedSubject: 'Groups',
74
+ // The rename/delete batch may rewrite roles.yaml (group:<ref> members)
75
+ // — same pre-disk no-lockout gate the roles admin attaches.
76
+ validateWrite: makeRolesYamlWriteValidator(kbDirName),
77
+ });
78
+ // The event bus keeps the roster's `referencedBy` fresh: a share-dialog
79
+ // grant/revoke on any access.md happens outside this service, and the
80
+ // scanner invalidates its cache on those writes' events (TTL as backstop).
81
+ this.references = new KbReferenceScanner(workspaceService, kbDirName, eventBus);
82
+ }
83
+ get defaultBranch() {
84
+ return this.defaultBranchOf();
85
+ }
86
+ get workspaceId() {
87
+ return workspaceIdForBranch(this.defaultBranch);
88
+ }
89
+ async ensureWorkspace() {
90
+ await this.workspaceService.getOrCreateForBranch(this.defaultBranch);
91
+ return this.workspaceId;
92
+ }
93
+ /**
94
+ * IdP mode iff `synced-groups.yaml` exists — same rule the resolver applies.
95
+ *
96
+ * KNOWN GAP, deliberate: this cannot see the connect WINDOW — a directory
97
+ * connection that is configured but whose first provisioning push has not
98
+ * landed the synced file yet. Core's `SyncedGroupsSource` seam exposes only
99
+ * `listGroups()` (the overlay owns the connection), so "connected but not
100
+ * yet materialized" is not cheaply knowable server-side; the UI keeps its
101
+ * own suppression for that window.
102
+ */
103
+ async getMode() {
104
+ const workspaceId = await this.ensureWorkspace();
105
+ return (await this.locked.readKbFile(workspaceId, SYNCED_GROUPS_YAML)) !== null ? 'idp' : 'manual';
106
+ }
107
+ // ---- Read ---------------------------------------------------------------
108
+ /**
109
+ * Mode + roster + source health. Reads the ACTIVE source through the same
110
+ * `loadActiveGroups` rule the resolver applies, so mode, health, and the
111
+ * group set can never disagree with resolution:
112
+ *
113
+ * - IdP mode (synced file present, even broken): the roster IS the synced
114
+ * file — read-only in the UI; the manual file is retired and showing it
115
+ * would misreport who has access. A broken synced file yields an EMPTY
116
+ * roster with the `groupsHealth` marker (mode stays 'idp' — no
117
+ * fallback to manual, which would resurrect retired groups).
118
+ * - Manual mode, broken groups.yaml: 422 with the parse message — the
119
+ * repair path must show the operator what to fix, never a dead page.
120
+ */
121
+ async getRoster() {
122
+ const workspaceId = await this.ensureWorkspace();
123
+ const active = await loadActiveGroups((f) => this.locked.readKbFile(workspaceId, f));
124
+ if (active.sourceFile === GROUPS_YAML && !active.health.ok) {
125
+ throw new GroupsAdminError(`groups.yaml cannot be read: ${active.health.reason}`, 422, {
126
+ kind: 'broken-groups',
127
+ file: active.health.file,
128
+ reason: active.health.reason,
129
+ });
130
+ }
131
+ const [referencesByToken, assignedByGroup] = await Promise.all([
132
+ this.references.scan(workspaceId),
133
+ this.rolesReferencingGroups(workspaceId),
134
+ ]);
135
+ const mode = active.sourceFile === SYNCED_GROUPS_YAML ? 'idp' : 'manual';
136
+ const groups = [];
137
+ for (const [canonical, def] of active.groups) {
138
+ groups.push({
139
+ canonical,
140
+ displayName: def.displayName,
141
+ // IdP members render sorted (machine-generated file, no author order
142
+ // to honour); manual members keep file order (the editor's view).
143
+ members: mode === 'idp' ? [...def.emails].sort() : [...def.emails],
144
+ // A group's references are its BARE-token hits, unconditionally: this
145
+ // group exists in the active source, so under group-first precedence
146
+ // it owns the bare key — even when a role shares the name (the roles
147
+ // roster is the mirror image: it then skips the bare hits and counts
148
+ // only `role/<name>`). Explicit `role/<name>` hits are never a group
149
+ // reference.
150
+ referencedBy: referencesByToken.get(canonical) ?? [],
151
+ assignedToRoles: assignedByGroup.get(canonical) ?? [],
152
+ });
153
+ }
154
+ return { mode, groups, groupsHealth: active.health };
155
+ }
156
+ /**
157
+ * Display names from the ACTIVE group source (synced in IdP mode, manual
158
+ * otherwise). Malformed files degrade to an empty list, matching the
159
+ * resolver's degrade rule.
160
+ */
161
+ async listActiveGroupNames() {
162
+ const workspaceId = await this.ensureWorkspace();
163
+ const active = await loadActiveGroups((f) => this.locked.readKbFile(workspaceId, f));
164
+ return [...active.groups.values()].map((g) => g.displayName);
165
+ }
166
+ /** Manual group display names — what the connect-time warning dialog lists. */
167
+ async listManualGroupNames() {
168
+ const workspaceId = await this.ensureWorkspace();
169
+ const text = (await this.locked.readKbFile(workspaceId, GROUPS_YAML)) ?? '';
170
+ return parseGroupsModel(text).map((g) => g.displayName);
171
+ }
172
+ // ---- Mutations ----------------------------------------------------------
173
+ async createGroup(actor, displayName) {
174
+ // Mode gate FIRST: in IdP mode every mutation must answer the typed 409
175
+ // (the UI's cue to point at the identity provider) — a name-collision 422
176
+ // for a name like "Admin" would mislead about what is actually refused.
177
+ await this.assertManualMode(await this.ensureWorkspace());
178
+ await this.assertRoleNameFree(displayName);
179
+ await this.runEdit(actor, (text) => editCreateGroup(text, displayName));
180
+ return this.getRoster();
181
+ }
182
+ /**
183
+ * Delete a group AND unassign it from every role — the `group:<canonical>`
184
+ * members in roles.yaml — in the SAME atomic locked commit (mirror of the
185
+ * rename's ref rewrite): a delete that left the refs would silently shrink
186
+ * each assigned role's membership behind a mere log warning. Grant
187
+ * references in access.md are NOT touched — a dangling grant resolves to
188
+ * nothing by design (and the roster's `referencedBy` fed the confirm
189
+ * dialog's warning).
190
+ */
191
+ async deleteGroup(actor, canonical) {
192
+ const workspaceId = await this.ensureWorkspace();
193
+ await this.assertManualMode(workspaceId);
194
+ await this.locked.withFileLocks(workspaceId, actor, [GROUPS_YAML, ROLES_YAML], async () => {
195
+ const groupsOriginal = await this.locked.readKbFile(workspaceId, GROUPS_YAML);
196
+ const groupsEdit = this.guardEdit(() => editDeleteGroup(groupsOriginal ?? '', canonical));
197
+ this.assertLoadable(groupsEdit.text);
198
+ const files = [
199
+ { repoRel: GROUPS_YAML, content: groupsEdit.text, original: groupsOriginal },
200
+ ];
201
+ const rolesText = await this.locked.readKbFile(workspaceId, ROLES_YAML);
202
+ if (rolesText !== null) {
203
+ let rolesEdit;
204
+ try {
205
+ rolesEdit = removeGroupRefsEverywhere(rolesText, canonical);
206
+ }
207
+ catch (err) {
208
+ // Fail closed on a malformed roles.yaml: deleting the group without
209
+ // unassigning it would strand dangling refs the delete exists to clean.
210
+ throw new GroupsAdminError(`Cannot rewrite role assignments in ${ROLES_YAML}; delete aborted with no changes`, 422, { cause: err?.message });
211
+ }
212
+ if (rolesEdit.changed) {
213
+ // Same pre-disk no-lockout gate LockingFilesystem would have run.
214
+ makeRolesYamlWriteValidator(this.kbDirName)(`${this.kbDirName}/${ROLES_YAML}`, rolesEdit.text);
215
+ files.push({ repoRel: ROLES_YAML, content: rolesEdit.text, original: rolesText });
216
+ }
217
+ }
218
+ await this.locked.writeAndCommitLocked(workspaceId, actor, files, `Delete group ${canonical}`);
219
+ this.afterWrite(workspaceId, actor, files.map((f) => f.repoRel));
220
+ });
221
+ return this.getRoster();
222
+ }
223
+ async addMember(actor, canonical, email) {
224
+ await this.runEdit(actor, (text) => editAddMember(text, canonical, email));
225
+ return this.getRoster();
226
+ }
227
+ async removeMember(actor, canonical, email) {
228
+ await this.runEdit(actor, (text) => editRemoveMember(text, canonical, email));
229
+ return this.getRoster();
230
+ }
231
+ /**
232
+ * Rename — canonical-changing renames rewrite every grant reference in ONE
233
+ * atomic commit (same machinery, same reasoning as ref unassignment on
234
+ * delete: a partial rewrite is a silent access drop).
235
+ */
236
+ async renameGroup(actor, canonical, newDisplayName) {
237
+ const workspaceId = await this.ensureWorkspace();
238
+ await this.assertManualMode(workspaceId);
239
+ const newCanonical = canonicalRoleName(newDisplayName);
240
+ if (newCanonical !== canonical)
241
+ await this.assertRoleNameFree(newDisplayName);
242
+ // Both roster files are read AND rewritten from snapshots — hold their
243
+ // locks across the whole build so a concurrent roster edit can't land in
244
+ // between and be overwritten by the batch.
245
+ await this.locked.withFileLocks(workspaceId, actor, [GROUPS_YAML, ROLES_YAML], () => this.renameGroupLocked(workspaceId, actor, canonical, newCanonical, newDisplayName));
246
+ return this.getRoster();
247
+ }
248
+ async renameGroupLocked(workspaceId, actor, canonical, newCanonical, newDisplayName) {
249
+ const text = (await this.locked.readKbFile(workspaceId, GROUPS_YAML)) ?? '';
250
+ const groupsEdit = this.guardEdit(() => editRenameDisplay(text, canonical, newDisplayName));
251
+ const files = [];
252
+ if (groupsEdit.changed) {
253
+ this.assertLoadable(groupsEdit.text);
254
+ files.push({ repoRel: GROUPS_YAML, content: groupsEdit.text, original: text });
255
+ }
256
+ if (newCanonical !== canonical) {
257
+ // Grant references (bare tokens in access.md + node frontmatter). The
258
+ // rewrite returns each file's ORIGINAL text alongside the new content,
259
+ // so the rollback snapshots reuse the read it already did — no second
260
+ // full-KB pass. Explicit `role/<name>` tokens are untouched by design:
261
+ // they reference the ROLE, not this group.
262
+ const refWrites = await this.references.rewriteReferences(workspaceId, canonical, newDisplayName.trim(), (message, cause) => new GroupsAdminError(message, 422, { cause }));
263
+ for (const w of refWrites) {
264
+ files.push({ repoRel: w.repoRelativePath, content: w.content, original: w.original });
265
+ }
266
+ // Role→group assignments live in roles.yaml as `group:<canonical>` —
267
+ // stored canonical, so they go stale on a canonical-changing rename and
268
+ // the role would silently stop expanding to the group's members. Rewrite
269
+ // them in the SAME atomic commit. Fail closed on a malformed roles.yaml:
270
+ // committing the rename without it would strand any refs it holds.
271
+ const rolesText = await this.locked.readKbFile(workspaceId, ROLES_YAML);
272
+ if (rolesText !== null) {
273
+ let rolesEdit;
274
+ try {
275
+ rolesEdit = renameGroupRefs(rolesText, canonical, newCanonical);
276
+ }
277
+ catch (err) {
278
+ throw new GroupsAdminError(`Cannot rewrite role assignments in ${ROLES_YAML}; rename aborted with no changes`, 422, { cause: err?.message });
279
+ }
280
+ if (rolesEdit.changed) {
281
+ // Same pre-disk no-lockout gate LockingFilesystem would have run —
282
+ // the plain-write path must not lose it (a roles.yaml that fails
283
+ // the resolver's parser is an app-wide admin lockout).
284
+ makeRolesYamlWriteValidator(this.kbDirName)(`${this.kbDirName}/${ROLES_YAML}`, rolesEdit.text);
285
+ files.push({ repoRel: ROLES_YAML, content: rolesEdit.text, original: rolesText });
286
+ }
287
+ }
288
+ }
289
+ if (files.length === 0)
290
+ return;
291
+ // Roster locks are already ours (withFileLocks; strict same-user acquire
292
+ // forbids LockingFilesystem here). Grant-reference candidates are written
293
+ // unlocked — same exposure every reference rewrite has always had.
294
+ await this.locked.writeAndCommitLocked(workspaceId, actor, files, `Rename group ${canonical} → ${newDisplayName.trim()}`);
295
+ this.afterWrite(workspaceId, actor, files.map((f) => f.repoRel));
296
+ }
297
+ /**
298
+ * Connect-time retirement: delete `groups.yaml` in one commit. Called by the
299
+ * SCIM connect flow AFTER the admin confirmed the warning dialog ("groups
300
+ * you don't recreate in the IdP will be lost"). Recovery is a git revert —
301
+ * the file's history keeps every retired group. No-op when the file is
302
+ * already absent.
303
+ */
304
+ async retireManualGroups(actor) {
305
+ const workspaceId = await this.ensureWorkspace();
306
+ let retired = false;
307
+ // Existence check AND delete under the SAME lock: checked outside it, a
308
+ // concurrent create could land groups.yaml right after a "not there"
309
+ // answer (leaving retired manual groups behind to resurrect if the synced
310
+ // file ever goes away), or a concurrent retire could delete it right
311
+ // after a "there" answer and fail this one on ENOENT.
312
+ await this.locked.withFileLocks(workspaceId, actor, [GROUPS_YAML], async () => {
313
+ const original = await this.locked.readKbFile(workspaceId, GROUPS_YAML);
314
+ if (original === null)
315
+ return; // already absent — a no-op retire
316
+ await this.locked.writeAndCommitLocked(workspaceId, actor, [{ repoRel: GROUPS_YAML, content: null, original }], 'Retire manual groups — directory sync connected');
317
+ retired = true;
318
+ });
319
+ if (retired)
320
+ this.afterWrite(workspaceId, actor, [GROUPS_YAML]);
321
+ return retired;
322
+ }
323
+ // ---- Internals ----------------------------------------------------------
324
+ guardEdit(fn) {
325
+ try {
326
+ return fn();
327
+ }
328
+ catch (err) {
329
+ if (err instanceof GroupsEditError)
330
+ throw new GroupsAdminError(err.message, err.status);
331
+ throw err;
332
+ }
333
+ }
334
+ assertLoadable(candidate) {
335
+ const v = validateGroupsFile(candidate, GROUPS_YAML);
336
+ if (!v.ok) {
337
+ throw new GroupsAdminError(`groups.yaml would be invalid: ${v.errors.join('; ')}`, 422);
338
+ }
339
+ }
340
+ async assertManualMode(workspaceId) {
341
+ if ((await this.locked.readKbFile(workspaceId, SYNCED_GROUPS_YAML)) !== null) {
342
+ throw new GroupsAdminError('Groups are synced from your identity provider — manage membership there.', 409, { kind: 'idp-mode' });
343
+ }
344
+ }
345
+ /**
346
+ * Refuse a group name that IS a role. Bare-name precedence (group-first)
347
+ * would make the group win resolution, so this is deliberate FRICTION, not
348
+ * a correctness need: an admin naming a group after a capability role is
349
+ * far more likely shadowing by accident than by intent. IdP-sourced
350
+ * collisions still happen (the sync writer doesn't consult roles.yaml) and
351
+ * precedence resolves them.
352
+ */
353
+ async assertRoleNameFree(displayName) {
354
+ const workspaceId = await this.ensureWorkspace();
355
+ const canonical = canonicalRoleName(displayName);
356
+ const rolesText = (await this.locked.readKbFile(workspaceId, 'roles.yaml')) ?? '';
357
+ let roles;
358
+ try {
359
+ roles = parseRolesModel(rolesText);
360
+ }
361
+ catch {
362
+ // A broken roles.yaml cannot vouch for the name being free — refuse
363
+ // rather than let a group land that may shadow a role once repaired.
364
+ throw new GroupsAdminError('roles.yaml is not readable — fix it before creating groups.', 409);
365
+ }
366
+ if (roles.some((r) => canonicalRoleName(r.displayName) === canonical)) {
367
+ throw new GroupsAdminError(`'${displayName.trim()}' is a role name — name the group something else (a role can be referenced explicitly as 'role/${canonical}').`, 422);
368
+ }
369
+ }
370
+ /**
371
+ * Which roles carry a `group:<canonical>` assignment, per group canonical.
372
+ * Advisory (feeds the roster's `assignedToRoles` warning field): a broken
373
+ * roles.yaml yields an empty map rather than failing the roster.
374
+ */
375
+ async rolesReferencingGroups(workspaceId) {
376
+ const rolesText = (await this.locked.readKbFile(workspaceId, ROLES_YAML)) ?? '';
377
+ const byGroup = new Map();
378
+ let roles;
379
+ try {
380
+ roles = parseRolesModel(rolesText);
381
+ }
382
+ catch {
383
+ return byGroup;
384
+ }
385
+ for (const role of roles) {
386
+ const roleCanonical = canonicalRoleName(role.displayName);
387
+ for (const member of role.members) {
388
+ if (!isGroupRefMember(member))
389
+ continue;
390
+ const groupCanonical = canonicalRoleName(member.slice(GROUP_REF_PREFIX.length));
391
+ const list = byGroup.get(groupCanonical);
392
+ if (list) {
393
+ if (!list.includes(roleCanonical))
394
+ list.push(roleCanonical);
395
+ }
396
+ else {
397
+ byGroup.set(groupCanonical, [roleCanonical]);
398
+ }
399
+ }
400
+ }
401
+ return byGroup;
402
+ }
403
+ async runEdit(actor, pre) {
404
+ const workspaceId = await this.ensureWorkspace();
405
+ await this.assertManualMode(workspaceId);
406
+ return this.locked.withFileLocks(workspaceId, actor, [GROUPS_YAML], () => this.runEditLocked(workspaceId, actor, pre));
407
+ }
408
+ async runEditLocked(workspaceId, actor, pre) {
409
+ // Keep absent (null) distinct from existing-but-empty ('') — rollback
410
+ // must DELETE a file it created, not truncate one that was already there.
411
+ const original = await this.locked.readKbFile(workspaceId, GROUPS_YAML);
412
+ const result = this.guardEdit(() => pre(original ?? ''));
413
+ if (!result.changed)
414
+ return;
415
+ this.assertLoadable(result.text);
416
+ await this.locked.writeAndCommitLocked(workspaceId, actor, [{ repoRel: GROUPS_YAML, content: result.text, original }], `Update ${GROUPS_YAML}`);
417
+ this.afterWrite(workspaceId, actor, [GROUPS_YAML]);
418
+ }
419
+ afterWrite(workspaceId, actor, repoRelPaths) {
420
+ this.accessControl.invalidate(workspaceId);
421
+ // Any write in this batch may have moved grant references (scanned-file
422
+ // rewrites on rename — `.md` AND `.tool`); drop the cached scan so the
423
+ // next roster is fresh.
424
+ if (repoRelPaths.some(hasAccessFrontmatterExtension))
425
+ this.references.invalidate(workspaceId);
426
+ if (!this.eventBus)
427
+ return;
428
+ for (const repoRel of repoRelPaths) {
429
+ this.eventBus.emit({
430
+ kind: 'file-changed',
431
+ workspaceId,
432
+ branch: this.defaultBranch,
433
+ path: `${this.kbDirName}/${repoRel}`,
434
+ newSha: null,
435
+ byUserId: actor.id,
436
+ byUserName: actor.name,
437
+ });
438
+ }
439
+ this.eventBus.emit({ kind: 'fs-tree-changed', workspaceId, branch: this.defaultBranch });
440
+ }
441
+ }
442
+ //# sourceMappingURL=groups-admin.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"groups-admin.service.js","sourceRoot":"","sources":["../../../src/modules/access/groups-admin.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Pure parse → edit → re-emit editor for `groups.yaml` — the manual-mode
3
+ * group file. Mirrors `roles-edit.ts` (same re-emit rationale: no body, no
4
+ * comments to preserve, deterministic canonical output), with two deliberate
5
+ * differences:
6
+ *
7
+ * - No Admin invariants — groups carry no capabilities, so there is no
8
+ * lockout class of mistake here.
9
+ * - Members are EMAILS ONLY. `group:` references are a roles.yaml grammar
10
+ * (a role assigned to a group); a group containing a group is nesting,
11
+ * which is a deliberate non-goal.
12
+ *
13
+ * Policy that needs more than this file's text — IdP-mode gating, collision
14
+ * with role names — lives in `groups-admin.service.ts`.
15
+ */
16
+ /** Bad-input failure when editing groups.yaml. */
17
+ export declare class GroupsEditError extends Error {
18
+ readonly status: number;
19
+ constructor(message: string, status?: number);
20
+ }
21
+ export interface GroupModel {
22
+ displayName: string;
23
+ /** canonicalised, de-duplicated, in first-seen order */
24
+ members: string[];
25
+ }
26
+ export type GroupsModel = GroupModel[];
27
+ export interface GroupsEditResult {
28
+ text: string;
29
+ changed: boolean;
30
+ }
31
+ /** Thin assert wrapper over the SHARED name-safety predicate (`group-files.ts`
32
+ * `unsafeNameReason`) — one namespace, one set of rules, including the
33
+ * reserved `role/` prefix. */
34
+ export declare function assertSafeGroupDisplayName(displayName: string): void;
35
+ /**
36
+ * Parse groups.yaml into the ordered model; `[]` for an empty/missing file.
37
+ *
38
+ * DERIVED from the resolver's own `parseGroupsFile` — one parser for the
39
+ * grammar, so the editor's skip rules (empty/reserved/duplicate names,
40
+ * malformed member emails) can never drift from the resolver's: the admin
41
+ * roster and the edit surface must never show an entry the resolver ignores,
42
+ * or an admin "manages" a group that grants nothing. Consequence, deliberate:
43
+ * the next edit's re-emit garbage-collects those dead entries from the file.
44
+ * Structural failures throw GroupsEditError (422) — the caller reads under
45
+ * the lock, so a broken on-disk file is an operator problem surfaced loudly,
46
+ * not a silent reset.
47
+ */
48
+ export declare function parseGroupsModel(text: string): GroupsModel;
49
+ /** Deterministic canonical emit; empty groups as `Name: []`. */
50
+ export declare function emitGroupsModel(model: GroupsModel): string;
51
+ export declare function createGroup(text: string, displayName: string): GroupsEditResult;
52
+ export declare function deleteGroup(text: string, canonical: string): GroupsEditResult;
53
+ export declare function addGroupMember(text: string, canonical: string, rawEmail: string): GroupsEditResult;
54
+ export declare function removeGroupMember(text: string, canonical: string, rawEmail: string): GroupsEditResult;
55
+ /** Rename a group's display name (canonical may change — service layer owns
56
+ * the reference rewrite when it does). */
57
+ export declare function renameGroupDisplay(text: string, canonical: string, newDisplayName: string): GroupsEditResult;
58
+ //# sourceMappingURL=groups-edit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"groups-edit.d.ts","sourceRoot":"","sources":["../../../src/modules/access/groups-edit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAWH,kDAAkD;AAClD,qBAAa,eAAgB,SAAQ,KAAK;IACxC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;gBACZ,OAAO,EAAE,MAAM,EAAE,MAAM,SAAM;CAK1C;AAED,MAAM,WAAW,UAAU;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,wDAAwD;IACxD,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,MAAM,WAAW,GAAG,UAAU,EAAE,CAAC;AAEvC,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;+BAE+B;AAC/B,wBAAgB,0BAA0B,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAKpE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAQ1D;AAED,gEAAgE;AAChE,wBAAgB,eAAe,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,CAW1D;AAiBD,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,gBAAgB,CAY/E;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,gBAAgB,CAM7E;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,gBAAgB,CAqBlG;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,gBAAgB,CASrG;AAED;2CAC2C;AAC3C,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EACjB,cAAc,EAAE,MAAM,GACrB,gBAAgB,CAclB"}