@supacloud/cli 0.28.1 → 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
@@ -108,10 +108,12 @@ loopback development origins, with the default `:80` likewise omitted. Use
108
108
 
109
109
  ### Verified release controls
110
110
 
111
- `release` is an official CLI entry point for the existing Management API
112
- logical-backup and PostgREST lifecycle capabilities. It requires the Management
113
- API context above; it does not promote an application `service_role` key to
114
- Management authority.
111
+ `release` is an official CLI entry point for verified Management API controls
112
+ and the release-canary fixture receipt replay. Management actions require the
113
+ Management API context above. `release_canary_fixture_stage_replay` additionally
114
+ requires the same project's `SUPABASE_URL` and `SUPABASE_SERVICE_ROLE_KEY`; the
115
+ key is sent only to that application origin and is never promoted to Management
116
+ authority.
115
117
 
116
118
  Project pause and restore are explicit lifecycle commands. A logical backup
117
119
  restore requires an operator to pause the selected project first and then use
@@ -138,6 +140,11 @@ supacloud-cli release logical_backup_restore --ref abc123 \
138
140
  --restore_confirmation RESTORE_PROJECT:abc123:logical-full_abc123_<backup-id-suffix>:<64-lowercase-hex>
139
141
  supacloud-cli release postgrest_status --ref abc123
140
142
  supacloud-cli release postgrest_restart --ref abc123
143
+ supacloud-cli release release_canary_fixture_stage_replay --ref abc123 \
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>
141
148
  ```
142
149
 
143
150
  Backup creation reports success only after the CLI verifies exactly one new
@@ -158,6 +165,26 @@ receipt and reads back `desired=running`, `actual=running`, and
158
165
  `health=healthy`. Both mutating controls follow the normal production
159
166
  confirmation and read-only protections.
160
167
 
168
+ `release_canary_fixture_stage_replay` performs one non-retried, response-bounded
169
+ call to the fixed `fa_release_canary_fixture_stage` PostgREST RPC with both
170
+ bearer and `apikey` service-role headers. It accepts only canonical subject and
171
+ request UUIDs, requires a strict `staged` and `idempotent=true` four-field
172
+ receipt, and emits only that safe projection. Before and after the call, the CLI
173
+ reads the selected project's authoritative endpoint projection and requires the
174
+ configured application origin to match its API origin or alias. Production
175
+ confirmation is mandatory.
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
+
161
188
  The legacy `.env` fallback is unclassified and therefore does not enable the
162
189
  production confirmation gate. Production automation must select a `prod` or
163
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"]
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: {
@@ -6805,14 +6805,17 @@ async function responseJsonOrNull(response) {
6805
6805
  class HttpTransport {
6806
6806
  baseUrl;
6807
6807
  token;
6808
+ apiKey;
6808
6809
  constructor(config) {
6809
6810
  this.baseUrl = config.baseUrl.replace(/\/$/, "");
6810
6811
  this.token = config.token;
6812
+ this.apiKey = config.apiKey ?? "";
6811
6813
  }
6812
6814
  headers() {
6813
6815
  return {
6814
6816
  Authorization: `Bearer ${this.token}`,
6815
- "Content-Type": "application/json"
6817
+ "Content-Type": "application/json",
6818
+ ...this.apiKey ? { apikey: this.apiKey } : {}
6816
6819
  };
6817
6820
  }
6818
6821
  async mutationWithResponseReader(method, path, serializedBody, responseReader, timeoutMs = DEFAULT_TIMEOUT) {
@@ -10562,6 +10565,15 @@ function projectEndpointRead(response, expectedRef) {
10562
10565
  const projection = projectEndpointProjection(response.data);
10563
10566
  return projection && projection.project_ref === expectedRef ? successfulResult2(projection) : failedResult2("Invalid project endpoint response");
10564
10567
  }
10568
+ function projectApiOrigins(response, expectedRef) {
10569
+ if (!successfulResponse2(response))
10570
+ return null;
10571
+ const projection = projectEndpointProjection(response.data);
10572
+ if (!projection || projection.project_ref !== expectedRef)
10573
+ return null;
10574
+ const api = projection.endpoints.api;
10575
+ return [api.origin, ...api.aliases.map((alias) => `${api.scheme}://${alias}`)];
10576
+ }
10565
10577
 
10566
10578
  // src/shared/tools/project-cli-tools.ts
