135 行
16 KiB
Markdown
135 行
16 KiB
Markdown
# 生产登录态只读验收:执行计划与证据
|
||
|
||
> 收口状态:AUTH ROLES PASS / EMPLOYEE NEGATIVE PASS / CONTRACT UI PAUSED。管理员与员工认证均已通过,员工合同管理权限负向已在生产登录态验证;合同 UI 因生产零合同转为事件触发的独立只读补验,不阻塞本任务生命周期归档。
|
||
|
||
## 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 替代。
|
||
- [x] `command -v npx` 可用;若凭据恢复,浏览器只能使用单 worker、零 retry。
|
||
- [x] 生产 preflight 使用两个独立时间戳报告引用;未复用历史 PASS,任务文档只记脱敏摘要。
|
||
|
||
## 3. 执行顺序
|
||
|
||
### Phase A:生产只读基线
|
||
|
||
- [x] 复用受控身份引导和 read-only preflight,精确身份组与 13/13 聚合体检通过。
|
||
- [x] migration reconciliation 为 65 条 applied、pending=none,未见 failed/diverged 异常;只读 wrapper 未进入 `deploy.sh`。
|
||
- [x] 公网 build 短标识为 `0224ad83d7f8`,与已部署产品候选匹配;未把当前文档 HEAD 当作产品 SHA。
|
||
- [x] PRE 为 `terminatedRows=0`、`rollbackWindow=OPEN`、`basis=terminated_status_count`。
|
||
|
||
### Phase B:登录前账号诊断
|
||
|
||
- [x] 使用同一 `BEGIN READ ONLY` 事务查询 admin/employee;查询合同覆盖 role key、matchedBy、active/archived/locked、failureCount、expectedRoleReady。
|
||
- [x] 两个角色均返回 `matchedBy=none`;由于没有匹配到账号,active/archived/locked、failureCount、expectedRoleReady 均记为 `N/A`,不把占位值当作生产账号事实;未输出 identifier、账号、锁定时间或角色明细。
|
||
- [x] 查询只按 email/phone 精确匹配,未把 employee_no 当作可登录身份。
|
||
- [x] 按 PRD R3 分流为仓库外 identifier 过期或不符合现行登录合同;标 `PAUSED`,没有进入密码、解锁、激活或授权写入。
|
||
- [x] 登录尝试数:admin=0、employee=0。
|
||
|
||
经用户确认继续只读定性后,补充完成:
|
||
|
||
- [x] 旧 identifier 对生产 `employee_no` 的精确匹配也均为 false,排除“只是把员工编号误当登录名”。
|
||
- [x] 在远端 `BEGIN READ ONLY` 事务内按角色筛选活跃候选,并用现有仓库外密码与候选 hash 离线比较;密码与 hash 均未离开远端内存。
|
||
- [x] admin:活跃候选 1、正确 identifier 可唯一解析为 email、locked=false、failureCount=0、passwordMatchCount=0。
|
||
- [x] employee:非管理员 employee 候选 5、passwordMatchCount=0,无法在不猜人的情况下选定验收账号。
|
||
- [x] 未把正确管理员 identifier 写入 task、对话或仓库,也未修改仓库外 secret;密码重置/员工账号选择转为独立批准事项。
|
||
|
||
员工负向目标资格补充诊断:
|
||
|
||
- [x] 重新通过生产 origin 与 systemd contract 身份门禁后,在运行容器内用 `BEGIN READ ONLY` 查询 5 个活跃非管理员候选;事务只读状态为 `on`,未调用登录端点。
|
||
- [x] 5 个候选的登录 identifier 均已就绪、当前锁定数为 0、失败计数均为 0;其中 1 个具备合同管理权限,3 个仍要求首次改密。
|
||
- [x] 以“可进入只读合同工作区、无合同管理权限、无需首次改密、未锁定”为资格合同,恰好 1 个候选通过,员工负向目标已从 5 个收敛为唯一 1 个;未输出 identifier、账号、角色明细或数据库 ID。
|
||
- [x] 使用运行容器内 `repair-oa-only-smoke-accounts.js` 的既有 smoke 安全判据对唯一目标分类:账号自身命名模式命中、私有 smoke allowlist 未命中,结论为专用 OA-only smoke 账号,不是真人员工;未输出命中的原始字段。
|
||
- [x] 本次未注入或比较任何密码,不把此前 `passwordMatchCount=0` 伪装为本轮现场结果;当前事实是目标已唯一,但仍缺少已确认有效的仓库外凭据,employee attempts 继续为 0。
|
||
- [x] 资格诊断 PRE/POST 与 smoke 分类后的最终 POST 均为 `terminatedRows=0`、`rollbackWindow=OPEN`;未产生账号、权限、合同、session、审计或失败计数写入。
|
||
|
||
自助密码重置可用性只读核对:
|
||
|
||
- [x] 运行容器 `EMAIL_ENABLED=false`;公网 `/api/auth/capabilities` 独立返回 `emailDeliveryReady=false`、原因 `email_disabled`。
|
||
- [x] 唯一活跃管理员有 email,`passwordResetLocked=false`、`passwordResetRequestCount=0`;未调用 `/forgot-password`,未生成验证码、增加请求计数或触发邮件。
|
||
- [x] `~/.hyb-oa/smtp-live.env` 的本地存在不等同于生产邮件通道启用;启用生产邮件需要配置变更与重启,超出当前只读授权。
|
||
- [x] 选择更窄的后续路径:如获单独批准,优先使用仓库既有 `reset-active-admin-password.js` 的 exact-admin、bcrypt、事务与审计合同,不手写 hash SQL,不修改角色、员工启用状态或 email。
|
||
- [x] 已识别脚本完整副作用:设置 `must_change_password`、清理认证失败/锁定状态、递增 token version、撤销活跃会话、失效密码重置/邮件登录验证码并写不可删除的审计。批准请求必须逐项披露。
|
||
- [x] 如另行批准,旧 hash 仅能在受控生产现场以最小权限短期保存,作为有限凭据恢复材料;不进入仓库、task、普通日志或对话,不构成上述副作用的完整回滚,并须在验收/恢复窗口关闭后立即销毁。
|
||
- [x] 邮件能力与 break-glass 建议只读 reviewer 首轮 `PASS_WITH_FINDINGS` 的两项证据边界已修订,最终复查为 `PASS`;reviewer 未修改文件或生产状态。
|
||
|
||
执行备注:账号诊断前两次远端包装探针分别在脚本解码和 Compose 必填候选标识处未产生约定查询 marker,均不计为查询成功;修正为注入已验证产品候选完整/12 位标识后,唯一有效诊断事务返回 `transaction=read_only`。这些失败均先于数据库事务和登录。
|
||
|
||
### Phase C:有限登录(仅凭据合法恢复后)
|
||
|
||
- [x] 管理员凭据来源已切换:来自已归档 `08-09-production-admin-break-glass-reset` 的受控 break-glass 恢复和首次强制改密,不是本任务最初诊断为失效的原始 secret;值不进入任务证据。
|
||
- [x] 员工凭据来源已切换:来自 `08-10-production-employee-smoke-break-glass-reset` 的受控 break-glass 恢复和首次强制改密,不是本任务最初诊断为失效的原始 secret;最终 secret 已就绪且值不进入任务证据。
|
||
- [x] 08-10 最终交接证明目标仍唯一、`rolesExact=true`,`*`、`project.manage`、`project.view_all`、`system.settings` 四项权限全 false;该结论只解除凭据阻塞,不替代本任务的生产登录态负向验收。
|
||
- [x] admin 只执行一次登录、零 retry,HTTP 200 并进入 `/portal`;写后只读复核为 failureCount=0、locked=false、近 15 分钟 login success=1、login failure=0。
|
||
- [x] employee 使用 08-10 恢复后的最终凭据登录一次、零 retry,HTTP 200 并进入 `/portal`;没有用原始失效 secret 试探登录。
|
||
- [x] admin attempts=1;未发生第二次登录。浏览器在合同台账阶段 fail-closed 后关闭,未到达正常 logout;active sessions 从恢复后的 1 变为 2,新增会话留待自然过期,本任务未获直接数据库撤销授权。
|
||
- [x] employee attempts=1、login POST=1,浏览器 context 已清理;没有第二次尝试或主动 logout 写入,新会话留待自然过期且浏览器 token 已不可用。
|
||
- [x] 已记录 admin attempts=1、employee attempts=1;两个角色均没有第二次尝试。
|
||
|
||
### Phase D:登录态只读验收
|
||
|
||
- [ ] PARTIAL:admin 登录成功;浏览器进入合同台账阶段后因无可用合同样本停止,未进入详情。生产数据库只读计数确认合同总数为 0,active/expired/draft/archived/terminated 均为 0。
|
||
- [ ] PAUSED:无 active/expired 样本,无法进入终止面板;未填写原因、未打开确认层、terminate POST=0、全部合同 mutation request=0。
|
||
- [x] 员工负向 PASS:登录后可只读到达合同列表与“合同”标题,管理入口“新增合同”不存在;直达 `/contracts-registry/new` 被前端权限门重定向到 `/403` 并显示“无权限访问”。
|
||
- [x] 员工负向网络证据:合同 GET=2、合同 mutation=0、全部非认证 mutation=0;未进入详情、未发送创建/编辑/归档/续约/终止/导出请求,未切换管理员身份。
|
||
- [x] 证据边界:本轮直接验证的是 UI 隐藏、前端路由拒绝和零业务写入;“已登录但无合同管理权限的用户调用服务端写端点会返回 403”未在生产直接触发,因为现行批准合同禁止发送业务 mutation。该结论由匿名鉴权负向 3/3、已发布候选中五个合同写端点均经 `canManageContractRegistry` 守卫的源码复核,以及运行 SHA 与候选一致三层证据支撑,不记为生产实测。
|
||
- [ ] PAUSED:archived 样本数为 0,无法验证字段锁定;未创建数据。
|
||
- [ ] PAUSED:terminated 样本数为 0,无法验证“已终止”标签与终止事实区;未制造样本。
|
||
|
||
### Phase E:窗口复核与收口
|
||
|
||
- [x] 首次账号分流、扩展账号诊断、邮件能力诊断后均查询 POST;连同初始 PRE 共四次,均为 `terminatedRows=0`、`rollbackWindow=OPEN`、`basis=terminated_status_count`。
|
||
- [x] 四次窗口证据均为 OPEN/0,未发现同期真实用户关闭窗口。
|
||
- [x] 凭据恢复后的管理员验收 PRE/POST 再次均为 `terminatedRows=0`、`rollbackWindow=OPEN`、`basis=terminated_status_count`;本任务累计六次快照均 OPEN/0。
|
||
- [x] 本轮浏览器 admin login=1/200;terminate POST=0、合同 mutation=0。runner 的状态词基线曾写成“已到期”而产品映射为“已过期”,但生产合同总数为 0,状态循环未执行,该观察不是本轮停止原因;依零 retry 合同未重跑。
|
||
- [x] 管理员阶段当时汇总:admin attempts=1、employee attempts=0;terminate POST=0、合同 mutation=0;未执行账号修复、权限、合同或 schema 写入。成功登录按产品合同创建 1 个新 session 并写登录审计,浏览器未到达 logout;后续 employee 结果见本轮员工负向记录。
|
||
- [x] 扩展诊断只读 reviewer 首轮 `PASS_WITH_FINDINGS` 要求收窄员工密码与 UI PAUSED 措辞,修订后最终复查为 `PASS`;浏览器验收收口复核另发现旧汇总把成功登录副作用误写成“写入 0”,修正为 admin attempts=1、session 1→2 与登录审计后,最终复核为 `PASS`。reviewer 未修改文件、代码或生产状态。
|
||
- [x] 管理员阶段已按当时证据更新 PRD AC:管理员认证通过,员工与无样本 UI 分支保持 `PARTIAL/PAUSED`;后续员工凭据恢复与负向 PASS 已另行追加,合同无样本分支继续 `PAUSED`。
|
||
- [x] 已按 `trellis-update-spec` 将零 retry runner 的细粒度阶段、状态词源码基线、无样本独立分类和登录副作用对账写入现有 `.trellis/spec/hyb-oa/deployment-trialrun.md`,未新建规约文件。
|
||
- [x] 已为合同 UI `PAUSED` 写死事件触发条件:生产出现首条 `contract_registry` 记录后启动只读补验,不按日期等待、不制造样本,也不把记录出现视为终止授权。仓库事实源显示合同台账原已纳入首批真实使用且历史存在测试数据,因此当前零合同只记为待负责人确认的运营事实,不擅自归因为“模块尚未启用”。
|
||
- [x] 员工负向 PRE/POST 均通过已发布候选的生产 identity、13/13 聚合体检与 migration pending=none,并分别得到 `rollbackWindow=OPEN`、`terminatedRows=0`、`basis=terminated_status_count`;本任务有效窗口快照累计十一轮均 OPEN/0。
|
||
- [x] 会话残留决策已保留:08-10 交接时记录的 `activeSessions=1` 未另行撤销,选择让其自然过期,因为撤销需要新的生产认证写入批准且浏览器 token 已销毁;本轮员工负向登录又按产品合同创建一次会话,context/token 同样已销毁,未为追求计数归零追加 logout 或数据库写入。
|
||
- [x] 当前分支含未推送 task 证据,首次 PRE 在 13/13 后被 provenance 拒绝;detached 候选又因无 upstream 被拒绝。两次均未进入窗口查询。最终 PRE/POST 均从临时、跟踪 `origin/main` 的 clean worktree 执行,remoteVerified 且未使用 unpublished override;临时 worktree/branch 已移除。
|
||
- [x] 员工负向只读 reviewer 交叉核对已发布候选:新增路由与新增按钮均要求三项管理权限、详情 mutation 继续受同一权限集合限制、服务端五个合同写端点均由 `canManageContractRegistry` 守卫;结合生产 403 与 mutation=0,结论 PASS。首版 reviewer 因错误要求权限数组源码顺序一致而自我失败,修正为分别匹配实际等价集合后通过;未修改代码或生产状态。
|
||
|
||
## 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。
|
||
|
||
执行后复跑同一组门禁仍 PASS;docs registry audit 为 0 errors / 1 个既有“22 个未注册 docs 文件”warning,本任务未新增或修改 `docs/` 文件。
|
||
|
||
## 6. 当前结论
|
||
|
||
生产只读基线与账号诊断已执行,独立任务 `08-09-production-admin-break-glass-reset` 与 `08-10-production-employee-smoke-break-glass-reset` 分别恢复管理员和专用 employee smoke 的认证。管理员与员工本轮均只登录一次、HTTP 200、零 retry;员工登录态下合同列表可读但新增入口不可见,直达新增路由为 403,合同及全部非认证 mutation 均为 0,负向权限验证 PASS。生产合同总数仍为 0,无法验证详情、终止入口初始禁用、归档锁定或已终止标签,未制造样本;一旦出现首条记录即触发只读补验。员工负向 PRE/POST 后,本任务累计十一次有效窗口快照全部 OPEN/0。当前结论为两类认证与员工负向通过、合同 UI 因无数据 `PAUSED`;仍不得告知用户可以测试合同终止功能。
|
||
|
||
## 7. 生命周期收口
|
||
|
||
- 2026-08-23 按用户确认的总体收口顺序关闭本任务。适用的认证、员工权限负向、版本/迁移、mutation 计数和回滚窗口证据均已完成。
|
||
- Phase D 中依赖合同样本的四项保持未勾选,准确表示生产零合同导致的 `PARTIAL/PAUSED`,不补造日期、截图、合同或终止证据。
|
||
- 下一次只读验收由“生产出现首条真实合同记录”触发并新建 Trellis task;本任务归档不授权终止、归档、编辑、创建合同,也不授权账号或权限写入。
|