@synth-deploy/server 0.1.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 (317) hide show
  1. package/dist/agent/debrief-retention.d.ts +12 -0
  2. package/dist/agent/debrief-retention.d.ts.map +1 -0
  3. package/dist/agent/debrief-retention.js +27 -0
  4. package/dist/agent/debrief-retention.js.map +1 -0
  5. package/dist/agent/envoy-client.d.ts +216 -0
  6. package/dist/agent/envoy-client.d.ts.map +1 -0
  7. package/dist/agent/envoy-client.js +266 -0
  8. package/dist/agent/envoy-client.js.map +1 -0
  9. package/dist/agent/envoy-registry.d.ts +102 -0
  10. package/dist/agent/envoy-registry.d.ts.map +1 -0
  11. package/dist/agent/envoy-registry.js +319 -0
  12. package/dist/agent/envoy-registry.js.map +1 -0
  13. package/dist/agent/health-checker.d.ts +39 -0
  14. package/dist/agent/health-checker.d.ts.map +1 -0
  15. package/dist/agent/health-checker.js +49 -0
  16. package/dist/agent/health-checker.js.map +1 -0
  17. package/dist/agent/mcp-client-manager.d.ts +36 -0
  18. package/dist/agent/mcp-client-manager.d.ts.map +1 -0
  19. package/dist/agent/mcp-client-manager.js +106 -0
  20. package/dist/agent/mcp-client-manager.js.map +1 -0
  21. package/dist/agent/stale-deployment-detector.d.ts +15 -0
  22. package/dist/agent/stale-deployment-detector.d.ts.map +1 -0
  23. package/dist/agent/stale-deployment-detector.js +50 -0
  24. package/dist/agent/stale-deployment-detector.js.map +1 -0
  25. package/dist/agent/step-runner.d.ts +31 -0
  26. package/dist/agent/step-runner.d.ts.map +1 -0
  27. package/dist/agent/step-runner.js +80 -0
  28. package/dist/agent/step-runner.js.map +1 -0
  29. package/dist/agent/synth-agent.d.ts +168 -0
  30. package/dist/agent/synth-agent.d.ts.map +1 -0
  31. package/dist/agent/synth-agent.js +1195 -0
  32. package/dist/agent/synth-agent.js.map +1 -0
  33. package/dist/api/agent.d.ts +36 -0
  34. package/dist/api/agent.d.ts.map +1 -0
  35. package/dist/api/agent.js +867 -0
  36. package/dist/api/agent.js.map +1 -0
  37. package/dist/api/api-keys.d.ts +4 -0
  38. package/dist/api/api-keys.d.ts.map +1 -0
  39. package/dist/api/api-keys.js +118 -0
  40. package/dist/api/api-keys.js.map +1 -0
  41. package/dist/api/artifacts.d.ts +5 -0
  42. package/dist/api/artifacts.d.ts.map +1 -0
  43. package/dist/api/artifacts.js +142 -0
  44. package/dist/api/artifacts.js.map +1 -0
  45. package/dist/api/auth.d.ts +4 -0
  46. package/dist/api/auth.d.ts.map +1 -0
  47. package/dist/api/auth.js +280 -0
  48. package/dist/api/auth.js.map +1 -0
  49. package/dist/api/deployments.d.ts +11 -0
  50. package/dist/api/deployments.d.ts.map +1 -0
  51. package/dist/api/deployments.js +1098 -0
  52. package/dist/api/deployments.js.map +1 -0
  53. package/dist/api/environments.d.ts +5 -0
  54. package/dist/api/environments.d.ts.map +1 -0
  55. package/dist/api/environments.js +69 -0
  56. package/dist/api/environments.js.map +1 -0
  57. package/dist/api/envoy-reports.d.ts +17 -0
  58. package/dist/api/envoy-reports.d.ts.map +1 -0
  59. package/dist/api/envoy-reports.js +138 -0
  60. package/dist/api/envoy-reports.js.map +1 -0
  61. package/dist/api/envoys.d.ts +5 -0
  62. package/dist/api/envoys.d.ts.map +1 -0
  63. package/dist/api/envoys.js +192 -0
  64. package/dist/api/envoys.js.map +1 -0
  65. package/dist/api/fleet.d.ts +11 -0
  66. package/dist/api/fleet.d.ts.map +1 -0
  67. package/dist/api/fleet.js +394 -0
  68. package/dist/api/fleet.js.map +1 -0
  69. package/dist/api/graph.d.ts +8 -0
  70. package/dist/api/graph.d.ts.map +1 -0
  71. package/dist/api/graph.js +355 -0
  72. package/dist/api/graph.js.map +1 -0
  73. package/dist/api/health.d.ts +20 -0
  74. package/dist/api/health.d.ts.map +1 -0
  75. package/dist/api/health.js +248 -0
  76. package/dist/api/health.js.map +1 -0
  77. package/dist/api/idp-schemas.d.ts +41 -0
  78. package/dist/api/idp-schemas.d.ts.map +1 -0
  79. package/dist/api/idp-schemas.js +17 -0
  80. package/dist/api/idp-schemas.js.map +1 -0
  81. package/dist/api/idp.d.ts +6 -0
  82. package/dist/api/idp.d.ts.map +1 -0
  83. package/dist/api/idp.js +620 -0
  84. package/dist/api/idp.js.map +1 -0
  85. package/dist/api/intake.d.ts +10 -0
  86. package/dist/api/intake.d.ts.map +1 -0
  87. package/dist/api/intake.js +418 -0
  88. package/dist/api/intake.js.map +1 -0
  89. package/dist/api/partitions.d.ts +5 -0
  90. package/dist/api/partitions.d.ts.map +1 -0
  91. package/dist/api/partitions.js +113 -0
  92. package/dist/api/partitions.js.map +1 -0
  93. package/dist/api/progress-event-store.d.ts +62 -0
  94. package/dist/api/progress-event-store.d.ts.map +1 -0
  95. package/dist/api/progress-event-store.js +118 -0
  96. package/dist/api/progress-event-store.js.map +1 -0
  97. package/dist/api/schemas.d.ts +1000 -0
  98. package/dist/api/schemas.d.ts.map +1 -0
  99. package/dist/api/schemas.js +328 -0
  100. package/dist/api/schemas.js.map +1 -0
  101. package/dist/api/security-boundaries.d.ts +4 -0
  102. package/dist/api/security-boundaries.d.ts.map +1 -0
  103. package/dist/api/security-boundaries.js +32 -0
  104. package/dist/api/security-boundaries.js.map +1 -0
  105. package/dist/api/settings.d.ts +4 -0
  106. package/dist/api/settings.d.ts.map +1 -0
  107. package/dist/api/settings.js +99 -0
  108. package/dist/api/settings.js.map +1 -0
  109. package/dist/api/system.d.ts +75 -0
  110. package/dist/api/system.d.ts.map +1 -0
  111. package/dist/api/system.js +558 -0
  112. package/dist/api/system.js.map +1 -0
  113. package/dist/api/telemetry.d.ts +4 -0
  114. package/dist/api/telemetry.d.ts.map +1 -0
  115. package/dist/api/telemetry.js +24 -0
  116. package/dist/api/telemetry.js.map +1 -0
  117. package/dist/api/users.d.ts +4 -0
  118. package/dist/api/users.d.ts.map +1 -0
  119. package/dist/api/users.js +173 -0
  120. package/dist/api/users.js.map +1 -0
  121. package/dist/archive-unpacker.d.ts +24 -0
  122. package/dist/archive-unpacker.d.ts.map +1 -0
  123. package/dist/archive-unpacker.js +239 -0
  124. package/dist/archive-unpacker.js.map +1 -0
  125. package/dist/artifact-analyzer.d.ts +59 -0
  126. package/dist/artifact-analyzer.d.ts.map +1 -0
  127. package/dist/artifact-analyzer.js +334 -0
  128. package/dist/artifact-analyzer.js.map +1 -0
  129. package/dist/auth/idp/index.d.ts +9 -0
  130. package/dist/auth/idp/index.d.ts.map +1 -0
  131. package/dist/auth/idp/index.js +5 -0
  132. package/dist/auth/idp/index.js.map +1 -0
  133. package/dist/auth/idp/ldap.d.ts +56 -0
  134. package/dist/auth/idp/ldap.d.ts.map +1 -0
  135. package/dist/auth/idp/ldap.js +276 -0
  136. package/dist/auth/idp/ldap.js.map +1 -0
  137. package/dist/auth/idp/oidc.d.ts +27 -0
  138. package/dist/auth/idp/oidc.d.ts.map +1 -0
  139. package/dist/auth/idp/oidc.js +97 -0
  140. package/dist/auth/idp/oidc.js.map +1 -0
  141. package/dist/auth/idp/role-mapping.d.ts +9 -0
  142. package/dist/auth/idp/role-mapping.d.ts.map +1 -0
  143. package/dist/auth/idp/role-mapping.js +16 -0
  144. package/dist/auth/idp/role-mapping.js.map +1 -0
  145. package/dist/auth/idp/saml.d.ts +40 -0
  146. package/dist/auth/idp/saml.d.ts.map +1 -0
  147. package/dist/auth/idp/saml.js +117 -0
  148. package/dist/auth/idp/saml.js.map +1 -0
  149. package/dist/auth/idp/types.d.ts +23 -0
  150. package/dist/auth/idp/types.d.ts.map +1 -0
  151. package/dist/auth/idp/types.js +2 -0
  152. package/dist/auth/idp/types.js.map +1 -0
  153. package/dist/fleet/fleet-executor.d.ts +35 -0
  154. package/dist/fleet/fleet-executor.d.ts.map +1 -0
  155. package/dist/fleet/fleet-executor.js +228 -0
  156. package/dist/fleet/fleet-executor.js.map +1 -0
  157. package/dist/fleet/fleet-store.d.ts +13 -0
  158. package/dist/fleet/fleet-store.d.ts.map +1 -0
  159. package/dist/fleet/fleet-store.js +13 -0
  160. package/dist/fleet/fleet-store.js.map +1 -0
  161. package/dist/fleet/index.d.ts +5 -0
  162. package/dist/fleet/index.d.ts.map +1 -0
  163. package/dist/fleet/index.js +4 -0
  164. package/dist/fleet/index.js.map +1 -0
  165. package/dist/fleet/representative-selector.d.ts +15 -0
  166. package/dist/fleet/representative-selector.d.ts.map +1 -0
  167. package/dist/fleet/representative-selector.js +71 -0
  168. package/dist/fleet/representative-selector.js.map +1 -0
  169. package/dist/graph/graph-executor.d.ts +36 -0
  170. package/dist/graph/graph-executor.d.ts.map +1 -0
  171. package/dist/graph/graph-executor.js +348 -0
  172. package/dist/graph/graph-executor.js.map +1 -0
  173. package/dist/graph/graph-inference.d.ts +22 -0
  174. package/dist/graph/graph-inference.d.ts.map +1 -0
  175. package/dist/graph/graph-inference.js +149 -0
  176. package/dist/graph/graph-inference.js.map +1 -0
  177. package/dist/graph/graph-store.d.ts +12 -0
  178. package/dist/graph/graph-store.d.ts.map +1 -0
  179. package/dist/graph/graph-store.js +61 -0
  180. package/dist/graph/graph-store.js.map +1 -0
  181. package/dist/graph/index.d.ts +5 -0
  182. package/dist/graph/index.d.ts.map +1 -0
  183. package/dist/graph/index.js +4 -0
  184. package/dist/graph/index.js.map +1 -0
  185. package/dist/index.d.ts +2 -0
  186. package/dist/index.d.ts.map +1 -0
  187. package/dist/index.js +837 -0
  188. package/dist/index.js.map +1 -0
  189. package/dist/intake/index.d.ts +6 -0
  190. package/dist/intake/index.d.ts.map +1 -0
  191. package/dist/intake/index.js +5 -0
  192. package/dist/intake/index.js.map +1 -0
  193. package/dist/intake/intake-processor.d.ts +17 -0
  194. package/dist/intake/intake-processor.d.ts.map +1 -0
  195. package/dist/intake/intake-processor.js +99 -0
  196. package/dist/intake/intake-processor.js.map +1 -0
  197. package/dist/intake/intake-store.d.ts +7 -0
  198. package/dist/intake/intake-store.d.ts.map +1 -0
  199. package/dist/intake/intake-store.js +7 -0
  200. package/dist/intake/intake-store.js.map +1 -0
  201. package/dist/intake/registry-poller.d.ts +41 -0
  202. package/dist/intake/registry-poller.d.ts.map +1 -0
  203. package/dist/intake/registry-poller.js +202 -0
  204. package/dist/intake/registry-poller.js.map +1 -0
  205. package/dist/intake/webhook-handlers.d.ts +37 -0
  206. package/dist/intake/webhook-handlers.d.ts.map +1 -0
  207. package/dist/intake/webhook-handlers.js +268 -0
  208. package/dist/intake/webhook-handlers.js.map +1 -0
  209. package/dist/logger.d.ts +5 -0
  210. package/dist/logger.d.ts.map +1 -0
  211. package/dist/logger.js +15 -0
  212. package/dist/logger.js.map +1 -0
  213. package/dist/mcp/resources.d.ts +9 -0
  214. package/dist/mcp/resources.d.ts.map +1 -0
  215. package/dist/mcp/resources.js +72 -0
  216. package/dist/mcp/resources.js.map +1 -0
  217. package/dist/mcp/server.d.ts +15 -0
  218. package/dist/mcp/server.d.ts.map +1 -0
  219. package/dist/mcp/server.js +20 -0
  220. package/dist/mcp/server.js.map +1 -0
  221. package/dist/mcp/tools.d.ts +9 -0
  222. package/dist/mcp/tools.d.ts.map +1 -0
  223. package/dist/mcp/tools.js +88 -0
  224. package/dist/mcp/tools.js.map +1 -0
  225. package/dist/middleware/auth.d.ts +29 -0
  226. package/dist/middleware/auth.d.ts.map +1 -0
  227. package/dist/middleware/auth.js +76 -0
  228. package/dist/middleware/auth.js.map +1 -0
  229. package/dist/middleware/permissions.d.ts +13 -0
  230. package/dist/middleware/permissions.d.ts.map +1 -0
  231. package/dist/middleware/permissions.js +32 -0
  232. package/dist/middleware/permissions.js.map +1 -0
  233. package/dist/pattern-store.d.ts +104 -0
  234. package/dist/pattern-store.d.ts.map +1 -0
  235. package/dist/pattern-store.js +299 -0
  236. package/dist/pattern-store.js.map +1 -0
  237. package/package.json +54 -0
  238. package/src/agent/debrief-retention.ts +44 -0
  239. package/src/agent/envoy-client.ts +474 -0
  240. package/src/agent/envoy-registry.ts +384 -0
  241. package/src/agent/health-checker.ts +70 -0
  242. package/src/agent/mcp-client-manager.ts +131 -0
  243. package/src/agent/stale-deployment-detector.ts +79 -0
  244. package/src/agent/step-runner.ts +124 -0
  245. package/src/agent/synth-agent.ts +1567 -0
  246. package/src/api/agent.ts +1075 -0
  247. package/src/api/api-keys.ts +129 -0
  248. package/src/api/artifacts.ts +194 -0
  249. package/src/api/auth.ts +320 -0
  250. package/src/api/deployments.ts +1347 -0
  251. package/src/api/environments.ts +97 -0
  252. package/src/api/envoy-reports.ts +159 -0
  253. package/src/api/envoys.ts +237 -0
  254. package/src/api/fleet.ts +510 -0
  255. package/src/api/graph.ts +516 -0
  256. package/src/api/health.ts +311 -0
  257. package/src/api/idp-schemas.ts +19 -0
  258. package/src/api/idp.ts +735 -0
  259. package/src/api/intake.ts +537 -0
  260. package/src/api/partitions.ts +147 -0
  261. package/src/api/progress-event-store.ts +153 -0
  262. package/src/api/schemas.ts +376 -0
  263. package/src/api/security-boundaries.ts +54 -0
  264. package/src/api/settings.ts +118 -0
  265. package/src/api/system.ts +704 -0
  266. package/src/api/telemetry.ts +32 -0
  267. package/src/api/users.ts +210 -0
  268. package/src/archive-unpacker.ts +271 -0
  269. package/src/artifact-analyzer.ts +438 -0
  270. package/src/auth/idp/index.ts +8 -0
  271. package/src/auth/idp/ldap.ts +340 -0
  272. package/src/auth/idp/oidc.ts +117 -0
  273. package/src/auth/idp/role-mapping.ts +22 -0
  274. package/src/auth/idp/saml.ts +148 -0
  275. package/src/auth/idp/types.ts +22 -0
  276. package/src/fleet/fleet-executor.ts +309 -0
  277. package/src/fleet/fleet-store.ts +13 -0
  278. package/src/fleet/index.ts +4 -0
  279. package/src/fleet/representative-selector.ts +83 -0
  280. package/src/graph/graph-executor.ts +446 -0
  281. package/src/graph/graph-inference.ts +184 -0
  282. package/src/graph/graph-store.ts +75 -0
  283. package/src/graph/index.ts +4 -0
  284. package/src/index.ts +916 -0
  285. package/src/intake/index.ts +5 -0
  286. package/src/intake/intake-processor.ts +111 -0
  287. package/src/intake/intake-store.ts +7 -0
  288. package/src/intake/registry-poller.ts +230 -0
  289. package/src/intake/webhook-handlers.ts +328 -0
  290. package/src/logger.ts +19 -0
  291. package/src/mcp/resources.ts +98 -0
  292. package/src/mcp/server.ts +34 -0
  293. package/src/mcp/tools.ts +117 -0
  294. package/src/middleware/auth.ts +103 -0
  295. package/src/middleware/permissions.ts +35 -0
  296. package/src/pattern-store.ts +409 -0
  297. package/tests/agent-mode.test.ts +536 -0
  298. package/tests/api-handlers.test.ts +1245 -0
  299. package/tests/archive-unpacker.test.ts +179 -0
  300. package/tests/artifact-analyzer.test.ts +240 -0
  301. package/tests/auth-middleware.test.ts +189 -0
  302. package/tests/decision-diary.test.ts +957 -0
  303. package/tests/diary-reader.test.ts +782 -0
  304. package/tests/envoy-client.test.ts +342 -0
  305. package/tests/envoy-reports.test.ts +156 -0
  306. package/tests/mcp-tools.test.ts +213 -0
  307. package/tests/orchestration.test.ts +536 -0
  308. package/tests/partition-deletion.test.ts +143 -0
  309. package/tests/partition-isolation.test.ts +830 -0
  310. package/tests/pattern-store.test.ts +371 -0
  311. package/tests/rbac-enforcement.test.ts +409 -0
  312. package/tests/ssrf-validation.test.ts +56 -0
  313. package/tests/stale-deployment.test.ts +85 -0
  314. package/tests/step-runner.test.ts +308 -0
  315. package/tests/ui-journey.test.ts +330 -0
  316. package/tsconfig.json +11 -0
  317. package/vitest.config.ts +27 -0
