文件
hyb-oa/.trellis/tasks/archive/2026-08/08-08-production-authenticated-readiness/prd.md
T

18 KiB

生产登录态只读验收

Goal

在生产版本已经部署、迁移已全部应用但管理员与员工验收凭据均返回 401 的背景下,先用生产数据库只读状态诊断区分 identifier 失配、账号停用、角色变化、锁定与凭据失效,再在合法恢复的仓库外凭据上执行最小登录态只读验收。

本任务不得为了通过验收修改账号、密码、锁定状态、角色、权限或合同数据;合同终止能力只验证到初始禁用按钮,不输入足以启用按钮的原因、不打开确认层、不发送终止请求。只有必要登录态检查通过且验收前后回滚窗口均有证据时,才可将“功能已上线但未登录态验证”的 PAUSED 收敛为准确的生产 readiness 结论。

Background and Confirmed Facts

  • 已归档任务 08-08-production-deployment-execution 的产品候选为 0224ad8;后续 d5fb2ec、ff46794 只包含 Trellis/spec 证据。生产运行与公网 SHA 应核对已部署产品候选,不能错误要求匹配当前仓库 HEAD。
  • 生产已有 65 条 migration 全部 applied,termination migration 已生效;部署结束时 terminatedRows=0、rollbackWindow=OPEN。窗口会在第一条已提交 terminated 记录产生时永久关闭,不按时间计算。
  • 前一任务匿名合同鉴权负向 3/3 通过,但管理员、员工仓库外验收凭据在登录处稳定返回 401;这只能证明当前凭据无法登录,不能证明是密码错误,也不能排除 identifier 已过期或不符合登录语义。
  • server/src/routes/auth.js 的密码登录只按 email 或 phone 精确查找,不接受 employee_no。账号不存在或密码错误返回 401;停用返回 403;仍处于锁定期返回 423。
  • 密码失败会更新 login_failed_count、login_failed_first_at、login_locked_until。继续试登录会改变生产安全状态,甚至把账号推入锁定,因此登录不能作为第一步诊断手段。
  • server/src/scripts/repair-oa-only-smoke-accounts.js 默认 dry-run 并使用 BEGIN READ ONLY,可借鉴其脱敏查询和角色诊断;但其 identifier 查询同时接受员工编号,与真实登录语义不完全一致,且 --apply 路径不属于本任务。
  • 前端仅当当前用户可管理且合同状态为 active/expired 时渲染终止面板;draft/archived 不显示入口。因此无法用服务端 terminate_not_allowed 为确认层误操作提供 UI 兜底。
  • 服务端终止端点也只接受 active/expired。本任务不会对任何合同调用该端点,不会主动关闭回滚窗口。
  • 当前没有同构预发环境;生产是唯一公网运行环境。本任务所有生产读取、登录和浏览器行为都必须 fail-closed,并保留脱敏证据。

Scope

  1. 复核已部署产品 SHA、生产 migration 状态与目标身份,只读取本任务需要的稳定状态,不重复部署。
  2. 在任何登录尝试前,分别对管理员和员工验收 identity 做只读诊断:按真实 auth 语义确认是否精确匹配 email/phone,读取启用/归档、登录失败计数、锁定状态和期望角色是否就绪。
  3. 按诊断结果分类:identifier 失配、锁定、停用/角色漂移、或在状态完全健康时推定仓库外密码凭据失效;需要修改生产状态时停止并另行批准。
  4. 只有仓库外凭据由合法来源恢复且账号只读门禁通过后,才允许管理员、员工各进行最多一次登录验证;随后执行合同工作区的最小只读正向/负向验收。
  5. 在登录态验收前后分别查询 terminated 行数并记录回滚窗口,形成不含凭据、账号、合同正文和生产身份明文的证据。

Requirements

R1 只读目标与版本基线

  • 复用已批准、已验证的生产身份引导与 read-only preflight;不得把本地、回归库、shadow 或其他环境当作生产。
  • 读取 migration status、运行/公网 build SHA 和窗口状态,不执行 deploy、migrate deploy、seed、同步或应用切换。
  • SHA 预期值固定为已部署产品候选 0224ad8;当前 HEAD 仅用于任务文档,不能作为“生产落后”的错误判据。

