@polardbx/pxbaas 0.1.0-beta.3 → 0.1.0-beta.4

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.
Files changed (103) hide show
  1. package/README.md +40 -8
  2. package/dist/app/local-index.d.ts +70 -0
  3. package/dist/app/local-index.d.ts.map +1 -0
  4. package/dist/app/local-index.js +140 -0
  5. package/dist/app/local-index.js.map +1 -0
  6. package/dist/app/preflight.d.ts +12 -0
  7. package/dist/app/preflight.d.ts.map +1 -1
  8. package/dist/app/preflight.js +30 -17
  9. package/dist/app/preflight.js.map +1 -1
  10. package/dist/cli/root.d.ts.map +1 -1
  11. package/dist/cli/root.js +3 -0
  12. package/dist/cli/root.js.map +1 -1
  13. package/dist/commands/app/index.d.ts.map +1 -1
  14. package/dist/commands/app/index.js +398 -46
  15. package/dist/commands/app/index.js.map +1 -1
  16. package/dist/commands/completion/index.d.ts.map +1 -1
  17. package/dist/commands/completion/index.js +5 -1
  18. package/dist/commands/completion/index.js.map +1 -1
  19. package/dist/commands/data/index.d.ts +52 -0
  20. package/dist/commands/data/index.d.ts.map +1 -1
  21. package/dist/commands/data/index.js +201 -39
  22. package/dist/commands/data/index.js.map +1 -1
  23. package/dist/commands/db/index.d.ts +14 -0
  24. package/dist/commands/db/index.d.ts.map +1 -1
  25. package/dist/commands/db/index.js +126 -28
  26. package/dist/commands/db/index.js.map +1 -1
  27. package/dist/commands/functions/new.d.ts +4 -0
  28. package/dist/commands/functions/new.d.ts.map +1 -1
  29. package/dist/commands/functions/new.js +24 -9
  30. package/dist/commands/functions/new.js.map +1 -1
  31. package/dist/commands/login/index.d.ts.map +1 -1
  32. package/dist/commands/login/index.js +15 -30
  33. package/dist/commands/login/index.js.map +1 -1
  34. package/dist/commands/profile/import-env.d.ts +7 -2
  35. package/dist/commands/profile/import-env.d.ts.map +1 -1
  36. package/dist/commands/profile/import-env.js +141 -16
  37. package/dist/commands/profile/import-env.js.map +1 -1
  38. package/dist/commands/profile/index.d.ts.map +1 -1
  39. package/dist/commands/profile/index.js +13 -1
  40. package/dist/commands/profile/index.js.map +1 -1
  41. package/dist/commands/storage/index.d.ts.map +1 -1
  42. package/dist/commands/storage/index.js +209 -7
  43. package/dist/commands/storage/index.js.map +1 -1
  44. package/dist/commands/tenant/index.d.ts +19 -0
  45. package/dist/commands/tenant/index.d.ts.map +1 -0
  46. package/dist/commands/tenant/index.js +662 -0
  47. package/dist/commands/tenant/index.js.map +1 -0
  48. package/dist/commands/whoami/index.js +19 -0
  49. package/dist/commands/whoami/index.js.map +1 -1
  50. package/dist/credentials/credential-store.d.ts +30 -1
  51. package/dist/credentials/credential-store.d.ts.map +1 -1
  52. package/dist/credentials/credential-store.js +114 -12
  53. package/dist/credentials/credential-store.js.map +1 -1
  54. package/dist/credentials/env-file.d.ts +48 -7
  55. package/dist/credentials/env-file.d.ts.map +1 -1
  56. package/dist/credentials/env-file.js +91 -16
  57. package/dist/credentials/env-file.js.map +1 -1
  58. package/dist/credentials/jwt-introspect.d.ts +7 -0
  59. package/dist/credentials/jwt-introspect.d.ts.map +1 -1
  60. package/dist/credentials/jwt-introspect.js.map +1 -1
  61. package/dist/credentials/masker.d.ts +3 -0
  62. package/dist/credentials/masker.d.ts.map +1 -1
  63. package/dist/credentials/masker.js +3 -0
  64. package/dist/credentials/masker.js.map +1 -1
  65. package/dist/credentials/profile.d.ts +12 -0
  66. package/dist/credentials/profile.d.ts.map +1 -1
  67. package/dist/credentials/profile.js +23 -0
  68. package/dist/credentials/profile.js.map +1 -1
  69. package/dist/credentials/tty-guard.d.ts +10 -0
  70. package/dist/credentials/tty-guard.d.ts.map +1 -1
  71. package/dist/credentials/tty-guard.js +32 -0
  72. package/dist/credentials/tty-guard.js.map +1 -1
  73. package/dist/main.js +21 -2
  74. package/dist/main.js.map +1 -1
  75. package/dist/tenant/bundle.d.ts +66 -0
  76. package/dist/tenant/bundle.d.ts.map +1 -0
  77. package/dist/tenant/bundle.js +132 -0
  78. package/dist/tenant/bundle.js.map +1 -0
  79. package/dist/tenant/guide.d.ts +50 -0
  80. package/dist/tenant/guide.d.ts.map +1 -0
  81. package/dist/tenant/guide.js +152 -0
  82. package/dist/tenant/guide.js.map +1 -0
  83. package/dist/tenant/probe.d.ts +65 -0
  84. package/dist/tenant/probe.d.ts.map +1 -0
  85. package/dist/tenant/probe.js +159 -0
  86. package/dist/tenant/probe.js.map +1 -0
  87. package/dist/tenant/verify.d.ts +37 -0
  88. package/dist/tenant/verify.d.ts.map +1 -0
  89. package/dist/tenant/verify.js +132 -0
  90. package/dist/tenant/verify.js.map +1 -0
  91. package/dist/version.d.ts +29 -0
  92. package/dist/version.d.ts.map +1 -0
  93. package/dist/version.js +65 -0
  94. package/dist/version.js.map +1 -0
  95. package/package.json +2 -2
  96. package/skills/pxbaas-app-spec/references/runtime-contract.md +1 -1
  97. package/skills/pxbaas-cli/SKILL.md +14 -5
  98. package/skills/pxbaas-cli/references/auth-and-credentials.md +19 -3
  99. package/skills/pxbaas-cli/references/data-postgrest.md +4 -3
  100. package/skills/pxbaas-cli/references/db-and-meta.md +5 -0
  101. package/skills/pxbaas-cli/references/safety-and-two-phase.md +42 -13
  102. package/skills/pxbaas-cli/references/tenant-admin.md +122 -0
  103. package/skills/pxbaas-cli/references/troubleshooting.md +4 -1