@@ -0,0 +1,76 @@
1
+ import { SignJWT, jwtVerify } from "jose";
2
+ const EXEMPT_ROUTES = ["/health", "/api/health", "/api/auth/login", "/api/auth/register", "/api/auth/refresh", "/api/auth/status", "/api/auth/providers", "/api/envoy/report"];
3
+ const EXEMPT_PREFIXES = ["/api/auth/oidc/", "/api/auth/callback/oidc/", "/api/auth/saml/", "/api/auth/callback/saml/", "/api/auth/ldap/", "/api/intake/webhook/"];
4
+ // Envoy callback endpoints — validated by envoy token, not user JWT
5
+ const EXEMPT_PATTERNS = [/^\/api\/deployments\/[^/]+\/progress$/];
6
+ /**
7
+ * Registers JWT-based authentication middleware on a Fastify instance.
8
+ *
9
+ * All /api/ and /mcp routes require a valid JWT Bearer token,
10
+ * except routes listed in EXEMPT_ROUTES. Static file serving and
11
+ * health endpoints are always accessible.
12
+ */
13
+ export function registerAuthMiddleware(app, userStore, userRoleStore, sessionStore, jwtSecret) {
14
+ app.addHook("onRequest", async (request, reply) => {
15
+ // Skip exempt routes
16
+ if (EXEMPT_ROUTES.some((r) => request.url.startsWith(r)))
17
+ return;
18
+ // Skip OIDC auth routes (dynamic paths)
19
+ if (EXEMPT_PREFIXES.some((p) => request.url.startsWith(p)))
20
+ return;
21
+ // Skip envoy callback endpoints (validated by envoy token in the route handler)
22
+ if (EXEMPT_PATTERNS.some((p) => p.test(request.url)))
23
+ return;
24
+ // Also skip static file serving (non-API routes), but NOT /mcp — it requires auth
25
+ if (!request.url.startsWith("/api/") && !request.url.startsWith("/mcp"))
26
+ return;
27
+ // Accept token from Authorization header or ?token= query param
28
+ // (EventSource API cannot send headers, so SSE endpoints use query param)
29
+ const authHeader = request.headers.authorization;
30
+ const queryToken = request.query?.token;
31
+ const rawToken = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : queryToken;
32
+ if (!rawToken) {
33
+ reply.status(401).send({ error: "Authentication required" });
34
+ return;
35
+ }
36
+ const token = rawToken;
37
+ try {
38
+ const { payload } = await jwtVerify(token, jwtSecret);
39
+ const userId = payload.sub;
40
+ const user = userStore.getById(userId);
41
+ if (!user) {
42
+ reply.status(401).send({ error: "User not found" });
43
+ return;
44
+ }
45
+ const session = sessionStore.getByToken(token);
46
+ if (!session) {
47
+ reply.status(401).send({ error: "Session expired" });
48
+ return;
49
+ }
50
+ const permissions = userRoleStore.getUserPermissions(userId);
51
+ request.user = { id: userId, email: user.email, name: user.name, permissions };
52
+ }
53
+ catch {
54
+ reply.status(401).send({ error: "Invalid token" });
55
+ }
56
+ });
57
+ return { enabled: true };
58
+ }
59
+ export async function generateTokens(userId, jwtSecret) {
60
+ const sessionTtl = process.env.SYNTH_SESSION_TTL ?? "8h";
61
+ const sessionTtlMs = sessionTtl.endsWith("h")
62
+ ? parseInt(sessionTtl) * 60 * 60 * 1000
63
+ : sessionTtl.endsWith("m")
64
+ ? parseInt(sessionTtl) * 60 * 1000
65
+ : 8 * 60 * 60 * 1000;
66
+ const token = await new SignJWT({ sub: userId })
67
+ .setProtectedHeader({ alg: "HS256" })
68
+ .setExpirationTime(sessionTtl)
69
+ .sign(jwtSecret);
70
+ const refreshToken = await new SignJWT({ sub: userId, type: "refresh" })
71
+ .setProtectedHeader({ alg: "HS256" })
72
+ .setExpirationTime("7d")
73
+ .sign(jwtSecret);
74
+ return { token, refreshToken, expiresAt: new Date(Date.now() + sessionTtlMs) };
75
+ }
76
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/middleware/auth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import type { FastifyRequest, FastifyReply } from "fastify";
2
+ import type { Permission, EnterpriseFeature } from "@synth-deploy/core";
3
+ /**
4
+ * Returns a Fastify preHandler that checks whether the authenticated user
5
+ * has all of the specified permissions.
6
+ */
7
+ export declare function requirePermission(...permissions: Permission[]): (request: FastifyRequest, reply: FastifyReply) => Promise<void>;
8
+ /**
9
+ * Returns a Fastify preHandler that gates an enterprise-only feature.
10
+ * Throws EditionError (caught by global error handler → 402) on Community edition.
11
+ */
12
+ export declare function requireEdition(feature: EnterpriseFeature): (_request: FastifyRequest, _reply: FastifyReply) => Promise<void>;
13
+ //# sourceMappingURL=permissions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../../src/middleware/permissions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC5D,OAAO,KAAK,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAGxE;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,WAAW,EAAE,UAAU,EAAE,IAC9C,SAAS,cAAc,EAAE,OAAO,YAAY,mBAe3D;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,iBAAiB,IACzC,UAAU,cAAc,EAAE,QAAQ,YAAY,mBAG7D"}
@@ -0,0 +1,32 @@
1
+ import { requireEnterprise } from "@synth-deploy/core";
2
+ /**
3
+ * Returns a Fastify preHandler that checks whether the authenticated user
4
+ * has all of the specified permissions.
5
+ */
6
+ export function requirePermission(...permissions) {
7
+ return async (request, reply) => {
8
+ const user = request.user;
9
+ if (!user) {
10
+ reply.status(401).send({ error: "Authentication required" });
11
+ return;
12
+ }
13
+ const missing = permissions.filter((p) => !user.permissions.includes(p));
14
+ if (missing.length > 0) {
15
+ reply.status(403).send({
16
+ error: "Forbidden",
17
+ required: permissions,
18
+ message: `This action requires: ${missing.join(", ")}`,
19
+ });
20
+ }
21
+ };
22
+ }
23
+ /**
24
+ * Returns a Fastify preHandler that gates an enterprise-only feature.
25
+ * Throws EditionError (caught by global error handler → 402) on Community edition.
26
+ */
27
+ export function requireEdition(feature) {
28
+ return async (_request, _reply) => {
29
+ requireEnterprise(feature);
30
+ };
31
+ }
32
+ //# sourceMappingURL=permissions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permissions.js","sourceRoot":"","sources":["../../src/middleware/permissions.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAAuB,MAAM,oBAAoB,CAAC;AAE5E;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,GAAG,WAAyB;IAC5D,OAAO,KAAK,EAAE,OAAuB,EAAE,KAAmB,EAAE,EAAE;QAC5D,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;QAC1B,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,yBAAyB,EAAE,CAAC,CAAC;YAC7D,OAAO;QACT,CAAC;QACD,MAAM,OAAO,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;QACzE,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACvB,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;gBACrB,KAAK,EAAE,WAAW;gBAClB,QAAQ,EAAE,WAAW;gBACrB,OAAO,EAAE,yBAAyB,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;aACvD,CAAC,CAAC;QACL,CAAC;IACH,CAAC,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,OAA0B;IACvD,OAAO,KAAK,EAAE,QAAwB,EAAE,MAAoB,EAAE,EAAE;QAC9D,iBAAiB,CAAC,OAAO,CAAC,CAAC;IAC7B,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,104 @@
1
+ export interface CorrectionRecord {
2
+ timestamp: Date;
3
+ field: string;
4
+ from: string;
5
+ to: string;
6
+ artifactId: string;
7
+ }
8
+ export interface AnalysisPattern {
9
+ id: string;
10
+ source: string;
11
+ artifactType: string;
12
+ namePattern: string;
13
+ corrections: CorrectionRecord[];
14
+ derivedAnalysis: DerivedAnalysis;
15
+ confidence: number;
16
+ appliedCount: number;
17
+ createdAt: Date;
18
+ updatedAt: Date;
19
+ }
20
+ /**
21
+ * The subset of ArtifactAnalysis fields that can be derived from pattern
22
+ * corrections. Each field is optional — only corrected fields are stored.
23
+ */
24
+ export interface DerivedAnalysis {
25
+ summary?: string;
26
+ dependencies?: string[];
27
+ configurationExpectations?: Record<string, string>;
28
+ deploymentIntent?: string;
29
+ }
30
+ export interface PatternMatch {
31
+ pattern: AnalysisPattern;
32
+ /** "auto" when confidence >= 0.7 and >= 2 corrections; "suggest" otherwise */
33
+ mode: "auto" | "suggest";
34
+ }
35
+ /**
36
+ * Recomputes confidence from a correction history.
37
+ *
38
+ * - First correction: 0.5
39
+ * - Each subsequent correction that agrees with the current value: +0.15
40
+ * - A contradictory correction (same field, different `to` value as the
41
+ * last correction for that field) resets to 0.5
42
+ * - Capped at 0.95
43
+ */
44
+ export declare function computeConfidence(corrections: CorrectionRecord[]): number;
45
+ /**
46
+ * SQLite-backed storage for artifact analysis patterns.
47
+ *
48
+ * Patterns capture corrections users make to LLM-generated artifact analyses.
49
+ * When enough consistent corrections accumulate for a given source + type +
50
+ * name combination, the system can auto-apply the learned corrections instead
51
+ * of re-running LLM analysis.
52
+ */
53
+ export declare class PatternStore {
54
+ private db;
55
+ private stmts;
56
+ constructor(dbPath: string);
57
+ /**
58
+ * Record a correction against a pattern, creating the pattern if it doesn't
59
+ * exist. Returns the updated pattern.
60
+ */
61
+ recordCorrection(key: {
62
+ source: string;
63
+ artifactType: string;
64
+ namePattern: string;
65
+ }, correction: Omit<CorrectionRecord, "timestamp">): AnalysisPattern;
66
+ /**
67
+ * Find patterns matching a given artifact by source + type, then filter by
68
+ * name glob match. Returns patterns sorted by confidence (descending).
69
+ */
70
+ findMatches(source: string, artifactType: string, artifactName: string): PatternMatch[];
71
+ /**
72
+ * Record that a pattern was applied to an artifact.
73
+ */
74
+ recordApplication(patternId: string): void;
75
+ /**
76
+ * Get a pattern by ID.
77
+ */
78
+ getById(id: string): AnalysisPattern | undefined;
79
+ /**
80
+ * List all patterns, most recently updated first.
81
+ */
82
+ listAll(): AnalysisPattern[];
83
+ /**
84
+ * Delete a pattern.
85
+ */
86
+ delete(id: string): boolean;
87
+ /**
88
+ * Close the database connection.
89
+ */
90
+ close(): void;
91
+ private _findExact;
92
+ /**
93
+ * Build a DerivedAnalysis by taking the latest correction `to` value for
94
+ * each known field. Supports: summary, deploymentIntent, dependencies
95
+ * (comma-separated), and configurationExpectations (key=value).
96
+ */
97
+ private _buildDerivedAnalysis;
98
+ /**
99
+ * Simple glob matching: supports `*` (any characters) and `?` (single char).
100
+ * Used to match artifact names against stored name patterns.
101
+ */
102
+ private _globMatch;
103
+ }
104
+ //# sourceMappingURL=pattern-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pattern-store.d.ts","sourceRoot":"","sources":["../src/pattern-store.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,IAAI,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,gBAAgB,EAAE,CAAC;IAChC,eAAe,EAAE,eAAe,CAAC;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;CACjB;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,yBAAyB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnD,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,eAAe,CAAC;IACzB,8EAA8E;IAC9E,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;CAC1B;AAWD;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,gBAAgB,EAAE,GAAG,MAAM,CAqBzE;AA6CD;;;;;;;GAOG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,EAAE,CAAoB;IAC9B,OAAO,CAAC,KAAK,CAQX;gBAEU,MAAM,EAAE,MAAM;IAwD1B;;;OAGG;IACH,gBAAgB,CACd,GAAG,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,EAClE,UAAU,EAAE,IAAI,CAAC,gBAAgB,EAAE,WAAW,CAAC,GAC9C,eAAe;IA+DlB;;;OAGG;IACH,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,YAAY,EAAE;IAmBvF;;OAEG;IACH,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAO1C;;OAEG;IACH,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS;IAKhD;;OAEG;IACH,OAAO,IAAI,eAAe,EAAE;IAK5B;;OAEG;IACH,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAK3B;;OAEG;IACH,KAAK,IAAI,IAAI;IAQb,OAAO,CAAC,UAAU;IAclB;;;;OAIG;IACH,OAAO,CAAC,qBAAqB;IAkC7B;;;OAGG;IACH,OAAO,CAAC,UAAU;CAQnB"}
@@ -0,0 +1,299 @@
1
+ import crypto from "node:crypto";
2
+ import Database from "better-sqlite3";
3
+ // ---------------------------------------------------------------------------
4
+ // Confidence calculation
5
+ // ---------------------------------------------------------------------------
6
+ const INITIAL_CORRECTION_CONFIDENCE = 0.5;
7
+ const CONSISTENT_CORRECTION_BOOST = 0.15;
8
+ const CONTRADICTION_RESET_CONFIDENCE = 0.5;
9
+ const MAX_CONFIDENCE = 0.95;
10
+ /**
11
+ * Recomputes confidence from a correction history.
12
+ *
13
+ * - First correction: 0.5
14
+ * - Each subsequent correction that agrees with the current value: +0.15
15
+ * - A contradictory correction (same field, different `to` value as the
16
+ * last correction for that field) resets to 0.5
17
+ * - Capped at 0.95
18
+ */
19
+ export function computeConfidence(corrections) {
20
+ if (corrections.length === 0)
21
+ return 0;
22
+ let confidence = INITIAL_CORRECTION_CONFIDENCE;
23
+ // Track the latest `to` value per field to detect contradictions
24
+ const latestByField = new Map();
25
+ for (const c of corrections) {
26
+ const prev = latestByField.get(c.field);
27
+ if (prev !== undefined && prev !== c.to) {
28
+ // Contradictory correction — reset
29
+ confidence = CONTRADICTION_RESET_CONFIDENCE;
30
+ }
31
+ else if (prev !== undefined) {
32
+ // Consistent — boost
33
+ confidence = Math.min(confidence + CONSISTENT_CORRECTION_BOOST, MAX_CONFIDENCE);
34
+ }
35
+ // First correction for this field — confidence stays at initial
36
+ latestByField.set(c.field, c.to);
37
+ }
38
+ return Math.round(confidence * 100) / 100; // avoid floating-point noise
39
+ }
40
+ function rowToPattern(row) {
41
+ const corrections = JSON.parse(row.corrections).map((c) => ({
42
+ ...c,
43
+ timestamp: new Date(c.timestamp),
44
+ }));
45
+ return {
46
+ id: row.id,
47
+ source: row.source,
48
+ artifactType: row.artifact_type,
49
+ namePattern: row.name_pattern,
50
+ corrections,
51
+ derivedAnalysis: JSON.parse(row.derived_analysis),
52
+ confidence: row.confidence,
53
+ appliedCount: row.applied_count,
54
+ createdAt: new Date(row.created_at),
55
+ updatedAt: new Date(row.updated_at),
56
+ };
57
+ }
58
+ // ---------------------------------------------------------------------------
59
+ // PatternStore
60
+ // ---------------------------------------------------------------------------
61
+ /**
62
+ * SQLite-backed storage for artifact analysis patterns.
63
+ *
64
+ * Patterns capture corrections users make to LLM-generated artifact analyses.
65
+ * When enough consistent corrections accumulate for a given source + type +
66
+ * name combination, the system can auto-apply the learned corrections instead
67
+ * of re-running LLM analysis.
68
+ */
69
+ export class PatternStore {
70
+ db;
71
+ stmts;
72
+ constructor(dbPath) {
73
+ this.db = new Database(dbPath);
74
+ this.db.pragma("journal_mode = WAL");
75
+ this.db.pragma("foreign_keys = ON");
76
+ this.db.exec(`
77
+ CREATE TABLE IF NOT EXISTS analysis_patterns (
78
+ id TEXT PRIMARY KEY,
79
+ source TEXT NOT NULL,
80
+ artifact_type TEXT NOT NULL,
81
+ name_pattern TEXT NOT NULL,
82
+ corrections TEXT NOT NULL DEFAULT '[]',
83
+ derived_analysis TEXT NOT NULL DEFAULT '{}',
84
+ confidence REAL NOT NULL DEFAULT 0,
85
+ applied_count INTEGER NOT NULL DEFAULT 0,
86
+ created_at TEXT NOT NULL,
87
+ updated_at TEXT NOT NULL
88
+ );
89
+
90
+ CREATE INDEX IF NOT EXISTS idx_patterns_source_type
91
+ ON analysis_patterns(source, artifact_type);
92
+ CREATE INDEX IF NOT EXISTS idx_patterns_confidence
93
+ ON analysis_patterns(confidence);
94
+ `);
95
+ this.stmts = {
96
+ insert: this.db.prepare(`
97
+ INSERT INTO analysis_patterns
98
+ (id, source, artifact_type, name_pattern, corrections, derived_analysis, confidence, applied_count, created_at, updated_at)
99
+ VALUES
100
+ (@id, @source, @artifact_type, @name_pattern, @corrections, @derived_analysis, @confidence, @applied_count, @created_at, @updated_at)
101
+ `),
102
+ update: this.db.prepare(`
103
+ UPDATE analysis_patterns
104
+ SET corrections = @corrections,
105
+ derived_analysis = @derived_analysis,
106
+ confidence = @confidence,
107
+ updated_at = @updated_at
108
+ WHERE id = @id
109
+ `),
110
+ getById: this.db.prepare(`SELECT * FROM analysis_patterns WHERE id = ?`),
111
+ findMatches: this.db.prepare(`
112
+ SELECT * FROM analysis_patterns
113
+ WHERE source = @source AND artifact_type = @artifact_type
114
+ ORDER BY confidence DESC
115
+ `),
116
+ incrementApplied: this.db.prepare(`
117
+ UPDATE analysis_patterns
118
+ SET applied_count = applied_count + 1, updated_at = @updated_at
119
+ WHERE id = @id
120
+ `),
121
+ listAll: this.db.prepare(`SELECT * FROM analysis_patterns ORDER BY updated_at DESC`),
122
+ deleteById: this.db.prepare(`DELETE FROM analysis_patterns WHERE id = ?`),
123
+ };
124
+ }
125
+ /**
126
+ * Record a correction against a pattern, creating the pattern if it doesn't
127
+ * exist. Returns the updated pattern.
128
+ */
129
+ recordCorrection(key, correction) {
130
+ const now = new Date();
131
+ const existing = this._findExact(key.source, key.artifactType, key.namePattern);
132
+ if (existing) {
133
+ const record = { ...correction, timestamp: now };
134
+ const corrections = [...existing.corrections, record];
135
+ const confidence = computeConfidence(corrections);
136
+ // Rebuild derived analysis from latest corrections
137
+ const derivedAnalysis = this._buildDerivedAnalysis(corrections);
138
+ this.stmts.update.run({
139
+ id: existing.id,
140
+ corrections: JSON.stringify(corrections),
141
+ derived_analysis: JSON.stringify(derivedAnalysis),
142
+ confidence,
143
+ updated_at: now.toISOString(),
144
+ });
145
+ return {
146
+ ...existing,
147
+ corrections,
148
+ derivedAnalysis: derivedAnalysis,
149
+ confidence,
150
+ updatedAt: now,
151
+ };
152
+ }
153
+ // Create new pattern
154
+ const record = { ...correction, timestamp: now };
155
+ const corrections = [record];
156
+ const confidence = computeConfidence(corrections);
157
+ const derivedAnalysis = this._buildDerivedAnalysis(corrections);
158
+ const id = crypto.randomUUID();
159
+ this.stmts.insert.run({
160
+ id,
161
+ source: key.source,
162
+ artifact_type: key.artifactType,
163
+ name_pattern: key.namePattern,
164
+ corrections: JSON.stringify(corrections),
165
+ derived_analysis: JSON.stringify(derivedAnalysis),
166
+ confidence,
167
+ applied_count: 0,
168
+ created_at: now.toISOString(),
169
+ updated_at: now.toISOString(),
170
+ });
171
+ return {
172
+ id,
173
+ source: key.source,
174
+ artifactType: key.artifactType,
175
+ namePattern: key.namePattern,
176
+ corrections,
177
+ derivedAnalysis: derivedAnalysis,
178
+ confidence,
179
+ appliedCount: 0,
180
+ createdAt: now,
181
+ updatedAt: now,
182
+ };
183
+ }
184
+ /**
185
+ * Find patterns matching a given artifact by source + type, then filter by
186
+ * name glob match. Returns patterns sorted by confidence (descending).
187
+ */
188
+ findMatches(source, artifactType, artifactName) {
189
+ const rows = this.stmts.findMatches.all({ source, artifact_type: artifactType });
190
+ const matches = [];
191
+ for (const row of rows) {
192
+ const pattern = rowToPattern(row);
193
+ if (this._globMatch(pattern.namePattern, artifactName)) {
194
+ const autoApply = pattern.corrections.length >= 2 && pattern.confidence >= 0.7;
195
+ matches.push({
196
+ pattern,
197
+ mode: autoApply ? "auto" : "suggest",
198
+ });
199
+ }
200
+ }
201
+ return matches;
202
+ }
203
+ /**
204
+ * Record that a pattern was applied to an artifact.
205
+ */
206
+ recordApplication(patternId) {
207
+ this.stmts.incrementApplied.run({
208
+ id: patternId,
209
+ updated_at: new Date().toISOString(),
210
+ });
211
+ }
212
+ /**
213
+ * Get a pattern by ID.
214
+ */
215
+ getById(id) {
216
+ const row = this.stmts.getById.get(id);
217
+ return row ? rowToPattern(row) : undefined;
218
+ }
219
+ /**
220
+ * List all patterns, most recently updated first.
221
+ */
222
+ listAll() {
223
+ const rows = this.stmts.listAll.all();
224
+ return rows.map(rowToPattern);
225
+ }
226
+ /**
227
+ * Delete a pattern.
228
+ */
229
+ delete(id) {
230
+ const result = this.stmts.deleteById.run(id);
231
+ return result.changes > 0;
232
+ }
233
+ /**
234
+ * Close the database connection.
235
+ */
236
+ close() {
237
+ this.db.close();
238
+ }
239
+ // -------------------------------------------------------------------------
240
+ // Private helpers
241
+ // -------------------------------------------------------------------------
242
+ _findExact(source, artifactType, namePattern) {
243
+ const rows = this.stmts.findMatches.all({ source, artifact_type: artifactType });
244
+ for (const row of rows) {
245
+ if (row.name_pattern === namePattern) {
246
+ return rowToPattern(row);
247
+ }
248
+ }
249
+ return undefined;
250
+ }
251
+ /**
252
+ * Build a DerivedAnalysis by taking the latest correction `to` value for
253
+ * each known field. Supports: summary, deploymentIntent, dependencies
254
+ * (comma-separated), and configurationExpectations (key=value).
255
+ */
256
+ _buildDerivedAnalysis(corrections) {
257
+ const derived = {};
258
+ const latestByField = new Map();
259
+ for (const c of corrections) {
260
+ latestByField.set(c.field, c.to);
261
+ }
262
+ for (const [field, value] of latestByField) {
263
+ switch (field) {
264
+ case "summary":
265
+ derived.summary = value;
266
+ break;
267
+ case "deploymentIntent":
268
+ derived.deploymentIntent = value;
269
+ break;
270
+ case "dependencies":
271
+ derived.dependencies = value.split(",").map((d) => d.trim()).filter(Boolean);
272
+ break;
273
+ default:
274
+ // Treat as a configuration expectation
275
+ if (field.startsWith("config.")) {
276
+ if (!derived.configurationExpectations) {
277
+ derived.configurationExpectations = {};
278
+ }
279
+ derived.configurationExpectations[field.slice("config.".length)] = value;
280
+ }
281
+ break;
282
+ }
283
+ }
284
+ return derived;
285
+ }
286
+ /**
287
+ * Simple glob matching: supports `*` (any characters) and `?` (single char).
288
+ * Used to match artifact names against stored name patterns.
289
+ */
290
+ _globMatch(pattern, name) {
291
+ // Escape regex special chars except * and ?
292
+ const regexStr = pattern
293
+ .replace(/[.+^${}()|[\]\\]/g, "\\$&")
294
+ .replace(/\*/g, ".*")
295
+ .replace(/\?/g, ".");
296
+ return new RegExp(`^${regexStr}$`).test(name);
297
+ }
298
+ }
299
+ //# sourceMappingURL=pattern-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pattern-store.js","sourceRoot":"","sources":["../src/pattern-store.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@synth-deploy/server",
3
+ "version": "0.1.0",
4
+ "license": "BUSL-1.1",
5
+ "type": "module",
6
+ "publishConfig": {
7
+ "access": "public"
8
+ },
9
+ "main": "dist/index.js",
10
+ "bin": {
11
+ "synth-server": "./dist/index.js"
12
+ },
13
+ "exports": {
14
+ ".": "./dist/index.js",
15
+ "./agent/*": "./dist/agent/*",
16
+ "./api/*": "./dist/api/*",
17
+ "./mcp/*": "./dist/mcp/*"
18
+ },
19
+ "scripts": {
20
+ "build": "tsc",
21
+ "dev": "tsx --env-file=../../.env --watch src/index.ts",
22
+ "start": "node dist/index.js",
23
+ "test": "vitest run"
24
+ },
25
+ "dependencies": {
26
+ "@fastify/cors": "^11.2.0",
27
+ "@fastify/formbody": "^8.0.2",
28
+ "@fastify/multipart": "^9.4.0",
29
+ "@fastify/rate-limit": "^10.3.0",
30
+ "@fastify/static": "^9.0.0",
31
+ "@modelcontextprotocol/sdk": "^1.26.0",
32
+ "@node-saml/node-saml": "^5.1.0",
33
+ "@synth-deploy/core": "*",
34
+ "@types/bcryptjs": "^2.4.6",
35
+ "adm-zip": "^0.5.16",
36
+ "bcryptjs": "^3.0.3",
37
+ "better-sqlite3": "^12.6.2",
38
+ "fastify": "^5.0.0",
39
+ "ldapjs": "^3.0.7",
40
+ "openid-client": "^6.8.2",
41
+ "tar-stream": "^2.2.0",
42
+ "zod": "^3.25.0"
43
+ },
44
+ "devDependencies": {
45
+ "@types/adm-zip": "^0.5.7",
46
+ "@types/better-sqlite3": "^7.6.13",
47
+ "@types/ldapjs": "^3.0.6",
48
+ "@types/node": "^25.3.0",
49
+ "@types/tar-stream": "^3.1.4",
50
+ "tsx": "^4.0.0",
51
+ "typescript": "^5.7.0",
52
+ "vitest": "^4.0.18"
53
+ }
54
+ }
@@ -0,0 +1,44 @@
1
+ import type { PersistentDecisionDebrief } from "@synth-deploy/core";
2
+
3
+ const DEFAULT_RETENTION_DAYS = Number(
4
+ process.env.SYNTH_DEBRIEF_RETENTION_DAYS ?? 90,
5
+ );
6
+
7
+ const DEFAULT_RETENTION_INTERVAL_MS = Number(
8
+ process.env.SYNTH_RETENTION_INTERVAL_MS ?? 24 * 60 * 60 * 1000, // daily
9
+ );
10
+
11
+ /**
12
+ * Runs a single retention pass: purges debrief entries older than
13
+ * the retention threshold.
14
+ */
15
+ export function runRetentionPass(
16
+ debrief: PersistentDecisionDebrief,
17
+ retentionDays: number = DEFAULT_RETENTION_DAYS,
18
+ ): number {
19
+ const cutoff = new Date(Date.now() - retentionDays * 24 * 60 * 60 * 1000);
20
+ const purged = debrief.purgeOlderThan(cutoff);
21
+ if (purged > 0) {
22
+ console.log(`[debrief-retention] Purged ${purged} entries older than ${retentionDays} days`);
23
+ }
24
+ return purged;
25
+ }
26
+
27
+ /**
28
+ * Starts a periodic retention scanner.
29
+ * Returns a cleanup function to stop the interval.
30
+ */
31
+ export function startRetentionScanner(
32
+ debrief: PersistentDecisionDebrief,
33
+ intervalMs: number = DEFAULT_RETENTION_INTERVAL_MS,
34
+ retentionDays: number = DEFAULT_RETENTION_DAYS,
35
+ ): () => void {
36
+ // Run once on startup
37
+ runRetentionPass(debrief, retentionDays);
38
+
39
+ const timer = setInterval(() => {
40
+ runRetentionPass(debrief, retentionDays);
41
+ }, intervalMs);
42
+
43
+ return () => clearInterval(timer);
44
+ }