Skip to content

2c: 重订阅恢复(显式降水位:自报只允许向下修正) - #52

Merged
Windsander merged 5 commits into
mainfrom
feat/namespace-rejoin
Sep 18, 2026
Merged

Windsander merged 5 commits into
mainfrom
feat/namespace-rejoin

Conversation

@Windsander

Copy link
Copy Markdown
Owner

2c · 重订阅恢复(显式降水位)

退订清理后重新加入:显式声明本机该分区已重置,使对端从 0 重新 offer、历史完整拉回。复用既有语义「对端自报水位只允许向下修正」,未新增同步协议、未放宽快照门禁(“自报分区水位为空”的判据改为“每个分区时钟均无作者计数”)、无 tombstone

实现(R1–R5)

  • R1 准入rejoinNamespace 要求 ①getEffectiveNamespaces(self) 含该分区(存在签发给本机的 grant;默认拒绝不变)②成员在册;任一不满足 → 显式失败 not-authorized / membership-not-active
  • R2 显式降水位SyncManager.recordPeerHello 起「自报只允许向下修正」——对端自报某作者计数低于本机记录时下调到自报值(作者缺失=0),绝不向上;下一次 missingEventsForPeer 即从修正后的水位重发(或按既有“空水位”门禁发初始快照,门禁不放宽)。重入方通过图外 reset 标记驱动 buildLocalNamespaceClocks 把该分区以空时钟随 hello 上报(普通空订阅不上报,避免无谓修正)。
  • R3 本地标记<storagePath>.rejoin.<ns>.json(图外、不同步、无 tombstone);Mebular.initialize 依据它恢复 reset 声明。
  • R4 幂等/oracle-free:重复 rejoin 安全;rejoin/reset 不改变 __policy__ 派生(签发者不变)。
  • R5 CLI/可观测fleet rejoin --namespace <ns>doctor重入状态(reset=true/false)。

主张 → 证据

# 主张 证据
1 leave→rejoin→历史完整拉回(与对端一致) 2c ①(jest)+ verify:fleet:rejoin(12→12)
2 未授权重入→拉不到(显式失败) 2c ② + CLI not-authorized
3 未 reset→对端不重发;reset 后才重发 2c ③(B 署名事件,水位>0)
4 重复 rejoin 幂等 2c ④
5 无 tombstone + __policy__ 保留 2c ④/⑤ + CLI
6 oracle-free:签发者不变 2c ⑥
7 B 不受影响 CLI ① B 数据不受影响 {"execB":3,"bTasks":12}
8 不回归 722 ×6、覆盖阈值全过、lint/build 无输出、CI 三 job 绿、verify:fleet:all 114/114

红 → 绿(断言原文)

不变量 破坏点 锚点 结果
reset 信号 buildLocalNamespaceClocks 不含 resetNamespaces 2c ③ 未 reset…reset 后才重发 (reset 后未重发)→ 还原 ✓
向下修正(脚本类,S9 先 npm run build if (reported < known)if (false) verify:fleet:rejoin FAIL A 历史完整拉回 {"before":12,"after":0}(10→9)→ 还原 10/10

跨端 E2E 原始输出(verify:fleet:rejoin,10/10)

PASS  前置:B 已接收并执行 N 次
PASS  ① A leave 成功且本分区清空(__policy__ 保留)  {"deleted":{"events":12,...}}
PASS  ① A rejoin 成功(reset,准入通过)  {"ok":true,"reset":true,"authorized":true}
PASS  ① A 历史完整拉回(与清理前一致)  {"before":12,"after":12}
PASS  ① B 数据不受影响  {"execB":3,"bTasks":12}
PASS  ① 无 tombstone + __policy__ 保留
PASS  ② 未授权重入 → ok=false, reason=not-authorized(显式)
FLEET_SUMMARY {"total":10,"passed":10,"failed":[],"skipped":[]}

SEALING / 矩阵 diff 要点

  • POLICY-INVARIANTS.md:横切不变量 11(准入、显式降水位=自报只向下修正、图外 reset 标记、oracle-free/幂等、快照门禁不放宽)。
  • SEALING.md §2.3:在「自报不抬升」后补 2c 例外——自报只允许向下修正;§3 新增重订阅恢复语义与破坏性/前向差异(旧节点无“向下修正” → 重入只可能少收,安全方向)。
  • 同步更新一条既有断言:namespace-watermarks.test.ts 的 F3 由「自报低不下降」改为「向下修正自动解污染 + reset 仍可显式修复」(断言更新/收紧,不削弱)。

未做项 / 风险

  • 前向差异:旧节点不识别“向下修正” → 对旧端重入只可能少收(安全方向),需同版本互通。
  • 重入依赖对端仍在册且授权本机(例如对端 fleet grant --to <rejoin设备>);否则显式 not-authorized
  • 自报向下修正使 resetPeerWatermarks 部分冗余(hello 自报即可解污染);显式 reset 仍用于离线/尚未 hello 的场景。
  • 快照门禁未放宽;跨公网未验收。

测试方式

npm ci && npm run build
npm run test:coverage
npm run verify:fleet:all
node scripts/verify-fleet-rejoin.mjs

POLICY-INVARIANTS:横切不变量 11(准入、显式降水位向下修正、图外标记、oracle-free/幂等、快照门禁不放宽)。
SEALING §2.3 补“自报只允许向下修正”例外;§3 事件/语义与破坏性/前向差异。
SyncManager:resetNamespaces(显式重置分区随 hello 以空时钟上报);recordPeerHello 自报只允许向下修正(作者缺失=0,绝不向上);快照“空水位”判据改为“每分区时钟均无计数”。
Mebular:rejoinNamespace(准入=生效授权(self) ∧ 成员在册;写图外 rejoin 标记;清本地水位)+ hasRejoinReset;初始化按标记恢复 reset 声明。
BREAKING CHANGE: 对端 hello 自报低于本机记录时下调本地水位(再订阅恢复)。
rejoin.test:清理后重入历史完整拉回、未授权显式失败、未 reset 不重发、重复幂等、无 tombstone/policy 保留、oracle-free。
namespace-watermarks:F3 改为“向下修正自动解污染 + reset 仍可显式修复”(断言收紧/更新,不削弱)。
onboard:rejoinNamespace/rejoinReset。cli:rejoin 子命令。
doctor:重入状态(reset 标记,恒 PASS 供审计)。
CLI:A/B 派活→A leave→A rejoin(reset)→再同步→A 历史拉回;B 不受影响;无 tombstone/policy 保留;未授权重入显式失败。all 114/114。
@Windsander
Windsander merged commit f4943a2 into main Sep 18, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant