@codemation/host 0.8.0 → 0.9.1
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 +59 -0
- 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-BT0y0LVC.d.ts → AppConfigFactory-DncmwCD1.d.ts} +2918 -199
- package/dist/{AppContainerFactory-DRTjG7nG.js → AppContainerFactory-CHCXP2rn.js} +1735 -474
- package/dist/AppContainerFactory-CHCXP2rn.js.map +1 -0
- package/dist/{CodemationAppContext-CGFYVcSb.d.ts → CodemationAppContext-K51b7oXe.d.ts} +3 -3
- package/dist/{CodemationAuthoring.types-DiKKogum.d.ts → CodemationAuthoring.types-BXlXIl4K.d.ts} +4 -4
- package/dist/{CodemationConfigNormalizer-48f-T66P.d.ts → CodemationConfigNormalizer-B4rDYC9h.d.ts} +3 -3
- package/dist/{CodemationConsumerConfigLoader-_PIYqwVx.d.ts → CodemationConsumerConfigLoader-Dt4jyLx6.d.ts} +2 -2
- package/dist/{CodemationPluginListMerger-DP7djJ9S.d.ts → CodemationPluginListMerger-DS6I3Xe0.d.ts} +24 -12
- 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-BLloBztI.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/{InternalHonoApiRouteRegistrar-c7t3KnV_.d.ts → InternalHonoApiRouteRegistrar-Ce1yxpnO.d.ts} +1 -1
- 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-Dv04tJ-6.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/client.d.ts +1 -1
- package/dist/client.js +1 -1
- package/dist/consumer.d.ts +5 -5
- 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 +17 -16
- package/dist/index.js +8 -8
- 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 -13
- package/dist/nextServer.js +6 -6
- package/dist/pairing.d.ts +28 -9
- 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-09PKasWR.d.ts → server-B5trn7y4.d.ts} +5 -5
- package/dist/{server-vtRCPgRJ.js → server-CNj_y0QO.js} +4 -4
- package/dist/{server-vtRCPgRJ.js.map → server-CNj_y0QO.js.map} +1 -1
- package/dist/server.d.ts +10 -10
- package/dist/server.js +8 -8
- package/package.json +11 -10
- 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 +100 -0
- package/src/credentials/CachingCredentialMaterialProvider.ts +96 -0
- package/src/credentials/CompositeCredentialMaterialProvider.ts +47 -0
- package/src/credentials/ControlPlaneCatalogFetcher.ts +4 -24
- 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 +89 -0
- package/src/hitl/HitlTimeoutWorker.ts +143 -0
- package/src/hitl/InboxChannelResolver.ts +49 -0
- package/src/hitl/LocalInboxChannel.ts +37 -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/http/ApiPaths.ts +14 -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/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-DRTjG7nG.js.map +0 -1
- package/dist/CredentialServices-Dk8yypeL.js.map +0 -1
- package/dist/InternalPingRegistrar-DY3kSfxP.js.map +0 -1
- package/dist/persistenceServer-B71RGvSj.d.ts +0 -30
- package/dist/persistenceServer-C-hH4z6l.js.map +0 -1
- 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(
|
|
@@ -718,6 +747,7 @@ export class AppContainerFactory {
|
|
|
718
747
|
container.registerSingleton(CredentialRuntimeMaterialService, CredentialRuntimeMaterialService);
|
|
719
748
|
container.registerSingleton(WorkflowCredentialNodeResolver, WorkflowCredentialNodeResolver);
|
|
720
749
|
container.registerSingleton(CredentialInstanceService, CredentialInstanceService);
|
|
750
|
+
container.registerSingleton(AppGalleryProjector, AppGalleryProjector);
|
|
721
751
|
container.registerSingleton(CredentialBindingService, CredentialBindingService);
|
|
722
752
|
container.registerSingleton(WorkflowActivationPreflightRules, WorkflowActivationPreflightRules);
|
|
723
753
|
container.registerSingleton(WorkflowActivationPreflight, WorkflowActivationPreflight);
|
|
@@ -740,6 +770,26 @@ export class AppContainerFactory {
|
|
|
740
770
|
container.registerSingleton(LocalOAuthFlowExecutor, LocalOAuthFlowExecutor);
|
|
741
771
|
}
|
|
742
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);
|
|
743
793
|
container.registerSingleton(CodemationFrontendAuthSnapshotFactory, CodemationFrontendAuthSnapshotFactory);
|
|
744
794
|
container.registerSingleton(FrontendAppConfigFactory, FrontendAppConfigFactory);
|
|
745
795
|
container.registerSingleton(PublicFrontendBootstrapFactory, PublicFrontendBootstrapFactory);
|
|
@@ -852,6 +902,7 @@ export class AppContainerFactory {
|
|
|
852
902
|
),
|
|
853
903
|
});
|
|
854
904
|
container.registerSingleton(OtelExecutionTelemetryFactory, OtelExecutionTelemetryFactory);
|
|
905
|
+
container.registerSingleton(ResumeTelemetryContextForRun, ResumeTelemetryContextForRun);
|
|
855
906
|
container.registerSingleton(InMemoryRunTraceContextRepository, InMemoryRunTraceContextRepository);
|
|
856
907
|
container.registerSingleton(InMemoryTelemetrySpanStore, InMemoryTelemetrySpanStore);
|
|
857
908
|
container.registerSingleton(InMemoryTelemetryArtifactStore, InMemoryTelemetryArtifactStore);
|
|
@@ -874,6 +925,32 @@ export class AppContainerFactory {
|
|
|
874
925
|
container.registerSingleton(PrismaWorkflowActivationRepository, PrismaWorkflowActivationRepository);
|
|
875
926
|
container.registerSingleton(InMemoryWorkflowActivationRepository, InMemoryWorkflowActivationRepository);
|
|
876
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
|
+
});
|
|
877
954
|
container.register(ApplicationTokens.WorkflowDefinitionRepository, {
|
|
878
955
|
useFactory: instanceCachingFactory(
|
|
879
956
|
(dependencyContainer) =>
|
|
@@ -1024,18 +1101,33 @@ export class AppContainerFactory {
|
|
|
1024
1101
|
return;
|
|
1025
1102
|
}
|
|
1026
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);
|
|
1027
1106
|
container.registerSingleton(HmacRequestSigner, HmacRequestSigner);
|
|
1028
1107
|
container.registerSingleton(PairedFetch, PairedFetch);
|
|
1108
|
+
// T6: Durable nonce store — PrismaHmacNonceStore survives process restarts.
|
|
1109
|
+
container.registerSingleton(HmacNonceStoreToken, PrismaHmacNonceStore);
|
|
1029
1110
|
container.registerSingleton(IncomingHmacVerifier, IncomingHmacVerifier);
|
|
1030
1111
|
container.registerSingleton(InternalHmacAuthMiddleware, InternalHmacAuthMiddleware);
|
|
1031
1112
|
container.registerSingleton(BrokerClient, BrokerClient);
|
|
1032
1113
|
container.registerSingleton(ApplicationTokens.InternalHonoApiRouteRegistrar, InternalPingRegistrar);
|
|
1033
1114
|
container.registerSingleton(ApplicationTokens.InternalHonoApiRouteRegistrar, InternalCredentialsPushRegistrar);
|
|
1115
|
+
container.registerSingleton(ApplicationTokens.InternalHonoApiRouteRegistrar, InternalCredentialsBindingRegistrar);
|
|
1034
1116
|
container.registerSingleton(ApplicationTokens.InternalHonoApiRouteRegistrar, InternalCredentialsListRegistrar);
|
|
1035
1117
|
container.registerSingleton(ApplicationTokens.InternalHonoApiRouteRegistrar, InternalWorkflowsListRegistrar);
|
|
1036
1118
|
container.registerSingleton(ApplicationTokens.InternalHonoApiRouteRegistrar, InternalWorkflowDetailRegistrar);
|
|
1037
1119
|
container.registerSingleton(ApplicationTokens.InternalHonoApiRouteRegistrar, InternalWorkflowActivationRegistrar);
|
|
1038
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
|
+
);
|
|
1039
1131
|
}
|
|
1040
1132
|
|
|
1041
1133
|
private registerOperationalInfrastructure(container: Container): void {
|
|
@@ -1122,6 +1214,9 @@ export class AppContainerFactory {
|
|
|
1122
1214
|
binaryStorage,
|
|
1123
1215
|
new LazyExecutionTelemetryFactory(() => container.resolve(OtelExecutionTelemetryFactory)),
|
|
1124
1216
|
new CatalogBackedCostTrackingTelemetryFactory(new StaticCostCatalog(FrameworkCostCatalogEntries)),
|
|
1217
|
+
undefined,
|
|
1218
|
+
undefined,
|
|
1219
|
+
container,
|
|
1125
1220
|
),
|
|
1126
1221
|
);
|
|
1127
1222
|
this.registerRuntimeNodeActivationScheduler(container);
|
|
@@ -1159,6 +1254,8 @@ export class AppContainerFactory {
|
|
|
1159
1254
|
container.resolve(PrismaWorkflowActivationRepository),
|
|
1160
1255
|
);
|
|
1161
1256
|
container.registerInstance(ApplicationTokens.CredentialStore, container.resolve(PrismaCredentialStore));
|
|
1257
|
+
// HITL: wire PrismaHumanTaskStore now that PrismaDatabaseClientToken is available
|
|
1258
|
+
container.registerInstance(HumanTaskStoreToken, container.resolve(PrismaHumanTaskStore));
|
|
1162
1259
|
container.registerInstance(ApplicationTokens.RunTraceContextRepository, runTraceContextRepository);
|
|
1163
1260
|
container.registerInstance(ApplicationTokens.TelemetrySpanStore, telemetrySpanStore);
|
|
1164
1261
|
container.registerInstance(ApplicationTokens.TelemetryArtifactStore, telemetryArtifactStore);
|
|
@@ -1171,6 +1268,9 @@ export class AppContainerFactory {
|
|
|
1171
1268
|
binaryStorage,
|
|
1172
1269
|
new LazyExecutionTelemetryFactory(() => container.resolve(OtelExecutionTelemetryFactory)),
|
|
1173
1270
|
new CatalogBackedCostTrackingTelemetryFactory(new StaticCostCatalog(FrameworkCostCatalogEntries)),
|
|
1271
|
+
undefined,
|
|
1272
|
+
undefined,
|
|
1273
|
+
container,
|
|
1174
1274
|
),
|
|
1175
1275
|
);
|
|
1176
1276
|
if (appConfig.scheduler.kind === "bullmq") {
|
|
@@ -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,12 +26,11 @@ 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 /internal/catalog/oauth-apps
|
|
36
34
|
* GET /internal/catalog/mcp-servers
|
|
37
35
|
* GET /internal/catalog/credential-types
|
|
38
36
|
*
|
|
@@ -41,7 +39,7 @@ type EndpointState = {
|
|
|
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}/internal/catalog/oauth-apps`),
|
|
157
|
+
const [mcpResult, credTypesResult] = await Promise.allSettled([
|
|
168
158
|
this.pairedFetch.get(`${base}/internal/catalog/mcp-servers`),
|
|
169
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
|
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { inject, injectable } from "@codemation/core";
|
|
2
|
+
import type { Hono } from "hono";
|
|
3
|
+
import type { Logger, LoggerFactory } from "../application/logging/Logger";
|
|
4
|
+
import { ApplicationRequestError } from "../application/ApplicationRequestError";
|
|
5
|
+
import { ApplicationTokens } from "../applicationTokens";
|
|
6
|
+
import { CredentialBindingService } from "../domain/credentials/CredentialServices";
|
|
7
|
+
import { InternalHmacAuthMiddleware } from "../pairing/InternalHmacAuthMiddleware";
|
|
8
|
+
import type { InternalHonoApiRouteRegistrar } from "../presentation/http/hono/InternalHonoApiRouteRegistrar";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Body shape pushed from the control-plane concierge when binding a
|
|
12
|
+
* credential instance to a workflow node slot.
|
|
13
|
+
*
|
|
14
|
+
* Note: the original story brief described the body as
|
|
15
|
+
* `{ credentialInstanceId, nodeId, slotName }`, but `CredentialBinding`
|
|
16
|
+
* requires a `workflowId` and the codebase uses `slotKey` (not `slotName`).
|
|
17
|
+
* The request shape was corrected here and on the matching concierge tool.
|
|
18
|
+
*/
|
|
19
|
+
type CredentialBindingBody = Readonly<{
|
|
20
|
+
workflowId: string;
|
|
21
|
+
nodeId: string;
|
|
22
|
+
slotKey: string;
|
|
23
|
+
credentialInstanceId: string;
|
|
24
|
+
}>;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Registers POST /internal/credentials/binding — HMAC-verified endpoint that
|
|
28
|
+
* lets the control-plane concierge bind a credential instance to a workflow
|
|
29
|
+
* node slot on behalf of a workspace user.
|
|
30
|
+
*/
|
|
31
|
+
@injectable()
|
|
32
|
+
export class InternalCredentialsBindingRegistrar implements InternalHonoApiRouteRegistrar {
|
|
33
|
+
private readonly logger: Logger;
|
|
34
|
+
|
|
35
|
+
constructor(
|
|
36
|
+
@inject(InternalHmacAuthMiddleware) private readonly hmacMiddleware: InternalHmacAuthMiddleware,
|
|
37
|
+
@inject(CredentialBindingService) private readonly credentialBindingService: CredentialBindingService,
|
|
38
|
+
@inject(ApplicationTokens.LoggerFactory) loggerFactory: LoggerFactory,
|
|
39
|
+
) {
|
|
40
|
+
this.logger = loggerFactory.create("InternalCredentialsBindingRegistrar");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
register(app: Hono): void {
|
|
44
|
+
app.post("/internal/credentials/binding", this.hmacMiddleware.handle(), async (c) => {
|
|
45
|
+
try {
|
|
46
|
+
const rawBody = c.get("body" as never) as string | undefined;
|
|
47
|
+
const body = (rawBody ? JSON.parse(rawBody) : await c.req.json()) as Partial<CredentialBindingBody>;
|
|
48
|
+
|
|
49
|
+
if (!body.workflowId || typeof body.workflowId !== "string") {
|
|
50
|
+
return c.json({ error: "workflowId is required" }, 400);
|
|
51
|
+
}
|
|
52
|
+
if (!body.nodeId || typeof body.nodeId !== "string") {
|
|
53
|
+
return c.json({ error: "nodeId is required" }, 400);
|
|
54
|
+
}
|
|
55
|
+
if (!body.slotKey || typeof body.slotKey !== "string") {
|
|
56
|
+
return c.json({ error: "slotKey is required" }, 400);
|
|
57
|
+
}
|
|
58
|
+
if (!body.credentialInstanceId || typeof body.credentialInstanceId !== "string") {
|
|
59
|
+
return c.json({ error: "credentialInstanceId is required" }, 400);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const binding = await this.credentialBindingService.upsertBinding({
|
|
63
|
+
workflowId: body.workflowId,
|
|
64
|
+
nodeId: body.nodeId,
|
|
65
|
+
slotKey: body.slotKey,
|
|
66
|
+
instanceId: body.credentialInstanceId,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
this.logger.info(
|
|
70
|
+
`Credential binding upserted for workflow=${body.workflowId} node=${body.nodeId} slot=${body.slotKey}`,
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
return c.json({ ok: true, binding });
|
|
74
|
+
} catch (error) {
|
|
75
|
+
if (error instanceof ApplicationRequestError) {
|
|
76
|
+
return c.json(error.payload, error.status as 400 | 404);
|
|
77
|
+
}
|
|
78
|
+
this.logger.error("Credential binding handler error", error instanceof Error ? error : undefined);
|
|
79
|
+
return c.json({ error: "Internal server error" }, 500);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|