@bevel-software/platform-core-backend 0.19.0 → 0.20.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 (255) hide show
  1. package/dist/core/create-core-server.d.ts.map +1 -1
  2. package/dist/core/create-core-server.js +22 -0
  3. package/dist/core/create-core-server.js.map +1 -1
  4. package/dist/core/create-core-services.d.ts +3 -0
  5. package/dist/core/create-core-services.d.ts.map +1 -1
  6. package/dist/core/create-core-services.js +10 -1
  7. package/dist/core/create-core-services.js.map +1 -1
  8. package/dist/modules/audit/agent-audit.service.d.ts +54 -0
  9. package/dist/modules/audit/agent-audit.service.d.ts.map +1 -0
  10. package/dist/modules/audit/agent-audit.service.js +290 -0
  11. package/dist/modules/audit/agent-audit.service.js.map +1 -0
  12. package/dist/modules/audit/agent-rest-audit.middleware.d.ts +40 -0
  13. package/dist/modules/audit/agent-rest-audit.middleware.d.ts.map +1 -0
  14. package/dist/modules/audit/agent-rest-audit.middleware.js +75 -0
  15. package/dist/modules/audit/agent-rest-audit.middleware.js.map +1 -0
  16. package/dist/modules/audit/audit.contract.d.ts +145 -0
  17. package/dist/modules/audit/audit.contract.d.ts.map +1 -0
  18. package/dist/modules/audit/audit.contract.js +35 -0
  19. package/dist/modules/audit/audit.contract.js.map +1 -0
  20. package/dist/modules/audit/audit.routes.d.ts +21 -0
  21. package/dist/modules/audit/audit.routes.d.ts.map +1 -0
  22. package/dist/modules/audit/audit.routes.js +157 -0
  23. package/dist/modules/audit/audit.routes.js.map +1 -0
  24. package/dist/modules/audit/event-classifier.d.ts +52 -0
  25. package/dist/modules/audit/event-classifier.d.ts.map +1 -0
  26. package/dist/modules/audit/event-classifier.js +123 -0
  27. package/dist/modules/audit/event-classifier.js.map +1 -0
  28. package/dist/modules/audit/index.d.ts +7 -0
  29. package/dist/modules/audit/index.d.ts.map +1 -0
  30. package/dist/modules/audit/index.js +7 -0
  31. package/dist/modules/audit/index.js.map +1 -0
  32. package/dist/modules/audit/request-audit.d.ts +57 -0
  33. package/dist/modules/audit/request-audit.d.ts.map +1 -0
  34. package/dist/modules/audit/request-audit.js +117 -0
  35. package/dist/modules/audit/request-audit.js.map +1 -0
  36. package/dist/modules/database/core-schema.d.ts +414 -0
  37. package/dist/modules/database/core-schema.d.ts.map +1 -1
  38. package/dist/modules/database/core-schema.js +99 -0
  39. package/dist/modules/database/core-schema.js.map +1 -1
  40. package/dist/modules/diff/diff.service.d.ts.map +1 -1
  41. package/dist/modules/diff/diff.service.js +6 -1
  42. package/dist/modules/diff/diff.service.js.map +1 -1
  43. package/dist/modules/kb-fs/git-guarded-filesystem.d.ts +9 -1
  44. package/dist/modules/kb-fs/git-guarded-filesystem.d.ts.map +1 -1
  45. package/dist/modules/kb-fs/git-guarded-filesystem.js +16 -3
  46. package/dist/modules/kb-fs/git-guarded-filesystem.js.map +1 -1
  47. package/dist/modules/mcp/mcp-auth.middleware.d.ts +1 -0
  48. package/dist/modules/mcp/mcp-auth.middleware.d.ts.map +1 -1
  49. package/dist/modules/mcp/mcp-auth.middleware.js +32 -0
  50. package/dist/modules/mcp/mcp-auth.middleware.js.map +1 -1
  51. package/dist/modules/mcp/mcp.routes.d.ts.map +1 -1
  52. package/dist/modules/mcp/mcp.routes.js +8 -1
  53. package/dist/modules/mcp/mcp.routes.js.map +1 -1
  54. package/dist/modules/mcp/mcp.service.d.ts +9 -1
  55. package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
  56. package/dist/modules/mcp/mcp.service.js +48 -5
  57. package/dist/modules/mcp/mcp.service.js.map +1 -1
  58. package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts +59 -0
  59. package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts.map +1 -1
  60. package/dist/modules/mcp/oauth/bevel-oauth-provider.js +180 -8
  61. package/dist/modules/mcp/oauth/bevel-oauth-provider.js.map +1 -1
  62. package/dist/modules/settings/deployment-settings.service.d.ts +10 -2
  63. package/dist/modules/settings/deployment-settings.service.d.ts.map +1 -1
  64. package/dist/modules/settings/deployment-settings.service.js +32 -6
  65. package/dist/modules/settings/deployment-settings.service.js.map +1 -1
  66. package/dist/modules/tool-auth/external-api-key.interface.d.ts +21 -5
  67. package/dist/modules/tool-auth/external-api-key.interface.d.ts.map +1 -1
  68. package/dist/modules/tool-auth/external-api-key.interface.js.map +1 -1
  69. package/dist/modules/tool-auth/external-api-key.service.d.ts +1 -0
  70. package/dist/modules/tool-auth/external-api-key.service.d.ts.map +1 -1
  71. package/dist/modules/tool-auth/external-api-key.service.js +22 -9
  72. package/dist/modules/tool-auth/external-api-key.service.js.map +1 -1
  73. package/dist/modules/tool-auth/internal-token.service.d.ts +10 -0
  74. package/dist/modules/tool-auth/internal-token.service.d.ts.map +1 -1
  75. package/dist/modules/tool-auth/internal-token.service.js +1 -0
  76. package/dist/modules/tool-auth/internal-token.service.js.map +1 -1
  77. package/dist/modules/tool-auth/tool-auth.middleware.d.ts +8 -0
  78. package/dist/modules/tool-auth/tool-auth.middleware.d.ts.map +1 -1
  79. package/dist/modules/tool-auth/tool-auth.middleware.js +9 -1
  80. package/dist/modules/tool-auth/tool-auth.middleware.js.map +1 -1
  81. package/dist/modules/workspace/workspace.service.d.ts +8 -0
  82. package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
  83. package/dist/modules/workspace/workspace.service.js +9 -0
  84. package/dist/modules/workspace/workspace.service.js.map +1 -1
  85. package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
  86. package/dist/modules/workspace/workspace.tools.js +49 -13
  87. package/dist/modules/workspace/workspace.tools.js.map +1 -1
  88. package/dist/shared/git-internals.d.ts +7 -1
  89. package/dist/shared/git-internals.d.ts.map +1 -1
  90. package/dist/shared/git-internals.js +67 -9
  91. package/dist/shared/git-internals.js.map +1 -1
  92. package/migrations/0012_agent_audit.sql +41 -0
  93. package/migrations/0013_agent_identity_and_key_soft_delete.sql +29 -0
  94. package/migrations/meta/0012_snapshot.json +2247 -0
  95. package/migrations/meta/0013_snapshot.json +2259 -0
  96. package/migrations/meta/_journal.json +14 -0
  97. package/package.json +4 -4
  98. package/src/core/create-core-server.ts +28 -0
  99. package/src/core/create-core-services.ts +13 -0
  100. package/src/modules/audit/__tests__/agent-audit.service.test.ts +370 -0
  101. package/src/modules/audit/__tests__/agent-rest-audit.middleware.test.ts +113 -0
  102. package/src/modules/audit/__tests__/audit.routes.test.ts +201 -0
  103. package/src/modules/audit/__tests__/event-classifier.test.ts +121 -0
  104. package/src/modules/audit/agent-audit.service.ts +316 -0
  105. package/src/modules/audit/agent-rest-audit.middleware.ts +83 -0
  106. package/src/modules/audit/audit.contract.ts +164 -0
  107. package/src/modules/audit/audit.routes.ts +168 -0
  108. package/src/modules/audit/event-classifier.ts +155 -0
  109. package/src/modules/audit/index.ts +14 -0
  110. package/src/modules/audit/request-audit.ts +141 -0
  111. package/src/modules/database/core-schema.ts +104 -0
  112. package/src/modules/diff/diff.service.ts +6 -1
  113. package/src/modules/kb-fs/git-guarded-filesystem.ts +16 -3
  114. package/src/modules/mcp/__tests__/bevel-oauth-provider.test.ts +183 -13
  115. package/src/modules/mcp/__tests__/mcp-auth.middleware.test.ts +60 -2
  116. package/src/modules/mcp/__tests__/mcp.routes.local-token.test.ts +24 -4
  117. package/src/modules/mcp/__tests__/mcp.routes.stateless.test.ts +1 -1
  118. package/src/modules/mcp/__tests__/mcp.service.test.ts +120 -3
  119. package/src/modules/mcp/mcp-auth.middleware.ts +31 -0
  120. package/src/modules/mcp/mcp.routes.ts +11 -1
  121. package/src/modules/mcp/mcp.service.ts +62 -4
  122. package/src/modules/mcp/oauth/bevel-oauth-provider.ts +184 -6
  123. package/src/modules/settings/__tests__/deployment-settings.service.test.ts +35 -0
  124. package/src/modules/settings/deployment-settings.service.ts +47 -8
  125. package/src/modules/tool-auth/__tests__/external-api-key.service.test.ts +29 -8
  126. package/src/modules/tool-auth/__tests__/internal-token.service.test.ts +6 -0
  127. package/src/modules/tool-auth/external-api-key.interface.ts +22 -5
  128. package/src/modules/tool-auth/external-api-key.service.ts +25 -8
  129. package/src/modules/tool-auth/internal-token.service.ts +11 -0
  130. package/src/modules/tool-auth/tool-auth.middleware.ts +17 -1
  131. package/src/modules/workspace/__tests__/git-internals.security.test.ts +135 -44
  132. package/src/modules/workspace/workspace.service.ts +9 -0
  133. package/src/modules/workspace/workspace.tools.ts +47 -11
  134. package/src/shared/__tests__/git-internals.test.ts +41 -0
  135. package/src/shared/git-internals.ts +61 -6
  136. package/dist/modules/access/access-errors.d.ts +0 -34
  137. package/dist/modules/access/access-errors.d.ts.map +0 -1
  138. package/dist/modules/access/access-errors.js +0 -40
  139. package/dist/modules/access/access-errors.js.map +0 -1
  140. package/dist/modules/access/access-splice.d.ts +0 -140
  141. package/dist/modules/access/access-splice.d.ts.map +0 -1
  142. package/dist/modules/access/access-splice.js +0 -389
  143. package/dist/modules/access/access-splice.js.map +0 -1
  144. package/dist/modules/access/group-files.d.ts +0 -83
  145. package/dist/modules/access/group-files.d.ts.map +0 -1
  146. package/dist/modules/access/group-files.js +0 -167
  147. package/dist/modules/access/group-files.js.map +0 -1
  148. package/dist/modules/access/kb-read-filter.d.ts +0 -41
  149. package/dist/modules/access/kb-read-filter.d.ts.map +0 -1
  150. package/dist/modules/access/kb-read-filter.js +0 -60
  151. package/dist/modules/access/kb-read-filter.js.map +0 -1
  152. package/dist/modules/access/render-roles-yaml.d.ts +0 -22
  153. package/dist/modules/access/render-roles-yaml.d.ts.map +0 -1
  154. package/dist/modules/access/render-roles-yaml.js +0 -56
  155. package/dist/modules/access/render-roles-yaml.js.map +0 -1
  156. package/dist/modules/access/roles-yaml-guard.d.ts +0 -56
  157. package/dist/modules/access/roles-yaml-guard.d.ts.map +0 -1
  158. package/dist/modules/access/roles-yaml-guard.js +0 -79
  159. package/dist/modules/access/roles-yaml-guard.js.map +0 -1
  160. package/dist/modules/diff/diff-paths.d.ts +0 -2
  161. package/dist/modules/diff/diff-paths.d.ts.map +0 -1
  162. package/dist/modules/diff/diff-paths.js +0 -9
  163. package/dist/modules/diff/diff-paths.js.map +0 -1
  164. package/dist/modules/groups/group-provision.service.d.ts +0 -130
  165. package/dist/modules/groups/group-provision.service.d.ts.map +0 -1
  166. package/dist/modules/groups/group-provision.service.js +0 -288
  167. package/dist/modules/groups/group-provision.service.js.map +0 -1
  168. package/dist/modules/groups/groups.contract.d.ts +0 -106
  169. package/dist/modules/groups/groups.contract.d.ts.map +0 -1
  170. package/dist/modules/groups/groups.contract.js +0 -36
  171. package/dist/modules/groups/groups.contract.js.map +0 -1
  172. package/dist/modules/groups/groups.routes.d.ts +0 -42
  173. package/dist/modules/groups/groups.routes.d.ts.map +0 -1
  174. package/dist/modules/groups/groups.routes.js +0 -379
  175. package/dist/modules/groups/groups.routes.js.map +0 -1
  176. package/dist/modules/groups/groups.service.d.ts +0 -60
  177. package/dist/modules/groups/groups.service.d.ts.map +0 -1
  178. package/dist/modules/groups/groups.service.js +0 -172
  179. package/dist/modules/groups/groups.service.js.map +0 -1
  180. package/dist/modules/groups/index.d.ts +0 -7
  181. package/dist/modules/groups/index.d.ts.map +0 -1
  182. package/dist/modules/groups/index.js +0 -6
  183. package/dist/modules/groups/index.js.map +0 -1
  184. package/dist/modules/groups/join-proposals.d.ts +0 -53
  185. package/dist/modules/groups/join-proposals.d.ts.map +0 -1
  186. package/dist/modules/groups/join-proposals.js +0 -67
  187. package/dist/modules/groups/join-proposals.js.map +0 -1
  188. package/dist/modules/groups/join-requests.service.d.ts +0 -81
  189. package/dist/modules/groups/join-requests.service.d.ts.map +0 -1
  190. package/dist/modules/groups/join-requests.service.js +0 -135
  191. package/dist/modules/groups/join-requests.service.js.map +0 -1
  192. package/dist/modules/mcp/mcp-session-store.d.ts +0 -74
  193. package/dist/modules/mcp/mcp-session-store.d.ts.map +0 -1
  194. package/dist/modules/mcp/mcp-session-store.js +0 -131
  195. package/dist/modules/mcp/mcp-session-store.js.map +0 -1
  196. package/dist/modules/workflow/file-change-notifier.d.ts +0 -38
  197. package/dist/modules/workflow/file-change-notifier.d.ts.map +0 -1
  198. package/dist/modules/workflow/file-change-notifier.js +0 -22
  199. package/dist/modules/workflow/file-change-notifier.js.map +0 -1
  200. package/dist/modules/workflow/git/branch-name.d.ts +0 -10
  201. package/dist/modules/workflow/git/branch-name.d.ts.map +0 -1
  202. package/dist/modules/workflow/git/branch-name.js +0 -76
  203. package/dist/modules/workflow/git/branch-name.js.map +0 -1
  204. package/dist/modules/workflow/git/clone-config.d.ts +0 -61
  205. package/dist/modules/workflow/git/clone-config.d.ts.map +0 -1
  206. package/dist/modules/workflow/git/clone-config.js +0 -69
  207. package/dist/modules/workflow/git/clone-config.js.map +0 -1
  208. package/dist/modules/workflow/git/mutex.d.ts +0 -11
  209. package/dist/modules/workflow/git/mutex.d.ts.map +0 -1
  210. package/dist/modules/workflow/git/mutex.js +0 -23
  211. package/dist/modules/workflow/git/mutex.js.map +0 -1
  212. package/dist/modules/workflow/locking-filesystem.d.ts +0 -137
  213. package/dist/modules/workflow/locking-filesystem.d.ts.map +0 -1
  214. package/dist/modules/workflow/locking-filesystem.js +0 -553
  215. package/dist/modules/workflow/locking-filesystem.js.map +0 -1
  216. package/dist/modules/workflow/read-only-filesystem.d.ts +0 -24
  217. package/dist/modules/workflow/read-only-filesystem.d.ts.map +0 -1
  218. package/dist/modules/workflow/read-only-filesystem.js +0 -39
  219. package/dist/modules/workflow/read-only-filesystem.js.map +0 -1
  220. package/dist/modules/workflow/workflow.errors.d.ts +0 -197
  221. package/dist/modules/workflow/workflow.errors.d.ts.map +0 -1
  222. package/dist/modules/workflow/workflow.errors.js +0 -298
  223. package/dist/modules/workflow/workflow.errors.js.map +0 -1
  224. package/dist/modules/workspace/bevel-ignore.d.ts +0 -30
  225. package/dist/modules/workspace/bevel-ignore.d.ts.map +0 -1
  226. package/dist/modules/workspace/bevel-ignore.js +0 -61
  227. package/dist/modules/workspace/bevel-ignore.js.map +0 -1
  228. package/dist/modules/workspace/kb-seed.interface.d.ts +0 -36
  229. package/dist/modules/workspace/kb-seed.interface.d.ts.map +0 -1
  230. package/dist/modules/workspace/kb-seed.interface.js +0 -2
  231. package/dist/modules/workspace/kb-seed.interface.js.map +0 -1
  232. package/dist/modules/workspace/kb-seed.service.d.ts +0 -125
  233. package/dist/modules/workspace/kb-seed.service.d.ts.map +0 -1
  234. package/dist/modules/workspace/kb-seed.service.js +0 -535
  235. package/dist/modules/workspace/kb-seed.service.js.map +0 -1
  236. package/dist/modules/workspace/plugins-migration.d.ts +0 -50
  237. package/dist/modules/workspace/plugins-migration.d.ts.map +0 -1
  238. package/dist/modules/workspace/plugins-migration.js +0 -379
  239. package/dist/modules/workspace/plugins-migration.js.map +0 -1
  240. package/dist/shared/fs-errors.d.ts +0 -8
  241. package/dist/shared/fs-errors.d.ts.map +0 -1
  242. package/dist/shared/fs-errors.js +0 -11
  243. package/dist/shared/fs-errors.js.map +0 -1
  244. package/dist/shared/fs-walk.d.ts +0 -23
  245. package/dist/shared/fs-walk.d.ts.map +0 -1
  246. package/dist/shared/fs-walk.js +0 -42
  247. package/dist/shared/fs-walk.js.map +0 -1
  248. package/dist/shared/hash-email.d.ts +0 -11
  249. package/dist/shared/hash-email.d.ts.map +0 -1
  250. package/dist/shared/hash-email.js +0 -14
  251. package/dist/shared/hash-email.js.map +0 -1
  252. package/dist/shared/kb-walk.d.ts +0 -52
  253. package/dist/shared/kb-walk.d.ts.map +0 -1
  254. package/dist/shared/kb-walk.js +0 -76
  255. package/dist/shared/kb-walk.js.map +0 -1