R2 登录前账号状态诊断硬门禁

  • 第一项账号动作必须是数据库只读查询,禁止先调用 /api/auth/login、浏览器提交登录表单或依赖旧 401 猜测原因。
  • 查询必须按真实登录语义把仓库外 identifier 仅与 email/phone 精确比较;允许只输出 matchedBy=email|phone|none,不得输出 identifier、邮箱、手机号、员工编号、密码 hash 或原始账号记录。
  • 每个角色只记录:角色 key、active/archived/locked 布尔、失败计数、matchedBy、期望角色是否就绪。锁定时间只表达“当前是否锁定”,不在任务证据中保留原值。
  • repair-oa-only-smoke-accounts.js 只允许借鉴或调用其默认 dry-run/read-only 能力;严禁 --apply、repair、unlock、密码重置、激活或角色写入。
  • 只读查询失败、目标不唯一、identifier 无法匹配、账号停用、角色不符或当前锁定均立即 PAUSED,不得通过再试登录获得更多信息。

R3 401 分流与有限登录预算

  • matchedBy=none:判定仓库外验收 identifier 过期或不符合现行 email/phone 登录合同;由账号持有人提供当前凭据并更新仓库外 secret。本任务不得改生产账号。
  • active=true、locked=false、角色正确且 identifier 精确匹配,但既有登录证据为 401:按当前 auth 分支判定仓库外密码凭据不匹配;密码修改/重置超出范围,停止并另行批准。
  • 当前锁定:等待自然过期或解锁均不在本任务内;记录 PAUSED,禁止增加失败次数。
  • 停用、归档或权限/角色变化:只记录事实与影响,任何恢复或授权调整另设任务。
  • 只有合法的新鲜仓库外凭据已经就位、R1/R2 全部通过且失败计数没有继续增长时,才允许管理员与员工各最多一次真实登录。执行固定 workers=1、retries=0;任一失败立即停止,并重新只读读取失败计数/锁定状态,不再尝试第二次。

R4 回滚窗口双快照

  • 登录态验收前查询 contract_registry.status='terminated' 的真实行数:0 为 OPEN,大于 0 为 CLOSED;查询失败不得默认 OPEN。
  • 所有验收步骤结束或中止后再次执行同一只读查询,记录前后 count、窗口状态和读取顺序。
  • 若本任务未发送终止请求但窗口由 OPEN 变为 CLOSED,必须记录为真实用户已使用终止能力;不得回退旧应用,后续仅允许 roll-forward/兼容 hotfix。
  • 本任务无论窗口状态如何都不得创建、归档、编辑或终止合同。

R5 登录态只读验收边界

  • 管理员:打开合同台账与一份现有详情,验证自然中文状态、详情摘要和权限允许的只读工作区。
  • 对现有 active/expired 合同,只验证终止面板存在、至少 10 字提示存在、终止按钮初始禁用;不输入终止原因、不点击终止按钮、不打开确认层,并监听/断言没有向 /api/contracts-registry/:id/terminate 发出 POST。
  • 员工:验证无合同管理权限的负向路径;不得以删除断言、扩大员工权限或切换成管理员身份代替负向验收。
  • 仅在生产已有相应数据时只读验证:已有 archived 合同可验证字段锁定;已有 terminated 合同可验证“已终止”标签和终止事实区。数据不存在时记 PAUSED,不得为验收制造记录。
  • 浏览器优先使用 Playwright CLI 进行受控交互,执行前确认 command -v npx;不得新建会改变产品或测试合同的自动化 spec。若 CLI 不能保证不触发写入,则退回更窄的人工只读检查。

R6 证据、结论与停止条件

  • 原始生产输出仅留受控现场;任务内只记录 role key、布尔值、失败计数、HTTP 状态、测试数字、窗口 count、SHA 与脱敏引用。
  • 不记录密码、token、完整 identifier、账号、邮箱/手机号、生产连接串、SSH 身份、数据库名、合同正文、终止原因或原始 payload。
  • 任一登录、身份、权限、数据前置或浏览器检查失败均按原因 PAUSED/FAIL;不得为“全绿”修改生产账号、权限、合同或测试断言。
  • 只有管理员正向、员工负向、终止入口安全断言和前后窗口证据均达到适用验收标准,才可说明生产登录态 readiness 通过。缺少 archived/terminated 样本的可选只读项保持 PAUSED,但不得伪装为已验证。

