From 4f3f761d0aa00cea8bbd5b61ae9ba5b6ca5edac9 Mon Sep 17 00:00:00 2001 From: shunli Date: Sat, 8 Aug 2026 22:57:52 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A7=84=E5=88=92(08-08-production-authenticat?= =?UTF-8?q?ed-readiness):=20=E9=94=81=E5=AE=9A=E7=94=9F=E4=BA=A7=E7=99=BB?= =?UTF-8?q?=E5=BD=95=E6=80=81=E5=8F=AA=E8=AF=BB=E9=AA=8C=E6=94=B6=E9=97=A8?= =?UTF-8?q?=E7=A6=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../check.jsonl | 1 + .../design.md | 129 ++++++++++++++++ .../implement.jsonl | 1 + .../implement.md | 83 ++++++++++ .../prd.md | 144 ++++++++++++++++++ .../task.json | 36 +++++ 6 files changed, 394 insertions(+) create mode 100644 .trellis/tasks/08-08-production-authenticated-readiness/check.jsonl create mode 100644 .trellis/tasks/08-08-production-authenticated-readiness/design.md create mode 100644 .trellis/tasks/08-08-production-authenticated-readiness/implement.jsonl create mode 100644 .trellis/tasks/08-08-production-authenticated-readiness/implement.md create mode 100644 .trellis/tasks/08-08-production-authenticated-readiness/prd.md create mode 100644 .trellis/tasks/08-08-production-authenticated-readiness/task.json diff --git a/.trellis/tasks/08-08-production-authenticated-readiness/check.jsonl b/.trellis/tasks/08-08-production-authenticated-readiness/check.jsonl new file mode 100644 index 00000000..9dd3234a --- /dev/null +++ b/.trellis/tasks/08-08-production-authenticated-readiness/check.jsonl @@ -0,0 +1 @@ +{"_example": "Fill with {\"file\": \"\", \"reason\": \"\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."} diff --git a/.trellis/tasks/08-08-production-authenticated-readiness/design.md b/.trellis/tasks/08-08-production-authenticated-readiness/design.md new file mode 100644 index 00000000..8acad701 --- /dev/null +++ b/.trellis/tasks/08-08-production-authenticated-readiness/design.md @@ -0,0 +1,129 @@ +# 生产登录态只读验收:设计说明 + +## 1. 设计边界 + +本任务是生产账号诊断与登录态只读验收,不是账号修复或产品开发。前一部署任务已经证明产品候选、migration、匿名鉴权和公网基础 readiness;本任务只补齐因仓库外凭据 `401` 而缺失的登录态证据。 + +核心原则是“先读取状态,再决定是否允许一次登录”。账号失败计数和锁定属于生产安全状态,不能拿登录端点当探针。合同终止是不可逆写入,浏览器验收必须在输入和网络两层同时保证不会提交。 + +## 2. 状态序列 + +```text +生产身份 / 已部署 SHA / 65 migration(只读) + -> rollback window PRE(只读) + -> admin/employee identifier 登录语义 + 账号状态(BEGIN READ ONLY) + -> 分流:identifier / locked / inactive-role / password credential + -> 仅当合法新鲜凭据 + 全部门禁 PASS:每角色最多一次登录 + -> admin 合同详情只读 + terminate POST=0 + -> employee 权限负向 + -> rollback window POST(只读) + -> 只读 reviewer / 结论 +``` + +任何箭头失败都停止。`PRE` 与 `POST` 快照即使中间步骤 `PAUSED` 也都要执行;这样能区分本任务未写入与同期真实用户关闭窗口。 + +## 3. 账号只读诊断 + +### 3.1 查询合同 + +生产查询使用 `BEGIN READ ONLY`,只处理 admin/employee 两个 role key。identifier 从受控进程变量读取,在数据库内与 `email`、`phone` 做精确比较;不能因为现有 dry-run helper 支持 `employee_no` 就把员工编号视为可登录 identifier。 + +稳定输出模型: + +```text +roleKey=admin|employee +matchedBy=email|phone|none +active=true|false +archived=true|false +locked=true|false +failureCount=<非负整数> +expectedRoleReady=true|false +``` + +不输出 identifier、员工编号、邮箱、手机号、锁定时间、密码 hash、角色明细或原始行。查询目标多于一条、事务不是 read-only、输出字段异常都 fail-closed。 + +### 3.2 分流矩阵 + +| 只读结果 | 结论 | 动作 | +| --- | --- | --- | +| `matchedBy=none` | secret 中 identifier 过期或不符合 email/phone 登录合同 | `PAUSED`;账号持有人从合法来源恢复仓库外凭据 | +| active/role 不满足 | 账号或授权事实已变化 | `PAUSED`;另设账号/权限 task | +| `locked=true` | 账号当前锁定 | `PAUSED`;不试登录、不解锁 | +| 匹配、active、role 正确、未锁定,既有结果为 401 | 按当前 auth 分支可判定仓库外密码不匹配 | `PAUSED`;密码变更另行批准 | +| 合法新鲜凭据已恢复且全部健康 | 允许有限登录验证 | 每角色最多一次,单 worker、零 retry | + +账号不存在与密码错误在 HTTP 上同为 401;只有数据库只读诊断能安全拆分。若一次获准登录仍失败,立即重查 failure count/locked,记录差值并停止。 + +## 4. 回滚窗口双快照 + +termination migration 已 applied,因此必须真实查询: + +```sql +SELECT count(*) FROM contract_registry WHERE status = 'terminated'; +``` + +查询异常不可 catch 后默认 `OPEN`。两次快照使用同一目标、同一查询合同和同一输出格式: + +- count = 0:`rollbackWindow=OPEN` +- count > 0:`rollbackWindow=CLOSED` +- PRE OPEN / POST CLOSED 且 terminate request count = 0:并发真实用户已产生首条终止记录;记录窗口永久关闭,不归因于本任务 +- PRE CLOSED:全部验收仍只读,旧应用回退从一开始就不可用 + +## 5. 登录预算与浏览器控制 + +### 5.1 登录预算 + +- 执行前确认仓库外凭据是新鲜、合法来源,不靠修改生产账号获得。 +- 管理员、员工各最多一次;Playwright 固定 `--workers=1 --retries=0 --reporter=list`。 +- 禁止复用默认 CI retry;一次失败即结束该角色路径。 +- 失败后重新运行账号只读查询,证明是否增加失败计数或触发锁定。 + +### 5.2 合同详情安全断言 + +`draft/archived` 不显示终止面板,无法承担“误点后服务端 409”的兜底。对已有 `active/expired` 合同采用以下边界: + +1. 打开合同详情并验证自然中文状态和只读摘要;证据不采集正文或敏感字段。 +2. 注册请求监听,统计匹配终止端点的 `POST`;初始为 0。 +3. 验证“终止合同”面板、至少 10 字提示和 destructive 按钮存在。 +4. 终止原因保持空值或不足 10 字;断言按钮 disabled。 +5. 不点击按钮,不打开确认层,不调用 API;结束时终止 `POST` 仍为 0。 + +优先使用 Playwright CLI 的受控会话;若 CLI 命令不能可靠表达请求监听和 disabled 断言,则由执行者本人做更窄人工检查,只验证入口与按钮状态,不打开确认层。不得为本任务新增会提交数据的 spec。 + +### 5.3 权限与数据分支 + +- 管理员正向:能进入合同台账/详情。 +- 员工负向:合同管理入口不可达或服务端返回既定无权结果;不得扩大权限。 +- archived 样本存在:只读验证字段锁定;不存在则 `PAUSED`。 +- terminated 样本存在:只读验证中文标签与终止事实;不存在则 `PAUSED`,绝不制造样本。 + +## 6. 失败与权限边界 + +| 阶段 | 失败 | 处理 | +| --- | --- | --- | +| 身份/SHA/migration | 目标或版本不符 | 停止,不登录 | +| rollback PRE/POST | 查询失败或不可解析 | fail-closed,不推断 OPEN | +| identifier | 不匹配 email/phone 或不唯一 | `PAUSED`,恢复仓库外 secret | +| 状态/角色/锁定 | 任一不健康 | `PAUSED`,另行批准账号治理 | +| 登录 | 首次失败 | 停止,重查失败计数/锁定,不重试 | +| 管理员 UI | 数据缺失或只读断言失败 | `PAUSED/FAIL`,不造数据、不改产品 | +| 员工负向 | 权限扩大或结果不符 | `FAIL`,不改权限推进 | +| terminate 请求 | 计数不为 0 | 紧急停止并复核 POST 确切结果与窗口;不得再次操作 | + +## 7. 证据与隐私 + +原始查询和浏览器会话只在受控现场存在。提交材料保存稳定枚举、布尔、计数、SHA、HTTP status 和相对 evidence 引用;不保存生产 identity 明文、账号、凭据、cookie、合同正文、相对方、金额、终止原因或 payload。 + +截图不是必需证据;若必须截图,应使用无业务正文的局部裁切或脱敏摘要。任一敏感扫描命中都先处理证据,不降低 scanner。 + +## 8. 只读 review 与 readiness 结论 + +本任务涉及生产认证、权限和不可逆状态机边界,执行后必须启动只读 reviewer。reviewer 不修改代码、账号、权限、数据库或证据,只核对: + +- 登录前诊断确实先于登录; +- 每角色尝试次数与失败后复查; +- 终止原因/确认层/POST 均未越界; +- rollback window 前后快照及变化解释; +- 证据脱敏与最终 readiness 用语。 + +只要必需登录态 AC 仍 `PAUSED`,最终结论只能是“已部署但登录态验收未完成”,不能告知用户可以测试。 diff --git a/.trellis/tasks/08-08-production-authenticated-readiness/implement.jsonl b/.trellis/tasks/08-08-production-authenticated-readiness/implement.jsonl new file mode 100644 index 00000000..9dd3234a --- /dev/null +++ b/.trellis/tasks/08-08-production-authenticated-readiness/implement.jsonl @@ -0,0 +1 @@ +{"_example": "Fill with {\"file\": \"\", \"reason\": \"\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."} diff --git a/.trellis/tasks/08-08-production-authenticated-readiness/implement.md b/.trellis/tasks/08-08-production-authenticated-readiness/implement.md new file mode 100644 index 00000000..99d7d831 --- /dev/null +++ b/.trellis/tasks/08-08-production-authenticated-readiness/implement.md @@ -0,0 +1,83 @@ +# 生产登录态只读验收:执行计划与证据 + +> 当前状态:IN PROGRESS。规划已通过用户只读复核并启动;生产只读引导首轮在发布来源门禁处停止,尚未查询生产账号或尝试登录。 + +## 1. 规划确认 + +- [x] 前一部署任务证据确认:产品候选 `0224ad8`,65 条 migration applied,部署结束时 `rollbackWindow=OPEN`、`terminatedRows=0`,管理员/员工凭据均 401。 +- [x] auth 路由确认:identifier 仅接受 email/phone;401、403、423 分别覆盖不存在/密码错误、停用、锁定;失败会更新计数和锁定状态。 +- [x] 账号 dry-run helper 确认:可借鉴 `BEGIN READ ONLY` 与脱敏输出,但 employee_no 匹配宽于真实登录合同,`--apply` 明确排除。 +- [x] 终止入口确认:仅 active/expired 显示;draft/archived 无法作为 UI 误点的服务端状态机兜底。 +- [x] 安全验收选择:不打开确认层,只检查终止按钮初始禁用,并记录 terminate POST=0。 +- [x] 回滚窗口选择:验收前后双快照,查询失败不得默认 OPEN。 + +## 2. 启动门禁(规划批准后) + +- [x] 用户完成规划只读复核并明确批准实施。 +- [x] 运行 `task.py start`,当前 task 状态为 `in_progress`。 +- [x] 确认生产只读授权仍有效;本任务没有账号/权限/合同写入授权。 +- [x] 确认已部署产品 SHA 基线为 `0224ad8`,不以当前 HEAD 替代。 +- [ ] 确认 `command -v npx`;浏览器只能使用单 worker、零 retry。 +- [ ] 创建本轮独立、gitignored 的 evidence 目录;不得复用历史 PASS。 + +## 3. 执行顺序 + +### Phase A:生产只读基线 + +- [ ] 复用受控身份引导和 read-only preflight,确认唯一生产目标。 +- [ ] 人工阅读脱敏后的 migration status:65 条 applied、pending/failed/diverged 均无异常。 +- [ ] 核对运行/公网 build SHA 为已部署产品候选。 +- [ ] 查询 PRE `terminatedRows`,记录 `rollbackWindow=OPEN|CLOSED` 与时间顺序。 + +### Phase B:登录前账号诊断 + +- [ ] 使用 `BEGIN READ ONLY` 查询 admin;输出仅含 role key、matchedBy、active/archived/locked、failureCount、expectedRoleReady。 +- [ ] 使用同一查询合同诊断 employee。 +- [ ] 人工确认 identifier 只按 email/phone 登录语义匹配,未把 employee_no 当作可登录身份。 +- [ ] 按 PRD R3 分流;如需密码修改、解锁、激活或角色变更,立即标 `PAUSED` 并停止登录。 +- [ ] 记录本轮到此为止的登录尝试数;预期仍为 0。 + +### Phase C:有限登录(仅凭据合法恢复后) + +- [ ] 从受控 secret store 在同一进程注入新鲜凭据,不回显、不落盘。 +- [ ] admin 最多一次登录;失败立即停止并复查 failureCount/locked。 +- [ ] employee 最多一次登录;失败立即停止并复查 failureCount/locked。 +- [ ] 记录每角色 attempts、HTTP 结果、前后失败计数;禁止第二次尝试。 + +### Phase D:登录态只读验收 + +- [ ] 管理员打开合同台账与现有合同详情,验证自然中文状态和只读摘要。 +- [ ] 如有 active/expired 样本:监听 terminate POST,验证终止面板、10 字提示、原因不足 10 字、按钮 disabled;不打开确认层,结束时 POST=0。 +- [ ] 员工执行合同权限负向路径;未扩大权限、未删除断言。 +- [ ] 如有 archived 样本,只读验证字段锁定;无样本记 `PAUSED`。 +- [ ] 如已有 terminated 样本,只读验证中文标签与终止事实;无样本记 `PAUSED`,不得创建。 + +### Phase E:窗口复核与收口 + +- [ ] 无论前序 PASS/PAUSED,查询 POST `terminatedRows` 和窗口状态。 +- [ ] 对比 PRE/POST;若 OPEN→CLOSED 且 terminate POST=0,记录真实用户同期使用及只能 roll-forward/hotfix。 +- [ ] 汇总生产账号/权限/合同写入计数;本任务预期均为 0。 +- [ ] 启动只读 reviewer,reviewer 不修改代码或生产状态。 +- [ ] 按实际证据更新 PRD AC;不得把缺失样本或凭据阻塞勾成通过。 + +## 4. 预期证据表 + +| 证据 | 允许记录 | 禁止记录 | +| --- | --- | --- | +| 版本/迁移 | 候选 SHA、applied/pending/异常计数 | 数据库名、连接串、SSH identity | +| 账号诊断 | role key、matchedBy、布尔、失败计数 | identifier、邮箱、手机号、员工编号、hash、锁定时间原值 | +| 登录 | 角色、attempts、HTTP 状态 | 密码、cookie、token、request payload | +| 合同 UI | 中文状态、按钮 disabled、POST 计数 | 合同正文、相对方、金额、终止原因 | +| 窗口 | PRE/POST count、OPEN/CLOSED、顺序 | 原始数据库输出或业务行 | + +## 5. 规划门禁结果 + +- [x] `python3 ./.trellis/scripts/task.py validate 08-08-production-authenticated-readiness`:PASS。 +- [x] `node scripts/qa/check-process-contract.mjs --baseline --json`:PASS,0 errors / 0 warnings。 +- [x] `node scripts/qa/check-trellis-governance.mjs --json`:PASS,0 errors / 0 warnings。 +- [x] `node scripts/qa/check-sensitive-content.mjs`:PASS,0 findings。 +- [x] `git diff --check`:PASS。 + +## 6. 当前结论 + +规划已收敛并获批启动。首轮生产只读引导完成 13/13 聚合体检后,在本地发布来源校验处 fail-closed;原因是已批准的任务规划尚未形成 clean/published commit。该轮未进入生产 migration status、窗口查询、账号查询或登录。先提交并推送已复核规划,再从完整只读门禁重跑,不使用 override。 diff --git a/.trellis/tasks/08-08-production-authenticated-readiness/prd.md b/.trellis/tasks/08-08-production-authenticated-readiness/prd.md new file mode 100644 index 00000000..69af5308 --- /dev/null +++ b/.trellis/tasks/08-08-production-authenticated-readiness/prd.md @@ -0,0 +1,144 @@ +# 生产登录态只读验收 + +## 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`,65 条 migration 仍全部 applied,未触发部署或 schema 写入。 +- [ ] AC2:任何登录前已对管理员、员工分别完成只读状态诊断;证据包含 `matchedBy`、active/archived/locked、失败计数和期望角色是否就绪,不含账号或凭据明文。 +- [ ] AC3:账号诊断已按 identifier 失配、密码凭据失效、锁定、停用/角色漂移之一形成可解释结论;凡需要修改生产账号状态的分支均 `PAUSED` 并另行批准,没有用重复登录试错推进。 +- [ ] AC4:仅在合法凭据恢复且所有登录前门禁通过时,管理员、员工各执行不超过一次登录;`workers=1`、`retries=0`,任一失败后复查失败计数/锁定状态且没有第二次尝试。若凭据未恢复,本 AC 保持 `PAUSED`,不得勾选。 +- [ ] AC5:管理员已只读打开合同详情;对既有 `active/expired` 合同仅验证终止面板、10 字提示和按钮初始禁用,终止原因保持不足 10 字,确认层未打开,终止端点 `POST` 计数为 0。 +- [ ] AC6:员工合同权限负向路径通过,未扩大权限或删除断言;现有 archived/terminated 样本的只读检查按数据实际情况记录 PASS 或 `PAUSED`,未创建数据补齐样本。 +- [ ] AC7:验收前后均记录 `terminatedRows` 与 `rollbackWindow=OPEN|CLOSED`;若窗口变化,已明确记录真实用户使用与只能 roll-forward/hotfix 的后果。本任务真实合同终止写入计数为 0。 +- [ ] AC8:只读 reviewer 已复核账号诊断、登录尝试计数、终止请求计数、窗口双快照和证据脱敏;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-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`. diff --git a/.trellis/tasks/08-08-production-authenticated-readiness/task.json b/.trellis/tasks/08-08-production-authenticated-readiness/task.json new file mode 100644 index 00000000..e58794c6 --- /dev/null +++ b/.trellis/tasks/08-08-production-authenticated-readiness/task.json @@ -0,0 +1,36 @@ +{ + "id": "production-authenticated-readiness", + "name": "production-authenticated-readiness", + "title": "生产登录态只读验收", + "description": "诊断生产管理员与员工验收凭据 401 的原因,在不修改账号、权限或业务数据的前提下恢复最小登录态只读验收;登录前先查锁定/失败计数和 identifier 登录语义,合同终止只验证到初始禁用按钮,并在验收前后复核回滚窗口。", + "status": "in_progress", + "dev_type": null, + "scope": "production-auth-diagnosis-and-authenticated-readonly-acceptance", + "package": null, + "priority": "P1", + "creator": "shunli", + "assignee": "shunli", + "createdAt": "2026-08-08", + "completedAt": null, + "branch": null, + "base_branch": "main", + "worktree_path": null, + "commit": null, + "pr_url": null, + "subtasks": [], + "children": [], + "parent": null, + "relatedFiles": [ + "server/src/routes/auth.js", + "server/src/scripts/repair-oa-only-smoke-accounts.js", + "server/prisma/schema.prisma", + "server/src/routes/contractsRegistry.js", + "client/src/pages/ContractRegistryWorkspacePage.tsx", + "client/tests/e2e/live-smoke.spec.ts", + "scripts/qa/run-live-smoke-readiness.sh", + "deploy/trialrun/production-preflight-remote.sh", + ".trellis/spec/hyb-oa/deployment-trialrun.md" + ], + "notes": "仅规划,尚未启动实施。前一部署任务已完成 65 条迁移与匿名鉴权,但管理员/员工验收凭据均返回 401;本任务禁止用重复登录试错诊断,禁止修改生产账号或产生真实合同终止写入。", + "meta": {} +} \ No newline at end of file