@bevel-software/platform-core-backend 0.10.0 → 0.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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
@@ -1,41 +1,35 @@
1
1
  /**
2
- * Admin Roles & Members service — full CRUD on the default-branch `roles.yaml`.
2
+ * Admin Roles & Members service — MEMBERSHIP editing on the default-branch
3
+ * `roles.yaml`.
3
4
  *
4
- * This is the deferred "Slice 2" of `manage-access-grant-revoke`: the in-product
5
- * surface for creating/deleting roles, adding/removing members, and renaming a
6
- * role. It is the ONLY writer of `roles.yaml` outside a hand-edit, and it is
7
- * built around one non-negotiable safety property:
5
+ * Roles are APP-DEFINED capabilities (see `capability-registry.ts`): the
6
+ * product decides which roles exist; users can never create, rename, or
7
+ * delete one — those editors are gone. What remains admin-editable is
8
+ * MEMBERSHIP: adding/removing individual emails and assigning/unassigning
9
+ * groups (`group:<canonical>` members), on capability roles and on LEGACY
10
+ * people-set roles alike (pre-split roles keep resolving and stay editable;
11
+ * `convertRoleToGroup` is their migration path).
12
+ *
13
+ * It is the primary in-app writer of `roles.yaml` (the groups admin also
14
+ * rewrites its `group:` refs on group rename/delete, through the same shared
15
+ * validated pipeline), and it is built around one non-negotiable safety
16
+ * property:
8
17
  *
9
18
  * roles.yaml has NO admin-rescue (canWrite('roles.yaml') is a pure isAdmin
10
19
  * check) and loadModel HARD-THROWS on a parse failure (which isAdmin swallows
11
20
  * into false for EVERYONE). So a single malformed write = a permanent,
12
21
  * app-wide, in-app-unrecoverable admin lockout. Every mutation therefore runs
13
22
  * the candidate text through the resolver's OWN parser (validateRolesYaml)
14
- * before a byte hits disk; on any error it writes nothing.
15
- *
16
- * Two write paths, both gated by the same friendly pre-checks + validate gate:
17
- *
18
- * Single-file ops (create/delete role, add/remove member) — `runEdit`:
19
- * pre-check roles.yaml isn't locked → RolesAdminError 409
20
- * read CURRENT roles.yaml → parse → edit-in-memory → re-emit candidate
21
- * validateRolesYaml(candidate) → RolesAdminError 422, write nothing
22
- * LockingFilesystem.writeFile(roles.yaml) ← acquire → write → release;
23
- * release IS the commit + push (default per-file summary), so the commit
24
- * lands a beat after the write — same pipeline the human editor + agent
25
- * use. The HTTP roster reads the (synchronously written) working tree.
23
+ * before a byte hits disk; on any error it writes nothing. That parser also
24
+ * carries the Admin invariant — at least one DIRECT email member, group
25
+ * references alone are never enough — so no write path can land a
26
+ * directory-dependent Admin.
26
27
  *
27
- * Rename (`renameRole`) — keeps an ATOMIC multi-file special case because it
28
- * rewrites roles.yaml + every reference in ONE commit (a partial rewrite =
29
- * silent access drop):
30
- * pre-check roles.yaml isn't locked → RolesAdminError 409
31
- * parse + rewrite refs (fail-closed) → validate gate
32
- * LockingFilesystem.writeFiles(roles.yaml + refs) ← acquires every path's
33
- * lock, commits the curated set as ONE change (via the workflow service's
34
- * commitChanges), then releases without a per-file commit.
35
- *
36
- * All reads and writes target the DEFAULT branch — the file admin status itself
37
- * derives from. Editing any other branch's copy would show a roster that
38
- * doesn't match who is actually an admin.
28
+ * Lock/commit plumbing is the SHARED `AdminLockedCommits` helper (one
29
+ * implementation for this service and the groups admin — the twins diverged
30
+ * once into a real bug). Reference scanning is the SHARED
31
+ * `KbReferenceScanner` (cached, so roster mutations don't rerun the full-KB
32
+ * sweep the GET pays for).
39
33
  */
