@supacloud/cli 0.29.0 → 0.30.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/README.md CHANGED
@@ -142,6 +142,9 @@ supacloud-cli release postgrest_status --ref abc123
142
142
  supacloud-cli release postgrest_restart --ref abc123
143
143
  supacloud-cli release release_canary_fixture_stage_replay --ref abc123 \
144
144
  --subject <central-subject-uuid> --request_id <stage-request-uuid>
145
+ supacloud-cli release release_canary_fixture_disable_replay --ref abc123 \
146
+ --fixture_id <fixture-uuid> --disable_request_id <disable-request-uuid> \
147
+ --issuer <issuer-url> --subject <central-subject-uuid>
145
148
  ```
146
149
 
147
150
  Backup creation reports success only after the CLI verifies exactly one new
@@ -171,6 +174,17 @@ reads the selected project's authoritative endpoint projection and requires the
171
174
  configured application origin to match its API origin or alias. Production
172
175
  confirmation is mandatory.
173
176
 
177
+ `release_canary_fixture_disable_replay` performs one non-retried call to the
178
+ fixed `fa_release_canary_fixture_disable` PostgREST RPC using the selected
179
+ project's application service-role origin. Its receipt must contain exactly
180
+ `fixtureId`, `state="disabled"`, and a boolean `idempotent`; both the first
181
+ disable (`idempotent=false`) and same-request replay (`idempotent=true`) are
182
+ valid. The CLI then calls the existing fixed
183
+ `fa_release_canary_fixture_pending` RPC with only the exact issuer and subject
184
+ query parameters and accepts only its authoritative JSON boolean `false`
185
+ read-back. The fixture binding comes from the validated disable receipt before
186
+ reporting success. Management endpoint projection is checked before and after.
187
+
174
188
  The legacy `.env` fallback is unclassified and therefore does not enable the
175
189
  production confirmation gate. Production automation must select a `prod` or
176
190
  `production` profile, or set `SUPACLOUD_ENV=production` together with a complete
package/dist/index.js CHANGED
@@ -6491,7 +6491,7 @@ var ACTION_POLICY = {
6491
6491
  mutations: { read: ["status"] },
6492
6492
  release: {
6493
6493
  read: ["logical_backup_list", "postgrest_status"],
6494
- write: ["logical_backup_create", "logical_backup_restore", "postgrest_restart", "release_canary_fixture_stage_replay"]
6494
+ write: ["logical_backup_create", "logical_backup_restore", "postgrest_restart", "release_canary_fixture_stage_replay", "release_canary_fixture_disable_replay"]
6495
6495
  },
6496
6496
  secrets: { read: ["list"], write: ["upsert", "delete"] },
6497
6497
  frontend: {
@@ -12844,6 +12844,10 @@ var RELEASE_READ_RESPONSE_TIMEOUT_MS = 5000;
12844
12844
  var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
12845
12845
  var RELEASE_CANARY_TENANT_KEY = /^release-canary-[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
12846
12846
  var RELEASE_CANARY_STAGE_RECEIPT_KEYS = new Set(["fixtureId", "tenantKey", "state", "idempotent"]);
12847
+ var RELEASE_CANARY_DISABLE_RECEIPT_KEYS = new Set(["fixtureId", "state", "idempotent"]);
12848
+ var RELEASE_CANARY_DISABLE_RPC_PATH = "/rest/v1/rpc/fa_release_canary_fixture_disable";
12849
+ var RELEASE_CANARY_PENDING_RPC_PATH = "/rest/v1/rpc/fa_release_canary_fixture_pending";
12850
+ var RELEASE_CANARY_CLAIM_MAX_LENGTH = 2048;
12847
12851
  function isRecord3(value) {
12848
12852
  return value !== null && typeof value === "object" && !Array.isArray(value);
12849
12853
  }
@@ -12993,27 +12997,79 @@ function releaseCanaryStageReceipt(value) {
12993
12997
  idempotent: true
12994
12998
  };
12995
12999
  }
13000
+ function releaseCanaryIssuer(value) {
13001
+ if (typeof value !== "string" || value.length === 0 || value.length > RELEASE_CANARY_CLAIM_MAX_LENGTH || value !== value.trim() || /[\u0000-\u001f\u007f]/u.test(value)) {
13002
+ throw new Error("'issuer' must be a bounded absolute HTTP(S) issuer");
13003
+ }
13004
+ let parsed;
13005
+ try {
13006
+ parsed = new URL(value);
13007
+ } catch {
13008
+ throw new Error("'issuer' must be a bounded absolute HTTP(S) issuer");
13009
+ }
13010
+ const loopbackHttp = parsed.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(parsed.hostname) && parsed.port.length > 0;
13011
+ if (parsed.protocol !== "https:" && !loopbackHttp || parsed.username || parsed.password || parsed.search || parsed.hash) {
13012
+ throw new Error("'issuer' must be a bounded absolute HTTP(S) issuer");
13013
+ }
13014
+ return value;
13015
+ }
13016
+ function releaseCanaryDisableInput(fixtureId, disableRequestId, issuer, subject) {
13017
+ if (typeof fixtureId !== "string" || !UUID.test(fixtureId)) {
13018
+ throw new Error("'fixture_id' must be a canonical UUID");
13019
+ }
13020
+ if (typeof disableRequestId !== "string" || !UUID.test(disableRequestId)) {
13021
+ throw new Error("'disable_request_id' must be a canonical UUID");
13022
+ }
13023
+ if (typeof subject !== "string" || !UUID.test(subject)) {
13024
+ throw new Error("'subject' must be a canonical UUID");
13025
+ }
13026
+ return {
13027
+ p_fixture_id: fixtureId,
13028
+ p_disable_request_id: disableRequestId,
13029
+ p_issuer: releaseCanaryIssuer(issuer),
13030
+ p_subject: subject
13031
+ };
13032
+ }
13033
+ function releaseCanaryDisableReceipt(receiptCandidate, fixtureId) {
13034
+ if (!isRecord3(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")
13035
+ return null;
13036
+ return { fixtureId, state: "disabled", idempotent: receiptCandidate.idempotent };
13037
+ }
13038
+ function releaseCanaryPendingPath(request) {
13039
+ const params = new URLSearchParams({
13040
+ p_issuer: request.p_issuer,
13041
+ p_subject: request.p_subject
13042
+ });
13043
+ return `${RELEASE_CANARY_PENDING_RPC_PATH}?${params.toString()}`;
13044
+ }
13045
+ function releaseCanaryPendingReadback(readbackCandidate) {
13046
+ return readbackCandidate === false;
13047
+ }
12996
13048
  async function applicationOriginMatches(http, projectRef2, applicationOrigin) {
12997
- const endpointRead = await http.get(`${endpoint(projectRef2)}/endpoint/projection`, { maxResponseBytes: PROJECT_ENDPOINT_RESPONSE_MAX_BYTES });
13049
+ const endpointRead = await http.get(`${endpoint(projectRef2)}/endpoint/projection`, { maxJsonBytes: PROJECT_ENDPOINT_RESPONSE_MAX_BYTES, responseTimeoutMs: RELEASE_READ_RESPONSE_TIMEOUT_MS });
12998
13050
  return projectApiOrigins(endpointRead, projectRef2)?.includes(applicationOrigin) === true;
12999
13051
  }
13000
13052
  function registerReleaseTools(server, http, options = {}) {
13001
- server.tool("release", "Verified release controls. Management actions use the Management API; release_canary_fixture_stage_replay additionally requires the selected project's SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY.", {
13053
+ server.tool("release", "Verified release controls. Management actions use the Management API; release canary stage/disable replay additionally require the selected project's SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY.", {
13002
13054
  action: withDescription(stringEnum([
13003
13055
  "logical_backup_list",
13004
13056
  "logical_backup_create",
13005
13057
  "logical_backup_restore",
13006
13058
  "postgrest_status",
13007
13059
  "postgrest_restart",
13008
- "release_canary_fixture_stage_replay"
13060
+ "release_canary_fixture_stage_replay",
13061
+ "release_canary_fixture_disable_replay"
13009
13062
  ]), "Release control action"),
13010
13063
  ref: optional(Type.String(), options.projectRef ? "Optional override when not auto-linked" : "Project ref"),
13011
13064
  backup_id: optional(Type.String(), "[logical_backup_restore] Exact verified logical-full backup ID from the selected project inventory"),
13012
13065
  expected_sha256: optional(Type.String(), "[logical_backup_restore] Exact lowercase SHA-256 from the selected project inventory"),
13013
13066
  restore_confirmation: optional(Type.String(), "[logical_backup_restore] Exact RESTORE_PROJECT:<ref>:<backup_id>:<sha256> confirmation"),
13014
- subject: optional(Type.String(), "[release_canary_fixture_stage_replay] Exact central subject UUID"),
13015
- request_id: optional(Type.String(), "[release_canary_fixture_stage_replay] Exact idempotent stage request UUID")
13016
- }, async ({ action, ref, backup_id, expected_sha256, restore_confirmation, subject, request_id }) => {
13067
+ subject: optional(Type.String(), "[release_canary_fixture_stage_replay/disable_replay] Exact central subject UUID"),
13068
+ request_id: optional(Type.String(), "[release_canary_fixture_stage_replay] Exact idempotent stage request UUID"),
13069
+ fixture_id: optional(Type.String(), "[release_canary_fixture_disable_replay] Exact staged fixture UUID"),
13070
+ disable_request_id: optional(Type.String(), "[release_canary_fixture_disable_replay] Exact idempotent disable request UUID"),
13071
+ issuer: optional(Type.String(), "[release_canary_fixture_disable_replay] Exact HTTP(S) issuer")
13072
+ }, async ({ action, ref, backup_id, expected_sha256, restore_confirmation, subject, request_id, fixture_id, disable_request_id, issuer }) => {
13017
13073
  const projectRef2 = typeof ref === "string" && ref || options.projectRef;
13018
13074
  if (!projectRef2)
13019
13075
  throw new Error("'ref' is required for release controls");
@@ -13105,6 +13161,36 @@ function registerReleaseTools(server, http, options = {}) {
13105
13161
  receipt
13106
13162
  });
13107
13163
  }
13164
+ if (action === "release_canary_fixture_disable_replay") {
13165
+ if (!options.applicationHttp || !options.applicationOrigin) {
13166
+ throw new Error("release_canary_fixture_disable_replay requires SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY");
13167
+ }
13168
+ const request = releaseCanaryDisableInput(fixture_id, disable_request_id, issuer, subject);
13169
+ if (!await applicationOriginMatches(http, projectRef2, options.applicationOrigin)) {
13170
+ return releaseControlFailure("release.release_canary.fixture_disable_replay", "INVALID_RESPONSE", null);
13171
+ }
13172
+ const response = await options.applicationHttp.postReleaseMutation(RELEASE_CANARY_DISABLE_RPC_PATH, request);
13173
+ if (!response.ok || response.status !== 200) {
13174
+ return mutationFailure("release.release_canary.fixture_disable_replay", response);
13175
+ }
13176
+ const receipt = releaseCanaryDisableReceipt(response.data, request.p_fixture_id);
13177
+ if (!receipt) {
13178
+ return releaseControlFailure("release.release_canary.fixture_disable_replay", "OUTCOME_UNKNOWN", response.status);
13179
+ }
13180
+ const pendingRequest = {
13181
+ p_issuer: request.p_issuer,
13182
+ p_subject: request.p_subject
13183
+ };
13184
+ const pendingResponse = await options.applicationHttp.get(releaseCanaryPendingPath(pendingRequest), { maxJsonBytes: MUTATION_MAX_BYTES, responseTimeoutMs: RELEASE_READ_RESPONSE_TIMEOUT_MS });
13185
+ if (!pendingResponse.ok || pendingResponse.status !== 200 || !releaseCanaryPendingReadback(pendingResponse.data) || !await applicationOriginMatches(http, projectRef2, options.applicationOrigin)) {
13186
+ return releaseControlFailure("release.release_canary.fixture_disable_replay", "OUTCOME_UNKNOWN", pendingResponse.transportError ? null : pendingResponse.status);
13187
+ }
13188
+ return releaseControlSuccess("release.release_canary.fixture_disable_replay", {
13189
+ project_ref: projectRef2,
13190
+ receipt,
13191
+ pending: false
13192
+ });
13193
+ }
13108
13194
  if (action !== "postgrest_restart")
13109
13195
  throw new Error("Unknown release control action");
13110
13196
  const mutation = await http.postReleaseMutation(`${endpoint(projectRef2)}/services/postgrest/restart`);
@@ -13125,7 +13211,7 @@ function registerReleaseTools(server, http, options = {}) {
13125
13211
  // package.json
13126
13212
  var package_default = {
13127
13213
  name: "@supacloud/cli",
13128
- version: "0.29.0",
13214
+ version: "0.30.0",
13129
13215
  description: "Project-scoped CLI for SupaCloud users",
13130
13216
  type: "module",
13131
13217
  main: "./dist/index.js",
@@ -13354,8 +13440,8 @@ DEFAULT CONTEXT
13354
13440
  Without a selector or project variables, runs use the current project's legacy .env.
13355
13441
  Application status accepts SUPABASE_URL + SUPABASE_SERVICE_ROLE_KEY.
13356
13442
  Management-backed project commands require SUPACLOUD_API_URL +
13357
- SUPACLOUD_API_TOKEN. Only release release_canary_fixture_stage_replay may additionally use
13358
- the selected project's SUPABASE_* pair for one application-origin RPC.
13443
+ SUPACLOUD_API_TOKEN. Only release canary stage/disable replay actions may additionally use
13444
+ the selected project's SUPABASE_* pair for fixed application-origin RPCs.
13359
13445
  SUPACLOUD_PROJECT_REF is required when it cannot be inferred from <ref>.api.*.
13360
13446
 
13361
13447
  SUPACLOUD_READ_ONLY=true blocks remote writes. Production writes require an
@@ -13377,6 +13463,7 @@ EXAMPLES
13377
13463
  ${preferredCommand} release postgrest_status --ref abc123
13378
13464
  ${preferredCommand} release postgrest_restart --ref abc123
13379
13465
  ${preferredCommand} release release_canary_fixture_stage_replay --ref abc123 --subject <uuid> --request_id <uuid>
13466
+ ${preferredCommand} release release_canary_fixture_disable_replay --ref abc123 --fixture_id <uuid> --disable_request_id <uuid> --issuer <issuer-url> --subject <uuid>
13380
13467
  ${preferredCommand} queue stats --queue emails
13381
13468
  ${preferredCommand} queue dlq --queue emails --limit 20
13382
13469
  ${preferredCommand} frontend list --ref abc123
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supacloud/cli",
3
- "version": "0.29.0",
3
+ "version": "0.30.0",
4
4
  "description": "Project-scoped CLI for SupaCloud users",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -16,6 +16,7 @@ Load this reference when selecting a command surface or when a user asks an AI t
16
16
  | Rebuild local database | `supabase db_reset` | Local only; preserve required seed behavior |
17
17
  | Inspect or back up a remote database | `supabase db_pull`, `migration_list`, `db_dump`, `gen_types` | Requires explicit PostgreSQL DSN; redact it |
18
18
  | Replay the release-canary fixture stage receipt | `release release_canary_fixture_stage_replay` | Dual-bind Management project context and that project's service-role application origin; accepts only the exact subject/request UUID pair and a strict idempotent receipt |
19
+ | Disable a staged release-canary fixture | `release release_canary_fixture_disable_replay` | Uses the fixed disable RPC once, accepts idempotent false/true receipts, then queries the existing pending RPC with issuer/subject and requires authoritative JSON `false`; fixture binding comes from the strict disable receipt |
19
20
  | Preview/apply migrations remotely | `supabase push` | Always dry-run first; production needs explicit approval |
20
21
  | Mark proven-equivalent historical migrations as applied | `database baseline_migrations` | Dry-run, schema-equivalence proof, backup, explicit approval |
21
22
  | Inspect auth users or generate a controlled login link | `auth list_users`, `auth get_user`, `auth generate_link` | User reads are bounded; generation supports only `magiclink`, `recovery`, and `invite`, requires production confirmation, and returns only a validated action URL |
@@ -40,7 +41,7 @@ until a project-scoped context is resolved.
40
41
  - `secrets`: project secret management; never print values after write.
41
42
  - `queue`, `task_events`, `diagnostics`: asynchronous workload operations and bounded diagnostics.
42
43
  - `gateway`: project route/config/rebuild operations.
43
- - `release`: verified backup/PostgREST lifecycle controls plus a production-confirmed, non-retried, strict release-canary fixture stage replay. The replay requires both the selected Management project binding and that project's `SUPABASE_URL`/`SUPABASE_SERVICE_ROLE_KEY`.
44
+ - `release`: verified backup/PostgREST lifecycle controls plus production-confirmed, non-retried, strict release-canary fixture stage and disable replay actions. These actions require both the selected Management project binding and that project's `SUPABASE_URL`/`SUPABASE_SERVICE_ROLE_KEY`.
44
45
  - `ai`: inspect or install this packaged Skill.
45
46
 
46
47
  ## Safe inspection pattern