# 生产员工 Smoke Break-glass 重置 ## Goal 为 `08-08-production-authenticated-readiness` 已只读收敛出的唯一专用 OA-only smoke employee 恢复可控登录凭据,同时保持其角色精确为 `employee`、不获得合同管理权限、不影响真人员工。任务只负责重置生命周期;员工合同权限负向验收仍回到原 readiness task 完成。 本任务含本地安全工具实现、受控发布和一次生产账号写入三个风险层。任务启动只授权本地实现;发布候选与生产账号写入必须分别取得明确批准,任何一步均不得由前一步批准推定。 ## Background and Confirmed Facts - `08-08-production-authenticated-readiness` 已在生产以只读方式从 5 个活跃非管理员候选中收敛出唯一合格目标:identifier ready、未锁定、失败计数为 0、无需首次改密、可进入合同只读范围且不具备合同管理权限;该目标符合运行容器既有 smoke 命名判据,不是真人员工。员工登录 attempts 保持 0。 - 原仓库外员工 smoke 密码对 5 个候选均不匹配,必须恢复凭据后才能完成员工登录态负向验收。 - `repair-oa-only-smoke-accounts.js:357-367,391-446` 的 repair 语义不是密码重置:它可恢复 active/employment/archive 状态、清登录锁、递增 token version、补缺失角色并写 lifecycle event,但不更新 `password_hash`。 - 该 helper 的角色写入使用 `INSERT ... ON CONFLICT DO NOTHING`,只补角色、不删除额外角色,因此不能证明或恢复“角色恰好等于期望列表”;其报告还明确声明 `passwordReset=false`(`:520-523`)。 - helper 的默认 dry-run 使用 `BEGIN READ ONLY`(`:540-588`),且 `isSafeOaOnlySmokeTarget`(`:110-130`)可作为目标安全判据;`--apply` 同时要求 CLI flag 与 `OA_ONLY_SMOKE_REPAIR_APPLY=1`(`:37-41,259-263`)。 - 管理员既有 `reset-active-admin-password.js` 已证明同源 bcrypt、stdin 密码、字面确认串、事务内二次核对、目标限定的 session/challenge 撤销、审计和 `APPLY_UNCERTAIN -> verify` 模式可行;但不得把其目标面扩展到任意员工。 - 新员工脚本当前不在生产镜像。禁止通过 SSH 临时复制、`docker cp`、工作区覆盖、`node -e` 或手写 SQL 注入;必须先作为正常候选发布,再以运行镜像脚本 SHA 与该候选精确一致为执行前提。 - 生产合同终止回滚窗口最近九次均为 `OPEN / terminatedRows=0`。本任务不读写合同;登录和强制改密会产生预期认证写入,但不得进入业务页面。 ## Scope 1. 新增员工 smoke 专用 break-glass 密码重置脚本及 focused tests;不改管理员脚本的目标语义,不改现有 repair helper 的 apply 行为。 2. 在本地证明脚本只选择唯一安全目标,角色必须精确为 `['employee']`,权限不得包含 `project.manage`、`project.view_all`、`system.settings` 或 `*`,并在事务内复核这些不变量。 3. 本地实现通过只读 reviewer 后,另行获批提交、推送并走现有生产发布合同,使脚本进入运行镜像;不得带外复制脚本。 4. 另行获批后执行一次生产 reset:repair helper 仅 dry-run 对照,新脚本 preflight、单次 apply、verify、写后角色/权限复核、一次登录与一次强制改密、最终 verify 和 secret/恢复材料清理。 5. 完成后只向 `08-08-production-authenticated-readiness` 交接“员工最终凭据已就绪且负向权限前提仍成立”,不在本任务执行合同 UI/API 负向验收。 ## Requirements ### R1 现有 repair helper 只读对照 - 获批进入生产窗口后,先对唯一目标运行一次默认 `--dry-run`,保存脱敏动作集合与 `passwordReset=false` 结论;不得运行其 `--apply`。 - dry-run 若计划 `restore_active_employee`、`clear_login_lock` 或 `add_missing_roles`,说明现场已偏离既有诊断,本任务立即 `PAUSED`,不得让 helper 修复后继续。 - dry-run 必须继续通过 `isSafeOaOnlySmokeTarget`,且不能用其“包含期望角色”结论替代精确角色断言。 ### R2 新脚本的唯一目标与权限不变量 - 新增 `server/src/scripts/reset-employee-smoke-password.js`。脚本自行从生产状态发现目标,不从 argv/env 接收完整 identifier,不输出 email、phone、employee_no、name 或目标 id。 - 目标必须同时满足:`status=active`、`employment_status=active`、未 archived、smoke 判据命中、至少一个合法 email/phone 登录 identifier、当前未锁定、`must_change_password=false`、实际角色排序后精确等于 `['employee']`。 - 从实际角色解析出的权限不得包含 `project.manage`、`project.view_all`、`system.settings` 或 `*`。候选数不是恰好 1、角色多/少一项、权限扩大、状态变化或 smoke 判据失败均 `BLOCKED`。 - 脚本只断言角色和权限,禁止 create/delete/update `employee_roles`,禁止激活、归档、修改 employment/status、identifier 或任何业务数据。 - 事务内必须按目标 id、写前 token version、状态、smoke、精确角色和权限再次核对;任一变化导致事务零写入。 ### R3 受控密码重置合同 - 提供 `--preflight`、`--verify`、`--verify-password-changed`;可提供只读 `--diagnose-wecom-binding`。唯一写入口必须是 `--apply EMPLOYEE_SMOKE_BREAK_GLASS`,确认串不精确即 `explicit_confirmation_required`。 - 临时密码只从 stdin 读取,使用应用同源 `hashPassword` 与 `validatePassword`;不得进入 argv、环境变量、history、stdout、artifact、task 或对话。 - 单次事务仅更新目标认证状态:新 password hash、`must_change_password=true`、三类失败/锁定域与 password-reset request 状态清零、token version +1;仅撤销目标 session、失效目标 password reset code、消耗目标 email login code,并写专用 break-glass audit。 - 所有 session/challenge 写入必须带 `employee_id=target.id`;审计 actor/target 仅为该目标。不得修改角色、权限、启用/归档状态、企微绑定或其他账号。 - apply 必须严格一次。通道中断、输出缺失或 JSON 不可解析进入 `APPLY_UNCERTAIN`,此时只允许 `--verify`;verify 证明已落地时禁止 retry。 ### R4 写后重新验证负向前提 - `--verify` 不只验证 must-change、锁、session/challenge 与 audit,还必须重新发现唯一目标并证明角色精确为 `['employee']`、四项合同管理权限均为 false。 - verify 后再执行一份独立只读角色/权限快照,与写前基线逐项对账;不能沿用 `08-08` 的旧诊断,也不能只断言“包含 employee”。 - 只有 `rolesExact=true`、`contractManagePermissions=false`、目标仍唯一且 reset 副作用全部 PASS,才允许更新临时仓库外 secret 并进行一次登录。 ### R5 强制改密、secret 与交接 - 写前生成互不相同的临时密码和最终密码;保存旧 hash 为生产现场 `0700/0600`、最多 24 小时的高敏有限恢复材料。它不能恢复 token version、session、challenge、锁定计数或审计,不得称为回滚。 - 一次登录必须按预期进入 `/change-password`;一次强制改密提交成功后立即停在 `/portal`,不得主动进入合同或其他业务页、不得发送非认证业务 mutation,自动 retry 为 0。`/portal` 作为产品自然落点自动发起的只读 shell 请求须单独计数,不得误报为主动业务访问或伪造成 0。 - `--verify-password-changed` 必须证明 must-change=false、locks cleared、activeSessions=1、password-change audit 与本次 employee-smoke break-glass audit 均存在,且角色/权限不变量仍成立。 - 最终 secret 原子更新且权限为 `0600`;临时凭据、浏览器状态和旧 hash 在验证通过后销毁,只记录清理布尔。 - 回到 `08-08-production-authenticated-readiness` 后再执行员工合同权限负向验收;本任务成功不等于该负向验收已经通过。 ### R6 发布、证据与停止条件 - 本地代码必须先通过 focused tests、管理员脚本/repair helper 回归和只读 reviewer;reviewer 不改代码。 - 新脚本必须进入一个 clean/published、remoteVerified 的正常部署候选,经现有 `deploy-existing.sh` 合同另行批准发布;发布前不得生产写入,发布失败立即停止。 - 生产执行前,运行镜像脚本 SHA-256 必须与获批候选中的脚本精确一致;不得复制或替换运行脚本。 - 生产发布与生产账号写入分别需要明确批准。任何审批、SHA、身份、migration、目标、权限、恢复材料或 secret 门禁失败均 fail-closed。 - 任务证据只记录 SHA match、枚举、布尔、计数、apply/login/change-password 次数和清理状态,不记录生产身份、完整路径、identifier、账号、密码、hash、token、cookie 或原始 payload。 ## Acceptance Criteria - [x] AC1:现有 repair helper 的完整 apply 副作用已由源码和 focused regression 固化;生产仅运行 dry-run,输出证明 `passwordReset=false` 且 action 集合为空,helper apply 次数为 0。 - [x] AC2:新脚本的 preflight 仅在唯一 active-employment/unarchived smoke employee、identifier ready、未锁定、must-change=false、`roles == ['employee']` 且四项管理权限全 false 时 PASS;所有非唯一/额外角色/权限扩大/状态漂移用例均 fail-closed。 - [x] AC3:新脚本的 apply 只接受精确确认串和 stdin 密码;事务内二次核对目标/token/smoke/状态/精确角色/权限,且没有任何角色、权限、状态、identifier 或业务数据写入路径。 - [x] AC4:focused tests 证明密码、锁、token、目标限定 session/challenge 与专用 audit 副作用;同时回归管理员 reset 与 repair helper,未扩大既有工具目标面。 - [x] AC5:本地只读 reviewer 复核脚本与测试后通过;随后只在另行获批的正常发布中把脚本送入生产镜像,运行脚本 SHA 与 clean/published 候选精确一致,带外复制次数为 0。 - [x] AC6:生产 preflight PASS 后 apply 恰好一次;明确结果或 `APPLY_UNCERTAIN -> verify` 证明写入状态,审计计数写前/写后增量恰好 1,不存在第二次 apply。 - [x] AC7:apply 后 `--verify` 与独立只读快照均重新证明 `roles == ['employee']`、四项管理权限全 false、目标唯一,且 must-change、locks、session/challenge、token/audit 副作用符合合同;未沿用写前结论。 - [x] AC8:一次登录按预期进入强制改密,一次提交后 `--verify-password-changed` PASS;未主动访问业务功能且非认证 mutation 为 0,`/portal` 自发只读请求独立计数,最终 secret 为 `0600`,临时凭据、浏览器状态和旧 hash 已销毁。 - [x] AC9:生产其他账号、角色/权限、企微绑定、合同、schema 与业务数据写入为 0;任务内无敏感值泄露,执行后只读 reviewer 和 task/process/governance/sensitive/diff 门禁通过。 - [x] AC10:只向 `08-08-production-authenticated-readiness` 交接最终员工凭据可用与负向前提仍成立;员工合同权限负向仍由该 task 执行,本任务不宣称产品已可告知用户测试。 ## Out of Scope - 在本任务内执行合同列表/详情/终止入口或员工合同权限负向验收。 - 修改 employee 角色、权限、active/employment/archive 状态、identifier、企微绑定,创建新 smoke 账号或影响真人员工。 - 执行 `repair-oa-only-smoke-accounts.js --apply`,扩展管理员 break-glass 脚本到任意员工,或使用手写 SQL/Prisma、`node -e`、临时脚本复制完成写入。 - 启用邮件、SMTP、自助忘记密码、生产 feature flag、schema/migration 或合同数据。 - 把旧 hash 写回、删除审计或恢复历史 session/challenge;如确需此类写入必须另行批准。 ## Accepted Risks and Deferred Items - 正常发布会重启/切换应用,即使候选只新增运维脚本也属于生产变更;因此发布审批独立于 task start 和账号写入审批。 - smoke 账号风险低于唯一管理员,但 reset 和强制改密仍会递增 token version、撤销目标会话并写审计,不能视为零成本操作。 - 旧 hash 仅保留一个格式正确的历史值,不能还原其他认证状态;apply 后不存在完整回滚,只能依赖 verify、最终改密或另行批准的 roll-forward。 - 员工负向验收和合同 UI 样本阻塞不由本任务收口;本任务完成后原 readiness task 仍可能保持 PARTIAL/PAUSED。 ## Open Questions - 无。现有 helper 不具备密码重置能力,技术路径已收敛为新专用脚本;发布和生产写入在执行期分别走明确审批停点。 ## Test Plan ### 本地实现与复核 - `node --check server/src/scripts/reset-employee-smoke-password.js` - `cd server && npm test -- tests/unit/scripts/reset-employee-smoke-password.test.js tests/unit/scripts/reset-active-admin-password.test.js tests/unit/scripts/repair-oa-only-smoke-accounts.test.js` - `cd server && npm run prisma:validate` - `cd client && npm run typecheck && npm run build`(仅在形成可部署候选前执行) - `python3 ./.trellis/scripts/task.py validate 08-10-production-employee-smoke-break-glass-reset` - `node scripts/qa/check-process-contract.mjs --baseline --json` - `node scripts/qa/check-trellis-governance.mjs --json` - `node scripts/qa/check-sensitive-content.mjs` - `git diff --check` ### 另行获批的生产阶段 - clean/published provenance、生产 identity/read-only preflight、65 migration、运行/公网 SHA、rollback window。 - 正常部署新候选;部署完成后运行镜像新脚本 SHA 与候选精确一致。 - repair helper 单次 dry-run(禁止 apply)→ 新脚本 `--preflight` → 独立权限快照。 - 受控旧 hash、临时/最终 secret 就绪后单次 `--apply EMPLOYEE_SMOKE_BREAK_GLASS` → `--verify` → 写后独立角色/权限快照。 - 一次登录/强制改密 → `--verify-password-changed` → secret/hash/browser 清理 → 最终 read-only preflight/window。 - 执行后只读 reviewer 核对脚本 SHA、apply 次数、审计增量、副作用、写后角色/权限与清理证据。 ## Evidence Rules - 允许记录:候选/脚本 SHA、match 布尔、目标/审计/会话/challenge 计数、角色是否精确、权限布尔、attempt 次数、HTTP 状态、清理布尔。 - 禁止记录:identifier、email、phone、employee_no、name、目标 id、临时/最终密码、旧/新 hash、cookie/token、生产路径/身份/连接串和原始 payload。