fix: 取消CEO默认AP台账权限(09-07-manager-ap-access-scope)
这个提交包含在:
@@ -32,7 +32,7 @@ AP financial summary and reimbursement ledger reads, not general entity visibili
|
||||
|
||||
### 3. Contracts
|
||||
|
||||
Grant ledger reading to `ceo`, `finance`, `finance_mgr`, not `manager` or
|
||||
Grant ledger reading to `finance`, `finance_mgr`, not `ceo`, `manager` or
|
||||
`system_admin`. Preserve emergency `admin` wildcard semantics. Both reads require
|
||||
the dedicated permission before data queries; query filters cannot confer access.
|
||||
Frontend route and navigation projections use the same permission. Keep manager
|
||||
@@ -44,13 +44,13 @@ management to preserve read access. Export keeps its existing independent
|
||||
|
||||
Feature enabled: missing ledger permission returns 403 / `PERM_001`, with no
|
||||
financial query. Feature disabled: the existing 409 refusal may occur first.
|
||||
CEO/finance/finance manager and manager+finance retain reading. Old payment or
|
||||
Finance/finance manager and manager+finance or ceo+finance retain reading. CEO alone is refused. Old payment or
|
||||
risk permissions alone no longer imply ledger reading.
|
||||
|
||||
### 5. Good / Base / Bad Cases
|
||||
|
||||
- Base: manager keeps payment review but cannot enter AP.
|
||||
- Good: CEO reads AP without acquiring export or payment-write rights.
|
||||
- Good: CEO retains approval and payment review but cannot read AP without a separate finance role.
|
||||
- Bad: hiding a menu while leaving either backend read unrestricted.
|
||||
|
||||
### 6. Tests Required
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
# AP 全量读取最小收窄方案(专用权限修订已批准)
|
||||
|
||||
## 2026-09-08 最新获批修订
|
||||
|
||||
用户确认两个邮箱同名账号为本人,majianyong 保留 ceo;其他员工业务直接经理为 majianyong。CEO 审批身份不再携带全量台账读取。下方第 5 节起保留旧候选发布及预检历史,不作为修订后候选的发布授权;旧“CEO 可读”验收矩阵已被本节覆盖。当前不部署、不修改生产账号或经理关系。
|
||||
|
||||
## 1. 读取授权
|
||||
|
||||
获批修订:新增专用权限 `finance.ledger.view`,仅授予 ceo、finance、finance_mgr;两个 AP GET 及前端入口统一只检查此权限。manager 不授予;不存在 CEO 角色特判,旧付款管理、复核、风险和支付权限均不再独立授予 AP 读取。既有应急 admin 通配符语义不变。
|
||||
获批修订:新增专用权限 `finance.ledger.view`,仅授予 finance、finance_mgr;两个 AP GET 及前端入口统一只检查此权限。manager、ceo 不授予;不存在 CEO 角色特判,旧付款管理、复核、风险和支付权限均不再独立授予 AP 读取。既有应急 admin 通配符语义不变。
|
||||
|
||||
前后端各有静态 ROLE_PERMISSIONS,需要同步三类角色和相关测试,不是只改服务端三行。无数据库权限迁移,不授予 CEO 写权限,不按人员账号特判,不采用 manager deny-list。AP 导出仍使用原有独立权限。
|
||||
前后端各有静态 ROLE_PERMISSIONS,需要同步角色授予和相关测试,不是只改服务端三行。无数据库权限迁移,不授予 CEO 写权限,不按人员账号特判,不采用 manager deny-list。AP 导出仍使用原有独立权限。
|
||||
|
||||
## 2. 接入范围
|
||||
|
||||
@@ -20,9 +24,9 @@
|
||||
| --- | --- | --- | --- |
|
||||
| employee | 拒绝 | 按现有权限拒绝 | 拒绝 |
|
||||
| manager,仅复核职责 | 拒绝 | 保留原业务条件 | 拒绝 |
|
||||
| ceo | 保留全量读取 | 保留原业务条件 | 不新增 |
|
||||
| ceo | 拒绝 | 保留原业务条件 | 不新增 |
|
||||
| finance / finance_mgr | 专用权限允许 | 不改变角色和独立复核条件 | 按既有两项权限 |
|
||||
| manager+已授权财务角色 | 允许 | 原条件不变 | 按财务授权 |
|
||||
| manager/ceo+已授权财务角色 | 允许 | 原条件不变 | 按财务授权 |
|
||||
| 仅系统管理员 | 不新增 | 不新增 | 不新增 |
|
||||
| 仅持旧付款管理/复核/风险/支付权限 | 拒绝 | 按原权限 | 按既有两项权限 |
|
||||
|
||||
@@ -31,3 +35,261 @@ API 验证包括数据查询未发生、筛选不绕过;前端验证包括直
|
||||
## 4. 安全与交付边界
|
||||
|
||||
不查询生产日志,不回填历史访问结论。不触碰既有门禁交审文件;若实施必须修改已交审测试,先保存对应增量基线,单独交本 task diff,不混报旧包。实现后独立只读复核,提交、推送和部署继续另行授权。
|
||||
|
||||
## 5. 单次 AP 发布方案草案(2026-09-08,未获生产执行批准)
|
||||
|
||||
### 5.1 状态、候选和实际变更范围
|
||||
|
||||
- 已提交推送:门禁包 `9916712`、AP 修复 `58dcee0`,已有两个 Trellis 提交亦已推送。用户“同意建议”仅用于继续准备方案,不倒写成单次发布例外批准。
|
||||
- 拟用候选:`58dcee08f72013fe5b9448a3c2e82a759ba5b01f`,最终批准必须引用完整 SHA、目标和一次发布窗口。不得部署执行当时浮动的 main。
|
||||
- 生产当前运行 SHA、镜像引用、迁移状态和可用回滚点尚未核实。远端 Git 的旧 main 不是生产基线。必须以生产基线到候选的完整差异确定实际发布范围,不能将 main 包含的 Trellis/QA 等改动称为“只发布 AP 一个提交”。
|
||||
- AP 修复本身没有 schema/migration 或业务数据写入逻辑;但 `deploy.sh` 的 run-init 会执行 `prisma:migrate:deploy`、`seed:config`、`seed:trialrun`。无新增 migration 不等于整个部署零数据写入,seed 也不能未经核实视为无操作。
|
||||
- 当前主工作树还有两个未跟踪 task;来源检查要求干净。后续应准备干净、来源可核验且满足既有 upstream 合同的隔离检出,不删除其他 task、不加忽略豁免、不携带未提交材料发布。本轮未创建发布检出。
|
||||
|
||||
### 5.2 例外批准合同(与验证结果分开)
|
||||
|
||||
本草案不提供普通发布豁免开关,也不启用 R1。若业务决定不等待完整门禁全绿,必须另行批准仅针对本候选、目标、窗口的一次受控发布,明确:
|
||||
|
||||
1. 已知本候选没有完整候选验收 PASS;本次接受哪些验证缺口及理由,而非宣布门禁通过。
|
||||
2. 批准人、执行人、目标、完整候选 SHA、窗口、失败停止条件、回滚目标和回滚权限边界。
|
||||
3. 是否接受既有部署 init/seed 的实际写入面;若超出批准范围或无法说明,停止,不临时跳过 init、修改脚本或强行部署。
|
||||
4. 例外只涉及明确列出的候选验收缺口,不免除来源、环境身份、备份、迁移、数据库健康、公网边界及部署后验证等既有合同。
|
||||
5. 发现额外候选差异、目标不符或回滚点不可用即停止,重新交审,不把一次例外延续到下一候选。
|
||||
|
||||
生产只读预检和生产发布是两个动作;可先单独授权只读预检。发布写入以及可能的 seed 写入,须在预检事实补齐后明确批准,不由本草案自动授权。
|
||||
|
||||
### 5.3 验证缺口与发布前要求
|
||||
|
||||
- 已有证据:五文件后端 123/123,定向合成浏览器 6/6,typecheck、隔离 build、语法和治理通过,用户独立复核通过。它们绑定已交审来源,不能改写成提交后 full-local PASS。
|
||||
- 未取得:候选完整验收全绿、普通全绿耗时、全部浏览器根因清单及真实生产权限验收。旧 58 条为环境有效的失败观测;207 清单撤销决策资格。R1 继续暂停。
|
||||
- 只读预检范围拟限定:确认目标身份、运行版本、当前备份/回滚窗口、已应用迁移及候选待执行迁移;不查询历史个人访问日志,不读取完整台账、银行信息或单据正文,不写库、不启动部署。
|
||||
- 补齐实际发布差异后,按最终候选在干净隔离来源重验同一 AP 权限矩阵、付款复核与导出回归及来源检查;原验证不得冒充新候选结果。输出实际命令、环境、筛选、结果、耗时和 Git 状态,不追加完整门禁 PASS 字段。
|
||||
- 既有发布入口和任何会触发外联/写入的 post-smoke 均须在最终方案列明,不能以“只读验收”概括整个部署流程。本轮不运行这些脚本。
|
||||
|
||||
### 5.4 发布后验收(待批准窗口内执行)
|
||||
|
||||
- `/ready`、前后端 build 标记匹配批准 SHA;原有健康、公网和备份合同检查不得缺失。
|
||||
- 经理本人或获批的受控验证身份确认 AP 直连拒绝;后端两个 AP GET 无权请求拒绝且不返回数据。前端零请求和后端零财务查询是两层不同证据,不互相冒充。
|
||||
- 财务、CEO 的授权身份确认 AP 汇总及台账可达;经理付款复核入口仍可达。不存在某种真实角色账号时不新增或冒用身份,应保留本地正例并明确实际验证缺项。
|
||||
- 生产验收仅记录必要状态与脱敏结果,不保存完整台账,不进行真实审批、复核、付款、导出或修改角色来凑正例。实际业务写操作必须另外授权。
|
||||
- 未完成必须项不得报告发布验收通过或“已可公网测试”。即使成功,也只声明 AP 入口收窄;经理仍可通过报销列表查看全部报销。
|
||||
|
||||
### 5.5 回滚与回滚验收(单独明确)
|
||||
|
||||
- 发布前先核实可恢复的旧应用镜像/构建及配置引用和数据库兼容性。一个容器名或“以后 revert”不是可用回滚点;目标 SHA 和操作步骤待只读预检补齐。
|
||||
- AP 单独回退可通过另建 revert 提交实现,但 revert `58dcee0` 只撤回 AP,不会撤回其他随候选发布的改动,也不等同恢复未知生产基线;本轮不执行 revert。
|
||||
- 健康失败、版本标记不符、财务/CEO 意外被拒绝或正常经理复核被阻断时停止发布推进,按获批回滚合同恢复。若 init 已发生写入,不能假设应用回退会撤销这些写入,更不能自动恢复数据库或覆盖期间业务数据。
|
||||
- 回滚到旧权限会重新开放经理 AP 全量访问,这是一项明确的安全退让,须纳入回滚批准和后续补救,不称为“完全恢复正确权限”。
|
||||
- 回滚后核对旧 SHA、应用健康、核心登录/财务读取/复核入口及备份合同;记录恢复范围、未回退数据和仍存在的 AP 权限开口。无可用回滚点则不批准应用切换。
|
||||
|
||||
### 5.6 当前需要的下一步授权
|
||||
|
||||
建议先批准本节限定的生产只读预检,以填实生产基线、init 写入面和回滚目标;之后提交候选与窗口明确的一次例外批准请求。本草案不请求立即部署,不请求启用 R1,不恢复其他修复支线。
|
||||
|
||||
## 6. 2026-09-08 获批只读预检结果(不是部署资格)
|
||||
|
||||
### 6.1 版本与实际差异
|
||||
|
||||
- 实测生产运行 `f223bfb72fb2baef51199702c2def84c41af7aef`,应用容器 running;当前配置镜像引用可解析且等于运行镜像,备份合同 BUILD_SHA 与运行版本一致。
|
||||
- 拟发布 `58dcee08f72013fe5b9448a3c2e82a759ba5b01f` 相对生产共 7 个提交、103 文件。server/src 与 client/src 共 7 文件、+11/-4:前后端权限表、AP 守卫、服务端导航、前端路由/导航,以及 reimbursements 的 __testables 导出。
|
||||
- deploy、Prisma schema/migrations、两个 seed 文件在此范围无差异;生产容器内两个 seed 文件的 SHA-256 也与候选一致。此处“7 文件”是应用源码变更口径,不是整个候选交付文件数,也不保证其他路径绝不被构建打包。
|
||||
- 预检从固定候选的干净隔离检出执行,保留原工作树未跟踪 task;没有来源检查豁免。候选使用共享本地 Git 对象,仅作本地预检来源,不是额外独立备份。
|
||||
|
||||
### 6.2 迁移、备份与回滚证据分层
|
||||
|
||||
| 检查 | 本轮结果 | 不代表什么 |
|
||||
| --- | --- | --- |
|
||||
| 既有身份/部署合同聚合 | 13/13 PASS,bootstrap 只读模式退出 0,未进入 deploy.sh | 不是完整候选验收通过 |
|
||||
| Prisma 状态 | 66 个 migration,schema up to date;候选/数据库对拍 pending=none | 未运行 migrate deploy,不保证以后状态不变 |
|
||||
| 既有合同回滚窗口 | OPEN,terminatedRows=0 | 只是既有状态兼容性判据,不是整个恢复方案演练 |
|
||||
| 备份服务 | 2026-09-08 02:30:01–02:30:51 CST,Result=success、ExecMainStatus=0;timer active | 不是备份文件摘要、异机副本或恢复实测 |
|
||||
| 备份元数据 | 受控部署身份读取 last-success 被权限拒绝;已有非交互 sudo 能力检查也不可用 | 未读取/验证 manifest 和数据库/附件包摘要,不宣称备份缺失或损坏 |
|
||||
| 旧应用资源 | 运行镜像 ID 可 inspect,当前配置引用指向该镜像 | 尚未固定独立回滚标签/制品;候选构建可能覆盖原标签,不能直接称“可用回滚点已准备” |
|
||||
|
||||
没有新增密钥、改变 SSH/sudo/文件权限、借容器挂载规避备份目录权限,也未执行备份、恢复、容器切换或任意部署写操作。备份验真和旧应用制品固定仍是发布前缺项。
|
||||
|
||||
### 6.3 seed 在已有生产数据上的行为
|
||||
|
||||
本轮未运行 seed。以下由候选源码与受限只读账号分支观测建立,不能将“源码不变”解释为“执行无写入”。
|
||||
|
||||
- seed-config:费用类型 upsert 更新默认属性;报销编号格式和文档分类更新;四类 workflow definition 先取消激活再写回 v1;假期日历更新并删除/重建日条目;考勤策略、预算规则、假期类型/额度更新。既有审批规则非空时保留、CCB 银行字典存在时保留、已有 feature flags 的创建冲突被保留。默认主体缺日历绑定时还可能补绑定。
|
||||
- seed-trialrun:部门更新、工作区默认配置覆盖、指定 feature flags 强制开启并递增 version;这些是实写,不是迁移空操作。
|
||||
- seed 管理账号的配置身份只匹配 1 条 active、未归档记录,目前角色为 admin+employee;未开启归档重激活,未配置新建账号密码。本次会进入 existing-active 分支:更新账号状态/时间,删除并重建 admin+employee 角色记录,密码不变;按此快照角色集合不新增。没有输出账号、手机号、邮箱或密码。
|
||||
- 不把“该账号角色集合不变”推广为所有配置语义无变化。本轮未逐字段比对当前配置与 seed 默认值;即便值相同,更新时间、flag version、删除重建等写入仍会发生。
|
||||
- 账号分支查询使用 default_transaction_read_only=on、REPEATABLE READ READ ONLY、10 秒 statement / 1 秒 lock timeout,确认 transaction_read_only=on,最后 ROLLBACK;没有读取业务单据或个人访问日志。
|
||||
|
||||
### 6.4 发布决定尚缺的条件
|
||||
|
||||
代码差异确实小且已复核,但现有部署运行 seed 的写入面不能折算进“11 行改动”后忽略。现在不形成发布批准:先通过已有获准的受控渠道补备份摘要/有效恢复点核验,明确保存旧应用制品的步骤;再明确接受既有 seed 的写入范围或另审部署方案。不得擅自跳过 seed、恢复数据库或创建常设门禁豁免。
|
||||
|
||||
原始证据在 `artifacts/qa-flow/manager-ap-access-scope/readonly-preflight-20260908/`:preflight.log、preflight/pre/、metadata-result-02.json、backup-service-status.txt。首次元数据读取失败单独保留,不覆盖或冒充通过。此结果没有改变 58 条清单、阶段 B、R1 或业务消息的状态。
|
||||
|
||||
### 6.5 2026-09-08 seed 配置只读差异补查
|
||||
|
||||
用户明确不批准发布例外,仅批准继续只读比对。观测时间为 2026-09-08 08:42:41 CST;运行版本仍为 f223bfb,容器内两个 seed 摘要与候选一致。通过现有 Compose 实际解析 init 环境,额外 force-on flags 为空,因此本次强制开启集合为默认 14 项,不将运行中 server 环境冒充下次 init 输入。
|
||||
|
||||
| 对比范围 | 当前状态与 seed 目标差异 | 重跑仍会发生的写入 |
|
||||
| --- | --- | --- |
|
||||
| 默认 14 个强制开启开关(含 project_linking_v1) | 14/14 存在;mode=force_on、rollout=global、部门集合为空,note 也一致;额外配置 0 项 | 每项 version +1,并执行更新;不称无写入或物理幂等 |
|
||||
| seed-config 其他默认开关 | 无缺失待创建项;既有记录按创建冲突保留,不冒充全字段默认值相同 | 本次未发现缺失项插入需求 |
|
||||
| 报销、请假、采购、差旅四类工作流 | 均只有 v1 且激活;name、definition_json、is_active、created_by 与目标一致;无其他激活版本 | 仍先取消激活再 upsert v1,不能把最终定义一致等同执行无副作用 |
|
||||
| CN_2026_STD 假期日历 | 表头目标字段一致;39/39 日条目日期、类型、名称、note 一致,无多余日期,无需补默认主体绑定 | 仍删除重建 39 条日记录并更新时间;记录身份与审计元数据不能视为不变 |
|
||||
|
||||
比对脚本用 TypeScript AST 提取字面量默认值,不执行 seed 函数;生产查询使用 default_transaction_read_only=on、REPEATABLE READ READ ONLY、10 秒 statement / 1 秒 lock timeout,确认 transaction_read_only=on,ROLLBACK 完成后才输出脱敏结果。未读取业务单据、个人访问历史或原始工作流定义正文。
|
||||
|
||||
覆盖边界:这是上述三组的配置目标值比对,不是 seed 全部写入字段验真。费用类型、编号、文档分类、部门、工作区、考勤策略、预算及假期额度等仍只有源码写入面盘点,未逐字段比对生产值;不以三组无差异推断全部 seed 安全。值差异即使存在也不能单凭此证明人为调整动机。
|
||||
|
||||
证据目录:`artifacts/qa-flow/manager-ap-access-scope/seed-readonly-diff-20260908/`,成功结果为 comparison-02.json。首次 wrapper 使用 Compose v2 命令失败,尚未进入数据库查询;保留首次失败产物,改为兼容生产既有 docker-compose v1 后完成只读比对,未安装依赖或修改生产脚本。
|
||||
|
||||
发布状态不变:受控备份验真仍缺可用授权通道;独立回滚制品尚未固定,相关写操作未执行。本次三组对比不替代这两项,也不生成例外资格;未运行 seed、迁移、部署、提交或推送。
|
||||
|
||||
### 6.6 2026-09-08 剩余 seed 对象只读比对
|
||||
|
||||
沿用用户批准的 seed 只读比对范围,不申请或使用备份提权、不保存镜像。补查观测时间为 2026-09-08 09:22:19 CST;生产仍为 f223bfb,两个 seed 文件摘要与固定候选 58dcee0 一致。§6.5 的“其余对象未比对”是当时状态,以下结果补足该项,不倒填成同一事务快照。
|
||||
|
||||
| 对象 | 实际匹配 | 比较边界 |
|
||||
| --- | --- | --- |
|
||||
| 费用类型 | 44/44 | 39 个启用、5 个旧类型禁用;比较实际 update 的名称、描述、分类、排序、发票要求、默认/启用标志及 updated_by;既有 account_code_default 不被 update 覆写 |
|
||||
| 报销编号规则 | 1/1 | prefix、date_format、separator、number_length、reset_period;已有 current_number 不被 update 重置 |
|
||||
| 文档分类 | 11/11 | 实际 update 字段,包括默认工作流键、启用/归档状态和 updated_by |
|
||||
| 考勤策略 | 1/1 | 最早 created_at 对应记录唯一,比较上下班/午休时间、迟到容差、启用状态及 updated_by |
|
||||
| 默认预算控制规则 | 1/1 | 匹配 name/entity_type/未归档条件唯一;部门、主体、成本中心、费用类型限制与控制模式、优先级、启用、notes 等均一致 |
|
||||
| 假期类型 | 3/3 | 名称、描述、附件要求/MIME、排序、启用及 updated_by |
|
||||
| 假期默认额度策略 | 3/3 | annual_quota_days、enabled、description、updated_by;数字按数值比较;不是员工个人余额或假期流水查询 |
|
||||
| 工作区设置 | 1/1 | profile、org_mode、primary_entry_mode、notification_strategy、device_priority、完整 module_visibility_json 均一致 |
|
||||
| seed 目标部门 | 2/2 | 根部门及其管理子部门各唯一;parent_id 与 archived_at 均一致,不输出部门 ID |
|
||||
|
||||
新增 67 项目标记录均存在、匹配唯一,实际覆写字段均一致,无待创建记录。对 findFirst 无排序的分支没有假定任意某条记录代表全体:先核验匹配候选数量,本轮没有多候选歧义。
|
||||
|
||||
另外核实两条保留分支:CCB 字典记录存在,seed 不更新;approval_rules 有 3 条,seed 保留整组而不重建。不将“保持现有记录”写成“现有值等于默认值”。与前一轮 feature flags、工作流、日历及更早的管理账号分支证据合看,两个 seed main 调用的对象组均已有写入分支盘点和相应快照核对;账号状态/角色未在本次重查,引用 §6.3 的早先观测。
|
||||
|
||||
证据仍存现有 seed-readonly-diff-20260908 目录:defaults-remaining.json、compare-remaining.cjs、comparison-remaining-01.json、stderr-remaining-01.log。默认数组及工作区/编号/考勤 update 字面量由 AST 提取,不执行 seed 源码;其余字段投影逐项对照 update 处理体。数据库使用 READ ONLY 事务与既有超时限制,成功 ROLLBACK 后才输出结果,无业务单据或个人访问历史读取。
|
||||
|
||||
口径限制:上述是观测时刻“将被覆写的配置目标字段相同”,不是所有列不变、两轮同一快照或 seed 执行演练。时间戳、version、删除重建的记录身份/审计字段仍会变;中途失败、并发配置调整、运行时错误和恢复能力均未因此得到验证。不运行 seed 来验证最终状态,也不以此替代备份验真。
|
||||
|
||||
本地核验:比较脚本 node --check 通过;对象键顺序、额度数值、启用状态、审计字段、工作区 JSON 差异等正反合成检查通过,固定候选摘要与 67 项结果结构断言通过。首次本地合成检查因容器相对路径在仓库根不可用而失败,修正本地文件读取映射后通过;没有因此重跑生产查询。
|
||||
|
||||
发布例外仍未批准。备份摘要验真和独立回滚制品仍缺完成证据;摘要匹配不等于恢复实测,镜像回滚不撤销 seed 的数据库写入,完整候选验收仍未通过。没有生产写入、镜像保存、部署、提交或推送。
|
||||
|
||||
### 6.7 2026-09-08 获批固定旧运行镜像制品
|
||||
|
||||
用户对单独保存并校验当前 f223bfb 镜像明确回复“同意”。本节更新此前“镜像未固定”的状态,不扩大为备份读取或发布授权。
|
||||
|
||||
- 写入前确认运行 SHA=f223bfb72fb2baef51199702c2def84c41af7aef,镜像 ID=sha256:49585c3b9da993f3b0329ca972e8704ca422da508927c7daf1df77b70b0acad7,与前次预检一致。镜像 Size=1,079,067,702 字节;可用空间 10,094,391,296 字节,高于本次保守阈值 3,231,877,228 字节。
|
||||
- 按固定镜像 ID 执行 docker image save;2026-09-08 09:48:50 CST 完成。image.tar 大小 1,117,595,648 字节,SHA-256 为 82283a7905a93dd60420f7f2b8f1fb3c483bb97fe2ef61cf4113be8a4c49da51。
|
||||
- 存储定位:部署身份 home 锚点下 `.local/share/hyb-oa/rollback-artifacts/f223bfb-20260908T014752Z-0807083d/`;独立于 Docker 存储和部署源码目录。内含 image.tar、image.tar.sha256、receipt.json;目录 0700、文件 0600,没有改变既有目录权限、覆盖旧制品或新增镜像标签。
|
||||
- 归档完整读取,manifest 仅含目标镜像,配置内容 SHA-256 等于固定镜像 ID;14/14 层内容摘要与配置 rootfs.diff_ids、现有镜像 RootFS.Layers 一致。另起只读进程使用 sha256sum --check 复读已保存文件通过,回执内容和文件大小也一致。
|
||||
- 保存前后容器 ID、StartedAt、运行镜像及 BUILD_SHA 相同,未重启、切换、停止或清理服务;保存后可用空间 8,976,371,712 字节。未查询/写入数据库,未执行 seed、迁移、docker load 或部署。
|
||||
- 制品是同机持久副本,不是异机或异盘备份;完整性校验不是镜像导入/恢复实测。应用回退不能撤销 seed 数据库写入,旧版本仍包含经理 AP 读取过授权;执行回滚也须按独立批准和验收合同。
|
||||
|
||||
脱敏证据目录:`artifacts/qa-flow/manager-ap-access-scope/rollback-image-20260908/`,含 precheck-01.json、save-result-01.json、verify-result-01.json 与对应脚本/错误日志。脚本语法和禁止操作检查通过;没有访问受限数据库备份、修改 SSH/sudo 权限或绕权限读取。
|
||||
|
||||
剩余前置:备份 manifest/包摘要仍未验真,获准渠道仍待指定;完整候选验收与恢复实测缺口仍需如实列入例外。此次保存授权不等于发布例外、实际部署、提交或推送授权。
|
||||
|
||||
### 6.8 管理员备份验真渠道交接(用户回传完整性通过)
|
||||
|
||||
用户确认可用具备读取权限的管理员账号登录,并批准执行端仅上传只读校验脚本,由用户自行运行后回传脱敏结果。此为获准的人工执行渠道,不授予执行端 sudo、备份文件读取或修改权限能力。
|
||||
|
||||
脚本位于系统临时目录下独立目录 hyb-oa-backup-check-7fptuwbq,文件 check-backup-admin.py;SHA-256=58a604fc1e0ebab824553fa0507683ed5d8c44872d36d56c41574d8959ea6a35。上传复读匹配,私有目录 0700、文件 0600;未执行。用户命令对所读脚本字节先验摘要再执行,不直接信任可变临时路径上的内容。
|
||||
|
||||
检查边界:从已有 systemd 合同定位 last-success 和对应备份批次,校验 manifest 摘要、runId/完成状态/候选前生产基线、数据库包及附件包文件名/字节数/摘要,复查状态和 manifest 未变化;仅输出结果、备份时间/年龄和版本,不输出原始合同、manifest、完整路径或业务内容。成功标记为 PASS_INTEGRITY_ONLY,失败为 NOT_VERIFIED 及阶段/错误类型;未执行恢复、未验证异机副本或自动生成发布资格。
|
||||
|
||||
2026-09-08 用户在管理员终端执行已核对摘要的脚本并回传 JSON:status=PASS_INTEGRITY_ONLY,manifest=sha256_match,database/storage 均为 size_and_sha256_match,build=f223bfb72fb2baef51199702c2def84c41af7aef,restoreTested=false。备份完成时间为 2026-09-07T18:30:51.081470Z,即北京时间 2026-09-08 02:30:51;校验时 ageHours=9.56,不把这一年龄写成后续发布时的实时年龄。
|
||||
|
||||
证据来源明确为用户手动执行回传,不冒充执行端直接读取原始 manifest 或亲自运行管理员校验。此前已上传制品、当前备份完整性结果与 seed 比对已可供例外审查,但完整候选验收、恢复演练和异机副本核验缺口仍在。若需要数据库恢复,该备份之后的新业务数据可能丢失,不能仅因摘要匹配就自动恢复或认为零数据损失。
|
||||
|
||||
流程更正:本机实际存在 hyinda-root 管理员 SSH 别名,与生产目标一致;此前只检查部署身份和其 sudo 能力,没有及时检查现有管理员连接配置。ssh -G 仅验证配置解析,未验证执行端实际 root 登录;应先发现已有通道并请求限定授权,而不是先将命令执行转交用户。用户本次质询不视为无限管理员授权,不追加生产操作。后续优先由执行端在明确获准的通道和操作范围内完成,避免不必要的手工往返。
|
||||
|
||||
### 6.9 发布例外补充:恢复历史与异机链时间线
|
||||
|
||||
用户提出恢复演练历史遗漏后,只读核对两个既有 task,没有发起远端管理员操作或实际恢复。
|
||||
|
||||
- 恢复历史:08-23-isolated-backup-restore-drill/research/restore-drill-evidence.md 记录 Attempt 1–5 均为 NO_GO,该任务尚无数据库与附件完整隔离恢复成功证据。restoreTested=false 不能只解释为本次没有顺手测试,必须带上这五次未完成的历史。
|
||||
- 原因边界:Attempt 3 只确认数据库恢复命令非零,底层原因未知;其余身份/目标确认/安装器等问题有对应记录。不得把五次全部归因于已证实的演练器缺陷,也不得把五次 NO_GO 等同五份备份损坏或不可恢复。
|
||||
- 异机链后续:08-28-production-arm-backup-transfer-incident/implement.md 记录 2026-08-28 已自然恢复一个新鲜 ARM 原子批次(约 12.71 小时龄),dead-man 为 PASS_OFFHOST_DEADMAN_FRESH,生产 daily/watchdog success/0。两连续自然周期和容量验收尚未收口,不能说事件已修复,也不能把 08-27 的链路失败直接当作 09-08 当前状态。
|
||||
- 源端成功状态的含义:当前受管 scripts/run-automatic-backup.sh 在 rsync 和异机 finalize 返回成功之后才写 last-success。今日用户校验的成功状态因此不能简单解释为仅本地产出成功;但尚未在本轮独立读取 ARM 最新归档或核对远端脚本版本,不以本地回执替代异机现状验真。
|
||||
- 容量已有更新:09-08 镜像保存前目标文件系统可用约 10.09 GB,保存后约 8.98 GB(约 8.36 GiB)。这是本轮保存时观测,不是此刻两端全部数据/备份路径的实时容量;必须计入新增镜像占用,不能继续引用约两周前的 10 GiB 作为当前余量。
|
||||
|
||||
例外必须显式保留:五次 NO_GO 且完整恢复能力未证实;摘要匹配不等于可恢复;异机归档现状和连续周期尚未本轮独立核验;数据库回退可能丢失备份时点后数据且镜像回退不能撤销 seed 写入;完整候选验收未通过。过去部署承担过风险不是本次可接受的证据或授权。
|
||||
|
||||
建议下一步限定为获批后的单次只读检查:生产数据库/附件/备份/镜像制品所在文件系统的容量,ARM 最新正式归档的时间、身份/完整性及相关 timer/service/dead-man 状态;只输出脱敏结果,不触发备份/传输/恢复,不清理、不改配置或权限。当前只读管理员通道授权待确认,不因用户提出调查建议自行连接或部署。
|
||||
|
||||
### 6.10 获批两端只读复核结果(2026-09-08 15:02–15:04 CST)
|
||||
|
||||
用户已对 §6.9 的限定只读操作明确回复同意,执行端使用既有生产管理员 SSH 通道完成源端采集,ARM 使用现有登录账号检查既有非交互 sudo 能力;未上传远端探针文件,代码经 stdin 执行。此为新的执行端直接观测,与 §6.8 用户回传证据分开记录。
|
||||
|
||||
| 检查 | 结果 | 证据边界 |
|
||||
| --- | --- | --- |
|
||||
| 生产容量 | 数据库、附件、本地备份及旧镜像制品同一文件系统;总计 63,291,080,704 字节,已用 54,349,017,088,可用 8,925,286,400(约 8.93 GB / 8.31 GiB),使用率 86%,可用 inode 1,400,907 | 不是马上磁盘满,也不是容量验收通过;未进行空间来源全盘调查或清理 |
|
||||
| 本地备份 | 最新批次创建于 09-08 02:30:02 CST,约 12.53 小时龄,24 小时内;manifest 身份、精确三文件、数据库/附件字节数与 SHA-256 通过,版本/镜像匹配 f223bfb | 未恢复;只完整哈希最新批次,不声称所有历史批次均完整 |
|
||||
| 本地正式批次占用 | 7 个目录,文件合计 141,513,496 字节;最新 DB 2,888,951 字节,附件包 17,337,974 字节 | 不把全部磁盘占用归因于正式备份;其他对象/临时产物未全盘盘点 |
|
||||
| 生产 daily/watchdog | daily 今日 02:30:01–02:30:51 success/0;watchdog 今日 07:10:01–07:10:02 success/0;两 timer active/waiting | oneshot 执行后的 inactive/dead 不表示故障;不是两连续自然周期完整验收 |
|
||||
| 源端异机成功回执 | 实际 runner SHA-256 与 f223bfb 受管源码一致;最新 last-success 与最新本地批次身份/manifest 摘要一致,源码只有异机传输及 finalize 成功后才写状态 | 支持“今日源端确认过异机成功”,不能替代本轮 ARM 文件独立验真 |
|
||||
| ARM dead-man 调度 | 普通账号可读:今日 06:30:05 service success/0,timer active/waiting | 未读取私有 last-deadman summary,未校验远端运行脚本摘要,不冒充实际包摘要复核 |
|
||||
| ARM 归档和容量 | 未完成:SSH 可连;现有 sudo -n 需要密码,未得到管理员执行能力 | 不表示归档缺失、损坏或磁盘不足;没有尝试绕权限或修改授权 |
|
||||
|
||||
生产当前容器 BUILD_SHA 和镜像 ID 仍匹配 f223bfb;执行端本轮重新验证了本地备份完整性,但没有查询业务库、个人日志或下载备份。ARM 第一次无脱敏结果返回,后续只读能力检查确认密码要求后停止受限部分;补读普通账号可见 systemd 属性不涉及私有归档访问。
|
||||
|
||||
结论:已有今日源端成功回执与 ARM dead-man 成功状态,不能把历史中断写成目前仍在持续;但 ARM 当前最新正式归档、文件摘要和目标路径容量仍未独立验证,P1 不结案。生产 86%/约 8.93 GB 余量保留容量风险,不能仅凭最新成对备份约 20.23 MB 推断部署构建峰值也可承受。
|
||||
|
||||
证据目录:`artifacts/qa-flow/manager-ap-access-scope/offhost-readonly-20260908/`;production-01.json、arm-access-diagnostic-01.json、arm-services-unprivileged-01.json。ARM 首次失败产物原样保留,不覆写成成功。比较探针通过 Python 语法、合成批次成功/损坏反例、文件系统分组检查;禁止操作未执行。
|
||||
|
||||
本次无备份/传输/恢复触发、无权限配置变更、无清理、无部署、无提交/推送。发布例外、恢复演练和剩余容量处置均未获批,不能从用户的只读授权推导。
|
||||
|
||||
#### 15:46–15:47 CST 追加:直接执行授权与实际凭据限制
|
||||
|
||||
用户要求由执行端直接操作,不再转交手工。使用既有 ARM 主机配置/密钥、显式 root 身份仅请求 id -u,结果为 SSH exit 255 且认证拒绝;不是主机连接失败或 host key 失败。此结果不能概括为 ARM 禁止所有 root 登录,只说明当前配置/密钥未成功认证。
|
||||
|
||||
随后以普通账号运行已批准只读探针,systemd 可读,dead-man 阈值为 93600 秒(26 小时,与恢复演练 24 小时门禁不同);读取归档路径容量时明确 PermissionError。当前身份既不能通过非交互 sudo,也不能直接读取该路径,故停止受限部分,不尝试其他凭据、不改权限、不借其他进程/容器绕权限。
|
||||
|
||||
普通账号可直接观测的根文件系统容量为总计 210,518,515,712 字节、可用 95,961,047,040 字节、使用率 53%。此为根文件系统事实;未确认受限归档目录的实际文件系统归属,不能把“约 95.96 GB 余量”写成归档路径已验证。证据 arm-unprivileged-02.json、arm-root-filesystem-01.json 保留在现有 offhost-readonly-20260908 目录。
|
||||
|
||||
不再要求用户反复手工执行。现有能力下未完成的 ARM 归档摘要及目标路径容量保持未验证;不自动形成发布资格。用户已授予本轮限定操作权限与现有凭据能否执行是两个独立事实。
|
||||
|
||||
### 6.11 ARM 归档与容量补证(用户管理员终端回传)
|
||||
|
||||
用户说明具备交互式 sudo 能力后,在 ARM 终端执行提供的只读检查命令并回传 JSON。本节补足 §6.10 当时未能读取的最新正式归档和目标路径容量,不改写执行端之前的权限拒绝,也不冒充执行端自行提权或直接读取了私有归档。
|
||||
|
||||
- status=PASS_INTEGRITY_ONLY;归档创建时间 2026-09-07T18:30:02+00:00,即北京时间 2026-09-08 02:30:02;校验时年龄 16.48 小时,在 24 小时门禁内。此为检查时事实,不是发布时永久有效的新鲜度保证。
|
||||
- batchIdentitySha256=ecefcda74bacb640f842ca76ba9d6182ac03daf6700d8ce5400f21de52073b00;manifestSha256=902afe5a22e6624db99ea86a3873377b54561367509e0cdb8e74e6a73209a197。执行端已与生产 production-01.json 比较,两项逐字一致,说明源端和 ARM 所核对的是同一批次及同一 manifest。
|
||||
- buildMatchesExpected=true、imageMatchesExpected=true;数据库包、附件包均为 size_and_sha256_match。结合源端独立哈希结果,两端对应包均匹配同一登记摘要;未下载/恢复真实备份数据。
|
||||
- 归档路径 statvfs 返回可用 95,960,739,840 字节(约 95.96 GB / 89.37 GiB)、使用率 52.4%。本轮已取得归档路径本身的容量,不再仅依赖根文件系统推测;此前 53% 是另一时刻且向上取整的根文件系统口径,不据此推断磁盘趋势。
|
||||
- restoreTested=false。完整性与归档存在已补证,不代表恢复能力、连续自然周期、故障根因修复或 P1 结案。不得将“5 次 NO_GO、无完整恢复成功记录”弱化成“本次未测”;也不将其解释为备份损坏。
|
||||
|
||||
当前发布例外应保留的缺口更新为:完整候选验收未通过;五次恢复演练未完成,数据库/附件完整恢复能力未获实证;旧镜像虽已独立保存及校验,未做导入/回滚演练且不能撤销 seed 写入;数据库恢复可能丢失备份时点后的业务数据;生产上次实测 86% 使用率/约 8.93 GB 余量,不是部署构建峰值容量保证。异机最新归档“未验真”不再作为当前缺口,但实际发布前仍须重新核对时效、运行身份与容量。
|
||||
|
||||
本轮只对齐用户回传与本地证据、更新既有 task 文档;没有新增远端操作、备份/传输/恢复/部署,也未提交或推送。此补证不是发布例外或部署授权。
|
||||
|
||||
### 6.12 一次性发布例外批准记录(2026-09-08)
|
||||
|
||||
用户对明确限定的审批问题回复“批准”:仅针对候选 58dcee0 的一次性发布例外,接受 §6.11 及审批问题列出的验证缺口;该次审批明确不执行部署,实际部署另行授权。本记录于北京时间 2026-09-08 19:05 后写入,不将记录时间冒充用户消息的精确发生时间。
|
||||
|
||||
- 固定候选:58dcee08f72013fe5b9448a3c2e82a759ba5b01f;已核实生产基线/应用回退目标:f223bfb72fb2baef51199702c2def84c41af7aef。例外不覆盖任意后续提交或与此基线不同的未审变更。
|
||||
- 明确接受的缺口:完整候选验收未通过;恢复演练五次 NO_GO、尚无数据库与附件完整恢复成功证据;旧镜像未做实际导入/回滚演练;应用回退不能撤销 seed 的数据库写入,数据库恢复可能丢失备份时点后的业务数据;生产容量上次 86%/约 8.93 GB 余量不能当作发布构建峰值保证。
|
||||
- 执行前条件保留:重新核对实际运行身份、固定候选、备份新鲜度/已校验证据对应关系、独立回滚制品和容量。条件不足、无法确认或范围/风险实质变化时停止,不能用本批准覆盖新缺口。
|
||||
- 实际部署、数据库恢复、备份/镜像清理、配置/权限修改、提交/推送都不由本次例外自动授权。回滚验收仍按 §5 的独立合同,不把数据库恢复混入应用回退,也不擅自跳过部署 seed。
|
||||
- 此为单次候选风险接受,不是测试通过回执、常设门禁豁免或 R1 启用。58 条红项、合同阶段 B、六个 view_all、恢复演练与 P1 连续周期/容量治理不随之结案。
|
||||
|
||||
当前状态更新为“发布例外已批准;实际部署未授权、未执行”。此前未批准的记录均为各阶段历史状态,不倒填为先前已经取得资格。
|
||||
|
||||
### 6.13 部署授权与验收凭据预检阻碍(2026-09-08)
|
||||
|
||||
用户随后明确“批准例外,可以部署”,并指定版本、经理拒绝/财务可读/CEO 可读/经理复核入口保留四项验收;验收通过后再删除旧镜像制品。实际部署授权已取得,不能继续写成未授权。用户引用的 ARM 未核验状态已由 §6.11 补证,不回退事实。
|
||||
|
||||
部署前按 §5.3 在固定 58dcee0 干净隔离检出重新运行既有 AP/权限/导航/导出/付款回归,五文件 123/123 PASS;本地合成浏览器 6/6 PASS、单 worker、零重试并开 trace。没有迁移 31 个认证 spec 或修改业务断言;复验后来源仍干净。
|
||||
|
||||
为了避免部署完成后才发现无法验收,使用业务手册指定的受管 live-smoke 凭据,仅执行登录、/me 角色核对及成功会话登出;凭据和 token 只在进程内使用,不回显或保存原始账号/响应正文。19:26 CST 结果如下:
|
||||
|
||||
| 受管验收角色 | 观测 | 结论 |
|
||||
| --- | --- | --- |
|
||||
| manager | 登录 200、/me 200、角色匹配且仅 manager/employee 范围,mustChangePassword=true;登出 200 | 不能在不改密的条件下用于正常业务页面验收 |
|
||||
| finance | 登录 403 | 当前凭据未取得有效会话;具体拒绝原因未判定 |
|
||||
| ceo | 登录 401 | 当前凭据未取得有效会话;具体拒绝原因未判定 |
|
||||
|
||||
每个凭据仅一次尝试,未反复重试、查密码、改密、修复角色、伪造 token 或冒用其他身份。它们是受管验收账号的结果,不能推广成真实业务人员账号不可用。登录/登出及失败请求会走既有认证写入(会话/审计/计数),不宣称这部分完全不写库;没有真实业务提交/审批/付款。
|
||||
|
||||
既有一次性例外接受完整候选验收和恢复演练缺口,但不能自动豁免此次明确要求的四项公网角色验收。新前置不具备,故停在部署写入前,不运行 deploy-existing/deploy.sh、构建、migrate、seed 或容器切换。19:28 CST 公网 /ready=200、版本 f223bfb72fb2,生产 AP 仍未收窄。
|
||||
|
||||
下一分叉仅是是否另行接受“四项公网角色验收延后,先发布且保留旧镜像”;未获明确接受前不自行作此扩展。删除镜像的前提是验收通过,目前不满足,故未删除/转移。也不把本地 6/6 或 123/123 写成四项公网 PASS。
|
||||
|
||||
证据目录为 `artifacts/qa-flow/manager-ap-access-scope/release-20260908/`。本轮没有提交、推送、部署或镜像清理;暂停原因是实际验收能力缺口,不是 ARM 凭据或用户未批准部署。
|
||||
|
||||
@@ -1,5 +1,156 @@
|
||||
# 执行计划
|
||||
|
||||
## 2026-09-08 付款账户配置只读核对与候选纠偏
|
||||
|
||||
- 用户附件要求综合判断后续部署与业务恢复,并提出核对此前配置的建行付款账户。本轮只读核对配置,不将附件中的提交/推送建议、批量主体回填建议或业务付款建议转换为执行授权。
|
||||
- 21:10 CST 生产 f223bfb:legal_entities 共 1 条且启用未归档;payment_accounts 共 1 条且启用未归档,关联该启用主体及启用未归档的 CCB 银行字典;加密账号字段非空。只返回聚合数和存在性,没有输出或读取户名/账号明文,没有取回密文,没有调用解密。事务只读且 ROLLBACK 完成。
|
||||
- 结论仅为“建行付款账户已配置”,不建议重复新建;解密和 snapshot 生成未验证。snapshot 的实现会在进程内解密账号,所以“验证 snapshot 且完全不解密”不能同时成立。若获准校验,仅回传成功/失败,不输出明文、户名或完整 snapshot。
|
||||
- 候选必须包含本轮 CEO 撤权修订,不能部署旧 58dcee0;修订尚未提交。旧备份/回滚制品和 seed 比对作为历史证据保留,但发布前仍需核对时效、存在性及生产漂移,不能写成“只有候选 SHA 改一下即可”,新候选批准须明确。
|
||||
- 不认可直接回填旧快照的 7 条员工:全表含 smoke 标识记录,须先明确真实业务员工范围和目标主体,复核当前字段及原值,再另批写入。唯一启用主体只是技术上唯一,不自动等于全部记录业务归属已获确认。
|
||||
- 本地 readiness 代码除主体和付款账户外仍检查收款人银行/账号、单据状态、部门、批次占用及复核人等;未调用真实业务提交或组批,不宣布三张单必定可重提/审批/付款,也不凭角色默认断言唯一真实复核人。业务人员可先修订单据,但状态流转由实际系统结果验收,不替用户操作。
|
||||
- 证据:artifacts/qa-flow/manager-ap-access-scope/payer-config-readonly-20260908/result.json。探针语法通过;本轮无生产写入、提交、推送或部署。经理关系修正保持已执行并回读完成,用户接受执行证据不冒充用户独立生产验证。
|
||||
|
||||
## 2026-09-08 财务直接经理单字段修正(明确授权)
|
||||
|
||||
- 用户已确认 wangzhiling 为财务本人、yj 为员工本人,并明确授权仅将财务 direct_manager_id 从 NULL 设为 majianyong;不以是否已经发生提交故障作为修正前提。
|
||||
- 执行合同:唯一生产运行身份核对;事务内锁定两条员工记录及其角色记录,核对唯一身份、正常任职状态、完整角色及原经理空值;条件 UPDATE 仅一个字段、必须影响一行;提交后另开只读连接回读。任何前置不符均停止,不覆盖非空值;提交结果不明时不自动重试。
|
||||
- 不改角色、审批规则、单据、其他员工字段,不运行 seed、部署、付款或审批。经理与 CEO 是否在实际流程中合并审批尚未验证,不声称两次审批或自动去重。回退亦须另行授权,不能将“可改回空”冒充无影响。
|
||||
- 执行结果:2026-09-08 20:47:57 CST,PASS_APPLIED_AND_READBACK。原值在加锁事务内确认为 NULL,条件 UPDATE 影响 1 行,仅 SET direct_manager_id;提交后销毁原连接,用新连接只读回读,财务经理已指向 majianyong,双方完整角色与执行前一致。运行版本仍 f223bfb,未部署。
|
||||
- 执行过程原样保留:首次 stdin 模式 require.main 不等于 module,执行函数没有被调用,包装器因空输出解析失败;先只读确认原值仍空,再修正入口。入口自测首次因模拟 process.env 缺失失败,补齐模拟后通过,不冒充首轮通过。第二次在 UPDATE 前检测到未审阅触发器并回滚;只读审查发现唯一触发器仅禁止修改 employee_no,不产生字段写入,无 UPDATE rule。固定触发器/函数定义摘要,并在表级 ROW EXCLUSIVE 锁内复核后执行;未禁用任何触发器或约束。第三次成功提交并独立回读,没有重复成功写入。
|
||||
- 证据目录 artifacts/qa-flow/manager-ap-access-scope/finance-manager-correction-20260908/:result-applied.json 为最终回执;result.json 为触发器前置拒绝回执;reconciliation.json、trigger-inspection.json、stderr.log 记录前次停止和只读复核;selftest.json 为身份/状态/原值校验,stdin-selftest.json 为修正后的入口及连接失败回执验证。最终执行脚本 SHA-256 为 9b3abf70a4b36313b800ef3caddbb53e704a9ce0e7ec2f0acad58cb4adf55c43。
|
||||
- 本次数据关系修正已完成,不等于真实提交/审批闭环已验证。AP 修订保持用户已复核但未提交/部署;不因此启用门禁或恢复其他任务。
|
||||
|
||||
## 2026-09-08 AP 修订复核通过与全员工关系只读盘点
|
||||
|
||||
- 用户明确复核通过 CEO 读取修订;独立复核已由用户完成,不将其转换为提交、推送、部署或生产员工写入授权。
|
||||
- 用户新增授权一次全员工列表查询,仅内部 ID、登录邮箱、角色、直接经理关系;不读取姓名、金额、票据、银行。输出用 E1–E8 替代 UUID,邮箱域名脱敏;完整邮箱与内部 ID 仅在远端进程内用于关联,不落本地回执。
|
||||
- 20:36 CST 查询得到全表 8 条,非“8 条在职”;未查询任职状态,不能拿本次 5 条经理为空与旧 7 条在职/4 条为空直接比较。只读事务已成功 ROLLBACK,运行版本及镜像仍匹配 f223bfb。
|
||||
- 脱敏清单:E1 smoke-finance,employee+finance,经理空;E2 majianyong,ceo+employee,经理空;E3 oa_smoke_02,employee+manager,经理 E2;E4 yj,employee,经理 E2;E5 smoke-manager,employee+manager,经理空;E6 oa_smoke_01,employee,经理 E5;E7 wangzhiling,employee+finance,经理空;E8 wangshunli,admin+employee,经理空。标签来自邮箱本地部分,不凭 smoke 字样断言所有此类记录都不是业务账号。
|
||||
- 前次匹配失败的原因已具体化:系统记录为 wangzhiling 与 yj,而不是此前用于匹配的 wangzhuling 与 yangjun。两者是否分别对应用户所说的财务与员工仍需用户确认,不凭相似拼写/缩写认定真人。
|
||||
- E7 财务角色记录的直接经理确实为空,是确定的字段缺口;若用户确认它是财务本人,则与“其他员工经理均为 majianyong”的业务口径冲突,须单独交审生产回填。没有执行提交,不能宣布已实测所有入口 409;E4 的经理配置已指向 majianyong,若其确为 yangjun,则此项关系一致,不代表全部审批链验证完成。
|
||||
- 证据位于 artifacts/qa-flow/manager-ap-access-scope/employee-roster-readonly-20260908/result.json。探针语法检查通过;本轮不重跑产品测试、不改产品代码、不写数据库、不登录业务账号、不删回滚制品。部署仍暂停。
|
||||
|
||||
## 2026-09-08 身份确认后的 CEO 读取修订(待用户复核,不部署)
|
||||
|
||||
- 用户确认已匹配的邮箱同名账号为本人:wangshunli=admin+employee,majianyong=ceo+employee;确认其他员工业务直接经理为 majianyong,但不冒充数据库字段已一致。明确批准 AP 修订及另外两人的经理关系只读核对。
|
||||
- 本轮运行时增量仅 server/src/constants/permissions.js、client/src/auth/permissions.ts 各删除 ceo 的 finance.ledger.view 一行。与 HEAD 全角色权限数组逐项比较,其他权限不变;审批路由、seed、工作流、账号、六个 view_all 均未修改。finance/finance_mgr 及 admin 通配保留,ceo+finance 仍按并集放行。
|
||||
- 已更新角色、两个 AP GET、导航及浏览器矩阵:ceo 与 manager 拒绝;后端拒绝时零财务查询;前端直连 403、无入口、零 AP 请求。财务及两种混合角色正例渲染台账;CEO 四类审批与付款复核权限保留,导航单测确认付款入口保留。
|
||||
- 复验:后端五文件 124/124 PASS;本地合成浏览器 7/7 PASS,单 worker、零重试、trace、2.7 分钟;typecheck、独立 artifacts 构建、全角色增量核对及 diff 检查通过。不是完整候选验收,不是生产角色验收;构建仍有既有 Browserslist 陈旧提示。独占 Vite 已停止,未修改归档截图。
|
||||
- 20:23 CST 限定只读查询:majianyong 精确匹配一条;wangzhuling、yangjun 在工号、邮箱本地部分、企业微信绑定三个限定匹配方式下均为零条,因此二人的 direct_manager_id 未核实,不是查得为空,也不能断言不能提交。需要两人的系统工号才能继续定位,不需要密码、完整邮箱或业务内容。事务只读并成功 ROLLBACK,生产版本仍 f223bfb。
|
||||
- PRD、当前设计矩阵、权限规约已同步;旧发布历史保留并明确被新矩阵覆盖,旧候选批准不自动转用于新改动。用户独立复核待办,不派代理,不以主会话自检冒充独立复核。
|
||||
- 证据目录 artifacts/qa-flow/manager-ap-access-scope/ceo-scope-revision-20260908/:backend-final.log、browser.log、browser-results、typecheck.log、build.log、scope-check.json、manager-check.json、governance.log。review-code.diff 为代码/测试/规约增量,review-full.diff 包含既有未提交任务预检时间线,不能把后者全部归作本轮新增。
|
||||
- 验证命令:server 下使用既有 PAYMENT_ORDER_PYTHON_BIN 执行 npm test -- tests/unit/routes/apOperations.permissions.test.js tests/unit/constants/permissions.test.js tests/unit/workspaceSettings.navigation.test.js tests/unit/routes/exports.apOperations.test.js tests/unit/routes/paymentBatches.reimbursement.test.js;client 下 npm run typecheck、vite build --outDir 指向上述独立目录;E2E_MODE=local E2E_BASE_URL=http://127.0.0.1:23119 playwright test tests/e2e/payment-reviewer-permissions.spec.ts --grep '只有付款复核职责|AP 专用读取权限' --workers=1 --retries=0 --trace=on --reporter=list,输出指向上述目录。
|
||||
- 未提交、推送、部署或写生产;不删回滚制品,不回填员工经理。58 条、阶段 B、R1 保持暂停。交付保留:经理和 CEO 仍有原报销列表全量查看能力,本轮只收窄 AP。
|
||||
|
||||
## 2026-09-08 四账号与三单关系只读核对(最新边界)
|
||||
|
||||
- 用户批准四个指定账号的完整角色、当前与候选 AP 权限来源、通配符、直接经理及审批职责核对;分开核实当前 rejected 单的 submitter 与 owner。不读取金额、票据、银行、姓名或联系方式。
|
||||
- 使用精确企业微信绑定匹配,缺失不猜身份;只读事务成功回滚后输出脱敏结果。当前三单不自动等同旧快照中的三单。
|
||||
- 部署暂停,不修改账号、不移除角色、不删除回滚制品;结果交用户判断,不自动恢复部署。
|
||||
- 20:06–20:07 CST 两次受控只读事务均回滚完成,生产版本和镜像仍匹配 f223bfb;运行权限表、认证插件、AP 路由摘要匹配旧版本。四标识精确企业微信绑定均无匹配;补充限定工号/邮箱本地部分候选查询,未扫描或输出人员名册。
|
||||
- wangshunli 邮箱同名唯一候选为 admin+employee、持 *,当前及候选均可读 AP;majianyong 邮箱同名唯一候选为 ceo+employee、无 *,当前由 payment.batch.review、候选由 finance.ledger.view 放行。两候选均 active/在职/未归档、直接经理为空。邮箱同名不等于真人身份确认,不能写成四真人已核实。
|
||||
- wangzhuling、yangjun 在以上匹配方式中均未找到;不推断账号不存在或角色为何。四账号完整映射尚未完成,需业务确认候选和提供另外两人的系统身份定位,不需要密码或银行信息。
|
||||
- 当前恰有三条 rejected,三条 submitter_id=owner_id,均指向 majianyong 邮箱同名候选。未读取单号、金额、票据或银行,不能倒填历史三单对应关系。
|
||||
- 当前启用的三条审批规则最高档确为 manager→ceo→finance,owner_self_fallback_role=finance;活动报销工作流引用 ceo。majianyong 同名候选具有四类审批及付款复核权限,明确分配给该候选的 workflow_tasks 有 completed 2 条,无其他状态;不把权限资格或任务数当作当前完整审批责任。其他活动工作流未提取到 role 类型引用不等于没有其他分配规则。
|
||||
- 证据:artifacts/qa-flow/manager-ap-access-scope/account-role-readonly-20260908/ 的 result.json、result-candidates.json;后者账号标签仅表示邮箱同名候选。探针语法通过,未执行登录、部署、seed 或业务写入。初始绑定结果与补充候选结果分别保留。
|
||||
|
||||
## 2026-09-08 实际部署(已授权,公网角色验收前置失败,未进入部署写入)
|
||||
|
||||
- 用户明确批准例外并表示“可以部署”,目标仍为 58dcee08f72013fe5b9448a3c2e82a759ba5b01f;沿既有受管发布入口执行 migration/seed、应用切换、健康与版本校验及 AP 定向验收,不扩展产品改动。
|
||||
- 用户消息沿用了旧的“ARM 归档未核验”状态;实际已由用户管理员回传并与源端对齐,不将已补证事实退回未知,也不因此另索同一批准。
|
||||
- 执行顺序:固定干净候选定向复验 → 执行前运行身份/备份时效/旧镜像制品/容量检查 → 受管发布 → 版本与基础检查 → 经理 AP 拒绝、财务/CEO 可读、经理复核入口保留。使用既有受管 live-smoke 凭据,仅在进程内读取;不运行真实审批、复核、付款、导出或角色修改。
|
||||
- 用户提出验收通过后删除旧镜像制品;该条件尚未满足,发布和验收过程中保留制品,不提前删除、不转移到 ARM、不做其他空间清理。容量与管理员凭据后续事项仍不扩大为本轮修复。
|
||||
- 未授权数据库恢复、永久门禁豁免、提交/推送或其他任务归档。任何实际失败和验收缺项如实记录,不以部署命令退出 0 替代角色验收。
|
||||
- 固定 58dcee0 干净隔离来源复验通过:后端五文件 123/123、六条合成浏览器 6/6,workers=1、retries=0、trace=on;浏览器耗时 1.6 分钟,独占 Vite 已停止,候选工作树仍干净。这不是完整候选验收,也不是公网角色验收。
|
||||
- 19:26 CST 验收凭据预检发现新阻碍:受管 manager 登录 200、/me 200、角色匹配且无额外管理角色,但 mustChangePassword=true;finance 登录 403;ceo 登录 401。每个凭据仅尝试一次,成功的经理会话已登出(200),没有重置密码、修改角色、尝试替代凭据或操作业务。
|
||||
- 登录/登出及失败尝试可能产生正常认证会话、审计、失败计数写入,不能表述为“生产完全零写入”。本轮未运行部署入口、migrate、seed、构建或容器切换;19:28 CST /ready 200、构建仍 f223bfb72fb2。
|
||||
- 此结果仅说明当前受管验收凭据不能完成要求的四项公网验收,不证明真实业务人员账户故障;401/403 原因未进一步推断。验收范围实质变化不由已批准例外自动覆盖,暂停在部署前,待用户决定是否接受公网角色验收延后。镜像删除的“验收通过”条件未成立,保留独立制品。
|
||||
- 证据位于 artifacts/qa-flow/manager-ap-access-scope/release-20260908/,含 backend-tests.log、browser-tests.log、browser-results、live-credentials-precheck.json、ready-before-paused-release.json;公网预检未保存 token、密码、完整身份或响应正文。
|
||||
|
||||
## 2026-09-08 一次性发布例外(用户已批准,实际部署待单独授权)
|
||||
|
||||
- 用户对“仅针对候选 58dcee0 的一次性发布例外,接受已列明缺口;这一步不执行部署”明确回复“批准”。固定候选为 58dcee08f72013fe5b9448a3c2e82a759ba5b01f,已核实生产基线为 f223bfb72fb2baef51199702c2def84c41af7aef。
|
||||
- 接受范围:完整候选验收未通过;五次恢复演练均未完成、数据库/附件完整恢复及镜像实际回滚未经成功演练;镜像不撤销 seed 写入、数据库恢复可能丢失备份时点后数据;生产容量仍须执行前复核。
|
||||
- 不授权本轮部署、提交、推送、恢复数据库、修改权限/门禁或清理。候选改变或风险/实际写入范围实质变化重新交审;实际部署前仍复核身份、备份时效、回滚制品及容量,不足或无法确认则停止。
|
||||
- 此处仅登记例外决定,不把未通过测试标为通过,不启用 R1,不关闭恢复演练/P1 事件或扩大六个 view_all 范围。下一步只请求实际部署授权。
|
||||
|
||||
## 2026-09-08 ARM 管理员回传归档核验(此前受限项已补证)
|
||||
|
||||
- 用户自行在 ARM sudo 终端执行所提供的只读命令并回传 PASS_INTEGRITY_ONLY。最新归档创建于北京时间 09-08 02:30:02,校验时 ageHours=16.48、within24Hours=true;DB/附件大小及 SHA-256 均匹配,版本/镜像匹配预期。
|
||||
- 本地将回传 batchIdentitySha256 和 manifestSha256 与生产 production-01.json 逐字比对,两项均相等;两端同一批次/manifest 的证据链已对齐。证据来源为用户回传,不冒充执行端直接取得 ARM 私有文件。
|
||||
- 归档路径实际 statvfs 回传可用 95,960,739,840 字节、使用率 52.4%,补足此前仅有根文件系统代理量的缺口;不据此证明具体挂载身份,也不倒填旧时间点。
|
||||
- 不再保留“本轮异机归档文件未验真”作为当前缺项;保留五次恢复 NO_GO、未做成功恢复/镜像导入演练、完整候选验收未通过、生产容量及备份时点后数据损失风险。P1 连续周期/容量验收未因此结案,发布仍未批准。
|
||||
|
||||
## 2026-09-08 ARM 管理员通道直接尝试(现有凭据受限,不再转交手工)
|
||||
|
||||
- 用户明确要求执行端直接操作、不再询问;据此对同一 ARM 目标用现有 SSH 配置/密钥尝试 root 身份,仅执行 id -u 验证。认证被拒,未登录成功;不尝试密码、其他身份或改授权。
|
||||
- 普通账号执行现有只读探针,确认在 arm_capacity 阶段 PermissionError,归档受限不是仅凭 sudo 提示推断。dead-man 状态仍 success/0,阈值读到 93600 秒;未读取归档内容。
|
||||
- 15:47 CST 补取普通账号可访问的 ARM 根文件系统:使用率 53%,可用 95,961,047,040 字节(约 95.96 GB / 89.37 GiB)。未证明该文件系统就是私有归档目录所在文件系统,不以此替代归档盘实测。
|
||||
- 当前阻碍是凭据/文件权限而非业务授权;保留 ARM 最新归档及所属文件系统未独立验证,不继续索取用户手工操作,不把受限结果写成通过。无权限变更、生产写入或部署。
|
||||
|
||||
## 2026-09-08 两端容量与异机链只读复核(生产完成,ARM 权限受限)
|
||||
|
||||
- 用户已同意通过既有生产管理员和 ARM SSH 通道,由执行端完成一次限定只读检查:两端空间、最新正式异机归档时间/完整性及备份 timer/service/dead-man 状态。
|
||||
- 使用现有权限;拒绝访问即停止该项,不修改权限。不触发备份/传输/恢复/告警,不清理、不上传远端探针文件、不部署。探针通过标准输入运行,只回传脱敏结果;真实备份不下载到开发机。
|
||||
- 生产 15:02 CST:数据库/附件/备份/旧镜像制品均位于同一文件系统,使用率 86%,可用 8,925,286,400 字节;最新成对备份完整性通过、约 12.53 小时龄,正式本地批次目录 7 个,合计文件约 141.51 MB。运行 SHA/镜像未变,daily/watchdog 成功、timer active。
|
||||
- 生产实际备份 runner 摘要与 f223bfb 受管源码匹配,last-success 对应最新本地批次且 manifest 摘要匹配;该源码在异机 finalize 成功后写成功状态。因此源端有今日传输/finalize 成功回执,不应继续称“两周前失败一直未恢复”。
|
||||
- ARM SSH 可连,但 sudo -n 执行失败;只读 sudo 能力检查明确 passwordRequired=true。停止受限归档/容量读取,不更换身份、不索取密码、不改权限。普通账号读取到 dead-man 今日 06:30 success/0、timer active;未读取最新正式归档或 last-deadman 文件,未重算 ARM 包摘要。
|
||||
- 结果不能冒充两端全通过、两连续自然周期验收或恢复成功。证据位于 artifacts/qa-flow/manager-ap-access-scope/offhost-readonly-20260908/;脚本语法、合成批次正反检查及文件系统分组检查通过。详见 design.md §6.10。
|
||||
|
||||
## 2026-09-08 发布例外恢复风险补充(本地核对完成,远端新调查待授权)
|
||||
|
||||
- 核对恢复演练 Attempt 1–5 全为 NO_GO;该 task 没有完整恢复成功证据。Attempt 3 底层恢复失败原因未知,不能把五次全部解释成已证实的工具缺陷。
|
||||
- 后续 P1 task 记录 08-28 自然恢复一个新鲜 ARM 原子批次,但连续两个自然周期及容量验收未收口;不以 08-27 失败记录倒填今日现状。
|
||||
- 发布例外已补上历史未成功恢复、异机现状未独立复核、数据回退损失和完整候选验收缺口,详见 design.md §6.9。镜像保存后可用约 8.98 GB,不能忽略本轮新增制品占用。
|
||||
- 下一步拟一次只读核对两端容量及异机链,等待限定管理员通道授权;不实际恢复、不触发备份、不清理、不部署。
|
||||
|
||||
## 2026-09-08 管理员只读备份校验脚本交接(用户已回传完整性通过)
|
||||
|
||||
- 用户确认能使用具备备份读取权限的服务器管理员账号,并单独批准上传脚本、由用户在管理员终端执行。该授权不包含执行端提权或自行读取受限备份。
|
||||
- 校验脚本已上传至系统临时目录下独立目录 hyb-oa-backup-check-7fptuwbq,目录 0700、文件 0600;文件名 check-backup-admin.py。上传后复读 SHA-256 与本地一致:58a604fc1e0ebab824553fa0507683ed5d8c44872d36d56c41574d8959ea6a35。
|
||||
- 本地合成正例、数据库包损坏、状态文件缺字段反例通过;仅上传,未执行远端校验脚本、未读取备份、未改变既有权限。回执 admin-script-upload-01.json 存于既有 rollback-image-20260908 证据目录。
|
||||
- 交给用户的命令先校验脚本内容摘要,再以 Python 隔离模式执行同一份已读字节;只回传脱敏校验结果。用户已回传 PASS_INTEGRITY_ONLY:manifest、数据库包、附件包摘要/大小匹配,备份版本 f223bfb,完成于北京时间 2026-09-08 02:30:51,校验时年龄 9.56 小时;restoreTested=false。
|
||||
- 结果归因为用户在管理员终端执行,不冒充执行端直接核验。管理员 SSH 别名发现过晚造成不必要手工操作;后续先检查既有通道,再请求限定授权,不将连接配置当作已有无限授权。详见 design.md §6.8。
|
||||
- 镜像独立保存、备份完整性和 seed 比对结果已具备,发布例外仍未批准。恢复实测、完整候选验收及恢复备份后丢失后续业务数据的风险仍须明确;未追加生产操作。
|
||||
|
||||
## 2026-09-08 独立旧镜像制品保存(已获批执行并完成校验)
|
||||
|
||||
- 用户对“保存 f223bfb 当前运行镜像为独立文件并校验;先检查空间、不重启、不切换、不删旧镜像、不部署”明确回复同意。
|
||||
- 执行范围:核验容器/镜像身份及目标磁盘空间,在部署身份的持久用户目录中创建独立私有制品目录,docker save 固定镜像 ID;核对归档、配置 ID、层摘要,记录文件 SHA-256 和前后运行身份。空间不足或身份漂移停止,失败产物不冒充可用制品。
|
||||
- 不读取受限数据库备份、不变更现有权限、不执行 docker load/镜像切换/清理;本授权不是备份验真、恢复演练或发布授权。
|
||||
- 09:48:50 CST 保存完成:旧运行镜像 ID 与预检一致;独立 image.tar 为 1,117,595,648 字节,配置摘要及 14/14 层摘要匹配,另起只读校验进程复读 SHA-256 通过。容器身份、启动时间和运行版本未变。
|
||||
- 制品位于部署身份持久用户目录、Docker 存储之外;私有目录 0700、文件 0600。它仍是同机副本,未做 docker load 或恢复演练,不覆盖主机/磁盘损坏风险。位置锚点、文件名与完整摘要见 design.md §6.7 及脱敏回执。
|
||||
|
||||
## 2026-09-08 剩余 seed 对象比对(已完成,不替代恢复前置)
|
||||
|
||||
- 09:22:19 CST 只读快照:费用类型 44、编号 1、分类 11、考勤 1、预算规则 1、假期类型 3、假期额度策略 3、工作区 1、部门 2,共 67 项,均唯一存在且实际覆写字段一致;CCB 已存在、3 条审批规则走保留分支。
|
||||
- 这是配置目标字段一致,不是 seed 无写入。未查询员工假期余额;管理账号分支引用早先证据,本次未重查。前一轮 14 开关/四工作流/39 日历与本轮不是同一快照。细项与限制见 design.md §6.6。
|
||||
- 只读事务 ROLLBACK 完成;源摘要匹配固定候选。比较脚本语法、比较原语正反检查和 67 项结果断言通过;首次本地自检路径映射错误已修正,无生产重试。
|
||||
- 备份渠道仍待指定,独立镜像制品保存未获明确执行授权;不自动把补查完成转换为发布资格。R1、阶段 B、58 条及业务消息状态不变。
|
||||
|
||||
## 2026-09-08 seed 只读差异补查(已完成三组对比,仍不发布)
|
||||
|
||||
- 按用户授权比对默认 14 个强制开启开关、四类工作流和 CN_2026_STD 日历;实际 init 额外 flags 为 0。08:42:41 CST 快照结果:14/14 开关目标字段一致;四类仅 v1 激活且定义一致;日历表头及 39/39 日条目一致,无多余日期或待补主体绑定。
|
||||
- 查询严格 READ ONLY 并完成 ROLLBACK;未执行 seed。即使目标值一致,version 递增、工作流取消激活再写回、日历删除重建仍会发生;其余 seed 写入对象未逐字段比对,不宣称全量无副作用。
|
||||
- 证据在 artifacts/qa-flow/manager-ap-access-scope/seed-readonly-diff-20260908/;首次 Compose v2 不兼容失败与后续 v1 成功结果分开保留。详见 design.md §6.5。
|
||||
- 备份验真仍受授权通道限制,独立回滚制品未固定;不绕权限、不擅自执行制品写操作、不批准或执行部署。58 条、阶段 B、R1 与业务消息状态不变。
|
||||
|
||||
## 2026-09-08 获批生产只读预检(已执行,未部署)
|
||||
|
||||
- 用户单独批准只读预检及 seed 行为核实。本轮从固定 58dcee0 干净隔离检出,以 PRODUCTION_READ_ONLY_PREFLIGHT=1 执行既有 bootstrap;exit 0,13/13 合同检查,迁移 pending=none,既有回滚窗口 OPEN,明确未进入 deploy.sh。
|
||||
- 生产 BUILD_SHA=f223bfb;对候选 7 提交/103 文件,其中应用源码 7 文件 +11/-4,deploy/schema/migration/seed 无差异。seed 在生产容器内的摘要也与候选一致。
|
||||
- 只读 seed 账号分支为唯一 active admin+employee,重跑角色集合不新增,但仍重写角色记录/时间。配置 seed 会更新或重置多类配置,不是零写入;细节见 design.md §6。
|
||||
- 备份服务本日 02:30 成功、合同版本一致;备份目录元数据权限不足,未验证 manifest/备份文件摘要。旧运行镜像可 inspect,但未固定独立回滚制品,不把预检 PASS 扩大为备份恢复和发布资格 PASS。
|
||||
- 未改生产权限或通道,未部署、迁移、seed、真实业务写入或查个人访问日志;只读 SQL 显式 READ ONLY 并 ROLLBACK。业务消息仍未发送。
|
||||
- 本轮只补两份既有 task 文档与忽略目录证据,不提交、不推送,不修新问题。两个未跟踪其他 task 原样保留。
|
||||
|
||||
## 2026-09-08 发布方案准备(仅文档)
|
||||
|
||||
- 提交推送已完成:门禁 `9916712`、AP `58dcee0`,与已有两个 Trellis 提交一并到远端。此处如实补记实际结果,不生成第三笔提交或再次推送。
|
||||
- 用户同意继续准备发布方案;已在 design.md §5 分开写单次例外批准、验证缺口、回滚验收。生产预检、执行发布、seed 写入及回滚均未授权或执行。
|
||||
- 本轮只读仓库发现既有部署 run-init 包含迁移和 seed,不能承诺“无 schema 改动即零部署数据写入”;实际生产基线和回滚点待获批只读预检。
|
||||
- 业务三个具体问题仍待真实人员沟通,没有发送工具,不代发、不记为已完成。非建行限制应按既有付款工具合同解释,不能未经业务决定扩展成所有付款路径禁止。
|
||||
|
||||
## 2026-09-08 独立复核与提交授权
|
||||
|
||||
- 用户已独立复核通过本 task 的 18 文件包,包括单权限守卫、原有获准角色无误伤、经理复核保留、前后端正反测试与 AP-only 边界;不是主会话自检替代独立复核。
|
||||
@@ -55,3 +206,21 @@ git diff --check
|
||||
|
||||
- 复核代码、正反权限结果和范围;本轮不请求或执行生产变更。task 保持 in_progress。
|
||||
- 明确保留:经理仍可从报销列表查看全部单据,本轮只收窄 AP 入口,未实现所有业务域“只看相关单据”。
|
||||
|
||||
|
||||
## 2026-09-09 T0 独立交审副本
|
||||
|
||||
用户批准后续规划开始本地实施。AP 10 个既有差异文件已完整复制到专属 detached worktree,基线为 58dcee0;只在此副本纠正 task.json 的陈旧 CEO 授权描述并追加本节。付款按钮/制单测试的四文件差异未进入本工作树,原主工作树全部改动保留作对照,不宣称原混合工作树已清空。
|
||||
|
||||
隔离副本后端5文件124/124通过,保留付款复核/导出旁路回归。首轮123通过、1条BIFF8导出预期200实际409;本地默认Python缺xlwt/xlutils,补充已有固定PAYMENT_ORDER_PYTHON_BIN后同一集合124/124通过,代码与断言无改动。未重跑浏览器,既有独立复核及浏览器结果仍为此前证据,不倒写成本轮复跑。
|
||||
|
||||
| 代码 | 发布 | 业务验收 | 等谁 |
|
||||
| --- | --- | --- | --- |
|
||||
| CEO撤权修订已复核,独立副本已隔离复验;摘要已纠正 | 未提交/推送/部署;58dcee0不是最终修复候选 | 真实账号线上AP权限尚未做新版本验收;其他view_all仍保留 | 等明确提交授权,之后推送/部署按授权推进 |
|
||||
|
||||
与本轮隔离/校验证据的关联见原主工作区 `artifacts/qa-flow/project-roadmap-execution-20260909/`。本次是主会话自检,不冒充新增独立审核。提交建议标题:`fix: 取消CEO默认AP台账权限(09-07-manager-ap-access-scope)`;标题建议不代表已经提交。
|
||||
|
||||
|
||||
## 2026-09-09 提交与推送授权
|
||||
|
||||
用户对“AP 与付款修复分两笔提交并推送,不部署、不执行真实恢复、不混入后续准备文档”明确回复“是”。本次只提交本 task 对应已隔离交审文件及当前授权记录;原恢复 task、错误豁免后续阶段和另外两个未跟踪 task 不进入这两笔。实际提交 SHA 与远端结果以 Git 回执为准,不提前宣称 push 成功;生产仍未部署,业务验收状态不随提交改变。
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
## Goal
|
||||
|
||||
仅收窄 AP 财务台账与付款汇总的全量读取入口:只有经理/付款复核职责的账号不得借付款复核权限访问全公司 AP 数据;保留 CEO 全量读取、既有财务读取及经理正常审批和付款复核能力。
|
||||
仅收窄 AP 财务台账与付款汇总的全量读取入口:只有经理/付款复核职责的账号不得借付款复核权限访问全公司 AP 数据;保留既有财务读取及经理、CEO 正常审批和付款复核能力;CEO 角色本身不授予全量读取。
|
||||
|
||||
2026-09-07 用户已批准 AP-only 方案实施,附修订:使用 finance.ledger.view 专用权限,不使用 CEO 角色分支。仅授予 ceo、finance、finance_mgr;前后端同步。当前 in_progress;不查询生产、不部署。
|
||||
2026-09-07 用户已批准 AP-only 方案实施,附修订:使用 finance.ledger.view 专用权限,不使用 CEO 角色分支。原授予 ceo、finance、finance_mgr;2026-09-08 身份确认后用户批准修订为仅 finance、finance_mgr,保留 admin 通配,ceo 审批与复核不变。另批准限定员工经理关系只读查询;不写生产、不部署。
|
||||
|
||||
## 已确认事实与边界
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
## Requirements
|
||||
|
||||
- R1:仅持经理/付款复核职责、没有其他 AP 全量授权的账号访问两个 AP GET 接口必须被后端拒绝,不返回汇总、台账或计数;不得仅靠隐藏前端菜单。
|
||||
- R2:CEO、finance、finance_mgr 通过专用权限保留两个接口的全量读取;旧付款/风险权限码本身不再隐含读取。不得通过授予 CEO 付款管理等写权限实现读取,也不得删除经理的 `payment.batch.review`。
|
||||
- R2:仅 finance、finance_mgr 通过专用权限保留两个接口的全量读取;仅 CEO 角色拒绝;旧付款/风险权限码本身不再隐含读取。不得通过授予 CEO 付款管理等写权限实现读取,也不得删除经理的 `payment.batch.review`。
|
||||
- R3:经理原有正常审批、付款复核及独立复核约束不变。前端提供既有付款复核入口,不以恢复 AP 全量访问解决导航问题。
|
||||
- R4:多角色按显式授权并集合成;manager 与已授权财务角色组合不得因包含 manager 被一票否决。系统管理员身份不自动等同财务权限。
|
||||
- R5:查询参数、日期、关键词、分页不能绕过 AP 读取守卫。导出保持原权限,不将 CEO 全量读取等同导出或写入授权。
|
||||
@@ -37,11 +37,11 @@
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] 普通员工和仅经理角色对两个 GET 均被拒绝;功能开启时为 403,关闭时可保留既有无数据 409。拒绝路径不调用财务数据查询。
|
||||
- [ ] CEO、finance、finance_mgr 正例通过;manager+财务组合保留其财务授权;仅系统管理员不新增授权。
|
||||
- [ ] 普通员工、仅经理及仅 CEO 角色对两个 GET 均被拒绝;功能开启时为 403,关闭时可保留既有无数据 409。拒绝路径不调用财务数据查询。
|
||||
- [ ] finance、finance_mgr 正例通过;manager+财务及 ceo+财务组合保留其财务授权;仅系统管理员不新增授权。
|
||||
- [ ] 日期、状态、关键词等查询组合不能改变拒绝结果;经理 AP 导出仍拒绝,既有导出角色不退化。
|
||||
- [ ] 经理付款复核正例及原有独立复核负例通过;不扩大其付款执行、组批或其他写权限。
|
||||
- [ ] 前端 AP 直连被阻止、导航不暴露不可用入口,经理付款复核入口仍可达;CEO 和财务 AP 入口保留。
|
||||
- [ ] 前端 AP 直连被阻止、导航不暴露不可用入口,经理付款复核入口仍可达;仅 CEO 的 AP 入口拒绝,财务 AP 入口保留。
|
||||
- [ ] 六个 view_all 未修改,交付明确“仅 AP 收窄”,不冒充所有业务单据范围收窄。
|
||||
- [ ] 针对性验证与只读独立复核有记录;无部署、生产查询或写入,除非另获明确授权。
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
- Fastify inject:两个 AP GET、筛选组合、旧权限码反例、专用权限正例、feature-off、无权时零财务查询。
|
||||
- 角色与导航单测:经理、CEO、财务、财务经理、系统管理员、多角色;保持经理付款入口。
|
||||
- 既有 AP 导出及付款复核回归:保留独立授权、制单/复核分离及 CEO 无新增导出权。
|
||||
- 本地合成浏览器:经理直连 403 和零 AP 请求、CEO/财务/财务经理/混合角色进入 AP 和台账、经理付款页保留;零重试、trace,排除合同用例及其他红项。
|
||||
- 本地合成浏览器:经理和 CEO 直连 403 和零 AP 请求、财务/财务经理/混合角色进入 AP 和台账、经理付款页保留;零重试、trace,排除合同用例及其他红项。
|
||||
- 前端 typecheck、隔离目录 build、语法、diff、治理检查。扩大付款回归需既有固定 BIFF8 Python 依赖,不改产品逻辑绕过环境缺项。
|
||||
- 独立只读权限复核待用户;inline 主会话自检不冒充独立复核。
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"id": "manager-ap-access-scope",
|
||||
"name": "manager-ap-access-scope",
|
||||
"title": "经理 AP 财务台账与付款汇总访问收紧",
|
||||
"description": "使用 finance.ledger.view 收紧 AP 全量读取,保留 CEO 财务读取与经理复核;其他 view_all 和导出授权不变,不部署",
|
||||
"description": "使用 finance.ledger.view 收紧 AP 全量读取,仅 finance/finance_mgr 授予,保留 admin 通配;CEO/经理审批与付款复核、其他 view_all 和导出授权不变。实现已复核,生产发布单独验收。",
|
||||
"status": "in_progress",
|
||||
"dev_type": null,
|
||||
"scope": "AP-only 专用读取权限、对应前后端投影和权限验证",
|
||||
|
||||
@@ -68,7 +68,6 @@ const ROLE_PERMISSIONS: Record<string, readonly string[]> = {
|
||||
"payment.batch.review",
|
||||
],
|
||||
ceo: [
|
||||
"finance.ledger.view",
|
||||
...EMPLOYEE_BASE_PERMISSIONS,
|
||||
...PROCUREMENT_SELF_SERVICE_PERMISSIONS,
|
||||
...MASTERDATA_VIEW_PERMISSIONS,
|
||||
|
||||
@@ -346,25 +346,28 @@ async function login(page: Page, actor: SyntheticActor) {
|
||||
await expect(page).toHaveURL(/\/(?:portal|mobile)$/);
|
||||
}
|
||||
|
||||
test("只有付款复核职责的经理不能直连应付账款且不请求台账", async ({ page }) => {
|
||||
test.skip(process.env.E2E_MODE === "live", "只使用本地合成角色,不访问公网");
|
||||
const pageErrors = capturePageErrors(page);
|
||||
await mockSyntheticApis(page, REVIEWER_ACTOR);
|
||||
await login(page, REVIEWER_ACTOR);
|
||||
for (const role of ["manager", "ceo"]) {
|
||||
test(`只有付款复核职责的 ${role} 不能直连应付账款且不请求台账`, async ({ page }) => {
|
||||
test.skip(process.env.E2E_MODE === "live", "只使用本地合成角色,不访问公网");
|
||||
const pageErrors = capturePageErrors(page);
|
||||
await mockSyntheticApis(page, { ...REVIEWER_ACTOR, roleCodes: [role] });
|
||||
await login(page, REVIEWER_ACTOR);
|
||||
|
||||
let apRequests = 0;
|
||||
page.on("request", (request) => {
|
||||
if (new URL(request.url()).pathname.startsWith("/api/ap-operations")) apRequests += 1;
|
||||
let apRequests = 0;
|
||||
page.on("request", (request) => {
|
||||
if (new URL(request.url()).pathname.startsWith("/api/ap-operations")) apRequests += 1;
|
||||
});
|
||||
await page.goto("/ap-operations");
|
||||
await expect(page).toHaveURL(/\/403$/);
|
||||
await expect(page.getByRole("link", { name: "应付账款", exact: true })).toHaveCount(0);
|
||||
expect(apRequests).toBe(0);
|
||||
await expect(page.getByTestId("ap-export")).toHaveCount(0);
|
||||
expectNoPageErrors(pageErrors);
|
||||
});
|
||||
await page.goto("/ap-operations");
|
||||
await expect(page).toHaveURL(/\/403$/);
|
||||
await expect(page.getByRole("link", { name: "应付账款", exact: true })).toHaveCount(0);
|
||||
expect(apRequests).toBe(0);
|
||||
await expect(page.getByTestId("ap-export")).toHaveCount(0);
|
||||
expectNoPageErrors(pageErrors);
|
||||
});
|
||||
|
||||
for (const roleCodes of [["ceo"], ["finance"], ["finance_mgr"], ["manager", "finance"]]) {
|
||||
}
|
||||
|
||||
for (const roleCodes of [["finance"], ["finance_mgr"], ["manager", "finance"], ["ceo", "finance"]]) {
|
||||
test(`AP 专用读取权限允许 ${roleCodes.join("+")} 进入台账且不提升导出权限`, async ({ page }) => {
|
||||
test.skip(process.env.E2E_MODE === "live", "只使用本地合成角色,不访问公网");
|
||||
const actor = { ...FINANCE_ACTOR, roleCodes };
|
||||
@@ -375,8 +378,7 @@ for (const roleCodes of [["ceo"], ["finance"], ["finance_mgr"], ["manager", "fin
|
||||
await expect(page.getByRole("heading", { level: 1, name: "应付账款", exact: true })).toBeVisible();
|
||||
await page.getByRole("tab", { name: /^财务台账/ }).click();
|
||||
await expect(page.getByTestId("finance-ledger-table")).toBeVisible();
|
||||
if (roleCodes.includes("ceo")) await expect(page.getByTestId("ap-export")).toHaveCount(0);
|
||||
else await expect(page.getByTestId("ap-export")).toBeVisible();
|
||||
await expect(page.getByTestId("ap-export")).toBeVisible();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -162,7 +162,6 @@ const ROLE_PERMISSIONS = {
|
||||
"payment.batch.review",
|
||||
],
|
||||
ceo: [
|
||||
"finance.ledger.view",
|
||||
...EMPLOYEE_BASE_PERMISSIONS,
|
||||
...PROCUREMENT_SELF_SERVICE_PERMISSIONS,
|
||||
...MASTERDATA_VIEW_PERMISSIONS,
|
||||
|
||||
@@ -6,13 +6,18 @@ const {
|
||||
|
||||
describe("Permission Constants", () => {
|
||||
it("separates full AP reading from payment review and preserves multi-role grants", () => {
|
||||
for (const role of ["ceo", "finance", "finance_mgr"]) {
|
||||
for (const role of ["finance", "finance_mgr"]) {
|
||||
expect(hasPermission(resolvePermissionsForRoles([role]), "finance.ledger.view")).toBe(true);
|
||||
}
|
||||
for (const role of ["manager", "employee", "system_admin", "hr", "administration"]) {
|
||||
for (const role of ["ceo", "manager", "employee", "system_admin", "hr", "administration"]) {
|
||||
expect(hasPermission(resolvePermissionsForRoles([role]), "finance.ledger.view")).toBe(false);
|
||||
}
|
||||
expect(hasPermission(resolvePermissionsForRoles(["manager", "finance"]), "finance.ledger.view")).toBe(true);
|
||||
for (const role of ["manager", "ceo"]) {
|
||||
expect(hasPermission(resolvePermissionsForRoles([role, "finance"]), "finance.ledger.view")).toBe(true);
|
||||
for (const permission of ["expense.approve", "leave.approve", "attendance.approve", "procurement.approve", "payment.batch.review"]) {
|
||||
expect(hasPermission(resolvePermissionsForRoles([role]), permission)).toBe(true);
|
||||
}
|
||||
}
|
||||
expect(hasPermission(resolvePermissionsForRoles(["manager"]), "payment.batch.review")).toBe(true);
|
||||
expect(hasPermission(resolvePermissionsForRoles(["ceo"]), "payment.batch.manage")).toBe(false);
|
||||
});
|
||||
|
||||
@@ -44,7 +44,7 @@ async function buildApp(permissionCodes, enabled = true) {
|
||||
describe("AP full-ledger permission boundary", () => {
|
||||
it.each([
|
||||
["employee", false], ["manager", false], ["system_admin", false],
|
||||
["ceo", true], ["finance", true], ["finance_mgr", true], ["admin", true],
|
||||
["ceo", false], ["finance", true], ["finance_mgr", true], ["admin", true],
|
||||
])("checks both reads for %s", async (role, allowed) => {
|
||||
const { app, queries } = await buildApp(resolvePermissionsForRoles([role]));
|
||||
try {
|
||||
@@ -74,7 +74,7 @@ describe("AP full-ledger permission boundary", () => {
|
||||
},
|
||||
);
|
||||
|
||||
it.each([["finance.ledger.view"], resolvePermissionsForRoles(["manager", "finance"])].map((permissions) => [permissions]))(
|
||||
it.each([["finance.ledger.view"], resolvePermissionsForRoles(["manager", "finance"]), resolvePermissionsForRoles(["ceo", "finance"])].map((permissions) => [permissions]))(
|
||||
"honors explicit or combined-role ledger authority %j", async (permissions) => {
|
||||
const { app } = await buildApp(permissions);
|
||||
try {
|
||||
|
||||
@@ -64,8 +64,8 @@ describe("workspace small-team navigation defaults", () => {
|
||||
hasPermission: (candidate, permission) => hasPermission(candidate.permissionCodes, permission),
|
||||
});
|
||||
const keys = Object.values(navigation.sections).flat().map((item) => item.key);
|
||||
expect(keys.includes("apOperations")).toBe(["ceo", "finance", "finance_mgr"].includes(role));
|
||||
if (role === "manager") expect(keys).toContain("paymentBatches");
|
||||
expect(keys.includes("apOperations")).toBe(["finance", "finance_mgr"].includes(role));
|
||||
if (["manager", "ceo"].includes(role)) expect(keys).toContain("paymentBatches");
|
||||
});
|
||||
|
||||
it("keeps only the small-team task path in default main navigation", () => {
|
||||
|
||||
在新工单中引用
屏蔽一个用户