Acceptance Criteria

  • AC1:生产运行/公网 SHA 对应已部署产品候选 0224ad8(短标识 0224ad83d7f8);65 条 migration 仍全部 applied、pending=none,未触发部署或 schema 写入。
  • AC2:任何登录前已对管理员、员工完成同一 BEGIN READ ONLY 查询合同;查询设计覆盖 matchedBy、active/archived/locked、失败计数和期望角色是否就绪。两个 identifier 实际均 matchedBy=none,故账号属性记为 N/A 而非生产事实,不含账号或凭据明文。
  • AC3:两个仓库外 identifier 均不匹配 email/phone/employee_no。只读角色候选与远端内存密码 hash 比对进一步证明:唯一活跃管理员可解析为 email、未锁定、失败计数为 0,但原管理员密码不匹配;员工有 5 个活跃非管理员候选,原员工验收密码对这些候选的匹配数为 0。后续只读资格诊断将员工目标收敛为唯一 1 个:其登录 identifier 已就绪、未锁定、失败计数为 0、无需首次改密、可进入只读合同工作区且不具备合同管理权限;运行容器内既有 smoke 命名判据将其分类为专用 OA-only smoke 账号,未依赖私有 allowlist。本次没有注入或比较密码。登录尝试保持 0,未输出 identifier/hash/角色明细。
  • AC4:管理员凭据已由独立 08-09-production-admin-break-glass-reset 合法恢复,本轮唯一一次登录 HTTP 200,失败计数为 0 且未锁定;员工凭据已由独立 08-10-production-employee-smoke-break-glass-reset 合法恢复,交接时目标唯一、rolesExact=true、四项管理权限全 false,本轮唯一一次登录同为 HTTP 200。两个角色均零 retry,未试探原始失效凭据。
  • AC5 — PAUSED:管理员认证通过,但生产合同总数为 0,无法进入详情或终止面板;终止端点 POST 计数为 0、全部合同 mutation 计数为 0,确认层未打开,未制造合同。
  • AC6:员工负向目标已通过只读资格诊断唯一确定,并确认是专用 OA-only smoke 账号而非真人员工;最终凭据来自 08-10 break-glass,角色精确与四项权限前提未变。生产登录后合同列表可读、管理入口“新增合同”不可见,直达新增路由进入 403;合同读取请求 2、合同 mutation 与全部非认证 mutation 均为 0。archived/terminated 样本仍为 0,相应可选 UI 检查保持 PAUSED,未扩大权限、修改断言或制造数据。
  • AC7:初始、登录前停止后、扩展账号诊断后、邮件能力诊断后、管理员验收 PRE/POST、员工资格诊断 PRE/POST、smoke 分类最终 POST 以及员工负向 PRE/POST 共十一次有效窗口证据均为 terminatedRows=0、rollbackWindow=OPEN、basis=terminated_status_count;窗口未变化,本任务真实合同终止写入计数为 0。
  • AC8:邮件能力与 break-glass 建议审查、浏览器验收收口审查的 findings 均已按证据边界修订,最终只读复查为 PASS;task validate、process contract、governance、sensitive scan、docs registry audit 与 diff check 已复跑,docs audit 仅保留既有 22 个未注册文件 warning。
  • AC9:结论明确为产品已部署、生产基线健康,管理员认证与员工登录态权限负向均通过;合同 UI 因生产零记录仍 PAUSED,当前不得告知用户可以测试合同终止功能。

Lifecycle Closeout

  • 本任务于 2026-08-23 按“已完成适用验收、保留无样本分支暂停”的口径收口:管理员与专用 employee smoke 账号均使用合法恢复后的仓库外凭据完成唯一一次登录,HTTP 200、零 retry;员工合同管理权限负向验证通过。
  • 生产合同总数为 0,AC5 以及 archived/terminated 样本相关 UI 断言没有可用业务样本,继续保持未勾选 PAUSED。归档只表示本任务生命周期关闭,不表示合同详情、终止面板、归档锁定或已终止标签已通过生产验收。
  • 本任务累计十一次有效 rollback window 快照均为 OPEN/0;terminate POST、合同 mutation 和全部非认证 mutation 均为 0,未创建测试合同、未修改账号、权限或业务数据。
  • 后续仅在生产出现首条真实 contract_registry 记录时新建只读补验任务;先核对样本状态与窗口,再验证该样本适用的只读 UI。记录出现不构成合同终止或任何业务写入授权。

