@supacloud/cli 0.80.0 → 0.82.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/dist/index.js CHANGED
@@ -178699,7 +178699,17 @@ function outcomeUnknownGuidance(text) {
178699
178699
  }
178700
178700
  if (typeof payload !== "object" || payload === null || !("ok" in payload) || payload.ok !== false || !("error" in payload) || typeof payload.error !== "object" || payload.error === null || !("code" in payload.error) || payload.error.code !== "OUTCOME_UNKNOWN")
178701
178701
  return null;
178702
- return "OUTCOME_UNKNOWN:操作结果无法确认,服务端可能已经完成操作,但客户端未收到可验证的最终结果。" + "请先查询当前状态或操作回执,再决定是否重试;不要直接重复提交。";
178702
+ const data = payload;
178703
+ const { project_ref: ref, application_id: id, environment_id: environment } = data;
178704
+ const safeScope = typeof ref === "string" && /^[a-z0-9-]{1,20}$/.test(ref) && typeof id === "string" && /^[A-Za-z0-9_-]{1,64}$/.test(id) && typeof environment === "string" && /^[A-Za-z0-9_-]{1,64}$/.test(environment);
178705
+ let command;
178706
+ if (safeScope && (data.operation === "applications.promote_application" || data.operation === "applications.reconcile_promotion") && typeof data.mutation_id === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(data.mutation_id)) {
178707
+ command = `supacloud-cli app promote-status --ref ${ref} --id ${id} --environment_id ${environment} --mutation_id ${data.mutation_id}`;
178708
+ } else if (safeScope && (data.operation === "applications.create_preview" || data.operation === "applications.cleanup_preview")) {
178709
+ command = `supacloud-cli app preview-list --ref ${ref} --id ${id} --environment_id ${environment}`;
178710
+ }
178711
+ return "OUTCOME_UNKNOWN:操作结果无法确认,服务端可能已经完成操作,但客户端未收到可验证的最终结果。" + "请先查询当前状态或操作回执,再决定是否重试;不要直接重复提交。" + (command ? `
178712
+ 保持原 --env/--env-file 选择,先查询:${command}` : "");
178703
178713
  }
178704
178714
 
178705
178715
  // src/shared/cli.ts
