@codemation/host 0.6.0 → 0.8.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 (226) hide show
  1. package/CHANGELOG.md +483 -0
  2. package/dist/{ApiPaths-CLTHphYZ.js → ApiPaths-Dv1dcHu_.js} +4 -4
  3. package/dist/ApiPaths-Dv1dcHu_.js.map +1 -0
  4. package/dist/{AppConfigFactory-YnveXE9k.d.ts → AppConfigFactory-BT0y0LVC.d.ts} +8490 -5548
  5. package/dist/{AppConfigFactory-C6q-CSKb.js → AppConfigFactory-Cx4qQvRk.js} +112 -52
  6. package/dist/AppConfigFactory-Cx4qQvRk.js.map +1 -0
  7. package/dist/{AppContainerFactory-qaqc-R1D.js → AppContainerFactory-DRTjG7nG.js} +7298 -1732
  8. package/dist/AppContainerFactory-DRTjG7nG.js.map +1 -0
  9. package/dist/{CodemationAppContext-DRu1Dpri.d.ts → CodemationAppContext-CGFYVcSb.d.ts} +14 -4
  10. package/dist/{CodemationAuthoring.types-DZl-sJaM.js → CodemationAuthoring.types-BteaR3Dc.js} +19 -6
  11. package/dist/CodemationAuthoring.types-BteaR3Dc.js.map +1 -0
  12. package/dist/{CodemationAuthoring.types-fBRppnmi.d.ts → CodemationAuthoring.types-DiKKogum.d.ts} +30 -5
  13. package/dist/{CodemationConfigNormalizer-DVko3cVN.d.ts → CodemationConfigNormalizer-48f-T66P.d.ts} +3 -3
  14. package/dist/{CodemationConsumerConfigLoader-BeAUS144.js → CodemationConsumerConfigLoader-By-6tuGc.js} +81 -10
  15. package/dist/CodemationConsumerConfigLoader-By-6tuGc.js.map +1 -0
  16. package/dist/{CodemationConsumerConfigLoader-DJWr86f-.d.ts → CodemationConsumerConfigLoader-_PIYqwVx.d.ts} +18 -2
  17. package/dist/{CodemationPluginListMerger-B-W5Fa_X.js → CodemationPluginListMerger-D1B1IEbt.js} +1 -1
  18. package/dist/{CodemationPluginListMerger-B-W5Fa_X.js.map → CodemationPluginListMerger-D1B1IEbt.js.map} +1 -1
  19. package/dist/{CodemationPluginListMerger-DGc-jfa2.d.ts → CodemationPluginListMerger-DP7djJ9S.d.ts} +151 -19
  20. package/dist/CodemationTsyringeTypeInfoRegistrar-Bj6FJYFz.js +97 -0
  21. package/dist/CodemationTsyringeTypeInfoRegistrar-Bj6FJYFz.js.map +1 -0
  22. package/dist/{CodemationWhitelabelConfig-CWbcyQqn.d.ts → CodemationWhitelabelConfig-Ca2mCUeC.d.ts} +2 -2
  23. package/dist/{CollectionContracts.types-DdpHft0i.d.ts → CollectionContracts.types-DDyFYT_D.d.ts} +1 -1
  24. package/dist/{CredentialContractsRegistry-DrMIDSw8.d.ts → CredentialContractsRegistry-Bq2bq28t.d.ts} +2 -2
  25. package/dist/{CredentialServices-UfvHB-rN.d.ts → CredentialServices-BLloBztI.d.ts} +65 -20
  26. package/dist/{CredentialServices-CgxwguAv.js → CredentialServices-Dk8yypeL.js} +310 -51
  27. package/dist/CredentialServices-Dk8yypeL.js.map +1 -0
  28. package/dist/InternalHonoApiRouteRegistrar-c7t3KnV_.d.ts +17 -0
  29. package/dist/InternalPingRegistrar-DY3kSfxP.js +221 -0
  30. package/dist/InternalPingRegistrar-DY3kSfxP.js.map +1 -0
  31. package/dist/{ItemsInputNormalizer-C-KHg9Mo.d.ts → ItemsInputNormalizer-_RwIfRIQ.d.ts} +89 -25
  32. package/dist/{LogLevelPolicyFactory-CampWO0l.d.ts → LogLevelPolicyFactory-ewCHLDLn.d.ts} +2 -2
  33. package/dist/{PublicFrontendBootstrap-DzBgwOnG.d.ts → PublicFrontendBootstrap-Cev3qK46.d.ts} +9 -2
  34. package/dist/PublicFrontendBootstrapFactory-Dv04tJ-6.d.ts +82 -0
  35. package/dist/{PublicFrontendBootstrapJsonCodec-Cl_DLRh0.d.ts → PublicFrontendBootstrapJsonCodec-CXG9Dxft.d.ts} +3 -3
  36. package/dist/{PublicFrontendBootstrapJsonCodec-DzqvA0uo.js → PublicFrontendBootstrapJsonCodec-CegIF_ne.js} +7 -2
  37. package/dist/PublicFrontendBootstrapJsonCodec-CegIF_ne.js.map +1 -0
  38. package/dist/ServerLoggerFactory-Ckk52S3w.js +223 -0
  39. package/dist/ServerLoggerFactory-Ckk52S3w.js.map +1 -0
  40. package/dist/{TelemetryContracts-DbaNomrH.d.ts → TelemetryContracts-BtDx84Cp.d.ts} +13 -4
  41. package/dist/{WorkflowPolicyUiPresentationFactory-DQEY-h_S.d.ts → WorkflowPolicyUiPresentationFactory-6MyjCvBO.d.ts} +2 -2
  42. package/dist/{WorkflowPolicyUiPresentationFactory-DhPqQ9aB.js → WorkflowPolicyUiPresentationFactory-Bb-ae_Zh.js} +1 -1
  43. package/dist/{WorkflowPolicyUiPresentationFactory-DhPqQ9aB.js.map → WorkflowPolicyUiPresentationFactory-Bb-ae_Zh.js.map} +1 -1
  44. package/dist/{WorkflowViewContracts-CzK2KFuz.d.ts → WorkflowViewContracts-B7aFQcIw.d.ts} +10 -1
  45. package/dist/authoring.d.ts +5 -5
  46. package/dist/authoring.js +1 -1
  47. package/dist/client.d.ts +4 -4
  48. package/dist/client.js +2 -2
  49. package/dist/consumer.d.ts +6 -6
  50. package/dist/consumer.js +2 -2
  51. package/dist/credentials.d.ts +6 -6
  52. package/dist/credentials.js +1 -1
  53. package/dist/devServerSidecar.d.ts +2 -2
  54. package/dist/devServerSidecar.js +1 -94
  55. package/dist/devServerSidecar.js.map +1 -1
  56. package/dist/dto.d.ts +6 -6
  57. package/dist/{index-BbBk26m0.d.ts → index-DilAYwnH.d.ts} +49 -3
  58. package/dist/index.d.ts +141 -21
  59. package/dist/index.js +109 -14
  60. package/dist/index.js.map +1 -0
  61. package/dist/mapping.d.ts +2 -2
  62. package/dist/mapping.js +1 -1
  63. package/dist/nextServer.d.ts +42 -113
  64. package/dist/nextServer.js +9 -7
  65. package/dist/pairing.d.ts +93 -0
  66. package/dist/pairing.js +5 -0
  67. package/dist/pairing.types-snfZ_OzB.d.ts +19 -0
  68. package/dist/persistenceServer-B71RGvSj.d.ts +30 -0
  69. package/dist/{persistenceServer-CmsIKnO9.js → persistenceServer-C-hH4z6l.js} +2 -2
  70. package/dist/{persistenceServer-CmsIKnO9.js.map → persistenceServer-C-hH4z6l.js.map} +1 -1
  71. package/dist/persistenceServer.d.ts +8 -8
  72. package/dist/persistenceServer.js +3 -3
  73. package/dist/{server-MUNGsBYK.d.ts → server-09PKasWR.d.ts} +21 -6
  74. package/dist/{server-CJFfY67o.js → server-vtRCPgRJ.js} +7 -6
  75. package/dist/{server-CJFfY67o.js.map → server-vtRCPgRJ.js.map} +1 -1
  76. package/dist/server.d.ts +14 -14
  77. package/dist/server.js +13 -11
  78. package/package.json +47 -58
  79. package/prisma/migrations/20260519000000_workflow_audit_log/migration.sql +23 -0
  80. package/prisma/migrations/20260519100000_storage_growth_fixes/migration.sql +61 -0
  81. package/prisma/migrations.sqlite/20260519000000_workflow_audit_log/migration.sql +21 -0
  82. package/prisma/migrations.sqlite/20260519100000_storage_growth_fixes/migration.sql +29 -0
  83. package/prisma/schema.postgresql.prisma +55 -17
  84. package/prisma/schema.sqlite.prisma +55 -17
  85. package/prisma-generated/prisma-postgresql-client/edge.js +33 -5
  86. package/prisma-generated/prisma-postgresql-client/index-browser.js +29 -1
  87. package/prisma-generated/prisma-postgresql-client/index.d.ts +8933 -5716
  88. package/prisma-generated/prisma-postgresql-client/index.js +33 -5
  89. package/prisma-generated/prisma-postgresql-client/package.json +1 -1
  90. package/prisma-generated/prisma-postgresql-client/schema.prisma +38 -0
  91. package/prisma-generated/prisma-sqlite-client/edge.js +33 -5
  92. package/prisma-generated/prisma-sqlite-client/index-browser.js +29 -1
  93. package/prisma-generated/prisma-sqlite-client/index.d.ts +8925 -5713
  94. package/prisma-generated/prisma-sqlite-client/index.js +33 -5
  95. package/prisma-generated/prisma-sqlite-client/package.json +1 -1
  96. package/prisma-generated/prisma-sqlite-client/schema.prisma +38 -0
  97. package/scripts/check-collections.mjs +18 -0
  98. package/scripts/generate-prisma-clients.mjs +20 -11
  99. package/src/application/WorkflowAuditLogPruneScheduler.ts +96 -0
  100. package/src/application/auth/AuthenticatedPrincipal.ts +4 -0
  101. package/src/application/commands/StartWorkflowRunCommandHandler.ts +4 -0
  102. package/src/application/contracts/WorkflowViewContracts.ts +6 -0
  103. package/src/application/contracts/WorkflowWebsocketMessage.ts +3 -1
  104. package/src/application/mapping/WorkflowDefinitionMapper.ts +40 -1
  105. package/src/application/runs/WorkflowRunRetentionPruneScheduler.ts +7 -1
  106. package/src/application/telemetry/OtelExecutionTelemetry.types.ts +5 -0
  107. package/src/application/telemetry/OtelExecutionTelemetryFactory.ts +4 -0
  108. package/src/application/telemetry/StoredTelemetrySpanScope.ts +6 -2
  109. package/src/application/telemetry/TelemetryRetentionTimestampFactory.ts +27 -17
  110. package/src/application/telemetry/TelemetrySpanPublisher.ts +11 -0
  111. package/src/application/websocket/TelemetrySpanWebsocketRelay.ts +31 -0
  112. package/src/applicationTokens.ts +20 -1
  113. package/src/audit/IAuditEmitter.ts +32 -0
  114. package/src/audit/PrismaWorkflowAuditLogRepository.ts +34 -0
  115. package/src/audit/WorkflowAuditLogWriter.ts +125 -0
  116. package/src/auth/managed/ManagedAuthConfig.ts +29 -0
  117. package/src/auth/managed/ManagedAuthMiddleware.ts +52 -0
  118. package/src/auth/managed/ManagedCorsMiddleware.ts +43 -0
  119. package/src/auth/managed/ManagedModeBootGuard.ts +27 -0
  120. package/src/auth/managed/index.ts +5 -0
  121. package/src/bootstrap/AppContainerFactory.ts +295 -29
  122. package/src/bootstrap/AppContainerLifecycle.ts +31 -0
  123. package/src/bootstrap/perf/BootTimer.ts +168 -0
  124. package/src/bootstrap/runtime/AppConfigFactory.ts +21 -65
  125. package/src/bootstrap/runtime/FrontendRuntime.ts +4 -1
  126. package/src/bootstrap/runtime/HeadlessApiRuntime.ts +47 -0
  127. package/src/bootstrap/runtime/WorkerRuntime.ts +2 -1
  128. package/src/credentials/BrokerClient.ts +49 -0
  129. package/src/credentials/BrokerRefreshError.ts +12 -0
  130. package/src/credentials/BrokerRefreshInvalidGrantError.ts +13 -0
  131. package/src/credentials/ControlPlaneCatalogFetcher.ts +261 -0
  132. package/src/credentials/CredentialOAuth2MaterialReader.ts +136 -0
  133. package/src/credentials/InternalCredentialsListRegistrar.ts +48 -0
  134. package/src/credentials/InternalCredentialsPushRegistrar.ts +125 -0
  135. package/src/credentials/LocalOAuthFlowExecutor.ts +316 -0
  136. package/src/credentials/ManagedOAuthFlowExecutor.ts +94 -0
  137. package/src/credentials/ManagedOAuthRefreshInvalidGrantError.ts +13 -0
  138. package/src/credentials/catalogTypes.ts +4 -0
  139. package/src/credentials/refresh/CredentialDisconnectedError.ts +11 -0
  140. package/src/domain/credentials/CredentialBindingService.ts +54 -2
  141. package/src/domain/credentials/CredentialKeyRotatedError.ts +22 -0
  142. package/src/domain/credentials/CredentialSecretCipher.ts +68 -6
  143. package/src/domain/credentials/CredentialTypeRegistryImpl.ts +117 -10
  144. package/src/domain/credentials/OAuth2RedirectUriResolver.ts +79 -0
  145. package/src/domain/credentials/WorkflowCredentialNodeResolver.ts +14 -5
  146. package/src/domain/telemetry/TelemetryContracts.ts +7 -1
  147. package/src/domain/workflows/WorkflowActivationPreflight.ts +24 -1
  148. package/src/domain/workflows/WorkflowActivationPreflightRules.ts +40 -1
  149. package/src/index.ts +9 -0
  150. package/src/infrastructure/binary/LocalFilesystemBinaryStorageRegistry.ts +29 -1
  151. package/src/infrastructure/binary/S3BinaryStorage.ts +169 -0
  152. package/src/infrastructure/binary/S3BinaryStorageConfig.ts +17 -0
  153. package/src/infrastructure/config/CodemationPluginRegistrar.ts +3 -1
  154. package/src/infrastructure/persistence/CodemationDatabaseUrlParser.ts +41 -0
  155. package/src/infrastructure/persistence/InMemoryTelemetryArtifactStore.ts +8 -3
  156. package/src/infrastructure/persistence/PrismaMigrationDeployer.ts +21 -13
  157. package/src/infrastructure/persistence/PrismaTelemetryArtifactStore.ts +43 -8
  158. package/src/infrastructure/persistence/PrismaWorkflowRunRepository.ts +26 -3
  159. package/src/infrastructure/persistence/PrismaWorkflowSnapshotRepository.ts +48 -0
  160. package/src/mcp/AgentMcpIntegrationImpl.ts +344 -0
  161. package/src/mcp/McpClientFactory.ts +29 -0
  162. package/src/mcp/McpConnectionPool.ts +184 -0
  163. package/src/mcp/McpConnectionPool.types.ts +12 -0
  164. package/src/mcp/McpServerCatalog.ts +104 -0
  165. package/src/mcp/index.ts +5 -0
  166. package/src/pairing/HmacRequestSigner.ts +32 -0
  167. package/src/pairing/IncomingHmacVerifier.ts +82 -0
  168. package/src/pairing/InternalHmacAuthMiddleware.ts +33 -0
  169. package/src/pairing/InternalPingRegistrar.ts +25 -0
  170. package/src/pairing/PairedFetch.ts +33 -0
  171. package/src/pairing/PairingConfigFactory.ts +35 -0
  172. package/src/pairing/PairingConfigToken.ts +6 -0
  173. package/src/pairing/index.ts +14 -0
  174. package/src/pairing/pairing.types.ts +18 -0
  175. package/src/pairing.ts +17 -0
  176. package/src/persistenceServer.ts +1 -0
  177. package/src/presentation/config/AppConfig.ts +7 -1
  178. package/src/presentation/config/CodemationAuthConfig.ts +1 -1
  179. package/src/presentation/config/CodemationAuthoring.types.ts +60 -5
  180. package/src/presentation/config/CodemationConfig.ts +9 -0
  181. package/src/presentation/config/CodemationConfigNormalizer.ts +39 -1
  182. package/src/presentation/config/CodemationPlugin.ts +2 -1
  183. package/src/presentation/frontend/CodemationFrontendAuthSnapshot.ts +5 -0
  184. package/src/presentation/frontend/CodemationFrontendAuthSnapshotFactory.ts +7 -1
  185. package/src/presentation/frontend/PublicFrontendBootstrap.ts +2 -0
  186. package/src/presentation/frontend/PublicFrontendBootstrapFactory.ts +5 -1
  187. package/src/presentation/frontend/PublicFrontendBootstrapJsonCodec.ts +4 -1
  188. package/src/presentation/http/ApiPaths.ts +4 -4
  189. package/src/presentation/http/HeadlessHttpServerFactory.ts +56 -0
  190. package/src/presentation/http/ServerHttpErrorResponseFactory.ts +39 -2
  191. package/src/presentation/http/hono/CodemationHonoApiAppFactory.ts +33 -8
  192. package/src/presentation/http/hono/InternalHonoApiRouteRegistrar.ts +12 -0
  193. package/src/presentation/http/hono/registrars/ManagedMeHonoApiRouteRegistrar.ts +35 -0
  194. package/src/presentation/http/hono/registrars/OAuth2HonoApiRouteRegistrar.ts +2 -2
  195. package/src/presentation/http/routeHandlers/CredentialHttpRouteHandler.ts +28 -0
  196. package/src/presentation/http/routeHandlers/OAuth2HttpRouteHandlerFactory.ts +98 -41
  197. package/src/presentation/server/CodemationConsumerConfigLoader.ts +59 -7
  198. package/src/presentation/server/CodemationPluginDiscovery.ts +5 -0
  199. package/src/presentation/server/WorkflowDefinitionExportsResolver.ts +18 -0
  200. package/src/presentation/server/WorkflowModulePathFinder.ts +12 -1
  201. package/src/presentation/websocket/ManagedWebsocketAuthenticator.ts +50 -0
  202. package/src/presentation/websocket/WebsocketAuthenticator.types.ts +12 -0
  203. package/src/presentation/websocket/WorkflowWebsocketServer.ts +24 -3
  204. package/src/presentation/websocket/WorkflowWebsocketServerFactory.ts +16 -0
  205. package/src/process/ExecaProcessRunner.ts +41 -0
  206. package/src/process/ProcessRunner.types.ts +39 -0
  207. package/src/server.ts +2 -0
  208. package/src/workflows/InternalWorkflowActivationRegistrar.ts +42 -0
  209. package/src/workflows/InternalWorkflowDetailRegistrar.ts +33 -0
  210. package/src/workflows/InternalWorkflowTestRunRegistrar.ts +91 -0
  211. package/src/workflows/InternalWorkflowsListRegistrar.ts +28 -0
  212. package/src/workflows/discovery/WorkflowDirectoryDiscoverer.ts +79 -0
  213. package/tsconfig.json +2 -0
  214. package/vitest.shared.ts +5 -0
  215. package/dist/ApiPaths-CLTHphYZ.js.map +0 -1
  216. package/dist/AppConfigFactory-C6q-CSKb.js.map +0 -1
  217. package/dist/AppContainerFactory-qaqc-R1D.js.map +0 -1
  218. package/dist/CodemationAuthoring.types-DZl-sJaM.js.map +0 -1
  219. package/dist/CodemationConsumerConfigLoader-BeAUS144.js.map +0 -1
  220. package/dist/CredentialServices-CgxwguAv.js.map +0 -1
  221. package/dist/PublicFrontendBootstrapFactory-Cb2pLmDd.d.ts +0 -45
  222. package/dist/PublicFrontendBootstrapJsonCodec-DzqvA0uo.js.map +0 -1
  223. package/dist/ServerLoggerFactory-BKSIh9Xv.js +0 -98
  224. package/dist/ServerLoggerFactory-BKSIh9Xv.js.map +0 -1
  225. package/dist/persistenceServer-vtJAGDat.d.ts +0 -9
  226. package/src/domain/credentials/OAuth2ConnectServiceFactory.ts +0 -411
