gencow 0.1.190 → 0.1.191

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.
@@ -16,6 +16,7 @@ const APP_DELETE_ACTIVE_STATES = new Set([
16
16
  "failed_tenant_db_cleanup",
17
17
  "failed_catalog_cleanup",
18
18
  ]);
19
+ const APP_DELETE_TERMINAL_FAILURE_STATES = new Set(["blocked_catalog_outcome_unknown"]);
19
20
  const CORRELATION_ID_PATTERN = /^[A-Za-z0-9_-]{8,128}$/u;
20
21
  const DIAGNOSTIC_CODE_PATTERN = /^[A-Za-z][A-Za-z0-9_]{2,127}$/u;
21
22
  const APP_ID_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+){2}$/u;
@@ -74,6 +75,20 @@ function isMatchingTerminalResponse(value, accepted, expectedAppId) {
74
75
  );
75
76
  }
76
77
 
78
+ function isMatchingTerminalFailureResponse(value, accepted, expectedAppId) {
79
+ return (
80
+ value &&
81
+ typeof value === "object" &&
82
+ !Array.isArray(value) &&
83
+ value.success === false &&
84
+ value.deleted === expectedAppId &&
85
+ value.operationId === accepted.operationId &&
86
+ value.correlationId === accepted.correlationId &&
87
+ APP_DELETE_TERMINAL_FAILURE_STATES.has(value.state) &&
88
+ DIAGNOSTIC_CODE_PATTERN.test(value.code ?? "")
89
+ );
90
+ }
91
+
77
92
  export async function pollAppDeleteOperation({
78
93
  creds,
79
94
  initialBody,
@@ -99,6 +114,9 @@ export async function pollAppDeleteOperation({
99
114
  if (!statusRes?.ok || !statusData) return invalidPollResponse(accepted);
100
115
  successfulPollResponses += 1;
101
116
  if (isMatchingTerminalResponse(statusData, accepted, name)) return statusData;
117
+ if (isMatchingTerminalFailureResponse(statusData, accepted, name)) {
118
+ return { ...statusData, error: statusData.code };
119
+ }
102
120
  const operation = parseAppDeleteOperationEnvelope(statusData, name);
103
121
  if (
104
122
  !operation ||
@@ -0,0 +1,7 @@
1
+ import { posix, win32 } from "node:path";
2
+
3
+ export function toDrizzleProjectPath(projectRoot, canonicalFile) {
4
+ const pathApi = win32.isAbsolute(projectRoot) ? win32 : posix;
5
+ const projectPath = pathApi.relative(projectRoot, canonicalFile).replaceAll("\\", "/");
6
+ return projectPath.startsWith(".") ? projectPath : `./${projectPath}`;
7
+ }
@@ -1,6 +1,8 @@
1
1
  import { existsSync, lstatSync, realpathSync } from "node:fs";
2
2
  import { basename, dirname, isAbsolute, relative, resolve } from "node:path";
3
3
 
4
+ import { toDrizzleProjectPath } from "./canonical-drizzle-generator-paths.mjs";
5
+
4
6
  export const GENCOW_DRIZZLE_CONFIG_CONTRACT_VERSION = 1;
5
7
 
6
8
  function requiredEnvironment(name) {
@@ -38,7 +40,7 @@ function resolveSchemaFiles(projectRoot) {
38
40
  }
39
41
  if (!seen.has(canonical)) {
40
42
  seen.add(canonical);
41
- files.push(canonical);
43
+ files.push(toDrizzleProjectPath(projectRoot, canonical));
42
44
  }
43
45
  }
44
46
  if (files.length === 0) throw new Error("Gencow migration schema list is empty");
@@ -6,6 +6,7 @@ export const REQUIRED_CLI_TARBALL_FILES = Object.freeze([
6
6
  "lib/cron-manifest.mjs",
7
7
  "lib/dev-cloud-bundle.mjs",
8
8
  "lib/canonical-drizzle-generator.config.mjs",
9
+ "lib/canonical-drizzle-generator-paths.mjs",
9
10
  "runtime/tooling.mjs",
10
11
  "runtime/server.mjs",
11
12
  "templateFeature/ai/manifest.json",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gencow",
3
- "version": "0.1.190",
3
+ "version": "0.1.191",
4
4
  "description": "Gencow — AI Backend Engine",
5
5
  "type": "module",
6
6
  "bin": {
@@ -33,8 +33,8 @@
33
33
  "devDependencies": {
34
34
  "@types/node": "^25.9.5",
35
35
  "better-auth": "^1.6.23",
36
- "@gencow/core": "0.1.42",
37
36
  "@gencow/client": "0.2.6",
37
+ "@gencow/core": "0.1.42",
38
38
  "@gencow/react": "0.2.6"
39
39
  },
40
40
  "scripts": {
@@ -88955,7 +88955,7 @@ var require_package = __commonJS({
88955
88955
  var require_libvips = __commonJS({
88956
88956
  "../../node_modules/.pnpm/sharp@0.34.5/node_modules/sharp/lib/libvips.js"(exports2, module2) {
88957
88957
  var { spawnSync } = __require("node:child_process");
88958
- var { createHash: createHash24 } = __require("node:crypto");
88958
+ var { createHash: createHash25 } = __require("node:crypto");
88959
88959
  var semverCoerce = require_coerce();
88960
88960
  var semverGreaterThanOrEqualTo = require_gte();
88961
88961
  var semverSatisfies = require_satisfies();
@@ -89046,7 +89046,7 @@ var require_libvips = __commonJS({
89046
89046
  }
89047
89047
  return false;
89048
89048
  };
89049
- var sha512 = (s) => createHash24("sha512").update(s).digest("hex");
89049
+ var sha512 = (s) => createHash25("sha512").update(s).digest("hex");
89050
89050
  var yarnLocator = () => {
89051
89051
  try {
89052
89052
  const identHash = sha512(`imgsharp-libvips-${buildPlatformArch()}`);
@@ -94229,7 +94229,29 @@ var init_platform_active_slot = __esm({
94229
94229
  });
94230
94230
 
94231
94231
  // ../server/dist/server-platform-slot-promotion.js
94232
- import { timingSafeEqual as timingSafeEqual6 } from "crypto";
94232
+ import { createHash as createHash17, timingSafeEqual as timingSafeEqual6 } from "crypto";
94233
+ function parseRouteProjectionDegradedApprovalHeaders(input) {
94234
+ if (input.count === void 0 && input.digest === void 0 && input.appIds === void 0)
94235
+ return void 0;
94236
+ if (!/^[1-9][0-9]{0,3}$/u.test(input.count ?? "") || !/^[a-f0-9]{64}$/u.test(input.digest ?? "") || !/^[1-9][0-9]{0,9}(,[1-9][0-9]{0,9}){0,999}$/u.test(input.appIds ?? "")) {
94237
+ throw new Error("PLATFORM_ROUTE_DEGRADED_APPROVAL_INVALID");
94238
+ }
94239
+ const readyMismatchAppCount = Number(input.count);
94240
+ const readyMismatchAppIds = input.appIds.split(",").map(Number);
94241
+ const canonicalAppIds = [...new Set(readyMismatchAppIds)].sort((left, right) => left - right);
94242
+ const canonicalDigest = createHash17("sha256").update(JSON.stringify(canonicalAppIds)).digest("hex");
94243
+ if (!Number.isSafeInteger(readyMismatchAppCount) || readyMismatchAppCount > 1e3 || readyMismatchAppIds.some((appId) => !Number.isSafeInteger(appId) || appId <= 0) || readyMismatchAppIds.some((appId, index) => appId !== canonicalAppIds[index]) || readyMismatchAppCount !== canonicalAppIds.length || input.digest !== canonicalDigest) {
94244
+ throw new Error("PLATFORM_ROUTE_DEGRADED_APPROVAL_INVALID");
94245
+ }
94246
+ return { readyMismatchAppCount, readyMismatchAppDigest: input.digest, readyMismatchAppIds };
94247
+ }
94248
+ function routeProjectionDegradedApprovalFromRequest(context2) {
94249
+ return parseRouteProjectionDegradedApprovalHeaders({
94250
+ count: context2.req.header(ROUTE_READY_MISMATCH_COUNT_HEADER),
94251
+ digest: context2.req.header(ROUTE_READY_MISMATCH_DIGEST_HEADER),
94252
+ appIds: context2.req.header(ROUTE_READY_MISMATCH_APP_IDS_HEADER)
94253
+ });
94254
+ }
94233
94255
  function applyPlatformSlotPromotionRequestTimeout(request, server) {
94234
94256
  if (!["/internal/platform-slot/prepare-promotion", "/internal/platform-slot/promote"].includes(new URL(request.url).pathname))
94235
94257
  return false;
@@ -94274,8 +94296,9 @@ function registerPlatformSlotPromotionRoute(params) {
94274
94296
  }
94275
94297
  try {
94276
94298
  state2 = "preparing";
94299
+ const approval = routeProjectionDegradedApprovalFromRequest(c);
94277
94300
  const result2 = await params.autoRecoveryRuntime.promote();
94278
- const evidence = await params.assertTenantRouteParity?.();
94301
+ const evidence = await params.assertTenantRouteParity?.(approval);
94279
94302
  if (!evidence?.evidenceDigest || !Number.isSafeInteger(evidence.bindingCount)) {
94280
94303
  throw new Error("PLATFORM_TENANT_ROUTE_PARITY_EVIDENCE_INVALID");
94281
94304
  }
@@ -94306,7 +94329,8 @@ function registerPlatformSlotPromotionRoute(params) {
94306
94329
  return c.json({ status: "prepared", promoted: false, code: "PLATFORM_SLOT_NOT_ROUTED" }, 409);
94307
94330
  }
94308
94331
  state2 = "promoting";
94309
- const currentEvidence = await params.assertTenantRouteParity?.();
94332
+ const approval = routeProjectionDegradedApprovalFromRequest(c);
94333
+ const currentEvidence = await params.assertTenantRouteParity?.(approval);
94310
94334
  if (!currentEvidence || currentEvidence.bindingCount !== preparedEvidence.bindingCount || currentEvidence.evidenceDigest !== preparedEvidence.evidenceDigest)
94311
94335
  throw new Error("PLATFORM_TENANT_ROUTE_PARITY_EVIDENCE_CHANGED");
94312
94336
  params.setCandidateMode(false);
@@ -94322,11 +94346,14 @@ function registerPlatformSlotPromotionRoute(params) {
94322
94346
  }
94323
94347
  });
94324
94348
  }
94325
- var PLATFORM_SLOT_PROMOTION_REQUEST_TIMEOUT_SECONDS;
94349
+ var ROUTE_READY_MISMATCH_COUNT_HEADER, ROUTE_READY_MISMATCH_DIGEST_HEADER, ROUTE_READY_MISMATCH_APP_IDS_HEADER, PLATFORM_SLOT_PROMOTION_REQUEST_TIMEOUT_SECONDS;
94326
94350
  var init_server_platform_slot_promotion = __esm({
94327
94351
  "../server/dist/server-platform-slot-promotion.js"() {
94328
94352
  "use strict";
94329
94353
  init_platform_active_slot();
94354
+ ROUTE_READY_MISMATCH_COUNT_HEADER = "X-Gencow-Emergency-Route-Ready-Mismatch-Count";
94355
+ ROUTE_READY_MISMATCH_DIGEST_HEADER = "X-Gencow-Emergency-Route-Ready-Mismatch-Digest";
94356
+ ROUTE_READY_MISMATCH_APP_IDS_HEADER = "X-Gencow-Emergency-Route-Ready-Mismatch-App-Ids";
94330
94357
  PLATFORM_SLOT_PROMOTION_REQUEST_TIMEOUT_SECONDS = 240;
94331
94358
  }
94332
94359
  });
@@ -134090,7 +134117,7 @@ var init_official_custom_domains = __esm({
134090
134117
  });
134091
134118
 
134092
134119
  // ../server/dist/server-platform-app-proxy-domain.js
134093
- import { createHash as createHash21 } from "crypto";
134120
+ import { createHash as createHash22 } from "crypto";
134094
134121
  function stripTrailingDots(host) {
134095
134122
  return host.replace(/\.+$/, "");
134096
134123
  }
@@ -134169,7 +134196,7 @@ function decodeActiveDomainApp(value, expectedHostname) {
134169
134196
  return { appName, verifiedBinding };
134170
134197
  }
134171
134198
  function hashCustomDomainHost(host) {
134172
- return createHash21("sha256").update(host).digest("hex").slice(0, 16);
134199
+ return createHash22("sha256").update(host).digest("hex").slice(0, 16);
134173
134200
  }
134174
134201
  function isGenerationChangedError(error51) {
134175
134202
  return error51?.code === "GENCOW_DB_GENERATION_CHANGED";
@@ -134409,7 +134436,7 @@ var init_server_platform_media_worker_storage_runtime = __esm({
134409
134436
  });
134410
134437
 
134411
134438
  // ../server/dist/server-runtime-activity-store.js
134412
- import { createHash as createHash22 } from "node:crypto";
134439
+ import { createHash as createHash23 } from "node:crypto";
134413
134440
  function rowsFromUnknown3(result2) {
134414
134441
  if (Array.isArray(result2))
134415
134442
  return result2;
@@ -134660,7 +134687,7 @@ async function checkpointRuntimeActivity(rawSql, checkpoint) {
134660
134687
  if (!Number.isSafeInteger(appId) || !Number.isSafeInteger(activityEpoch) || activityEpoch < 0 || !nodeId || !Number.isSafeInteger(nodeEpoch) || nodeEpoch < 0) {
134661
134688
  return null;
134662
134689
  }
134663
- const bindingHash = createHash22("sha256").update(JSON.stringify([
134690
+ const bindingHash = createHash23("sha256").update(JSON.stringify([
134664
134691
  "runtime-activity-binding-v1",
134665
134692
  appId,
134666
134693
  typeof row?.activation_id === "string" ? row.activation_id : null,
@@ -149672,7 +149699,7 @@ var init_server_platform_notification_worker_runtime = __esm({
149672
149699
  });
149673
149700
 
149674
149701
  // ../server/src/server-node-proxy-signature.ts
149675
- import { createHash as createHash23, createHmac as createHmac9, timingSafeEqual as timingSafeEqual11 } from "crypto";
149702
+ import { createHash as createHash24, createHmac as createHmac9, timingSafeEqual as timingSafeEqual11 } from "crypto";
149676
149703
  function sanitizeNodeProxyForwardHeaders(headers) {
149677
149704
  const sanitized = new Headers();
149678
149705
  for (const [key, value] of headers.entries()) {
@@ -149695,7 +149722,7 @@ function canonicalSignedHeaderHash2(headers) {
149695
149722
  const value = headers?.get(name) ?? "";
149696
149723
  return `${name}:${value.trim()}`;
149697
149724
  }).join("\n");
149698
- return createHash23("sha256").update(canonical).digest("base64url");
149725
+ return createHash24("sha256").update(canonical).digest("base64url");
149699
149726
  }
149700
149727
  function buildSignaturePayload2(input) {
149701
149728
  return [
@@ -150030,17 +150057,22 @@ var init_server_platform_runtime_helpers = __esm({
150030
150057
 
150031
150058
  // ../server/dist/server-legacy-runtime-recovery-route.js
150032
150059
  import { resolve as resolve45 } from "node:path";
150060
+ function parseLegacyRuntimeRecoveryMode(value) {
150061
+ return value === "preserve_replace" || value === "canonicalize_absent" || value === "retire_unadmitted" ? value : null;
150062
+ }
150033
150063
  function registerLegacyRuntimeRecoveryRoute(params) {
150034
150064
  const logger3 = params.logger ?? console;
150035
150065
  params.app.post("/internal/runtime-recovery/legacy-preserve-replace", async (c) => {
150036
- if (!params.internalToken || c.req.header("X-Internal-Token") !== params.internalToken) {
150066
+ if (params.internalToken && c.req.header("X-Internal-Token") !== params.internalToken) {
150037
150067
  return c.json({ error: "Forbidden" }, 403);
150038
150068
  }
150039
150069
  const body = await c.req.json().catch(() => null);
150040
150070
  const appName = typeof body?.appName === "string" ? body.appName.trim() : "";
150041
- const mode = body?.mode === "canonicalize_absent" ? "canonicalize_absent" : "preserve_replace";
150071
+ const mode = parseLegacyRuntimeRecoveryMode(body?.mode ?? "preserve_replace");
150042
150072
  if (!APP_NAME.test(appName))
150043
150073
  return c.json({ error: "appName required" }, 400);
150074
+ if (!mode)
150075
+ return c.json({ error: "recovery mode invalid" }, 400);
150044
150076
  if (!await params.isActiveController())
150045
150077
  return c.json({ error: "Controller unavailable" }, 409);
150046
150078
  try {
@@ -150060,6 +150092,63 @@ function registerLegacyRuntimeRecoveryRoute(params) {
150060
150092
  const result3 = await convergeMalformedLegacyAbsence({ db: params.db, appId: app2.id, appName, currentNodeId: params.currentNodeId, evidence });
150061
150093
  return c.json({ ok: true, result: result3 });
150062
150094
  }
150095
+ if (mode === "retire_unadmitted") {
150096
+ const { getRuntimeLifecycleActivityCoordinator } = await import(resolve45(params.functionsPath, "../src/runtime-lifecycle-activity-coordinator.ts"));
150097
+ const { stopRuntimeAuthoritatively } = await import(resolve45(params.functionsPath, "../src/runtime-control-plane-lifecycle.ts"));
150098
+ const { observeLegacyRuntimeEvidence } = await import(resolve45(params.functionsPath, "../src/legacy-runtime-evidence-observer.ts"));
150099
+ const { convergeMalformedLegacyAbsence } = await import(resolve45(params.functionsPath, "../src/runtime-malformed-legacy-absence-recovery.ts"));
150100
+ const currentRows = await params.db.select({
150101
+ port: params.appsTable.port,
150102
+ instanceId: params.appsTable.instanceId,
150103
+ dataDir: params.appsTable.dataDir,
150104
+ nodeId: params.appsTable.nodeId,
150105
+ currentDeployId: params.appsTable.currentDeployId,
150106
+ hasBackend: params.appsTable.hasBackend
150107
+ }).from(params.appsTable).where(params.eqOp(params.appsTable.id, app2.id)).limit(1);
150108
+ const current = currentRows[0];
150109
+ const activity = getRuntimeLifecycleActivityCoordinator();
150110
+ if (!current || !activity)
150111
+ throw new Error("LEGACY_RUNTIME_RETIRE_COORDINATOR_UNAVAILABLE");
150112
+ activity.closeExplicitDrainGate(app2.id, appName);
150113
+ try {
150114
+ await activity.synchronizeRuntimeBinding(app2.id, appName);
150115
+ await activity.waitForQuiescence(app2.id, appName);
150116
+ const before2 = await observeLegacyRuntimeEvidence({
150117
+ appName,
150118
+ appPort: current.port,
150119
+ appNodeId: params.currentNodeId,
150120
+ currentNodeId: params.currentNodeId
150121
+ });
150122
+ if (!before2.observationComplete || ![before2.directProcess, before2.warmPoolAssignment, before2.listener].includes("present") || before2.effectiveRoute !== "exact") {
150123
+ throw new Error("LEGACY_RUNTIME_RETIRE_INCUMBENT_NOT_EXACT");
150124
+ }
150125
+ await stopRuntimeAuthoritatively({
150126
+ db: params.db,
150127
+ app: { id: app2.id, name: appName, ...current },
150128
+ currentNodeId: params.currentNodeId,
150129
+ reason: "operator_stop",
150130
+ actorId: "legacy-runtime-recovery",
150131
+ actorKind: "platform",
150132
+ expectedAuthority: "observe"
150133
+ });
150134
+ const evidence = await observeLegacyRuntimeEvidence({
150135
+ appName,
150136
+ appPort: current.port,
150137
+ appNodeId: params.currentNodeId,
150138
+ currentNodeId: params.currentNodeId
150139
+ });
150140
+ const result3 = await convergeMalformedLegacyAbsence({
150141
+ db: params.db,
150142
+ appId: app2.id,
150143
+ appName,
150144
+ currentNodeId: params.currentNodeId,
150145
+ evidence
150146
+ });
150147
+ return c.json({ ok: true, result: result3 });
150148
+ } finally {
150149
+ activity.reopenDrainGate(app2.id);
150150
+ }
150151
+ }
150063
150152
  const { convergeLegacyRuntimeAuthority } = await import(resolve45(params.functionsPath, "../src/legacy-runtime-bootstrap-orchestrator.ts"));
150064
150153
  const result2 = await convergeLegacyRuntimeAuthority({
150065
150154
  db: params.db,
@@ -150644,7 +150733,7 @@ async function bootstrapPlatformRuntime(params) {
150644
150733
  const convergeRuntimeRouteProjection = requirePlatformExplicitStopConverger(autoRecoveryRuntime.convergeRuntimeRouteProjection);
150645
150734
  routeProjectionRuntime.setAbsentRouteConverger(convergeRuntimeRouteProjection);
150646
150735
  params.app.get("/internal/platform-recovery/status", (c) => c.json(autoRecoveryRuntime.snapshot()));
150647
- registerProcessPlatformSlotPromotionRoute(params.app, params.rawSql, autoRecoveryRuntime, () => routeProjectionRuntime.convergeServingFleet(), logger3);
150736
+ registerProcessPlatformSlotPromotionRoute(params.app, params.rawSql, autoRecoveryRuntime, (approval) => routeProjectionRuntime.convergeServingFleet(approval), logger3);
150648
150737
  const explicitStopReconciler = await preparePlatformExplicitStopReconcilerForRuntime({
150649
150738
  functionsPath: params.functionsPath,
150650
150739
  db: params.db,
@@ -152779,6 +152868,8 @@ function classifyPostgresClientFailure(error51) {
152779
152868
  }
152780
152869
  for (const item of chain2) {
152781
152870
  const code = errorCode(item);
152871
+ if (code === "GENCOW_DB_GENERATION_CHANGED")
152872
+ return "generation_changed";
152782
152873
  if (code === "ERR_POSTGRES_CONNECTION_CLOSED")
152783
152874
  return "connection_closed";
152784
152875
  if (code === "ECONNRESET" || code === "EPIPE")
@@ -152919,11 +153010,11 @@ function createRuntimePostgresGenerationManager(params) {
152919
153010
  let recoveryAttempts = 0;
152920
153011
  let recoveryTimer;
152921
153012
  const randomUnit = () => Math.min(0.999999999, Math.max(0, randomImpl()));
152922
- function createRecord(client, generation, jitterMs) {
153013
+ function createRecord(client, generation, jitterMs, state2 = "healthy") {
152923
153014
  return {
152924
153015
  client,
152925
153016
  generation,
152926
- state: "healthy",
153017
+ state: state2,
152927
153018
  inFlight: 0,
152928
153019
  closeStarted: false,
152929
153020
  admitAfter: Date.now() + jitterMs
@@ -152932,7 +153023,7 @@ function createRuntimePostgresGenerationManager(params) {
152932
153023
  function snapshot() {
152933
153024
  if (managerClosed)
152934
153025
  throw new RuntimePostgresGenerationError("GENCOW_DB_CLIENT_CLOSED");
152935
- if (active.state !== "healthy") {
153026
+ if (active.state !== "healthy" && active.state !== "suspect") {
152936
153027
  throw new RuntimePostgresGenerationError("GENCOW_DB_RECOVERY_CIRCUIT_OPEN");
152937
153028
  }
152938
153029
  return { client: active.client, generation: active.generation };
@@ -152951,7 +153042,7 @@ function createRuntimePostgresGenerationManager(params) {
152951
153042
  void params.closeClient(record2.client).finally(() => {
152952
153043
  record2.state = "closed";
152953
153044
  quarantined.delete(record2.generation);
152954
- if (!managerClosed && recoveryCircuitOpen && active.state !== "healthy" && quarantined.size < maxQuarantinedGenerations) {
153045
+ if (!managerClosed && recoveryCircuitOpen && active.state !== "healthy" && active.state !== "suspect" && quarantined.size < maxQuarantinedGenerations) {
152955
153046
  const reason = lastRecoveryReason ?? "generation_capacity_recovered";
152956
153047
  scheduleRecovery(reason);
152957
153048
  }
@@ -152965,14 +153056,20 @@ function createRuntimePostgresGenerationManager(params) {
152965
153056
  try {
152966
153057
  nextGeneration += 1;
152967
153058
  const jitterMs = recoveryJitterMaxMs === 0 ? 0 : Math.floor(randomUnit() * (recoveryJitterMaxMs + 1));
152968
- active = createRecord(params.createClient(), nextGeneration, jitterMs);
152969
- recoveryCircuitOpen = false;
152970
- recoveryAttempts = 0;
152971
- recoveryInFlight = false;
153059
+ active = createRecord(params.createClient(), nextGeneration, jitterMs, params.verifyClient ? "suspect" : "healthy");
152972
153060
  if (recoveryTimer) {
152973
153061
  clearTimeoutImpl(recoveryTimer);
152974
153062
  recoveryTimer = void 0;
152975
153063
  }
153064
+ if (params.verifyClient) {
153065
+ recoveryCircuitOpen = true;
153066
+ recoveryInFlight = true;
153067
+ active.verification = verifyReplacement(active, reason);
153068
+ return true;
153069
+ }
153070
+ recoveryCircuitOpen = false;
153071
+ recoveryAttempts = 0;
153072
+ recoveryInFlight = false;
152976
153073
  params.onRecovered?.({ generation: nextGeneration, reason });
152977
153074
  return true;
152978
153075
  } catch {
@@ -152980,8 +153077,39 @@ function createRuntimePostgresGenerationManager(params) {
152980
153077
  return false;
152981
153078
  }
152982
153079
  }
153080
+ async function verifyReplacement(record2, reason) {
153081
+ try {
153082
+ const waitMs = Math.max(0, record2.admitAfter - Date.now());
153083
+ if (waitMs > 0) {
153084
+ await new Promise((resolve50) => {
153085
+ const timer2 = setTimeoutImpl(resolve50, waitMs);
153086
+ timer2.unref?.();
153087
+ });
153088
+ }
153089
+ await params.verifyClient?.(record2.client);
153090
+ if (managerClosed || record2 !== active || record2.state !== "suspect") {
153091
+ return;
153092
+ }
153093
+ record2.state = "healthy";
153094
+ recoveryCircuitOpen = false;
153095
+ recoveryInFlight = false;
153096
+ recoveryAttempts = 0;
153097
+ params.onRecovered?.({ generation: record2.generation, reason });
153098
+ } catch {
153099
+ if (managerClosed || record2 !== active || record2.state !== "suspect") {
153100
+ return;
153101
+ }
153102
+ record2.state = "quarantined";
153103
+ quarantined.set(record2.generation, record2);
153104
+ lastRecoveryReason = reason;
153105
+ recoveryCircuitOpen = true;
153106
+ recoveryInFlight = false;
153107
+ closeWhenDrained(record2);
153108
+ scheduleRecovery(reason);
153109
+ }
153110
+ }
152983
153111
  function scheduleRecovery(reason) {
152984
- if (managerClosed || active.state === "healthy" || recoveryTimer)
153112
+ if (managerClosed || active.state === "healthy" || active.state === "suspect" || recoveryTimer)
152985
153113
  return;
152986
153114
  recoveryInFlight = true;
152987
153115
  recoveryAttempts += 1;
@@ -153007,12 +153135,18 @@ function createRuntimePostgresGenerationManager(params) {
153007
153135
  }
153008
153136
  record2.inFlight += 1;
153009
153137
  return Promise.resolve().then(async () => {
153138
+ if (record2.state === "suspect") {
153139
+ await record2.verification;
153140
+ }
153010
153141
  const waitMs = Math.max(0, record2.admitAfter - Date.now());
153011
153142
  if (waitMs > 0)
153012
153143
  await new Promise((resolve50) => setTimeout(resolve50, waitMs));
153013
- if (managerClosed || record2.state !== "healthy" || record2 !== active) {
153144
+ if (managerClosed || record2 !== active) {
153014
153145
  throw new RuntimePostgresGenerationError("GENCOW_DB_GENERATION_CHANGED");
153015
153146
  }
153147
+ if (record2.state !== "healthy") {
153148
+ throw new RuntimePostgresGenerationError("GENCOW_DB_RECOVERY_CIRCUIT_OPEN");
153149
+ }
153016
153150
  return work();
153017
153151
  }).finally(() => {
153018
153152
  record2.inFlight = Math.max(0, record2.inFlight - 1);
@@ -153108,6 +153242,33 @@ async function closeWithBoundedWait(client, timeoutMs) {
153108
153242
  } catch {
153109
153243
  }
153110
153244
  }
153245
+ async function verifyRuntimePostgresConnectivity(client, timeoutMs) {
153246
+ const pending = client.unsafe("SELECT 1");
153247
+ let timeout;
153248
+ try {
153249
+ await Promise.race([
153250
+ pending,
153251
+ new Promise((_3, reject2) => {
153252
+ timeout = setTimeout(() => {
153253
+ try {
153254
+ pending.cancel?.();
153255
+ } catch {
153256
+ }
153257
+ reject2(new RuntimePostgresClientError({
153258
+ reason: "operation_timeout",
153259
+ operation: "unsafe",
153260
+ generation: 0,
153261
+ elapsedMs: timeoutMs
153262
+ }));
153263
+ }, timeoutMs);
153264
+ timeout.unref?.();
153265
+ })
153266
+ ]);
153267
+ } finally {
153268
+ if (timeout !== void 0)
153269
+ clearTimeout(timeout);
153270
+ }
153271
+ }
153111
153272
  function rowsToValues(rows4) {
153112
153273
  if (!Array.isArray(rows4))
153113
153274
  return rows4;
@@ -153140,6 +153301,7 @@ function createRuntimePostgresClient(params) {
153140
153301
  let lastFailure;
153141
153302
  const generations = createRuntimePostgresGenerationManager({
153142
153303
  createClient: params.createClient,
153304
+ verifyClient: (client) => verifyRuntimePostgresConnectivity(client, operationTimeoutMs > 0 ? operationTimeoutMs : 1e4),
153143
153305
  closeClient: (client) => closeWithBoundedWait(client, 250),
153144
153306
  recoveryJitterMaxMs: params.recoveryJitterMaxMs ?? (operationTimeoutMs > 0 ? Math.max(1, Math.min(25, Math.floor(operationTimeoutMs / 4))) : 25),
153145
153307
  onRecovered: ({ generation, reason }) => {
@@ -153177,7 +153339,7 @@ function createRuntimePostgresClient(params) {
153177
153339
  const causeCode = recordedFailure.causeCode ? ` causeCode=${recordedFailure.causeCode}` : "";
153178
153340
  const causeName = recordedFailure.causeName ? ` causeName=${recordedFailure.causeName}` : "";
153179
153341
  logger3.warn(`[db] postgres client quarantined label=${params.label}${dbInfo} reason=${recycleParams.reason} operation=${recycleParams.operation} elapsedMs=${recycleParams.elapsedMs} generation=${recycleParams.expectedGeneration} recoveries=${recoveries}${causeName}${causeCode}`);
153180
- if (!recovery.recovered) {
153342
+ if (diagnostics.recoveryCircuitOpen) {
153181
153343
  logger3.warn(`[db] postgres client circuit_open label=${params.label}${dbInfo} reason=${recycleParams.reason} generation=${diagnostics.activeGeneration} recoveries=${recoveries}`);
153182
153344
  }
153183
153345
  return recovery.recovered;
@@ -184150,6 +184312,7 @@ var ProcedureRpcHandler = class {
184150
184312
  var RECYCLED_DB_ERROR_CODES = /* @__PURE__ */ new Set([
184151
184313
  "GENCOW_DB_CONNECTION_CLOSED",
184152
184314
  "GENCOW_DB_CONNECTION_TIMEOUT",
184315
+ "GENCOW_DB_GENERATION_CHANGED",
184153
184316
  "GENCOW_DB_OPERATION_TIMEOUT",
184154
184317
  "GENCOW_DB_SOCKET_CLOSED"
184155
184318
  ]);
@@ -184250,6 +184413,7 @@ var APP_DELETE_ACTIVE_STATES = /* @__PURE__ */ new Set([
184250
184413
  "failed_tenant_db_cleanup",
184251
184414
  "failed_catalog_cleanup"
184252
184415
  ]);
184416
+ var APP_DELETE_TERMINAL_FAILURE_STATES = /* @__PURE__ */ new Set(["blocked_catalog_outcome_unknown"]);
184253
184417
  var CAPABILITY_PATTERN = /^[a-z0-9][a-z0-9-]{0,63}$/u;
184254
184418
  function createAppDeleteStatusLoader(getQueryDef2) {
184255
184419
  return async ({ ctx, args, result: result2 }) => {
@@ -184292,6 +184456,12 @@ function isMatchingNonterminalAppDeleteResult(value, accepted) {
184292
184456
  const result2 = value;
184293
184457
  return result2.operationId === accepted.operationId && result2.correlationId === accepted.correlationId && result2.deleted === accepted.deleted;
184294
184458
  }
184459
+ function isMatchingTerminalFailureAppDeleteResult(value, accepted) {
184460
+ if (!value || typeof value !== "object" || Array.isArray(value))
184461
+ return false;
184462
+ const result2 = value;
184463
+ return result2.success === false && APP_DELETE_TERMINAL_FAILURE_STATES.has(String(result2.state ?? "")) && result2.operationId === accepted.operationId && result2.correlationId === accepted.correlationId && result2.deleted === accepted.deleted;
184464
+ }
184295
184465
  async function resolveMutationSuccessResponse(input) {
184296
184466
  if (input.mutationName !== "apps.delete" || !isNonterminalAppDeleteResult(input.result)) {
184297
184467
  return { result: input.result, status: 201 };
@@ -184319,6 +184489,9 @@ async function resolveMutationSuccessResponse(input) {
184319
184489
  if (isMatchingTerminalAppDeleteResult(status, accepted)) {
184320
184490
  return { result: status, status: 201 };
184321
184491
  }
184492
+ if (isMatchingTerminalFailureAppDeleteResult(status, accepted)) {
184493
+ return { result: status, status: 409 };
184494
+ }
184322
184495
  if (!isMatchingNonterminalAppDeleteResult(status, accepted)) {
184323
184496
  return { result: input.result, status: 409 };
184324
184497
  }
@@ -188192,6 +188365,16 @@ function registerServerHealthRoutes(input) {
188192
188365
  isCoreAdmissionReady: input.isCoreAdmissionReady
188193
188366
  }));
188194
188367
  input.app.get("/health/ready", createReadinessHandler({
188368
+ rawSql: input.rawSql,
188369
+ checkPlatformDatabaseCapabilities: input.platformDatabaseCapabilities ? () => input.platformDatabaseCapabilities.checkReadiness() : void 0,
188370
+ requirePlatformDatabaseCapabilities: input.isPlatform,
188371
+ getRegisteredQueries: input.getRegisteredQueries,
188372
+ getRegisteredMutations: input.getRegisteredMutations,
188373
+ getOperationInventory: input.getOperationInventory,
188374
+ runtimeEnv: input.runtimeEnv,
188375
+ readDeepHealth: platformDeepHealth?.read
188376
+ }));
188377
+ input.app.get("/health/route-ready", createReadinessHandler({
188195
188378
  rawSql: input.rawSql,
188196
188379
  checkPlatformDatabaseCapabilities: input.platformDatabaseCapabilities ? () => input.platformDatabaseCapabilities.checkReadiness() : void 0,
188197
188380
  requirePlatformDatabaseCapabilities: input.isPlatform,
@@ -189973,7 +190156,7 @@ async function recordAuthActivity(dbInstance2, c, params) {
189973
190156
  }
189974
190157
 
189975
190158
  // ../server/dist/auth-oauth-exchange-store.js
189976
- import { createHash as createHash17 } from "crypto";
190159
+ import { createHash as createHash18 } from "crypto";
189977
190160
  import { sql as sql29 } from "drizzle-orm";
189978
190161
 
189979
190162
  // ../server/dist/crypto.js
@@ -189997,8 +190180,8 @@ function getSecretKey() {
189997
190180
  return cachedKey;
189998
190181
  const internalToken = process.env.GENCOW_INTERNAL_TOKEN;
189999
190182
  if (internalToken) {
190000
- const { createHash: createHash24 } = __require("crypto");
190001
- cachedKey = createHash24("sha256").update(`gencow-settings-key:${internalToken}`).digest().subarray(0, KEY_LENGTH);
190183
+ const { createHash: createHash25 } = __require("crypto");
190184
+ cachedKey = createHash25("sha256").update(`gencow-settings-key:${internalToken}`).digest().subarray(0, KEY_LENGTH);
190002
190185
  return cachedKey;
190003
190186
  }
190004
190187
  const keyPath = resolveSecretKeyPath();
@@ -190069,7 +190252,7 @@ function rowsFromResult17(result2) {
190069
190252
  return [];
190070
190253
  }
190071
190254
  function hashOAuthExchangeCode(code) {
190072
- return createHash17("sha256").update(code).digest("hex");
190255
+ return createHash18("sha256").update(code).digest("hex");
190073
190256
  }
190074
190257
  async function createOAuthExchangeCode(db, record2) {
190075
190258
  const code = crypto.randomUUID();
@@ -191713,7 +191896,7 @@ async function resolveAuthMePayload(input) {
191713
191896
  }
191714
191897
 
191715
191898
  // ../server/dist/auth-oauth-invite-reservation.js
191716
- import { createHash as createHash18, randomUUID as randomUUID12 } from "crypto";
191899
+ import { createHash as createHash19, randomUUID as randomUUID12 } from "crypto";
191717
191900
  import { sql as sql33 } from "drizzle-orm";
191718
191901
 
191719
191902
  // ../server/dist/invite-code.js
@@ -191809,7 +191992,7 @@ function rowsFromResult21(result2) {
191809
191992
  return [];
191810
191993
  }
191811
191994
  function hashReservationToken(token) {
191812
- return createHash18("sha256").update(token).digest("hex");
191995
+ return createHash19("sha256").update(token).digest("hex");
191813
191996
  }
191814
191997
  function normalizeProvider2(provider) {
191815
191998
  return typeof provider === "string" && provider.trim() ? provider.trim().toLowerCase() : "unknown";
@@ -191915,7 +192098,7 @@ async function finalizeOAuthInviteReservation(db, token, user2) {
191915
192098
  }
191916
192099
 
191917
192100
  // ../server/dist/auth-oauth-signup-handoff.js
191918
- import { createHash as createHash19, randomUUID as randomUUID13 } from "crypto";
192101
+ import { createHash as createHash20, randomUUID as randomUUID13 } from "crypto";
191919
192102
  import { sql as sql34 } from "drizzle-orm";
191920
192103
  init_postgres_temporal();
191921
192104
  var OAUTH_SIGNUP_HANDOFF_TTL_MS = BETTER_AUTH_OAUTH_STATE_TTL_MS + 5 * 60 * 1e3;
@@ -191928,7 +192111,7 @@ function rowsFromResult22(result2) {
191928
192111
  return [];
191929
192112
  }
191930
192113
  function hashHandoffToken(token) {
191931
- return createHash19("sha256").update(token).digest("hex");
192114
+ return createHash20("sha256").update(token).digest("hex");
191932
192115
  }
191933
192116
  function normalizeProvider3(provider) {
191934
192117
  return typeof provider === "string" && provider.trim() ? provider.trim().toLowerCase() : "unknown";
@@ -192201,7 +192384,7 @@ function enforceAuthRateLimit(c, pathname, options = {}) {
192201
192384
  }
192202
192385
 
192203
192386
  // ../server/dist/server-node-proxy-signature.js
192204
- import { createHash as createHash20, createHmac as createHmac8, timingSafeEqual as timingSafeEqual10 } from "crypto";
192387
+ import { createHash as createHash21, createHmac as createHmac8, timingSafeEqual as timingSafeEqual10 } from "crypto";
192205
192388
  var NODE_PROXY_VERSION = "v1";
192206
192389
  var DEFAULT_MAX_SKEW_MS = 3e4;
192207
192390
  var NODE_PROXY_CONTROL_PLANE_APP_HEADER = "X-Gencow-Control-Plane-App";
@@ -192225,7 +192408,7 @@ function canonicalSignedHeaderHash(headers) {
192225
192408
  const value = headers?.get(name) ?? "";
192226
192409
  return `${name}:${value.trim()}`;
192227
192410
  }).join("\n");
192228
- return createHash20("sha256").update(canonical).digest("base64url");
192411
+ return createHash21("sha256").update(canonical).digest("base64url");
192229
192412
  }
192230
192413
  function buildSignaturePayload(input) {
192231
192414
  return [
@@ -193827,6 +194010,20 @@ var SYSTEM_ENV_KEYS = [
193827
194010
  // local/test-only release storage override
193828
194011
  "GENCOW_RELEASE_OBJECTS_DIR",
193829
194012
  // local/test-only release storage path
194013
+ "GENCOW_SERVING_ROUTE_PUBLICATION_MODE",
194014
+ // Platform serving route rollout policy
194015
+ "GENCOW_SERVING_ROUTE_PUBLICATION_ENVIRONMENT",
194016
+ // Platform serving route environment fence
194017
+ "GENCOW_SERVING_ROUTE_PUBLICATION_APP_ALLOWLIST",
194018
+ // Platform serving route app policy
194019
+ "GENCOW_SERVING_ROUTE_TOPOLOGY_REVISION",
194020
+ // Platform serving route topology fence
194021
+ "GENCOW_SERVING_ROUTE_UPSERT_TARGET_SET_ID",
194022
+ // Platform serving route target set
194023
+ "GENCOW_SERVING_ROUTE_TOMBSTONE_TARGET_SET_ID",
194024
+ // Platform serving route tombstone target set
194025
+ "GENCOW_SERVING_ROUTE_ARTIFACT_QUOTA_BYTES",
194026
+ // serving artifact quota
193830
194027
  "GENCOW_BACKUP_RETENTION_V2_MODE",
193831
194028
  // platform backup retention rollout mode
193832
194029
  "GENCOW_BACKUP_RETENTION_V2_EFFECTIVE_AT",