@codemation/host 0.7.0 → 0.9.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 +89 -0
- package/LICENSE +37 -1
- package/dist/{ApiPaths-Dv1dcHu_.js → ApiPaths-DCvrlIjg.js} +12 -1
- package/dist/{ApiPaths-Dv1dcHu_.js.map → ApiPaths-DCvrlIjg.js.map} +1 -1
- package/dist/{AppConfigFactory-Cx4qQvRk.js → AppConfigFactory-D4LL1aOR.js} +77 -297
- package/dist/AppConfigFactory-D4LL1aOR.js.map +1 -0
- package/dist/{AppConfigFactory-DnLoQ9Li.d.ts → AppConfigFactory-DncmwCD1.d.ts} +2918 -199
- package/dist/{AppContainerFactory-DqKYCRNP.js → AppContainerFactory-jpYXGZGe.js} +1733 -475
- package/dist/AppContainerFactory-jpYXGZGe.js.map +1 -0
- package/dist/{CodemationAppContext-CKVv9W9q.d.ts → CodemationAppContext-K51b7oXe.d.ts} +9 -3
- package/dist/{CodemationAuthoring.types-DA3G3s6d.d.ts → CodemationAuthoring.types-BXlXIl4K.d.ts} +9 -4
- package/dist/{CodemationAuthoring.types-NGkBcmmT.js → CodemationAuthoring.types-BteaR3Dc.js} +3 -2
- package/dist/CodemationAuthoring.types-BteaR3Dc.js.map +1 -0
- package/dist/{CodemationConfigNormalizer-BAKjetJ6.d.ts → CodemationConfigNormalizer-B4rDYC9h.d.ts} +3 -3
- package/dist/{CodemationConsumerConfigLoader-GYpBBvqE.js → CodemationConsumerConfigLoader-By-6tuGc.js} +3 -1
- package/dist/CodemationConsumerConfigLoader-By-6tuGc.js.map +1 -0
- package/dist/{CodemationConsumerConfigLoader-nxOqvv46.d.ts → CodemationConsumerConfigLoader-Dt4jyLx6.d.ts} +3 -2
- package/dist/{CodemationPluginListMerger-DKLAHT2b.d.ts → CodemationPluginListMerger-DS6I3Xe0.d.ts} +64 -27
- package/dist/{persistenceServer-C-hH4z6l.js → CodemationPostgresPrismaClientFactory-C7156Fe-.js} +2 -2
- package/dist/CodemationPostgresPrismaClientFactory-C7156Fe-.js.map +1 -0
- package/dist/CodemationPostgresPrismaClientFactory-CTNTPnDr.d.ts +9 -0
- package/dist/{CredentialContractsRegistry-Bq2bq28t.d.ts → CredentialContractsRegistry-Dgu-rEXi.d.ts} +16 -3
- package/dist/{CredentialServices-Be2I60Th.d.ts → CredentialServices-B3wPyp2y.d.ts} +4 -4
- package/dist/{CredentialServices-Dk8yypeL.js → CredentialServices-Bios0dM8.js} +10 -4
- package/dist/CredentialServices-Bios0dM8.js.map +1 -0
- package/dist/{InternalPingRegistrar-DY3kSfxP.js → InternalPingRegistrar-BavAAnvk.js} +19 -16
- package/dist/InternalPingRegistrar-BavAAnvk.js.map +1 -0
- package/dist/{ItemsInputNormalizer-_RwIfRIQ.d.ts → ItemsInputNormalizer-CFkfNMLt.d.ts} +1434 -1225
- package/dist/PrismaMigrationDeployer-DdEcXXVi.d.ts +14 -0
- package/dist/{PublicFrontendBootstrapFactory-CY2FS-5g.d.ts → PublicFrontendBootstrapFactory-ClEjZP74.d.ts} +2 -2
- package/dist/{PublicFrontendBootstrapJsonCodec-CXG9Dxft.d.ts → PublicFrontendBootstrapJsonCodec-HNItQ7ol.d.ts} +6 -1
- package/dist/{TelemetryContracts-BtDx84Cp.d.ts → TelemetryContracts-DpZEODQM.d.ts} +2 -2
- package/dist/{WorkflowPolicyUiPresentationFactory-6MyjCvBO.d.ts → WorkflowPolicyUiPresentationFactory-BNn2fvR_.d.ts} +2 -2
- package/dist/{WorkflowPolicyUiPresentationFactory-Bb-ae_Zh.js → WorkflowPolicyUiPresentationFactory-DfvD2VHk.js} +1 -1
- package/dist/{WorkflowPolicyUiPresentationFactory-Bb-ae_Zh.js.map → WorkflowPolicyUiPresentationFactory-DfvD2VHk.js.map} +1 -1
- package/dist/authoring.d.ts +4 -4
- package/dist/authoring.js +1 -1
- package/dist/client.d.ts +1 -1
- package/dist/client.js +1 -1
- package/dist/consumer.d.ts +5 -5
- package/dist/consumer.js +1 -1
- package/dist/credentials.d.ts +5 -5
- package/dist/credentials.js +1 -1
- package/dist/devServerSidecar.d.ts +2 -2
- package/dist/dto.d.ts +5 -5
- package/dist/{index-DilAYwnH.d.ts → index-ChIfeWzk.d.ts} +71 -28
- package/dist/index.d.ts +49 -17
- package/dist/index.js +106 -13
- package/dist/index.js.map +1 -0
- package/dist/infrastructure/persistence/PrismaMigrationOperations.d.ts +44 -0
- package/dist/infrastructure/persistence/PrismaMigrationOperations.js +302 -0
- package/dist/infrastructure/persistence/PrismaMigrationOperations.js.map +1 -0
- package/dist/mapping.d.ts +2 -2
- package/dist/mapping.js +1 -1
- package/dist/nextServer.d.ts +15 -39
- package/dist/nextServer.js +6 -6
- package/dist/pairing.d.ts +27 -8
- package/dist/pairing.js +19 -3
- package/dist/pairing.js.map +1 -0
- package/dist/{pairing.types-snfZ_OzB.d.ts → pairing.types-D9Bjn98U.d.ts} +1 -1
- package/dist/persistenceServer.d.ts +31 -7
- package/dist/persistenceServer.js +2 -2
- package/dist/{server-C4bS62rg.d.ts → server-B5trn7y4.d.ts} +5 -5
- package/dist/{server-Y7kxwtCK.js → server-BlG9qV5S.js} +5 -5
- package/dist/{server-Y7kxwtCK.js.map → server-BlG9qV5S.js.map} +1 -1
- package/dist/server.d.ts +10 -10
- package/dist/server.js +9 -9
- package/package.json +28 -25
- package/playwright.config.ts +8 -2
- package/playwright.scaffolded-dev.config.ts +8 -2
- package/prisma/migrations/20260526120000_credential_material_pointer/migration.sql +18 -0
- package/prisma/migrations/20260527120000_add_human_task/migration.sql +32 -0
- package/prisma/migrations/20260527130000_add_hitl_state_json/migration.sql +6 -0
- package/prisma/migrations/20260527130000_add_hmac_nonce/migration.sql +12 -0
- package/prisma/migrations.sqlite/20260526120000_credential_material_pointer/migration.sql +13 -0
- package/prisma/migrations.sqlite/20260527120000_add_human_task/migration.sql +30 -0
- package/prisma/migrations.sqlite/20260527130000_add_hitl_state_json/migration.sql +6 -0
- package/prisma/migrations.sqlite/20260527130000_add_hmac_nonce/migration.sql +9 -0
- package/prisma/schema.postgresql.prisma +48 -0
- package/prisma/schema.sqlite.prisma +48 -0
- package/prisma-generated/prisma-postgresql-client/edge.js +40 -6
- package/prisma-generated/prisma-postgresql-client/index-browser.js +36 -2
- package/prisma-generated/prisma-postgresql-client/index.d.ts +3179 -163
- package/prisma-generated/prisma-postgresql-client/index.js +40 -6
- package/prisma-generated/prisma-postgresql-client/package.json +1 -1
- package/prisma-generated/prisma-postgresql-client/schema.prisma +48 -0
- package/prisma-generated/prisma-sqlite-client/edge.js +40 -6
- package/prisma-generated/prisma-sqlite-client/index-browser.js +36 -2
- package/prisma-generated/prisma-sqlite-client/index.d.ts +3175 -163
- package/prisma-generated/prisma-sqlite-client/index.js +40 -6
- package/prisma-generated/prisma-sqlite-client/package.json +1 -1
- package/prisma-generated/prisma-sqlite-client/schema.prisma +48 -0
- package/src/application/contracts/CredentialContractsRegistry.ts +15 -0
- package/src/application/credentials/AppGalleryProjector.ts +69 -0
- package/src/application/hitl/DecideHumanTaskCommandHandler.ts +149 -0
- package/src/application/hitl/DecisionSchemaValidator.ts +22 -0
- package/src/application/hitl/HitlCallbackHandler.ts +96 -0
- package/src/application/mapping/WorkflowDefinitionMapper.ts +1 -3
- package/src/application/queries/CredentialQueryHandlers.ts +2 -0
- package/src/application/queries/GetCredentialAppsQuery.ts +4 -0
- package/src/application/queries/GetCredentialAppsQueryHandler.ts +27 -0
- package/src/application/telemetry/ResumeTelemetryContextForRun.ts +53 -0
- package/src/application/telemetry/TelemetryRetentionTimestampFactory.ts +9 -8
- package/src/applicationTokens.ts +11 -1
- package/src/auth/managed/ManagedCorsMiddleware.ts +20 -5
- package/src/bootstrap/AppContainerFactory.ts +121 -3
- package/src/bootstrap/runtime/HeadlessApiRuntime.ts +47 -0
- package/src/credentials/CachingCredentialMaterialProvider.ts +96 -0
- package/src/credentials/CompositeCredentialMaterialProvider.ts +47 -0
- package/src/credentials/ControlPlaneCatalogFetcher.ts +8 -28
- package/src/credentials/ControlPlaneCredentialMaterialProvider.ts +79 -0
- package/src/credentials/CredentialOAuth2MaterialReader.ts +2 -7
- package/src/credentials/InternalCredentialsBindingRegistrar.ts +83 -0
- package/src/credentials/LocalCredentialMaterialProvider.ts +92 -0
- package/src/domain/credentials/CredentialInstanceService.ts +5 -1
- package/src/domain/credentials/CredentialTypeRegistryImpl.ts +18 -4
- package/src/domain/workflows/WorkflowActivationPreflightRules.ts +7 -4
- package/src/dto.ts +2 -0
- package/src/hitl/ControlPlaneInboxChannel.ts +102 -0
- package/src/hitl/HitlResumeTokenSigner.ts +80 -0
- package/src/hitl/HitlTimeoutJobScheduler.ts +77 -0
- package/src/hitl/HitlTimeoutWorker.ts +138 -0
- package/src/hitl/InboxChannelResolver.ts +49 -0
- package/src/hitl/LocalInboxChannel.ts +37 -0
- package/src/index.ts +3 -0
- package/src/infrastructure/persistence/PrismaCredentialStore.ts +10 -0
- package/src/infrastructure/persistence/PrismaHmacNonceStore.ts +29 -0
- package/src/infrastructure/persistence/PrismaHumanTaskStore.ts +156 -0
- package/src/infrastructure/persistence/PrismaMigrationDeployer.ts +53 -383
- package/src/infrastructure/persistence/PrismaMigrationOperations.ts +401 -0
- package/src/infrastructure/persistence/PrismaWorkflowRunRepository.ts +39 -0
- package/src/mcp/AgentMcpIntegrationImpl.ts +5 -1
- package/src/pairing/HmacNonceStore.ts +14 -0
- package/src/pairing/HmacNonceStoreToken.ts +4 -0
- package/src/pairing/HmacRequestSigner.ts +10 -1
- package/src/pairing/InMemoryHmacNonceStore.ts +24 -0
- package/src/pairing/IncomingHmacVerifier.ts +28 -12
- package/src/pairing/InternalHmacAuthMiddleware.ts +1 -1
- package/src/pairing/index.ts +3 -0
- package/src/presentation/config/CodemationAuthoring.types.ts +7 -1
- package/src/presentation/config/CodemationConfig.ts +6 -0
- package/src/presentation/http/ApiPaths.ts +14 -0
- package/src/presentation/http/HeadlessHttpServerFactory.ts +56 -0
- package/src/presentation/http/hono/HonoHttpAnonymousRoutePolicyRegistry.ts +4 -0
- package/src/presentation/http/hono/registrars/CredentialHonoApiRouteRegistrar.ts +1 -0
- package/src/presentation/http/hono/registrars/HitlDecideHonoApiRouteRegistrar.ts +54 -0
- package/src/presentation/http/hono/registrars/HitlInternalCallbackHonoApiRouteRegistrar.ts +33 -0
- package/src/presentation/http/hono/registrars/HitlResumeHonoApiRouteRegistrar.ts +43 -0
- package/src/presentation/http/routeHandlers/CredentialHttpRouteHandler.ts +9 -0
- package/src/presentation/http/routeHandlers/OAuth2HttpRouteHandlerFactory.ts +1 -1
- package/src/presentation/server/CodemationConsumerConfigLoader.ts +7 -2
- package/src/presentation/websocket/WorkflowWebsocketServerFactory.ts +16 -0
- package/src/server.ts +7 -2
- package/src/workflows/InternalWorkflowTestRunRegistrar.ts +9 -0
- package/tsconfig.json +1 -0
- package/dist/AppConfigFactory-Cx4qQvRk.js.map +0 -1
- package/dist/AppContainerFactory-DqKYCRNP.js.map +0 -1
- package/dist/CodemationAuthoring.types-NGkBcmmT.js.map +0 -1
- package/dist/CodemationConsumerConfigLoader-GYpBBvqE.js.map +0 -1
- package/dist/CredentialServices-Dk8yypeL.js.map +0 -1
- package/dist/InternalPingRegistrar-DY3kSfxP.js.map +0 -1
- package/dist/persistenceServer-C-hH4z6l.js.map +0 -1
- package/dist/persistenceServer-CeTHtC6E.d.ts +0 -30
- package/src/credentials/catalogTypes.ts +0 -4
|
@@ -57,6 +57,7 @@ import {
|
|
|
57
57
|
UploadOverlayPinnedBinaryCommandHandler,
|
|
58
58
|
} from "../application/commands/WorkflowCommandHandlers";
|
|
59
59
|
import {
|
|
60
|
+
GetCredentialAppsQueryHandler,
|
|
60
61
|
GetCredentialFieldEnvStatusQueryHandler,
|
|
61
62
|
GetCredentialInstanceQueryHandler,
|
|
62
63
|
GetCredentialInstanceWithSecretsQueryHandler,
|
|
@@ -64,6 +65,7 @@ import {
|
|
|
64
65
|
ListCredentialInstancesQueryHandler,
|
|
65
66
|
ListCredentialTypesQueryHandler,
|
|
66
67
|
} from "../application/queries/CredentialQueryHandlers";
|
|
68
|
+
import { AppGalleryProjector } from "../application/credentials/AppGalleryProjector";
|
|
67
69
|
import {
|
|
68
70
|
ListUserAccountsQueryHandler,
|
|
69
71
|
VerifyUserInviteQueryHandler,
|
|
@@ -278,11 +280,18 @@ import { HmacRequestSigner } from "../pairing/HmacRequestSigner";
|
|
|
278
280
|
import { PairedFetch } from "../pairing/PairedFetch";
|
|
279
281
|
import { IncomingHmacVerifier } from "../pairing/IncomingHmacVerifier";
|
|
280
282
|
import { InternalHmacAuthMiddleware } from "../pairing/InternalHmacAuthMiddleware";
|
|
283
|
+
import { HmacNonceStoreToken } from "../pairing/HmacNonceStoreToken";
|
|
284
|
+
import { PrismaHmacNonceStore } from "../infrastructure/persistence/PrismaHmacNonceStore";
|
|
281
285
|
import { InternalPingRegistrar } from "../pairing/InternalPingRegistrar";
|
|
282
286
|
import { LocalOAuthFlowExecutor } from "../credentials/LocalOAuthFlowExecutor";
|
|
287
|
+
import { LocalCredentialMaterialProvider } from "../credentials/LocalCredentialMaterialProvider";
|
|
288
|
+
import { CachingCredentialMaterialProvider } from "../credentials/CachingCredentialMaterialProvider";
|
|
289
|
+
import { ControlPlaneCredentialMaterialProvider } from "../credentials/ControlPlaneCredentialMaterialProvider";
|
|
290
|
+
import { CompositeCredentialMaterialProvider } from "../credentials/CompositeCredentialMaterialProvider";
|
|
283
291
|
import { CredentialOAuth2MaterialReader } from "../credentials/CredentialOAuth2MaterialReader";
|
|
284
292
|
import { ManagedOAuthFlowExecutor } from "../credentials/ManagedOAuthFlowExecutor";
|
|
285
293
|
import { BrokerClient } from "../credentials/BrokerClient";
|
|
294
|
+
import { InternalCredentialsBindingRegistrar } from "../credentials/InternalCredentialsBindingRegistrar";
|
|
286
295
|
import { InternalCredentialsPushRegistrar } from "../credentials/InternalCredentialsPushRegistrar";
|
|
287
296
|
import { InternalCredentialsListRegistrar } from "../credentials/InternalCredentialsListRegistrar";
|
|
288
297
|
import { InternalWorkflowsListRegistrar } from "../workflows/InternalWorkflowsListRegistrar";
|
|
@@ -301,6 +310,23 @@ import { ManagedWebsocketAuthenticator } from "../presentation/websocket/Managed
|
|
|
301
310
|
import { JwksCache, ManagedJwtVerifier } from "@codemation/managed-auth";
|
|
302
311
|
import { CodemationTsyringeTypeInfoRegistrar } from "../presentation/server/CodemationTsyringeTypeInfoRegistrar";
|
|
303
312
|
import { ControlPlaneCatalogFetcher } from "../credentials/ControlPlaneCatalogFetcher";
|
|
313
|
+
import { PrismaHumanTaskStore } from "../infrastructure/persistence/PrismaHumanTaskStore";
|
|
314
|
+
import { HitlResumeTokenSigner } from "../hitl/HitlResumeTokenSigner";
|
|
315
|
+
import { HitlTimeoutJobScheduler } from "../hitl/HitlTimeoutJobScheduler";
|
|
316
|
+
import { HitlTimeoutWorker } from "../hitl/HitlTimeoutWorker";
|
|
317
|
+
import { DecideHumanTaskCommandHandler } from "../application/hitl/DecideHumanTaskCommandHandler";
|
|
318
|
+
import { DecisionSchemaValidator } from "../application/hitl/DecisionSchemaValidator";
|
|
319
|
+
import { HitlDecideHonoApiRouteRegistrar } from "../presentation/http/hono/registrars/HitlDecideHonoApiRouteRegistrar";
|
|
320
|
+
import { HitlResumeHonoApiRouteRegistrar } from "../presentation/http/hono/registrars/HitlResumeHonoApiRouteRegistrar";
|
|
321
|
+
import { HumanTaskStoreToken } from "@codemation/core";
|
|
322
|
+
import { HitlResumeTokenSignerToken, HitlTimeoutJobSchedulerToken, HitlWorkspaceIdToken } from "@codemation/core";
|
|
323
|
+
import { ControlPlaneInboxChannelToken, InboxChannelResolverToken, LocalInboxChannelToken } from "@codemation/core";
|
|
324
|
+
import { InboxChannelResolver } from "../hitl/InboxChannelResolver";
|
|
325
|
+
import { LocalInboxChannel } from "../hitl/LocalInboxChannel";
|
|
326
|
+
import { ControlPlaneInboxChannel } from "../hitl/ControlPlaneInboxChannel";
|
|
327
|
+
import { HitlCallbackHandler } from "../application/hitl/HitlCallbackHandler";
|
|
328
|
+
import { HitlInternalCallbackHonoApiRouteRegistrar } from "../presentation/http/hono/registrars/HitlInternalCallbackHonoApiRouteRegistrar";
|
|
329
|
+
import { ResumeTelemetryContextForRun } from "../application/telemetry/ResumeTelemetryContextForRun";
|
|
304
330
|
|
|
305
331
|
type AppContainerInputs = Readonly<{
|
|
306
332
|
appConfig: AppConfig;
|
|
@@ -313,6 +339,7 @@ type PrismaOwnership = Readonly<{
|
|
|
313
339
|
|
|
314
340
|
export class AppContainerFactory {
|
|
315
341
|
private static readonly queryHandlers = [
|
|
342
|
+
GetCredentialAppsQueryHandler,
|
|
316
343
|
GetCredentialFieldEnvStatusQueryHandler,
|
|
317
344
|
GetCredentialInstanceQueryHandler,
|
|
318
345
|
GetCredentialInstanceWithSecretsQueryHandler,
|
|
@@ -380,6 +407,8 @@ export class AppContainerFactory {
|
|
|
380
407
|
WhitelabelHonoApiRouteRegistrar,
|
|
381
408
|
WorkflowHonoApiRouteRegistrar,
|
|
382
409
|
CollectionHonoApiRouteRegistrar,
|
|
410
|
+
HitlDecideHonoApiRouteRegistrar,
|
|
411
|
+
HitlResumeHonoApiRouteRegistrar,
|
|
383
412
|
] as const;
|
|
384
413
|
|
|
385
414
|
constructor(
|
|
@@ -393,7 +422,9 @@ export class AppContainerFactory {
|
|
|
393
422
|
// Register the no-op publisher as a fallback so OtelExecutionTelemetryFactory can always
|
|
394
423
|
// resolve the token. registerOperationalInfrastructure overrides this with the WS relay.
|
|
395
424
|
container.registerInstance(ApplicationTokens.TelemetrySpanPublisher, NoOpTelemetrySpanPublisher);
|
|
396
|
-
BootTimer.measure("appContainer.registerCoreInfrastructure", () =>
|
|
425
|
+
BootTimer.measure("appContainer.registerCoreInfrastructure", () =>
|
|
426
|
+
this.registerCoreInfrastructure(container, inputs),
|
|
427
|
+
);
|
|
397
428
|
BootTimer.measure("appContainer.registerRepositoriesAndBuses", () => this.registerRepositoriesAndBuses(container));
|
|
398
429
|
BootTimer.measure("appContainer.registerApplicationServicesAndRoutes", () =>
|
|
399
430
|
this.registerApplicationServicesAndRoutes(container, inputs.appConfig),
|
|
@@ -481,7 +512,7 @@ export class AppContainerFactory {
|
|
|
481
512
|
});
|
|
482
513
|
}
|
|
483
514
|
|
|
484
|
-
private mergeConfigMcpServers(container: Container, appConfig: AppConfig): void {
|
|
515
|
+
private mergeConfigMcpServers(container: Container, appConfig: AppConfig): void {
|
|
485
516
|
const catalog = container.resolve(McpServerCatalog);
|
|
486
517
|
catalog.merge("config", appConfig.mcpServers ?? []);
|
|
487
518
|
}
|
|
@@ -716,6 +747,7 @@ private mergeConfigMcpServers(container: Container, appConfig: AppConfig): void
|
|
|
716
747
|
container.registerSingleton(CredentialRuntimeMaterialService, CredentialRuntimeMaterialService);
|
|
717
748
|
container.registerSingleton(WorkflowCredentialNodeResolver, WorkflowCredentialNodeResolver);
|
|
718
749
|
container.registerSingleton(CredentialInstanceService, CredentialInstanceService);
|
|
750
|
+
container.registerSingleton(AppGalleryProjector, AppGalleryProjector);
|
|
719
751
|
container.registerSingleton(CredentialBindingService, CredentialBindingService);
|
|
720
752
|
container.registerSingleton(WorkflowActivationPreflightRules, WorkflowActivationPreflightRules);
|
|
721
753
|
container.registerSingleton(WorkflowActivationPreflight, WorkflowActivationPreflight);
|
|
@@ -738,6 +770,26 @@ private mergeConfigMcpServers(container: Container, appConfig: AppConfig): void
|
|
|
738
770
|
container.registerSingleton(LocalOAuthFlowExecutor, LocalOAuthFlowExecutor);
|
|
739
771
|
}
|
|
740
772
|
container.registerSingleton(CredentialOAuth2MaterialReader, CredentialOAuth2MaterialReader);
|
|
773
|
+
// Register the local material provider unconditionally. The dispatcher picks
|
|
774
|
+
// between this and the control-plane provider in managed mode.
|
|
775
|
+
container.registerSingleton(LocalCredentialMaterialProvider, LocalCredentialMaterialProvider);
|
|
776
|
+
// In managed mode (paired with a
|
|
777
|
+
// control plane) wrap the cache around a `CompositeCredentialMaterialProvider`
|
|
778
|
+
// that dispatches by `ref.source`. In standalone mode the cache wraps the
|
|
779
|
+
// local provider directly.
|
|
780
|
+
if (container.isRegistered(PairedFetch, true)) {
|
|
781
|
+
container.registerSingleton(ControlPlaneCredentialMaterialProvider, ControlPlaneCredentialMaterialProvider);
|
|
782
|
+
container.registerSingleton(CompositeCredentialMaterialProvider, CompositeCredentialMaterialProvider);
|
|
783
|
+
container.register(ApplicationTokens.CredentialMaterialInnerProvider, {
|
|
784
|
+
useFactory: instanceCachingFactory((c) => c.resolve(CompositeCredentialMaterialProvider)),
|
|
785
|
+
});
|
|
786
|
+
} else {
|
|
787
|
+
container.register(ApplicationTokens.CredentialMaterialInnerProvider, {
|
|
788
|
+
useFactory: instanceCachingFactory((c) => c.resolve(LocalCredentialMaterialProvider)),
|
|
789
|
+
});
|
|
790
|
+
}
|
|
791
|
+
// In-memory TTL cache decorator.
|
|
792
|
+
container.registerSingleton(CachingCredentialMaterialProvider, CachingCredentialMaterialProvider);
|
|
741
793
|
container.registerSingleton(CodemationFrontendAuthSnapshotFactory, CodemationFrontendAuthSnapshotFactory);
|
|
742
794
|
container.registerSingleton(FrontendAppConfigFactory, FrontendAppConfigFactory);
|
|
743
795
|
container.registerSingleton(PublicFrontendBootstrapFactory, PublicFrontendBootstrapFactory);
|
|
@@ -850,6 +902,7 @@ private mergeConfigMcpServers(container: Container, appConfig: AppConfig): void
|
|
|
850
902
|
),
|
|
851
903
|
});
|
|
852
904
|
container.registerSingleton(OtelExecutionTelemetryFactory, OtelExecutionTelemetryFactory);
|
|
905
|
+
container.registerSingleton(ResumeTelemetryContextForRun, ResumeTelemetryContextForRun);
|
|
853
906
|
container.registerSingleton(InMemoryRunTraceContextRepository, InMemoryRunTraceContextRepository);
|
|
854
907
|
container.registerSingleton(InMemoryTelemetrySpanStore, InMemoryTelemetrySpanStore);
|
|
855
908
|
container.registerSingleton(InMemoryTelemetryArtifactStore, InMemoryTelemetryArtifactStore);
|
|
@@ -872,6 +925,32 @@ private mergeConfigMcpServers(container: Container, appConfig: AppConfig): void
|
|
|
872
925
|
container.registerSingleton(PrismaWorkflowActivationRepository, PrismaWorkflowActivationRepository);
|
|
873
926
|
container.registerSingleton(InMemoryWorkflowActivationRepository, InMemoryWorkflowActivationRepository);
|
|
874
927
|
container.registerSingleton(PrismaCredentialStore, PrismaCredentialStore);
|
|
928
|
+
// HITL: token signer + timeout scheduler (persistence wired in registerRuntimeInfrastructure)
|
|
929
|
+
container.registerSingleton(PrismaHumanTaskStore, PrismaHumanTaskStore);
|
|
930
|
+
// Default: no HITL store; overridden to PrismaHumanTaskStore in the Prisma path below
|
|
931
|
+
container.register(HumanTaskStoreToken, { useFactory: () => undefined });
|
|
932
|
+
container.registerSingleton(HitlResumeTokenSigner, HitlResumeTokenSigner);
|
|
933
|
+
container.register(HitlResumeTokenSignerToken, {
|
|
934
|
+
useFactory: instanceCachingFactory((dc) => dc.resolve(HitlResumeTokenSigner)),
|
|
935
|
+
});
|
|
936
|
+
container.registerSingleton(HitlTimeoutJobScheduler, HitlTimeoutJobScheduler);
|
|
937
|
+
container.register(HitlTimeoutJobSchedulerToken, {
|
|
938
|
+
useFactory: instanceCachingFactory((dc) => dc.resolve(HitlTimeoutJobScheduler)),
|
|
939
|
+
});
|
|
940
|
+
container.registerSingleton(HitlTimeoutWorker, HitlTimeoutWorker);
|
|
941
|
+
container.registerSingleton(DecisionSchemaValidator, DecisionSchemaValidator);
|
|
942
|
+
container.registerSingleton(DecideHumanTaskCommandHandler, DecideHumanTaskCommandHandler);
|
|
943
|
+
// HITL: inbox channel resolver (concrete local + control-plane channels registered below)
|
|
944
|
+
container.registerSingleton(InboxChannelResolver, InboxChannelResolver);
|
|
945
|
+
container.register(InboxChannelResolverToken, {
|
|
946
|
+
useFactory: instanceCachingFactory((dc) => dc.resolve(InboxChannelResolver)),
|
|
947
|
+
});
|
|
948
|
+
// HITL: local inbox channel — registered unconditionally; the resolver picks it
|
|
949
|
+
// whenever managed-mode CP channel is not present.
|
|
950
|
+
container.registerSingleton(LocalInboxChannel, LocalInboxChannel);
|
|
951
|
+
container.register(LocalInboxChannelToken, {
|
|
952
|
+
useFactory: instanceCachingFactory((dc) => dc.resolve(LocalInboxChannel)),
|
|
953
|
+
});
|
|
875
954
|
container.register(ApplicationTokens.WorkflowDefinitionRepository, {
|
|
876
955
|
useFactory: instanceCachingFactory(
|
|
877
956
|
(dependencyContainer) =>
|
|
@@ -988,7 +1067,23 @@ private mergeConfigMcpServers(container: Container, appConfig: AppConfig): void
|
|
|
988
1067
|
}
|
|
989
1068
|
|
|
990
1069
|
private registerPairingInfrastructure(container: Container, appConfig: AppConfig): void {
|
|
991
|
-
const
|
|
1070
|
+
const isManagedMode = appConfig.auth?.kind === "managed";
|
|
1071
|
+
let pairingConfig;
|
|
1072
|
+
try {
|
|
1073
|
+
pairingConfig = new PairingConfigFactory().create(appConfig.env);
|
|
1074
|
+
} catch (err) {
|
|
1075
|
+
if (isManagedMode) {
|
|
1076
|
+
// In managed mode the secret is required — let the error surface.
|
|
1077
|
+
throw err;
|
|
1078
|
+
}
|
|
1079
|
+
// In non-managed mode an invalid-but-present WORKSPACE_PAIRING_SECRET is a misconfiguration
|
|
1080
|
+
// warning, not a fatal error. Log and continue without pairing.
|
|
1081
|
+
const logger = container.resolve(ServerLoggerFactory).create("codemation.pairing");
|
|
1082
|
+
logger.warn(
|
|
1083
|
+
`WORKSPACE_PAIRING_SECRET is set but invalid — pairing disabled. ${err instanceof Error ? err.message : String(err)}`,
|
|
1084
|
+
);
|
|
1085
|
+
return;
|
|
1086
|
+
}
|
|
992
1087
|
if (!pairingConfig) {
|
|
993
1088
|
// Pairing is optional in non-production environments (local dev without CP integration).
|
|
994
1089
|
// Emit a startup warning so operators know the workspace-mcp HMAC channel is inactive.
|
|
@@ -1006,18 +1101,33 @@ private mergeConfigMcpServers(container: Container, appConfig: AppConfig): void
|
|
|
1006
1101
|
return;
|
|
1007
1102
|
}
|
|
1008
1103
|
container.registerInstance(PairingConfigToken, pairingConfig);
|
|
1104
|
+
// T7: Stamp workspaceId on HumanTaskRecord in managed mode for defense-in-depth workspace check.
|
|
1105
|
+
container.registerInstance(HitlWorkspaceIdToken, pairingConfig.workspaceId);
|
|
1009
1106
|
container.registerSingleton(HmacRequestSigner, HmacRequestSigner);
|
|
1010
1107
|
container.registerSingleton(PairedFetch, PairedFetch);
|
|
1108
|
+
// T6: Durable nonce store — PrismaHmacNonceStore survives process restarts.
|
|
1109
|
+
container.registerSingleton(HmacNonceStoreToken, PrismaHmacNonceStore);
|
|
1011
1110
|
container.registerSingleton(IncomingHmacVerifier, IncomingHmacVerifier);
|
|
1012
1111
|
container.registerSingleton(InternalHmacAuthMiddleware, InternalHmacAuthMiddleware);
|
|
1013
1112
|
container.registerSingleton(BrokerClient, BrokerClient);
|
|
1014
1113
|
container.registerSingleton(ApplicationTokens.InternalHonoApiRouteRegistrar, InternalPingRegistrar);
|
|
1015
1114
|
container.registerSingleton(ApplicationTokens.InternalHonoApiRouteRegistrar, InternalCredentialsPushRegistrar);
|
|
1115
|
+
container.registerSingleton(ApplicationTokens.InternalHonoApiRouteRegistrar, InternalCredentialsBindingRegistrar);
|
|
1016
1116
|
container.registerSingleton(ApplicationTokens.InternalHonoApiRouteRegistrar, InternalCredentialsListRegistrar);
|
|
1017
1117
|
container.registerSingleton(ApplicationTokens.InternalHonoApiRouteRegistrar, InternalWorkflowsListRegistrar);
|
|
1018
1118
|
container.registerSingleton(ApplicationTokens.InternalHonoApiRouteRegistrar, InternalWorkflowDetailRegistrar);
|
|
1019
1119
|
container.registerSingleton(ApplicationTokens.InternalHonoApiRouteRegistrar, InternalWorkflowActivationRegistrar);
|
|
1020
1120
|
container.registerSingleton(ApplicationTokens.InternalHonoApiRouteRegistrar, InternalWorkflowTestRunRegistrar);
|
|
1121
|
+
// HITL: CP inbox channel + inbound decision callback (managed mode only)
|
|
1122
|
+
container.registerSingleton(ControlPlaneInboxChannel, ControlPlaneInboxChannel);
|
|
1123
|
+
container.register(ControlPlaneInboxChannelToken, {
|
|
1124
|
+
useFactory: instanceCachingFactory((dc) => dc.resolve(ControlPlaneInboxChannel)),
|
|
1125
|
+
});
|
|
1126
|
+
container.registerSingleton(HitlCallbackHandler, HitlCallbackHandler);
|
|
1127
|
+
container.registerSingleton(
|
|
1128
|
+
ApplicationTokens.InternalHonoApiRouteRegistrar,
|
|
1129
|
+
HitlInternalCallbackHonoApiRouteRegistrar,
|
|
1130
|
+
);
|
|
1021
1131
|
}
|
|
1022
1132
|
|
|
1023
1133
|
private registerOperationalInfrastructure(container: Container): void {
|
|
@@ -1104,6 +1214,9 @@ private mergeConfigMcpServers(container: Container, appConfig: AppConfig): void
|
|
|
1104
1214
|
binaryStorage,
|
|
1105
1215
|
new LazyExecutionTelemetryFactory(() => container.resolve(OtelExecutionTelemetryFactory)),
|
|
1106
1216
|
new CatalogBackedCostTrackingTelemetryFactory(new StaticCostCatalog(FrameworkCostCatalogEntries)),
|
|
1217
|
+
undefined,
|
|
1218
|
+
undefined,
|
|
1219
|
+
container,
|
|
1107
1220
|
),
|
|
1108
1221
|
);
|
|
1109
1222
|
this.registerRuntimeNodeActivationScheduler(container);
|
|
@@ -1141,6 +1254,8 @@ private mergeConfigMcpServers(container: Container, appConfig: AppConfig): void
|
|
|
1141
1254
|
container.resolve(PrismaWorkflowActivationRepository),
|
|
1142
1255
|
);
|
|
1143
1256
|
container.registerInstance(ApplicationTokens.CredentialStore, container.resolve(PrismaCredentialStore));
|
|
1257
|
+
// HITL: wire PrismaHumanTaskStore now that PrismaDatabaseClientToken is available
|
|
1258
|
+
container.registerInstance(HumanTaskStoreToken, container.resolve(PrismaHumanTaskStore));
|
|
1144
1259
|
container.registerInstance(ApplicationTokens.RunTraceContextRepository, runTraceContextRepository);
|
|
1145
1260
|
container.registerInstance(ApplicationTokens.TelemetrySpanStore, telemetrySpanStore);
|
|
1146
1261
|
container.registerInstance(ApplicationTokens.TelemetryArtifactStore, telemetryArtifactStore);
|
|
@@ -1153,6 +1268,9 @@ private mergeConfigMcpServers(container: Container, appConfig: AppConfig): void
|
|
|
1153
1268
|
binaryStorage,
|
|
1154
1269
|
new LazyExecutionTelemetryFactory(() => container.resolve(OtelExecutionTelemetryFactory)),
|
|
1155
1270
|
new CatalogBackedCostTrackingTelemetryFactory(new StaticCostCatalog(FrameworkCostCatalogEntries)),
|
|
1271
|
+
undefined,
|
|
1272
|
+
undefined,
|
|
1273
|
+
container,
|
|
1156
1274
|
),
|
|
1157
1275
|
);
|
|
1158
1276
|
if (appConfig.scheduler.kind === "bullmq") {
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { AppConfig } from "../../presentation/config/AppConfig";
|
|
2
|
+
import type { AppContainerFactory } from "../AppContainerFactory";
|
|
3
|
+
import { FrontendRuntime } from "./FrontendRuntime";
|
|
4
|
+
import { CodemationHonoApiApp } from "../../presentation/http/hono/CodemationHonoApiAppFactory";
|
|
5
|
+
import type { WorkflowWebsocketServerFactory } from "../../presentation/websocket/WorkflowWebsocketServerFactory";
|
|
6
|
+
import type { HeadlessHttpServerFactory } from "../../presentation/http/HeadlessHttpServerFactory";
|
|
7
|
+
import type { Logger } from "../../application/logging/Logger";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Boots the Codemation API + WebSocket servers without the Next.js UI process.
|
|
11
|
+
* Used by `codemation serve web --headless` for workspace pod containers where the
|
|
12
|
+
* UI is served externally (e.g. from the control-plane's customer-ui).
|
|
13
|
+
*/
|
|
14
|
+
export class HeadlessApiRuntime {
|
|
15
|
+
constructor(
|
|
16
|
+
private readonly appContainerFactory: AppContainerFactory,
|
|
17
|
+
private readonly websocketServerFactory: WorkflowWebsocketServerFactory,
|
|
18
|
+
private readonly httpServerFactory: HeadlessHttpServerFactory,
|
|
19
|
+
private readonly logger: Logger,
|
|
20
|
+
) {}
|
|
21
|
+
|
|
22
|
+
async start(appConfig: AppConfig): Promise<void> {
|
|
23
|
+
const port = Number(appConfig.env.PORT ?? 4001);
|
|
24
|
+
|
|
25
|
+
this.logger.info(`Starting codemation headless API runtime`);
|
|
26
|
+
this.logger.info(`HTTP port: ${port}, WS port: ${appConfig.webSocketPort}`);
|
|
27
|
+
|
|
28
|
+
const websocketServer = this.websocketServerFactory.create(appConfig);
|
|
29
|
+
|
|
30
|
+
const container = await this.appContainerFactory.create({
|
|
31
|
+
appConfig,
|
|
32
|
+
sharedWorkflowWebsocketServer: websocketServer,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
await container.resolve(FrontendRuntime).start();
|
|
36
|
+
|
|
37
|
+
const honoApp = container.resolve(CodemationHonoApiApp);
|
|
38
|
+
const httpServer = this.httpServerFactory.create(honoApp, port, this.logger);
|
|
39
|
+
|
|
40
|
+
await new Promise<void>((resolve) => {
|
|
41
|
+
httpServer.listen(port, () => {
|
|
42
|
+
this.logger.info(`codemation headless API listening on port ${port}`);
|
|
43
|
+
resolve();
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { inject, injectable } from "@codemation/core";
|
|
2
|
+
import type {
|
|
3
|
+
CallerContext,
|
|
4
|
+
CredentialMaterialProvider,
|
|
5
|
+
CredentialMaterialRef,
|
|
6
|
+
MaterialBundle,
|
|
7
|
+
} from "@codemation/core";
|
|
8
|
+
|
|
9
|
+
import { ApplicationTokens } from "../applicationTokens";
|
|
10
|
+
import type { Logger, LoggerFactory } from "../application/logging/Logger";
|
|
11
|
+
|
|
12
|
+
type CacheEntry = Readonly<{
|
|
13
|
+
material: MaterialBundle;
|
|
14
|
+
expiresAt: number;
|
|
15
|
+
}>;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* In-memory TTL cache decorator for `CredentialMaterialProvider`.
|
|
19
|
+
*
|
|
20
|
+
* - Hits avoid the wrapped provider (no CP RPC, no audit row).
|
|
21
|
+
* - Misses/expired entries delegate to the wrapped provider and store the
|
|
22
|
+
* result with TTL = `min(material.expiresAt − 60s, now + 5min)`.
|
|
23
|
+
* - `setMaterial` delegates and then invalidates the entry, so the next
|
|
24
|
+
* `getMaterial` re-fetches fresh bytes.
|
|
25
|
+
*
|
|
26
|
+
* Cache is process-local only — never serialized, never shared across pods.
|
|
27
|
+
* See `docs/design/credentials-oauth-unification.md` and
|
|
28
|
+
* `planning/sprints/credentials-vault/03-in-memory-material-cache.md`.
|
|
29
|
+
*/
|
|
30
|
+
@injectable()
|
|
31
|
+
export class CachingCredentialMaterialProvider implements CredentialMaterialProvider {
|
|
32
|
+
private static readonly HARD_CAP_MS = 5 * 60 * 1000;
|
|
33
|
+
private static readonly EXPIRY_SAFETY_WINDOW_MS = 60 * 1000;
|
|
34
|
+
|
|
35
|
+
private readonly cache = new Map<string, CacheEntry>();
|
|
36
|
+
private readonly logger: Logger;
|
|
37
|
+
|
|
38
|
+
constructor(
|
|
39
|
+
@inject(ApplicationTokens.CredentialMaterialInnerProvider) private readonly inner: CredentialMaterialProvider,
|
|
40
|
+
@inject(ApplicationTokens.LoggerFactory) loggerFactory: LoggerFactory,
|
|
41
|
+
) {
|
|
42
|
+
this.logger = loggerFactory.create("codemation.credentials.material-cache");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async getMaterial(ref: CredentialMaterialRef, context: CallerContext): Promise<MaterialBundle> {
|
|
46
|
+
const key = this.keyFor(ref);
|
|
47
|
+
const now = Date.now();
|
|
48
|
+
const entry = this.cache.get(key);
|
|
49
|
+
if (entry && entry.expiresAt > now) {
|
|
50
|
+
this.logger.debug(`material-cache hit key=${key}`);
|
|
51
|
+
return entry.material;
|
|
52
|
+
}
|
|
53
|
+
if (entry) {
|
|
54
|
+
this.logger.debug(`material-cache expired key=${key}`);
|
|
55
|
+
this.cache.delete(key);
|
|
56
|
+
} else {
|
|
57
|
+
this.logger.debug(`material-cache miss key=${key}`);
|
|
58
|
+
}
|
|
59
|
+
const material = await this.inner.getMaterial(ref, context);
|
|
60
|
+
const ttlExpiry = this.computeCacheExpiry(material, Date.now());
|
|
61
|
+
if (ttlExpiry !== null) {
|
|
62
|
+
this.cache.set(key, { material, expiresAt: ttlExpiry });
|
|
63
|
+
}
|
|
64
|
+
return material;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async setMaterial(ref: CredentialMaterialRef, material: MaterialBundle): Promise<void> {
|
|
68
|
+
await this.inner.setMaterial(ref, material);
|
|
69
|
+
this.cache.delete(this.keyFor(ref));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
private keyFor(ref: CredentialMaterialRef): string {
|
|
73
|
+
return `${ref.source}::${ref.id}`;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Returns the absolute epoch-ms at which the cache entry should expire, or
|
|
78
|
+
* `null` if the entry should not be cached (computed TTL ≤ 0).
|
|
79
|
+
*/
|
|
80
|
+
private computeCacheExpiry(material: MaterialBundle, now: number): number | null {
|
|
81
|
+
const hardCapExpiry = now + CachingCredentialMaterialProvider.HARD_CAP_MS;
|
|
82
|
+
if (material.expiresAt === undefined) {
|
|
83
|
+
return hardCapExpiry;
|
|
84
|
+
}
|
|
85
|
+
const parsed = Date.parse(material.expiresAt);
|
|
86
|
+
if (Number.isNaN(parsed)) {
|
|
87
|
+
return hardCapExpiry;
|
|
88
|
+
}
|
|
89
|
+
const safeExpiry = parsed - CachingCredentialMaterialProvider.EXPIRY_SAFETY_WINDOW_MS;
|
|
90
|
+
const clamped = Math.min(safeExpiry, hardCapExpiry);
|
|
91
|
+
if (clamped <= now) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
return clamped;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { inject, injectable } from "@codemation/core";
|
|
2
|
+
import type {
|
|
3
|
+
CallerContext,
|
|
4
|
+
CredentialMaterialProvider,
|
|
5
|
+
CredentialMaterialRef,
|
|
6
|
+
MaterialBundle,
|
|
7
|
+
} from "@codemation/core";
|
|
8
|
+
import { ManagedCredentialMaterialWriteError } from "@codemation/core";
|
|
9
|
+
|
|
10
|
+
import { LocalCredentialMaterialProvider } from "./LocalCredentialMaterialProvider";
|
|
11
|
+
import { ControlPlaneCredentialMaterialProvider } from "./ControlPlaneCredentialMaterialProvider";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Routes `getMaterial` / `setMaterial` to the right inner provider based on
|
|
15
|
+
* `ref.source`. Registered as the inner of `CachingCredentialMaterialProvider`
|
|
16
|
+
* in managed mode so workspaces with mixed local + control-plane credential
|
|
17
|
+
* instances read each through the correct provider.
|
|
18
|
+
*
|
|
19
|
+
* Writes against `source: "control-plane"` always throw
|
|
20
|
+
* `ManagedCredentialMaterialWriteError` — managed credential bytes are owned
|
|
21
|
+
* by the control plane.
|
|
22
|
+
*
|
|
23
|
+
* See `planning/sprints/credentials-vault/02-controlplane-material-provider.md`.
|
|
24
|
+
*/
|
|
25
|
+
@injectable()
|
|
26
|
+
export class CompositeCredentialMaterialProvider implements CredentialMaterialProvider {
|
|
27
|
+
constructor(
|
|
28
|
+
@inject(LocalCredentialMaterialProvider) private readonly local: LocalCredentialMaterialProvider,
|
|
29
|
+
@inject(ControlPlaneCredentialMaterialProvider)
|
|
30
|
+
private readonly controlPlane: ControlPlaneCredentialMaterialProvider,
|
|
31
|
+
) {}
|
|
32
|
+
|
|
33
|
+
async getMaterial(ref: CredentialMaterialRef, context: CallerContext): Promise<MaterialBundle> {
|
|
34
|
+
return this.pick(ref).getMaterial(ref, context);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async setMaterial(ref: CredentialMaterialRef, material: MaterialBundle): Promise<void> {
|
|
38
|
+
if (ref.source === "control-plane") {
|
|
39
|
+
throw new ManagedCredentialMaterialWriteError();
|
|
40
|
+
}
|
|
41
|
+
return this.pick(ref).setMaterial(ref, material);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
private pick(ref: CredentialMaterialRef): CredentialMaterialProvider {
|
|
45
|
+
return ref.source === "control-plane" ? this.controlPlane : this.local;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -6,7 +6,6 @@ import type { AppConfig } from "../presentation/config/AppConfig";
|
|
|
6
6
|
import { PairedFetch } from "../pairing/PairedFetch";
|
|
7
7
|
import { PairingConfigToken } from "../pairing/PairingConfigToken";
|
|
8
8
|
import type { PairingConfig } from "../pairing/pairing.types";
|
|
9
|
-
import type { OAuthAppCatalogEntry } from "./catalogTypes";
|
|
10
9
|
|
|
11
10
|
/**
|
|
12
11
|
* Configuration read from env at construction time.
|
|
@@ -27,21 +26,20 @@ type EndpointState = {
|
|
|
27
26
|
};
|
|
28
27
|
|
|
29
28
|
/**
|
|
30
|
-
* Polls the
|
|
29
|
+
* Polls the control-plane catalog endpoints on a configurable interval,
|
|
31
30
|
* caches the last-known-good responses, and exposes the fetched data for
|
|
32
|
-
* credential-type overrides
|
|
31
|
+
* credential-type overrides and MCP server registrations.
|
|
33
32
|
*
|
|
34
33
|
* Endpoints (HMAC-gated via PairedFetch):
|
|
35
|
-
* GET /
|
|
36
|
-
* GET /
|
|
37
|
-
* GET /api/catalog/credential-types
|
|
34
|
+
* GET /internal/catalog/mcp-servers
|
|
35
|
+
* GET /internal/catalog/credential-types
|
|
38
36
|
*
|
|
39
37
|
* Failure semantics: a failure on one endpoint does NOT prevent updating the
|
|
40
38
|
* others. Each endpoint's consecutive-failure counter and staleness escalation
|
|
41
39
|
* are tracked independently.
|
|
42
40
|
*
|
|
43
41
|
* When not paired with a control plane (PairingConfigToken is null),
|
|
44
|
-
* start() returns immediately and all
|
|
42
|
+
* start() returns immediately and all getters remain null.
|
|
45
43
|
*/
|
|
46
44
|
@injectable()
|
|
47
45
|
export class ControlPlaneCatalogFetcher {
|
|
@@ -51,11 +49,9 @@ export class ControlPlaneCatalogFetcher {
|
|
|
51
49
|
/** Tracks in-flight refresh so stop() can safely await it. */
|
|
52
50
|
private inFlight: Promise<void> | null = null;
|
|
53
51
|
|
|
54
|
-
private _oauthApps: readonly OAuthAppCatalogEntry[] | null = null;
|
|
55
52
|
private _mcpServers: readonly McpServerDeclaration[] | null = null;
|
|
56
53
|
private _credentialTypeOverrides: readonly CredentialTypeDefinition[] | null = null;
|
|
57
54
|
|
|
58
|
-
private readonly oauthAppsState: EndpointState = { consecutiveFailures: 0, lastSuccessAt: null };
|
|
59
55
|
private readonly mcpServersState: EndpointState = { consecutiveFailures: 0, lastSuccessAt: null };
|
|
60
56
|
private readonly credentialTypesState: EndpointState = { consecutiveFailures: 0, lastSuccessAt: null };
|
|
61
57
|
|
|
@@ -82,11 +78,6 @@ export class ControlPlaneCatalogFetcher {
|
|
|
82
78
|
};
|
|
83
79
|
}
|
|
84
80
|
|
|
85
|
-
/** Latest fetched OAuth app catalog; null until first successful fetch. */
|
|
86
|
-
get oauthApps(): readonly OAuthAppCatalogEntry[] | null {
|
|
87
|
-
return this._oauthApps;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
81
|
/** Latest fetched MCP server declarations; null until first successful fetch. */
|
|
91
82
|
get mcpServers(): readonly McpServerDeclaration[] | null {
|
|
92
83
|
return this._mcpServers;
|
|
@@ -163,22 +154,11 @@ export class ControlPlaneCatalogFetcher {
|
|
|
163
154
|
const logger = this.loggers.create("ControlPlaneCatalogFetcher");
|
|
164
155
|
const base = this.pairingConfig.controlPlaneUrl;
|
|
165
156
|
|
|
166
|
-
const [
|
|
167
|
-
this.pairedFetch.get(`${base}/
|
|
168
|
-
this.pairedFetch.get(`${base}/
|
|
169
|
-
this.pairedFetch.get(`${base}/api/catalog/credential-types`),
|
|
157
|
+
const [mcpResult, credTypesResult] = await Promise.allSettled([
|
|
158
|
+
this.pairedFetch.get(`${base}/internal/catalog/mcp-servers`),
|
|
159
|
+
this.pairedFetch.get(`${base}/internal/catalog/credential-types`),
|
|
170
160
|
]);
|
|
171
161
|
|
|
172
|
-
await this.handleEndpointResult(
|
|
173
|
-
oauthResult,
|
|
174
|
-
this.oauthAppsState,
|
|
175
|
-
"oauth-apps",
|
|
176
|
-
(data) => {
|
|
177
|
-
this._oauthApps = data as OAuthAppCatalogEntry[];
|
|
178
|
-
},
|
|
179
|
-
logger,
|
|
180
|
-
);
|
|
181
|
-
|
|
182
162
|
await this.handleEndpointResult(
|
|
183
163
|
mcpResult,
|
|
184
164
|
this.mcpServersState,
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { inject, injectable } from "@codemation/core";
|
|
2
|
+
import type {
|
|
3
|
+
CallerContext,
|
|
4
|
+
CredentialMaterialProvider,
|
|
5
|
+
CredentialMaterialRef,
|
|
6
|
+
MaterialBundle,
|
|
7
|
+
} from "@codemation/core";
|
|
8
|
+
import {
|
|
9
|
+
IllegalMaterialSourceError,
|
|
10
|
+
ManagedCredentialMaterialWriteError,
|
|
11
|
+
ManagedMaterialFetchError,
|
|
12
|
+
} from "@codemation/core";
|
|
13
|
+
|
|
14
|
+
import { PairedFetch } from "../pairing/PairedFetch";
|
|
15
|
+
import { PairingConfigToken } from "../pairing/PairingConfigToken";
|
|
16
|
+
import type { PairingConfig } from "../pairing/pairing.types";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Control-plane (managed-mode) implementation of `CredentialMaterialProvider`.
|
|
20
|
+
*
|
|
21
|
+
* `getMaterial({ source: "control-plane", id }, callerContext)` HMAC-POSTs to
|
|
22
|
+
* `<CP>/internal/credentials/material/:id`
|
|
23
|
+
* with body `{ callerContext }`. The CP endpoint refreshes upstream tokens as
|
|
24
|
+
* needed and returns `{ accessToken, expiresAt, scopes, providerAccountId, typeId }`.
|
|
25
|
+
* The refresh token never crosses this boundary.
|
|
26
|
+
*
|
|
27
|
+
* `getMaterial` for `source: "local"` throws `IllegalMaterialSourceError`; a
|
|
28
|
+
* dispatcher (`CompositeCredentialMaterialProvider`) routes by source.
|
|
29
|
+
*
|
|
30
|
+
* `setMaterial` always throws `ManagedCredentialMaterialWriteError` — managed
|
|
31
|
+
* credential bytes are owned by the control plane.
|
|
32
|
+
*
|
|
33
|
+
* See `docs/design/credentials-oauth-unification.md` and
|
|
34
|
+
* `planning/sprints/credentials-vault/02-controlplane-material-provider.md`.
|
|
35
|
+
*/
|
|
36
|
+
@injectable()
|
|
37
|
+
export class ControlPlaneCredentialMaterialProvider implements CredentialMaterialProvider {
|
|
38
|
+
constructor(
|
|
39
|
+
@inject(PairedFetch) private readonly pairedFetch: PairedFetch,
|
|
40
|
+
@inject(PairingConfigToken) private readonly pairingConfig: PairingConfig,
|
|
41
|
+
) {}
|
|
42
|
+
|
|
43
|
+
async getMaterial(ref: CredentialMaterialRef, context: CallerContext): Promise<MaterialBundle> {
|
|
44
|
+
if (ref.source !== "control-plane") {
|
|
45
|
+
throw new IllegalMaterialSourceError(ref.source, "ControlPlaneCredentialMaterialProvider");
|
|
46
|
+
}
|
|
47
|
+
const url = `${this.pairingConfig.controlPlaneUrl}/internal/credentials/material/${encodeURIComponent(ref.id)}`;
|
|
48
|
+
const response = await this.pairedFetch.post(url, { callerContext: context });
|
|
49
|
+
if (!response.ok) {
|
|
50
|
+
const body = await response.text().catch(() => "");
|
|
51
|
+
throw new ManagedMaterialFetchError(response.status, body.slice(0, 500));
|
|
52
|
+
}
|
|
53
|
+
const json = (await response.json()) as {
|
|
54
|
+
accessToken?: unknown;
|
|
55
|
+
expiresAt?: unknown;
|
|
56
|
+
scopes?: unknown;
|
|
57
|
+
providerAccountId?: unknown;
|
|
58
|
+
typeId?: unknown;
|
|
59
|
+
};
|
|
60
|
+
if (typeof json.accessToken !== "string" || json.accessToken.length === 0) {
|
|
61
|
+
throw new ManagedMaterialFetchError(
|
|
62
|
+
response.status,
|
|
63
|
+
"missing accessToken in CP response",
|
|
64
|
+
"Control-plane material response missing accessToken",
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
accessToken: json.accessToken,
|
|
69
|
+
// CP intentionally never returns the refresh token to the workspace.
|
|
70
|
+
refreshToken: undefined,
|
|
71
|
+
expiresAt: typeof json.expiresAt === "string" ? json.expiresAt : undefined,
|
|
72
|
+
grantedScopes: Array.isArray(json.scopes) ? json.scopes.filter((s): s is string => typeof s === "string") : [],
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async setMaterial(_ref: CredentialMaterialRef, _material: MaterialBundle): Promise<void> {
|
|
77
|
+
throw new ManagedCredentialMaterialWriteError();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -4,10 +4,7 @@ import type { Clock, OAuthFlowExecutor, OAuthMaterial } from "@codemation/core";
|
|
|
4
4
|
import { ApplicationTokens } from "../applicationTokens";
|
|
5
5
|
import type { LoggerFactory } from "../application/logging/Logger";
|
|
6
6
|
import { CredentialSecretCipher } from "../domain/credentials/CredentialSecretCipher";
|
|
7
|
-
import type {
|
|
8
|
-
CredentialOAuth2MaterialRecord,
|
|
9
|
-
CredentialStore,
|
|
10
|
-
} from "../domain/credentials/CredentialServices";
|
|
7
|
+
import type { CredentialOAuth2MaterialRecord, CredentialStore } from "../domain/credentials/CredentialServices";
|
|
11
8
|
|
|
12
9
|
/**
|
|
13
10
|
* Reads OAuth2 material for a credential instance and proactively refreshes it
|
|
@@ -128,9 +125,7 @@ export class CredentialOAuth2MaterialReader {
|
|
|
128
125
|
refreshToken: typeof json.refreshToken === "string" ? json.refreshToken : undefined,
|
|
129
126
|
expiresAt: typeof json.expiresAt === "string" ? json.expiresAt : undefined,
|
|
130
127
|
grantedScopes:
|
|
131
|
-
typeof json.grantedScopes === "string"
|
|
132
|
-
? json.grantedScopes.split(/\s+/).filter((s) => s.length > 0)
|
|
133
|
-
: [],
|
|
128
|
+
typeof json.grantedScopes === "string" ? json.grantedScopes.split(/\s+/).filter((s) => s.length > 0) : [],
|
|
134
129
|
};
|
|
135
130
|
}
|
|
136
131
|
}
|