diff --git a/.agents/skills/hyb-oa-delivery/SKILL.md b/.agents/skills/hyb-oa-delivery/SKILL.md index eed39b82..5c27e1b3 100644 --- a/.agents/skills/hyb-oa-delivery/SKILL.md +++ b/.agents/skills/hyb-oa-delivery/SKILL.md @@ -17,7 +17,7 @@ Read only what is relevant: - For approval workflow, roles, self-approval, notification, deeplink, reminder, or escalation work, read `docs/需求-统一审批中心规约.md`. - For HR lifecycle, employee onboarding/offboarding, Enterprise WeChat directory sync, account disablement, or Enterprise WeChat API work, read `docs/需求-企业微信基础设施规约.md`, `docs/企微API能力目录.md`, `docs/管理员手册.md`, and the latest HCLT/HRWD/WAPI/WRRA Issue CSV evidence first. - For any Enterprise WeChat capability, Secret, callback, approval, check-in, HR assistant, or contact-directory task, read `docs/企微API能力目录.md` before planning or coding. WAPI fixes the current Secret matrix: contacts use `WECOM_CONTACT_SECRET`; workbench/OAuth/messages/HR assistant/roster/check-in/approval use `WECOM_AGENT_SECRET`; `WECOM_CHECKIN_SECRET` is only an explicit check-in override; `WECOM_HR_ASSISTANT_SECRET` is historical compatibility and not a default. Do not write runtime code that tries multiple Secrets to guess the correct one. -- For Enterprise WeChat-first HR/attendance runtime migration, read `docs/企微优先开发路线图.md` before planning or coding. WDRG makes the roadmap authoritative for stage order; WAPI adds the runtime-first stage before deep mirror work; WRRA states that WOUA `PASS_WITH_FINDINGS` does not clear deep mirror work; WOUA-FIX has now soft-archived the 7 OA active test accounts missing in WeCom, fixed the `T0000` seed reactivation behavior, transferred the `SMOKE201` test-manager side effects, and rerun WOUA with `oaActiveMissingInWecom=0`. WCPC has rerun the capability POC with an existing active test account and reached `GO_TO_HR_MASTER_SYNC`; WHMS/WHMSLIVE has implemented, deployed, and publicly verified `wecom_hr_master_sync_v1`, with the production flag restored to `force_off` and production real-employee batch apply not approved. WMSG1 proves real app-message delivery and Deeplink return only; WAPC1 fixes the approval configuration preparation checklist before mirror work; WAPC2 adds the leave/missed-punch approval template and low-risk `sp_no` read-only POC runner. WPROC1 fixes the engineering route: WAPC2 must be followed by WAPC2F automatic POC fixtures, WAPC2 rerun, WCBK1 callback POC, WACP3 compensation-pull POC, and WADM1 mirror data model design before `wecom_attendance_approval_mirror_v1`. Do not start `wecom_attendance_approval_mirror_v1`, `oa_hr_emergency_mode_v1`, `wecom_recovery_writeback_v1`, or `wecom_ruleops_v1` without the next stage's own Issue CSV. +- For Enterprise WeChat-first HR/attendance runtime migration, read `docs/企微优先开发路线图.md` before planning or coding. WDRG makes the roadmap authoritative for stage order; WAPI adds the runtime-first stage before deep mirror work; WRRA states that WOUA `PASS_WITH_FINDINGS` does not clear deep mirror work; WOUA-FIX has now soft-archived the 7 OA active test accounts missing in WeCom, fixed the `T0000` seed reactivation behavior, transferred the `SMOKE201` test-manager side effects, and rerun WOUA with `oaActiveMissingInWecom=0`. WCPC has rerun the capability POC with an existing active test account and reached `GO_TO_HR_MASTER_SYNC`; WHMS/WHMSLIVE has implemented, deployed, and publicly verified `wecom_hr_master_sync_v1`, with the production flag restored to `force_off` and production real-employee batch apply not approved. WMSG1 proves real app-message delivery and Deeplink return only; WAPC1 fixes the approval configuration preparation checklist before mirror work; WAPC2 adds the leave/missed-punch approval template and low-risk `sp_no` read-only POC runner. WPROC1 fixes the engineering route: WAPC2 must be followed by WAPC2F automatic POC fixtures, WAPC2 rerun, WCBK1 callback POC, WACP3 compensation-pull POC, and WADM1 mirror data model design before `wecom_attendance_approval_mirror_v1`. WPROC2 adds the executable `process gate`: before WAPC2F or other high-risk WeCom work, run `node scripts/qa/check-process-contract.mjs --baseline` and the issue-specific profile check to enforce DoR/DoD, fixed Secret source, non-self approval, live write switches, missing-config classification, and redaction boundaries. Do not start `wecom_attendance_approval_mirror_v1`, `oa_hr_emergency_mode_v1`, `wecom_recovery_writeback_v1`, or `wecom_ruleops_v1` without the next stage's own Issue CSV. - For reimbursement, invoice, OCR, duplicate invoice, or attachment work, read `docs/报销发票附件规则.md` and `references/reimbursement.md`. - For local/WeCom/public trial-run, OCR runtime, Caddy, or DB drift work, read `docs/试运行与自动化验证手册.md` and `references/trialrun.md`. - For how this skill should evolve, read `docs/Agent开发交付实践.md`. @@ -110,7 +110,7 @@ Final responses should include: - WMSG1 has verified real Enterprise WeChat personal-app delivery and Deeplink return: report `/app/artifacts/qa-flow/wecom-runtime-ready-20260518T234431Z/summary.json` has `checks.message.status=OK`, `errcode=0`, and `recipientCount=1`; the user confirmed receiving "OA 运行就绪检查" and opening the OA notifications page from the message. This only clears message/deeplink runtime evidence; approval template ID, approval `sp_no`, callback config, and deep mirror work remain separate later gates. - WAPC1 has fixed the WeCom approval configuration preparation gate: first templates are leave, missed punch, outing, and overtime; each needs template ID, scope, fields, approver rules, visible scope, and self-built app permission; low-risk test `sp_no` must not contain procurement, contract, reimbursement, invoice, bank receipt, sensitive attachment, or real sensitive business material; callback URL/Token/EncodingAESKey are deployment-only values and must not be written to repo docs or Issue CSV. Do not implement real approval callback handling, deep mirror, emergency mode, recovery writeback, or RuleOps until a later approved Issue covers the POC or implementation. - WAPC2 has added the WeCom approval template/detail read-only POC runner for leave and missed punch. Use `npm run check:wecom-approval-poc`; pass template IDs with `--template=leave:` / `--template=missed_punch:` and low-risk test approval numbers with `--sp-no=leave:` / `--sp-no=missed_punch:`. The runner must use only `WECOM_AGENT_SECRET`, redact full template IDs and `sp_no`, and never output form values, attachments, comments, full userid, token, Secret, or raw payload. -- WPROC1 fixes the WeCom approval engineering process. Future WeCom work must satisfy route gate, capability gate, and implementation gate; use automatic runners before asking the user to do manual work. Manual work is limited to write approval, secrets/callback config, WeCom backend settings that cannot be API-driven, real-device confirmation, and risk decisions. High-risk stages require POC before implementation: callback signature/decryption/idempotency, compensation pull, mirror data model, then mirror implementation. +- WPROC1 fixes the WeCom approval engineering process. Future WeCom work must satisfy route gate, capability gate, and implementation gate; use automatic runners before asking the user to do manual work. WPROC2 makes this executable through `node scripts/qa/check-process-contract.mjs --baseline` plus issue-specific profiles such as `--profile wecom-approval-fixture`; run it before WAPC2F and later high-risk WeCom contracts. Manual work is limited to write approval, secrets/callback config, WeCom backend settings that cannot be API-driven, real-device confirmation, and risk decisions. High-risk stages require POC before implementation: callback signature/decryption/idempotency, compensation pull, mirror data model, then mirror implementation. - Approval-chain correctness can be proven by PC or automation, while Enterprise WeChat validates entry, binding,免登录, notification, deeplink, and mobile layout. - If real-device mobile validation is paused after automation passes, record the gate as `PAUSED`; do not close RS6/UI6/R6 mobile blockers until representative real-device confirmation is recorded. - Role-specific pages should use one shared OA/mobile framework and tailor summaries, entries, actions, and sensitive fields by role. Do not create unrelated UI systems for employee, approver, finance, admin, or CEO views. diff --git a/docs/企微API能力目录.md b/docs/企微API能力目录.md index a48cfe1f..b57a286c 100644 --- a/docs/企微API能力目录.md +++ b/docs/企微API能力目录.md @@ -2,8 +2,8 @@ **文档状态**:当前企微 API 调研与本项目使用事实源。 **分类**:集成事实源 / API 能力目录。 -**适用时间**:WAPI / WAPC1 / WAPC2 / WPROC1 之后。 -**关联 Issue**:`WAPI,WAPC1,WAPC2,WPROC1`。 +**适用时间**:WAPI / WAPC1 / WAPC2 / WPROC1 / WPROC2 之后。 +**关联 Issue**:`WAPI,WAPC1,WAPC2,WPROC1,WPROC2`。 **最近核对**:2026-05-19。 **不得误读**:本文只记录本项目会用到的企业微信官方 API 摘要、链接和项目决策,不复制官方文档全文,不保存 Secret、access token、完整 userid、手机号、邮箱或企微原始 payload。官方文档和企业微信后台权限配置始终是最终依据。 @@ -14,6 +14,7 @@ - 产品 UI 支持不等于开放 API 支持。入职、转正、离职、工资条确认、考勤确认等人事助手产品能力,只有在官方 API 明确支持并通过 POC 后才能进入自动化实现。 - 生产写接口、真实员工批量同步、企微规则写入、应急模式开启和恢复回写都必须另建合同并获得明确批准。 - WPROC1 后默认自动化优先:能用 runner 自动生成、自动检查、自动复跑的,不要求人工手动操作;人工只处理写入批准、Secret/Token/EncodingAESKey 配置、企微后台无法 API 化配置、真机确认和风险拍板。 +- WPROC2 后,企微高风险合同必须先通过 `node scripts/qa/check-process-contract.mjs --baseline` 和对应 profile 的 process gate;WAPC2F 使用 `wecom-approval-fixture` profile。 - 审批类 POC 默认禁止自审;WAPC2F 默认测试链为申请人 `SMOKE002`、审批人 `SMOKE101`,两者必须 active、已绑定企微、可见、不是同一 OA 员工且不是同一企微 userid。若 `SMOKE101` 不可用,不自动换人,返回配置缺口并问答确认。 ## 2. Secret 矩阵 diff --git a/docs/企微优先开发路线图.md b/docs/企微优先开发路线图.md index cf2649a2..dc40b200 100644 --- a/docs/企微优先开发路线图.md +++ b/docs/企微优先开发路线图.md @@ -2,10 +2,10 @@ **文档状态**:当前开发路线图事实源。 **分类**:开发治理 / 路线图。 -**适用时间**:WDRG / WCP1 / WHMSLIVE / WOUA / WAPI / WRRA / WOUA-FIX / WRV1 / WRA1 / WMSG1 / WAPC1 / WAPC2 / WPROC1 之后。 -**关联 Issue**:`WDRG,WCP1,WHMS,WHMSLIVE,WOUA,WAPI,WRRA,WOUAFIX,WRV1,WRA1,WMSG1,WAPC1,WAPC2,WPROC1`。 +**适用时间**:WDRG / WCP1 / WHMSLIVE / WOUA / WAPI / WRRA / WOUA-FIX / WRV1 / WRA1 / WMSG1 / WAPC1 / WAPC2 / WPROC1 / WPROC2 之后。 +**关联 Issue**:`WDRG,WCP1,WHMS,WHMSLIVE,WOUA,WAPI,WRRA,WOUAFIX,WRV1,WRA1,WMSG1,WAPC1,WAPC2,WPROC1,WPROC2`。 **最近审计**:2026-05-19。 -**不得误读**:本文只控制后续开发节奏、阶段顺序和门禁。WHMSLIVE 后只代表企微 HR 主数据镜像已部署并受控验证;WAPI 后进一步固定企微 API/Secret 口径并转为运行优先;WRRA 后明确 `PASS_WITH_FINDINGS` 只代表只读对账成功且存在差异,不代表企微考勤/审批镜像、OA 应急接管、恢复回写或 RuleOps 已完成运行时实现。WMSG1 只证明企业微信应用消息可达和 Deeplink 可回到 OA;WAPC1 只固化审批配置准备清单;WAPC2 只新增请假/补卡模板详情和低风险 `sp_no` 审批详情只读 POC,不代表真实回调或镜像能力已完成。WPROC1 后,WAPC2 不直接放行深度镜像,必须先完成自动样件、回调、补偿拉取和镜像数据模型设计门禁。 +**不得误读**:本文只控制后续开发节奏、阶段顺序和门禁。WHMSLIVE 后只代表企微 HR 主数据镜像已部署并受控验证;WAPI 后进一步固定企微 API/Secret 口径并转为运行优先;WRRA 后明确 `PASS_WITH_FINDINGS` 只代表只读对账成功且存在差异,不代表企微考勤/审批镜像、OA 应急接管、恢复回写或 RuleOps 已完成运行时实现。WMSG1 只证明企业微信应用消息可达和 Deeplink 可回到 OA;WAPC1 只固化审批配置准备清单;WAPC2 只新增请假/补卡模板详情和低风险 `sp_no` 审批详情只读 POC,不代表真实回调或镜像能力已完成。WPROC1 后,WAPC2 不直接放行深度镜像,必须先完成自动样件、回调、补偿拉取和镜像数据模型设计门禁。WPROC2 只新增可执行 process gate,不实现 WAPC2F 业务能力。 ## 1. 路线图原则 @@ -36,6 +36,7 @@ | `WAPC1` | 配置准备清单已固化 | WMSG1 后的下一步改为审批配置准备清单:首批关注请假、补卡、外出、加班模板;准备模板 ID、低风险测试 `sp_no`、真实回调 URL/Token/EncodingAESKey 和后续 POC 准入项。本阶段不接真实回调、不调用写接口、不进入深度镜像。 | | `WAPC2` | 已部署并公网只读复跑 | 新增 QA-only `npm run check:wecom-approval-poc`,范围先锁定请假和补卡;固定 `WECOM_AGENT_SECRET` 调用 `POST /cgi-bin/oa/gettemplatedetail` 与 `POST /cgi-bin/oa/getapprovaldetail`,报告只输出脱敏模板 ID/`sp_no` 前缀、字段/节点计数和错误分类。公网报告 `/app/artifacts/qa-flow/wecom-approval-poc-20260519T020429Z/summary.json` 为 `PARTIAL_APPROVAL_POC_NEEDS_CONFIG`:Secret 可用,但缺请假/补卡模板 ID 和两条低风险 `sp_no`,未伪造成 PASS。 | | `WPROC1` | 路线与流程门禁已修正 | 固化三层门禁、Definition of Ready、Definition of Done、自动化优先和非自审规则。后续路线调整为 `WAPC2 -> WAPC2F 自动生成 POC 样件 -> WAPC2 自动复跑 -> WCBK1 回调只读 POC -> WACP3 补偿拉取 POC -> WADM1 镜像数据模型设计 -> wecom_attendance_approval_mirror_v1`。WAPC2F 默认测试链为 `SMOKE002 -> SMOKE101`,若 `SMOKE101` 不可用,不自动换人,先问答确认。 | +| `WPROC2` | 流程自动化门禁已新增 | 新增 `scripts/qa/check-process-contract.mjs`,把 WPROC1 的三层门禁、DoR/DoD、自动化优先、非自审、Secret 固定、live 写入开关、缺配置分类和脱敏边界升级为可执行 process gate;WAPC2F 实施前必须先通过 baseline gate 和 `wecom-approval-fixture` profile。 | ## 3. 阶段路线 @@ -45,7 +46,7 @@ | 2 | `wecom_hr_master_sync_v1` | 企微/人事助手员工、部门、主管、职位、联系方式和在职状态镜像到 OA。 | POC 证明人事助手/通讯录读取和回调能力可用,或明确缺口处理方式。 | WHMSLIVE 后已完成本地门禁、部署和受控公网验证;OA 可自动创建/更新员工档案、账号、角色、企微绑定和审计;最终 `force_off`,真实员工批量 apply 仍需单独批准;缺配置时记录 `SKIP/BLOCKED_BY_EXTERNAL_CONFIG`。 | | 3 | `runtime_first_wecom_enablement` | 先把已可用企微能力用于系统运行:入口、免登/绑定、脱敏消息、deeplink、通讯录 userid 清单、人事助手读取、打卡读取和只读对账。 | WAPI API 目录和 Secret 矩阵完成;WOUA 按新读取策略复跑。 | WOUA-FIX 后 OA active 缺企微已清零;WRV1/WRA1 通过 `check:wecom-runtime-ready` 给出运行优先结论;WMSG1 已证明真实消息和 Deeplink 可用。`PASS_WITH_CONFIG_GAPS` 只允许继续入口、免登、消息和只读读取主链路,不放行深度镜像;审批模板、测试 `sp_no`、真实回调仍阻断 `wecom_attendance_approval_mirror_v1`。 | | 4 | `WAPC1 / WAPC2` | 审批配置准备和请假/补卡模板详情、低风险审批详情只读 POC。 | 运行优先门禁完成;WMSG1 消息和 Deeplink 已确认。 | WAPC2 runner 已部署,缺模板 ID 或 `sp_no` 时真实记录 `PARTIAL_APPROVAL_POC_NEEDS_CONFIG`,不放行深度镜像。 | -| 5 | `WAPC2F 自动生成 POC 样件` | 尽量用 QA-only runner 自动创建 OA-POC 请假/补卡测试模板和低风险测试审批单。 | WAPC2 已证明 Secret 可用但缺模板或 `sp_no`;用户批准低风险企微测试样件写入。 | 默认 dry-run;live 写入必须显式开关;申请人 `SMOKE002`、审批人 `SMOKE101` 通过 active、绑定、可见和非自审预检;缺权限或不支持时记录 `SKIP/BLOCKED_BY_EXTERNAL_CONFIG`。 | +| 5 | `WAPC2F 自动生成 POC 样件` | 尽量用 QA-only runner 自动创建 OA-POC 请假/补卡测试模板和低风险测试审批单。 | WAPC2 已证明 Secret 可用但缺模板或 `sp_no`;WPROC2 baseline gate 和 WAPC2F profile gate 通过;用户批准低风险企微测试样件写入。 | 默认 dry-run;live 写入必须显式开关;申请人 `SMOKE002`、审批人 `SMOKE101` 通过 active、绑定、可见和非自审预检;缺权限或不支持时记录 `SKIP/BLOCKED_BY_EXTERNAL_CONFIG`。 | | 6 | `WAPC2 自动复跑` | 使用 WAPC2F 生成的脱敏模板 ID 和 `sp_no` 自动复跑只读 POC。 | WAPC2F 生成全部或部分样件。 | 请假/补卡模板详情与审批详情可读为 `PASS_APPROVAL_POC`;部分缺项为 `PARTIAL_APPROVAL_POC_NEEDS_CONFIG`。 | | 7 | `WCBK1 回调只读 POC` | 证明真实回调 URL 验证、验签、解密、幂等键和重复投递处理。 | 回调 URL、Token、EncodingAESKey 由部署环境配置;不得写入仓库或聊天。 | 真实回调可达、可验签/解密、重复投递幂等、失败重试语义明确;缺配置记录 `NEEDS_CONFIG`。 | | 8 | `WACP3 补偿拉取 POC` | 证明按时间窗口批量获取审批单号并可拉取审批详情,作为回调丢失补偿。 | WAPC2 或 WAPC2F 至少能提供低风险样例;官方 API 和权限明确。 | 能按窗口拿到审批单号并拉详情;不能伪造成 PASS,权限或可信 IP 问题记录 `SKIP/BLOCKED_BY_EXTERNAL_CONFIG`。 | diff --git a/docs/开发交付与变更管理规约.md b/docs/开发交付与变更管理规约.md index a661f1f9..4b5e3d23 100644 --- a/docs/开发交付与变更管理规约.md +++ b/docs/开发交付与变更管理规约.md @@ -3,7 +3,7 @@ **文档状态**:当前开发交付事实源。 **分类**:开发治理。 **适用范围**:hyb-oa 所有非微小代码、文档、测试、脚本和部署准备变更。 -**关联 Issue**:`GOV8,WDRG,WPROC1`。 +**关联 Issue**:`GOV8,WDRG,WPROC1,WPROC2`。 **最近审计**:2026-05-19。 **不得误读**:本文定义开发和变更流程,不替代具体业务 PRD、模块规约或测试规约。 **路线口径**:开发必须围绕最新 Issue CSV 执行;WDRG 后企微优先 HR/考勤改造必须先读 [企微优先开发路线图](./企微优先开发路线图.md),再按单功能 Issue CSV 实施。采购、合同、报销、财务、AP、代账和高敏附件继续 OA 主办,ERP 未来不接入 OA。 @@ -133,6 +133,13 @@ Definition of Done: - 人工只处理写入批准、Secret/Token/EncodingAESKey 配置、企微后台无法 API 化的配置、真机确认和风险拍板。 - 缺配置必须输出 `NEEDS_CONFIG` 或 `SKIP/BLOCKED_BY_EXTERNAL_CONFIG`,不得伪造成 PASS。 +WPROC2 后,三层门禁先由可执行 process gate 承接: + +- 基线检查:`node scripts/qa/check-process-contract.mjs --baseline`。 +- 单合同检查:`node scripts/qa/check-process-contract.mjs --issue --profile --json`。 +- 首批 profile 为 `general`、`docs-only`、`wecom` 和 `wecom-approval-fixture`。 +- WAPC2F 和后续高风险企微合同在实现前必须先通过 baseline gate 和对应 profile gate;`scripts/check-minimal.sh` 已接入 baseline gate。 + 高风险能力进入实现前必须先有 POC: - 真实回调先证明 URL 验证、验签、解密、幂等和重复投递。 diff --git a/docs/文档事实源状态矩阵.md b/docs/文档事实源状态矩阵.md index 262cdea5..5df6932d 100644 --- a/docs/文档事实源状态矩阵.md +++ b/docs/文档事实源状态矩阵.md @@ -79,6 +79,7 @@ | `WAPC1` | `issues/2026-05-19_08-44-58-wecom-approval-config-prep-v1.csv` | `EVIDENCE` | 企微审批配置准备清单已完成:WMSG1 后不直接进入深度镜像;首批模板为请假、补卡、外出、加班;模板 ID、低风险 `sp_no`、真实回调配置项、验签/解密/幂等/重试和补偿拉取准入已固化;本轮不改业务代码、不部署、不接真实回调、不调用企微写接口 | | `WAPC2` | `issues/2026-05-19_09-34-16-wecom-approval-template-spno-poc-v1.csv` | `CURRENT` | 企微审批模板与低风险 `sp_no` 只读 POC 已部署并公网复跑:新增 `check:wecom-approval-poc` QA-only runner 和只读集成服务,范围请假/补卡,固定 `WECOM_AGENT_SECRET` 调用 `gettemplatedetail`/`getapprovaldetail`;远端报告 `/app/artifacts/qa-flow/wecom-approval-poc-20260519T020429Z/summary.json` 为 `PARTIAL_APPROVAL_POC_NEEDS_CONFIG`,缺模板或 `sp_no` 不放行深度镜像 | | `WPROC1` | `issues/2026-05-19_10-43-50-wecom-process-route-engineering-v1.csv` | `CURRENT` | 企微路线与开发流程工程化修正:docs-only/contract-only 固化三层门禁、Definition of Ready、Definition of Done、自动化优先、非自审和高风险 POC 前置;修正 WAPC2F 默认链为 `SMOKE002 -> SMOKE101`,WAPC2 后不得直接进入深度镜像 | +| `WPROC2` | `issues/2026-05-19_11-15-10-process-gate-automation-v1.csv` | `CURRENT` | 开发流程自动化门禁:新增 `scripts/qa/check-process-contract.mjs` process gate、WAPC2F profile 和最小门禁接入;WAPC2F 实施前必须先通过 baseline gate,不部署、不写 OA/企微数据 | 旧 Issue CSV 保留为历史审计证据:不删除、不批量回写、不移动到 `docs/archive/` 或其他归档目录。新连续合同如替代旧合同,只在新合同 `Notes` 或文档索引中说明 supersedes。 @@ -86,15 +87,15 @@ | Doc_ID | Path | Category | Authority | Status | Owner | Related_Issues | Evidence | Supersedes | Last_Audited | Audit_Method | Risk | Notes | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | -| `IDX` | `docs/文档索引.md` | 入口索引 | 最高文档入口 | `CURRENT` | product/dev | `GOV8,QAOPS9,P2D,P2-ORG,P2-FIN-DECISION,P2-FIN-V1,P2-OCR-SAMPLE,P2Q,P2AUTO,P2AF,P2-FIN-LIVE-DECISION,P2-FIN-LIVE,PRELAUNCH,CASHIER-VOUCHER-V1,LAUNCH-DEPLOY-FINANCE,FIN-PROD-ENABLE,ACCOUNTING-HANDOFF-V1,SYSTEM-COMPLETE-GATE,LAUNCH-OPS-V1,REQ-COMPLETE-AUDIT,ATTV1,LAUNCH-AUTO-OPS-V1,AUTO-REMAINING-CLOSEOUT,HR-WECOM-DIRECTORY-V1,WFOC,DBWFOC,WFDG,WDRG,WCP1,WCPC,WHMS,WHMSLIVE,WOUA,WAPI,WRRA,WOUAFIX,WRV1,WRA1,WMSG1,WAPC1,WAPC2,WPROC1` | `rg` | none | 2026-05-19 | `node scripts/qa/audit-docs.mjs --check-registry` | medium | 判断当前该读什么 | -| `REG` | `docs/文档事实源状态矩阵.md` | 文档台账 | 文档分类和状态事实源 | `CURRENT` | product/dev | `GOV8,QAOPS9,P2D,P2-ORG,P2-FIN-DECISION,P2-FIN-V1,P2-OCR-SAMPLE,P2Q,P2AUTO,P2AF,P2-FIN-LIVE-DECISION,P2-FIN-LIVE,PRELAUNCH,CASHIER-VOUCHER-V1,LAUNCH-DEPLOY-FINANCE,FIN-PROD-ENABLE,ACCOUNTING-HANDOFF-V1,SYSTEM-COMPLETE-GATE,LAUNCH-OPS-V1,REQ-COMPLETE-AUDIT,ATTV1,LAUNCH-AUTO-OPS-V1,AUTO-REMAINING-CLOSEOUT,HR-WECOM-DIRECTORY-V1,WFOC,DBWFOC,WFDG,WDRG,WCP1,WCPC,WHMS,WHMSLIVE,WOUA,WAPI,WRRA,WOUAFIX,WRV1,WRA1,WMSG1,WAPC1,WAPC2,WPROC1` | `audit-docs` | none | 2026-05-19 | `audit-docs --check-registry` | medium | 判断文档权威性 | +| `IDX` | `docs/文档索引.md` | 入口索引 | 最高文档入口 | `CURRENT` | product/dev | `GOV8,QAOPS9,P2D,P2-ORG,P2-FIN-DECISION,P2-FIN-V1,P2-OCR-SAMPLE,P2Q,P2AUTO,P2AF,P2-FIN-LIVE-DECISION,P2-FIN-LIVE,PRELAUNCH,CASHIER-VOUCHER-V1,LAUNCH-DEPLOY-FINANCE,FIN-PROD-ENABLE,ACCOUNTING-HANDOFF-V1,SYSTEM-COMPLETE-GATE,LAUNCH-OPS-V1,REQ-COMPLETE-AUDIT,ATTV1,LAUNCH-AUTO-OPS-V1,AUTO-REMAINING-CLOSEOUT,HR-WECOM-DIRECTORY-V1,WFOC,DBWFOC,WFDG,WDRG,WCP1,WCPC,WHMS,WHMSLIVE,WOUA,WAPI,WRRA,WOUAFIX,WRV1,WRA1,WMSG1,WAPC1,WAPC2,WPROC1,WPROC2` | `rg` | none | 2026-05-19 | `node scripts/qa/audit-docs.mjs --check-registry` | medium | 判断当前该读什么 | +| `REG` | `docs/文档事实源状态矩阵.md` | 文档台账 | 文档分类和状态事实源 | `CURRENT` | product/dev | `GOV8,QAOPS9,P2D,P2-ORG,P2-FIN-DECISION,P2-FIN-V1,P2-OCR-SAMPLE,P2Q,P2AUTO,P2AF,P2-FIN-LIVE-DECISION,P2-FIN-LIVE,PRELAUNCH,CASHIER-VOUCHER-V1,LAUNCH-DEPLOY-FINANCE,FIN-PROD-ENABLE,ACCOUNTING-HANDOFF-V1,SYSTEM-COMPLETE-GATE,LAUNCH-OPS-V1,REQ-COMPLETE-AUDIT,ATTV1,LAUNCH-AUTO-OPS-V1,AUTO-REMAINING-CLOSEOUT,HR-WECOM-DIRECTORY-V1,WFOC,DBWFOC,WFDG,WDRG,WCP1,WCPC,WHMS,WHMSLIVE,WOUA,WAPI,WRRA,WOUAFIX,WRV1,WRA1,WMSG1,WAPC1,WAPC2,WPROC1,WPROC2` | `audit-docs` | none | 2026-05-19 | `audit-docs --check-registry` | medium | 判断文档权威性 | | `PRD-MVP` | `docs/海音达OA-MVP需求规格说明书.md` | 需求事实源 | PRD 主事实源 | `CURRENT` | product | `REQ8,P2T,GOV8,HR-WECOM-DIRECTORY-V1,WFOC,DBWFOC,WFDG,WDRG,WCP1,WCPC,WHMS,WHMSLIVE,WOUA,WAPI,WRRA,WAPC1` | Issue CSV | `docs/requirements-specification.md` | 2026-05-19 | `rg` | high | WAPC1 后定义 HR 主数据镜像读取口径、运行优先、WOUA 差异收口、消息实发和审批配置准备边界;ERP 未来不接入 OA | | `PRODUCT-SCOPE` | `docs/产品口径.md` | 需求事实源 | 产品边界事实源 | `CURRENT` | product | `REQ8,P2T,WFDG,WDRG,WCP1,WCPC,WHMS,WHMSLIVE,WOUA,WAPI,WRRA,WAPC1` | `rg` | none | 2026-05-19 | `rg` | high | 记录已拍板产品边界、企微/OA 设计门禁、开发路线图、WCPC 准入结论、WHMS 部署验证、WAPI 运行优先、WRRA 差异收口和 WAPC1 审批配置准备口径 | | `DESIGN-UI` | `docs/产品设计规则.md` | 设计事实源 | PC/移动/企业微信设计事实源 | `CURRENT` | design/frontend | `MIA7,MIF7,GOV8,DG8,DG9,WFDG` | Playwright/视觉审查/DG8/DG9/WFDG | none | 2026-05-18 | `rg` | high | 页面改动必须读取;DG8 后飞书为主 UI/布局参考;DG9 后按页面分层和样板选型;WFDG 后区分 OA 主办、企微镜像和 OA 应急接管 | -| `ROADMAP-WECOM` | `docs/企微优先开发路线图.md` | 开发治理 | 企微优先开发路线图事实源 | `CURRENT` | product/dev | `WDRG,WCP1,WCPC,WHMS,WHMSLIVE,WOUA,WAPI,WRRA,WOUAFIX,WRV1,WRA1,WMSG1,WAPC1,WAPC2,WPROC1` | Issue CSV | none | 2026-05-19 | `rg` | high | WPROC1 后记录企微 HR/考勤路线阶段顺序、API/Secret 口径、运行优先、消息实发、审批配置、只读 POC、WAPC2F 自动样件、WCBK1 回调、WACP3 补偿拉取、WADM1 模型设计和禁止跳阶段 | -| `WECOM-API-CATALOG` | `docs/企微API能力目录.md` | 集成事实源 | 企微 API 能力目录和 Secret 矩阵事实源 | `CURRENT` | product/backend/qa | `WAPI,WRRA,WAPC1,WAPC2,WPROC1` | 官方企业微信开发者中心链接/Issue CSV | none | 2026-05-19 | `rg` | high | 企微开发先读;只保存摘要、链接和项目决策,不保存 Secret、token、完整 userid、手机号、邮箱或原始 payload;WPROC1 后深度镜像等待自动样件、回调、补偿拉取和数据模型设计 | -| `DELIVERY` | `docs/开发交付与变更管理规约.md` | 开发治理 | 开发交付事实源 | `CURRENT` | dev/qa | `GOV8,WDRG,WRRA,WAPC1,WPROC1` | `rg` | none | 2026-05-19 | `rg` | high | Issue、review、commit、交付规则;WPROC1 后采用路线门禁、能力门禁、实现门禁、DoR/DoD 和自动化优先 | -| `QA-GATE` | `docs/测试规约与回归门禁.md` | 测试验收 | 测试和回归门禁事实源 | `CURRENT` | qa | `QAF8,GOV8,WPROC1` | 回归脚本/Playwright | none | 2026-05-19 | `rg` | high | 测试计划、DONE 门禁、企微三层门禁、自动化优先和非自审测试要求 | +| `ROADMAP-WECOM` | `docs/企微优先开发路线图.md` | 开发治理 | 企微优先开发路线图事实源 | `CURRENT` | product/dev | `WDRG,WCP1,WCPC,WHMS,WHMSLIVE,WOUA,WAPI,WRRA,WOUAFIX,WRV1,WRA1,WMSG1,WAPC1,WAPC2,WPROC1,WPROC2` | Issue CSV | none | 2026-05-19 | `rg` | high | WPROC2 后记录企微 HR/考勤路线阶段顺序、API/Secret 口径、运行优先、消息实发、审批配置、只读 POC、process gate、WAPC2F 自动样件、WCBK1 回调、WACP3 补偿拉取、WADM1 模型设计和禁止跳阶段 | +| `WECOM-API-CATALOG` | `docs/企微API能力目录.md` | 集成事实源 | 企微 API 能力目录和 Secret 矩阵事实源 | `CURRENT` | product/backend/qa | `WAPI,WRRA,WAPC1,WAPC2,WPROC1,WPROC2` | 官方企业微信开发者中心链接/Issue CSV | none | 2026-05-19 | `rg` | high | 企微开发先读;只保存摘要、链接和项目决策,不保存 Secret、token、完整 userid、手机号、邮箱或原始 payload;WPROC2 后高风险企微合同先跑 process gate | +| `DELIVERY` | `docs/开发交付与变更管理规约.md` | 开发治理 | 开发交付事实源 | `CURRENT` | dev/qa | `GOV8,WDRG,WRRA,WAPC1,WPROC1,WPROC2` | `rg` | none | 2026-05-19 | `rg` | high | Issue、review、commit、交付规则;WPROC2 后采用路线门禁、能力门禁、实现门禁、DoR/DoD、自动化优先和可执行 process gate | +| `QA-GATE` | `docs/测试规约与回归门禁.md` | 测试验收 | 测试和回归门禁事实源 | `CURRENT` | qa | `QAF8,GOV8,WPROC1,WPROC2` | 回归脚本/Playwright | none | 2026-05-19 | `rg` | high | 测试计划、DONE 门禁、企微三层门禁、自动化优先、非自审测试要求和 check-process-contract | | `AGENT-PRACTICE` | `docs/Agent开发交付实践.md` | Agent 治理 | Agent 协作事实源 | `CURRENT` | dev | `GOV8,DG8` | Skill 校验 | none | 2026-05-13 | `rg` | medium | Agent 协作开发实践和设计主参考层级 | | `AGENTS` | `AGENTS.md` | 执行硬约束 | 项目硬规则 | `CURRENT` | lead | `GOV8` | 人工核对 | none | 2026-05-13 | `rg` | high | 不可违反约束 | | `SKILL-HYB` | `.agents/skills/hyb-oa-delivery/SKILL.md` | Skill | 项目交付 Skill | `CURRENT` | dev | `GOV8,DG8,WFDG,WDRG,WCP1,WCPC,WHMS,WHMSLIVE,WOUA,WAPI,WRRA,WRV1,WRA1,WMSG1,WAPC1,WAPC2,WPROC1` | quick_validate | none | 2026-05-19 | `quick_validate.py` | medium | 每轮交付操作入口;企微/OA 后续先读 API 能力目录、Secret 矩阵、路线图和 WPROC1 状态,不得跳过 WAPC2F/WCBK1/WACP3/WADM1 直接做考勤审批镜像 | @@ -120,14 +121,14 @@ | `docs/文档事实源状态矩阵.md` | 当前事实源 | 文档分类和恢复规则 | | `docs/海音达OA-MVP需求规格说明书.md` | 当前事实源 | PRD 主事实源 | | `docs/产品设计规则.md` | 当前事实源 | PC、移动、企业微信设计规则;DG8 主 UI/布局参考层级 | -| `docs/企微优先开发路线图.md` | 当前事实源 | WDRG/WCP1/WCPC/WHMS/WHMSLIVE/WOUA/WAPI/WRRA/WAPC2/WPROC1 后企微优先 HR/考勤改造阶段顺序、API/Secret 口径、运行优先、审批只读 POC、自动样件、回调、补偿拉取、模型设计和禁止跳阶段 | -| `docs/企微API能力目录.md` | 当前事实源 | WAPI/WRRA/WAPC2/WPROC1 后企微官方 API、Secret 矩阵、权限/可信 IP/回调模板、读写边界、运行优先、审批只读 POC 和后续 POC 决策 | +| `docs/企微优先开发路线图.md` | 当前事实源 | WDRG/WCP1/WCPC/WHMS/WHMSLIVE/WOUA/WAPI/WRRA/WAPC2/WPROC1/WPROC2 后企微优先 HR/考勤改造阶段顺序、API/Secret 口径、运行优先、审批只读 POC、process gate、自动样件、回调、补偿拉取、模型设计和禁止跳阶段 | +| `docs/企微API能力目录.md` | 当前事实源 | WAPI/WRRA/WAPC2/WPROC1/WPROC2 后企微官方 API、Secret 矩阵、权限/可信 IP/回调模板、读写边界、运行优先、审批只读 POC 和后续 POC 决策 | | `docs/产品口径.md` | 当前事实源 | 产品边界和业务规则 | | `docs/需求-统一审批中心规约.md` | 当前事实源 | 审批、催办、超时、效率摘要 | | `docs/报销发票附件规则.md` | 当前事实源 | 报销、发票、附件细则 | | `docs/需求-报销发票OCR规约.md` | 当前事实源 | OCR 和报销模块规约 | | `docs/需求-企业微信基础设施规约.md` | 当前事实源 | 企业微信入口、身份、消息、deeplink | -| `docs/需求-请假考勤修正规约.md` | 当前事实源 | 请假、补卡、考勤修正、WAPC2 只读 POC 和 WPROC1 后续门禁 | +| `docs/需求-请假考勤修正规约.md` | 当前事实源 | 请假、补卡、考勤修正、WAPC2 只读 POC 和 WPROC1/WPROC2 后续门禁 | | `docs/需求-采购费用审批规约.md` | 当前事实源 | 采购和费用审批 | | `docs/需求-合同附件归档规约.md` | 当前事实源 | 合同台账和附件归档 | | `docs/需求-项目轻管理规约.md` | 当前事实源 | 项目轻管理 | @@ -196,8 +197,9 @@ - `ME6-3` 代表性企业微信真机门禁已关闭。 - MIF7-1..7 与 MIF7T 已完成本地整改、公网部署、公网匿名复检和带凭据 live smoke/OCR 复检。 -- R7E 真实扩大试运行阶段验收、P2 排序、R7E 收口、QAOPS8 上线硬化阶段收口、QAOPS9 上线硬化连续合同、PRELAUNCH 正式启用前公网预回归、CASHIER-VOUCHER-V1 财务闭环与银行回单附件、LAUNCH-DEPLOY-FINANCE 部署复检、FIN-PROD-ENABLE 正式常开、ACCOUNTING-HANDOFF-V1 代账交接 SOP、SYSTEM-COMPLETE-GATE 系统完成门禁、LAUNCH-OPS-V1 首周运营保障、REQ-COMPLETE-AUDIT 需求完成度复核、ATTV1 考勤统一入口、AUTO-REMAINING-CLOSEOUT 剩余自动化收口、HRWD-CONTACT-LIVE-TEST 公网企微通讯录真实写入闭环、WFOC 需求重构、DBWFOC 数据边界修正、WFDG 设计门禁、WDRG 路线图门禁、WCPC 配置复跑、WHMSLIVE、WAPI、WRRA、WOUA-FIX、WRA1、WMSG1、WAPC1、WAPC2 和 WPROC1 均已完成,当前 P0/P1 阻断为 0;P2D 已完成决策分流,P2-ORG 只读巡检已完成且无需写修复合同。LAUNCH-AUTO-OPS-V1 发现的公网 `payment_batch_v1=force_off` 漂移已由 AUTOREM 恢复为 `force_on`,正式自动巡检结论为 `GO_TO_OPERATE`;`erp_sync=force_off`,ERP 未来不接入 OA,正式支付接口、图片 OCR 和银行流水导入继续不进入当前路线。WCP1 已完成企业微信能力 QA-only 只读 POC,历史结论为 `PAUSE_FOR_CONFIG`;WCPC 使用既有在职测试号 `SMOKE002/测试员工`(脱敏 `oa_smoke***`)完成远端只读复跑,报告 `/tmp/wecom-capability-poc-wcpc-smoke002/summary.json`,结论 `GO_TO_HR_MASTER_SYNC`,`nextStageAllowed=true`,`blockingCapabilities=[]`。WHMS 已实现并部署验证 `wecom_hr_master_sync_v1`、同步批次/逐员工记录、dry-run/apply、管理员 API、People 管理入口和 QA-only runner;默认和最终均为 `force_off`,公网 `SMOKE002` 因缺手机号、邮箱和部门按 `missing_required_field` 跳过,未执行生产真实员工批量 apply。WAPI 已将企微官方 API 能力目录、Secret 矩阵和运行优先路线落为当前事实源:通讯录读取使用 `WECOM_CONTACT_SECRET` 和 `user/list_id`,成员详情只作可选富化,人事助手/花名册、打卡、消息、OAuth 和审批读取默认使用 `WECOM_AGENT_SECRET`,`WECOM_CHECKIN_SECRET` 只作专用打卡 override,`WECOM_HR_ASSISTANT_SECRET` 不参与默认读取;后续企微能力不清楚时先查官方 API 和 [企微 API 能力目录](./企微API能力目录.md),不得靠运行时多个 Secret 试错。WOUA-FIX 已按用户确认执行受控写入并复检:7 个 OA active 缺企微测试账号软归档,`T0000` seed 复活问题修正,`SMOKE201` 测试主管和待办副作用已转交,最终 WOUA 报告 `/app/artifacts/qa-flow/wecom-oa-user-consistency-20260518T161716Z/summary.json` 为 `PASS_WITH_FINDINGS`,但 OA active 5、企微 active 5、匹配 5、OA active 缺企微 0;剩余 5 个企微缺字段先接受不补,`HRWDTEST-20260517214548` 归档绑定保留审计。WRV1 原始报告 `/app/artifacts/qa-flow/wecom-runtime-ready-20260518T173402Z/summary.json` 曾因同步开关误阻断;WRA1 已修正并公网复跑,报告 `/app/artifacts/qa-flow/wecom-runtime-ready-20260518T180506Z/summary.json` 为 `PASS_WITH_CONFIG_GAPS`,入口/OAuth、Secret 矩阵、OA active 绑定、通讯录、人事助手、消息 dry-run 和打卡只读链路均通过,打卡记录 `readable_sync_disabled`。WMSG1 已证明企业微信应用消息和 Deeplink 可用;WAPC1 已固定首批审批模板(请假、补卡、外出、加班)、低风险 `sp_no`、真实回调配置项、验签/解密/幂等/重试和补偿拉取准入清单;WAPC2 已新增请假/补卡模板详情和审批详情只读 POC runner;WPROC1 已固定后续自动化优先、非自审和三层门禁。HR/考勤员工日常审批优先复用企业微信,OA 目标是按域镜像、人工接管、恢复回写和 RuleOps;采购、费用、合同、项目、报销、付款、AP、代账交接、审计和高敏附件继续 OA 主办。考勤/审批镜像、审批回调、OA 应急接管、恢复回写和 RuleOps 尚未实现;审批模板 ID、审批单号 `sp_no`、真实回调配置、补偿拉取、镜像数据模型和 HR 写接口仍需后续独立合同处理;不得跳过 WAPC2F、WCBK1、WACP3、WADM1 直接进入 `wecom_attendance_approval_mirror_v1`、`oa_hr_emergency_mode_v1`、`wecom_recovery_writeback_v1` 或 `wecom_ruleops_v1`。HRWD 已完成 OA 驱动企业微信通讯录同步,并通过 `HRWDTEST-20260517214548` 真实公网 create/update/disable 验证;测试成员按审计要求保留离职/禁用状态。 +- R7E 真实扩大试运行阶段验收、P2 排序、R7E 收口、QAOPS8 上线硬化阶段收口、QAOPS9 上线硬化连续合同、PRELAUNCH 正式启用前公网预回归、CASHIER-VOUCHER-V1 财务闭环与银行回单附件、LAUNCH-DEPLOY-FINANCE 部署复检、FIN-PROD-ENABLE 正式常开、ACCOUNTING-HANDOFF-V1 代账交接 SOP、SYSTEM-COMPLETE-GATE 系统完成门禁、LAUNCH-OPS-V1 首周运营保障、REQ-COMPLETE-AUDIT 需求完成度复核、ATTV1 考勤统一入口、AUTO-REMAINING-CLOSEOUT 剩余自动化收口、HRWD-CONTACT-LIVE-TEST 公网企微通讯录真实写入闭环、WFOC 需求重构、DBWFOC 数据边界修正、WFDG 设计门禁、WDRG 路线图门禁、WCPC 配置复跑、WHMSLIVE、WAPI、WRRA、WOUA-FIX、WRA1、WMSG1、WAPC1、WAPC2、WPROC1 和 WPROC2 均已完成,当前 P0/P1 阻断为 0;P2D 已完成决策分流,P2-ORG 只读巡检已完成且无需写修复合同。LAUNCH-AUTO-OPS-V1 发现的公网 `payment_batch_v1=force_off` 漂移已由 AUTOREM 恢复为 `force_on`,正式自动巡检结论为 `GO_TO_OPERATE`;`erp_sync=force_off`,ERP 未来不接入 OA,正式支付接口、图片 OCR 和银行流水导入继续不进入当前路线。WCP1 已完成企业微信能力 QA-only 只读 POC,历史结论为 `PAUSE_FOR_CONFIG`;WCPC 使用既有在职测试号 `SMOKE002/测试员工`(脱敏 `oa_smoke***`)完成远端只读复跑,报告 `/tmp/wecom-capability-poc-wcpc-smoke002/summary.json`,结论 `GO_TO_HR_MASTER_SYNC`,`nextStageAllowed=true`,`blockingCapabilities=[]`。WHMS 已实现并部署验证 `wecom_hr_master_sync_v1`、同步批次/逐员工记录、dry-run/apply、管理员 API、People 管理入口和 QA-only runner;默认和最终均为 `force_off`,公网 `SMOKE002` 因缺手机号、邮箱和部门按 `missing_required_field` 跳过,未执行生产真实员工批量 apply。WAPI 已将企微官方 API 能力目录、Secret 矩阵和运行优先路线落为当前事实源:通讯录读取使用 `WECOM_CONTACT_SECRET` 和 `user/list_id`,成员详情只作可选富化,人事助手/花名册、打卡、消息、OAuth 和审批读取默认使用 `WECOM_AGENT_SECRET`,`WECOM_CHECKIN_SECRET` 只作专用打卡 override,`WECOM_HR_ASSISTANT_SECRET` 不参与默认读取;后续企微能力不清楚时先查官方 API 和 [企微 API 能力目录](./企微API能力目录.md),不得靠运行时多个 Secret 试错。WOUA-FIX 已按用户确认执行受控写入并复检:7 个 OA active 缺企微测试账号软归档,`T0000` seed 复活问题修正,`SMOKE201` 测试主管和待办副作用已转交,最终 WOUA 报告 `/app/artifacts/qa-flow/wecom-oa-user-consistency-20260518T161716Z/summary.json` 为 `PASS_WITH_FINDINGS`,但 OA active 5、企微 active 5、匹配 5、OA active 缺企微 0;剩余 5 个企微缺字段先接受不补,`HRWDTEST-20260517214548` 归档绑定保留审计。WRV1 原始报告 `/app/artifacts/qa-flow/wecom-runtime-ready-20260518T173402Z/summary.json` 曾因同步开关误阻断;WRA1 已修正并公网复跑,报告 `/app/artifacts/qa-flow/wecom-runtime-ready-20260518T180506Z/summary.json` 为 `PASS_WITH_CONFIG_GAPS`,入口/OAuth、Secret 矩阵、OA active 绑定、通讯录、人事助手、消息 dry-run 和打卡只读链路均通过,打卡记录 `readable_sync_disabled`。WMSG1 已证明企业微信应用消息和 Deeplink 可用;WAPC1 已固定首批审批模板(请假、补卡、外出、加班)、低风险 `sp_no`、真实回调配置项、验签/解密/幂等/重试和补偿拉取准入清单;WAPC2 已新增请假/补卡模板详情和审批详情只读 POC runner;WPROC1 已固定后续自动化优先、非自审和三层门禁,WPROC2 已将其升级为 `check-process-contract` process gate。HR/考勤员工日常审批优先复用企业微信,OA 目标是按域镜像、人工接管、恢复回写和 RuleOps;采购、费用、合同、项目、报销、付款、AP、代账交接、审计和高敏附件继续 OA 主办。考勤/审批镜像、审批回调、OA 应急接管、恢复回写和 RuleOps 尚未实现;审批模板 ID、审批单号 `sp_no`、真实回调配置、补偿拉取、镜像数据模型和 HR 写接口仍需后续独立合同处理;不得跳过 WAPC2F、WCBK1、WACP3、WADM1 直接进入 `wecom_attendance_approval_mirror_v1`、`oa_hr_emergency_mode_v1`、`wecom_recovery_writeback_v1` 或 `wecom_ruleops_v1`。HRWD 已完成 OA 驱动企业微信通讯录同步,并通过 `HRWDTEST-20260517214548` 真实公网 create/update/disable 验证;测试成员按审计要求保留离职/禁用状态。 - WMSG1 已完成企微应用消息实发与 Deeplink 真机验证:远端报告 `/app/artifacts/qa-flow/wecom-runtime-ready-20260518T234431Z/summary.json` 显示 `checks.message.status=OK`、`errcode=0`、`recipientCount=1`,用户确认收到“OA 运行就绪检查”并可点击进入 OA 通知页面;本证据只证明消息和 Deeplink 可用,不放行审批模板、测试 `sp_no`、真实回调或深度镜像。 - WAPC1 已完成企微审批配置准备清单:首批只准备请假、补卡、外出、加班模板;每个模板需准备模板 ID、适用范围、字段项、审批人规则、可见范围和自建应用权限;测试 `sp_no` 必须低风险且不得含采购、合同、报销、发票、银行回单或高敏附件;真实回调 Token/EncodingAESKey 不写入仓库或文档。本证据只代表配置准备口径已固化,不代表真实回调、审批镜像、应急接管、恢复回写或 RuleOps 已实现。 - WAPC2 已完成企微审批模板与低风险 `sp_no` 只读 POC 本地实现、部署和公网只读复跑:范围只包括请假和补卡,固定 `WECOM_AGENT_SECRET`,远端报告为 `PARTIAL_APPROVAL_POC_NEEDS_CONFIG`;本证据只代表模板详情/审批详情只读 runner 已具备且远端 Secret 可用,不代表模板 ID、测试 `sp_no`、真实回调或深度镜像已配置完成。 - WPROC1 已修正企微路线与开发流程工程化门禁:后续默认自动化优先,人工只处理批准、密钥/回调配置、企微后台不可 API 化配置、真机确认和风险拍板;WAPC2F 默认使用 `SMOKE002 -> SMOKE101` 非自审链,`SMOKE101` 不可用时停止问答确认;`wecom_attendance_approval_mirror_v1` 前必须完成 WAPC2F、WAPC2 自动复跑、WCBK1、WACP3 和 WADM1。 +- WPROC2 已新增开发流程自动化门禁:`node scripts/qa/check-process-contract.mjs --baseline` 检查核心事实源和 WAPC2F 合同,`wecom-approval-fixture` profile 检查非自审、Secret 固定、live 双开关、缺配置分类和脱敏边界;该门禁已接入 `scripts/check-minimal.sh`。 diff --git a/docs/文档索引.md b/docs/文档索引.md index 3915aaaa..7f81b302 100644 --- a/docs/文档索引.md +++ b/docs/文档索引.md @@ -20,7 +20,7 @@ ## 最近事实源链 -R6 -> PC6 -> PA6 -> FL6 -> AP6 -> RI6 -> LP6 -> ME6/QG6 -> DG7/MRF7 -> MIA7 -> MIF7/MIF7T(公网部署与带凭据公网复检已收口) -> DG8 -> DG9 -> R7E -> FBA -> QAOPS9/P2D -> P2-ORG -> P2-FIN-DECISION -> P2-FIN-V1 -> P2-OCR-SAMPLE -> P2Q -> P2AUTO -> P2AF -> P2-FIN-LIVE-DECISION -> P2-FIN-LIVE -> PRELAUNCH -> CASHIER-VOUCHER-V1 -> LAUNCH-DEPLOY-FINANCE -> FIN-PROD-ENABLE -> ACCOUNTING-HANDOFF-V1 -> SYSTEM-COMPLETE-GATE -> LAUNCH-OPS-V1 -> REQ-COMPLETE-AUDIT -> ATTV1 -> LAUNCH-AUTO-OPS-V1 -> AUTO-REMAINING-CLOSEOUT -> HR-WECOM-DIRECTORY-V1 -> HRWD-CONTACT-LIVE-TEST -> WCFB -> WFOC -> DBWFOC -> WFDG -> WDRG -> WCP1 -> WCPC -> WHMS -> WHMSLIVE -> WOUA -> WAPI -> WRRA -> WOUA-FIX -> WRV1 -> WRA1 -> WMSG1 -> WAPC1 -> WAPC2 -> WPROC1。 +R6 -> PC6 -> PA6 -> FL6 -> AP6 -> RI6 -> LP6 -> ME6/QG6 -> DG7/MRF7 -> MIA7 -> MIF7/MIF7T(公网部署与带凭据公网复检已收口) -> DG8 -> DG9 -> R7E -> FBA -> QAOPS9/P2D -> P2-ORG -> P2-FIN-DECISION -> P2-FIN-V1 -> P2-OCR-SAMPLE -> P2Q -> P2AUTO -> P2AF -> P2-FIN-LIVE-DECISION -> P2-FIN-LIVE -> PRELAUNCH -> CASHIER-VOUCHER-V1 -> LAUNCH-DEPLOY-FINANCE -> FIN-PROD-ENABLE -> ACCOUNTING-HANDOFF-V1 -> SYSTEM-COMPLETE-GATE -> LAUNCH-OPS-V1 -> REQ-COMPLETE-AUDIT -> ATTV1 -> LAUNCH-AUTO-OPS-V1 -> AUTO-REMAINING-CLOSEOUT -> HR-WECOM-DIRECTORY-V1 -> HRWD-CONTACT-LIVE-TEST -> WCFB -> WFOC -> DBWFOC -> WFDG -> WDRG -> WCP1 -> WCPC -> WHMS -> WHMSLIVE -> WOUA -> WAPI -> WRRA -> WOUA-FIX -> WRV1 -> WRA1 -> WMSG1 -> WAPC1 -> WAPC2 -> WPROC1 -> WPROC2。 - R6、PC6、PA6、FL6、AP6、RI6、LP6 已完成并部署。 - ME6 自动化、公网视觉矩阵和 `ME6-3` 代表性企业微信真机门禁已完成。 @@ -72,6 +72,7 @@ R6 -> PC6 -> PA6 -> FL6 -> AP6 -> RI6 -> LP6 -> ME6/QG6 -> DG7/MRF7 -> MIA7 -> M - WAPC1 是 WMSG1 后的企微审批配置准备清单合同:只固化请假、补卡、外出、加班模板准备项、低风险 `sp_no`、真实回调配置项和 POC 准入,不开发镜像。 - WAPC2 是 WAPC1 后的请假/补卡审批只读 POC 合同:新增 `server/src/scripts/check-wecom-approval-poc.js` 和 `npm run check:wecom-approval-poc`,固定 `WECOM_AGENT_SECRET` 读取 `gettemplatedetail`/`getapprovaldetail`;缺模板 ID 或低风险 `sp_no` 时报告 `PARTIAL_APPROVAL_POC_NEEDS_CONFIG`,不放行深度镜像。 - WPROC1 是 WAPC2 后的企微路线与开发流程工程化修正合同:只更新合同和事实源,不写业务代码、不部署、不写 OA/企微数据;固定后续顺序为 `WAPC2F -> WAPC2 自动复跑 -> WCBK1 -> WACP3 -> WADM1 -> wecom_attendance_approval_mirror_v1`,并修正 WAPC2F 默认非自审链为 `SMOKE002 -> SMOKE101`。 +- WPROC2 是 WPROC1 后的开发流程自动化门禁合同:新增 `scripts/qa/check-process-contract.mjs` process gate,并接入 `scripts/check-minimal.sh`;WAPC2F 实施前必须先通过 baseline gate 和 `wecom-approval-fixture` profile。本轮不部署、不写 OA/企微数据、不实现 WAPC2F 业务 runner。 ## 当前活跃治理流 @@ -131,6 +132,7 @@ R6 -> PC6 -> PA6 -> FL6 -> AP6 -> RI6 -> LP6 -> ME6/QG6 -> DG7/MRF7 -> MIA7 -> M | `WAPC1` | `issues/2026-05-19_08-44-58-wecom-approval-config-prep-v1.csv` | 已完成:企微审批配置准备清单 | 固化 WMSG1 后下一步为审批配置准备,不直接进入深度镜像;首批模板为请假、补卡、外出、加班;准备模板 ID、低风险 `sp_no`、真实回调配置项和后续 POC 准入 | 不改业务代码/API/schema/UI;不部署;不接真实回调;不调用企微写接口;不配置模板或 `sp_no` | | `WAPC2` | `issues/2026-05-19_09-34-16-wecom-approval-template-spno-poc-v1.csv` | 已部署并公网只读复跑:企微审批模板与低风险 `sp_no` 只读 POC | 新增 QA-only runner、只读集成服务和 focused 单测;范围请假+补卡;固定 `WECOM_AGENT_SECRET` 调用 `gettemplatedetail` / `getapprovaldetail`;远端报告 `/app/artifacts/qa-flow/wecom-approval-poc-20260519T020429Z/summary.json` 为 `PARTIAL_APPROVAL_POC_NEEDS_CONFIG` | 不新增公开 API/schema/UI;不接真实回调;不调用企微写接口;不写 OA/企微数据;不进入深度镜像 | | `WPROC1` | `issues/2026-05-19_10-43-50-wecom-process-route-engineering-v1.csv` | 当前路线与流程治理:企微工程化门禁修正 | 固化三层门禁、Definition of Ready、Definition of Done、自动化优先、非自审和高风险能力先 POC;修正 WAPC2F 默认链为 `SMOKE002 -> SMOKE101` | docs-only/contract-only;不改业务代码、不部署、不写 OA/企微数据;WAPC2 后仍需 WAPC2F、WCBK1、WACP3、WADM1 后才能进入深度镜像 | +| `WPROC2` | `issues/2026-05-19_11-15-10-process-gate-automation-v1.csv` | 当前流程自动化门禁:process gate 框架 | 新增 `check-process-contract` QA-only CLI、WAPC2F profile、最小门禁接入和事实源回写;WAPC2F 实施前先跑 baseline gate | 不部署、不改业务 API/schema/UI、不写 OA/企微数据、不实现 WAPC2F runner | 旧 Issue CSV 不删除、不批量回写、不移动到归档目录。若新治理流替代旧合同,只在新 CSV 的 `Notes` 或本索引说明 supersedes,旧文件保留历史审计语义。 @@ -150,17 +152,17 @@ R6 -> PC6 -> PA6 -> FL6 -> AP6 -> RI6 -> LP6 -> ME6/QG6 -> DG7/MRF7 -> MIA7 -> M | 文档 | 用途 | | --- | --- | -| [海音达OA-MVP需求规格说明书](./海音达OA-MVP需求规格说明书.md) | PRD 主事实源;WAPC1/WAPC2/WPROC1 后包含企微审批配置、只读 POC 和后续工程化前置 | +| [海音达OA-MVP需求规格说明书](./海音达OA-MVP需求规格说明书.md) | PRD 主事实源;WAPC1/WAPC2/WPROC1/WPROC2 后包含企微审批配置、只读 POC、工程化前置和 process gate 口径 | | [文档事实源状态矩阵](./文档事实源状态矩阵.md) | 文档分类、删除恢复和冲突规则 | | [产品设计规则](./产品设计规则.md) | PC、移动、企业微信 WebView 设计事实源;DG8 后飞书工作台/飞书审批为唯一主 UI/布局参考;DG9 后按页面分层和样板选型 | -| [企微优先开发路线图](./企微优先开发路线图.md) | WDRG/WCP1/WCPC/WHMS/WHMSLIVE/WOUA/WAPI/WRRA/WOUA-FIX/WRV1/WRA1/WMSG1/WAPC1/WAPC2/WPROC1 后企微优先 HR/考勤改造的阶段顺序、准入/退出门禁、并行规则、禁止跳阶段、API/Secret 口径、运行优先、消息实发、审批配置、只读 POC、自动样件、回调、补偿拉取和模型设计事实源 | -| [企微 API 能力目录](./企微API能力目录.md) | WAPI/WAPC1/WAPC2/WPROC1 后企微官方 API、Secret 矩阵、权限/可信 IP/回调模板、读写边界、运行优先、审批配置、只读 POC、WAPC2F/WCBK1/WACP3/WADM1 决策事实源 | -| [产品口径](./产品口径.md) | 已拍板产品边界和业务规则;WAPC1/WAPC2/WPROC1 后包含审批配置、只读 POC 和工程化前置阶段顺序 | +| [企微优先开发路线图](./企微优先开发路线图.md) | WDRG/WCP1/WCPC/WHMS/WHMSLIVE/WOUA/WAPI/WRRA/WOUA-FIX/WRV1/WRA1/WMSG1/WAPC1/WAPC2/WPROC1/WPROC2 后企微优先 HR/考勤改造的阶段顺序、准入/退出门禁、并行规则、禁止跳阶段、API/Secret 口径、运行优先、消息实发、审批配置、只读 POC、process gate、自动样件、回调、补偿拉取和模型设计事实源 | +| [企微 API 能力目录](./企微API能力目录.md) | WAPI/WAPC1/WAPC2/WPROC1/WPROC2 后企微官方 API、Secret 矩阵、权限/可信 IP/回调模板、读写边界、运行优先、审批配置、只读 POC、WAPC2F/WCBK1/WACP3/WADM1 决策事实源 | +| [产品口径](./产品口径.md) | 已拍板产品边界和业务规则;WAPC1/WAPC2/WPROC1/WPROC2 后包含审批配置、只读 POC、工程化前置阶段顺序和 process gate | | [需求 - 统一审批中心规约](./需求-统一审批中心规约.md) | OA 主办审批、企微主办 HR/考勤员工日常审批镜像、催办、超时提醒、审批效率 | | [报销发票附件规则](./报销发票附件规则.md) | 报销、发票、OCR、附件细则 | | [需求 - 报销发票 OCR 规约](./需求-报销发票OCR规约.md) | 报销发票 OCR 模块规约 | | [需求 - 企业微信基础设施规约](./需求-企业微信基础设施规约.md) | 企业微信入口、身份、消息、deeplink、HR/考勤员工日常审批、打卡、人事助手、按域镜像、OA 接管和恢复回写 | -| [需求 - 请假考勤修正规约](./需求-请假考勤修正规约.md) | 企微请假/补卡/外出/加班、WAPC1 审批配置准备、WAPC2 请假/补卡只读 POC、WPROC1 后续门禁、考勤镜像、OA 应急接管和恢复回写 | +| [需求 - 请假考勤修正规约](./需求-请假考勤修正规约.md) | 企微请假/补卡/外出/加班、WAPC1 审批配置准备、WAPC2 请假/补卡只读 POC、WPROC1/WPROC2 后续门禁、考勤镜像、OA 应急接管和恢复回写 | | [需求 - 采购费用审批规约](./需求-采购费用审批规约.md) | 采购、费用、附件、审批规则 | | [需求 - 合同附件归档规约](./需求-合同附件归档规约.md) | 合同台账和附件归档边界 | | [需求 - 项目轻管理规约](./需求-项目轻管理规约.md) | 项目轻管理边界 | diff --git a/docs/测试规约与回归门禁.md b/docs/测试规约与回归门禁.md index 99fa5efe..cd1c620e 100644 --- a/docs/测试规约与回归门禁.md +++ b/docs/测试规约与回归门禁.md @@ -2,7 +2,7 @@ **文档状态**:当前测试事实源。 **适用范围**:hyb-oa MVP 首轮开发 A3-A8,以及后续所有非微小改动。 -**路线口径**:WFOC/DBWFOC/WFDG/WDRG 后,HR/考勤员工日常审批优先复用企业微信成熟能力,OA 做按域镜像、人工接管、恢复回写、财务闭环和审计中台;报销、采购、合同、项目、PDF OCR、付款批次、AP 台账、代账交接和高敏附件继续由 OA 主办;企业微信只做脱敏消息和 OA deeplink;飞书不进入当前路线。后续企微/OA 运行时迁移前必须先读 [企微优先开发路线图](./企微优先开发路线图.md),再做对应 POC。WPROC1 后,WAPC2 不能直接进入 `wecom_attendance_approval_mirror_v1`,必须先完成 WAPC2F、WCBK1、WACP3 和 WADM1;缺配置或权限时记录 `NEEDS_CONFIG` 或 `SKIP/BLOCKED_BY_EXTERNAL_CONFIG`,不得伪造成通过。 +**路线口径**:WFOC/DBWFOC/WFDG/WDRG 后,HR/考勤员工日常审批优先复用企业微信成熟能力,OA 做按域镜像、人工接管、恢复回写、财务闭环和审计中台;报销、采购、合同、项目、PDF OCR、付款批次、AP 台账、代账交接和高敏附件继续由 OA 主办;企业微信只做脱敏消息和 OA deeplink;飞书不进入当前路线。后续企微/OA 运行时迁移前必须先读 [企微优先开发路线图](./企微优先开发路线图.md),再做对应 POC。WPROC1 后,WAPC2 不能直接进入 `wecom_attendance_approval_mirror_v1`,必须先完成 WAPC2F、WCBK1、WACP3 和 WADM1;WPROC2 后先跑 `check-process-contract` process gate;缺配置或权限时记录 `NEEDS_CONFIG` 或 `SKIP/BLOCKED_BY_EXTERNAL_CONFIG`,不得伪造成通过。 本文回答“什么时候测试、测什么、怎么判定通过、怎么记录跳过”。具体业务验收仍以主 PRD、模块规约和最新 Issue CSV 为准。 @@ -51,6 +51,12 @@ WPROC1 后,企微 HR/考勤相关 Issue 在测试层面同步执行三层门 | 能力门禁 | runner 必须输出真实 `PASS`、`NEEDS_CONFIG` 或 `SKIP/BLOCKED_BY_EXTERNAL_CONFIG`,不能把缺 Secret、权限、可信 IP、模板、回调或可见范围写成通过。 | | 实现门禁 | Issue CSV、静态检查、单测、回归、公网验证、状态回填和中文提交必须按 `Test_Method` 闭环。 | +WPROC2 把上述要求固化为自动化 process gate: + +- 基线门禁:`node scripts/qa/check-process-contract.mjs --baseline`,已接入 `scripts/check-minimal.sh`。 +- 合同门禁:`node scripts/qa/check-process-contract.mjs --issue --profile --json`。 +- WAPC2F 必须使用 `wecom-approval-fixture` profile;后续企微高风险合同至少使用 `wecom` profile。 + Definition of Ready: - Issue CSV 已写清主流程、异常流程、权限、外部配置和脱敏报告边界。 diff --git a/issues/2026-05-19_10-24-43-wecom-approval-fixture-auto-generate-v1.csv b/issues/2026-05-19_10-24-43-wecom-approval-fixture-auto-generate-v1.csv index 520bce66..1264e227 100644 --- a/issues/2026-05-19_10-24-43-wecom-approval-fixture-auto-generate-v1.csv +++ b/issues/2026-05-19_10-24-43-wecom-approval-fixture-auto-generate-v1.csv @@ -1,10 +1,10 @@ ID,Title,Description,Acceptance,Test_Method,Tools,Dev_Status,Review1_Status,Regression_Status,Files,Dependencies,Notes -"WAPC2F-0","合同建档与边界冻结","建立 WAPC2F 企微审批 POC 样件自动生成合同;承接 WAPC2 当前缺模板 ID 和低风险 sp_no 的配置缺口,本轮目标是尽量自动创建专用测试模板和测试审批单。","1) CSV 校验通过;2) 范围限定 OA-POC 前缀请假/补卡测试模板和低风险测试审批单;3) 固定 WECOM_AGENT_SECRET;4) 默认 dry-run;5) 默认测试链为申请人 SMOKE002 -> 审批人 SMOKE101,禁止自审;6) 不修改正式模板、不接真实回调、不写 OA 业务数据、不进入 wecom_attendance_approval_mirror_v1。","受影响流程:WAPC2 缺配置 -> 自动生成 POC 样件 -> 复跑模板/审批详情只读 POC;验证步骤:python3 /home/shunli/.codex/skills/plan/scripts/validate_issues_csv.py issues/2026-05-19_10-24-43-wecom-approval-fixture-auto-generate-v1.csv。","python3|manual","TODO","TODO","TODO","issues/2026-05-19_10-24-43-wecom-approval-fixture-auto-generate-v1.csv","[]","本合同允许规划自动写企微测试样件,但真实 live 写入仍需后续实施前确认;回滚路径:撤回本轮代码/文档,企微后台中 OA-POC 测试模板和测试单如已生成需人工隐藏/停用或保留审计。" +"WAPC2F-0","合同建档与边界冻结","建立 WAPC2F 企微审批 POC 样件自动生成合同;承接 WAPC2 当前缺模板 ID 和低风险 sp_no 的配置缺口,本轮目标是尽量自动创建专用测试模板和测试审批单。","1) CSV 校验通过;2) 范围限定 OA-POC 前缀请假/补卡测试模板和低风险测试审批单;3) 固定 WECOM_AGENT_SECRET;4) 默认 dry-run;5) 默认测试链为申请人 SMOKE002 -> 审批人 SMOKE101,禁止自审;6) 不修改正式模板、不接真实回调、不写 OA 业务数据、不进入 wecom_attendance_approval_mirror_v1。","受影响流程:WAPC2 缺配置 -> 自动生成 POC 样件 -> 复跑模板/审批详情只读 POC;验证步骤:python3 /home/shunli/.codex/skills/plan/scripts/validate_issues_csv.py issues/2026-05-19_10-24-43-wecom-approval-fixture-auto-generate-v1.csv;node scripts/qa/check-process-contract.mjs --issue issues/2026-05-19_10-24-43-wecom-approval-fixture-auto-generate-v1.csv --profile wecom-approval-fixture --json。","python3|node|manual","TODO","TODO","TODO","issues/2026-05-19_10-24-43-wecom-approval-fixture-auto-generate-v1.csv","[]","WPROC2 baseline gate 通过后才能实施;本合同允许规划自动写企微测试样件,但真实 live 写入仍需后续实施前确认;回滚路径:撤回本轮代码/文档,企微后台中 OA-POC 测试模板和测试单如已生成需人工隐藏/停用或保留审计。" "WAPC2F-1","路线自动化优先修正","更新路线图和事实源,把 WAPC2 后续阶段调整为自动化优先:WAPC2F 自动生成样件 -> WAPC2 复跑 -> WCBK1 回调 POC -> WACP3 补偿拉取 POC -> WADM1 镜像数据模型 -> 镜像实现。","1) 文档明确后续能自动化的动作优先用 QA-only runner 执行;2) 人工只负责批准写入、配置不可自动化的后台项和确认风险;3) 不再默认要求手工创建模板或测试单;4) 不把自动生成 POC 样件误读为正式模板配置完成。","受影响流程:企微审批路线治理;验证步骤:node scripts/qa/audit-docs.mjs --check-registry;rg -n 'WAPC2F|自动生成|OA-POC|WCBK1|WACP3|WADM1|wecom_attendance_approval_mirror_v1' docs .agents/skills/hyb-oa-delivery/SKILL.md。","node|rg","TODO","TODO","TODO","docs/企微优先开发路线图.md|docs/企微API能力目录.md|docs/需求-统一审批中心规约.md|docs/需求-企业微信基础设施规约.md|docs/管理员手册.md|docs/文档索引.md|docs/文档事实源状态矩阵.md|.agents/skills/hyb-oa-delivery/SKILL.md","[""WAPC2F-0""]","文档只记录自动化策略和边界,不保存 Secret、Token、完整 template_id、完整 sp_no 或原始 payload。" -"WAPC2F-2","企微审批写接口调研与安全门禁","核对企业微信创建审批模板和提交审批申请官方 API 的实际字段、权限、可见范围、可信 IP、审批人规则和限制;固化 live 写入双重或三重确认。","1) create_template/applyevent 的方法、路径、Secret 和字段结构明确;2) 若官方接口不支持所需模板或测试单自动生成,改为 SKIP/BLOCKED_BY_EXTERNAL_CONFIG;3) live 写入必须同时指定 CLI 写入参数和 WECOM_APPROVAL_FIXTURE_WRITE=1;4) 不尝试其他 Secret。","受影响流程:自动样件生成前置安全审查;验证步骤:rg -n '创建审批模板|提交审批申请|applyevent|create_template|WECOM_APPROVAL_FIXTURE_WRITE' docs server/src/scripts server/tests。","rg|manual","TODO","TODO","TODO","docs/企微API能力目录.md|server/src/scripts/check-wecom-approval-fixtures.js|server/src/services/integrations/wecomApproval.js","[""WAPC2F-1""]","若官方文档或远端 API 返回能力不支持,后续只记录不可自动化项,不做绕路试错。" -"WAPC2F-3","QA-only 样件生成 runner","新增 QA-only runner check-wecom-approval-fixtures,用于 dry-run/live 创建 OA-POC 请假/补卡测试模板、提交低风险测试审批单,并输出脱敏 summary.json。","1) package.json 增加 check:wecom-approval-fixtures;2) 默认 dry-run 只构建脱敏 payload;3) live 写入必须显式 --live --create-templates --submit-test-approvals 且 WECOM_APPROVAL_FIXTURE_WRITE=1;4) 支持指定申请人/审批人员工号,默认申请人 SMOKE002、审批人 SMOKE101;5) 预检两者 active、已绑定企微、可见、不是同一 OA 员工且不是同一企微 userid;6) 命中自审时硬阻断;7) 报告不输出完整模板 ID、完整 sp_no、userid、手机号、邮箱、token、secret、表单值、附件、评论或原始 payload。","受影响流程:自动创建测试模板和测试审批单;验证步骤:cd server && node --check src/scripts/check-wecom-approval-fixtures.js;cd server && npx vitest run tests/unit/scripts/check-wecom-approval-fixtures.test.js --reporter=verbose。","node|vitest","TODO","TODO","TODO","server/src/scripts/check-wecom-approval-fixtures.js|server/package.json","[""WAPC2F-2""]","runner 只用于 QA 样件生成;不新增公开业务 API,不改 Prisma schema,不改前端;如果 SMOKE101 不可用,不自动切换为 HYD000007 或其他审批人,返回配置缺口并问答确认。" -"WAPC2F-4","审批模板创建与测试审批提交集成","扩展企微审批集成服务,固定 WECOM_AGENT_SECRET 调用创建审批模板和提交审批申请接口,并复用 WAPC2 脱敏、错误分类和无 fallback 规则。","1) 创建模板只创建 OA-POC 前缀测试模板;2) 提交审批只提交低风险无附件测试单;3) 申请人/审批人命中同一 OA 员工或同一企微 userid 时返回自审阻断;4) API 错误、权限、可见范围、可信 IP 或模板能力限制均返回 BLOCKED_BY_EXTERNAL_CONFIG;5) 不修改或覆盖正式模板。","受影响流程:企微审批测试样件写入;验证步骤:cd server && node --check src/services/integrations/wecomApproval.js;focused vitest 覆盖固定 Secret、无 fallback、dry-run/live 分支、自审阻断和 API 错误分类。","node|vitest","TODO","TODO","TODO","server/src/services/integrations/wecomApproval.js","[""WAPC2F-3""]","真实写企微测试模板可能在企微后台留下记录,报告和最终说明必须提醒管理员后续隐藏/停用/保留审计。" +"WAPC2F-2","企微审批写接口调研与安全门禁","核对企业微信创建审批模板和提交审批申请官方 API 的实际字段、权限、可见范围、可信 IP、审批人规则和限制;固化 live 写入双重或三重确认。","1) create_template/applyevent 的方法、路径、Secret 和字段结构明确;2) 若官方接口不支持所需模板或测试单自动生成,改为 SKIP/BLOCKED_BY_EXTERNAL_CONFIG;3) live 写入必须同时指定 CLI 写入参数和 WECOM_APPROVAL_FIXTURE_WRITE=1;4) 不尝试其他 Secret。","受影响流程:自动样件生成前置安全审查;验证步骤:rg -n '创建审批模板|提交审批申请|applyevent|create_template|WECOM_APPROVAL_FIXTURE_WRITE' docs server/src/scripts server/tests。","rg|manual","TODO","TODO","TODO","docs/企微API能力目录.md|server/src/scripts/check-wecom-approval-fixtures.js|server/src/services/integrations/wecomApproval.js","[""WAPC2F-1""]","若官方文档或远端 API 返回能力不支持,后续只记录不可自动化项,不做绕路试错;live 写入前必须明确清理/隐藏/停用或保留审计方式。" +"WAPC2F-3","QA-only 样件生成 runner","新增 QA-only runner check-wecom-approval-fixtures,用于 dry-run/live 创建 OA-POC 请假/补卡测试模板、提交低风险测试审批单,并输出脱敏 summary.json。","1) package.json 增加 check:wecom-approval-fixtures;2) 默认 dry-run 只构建脱敏 payload;3) live 写入必须显式 --live --create-templates --submit-test-approvals 且 WECOM_APPROVAL_FIXTURE_WRITE=1;4) 支持指定申请人/审批人员工号,默认申请人 SMOKE002、审批人 SMOKE101;5) 预检两者 active、已绑定企微、可见、不是同一 OA 员工且不是同一企微 userid;6) 命中自审时硬阻断;7) 报告不输出完整模板 ID、完整 sp_no、完整 userid、手机号、邮箱、token、secret、表单值、附件、评论或原始 payload。","受影响流程:自动创建测试模板和测试审批单;验证步骤:node scripts/qa/check-process-contract.mjs --baseline --json;cd server && node --check src/scripts/check-wecom-approval-fixtures.js;cd server && npx vitest run tests/unit/scripts/check-wecom-approval-fixtures.test.js --reporter=verbose。","node|vitest","TODO","TODO","TODO","server/src/scripts/check-wecom-approval-fixtures.js|server/package.json","[""WAPC2F-2""]","WPROC2 baseline gate 通过后才能实施;runner 只用于 QA 样件生成;不新增公开业务 API,不改 Prisma schema,不改前端;如果 SMOKE101 不可用,不自动切换为 HYD000007 或其他审批人,返回配置缺口并问答确认;live 写入留下的 OA-POC 测试模板/测试单需按清理、隐藏、停用或保留审计方式处理。" +"WAPC2F-4","审批模板创建与测试审批提交集成","扩展企微审批集成服务,固定 WECOM_AGENT_SECRET 调用创建审批模板和提交审批申请接口,并复用 WAPC2 脱敏、错误分类和无 fallback 规则。","1) 创建模板只创建 OA-POC 前缀测试模板;2) 提交审批只提交低风险无附件测试单;3) 申请人/审批人命中同一 OA 员工或同一企微 userid 时返回自审阻断;4) API 错误、权限、可见范围、可信 IP 或模板能力限制均返回 BLOCKED_BY_EXTERNAL_CONFIG;5) 不修改或覆盖正式模板。","受影响流程:企微审批测试样件写入;验证步骤:node scripts/qa/check-process-contract.mjs --issue issues/2026-05-19_10-24-43-wecom-approval-fixture-auto-generate-v1.csv --profile wecom-approval-fixture --json;cd server && node --check src/services/integrations/wecomApproval.js;focused vitest 覆盖固定 Secret、无 fallback、dry-run/live 分支、自审阻断和 API 错误分类。","node|vitest","TODO","TODO","TODO","server/src/services/integrations/wecomApproval.js","[""WAPC2F-3""]","真实写企微测试模板可能在企微后台留下记录;回滚/清理口径为后续隐藏、停用、人工清理或保留审计,报告和最终说明必须提醒管理员。" "WAPC2F-5","自动复跑 WAPC2 只读 POC","样件生成成功后,runner 自动把生成的模板 ID 和 sp_no 传给现有 check:wecom-approval-poc,验证 gettemplatedetail/getapprovaldetail 只读链路。","1) 样件生成成功后自动复用 WAPC2 runner 或等价内部函数复跑;2) 目标结论 PASS_APPROVAL_POC;3) 若只生成部分样件则返回 PARTIAL_APPROVAL_POC_NEEDS_CONFIG;4) 若企微阻断则返回 SKIP/BLOCKED_BY_EXTERNAL_CONFIG。","受影响流程:自动生成样件 -> 自动只读验证 -> 输出一份可审计结论;验证步骤:cd server && npx vitest run tests/unit/scripts/check-wecom-approval-fixtures.test.js tests/unit/scripts/check-wecom-approval-poc.test.js --reporter=verbose。","vitest|node","TODO","TODO","TODO","server/src/scripts/check-wecom-approval-fixtures.js|server/src/scripts/check-wecom-approval-poc.js","[""WAPC2F-4""]","自动复跑不得把生成的完整 ID 或 sp_no 写入报告;只允许脱敏前缀和计数。" "WAPC2F-6","Focused 单测与隐私回归","新增 focused vitest 覆盖样件生成 runner、写入保护、无 Secret fallback、脱敏报告、自动复跑和阻断分类。","1) 覆盖 dry-run 默认不写;2) 覆盖缺 WECOM_APPROVAL_FIXTURE_WRITE 时 live 不写;3) 覆盖 WECOM_AGENT_SECRET 固定选择;4) 覆盖创建模板、提交测试单、自动复跑;5) 覆盖申请人/审批人相同时自审硬阻断;6) 覆盖 SMOKE101 不可用时不自动换人;7) 覆盖隐私字段不泄露。","受影响流程:WAPC2F 本地质量门禁;验证步骤:cd server && npx vitest run tests/unit/scripts/check-wecom-approval-fixtures.test.js tests/unit/scripts/check-wecom-approval-poc.test.js --reporter=verbose。","vitest","TODO","TODO","TODO","server/tests/unit/scripts/check-wecom-approval-fixtures.test.js|server/tests/unit/scripts/check-wecom-approval-poc.test.js","[""WAPC2F-5""]","单测使用 mock fetch,不访问公网,不写企微。" -"WAPC2F-7","本地门禁与公网受控验证","本地门禁通过后,以 SYNC_ENV=0 受控部署;远端先 dry-run,再在单次进程内设置 WECOM_APPROVAL_FIXTURE_WRITE=1 执行 live 样件生成和 WAPC2 自动复跑。","1) 本地 CSV、node --check、prisma validate、focused vitest、文档审计、skill 校验和 git diff --check 通过;2) SYNC_ENV=0 部署,不同步本地 env;3) live 写入只创建 OA-POC 测试样件,申请链固定 SMOKE002 -> SMOKE101;4) 公网 /ready 和 runtime smoke 通过;5) 报告结论真实记录 PASS/PARTIAL/SKIP。","受影响流程:公网自动化 POC 样件生成和只读验证;验证步骤:python3 /home/shunli/.codex/skills/plan/scripts/validate_issues_csv.py issues/2026-05-19_10-24-43-wecom-approval-fixture-auto-generate-v1.csv;cd server && npm run prisma:validate;cd server && npx vitest run tests/unit/scripts/check-wecom-approval-fixtures.test.js tests/unit/scripts/check-wecom-approval-poc.test.js --reporter=verbose;node scripts/qa/audit-docs.mjs --check-registry;SYNC_ENV=0 bash deploy/trialrun/deploy.sh;远端 npm run check:wecom-approval-fixtures。","python3|node|npm|vitest|bash|ssh|curl","TODO","TODO","TODO","N/A","[""WAPC2F-6""]","公网 live 写入前最终说明必须再次提示会在企微后台留下 OA-POC 测试模板/审批记录;不得修改正式模板;若 SMOKE101 不可用,停止并问答确认,不自动换审批人。" -"WAPC2F-8","状态回填、中文提交与下一阶段问答","根据验证结果回填状态并中文提交;最终用问答方式让用户决定是否进入 WCBK1 回调只读 POC、WACP3 补偿拉取 POC 或暂停。","1) Issue 状态按 Test_Method 通过情况回填;2) 中文提交引用 WAPC2F;3) 最终说明包含当前状态、改了什么、测试了什么、结果是什么、剩余风险和下一步问答;4) 不直接进入回调或镜像实现。","受影响流程:WAPC2F 交付闭环和后续路线决策;验证步骤:git status --short;git log --oneline -1;最终问答列出 WCBK1/WACP3/WADM1 选择。","git|manual","TODO","TODO","TODO","issues/2026-05-19_10-24-43-wecom-approval-fixture-auto-generate-v1.csv|N/A","[""WAPC2F-7""]","后续动作默认自动化优先;人工只处理写入批准、企微后台无法 API 化的配置和真机确认。" +"WAPC2F-7","本地门禁与公网受控验证","本地门禁通过后,以 SYNC_ENV=0 受控部署;远端先 dry-run,再在单次进程内设置 WECOM_APPROVAL_FIXTURE_WRITE=1 执行 live 样件生成和 WAPC2 自动复跑。","1) WPROC2 baseline gate、CSV、node --check、prisma validate、focused vitest、文档审计、skill 校验和 git diff --check 通过;2) SYNC_ENV=0 部署,不同步本地 env;3) live 写入只创建 OA-POC 测试样件,申请链固定 SMOKE002 -> SMOKE101;4) 公网 /ready 和 runtime smoke 通过;5) 报告结论真实记录 PASS/PARTIAL/SKIP。","受影响流程:公网自动化 POC 样件生成和只读验证;验证步骤:node scripts/qa/check-process-contract.mjs --baseline --json;python3 /home/shunli/.codex/skills/plan/scripts/validate_issues_csv.py issues/2026-05-19_10-24-43-wecom-approval-fixture-auto-generate-v1.csv;cd server && npm run prisma:validate;cd server && npx vitest run tests/unit/scripts/check-wecom-approval-fixtures.test.js tests/unit/scripts/check-wecom-approval-poc.test.js --reporter=verbose;node scripts/qa/audit-docs.mjs --check-registry;SYNC_ENV=0 bash deploy/trialrun/deploy.sh;远端 npm run check:wecom-approval-fixtures。","python3|node|npm|vitest|bash|ssh|curl","TODO","TODO","TODO","N/A","[""WAPC2F-6""]","WPROC2 baseline gate 是实施前置;公网 live 写入前最终说明必须再次提示会在企微后台留下 OA-POC 测试模板/审批记录;不得修改正式模板;若 SMOKE101 不可用,停止并问答确认,不自动换审批人;清理方式为隐藏、停用、人工清理或保留审计。" +"WAPC2F-8","状态回填、中文提交与下一阶段问答","根据验证结果回填状态并中文提交;最终用问答方式让用户决定是否进入 WCBK1 回调只读 POC、WACP3 补偿拉取 POC 或暂停。","1) Issue 状态按 Test_Method 通过情况回填;2) 中文提交引用 WAPC2F;3) 最终说明包含当前状态、改了什么、测试了什么、结果是什么、剩余风险和下一步问答;4) 不直接进入回调或镜像实现。","受影响流程:WAPC2F 交付闭环和后续路线决策;验证步骤:git status --short;git log --oneline -1;最终问答列出 WCBK1/WACP3/WADM1 选择。","git|manual","TODO","TODO","TODO","issues/2026-05-19_10-24-43-wecom-approval-fixture-auto-generate-v1.csv|N/A","[""WAPC2F-7""]","后续动作默认自动化优先;人工只处理写入批准、企微后台无法 API 化的配置和真机确认;本行不写企微数据,若前序 live 生成样件则按隐藏、停用、人工清理或保留审计处理。" diff --git a/issues/2026-05-19_11-15-10-process-gate-automation-v1.csv b/issues/2026-05-19_11-15-10-process-gate-automation-v1.csv new file mode 100644 index 00000000..8da42f24 --- /dev/null +++ b/issues/2026-05-19_11-15-10-process-gate-automation-v1.csv @@ -0,0 +1,9 @@ +ID,Title,Description,Acceptance,Test_Method,Tools,Dev_Status,Review1_Status,Regression_Status,Files,Dependencies,Notes +"WPROC2-0","合同建档与边界冻结","建立 WPROC2 开发流程自动化门禁合同;本轮只把 WPROC1 文档规则升级为可执行 QA-only process gate,不部署、不写 OA/企微数据、不实现 WAPC2F 业务 runner。","1) CSV 校验通过;2) 范围限定 Issue CSV、QA 脚本、单测、最小门禁接入和事实源回写;3) 不新增公开业务 API、Prisma schema、前端页面或 feature flag;4) 不调用企微写接口。","受影响流程:开发流程治理;验证步骤:python3 /home/shunli/.codex/skills/plan/scripts/validate_issues_csv.py issues/2026-05-19_11-15-10-process-gate-automation-v1.csv。","python3|manual","DONE","DONE","DONE","issues/2026-05-19_11-15-10-process-gate-automation-v1.csv","[]","回滚路径:撤回 WPROC2 Issue、check-process-contract 脚本、测试、check-minimal 接入和事实源回写;本轮无数据库、部署、远端配置或企微写入;已通过 CSV 校验。" +"WPROC2-1","新增流程门禁 CLI","新增 QA-only CLI scripts/qa/check-process-contract.mjs,支持 --baseline 和 --issue --profile --json,首批 profile 为 general、wecom、wecom-approval-fixture、docs-only。","1) CLI 只读,不修改仓库或远端;2) --baseline 检查核心事实源、WPROC1/WPROC2 口径和 WAPC2F 合同;3) 单 Issue 检查输出 PASS/FAIL、errors、warnings 和 checks;4) 支持 CommonJS/Vitest 通过动态 import 复用导出函数。","受影响流程:本地/CI 流程门禁;验证步骤:node --check scripts/qa/check-process-contract.mjs;node scripts/qa/check-process-contract.mjs --baseline --json。","node","DONE","DONE","DONE","scripts/qa/check-process-contract.mjs","[""WPROC2-0""]","脚本不得打印 Secret、token、完整 userid、完整 template_id、完整 sp_no 或原始 payload;仅输出规则命中摘要;node --check 与 baseline JSON 均通过。" +"WPROC2-2","固化合同规则检查","在 process gate 中固化 CSV 必填列、状态值、Dependencies JSON、Test_Method 业务流程步骤、回滚/清理说明、事实源读取和缺配置分类检查。","1) 合法 WAPC2F 合同通过 wecom-approval-fixture profile;2) 缺 Dependencies JSON、缺业务流程 Test_Method、缺回滚/清理说明或缺配置分类时失败;3) docs-only profile 不误判为企微写入合同。","受影响流程:Issue CSV 质量门禁;验证步骤:node scripts/qa/check-process-contract.mjs --issue issues/2026-05-19_10-24-43-wecom-approval-fixture-auto-generate-v1.csv --profile wecom-approval-fixture --json;focused vitest。","node|vitest","DONE","DONE","DONE","scripts/qa/check-process-contract.mjs|server/tests/unit/scripts/check-process-contract.test.js","[""WPROC2-1""]","规则按当前 WPROC1/WAPC2F 口径实现;后续 profile 扩展另建 Issue;WAPC2F profile 与 focused vitest 均通过。" +"WPROC2-3","固化企微审批高风险检查","为 wecom-approval-fixture profile 增加 Secret 固定、无 fallback、dry-run 默认、live 双开关、非自审链路、WAPC2F 阶段顺序和脱敏输出检查。","1) 必须命中 WECOM_AGENT_SECRET、WECOM_APPROVAL_FIXTURE_WRITE=1、dry-run、SMOKE002、SMOKE101、禁止自审;2) 不允许默认 wangshunli 自审链;3) 必须命中 NEEDS_CONFIG 或 SKIP/BLOCKED_BY_EXTERNAL_CONFIG;4) 必须声明不输出手机号、邮箱、token、secret、完整 userid、完整 template_id、完整 sp_no 或原始 payload。","受影响流程:企微审批 POC 样件生成前置门禁;验证步骤:cd server && npx vitest run tests/unit/scripts/check-process-contract.test.js --reporter=verbose。","vitest|node","DONE","DONE","DONE","scripts/qa/check-process-contract.mjs|server/tests/unit/scripts/check-process-contract.test.js","[""WPROC2-2""]","该门禁只检查合同和事实源,不创建模板、不提交审批单、不访问公网企微、不写企微数据,因此无需清理;focused vitest 8 项通过。" +"WPROC2-4","接入最小门禁","在 scripts/check-minimal.sh 增加 process gate baseline 检查,使 ci-minimal 通过既有入口自动执行流程门禁;不单独修改 GitHub workflow。","1) scripts/check-minimal.sh 包含 node scripts/qa/check-process-contract.mjs --baseline;2) bash -n scripts/check-minimal.sh 通过;3) bash scripts/check-minimal.sh 通过或真实记录环境阻断。","受影响流程:本地最小门禁和 ci-minimal;验证步骤:bash -n scripts/check-minimal.sh;bash scripts/check-minimal.sh。","bash|node","DONE","DONE","DONE","scripts/check-minimal.sh","[""WPROC2-3""]","bash -n scripts/check-minimal.sh 与 bash scripts/check-minimal.sh 已通过;完整最小门禁包含 process gate、Prisma、后端静态、前端 typecheck/build 和 feature matrix。" +"WPROC2-5","更新 WAPC2F 合同前置说明","修正 WAPC2F 合同说明,不通过跨 CSV Dependencies 强绑;用 Notes 和 Test_Method 声明 WPROC2 baseline gate 是实施前置,并补充 process gate 命令。","1) WAPC2F 不新增跨 CSV Dependencies;2) WAPC2F Test_Method 包含 check-process-contract;3) Notes 说明 WPROC2 是实施前置;4) WAPC2F CSV 校验仍通过。","受影响流程:WAPC2F 进入实现前检查;验证步骤:python3 /home/shunli/.codex/skills/plan/scripts/validate_issues_csv.py issues/2026-05-19_10-24-43-wecom-approval-fixture-auto-generate-v1.csv;node scripts/qa/check-process-contract.mjs --issue issues/2026-05-19_10-24-43-wecom-approval-fixture-auto-generate-v1.csv --profile wecom-approval-fixture --json。","python3|node","DONE","DONE","DONE","issues/2026-05-19_10-24-43-wecom-approval-fixture-auto-generate-v1.csv","[""WPROC2-4""]","不回填 WAPC2F DONE;WAPC2F 仍是后续实现合同;WAPC2F CSV 校验和 profile gate 均通过。" +"WPROC2-6","回写事实源和 skill","更新开发交付规约、测试门禁、企微路线图、API 能力目录、文档索引、事实源矩阵和项目 skill,登记 WPROC2 和 check-process-contract。","1) 事实源说明 WPROC2 是 WAPC2F 前置 process gate;2) skill 要求企微高风险开发先跑 process gate;3) 文档审计和 skill 校验通过。","受影响流程:后续 agent 读取顺序和流程执行;验证步骤:node scripts/qa/audit-docs.mjs --check-registry;python3 /home/shunli/.codex/skills/.system/skill-creator/scripts/quick_validate.py .agents/skills/hyb-oa-delivery;rg -n 'WPROC2|check-process-contract|process gate' docs .agents/skills/hyb-oa-delivery/SKILL.md。","node|python3|rg","DONE","DONE","DONE","docs/开发交付与变更管理规约.md|docs/测试规约与回归门禁.md|docs/企微优先开发路线图.md|docs/企微API能力目录.md|docs/文档索引.md|docs/文档事实源状态矩阵.md|.agents/skills/hyb-oa-delivery/SKILL.md","[""WPROC2-5""]","不新建独立流程文档;优先更新现有事实源;文档审计通过但保留既有 24 个未登记文档 warning,skill 校验通过。" +"WPROC2-7","门禁验证、状态回填和提交","执行 WPROC2 Test Plan,按真实结果回填 WPROC2 状态并中文提交。","1) WPROC2/WAPC2F CSV 校验通过;2) node --check、process gate 自测、focused vitest、文档审计、skill 校验、关键词核验、git diff --check 和 check-minimal 通过或真实记录 SKIP/阻断;3) Issue 状态回填真实;4) 中文 commit 引用 WPROC2。","受影响流程:WPROC2 交付闭环;验证步骤:python3 /home/shunli/.codex/skills/plan/scripts/validate_issues_csv.py issues/2026-05-19_11-15-10-process-gate-automation-v1.csv;node --check scripts/qa/check-process-contract.mjs;node scripts/qa/check-process-contract.mjs --baseline --json;cd server && npx vitest run tests/unit/scripts/check-process-contract.test.js --reporter=verbose;bash scripts/check-minimal.sh;git diff --check。","python3|node|vitest|bash|git","DONE","DONE","DONE","N/A","[""WPROC2-6""]","本轮不部署;无公网 URL 变更;无 OA/企微业务数据写入;提交前门禁按最终 diff 复跑。" diff --git a/scripts/check-minimal.sh b/scripts/check-minimal.sh index f0128471..f83dc398 100755 --- a/scripts/check-minimal.sh +++ b/scripts/check-minimal.sh @@ -3,14 +3,21 @@ set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -echo "=== [1/4] Server Prisma Validate ===" +echo "=== [1/5] Process Contract Gate ===" +( + cd "$ROOT_DIR" + node scripts/qa/check-process-contract.mjs --baseline +) + +echo "" +echo "=== [2/5] Server Prisma Validate ===" ( cd "$ROOT_DIR/server" npm run prisma:validate ) echo "" -echo "=== [2/4] Server Static Checks ===" +echo "=== [3/5] Server Static Checks ===" ( cd "$ROOT_DIR/server" node --check src/routes/reimbursements.js @@ -62,7 +69,7 @@ echo "=== [2/4] Server Static Checks ===" ) echo "" -echo "=== [3/4] Client Typecheck + Build ===" +echo "=== [4/5] Client Typecheck + Build ===" ( cd "$ROOT_DIR/client" npm run typecheck @@ -70,7 +77,7 @@ echo "=== [3/4] Client Typecheck + Build ===" ) echo "" -echo "=== [4/4] Feature Matrix Consistency ===" +echo "=== [5/5] Feature Matrix Consistency ===" ( cd "$ROOT_DIR" python3 scripts/validate-feature-matrix.py diff --git a/scripts/qa/check-process-contract.mjs b/scripts/qa/check-process-contract.mjs new file mode 100644 index 00000000..44dff478 --- /dev/null +++ b/scripts/qa/check-process-contract.mjs @@ -0,0 +1,480 @@ +#!/usr/bin/env node + +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath, pathToFileURL } from "node:url"; + +const scriptDir = path.dirname(fileURLToPath(import.meta.url)); +const repoRoot = path.resolve(scriptDir, "../.."); + +const REQUIRED_COLUMNS = [ + "ID", + "Title", + "Description", + "Acceptance", + "Test_Method", + "Tools", + "Dev_Status", + "Review1_Status", + "Regression_Status", + "Files", + "Dependencies", + "Notes", +]; +const VALID_STATUSES = new Set(["TODO", "DOING", "DONE"]); +const VALID_PROFILES = new Set(["general", "wecom", "wecom-approval-fixture", "docs-only"]); +const WAPC2F_ISSUE = "issues/2026-05-19_10-24-43-wecom-approval-fixture-auto-generate-v1.csv"; +const WPROC2_ISSUE = "issues/2026-05-19_11-15-10-process-gate-automation-v1.csv"; + +function usage() { + return `Usage: + node scripts/qa/check-process-contract.mjs --baseline [--json] + node scripts/qa/check-process-contract.mjs --issue --profile [--json] + +This QA-only process gate is read-only. It validates Issue CSV contracts and +current WeCom process facts without printing secrets or raw external payloads.`; +} + +export function parseArgs(argv = process.argv.slice(2)) { + const options = { + baseline: false, + issue: "", + profile: "general", + json: false, + help: false, + }; + + for (let index = 0; index < argv.length; index += 1) { + const arg = argv[index]; + if (arg === "--baseline") { + options.baseline = true; + } else if (arg === "--issue") { + options.issue = argv[index + 1] ?? ""; + index += 1; + } else if (arg.startsWith("--issue=")) { + options.issue = arg.slice("--issue=".length); + } else if (arg === "--profile") { + options.profile = argv[index + 1] ?? ""; + index += 1; + } else if (arg.startsWith("--profile=")) { + options.profile = arg.slice("--profile=".length); + } else if (arg === "--json") { + options.json = true; + } else if (arg === "--help" || arg === "-h") { + options.help = true; + } else { + throw new Error(`unknown argument: ${arg}`); + } + } + + if (!options.help && !options.baseline && !options.issue) { + throw new Error("either --baseline or --issue is required"); + } + if (options.baseline && options.issue) { + throw new Error("--baseline and --issue cannot be used together"); + } + if (!VALID_PROFILES.has(options.profile)) { + throw new Error(`unsupported profile: ${options.profile}`); + } + return options; +} + +function createResult(scope, profile = "general") { + return { + status: "PASS", + scope, + profile, + checks: [], + errors: [], + warnings: [], + }; +} + +function addCheck(result, name, status, detail = "") { + result.checks.push({ name, status, detail }); +} + +function addError(result, code, message) { + result.errors.push({ code, message }); + result.status = "FAIL"; +} + +function addWarning(result, code, message) { + result.warnings.push({ code, message }); +} + +function splitCsvLine(line) { + const cells = []; + let current = ""; + let quoted = false; + + for (let index = 0; index < line.length; index += 1) { + const char = line[index]; + const next = line[index + 1]; + if (quoted && char === "\"" && next === "\"") { + current += "\""; + index += 1; + continue; + } + if (char === "\"") { + quoted = !quoted; + continue; + } + if (!quoted && char === ",") { + cells.push(current); + current = ""; + continue; + } + current += char; + } + + cells.push(current); + return cells; +} + +export function parseIssueCsv(source) { + const lines = source.split(/\r?\n/).filter((line) => line.trim()); + if (lines.length === 0) { + return { header: [], rows: [] }; + } + const header = splitCsvLine(lines[0]).map((cell) => cell.trim()); + const rows = lines.slice(1).map((line, lineIndex) => { + const cells = splitCsvLine(line); + const row = Object.fromEntries(header.map((key, index) => [key, cells[index] ?? ""])); + row.__line = lineIndex + 2; + return row; + }); + return { header, rows }; +} + +function includesAllColumns(header) { + return REQUIRED_COLUMNS.every((column) => header.includes(column)); +} + +function textOfRows(rows) { + return rows.map((row) => REQUIRED_COLUMNS.map((column) => row[column] ?? "").join(" ")).join("\n"); +} + +function normalize(value) { + return String(value ?? "").toLowerCase(); +} + +function hasTerm(text, term) { + return normalize(text).includes(normalize(term)); +} + +function hasAny(text, terms) { + return terms.some((term) => hasTerm(text, term)); +} + +function hasAll(text, terms) { + return terms.every((term) => hasTerm(text, term)); +} + +function validateBasicCsv({ result, header, rows }) { + if (!includesAllColumns(header)) { + const missing = REQUIRED_COLUMNS.filter((column) => !header.includes(column)); + addError(result, "csv.required_columns", `missing required columns: ${missing.join(", ")}`); + } else { + addCheck(result, "csv.required_columns", "PASS", "all required columns exist"); + } + + if (rows.length === 0) { + addError(result, "csv.rows", "Issue CSV has no rows"); + return; + } + addCheck(result, "csv.rows", "PASS", `${rows.length} rows`); + + const ids = new Set(); + for (const row of rows) { + const rowId = row.ID || `line ${row.__line}`; + if (!row.ID) { + addError(result, "csv.id", `missing ID at line ${row.__line}`); + } else if (ids.has(row.ID)) { + addError(result, "csv.id", `duplicate ID: ${row.ID}`); + } else { + ids.add(row.ID); + } + + for (const statusField of ["Dev_Status", "Review1_Status", "Regression_Status"]) { + if (!VALID_STATUSES.has(row[statusField])) { + addError(result, "csv.status", `${rowId} has invalid ${statusField}: ${row[statusField] || ""}`); + } + } + + try { + const deps = JSON.parse(row.Dependencies || ""); + if (!Array.isArray(deps) || deps.some((item) => typeof item !== "string")) { + addError(result, "csv.dependencies", `${rowId} Dependencies must be a JSON string array`); + } else { + for (const dep of deps) { + if (!ids.has(dep) && !rows.some((candidate) => candidate.ID === dep)) { + addError(result, "csv.dependencies", `${rowId} depends on unknown row: ${dep}`); + } + } + } + } catch (error) { + addError(result, "csv.dependencies", `${rowId} Dependencies is not valid JSON array`); + } + + const testMethod = row.Test_Method || ""; + if (!hasAll(testMethod, ["受影响流程", "验证步骤"])) { + addError(result, "csv.test_method", `${rowId} Test_Method must include affected business flow and reproducible steps`); + } + + if (!row.Files || !row.Files.trim()) { + addError(result, "csv.files", `${rowId} Files must not be empty`); + } + if (!row.Notes || !row.Notes.trim()) { + addError(result, "csv.notes", `${rowId} Notes must not be empty`); + } + + const rowText = REQUIRED_COLUMNS.map((column) => row[column] ?? "").join(" "); + const hasRiskyWriteMarker = /(--live|WECOM_APPROVAL_FIXTURE_WRITE|live 写入|真实写|写企微|企微后台|applyevent|create_template|submit-test-approvals|部署|生产)/i.test(rowText); + if (hasRiskyWriteMarker && !hasAny(rowText, ["回滚", "清理", "隐藏", "停用", "保留审计", "不部署", "不写", "不得修改正式模板"])) { + addError(result, "csv.rollback", `${rowId} mentions live/write/public risk but lacks rollback or cleanup wording`); + } + } + + if (!result.errors.some((item) => item.code.startsWith("csv."))) { + addCheck(result, "csv.format", "PASS", "IDs, statuses, dependencies, Test_Method, Files and Notes are valid"); + } +} + +function validateDocsOnly({ result, rows }) { + const text = textOfRows(rows); + if (hasAny(text, ["不改业务代码", "docs-only", "contract-only", "文档"])) { + addCheck(result, "profile.docs_only_scope", "PASS", "docs-only or contract-only boundary declared"); + } else { + addWarning(result, "profile.docs_only_scope", "docs-only profile did not find explicit docs-only wording"); + } +} + +function validateWecom({ result, rows }) { + const text = textOfRows(rows); + if (!hasAny(text, ["WECOM_", "Secret", "企微", "企业微信"])) { + addError(result, "wecom.scope", "WeCom profile must mention WeCom scope and Secret/API source"); + } + if (!hasAny(text, ["固定 WECOM", "固定 `WECOM", "Secret 来源", "Secret 矩阵"])) { + addError(result, "wecom.secret_source", "WeCom contract must fix its Secret source"); + } + if (!hasAny(text, ["不尝试其他 Secret", "无 fallback", "不得靠多个 Secret", "不允许运行时猜 Secret", "不再用运行时 Secret fallback"])) { + addError(result, "wecom.secret_source", "WeCom contract must fix Secret source and disallow runtime Secret guessing"); + } + if (!hasAny(text, ["NEEDS_CONFIG", "SKIP/BLOCKED_BY_EXTERNAL_CONFIG", "BLOCKED_BY_EXTERNAL_CONFIG"])) { + addError(result, "wecom.external_config", "WeCom contract must classify missing config or permission as NEEDS_CONFIG or SKIP/BLOCKED_BY_EXTERNAL_CONFIG"); + } + if (!hasAny(text, ["只读", "dry-run", "默认 dry-run", "不写"])) { + addError(result, "wecom.read_or_dry_run", "WeCom contract must default to read-only or dry-run"); + } + if (!result.errors.some((item) => item.code.startsWith("wecom."))) { + addCheck(result, "profile.wecom", "PASS", "Secret, external config, and read/dry-run gates are declared"); + } +} + +function validateWecomApprovalFixture({ result, rows }) { + validateWecom({ result, rows }); + const text = textOfRows(rows); + + const requiredTerms = [ + "WAPC2F", + "OA-POC", + "WECOM_AGENT_SECRET", + "WECOM_APPROVAL_FIXTURE_WRITE=1", + "--live", + "--create-templates", + "--submit-test-approvals", + "dry-run", + "SMOKE002", + "SMOKE101", + "自审", + "BLOCKED_BY_EXTERNAL_CONFIG", + "NEEDS_CONFIG", + ]; + for (const term of requiredTerms) { + if (!hasTerm(text, term)) { + addError(result, "fixture.required_term", `WAPC2F contract must mention ${term}`); + } + } + + if (hasAny(text, ["默认 wangshunli", "申请人 wangshunli", "审批人 wangshunli", "wangshunli -> wangshunli"])) { + addError(result, "fixture.self_approval", "WAPC2F must not default to wangshunli self-approval"); + } + if (!hasAny(text, ["不是同一 OA 员工", "不是同一企微 userid", "禁止自审", "自审硬阻断"])) { + addError(result, "fixture.non_self_approval", "WAPC2F must explicitly block self-approval by OA employee and WeCom userid"); + } + + const routePattern = /WAPC2F[\s\S]{0,240}WCBK1[\s\S]{0,240}WACP3[\s\S]{0,240}WADM1/; + if (!routePattern.test(text)) { + addError(result, "fixture.route_order", "WAPC2F route must show WAPC2F before WCBK1, WACP3 and WADM1"); + } + + const privacyTerms = [ + "手机号", + "邮箱", + "token", + "secret", + "完整模板 ID", + "完整 sp_no", + "完整 userid", + "原始 payload", + ]; + for (const term of privacyTerms) { + if (!hasTerm(text, term)) { + addError(result, "fixture.privacy", `WAPC2F contract must declare no ${term} in reports`); + } + } + + if (!result.errors.some((item) => item.code.startsWith("fixture."))) { + addCheck(result, "profile.wecom_approval_fixture", "PASS", "WAPC2F non-self, write switch, route and privacy gates are declared"); + } +} + +export function evaluateIssueContract({ csvText, issuePath = "", profile = "general" }) { + const result = createResult(issuePath || "inline issue", profile); + const { header, rows } = parseIssueCsv(csvText); + validateBasicCsv({ result, header, rows }); + + if (profile === "docs-only") { + validateDocsOnly({ result, rows }); + } else if (profile === "wecom") { + validateWecom({ result, rows }); + } else if (profile === "wecom-approval-fixture") { + validateWecomApprovalFixture({ result, rows }); + } + + return result; +} + +function readRepoText(relativePath, root = repoRoot) { + return fs.readFileSync(path.join(root, relativePath), "utf8"); +} + +function pathExists(relativePath, root = repoRoot) { + return fs.existsSync(path.join(root, relativePath)); +} + +function mergeChild(parent, child, label) { + parent.checks.push({ + name: label, + status: child.status, + detail: `${child.errors.length} errors, ${child.warnings.length} warnings`, + }); + for (const error of child.errors) { + addError(parent, `${label}.${error.code}`, error.message); + } + for (const warning of child.warnings) { + addWarning(parent, `${label}.${warning.code}`, warning.message); + } +} + +export function evaluateBaseline({ root = repoRoot } = {}) { + const result = createResult("baseline", "baseline"); + const requiredPaths = [ + "docs/开发交付与变更管理规约.md", + "docs/测试规约与回归门禁.md", + "docs/企微优先开发路线图.md", + "docs/企微API能力目录.md", + "docs/文档索引.md", + "docs/文档事实源状态矩阵.md", + ".agents/skills/hyb-oa-delivery/SKILL.md", + WAPC2F_ISSUE, + ]; + + for (const relativePath of requiredPaths) { + if (!pathExists(relativePath, root)) { + addError(result, "baseline.required_path", `missing required path: ${relativePath}`); + } + } + if (!result.errors.some((item) => item.code === "baseline.required_path")) { + addCheck(result, "baseline.required_paths", "PASS", `${requiredPaths.length} required paths exist`); + } + + const factPaths = requiredPaths.filter((item) => item !== WAPC2F_ISSUE); + const factText = factPaths + .filter((relativePath) => pathExists(relativePath, root)) + .map((relativePath) => readRepoText(relativePath, root)) + .join("\n"); + for (const term of ["WPROC2", "check-process-contract", "process gate", "Definition of Ready", "Definition of Done", "WAPC2F"]) { + if (!hasTerm(factText, term)) { + addError(result, "baseline.fact_source", `core fact sources must mention ${term}`); + } + } + if (!result.errors.some((item) => item.code === "baseline.fact_source")) { + addCheck(result, "baseline.fact_sources", "PASS", "WPROC2 process gate terms are in core fact sources"); + } + + if (pathExists(WAPC2F_ISSUE, root)) { + const wapc2f = evaluateIssueContract({ + csvText: readRepoText(WAPC2F_ISSUE, root), + issuePath: WAPC2F_ISSUE, + profile: "wecom-approval-fixture", + }); + mergeChild(result, wapc2f, "wapc2f"); + } + + if (pathExists(WPROC2_ISSUE, root)) { + const wproc2 = evaluateIssueContract({ + csvText: readRepoText(WPROC2_ISSUE, root), + issuePath: WPROC2_ISSUE, + profile: "general", + }); + mergeChild(result, wproc2, "wproc2"); + } + + return result; +} + +function formatTextResult(result) { + const lines = [`process-contract: status=${result.status} scope=${result.scope} profile=${result.profile}`]; + for (const check of result.checks) { + lines.push(`- ${check.status} ${check.name}${check.detail ? `: ${check.detail}` : ""}`); + } + for (const warning of result.warnings) { + lines.push(`warning ${warning.code}: ${warning.message}`); + } + for (const error of result.errors) { + lines.push(`error ${error.code}: ${error.message}`); + } + return lines.join("\n"); +} + +async function main() { + let options; + try { + options = parseArgs(); + } catch (error) { + console.error(error.message); + console.error(usage()); + process.exit(2); + } + + if (options.help) { + console.log(usage()); + return; + } + + const result = options.baseline + ? evaluateBaseline() + : evaluateIssueContract({ + csvText: readRepoText(options.issue), + issuePath: options.issue, + profile: options.profile, + }); + + if (options.json) { + console.log(JSON.stringify(result, null, 2)); + } else { + console.log(formatTextResult(result)); + } + if (result.status !== "PASS") { + process.exit(1); + } +} + +if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { + main().catch((error) => { + console.error(error?.stack || error?.message || String(error)); + process.exit(1); + }); +} diff --git a/server/tests/unit/scripts/check-process-contract.test.js b/server/tests/unit/scripts/check-process-contract.test.js new file mode 100644 index 00000000..e5a5c15e --- /dev/null +++ b/server/tests/unit/scripts/check-process-contract.test.js @@ -0,0 +1,143 @@ +const path = require("node:path"); +const { pathToFileURL } = require("node:url"); + +let gate; + +function csvEscape(value) { + return `"${String(value).replace(/"/g, "\"\"")}"`; +} + +function buildCsv(rows) { + const header = [ + "ID", + "Title", + "Description", + "Acceptance", + "Test_Method", + "Tools", + "Dev_Status", + "Review1_Status", + "Regression_Status", + "Files", + "Dependencies", + "Notes", + ]; + return [ + header.join(","), + ...rows.map((row) => header.map((column) => csvEscape(row[column] ?? "")).join(",")), + ].join("\n"); +} + +function validFixtureRow(overrides = {}) { + return { + ID: "WAPC2F-0", + Title: "合法企微审批样件合同", + Description: "WAPC2F 自动生成 OA-POC 请假/补卡测试模板;路线:WAPC2F -> WCBK1 -> WACP3 -> WADM1 -> wecom_attendance_approval_mirror_v1。", + Acceptance: "固定 WECOM_AGENT_SECRET;不尝试其他 Secret;默认 dry-run;live 必须 --live --create-templates --submit-test-approvals 且 WECOM_APPROVAL_FIXTURE_WRITE=1;申请人 SMOKE002 -> 审批人 SMOKE101;禁止自审,不是同一 OA 员工,不是同一企微 userid;缺配置记录 NEEDS_CONFIG 或 SKIP/BLOCKED_BY_EXTERNAL_CONFIG。", + Test_Method: "受影响流程:WAPC2 缺配置 -> 自动生成 POC 样件 -> WAPC2 自动复跑;验证步骤:node scripts/qa/check-process-contract.mjs --issue fixture.csv --profile wecom-approval-fixture --json。", + Tools: "node|vitest", + Dev_Status: "TODO", + Review1_Status: "TODO", + Regression_Status: "TODO", + Files: "scripts/qa/check-wecom-approval-fixtures.js|server/package.json", + Dependencies: "[]", + Notes: "回滚路径:撤回代码;企微后台 OA-POC 测试模板或测试单如已生成需隐藏、停用或保留审计;报告不得输出手机号、邮箱、token、secret、完整模板 ID、完整 sp_no、完整 userid 或原始 payload;无 fallback。", + ...overrides, + }; +} + +function evaluate(rows, profile = "wecom-approval-fixture") { + return gate.evaluateIssueContract({ + csvText: buildCsv(rows), + issuePath: "fixture.csv", + profile, + }); +} + +function errorCodes(result) { + return result.errors.map((item) => item.code); +} + +describe("check-process-contract", () => { + beforeAll(async () => { + const scriptPath = path.resolve(__dirname, "../../../../scripts/qa/check-process-contract.mjs"); + gate = await import(pathToFileURL(scriptPath).href); + }); + + it("passes a valid WAPC2F fixture contract", () => { + const result = evaluate([validFixtureRow()]); + + expect(result.status).toBe("PASS"); + expect(errorCodes(result)).toEqual([]); + expect(result.checks.some((item) => item.name === "profile.wecom_approval_fixture")).toBe(true); + }); + + it("fails when Dependencies is not a JSON array", () => { + const result = evaluate([validFixtureRow({ Dependencies: "WAPC2F-0" })]); + + expect(result.status).toBe("FAIL"); + expect(errorCodes(result)).toContain("csv.dependencies"); + }); + + it("fails when Test_Method lacks affected business flow wording", () => { + const result = evaluate([validFixtureRow({ Test_Method: "验证步骤:node scripts/qa/check-process-contract.mjs --baseline。" })]); + + expect(result.status).toBe("FAIL"); + expect(errorCodes(result)).toContain("csv.test_method"); + }); + + it("fails self-approval defaults", () => { + const result = evaluate([ + validFixtureRow({ + Acceptance: "默认 wangshunli -> wangshunli;固定 WECOM_AGENT_SECRET;默认 dry-run;live 必须 --live --create-templates --submit-test-approvals 且 WECOM_APPROVAL_FIXTURE_WRITE=1;缺配置记录 NEEDS_CONFIG 或 SKIP/BLOCKED_BY_EXTERNAL_CONFIG。", + }), + ]); + + expect(result.status).toBe("FAIL"); + expect(errorCodes(result)).toContain("fixture.self_approval"); + }); + + it("fails when live write switch is missing", () => { + const row = validFixtureRow({ + Acceptance: "固定 WECOM_AGENT_SECRET;不尝试其他 Secret;默认 dry-run;live 必须 --live --create-templates --submit-test-approvals;申请人 SMOKE002 -> 审批人 SMOKE101;禁止自审;缺配置记录 NEEDS_CONFIG 或 SKIP/BLOCKED_BY_EXTERNAL_CONFIG。", + }); + const result = evaluate([row]); + + expect(result.status).toBe("FAIL"); + expect(errorCodes(result)).toContain("fixture.required_term"); + expect(result.errors.some((item) => item.message.includes("WECOM_APPROVAL_FIXTURE_WRITE=1"))).toBe(true); + }); + + it("fails when Secret fallback is not explicitly forbidden", () => { + const row = validFixtureRow({ + Acceptance: "固定 WECOM_AGENT_SECRET;默认 dry-run;live 必须 --live --create-templates --submit-test-approvals 且 WECOM_APPROVAL_FIXTURE_WRITE=1;申请人 SMOKE002 -> 审批人 SMOKE101;禁止自审;缺配置记录 NEEDS_CONFIG 或 SKIP/BLOCKED_BY_EXTERNAL_CONFIG。", + Notes: "回滚路径:撤回代码;企微后台 OA-POC 测试模板或测试单如已生成需隐藏、停用或保留审计;报告不得输出手机号、邮箱、token、secret、完整模板 ID、完整 sp_no、完整 userid 或原始 payload。", + }); + const result = evaluate([row]); + + expect(result.status).toBe("FAIL"); + expect(errorCodes(result)).toContain("wecom.secret_source"); + }); + + it("fails when privacy report boundary is incomplete", () => { + const row = validFixtureRow({ + Notes: "回滚路径:撤回代码;企微后台 OA-POC 测试模板或测试单如已生成需隐藏、停用或保留审计;报告不得输出手机号、邮箱、token、secret、完整模板 ID、完整 userid 或原始 payload;无 fallback。", + }); + const result = evaluate([row]); + + expect(result.status).toBe("FAIL"); + expect(errorCodes(result)).toContain("fixture.privacy"); + }); + + it("parses CLI options for baseline and issue profiles", () => { + expect(gate.parseArgs(["--baseline", "--json"])).toMatchObject({ + baseline: true, + json: true, + }); + expect(gate.parseArgs(["--issue", "a.csv", "--profile", "wecom"])).toMatchObject({ + issue: "a.csv", + profile: "wecom", + }); + expect(() => gate.parseArgs(["--issue", "a.csv", "--profile", "unknown"])).toThrow(/unsupported profile/); + }); +});