# 邮箱登录与邀请绑定恢复

2026-10-04。本轮修改短剧库的 platform 服务，继续使用 Better Auth 官方邮箱 OTP。App、播放器和原生安装包没有修改。

首次注册的业务回调失败时，框架仍能完成邮箱登录。旧实现的最终登录回调只恢复用户档案，没有携带邀请码，因此可能出现“已登录，但邀请绑定为空”。现在在框架确认已验证会话后执行邀请绑定；首次注册回调只负责档案。临时绑定失败后，用户在有效窗口内携带原邀请码再次验证，或在邀请页手动绑定，可以恢复。

## 修改范围

| 文件 | 修改 |
| --- | --- |
| `services/platform/src/auth.mjs` | 最终登录回调携带原请求邀请码；身份仍取框架新会话，设备与 IP 仍取私有 Go 桥接 |
| `services/platform/src/runtime.mjs` | 绑定移到已验证登录回调，在档案注册成功后执行 |
| `services/platform/tests/referral-registration-recovery.test.mjs` | 真实 Better Auth HTTP 与隔离 RAM PostgreSQL 的故障恢复回归 |

没有增加依赖、数据库迁移或后台任务。没有重置注册时间、替换既有邀请关系、扩大绑定窗口或提前发奖。Web 每日 10 集、App 每日 50 集的本站播放口径保持不变。PRD、实施路线与任务卡同步了已确认范围：普通渠道无需来源侧签发，外部复制播放不纳入次数限制；邀请发奖仍另验可信观看证明。

## 验证

| 检查 | 结果与范围 |
| --- | --- |
| 修复前受控复现 | 一次注册时钟读取被拒绝后，2/2 登录成功、档案存在、邀请为空 |
| 修复后同场景 | 2/2 在最终已验证登录回调恢复绑定，原回调失败事件仍保留 |
| 新回归文件 | 原实现 3/6 通过；修改后 6/6 通过。包含 5 个子场景及父项 |
| 新回归与已有登录/邀请 HTTP | 16/16 通过 |
| platform 全量 | 263/263 通过，包含已有回归；不能把各项计数相加 |
| 公开 Go → Better Auth → PostgreSQL | 两份独立Cookie会话完成官方邮箱注册/Go会话桥接；首次绑定一次，再次 OTP 保留原关系；同 IP 标记进入审核，奖励行仍为 0；Go 子进程退出 0，上游请求禁止 |

新回归覆盖：正常注册和重复验证、首次注册回调失败、绑定前临时数据库失败、绑定提交成功但响应丢失、重复归属/同设备/超过 24 小时/已有达标记录的拒绝。测试只在借用的隔离连接中注入故障，没有修改主机时钟或既有数据库。

公开 Go 联调首次遗漏 Web 的浏览器身份初始化，得到两个注册设备为空、邀请未绑定的结果。补齐实际页面已有的 `/api/ui/viewer` 和必要的 `confirm=1` 后通过。这是测试设置修正，没有改变 Go 的设备信任规则。直接调用 OTP 而未建立可信设备的请求，不会自动绑定邀请码。

## 本轮复核与边界

本轮由主 Agent 复核和执行测试，没有新增独立子 Agent 复审。回调只接收框架确认的已验证用户；失败保持登录可用，日志仅使用固定失败代码；邀请事务继续使用原唯一关系与资格检查。再次验证不会续期注册时间、创建第二条关系或产生奖励。

早期 210/212 全量日志只保留“新用户邀请为空”，没有原始原因代码。本轮已复现并修复一个会产生相同症状的恢复缺口，不能倒推为当时的唯一原因。该日志继续作为历史观察保留；当前受控恢复和真实公开接口流程均已验证。

公开接口使用Cookie jar驱动真实HTTP；本轮没有新增Chrome或App界面实测。OTP由本地测试适配器接收，未发真实邮件。同 IP 的资格达标、可信视频观看证明、安装资格、真实 SMTP 和正式部署仍需验收。客户端位置和离线播放记录不能作为邀请发奖证明。

本轮仅发布文档；没有部署业务服务、迁移生产数据库、发布 App 或 OTA。最新 App 回归沿用 356/356，Android 加密缓存验收见[Android 记录](dramivio-cenc-android-development.md)。

[本轮验证数据](dramivio-registration-recovery-validation.json) · [当前开发状态](dramivio-development-status.md) · [功能与剩余范围](dramivio-consumer-implementation.md)
