Files
dsh_ai1net_server/交付物/建库口接线-B档落地-20260923.md
T
admin ce8e6ceed9 chore(工作区): 纳入版本控制基线(回收 411 MB 过程产物)
回收 411 MB(470 M → 58.8 M),全部经回收站,可恢复:
- 待清理/(146.2 M,含 relay 分片 128 M 与 42 项过程目录)
- tmp/(32.4 M,按接续棒命名的过程临时区)
- .workbuddy/tmp/(39.5 M)
- 4 份 workbuddy.db 冗余副本(101 M,09-23 事故的坏副本 / 抢救产物)
- tmp/im16/gw/centrifugo 二进制(63.9 M,可重下)+ 缓存残留

入库范围:常驻规则(CODEBUDDY.md / README.md / state.py)、在途接续入口与
接续包、docs/、交付物/、交接单/、归档/、scripts/、.codebuddy/、
.workbuddy/memory/;共 398 件,其中 >60 KB 的 26 件全为文档。

排除(.gitignore):tmp/、待清理/、运行态日志与缓存、*.db 与 DB 备份整目录、
打包二进制(*.tar.gz / *.tgz)、记忆修复前备份。
2026-09-24 07:51:03 +08:00

111 lines
6.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 建库口接线(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. **迁移前结构备份**(本棒记的已知缺口,见 §二)。