Out of Scope

  • 修改、重置或轮换生产密码;解锁账号;清零失败计数;激活/恢复员工;修改邮箱、手机号、员工编号、角色或权限。
  • 调用 repair-oa-only-smoke-accounts.js --apply,或任何 repair/unlock/seed/手工 SQL 写入。
  • 创建测试合同、改变合同状态、归档/恢复/续约/编辑合同、填写有效终止原因、打开确认终止层、调用终止端点或制造 terminated 样本。
  • 修改产品代码、auth 逻辑、合同状态机、Prisma schema/migration、部署脚本、preflight tooling 或现有测试断言。
  • 将“验收凭据有效性前移到 preflight”、部署合同去重、失败路径 harness 等 tooling 改进并入本任务;这些另设任务。
  • 企业微信真机、生产写入 smoke、真实付款、审批、OCR 或其他业务流验收。

Accepted Risk and Deferred Items

  • 生产终止功能已可达,真实用户可能在验收期间关闭回滚窗口;双快照只能发现变化,不能阻止真实使用。窗口一旦关闭,只能 roll-forward 或兼容 hotfix。
  • 合同 UI 后续验收不按日期开放等待,而以生产 contract_registry 出现首条记录为触发条件:届时先只读复核样本状态与 rollback window,再在不修改该记录的前提下补验详情、状态标签、字段锁定和终止按钮初始禁用;不得把“首条记录出现”自动解释为允许终止写入。
  • 当前生产合同总数为 0 是数据库事实,但仓库事实源已将合同台账列为首批真实使用域,历史试运行报告还曾记录 TEST/SMOKE 数据。现有证据只能排除连错库,不能判定零数据是预期未启用、后续清理结果或尚未完成真实数据迁入;在负责人确认前保持为运营事实待确认,不把它写成产品缺陷或正常结论。
  • 没有同构预发环境;本任务主动放弃确认层视觉验收,以降低不可逆误操作风险。
  • 既有生产可能没有 archived 或 terminated 样本;缺少数据时相应只读断言保持 PAUSED,不得为覆盖率制造业务数据。
  • 如果仓库外凭据确已失效,本任务可能在账号诊断阶段结束而无法完成登录态验收;这是真实阻塞,不授权以生产账号写入换取通过。
  • 若后续另行批准 reset-active-admin-password.js,它不仅更新 password hash,还会设置 must_change_password、清理认证失败/锁定状态、递增 token version、撤销活跃会话、失效验证码并写审计。旧 hash 只能作为高敏、短期、受限访问的有限凭据恢复材料,不能回滚这些其他效果,也不能删除审计;不得把它称为完整回滚。

Operational Inputs (not committed)

  • 已批准生产只读身份引导所需的受控进程变量与 SSH contract。
  • 仓库外 live-smoke 管理员/员工 identifier 与密码;只允许从受控 secret store 注入,不进入命令历史、仓库、任务文档或对话。
  • 已部署产品候选完整 SHA;任务证据可保留其 Git commit,不保留任何生产身份明文。

Open Questions

  • 无。凭据当前能否恢复属于执行门禁结果,不是规划歧义;失败时按 R3 PAUSED。

Test Plan

规划门禁

  • python3 ./.trellis/scripts/task.py validate 08-08-production-authenticated-readiness
  • 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

执行门禁(另行启动后)

  • 生产 identity/read-only preflight、migration status、运行/公网 SHA。
  • 验收前 terminatedRows / rollback window 快照。
  • 管理员/员工账号状态的 BEGIN READ ONLY 诊断;先核对 identifier 的 email/phone 登录语义,再判断状态、角色、失败计数与锁定。
  • 仅在门禁通过且凭据恢复后,用单 worker、零 retry 各尝试一次登录。
  • Playwright CLI 或更窄人工检查:管理员合同详情、终止按钮初始禁用且零终止请求;员工权限负向。
  • 验收后 terminatedRows / rollback window 快照。
  • 只读 reviewer 与规划门禁命令复跑。

Evidence Rules

  • 账号诊断只保存枚举、布尔和计数;任何完整 identifier、锁定时间原值和数据库行均不进入 task。
  • 浏览器证据只保留非敏感 UI 状态和请求计数;截图不得包含合同正文、相对方敏感信息、金额或终止原因。
  • 失败证据必须保留真实 HTTP 状态和阶段,但不得保留请求 payload、cookie、token 或凭据。
  • 复用 artifacts 目录时必须使用新的时间戳目录,禁止把历史 PASS 当成本轮证据。

Notes

  • Keep prd.md focused on requirements, constraints, and acceptance criteria.
  • Lightweight tasks can remain PRD-only.
  • For complex tasks, add design.md for technical design and implement.md for execution planning before task.py start.