Files

110 lines
6.7 KiB
Markdown
Raw Permalink Normal View History

# 建库口接线(B 档)· 落地件 — 2026-09-23
> **归口**:插件投放与分库线 · **第 4 棒 · 执行棒**(automation `6e8cedcd`)。
> **前置件**:`交付物/数据面管理面-建库与更新流程-20260922.md §八`(设计件)+
> `交付物/插件建库权限-B档落地-20260923.md`(B 档权限与契约)。
> **状态**:✅ 代码面已落地 + 47 真机取证通过 + 服务器零残留。
---
## 一 一句话结论
设计件 §八 里**最后一个没做的口**(建库执行口 `datastore.ts`)已接线:`CREATE DATABASE` 真调用、
`currentSchemaOf` 去占位真读 `information_schema`、台账+审计落点、`migrate` 去 503、
台账 × `pg_database` 对账告警。**B 档"三点兜底"全部由代码兑现**。
---
## 二 落地清单(逐条对 §5 本轮动作)
| # | 要求 | 落点 | 状态 |
|---|---|---|---|
| 1 | `CREATE DATABASE` 真调用(单语句、⛔ 不走事务) | `datastore.ts:createPluginDatabase`(`pool.query` 隐式自动提交,⛔ 不包 `BEGIN`) | ✅ |
| 2 | `currentSchemaOf` 去占位 | `datastore.ts:readCurrentSchema`(真读 `information_schema.columns` + `pg_indexes` + `p_meta_schema`);路由层 `currentSchemaOf` 改调它 | ✅ |
| 3 | 台账 + 审计落点 | 建库成功 `state='created'`;失败 `state='error'` + `last_error`;两步都 `recordPluginDataAudit` | ✅ |
| 4 | `migrate` 去 503 | `datastore.ts:executePlan`(建库项非事务/表级 DDL **同一事务**)+ 路由真执行 | ✅ |
| 5 | 台账 × `pg_database` 对账 | `datastore.ts:reconcilePluginDatabases` + `GET /api/plugins/business/datastores/reconcile` | ✅ |
### 关键实现点(三条最容易写错的)
1. **建库的语句姿势** —— `CREATE DATABASE` 走 `pool.query()`(implicit autocommit),
**⛔ 绝不 `withTx`**。这是 D 档被证伪的同一条内核限制(`cannot be executed from a function`
/ 不能进事务块),B 档只是不再需要函数封装。
2. **库名的两重防线** —— ① `pluginDbNameOf` 生成(⛔ 不收请求体里的库名);
② `assertPluginDbName` 在**拼 SQL 之前**复校,再加 `quoteIdent`(`"` 翻倍)标准引用。
单测有一条注入形态(`x'; DROP DATABASE dshs; --`)专门钉这条。
3. **`plan_hash` 的 `COALESCE` 语义** —— 建库 / 迁移路径一律**不传** `planHash`(omit ⇒ 保留旧值),
⛔ 传 `null` 会把预演写下的指纹清掉 ⇒ 等于拆掉 TOCTOU 防护。真机取证已断言保留成立。
### 已知缺口(⛔ 不假装做了)
| 缺口 | 原因 | 何时补 |
|---|---|---|
| **迁移前结构备份**(设计件 §七-3 `pg_dump --schema-only`) | 属运维面(`pg_dump` + `/opt/dsh/backups/plugin-db/`),与 S5-a 同批 | 归入「S5-a 兼容矩阵 / 运维件」棒 |
| **47 控制面停在迁移 v13** | v14/v15(含两张台账表)要平台启动时由 `runPgMigrations` 落;本棒未部署 47 | **部署本棒代码时随 `restart dshs` 自动补** |
风险等价性说明:缺口下的执行失败靠**事务回滚**兜(表级 DDL 全在同一事务,失败即整体回退),
且「只增不减」已由 `forbidden` 把死 ⇒ 破坏面 = 只增结构(可人工 `DROP`)。
---
## 三 47 真机取证(用**真实编译产物**,非手写 SQL)
机位:47(`/opt/dshs` · PG 13.23 `127.0.0.1:15432`)。探针 `tmp/evid47/probe2.mjs`。
```
created : "exists" ← 第二次跑(首次已建有库);首跑读数 = "created",71 ms
idempotent : "exists" ← 重复建库幂等,⛔ 不报错
badNameRejected : "invalid_plugin_db_name" ← evil_probe4 被平台侧拦下,⛔ 未下发 PG
currentRead : { exists: true, schemaVersion: 7,
tables: ["p_bselftest4_probe:1", "p_meta_schema:2"] } ← 去占位:真表真列真版本
ledgerRow : { db_name: "dshs_pl_bselftest4", state: "ready", schema_version: 7 }
planHashPreserved : true ← COALESCE 语义成立(第二次 upsert 未传 planHash 仍保留)
auditRows : 1 ← 审计流水落了
reconcile.orphans : ["dshs_pl_borphan4"] ← 🔴 绕过平台建的库**被抓出**
reconcile.missing : []
cleaned : { pgPluginDbsLeft: 0, controlTablesLeft: 13, expectedControlTables: 13 }
```
**残留核查(独立于探针,清理后另跑一次)**:
`pluginDbs: []` | 控制面表 = 原始 13 张(`plugin_datastores` / `plugin_data_audit` 均已 DROP)
| `systemctl is-active dshs` = `active` | `/tmp/evid4` 已删。
> ⚠️ **取证姿势的一条实测**:探针第一次跑失败了(在台账步骤,因 47 无 v15 表),
> 而那次失败**跳过了清理** ⇒ 测试库 `dshs_pl_bselftest4` 留在了 PG 上。
> 第二次跑把清理放进 `catch` 兜底后重跑,才回到零残留。
> **教训**:真机取证的清理必须写在 `catch` 里(失败路径也会留东西),⛔ 只在成功路径清。
---
## 四 验证(本机三闸)
| 闸 | 判据 | 实测 |
|---|---|---|
| 构建 | `npm run build` rc=0 | **rc=0** |
| 回归 | `npm test`(Node 22)0 败 | **426 tests / 424 过 / 0 败 / 2 跳过**,rc=0(基线 391 过 ⇒ 本棒 **+35**) |
| 分层 | `node scripts/check-layering.mjs` 无新增违规 | **✅ 无新增违规**(`added: 0`) |
`npm run verify` 未跑(按本线惯例它必红且与本线无关)。
---
## 五 改了哪些文件(本棒)
| 文件 | 改动 |
|---|---|
| `src/db/plugin-data/datastore.ts`(**新 · 479 行**) | 建库口 / 现状真读 / 计划执行 / 对账 / 错误码映射 / 连接管理 |
| `src/web/routes/business-plugins.ts` | `currentSchemaOf` 去占位;新增 `POST /datastore`(建库)+ `GET /datastores/reconcile`;`/datastore/plan` 的 `executable` 改为真判据;`/datastore/migrate` 去 503 真执行;控制面连接池(懒创建 + `onClose`) |
| `web/portal.html` | 「建库」按钮改调新路由(原为"尚未接线"禁态);`createDatastore()`;「确认执行」禁用文案改由服务端原因驱动 |
| `src/db/plugin-data/diff.ts` | 仅注释:去掉"建库口尚未接线"的过时说明 |
| `test/plugin-data.test.mjs` | **+35 条**:引用转义 / `pluginDbUrlOf` / 错误码分类 / 台账表 DDL / 对账集合逻辑 |
---
## 六 下一棒(仍未做,⛔ 本棒不预登记队列)
1. **三份文档回写** —— 交接单 §五 S4-2 / §十-D1、`DB-03 §三/§四/§六`、`DEPLOY-本部署.md`
(追加 `ALTER ROLE dshs CREATEDB;`)。共享文件,须持锁成批做。
2. **S3 跨机装配 · S5-a 兼容矩阵 · S6 门户三段式 UI**(执行棒①遗留)。
3. **跨节点内容分发**。
4. **迁移前结构备份**(本棒记的已知缺口,见 §二)。