@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.
Files changed (148) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/dist/{ApiPaths-Dv1dcHu_.js → ApiPaths-DCvrlIjg.js} +12 -1
  3. package/dist/{ApiPaths-Dv1dcHu_.js.map → ApiPaths-DCvrlIjg.js.map} +1 -1
  4. package/dist/{AppConfigFactory-Cx4qQvRk.js → AppConfigFactory-D4LL1aOR.js} +77 -297
  5. package/dist/AppConfigFactory-D4LL1aOR.js.map +1 -0
  6. package/dist/{AppConfigFactory-BT0y0LVC.d.ts → AppConfigFactory-DncmwCD1.d.ts} +2918 -199
  7. package/dist/{AppContainerFactory-DRTjG7nG.js → AppContainerFactory-CHCXP2rn.js} +1735 -474
  8. package/dist/AppContainerFactory-CHCXP2rn.js.map +1 -0
  9. package/dist/{CodemationAppContext-CGFYVcSb.d.ts → CodemationAppContext-K51b7oXe.d.ts} +3 -3
  10. package/dist/{CodemationAuthoring.types-DiKKogum.d.ts → CodemationAuthoring.types-BXlXIl4K.d.ts} +4 -4
  11. package/dist/{CodemationConfigNormalizer-48f-T66P.d.ts → CodemationConfigNormalizer-B4rDYC9h.d.ts} +3 -3
  12. package/dist/{CodemationConsumerConfigLoader-_PIYqwVx.d.ts → CodemationConsumerConfigLoader-Dt4jyLx6.d.ts} +2 -2
  13. package/dist/{CodemationPluginListMerger-DP7djJ9S.d.ts → CodemationPluginListMerger-DS6I3Xe0.d.ts} +24 -12
  14. package/dist/{persistenceServer-C-hH4z6l.js → CodemationPostgresPrismaClientFactory-C7156Fe-.js} +2 -2
  15. package/dist/CodemationPostgresPrismaClientFactory-C7156Fe-.js.map +1 -0
  16. package/dist/CodemationPostgresPrismaClientFactory-CTNTPnDr.d.ts +9 -0
  17. package/dist/{CredentialContractsRegistry-Bq2bq28t.d.ts → CredentialContractsRegistry-Dgu-rEXi.d.ts} +16 -3
  18. package/dist/{CredentialServices-BLloBztI.d.ts → CredentialServices-B3wPyp2y.d.ts} +4 -4
  19. package/dist/{CredentialServices-Dk8yypeL.js → CredentialServices-Bios0dM8.js} +10 -4
  20. package/dist/CredentialServices-Bios0dM8.js.map +1 -0
  21. package/dist/{InternalHonoApiRouteRegistrar-c7t3KnV_.d.ts → InternalHonoApiRouteRegistrar-Ce1yxpnO.d.ts} +1 -1
  22. package/dist/{InternalPingRegistrar-DY3kSfxP.js → InternalPingRegistrar-BavAAnvk.js} +19 -16
  23. package/dist/InternalPingRegistrar-BavAAnvk.js.map +1 -0
  24. package/dist/{ItemsInputNormalizer-_RwIfRIQ.d.ts → ItemsInputNormalizer-CFkfNMLt.d.ts} +1434 -1225
  25. package/dist/PrismaMigrationDeployer-DdEcXXVi.d.ts +14 -0
  26. package/dist/{PublicFrontendBootstrapFactory-Dv04tJ-6.d.ts → PublicFrontendBootstrapFactory-ClEjZP74.d.ts} +2 -2
  27. package/dist/{PublicFrontendBootstrapJsonCodec-CXG9Dxft.d.ts → PublicFrontendBootstrapJsonCodec-HNItQ7ol.d.ts} +6 -1
  28. package/dist/{TelemetryContracts-BtDx84Cp.d.ts → TelemetryContracts-DpZEODQM.d.ts} +2 -2
  29. package/dist/{WorkflowPolicyUiPresentationFactory-6MyjCvBO.d.ts → WorkflowPolicyUiPresentationFactory-BNn2fvR_.d.ts} +2 -2
  30. package/dist/{WorkflowPolicyUiPresentationFactory-Bb-ae_Zh.js → WorkflowPolicyUiPresentationFactory-DfvD2VHk.js} +1 -1
  31. package/dist/{WorkflowPolicyUiPresentationFactory-Bb-ae_Zh.js.map → WorkflowPolicyUiPresentationFactory-DfvD2VHk.js.map} +1 -1
  32. package/dist/authoring.d.ts +4 -4
  33. package/dist/client.d.ts +1 -1
  34. package/dist/client.js +1 -1
  35. package/dist/consumer.d.ts +5 -5
  36. package/dist/credentials.d.ts +5 -5
  37. package/dist/credentials.js +1 -1
  38. package/dist/devServerSidecar.d.ts +2 -2
  39. package/dist/dto.d.ts +5 -5
  40. package/dist/{index-DilAYwnH.d.ts → index-ChIfeWzk.d.ts} +71 -28
  41. package/dist/index.d.ts +17 -16
  42. package/dist/index.js +8 -8
  43. package/dist/infrastructure/persistence/PrismaMigrationOperations.d.ts +44 -0
  44. package/dist/infrastructure/persistence/PrismaMigrationOperations.js +302 -0
  45. package/dist/infrastructure/persistence/PrismaMigrationOperations.js.map +1 -0
  46. package/dist/mapping.d.ts +2 -2
  47. package/dist/mapping.js +1 -1
  48. package/dist/nextServer.d.ts +15 -13
  49. package/dist/nextServer.js +6 -6
  50. package/dist/pairing.d.ts +28 -9
  51. package/dist/pairing.js +19 -3
  52. package/dist/pairing.js.map +1 -0
  53. package/dist/{pairing.types-snfZ_OzB.d.ts → pairing.types-D9Bjn98U.d.ts} +1 -1
  54. package/dist/persistenceServer.d.ts +31 -7
  55. package/dist/persistenceServer.js +2 -2
  56. package/dist/{server-09PKasWR.d.ts → server-B5trn7y4.d.ts} +5 -5
  57. package/dist/{server-vtRCPgRJ.js → server-CNj_y0QO.js} +4 -4
  58. package/dist/{server-vtRCPgRJ.js.map → server-CNj_y0QO.js.map} +1 -1
  59. package/dist/server.d.ts +10 -10
  60. package/dist/server.js +8 -8
  61. package/package.json +11 -10
  62. package/playwright.config.ts +8 -2
  63. package/playwright.scaffolded-dev.config.ts +8 -2
  64. package/prisma/migrations/20260526120000_credential_material_pointer/migration.sql +18 -0
  65. package/prisma/migrations/20260527120000_add_human_task/migration.sql +32 -0
  66. package/prisma/migrations/20260527130000_add_hitl_state_json/migration.sql +6 -0
  67. package/prisma/migrations/20260527130000_add_hmac_nonce/migration.sql +12 -0
  68. package/prisma/migrations.sqlite/20260526120000_credential_material_pointer/migration.sql +13 -0
  69. package/prisma/migrations.sqlite/20260527120000_add_human_task/migration.sql +30 -0
  70. package/prisma/migrations.sqlite/20260527130000_add_hitl_state_json/migration.sql +6 -0
  71. package/prisma/migrations.sqlite/20260527130000_add_hmac_nonce/migration.sql +9 -0
  72. package/prisma/schema.postgresql.prisma +48 -0
  73. package/prisma/schema.sqlite.prisma +48 -0
  74. package/prisma-generated/prisma-postgresql-client/edge.js +40 -6
  75. package/prisma-generated/prisma-postgresql-client/index-browser.js +36 -2
  76. package/prisma-generated/prisma-postgresql-client/index.d.ts +3179 -163
  77. package/prisma-generated/prisma-postgresql-client/index.js +40 -6
  78. package/prisma-generated/prisma-postgresql-client/package.json +1 -1
  79. package/prisma-generated/prisma-postgresql-client/schema.prisma +48 -0
  80. package/prisma-generated/prisma-sqlite-client/edge.js +40 -6
  81. package/prisma-generated/prisma-sqlite-client/index-browser.js +36 -2
  82. package/prisma-generated/prisma-sqlite-client/index.d.ts +3175 -163
  83. package/prisma-generated/prisma-sqlite-client/index.js +40 -6
  84. package/prisma-generated/prisma-sqlite-client/package.json +1 -1
  85. package/prisma-generated/prisma-sqlite-client/schema.prisma +48 -0
  86. package/src/application/contracts/CredentialContractsRegistry.ts +15 -0
  87. package/src/application/credentials/AppGalleryProjector.ts +69 -0
  88. package/src/application/hitl/DecideHumanTaskCommandHandler.ts +149 -0
  89. package/src/application/hitl/DecisionSchemaValidator.ts +22 -0
  90. package/src/application/hitl/HitlCallbackHandler.ts +96 -0
  91. package/src/application/mapping/WorkflowDefinitionMapper.ts +1 -3
  92. package/src/application/queries/CredentialQueryHandlers.ts +2 -0
  93. package/src/application/queries/GetCredentialAppsQuery.ts +4 -0
  94. package/src/application/queries/GetCredentialAppsQueryHandler.ts +27 -0
  95. package/src/application/telemetry/ResumeTelemetryContextForRun.ts +53 -0
  96. package/src/application/telemetry/TelemetryRetentionTimestampFactory.ts +9 -8
  97. package/src/applicationTokens.ts +11 -1
  98. package/src/auth/managed/ManagedCorsMiddleware.ts +20 -5
  99. package/src/bootstrap/AppContainerFactory.ts +100 -0
  100. package/src/credentials/CachingCredentialMaterialProvider.ts +96 -0
  101. package/src/credentials/CompositeCredentialMaterialProvider.ts +47 -0
  102. package/src/credentials/ControlPlaneCatalogFetcher.ts +4 -24
  103. package/src/credentials/ControlPlaneCredentialMaterialProvider.ts +79 -0
  104. package/src/credentials/CredentialOAuth2MaterialReader.ts +2 -7
  105. package/src/credentials/InternalCredentialsBindingRegistrar.ts +83 -0
  106. package/src/credentials/LocalCredentialMaterialProvider.ts +92 -0
  107. package/src/domain/credentials/CredentialInstanceService.ts +5 -1
  108. package/src/domain/credentials/CredentialTypeRegistryImpl.ts +18 -4
  109. package/src/domain/workflows/WorkflowActivationPreflightRules.ts +7 -4
  110. package/src/dto.ts +2 -0
  111. package/src/hitl/ControlPlaneInboxChannel.ts +102 -0
  112. package/src/hitl/HitlResumeTokenSigner.ts +80 -0
  113. package/src/hitl/HitlTimeoutJobScheduler.ts +89 -0
  114. package/src/hitl/HitlTimeoutWorker.ts +143 -0
  115. package/src/hitl/InboxChannelResolver.ts +49 -0
  116. package/src/hitl/LocalInboxChannel.ts +37 -0
  117. package/src/infrastructure/persistence/PrismaCredentialStore.ts +10 -0
  118. package/src/infrastructure/persistence/PrismaHmacNonceStore.ts +29 -0
  119. package/src/infrastructure/persistence/PrismaHumanTaskStore.ts +156 -0
  120. package/src/infrastructure/persistence/PrismaMigrationDeployer.ts +53 -383
  121. package/src/infrastructure/persistence/PrismaMigrationOperations.ts +401 -0
  122. package/src/infrastructure/persistence/PrismaWorkflowRunRepository.ts +39 -0
  123. package/src/mcp/AgentMcpIntegrationImpl.ts +5 -1
  124. package/src/pairing/HmacNonceStore.ts +14 -0
  125. package/src/pairing/HmacNonceStoreToken.ts +4 -0
  126. package/src/pairing/HmacRequestSigner.ts +10 -1
  127. package/src/pairing/InMemoryHmacNonceStore.ts +24 -0
  128. package/src/pairing/IncomingHmacVerifier.ts +28 -12
  129. package/src/pairing/InternalHmacAuthMiddleware.ts +1 -1
  130. package/src/pairing/index.ts +3 -0
  131. package/src/presentation/http/ApiPaths.ts +14 -0
  132. package/src/presentation/http/hono/HonoHttpAnonymousRoutePolicyRegistry.ts +4 -0
  133. package/src/presentation/http/hono/registrars/CredentialHonoApiRouteRegistrar.ts +1 -0
  134. package/src/presentation/http/hono/registrars/HitlDecideHonoApiRouteRegistrar.ts +54 -0
  135. package/src/presentation/http/hono/registrars/HitlInternalCallbackHonoApiRouteRegistrar.ts +33 -0
  136. package/src/presentation/http/hono/registrars/HitlResumeHonoApiRouteRegistrar.ts +43 -0
  137. package/src/presentation/http/routeHandlers/CredentialHttpRouteHandler.ts +9 -0
  138. package/src/presentation/http/routeHandlers/OAuth2HttpRouteHandlerFactory.ts +1 -1
  139. package/src/server.ts +7 -2
  140. package/src/workflows/InternalWorkflowTestRunRegistrar.ts +9 -0
  141. package/tsconfig.json +1 -0
  142. package/dist/AppConfigFactory-Cx4qQvRk.js.map +0 -1
  143. package/dist/AppContainerFactory-DRTjG7nG.js.map +0 -1
  144. package/dist/CredentialServices-Dk8yypeL.js.map +0 -1
  145. package/dist/InternalPingRegistrar-DY3kSfxP.js.map +0 -1
  146. package/dist/persistenceServer-B71RGvSj.d.ts +0 -30
  147. package/dist/persistenceServer-C-hH4z6l.js.map +0 -1
  148. package/src/credentials/catalogTypes.ts +0 -4
