@bevel-software/platform-core-backend 0.30.0 → 0.32.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 (210) hide show
  1. package/agent-guide/access-control.md +49 -4
  2. package/agent-guide/html-views.md +76 -2
  3. package/dist/core/catalog-revision.js +3 -3
  4. package/dist/core/create-core-server.d.ts.map +1 -1
  5. package/dist/core/create-core-server.js +2 -1
  6. package/dist/core/create-core-server.js.map +1 -1
  7. package/dist/core/create-core-services.d.ts +7 -0
  8. package/dist/core/create-core-services.d.ts.map +1 -1
  9. package/dist/core/create-core-services.js +16 -3
  10. package/dist/core/create-core-services.js.map +1 -1
  11. package/dist/index.d.ts +1 -0
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +1 -0
  14. package/dist/index.js.map +1 -1
  15. package/dist/modules/access/access-control.service.d.ts.map +1 -1
  16. package/dist/modules/access/access-control.service.js +1 -5
  17. package/dist/modules/access/access-control.service.js.map +1 -1
  18. package/dist/modules/access-model/index.d.ts +14 -0
  19. package/dist/modules/access-model/index.d.ts.map +1 -0
  20. package/dist/modules/access-model/index.js +13 -0
  21. package/dist/modules/access-model/index.js.map +1 -0
  22. package/dist/modules/audit/agent-audit.service.d.ts.map +1 -1
  23. package/dist/modules/audit/agent-audit.service.js +66 -6
  24. package/dist/modules/audit/agent-audit.service.js.map +1 -1
  25. package/dist/modules/audit/audit.contract.d.ts +52 -8
  26. package/dist/modules/audit/audit.contract.d.ts.map +1 -1
  27. package/dist/modules/audit/audit.contract.js +37 -1
  28. package/dist/modules/audit/audit.contract.js.map +1 -1
  29. package/dist/modules/audit/audit.routes.d.ts +1 -1
  30. package/dist/modules/audit/audit.routes.d.ts.map +1 -1
  31. package/dist/modules/audit/audit.routes.js +15 -13
  32. package/dist/modules/audit/audit.routes.js.map +1 -1
  33. package/dist/modules/audit/request-audit.d.ts +21 -10
  34. package/dist/modules/audit/request-audit.d.ts.map +1 -1
  35. package/dist/modules/audit/request-audit.js +26 -10
  36. package/dist/modules/audit/request-audit.js.map +1 -1
  37. package/dist/modules/auth/account-hooks.d.ts +4 -6
  38. package/dist/modules/auth/account-hooks.d.ts.map +1 -1
  39. package/dist/modules/auth/account-hooks.js +7 -17
  40. package/dist/modules/auth/account-hooks.js.map +1 -1
  41. package/dist/modules/auth/auth.service.d.ts +9 -8
  42. package/dist/modules/auth/auth.service.d.ts.map +1 -1
  43. package/dist/modules/auth/auth.service.js +19 -18
  44. package/dist/modules/auth/auth.service.js.map +1 -1
  45. package/dist/modules/database/core-schema.d.ts +43 -4
  46. package/dist/modules/database/core-schema.d.ts.map +1 -1
  47. package/dist/modules/database/core-schema.js +31 -7
  48. package/dist/modules/database/core-schema.js.map +1 -1
  49. package/dist/modules/kb-fs/index.d.ts +1 -0
  50. package/dist/modules/kb-fs/index.d.ts.map +1 -1
  51. package/dist/modules/kb-fs/index.js +1 -0
  52. package/dist/modules/kb-fs/index.js.map +1 -1
  53. package/dist/modules/kb-fs/locking-filesystem.d.ts.map +1 -1
  54. package/dist/modules/kb-fs/locking-filesystem.js +3 -12
  55. package/dist/modules/kb-fs/locking-filesystem.js.map +1 -1
  56. package/dist/modules/kb-fs/mutex.d.ts +11 -0
  57. package/dist/modules/kb-fs/mutex.d.ts.map +1 -1
  58. package/dist/modules/kb-fs/mutex.js +14 -0
  59. package/dist/modules/kb-fs/mutex.js.map +1 -1
  60. package/dist/modules/mcp/mcp-auth.middleware.d.ts +14 -0
  61. package/dist/modules/mcp/mcp-auth.middleware.d.ts.map +1 -1
  62. package/dist/modules/mcp/mcp-auth.middleware.js +4 -0
  63. package/dist/modules/mcp/mcp-auth.middleware.js.map +1 -1
  64. package/dist/modules/mcp/mcp.routes.d.ts +5 -0
  65. package/dist/modules/mcp/mcp.routes.d.ts.map +1 -1
  66. package/dist/modules/mcp/mcp.routes.js +67 -0
  67. package/dist/modules/mcp/mcp.routes.js.map +1 -1
  68. package/dist/modules/mcp/mcp.service.d.ts +107 -27
  69. package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
  70. package/dist/modules/mcp/mcp.service.js +257 -50
  71. package/dist/modules/mcp/mcp.service.js.map +1 -1
  72. package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts +28 -0
  73. package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts.map +1 -1
  74. package/dist/modules/mcp/oauth/bevel-oauth-provider.js +166 -39
  75. package/dist/modules/mcp/oauth/bevel-oauth-provider.js.map +1 -1
  76. package/dist/modules/mcp/request-context.d.ts +25 -0
  77. package/dist/modules/mcp/request-context.d.ts.map +1 -0
  78. package/dist/modules/mcp/request-context.js +11 -0
  79. package/dist/modules/mcp/request-context.js.map +1 -0
  80. package/dist/modules/mcp/shared-definitions.d.ts +56 -0
  81. package/dist/modules/mcp/shared-definitions.d.ts.map +1 -0
  82. package/dist/modules/mcp/shared-definitions.js +117 -0
  83. package/dist/modules/mcp/shared-definitions.js.map +1 -0
  84. package/dist/modules/mcp/surface-cache.d.ts +70 -0
  85. package/dist/modules/mcp/surface-cache.d.ts.map +1 -0
  86. package/dist/modules/mcp/surface-cache.js +104 -0
  87. package/dist/modules/mcp/surface-cache.js.map +1 -0
  88. package/dist/modules/tool-helpers/tool-context.d.ts.map +1 -1
  89. package/dist/modules/tool-helpers/tool-context.js +33 -12
  90. package/dist/modules/tool-helpers/tool-context.js.map +1 -1
  91. package/dist/modules/tool-helpers/tool.contract.d.ts +8 -0
  92. package/dist/modules/tool-helpers/tool.contract.d.ts.map +1 -1
  93. package/dist/modules/tool-helpers/tool.contract.js.map +1 -1
  94. package/dist/modules/workflow/agent-tools/change-request-read.tools.d.ts.map +1 -1
  95. package/dist/modules/workflow/agent-tools/change-request-read.tools.js +3 -14
  96. package/dist/modules/workflow/agent-tools/change-request-read.tools.js.map +1 -1
  97. package/dist/modules/workflow/agent-tools/change-request-summary.d.ts +10 -1
  98. package/dist/modules/workflow/agent-tools/change-request-summary.d.ts.map +1 -1
  99. package/dist/modules/workflow/agent-tools/change-request-summary.js +2 -0
  100. package/dist/modules/workflow/agent-tools/change-request-summary.js.map +1 -1
  101. package/dist/modules/workflow/agent-tools/workflow.tools.d.ts +18 -1
  102. package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +1 -1
  103. package/dist/modules/workflow/agent-tools/workflow.tools.js +458 -2
  104. package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -1
  105. package/dist/modules/workflow/change-request-hooks.d.ts +78 -0
  106. package/dist/modules/workflow/change-request-hooks.d.ts.map +1 -0
  107. package/dist/modules/workflow/change-request-hooks.js +15 -0
  108. package/dist/modules/workflow/change-request-hooks.js.map +1 -0
  109. package/dist/modules/workflow/git/git.service.d.ts +113 -4
  110. package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
  111. package/dist/modules/workflow/git/git.service.js +470 -106
  112. package/dist/modules/workflow/git/git.service.js.map +1 -1
  113. package/dist/modules/workflow/git/pull-request.service.d.ts +10 -0
  114. package/dist/modules/workflow/git/pull-request.service.d.ts.map +1 -1
  115. package/dist/modules/workflow/git/pull-request.service.js +9 -0
  116. package/dist/modules/workflow/git/pull-request.service.js.map +1 -1
  117. package/dist/modules/workflow/read-on-branch.d.ts +35 -0
  118. package/dist/modules/workflow/read-on-branch.d.ts.map +1 -0
  119. package/dist/modules/workflow/read-on-branch.js +58 -0
  120. package/dist/modules/workflow/read-on-branch.js.map +1 -0
  121. package/dist/modules/workflow/workflow.routes.d.ts.map +1 -1
  122. package/dist/modules/workflow/workflow.routes.js +22 -41
  123. package/dist/modules/workflow/workflow.routes.js.map +1 -1
  124. package/dist/modules/workflow/workflow.service.d.ts +124 -24
  125. package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
  126. package/dist/modules/workflow/workflow.service.js +326 -48
  127. package/dist/modules/workflow/workflow.service.js.map +1 -1
  128. package/dist/modules/workspace/index.d.ts +2 -0
  129. package/dist/modules/workspace/index.d.ts.map +1 -1
  130. package/dist/modules/workspace/index.js +4 -0
  131. package/dist/modules/workspace/index.js.map +1 -1
  132. package/dist/shared/git.contract.d.ts +7 -0
  133. package/dist/shared/git.contract.d.ts.map +1 -1
  134. package/dist/shared/git.contract.js +9 -0
  135. package/dist/shared/git.contract.js.map +1 -1
  136. package/dist/shared/listener-seam.d.ts +34 -0
  137. package/dist/shared/listener-seam.d.ts.map +1 -0
  138. package/dist/shared/listener-seam.js +42 -0
  139. package/dist/shared/listener-seam.js.map +1 -0
  140. package/dist/shared/no-link-below.d.ts +10 -0
  141. package/dist/shared/no-link-below.d.ts.map +1 -0
  142. package/dist/shared/no-link-below.js +30 -0
  143. package/dist/shared/no-link-below.js.map +1 -0
  144. package/migrations/0018_agent_events_page.sql +11 -0
  145. package/migrations/0019_oauth_token_rotated_at.sql +7 -0
  146. package/migrations/meta/0018_snapshot.json +2429 -0
  147. package/migrations/meta/0019_snapshot.json +2435 -0
  148. package/migrations/meta/_journal.json +15 -1
  149. package/package.json +3 -3
  150. package/src/core/catalog-revision.ts +3 -3
  151. package/src/core/create-core-server.ts +2 -1
  152. package/src/core/create-core-services.ts +21 -2
  153. package/src/index.ts +7 -0
  154. package/src/modules/access/access-control.service.ts +1 -6
  155. package/src/modules/access-model/index.ts +13 -0
  156. package/src/modules/agent-guide/__tests__/agent-guide.test.ts +22 -1
  157. package/src/modules/audit/__tests__/agent-audit.service.test.ts +78 -1
  158. package/src/modules/audit/__tests__/audit.routes.test.ts +43 -5
  159. package/src/modules/audit/agent-audit.service.ts +66 -6
  160. package/src/modules/audit/audit.contract.ts +62 -10
  161. package/src/modules/audit/audit.routes.ts +14 -12
  162. package/src/modules/audit/request-audit.ts +28 -13
  163. package/src/modules/auth/__tests__/account-deactivation.test.ts +38 -2
  164. package/src/modules/auth/account-hooks.ts +13 -21
  165. package/src/modules/auth/auth.service.ts +19 -18
  166. package/src/modules/database/core-schema.ts +31 -7
  167. package/src/modules/kb-fs/__tests__/mutex.canonical.test.ts +57 -0
  168. package/src/modules/kb-fs/index.ts +1 -0
  169. package/src/modules/kb-fs/locking-filesystem.ts +3 -12
  170. package/src/modules/kb-fs/mutex.ts +16 -0
  171. package/src/modules/mcp/__tests__/bevel-oauth-provider.pg.test.ts +279 -0
  172. package/src/modules/mcp/__tests__/bevel-oauth-provider.test.ts +190 -15
  173. package/src/modules/mcp/__tests__/mcp-routes-harness.ts +7 -1
  174. package/src/modules/mcp/__tests__/mcp.routes.page.test.ts +115 -0
  175. package/src/modules/mcp/__tests__/mcp.surface-cache.test.ts +521 -0
  176. package/src/modules/mcp/__tests__/shared-definitions.test.ts +28 -0
  177. package/src/modules/mcp/__tests__/surface-cache.test.ts +127 -0
  178. package/src/modules/mcp/mcp-auth.middleware.ts +21 -0
  179. package/src/modules/mcp/mcp.routes.ts +68 -0
  180. package/src/modules/mcp/mcp.service.ts +315 -51
  181. package/src/modules/mcp/oauth/bevel-oauth-provider.ts +214 -48
  182. package/src/modules/mcp/request-context.ts +34 -0
  183. package/src/modules/mcp/shared-definitions.ts +121 -0
  184. package/src/modules/mcp/surface-cache.ts +154 -0
  185. package/src/modules/tool-helpers/tool-context.ts +37 -18
  186. package/src/modules/tool-helpers/tool.contract.ts +8 -0
  187. package/src/modules/tool-registry/__tests__/own-tool-schemas.test.ts +1 -1
  188. package/src/modules/tool-registry/__tests__/tool-description-length.test.ts +1 -1
  189. package/src/modules/workflow/__tests__/change-request-hooks.test.ts +62 -0
  190. package/src/modules/workflow/__tests__/workflow.routes.history-read-gate.test.ts +23 -12
  191. package/src/modules/workflow/__tests__/workflow.routes.open-change-request.test.ts +79 -0
  192. package/src/modules/workflow/__tests__/workflow.service.delete-branch.test.ts +9 -1
  193. package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +508 -5
  194. package/src/modules/workflow/agent-tools/__tests__/workflow.tools.test.ts +474 -4
  195. package/src/modules/workflow/agent-tools/change-request-read.tools.ts +4 -22
  196. package/src/modules/workflow/agent-tools/change-request-summary.ts +10 -1
  197. package/src/modules/workflow/agent-tools/workflow.tools.ts +468 -1
  198. package/src/modules/workflow/change-request-hooks.ts +96 -0
  199. package/src/modules/workflow/git/__tests__/git.service.diffFileBetweenBranches.test.ts +76 -11
  200. package/src/modules/workflow/git/__tests__/git.service.mergeIntoBranch.test.ts +721 -0
  201. package/src/modules/workflow/git/git.service.ts +537 -106
  202. package/src/modules/workflow/git/pull-request.service.ts +10 -0
  203. package/src/modules/workflow/read-on-branch.ts +79 -0
  204. package/src/modules/workflow/workflow.routes.ts +21 -39
  205. package/src/modules/workflow/workflow.service.ts +390 -55
  206. package/src/modules/workspace/index.ts +11 -0
  207. package/src/shared/__tests__/no-link-below.test.ts +45 -0
  208. package/src/shared/git.contract.ts +10 -0
  209. package/src/shared/listener-seam.ts +52 -0
  210. package/src/shared/no-link-below.ts +29 -0
