# 2026-10-13 指定账号清理与同步来源保护 ## 生产处理结果 用户原名单去重为 27 个账号(ID 51 重复),随后明确追加两个阅读账号(1852、1859),共 29 个。按对象 ID、旧数字编号、来源、更新时间共同校验后软删除并停用,注销 77 个登录会话。保留删除凭据和 `SyncExcluded` 标记,避免后续账号补录恢复。操作前快照及处理回执位于本机私有目录 `~/.local/state/xiaoshu/account-cleanup/2026-10-13/`,不提交个人资料和凭据。 14 个被删除账号来自 `reading-unified:user:*`。旧阅读合并脚本按 MAX(legacyUserId)+1 给未匹配的阅读账号分配编号,误占了旧系统编号空间。其中 13 个编号与旧 WXAPP 返回的真实小树账号碰撞:1846、1847、1848、1850、1851、1852、1853、1854、1855、1856、1857、1858、1859。删除指定阅读账号时撤销其伪编号,在删除凭据中保留原编号;按实时旧接口核实身份后恢复 13 个独立小树账号。1849 没有当前旧源账号,未猜测或补造身份。 恢复时保留 1858 在新系统已核验过的余额(30 分钟课时 40、陪练时长 20),记录与旧源差异,不以旧源 39 / 19.5 覆盖。其余恢复账号以已核实的旧源初始资料建立;未修改其他已有账户余额。 管理后台实际列表接口复查:会员列表 628 条、陪练列表 167 条,29 个删除对象均不再出现。恢复的 8 个会员、5 个陪练显示正确旧源身份,其中 2 个陪练沿用旧源停用状态。数字编号被真实小树用户继续使用,不代表被删的阅读账号复活。 ## 数据完整性 清理前后按完整行内容计算校验和,以下关联数据数量和校验和一致: | 数据 | 条数 | |---|---:| | 课程绑定 | 29 | | 每日学习 | 94 | | 练习 | 2179 | | 抗遗忘 | 951 | | 预约 | 304 | | 上课 | 80 | | 复习计薪凭据 | 420 | | 四类课时流水合计 | 77 | 29 个删除对象仍有删除标记,可审计恢复;其可用会话为 0。没有级联删除课程、工资或历史流水,也没有重算工资。历史计薪凭据中的姓名快照未重写,本次验证的是管理列表身份和业务记录完整性。 ## 防再次导入 - 夜间补账号和手工 backfill 均跳过已删除、`SyncExcluded` 账号,禁止 `--refresh-existing` 恢复这些账号。 - `reading-*` 编号不能作为老系统身份匹配证据;发现冲突记录专门原因,夜间执行器停止后续业务投影,避免继续串号。 - 阅读账号只有 `ReadingSourceVerified=xiaoshu` 且匹配明确时才能合并。未知来源和乍兔账号不自动合并;未匹配、歧义账号不再晋升为小树会员,也不生成旧系统数字编号。 - 删除对象的原编号只是审计字段;真正的 `legacyUserId` 与 `legacyUserData.UserID` 同时撤销,防止旧查询的回退逻辑再次匹配。 线上直接同步网关已发布 `2026-10-13-account-protection-v2` 并逐字回读。使用实际部署的筛选函数只读核验旧源全部 737 个账号:735 匹配、2 个明确删除的旧账号跳过、0 缺失、0 编号冲突。此次未伪造运行检查点,也未重新执行整轮业务同步。 Mac 的已安装执行器已更新且与测试源码一致;LaunchAgent 仍为北京时间 23:00,上一退出码 0,`--check` 返回 ready。该检查不是下一次定时执行的成功回执。 ## 验证 `node --test scripts/tests/direct-nightly-sync.test.mjs scripts/tests/reading-account-policy.test.mjs scripts/tests/retire-imported-accounts.test.mjs`:20 项通过。覆盖删除保护、跨来源禁止合并、伪编号冲突、冲突时业务同步阻断、幂等清理和身份版本变化时事务回滚。 真实云函数 SDK 的 Company 指针创建与本地模拟存在差异;身份恢复通过 Parse REST 完成并回读。夜间新增账号路径改为读取真实 Company 对象,生产只读序列化验证确认其指针有效,避免使用不兼容的双参数 createWithoutData。