OAAUTO1 执行自动化收口证据

这个提交包含在:
2026-05-23 23:34:29 +08:00
父节点 141adc8cd9
当前提交 a3f13eab57
共修改 2 个文件,包含 100 行新增和 8 行删除
@@ -1,18 +1,18 @@
"ID","Title","Description","Acceptance","Test_Method","Tools","Dev_Status","Review1_Status","Regression_Status","Files","Dependencies","Notes"
"OAAUTO1-0","建立自动化与人工收口拆分合同","基于 OASMALL1、OANEXT2、OALAUNCH1 和 OAOPS1 当前状态,建立启用前下一步执行拆分:我能自动完成的工作、必须人工完成的工作、自动化不能替代的边界和推荐顺序。","Issue CSV 校验通过;明确本合同只规划和细化步骤,不写公网业务数据、不部署、不修改业务代码;所有执行项需用户后续采纳后再进入对应合同。","受影响流程:启用前收口 -> 自动化/人工分工 -> 用户采纳 -> 后续执行;验证步骤:python3 /home/shunli/.codex/skills/plan/scripts/validate_issues_csv.py issues/2026-05-23_23-01-55-oa-automation-manual-cutover-v1.csv。","python3|manual","DONE","DONE","DONE","issues/2026-05-23_23-01-55-oa-automation-manual-cutover-v1.csv","[]","MAGI 审视:自动化已覆盖 OCR、权限、TEST/SMOKE 技术闭环和每日巡检;执行:拆分剩余自动/人工工作;提升:自动先扫风险,人工只做真实业务和决策。回滚/清理:本合同只新增 Issue CSV,若不采纳可删除或后续标记 superseded,不影响公网、数据库或业务数据。"
"OAAUTO1-1","当前剩余门禁基线","核对当前最终启用仍未完成的门禁:真实业务闭环、备份恢复演练、数据治理/培训、7 天稳定化、Day 7 Go/No-Go;权限矩阵、OCR 小样本、TEST/SMOKE 业务闭环已具备自动证据。","形成明确基线:自动证据不等于正式启用;OALAUNCH1 仍需真实业务、恢复和人工确认。","受影响流程:Issue 状态读取 -> 证据基线 -> 后续顺序;验证步骤:读取 OALAUNCH1-3/4/6/7、OAOPS1-3/4/5/6、OANEXT2-6/7/8/9/10 当前状态。","python3|rg|manual","DONE","DONE","DONE","issues/2026-05-23_16-49-39-oa-final-launch-readiness-v1.csv;issues/2026-05-22_20-09-22-oa-real-use-stabilization-v1.csv;issues/2026-05-23_20-47-20-oa-next-adoption-detailed-roadmap-v2.csv","[""OAAUTO1-0""]","当前证据:权限矩阵 PASS_PERMISSION_MATRIX_READY;TEST/SMOKE 技术闭环 PASS_TEST_SMOKE_BUSINESS_LOOP_READY;OCR 小样本 coverage_satisfied;OALAUNCH1 summary 仍 PAUSED_MANUAL_CONFIRMATION。"
"OAAUTO1-2","自动化第 1 步:公网只读日巡检","我可以先自动运行 OAOPS1 公网只读巡检,复核 /ready、账号 readiness、feature flags、报销/OCR 入口、采购/合同/付款/AP 入口、移动/企微入口和隐私扫描。","生成 artifacts/qa-flow/oa-real-use-daily-<timestamp>/summary.json;结论为 PASS_OA_REAL_USE_DAILY_READY 或可解释 finding;报告不含密码、Secret、完整账号、票据原文或合同正文。","受影响流程:公网健康 -> 入口/权限/只读探针 -> 日收口;验证步骤:LIVE_SMOKE_BASE_URL=https://oa.hyinda.com bash scripts/qa/run-oa-real-use-daily.sh。","bash|node|playwright","TODO","TODO","TODO","scripts/qa/run-oa-real-use-daily.sh;artifacts/qa-flow/oa-real-use-daily-*","[""OAAUTO1-1""]","可自动完成;需要公网网络和 live smoke env,但不创建业务数据、不付款、不写企微。若遇到 429 或外部依赖失败,按 finding 记录。"
"OAAUTO1-3","自动化第 2 步:最终启用汇总刷新","我可以用最新权限矩阵和巡检证据自动刷新 OALAUNCH1 summary,明确还缺哪些人工或运维门禁。","生成 artifacts/qa-flow/oa-final-launch-readiness-<timestamp>/summary.json;open gates 清晰列出;在真实业务、备份和 Go/No-Go 未完成前不得输出 PASS_OA_LAUNCH_READY。","受影响流程:证据汇总 -> open gates -> Go/No-Go 前状态;验证步骤:OALAUNCH_PERMISSION_EVIDENCE=artifacts/qa-flow/oa-permission-matrix-20260523-magi/summary.json bash scripts/qa/run-oa-final-launch-readiness.sh。","bash|manual","TODO","TODO","TODO","scripts/qa/run-oa-final-launch-readiness.sh;artifacts/qa-flow/oa-final-launch-readiness-*","[""OAAUTO1-2""]","可自动完成;只读汇总,不能替代用户决策。"
"OAAUTO1-4","自动化第 3 步:权限负例补充冻结项与搜索","在现有权限矩阵基础上,我可以扩展只读自动化,覆盖冻结项、全局搜索、薪酬绩效差旅等高敏/后置模块直连负例。","普通员工无法通过导航、搜索、直连 API 访问冻结/高敏模块;授权角色必要入口可用;报告脱敏并可作为 OALAUNCH_PERMISSION_EVIDENCE 的补充。","受影响流程:多角色登录 -> 导航/搜索/API 负例 -> 脱敏报告;验证步骤:扩展或复用 scripts/qa/check-oa-permission-matrix.mjs 并运行。","node|playwright","TODO","TODO","TODO","scripts/qa/check-oa-permission-matrix.mjs;server/src/routes/search.js;client/src/features/navigation/schema.ts;server/src/routes/featureFlags.js","[""OAAUTO1-1""]","可自动完成但属于脚本增强;若修改 QA 脚本,需要先进入实现合同或更新本合同采纳状态。若发现真实权限漏洞,需新建修复合同并 reviewer 评审。"
"OAAUTO1-5","自动化第 4 步:OCR 代表样本复核","我可以按够用原则复跑 2-3 张代表 PDF 发票 OCR 小样本,验证秒级 OCR、重复/已用终态和脱敏报告;不跑 105 张全量,除非单独批准压力/覆盖专项。","summary 只含 alias、状态、耗时、字段命中布尔值和可解释失败分类;不提交报销、不审批、不付款,不输出票号、销售方、金额、完整路径或原始 payload。","受影响流程:PDF 样本 -> OCR -> 脱敏统计;验证步骤:PLAYWRIGHT_OCR_BATCH_MAX_SAMPLES=3 PLAYWRIGHT_OCR_BATCH_STOP_WHEN_COVERED=1 运行 live invoice OCR batch。","playwright|bash","TODO","TODO","TODO","client/tests/e2e/manual/live-invoice-ocr-batch.spec.ts;client/tests/e2e/helpers/liveInvoiceOcr.ts;artifacts/qa-flow/oinv1-*","[""OAAUTO1-2""]","可自动完成;用户已明确够用即可,不把 105 张全量作为启用前门禁。"
"OAAUTO1-2","自动化第 1 步:公网只读日巡检","我可以先自动运行 OAOPS1 公网只读巡检,复核 /ready、账号 readiness、feature flags、报销/OCR 入口、采购/合同/付款/AP 入口、移动/企微入口和隐私扫描。","生成 artifacts/qa-flow/oa-real-use-daily-<timestamp>/summary.json;结论为 PASS_OA_REAL_USE_DAILY_READY 或可解释 finding;报告不含密码、Secret、完整账号、票据原文或合同正文。","受影响流程:公网健康 -> 入口/权限/只读探针 -> 日收口;验证步骤:LIVE_SMOKE_BASE_URL=https://oa.hyinda.com bash scripts/qa/run-oa-real-use-daily.sh。","bash|node|playwright","DONE","DONE","DONE","scripts/qa/run-oa-real-use-daily.sh;artifacts/qa-flow/oa-real-use-daily-*","[""OAAUTO1-1""]","已完成:artifacts/qa-flow/oa-real-use-daily-20260523-231300/summary.json 为 PASS_OA_REAL_USE_DAILY_READY,findingCount=0;覆盖 /ready、live readiness、OA-only readiness、feature flags、认证只读探针、移动入口、企微渲染和 wecom-entry;不创建业务数据、不付款、不写企微。"
"OAAUTO1-3","自动化第 2 步:最终启用汇总刷新","我可以用最新权限矩阵和巡检证据自动刷新 OALAUNCH1 summary,明确还缺哪些人工或运维门禁。","生成 artifacts/qa-flow/oa-final-launch-readiness-<timestamp>/summary.json;open gates 清晰列出;在真实业务、备份和 Go/No-Go 未完成前不得输出 PASS_OA_LAUNCH_READY。","受影响流程:证据汇总 -> open gates -> Go/No-Go 前状态;验证步骤:OALAUNCH_PERMISSION_EVIDENCE=artifacts/qa-flow/oa-permission-matrix-20260523-magi/summary.json bash scripts/qa/run-oa-final-launch-readiness.sh。","bash|manual","DONE","DONE","DONE","scripts/qa/run-oa-final-launch-readiness.sh;artifacts/qa-flow/oa-final-launch-readiness-*","[""OAAUTO1-2""]","已完成:artifacts/qa-flow/oa-final-launch-readiness-20260523-231746/summary.json 为 PAUSED_MANUAL_CONFIRMATION,openCount=12,无新增 finding;仍缺真实业务、备份恢复、7 天稳定化和 Day 7 Go/No-Go。"
"OAAUTO1-4","自动化第 3 步:权限负例补充冻结项与搜索","在现有权限矩阵基础上,我可以扩展只读自动化,覆盖冻结项、全局搜索、薪酬绩效差旅等高敏/后置模块直连负例。","普通员工无法通过导航、搜索、直连 API 访问冻结/高敏模块;授权角色必要入口可用;报告脱敏并可作为 OALAUNCH_PERMISSION_EVIDENCE 的补充。","受影响流程:多角色登录 -> 导航/搜索/API 负例 -> 脱敏报告;验证步骤:扩展或复用 scripts/qa/check-oa-permission-matrix.mjs 并运行。","node|playwright","TODO","TODO","TODO","scripts/qa/check-oa-permission-matrix.mjs;server/src/routes/search.js;client/src/features/navigation/schema.ts;server/src/routes/featureFlags.js","[""OAAUTO1-1""]","已增强 QA 脚本并运行 artifacts/qa-flow/oa-permission-matrix-20260523-oauto1/summary.json,结论 SKIP/BLOCKED_BY_EXTERNAL_CONFIG:差旅列表对 employee/manager/finance 返回 200 空列表,HR 可访问 payroll/performance,manager 搜索可见合同台账摘要。未改生产权限;需用户决定这是既有授权口径还是上线前冻结项修复。回滚/清理:若不采纳增强检查,可回退本 QA 脚本改动或保留为非阻断报告;若后续修生产权限,需独立 Issue、reviewer 和 feature flag/权限回滚方案。"
"OAAUTO1-5","自动化第 4 步:OCR 代表样本复核","我可以按够用原则复跑 2-3 张代表 PDF 发票 OCR 小样本,验证秒级 OCR、重复/已用终态和脱敏报告;不跑 105 张全量,除非单独批准压力/覆盖专项。","summary 只含 alias、状态、耗时、字段命中布尔值和可解释失败分类;不提交报销、不审批、不付款,不输出票号、销售方、金额、完整路径或原始 payload。","受影响流程:PDF 样本 -> OCR -> 脱敏统计;验证步骤:PLAYWRIGHT_OCR_BATCH_MAX_SAMPLES=3 PLAYWRIGHT_OCR_BATCH_STOP_WHEN_COVERED=1 运行 live invoice OCR batch。","playwright|bash","DONE","DONE","DONE","client/tests/e2e/manual/live-invoice-ocr-batch.spec.ts;client/tests/e2e/helpers/liveInvoiceOcr.ts;artifacts/qa-flow/oinv1-*","[""OAAUTO1-2""]","已完成:artifacts/qa-flow/oinv1-redacted-batch-oauto1-20260523-2318/summary.json,plannedSamples=3,available=3,字段命中 3/3,耗时约 0.2-2.4 秒;报告只含别名/状态/布尔命中,不提交报销、不审批、不付款。"
"OAAUTO1-6","自动化第 5 步:TEST/SMOKE 技术闭环复跑","如需回归,我可以复跑 TEST/SMOKE/OANEXT2 业务闭环,覆盖项目、合同台账、报销审批、付款批次、合成回单、已付款标记、AP 导出、采购审批和关联。","仅创建带 TEST/SMOKE 标记的数据;不执行真实银行付款、不接支付、不写 ERP、不导入银行流水、不上传真实票面原图。","受影响流程:TEST/SMOKE 报销 -> 审批 -> 付款/AP;采购 -> 合同/项目;验证步骤:OA_TEST_SMOKE_BUSINESS_WRITE=1 LIVE_SMOKE_BASE_URL=https://oa.hyinda.com node scripts/qa/run-oa-test-smoke-business-loop.mjs。","node|bash","TODO","TODO","TODO","scripts/qa/run-oa-test-smoke-business-loop.mjs;artifacts/qa-flow/oa-test-smoke-business-loop-*","[""OAAUTO1-2""]","可自动完成但会写 TEST/SMOKE 公网测试数据;每次复跑前需确认是否允许继续保留审计测试数据。"
"OAAUTO1-7","自动化第 6 步:人工证据包模板与脱敏汇总","我可以准备真实业务证据包模板和脱敏汇总方式,让人工只记录业务编号别名、状态、责任人、日期和证据路径,不暴露真实票据、回单、合同正文或银行资料。","形成可填写的 evidence 结构或 summary 输入清单;run-oa-final-launch-readiness 可读取或人工引用;不要求上传敏感原文。","受影响流程:人工真实样本 -> 脱敏 evidence -> OALAUNCH 汇总;验证步骤:检查证据包字段不含密码、完整账号、真实票号、合同正文、银行流水或原始附件。","manual|bash","TODO","TODO","TODO","artifacts/qa-flow;docs/trialrun/人工试运行报告.md;scripts/qa/run-oa-final-launch-readiness.sh","[""OAAUTO1-3""]","可自动辅助;若需要写文档模板,优先更新现有事实源,不新建孤立文档。"
"OAAUTO1-7","自动化第 6 步:人工证据包模板与脱敏汇总","我可以准备真实业务证据包模板和脱敏汇总方式,让人工只记录业务编号别名、状态、责任人、日期和证据路径,不暴露真实票据、回单、合同正文或银行资料。","形成可填写的 evidence 结构或 summary 输入清单;run-oa-final-launch-readiness 可读取或人工引用;不要求上传敏感原文。","受影响流程:人工真实样本 -> 脱敏 evidence -> OALAUNCH 汇总;验证步骤:检查证据包字段不含密码、完整账号、真实票号、合同正文、银行流水或原始附件。","manual|bash","DONE","DONE","DONE","artifacts/qa-flow;docs/trialrun/人工试运行报告.md;scripts/qa/run-oa-final-launch-readiness.sh","[""OAAUTO1-3""]","已完成:artifacts/qa-flow/oa-launch-manual-evidence-oauto1-20260523-2324/summary.json 和 manual-evidence-template.md,覆盖真实报销、采购/费用、合同台账和每日收口;只记录脱敏别名、状态、角色和证据路径。"
"OAAUTO1-8","人工第 1 步:真实报销闭环操作","员工在公网 OA 提交 1 条真实报销并上传 PDF 发票;审批人查看详情后审批;财务创建付款批次、线下银行付款、上传回单、标记已付款并导出 AP/报销/票据资料。","报销真实业务闭环完成;OA 内状态、审批记录、付款批次、回单附件、已付款标记和 AP 导出均可追溯。","受影响流程:员工提交报销 -> 审批 -> 财务付款批次 -> 回单 -> 已付款 -> AP 导出;验证步骤:人工操作后运行 OAAUTO1-3 汇总,或在 evidence 包记录脱敏状态。","manual","TODO","TODO","TODO","external|public-oa","[""OAAUTO1-7""]","人工步骤:1 登录员工账号;2 进入报销/发票上传;3 上传真实 PDF 发票;4 核对 OCR 结果;5 填金额/项目/费用类型;6 提交;7 审批人打开详情审批;8 财务建付款批次;9 在线下银行付款;10 上传回单;11 标记已付款;12 导出 AP/报销/票据。不要把票据原图、票号、银行流水或回单原文发到聊天/仓库。"
"OAAUTO1-9","人工第 2 步:真实采购或费用申请操作","员工在公网 OA 提交 1 条真实采购/费用申请,尽量关联项目或合同;审批人查看详情后通过或退回;管理员确认审计记录和项目/合同摘要可追溯。","至少 1 条真实采购/费用流程完成;有关联项目或合同;审批、退回或通过记录可查。","受影响流程:采购/费用申请 -> 审批 -> 项目/合同关联 -> 审计;验证步骤:人工操作后记录脱敏业务别名、状态和关联对象,最终汇总到 OALAUNCH。","manual","TODO","TODO","TODO","external|public-oa","[""OAAUTO1-7""]","人工步骤:1 登录员工或负责人账号;2 进入采购/费用申请;3 填申请事由、金额区间、供应商摘要和用途;4 选择项目或合同关联;5 提交;6 审批人打开详情处理;7 管理员查看列表、详情和审计记录;8 若退回,记录原因并验证可重提。敏感报价和成本明细只按公司边界上传或线下留存。"
"OAAUTO1-10","人工第 3 步:真实合同台账和归档位置操作","负责人在公网 OA 建 1 条真实合同台账,填写合同编号、合同摘要、负责人、关联项目/采购/付款、线下归档位置和敏感级别;不上传涉密正文。","至少 1 条真实合同台账可查;归档位置明确;权限检查通过;敏感正文不进入公网 OA 或自动报告。","受影响流程:合同登记 -> 关联项目/采购/付款 -> 归档位置 -> 权限抽查;验证步骤:管理员和普通员工分别查看,确认授权可见、越权不可见或敏感字段脱敏。","manual","TODO","TODO","TODO","external|public-oa","[""OAAUTO1-7""]","人工步骤:1 登录负责人或管理员账号;2 进入合同台账;3 新建合同记录;4 填编号/名称摘要/签约方摘要/负责人/日期/金额区间;5 关联项目、采购或付款;6 填线下归档位置;7 设置敏感级别;8 保存;9 用普通员工账号确认不能越权查看敏感附件或详情。涉密合同正文、敏感报价、成本细节不上传。"
"OAAUTO1-11","人工第 4 步:每日问题登记和阻断处理","7 天稳定化期间,管理员每天查看自动巡检和人工业务反馈,把问题分为 blocker、finding、feedback;blocker 当天修复、绕行或暂停流程。","每日有收口记录;blocker 有修复合同/绕行责任人/暂停决定;非阻断有 owner 和处理日期或接受理由。","受影响流程:日巡检 -> 人工反馈 -> 分类 -> 修复/绕行/暂停;验证步骤:Day 7 汇总检查 7 天记录和 unresolved blocker 数量。","manual","TODO","TODO","TODO","docs/trialrun/人工试运行报告.md;issues","[""OAAUTO1-2"",""OAAUTO1-8"",""OAAUTO1-9"",""OAAUTO1-10""]","人工步骤:1 每天固定时间查看 OAOPS1 summary;2 汇总员工/审批人/财务反馈;3 标记 blocker/finding/feedback;4 blocker 当天新建修复合同或暂停流程;5 人工绕行必须记录风险和补录责任人;6 非阻断进入反馈池。"
"OAAUTO1-12","半自动第 1 步:备份恢复演练准备","我可以准备影子库恢复检查清单、命令草案和证据模板;实际恢复范围、时间窗口和服务器操作必须由你批准,且不得影响公网生产库。","备份位置、恢复目标、验证表、附件抽样、RTO/RPO、负责人和清理路径明确;未批准前不执行恢复。","受影响流程:备份检查 -> 影子恢复 -> 抽样验证 -> 证据汇总;验证步骤:审批后按 OPS runbook 执行,OALAUNCH_BACKUP_EVIDENCE 指向恢复证据。","manual|ops|bash","TODO","TODO","TODO","docs/trialrun/OPS7运维交接Runbook.md;docs/ops/非功能基线.md","[""OAAUTO1-3""]","半自动。人工批准点:A 做影子库恢复;B 先只核对备份存在;C 暂停。禁止 destructive restore、migrate reset、覆盖公网生产库。清理只删除影子库/临时目录/临时凭据。"
"OAAUTO1-12","半自动第 1 步:备份恢复演练准备","我可以准备影子库恢复检查清单、命令草案和证据模板;实际恢复范围、时间窗口和服务器操作必须由你批准,且不得影响公网生产库。","备份位置、恢复目标、验证表、附件抽样、RTO/RPO、负责人和清理路径明确;未批准前不执行恢复。","受影响流程:备份检查 -> 影子恢复 -> 抽样验证 -> 证据汇总;验证步骤:审批后按 OPS runbook 执行,OALAUNCH_BACKUP_EVIDENCE 指向恢复证据。","manual|ops|bash","TODO","TODO","TODO","docs/trialrun/OPS7运维交接Runbook.md;docs/ops/非功能基线.md","[""OAAUTO1-3""]","已准备检查单 artifacts/qa-flow/oa-launch-manual-evidence-oauto1-20260523-2324/backup-restore-drill-checklist.md;未执行恢复,仍需人工选择 A 影子库恢复、B 只核对备份存在、C 暂停。禁止 destructive restore、migrate reset、覆盖公网生产库。"
"OAAUTO1-13","自动与人工结合:7 天稳定化执行节奏","在用户确认起始日期后,我每天自动跑 OAOPS1 和 OALAUNCH 汇总;你每天做人工问题收口和必要真实业务确认。","连续 7 天报告齐全;无不可解释 blocker;Day 7 可进入正式启用决策。","受影响流程:每日自动巡检 -> 人工日收口 -> Day 7 汇总;验证步骤:每天运行 run-oa-real-use-daily.sh;Day 7 运行 run-oa-final-launch-readiness.sh。","bash|manual","TODO","TODO","TODO","scripts/qa/run-oa-real-use-daily.sh;scripts/qa/run-oa-final-launch-readiness.sh;artifacts/qa-flow/oa-real-use-daily-*","[""OAAUTO1-8"",""OAAUTO1-9"",""OAAUTO1-10"",""OAAUTO1-12""]","建议顺序:先完成 1 条真实业务样本和备份恢复演练,再启动 7 天;不能用旧报告凑连续 7 天。"
"OAAUTO1-14","人工最终决策:Day 7 Go/No-Go","第 7 天根据最终启用汇总、真实业务证据、备份恢复、权限矩阵、SOP 和 7 天问题记录,由用户决定正式启用、带 finding 启用或暂停整改。","用户明确选择 A/B/C;A 为 PASS_OA_LAUNCH_READY 后启用;B 为 PASS_WITH_FINDINGS 并指定 owner/日期;C 为暂停启用并进入整改合同。","受影响流程:最终汇总 -> 用户决策 -> 正式启用/带 finding/暂停;验证步骤:设置 OALAUNCH_MANUAL_ACCEPT 或人工记录决策后复跑 run-oa-final-launch-readiness.sh。","manual|bash","TODO","TODO","TODO","scripts/qa/run-oa-final-launch-readiness.sh;issues/2026-05-23_16-49-39-oa-final-launch-readiness-v1.csv","[""OAAUTO1-13""]","不能自动替用户决策。选项:A 正式启用;B 带 finding 启用但指定 owner 和日期;C 暂停,先整改。"
"OAAUTO1-15","自动化替代边界说明","明确哪些可以用自动化测试替代,哪些不能:自动化可替代访问性、权限负例、OCR 可用性、技术闭环、脱敏扫描和汇总;不能替代真实付款、业务合理性、审批人判断、合同归档真实性、恢复演练接受和 Go/No-Go。","边界在后续执行中被遵守;任何报告不得把自动化 PASS 描述成真实业务已完成或正式启用已批准。","受影响流程:自动化测试 -> 人工业务 -> 启用决策;验证步骤:审查最终回复、Issue Notes 和 OALAUNCH summary 结论是否避免伪 PASS。","manual","TODO","TODO","TODO","issues/2026-05-23_23-01-55-oa-automation-manual-cutover-v1.csv","[""OAAUTO1-1""]","原则:技术可用性可自动化,业务真实性和责任承担不可自动化。"
"OAAUTO1-15","自动化替代边界说明","明确哪些可以用自动化测试替代,哪些不能:自动化可替代访问性、权限负例、OCR 可用性、技术闭环、脱敏扫描和汇总;不能替代真实付款、业务合理性、审批人判断、合同归档真实性、恢复演练接受和 Go/No-Go。","边界在后续执行中被遵守;任何报告不得把自动化 PASS 描述成真实业务已完成或正式启用已批准。","受影响流程:自动化测试 -> 人工业务 -> 启用决策;验证步骤:审查最终回复、Issue Notes 和 OALAUNCH summary 结论是否避免伪 PASS。","manual","DONE","DONE","DONE","issues/2026-05-23_23-01-55-oa-automation-manual-cutover-v1.csv","[""OAAUTO1-1""]","已执行边界:自动项只覆盖只读巡检、汇总、权限/搜索负例和 OCR 小样本;未执行真实付款、真实业务审批决策、备份恢复、TEST/SMOKE 复跑写入或 Go/No-Go。"
"OAAUTO1-16","本合同校验与停止点","完成本 CSV 校验、process gate 和 diff 检查;提交后等待用户决定是否开始 OAAUTO1-2 的自动只读巡检或先执行人工样本。","validate_issues_csv.py、check-process-contract general、git diff --check 通过;本轮不部署、不写公网、不修改业务代码。","受影响流程:规划 -> 校验 -> 用户审查 -> 后续执行;验证步骤:python3 /home/shunli/.codex/skills/plan/scripts/validate_issues_csv.py <csv>;node scripts/qa/check-process-contract.mjs --issue <csv> --profile general --json;git diff --check。","python3|node|git","DONE","DONE","DONE","issues/2026-05-23_23-01-55-oa-automation-manual-cutover-v1.csv","[""OAAUTO1-1""]","本轮只规划和细化,不执行公网巡检、不提交真实业务、不恢复备份;已通过 validate_issues_csv.py、check-process-contract general、git diff --check。"
1 ID Title Description Acceptance Test_Method Tools Dev_Status Review1_Status Regression_Status Files Dependencies Notes
2 OAAUTO1-0 建立自动化与人工收口拆分合同 基于 OASMALL1、OANEXT2、OALAUNCH1 和 OAOPS1 当前状态,建立启用前下一步执行拆分:我能自动完成的工作、必须人工完成的工作、自动化不能替代的边界和推荐顺序。 Issue CSV 校验通过;明确本合同只规划和细化步骤,不写公网业务数据、不部署、不修改业务代码;所有执行项需用户后续采纳后再进入对应合同。 受影响流程:启用前收口 -> 自动化/人工分工 -> 用户采纳 -> 后续执行;验证步骤:python3 /home/shunli/.codex/skills/plan/scripts/validate_issues_csv.py issues/2026-05-23_23-01-55-oa-automation-manual-cutover-v1.csv。 python3|manual DONE DONE DONE issues/2026-05-23_23-01-55-oa-automation-manual-cutover-v1.csv [] MAGI 审视:自动化已覆盖 OCR、权限、TEST/SMOKE 技术闭环和每日巡检;执行:拆分剩余自动/人工工作;提升:自动先扫风险,人工只做真实业务和决策。回滚/清理:本合同只新增 Issue CSV,若不采纳可删除或后续标记 superseded,不影响公网、数据库或业务数据。
3 OAAUTO1-1 当前剩余门禁基线 核对当前最终启用仍未完成的门禁:真实业务闭环、备份恢复演练、数据治理/培训、7 天稳定化、Day 7 Go/No-Go;权限矩阵、OCR 小样本、TEST/SMOKE 业务闭环已具备自动证据。 形成明确基线:自动证据不等于正式启用;OALAUNCH1 仍需真实业务、恢复和人工确认。 受影响流程:Issue 状态读取 -> 证据基线 -> 后续顺序;验证步骤:读取 OALAUNCH1-3/4/6/7、OAOPS1-3/4/5/6、OANEXT2-6/7/8/9/10 当前状态。 python3|rg|manual DONE DONE DONE issues/2026-05-23_16-49-39-oa-final-launch-readiness-v1.csv;issues/2026-05-22_20-09-22-oa-real-use-stabilization-v1.csv;issues/2026-05-23_20-47-20-oa-next-adoption-detailed-roadmap-v2.csv ["OAAUTO1-0"] 当前证据:权限矩阵 PASS_PERMISSION_MATRIX_READY;TEST/SMOKE 技术闭环 PASS_TEST_SMOKE_BUSINESS_LOOP_READY;OCR 小样本 coverage_satisfied;OALAUNCH1 summary 仍 PAUSED_MANUAL_CONFIRMATION。
4 OAAUTO1-2 自动化第 1 步:公网只读日巡检 我可以先自动运行 OAOPS1 公网只读巡检,复核 /ready、账号 readiness、feature flags、报销/OCR 入口、采购/合同/付款/AP 入口、移动/企微入口和隐私扫描。 生成 artifacts/qa-flow/oa-real-use-daily-<timestamp>/summary.json;结论为 PASS_OA_REAL_USE_DAILY_READY 或可解释 finding;报告不含密码、Secret、完整账号、票据原文或合同正文。 受影响流程:公网健康 -> 入口/权限/只读探针 -> 日收口;验证步骤:LIVE_SMOKE_BASE_URL=https://oa.hyinda.com bash scripts/qa/run-oa-real-use-daily.sh。 bash|node|playwright TODO DONE TODO DONE TODO DONE scripts/qa/run-oa-real-use-daily.sh;artifacts/qa-flow/oa-real-use-daily-* ["OAAUTO1-1"] 可自动完成;需要公网网络和 live smoke env,但不创建业务数据、不付款、不写企微。若遇到 429 或外部依赖失败,按 finding 记录。 已完成:artifacts/qa-flow/oa-real-use-daily-20260523-231300/summary.json 为 PASS_OA_REAL_USE_DAILY_READY,findingCount=0;覆盖 /ready、live readiness、OA-only readiness、feature flags、认证只读探针、移动入口、企微渲染和 wecom-entry;不创建业务数据、不付款、不写企微。
5 OAAUTO1-3 自动化第 2 步:最终启用汇总刷新 我可以用最新权限矩阵和巡检证据自动刷新 OALAUNCH1 summary,明确还缺哪些人工或运维门禁。 生成 artifacts/qa-flow/oa-final-launch-readiness-<timestamp>/summary.json;open gates 清晰列出;在真实业务、备份和 Go/No-Go 未完成前不得输出 PASS_OA_LAUNCH_READY。 受影响流程:证据汇总 -> open gates -> Go/No-Go 前状态;验证步骤:OALAUNCH_PERMISSION_EVIDENCE=artifacts/qa-flow/oa-permission-matrix-20260523-magi/summary.json bash scripts/qa/run-oa-final-launch-readiness.sh。 bash|manual TODO DONE TODO DONE TODO DONE scripts/qa/run-oa-final-launch-readiness.sh;artifacts/qa-flow/oa-final-launch-readiness-* ["OAAUTO1-2"] 可自动完成;只读汇总,不能替代用户决策。 已完成:artifacts/qa-flow/oa-final-launch-readiness-20260523-231746/summary.json 为 PAUSED_MANUAL_CONFIRMATION,openCount=12,无新增 finding;仍缺真实业务、备份恢复、7 天稳定化和 Day 7 Go/No-Go。
6 OAAUTO1-4 自动化第 3 步:权限负例补充冻结项与搜索 在现有权限矩阵基础上,我可以扩展只读自动化,覆盖冻结项、全局搜索、薪酬绩效差旅等高敏/后置模块直连负例。 普通员工无法通过导航、搜索、直连 API 访问冻结/高敏模块;授权角色必要入口可用;报告脱敏并可作为 OALAUNCH_PERMISSION_EVIDENCE 的补充。 受影响流程:多角色登录 -> 导航/搜索/API 负例 -> 脱敏报告;验证步骤:扩展或复用 scripts/qa/check-oa-permission-matrix.mjs 并运行。 node|playwright TODO TODO TODO scripts/qa/check-oa-permission-matrix.mjs;server/src/routes/search.js;client/src/features/navigation/schema.ts;server/src/routes/featureFlags.js ["OAAUTO1-1"] 可自动完成但属于脚本增强;若修改 QA 脚本,需要先进入实现合同或更新本合同采纳状态。若发现真实权限漏洞,需新建修复合同并 reviewer 评审。 已增强 QA 脚本并运行 artifacts/qa-flow/oa-permission-matrix-20260523-oauto1/summary.json,结论 SKIP/BLOCKED_BY_EXTERNAL_CONFIG:差旅列表对 employee/manager/finance 返回 200 空列表,HR 可访问 payroll/performance,manager 搜索可见合同台账摘要。未改生产权限;需用户决定这是既有授权口径还是上线前冻结项修复。回滚/清理:若不采纳增强检查,可回退本 QA 脚本改动或保留为非阻断报告;若后续修生产权限,需独立 Issue、reviewer 和 feature flag/权限回滚方案。
7 OAAUTO1-5 自动化第 4 步:OCR 代表样本复核 我可以按够用原则复跑 2-3 张代表 PDF 发票 OCR 小样本,验证秒级 OCR、重复/已用终态和脱敏报告;不跑 105 张全量,除非单独批准压力/覆盖专项。 summary 只含 alias、状态、耗时、字段命中布尔值和可解释失败分类;不提交报销、不审批、不付款,不输出票号、销售方、金额、完整路径或原始 payload。 受影响流程:PDF 样本 -> OCR -> 脱敏统计;验证步骤:PLAYWRIGHT_OCR_BATCH_MAX_SAMPLES=3 PLAYWRIGHT_OCR_BATCH_STOP_WHEN_COVERED=1 运行 live invoice OCR batch。 playwright|bash TODO DONE TODO DONE TODO DONE client/tests/e2e/manual/live-invoice-ocr-batch.spec.ts;client/tests/e2e/helpers/liveInvoiceOcr.ts;artifacts/qa-flow/oinv1-* ["OAAUTO1-2"] 可自动完成;用户已明确够用即可,不把 105 张全量作为启用前门禁。 已完成:artifacts/qa-flow/oinv1-redacted-batch-oauto1-20260523-2318/summary.json,plannedSamples=3,available=3,字段命中 3/3,耗时约 0.2-2.4 秒;报告只含别名/状态/布尔命中,不提交报销、不审批、不付款。
8 OAAUTO1-6 自动化第 5 步:TEST/SMOKE 技术闭环复跑 如需回归,我可以复跑 TEST/SMOKE/OANEXT2 业务闭环,覆盖项目、合同台账、报销审批、付款批次、合成回单、已付款标记、AP 导出、采购审批和关联。 仅创建带 TEST/SMOKE 标记的数据;不执行真实银行付款、不接支付、不写 ERP、不导入银行流水、不上传真实票面原图。 受影响流程:TEST/SMOKE 报销 -> 审批 -> 付款/AP;采购 -> 合同/项目;验证步骤:OA_TEST_SMOKE_BUSINESS_WRITE=1 LIVE_SMOKE_BASE_URL=https://oa.hyinda.com node scripts/qa/run-oa-test-smoke-business-loop.mjs。 node|bash TODO TODO TODO scripts/qa/run-oa-test-smoke-business-loop.mjs;artifacts/qa-flow/oa-test-smoke-business-loop-* ["OAAUTO1-2"] 可自动完成但会写 TEST/SMOKE 公网测试数据;每次复跑前需确认是否允许继续保留审计测试数据。
9 OAAUTO1-7 自动化第 6 步:人工证据包模板与脱敏汇总 我可以准备真实业务证据包模板和脱敏汇总方式,让人工只记录业务编号别名、状态、责任人、日期和证据路径,不暴露真实票据、回单、合同正文或银行资料。 形成可填写的 evidence 结构或 summary 输入清单;run-oa-final-launch-readiness 可读取或人工引用;不要求上传敏感原文。 受影响流程:人工真实样本 -> 脱敏 evidence -> OALAUNCH 汇总;验证步骤:检查证据包字段不含密码、完整账号、真实票号、合同正文、银行流水或原始附件。 manual|bash TODO DONE TODO DONE TODO DONE artifacts/qa-flow;docs/trialrun/人工试运行报告.md;scripts/qa/run-oa-final-launch-readiness.sh ["OAAUTO1-3"] 可自动辅助;若需要写文档模板,优先更新现有事实源,不新建孤立文档。 已完成:artifacts/qa-flow/oa-launch-manual-evidence-oauto1-20260523-2324/summary.json 和 manual-evidence-template.md,覆盖真实报销、采购/费用、合同台账和每日收口;只记录脱敏别名、状态、角色和证据路径。
10 OAAUTO1-8 人工第 1 步:真实报销闭环操作 员工在公网 OA 提交 1 条真实报销并上传 PDF 发票;审批人查看详情后审批;财务创建付款批次、线下银行付款、上传回单、标记已付款并导出 AP/报销/票据资料。 报销真实业务闭环完成;OA 内状态、审批记录、付款批次、回单附件、已付款标记和 AP 导出均可追溯。 受影响流程:员工提交报销 -> 审批 -> 财务付款批次 -> 回单 -> 已付款 -> AP 导出;验证步骤:人工操作后运行 OAAUTO1-3 汇总,或在 evidence 包记录脱敏状态。 manual TODO TODO TODO external|public-oa ["OAAUTO1-7"] 人工步骤:1 登录员工账号;2 进入报销/发票上传;3 上传真实 PDF 发票;4 核对 OCR 结果;5 填金额/项目/费用类型;6 提交;7 审批人打开详情审批;8 财务建付款批次;9 在线下银行付款;10 上传回单;11 标记已付款;12 导出 AP/报销/票据。不要把票据原图、票号、银行流水或回单原文发到聊天/仓库。
11 OAAUTO1-9 人工第 2 步:真实采购或费用申请操作 员工在公网 OA 提交 1 条真实采购/费用申请,尽量关联项目或合同;审批人查看详情后通过或退回;管理员确认审计记录和项目/合同摘要可追溯。 至少 1 条真实采购/费用流程完成;有关联项目或合同;审批、退回或通过记录可查。 受影响流程:采购/费用申请 -> 审批 -> 项目/合同关联 -> 审计;验证步骤:人工操作后记录脱敏业务别名、状态和关联对象,最终汇总到 OALAUNCH。 manual TODO TODO TODO external|public-oa ["OAAUTO1-7"] 人工步骤:1 登录员工或负责人账号;2 进入采购/费用申请;3 填申请事由、金额区间、供应商摘要和用途;4 选择项目或合同关联;5 提交;6 审批人打开详情处理;7 管理员查看列表、详情和审计记录;8 若退回,记录原因并验证可重提。敏感报价和成本明细只按公司边界上传或线下留存。
12 OAAUTO1-10 人工第 3 步:真实合同台账和归档位置操作 负责人在公网 OA 建 1 条真实合同台账,填写合同编号、合同摘要、负责人、关联项目/采购/付款、线下归档位置和敏感级别;不上传涉密正文。 至少 1 条真实合同台账可查;归档位置明确;权限检查通过;敏感正文不进入公网 OA 或自动报告。 受影响流程:合同登记 -> 关联项目/采购/付款 -> 归档位置 -> 权限抽查;验证步骤:管理员和普通员工分别查看,确认授权可见、越权不可见或敏感字段脱敏。 manual TODO TODO TODO external|public-oa ["OAAUTO1-7"] 人工步骤:1 登录负责人或管理员账号;2 进入合同台账;3 新建合同记录;4 填编号/名称摘要/签约方摘要/负责人/日期/金额区间;5 关联项目、采购或付款;6 填线下归档位置;7 设置敏感级别;8 保存;9 用普通员工账号确认不能越权查看敏感附件或详情。涉密合同正文、敏感报价、成本细节不上传。
13 OAAUTO1-11 人工第 4 步:每日问题登记和阻断处理 7 天稳定化期间,管理员每天查看自动巡检和人工业务反馈,把问题分为 blocker、finding、feedback;blocker 当天修复、绕行或暂停流程。 每日有收口记录;blocker 有修复合同/绕行责任人/暂停决定;非阻断有 owner 和处理日期或接受理由。 受影响流程:日巡检 -> 人工反馈 -> 分类 -> 修复/绕行/暂停;验证步骤:Day 7 汇总检查 7 天记录和 unresolved blocker 数量。 manual TODO TODO TODO docs/trialrun/人工试运行报告.md;issues ["OAAUTO1-2","OAAUTO1-8","OAAUTO1-9","OAAUTO1-10"] 人工步骤:1 每天固定时间查看 OAOPS1 summary;2 汇总员工/审批人/财务反馈;3 标记 blocker/finding/feedback;4 blocker 当天新建修复合同或暂停流程;5 人工绕行必须记录风险和补录责任人;6 非阻断进入反馈池。
14 OAAUTO1-12 半自动第 1 步:备份恢复演练准备 我可以准备影子库恢复检查清单、命令草案和证据模板;实际恢复范围、时间窗口和服务器操作必须由你批准,且不得影响公网生产库。 备份位置、恢复目标、验证表、附件抽样、RTO/RPO、负责人和清理路径明确;未批准前不执行恢复。 受影响流程:备份检查 -> 影子恢复 -> 抽样验证 -> 证据汇总;验证步骤:审批后按 OPS runbook 执行,OALAUNCH_BACKUP_EVIDENCE 指向恢复证据。 manual|ops|bash TODO TODO TODO docs/trialrun/OPS7运维交接Runbook.md;docs/ops/非功能基线.md ["OAAUTO1-3"] 半自动。人工批准点:A 做影子库恢复;B 先只核对备份存在;C 暂停。禁止 destructive restore、migrate reset、覆盖公网生产库。清理只删除影子库/临时目录/临时凭据。 已准备检查单 artifacts/qa-flow/oa-launch-manual-evidence-oauto1-20260523-2324/backup-restore-drill-checklist.md;未执行恢复,仍需人工选择 A 影子库恢复、B 只核对备份存在、C 暂停。禁止 destructive restore、migrate reset、覆盖公网生产库。
15 OAAUTO1-13 自动与人工结合:7 天稳定化执行节奏 在用户确认起始日期后,我每天自动跑 OAOPS1 和 OALAUNCH 汇总;你每天做人工问题收口和必要真实业务确认。 连续 7 天报告齐全;无不可解释 blocker;Day 7 可进入正式启用决策。 受影响流程:每日自动巡检 -> 人工日收口 -> Day 7 汇总;验证步骤:每天运行 run-oa-real-use-daily.sh;Day 7 运行 run-oa-final-launch-readiness.sh。 bash|manual TODO TODO TODO scripts/qa/run-oa-real-use-daily.sh;scripts/qa/run-oa-final-launch-readiness.sh;artifacts/qa-flow/oa-real-use-daily-* ["OAAUTO1-8","OAAUTO1-9","OAAUTO1-10","OAAUTO1-12"] 建议顺序:先完成 1 条真实业务样本和备份恢复演练,再启动 7 天;不能用旧报告凑连续 7 天。
16 OAAUTO1-14 人工最终决策:Day 7 Go/No-Go 第 7 天根据最终启用汇总、真实业务证据、备份恢复、权限矩阵、SOP 和 7 天问题记录,由用户决定正式启用、带 finding 启用或暂停整改。 用户明确选择 A/B/C;A 为 PASS_OA_LAUNCH_READY 后启用;B 为 PASS_WITH_FINDINGS 并指定 owner/日期;C 为暂停启用并进入整改合同。 受影响流程:最终汇总 -> 用户决策 -> 正式启用/带 finding/暂停;验证步骤:设置 OALAUNCH_MANUAL_ACCEPT 或人工记录决策后复跑 run-oa-final-launch-readiness.sh。 manual|bash TODO TODO TODO scripts/qa/run-oa-final-launch-readiness.sh;issues/2026-05-23_16-49-39-oa-final-launch-readiness-v1.csv ["OAAUTO1-13"] 不能自动替用户决策。选项:A 正式启用;B 带 finding 启用但指定 owner 和日期;C 暂停,先整改。
17 OAAUTO1-15 自动化替代边界说明 明确哪些可以用自动化测试替代,哪些不能:自动化可替代访问性、权限负例、OCR 可用性、技术闭环、脱敏扫描和汇总;不能替代真实付款、业务合理性、审批人判断、合同归档真实性、恢复演练接受和 Go/No-Go。 边界在后续执行中被遵守;任何报告不得把自动化 PASS 描述成真实业务已完成或正式启用已批准。 受影响流程:自动化测试 -> 人工业务 -> 启用决策;验证步骤:审查最终回复、Issue Notes 和 OALAUNCH summary 结论是否避免伪 PASS。 manual TODO DONE TODO DONE TODO DONE issues/2026-05-23_23-01-55-oa-automation-manual-cutover-v1.csv ["OAAUTO1-1"] 原则:技术可用性可自动化,业务真实性和责任承担不可自动化。 已执行边界:自动项只覆盖只读巡检、汇总、权限/搜索负例和 OCR 小样本;未执行真实付款、真实业务审批决策、备份恢复、TEST/SMOKE 复跑写入或 Go/No-Go。
18 OAAUTO1-16 本合同校验与停止点 完成本 CSV 校验、process gate 和 diff 检查;提交后等待用户决定是否开始 OAAUTO1-2 的自动只读巡检或先执行人工样本。 validate_issues_csv.py、check-process-contract general、git diff --check 通过;本轮不部署、不写公网、不修改业务代码。 受影响流程:规划 -> 校验 -> 用户审查 -> 后续执行;验证步骤:python3 /home/shunli/.codex/skills/plan/scripts/validate_issues_csv.py <csv>;node scripts/qa/check-process-contract.mjs --issue <csv> --profile general --json;git diff --check。 python3|node|git DONE DONE DONE issues/2026-05-23_23-01-55-oa-automation-manual-cutover-v1.csv ["OAAUTO1-1"] 本轮只规划和细化,不执行公网巡检、不提交真实业务、不恢复备份;已通过 validate_issues_csv.py、check-process-contract general、git diff --check。
+93 -1
查看文件
@@ -49,6 +49,18 @@ const ROLE_TARGETS = [
{ id: "notification-deliveries", path: "/api/notifications/deliveries?limit=5", statuses: [403] },
{ id: "system-feature-flags", path: "/api/system/feature-flags", statuses: [403] },
{ id: "erp-status", path: "/api/integrations/erp/master-data/status", statuses: [403] },
{ id: "payroll-items", path: "/api/payroll/items", statuses: [403] },
{ id: "performance-cycles", path: "/api/performance/cycles", statuses: [403, 409] },
{ id: "travel-requests", path: "/api/travel-requests?page=1&pageSize=5", statuses: [403, 409] },
{
id: "global-search-sensitive",
path: "/api/search?q=TEST&limit=10",
statuses: [200, 409],
searchExpectations: {
forbiddenEntityTypes: ["contract_registry", "confidential_doc_ref", "travel_request"],
forbiddenHrefPrefixes: ["/contracts-registry", "/travel-requests"],
},
},
],
},
{
@@ -86,6 +98,18 @@ const ROLE_TARGETS = [
{ id: "payment-batches", path: "/api/payment-batches?pageSize=5", statuses: [403, 409] },
{ id: "audit-logs", path: "/api/audit-logs?pageSize=5", statuses: [403] },
{ id: "erp-status", path: "/api/integrations/erp/master-data/status", statuses: [403] },
{ id: "payroll-items", path: "/api/payroll/items", statuses: [403] },
{ id: "performance-cycles", path: "/api/performance/cycles", statuses: [403, 409] },
{ id: "travel-requests", path: "/api/travel-requests?page=1&pageSize=5", statuses: [403, 409] },
{
id: "global-search-sensitive",
path: "/api/search?q=TEST&limit=10",
statuses: [200, 409],
searchExpectations: {
forbiddenEntityTypes: ["contract_registry", "confidential_doc_ref", "travel_request"],
forbiddenHrefPrefixes: ["/contracts-registry", "/travel-requests"],
},
},
],
},
{
@@ -124,6 +148,18 @@ const ROLE_TARGETS = [
{ id: "notification-deliveries", path: "/api/notifications/deliveries?limit=5", statuses: [403] },
{ id: "system-feature-flags", path: "/api/system/feature-flags", statuses: [403] },
{ id: "erp-status", path: "/api/integrations/erp/master-data/status", statuses: [403] },
{ id: "payroll-items", path: "/api/payroll/items", statuses: [403] },
{ id: "performance-cycles", path: "/api/performance/cycles", statuses: [403, 409] },
{ id: "travel-requests", path: "/api/travel-requests?page=1&pageSize=5", statuses: [403, 409] },
{
id: "global-search-sensitive",
path: "/api/search?q=TEST&limit=10",
statuses: [200, 409],
searchExpectations: {
forbiddenEntityTypes: ["contract_registry", "confidential_doc_ref", "travel_request"],
forbiddenHrefPrefixes: ["/contracts-registry", "/travel-requests"],
},
},
],
},
{
@@ -197,6 +233,17 @@ const ROLE_TARGETS = [
{ id: "payment-batches", path: "/api/payment-batches?pageSize=5", statuses: [403, 409] },
{ id: "audit-logs", path: "/api/audit-logs?pageSize=5", statuses: [403] },
{ id: "system-feature-flags", path: "/api/system/feature-flags", statuses: [403] },
{ id: "payroll-items", path: "/api/payroll/items", statuses: [403] },
{ id: "performance-cycles", path: "/api/performance/cycles", statuses: [403, 409] },
{
id: "global-search-sensitive",
path: "/api/search?q=TEST&limit=10",
statuses: [200, 409],
searchExpectations: {
forbiddenEntityTypes: ["contract_registry", "confidential_doc_ref", "travel_request"],
forbiddenHrefPrefixes: ["/contracts-registry", "/travel-requests"],
},
},
],
},
{
@@ -231,6 +278,17 @@ const ROLE_TARGETS = [
negative: [
{ id: "system-feature-flags", path: "/api/system/feature-flags", statuses: [403] },
{ id: "notification-deliveries", path: "/api/notifications/deliveries?limit=5", statuses: [403] },
{ id: "payroll-items", path: "/api/payroll/items", statuses: [403] },
{ id: "performance-cycles", path: "/api/performance/cycles", statuses: [403, 409] },
{
id: "global-search-sensitive",
path: "/api/search?q=TEST&limit=10",
statuses: [200, 409],
searchExpectations: {
forbiddenEntityTypes: ["contract_registry", "confidential_doc_ref", "travel_request"],
forbiddenHrefPrefixes: ["/contracts-registry", "/travel-requests"],
},
},
],
},
{
@@ -255,6 +313,9 @@ const ROLE_TARGETS = [
{ id: "audit-logs", path: "/api/audit-logs?pageSize=5", statuses: [200] },
{ id: "notification-deliveries", path: "/api/notifications/deliveries?limit=5", statuses: [200] },
{ id: "system-feature-flags", path: "/api/system/feature-flags", statuses: [200] },
{ id: "payroll-items", path: "/api/payroll/items", statuses: [200] },
{ id: "performance-cycles", path: "/api/performance/cycles", statuses: [200, 409] },
{ id: "global-search", path: "/api/search?q=TEST&limit=10", statuses: [200, 409] },
{ id: "assets", path: "/api/assets?pageSize=5", statuses: [200] },
{
id: "navigation",
@@ -384,6 +445,33 @@ function evaluateNavigationExpectations(json, expectations) {
return [...visible, ...hidden];
}
function evaluateSearchExpectations(json, expectations) {
if (!expectations || !Array.isArray(json?.data?.items)) return [];
const items = json.data.items.filter((item) => item && typeof item === "object");
const forbiddenEntityTypes = new Set(expectations.forbiddenEntityTypes || []);
const forbiddenHrefPrefixes = expectations.forbiddenHrefPrefixes || [];
const results = [];
for (const entityType of forbiddenEntityTypes) {
const matchedCount = items.filter((item) => item.entityType === entityType).length;
results.push({
type: "forbidden_entity_type",
value: entityType,
status: matchedCount === 0 ? "PASS" : "FAIL",
matchedCount,
});
}
for (const hrefPrefix of forbiddenHrefPrefixes) {
const matchedCount = items.filter((item) => typeof item.href === "string" && item.href.startsWith(hrefPrefix)).length;
results.push({
type: "forbidden_href_prefix",
value: hrefPrefix,
status: matchedCount === 0 ? "PASS" : "FAIL",
matchedCount,
});
}
return results;
}
function collectSensitiveKeyHits(value, currentPath = "data", hits = []) {
if (hits.length >= 12) return hits;
if (Array.isArray(value)) {
@@ -471,17 +559,20 @@ async function probe(accessToken, probe, kind) {
const expected = probe.statuses.includes(response.status);
const dataExpectations = response.ok ? evaluateDataExpectations(response.json, probe.dataExpectations) : [];
const navigationExpectations = response.ok ? evaluateNavigationExpectations(response.json, probe.navigationExpectations) : [];
const searchExpectations = response.ok ? evaluateSearchExpectations(response.json, probe.searchExpectations) : [];
const dataExpectationFailed = dataExpectations.some((item) => item.status === "FAIL");
const navigationExpectationFailed = navigationExpectations.some((item) => item.status === "FAIL");
const searchExpectationFailed = searchExpectations.some((item) => item.status === "FAIL");
const sensitiveKeyHits = response.ok ? collectSensitiveKeyHits(response.json?.data).slice(0, 12) : [];
return {
id: probe.id,
kind,
status: expected && !dataExpectationFailed && !navigationExpectationFailed && sensitiveKeyHits.length === 0 ? "PASS" : "FAIL",
status: expected && !dataExpectationFailed && !navigationExpectationFailed && !searchExpectationFailed && sensitiveKeyHits.length === 0 ? "PASS" : "FAIL",
httpStatus: response.status,
expectedStatuses: probe.statuses,
dataExpectations,
navigationExpectations,
searchExpectations,
sensitiveKeyHits,
summary: response.ok ? summarizeData(response.json) : {
code: response.json?.error?.code ?? null,
@@ -544,6 +635,7 @@ function collectIssues(results) {
code: probeResult.summary?.code ?? null,
failedDataExpectations: probeResult.dataExpectations?.filter((item) => item.status === "FAIL") || [],
failedNavigationExpectations: probeResult.navigationExpectations?.filter((item) => item.status === "FAIL") || [],
failedSearchExpectations: probeResult.searchExpectations?.filter((item) => item.status === "FAIL") || [],
sensitiveKeyHits: probeResult.sensitiveKeyHits || [],
});
}