@codemation/host 0.6.0 → 0.7.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.
- package/CHANGELOG.md +431 -0
- package/LICENSE +1 -37
- package/dist/{ApiPaths-CLTHphYZ.js → ApiPaths-Dv1dcHu_.js} +4 -4
- package/dist/ApiPaths-Dv1dcHu_.js.map +1 -0
- package/dist/{AppConfigFactory-C6q-CSKb.js → AppConfigFactory-Cx4qQvRk.js} +112 -52
- package/dist/AppConfigFactory-Cx4qQvRk.js.map +1 -0
- package/dist/{AppConfigFactory-YnveXE9k.d.ts → AppConfigFactory-DnLoQ9Li.d.ts} +8490 -5548
- package/dist/{AppContainerFactory-qaqc-R1D.js → AppContainerFactory-DqKYCRNP.js} +7641 -2083
- package/dist/AppContainerFactory-DqKYCRNP.js.map +1 -0
- package/dist/{CodemationAppContext-DRu1Dpri.d.ts → CodemationAppContext-CKVv9W9q.d.ts} +8 -4
- package/dist/{CodemationAuthoring.types-fBRppnmi.d.ts → CodemationAuthoring.types-DA3G3s6d.d.ts} +25 -5
- package/dist/{CodemationAuthoring.types-DZl-sJaM.js → CodemationAuthoring.types-NGkBcmmT.js} +18 -6
- package/dist/CodemationAuthoring.types-NGkBcmmT.js.map +1 -0
- package/dist/{CodemationConfigNormalizer-DVko3cVN.d.ts → CodemationConfigNormalizer-BAKjetJ6.d.ts} +3 -3
- package/dist/{CodemationConsumerConfigLoader-BeAUS144.js → CodemationConsumerConfigLoader-GYpBBvqE.js} +79 -10
- package/dist/CodemationConsumerConfigLoader-GYpBBvqE.js.map +1 -0
- package/dist/{CodemationConsumerConfigLoader-DJWr86f-.d.ts → CodemationConsumerConfigLoader-nxOqvv46.d.ts} +17 -2
- package/dist/{CodemationPluginListMerger-B-W5Fa_X.js → CodemationPluginListMerger-D1B1IEbt.js} +1 -1
- package/dist/{CodemationPluginListMerger-B-W5Fa_X.js.map → CodemationPluginListMerger-D1B1IEbt.js.map} +1 -1
- package/dist/{CodemationPluginListMerger-DGc-jfa2.d.ts → CodemationPluginListMerger-DKLAHT2b.d.ts} +123 -16
- package/dist/CodemationTsyringeTypeInfoRegistrar-Bj6FJYFz.js +97 -0
- package/dist/CodemationTsyringeTypeInfoRegistrar-Bj6FJYFz.js.map +1 -0
- package/dist/{CodemationWhitelabelConfig-CWbcyQqn.d.ts → CodemationWhitelabelConfig-Ca2mCUeC.d.ts} +2 -2
- package/dist/{CollectionContracts.types-DdpHft0i.d.ts → CollectionContracts.types-DDyFYT_D.d.ts} +1 -1
- package/dist/{CredentialContractsRegistry-DrMIDSw8.d.ts → CredentialContractsRegistry-Bq2bq28t.d.ts} +2 -2
- package/dist/{CredentialServices-UfvHB-rN.d.ts → CredentialServices-Be2I60Th.d.ts} +65 -20
- package/dist/{CredentialServices-CgxwguAv.js → CredentialServices-Dk8yypeL.js} +310 -51
- package/dist/CredentialServices-Dk8yypeL.js.map +1 -0
- package/dist/InternalHonoApiRouteRegistrar-Ce1yxpnO.d.ts +17 -0
- package/dist/InternalPingRegistrar-DY3kSfxP.js +221 -0
- package/dist/InternalPingRegistrar-DY3kSfxP.js.map +1 -0
- package/dist/{ItemsInputNormalizer-C-KHg9Mo.d.ts → ItemsInputNormalizer-_RwIfRIQ.d.ts} +89 -25
- package/dist/{LogLevelPolicyFactory-CampWO0l.d.ts → LogLevelPolicyFactory-ewCHLDLn.d.ts} +2 -2
- package/dist/{PublicFrontendBootstrap-DzBgwOnG.d.ts → PublicFrontendBootstrap-Cev3qK46.d.ts} +9 -2
- package/dist/PublicFrontendBootstrapFactory-CY2FS-5g.d.ts +82 -0
- package/dist/{PublicFrontendBootstrapJsonCodec-Cl_DLRh0.d.ts → PublicFrontendBootstrapJsonCodec-CXG9Dxft.d.ts} +3 -3
- package/dist/{PublicFrontendBootstrapJsonCodec-DzqvA0uo.js → PublicFrontendBootstrapJsonCodec-CegIF_ne.js} +7 -2
- package/dist/PublicFrontendBootstrapJsonCodec-CegIF_ne.js.map +1 -0
- package/dist/ServerLoggerFactory-Ckk52S3w.js +223 -0
- package/dist/ServerLoggerFactory-Ckk52S3w.js.map +1 -0
- package/dist/{TelemetryContracts-DbaNomrH.d.ts → TelemetryContracts-BtDx84Cp.d.ts} +13 -4
- package/dist/{WorkflowPolicyUiPresentationFactory-DQEY-h_S.d.ts → WorkflowPolicyUiPresentationFactory-6MyjCvBO.d.ts} +2 -2
- package/dist/{WorkflowPolicyUiPresentationFactory-DhPqQ9aB.js → WorkflowPolicyUiPresentationFactory-Bb-ae_Zh.js} +1 -1
- package/dist/{WorkflowPolicyUiPresentationFactory-DhPqQ9aB.js.map → WorkflowPolicyUiPresentationFactory-Bb-ae_Zh.js.map} +1 -1
- package/dist/{WorkflowViewContracts-CzK2KFuz.d.ts → WorkflowViewContracts-B7aFQcIw.d.ts} +10 -1
- package/dist/authoring.d.ts +5 -5
- package/dist/authoring.js +1 -1
- package/dist/client.d.ts +4 -4
- package/dist/client.js +2 -2
- package/dist/consumer.d.ts +6 -6
- package/dist/consumer.js +2 -2
- package/dist/credentials.d.ts +6 -6
- package/dist/credentials.js +1 -1
- package/dist/devServerSidecar.d.ts +2 -2
- package/dist/devServerSidecar.js +1 -94
- package/dist/devServerSidecar.js.map +1 -1
- package/dist/dto.d.ts +6 -6
- package/dist/{index-BbBk26m0.d.ts → index-DilAYwnH.d.ts} +49 -3
- package/dist/index.d.ts +110 -21
- package/dist/index.js +15 -13
- package/dist/mapping.d.ts +2 -2
- package/dist/mapping.js +1 -1
- package/dist/nextServer.d.ts +43 -88
- package/dist/nextServer.js +9 -7
- package/dist/pairing.d.ts +93 -0
- package/dist/pairing.js +5 -0
- package/dist/pairing.types-snfZ_OzB.d.ts +19 -0
- package/dist/{persistenceServer-CmsIKnO9.js → persistenceServer-C-hH4z6l.js} +2 -2
- package/dist/{persistenceServer-CmsIKnO9.js.map → persistenceServer-C-hH4z6l.js.map} +1 -1
- package/dist/persistenceServer-CeTHtC6E.d.ts +30 -0
- package/dist/persistenceServer.d.ts +8 -8
- package/dist/persistenceServer.js +3 -3
- package/dist/{server-MUNGsBYK.d.ts → server-C4bS62rg.d.ts} +21 -6
- package/dist/{server-CJFfY67o.js → server-Y7kxwtCK.js} +7 -6
- package/dist/{server-CJFfY67o.js.map → server-Y7kxwtCK.js.map} +1 -1
- package/dist/server.d.ts +14 -14
- package/dist/server.js +13 -11
- package/package.json +29 -42
- package/prisma/migrations/20260519000000_workflow_audit_log/migration.sql +23 -0
- package/prisma/migrations/20260519100000_storage_growth_fixes/migration.sql +61 -0
- package/prisma/migrations.sqlite/20260519000000_workflow_audit_log/migration.sql +21 -0
- package/prisma/migrations.sqlite/20260519100000_storage_growth_fixes/migration.sql +29 -0
- package/prisma/schema.postgresql.prisma +55 -17
- package/prisma/schema.sqlite.prisma +55 -17
- package/prisma-generated/prisma-postgresql-client/edge.js +33 -5
- package/prisma-generated/prisma-postgresql-client/index-browser.js +29 -1
- package/prisma-generated/prisma-postgresql-client/index.d.ts +8933 -5716
- package/prisma-generated/prisma-postgresql-client/index.js +33 -5
- package/prisma-generated/prisma-postgresql-client/package.json +1 -1
- package/prisma-generated/prisma-postgresql-client/schema.prisma +38 -0
- package/prisma-generated/prisma-sqlite-client/edge.js +33 -5
- package/prisma-generated/prisma-sqlite-client/index-browser.js +29 -1
- package/prisma-generated/prisma-sqlite-client/index.d.ts +8925 -5713
- package/prisma-generated/prisma-sqlite-client/index.js +33 -5
- package/prisma-generated/prisma-sqlite-client/package.json +1 -1
- package/prisma-generated/prisma-sqlite-client/schema.prisma +38 -0
- package/scripts/check-collections.mjs +18 -0
- package/scripts/generate-prisma-clients.mjs +20 -11
- package/src/application/WorkflowAuditLogPruneScheduler.ts +96 -0
- package/src/application/auth/AuthenticatedPrincipal.ts +4 -0
- package/src/application/commands/StartWorkflowRunCommandHandler.ts +4 -0
- package/src/application/contracts/WorkflowViewContracts.ts +6 -0
- package/src/application/contracts/WorkflowWebsocketMessage.ts +3 -1
- package/src/application/mapping/WorkflowDefinitionMapper.ts +40 -1
- package/src/application/runs/WorkflowRunRetentionPruneScheduler.ts +7 -1
- package/src/application/telemetry/OtelExecutionTelemetry.types.ts +5 -0
- package/src/application/telemetry/OtelExecutionTelemetryFactory.ts +4 -0
- package/src/application/telemetry/StoredTelemetrySpanScope.ts +6 -2
- package/src/application/telemetry/TelemetryRetentionTimestampFactory.ts +27 -17
- package/src/application/telemetry/TelemetrySpanPublisher.ts +11 -0
- package/src/application/websocket/TelemetrySpanWebsocketRelay.ts +31 -0
- package/src/applicationTokens.ts +20 -1
- package/src/audit/IAuditEmitter.ts +32 -0
- package/src/audit/PrismaWorkflowAuditLogRepository.ts +34 -0
- package/src/audit/WorkflowAuditLogWriter.ts +125 -0
- package/src/auth/managed/ManagedAuthConfig.ts +29 -0
- package/src/auth/managed/ManagedAuthMiddleware.ts +52 -0
- package/src/auth/managed/ManagedCorsMiddleware.ts +43 -0
- package/src/auth/managed/ManagedModeBootGuard.ts +27 -0
- package/src/auth/managed/index.ts +5 -0
- package/src/bootstrap/AppContainerFactory.ts +277 -29
- package/src/bootstrap/AppContainerLifecycle.ts +31 -0
- package/src/bootstrap/perf/BootTimer.ts +168 -0
- package/src/bootstrap/runtime/AppConfigFactory.ts +21 -65
- package/src/bootstrap/runtime/FrontendRuntime.ts +4 -1
- package/src/bootstrap/runtime/WorkerRuntime.ts +2 -1
- package/src/credentials/BrokerClient.ts +49 -0
- package/src/credentials/BrokerRefreshError.ts +12 -0
- package/src/credentials/BrokerRefreshInvalidGrantError.ts +13 -0
- package/src/credentials/ControlPlaneCatalogFetcher.ts +261 -0
- package/src/credentials/CredentialOAuth2MaterialReader.ts +136 -0
- package/src/credentials/InternalCredentialsListRegistrar.ts +48 -0
- package/src/credentials/InternalCredentialsPushRegistrar.ts +125 -0
- package/src/credentials/LocalOAuthFlowExecutor.ts +316 -0
- package/src/credentials/ManagedOAuthFlowExecutor.ts +94 -0
- package/src/credentials/ManagedOAuthRefreshInvalidGrantError.ts +13 -0
- package/src/credentials/catalogTypes.ts +4 -0
- package/src/credentials/refresh/CredentialDisconnectedError.ts +11 -0
- package/src/domain/credentials/CredentialBindingService.ts +54 -2
- package/src/domain/credentials/CredentialKeyRotatedError.ts +22 -0
- package/src/domain/credentials/CredentialSecretCipher.ts +68 -6
- package/src/domain/credentials/CredentialTypeRegistryImpl.ts +117 -10
- package/src/domain/credentials/OAuth2RedirectUriResolver.ts +79 -0
- package/src/domain/credentials/WorkflowCredentialNodeResolver.ts +14 -5
- package/src/domain/telemetry/TelemetryContracts.ts +7 -1
- package/src/domain/workflows/WorkflowActivationPreflight.ts +24 -1
- package/src/domain/workflows/WorkflowActivationPreflightRules.ts +40 -1
- package/src/index.ts +6 -0
- package/src/infrastructure/binary/LocalFilesystemBinaryStorageRegistry.ts +29 -1
- package/src/infrastructure/binary/S3BinaryStorage.ts +169 -0
- package/src/infrastructure/binary/S3BinaryStorageConfig.ts +17 -0
- package/src/infrastructure/config/CodemationPluginRegistrar.ts +3 -1
- package/src/infrastructure/persistence/CodemationDatabaseUrlParser.ts +41 -0
- package/src/infrastructure/persistence/InMemoryTelemetryArtifactStore.ts +8 -3
- package/src/infrastructure/persistence/PrismaMigrationDeployer.ts +21 -13
- package/src/infrastructure/persistence/PrismaTelemetryArtifactStore.ts +43 -8
- package/src/infrastructure/persistence/PrismaWorkflowRunRepository.ts +26 -3
- package/src/infrastructure/persistence/PrismaWorkflowSnapshotRepository.ts +48 -0
- package/src/mcp/AgentMcpIntegrationImpl.ts +344 -0
- package/src/mcp/McpClientFactory.ts +29 -0
- package/src/mcp/McpConnectionPool.ts +184 -0
- package/src/mcp/McpConnectionPool.types.ts +12 -0
- package/src/mcp/McpServerCatalog.ts +104 -0
- package/src/mcp/index.ts +5 -0
- package/src/pairing/HmacRequestSigner.ts +32 -0
- package/src/pairing/IncomingHmacVerifier.ts +82 -0
- package/src/pairing/InternalHmacAuthMiddleware.ts +33 -0
- package/src/pairing/InternalPingRegistrar.ts +25 -0
- package/src/pairing/PairedFetch.ts +33 -0
- package/src/pairing/PairingConfigFactory.ts +35 -0
- package/src/pairing/PairingConfigToken.ts +6 -0
- package/src/pairing/index.ts +14 -0
- package/src/pairing/pairing.types.ts +18 -0
- package/src/pairing.ts +17 -0
- package/src/persistenceServer.ts +1 -0
- package/src/presentation/config/AppConfig.ts +7 -1
- package/src/presentation/config/CodemationAuthConfig.ts +1 -1
- package/src/presentation/config/CodemationAuthoring.types.ts +54 -5
- package/src/presentation/config/CodemationConfig.ts +3 -0
- package/src/presentation/config/CodemationConfigNormalizer.ts +39 -1
- package/src/presentation/config/CodemationPlugin.ts +2 -1
- package/src/presentation/frontend/CodemationFrontendAuthSnapshot.ts +5 -0
- package/src/presentation/frontend/CodemationFrontendAuthSnapshotFactory.ts +7 -1
- package/src/presentation/frontend/PublicFrontendBootstrap.ts +2 -0
- package/src/presentation/frontend/PublicFrontendBootstrapFactory.ts +5 -1
- package/src/presentation/frontend/PublicFrontendBootstrapJsonCodec.ts +4 -1
- package/src/presentation/http/ApiPaths.ts +4 -4
- package/src/presentation/http/ServerHttpErrorResponseFactory.ts +39 -2
- package/src/presentation/http/hono/CodemationHonoApiAppFactory.ts +33 -8
- package/src/presentation/http/hono/InternalHonoApiRouteRegistrar.ts +12 -0
- package/src/presentation/http/hono/registrars/ManagedMeHonoApiRouteRegistrar.ts +35 -0
- package/src/presentation/http/hono/registrars/OAuth2HonoApiRouteRegistrar.ts +2 -2
- package/src/presentation/http/routeHandlers/CredentialHttpRouteHandler.ts +28 -0
- package/src/presentation/http/routeHandlers/OAuth2HttpRouteHandlerFactory.ts +98 -41
- package/src/presentation/server/CodemationConsumerConfigLoader.ts +54 -7
- package/src/presentation/server/CodemationPluginDiscovery.ts +5 -0
- package/src/presentation/server/WorkflowDefinitionExportsResolver.ts +18 -0
- package/src/presentation/server/WorkflowModulePathFinder.ts +12 -1
- package/src/presentation/websocket/ManagedWebsocketAuthenticator.ts +50 -0
- package/src/presentation/websocket/WebsocketAuthenticator.types.ts +12 -0
- package/src/presentation/websocket/WorkflowWebsocketServer.ts +24 -3
- package/src/process/ExecaProcessRunner.ts +41 -0
- package/src/process/ProcessRunner.types.ts +39 -0
- package/src/server.ts +2 -0
- package/src/workflows/InternalWorkflowActivationRegistrar.ts +42 -0
- package/src/workflows/InternalWorkflowDetailRegistrar.ts +33 -0
- package/src/workflows/InternalWorkflowTestRunRegistrar.ts +91 -0
- package/src/workflows/InternalWorkflowsListRegistrar.ts +28 -0
- package/src/workflows/discovery/WorkflowDirectoryDiscoverer.ts +79 -0
- package/tsconfig.json +2 -0
- package/vitest.shared.ts +5 -0
- package/dist/ApiPaths-CLTHphYZ.js.map +0 -1
- package/dist/AppConfigFactory-C6q-CSKb.js.map +0 -1
- package/dist/AppContainerFactory-qaqc-R1D.js.map +0 -1
- package/dist/CodemationAuthoring.types-DZl-sJaM.js.map +0 -1
- package/dist/CodemationConsumerConfigLoader-BeAUS144.js.map +0 -1
- package/dist/CredentialServices-CgxwguAv.js.map +0 -1
- package/dist/PublicFrontendBootstrapFactory-Cb2pLmDd.d.ts +0 -45
- package/dist/PublicFrontendBootstrapJsonCodec-DzqvA0uo.js.map +0 -1
- package/dist/ServerLoggerFactory-BKSIh9Xv.js +0 -98
- package/dist/ServerLoggerFactory-BKSIh9Xv.js.map +0 -1
- package/dist/persistenceServer-vtJAGDat.d.ts +0 -9
- package/src/domain/credentials/OAuth2ConnectServiceFactory.ts +0 -411
|
@@ -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
|
|
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
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
7
|
-
|
|
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
|
|
11
|
-
return this.createExpiry(
|
|
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
|
|
15
|
-
return this.createExpiry(
|
|
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
|
|
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
|
|
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
|
+
}
|
package/src/applicationTokens.ts
CHANGED
|
@@ -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;
|