@@ -253,6 +253,16 @@ export class PullRequestService implements IPullRequestService {
253
253
  this.linkBase = changeRequestLinkBase(publicFrontendUrl);
254
254
  }
255
255
 
256
+ /**
257
+ * The link to change request `number`, as every summary and detail carries
258
+ * it — absolute under the public address, else the in-app path with its
259
+ * note. Needs no read: a caller holding only the number (an open whose push
260
+ * was refused, say) builds the same link a listing would.
261
+ */
262
+ linkFor(number: number): { url: string; urlNote?: string } {
263
+ return changeRequestLink(number, this.linkBase);
264
+ }
265
+
256
266
  setDetailEnricher(enricher: PrDetailEnricher): void {
257
267
  this.detailEnricher = enricher;
258
268
  }
@@ -0,0 +1,79 @@
1
+ import type { IAccessControl } from '../access/access-control.interface.js';
2
+ import { WorkflowDomainError } from '../../shared/domain-errors.js';
3
+
4
+ /**
5
+ * Which of `paths` `userEmail` may read at `origin/<base>`, as a membership
6
+ * set — ONE rule for the change-request reads (their file lists, judged at
7
+ * the request's base) and for `branch_status` (the paths a request would
8
+ * carry). A null answer (the ref, or its `roles.yaml`, does not resolve)
9
+ * withholds everything, which is the same reading the app's fork-point
10
+ * route gives it.
11
+ */
12
+ export async function readablePathsAt(
13
+ accessControl: Pick<IAccessControl, 'canReadBatchAtRef'>,
14
+ workspaceId: string,
15
+ userEmail: string,
16
+ base: string,
17
+ paths: string[],
18
+ ): Promise<Set<string>> {
19
+ if (paths.length === 0) return new Set();
20
+ const verdicts = await accessControl.canReadBatchAtRef(workspaceId, `origin/${base}`, userEmail, [...new Set(paths)]);
21
+ if (!verdicts) return new Set();
22
+ return new Set([...verdicts].filter(([, allowed]) => allowed).map(([p]) => p));
23
+ }
24
+
25
+ /**
26
+ * Whether `userEmail` may read `repoRelativePath` at EVERY one of `commits`
27
+ * — ONE rule for content served from another branch, for the app's compare
28
+ * route and the agent's history tools alike: the commits the service read
29
+ * the content at, pinned by the service inside its reservation (a diff's two
30
+ * sides, a file at a branch's published tip, the two commits a merge is of),
31
+ * so the verdict is about the bytes actually served, never about a branch
32
+ * ref a fetch may have moved between the verdict and the read. A commit
33
+ * whose rules cannot be read (null) grants nothing. An access-model FAILURE
34
+ * is not a null: it propagates with its own status (an unreadable access
35
+ * tree is the documented 503 a client retries).
36
+ */
37
+ export async function readableAtCommits(
38
+ accessControl: Pick<IAccessControl, 'canReadAtRef'>,
39
+ workspaceId: string,
40
+ userEmail: string,
41
+ repoRelativePath: string,
42
+ commits: readonly string[],
43
+ ): Promise<boolean> {
44
+ for (const sha of commits) {
45
+ if ((await accessControl.canReadAtRef(workspaceId, sha, userEmail, repoRelativePath)) !== true) return false;
46
+ }
47
+ return true;
48
+ }
49
+
50
+ /** The refusal `readableAtCommits` answers false with, as every caller words it: the branch named, never the commit. */
51
+ export class ReadOnBranchDeniedError extends WorkflowDomainError {
52
+ constructor(displayPath: string, branch: string) {
53
+ super(`You don't have permission to read "${displayPath}" on "${branch}".`, 403, {
54
+ kind: 'read-denied',
55
+ path: displayPath,
56
+ branch,
57
+ });
58
+ this.name = 'ReadOnBranchDeniedError';
59
+ }
60
+ }
61
+
62
+ /**
63
+ * `readableAtCommits` at every commit `branch` resolves to (the one its
64
+ * content was served from, and any other its local head or published tip
65
+ * names), thrown when it answers false.
66
+ */
67
+ export async function assertReadableAtCommits(
68
+ accessControl: Pick<IAccessControl, 'canReadAtRef'>,
69
+ workspaceId: string,
70
+ userEmail: string,
71
+ repoRelativePath: string,
72
+ commits: readonly string[],
73
+ branch: string,
74
+ displayPath: string = repoRelativePath,
75
+ ): Promise<void> {
76
+ if (!(await readableAtCommits(accessControl, workspaceId, userEmail, repoRelativePath, commits))) {
77
+ throw new ReadOnBranchDeniedError(displayPath, branch);
78
+ }
79
+ }
@@ -38,6 +38,7 @@ import type { WorkspaceService } from '../workspace/workspace.service.js';
38
38
  import { branchForWorkspaceId } from '../../shared/workspace-id.js';
