@codemation/host 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +483 -0
- package/dist/{ApiPaths-CLTHphYZ.js → ApiPaths-Dv1dcHu_.js} +4 -4
- package/dist/ApiPaths-Dv1dcHu_.js.map +1 -0
- package/dist/{AppConfigFactory-YnveXE9k.d.ts → AppConfigFactory-BT0y0LVC.d.ts} +8490 -5548
- package/dist/{AppConfigFactory-C6q-CSKb.js → AppConfigFactory-Cx4qQvRk.js} +112 -52
- package/dist/AppConfigFactory-Cx4qQvRk.js.map +1 -0
- package/dist/{AppContainerFactory-qaqc-R1D.js → AppContainerFactory-DRTjG7nG.js} +7298 -1732
- package/dist/AppContainerFactory-DRTjG7nG.js.map +1 -0
- package/dist/{CodemationAppContext-DRu1Dpri.d.ts → CodemationAppContext-CGFYVcSb.d.ts} +14 -4
- package/dist/{CodemationAuthoring.types-DZl-sJaM.js → CodemationAuthoring.types-BteaR3Dc.js} +19 -6
- package/dist/CodemationAuthoring.types-BteaR3Dc.js.map +1 -0
- package/dist/{CodemationAuthoring.types-fBRppnmi.d.ts → CodemationAuthoring.types-DiKKogum.d.ts} +30 -5
- package/dist/{CodemationConfigNormalizer-DVko3cVN.d.ts → CodemationConfigNormalizer-48f-T66P.d.ts} +3 -3
- package/dist/{CodemationConsumerConfigLoader-BeAUS144.js → CodemationConsumerConfigLoader-By-6tuGc.js} +81 -10
- package/dist/CodemationConsumerConfigLoader-By-6tuGc.js.map +1 -0
- package/dist/{CodemationConsumerConfigLoader-DJWr86f-.d.ts → CodemationConsumerConfigLoader-_PIYqwVx.d.ts} +18 -2
- package/dist/{CodemationPluginListMerger-B-W5Fa_X.js → CodemationPluginListMerger-D1B1IEbt.js} +1 -1
- package/dist/{CodemationPluginListMerger-B-W5Fa_X.js.map → CodemationPluginListMerger-D1B1IEbt.js.map} +1 -1
- package/dist/{CodemationPluginListMerger-DGc-jfa2.d.ts → CodemationPluginListMerger-DP7djJ9S.d.ts} +151 -19
- package/dist/CodemationTsyringeTypeInfoRegistrar-Bj6FJYFz.js +97 -0
- package/dist/CodemationTsyringeTypeInfoRegistrar-Bj6FJYFz.js.map +1 -0
- package/dist/{CodemationWhitelabelConfig-CWbcyQqn.d.ts → CodemationWhitelabelConfig-Ca2mCUeC.d.ts} +2 -2
- package/dist/{CollectionContracts.types-DdpHft0i.d.ts → CollectionContracts.types-DDyFYT_D.d.ts} +1 -1
- package/dist/{CredentialContractsRegistry-DrMIDSw8.d.ts → CredentialContractsRegistry-Bq2bq28t.d.ts} +2 -2
- package/dist/{CredentialServices-UfvHB-rN.d.ts → CredentialServices-BLloBztI.d.ts} +65 -20
- package/dist/{CredentialServices-CgxwguAv.js → CredentialServices-Dk8yypeL.js} +310 -51
- package/dist/CredentialServices-Dk8yypeL.js.map +1 -0
- package/dist/InternalHonoApiRouteRegistrar-c7t3KnV_.d.ts +17 -0
- package/dist/InternalPingRegistrar-DY3kSfxP.js +221 -0
- package/dist/InternalPingRegistrar-DY3kSfxP.js.map +1 -0
- package/dist/{ItemsInputNormalizer-C-KHg9Mo.d.ts → ItemsInputNormalizer-_RwIfRIQ.d.ts} +89 -25
- package/dist/{LogLevelPolicyFactory-CampWO0l.d.ts → LogLevelPolicyFactory-ewCHLDLn.d.ts} +2 -2
- package/dist/{PublicFrontendBootstrap-DzBgwOnG.d.ts → PublicFrontendBootstrap-Cev3qK46.d.ts} +9 -2
- package/dist/PublicFrontendBootstrapFactory-Dv04tJ-6.d.ts +82 -0
- package/dist/{PublicFrontendBootstrapJsonCodec-Cl_DLRh0.d.ts → PublicFrontendBootstrapJsonCodec-CXG9Dxft.d.ts} +3 -3
- package/dist/{PublicFrontendBootstrapJsonCodec-DzqvA0uo.js → PublicFrontendBootstrapJsonCodec-CegIF_ne.js} +7 -2
- package/dist/PublicFrontendBootstrapJsonCodec-CegIF_ne.js.map +1 -0
- package/dist/ServerLoggerFactory-Ckk52S3w.js +223 -0
- package/dist/ServerLoggerFactory-Ckk52S3w.js.map +1 -0
- package/dist/{TelemetryContracts-DbaNomrH.d.ts → TelemetryContracts-BtDx84Cp.d.ts} +13 -4
- package/dist/{WorkflowPolicyUiPresentationFactory-DQEY-h_S.d.ts → WorkflowPolicyUiPresentationFactory-6MyjCvBO.d.ts} +2 -2
- package/dist/{WorkflowPolicyUiPresentationFactory-DhPqQ9aB.js → WorkflowPolicyUiPresentationFactory-Bb-ae_Zh.js} +1 -1
- package/dist/{WorkflowPolicyUiPresentationFactory-DhPqQ9aB.js.map → WorkflowPolicyUiPresentationFactory-Bb-ae_Zh.js.map} +1 -1
- package/dist/{WorkflowViewContracts-CzK2KFuz.d.ts → WorkflowViewContracts-B7aFQcIw.d.ts} +10 -1
- package/dist/authoring.d.ts +5 -5
- package/dist/authoring.js +1 -1
- package/dist/client.d.ts +4 -4
- package/dist/client.js +2 -2
- package/dist/consumer.d.ts +6 -6
- package/dist/consumer.js +2 -2
- package/dist/credentials.d.ts +6 -6
- package/dist/credentials.js +1 -1
- package/dist/devServerSidecar.d.ts +2 -2
- package/dist/devServerSidecar.js +1 -94
- package/dist/devServerSidecar.js.map +1 -1
- package/dist/dto.d.ts +6 -6
- package/dist/{index-BbBk26m0.d.ts → index-DilAYwnH.d.ts} +49 -3
- package/dist/index.d.ts +141 -21
- package/dist/index.js +109 -14
- package/dist/index.js.map +1 -0
- package/dist/mapping.d.ts +2 -2
- package/dist/mapping.js +1 -1
- package/dist/nextServer.d.ts +42 -113
- package/dist/nextServer.js +9 -7
- package/dist/pairing.d.ts +93 -0
- package/dist/pairing.js +5 -0
- package/dist/pairing.types-snfZ_OzB.d.ts +19 -0
- package/dist/persistenceServer-B71RGvSj.d.ts +30 -0
- package/dist/{persistenceServer-CmsIKnO9.js → persistenceServer-C-hH4z6l.js} +2 -2
- package/dist/{persistenceServer-CmsIKnO9.js.map → persistenceServer-C-hH4z6l.js.map} +1 -1
- package/dist/persistenceServer.d.ts +8 -8
- package/dist/persistenceServer.js +3 -3
- package/dist/{server-MUNGsBYK.d.ts → server-09PKasWR.d.ts} +21 -6
- package/dist/{server-CJFfY67o.js → server-vtRCPgRJ.js} +7 -6
- package/dist/{server-CJFfY67o.js.map → server-vtRCPgRJ.js.map} +1 -1
- package/dist/server.d.ts +14 -14
- package/dist/server.js +13 -11
- package/package.json +47 -58
- package/prisma/migrations/20260519000000_workflow_audit_log/migration.sql +23 -0
- package/prisma/migrations/20260519100000_storage_growth_fixes/migration.sql +61 -0
- package/prisma/migrations.sqlite/20260519000000_workflow_audit_log/migration.sql +21 -0
- package/prisma/migrations.sqlite/20260519100000_storage_growth_fixes/migration.sql +29 -0
- package/prisma/schema.postgresql.prisma +55 -17
- package/prisma/schema.sqlite.prisma +55 -17
- package/prisma-generated/prisma-postgresql-client/edge.js +33 -5
- package/prisma-generated/prisma-postgresql-client/index-browser.js +29 -1
- package/prisma-generated/prisma-postgresql-client/index.d.ts +8933 -5716
- package/prisma-generated/prisma-postgresql-client/index.js +33 -5
- package/prisma-generated/prisma-postgresql-client/package.json +1 -1
- package/prisma-generated/prisma-postgresql-client/schema.prisma +38 -0
- package/prisma-generated/prisma-sqlite-client/edge.js +33 -5
- package/prisma-generated/prisma-sqlite-client/index-browser.js +29 -1
- package/prisma-generated/prisma-sqlite-client/index.d.ts +8925 -5713
- package/prisma-generated/prisma-sqlite-client/index.js +33 -5
- package/prisma-generated/prisma-sqlite-client/package.json +1 -1
- package/prisma-generated/prisma-sqlite-client/schema.prisma +38 -0
- package/scripts/check-collections.mjs +18 -0
- package/scripts/generate-prisma-clients.mjs +20 -11
- package/src/application/WorkflowAuditLogPruneScheduler.ts +96 -0
- package/src/application/auth/AuthenticatedPrincipal.ts +4 -0
- package/src/application/commands/StartWorkflowRunCommandHandler.ts +4 -0
- package/src/application/contracts/WorkflowViewContracts.ts +6 -0
- package/src/application/contracts/WorkflowWebsocketMessage.ts +3 -1
- package/src/application/mapping/WorkflowDefinitionMapper.ts +40 -1
- package/src/application/runs/WorkflowRunRetentionPruneScheduler.ts +7 -1
- package/src/application/telemetry/OtelExecutionTelemetry.types.ts +5 -0
- package/src/application/telemetry/OtelExecutionTelemetryFactory.ts +4 -0
- package/src/application/telemetry/StoredTelemetrySpanScope.ts +6 -2
- package/src/application/telemetry/TelemetryRetentionTimestampFactory.ts +27 -17
- package/src/application/telemetry/TelemetrySpanPublisher.ts +11 -0
- package/src/application/websocket/TelemetrySpanWebsocketRelay.ts +31 -0
- package/src/applicationTokens.ts +20 -1
- package/src/audit/IAuditEmitter.ts +32 -0
- package/src/audit/PrismaWorkflowAuditLogRepository.ts +34 -0
- package/src/audit/WorkflowAuditLogWriter.ts +125 -0
- package/src/auth/managed/ManagedAuthConfig.ts +29 -0
- package/src/auth/managed/ManagedAuthMiddleware.ts +52 -0
- package/src/auth/managed/ManagedCorsMiddleware.ts +43 -0
- package/src/auth/managed/ManagedModeBootGuard.ts +27 -0
- package/src/auth/managed/index.ts +5 -0
- package/src/bootstrap/AppContainerFactory.ts +295 -29
- package/src/bootstrap/AppContainerLifecycle.ts +31 -0
- package/src/bootstrap/perf/BootTimer.ts +168 -0
- package/src/bootstrap/runtime/AppConfigFactory.ts +21 -65
- package/src/bootstrap/runtime/FrontendRuntime.ts +4 -1
- package/src/bootstrap/runtime/HeadlessApiRuntime.ts +47 -0
- package/src/bootstrap/runtime/WorkerRuntime.ts +2 -1
- package/src/credentials/BrokerClient.ts +49 -0
- package/src/credentials/BrokerRefreshError.ts +12 -0
- package/src/credentials/BrokerRefreshInvalidGrantError.ts +13 -0
- package/src/credentials/ControlPlaneCatalogFetcher.ts +261 -0
- package/src/credentials/CredentialOAuth2MaterialReader.ts +136 -0
- package/src/credentials/InternalCredentialsListRegistrar.ts +48 -0
- package/src/credentials/InternalCredentialsPushRegistrar.ts +125 -0
- package/src/credentials/LocalOAuthFlowExecutor.ts +316 -0
- package/src/credentials/ManagedOAuthFlowExecutor.ts +94 -0
- package/src/credentials/ManagedOAuthRefreshInvalidGrantError.ts +13 -0
- package/src/credentials/catalogTypes.ts +4 -0
- package/src/credentials/refresh/CredentialDisconnectedError.ts +11 -0
- package/src/domain/credentials/CredentialBindingService.ts +54 -2
- package/src/domain/credentials/CredentialKeyRotatedError.ts +22 -0
- package/src/domain/credentials/CredentialSecretCipher.ts +68 -6
- package/src/domain/credentials/CredentialTypeRegistryImpl.ts +117 -10
- package/src/domain/credentials/OAuth2RedirectUriResolver.ts +79 -0
- package/src/domain/credentials/WorkflowCredentialNodeResolver.ts +14 -5
- package/src/domain/telemetry/TelemetryContracts.ts +7 -1
- package/src/domain/workflows/WorkflowActivationPreflight.ts +24 -1
- package/src/domain/workflows/WorkflowActivationPreflightRules.ts +40 -1
- package/src/index.ts +9 -0
- package/src/infrastructure/binary/LocalFilesystemBinaryStorageRegistry.ts +29 -1
- package/src/infrastructure/binary/S3BinaryStorage.ts +169 -0
- package/src/infrastructure/binary/S3BinaryStorageConfig.ts +17 -0
- package/src/infrastructure/config/CodemationPluginRegistrar.ts +3 -1
- package/src/infrastructure/persistence/CodemationDatabaseUrlParser.ts +41 -0
- package/src/infrastructure/persistence/InMemoryTelemetryArtifactStore.ts +8 -3
- package/src/infrastructure/persistence/PrismaMigrationDeployer.ts +21 -13
- package/src/infrastructure/persistence/PrismaTelemetryArtifactStore.ts +43 -8
- package/src/infrastructure/persistence/PrismaWorkflowRunRepository.ts +26 -3
- package/src/infrastructure/persistence/PrismaWorkflowSnapshotRepository.ts +48 -0
- package/src/mcp/AgentMcpIntegrationImpl.ts +344 -0
- package/src/mcp/McpClientFactory.ts +29 -0
- package/src/mcp/McpConnectionPool.ts +184 -0
- package/src/mcp/McpConnectionPool.types.ts +12 -0
- package/src/mcp/McpServerCatalog.ts +104 -0
- package/src/mcp/index.ts +5 -0
- package/src/pairing/HmacRequestSigner.ts +32 -0
- package/src/pairing/IncomingHmacVerifier.ts +82 -0
- package/src/pairing/InternalHmacAuthMiddleware.ts +33 -0
- package/src/pairing/InternalPingRegistrar.ts +25 -0
- package/src/pairing/PairedFetch.ts +33 -0
- package/src/pairing/PairingConfigFactory.ts +35 -0
- package/src/pairing/PairingConfigToken.ts +6 -0
- package/src/pairing/index.ts +14 -0
- package/src/pairing/pairing.types.ts +18 -0
- package/src/pairing.ts +17 -0
- package/src/persistenceServer.ts +1 -0
- package/src/presentation/config/AppConfig.ts +7 -1
- package/src/presentation/config/CodemationAuthConfig.ts +1 -1
- package/src/presentation/config/CodemationAuthoring.types.ts +60 -5
- package/src/presentation/config/CodemationConfig.ts +9 -0
- package/src/presentation/config/CodemationConfigNormalizer.ts +39 -1
- package/src/presentation/config/CodemationPlugin.ts +2 -1
- package/src/presentation/frontend/CodemationFrontendAuthSnapshot.ts +5 -0
- package/src/presentation/frontend/CodemationFrontendAuthSnapshotFactory.ts +7 -1
- package/src/presentation/frontend/PublicFrontendBootstrap.ts +2 -0
- package/src/presentation/frontend/PublicFrontendBootstrapFactory.ts +5 -1
- package/src/presentation/frontend/PublicFrontendBootstrapJsonCodec.ts +4 -1
- package/src/presentation/http/ApiPaths.ts +4 -4
- package/src/presentation/http/HeadlessHttpServerFactory.ts +56 -0
- package/src/presentation/http/ServerHttpErrorResponseFactory.ts +39 -2
- package/src/presentation/http/hono/CodemationHonoApiAppFactory.ts +33 -8
- package/src/presentation/http/hono/InternalHonoApiRouteRegistrar.ts +12 -0
- package/src/presentation/http/hono/registrars/ManagedMeHonoApiRouteRegistrar.ts +35 -0
- package/src/presentation/http/hono/registrars/OAuth2HonoApiRouteRegistrar.ts +2 -2
- package/src/presentation/http/routeHandlers/CredentialHttpRouteHandler.ts +28 -0
- package/src/presentation/http/routeHandlers/OAuth2HttpRouteHandlerFactory.ts +98 -41
- package/src/presentation/server/CodemationConsumerConfigLoader.ts +59 -7
- package/src/presentation/server/CodemationPluginDiscovery.ts +5 -0
- package/src/presentation/server/WorkflowDefinitionExportsResolver.ts +18 -0
- package/src/presentation/server/WorkflowModulePathFinder.ts +12 -1
- package/src/presentation/websocket/ManagedWebsocketAuthenticator.ts +50 -0
- package/src/presentation/websocket/WebsocketAuthenticator.types.ts +12 -0
- package/src/presentation/websocket/WorkflowWebsocketServer.ts +24 -3
- package/src/presentation/websocket/WorkflowWebsocketServerFactory.ts +16 -0
- package/src/process/ExecaProcessRunner.ts +41 -0
- package/src/process/ProcessRunner.types.ts +39 -0
- package/src/server.ts +2 -0
- package/src/workflows/InternalWorkflowActivationRegistrar.ts +42 -0
- package/src/workflows/InternalWorkflowDetailRegistrar.ts +33 -0
- package/src/workflows/InternalWorkflowTestRunRegistrar.ts +91 -0
- package/src/workflows/InternalWorkflowsListRegistrar.ts +28 -0
- package/src/workflows/discovery/WorkflowDirectoryDiscoverer.ts +79 -0
- package/tsconfig.json +2 -0
- package/vitest.shared.ts +5 -0
- package/dist/ApiPaths-CLTHphYZ.js.map +0 -1
- package/dist/AppConfigFactory-C6q-CSKb.js.map +0 -1
- package/dist/AppContainerFactory-qaqc-R1D.js.map +0 -1
- package/dist/CodemationAuthoring.types-DZl-sJaM.js.map +0 -1
- package/dist/CodemationConsumerConfigLoader-BeAUS144.js.map +0 -1
- package/dist/CredentialServices-CgxwguAv.js.map +0 -1
- package/dist/PublicFrontendBootstrapFactory-Cb2pLmDd.d.ts +0 -45
- package/dist/PublicFrontendBootstrapJsonCodec-DzqvA0uo.js.map +0 -1
- package/dist/ServerLoggerFactory-BKSIh9Xv.js +0 -98
- package/dist/ServerLoggerFactory-BKSIh9Xv.js.map +0 -1
- package/dist/persistenceServer-vtJAGDat.d.ts +0 -9
- package/src/domain/credentials/OAuth2ConnectServiceFactory.ts +0 -411
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { createHmac, createHash, randomBytes } from "node:crypto";
|
|
2
|
+
import { inject, injectable } from "@codemation/core";
|
|
3
|
+
import type { PairingConfig } from "./pairing.types";
|
|
4
|
+
import { PairingConfigToken } from "./PairingConfigToken";
|
|
5
|
+
|
|
6
|
+
export interface SignedHeaders {
|
|
7
|
+
readonly Authorization: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
@injectable()
|
|
11
|
+
export class HmacRequestSigner {
|
|
12
|
+
constructor(@inject(PairingConfigToken) private readonly config: PairingConfig) {}
|
|
13
|
+
|
|
14
|
+
sign(method: string, urlOrPath: string, body: string): SignedHeaders {
|
|
15
|
+
const ts = Math.floor(Date.now() / 1000);
|
|
16
|
+
const nonce = randomBytes(16).toString("base64");
|
|
17
|
+
|
|
18
|
+
const parsed = new URL(urlOrPath, "http://placeholder");
|
|
19
|
+
const path = (parsed.pathname + parsed.search).toLowerCase();
|
|
20
|
+
|
|
21
|
+
const bodyHash = createHash("sha256").update(body, "utf8").digest("hex");
|
|
22
|
+
const baseString = [method.toUpperCase(), path, ts, nonce, bodyHash].join("\n");
|
|
23
|
+
|
|
24
|
+
// eslint-disable-next-line codemation/no-buffer-everything -- pairing secret is 32 bytes, never large
|
|
25
|
+
const secretBytes = Buffer.from(this.config.pairingSecret, "base64");
|
|
26
|
+
const sig = createHmac("sha256", secretBytes).update(baseString, "utf8").digest("base64");
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
Authorization: `Codemation-Hmac v=1,workspaceId=${this.config.workspaceId},ts=${ts},nonce=${nonce},sig=${sig}`,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { createHmac, createHash, timingSafeEqual } from "node:crypto";
|
|
2
|
+
import { inject, injectable } from "@codemation/core";
|
|
3
|
+
import type { PairingConfig, PairingVerificationResult } from "./pairing.types";
|
|
4
|
+
import { PairingConfigToken } from "./PairingConfigToken";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Verifies incoming HMAC-signed requests from the control plane.
|
|
8
|
+
* Mirrors the control-plane HmacVerifier — both sides follow docs/pairing-protocol.md.
|
|
9
|
+
*/
|
|
10
|
+
@injectable()
|
|
11
|
+
export class IncomingHmacVerifier {
|
|
12
|
+
private readonly usedNonces = new Map<string, number>();
|
|
13
|
+
private readonly nonceTtlSeconds = 600; // 10 minutes
|
|
14
|
+
|
|
15
|
+
constructor(@inject(PairingConfigToken) private readonly config: PairingConfig) {}
|
|
16
|
+
|
|
17
|
+
verify(method: string, url: string, body: string, authHeader: string | null): PairingVerificationResult {
|
|
18
|
+
if (!this.config.pairingSecret || this.config.pairingSecret.trim().length === 0) {
|
|
19
|
+
throw new Error("IncomingHmacVerifier: pairingSecret is not configured — cannot verify HMAC requests.");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (!authHeader?.startsWith("Codemation-Hmac ")) {
|
|
23
|
+
return { failure: "missing" };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const parts = this.parseHeader(authHeader);
|
|
27
|
+
if (!parts) return { failure: "missing" };
|
|
28
|
+
if (parts.v !== "1") return { failure: "version" };
|
|
29
|
+
|
|
30
|
+
const nowSec = Math.floor(Date.now() / 1000);
|
|
31
|
+
if (Math.abs(nowSec - parts.ts) > 300) return { failure: "expired" };
|
|
32
|
+
|
|
33
|
+
if (parts.workspaceId !== this.config.workspaceId) return { failure: "workspace" };
|
|
34
|
+
|
|
35
|
+
const parsed = new URL(url, "http://placeholder");
|
|
36
|
+
const path = (parsed.pathname + parsed.search).toLowerCase();
|
|
37
|
+
const bodyHash = createHash("sha256").update(body, "utf8").digest("hex");
|
|
38
|
+
const baseString = [method.toUpperCase(), path, parts.ts, parts.nonce, bodyHash].join("\n");
|
|
39
|
+
|
|
40
|
+
// eslint-disable-next-line codemation/no-buffer-everything -- pairing secret is 32 bytes, never large
|
|
41
|
+
const secretBytes = Buffer.from(this.config.pairingSecret, "base64");
|
|
42
|
+
const expected = createHmac("sha256", secretBytes).update(baseString, "utf8").digest("base64");
|
|
43
|
+
|
|
44
|
+
const expectedBuf = Buffer.from(expected);
|
|
45
|
+
const actualBuf = Buffer.from(parts.sig);
|
|
46
|
+
if (expectedBuf.length !== actualBuf.length || !timingSafeEqual(expectedBuf, actualBuf)) {
|
|
47
|
+
return { failure: "signature" };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
this.pruneExpiredNonces(nowSec);
|
|
51
|
+
const nonceKey = `${parts.workspaceId}:${parts.nonce}`;
|
|
52
|
+
if (this.usedNonces.has(nonceKey)) return { failure: "replay" };
|
|
53
|
+
this.usedNonces.set(nonceKey, nowSec + this.nonceTtlSeconds);
|
|
54
|
+
|
|
55
|
+
return { workspaceId: parts.workspaceId };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
private parseHeader(header: string): {
|
|
59
|
+
v: string;
|
|
60
|
+
workspaceId: string;
|
|
61
|
+
ts: number;
|
|
62
|
+
nonce: string;
|
|
63
|
+
sig: string;
|
|
64
|
+
} | null {
|
|
65
|
+
const payload = header.slice("Codemation-Hmac ".length);
|
|
66
|
+
const fields: Record<string, string> = {};
|
|
67
|
+
for (const part of payload.split(",")) {
|
|
68
|
+
const eq = part.indexOf("=");
|
|
69
|
+
if (eq === -1) return null;
|
|
70
|
+
fields[part.slice(0, eq).trim()] = part.slice(eq + 1).trim();
|
|
71
|
+
}
|
|
72
|
+
const { v, workspaceId, ts, nonce, sig } = fields;
|
|
73
|
+
if (!v || !workspaceId || !ts || !nonce || !sig) return null;
|
|
74
|
+
return { v, workspaceId, ts: Number(ts), nonce, sig };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
private pruneExpiredNonces(nowSec: number): void {
|
|
78
|
+
for (const [key, expiry] of this.usedNonces.entries()) {
|
|
79
|
+
if (expiry <= nowSec) this.usedNonces.delete(key);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { inject, injectable } from "@codemation/core";
|
|
2
|
+
import type { Context, MiddlewareHandler, Next } from "hono";
|
|
3
|
+
import { IncomingHmacVerifier } from "./IncomingHmacVerifier";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Hono middleware that verifies HMAC-signed requests on /internal/* routes.
|
|
7
|
+
* Rejects with 401 on any auth failure (failure mode is never leaked).
|
|
8
|
+
*
|
|
9
|
+
* Downstream handlers read the consumed body from `c.get("body")` when needed —
|
|
10
|
+
* do NOT call `c.req.text()` again after this middleware runs.
|
|
11
|
+
*/
|
|
12
|
+
@injectable()
|
|
13
|
+
export class InternalHmacAuthMiddleware {
|
|
14
|
+
constructor(@inject(IncomingHmacVerifier) private readonly verifier: IncomingHmacVerifier) {}
|
|
15
|
+
|
|
16
|
+
handle(): MiddlewareHandler {
|
|
17
|
+
return async (c: Context, next: Next) => {
|
|
18
|
+
const body = c.req.method === "GET" || c.req.method === "HEAD" ? "" : await c.req.text();
|
|
19
|
+
|
|
20
|
+
const result = this.verifier.verify(c.req.method, c.req.url, body, c.req.header("authorization") ?? null);
|
|
21
|
+
|
|
22
|
+
if ("failure" in result) {
|
|
23
|
+
return c.json({ error: "Unauthorized" }, 401);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Body stored for downstream handlers that need it (e.g., credential push).
|
|
27
|
+
// Access via c.get("body") — do NOT call c.req.text() again.
|
|
28
|
+
// workspaceId is available from PairingConfig since installation has a single workspace.
|
|
29
|
+
c.set("body" as never, body);
|
|
30
|
+
await next();
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { inject, injectable } from "@codemation/core";
|
|
2
|
+
import type { Hono } from "hono";
|
|
3
|
+
import { InternalHmacAuthMiddleware } from "./InternalHmacAuthMiddleware";
|
|
4
|
+
import type { InternalHonoApiRouteRegistrar } from "../presentation/http/hono/InternalHonoApiRouteRegistrar";
|
|
5
|
+
import type { PairingConfig } from "./pairing.types";
|
|
6
|
+
import { PairingConfigToken } from "./PairingConfigToken";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Registers GET /internal/ping — a smoke-test endpoint for verifying workspace pairing.
|
|
10
|
+
* Returns { pong: true, workspaceId } when the HMAC signature validates correctly.
|
|
11
|
+
*/
|
|
12
|
+
@injectable()
|
|
13
|
+
export class InternalPingRegistrar implements InternalHonoApiRouteRegistrar {
|
|
14
|
+
constructor(
|
|
15
|
+
@inject(InternalHmacAuthMiddleware) private readonly hmacMiddleware: InternalHmacAuthMiddleware,
|
|
16
|
+
@inject(PairingConfigToken) private readonly pairingConfig: PairingConfig,
|
|
17
|
+
) {}
|
|
18
|
+
|
|
19
|
+
register(app: Hono): void {
|
|
20
|
+
const { workspaceId } = this.pairingConfig;
|
|
21
|
+
app.get("/internal/ping", this.hmacMiddleware.handle(), (c) => {
|
|
22
|
+
return c.json({ pong: true, workspaceId });
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { inject, injectable } from "@codemation/core";
|
|
2
|
+
import { HmacRequestSigner } from "./HmacRequestSigner";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Thin fetch wrapper that automatically HMAC-signs outgoing requests
|
|
6
|
+
* to the control plane using the workspace's pairing secret.
|
|
7
|
+
*
|
|
8
|
+
* Use this for any server-to-server request from the installation to the CP.
|
|
9
|
+
*/
|
|
10
|
+
@injectable()
|
|
11
|
+
export class PairedFetch {
|
|
12
|
+
constructor(@inject(HmacRequestSigner) private readonly signer: HmacRequestSigner) {}
|
|
13
|
+
|
|
14
|
+
async get(url: string): Promise<Response> {
|
|
15
|
+
const headers = this.signer.sign("GET", url, "");
|
|
16
|
+
return fetch(url, { method: "GET", headers: { ...headers } });
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async post(url: string, body: unknown): Promise<Response> {
|
|
20
|
+
const bodyString = JSON.stringify(body);
|
|
21
|
+
const headers = this.signer.sign("POST", url, bodyString);
|
|
22
|
+
return fetch(url, {
|
|
23
|
+
method: "POST",
|
|
24
|
+
headers: { ...headers, "Content-Type": "application/json" },
|
|
25
|
+
body: bodyString,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async delete(url: string): Promise<Response> {
|
|
30
|
+
const headers = this.signer.sign("DELETE", url, "");
|
|
31
|
+
return fetch(url, { method: "DELETE", headers: { ...headers } });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { PairingConfig } from "./pairing.types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Reads pairing configuration from environment variables.
|
|
5
|
+
*
|
|
6
|
+
* Required env vars when pairing is enabled:
|
|
7
|
+
* WORKSPACE_ID — the workspace's database ID
|
|
8
|
+
* WORKSPACE_PAIRING_SECRET — base64-encoded 32-byte shared secret
|
|
9
|
+
* CONTROL_PLANE_URL — base URL of the control plane API
|
|
10
|
+
*
|
|
11
|
+
* Returns null if any required variable is absent (pairing disabled).
|
|
12
|
+
* See docs/pairing-protocol.md for full bootstrap instructions.
|
|
13
|
+
*/
|
|
14
|
+
export class PairingConfigFactory {
|
|
15
|
+
create(env: Readonly<NodeJS.ProcessEnv>): PairingConfig | null {
|
|
16
|
+
const workspaceId = env["WORKSPACE_ID"];
|
|
17
|
+
const pairingSecret = env["WORKSPACE_PAIRING_SECRET"];
|
|
18
|
+
const controlPlaneUrl = env["CONTROL_PLANE_URL"];
|
|
19
|
+
|
|
20
|
+
if (!workspaceId || !pairingSecret || !controlPlaneUrl) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// eslint-disable-next-line codemation/no-buffer-everything -- pairing secret is always 32 bytes; bounded by validation below.
|
|
25
|
+
const decoded = Buffer.from(pairingSecret, "base64");
|
|
26
|
+
if (decoded.length !== 32) {
|
|
27
|
+
throw new Error(
|
|
28
|
+
`WORKSPACE_PAIRING_SECRET must be a base64-encoded 32-byte value (got ${decoded.length} bytes). ` +
|
|
29
|
+
`Generate a valid secret with: openssl rand -base64 32`,
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return { workspaceId, pairingSecret, controlPlaneUrl };
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { TypeToken } from "@codemation/core";
|
|
2
|
+
import type { PairingConfig } from "./pairing.types";
|
|
3
|
+
|
|
4
|
+
// Symbol token so the DI container can inject PairingConfig.
|
|
5
|
+
// Registered by PairingConfigFactory in the composition root.
|
|
6
|
+
export const PairingConfigToken = Symbol.for("codemation.pairing.PairingConfig") as TypeToken<PairingConfig>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export { HmacRequestSigner } from "./HmacRequestSigner";
|
|
2
|
+
export type { SignedHeaders } from "./HmacRequestSigner";
|
|
3
|
+
export { PairedFetch } from "./PairedFetch";
|
|
4
|
+
export { IncomingHmacVerifier } from "./IncomingHmacVerifier";
|
|
5
|
+
export { InternalHmacAuthMiddleware } from "./InternalHmacAuthMiddleware";
|
|
6
|
+
export { InternalPingRegistrar } from "./InternalPingRegistrar";
|
|
7
|
+
export { PairingConfigFactory } from "./PairingConfigFactory";
|
|
8
|
+
export { PairingConfigToken } from "./PairingConfigToken";
|
|
9
|
+
export type {
|
|
10
|
+
PairingConfig,
|
|
11
|
+
PairingVerificationResult,
|
|
12
|
+
PairingVerificationFailure,
|
|
13
|
+
PairingVerificationSuccess,
|
|
14
|
+
} from "./pairing.types";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface PairingConfig {
|
|
2
|
+
/** The workspace's database ID. */
|
|
3
|
+
readonly workspaceId: string;
|
|
4
|
+
/** Base64-encoded 32-byte raw secret shared with the control plane. */
|
|
5
|
+
readonly pairingSecret: string;
|
|
6
|
+
/** Base URL of the control plane API, e.g. https://api.codemation.io */
|
|
7
|
+
readonly controlPlaneUrl: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type PairingVerificationFailure = {
|
|
11
|
+
readonly failure: "missing" | "version" | "expired" | "workspace" | "signature" | "replay";
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type PairingVerificationSuccess = {
|
|
15
|
+
readonly workspaceId: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type PairingVerificationResult = PairingVerificationSuccess | PairingVerificationFailure;
|
package/src/pairing.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Subpath entry point: @codemation/host/pairing
|
|
2
|
+
export {
|
|
3
|
+
HmacRequestSigner,
|
|
4
|
+
PairedFetch,
|
|
5
|
+
IncomingHmacVerifier,
|
|
6
|
+
InternalHmacAuthMiddleware,
|
|
7
|
+
InternalPingRegistrar,
|
|
8
|
+
PairingConfigFactory,
|
|
9
|
+
PairingConfigToken,
|
|
10
|
+
} from "./pairing/index";
|
|
11
|
+
export type {
|
|
12
|
+
PairingConfig,
|
|
13
|
+
PairingVerificationResult,
|
|
14
|
+
PairingVerificationFailure,
|
|
15
|
+
PairingVerificationSuccess,
|
|
16
|
+
SignedHeaders,
|
|
17
|
+
} from "./pairing/index";
|
package/src/persistenceServer.ts
CHANGED
|
@@ -2,4 +2,5 @@ export { CodemationPostgresPrismaClientFactory } from "./infrastructure/persiste
|
|
|
2
2
|
export type { AppPersistenceConfig } from "./presentation/config/AppConfig";
|
|
3
3
|
export { AppConfigFactory } from "./bootstrap/runtime/AppConfigFactory";
|
|
4
4
|
export { PrismaMigrationDeployer } from "./infrastructure/persistence/PrismaMigrationDeployer";
|
|
5
|
+
export { CodemationDatabaseUrlParser } from "./infrastructure/persistence/CodemationDatabaseUrlParser";
|
|
5
6
|
export type { PrismaDatabaseClient as PrismaClient } from "./infrastructure/persistence/PrismaDatabaseClient";
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
AnyCredentialType,
|
|
3
|
+
CollectionDefinition,
|
|
4
|
+
McpServerDeclaration,
|
|
5
|
+
WorkflowDefinition,
|
|
6
|
+
} from "@codemation/core";
|
|
2
7
|
import type { CodemationContainerRegistration } from "../../bootstrap/CodemationContainerRegistration";
|
|
3
8
|
import type { CodemationPlugin } from "./CodemationPlugin";
|
|
4
9
|
import type {
|
|
@@ -32,6 +37,7 @@ export interface AppConfig {
|
|
|
32
37
|
readonly collections: ReadonlyArray<CollectionDefinition>;
|
|
33
38
|
readonly plugins: ReadonlyArray<CodemationPlugin>;
|
|
34
39
|
readonly pluginLoadSummary?: ReadonlyArray<AppPluginLoadSummary>;
|
|
40
|
+
readonly mcpServers: ReadonlyArray<McpServerDeclaration>;
|
|
35
41
|
readonly hasConfiguredCredentialSessionServiceRegistration: boolean;
|
|
36
42
|
readonly log?: CodemationLogConfig;
|
|
37
43
|
readonly engineExecutionLimits?: CodemationEngineExecutionLimitsConfig;
|
|
@@ -4,7 +4,7 @@ import type { BetterAuthOptions } from "better-auth";
|
|
|
4
4
|
* Consumer-declared authentication profile for the hosted UI + HTTP API.
|
|
5
5
|
* Social provider ids intentionally match Better Auth's provider ids so config stays 1:1 with the auth runtime.
|
|
6
6
|
*/
|
|
7
|
-
export type CodemationAuthKind = "local" | "oauth" | "oidc";
|
|
7
|
+
export type CodemationAuthKind = "local" | "oauth" | "oidc" | "managed";
|
|
8
8
|
|
|
9
9
|
export type CodemationAuthOAuthProviderId = Extract<
|
|
10
10
|
keyof NonNullable<BetterAuthOptions["socialProviders"]>,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AnyCredentialType, DefinedCollection, DefinedNode } from "@codemation/core";
|
|
1
|
+
import type { AnyCredentialType, DefinedCollection, DefinedNode, McpServerDeclaration } from "@codemation/core";
|
|
2
2
|
import type { CodemationAppContext } from "./CodemationAppContext";
|
|
3
3
|
import type {
|
|
4
4
|
CodemationAppDefinition,
|
|
@@ -12,14 +12,20 @@ import type { CodemationWhitelabelConfig } from "./CodemationWhitelabelConfig";
|
|
|
12
12
|
export interface FriendlyCodemationDatabaseConfig {
|
|
13
13
|
readonly kind: "postgresql" | "sqlite";
|
|
14
14
|
readonly url?: string;
|
|
15
|
+
/** Name of an environment variable whose value is the PostgreSQL connection URL. Co-exclusive with `url`. */
|
|
16
|
+
readonly urlEnv?: string;
|
|
15
17
|
readonly filePath?: string;
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
export interface FriendlyCodemationExecutionConfig {
|
|
19
21
|
readonly mode?: "inline" | "queue";
|
|
22
|
+
/** Name of an environment variable whose value is "inline" or "queue". Co-exclusive with `mode`. */
|
|
23
|
+
readonly modeEnv?: string;
|
|
20
24
|
readonly queuePrefix?: string;
|
|
21
25
|
readonly workerQueues?: ReadonlyArray<string>;
|
|
22
26
|
readonly redisUrl?: string;
|
|
27
|
+
/** Name of an environment variable whose value is the Redis connection URL. Co-exclusive with `redisUrl`. */
|
|
28
|
+
readonly redisUrlEnv?: string;
|
|
23
29
|
}
|
|
24
30
|
|
|
25
31
|
export interface DefineCodemationAppOptions extends Omit<
|
|
@@ -27,6 +33,11 @@ export interface DefineCodemationAppOptions extends Omit<
|
|
|
27
33
|
"app" | "credentialTypes" | "register" | "whitelabel" | "auth" | "collections"
|
|
28
34
|
> {
|
|
29
35
|
readonly name?: string;
|
|
36
|
+
/**
|
|
37
|
+
* Reserved compatibility-date field. Declares the Codemation framework version this workspace
|
|
38
|
+
* targets. No enforcement in 1.0.0 — presence only. See backlog/codemation-version-compatibility-gate.md.
|
|
39
|
+
*/
|
|
40
|
+
readonly codemationVersion?: string;
|
|
30
41
|
readonly auth?: CodemationConfig["auth"];
|
|
31
42
|
readonly database?: FriendlyCodemationDatabaseConfig;
|
|
32
43
|
readonly execution?: FriendlyCodemationExecutionConfig;
|
|
@@ -36,6 +47,13 @@ export interface DefineCodemationAppOptions extends Omit<
|
|
|
36
47
|
readonly credentials?: ReadonlyArray<AnyCredentialType>;
|
|
37
48
|
readonly register?: (context: CodemationAppContext) => void;
|
|
38
49
|
readonly whitelabel?: CodemationWhitelabelConfig;
|
|
50
|
+
/**
|
|
51
|
+
* Path (relative to the consumer project root) to a directory from which workflows are auto-discovered.
|
|
52
|
+
* All `*.ts` / `*.tsx` files (excluding `*.test.*` and `*.d.ts`) are imported and any exported
|
|
53
|
+
* `WorkflowDefinition` values are registered. Co-exclusive with providing this directory in
|
|
54
|
+
* `workflowDiscovery.directories`.
|
|
55
|
+
*/
|
|
56
|
+
readonly workflowsDir?: string;
|
|
39
57
|
}
|
|
40
58
|
|
|
41
59
|
export interface DefinePluginOptions {
|
|
@@ -44,6 +62,7 @@ export interface DefinePluginOptions {
|
|
|
44
62
|
readonly nodes?: ReadonlyArray<DefinedNode<string, Record<string, unknown>, unknown, unknown>>;
|
|
45
63
|
readonly collections?: ReadonlyArray<DefinedCollection>;
|
|
46
64
|
readonly credentials?: ReadonlyArray<AnyCredentialType>;
|
|
65
|
+
readonly mcpServers?: ReadonlyArray<McpServerDeclaration>;
|
|
47
66
|
readonly register?: (context: CodemationPluginContext) => void | Promise<void>;
|
|
48
67
|
readonly sandbox?: CodemationConfig;
|
|
49
68
|
}
|
|
@@ -53,13 +72,16 @@ class CodemationAuthoringConfigFactory {
|
|
|
53
72
|
const appDefinition = this.createAppDefinition(options);
|
|
54
73
|
const credentialTypes = [...(options.credentialTypes ?? []), ...(options.credentials ?? [])];
|
|
55
74
|
const register = this.composeAppRegister(options.register, options.nodes, options.collections);
|
|
56
|
-
const { workflows,
|
|
75
|
+
const { workflows, plugins, runtime, log, mcpServers, codemationVersion } = options;
|
|
76
|
+
const workflowDiscovery = this.mergeWorkflowDiscovery(options.workflowDiscovery, options.workflowsDir);
|
|
57
77
|
return {
|
|
58
78
|
workflows,
|
|
59
79
|
workflowDiscovery,
|
|
60
80
|
plugins,
|
|
61
81
|
runtime,
|
|
62
82
|
log,
|
|
83
|
+
mcpServers,
|
|
84
|
+
codemationVersion,
|
|
63
85
|
app: appDefinition,
|
|
64
86
|
credentialTypes,
|
|
65
87
|
register,
|
|
@@ -70,6 +92,7 @@ class CodemationAuthoringConfigFactory {
|
|
|
70
92
|
return {
|
|
71
93
|
pluginPackageId: options.pluginPackageId,
|
|
72
94
|
sandbox: options.sandbox,
|
|
95
|
+
mcpServers: options.mcpServers,
|
|
73
96
|
async register(context: CodemationPluginContext): Promise<void> {
|
|
74
97
|
for (const nodeDefinition of options.nodes ?? []) {
|
|
75
98
|
nodeDefinition.register(context);
|
|
@@ -112,9 +135,15 @@ class CodemationAuthoringConfigFactory {
|
|
|
112
135
|
sqliteFilePath: database.filePath,
|
|
113
136
|
};
|
|
114
137
|
}
|
|
138
|
+
if (database.url !== undefined && database.urlEnv !== undefined) {
|
|
139
|
+
throw new Error(
|
|
140
|
+
"defineCodemationApp: database.url and database.urlEnv are mutually exclusive — provide one or the other.",
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
const url = database.urlEnv !== undefined ? process.env[database.urlEnv] : database.url;
|
|
115
144
|
return {
|
|
116
145
|
kind: "postgresql",
|
|
117
|
-
url
|
|
146
|
+
url,
|
|
118
147
|
};
|
|
119
148
|
}
|
|
120
149
|
|
|
@@ -124,11 +153,37 @@ class CodemationAuthoringConfigFactory {
|
|
|
124
153
|
if (!execution) {
|
|
125
154
|
return undefined;
|
|
126
155
|
}
|
|
156
|
+
if (execution.mode !== undefined && execution.modeEnv !== undefined) {
|
|
157
|
+
throw new Error(
|
|
158
|
+
"defineCodemationApp: execution.mode and execution.modeEnv are mutually exclusive — provide one or the other.",
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
if (execution.redisUrl !== undefined && execution.redisUrlEnv !== undefined) {
|
|
162
|
+
throw new Error(
|
|
163
|
+
"defineCodemationApp: execution.redisUrl and execution.redisUrlEnv are mutually exclusive — provide one or the other.",
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
const rawMode = execution.modeEnv !== undefined ? process.env[execution.modeEnv] : execution.mode;
|
|
167
|
+
const mode = rawMode === "inline" || rawMode === "queue" ? rawMode : undefined;
|
|
168
|
+
const redisUrl = execution.redisUrlEnv !== undefined ? process.env[execution.redisUrlEnv] : execution.redisUrl;
|
|
127
169
|
return {
|
|
128
|
-
kind:
|
|
170
|
+
kind: mode,
|
|
129
171
|
queuePrefix: execution.queuePrefix,
|
|
130
172
|
workerQueues: execution.workerQueues,
|
|
131
|
-
redisUrl
|
|
173
|
+
redisUrl,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
private static mergeWorkflowDiscovery(
|
|
178
|
+
existing: CodemationConfig["workflowDiscovery"],
|
|
179
|
+
workflowsDir: string | undefined,
|
|
180
|
+
): CodemationConfig["workflowDiscovery"] {
|
|
181
|
+
if (!workflowsDir) {
|
|
182
|
+
return existing;
|
|
183
|
+
}
|
|
184
|
+
const existingDirectories = existing?.directories ?? [];
|
|
185
|
+
return {
|
|
186
|
+
directories: [...existingDirectories, workflowsDir],
|
|
132
187
|
};
|
|
133
188
|
}
|
|
134
189
|
|
|
@@ -3,6 +3,7 @@ import type {
|
|
|
3
3
|
CollectionDefinition,
|
|
4
4
|
DefinedCollection,
|
|
5
5
|
EngineExecutionLimitsPolicyConfig,
|
|
6
|
+
McpServerDeclaration,
|
|
6
7
|
WorkflowDefinition,
|
|
7
8
|
} from "@codemation/core";
|
|
8
9
|
import type { CodemationAuthConfig } from "./CodemationAuthConfig";
|
|
@@ -73,6 +74,12 @@ export interface CodemationApplicationRuntimeConfig {
|
|
|
73
74
|
|
|
74
75
|
export interface CodemationConfig {
|
|
75
76
|
readonly app?: CodemationAppDefinition;
|
|
77
|
+
/**
|
|
78
|
+
* Reserved compatibility-date field. Declares the Codemation framework version this workspace
|
|
79
|
+
* targets. Logged at INFO level on boot. No enforcement in 1.0.0.
|
|
80
|
+
* See backlog/codemation-version-compatibility-gate.md.
|
|
81
|
+
*/
|
|
82
|
+
readonly codemationVersion?: string;
|
|
76
83
|
readonly register?: (context: CodemationAppContext) => void;
|
|
77
84
|
readonly runtime?: CodemationApplicationRuntimeConfig;
|
|
78
85
|
readonly workflows?: ReadonlyArray<WorkflowDefinition>;
|
|
@@ -80,6 +87,8 @@ export interface CodemationConfig {
|
|
|
80
87
|
readonly plugins?: ReadonlyArray<CodemationPlugin>;
|
|
81
88
|
/** Consumer-defined `CredentialType` entries (see `@codemation/core`), applied when the host loads config. */
|
|
82
89
|
readonly credentialTypes?: ReadonlyArray<AnyCredentialType>;
|
|
90
|
+
/** MCP server declarations from this config file (merged with plugin and control-plane sources at startup). */
|
|
91
|
+
readonly mcpServers?: ReadonlyArray<McpServerDeclaration>;
|
|
83
92
|
/** Declared collections available at runtime via `ctx.collections`. Accepts either raw `CollectionDefinition` or `DefinedCollection` (the result of `defineCollection(...)`). */
|
|
84
93
|
readonly collections?: ReadonlyArray<CollectionDefinition | DefinedCollection>;
|
|
85
94
|
/** Optional shell whitelabel (product name, logo path). */
|
|
@@ -8,6 +8,7 @@ import type {
|
|
|
8
8
|
} from "@codemation/core";
|
|
9
9
|
import type { CodemationContainerRegistration } from "../../bootstrap/CodemationContainerRegistration";
|
|
10
10
|
import type { CodemationAppContext } from "./CodemationAppContext";
|
|
11
|
+
import type { CodemationAuthConfig } from "./CodemationAuthConfig";
|
|
11
12
|
import type { CodemationClassToken } from "./CodemationClassToken";
|
|
12
13
|
import type {
|
|
13
14
|
CodemationApplicationRuntimeConfig,
|
|
@@ -25,6 +26,9 @@ export type NormalizedCodemationConfig = Omit<CodemationConfig, "collections"> &
|
|
|
25
26
|
|
|
26
27
|
export class CodemationConfigNormalizer {
|
|
27
28
|
normalize(config: CodemationConfig): NormalizedCodemationConfig {
|
|
29
|
+
const auth = config.app?.auth ?? config.auth;
|
|
30
|
+
this.assertAuthConfig(auth);
|
|
31
|
+
this.assertManagedModeConstraints(config, auth);
|
|
28
32
|
const collected = this.collectRegistration(config);
|
|
29
33
|
const normalizedRuntime = this.normalizeRuntimeConfig(config);
|
|
30
34
|
const normalizedWorkflowDiscoveryDirectories = [
|
|
@@ -34,7 +38,7 @@ export class CodemationConfigNormalizer {
|
|
|
34
38
|
|
|
35
39
|
return {
|
|
36
40
|
...config,
|
|
37
|
-
auth
|
|
41
|
+
auth,
|
|
38
42
|
containerRegistrations: collected.containerRegistrations,
|
|
39
43
|
credentialTypes: [...(config.credentialTypes ?? []), ...collected.credentialTypes],
|
|
40
44
|
collections: [...this.unwrapCollections(config.collections), ...collected.collections],
|
|
@@ -49,6 +53,23 @@ export class CodemationConfigNormalizer {
|
|
|
49
53
|
};
|
|
50
54
|
}
|
|
51
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Enforces managed-mode invariants beyond what `assertAuthConfig` covers:
|
|
58
|
+
* managed-mode workspaces are always Postgres and always require at least one workflow source.
|
|
59
|
+
*/
|
|
60
|
+
private assertManagedModeConstraints(config: CodemationConfig, auth: CodemationAuthConfig | undefined): void {
|
|
61
|
+
if (auth?.kind !== "managed") {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const hasWorkflows = (config.workflows?.length ?? 0) > 0;
|
|
65
|
+
const hasWorkflowDiscovery = (config.workflowDiscovery?.directories?.length ?? 0) > 0;
|
|
66
|
+
if (!hasWorkflows && !hasWorkflowDiscovery) {
|
|
67
|
+
throw new Error(
|
|
68
|
+
'Managed-mode workspaces require at least one workflow source. Provide "workflows" or "workflowsDir" (which maps to workflowDiscovery.directories) in defineCodemationApp.',
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
52
73
|
private collectRegistration(config: CodemationConfig): Readonly<{
|
|
53
74
|
containerRegistrations: ReadonlyArray<CodemationContainerRegistration<unknown>>;
|
|
54
75
|
credentialTypes: ReadonlyArray<AnyCredentialType>;
|
|
@@ -187,6 +208,23 @@ export class CodemationConfigNormalizer {
|
|
|
187
208
|
};
|
|
188
209
|
}
|
|
189
210
|
|
|
211
|
+
private assertAuthConfig(authConfig: CodemationConfig["auth"]): void {
|
|
212
|
+
if (authConfig?.kind !== "managed") {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
if (authConfig.oauth && authConfig.oauth.length > 0) {
|
|
216
|
+
throw new Error('auth.kind "managed" cannot be combined with oauth providers. Remove the oauth config.');
|
|
217
|
+
}
|
|
218
|
+
if (authConfig.oidc && authConfig.oidc.length > 0) {
|
|
219
|
+
throw new Error('auth.kind "managed" cannot be combined with oidc providers. Remove the oidc config.');
|
|
220
|
+
}
|
|
221
|
+
if (authConfig.allowUnauthenticatedInDevelopment === true) {
|
|
222
|
+
throw new Error(
|
|
223
|
+
'auth.kind "managed" cannot be combined with allowUnauthenticatedInDevelopment. Remove that flag.',
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
190
228
|
private mergeWorkflows(
|
|
191
229
|
configuredWorkflows: ReadonlyArray<WorkflowDefinition>,
|
|
192
230
|
registeredWorkflows: ReadonlyArray<WorkflowDefinition>,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AnyCredentialType, Container } from "@codemation/core";
|
|
1
|
+
import type { AnyCredentialType, Container, McpServerDeclaration } from "@codemation/core";
|
|
2
2
|
import type { LoggerFactory } from "../../application/logging/Logger";
|
|
3
3
|
import type { AppConfig } from "./AppConfig";
|
|
4
4
|
import type { CodemationRegistrationContextBase } from "./CodemationAppContext";
|
|
@@ -13,6 +13,7 @@ export interface CodemationPluginContext extends CodemationRegistrationContextBa
|
|
|
13
13
|
export interface CodemationPluginConfig {
|
|
14
14
|
readonly pluginPackageId?: string;
|
|
15
15
|
readonly credentialTypes?: ReadonlyArray<AnyCredentialType>;
|
|
16
|
+
readonly mcpServers?: ReadonlyArray<McpServerDeclaration>;
|
|
16
17
|
readonly register?: (context: CodemationPluginContext) => void | Promise<void>;
|
|
17
18
|
readonly sandbox?: CodemationConfig;
|
|
18
19
|
}
|
|
@@ -11,4 +11,9 @@ export type CodemationFrontendAuthSnapshot = Readonly<{
|
|
|
11
11
|
oauthProviders: ReadonlyArray<CodemationFrontendAuthProviderSnapshot>;
|
|
12
12
|
secret: string | null;
|
|
13
13
|
uiAuthEnabled: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Present when auth.kind === "managed". Carries the CP-web origin for CORS
|
|
16
|
+
* awareness. No UI login flow is rendered in managed mode.
|
|
17
|
+
*/
|
|
18
|
+
cpWebOrigin?: string;
|
|
14
19
|
}>;
|
|
@@ -29,13 +29,19 @@ export class CodemationFrontendAuthSnapshotFactory {
|
|
|
29
29
|
uiAuthEnabled: boolean;
|
|
30
30
|
}>,
|
|
31
31
|
): CodemationFrontendAuthSnapshot {
|
|
32
|
-
|
|
32
|
+
const snapshot = {
|
|
33
33
|
config: args.authConfig,
|
|
34
34
|
credentialsEnabled: args.authConfig?.kind === "local",
|
|
35
35
|
oauthProviders: this.createOauthProviders(args.authConfig),
|
|
36
36
|
secret: this.resolveAuthSecret(args.env),
|
|
37
37
|
uiAuthEnabled: args.uiAuthEnabled,
|
|
38
38
|
};
|
|
39
|
+
const cpWebOrigin =
|
|
40
|
+
args.authConfig?.kind === "managed" ? args.env["CP_WEB_ORIGIN"]?.trim() || undefined : undefined;
|
|
41
|
+
if (cpWebOrigin) {
|
|
42
|
+
return { ...snapshot, cpWebOrigin };
|
|
43
|
+
}
|
|
44
|
+
return snapshot;
|
|
39
45
|
}
|
|
40
46
|
|
|
41
47
|
private resolveUiAuthEnabled(authConfig: CodemationAuthConfig | undefined, env: NodeJS.ProcessEnv): boolean {
|
|
@@ -9,4 +9,6 @@ export type PublicFrontendBootstrap = Readonly<{
|
|
|
9
9
|
oauthProviders: ReadonlyArray<CodemationFrontendAuthProviderSnapshot>;
|
|
10
10
|
productName: string;
|
|
11
11
|
uiAuthEnabled: boolean;
|
|
12
|
+
/** Present in managed mode — the CP web origin. No UI login is rendered when this is set. */
|
|
13
|
+
cpWebOrigin?: string;
|
|
12
14
|
}>;
|
|
@@ -12,12 +12,16 @@ export class PublicFrontendBootstrapFactory {
|
|
|
12
12
|
|
|
13
13
|
create(): PublicFrontendBootstrap {
|
|
14
14
|
const frontendAppConfig = this.frontendAppConfigFactory.create();
|
|
15
|
-
|
|
15
|
+
const bootstrap: PublicFrontendBootstrap = {
|
|
16
16
|
credentialsEnabled: frontendAppConfig.auth.credentialsEnabled,
|
|
17
17
|
logoUrl: frontendAppConfig.logoUrl,
|
|
18
18
|
oauthProviders: frontendAppConfig.auth.oauthProviders,
|
|
19
19
|
productName: frontendAppConfig.productName,
|
|
20
20
|
uiAuthEnabled: frontendAppConfig.auth.uiAuthEnabled,
|
|
21
21
|
};
|
|
22
|
+
if (frontendAppConfig.auth.cpWebOrigin) {
|
|
23
|
+
return { ...bootstrap, cpWebOrigin: frontendAppConfig.auth.cpWebOrigin };
|
|
24
|
+
}
|
|
25
|
+
return bootstrap;
|
|
22
26
|
}
|
|
23
27
|
}
|