@supacloud/cli 0.28.1 → 0.29.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,8 @@ 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>
141
145
  ```
142
146
 
143
147
  Backup creation reports success only after the CLI verifies exactly one new
@@ -158,6 +162,15 @@ receipt and reads back `desired=running`, `actual=running`, and
158
162
  `health=healthy`. Both mutating controls follow the normal production
159
163
  confirmation and read-only protections.
160
164
 
165
+ `release_canary_fixture_stage_replay` performs one non-retried, response-bounded
166
+ call to the fixed `fa_release_canary_fixture_stage` PostgREST RPC with both
167
+ bearer and `apikey` service-role headers. It accepts only canonical subject and
168
+ request UUIDs, requires a strict `staged` and `idempotent=true` four-field
169
+ receipt, and emits only that safe projection. Before and after the call, the CLI
170
+ reads the selected project's authoritative endpoint projection and requires the
171
+ configured application origin to match its API origin or alias. Production
172
+ confirmation is mandatory.
173
+
161
174
  The legacy `.env` fallback is unclassified and therefore does not enable the
162
175
  production confirmation gate. Production automation must select a `prod` or
163
176
  `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"]
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,9 @@ 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"]);
12832
12847
  function isRecord3(value) {
12833
12848
  return value !== null && typeof value === "object" && !Array.isArray(value);
12834
12849
  }
@@ -12961,20 +12976,44 @@ function readPostgrestFailure(operation, read) {
12961
12976
  function isRestartReceipt(value) {
12962
12977
  return isRecord3(value) && value.service === "postgrest" && value.action === "restart" && value.success === true;
12963
12978
  }
12979
+ function releaseCanaryStageInput(subject, requestId) {
12980
+ if (typeof subject !== "string" || !UUID.test(subject))
12981
+ throw new Error("'subject' must be a canonical UUID");
12982
+ if (typeof requestId !== "string" || !UUID.test(requestId))
12983
+ throw new Error("'request_id' must be a canonical UUID");
12984
+ return { p_subject: subject, p_request_id: requestId };
12985
+ }
12986
+ function releaseCanaryStageReceipt(value) {
12987
+ 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)
12988
+ return null;
12989
+ return {
12990
+ fixtureId: value.fixtureId,
12991
+ tenantKey: value.tenantKey,
12992
+ state: "staged",
12993
+ idempotent: true
12994
+ };
12995
+ }
12996
+ async function applicationOriginMatches(http, projectRef2, applicationOrigin) {
12997
+ const endpointRead = await http.get(`${endpoint(projectRef2)}/endpoint/projection`, { maxResponseBytes: PROJECT_ENDPOINT_RESPONSE_MAX_BYTES });
12998
+ return projectApiOrigins(endpointRead, projectRef2)?.includes(applicationOrigin) === true;
12999
+ }
12964
13000
  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", {
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.", {
12966
13002
  action: withDescription(stringEnum([
12967
13003
  "logical_backup_list",
12968
13004
  "logical_backup_create",
12969
13005
  "logical_backup_restore",
12970
13006
  "postgrest_status",
12971
- "postgrest_restart"
13007
+ "postgrest_restart",
13008
+ "release_canary_fixture_stage_replay"
12972
13009
  ]), "Release control action"),
12973
13010
  ref: optional(Type.String(), options.projectRef ? "Optional override when not auto-linked" : "Project ref"),
12974
13011
  backup_id: optional(Type.String(), "[logical_backup_restore] Exact verified logical-full backup ID from the selected project inventory"),
12975
13012
  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 }) => {
13013
+ 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 }) => {
12978
13017
  const projectRef2 = typeof ref === "string" && ref || options.projectRef;
12979
13018
  if (!projectRef2)
12980
13019
  throw new Error("'ref' is required for release controls");
@@ -13045,6 +13084,27 @@ function registerReleaseTools(server, http, options = {}) {
13045
13084
  postgrest: read2.status
13046
13085
  });
13047
13086
  }
13087
+ if (action === "release_canary_fixture_stage_replay") {
13088
+ if (!options.applicationHttp || !options.applicationOrigin) {
13089
+ throw new Error("release_canary_fixture_stage_replay requires SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY");
13090
+ }
13091
+ const request = releaseCanaryStageInput(subject, request_id);
13092
+ if (!await applicationOriginMatches(http, projectRef2, options.applicationOrigin)) {
13093
+ return releaseControlFailure("release.release_canary.fixture_stage_replay", "INVALID_RESPONSE", null);
13094
+ }
13095
+ const response = await options.applicationHttp.postReleaseMutation("/rest/v1/rpc/fa_release_canary_fixture_stage", request);
13096
+ if (!response.ok || response.status !== 200) {
13097
+ return mutationFailure("release.release_canary.fixture_stage_replay", response);
13098
+ }
13099
+ const receipt = releaseCanaryStageReceipt(response.data);
13100
+ if (!receipt || !await applicationOriginMatches(http, projectRef2, options.applicationOrigin)) {
13101
+ return releaseControlFailure("release.release_canary.fixture_stage_replay", "OUTCOME_UNKNOWN", response.status);
13102
+ }
13103
+ return releaseControlSuccess("release.release_canary.fixture_stage_replay", {
13104
+ project_ref: projectRef2,
13105
+ receipt
13106
+ });
13107
+ }
13048
13108
  if (action !== "postgrest_restart")
