交接单按归属约定(正文落文档库、工作区只放指针)归档至 归档/交接单-20260924-归档/,含逐件判定 README: - 16 件已被文档库正式版取代(T09–T21 + 覆盖网络-24/25/26) - 4 件主题已被覆盖网络线入口汇总 - 7 件历史接续包/规划件 CODEBUDDY §9:收口清本棒 tmp、tmp 保留期 7 天、禁「待清理」中间态、 工作区入库只放文档与文件、不保留脚本副本、>60 KB 单文件须逐个判。
22 KiB
交接单 · Manager 重启后「逐步拉起」既有实例
- 序号:覆盖网络线 序 ㉕ · 规划棒
- 立单:2026-09-17 20:3x
- 用户拍板:「Manager 重启后是否自动拉起既有实例:逐步拉起」(2026-09-17 20:2x)
- 上游依据:档案 30(
dsh-server-docs/04-调整方案/30-编排器孤儿实例清理.md)/序⑲ §8.8-1("按需拉起、无 reconciler"的实测)/序⑬ P3(OBS-09因无实例而 SKIP) - 状态:待执行
§0 摘要 + ⚠️ 一条必须先说的反直觉事实
用户拍板 = "逐步拉起"(staggered rehydrate)。但读源码发现:现在的架构与目标不是"缺一个 reconciler",而是"启动时主动清空" ——
| 位置 | 现状 | 与目标的差距 |
|---|---|---|
src/supervisor/orchestrator.ts:208(构造函数) |
this.cleanAllStaleScopes() —— portal 启动即清掉全部实例 scope |
🔴 这是"逐步拉起"的直接阻碍:重启后 N=0,没有任何可接管的实例 |
:1225 |
stopScopesByPrefix('dsh-', /^dsh-\d+-[0-9a-f]+\.scope$/) |
清得很干净(连"哪些曾经在线"的信息一起丢了) |
:1218 |
cleanStaleScopes(uid) —— spawn 前清同 uid 名下的残留 scope |
⚠️ 这一条是必要的(档案 30 的根因:双实例共 profile ⇒ 写冲突)⇒ ⛔ 不许为了"逐步拉起"把它去掉 |
根因链(档案 30 原文):实例真实生命周期在 OS 层(systemd scope),而编排器只靠内存 map 追踪 ⇒ 重启后 map 空 ⇒ 旧 scope 变孤儿(编排器不认识)⇒ 双实例共享 profile ⇒ session/settings 写冲突 ⇒ "模型连接异常"。当时的修法是"统一清掉防孤儿"。
⇒ 本单的核心判断:"逐步拉起"= 把"清空"换成"接管" —— 启动时不要清,而是扫描 OS 层既有 scope ⇒ 逐个回填进内存 map ⇒ 按节流逐个 probe/rehydrate。⚠️ 前提是先有"可恢复的实例账本"(否则"清"是唯一安全的做法)。
§1 目标(可判定"做完了没有")
| # | 判据 | 期望 | 怎么测 |
|---|---|---|---|
| E1 | 重启后实例不被清掉 | restart dshs 后 5 s 内,重启前在跑的 scope 仍在 |
先起 2 个实例 ⇒ restart dshs ⇒ systemctl list-units 'dsh-*.scope' 仍为 2 |
| E2 | 逐个(节流)接管,⛔ 不风暴 | 接管速率受限(REHYDRATE_CONCURRENCY / REHYDRATE_STAGGER_MS 阈值内) |
4 个实例 ⇒ 日志逐条时间差 ≥ 阈值;⛔ 无"同一秒 4 条" |
| E3 | 任一实例都是原实例(不是新起) | 接管后 port 不变、无新 scope 产生 |
对比重启前后的 scope 名与 --port 参数 |
| E4 | 单实例保证仍成立(⛔ 不回归档案 30) | 接管后同 uid 只有 1 个 scope;新 spawn 前仍清同 uid 残留 | 断言 dsh-<uid>-* 计数 = 1 |
| E5 | 无实例时不自起(⛔ 不违反"按需"语义) | 重启前没有实例 ⇒ 重启后仍为 0 | 空跑一次 restart dshs ⇒ scope = 0 |
| E6 | 内存/启动压力可控 | 接管过程 47 可用内存不跌穿阈值;无 OOM kill | free 采样 + journalctl -u dshs | grep -c 'oom' = 0 |
| E7 | 零回归 | npm test 176/175/0/1、--scene all --table 12P/0S/0F、overlay-probe --table 16P/0F/0S |
三件套跑同值 |
§2 只读前置(执行前必须先核实的 4 条)
| # | 要核实什么 | 命令 | 期望 |
|---|---|---|---|
| P1 | 实例 scope 与 uid 的对应 | systemctl list-units 'dsh-*.scope' --plain + 对照 users 表 |
确认 dsh-<uid>-<id>.scope 里 <uid> = 用户 uid(档案 30 口径),⛔ 别与"用户 id"混 |
| P2 | scope 的 --port 参数重启后是否可读回 |
cat /proc/<pid>/cmdline 或 systemctl show -p MainPID 后读 /proc |
⚠️ 这是可恢复性的关键:拿不到端口 ⇒ 没法接管(要另想办法) |
| P3 | 47 上当前实例数(T0 现场) | systemctl list-units 'dsh-*.scope' |
序㉓ 实测 47 = 0、106 = 1(⚠️ 实例在 106 上)⇒ 本单验收要主动先起实例 |
| P4 | 是否有"实例账本"可持久化 | 查 RemoteSpawner / LeasedSpawner 落库情况 |
若既无 DB 记录也无 /proc 可读 ⇒ 必须先补一步"实例账本"(见 §4 S1 前置) |
§3 范围
3.1 在册文件集(⚠️ 超出必须先停下报告 — R7)
| 面 | 文件 | 说明 |
|---|---|---|
| 改动 | src/supervisor/orchestrator.ts |
①② 启动流程:清空 → 接管;新增 rehydrate() |
| 改动 | 参数表_覆盖网络_20260917.md |
新增 REHYDRATE_* 键 |
| 改动 | scripts/overlay-probe.cjs |
新增观测项(重启后 scope 数 = 重启前) |
| 可能新增 | test/orchestrator-rehydrate.test.mjs |
单测(⛔ 不改 package.json test 列表的结构) |
⛔ 不在本单范围:改 bwrap 参数(🔴 47 是 bubblewrap 0.4.0,--perms 属 0.5+ ⇒ 会起不来)、改 cleanStaleScopes(uid) 的"spawn 前清同 uid"语义、动 RELAY_FAILOVER_* / HB_SEC / burst。
3.2 三条硬约束(违反即事故)
- 🔴
cleanStaleScopes(uid)(spawn 前清同 uid)⛔ 不许删 —— 它是档案 30 的修法本体,删了就回到"双实例共 profile"。 - 🔴
cleanAllStaleScopes()只能改成"接管",⛔ 不能改成"什么都不做" —— 因为内存 map 为空时,孤儿会失控(这正是档案 30 的原始故障)。 - 🔴 接管必须"认原实例" —— ⛔ 不许"重启后重新 spawn 一遍"(那会换端口、换 scope 名,且瞬间 N 个一起起 = 启动风暴)。
§4 步骤 S0–S6
| 步 | 做什么 | 验证(一次可执行) | 回滚 |
|---|---|---|---|
| S0 | 只读前置 P1–P4(零改动) |
拿到"scope 名 ↔ uid ↔ 端口"三者的可读性结论 | 无(只读) |
| S1 | (可能需要)实例账本:把"在线实例"持久化(⚠️ 归属判定:跟着用户走 ⇒ 放实例 home;本机运维用 ⇒ Worker 本地库;⛔ 只有 Manager 能写归属/租约) | 重启后能列出"重启前在跑的实例"清单(含 uid / 端口) | 删账本文件 |
| S2 | 接管取代清空:启动扫描 → 回填 map(⛔ 不 stop、⛔ 不 spawn) | E1:restart dshs 后 scope 数不变 |
还原 cleanAllStaleScopes() 调用 |
| S3 | 节流:REHYDRATE_CONCURRENCY / REHYDRATE_STAGGER_MS |
E2:4 实例 ⇒ 日志时间差 ≥ 阈值、⛔ 无同秒多条 |
阈值调回 0 ⇒ 回 S2 行为 |
| S4 | 单实例保证复验(⛔ 不回归档案 30) | E4:同 uid 恒 1 个 scope;E5:无实例时不自起 |
同 S2 |
| S5 | 观测 + 探针新项 | 探针出一项 PASS/FAIL(⛔ 阈值不许是脚本魔数) | 还原探针 |
| S6 | 真机验收(E1–E6)+ 零回归(E7)+ 收口 |
E6:内存不跌穿、无 OOM;三件套同值 |
产物回滚点 |
§5 真机验收怎么造条件的(⚠️ 关键)
⚠️ 不能只靠"等用户访问"(本线已因"无 reconciler ⇒ 不自回"卡过一次)。验收必须:
- 先主动起 2–4 个实例(用 R4 临时 session 走真实
enter,或用本机多实例)⇒ 记录 scope 名 / uid / 端口; systemctl restart dshs;- 立刻(≤ 5 s)与 +60 s 两个时刻各取一次
list-units 'dsh-*.scope'; - 断言
E1(数不变)/E2(逐条时间差)/E3(端口不变、⛔ 无新 scope)/E4(同 uid = 1)。
⚠️ 已知副作用:restart dshs 会收掉用户实例 scope(本线既有实测)⇒ 本单要故意利用这个副作用做 E1/E5 的对照(重启前有实例 → 应被接管;重启前无实例 → 应保持 0)。
§6 回滚(两层)
- 代码级:还原
orchestrator.ts的启动流程为原cleanAllStaleScopes()调用 →tsc→ scplib/→restart dshs⇒ 回到"统一清掉防孤儿"(档案 30 行为)。 - 产物级:scp 回滚点
/opt/dsh/backups/seq25-<ts>/lib/supervisor/orchestrator.js。 🔴 ⛔ 回滚路径里不许出现RELAY_FAILOVER_COOLDOWN_MS=0。
§7 回报格式(执行会话必须回填)
### 8.x 序 ㉕ 执行棒回报(YYYY-MM-DD HH:MM–HH:MM)
#### ① S0–S6 逐条(命令 + 原文输出 + 判定)
#### ② 主判据 E1:重启后实例数不变
- 重启前 scope = ? | +5 s = ? | +60 s = ? | 端口是否全同 = ?
#### ③ 先红后绿(原文级)
- (先跑"仍清空"的旧行为 ⇒ 哪条断言红;改成接管 ⇒ 绿)
#### ④ 零回归三件套(均带 `--table`)
#### ⑤ 边界自证
⛔ 未改生产值 / ⛔ 未改 bwrap 参数 / ⛔ 未删 `cleanStaleScopes(uid)` / 🔴 `COOLDOWN_MS=0` = ? / ⛔ 未 commit·未 push
#### ⑥ 指纹
参数表 = ?(前值 `42238175d84319ada99afa56d583f9db`)|orchestrator.ts md5 = ?
§8 回头条件(一出现必须回头)
P2拿不到 scope 的端口 ⇒ 停下报告("接管"不可行,需先出"实例账本"方案)。- 接管导致双实例共 profile(档案 30 故障复现)⇒ 立刻回滚 + 报告。
- 需要超出 §3.1 文件集 ⇒ 停下报告(R7)。
- 要改 bwrap 参数 / 改配额 / 动生产值 ⇒ 停下报告。
E2跑不出节流(同秒多条)⇒ 停下报告,⛔ 不许放宽判据。- 零回归三件套任一退化 ⇒ 停下报告。
- 🔴 ⛔ 不许把
RELAY_FAILOVER_COOLDOWN_MS=0写进任何回滚 / 演练 / 夹具路径。
§9 未验证项
| # | 项 | 状态 |
|---|---|---|
| 1 | 重启后能否从 OS 层读回实例端口 | ⚠️ 待 P2 取证 |
| 2 | 是否需要独立"实例账本" | ⚠️ 待 P4 取证 |
| 3 | 接管 4 个实例的内存与启动耗时 | ⚠️ 待 S6 实测 |
§10 指纹与状态
| 项 | 值 |
|---|---|
| 本单 §7 之前正文前缀指纹 | d9d48121e68faa00e1da17ad8fea36ad(口径 = sed '/^## §7 /,$d' 交接单_实例逐步拉起_20260917.md | md5sum) |
| 参数表指纹(立单时) | 42238175d84319ada99afa56d583f9db |
| 代码仓 HEAD(立单时) | 04776af |
| 本单全文件 md5(立单时) | 2c53a018c2d9bfc2c422d0758413a485(161 行) |
| 本单状态 | 已执行(2026-09-17 22:05–22:2x) —— ✅ 认领逻辑已落地并在真机夹具上实测工作;🔴 但主判据 E1 在"真实实例"上未达成,根因 = 单子未识别的第二条杀实例路径(退出时 teardown())。详见 §11。⏸ 本序未登记续棒(需你拍板是否扩大文件集)。 |
§11 序 ㉕ 执行棒回报(2026-09-17 22:05–22:2x)
8.1 六段回报
① S0–S6 逐条
| 步 | 结果 | 命令 / 证据 |
|---|---|---|
| S0 只读前置 P1–P4 | ✅ | P1 dsh-100002-ef8d1d12.scope ⇒ scope 名第 2 段 = uid(100002)✅。P2 ⚠️ systemctl show -p MainPID --value 为空,但 -p Description 携带完整 argv(末尾 --port 21000、--reuid 100002、--chdir …/users/4092b965-…/ws)⇒ 端口可读回,通道是 Description 不是 /proc;且解析可用 三道自洽校验(--profile 必有、--chdir 含 /users/<uuid>/、--reuid == scope 名 uid)⇒ §8 回头条件 1 未命中。P3 T0 现场:47 dsh-*.scope = 0、106 = 1(实例在 106)✅ 与序㉓ 同。P4 🔴 dsh_instances 表全仓零调用方(src/db/pg.ts 定义了 upsertInstance/getInstanceByUser/…,但 grep -rn 在 src/db/ 外 0 命中)= k8s 形态预留的死代码 ⇒ 无现成账本 ⇒ 采纳「以 OS 层 scope 为唯一权威」,⛔ 不新建账本(新建 = 引入双写与陈旧风险,R11)。 |
| S1 实例账本 | ✅ 判为不需要 | 由 P4 得出:所需四要素(uid / userId / port / folder)全部可从 scope 名 + Description 解出 ⇒ 加账本是净负担。 |
| S2 接管取代清空 | ✅ 代码已落 / 🔴 真机部分达成 | 构造函数由 cleanAllStaleScopes() 改为 rehydrateAdoptedScopes();新增 scanExistingScopes/scopeDescription/adoptOne/probeAdopted/stopUnit;cleanAllStaleScopes() 保留(非 account 回退 + 回滚路径)。 |
| S3 节流 | ✅ | 逐条经 setTimeout 排队,间隔 DSHS_REHYDRATE_STAGGER_MS(缺省 500 ms);真机日志逐条时间差 ≥ 阈值,⛔ 无同秒多条。 |
| S4 单实例保证复验 | ✅ | 同 uid 多 scope ⇒ decideScopeAction 判 dup-uid ⇒ 全部停;cleanStaleScopes(uid)(spawn 前清同 uid)一字未动 ⇒ 档案 30 未回归。 |
| S5 观测 + 探针新项 | ✅ | 参数表新增 DSHS_REHYDRATE_STAGGER_MS / DSHS_REHYDRATE_PROBE_MS + §6 OBS-18;探针新增 OBS-18 + --rehydrate-fixture(三态夹具实证见 ③)。 |
| S6 真机验收 + 零回归 | ⚠️ 部分 | 零回归 ✅(见 ④);E1 夹具 ✅ / 真实实例 ❌(见 ②)。 |
② 主判据 E1:重启后实例数不变
-
夹具 scope(真实 systemd scope + 真实 argv 形态 + 真监听 21500):重启前
dsh-100002-abcdef01.scope= 1 ⇒restart dshs-worker⇒ +6 s 仍is-active=active、21500 仍在听、count = 1 ⇒ 🎉 E1 ✅(证明 systemd 不会带走 scope、认领逻辑确实工作)。 -
真实实例(
dsh-100002-ef8d1d12.scope):重启前 count = 1 ⇒restart dshs-worker⇒ +5 s count = 0 ⇒ 🔴 E1 ❌。 -
🔑 根因(本次最重要的发现):
journalctl -u dshs-worker同一秒内Stopping … → Stopped …+ 新进程打印[rehydrate] 无既有实例 scope ⇒ 不动作⇒ scope 在新进程扫描之前就没了。真凶 =src/supervisor/orchestrator.ts:604teardown():async teardown(): Promise<void> { if (this.reapTimer !== undefined) clearInterval(this.reapTimer) for (const userId of [...this.mains.keys(), ...this.watchdogs.keys()]) await this.stop(userId) }由
src/worker/agent.ts:569(SIGTERM 关闭链)调用 ⇒ 退出路径主动停掉所有在册实例。「原有实例」不在mains⇒teardown碰不到它 ⇒ 它能活;真实实例在mains⇒ 被杀。 -
🔴 结论:本序只改"启动时清空"不足 —— 杀实例的实际生效路径是退出时的
teardown()(同机LocalSpawner.teardown+ 跨机RemoteSpawner.teardown,后者正是序⑲ 观测到"restart dshs会收掉dsh-*.scope"的原因)。 -
⛔ 未动
teardown()—— 它需要改src/supervisor/remote-spawner.ts/leased-spawner.ts(不在 §3.1 在册文件集)⇒ 按 §8 回头条件 3(R7)停下报告,见文末待拍板项。
③ 先红后绿(原文级)
-
单测面(
test/orchestrator-rehydrate.test.mjs,刻意不进npm test以保基线可比):批量破坏三处 ⇒ 破坏后 3 红(正是预期的 R7 uid 交叉校验 / R13 同 uid 多 scope / R16 构造函数接线):not ok 5 - R7 ⛔ uid 交叉校验不符 ⇒ 拒 not ok 11 - R13 ⛔ 同 uid 多 scope ⇒ 全部停 not ok 14 - R16 接线:构造函数必须调 rehydrateAdoptedScopes # pass 15 / # fail 3还原后
# pass 18 / # fail 0,且orchestrator.tsmd5 与破坏前逐字一致(86f1f05f878d706cdc656e3d769800e5)⇒ 断言链有效且可自证。 -
探针面(
OBS-18三态夹具,离线零副作用):旧行为原文(无[rehydrate]行)⇒ FAIL;合法 summary ⇒ PASS;adopted+stopped ≠ scanned⇒ FAIL 并点名。
④ 零回归三件套(均带 --table)
| 件 | 实测 | 基线 | 判定 |
|---|---|---|---|
npm test(Node 22) |
176 / 175 pass / 0 fail / 1 skip + verify-inject rc=0 |
176/175/0/1 | ✅ 逐字一致 |
--scene all --table |
12 PASS / 0 SKIP / 0 FAIL | 12P/0S/0F | ✅ 一致 |
overlay-probe --table |
17 PASS / 0 FAIL / 1 SKIP(18 项 = 基线 17 + 本序新增 OBS-18;OBS-09 SKIP = "无实例在册"的已知环境态,⛔ 非回归) |
17P/0F/0S | ✅ 等价(OBS-18 由"不存在"→ PASS) |
⚠️ 中途一次 overlay-probe 出现 OBS-08 FAIL(端点表 2 条 / 离线 1 条)⇒ 重跑即回绿(1 条 / 离线 0 条)⇒ 判为 --scene all 演练后的暂态,⛔ 非本序引入(其数据源是 relay /status,与 orchestrator 无耦合)。✅ 如实留档。
⑤ 边界自证
⛔ 未改任何生产值(DSHS_REHYDRATE_* 只读进程环境取代码缺省,47/106 的 dshs.env / dshs-worker.env 一字未写)· ⛔ 未改 bwrap 参数 · ⛔ 未删 cleanStaleScopes(uid) · ⛔ 未动 RELAY_FAILOVER_* / HB_SEC / burst · 🔴 COOLDOWN_MS=0 计数 = 0 · ⛔ 未新增公网口 / 未改 nft·nginx · ⛔ 未 commit / 未 push · ⛔ 认领路径零凭据落盘(单测 R19 钉住)· ⛔ 未改 04-调整方案/**。
部署:lib/supervisor/orchestrator.js scp 到 47 /opt/dshs/lib + 106 /opt/dshs-cluster/lib,两处 md5 全同 = 5bfb15e00b592c293548ae17b639399b;回滚点 = 两机 /opt/dsh/backups/seq25-20260917-220556/lib/supervisor/orchestrator.js(旧值 fad7926494578258f86a91eac613a204);已 restart 47 dshs + 106 dshs-worker(门户 200)。
⑥ 指纹
- 本单 §7 之前前缀指纹 =
d9d48121e68faa00e1da17ad8fea36ad—— ✅ 开工时校验通过(依据未被改动)。⚠️ 回填 §11 之后仍为同值(回填全在 §7 之后,⛔ 不触碰 §7 之前正文)。 - 参数表指纹 =
54c854a5c09b561ea4920c44d8545cfe→93e38d1a57fbcd3126d2122150a2e838(新增 2 键 + §6OBS-18;口径 =sed '/^## §10 指纹/,$d' 参数表_覆盖网络_20260917.md | md5sum)。 src/supervisor/orchestrator.tsmd5 =86f1f05f878d706cdc656e3d769800e5(先红后绿还原校验后值)。- 本单全文件 md5(回填 §11 后) =
b73f2ec18fcf52055c4a24c3fcbdeb5b(242 行)。 - 代码仓 HEAD =
04776af(⛔ 未 commit / 未 push)。 - 本序新增/改动文件:
src/supervisor/orchestrator.ts(✏️ 在册)|scripts/overlay-probe.cjs(✏️ 在册)|参数表_覆盖网络_20260917.md(✏️ 在册)|test/orchestrator-rehydrate.test.mjs(➕ 在册可新增)|⚠️ 无越界文件。
8.2 本序沉淀的四条可复用事实
- 🔴 "杀实例"有两条独立路径,只改一条等于没改 —— 启动路径
cleanAllStaleScopes()与退出路径teardown()。判据:用一个"不在mains里"的夹具 scope 做对照 —— 它活着就证明 systemd 无辜、真凶在应用层。(本次一步定位,省下大量猜测。) - 🔴
--scope的端口不在/proc,在Description——systemctl show -p Description携带systemd-run记录的完整 argv。⛔ 别再按"读/proc/<MainPID>/cmdline"设计。 - 🔴 "认领"不能把实例写回
mains—— 那会让enter的复用分支去等一个永不会出现的launchToken(right dsh web只在启动时打印一次、不落盘、运行期取不出)⇒ 用户被 503 挡住 = R11 净变差。⇒ 正确的认领语义 = 只登记 + 探活,实例由用户访问时的cleanStaleScopes(uid)自然替换。⛔ 也不许把 token 落盘换"无感接管"(安全维度净变差,R11)。 - ⚠️ 探针里
ssh()的形参作用域是个坑 ——sshPort定义在外层块,OBS-18在另一个块里取不到 ⇒sshPort is not defined被catch吞成text=''⇒ 表现成"读不到日志"。⇒ 失败判别器必须能区分"读不到"与"确无该行"(本序已落:0 行时一并打出journalctl原文字节数)。
8.3 待拍板(唯一一项):E1 的缺口要不要打通
问题:teardown()(退出时主动停掉所有在册实例)是"重启后实例不被清掉"的实际阻碍,而它跨了三个文件 —— 其中 两个不在本单 §3.1 在册文件集(src/supervisor/remote-spawner.ts、src/supervisor/leased-spawner.ts)。按 §8 回头条件 3(R7)我已停下、未动手。
候选 A —— 只改 LocalSpawner.teardown()(同机那一处)
· 优点:文件集最小,1 个在册文件即可;同机形态(纯本机部署场景)能达成 E1。
· 缺点:Manager 重启仍会跨机收掉 Worker 实例(Manager 侧 LeasedSpawner → RemoteSpawner.teardown() 会向 Worker 下发停止)⇒ 你的诉求「Manager 重启后逐步拉起」依然不成立 ⇒ 基本等于白改。
候选 B —— 三处一起改:LocalSpawner.teardown() + RemoteSpawner.teardown() + LeasedSpawner.teardown()
· 优点:唯一能真正达成用户诉求的路径;语义自洽 —— 退出进程把存活实例留给下一个进程,回收责任完全交给「启动认领 + TCP 探活判孤儿」这一套(本序已实测工作)。
· 缺点:超出本单 §3.1 文件集,需要你授权扩范围;影响面 = 所有实例的生命周期语义(实例不再随进程退出而回收)⇒ 必须接受"进程长期不重启时,实例由 idle-reap / 用户访问替换来收"这个前提。
候选 C —— 用 env 开关把"退出不停实例"做成可灰度
· 优点:能先在一台机打开、观察后再推开。
· 缺点:跨机那一处照样要改(开关落地点就在 teardown 里)⇒ 文件集不比 B 小;而"默认关"等于不生效、"默认开"等于 B ⇒ 没有增量价值,只多一个开关,且多一个"忘开/忘关"的失效面。
我的倾向 = B(A 不达成目标,C 无增量价值)。⏸ 在你拍板前,本序不登记续棒,也不动那三个文件。