@supacloud/cli 0.77.0 → 0.79.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
@@ -179093,8 +179093,19 @@ function resolveSupaCloudContext(env = process.env, cwd = process.cwd(), selecti
179093
179093
  var ACTION_POLICY = {
179094
179094
  deploy: { write: ["deploy"] },
179095
179095
  applications: {
179096
- read: ["list_releases", "get_release", "get_runtime", "get_deployment_evidence", "get_configuration", "logs"],
179097
- write: ["upload_release", "put_configuration", "activate_release", "reconcile_activation", "retire_activation"]
179096
+ read: ["list_releases", "get_release", "get_runtime", "get_deployment_evidence", "get_configuration", "get_rollback_snapshot", "logs", "get_preview_plan"],
179097
+ write: [
179098
+ "upload_release",
179099
+ "put_configuration",
179100
+ "activate_release",
179101
+ "rollback_release",
179102
+ "reconcile_activation",
179103
+ "retire_activation",
179104
+ "create_preview",
179105
+ "list_previews",
179106
+ "get_preview",
179107
+ "cleanup_preview"
179108
+ ]
179098
179109
  },
179099
179110
  project: {
179100
179111
  read: ["get", "endpoints", "health", "logs", "api_keys", "settings", "tasks", "task_detail", "task_stats", "dlq", "background_settings"],
@@ -179189,8 +179200,8 @@ var ACTION_POLICY = {
179189
179200
  ai: { local: ["show_skill", "install_skill"] },
179190
179201
  app: {
179191
179202
  local: ["init", "generate", "dev", "watch", "verify-plan", "compile", "check", "graph", "explain", "export-tools", "context", "doctor", "fix", "plan", "build"],
179192
- read: ["status", "logs"],
179193
- write: ["upload", "configure", "deploy", "rollback", "reconcile", "retire"]
179203
+ read: ["status", "logs", "rollback-plan", "preview-plan"],
179204
+ write: ["upload", "configure", "deploy", "rollback", "reconcile", "retire", "preview", "previews", "preview-status", "preview-cleanup"]
179194
179205
  },
179195
179206
  db: {
179196
179207
  local: ["context", "lint", "explain", "diff", "role_sql"],
@@ -179528,19 +179539,34 @@ class HttpTransport {
179528
179539
  }
179529
179540
  }
179530
179541
  async get(path, options = {}) {
179542
+ const timeoutMs = validatedPostTimeout(options);
179531
179543
  const maxResponseBytes = validatedGetResponseLimit(options);
179532
179544
  const maxJsonBytes = validatedJsonResponseLimit(options.maxJsonBytes);
179533
179545
  const responseTimeoutMs = validatedResponseTimeout(options.responseTimeoutMs);
179546
+ const totalTimeoutMs = validatedResponseTimeout(options.totalTimeoutMs);
179534
179547
  if (maxResponseBytes !== undefined && maxJsonBytes !== undefined) {
179535
179548
  throw new RangeError("HTTP response limit options are mutually exclusive");
179536
179549
  }
179550
+ if (totalTimeoutMs !== undefined && (options.retry !== false || maxJsonBytes === undefined)) {
179551
+ throw new RangeError("HTTP total timeout requires bounded JSON and retry: false");
179552
+ }
179553
+ const deadline = totalTimeoutMs === undefined ? undefined : performance.now() + totalTimeoutMs;
179537
179554
  try {
179538
- const res = await fetchWithRetry(`${this.baseUrl}${path}`, {
179555
+ const request = options.retry === false ? fetchWithTimeout : fetchWithRetry;
179556
+ const res = await request(`${this.baseUrl}${path}`, {
179539
179557
  method: "GET",
179540
179558
  headers: this.headers()
179541
- }, DEFAULT_TIMEOUT, this.insecureTls);
179559
+ }, totalTimeoutMs === undefined ? timeoutMs : Math.min(timeoutMs, totalTimeoutMs), this.insecureTls);
179542
179560
  if (maxJsonBytes !== undefined) {
179543
- const data = await boundedResponseJson(res, maxJsonBytes, responseTimeoutMs);
179561
+ const remaining = deadline === undefined ? undefined : Math.floor(deadline - performance.now());
179562
+ if (remaining !== undefined && remaining <= 0) {
179563
+ res.body?.cancel().catch(() => {
179564
+ return;
179565
+ });
179566
+ return responseReadFailure(res.status);
179567
+ }
179568
+ const bodyTimeout = remaining === undefined ? responseTimeoutMs : Math.min(responseTimeoutMs ?? remaining, remaining);
179569
+ const data = await boundedResponseJson(res, maxJsonBytes, bodyTimeout);
179544
179570
  return data === null ? responseReadFailure(res.status) : { ok: res.ok, status: res.status, data };
179545
179571
  }
179546
179572
  const data = maxResponseBytes === undefined ? await res.json().catch(() => null) : await boundedResponseJson(res, maxResponseBytes);
@@ -180255,6 +180281,19 @@ var ApplicationActivationRetirementResultSchema = _Object_({
180255
180281
  activation_id: ApplicationActivationIdSchema,
180256
180282
  retired_at: String2()
180257
180283
  }, { additionalProperties: false });
180284
+ var RollbackActivationSchema = _Object_({
180285
+ release_id: ApplicationReleaseIdSchema,
180286
+ configuration_id: ApplicationConfigurationIdSchema,
180287
+ activation_id: ApplicationActivationIdSchema
180288
+ }, { additionalProperties: false });
180289
+ var ApplicationRollbackSnapshotSchema = _Object_({
180290
+ schema: Literal("supacloud.application-rollback-snapshot.v1"),
180291
+ project_ref: String2({ pattern: "^[a-z0-9-]{1,20}$" }),
180292
+ application_id: ApplicationIdSchema,
180293
+ environment_id: ApplicationIdSchema,
180294
+ active: Union([RollbackActivationSchema, Null()]),
180295
+ previous: Union([RollbackActivationSchema, Null()])
180296
+ }, { additionalProperties: false });
180258
180297
  // node_modules/@supacloud/delivery/src/deployment-evidence.ts
180259
180298
  var closed3 = { additionalProperties: false };
180260
180299
  var sha256 = String2({ pattern: "^[a-f0-9]{64}$" });
@@ -187227,6 +187266,333 @@ var ApplicationActivationIdSchema2 = ApplicationActivationIdSchema;
187227
187266
  var ApplicationActivationWriteSchema2 = ApplicationActivationWriteSchema;
187228
187267
  var ApplicationActivationResultSchema2 = ApplicationActivationResultSchema;
187229
187268
  var ApplicationActivationRetirementResultSchema2 = ApplicationActivationRetirementResultSchema;
187269
+ var ApplicationRollbackSnapshotSchema2 = ApplicationRollbackSnapshotSchema;
187270
+
187271
+ // src/shared/tools/application-preview-tools.ts
187272
+ var APPLICATION_PREVIEW_ACTIONS = [
187273
+ "get_preview_plan",
187274
+ "create_preview",
187275
+ "list_previews",
187276
+ "get_preview",
187277
+ "cleanup_preview"
187278
+ ];
187279
+ var previewIdSchema = String2({ pattern: "^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$" });
187280
+ var branchRefSchema = String2({ pattern: "^[a-z0-9-]{1,20}$" });
187281
+ var ttlSecondsSchema = Integer({ minimum: 300, maximum: 604800 });
187282
+ var waitSecondsSchema = Integer({ minimum: 1, maximum: 3600 });
187283
+ var timestampSchema = String2({
187284
+ pattern: "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z$",
187285
+ maxLength: 24
187286
+ });
187287
+ var APPLICATION_PREVIEW_FIELDS = {
187288
+ preview_id: optional(previewIdSchema, "[get_preview/cleanup_preview] Preview receipt ID"),
187289
+ branch_ref: optional(branchRefSchema, "[get_preview_plan] Proposed branch ref; create assigns its own"),
187290
+ branch_name: optional(String2({ minLength: 1, maxLength: 80 }), "[create_preview] Branch display name"),
187291
+ data_mode: optional(stringEnum(["schema_only", "full_clone"]), "[get_preview_plan/create_preview] Default schema_only; full_clone copies rows"),
187292
+ ttl_seconds: optional(ttlSecondsSchema, "[get_preview_plan/create_preview] Lifetime from 300 to 604800 seconds; creation defaults to the platform TTL"),
187293
+ wait: optional(Boolean2(), "[create_preview/get_preview] Wait for verified readiness of this preview only"),
187294
+ timeout_seconds: optional(waitSecondsSchema, "[create_preview/get_preview] Observation budget with wait, from 1 to 3600 seconds (default 300)")
187295
+ };
187296
+ var strict = { additionalProperties: false };
187297
+ var phase = stringEnum(["pending", "ready", "failed", "cleaned"]);
187298
+ var check5 = String2({ pattern: "^[a-z][a-z0-9_]{0,63}$" });
187299
+ var checks = _Array_(check5, { maxItems: 64, uniqueItems: true });
187300
+ var revision = Union([ApplicationConfigurationIdSchema2, Null()]);
187301
+ var receiptSchema = _Object_({
187302
+ schema: Literal("supacloud.application-preview.v1"),
187303
+ preview_id: previewIdSchema,
187304
+ project_ref: branchRefSchema,
187305
+ application_id: ApplicationIdSchema2,
187306
+ environment_id: ApplicationIdSchema2,
187307
+ release_id: ApplicationReleaseIdSchema2,
187308
+ expires_at: Union([timestampSchema, Null()]),
187309
+ status: stringEnum(["planned", "provisioning", "ready", "failed", "cleaned"]),
187310
+ resources: _Object_({
187311
+ build_artifact: _Object_({ status: phase, release_id: ApplicationReleaseIdSchema2 }, strict),
187312
+ database_branch: _Object_({ status: phase, branch_ref: branchRefSchema, data_mode: stringEnum(["schema_only", "full_clone"]) }, strict),
187313
+ queue_namespace: _Object_({ status: phase, namespace: String2({ minLength: 1, maxLength: 128 }) }, strict),
187314
+ storage_namespace: _Object_({ status: phase, namespace: branchRefSchema }, strict),
187315
+ test_secret: _Object_({ status: phase, name: String2({ pattern: "^[A-Z0-9_-]{1,128}$" }), value_issued: Literal(false) }, strict),
187316
+ configuration_revision: _Object_({ status: phase, configuration_id: revision }, strict),
187317
+ application_activation: _Object_({ status: phase, activation_id: Union([previewIdSchema, Null()]) }, strict),
187318
+ smoke_test: _Object_({ status: phase, checks, passed: checks, failed: checks }, strict)
187319
+ }, strict),
187320
+ cleanup: _Object_({
187321
+ required: Boolean2(),
187322
+ completed: Boolean2(),
187323
+ error: Union([String2({ pattern: "^[A-Z0-9_]{1,128}$" }), Null()])
187324
+ }, strict),
187325
+ branch_name: Optional(String2({ maxLength: 80 })),
187326
+ queue_name: Optional(String2({ maxLength: 128 })),
187327
+ test_secret_name: Optional(String2({ pattern: "^[A-Z0-9_]{1,128}$" })),
187328
+ source_configuration_id: Optional(revision),
187329
+ created_at: Optional(timestampSchema),
187330
+ updated_at: Optional(timestampSchema)
187331
+ }, strict);
187332
+ function required5(args, field) {
187333
+ const value = args[field];
187334
+ if (typeof value !== "string" || !value.trim())
187335
+ throw new Error(`'${field}' is required`);
187336
+ return value.trim();
187337
+ }
187338
+ var mandatoryChecks = [
187339
+ "release_artifact",
187340
+ "database_branch",
187341
+ "queue_namespace",
187342
+ "storage_namespace",
187343
+ "test_secret",
187344
+ "configuration_revision",
187345
+ "application_activation",
187346
+ "application_readiness",
187347
+ "tenant_runtime"
187348
+ ];
187349
+ var actionFields = {
187350
+ get_preview_plan: ["release_id", "branch_ref", "data_mode", "configuration_id", "ttl_seconds"],
187351
+ create_preview: ["release_id", "configuration_id", "data_mode", "branch_name", "ttl_seconds", "wait", "timeout_seconds"],
187352
+ list_previews: [],
187353
+ get_preview: ["preview_id", "wait", "timeout_seconds"],
187354
+ cleanup_preview: ["preview_id"]
187355
+ };
187356
+ function timestamp(value) {
187357
+ return Number.isFinite(Date.parse(value)) && new Date(value).toISOString() === value;
187358
+ }
187359
+ function receipt(value, identity) {
187360
+ 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) {
187361
+ throw new Error("Invalid preview receipt");
187362
+ }
187363
+ if (value.expires_at !== null && !timestamp(value.expires_at) || value.created_at !== undefined && !timestamp(value.created_at) || value.updated_at !== undefined && !timestamp(value.updated_at) || value.expires_at !== null && value.created_at !== undefined && !Check2(ttlSecondsSchema, (Date.parse(value.expires_at) - Date.parse(value.created_at)) / 1000)) {
187364
+ throw new Error("Invalid preview expiry");
187365
+ }
187366
+ if (value.status === "ready") {
187367
+ if (value.resources.smoke_test.status !== "ready" || value.resources.smoke_test.failed.length > 0 || mandatoryChecks.some((check) => !value.resources.smoke_test.checks.includes(check) || !value.resources.smoke_test.passed.includes(check)) || value.resources.smoke_test.passed.some((check) => !value.resources.smoke_test.checks.includes(check)) || Object.values(value.resources).some((resource) => resource.status !== "ready") || !value.resources.configuration_revision.configuration_id || !value.resources.application_activation.activation_id) {
187368
+ throw new Error("Incomplete preview readiness");
187369
+ }
187370
+ }
187371
+ if (value.status === "cleaned" && (!value.cleanup.completed || value.cleanup.required || [
187372
+ value.resources.database_branch,
187373
+ value.resources.queue_namespace,
187374
+ value.resources.storage_namespace,
187375
+ value.resources.test_secret
187376
+ ].some((resource) => resource.status !== "cleaned"))) {
187377
+ throw new Error("Incomplete preview cleanup");
187378
+ }
187379
+ const {
187380
+ branch_name: _branchName,
187381
+ queue_name: _queueName,
187382
+ test_secret_name: _secretName,
187383
+ source_configuration_id: _sourceConfiguration,
187384
+ created_at: _createdAt,
187385
+ updated_at: _updatedAt,
187386
+ ...publicReceipt
187387
+ } = value;
187388
+ return publicReceipt;
187389
+ }
187390
+ async function sourceManifest(http, path, ref, id, releaseId) {
187391
+ const result = await http.get(`${path}/releases/${releaseId}`, { maxJsonBytes: 196608, responseTimeoutMs: 30000 });
187392
+ if (!result.ok)
187393
+ return { result };
187394
+ try {
187395
+ const data = result.data;
187396
+ if (!data || typeof data !== "object" || !("project_ref" in data) || data.project_ref !== ref || !("application_id" in data) || data.application_id !== id || !("release" in data))
187397
+ throw new Error;
187398
+ const release = parseApplicationReleaseRecord(data.release);
187399
+ if (release.project_ref !== ref || release.application_id !== id || release.release_id !== releaseId)
187400
+ throw new Error;
187401
+ return { result, manifest: release.manifest_sha256 };
187402
+ } catch {
187403
+ return { result };
187404
+ }
187405
+ }
187406
+ async function waitForPreview(http, path, operation, identity, initial, timeoutSeconds, startedAt = performance.now()) {
187407
+ const deadline = startedAt + timeoutSeconds * 1000;
187408
+ let current = initial;
187409
+ const state = () => ({
187410
+ ...identity,
187411
+ preview_id: initial.preview_id,
187412
+ preview: current,
187413
+ reconciliation: {
187414
+ action: "get_preview",
187415
+ ref: identity.project_ref,
187416
+ id: identity.application_id,
187417
+ environment_id: identity.environment_id,
187418
+ preview_id: initial.preview_id,
187419
+ wait: true,
187420
+ timeout_seconds: timeoutSeconds
187421
+ }
187422
+ });
187423
+ while (true) {
187424
+ if (current.status === "ready")
187425
+ return releaseControlSuccess(operation, {
187426
+ ...identity,
187427
+ preview: current,
187428
+ waiting: { status: "ready", timeout_seconds: timeoutSeconds }
187429
+ });
187430
+ if (current.status !== "provisioning")
187431
+ return releaseControlFailure(operation, "MUTATION_NOT_SUCCEEDED", null, {
187432
+ ...state(),
187433
+ waiting: { status: "stopped", timeout_seconds: timeoutSeconds }
187434
+ });
187435
+ const remaining = Math.floor(deadline - performance.now());
187436
+ if (remaining < 2)
187437
+ return releaseControlFailure(operation, "MUTATION_NOT_SUCCEEDED", null, {
187438
+ ...state(),
187439
+ waiting: { status: "timed_out", timeout_seconds: timeoutSeconds }
187440
+ });
187441
+ const requestBudget = Math.min(120000, remaining);
187442
+ const result = await http.get(`${path}/previews/${initial.preview_id}`, {
187443
+ retry: false,
187444
+ timeoutMs: requestBudget,
187445
+ responseTimeoutMs: requestBudget,
187446
+ totalTimeoutMs: requestBudget,
187447
+ maxJsonBytes: 1048576
187448
+ });
187449
+ if (!result.ok)
187450
+ return releaseControlFailure(operation, "HTTP_ERROR", result.transportError ? null : result.status, {
187451
+ ...state(),
187452
+ waiting: { status: "observation_failed", timeout_seconds: timeoutSeconds }
187453
+ });
187454
+ try {
187455
+ const next = receipt(result.data, {
187456
+ ref: identity.project_ref,
187457
+ id: identity.application_id,
187458
+ environment: identity.environment_id,
187459
+ previewId: initial.preview_id
187460
+ });
187461
+ if (next.status === "planned" || next.release_id !== initial.release_id || next.expires_at !== initial.expires_at || next.resources.database_branch.branch_ref !== initial.resources.database_branch.branch_ref || next.resources.database_branch.data_mode !== initial.resources.database_branch.data_mode || current.resources.application_activation.activation_id !== null && next.resources.application_activation.activation_id !== current.resources.application_activation.activation_id || current.resources.configuration_revision.configuration_id !== null && next.resources.configuration_revision.configuration_id !== current.resources.configuration_revision.configuration_id) {
187462
+ throw new Error("Preview identity changed during observation");
187463
+ }
187464
+ current = next;
187465
+ } catch {
187466
+ return releaseControlFailure(operation, "INVALID_RESPONSE", result.status, {
187467
+ ...state(),
187468
+ waiting: { status: "observation_failed", timeout_seconds: timeoutSeconds }
187469
+ });
187470
+ }
187471
+ if (current.status === "provisioning") {
187472
+ await new Promise((resolve) => setTimeout(resolve, Math.max(0, Math.min(1000, deadline - performance.now()))));
187473
+ }
187474
+ }
187475
+ }
187476
+ async function applicationPreviewAction(http, args, project) {
187477
+ const action = required5(args, "action"), ref = required5(args, "ref"), id = required5(args, "id");
187478
+ const environment = required5(args, "environment_id");
187479
+ if (!Check2(ApplicationIdSchema2, environment))
187480
+ throw new Error("Invalid environment ID");
187481
+ const fields = new Set(["action", "ref", "id", "environment_id", ...actionFields[action] ?? []]);
187482
+ if (Object.entries(args).some(([key, value]) => value !== undefined && !fields.has(key))) {
187483
+ throw new Error("Invalid option for preview action");
187484
+ }
187485
+ if (args["ttl_seconds"] !== undefined && !Check2(ttlSecondsSchema, args["ttl_seconds"])) {
187486
+ throw new Error("ttl_seconds must be an integer from 300 to 604800");
187487
+ }
187488
+ if (args["wait"] !== undefined && typeof args["wait"] !== "boolean")
187489
+ throw new Error("wait must be boolean");
187490
+ if (args["timeout_seconds"] !== undefined && (args["wait"] !== true || !Check2(waitSecondsSchema, args["timeout_seconds"]))) {
187491
+ throw new Error("timeout_seconds requires wait and must be an integer from 1 to 3600");
187492
+ }
187493
+ const timeoutSeconds = args["wait"] === true ? args["timeout_seconds"] ?? 300 : undefined;
187494
+ const startedAt = action === "get_preview" && timeoutSeconds !== undefined ? performance.now() : undefined;
187495
+ const operation = `applications.${action}`;
187496
+ const identity = { project_ref: ref, application_id: id, environment_id: environment };
187497
+ const applicationPath = `/v1/projects/${project}/applications/${encodeURIComponent(id)}`;
187498
+ const path = `${applicationPath}/environments/${encodeURIComponent(environment)}`;
187499
+ const previewId = action === "get_preview" || action === "cleanup_preview" ? required5(args, "preview_id") : undefined;
187500
+ const mutation = action === "create_preview" || action === "cleanup_preview";
187501
+ const requestBudget = startedAt === undefined ? 120000 : Math.min(120000, timeoutSeconds * 1000);
187502
+ const options = {
187503
+ timeoutMs: requestBudget,
187504
+ maxJsonBytes: 1048576,
187505
+ responseTimeoutMs: requestBudget,
187506
+ retry: false,
187507
+ ...startedAt === undefined ? {} : { totalTimeoutMs: requestBudget }
187508
+ };
187509
+ let sourceHash;
187510
+ let releaseId;
187511
+ let result;
187512
+ if (action === "get_preview_plan" || action === "create_preview") {
187513
+ releaseId = required5(args, "release_id");
187514
+ if (!Check2(ApplicationReleaseIdSchema2, releaseId))
187515
+ throw new Error("Invalid release ID");
187516
+ if (action === "get_preview_plan") {
187517
+ const query = new URLSearchParams({ release_id: releaseId, branch_ref: required5(args, "branch_ref") });
187518
+ if (typeof args["data_mode"] === "string")
187519
+ query.set("data_mode", args["data_mode"]);
187520
+ if (typeof args["configuration_id"] === "string")
187521
+ query.set("configuration_id", args["configuration_id"]);
187522
+ if (args["ttl_seconds"] !== undefined)
187523
+ query.set("ttl_seconds", String(args["ttl_seconds"]));
187524
+ result = await http.get(`${path}/preview-plan?${query}`, options);
187525
+ } else {
187526
+ const configurationId = required5(args, "configuration_id");
187527
+ if (!Check2(ApplicationConfigurationIdSchema2, configurationId))
187528
+ throw new Error("Invalid configuration ID");
187529
+ const source = await sourceManifest(http, applicationPath, ref, id, releaseId);
187530
+ if (!source.manifest)
187531
+ return releaseControlFailure(operation, source.result.ok ? "INVALID_RESPONSE" : "HTTP_ERROR", source.result.status, { ...identity, release_id: releaseId, configuration_id: configurationId });
187532
+ sourceHash = source.manifest;
187533
+ result = await http.post(`${path}/previews`, {
187534
+ release_id: releaseId,
187535
+ configuration_id: configurationId,
187536
+ data_mode: args["data_mode"] ?? "schema_only",
187537
+ ...args["branch_name"] === undefined ? {} : { branch_name: args["branch_name"] },
187538
+ ...args["ttl_seconds"] === undefined ? {} : { ttl_seconds: args["ttl_seconds"] }
187539
+ }, options);
187540
+ }
187541
+ } else if (action === "list_previews") {
187542
+ result = await http.get(`${path}/previews`, options);
187543
+ } else if (previewId) {
187544
+ if (!Check2(previewIdSchema, previewId))
187545
+ throw new Error("Invalid preview ID");
187546
+ result = action === "cleanup_preview" ? await http.deleteReleaseMutation(`${path}/previews/${previewId}`) : await http.get(`${path}/previews/${previewId}`, options);
187547
+ } else {
187548
+ throw new Error("Unknown preview action");
187549
+ }
187550
+ const state = {
187551
+ ...identity,
187552
+ ...previewId ? { preview_id: previewId } : {},
187553
+ ...releaseId ? { source_release_id: releaseId } : {},
187554
+ reconciliation: previewId ? { action: "get_preview", ref, id, environment_id: environment, preview_id: previewId } : { action: "list_previews", ref, id, environment_id: environment }
187555
+ };
187556
+ if (!result.ok)
187557
+ return mutation ? releaseControlMutationFailure(operation, result, state) : releaseControlFailure(operation, "HTTP_ERROR", result.status, state);
187558
+ try {
187559
+ if (action === "list_previews") {
187560
+ const inventorySchema = _Object_({
187561
+ ..._Object_({ project_ref: branchRefSchema, application_id: ApplicationIdSchema2, environment_id: ApplicationIdSchema2 }).properties,
187562
+ previews: _Array_(receiptSchema, { maxItems: 1000 })
187563
+ }, strict);
187564
+ if (!Check2(inventorySchema, result.data) || result.data.project_ref !== ref || result.data.application_id !== id || result.data.environment_id !== environment)
187565
+ throw new Error;
187566
+ const previews = result.data.previews.map((value) => receipt(value, { ref, id, environment }));
187567
+ if (new Set(previews.map((value) => value.preview_id)).size !== previews.length)
187568
+ throw new Error;
187569
+ return releaseControlSuccess(operation, { ...identity, previews });
187570
+ }
187571
+ const preview = receipt(result.data, {
187572
+ ref,
187573
+ id,
187574
+ environment,
187575
+ ...previewId === undefined ? {} : { previewId }
187576
+ });
187577
+ if (action === "get_preview_plan") {
187578
+ if (preview.status !== "planned" || preview.release_id !== releaseId || preview.resources.database_branch.branch_ref !== args["branch_ref"] || preview.resources.database_branch.data_mode !== (args["data_mode"] ?? "schema_only") || (args["ttl_seconds"] === undefined ? preview.expires_at !== null : preview.expires_at === null))
187579
+ throw new Error;
187580
+ } else {
187581
+ if (preview.status === "planned" || preview.resources.database_branch.branch_ref !== `pv${preview.preview_id.replaceAll("-", "").slice(0, 18)}`)
187582
+ throw new Error;
187583
+ if (action === "create_preview" && (preview.release_id !== applicationReleaseId(preview.resources.database_branch.branch_ref, id, sourceHash) || preview.resources.database_branch.data_mode !== (args["data_mode"] ?? "schema_only") || !Check2(receiptSchema, result.data) || result.data.source_configuration_id !== args["configuration_id"] || preview.expires_at === null || result.data.created_at === undefined || args["ttl_seconds"] !== undefined && Date.parse(preview.expires_at) - Date.parse(result.data.created_at) !== args["ttl_seconds"] * 1000))
187584
+ throw new Error;
187585
+ }
187586
+ if (preview.status === "failed" || action === "cleanup_preview" && preview.status !== "cleaned") {
187587
+ return releaseControlFailure(operation, "MUTATION_NOT_SUCCEEDED", result.status, { ...identity, preview });
187588
+ }
187589
+ if (timeoutSeconds !== undefined)
187590
+ return await waitForPreview(http, path, operation, identity, preview, timeoutSeconds, startedAt);
187591
+ return releaseControlSuccess(operation, { ...identity, preview });
187592
+ } catch {
187593
+ return releaseControlFailure(operation, mutation ? "OUTCOME_UNKNOWN" : "INVALID_RESPONSE", result.status, state);
187594
+ }
187595
+ }
187230
187596
 
187231
187597
  // src/shared/tools/application-tools.ts
187232
187598
  var APPLICATION_TOOL_SCHEMA = {
@@ -187239,26 +187605,30 @@ var APPLICATION_TOOL_SCHEMA = {
187239
187605
  "get_configuration",
187240
187606
  "put_configuration",
187241
187607
  "activate_release",
187608
+ "rollback_release",
187609
+ "get_rollback_snapshot",
187242
187610
  "reconcile_activation",
187243
187611
  "retire_activation",
187612
+ ...APPLICATION_PREVIEW_ACTIONS,
187244
187613
  "logs"
187245
187614
  ]), "Action"),
187246
187615
  ref: withDescription(String2(), "Project ref"),
187247
187616
  id: withDescription(ApplicationIdSchema2, "Application ID"),
187248
- environment_id: optional(withDescription(ApplicationIdSchema2, "[get_runtime/get_configuration/put_configuration] Environment ID")),
187249
- configuration_id: optional(ApplicationConfigurationIdSchema2, "[get_configuration/activate_release] Immutable revision; required for activation"),
187250
- activation_id: optional(ApplicationActivationIdSchema2, "[activate_release/reconcile_activation/retire_activation] Explicit stable activation ID"),
187251
- expected_activation_id: optional(Union([ApplicationActivationIdSchema2, Null(), Literal("absent")]), "[activate_release] Current activation ID, or absent for first activation"),
187617
+ environment_id: optional(withDescription(ApplicationIdSchema2, "[get_runtime/get_configuration/put_configuration/get_rollback_snapshot/rollback_release] Environment ID")),
187618
+ configuration_id: optional(ApplicationConfigurationIdSchema2, "[get_configuration/activate_release/rollback_release] Immutable revision; required for explicit activation"),
187619
+ activation_id: optional(ApplicationActivationIdSchema2, "[activate_release/reconcile_activation/retire_activation/rollback_release] Explicit stable activation ID; generated for rollback if omitted"),
187620
+ expected_activation_id: optional(Union([ApplicationActivationIdSchema2, Null(), Literal("absent")]), "[activate_release/rollback_release] Current activation ID, or absent for first activation; selected by platform for default rollback"),
187252
187621
  configuration_path: optional(String2(), "[put_configuration] Local configuration write JSON including revision and expected revision"),
187253
187622
  manifest_path: optional(String2(), "[upload_release] Local delivery.manifest.json"),
187254
- release_id: optional(ApplicationReleaseIdSchema2, "[get_release/activate_release/reconcile_activation] Immutable application release ID"),
187623
+ release_id: optional(ApplicationReleaseIdSchema2, "[get_release/activate_release/reconcile_activation/rollback_release] Immutable application release ID; platform selects previous for default rollback"),
187255
187624
  cursor: optional(ApplicationReleaseIdSchema2, "[list_releases] Last release ID"),
187256
187625
  limit: optional(Integer({ minimum: 1, maximum: 100 }), "[list_releases] Page size, default 50"),
187257
187626
  offset: optional(Integer({ minimum: 0, maximum: 1e6 }), "[logs] Result offset"),
187258
187627
  service: optional(String2(), "[logs] Application target/service filter"),
187259
187628
  search: optional(String2(), "[logs] Full-text log filter"),
187260
187629
  start: optional(String2(), "[logs] ISO start timestamp"),
187261
- end: optional(String2(), "[logs] ISO end timestamp")
187630
+ end: optional(String2(), "[logs] ISO end timestamp"),
187631
+ ...APPLICATION_PREVIEW_FIELDS
187262
187632
  };
187263
187633
  var responseSchema = _Object_({
187264
187634
  project_ref: String2(),
@@ -187329,7 +187699,7 @@ async function configurationAction(http, args, project) {
187329
187699
  return releaseControlFailure(operation, action === "put_configuration" ? "OUTCOME_UNKNOWN" : "INVALID_RESPONSE", result.status, identity);
187330
187700
  }
187331
187701
  }
187332
- async function activationAction(http, args, project) {
187702
+ async function activationAction(http, args, project, rollback) {
187333
187703
  const action = text(args, "action"), ref = text(args, "ref"), id = text(args, "id");
187334
187704
  const environmentId = text(args, "environment_id"), activationId = text(args, "activation_id");
187335
187705
  const releaseId = args["release_id"] === undefined ? undefined : text(args, "release_id");
@@ -187339,7 +187709,7 @@ async function activationAction(http, args, project) {
187339
187709
  if (!Check2(ApplicationIdSchema2, environmentId) || !Check2(ApplicationActivationIdSchema2, activationId) || action !== "retire_activation" && !Check2(ApplicationReleaseIdSchema2, releaseId)) {
187340
187710
  throw new Error("Invalid application activation identity");
187341
187711
  }
187342
- const operation = `applications.${action}`;
187712
+ const operation = rollback ? "applications.rollback_release" : `applications.${action}`;
187343
187713
  const path = `/v1/projects/${project}/applications/${encodeURIComponent(id)}/environments/${encodeURIComponent(environmentId)}/activations`;
187344
187714
  const identity = {
187345
187715
  project_ref: ref,
@@ -187348,6 +187718,7 @@ async function activationAction(http, args, project) {
187348
187718
  activation_id: activationId,
187349
187719
  ...releaseId ? { release_id: releaseId } : {}
187350
187720
  };
187721
+ const safeState = { ...identity, ...rollback ?? {} };
187351
187722
  let body = {};
187352
187723
  if (action === "activate_release") {
187353
187724
  body = {
@@ -187362,7 +187733,9 @@ async function activationAction(http, args, project) {
187362
187733
  const endpoint = action === "activate_release" ? path : `${path}/${activationId}/${action === "retire_activation" ? "retire" : "reconcile"}`;
187363
187734
  const result = await http.post(endpoint, body, { timeoutMs: 120000, maxJsonBytes: 65536, responseTimeoutMs: 30000 });
187364
187735
  if (!result.ok)
187365
- return releaseControlMutationFailure(operation, result, identity);
187736
+ return releaseControlMutationFailure(operation, result, safeState);
187737
+ if (result.status !== 200)
187738
+ return releaseControlFailure(operation, "OUTCOME_UNKNOWN", result.status, safeState);
187366
187739
  if (action === "retire_activation") {
187367
187740
  if (!Check2(ApplicationActivationRetirementResultSchema2, result.data) || Object.entries(identity).some(([key, value]) => Reflect.get(result.data, key) !== value)) {
187368
187741
  return releaseControlFailure(operation, "OUTCOME_UNKNOWN", result.status, identity);
@@ -187370,9 +187743,60 @@ async function activationAction(http, args, project) {
187370
187743
  return releaseControlSuccess(operation, result.data);
187371
187744
  }
187372
187745
  if (!Check2(ApplicationActivationResultSchema2, result.data) || Object.entries(identity).some(([key, value]) => Reflect.get(result.data, key) !== value) || action === "reconcile_activation" && !result.data.replayed) {
187373
- return releaseControlFailure(operation, "OUTCOME_UNKNOWN", result.status, identity);
187746
+ return releaseControlFailure(operation, "OUTCOME_UNKNOWN", result.status, safeState);
187374
187747
  }
187375
- return releaseControlSuccess(operation, result.data);
187748
+ return releaseControlSuccess(operation, { ...result.data, ...rollback ?? {} });
187749
+ }
187750
+ async function rollbackAction(http, args, project) {
187751
+ const ref = text(args, "ref"), id = text(args, "id"), environmentId = text(args, "environment_id");
187752
+ if (!Check2(ApplicationIdSchema2, environmentId))
187753
+ throw new Error("Invalid environment ID");
187754
+ const operation = `applications.${text(args, "action")}`;
187755
+ const identity = { project_ref: ref, application_id: id, environment_id: environmentId };
187756
+ let activationId = args["activation_id"];
187757
+ if (activationId !== undefined && !Check2(ApplicationActivationIdSchema2, activationId)) {
187758
+ throw new Error("Invalid application activation ID");
187759
+ }
187760
+ let releaseId = args["release_id"], configurationId = args["configuration_id"];
187761
+ let expectedActivationId = args["expected_activation_id"];
187762
+ if (releaseId === undefined || args["action"] === "get_rollback_snapshot") {
187763
+ if (releaseId !== undefined || configurationId !== undefined || expectedActivationId !== undefined) {
187764
+ throw new Error("Default rollback selection cannot mix explicit release/configuration/CAS flags");
187765
+ }
187766
+ const response = await http.get(`/v1/projects/${project}/applications/${encodeURIComponent(id)}/environments/${encodeURIComponent(environmentId)}/rollback-snapshot`, { maxJsonBytes: 65536, responseTimeoutMs: 30000 });
187767
+ if (!response.ok)
187768
+ return releaseControlFailure(operation, "HTTP_ERROR", response.transportError ? null : response.status, identity);
187769
+ const snapshot = response.data;
187770
+ if (response.status !== 200 || !Check2(ApplicationRollbackSnapshotSchema2, snapshot) || snapshot.project_ref !== ref || snapshot.application_id !== id || snapshot.environment_id !== environmentId || snapshot.previous !== null && (snapshot.active === null || snapshot.previous.activation_id === snapshot.active.activation_id)) {
187771
+ return releaseControlFailure(operation, "INVALID_RESPONSE", response.status, identity);
187772
+ }
187773
+ if (args["action"] === "get_rollback_snapshot")
187774
+ return releaseControlSuccess(operation, { ...identity, snapshot });
187775
+ if (!snapshot.active || !snapshot.previous) {
187776
+ return releaseControlFailure(operation, "MUTATION_NOT_SUCCEEDED", response.status, {
187777
+ ...identity,
187778
+ reason: "NO_PREVIOUS_ACTIVATION"
187779
+ });
187780
+ }
187781
+ if (activationId === snapshot.active.activation_id || activationId === snapshot.previous.activation_id) {
187782
+ throw new Error("Rollback requires a new activation ID");
187783
+ }
187784
+ releaseId = snapshot.previous.release_id;
187785
+ configurationId = snapshot.previous.configuration_id;
187786
+ expectedActivationId = snapshot.active.activation_id;
187787
+ }
187788
+ activationId ??= crypto.randomUUID();
187789
+ return activationAction(http, {
187790
+ ...args,
187791
+ action: "activate_release",
187792
+ activation_id: activationId,
187793
+ release_id: releaseId,
187794
+ configuration_id: configurationId,
187795
+ expected_activation_id: expectedActivationId
187796
+ }, project, {
187797
+ configuration_id: configurationId,
187798
+ expected_activation_id: expectedActivationId === "absent" ? null : expectedActivationId
187799
+ });
187376
187800
  }
187377
187801
  function registerApplicationTools(server2, http) {
187378
187802
  registerTool(server2, "applications", "Store, inspect and explicitly activate immutable HTTP/Worker application releases. Upload does not activate a release.", APPLICATION_TOOL_SCHEMA, async (args) => {
@@ -187382,6 +187806,12 @@ function registerApplicationTools(server2, http) {
187382
187806
  throw new Error("Invalid application ID");
187383
187807
  const path = `/v1/projects/${project}/applications/${encodeURIComponent(id)}/releases`;
187384
187808
  const operation = `applications.${action}`;
187809
+ if (action === "rollback_release" || action === "get_rollback_snapshot") {
187810
+ return rollbackAction(http, args, project);
187811
+ }
187812
+ if (APPLICATION_PREVIEW_ACTIONS.some((value) => value === action)) {
187813
+ return applicationPreviewAction(http, args, project);
187814
+ }
187385
187815
  if (action === "activate_release" || action === "reconcile_activation" || action === "retire_activation") {
187386
187816
  return activationAction(http, args, project);
187387
187817
  }
@@ -188750,6 +189180,34 @@ Actions: routes, upsert_route, update_route, delete_route, config, get_certifica
188750
189180
  }
188751
189181
 
188752
189182
  // src/shared/tools/branch-tools.ts
189183
+ var SHA256_PATTERN = /^[0-9a-f]{64}$/;
189184
+ var PROMOTION_PLAN_MAX_JSON_BYTES = 512 * 1024;
189185
+ var PROMOTION_BLOCK_MESSAGES = {
189186
+ parent_ahead: "Parent history is ahead; recreate or rebase the preview branch.",
189187
+ checksum_mismatch: "Parent and branch migration checksums differ.",
189188
+ stored_checksum_mismatch: "Stored checksum does not match migration SQL.",
189189
+ name_conflict: "Migration name already exists in the parent.",
189190
+ out_of_order_migration: "Migration precedes the latest parent migration.",
189191
+ empty_migration: "Migration has no executable statements.",
189192
+ non_transactional_sql: "Migration requires a separate maintenance path.",
189193
+ unsupported_sql: "Migration exceeds the project-scoped SQL policy."
189194
+ };
189195
+ var PROMOTION_FAILURE_CODES = new Set([
189196
+ "promotion_locked",
189197
+ "promotion_plan_changed",
189198
+ "promotion_blocked",
189199
+ "destructive_confirmation_required",
189200
+ "promotion_apply_failed",
189201
+ "promotion_readback_failed",
189202
+ "promotion_failed",
189203
+ "promotion_plan_failed",
189204
+ "promotion_plan_required",
189205
+ "branch_not_active"
189206
+ ]);
189207
+ var PROMOTION_WARNINGS = [
189208
+ "Only recorded migrations are promoted; branch data and untracked schema changes are not copied.",
189209
+ "Migration SQL can modify parent data and must be reviewed."
189210
+ ];
188753
189211
  function resolveProjectRef(ref, projectRef) {
188754
189212
  const resolved = typeof ref === "string" && ref.trim() ? ref.trim() : projectRef || "";
188755
189213
  if (!resolved)
@@ -188761,32 +189219,188 @@ function requireString(value, field) {
188761
189219
  throw new Error(`'${field}' is required`);
188762
189220
  return value.trim();
188763
189221
  }
189222
+ function isRecord4(value) {
189223
+ return !!value && typeof value === "object" && !Array.isArray(value);
189224
+ }
189225
+ function isSha256(value) {
189226
+ return typeof value === "string" && SHA256_PATTERN.test(value);
189227
+ }
189228
+ function parsePromotionEntry(candidate) {
189229
+ if (!isRecord4(candidate))
189230
+ return null;
189231
+ const { version, name, checksum, statement_count: statementCount, statements, destructive } = candidate;
189232
+ if (typeof version !== "string" || !/^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$/.test(version))
189233
+ return null;
189234
+ if (name !== null && (typeof name !== "string" || name.length > 256 || /[\x00-\x1f\x7f]/.test(name)))
189235
+ return null;
189236
+ if (!isSha256(checksum))
189237
+ return null;
189238
+ if (typeof statementCount !== "number" || !Number.isSafeInteger(statementCount) || statementCount < 0)
189239
+ return null;
189240
+ if (typeof destructive !== "boolean")
189241
+ return null;
189242
+ if (statements !== undefined && (!Array.isArray(statements) || statements.length !== statementCount || statements.some((statement) => typeof statement !== "string"))) {
189243
+ return null;
189244
+ }
189245
+ return {
189246
+ version,
189247
+ name,
189248
+ checksum,
189249
+ statement_count: statementCount,
189250
+ destructive
189251
+ };
189252
+ }
189253
+ function parsePromotionBlock(candidate) {
189254
+ if (!isRecord4(candidate))
189255
+ return null;
189256
+ const { code, version, name, message } = candidate;
189257
+ if (typeof code !== "string" || !Object.hasOwn(PROMOTION_BLOCK_MESSAGES, code))
189258
+ return null;
189259
+ if (typeof version !== "string" || !/^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$/.test(version))
189260
+ return null;
189261
+ if (name !== null && (typeof name !== "string" || name.length > 256 || /[\x00-\x1f\x7f]/.test(name)))
189262
+ return null;
189263
+ if (typeof message !== "string")
189264
+ return null;
189265
+ const blockCode = code;
189266
+ return {
189267
+ code: blockCode,
189268
+ version,
189269
+ name,
189270
+ message: PROMOTION_BLOCK_MESSAGES[blockCode]
189271
+ };
189272
+ }
189273
+ function uniqueVersions(entries) {
189274
+ const versions = entries.map((entry) => entry.version);
189275
+ return new Set(versions).size === versions.length;
189276
+ }
189277
+ function samePromotionEntry(left, right) {
189278
+ return left.version === right.version && left.checksum === right.checksum && left.name === right.name && left.statement_count === right.statement_count && left.destructive === right.destructive;
189279
+ }
189280
+ function parsePromotionPlan(candidate, expectedScope) {
189281
+ if (!isRecord4(candidate))
189282
+ return null;
189283
+ const {
189284
+ mode,
189285
+ parent_ref: parentRef,
189286
+ branch_ref: branchRef,
189287
+ safe_to_apply: safeToApply,
189288
+ plan_checksum: planChecksum,
189289
+ pending,
189290
+ applied,
189291
+ blocked,
189292
+ warnings,
189293
+ requires_destructive_confirmation: requiresDestructiveConfirmation,
189294
+ ignored_branch_data: ignoredBranchData
189295
+ } = candidate;
189296
+ if (mode !== "migrations" || typeof parentRef !== "string" || typeof branchRef !== "string" || typeof safeToApply !== "boolean" || !isSha256(planChecksum) || !Array.isArray(pending) || !Array.isArray(applied) || !Array.isArray(blocked) || !Array.isArray(warnings) || typeof requiresDestructiveConfirmation !== "boolean" || ignoredBranchData !== true) {
189297
+ return null;
189298
+ }
189299
+ if (parentRef !== expectedScope.parentRef || branchRef !== expectedScope.branchRef)
189300
+ return null;
189301
+ const pendingEntries = pending.map(parsePromotionEntry);
189302
+ const appliedEntries = applied.map(parsePromotionEntry);
189303
+ const blockingFindings = blocked.map(parsePromotionBlock);
189304
+ if (pendingEntries.some((entry) => entry === null) || appliedEntries.some((entry) => entry === null) || blockingFindings.some((entry) => entry === null) || warnings.some((warning) => typeof warning !== "string")) {
189305
+ return null;
189306
+ }
189307
+ const normalizedPending = pendingEntries.filter((entry) => entry !== null);
189308
+ const normalizedApplied = appliedEntries.filter((entry) => entry !== null);
189309
+ const normalizedBlocked = blockingFindings.filter((entry) => entry !== null);
189310
+ const hasDestructivePending = normalizedPending.some((entry) => entry.destructive);
189311
+ if (!uniqueVersions(normalizedPending) || !uniqueVersions(normalizedApplied) || !uniqueVersions([...normalizedPending, ...normalizedApplied]) || normalizedPending.some((entry) => entry.statement_count === 0) || safeToApply !== (normalizedBlocked.length === 0) || requiresDestructiveConfirmation !== hasDestructivePending) {
189312
+ return null;
189313
+ }
189314
+ return {
189315
+ mode,
189316
+ parent_ref: parentRef,
189317
+ branch_ref: branchRef,
189318
+ safe_to_apply: safeToApply,
189319
+ plan_checksum: planChecksum,
189320
+ pending: normalizedPending,
189321
+ applied: normalizedApplied,
189322
+ blocked: normalizedBlocked,
189323
+ warnings: [...PROMOTION_WARNINGS],
189324
+ requires_destructive_confirmation: requiresDestructiveConfirmation,
189325
+ ignored_branch_data: true
189326
+ };
189327
+ }
189328
+ function parsePromotionResult(candidate, expectedScope, reviewedPending) {
189329
+ if (!isRecord4(candidate) || candidate.promoted !== true || candidate.mode !== "migrations" || candidate.project_ref !== expectedScope.parentRef || candidate.branch_ref !== expectedScope.branchRef || candidate.branch_data_copied !== false || !Array.isArray(candidate.applied)) {
189330
+ return null;
189331
+ }
189332
+ const applied = candidate.applied.map(parsePromotionEntry);
189333
+ if (applied.some((entry) => entry === null))
189334
+ return null;
189335
+ const normalizedApplied = applied.filter((entry) => entry !== null);
189336
+ if (!uniqueVersions(normalizedApplied) || normalizedApplied.length !== reviewedPending.length)
189337
+ return null;
189338
+ const reviewedByVersion = new Map(reviewedPending.map((entry) => [entry.version, entry]));
189339
+ if (normalizedApplied.some((entry) => {
189340
+ const reviewed = reviewedByVersion.get(entry.version);
189341
+ return !reviewed || !samePromotionEntry(entry, reviewed);
189342
+ }))
189343
+ return null;
189344
+ const plan = parsePromotionPlan(candidate.plan, expectedScope);
189345
+ if (!plan || !plan.safe_to_apply || plan.pending.length > 0)
189346
+ return null;
189347
+ const readbackByVersion = new Map(plan.applied.map((entry) => [entry.version, entry]));
189348
+ if (normalizedApplied.some((entry) => {
189349
+ const readback = readbackByVersion.get(entry.version);
189350
+ return !readback || !samePromotionEntry(entry, readback);
189351
+ }))
189352
+ return null;
189353
+ return {
189354
+ promoted: true,
189355
+ mode: "migrations",
189356
+ project_ref: expectedScope.parentRef,
189357
+ branch_ref: expectedScope.branchRef,
189358
+ applied: normalizedApplied,
189359
+ plan,
189360
+ branch_data_copied: false
189361
+ };
189362
+ }
189363
+ function safePromotionCode(result) {
189364
+ if (!isRecord4(result.data) || typeof result.data.code !== "string")
189365
+ return;
189366
+ return PROMOTION_FAILURE_CODES.has(result.data.code) ? result.data.code : undefined;
189367
+ }
189368
+ function appliedVersions(result) {
189369
+ if (result.transportError || result.responseReadError || !isRecord4(result.data) || !Array.isArray(result.data.applied))
189370
+ return [];
189371
+ const entries = result.data.applied.map(parsePromotionEntry);
189372
+ if (entries.some((entry) => !entry))
189373
+ return [];
189374
+ const validated = entries.filter((entry) => entry !== null);
189375
+ return uniqueVersions(validated) ? validated.map((entry) => entry.version) : [];
189376
+ }
189377
+ function promotionFailure(response, json, unknownOutcome) {
189378
+ if (json)
189379
+ return response;
189380
+ const state = JSON.parse(response.content[0].text);
189381
+ if (!isRecord4(state) || !isRecord4(state.error))
189382
+ return response;
189383
+ const lines = [
189384
+ unknownOutcome ? "Promotion outcome is unknown; do not repeat the request automatically." : "Promotion request was not verified.",
189385
+ `Error: ${state.error.code}`,
189386
+ `Project: ${state.project_ref}; branch: ${state.branch_ref}`,
189387
+ ...typeof state.reason === "string" ? [`Reason: ${state.reason}`] : [],
189388
+ ...state.mutation_sent === false ? ["No promotion request was sent."] : [],
189389
+ ...typeof state.plan_checksum === "string" ? [`Reviewed checksum: ${state.plan_checksum}`] : [],
189390
+ ...Array.isArray(state.reported_applied_versions) && state.reported_applied_versions.length > 0 ? [`Server-reported applied versions: ${state.reported_applied_versions.join(", ")}`] : [],
189391
+ "Fetch a fresh promotion_plan and review ledger state before another promote."
189392
+ ];
189393
+ return { isError: true, content: [{ type: "text", text: lines.join(`
189394
+ `) }] };
189395
+ }
188764
189396
  function responseError(result) {
188765
- if (result.data && typeof result.data === "object") {
188766
- const data = result.data;
188767
- const message = data.error || data.message;
188768
- if (typeof message === "string") {
188769
- const details = [message];
188770
- if (Array.isArray(data.applied) && data.applied.length > 0) {
188771
- const versions = data.applied.map((entry) => entry && typeof entry === "object" ? entry.version : null).filter((version) => typeof version === "string");
188772
- details.push(`Applied before failure: ${versions.join(", ") || data.applied.length}`);
188773
- details.push("Fetch a fresh promotion_plan before retrying.");
188774
- }
188775
- if (data.replacement_committed === true) {
188776
- details.push(`Database replacement committed; recovery is required${typeof data.backup_database === "string" ? ` using backup ${data.backup_database}` : ""}.`);
188777
- }
188778
- return details.join(`
188779
- `);
188780
- }
189397
+ if (isRecord4(result.data)) {
189398
+ const message = result.data.error || result.data.message;
189399
+ if (typeof message === "string")
189400
+ return message;
188781
189401
  }
188782
189402
  return `Request failed with HTTP ${result.status}`;
188783
189403
  }
188784
- function isPromotionPlan(candidate) {
188785
- if (!candidate || typeof candidate !== "object")
188786
- return false;
188787
- const plan = candidate;
188788
- return plan.mode === "migrations" && typeof plan.safe_to_apply === "boolean" && typeof plan.plan_checksum === "string" && Array.isArray(plan.pending) && Array.isArray(plan.applied) && Array.isArray(plan.blocked) && Array.isArray(plan.warnings) && typeof plan.requires_destructive_confirmation === "boolean";
188789
- }
188790
189404
  function projectPath(ref) {
188791
189405
  return `/v1/projects/${encodeURIComponent(ref)}/branches`;
188792
189406
  }
@@ -188828,14 +189442,10 @@ function readOnlyResult() {
188828
189442
  content: [{ type: "text", text: "⚠️ Branch write blocked in read-only mode." }]
188829
189443
  };
188830
189444
  }
188831
- function formatPromotionResult(responseValue) {
188832
- if (!responseValue || typeof responseValue !== "object")
188833
- return "Migrations promoted.";
188834
- const response = responseValue;
188835
- const applied = Array.isArray(response.applied) ? response.applied : [];
188836
- const versions = applied.map((entry) => entry && typeof entry === "object" ? entry.version : null).filter((version) => typeof version === "string");
189445
+ function formatPromotionResult(response) {
189446
+ const versions = response.applied.map((entry) => entry.version);
188837
189447
  return [
188838
- `Migration promotion completed: ${applied.length} applied.`,
189448
+ `Migration promotion completed: ${response.applied.length} applied.`,
188839
189449
  ...versions.length > 0 ? [`Versions: ${versions.join(", ")}`] : [],
188840
189450
  "Branch data was not automatically copied to the parent project."
188841
189451
  ].join(`
@@ -188855,13 +189465,19 @@ function registerBranchTools(server2, http, options = {}) {
188855
189465
  name: optional(String2(), "[create] Branch name"),
188856
189466
  data_mode: optional(stringEnum(["schema_only", "full_clone"]), "[create] Preview data mode (default: schema_only)"),
188857
189467
  plan_checksum: optional(String2(), "[promote] Reviewed plan checksum from promotion_plan"),
188858
- confirm_destructive: optional(Boolean2(), "[promote] Confirm reviewed destructive migrations")
189468
+ confirm_destructive: optional(Boolean2(), "[promote] Confirm reviewed destructive migrations"),
189469
+ json: optional(Boolean2(), "[promotion_plan/promote] Return machine-readable JSON")
188859
189470
  }, async (args) => {
188860
189471
  const action = requireString(args.action, "action");
188861
189472
  const ref = resolveProjectRef(args.ref, options.projectRef);
188862
189473
  const writeAction = action === "create" || action === "delete" || action === "promote";
188863
- if (writeAction && options.readOnly)
188864
- return readOnlyResult();
189474
+ if (writeAction && options.readOnly) {
189475
+ return args.json && action === "promote" ? releaseControlFailure("branch.promote", "MUTATION_NOT_SUCCEEDED", null, {
189476
+ project_ref: ref,
189477
+ branch_ref: args.branch_ref,
189478
+ reason: "READ_ONLY"
189479
+ }) : readOnlyResult();
189480
+ }
188865
189481
  let result;
188866
189482
  if (action === "list") {
188867
189483
  result = await http.get(projectPath(ref));
@@ -188876,29 +189492,91 @@ function registerBranchTools(server2, http, options = {}) {
188876
189492
  result = await http.delete(branchPath(ref, branchRef));
188877
189493
  } else if (action === "promotion_plan") {
188878
189494
  const branchRef = requireString(args.branch_ref, "branch_ref");
188879
- result = await http.get(`${branchPath(ref, branchRef)}/promote/plan`);
188880
- if (result.ok && isPromotionPlan(result.data)) {
188881
- return { content: [{ type: "text", text: formatPromotionPlan(result.data) }] };
189495
+ const scope = { project_ref: ref, branch_ref: branchRef };
189496
+ result = await http.get(`${branchPath(ref, branchRef)}/promote/plan`, {
189497
+ maxJsonBytes: PROMOTION_PLAN_MAX_JSON_BYTES,
189498
+ responseTimeoutMs: 5000
189499
+ });
189500
+ if (!result.ok) {
189501
+ return promotionFailure(releaseControlFailure("branch.promotion_plan", "HTTP_ERROR", result.transportError ? null : result.status, scope), args.json === true, false);
189502
+ }
189503
+ const plan = parsePromotionPlan(result.data, { parentRef: ref, branchRef });
189504
+ if (result.status !== 200 || !plan) {
189505
+ return promotionFailure(releaseControlFailure("branch.promotion_plan", "INVALID_RESPONSE", result.status, scope), args.json === true, false);
188882
189506
  }
189507
+ return args.json ? releaseControlSuccess("branch.promotion_plan", { ...scope, plan }) : { content: [{ type: "text", text: formatPromotionPlan(plan) }] };
188883
189508
  } else if (action === "promote") {
188884
189509
  const branchRef = requireString(args.branch_ref, "branch_ref");
188885
189510
  const planChecksum = requireString(args.plan_checksum, "plan_checksum");
189511
+ if (!isSha256(planChecksum))
189512
+ throw new Error("'plan_checksum' must be a 64-character lowercase SHA-256 checksum");
189513
+ const state = {
189514
+ project_ref: ref,
189515
+ branch_ref: branchRef,
189516
+ plan_checksum: planChecksum,
189517
+ automatic_retry: false,
189518
+ reconciliation: { action: "promotion_plan", ref, branch_ref: branchRef }
189519
+ };
189520
+ const preflight = await http.get(`${branchPath(ref, branchRef)}/promote/plan`, {
189521
+ maxJsonBytes: PROMOTION_PLAN_MAX_JSON_BYTES,
189522
+ responseTimeoutMs: 5000
189523
+ });
189524
+ const beforeMutation = { ...state, mutation_sent: false };
189525
+ if (!preflight.ok) {
189526
+ return promotionFailure(releaseControlFailure("branch.promote", "HTTP_ERROR", preflight.transportError ? null : preflight.status, { ...beforeMutation, reason: "PREFLIGHT_FAILED" }), args.json === true, false);
189527
+ }
189528
+ const plan = parsePromotionPlan(preflight.data, { parentRef: ref, branchRef });
189529
+ if (preflight.status !== 200 || !plan) {
189530
+ return promotionFailure(releaseControlFailure("branch.promote", "INVALID_RESPONSE", preflight.status, { ...beforeMutation, reason: "PREFLIGHT_UNVERIFIED" }), args.json === true, false);
189531
+ }
189532
+ const preflightReason = plan.plan_checksum !== planChecksum ? "PLAN_CHANGED" : !plan.safe_to_apply ? "PLAN_BLOCKED" : plan.requires_destructive_confirmation && args.confirm_destructive !== true ? "DESTRUCTIVE_CONFIRMATION_REQUIRED" : null;
189533
+ if (preflightReason) {
189534
+ return promotionFailure(releaseControlFailure("branch.promote", "MUTATION_NOT_SUCCEEDED", null, { ...beforeMutation, reason: preflightReason, observed_plan_checksum: plan.plan_checksum }), args.json === true, false);
189535
+ }
189536
+ if (plan.pending.length === 0) {
189537
+ return args.json ? releaseControlSuccess("branch.promote", {
189538
+ ...beforeMutation,
189539
+ promoted: false,
189540
+ unchanged: true,
189541
+ mode: "migrations",
189542
+ applied: [],
189543
+ plan,
189544
+ branch_data_copied: false,
189545
+ reviewed_plan_checksum: planChecksum
189546
+ }) : { content: [{ type: "text", text: "Migration promotion unchanged: no pending migrations; no promotion request sent." }] };
189547
+ }
188886
189548
  result = await http.post(`${branchPath(ref, branchRef)}/promote`, {
188887
189549
  mode: "migrations",
188888
189550
  plan_checksum: planChecksum,
188889
189551
  confirm_destructive: args.confirm_destructive === true
189552
+ }, {
189553
+ maxJsonBytes: PROMOTION_PLAN_MAX_JSON_BYTES,
189554
+ responseTimeoutMs: 5000,
189555
+ timeoutMs: 30000
188890
189556
  });
189557
+ if (!result.ok) {
189558
+ return promotionFailure(releaseControlMutationFailure("branch.promote", result, {
189559
+ ...state,
189560
+ mutation_sent: true,
189561
+ reported_applied_versions: appliedVersions(result),
189562
+ ...safePromotionCode(result) ? { server_code: safePromotionCode(result) } : {}
189563
+ }), args.json === true, result.transportError === true || result.responseReadError === true || result.status === 408 || result.status >= 500);
189564
+ }
189565
+ const promoted = parsePromotionResult(result.data, { parentRef: ref, branchRef }, plan.pending);
189566
+ if (result.status !== 200 || !promoted) {
189567
+ return promotionFailure(releaseControlFailure("branch.promote", "OUTCOME_UNKNOWN", result.status, { ...state, mutation_sent: true }), args.json === true, true);
189568
+ }
189569
+ return args.json ? releaseControlSuccess("branch.promote", {
189570
+ ...promoted,
189571
+ unchanged: false,
189572
+ mutation_sent: true,
189573
+ reviewed_plan_checksum: planChecksum
189574
+ }) : { content: [{ type: "text", text: formatPromotionResult(promoted) }] };
188891
189575
  } else {
188892
189576
  throw new Error(`Unknown branch action: ${action}`);
188893
189577
  }
188894
189578
  if (!result.ok) {
188895
- return {
188896
- isError: true,
188897
- content: [{ type: "text", text: `❌ ${responseError(result)}` }]
188898
- };
188899
- }
188900
- if (action === "promote") {
188901
- return { content: [{ type: "text", text: formatPromotionResult(result.data) }] };
189579
+ return { isError: true, content: [{ type: "text", text: responseError(result) }] };
188902
189580
  }
188903
189581
  return { content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }] };
188904
189582
  });
@@ -189433,7 +190111,7 @@ import { resolve as resolve11 } from "node:path";
189433
190111
  // src/shared/tools/lite-cli-command.ts
189434
190112
  import { existsSync as existsSync8, readFileSync as readFileSync7, statSync as statSync6 } from "node:fs";
189435
190113
  import { isAbsolute as isAbsolute3, join as join8, relative as relative3, resolve as resolve10 } from "node:path";
189436
- function isRecord4(value) {
190114
+ function isRecord5(value) {
189437
190115
  return value !== null && typeof value === "object" && !Array.isArray(value);
189438
190116
  }
189439
190117
  function resolveLiteCommand(workdir, environment = process.env) {
@@ -189448,7 +190126,7 @@ function resolveLiteCommand(workdir, environment = process.env) {
189448
190126
  if (!existsSync8(manifestPath))
189449
190127
  return ["supacloud-lite"];
189450
190128
  const manifest = JSON.parse(readFileSync7(manifestPath, "utf8"));
189451
- if (!isRecord4(manifest) || manifest.name !== "@supacloud/lite" || !isRecord4(manifest.bin)) {
190129
+ if (!isRecord5(manifest) || manifest.name !== "@supacloud/lite" || !isRecord5(manifest.bin)) {
189452
190130
  throw new Error("Invalid installed @supacloud/lite package manifest");
189453
190131
  }
189454
190132
  const entry = manifest.bin["supacloud-lite"];
@@ -193377,21 +194055,34 @@ var REMOTE_APP_ACTIONS = {
193377
194055
  configure: "put_configuration",
193378
194056
  deploy: "activate_release",
193379
194057
  status: "get_runtime",
193380
- rollback: "activate_release",
194058
+ rollback: "rollback_release",
194059
+ "rollback-plan": "get_rollback_snapshot",
193381
194060
  reconcile: "reconcile_activation",
193382
194061
  retire: "retire_activation",
193383
- logs: "logs"
194062
+ logs: "logs",
194063
+ "preview-plan": "get_preview_plan",
194064
+ preview: "create_preview",
194065
+ previews: "list_previews",
194066
+ "preview-status": "get_preview",
194067
+ "preview-cleanup": "cleanup_preview"
193384
194068
  };
193385
194069
  var REMOTE_APP_DESCRIPTIONS = {
193386
- ref: "[upload/configure/deploy/status/rollback/reconcile/retire] Project ref (defaults to context)",
193387
- id: "[upload/configure/deploy/status/rollback/reconcile/retire] Application ID",
193388
- environment_id: "[configure/deploy/status/rollback/reconcile/retire] Environment ID",
193389
- configuration_id: "[deploy/rollback] Required immutable configuration revision",
193390
- activation_id: "[deploy/rollback/reconcile/retire] Required explicit activation ID",
193391
- expected_activation_id: "[deploy/rollback] Required current activation ID, or absent for first activation",
194070
+ ref: "[upload/configure/deploy/status/rollback/rollback-plan/reconcile/retire/preview-plan/preview/previews/preview-status/preview-cleanup] Project ref (defaults to context)",
194071
+ id: "[upload/configure/deploy/status/rollback/rollback-plan/reconcile/retire/preview-plan/preview/previews/preview-status/preview-cleanup] Application ID",
194072
+ environment_id: "[configure/deploy/status/rollback/rollback-plan/reconcile/retire/preview-plan/preview/previews/preview-status/preview-cleanup] Environment ID",
194073
+ configuration_id: "[deploy/rollback/preview] Required for deploy/preview/explicit rollback; platform selects for default rollback",
194074
+ activation_id: "[deploy/rollback/reconcile/retire] Required explicit activation ID; generated for rollback if omitted",
194075
+ expected_activation_id: "[deploy/rollback] Required for deploy/explicit rollback; platform selects current CAS for default rollback",
193392
194076
  configuration_path: "[configure] Configuration write JSON including revision and expected revision",
193393
194077
  manifest_path: "[upload] Local delivery.manifest.json",
193394
- release_id: "[deploy/rollback/reconcile] Required immutable application release ID"
194078
+ release_id: "[deploy/rollback/reconcile/preview-plan/preview] Required for deploy/reconcile/preview; defaults to journal-selected previous for rollback",
194079
+ preview_id: "[preview-status/preview-cleanup] Preview receipt ID",
194080
+ branch_ref: "[preview-plan] Proposed branch ref; preview assigns its own",
194081
+ branch_name: "[preview] Branch display name",
194082
+ data_mode: "[preview-plan/preview] Default schema_only; full_clone copies rows",
194083
+ ttl_seconds: "[preview-plan/preview] Lifetime from 300 to 604800 seconds; creation defaults to the platform TTL",
194084
+ wait: "[preview/preview-status] Wait for verified readiness of the selected preview",
194085
+ timeout_seconds: "[preview/preview-status] Observation budget with --wait, from 1 to 3600 seconds (default 300)"
193395
194086
  };
193396
194087
  var { action: _remoteAction, ...remoteFields } = APPLICATION_TOOL_SCHEMA;
193397
194088
  var REMOTE_APP_SCHEMA = Partial(_Object_(remoteFields)).properties;
@@ -194394,10 +195085,16 @@ function registerAppTools(server2, options = {}) {
194394
195085
  "deploy",
194395
195086
  "status",
194396
195087
  "rollback",
195088
+ "rollback-plan",
194397
195089
  "reconcile",
194398
195090
  "retire",
194399
- "logs"
194400
- ]), "App action; upload/configure only prepare, rollback activates an explicit old release without schema downgrade"),
195091
+ "logs",
195092
+ "preview-plan",
195093
+ "preview",
195094
+ "previews",
195095
+ "preview-status",
195096
+ "preview-cleanup"
195097
+ ]), "App action; upload/configure only prepare, rollback activates the journal-selected previous release without schema downgrade"),
194401
195098
  kind: optional(stringEnum(["module", "command", "query", "controller", "job", "contract", "resource"]), "[generate] Scaffold kind"),
194402
195099
  template: optional(stringEnum(["minimal", "http", "command", "edge"]), "[init] Minimal application by default; explicit http/command/edge recipes"),
194403
195100
  name: optional(String2(), "[init/generate] Project or object name"),
@@ -195840,7 +196537,7 @@ var RELEASE_CANARY_DISABLE_RECEIPT_KEYS = new Set(["fixtureId", "state", "idempo
195840
196537
  var RELEASE_CANARY_DISABLE_RPC_PATH = "/rest/v1/rpc/fa_release_canary_fixture_disable";
195841
196538
  var RELEASE_CANARY_PENDING_RPC_PATH = "/rest/v1/rpc/fa_release_canary_fixture_pending";
195842
196539
  var RELEASE_CANARY_CLAIM_MAX_LENGTH = 2048;
195843
- function isRecord5(value) {
196540
+ function isRecord6(value) {
195844
196541
  return value !== null && typeof value === "object" && !Array.isArray(value);
195845
196542
  }
195846
196543
  function canonicalTimestamp4(value) {
@@ -195856,7 +196553,7 @@ function backupBelongsToProject(backupId, projectRef) {
195856
196553
  return BACKUP_ID.test(backupId) && backupId.startsWith(`logical-full_${projectRef}_`);
195857
196554
  }
195858
196555
  function verifiedBackup(value, projectRef) {
195859
- if (!isRecord5(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))
196556
+ 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))
195860
196557
  return null;
195861
196558
  return {
195862
196559
  backup_id: value.backup_id,
@@ -195870,7 +196567,7 @@ function verifiedBackup(value, projectRef) {
195870
196567
  };
195871
196568
  }
195872
196569
  function backupInventory(value, projectRef) {
195873
- if (!isRecord5(value) || !Array.isArray(value.backups))
196570
+ if (!isRecord6(value) || !Array.isArray(value.backups))
195874
196571
  return null;
195875
196572
  const backups = value.backups.map((backup) => verifiedBackup(backup, projectRef));
195876
196573
  if (backups.some((backup) => backup === null))
@@ -195949,7 +196646,7 @@ function readInventoryFailure(operation, read) {
195949
196646
  return null;
195950
196647
  }
195951
196648
  function postgrestStatus(value) {
195952
- if (!isRecord5(value) || value.component !== "postgrest" || !["running", "stopped"].includes(String(value.desired)) || !["running", "stopped", "starting", "error"].includes(String(value.actual)) || !["healthy", "unhealthy", "unknown"].includes(String(value.health)))
196649
+ if (!isRecord6(value) || value.component !== "postgrest" || !["running", "stopped"].includes(String(value.desired)) || !["running", "stopped", "starting", "error"].includes(String(value.actual)) || !["healthy", "unhealthy", "unknown"].includes(String(value.health)))
195953
196650
  return null;
195954
196651
  return {
195955
196652
  desired: value.desired,
@@ -195970,7 +196667,7 @@ function readPostgrestFailure(operation, read) {
195970
196667
  return read.response.status === 200 && read.status ? null : releaseControlFailure(operation, "INVALID_RESPONSE", read.response.status);
195971
196668
  }
195972
196669
  function isRestartReceipt(value) {
195973
- return isRecord5(value) && value.service === "postgrest" && value.action === "restart" && value.success === true;
196670
+ return isRecord6(value) && value.service === "postgrest" && value.action === "restart" && value.success === true;
195974
196671
  }
195975
196672
  function releaseCanaryStageInput(subject, requestId) {
195976
196673
  if (typeof subject !== "string" || !UUID.test(subject))
@@ -195980,7 +196677,7 @@ function releaseCanaryStageInput(subject, requestId) {
195980
196677
  return { p_subject: subject, p_request_id: requestId };
195981
196678
  }
195982
196679
  function releaseCanaryStageReceipt(value) {
195983
- if (!isRecord5(value) || Object.keys(value).some((key) => !RELEASE_CANARY_STAGE_RECEIPT_KEYS.has(key)) || Object.keys(value).length !== RELEASE_CANARY_STAGE_RECEIPT_KEYS.size || typeof value.fixtureId !== "string" || !UUID.test(value.fixtureId) || typeof value.tenantKey !== "string" || !RELEASE_CANARY_TENANT_KEY.test(value.tenantKey) || value.state !== "staged" || value.idempotent !== true)
196680
+ if (!isRecord6(value) || Object.keys(value).some((key) => !RELEASE_CANARY_STAGE_RECEIPT_KEYS.has(key)) || Object.keys(value).length !== RELEASE_CANARY_STAGE_RECEIPT_KEYS.size || typeof value.fixtureId !== "string" || !UUID.test(value.fixtureId) || typeof value.tenantKey !== "string" || !RELEASE_CANARY_TENANT_KEY.test(value.tenantKey) || value.state !== "staged" || value.idempotent !== true)
195984
196681
  return null;
195985
196682
  return {
195986
196683
  fixtureId: value.fixtureId,
@@ -196023,7 +196720,7 @@ function releaseCanaryDisableInput(fixtureId, disableRequestId, issuer, subject)
196023
196720
  };
196024
196721
  }
196025
196722
  function releaseCanaryDisableReceipt(receiptCandidate, fixtureId) {
196026
- if (!isRecord5(receiptCandidate) || Object.keys(receiptCandidate).some((key) => !RELEASE_CANARY_DISABLE_RECEIPT_KEYS.has(key)) || Object.keys(receiptCandidate).length !== RELEASE_CANARY_DISABLE_RECEIPT_KEYS.size || receiptCandidate.fixtureId !== fixtureId || receiptCandidate.state !== "disabled" || typeof receiptCandidate.idempotent !== "boolean")
196723
+ if (!isRecord6(receiptCandidate) || Object.keys(receiptCandidate).some((key) => !RELEASE_CANARY_DISABLE_RECEIPT_KEYS.has(key)) || Object.keys(receiptCandidate).length !== RELEASE_CANARY_DISABLE_RECEIPT_KEYS.size || receiptCandidate.fixtureId !== fixtureId || receiptCandidate.state !== "disabled" || typeof receiptCandidate.idempotent !== "boolean")
196027
196724
  return null;
196028
196725
  return { fixtureId, state: "disabled", idempotent: receiptCandidate.idempotent };
196029
196726
  }
@@ -196196,7 +196893,7 @@ function handleScopeRebind(args) {
196196
196893
  } catch {
196197
196894
  throw new Error(`Scope file '${filePath}' is not valid JSON`);
196198
196895
  }
196199
- if (!isRecord5(parsed) || typeof parsed.baseCommit !== "string" || !/^[0-9a-f]{40}$/.test(parsed.baseCommit)) {
196896
+ if (!isRecord6(parsed) || typeof parsed.baseCommit !== "string" || !/^[0-9a-f]{40}$/.test(parsed.baseCommit)) {
196200
196897
  throw new Error(`Scope file '${filePath}' has missing or invalid baseCommit`);
196201
196898
  }
196202
196899
  const previousBaseCommit = parsed.baseCommit;
@@ -196260,7 +196957,7 @@ function handleScopeInspect(args) {
196260
196957
  } catch {
196261
196958
  throw new Error(`Scope file '${args.file}' is not valid JSON`);
196262
196959
  }
196263
- if (!isRecord5(parsed) || typeof parsed.baseCommit !== "string" || !/^[0-9a-f]{40}$/.test(parsed.baseCommit)) {
196960
+ if (!isRecord6(parsed) || typeof parsed.baseCommit !== "string" || !/^[0-9a-f]{40}$/.test(parsed.baseCommit)) {
196264
196961
  throw new Error(`Scope file '${args.file}' has missing or invalid baseCommit`);
196265
196962
  }
196266
196963
  const scopeSha256 = genericScopedReleaseScopeSha256(parsed);
@@ -196422,7 +197119,7 @@ function registerReleaseTools(server2, http, options = {}) {
196422
197119
  if (!mutation.ok || mutation.status !== 200) {
196423
197120
  return mutationFailure("release.logical_backup.create", mutation);
196424
197121
  }
196425
- const responseBackup = isRecord5(mutation.data) ? verifiedBackup(mutation.data.backup, projectRef) : null;
197122
+ const responseBackup = isRecord6(mutation.data) ? verifiedBackup(mutation.data.backup, projectRef) : null;
196426
197123
  const afterFailure = readInventoryFailure("release.logical_backup.create", after);
196427
197124
  const addedBackup = after.inventory && newlyCreatedBackup(before.inventory, after.inventory);
196428
197125
  if (!responseBackup || afterFailure || !addedBackup || !equalBackup(responseBackup, addedBackup)) {
@@ -196447,7 +197144,7 @@ function registerReleaseTools(server2, http, options = {}) {
196447
197144
  if (!mutation.ok || mutation.status !== 200) {
196448
197145
  return mutationFailure("release.logical_backup.restore", mutation);
196449
197146
  }
196450
- const responseBackup = isRecord5(mutation.data) ? verifiedBackup(mutation.data.restored_backup, projectRef) : null;
197147
+ const responseBackup = isRecord6(mutation.data) ? verifiedBackup(mutation.data.restored_backup, projectRef) : null;
196451
197148
  const after = await readInventory(http, projectRef);
196452
197149
  const afterFailure = readInventoryFailure("release.logical_backup.restore", after);
196453
197150
  const restoredInventoryBackup = after.inventory?.find((backup) => backup.backup_id === request.backup_id);
@@ -197964,7 +198661,7 @@ function registerRemoteDevTools(server2, options = {}) {
197964
198661
  // package.json
197965
198662
  var package_default = {
197966
198663
  name: "@supacloud/cli",
197967
- version: "0.77.0",
198664
+ version: "0.79.0",
197968
198665
  description: "Project-scoped CLI for SupaCloud users",
197969
198666
  type: "module",
197970
198667
  main: "./dist/index.js",
@@ -198016,14 +198713,14 @@ var package_default = {
198016
198713
  },
198017
198714
  dependencies: {
198018
198715
  "@supacloud/compiler": "^0.43.0",
198019
- "@supacloud/delivery": "0.8.0",
198716
+ "@supacloud/delivery": "0.9.0",
198020
198717
  "@supacloud/db": "0.16.0",
198021
198718
  fflate: "^0.8.3",
198022
198719
  typebox: "1.3.34"
198023
198720
  },
198024
198721
  overrides: {
198025
198722
  "@supacloud/contracts": "0.8.0",
198026
- "@supacloud/delivery": "0.8.0",
198723
+ "@supacloud/delivery": "0.9.0",
198027
198724
  "@graphql-tools/utils": "^12.0.3"
198028
198725
  },
198029
198726
  devDependencies: {
@@ -198286,6 +198983,11 @@ EXAMPLES
198286
198983
  ${preferredCommand} app configure --ref abc123 --id orders --environment_id test --configuration_path configuration.json
198287
198984
  ${preferredCommand} app deploy --ref abc123 --id orders --environment_id test --release_id <sha256> --configuration_id <uuid> --activation_id <uuid> --expected_activation_id absent
198288
198985
  ${preferredCommand} app status --ref abc123 --id orders --environment_id test
198986
+ ${preferredCommand} app preview-plan --ref abc123 --id orders --environment_id test --release_id <sha256> --branch_ref preview-orders
198987
+ ${preferredCommand} app preview --ref abc123 --id orders --environment_id test --release_id <sha256> --configuration_id <uuid> --ttl_seconds 3600 --wait
198988
+ ${preferredCommand} app previews --ref abc123 --id orders --environment_id test
198989
+ ${preferredCommand} app preview-status --ref abc123 --id orders --environment_id test --preview_id <uuid> --wait --timeout_seconds 300
198990
+ ${preferredCommand} app preview-cleanup --ref abc123 --id orders --environment_id test --preview_id <uuid>
198289
198991
  ${preferredCommand} app logs --ref abc123 --id orders --environment_id test
198290
198992
  ${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>
198291
198993
  ${preferredCommand} app check --root . --strict