@@ -107,7 +107,13 @@ async function resolvedRealPath(absolutePath: string): Promise<string | null> {
107
107
  * Refuse a path under `rootDir` that names the git folder or resolves into it.
108
108
  *
109
109
  * `inputPath` is the caller's own spelling (checked lexically, encoded forms
110
- * included); `absolutePath` is where it lands on disk. Only the part below
110
+ * included, and probed on disk every way it could land — see
111
+ * {@link candidateTargets}); `absolutePath`, when a layer has already
112
+ * decided where the path goes, is that place, judged AS WELL. Both in one
113
+ * call, because the two answer different questions — a link reached by a
114
+ * reading this layer does not use is caught only by the spelling, a link the
115
+ * layer's own resolution follows only by the place — and one call walks the
116
+ * root once and probes a place that both name once. Only the part below
111
117
  * `rootDir` is judged, so a workspaces root that itself sits under some
112
118
  * `.git` directory does not refuse everything. A link that leaves the root
113
119
  * is judged on its target's path relative to the root, so a link into
@@ -116,9 +122,58 @@ async function resolvedRealPath(absolutePath: string): Promise<string | null> {
116
122
  */
117
123
  export async function assertNotGitInternals(rootDir: string, inputPath: string, absolutePath?: string): Promise<void> {
118
124
  assertNoGitInternalsSegment(inputPath);
119
- const target = absolutePath ?? path.resolve(rootDir, inputPath.replace(/^[\\/]+/, ''));
120
- if (hasGitInternalsSegment(path.relative(path.resolve(rootDir), target))) throw new GitInternalsError();
121
- const [realTarget, realRoot] = await Promise.all([resolvedRealPath(target), resolvedRealPath(path.resolve(rootDir))]);
122
- if (realTarget === null || realRoot === null) return;
123
- if (hasGitInternalsSegment(path.relative(realRoot, realTarget))) throw new GitInternalsError();
125
+ const root = path.resolve(rootDir);
126
+ const realRoot = await resolvedRealPath(root);
127
+ // Each place once, remembering whether a layer resolved it (probed as
128
+ // given) or only a spelling named it (probed only inside the root).
129
+ const targets = new Map<string, boolean>();
130
+ for (const target of candidateTargets(root, inputPath)) targets.set(target, false);
131
+ if (absolutePath !== undefined) targets.set(absolutePath, true);
132
+ for (const [target, resolved] of targets) {
133
+ const relative = path.relative(root, target);
134
+ if (hasGitInternalsSegment(relative)) throw new GitInternalsError();
135
+ if (realRoot === null) continue;
136
+ // A raw spelling fans out into candidates, and one of them can name a
137
+ // place outside the workspace entirely — `/etc/…`, a deep climb. Such a
138
+ // candidate is judged by the lexical reading above (a link into another
139
+ // checkout's `.git` is named there) but NOT probed on disk: probing it
140
+ // would let an unreadable external directory answer with its own
141
+ // `EACCES` instead of the path rule's typed refusal for a spelling that
142
+ // was never a workspace path. A path a layer already resolved is the one
143
+ // place it is, so it is probed as given.
144
+ // `..` and `../…` are the climbs; `..link` is an ordinary name that merely
145
+ // starts with those characters, and a link by that name can point into the
146
+ // folder — so it is probed like any other entry under the root.
147
+ const climbsOut = relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative);
148
+ if (!resolved && (relative === '' || climbsOut)) continue;
149
+ const realTarget = await resolvedRealPath(target);
150
+ if (realTarget !== null && hasGitInternalsSegment(path.relative(realRoot, realTarget))) throw new GitInternalsError();
151
+ }
152
+ }
153
+
154
+ /**
155
+ * Every place one raw spelling could LAND, because a spelling is not yet a
156
+ * path until something decides how to read it.
157
+ *
158
+ * A backslash separates segments on Windows and is an ordinary character in a
159
+ * filename here, so `kb\\Notes\\..\\link` is read BOTH ways; an absolute path
160
+ * is both the path it names and — as every workspace caller reads it — that
161
+ * path with its leading slashes dropped, under the workspace; and a leading
162
+ * climb (`../kb/link`) is both the climb it spells and the path left when the
163
+ * climb is dropped, which is how a lenient reader takes it. The spelling is
164
+ * refused when ANY reading lands in the git folder: which reading a later
165
+ * layer picks is not something this rule should have to predict.
166
+ *
167
+ * This is what a CALLER's own spelling needs. A path some layer has already
168
+ * resolved arrives as `absolutePath` and is judged as the one place it is.
169
+ */
170
+ function candidateTargets(root: string, inputPath: string): string[] {
171
+ const targets = new Set<string>();
172
+ for (const spelling of new Set([inputPath, inputPath.replace(/\\/g, '/')])) {
173
+ const anchored = spelling.replace(/^[\\/]+/, '');
174
+ targets.add(path.resolve(root, anchored));
175
+ targets.add(path.resolve(root, anchored.replace(/^(\.\.?[\\/])+/, '')));
176
+ if (/^[\\/]/.test(spelling)) targets.add(path.resolve(spelling));
177
+ }
178
+ return [...targets];
124
179
  }