40
34
  import type { WorkflowEventBus } from '../workflow/event-bus.js';
41
35
  import type { AuthUser, IWorkspaceService, IWorkflowService } from '@bevel-software/platform-shared';
@@ -49,12 +43,27 @@ export declare class RolesAdminError extends WorkflowDomainError {
49
43
  export interface RoleRosterEntry {
50
44
  canonical: string;
51
45
  displayName: string;
46
+ /** Individual member EMAILS (group assignments are split into `groups`). */
52
47
  members: string[];
48
+ /** Canonical names of groups this role is assigned to (`group:` members). */
49
+ groups: string[];
53
50
  isAdmin: boolean;
54
51
  /**
55
- * Every access rule referencing this role — folder `access.md` AND node
56
- * frontmatter. Sound: this is the SAME scan the rename rewrite uses, so the
57
- * delete warning's count matches what a delete/rename would actually touch.
52
+ * Registry metadata when this is a CAPABILITY role (Admin today); null for
53
+ * a legacy pre-split people-set role, which the UI flags with a
54
+ * convert-to-group action.
55
+ */
56
+ capability: {
57
+ description: string;
58
+ groupAssignable: boolean;
59
+ } | null;
60
+ /**
61
+ * Every access rule referencing this ROLE — folder `access.md` AND node
62
+ * frontmatter. `role/<name>` hits always count; a BARE-token hit counts
63
+ * only when no group shadows the name (bare tokens resolve group-first, so
64
+ * a shadowed bare grant is the GROUP's reference, listed on the groups
65
+ * roster instead). Sound: this is the SAME scan the group rename/delete
66
+ * rewrites use.
58
67
  */
59
68
  referencedBy: {
60
69
  path: string;
@@ -94,6 +103,10 @@ export declare class RolesAdminService {
94
103
  * recovery exists to escape.
95
104
  */
96
105
  private readonly recoveryAdmins;
106
+ /** Shared lock/commit plumbing — see module doc. */
107
+ private readonly locked;
108
+ /** Shared cached reference scanner — see module doc. */
109
+ private readonly references;
97
110
  constructor(workspaceService: IWorkspaceService, workflowService: IWorkflowService, accessControl: IAccessControl, kbDirName: string,
98
111
  /**
99
112
  * The branch whose roles.yaml is authoritative for admin status, read
@@ -131,29 +144,13 @@ export declare class RolesAdminService {
131
144
  private emitWrites;
132
145
  /** Ensure the authoritative clone exists, then return its workspace id. */
133
146
  private ensureWorkspace;
134
- private repoDir;
135
- /**
136
- * A lock-aware filesystem scoped to `actor` — writes auto-commit + push as the
137
- * actor on release, through the same pipeline as the human editor and agent.
138
- * Built per-op because the lock context captures the acting user.
139
- */
140
- private lockingFsForActor;
141
147
  /**
142
148
  * Fail fast with a friendly 409 if roles.yaml is already locked by someone
143
- * else. The actual lock is taken by the LockingFilesystem write that follows;
144
- * this only preserves the explicit "being edited by X" contract (the raw
145
- * LockingFilesystem contention error is status-less and would surface as 500).
149
+ * else. The actual lock is taken by the write that follows; this only
150
+ * preserves the explicit "being edited by X" contract (the raw contention
151
+ * error is status-less and would surface as 500).
146
152
  */
147
153
  private assertRolesUnlocked;
148
- /**
149
- * Run a lock-aware filesystem write, mapping its status-less contention error
150
- * ("Skipped editing … — locked by …") to the friendly 409. {@link
151
- * assertRolesUnlocked} pre-checks the common case (and names the holder), but
152
- * it's a TOCTOU check — another admin can grab the lock between it and the
153
- * actual acquire inside the write. This catches that race so contention always
154
- * surfaces as a 409, never an unhandled 500.
155
- */
156
- private mapLockContention;
157
154
  private readRolesYaml;
158
155
  /** The roster: every role on the default branch with members + references. */
159
156
  getRoster(): Promise<RoleRosterEntry[]>;
@@ -184,85 +181,57 @@ export declare class RolesAdminService {
184
181
  * through precisely when the file is broken.
185
182
  */
186
183
  recover(actor: AuthUser): Promise<RoleRosterEntry[]>;
187
- createRole(actor: AuthUser, displayName: string): Promise<RoleRosterEntry[]>;
188
- deleteRole(actor: AuthUser, canonical: string): Promise<RoleRosterEntry[]>;
189
184
  addMember(actor: AuthUser, canonical: string, email: string): Promise<RoleRosterEntry[]>;
190
185
  /**
191
- * Remove a member. Refuses to empty the Admin role (422). Removing the
192
- * caller's OWN last Admin membership requires `confirm` (409 otherwise) — no
193
- * silent self-lockout, since admin status is read live from this file.
186
+ * Remove a member. Refuses to remove the Admin role's LAST DIRECT EMAIL
187
+ * (422) — the kept invariant: whatever groups Admin references, at least
188
+ * one directory-independent email member must remain, or a broken IdP
189
+ * connection becomes an admin lockout. Removing the caller's OWN last
190
+ * Admin membership requires `confirm` (409 otherwise) — no silent
191
+ * self-lockout, since admin status is read live from this file.
194
192
  */
195
193
  removeMember(actor: AuthUser, canonical: string, email: string, confirm: boolean): Promise<RoleRosterEntry[]>;
196
194
  /**
197
- * Rename a role's display name.
198
- * - canonical UNCHANGED (casing/whitespace) → single roles.yaml edit.
199
- * - canonical CHANGES → rewrite every genuine role reference in access.md +
200
- * node frontmatter AND roles.yaml in ONE atomic commit.
201
- * - Admin canonical → non-admin canonical is refused (400): it would break
202
- * isAdminEmail's roles.has('admin') lookup. Casing-only Admin OK.
195
+ * Assign a role to a group — Admin included (the parse-time invariant keeps
196
+ * at least one direct email on Admin, so a group ref can never be its only
197
+ * membership).
198
+ *
199
+ * The ref must name a group the ACTIVE source knows: mergeGroupsIntoRoles
200
+ * ignores an unknown ref with only a log warning, so a typo here would be
201
+ * accepted and then silently grant the role to nobody. The check is
202
+ * BEST-EFFORT validation, not a race-free guarantee: the manual group
203
+ * file's lock is held across validate + write (so a concurrent groups-page
204
+ * deletion/rename can't invalidate the ref mid-flight), but the IdP sync
205
+ * writer commits through its own lock — a provisioning push can retire the
206
+ * group between this check and the next sync. That is fine by design: a
207
+ * dangling ref resolves to nothing, with a resolver warning naming it.
208
+ */
209
+ assignGroup(actor: AuthUser, canonical: string, groupName: string): Promise<RoleRosterEntry[]>;
210
+ unassignGroup(actor: AuthUser, canonical: string, groupName: string): Promise<RoleRosterEntry[]>;
211
+ /**
212
+ * Convert a LEGACY people-set role into a manual group — the migration the
213
+ * roles/groups split defines: "Product" was never a capability, it was a
214
+ * team. Atomic two-file move (roles.yaml loses the role, groups.yaml gains
215
+ * the group with the same members) in ONE commit; grant references keep
216
+ * working untouched because the NAME does not change.
217
+ *
218
+ * Refusals: capability roles (they ARE roles — Admin included), roles with
219
+ * group assignments (unwind those first: a group containing a group is
220
+ * nesting), IdP mode (groups are managed in the identity provider — this
221
+ * would write a retired file), and a groups.yaml name collision.
203
222
  */
204
- renameRole(actor: AuthUser, canonical: string, newDisplayName: string): Promise<RoleRosterEntry[]>;
223
+ convertRoleToGroup(actor: AuthUser, canonical: string): Promise<RoleRosterEntry[]>;
205
224
  /** Map a RolesEditError to a RolesAdminError (preserving status). */
206
225
  private guardEdit;
207
226
  private assertLoadable;
208
227
  /**
209
- * Single-file roles.yaml mutation. `pre` produces the candidate (and may throw
210
- * RolesAdminError for invariant violations before any write). Skips on a no-op.
211
- *
212
- * The write goes through a {@link LockingFilesystem}: acquiring the per-file
213
- * lock, writing, then releasing — and release IS the commit + push (attributed
214
- * to `actor`), the same pipeline the human editor and agent use. So unlike the
215
- * rename's synchronous atomic commit, the git commit here lands on release
216
- * (a beat after the write); the HTTP response reads the working tree, which is
217
- * already on disk, so the returned roster is correct.
228
+ * Single-file roles.yaml mutation under the shared file-lock/commit helper.
229
+ * `pre` produces the candidate (and may throw RolesAdminError for invariant
230
+ * violations before any write). Skips on a no-op. Every candidate passes
231
+ * the resolver's own parser (assertLoadable) plus the pre-disk validator
232
+ * before a byte lands.
218
233
  */
219
234
  private runEdit;
220
- /**
221
- * Find every genuine role reference to `oldCanonical` across folder access.md
222
- * AND node frontmatter, and rewrite each to `newDisplayName`. Reference-aware:
223
- * a line is rewritten ONLY if it PARSES as a role entry whose canonical name
224
- * == oldCanonical. Prose, comments, `Name <email>` user entries, other keys,
225
- * and substrings never match. Returns the repo-relative writes to commit.
226
- */
227
- private rewriteRoleReferences;
228
- /**
229
- * Repo-relative paths of every file that could carry a role reference: all
230
- * `access.md` files plus every `.md` node (its own frontmatter). Sourced from
231
- * the workspace file tree (which already skips `.git` and honours
232
- * `.bevelignore`), filtered to `.md` under the KB dir and returned bare
233
- * repo-relative. (`roles.yaml` isn't `.md`, so it's excluded; the rename
234
- * commits it separately.) Under save=share the working tree matches the
235
- * committed set, so this is the same candidate list git-tracking would give.
236
- */
237
- private collectCandidateFiles;
238
- /**
239
- * Sound scan of EVERY `.md` (folder `access.md` + node frontmatter)
240
- * for genuine role references, indexed by canonical role name. Shares the
241
- * candidate set and the config-region role-entry parse with the rename
242
- * rewrite (`findRoleRefsInText` / `rewriteRoleTokensInText`), so the delete
243
- * warning and the rename rewrite see the SAME references — the warning can no
244
- * longer undercount frontmatter the rename would touch. A file we cannot read
245
- * is skipped (this is an advisory read, not the atomic write path): missing a
246
- * reference here only weakens the warning, it cannot drop access.
247
- */
248
- private scanRoleReferences;
235
+ private runEditLocked;
249
236
  }
250
- /** Every genuine role reference in `text`'s config region, as {role, verb}. */
251
- export declare function findRoleRefsInText(text: string, isMarkdown?: boolean): {
252
- role: string;
253
- verb: string;
254
- }[];
255
- /**
256
- * Rewrite every CONFIG-REGION line that PARSES as a role reference whose
257
- * canonical name == `oldCanonical`, replacing the role token with
258
- * `newDisplayName` (preserving any leading `deny ` and indentation). Only the
259
- * frontmatter block of a markdown file is touched — the body is left
260
- * byte-for-byte intact, so a prose line like `- Sales` is never corrupted.
261
- * Lines that don't parse as a matching role entry (user entries, other keys,
262
- * substrings) are also untouched. Exported for test.
263
- *
264
- * `isMarkdown` (default true) marks files that carry a markdown body below the
265
- * frontmatter; pass false only for a pure-config file with no body.
266
- */
267
- export declare function rewriteRoleTokensInText(text: string, oldCanonical: string, newDisplayName: string, isMarkdown?: boolean): string;
268
237
  //# sourceMappingURL=roles-admin.service.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"roles-admin.service.d.ts","sourceRoot":"","sources":["../../../src/modules/access/roles-admin.service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"roles-admin.service.d.ts","sourceRoot":"","sources":["../../../src/modules/access/roles-admin.service.ts"],"names":[],"mappings":"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"}