进展(07-28-all-account-login-readiness): 恢复生产邮件找回能力

这个提交包含在:
2026-08-23 18:30:30 +08:00
父节点 73ebb4cba5
当前提交 e7b516ed3e
共修改 4 个文件,包含 43 行新增和 4 行删除
+25 -1
查看文件
@@ -2085,7 +2085,7 @@ node scripts/qa/payment-batch-flag-snapshot.mjs restore --snapshot-file "$REPORT
### 1. Scope / Trigger ### 1. Scope / Trigger
- Trigger: an approved production runner changes feature flags or another versioned configuration through an API, then uses browser/network observation, response parsing or local counters to decide whether the write landed or whether rollback is needed. - Trigger: an approved production runner changes feature flags or another versioned/runtime configuration through an API or environment file, then uses browser/network observation, response parsing, container health metadata or local counters to decide whether the write landed or whether rollback is needed.
- Purpose: prevent an observer defect from turning a known successful write into an unnecessary compensating write, while preserving zero retry and exact audit evidence. - Purpose: prevent an observer defect from turning a known successful write into an unnecessary compensating write, while preserving zero retry and exact audit evidence.
### 2. Signatures ### 2. Signatures
@@ -2094,6 +2094,7 @@ node scripts/qa/payment-batch-flag-snapshot.mjs restore --snapshot-file "$REPORT
- Authoritative readback: GET the changed resources and compare `version`, configured value and effective state with both the PRE snapshot and the expected POST snapshot. - Authoritative readback: GET the changed resources and compare `version`, configured value and effective state with both the PRE snapshot and the expected POST snapshot.
- Independent evidence: action-specific audit total and per-target changelog deltas. - Independent evidence: action-specific audit total and per-target changelog deltas.
- Observer sources must be named separately: page requests, browser-context requests, `APIRequestContext`, server response and audit/readback. - Observer sources must be named separately: page requests, browser-context requests, `APIRequestContext`, server response and audit/readback.
- Container restart witness: inspect whether the service actually defines a Docker healthcheck, then combine running state, restart count, public `/ready` and the changed capability's read-only response. An absent healthcheck is `health=none`, not an unhealthy result.
### 3. Contracts ### 3. Contracts
@@ -2107,6 +2108,7 @@ node scripts/qa/payment-batch-flag-snapshot.mjs restore --snapshot-file "$REPORT
- Make POST validation proportional to the changed dependency graph and failure cost. A reversible, zero-use feature flag with statically verified soft consumers does not justify probing unrelated domains merely because they are available. - Make POST validation proportional to the changed dependency graph and failure cost. A reversible, zero-use feature flag with statically verified soft consumers does not justify probing unrelated domains merely because they are available.
- For a confirmed low-risk, reversible configuration state, a POST witness failure defaults to preserving the applied state for read-only diagnosis. Rollback requires evidence of a real product regression, not merely a runner assertion, stale error accumulator or probe-contract mismatch. - For a confirmed low-risk, reversible configuration state, a POST witness failure defaults to preserving the applied state for read-only diagnosis. Rollback requires evidence of a real product regression, not merely a runner assertion, stale error accumulator or probe-contract mismatch.
- Every HTTP probe must use the real route query contract. Page-error evidence must be sliced from immediately before one navigation until that navigation completes; errors accumulated during login, PRE or earlier pages cannot fail a later page witness. - Every HTTP probe must use the real route query contract. Page-error evidence must be sliced from immediately before one navigation until that navigation completes; errors accumulated during login, PRE or earlier pages cannot fail a later page witness.
- A restart waiter may require Docker healthy only when the resolved running service defines a healthcheck. If none is defined, it must not manufacture a failure; use running state, stable restart count, public `/ready` and dependency-specific read-only capability checks before deciding whether rollback is warranted.
### 4. Validation & Error Matrix ### 4. Validation & Error Matrix
@@ -2117,14 +2119,18 @@ node scripts/qa/payment-batch-flag-snapshot.mjs restore --snapshot-file "$REPORT
- Some targets changed -> `STOP_FOR_MANUAL_DECISION`; stop, preserve evidence and request an explicit human decision. Neither continue nor rollback is automatically correct. - Some targets changed -> `STOP_FOR_MANUAL_DECISION`; stop, preserve evidence and request an explicit human decision. Neither continue nor rollback is automatically correct.
- Readback itself fails -> state remains unknown; stop with zero further mutation. - Readback itself fails -> state remains unknown; stop with zero further mutation.
- Expected POST is confirmed and the only witness fails -> preserve the applied state, open a fresh read-only context and classify the witness. Confirmed product regression may enter an explicitly approved rollback; runner/probe defect must not. - Expected POST is confirmed and the only witness fails -> preserve the applied state, open a fresh read-only context and classify the witness. Confirmed product regression may enter an explicitly approved rollback; runner/probe defect must not.
- Container is running with `health=none`, restart count is stable, public `/ready` passes and the changed capability is ready -> classify the health waiter as incompatible with the runtime contract; preserve the applied state and fix the observer separately.
- Container exits, restart count increases, public `/ready` fails or the changed capability remains unavailable -> treat as a real POST acceptance failure and follow the approved rollback/stop contract.
### 5. Good/Base/Bad Cases ### 5. Good/Base/Bad Cases
- Good: `APIRequestContext` writes a three-item batch while a page listener reports zero; GET shows all three at `PRE+1`, audit is `+3`, so the runner continues POST verification without rollback. - Good: `APIRequestContext` writes a three-item batch while a page listener reports zero; GET shows all three at `PRE+1`, audit is `+3`, so the runner continues POST verification without rollback.
- Base: the batch response and observer agree, readback confirms the expected state, and the audit delta matches target count. - Base: the batch response and observer agree, readback confirms the expected state, and the audit delta matches target count.
- Base: a configuration-only restart targets a service without Docker healthcheck; running state, restart count, public readiness and the changed capability all pass, so the applied state is retained.
- Bad: treat a page-only counter as authoritative for a context request and immediately restore the PRE values before checking current versions. - Bad: treat a page-only counter as authoritative for a context request and immediately restore the PRE values before checking current versions.
- Bad: call rollback because it is pre-authorized while the forward result is still unknown. - Bad: call rollback because it is pre-authorized while the forward result is still unknown.
- Bad: add unrelated domain probes to a three-flag soft-disable, send unsupported query parameters, or let errors from an earlier page fail the current Portal witness. - Bad: add unrelated domain probes to a three-flag soft-disable, send unsupported query parameters, or let errors from an earlier page fail the current Portal witness.
- Bad: require `.State.Health.Status=healthy` without first proving a healthcheck exists, then rollback a healthy service because Docker reports no health object.
### 6. Tests Required ### 6. Tests Required
@@ -2134,6 +2140,7 @@ node scripts/qa/payment-batch-flag-snapshot.mjs restore --snapshot-file "$REPORT
- Assert final evidence reconciles response count, readback versions, global audit delta and per-target changelog delta; no one source may silently override contradictions. - Assert final evidence reconciles response count, readback versions, global audit delta and per-target changelog delta; no one source may silently override contradictions.
- Derive every probe path/query from the actual route/client contract. For browser witnesses, reset or snapshot the page-error index before each navigation and assert only the new slice. - Derive every probe path/query from the actual route/client contract. For browser witnesses, reset or snapshot the page-error index before each navigation and assert only the new slice.
- For low-risk reversible changes, test that a witness failure preserves POST state for diagnosis and that rollback is unreachable until a fresh read-only check confirms product regression. - For low-risk reversible changes, test that a witness failure preserves POST state for diagnosis and that rollback is unreachable until a fresh read-only check confirms product regression.
- Cover both resolved service shapes: healthcheck present must reach healthy; healthcheck absent must require running state, unchanged restart count, public `/ready` and the dependency-specific capability. Assert `health=none` alone cannot trigger rollback.
### 7. Wrong vs Correct ### 7. Wrong vs Correct
@@ -2156,6 +2163,23 @@ forward response/counter disagree -> APPLY_UNCERTAIN
-> rollback only after confirmed product regression and explicit approval -> rollback only after confirmed product regression and explicit approval
``` ```
#### Wrong
```text
restart service -> always wait for Docker healthy
-> no healthcheck object -> declare failure -> rollback
```
#### Correct
```text
resolve service healthcheck contract
-> healthcheck present: require healthy
-> healthcheck absent: running + stable restart count + public /ready
+ dependency-specific capability
-> rollback only for a confirmed POST acceptance failure
```
## Scenario: Unique Production Administrator Break-Glass Reset ## Scenario: Unique Production Administrator Break-Glass Reset
### 1. Scope / Trigger ### 1. Scope / Trigger
@@ -58,6 +58,18 @@
- [x] 最终生产计数:active 6、must-change 3、binding 6、binding 缺失/冲突/inactive 漂移 0、密码历史成功覆盖 3、企微历史成功审计覆盖 4、近 7 天企微成功审计覆盖 1、三类认证锁定 0、异常活跃会话 0。 - [x] 最终生产计数:active 6、must-change 3、binding 6、binding 缺失/冲突/inactive 漂移 0、密码历史成功覆盖 3、企微历史成功审计覆盖 4、近 7 天企微成功审计覆盖 1、三类认证锁定 0、异常活跃会话 0。
- [x] 近 7 天角色变更、企微通讯录写入和 binding 写入审计均为 0;本轮未登录真人账号,未修改数据库、角色、binding、会话或企微通讯录。 - [x] 近 7 天角色变更、企微通讯录写入和 binding 写入审计均为 0;本轮未登录真人账号,未修改数据库、角色、binding、会话或企微通讯录。
## 4C. 2026-08-23 Production Email Recovery Enablement
- [x] 先以公网只读方式确认 OA `/ready` 正常、认证 capabilities 返回 `email_disabled`;页面在 POST 前拦截,未签发验证码、未增加请求次数,排除员工邮箱或账号本身导致的现场失败。
- [x] 无发送邮件的 SMTP 连接与认证探测通过;历史生产证据显示邮件开关此前持续关闭,任务中“忘记密码已启用”的旧记录不再作为当前事实。
- [x] 用户明确批准启用腾讯云真实生产邮件能力;生产 `.env` 已原子改为 `EMAIL_ENABLED=true`,未改其他配置、数据库、账号、角色、binding、审批规则或企业微信通讯录。
- [x] 第一次尝试因 Compose 缺少显式构建标识在写入前停止,第二次尝试因发现生产没有 worker 在写入前停止;两次均为零写入、未自动重试。
- [x] 只重建当前唯一 server,保持生产 `server=1、worker=0、JOBS_ENABLED=true` 的既有拓扑,未擅自启动 worker,避免形成重复作业调度;运行构建未改变。
- [x] 初始健康等待器错误要求 Docker healthcheck,但当前 server 容器没有 healthcheck;随后以容器 running、restartCount=0、公网 `/ready=200` 以及 capabilities 通过确认服务正常,因此未回滚。
- [x] 最终 capabilities 为 `emailDeliveryReady=true`、`passwordResetEnabled=true` 且 `statusReasons=[]`;SMTP 连接与认证通过,未发送测试邮件,migration pending=none,rollback window 为 `OPEN/0`。
- [x] 临时敏感回滚副本已删除,生产 `.env` 保留已批准的启用状态;本轮未部署代码、未运行 migration、未登录员工账号、未发送邮件。
- [ ] 由剩余 3 名首次改密人员本人逐一完成验证码重置和密码登录;管理员不接触其密码或验证码,任何额外重置、解锁或配置写入仍需另行批准。
## 5. Stop Conditions ## 5. Stop Conditions
- 企业邮箱不可达、验证码投递异常或账户锁定:停止该员工操作,不连续请求。 - 企业邮箱不可达、验证码投递异常或账户锁定:停止该员工操作,不连续请求。
@@ -25,7 +25,8 @@
- 3 个 active 账户已有企微 binding;唯一 active 管理员正是缺 binding 的账户,且该管理员已有历史密码成功登录、当前无需强制改密但本人已不知道密码。 - 3 个 active 账户已有企微 binding;唯一 active 管理员正是缺 binding 的账户,且该管理员已有历史密码成功登录、当前无需强制改密但本人已不知道密码。
- 企业微信通讯录本身拥有管理员 userid,但生产只读核对显示 OA 侧没有该管理员的目录同步 userid、成功 HR 主数据映射或 identity binding;当前 OAuth 回调只能拿到 userid,无法仅凭它反推出唯一 OA 账户。 - 企业微信通讯录本身拥有管理员 userid,但生产只读核对显示 OA 侧没有该管理员的目录同步 userid、成功 HR 主数据映射或 identity binding;当前 OAuth 回调只能拿到 userid,无法仅凭它反推出唯一 OA 账户。
- 现有企微 HR 主数据同步已经能够按 binding、userid/员工编号、手机号或企业邮箱解析员工并在 apply 时写入 binding,但当前普通 `mode=login` 回调只查 binding;binding 缺失时不会利用企微通讯录企业邮箱匹配既有 OA 账户。 - 现有企微 HR 主数据同步已经能够按 binding、userid/员工编号、手机号或企业邮箱解析员工并在 apply 时写入 binding,但当前普通 `mode=login` 回调只查 binding;binding 缺失时不会利用企微通讯录企业邮箱匹配既有 OA 账户。
- 公网认证能力当前报告企业邮箱验证码登录和忘记密码均已启用。 - 生产认证能力在 2026-08-23 现场复核时曾报告 `email_disabled`:OA `/ready` 正常,但生产邮件开关关闭,找回密码页面在请求签发前即失败;该次操作未签发验证码、未增加请求次数,也不是员工邮箱或账号问题。
- 用户于 2026-08-23 明确批准启用腾讯云真实生产邮件能力;启用后公网 capabilities 已恢复 `emailDeliveryReady=true`、`passwordResetEnabled=true` 且无状态原因,SMTP 连接与认证通过,未发送测试邮件。
- `/forgot-password -> /reset-password` 是完整的验证码重设密码路径;正确验证码会设置新密码并清除 `must_change_password`,不是死路。 - `/forgot-password -> /reset-password` 是完整的验证码重设密码路径;正确验证码会设置新密码并清除 `must_change_password`,不是死路。
- 本次实际失败发生在 `/reset-password` 成功之前:第二次签发使第一次验证码失效,用户收到并使用旧验证码后被后端拒绝;未发现“重置成功后被前端缓存再次送回改密页”的现场证据。 - 本次实际失败发生在 `/reset-password` 成功之前:第二次签发使第一次验证码失效,用户收到并使用旧验证码后被后端拒绝;未发现“重置成功后被前端缓存再次送回改密页”的现场证据。
- `/email-login/code -> /email-login/verify` 是另一条邮箱验证码登录路径;现有前端在验证成功后仍遵守 `must_change_password` 跳转,而修改密码页强制要求旧密码,因此它对未知随机初始密码账户可能形成死路,但与本次失败无关。 - `/email-login/code -> /email-login/verify` 是另一条邮箱验证码登录路径;现有前端在验证成功后仍遵守 `must_change_password` 跳转,而修改密码页强制要求旧密码,因此它对未知随机初始密码账户可能形成死路,但与本次失败无关。
@@ -79,7 +80,9 @@
- [ ] 所有重置、绑定、解锁、会话撤销均有 OA 审计且未扩角色、未修改企业微信通讯录。 - [ ] 所有重置、绑定、解锁、会话撤销均有 OA 审计且未扩角色、未修改企业微信通讯录。
- [x] 当前唯一 active `manager` 的直属主管唯一解析为 active `majianyong`,不存在自环或主管链环路,并有脱敏审计;`wangshunli` 不进入业务审批链。 - [x] 当前唯一 active `manager` 的直属主管唯一解析为 active `majianyong`,不存在自环或主管链环路,并有脱敏审计;`wangshunli` 不进入业务审批链。
- [x] `majianyong` 保持 `ceo` 且无直属主管;其本人报销目标为 active 非本人 `finance`,当前 `finance_mgr` 配置缺口未修复前保持阻断且不发生自审。 - [x] `majianyong` 保持 `ceo` 且无直属主管;其本人报销目标为 active 非本人 `finance`,当前 `finance_mgr` 配置缺口未修复前保持阻断且不发生自审。
- [ ] 自动化、Trellis、敏感扫描、生产 `/ready` 和关联认证 smoke 通过后才通知员工测试。 - [x] 自动化、Trellis、敏感扫描、生产 `/ready` 和关联认证 smoke 已通过,可以通知剩余首次改密人员逐人测试。
当前员工可测试边界:生产邮件能力、公网 `/ready`、认证 capabilities 与 SMTP 连接/认证已通过;剩余 3 名首次改密人员可由本人逐一进入忘记密码流程。不得在聊天中传递密码或验证码,不连续点击重发;新签发验证码会使旧验证码失效。
## Definition of Done ## Definition of Done
@@ -21,6 +21,6 @@
"children": [], "children": [],
"parent": null, "parent": null,
"relatedFiles": [], "relatedFiles": [],
"notes": "首次企微免密登录代码已随最新主干版本进入生产;live 只读审计确认 OA active 6、企微 active 6、binding 6、唯一匹配 6,缺失、冲突、状态漂移和待回填均为 0,因此无需 apply。首批人员和其余 3 人的企微工作台真实登录均由用户确认成功,当前 6/6 真人企微主入口完成。2026-08-23 腾讯云生产补充只读聚合再次确认 active/binding 6/6、锁定/冲突/异常活跃会话为 0、近 7 天角色/通讯录/binding 写入为 0;仍有 3 个 must-change,密码历史成功覆盖仅 3/6。任务继续保留,用于逐人密码回退和首次改密收口;不把企微免登成功扩大为全部账户双路径验收完成。", "notes": "首次企微免密登录代码已进入生产,当前 6/6 真人企微主入口通过;生产只读聚合确认 active/binding 6/6,锁定、冲突和异常活跃会话均为 0。2026-08-23 现场发现邮件找回因生产开关关闭而不可用,用户批准后已在腾讯云真实生产启用;公网 /ready、认证 capabilities、SMTP 连接与认证均通过,未发送测试邮件,未部署代码或运行 migration,且保持 server=1、worker=0 的既有拓扑。当前仍有 3 人处于首次改密状态,任务继续保留,等待其本人逐一完成验证码重置和密码登录;管理员不接触密码或验证码。",
"meta": {} "meta": {}
} }