@@ -179093,20 +179103,8 @@ function resolveSupaCloudContext(env = process.env, cwd = process.cwd(), selecti
179093
179103
  var ACTION_POLICY = {
179094
179104
  deploy: { write: ["deploy"] },
179095
179105
  applications: {
179096
- read: ["list_releases", "get_release", "get_runtime", "get_deployment_evidence", "get_configuration", "get_rollback_snapshot", "get_history", "get_deploy_plan", "logs", "get_preview_plan"],
179097
- write: [
179098
- "upload_release",
179099
- "put_configuration",
179100
- "activate_release",
179101
- "deploy_release",
179102
- "rollback_release",
179103
- "reconcile_activation",
179104
- "retire_activation",
179105
- "create_preview",
179106
- "list_previews",
179107
- "get_preview",
179108
- "cleanup_preview"
179109
- ]
179106
+ read: ["list_releases", "get_release", "get_runtime", "get_deployment_evidence", "get_configuration", "get_release_transfer_plan", "get_promotion_plan", "get_rollback_snapshot", "get_history", "get_deploy_plan", "get_promotion_status", "get_preview_plan", "logs"],
179107
+ write: ["upload_release", "put_configuration", "transfer_release", "promote_application", "reconcile_promotion", "create_preview", "list_previews", "get_preview", "cleanup_preview", "activate_release", "deploy_release", "rollback_release", "reconcile_activation", "retire_activation"]
179110
179108
  },
179111
179109
  project: {
179112
179110
  read: ["get", "endpoints", "health", "logs", "api_keys", "settings", "tasks", "task_detail", "task_stats", "dlq", "background_settings"],
@@ -179178,7 +179176,7 @@ var ACTION_POLICY = {
179178
179176
  scheduled_functions: { read: ["list", "get"], write: ["create", "update", "delete"] },
179179
179177
  mutations: { read: ["status"] },
179180
179178
  release: {
179181
- read: ["logical_backup_list", "postgrest_status"],
179179
+ read: ["logical_backup_list", "logical_backup_status", "postgrest_status"],
179182
179180
  local: ["scope_inspect", "scope_rebind", "scope_create"],
179183
179181
  write: ["logical_backup_create", "logical_backup_restore", "postgrest_restart", "release_canary_fixture_stage_replay", "release_canary_fixture_disable_replay"]
179184
179182
  },
@@ -179201,8 +179199,8 @@ var ACTION_POLICY = {
179201
179199
  ai: { local: ["show_skill", "install_skill"] },
179202
179200
  app: {
179203
179201
  local: ["init", "generate", "dev", "watch", "verify-plan", "compile", "check", "graph", "explain", "export-tools", "context", "doctor", "fix", "plan", "build"],
179204
- read: ["status", "logs", "rollback-plan", "history", "deploy-plan", "diff", "preview-plan"],
179205
- write: ["upload", "configure", "deploy", "rollback", "reconcile", "retire", "preview", "previews", "preview-status", "preview-cleanup"]
179202
+ read: ["status", "logs", "transfer-plan", "promote-plan", "rollback-plan", "history", "deploy-plan", "diff", "promote-status", "preview-plan"],
179203
+ write: ["upload", "configure", "transfer", "promote", "promote-reconcile", "deploy", "rollback", "reconcile", "retire", "preview", "previews", "preview-list", "preview-create", "preview-status", "preview-cleanup"]
179206
179204
  },
179207
179205
  db: {
179208
179206
  local: ["context", "lint", "explain", "diff", "role_sql"],
@@ -180419,6 +180417,52 @@ function parseApplicationActivationHistory(value) {
180419
180417
  }
180420
180418
  return structuredClone(value);
180421
180419
  }
180420
+ // node_modules/@supacloud/delivery/src/application-release-transfer.ts
180421
+ var projectRef = String2({ pattern: "^[A-Za-z0-9_-]{1,20}$" });
180422
+ var source = _Object_({
180423
+ project_ref: projectRef,
180424
+ release_id: ApplicationReleaseIdSchema,
180425
+ manifest_sha256: ApplicationReleaseIdSchema
180426
+ }, { additionalProperties: false });
180427
+ var identity = {
180428
+ project_ref: projectRef,
180429
+ application_id: ApplicationIdSchema,
180430
+ source,
180431
+ candidate_release_id: ApplicationReleaseIdSchema
180432
+ };
180433
+ var ApplicationReleaseTransferPlanSchema = _Object_({
180434
+ schema: Literal("supacloud.application-release-transfer-plan.v1"),
180435
+ ...identity,
180436
+ action: Union([Literal("materialize"), Literal("no-op")]),
180437
+ execution_performed: Literal(false)
180438
+ }, { additionalProperties: false });
180439
+ var ApplicationReleaseTransferResultSchema = _Object_({
180440
+ schema: Literal("supacloud.application-release-transfer-result.v1"),
180441
+ ...identity,
180442
+ release: ApplicationReleaseRecordSchema,
180443
+ activation_performed: Literal(false)
180444
+ }, { additionalProperties: false });
180445
+ function assertIdentity(value) {
180446
+ if (value.source.release_id !== applicationReleaseId(value.source.project_ref, value.application_id, value.source.manifest_sha256) || value.candidate_release_id !== applicationReleaseId(value.project_ref, value.application_id, value.source.manifest_sha256)) {
180447
+ throw new Error("Invalid application release transfer identity");
180448
+ }
180449
+ }
180450
+ function parseApplicationReleaseTransferPlan(candidate) {
180451
+ if (!Check2(ApplicationReleaseTransferPlanSchema, candidate))
180452
+ throw new Error("Invalid application release transfer plan");
180453
+ assertIdentity(candidate);
180454
+ return candidate;
180455
+ }
180456
+ function parseApplicationReleaseTransferResult(candidate) {
180457
+ if (!Check2(ApplicationReleaseTransferResultSchema, candidate))
180458
+ throw new Error("Invalid application release transfer result");
180459
+ assertIdentity(candidate);
180460
+ const release = parseApplicationReleaseRecord(candidate.release);
180461
+ if (release.project_ref !== candidate.project_ref || release.application_id !== candidate.application_id || release.release_id !== candidate.candidate_release_id || release.manifest_sha256 !== candidate.source.manifest_sha256) {
180462
+ throw new Error("Invalid application release transfer result");
180463
+ }
180464
+ return candidate;
180465
+ }
180422
180466
  // node_modules/@supacloud/delivery/src/deployment-evidence.ts
180423
180467
  var closed3 = { additionalProperties: false };
180424
180468
  var sha256 = String2({ pattern: "^[a-f0-9]{64}$" });
@@ -180544,6 +180588,116 @@ function parseDeploymentEvidence(value) {
180544
180588
  }
180545
180589
  return structuredClone(value);
180546
180590
  }
180591
+ // node_modules/@supacloud/delivery/src/application-promotion.ts
180592
+ var closed4 = { additionalProperties: false };
180593
+ var projectRef2 = String2({ pattern: "^[a-z0-9-]{1,20}$" });
180594
+ var sha2562 = ApplicationReleaseIdSchema;
180595
+ var nullableActivation = Union([ApplicationActivationIdSchema, Null()]);
180596
+ var nullableDigest = Union([sha2562, Null()]);
180597
+ var observation = {
180598
+ activation_id: nullableActivation,
180599
+ receipt_confirmed: Boolean2(),
180600
+ ready: Boolean2(),
180601
+ smoke_verified: Boolean2(),
180602
+ evidence_sha256: nullableDigest,
180603
+ migration_ledger_digest: nullableDigest
180604
+ };
180605
+ var blocker = [
180606
+ "SOURCE_NOT_ACTIVE",
180607
+ "SOURCE_RECEIPT_UNCONFIRMED",
180608
+ "SOURCE_NOT_READY",
180609
+ "SOURCE_SMOKE_UNVERIFIED",
180610
+ "TARGET_CONFIGURATION_MISSING",
180611
+ "TARGET_CONFIGURATION_MISMATCH",
180612
+ "MIGRATION_PENDING",
180613
+ "MIGRATION_CONFLICT",
180614
+ "OPERATOR_PROVISIONING_REQUIRED",
180615
+ "BACKUP_REQUIRED"
180616
+ ];
180617
+ var ApplicationPromotionPlanSchema = _Object_({
180618
+ schema: Literal("supacloud.application-promotion-plan.v1"),
180619
+ project_ref: projectRef2,
180620
+ application_id: ApplicationIdSchema,
180621
+ environment_id: ApplicationIdSchema,
180622
+ manifest_sha256: sha2562,
180623
+ plan_sha256: sha2562,
180624
+ source: _Object_({
180625
+ project_ref: projectRef2,
180626
+ environment_id: ApplicationIdSchema,
180627
+ release_id: sha2562,
180628
+ ...observation
180629
+ }, closed4),
180630
+ target: _Object_({
180631
+ candidate_release_id: sha2562,
180632
+ current_release_id: Union([sha2562, Null()]),
180633
+ artifact_action: Union([Literal("materialize"), Literal("reuse")]),
180634
+ configuration_id: Union([ApplicationConfigurationIdSchema, Null()]),
180635
+ configuration: Union([ApplicationConfigurationViewSchema, Null()]),
180636
+ ...observation
180637
+ }, closed4),
180638
+ migrations: _Object_({
180639
+ ledger_digest: sha2562,
180640
+ ledger_compatible: Boolean2(),
180641
+ project_migrations_applied: Boolean2(),
180642
+ pending_versions: _Array_(String2({ pattern: "^\\d{1,20}$" }), { maxItems: 4096, uniqueItems: true }),
180643
+ operator_provisioning_required: Boolean2()
180644
+ }, closed4),
180645
+ backup: _Object_({
180646
+ required: Boolean2(),
180647
+ confirmed: Literal(false)
180648
+ }, closed4),
180649
+ action: Union([Literal("promote"), Literal("no-op"), Literal("blocked")]),
180650
+ blockers: _Array_(Enum([...blocker]), {
180651
+ maxItems: blocker.length,
180652
+ uniqueItems: true
180653
+ }),
180654
+ steps: _Array_(String2({ minLength: 1, maxLength: 64 }), { maxItems: 8, uniqueItems: true }),
180655
+ execution_performed: Literal(false),
180656
+ data_recovery: Literal("separate-required")
180657
+ }, closed4);
180658
+ function applicationPromotionPlanDigest(value) {
180659
+ return digest(canonical(value));
180660
+ }
180661
+ function applicationPromotionAction(value) {
180662
+ if (value.blockers.length > 0)
180663
+ return "blocked";
180664
+ return value.target.artifact_action === "reuse" && value.target.current_release_id === value.target.candidate_release_id && value.target.configuration !== null && value.target.activation_id !== null && value.target.receipt_confirmed && value.target.ready && value.target.smoke_verified && value.migrations.project_migrations_applied && !value.migrations.operator_provisioning_required && (!value.backup.required || value.backup.confirmed) ? "no-op" : "promote";
180665
+ }
180666
+ function applicationPromotionSteps(value) {
180667
+ if (value.action === "no-op")
180668
+ return [];
180669
+ return [
180670
+ ...value.blockers.some((code) => code.startsWith("SOURCE_")) ? ["verify-source"] : [],
180671
+ ...value.blockers.some((code) => code.startsWith("TARGET_CONFIGURATION_")) ? ["bind-target-configuration"] : [],
180672
+ ...value.blockers.includes("MIGRATION_CONFLICT") ? ["resolve-migration-conflicts"] : [],
180673
+ ...!value.backup.required || value.backup.confirmed ? [] : ["backup"],
180674
+ ...value.migrations.project_migrations_applied ? [] : ["review-and-apply-migrations"],
180675
+ ...value.migrations.operator_provisioning_required ? ["verify-operator-provisioning"] : [],
180676
+ ...value.action === "promote" ? [
180677
+ ...value.target.artifact_action === "materialize" ? ["transfer"] : [],
180678
+ "activate-with-cas",
180679
+ "verify-runtime-and-smoke"
180680
+ ] : []
180681
+ ];
180682
+ }
180683
+ function parseApplicationPromotionPlan(value) {
180684
+ if (!Check2(ApplicationPromotionPlanSchema, value))
180685
+ throw new Error("Invalid application promotion plan");
180686
+ const { plan_sha256: _planSha256, ...content } = value;
180687
+ if (value.migrations.project_migrations_applied && (!value.migrations.ledger_compatible || value.migrations.pending_versions.length > 0) || !value.source.activation_id && (value.source.ready || value.source.receipt_confirmed || value.source.smoke_verified) || !value.target.activation_id && (value.target.ready || value.target.receipt_confirmed || value.target.smoke_verified) || !value.blockers.length && (!value.source.ready || !value.source.receipt_confirmed || !value.source.smoke_verified || !value.source.activation_id || !value.target.configuration || !value.migrations.ledger_compatible)) {
180688
+ throw new Error("Invalid application promotion prerequisites");
180689
+ }
180690
+ if (value.backup.required !== !value.migrations.project_migrations_applied || [value.source, value.target].some((observation) => observation.smoke_verified && (observation.evidence_sha256 === null || observation.migration_ledger_digest === null)) || value.target.smoke_verified && value.target.migration_ledger_digest !== value.migrations.ledger_digest) {
180691
+ throw new Error("Invalid application promotion migration binding");
180692
+ }
180693
+ if (value.source.release_id !== applicationReleaseId(value.source.project_ref, value.application_id, value.manifest_sha256) || value.target.candidate_release_id !== applicationReleaseId(value.project_ref, value.application_id, value.manifest_sha256) || value.action !== applicationPromotionAction(content) || canonical(value.steps) !== canonical(applicationPromotionSteps(content)) || value.plan_sha256 !== applicationPromotionPlanDigest(content) || value.target.configuration !== null && (() => {
180694
+ const configuration = parseApplicationConfigurationView(value.target.configuration);
180695
+ return configuration.project_ref !== value.project_ref || configuration.application_id !== value.application_id || configuration.environment_id !== value.environment_id || configuration.configuration_id !== value.target.configuration_id;
180696
+ })()) {
180697
+ throw new Error("Invalid application promotion plan identity");
180698
+ }
180699
+ return structuredClone(value);
180700
+ }
180547
180701
  // node_modules/@supacloud/delivery/src/application-runtime-identity.ts
180548
180702
  var ApplicationRuntimeIdentitySchema = _Object_({
180549
180703
  schema: Literal("supacloud.application-runtime.v1"),
@@ -181551,7 +181705,7 @@ ALTER TABLE supacloud_commands.execution_receipts
181551
181705
  DROP COLUMN IF EXISTS recovery_attempts;
181552
181706
  `;
181553
181707
  var MAX_MIGRATION_VERSION = 9223372036854775807n;
181554
- function sha2562(value) {
181708
+ function sha2563(value) {
181555
181709
  return createHash22("sha256").update(value).digest("hex");
181556
181710
  }
181557
181711
  function normalizedSql(value) {
@@ -181620,7 +181774,7 @@ function planMigrationRebase(migrations, options, baselineSql) {
181620
181774
  file: migration.file,
181621
181775
  version: migration.version,
181622
181776
  name: migration.name,
181623
- sha256: sha2562(normalizedSql(migration.sql))
181777
+ sha256: sha2563(normalizedSql(migration.sql))
181624
181778
  }));
181625
181779
  return {
181626
181780
  version: 1,
@@ -181630,8 +181784,8 @@ function planMigrationRebase(migrations, options, baselineSql) {
181630
181784
  baselineMigration,
181631
181785
  retainedMigrations: retained.map((migration) => migration.file),
181632
181786
  archivedMigrations: archived.map((migration) => migration.file),
181633
- sourceHistorySha256: sha2562(JSON.stringify(sourceHistory)),
181634
- baselineSha256: sha2562(normalizedBaseline),
181787
+ sourceHistorySha256: sha2563(JSON.stringify(sourceHistory)),
181788
+ baselineSha256: sha2563(normalizedBaseline),
181635
181789
  warnings: [
181636
181790
  "The baseline must come from a schema-only snapshot of the verified target database.",
181637
181791
  "Data backfills, reference data and non-replayable side effects need a separate review.",
@@ -181667,7 +181821,7 @@ async function rebaseMigrations(options) {
181667
181821
  file: migration.file,
181668
181822
  version: migration.version,
181669
181823
  name: migration.name,
181670
- sha256: sha2562(normalizedSql(migration.sql))
181824
+ sha256: sha2563(normalizedSql(migration.sql))
181671
181825
  }))
181672
181826
  }, null, 2) + `
181673
181827
  `);
@@ -184569,7 +184723,7 @@ function clientId(value) {
184569
184723
  }
184570
184724
  return value;
184571
184725
  }
184572
- function projectRef(value) {
184726
+ function projectRef3(value) {
184573
184727
  if (typeof value !== "string" || !value.trim())
184574
184728
  throw new Error("'ref' is required");
184575
184729
  return projectRefPathSegment(value.trim(), "OAuth client");
@@ -184721,7 +184875,7 @@ function registerOAuthClientTools(server2, http) {
184721
184875
  client_id: optional(String2(), "[get/delete] Exact release-canary public OAuth client ID"),
184722
184876
  redirect_uri: optional(String2(), "[create/delete] Exact HTTPS or port-bound RFC 8252 loopback callback")
184723
184877
  }, async ({ action, ref, client_id, redirect_uri }) => {
184724
- const targetRef = projectRef(ref);
184878
+ const targetRef = projectRef3(ref);
184725
184879
  if (action === "list")
184726
184880
  return listReleaseCanaryClients(http, targetRef);
184727
184881
  if (action === "get")
@@ -187393,6 +187547,432 @@ var ApplicationActivationResultSchema2 = ApplicationActivationResultSchema;
187393
187547
  var ApplicationActivationRetirementResultSchema2 = ApplicationActivationRetirementResultSchema;
187394
187548
  var ApplicationRollbackSnapshotSchema2 = ApplicationRollbackSnapshotSchema;
187395
187549
  var ApplicationActivationHistoryCursorSchema2 = ApplicationActivationHistoryCursorSchema;
187550
+ var ApplicationPromotionPlanSchema2 = ApplicationPromotionPlanSchema;
187551
+
187552
+ // src/shared/mutation-protocol.ts
187553
+ import { createHash as createHash6 } from "node:crypto";
187554
+ var MUTATION_ID_PATTERN2 = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
187555
+ var FINGERPRINT_PATTERN = /^[0-9a-f]{64}$/;
187556
+ var OPERATION_PATTERN = /^[a-z0-9][a-z0-9._:-]{0,127}$/;
187557
+ var RESOURCE_KEY_PATTERN = /^v1\/(?:[a-z0-9][a-z0-9._-]{0,63})\/([A-Za-z0-9_-]{2,171})$/;
187558
+ var RESOURCE_ID_CONTROL_PATTERN = /[\u0000-\u001f\u007f-\u009f]/u;
187559
+ var FAILURE_CODE_PATTERN = /^[A-Z][A-Z0-9_]{0,63}$/;
187560
+ var LEASE_OWNER_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:@/-]{0,254}$/;
187561
+ var TIMESTAMP_PATTERN2 = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
187562
+ var MAX_STATUS_RESPONSE_BYTES = 196608;
187563
+ var MAX_JSON_DEPTH = 32;
187564
+ var MAX_RESOURCE_ID_BYTES = 128;
187565
+ var FATAL_UTF8_DECODER = new TextDecoder("utf-8", { fatal: true });
187566
+ var MUTATION_STATUSES = new Set([
187567
+ "pending",
187568
+ "running",
187569
+ "succeeded",
187570
+ "failed_retryable",
187571
+ "failed_terminal",
187572
+ "outcome_unknown"
187573
+ ]);
187574
+ var MUTATION_RESPONSE_KEYS = ["project_ref", "mutation"];
187575
+ var MUTATION_KEYS = [
187576
+ "project_ref",
187577
+ "mutation_id",
187578
+ "operation",
187579
+ "resource_key",
187580
+ "request_fingerprint",
187581
+ "principal",
187582
+ "status",
187583
+ "checkpoint",
187584
+ "receipt",
187585
+ "response_status",
187586
+ "failure_code",
187587
+ "lease",
187588
+ "completed_at",
187589
+ "created_at",
187590
+ "updated_at"
187591
+ ];
187592
+ var PRINCIPAL_KEYS = ["type", "id"];
187593
+ var LEASE_KEYS = ["owner", "expires_at", "fencing_epoch"];
187594
+ function isMutationId(candidate) {
187595
+ return typeof candidate === "string" && MUTATION_ID_PATTERN2.test(candidate);
187596
+ }
187597
+ function assertCanonicalJsonNode(candidate, seen, depth) {
187598
+ if (candidate === null || typeof candidate === "string" || typeof candidate === "boolean")
187599
+ return;
187600
+ if (typeof candidate === "number" && Number.isFinite(candidate))
187601
+ return;
187602
+ if (typeof candidate !== "object" || depth > MAX_JSON_DEPTH) {
187603
+ throw new Error("Mutation request must be bounded JSON");
187604
+ }
187605
+ if (seen.has(candidate))
187606
+ throw new Error("Mutation request must not contain cycles");
187607
+ seen.add(candidate);
187608
+ const prototype = Object.getPrototypeOf(candidate);
187609
+ const plainRecord = prototype === Object.prototype || prototype === null;
187610
+ const children = Array.isArray(candidate) ? candidate : plainRecord ? Object.values(candidate) : null;
187611
+ if (!children)
187612
+ throw new Error("Mutation request must contain only JSON objects and arrays");
187613
+ for (const child of children)
187614
+ assertCanonicalJsonNode(child, seen, depth + 1);
187615
+ seen.delete(candidate);
187616
+ }
187617
+ function stableStringifyNode(jsonValue) {
187618
+ if (jsonValue === null || typeof jsonValue !== "object") {
187619
+ const serialized = JSON.stringify(jsonValue);
187620
+ if (serialized === undefined)
187621
+ throw new Error("Mutation request must contain exact JSON values");
187622
+ return serialized;
187623
+ }
187624
+ if (Array.isArray(jsonValue))
187625
+ return `[${jsonValue.map(stableStringifyNode).join(",")}]`;
187626
+ const record = jsonValue;
187627
+ const fields = Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableStringifyNode(record[key])}`);
187628
+ return `{${fields.join(",")}}`;
187629
+ }
187630
+ function mutationRequestFingerprint(normalizedRequest) {
187631
+ assertCanonicalJsonNode(normalizedRequest, new WeakSet, 0);
187632
+ return createHash6("sha256").update(stableStringifyNode(normalizedRequest)).digest("hex");
187633
+ }
187634
+ function objectRecord3(candidate) {
187635
+ return candidate && typeof candidate === "object" && !Array.isArray(candidate) ? candidate : null;
187636
+ }
187637
+ function exactRecord(candidate, keys) {
187638
+ const record = objectRecord3(candidate);
187639
+ if (!record || Object.keys(record).length !== keys.length)
187640
+ return null;
187641
+ return keys.every((key) => Object.hasOwn(record, key)) ? record : null;
187642
+ }
187643
+ function emptyProjection(candidate) {
187644
+ const record = objectRecord3(candidate);
187645
+ return record && Object.keys(record).length === 0 ? record : null;
187646
+ }
187647
+ function canonicalTimestamp2(candidate) {
187648
+ if (typeof candidate !== "string" || !TIMESTAMP_PATTERN2.test(candidate))
187649
+ return false;
187650
+ const milliseconds = Date.parse(candidate);
187651
+ return Number.isFinite(milliseconds) && new Date(milliseconds).toISOString() === candidate;
187652
+ }
187653
+ function nullableTimestamp(candidate) {
187654
+ return candidate === null || canonicalTimestamp2(candidate);
187655
+ }
187656
+ function safePrincipal(candidate) {
187657
+ const principal = exactRecord(candidate, PRINCIPAL_KEYS);
187658
+ if (!principal || !["master", "admin", "project"].includes(String(principal["type"])))
187659
+ return null;
187660
+ if (typeof principal["id"] !== "string" || !principal["id"] || principal["id"].length > 320 || principal["id"].trim() !== principal["id"] || /[\u0000-\u001f\u007f]/u.test(principal["id"]))
187661
+ return null;
187662
+ return { type: principal["type"], id: principal["id"] };
187663
+ }
187664
+ function safeLease(candidate) {
187665
+ const lease = exactRecord(candidate, LEASE_KEYS);
187666
+ if (!lease || lease["owner"] !== null && (typeof lease["owner"] !== "string" || !LEASE_OWNER_PATTERN.test(lease["owner"])))
187667
+ return null;
187668
+ if (!nullableTimestamp(lease["expires_at"]) || !Number.isSafeInteger(lease["fencing_epoch"]) || Number(lease["fencing_epoch"]) < 0)
187669
+ return null;
187670
+ return {
187671
+ owner: lease["owner"],
187672
+ expires_at: lease["expires_at"],
187673
+ fencing_epoch: Number(lease["fencing_epoch"])
187674
+ };
187675
+ }
187676
+ function safeResponseStatus(candidate) {
187677
+ if (candidate === null)
187678
+ return null;
187679
+ return Number.isInteger(candidate) && Number(candidate) >= 100 && Number(candidate) <= 599 ? Number(candidate) : undefined;
187680
+ }
187681
+ function validLeaseState(status, lease) {
187682
+ const running = status === "running";
187683
+ return running ? lease.owner !== null && lease.expires_at !== null && lease.fencing_epoch > 0 : lease.owner === null && lease.expires_at === null;
187684
+ }
187685
+ function validMutationLifecycle(mutation, receipt, responseStatus) {
187686
+ const terminal = ["succeeded", "failed_terminal", "outcome_unknown"].includes(String(mutation["status"]));
187687
+ if (mutation["completed_at"] !== null !== terminal)
187688
+ return false;
187689
+ if (mutation["status"] === "succeeded") {
187690
+ return receipt !== null && responseStatus !== null && responseStatus >= 200 && responseStatus < 300 && mutation["failure_code"] === null;
187691
+ }
187692
+ if (mutation["status"] === "failed_terminal") {
187693
+ return receipt !== null && typeof mutation["failure_code"] === "string";
187694
+ }
187695
+ if (mutation["status"] === "failed_retryable" || mutation["status"] === "outcome_unknown") {
187696
+ return receipt !== null && typeof mutation["failure_code"] === "string";
187697
+ }
187698
+ if (mutation["status"] === "pending") {
187699
+ return receipt === null && responseStatus === null && mutation["failure_code"] === null;
187700
+ }
187701
+ return true;
187702
+ }
187703
+ function canonicalMutationResourceKey(candidate) {
187704
+ if (typeof candidate !== "string")
187705
+ return false;
187706
+ const match = RESOURCE_KEY_PATTERN.exec(candidate);
187707
+ if (!match)
187708
+ return false;
187709
+ const encodedResourceId = match[1];
187710
+ const resourceIdBytes = Buffer.from(encodedResourceId, "base64url");
187711
+ if (resourceIdBytes.byteLength < 1 || resourceIdBytes.byteLength > MAX_RESOURCE_ID_BYTES || resourceIdBytes.toString("base64url") !== encodedResourceId)
187712
+ return false;
187713
+ let resourceId;
187714
+ try {
187715
+ resourceId = FATAL_UTF8_DECODER.decode(resourceIdBytes);
187716
+ } catch (decodeError) {
187717
+ if (decodeError instanceof TypeError)
187718
+ return false;
187719
+ throw decodeError;
187720
+ }
187721
+ return resourceId.trim() === resourceId && !RESOURCE_ID_CONTROL_PATTERN.test(resourceId) && Buffer.from(resourceId, "utf8").equals(resourceIdBytes);
187722
+ }
187723
+ function validMutationIdentity(mutation) {
187724
+ if (!isMutationId(mutation["mutation_id"]) || typeof mutation["project_ref"] !== "string")
187725
+ return false;
187726
+ if (typeof mutation["operation"] !== "string" || !OPERATION_PATTERN.test(mutation["operation"]))
187727
+ return false;
187728
+ if (mutation["resource_key"] !== null && !canonicalMutationResourceKey(mutation["resource_key"]))
187729
+ return false;
187730
+ return typeof mutation["request_fingerprint"] === "string" && FINGERPRINT_PATTERN.test(mutation["request_fingerprint"]);
187731
+ }
187732
+ function validMutationTerminalFields(mutation) {
187733
+ if (typeof mutation["status"] !== "string" || !MUTATION_STATUSES.has(mutation["status"]))
187734
+ return false;
187735
+ if (mutation["failure_code"] !== null && (typeof mutation["failure_code"] !== "string" || !FAILURE_CODE_PATTERN.test(mutation["failure_code"])))
187736
+ return false;
187737
+ return nullableTimestamp(mutation["completed_at"]) && canonicalTimestamp2(mutation["created_at"]) && canonicalTimestamp2(mutation["updated_at"]);
187738
+ }
187739
+ function safeMutationStatus(candidate) {
187740
+ const mutation = exactRecord(candidate, MUTATION_KEYS);
187741
+ if (!mutation || !validMutationIdentity(mutation) || !validMutationTerminalFields(mutation))
187742
+ return null;
187743
+ const principal = safePrincipal(mutation["principal"]);
187744
+ const checkpoint = emptyProjection(mutation["checkpoint"]);
187745
+ const receipt = mutation["receipt"] === null ? null : emptyProjection(mutation["receipt"]);
187746
+ const responseStatus = safeResponseStatus(mutation["response_status"]);
187747
+ const lease = safeLease(mutation["lease"]);
187748
+ if (!principal || !checkpoint || mutation["receipt"] !== null && !receipt || responseStatus === undefined || !lease || !validLeaseState(String(mutation["status"]), lease) || !validMutationLifecycle(mutation, receipt, responseStatus))
187749
+ return null;
187750
+ return {
187751
+ project_ref: mutation["project_ref"],
187752
+ mutation_id: mutation["mutation_id"],
187753
+ operation: mutation["operation"],
187754
+ resource_key: mutation["resource_key"],
187755
+ request_fingerprint: mutation["request_fingerprint"],
187756
+ principal,
187757
+ status: mutation["status"],
187758
+ checkpoint,
187759
+ receipt,
187760
+ response_status: responseStatus,
187761
+ failure_code: mutation["failure_code"],
187762
+ lease,
187763
+ completed_at: mutation["completed_at"],
187764
+ created_at: mutation["created_at"],
187765
+ updated_at: mutation["updated_at"]
187766
+ };
187767
+ }
187768
+ function mutationStatusPath(ref, mutationId) {
187769
+ if (!isMutationId(mutationId))
187770
+ throw new Error("'mutation_id' must be a UUIDv4");
187771
+ return `/v1/projects/${projectRefPathSegment(ref, "Mutations")}/mutations/${encodeURIComponent(mutationId)}`;
187772
+ }
187773
+ function mutationStatusResponse(ref, mutationId, payload) {
187774
+ const response = exactRecord(payload, MUTATION_RESPONSE_KEYS);
187775
+ const mutation = safeMutationStatus(response?.["mutation"]);
187776
+ return response?.["project_ref"] === ref && mutation?.project_ref === ref && mutation.mutation_id === mutationId ? mutation : null;
187777
+ }
187778
+ async function fetchMutationStatus(http, ref, mutationId) {
187779
+ const response = await http.get(mutationStatusPath(ref, mutationId), {
187780
+ maxResponseBytes: MAX_STATUS_RESPONSE_BYTES
187781
+ });
187782
+ if (!response.ok) {
187783
+ return { kind: "unavailable", httpStatus: response.transportError ? null : response.status };
187784
+ }
187785
+ const mutation = mutationStatusResponse(ref, mutationId, response.data);
187786
+ return mutation ? { kind: "available", mutation } : { kind: "invalid" };
187787
+ }
187788
+
187789
+ // src/shared/tools/application-workflow-schemas.ts
187790
+ var closed5 = { additionalProperties: false };
187791
+ var ref5 = String2({ pattern: "^[A-Za-z0-9_-]{1,20}$" });
187792
+ var uuid = ApplicationActivationIdSchema2;
187793
+ var nullableUuid = Union([uuid, Null()]);
187794
+ var nullableDigest2 = Union([ApplicationReleaseIdSchema2, Null()]);
187795
+ var timestamp2 = String2({ pattern: "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z$" });
187796
+ var scope2 = { project_ref: ref5, application_id: ApplicationIdSchema2, environment_id: ApplicationIdSchema2 };
187797
+ var ApplicationPromotionResultSchema = _Object_({
187798
+ ...scope2,
187799
+ mutation_id: uuid,
187800
+ activation_id: uuid,
187801
+ plan_sha256: ApplicationReleaseIdSchema2,
187802
+ action: Literal("promote"),
187803
+ replayed: Boolean2()
187804
+ }, closed5);
187805
+ var ApplicationPromotionStatusSchema = _Object_({
187806
+ ...scope2,
187807
+ mutation: Unknown(),
187808
+ promotion: _Object_({
187809
+ phase: Union([
187810
+ Literal("prepared"),
187811
+ Literal("transferring"),
187812
+ Literal("migrating"),
187813
+ Literal("activating"),
187814
+ Literal("verifying"),
187815
+ Null()
187816
+ ]),
187817
+ plan_sha256: nullableDigest2,
187818
+ activation_id: nullableUuid,
187819
+ release_id: nullableDigest2,
187820
+ configuration_id: Union([ApplicationConfigurationIdSchema2, Null()]),
187821
+ backup_id: Union([String2({ pattern: "^logical-full_[a-z0-9-]{1,20}_[a-f0-9]{32}$" }), Null()]),
187822
+ data_recovery: Literal("separate-required")
187823
+ }, closed5)
187824
+ }, closed5);
187825
+ var phase = Union([Literal("pending"), Literal("ready"), Literal("failed"), Literal("cleaned")]);
187826
+ var previewId = String2({ pattern: "^[a-f0-9-]{8,64}$" });
187827
+ var name = String2({ minLength: 1, maxLength: 256 });
187828
+ var checks = _Array_(String2({ minLength: 1, maxLength: 128 }), { maxItems: 64, uniqueItems: true });
187829
+ var ApplicationPreviewReceiptSchema = _Object_({
187830
+ schema: Literal("supacloud.application-preview.v1"),
187831
+ ...scope2,
187832
+ preview_id: previewId,
187833
+ release_id: ApplicationReleaseIdSchema2,
187834
+ expires_at: Union([timestamp2, Null()]),
187835
+ status: Union([
187836
+ Literal("planned"),
187837
+ Literal("provisioning"),
187838
+ Literal("ready"),
187839
+ Literal("failed"),
187840
+ Literal("cleaned")
187841
+ ]),
187842
+ resources: _Object_({
187843
+ build_artifact: _Object_({ status: phase, release_id: ApplicationReleaseIdSchema2 }, closed5),
187844
+ database_branch: _Object_({
187845
+ status: phase,
187846
+ branch_ref: ref5,
187847
+ data_mode: Union([Literal("schema_only"), Literal("full_clone")])
187848
+ }, closed5),
187849
+ queue_namespace: _Object_({ status: phase, namespace: name }, closed5),
187850
+ storage_namespace: _Object_({ status: phase, namespace: name }, closed5),
187851
+ test_secret: _Object_({ status: phase, name, value_issued: Literal(false) }, closed5),
187852
+ configuration_revision: _Object_({ status: phase, configuration_id: Union([ApplicationConfigurationIdSchema2, Null()]) }, closed5),
187853
+ application_activation: _Object_({ status: phase, activation_id: nullableUuid }, closed5),
187854
+ smoke_test: _Object_({ status: phase, checks, passed: checks, failed: checks }, closed5)
187855
+ }, closed5),
187856
+ cleanup: _Object_({
187857
+ required: Boolean2(),
187858
+ completed: Boolean2(),
187859
+ error: Union([String2({ pattern: "^[A-Z][A-Z0-9_]{0,63}$" }), Null()])
187860
+ }, closed5),
187861
+ branch_name: Optional(String2({ minLength: 1, maxLength: 80 })),
187862
+ queue_name: Optional(name),
187863
+ test_secret_name: Optional(name),
187864
+ source_configuration_id: Optional(Union([ApplicationConfigurationIdSchema2, Null()])),
187865
+ created_at: Optional(timestamp2),
187866
+ updated_at: Optional(timestamp2)
187867
+ }, closed5);
187868
+ var previewListSchema = _Object_({
187869
+ ...scope2,
187870
+ previews: _Array_(ApplicationPreviewReceiptSchema, { maxItems: 1000 })
187871
+ }, closed5);
187872
+ function assertScope(value, identity) {
187873
+ if (value.project_ref !== identity.project_ref || value.application_id !== identity.application_id || value.environment_id !== identity.environment_id)
187874
+ throw new Error("Application workflow scope mismatch");
187875
+ }
187876
+ function parsePromotionStatus(value, identity) {
187877
+ if (!Check2(ApplicationPromotionStatusSchema, value))
187878
+ throw new Error("Invalid promotion status");
187879
+ assertScope(value, identity);
187880
+ const mutation = safeMutationStatus(value.mutation);
187881
+ const resourceId = mutationRequestFingerprint({
187882
+ applicationId: identity.application_id,
187883
+ environmentId: identity.environment_id
187884
+ });
187885
+ const resourceKey = `v1/application_release/${Buffer.from(resourceId).toString("base64url")}`;
187886
+ if (!mutation || mutation.project_ref !== identity.project_ref || mutation.mutation_id !== identity.mutation_id || mutation.operation !== "application.release.promote" || mutation.resource_key !== resourceKey || value.promotion.activation_id === identity.mutation_id) {
187887
+ throw new Error("Invalid promotion mutation identity");
187888
+ }
187889
+ if (mutation.status === "succeeded" && (mutation.response_status !== 200 || mutation.receipt === null || value.promotion.phase !== "verifying" || value.promotion.activation_id === null || value.promotion.plan_sha256 === null || value.promotion.release_id === null || value.promotion.configuration_id === null || value.promotion.backup_id === null)) {
187890
+ throw new Error("Invalid promotion success state");
187891
+ }
187892
+ if (value.promotion.backup_id !== null && !value.promotion.backup_id.startsWith(`logical-full_${identity.project_ref}_`)) {
187893
+ throw new Error("Invalid promotion backup identity");
187894
+ }
187895
+ return structuredClone({ ...value, mutation });
187896
+ }
187897
+ function parsePreviewReceipt(value, identity) {
187898
+ if (!Check2(ApplicationPreviewReceiptSchema, value))
187899
+ throw new Error("Invalid preview receipt");
187900
+ assertScope(value, identity);
187901
+ if (identity.preview_id !== undefined && value.preview_id !== identity.preview_id || identity.release_id !== undefined && value.release_id !== identity.release_id || identity.branch_ref !== undefined && value.resources.database_branch.branch_ref !== identity.branch_ref || identity.data_mode !== undefined && value.resources.database_branch.data_mode !== identity.data_mode || identity.configuration_id !== undefined && value.source_configuration_id !== identity.configuration_id || value.resources.build_artifact.release_id !== value.release_id)
187902
+ throw new Error("Invalid preview identity");
187903
+ return structuredClone(value);
187904
+ }
187905
+ function parsePreviewList(value, identity) {
187906
+ if (!Check2(previewListSchema, value))
187907
+ throw new Error("Invalid preview list");
187908
+ assertScope(value, identity);
187909
+ const ids = new Set;
187910
+ for (const item of value.previews) {
187911
+ parsePreviewReceipt(item, identity);
187912
+ if (ids.has(item.preview_id))
187913
+ throw new Error("Duplicate preview identity");
187914
+ ids.add(item.preview_id);
187915
+ }
187916
+ return structuredClone(value);
187917
+ }
187918
+
187919
+ // src/shared/tools/application-release-transfer.ts
187920
+ async function applicationReleaseTransfer(http, input, project) {
187921
+ const operation = `applications.${input.action}`;
187922
+ const identity = {
187923
+ project_ref: input.ref,
187924
+ application_id: input.id,
187925
+ source_ref: input.sourceRef,
187926
+ source_release_id: input.sourceReleaseId
187927
+ };
187928
+ const path = `/v1/projects/${project}/applications/${encodeURIComponent(input.id)}`;
187929
+ const query = new URLSearchParams({ source_ref: input.sourceRef, source_release_id: input.sourceReleaseId });
187930
+ const response = await http.get(`${path}/release-transfer-plan?${query}`, { maxJsonBytes: 65536, responseTimeoutMs: 30000 });
187931
+ if (!response.ok)
187932
+ return releaseControlFailure(operation, "HTTP_ERROR", response.transportError ? null : response.status, identity);
187933
+ let plan;
187934
+ try {
187935
+ if (response.status !== 200)
187936
+ throw new Error;
187937
+ plan = parseApplicationReleaseTransferPlan(response.data);
187938
+ if (plan.project_ref !== input.ref || plan.application_id !== input.id || plan.source.project_ref !== input.sourceRef || plan.source.release_id !== input.sourceReleaseId)
187939
+ throw new Error;
187940
+ } catch {
187941
+ return releaseControlFailure(operation, "INVALID_RESPONSE", response.status, identity);
187942
+ }
187943
+ if (input.action === "get_release_transfer_plan")
187944
+ return releaseControlSuccess(operation, { ...identity, plan });
187945
+ const safeIdentity = {
187946
+ ...identity,
187947
+ release_id: plan.candidate_release_id,
187948
+ manifest_sha256: plan.source.manifest_sha256
187949
+ };
187950
+ if (plan.action === "no-op")
187951
+ return releaseControlSuccess(operation, {
187952
+ ...safeIdentity,
187953
+ no_op: true,
187954
+ execution_performed: false,
187955
+ activation_performed: false,
187956
+ plan
187957
+ });
187958
+ const result = await http.postReleaseMutation(`${path}/release-transfers`, {
187959
+ source_ref: input.sourceRef,
187960
+ source_release_id: input.sourceReleaseId,
187961
+ expected_manifest_sha256: plan.source.manifest_sha256
187962
+ }, { timeoutMs: 120000 });
187963
+ if (!result.ok)
187964
+ return releaseControlMutationFailure(operation, result, safeIdentity);
187965
+ try {
187966
+ if (result.status !== 200)
187967
+ throw new Error;
187968
+ const receipt = parseApplicationReleaseTransferResult(result.data);
187969
+ if (receipt.project_ref !== input.ref || receipt.application_id !== input.id || receipt.source.project_ref !== input.sourceRef || receipt.source.release_id !== input.sourceReleaseId || receipt.candidate_release_id !== plan.candidate_release_id || receipt.source.manifest_sha256 !== plan.source.manifest_sha256)
187970
+ throw new Error;
187971
+ return releaseControlSuccess(operation, { ...safeIdentity, no_op: false, transfer: receipt });
187972
+ } catch {
187973
+ return releaseControlFailure(operation, "OUTCOME_UNKNOWN", result.status, safeIdentity);
187974
+ }
187975
+ }
187396
187976
 
187397
187977
  // src/shared/tools/application-preview-tools.ts
187398
187978
  var APPLICATION_PREVIEW_ACTIONS = [
@@ -187420,9 +188000,9 @@ var APPLICATION_PREVIEW_FIELDS = {
187420
188000
  timeout_seconds: optional(waitSecondsSchema, "[create_preview/get_preview] Observation budget with wait, from 1 to 3600 seconds (default 300)")
187421
188001
  };
187422
188002
  var strict = { additionalProperties: false };
187423
- var phase = stringEnum(["pending", "ready", "failed", "cleaned"]);
188003
+ var phase2 = stringEnum(["pending", "ready", "failed", "cleaned"]);
187424
188004
  var check5 = String2({ pattern: "^[a-z][a-z0-9_]{0,63}$" });
187425
- var checks = _Array_(check5, { maxItems: 64, uniqueItems: true });
188005
+ var checks2 = _Array_(check5, { maxItems: 64, uniqueItems: true });
187426
188006
  var revision = Union([ApplicationConfigurationIdSchema2, Null()]);
187427
188007
  var receiptSchema = _Object_({
187428
188008
  schema: Literal("supacloud.application-preview.v1"),
@@ -187434,14 +188014,14 @@ var receiptSchema = _Object_({
187434
188014
  expires_at: Union([timestampSchema, Null()]),
187435
188015
  status: stringEnum(["planned", "provisioning", "ready", "failed", "cleaned"]),
187436
188016
  resources: _Object_({
187437
- build_artifact: _Object_({ status: phase, release_id: ApplicationReleaseIdSchema2 }, strict),
187438
- database_branch: _Object_({ status: phase, branch_ref: branchRefSchema, data_mode: stringEnum(["schema_only", "full_clone"]) }, strict),
187439
- queue_namespace: _Object_({ status: phase, namespace: String2({ minLength: 1, maxLength: 128 }) }, strict),
187440
- storage_namespace: _Object_({ status: phase, namespace: branchRefSchema }, strict),
187441
- test_secret: _Object_({ status: phase, name: String2({ pattern: "^[A-Z0-9_-]{1,128}$" }), value_issued: Literal(false) }, strict),
187442
- configuration_revision: _Object_({ status: phase, configuration_id: revision }, strict),
187443
- application_activation: _Object_({ status: phase, activation_id: Union([previewIdSchema, Null()]) }, strict),
187444
- smoke_test: _Object_({ status: phase, checks, passed: checks, failed: checks }, strict)
188017
+ build_artifact: _Object_({ status: phase2, release_id: ApplicationReleaseIdSchema2 }, strict),
188018
+ database_branch: _Object_({ status: phase2, branch_ref: branchRefSchema, data_mode: stringEnum(["schema_only", "full_clone"]) }, strict),
188019
+ queue_namespace: _Object_({ status: phase2, namespace: String2({ minLength: 1, maxLength: 128 }) }, strict),
188020
+ storage_namespace: _Object_({ status: phase2, namespace: branchRefSchema }, strict),
188021
+ test_secret: _Object_({ status: phase2, name: String2({ pattern: "^[A-Z0-9_-]{1,128}$" }), value_issued: Literal(false) }, strict),
188022
+ configuration_revision: _Object_({ status: phase2, configuration_id: revision }, strict),
188023
+ application_activation: _Object_({ status: phase2, activation_id: Union([previewIdSchema, Null()]) }, strict),
188024
+ smoke_test: _Object_({ status: phase2, checks: checks2, passed: checks2, failed: checks2 }, strict)
187445
188025
  }, strict),
187446
188026
  cleanup: _Object_({
187447
188027
  required: Boolean2(),
@@ -187479,14 +188059,14 @@ var actionFields = {
187479
188059
  get_preview: ["preview_id", "wait", "timeout_seconds"],
187480
188060
  cleanup_preview: ["preview_id"]
187481
188061
  };
187482
- function timestamp2(value) {
188062
+ function timestamp3(value) {
187483
188063
  return Number.isFinite(Date.parse(value)) && new Date(value).toISOString() === value;
187484
188064
  }
187485
188065
  function receipt(value, identity) {
187486
188066
  if (!Check2(receiptSchema, value) || value.project_ref !== identity.ref || value.application_id !== identity.id || value.environment_id !== identity.environment || identity.previewId && value.preview_id !== identity.previewId || value.resources.build_artifact.release_id !== value.release_id || value.resources.storage_namespace.namespace !== value.resources.database_branch.branch_ref) {
187487
188067
  throw new Error("Invalid preview receipt");
187488
188068
  }
187489
- if (value.expires_at !== null && !timestamp2(value.expires_at) || value.created_at !== undefined && !timestamp2(value.created_at) || value.updated_at !== undefined && !timestamp2(value.updated_at) || value.expires_at !== null && value.created_at !== undefined && !Check2(ttlSecondsSchema, (Date.parse(value.expires_at) - Date.parse(value.created_at)) / 1000)) {
188069
+ if (value.expires_at !== null && !timestamp3(value.expires_at) || value.created_at !== undefined && !timestamp3(value.created_at) || value.updated_at !== undefined && !timestamp3(value.updated_at) || value.expires_at !== null && value.created_at !== undefined && !Check2(ttlSecondsSchema, (Date.parse(value.expires_at) - Date.parse(value.created_at)) / 1000)) {
187490
188070
  throw new Error("Invalid preview expiry");
187491
188071
  }
187492
188072
  if (value.status === "ready") {
@@ -187710,7 +188290,11 @@ async function applicationPreviewAction(http, args, project) {
187710
188290
  throw new Error;
187711
188291
  }
187712
188292
  if (preview.status === "failed" || action === "cleanup_preview" && preview.status !== "cleaned") {
187713
- return releaseControlFailure(operation, "MUTATION_NOT_SUCCEEDED", result.status, { ...identity, preview });
188293
+ return releaseControlFailure(operation, "MUTATION_NOT_SUCCEEDED", result.status, {
188294
+ ...identity,
188295
+ preview_id: preview.preview_id,
188296
+ preview
188297
+ });
187714
188298
  }
187715
188299
  if (timeoutSeconds !== undefined)
187716
188300
  return await waitForPreview(http, path, operation, identity, preview, timeoutSeconds, startedAt);
@@ -187732,12 +188316,23 @@ var APPLICATION_TOOL_SCHEMA = {
187732
188316
  "put_configuration",
187733
188317
  "get_deploy_plan",
187734
188318
  "deploy_release",
188319
+ "get_release_transfer_plan",
188320
+ "transfer_release",
188321
+ "get_promotion_plan",
188322
+ "promote_application",
188323
+ "get_promotion_status",
188324
+ "reconcile_promotion",
188325
+ "get_preview_plan",
188326
+ "list_previews",
188327
+ "create_preview",
188328
+ "get_preview",
188329
+ "cleanup_preview",
187735
188330
  "activate_release",
188331
+ "reconcile_activation",
188332
+ "retire_activation",
187736
188333
  "rollback_release",
187737
188334
  "get_rollback_snapshot",
187738
188335
  "get_history",
187739
- "reconcile_activation",
187740
- "retire_activation",
187741
188336
  ...APPLICATION_PREVIEW_ACTIONS,
187742
188337
  "logs"
187743
188338
  ]), "Action"),
@@ -187750,6 +188345,12 @@ var APPLICATION_TOOL_SCHEMA = {
187750
188345
  configuration_path: optional(String2(), "[put_configuration] Local configuration write JSON including revision and expected revision"),
187751
188346
  manifest_path: optional(String2(), "[upload_release] Local delivery.manifest.json"),
187752
188347
  release_id: optional(ApplicationReleaseIdSchema2, "[get_release/get_deploy_plan/deploy_release/activate_release/reconcile_activation/rollback_release] Immutable application release ID; platform selects previous for default rollback"),
188348
+ source_ref: optional(String2({ pattern: "^[A-Za-z0-9_-]{1,20}$" }), "[get_release_transfer_plan/transfer_release] Source project ref"),
188349
+ source_release_id: optional(ApplicationReleaseIdSchema2, "[get_release_transfer_plan/transfer_release] Source immutable release ID"),
188350
+ source_environment_id: optional(ApplicationIdSchema2, "[get_promotion_plan] Source environment"),
188351
+ mutation_id: optional(ApplicationActivationIdSchema2, "[promote_application/get_promotion_status/reconcile_promotion] Stable promotion mutation ID"),
188352
+ approved_migration_digest: optional(ApplicationReleaseIdSchema2, "[promote_application] Exact reviewed migration execution-plan digest"),
188353
+ plan: optional(ApplicationPromotionPlanSchema2, "[promote_application] Immutable plan returned by get_promotion_plan"),
187753
188354
  cursor: optional(Union([ApplicationReleaseIdSchema2, ApplicationActivationHistoryCursorSchema2]), "[list_releases/get_history] Returned page cursor"),
187754
188355
  limit: optional(Integer({ minimum: 1, maximum: 100 }), "[list_releases/get_history] Page size, default 50/20"),
187755
188356
  offset: optional(Integer({ minimum: 0, maximum: 1e6 }), "[logs] Result offset"),
@@ -187770,6 +188371,12 @@ var inventorySchema = _Object_({
187770
188371
  releases: _Array_(ApplicationReleaseRecordSchema2, { maxItems: 100 }),
187771
188372
  next_cursor: Union([ApplicationReleaseIdSchema2, Null()])
187772
188373
  });
188374
+ function promotionPath(project, id, environmentId) {
188375
+ return `/v1/projects/${project}/applications/${encodeURIComponent(id)}/environments/${encodeURIComponent(environmentId)}/promotions`;
188376
+ }
188377
+ function previewPath(project, id, environmentId) {
188378
+ return `/v1/projects/${project}/applications/${encodeURIComponent(id)}/environments/${encodeURIComponent(environmentId)}`;
188379
+ }
187773
188380
  function text(args, field) {
187774
188381
  const value = args[field];
187775
188382
  if (typeof value !== "string" || !value.trim())
@@ -188045,13 +188652,59 @@ function registerApplicationTools(server2, http) {
188045
188652
  throw new Error("Invalid application ID");
188046
188653
  const path = `/v1/projects/${project}/applications/${encodeURIComponent(id)}/releases`;
188047
188654
  const operation = `applications.${action}`;
188048
- if (action === "get_deploy_plan") {
188049
- const observed = await readDeployPlan(http, args, project);
188050
- return observed.failure ?? releaseControlSuccess(operation, { ...observed.identity, plan: observed.plan });
188051
- }
188052
- if (action === "deploy_release")
188053
- return deployAction(http, args, project);
188054
- if (action === "rollback_release" || action === "get_rollback_snapshot") {
188655
+ if (action === "get_promotion_plan") {
188656
+ const environmentId = text(args, "environment_id");
188657
+ const sourceRef = text(args, "source_ref"), sourceEnvironmentId = text(args, "source_environment_id");
188658
+ const sourceReleaseId = text(args, "source_release_id");
188659
+ if (!/^[a-z0-9-]{1,20}$/.test(sourceRef) || !Check2(ApplicationReleaseIdSchema2, sourceReleaseId) || !Check2(ApplicationIdSchema2, environmentId) || !Check2(ApplicationIdSchema2, sourceEnvironmentId)) {
188660
+ throw new Error("Invalid promotion environment");
188661
+ }
188662
+ const query = new URLSearchParams({
188663
+ source_ref: sourceRef,
188664
+ source_environment_id: sourceEnvironmentId,
188665
+ source_release_id: sourceReleaseId
188666
+ });
188667
+ if (typeof args["configuration_id"] === "string")
188668
+ query.set("configuration_id", args["configuration_id"]);
188669
+ const result = await http.get(`/v1/projects/${project}/applications/${encodeURIComponent(id)}/environments/${encodeURIComponent(environmentId)}/promotion-plan?${query}`, { maxJsonBytes: 262144, responseTimeoutMs: 30000 });
188670
+ const identity = {
188671
+ project_ref: ref,
188672
+ application_id: id,
188673
+ environment_id: environmentId,
188674
+ source_ref: sourceRef,
188675
+ source_environment_id: sourceEnvironmentId,
188676
+ source_release_id: sourceReleaseId
188677
+ };
188678
+ if (!result.ok)
188679
+ return releaseControlFailure(operation, "HTTP_ERROR", result.status, identity);
188680
+ try {
188681
+ if (result.status !== 200)
188682
+ throw new Error;
188683
+ const plan = parseApplicationPromotionPlan(result.data);
188684
+ if (plan.project_ref !== ref || plan.application_id !== id || plan.environment_id !== environmentId || plan.source.project_ref !== sourceRef || plan.source.environment_id !== sourceEnvironmentId || plan.source.release_id !== sourceReleaseId || args["configuration_id"] !== undefined && plan.target.configuration_id !== args["configuration_id"]) {
188685
+ throw new Error;
188686
+ }
188687
+ return releaseControlSuccess(operation, { ...identity, plan });
188688
+ } catch {
188689
+ return releaseControlFailure(operation, "INVALID_RESPONSE", result.status, identity);
188690
+ }
188691
+ }
188692
+ if (action === "get_release_transfer_plan" || action === "transfer_release") {
188693
+ return applicationReleaseTransfer(http, {
188694
+ action,
188695
+ ref,
188696
+ id,
188697
+ sourceRef: text(args, "source_ref"),
188698
+ sourceReleaseId: text(args, "source_release_id")
188699
+ }, project);
188700
+ }
188701
+ if (action === "get_deploy_plan") {
188702
+ const observed = await readDeployPlan(http, args, project);
188703
+ return observed.failure ?? releaseControlSuccess(operation, { ...observed.identity, plan: observed.plan });
188704
+ }
188705
+ if (action === "deploy_release")
188706
+ return deployAction(http, args, project);
188707
+ if (action === "rollback_release" || action === "get_rollback_snapshot") {
188055
188708
  return rollbackAction(http, args, project);
188056
188709
  }
188057
188710
  if (action === "get_history")
@@ -188116,6 +188769,166 @@ function registerApplicationTools(server2, http) {
188116
188769
  return releaseControlFailure(operation, "INVALID_RESPONSE", result.status);
188117
188770
  }
188118
188771
  }
188772
+ if (action === "promote_application") {
188773
+ const environmentId = text(args, "environment_id");
188774
+ const sourceRef = text(args, "source_ref"), sourceEnvironmentId = text(args, "source_environment_id");
188775
+ const sourceReleaseId = text(args, "source_release_id");
188776
+ const mutationId = text(args, "mutation_id");
188777
+ if (!Check2(ApplicationIdSchema2, environmentId) || !/^[a-z0-9-]{1,20}$/.test(sourceRef) || !Check2(ApplicationIdSchema2, sourceEnvironmentId) || !Check2(ApplicationReleaseIdSchema2, sourceReleaseId) || !Check2(ApplicationActivationIdSchema2, mutationId)) {
188778
+ throw new Error("Invalid promotion identity");
188779
+ }
188780
+ const plan = parseApplicationPromotionPlan(args["plan"]);
188781
+ if (plan.project_ref !== ref || plan.application_id !== id || plan.environment_id !== environmentId || plan.source.project_ref !== sourceRef || plan.source.environment_id !== sourceEnvironmentId || plan.source.release_id !== sourceReleaseId || plan.action === "no-op" || plan.blockers.some((code) => !["MIGRATION_PENDING", "BACKUP_REQUIRED"].includes(code)) || plan.target.configuration_id === null) {
188782
+ throw new Error("Promotion plan does not match the request");
188783
+ }
188784
+ const configurationId = text(args, "configuration_id");
188785
+ if (configurationId !== plan.target.configuration_id)
188786
+ throw new Error("Promotion configuration does not match the plan");
188787
+ const identity = {
188788
+ project_ref: ref,
188789
+ application_id: id,
188790
+ environment_id: environmentId,
188791
+ mutation_id: mutationId,
188792
+ plan_sha256: plan.plan_sha256
188793
+ };
188794
+ const result = await http.post(`${promotionPath(project, id, environmentId)}`, {
188795
+ mutation_id: mutationId,
188796
+ source_ref: sourceRef,
188797
+ source_environment_id: sourceEnvironmentId,
188798
+ source_release_id: sourceReleaseId,
188799
+ configuration_id: configurationId,
188800
+ plan,
188801
+ ...typeof args["approved_migration_digest"] === "string" ? { approved_migration_digest: args["approved_migration_digest"] } : {}
188802
+ }, { timeoutMs: 120000, maxJsonBytes: 65536, responseTimeoutMs: 30000 });
188803
+ if (!result.ok)
188804
+ return releaseControlMutationFailure(operation, result, identity);
188805
+ try {
188806
+ const body = result.data;
188807
+ if (result.status !== 200 || !Check2(ApplicationPromotionResultSchema, body))
188808
+ throw new Error;
188809
+ if (body.project_ref !== ref || body.application_id !== id || body.environment_id !== environmentId || body.mutation_id !== mutationId || body.plan_sha256 !== plan.plan_sha256 || body.activation_id === mutationId || body.activation_id === plan.target.activation_id) {
188810
+ throw new Error;
188811
+ }
188812
+ return releaseControlSuccess(operation, { ...identity, result: body });
188813
+ } catch {
188814
+ return releaseControlFailure(operation, "OUTCOME_UNKNOWN", result.status, identity);
188815
+ }
188816
+ }
188817
+ if (action === "get_promotion_status" || action === "reconcile_promotion") {
188818
+ const environmentId = text(args, "environment_id"), mutationId = text(args, "mutation_id");
188819
+ if (!Check2(ApplicationIdSchema2, environmentId) || !Check2(ApplicationActivationIdSchema2, mutationId)) {
188820
+ throw new Error("Invalid promotion identity");
188821
+ }
188822
+ const identity = {
188823
+ project_ref: ref,
188824
+ application_id: id,
188825
+ environment_id: environmentId,
188826
+ mutation_id: mutationId
188827
+ };
188828
+ const endpoint = `${promotionPath(project, id, environmentId)}/${mutationId}`;
188829
+ const result = action === "get_promotion_status" ? await http.get(endpoint, { maxJsonBytes: 65536, responseTimeoutMs: 30000 }) : await http.post(`${endpoint}/reconcile`, {}, { timeoutMs: 120000, maxJsonBytes: 65536, responseTimeoutMs: 30000 });
188830
+ if (!result.ok)
188831
+ return action === "reconcile_promotion" ? releaseControlMutationFailure(operation, result, identity) : releaseControlFailure(operation, "HTTP_ERROR", result.status, identity);
188832
+ try {
188833
+ if (result.status !== 200)
188834
+ throw new Error;
188835
+ const status = parsePromotionStatus(result.data, identity);
188836
+ if (action === "reconcile_promotion" && status.mutation.status !== "succeeded") {
188837
+ return releaseControlFailure(operation, status.mutation.status === "outcome_unknown" ? "OUTCOME_UNKNOWN" : "MUTATION_NOT_SUCCEEDED", result.status, { ...identity, ...status });
188838
+ }
188839
+ return releaseControlSuccess(operation, status);
188840
+ } catch {
188841
+ return releaseControlFailure(operation, action === "reconcile_promotion" ? "OUTCOME_UNKNOWN" : "INVALID_RESPONSE", result.status, identity);
188842
+ }
188843
+ }
188844
+ if (action === "get_preview_plan" || action === "list_previews" || action === "create_preview" || action === "get_preview" || action === "cleanup_preview") {
188845
+ const environmentId = text(args, "environment_id");
188846
+ if (!Check2(ApplicationIdSchema2, environmentId))
188847
+ throw new Error("Invalid preview environment");
188848
+ const previewId = action === "get_preview" || action === "cleanup_preview" ? text(args, "preview_id") : undefined;
188849
+ const identity = {
188850
+ project_ref: ref,
188851
+ application_id: id,
188852
+ environment_id: environmentId,
188853
+ ...previewId === undefined ? {} : { preview_id: previewId }
188854
+ };
188855
+ const base = previewPath(project, id, environmentId);
188856
+ let result;
188857
+ let sourceRelease;
188858
+ if (action === "get_preview_plan") {
188859
+ if (args["configuration_id"] !== undefined) {
188860
+ throw new Error("preview-plan only plans resource isolation; configuration_id applies to preview-create");
188861
+ }
188862
+ const releaseId = text(args, "release_id"), branchRef = text(args, "branch_ref");
188863
+ if (!Check2(ApplicationReleaseIdSchema2, releaseId) || !/^[A-Za-z0-9_-]{1,20}$/.test(branchRef))
188864
+ throw new Error("Invalid preview plan identity");
188865
+ const query = new URLSearchParams({ release_id: releaseId, branch_ref: branchRef });
188866
+ if (typeof args["data_mode"] === "string")
188867
+ query.set("data_mode", args["data_mode"]);
188868
+ result = await http.get(`${base}/preview-plan?${query}`, { maxJsonBytes: 65536, responseTimeoutMs: 30000 });
188869
+ } else if (action === "list_previews") {
188870
+ result = await http.get(`${base}/previews`, { maxJsonBytes: 262144, responseTimeoutMs: 30000 });
188871
+ } else {
188872
+ if (previewId !== undefined && !/^[a-f0-9-]{8,64}$/.test(previewId))
188873
+ throw new Error("Invalid preview ID");
188874
+ const endpoint = previewId === undefined ? `${base}/previews` : `${base}/previews/${previewId}`;
188875
+ if (action === "create_preview") {
188876
+ const releaseId = text(args, "release_id");
188877
+ if (!Check2(ApplicationReleaseIdSchema2, releaseId))
188878
+ throw new Error("Invalid preview release ID");
188879
+ const source = await http.get(`${path}/${releaseId}`, { maxJsonBytes: 196608, responseTimeoutMs: 30000 });
188880
+ if (!source.ok)
188881
+ return releaseControlFailure(operation, "HTTP_ERROR", source.status, identity);
188882
+ try {
188883
+ if (source.status !== 200 || !Check2(responseSchema, source.data) || source.data.project_ref !== ref || source.data.application_id !== id)
188884
+ throw new Error;
188885
+ sourceRelease = boundRelease(source.data.release, ref, id);
188886
+ if (sourceRelease.release_id !== releaseId)
188887
+ throw new Error;
188888
+ } catch {
188889
+ return releaseControlFailure(operation, "INVALID_RESPONSE", source.status, identity);
188890
+ }
188891
+ result = await http.post(endpoint, {
188892
+ release_id: releaseId,
188893
+ ...typeof args["branch_name"] === "string" ? { branch_name: args["branch_name"] } : {},
188894
+ ...typeof args["data_mode"] === "string" ? { data_mode: args["data_mode"] } : {},
188895
+ ...typeof args["configuration_id"] === "string" ? { configuration_id: args["configuration_id"] } : {}
188896
+ }, { timeoutMs: 120000, maxJsonBytes: 131072, responseTimeoutMs: 30000 });
188897
+ } else if (action === "cleanup_preview") {
188898
+ result = await http.deleteReleaseMutation(endpoint);
188899
+ } else {
188900
+ result = await http.get(endpoint, { maxJsonBytes: 131072, responseTimeoutMs: 30000 });
188901
+ }
188902
+ }
188903
+ if (!result.ok)
188904
+ return action === "create_preview" || action === "cleanup_preview" ? releaseControlMutationFailure(operation, result, identity) : releaseControlFailure(operation, "HTTP_ERROR", result.status, identity);
188905
+ try {
188906
+ if (result.status !== (action === "create_preview" ? 202 : 200))
188907
+ throw new Error;
188908
+ if (action === "list_previews") {
188909
+ const list = parsePreviewList(result.data, identity);
188910
+ return releaseControlSuccess(operation, { ...identity, previews: list.previews });
188911
+ }
188912
+ const preview = parsePreviewReceipt(result.data, {
188913
+ ...identity,
188914
+ ...typeof args["preview_id"] === "string" ? { preview_id: args["preview_id"] } : {},
188915
+ ...action === "get_preview_plan" && typeof args["release_id"] === "string" ? { release_id: args["release_id"] } : {},
188916
+ ...action === "get_preview_plan" && typeof args["branch_ref"] === "string" ? { branch_ref: args["branch_ref"] } : {},
188917
+ ...action === "get_preview_plan" || action === "create_preview" ? { data_mode: String(args["data_mode"] ?? "schema_only") } : {},
188918
+ ...action === "create_preview" && typeof args["configuration_id"] === "string" ? { configuration_id: args["configuration_id"] } : {}
188919
+ });
188920
+ if (action === "get_preview_plan" && preview.status !== "planned" || action === "create_preview" && preview.status !== "provisioning")
188921
+ throw new Error;
188922
+ if (action === "create_preview" && (!sourceRelease || preview.resources.database_branch.branch_ref !== `pv${preview.preview_id.replaceAll("-", "").slice(0, 18)}` || preview.resources.database_branch.branch_ref === ref || preview.release_id !== applicationReleaseId(preview.resources.database_branch.branch_ref, id, sourceRelease.manifest_sha256)))
188923
+ throw new Error;
188924
+ if (action === "cleanup_preview" && (preview.status !== "cleaned" || !preview.cleanup.completed || preview.cleanup.required)) {
188925
+ return releaseControlFailure(operation, "MUTATION_NOT_SUCCEEDED", result.status, { ...identity, preview });
188926
+ }
188927
+ return releaseControlSuccess(operation, { ...identity, preview });
188928
+ } catch {
188929
+ return releaseControlFailure(operation, action === "create_preview" || action === "cleanup_preview" ? "OUTCOME_UNKNOWN" : "INVALID_RESPONSE", result.status, identity);
188930
+ }
188931
+ }
188119
188932
  if (action === "logs") {
188120
188933
  const environmentId = text(args, "environment_id");
188121
188934
  if (!Check2(ApplicationIdSchema2, environmentId))
@@ -188292,7 +189105,7 @@ function matchingText(candidate, maxLength, pattern) {
188292
189105
  const candidateText = boundedText2(candidate, maxLength);
188293
189106
  return candidateText && pattern.test(candidateText) ? candidateText : null;
188294
189107
  }
188295
- function canonicalTimestamp2(candidate) {
189108
+ function canonicalTimestamp3(candidate) {
188296
189109
  const timestamp = boundedText2(candidate, 64);
188297
189110
  if (!timestamp)
188298
189111
  return null;
@@ -188307,7 +189120,7 @@ function projectedSummary(project) {
188307
189120
  organization_slug: matchingText(project.organization_slug, 128, SAFE_IDENTIFIER_PATTERN),
188308
189121
  name: boundedText2(project.name, 100),
188309
189122
  region: matchingText(project.region, 64, REGION_PATTERN),
188310
- created_at: canonicalTimestamp2(project.created_at),
189123
+ created_at: canonicalTimestamp3(project.created_at),
188311
189124
  status: matchingText(project.status, 64, STATUS_PATTERN)
188312
189125
  };
188313
189126
  return Object.values(summary).every((field) => field !== null) ? summary : null;
@@ -194299,6 +195112,16 @@ async function applyScaffoldWrites(plan) {
194299
195112
  // src/shared/tools/app-tools.ts
194300
195113
  var REMOTE_APP_ACTIONS = {
194301
195114
  upload: "upload_release",
195115
+ "transfer-plan": "get_release_transfer_plan",
195116
+ "promote-plan": "get_promotion_plan",
195117
+ "promote-status": "get_promotion_status",
195118
+ "promote-reconcile": "reconcile_promotion",
195119
+ "preview-plan": "get_preview_plan",
195120
+ "preview-list": "list_previews",
195121
+ "preview-create": "create_preview",
195122
+ "preview-status": "get_preview",
195123
+ "preview-cleanup": "cleanup_preview",
195124
+ transfer: "transfer_release",
194302
195125
  configure: "put_configuration",
194303
195126
  deploy: "deploy_release",
194304
195127
  "deploy-plan": "get_deploy_plan",
@@ -194310,33 +195133,35 @@ var REMOTE_APP_ACTIONS = {
194310
195133
  reconcile: "reconcile_activation",
194311
195134
  retire: "retire_activation",
194312
195135
  logs: "logs",
194313
- "preview-plan": "get_preview_plan",
194314
195136
  preview: "create_preview",
194315
- previews: "list_previews",
194316
- "preview-status": "get_preview",
194317
- "preview-cleanup": "cleanup_preview"
195137
+ previews: "list_previews"
194318
195138
  };
194319
195139
  var REMOTE_APP_DESCRIPTIONS = {
194320
- ref: "[upload/configure/deploy/deploy-plan/diff/status/rollback/rollback-plan/history/reconcile/retire/preview-plan/preview/previews/preview-status/preview-cleanup] Project ref (defaults to context)",
194321
- id: "[upload/configure/deploy/deploy-plan/diff/status/rollback/rollback-plan/history/reconcile/retire/preview-plan/preview/previews/preview-status/preview-cleanup] Application ID",
194322
- environment_id: "[configure/deploy/deploy-plan/diff/status/rollback/rollback-plan/history/reconcile/retire/preview-plan/preview/previews/preview-status/preview-cleanup] Environment ID",
194323
- configuration_id: "[deploy/deploy-plan/diff/rollback/preview] Required for deployment/preview; platform selects for default rollback",
194324
- activation_id: "[deploy/rollback/reconcile/retire] Stable activation ID; auto-generated for deploy/rollback if omitted",
194325
- expected_activation_id: "[deploy/rollback] Auto-selected from verified state; supply with activation_id for an exact retry",
195140
+ ref: "[upload/configure/transfer-plan/transfer/promote-plan/diff/promote/promote-status/promote-reconcile/preview-plan/preview/previews/preview-list/preview-create/preview-status/preview-cleanup/deploy/deploy-plan/status/rollback/rollback-plan/history/reconcile/retire] Project ref (defaults to context)",
195141
+ id: "[upload/configure/transfer-plan/transfer/promote-plan/diff/promote/promote-status/promote-reconcile/preview-plan/preview/previews/preview-list/preview-create/preview-status/preview-cleanup/deploy/deploy-plan/status/rollback/rollback-plan/history/reconcile/retire] Application ID",
195142
+ environment_id: "[configure/promote-plan/diff/promote/promote-status/promote-reconcile/preview-plan/preview/previews/preview-list/preview-create/preview-status/preview-cleanup/deploy/deploy-plan/status/rollback/rollback-plan/history/reconcile/retire] Target environment ID",
195143
+ configuration_id: "[promote-plan/diff/promote/preview-plan/preview/preview-create/deploy/deploy-plan/rollback] Immutable target configuration revision",
195144
+ activation_id: "[deploy/rollback/reconcile/retire] Required explicit activation ID",
195145
+ expected_activation_id: "[deploy/rollback] Required current activation ID, or absent for first activation",
194326
195146
  configuration_path: "[configure] Configuration write JSON including revision and expected revision",
194327
195147
  manifest_path: "[upload] Local delivery.manifest.json",
194328
- release_id: "[deploy/deploy-plan/diff/rollback/reconcile/preview-plan/preview] Required for deployment/preview; defaults to journal-selected previous for rollback",
194329
- preview_id: "[preview-status/preview-cleanup] Preview receipt ID",
194330
- branch_ref: "[preview-plan] Proposed branch ref; preview assigns its own",
194331
- branch_name: "[preview] Branch display name",
194332
- data_mode: "[preview-plan/preview] Default schema_only; full_clone copies rows",
194333
- ttl_seconds: "[preview-plan/preview] Lifetime from 300 to 604800 seconds; creation defaults to the platform TTL",
194334
- wait: "[preview/preview-status] Wait for verified readiness of the selected preview",
194335
- timeout_seconds: "[preview/preview-status] Observation budget with --wait, from 1 to 3600 seconds (default 300)",
195148
+ release_id: "[preview-plan/preview/preview-create/deploy/deploy-plan/diff/rollback/reconcile] Required immutable application release ID",
195149
+ source_ref: "[transfer-plan/transfer/promote-plan/diff/promote] Source project ref",
195150
+ source_release_id: "[transfer-plan/transfer/promote-plan/diff/promote] Source immutable release ID",
195151
+ source_environment_id: "[promote-plan/diff/promote] Source environment ID",
195152
+ mutation_id: "[promote-status/promote-reconcile] Stable promotion mutation ID",
195153
+ approved_migration_digest: "[promote] Exact reviewed migration execution-plan digest",
195154
+ branch_ref: "[preview-plan] Preview branch identity",
195155
+ branch_name: "[preview/preview-create] Human-readable preview branch name",
195156
+ data_mode: "[preview-plan/preview/preview-create] schema_only or full_clone",
195157
+ ttl_seconds: "[preview-plan/preview/preview-create] Lifetime from 300 to 604800 seconds; creation defaults to the platform TTL",
195158
+ wait: "[preview/preview-create/preview-status] Wait for verified readiness of the selected preview",
195159
+ timeout_seconds: "[preview/preview-create/preview-status] Observation budget with --wait, from 1 to 3600 seconds (default 300)",
195160
+ preview_id: "[preview-status/preview-cleanup] Preview ID",
194336
195161
  cursor: "[history] Returned page cursor",
194337
195162
  limit: "[history] Page size, default 20 (1-100)"
194338
195163
  };
194339
- var { action: _remoteAction, ...remoteFields } = APPLICATION_TOOL_SCHEMA;
195164
+ var { action: _remoteAction, plan: _remotePlan, ...remoteFields } = APPLICATION_TOOL_SCHEMA;
194340
195165
  var REMOTE_APP_SCHEMA = Partial(_Object_(remoteFields)).properties;
194341
195166
  for (const [name, schema] of Object.entries(REMOTE_APP_SCHEMA)) {
194342
195167
  const description = REMOTE_APP_DESCRIPTIONS[name];
@@ -195233,6 +196058,30 @@ async function runDelivery(args) {
195233
196058
  const result = await buildDeliveryProject(options, loaded.delivery);
195234
196059
  return textResult(JSON.stringify(result, null, 2), !result.ok);
195235
196060
  }
196061
+ function formatReleaseTransfer(result) {
196062
+ if (result.isError)
196063
+ return result;
196064
+ try {
196065
+ const payload = JSON.parse(result.content[0]?.text ?? "");
196066
+ if (!payload || typeof payload !== "object" || !("ok" in payload) || payload.ok !== true)
196067
+ return result;
196068
+ const plan = "plan" in payload ? parseApplicationReleaseTransferPlan(payload.plan) : null;
196069
+ const receipt = !plan && "transfer" in payload ? parseApplicationReleaseTransferResult(payload.transfer) : null;
196070
+ const data = plan ?? receipt;
196071
+ if (!data)
196072
+ return result;
196073
+ return textResult([
196074
+ `${data.project_ref}/${data.application_id}: ${plan ? plan.action === "no-op" ? "No changes" : "Transfer required" : "Artifact transferred"}`,
196075
+ `Source: ${data.source.project_ref} / ${data.source.release_id}`,
196076
+ `Target release: ${data.candidate_release_id}`,
196077
+ `Manifest: ${data.source.manifest_sha256}`,
196078
+ "Activation: not performed"
196079
+ ].join(`
196080
+ `));
196081
+ } catch {
196082
+ return result;
196083
+ }
196084
+ }
195236
196085
  var historyReceiptSchema = _Object_({
195237
196086
  schema: Literal(RELEASE_CONTROL_RESPONSE_SCHEMA),
195238
196087
  ok: Literal(true),
@@ -195302,6 +196151,167 @@ function formatRemoteDeployPlan(result) {
195302
196151
  return result;
195303
196152
  }
195304
196153
  }
196154
+ function formatPromotionPlan2(result) {
196155
+ if (result.isError)
196156
+ return result;
196157
+ try {
196158
+ const payload = JSON.parse(result.content[0]?.text ?? "");
196159
+ if (!payload || typeof payload !== "object" || !("plan" in payload))
196160
+ return result;
196161
+ const plan = parseApplicationPromotionPlan(payload.plan);
196162
+ return textResult([
196163
+ `${plan.project_ref}/${plan.application_id}/${plan.environment_id}: ${plan.action}`,
196164
+ `Source: ${plan.source.project_ref}/${plan.source.environment_id}`,
196165
+ ...plan.blockers.length ? [`Blocked: ${plan.blockers.join(", ")}`] : [],
196166
+ ...plan.steps.length ? [`Next: ${plan.steps.join(" -> ")}`] : [],
196167
+ "Execution: not performed"
196168
+ ].join(`
196169
+ `));
196170
+ } catch {
196171
+ return result;
196172
+ }
196173
+ }
196174
+ function formatPromotionExecution(result) {
196175
+ if (result.isError)
196176
+ return result;
196177
+ try {
196178
+ const payload = JSON.parse(result.content[0]?.text ?? "");
196179
+ if (!payload || typeof payload !== "object")
196180
+ return result;
196181
+ const operation = "operation" in payload && typeof payload.operation === "string" ? payload.operation : "";
196182
+ if (operation !== "applications.promote_application")
196183
+ return result;
196184
+ if ("result" in payload && Check2(ApplicationPromotionResultSchema, payload.result)) {
196185
+ const envelope = payload;
196186
+ const receipt = payload.result;
196187
+ if (receipt.project_ref !== envelope.project_ref || receipt.application_id !== envelope.application_id || receipt.environment_id !== envelope.environment_id || receipt.mutation_id !== envelope.mutation_id || receipt.plan_sha256 !== envelope.plan_sha256)
196188
+ return result;
196189
+ return textResult([
196190
+ `${String(envelope.project_ref)}/${String(envelope.application_id)}/${String(envelope.environment_id)}: promoted`,
196191
+ `Mutation: ${String(envelope.mutation_id)}`,
196192
+ `Activation: ${String(receipt.activation_id)}`,
196193
+ `Replayed: ${String(receipt.replayed)}`
196194
+ ].join(`
196195
+ `));
196196
+ }
196197
+ } catch {}
196198
+ return result;
196199
+ }
196200
+ function formatWorkflowObservation(result, action) {
196201
+ if (result.isError)
196202
+ return result;
196203
+ try {
196204
+ const payload = JSON.parse(result.content[0]?.text ?? "");
196205
+ if (!payload || typeof payload !== "object")
196206
+ return result;
196207
+ const data = payload;
196208
+ if (typeof data.project_ref !== "string" || typeof data.application_id !== "string" || typeof data.environment_id !== "string")
196209
+ return result;
196210
+ const identity = {
196211
+ project_ref: data.project_ref,
196212
+ application_id: data.application_id,
196213
+ environment_id: data.environment_id
196214
+ };
196215
+ const label = `${identity.project_ref}/${identity.application_id}/${identity.environment_id}`;
196216
+ if (action === "promote-status" || action === "promote-reconcile") {
196217
+ if (!data.mutation || typeof data.mutation !== "object" || !("mutation_id" in data.mutation) || typeof data.mutation.mutation_id !== "string")
196218
+ return result;
196219
+ const status = parsePromotionStatus({ ...identity, mutation: data.mutation, promotion: data.promotion }, { ...identity, mutation_id: data.mutation.mutation_id });
196220
+ return textResult([
196221
+ `${label}: ${status.mutation.status}`,
196222
+ `Mutation: ${status.mutation.mutation_id}`,
196223
+ `Phase: ${status.promotion.phase ?? "not recorded"}`,
196224
+ ...status.promotion.backup_id ? [`Backup: ${status.promotion.backup_id}`] : []
196225
+ ].join(`
196226
+ `));
196227
+ }
196228
+ if (action === "preview-list") {
196229
+ const list = parsePreviewList({ ...identity, previews: data.previews }, identity);
196230
+ return textResult([
196231
+ `${label}: ${list.previews.length} preview(s)`,
196232
+ ...list.previews.map((preview) => `${preview.preview_id} ${preview.status} ${preview.resources.database_branch.branch_ref}`)
196233
+ ].join(`
196234
+ `));
196235
+ }
196236
+ if (["preview-create", "preview-status", "preview-cleanup"].includes(action)) {
196237
+ const preview = parsePreviewReceipt(data.preview, identity);
196238
+ return textResult([
196239
+ `${label}: ${preview.status}`,
196240
+ `Preview: ${preview.preview_id}`,
196241
+ `Branch: ${preview.resources.database_branch.branch_ref} (${preview.resources.database_branch.data_mode})`,
196242
+ `Cleanup: ${preview.cleanup.completed ? "completed" : preview.cleanup.required ? "required" : "not required"}`
196243
+ ].join(`
196244
+ `));
196245
+ }
196246
+ } catch {}
196247
+ return result;
196248
+ }
196249
+ async function runPromotion(request, options, delegate) {
196250
+ const ref = request.ref ?? options.projectRef;
196251
+ if (request.mutation_id !== undefined)
196252
+ throw new Error("app promote generates a new mutation ID; use promote-status or promote-reconcile to inspect an existing mutation");
196253
+ const { format, json, ...remoteRequest } = request;
196254
+ const planArgs = {
196255
+ ...remoteRequest,
196256
+ action: "get_promotion_plan",
196257
+ ref
196258
+ };
196259
+ validateToolArguments(APPLICATION_TOOL_SCHEMA, planArgs);
196260
+ const planned = await delegate(planArgs);
196261
+ if (planned.isError)
196262
+ return planned;
196263
+ let payload;
196264
+ try {
196265
+ payload = JSON.parse(planned.content[0]?.text ?? "");
196266
+ } catch {
196267
+ return textResult("Promotion plan response was not valid JSON.", true);
196268
+ }
196269
+ if (!payload || typeof payload !== "object" || !("plan" in payload)) {
196270
+ return textResult("Promotion plan response was invalid.", true);
196271
+ }
196272
+ let plan;
196273
+ try {
196274
+ plan = parseApplicationPromotionPlan(payload.plan);
196275
+ } catch {
196276
+ return textResult("Promotion plan failed immutable validation.", true);
196277
+ }
196278
+ const identity = { project_ref: ref, application_id: request.id, environment_id: request.environment_id };
196279
+ if (plan.project_ref !== ref || plan.application_id !== request.id || plan.environment_id !== request.environment_id || plan.source.project_ref !== request.source_ref || plan.source.environment_id !== request.source_environment_id || plan.source.release_id !== request.source_release_id || request.configuration_id !== undefined && plan.target.configuration_id !== request.configuration_id) {
196280
+ return releaseControlFailure("applications.promote_application", "INVALID_RESPONSE", null, identity);
196281
+ }
196282
+ if (plan.action === "no-op") {
196283
+ const noOp = releaseControlSuccess("applications.promote_application", {
196284
+ project_ref: plan.project_ref,
196285
+ application_id: plan.application_id,
196286
+ environment_id: plan.environment_id,
196287
+ plan,
196288
+ no_op: true,
196289
+ execution_performed: false
196290
+ });
196291
+ return format === "json" || json ? noOp : textResult(`${plan.project_ref}/${plan.application_id}/${plan.environment_id}: no-op
196292
+ Execution: skipped`);
196293
+ }
196294
+ if (plan.blockers.some((code) => !["MIGRATION_PENDING", "BACKUP_REQUIRED"].includes(code))) {
196295
+ return releaseControlFailure("applications.promote_application", "MUTATION_NOT_SUCCEEDED", null, { ...identity, plan, execution_performed: false });
196296
+ }
196297
+ if (!plan.target.configuration_id)
196298
+ return textResult("Promotion plan has no target configuration revision.", true);
196299
+ const mutationId = crypto.randomUUID();
196300
+ const executeArgs = {
196301
+ ...remoteRequest,
196302
+ action: "promote_application",
196303
+ ref,
196304
+ mutation_id: mutationId,
196305
+ source_ref: plan.source.project_ref,
196306
+ source_environment_id: plan.source.environment_id,
196307
+ source_release_id: plan.source.release_id,
196308
+ configuration_id: plan.target.configuration_id,
196309
+ plan
196310
+ };
196311
+ validateToolArguments(APPLICATION_TOOL_SCHEMA, executeArgs);
196312
+ const result = await delegate(executeArgs);
196313
+ return format === "json" || json ? result : formatPromotionExecution(result);
196314
+ }
195305
196315
  async function runAppTool(request, options = {}) {
195306
196316
  if (request.dry_run !== undefined && request["dry-run"] !== undefined || request.register_in !== undefined && request["register-in"] !== undefined || request.database_url !== undefined && request["database-url"] !== undefined) {
195307
196317
  throw new ScaffoldError("SCAFFOLD_OPTION_INVALID", "Do not combine hyphenated and underscored aliases of the same flag");
@@ -195318,14 +196328,22 @@ async function runAppTool(request, options = {}) {
195318
196328
  if (request.database_url !== undefined && request.action !== "dev" && request.action !== "watch") {
195319
196329
  throw new ScaffoldError("SCAFFOLD_OPTION_INVALID", "--database-url applies only to app dev --profile integration");
195320
196330
  }
195321
- if (request.json !== undefined && (request.action !== "history" || request.json !== true || request.format === "text")) {
195322
- throw new Error("--json applies only to app history and cannot be combined with --format text");
196331
+ if (request.json !== undefined && !Object.hasOwn(REMOTE_APP_ACTIONS, request.action) && request.action !== "promote")
196332
+ throw new Error("--json applies only to remote app actions");
196333
+ if (request.json && request.format === "text")
196334
+ throw new Error("--json and --format text cannot be combined");
196335
+ if (request.action === "promote") {
196336
+ const delegate = options.getApplications?.();
196337
+ if (!delegate)
196338
+ return textResult("App remote actions require a Management API context.", true);
196339
+ return runPromotion(request, options, delegate);
195323
196340
  }
195324
196341
  if (Object.hasOwn(REMOTE_APP_ACTIONS, request.action)) {
195325
196342
  const delegate = options.getApplications?.();
195326
196343
  if (!delegate)
195327
196344
  return textResult("App remote actions require a Management API context.", true);
195328
- const action = REMOTE_APP_ACTIONS[request.action];
196345
+ const promotionDiff = request.action === "diff" && request.source_ref !== undefined && request.source_environment_id !== undefined && request.source_release_id !== undefined;
196346
+ const action = promotionDiff ? "get_promotion_plan" : REMOTE_APP_ACTIONS[request.action];
195329
196347
  const { format, json, ...remoteRequest } = request;
195330
196348
  const args = {
195331
196349
  ...remoteRequest,
@@ -195337,7 +196355,14 @@ async function runAppTool(request, options = {}) {
195337
196355
  if (request.action === "history" && format !== "json" && json !== true) {
195338
196356
  return historyText(result, request, args.ref);
195339
196357
  }
195340
- return (request.action === "deploy-plan" || request.action === "diff") && format !== "json" ? formatRemoteDeployPlan(result) : result;
196358
+ if ((request.action === "deploy-plan" || request.action === "diff") && format !== "json" && (request.action === "deploy-plan" || request.source_ref === undefined || request.source_environment_id === undefined || request.source_release_id === undefined)) {
196359
+ return formatRemoteDeployPlan(result);
196360
+ }
196361
+ if (request.action === "promote-plan" || request.action === "diff" && request.source_ref !== undefined && request.source_environment_id !== undefined && request.source_release_id !== undefined) {
196362
+ if (!result.isError && format !== "json" && !json)
196363
+ return formatPromotionPlan2(result);
196364
+ }
196365
+ return (request.action === "transfer-plan" || request.action === "transfer") && format !== "json" && !json ? formatReleaseTransfer(result) : format !== "json" && !json ? formatWorkflowObservation(result, request.action) : result;
195341
196366
  }
195342
196367
  switch (request.action) {
195343
196368
  case "verify-plan": {
@@ -195411,6 +196436,9 @@ function registerAppTools(server2, options = {}) {
195411
196436
  "build",
195412
196437
  "upload",
195413
196438
  "configure",
196439
+ "transfer-plan",
196440
+ "transfer",
196441
+ "promote-plan",
195414
196442
  "deploy",
195415
196443
  "deploy-plan",
195416
196444
  "diff",
@@ -195424,9 +196452,14 @@ function registerAppTools(server2, options = {}) {
195424
196452
  "preview-plan",
195425
196453
  "preview",
195426
196454
  "previews",
196455
+ "preview-list",
196456
+ "preview-create",
195427
196457
  "preview-status",
195428
- "preview-cleanup"
195429
- ]), "App action; local plan is topology-only, deploy-plan/diff observe remote state, deploy skips verified no-op, rollback never downgrades schema"),
196458
+ "preview-cleanup",
196459
+ "promote",
196460
+ "promote-status",
196461
+ "promote-reconcile"
196462
+ ]), "App action; promote obtains and verifies its plan before one mutation request"),
195430
196463
  kind: optional(stringEnum(["module", "command", "query", "controller", "job", "contract", "resource"]), "[generate] Scaffold kind"),
195431
196464
  template: optional(stringEnum(["minimal", "http", "command", "edge"]), "[init] Minimal application by default; explicit http/command/edge recipes"),
195432
196465
  name: optional(String2(), "[init/generate] Project or object name"),
@@ -195441,8 +196474,8 @@ function registerAppTools(server2, options = {}) {
195441
196474
  include: optional(String2(), "[dev/compile/check] Comma-separated glob patterns for source files"),
195442
196475
  out_dir: optional(String2(), "[dev/compile/plan/build/export-tools] Output directory (default: configured outDir)"),
195443
196476
  strict: optional(Boolean2(), "[dev/compile/check] Promote warnings to errors"),
195444
- format: optional(stringEnum(["text", "json"]), "[dev/generate/compile/check/plan/deploy-plan/diff/history/graph/export-tools] Output format (default: text)"),
195445
- json: optional(Literal(true), "[history] Return the validated JSON receipt"),
196477
+ format: optional(stringEnum(["text", "json"]), "[dev/generate/compile/check/plan/deploy-plan/diff/history/promote-plan/graph/export-tools] Output format (default: text)"),
196478
+ json: optional(Boolean2(), "[history/transfer-plan/transfer/promote-plan] Preserve or return the full machine-readable receipt"),
195446
196479
  profile: optional(stringEnum(["fast", "integration"]), "[dev] Run dev or dev:integration; integration verifies an explicit loopback database"),
195447
196480
  once: optional(Boolean2(), "[dev] Validate and report once without watching"),
195448
196481
  watch: optional(Boolean2(), "[dev] Watch for changes (default: true; --once disables)"),
@@ -195484,7 +196517,7 @@ import { existsSync as existsSync13 } from "node:fs";
195484
196517
  import { join as join17, resolve as resolve21 } from "node:path";
195485
196518
 
195486
196519
  // src/shared/tools/db-workflow.ts
195487
- import { createHash as createHash6 } from "node:crypto";
196520
+ import { createHash as createHash7 } from "node:crypto";
195488
196521
  import { lstat as lstat4, mkdir as mkdir7, mkdtemp, readFile as readFile6, readdir as readdir7, rename as rename3, rm as rm2, writeFile as writeFile6 } from "node:fs/promises";
195489
196522
  import { tmpdir as tmpdir2 } from "node:os";
195490
196523
  import { dirname as dirname10, isAbsolute as isAbsolute7, join as join16, relative as relative9, resolve as resolve20 } from "node:path";
@@ -195550,7 +196583,7 @@ function parseDatabaseSourcesConfig(value) {
195550
196583
  }
195551
196584
 
195552
196585
  // src/shared/tools/db-workflow.ts
195553
- var hash3 = (content) => createHash6("sha256").update(content).digest("hex");
196586
+ var hash3 = (content) => createHash7("sha256").update(content).digest("hex");
195554
196587
  var result = (value, isError = false) => ({
195555
196588
  isError,
195556
196589
  content: [{ type: "text", text: JSON.stringify(value, null, 2) }]
@@ -196239,7 +197272,7 @@ function parseHeaderEnvironment(input) {
196239
197272
  }
196240
197273
  }
196241
197274
  var headerEnvironmentSchema = Optional(decodedSchema(Union([String2(), headerEnvironmentRecord]), headerEnvironmentRecord, parseHeaderEnvironment));
196242
- function objectRecord3(candidate) {
197275
+ function objectRecord4(candidate) {
196243
197276
  return candidate && typeof candidate === "object" && !Array.isArray(candidate) ? candidate : null;
196244
197277
  }
196245
197278
  function boundedCronInteger(input, minimum, maximum) {
@@ -196284,7 +197317,7 @@ function readScheduleBodyFile(bodyPathInput) {
196284
197317
  throw error;
196285
197318
  throw new Error("Scheduled Function body file must contain exact JSON");
196286
197319
  }
196287
- const body = objectRecord3(payload);
197320
+ const body = objectRecord4(payload);
196288
197321
  if (!body)
196289
197322
  throw new Error("Scheduled Function body file must contain a JSON object");
196290
197323
  return body;
@@ -196325,7 +197358,7 @@ function headerValueIsStable(name, value) {
196325
197358
  function scheduleHeaders(mapping, environment) {
196326
197359
  if (mapping === undefined)
196327
197360
  return;
196328
- const headerEnvironment = objectRecord3(mapping);
197361
+ const headerEnvironment = objectRecord4(mapping);
196329
197362
  if (!headerEnvironment)
196330
197363
  throw new Error("'header_env' must be a JSON object");
196331
197364
  const entries = Object.entries(headerEnvironment).map(([headerName, environmentName]) => resolvedHeaderEntry(headerName, environmentName, environment));
@@ -196366,8 +197399,8 @@ function safeSchedulePayload(schedule) {
196366
197399
  return { body_empty: schedule.body_empty, header_names: headerNames };
196367
197400
  }
196368
197401
  function legacySchedulePayload(schedule) {
196369
- const body = objectRecord3(schedule.body);
196370
- const headers = objectRecord3(schedule.headers);
197402
+ const body = objectRecord4(schedule.body);
197403
+ const headers = objectRecord4(schedule.headers);
196371
197404
  if (!body || !headers)
196372
197405
  return null;
196373
197406
  const headerNames = safeHeaderNames(Object.keys(headers));
@@ -196384,7 +197417,7 @@ function safeHeaderNames(candidate) {
196384
197417
  return valid && new Set(names).size === names.length ? names.sort() : null;
196385
197418
  }
196386
197419
  function safeSchedule(candidate) {
196387
- const schedule = objectRecord3(candidate);
197420
+ const schedule = objectRecord4(candidate);
196388
197421
  if (!schedule || !validSafeSchedule(schedule))
196389
197422
  return null;
196390
197423
  const safePayload = safeSchedulePayload(schedule);
@@ -196430,7 +197463,7 @@ function listResponse(ref, response) {
196430
197463
  const operation = "scheduled_functions.list";
196431
197464
  if (!response.ok)
196432
197465
  return scheduleFailure(operation, response);
196433
- const payload = objectRecord3(response.data);
197466
+ const payload = objectRecord4(response.data);
196434
197467
  const rawSchedules = payload?.schedules;
196435
197468
  const schedules = Array.isArray(rawSchedules) ? rawSchedules.map(safeSchedule) : null;
196436
197469
  if (payload?.project_ref !== ref || !schedules || schedules.some((schedule) => !schedule)) {
@@ -196445,7 +197478,7 @@ function getResponse(ref, scheduleId, response) {
196445
197478
  const operation = "scheduled_functions.get";
196446
197479
  if (!response.ok)
196447
197480
  return scheduleFailure(operation, response);
196448
- const payload = objectRecord3(response.data);
197481
+ const payload = objectRecord4(response.data);
196449
197482
  const schedule = safeSchedule(payload?.schedule);
196450
197483
  if (payload?.project_ref !== ref || !schedule || schedule.id !== scheduleId) {
196451
197484
  return releaseControlFailure(operation, "INVALID_RESPONSE", null);
@@ -196458,7 +197491,7 @@ function mutationResponse(expectation, response) {
196458
197491
  const operation = `scheduled_functions.${action}`;
196459
197492
  if (!response.ok)
196460
197493
  return releaseControlMutationFailure(operation, response);
196461
- const payload = objectRecord3(response.data);
197494
+ const payload = objectRecord4(response.data);
196462
197495
  const schedule = safeSchedule(payload?.schedule);
196463
197496
  const confirmsRequest = schedule && Object.entries(expectedFields).every(([field, expected]) => isDeepStrictEqual(schedule[field], expected));
196464
197497
  const confirmsRevision = action === "create" || payload?.previous_updated_at === expectation.expectedUpdatedAt && schedule !== null && schedule.updated_at > expectation.expectedUpdatedAt;
@@ -196476,7 +197509,7 @@ function deleteResponse(ref, scheduleId, expectedUpdatedAt, response) {
196476
197509
  const operation = "scheduled_functions.delete";
196477
197510
  if (!response.ok)
196478
197511
  return releaseControlMutationFailure(operation, response);
196479
- const payload = objectRecord3(response.data);
197512
+ const payload = objectRecord4(response.data);
196480
197513
  if (payload?.deleted !== true || payload.project_ref !== ref || payload.schedule_id !== scheduleId || payload.deleted_updated_at !== expectedUpdatedAt) {
196481
197514
  return releaseControlFailure(operation, "OUTCOME_UNKNOWN", response.status);
196482
197515
  }
@@ -196616,204 +197649,6 @@ var SCHEDULE_TOOL_SCHEMA = {
196616
197649
  header_env: withDescription(headerEnvironmentSchema, "[create/update] JSON map of HTTP header names to environment variable names")
196617
197650
  };
196618
197651
 
196619
- // src/shared/mutation-protocol.ts
196620
- var MUTATION_ID_PATTERN2 = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
196621
- var FINGERPRINT_PATTERN = /^[0-9a-f]{64}$/;
196622
- var OPERATION_PATTERN = /^[a-z0-9][a-z0-9._:-]{0,127}$/;
196623
- var RESOURCE_KEY_PATTERN = /^v1\/(?:[a-z0-9][a-z0-9._-]{0,63})\/([A-Za-z0-9_-]{2,171})$/;
196624
- var RESOURCE_ID_CONTROL_PATTERN = /[\u0000-\u001f\u007f-\u009f]/u;
196625
- var FAILURE_CODE_PATTERN = /^[A-Z][A-Z0-9_]{0,63}$/;
196626
- var LEASE_OWNER_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:@/-]{0,254}$/;
196627
- var TIMESTAMP_PATTERN2 = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
196628
- var MAX_STATUS_RESPONSE_BYTES = 196608;
196629
- var MAX_RESOURCE_ID_BYTES = 128;
196630
- var FATAL_UTF8_DECODER = new TextDecoder("utf-8", { fatal: true });
196631
- var MUTATION_STATUSES = new Set([
196632
- "pending",
196633
- "running",
196634
- "succeeded",
196635
- "failed_retryable",
196636
- "failed_terminal",
196637
- "outcome_unknown"
196638
- ]);
196639
- var MUTATION_RESPONSE_KEYS = ["project_ref", "mutation"];
196640
- var MUTATION_KEYS = [
196641
- "project_ref",
196642
- "mutation_id",
196643
- "operation",
196644
- "resource_key",
196645
- "request_fingerprint",
196646
- "principal",
196647
- "status",
196648
- "checkpoint",
196649
- "receipt",
196650
- "response_status",
196651
- "failure_code",
196652
- "lease",
196653
- "completed_at",
196654
- "created_at",
196655
- "updated_at"
196656
- ];
196657
- var PRINCIPAL_KEYS = ["type", "id"];
196658
- var LEASE_KEYS = ["owner", "expires_at", "fencing_epoch"];
196659
- function isMutationId(candidate) {
196660
- return typeof candidate === "string" && MUTATION_ID_PATTERN2.test(candidate);
196661
- }
196662
- function objectRecord4(candidate) {
196663
- return candidate && typeof candidate === "object" && !Array.isArray(candidate) ? candidate : null;
196664
- }
196665
- function exactRecord(candidate, keys) {
196666
- const record = objectRecord4(candidate);
196667
- if (!record || Object.keys(record).length !== keys.length)
196668
- return null;
196669
- return keys.every((key) => Object.hasOwn(record, key)) ? record : null;
196670
- }
196671
- function emptyProjection(candidate) {
196672
- const record = objectRecord4(candidate);
196673
- return record && Object.keys(record).length === 0 ? record : null;
196674
- }
196675
- function canonicalTimestamp3(candidate) {
196676
- if (typeof candidate !== "string" || !TIMESTAMP_PATTERN2.test(candidate))
196677
- return false;
196678
- const milliseconds = Date.parse(candidate);
196679
- return Number.isFinite(milliseconds) && new Date(milliseconds).toISOString() === candidate;
196680
- }
196681
- function nullableTimestamp(candidate) {
196682
- return candidate === null || canonicalTimestamp3(candidate);
196683
- }
196684
- function safePrincipal(candidate) {
196685
- const principal = exactRecord(candidate, PRINCIPAL_KEYS);
196686
- if (!principal || !["master", "admin", "project"].includes(String(principal.type)))
196687
- return null;
196688
- if (typeof principal.id !== "string" || !principal.id || principal.id.length > 320 || principal.id.trim() !== principal.id || /[\u0000-\u001f\u007f]/u.test(principal.id))
196689
- return null;
196690
- return { type: principal.type, id: principal.id };
196691
- }
196692
- function safeLease(candidate) {
196693
- const lease = exactRecord(candidate, LEASE_KEYS);
196694
- if (!lease || lease.owner !== null && (typeof lease.owner !== "string" || !LEASE_OWNER_PATTERN.test(lease.owner)))
196695
- return null;
196696
- if (!nullableTimestamp(lease.expires_at) || !Number.isSafeInteger(lease.fencing_epoch) || Number(lease.fencing_epoch) < 0)
196697
- return null;
196698
- return {
196699
- owner: lease.owner,
196700
- expires_at: lease.expires_at,
196701
- fencing_epoch: Number(lease.fencing_epoch)
196702
- };
196703
- }
196704
- function safeResponseStatus(candidate) {
196705
- if (candidate === null)
196706
- return null;
196707
- return Number.isInteger(candidate) && Number(candidate) >= 100 && Number(candidate) <= 599 ? Number(candidate) : undefined;
196708
- }
196709
- function validLeaseState(status, lease) {
196710
- const running = status === "running";
196711
- return running ? lease.owner !== null && lease.expires_at !== null && lease.fencing_epoch > 0 : lease.owner === null && lease.expires_at === null;
196712
- }
196713
- function validMutationLifecycle(mutation, receipt, responseStatus) {
196714
- const terminal = ["succeeded", "failed_terminal", "outcome_unknown"].includes(String(mutation.status));
196715
- if (mutation.completed_at !== null !== terminal)
196716
- return false;
196717
- if (mutation.status === "succeeded") {
196718
- return receipt !== null && responseStatus !== null && responseStatus >= 200 && responseStatus < 300 && mutation.failure_code === null;
196719
- }
196720
- if (mutation.status === "failed_terminal") {
196721
- return receipt !== null && typeof mutation.failure_code === "string";
196722
- }
196723
- if (mutation.status === "failed_retryable" || mutation.status === "outcome_unknown") {
196724
- return receipt !== null && typeof mutation.failure_code === "string";
196725
- }
196726
- if (mutation.status === "pending") {
196727
- return receipt === null && responseStatus === null && mutation.failure_code === null;
196728
- }
196729
- return true;
196730
- }
196731
- function canonicalMutationResourceKey(candidate) {
196732
- if (typeof candidate !== "string")
196733
- return false;
196734
- const match = RESOURCE_KEY_PATTERN.exec(candidate);
196735
- if (!match)
196736
- return false;
196737
- const encodedResourceId = match[1];
196738
- const resourceIdBytes = Buffer.from(encodedResourceId, "base64url");
196739
- if (resourceIdBytes.byteLength < 1 || resourceIdBytes.byteLength > MAX_RESOURCE_ID_BYTES || resourceIdBytes.toString("base64url") !== encodedResourceId)
196740
- return false;
196741
- let resourceId;
196742
- try {
196743
- resourceId = FATAL_UTF8_DECODER.decode(resourceIdBytes);
196744
- } catch (decodeError) {
196745
- if (decodeError instanceof TypeError)
196746
- return false;
196747
- throw decodeError;
196748
- }
196749
- return resourceId.trim() === resourceId && !RESOURCE_ID_CONTROL_PATTERN.test(resourceId) && Buffer.from(resourceId, "utf8").equals(resourceIdBytes);
196750
- }
196751
- function validMutationIdentity(mutation) {
196752
- if (!isMutationId(mutation.mutation_id) || typeof mutation.project_ref !== "string")
196753
- return false;
196754
- if (typeof mutation.operation !== "string" || !OPERATION_PATTERN.test(mutation.operation))
196755
- return false;
196756
- if (mutation.resource_key !== null && !canonicalMutationResourceKey(mutation.resource_key))
196757
- return false;
196758
- return typeof mutation.request_fingerprint === "string" && FINGERPRINT_PATTERN.test(mutation.request_fingerprint);
196759
- }
196760
- function validMutationTerminalFields(mutation) {
196761
- if (typeof mutation.status !== "string" || !MUTATION_STATUSES.has(mutation.status))
196762
- return false;
196763
- if (mutation.failure_code !== null && (typeof mutation.failure_code !== "string" || !FAILURE_CODE_PATTERN.test(mutation.failure_code)))
196764
- return false;
196765
- return nullableTimestamp(mutation.completed_at) && canonicalTimestamp3(mutation.created_at) && canonicalTimestamp3(mutation.updated_at);
196766
- }
196767
- function safeMutationStatus(candidate) {
196768
- const mutation = exactRecord(candidate, MUTATION_KEYS);
196769
- if (!mutation || !validMutationIdentity(mutation) || !validMutationTerminalFields(mutation))
196770
- return null;
196771
- const principal = safePrincipal(mutation.principal);
196772
- const checkpoint = emptyProjection(mutation.checkpoint);
196773
- const receipt = mutation.receipt === null ? null : emptyProjection(mutation.receipt);
196774
- const responseStatus = safeResponseStatus(mutation.response_status);
196775
- const lease = safeLease(mutation.lease);
196776
- if (!principal || !checkpoint || mutation.receipt !== null && !receipt || responseStatus === undefined || !lease || !validLeaseState(String(mutation.status), lease) || !validMutationLifecycle(mutation, receipt, responseStatus))
196777
- return null;
196778
- return {
196779
- project_ref: mutation.project_ref,
196780
- mutation_id: mutation.mutation_id,
196781
- operation: mutation.operation,
196782
- resource_key: mutation.resource_key,
196783
- request_fingerprint: mutation.request_fingerprint,
196784
- principal,
196785
- status: mutation.status,
196786
- checkpoint,
196787
- receipt,
196788
- response_status: responseStatus,
196789
- failure_code: mutation.failure_code,
196790
- lease,
196791
- completed_at: mutation.completed_at,
196792
- created_at: mutation.created_at,
196793
- updated_at: mutation.updated_at
196794
- };
196795
- }
196796
- function mutationStatusPath(ref, mutationId) {
196797
- if (!isMutationId(mutationId))
196798
- throw new Error("'mutation_id' must be a UUIDv4");
196799
- return `/v1/projects/${projectRefPathSegment(ref, "Mutations")}/mutations/${encodeURIComponent(mutationId)}`;
196800
- }
196801
- function mutationStatusResponse(ref, mutationId, payload) {
196802
- const response = exactRecord(payload, MUTATION_RESPONSE_KEYS);
196803
- const mutation = safeMutationStatus(response?.mutation);
196804
- return response?.project_ref === ref && mutation?.project_ref === ref && mutation.mutation_id === mutationId ? mutation : null;
196805
- }
196806
- async function fetchMutationStatus(http, ref, mutationId) {
196807
- const response = await http.get(mutationStatusPath(ref, mutationId), {
196808
- maxResponseBytes: MAX_STATUS_RESPONSE_BYTES
196809
- });
196810
- if (!response.ok) {
196811
- return { kind: "unavailable", httpStatus: response.transportError ? null : response.status };
196812
- }
196813
- const mutation = mutationStatusResponse(ref, mutationId, response.data);
196814
- return mutation ? { kind: "available", mutation } : { kind: "invalid" };
196815
- }
196816
-
196817
197652
  // src/shared/tools/mutation-tools.ts
196818
197653
  function requiredText3(args, name) {
196819
197654
  const candidate = args[name];
@@ -196852,7 +197687,7 @@ var MUTATION_TOOL_SCHEMA = {
196852
197687
  };
196853
197688
 
196854
197689
  // src/shared/tools/release-tools.ts
196855
- import { createHash as createHash7 } from "node:crypto";
197690
+ import { createHash as createHash8 } from "node:crypto";
196856
197691
  import { existsSync as existsSync14, mkdirSync as mkdirSync4, readdirSync as readdirSync5, readFileSync as readFileSync11, writeFileSync as writeFileSync3 } from "node:fs";
196857
197692
  import { dirname as dirname11, join as join18, relative as relative10, resolve as resolve23 } from "node:path";
196858
197693
  var SAFE_PROJECT_REF = /^[A-Za-z0-9_-]{1,64}$/;
@@ -196883,7 +197718,8 @@ function validProjectRef(ref) {
196883
197718
  return SAFE_PROJECT_REF.test(ref);
196884
197719
  }
196885
197720
  function backupBelongsToProject(backupId, projectRef) {
196886
- return BACKUP_ID.test(backupId) && backupId.startsWith(`logical-full_${projectRef}_`);
197721
+ const prefix = `logical-full_${projectRef}_`;
197722
+ return BACKUP_ID.test(backupId) && backupId.startsWith(prefix) && /^[a-f0-9]{32}$/.test(backupId.slice(prefix.length));
196887
197723
  }
196888
197724
  function verifiedBackup(value, projectRef) {
196889
197725
  if (!isRecord6(value) || typeof value.backup_id !== "string" || !backupBelongsToProject(value.backup_id, projectRef) || value.project_ref !== projectRef || typeof value.database !== "string" || !SAFE_DATABASE.test(value.database) || value.kind !== "logical-full" || !canonicalTimestamp4(value.created_at) || !canonicalTimestamp4(value.completed_at) || new Date(value.completed_at).valueOf() < new Date(value.created_at).valueOf() || typeof value.bytes !== "number" || !Number.isSafeInteger(value.bytes) || value.bytes <= 0 || typeof value.sha256 !== "string" || !SHA256.test(value.sha256))
@@ -196922,16 +197758,11 @@ function publicBackup(backup) {
196922
197758
  function equalBackup(left, right) {
196923
197759
  return left.backup_id === right.backup_id && left.project_ref === right.project_ref && left.database === right.database && left.kind === right.kind && left.created_at === right.created_at && left.completed_at === right.completed_at && left.bytes === right.bytes && left.sha256 === right.sha256;
196924
197760
  }
196925
- function newlyCreatedBackup(before, after) {
196926
- const afterById = new Map(after.map((backup) => [backup.backup_id, backup]));
196927
- for (const previous of before) {
196928
- const current = afterById.get(previous.backup_id);
196929
- if (!current || !equalBackup(previous, current))
196930
- return null;
197761
+ function requiredBackupId(value, projectRef) {
197762
+ if (typeof value !== "string" || !backupBelongsToProject(value, projectRef)) {
197763
+ throw new Error("'backup_id' must identify a logical-full backup for 'ref'");
196931
197764
  }
196932
- const known = new Set(before.map((backup) => backup.backup_id));
196933
- const additions = after.filter((backup) => !known.has(backup.backup_id));
196934
- return additions.length === 1 ? additions[0] : null;
197765
+ return value;
196935
197766
  }
196936
197767
  function restoreRequest(projectRef, backupId, expectedSha256, restoreConfirmation) {
196937
197768
  if (typeof backupId !== "string" || !backupBelongsToProject(backupId, projectRef)) {
@@ -196957,11 +197788,27 @@ function httpFailure(operation, response) {
196957
197788
  }
196958
197789
  return releaseControlFailure(operation, "HTTP_ERROR", response.transportError ? null : response.status);
196959
197790
  }
196960
- function mutationFailure(operation, response) {
196961
- if (response.responseReadError || response.transportError || response.status === 408 || response.status >= 500) {
196962
- return releaseControlFailure(operation, "OUTCOME_UNKNOWN", response.transportError ? null : response.status);
197791
+ function mutationOutcomeUnknown(response) {
197792
+ return Boolean(response.responseReadError || response.transportError || response.status === 408 || response.status >= 500 || response.status >= 200 && response.status < 300 && response.status !== 200);
197793
+ }
197794
+ function mutationFailure(operation, response, safeState = {}) {
197795
+ if (mutationOutcomeUnknown(response)) {
197796
+ return releaseControlFailure(operation, "OUTCOME_UNKNOWN", response.transportError ? null : response.status, safeState);
196963
197797
  }
196964
- return releaseControlFailure(operation, "HTTP_ERROR", response.status);
197798
+ return releaseControlFailure(operation, "HTTP_ERROR", response.status, safeState);
197799
+ }
197800
+ async function readBackup(http, projectRef, backupId) {
197801
+ let response;
197802
+ try {
197803
+ response = await http.get(`${endpoint(projectRef)}/database/backups/logical/${backupId}`, {
197804
+ maxJsonBytes: MUTATION_MAX_BYTES,
197805
+ responseTimeoutMs: RELEASE_READ_RESPONSE_TIMEOUT_MS
197806
+ });
197807
+ } catch {
197808
+ response = { ok: false, status: 0, data: null, transportError: true };
197809
+ }
197810
+ const candidate = response.ok && response.status === 200 && isRecord6(response.data) ? verifiedBackup(response.data.backup, projectRef) : null;
197811
+ return { response, backup: candidate?.backup_id === backupId ? candidate : null };
196965
197812
  }
196966
197813
  async function readInventory(http, projectRef) {
196967
197814
  const response = await http.get(`${endpoint(projectRef)}/database/backups/logical`, {
@@ -197082,7 +197929,7 @@ function genericScopedReleaseScopeJson(scope) {
197082
197929
  return JSON.stringify(canonicalValue(scope));
197083
197930
  }
197084
197931
  function genericScopedReleaseScopeSha256(scope) {
197085
- return createHash7("sha256").update(genericScopedReleaseScopeJson(scope)).digest("hex");
197932
+ return createHash8("sha256").update(genericScopedReleaseScopeJson(scope)).digest("hex");
197086
197933
  }
197087
197934
  function runGitRevParse(cwd, args) {
197088
197935
  const res = Bun.spawnSync(["git", ...args], { cwd });
@@ -197374,6 +198221,7 @@ function registerReleaseTools(server2, http, options = {}) {
197374
198221
  const localActions = ["scope_inspect", "scope_rebind", "scope_create"];
197375
198222
  const remoteActions = [
197376
198223
  "logical_backup_list",
198224
+ "logical_backup_status",
197377
198225
  "logical_backup_create",
197378
198226
  "logical_backup_restore",
197379
198227
  "postgrest_status",
@@ -197396,7 +198244,7 @@ function registerReleaseTools(server2, http, options = {}) {
197396
198244
  web: optional(Boolean2(), "[scope_create] Whether web assets are included in release scope"),
197397
198245
  dry_run: optional(Boolean2(), "[scope_rebind] Perform calculation and validation without writing files"),
197398
198246
  cwd: optional(String2(), "[scope_inspect/scope_rebind/scope_create] Base directory for relative file and git resolution"),
197399
- backup_id: optional(String2(), "[logical_backup_restore] Exact verified logical-full backup ID from the selected project inventory"),
198247
+ backup_id: optional(String2(), "[logical_backup_create/status/restore] Stable project-bound backup ID; create generates one when omitted"),
197400
198248
  expected_sha256: optional(String2(), "[logical_backup_restore] Exact lowercase SHA-256 from the selected project inventory"),
197401
198249
  restore_confirmation: optional(String2(), "[logical_backup_restore] Exact RESTORE_PROJECT:<ref>:<backup_id>:<sha256> confirmation"),
197402
198250
  subject: optional(String2(), "[release_canary_fixture_stage_replay/disable_replay] Exact central subject UUID"),
@@ -197441,28 +198289,47 @@ function registerReleaseTools(server2, http, options = {}) {
197441
198289
  });
197442
198290
  }
197443
198291
  if (action === "logical_backup_create") {
197444
- const before = await readInventory(http, projectRef);
197445
- const beforeFailure = readInventoryFailure("release.logical_backup.create", before);
197446
- if (beforeFailure)
197447
- return beforeFailure;
197448
- const mutation = await http.postReleaseMutation(`${endpoint(projectRef)}/database/backups/logical`, {}, {
197449
- timeoutMs: BACKUP_TIMEOUT_MS
197450
- });
197451
- const after = await readInventory(http, projectRef);
198292
+ const backupId = requiredBackupId(backup_id ?? `logical-full_${projectRef}_${crypto.randomUUID().replaceAll("-", "")}`, projectRef);
198293
+ const state = { project_ref: projectRef, backup_id: backupId };
198294
+ let mutation;
198295
+ try {
198296
+ mutation = await http.postReleaseMutation(`${endpoint(projectRef)}/database/backups/logical`, { backup_id: backupId }, { timeoutMs: BACKUP_TIMEOUT_MS });
198297
+ } catch {
198298
+ mutation = { ok: false, status: 0, data: null, transportError: true };
198299
+ }
197452
198300
  if (!mutation.ok || mutation.status !== 200) {
197453
- return mutationFailure("release.logical_backup.create", mutation);
198301
+ if (mutationOutcomeUnknown(mutation)) {
198302
+ const observed = await readBackup(http, projectRef, backupId);
198303
+ if (observed.backup)
198304
+ return releaseControlFailure("release.logical_backup.create", "OUTCOME_UNKNOWN", mutation.transportError ? null : mutation.status, {
198305
+ ...state,
198306
+ backup: publicBackup(observed.backup),
198307
+ backup_verified: true,
198308
+ creation_confirmed: false
198309
+ });
198310
+ }
198311
+ return mutationFailure("release.logical_backup.create", mutation, state);
197454
198312
  }
197455
198313
  const responseBackup = isRecord6(mutation.data) ? verifiedBackup(mutation.data.backup, projectRef) : null;
197456
- const afterFailure = readInventoryFailure("release.logical_backup.create", after);
197457
- const addedBackup = after.inventory && newlyCreatedBackup(before.inventory, after.inventory);
197458
- if (!responseBackup || afterFailure || !addedBackup || !equalBackup(responseBackup, addedBackup)) {
197459
- return releaseControlFailure("release.logical_backup.create", "OUTCOME_UNKNOWN", mutation.status);
198314
+ const after = await readBackup(http, projectRef, backupId);
198315
+ if (!responseBackup || responseBackup.backup_id !== backupId || !after.backup || !equalBackup(responseBackup, after.backup)) {
198316
+ return releaseControlFailure("release.logical_backup.create", "OUTCOME_UNKNOWN", mutation.status, state);
197460
198317
  }
197461
198318
  return releaseControlSuccess("release.logical_backup.create", {
197462
- project_ref: projectRef,
197463
- backup: publicBackup(addedBackup)
198319
+ ...state,
198320
+ backup: publicBackup(after.backup)
197464
198321
  });
197465
198322
  }
198323
+ if (action === "logical_backup_status") {
198324
+ const backupId = requiredBackupId(backup_id, projectRef);
198325
+ const read = await readBackup(http, projectRef, backupId);
198326
+ const state = { project_ref: projectRef, backup_id: backupId };
198327
+ if (!read.response.ok || read.response.status !== 200) {
198328
+ const code = read.response.responseReadError || read.response.status >= 200 && read.response.status < 300 ? "INVALID_RESPONSE" : "HTTP_ERROR";
198329
+ return releaseControlFailure("release.logical_backup.status", code, read.response.transportError ? null : read.response.status, state);
198330
+ }
198331
+ return read.backup ? releaseControlSuccess("release.logical_backup.status", { ...state, backup: publicBackup(read.backup) }) : releaseControlFailure("release.logical_backup.status", "INVALID_RESPONSE", read.response.status, state);
198332
+ }
197466
198333
  if (action === "logical_backup_restore") {
197467
198334
  const request = restoreRequest(projectRef, backup_id, expected_sha256, restore_confirmation);
197468
198335
  const before = await readInventory(http, projectRef);
@@ -197568,7 +198435,7 @@ function registerReleaseTools(server2, http, options = {}) {
197568
198435
 
197569
198436
  // src/shared/tools/deploy-tools.ts
197570
198437
  import { access, lstat as lstat5, mkdtemp as mkdtemp2, opendir, readFile as readFile7, rm as rm3, writeFile as writeFile7 } from "node:fs/promises";
197571
- import { createHash as createHash8 } from "node:crypto";
198438
+ import { createHash as createHash9 } from "node:crypto";
197572
198439
  import { tmpdir as tmpdir3 } from "node:os";
197573
198440
  import { basename as basename10, dirname as dirname12, join as join19, relative as relative11, resolve as resolve24, sep as sep5 } from "node:path";
197574
198441
 
@@ -198503,7 +199370,7 @@ async function createFrontendArchive(outputDirectory) {
198503
199370
  try {
198504
199371
  const archiveBytes = zipSync(collected.files, { level: 6, mtime: FIXED_ZIP_MTIME });
198505
199372
  await writeFile7(archivePath, archiveBytes);
198506
- const sha256 = createHash8("sha256").update(archiveBytes).digest("hex");
199373
+ const sha256 = createHash9("sha256").update(archiveBytes).digest("hex");
198507
199374
  return {
198508
199375
  archivePath,
198509
199376
  sha256,
@@ -198726,7 +199593,7 @@ function registerDeployTools(server2, http, options = {}) {
198726
199593
  import { existsSync as existsSync15 } from "node:fs";
198727
199594
  import { readFile as readFile8 } from "node:fs/promises";
198728
199595
  import { readdir as readdir8 } from "node:fs/promises";
198729
- import { createHash as createHash9 } from "node:crypto";
199596
+ import { createHash as createHash10 } from "node:crypto";
198730
199597
  import { join as join20, resolve as resolve25 } from "node:path";
198731
199598
  import { compileProject as compileProject2 } from "@supacloud/compiler";
198732
199599
  var SAFE_TOKEN = /^[A-Za-z0-9._:@/+,-]+$/;
@@ -198825,7 +199692,7 @@ async function sourceFingerprint(root) {
198825
199692
  }
198826
199693
  };
198827
199694
  await visit(root);
198828
- const hash = createHash9("sha256");
199695
+ const hash = createHash10("sha256");
198829
199696
  for (const file of files) {
198830
199697
  hash.update(file.slice(root.length).replace(/\\/g, "/"));
198831
199698
  hash.update(await readFile8(file));
@@ -198994,7 +199861,7 @@ function registerRemoteDevTools(server2, options = {}) {
198994
199861
  // package.json
198995
199862
  var package_default = {
198996
199863
  name: "@supacloud/cli",
198997
- version: "0.80.0",
199864
+ version: "0.82.0",
198998
199865
  description: "Project-scoped CLI for SupaCloud users",
198999
199866
  type: "module",
199000
199867
  main: "./dist/index.js",
@@ -199046,14 +199913,14 @@ var package_default = {
199046
199913
  },
199047
199914
  dependencies: {
199048
199915
  "@supacloud/compiler": "^0.43.0",
199049
- "@supacloud/delivery": "0.10.0",
199916
+ "@supacloud/delivery": "0.11.0",
199050
199917
  "@supacloud/db": "0.16.0",
199051
199918
  fflate: "^0.8.3",
199052
199919
  typebox: "1.3.34"
199053
199920
  },
199054
199921
  overrides: {
199055
199922
  "@supacloud/contracts": "0.8.0",
199056
- "@supacloud/delivery": "0.10.0",
199923
+ "@supacloud/delivery": "0.11.0",
199057
199924
  "@graphql-tools/utils": "^12.0.3"
199058
199925
  },
199059
199926
  devDependencies: {
@@ -199274,6 +200141,7 @@ EXAMPLES
199274
200141
  ${preferredCommand} project logs --log_type database
199275
200142
  ${preferredCommand} project task_stats
199276
200143
  ${preferredCommand} release logical_backup_create --ref abc123
200144
+ ${preferredCommand} release logical_backup_status --ref abc123 --backup_id <backup_id>
199277
200145
  ${preferredCommand} release logical_backup_restore --ref abc123 --backup_id <backup_id> --expected_sha256 <sha256> --restore_confirmation RESTORE_PROJECT:abc123:<backup_id>:<sha256>
199278
200146
  ${preferredCommand} release postgrest_status --ref abc123
199279
200147
  ${preferredCommand} release postgrest_restart --ref abc123
@@ -199318,11 +200186,19 @@ EXAMPLES
199318
200186
  ${preferredCommand} app diff --ref abc123 --id orders --environment_id test --release_id <sha256> --configuration_id <uuid>
199319
200187
  ${preferredCommand} app deploy --ref abc123 --id orders --environment_id test --release_id <sha256> --configuration_id <uuid>
199320
200188
  ${preferredCommand} app status --ref abc123 --id orders --environment_id test
200189
+ ${preferredCommand} app diff --ref abc123 --id orders --environment_id production --source_ref staging --source_environment_id staging --source_release_id <sha256>
200190
+ ${preferredCommand} --env production app promote-plan --id orders --environment_id production --source_ref staging --source_environment_id staging --source_release_id <sha256>
199321
200191
  ${preferredCommand} app preview-plan --ref abc123 --id orders --environment_id test --release_id <sha256> --branch_ref preview-orders
199322
200192
  ${preferredCommand} app preview --ref abc123 --id orders --environment_id test --release_id <sha256> --configuration_id <uuid> --ttl_seconds 3600 --wait
199323
200193
  ${preferredCommand} app previews --ref abc123 --id orders --environment_id test
199324
200194
  ${preferredCommand} app preview-status --ref abc123 --id orders --environment_id test --preview_id <uuid> --wait --timeout_seconds 300
199325
200195
  ${preferredCommand} app preview-cleanup --ref abc123 --id orders --environment_id test --preview_id <uuid>
200196
+ ${preferredCommand} --env production app promote --id orders --environment_id production --source_ref staging --source_environment_id staging --source_release_id <sha256> --confirm-production abc123
200197
+ ${preferredCommand} app promote-status --ref abc123 --id orders --environment_id production --mutation_id <uuid>
200198
+ ${preferredCommand} app promote-reconcile --ref abc123 --id orders --environment_id production --mutation_id <uuid>
200199
+ ${preferredCommand} app preview-create --ref abc123 --id orders --environment_id test --release_id <sha256> --data_mode schema_only
200200
+ ${preferredCommand} app preview-list --ref abc123 --id orders --environment_id test
200201
+ ${preferredCommand} app preview-cleanup --ref abc123 --id orders --environment_id test --preview_id <preview-id>
199326
200202
  ${preferredCommand} app logs --ref abc123 --id orders --environment_id test
199327
200203
  ${preferredCommand} app rollback --ref abc123 --id orders --environment_id test --release_id <old-sha256> --configuration_id <uuid> --activation_id <new-uuid> --expected_activation_id <current-uuid>
199328
200204
  ${preferredCommand} app check --root . --strict