@@ -1,4 +1,4 @@
1
- import type { AnyCredentialType, CollectionDefinition, Container } from "@codemation/core";
1
+ import type { AnyCredentialType, CollectionDefinition, Container, McpServerDeclaration } from "@codemation/core";
2
2
  import type { LoggerFactory } from "../../application/logging/Logger";
3
3
  import type { AppConfig } from "../../presentation/config/AppConfig";
4
4
  import type { CodemationPlugin } from "../../presentation/config/CodemationPlugin";
@@ -11,6 +11,7 @@ export class CodemationPluginRegistrar {
11
11
  appConfig: AppConfig;
12
12
  registerCredentialType: (type: AnyCredentialType) => void;
13
13
  registerCollection: (definition: CollectionDefinition) => void;
14
+ mergeMcpServers: (declarations: ReadonlyArray<McpServerDeclaration>) => void;
14
15
  loggerFactory: LoggerFactory;
15
16
  }>,
16
17
  ): Promise<void> {
@@ -18,6 +19,7 @@ export class CodemationPluginRegistrar {
18
19
  for (const credentialType of plugin.credentialTypes ?? []) {
19
20
  args.registerCredentialType(credentialType);
20
21
  }
22
+ args.mergeMcpServers(plugin.mcpServers ?? []);
21
23
  if (!plugin.register) {
22
24
  continue;
23
25
  }
@@ -0,0 +1,41 @@
1
+ import path from "node:path";
2
+ import type { AppPersistenceConfig } from "../../presentation/config/AppConfig";
3
+
4
+ /**
5
+ * Parses `CODEMATION_DATABASE_URL` into an {@link AppPersistenceConfig}.
6
+ *
7
+ * Supported schemes (case-insensitive):
8
+ * - `sqlite://relative/path/to/file.db` → resolved relative to consumerRoot
9
+ * - `sqlite:///absolute/path/to/file.db` → leading slash = POSIX absolute
10
+ * - `sqlite://C:/path/file.db` → Windows-style absolute (path.isAbsolute()
11
+ * returns true for these)
12
+ * - `pgsql://user:pass@host:5432/dbname` → normalised to postgresql://
13
+ * - `postgresql://user:pass@host:5432/db` → pass-through (Prisma's expected scheme)
14
+ * - `postgres://user:pass@host:5432/db` → pass-through (common alias)
15
+ *
16
+ * Throws on any other scheme. Empty / whitespace input is also an error — callers
17
+ * should default before calling parse().
18
+ */
19
+ export class CodemationDatabaseUrlParser {
20
+ parse(url: string, consumerRoot: string): AppPersistenceConfig {
21
+ const trimmed = url.trim();
22
+ if (trimmed.length === 0) {
23
+ throw new Error("CODEMATION_DATABASE_URL is empty.");
24
+ }
25
+ if (trimmed.toLowerCase().startsWith("sqlite://")) {
26
+ const remainder = trimmed.slice("sqlite://".length);
27
+ const filePath = path.isAbsolute(remainder) ? remainder : path.resolve(consumerRoot, remainder);
28
+ return { kind: "sqlite", databaseFilePath: filePath };
29
+ }
30
+ if (trimmed.toLowerCase().startsWith("pgsql://")) {
31
+ return { kind: "postgresql", databaseUrl: `postgresql://${trimmed.slice("pgsql://".length)}` };
32
+ }
33
+ if (trimmed.toLowerCase().startsWith("postgresql://") || trimmed.toLowerCase().startsWith("postgres://")) {
34
+ return { kind: "postgresql", databaseUrl: trimmed };
35
+ }
36
+ throw new Error(
37
+ `Unsupported CODEMATION_DATABASE_URL scheme: "${trimmed}". ` +
38
+ `Use sqlite://, pgsql://, postgresql://, or postgres://.`,
39
+ );
40
+ }
41
+ }
@@ -4,6 +4,7 @@ import type {
4
4
  TelemetryArtifactRecord,
5
5
  TelemetryArtifactStore,
6
6
  TelemetryArtifactWrite,
7
+ TelemetryPruneArgs,
7
8
  } from "../../domain/telemetry/TelemetryContracts";
8
9
 
9
10
  @injectable()
@@ -43,14 +44,18 @@ export class InMemoryTelemetryArtifactStore implements TelemetryArtifactStore {
43
44
  .sort((left, right) => left.createdAt.localeCompare(right.createdAt));
44
45
  }
45
46
 
46
- async pruneExpired(args: Readonly<{ nowIso: string; limit?: number }>): Promise<number> {
47
+ async pruneExpired(args: TelemetryPruneArgs): Promise<{ count: number; storageKeys: ReadonlyArray<string> }> {
47
48
  const candidates = [...this.rows.entries()]
48
49
  .filter(([, row]) => row.retentionExpiresAt !== undefined && row.retentionExpiresAt <= args.nowIso)
49
50
  .sort((left, right) => (left[1].retentionExpiresAt ?? "").localeCompare(right[1].retentionExpiresAt ?? ""))
50
51
  .slice(0, args.limit ?? Number.MAX_SAFE_INTEGER);
51
- for (const [key] of candidates) {
52
+ const storageKeys: string[] = [];
53
+ for (const [key, row] of candidates) {
54
+ if (row.payloadStorageKey) {
55
+ storageKeys.push(row.payloadStorageKey);
56
+ }
52
57
  this.rows.delete(key);
53
58
  }
54
- return candidates.length;
59
+ return { count: candidates.length, storageKeys };
55
60
  }
56
61
  }
@@ -1,4 +1,4 @@
1
- import { createClient, type Client } from "@libsql/client";
1
+ import type { Client } from "@libsql/client";
2
2
  import { injectable } from "@codemation/core";
3
3
  import { spawn } from "node:child_process";
4
4
  import { existsSync } from "node:fs";
@@ -105,11 +105,12 @@ export class PrismaMigrationDeployer {
105
105
  env?: Readonly<NodeJS.ProcessEnv>;
106
106
  }>,
107
107
  ): Promise<void> {
108
- const prismaConfigPath = this.resolveAbsolutePrismaConfigPath();
108
+ const resolverEnv = { ...process.env, ...(args.env ?? {}) };
109
+ const prismaConfigPath = this.resolveAbsolutePrismaConfigPath(resolverEnv);
109
110
  await new Promise<void>((resolve, reject) => {
110
111
  const command = spawn(
111
112
  process.execPath,
112
- [...[this.resolvePrismaCliPath(), ...args.prismaArgs], "--config", path.basename(prismaConfigPath)],
113
+ [...[this.resolvePrismaCliPath(resolverEnv), ...args.prismaArgs], "--config", path.basename(prismaConfigPath)],
113
114
  {
114
115
  cwd: path.dirname(prismaConfigPath),
115
116
  env: this.createProcessEnvironment(args.databaseUrl, args.provider, args.env),
@@ -177,6 +178,10 @@ export class PrismaMigrationDeployer {
177
178
  }
178
179
 
179
180
  private async repairPartiallyAppliedNormalizedRuntimeSqliteDatabase(databaseFilePath: string): Promise<boolean> {
181
+ // Lazy import: @libsql/client pulls in platform-specific native bindings that confuse the
182
+ // Next.js / Turbopack module tracer (forcing the whole project to be traced via NFT). This
183
+ // recovery path is rarely needed, so defer the load until it's actually invoked.
184
+ const { createClient } = await import("@libsql/client");
180
185
  const client = createClient({ url: this.sqliteFilePathToDatabaseUrl(databaseFilePath) });
181
186
  try {
182
187
  const failedMigration = await this.hasActiveFailedMigrationRecord(
@@ -288,6 +293,7 @@ export class PrismaMigrationDeployer {
288
293
  }
289
294
 
290
295
  private async cleanupNormalizedRuntimeLegacyArtifacts(databaseFilePath: string): Promise<void> {
296
+ const { createClient } = await import("@libsql/client");
291
297
  const client = createClient({ url: this.sqliteFilePathToDatabaseUrl(databaseFilePath) });
292
298
  try {
293
299
  const runColumns = await this.readSqliteTableColumns(client, "Run");
@@ -326,14 +332,15 @@ export class PrismaMigrationDeployer {
326
332
  };
327
333
  }
328
334
 
329
- private resolvePrismaCliPath(): string {
330
- const configuredPath = process.env.CODEMATION_PRISMA_CLI_PATH;
335
+ private resolvePrismaCliPath(env: Readonly<NodeJS.ProcessEnv>): string {
336
+ const configuredPath = env.CODEMATION_PRISMA_CLI_PATH;
331
337
  if (configuredPath && existsSync(configuredPath)) {
332
338
  return configuredPath;
333
339
  }
340
+ const packageRoot = this.resolvePackageRoot(env);
334
341
  const packageManagerCandidates = [
335
342
  path.resolve(process.cwd(), "node_modules", "prisma", "build", "index.js"),
336
- path.resolve(this.resolvePackageRoot(), "node_modules", "prisma", "build", "index.js"),
343
+ path.resolve(packageRoot, "node_modules", "prisma", "build", "index.js"),
337
344
  ];
338
345
  for (const candidate of packageManagerCandidates) {
339
346
  if (existsSync(candidate)) {
@@ -342,7 +349,7 @@ export class PrismaMigrationDeployer {
342
349
  }
343
350
  try {
344
351
  return this.require.resolve("prisma/build/index.js", {
345
- paths: [process.cwd(), this.resolvePackageRoot()],
352
+ paths: [process.cwd(), packageRoot],
346
353
  });
347
354
  } catch {
348
355
  throw new Error(
@@ -351,16 +358,17 @@ export class PrismaMigrationDeployer {
351
358
  }
352
359
  }
353
360
 
354
- private resolveAbsolutePrismaConfigPath(): string {
355
- const configuredPath = process.env.CODEMATION_PRISMA_CONFIG_PATH;
361
+ private resolveAbsolutePrismaConfigPath(env: Readonly<NodeJS.ProcessEnv>): string {
362
+ const configuredPath = env.CODEMATION_PRISMA_CONFIG_PATH;
363
+ const packageRoot = this.resolvePackageRoot(env);
356
364
  if (configuredPath) {
357
- return path.isAbsolute(configuredPath) ? configuredPath : path.resolve(this.resolvePackageRoot(), configuredPath);
365
+ return path.isAbsolute(configuredPath) ? configuredPath : path.resolve(packageRoot, configuredPath);
358
366
  }
359
- return path.resolve(this.resolvePackageRoot(), "prisma.config.ts");
367
+ return path.resolve(packageRoot, "prisma.config.ts");
360
368
  }
361
369
 
362
- resolvePackageRoot(): string {
363
- const configuredRoot = process.env.CODEMATION_HOST_PACKAGE_ROOT;
370
+ resolvePackageRoot(env: Readonly<NodeJS.ProcessEnv> = process.env): string {
371
+ const configuredRoot = env.CODEMATION_HOST_PACKAGE_ROOT;
364
372
  if (configuredRoot) {
365
373
  return configuredRoot;
366
374
  }
@@ -1,12 +1,17 @@
1
- import { inject, injectable } from "@codemation/core";
1
+ import type { BinaryBody, BinaryStorage } from "@codemation/core";
2
+ import { CoreTokens, inject, injectable } from "@codemation/core";
2
3
  import { OtelIdentityFactory } from "../../application/telemetry/OtelIdentityFactory";
3
4
  import type {
4
5
  TelemetryArtifactRecord,
5
6
  TelemetryArtifactStore,
6
7
  TelemetryArtifactWrite,
8
+ TelemetryPruneArgs,
7
9
  } from "../../domain/telemetry/TelemetryContracts";
8
10
  import { PrismaDatabaseClientToken, type PrismaDatabaseClient } from "./PrismaDatabaseClient";
9
11
 
12
+ /** Payloads larger than this byte threshold are offloaded to BinaryStorage. */
13
+ const PAYLOAD_OFFLOAD_THRESHOLD_BYTES = 64_000;
14
+
10
15
  @injectable()
11
16
  export class PrismaTelemetryArtifactStore implements TelemetryArtifactStore {
12
17
  constructor(
@@ -14,11 +19,36 @@ export class PrismaTelemetryArtifactStore implements TelemetryArtifactStore {
14
19
  private readonly prisma: PrismaDatabaseClient,
15
20
  @inject(OtelIdentityFactory)
16
21
  private readonly otelIdentityFactory: OtelIdentityFactory,
22
+ @inject(CoreTokens.BinaryStorage)
23
+ private readonly binaryStorage: BinaryStorage,
17
24
  ) {}
18
25
 
19
26
  async save(record: TelemetryArtifactWrite): Promise<TelemetryArtifactRecord> {
20
27
  const artifactId = this.otelIdentityFactory.createArtifactId();
21
28
  const createdAt = new Date().toISOString();
29
+
30
+ // Resolve inline vs offloaded payload
31
+ let payloadText: string | null = record.payloadText ?? null;
32
+ let payloadJson: string | null = record.payloadJson !== undefined ? JSON.stringify(record.payloadJson) : null;
33
+ let payloadStorageKey: string | null = null;
34
+
35
+ const payloadTextBytes = payloadText ? Buffer.byteLength(payloadText, "utf8") : 0;
36
+ const payloadJsonBytes = payloadJson ? Buffer.byteLength(payloadJson, "utf8") : 0;
37
+
38
+ if (payloadTextBytes > PAYLOAD_OFFLOAD_THRESHOLD_BYTES) {
39
+ const storageKey = `telemetry-artifacts/${artifactId}.txt`;
40
+ const body: BinaryBody = Buffer.from(payloadText!, "utf8");
41
+ await this.binaryStorage.write({ storageKey, body });
42
+ payloadStorageKey = storageKey;
43
+ payloadText = null;
44
+ } else if (payloadJsonBytes > PAYLOAD_OFFLOAD_THRESHOLD_BYTES) {
45
+ const storageKey = `telemetry-artifacts/${artifactId}.json`;
46
+ const body: BinaryBody = Buffer.from(payloadJson!, "utf8");
47
+ await this.binaryStorage.write({ storageKey, body });
48
+ payloadStorageKey = storageKey;
49
+ payloadJson = null;
50
+ }
51
+
22
52
  await this.prisma.telemetryArtifact.create({
23
53
  data: {
24
54
  artifactId,
@@ -32,8 +62,9 @@ export class PrismaTelemetryArtifactStore implements TelemetryArtifactStore {
32
62
  contentType: record.contentType,
33
63
  previewText: record.previewText ?? null,
34
64
  previewJson: record.previewJson !== undefined ? JSON.stringify(record.previewJson) : null,
35
- payloadText: record.payloadText ?? null,
36
- payloadJson: record.payloadJson !== undefined ? JSON.stringify(record.payloadJson) : null,
65
+ payloadText,
66
+ payloadJson,
67
+ payloadStorageKey,
37
68
  bytes: record.bytes ?? null,
38
69
  truncated: record.truncated ?? null,
39
70
  createdAt,
@@ -53,8 +84,9 @@ export class PrismaTelemetryArtifactStore implements TelemetryArtifactStore {
53
84
  contentType: record.contentType,
54
85
  previewText: record.previewText,
55
86
  previewJson: record.previewJson,
56
- payloadText: record.payloadText,
57
- payloadJson: record.payloadJson,
87
+ payloadText: payloadText ?? undefined,
88
+ payloadJson: payloadJson !== null ? JSON.parse(payloadJson) : undefined,
89
+ payloadStorageKey: payloadStorageKey ?? undefined,
58
90
  bytes: record.bytes,
59
91
  truncated: record.truncated,
60
92
  createdAt,
@@ -82,6 +114,7 @@ export class PrismaTelemetryArtifactStore implements TelemetryArtifactStore {
82
114
  previewJson: this.parseJson(row.previewJson),
83
115
  payloadText: row.payloadText ?? undefined,
84
116
  payloadJson: this.parseJson(row.payloadJson),
117
+ payloadStorageKey: row.payloadStorageKey ?? undefined,
85
118
  bytes: row.bytes ?? undefined,
86
119
  truncated: row.truncated ?? undefined,
87
120
  createdAt: row.createdAt,
@@ -90,7 +123,7 @@ export class PrismaTelemetryArtifactStore implements TelemetryArtifactStore {
90
123
  }));
91
124
  }
92
125
 
93
- async pruneExpired(args: Readonly<{ nowIso: string; limit?: number }>): Promise<number> {
126
+ async pruneExpired(args: TelemetryPruneArgs): Promise<{ count: number; storageKeys: ReadonlyArray<string> }> {
94
127
  const rows = await this.prisma.telemetryArtifact.findMany({
95
128
  where: {
96
129
  retentionExpiresAt: {
@@ -99,13 +132,15 @@ export class PrismaTelemetryArtifactStore implements TelemetryArtifactStore {
99
132
  },
100
133
  select: {
101
134
  artifactId: true,
135
+ payloadStorageKey: true,
102
136
  },
103
137
  orderBy: [{ retentionExpiresAt: "asc" }, { artifactId: "asc" }],
104
138
  ...(args.limit ? { take: args.limit } : {}),
105
139
  });
106
140
  if (rows.length === 0) {
107
- return 0;
141
+ return { count: 0, storageKeys: [] };
108
142
  }
143
+ const storageKeys = rows.flatMap((row) => (row.payloadStorageKey ? [row.payloadStorageKey] : []));
109
144
  const result = await this.prisma.telemetryArtifact.deleteMany({
110
145
  where: {
111
146
  artifactId: {
@@ -113,7 +148,7 @@ export class PrismaTelemetryArtifactStore implements TelemetryArtifactStore {
113
148
  },
114
149
  },
115
150
  });
116
- return result.count;
151
+ return { count: result.count, storageKeys };
117
152
  }
118
153
 
119
154
  private parseJson(value: string | null): unknown {
@@ -22,6 +22,8 @@ import { inject, injectable } from "@codemation/core";
22
22
  import type { WorkflowRunRepository } from "../../domain/runs/WorkflowRunRepository";
23
23
  import type { Prisma } from "../../../prisma-generated/prisma-postgresql-client/client.js";
24
24
  import { PrismaDatabaseClientToken, type PrismaDatabaseClient } from "./PrismaDatabaseClient";
25
+ import type { WorkflowSnapshotRepository } from "./PrismaWorkflowSnapshotRepository";
26
+ import { PrismaWorkflowSnapshotRepository } from "./PrismaWorkflowSnapshotRepository";
25
27
 
26
28
  type ExecutionInstanceRow = {
27
29
  instanceId: string;
@@ -80,7 +82,10 @@ type RunSlotProjectionRow = {
80
82
 
81
83
  @injectable()
82
84
  export class PrismaWorkflowRunRepository implements WorkflowRunRepository, WorkflowExecutionRepository {
83
- constructor(@inject(PrismaDatabaseClientToken) private readonly prisma: PrismaDatabaseClient) {}
85
+ constructor(
86
+ @inject(PrismaDatabaseClientToken) private readonly prisma: PrismaDatabaseClient,
87
+ @inject(PrismaWorkflowSnapshotRepository) private readonly snapshotRepo: WorkflowSnapshotRepository,
88
+ ) {}
84
89
 
85
90
  async createRun(args: {
86
91
  runId: RunId;
@@ -96,6 +101,14 @@ export class PrismaWorkflowRunRepository implements WorkflowRunRepository, Workf
96
101
  }): Promise<void> {
97
102
  const now = new Date().toISOString();
98
103
  const testContext = args.executionOptions?.testContext;
104
+ const snapshotJson = args.workflowSnapshot ? JSON.stringify(args.workflowSnapshot) : null;
105
+ const workflowSnapshotId =
106
+ snapshotJson !== null
107
+ ? await this.snapshotRepo.findOrCreate({
108
+ workflowId: args.workflowId,
109
+ snapshotJson,
110
+ })
111
+ : null;
99
112
  await this.prisma.run.create({
100
113
  data: {
101
114
  runId: args.runId,
@@ -108,7 +121,8 @@ export class PrismaWorkflowRunRepository implements WorkflowRunRepository, Workf
108
121
  revision: 0,
109
122
  outputsByNodeJson: JSON.stringify({}),
110
123
  controlJson: args.control ? JSON.stringify(args.control) : null,
111
- workflowSnapshotJson: args.workflowSnapshot ? JSON.stringify(args.workflowSnapshot) : null,
124
+ workflowSnapshotJson: snapshotJson,
125
+ workflowSnapshotId,
112
126
  policySnapshotJson: args.policySnapshot ? JSON.stringify(args.policySnapshot) : null,
113
127
  engineCountersJson: args.engineCounters ? JSON.stringify(args.engineCounters) : null,
114
128
  mutableStateJson: args.mutableState ? JSON.stringify(args.mutableState) : null,
@@ -290,6 +304,14 @@ export class PrismaWorkflowRunRepository implements WorkflowRunRepository, Workf
290
304
  const workItems = this.buildWorkItems(state, now);
291
305
  const instances = this.buildExecutionInstances(state);
292
306
  const projectionJson = this.buildProjectionSlotStatesJson(state);
307
+ const snapshotJson = state.workflowSnapshot ? JSON.stringify(state.workflowSnapshot) : null;
308
+ const workflowSnapshotId =
309
+ snapshotJson !== null
310
+ ? await this.snapshotRepo.findOrCreate({
311
+ workflowId: state.workflowId,
312
+ snapshotJson,
313
+ })
314
+ : null;
293
315
 
294
316
  await this.prisma.$transaction(async (tx) => {
295
317
  await tx.runWorkItem.deleteMany({ where: { runId: state.runId } });
@@ -389,7 +411,8 @@ export class PrismaWorkflowRunRepository implements WorkflowRunRepository, Workf
389
411
  parentJson: state.parent ? JSON.stringify(state.parent) : null,
390
412
  executionOptionsJson: state.executionOptions ? JSON.stringify(state.executionOptions) : null,
391
413
  controlJson: state.control ? JSON.stringify(state.control) : null,
392
- workflowSnapshotJson: state.workflowSnapshot ? JSON.stringify(state.workflowSnapshot) : null,
414
+ workflowSnapshotJson: snapshotJson,
415
+ workflowSnapshotId,
393
416
  policySnapshotJson: state.policySnapshot ? JSON.stringify(state.policySnapshot) : null,
394
417
  engineCountersJson: state.engineCounters ? JSON.stringify(state.engineCounters) : null,
395
418
  mutableStateJson: state.mutableState ? JSON.stringify(state.mutableState) : null,
@@ -0,0 +1,48 @@
1
+ import { createHash } from "node:crypto";
2
+ import { inject, injectable } from "@codemation/core";
3
+ import { PrismaDatabaseClientToken, type PrismaDatabaseClient } from "./PrismaDatabaseClient";
4
+
5
+ export interface WorkflowSnapshotRepository {
6
+ /**
7
+ * Returns the id of an existing snapshot matching (workflowId, snapshotHash), or creates
8
+ * a new one from the provided snapshotJson. Deduplication is by content hash.
9
+ */
10
+ findOrCreate(args: Readonly<{ workflowId: string; snapshotJson: string }>): Promise<string>;
11
+ }
12
+
13
+ @injectable()
14
+ export class PrismaWorkflowSnapshotRepository implements WorkflowSnapshotRepository {
15
+ constructor(
16
+ @inject(PrismaDatabaseClientToken)
17
+ private readonly prisma: PrismaDatabaseClient,
18
+ ) {}
19
+
20
+ async findOrCreate(args: Readonly<{ workflowId: string; snapshotJson: string }>): Promise<string> {
21
+ const snapshotHash = createHash("sha256").update(args.snapshotJson, "utf8").digest("hex");
22
+ const existing = await this.prisma.workflowSnapshot.findUnique({
23
+ where: { workflowId_snapshotHash: { workflowId: args.workflowId, snapshotHash } },
24
+ select: { id: true },
25
+ });
26
+ if (existing) {
27
+ return existing.id;
28
+ }
29
+ const id = crypto.randomUUID();
30
+ await this.prisma.workflowSnapshot.upsert({
31
+ where: { workflowId_snapshotHash: { workflowId: args.workflowId, snapshotHash } },
32
+ create: {
33
+ id,
34
+ workflowId: args.workflowId,
35
+ snapshotHash,
36
+ snapshotJson: args.snapshotJson,
37
+ createdAt: new Date().toISOString(),
38
+ },
39
+ update: {},
40
+ });
41
+ // Re-fetch so the returned id is the winner under concurrent inserts
42
+ const row = await this.prisma.workflowSnapshot.findUniqueOrThrow({
43
+ where: { workflowId_snapshotHash: { workflowId: args.workflowId, snapshotHash } },
44
+ select: { id: true },
45
+ });
46
+ return row.id;
47
+ }
48
+ }