@polardbx/pxbaas 0.1.0-beta.1 → 0.1.0-beta.10

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 (180) hide show
  1. package/README.md +224 -0
  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 +24 -1
  7. package/dist/app/preflight.d.ts.map +1 -1
  8. package/dist/app/preflight.js +47 -11
  9. package/dist/app/preflight.js.map +1 -1
  10. package/dist/app/spec-lint.d.ts.map +1 -1
  11. package/dist/app/spec-lint.js +16 -0
  12. package/dist/app/spec-lint.js.map +1 -1
  13. package/dist/app/templates.d.ts +24 -0
  14. package/dist/app/templates.d.ts.map +1 -1
  15. package/dist/app/templates.js.map +1 -1
  16. package/dist/cli/help-guides.d.ts +17 -0
  17. package/dist/cli/help-guides.d.ts.map +1 -0
  18. package/dist/cli/help-guides.js +132 -0
  19. package/dist/cli/help-guides.js.map +1 -0
  20. package/dist/cli/root.d.ts.map +1 -1
  21. package/dist/cli/root.js +6 -0
  22. package/dist/cli/root.js.map +1 -1
  23. package/dist/commands/app/index.d.ts.map +1 -1
  24. package/dist/commands/app/index.js +687 -47
  25. package/dist/commands/app/index.js.map +1 -1
  26. package/dist/commands/completion/index.d.ts.map +1 -1
  27. package/dist/commands/completion/index.js +5 -1
  28. package/dist/commands/completion/index.js.map +1 -1
  29. package/dist/commands/data/index.d.ts +52 -0
  30. package/dist/commands/data/index.d.ts.map +1 -1
  31. package/dist/commands/data/index.js +201 -39
  32. package/dist/commands/data/index.js.map +1 -1
  33. package/dist/commands/db/index.d.ts +15 -0
  34. package/dist/commands/db/index.d.ts.map +1 -1
  35. package/dist/commands/db/index.js +200 -55
  36. package/dist/commands/db/index.js.map +1 -1
  37. package/dist/commands/functions/new.d.ts +4 -0
  38. package/dist/commands/functions/new.d.ts.map +1 -1
  39. package/dist/commands/functions/new.js +24 -9
  40. package/dist/commands/functions/new.js.map +1 -1
  41. package/dist/commands/gen/index.d.ts.map +1 -1
  42. package/dist/commands/gen/index.js +13 -10
  43. package/dist/commands/gen/index.js.map +1 -1
  44. package/dist/commands/guide/index.d.ts +8 -0
  45. package/dist/commands/guide/index.d.ts.map +1 -0
  46. package/dist/commands/guide/index.js +20 -0
  47. package/dist/commands/guide/index.js.map +1 -0
  48. package/dist/commands/instance/index.d.ts.map +1 -1
  49. package/dist/commands/instance/index.js +120 -0
  50. package/dist/commands/instance/index.js.map +1 -1
  51. package/dist/commands/login/index.d.ts.map +1 -1
  52. package/dist/commands/login/index.js +23 -63
  53. package/dist/commands/login/index.js.map +1 -1
  54. package/dist/commands/profile/import-env.d.ts +7 -2
  55. package/dist/commands/profile/import-env.d.ts.map +1 -1
  56. package/dist/commands/profile/import-env.js +141 -16
  57. package/dist/commands/profile/import-env.js.map +1 -1
  58. package/dist/commands/profile/index.d.ts.map +1 -1
  59. package/dist/commands/profile/index.js +16 -1
  60. package/dist/commands/profile/index.js.map +1 -1
  61. package/dist/commands/skill/index.d.ts.map +1 -1
  62. package/dist/commands/skill/index.js +89 -14
  63. package/dist/commands/skill/index.js.map +1 -1
  64. package/dist/commands/storage/index.d.ts.map +1 -1
  65. package/dist/commands/storage/index.js +209 -7
  66. package/dist/commands/storage/index.js.map +1 -1
  67. package/dist/commands/tenant/index.d.ts +19 -0
  68. package/dist/commands/tenant/index.d.ts.map +1 -0
  69. package/dist/commands/tenant/index.js +667 -0
  70. package/dist/commands/tenant/index.js.map +1 -0
  71. package/dist/commands/whoami/index.js +19 -0
  72. package/dist/commands/whoami/index.js.map +1 -1
  73. package/dist/credentials/credential-store.d.ts +30 -1
  74. package/dist/credentials/credential-store.d.ts.map +1 -1
  75. package/dist/credentials/credential-store.js +114 -12
  76. package/dist/credentials/credential-store.js.map +1 -1
  77. package/dist/credentials/env-file.d.ts +48 -7
  78. package/dist/credentials/env-file.d.ts.map +1 -1
  79. package/dist/credentials/env-file.js +91 -16
  80. package/dist/credentials/env-file.js.map +1 -1
  81. package/dist/credentials/index.d.ts +2 -0
  82. package/dist/credentials/index.d.ts.map +1 -1
  83. package/dist/credentials/index.js +2 -0
  84. package/dist/credentials/index.js.map +1 -1
  85. package/dist/credentials/jwt-introspect.d.ts +7 -0
  86. package/dist/credentials/jwt-introspect.d.ts.map +1 -1
  87. package/dist/credentials/jwt-introspect.js.map +1 -1
  88. package/dist/credentials/masker.d.ts +3 -0
  89. package/dist/credentials/masker.d.ts.map +1 -1
  90. package/dist/credentials/masker.js +3 -0
  91. package/dist/credentials/masker.js.map +1 -1
  92. package/dist/credentials/profile.d.ts +12 -0
  93. package/dist/credentials/profile.d.ts.map +1 -1
  94. package/dist/credentials/profile.js +23 -0
  95. package/dist/credentials/profile.js.map +1 -1
  96. package/dist/credentials/tty-guard.d.ts +10 -0
  97. package/dist/credentials/tty-guard.d.ts.map +1 -1
  98. package/dist/credentials/tty-guard.js +32 -0
  99. package/dist/credentials/tty-guard.js.map +1 -1
  100. package/dist/db/apply-schema.d.ts +98 -11
  101. package/dist/db/apply-schema.d.ts.map +1 -1
  102. package/dist/db/apply-schema.js +247 -80
  103. package/dist/db/apply-schema.js.map +1 -1
  104. package/dist/db/introspect.d.ts +54 -0
  105. package/dist/db/introspect.d.ts.map +1 -0
  106. package/dist/db/introspect.js +140 -0
  107. package/dist/db/introspect.js.map +1 -0
  108. package/dist/db/sql-file.d.ts +55 -0
  109. package/dist/db/sql-file.d.ts.map +1 -1
  110. package/dist/db/sql-file.js +417 -0
  111. package/dist/db/sql-file.js.map +1 -1
  112. package/dist/instance/zero-whitelist.d.ts +39 -0
  113. package/dist/instance/zero-whitelist.d.ts.map +1 -0
  114. package/dist/instance/zero-whitelist.js +67 -0
  115. package/dist/instance/zero-whitelist.js.map +1 -0
  116. package/dist/main.js +26 -3
  117. package/dist/main.js.map +1 -1
  118. package/dist/skill/agents-md.d.ts +25 -0
  119. package/dist/skill/agents-md.d.ts.map +1 -0
  120. package/dist/skill/agents-md.js +62 -0
  121. package/dist/skill/agents-md.js.map +1 -0
  122. package/dist/skill/hosts.d.ts +31 -7
  123. package/dist/skill/hosts.d.ts.map +1 -1
  124. package/dist/skill/hosts.js +65 -7
  125. package/dist/skill/hosts.js.map +1 -1
  126. package/dist/skill/installer.d.ts +15 -3
  127. package/dist/skill/installer.d.ts.map +1 -1
  128. package/dist/skill/installer.js +62 -24
  129. package/dist/skill/installer.js.map +1 -1
  130. package/dist/tenant/bundle.d.ts +66 -0
  131. package/dist/tenant/bundle.d.ts.map +1 -0
  132. package/dist/tenant/bundle.js +132 -0
  133. package/dist/tenant/bundle.js.map +1 -0
  134. package/dist/tenant/guide.d.ts +50 -0
  135. package/dist/tenant/guide.d.ts.map +1 -0
  136. package/dist/tenant/guide.js +152 -0
  137. package/dist/tenant/guide.js.map +1 -0
  138. package/dist/tenant/probe.d.ts +65 -0
  139. package/dist/tenant/probe.d.ts.map +1 -0
  140. package/dist/tenant/probe.js +159 -0
  141. package/dist/tenant/probe.js.map +1 -0
  142. package/dist/tenant/verify.d.ts +37 -0
  143. package/dist/tenant/verify.d.ts.map +1 -0
  144. package/dist/tenant/verify.js +132 -0
  145. package/dist/tenant/verify.js.map +1 -0
  146. package/dist/version.d.ts +29 -0
  147. package/dist/version.d.ts.map +1 -0
  148. package/dist/version.js +65 -0
  149. package/dist/version.js.map +1 -0
  150. package/package.json +3 -3
  151. package/skills/pxbaas-app-spec/SKILL.md +4 -2
  152. package/skills/pxbaas-app-spec/references/deploy-options.md +3 -2
  153. package/skills/pxbaas-app-spec/references/runtime-contract.md +2 -2
  154. package/skills/pxbaas-app-spec/references/templates-examples.md +7 -4
  155. package/skills/pxbaas-cli/SKILL.md +26 -8
  156. package/skills/pxbaas-cli/references/app-deploy.md +46 -3
  157. package/skills/pxbaas-cli/references/auth-and-credentials.md +30 -2
  158. package/skills/pxbaas-cli/references/data-postgrest.md +19 -3
  159. package/skills/pxbaas-cli/references/db-and-meta.md +17 -0
  160. package/skills/pxbaas-cli/references/multi-tenant-apps.md +35 -4
  161. package/skills/pxbaas-cli/references/provision.md +16 -1
  162. package/skills/pxbaas-cli/references/safety-and-two-phase.md +42 -13
  163. package/skills/pxbaas-cli/references/tenant-admin.md +122 -0
  164. package/skills/pxbaas-cli/references/troubleshooting.md +4 -1
  165. package/templates/webapp/csr-bff/db/rls.json +13 -0
  166. package/templates/webapp/csr-bff/db/schema.sql +8 -28
  167. package/templates/webapp/manifest.json +131 -8
  168. package/templates/webapp/react-vite-hono/backend/index.ts +21 -4
  169. package/templates/webapp/ssr-blog/db/rls.json +13 -0
  170. package/templates/webapp/ssr-blog/db/schema.sql +8 -28
  171. package/templates/webapp/ssr-hono/backend/index.ts +50 -31
  172. package/templates/webapp/ssr-shop/db/rls.json +16 -0
  173. package/templates/webapp/ssr-shop/db/schema.sql +12 -42
  174. package/dist/commands/login/login.d.ts +0 -3
  175. package/dist/commands/login/login.d.ts.map +0 -1
  176. package/dist/commands/login/login.js +0 -185
  177. package/dist/commands/login/login.js.map +0 -1
  178. /package/templates/webapp/vanilla-static/frontend/{app.js → dist/app.js} +0 -0
  179. /package/templates/webapp/vanilla-static/frontend/{index.html → dist/index.html} +0 -0
  180. /package/templates/webapp/vanilla-static/frontend/{style.css → dist/style.css} +0 -0