@@ -0,0 +1,132 @@
1
+ /**
2
+ * 建租户后的验证闭环(方案文档 G4)。
3
+ *
4
+ * 平台侧的建租户接口会把「建库 / 建账号 / GRANT / 租户库内 RLS 表 / Realtime 注册」
5
+ * 全部当作**非致命**步骤:任何一步失败只 console.error,HTTP 仍然返回 201
6
+ * (pages/api/platform/tenants/index.ts:265 起)。也就是说"创建成功"并不等于
7
+ * "这个租户可用"。CLI 若在 create 之后直接报成功,管理员就会把平台缺陷当成 CLI 的 bug。
8
+ *
9
+ * 这里用新签发的**租户自己的** service_role key 走一遍租户自助 DDL 通道,逐项确认:
10
+ * 1. SELECT DATABASE() ⇒ 密钥有效、租户处于 active、自助 DDL 通道可用,并拿到真实库名
11
+ * 2. information_schema 计数 ⇒ 租户库确实存在且可内省(0 张表对新租户是正常的)
12
+ * 3. RLS 注册表可读 ⇒ 中心 RLS 元数据对该租户可达(RLS 落库的前置条件)
13
+ * 三条都是只读语句,不改变任何状态。
14
+ */
15
+ import { TenantDdlDriver, TenantDdlError } from '@polardbx/pxbaas-client';
16
+ export async function verifyTenantUsable(input) {
17
+ const driver = new TenantDdlDriver({
18
+ endpoint: input.endpoint,
19
+ serviceRoleKey: input.serviceRoleKey,
20
+ timeout: input.timeout ?? 30_000,
21
+ });
22
+ const steps = [];
23
+ let dbName;
24
+ // 1) 租户自助 DDL 通道 + 密钥有效性
25
+ let channelOk = false;
26
+ try {
27
+ const rows = (await driver.query('SELECT DATABASE() AS db'));
28
+ const value = rows?.[0]?.db;
29
+ dbName = typeof value === 'string' && value ? value : undefined;
30
+ channelOk = Boolean(dbName);
31
+ steps.push({
32
+ name: 'Tenant DDL channel',
33
+ status: channelOk ? 'pass' : 'fail',
34
+ detail: channelOk
35
+ ? `Connected as tenant database "${dbName}"`
36
+ : 'SELECT DATABASE() returned no database name',
37
+ remediation: channelOk
38
+ ? undefined
39
+ : 'Verify the tenant is active and that the key belongs to this tenant (pxbaas doctor --json).',
40
+ });
41
+ }
42
+ catch (error) {
43
+ steps.push({
44
+ name: 'Tenant DDL channel',
45
+ status: 'fail',
46
+ detail: describe(error),
47
+ remediation: 'The tenant database or its account may not have been provisioned. Platform-side tenant creation treats those steps as non-fatal, so a 201 response does not guarantee a usable tenant — ask the operator to check the tenant db, user and GRANT.',
48
+ });
49
+ }
50
+ // 2) 库可内省(存在性代理判据:能查 information_schema 即库已在)
51
+ if (channelOk) {
52
+ try {
53
+ const rows = (await driver.query('SELECT COUNT(*) AS t FROM information_schema.tables WHERE table_schema = DATABASE()'));
54
+ const count = Number(rows?.[0]?.t ?? NaN);
55
+ steps.push({
56
+ name: 'Tenant database introspectable',
57
+ status: Number.isFinite(count) ? 'pass' : 'fail',
58
+ detail: Number.isFinite(count)
59
+ ? `${count} table(s) currently in "${dbName}"`
60
+ : 'Count query returned an unexpected shape',
61
+ });
62
+ }
63
+ catch (error) {
64
+ steps.push({
65
+ name: 'Tenant database introspectable',
66
+ status: 'fail',
67
+ detail: describe(error),
68
+ remediation: 'Create tables with "pxbaas db push <file>" or "pxbaas app db-init <name>".',
69
+ });
70
+ }
71
+ }
72
+ else {
73
+ steps.push({
74
+ name: 'Tenant database introspectable',
75
+ status: 'skip',
76
+ detail: 'Skipped because the DDL channel is not usable.',
77
+ });
78
+ }
79
+ // 3) 中心 RLS 注册表对该租户可达
80
+ if (channelOk) {
81
+ try {
82
+ const tables = await driver.listRlsTables();
83
+ steps.push({
84
+ name: 'RLS registry reachable',
85
+ status: 'pass',
86
+ detail: `${tables.length} RLS-registered table(s) for this tenant`,
87
+ });
88
+ }
89
+ catch (error) {
90
+ steps.push({
91
+ name: 'RLS registry reachable',
92
+ status: 'fail',
93
+ detail: describe(error),
94
+ remediation: 'RLS metadata may not be initialized for this tenant. Register policies through "pxbaas db push --rls db/rls.json" (multi-tenant binds table_schema server-side).',
95
+ });
96
+ }
97
+ }
98
+ else {
99
+ steps.push({ name: 'RLS registry reachable', status: 'skip', detail: 'Skipped (channel unavailable).' });
100
+ }
101
+ // 4) Realtime 注册(平台侧非致命;false 时该租户 WS 握手会 403)
102
+ if (input.realtimeProvisioned === undefined) {
103
+ steps.push({
104
+ name: 'Realtime registration',
105
+ status: 'skip',
106
+ detail: 'Not reported by this endpoint (only the create response carries it).',
107
+ });
108
+ }
109
+ else {
110
+ steps.push({
111
+ name: 'Realtime registration',
112
+ status: input.realtimeProvisioned ? 'pass' : 'fail',
113
+ detail: input.realtimeProvisioned
114
+ ? 'Tenant registered for Realtime'
115
+ : 'Realtime registration failed (platform-side, non-fatal for REST/Auth)',
116
+ remediation: input.realtimeProvisioned
117
+ ? undefined
118
+ : 'WebSocket subscriptions for this tenant will be rejected until it is registered in Realtime; ask the operator to re-run registration.',
119
+ });
120
+ }
121
+ return {
122
+ ok: steps.every((step) => step.status !== 'fail'),
123
+ dbName,
124
+ steps,
125
+ };
126
+ }
127
+ function describe(error) {
128
+ if (error instanceof TenantDdlError)
129
+ return `${error.code}: ${error.message}`;
130
+ return error instanceof Error ? error.message : String(error);
131
+ }
132
+ //# sourceMappingURL=verify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verify.js","sourceRoot":"","sources":["../../src/tenant/verify.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Runtime CLI version resolution.
3
+ *
4
+ * `npm run build` is a plain `tsc --build`, so there is no bundler step that
5
+ * could inline `package.json#version` into the emitted JS — and adding one is
6
+ * out of the question for a build pipeline this simple. Read the manifest from
7
+ * disk instead, walking up from this module so every supported layout works:
8
+ *
9
+ * - source tree : <pkg>/dist/version.js -> <pkg>/package.json
10
+ * - npm install : node_modules/@polardbx/pxbaas/dist/version.js -> .../package.json
11
+ *
12
+ * The walk-up must not stop at the first manifest it finds: in a monorepo the
13
+ * workspace root (`cli/package.json`, version `0.0.0-dev`) is also an ancestor,
14
+ * which is exactly how `--version` used to report a version nobody published.
15
+ * Only the manifest that owns this package (matching `name`, or declaring the
16
+ * `pxbaas` bin) counts.
17
+ *
18
+ * Never throws: an unreadable manifest must not stop `pxbaas --help`.
19
+ */
20
+ export declare const PACKAGE_NAME = "@polardbx/pxbaas";
21
+ /** Reported when no owning manifest can be found (e.g. a single-file bundle). */
22
+ export declare const UNKNOWN_VERSION = "0.0.0-unknown";
23
+ /**
24
+ * The published version of this CLI, or {@link UNKNOWN_VERSION} when it cannot be
25
+ * determined. Synchronous on purpose: commander's `.version()` is wired up at
26
+ * module scope, before any await could run.
27
+ */
28
+ export declare function resolvePackageVersion(startDir?: string): string;
29
+ //# sourceMappingURL=version.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAMH,eAAO,MAAM,YAAY,qBAAqB,CAAC;AAE/C,iFAAiF;AACjF,eAAO,MAAM,eAAe,kBAAkB,CAAC;AA8B/C;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,GAAE,MAAoB,GAAG,MAAM,CAY5E"}
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Runtime CLI version resolution.
3
+ *
4
+ * `npm run build` is a plain `tsc --build`, so there is no bundler step that
5
+ * could inline `package.json#version` into the emitted JS — and adding one is
6
+ * out of the question for a build pipeline this simple. Read the manifest from
7
+ * disk instead, walking up from this module so every supported layout works:
8
+ *
9
+ * - source tree : <pkg>/dist/version.js -> <pkg>/package.json
10
+ * - npm install : node_modules/@polardbx/pxbaas/dist/version.js -> .../package.json
11
+ *
12
+ * The walk-up must not stop at the first manifest it finds: in a monorepo the
13
+ * workspace root (`cli/package.json`, version `0.0.0-dev`) is also an ancestor,
14
+ * which is exactly how `--version` used to report a version nobody published.
15
+ * Only the manifest that owns this package (matching `name`, or declaring the
16
+ * `pxbaas` bin) counts.
17
+ *
18
+ * Never throws: an unreadable manifest must not stop `pxbaas --help`.
19
+ */
20
+ import { readFileSync } from 'node:fs';
21
+ import { dirname, join } from 'node:path';
22
+ import { fileURLToPath } from 'node:url';
23
+ export const PACKAGE_NAME = '@polardbx/pxbaas';
24
+ /** Reported when no owning manifest can be found (e.g. a single-file bundle). */
25
+ export const UNKNOWN_VERSION = '0.0.0-unknown';
26
+ /** How many parent directories to probe before giving up. */
27
+ const MAX_WALK_DEPTH = 8;
28
+ function moduleDir() {
29
+ return dirname(fileURLToPath(import.meta.url));
30
+ }
31
+ function readManifest(dir) {
32
+ try {
33
+ return JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8'));
34
+ }
35
+ catch {
36
+ return null;
37
+ }
38
+ }
39
+ /** True when the manifest belongs to this CLI package rather than an ancestor workspace. */
40
+ function ownsThisPackage(manifest) {
41
+ if (manifest.name === PACKAGE_NAME)
42
+ return true;
43
+ const bin = manifest.bin;
44
+ return typeof bin === 'object' && bin !== null && 'pxbaas' in bin;
45
+ }
46
+ /**
47
+ * The published version of this CLI, or {@link UNKNOWN_VERSION} when it cannot be
48
+ * determined. Synchronous on purpose: commander's `.version()` is wired up at
49
+ * module scope, before any await could run.
50
+ */
51
+ export function resolvePackageVersion(startDir = moduleDir()) {
52
+ let dir = startDir;
53
+ for (let i = 0; i < MAX_WALK_DEPTH; i += 1) {
54
+ const manifest = readManifest(dir);
55
+ if (manifest && ownsThisPackage(manifest)) {
56
+ return manifest.version ?? UNKNOWN_VERSION;
57
+ }
58
+ const parent = dirname(dir);
59
+ if (parent === dir)
60
+ break;
61
+ dir = parent;
62
+ }
63
+ return UNKNOWN_VERSION;
64
+ }
65
+ //# sourceMappingURL=version.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,MAAM,CAAC,MAAM,YAAY,GAAG,kBAAkB,CAAC;AAE/C,iFAAiF;AACjF,MAAM,CAAC,MAAM,eAAe,GAAG,eAAe,CAAC;AAE/C,6DAA6D;AAC7D,MAAM,cAAc,GAAG,CAAC,CAAC;AAQzB,SAAS,SAAS;IAChB,OAAO,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AACjD,CAAC;AAED,SAAS,YAAY,CAAC,GAAW;IAC/B,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,EAAE,MAAM,CAAC,CAAoB,CAAC;IACxF,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,4FAA4F;AAC5F,SAAS,eAAe,CAAC,QAAyB;IAChD,IAAI,QAAQ,CAAC,IAAI,KAAK,YAAY;QAAE,OAAO,IAAI,CAAC;IAChD,MAAM,GAAG,GAAG,QAAQ,CAAC,GAAG,CAAC;IACzB,OAAO,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI,IAAI,QAAQ,IAAI,GAAG,CAAC;AACpE,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CAAC,WAAmB,SAAS,EAAE;IAClE,IAAI,GAAG,GAAG,QAAQ,CAAC;IACnB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,cAAc,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QAC3C,MAAM,QAAQ,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;QACnC,IAAI,QAAQ,IAAI,eAAe,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC1C,OAAO,QAAQ,CAAC,OAAO,IAAI,eAAe,CAAC;QAC7C,CAAC;QACD,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;QAC5B,IAAI,MAAM,KAAK,GAAG;YAAE,MAAM;QAC1B,GAAG,GAAG,MAAM,CAAC;IACf,CAAC;IACD,OAAO,eAAe,CAAC;AACzB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polardbx/pxbaas",
3
- "version": "0.1.0-beta.3",
3
+ "version": "0.1.0-beta.4",
4
4
  "description": "PolarDB-X BaaS CLI application",
