14 KiB
14 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
- 复核已部署产品 SHA、生产 migration 状态与目标身份,只读取本任务需要的稳定状态,不重复部署。
- 在任何登录尝试前,分别对管理员和员工验收 identity 做只读诊断:按真实 auth 语义确认是否精确匹配 email/phone,读取启用/归档、登录失败计数、锁定状态和期望角色是否就绪。
- 按诊断结果分类:identifier 失配、锁定、停用/角色漂移、或在状态完全健康时推定仓库外密码凭据失效;需要修改生产状态时停止并另行批准。
- 只有仓库外凭据由合法来源恢复且账号只读门禁通过后,才允许管理员、员工各进行最多一次登录验证;随后执行合同工作区的最小只读正向/负向验收。
- 在登录态验收前后分别查询
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。登录尝试保持 0,未输出 identifier/hash。
- AC4 — PAUSED:管理员正确 identifier 已在只读内存中唯一解析,但现有密码确认失效;员工目标账号仍不唯一,现有验收密码不匹配任何活跃非管理员候选。密码重置或账号选择需要单独批准,本轮各角色登录尝试均为 0。
- AC5 — PAUSED:管理员 identifier 已解析但密码不匹配,未建立管理员会话,未进入合同详情或终止面板;终止端点
POST计数为 0,确认层未打开。 - AC6 — PAUSED:员工目标账号未唯一确定且现有验收密码不匹配活跃候选,未执行权限负向或 archived/terminated 样本 UI 检查;未扩大权限、修改断言或制造数据。
- AC7:初始、登录前停止后、扩展只读诊断后共三次窗口证据均为
terminatedRows=0、rollbackWindow=OPEN、basis=terminated_status_count;窗口未变化,本任务真实合同终止写入计数为 0。 - AC8:扩展诊断的只读 reviewer 首轮
PASS_WITH_FINDINGS已完成措辞修订,最终复查为PASS;task validate、process contract、governance、sensitive scan、diff check 均通过。 - AC9:结论明确为产品已部署、生产基线健康,但登录态验收仍
PAUSED;当前不得告知用户可以测试。
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。
- 没有同构预发环境;本任务主动放弃确认层视觉验收,以降低不可逆误操作风险。
- 既有生产可能没有 archived 或 terminated 样本;缺少数据时相应只读断言保持
PAUSED,不得为覆盖率制造业务数据。 - 如果仓库外凭据确已失效,本任务可能在账号诊断阶段结束而无法完成登录态验收;这是真实阻塞,不授权以生产账号写入换取通过。
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-readinessnode scripts/qa/check-process-contract.mjs --baseline --jsonnode scripts/qa/check-trellis-governance.mjs --jsonnode scripts/qa/check-sensitive-content.mjsgit 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.mdfocused on requirements, constraints, and acceptance criteria. - Lightweight tasks can remain PRD-only.
- For complex tasks, add
design.mdfor technical design andimplement.mdfor execution planning beforetask.py start.