484 lines
20 KiB
Markdown
484 lines
20 KiB
Markdown
# S5 / S6 真机 E2E 验收 — 部署落地(第 8 棒)
|
||||
|
|
|
|||
|
|
> **线**:插件投放与分库线 | **棒次**:第 8 棒 · 执行棒 | **日期**:2026-09-23
|
|||
|
|
> **执行依据**:`接续入口_插件投放与分库线_20260922.md` §0 最新行 + §5「本轮动作(第 8 棒)」
|
|||
|
|
> **交接单**:`dsh-server-docs/交接单/插件投放与分库线-①共享只读包库与插件数据面.md` §五 S5 / §六
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 〇、总判定(先给结论)
|
|||
|
|
|
|||
|
|
| 项 | 判定 | 关键证据 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| 部署到 47 + 106 | ✅ 完成 | 三机 md5 逐字一致 |
|
|||
|
|
| **S5-E ①** v1→v2 升级、存量数据保留 | ✅ **PASS** | 升级前后均 3 行 |
|
|||
|
|
| **S5-E ②** 强行降级 ⇒ 拒绝且库不变 | ✅ **PASS** | `409 plan_blocked` + 列/行未变 |
|
|||
|
|
| **S5-E ③** 同名同版本重传 ⇒ 幂等 | ✅ **PASS** | `http=200`、tgz md5 与源一致 |
|
|||
|
|
| **S5-E ④** 迁移前结构备份落盘 | ✅ **PASS** | 2 个 `.sql`(643 / 1344 字节)、0 字节文件 = 0 |
|
|||
|
|
| **S5-E ⑤** 备份不可写 ⇒ 500 且库不变 | ✅ **PASS** | `500 schema_backup_failed` + `mkdir_failed: EPERM` |
|
|||
|
|
| **S6-E ①** 非 admin `/mine` 条数 = 候选池全量 | ✅ **PASS** | 5 = 5 |
|
|||
|
|
| **S6-E ②** 开关关 ⇒ 404(只记事实) | ✅ **PASS** | `404`(⛔ 未开开关) |
|
|||
|
|
| **S6-E ③** admin 新增 ⇒ 用户刷新即可见 | ✅ **PASS** | 5 → 6 |
|
|||
|
|
| 门户三段式 UI 逻辑自洽 | ✅ **PASS**(逻辑层 7/7) | 互斥 / 完备 / 语义正确 |
|
|||
|
|
| **真机缺陷修复** | 🔴 **发现 1 个真实缺陷并已修** | `pg_dump` env 传递错误 ⇒ 见 §三 |
|
|||
|
|
| 回归三门 | ✅ 全绿 | build rc=0 · 449/447/0/2 · `added: 0` |
|
|||
|
|
|
|||
|
|
**本轮净产出 = 修掉一个"备份机制在生产上根本跑不通"的真实缺陷**(本机测试测不出、只有真机 E2E 能暴露)。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 一、部署与三机一致性
|
|||
|
|
|
|||
|
|
### 1.1 构建
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
$ cd D:/github/dsh_shenxian && npm run build
|
|||
|
|
(无输出 = tsc 通过)
|
|||
|
|
退出码 = 0
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 1.2 三机 md5 逐字一致(部署判据)
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
$ md5sum lib/db/plugin-data/datastore.js lib/web/routes/business-plugins.js
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
| 文件 | 本机 | 47 (`/opt/dshs/lib/...`) | 106 (`/opt/dshs-cluster/lib/...`) |
|
|||
|
|
|---|---|---|---|
|
|||
|
|
| `db/plugin-data/datastore.js` | `9fedd77c80812ec47160e9d413c8d0ab` | 同 | 同 |
|
|||
|
|
| `web/routes/business-plugins.js` | `fe456d60c1f827db53fa1a54ba229591` | 同 | 同 |
|
|||
|
|
| `config.js` | 同 | 同 | 同 |
|
|||
|
|
| `web/portal.html` | `c4f012ce9916825935c0d166daafb2c0`(64894 字节) | 同 | — |
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
$ systemctl restart dshs # 47
|
|||
|
|
退出码 = 0
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 1.3 v15 账本表仍在(第 6 棒落地的迁移)
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
$ psql -h 127.0.0.1 -p 15432 -U dshs -d dshs -Atc \
|
|||
|
|
"select tablename from pg_tables where schemaname='public' order by 1;"
|
|||
|
|
audit_log business_plugins credential_vault domains dsh_hosts dsh_instances
|
|||
|
|
email_codes folder_plugins messages overlay_devices plugin_data_audit
|
|||
|
|
plugin_datastores room_members rooms schema_migrations sessions users workspaces
|
|||
|
|
退出码 = 0
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
⇒ `plugin_datastores` / `plugin_data_audit` 均在 ⇒ **v15 迁移未丢**。
|
|||
|
|
|
|||
|
|
### 1.4 部署前备份(可回滚)
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
/opt/dsh/backups/lib-s5s6/47-pre-s5s6-20260923-085319.tgz
|
|||
|
|
/opt/dsh/backups/lib-s5s6/106-pre-s5s6-20260923-085319.tgz
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 二、S5-E ①–⑤ 真机 E2E
|
|||
|
|
|
|||
|
|
**探针插件**:`@dsh-local/s5probe`,表 `p_s5probe_notes`(scope `user`),内核自动注入 `created_at` / `updated_at`。
|
|||
|
|
|
|||
|
|
### 2.1 S5-E ① v1→v2 升级 + 存量数据保留 ⇒ PASS
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
═══ S5-E 真机 E2E v2(47 · Manager · PG 127.0.0.1:15432)═══
|
|||
|
|
── S5-E ① v1 腿 ──
|
|||
|
|
上传 v1 http=200 ver=1.0.0
|
|||
|
|
建库 http=200 created=None
|
|||
|
|
预演 v1 http=200 planHash=ebd541642cd3e9eb…
|
|||
|
|
迁移 v1(建表) http=200
|
|||
|
|
v1 列 user_id:text, created_at:bigint, updated_at:bigint, title:text, body:text
|
|||
|
|
v1 表清单 p_meta_schema, p_s5probe_notes
|
|||
|
|
写 3 行(直连插件库 · 数据由插件负责 · 补内核审计列)
|
|||
|
|
INSERT 0 3
|
|||
|
|
▶ rows_before_upgrade = 3 (期望 3)
|
|||
|
|
|
|||
|
|
── S5-E ① v2 腿(升级 · 只增不减)──
|
|||
|
|
上传 v2 http=200 ver=2.0.0
|
|||
|
|
预演 v2 http=200 items=['add_column'] forbidden=0
|
|||
|
|
迁移 v2(加列) http=200
|
|||
|
|
v2 列 user_id:text, created_at:bigint, updated_at:bigint, title:text, body:text, pinned:boolean
|
|||
|
|
▶ rows_after_upgrade = 3 (期望 3 —— 存量数据原样保留)
|
|||
|
|
① 升级后行数仍在 : 3 (期望 3) ⇒ PASS
|
|||
|
|
退出码 = 0
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2.2 S5-E ② 强行降级 ⇒ 409 且库未变 ⇒ PASS
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
── ② 强行降级(装回 v1 · schemaVersion 1 < 库现 2)──
|
|||
|
|
降级前:台账=ready|2 | 列=user_id,created_at,updated_at,title,body,pinned
|
|||
|
|
上传 v1(降级包) http=200 ver=1.0.0
|
|||
|
|
预演(降级) http=200
|
|||
|
|
items = []
|
|||
|
|
forbidden = [{"kind": "drop_column", "target": "p_s5probe_notes.pinned",
|
|||
|
|
"detail": "现状有此列、声明里没有 ⇒ 拒绝(DB-03 §四:不允许删列)"}]
|
|||
|
|
executable= False
|
|||
|
|
state = blocked
|
|||
|
|
迁移(降级) http=409
|
|||
|
|
error = plan_blocked
|
|||
|
|
msg = 迁移违反「只增不减」:声明里出现了删列 / 改类型 / 重命名 / 无默认值非空列。请修正声明后重传。
|
|||
|
|
降级后:列=user_id,created_at,updated_at,title,body,pinned | 行数=3
|
|||
|
|
▶ 判据 ②(库未被改)⇒ PASS
|
|||
|
|
退出码 = 0
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
⇒ **防降级护栏在真机生效**:预演即拦(`executable=false` / `state=blocked`),迁移 409,**列与行数一字未动**。
|
|||
|
|
|
|||
|
|
### 2.3 S5-E ③ 同名同版本重传 ⇒ 幂等 ⇒ PASS
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
重传 v2 http=200 tgz md5=0e69625bb1a1e626…(与源一致)
|
|||
|
|
退出码 = 0
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2.4 S5-E ④ 迁移前结构备份落盘(新判据)⇒ PASS
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
── ④ 迁移前结构备份落盘判据(/opt/dsh/backups/plugin-db/<pkg>/)──
|
|||
|
|
total 16
|
|||
|
|
drwx------ 2 root root 4096 Sep 23 09:13 .
|
|||
|
|
drwx------ 3 root root 4096 Sep 23 09:13 ..
|
|||
|
|
-rw------- 1 root root 643 Sep 23 09:13 v0-2026-09-23T01-13-10-233.sql
|
|||
|
|
-rw------- 1 root root 1344 Sep 23 09:13 v1-2026-09-23T01-13-32-231.sql
|
|||
|
|
▶ .sql 个数 = 2(≥2:v0 首建后 + v1 升级前)
|
|||
|
|
▶ 0 字节文件数 = 0(期望 0)
|
|||
|
|
▶ 判据 ④ ⇒ PASS
|
|||
|
|
台账里的备份审计行:
|
|||
|
|
datastore_migrate_backup_ok x2
|
|||
|
|
退出码 = 0
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
⇒ 命名符合 `<v>-<ts>.sql`,**均为非 0 字节**,台账双行审计。
|
|||
|
|
|
|||
|
|
### 2.5 S5-E ⑤ 反证:备份不可写 ⇒ 500 且库未变 ⇒ PASS
|
|||
|
|
|
|||
|
|
> ⚠️ **诚实记录:本腿第一次尝试(`chmod 000`)FAILED**。
|
|||
|
|
> 原因:平台以 **root** 运行,`chmod 000` **拦不住 root**(实测 `touch` 仍成功)。
|
|||
|
|
> 第一次失败的原始输出(保留):
|
|||
|
|
> ```
|
|||
|
|
> 迁移(备份不可写) http=200 ⇒ FAIL
|
|||
|
|
> ▶ 判据 ⑤(库结构未变)⇒ FAIL
|
|||
|
|
> ```
|
|||
|
|
> ⇒ 改用**只读文件系统**作反证载体(`DSH_PLATFORM_DIR=/sys/dshs-nope`)重跑。
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
═══ S5-E ⑤ 反证(备份根 → 只读文件系统)═══
|
|||
|
|
① 对照:正常备份根 ⇒ 迁移应 200
|
|||
|
|
migrate http=200 ⇒ PASS(对照成立的证明)
|
|||
|
|
② 反证:把平台目录指到 /sys/dshs-nope(只读文件系统 ⇒ root 也写不进)
|
|||
|
|
Environment="DSH_PLATFORM_DIR=/sys/dshs-nope"
|
|||
|
|
active
|
|||
|
|
反证前库列 = user_id,created_at,updated_at,title,body
|
|||
|
|
migrate http=500 ⇒ PASS
|
|||
|
|
error = schema_backup_failed
|
|||
|
|
backupError = mkdir_failed: EPERM: operation not permitted,
|
|||
|
|
mkdir '/sys/dshs-nope/backups/plugin-db/_dsh-local_s5probe'
|
|||
|
|
反证后库列 = user_id,created_at,updated_at,title,body
|
|||
|
|
▶ 判据 ⑤a(500 schema_backup_failed)⇒ PASS
|
|||
|
|
▶ 判据 ⑤b(库结构未变) ⇒ PASS
|
|||
|
|
③ 复原:还原 platform-dirs.conf → daemon-reload → restart
|
|||
|
|
Environment="DSH_PLATFORM_DIR=/opt/dsh"
|
|||
|
|
active
|
|||
|
|
退出码 = 0
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**环境复原已复验**:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Environment="DSH_PLATFORM_DIR=/opt/dsh" ← drop-in 唯一载体
|
|||
|
|
(无 .bak-s5e 残留 ✓)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
> ⚠️ **踩坑记录**:第一次尝试新建第二个 drop-in `s5e-counterproof.conf` 会被**静默覆盖**(同名键后写者胜),故改为 sed 编辑**唯一载体** `platform-dirs.conf` 并原样还原 —— 与记忆中的「drop-in 才是唯一载体」一致。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 三、🔴 本轮发现的真实缺陷(已修 + 已回归钉住 + 已重部署)
|
|||
|
|
|
|||
|
|
### 3.1 现象
|
|||
|
|
|
|||
|
|
S5-E ④ 在真机上**根本跑不通** —— 结构备份永远失败:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
pg_dump: error: connection to database "dshs_pl_s5probe" failed:
|
|||
|
|
could not connect to server: No such file or directory
|
|||
|
|
Is the server running locally and accepting connections on
|
|||
|
|
Unix domain socket "/var/run/postgresql/.s.PGSQL.5432"?
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3.2 根因(受控复现确认)
|
|||
|
|
|
|||
|
|
`src/db/plugin-data/datastore.ts` 里 `pgEnvFrom(baseUrl)` 的返回值被**展开在 `execFileSync` 的 options 层**(与 `env:` 同级),而不是**合进 `env`**:
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
// ❌ 缺陷写法:PGHOST/PGPORT/PGUSER 变成了"选项名"而非环境变量
|
|||
|
|
execFileSync('pg_dump', args, { ...pgEnvFrom(baseUrl), env: {...}, timeout: 120_000 })
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
⇒ `pg_dump` 子进程**收不到** `PGHOST`/`PGPORT`/`PGUSER` ⇒ 退回默认 **Unix socket `:5432`** ⇒ 47 上 PG 实际在 `127.0.0.1:15432` ⇒ 连接失败。
|
|||
|
|
|
|||
|
|
**本机测试测不出**:本机没有 `pg_dump`,该路径根本不会被走到;只有真机 E2E 能暴露。
|
|||
|
|
|
|||
|
|
### 3.3 修复
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
env: pgDumpEnv(baseUrl, password),
|
|||
|
|
timeout: 120_000,
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
export function pgDumpEnv(baseUrl: string, password: string): NodeJS.ProcessEnv {
|
|||
|
|
const env: NodeJS.ProcessEnv = {
|
|||
|
|
...process.env,
|
|||
|
|
PGPASSWORD: password,
|
|||
|
|
PGCONNECT_TIMEOUT: '15',
|
|||
|
|
}
|
|||
|
|
try {
|
|||
|
|
const u = new URL(baseUrl)
|
|||
|
|
if (u.hostname !== '') env.PGHOST = u.hostname
|
|||
|
|
if (u.port !== '') env.PGPORT = u.port
|
|||
|
|
if (u.username !== '') env.PGUSER = decodeURIComponent(u.username)
|
|||
|
|
} catch { /* 连接串不可解析 ⇒ 保留 ...process.env */ }
|
|||
|
|
return env
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**顺带修一处不实文案**:`business-plugins.ts` 里第 4 棒遗留的 `backup: 'not_implemented'` 未随第 7 棒接线更新,响应改为真实值:
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
backup: backupPathOrNull === null ? 'skipped_first_create' : 'taken',
|
|||
|
|
backupPath: backupPathOrNull,
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3.4 回归钉住(`test/plugin-data.test.mjs`,+2 用例)
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
$ npm test
|
|||
|
|
⬇ 见 §四
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
> ⚠️ 该用例第一版尝试用 PATH 假 `pg_dump` 桩,**Windows 上失败**(`execFileSync` 无法解析 PATH 里的 `.cmd`/`.bat`:`ENOENT` / `EINVAL`)⇒ 改为**直接断言不变量**(env 必须含四个 PG 变量、且⛔ 不得混进 `stdio`/`timeout`/`cwd`/`encoding` 这些选项名 —— 正是原缺陷的特征)。
|
|||
|
|
|
|||
|
|
### 3.5 重部署 + 复验
|
|||
|
|
|
|||
|
|
修后重构建、重部署到 47/106,`datastore.js` md5 `9fedd77c80812ec47160e9d413c8d0ab` **三机一致**,重启后 ④ 腿备份**成功落盘**(643 / 1344 字节)⇒ 缺陷闭环。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 四、回归三门(改完 → 回归 → 才放锁)
|
|||
|
|
|
|||
|
|
| 门 | 命令 | 原始输出 | 退出码 |
|
|||
|
|
|---|---|---|---|
|
|||
|
|
| 编译 | `npm run build` | 无输出(tsc 过) | **0** |
|
|||
|
|
| 全量测试 | `npm test`(Node 22.22.2) | **449 tests / 447 pass / 0 fail / 2 skipped** | **0** |
|
|||
|
|
| 分层 | `node scripts/check-layering.mjs` | `✅ 无新增违规`(`added: 0`) | **0** |
|
|||
|
|
|
|||
|
|
> 基线 **447 / 445 pass / 0 fail / 2 skipped** ⇒ 本轮 **+2**(新增的 `pgDumpEnv` 不变量用例),**失败数仍为 0**。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 五、S6-E 真机 E2E
|
|||
|
|
|
|||
|
|
### 5.1 S6-E ① 非 admin `/mine` 条数 = 候选池全量 ⇒ PASS
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
── ① 非 admin 用户 GET /api/plugins/mine 条数 = 候选池全量 ──
|
|||
|
|
admin /api/plugins/business http=200
|
|||
|
|
guest /api/plugins/mine http=200
|
|||
|
|
▶ 候选池全量 = 5 | guest 可见 = 5
|
|||
|
|
▶ 判据 ①(条数一致)⇒ PASS
|
|||
|
|
退出码 = 0
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 5.2 S6-E ② 开关关 ⇒ 404(🔴 只记事实,⛔ 未开开关)⇒ PASS
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
── ② GET /api/plugins/shared/catalog —— 🔴 只记事实,⛔ 不打开开关 ──
|
|||
|
|
admin http=404
|
|||
|
|
guest http=404
|
|||
|
|
▶ 开关 DSHS_SHARED_CATALOG_PUBLIC 当前值: (未设置 = 默认 false)
|
|||
|
|
▶ 判据 ②(关着 ⇒ 404,⛔ 不是空列表)⇒ PASS
|
|||
|
|
⛔ 本棒未打开该开关(属 §1「扩大可见面」红线门禁,须用户拍板)
|
|||
|
|
退出码 = 0
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
> 🔴 **红线遵守声明**:`DSHS_SHARED_CATALOG_PUBLIC` 属「扩大可见面」红线门禁 ⇒ **本棒全程未开**,仅记录"关着 ⇒ 404"这一事实。
|
|||
|
|
|
|||
|
|
### 5.3 S6-E ③ admin 新增 ⇒ 用户刷新即可见 ⇒ PASS
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
── ③ admin 新增插件 ⇒ 非 admin 刷新即可见(无需任何管理动作)──
|
|||
|
|
新增前 guest 可见 = 5
|
|||
|
|
上传新插件 http=200
|
|||
|
|
ok=True name=@dsh-local/s6newprobe
|
|||
|
|
新增后 guest 可见 = 6 | 含新插件 = True
|
|||
|
|
▶ 判据 ③(刷新即可见 · 无需管理动作)⇒ PASS
|
|||
|
|
▶ 判据 ③b(条数 +1)⇒ PASS
|
|||
|
|
退出码 = 0
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 5.4 S6-E ② 深挖:enable 落点(跨机形态真实发现)
|
|||
|
|
|
|||
|
|
> **背景**:客用户(`guest` / uid `100002`)实例**在 106(worker)**,不在 47。
|
|||
|
|
> `/api/plugins/mine` 读的是**平台本机** `profiles`(47 上为空)⇒ 字面判据 `enabled:true` **未闭合**。
|
|||
|
|
|
|||
|
|
**实际 enable 确实成功,且正确落到实例所在机 106**:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
POST /api/plugins/mine/apply http=200
|
|||
|
|
{"ok":true,"taskId":"023a87d692d07654"}
|
|||
|
|
|
|||
|
|
--- 106 上 guest 的 node_modules 出现 storyforge 软链,且指向共享层 ---
|
|||
|
|
lrwxrwxrwx 1 dsh-eeccbc638afc46bdb663 100002 123 Sep 23 09:43
|
|||
|
|
.../home/profiles/web/node_modules/@dsh-local/storyforge
|
|||
|
|
-> ../.pnpm/@dsh-local+storyforge@file+..+..+..+..+..+bundled-plugins+_dsh-local_storyforge/node_modules/@dsh-local/storyforge
|
|||
|
|
|
|||
|
|
--- 该软链可解到 package.json --- ✓ 可解
|
|||
|
|
--- 106 侧 package.json 已写入 ---
|
|||
|
|
"@dsh-local/storyforge": "file:/var/lib/dshs/bundled-plugins/_dsh-local_storyforge"
|
|||
|
|
▶ 判据(装配真落到实例所在机)⇒ PASS
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**判定**:**不是代码缺陷,是拓扑形态的必然结果** ——
|
|||
|
|
`/mine` 的 `enabled` 字段取自**平台本机 profile 快照**;当用户实例在 worker 上时,本机快照**必然**反映不了远端装配。
|
|||
|
|
|
|||
|
|
**旁证(沙箱命令行原文,证明共享层已 ro 绑进实例)**:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
dsh-100002-9648cef4.scope loaded active running /usr/bin/bwrap
|
|||
|
|
... --ro-bind-try /var/lib/dshs/bundled-plugins /var/lib/dshs/bundled-plugins
|
|||
|
|
... -- setpriv --reuid 100002 --regid 100002 /usr/bin/dsh --profile web --host 127.0.0.1 --port 21000
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
⇒ **S1 共享只读层挂载成立**(`--ro-bind-try`),且实例确实在跑。
|
|||
|
|
|
|||
|
|
**该发现的价值**:这是 S3「跨机装配」要解决的真实形态问题 —— **平台侧的"用户可见启停状态"需要一个跨机回传通路**(当前只有单向 apply,没有状态回流)。**建议单独立项**,本轮只记录、不改架构。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 六、门户三段式 UI 观察
|
|||
|
|
|
|||
|
|
> ⚠️ **本机 `agent-browser` 每次调用均挂死**(8 分钟无输出;Chromium 进程起了但 `open` 不返回)—— 属环境限制,按技能指引 fail-fast 处理(已 `agent-browser close` + 清理 chrome 进程)。
|
|||
|
|
> ⇒ 改用**等价判据**:`ui-logic-check.mjs` 用**真实 API 数据**复跑 `portal.html` 里 `loadVisibility()` 的三段派生逻辑。
|
|||
|
|
|
|||
|
|
派生逻辑(与 `portal.html` **逐字一致**):
|
|||
|
|
|
|||
|
|
```js
|
|||
|
|
const shared = (catalog && catalog.plugins) || []
|
|||
|
|
const sharedIds = new Set(shared.map((p) => p.id))
|
|||
|
|
const enabled = mine.filter((p) => p.enabled)
|
|||
|
|
const disabled = mine.filter((p) => !p.enabled && sharedIds.has(p.id))
|
|||
|
|
const unavailable= mine.filter((p) => !p.enabled && !sharedIds.has(p.id))
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
结果:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
═══ S6 三段式 UI 逻辑评估(真实 API 数据)═══
|
|||
|
|
catalogHttp: 404 | mineHttp: 200
|
|||
|
|
① shared (平台共享只读): 0
|
|||
|
|
② enabled (已启用): 2
|
|||
|
|
③ disabled (已停用): 0
|
|||
|
|
⋯ unavailable (暂不可用): 4
|
|||
|
|
mine 总数: 6
|
|||
|
|
switchOff (catalog===null ⇒ 显示开启提示): true
|
|||
|
|
--- 断言 ---
|
|||
|
|
A. 互斥(两两不相交): true
|
|||
|
|
B. 完备(②+③+⋯ = mine 总数): true 「6 vs 6」
|
|||
|
|
C. ② 全为 enabled:true: true
|
|||
|
|
D. ③ 全部在共享层内(能装): true
|
|||
|
|
E. ⋯ 全部不在共享层内(装不到): true
|
|||
|
|
F. 开关关 ⇒ ③=0(⛔ 不能把池里的都算"用户没开"): true
|
|||
|
|
G. 开关关 ⇒ ⋯ = mine 里未启用的全部: true
|
|||
|
|
═══ 总判定:✅ 三段自洽/互斥/完备 ═══
|
|||
|
|
退出码 = 0
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**⚠️ 未验项(具名)**:CSS 视觉渲染、tab 点击切换 —— 需真浏览器,本机环境不具备。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 七、清理与复原(零残留复验)
|
|||
|
|
|
|||
|
|
| 对象 | 处置 | 复验 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| 探针库 `dshs_pl_s5probe` | `DROP DATABASE` | `pg_database like '%probe%'` = **0** ✅ |
|
|||
|
|
| 候选池 `@dsh-local/s5probe` / `s6newprobe` | 删行 + 删 tgz | 池子恢复 = **原始 4 个** ✅ |
|
|||
|
|
| 账本 `plugin_datastores` | 删探针行 | `like '%probe%'` = **0** ✅ |
|
|||
|
|
| 审计 `plugin_data_audit` | 删探针行 | `like '%probe%'` = **0** ✅ |
|
|||
|
|
| 47 备份目录 `plugin-db/_dsh-local_s5probe` | 删 | 目录不存在 ✅ |
|
|||
|
|
| 共享层 `bundled-plugins/*probe*` | 删(**保留** `_dsh-local_storyforge` / `dsh-plugin-mcn-suite`) | 恢复 2 项 ✅ |
|
|||
|
|
| `platform-dirs.conf` | sed 改 → 原样还原 | `DSH_PLATFORM_DIR=/opt/dsh` + **无 `.bak-s5e`** ✅ |
|
|||
|
|
| **106 客实例 `storyforge` 启用** | **已回退**(见下) | ✅ |
|
|||
|
|
|
|||
|
|
**106 客实例回退明细**(还原到我介入前的 4 依赖状态):
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
dependencies: ['@dsh-local/business-plugins', '@dsh-local/portal-entry',
|
|||
|
|
'@dsh-local/workspace-scoped-picker', '@softspark/dsh-file-preview']
|
|||
|
|
bundles: ['@deepseek-ai/dsh-base', '@deepseek-ai/dsh-web-app',
|
|||
|
|
'@dsh-local/business-plugins', '@dsh-local/portal-entry',
|
|||
|
|
'@dsh-local/workspace-scoped-picker', '@softspark/dsh-file-preview']
|
|||
|
|
✓ package.json 已无 storyforge
|
|||
|
|
✓ symlink 已除
|
|||
|
|
✓ .pnpm 已除
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**106 健康复验**:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
dshs-worker.service = active
|
|||
|
|
dshs-relay.service = active
|
|||
|
|
dsh-100002-9648cef4.scope = active running
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 八、⛔ 未做的事(具名原因)
|
|||
|
|
|
|||
|
|
1. **装配协议 `file:` vs `link:`** ⇒ `awaiting-user-decision`,**本轮零改动**(⛔ 未碰 `plugin-assembly.ts` 协议语法、⛔ 未改交接单里 `file:` 相关描述)。
|
|||
|
|
2. **可见面开关 `DSHS_SHARED_CATALOG_PUBLIC`** ⇒ 属 **§1「扩大可见面」红线门禁**,**未开**,只记录 404 事实。
|
|||
|
|
3. **跨节点内容分发** ⇒ 无平台侧通路(106 共享层手工同步),**需单独立项**。
|
|||
|
|
4. **S6-E ② 字面判据(`enabled:true`)未闭合** ⇒ 原因是**拓扑形态**(实例在 106)而非代码缺陷;已用 106 侧文件证据闭合。**平台侧用户可见启停状态的跨机回流 = 缺失能力,建议立项。**
|
|||
|
|
5. **门户 UI 的 CSS 渲染 / tab 点击** ⇒ 本机 `agent-browser` 挂死,环境不具备;已用逻辑层等价判据覆盖。
|
|||
|
|
6. ⛔ 未 commit / 未 push / ⛔ 未引新依赖 / ⛔ 无 merge / ⛔ 未跑全库 Glob/Grep。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 九、技术附录
|
|||
|
|
|
|||
|
|
### 9.1 测试资产落点
|
|||
|
|
|
|||
|
|
| 文件 | 用途 |
|
|||
|
|
|---|---|
|
|||
|
|
| `tmp/s5e/pkg-v1/` · `pkg-v2/` | 探针插件声明(v1:`title`/`body`;v2:+`pinned`) |
|
|||
|
|
| `tmp/s5e/v1.tgz` · `v2.tgz` | 476 / 523 字节 |
|
|||
|
|
| `tmp/s5e/driver.sh` · `driver2.sh` · `driver3.sh` · `driver5.sh` · `driver6.sh` | 各腿驱动 |
|
|||
|
|
| `tmp/s5e/ui-logic-check.mjs` | 三段式逻辑评估 |
|
|||
|
|
| `tmp/s5e/*.out` | 全部原始输出(本报告引用的每一段均出自此处) |
|
|||
|
|
|
|||
|
|
### 9.2 关键 API 契约(本轮实测确认)
|
|||
|
|
|
|||
|
|
| 端点 | 方法 | 本轮实测 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| `/api/plugins/business` | GET | 200 · 候选池全量 |
|
|||
|
|
| `/api/plugins/mine` | GET | 200 · 含 `enabled` 字段 |
|
|||
|
|
| `/api/plugins/mine/apply` | POST | 200 · `{ok:true, taskId}`(**异步**) |
|
|||
|
|
| `/api/plugins/shared/catalog` | GET | **404**(开关关) |
|
|||
|
|
| `/datastore/plan` | POST | 200 · 返回 `items` / `forbidden` / `executable` / `state` |
|
|||
|
|
| `/datastore/migrate` | POST | 200 / **409 `plan_blocked`** / **500 `schema_backup_failed`** |
|
|||
|
|
|
|||
|
|
### 9.3 表名/列名实况(订正我的两次误判)
|
|||
|
|
|
|||
|
|
- 候选池表 = **`business_plugins`**(**无** `user_id`,全局池)—— 不是 `user_plugins`。
|
|||
|
|
- 账本列 = `plugin_datastores`(**`state`** / `schema_version` / `plan_hash` / `last_error`) —— **无** `status` 列。
|
|||
|
|
- **不存在** `plugin_tasks` 表 ⇒ 异步任务状态**在内存**,落库不可查。
|
|||
|
|
- 探针表实名 = **`p_s5probe_notes`**(`p_<pluginId>_<tableName>`);内核**强制**注入 `created_at`/`updated_at BIGINT NOT NULL`。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
**本报告全部数字均出自原始输出文件,未做任何"看起来应该对"的推断。**
|