@codemation/host 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +89 -0
- package/LICENSE +37 -1
- package/dist/{ApiPaths-Dv1dcHu_.js → ApiPaths-DCvrlIjg.js} +12 -1
- package/dist/{ApiPaths-Dv1dcHu_.js.map → ApiPaths-DCvrlIjg.js.map} +1 -1
- package/dist/{AppConfigFactory-Cx4qQvRk.js → AppConfigFactory-D4LL1aOR.js} +77 -297
- package/dist/AppConfigFactory-D4LL1aOR.js.map +1 -0
- package/dist/{AppConfigFactory-DnLoQ9Li.d.ts → AppConfigFactory-DncmwCD1.d.ts} +2918 -199
- package/dist/{AppContainerFactory-DqKYCRNP.js → AppContainerFactory-jpYXGZGe.js} +1733 -475
- package/dist/AppContainerFactory-jpYXGZGe.js.map +1 -0
- package/dist/{CodemationAppContext-CKVv9W9q.d.ts → CodemationAppContext-K51b7oXe.d.ts} +9 -3
- package/dist/{CodemationAuthoring.types-DA3G3s6d.d.ts → CodemationAuthoring.types-BXlXIl4K.d.ts} +9 -4
- package/dist/{CodemationAuthoring.types-NGkBcmmT.js → CodemationAuthoring.types-BteaR3Dc.js} +3 -2
- package/dist/CodemationAuthoring.types-BteaR3Dc.js.map +1 -0
- package/dist/{CodemationConfigNormalizer-BAKjetJ6.d.ts → CodemationConfigNormalizer-B4rDYC9h.d.ts} +3 -3
- package/dist/{CodemationConsumerConfigLoader-GYpBBvqE.js → CodemationConsumerConfigLoader-By-6tuGc.js} +3 -1
- package/dist/CodemationConsumerConfigLoader-By-6tuGc.js.map +1 -0
- package/dist/{CodemationConsumerConfigLoader-nxOqvv46.d.ts → CodemationConsumerConfigLoader-Dt4jyLx6.d.ts} +3 -2
- package/dist/{CodemationPluginListMerger-DKLAHT2b.d.ts → CodemationPluginListMerger-DS6I3Xe0.d.ts} +64 -27
- package/dist/{persistenceServer-C-hH4z6l.js → CodemationPostgresPrismaClientFactory-C7156Fe-.js} +2 -2
- package/dist/CodemationPostgresPrismaClientFactory-C7156Fe-.js.map +1 -0
- package/dist/CodemationPostgresPrismaClientFactory-CTNTPnDr.d.ts +9 -0
- package/dist/{CredentialContractsRegistry-Bq2bq28t.d.ts → CredentialContractsRegistry-Dgu-rEXi.d.ts} +16 -3
- package/dist/{CredentialServices-Be2I60Th.d.ts → CredentialServices-B3wPyp2y.d.ts} +4 -4
- package/dist/{CredentialServices-Dk8yypeL.js → CredentialServices-Bios0dM8.js} +10 -4
- package/dist/CredentialServices-Bios0dM8.js.map +1 -0
- package/dist/{InternalPingRegistrar-DY3kSfxP.js → InternalPingRegistrar-BavAAnvk.js} +19 -16
- package/dist/InternalPingRegistrar-BavAAnvk.js.map +1 -0
- package/dist/{ItemsInputNormalizer-_RwIfRIQ.d.ts → ItemsInputNormalizer-CFkfNMLt.d.ts} +1434 -1225
- package/dist/PrismaMigrationDeployer-DdEcXXVi.d.ts +14 -0
- package/dist/{PublicFrontendBootstrapFactory-CY2FS-5g.d.ts → PublicFrontendBootstrapFactory-ClEjZP74.d.ts} +2 -2
- package/dist/{PublicFrontendBootstrapJsonCodec-CXG9Dxft.d.ts → PublicFrontendBootstrapJsonCodec-HNItQ7ol.d.ts} +6 -1
- package/dist/{TelemetryContracts-BtDx84Cp.d.ts → TelemetryContracts-DpZEODQM.d.ts} +2 -2
- package/dist/{WorkflowPolicyUiPresentationFactory-6MyjCvBO.d.ts → WorkflowPolicyUiPresentationFactory-BNn2fvR_.d.ts} +2 -2
- package/dist/{WorkflowPolicyUiPresentationFactory-Bb-ae_Zh.js → WorkflowPolicyUiPresentationFactory-DfvD2VHk.js} +1 -1
- package/dist/{WorkflowPolicyUiPresentationFactory-Bb-ae_Zh.js.map → WorkflowPolicyUiPresentationFactory-DfvD2VHk.js.map} +1 -1
- package/dist/authoring.d.ts +4 -4
- package/dist/authoring.js +1 -1
- package/dist/client.d.ts +1 -1
- package/dist/client.js +1 -1
- package/dist/consumer.d.ts +5 -5
- package/dist/consumer.js +1 -1
- package/dist/credentials.d.ts +5 -5
- package/dist/credentials.js +1 -1
- package/dist/devServerSidecar.d.ts +2 -2
- package/dist/dto.d.ts +5 -5
- package/dist/{index-DilAYwnH.d.ts → index-ChIfeWzk.d.ts} +71 -28
- package/dist/index.d.ts +49 -17
- package/dist/index.js +106 -13
- package/dist/index.js.map +1 -0
- package/dist/infrastructure/persistence/PrismaMigrationOperations.d.ts +44 -0
- package/dist/infrastructure/persistence/PrismaMigrationOperations.js +302 -0
- package/dist/infrastructure/persistence/PrismaMigrationOperations.js.map +1 -0
- package/dist/mapping.d.ts +2 -2
- package/dist/mapping.js +1 -1
- package/dist/nextServer.d.ts +15 -39
- package/dist/nextServer.js +6 -6
- package/dist/pairing.d.ts +27 -8
- package/dist/pairing.js +19 -3
- package/dist/pairing.js.map +1 -0
- package/dist/{pairing.types-snfZ_OzB.d.ts → pairing.types-D9Bjn98U.d.ts} +1 -1
- package/dist/persistenceServer.d.ts +31 -7
- package/dist/persistenceServer.js +2 -2
- package/dist/{server-C4bS62rg.d.ts → server-B5trn7y4.d.ts} +5 -5
- package/dist/{server-Y7kxwtCK.js → server-BlG9qV5S.js} +5 -5
- package/dist/{server-Y7kxwtCK.js.map → server-BlG9qV5S.js.map} +1 -1
- package/dist/server.d.ts +10 -10
- package/dist/server.js +9 -9
- package/package.json +28 -25
- package/playwright.config.ts +8 -2
- package/playwright.scaffolded-dev.config.ts +8 -2
- package/prisma/migrations/20260526120000_credential_material_pointer/migration.sql +18 -0
- package/prisma/migrations/20260527120000_add_human_task/migration.sql +32 -0
- package/prisma/migrations/20260527130000_add_hitl_state_json/migration.sql +6 -0
- package/prisma/migrations/20260527130000_add_hmac_nonce/migration.sql +12 -0
- package/prisma/migrations.sqlite/20260526120000_credential_material_pointer/migration.sql +13 -0
- package/prisma/migrations.sqlite/20260527120000_add_human_task/migration.sql +30 -0
- package/prisma/migrations.sqlite/20260527130000_add_hitl_state_json/migration.sql +6 -0
- package/prisma/migrations.sqlite/20260527130000_add_hmac_nonce/migration.sql +9 -0
- package/prisma/schema.postgresql.prisma +48 -0
- package/prisma/schema.sqlite.prisma +48 -0
- package/prisma-generated/prisma-postgresql-client/edge.js +40 -6
- package/prisma-generated/prisma-postgresql-client/index-browser.js +36 -2
- package/prisma-generated/prisma-postgresql-client/index.d.ts +3179 -163
- package/prisma-generated/prisma-postgresql-client/index.js +40 -6
- package/prisma-generated/prisma-postgresql-client/package.json +1 -1
- package/prisma-generated/prisma-postgresql-client/schema.prisma +48 -0
- package/prisma-generated/prisma-sqlite-client/edge.js +40 -6
- package/prisma-generated/prisma-sqlite-client/index-browser.js +36 -2
- package/prisma-generated/prisma-sqlite-client/index.d.ts +3175 -163
- package/prisma-generated/prisma-sqlite-client/index.js +40 -6
- package/prisma-generated/prisma-sqlite-client/package.json +1 -1
- package/prisma-generated/prisma-sqlite-client/schema.prisma +48 -0
- package/src/application/contracts/CredentialContractsRegistry.ts +15 -0
- package/src/application/credentials/AppGalleryProjector.ts +69 -0
- package/src/application/hitl/DecideHumanTaskCommandHandler.ts +149 -0
- package/src/application/hitl/DecisionSchemaValidator.ts +22 -0
- package/src/application/hitl/HitlCallbackHandler.ts +96 -0
- package/src/application/mapping/WorkflowDefinitionMapper.ts +1 -3
- package/src/application/queries/CredentialQueryHandlers.ts +2 -0
- package/src/application/queries/GetCredentialAppsQuery.ts +4 -0
- package/src/application/queries/GetCredentialAppsQueryHandler.ts +27 -0
- package/src/application/telemetry/ResumeTelemetryContextForRun.ts +53 -0
- package/src/application/telemetry/TelemetryRetentionTimestampFactory.ts +9 -8
- package/src/applicationTokens.ts +11 -1
- package/src/auth/managed/ManagedCorsMiddleware.ts +20 -5
- package/src/bootstrap/AppContainerFactory.ts +121 -3
- package/src/bootstrap/runtime/HeadlessApiRuntime.ts +47 -0
- package/src/credentials/CachingCredentialMaterialProvider.ts +96 -0
- package/src/credentials/CompositeCredentialMaterialProvider.ts +47 -0
- package/src/credentials/ControlPlaneCatalogFetcher.ts +8 -28
- package/src/credentials/ControlPlaneCredentialMaterialProvider.ts +79 -0
- package/src/credentials/CredentialOAuth2MaterialReader.ts +2 -7
- package/src/credentials/InternalCredentialsBindingRegistrar.ts +83 -0
- package/src/credentials/LocalCredentialMaterialProvider.ts +92 -0
- package/src/domain/credentials/CredentialInstanceService.ts +5 -1
- package/src/domain/credentials/CredentialTypeRegistryImpl.ts +18 -4
- package/src/domain/workflows/WorkflowActivationPreflightRules.ts +7 -4
- package/src/dto.ts +2 -0
- package/src/hitl/ControlPlaneInboxChannel.ts +102 -0
- package/src/hitl/HitlResumeTokenSigner.ts +80 -0
- package/src/hitl/HitlTimeoutJobScheduler.ts +77 -0
- package/src/hitl/HitlTimeoutWorker.ts +138 -0
- package/src/hitl/InboxChannelResolver.ts +49 -0
- package/src/hitl/LocalInboxChannel.ts +37 -0
- package/src/index.ts +3 -0
- package/src/infrastructure/persistence/PrismaCredentialStore.ts +10 -0
- package/src/infrastructure/persistence/PrismaHmacNonceStore.ts +29 -0
- package/src/infrastructure/persistence/PrismaHumanTaskStore.ts +156 -0
- package/src/infrastructure/persistence/PrismaMigrationDeployer.ts +53 -383
- package/src/infrastructure/persistence/PrismaMigrationOperations.ts +401 -0
- package/src/infrastructure/persistence/PrismaWorkflowRunRepository.ts +39 -0
- package/src/mcp/AgentMcpIntegrationImpl.ts +5 -1
- package/src/pairing/HmacNonceStore.ts +14 -0
- package/src/pairing/HmacNonceStoreToken.ts +4 -0
- package/src/pairing/HmacRequestSigner.ts +10 -1
- package/src/pairing/InMemoryHmacNonceStore.ts +24 -0
- package/src/pairing/IncomingHmacVerifier.ts +28 -12
- package/src/pairing/InternalHmacAuthMiddleware.ts +1 -1
- package/src/pairing/index.ts +3 -0
- package/src/presentation/config/CodemationAuthoring.types.ts +7 -1
- package/src/presentation/config/CodemationConfig.ts +6 -0
- package/src/presentation/http/ApiPaths.ts +14 -0
- package/src/presentation/http/HeadlessHttpServerFactory.ts +56 -0
- package/src/presentation/http/hono/HonoHttpAnonymousRoutePolicyRegistry.ts +4 -0
- package/src/presentation/http/hono/registrars/CredentialHonoApiRouteRegistrar.ts +1 -0
- package/src/presentation/http/hono/registrars/HitlDecideHonoApiRouteRegistrar.ts +54 -0
- package/src/presentation/http/hono/registrars/HitlInternalCallbackHonoApiRouteRegistrar.ts +33 -0
- package/src/presentation/http/hono/registrars/HitlResumeHonoApiRouteRegistrar.ts +43 -0
- package/src/presentation/http/routeHandlers/CredentialHttpRouteHandler.ts +9 -0
- package/src/presentation/http/routeHandlers/OAuth2HttpRouteHandlerFactory.ts +1 -1
- package/src/presentation/server/CodemationConsumerConfigLoader.ts +7 -2
- package/src/presentation/websocket/WorkflowWebsocketServerFactory.ts +16 -0
- package/src/server.ts +7 -2
- package/src/workflows/InternalWorkflowTestRunRegistrar.ts +9 -0
- package/tsconfig.json +1 -0
- package/dist/AppConfigFactory-Cx4qQvRk.js.map +0 -1
- package/dist/AppContainerFactory-DqKYCRNP.js.map +0 -1
- package/dist/CodemationAuthoring.types-NGkBcmmT.js.map +0 -1
- package/dist/CodemationConsumerConfigLoader-GYpBBvqE.js.map +0 -1
- package/dist/CredentialServices-Dk8yypeL.js.map +0 -1
- package/dist/InternalPingRegistrar-DY3kSfxP.js.map +0 -1
- package/dist/persistenceServer-C-hH4z6l.js.map +0 -1
- package/dist/persistenceServer-CeTHtC6E.d.ts +0 -30
- package/src/credentials/catalogTypes.ts +0 -4
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { inject, injectable } from "@codemation/core";
|
|
2
|
+
import type { InboxChannel, InboxChannelResolverSeam } from "@codemation/core";
|
|
3
|
+
import { ControlPlaneInboxChannelToken, LocalInboxChannelToken } from "@codemation/core";
|
|
4
|
+
import type { Logger } from "../application/logging/Logger";
|
|
5
|
+
import type { PairingConfig } from "../pairing/pairing.types";
|
|
6
|
+
import { PairingConfigToken } from "../pairing/PairingConfigToken";
|
|
7
|
+
import { ServerLoggerFactory } from "../infrastructure/logging/ServerLoggerFactory";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Resolves the correct `InboxChannel` for the current deployment mode.
|
|
11
|
+
*
|
|
12
|
+
* - Managed mode (PairingConfig present + CP channel registered): returns CP channel.
|
|
13
|
+
* - Otherwise: returns local channel.
|
|
14
|
+
* - If managed mode is detected but CP channel is not registered, falls back to local
|
|
15
|
+
* and emits a warning (misconfiguration).
|
|
16
|
+
*/
|
|
17
|
+
@injectable()
|
|
18
|
+
export class InboxChannelResolver implements InboxChannelResolverSeam {
|
|
19
|
+
private readonly logger: Logger;
|
|
20
|
+
|
|
21
|
+
constructor(
|
|
22
|
+
@inject(PairingConfigToken, { isOptional: true }) private readonly pairingConfig: PairingConfig | null,
|
|
23
|
+
@inject(LocalInboxChannelToken, { isOptional: true }) private readonly local: InboxChannel | null,
|
|
24
|
+
@inject(ControlPlaneInboxChannelToken, { isOptional: true }) private readonly cp: InboxChannel | null,
|
|
25
|
+
@inject(ServerLoggerFactory) loggerFactory: ServerLoggerFactory,
|
|
26
|
+
) {
|
|
27
|
+
this.logger = loggerFactory.create("codemation.hitl.inbox");
|
|
28
|
+
|
|
29
|
+
if (pairingConfig && !cp) {
|
|
30
|
+
this.logger.warn(
|
|
31
|
+
"InboxChannelResolver: managed mode is active but no ControlPlaneInboxChannel is registered. " +
|
|
32
|
+
"Falling back to local inbox channel. Register a ControlPlaneInboxChannel to resolve this.",
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
resolve(): { channel: InboxChannel; workspaceId?: string } {
|
|
38
|
+
if (this.pairingConfig && this.cp) {
|
|
39
|
+
return { channel: this.cp, workspaceId: this.pairingConfig.workspaceId };
|
|
40
|
+
}
|
|
41
|
+
if (!this.local) {
|
|
42
|
+
throw new Error(
|
|
43
|
+
"InboxChannelResolver: no inbox channel is registered. " +
|
|
44
|
+
"Register a LocalInboxChannel or ControlPlaneInboxChannel.",
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
return { channel: this.local };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { inject, injectable } from "@codemation/core";
|
|
3
|
+
import type { InboxChannel, InboxDeliverArgs, InboxDelivery } from "@codemation/core";
|
|
4
|
+
import { ServerLoggerFactory } from "../infrastructure/logging/ServerLoggerFactory";
|
|
5
|
+
import type { Logger } from "../application/logging/Logger";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Local inbox channel for non-managed (dev) mode.
|
|
9
|
+
*
|
|
10
|
+
* `deliver` logs the pending task so developers without the UI can see the
|
|
11
|
+
* taskId / resumeUrl in the console, then returns an `InboxDelivery` where
|
|
12
|
+
* `inboxItemId === task.taskId`. The local channel has no separate inbox
|
|
13
|
+
* concept — the dev inbox UI queries `HumanTaskStore.findAllPending()` directly.
|
|
14
|
+
*
|
|
15
|
+
* Security (T4): The raw resume token is NOT logged. Only the first 8 hex characters of
|
|
16
|
+
* sha256(rawToken) are emitted as a fingerprint to enable log-correlation without leaking
|
|
17
|
+
* the workspace-bound authority token. The local dev inbox UI decides via
|
|
18
|
+
* POST /api/hitl/tasks/:taskId/decide (session-auth), so the token URL is not needed at
|
|
19
|
+
* the log level.
|
|
20
|
+
*/
|
|
21
|
+
@injectable()
|
|
22
|
+
export class LocalInboxChannel implements InboxChannel {
|
|
23
|
+
readonly kind = "local" as const;
|
|
24
|
+
private readonly logger: Logger;
|
|
25
|
+
|
|
26
|
+
constructor(@inject(ServerLoggerFactory) loggerFactory: ServerLoggerFactory) {
|
|
27
|
+
this.logger = loggerFactory.create("codemation.hitl.local-inbox");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async deliver(args: InboxDeliverArgs): Promise<InboxDelivery> {
|
|
31
|
+
const tokenFingerprint = createHash("sha256").update(args.task.resumeUrl, "utf8").digest("hex").slice(0, 8);
|
|
32
|
+
this.logger.info(
|
|
33
|
+
`HITL task pending in local inbox — taskId=${args.task.taskId} title="${args.subject.title}" tokenFingerprint=${tokenFingerprint}`,
|
|
34
|
+
);
|
|
35
|
+
return { kind: "local", inboxItemId: args.task.taskId };
|
|
36
|
+
}
|
|
37
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -13,6 +13,9 @@ export { CollectionSchemaSyncerHolder } from "./infrastructure/collections/Colle
|
|
|
13
13
|
export { FrontendRuntime } from "./bootstrap/runtime/FrontendRuntime";
|
|
14
14
|
export { WorkerRuntime } from "./bootstrap/runtime/WorkerRuntime";
|
|
15
15
|
export { AppConfigFactory } from "./bootstrap/runtime/AppConfigFactory";
|
|
16
|
+
export { HeadlessApiRuntime } from "./bootstrap/runtime/HeadlessApiRuntime";
|
|
17
|
+
export { WorkflowWebsocketServerFactory } from "./presentation/websocket/WorkflowWebsocketServerFactory";
|
|
18
|
+
export { HeadlessHttpServerFactory } from "./presentation/http/HeadlessHttpServerFactory";
|
|
16
19
|
export { ApplicationTokens } from "./applicationTokens";
|
|
17
20
|
export { workflow } from "@codemation/core-nodes";
|
|
18
21
|
export { CodemationBootstrapRequest } from "./bootstrap/CodemationBootstrapRequest";
|
|
@@ -46,6 +46,8 @@ export class PrismaCredentialStore implements CredentialStore {
|
|
|
46
46
|
setupStatus: args.instance.setupStatus,
|
|
47
47
|
createdAt: args.instance.createdAt,
|
|
48
48
|
updatedAt: args.instance.updatedAt,
|
|
49
|
+
materialSource: args.instance.material.source,
|
|
50
|
+
materialRef: args.instance.material.ref,
|
|
49
51
|
},
|
|
50
52
|
update: {
|
|
51
53
|
typeId: args.instance.typeId,
|
|
@@ -56,6 +58,8 @@ export class PrismaCredentialStore implements CredentialStore {
|
|
|
56
58
|
tagsJson: JSON.stringify(args.instance.tags),
|
|
57
59
|
setupStatus: args.instance.setupStatus,
|
|
58
60
|
updatedAt: args.instance.updatedAt,
|
|
61
|
+
materialSource: args.instance.material.source,
|
|
62
|
+
materialRef: args.instance.material.ref,
|
|
59
63
|
},
|
|
60
64
|
});
|
|
61
65
|
if (args.secretMaterial) {
|
|
@@ -323,6 +327,8 @@ export class PrismaCredentialStore implements CredentialStore {
|
|
|
323
327
|
setupStatus: string;
|
|
324
328
|
createdAt: string;
|
|
325
329
|
updatedAt: string;
|
|
330
|
+
materialSource: string;
|
|
331
|
+
materialRef: string;
|
|
326
332
|
}>,
|
|
327
333
|
): CredentialInstanceRecord {
|
|
328
334
|
return {
|
|
@@ -336,6 +342,10 @@ export class PrismaCredentialStore implements CredentialStore {
|
|
|
336
342
|
setupStatus: row.setupStatus as CredentialInstanceRecord["setupStatus"],
|
|
337
343
|
createdAt: row.createdAt,
|
|
338
344
|
updatedAt: row.updatedAt,
|
|
345
|
+
material: {
|
|
346
|
+
source: row.materialSource as CredentialInstanceRecord["material"]["source"],
|
|
347
|
+
ref: row.materialRef === "" ? row.instanceId : row.materialRef,
|
|
348
|
+
},
|
|
339
349
|
};
|
|
340
350
|
}
|
|
341
351
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { inject, injectable } from "@codemation/core";
|
|
2
|
+
import type { HmacNonceStore } from "../../pairing/HmacNonceStore";
|
|
3
|
+
import { PrismaDatabaseClientToken, type PrismaDatabaseClient } from "./PrismaDatabaseClient";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Durable HMAC nonce store backed by the Prisma database (T6 security fix).
|
|
7
|
+
*
|
|
8
|
+
* Uses an upsert with `create`+unique constraint to achieve atomic
|
|
9
|
+
* "insert if not exists": a Prisma unique-constraint violation means the nonce
|
|
10
|
+
* was already present (replay). Expired nonces are pruned inline on each call —
|
|
11
|
+
* acceptable for v1 given the low write rate of the pairing channel.
|
|
12
|
+
*/
|
|
13
|
+
@injectable()
|
|
14
|
+
export class PrismaHmacNonceStore implements HmacNonceStore {
|
|
15
|
+
constructor(@inject(PrismaDatabaseClientToken) private readonly prisma: PrismaDatabaseClient) {}
|
|
16
|
+
|
|
17
|
+
async recordIfNew(nonce: string, expiresAt: Date): Promise<boolean> {
|
|
18
|
+
// Prune expired nonces inline (v1 housekeeping; low write rate on this table)
|
|
19
|
+
await this.prisma.hmacNonce.deleteMany({ where: { expiresAt: { lt: new Date() } } });
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
await this.prisma.hmacNonce.create({ data: { nonce, expiresAt } });
|
|
23
|
+
return true;
|
|
24
|
+
} catch {
|
|
25
|
+
// Unique constraint violation → nonce already present → replay
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { inject, injectable } from "@codemation/core";
|
|
2
|
+
import type { HumanTaskActor, HumanTaskRecord, HumanTaskStore } from "@codemation/core";
|
|
3
|
+
import type { JsonValue } from "@codemation/core";
|
|
4
|
+
import { PrismaDatabaseClientToken, type PrismaDatabaseClient } from "./PrismaDatabaseClient";
|
|
5
|
+
|
|
6
|
+
@injectable()
|
|
7
|
+
export class PrismaHumanTaskStore implements HumanTaskStore {
|
|
8
|
+
constructor(@inject(PrismaDatabaseClientToken) private readonly prisma: PrismaDatabaseClient) {}
|
|
9
|
+
|
|
10
|
+
async create(record: HumanTaskRecord): Promise<void> {
|
|
11
|
+
await this.prisma.humanTask.create({
|
|
12
|
+
data: {
|
|
13
|
+
id: record.id,
|
|
14
|
+
runId: record.runId,
|
|
15
|
+
workflowId: record.workflowId,
|
|
16
|
+
workspaceId: record.workspaceId ?? null,
|
|
17
|
+
nodeId: record.nodeId,
|
|
18
|
+
activationId: record.activationId,
|
|
19
|
+
itemIndex: record.itemIndex,
|
|
20
|
+
status: record.status,
|
|
21
|
+
channel: record.channel,
|
|
22
|
+
subjectJson: JSON.stringify(record.subject),
|
|
23
|
+
metadataJson: JSON.stringify(record.metadata),
|
|
24
|
+
decisionSchemaJson: record.decisionSchemaJson,
|
|
25
|
+
decisionSchemaHash: record.decisionSchemaHash,
|
|
26
|
+
onTimeout: record.onTimeout,
|
|
27
|
+
deliveryRefJson: record.deliveryRef !== undefined ? JSON.stringify(record.deliveryRef) : null,
|
|
28
|
+
decisionJson: null,
|
|
29
|
+
decidedAt: null,
|
|
30
|
+
decidedByJson: null,
|
|
31
|
+
resumeTokenHash: record.resumeTokenHash,
|
|
32
|
+
expiresAt: record.expiresAt,
|
|
33
|
+
createdAt: record.createdAt,
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async findById(taskId: string): Promise<HumanTaskRecord | undefined> {
|
|
39
|
+
const row = await this.prisma.humanTask.findUnique({ where: { id: taskId } });
|
|
40
|
+
return row ? this.toRecord(row) : undefined;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async findByResumeTokenHash(tokenHash: string): Promise<HumanTaskRecord | undefined> {
|
|
44
|
+
const row = await this.prisma.humanTask.findFirst({ where: { resumeTokenHash: tokenHash } });
|
|
45
|
+
return row ? this.toRecord(row) : undefined;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async findPendingForWorkspace(workspaceId: string): Promise<ReadonlyArray<HumanTaskRecord>> {
|
|
49
|
+
const rows = await this.prisma.humanTask.findMany({
|
|
50
|
+
where: { workspaceId, status: "pending" },
|
|
51
|
+
orderBy: { expiresAt: "asc" },
|
|
52
|
+
});
|
|
53
|
+
return rows.map((row) => this.toRecord(row));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async findAllPending(): Promise<ReadonlyArray<HumanTaskRecord>> {
|
|
57
|
+
const rows = await this.prisma.humanTask.findMany({
|
|
58
|
+
where: { status: "pending" },
|
|
59
|
+
orderBy: { expiresAt: "asc" },
|
|
60
|
+
});
|
|
61
|
+
return rows.map((row) => this.toRecord(row));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async markDecided(args: {
|
|
65
|
+
taskId: string;
|
|
66
|
+
decision: JsonValue;
|
|
67
|
+
decidedBy: HumanTaskActor;
|
|
68
|
+
decidedAt: Date;
|
|
69
|
+
}): Promise<void> {
|
|
70
|
+
await this.prisma.humanTask.update({
|
|
71
|
+
where: { id: args.taskId },
|
|
72
|
+
data: {
|
|
73
|
+
status: "decided",
|
|
74
|
+
decisionJson: JSON.stringify(args.decision),
|
|
75
|
+
decidedAt: args.decidedAt,
|
|
76
|
+
decidedByJson: JSON.stringify(args.decidedBy),
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async markTimedOut(taskId: string): Promise<void> {
|
|
82
|
+
await this.prisma.humanTask.update({
|
|
83
|
+
where: { id: taskId },
|
|
84
|
+
data: { status: "timed_out" },
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async markAutoAccepted(taskId: string): Promise<void> {
|
|
89
|
+
await this.prisma.humanTask.update({
|
|
90
|
+
where: { id: taskId },
|
|
91
|
+
data: { status: "auto_accepted" },
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async markCancelled(taskId: string): Promise<void> {
|
|
96
|
+
await this.prisma.humanTask.update({
|
|
97
|
+
where: { id: taskId },
|
|
98
|
+
data: { status: "cancelled" },
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async cancelPendingForRun(runId: string): Promise<void> {
|
|
103
|
+
await this.prisma.humanTask.updateMany({
|
|
104
|
+
where: { runId, status: "pending" },
|
|
105
|
+
data: { status: "cancelled" },
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private toRecord(row: {
|
|
110
|
+
id: string;
|
|
111
|
+
runId: string;
|
|
112
|
+
workflowId: string;
|
|
113
|
+
workspaceId: string | null;
|
|
114
|
+
nodeId: string;
|
|
115
|
+
activationId: string;
|
|
116
|
+
itemIndex: number;
|
|
117
|
+
status: string;
|
|
118
|
+
channel: string;
|
|
119
|
+
subjectJson: string;
|
|
120
|
+
metadataJson: string;
|
|
121
|
+
decisionSchemaJson: string;
|
|
122
|
+
decisionSchemaHash: string;
|
|
123
|
+
onTimeout: string;
|
|
124
|
+
deliveryRefJson: string | null;
|
|
125
|
+
decisionJson: string | null;
|
|
126
|
+
decidedAt: Date | null;
|
|
127
|
+
decidedByJson: string | null;
|
|
128
|
+
resumeTokenHash: string;
|
|
129
|
+
expiresAt: Date;
|
|
130
|
+
createdAt: Date;
|
|
131
|
+
}): HumanTaskRecord {
|
|
132
|
+
return {
|
|
133
|
+
id: row.id,
|
|
134
|
+
runId: row.runId,
|
|
135
|
+
workflowId: row.workflowId,
|
|
136
|
+
workspaceId: row.workspaceId ?? undefined,
|
|
137
|
+
nodeId: row.nodeId,
|
|
138
|
+
activationId: row.activationId,
|
|
139
|
+
itemIndex: row.itemIndex,
|
|
140
|
+
status: row.status as HumanTaskRecord["status"],
|
|
141
|
+
channel: row.channel,
|
|
142
|
+
subject: JSON.parse(row.subjectJson),
|
|
143
|
+
metadata: JSON.parse(row.metadataJson),
|
|
144
|
+
decisionSchemaJson: row.decisionSchemaJson,
|
|
145
|
+
decisionSchemaHash: row.decisionSchemaHash,
|
|
146
|
+
onTimeout: row.onTimeout as "halt" | "auto-accept",
|
|
147
|
+
deliveryRef: row.deliveryRefJson !== null ? (JSON.parse(row.deliveryRefJson) as JsonValue) : undefined,
|
|
148
|
+
decision: row.decisionJson !== null ? (JSON.parse(row.decisionJson) as JsonValue) : undefined,
|
|
149
|
+
decidedAt: row.decidedAt ?? undefined,
|
|
150
|
+
decidedBy: row.decidedByJson !== null ? JSON.parse(row.decidedByJson) : undefined,
|
|
151
|
+
resumeTokenHash: row.resumeTokenHash,
|
|
152
|
+
expiresAt: row.expiresAt,
|
|
153
|
+
createdAt: row.createdAt,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
}
|