39
39
  import type { WorkflowEventBus } from './event-bus.js';
40
40
  import { WorkflowDomainError } from '../../shared/domain-errors.js';
41
+ import { assertReadableAtCommits } from './read-on-branch.js';
41
42
  import { assertValidRelativePath } from '../kb-fs/branch-name.js';
42
43
  import { domainErrorBody } from '../../shared/http-errors.js';
43
44
  import { canonicalFileIdentity } from '../../shared/canonical-file-identity.js';
@@ -413,46 +414,27 @@ export function createWorkflowRoutes(
413
414
  // The other history endpoints serve commits of the URL workspace's own
414
415
  // branch, so its working-tree verdict covers what they return. This one
415
416
  // serves content of TWO caller-named branches — the verdict must come
416
- // from the refs actually being served, or a caller could pick a
417
- // workspace whose tree grants what the compared branches' trees deny.
418
- //
419
- // The comparison prefers a LOCAL ref (and the working tree for the
420
- // checked-out branch) over `origin/<branch>`, so both candidate refs are
421
- // authorized: every ref that RESOLVES must grant the read, and a branch
422
- // none of whose refs resolve is denied — never serve a ref that cannot
423
- // be authorized. (The working-tree side is additionally covered by the
424
- // workspace `canRead` above.)
425
- {
426
- const user = await requireUser(req, res);
427
- if (!user) return;
428
- const repoRelative = toKbRelative(pathParam, kbDirName)!;
429
- for (const branch of [fromBranch, toBranch]) {
430
- const verdicts: (boolean | null)[] = [];
431
- for (const ref of [`origin/${branch}`, branch]) {
432
- try {
433
- verdicts.push(await accessControl.canReadAtRef(req.params.id, ref, user.email, repoRelative));
434
- } catch (err) {
435
- // An access-model FAILURE is not an unresolvable ref: mapped to
436
- // null it would let the other candidate's grant serve a ref
437
- // whose authorization errored. Fail closed — but keep the
438
- // error's own status: an unreadable access tree is the
439
- // documented 503 a client retries, not a permanent 403 denial.
440
- const { status, body } = toHttpError(err);
441
- res.status(status).json(body);
442
- return;
443
- }
444
- }
445
- const denied = verdicts.some((v) => v === false);
446
- const unresolvable = verdicts.every((v) => v === null);
447
- if (denied || unresolvable) {
448
- res.status(403).json({ error: `You don't have permission to read "${pathParam}" on "${branch}".` });
449
- return;
450
- }
451
- }
452
- }
417
+ // from the commits actually served (`readableAtCommits`, the one rule
418
+ // this route and the agent's history tools share): the service pins the
419
+ // commit each side is read at and asks this verdict inside its
420
+ // reservation, before anything is said about the path at either, with
421
+ // every commit the branch resolves to — its local head and its published
422
+ // tip, all of which must grant, as this route has always required of
423
+ // both refs — never at a branch ref a fetch may move between a verdict
424
+ // and a read. (The
425
+ // working-tree side is additionally covered by the workspace `canRead`
426
+ // above.) An access-model failure keeps its own status: an unreadable
427
+ // access tree is the documented 503 a client retries, not a permanent
428
+ // 403 denial.
429
+ const user = await requireUser(req, res);
430
+ if (!user) return;
431
+ const repoRelative = toKbRelative(pathParam, kbDirName)!;
453
432
  try {
454
- const diff = await workflow.compareFile(req.params.id, pathParam, fromBranch, toBranch);
455
- res.json({ diff });
433
+ const compared = await workflow.compareFile(req.params.id, pathParam, fromBranch, toBranch, async (sides) => {
434
+ await assertReadableAtCommits(accessControl, req.params.id, user.email, repoRelative, sides.from, fromBranch, pathParam);
435
+ await assertReadableAtCommits(accessControl, req.params.id, user.email, repoRelative, sides.to, toBranch, pathParam);
436
+ });
437
+ res.json({ diff: compared.diff });
456
438
  } catch (err) {
457
439
  const { status, body } = toHttpError(err);
458
440
  res.status(status).json(body);