@@ -1,34 +0,0 @@
1
- import { WorkflowDomainError } from '../workflow/workflow.errors.js';
2
- export interface AccessDeniedDetails {
3
- /** repo-relative POSIX path the caller tried to write */
4
- path: string;
5
- /** Display names of roles that would grant write at this path. */
6
- eligibleRoles: string[];
7
- /** Direct user grants at this path, in `{name, email}` form. */
8
- eligibleUsers: {
9
- name: string;
10
- email: string;
11
- }[];
12
- }
13
- /**
14
- * Thrown when a user lacks the `write` permission on a path under the
15
- * access-control rules in `roles.yaml` + `access.md`.
16
- *
17
- * Carries enough detail in the JSON payload for the frontend to render
18
- * "you don't have permission to write to <path>; eligible: <roles + users>"
19
- * without a follow-up roundtrip.
20
- */
21
- export declare class AccessDeniedError extends WorkflowDomainError {
22
- readonly access: AccessDeniedDetails;
23
- constructor(details: AccessDeniedDetails);
24
- }
25
- /**
26
- * Thrown when the access-control config (roles.yaml or access.md) is missing
27
- * or malformed at runtime — distinct from AccessDeniedError because the cause
28
- * is a config bug, not a permission decision.
29
- */
30
- export declare class AccessConfigError extends WorkflowDomainError {
31
- readonly errors: string[];
32
- constructor(errors: string[]);
33
- }
34
- //# sourceMappingURL=access-errors.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"access-errors.d.ts","sourceRoot":"","sources":["../../../src/modules/access/access-errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AAErE,MAAM,WAAW,mBAAmB;IAClC,yDAAyD;IACzD,IAAI,EAAE,MAAM,CAAC;IACb,kEAAkE;IAClE,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,gEAAgE;IAChE,aAAa,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAClD;AAED;;;;;;;GAOG;AACH,qBAAa,iBAAkB,SAAQ,mBAAmB;IACxD,QAAQ,CAAC,MAAM,EAAE,mBAAmB,CAAC;gBAEzB,OAAO,EAAE,mBAAmB;CAkBzC;AAED;;;;GAIG;AACH,qBAAa,iBAAkB,SAAQ,mBAAmB;IACxD,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC;gBAEd,MAAM,EAAE,MAAM,EAAE;CAS7B"}
@@ -1,40 +0,0 @@
1
- import { WorkflowDomainError } from '../workflow/workflow.errors.js';
2
- /**
3
- * Thrown when a user lacks the `write` permission on a path under the
4
- * access-control rules in `roles.yaml` + `access.md`.
5
- *
6
- * Carries enough detail in the JSON payload for the frontend to render
7
- * "you don't have permission to write to <path>; eligible: <roles + users>"
8
- * without a follow-up roundtrip.
9
- */
10
- export class AccessDeniedError extends WorkflowDomainError {
11
- access;
12
- constructor(details) {
13
- const rolesPart = details.eligibleRoles.length
14
- ? details.eligibleRoles.join(', ')
15
- : 'none';
16
- const usersPart = details.eligibleUsers.length
17
- ? details.eligibleUsers
18
- .map((u) => (u.name ? `${u.name} <${u.email}>` : u.email))
19
- .join(', ')
20
- : '';
21
- const eligible = [rolesPart, usersPart].filter(Boolean).join('; ');
22
- super(`You don't have permission to write to "${details.path}". Eligible: ${eligible}.`, 403, { access: details });
23
- this.name = 'AccessDeniedError';
24
- this.access = details;
25
- }
26
- }
27
- /**
28
- * Thrown when the access-control config (roles.yaml or access.md) is missing
29
- * or malformed at runtime — distinct from AccessDeniedError because the cause
30
- * is a config bug, not a permission decision.
31
- */
32
- export class AccessConfigError extends WorkflowDomainError {
33
- errors;
34
- constructor(errors) {
35
- super(`Access-control config is invalid: ${errors.join('; ')}`, 500, { accessConfigErrors: errors });
36
- this.name = 'AccessConfigError';
37
- this.errors = errors;
38
- }
39
- }
40
- //# sourceMappingURL=access-errors.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"access-errors.js","sourceRoot":"","sources":["../../../src/modules/access/access-errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AAWrE;;;;;;;GAOG;AACH,MAAM,OAAO,iBAAkB,SAAQ,mBAAmB;IAC/C,MAAM,CAAsB;IAErC,YAAY,OAA4B;QACtC,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM;YAC5C,CAAC,CAAC,OAAO,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC;YAClC,CAAC,CAAC,MAAM,CAAC;QACX,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM;YAC5C,CAAC,CAAC,OAAO,CAAC,aAAa;iBAClB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;iBACzD,IAAI,CAAC,IAAI,CAAC;YACf,CAAC,CAAC,EAAE,CAAC;QACP,MAAM,QAAQ,GAAG,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnE,KAAK,CACH,0CAA0C,OAAO,CAAC,IAAI,gBAAgB,QAAQ,GAAG,EACjF,GAAG,EACH,EAAE,MAAM,EAAE,OAAO,EAAE,CACpB,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;QAChC,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC;IACxB,CAAC;CACF;AAED;;;;GAIG;AACH,MAAM,OAAO,iBAAkB,SAAQ,mBAAmB;IAC/C,MAAM,CAAW;IAE1B,YAAY,MAAgB;QAC1B,KAAK,CACH,qCAAqC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EACxD,GAAG,EACH,EAAE,kBAAkB,EAAE,MAAM,EAAE,CAC/B,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;QAChC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;CACF"}
@@ -1,140 +0,0 @@
1
- /**
2
- * Surgical text-splice editor for `access.md` frontmatter and node-frontmatter
3
- * access verbs.
4
- *
5
- * WHY a splice and not parse→model→emit: the resolver's parser
6
- * (`parseYamlSubset` / `extractFrontmatter` / `stripComment`) deliberately
7
- * throws away comments, blank lines, key order, AND the markdown body below the
8
- * closing `---`. Re-serialising from the parsed model would silently DELETE the
9
- * prose bodies the real `access.md` files carry (the repo-root one is a whole
10
- * README) — and a `parse(emit(x))` round-trip test would PASS anyway because
11
- * parse ignores the body. So every mutation here edits the RAW text: we locate
12
- * the verb's block, splice exactly one list line in or out, and leave every
13
- * other byte — comments, ordering, blank lines, the body — untouched.
14
- *
15
- * ---
16
- * write: (verb key line, indent 0)
17
- * - Admin (list item, indent 2) <- splice point
18
- * - Felix <f@x.eu> (list item)
19
- * # a comment (preserved verbatim)
20
- * owner: [] (inline-empty list)
21
- * ---
22
- * # Body prose (preserved verbatim)
23
- *
24
- * The same engine serves node-frontmatter, which additionally accepts a scalar
25
- * form (`owner: Felix <f@x.eu>`). Adding a second principal to a scalar verb
26
- * normalises it to the block-list form.
27
- */
28
- import { type Verb } from './access-control.service.js';
29
- /**
30
- * Which rule block a mutation edits.
31
- *
32
- * - `'node'` (default): the file's YAML frontmatter — node files, and every
33
- * call that predates the two-format `access.md` story.
34
- * - `'folder'`: the block that governs the CONTAINING FOLDER of an
35
- * `access.md` — the body when the file is in the new (body-governed)
36
- * format, the frontmatter otherwise. Callers mutating folder rules pass
37
- * this so a grant can never land in a new-format file's frontmatter
38
- * (which governs the FILE itself, not the folder).
39
- */
40
- export type SpliceTarget = 'node' | 'folder';
41
- /** A principal to grant/revoke, in the resolver's canonical terms. */
42
- export type Principal = {
43
- kind: 'user';
44
- email: string;
45
- displayName: string;
46
- } | {
47
- kind: 'role';
48
- role: string;
49
- };
50
- /** Result of a splice — the new full file text and whether anything changed. */
51
- export interface SpliceResult {
52
- text: string;
53
- changed: boolean;
54
- }
55
- /** Thrown when the input can't be spliced safely (malformed frontmatter, etc.). */
56
- export declare class AccessSpliceError extends Error {
57
- constructor(message: string);
58
- }
59
- /**
60
- * Render a principal as the canonical access-entry line value the parser reads
61
- * back (`Name <email>` or a bare role display name). The caller is responsible
62
- * for having validated the principal first (see `validatePrincipal`).
63
- *
64
- * `deny` prefixes the entry with `deny ` so the resolver reads it as a denial
65
- * (the `deny-here` per-item override) — same line shape, opposite verdict.
66
- */
67
- export declare function renderEntry(p: Principal, deny?: boolean): string;
68
- /**
69
- * Validate a principal before it is spliced into a file. Rejects anything that
70
- * could break frontmatter or smuggle in extra YAML — newlines, control chars,
71
- * and emails / names that don't match the resolver's own regexes. This is the
72
- * injection guard: a principal that survives this is safe to render verbatim
73
- * into a ` - ` list line.
74
- *
75
- * Returns the canonical principal (lowercased email / canonical role) on
76
- * success, or throws `AccessSpliceError`.
77
- */
78
- export declare function validatePrincipal(p: Principal): Principal;
79
- /**
80
- * How a role-shaped principal's TOKEN is compared against a file entry:
81
- *
82
- * - `'exact'`: the entry must carry the SAME spelling — a bare token and a
83
- * `role/<name>` token are DIFFERENT principals (bare = whoever owns the
84
- * bare key under group-first precedence, `role/` = the role). Grants
85
- * always use this: granting the group `X` while `role/X` is present (or
86
- * vice versa) must NOT be treated as already-granted, or the other
87
- * principal silently never receives the grant.
88
- * - `'name'`: both spellings of the name match. Revokes use this ONLY when
89
- * no group shadows the bare name — then both spellings resolve to the
90
- * role, and revoking the role must also remove legacy bare spellings.
91
- */
92
- export type TokenMatch = 'exact' | 'name';
93
- /**
94
- * Add `principal` under `verb`. Idempotent: if an entry with the EXACT same
95
- * token spelling already exists with the SAME deny-ness (a grant when
96
- * granting, a deny when denying), returns `changed: false`. Idempotency is
97
- * deliberately exact-token, never name-level: a bare token and a `role/`
98
- * token are different principals (group-first precedence gives the bare key
99
- * to a same-named group), so a bare-`X` GROUP grant must go through even when
100
- * `role/X` is present, and vice versa. Creates the verb key (and the
101
- * frontmatter fence, for a fresh file) as needed. Preserves everything else
102
- * byte-for-byte.
103
- *
104
- * `allowScalar` controls whether a fresh single-entry verb may be written in
105
- * the node-frontmatter scalar form (`owner: Name <email>`). access.md always
106
- * uses the block-list form; node frontmatter prefers scalar for a lone entry.
107
- *
108
- * `deny` writes the entry as a `deny <principal>` denial instead of a grant
109
- * (the `deny-here` per-item override). The block/scalar/empty-list handling is
110
- * identical — only the rendered line and the idempotency check differ — so the
111
- * deny path reuses this engine rather than a parallel `spliceDeny`. NOTE: the
112
- * resolver lets a same-scope grant override a same-scope deny of the same
113
- * principal, so a caller adding a deny where a grant already exists must strip
114
- * that grant first (see `AccessMutationService`); this function only writes the
115
- * one entry it's asked to.
116
- */
117
- export declare function spliceGrant(text: string, verb: Verb, rawPrincipal: Principal, opts?: {
118
- allowScalar?: boolean;
119
- deny?: boolean;
120
- target?: SpliceTarget;
121
- }): SpliceResult;
122
- /**
123
- * Remove every entry naming `principal` under `verb` (grant or deny). Idempotent:
124
- * returns `changed: false` when the principal isn't present. If removing the
125
- * last item empties the verb, the `verb:` key is collapsed to `verb: []` so the
126
- * file still parses cleanly (the resolver reads `[]` as an empty list).
127
- * Preserves everything else.
128
- *
129
- * Token matching for role-shaped principals is `tokenMatch` (default `'name'`:
130
- * BOTH spellings — revoking the role removes legacy bare spellings too).
131
- * Callers pass `'exact'` when a GROUP shadows the principal's bare name: the
132
- * bare token then belongs to the group and the `role/` token to the role, so a
133
- * revoke of one must never strip the other (see AccessMutationService, which
134
- * derives the flag from the merged principal index).
135
- */
136
- export declare function spliceRevoke(text: string, verb: Verb, rawPrincipal: Principal, opts?: {
137
- target?: SpliceTarget;
138
- tokenMatch?: TokenMatch;
139
- }): SpliceResult;
140
- //# sourceMappingURL=access-splice.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"access-splice.d.ts","sourceRoot":"","sources":["../../../src/modules/access/access-splice.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EACL,KAAK,IAAI,EAMV,MAAM,6BAA6B,CAAC;AAErC;;;;;;;;;;GAUG;AACH,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,QAAQ,CAAC;AAE7C,sEAAsE;AACtE,MAAM,MAAM,SAAS,GACjB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GACpD;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnC,gFAAgF;AAChF,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,mFAAmF;AACnF,qBAAa,iBAAkB,SAAQ,KAAK;gBAC9B,OAAO,EAAE,MAAM;CAI5B;AAMD;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,SAAS,EAAE,IAAI,UAAQ,GAAG,MAAM,CAG9D;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,SAAS,GAAG,SAAS,CAgDzD;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,MAAM,CAAC;AA+L1C;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,IAAI,EACV,YAAY,EAAE,SAAS,EACvB,IAAI,GAAE;IAAE,WAAW,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,YAAY,CAAA;CAAO,GAC1E,YAAY,CA0Dd;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,IAAI,EACV,YAAY,EAAE,SAAS,EACvB,IAAI,GAAE;IAAE,MAAM,CAAC,EAAE,YAAY,CAAC;IAAC,UAAU,CAAC,EAAE,UAAU,CAAA;CAAO,GAC5D,YAAY,CAkCd"}