13049
13109
  throw new Error("Unknown release control action");
13050
13110
  const mutation = await http.postReleaseMutation(`${endpoint(projectRef2)}/services/postgrest/restart`);
@@ -13065,7 +13125,7 @@ function registerReleaseTools(server, http, options = {}) {
13065
13125
  // package.json
13066
13126
  var package_default = {
13067
13127
  name: "@supacloud/cli",
13068
- version: "0.28.1",
13128
+ version: "0.29.0",
13069
13129
  description: "Project-scoped CLI for SupaCloud users",
13070
13130
  type: "module",
13071
13131
  main: "./dist/index.js",
@@ -13294,7 +13354,8 @@ DEFAULT CONTEXT
13294
13354
  Without a selector or project variables, runs use the current project's legacy .env.
13295
13355
  Application status accepts SUPABASE_URL + SUPABASE_SERVICE_ROLE_KEY.
13296
13356
  Management-backed project commands require SUPACLOUD_API_URL +
13297
- SUPACLOUD_API_TOKEN. These credential scopes are never mixed.
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.
13298
13359
  SUPACLOUD_PROJECT_REF is required when it cannot be inferred from <ref>.api.*.
13299
13360
 
13300
13361
  SUPACLOUD_READ_ONLY=true blocks remote writes. Production writes require an
@@ -13315,6 +13376,7 @@ EXAMPLES
13315
13376
  ${preferredCommand} release logical_backup_restore --ref abc123 --backup_id <backup_id> --expected_sha256 <sha256> --restore_confirmation RESTORE_PROJECT:abc123:<backup_id>:<sha256>
13316
13377
  ${preferredCommand} release postgrest_status --ref abc123
13317
13378
  ${preferredCommand} release postgrest_restart --ref abc123
13379
+ ${preferredCommand} release release_canary_fixture_stage_replay --ref abc123 --subject <uuid> --request_id <uuid>
13318
13380
  ${preferredCommand} queue stats --queue emails
13319
13381
  ${preferredCommand} queue dlq --queue emails --limit 20
13320
13382
  ${preferredCommand} frontend list --ref abc123
@@ -13473,6 +13535,11 @@ function createCliTools(context, confirmProduction) {
13473
13535
  baseUrl: context.apiUrl,
13474
13536
  token: context.apiToken
13475
13537
  });
13538
+ const applicationHttp = context.inferredSupabaseUrl && context.inferredServiceRoleKey ? new HttpTransport({
13539
+ baseUrl: context.inferredSupabaseUrl,
13540
+ token: context.inferredServiceRoleKey,
13541
+ apiKey: context.inferredServiceRoleKey
13542
+ }) : undefined;
13476
13543
  const assign = (extra) => Object.assign(tools, extra);
13477
13544
  assign(captureTools((server) => registerUserProjectCliTools(server, http, {
13478
13545
  projectRef: context.projectRef || undefined
@@ -13494,7 +13561,9 @@ function createCliTools(context, confirmProduction) {
13494
13561
  })));
13495
13562
  assign(captureTools((server) => registerMutationTools(server, http)));
13496
13563
  assign(captureTools((server) => registerReleaseTools(server, http, {
13497
- projectRef: context.projectRef || undefined
13564
+ projectRef: context.projectRef || undefined,
13565
+ applicationHttp,
13566
+ applicationOrigin: context.inferredSupabaseUrl || undefined
13498
13567
  })));
13499
13568
  assign(captureTools((server) => registerFrontendTools(server, http)));
13500
13569
  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.29.0",
4
4
  "description": "Project-scoped CLI for SupaCloud users",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -15,6 +15,7 @@ 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 |
18
19
  | Preview/apply migrations remotely | `supabase push` | Always dry-run first; production needs explicit approval |
19
20
  | Mark proven-equivalent historical migrations as applied | `database baseline_migrations` | Dry-run, schema-equivalence proof, backup, explicit approval |
20
21
  | 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 +40,7 @@ until a project-scoped context is resolved.
39
40
  - `secrets`: project secret management; never print values after write.
40
41
  - `queue`, `task_events`, `diagnostics`: asynchronous workload operations and bounded diagnostics.
41
42
  - `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`.
42
44
  - `ai`: inspect or install this packaged Skill.
43
45
 
44
46
  ## Safe inspection pattern