@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
@@ -0,0 +1,157 @@
1
+ import express from 'express';
2
+ import { logger } from '../../shared/logging.js';
3
+ import { TokenNotFoundError } from '../tool-auth/external-api-key.errors.js';
4
+ import { AuditPrincipalNotFoundError, InvalidCursorError, isUuid, } from './audit.contract.js';
5
+ import { MAX_EVENT_PAGE } from './agent-audit.service.js';
6
+ import '../auth/auth.middleware.js'; // Express Request augmentation (req.userId / req.userEmail)
7
+ const log = logger('audit');
8
+ const DEFAULT_EVENT_PAGE = 50;
9
+ /**
10
+ * The Audit log's routes, JWT-only (mounted behind the session middleware):
11
+ *
12
+ * GET /api/audit/principals?scope=me|all — agents + keys, with owners and event counts
13
+ * GET /api/audit/principals/:kind/:id/events — one principal's events, newest first, cursor-paged
14
+ * DELETE /api/audit/agents/:id — revoke an agent (owner or admin)
15
+ * DELETE /api/audit/keys/:id — revoke a key (owner or admin)
16
+ *
17
+ * Everyone may read and revoke their OWN rows; `scope=all` and anyone else's
18
+ * rows are admins' (per request, through {@link IAdminAccessService}). Both
19
+ * revokes are recorded as the owner's or an admin's doing, so the owner's
20
+ * page can say which. Keys keep their existing per-user and admin routes;
21
+ * the one here exists so the page has a single client for both kinds.
22
+ */
23
+ export function createAuditRoutes(audit, keys, adminAccess) {
24
+ const router = express.Router();
25
+ const isAdmin = (req) => adminAccess.isAdmin(req.userEmail);
26
+ /** The principal named by the URL, or null when it names none (a malformed id is a not-found, not a 500). */
27
+ const principalOf = (req) => {
28
+ const kind = String(req.params.kind);
29
+ const id = String(req.params.id);
30
+ if ((kind !== 'key' && kind !== 'agent') || !isUuid(id))
31
+ return null;
32
+ return { kind, id };
33
+ };
34
+ /** Whether the caller may read or revoke `principal`: its owner, or an admin. Null when there is no such principal. */
35
+ const mayAct = async (req, principal) => {
36
+ const owner = await audit.ownerOf(principal);
37
+ if (owner === null)
38
+ return null;
39
+ if (owner === req.userId)
40
+ return true;
41
+ return isAdmin(req);
42
+ };
43
+ router.get('/audit/principals', async (req, res) => {
44
+ try {
45
+ const all = req.query.scope === 'all';
46
+ if (all && !(await isAdmin(req))) {
47
+ res.status(403).json({ error: 'Admins only' });
48
+ return;
49
+ }
50
+ const principals = await audit.listPrincipals(all ? 'all' : { userId: req.userId });
51
+ res.json({ principals });
52
+ }
53
+ catch (err) {
54
+ log.error('list principals failed:', { err });
55
+ res.status(500).json({ error: 'Failed to load the audit log' });
56
+ }
57
+ });
58
+ router.get('/audit/principals/:kind/:id/events', async (req, res) => {
59
+ const principal = principalOf(req);
60
+ if (!principal) {
61
+ res.status(404).json({ error: 'No such agent or key' });
62
+ return;
63
+ }
64
+ try {
65
+ const allowed = await mayAct(req, principal);
66
+ if (allowed === null) {
67
+ res.status(404).json({ error: 'No such agent or key' });
68
+ return;
69
+ }
70
+ if (!allowed) {
71
+ res.status(403).json({ error: 'Not yours' });
72
+ return;
73
+ }
74
+ const rawLimit = Number(req.query.limit);
75
+ const limit = Number.isFinite(rawLimit) && rawLimit > 0 ? Math.min(rawLimit, MAX_EVENT_PAGE) : DEFAULT_EVENT_PAGE;
76
+ const before = typeof req.query.before === 'string' && req.query.before ? req.query.before : undefined;
77
+ res.json(await audit.listEvents(principal, { before, limit }));
78
+ }
79
+ catch (err) {
80
+ if (err instanceof InvalidCursorError) {
81
+ res.status(400).json({ error: 'Invalid cursor — reload the events from the start' });
82
+ return;
83
+ }
84
+ log.error('list events failed:', { err });
85
+ res.status(500).json({ error: 'Failed to load events' });
86
+ }
87
+ });
88
+ router.delete('/audit/agents/:id', async (req, res) => {
89
+ const id = String(req.params.id);
90
+ if (!isUuid(id)) {
91
+ res.status(404).json({ error: 'No such agent' });
92
+ return;
93
+ }
94
+ try {
95
+ // Own connection: the owner's revoke, recorded as theirs — an admin
96
+ // cutting off their OWN agent disconnected it, and the row must not
97
+ // tell them an admin took it. Anyone else's: only an admin may, and it
98
+ // is recorded as an admin's so the owner is told. Same order as keys.
99
+ const owner = await audit.ownerOf({ kind: 'agent', id });
100
+ if (owner === req.userId) {
101
+ await audit.revokeConnection(id, 'owner', req.userId);
102
+ }
103
+ else if (await isAdmin(req)) {
104
+ await audit.revokeConnection(id, 'admin');
105
+ log.info('revoke audit:', { action: 'admin-revoke-agent', actorUserId: req.userId, connectionId: id });
106
+ }
107
+ else {
108
+ res.status(owner === null ? 404 : 403).json({ error: owner === null ? 'No such agent' : 'Not yours' });
109
+ return;
110
+ }
111
+ res.json({ status: 'revoked' });
112
+ }
113
+ catch (err) {
114
+ if (err instanceof AuditPrincipalNotFoundError) {
115
+ res.status(404).json({ error: err.message });
116
+ return;
117
+ }
118
+ log.error('revoke agent failed:', { err });
119
+ res.status(500).json({ error: 'Failed to revoke this agent' });
120
+ }
121
+ });
122
+ router.delete('/audit/keys/:id', async (req, res) => {
123
+ const id = String(req.params.id);
124
+ if (!isUuid(id)) {
125
+ res.status(404).json({ error: 'Token not found' });
126
+ return;
127
+ }
128
+ try {
129
+ const admin = await isAdmin(req);
130
+ // Own key: the owner's revoke (recorded as theirs). Anyone else's: only
131
+ // an admin may, and it is recorded as an admin's so the owner is told.
132
+ const owner = await audit.ownerOf({ kind: 'key', id });
133
+ if (owner === req.userId) {
134
+ await keys.revoke(id, req.userId);
135
+ }
136
+ else if (admin) {
137
+ await keys.revokeAny(id);
138
+ log.info('revoke audit:', { action: 'admin-revoke-key', actorUserId: req.userId, tokenId: id });
139
+ }
140
+ else {
141
+ res.status(owner === null ? 404 : 403).json({ error: owner === null ? 'Token not found' : 'Not yours' });
142
+ return;
143
+ }
144
+ res.json({ status: 'revoked' });
145
+ }
146
+ catch (err) {
147
+ if (err instanceof TokenNotFoundError) {
148
+ res.status(404).json({ error: err.message });
149
+ return;
150
+ }
151
+ log.error('revoke key failed:', { err });
152
+ res.status(500).json({ error: 'Failed to revoke this key' });
153
+ }
154
+ });
155
+ return router;
156
+ }
157
+ //# sourceMappingURL=audit.routes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit.routes.js","sourceRoot":"","sources":["../../../src/modules/audit/audit.routes.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ import type { AgentEventKind } from './audit.contract.js';
2
+ /**
3
+ * Pure classification of one UTCP tool call into what the Audit log records:
4
+ * a hexis capability, a manual's tool, or a skill read. No I/O — the one
5
+ * catalog it may need (the skill folders) is handed in by the caller, which
6
+ * asks {@link skillReadPath} first whether that catalog is worth fetching.
7
+ */
8
+ /** What a call is logged as: the kind, the manual (or skill folder), and the bare name. */
9
+ export interface ClassifiedCall {
10
+ kind: AgentEventKind;
11
+ manual: string | null;
12
+ name: string;
13
+ }
14
+ /** A skill as the classifier needs it: its name and its repo-relative folder. */
15
+ export interface SkillFolder {
16
+ name: string;
17
+ path: string;
18
+ }
19
+ export interface ClassifierContext {
20
+ /** The registered name of the platform's own manual (`KNOWLEDGE_BASE`). */
21
+ kbManualName: string;
22
+ /** Registered (rewritten) manual name → catalog name, for pooled MCP manuals. */
23
+ catalogNames: ReadonlyMap<string, string>;
24
+ }
25
+ /** `<manual>.<tool>` → its two halves; a bare name has no manual. */
26
+ export declare function splitUtcpName(utcpName: string): {
27
+ manual: string;
28
+ tool: string;
29
+ };
30
+ /**
31
+ * When a platform read names a path, the path — the signal that the caller
32
+ * should fetch the skill catalog before classifying (a folder match turns
33
+ * the read into a skill event). Null for every other call, so the catalog is
34
+ * never fetched for a `notion.search` or a `write_file`.
35
+ */
36
+ export declare function skillReadPath(utcpName: string, args: Record<string, unknown>, kbManualName: string): string | null;
37
+ /**
38
+ * The skill whose folder contains `path`, if any. Matched on a segment
39
+ * boundary anywhere in the path — a caller may spell the path from the
40
+ * repository root (`Plugins/Sales/rfi/SKILL.md`) or from one level above it
41
+ * (`knowledge-base/Plugins/Sales/rfi/SKILL.md`), and both are that skill.
42
+ * The longest folder wins, so a skill nested under another's folder is not
43
+ * mistaken for its parent.
44
+ */
45
+ export declare function skillContaining(path: string, skills: readonly SkillFolder[]): SkillFolder | null;
46
+ /**
47
+ * Classify one call. `skills` is the catalog when {@link skillReadPath} asked
48
+ * for it and the caller could fetch it; null otherwise (the read is then
49
+ * logged as the capability it is, which is the honest fallback).
50
+ */
51
+ export declare function classifyToolCall(utcpName: string, args: Record<string, unknown>, ctx: ClassifierContext, skills: readonly SkillFolder[] | null): ClassifiedCall;
52
+ //# sourceMappingURL=event-classifier.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-classifier.d.ts","sourceRoot":"","sources":["../../../src/modules/audit/event-classifier.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D;;;;;GAKG;AAEH,2FAA2F;AAC3F,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,cAAc,CAAC;IACrB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,iFAAiF;AACjF,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,iBAAiB;IAChC,2EAA2E;IAC3E,YAAY,EAAE,MAAM,CAAC;IACrB,iFAAiF;IACjF,YAAY,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC3C;AAYD,qEAAqE;AACrE,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAKhF;AAsBD;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAMlH;AAwBD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,WAAW,EAAE,GAAG,WAAW,GAAG,IAAI,CAahG;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,GAAG,EAAE,iBAAiB,EACtB,MAAM,EAAE,SAAS,WAAW,EAAE,GAAG,IAAI,GACpC,cAAc,CAkBhB"}
@@ -0,0 +1,123 @@
1
+ /**
2
+ * The platform tools whose `path` argument may point INTO a skill folder — a
3
+ * read of `Plugins/Sales/rfi/SKILL.md` or of a file bundled beside it is the
4
+ * agent reading that skill, and is logged as such rather than as a file read.
5
+ */
6
+ const SKILL_FOLDER_READ_TOOLS = new Set(['read_file', 'grep', 'list_files']);
7
+ /** The platform tool that loads a skill by name. */
8
+ const GET_SKILL = 'get_skill';
9
+ /** `<manual>.<tool>` → its two halves; a bare name has no manual. */
10
+ export function splitUtcpName(utcpName) {
11
+ const dot = utcpName.indexOf('.');
12
+ return dot < 0
13
+ ? { manual: '', tool: utcpName }
14
+ : { manual: utcpName.slice(0, dot), tool: utcpName.slice(dot + 1) };
15
+ }
16
+ /**
17
+ * The `path` a platform tool was called with, wherever the caller put it.
18
+ * Bevel-hosted http tools take their arguments inside a `body` envelope
19
+ * (`{ body: { path } }`); a caller that flattened it (`{ path }`) is read too.
20
+ */
21
+ function pathArgument(args) {
22
+ const body = args.body;
23
+ const inner = body && typeof body === 'object' ? body.path : undefined;
24
+ const candidate = typeof inner === 'string' ? inner : args.path;
25
+ return typeof candidate === 'string' && candidate.length > 0 ? candidate : null;
26
+ }
27
+ /** The `name` a `get_skill` call asked for, from the envelope or flattened. */
28
+ function skillNameArgument(args) {
29
+ const body = args.body;
30
+ const inner = body && typeof body === 'object' ? body.name : undefined;
31
+ const candidate = typeof inner === 'string' ? inner : args.name;
32
+ return typeof candidate === 'string' && candidate.length > 0 ? candidate : null;
33
+ }
34
+ /**
35
+ * When a platform read names a path, the path — the signal that the caller
36
+ * should fetch the skill catalog before classifying (a folder match turns
37
+ * the read into a skill event). Null for every other call, so the catalog is
38
+ * never fetched for a `notion.search` or a `write_file`.
39
+ */
40
+ export function skillReadPath(utcpName, args, kbManualName) {
41
+ const { manual, tool } = splitUtcpName(utcpName);
42
+ if (manual !== kbManualName)
43
+ return null;
44
+ if (tool === GET_SKILL)
45
+ return skillNameArgument(args) === null ? null : '';
46
+ if (!SKILL_FOLDER_READ_TOOLS.has(tool))
47
+ return null;
48
+ return pathArgument(args);
49
+ }
50
+ /**
51
+ * The path as segments, resolved: forward slashes, `.` dropped, `..` taken
52
+ * back a segment, no empty segments — so two spellings of one place compare
53
+ * equal, and a path that climbs OUT of a folder is never matched to it. A
54
+ * path that climbs above its own start has no place at all and resolves to
55
+ * the empty string, which matches nothing. (The file tools refuse `..`
56
+ * outright; the log must still not credit a refused read to a skill.)
57
+ */
58
+ function normalizePath(p) {
59
+ const out = [];
60
+ for (const segment of p.replace(/\\/g, '/').split('/')) {
61
+ if (segment === '' || segment === '.')
62
+ continue;
63
+ if (segment === '..') {
64
+ if (out.length === 0)
65
+ return '';
66
+ out.pop();
67
+ continue;
68
+ }
69
+ out.push(segment);
70
+ }
71
+ return out.join('/');
72
+ }
73
+ /**
74
+ * The skill whose folder contains `path`, if any. Matched on a segment
75
+ * boundary anywhere in the path — a caller may spell the path from the
76
+ * repository root (`Plugins/Sales/rfi/SKILL.md`) or from one level above it
77
+ * (`knowledge-base/Plugins/Sales/rfi/SKILL.md`), and both are that skill.
78
+ * The longest folder wins, so a skill nested under another's folder is not
79
+ * mistaken for its parent.
80
+ */
81
+ export function skillContaining(path, skills) {
82
+ const normalized = normalizePath(path);
83
+ if (!normalized)
84
+ return null;
85
+ const haystack = `/${normalized}/`;
86
+ let best = null;
87
+ for (const skill of skills) {
88
+ const folder = normalizePath(skill.path);
89
+ if (!folder)
90
+ continue;
91
+ if (haystack.includes(`/${folder}/`) && (!best || folder.length > normalizePath(best.path).length)) {
92
+ best = skill;
93
+ }
94
+ }
95
+ return best;
96
+ }
97
+ /**
98
+ * Classify one call. `skills` is the catalog when {@link skillReadPath} asked
99
+ * for it and the caller could fetch it; null otherwise (the read is then
100
+ * logged as the capability it is, which is the honest fallback).
101
+ */
102
+ export function classifyToolCall(utcpName, args, ctx, skills) {
103
+ const { manual, tool } = splitUtcpName(utcpName);
104
+ // A meta-tool (`call_tool_chain`, `list_tools`, …) has no manual: the platform's own.
105
+ if (manual === '' || manual === ctx.kbManualName) {
106
+ if (tool === GET_SKILL) {
107
+ const name = skillNameArgument(args);
108
+ if (name) {
109
+ const folder = skills?.find((s) => s.name === name)?.path ?? null;
110
+ return { kind: 'skill', manual: folder, name };
111
+ }
112
+ }
113
+ else if (SKILL_FOLDER_READ_TOOLS.has(tool) && skills) {
114
+ const path = pathArgument(args);
115
+ const skill = path ? skillContaining(path, skills) : null;
116
+ if (skill)
117
+ return { kind: 'skill', manual: skill.path, name: skill.name };
118
+ }
119
+ return { kind: 'capability', manual: null, name: tool };
120
+ }
121
+ return { kind: 'tool', manual: ctx.catalogNames.get(manual) ?? manual, name: tool };
122
+ }
123
+ //# sourceMappingURL=event-classifier.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-classifier.js","sourceRoot":"","sources":["../../../src/modules/audit/event-classifier.ts"],"names":[],"mappings":"AA6BA;;;;GAIG;AACH,MAAM,uBAAuB,GAAwB,IAAI,GAAG,CAAC,CAAC,WAAW,EAAE,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC;AAElG,oDAAoD;AACpD,MAAM,SAAS,GAAG,WAAW,CAAC;AAE9B,qEAAqE;AACrE,MAAM,UAAU,aAAa,CAAC,QAAgB;IAC5C,MAAM,GAAG,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAClC,OAAO,GAAG,GAAG,CAAC;QACZ,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;QAChC,CAAC,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,IAAI,EAAE,QAAQ,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,EAAE,CAAC;AACxE,CAAC;AAED;;;;GAIG;AACH,SAAS,YAAY,CAAC,IAA6B;IACjD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;IACvB,MAAM,KAAK,GAAG,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAE,IAAgC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;IACpG,MAAM,SAAS,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;IAChE,OAAO,OAAO,SAAS,KAAK,QAAQ,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC;AAClF,CAAC;AAED,+EAA+E;AAC/E,SAAS,iBAAiB,CAAC,IAA6B;IACtD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;IACvB,MAAM,KAAK,GAAG,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAE,IAAgC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;IACpG,MAAM,SAAS,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;IAChE,OAAO,OAAO,SAAS,KAAK,QAAQ,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC;AAClF,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,QAAgB,EAAE,IAA6B,EAAE,YAAoB;IACjG,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;IACjD,IAAI,MAAM,KAAK,YAAY;QAAE,OAAO,IAAI,CAAC;IACzC,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO,iBAAiB,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;IAC5E,IAAI,CAAC,uBAAuB,CAAC,GAAG,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACpD,OAAO,YAAY,CAAC,IAAI,CAAC,CAAC;AAC5B,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,aAAa,CAAC,CAAS;IAC9B,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,KAAK,MAAM,OAAO,IAAI,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QACvD,IAAI,OAAO,KAAK,EAAE,IAAI,OAAO,KAAK,GAAG;YAAE,SAAS;QAChD,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;YACrB,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO,EAAE,CAAC;YAChC,GAAG,CAAC,GAAG,EAAE,CAAC;YACV,SAAS;QACX,CAAC;QACD,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACpB,CAAC;IACD,OAAO,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACvB,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAAC,IAAY,EAAE,MAA8B;IAC1E,MAAM,UAAU,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC;IACvC,IAAI,CAAC,UAAU;QAAE,OAAO,IAAI,CAAC;IAC7B,MAAM,QAAQ,GAAG,IAAI,UAAU,GAAG,CAAC;IACnC,IAAI,IAAI,GAAuB,IAAI,CAAC;IACpC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,MAAM,MAAM,GAAG,aAAa,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACzC,IAAI,CAAC,MAAM;YAAE,SAAS;QACtB,IAAI,QAAQ,CAAC,QAAQ,CAAC,IAAI,MAAM,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,IAAI,MAAM,CAAC,MAAM,GAAG,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC;YACnG,IAAI,GAAG,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAC9B,QAAgB,EAChB,IAA6B,EAC7B,GAAsB,EACtB,MAAqC;IAErC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;IACjD,sFAAsF;IACtF,IAAI,MAAM,KAAK,EAAE,IAAI,MAAM,KAAK,GAAG,CAAC,YAAY,EAAE,CAAC;QACjD,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YACvB,MAAM,IAAI,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC;YACrC,IAAI,IAAI,EAAE,CAAC;gBACT,MAAM,MAAM,GAAG,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,EAAE,IAAI,IAAI,IAAI,CAAC;gBAClE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;YACjD,CAAC;QACH,CAAC;aAAM,IAAI,uBAAuB,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,MAAM,EAAE,CAAC;YACvD,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;YAChC,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,eAAe,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;YAC1D,IAAI,KAAK;gBAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC;QAC5E,CAAC;QACD,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAC1D,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;AACtF,CAAC"}
@@ -0,0 +1,7 @@
1
+ export * from './audit.contract.js';
2
+ export { AgentAuditService, MAX_EVENT_PAGE, retentionDaysFrom } from './agent-audit.service.js';
3
+ export { createAgentRestAuditMiddleware } from './agent-rest-audit.middleware.js';
4
+ export { createAuditRoutes } from './audit.routes.js';
5
+ export { RequestAudit } from './request-audit.js';
6
+ export { classifyToolCall, skillContaining, skillReadPath, splitUtcpName, type ClassifiedCall, type ClassifierContext, type SkillFolder, } from './event-classifier.js';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/modules/audit/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC;AACpC,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAChG,OAAO,EAAE,8BAA8B,EAAE,MAAM,kCAAkC,CAAC;AAClF,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,aAAa,EACb,aAAa,EACb,KAAK,cAAc,EACnB,KAAK,iBAAiB,EACtB,KAAK,WAAW,GACjB,MAAM,uBAAuB,CAAC"}
@@ -0,0 +1,7 @@
1
+ export * from './audit.contract.js';
2
+ export { AgentAuditService, MAX_EVENT_PAGE, retentionDaysFrom } from './agent-audit.service.js';
3
+ export { createAgentRestAuditMiddleware } from './agent-rest-audit.middleware.js';
4
+ export { createAuditRoutes } from './audit.routes.js';
5
+ export { RequestAudit } from './request-audit.js';
6
+ export { classifyToolCall, skillContaining, skillReadPath, splitUtcpName, } from './event-classifier.js';
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/modules/audit/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC;AACpC,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAChG,OAAO,EAAE,8BAA8B,EAAE,MAAM,kCAAkC,CAAC;AAClF,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,aAAa,EACb,aAAa,GAId,MAAM,uBAAuB,CAAC"}
@@ -0,0 +1,57 @@
1
+ import type { CodeModeUtcpClient } from '@utcp/code-mode';
2
+ import type { AgentEventOutcome, AuditPrincipalRef, IAgentEventRecorder } from './audit.contract.js';
3
+ import { type ClassifiedCall, type SkillFolder } from './event-classifier.js';
4
+ /**
5
+ * The Audit log's view of ONE MCP request: who is calling (the user and the
6
+ * key or agent connection the request arrived through), and how to name what
7
+ * they call. The MCP proxy creates one per request that has a principal and
8
+ * routes every call through it; a request with none (a browser JWT, an
9
+ * internal token minted before connections existed) gets no instance and
10
+ * records nothing.
11
+ *
12
+ * Holds nothing that outlives the request. The skill catalog it may need to
13
+ * tell a skill read from a file read is fetched at most once, and only when
14
+ * a platform read names a path.
15
+ */
16
+ export declare class RequestAudit {
17
+ private readonly recorder;
18
+ private readonly userId;
19
+ private readonly principal;
20
+ private readonly ctx;
21
+ private skills;
22
+ constructor(recorder: IAgentEventRecorder, userId: string, principal: AuditPrincipalRef, ctx: {
23
+ kbManualName: string;
24
+ /** Read at classification time — the surface (and its catalog names) is built lazily. */
25
+ catalogNames: () => ReadonlyMap<string, string>;
26
+ /** The caller's skill catalog, for telling a skill read from a file read. */
27
+ loadSkills: () => Promise<readonly SkillFolder[]>;
28
+ });
29
+ /** Record one already-classified call. Never throws. */
30
+ emit(call: ClassifiedCall, outcome: AgentEventOutcome, durationMs: number | null): void;
31
+ /** What a UTCP call is logged as — fetching the skill catalog first when the call might be a skill read. */
32
+ classify(utcpName: string, args: Record<string, unknown>): Promise<ClassifiedCall>;
33
+ /**
34
+ * Run a UTCP call and record it: `error` when it throws or `isError` says
35
+ * the result is one, `ok` otherwise. The result (or the throw) reaches the
36
+ * caller unchanged — recording is a side effect, never a filter.
37
+ *
38
+ * The call starts FIRST and is classified alongside: naming a read may cost
39
+ * a loopback fetch of the skill catalog, and that is the log's business,
40
+ * not the tool's — a tool result never waits on its own bookkeeping.
41
+ */
42
+ call<T>(utcpName: string, args: Record<string, unknown>, run: () => Promise<T>, isError: (result: T) => boolean): Promise<T>;
43
+ /** A call refused before it ran, because the caller's sign-in for its manual is missing. */
44
+ denied(utcpName: string, args: Record<string, unknown>): Promise<void>;
45
+ /**
46
+ * Record every tool call made from INSIDE `call_tool_chain`: the chain
47
+ * bridges each in-isolate tool function to `client.callTool`, so wrapping
48
+ * that one entry point catches them all. Installed after the downstream
49
+ * routing wrapper so the timing covers the pool lease and any retry too.
50
+ * The direct MCP path (`callToolStreaming`) is deliberately NOT wrapped —
51
+ * the proxy's tool handler records those itself, with the result's
52
+ * `isError` in hand.
53
+ */
54
+ instrumentChainCalls(client: CodeModeUtcpClient): void;
55
+ private skillCatalog;
56
+ }
57
+ //# sourceMappingURL=request-audit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request-audit.d.ts","sourceRoot":"","sources":["../../../src/modules/audit/request-audit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAE1D,OAAO,KAAK,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AACrG,OAAO,EAGL,KAAK,cAAc,EAEnB,KAAK,WAAW,EACjB,MAAM,uBAAuB,CAAC;AAI/B;;;;;;;;;;;GAWG;AACH,qBAAa,YAAY;IAIrB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,GAAG;IANtB,OAAO,CAAC,MAAM,CAAqD;gBAGhD,QAAQ,EAAE,mBAAmB,EAC7B,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,iBAAiB,EAC5B,GAAG,EAAE;QACpB,YAAY,EAAE,MAAM,CAAC;QACrB,yFAAyF;QACzF,YAAY,EAAE,MAAM,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAChD,6EAA6E;QAC7E,UAAU,EAAE,MAAM,OAAO,CAAC,SAAS,WAAW,EAAE,CAAC,CAAC;KACnD;IAGH,wDAAwD;IACxD,IAAI,CAAC,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAgBvF,4GAA4G;IACtG,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,cAAc,CAAC;IAOxF;;;;;;;;OAQG;IACG,IAAI,CAAC,CAAC,EACV,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,GAAG,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACrB,OAAO,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,OAAO,GAC9B,OAAO,CAAC,CAAC,CAAC;IAeb,4FAA4F;IACtF,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAI5E;;;;;;;;OAQG;IACH,oBAAoB,CAAC,MAAM,EAAE,kBAAkB,GAAG,IAAI;IAMtD,OAAO,CAAC,YAAY;CAUrB"}
@@ -0,0 +1,117 @@
1
+ import { logger } from '../../shared/logging.js';
2
+ import { classifyToolCall, skillReadPath, } from './event-classifier.js';
3
+ const log = logger('audit');
4
+ /**
5
+ * The Audit log's view of ONE MCP request: who is calling (the user and the
6
+ * key or agent connection the request arrived through), and how to name what
7
+ * they call. The MCP proxy creates one per request that has a principal and
8
+ * routes every call through it; a request with none (a browser JWT, an
9
+ * internal token minted before connections existed) gets no instance and
10
+ * records nothing.
11
+ *
12
+ * Holds nothing that outlives the request. The skill catalog it may need to
13
+ * tell a skill read from a file read is fetched at most once, and only when
14
+ * a platform read names a path.
15
+ */
16
+ export class RequestAudit {
17
+ recorder;
18
+ userId;
19
+ principal;
20
+ ctx;
21
+ skills;
22
+ constructor(recorder, userId, principal, ctx) {
23
+ this.recorder = recorder;
24
+ this.userId = userId;
25
+ this.principal = principal;
26
+ this.ctx = ctx;
27
+ }
28
+ /** Record one already-classified call. Never throws. */
29
+ emit(call, outcome, durationMs) {
30
+ try {
31
+ this.recorder.record({
32
+ userId: this.userId,
33
+ principal: this.principal,
34
+ kind: call.kind,
35
+ manual: call.manual,
36
+ name: call.name,
37
+ outcome,
38
+ durationMs,
39
+ });
40
+ }
41
+ catch (err) {
42
+ log.warn('recording an agent event failed:', { err });
43
+ }
44
+ }
45
+ /** What a UTCP call is logged as — fetching the skill catalog first when the call might be a skill read. */
46
+ async classify(utcpName, args) {
47
+ const ctx = { kbManualName: this.ctx.kbManualName, catalogNames: this.ctx.catalogNames() };
48
+ const wantsSkills = skillReadPath(utcpName, args, ctx.kbManualName) !== null;
49
+ const skills = wantsSkills ? await this.skillCatalog() : null;
50
+ return classifyToolCall(utcpName, args, ctx, skills);
51
+ }
52
+ /**
53
+ * Run a UTCP call and record it: `error` when it throws or `isError` says
54
+ * the result is one, `ok` otherwise. The result (or the throw) reaches the
55
+ * caller unchanged — recording is a side effect, never a filter.
56
+ *
57
+ * The call starts FIRST and is classified alongside: naming a read may cost
58
+ * a loopback fetch of the skill catalog, and that is the log's business,
59
+ * not the tool's — a tool result never waits on its own bookkeeping.
60
+ */
61
+ async call(utcpName, args, run, isError) {
62
+ const started = performance.now();
63
+ const classified = this.classify(utcpName, args);
64
+ try {
65
+ const result = await run();
66
+ const duration = elapsed(started);
67
+ this.emit(await classified, isError(result) ? 'error' : 'ok', duration);
68
+ return result;
69
+ }
70
+ catch (err) {
71
+ const duration = elapsed(started);
72
+ this.emit(await classified, 'error', duration);
73
+ throw err;
74
+ }
75
+ }
76
+ /** A call refused before it ran, because the caller's sign-in for its manual is missing. */
77
+ async denied(utcpName, args) {
78
+ this.emit(await this.classify(utcpName, args), 'denied', null);
79
+ }
80
+ /**
81
+ * Record every tool call made from INSIDE `call_tool_chain`: the chain
82
+ * bridges each in-isolate tool function to `client.callTool`, so wrapping
83
+ * that one entry point catches them all. Installed after the downstream
84
+ * routing wrapper so the timing covers the pool lease and any retry too.
85
+ * The direct MCP path (`callToolStreaming`) is deliberately NOT wrapped —
86
+ * the proxy's tool handler records those itself, with the result's
87
+ * `isError` in hand.
88
+ */
89
+ instrumentChainCalls(client) {
90
+ const callTool = client.callTool.bind(client);
91
+ client.callTool = (toolName, toolArgs) => this.call(toolName, toolArgs, () => callTool(toolName, toolArgs), isErrorShaped);
92
+ }
93
+ skillCatalog() {
94
+ // A failed fetch is remembered as "no catalog" for the rest of the
95
+ // request: the read is then logged as a capability, and one hiccup does
96
+ // not cost a loopback round-trip per subsequent call.
97
+ this.skills ??= this.ctx.loadSkills().catch((err) => {
98
+ log.warn('skill catalog unavailable for audit classification:', { err });
99
+ return null;
100
+ });
101
+ return this.skills;
102
+ }
103
+ }
104
+ function elapsed(started) {
105
+ return Math.max(0, Math.round(performance.now() - started));
106
+ }
107
+ /**
108
+ * A chain-internal result that is the tool saying it failed. A downstream
109
+ * MCP server answers a failed call with `{ isError: true, content }` as a
110
+ * VALUE (the UTCP mcp protocol hands it through rather than throwing), and
111
+ * the direct path records that as an error — so must the chain path, or the
112
+ * same failure would read `ok` inside a chain and `error` outside one.
113
+ */
114
+ function isErrorShaped(result) {
115
+ return typeof result === 'object' && result !== null && result.isError === true;
116
+ }
117
+ //# sourceMappingURL=request-audit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request-audit.js","sourceRoot":"","sources":["../../../src/modules/audit/request-audit.ts"],"names":[],"mappings":"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"}