10567
10579
  function projectReadResponse(readResult) {
@@ -12829,6 +12841,13 @@ var INVENTORY_MAX_BYTES = 1024 * 1024;
12829
12841
  var MUTATION_MAX_BYTES = 64 * 1024;
12830
12842
  var BACKUP_TIMEOUT_MS = 36 * 60000;
12831
12843
  var RELEASE_READ_RESPONSE_TIMEOUT_MS = 5000;
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
+ 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
+ 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;
12832
12851
  function isRecord3(value) {
12833
12852
  return value !== null && typeof value === "object" && !Array.isArray(value);
12834
12853
  }
@@ -12961,20 +12980,96 @@ function readPostgrestFailure(operation, read) {
12961
12980
  function isRestartReceipt(value) {
12962
12981
  return isRecord3(value) && value.service === "postgrest" && value.action === "restart" && value.success === true;
12963
12982
  }
12983
+ function releaseCanaryStageInput(subject, requestId) {
12984
+ if (typeof subject !== "string" || !UUID.test(subject))
12985
+ throw new Error("'subject' must be a canonical UUID");
12986
+ if (typeof requestId !== "string" || !UUID.test(requestId))
12987
+ throw new Error("'request_id' must be a canonical UUID");
12988
+ return { p_subject: subject, p_request_id: requestId };
12989
+ }
12990
+ function releaseCanaryStageReceipt(value) {
12991
+ if (!isRecord3(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)
12992
+ return null;
12993
+ return {
12994
+ fixtureId: value.fixtureId,
12995
+ tenantKey: value.tenantKey,
12996
+ state: "staged",
12997
+ idempotent: true
12998
+ };
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
+ }
13048
+ async function applicationOriginMatches(http, projectRef2, applicationOrigin) {
13049
+ const endpointRead = await http.get(`${endpoint(projectRef2)}/endpoint/projection`, { maxJsonBytes: PROJECT_ENDPOINT_RESPONSE_MAX_BYTES, responseTimeoutMs: RELEASE_READ_RESPONSE_TIMEOUT_MS });
13050
+ return projectApiOrigins(endpointRead, projectRef2)?.includes(applicationOrigin) === true;
13051
+ }
12964
13052
  function registerReleaseTools(server, http, options = {}) {
12965
- server.tool("release", "Verified release controls using a Management API credential. Actions: logical_backup_list, logical_backup_create, logical_backup_restore, postgrest_status, postgrest_restart", {
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.", {
12966
13054
  action: withDescription(stringEnum([
12967
13055
  "logical_backup_list",
12968
13056
  "logical_backup_create",
12969
13057
  "logical_backup_restore",
12970
13058
  "postgrest_status",
12971
- "postgrest_restart"
13059
+ "postgrest_restart",
13060
+ "release_canary_fixture_stage_replay",
13061
+ "release_canary_fixture_disable_replay"
12972
13062
  ]), "Release control action"),
12973
13063
  ref: optional(Type.String(), options.projectRef ? "Optional override when not auto-linked" : "Project ref"),
12974
13064
  backup_id: optional(Type.String(), "[logical_backup_restore] Exact verified logical-full backup ID from the selected project inventory"),
12975
13065
  expected_sha256: optional(Type.String(), "[logical_backup_restore] Exact lowercase SHA-256 from the selected project inventory"),
12976
- restore_confirmation: optional(Type.String(), "[logical_backup_restore] Exact RESTORE_PROJECT:<ref>:<backup_id>:<sha256> confirmation")
12977
- }, async ({ action, ref, backup_id, expected_sha256, restore_confirmation }) => {
13066
+ restore_confirmation: optional(Type.String(), "[logical_backup_restore] Exact RESTORE_PROJECT:<ref>:<backup_id>:<sha256> confirmation"),
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 }) => {
12978
13073
  const projectRef2 = typeof ref === "string" && ref || options.projectRef;
12979
13074
  if (!projectRef2)
12980
13075
  throw new Error("'ref' is required for release controls");
@@ -13045,6 +13140,57 @@ function registerReleaseTools(server, http, options = {}) {
13045
13140
  postgrest: read2.status
13046
13141
  });
13047
13142
  }
13143
+ if (action === "release_canary_fixture_stage_replay") {
13144
+ if (!options.applicationHttp || !options.applicationOrigin) {
13145
+ throw new Error("release_canary_fixture_stage_replay requires SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY");
13146
+ }
13147
+ const request = releaseCanaryStageInput(subject, request_id);
13148
+ if (!await applicationOriginMatches(http, projectRef2, options.applicationOrigin)) {
13149
+ return releaseControlFailure("release.release_canary.fixture_stage_replay", "INVALID_RESPONSE", null);
13150
+ }
13151
+ const response = await options.applicationHttp.postReleaseMutation("/rest/v1/rpc/fa_release_canary_fixture_stage", request);
13152
+ if (!response.ok || response.status !== 200) {
13153
+ return mutationFailure("release.release_canary.fixture_stage_replay", response);
13154
+ }
13155
+ const receipt = releaseCanaryStageReceipt(response.data);
13156
+ if (!receipt || !await applicationOriginMatches(http, projectRef2, options.applicationOrigin)) {
13157
+ return releaseControlFailure("release.release_canary.fixture_stage_replay", "OUTCOME_UNKNOWN", response.status);
13158
+ }
13159
+ return releaseControlSuccess("release.release_canary.fixture_stage_replay", {
13160
+ project_ref: projectRef2,
13161
+ receipt
13162
+ });
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
+ }
13048
13194
  if (action !== "postgrest_restart")
13049
13195
  throw new Error("Unknown release control action");
13050
13196
  const mutation = await http.postReleaseMutation(`${endpoint(projectRef2)}/services/postgrest/restart`);
@@ -13065,7 +13211,7 @@ function registerReleaseTools(server, http, options = {}) {
13065
13211
  // package.json
13066
13212
  var package_default = {
13067
13213
  name: "@supacloud/cli",
13068
- version: "0.28.1",
13214
+ version: "0.30.0",
13069
13215
  description: "Project-scoped CLI for SupaCloud users",
13070
13216
  type: "module",
13071
13217
  main: "./dist/index.js",
@@ -13294,7 +13440,8 @@ DEFAULT CONTEXT
13294
13440
  Without a selector or project variables, runs use the current project's legacy .env.
13295
13441
  Application status accepts SUPABASE_URL + SUPABASE_SERVICE_ROLE_KEY.
13296
13442
  Management-backed project commands require SUPACLOUD_API_URL +
13297
- SUPACLOUD_API_TOKEN. These credential scopes are never mixed.
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.
13298
13445
  SUPACLOUD_PROJECT_REF is required when it cannot be inferred from <ref>.api.*.
13299
13446
 
13300
13447
  SUPACLOUD_READ_ONLY=true blocks remote writes. Production writes require an
@@ -13315,6 +13462,8 @@ EXAMPLES
13315
13462
  ${preferredCommand} release logical_backup_restore --ref abc123 --backup_id <backup_id> --expected_sha256 <sha256> --restore_confirmation RESTORE_PROJECT:abc123:<backup_id>:<sha256>
13316
13463
  ${preferredCommand} release postgrest_status --ref abc123
13317
13464
  ${preferredCommand} release postgrest_restart --ref abc123
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>
13318
13467
  ${preferredCommand} queue stats --queue emails
13319
13468
  ${preferredCommand} queue dlq --queue emails --limit 20
13320
13469
  ${preferredCommand} frontend list --ref abc123
@@ -13473,6 +13622,11 @@ function createCliTools(context, confirmProduction) {
13473
13622
  baseUrl: context.apiUrl,
13474
13623
  token: context.apiToken
13475
13624
  });
13625
+ const applicationHttp = context.inferredSupabaseUrl && context.inferredServiceRoleKey ? new HttpTransport({
13626
+ baseUrl: context.inferredSupabaseUrl,
13627
+ token: context.inferredServiceRoleKey,
13628
+ apiKey: context.inferredServiceRoleKey
13629
+ }) : undefined;
13476
13630
  const assign = (extra) => Object.assign(tools, extra);
13477
13631
  assign(captureTools((server) => registerUserProjectCliTools(server, http, {
13478
13632
  projectRef: context.projectRef || undefined
@@ -13494,7 +13648,9 @@ function createCliTools(context, confirmProduction) {
13494
13648
  })));
13495
13649
  assign(captureTools((server) => registerMutationTools(server, http)));
13496
13650
  assign(captureTools((server) => registerReleaseTools(server, http, {
13497
- projectRef: context.projectRef || undefined
13651
+ projectRef: context.projectRef || undefined,
13652
+ applicationHttp,
13653
+ applicationOrigin: context.inferredSupabaseUrl || undefined
13498
13654
  })));
13499
13655
  assign(captureTools((server) => registerFrontendTools(server, http)));
13500
13656
  assign(captureTools((server) => registerGatewayTools(server, http, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supacloud/cli",
3
- "version": "0.28.1",
3
+ "version": "0.30.0",
4
4
  "description": "Project-scoped CLI for SupaCloud users",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -15,6 +15,8 @@ Load this reference when selecting a command surface or when a user asks an AI t
15
15
  | Generate migration from local schema changes | `supabase db_diff` | Review generated SQL before use |
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
+ | 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 |
18
20
  | Preview/apply migrations remotely | `supabase push` | Always dry-run first; production needs explicit approval |
19
21
  | Mark proven-equivalent historical migrations as applied | `database baseline_migrations` | Dry-run, schema-equivalence proof, backup, explicit approval |
20
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 |
@@ -39,6 +41,7 @@ until a project-scoped context is resolved.
39
41
  - `secrets`: project secret management; never print values after write.
40
42
  - `queue`, `task_events`, `diagnostics`: asynchronous workload operations and bounded diagnostics.
41
43
  - `gateway`: project route/config/rebuild operations.
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`.
42
45
  - `ai`: inspect or install this packaged Skill.
43
46
 
44
47
  ## Safe inspection pattern