415 lines
48 KiB
Markdown
415 lines
48 KiB
Markdown
# 工作日志 · 2026-09(第 14 片)
|
||||
|
|
|
|||
|
|
> ⚠️ **本目录日志已按【月】分片**(2026-09-15 用户定,单文件 ≤50 KB):同月续片 `2026-09.md` / `2026-09-下.md` / `2026-09-下2.md` / `2026-09-下3.md` / `2026-09-下4.md` / `2026-09-下5.md` / `2026-09-下6.md` / `2026-09-下7.md` / `2026-09-下8.md` / `2026-09-下9.md` / `2026-09-下10.md` / `2026-09-下11.md` / `2026-09-下12.md` / `2026-09-下13.md` / `2026-09-下14.md` / `2026-09-下15.md` / `2026-09-下16.md` / `2026-09-下17.md` / `2026-09-下18.md` / `2026-09-下19.md` / `2026-09-下20.md` / `2026-09-下21.md` / `2026-09-下22.md` / `2026-09-下23.md`
|
|||
|
|
> **写入约定**:一律 append 到**当月最后一片**;该片超 50 KB ⇒ 新建 `2026-09-下N.md`;⛔ **不要再按日新建 `2026-09-DD.md`**。
|
|||
|
|
> **覆盖来源**:2026-09-13.md
|
|||
|
|
> **上一片**:`2026-09-下12.md` **下一片**:`2026-09-下14.md`
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
## 10:12–10:25 会话 `a2665dd3`:**独立复核「另一会话的内存结论」(用户要求确认)**
|
|||
|
|
|
|||
|
|
**被评为"确认"的三条 → 复核结果**
|
|||
|
|
|
|||
|
|
| 对方结论 | 我的实测 | 判定 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| exitCode **8×134 + 1×137**(137 = cgroup SIGKILL) | `--since today` 统计 = **8×134 + 1×137**;137 时刻 **10:03:11** | ✅ **逐条一致** |
|
|||
|
|
| guest cgroup **384/384/384**(高位 100%) | `usage=368.2 / peak=384.0 / limit=384` | ✅ 一致 |
|
|||
|
|
| gateway ≈390.4 MB |实例本体 ≈44.0 MB | **331.5 MB**(gateway pid 429466)|**91.6 MB**(dsh pid 427842) | ⚠️ 同量级,**数值有偏**(gateway 热身前/后波动 330→390;本体随加载阶段 44→92) |
|
|||
|
|
| 「是内存,不是恢复逻辑」 | 与其一致:OOM 是真因;恢复循环(`GET /`)是**结果**不是原因 | ✅ 成立 |
|
|||
|
|
| 「502 是 CF 的,nginx 里没有 502」 | ❌ **不成立**:`/www/wwwlogs/alotbuy.com.log` 里状态位 502 共 **579** 条(`/api/remote.mux` 362 · `/univer-api/state` 39 · `GET /` 29)⇒ **nginx/源站自己也返 502**(上游 127.0.0.1:3080 失败)。CF 页面是同一失败在 edge 的另一层呈现 | ❌ **需修正** |
|
|||
|
|
|
|||
|
|
**★ 对方漏掉的关键一环(我已于 10:10 修掉)**:**陈旧 socket**。内存杀掉 gateway 后,残留的 socket 文件让**之后每次启动都失败**(`bind()` EADDRINUSE)→ 对外只报 `did not become ready within 10000ms`。
|
|||
|
|
⇒ 「无法启动」是**两个原因叠加**:**内存把它杀掉** + **陈旧 socket 让它再也起不来**。**0.2.17 已修(绑前 unlink)并在 guest 上验证通过** ⇒ **方案 (a) 必须带上这个修复**,否则升配额后仍会偶发"永久无法启动"。
|
|||
|
|
|
|||
|
|
**★ 方案 (a) 的宿主容量复核(关键修正)**
|
|||
|
|
- 实测宿主:`total 1870 MB`|`used 1062`|**`available 808 MB`**|**`swap 已用 679 / 1024 MB`**(说明**已在换页**)。
|
|||
|
|
- 对方给的 `640 × maxIdleInstances 2` = **1280 MiB** ⇒ **放不下**(808 可用 + 345 空闲 swap ≈ 1153 < 1280)✗。
|
|||
|
|
- ⇒ 若走 (a):**必须把常驻/激活实例数压到 1**(`maxIdleInstances 4 → 1`),或做**分级配额**(univer 用户 640 / 其他 256–384;但配额在 `orchestrator.ts:625` 硬编码,分级=改码)。**长期仍应扩宿主内存**(花钱)。
|
|||
|
|
|
|||
|
|
**一句话**:对方的定因(内存)与"恢复逻辑没坏"成立;但(i)"nginx 无 502"错,(ii)漏了陈旧 socket 这一环(已修),(iii)(a) 的容量账要按 `available 808 + swap 679` 重算 ⇒ 640 只能配 1 个常驻实例。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 10:14–10:2x · 开源项目命名定稿:**DSH 多租户平台 / DSH Multi-Tenant Platform / `dsh-multitenant`**
|
|||
|
|
|
|||
|
|
**用户指令**:「名字就叫 dsh 多租户平台 对应的英文,项目中文名称「太妙」(认可)」⇒ 本会话中间候选 `dsh-hosting` 被取代。
|
|||
|
|
|
|||
|
|
**命名口径(已定稿,写进 README「名称与渊源」+ NOTICE §4 + 台账 §七)**
|
|||
|
|
|
|||
|
|
| 项 | 值 |
|
|||
|
|
|---|---|
|
|||
|
|
| 中文名 | **DSH 多租户平台** |
|
|||
|
|
| English name | **DSH Multi-Tenant Platform** |
|
|||
|
|
| 技术标识 | **`dsh-multitenant`** | env 前缀 `DSH_MULTITENANT_*`(36 种)| 数据根 `/var/lib/dsh-multitenant` | `dsh-multitenant.{service,env}` | 库文件 `dsh-multitenant.db` |
|
|||
|
|
|
|||
|
|
**占用实测**(R-O8 要求):npm `registry.npmjs.org/dsh-multitenant` → 404;`dshbase.com/plugins/dsh-multitenant` → 404 ✓ 可用。
|
|||
|
|
|
|||
|
|
**执行**:改脚本 GLOBAL 的**目标值**(`dsh-hosting`→`dsh-multitenant`、`DSH_HOSTING_`→`DSH_MULTITENANT_`、路径与库名同改)+ `DST` 目录名 → 改 `_verify_tsc.mjs` 的 `EX` → `--force` 重建 → 手工层批量替换(README 42 处 / LICENSE 1 / NOTICE 4 / install.sh 22)→ README 标题改「**DSH 多租户平台 · DSH Multi-Tenant Platform**」+「名称与渊源」加「中文名 / English name / 技术标识 / 命名口径」→ 再重建验证。
|
|||
|
|
|
|||
|
|
**新坑(已写进技能 §8 坑 14)**:**废弃的旧候选名必须进阻断性探针** —— 否则 `_overlay` 里的中间名会**静默残留**(本次正是靠把 `dsh-hosting` / `DSH_HOSTING_` / `/var/lib/dsh-hosting` 加进 `LEAK_PROBES`,才发现 **72 处**残留)。
|
|||
|
|
|
|||
|
|
**验证(全绿)**:阻断探针 **0 命中**(含全部曾用名)| 旧名残留 grep 为空 | `tsc --noEmit` **exit 0** | `bash -n install.sh` OK 且 `--help` 显示新名 | 140 文件 | env 前缀 36 种一致。
|
|||
|
|
**技能 v1.0.3**(R-O8 补命名口径与已排除候选;§8 补坑 14/15;全文路径改 `dsh-multitenant`)→ 归档副本已同步(md5 `631ad317…`),四件套 exit 0,锁已释放。
|
|||
|
|
|
|||
|
|
> ⚠️ 小插曲:本轮有一次 `--claim-exec` 因**命令被重试**而报「抢不到锁」—— 只读确认 OWNER 是**我自己的会话名**(10:17 那次声明),未做任何接管,随后正常释放,现**无锁**。R9 未被触碰。
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
## 10:14–10:40 会话 `a2665dd3`:**gateway 内存构成实测 + 「加堆限」方案被实测证伪(已回滚)**
|
|||
|
|
|
|||
|
|
**用户问**:为什么 gateway 要这么多内存?有办法优化改造吗?
|
|||
|
|
|
|||
|
|
### 一、构成实测(生产,pid 429466)
|
|||
|
|
| 项 | 值 |
|
|||
|
|
|---|---|
|
|||
|
|
| `VmRSS` | **335 MB**(多次采样区间 **335–400**,噪声 ±30) |
|
|||
|
|
| 其中 `[anon]`(V8 堆 + 匿名映射) | **293 MB = 87%** |
|
|||
|
|
| node 代码段 | libsql native | 41 MB | 1 MB |
|
|||
|
|
| `VmSwap` | 46–93 MB(**已在换页**) |
|
|||
|
|
| gateway 环境里的 `NODE_OPTIONS` | **不存在** ⇒ **没有堆上限**(不继承实例的 160) |
|
|||
|
|
| 产物 | `artifacts/gateway.cjs` **单文件 59 MB**(esbuild `bundle:true, packages:'bundle'`;只 external node builtins + libsql + 3 个 native binding) |
|
|||
|
|
|
|||
|
|
**根因**:它不是插件而是**整套 Univer 协同服务端**(`@univerjs-pro/collaboration-service` 等闭源 SDK + 多 unit 类型 + 协同运行时 + 视图资产服务 + sqlite 文件存储),**全部内联**、`startServer()` 时一次性加载 ⇒ **常驻内存是真实需求**。
|
|||
|
|
|
|||
|
|
### 二、★方案 A(给 gateway 加 V8 堆限)**实测无效,已回滚**
|
|||
|
|
- 试了 `NODE_OPTIONS=--max-old-space-size=160 --max-semi-space-size=8`(改 `gateway/launcher.ts`,0.2.18):
|
|||
|
|
**RSS 335 → 400 MB、swap 93 → 46 MB,总量持平**(cgroup 368→369)⇒ **不是 GC 懒惰,是真需求**;且加限还引入 `134 abort` 风险。
|
|||
|
|
- ⇒ **已回滚到 0.2.17**(`launcher 含堆限: 0 处`)并复测(RSS 363–386 / swap 58)✓。**教训:这类"调参省钱"的假设必须先 A/B 实测再留档**(本次幸好测了)。
|
|||
|
|
|
|||
|
|
### 三、其余方案的可行性摸底
|
|||
|
|
- **B(裁 unit 类型 / 只留 sheet)**:❌ **基本不可行** —— 加载面由**闭源第三方 SDK** `@univerjs-pro/collaboration-service` 决定(我们自己的 `collab-service.ts` 只引用 `snapshot?.board?.name` 之类),没有现成 preset 开关;要裁只能篡改 bundle(高风险)。
|
|||
|
|
- **C(代码分割)**:收益**不确定** —— 若 SDK 在启动时就实例化所有 unit 类型,切分无益;需先测(可做,但要 A/B)。
|
|||
|
|
- **D(把 gateway 移出实例 cgroup,独立 systemd 预算)**:⭐ **现在看最优** —— 不动闭源 SDK、不动插件行为、实例的 384 不再被挤,且"探针还能继续测实例本体";代价是隔离/生命周期模型变化(要评估 R5:仍以该 uid 运行,但生命周期不再随实例)。
|
|||
|
|
- **A 已证伪;换 libsql(1 MB)与多用户共享 gateway(权限面扩大)不值得。**
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 10:24–10:3x · 开源 README:**出处改为「文末提一句」**(用户纠正措辞与位置)
|
|||
|
|
|
|||
|
|
**用户指令**:「在最后提一下引用了谁就行 不要上来就重点讲用了谁,感觉是在宣传别人的项目,已经很多地方深度改造过了」
|
|||
|
|
|
|||
|
|
**改动(只动 README,不动法律文件)**
|
|||
|
|
1. **删掉**开头的 `## 名称与渊源` 章节(原在「目录」之后、「特性速览」之前)+ 目录项 —— 那段把上游摆在了正文最前。
|
|||
|
|
2. **正文改以本项目为主语**:
|
|||
|
|
- `### v1.0.0 相对上游 \`dshs\` 做了什么` → **`### v1.0.0 的六组改造`**;
|
|||
|
|
- 删掉「上游提供了**骨架**:…… v1.0.0 把「能跑」补齐为……」,改为「本项目把「能跑」补齐为「能对外长期运维」,改动分六组:」;
|
|||
|
|
- 版本表 v0.x 行去掉「基于上游 `dshs` 的」。
|
|||
|
|
3. **出处只在文末一句**(「第三方组件与致谢」):「起步阶段参考了 dshs(上游作者(已按要求不再具名),MIT)……此后在本仓库做了**大量深度改造**……**感谢作者把它开源出来。**」——去掉了「离开它就没有这个项目」「只是接着往前走了一步」这类过度抬举。
|
|||
|
|
4. **授权章节**:第一层作用域措辞改「起步时参考的骨架代码」;删掉「也是诚实的做法/可仅使用上游部分自行扩展」(把读者往上游引)。
|
|||
|
|
5. **法律部分一分不减**:`LICENSE-UPSTREAM-MIT.txt` 逐字节未动;`LICENSE` 第一层说明、`THIRD-PARTY-NOTICES.md` §4 保持完整 —— 「轻描淡写」只作用于 README 正文。
|
|||
|
|
|
|||
|
|
**验证**:README 里 `上游/骨架/dshs/上游作者(已按要求不再具名)` 只剩**文末 3 处**(授权表 1 + 致谢 1 + 授权摘要 1);`nginx 上游` 是无关技术词;阻断探针 **0 命中**。
|
|||
|
|
|
|||
|
|
**技能升到 v1.0.4**:新增 **R-O8 附则「出处的措辞与位置」**(位置 / 主语 / 措辞 / 边界 / 代码注释 五条)+ **§8 坑 16**(`--claim-exec` 与其它步骤写在同一命令里,命令被重试会出现「抢不到锁」**假告警** —— 先只读看 OWNER 是不是自己,**放锁前必须再确认 OWNER**,否则可能放掉别人的锁 = R9 反向违反)。归档副本已同步(md5 `8b95d10d…`),四件套 exit 0,锁已释放。
|
|||
|
|
|
|||
|
|
|
|||
|
|
## 10:27–10:45 会话 `a2665dd3`:**D 方案与「共享 gateway」的多租户评估**
|
|||
|
|
|
|||
|
|
**两个新查到的事实(决定结论)**
|
|||
|
|
1. **gateway 没有「空闲自停」**:`gateway-process.ts` 只有 `stop()`(实例停机时调用),**没有 idle 超时** ⇒ 一旦启动就常驻,直到实例停。
|
|||
|
|
2. **应用层路径白名单没启用**:代码里有 `allowedRoot`(`UNIVER_ALLOWED_ROOT` → `server.ts` → `univerfile-manager` 校验「非 `.univer` / 越出 allowedRoot → 400」),但 **launcher 不注入该 env**(实测 gateway environ 只有 `UNIVER_COLLAB_GATEWAY_PORT` / `_SOCKET` / `UNIVER_VIEW_ASSETS_ROOT`)⇒ **生产上没有 allow-list**,隔离**完全依赖 uid + bwrap**。
|
|||
|
|
|
|||
|
|
**D(把 gateway 移出实例 cgroup)的多租户评估**:D **不是共享** —— 仍是"每用户一个 gateway、以该用户 uid 跑",只改 cgroup/生命周期归属。
|
|||
|
|
| 维度 | 现在(在实例内) | D |
|
|||
|
|
|---|---|---|
|
|||
|
|
| uid | 100002(该用户) | 不变 ✓ |
|
|||
|
|
| **文件系统视野** | **bwrap 合成根**:私有 `/tmp`、`/etc` 是 tmpfs、`/usr`·`/lib64` 只读、仅 bind 该用户目录 | **宿主完整 fs**(/etc、/opt、**其它用户的 `users/<uid>`**)✗ |
|
|||
|
|
| 应用层校验 | **未启用**(见上) | 同样未启用 ⇒ 只剩 uid 权限兜底 ✗ |
|
|||
|
|
| 资源 | 与实例共享 384(互相挤) | 各自独立预算 ✓(= D 的收益) |
|
|||
|
|
| 生命周期/记账 | 随 scope 起停、天然记账 | 要自理(停实例后它还活着?陈旧残留?)+新记账 |
|
|||
|
|
⇒ **判定**:D 技术上能保住 uid 隔离,但**牺牲命名空间/文件系统隔离** = **隔离降级**(R5"扩大可见面")⇒ 要走必须同时补:① 注入 `UNIVER_ALLOWED_ROOT=<该用户 ws>` 启用 allow-list ② 独立 unit 带同样 cgroup 预算 + 硬化 ③ 出权限影响评估并由用户拍板。
|
|||
|
|
|
|||
|
|
**「不同用户共用一个 gateway」= ❌ 不可行**(四条硬理由):① 它是**有状态数据平面**(unit/快照/协同会话在内存)⇒ 共享即数据混在一起;② 路径校验未启用 ⇒ 只能靠闭源 SDK 的应用层鉴权,**无法审计**(供应链 + 越权,R5 扩大);③ 单个用户的大表格能把共享 gateway 压爆 ⇒ **全租户受影响**;④ 无法按租户归因资源。唯一成立的同尺度 = **同一用户自己的多会话共用一个**(现状)。
|
|||
|
|
|
|||
|
|
**★ 由此产生更优的小方案 E(我现在的推荐)**:**给 gateway 加「空闲自停」**(如 10 min 无请求 → 自行退出;下次用再起,0.2.17 已保证能重起)。
|
|||
|
|
- 收益:**不测 univer 时零占用**(探针变"按需施压",日常不再挤实例);**不动隔离、不动闭源 SDK**、改动小(host 侧定时器)。
|
|||
|
|
- 代价:首次打开多 ~5–10 s 启动(当前就绪超时 10 s 需同步调大)。
|
|||
|
|
- ⇒ 把"结构性冲突"降级为"按需成本",且**不碰多租户模型**。
|
|||
|
|
|
|||
|
|
|
|||
|
|
## 10:30 追加:**方案 E 的实现落点(已摸清代码结构)与适用范围**
|
|||
|
|
|
|||
|
|
**代码结构(已读)**
|
|||
|
|
- 生命周期所有者 = `src/host/processes/gateway/supervisor.ts` 的 `GatewaySupervisor`:`status()` / `ensure()` / `start()` / `dispose()`,已有 `gatewayStartupTimeoutMs`(=10 s,即报错里那个 10000ms)、`gatewayRequestTimeoutMs`、`gatewayPort`;`GatewayProcess.stop()` 已存在。
|
|||
|
|
- **关键:`ensure()`/`status()` 每次都先 `gatewayIsHealthy()` 探活**,探不通就重起 ⇒ **gateway 自己退出后,host 会自动重新拉起**(无需 host 侧记账)✓
|
|||
|
|
|
|||
|
|
**E 的正确实现位置 = gateway 侧自停**(不是 host 侧定时器)
|
|||
|
|
- 原因:**worker 与浏览器是直接走 unix socket 的**,host 看不到这部分流量 ⇒ 用 host 侧"最近调用时间"判空闲会**把忙误判成闲**(可能把正在编辑的 gateway 停掉)✗
|
|||
|
|
- 做法:`src/gateway-app/server.ts` 记录「最后一次 HTTP 请求 + 活跃 WS 连接数」(`transport/http.ts` 的 listener 包一层 touch;`transport/ws.ts` 计数连接);定时检查,**无请求 ∧ 无活跃 WS ∧ 超过 `UNIVER_DSH_GATEWAY_IDLE_MS`(默认 10 min,0=关闭)→ `process.exit(0)`**。
|
|||
|
|
- 无需在退出前 unlink socket(0.2.17 已保证下次绑得上)✓
|
|||
|
|
- 配套:`gatewayStartupTimeoutMs` 10 s → **20 s**(冷启动 5–10 s,10 s 太紧)。
|
|||
|
|
|
|||
|
|
**★ E 的边界(必须说清)**:E 只消除**空闲期**占用;**用起来时仍需要 ~480 MB**(实例 90 + gateway 390)⇒ **384 配额下"用的时候照样装不下"**。所以:
|
|||
|
|
- 只做 E ⇒ 探针从"常驻挤压"变"按需施压"(更可控,但用的时候仍会 OOM —— 这恰是探针要观察的);
|
|||
|
|
- **E + (a) 提配额(640/256,常驻数 ≤1)** ⇒ 平时不碍事 + 用起来也不死 = 完整体验。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 10:28–10:4x · 开源 README:**开头改为「亮点」**(用户指出「基线 ≠ 亮点」)
|
|||
|
|
|
|||
|
|
**用户指令**:「部署到一台服务器,多个用户注册并经管理员审核,各自获得一套相互隔离 —— 这个只是项目的基础不是重点,**重点是多租户各自进程的安全隔离、状态恢复、插件和技能的管理**。多看看项目找出这个项目的亮点」「对了多想想 **不要弄了半天亮点都体现不出来**」。
|
|||
|
|
|
|||
|
|
**做法:先读码再写亮点**(不凭印象)
|
|||
|
|
- 按体量排序找最重的模块(`proxy.ts` 1339 · `orchestrator.ts` 1062 · `business-plugins.ts` 742 · `skills.ts` 686 · `k8s-spawner.ts` 687 · `pg.ts` 508 · `plugin-compat.ts` 337 · `leader.ts` 254 …),逐个读头注与关键函数;
|
|||
|
|
- 又读了 `isolation.ts` / `firewall.ts`(端口守卫)/ `fs-guard.ts`(路径围栏)/ `crash-policy.ts` / `reconcile.ts` / `session-preset.ts` / `security-scan.ts` / `spawn.ts` / `patch.ts` / `nginx/generate.ts` / `file-service.ts`;
|
|||
|
|
- 关键数值**逐一回代码核对**(心跳 25000ms、`CrashBreakerOpenError`+`retryAfterMs`、目录 3408 条/覆盖 1854、技能 rank 600/400、扫描 P0/P1 与 8MB)。
|
|||
|
|
|
|||
|
|
**README 改动**
|
|||
|
|
1. 开头删掉「部署到一台服务器,多个用户注册…」(= 基线),换成 **一句定位 + 三条主线速览**(隔离 / 恢复 / 治理)+ 一句「注册审核、独立实例、域名访问是**基线**不是亮点」。
|
|||
|
|
2. `## 特性速览`(6 条形容词式 bullet)→ **`## 亮点`(四大节表格,每条带机制 + 反直觉细节 + 为什么这么设计)**:
|
|||
|
|
- **① 进程级安全隔离**:确定性 uid(`baseUid + row_id`)+ `setpriv` ⇒ `0700` 是**内核边界**;**端口守卫必须在 OUTPUT 链按客户端 uid 匹配且 `-I OUTPUT 1` 插链首**(否则 ufw/conntrack 的 ACCEPT 先吃包;不支持就 fail loud);nftables 只匹配**主动发起**(TCP 纯 SYN / UDP `ct state new`,否则回包一起被拒 ⇒ 实例整体不可用);**浏览器信任围栏**(剥离 `Origin/Referer/Sec-Fetch-*`、Host 覆写回环);env 白名单重建;路径围栏可钉 POSIX 语义;上传 P0/P1 分级扫描。
|
|||
|
|
- **② 故障自愈与状态恢复**:崩溃策略是**可单测纯函数**(退避 + 熔断跨轮存活 + `503 instance_circuit_open` 带 `retryAfterMs`);**按需守护不常驻**(稳态 1 进程/活跃用户);回收后导航过渡页 / XHR 等就绪 / **401 透明重放** / 回页面自动唤醒;注入脚本 25s 心跳 + 断流(**连续两次失败**才恢复);**注入脚本先模板求值再 `node --check`**(grep/`new Function` 都是假绿);K8s informer + reconcile 对账 + 仅 leader 写;**会话档位自诊断**(只读解压多帧 zstd 读实际 preset)。
|
|||
|
|
- **③ 插件与技能受控管理**:双判据兼容性预检(semver **默认语义** + 运行时导出符号);**「兼容 ≠ 可用」**的托管友好性预检(绝对 URL = 阻断);启用三步(探活/快照回滚/逐插件隔离);内存预估+超限拦截;provider 联动;同名上传=**替换**;技能两层(rank 600/400)+ **两阶段原子替换**;**watch-based ⇒ 装卸免重启**;官方目录 ~3400 条(覆盖 54%)。
|
|||
|
|
- **④ 工程纵深**:双后端同接口(local/k8s)、DB 双后端、关键决策皆纯函数、`npm test` + 9 个 `smoke:*`。
|
|||
|
|
3. `功能详解` 加一句「本节是**能力清单**(有什么);机制见**亮点**」,避免与亮点重复;`安全模型` 补 3 行(同机旁路 / 到实例的请求 / 实例环境)。
|
|||
|
|
|
|||
|
|
**验证**:阻断探针 **0 命中**;README 436 行、`## 亮点` 锚点存在;导出物 140 文件。
|
|||
|
|
**技能升到 v1.0.5**:新增 **R-O8 附则 2「README 内容口径」**(开头=亮点非门槛 / 亮点先于功能清单 / 每条必须带机制与数值 / **先读码再写亮点**+具体读法 / 讲出「难在哪」/ 补工程纵深)。归档副本已同步(md5 `39288e87…`),四件套 exit 0,锁已释放。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 10:34–10:4x · README:去掉「议论式」表述(第三轮纠正)
|
|||
|
|
|
|||
|
|
**用户指令**:「这是开源项目描述 写这个是干什么呢,**不是让你讨好我**」—— 直指开头那句
|
|||
|
|
`> 重点不是「能起进程」——多租户演示十分钟就能写出来。重点是这三件事:`
|
|||
|
|
|
|||
|
|
**问题定性**:那句话是**在跟作者对话**(回应我上一轮的"基线 vs 亮点"),不是写给读者的。读者不知道也不需要知道"作者曾经写错过什么";「十分钟就能写出来」这种表达放在开源描述里既没信息量、又像自我表演。
|
|||
|
|
|
|||
|
|
**改动**
|
|||
|
|
1. **开头块引用整段删除**,换成:**一句 tagline**(「把 DSH 变成可对公网运营的多租户服务」)+ **用户视角描述**(注册审核 → 独立实例与文件根 → 网页桌面 + 自有域名)+ **三条重心的名词式列表**(隔离 / 恢复 / 治理)+ 一句指向「亮点」。
|
|||
|
|
2. **去掉重复定位句** —— 原先 tagline 与描述段把「把 DSH 变成…」说了两遍,删掉一处。
|
|||
|
|
3. **四个小节标题去括号议论**:`### 一、进程级安全隔离`(原带「(不是「同机不同目录」)」)· `### 二、故障自愈与状态恢复`(原带「(面向「长期开着」)」)· `### 三、插件与技能的受控管理`(原带「(「能装」和「装了不出事」是两件事)」)· `### 四、工程基础`(原「工程纵深(为什么这些机制可信)」)。**对比与理由留在表格单元格里当技术说明**,标题只写名词。
|
|||
|
|
|
|||
|
|
**复查**:`重点 / 十分钟 / 演示 / 难的是 / 凭什么 / 拖死` 等议论词在 README 中 **0 命中**;阻断探针 0;README 437 行。
|
|||
|
|
|
|||
|
|
**技能升到 v1.0.6**:R-O8 附则 2 再补三条 —— **面向读者不是面向作者**(禁「重点不是 X 而是 Y」)· **小节标题别加议论** · **别重复定位句**。归档副本已同步(md5 `ee943798…`),四件套 exit 0,锁已释放。
|
|||
|
|
|
|||
|
|
> 📌 **三轮纠正的合力(这才是真正的口径)**:① 出处**只放文末**(别替别人宣传)→ ② 开头写**亮点/重心**而非基线门槛 → ③ 措辞**面向读者**、不写议论与自我表演。三条已全部固化进技能 R-O8 附则 1/2。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 10:37–10:5x · 开源文档审计 + 删掉「补齐了 DSH 的哪些缺口」
|
|||
|
|
|
|||
|
|
**用户指令**:「详细检查准备开源的相关文档 是否有相关问题以及 之前提到要重点检查的问题」+「(架构里的)它补齐了 DSH 的哪些缺口 这个内容也 不是重点」
|
|||
|
|
- 第二条已执行:删掉 `## 架构` 下的 `### 它补齐了 DSH 的哪些缺口`(那 4 行讲的还是账号/访问面 = 基线),换成一行指向「亮点」。
|
|||
|
|
|
|||
|
|
**审计方法**:机检为主(脚本比对)+ 抽样人工核。**完整记录写进台账 §八**。
|
|||
|
|
|
|||
|
|
**✅ 通过 9 项**:目录锚点 **15/15** 有效 · 相对文件引用全在 · README 提到的 npm script 全在且 `smoke:*` 计数 = 9 · **install.sh 的 env 名全部真实存在** · **配置表没有一个是代码里不存在的**(反向检查)· 依赖 **178 包**许可证分布与文档**完全一致** · 文档内 **0** 内部档案号 · `ci.sh`/workflow/package.json 引用脚本全在 · 配置默认值逐项核对一致。
|
|||
|
|
|
|||
|
|
**🔧 已修 4 类**(全部在 `_build_export.py` 里有规则,重建不丢)
|
|||
|
|
1. ⚠️ **真 bug(代码不是注释)**:`orchestrator.ts` 的「目录选择器初始化脚本」默认值指向 `poc/workspace-scoped-picker/ensure-workspace-picker.cjs` —— 该插件已随 R-O3 移除 ⇒ 默认值改**空串**(有 `existsSync` 守卫,空即 no-op),`DSH_PICKER_ENSURE_SCRIPT` 保留为可选钩子。**已进 `LINE_REWRITE`**。
|
|||
|
|
2. 4 处注释点名已移除脚本(`ws-cleanup.cjs` / `business-plugins.ts` / `admin.ts` / `orchestrator.ts` 注释)→ 中性化,**已进 GLOBAL**。
|
|||
|
|
3. README 配置表**漏记 11 项**,其中最要紧的是**崩溃熔断 6 项**与**空闲回收 3 项**(正是「亮点」里的功能!)→ 已补全(含 `RESTART_BACKOFF_MAX` / `CRASH_MAX_RESTARTS` / `CRASH_WINDOW` / `CRASH_STABLE` / `CRASH_BREAKER_COOLDOWN` / `CRASH_BREAKER_MAX_COOLDOWN` / `MAX_IDLE_INSTANCES` / `INSTANCE_IDLE_TTL` / `IDLE_REAP_INTERVAL` / `BUNDLED_SKILL_DIR`)。
|
|||
|
|
4. **`install.sh` 写入值与 README「默认值」列不符**(脚本 `ISOLATION_MODE=account` / `ENABLE_PATCH=true` vs 代码默认 `soft` / `false`)→ 表后加「补充说明」:脚本取值差异 + **平台注入变量 3 个**(`ROLE`/`HANDOFF_PATH`/`USER_ROOT`)+ CLI 专用 + 可选钩子。
|
|||
|
|
|
|||
|
|
**验证**:重建后 `tsc --noEmit` **exit 0** · 阻断探针 **0 命中** · 悬空脚本名 grep 为空 · `install.sh` 语法 OK · 140 文件。
|
|||
|
|
|
|||
|
|
**⏳ 待用户拍板 6 项**(**未擅自改**,R7:>10 文件需先点头)
|
|||
|
|
- **① 29 个文件 / 42 处 `docs/*.md` 悬空引用**(`docs/k8s.md`×37、`k8s-deploy.md`×3、`blueprint.md`×1、`domain-config.md`×1;全在注释里;其中 `docs/k8s.md` 上游**从未公开过**)→ 建议做一次纯注释替换(映射到 README 章节)。
|
|||
|
|
- ② 183 处「档案 NN」注释编号(可与 ① 一次做完)。
|
|||
|
|
- ③ **7 处占位符**(`<YOUR_GITHUB_ACCOUNT>`×3 / `<CONTACT_EMAIL>`×3 / `<COPYRIGHT_HOLDER>`×1)发布前必须替换。
|
|||
|
|
- ④ `install.sh` 未在真 Linux 跑过 ⑤ `LICENSE` 自拟条款 + 分层授权前提待确认 ⑥ `poc/` 两个插件是否加回。
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
## 10:38–11:0x 用户转向「保留功能前提下的架构/功能优化」→ 出评估档 80(P0 已上线)
|
|||
|
|
|
|||
|
|
**用户两句话**:① 「看看能否彻底抛弃 dshs 的架构 以及跳转 portal.html,不要在本项目中体现 dshs 任何内容,把 admin 的门户管理都放到设置选项中 弹窗展示」→ ② 「看看如何优化架构和功能,在保留现有功能的基础上 优化重构后有更好的效果」。
|
|||
|
|
|
|||
|
|
**我的判断(写进档案 80)**:**不推倒重来** —— 平台本体(账号/审批/实例生命周期/反代/配额/DB)正是"要保留的功能";而**今天几次事故根因全是「层层补丁叠加」**(模板转义把注入脚本写崩 ×2、配额与插件集合脱钩致 OOM 137、缺有界恢复、自愈 5 代补丁)⇒ 方向 = **收口 + 显式化 + 构建期校验**。
|
|||
|
|
|
|||
|
|
**9 项优化(按事故排序)**:①注入脚本外置+构建期硬校验 ②`npm run verify` 统一校验 ③**实例配额跟随插件集合**(自适应内存;今天 384<390 的实证)④**门户管理就地化 = 弹窗 iframe**(实测平台页**无 `X-Frame-Options`** ⇒ 同站子域可嵌;⚠️ 与档案 05 PoC-2 ① 结论冲突 + **触发 R5**,推荐 iframe 而非能力下沉)⑤自愈收口成一份状态机 ⑥有界恢复 ⑦门户页面结构 ⑧可观测性统一 ⑨文档/代码同仓。
|
|||
|
|
**不做**:重写平台 / 管理 API 下沉进实例 / 改内部命名(systemd·路径·DB)/ 给 univer 设内存上限或禁用(用户明示它是压力探针)。
|
|||
|
|
|
|||
|
|
**P0 已上线(静态页免重启,7 文件)**:`index`→工作台、`login`→登录、`register`→注册、`portal`→管理门户(副标题 →「平台管理」)、`admin`→管理台、`wake`→正在启动工作区、`design.css` 注释去名;线上逐个 `<title>` 取回已验证。
|
|||
|
|
|
|||
|
|
**顺手修掉我自己的工具 bug**:`scripts/docs-index-stats.py` 读取时**没加 `newline=""`** ⇒ 把 INDEX **CRLF 转成 LF**(正是今天记录过两次的陷阱)。已改为「读也 `newline=""` + 按文件自身 eol join」,并修掉历史遗留的 1 处孤立 LF 与摘要行;复跑 3 次确认幂等(CRLF 198/孤立 LF 0),`git diff` 只剩 13/4 行真实改动。**教训升级:自建工具同样必须守 `newline=""`。**
|
|||
|
|
|
|||
|
|
**文档**:档案 80 + INDEX 行 + docs-manifest,四件套 rc=0,对账 一致 133(余 4 不一致 + 2 仅本地 = 其它会话积压);锁与占号(80)均已释放。未 commit / 未 push。
|
|||
|
|
⚠️ 另记:`04-调整方案/.lock-78` 是**并行会话遗留**(它把档案写成 79 却留了 .lock-78),我未触碰(R9)。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 10:43–10:5x · 「模式 B = 未验证」全量标注(用户纠正)
|
|||
|
|
|
|||
|
|
**用户指令**:「模式 B · Kubernetes **去掉这个,根本没验证**,应该是**待开发验证**」。
|
|||
|
|
|
|||
|
|
**判定:不能删代码,但必须停止把它当可选项宣传。** 模式 B 有完整代码(`k8s-spawner` 687 行 / `leader` 254 / `reconcile` 170)+ `deploy/` 11 个清单 + 单测 + `poc/` 实验记录,但**从未端到端部署验收**(真实生产只有模式 A)。⇒ 一律标注「实验性 · 未验证」,并与模式 A 在**同一张表里用状态行对照**。
|
|||
|
|
|
|||
|
|
**改动(README 12 处 + install.sh 1 处 + LICENSE/NOTICE 3 处)**
|
|||
|
|
1. **`## 部署形态` 重写**:开头一句「当前可用的是模式 A」;表格新增 **「状态」行**(A ✅ 生产使用中 / B ⚠️ 未验证,请勿用于生产);把原来的「已完整落地(Phase 0–4)」式段落改为「设计意图 …… **这条路径还没有走过一次完整的部署验收**」。
|
|||
|
|
2. **配置表 7 处** k8s 变量加前缀「(模式 B · 未验证)」/「(模式 B · 未验证;必填)」,`DEPLOY_MODE` 行同步。
|
|||
|
|
3. **亮点/功能详解/安全模型** 里 5 处 `K8s Pod 边界`、`K8s 侧同一套闭环`、`双部署后端共用同一接口`、`数据层双后端` 全部加注「模式 B · 未验证」。
|
|||
|
|
4. **目录结构**:`deploy/` → 「K8s 清单(模式 B · 实验性,未验证)」;`poc/` → 「K8s 可行性 PoC 实验记录 …… 仅实验,未形成可部署形态」;`fs/` 行注明 K8s 实现未验证。
|
|||
|
|
5. **FAQ 新增一条**:「Kubernetes(模式 B)现在能用吗?」→ 直答「**还不能算可用**」并说清缺哪一步。
|
|||
|
|
6. **`install.sh`** 报错文案:`模式 A 仅支持 Linux(K8s 用 deploy/ 下的清单)` → `本脚本只做模式 A(单机);K8s 清单见 deploy/(模式 B,实验性、未验证)`。
|
|||
|
|
7. **归属措辞降级**(LICENSE / THIRD-PARTY-NOTICES / README 授权表与致谢):上游贡献里的「双部署**形态**」→「双部署**框架**」,避免暗示未验证路径是可选项。
|
|||
|
|
|
|||
|
|
**验证**:`grep 未命中` 全通过;残余 `已完整落地|Phase|3 副本` **0 命中**;阻断探针 0;`bash -n install.sh` OK;140 文件。
|
|||
|
|
|
|||
|
|
**技能升到 v1.0.7**:新增 **R-O9 · 只写「已验证的」能力**(宣称必须可复现:**有代码+单测+PoC ≠ 可用**;未验证的标「实验性 · 未验证」并与可用路径同表对照;删「已完整落地/Phase」式表述;配置表与脚本文案同步;FAQ 直答;归属措辞降级;附**落点清单**)。归档副本已同步(md5 `22f0f649…`),四件套 exit 0,锁已释放。
|
|||
|
|
|
|||
|
|
> ⚠️ 修技能时**误删了附则 2 表格的一行**(`别重复定位句`)—— `Edit` 的 old_string 只写了那一行却替换成整段,导致表格断裂;已即时发现并补回 + 补分隔线。教训:**用 Edit 追加内容时,new_string 必须包含 old_string 的全部内容**。
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
## 10:33–10:55 会话 `a2665dd3`:**方案 E 已实现 + lab 验证通过(0.2.19)**
|
|||
|
|
|
|||
|
|
**用户**:「可以试试」⇒ 实施 E(gateway 空闲自停)。
|
|||
|
|
|
|||
|
|
**改动(3 处 + 版本 0.2.19)**
|
|||
|
|
| 文件 | 改动 |
|
|||
|
|
|---|---|
|
|||
|
|
| `src/gateway-app/server.ts` | 新增 `startIdleWatchdog({httpServer, wss})`:每个 HTTP 请求 touch `lastActivity`;`wss.clients.size > 0` 视为"有人在看"(刷新计时);**无请求 ∧ 无客户端 ∧ 静默超过阈值 → 打日志 + `process.exit(0)`**;阈值 `UNIVER_DSH_GATEWAY_IDLE_MS`(默认 **600000 = 10 min**,**0 = 关闭**);定时器 `unref()` + 间隔自适应(阈值/4,夹在 5–60 s) |
|
|||
|
|
| `src/host/processes/gateway/launcher.ts` | 透传 `UNIVER_DSH_GATEWAY_IDLE_MS`(可按实例调,不用重建) |
|
|||
|
|
| `src/host/config.ts` | `gatewayStartupTimeoutMs` 默认 **10_000 → 20_000**(冷启动本来要 5–10 s,10 s 太紧 —— 也是 "did not become ready" 的成因之一) |
|
|||
|
|
- 产物核验:`gateway.cjs` 含 `startIdleWatchdog`×2 / `UNIVER_DSH_GATEWAY_IDLE_MS`×1;`lib/index.js` 含透传 ✓;`tsc` 三个改动文件零错误。
|
|||
|
|
|
|||
|
|
**★ lab 实测通过(独立 socket /tmp/uvlab.sock,阈值 20 s,不碰生产实例)**
|
|||
|
|
- 启动日志:`[dsh-univer-gateway] listening on unix:/tmp/uvlab.sock`
|
|||
|
|
- 25 s 后:**`[dsh-univer-gateway] idle 20s with no client → exiting to release memory`** ⇒ 进程自行退出 ✓✓
|
|||
|
|
- 踩坑:lab 的 `NODE_PATH` 必须 = **`dirname <pkg>`**(即 `.pnpm/<pkg>@…/node_modules`),写成 `$GP/../node_modules` 会多一层 `node_modules` → `Cannot find module`。
|
|||
|
|
|
|||
|
|
**产物**:`dsh-univer-office-0.2.19.tgz`(42,046,985 B / md5 `7ec224c95fa471076227f1b0ed2d3342`);投放中(`http=200` 待确认)。
|
|||
|
|
|
|||
|
|
**回答用户"这插件不就是显示文件吗,为什么这么难"**(要点,已口头给出):它不是"预览器"而是**服务端活文档系统**(协同模型:unit/worktree/快照/变更集);核心是**闭源** `@univerjs-pro/collaboration-service`(加载面不可裁);叠加我们的约束(多租户 + nft 封 loopback ⇒ 只能 unix socket;实例 384 MiB)⇒ 难的不是"渲染",是"在沙箱里常驻一个完整办公套件服务端"。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 10:48–11:0x · README 结构按社区标准重排 + 22 项自审
|
|||
|
|
|
|||
|
|
**用户指令**:「说明文档的顺序还需要优化,参考优秀项目的说明文档,如何结构化展示内容,以及**是否应该把授权等信息放在最前面**,可以去找找有没有写 github 说明的相关 skill」。
|
|||
|
|
|
|||
|
|
**A. 先找技能**(按规矩先搜市场)
|
|||
|
|
- SkillHub 检索到三个对口技能:**`readme-craft`**(README 写作/审计/重写;蒸馏 awesome-readme · Standard README · Art of README · Make a README · GitHub Docs · thoughtbot)、`standard-readme`、`readme-grader`。
|
|||
|
|
- 下载到**工作区内**临时目录(`$HOME` 写入被沙箱拒;SkillHub 的 download API 只在工作区内可落盘)读完即清理;**未安装到本机**。`readme-craft` 详情页带两份厂商安全报告(keen / sanbu 均"安全,无风险")。
|
|||
|
|
- WorkBuddy 内置市场搜 `readme` 只有 `git-copilot`(偏提交信息),无专用结构技能。
|
|||
|
|
|
|||
|
|
**B. 回答「授权是否放最前」= 不放,但要加一行摘要**
|
|||
|
|
- 依据:**6 个权威来源共识排序**均把 License 放在**最后**;readme-craft 的 **Trust 评分项**明文「License 在最后?」。
|
|||
|
|
- 但我们是**双授权**(非 MIT)⇒ 授权是**决策信息** ⇒ 在徽章下方加**一行授权摘要**(+锚点链接),完整条款仍在末节,并以「授权摘要 + **非 SPDX** 说明」收尾。
|
|||
|
|
|
|||
|
|
**C. 重排为 16 节(认知漏斗 Hook → Onboarding → Content → Trust → Meta)**
|
|||
|
|
- 关键移动:`安全模型` 从「版本与迭代之后」**上移到 `架构` 之后**(原位置属漏斗倒序);`插件移植指南` 提到 `开发` 前;`第三方组件与致谢` 与 `授权` 对调 ⇒ **授权压轴**。
|
|||
|
|
- **用脚本按 `## ` 标题切分再拼装**(不手工搬),重排后**重算 TOC** 并机检:**16/16 锚点可解析、顺序与章节一致**。
|
|||
|
|
|
|||
|
|
**D. 补齐审计扣分项**
|
|||
|
|
- **H3 Hero Visual**:Hero 区(前 50 行内)新增**多租户拓扑图**;`架构` 节里那份重复拓扑改写成**两条链路**(请求 + 自愈,含熔断 10min→6h)⇒ 同时消除信息重复。
|
|||
|
|
- **C3 API 概览(原来 0 分)**:新增 **`## 控制面 API`**,10 组端点**全部用 `grep -oE "app\.(get|post|put|delete)\("` 从 `src/web/routes/*.ts` 抓真实路径**(含 `skills/mine` 两阶段暂存→apply、`plugins/mine/apply` + `task/:id` 轮询)。
|
|||
|
|
- **H2**:让 `package.json` 的 `description` 与 README 一行描述**同一起句**(写成 `_build_export.py` 规则)。
|
|||
|
|
- 顺带修:表格单元格里**不能写裸 `|`**(`GET|POST` 会截断表格)。
|
|||
|
|
|
|||
|
|
**E. 自审结论:93/100 → S 级**(Hook 25 · Onboarding 25 · Content 20 · Trust 9 · Structure 9 · Polish 5)。**扣分只有"发布后才能补的"两项**:CI 徽章 0/3、维护者信息 0/3(`<CONTACT_EMAIL>` 未填);另信息重复扣 1。
|
|||
|
|
|
|||
|
|
**技能升到 v1.0.8**:新增 **R-O8 附则 3 · README 结构顺序**(10 条硬规则 + 22 项审计口径 + 定稿顺序图)。归档副本已同步(md5 `31d98264…`),四件套 exit 0,锁已释放。临时技能目录已清理。
|
|||
|
|
|
|||
|
|
|
|||
|
|
## 11:0x 用户授权「按方案改进,要新架构 + 命名细节」→ 出档案 81(总纲)+ R0 完成 + R2 提前做掉一项
|
|||
|
|
|
|||
|
|
**用户原话**:「按照方案改进,要求改造后是一个新的更好的架构包括命名方式等细节」+「按06-工作台ui规范调整优化ui,包括登录窗口样式等,如点击登录后停留几秒不能操作按钮也没有加载动画」。
|
|||
|
|
|
|||
|
|
**交付 1:档案 81《目标架构与命名规范(重构总纲)》**
|
|||
|
|
- **命名**:对外统一「**工作台**」(英文 **DSH Workspace**);内部统一前缀 **`dshs`**(单元 / 源码目录 / 数据目录 / DB / env 前缀 / 包名 / 注入脚本 / 注入全局标记 / CSS 类全表,每项都写了**迁移方式**)。
|
|||
|
|
- **兼容策略(硬要求)**:内部改名一律 **双名期(symlink + 新旧 env 双读)→ 切换期(删旧名)**,绝不硬切 —— 今天刚因"hooks 绝对路径失配导致全机写操作被拒"上过一课。
|
|||
|
|
- **六层架构**:静态 / 注入 / 控制面 / 数据面 / 管理面 / 可观测;**4 处关键改动**:注入脚本出模板字面量 · 自愈收口成状态机 · **配额跟随插件集合** · **有界恢复**。
|
|||
|
|
- **分期 R0–R4**(每期带验收与回滚):R0 ✅ 本轮完成|R1 四项(需一次重启)|R2 管理面就地化(**先 R5 评估**)|R3 内部标识迁移(两次窗口,最后做)|R4 文档同仓。
|
|||
|
|
|
|||
|
|
**交付 2:R0 全部完成**
|
|||
|
|
- 静态页去痕迹(7 文件,已上线);`npm run verify` 统一入口(build + 单测 + 注入脚本**运行时**校验 + 静态页不变量)。
|
|||
|
|
- **新增 `scripts/verify-static.mjs`**:① 去痕迹(`web/**.{html,css,js}` 不得含内部平台名)② title 非空 ③ wake.html 五个 id + 熔断分支 ④ **内联 `<script>` 必须能 `node --check`**(今天两次事故都是"脚本语法错 = 静默失效",此前无任何校验能发现)—— 现 10 段内联脚本全通过。
|
|||
|
|
|
|||
|
|
**交付 3:R2 提前做掉「登录/注册页提交忙碌态」(用户报障,已上线)**
|
|||
|
|
- 根因:`login.html` 提交处理**没有任何忙碌态**;`/api/auth/login` 走 bcrypt 数秒、`/api/dsh/enter` 还要拉起实例(可达 20s)⇒ 期间按钮可点、无反馈。
|
|||
|
|
- 改法(对齐 06-UI规范 Token,只加不删):`.btn[disabled]` / `.btn.is-busy` / `.btn .sp`(14px 转圈)+ `prefers-reduced-motion` 关闭 + 通用 `setBusy(busy,label)`(记住原 innerHTML、退出原样恢复)+ **两段式文案**「登录中… → 正在进入工作台…」;register 同构(成功后保持禁用防重复注册)。
|
|||
|
|
- **真 Chrome 实测**:点击后 **220 ms** 即 `disabled=true` + `is-busy` + spinner +「登录中…」;失败后恢复「登 录」并显示「用户名或密码错误」。截图 `_patch77/shots/login-busy.png`。
|
|||
|
|
- 部署:静态页 + 校验脚本 scp(**免重启**)。回滚 `git checkout -- web/login.html web/register.html`。
|
|||
|
|
|
|||
|
|
**文档**:档案 81(含 §七 进度追加)+ INDEX 行 + manifest 已推送;四件套 rc=0;锁与占号(81)已释放。未 commit / 未 push。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 11:04–11:1x · README 撤掉前排授权摘要 + 🔴 **误放别的会话的锁(事故)**
|
|||
|
|
|
|||
|
|
### 一、README(按用户意见)
|
|||
|
|
用户指出「授权……」虽然章节在最后,但**前排正文里还留着我加的那句授权 blockquote** ⇒ **撤掉**。
|
|||
|
|
- 现状(已核实):`## 授权与商业使用` 本来就在**最后**(第 464 行 / 共 484 行);前排只剩 **① License 徽章(第 7 行,链接到末节)+ ② 我加的第 12 行摘要**。
|
|||
|
|
- 处置:**删掉第 12 行那句 blockquote** ⇒ 全文授权信息只剩:顶部**徽章** + 目录项 + **末节**(含文末一行摘要)。徽章已写明 `personal free | commercial paid`,不丢信息。
|
|||
|
|
- 验证:TOC **16/16 锚点正确、顺序一致**;阻断探针 0;484 行。技能 **v1.0.9** 同步改口径(附则 3 第 2 条:**授权不进前排正文**,靠徽章 + 末节摘要)。
|
|||
|
|
|
|||
|
|
### 二、🔴 事故:把另一个会话的全局执行锁 `rm -rf` 掉了(已恢复)
|
|||
|
|
**经过**:为同步技能归档副本,我把 `--claim-exec` 与 `--release-exec` 写在**同一条命令**里,并把抢锁输出**重定向到文件**只显示了 `OWNER` 首行 ⇒ **没看见「✗ 抢锁失败」**。那时锁属于 **`R1-注入层-1104`**(11:03 起)。命令末尾的 `--release-exec` 实现是 `rm -rf "$LOCKEXEC"` ⇒ **别人的锁被我删掉**。
|
|||
|
|
|
|||
|
|
**我的两个失误**:① 抢锁结果没当场看;② claim 与 release 同命令(**这正是我自己在技能坑 16 里写过的禁忌**,写了却没守住)。
|
|||
|
|
|
|||
|
|
**恢复**:该脚本的 release **不留档**,于是按 `handoff-guard.sh:43` 的格式**原样重建** —— `R1-注入层-1104` / `开始:09-13 11:03` / `在做:(未声明单号)`(值取自抢锁失败时的输出)。已验证:信息模式显示占用者仍是 `R1-注入层-1104`,我自己再抢**被正确拒绝** ✓。**该会话的 `--release-exec` 仍可正常释放**,流程未断。
|
|||
|
|
⚠️ 若 `R1-注入层-1104` 其实已结束,这个锁会变成**残留锁** ⇒ 需**用户本人**撤(处置权只属于用户)。
|
|||
|
|
|
|||
|
|
**同时披露**:在那段「以为自己是持有者」的窗口里,我还做了两件事 —— ① `cp` 覆盖了**自己的**技能归档副本(低风险);② 跑了四件套,其中 `docs-manifest.py` **写了一次 `docs-manifest.json`**(派生文件、可重生成)。两者都与对方的工作无内容冲突,但**纪律上不该发生**。
|
|||
|
|
|
|||
|
|
**已固化的改进**:技能 §8 坑 16 **升级为 🔴 事故级**(claim 输出必须当场看 / 放锁前再确认 OWNER / 误放后按 `handoff-guard.sh:43` 重建并告知用户);技能版本 v1.0.9 → **v1.1.0**。
|
|||
|
|
⏳ **待补**:技能归档副本仍停在 v1.0.9(锁被别人持有 ⇒ **按纪律停手**),锁释放后按 §10 清单同步。
|
|||
|
|
|
|||
|
|
## 11:0x–11:5x 「继续处理直到改造完毕」:R1 全部落地并验收 + R3 双名期第一步 + R2 的 R5 评估
|
|||
|
|
|
|||
|
|
**R1(需重启,已完成)**
|
|||
|
|
- ① 注入脚本外置:`assets/inject/{recovery,assist}.js` + `proxy.ts` 用 `loadInject()` 运行时读(fail-fast)⇒ 27KB 内联模板字面量删除,「转义写崩」从根消失
|
|||
|
|
- ② 自愈收口:`recovery.js` 头部写明**唯一状态机表**(5 类触发面 × 判定 × 动作)
|
|||
|
|
- ③ 有界恢复:10 min 内恢复 >3 次 → 停手 + 手动「重试」按钮(治无限循环)
|
|||
|
|
- ④ 配额随插件集合:`instanceMemMb()`(160+Σ预估,下限384/上限1024)+ V8 堆**由配额独占**(`withHeap()` 摘掉 env 里的旧值再按配额补回,`DSHS_HEAP_MB_OVERRIDE` 可强指)
|
|||
|
|
- **验收**:服务 active(PID 435201)、门户 200;**guest 新实例 MemoryMax=544MB + NODE_OPTIONS=--max-old-space-size=256**;guest 实例页含 `overRecoverBudget`/`__dshRetryBtn`/状态机表/新视觉 ✓
|
|||
|
|
- ⚠️ **事故(约 1 分钟不可用)**:`__dirname` 在 ESM 里不存在 → 平台启动即失败、systemd 重启 3 次;改用 `dirname(fileURLToPath(import.meta.url))` 后恢复。**教训:`npm run build` 通过 ≠ 能跑,ESM/CJS 运行时差异编译期查不出 ⇒ 部署后必须立刻 curl 自证。**
|
|||
|
|
- ⚠️ 另一发现:`DSH_INSTANCE_NODE_OPTIONS` 不在 unit / manager env 里却仍在进程 env ⇒ **改配置够不到**,只能代码侧独占堆。
|
|||
|
|
- 备份 `/opt/dsh/backups/pre-r1-20260913-110923`。
|
|||
|
|
|
|||
|
|
**R3 双名期第一步(零中断,已完成)**:`/opt/dshs`、`/var/lib/dshs`、`dshs.service` 三个 symlink + daemon-reload ⇒ **两名字同 PID 435201**;回滚一条口令(`rm -f` 三个 + daemon-reload)。剩余:env 双读 → 逐条改绝对路径 → 切换期(改单元真名 + 迁 DB)。
|
|||
|
|
|
|||
|
|
**R2(待用户拍板)**:已出 **R5 权限影响评估**(结论:建议批准「iframe 内嵌 + 仅 admin + 只读优先」;实测 portal.html 无 `X-Frame-Options`)。
|
|||
|
|
**R4**:两条路(文档并入代码仓 / 保持双库但单向导出)待用户选。
|
|||
|
|
|
|||
|
|
文档:档案 81 §八/§九 已写入并推送;四件套 rc=0;双锁已释放。未 commit / 未 push。
|
|||
|
|
|
|||
|
|
## 11:18 新需求立项:**多语言(i18n)· 排最后(R5)**
|
|||
|
|
|
|||
|
|
用户原话:「加个需求 相关页面能否 支持多语言,可以最后处理」。
|
|||
|
|
- 已写进 **档案 81 §十**(范围 / 方案 / 为什么排最后 / 验收口径)+ §四 分期表加 R5 行 + **03-路线图 待办表**加「R2–R5 待排期」行 + INDEX 04-81 行补记。
|
|||
|
|
- **范围**:✅ 平台 6 个静态页(`/`/login/register/portal/admin/wake)+ 注入脚本文案 + 平台 API 错误码→文案映射;❌ **不动**实例内 dsh 官方 UI(R2 红线)与业务插件文案。
|
|||
|
|
- **方案**:`assets/i18n/{zh-CN,en}.json` 词条表 + `assets/i18n.js` 极小运行时(`?lang=` > cookie `dshs_lang` > `navigator.language` > zh-CN 兜底;`data-i18n` 标注就地替换;缺 key 回退中文并 warn)+ 页脚语言下拉;`verify-static.mjs` 加「裸中文」防回归判据。
|
|||
|
|
- **排最后的三条硬理由**:① R2 门户改造会横穿页面,先抽白抽 ② R3 命名未收尾,`assets/i18n/` 目录要对齐 `dshs` 体系 ③ 纯体验增强、不修事故(今天 4 类事故已闭环)。
|
|||
|
|
- 同步记录 R2(待 R5 权限确认)/ R3(双名期第一步已做,剩 env 双读+路径改写+切换期)/ R4(待选 a/b)的状态。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 11:18–11:3x · 致谢「只写一行」+ 🔴 发现导出基线在漂
|
|||
|
|
|
|||
|
|
### 一、按用户意见精简(第七次文案纠正)
|
|||
|
|
用户指出致谢行「**有必要讲这么多吗,好好想想**」。原文三段里有两段是在讲**我们自己**:
|
|||
|
|
- 「此后在本仓库做了大量深度改造」= **自我表扬**(致谢不是讲功绩的地方)
|
|||
|
|
- 「项目名也从它的 X 换成了我们的 Y(避免指代混淆…)」= **内部事务**,读者不关心
|
|||
|
|
- 「骨架范围枚举(认证与审核 · 网页桌面 · …)」= 与 `LICENSE` 第一层 / `THIRD-PARTY-NOTICES.md §4` **重复**
|
|||
|
|
|
|||
|
|
**改为一行**:`起步时参考了 [dshs](作者 上游作者(已按要求不再具名),MIT)—— 感谢作者开源。`(法律范围保留在 LICENSE / NOTICE 两处 ✓ 未丢)
|
|||
|
|
|
|||
|
|
**同类一并清掉**(这才是"好好想想"):
|
|||
|
|
- 版本与迭代里重复的议论句「本项目把『能跑』补齐为『能对外长期运维』」→ 删(标题已是「六组改造」)
|
|||
|
|
- 授权节尾「—— 这是 MIT 的硬性要求,必须保留」→ 删解释,只留规则本体
|
|||
|
|
- 部署形态末「因此 README 与 install.sh 一律以模式 A 为准」→ 删(关于文档自身的话术)
|
|||
|
|
- DeepSeek Harness 那条收紧为一行
|
|||
|
|
- **三处「机制见亮点」指针砍到 2 处**(Hero + 功能详解),NOTICE 致谢行去掉与致谢无关的自家验证状态括注
|
|||
|
|
|
|||
|
|
**规则固化**:技能 R-O8 附则 1 新增 3 条 —— **致谢只写一行**(禁骨架枚举 / 禁自我表扬 / 禁改名理由)· **别解释我们为什么这么写** · **指针行不重复**。
|
|||
|
|
|
|||
|
|
**验证**:TOC 16/16 锚点正确;探针 0;`tsc --noEmit` exit 0;README 478 行。
|
|||
|
|
|
|||
|
|
### 二、🔴 导出基线在漂(本轮最重要的发现)
|
|||
|
|
重建时探针突然报 **`mcn-suite`** ⇒ 溯源发现:**源码仓 `D:\github\dsh_shenxian` 的 HEAD 仍是 `da3e0f9`,但工作树已有 20 个未提交文件**(`orchestrator.ts` / `crash-policy.ts` / `proxy.ts` / `spawner.ts` / `config.ts` / `web/*.html` / `test/*` / `package.json` …)—— **多个会话正在并行改源码**(R1-注入层 / R5多语言立项 等)。而导出脚本**复制的是工作树** ⇒ **导出里混入了别人未完成的改动**,且新增标识(`'dsh-plugin-mcn-suite'`、内存预估表)随他们提交冒出来。
|
|||
|
|
|
|||
|
|
- 已处理:新增 GLOBAL 规则把 `'dsh-plugin-mcn-suite'` → `'某插件套件'`、注释去 MCN 化(`dsh-univer-office: 384` 按特许保留 ✓);重建后探针 **0 命中**。
|
|||
|
|
- **发布前必须二选一**(已写进技能 §10):① **改成按 commit 取**(`git archive <commit>`,才是真正的冻结版本);② 或接受"含未提交工作",则**每次重建都要重跑探针**并复核 README 描述与实际一致。
|
|||
|
|
|
|||
|
|
### 三、待补(锁在别的会话手里)
|
|||
|
|
技能已到 **v1.1.x**,归档副本仍停在 **v1.0.9** —— 锁先后被 `R1-注入层-1104`(11:03) / `R5多语言立项-1120`(11:18) 持有 ⇒ **按纪律停手**,同步清单已写进技能 §10。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|