归档(08-08-production-authenticated-readiness): 关闭生产登录态只读验收
这个提交包含在:
@@ -0,0 +1 @@
|
||||
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. 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."}
|
||||
@@ -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`,最终结论只能是“已部署但登录态验收未完成”,不能告知用户可以测试。
|
||||
@@ -0,0 +1 @@
|
||||
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. 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."}
|
||||
@@ -0,0 +1,134 @@
|
||||
# 生产登录态只读验收:执行计划与证据
|
||||
|
||||
> 收口状态: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;本任务归档不授权终止、归档、编辑、创建合同,也不授权账号或权限写入。
|
||||
@@ -0,0 +1,154 @@
|
||||
# 生产登录态只读验收
|
||||
|
||||
## 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
|
||||
|
||||
- [x] AC1:生产运行/公网 SHA 对应已部署产品候选 `0224ad8`(短标识 `0224ad83d7f8`);65 条 migration 仍全部 applied、pending=none,未触发部署或 schema 写入。
|
||||
- [x] AC2:任何登录前已对管理员、员工完成同一 `BEGIN READ ONLY` 查询合同;查询设计覆盖 `matchedBy`、active/archived/locked、失败计数和期望角色是否就绪。两个 identifier 实际均 `matchedBy=none`,故账号属性记为 `N/A` 而非生产事实,不含账号或凭据明文。
|
||||
- [x] AC3:两个仓库外 identifier 均不匹配 email/phone/employee_no。只读角色候选与远端内存密码 hash 比对进一步证明:唯一活跃管理员可解析为 email、未锁定、失败计数为 0,但原管理员密码不匹配;员工有 5 个活跃非管理员候选,原员工验收密码对这些候选的匹配数为 0。后续只读资格诊断将员工目标收敛为唯一 1 个:其登录 identifier 已就绪、未锁定、失败计数为 0、无需首次改密、可进入只读合同工作区且不具备合同管理权限;运行容器内既有 smoke 命名判据将其分类为专用 OA-only smoke 账号,未依赖私有 allowlist。本次没有注入或比较密码。登录尝试保持 0,未输出 identifier/hash/角色明细。
|
||||
- [x] 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,确认层未打开,未制造合同。
|
||||
- [x] AC6:员工负向目标已通过只读资格诊断唯一确定,并确认是专用 OA-only smoke 账号而非真人员工;最终凭据来自 08-10 break-glass,角色精确与四项权限前提未变。生产登录后合同列表可读、管理入口“新增合同”不可见,直达新增路由进入 403;合同读取请求 2、合同 mutation 与全部非认证 mutation 均为 0。archived/terminated 样本仍为 0,相应可选 UI 检查保持 `PAUSED`,未扩大权限、修改断言或制造数据。
|
||||
- [x] AC7:初始、登录前停止后、扩展账号诊断后、邮件能力诊断后、管理员验收 PRE/POST、员工资格诊断 PRE/POST、smoke 分类最终 POST 以及员工负向 PRE/POST 共十一次有效窗口证据均为 `terminatedRows=0`、`rollbackWindow=OPEN`、`basis=terminated_status_count`;窗口未变化,本任务真实合同终止写入计数为 0。
|
||||
- [x] AC8:邮件能力与 break-glass 建议审查、浏览器验收收口审查的 findings 均已按证据边界修订,最终只读复查为 `PASS`;task validate、process contract、governance、sensitive scan、docs registry audit 与 diff check 已复跑,docs audit 仅保留既有 22 个未注册文件 warning。
|
||||
- [x] 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`.
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"id": "production-authenticated-readiness",
|
||||
"name": "production-authenticated-readiness",
|
||||
"title": "生产登录态只读验收",
|
||||
"description": "诊断生产管理员与员工验收凭据 401 的原因,在不修改账号、权限或业务数据的前提下恢复最小登录态只读验收;登录前先查锁定/失败计数和 identifier 登录语义,合同终止只验证到初始禁用按钮,并在验收前后复核回滚窗口。",
|
||||
"status": "completed",
|
||||
"dev_type": null,
|
||||
"scope": "production-auth-diagnosis-and-authenticated-readonly-acceptance",
|
||||
"package": null,
|
||||
"priority": "P1",
|
||||
"creator": "shunli",
|
||||
"assignee": "shunli",
|
||||
"createdAt": "2026-08-08",
|
||||
"completedAt": "2026-08-23",
|
||||
"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": "管理员与专用 employee smoke 凭据已分别由独立 break-glass 任务合法恢复,两个角色均唯一一次登录成功、零 retry;员工合同管理权限负向通过。生产合同总数为 0,详情、终止面板、archived/terminated UI 保持 PAUSED,未来出现首条真实合同时新建只读补验任务。累计十一次 rollback window 均 OPEN/0,terminate POST、合同 mutation 与非认证 mutation 均为 0。",
|
||||
"meta": {}
|
||||
}
|
||||
在新工单中引用
屏蔽一个用户