gencow 0.1.230 → 0.1.232

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/core/index.js CHANGED
@@ -3016,6 +3016,7 @@ var RESERVED_TENANT_RUNTIME_ENV_KEYS = [
3016
3016
  "GENCOW_PLATFORM_DB",
3017
3017
  "GENCOW_MANAGED_PUBLIC_ORIGINS",
3018
3018
  "PLATFORM_OPENAI_KEY",
3019
+ "PLATFORM_OPENROUTER_KEY",
3019
3020
  "PLATFORM_GOOGLE_KEY",
3020
3021
  "PLATFORM_GOOGLE_CLIENT_ID",
3021
3022
  "PLATFORM_GOOGLE_CLIENT_SECRET",
@@ -445,7 +445,9 @@ function createDbMigrationCommand(deps, command) {
445
445
  emitFailure(
446
446
  error?.code === "MIGRATION_BUNDLE_LOCAL_INTEGRITY_FAILED"
447
447
  ? buildLocalBundleIntegrityFailureDiagnostic(clientCorrelationId())
448
- : buildClientBundleFailureDiagnostic(clientCorrelationId(), retryCommand),
448
+ : error?.code === "MIGRATION_BUNDLE_SCHEMA_CHANGE_WITHOUT_FORWARD_MIGRATION"
449
+ ? buildClientUserFixableDiagnostic(error.code, clientCorrelationId())
450
+ : buildClientBundleFailureDiagnostic(clientCorrelationId(), retryCommand),
449
451
  );
450
452
  return;
451
453
  }
@@ -42,6 +42,7 @@ import {
42
42
  } from "./deploy-lockfile-selection.mjs";
43
43
  import { detectProjectConfigFile, warnProjectConfigSelectionOnce } from "./project-config-selection.mjs";
44
44
  import { writeCanonicalDeployBundle } from "./deploy-bundle-staging.mjs";
45
+ import { buildClientUserFixableDiagnostic } from "./migration-client-diagnostic.mjs";
45
46
 
46
47
  async function loadProjectConfigDefault(options) {
47
48
  const { loadConfig } = await import("./cli-project-runtime.mjs");
@@ -484,7 +485,15 @@ export function createDeployPackageRuntime({
484
485
  writeFileSyncImpl,
485
486
  }));
486
487
  } catch (caught) {
487
- errorImpl(`Packaging failed: ${caught.message}`);
488
+ if (caught?.code === "MIGRATION_BUNDLE_SCHEMA_CHANGE_WITHOUT_FORWARD_MIGRATION") {
489
+ for (const line of renderMigrationDiagnosticLines(
490
+ buildClientUserFixableDiagnostic(caught.code, `client-${Date.now()}`),
491
+ )) {
492
+ errorImpl(line);
493
+ }
494
+ } else {
495
+ errorImpl(`Packaging failed: ${caught.message}`);
496
+ }
488
497
  exitImpl(1);
489
498
  cleanupPreparedDeploySource();
490
499
  return null;
@@ -25,7 +25,12 @@ import {
25
25
  import { emitMigrationAdvisories, emitMigrationResponsibilityNotice } from "./migration-advisory.mjs";
26
26
  import { resolveCreatedAppResponse } from "./app-create-response.mjs";
27
27
  import { readProjectDisplayName } from "./cli-project-runtime.mjs";
28
- import { getCliInvocationSelection, resolveProjectMetadataPath, resolveProjectSelection } from "./project-context.mjs";
28
+ import {
29
+ getCliInvocationSelection,
30
+ resolveProjectMetadataPath,
31
+ resolveProjectSelection,
32
+ } from "./project-context.mjs";
33
+ import { buildClientUserFixableDiagnostic } from "./migration-client-diagnostic.mjs";
29
34
 
30
35
  export function resolveDevCloudAppName({
31
36
  cloudArgs,
@@ -264,20 +269,35 @@ export function createDevCloudCommand(deps) {
264
269
  ({ createDevCloudDeployBundle: createDevCloudDeployBundleImpl } =
265
270
  await import("./dev-cloud-bundle.mjs"));
266
271
  }
267
- const { bundle, auditMessage } = await createDevCloudDeployBundleImpl({
268
- cwd: projectDir,
269
- rootDir: functionsDir,
270
- envFile: config.envFile,
271
- cliVersion: deps.cliVersion ?? "0.0.0",
272
- generatorVersion: migrationGeneratorVersion,
273
- config,
274
- existsSyncImpl: deps.existsSyncImpl,
275
- mkdirSyncImpl: deps.mkdirSyncImpl,
276
- readFileSyncImpl: deps.readFileSyncImpl,
277
- resolvePathImpl: deps.resolvePathImpl,
278
- warnImpl: deps.warnImpl,
279
- writeFileSyncImpl: deps.writeFileSyncImpl,
280
- });
272
+ let bundle;
273
+ let auditMessage;
274
+ try {
275
+ ({ bundle, auditMessage } = await createDevCloudDeployBundleImpl({
276
+ cwd: projectDir,
277
+ rootDir: functionsDir,
278
+ envFile: config.envFile,
279
+ cliVersion: deps.cliVersion ?? "0.0.0",
280
+ generatorVersion: migrationGeneratorVersion,
281
+ config,
282
+ existsSyncImpl: deps.existsSyncImpl,
283
+ mkdirSyncImpl: deps.mkdirSyncImpl,
284
+ readFileSyncImpl: deps.readFileSyncImpl,
285
+ resolvePathImpl: deps.resolvePathImpl,
286
+ warnImpl: deps.warnImpl,
287
+ writeFileSyncImpl: deps.writeFileSyncImpl,
288
+ }));
289
+ } catch (error) {
290
+ if (error?.code === "MIGRATION_BUNDLE_SCHEMA_CHANGE_WITHOUT_FORWARD_MIGRATION") {
291
+ for (const line of renderMigrationDiagnosticLines(
292
+ buildClientUserFixableDiagnostic(error.code, `client-${Date.now()}`),
293
+ )) {
294
+ deps.errorImpl(line);
295
+ }
296
+ } else {
297
+ throw error;
298
+ }
299
+ return;
300
+ }
281
301
  if (auditMessage) deps.logImpl(auditMessage);
282
302
  const protocolHeaders = await buildDevCloudDeployProtocolHeaders({
283
303
  bundle,
@@ -483,7 +503,8 @@ ${deps.BOLD}${deps.CYAN}🚀 Gencow Cloud Dev${deps.RESET}
483
503
  const GENERATED_FILES = new Set(["api.ts", "README.md"]);
484
504
  watchImpl(absoluteFunctions, { recursive: true }, (_eventType, filename) => {
485
505
  if (!filename || filename.includes("node_modules")) return;
486
- if (isServerCodegenWatchPath(filename, projectDir, config, { resolvePathImpl: deps.resolvePathImpl })) return;
506
+ if (isServerCodegenWatchPath(filename, projectDir, config, { resolvePathImpl: deps.resolvePathImpl }))
507
+ return;
487
508
  const base = (filename.includes("/") ? filename.split("/").pop() : filename) ?? filename;
488
509
  if (GENERATED_FILES.has(base)) return;
489
510
  pendingFile = filename;
@@ -7,6 +7,7 @@ import {
7
7
  MIGRATION_BUNDLE_PROTOCOL_VERSION,
8
8
  parseMigrationFolderMillis,
9
9
  } from "@gencow/migration-contract";
10
+ import { deriveTenantCapabilities, isAuthSchemaSourcePath } from "./tenant-capability-compat.mjs";
10
11
 
11
12
  export { MIGRATION_BUNDLE_CAPABILITY_V1, MIGRATION_BUNDLE_MANIFEST_PATH, MIGRATION_BUNDLE_PROTOCOL_VERSION };
12
13
 
@@ -133,6 +134,37 @@ function bundleFileRows(backendRoot, directory = backendRoot) {
133
134
  return rows;
134
135
  }
135
136
 
137
+ function tenantCapabilitySources(backendRoot, directory = backendRoot) {
138
+ const sources = [];
139
+ for (const entry of readdirSync(directory).sort()) {
140
+ const path = resolve(directory, entry);
141
+ const stats = lstatSync(path);
142
+ if (stats.isDirectory()) {
143
+ sources.push(...tenantCapabilitySources(backendRoot, path));
144
+ continue;
145
+ }
146
+ const manifestPath = canonicalMigrationManifestPath(relative(backendRoot, path));
147
+ if (stats.isFile() && isAuthSchemaSourcePath(manifestPath)) {
148
+ sources.push({ path: manifestPath, source: readFileSync(path, "utf8") });
149
+ }
150
+ }
151
+ return sources;
152
+ }
153
+
154
+ function hasRateLimitCreationMigration(migrations) {
155
+ return migrations.some(({ path }) =>
156
+ /\bCREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?(?:["']?public["']?\.)?["']?rate_limit["']?\b/iu.test(
157
+ readFileSync(path, "utf8"),
158
+ ),
159
+ );
160
+ }
161
+
162
+ function schemaChangeWithoutForwardMigrationError() {
163
+ const error = new Error("MIGRATION_BUNDLE_SCHEMA_CHANGE_WITHOUT_FORWARD_MIGRATION");
164
+ error.code = "MIGRATION_BUNDLE_SCHEMA_CHANGE_WITHOUT_FORWARD_MIGRATION";
165
+ return error;
166
+ }
167
+
136
168
  function schemaSourceRows(backendRoot, schemaSourcePaths) {
137
169
  if (schemaSourcePaths === undefined) return null;
138
170
  const rows = [];
@@ -183,6 +215,13 @@ export function unsignedMigrationBundleManifest({
183
215
  sqlSha256: sha256(readFileSync(migration.path)),
184
216
  requiredCapabilities: [MIGRATION_BUNDLE_CAPABILITY_V1],
185
217
  }));
218
+ const requiredTenantCapabilities = deriveTenantCapabilities(tenantCapabilitySources(backendRoot));
219
+ if (
220
+ requiredTenantCapabilities.length > 0 &&
221
+ !hasRateLimitCreationMigration(migrationSourceFiles(backendRoot))
222
+ ) {
223
+ throw schemaChangeWithoutForwardMigrationError();
224
+ }
186
225
  return {
187
226
  protocolVersion: MIGRATION_BUNDLE_PROTOCOL_VERSION,
188
227
  generator: "drizzle-kit",
@@ -191,6 +230,7 @@ export function unsignedMigrationBundleManifest({
191
230
  cliVersion,
192
231
  ...(sources === null ? {} : { schemaSources: sources }),
193
232
  schemaSnapshotHash: sources === null ? sha256(readFileSync(schemaPath)) : sha256(JSON.stringify(sources)),
233
+ ...(requiredTenantCapabilities.length > 0 ? { requiredTenantCapabilities } : {}),
194
234
  migrations,
195
235
  };
196
236
  }
@@ -48,6 +48,12 @@ const USER_FIXABLE_FAILURES = {
48
48
  detail:
49
49
  "Fix schema imports, types, or Drizzle configuration before uploading any existing migration bundle.",
50
50
  },
51
+ MIGRATION_BUNDLE_SCHEMA_CHANGE_WITHOUT_FORWARD_MIGRATION: {
52
+ phase: "PREFLIGHT",
53
+ target: "PENDING_MIGRATION",
54
+ summary: "The durable Auth schema changed without a forward migration.",
55
+ detail: "Run gencow db:generate, review the rate_limit migration, then create a fresh migration bundle.",
56
+ },
51
57
  MIGRATION_CHECK_CLOUD_ONLY: {
52
58
  phase: "PREFLIGHT",
53
59
  target: "PROJECT_CONFIG",
@@ -0,0 +1,49 @@
1
+ import * as migrationContract from "@gencow/migration-contract";
2
+
3
+ const AUTH_SCHEMA_FILE_PATTERN = /(?:^|\/)(?:schema-auth|auth-schema)(?:\.gen)?\.(?:c|m)?(?:j|t)s$/u;
4
+ const AUTH_RATE_LIMIT_CAPABILITY = "tenant.auth.rate_limit.v1";
5
+
6
+ function removeComments(source) {
7
+ return source.replace(/\/\*[\s\S]*?\*\//gu, " ").replace(/(^|[^:])\/\/.*$/gmu, "$1 ");
8
+ }
9
+
10
+ function fallbackIsAuthSchemaSourcePath(path) {
11
+ return AUTH_SCHEMA_FILE_PATTERN.test(String(path).replaceAll("\\", "/"));
12
+ }
13
+
14
+ function fallbackSourceDeclaresDurableAuthRateLimit(source) {
15
+ const normalized = removeComments(source);
16
+ return (
17
+ /\b(?:export\s+)?const\s+rateLimit\s*=\s*pgTable\s*\(\s*["']rate_limit["']/u.test(normalized) ||
18
+ /\b(?:export\s+)?const\s+rateLimits\s*=\s*pgTable\s*\(\s*["']rate_limit["']/u.test(normalized)
19
+ );
20
+ }
21
+
22
+ // A CLI can be linked against an older published migration-contract while the
23
+ // Platform has already learned a newer source shape (for example generated
24
+ // schema-auth.gen.ts). Capability detection must therefore be monotonic: an
25
+ // older contract may not make a bundle under-declare a capability that the
26
+ // Platform will enforce.
27
+ export function createTenantCapabilityCompatibility(contract = migrationContract) {
28
+ return {
29
+ isAuthSchemaSourcePath: (path) =>
30
+ Boolean(contract.isAuthSchemaSourcePath?.(path)) || fallbackIsAuthSchemaSourcePath(path),
31
+ deriveTenantCapabilities: (sources) => {
32
+ const contractCapabilities =
33
+ typeof contract.deriveTenantCapabilities === "function"
34
+ ? contract.deriveTenantCapabilities(sources)
35
+ : [];
36
+ const fallbackCapabilities = sources.some(
37
+ (source) =>
38
+ fallbackIsAuthSchemaSourcePath(source.path) &&
39
+ fallbackSourceDeclaresDurableAuthRateLimit(source.source),
40
+ )
41
+ ? [AUTH_RATE_LIMIT_CAPABILITY]
42
+ : [];
43
+ return [...new Set([...contractCapabilities, ...fallbackCapabilities])];
44
+ },
45
+ };
46
+ }
47
+
48
+ const compatibility = createTenantCapabilityCompatibility();
49
+ export const { isAuthSchemaSourcePath, deriveTenantCapabilities } = compatibility;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gencow",
3
- "version": "0.1.230",
3
+ "version": "0.1.232",
4
4
  "description": "Gencow — AI Backend Engine",
5
5
  "type": "module",
6
6
  "bin": {
@@ -20,7 +20,7 @@
20
20
  "dashboard/"
21
21
  ],
22
22
  "dependencies": {
23
- "@gencow/migration-contract": "0.1.13",
23
+ "@gencow/migration-contract": "0.1.16",
24
24
  "@modelcontextprotocol/sdk": "^1.29.0",
25
25
  "drizzle-kit": "1.0.0-rc.4",
26
26
  "drizzle-orm": "1.0.0-rc.4",
@@ -37,9 +37,9 @@
37
37
  "ai": "^6.0.233",
38
38
  "@types/node": "^25.9.5",
39
39
  "better-auth": "^1.6.23",
40
- "@gencow/core": "0.1.44",
41
40
  "@gencow/client": "0.2.7",
42
- "@gencow/react": "0.2.7"
41
+ "@gencow/react": "0.2.7",
42
+ "@gencow/core": "0.1.44"
43
43
  },
44
44
  "scripts": {
45
45
  "prebuild": "pnpm --filter @gencow/migration-contract run build && pnpm --filter @gencow/server run build",