@supacloud/cli 0.81.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,21 +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_release_transfer_plan", "get_promotion_plan", "get_rollback_snapshot", "get_history", "get_deploy_plan", "logs", "get_preview_plan"],
179097
- write: [
179098
- "upload_release",
179099
- "put_configuration",
179100
- "transfer_release",
179101
- "activate_release",
179102
- "deploy_release",
179103
- "rollback_release",
179104
- "reconcile_activation",
179105
- "retire_activation",
179106
- "create_preview",
179107
- "list_previews",
179108
- "get_preview",
179109
- "cleanup_preview"
179110
- ]
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"]
179111
179108
  },
179112
179109
  project: {
179113
179110
  read: ["get", "endpoints", "health", "logs", "api_keys", "settings", "tasks", "task_detail", "task_stats", "dlq", "background_settings"],
@@ -179202,8 +179199,8 @@ var ACTION_POLICY = {
179202
179199
  ai: { local: ["show_skill", "install_skill"] },
179203
179200
  app: {
179204
179201
  local: ["init", "generate", "dev", "watch", "verify-plan", "compile", "check", "graph", "explain", "export-tools", "context", "doctor", "fix", "plan", "build"],
179205
- read: ["status", "logs", "transfer-plan", "promote-plan", "rollback-plan", "history", "deploy-plan", "diff", "preview-plan"],
179206
- write: ["upload", "configure", "transfer", "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"]
179207
179204
  },
179208
179205
  db: {
179209
179206
  local: ["context", "lint", "explain", "diff", "role_sql"],
@@ -187550,6 +187547,374 @@ var ApplicationActivationResultSchema2 = ApplicationActivationResultSchema;
187550
187547
  var ApplicationActivationRetirementResultSchema2 = ApplicationActivationRetirementResultSchema;
187551
187548
  var ApplicationRollbackSnapshotSchema2 = ApplicationRollbackSnapshotSchema;
187552
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
+ }
187553
187918
 
187554
187919
  // src/shared/tools/application-release-transfer.ts
187555
187920
  async function applicationReleaseTransfer(http, input, project) {
@@ -187635,9 +188000,9 @@ var APPLICATION_PREVIEW_FIELDS = {
187635
188000
  timeout_seconds: optional(waitSecondsSchema, "[create_preview/get_preview] Observation budget with wait, from 1 to 3600 seconds (default 300)")
187636
188001
  };
187637
188002
  var strict = { additionalProperties: false };
187638
- var phase = stringEnum(["pending", "ready", "failed", "cleaned"]);
188003
+ var phase2 = stringEnum(["pending", "ready", "failed", "cleaned"]);
187639
188004
  var check5 = String2({ pattern: "^[a-z][a-z0-9_]{0,63}$" });
187640
- var checks = _Array_(check5, { maxItems: 64, uniqueItems: true });
188005
+ var checks2 = _Array_(check5, { maxItems: 64, uniqueItems: true });
187641
188006
  var revision = Union([ApplicationConfigurationIdSchema2, Null()]);
187642
188007
  var receiptSchema = _Object_({
187643
188008
  schema: Literal("supacloud.application-preview.v1"),
@@ -187649,14 +188014,14 @@ var receiptSchema = _Object_({
187649
188014
  expires_at: Union([timestampSchema, Null()]),
187650
188015
  status: stringEnum(["planned", "provisioning", "ready", "failed", "cleaned"]),
187651
188016
  resources: _Object_({
187652
- build_artifact: _Object_({ status: phase, release_id: ApplicationReleaseIdSchema2 }, strict),
187653
- database_branch: _Object_({ status: phase, branch_ref: branchRefSchema, data_mode: stringEnum(["schema_only", "full_clone"]) }, strict),
187654
- queue_namespace: _Object_({ status: phase, namespace: String2({ minLength: 1, maxLength: 128 }) }, strict),
187655
- storage_namespace: _Object_({ status: phase, namespace: branchRefSchema }, strict),
187656
- test_secret: _Object_({ status: phase, name: String2({ pattern: "^[A-Z0-9_-]{1,128}$" }), value_issued: Literal(false) }, strict),
187657
- configuration_revision: _Object_({ status: phase, configuration_id: revision }, strict),
187658
- application_activation: _Object_({ status: phase, activation_id: Union([previewIdSchema, Null()]) }, strict),
187659
- 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)
187660
188025
  }, strict),
187661
188026
  cleanup: _Object_({
187662
188027
  required: Boolean2(),
@@ -187694,14 +188059,14 @@ var actionFields = {
187694
188059
  get_preview: ["preview_id", "wait", "timeout_seconds"],
187695
188060
  cleanup_preview: ["preview_id"]
187696
188061
  };
187697
- function timestamp2(value) {
188062
+ function timestamp3(value) {
187698
188063
  return Number.isFinite(Date.parse(value)) && new Date(value).toISOString() === value;
187699
188064
  }
187700
188065
  function receipt(value, identity) {
187701
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) {
187702
188067
  throw new Error("Invalid preview receipt");
187703
188068
  }
187704
- 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)) {
187705
188070
  throw new Error("Invalid preview expiry");
187706
188071
  }
187707
188072
  if (value.status === "ready") {
@@ -187925,7 +188290,11 @@ async function applicationPreviewAction(http, args, project) {
187925
188290
  throw new Error;
187926
188291
  }
187927
188292
  if (preview.status === "failed" || action === "cleanup_preview" && preview.status !== "cleaned") {
187928
- 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
+ });
187929
188298
  }
187930
188299
  if (timeoutSeconds !== undefined)
187931
188300
  return await waitForPreview(http, path, operation, identity, preview, timeoutSeconds, startedAt);
@@ -187950,12 +188319,20 @@ var APPLICATION_TOOL_SCHEMA = {
187950
188319
  "get_release_transfer_plan",
187951
188320
  "transfer_release",
187952
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",
187953
188330
  "activate_release",
188331
+ "reconcile_activation",
188332
+ "retire_activation",
187954
188333
  "rollback_release",
187955
188334
  "get_rollback_snapshot",
187956
188335
  "get_history",
187957
- "reconcile_activation",
187958
- "retire_activation",
187959
188336
  ...APPLICATION_PREVIEW_ACTIONS,
187960
188337
  "logs"
187961
188338
  ]), "Action"),
@@ -187971,6 +188348,9 @@ var APPLICATION_TOOL_SCHEMA = {
187971
188348
  source_ref: optional(String2({ pattern: "^[A-Za-z0-9_-]{1,20}$" }), "[get_release_transfer_plan/transfer_release] Source project ref"),
187972
188349
  source_release_id: optional(ApplicationReleaseIdSchema2, "[get_release_transfer_plan/transfer_release] Source immutable release ID"),
187973
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"),
187974
188354
  cursor: optional(Union([ApplicationReleaseIdSchema2, ApplicationActivationHistoryCursorSchema2]), "[list_releases/get_history] Returned page cursor"),
187975
188355
  limit: optional(Integer({ minimum: 1, maximum: 100 }), "[list_releases/get_history] Page size, default 50/20"),
187976
188356
  offset: optional(Integer({ minimum: 0, maximum: 1e6 }), "[logs] Result offset"),
@@ -187991,6 +188371,12 @@ var inventorySchema = _Object_({
187991
188371
  releases: _Array_(ApplicationReleaseRecordSchema2, { maxItems: 100 }),
187992
188372
  next_cursor: Union([ApplicationReleaseIdSchema2, Null()])
187993
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
+ }
187994
188380
  function text(args, field) {
187995
188381
  const value = args[field];
187996
188382
  if (typeof value !== "string" || !value.trim())
@@ -188383,6 +188769,166 @@ function registerApplicationTools(server2, http) {
188383
188769
  return releaseControlFailure(operation, "INVALID_RESPONSE", result.status);
188384
188770
  }
188385
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
+ }
188386
188932
  if (action === "logs") {
188387
188933
  const environmentId = text(args, "environment_id");
188388
188934
  if (!Check2(ApplicationIdSchema2, environmentId))
@@ -188559,7 +189105,7 @@ function matchingText(candidate, maxLength, pattern) {
188559
189105
  const candidateText = boundedText2(candidate, maxLength);
188560
189106
  return candidateText && pattern.test(candidateText) ? candidateText : null;
188561
189107
  }
188562
- function canonicalTimestamp2(candidate) {
189108
+ function canonicalTimestamp3(candidate) {
188563
189109
  const timestamp = boundedText2(candidate, 64);
188564
189110
  if (!timestamp)
188565
189111
  return null;
@@ -188574,7 +189120,7 @@ function projectedSummary(project) {
188574
189120
  organization_slug: matchingText(project.organization_slug, 128, SAFE_IDENTIFIER_PATTERN),
188575
189121
  name: boundedText2(project.name, 100),
188576
189122
  region: matchingText(project.region, 64, REGION_PATTERN),
188577
- created_at: canonicalTimestamp2(project.created_at),
189123
+ created_at: canonicalTimestamp3(project.created_at),
188578
189124
  status: matchingText(project.status, 64, STATUS_PATTERN)
188579
189125
  };
188580
189126
  return Object.values(summary).every((field) => field !== null) ? summary : null;
@@ -194568,6 +195114,13 @@ var REMOTE_APP_ACTIONS = {
194568
195114
  upload: "upload_release",
194569
195115
  "transfer-plan": "get_release_transfer_plan",
194570
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",
194571
195124
  transfer: "transfer_release",
194572
195125
  configure: "put_configuration",
194573
195126
  deploy: "deploy_release",
@@ -194580,36 +195133,35 @@ var REMOTE_APP_ACTIONS = {
194580
195133
  reconcile: "reconcile_activation",
194581
195134
  retire: "retire_activation",
194582
195135
  logs: "logs",
194583
- "preview-plan": "get_preview_plan",
194584
195136
  preview: "create_preview",
194585
- previews: "list_previews",
194586
- "preview-status": "get_preview",
194587
- "preview-cleanup": "cleanup_preview"
195137
+ previews: "list_previews"
194588
195138
  };
194589
195139
  var REMOTE_APP_DESCRIPTIONS = {
194590
- ref: "[upload/configure/transfer-plan/transfer/promote-plan/deploy/deploy-plan/diff/status/rollback/rollback-plan/history/reconcile/retire/preview-plan/preview/previews/preview-status/preview-cleanup] Project ref (defaults to context)",
194591
- id: "[upload/configure/transfer-plan/transfer/promote-plan/deploy/deploy-plan/diff/status/rollback/rollback-plan/history/reconcile/retire/preview-plan/preview/previews/preview-status/preview-cleanup] Application ID",
194592
- environment_id: "[configure/promote-plan/deploy/deploy-plan/diff/status/rollback/rollback-plan/history/reconcile/retire/preview-plan/preview/previews/preview-status/preview-cleanup] Target environment ID",
194593
- configuration_id: "[promote-plan/deploy/deploy-plan/diff/rollback/preview] Immutable target configuration revision; required for deployment/preview; platform selects for default rollback",
194594
- activation_id: "[deploy/rollback/reconcile/retire] Stable activation ID; auto-generated for deploy/rollback if omitted",
194595
- 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",
194596
195146
  configuration_path: "[configure] Configuration write JSON including revision and expected revision",
194597
195147
  manifest_path: "[upload] Local delivery.manifest.json",
194598
- release_id: "[deploy/deploy-plan/diff/rollback/reconcile/preview-plan/preview] Required for deployment/preview; defaults to journal-selected previous for rollback",
194599
- source_ref: "[transfer-plan/transfer/promote-plan] Source project ref",
194600
- source_release_id: "[transfer-plan/transfer/promote-plan] Source immutable release ID",
194601
- source_environment_id: "[promote-plan] Source environment ID",
194602
- preview_id: "[preview-status/preview-cleanup] Preview receipt ID",
194603
- branch_ref: "[preview-plan] Proposed branch ref; preview assigns its own",
194604
- branch_name: "[preview] Branch display name",
194605
- data_mode: "[preview-plan/preview] Default schema_only; full_clone copies rows",
194606
- ttl_seconds: "[preview-plan/preview] Lifetime from 300 to 604800 seconds; creation defaults to the platform TTL",
194607
- wait: "[preview/preview-status] Wait for verified readiness of the selected preview",
194608
- 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",
194609
195161
  cursor: "[history] Returned page cursor",
194610
195162
  limit: "[history] Page size, default 20 (1-100)"
194611
195163
  };
194612
- var { action: _remoteAction, ...remoteFields } = APPLICATION_TOOL_SCHEMA;
195164
+ var { action: _remoteAction, plan: _remotePlan, ...remoteFields } = APPLICATION_TOOL_SCHEMA;
194613
195165
  var REMOTE_APP_SCHEMA = Partial(_Object_(remoteFields)).properties;
194614
195166
  for (const [name, schema] of Object.entries(REMOTE_APP_SCHEMA)) {
194615
195167
  const description = REMOTE_APP_DESCRIPTIONS[name];
@@ -195599,6 +196151,167 @@ function formatRemoteDeployPlan(result) {
195599
196151
  return result;
195600
196152
  }
195601
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
+ }
195602
196315
  async function runAppTool(request, options = {}) {
195603
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) {
195604
196317
  throw new ScaffoldError("SCAFFOLD_OPTION_INVALID", "Do not combine hyphenated and underscored aliases of the same flag");
@@ -195615,16 +196328,22 @@ async function runAppTool(request, options = {}) {
195615
196328
  if (request.database_url !== undefined && request.action !== "dev" && request.action !== "watch") {
195616
196329
  throw new ScaffoldError("SCAFFOLD_OPTION_INVALID", "--database-url applies only to app dev --profile integration");
195617
196330
  }
195618
- if (request.json !== undefined && request.action !== "history" && request.action !== "transfer-plan" && request.action !== "transfer" && request.action !== "promote-plan") {
195619
- throw new Error("--json applies only to app history, transfer-plan, transfer or promote-plan");
195620
- }
195621
- if (request.json !== undefined && request.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")
195622
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);
196340
+ }
195623
196341
  if (Object.hasOwn(REMOTE_APP_ACTIONS, request.action)) {
195624
196342
  const delegate = options.getApplications?.();
195625
196343
  if (!delegate)
195626
196344
  return textResult("App remote actions require a Management API context.", true);
195627
- 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];
195628
196347
  const { format, json, ...remoteRequest } = request;
195629
196348
  const args = {
195630
196349
  ...remoteRequest,
@@ -195636,24 +196355,14 @@ async function runAppTool(request, options = {}) {
195636
196355
  if (request.action === "history" && format !== "json" && json !== true) {
195637
196356
  return historyText(result, request, args.ref);
195638
196357
  }
195639
- if ((request.action === "deploy-plan" || request.action === "diff") && format !== "json") {
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)) {
195640
196359
  return formatRemoteDeployPlan(result);
195641
196360
  }
195642
- if (request.action === "promote-plan" && !result.isError && format !== "json" && !json) {
195643
- const payload = JSON.parse(result.content[0]?.text ?? "");
195644
- if (!payload || typeof payload !== "object" || !("plan" in payload))
195645
- return result;
195646
- const plan = parseApplicationPromotionPlan(payload.plan);
195647
- return textResult([
195648
- `${plan.project_ref}/${plan.application_id}/${plan.environment_id}: ${plan.action}`,
195649
- `Source: ${plan.source.project_ref}/${plan.source.environment_id}`,
195650
- ...plan.blockers.length ? [`Blocked: ${plan.blockers.join(", ")}`] : [],
195651
- ...plan.steps.length ? [`Next: ${plan.steps.join(" -> ")}`] : [],
195652
- "Execution: not performed"
195653
- ].join(`
195654
- `));
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);
195655
196364
  }
195656
- return (request.action === "transfer-plan" || request.action === "transfer") && format !== "json" && !json ? formatReleaseTransfer(result) : result;
196365
+ return (request.action === "transfer-plan" || request.action === "transfer") && format !== "json" && !json ? formatReleaseTransfer(result) : format !== "json" && !json ? formatWorkflowObservation(result, request.action) : result;
195657
196366
  }
195658
196367
  switch (request.action) {
195659
196368
  case "verify-plan": {
@@ -195743,9 +196452,14 @@ function registerAppTools(server2, options = {}) {
195743
196452
  "preview-plan",
195744
196453
  "preview",
195745
196454
  "previews",
196455
+ "preview-list",
196456
+ "preview-create",
195746
196457
  "preview-status",
195747
- "preview-cleanup"
195748
- ]), "App action; transfer reuses a verified release without build or activation; 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"),
195749
196463
  kind: optional(stringEnum(["module", "command", "query", "controller", "job", "contract", "resource"]), "[generate] Scaffold kind"),
195750
196464
  template: optional(stringEnum(["minimal", "http", "command", "edge"]), "[init] Minimal application by default; explicit http/command/edge recipes"),
195751
196465
  name: optional(String2(), "[init/generate] Project or object name"),
@@ -195803,7 +196517,7 @@ import { existsSync as existsSync13 } from "node:fs";
195803
196517
  import { join as join17, resolve as resolve21 } from "node:path";
195804
196518
 
195805
196519
  // src/shared/tools/db-workflow.ts
195806
- import { createHash as createHash6 } from "node:crypto";
196520
+ import { createHash as createHash7 } from "node:crypto";
195807
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";
195808
196522
  import { tmpdir as tmpdir2 } from "node:os";
195809
196523
  import { dirname as dirname10, isAbsolute as isAbsolute7, join as join16, relative as relative9, resolve as resolve20 } from "node:path";
@@ -195869,7 +196583,7 @@ function parseDatabaseSourcesConfig(value) {
195869
196583
  }
195870
196584
 
195871
196585
  // src/shared/tools/db-workflow.ts
195872
- var hash3 = (content) => createHash6("sha256").update(content).digest("hex");
196586
+ var hash3 = (content) => createHash7("sha256").update(content).digest("hex");
195873
196587
  var result = (value, isError = false) => ({
195874
196588
  isError,
195875
196589
  content: [{ type: "text", text: JSON.stringify(value, null, 2) }]
@@ -196558,7 +197272,7 @@ function parseHeaderEnvironment(input) {
196558
197272
  }
196559
197273
  }
196560
197274
  var headerEnvironmentSchema = Optional(decodedSchema(Union([String2(), headerEnvironmentRecord]), headerEnvironmentRecord, parseHeaderEnvironment));
196561
- function objectRecord3(candidate) {
197275
+ function objectRecord4(candidate) {
196562
197276
  return candidate && typeof candidate === "object" && !Array.isArray(candidate) ? candidate : null;
196563
197277
  }
196564
197278
  function boundedCronInteger(input, minimum, maximum) {
@@ -196603,7 +197317,7 @@ function readScheduleBodyFile(bodyPathInput) {
196603
197317
  throw error;
196604
197318
  throw new Error("Scheduled Function body file must contain exact JSON");
196605
197319
  }
196606
- const body = objectRecord3(payload);
197320
+ const body = objectRecord4(payload);
196607
197321
  if (!body)
196608
197322
  throw new Error("Scheduled Function body file must contain a JSON object");
196609
197323
  return body;
@@ -196644,7 +197358,7 @@ function headerValueIsStable(name, value) {
196644
197358
  function scheduleHeaders(mapping, environment) {
196645
197359
  if (mapping === undefined)
196646
197360
  return;
196647
- const headerEnvironment = objectRecord3(mapping);
197361
+ const headerEnvironment = objectRecord4(mapping);
196648
197362
  if (!headerEnvironment)
196649
197363
  throw new Error("'header_env' must be a JSON object");
196650
197364
  const entries = Object.entries(headerEnvironment).map(([headerName, environmentName]) => resolvedHeaderEntry(headerName, environmentName, environment));
@@ -196685,8 +197399,8 @@ function safeSchedulePayload(schedule) {
196685
197399
  return { body_empty: schedule.body_empty, header_names: headerNames };
196686
197400
  }
196687
197401
  function legacySchedulePayload(schedule) {
196688
- const body = objectRecord3(schedule.body);
196689
- const headers = objectRecord3(schedule.headers);
197402
+ const body = objectRecord4(schedule.body);
197403
+ const headers = objectRecord4(schedule.headers);
196690
197404
  if (!body || !headers)
196691
197405
  return null;
196692
197406
  const headerNames = safeHeaderNames(Object.keys(headers));
@@ -196703,7 +197417,7 @@ function safeHeaderNames(candidate) {
196703
197417
  return valid && new Set(names).size === names.length ? names.sort() : null;
196704
197418
  }
196705
197419
  function safeSchedule(candidate) {
196706
- const schedule = objectRecord3(candidate);
197420
+ const schedule = objectRecord4(candidate);
196707
197421
  if (!schedule || !validSafeSchedule(schedule))
196708
197422
  return null;
196709
197423
  const safePayload = safeSchedulePayload(schedule);
@@ -196749,7 +197463,7 @@ function listResponse(ref, response) {
196749
197463
  const operation = "scheduled_functions.list";
196750
197464
  if (!response.ok)
196751
197465
  return scheduleFailure(operation, response);
196752
- const payload = objectRecord3(response.data);
197466
+ const payload = objectRecord4(response.data);
196753
197467
  const rawSchedules = payload?.schedules;
196754
197468
  const schedules = Array.isArray(rawSchedules) ? rawSchedules.map(safeSchedule) : null;
196755
197469
  if (payload?.project_ref !== ref || !schedules || schedules.some((schedule) => !schedule)) {
@@ -196764,7 +197478,7 @@ function getResponse(ref, scheduleId, response) {
196764
197478
  const operation = "scheduled_functions.get";
196765
197479
  if (!response.ok)
196766
197480
  return scheduleFailure(operation, response);
196767
- const payload = objectRecord3(response.data);
197481
+ const payload = objectRecord4(response.data);
196768
197482
  const schedule = safeSchedule(payload?.schedule);
196769
197483
  if (payload?.project_ref !== ref || !schedule || schedule.id !== scheduleId) {
196770
197484
  return releaseControlFailure(operation, "INVALID_RESPONSE", null);
@@ -196777,7 +197491,7 @@ function mutationResponse(expectation, response) {
196777
197491
  const operation = `scheduled_functions.${action}`;
196778
197492
  if (!response.ok)
196779
197493
  return releaseControlMutationFailure(operation, response);
196780
- const payload = objectRecord3(response.data);
197494
+ const payload = objectRecord4(response.data);
196781
197495
  const schedule = safeSchedule(payload?.schedule);
196782
197496
  const confirmsRequest = schedule && Object.entries(expectedFields).every(([field, expected]) => isDeepStrictEqual(schedule[field], expected));
196783
197497
  const confirmsRevision = action === "create" || payload?.previous_updated_at === expectation.expectedUpdatedAt && schedule !== null && schedule.updated_at > expectation.expectedUpdatedAt;
@@ -196795,7 +197509,7 @@ function deleteResponse(ref, scheduleId, expectedUpdatedAt, response) {
196795
197509
  const operation = "scheduled_functions.delete";
196796
197510
  if (!response.ok)
196797
197511
  return releaseControlMutationFailure(operation, response);
196798
- const payload = objectRecord3(response.data);
197512
+ const payload = objectRecord4(response.data);
196799
197513
  if (payload?.deleted !== true || payload.project_ref !== ref || payload.schedule_id !== scheduleId || payload.deleted_updated_at !== expectedUpdatedAt) {
196800
197514
  return releaseControlFailure(operation, "OUTCOME_UNKNOWN", response.status);
196801
197515
  }
@@ -196935,204 +197649,6 @@ var SCHEDULE_TOOL_SCHEMA = {
196935
197649
  header_env: withDescription(headerEnvironmentSchema, "[create/update] JSON map of HTTP header names to environment variable names")
196936
197650
  };
196937
197651
 
196938
- // src/shared/mutation-protocol.ts
196939
- 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}$/;
196940
- var FINGERPRINT_PATTERN = /^[0-9a-f]{64}$/;
196941
- var OPERATION_PATTERN = /^[a-z0-9][a-z0-9._:-]{0,127}$/;
196942
- var RESOURCE_KEY_PATTERN = /^v1\/(?:[a-z0-9][a-z0-9._-]{0,63})\/([A-Za-z0-9_-]{2,171})$/;
196943
- var RESOURCE_ID_CONTROL_PATTERN = /[\u0000-\u001f\u007f-\u009f]/u;
196944
- var FAILURE_CODE_PATTERN = /^[A-Z][A-Z0-9_]{0,63}$/;
196945
- var LEASE_OWNER_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:@/-]{0,254}$/;
196946
- var TIMESTAMP_PATTERN2 = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
196947
- var MAX_STATUS_RESPONSE_BYTES = 196608;
196948
- var MAX_RESOURCE_ID_BYTES = 128;
196949
- var FATAL_UTF8_DECODER = new TextDecoder("utf-8", { fatal: true });
196950
- var MUTATION_STATUSES = new Set([
196951
- "pending",
196952
- "running",
196953
- "succeeded",
196954
- "failed_retryable",
196955
- "failed_terminal",
196956
- "outcome_unknown"
196957
- ]);
196958
- var MUTATION_RESPONSE_KEYS = ["project_ref", "mutation"];
196959
- var MUTATION_KEYS = [
196960
- "project_ref",
196961
- "mutation_id",
196962
- "operation",
196963
- "resource_key",
196964
- "request_fingerprint",
196965
- "principal",
196966
- "status",
196967
- "checkpoint",
196968
- "receipt",
196969
- "response_status",
196970
- "failure_code",
196971
- "lease",
196972
- "completed_at",
196973
- "created_at",
196974
- "updated_at"
196975
- ];
196976
- var PRINCIPAL_KEYS = ["type", "id"];
196977
- var LEASE_KEYS = ["owner", "expires_at", "fencing_epoch"];
196978
- function isMutationId(candidate) {
196979
- return typeof candidate === "string" && MUTATION_ID_PATTERN2.test(candidate);
196980
- }
196981
- function objectRecord4(candidate) {
196982
- return candidate && typeof candidate === "object" && !Array.isArray(candidate) ? candidate : null;
196983
- }
196984
- function exactRecord(candidate, keys) {
196985
- const record = objectRecord4(candidate);
196986
- if (!record || Object.keys(record).length !== keys.length)
196987
- return null;
196988
- return keys.every((key) => Object.hasOwn(record, key)) ? record : null;
196989
- }
196990
- function emptyProjection(candidate) {
196991
- const record = objectRecord4(candidate);
196992
- return record && Object.keys(record).length === 0 ? record : null;
196993
- }
196994
- function canonicalTimestamp3(candidate) {
196995
- if (typeof candidate !== "string" || !TIMESTAMP_PATTERN2.test(candidate))
196996
- return false;
196997
- const milliseconds = Date.parse(candidate);
196998
- return Number.isFinite(milliseconds) && new Date(milliseconds).toISOString() === candidate;
196999
- }
197000
- function nullableTimestamp(candidate) {
197001
- return candidate === null || canonicalTimestamp3(candidate);
197002
- }
197003
- function safePrincipal(candidate) {
197004
- const principal = exactRecord(candidate, PRINCIPAL_KEYS);
197005
- if (!principal || !["master", "admin", "project"].includes(String(principal.type)))
197006
- return null;
197007
- if (typeof principal.id !== "string" || !principal.id || principal.id.length > 320 || principal.id.trim() !== principal.id || /[\u0000-\u001f\u007f]/u.test(principal.id))
197008
- return null;
197009
- return { type: principal.type, id: principal.id };
197010
- }
197011
- function safeLease(candidate) {
197012
- const lease = exactRecord(candidate, LEASE_KEYS);
197013
- if (!lease || lease.owner !== null && (typeof lease.owner !== "string" || !LEASE_OWNER_PATTERN.test(lease.owner)))
197014
- return null;
197015
- if (!nullableTimestamp(lease.expires_at) || !Number.isSafeInteger(lease.fencing_epoch) || Number(lease.fencing_epoch) < 0)
197016
- return null;
197017
- return {
197018
- owner: lease.owner,
197019
- expires_at: lease.expires_at,
197020
- fencing_epoch: Number(lease.fencing_epoch)
197021
- };
197022
- }
197023
- function safeResponseStatus(candidate) {
197024
- if (candidate === null)
197025
- return null;
197026
- return Number.isInteger(candidate) && Number(candidate) >= 100 && Number(candidate) <= 599 ? Number(candidate) : undefined;
197027
- }
197028
- function validLeaseState(status, lease) {
197029
- const running = status === "running";
197030
- return running ? lease.owner !== null && lease.expires_at !== null && lease.fencing_epoch > 0 : lease.owner === null && lease.expires_at === null;
197031
- }
197032
- function validMutationLifecycle(mutation, receipt, responseStatus) {
197033
- const terminal = ["succeeded", "failed_terminal", "outcome_unknown"].includes(String(mutation.status));
197034
- if (mutation.completed_at !== null !== terminal)
197035
- return false;
197036
- if (mutation.status === "succeeded") {
197037
- return receipt !== null && responseStatus !== null && responseStatus >= 200 && responseStatus < 300 && mutation.failure_code === null;
197038
- }
197039
- if (mutation.status === "failed_terminal") {
197040
- return receipt !== null && typeof mutation.failure_code === "string";
197041
- }
197042
- if (mutation.status === "failed_retryable" || mutation.status === "outcome_unknown") {
197043
- return receipt !== null && typeof mutation.failure_code === "string";
197044
- }
197045
- if (mutation.status === "pending") {
197046
- return receipt === null && responseStatus === null && mutation.failure_code === null;
197047
- }
197048
- return true;
197049
- }
197050
- function canonicalMutationResourceKey(candidate) {
197051
- if (typeof candidate !== "string")
197052
- return false;
197053
- const match = RESOURCE_KEY_PATTERN.exec(candidate);
197054
- if (!match)
197055
- return false;
197056
- const encodedResourceId = match[1];
197057
- const resourceIdBytes = Buffer.from(encodedResourceId, "base64url");
197058
- if (resourceIdBytes.byteLength < 1 || resourceIdBytes.byteLength > MAX_RESOURCE_ID_BYTES || resourceIdBytes.toString("base64url") !== encodedResourceId)
197059
- return false;
197060
- let resourceId;
197061
- try {
197062
- resourceId = FATAL_UTF8_DECODER.decode(resourceIdBytes);
197063
- } catch (decodeError) {
197064
- if (decodeError instanceof TypeError)
197065
- return false;
197066
- throw decodeError;
197067
- }
197068
- return resourceId.trim() === resourceId && !RESOURCE_ID_CONTROL_PATTERN.test(resourceId) && Buffer.from(resourceId, "utf8").equals(resourceIdBytes);
197069
- }
197070
- function validMutationIdentity(mutation) {
197071
- if (!isMutationId(mutation.mutation_id) || typeof mutation.project_ref !== "string")
197072
- return false;
197073
- if (typeof mutation.operation !== "string" || !OPERATION_PATTERN.test(mutation.operation))
197074
- return false;
197075
- if (mutation.resource_key !== null && !canonicalMutationResourceKey(mutation.resource_key))
197076
- return false;
197077
- return typeof mutation.request_fingerprint === "string" && FINGERPRINT_PATTERN.test(mutation.request_fingerprint);
197078
- }
197079
- function validMutationTerminalFields(mutation) {
197080
- if (typeof mutation.status !== "string" || !MUTATION_STATUSES.has(mutation.status))
197081
- return false;
197082
- if (mutation.failure_code !== null && (typeof mutation.failure_code !== "string" || !FAILURE_CODE_PATTERN.test(mutation.failure_code)))
197083
- return false;
197084
- return nullableTimestamp(mutation.completed_at) && canonicalTimestamp3(mutation.created_at) && canonicalTimestamp3(mutation.updated_at);
197085
- }
197086
- function safeMutationStatus(candidate) {
197087
- const mutation = exactRecord(candidate, MUTATION_KEYS);
197088
- if (!mutation || !validMutationIdentity(mutation) || !validMutationTerminalFields(mutation))
197089
- return null;
197090
- const principal = safePrincipal(mutation.principal);
197091
- const checkpoint = emptyProjection(mutation.checkpoint);
197092
- const receipt = mutation.receipt === null ? null : emptyProjection(mutation.receipt);
197093
- const responseStatus = safeResponseStatus(mutation.response_status);
197094
- const lease = safeLease(mutation.lease);
197095
- if (!principal || !checkpoint || mutation.receipt !== null && !receipt || responseStatus === undefined || !lease || !validLeaseState(String(mutation.status), lease) || !validMutationLifecycle(mutation, receipt, responseStatus))
197096
- return null;
197097
- return {
197098
- project_ref: mutation.project_ref,
197099
- mutation_id: mutation.mutation_id,
197100
- operation: mutation.operation,
197101
- resource_key: mutation.resource_key,
197102
- request_fingerprint: mutation.request_fingerprint,
197103
- principal,
197104
- status: mutation.status,
197105
- checkpoint,
197106
- receipt,
197107
- response_status: responseStatus,
197108
- failure_code: mutation.failure_code,
197109
- lease,
197110
- completed_at: mutation.completed_at,
197111
- created_at: mutation.created_at,
197112
- updated_at: mutation.updated_at
197113
- };
197114
- }
197115
- function mutationStatusPath(ref, mutationId) {
197116
- if (!isMutationId(mutationId))
197117
- throw new Error("'mutation_id' must be a UUIDv4");
197118
- return `/v1/projects/${projectRefPathSegment(ref, "Mutations")}/mutations/${encodeURIComponent(mutationId)}`;
197119
- }
197120
- function mutationStatusResponse(ref, mutationId, payload) {
197121
- const response = exactRecord(payload, MUTATION_RESPONSE_KEYS);
197122
- const mutation = safeMutationStatus(response?.mutation);
197123
- return response?.project_ref === ref && mutation?.project_ref === ref && mutation.mutation_id === mutationId ? mutation : null;
197124
- }
197125
- async function fetchMutationStatus(http, ref, mutationId) {
197126
- const response = await http.get(mutationStatusPath(ref, mutationId), {
197127
- maxResponseBytes: MAX_STATUS_RESPONSE_BYTES
197128
- });
197129
- if (!response.ok) {
197130
- return { kind: "unavailable", httpStatus: response.transportError ? null : response.status };
197131
- }
197132
- const mutation = mutationStatusResponse(ref, mutationId, response.data);
197133
- return mutation ? { kind: "available", mutation } : { kind: "invalid" };
197134
- }
197135
-
197136
197652
  // src/shared/tools/mutation-tools.ts
197137
197653
  function requiredText3(args, name) {
197138
197654
  const candidate = args[name];
@@ -197171,7 +197687,7 @@ var MUTATION_TOOL_SCHEMA = {
197171
197687
  };
197172
197688
 
197173
197689
  // src/shared/tools/release-tools.ts
197174
- import { createHash as createHash7 } from "node:crypto";
197690
+ import { createHash as createHash8 } from "node:crypto";
197175
197691
  import { existsSync as existsSync14, mkdirSync as mkdirSync4, readdirSync as readdirSync5, readFileSync as readFileSync11, writeFileSync as writeFileSync3 } from "node:fs";
197176
197692
  import { dirname as dirname11, join as join18, relative as relative10, resolve as resolve23 } from "node:path";
197177
197693
  var SAFE_PROJECT_REF = /^[A-Za-z0-9_-]{1,64}$/;
@@ -197413,7 +197929,7 @@ function genericScopedReleaseScopeJson(scope) {
197413
197929
  return JSON.stringify(canonicalValue(scope));
197414
197930
  }
197415
197931
  function genericScopedReleaseScopeSha256(scope) {
197416
- return createHash7("sha256").update(genericScopedReleaseScopeJson(scope)).digest("hex");
197932
+ return createHash8("sha256").update(genericScopedReleaseScopeJson(scope)).digest("hex");
197417
197933
  }
197418
197934
  function runGitRevParse(cwd, args) {
197419
197935
  const res = Bun.spawnSync(["git", ...args], { cwd });
@@ -197919,7 +198435,7 @@ function registerReleaseTools(server2, http, options = {}) {
197919
198435
 
197920
198436
  // src/shared/tools/deploy-tools.ts
197921
198437
  import { access, lstat as lstat5, mkdtemp as mkdtemp2, opendir, readFile as readFile7, rm as rm3, writeFile as writeFile7 } from "node:fs/promises";
197922
- import { createHash as createHash8 } from "node:crypto";
198438
+ import { createHash as createHash9 } from "node:crypto";
197923
198439
  import { tmpdir as tmpdir3 } from "node:os";
197924
198440
  import { basename as basename10, dirname as dirname12, join as join19, relative as relative11, resolve as resolve24, sep as sep5 } from "node:path";
197925
198441
 
@@ -198854,7 +199370,7 @@ async function createFrontendArchive(outputDirectory) {
198854
199370
  try {
198855
199371
  const archiveBytes = zipSync(collected.files, { level: 6, mtime: FIXED_ZIP_MTIME });
198856
199372
  await writeFile7(archivePath, archiveBytes);
198857
- const sha256 = createHash8("sha256").update(archiveBytes).digest("hex");
199373
+ const sha256 = createHash9("sha256").update(archiveBytes).digest("hex");
198858
199374
  return {
198859
199375
  archivePath,
198860
199376
  sha256,
@@ -199077,7 +199593,7 @@ function registerDeployTools(server2, http, options = {}) {
199077
199593
  import { existsSync as existsSync15 } from "node:fs";
199078
199594
  import { readFile as readFile8 } from "node:fs/promises";
199079
199595
  import { readdir as readdir8 } from "node:fs/promises";
199080
- import { createHash as createHash9 } from "node:crypto";
199596
+ import { createHash as createHash10 } from "node:crypto";
199081
199597
  import { join as join20, resolve as resolve25 } from "node:path";
199082
199598
  import { compileProject as compileProject2 } from "@supacloud/compiler";
199083
199599
  var SAFE_TOKEN = /^[A-Za-z0-9._:@/+,-]+$/;
@@ -199176,7 +199692,7 @@ async function sourceFingerprint(root) {
199176
199692
  }
199177
199693
  };
199178
199694
  await visit(root);
199179
- const hash = createHash9("sha256");
199695
+ const hash = createHash10("sha256");
199180
199696
  for (const file of files) {
199181
199697
  hash.update(file.slice(root.length).replace(/\\/g, "/"));
199182
199698
  hash.update(await readFile8(file));
@@ -199345,7 +199861,7 @@ function registerRemoteDevTools(server2, options = {}) {
199345
199861
  // package.json
199346
199862
  var package_default = {
199347
199863
  name: "@supacloud/cli",
199348
- version: "0.81.0",
199864
+ version: "0.82.0",
199349
199865
  description: "Project-scoped CLI for SupaCloud users",
199350
199866
  type: "module",
199351
199867
  main: "./dist/index.js",
@@ -199670,12 +200186,19 @@ EXAMPLES
199670
200186
  ${preferredCommand} app diff --ref abc123 --id orders --environment_id test --release_id <sha256> --configuration_id <uuid>
199671
200187
  ${preferredCommand} app deploy --ref abc123 --id orders --environment_id test --release_id <sha256> --configuration_id <uuid>
199672
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>
199673
200190
  ${preferredCommand} --env production app promote-plan --id orders --environment_id production --source_ref staging --source_environment_id staging --source_release_id <sha256>
199674
200191
  ${preferredCommand} app preview-plan --ref abc123 --id orders --environment_id test --release_id <sha256> --branch_ref preview-orders
199675
200192
  ${preferredCommand} app preview --ref abc123 --id orders --environment_id test --release_id <sha256> --configuration_id <uuid> --ttl_seconds 3600 --wait
199676
200193
  ${preferredCommand} app previews --ref abc123 --id orders --environment_id test
199677
200194
  ${preferredCommand} app preview-status --ref abc123 --id orders --environment_id test --preview_id <uuid> --wait --timeout_seconds 300
199678
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>
199679
200202
  ${preferredCommand} app logs --ref abc123 --id orders --environment_id test
199680
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>
199681
200204
  ${preferredCommand} app check --root . --strict