@polardbx/pxbaas 0.1.0-beta.3 → 0.1.0-beta.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -8
- package/dist/app/local-index.d.ts +70 -0
- package/dist/app/local-index.d.ts.map +1 -0
- package/dist/app/local-index.js +140 -0
- package/dist/app/local-index.js.map +1 -0
- package/dist/app/preflight.d.ts +12 -0
- package/dist/app/preflight.d.ts.map +1 -1
- package/dist/app/preflight.js +30 -17
- package/dist/app/preflight.js.map +1 -1
- package/dist/cli/root.d.ts.map +1 -1
- package/dist/cli/root.js +3 -0
- package/dist/cli/root.js.map +1 -1
- package/dist/commands/app/index.d.ts.map +1 -1
- package/dist/commands/app/index.js +398 -46
- package/dist/commands/app/index.js.map +1 -1
- package/dist/commands/completion/index.d.ts.map +1 -1
- package/dist/commands/completion/index.js +5 -1
- package/dist/commands/completion/index.js.map +1 -1
- package/dist/commands/data/index.d.ts +52 -0
- package/dist/commands/data/index.d.ts.map +1 -1
- package/dist/commands/data/index.js +201 -39
- package/dist/commands/data/index.js.map +1 -1
- package/dist/commands/db/index.d.ts +14 -0
- package/dist/commands/db/index.d.ts.map +1 -1
- package/dist/commands/db/index.js +126 -28
- package/dist/commands/db/index.js.map +1 -1
- package/dist/commands/functions/new.d.ts +4 -0
- package/dist/commands/functions/new.d.ts.map +1 -1
- package/dist/commands/functions/new.js +24 -9
- package/dist/commands/functions/new.js.map +1 -1
- package/dist/commands/login/index.d.ts.map +1 -1
- package/dist/commands/login/index.js +15 -30
- package/dist/commands/login/index.js.map +1 -1
- package/dist/commands/profile/import-env.d.ts +7 -2
- package/dist/commands/profile/import-env.d.ts.map +1 -1
- package/dist/commands/profile/import-env.js +141 -16
- package/dist/commands/profile/import-env.js.map +1 -1
- package/dist/commands/profile/index.d.ts.map +1 -1
- package/dist/commands/profile/index.js +13 -1
- package/dist/commands/profile/index.js.map +1 -1
- package/dist/commands/storage/index.d.ts.map +1 -1
- package/dist/commands/storage/index.js +209 -7
- package/dist/commands/storage/index.js.map +1 -1
- package/dist/commands/tenant/index.d.ts +19 -0
- package/dist/commands/tenant/index.d.ts.map +1 -0
- package/dist/commands/tenant/index.js +667 -0
- package/dist/commands/tenant/index.js.map +1 -0
- package/dist/commands/whoami/index.js +19 -0
- package/dist/commands/whoami/index.js.map +1 -1
- package/dist/credentials/credential-store.d.ts +30 -1
- package/dist/credentials/credential-store.d.ts.map +1 -1
- package/dist/credentials/credential-store.js +114 -12
- package/dist/credentials/credential-store.js.map +1 -1
- package/dist/credentials/env-file.d.ts +48 -7
- package/dist/credentials/env-file.d.ts.map +1 -1
- package/dist/credentials/env-file.js +91 -16
- package/dist/credentials/env-file.js.map +1 -1
- package/dist/credentials/jwt-introspect.d.ts +7 -0
- package/dist/credentials/jwt-introspect.d.ts.map +1 -1
- package/dist/credentials/jwt-introspect.js.map +1 -1
- package/dist/credentials/masker.d.ts +3 -0
- package/dist/credentials/masker.d.ts.map +1 -1
- package/dist/credentials/masker.js +3 -0
- package/dist/credentials/masker.js.map +1 -1
- package/dist/credentials/profile.d.ts +12 -0
- package/dist/credentials/profile.d.ts.map +1 -1
- package/dist/credentials/profile.js +23 -0
- package/dist/credentials/profile.js.map +1 -1
- package/dist/credentials/tty-guard.d.ts +10 -0
- package/dist/credentials/tty-guard.d.ts.map +1 -1
- package/dist/credentials/tty-guard.js +32 -0
- package/dist/credentials/tty-guard.js.map +1 -1
- package/dist/main.js +21 -2
- package/dist/main.js.map +1 -1
- package/dist/tenant/bundle.d.ts +66 -0
- package/dist/tenant/bundle.d.ts.map +1 -0
- package/dist/tenant/bundle.js +132 -0
- package/dist/tenant/bundle.js.map +1 -0
- package/dist/tenant/guide.d.ts +50 -0
- package/dist/tenant/guide.d.ts.map +1 -0
- package/dist/tenant/guide.js +152 -0
- package/dist/tenant/guide.js.map +1 -0
- package/dist/tenant/probe.d.ts +65 -0
- package/dist/tenant/probe.d.ts.map +1 -0
- package/dist/tenant/probe.js +159 -0
- package/dist/tenant/probe.js.map +1 -0
- package/dist/tenant/verify.d.ts +37 -0
- package/dist/tenant/verify.d.ts.map +1 -0
- package/dist/tenant/verify.js +132 -0
- package/dist/tenant/verify.js.map +1 -0
- package/dist/version.d.ts +29 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +65 -0
- package/dist/version.js.map +1 -0
- package/package.json +2 -2
- package/skills/pxbaas-app-spec/references/runtime-contract.md +1 -1
- package/skills/pxbaas-cli/SKILL.md +14 -5
- package/skills/pxbaas-cli/references/auth-and-credentials.md +19 -3
- package/skills/pxbaas-cli/references/data-postgrest.md +4 -3
- package/skills/pxbaas-cli/references/db-and-meta.md +5 -0
- package/skills/pxbaas-cli/references/safety-and-two-phase.md +42 -13
- package/skills/pxbaas-cli/references/tenant-admin.md +122 -0
- package/skills/pxbaas-cli/references/troubleshooting.md +4 -1
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# Tenant Administration (platform-operator surface)
|
|
2
|
+
|
|
3
|
+
Covers: `tenant login` · `tenant logout` · `tenant probe` · `tenant ls` · `tenant create` · `tenant keys` · `tenant repair-keys` · `tenant handoffs`, and the tenant access bundle.
|
|
4
|
+
|
|
5
|
+
**Who this is for:** the operator of a **multi-tenant** PolarDB-X BaaS instance. Ordinary tenants never use this surface — they hold per-tenant API keys and use `db` / `data` / `app`. Never point a tenant at these commands.
|
|
6
|
+
|
|
7
|
+
## Credential model (read this first)
|
|
8
|
+
|
|
9
|
+
| | Data plane | Admin plane |
|
|
10
|
+
| --- | --- | --- |
|
|
11
|
+
| Secret | per-tenant `anon` + `service_role` JWT | instance dashboard HTTP Basic (`DASHBOARD_USERNAME` / `DASHBOARD_PASSWORD`) |
|
|
12
|
+
| Keyring account | `credentials/<profile>` | `admin/<profile>` |
|
|
13
|
+
| Used by | `db`, `data`, `storage`, `auth`, `functions`, `app` | `tenant *` |
|
|
14
|
+
|
|
15
|
+
Rules that hold in code, not convention:
|
|
16
|
+
|
|
17
|
+
- **The password never goes in argv.** `--dashboard-user`, `--dashboard-password`, `--admin-password` are refused by `assertNoKeyArgs`. Supply them via TTY prompt (masked) or via `--user-stdin --password-stdin` (two lines: username, then password; both flags are required together).
|
|
18
|
+
- **`DASHBOARD_*` found in an env file is NOT imported by `profile import`.** It is reported as present-but-not-imported. Only `--with-admin` binds it, and then only after a successful admin-channel probe.
|
|
19
|
+
- **Endpoint must be the Kong gateway.** Studio's `/api/platform/*` routes do not authenticate on their own (`apiWrapper` is called without `withAuth`); the only gate is Kong's dashboard `basic-auth`. Targeting a component port bypasses it and is refused at the prompt.
|
|
20
|
+
- Plaintext is never printed. Output carries fingerprints (`sha256:<8>...<4>`); the only sinks for plaintext keys are the keyring (`--as-profile`) and a `0600` access bundle (`--bundle`).
|
|
21
|
+
|
|
22
|
+
## Capability probe
|
|
23
|
+
|
|
24
|
+
`pxbaas tenant probe --json` answers, in one read-only call, whether the current profile can operate the admin plane:
|
|
25
|
+
|
|
26
|
+
| `status` | meaning | next move |
|
|
27
|
+
| --- | --- | --- |
|
|
28
|
+
| `admin` | admin channel works | `tenant ls`, then `create` / `keys` |
|
|
29
|
+
| `not-admin` | 401/403 from the gateway | these are not dashboard credentials |
|
|
30
|
+
| `single-tenant` | instance reports `multiTenantMode:false` | tenant administration does not apply |
|
|
31
|
+
| `metadata-unreadable` | credentials accepted but tenant metadata fails to load | platform-side schema drift — do not report "no tenants" |
|
|
32
|
+
| `unreachable` | network/endpoint failure | fix connectivity first |
|
|
33
|
+
|
|
34
|
+
Why `metadata-unreadable` exists: the list endpoint also returns `200 + tenants: []` when its own query fails. The probe disambiguates by asking for a sentinel tenant id — `404` means the metadata path is healthy (so the empty list is real), `500` means it is not.
|
|
35
|
+
|
|
36
|
+
## Command reference
|
|
37
|
+
|
|
38
|
+
| Command | Options (defaults) | Notes |
|
|
39
|
+
| --- | --- | --- |
|
|
40
|
+
| `tenant login` | `--endpoint <url>`, `--user <name>`, `--user-stdin`, `--password-stdin` | Probes **before** storing; a refused probe stores nothing. Creates an admin-only profile when none exists (it holds no tenant keys, so data-plane commands on it still ask for `pxbaas login`) |
|
|
41
|
+
| `tenant logout` | — | Removes dashboard credentials only; tenant keys untouched |
|
|
42
|
+
| `tenant probe` | — | Read-only |
|
|
43
|
+
| `tenant ls` | `--status <active\|suspended\|deleting\|deleted>` | Redacted rows: id, status, plan, db, plus `hasAnonKey` / `hasServiceRoleKey` booleans. A missing key shows a `repair-keys` hint |
|
|
44
|
+
| `tenant create [tenant-id]` | `--generate-id`, `--plan` (`free`), `--display-name`, `--retention-days <n>`, `--reactivate`, `--dry-run`, `--yes`, `--as-profile [name]`, `--bundle <file>`, `--intranet <url>`, `--overwrite` | Id must match `^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$` |
|
|
45
|
+
| `tenant keys <tenant-id>` | `--as-profile [name]`, `--bundle <file>`, `--intranet <url>`, `--overwrite` | Requires one destination; refuses to bundle when either key is NULL |
|
|
46
|
+
| `tenant repair-keys` | `--confirm` | Without `--confirm` prints which tenants are missing keys and changes nothing |
|
|
47
|
+
| `tenant handoffs` | — | Local ledger: tenant id, timestamps, fingerprints, bundle path + sha256. Never key material |
|
|
48
|
+
|
|
49
|
+
All of them accept `--json` and `--profile <name>`.
|
|
50
|
+
|
|
51
|
+
## Two-phase behaviour
|
|
52
|
+
|
|
53
|
+
| Command | Preview | Authorization |
|
|
54
|
+
| --- | --- | --- |
|
|
55
|
+
| `tenant create` | `--dry-run` (shows plan + side effects) | TTY confirm, or `--yes`; without it **nothing is created** |
|
|
56
|
+
| `tenant repair-keys` | default (no `--confirm`) | `--confirm` |
|
|
57
|
+
| `tenant keys` / `ls` / `probe` / `handoffs` | n/a | read-only (writes are local: keyring / bundle file) |
|
|
58
|
+
|
|
59
|
+
`tenant create` exits non-zero (`process.exitCode = 2`) when post-create verification fails — a `201` from the platform is **not** proof of a usable tenant, because CREATE DATABASE / CREATE USER / GRANT / RLS tables / Realtime registration are non-fatal server-side steps. Verification runs `SELECT DATABASE()`, an `information_schema` table count, the RLS registry read, and reports `realtime_provisioned`.
|
|
60
|
+
|
|
61
|
+
## Danger: same-name create against a soft-deleted tenant
|
|
62
|
+
|
|
63
|
+
The platform's create path resurrects a `status='deleted'` row and **regenerates its `jwt_secret` and both API keys**. That silently invalidates whoever held the old keys and brings any surviving data back under new credentials. The CLI therefore refuses a colliding id by default:
|
|
64
|
+
|
|
65
|
+
- live tenant with the same id → refused, tells you to use `tenant keys <id>` instead
|
|
66
|
+
- soft-deleted tenant → refused unless you pass `--reactivate`
|
|
67
|
+
|
|
68
|
+
Never retry a failed create with a different flag combination hoping it will pass. Read the message.
|
|
69
|
+
|
|
70
|
+
## Tenant access bundle (handing keys to another human)
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
pxbaas tenant create acme --bundle ./acme.pxbaas.env # new tenant + bundle
|
|
74
|
+
pxbaas tenant keys acme --bundle ./acme.pxbaas.env # existing tenant + bundle
|
|
75
|
+
pxbaas tenant keys acme --as-profile # bind into this machine's keyring
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Admin credentials come from the active profile (`tenant login`), not from a per-command flag.
|
|
79
|
+
|
|
80
|
+
`--bundle` writes `0600` and refuses an existing path unless `--overwrite`. The file contains exactly:
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
MULTI_TENANT_MODE=true
|
|
84
|
+
SUPABASE_URL=<gateway>
|
|
85
|
+
DEFAULT_TENANT_ID=<tenant>
|
|
86
|
+
TENANT_ANON_KEY=<key>
|
|
87
|
+
TENANT_SERVICE_ROLE_KEY=<key>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Built by explicit field list, so `jwt_secret`, `DASHBOARD_*` and `GLOBAL_*` cannot appear in it. It is directly consumable by the receiving user:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
pxbaas profile import ./acme.pxbaas.env --name acme --json
|
|
94
|
+
pxbaas whoami --json # expect mode=multi-tenant, tenantId=acme
|
|
95
|
+
pxbaas doctor --json # Tenant Consistency must pass
|
|
96
|
+
pxbaas db tables --json
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
`create`/`keys` also print a copy-paste handoff note (import steps + both fingerprints) and append a line to `~/.baas/handoffs.json`. The receiver compares those fingerprints against `whoami` to prove nothing was swapped in transit.
|
|
100
|
+
|
|
101
|
+
**Treat a bundle as a long-lived plaintext credential.** Keys are signed with `exp ≈ 10 years` and the platform has **no revocation**: `PATCH /api/platform/tenants/[id]` does not accept `jwt_secret`, and HS256 tokens carry no `jti`, so re-signing keys does not invalidate old ones. Consequences for you as agent:
|
|
102
|
+
|
|
103
|
+
- never cat/read a bundle file into the conversation, never echo its contents, never commit it (add `*.pxbaas.env` to ignores)
|
|
104
|
+
- do not upload it to chat, ticket attachments, or shared drives; that channel choice is the operator's job
|
|
105
|
+
- tell the operator that distributing a bundle = distributing an irrevocable admin-grade credential for that tenant
|
|
106
|
+
|
|
107
|
+
## Gap guidance from `profile import`
|
|
108
|
+
|
|
109
|
+
When a multi-tenant env file lacks tenant-level keys, `profile import` now prints a diagnosis (which variables exist, whether each JWT carries `tenant_id`, whether `DASHBOARD_*` is present), states `Nothing was created and nothing was imported`, and routes to one of:
|
|
110
|
+
|
|
111
|
+
- **admin path** — when the file's dashboard credentials actually open the admin channel: `tenant login` → `tenant ls` → `tenant keys <id> --as-profile|--bundle`
|
|
112
|
+
- **request path** — otherwise: a checklist to send to the operator (tenant id + both keys, plus how to prove a key is tenant-level: its JWT payload must contain `tenant_id`)
|
|
113
|
+
|
|
114
|
+
The transient probe used for this routing is read-only and does **not** persist the dashboard credentials it borrowed from the file.
|
|
115
|
+
|
|
116
|
+
## Agent rules for this surface
|
|
117
|
+
|
|
118
|
+
1. **Never create a tenant on someone else's behalf without explicit confirmation** — it provisions a database, a MySQL account, GRANTs, a storage bucket and quota consumption.
|
|
119
|
+
2. **Prefer `tenant keys <id>` over `tenant create`**: most "I have no key" cases are an existing tenant whose key was never handed over. Run `tenant ls` first.
|
|
120
|
+
3. `tenant repair-keys` is the safe fix for a tenant visible with `hasAnonKey=false`; it is idempotent and re-signs from the existing `jwt_secret`. Do not present it as rotation.
|
|
121
|
+
4. Do not run `tenant create` with `--reactivate` unless the user has read the resurrection warning above.
|
|
122
|
+
5. There is no `suspend` / `resume` / `delete` / `quotas` in this surface yet — do not invent them; use Studio for tenant lifecycle beyond create.
|
|
@@ -57,7 +57,10 @@ pxbaas whoami --json # verify profile / tenant / role / key fingerprints
|
|
|
57
57
|
| MT: `/pg/query` 401 | it takes the **global** service key as both `Authorization` and `apikey`; Basic Auth and tenant keys are rejected | switch credential tier |
|
|
58
58
|
| MT: `403 Global credentials cannot access tenant resources` | global key used on a tenant-scoped endpoint | use that tenant's own anon/service key |
|
|
59
59
|
| MT: `403 Token tenant mismatch` | the key's `tenant_id` claim ≠ `X-Tenant-Id` | align them; never cross tenants |
|
|
60
|
-
| MT: `db query` 401 on a tenant profile |
|
|
60
|
+
| MT: `db query` 401 on a tenant profile | obsolete workaround — MT SQL no longer goes through `/pg/query`; something is wrong with the profile or the key | check `pxbaas doctor --json` (Tenant Consistency, JWT Expiry) and re-login with that tenant's per-tenant keys; `/pg/query` stays platform-admin only |
|
|
61
|
+
| `profile import` refuses a MT env file ("lacks tenant-level credentials") | the file only carries platform-global keys (JWT has no `tenant_id`) | follow the printed route: operator → `tenant login` then `tenant keys <id> --as-profile`; non-operator → send the printed checklist to the operator. Never create a tenant to work around a missing key |
|
|
62
|
+
| `tenant login` says "Not saved. ... could not open the tenant admin channel" | wrong credentials for the admin plane, or endpoint is not the Kong gateway | use the gateway URL (typically :8000); dashboard Basic creds are required — an API key never works here |
|
|
63
|
+
| `tenant probe` → `metadata-unreadable` | the credentials are fine but tenant metadata fails to load (schema drift on long-lived instances) | do not report "no tenants"; escalate to the platform operator |
|
|
61
64
|
| MT: RLS policies registered but have **no effect** | wrong `table_schema` (must be the tenant DB name, not `public`), or written into the tenant DB's unread `_mysqlrest_rls_*` copies | rewrite into the central `supabase._mysqlrest_rls_*`, then verify behaviourally (anon read / forged-owner write → 403) |
|
|
62
65
|
| `app publish` → `APP_MANIFEST_INVALID` | manifest `name` ≠ mount `appName`, or a v1 `mpa` manifest on a schemaVersion-2-only instance | align `name`; migrate the manifest (pxbaas-app-spec `deploy-options.md`) |
|
|
63
66
|
| `app publish` → `APP_SSR_PROBE_FAILED` | **platform-side**: cold tenant sandbox vs the probe budget (heavy `npm:` graph, ≈60 s) | warm the sandbox with a legacy function, then retry publish 3–5× |
|