@@ -1,400 +1,70 @@
1
- import type { Client } from "@libsql/client";
2
1
  import { injectable } from "@codemation/core";
3
- import { spawn } from "node:child_process";
4
- import { existsSync } from "node:fs";
5
- import { mkdir } from "node:fs/promises";
6
- import { createRequire } from "node:module";
7
- import path from "node:path";
8
- import { fileURLToPath } from "node:url";
9
2
  import type { AppPersistenceConfig } from "../../presentation/config/AppConfig";
10
3
 
11
4
  /**
12
5
  * Runs `prisma migrate deploy` against TCP PostgreSQL or a SQLite database file.
6
+ *
7
+ * This class is deliberately kept as a thin wrapper with no direct imports from
8
+ * node:fs, node:path, node:module, or node:url. All heavy filesystem and dynamic-
9
+ * require operations live in PrismaMigrationOperations, which is loaded lazily via
10
+ * a runtime-computed `import(...)` path. The Turbopack / Next.js NFT module tracer
11
+ * only follows statically-analysable import paths, so computing the specifier at
12
+ * runtime prevents it from walking the whole project when tracing this file.
13
13
  */
14
+
15
+ // The import specifier is intentionally split so static analysers (NFT / Turbopack)
16
+ // cannot resolve it at build time and trace the heavy fs/createRequire ops inside.
17
+ // We disambiguate source vs dist at runtime via `import.meta.url`, then resolve to
18
+ // an absolute `file://` URL. The absolute URL bypasses tsx-loader's namespace
19
+ // decoration, which otherwise propagates a `?tsx-namespace=...` query to every
20
+ // transitive `node:*` builtin import in PrismaMigrationOperations and trips
21
+ // `ENOENT: open 'node:path?tsx-namespace=...'` under the packaged smoke harness.
22
+ // - source (Vitest, `pnpm dev`): the wrapper and operations are siblings under
23
+ // `src/infrastructure/persistence/`, so the relative path is `./PrismaMigrationOperations.js`.
24
+ // - dist (published tgz): tsdown inlines the wrapper into top-level chunks, while
25
+ // the operations module is emitted as its own entry at
26
+ // `dist/infrastructure/persistence/PrismaMigrationOperations.js`.
27
+ const implSpecifier = /* @__PURE__ */ (() => {
28
+ const suffix = "Operations.js";
29
+ const relativePath = import.meta.url.includes("/src/")
30
+ ? "./PrismaMigration" + suffix
31
+ : "./infrastructure/persistence/PrismaMigration" + suffix;
32
+ // Absolute `file://` URL: keeps tsx-loader from propagating its
33
+ // `?tsx-namespace=...` query to transitive node:* imports inside the
34
+ // operations module, and keeps the Turbopack NFT tracer from following the
35
+ // path statically (which would walk the heavy fs/createRequire ops).
36
+ return new URL(relativePath, import.meta.url).href;
37
+ })();
38
+
14
39
  @injectable()
