文件
hyb-oa/.trellis/tasks/archive/2026-08/08-10-production-employee-smoke-break-glass-reset/prd.md
T

14 KiB
原始文件 Blame 文件历史

生产员工 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

  • AC1:现有 repair helper 的完整 apply 副作用已由源码和 focused regression 固化;生产仅运行 dry-run,输出证明 passwordReset=false 且 action 集合为空,helper apply 次数为 0。
  • AC2:新脚本的 preflight 仅在唯一 active-employment/unarchived smoke employee、identifier ready、未锁定、must-change=false、roles == ['employee'] 且四项管理权限全 false 时 PASS;所有非唯一/额外角色/权限扩大/状态漂移用例均 fail-closed。
  • AC3:新脚本的 apply 只接受精确确认串和 stdin 密码;事务内二次核对目标/token/smoke/状态/精确角色/权限,且没有任何角色、权限、状态、identifier 或业务数据写入路径。
  • AC4:focused tests 证明密码、锁、token、目标限定 session/challenge 与专用 audit 副作用;同时回归管理员 reset 与 repair helper,未扩大既有工具目标面。
  • AC5:本地只读 reviewer 复核脚本与测试后通过;随后只在另行获批的正常发布中把脚本送入生产镜像,运行脚本 SHA 与 clean/published 候选精确一致,带外复制次数为 0。
  • AC6:生产 preflight PASS 后 apply 恰好一次;明确结果或 APPLY_UNCERTAIN -> verify 证明写入状态,审计计数写前/写后增量恰好 1,不存在第二次 apply。
  • AC7:apply 后 --verify 与独立只读快照均重新证明 roles == ['employee']、四项管理权限全 false、目标唯一,且 must-change、locks、session/challenge、token/audit 副作用符合合同;未沿用写前结论。
  • AC8:一次登录按预期进入强制改密,一次提交后 --verify-password-changed PASS;未主动访问业务功能且非认证 mutation 为 0,/portal 自发只读请求独立计数,最终 secret 为 0600,临时凭据、浏览器状态和旧 hash 已销毁。
  • AC9:生产其他账号、角色/权限、企微绑定、合同、schema 与业务数据写入为 0;任务内无敏感值泄露,执行后只读 reviewer 和 task/process/governance/sensitive/diff 门禁通过。
  • 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。