@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,5 +1,5 @@
1
1
  {
2
- "name": "prisma-client-24b46c8e91122e4534d61d8dbe4a532cd64a32672708af7495f2eff7644bcf14",
2
+ "name": "prisma-client-f1f592cca96cc07ff4c49ca6848788c0583fceb20133a8099c5c0d76247b55cc",
3
3
  "main": "index.js",
4
4
  "types": "index.d.ts",
5
5
  "browser": "default.js",
@@ -18,6 +18,7 @@ model Run {
18
18
  executionOptionsJson String? @map("execution_options_json")
19
19
  controlJson String? @map("control_json")
20
20
  workflowSnapshotJson String? @map("workflow_snapshot_json")
21
+ workflowSnapshotId String? @map("workflow_snapshot_id")
21
22
  policySnapshotJson String? @map("policy_snapshot_json")
22
23
  engineCountersJson String? @map("engine_counters_json")
23
24
  mutableStateJson String? @map("mutable_state_json")
@@ -33,10 +34,12 @@ model Run {
33
34
  slotProjection RunSlotProjection?
34
35
  testSuiteRun TestSuiteRun? @relation(fields: [testSuiteRunId], references: [id])
35
36
  testAssertions TestAssertion[]
37
+ workflowSnapshot WorkflowSnapshot? @relation(fields: [workflowSnapshotId], references: [id])
36
38
 
37
39
  @@index([workflowId, startedAt])
38
40
  @@index([workflowId, status, finishedAt])
39
41
  @@index([testSuiteRunId, testCaseIndex])
42
+ @@index([workflowSnapshotId])
40
43
  }
41
44
 
42
45
  model RunWorkItem {
@@ -260,6 +263,20 @@ model TelemetrySpan {
260
263
  @@index([retentionExpiresAt])
261
264
  }
262
265
 
266
+ model WorkflowSnapshot {
267
+ id String @id
268
+ workflowId String @map("workflow_id")
269
+ /// SHA-256 hex digest of snapshotJson — dedup key: same workflow content → one row.
270
+ snapshotHash String @map("snapshot_hash")
271
+ snapshotJson String @map("snapshot_json")
272
+ createdAt String @map("created_at")
273
+
274
+ runs Run[]
275
+
276
+ @@unique([workflowId, snapshotHash])
277
+ @@index([workflowId, snapshotHash])
278
+ }
279
+
263
280
  model TelemetryArtifact {
264
281
  artifactId String @id @map("artifact_id")
265
282
  traceId String @map("trace_id")
@@ -274,6 +291,7 @@ model TelemetryArtifact {
274
291
  previewJson String? @map("preview_json")
275
292
  payloadText String? @map("payload_text")
276
293
  payloadJson String? @map("payload_json")
294
+ payloadStorageKey String? @map("payload_storage_key")
277
295
  bytes Int?
278
296
  truncated Boolean?
279
297
  createdAt String @map("created_at")
@@ -470,3 +488,23 @@ model VerificationToken {
470
488
  @@unique([identifier, token])
471
489
  @@map("codemation_auth_verification_token")
472
490
  }
491
+
492
+ model WorkflowAuditLog {
493
+ id String @id @map("id")
494
+ occurredAt DateTime @map("occurred_at")
495
+ actorUserId String @map("actor_user_id")
496
+ actorSessionId String? @map("actor_session_id")
497
+ action String @map("action")
498
+ resourceType String @map("resource_type")
499
+ resourceId String @map("resource_id")
500
+ outcome String @map("outcome")
501
+ errorCode String? @map("error_code")
502
+ correlationId String? @map("correlation_id")
503
+ workflowId String @map("workflow_id")
504
+ runId String? @map("run_id")
505
+ nodeId String? @map("node_id")
506
+
507
+ @@index([actorUserId, occurredAt])
508
+ @@index([workflowId, occurredAt])
509
+ @@map("workflow_audit_log")
510
+ }
@@ -0,0 +1,18 @@
1
+ import { AppConfigLoader } from "../src/presentation/server/AppConfigLoader.ts";
2
+
3
+ const loader = new AppConfigLoader();
4
+ const result = await loader.load({
5
+ consumerRoot: "/home/cblokland/projects/made/codemation/apps/test-dev",
6
+ repoRoot: "/home/cblokland/projects/made/codemation",
7
+ env: process.env,
8
+ });
9
+ console.log("collections.length:", result.appConfig.collections.length);
10
+ console.log(
11
+ "collection names:",
12
+ result.appConfig.collections.map((c) => c.name),
13
+ );
14
+ const first = result.appConfig.collections[0];
15
+ console.log(
16
+ "first collection:",
17
+ first ? { name: first.name, fields: Object.keys(first.fields ?? {}), indexes: first.indexes } : "no collections",
18
+ );
@@ -1,4 +1,5 @@
1
1
  import { spawnSync } from "node:child_process";
2
+ import { execaSync } from "execa";
2
3
  import { existsSync, realpathSync } from "node:fs";
3
4
  import { createRequire } from "node:module";
4
5
  import path from "node:path";
@@ -15,7 +16,7 @@ class PrismaClientGenerator {
15
16
  this.reexecUnderSupportedNodeWhenNeeded();
16
17
  for (const provider of this.providers) {
17
18
  const result = spawnSync(process.execPath, [this.prismaCliEntrypoint, "generate"], {
18
- cwd: import.meta.dirname.replace(/\/scripts$/, ""),
19
+ cwd: path.dirname(import.meta.dirname),
19
20
  env: {
20
21
  ...process.env,
21
22
  CODEMATION_PRISMA_PROVIDER: provider,
@@ -41,7 +42,7 @@ class PrismaClientGenerator {
41
42
  );
42
43
  }
43
44
  const result = spawnSync(supportedNodeBinary, [new URL(import.meta.url).pathname], {
44
- cwd: import.meta.dirname.replace(/\/scripts$/, ""),
45
+ cwd: path.dirname(import.meta.dirname),
45
46
  env: {
46
47
  ...process.env,
47
48
  [this.reexecMarker]: "1",
@@ -80,16 +81,24 @@ class PrismaClientGenerator {
80
81
  if (npmExecPath) {
81
82
  return realpathSync(npmExecPath);
82
83
  }
83
- const result = spawnSync("bash", ["-lc", 'realpath "$(command -v pnpm)"'], {
84
- env: process.env,
85
- encoding: "utf8",
86
- shell: false,
87
- });
88
- if (result.status !== 0) {
89
- return undefined;
84
+ try {
85
+ // execa resolves bare command names against the OS-appropriate PATH (handles `.cmd` / `.exe`
86
+ // shims on Windows automatically), so this works on every platform we run dev on.
87
+ const result = execaSync("pnpm", ["root", "-g"], { reject: false });
88
+ if (result.exitCode === 0 && typeof result.stdout === "string" && result.stdout.trim().length > 0) {
89
+ // pnpm root -g prints "<pnpm-store>/global/5/node_modules"; the pnpm binary lives one level
90
+ // up under the install root. We only need *a* directory close to the pnpm binary so the
91
+ // sibling-`node` lookup below can probe candidate paths.
92
+ const globalRoot = result.stdout.trim();
93
+ const candidate = path.resolve(globalRoot, "..", "..", "pnpm");
94
+ if (existsSync(candidate)) {
95
+ return realpathSync(candidate);
96
+ }
97
+ }
98
+ } catch {
99
+ // fall through
90
100
  }
91
- const pnpmPath = result.stdout.trim();
92
- return pnpmPath.length > 0 ? pnpmPath : undefined;
101
+ return undefined;
93
102
  }
94
103
 
95
104
  static isSupportedNode(version) {
@@ -0,0 +1,96 @@
1
+ import type { Clock } from "@codemation/core";
2
+ import { inject, injectable } from "@codemation/core";
3
+ import { ApplicationTokens } from "../applicationTokens";
4
+ import type { Logger } from "./logging/Logger";
5
+ import type { AppConfig } from "../presentation/config/AppConfig";
6
+ import { ServerLoggerFactory } from "../infrastructure/logging/ServerLoggerFactory";
7
+ import {
8
+ PrismaDatabaseClientToken,
9
+ type PrismaDatabaseClient,
10
+ } from "../infrastructure/persistence/PrismaDatabaseClient";
11
+
12
+ /**
13
+ * Periodically deletes WorkflowAuditLog rows older than the configured retention period.
14
+ *
15
+ * Default retention: 90 days.
16
+ * Override: `CODEMATION_AUDIT_WORKFLOW_RETENTION_SECONDS` in env.
17
+ * Disable: `CODEMATION_AUDIT_PRUNE_ENABLED=false`.
18
+ */
19
+ @injectable()
20
+ export class WorkflowAuditLogPruneScheduler {
21
+ static readonly defaultIntervalMs = 60 * 60 * 1_000;
22
+ /** 90 days in seconds (default retention). */
23
+ static readonly defaultRetentionSeconds = 90 * 24 * 3600;
24
+
25
+ private timer: ReturnType<typeof setInterval> | undefined;
26
+ private readonly logger: Logger;
27
+
28
+ constructor(
29
+ @inject(ApplicationTokens.Clock) private readonly clock: Clock,
30
+ @inject(PrismaDatabaseClientToken) private readonly prisma: PrismaDatabaseClient,
31
+ @inject(ApplicationTokens.AppConfig) private readonly appConfig: AppConfig,
32
+ @inject(ServerLoggerFactory) loggerFactory: ServerLoggerFactory,
33
+ ) {
34
+ this.logger = loggerFactory.create("codemation.auditPrune");
35
+ }
36
+
37
+ start(): void {
38
+ if (this.appConfig.env.CODEMATION_AUDIT_PRUNE_ENABLED === "false") {
39
+ return;
40
+ }
41
+ if (this.timer) {
42
+ return;
43
+ }
44
+ const intervalMs = Number(
45
+ this.appConfig.env.CODEMATION_AUDIT_PRUNE_INTERVAL_MS ??
46
+ this.appConfig.env.CODEMATION_RUN_PRUNE_INTERVAL_MS ??
47
+ WorkflowAuditLogPruneScheduler.defaultIntervalMs,
48
+ );
49
+ void this.runScheduledTick();
50
+ this.timer = setInterval(() => {
51
+ void this.runScheduledTick();
52
+ }, intervalMs);
53
+ }
54
+
55
+ stop(): void {
56
+ if (this.timer) {
57
+ clearInterval(this.timer);
58
+ this.timer = undefined;
59
+ }
60
+ }
61
+
62
+ /** Exposed for tests; production path is the interval started by {@link start}. */
63
+ async runOnce(): Promise<void> {
64
+ const retentionSec = Number(
65
+ this.appConfig.env.CODEMATION_AUDIT_WORKFLOW_RETENTION_SECONDS ??
66
+ WorkflowAuditLogPruneScheduler.defaultRetentionSeconds,
67
+ );
68
+ const now = this.clock.now();
69
+ const cutoff = new Date(now.getTime() - retentionSec * 1000);
70
+ const limit = Number(this.appConfig.env.CODEMATION_TELEMETRY_PRUNE_LIMIT ?? 2_000);
71
+
72
+ // Two-step delete: find IDs first (respects limit), then delete by ID.
73
+ const rows = await this.prisma.workflowAuditLog.findMany({
74
+ where: { occurredAt: { lt: cutoff } },
75
+ select: { id: true },
76
+ take: limit,
77
+ });
78
+
79
+ if (rows.length === 0) {
80
+ return;
81
+ }
82
+
83
+ const ids = rows.map((r) => r.id);
84
+ await this.prisma.workflowAuditLog.deleteMany({ where: { id: { in: ids } } });
85
+
86
+ this.logger.info(`WorkflowAuditLog prune: deleted ${ids.length} row(s) older than ${cutoff.toISOString()}`);
87
+ }
88
+
89
+ private async runScheduledTick(): Promise<void> {
90
+ try {
91
+ await this.runOnce();
92
+ } catch (error) {
93
+ this.logger.warn(`WorkflowAuditLog prune tick failed: ${error instanceof Error ? error.message : String(error)}`);
94
+ }
95
+ }
96
+ }
@@ -2,4 +2,8 @@ export type AuthenticatedPrincipal = Readonly<{
2
2
  id: string;
3
3
  email: string | null;
4
4
  name: string | null;
5
+ /** Set to "managed-jwt" when the principal was verified from a CP-signed bearer token. */
6
+ source?: "managed-jwt";
7
+ /** The workspace ID from the JWT `aud` claim. Present when source === "managed-jwt". */
8
+ workspaceId?: string;
5
9
  }>;
@@ -21,6 +21,7 @@ import { CommandHandler } from "../bus/CommandHandler";
21
21
  import type { CreateRunRequest, RunCommandResult } from "../contracts/RunContracts";
22
22
  import { WorkflowDebuggerOverlayStateFactory } from "../workflows/WorkflowDebuggerOverlayStateFactory";
23
23
  import { StartWorkflowRunCommand } from "./StartWorkflowRunCommand";
24
+ import { CredentialBindingService } from "../../domain/credentials/CredentialBindingService";
24
25
 
25
26
  @HandlesCommand.forCommand(StartWorkflowRunCommand)
26
27
  export class StartWorkflowRunCommandHandler extends CommandHandler<StartWorkflowRunCommand, RunCommandResult> {
@@ -39,6 +40,8 @@ export class StartWorkflowRunCommandHandler extends CommandHandler<StartWorkflow
39
40
  private readonly workflowRunRepository: WorkflowRunRepository,
40
41
  @inject(ApplicationTokens.WorkflowDebuggerOverlayRepository)
41
42
  private readonly workflowDebuggerOverlayRepository: WorkflowDebuggerOverlayRepository,
43
+ @inject(CredentialBindingService)
44
+ private readonly credentialBindingService: CredentialBindingService,
42
45
  @inject(ApplicationTokens.LoggerFactory)
43
46
  loggerFactory: LoggerFactory,
44
47
  ) {
@@ -58,6 +61,7 @@ export class StartWorkflowRunCommandHandler extends CommandHandler<StartWorkflow
58
61
  if (!workflow) {
59
62
  throw new ApplicationRequestError(404, "Unknown workflowId");
60
63
  }
64
+ await this.credentialBindingService.assertRequiredCredentialsBound(body.workflowId);
61
65
  const executionOptions = body.mode
62
66
  ? {
63
67
  mode: body.mode,
@@ -28,6 +28,12 @@ export type WorkflowNodeDto = Readonly<{
28
28
  * the properties panel as an "Open in editor" navigation link.
29
29
  */
30
30
  referencedWorkflowId?: string;
31
+ /**
32
+ * Static configuration summary for the inspector — short label/value pairs that describe
33
+ * what this node will do at a glance, before any run telemetry exists. Pulled from the
34
+ * node config's optional `inspectorSummary()` hook (`NodeConfigBase.inspectorSummary`).
35
+ */
36
+ inspectorSummary?: ReadonlyArray<Readonly<{ label: string; value: string }>>;
31
37
  }>;
32
38
 
33
39
  export type WorkflowEdgeDto = Readonly<{
@@ -1,8 +1,10 @@
1
1
  import type { RunEvent } from "@codemation/core";
2
+ import type { TelemetrySpanUpsert } from "../../domain/telemetry/TelemetryContracts";
2
3
 
3
4
  export type WorkflowWebsocketMessage =
4
5
  | Readonly<{ kind: "event"; event: RunEvent }>
5
6
  | Readonly<{ kind: "workflowChanged"; workflowId: string }>
6
7
  | Readonly<{ kind: "devBuildStarted"; workflowId: string; buildVersion?: string }>
7
8
  | Readonly<{ kind: "devBuildCompleted"; workflowId: string; buildVersion: string }>
8
- | Readonly<{ kind: "devBuildFailed"; workflowId: string; message: string }>;
9
+ | Readonly<{ kind: "devBuildFailed"; workflowId: string; message: string }>
10
+ | Readonly<{ kind: "telemetryEvent"; runId: string; span: TelemetrySpanUpsert }>;
@@ -13,6 +13,7 @@ import type {
13
13
  WorkflowNodeDto,
14
14
  WorkflowSummary,
15
15
  } from "../contracts/WorkflowViewContracts";
16
+ import { McpServerCatalog } from "../../mcp/McpServerCatalog";
16
17
  import type { DataMapper } from "./DataMapper";
17
18
  import { WorkflowPolicyUiPresentationFactory } from "./WorkflowPolicyUiPresentationFactory";
18
19
 
@@ -23,6 +24,8 @@ export class WorkflowDefinitionMapper implements DataMapper<WorkflowDefinition,
23
24
  private readonly policyUi: WorkflowPolicyUiPresentationFactory,
24
25
  @inject(CoreTokens.WorkflowActivationPolicy)
25
26
  private readonly workflowActivationPolicy: WorkflowActivationPolicy,
27
+ @inject(McpServerCatalog)
28
+ private readonly mcpCatalog: McpServerCatalog,
26
29
  ) {}
27
30
 
28
31
  async map(workflow: WorkflowDefinition): Promise<WorkflowDto> {
@@ -134,6 +137,7 @@ export class WorkflowDefinitionMapper implements DataMapper<WorkflowDefinition,
134
137
  : undefined;
135
138
  const description = (node.config as { description?: string }).description;
136
139
  const referencedWorkflowId = (node.config as { workflowId?: string }).workflowId;
140
+ const inspectorSummary = this.readInspectorSummary(node.config);
137
141
  nodes.push({
138
142
  id: node.id,
139
143
  kind: node.kind,
@@ -149,6 +153,7 @@ export class WorkflowDefinitionMapper implements DataMapper<WorkflowDefinition,
149
153
  ...(typeof referencedWorkflowId === "string" && referencedWorkflowId.trim().length > 0
150
154
  ? { referencedWorkflowId }
151
155
  : {}),
156
+ ...(inspectorSummary ? { inspectorSummary } : {}),
152
157
  });
153
158
  if (AgentConfigInspector.isAgentNodeConfig(node.config)) {
154
159
  this.appendVirtualConnectionNodes(
@@ -221,7 +226,9 @@ export class WorkflowDefinitionMapper implements DataMapper<WorkflowDefinition,
221
226
  if (!AgentConfigInspector.isAgentNodeConfig(node.config)) {
222
227
  continue;
223
228
  }
224
- const descriptors = AgentConnectionNodeCollector.collect(node.id, node.config);
229
+ const descriptors = AgentConnectionNodeCollector.collect(node.id, node.config, (id) =>
230
+ this.mcpCatalog.get(id),
231
+ );
225
232
  byAgentNodeId.set(node.id, descriptors);
226
233
  const byChildId = new Map<string, AgentConnectionNodeDescriptor>();
227
234
  for (const descriptor of descriptors) {
@@ -287,6 +294,38 @@ export class WorkflowDefinitionMapper implements DataMapper<WorkflowDefinition,
287
294
  * Omit optional port fields when undefined so persisted snapshot DTOs (which never serialize
288
295
  * undefined keys) stay aligned with live workflow mapping.
289
296
  */
297
+ private readInspectorSummary(
298
+ config: NodeDefinition["config"] | undefined,
299
+ ): ReadonlyArray<Readonly<{ label: string; value: string }>> | undefined {
300
+ if (!config || typeof config !== "object") {
301
+ return undefined;
302
+ }
303
+ const fn = (config as { inspectorSummary?: () => unknown }).inspectorSummary;
304
+ if (typeof fn !== "function") {
305
+ return undefined;
306
+ }
307
+ let raw: unknown;
308
+ try {
309
+ raw = fn.call(config);
310
+ } catch {
311
+ // A misbehaving inspectorSummary must not break workflow loading; skip silently.
312
+ return undefined;
313
+ }
314
+ if (!Array.isArray(raw)) {
315
+ return undefined;
316
+ }
317
+ const rows: Array<Readonly<{ label: string; value: string }>> = [];
318
+ for (const entry of raw) {
319
+ if (!entry || typeof entry !== "object") continue;
320
+ const { label, value } = entry as { label?: unknown; value?: unknown };
321
+ if (typeof label !== "string" || typeof value !== "string") continue;
322
+ const trimmedLabel = label.trim();
323
+ if (trimmedLabel.length === 0) continue;
324
+ rows.push({ label: trimmedLabel, value });
325
+ }
326
+ return rows.length > 0 ? rows : undefined;
327
+ }
328
+
290
329
  private nodePortFieldsFromConfig(
291
330
  config: NodeDefinition["config"] | undefined,
292
331
  ): Pick<WorkflowNodeDto, "continueWhenEmptyOutput" | "declaredOutputPorts" | "declaredInputPorts"> {
@@ -104,7 +104,13 @@ export class WorkflowRunRetentionPruneScheduler {
104
104
  if (this.appConfig.env.CODEMATION_TELEMETRY_PRUNE_ENABLED !== "false") {
105
105
  const telemetryLimit = Number(this.appConfig.env.CODEMATION_TELEMETRY_PRUNE_LIMIT ?? 2_000);
106
106
  prunedSpanCount = await this.telemetrySpanStore.pruneExpired({ nowIso, limit: telemetryLimit });
107
- prunedArtifactCount = await this.telemetryArtifactStore.pruneExpired({ nowIso, limit: telemetryLimit });
107
+ const { count: artifactCount, storageKeys: artifactStorageKeys } = await this.telemetryArtifactStore.pruneExpired(
108
+ { nowIso, limit: telemetryLimit },
109
+ );
110
+ for (const key of artifactStorageKeys) {
111
+ await this.binaryStorage.delete(key);
112
+ }
113
+ prunedArtifactCount = artifactCount;
108
114
  prunedMetricCount = await this.telemetryMetricPointStore.pruneExpired({ nowIso, limit: telemetryLimit });
109
115
  }
110
116
 
@@ -9,6 +9,7 @@ import { OtelIdentityFactory } from "./OtelIdentityFactory";
9
9
  import { TelemetryEnricherChain } from "./TelemetryEnricherChain";
10
10
  import { TelemetryPrivacyPolicy } from "./TelemetryPrivacyPolicy";
11
11
  import { TelemetryRetentionTimestampFactory } from "./TelemetryRetentionTimestampFactory";
12
+ import { NoOpTelemetrySpanPublisher, type TelemetrySpanPublisher } from "./TelemetrySpanPublisher";
12
13
 
13
14
  export type StoredExecutionTelemetryDeps = Readonly<{
14
15
  traceId: string;
@@ -24,8 +25,12 @@ export type StoredExecutionTelemetryDeps = Readonly<{
24
25
  telemetryPrivacyPolicy: TelemetryPrivacyPolicy;
25
26
  telemetryRetentionTimestampFactory: TelemetryRetentionTimestampFactory;
26
27
  otelIdentityFactory: OtelIdentityFactory;
28
+ /** Optional publisher for streaming span upserts over WebSocket. Defaults to no-op. */
29
+ telemetrySpanPublisher?: TelemetrySpanPublisher;
27
30
  }>;
28
31
 
32
+ export { NoOpTelemetrySpanPublisher };
33
+
29
34
  export type StoredSpanScopeArgs = StoredExecutionTelemetryDeps &
30
35
  Readonly<{
31
36
  spanId: string;
@@ -12,6 +12,7 @@ import { StoredExecutionTelemetry } from "./StoredExecutionTelemetry";
12
12
  import { TelemetryEnricherChain } from "./TelemetryEnricherChain";
13
13
  import { TelemetryPrivacyPolicy } from "./TelemetryPrivacyPolicy";
14
14
  import { TelemetryRetentionTimestampFactory } from "./TelemetryRetentionTimestampFactory";
15
+ import type { TelemetrySpanPublisher } from "./TelemetrySpanPublisher";
15
16
 
16
17
  @injectable()
17
18
  export class OtelExecutionTelemetryFactory implements ExecutionTelemetryFactory {
@@ -32,6 +33,8 @@ export class OtelExecutionTelemetryFactory implements ExecutionTelemetryFactory
32
33
  private readonly telemetryRetentionTimestampFactory: TelemetryRetentionTimestampFactory,
33
34
  @inject(OtelIdentityFactory)
34
35
  private readonly otelIdentityFactory: OtelIdentityFactory,
36
+ @inject(ApplicationTokens.TelemetrySpanPublisher)
37
+ private readonly telemetrySpanPublisher: TelemetrySpanPublisher,
35
38
  ) {}
36
39
 
37
40
  create(
@@ -51,6 +54,7 @@ export class OtelExecutionTelemetryFactory implements ExecutionTelemetryFactory
51
54
  telemetryPrivacyPolicy: this.telemetryPrivacyPolicy,
52
55
  telemetryRetentionTimestampFactory: this.telemetryRetentionTimestampFactory,
53
56
  otelIdentityFactory: this.otelIdentityFactory,
57
+ telemetrySpanPublisher: this.telemetrySpanPublisher,
54
58
  });
55
59
  }
56
60
  }
@@ -12,6 +12,7 @@ import type {
12
12
  } from "@codemation/core";
13
13
  import { NoOpTelemetryArtifactReference } from "@codemation/core";
14
14
  import type { TelemetrySpanUpsert } from "../../domain/telemetry/TelemetryContracts";
15
+ import { NoOpTelemetrySpanPublisher } from "./TelemetrySpanPublisher";
15
16
  import type { StoredSpanScopeArgs } from "./OtelExecutionTelemetry.types";
16
17
 
17
18
  export class StoredTelemetrySpanScope implements TelemetrySpanScope {
@@ -226,7 +227,7 @@ export class StoredTelemetrySpanScope implements TelemetrySpanScope {
226
227
  const retentionExpiresAt =
227
228
  update.retentionExpiresAt ??
228
229
  this.deps.telemetryRetentionTimestampFactory.createSpanExpiry(this.deps.policySnapshot, observedAt);
229
- await this.deps.telemetrySpanStore.upsert({
230
+ const upsertRecord: TelemetrySpanUpsert = {
230
231
  traceId: this.traceId,
231
232
  spanId: this.spanId,
232
233
  parentSpanId: this.parentSpanId,
@@ -241,7 +242,10 @@ export class StoredTelemetrySpanScope implements TelemetrySpanScope {
241
242
  nodeRole: enrichment.nodeRole,
242
243
  retentionExpiresAt,
243
244
  ...update,
244
- });
245
+ };
246
+ await this.deps.telemetrySpanStore.upsert(upsertRecord);
247
+ const publisher = this.deps.telemetrySpanPublisher ?? NoOpTelemetrySpanPublisher;
248
+ await publisher.publishSpan(upsertRecord);
245
249
  await this.touchTraceContextExpiry(
246
250
  this.deps.telemetryRetentionTimestampFactory.createTraceContextExpiry(this.deps.policySnapshot, observedAt),
247
251
  );
@@ -3,38 +3,48 @@ import { injectable } from "@codemation/core";
3
3
 
4
4
  @injectable()
5
5
  export class TelemetryRetentionTimestampFactory {
6
- createSpanExpiry(policySnapshot: PersistedRunPolicySnapshot | undefined, observedAt: Date): string | undefined {
7
- return this.createExpiry(policySnapshot?.telemetrySpanRetentionSeconds, observedAt);
6
+ /** Default span retention: 7 days (overridden by policySnapshot). */
7
+ static readonly defaultSpanRetentionSeconds = 7 * 24 * 3600;
8
+ /** Default artifact retention: 3 days (overridden by policySnapshot). */
9
+ static readonly defaultArtifactRetentionSeconds = 3 * 24 * 3600;
10
+ /** Default metric retention: 30 days (overridden by policySnapshot). */
11
+ static readonly defaultMetricRetentionSeconds = 30 * 24 * 3600;
12
+
13
+ createSpanExpiry(policySnapshot: PersistedRunPolicySnapshot | undefined, observedAt: Date): string {
14
+ return this.createExpiry(
15
+ policySnapshot?.telemetrySpanRetentionSeconds ?? TelemetryRetentionTimestampFactory.defaultSpanRetentionSeconds,
16
+ observedAt,
17
+ );
8
18
  }
9
19
 
10
- createArtifactExpiry(policySnapshot: PersistedRunPolicySnapshot | undefined, observedAt: Date): string | undefined {
11
- return this.createExpiry(policySnapshot?.telemetryArtifactRetentionSeconds, observedAt);
20
+ createArtifactExpiry(policySnapshot: PersistedRunPolicySnapshot | undefined, observedAt: Date): string {
21
+ return this.createExpiry(
22
+ policySnapshot?.telemetryArtifactRetentionSeconds ?? TelemetryRetentionTimestampFactory.defaultArtifactRetentionSeconds,
23
+ observedAt,
24
+ );
12
25
  }
13
26
 
14
- createMetricExpiry(policySnapshot: PersistedRunPolicySnapshot | undefined, observedAt: Date): string | undefined {
15
- return this.createExpiry(policySnapshot?.telemetryMetricRetentionSeconds, observedAt);
27
+ createMetricExpiry(policySnapshot: PersistedRunPolicySnapshot | undefined, observedAt: Date): string {
28
+ return this.createExpiry(
29
+ policySnapshot?.telemetryMetricRetentionSeconds ?? TelemetryRetentionTimestampFactory.defaultMetricRetentionSeconds,
30
+ observedAt,
31
+ );
16
32
  }
17
33
 
18
34
  createTraceContextExpiry(
19
35
  policySnapshot: PersistedRunPolicySnapshot | undefined,
20
36
  observedAt: Date,
21
- ): string | undefined {
37
+ ): string {
22
38
  const candidates = [
23
- policySnapshot?.telemetrySpanRetentionSeconds,
24
- policySnapshot?.telemetryArtifactRetentionSeconds,
25
- policySnapshot?.telemetryMetricRetentionSeconds,
39
+ policySnapshot?.telemetrySpanRetentionSeconds ?? TelemetryRetentionTimestampFactory.defaultSpanRetentionSeconds,
40
+ policySnapshot?.telemetryArtifactRetentionSeconds ?? TelemetryRetentionTimestampFactory.defaultArtifactRetentionSeconds,
41
+ policySnapshot?.telemetryMetricRetentionSeconds ?? TelemetryRetentionTimestampFactory.defaultMetricRetentionSeconds,
26
42
  ].filter((value): value is number => typeof value === "number" && value > 0);
27
- if (candidates.length === 0) {
28
- return undefined;
29
- }
30
43
  const maxSeconds = Math.max(...candidates);
31
44
  return this.createExpiry(maxSeconds, observedAt);
32
45
  }
33
46
 
34
- private createExpiry(retentionSeconds: number | undefined, observedAt: Date): string | undefined {
35
- if (!retentionSeconds || retentionSeconds <= 0) {
36
- return undefined;
37
- }
47
+ private createExpiry(retentionSeconds: number, observedAt: Date): string {
38
48
  return new Date(observedAt.getTime() + retentionSeconds * 1000).toISOString();
39
49
  }
40
50
  }
@@ -0,0 +1,11 @@
1
+ import type { TelemetrySpanUpsert } from "../../domain/telemetry/TelemetryContracts";
2
+
3
+ export interface TelemetrySpanPublisher {
4
+ publishSpan(span: TelemetrySpanUpsert): Promise<void>;
5
+ }
6
+
7
+ export const NoOpTelemetrySpanPublisher: TelemetrySpanPublisher = {
8
+ async publishSpan(_span: TelemetrySpanUpsert): Promise<void> {
9
+ // No-op: used in tests and when websocket relay is not wired in.
10
+ },
11
+ };
@@ -0,0 +1,31 @@
1
+ import { inject, injectable } from "@codemation/core";
2
+ import type { TelemetrySpanUpsert } from "../../domain/telemetry/TelemetryContracts";
3
+ import { ApplicationTokens } from "../../applicationTokens";
4
+ import type { TelemetrySpanPublisher } from "../telemetry/TelemetrySpanPublisher";
5
+ import type { WorkflowWebsocketPublisher } from "./WorkflowWebsocketPublisher";
6
+
7
+ /**
8
+ * Implements {@link TelemetrySpanPublisher} by forwarding each span upsert to a
9
+ * per-run WebSocket room (`run:<runId>`). Clients subscribe to this room when they
10
+ * open the inspector for a specific run.
11
+ *
12
+ * The relay fires *after* the span has been committed to persistent storage so that
13
+ * HTTP catch-up (on reconnect or initial mount) and WS pushes represent a consistent
14
+ * view of the data.
15
+ */
16
+ @injectable()
17
+ export class TelemetrySpanWebsocketRelay implements TelemetrySpanPublisher {
18
+ constructor(
19
+ @inject(ApplicationTokens.WorkflowWebsocketPublisher)
20
+ private readonly workflowWebsocketPublisher: WorkflowWebsocketPublisher,
21
+ ) {}
22
+
23
+ async publishSpan(span: TelemetrySpanUpsert): Promise<void> {
24
+ const roomId = `run:${span.runId}`;
25
+ await this.workflowWebsocketPublisher.publishToRoom(roomId, {
26
+ kind: "telemetryEvent",
27
+ runId: span.runId,
28
+ span,
29
+ });
30
+ }
31
+ }
@@ -1,4 +1,4 @@
1
- import type { Clock, TypeToken } from "@codemation/core";
1
+ import type { Clock, OAuthFlowExecutor, TypeToken } from "@codemation/core";
2
2
  import type { SessionVerifier } from "./application/auth/SessionVerifier";
3
3
  import type { Command } from "./application/bus/Command";
4
4
  import type { CommandBus } from "./application/bus/CommandBus";
@@ -10,7 +10,9 @@ import type { Query } from "./application/bus/Query";
10
10
  import type { QueryBus } from "./application/bus/QueryBus";
11
11
  import type { QueryHandler } from "./application/bus/QueryHandler";
12
12
  import type { Logger, LoggerFactory } from "./application/logging/Logger";
13
+ import type { ProcessRunner } from "./process/ProcessRunner.types";
13
14
  import type { WorkflowWebsocketPublisher } from "./application/websocket/WorkflowWebsocketPublisher";
15
+ import type { TelemetrySpanPublisher } from "./application/telemetry/TelemetrySpanPublisher";
14
16
  import type { CredentialStore } from "./domain/credentials/CredentialServices";
15
17
  import type {
16
18
  RunTraceContextRepository,
@@ -29,6 +31,10 @@ import type { AppConfig } from "./presentation/config/AppConfig";
29
31
  import type { CodemationAuthConfig } from "./presentation/config/CodemationAuthConfig";
30
32
  import type { CodemationWhitelabelConfig } from "./presentation/config/CodemationWhitelabelConfig";
31
33
  import type { HonoApiRouteRegistrar } from "./presentation/http/hono/HonoApiRouteRegistrar";
34
+ import type { InternalHonoApiRouteRegistrar } from "./presentation/http/hono/InternalHonoApiRouteRegistrar";
35
+ import type { ManagedCorsMiddleware } from "./auth/managed/ManagedCorsMiddleware";
36
+ import type { WebsocketAuthenticator } from "./presentation/websocket/WebsocketAuthenticator.types";
37
+ import type { IWorkflowAuditEmitter } from "./audit/IAuditEmitter";
32
38
 
33
39
  export const ApplicationTokens = {
34
40
  CodemationAuthConfig: Symbol.for("codemation.application.CodemationAuthConfig") as TypeToken<
@@ -51,9 +57,19 @@ export const ApplicationTokens = {
51
57
  DomainEventHandler<DomainEvent>
52
58
  >,
53
59
  HonoApiRouteRegistrar: Symbol.for("codemation.application.HonoApiRouteRegistrar") as TypeToken<HonoApiRouteRegistrar>,
60
+ InternalHonoApiRouteRegistrar: Symbol.for(
61
+ "codemation.application.InternalHonoApiRouteRegistrar",
62
+ ) as TypeToken<InternalHonoApiRouteRegistrar>,
63
+ ManagedCorsMiddleware: Symbol.for("codemation.application.ManagedCorsMiddleware") as TypeToken<ManagedCorsMiddleware>,
64
+ WebsocketAuthenticator: Symbol.for(
65
+ "codemation.application.WebsocketAuthenticator",
66
+ ) as TypeToken<WebsocketAuthenticator | null>,
54
67
  WorkflowWebsocketPublisher: Symbol.for(
55
68
  "codemation.application.WorkflowWebsocketPublisher",
56
69
  ) as TypeToken<WorkflowWebsocketPublisher>,
70
+ TelemetrySpanPublisher: Symbol.for(
71
+ "codemation.application.TelemetrySpanPublisher",
72
+ ) as TypeToken<TelemetrySpanPublisher>,
57
73
  WorkerRuntimeScheduler: Symbol.for(
58
74
  "codemation.application.WorkerRuntimeScheduler",
59
75
  ) as TypeToken<WorkerRuntimeScheduler>,
@@ -87,4 +103,7 @@ export const ApplicationTokens = {
87
103
  PrismaClient: Symbol.for("codemation.application.PrismaClient") as TypeToken<PrismaDatabaseClient>,
88
104
  SessionVerifier: Symbol.for("codemation.application.SessionVerifier") as TypeToken<SessionVerifier>,
89
105
  Clock: Symbol.for("codemation.application.Clock") as TypeToken<Clock>,
106
+ WorkflowAuditEmitter: Symbol.for("codemation.application.WorkflowAuditEmitter") as TypeToken<IWorkflowAuditEmitter>,
107
+ ProcessRunner: Symbol.for("codemation.application.ProcessRunner") as TypeToken<ProcessRunner>,
108
+ OAuthFlowExecutor: Symbol.for("codemation.application.OAuthFlowExecutor") as TypeToken<OAuthFlowExecutor>,
90
109
  } as const;