5
5
  "keywords": [
6
6
  "polardbx",
@@ -35,7 +35,7 @@
35
35
  "inquirer": "^9.2.0",
36
36
  "jsonwebtoken": "^9.0.0",
37
37
  "keytar": "^7.9.0",
38
- "@polardbx/pxbaas-client": "0.1.0-beta.3"
38
+ "@polardbx/pxbaas-client": "0.1.0-beta.4"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@types/inquirer": "^9.0.0",
@@ -139,7 +139,7 @@ For large frontends regardless of mode, host heavy static assets on Storage/CDN
139
139
 
140
140
  - The backend entrypoint must call `Deno.serve`; do not bind a port.
141
141
  - Node APIs (`node:*`, `require`, `process`, and Node server APIs) are forbidden.
142
- - Runtime `Deno.env` access is limited to the platform public three-key whitelist `SUPABASE_URL` / `SUPABASE_ANON_KEY` / `SUPABASE_INTERNAL_URL` (injected into App candidate workers per edge-runtime `app-env.ts`, MR #30497903). Reading any other variable or `Deno.env.toObject()`, non-empty Secrets, and any egress policy other than `deny` are unsupported in the first release (`deno-env-not-allowed`, `custom-secrets-not-supported`). `SUPABASE_URL` is render-only — never fetch through it; use `SUPABASE_INTERNAL_URL` as the data-plane base.
142
+ - Runtime `Deno.env` access is limited to the platform public three-key whitelist `SUPABASE_URL` / `SUPABASE_ANON_KEY` / `SUPABASE_INTERNAL_URL` (injected into App candidate workers per edge-runtime `app-env.ts`, MR #30497903). Reading any other variable or `Deno.env.toObject()`, non-empty Secrets, and any egress policy other than `deny` are unsupported in the first release (`deno-env-not-allowed`, `custom-secrets-not-supported`). `SUPABASE_URL` is render-only — never fetch through it; use `SUPABASE_INTERNAL_URL` as the data-plane base. These three names are platform-reserved: the platform value always wins over a user Secret of the same name, so never declare them in `backend.secrets`.
143
143
  - `egressPolicy: "deny"` means **no public egress**, not "no network": the sandbox egress filter blocks public origins and arbitrary outbound, but the platform-internal base (`SUPABASE_INTERNAL_URL`) stays reachable, so the worker MAY fetch the instance's own REST/Auth/Storage through it. Only `deny` is accepted in the first release (any other value is a blocker).
144
144
  - Arbitrary `http:`, `https:`, and `jsr:` imports are blocked. The only approved `npm:` packages are Hono (exact capability version) plus the curated SSR renderers reported by capability `ssrLibs` (exact `npm:<name>@<version>`, optional `/subpath`); anything else fails spec-lint (`ssr-lib-not-allowed`, or `no-remote-import` when the instance reports no `ssrLibs`).
145
145
  - Scaffolded Hono source uses `npm:hono@{{HONO_VERSION}}`; candidate staging substitutes the exact version without modifying local source.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: pxbaas-cli
3
- description: Use the PolarDB-X BaaS CLI (command `pxbaas`) to consume the data plane from the command line — run SQL, inspect tables/schema, query and edit table data (PostgREST), deploy/list/delete Edge Functions, manage storage objects and auth users, generate types, claim a free disposable instance (`pxbaas provision`), and scaffold/deploy/publish/rollback Webapps (`pxbaas app`). Use when the user works with a PolarDB-X BaaS / MySQL-Supabase instance through `pxbaas`, has no instance yet and wants to provision one, wants to deploy a demo webapp, or mentions the pxbaas CLI, querying BaaS tables/data, running SQL, edge function deploy, storage objects, auth users, gen types, or pxbaas login/doctor/401/403 errors. Data-plane consumption + self-service provisioning (not platform deployment or tenant lifecycle). Agent rules — never run `pxbaas login` (human-only TTY), but `pxbaas provision` and `pxbaas profile import <env-file>` (bind a single-tenant instance from a `.env` file) ARE agent-runnable; always pass `--json`; confirm destructive operations with the user before executing.
3
+ description: Use the PolarDB-X BaaS CLI (`pxbaas`) to work with a PolarDB-X BaaS / MySQL-Supabase instance: run SQL and inspect tables/schema (`db`), read/write rows PostgREST-style (`data`, `rest`), deploy/list/delete Edge Functions (`functions`), scaffold-deploy-publish Webapps and share links (`app`), storage objects, auth users, type generation, claim a free disposable instance (`provision`), and - for instance operators - administer tenants and hand out per-tenant keys (`tenant probe/ls/create/keys/repair-keys`, access bundles). Trigger on pxbaas commands, BaaS SQL or table questions, function/webapp deploy, storage or users, gen types, provisioning, multi-tenant login, and 401/403 errors. Agent rules: never run `pxbaas login` (TTY-only); `provision` and `profile import <env-file>` are agent-runnable; always pass `--json`; never print or request plaintext keys; never create a tenant without explicit user confirmation.
4
4
  ---
5
5
 
6
6
  # pxbaas-cli
@@ -54,7 +54,7 @@ All requests go through a single Kong gateway (port **8000**). Key path prefixes
54
54
  | **1** | **NEVER run `pxbaas login`** | It requires an interactive TTY; agents cannot complete it. Credentials are provisioned by a human. If not logged in, **ask the user to run `pxbaas login`** — do not attempt it and do not ask for plaintext keys. **Exception:** `pxbaas provision` IS agent-runnable (keys come from Zero over TLS and are stored without being shown) — see `references/provision.md`. |
55
55
  | **2** | **Pass `--json` for anything you parse** | Status queries, remote operations, and any command whose result you read MUST use `--json` (parse JSON, never human-readable text). Pure local scaffolding, shell completion, and file-output commands (e.g. `gen types --output <file>`) are human-facing — `--json` is optional there. |
56
56
  | **3** | **You cannot see plaintext keys** | Output shows only fingerprints (sha256 first 8 + last 4). Never expect, echo, or reconstruct a plaintext anon/service key. |
57
- | **4** | **Confirm destructive ops first** | ⚠️ The CLI currently has **no built-in confirmation gate**. Before any write/delete/DDL, YOU must restate the target and impact and get the user's explicit consent. See `references/safety-and-two-phase.md`. |
57
+ | **4** | **Confirm destructive ops first** | Some commands gate themselves (`--dry-run`/`--yes` on `db push`, `app db-init`, `data delete`; `--confirm` on `app remove`, `app destroy`, `instance release`, `app share revoke`, `skill remove`, `tenant create`, `tenant repair-keys`). **Many do not** — `db create-table` / `db add-column`, `functions deploy` / `delete`, `storage rm` / `mv` / `cp` overwrite, `auth users delete`, `rest` writes execute immediately. For those, YOU are the gate: restate target, impact and reversibility, then get explicit consent. See `references/safety-and-two-phase.md`. |
58
58
  | **5** | **Respect role & tenant isolation** | Prefer `anon` for reads; explain when `service_role` is needed (it bypasses RLS). Never mix credentials across profiles. |
59
59
 
60
60
  ## First-run discovery
@@ -76,7 +76,7 @@ Legend — **R** = read/safe · **W** = write · **D** = destructive (consent re
76
76
  | Command | Default role | R/W/D | Details |
77
77
  |---|---|---|---|
78
78
  | `login` (human TTY only) | — | special | auth-and-credentials.md |
79
- | `profile import <env-file>` (agent-runnable, single-tenant) | — | W (local) | auth-and-credentials.md |
79
+ | `profile import <env-file>` (agent-runnable; single-tenant, or multi-tenant when the file carries `TENANT_*` keys) | — | W (local) | auth-and-credentials.md |
80
80
  | `logout` | — | W (local) | auth-and-credentials.md |
81
81
  | `whoami` | — | R | auth-and-credentials.md |
82
82
  | `status` | — | R | auth-and-credentials.md |
@@ -109,6 +109,11 @@ Legend — **R** = read/safe · **W** = write · **D** = destructive (consent re
109
109
  | `auth users delete <userId>` | service_role | **D** | auth-and-credentials.md |
110
110
  | `auth users invite <email>` | service_role | W | auth-and-credentials.md |
111
111
  | `completion <shell>` | — | R (local) | (this file) |
112
+ | `tenant login` / `tenant logout` | dashboard Basic | W (local) | tenant-admin.md |
113
+ | `tenant probe` / `tenant ls` / `tenant handoffs` | dashboard Basic | R | tenant-admin.md |
114
+ | `tenant create [id]` | dashboard Basic | **D/W** (provisions db, account, GRANT, bucket, quota) | tenant-admin.md |
115
+ | `tenant keys <id>` | dashboard Basic | W (local; writes keyring or 0600 bundle) | tenant-admin.md |
116
+ | `tenant repair-keys` | dashboard Basic | W (`--confirm`) | tenant-admin.md |
112
117
  | `provision` (agent-runnable) | — | W (local+remote) | provision.md |
113
118
  | `instance list` / `instance current` | — | R | provision.md |
114
119
  | `instance credentials <id>` | — | R (`--reveal` for dashboard pw) | provision.md |
@@ -122,7 +127,7 @@ Legend — **R** = read/safe · **W** = write · **D** = destructive (consent re
122
127
  | `app remove <name>` | service_role | **D** (`--confirm`; legacy `spa/api/mpa` only) | app-deploy.md |
123
128
  | `app destroy <name>` | service_role | **D** (two-phase `--confirm`; webapp only; deletes edge funcs + mount, **keeps DB tables**) | app-deploy.md |
124
129
 
125
- For provisioning / multi-instance selection see **provision.md**; for the `pxbaas app` commands see **app-deploy.md**. For how to **write** edge-function webapp code (Deno runtime contract, guardrails, templates, raw deploy paths), use the companion **`pxbaas-app-spec` skill**. For the live platform API beyond the CLI (all endpoints, multi-tenant rules, error codes, limitations), see **api-reference.md** (`GET ${endpoint}/docs`).
130
+ For provisioning / multi-instance selection see **provision.md**; for the `pxbaas app` commands see **app-deploy.md**; for tenant provisioning and handing per-tenant keys to end users see **tenant-admin.md** (operator-only surface). For how to **write** edge-function webapp code (Deno runtime contract, guardrails, templates, raw deploy paths), use the companion **`pxbaas-app-spec` skill**. For the live platform API beyond the CLI (all endpoints, multi-tenant rules, error codes, limitations), see **api-reference.md** (`GET ${endpoint}/docs`).
126
131
 
127
132
  ## Natural language → command
128
133
 
@@ -142,13 +147,15 @@ For provisioning / multi-instance selection see **provision.md**; for the `pxbaa
142
147
  "give me a shareable demo URL" → pxbaas app url <name> --json
143
148
  "destroy/delete this webapp" → confirm first → pxbaas app destroy <name> --confirm --json (removes edge funcs + mount; DB tables kept)
144
149
  "can't connect / error" → pxbaas doctor --json; if credentials missing, ask user to run pxbaas login
150
+ "env says no tenant-level keys" → read the guidance it prints; if the user operates the instance: pxbaas tenant login → tenant ls → tenant keys <id> --as-profile (never create a tenant without asking)
151
+ "give this tenant to a new user" → pxbaas tenant keys <id> --bundle ./<id>.pxbaas.env, then send the printed handoff note (contains fingerprints, not keys)
145
152
  ```
146
153
 
147
154
  ## What NOT to do
148
155
 
149
156
  - **NEVER run `pxbaas login`** — it needs an interactive TTY. Ask the user to log in instead.
150
157
  - **NEVER expect or echo plaintext keys** — output is fingerprint-only; never ask the user for a raw key to build a command.
151
- - **NEVER run `data delete` / `storage rm` / `functions delete` / DDL without previewing first** and getting explicit user consent (no built-in gate exists).
158
+ - **NEVER run `storage rm` / `functions delete` / `db create-table` / `db add-column` / `rest` writes / `auth users delete` without previewing first** and getting explicit user consent — those have **no** gate. `data delete`, `db push`, `app db-init`, `app destroy`, `instance release`, `tenant create` and `tenant repair-keys` do gate themselves; satisfy their `--yes`/`--confirm` only **after** the user agreed.
152
159
  - **NEVER write PostgreSQL SQL in `db query`** — the backend is MySQL/PolarDB-X (use `CAST(x AS t)`, backticks, `JSON_EXTRACT`, `UUID()`, not `::`, `"col"`, `->>`, `gen_random_uuid()`).
153
160
  - **NEVER assume an API listed in `/docs` is available via the CLI** — `/docs` covers the whole platform including admin APIs (PG-Meta, Management API) and tenant lifecycle; the CLI wraps only the customer data-plane subset in the command map above.
154
161
  - **NEVER mix credentials across profiles** or target another tenant; the CLI enforces tenant isolation.
@@ -157,6 +164,8 @@ For provisioning / multi-instance selection see **provision.md**; for the `pxbaa
157
164
  - **NEVER parse human-readable output** — always use `--json`.
158
165
  - **NEVER loop-retry Zero capacity/rate errors** (`429 SUPABASE_LIMIT_REACHED` / `503 SUPABASE_POOL_EXHAUSTED`) — stop, inform the user, offer `pxbaas instance release`; retry at most once for pool exhaustion.
159
166
  - **NEVER expect plaintext keys from `pxbaas provision`** — it stores them in the keyring and shows only fingerprints (this is why it is agent-runnable, unlike `pxbaas login`).
167
+ - **NEVER cat, echo, commit or transmit a tenant access bundle** (`*.pxbaas.env`) — it holds plaintext, ~10-year, non-revocable keys. The CLI only writes them to a `0600` file or the keyring; distributing the file is the operator's decision, made over a secure channel.
168
+ - **NEVER create a tenant as a workaround for a missing key.** Run `tenant ls` first: most "no key" cases are an existing tenant whose key was never handed over. A create that collides with a soft-deleted id resurrects it and regenerates that tenant's keys — the CLI refuses this without `--reactivate`.
160
169
  - **NEVER upload frontend source, `.env` files, source maps, `node_modules`, Service Workers, or binary assets as a Webapp artifact.** Build first; upload only the bounded static output. Put large/binary assets on Storage/CDN (see app-deploy.md).
161
170
 
162
171
  ## Global flags
@@ -1,6 +1,6 @@
1
1
  # Credentials, Environment & User Management
2
2
 
3
- Covers: `login` (human-only) · `profile import` (agent-runnable, env-file) · `logout` · `whoami` · `status` · `doctor` · `profile` · `auth users`.
3
+ Covers: `login` (human-only) · `profile import` (agent-runnable, env-file) · `logout` · `whoami` · `status` · `doctor` · `profile` · `auth users` · `tenant login` / `tenant logout` (operator credentials; see tenant-admin.md).
4
4
 
5
5
  ## login — agents must NEVER run this
6
6
 
@@ -13,7 +13,7 @@ pxbaas login --endpoint <url> --tenant-id <id> --anon-key-stdin --service-key-st
13
13
  - Credentials are stored in the OS keyring (keytar), falling back to a `0600` file when no keyring is available.
14
14
  - If another command reports `No active profile. Use "pxbaas login" ...` → **ask the user to log in manually**, OR — if a Supabase/BaaS `.env` file for the instance is available — bind it non-interactively with `pxbaas profile import <env-file>` (see below; that one IS agent-runnable).
15
15
 
16
- ## profile import — bind a single-tenant instance from a `.env` file (agents MAY run this)
16
+ ## profile import — bind an instance from a `.env` file (agents MAY run this)
17
17
 
18
18
  ```bash
19
19
  pxbaas profile import <env-file> --name <profile> [--intranet] --json
@@ -22,7 +22,23 @@ pxbaas profile import <env-file> --name <profile> [--intranet] --json
22
22
  - Reads `SUPABASE_URL` (fallback `SUPABASE_PUBLIC_URL` / `SUPABASE_INTRANET_URL`), `SUPABASE_ANON_KEY` (fallback `ANON_KEY` / `GLOBAL_ANON_KEY`) and `SUPABASE_SERVICE_ROLE_KEY` (fallback `SERVICE_ROLE_KEY` / `GLOBAL_SERVICE_ROLE_KEY`) from the file, validates the JWTs (rejects expired keys), then writes the profile + credentials and switches current to it.
23
23
  - **Agent-runnable**: keys come from a file, never from argv/shell history, so no TTY is needed. Prefer this over `login` when you already have the instance `.env`.
24
24
  - `--intranet` prefers `SUPABASE_INTRANET_URL` (VPC-internal entrypoint) over the public URL. `--name` sets the profile name (default: global `--profile`, else `default`); an existing profile of the same name is **overwritten**.
25
- - **Single-tenant only for now.** A `MULTI_TENANT_MODE=true` env file is rejected with guidance — its global keys cannot access tenant resources; use `pxbaas login` with per-tenant anon/service keys instead.
25
+ - **Multi-tenant env files are supported** when they carry tenant-level credentials: `MULTI_TENANT_MODE=true` + `DEFAULT_TENANT_ID` + `TENANT_ANON_KEY` + `TENANT_SERVICE_ROLE_KEY`. Global keys (`GLOBAL_ANON_KEY` / `GLOBAL_SERVICE_ROLE_KEY`) are reported as present-but-not-imported — they cannot reach tenant resources, so the profile never absorbs them.
26
+ - If a `MULTI_TENANT_MODE=true` file lacks the tenant keys, the command refuses and prints a diagnosis (which variables exist, whether each JWT carries a `tenant_id` claim) plus one of two routes: the operator path (`pxbaas tenant login` → `tenant ls` → `tenant keys <id> --as-profile`) or a checklist to send to the operator. Nothing is created and nothing is imported either way; the temporary admin probe it runs to pick the route does not persist the credentials it borrowed.
27
+ - **`DASHBOARD_USERNAME` / `DASHBOARD_PASSWORD` in an env file are NOT imported by default.** Only `--with-admin` binds them, and then only after the credentials actually open the tenant admin channel. Pass `--with-admin` only when the user is the instance operator.
28
+
29
+ ## Operator credentials (`tenant login`)
30
+
31
+ ```bash
32
+ pxbaas tenant login --endpoint https://<gateway> # TTY: masked password prompt
33
+ printf '%s\n%s\n' "$USER" "$PASS" | \
34
+ pxbaas tenant login --endpoint https://<gateway> --user-stdin --password-stdin
35
+ pxbaas tenant logout # drops dashboard creds only, keeps tenant keys
36
+ ```
37
+
38
+ - Stored under a separate keyring account (`admin/<profile>`); data-plane commands cannot read it.
39
+ - The password is refused in argv (`--dashboard-password` / `--admin-password` are blocked by the same guard as API keys).
40
+ - `tenant login` probes the admin channel **before** storing; a refused probe stores nothing.
41
+ - `pxbaas whoami --json` reports `platformAdmin: {available, userMasked, passwordFingerprint}` — never the secret.
26
42
 
27
43
  ## Verify login state (run these first)
28
44
 
@@ -55,10 +55,11 @@ pxbaas data update <table> --values '{"done":true}' --filter "id=eq.1" --json
55
55
  pxbaas data delete <table> --filter "id=eq.1" --json
56
56
  ```
57
57
 
58
- - **Flags:** `--filter <f>` (**required**) · `--json`
58
+ - **Flags:** `--filter <f>` (**required**, repeatable to combine) · `--yes` · `--json`
59
59
  - **Output (`--json`):** array of deleted rows.
60
- - ⚠️ **Destructive, no built-in gate:** it issues `DELETE /rest/v1/<table>?<filter>` directly.
61
- Before running: preview with `data select` using the SAME filter → restate the row count/content to the user → get explicit consent → then delete.
60
+ - **Gated in the CLI:** it first runs a read-only preview of the affected row count and prints the exact request URL, then requires authorization — an interactive confirm, or `--yes` for non-interactive use. Nothing is deleted without it, and a refused run still carries a `preview` object in `--json` so an audit consumer can see what was declined. A failed preview also blocks the delete.
61
+ - `--filter` is mandatory because an unfiltered delete would take the whole table.
62
+ - Still preview the target yourself first (`data select` with the SAME filter) and get the user's explicit consent before passing `--yes`.
62
63
 
63
64
  ## rest — raw HTTP passthrough (debugging)
64
65
 
@@ -40,6 +40,7 @@ pxbaas db tables --schema public --role service_role --json
40
40
 
41
41
  - **Flags:** `--schema <name>` (default `public`) · `--role` (default **service_role**) · `--json`
42
42
  - Listing all tables needs the admin view, hence the service_role default.
43
+ - **Multi-tenant:** works — introspection routes through `/api/tenant-ddl` (`information_schema`) with the per-tenant service_role key, so `--schema` is bound to the tenant database. The `schema` field in `--json` reports the database actually queried.
43
44
 
44
45
  ## db schema — inspect a table (columns + indexes)
45
46
 
@@ -60,6 +61,8 @@ pxbaas db create-table <name> \
60
61
 
61
62
  - **Flags:** `--columns <json>` (array of `{name,type,nullable?,primaryKey?}`; omit → default `id bigint PK` + `created_at timestamp`) · `--schema` (default `public`) · `--role` (default service_role) · `--json`
62
63
  - ⚠️ **Destructive DDL, no built-in gate:** restate the table name, schema, and column definitions and get explicit user consent before running (see safety-and-two-phase.md).
64
+ - **Multi-tenant:** works on an MT profile — the DDL runs through `/api/tenant-ddl` against the tenant's own database, always as the tenant service_role. `--schema` is ignored (warning prints the effective tenant db) and `--role anon` is ignored.
65
+ - Prints a REST schema-cache reminder: `/rest/v1` caches column lists, so `select=*` may not show the new column for up to ~30s. Enumerate columns explicitly or retry.
63
66
 
64
67
  ## db add-column — add a column (destructive DDL)
65
68
 
@@ -70,6 +73,8 @@ pxbaas db add-column <table> <column> --type text --nullable --default <value> \
70
73
 
71
74
  - **Flags:** `--type <t>` (default `text`) · `--nullable` (default true) · `--default <value>` · `--schema` (default `public`) · `--role` (default service_role) · `--json`
72
75
  - ⚠️ **Destructive DDL, no built-in gate:** confirm with the user first.
76
+ - **Multi-tenant:** same as `db create-table` — tenant's own database, `--schema`/`--role anon` ignored with a warning.
77
+ - Restarts apply after the schema cache refreshes; see the cache note under `db create-table`.
73
78
 
74
79
  ## gen types — generate type definitions
75
80
 
@@ -2,28 +2,57 @@
2
2
 
3
3
  This is the safety baseline for using `pxbaas`. **The CLI currently has NO built-in confirmation gate**, so the responsibility to confirm falls on YOU (the agent).
4
4
 
5
- ## Key fact: two-phase confirmation is NOT implemented
5
+ ## Which commands actually gate themselves
6
6
 
7
- - The README/design docs claim destructive operations support two-phase `--plan` / `--confirm <token>`.
8
- - **In the actual source, this is not implemented.** The descriptions of `db create-table` / `db add-column` / `data delete` say "two-phase confirmation", but there is no `--plan`/`--confirm` flag and no interactive prompt; e.g. `data delete` issues `DELETE /rest/v1/<table>?<filter>` directly.
9
- - **Implication:** do NOT assume the CLI will block a dangerous operation for you. **You are the confirmation gate.**
7
+ Gating landed after this page was first written, so it is now a per-command fact rather
8
+ than a global one. Verified against `--help` output:
10
9
 
11
- ## Destructive / write operations (confirm with the user first)
10
+ | Command | Gate |
11
+ | --- | --- |
12
+ | `db push <file>`, `app db-init <name>` | shows the full plan, then TTY confirm or `--yes`; without authorization **no statement runs** |
13
+ | `data delete <table>` | read-only preview of affected rows + exact request URL, then TTY confirm or `--yes`; `--json` keeps a `preview` field even when refused |
14
+ | `app remove`, `app destroy`, `app share revoke`, `instance release`, `skill remove` | require `--confirm`; without them the command returns a `dry-run` preview |
15
+ | `tenant create` | `--dry-run` plan, then TTY confirm or `--yes`; refuses a colliding tenant id unless `--reactivate` |
16
+ | `tenant repair-keys` | requires `--confirm` |
17
+
18
+ **No gate at all** — you are the confirmation layer for these:
19
+
20
+ | Command | Risk |
21
+ | --- | --- |
22
+ | `db create-table`, `db add-column` | DDL, executes immediately (help text says so) |
23
+ | `db query "<write/DDL>"` | executes whatever the SQL says |
24
+ | `data insert`, `data update` | writes; `update` requires `--filter` but may still hit many rows |
25
+ | `rest` with POST/PATCH/PUT/DELETE | whatever the method does |
26
+ | `functions deploy`, `functions delete` | publishes/overwrites or deletes a live function, irreversible |
27
+ | `app deploy` | publishes live (preflight + spec lint run, but no interactive confirm) |
28
+ | `storage rm` / `mv` / `cp` overwrite | deletes, moves, or silently overwrites objects |
29
+ | `auth users create` / `delete` / `invite` | mutates users; `invite` sends real email |
30
+ | `profile rm`, `logout`, `tenant logout` | removes local credentials (config, not instance data) |
31
+
32
+ Read-only and always safe: `whoami` `status` `doctor` `profile ls` `tenant probe` `tenant ls`
33
+ `tenant handoffs` `db tables` `db schema` `gen types` `data select` `functions list` `storage ls`
34
+ `auth users list/get` `app list` `app url` `app capabilities` `app templates` `instance list/current`.
35
+
36
+ ## Risk classification (what impact to expect)
12
37
 
13
38
  | Command | Risk |
14
39
  |---|---|
15
- | `data delete <table> --filter ...` | deletes rows, irreversible |
40
+ | `data delete <table> --filter ...` | deletes rows, irreversible (gated: preview + `--yes`) |
16
41
  | `data insert` / `data update` | writes / mutates data |
17
- | `db create-table` / `db add-column` | schema change (DDL) |
42
+ | `db create-table` / `db add-column` | schema change (DDL), executes immediately |
18
43
  | `db query "<write or DDL SQL>"` | depends on the SQL |
44
+ | `db push` / `app db-init` | DDL + RLS + seed; sequential, stops on first error, **never rolls back** |
19
45
  | `functions deploy` | publishes / overwrites a live function |
20
- | `functions delete <slug>` | deletes a live function, irreversible |
21
- | `storage rm` / `storage mv` / `storage cp` (overwrite) | deletes / moves / overwrites objects |
22
- | `auth users delete` / `create` / `invite` | deletes / creates users, sends invites |
46
+ | `functions delete <slug>` | deletes a live function, irreversible; also revokes its share links |
47
+ | `app deploy` / `publish` / `rollback` / `unpublish` | changes what is live for users |
48
+ | `app remove` / `app destroy [--purge-db]` | deletes functions + mount; `--purge-db` also drops tables and RLS rows |
49
+ | `storage rm` / `mv` / `cp` (overwrite) | deletes / moves / overwrites objects; remote→remote `cp` is a **move** (source is removed) |
50
+ | `auth users delete` / `create` / `invite` | deletes / creates users; `invite` sends real email |
23
51
  | `rest` POST/PATCH/DELETE/PUT | depends on method and path |
24
- | `profile rm` / `logout` | removes local credential config |
25
-
26
- **Read-only safe commands** (no confirmation needed): `whoami` `status` `doctor` `profile ls/use` `db tables/schema` `db query "<SELECT>"` `gen types` `data select` `functions list` `functions new` (local only) `storage ls` `auth users list/get` `completion`.
52
+ | `profile rm` / `logout` / `tenant logout` | removes local credentials (also the stored keys, so re-login is required) |
53
+ | `tenant create` | provisions a database, a MySQL account + GRANTs, a storage bucket, RLS tables, Realtime registration; consumes quota |
54
+ | `tenant repair-keys` | re-signs NULL-keyed tenants from the existing `jwt_secret`; idempotent, **not** a rotation |
55
+ | `instance release` | destroys cloud resources and the local profile |
27
56
 
28
57
  ## Agent confirmation protocol (for destructive ops)
29
58