15
40
  export class PrismaMigrationDeployer {
16
- private static readonly normalizedRuntimeMigrationName = "20260407140000_run_normalized_persistence";
17
- private readonly require = createRequire(import.meta.url);
18
-
19
41
  async deployPersistence(persistence: AppPersistenceConfig, env?: Readonly<NodeJS.ProcessEnv>): Promise<void> {
20
- if (persistence.kind === "none") {
21
- return;
22
- }
23
- if (persistence.kind === "postgresql") {
24
- await this.deployPostgres({ databaseUrl: persistence.databaseUrl, env });
25
- return;
26
- }
27
- await this.deploySqlite({ databaseFilePath: persistence.databaseFilePath, env });
42
+ const { PrismaMigrationOperations } = await import(
43
+ /* webpackIgnore: true */
44
+ /* @vite-ignore */
45
+ implSpecifier
46
+ );
47
+ // eslint-disable-next-line codemation/no-manual-di-new -- lazy-loaded impl; cannot be registered in DI without breaking the bootstrap chain
48
+ return new PrismaMigrationOperations().deployPersistence(persistence, env);
28
49
  }
29
50
 
30
51
  async deploy(args: Readonly<{ databaseUrl: string; env?: Readonly<NodeJS.ProcessEnv> }>): Promise<void> {
31
- await this.deployPostgres(args);
32
- }
33
-
34
- private async deploySqlite(
35
- args: Readonly<{ databaseFilePath: string; env?: Readonly<NodeJS.ProcessEnv> }>,
36
- ): Promise<void> {
37
- await this.ensureSqliteParentDirectoryExists(args.databaseFilePath);
38
- const databaseUrl = this.sqliteFilePathToDatabaseUrl(args.databaseFilePath);
39
- try {
40
- await this.deployWithProvider({
41
- provider: "sqlite",
42
- databaseUrl,
43
- env: args.env,
44
- });
45
- } catch (error) {
46
- const recovered = await this.tryRecoverPartiallyAppliedNormalizedRuntimeMigration({
47
- databaseFilePath: args.databaseFilePath,
48
- databaseUrl,
49
- env: args.env,
50
- error,
51
- });
52
- if (!recovered) {
53
- throw error;
54
- }
55
- }
56
- await this.cleanupNormalizedRuntimeLegacyArtifacts(args.databaseFilePath);
57
- }
58
-
59
- private async deployPostgres(
60
- args: Readonly<{ databaseUrl: string; env?: Readonly<NodeJS.ProcessEnv> }>,
61
- ): Promise<void> {
62
- await this.deployWithProvider({
63
- provider: "postgresql",
64
- databaseUrl: args.databaseUrl,
65
- env: args.env,
66
- });
67
- }
68
-
69
- private async deployWithProvider(
70
- args: Readonly<{
71
- provider: "postgresql" | "sqlite";
72
- databaseUrl: string;
73
- env?: Readonly<NodeJS.ProcessEnv>;
74
- }>,
75
- ): Promise<void> {
76
- await this.runPrismaCommand({
77
- prismaArgs: ["migrate", "deploy"],
78
- provider: args.provider,
79
- databaseUrl: args.databaseUrl,
80
- env: args.env,
81
- });
82
- }
83
-
84
- private async resolveAppliedMigration(
85
- args: Readonly<{
86
- provider: "postgresql" | "sqlite";
87
- databaseUrl: string;
88
- migrationName: string;
89
- env?: Readonly<NodeJS.ProcessEnv>;
90
- }>,
91
- ): Promise<void> {
92
- await this.runPrismaCommand({
93
- prismaArgs: ["migrate", "resolve", "--applied", args.migrationName],
94
- provider: args.provider,
95
- databaseUrl: args.databaseUrl,
96
- env: args.env,
97
- });
98
- }
99
-
100
- private async runPrismaCommand(
101
- args: Readonly<{
102
- prismaArgs: string[];
103
- provider: "postgresql" | "sqlite";
104
- databaseUrl: string;
105
- env?: Readonly<NodeJS.ProcessEnv>;
106
- }>,
107
- ): Promise<void> {
108
- const resolverEnv = { ...process.env, ...(args.env ?? {}) };
109
- const prismaConfigPath = this.resolveAbsolutePrismaConfigPath(resolverEnv);
110
- await new Promise<void>((resolve, reject) => {
111
- const command = spawn(
112
- process.execPath,
113
- [...[this.resolvePrismaCliPath(resolverEnv), ...args.prismaArgs], "--config", path.basename(prismaConfigPath)],
114
- {
115
- cwd: path.dirname(prismaConfigPath),
116
- env: this.createProcessEnvironment(args.databaseUrl, args.provider, args.env),
117
- stdio: ["ignore", "pipe", "pipe"],
118
- },
119
- );
120
- let stdout = "";
121
- let stderr = "";
122
- command.stdout.on("data", (chunk: Buffer | string) => {
123
- stdout += chunk.toString();
124
- });
125
- command.stderr.on("data", (chunk: Buffer | string) => {
126
- stderr += chunk.toString();
127
- });
128
- command.once("error", (error) => {
129
- reject(error);
130
- });
131
- command.once("close", (exitCode) => {
132
- if (exitCode === 0) {
133
- resolve();
134
- return;
135
- }
136
- reject(this.createDeployError(exitCode, stdout, stderr));
137
- });
138
- });
139
- }
140
-
141
- private async tryRecoverPartiallyAppliedNormalizedRuntimeMigration(
142
- args: Readonly<{
143
- databaseFilePath: string;
144
- databaseUrl: string;
145
- env?: Readonly<NodeJS.ProcessEnv>;
146
- error: unknown;
147
- }>,
148
- ): Promise<boolean> {
149
- if (!this.isRecoverableNormalizedRuntimeMigrationError(args.error)) {
150
- return false;
151
- }
152
- const repaired = await this.repairPartiallyAppliedNormalizedRuntimeSqliteDatabase(args.databaseFilePath);
153
- if (!repaired) {
154
- return false;
155
- }
156
- await this.resolveAppliedMigration({
157
- provider: "sqlite",
158
- databaseUrl: args.databaseUrl,
159
- migrationName: PrismaMigrationDeployer.normalizedRuntimeMigrationName,
160
- env: args.env,
161
- });
162
- await this.deployWithProvider({
163
- provider: "sqlite",
164
- databaseUrl: args.databaseUrl,
165
- env: args.env,
166
- });
167
- return true;
168
- }
169
-
170
- private isRecoverableNormalizedRuntimeMigrationError(error: unknown): boolean {
171
- if (!(error instanceof Error)) {
172
- return false;
173
- }
174
- return (
175
- error.message.includes("Error: P3009") &&
176
- error.message.includes(PrismaMigrationDeployer.normalizedRuntimeMigrationName)
52
+ const { PrismaMigrationOperations } = await import(
53
+ /* webpackIgnore: true */
54
+ /* @vite-ignore */
55
+ implSpecifier
177
56
  );
57
+ // eslint-disable-next-line codemation/no-manual-di-new -- lazy-loaded impl; cannot be registered in DI without breaking the bootstrap chain
58
+ return new PrismaMigrationOperations().deploy(args);
178
59
  }
179
60
 
180
- private async repairPartiallyAppliedNormalizedRuntimeSqliteDatabase(databaseFilePath: string): Promise<boolean> {
181
- // Lazy import: @libsql/client pulls in platform-specific native bindings that confuse the
182
- // Next.js / Turbopack module tracer (forcing the whole project to be traced via NFT). This
183
- // recovery path is rarely needed, so defer the load until it's actually invoked.
184
- const { createClient } = await import("@libsql/client");
185
- const client = createClient({ url: this.sqliteFilePathToDatabaseUrl(databaseFilePath) });
186
- try {
187
- const failedMigration = await this.hasActiveFailedMigrationRecord(
188
- client,
189
- PrismaMigrationDeployer.normalizedRuntimeMigrationName,
190
- );
191
- if (!failedMigration) {
192
- return false;
193
- }
194
- const runColumns = await this.readSqliteTableColumns(client, "Run");
195
- const hasNormalizedRunShape =
196
- runColumns.has("finished_at") &&
197
- runColumns.has("revision") &&
198
- runColumns.has("outputs_by_node_json") &&
199
- !runColumns.has("state_json");
200
- if (!hasNormalizedRunShape) {
201
- return false;
202
- }
203
- await this.ensureNormalizedRuntimeRepairArtifacts(client);
204
- return true;
205
- } finally {
206
- client.close();
207
- }
208
- }
209
-
210
- private async hasActiveFailedMigrationRecord(client: Client, migrationName: string): Promise<boolean> {
211
- const result = await client.execute({
212
- sql: [
213
- 'SELECT 1 AS "has_failed"',
214
- 'FROM "_prisma_migrations"',
215
- 'WHERE "migration_name" = ?',
216
- ' AND "finished_at" IS NULL',
217
- ' AND "rolled_back_at" IS NULL',
218
- "LIMIT 1",
219
- ].join(" "),
220
- args: [migrationName],
221
- });
222
- return result.rows.length > 0;
223
- }
224
-
225
- private async readSqliteTableColumns(client: Client, tableName: string): Promise<Set<string>> {
226
- const result = await client.execute(`PRAGMA table_info("${tableName}")`);
227
- return new Set(result.rows.map((row) => String(row.name)));
228
- }
229
-
230
- private async ensureNormalizedRuntimeRepairArtifacts(client: Client): Promise<void> {
231
- await client.execute(`
232
- CREATE TABLE IF NOT EXISTS "RunWorkItem" (
233
- "work_item_id" TEXT NOT NULL PRIMARY KEY,
234
- "run_id" TEXT NOT NULL,
235
- "workflow_id" TEXT NOT NULL,
236
- "status" TEXT NOT NULL,
237
- "target_node_id" TEXT NOT NULL,
238
- "batch_id" TEXT NOT NULL,
239
- "queue_name" TEXT,
240
- "claim_token" TEXT,
241
- "claimed_by" TEXT,
242
- "claimed_at" TEXT,
243
- "available_at" TEXT NOT NULL,
244
- "enqueued_at" TEXT NOT NULL,
245
- "completed_at" TEXT,
246
- "failed_at" TEXT,
247
- "source_instance_id" TEXT,
248
- "parent_instance_id" TEXT,
249
- "items_in" INTEGER NOT NULL,
250
- "inputs_by_port_json" TEXT NOT NULL,
251
- "error_json" TEXT,
252
- CONSTRAINT "RunWorkItem_run_id_fkey" FOREIGN KEY ("run_id") REFERENCES "Run"("run_id") ON DELETE CASCADE ON UPDATE CASCADE
253
- )
254
- `);
255
- await client.execute(`
256
- CREATE INDEX IF NOT EXISTS "RunWorkItem_run_id_status_available_at_idx"
257
- ON "RunWorkItem"("run_id", "status", "available_at")
258
- `);
259
- await client.execute(`
260
- CREATE INDEX IF NOT EXISTS "RunWorkItem_run_id_target_node_id_batch_id_idx"
261
- ON "RunWorkItem"("run_id", "target_node_id", "batch_id")
262
- `);
263
- await client.execute(`
264
- CREATE TABLE IF NOT EXISTS "RunSlotProjection" (
265
- "run_id" TEXT NOT NULL PRIMARY KEY,
266
- "workflow_id" TEXT NOT NULL,
267
- "revision" INTEGER NOT NULL,
268
- "updated_at" TEXT NOT NULL,
269
- "slot_states_json" TEXT NOT NULL,
270
- CONSTRAINT "RunSlotProjection_run_id_fkey" FOREIGN KEY ("run_id") REFERENCES "Run"("run_id") ON DELETE CASCADE ON UPDATE CASCADE
271
- )
272
- `);
273
- await client.execute(`
274
- CREATE INDEX IF NOT EXISTS "RunSlotProjection_workflow_id_updated_at_idx"
275
- ON "RunSlotProjection"("workflow_id", "updated_at")
276
- `);
277
- await client.execute(`
278
- INSERT OR IGNORE INTO "RunSlotProjection" (
279
- "run_id",
280
- "workflow_id",
281
- "revision",
282
- "updated_at",
283
- "slot_states_json"
284
- )
285
- SELECT
286
- "run_id",
287
- "workflow_id",
288
- "revision",
289
- "updated_at",
290
- json_object('slotStatesByNodeId', json('{}'))
291
- FROM "Run"
292
- `);
293
- }
294
-
295
- private async cleanupNormalizedRuntimeLegacyArtifacts(databaseFilePath: string): Promise<void> {
296
- const { createClient } = await import("@libsql/client");
297
- const client = createClient({ url: this.sqliteFilePathToDatabaseUrl(databaseFilePath) });
298
- try {
299
- const runColumns = await this.readSqliteTableColumns(client, "Run");
300
- const hasNormalizedRunShape =
301
- runColumns.has("finished_at") &&
302
- runColumns.has("revision") &&
303
- runColumns.has("outputs_by_node_json") &&
304
- !runColumns.has("state_json");
305
- if (!hasNormalizedRunShape) {
306
- return;
307
- }
308
- const runSlotProjectionColumns = await this.readSqliteTableColumns(client, "RunSlotProjection");
309
- await client.execute('DROP TABLE IF EXISTS "Run_legacy"');
310
- if (runSlotProjectionColumns.size > 0) {
311
- await client.execute('DROP TABLE IF EXISTS "RunProjection"');
312
- }
313
- } finally {
314
- client.close();
315
- }
316
- }
317
-
318
- private sqliteFilePathToDatabaseUrl(databaseFilePath: string): string {
319
- return `file:${path.resolve(databaseFilePath)}`;
320
- }
321
-
322
- private createProcessEnvironment(
323
- databaseUrl: string,
324
- provider: "postgresql" | "sqlite",
325
- env?: Readonly<NodeJS.ProcessEnv>,
326
- ): NodeJS.ProcessEnv {
327
- return {
328
- ...process.env,
329
- ...(env ?? {}),
330
- DATABASE_URL: databaseUrl,
331
- CODEMATION_PRISMA_PROVIDER: provider,
332
- };
333
- }
334
-
335
- private resolvePrismaCliPath(env: Readonly<NodeJS.ProcessEnv>): string {
336
- const configuredPath = env.CODEMATION_PRISMA_CLI_PATH;
337
- if (configuredPath && existsSync(configuredPath)) {
338
- return configuredPath;
339
- }
340
- const packageRoot = this.resolvePackageRoot(env);
341
- const packageManagerCandidates = [
342
- path.resolve(process.cwd(), "node_modules", "prisma", "build", "index.js"),
343
- path.resolve(packageRoot, "node_modules", "prisma", "build", "index.js"),
344
- ];
345
- for (const candidate of packageManagerCandidates) {
346
- if (existsSync(candidate)) {
347
- return candidate;
348
- }
349
- }
350
- try {
351
- return this.require.resolve("prisma/build/index.js", {
352
- paths: [process.cwd(), packageRoot],
353
- });
354
- } catch {
355
- throw new Error(
356
- "Unable to resolve the Prisma CLI required for startup migrations. Ensure `prisma` is installed.",
357
- );
358
- }
359
- }
360
-
361
- private resolveAbsolutePrismaConfigPath(env: Readonly<NodeJS.ProcessEnv>): string {
362
- const configuredPath = env.CODEMATION_PRISMA_CONFIG_PATH;
363
- const packageRoot = this.resolvePackageRoot(env);
364
- if (configuredPath) {
365
- return path.isAbsolute(configuredPath) ? configuredPath : path.resolve(packageRoot, configuredPath);
366
- }
367
- return path.resolve(packageRoot, "prisma.config.ts");
368
- }
369
-
370
- resolvePackageRoot(env: Readonly<NodeJS.ProcessEnv> = process.env): string {
371
- const configuredRoot = env.CODEMATION_HOST_PACKAGE_ROOT;
372
- if (configuredRoot) {
373
- return configuredRoot;
374
- }
375
- let currentDirectory = path.dirname(fileURLToPath(import.meta.url));
376
- for (let depth = 0; depth < 8; depth += 1) {
377
- if (existsSync(path.join(currentDirectory, "prisma", "schema.postgresql.prisma"))) {
378
- return currentDirectory;
379
- }
380
- const parentDirectory = path.dirname(currentDirectory);
381
- if (parentDirectory === currentDirectory) {
382
- break;
383
- }
384
- currentDirectory = parentDirectory;
385
- }
386
- throw new Error(`Could not locate prisma/schema.postgresql.prisma near ${fileURLToPath(import.meta.url)}.`);
387
- }
388
-
389
- private async ensureSqliteParentDirectoryExists(databaseFilePath: string): Promise<void> {
390
- await mkdir(path.dirname(databaseFilePath), { recursive: true });
391
- }
392
-
393
- private createDeployError(exitCode: number | null, stdout: string, stderr: string): Error {
394
- const output = stderr.trim() || stdout.trim();
395
- if (!output) {
396
- return new Error(`Prisma migrate deploy failed during startup with exit code ${exitCode ?? "unknown"}.`);
397
- }
398
- return new Error(`Prisma migrate deploy failed during startup with exit code ${exitCode ?? "unknown"}.\n${output}`);
61
+ async resolvePackageRoot(env: Readonly<NodeJS.ProcessEnv> = process.env): Promise<string> {
62
+ const { PrismaMigrationOperations } = await import(
63
+ /* webpackIgnore: true */
64
+ /* @vite-ignore */
65
+ implSpecifier
66
+ );
67
+ // eslint-disable-next-line codemation/no-manual-di-new -- lazy-loaded impl; cannot be registered in DI without breaking the bootstrap chain
68
+ return new PrismaMigrationOperations().resolvePackageRoot(env);
399
69
  }
400
70
  }