diff --git a/.trellis/spec/hyb-oa/deployment-trialrun.md b/.trellis/spec/hyb-oa/deployment-trialrun.md index 39bef31d..09354be6 100644 --- a/.trellis/spec/hyb-oa/deployment-trialrun.md +++ b/.trellis/spec/hyb-oa/deployment-trialrun.md @@ -2085,7 +2085,7 @@ node scripts/qa/payment-batch-flag-snapshot.mjs restore --snapshot-file "$REPORT ### 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. ### 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. - 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. +- 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 @@ -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. - 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. +- 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 @@ -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. - 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. +- 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 - 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: 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: 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: 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 @@ -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. - 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. +- 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 @@ -2156,6 +2163,23 @@ forward response/counter disagree -> APPLY_UNCERTAIN -> 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 ### 1. Scope / Trigger diff --git a/.trellis/tasks/07-28-all-account-login-readiness/implement.md b/.trellis/tasks/07-28-all-account-login-readiness/implement.md index f75907cb..ba2d839d 100644 --- a/.trellis/tasks/07-28-all-account-login-readiness/implement.md +++ b/.trellis/tasks/07-28-all-account-login-readiness/implement.md @@ -58,6 +58,18 @@ - [x] 最终生产计数:active 6、must-change 3、binding 6、binding 缺失/冲突/inactive 漂移 0、密码历史成功覆盖 3、企微历史成功审计覆盖 4、近 7 天企微成功审计覆盖 1、三类认证锁定 0、异常活跃会话 0。 - [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 - 企业邮箱不可达、验证码投递异常或账户锁定:停止该员工操作,不连续请求。 diff --git a/.trellis/tasks/07-28-all-account-login-readiness/prd.md b/.trellis/tasks/07-28-all-account-login-readiness/prd.md index 83f37618..06a33302 100644 --- a/.trellis/tasks/07-28-all-account-login-readiness/prd.md +++ b/.trellis/tasks/07-28-all-account-login-readiness/prd.md @@ -25,7 +25,8 @@ - 3 个 active 账户已有企微 binding;唯一 active 管理员正是缺 binding 的账户,且该管理员已有历史密码成功登录、当前无需强制改密但本人已不知道密码。 - 企业微信通讯录本身拥有管理员 userid,但生产只读核对显示 OA 侧没有该管理员的目录同步 userid、成功 HR 主数据映射或 identity binding;当前 OAuth 回调只能拿到 userid,无法仅凭它反推出唯一 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`,不是死路。 - 本次实际失败发生在 `/reset-password` 成功之前:第二次签发使第一次验证码失效,用户收到并使用旧验证码后被后端拒绝;未发现“重置成功后被前端缓存再次送回改密页”的现场证据。 - `/email-login/code -> /email-login/verify` 是另一条邮箱验证码登录路径;现有前端在验证成功后仍遵守 `must_change_password` 跳转,而修改密码页强制要求旧密码,因此它对未知随机初始密码账户可能形成死路,但与本次失败无关。 @@ -79,7 +80,9 @@ - [ ] 所有重置、绑定、解锁、会话撤销均有 OA 审计且未扩角色、未修改企业微信通讯录。 - [x] 当前唯一 active `manager` 的直属主管唯一解析为 active `majianyong`,不存在自环或主管链环路,并有脱敏审计;`wangshunli` 不进入业务审批链。 - [x] `majianyong` 保持 `ceo` 且无直属主管;其本人报销目标为 active 非本人 `finance`,当前 `finance_mgr` 配置缺口未修复前保持阻断且不发生自审。 -- [ ] 自动化、Trellis、敏感扫描、生产 `/ready` 和关联认证 smoke 通过后才通知员工测试。 +- [x] 自动化、Trellis、敏感扫描、生产 `/ready` 和关联认证 smoke 已通过,可以通知剩余首次改密人员逐人测试。 + +当前员工可测试边界:生产邮件能力、公网 `/ready`、认证 capabilities 与 SMTP 连接/认证已通过;剩余 3 名首次改密人员可由本人逐一进入忘记密码流程。不得在聊天中传递密码或验证码,不连续点击重发;新签发验证码会使旧验证码失效。 ## Definition of Done diff --git a/.trellis/tasks/07-28-all-account-login-readiness/task.json b/.trellis/tasks/07-28-all-account-login-readiness/task.json index ec2938f9..ebbde8c6 100644 --- a/.trellis/tasks/07-28-all-account-login-readiness/task.json +++ b/.trellis/tasks/07-28-all-account-login-readiness/task.json @@ -21,6 +21,6 @@ "children": [], "parent": null, "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": {} }