@@ -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.1",
3
+ "version": "0.1.0-beta.10",
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.1"
38
+ "@polardbx/pxbaas-client": "0.1.0-beta.10"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@types/inquirer": "^9.0.0",
@@ -49,7 +49,7 @@
49
49
  "vitest": "^1.2.0"
50
50
  },
51
51
  "engines": {
52
- "node": ">=20.0.0"
52
+ "node": "^22.23.4 || ^24.22.1 || >=26.12.1"
53
53
  },
54
54
  "scripts": {
55
55
  "build": "tsc --build",
@@ -100,12 +100,14 @@ SchemaVersion 2 Webapp:
100
100
 
101
101
  ```
102
102
  apps/<name>/
103
- ├── frontend/ # source project or vanilla static source
104
- │ └── dist/ # canonical React/Vite deployment root
103
+ ├── frontend/ # React source project (templates that build)
104
+ │ └── dist/ # frontend.root — the frontend files deploy uploads
105
105
  ├── backend/index.ts # optional Deno/Hono API
106
106
  └── app.json # mode=webapp, frontend/backend contracts, deployment state
107
107
  ```
108
108
 
109
+ `frontend.root` is both the local artifact directory and the uploaded key prefix; anything you want served must live inside it.
110
+
109
111
  Legacy modes retain `index.ts` plus optional `mpa` static files.
110
112
 
111
113
  - Webapp templates: `vanilla-static`, `react-vite`, `react-vite-hono`, `ssr-hono`, `ssr-auth` (signed-in SSR via proxy-injected session headers). Reference application templates (ship `db/schema.sql` + README + a DEPLOY CONFIG block to fill before deploy): `ssr-blog` (full-site SSR + proxy-injected session), `csr-bff` (front/back-separated CSR + BFF), `ssr-shop` (React SSR shop, browser-side session), `ssr-frameworks` (four-framework SSR showcase). `pxbaas app templates` is the authoritative list.
@@ -24,8 +24,9 @@ pxbaas app rollback <name> --json
24
24
  pxbaas app unpublish <name> --json
25
25
  ```
26
26
 
27
- - `vanilla-static` needs no build; React templates use their committed lockfile and fixed versions.
28
- - `--frontend` must point to prebuilt output. `--backend` points to Deno source.
27
+ - `vanilla-static` needs no build — it ships `frontend/dist/index.html|style.css|app.js` directly. React templates use their committed lockfile and fixed versions.
28
+ - Every webapp template must keep its served files inside `app.json` `frontend.root` (default `frontend/dist`); deploy stages exactly that directory. Pass `--frontend <dir>` only when your build output lives somewhere else — it resolves against the current working directory, not `--dir`. A missing artifact directory fails upload with `APP_ARTIFACT_INVALID` rather than silently shipping something else.
29
+ - `--backend <dir>` points to the Deno/Hono source directory; its uploaded prefix comes from `backend.entrypoint`'s parent, so keep the entrypoint under `backend/`.
29
30
  - Deploy automatically publishes. `app publish` is useful when a prior publish failed after candidate upload.
30
31
  - Confirm deploy/publish/rollback/unpublish with the user first.
31
32
  - JSON failures consistently contain `code` and `message`.
@@ -98,7 +98,7 @@ Apps may declare `frontend.fallback: "worker"` in app.json: static-miss non-`/ap
98
98
  - **React SSR emits no event handlers**: `renderToString` drops every `on*` prop (lowercase `onclick` included). Wire interactivity with `data-*` attributes (passed through verbatim) plus one client-side delegated listener; verify by grepping the served HTML for the attribute, not by reading component code.
99
99
  - **MUST** fetch data client-side after hydration (relative `./api/*` or the instance REST/Auth URLs with the user's JWT) for the default SSR-shell shape. For server-side data fetching (L3 full SSR / SEO first paint), **MUST** use the platform-injected `SUPABASE_INTERNAL_URL` as the base — it is the ONLY origin reachable from inside the sandbox (the public `SUPABASE_URL` is egress-filtered and cluster service names do not resolve). The platform derives `SUPABASE_INTERNAL_URL` from the public host via DX-12 (edge-runtime `app-env.ts`, MR #30497903); the worker just reads it and **MUST NOT** re-derive from `SUPABASE_URL` or fetch `SUPABASE_URL` directly. Use `SUPABASE_ANON_KEY` (or the forwarded user JWT) as the credential; server-side RLS still applies on the internal base. These three public keys reach App candidate workers only on sandbox images carrying MR #30497903 — degrade gracefully (render the shell without server data) when `SUPABASE_INTERNAL_URL` is empty.
100
100
  - SSR responses are `private, no-cache` with the platform CSP (inline hydrate scripts allowed); do not rely on shared caching of rendered pages.
101
- - **Path shapes (interim contract while `APP_WORKER_PATH_REWRITE` is off)**: the `/api/*` branch delivers the STRIPPED logical path to the worker (`/apps/{m}/api/data` → worker sees `/data`), while static-miss fallthrough delivers the FULL logical path (`/apps/{m}/page` → worker sees `/page`). Workers MUST register both the stripped and `/api`-prefixed shapes for every JSON endpoint (the `ssr-hono` template does); hydrate scripts calling `./api/*` from an SSR page rely on the stripped shape being routed.
101
+ - **Path shapes (current default, `APP_WORKER_PATH_REWRITE` OFF)**: the proxy forwards EVERY worker request as `/<candidateSlug>/<logicalPath>` — the candidate slug prefix is **not** stripped at runtime. So `/apps/{m}/api/data` reaches the worker as `/<candidateSlug>/data`, and a static-miss fallthrough `/apps/{m}/page` reaches it as `/<candidateSlug>/page` (the `/api` segment is consumed by the proxy; the slug is prepended for edge-runtime function routing). The stripped logical path is delivered ONLY via the `X-Pxbaas-Worker-Path` header when `APP_WORKER_PATH_REWRITE` is ON, which is **disabled by default** (the runtime `new Request(url, req)` rewrite crashes with a streamRid TypeError on current sandbox binaries — D18). Workers MUST therefore normalize the leading `/<candidateSlug>` prefix and match on the logical path, e.g. `const p = new URL(req.url).pathname.replace(/^\/app_[^/]+/, "")` then dispatch on `p` (the `ssr-hono`, `ssr-shop`, `ssr-blog`, `csr-bff` templates all do this). A literal `app.get("/data")` will NOT match; do not rely on the declared `pathMode: "strip-app-api-prefix"` being honored at runtime yet. Hydrate scripts calling `./api/*` from an SSR page are routed the same way (browser `/apps/{m}/api/data` → worker `/<candidateSlug>/data`).
102
102
  - Start from the `ssr-hono` template (`pxbaas app new --template ssr-hono`); spec-lint enforces the rules above (`ssr-probe-declared`, `ssr-self-contained`, `ssr-marker-present`, `ssr-no-browser-globals`, `ssr-lib-not-allowed`).
103
103
 
104
104
  ## Lifecycle & cold start
@@ -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.
@@ -13,12 +13,12 @@ apps/<name>/
13
13
  │ ├── package-lock.json # committed reproducible lockfile
14
14
  │ ├── vite.config.ts # base: "./", sourcemap: false
15
15
  │ ├── src/ # React + HashRouter + ordinary CSS
16
- │ └── dist/ # build output deployed by the CLI
16
+ │ └── dist/ # frontend.root — the frontend files deploy uploads
17
17
  ├── backend/index.ts # react-vite-hono only
18
18
  └── app.json
19
19
  ```
20
20
 
21
- `vanilla-static` stores editable files directly under `frontend/`; deploy maps them to canonical `frontend/dist/*`. React templates require `npm ci && npm run build` before deploy.
21
+ `app.json` `frontend.root` has one meaning: the local artifact directory **and** the uploaded key prefix. Everything you want served must live inside it; files elsewhere under `frontend/` are not uploaded. `vanilla-static` therefore ships its editable `index.html` / `style.css` / `app.js` directly in `frontend/dist/` and needs no build. React templates keep source in `frontend/src/` and require `npm ci && npm run build` before deploy.
22
22
 
23
23
  Legacy single-file (`spa`/`api`):
24
24
  ```
@@ -84,8 +84,11 @@ apps/<name>/
84
84
  | `ssr-shop` | React 18 + Hono SSR shop; catalog/detail/search server-rendered, cart/checkout hydrate; `session: "browser-side"`, RLS-gated orders | framework SSR with browser-side auth |
85
85
  | `ssr-frameworks` | same UI rendered by React / Vue / Preact / Solid on Hono, plus `/compare`; no data plane | framework SSR capability showcase (heaviest cold start) |
86
86
 
87
- `ssr-blog`, `csr-bff` and `ssr-shop` each ship a `db/schema.sql` (idempotent DDL + RLS + seed) with a `__SCHEMA__`
88
- token: use `public` on single-tenant instances and `tenant_<id>` on multi-tenant ones. Their workers carry a
87
+ `ssr-blog`, `csr-bff` and `ssr-shop` each ship a `db/schema.sql` (idempotent DDL + seed, with a `__SCHEMA__`
88
+ token) and a declarative `db/rls.json` (the RLS registry). Run `pxbaas app db-init <name>` to apply both —
89
+ it is mode-aware, so you do **not** hand-edit `__SCHEMA__`: single-tenant resolves it to `public` via `/pg/query`,
90
+ multi-tenant de-qualifies it and runs DDL on the tenant's own db via `/api/tenant-ddl`, registering RLS through the
91
+ structured `/rls/*` endpoints (`table_schema` bound server-side). Their workers carry a
89
92
  **DEPLOY CONFIG** block (`INTERNAL_BASE` / `ANON_KEY`) that is empty by design — an unconfigured worker returns an
90
93
  explicit 503 instead of silently falling back to someone else's instance or tenant.
91
94
 
@@ -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` IS 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
@@ -62,6 +62,7 @@ All requests go through a single Kong gateway (port **8000**). Key path prefixes
62
62
  Run these (all read-only) to establish state before doing anything else:
63
63
 
64
64
  ```bash
65
+ pxbaas guide # 0. printable end-to-end develop/deploy/verify/share workflow (no auth needed)
65
66
  pxbaas status --json # 1. capability URLs; confirm endpoint and availability
66
67
  pxbaas doctor --json # 2. health: connectivity, JWT validity, expiry, tenant status
67
68
  pxbaas whoami --json # 3. current profile / tenant / role / key fingerprints
@@ -76,6 +77,7 @@ Legend — **R** = read/safe · **W** = write · **D** = destructive (consent re
76
77
  | Command | Default role | R/W/D | Details |
77
78
  |---|---|---|---|
78
79
  | `login` (human TTY only) | — | special | auth-and-credentials.md |
80
+ | `profile import <env-file>` (agent-runnable; single-tenant, or multi-tenant when the file carries `TENANT_*` keys) | — | W (local) | auth-and-credentials.md |
79
81
  | `logout` | — | W (local) | auth-and-credentials.md |
80
82
  | `whoami` | — | R | auth-and-credentials.md |
81
83
  | `status` | — | R | auth-and-credentials.md |
@@ -108,23 +110,32 @@ Legend — **R** = read/safe · **W** = write · **D** = destructive (consent re
108
110
  | `auth users delete <userId>` | service_role | **D** | auth-and-credentials.md |
109
111
  | `auth users invite <email>` | service_role | W | auth-and-credentials.md |
110
112
  | `completion <shell>` | — | R (local) | (this file) |
113
+ | `guide` | — | R (local, no auth) | (this file) |
114
+ | `tenant login` / `tenant logout` | dashboard Basic | W (local) | tenant-admin.md |
115
+ | `tenant probe` / `tenant ls` / `tenant handoffs` | dashboard Basic | R | tenant-admin.md |
116
+ | `tenant create [id]` | dashboard Basic | **D/W** (provisions db, account, GRANT, bucket, quota) | tenant-admin.md |
117
+ | `tenant keys <id>` | dashboard Basic | W (local; writes keyring or 0600 bundle) | tenant-admin.md |
118
+ | `tenant repair-keys` | dashboard Basic | W (`--confirm`) | tenant-admin.md |
111
119
  | `provision` (agent-runnable) | — | W (local+remote) | provision.md |
112
120
  | `instance list` / `instance current` | — | R | provision.md |
113
121
  | `instance credentials <id>` | — | R (`--reveal` for dashboard pw) | provision.md |
114
122
  | `instance release <id>` | — | **D** (two-phase `--confirm`) | provision.md |
123
+ | `instance whitelist get` / `instance whitelist add <ips>` | — | R / W (**Zero instances only**; append-only, `--dry-run` to preview) | provision.md |
115
124
  | `app new <name>` | — | local | app-deploy.md |
116
125
  | `app capabilities` | service_role | R | app-deploy.md |
117
126
  | `app deploy <name>` | service_role | **D/W** | app-deploy.md |
118
127
  | `app publish <name>` / `app rollback <name>` / `app unpublish <name>` | service_role | **D/W** | app-deploy.md |
119
128
  | `app verify <name>` / `app url <name>` | — | R | app-deploy.md |
120
129
  | `app list` / `app templates` | service_role | R | app-deploy.md |
121
- | `app remove <name>` | service_role | **D** (`--confirm`) | app-deploy.md |
130
+ | `app remove <name>` | service_role | **D** (`--confirm`; legacy `spa/api/mpa` only) | app-deploy.md |
131
+ | `app destroy <name>` | service_role | **D** (two-phase `--confirm`; webapp only; deletes edge funcs + mount, **keeps DB tables**) | app-deploy.md |
122
132
 
123
- 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`).
133
+ 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`).
124
134
 
125
135
  ## Natural language → command
126
136
 
127
137
  ```
138
+ "how do I develop and deploy an app" → pxbaas guide (follow it; the bundled skills carry the details)
128
139
  "show the schema of todos" → pxbaas db schema todos --json
129
140
  "what tables are there" → pxbaas db tables --json
130
141
  "count the users" → pxbaas db query "SELECT count(*) FROM users" --json
@@ -136,24 +147,31 @@ For provisioning / multi-instance selection see **provision.md**; for the `pxbaa
136
147
  "delete this object" → confirm first → pxbaas storage rm <bucket/path> --json
137
148
  "I need a BaaS instance" → pxbaas provision --json (free 15-day Zero instance; agent-runnable)
138
149
  "which instance am I on" → pxbaas instance current --json (or pxbaas whoami --json)
150
+ "show/extend my instance whitelist" → pxbaas instance whitelist get --json · pxbaas instance whitelist add <ip,cidr> --json (Zero instances only; append-only)
139
151
  "deploy this demo webapp" → confirm first → pxbaas app deploy <name> --json
140
152
  "give me a shareable demo URL" → pxbaas app url <name> --json
153
+ "published/shared but they can't open it" → IP-whitelist gate: pxbaas instance whitelist add <their-ip> --json (Zero instance) · purchased instance: add the recipient IP via the cloud console/control plane (CLI whitelist is Zero-only)
154
+ "destroy/delete this webapp" → confirm first → pxbaas app destroy <name> --confirm --json (removes edge funcs + mount; DB tables kept)
141
155
  "can't connect / error" → pxbaas doctor --json; if credentials missing, ask user to run pxbaas login
156
+ "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)
157
+ "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)
142
158
  ```
143
159
 
144
160
  ## What NOT to do
145
161
 
146
162
  - **NEVER run `pxbaas login`** — it needs an interactive TTY. Ask the user to log in instead.
147
163
  - **NEVER expect or echo plaintext keys** — output is fingerprint-only; never ask the user for a raw key to build a command.
148
- - **NEVER run `data delete` / `storage rm` / `functions delete` / DDL without previewing first** and getting explicit user consent (no built-in gate exists).
164
+ - **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.
149
165
  - **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()`).
150
166
  - **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.
151
167
  - **NEVER mix credentials across profiles** or target another tenant; the CLI enforces tenant isolation.
152
- - **NEVER write multi-tenant RLS policies into a tenant DB's `_mysqlrest_rls_*` copies, or with `table_schema='public'`** — the authoritative store is the central `supabase` DB keyed by the tenant database name, and a wrong value fails **silently** (RLS stays off). See `references/multi-tenant-apps.md`.
153
- - **NEVER assume `pxbaas db query` works on a multi-tenant profile** — the pg-meta path rejects tenant keys (401); run tenant SQL via `/pg/query` with the global service key and fully-qualified `tenant_<id>.<table>` names.
168
+ - **NEVER write multi-tenant RLS policies into a tenant DB's `_mysqlrest_rls_*` copies, or with `table_schema='public'`** — the authoritative store is the central `supabase` DB keyed by the tenant database name, and a wrong value fails **silently** (RLS stays off). Prefer declaring RLS in `db/rls.json` and letting `pxbaas app db-init` bind `table_schema` for you. See `references/multi-tenant-apps.md`.
169
+ - **On a multi-tenant profile, `db query` / `db push` / `app db-init` route through `/api/tenant-ddl` with the per-tenant service_role key** — DDL runs on the tenant's OWN db (GRANT-isolated) and RLS goes through the structured `/rls/*` endpoints (`table_schema` bound server-side). `/pg/query` stays platform-admin only and rejects per-tenant keys; do not hand-run central RLS INSERTs with a global key.
154
170
  - **NEVER parse human-readable output** — always use `--json`.
155
171
  - **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.
156
172
  - **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`).
173
+ - **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.
174
+ - **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`.
157
175
  - **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).
158
176
 
159
177
  ## Global flags
@@ -163,6 +181,6 @@ For provisioning / multi-instance selection see **provision.md**; for the `pxbaa
163
181
  ## Installing / updating these skills
164
182
 
165
183
  The CLI ships **two** bundled skills — `pxbaas-cli` (this one) and `pxbaas-app-spec` (webapp authoring) — installable into Agent hosts (offline, no login):
166
- `pxbaas skill install --host <qoder|claude|cursor|qwen|hermes>` (or `--all`) installs **all** bundled skills; `--skill <name>` installs just one. Also `pxbaas skill list` and `pxbaas skill remove`. These are setup/meta commands, not part of the data-plane surface above.
184
+ `pxbaas skill install --host <qoder|claude|cursor|qwen|hermes|codex|gemini|copilot|qwenworkcn|agents>` (or `--all`, which skips the neutral `agents` location) installs **all** bundled skills; `--skill <name>` installs just one. `--scope project` writes repository-level conventions instead (`.agents/skills`, `.gemini/skills`, `.github/skills`, `.claude/skills`, `.qwen/skills`, `.trae/skills`, `.codebuddy/skills`, `.cline/skills`), so skills travel with the code; with `--agents-md` it also maintains a managed pointer block in AGENTS.md for agents without a SKILL.md convention. Also `pxbaas skill list` and `pxbaas skill remove` (two-phase). These are setup/meta commands, not part of the data-plane surface above.
167
185
 
168
186
  > The command surface above is derived from the actual source (`apps/baas/src`). When commands change, update this skill and the references accordingly.
@@ -1,11 +1,13 @@
1
1
  # Webapp Demo Deploy — `pxbaas app` commands
2
2
 
3
- Covers: `app new` · `app capabilities` · `app deploy` · `app publish` · `app rollback` · `app unpublish` · `app verify` · `app url` · `app list` · `app remove` · `app templates`.
3
+ Covers: `app new` · `app capabilities` · `app deploy` · `app publish` · `app rollback` · `app unpublish` · `app destroy` · `app verify` · `app url` · `app list` · `app remove` · `app templates`.
4
4
 
5
5
  `pxbaas app` supports both the legacy `spa`/`api`/`mpa` Edge Function demos and schemaVersion 2 Webapps with a prebuilt static frontend plus an optional Deno or Hono backend. Webapp deploys create immutable function candidates and publish them to a stable App mount using generation compare-and-swap (CAS).
6
6
 
7
7
  > **How to WRITE the webapp code** (runtime contract, frontend/backend guardrails, templates, and deploy paths) lives in the dedicated **`pxbaas-app-spec` skill**. This page is only the `pxbaas app` command surface.
8
8
 
9
+ > **End-to-end workflow** (instance binding, scoped approvals, stage-by-stage recovery, network/whitelist handling) lives in **`pxbaas guide`**; run it first and follow it. This page covers only the `pxbaas app` command surface.
10
+
9
11
  ## URL models
10
12
 
11
13
  Legacy modes keep the Edge Function URL:
@@ -56,7 +58,7 @@ Legacy `spa`/`api`/`mpa` behavior is unchanged. For `mode=webapp`, deploy:
56
58
 
57
59
  1. reads and validates Manifest v2;
58
60
  2. checks capabilities before any upload;
59
- 3. stages only the prebuilt frontend, optional backend, and generated `app.json`;
61
+ 3. stages the prebuilt frontend from `<appDir>/<frontend.root>` (override the directory with `--frontend <dir>`), the optional backend, and the generated `app.json`;
60
62
  4. runs frontend/backend spec-lint and capability size limits;
61
63
  5. creates or loads the stable App mount;
62
64
  6. uploads an immutable `app_{appId}_r_{deployId}` candidate;
@@ -99,6 +101,33 @@ pxbaas app unpublish <name> [--dir ./apps] [--json]
99
101
 
100
102
  These commands are schemaVersion 2 only. They read `deployment.mountId`, `deployment.candidateFunctionSlug`, and `deployment.generation` from local `app.json`, send the generation as a CAS precondition, and persist the returned generation. A stale generation returns a normalized conflict code/message instead of silently overwriting another publish.
101
103
 
104
+ ## pxbaas app db-init — initialize the app's database (single- AND multi-tenant)
105
+
106
+ ```bash
107
+ pxbaas app db-init <name> [--dir ./apps] [--schema public] [--dry-run] [--yes] [--json]
108
+ ```
109
+
110
+ - Applies the app's bundled `db/schema.sql` (DDL + seed) plus the declarative `db/rls.json` (RLS registry). Shows the full plan and requires authorization (`--dry-run` previews; `--yes` for non-interactive); runs sequentially, **stops on the first error, never rolls back**. The CLI never auto-runs DDL during `deploy`.
111
+ - **Mode-aware:** single-tenant runs through `/pg/query` (`__SCHEMA__`→`--schema`, default `public`); multi-tenant runs through `/api/tenant-ddl` with the per-tenant service_role key (`__SCHEMA__` de-qualified, DDL on the tenant's own db, RLS via the structured `/rls/*` endpoints with `table_schema` bound server-side). See `references/multi-tenant-apps.md` §3b.
112
+ - **RLS source (hybrid):** `db/rls.json` is preferred; if absent, RLS INSERTs embedded in `schema.sql` are parsed (multi-tenant) or run verbatim (single-tenant). When both exist, `rls.json` wins and the embedded INSERTs are skipped with a warning.
113
+
114
+ ## pxbaas app destroy — permanently tear down a webapp (destructive; two-phase)
115
+
116
+ ```bash
117
+ pxbaas app destroy <name> [--dir ./apps] [--confirm] [--json]
118
+ ```
119
+
120
+ - schemaVersion 2 webapp only. Legacy `spa/api/mpa` functions keep using `app remove` (which now rejects a webapp and points here).
121
+ - **Reclaims two things**: the mount's edge-func candidates (`active`/`previous` plus any local unpublished `candidateFunctionSlug`) and the `_platform.app_mounts` mount-id record. Each function delete also revokes its share links and notifies the proxy (cache invalidation + quota release).
122
+ - **Does NOT drop DB business tables by default** (scope decision). Seed data and the central RLS metadata rows are left untouched. Pass `--purge-db` to also drop the app's own tables and remove its RLS registry rows (policies + the central `_mysqlrest_rls_tables` toggle, via `DELETE /api/tenant-ddl/rls/tables`), derived from `db/schema.sql` CREATE names + `db/rls.json`. On instances predating that DELETE endpoint the toggle row is left (harmless; re-created on the next `db-init`).
123
+ - Without `--confirm` it only previews what will be reclaimed (`status: "dry-run"`). With `--confirm` it destroys, then clears only the local `app.json` `deployment` block (source tree is kept for re-deploy).
124
+ - Idempotent: re-destroying an already-gone mount succeeds (`alreadyGone: true`). The server disables the mount before teardown, so a mid-way failure is safe to re-run.
125
+
126
+ ```json
127
+ { "status": "destroyed", "app": "shop", "mountId": "m-abc...", "alreadyGone": false,
128
+ "deletedFunctions": [ { "slug": "app_..._r_...", "deleted": true } ], "tablesUntouched": true }
129
+ ```
130
+
102
131
  ## pxbaas app verify — cold-start-aware check
103
132
 
104
133
  ```bash
@@ -114,10 +143,24 @@ pxbaas app verify <name> [--dir ./apps] [--slug <slug>] [--timeout 60] [--json]
114
143
  ```bash
115
144
  pxbaas app url <name> [--json] # print Public or Preview mount URL (legacy: share URL)
116
145
  pxbaas app list [--json] # list deployed functions
117
- pxbaas app remove <name> [--confirm] [--json] # delete the function (two-phase; needs --confirm)
146
+ pxbaas app remove <name> [--confirm] [--json] # delete a LEGACY spa/api/mpa function (two-phase; webapps use `app destroy`)
118
147
  pxbaas app templates [--json] # list all bundled templates
119
148
  ```
120
149
 
150
+ ## Delivering access — IP whitelist after publish / share
151
+
152
+ A published Webapp URL (`app url`) and any secure share link (`app share create`) are gated by the **instance IP whitelist**: a recipient whose egress IP is not allowed cannot open them, even though your machine can. So after `app deploy` / `app publish` / `app url` or `app share create`, **proactively raise the whitelist with the user** and act by instance kind:
153
+
154
+ - **Zero trial instance** (profile `source: zero`): the CLI can manage it. Preview, then append on approval (append-only and idempotent — never deletes existing entries, so it cannot lock you out):
155
+ ```bash
156
+ pxbaas instance whitelist get --json
157
+ pxbaas instance whitelist add <recipient-ip-or-cidr> --json
158
+ ```
159
+ The `add` call itself must originate from an already-allowed IP (claiming IP or an existing whitelist entry); otherwise it returns `IP_MISMATCH`. See `provision.md`.
160
+ - **Purchased / production instance**: `pxbaas instance whitelist` is **Zero-only** and will refuse on a `login` profile. Remind the user to add the recipient's egress IP via the cloud console / control plane **in time**, or the published/shared URL stays unreachable for them.
161
+
162
+ `Preview` exposure (shared origin, not an isolated public App domain) is a **separate** limitation from the IP whitelist — state both, and never promise public access from a local HTTP 200. Cross-ref: `pxbaas guide` step 8.
163
+
121
164
  ## Constraints (state these to the user)
122
165
 
123
166
  - A Webapp frontend must be prebuilt and prefix-safe: relative Vite base, `HashRouter`, no Service Worker, `.env`, source map, `node_modules`, source tree, or binary artifact.
@@ -1,6 +1,6 @@
1
1
  # Credentials, Environment & User Management
2
2
 
3
- Covers: `login` (human-only) · `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
 
@@ -11,7 +11,34 @@ pxbaas login --endpoint <url> --tenant-id <id> --anon-key-stdin --service-key-st
11
11
  - **Requires an interactive TTY** (it prompts via inquirer); agents cannot complete it. **Do not attempt.**
12
12
  - Keys are read from stdin via `--anon-key-stdin` / `--service-key-stdin` so they never land in shell history.
13
13
  - Credentials are stored in the OS keyring (keytar), falling back to a `0600` file when no keyring is available.
14
- - If another command reports `No active profile. Use "pxbaas login" ...` → **ask the user to log in manually**. Do not work around it and do not ask for plaintext keys.
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
+
16
+ ## profile import — bind an instance from a `.env` file (agents MAY run this)
17
+
18
+ ```bash
19
+ pxbaas profile import <env-file> --name <profile> [--intranet] --json
20
+ ```
21
+
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
+ - **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
+ - `--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
+ - **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.
15
42
 
16
43
  ## Verify login state (run these first)
17
44
 
@@ -65,6 +92,7 @@ pxbaas logout # clears credentials for the current profile
65
92
  pxbaas profile ls --json # list all profiles
66
93
  pxbaas profile use <name> --json # switch current profile
67
94
  pxbaas profile rm <name> --json # remove a profile (destructive to local config — confirm first)
95
+ pxbaas profile import <env-file> --name <profile> --json # bind a single-tenant instance from a .env file (agent-runnable; see above)
68
96
  ```
69
97
 
70
98
  - You can also pass global `--profile <name>` to target one environment without changing the default.
@@ -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
 
@@ -83,3 +84,18 @@ pxbaas rest POST "/todos" --data '{"title":"x"}' --role anon --json
83
84
  ```
84
85
 
85
86
  > REST endpoint details, embedding/RPC constraints: fetch `${endpoint}/docs` — see api-reference.md.
87
+
88
+ ## Schema cache after DDL
89
+
90
+ The REST layer (`/rest/v1`) caches each table's column list. After a DDL change
91
+ (`ALTER TABLE ... ADD/DROP/MODIFY COLUMN`, or `CREATE`/`DROP TABLE`) the cache
92
+ re-introspects automatically: immediately where the platform wires DDL to a
93
+ cache reload, otherwise within one refresh interval (~30s). Until then:
94
+
95
+ - `select=*` (and `Prefer: return=representation` bodies) may still omit a
96
+ just-added column. This is a stale column list, not an error — the row is
97
+ returned, only the new field is missing.
98
+ - Workaround: enumerate columns explicitly (`?select=id,room_id,board`) instead
99
+ of `*`, or retry after a few seconds.
100
+
101
+ `pxbaas db add-column` / `db create-table` print a reminder about this.
@@ -19,6 +19,18 @@ pxbaas db query "SELECT count(*) FROM users" --role anon --json
19
19
  { "rows": [ { "count(*)": 42 } ], "rowCount": 1, "fields": [ { "name": "count(*)", "dataType": "BIGINT" } ] }
20
20
  ```
21
21
  - **Gotchas:** `db query` can also run write/DDL SQL. If the statement contains INSERT/UPDATE/DELETE/DROP/ALTER/CREATE, treat it as destructive and confirm with the user first.
22
+ - **Multi-tenant:** with an MT profile (`--tenant-id`), `db query` routes through `/api/tenant-ddl/query` using the per-tenant service_role key and runs on the tenant's OWN database (`/pg/query` is platform-admin only and rejects per-tenant keys). `--role anon` is not applicable there — the tenant-ddl endpoint is service_role-only, so the query always runs as the tenant service_role. For batch DDL + RLS use `db push <file>` or `app db-init <name>` (see app-deploy.md); both are MT-aware.
23
+
24
+ ## db push — apply a SQL file (DDL + RLS + seed)
25
+
26
+ ```bash
27
+ pxbaas db push ./db/schema.sql --schema public --dry-run # preview the plan
28
+ pxbaas db push ./db/schema.sql --yes # apply (stop-on-first-error, no rollback)
29
+ ```
30
+
31
+ - **Flags:** `--schema <name>` (single-tenant `__SCHEMA__` target, default `public`) · `--rls <file>` (declarative `db/rls.json`) · `--role` (default service_role) · `--dry-run` · `--yes` · `--json`
32
+ - Mode-aware: single-tenant runs through `/pg/query`; multi-tenant through `/api/tenant-ddl` (de-qualifies `__SCHEMA__`, diverts RLS to the structured endpoints). Shows the full plan and requires authorization; never rolls back.
33
+ - `app db-init <name>` is the app-bundled convenience wrapper (reads `<appDir>/db/schema.sql` + `db/rls.json`).
22
34
 
23
35
  ## db tables — list tables
24
36
 
@@ -28,6 +40,7 @@ pxbaas db tables --schema public --role service_role --json
28
40
 
29
41
  - **Flags:** `--schema <name>` (default `public`) · `--role` (default **service_role**) · `--json`
30
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.
31
44
 
32
45
  ## db schema — inspect a table (columns + indexes)
33
46
 
@@ -48,6 +61,8 @@ pxbaas db create-table <name> \
48
61
 
49
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`
50
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.
51
66
 
52
67
  ## db add-column — add a column (destructive DDL)
53
68
 
@@ -58,6 +73,8 @@ pxbaas db add-column <table> <column> --type text --nullable --default <value> \
58
73
 
59
74
  - **Flags:** `--type <t>` (default `text`) · `--nullable` (default true) · `--default <value>` · `--schema` (default `public`) · `--role` (default service_role) · `--json`
60
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`.
61
78
 
62
79
  ## gen types — generate type definitions
63
80
 
@@ -12,14 +12,20 @@ most common source of 401/403 confusion.
12
12
  | `/api/platform/*` (Studio: tenants CRUD) | **dashboard Basic Auth** (`DASHBOARD_USERNAME`/`DASHBOARD_PASSWORD`) | global service key → `401 Unauthorized` |
13
13
  | `/pg/query` (ad-hoc SQL) | **global service_role key**, sent as BOTH `Authorization: Bearer` and `apikey` | Basic Auth → 401 "apikey or Bearer required"; per-tenant key → `401` |
14
14
  | Data plane `/rest/v1`, `/auth/v1`; Management API `/v1/projects/default/*` (apps create/publish, functions deploy) | **per-tenant key** (Management API also needs `X-Tenant-Id`) | global key + `X-Tenant-Id` → `403 Global credentials cannot access tenant resources` |
15
+ | `/api/tenant-ddl/*` (tenant self-service DDL + RLS) | **per-tenant service_role key** — send only `Authorization: Bearer`; Kong verifies the JWT and injects the trusted `X-Tenant-Id` (never send it yourself) | global key → `403 GLOBAL_KEY_NOT_ALLOWED`; per-tenant anon → `403 ROLE_NOT_ALLOWED`; suspended tenant → `403 TENANT_NOT_ACTIVE` |
16
+
17
+ > RLS registry management is symmetric: `rls/tables` and `rls/policies` both support POST/GET/DELETE
18
+ > (DELETE on `rls/tables` does not require the table to still exist, so it cleans up after a DROP).
15
19
 
16
20
  - A per-tenant anon/service key carries a `tenant_id` claim, so **REST and Auth route from the
17
21
  key alone** — `X-Tenant-Id` is optional there. Issued user JWTs carry `aud=<tenant_id>`.
18
22
  - Using tenant X's key with `X-Tenant-Id: Y` → `403 Token tenant mismatch` (gateway guardrail).
19
- - CLI: `pxbaas login --endpoint <url> --tenant-id <id>` with the tenant's keys gives a working
20
- MT profile (`app capabilities`, `functions`, `rest` all fine), but **`pxbaas db query` returns
21
- 401 in MT** (the pg-meta path does not accept tenant keys). Run tenant SQL through
22
- `/pg/query` with the global key, using fully-qualified `tenant_<id>.<table>` names.
23
+ - CLI: `pxbaas login --endpoint <url> --tenant-id <id>` with the tenant's per-tenant keys gives a
24
+ working MT profile. As of the tenant self-service DDL capability, **`pxbaas db query`, `db push`,
25
+ and `app db-init` all work in MT** — they route through `/api/tenant-ddl` with the per-tenant
26
+ service_role key (DDL runs on the tenant's own db; the tenant MySQL account's `GRANT ALL ON
27
+ tenant_<id>.*` is the hard isolation boundary). The old "`db query` 401s in MT, use the global key
28
+ on `/pg/query`" guidance is obsolete; `/pg/query` remains platform-admin only.
23
29
 
24
30
  ## 2. Provisioning a tenant
25
31
 
@@ -63,6 +69,31 @@ curl -sS -o /dev/null -w '%{http_code}\n' -X POST "$ENDPOINT/rest/v1/posts" \
63
69
  -d '{"title":"x","author_id":"someone-else","status":"draft"}' # expect 403 PGRST302
64
70
  ```
65
71
 
72
+ ## 3b. Tenant self-service DDL / db-init as a normal tenant
73
+
74
+ A normal tenant (holding only its per-tenant service_role key — no platform admin credential) can
75
+ initialize and tear down its own app database through the CLI:
76
+
77
+ ```bash
78
+ pxbaas login --endpoint <url> --tenant-id <id> # paste the tenant's per-tenant anon + service keys
79
+ pxbaas app db-init <name> --dir ./apps --dry-run # preview the plan (DDL + seed + RLS)
80
+ pxbaas app db-init <name> --dir ./apps --yes # apply (stop-on-first-error, no rollback)
81
+ pxbaas db query "SELECT count(*) FROM posts" --json
82
+ pxbaas app destroy <name> --confirm --purge-db # optional: also drop the app's tables + RLS rows
83
+ ```
84
+
85
+ - `db-init`/`db push` read `db/schema.sql` (DDL + seed) and the declarative `db/rls.json` (RLS).
86
+ In MT the `__SCHEMA__` qualifier is dropped (the connection is already bound to the tenant db) and
87
+ cross-schema names are refused by GRANT.
88
+ - **RLS is registered through the structured `/api/tenant-ddl/rls/*` endpoints**, not raw central
89
+ INSERTs (the tenant account has no GRANT on the shared `supabase` db). `table_schema` is bound
90
+ server-side to the verified tenant db — the CLI never sends it, so a tenant cannot touch another's
91
+ policies. If `db/rls.json` is absent, RLS INSERTs embedded in `schema.sql` are parsed and routed
92
+ the same way.
93
+ - `--purge-db` DROPs the app's tables and DELETEs its RLS registry rows — both the policies and the
94
+ central `_mysqlrest_rls_tables` toggle (via `DELETE /api/tenant-ddl/rls/tables`). On instances that
95
+ predate that endpoint the toggle row is left (harmless; re-created on the next `db-init`).
96
+
66
97
  ## 4. Deploying a webapp into a tenant
67
98
 
68
99
  - Every Management API call needs `Authorization: Bearer <tenant service_role_key>` **and**