@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
+ * 租户交付包(tenant access bundle)。
3
+ *
4
+ * 解决的问题:管理员在 CLI 里拿到了新租户的密钥之后,怎么把它交给"另一个真人"。
5
+ * 交付物是一份**规范 env 文件**,格式与 `pxbaas profile import` 的多租户输入契约逐字
6
+ * 对齐(credentials/env-file.ts 的 TENANT_* 分支),所以收件人不需要手改任何字段名。
7
+ *
8
+ * 三条硬纪律:
9
+ * 1. **字段白名单**。bundle 内容由本模块**逐字段显式拼装**,因此结构上不可能包含
10
+ * `jwt_secret`(租户签名母密钥)、`DASHBOARD_*`(实例管理口令)或 `GLOBAL_*`
11
+ * (平台全局密钥)。这不是"记得过滤",而是根本没有把它们写进去的入口。
12
+ * 2. **明文只落 0600 文件,绝不进 stdout / `--json` / 日志**。终端上只出现指纹。
13
+ * 3. **不留存于内存之外**:调用方拿到 keys 后要么写钥匙串,要么交给本模块写文件。
14
+ */
15
+ import { createHash } from 'node:crypto';
16
+ import { existsSync } from 'node:fs';
17
+ import { chmod, mkdir, readFile, writeFile } from 'node:fs/promises';
18
+ import { dirname, join, resolve } from 'node:path';
19
+ import { homedir } from 'node:os';
20
+ import { computeFingerprint } from '../credentials/index.js';
21
+ export const BUNDLE_VERSION = 1;
22
+ /**
23
+ * 渲染交付包文本。只输出这 5(+1)个变量:
24
+ * MULTI_TENANT_MODE / SUPABASE_URL / DEFAULT_TENANT_ID / TENANT_ANON_KEY /
25
+ * TENANT_SERVICE_ROLE_KEY(可选 SUPABASE_INTRANET_URL)。
26
+ */
27
+ export function renderBundle(input) {
28
+ const endpoint = stripSlash(input.endpoint);
29
+ const generatedAt = input.generatedAt ?? new Date().toISOString();
30
+ // 注释头:parseEnvText 会安全忽略 '#' 行,因此头部不需要解析器改动。
31
+ const header = [
32
+ '# PolarDB-X BaaS 租户接入包 (tenant access bundle)',
33
+ `# tenant_id : ${input.tenantId}`,
34
+ `# instance : ${endpoint} (Kong 网关,勿改成 Studio/内网调试端口)`,
35
+ `# created_at : ${generatedAt}`,
36
+ input.issuedBy ? `# issued_by : ${input.issuedBy}` : undefined,
37
+ '#',
38
+ '# ⚠ 本文件含明文租户密钥,且:有效期约 10 年、当前平台不支持撤销。',
39
+ '# - 只通过安全通道传输(受控网盘 / 密邮正文 / 当面拷贝);不要贴进 IM 群、工单附件、截图或聊天记录',
40
+ '# - 收件人导入成功后请删除本文件(凭据会进入其操作系统钥匙串)',
41
+ '# - 怀疑泄露:当前只能由平台侧重建该租户',
42
+ '# ============================================================',
43
+ 'MULTI_TENANT_MODE=true',
44
+ `SUPABASE_URL=${endpoint}`,
45
+ `DEFAULT_TENANT_ID=${input.tenantId}`,
46
+ `TENANT_ANON_KEY=${input.anonKey}`,
47
+ `TENANT_SERVICE_ROLE_KEY=${input.serviceRoleKey}`,
48
+ ];
49
+ if (input.intranetEndpoint) {
50
+ header.push(`SUPABASE_INTRANET_URL=${stripSlash(input.intranetEndpoint)}`);
51
+ }
52
+ return header.join('\n') + '\n';
53
+ }
54
+ /**
55
+ * 写交付包。目标已存在时**必须**显式 `overwrite`,否则拒绝 —— 不静默覆盖别人的接入包。
56
+ */
57
+ export async function writeBundle(file, input, options = {}) {
58
+ const path = resolve(file);
59
+ if (existsSync(path) && !options.overwrite) {
60
+ throw new Error(`Bundle file already exists: ${path}. Pass --overwrite to replace it (the old bundle's keys stay valid — overwriting the file does NOT revoke them).`);
61
+ }
62
+ const text = renderBundle(input);
63
+ const dir = dirname(path);
64
+ if (!existsSync(dir)) {
65
+ await mkdir(dir, { mode: 0o700, recursive: true });
66
+ }
67
+ await writeFile(path, text, { mode: 0o600 });
68
+ await chmod(path, 0o600);
69
+ const generatedAt = input.generatedAt ?? new Date().toISOString();
70
+ const record = {
71
+ tenantId: input.tenantId,
72
+ endpoint: stripSlash(input.endpoint),
73
+ generatedAt,
74
+ anonFingerprint: computeFingerprint(input.anonKey),
75
+ serviceFingerprint: computeFingerprint(input.serviceRoleKey),
76
+ bundlePath: path,
77
+ bundleSha256: createHash('sha256').update(text).digest('hex'),
78
+ issuedBy: input.issuedBy,
79
+ };
80
+ await appendHandoffRecord(record, options.homeDir);
81
+ return { path, bytes: Buffer.byteLength(text), record, instructions: renderInstructions(record) };
82
+ }
83
+ /** 可直接复制转发给使用者的交接说明(不含任何密钥值)。 */
84
+ export function renderInstructions(record) {
85
+ return [
86
+ `下一步:把 ${record.bundlePath} 通过安全通道发给该租户的使用者,并附上以下说明。`,
87
+ '',
88
+ ` 1) 安装 CLI(需 Node.js >= 20): npm install -g @polardbx/pxbaas@beta`,
89
+ ` 2) 导入接入包: pxbaas profile import ${record.bundlePath.split('/').pop()} --name ${record.tenantId} --json`,
90
+ ' 3) 自检: pxbaas whoami --json → 期望 mode=multi-tenant、tenantId=' + record.tenantId,
91
+ ' 4) 健康检查: pxbaas doctor --json → 5 项全 pass,Tenant Consistency 必须 pass',
92
+ ' 5) 打通数据面: pxbaas db tables --json',
93
+ ' 6) 全部通过后删除该 env 文件',
94
+ '',
95
+ ' 指纹核对(证明收件人拿到的就是这份包,防传输途中被替换):',
96
+ ` anon: ${record.anonFingerprint}`,
97
+ ` service_role: ${record.serviceFingerprint}`,
98
+ ' 收件人在 `pxbaas whoami --json` 里比对 anonKey.fingerprint / serviceKey.fingerprint 是否一致。',
99
+ ].join('\n');
100
+ }
101
+ const HANDOFF_FILE_NAME = 'handoffs.json';
102
+ export async function listHandoffs(homeDir) {
103
+ const path = join(homeDir ?? join(homedir(), '.baas'), HANDOFF_FILE_NAME);
104
+ if (!existsSync(path))
105
+ return [];
106
+ try {
107
+ const parsed = JSON.parse(await readFile(path, 'utf-8'));
108
+ return parsed.records ?? [];
109
+ }
110
+ catch {
111
+ return [];
112
+ }
113
+ }
114
+ /**
115
+ * 签发台账:只记指纹与文件哈希,**不含任何明文密钥**。0600。
116
+ * 泄露事件回溯时这是唯一能回答"我发过哪些包、对应哪个租户"的记录。
117
+ */
118
+ export async function appendHandoffRecord(record, homeDir) {
119
+ const dir = homeDir ?? join(homedir(), '.baas');
120
+ const path = join(dir, HANDOFF_FILE_NAME);
121
+ const records = await listHandoffs(dir);
122
+ records.push(record);
123
+ if (!existsSync(dir)) {
124
+ await mkdir(dir, { mode: 0o700, recursive: true });
125
+ }
126
+ await writeFile(path, JSON.stringify({ version: BUNDLE_VERSION, records }, null, 2) + '\n', { mode: 0o600 });
127
+ await chmod(path, 0o600);
128
+ }
129
+ function stripSlash(value) {
130
+ return value.replace(/\/+$/, '');
131
+ }
132
+ //# sourceMappingURL=bundle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bundle.js","sourceRoot":"","sources":["../../src/tenant/bundle.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * 多租户接入缺口的诊断与引导(方案文档 §5.4)。
3
+ *
4
+ * 原来 `profile import` 撞到"多租户 env 却没有租户级凭据"时只抛一句拒绝,而它给出的
5
+ * 两个建议("重新生成 client.env"、"用 per-tenant key 登录")**都是使用者自己做不到**
6
+ * 的动作——租户级密钥由平台签发。结果是 CLI 把用户堵在门口,还让他以为是自己的错。
7
+ *
8
+ * 本模块把这一处变成两条明确出口:
9
+ * - 非管理员 → 一份可以直接转给实例管理员的**申请清单**(要哪个租户、哪两把值、
10
+ * 以及怎么判断拿到的到底是不是租户级密钥);
11
+ * - 具备管理面能力 → 指出可以用 `pxbaas tenant ...` 自助开通/取键,并给出命令。
12
+ *
13
+ * 边界:这里**只读、只打印**,绝不在失败分支里创建租户(读路径不携带建库/建账号/
14
+ * 占配额这类写副作用,见方案 N2)。管理凭据也只做**临时**探测,绝不落库(N3)。
15
+ */
16
+ import { type ParsedEnv } from '../credentials/index.js';
17
+ import { type TenantProbeResult } from './probe.js';
18
+ export interface KeyFinding {
19
+ variable: string;
20
+ role?: string;
21
+ /** JWT 里的 tenant_id claim;无 = 平台全局密钥,无法访问租户资源。 */
22
+ tenantId?: string;
23
+ expired: boolean;
24
+ fingerprint: string;
25
+ note: string;
26
+ }
27
+ export interface TenantGapDiagnostics {
28
+ envFile?: string;
29
+ endpoint?: string;
30
+ multiTenantMode?: string;
31
+ defaultTenantId?: string;
32
+ tenantLevelKeysPresent: string[];
33
+ keyFindings: KeyFinding[];
34
+ /** env 文件里是否有成对的 dashboard 凭据(仅报告存在,不导入)。 */
35
+ adminCredentialsPresent: boolean;
36
+ }
37
+ /** 纯本地解析:不联网、不写任何状态。 */
38
+ export declare function diagnoseTenantCredentialGap(env: ParsedEnv, options?: {
39
+ envFile?: string;
40
+ }): TenantGapDiagnostics;
41
+ /** 非管理员出口:一份可原样转给实例管理员的申请清单。 */
42
+ export declare function renderRequestChecklist(diag: TenantGapDiagnostics): string;
43
+ /** 管理员出口:指出可以自助的动作与命令。 */
44
+ export declare function renderAdminPath(diag: TenantGapDiagnostics, probe: TenantProbeResult): string;
45
+ /**
46
+ * 用 env 里的 dashboard 凭据做一次**临时**探测:只在内存里使用,不落库、不写文件。
47
+ * 没有成对凭据时返回 undefined(调用方走非管理员出口)。
48
+ */
49
+ export declare function tryProbeFromEnv(env: ParsedEnv, endpoint: string | undefined): Promise<TenantProbeResult | undefined>;
50
+ //# sourceMappingURL=guide.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guide.d.ts","sourceRoot":"","sources":["../../src/tenant/guide.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAiC,KAAK,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAExF,OAAO,EAAoB,KAAK,iBAAiB,EAAE,MAAM,YAAY,CAAC;AActE,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kDAAkD;IAClD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,sBAAsB,EAAE,MAAM,EAAE,CAAC;IACjC,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,6CAA6C;IAC7C,uBAAuB,EAAE,OAAO,CAAC;CAClC;AAED,wBAAwB;AACxB,wBAAgB,2BAA2B,CACzC,GAAG,EAAE,SAAS,EACd,OAAO,GAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAA;CAAO,GACjC,oBAAoB,CAiDtB;AAED,gCAAgC;AAChC,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,oBAAoB,GAAG,MAAM,CAiCzE;AAED,0BAA0B;AAC1B,wBAAgB,eAAe,CAAC,IAAI,EAAE,oBAAoB,EAAE,KAAK,EAAE,iBAAiB,GAAG,MAAM,CAwB5F;AAED;;;GAGG;AACH,wBAAsB,eAAe,CACnC,GAAG,EAAE,SAAS,EACd,QAAQ,EAAE,MAAM,GAAG,SAAS,GAC3B,OAAO,CAAC,iBAAiB,GAAG,SAAS,CAAC,CAUxC"}
@@ -0,0 +1,152 @@
1
+ /**
2
+ * 多租户接入缺口的诊断与引导(方案文档 §5.4)。
3
+ *
4
+ * 原来 `profile import` 撞到"多租户 env 却没有租户级凭据"时只抛一句拒绝,而它给出的
5
+ * 两个建议("重新生成 client.env"、"用 per-tenant key 登录")**都是使用者自己做不到**
6
+ * 的动作——租户级密钥由平台签发。结果是 CLI 把用户堵在门口,还让他以为是自己的错。
7
+ *
8
+ * 本模块把这一处变成两条明确出口:
9
+ * - 非管理员 → 一份可以直接转给实例管理员的**申请清单**(要哪个租户、哪两把值、
10
+ * 以及怎么判断拿到的到底是不是租户级密钥);
11
+ * - 具备管理面能力 → 指出可以用 `pxbaas tenant ...` 自助开通/取键,并给出命令。
12
+ *
13
+ * 边界:这里**只读、只打印**,绝不在失败分支里创建租户(读路径不携带建库/建账号/
14
+ * 占配额这类写副作用,见方案 N2)。管理凭据也只做**临时**探测,绝不落库(N3)。
15
+ */
16
+ import { computeFingerprint, decodeJwt } from '../credentials/index.js';
17
+ import { resolveAdminFromEnv } from '../credentials/env-file.js';
18
+ import { probeTenantAdmin } from './probe.js';
19
+ /** 判据用的候选变量:先看租户级,再看会被误当租户级的平台全局级。 */
20
+ const KEY_CANDIDATES = [
21
+ 'TENANT_ANON_KEY',
22
+ 'TENANT_SERVICE_ROLE_KEY',
23
+ 'SUPABASE_ANON_KEY',
24
+ 'ANON_KEY',
25
+ 'GLOBAL_ANON_KEY',
26
+ 'SUPABASE_SERVICE_ROLE_KEY',
27
+ 'SERVICE_ROLE_KEY',
28
+ 'GLOBAL_SERVICE_ROLE_KEY',
29
+ ];
30
+ /** 纯本地解析:不联网、不写任何状态。 */
31
+ export function diagnoseTenantCredentialGap(env, options = {}) {
32
+ const keyFindings = [];
33
+ const tenantLevelKeysPresent = [];
34
+ for (const variable of KEY_CANDIDATES) {
35
+ const value = (env[variable] ?? '').trim();
36
+ if (!value)
37
+ continue;
38
+ const parts = value.split('.');
39
+ // 与 whoami / tenant 同一套展示口径:sha256 前 8 + 后 4,前缀只在这一处加。
40
+ const fingerprint = computeFingerprint(value);
41
+ let role;
42
+ let tenantId;
43
+ let expired = false;
44
+ let note = 'Value is not a JWT (three dot-separated parts expected) — cannot be a Supabase-style API key.';
45
+ if (parts.length === 3) {
46
+ try {
47
+ const payload = decodeJwt(value);
48
+ role = payload.role;
49
+ tenantId = payload.tenant_id;
50
+ expired = typeof payload.exp === 'number' && payload.exp * 1000 <= Date.now();
51
+ note = tenantId
52
+ ? `Tenant-level key for tenant "${tenantId}"${expired ? ' (EXPIRED)' : ''}`
53
+ : `Platform-global key (no tenant_id claim)${expired ? ' (EXPIRED)' : ''} — cannot reach tenant resources`;
54
+ }
55
+ catch (error) {
56
+ note = `JWT payload unparsable: ${error instanceof Error ? error.message : String(error)}`;
57
+ }
58
+ }
59
+ if (/^TENANT_/.test(variable))
60
+ tenantLevelKeysPresent.push(variable);
61
+ keyFindings.push({ variable, role, tenantId, expired, fingerprint, note });
62
+ }
63
+ const endpoint = firstNonEmpty([
64
+ env.SUPABASE_URL,
65
+ env.SUPABASE_PUBLIC_URL,
66
+ env.SUPABASE_INTRANET_URL,
67
+ ]);
68
+ return {
69
+ envFile: options.envFile,
70
+ endpoint,
71
+ multiTenantMode: firstNonEmpty([env.MULTI_TENANT_MODE]),
72
+ defaultTenantId: firstNonEmpty([env.DEFAULT_TENANT_ID]),
73
+ tenantLevelKeysPresent,
74
+ keyFindings,
75
+ adminCredentialsPresent: Boolean(resolveAdminFromEnv(env)),
76
+ };
77
+ }
78
+ /** 非管理员出口:一份可原样转给实例管理员的申请清单。 */
79
+ export function renderRequestChecklist(diag) {
80
+ const lines = [
81
+ '你需要的不是新建租户,而是**某个租户的两把 per-tenant 密钥**。请向实例管理员申请:',
82
+ '',
83
+ ` tenant_id: ${diag.defaultTenantId ?? '<要访问的租户 ID>'}`,
84
+ ' anon key: (JWT payload 必须含 tenant_id)',
85
+ ' service role key: (同上)',
86
+ ` 实例端点: ${diag.endpoint ?? '<Kong 网关地址,通常是 :8000>'}`,
87
+ '',
88
+ '拿到后请写成这样一份接入包(变量名必须逐字一致),再导入:',
89
+ '',
90
+ ' MULTI_TENANT_MODE=true',
91
+ ` SUPABASE_URL=${diag.endpoint ?? 'https://<网关地址>'}`,
92
+ ` DEFAULT_TENANT_ID=${diag.defaultTenantId ?? '<租户 ID>'}`,
93
+ ' TENANT_ANON_KEY=<粘贴 anon key>',
94
+ ' TENANT_SERVICE_ROLE_KEY=<粘贴 service role key>',
95
+ '',
96
+ ` pxbaas profile import ./<租户>.pxbaas.env --name <租户> --json`,
97
+ ' pxbaas whoami --json # 期望 mode=multi-tenant 且 tenantId 正确',
98
+ '',
99
+ '自检判据(拿到密钥先解码看一眼,能立刻分辨管理员给的是不是租户级密钥):',
100
+ " printf '%s' \"$TENANT_ANON_KEY\" | cut -d. -f2 | base64 -d",
101
+ ' → payload 里必须出现 "tenant_id":"<租户ID>";没有就是平台全局密钥,用不了。',
102
+ ];
103
+ if (diag.adminCredentialsPresent) {
104
+ lines.push('', '注意:这份 env 里带着 DASHBOARD_USERNAME / DASHBOARD_PASSWORD(实例管理面口令)。', '按安全设计它们不会被导入。若这份文件原本来自管理员,请改由管理员用', '"pxbaas tenant login" + "pxbaas tenant keys <id> --bundle <file>" 正规发放接入包。');
105
+ }
106
+ return lines.join('\n');
107
+ }
108
+ /** 管理员出口:指出可以自助的动作与命令。 */
109
+ export function renderAdminPath(diag, probe) {
110
+ const endpoint = diag.endpoint ?? probe.endpoint;
111
+ const tenant = diag.defaultTenantId;
112
+ const lines = [
113
+ '本机凭据可打开该实例的租户管理面。可以选择自助处理(CLI 不会替你建租户,需显式执行):',
114
+ '',
115
+ ` pxbaas tenant login --endpoint ${endpoint} # 绑定管理凭据(口令走掩码或 stdin,不进 argv)`,
116
+ ' pxbaas tenant ls --json # 看这个实例上到底有哪些租户',
117
+ ];
118
+ if (tenant) {
119
+ lines.push(` pxbaas tenant keys ${tenant} --admin --as-profile # 已有该租户:直接把它的密钥绑进本机钥匙串`, ` pxbaas tenant keys ${tenant} --admin --bundle ./${tenant}.pxbaas.env # 要交给别人:生成规范接入包`, ` pxbaas tenant create ${tenant} --dry-run # 确实需要新租户时先看计划`);
120
+ }
121
+ else {
122
+ lines.push(' pxbaas tenant create <tenant-id> --dry-run # 需要新租户时先看计划');
123
+ }
124
+ lines.push('', ' 提示:先跑 tenant ls 再决定建不建——多数情况该做的是取回已有租户的密钥,', ' 而不是造一个新租户。');
125
+ return lines.join('\n');
126
+ }
127
+ /**
128
+ * 用 env 里的 dashboard 凭据做一次**临时**探测:只在内存里使用,不落库、不写文件。
129
+ * 没有成对凭据时返回 undefined(调用方走非管理员出口)。
130
+ */
131
+ export async function tryProbeFromEnv(env, endpoint) {
132
+ if (!endpoint)
133
+ return undefined;
134
+ const admin = resolveAdminFromEnv(env);
135
+ if (!admin)
136
+ return undefined;
137
+ try {
138
+ return await probeTenantAdmin({ endpoint, username: admin.username, password: admin.password });
139
+ }
140
+ catch {
141
+ // 探测本身失败不该掩盖原始报错:按"不具备管理面"处理即可。
142
+ return undefined;
143
+ }
144
+ }
145
+ function firstNonEmpty(values) {
146
+ for (const value of values) {
147
+ if (typeof value === 'string' && value.trim())
148
+ return value.trim();
149
+ }
150
+ return undefined;
151
+ }
152
+ //# sourceMappingURL=guide.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guide.js","sourceRoot":"","sources":["../../src/tenant/guide.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,65 @@
1
+ /**
2
+ * 平台管理面能力探测(tenant probe)。
3
+ *
4
+ * 为什么要它:`profile import` / `login` 撞墙时,CLI 必须先知道"这个人到底是不是
5
+ * 管理员"才能给出正确的下一步。判据不能用"本机有没有 DASHBOARD_* 凭据"——持有凭据
6
+ * 与被授予权限是两件事(运维把服务端全量 env 发给客户排查问题,客户就"被管理员"了)。
7
+ * 所以这里用一次**只读**调用去问平台,按回答分流。
8
+ *
9
+ * 关键陷阱(方案文档 G3):Studio 的列表接口在「元数据查询失败」时同样返回
10
+ * `200 + { tenants: [], multiTenantMode: true }`(见 pages/api/platform/tenants/index.ts
11
+ * 的三处 200 空返回)。只看 HTTP 状态会把"平台库有问题/存量 schema 漂移"误报成
12
+ * "你没有权限 / 这个实例没有租户"。本模块用详情接口做二次判别:
13
+ * GET /api/platform/tenants/{不存在的 id}
14
+ * → 404 = 查询链路健康 ⇒ 空列表确实是"没有租户"
15
+ * → 500 = 查询本身失败 ⇒ 元数据不可读(多半是 schema 漂移)
16
+ * 这条判据来自 [id].ts 的实现(result.error → 500,未找到 → 404),非猜测。
17
+ */
18
+ import { type RedactedTenant } from '@polardbx/pxbaas-client';
19
+ export type ProbeStatus =
20
+ /** 具备平台管理面能力 */
21
+ 'admin'
22
+ /** 凭据无效/无权限(401/403) */
23
+ | 'not-admin'
24
+ /** 该实例不是多租户实例(multiTenantMode=false) */
25
+ | 'single-tenant'
26
+ /** 元数据不可读:列表为空且详情探测失败(G3/G5) */
27
+ | 'metadata-unreadable'
28
+ /** 网络不可达 / 端点错误 */
29
+ | 'unreachable';
30
+ export interface TenantProbeResult {
31
+ status: ProbeStatus;
32
+ /** 是否可执行租户管理写操作。仅 admin 为 true。 */
33
+ adminCapable: boolean;
34
+ endpoint: string;
35
+ multiTenantMode: boolean;
36
+ /** 仅 admin / metadata-unreadable 时可能有值(后者通常为空)。 */
37
+ tenants?: RedactedTenant[];
38
+ /** 一句话结论,可直接进诊断输出。 */
39
+ detail: string;
40
+ /** 归一化错误码(TENANT_ADMIN_UNAUTHORIZED 等),便于 --json 消费方分支。 */
41
+ errorCode?: string;
42
+ }
43
+ export interface AdminContext {
44
+ endpoint: string;
45
+ username: string;
46
+ password: string;
47
+ profileName: string;
48
+ }
49
+ /**
50
+ * 从当前(或指定)profile 的钥匙串账目里取管理凭据。
51
+ * 缺失时抛可直接展示的错误 —— 调用方不需要再判断"是 CLI 坏了还是没登录"。
52
+ */
53
+ export declare function loadAdminContext(options?: {
54
+ profile?: string;
55
+ }): Promise<AdminContext>;
56
+ export interface ProbeInput {
57
+ endpoint: string;
58
+ username: string;
59
+ password: string;
60
+ /** 探测用租户列表的条数上限无关;此值仅用于详情判别的超时。 */
61
+ timeout?: number;
62
+ }
63
+ /** 用给定凭据探测管理面能力。纯只读:不会发出任何写请求。 */
64
+ export declare function probeTenantAdmin(input: ProbeInput): Promise<TenantProbeResult>;
65
+ //# sourceMappingURL=probe.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"probe.d.ts","sourceRoot":"","sources":["../../src/tenant/probe.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAGL,KAAK,cAAc,EACpB,MAAM,yBAAyB,CAAC;AAMjC,MAAM,MAAM,WAAW;AACrB,gBAAgB;AACd,OAAO;AACT,wBAAwB;GACtB,WAAW;AACb,wCAAwC;GACtC,eAAe;AACjB,gCAAgC;GAC9B,qBAAqB;AACvB,mBAAmB;GACjB,aAAa,CAAC;AAElB,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,WAAW,CAAC;IACpB,mCAAmC;IACnC,YAAY,EAAE,OAAO,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,OAAO,CAAC;IACzB,mDAAmD;IACnD,OAAO,CAAC,EAAE,cAAc,EAAE,CAAC;IAC3B,sBAAsB;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,2DAA2D;IAC3D,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CAAC,OAAO,GAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,OAAO,CAAC,YAAY,CAAC,CA8BhG;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,mCAAmC;IACnC,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,kCAAkC;AAClC,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAkEpF"}
@@ -0,0 +1,159 @@
1
+ /**
2
+ * 平台管理面能力探测(tenant probe)。
3
+ *
4
+ * 为什么要它:`profile import` / `login` 撞墙时,CLI 必须先知道"这个人到底是不是
5
+ * 管理员"才能给出正确的下一步。判据不能用"本机有没有 DASHBOARD_* 凭据"——持有凭据
6
+ * 与被授予权限是两件事(运维把服务端全量 env 发给客户排查问题,客户就"被管理员"了)。
7
+ * 所以这里用一次**只读**调用去问平台,按回答分流。
8
+ *
9
+ * 关键陷阱(方案文档 G3):Studio 的列表接口在「元数据查询失败」时同样返回
10
+ * `200 + { tenants: [], multiTenantMode: true }`(见 pages/api/platform/tenants/index.ts
11
+ * 的三处 200 空返回)。只看 HTTP 状态会把"平台库有问题/存量 schema 漂移"误报成
12
+ * "你没有权限 / 这个实例没有租户"。本模块用详情接口做二次判别:
13
+ * GET /api/platform/tenants/{不存在的 id}
14
+ * → 404 = 查询链路健康 ⇒ 空列表确实是"没有租户"
15
+ * → 500 = 查询本身失败 ⇒ 元数据不可读(多半是 schema 漂移)
16
+ * 这条判据来自 [id].ts 的实现(result.error → 500,未找到 → 404),非猜测。
17
+ */
18
+ import { TenantAdminDriver, TenantAdminError, } from '@polardbx/pxbaas-client';
19
+ import { CredentialStore, ProfileManager } from '../credentials/index.js';
20
+ /** 探测用的哨兵租户 ID:合法字符集内、且按命名规范不可能真实存在。 */
21
+ const METADATA_PROBE_ID = '__pxbaas_probe__';
22
+ /**
23
+ * 从当前(或指定)profile 的钥匙串账目里取管理凭据。
24
+ * 缺失时抛可直接展示的错误 —— 调用方不需要再判断"是 CLI 坏了还是没登录"。
25
+ */
26
+ export async function loadAdminContext(options = {}) {
27
+ const profiles = new ProfileManager();
28
+ const profile = options.profile
29
+ ? await profiles.getProfile(options.profile)
30
+ : await profiles.getCurrentProfile();
31
+ if (!profile) {
32
+ throw new Error(options.profile
33
+ ? `Profile '${options.profile}' not found. Run "pxbaas profile ls" to list profiles.`
34
+ : 'No active profile. Run "pxbaas login" or "pxbaas tenant login --endpoint <url>" first.');
35
+ }
36
+ const store = new CredentialStore();
37
+ const admin = await store.getAdminCredentials(profile.name);
38
+ if (!admin) {
39
+ throw new Error(`Profile '${profile.name}' has no platform admin credentials. ` +
40
+ `Run "pxbaas tenant login --endpoint ${profile.endpoint}" (or add --with-admin to "pxbaas profile import") ` +
41
+ `with the instance dashboard credentials.`);
42
+ }
43
+ return {
44
+ endpoint: profile.endpoint,
45
+ username: admin.username,
46
+ password: admin.password,
47
+ profileName: profile.name,
48
+ };
49
+ }
50
+ /** 用给定凭据探测管理面能力。纯只读:不会发出任何写请求。 */
51
+ export async function probeTenantAdmin(input) {
52
+ const driver = new TenantAdminDriver({
53
+ endpoint: input.endpoint,
54
+ username: input.username,
55
+ password: input.password,
56
+ timeout: input.timeout,
57
+ });
58
+ let tenants;
59
+ let multiTenantMode;
60
+ try {
61
+ const listed = await driver.list();
62
+ tenants = listed.tenants;
63
+ multiTenantMode = listed.multiTenantMode;
64
+ }
65
+ catch (error) {
66
+ return classifyFailure(input.endpoint, error);
67
+ }
68
+ if (!multiTenantMode) {
69
+ return {
70
+ status: 'single-tenant',
71
+ adminCapable: false,
72
+ endpoint: input.endpoint,
73
+ multiTenantMode: false,
74
+ detail: 'This instance is not in multi-tenant mode (multiTenantMode=false). Tenant administration does not apply here.',
75
+ };
76
+ }
77
+ if (tenants.length === 0) {
78
+ // 空列表有两种成因,必须用详情接口判别,否则会把平台故障报成"没有租户"。
79
+ const health = await probeMetadataHealth(driver);
80
+ if (!health.readable) {
81
+ return {
82
+ status: 'metadata-unreadable',
83
+ adminCapable: false,
84
+ endpoint: input.endpoint,
85
+ multiTenantMode: true,
86
+ tenants: [],
87
+ errorCode: health.errorCode,
88
+ detail: 'Credentials are accepted, but the tenant metadata could not be read ' +
89
+ `(${health.errorCode ?? 'unknown error'}). On long-lived instances this usually means schema drift ` +
90
+ '(e.g. a missing column in _platform.tenants) rather than an empty tenant list. ' +
91
+ 'Verify with the platform operator before retrying.',
92
+ };
93
+ }
94
+ return {
95
+ status: 'admin',
96
+ adminCapable: true,
97
+ endpoint: input.endpoint,
98
+ multiTenantMode: true,
99
+ tenants: [],
100
+ detail: 'Admin channel works. This instance has no tenants yet — you can create the first one with "pxbaas tenant create".',
101
+ };
102
+ }
103
+ return {
104
+ status: 'admin',
105
+ adminCapable: true,
106
+ endpoint: input.endpoint,
107
+ multiTenantMode: true,
108
+ tenants,
109
+ detail: `Admin channel works. ${tenants.length} tenant(s) visible on this instance.`,
110
+ };
111
+ }
112
+ async function probeMetadataHealth(driver) {
113
+ try {
114
+ await driver.get(METADATA_PROBE_ID);
115
+ // 哨兵 ID 竟然存在:极不可能,但存在即说明查询链路是通的。
116
+ return { readable: true };
117
+ }
118
+ catch (error) {
119
+ if (error instanceof TenantAdminError) {
120
+ // 404 ⇒ 查询执行成功且没找到 ⇒ 链路健康
121
+ if (error.code === 'TENANT_NOT_FOUND')
122
+ return { readable: true, errorCode: error.code };
123
+ return { readable: false, errorCode: error.code };
124
+ }
125
+ return { readable: false };
126
+ }
127
+ }
128
+ function classifyFailure(endpoint, error) {
129
+ const adminError = error instanceof TenantAdminError
130
+ ? error
131
+ : new TenantAdminError('TENANT_ADMIN_UNKNOWN_ERROR', error instanceof Error ? error.message : String(error));
132
+ if (adminError.code === 'TENANT_ADMIN_UNAUTHORIZED' || adminError.code === 'TENANT_ADMIN_FORBIDDEN') {
133
+ return {
134
+ status: 'not-admin',
135
+ adminCapable: false,
136
+ endpoint,
137
+ multiTenantMode: false,
138
+ errorCode: adminError.code,
139
+ detail: 'These credentials are not accepted by the tenant admin channel ' +
140
+ `(${adminError.code}, HTTP ${adminError.status ?? 'n/a'}). ` +
141
+ 'Tenant administration needs the instance dashboard credentials, not an API key.',
142
+ };
143
+ }
144
+ // 注意:多租户关闭的信号是列表响应里的 `multiTenantMode: false`(GET 集合接口对
145
+ // 非多租户实例返回 200 + 空数组),**不是** HTTP 400 —— 400 只出现在写操作分支
146
+ // ("Multi-tenant mode is not enabled")。所以这里不能把 400 解读成单租户实例,
147
+ // 否则等于凭一个猜测替用户下结论。400 归到"端点没按预期应答"。
148
+ return {
149
+ status: 'unreachable',
150
+ adminCapable: false,
151
+ endpoint,
152
+ multiTenantMode: false,
153
+ errorCode: adminError.code,
154
+ detail: adminError.code === 'TENANT_ADMIN_BAD_REQUEST'
155
+ ? `Tenant admin channel answered 400: ${adminError.message}. This instance did not report multi-tenant mode, so no tenant operation was attempted.`
156
+ : `Tenant admin channel could not be reached: ${adminError.message}`,
157
+ };
158
+ }
159
+ //# sourceMappingURL=probe.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"probe.js","sourceRoot":"","sources":["../../src/tenant/probe.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
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
+ export type VerifyStatus = 'pass' | 'fail' | 'skip';
16
+ export interface VerifyStep {
17
+ name: string;
18
+ status: VerifyStatus;
19
+ detail: string;
20
+ /** 失败时给管理员的可执行补救建议。 */
21
+ remediation?: string;
22
+ }
23
+ export interface VerifyTenantResult {
24
+ ok: boolean;
25
+ /** 租户实际生效的库名(来自 SELECT DATABASE(),非拼接推断)。 */
26
+ dbName?: string;
27
+ steps: VerifyStep[];
28
+ }
29
+ export interface VerifyTenantInput {
30
+ endpoint: string;
31
+ serviceRoleKey: string;
32
+ /** create 响应里的 realtime_provisioned;为 false 时记为 warn 级步骤(平台侧非致命)。 */
33
+ realtimeProvisioned?: boolean;
34
+ timeout?: number;
35
+ }
36
+ export declare function verifyTenantUsable(input: VerifyTenantInput): Promise<VerifyTenantResult>;
37
+ //# sourceMappingURL=verify.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../../src/tenant/verify.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AAEpD,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,YAAY,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,uBAAuB;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,OAAO,CAAC;IACZ,6CAA6C;IAC7C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,UAAU,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,qEAAqE;IACrE,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,wBAAsB,kBAAkB,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAkH9F"}