WREAD1 准备企微审批可读性门禁
这个提交包含在:
@@ -17,9 +17,9 @@ 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/WPROC2 fixed the engineering process gate; WEGOV1 is the route gate, and WACP3R has now rerun the compensation-pull POC by automatically discovering OA-POC leave/missed-punch template IDs from `SMOKE002` creator. Public report `/app/artifacts/qa-flow/wecom-approval-compensation-poc-20260519T115944Z/summary.json` is `PASS_WITH_FINDINGS`: compensation pull and detail read work, with finding that callback evidence files are not persisted across deploys. WREAD1 readability/misapproval check, WADM1 self-owned archive/mirror model design, and WARCH1 minimum history archive still remain 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. WAPC2F has implemented and deployed `npm run check:wecom-approval-fixtures`; public dry-run reached `PASS_FIXTURE_DRY_RUN_READY` at `/app/artifacts/qa-flow/wecom-approval-fixtures-20260519T045001Z/summary.json`, but no real WeCom template or approval was created because `WECOM_APPROVAL_FIXTURE_WRITE=1` was not set. 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.
|
||||
- WAPC2FLIVE supersedes the WAPC2F dry-run gap for OA-POC samples: after user approval, the runner was corrected to the official `template_name` + `property` controls create-template payload, deployed with `SYNC_ENV=0`, and the public live report `/app/artifacts/qa-flow/wecom-approval-fixtures-20260519T053400Z/summary.json` reached `PASS_FIXTURE_CREATED`; leave/missed-punch OA-POC templates and low-risk test approvals were created and retained for audit, with WAPC2 auto-rerun `PASS_APPROVAL_POC`. WCBK1 has added and deployed read-only `GET/POST /api/wecom/approval/callback`, `wecomCallbackCrypto`, `npm run check:wecom-approval-callback-poc`, and the `wecom-callback` process gate. After remote callback config was set, public report `/app/artifacts/qa-flow/wecom-approval-callback-poc-20260519T083422Z/summary.json` reached `PASS_CALLBACK_URL_VERIFIED`; after `SMOKE101` approved an existing OA-POC missed-punch test approval, `/app/artifacts/qa-flow/wecom-approval-callback-poc-20260519T092648Z/summary.json` reached `PASS_CALLBACK_EVENT_CONFIRMED` with one redacted event. WACP3/WACP3R has added and deployed `npm run check:wecom-approval-compensation-poc`, `POST /cgi-bin/oa/getapprovalinfo` read support, and automatic template discovery by `SMOKE002` creator; public report `/app/artifacts/qa-flow/wecom-approval-compensation-poc-20260519T115944Z/summary.json` is `PASS_WITH_FINDINGS`, because approval details were pulled but callback evidence is not persisted after deploy. This still does not clear mirror data model, emergency mode, writeback, or RuleOps.
|
||||
- WEGOV1 changes the current WeCom route to "Enterprise WeChat owns mature HR/attendance flows; OA stores self-owned history, audit, entry, messages, and emergency fallback." Treat four domains explicitly: WeCom-owned domain is check-in, leave, missed punch, outing, overtime, leave balance, and HR assistant; OA-owned domain remains reimbursement, procurement, contracts, projects, payments, invoices, bank receipts, sensitive attachments, and operating audit; OA archive domain stores WeCom business history, full external keys, and necessary snapshots; OA emergency domain is manually enabled only when WeCom is unavailable. Do not assume a full isolated WeCom sandbox; use the controlled test domain `SMOKE002 -> SMOKE101`, `OA-POC` templates, low-risk approvals, synthetic tests, public live runners, and limited real-device confirmation. Do not start deep mirror until WACP3 compensation pull POC, WREAD1 readability/misapproval gate, WADM1 self-owned archive/mirror data model, and WARCH1 minimum archive implementation have their own contracts and evidence.
|
||||
- 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/WPROC2 fixed the engineering process gate; WEGOV1 is the route gate, and WACP3R has now rerun the compensation-pull POC by automatically discovering OA-POC leave/missed-punch template IDs from `SMOKE002` creator. Public report `/app/artifacts/qa-flow/wecom-approval-compensation-poc-20260519T115944Z/summary.json` is `PASS_WITH_FINDINGS`: compensation pull and detail read work, with finding that callback evidence files are not persisted across deploys. WREAD1 has now added and deployed `npm run check:wecom-approval-readability-gate`; public report `/app/artifacts/qa-flow/wecom-approval-readability-gate-20260519T125532Z/summary.json` is `PAUSED_MANUAL_CONFIRMATION`, so template-level real-device readability/misapproval confirmation, WADM1 self-owned archive/mirror model design, and WARCH1 minimum history archive still remain 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. WAPC2F has implemented and deployed `npm run check:wecom-approval-fixtures`; public dry-run reached `PASS_FIXTURE_DRY_RUN_READY` at `/app/artifacts/qa-flow/wecom-approval-fixtures-20260519T045001Z/summary.json`, but no real WeCom template or approval was created because `WECOM_APPROVAL_FIXTURE_WRITE=1` was not set. 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.
|
||||
- WAPC2FLIVE supersedes the WAPC2F dry-run gap for OA-POC samples: after user approval, the runner was corrected to the official `template_name` + `property` controls create-template payload, deployed with `SYNC_ENV=0`, and the public live report `/app/artifacts/qa-flow/wecom-approval-fixtures-20260519T053400Z/summary.json` reached `PASS_FIXTURE_CREATED`; leave/missed-punch OA-POC templates and low-risk test approvals were created and retained for audit, with WAPC2 auto-rerun `PASS_APPROVAL_POC`. WCBK1 has added and deployed read-only `GET/POST /api/wecom/approval/callback`, `wecomCallbackCrypto`, `npm run check:wecom-approval-callback-poc`, and the `wecom-callback` process gate. After remote callback config was set, public report `/app/artifacts/qa-flow/wecom-approval-callback-poc-20260519T083422Z/summary.json` reached `PASS_CALLBACK_URL_VERIFIED`; after `SMOKE101` approved an existing OA-POC missed-punch test approval, `/app/artifacts/qa-flow/wecom-approval-callback-poc-20260519T092648Z/summary.json` reached `PASS_CALLBACK_EVENT_CONFIRMED` with one redacted event. WACP3/WACP3R has added and deployed `npm run check:wecom-approval-compensation-poc`, `POST /cgi-bin/oa/getapprovalinfo` read support, and automatic template discovery by `SMOKE002` creator; WREAD1 deploy reran WACP3R at `/app/artifacts/qa-flow/wecom-approval-compensation-poc-20260519T125515Z/summary.json` and then generated `/app/artifacts/qa-flow/wecom-approval-readability-gate-20260519T125532Z/summary.json` as `PAUSED_MANUAL_CONFIRMATION`. This still does not clear template-level real-device readability, mirror data model, emergency mode, writeback, or RuleOps.
|
||||
- WEGOV1 changes the current WeCom route to "Enterprise WeChat owns mature HR/attendance flows; OA stores self-owned history, audit, entry, messages, and emergency fallback." Treat four domains explicitly: WeCom-owned domain is check-in, leave, missed punch, outing, overtime, leave balance, and HR assistant; OA-owned domain remains reimbursement, procurement, contracts, projects, payments, invoices, bank receipts, sensitive attachments, and operating audit; OA archive domain stores WeCom business history, full external keys, and necessary snapshots; OA emergency domain is manually enabled only when WeCom is unavailable. Do not assume a full isolated WeCom sandbox; use the controlled test domain `SMOKE002 -> SMOKE101`, `OA-POC` templates, low-risk approvals, synthetic tests, public live runners, and limited real-device confirmation. Do not start deep mirror until WACP3 compensation pull POC, WREAD1 template-level real-device readability/misapproval confirmation, WADM1 self-owned archive/mirror data model, and WARCH1 minimum archive implementation have their own contracts and evidence.
|
||||
- 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`.
|
||||
|
||||
@@ -59,6 +59,7 @@ DCL7 已删除本文件中 R70-R91 的长流水历史,避免旧环境、旧开
|
||||
- 2026-05-19 WCBK1 实现并完成企微审批回调只读 POC:新增只读 `GET/POST /api/wecom/approval/callback`、企微回调验签/AES 解密/事件归一化服务、`npm run check:wecom-approval-callback-poc` 和 `wecom-callback` process gate。已以 `SYNC_ENV=0` 部署并复检 `/ready`;初始远端报告 `/app/artifacts/qa-flow/wecom-approval-callback-poc-20260519T065414Z/summary.json` 为 `PARTIAL_CALLBACK_NEEDS_CONFIG`,synthetic 回调验签、解密和审批事件解析通过。配置真实回调后,远端报告 `/app/artifacts/qa-flow/wecom-approval-callback-poc-20260519T083422Z/summary.json` 达到 `PASS_CALLBACK_URL_VERIFIED`;用户随后由 `SMOKE101` 同意一条现有 OA-POC 补卡测试审批单,复跑报告 `/app/artifacts/qa-flow/wecom-approval-callback-poc-20260519T092648Z/summary.json` 达到 `PASS_CALLBACK_EVENT_CONFIRMED`,脱敏证据记录 1 条已通过审批状态事件。本轮不建镜像表、不写 OA 业务数据、不写企微数据、不创建新模板或审批单。
|
||||
- 2026-05-19 WEGOV1 完成企微主办治理与 OA 自建留存路线修正:本轮只更新 Issue CSV 和事实源,不改业务代码、不部署、不写 OA/企微数据。结论固定为“企微成熟功能优先上线,OA 做自建数据留存、审计、入口、消息和应急兜底”;HR/考勤日常流程优先企微主办,报销、采购、合同、项目、付款、发票、银行回单、高敏附件和经营审计继续 OA 主办。后续顺序调整为 `WACP3 -> WREAD1 -> WADM1 -> WARCH1 -> wecom_attendance_approval_mirror_v1`,其中 WARCH1 先保证企微不可用时可查看已保存历史,再进入完整镜像增强。
|
||||
- 2026-05-19 WACP3/WACP3R 完成企微审批补偿拉取只读 POC:新增 `server/src/scripts/check-wecom-approval-compensation-poc.js` / `npm run check:wecom-approval-compensation-poc`,固定 `WECOM_AGENT_SECRET` 调用 `POST /cgi-bin/oa/getapprovalinfo`,按时间窗口和模板过滤拉审批单号,再复用 `getapprovaldetail` 拉详情并与 WCBK1 回调证据脱敏比对。初始公网报告因缺模板 ID 为 `PARTIAL_COMPENSATION_NEEDS_CONFIG`;WACP3R 改为按 `SMOKE002` creator 自动发现 OA-POC 请假/补卡模板并复跑,公网报告 `/app/artifacts/qa-flow/wecom-approval-compensation-poc-20260519T115944Z/summary.json` 为 `PASS_WITH_FINDINGS`:请假/补卡各拉到 1 条详情,finding 为回调证据文件未持久化。本轮未写 OA/企微数据,未保存完整模板 ID。
|
||||
- 2026-05-19 WREAD1 新增企微审批可读性与误审批防护门禁:新增 `server/src/scripts/check-wecom-approval-readability-gate.js` / `npm run check:wecom-approval-readability-gate`,只读汇总 OA-POC 请假/补卡证据并输出真机确认清单。本轮以 `SYNC_ENV=0` 部署,不同步本地 env;部署后先只读复跑 WACP3R 自动发现模板并生成 `/app/artifacts/qa-flow/wecom-approval-compensation-poc-20260519T125515Z/summary.json`,再执行 WREAD1,报告 `/app/artifacts/qa-flow/wecom-approval-readability-gate-20260519T125532Z/summary.json` 为 `PAUSED_MANUAL_CONFIRMATION`。本轮未新建企微模板或审批单,未写 OA/企微数据;下一步需要审批人真机分别确认请假/补卡消息卡片、列表、详情页和快捷同意/驳回误点风险。
|
||||
|
||||
## 关键证据索引
|
||||
|
||||
@@ -116,6 +117,7 @@ DCL7 已删除本文件中 R70-R91 的长流水历史,避免旧环境、旧开
|
||||
| WCBK1 | 已完成公网真实回调只读 POC:只读回调端点和 synthetic runner 通过本地加密链路;远端配置真实回调后达到 `PASS_CALLBACK_URL_VERIFIED`,`SMOKE101` 同意现有 OA-POC 补卡测试单后达到 `PASS_CALLBACK_EVENT_CONFIRMED`;只保存脱敏事件证据,不写业务镜像 | `issues/2026-05-19_14-11-20-wecom-approval-callback-readonly-poc-v1.csv`、`/app/artifacts/qa-flow/wecom-approval-callback-poc-20260519T092648Z/summary.json` |
|
||||
| WEGOV1 | 已完成 docs-only 路线治理:固定企微主办域、OA 主办域、OA 留存域、OA 应急域、受控测试域和自动化/人工边界;明确 WCBK1 证据不直接放行完整镜像,后续先做 WACP3/WACP3R、WREAD1、WADM1 和 WARCH1 | `issues/2026-05-19_18-17-44-wecom-governance-owned-oa-archive-route-v1.csv` |
|
||||
| WACP3/WACP3R | 已实现并公网只读复跑:新增补偿拉取 runner 和 `getapprovalinfo` 只读封装;WACP3R 不再要求人工填写模板 ID,改为按 `SMOKE002` creator 自动发现 OA-POC 请假/补卡模板并复跑。远端报告 `/app/artifacts/qa-flow/wecom-approval-compensation-poc-20260519T115944Z/summary.json` 为 `PASS_WITH_FINDINGS`:请假/补卡各拉到 1 条详情,finding 为回调证据文件未持久化,未写 OA/企微数据 | `issues/2026-05-19_19-03-06-wecom-approval-compensation-pull-poc-v1.csv`、`issues/2026-05-19_19-42-22-wecom-approval-compensation-config-rerun-v1.csv`、`/app/artifacts/qa-flow/wecom-approval-compensation-poc-20260519T115944Z/summary.json` |
|
||||
| WREAD1 | 自动门禁已部署并公网只读汇总:新增可读性 runner,部署后复跑 WACP3R 生成 fresh 脱敏证据,再执行 WREAD1 得到 `PAUSED_MANUAL_CONFIRMATION`;等待审批人真机确认请假/补卡可读性和快捷按钮误点风险 | `issues/2026-05-19_20-39-40-wecom-approval-readability-gate-v1.csv`、`/app/artifacts/qa-flow/wecom-approval-compensation-poc-20260519T125515Z/summary.json`、`/app/artifacts/qa-flow/wecom-approval-readability-gate-20260519T125532Z/summary.json` |
|
||||
|
||||
## 当前门禁
|
||||
|
||||
|
||||
+1
-1
@@ -107,7 +107,7 @@ WPROC1 将 WAPC2 之后的审批路线固定为:
|
||||
- `WCBK1` 已完成真实回调 POC:`npm run check:wecom-approval-callback-poc -- --live` 先达到 `PASS_CALLBACK_URL_VERIFIED`,随后由 `SMOKE101` 同意一条现有 OA-POC 补卡测试审批单并复跑达到 `PASS_CALLBACK_EVENT_CONFIRMED`。报告只记录事件类型、审批类型、状态、脱敏 `sp_no`/模板前缀和幂等键摘要,不保存 Token、EncodingAESKey、原始 XML、密文、完整 `sp_no`、完整 userid、表单值、附件或评论。
|
||||
- `WEGOV1` 固定混合架构:企微主办域为 HR/考勤成熟功能,OA 主办域为财务经营和高敏业务,OA 留存域保存企微关键历史和完整外部关联键,OA 应急域只在企微不可用时人工开启。
|
||||
- `WACP3R` 已把补偿拉取复跑改为自动发现:先按 `SMOKE002` creator 获取 OA-POC 审批单号和详情,识别请假/补卡模板,再按模板过滤拉取详情;公网报告 `/app/artifacts/qa-flow/wecom-approval-compensation-poc-20260519T115944Z/summary.json` 为 `PASS_WITH_FINDINGS`。finding 是回调证据文件未持久化,不得误读为镜像入库或自建留存已完成。
|
||||
- `WREAD1` 必须用真机或代表设备确认审批消息、审批列表和详情页的可读性;若快捷同意/驳回容易误操作,则模板调整或该模板回退 OA 主办。
|
||||
- `WREAD1` 已新增只读可读性 runner 并公网生成 `/app/artifacts/qa-flow/wecom-approval-readability-gate-20260519T125532Z/summary.json`,结论 `PAUSED_MANUAL_CONFIRMATION`;仍必须用真机或代表设备确认审批消息、审批列表和详情页的可读性。若快捷同意/驳回容易误操作,则模板调整或该模板回退 OA 主办。
|
||||
- `WADM1` 必须先设计自建留存和镜像字段、状态机、幂等键、索引、隐私保留和迁移回滚方案,再进入 schema 或业务代码实现。
|
||||
- `WARCH1` 必须先实现最小自建历史留存,让 OA 在企微 API 不可用时仍可查看已保存历史,再进入完整镜像增强。
|
||||
|
||||
|
||||
+5
-4
@@ -2,10 +2,10 @@
|
||||
|
||||
**文档状态**:当前开发路线图事实源。
|
||||
**分类**:开发治理 / 路线图。
|
||||
**适用时间**:WDRG / WCP1 / WHMSLIVE / WOUA / WAPI / WRRA / WOUA-FIX / WRV1 / WRA1 / WMSG1 / WAPC1 / WAPC2 / WPROC1 / WPROC2 / WAPC2F / WAPC2FLIVE / WCBK1 / WEGOV1 / WACP3 / WACP3R 之后。
|
||||
**关联 Issue**:`WDRG,WCP1,WHMS,WHMSLIVE,WOUA,WAPI,WRRA,WOUAFIX,WRV1,WRA1,WMSG1,WAPC1,WAPC2,WPROC1,WPROC2,WAPC2F,WAPC2FLIVE,WCBK1,WEGOV1,WACP3,WACP3R`。
|
||||
**适用时间**:WDRG / WCP1 / WHMSLIVE / WOUA / WAPI / WRRA / WOUA-FIX / WRV1 / WRA1 / WMSG1 / WAPC1 / WAPC2 / WPROC1 / WPROC2 / WAPC2F / WAPC2FLIVE / WCBK1 / WEGOV1 / WACP3 / WACP3R / WREAD1 自动汇总之后。
|
||||
**关联 Issue**:`WDRG,WCP1,WHMS,WHMSLIVE,WOUA,WAPI,WRRA,WOUAFIX,WRV1,WRA1,WMSG1,WAPC1,WAPC2,WPROC1,WPROC2,WAPC2F,WAPC2FLIVE,WCBK1,WEGOV1,WACP3,WACP3R,WREAD1`。
|
||||
**最近审计**:2026-05-19。
|
||||
**不得误读**:本文只控制后续开发节奏、阶段顺序和门禁。WHMSLIVE 后只代表企微 HR 主数据镜像已部署并受控验证;WAPI 后进一步固定企微 API/Secret 口径并转为运行优先;WRRA 后明确 `PASS_WITH_FINDINGS` 只代表只读对账成功且存在差异,不代表企微考勤/审批镜像、OA 应急接管、恢复回写或 RuleOps 已完成运行时实现。WMSG1 只证明企业微信应用消息可达和 Deeplink 可回到 OA;WAPC1 只固化审批配置准备清单;WAPC2 只新增请假/补卡模板详情和低风险 `sp_no` 审批详情只读 POC,不代表真实回调或镜像能力已完成。WPROC1 后,WAPC2 不直接放行深度镜像,必须先完成自动样件、回调、补偿拉取和镜像数据模型设计门禁。WPROC2 只新增可执行 process gate。WAPC2FLIVE 已创建 OA-POC 请假/补卡测试模板和低风险测试审批单,并自动复跑 WAPC2 得到 `PASS_APPROVAL_POC`。WCBK1 已完成真实回调 URL 验证和一条现有 OA-POC 测试审批单事件确认,公网报告为 `PASS_CALLBACK_EVENT_CONFIRMED`;WEGOV1 后路线明确为“企微成熟功能优先上线,OA 做自建留存、审计、入口、消息和应急兜底”。WACP3R 已将补偿拉取复跑修正为自动按 `SMOKE002` creator 发现 OA-POC 请假/补卡模板,公网报告 `/app/artifacts/qa-flow/wecom-approval-compensation-poc-20260519T115944Z/summary.json` 为 `PASS_WITH_FINDINGS`:已拉到请假/补卡审批详情,但当前回调证据文件未持久化到可跨部署读取的位置,作为 findings 进入 WADM1/WARCH1。上述证据只证明企微主办 HR/考勤的可用基础,不代表可读性门禁、自建留存模型、最小历史留存或 `wecom_attendance_approval_mirror_v1` 已完成。
|
||||
**不得误读**:本文只控制后续开发节奏、阶段顺序和门禁。WHMSLIVE 后只代表企微 HR 主数据镜像已部署并受控验证;WAPI 后进一步固定企微 API/Secret 口径并转为运行优先;WRRA 后明确 `PASS_WITH_FINDINGS` 只代表只读对账成功且存在差异,不代表企微考勤/审批镜像、OA 应急接管、恢复回写或 RuleOps 已完成运行时实现。WMSG1 只证明企业微信应用消息可达和 Deeplink 可回到 OA;WAPC1 只固化审批配置准备清单;WAPC2 只新增请假/补卡模板详情和低风险 `sp_no` 审批详情只读 POC,不代表真实回调或镜像能力已完成。WPROC1 后,WAPC2 不直接放行深度镜像,必须先完成自动样件、回调、补偿拉取和镜像数据模型设计门禁。WPROC2 只新增可执行 process gate。WAPC2FLIVE 已创建 OA-POC 请假/补卡测试模板和低风险测试审批单,并自动复跑 WAPC2 得到 `PASS_APPROVAL_POC`。WCBK1 已完成真实回调 URL 验证和一条现有 OA-POC 测试审批单事件确认,公网报告为 `PASS_CALLBACK_EVENT_CONFIRMED`;WEGOV1 后路线明确为“企微成熟功能优先上线,OA 做自建留存、审计、入口、消息和应急兜底”。WACP3R 已将补偿拉取复跑修正为自动按 `SMOKE002` creator 发现 OA-POC 请假/补卡模板,公网报告 `/app/artifacts/qa-flow/wecom-approval-compensation-poc-20260519T115944Z/summary.json` 为 `PASS_WITH_FINDINGS`;WREAD1 又在部署后只读复跑补偿拉取 `/app/artifacts/qa-flow/wecom-approval-compensation-poc-20260519T125515Z/summary.json`,并生成可读性门禁报告 `/app/artifacts/qa-flow/wecom-approval-readability-gate-20260519T125532Z/summary.json`,结论为 `PAUSED_MANUAL_CONFIRMATION`。上述证据只证明请假/补卡样例仍可按 API 读取,尚未得到审批人真机可读性和误点风险结论;不代表自建留存模型、最小历史留存或 `wecom_attendance_approval_mirror_v1` 已完成。
|
||||
|
||||
## 1. 路线图原则
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
| `WCBK1` | 真实回调 URL 与事件已确认 | 新增只读 `GET/POST /api/wecom/approval/callback`、`npm run check:wecom-approval-callback-poc` 和 `wecom-callback` process gate。远端配置回调 URL、Token 和 EncodingAESKey 后,live runner 报告 `/app/artifacts/qa-flow/wecom-approval-callback-poc-20260519T083422Z/summary.json` 达到 `PASS_CALLBACK_URL_VERIFIED`;用户随后由 `SMOKE101` 同意一条现有 OA-POC 补卡测试审批单,复跑报告 `/app/artifacts/qa-flow/wecom-approval-callback-poc-20260519T092648Z/summary.json` 达到 `PASS_CALLBACK_EVENT_CONFIRMED`,脱敏证据记录 `eventCount=1`、状态 `已通过`。 |
|
||||
| `WEGOV1` | 路线和治理已调整 | 明确混合架构:企微成熟功能优先上线,OA 做自建数据留存、审计、入口、消息和应急兜底;固化受控测试域、自动化优先和人工边界;后续顺序调整为 WACP3、WREAD1、WADM1、WARCH1 后再评估深度镜像。 |
|
||||
| `WACP3/WACP3R` | 已实现并公网只读复跑,有 findings | 新增 QA-only `npm run check:wecom-approval-compensation-poc` 和 `POST /cgi-bin/oa/getapprovalinfo` 只读封装,固定 `WECOM_AGENT_SECRET`。WACP3R 改为按 `SMOKE002` creator 自动发现 OA-POC 请假/补卡模板 ID,再按模板过滤拉审批单号并用 `getapprovaldetail` 拉详情;公网报告 `/app/artifacts/qa-flow/wecom-approval-compensation-poc-20260519T115944Z/summary.json` 为 `PASS_WITH_FINDINGS`:请假/补卡各拉到 1 条详情,发现回调证据文件未持久化导致当前无法跨部署比对历史回调。未写 OA/企微数据,未保存完整模板 ID。 |
|
||||
| `WREAD1` | 自动汇总已部署,等待真机确认 | 新增 QA-only `npm run check:wecom-approval-readability-gate`,只读汇总现有 OA-POC 请假/补卡证据并输出人工确认清单。部署后因容器 artifact 重建,先只读复跑 WACP3R 生成 `/app/artifacts/qa-flow/wecom-approval-compensation-poc-20260519T125515Z/summary.json`,再生成 WREAD1 报告 `/app/artifacts/qa-flow/wecom-approval-readability-gate-20260519T125532Z/summary.json`,结论 `PAUSED_MANUAL_CONFIRMATION`。本阶段不写 OA/企微数据,不新建模板或审批单;模板级结论需审批人真机确认后才能写入。 |
|
||||
|
||||
## 3. 阶段路线
|
||||
|
||||
@@ -59,7 +60,7 @@
|
||||
| 7 | `WCBK1 回调只读 POC` | 证明真实回调 URL 验证、验签、解密、幂等键和重复投递处理。 | 回调 URL、Token、EncodingAESKey 由部署环境配置;不得写入仓库或聊天。 | 已完成:`PASS_CALLBACK_EVENT_CONFIRMED`。远端 live URL 验证通过后,由 `SMOKE101` 处理现有 OA-POC 补卡测试单触发真实事件;报告只记录脱敏事件类型、状态、`sp_no`/模板前缀和幂等键摘要。 |
|
||||
| 8 | `WEGOV1 企微主办治理与自建留存路线修正` | 固化混合主办边界、OA 自建留存策略、受控测试域和后续顺序。 | WCBK1 已确认真实回调事件;用户选择优先利用企微成熟功能并要求 OA 保存关键历史。 | docs-only 收口;文档明确企微主办域、OA 主办域、OA 留存域、OA 应急域、自动化与人工边界。 |
|
||||
| 9 | `WACP3 补偿拉取 POC` | 证明按时间窗口批量获取审批单号并可拉取审批详情,作为回调丢失补偿。 | WEGOV1 完成;WAPC2/WAPC2FLIVE/WCBK1 已提供低风险样例、详情读取和真实事件证据;官方 API 和权限明确。 | WACP3R 已自动发现 OA-POC 请假/补卡模板并复跑到 `PASS_WITH_FINDINGS`:补偿拉取和详情读取可用;findings 为当前回调证据文件未持久化,需在 WADM1/WARCH1 设计留存模型时解决。 |
|
||||
| 10 | `WREAD1 企微审批可读性与误审批防护门禁` | 真机确认请假、补卡、外出、加班在企微里是否看得清再批,评估快捷同意/驳回误操作风险。 | WACP3 完成或并行准备低风险样例;审批模板可在企微手机端由代表审批人查看。 | 输出 `PASS_READABLE`、`PASS_WITH_TEMPLATE_ADJUSTMENTS` 或 `FAIL_KEEP_OA_PRIMARY_FOR_THIS_TEMPLATE`;不可读模板不得进入正式镜像。 |
|
||||
| 10 | `WREAD1 企微审批可读性与误审批防护门禁` | 真机确认请假、补卡、外出、加班在企微里是否看得清再批,评估快捷同意/驳回误操作风险。 | WACP3 完成或并行准备低风险样例;审批模板可在企微手机端由代表审批人查看。 | 当前自动汇总为 `PAUSED_MANUAL_CONFIRMATION`;待人工按模板输出 `PASS_READABLE`、`PASS_WITH_TEMPLATE_ADJUSTMENTS` 或 `FAIL_KEEP_OA_PRIMARY_FOR_THIS_TEMPLATE`。不可读模板不得进入正式镜像。 |
|
||||
| 11 | `WADM1 自建留存/镜像数据模型设计` | 先设计 OA 留存字段、状态机、幂等键、索引、隐私保留、审计和迁移回滚方案。 | WACP3 证明补偿拉取可用;WREAD1 明确模板可读性或回退策略。 | 模型覆盖审批、打卡、人事关键快照、回调事件和补偿拉取记录;设计经 Issue CSV 固化后才允许 schema 或业务代码实现。 |
|
||||
| 12 | `WARCH1 最小自建历史留存实现` | 只读保存企微审批、打卡、人事关键快照,先满足企微不可用时可查看历史。 | WADM1 完成并批准 schema/接口实现。 | OA 可查看已保存历史摘要和完整外部关联键;报告和日志只输出脱敏前缀;不保存 Secret、token、EncodingAESKey、原始密文或无治理原始 payload。 |
|
||||
| 13 | `wecom_attendance_approval_mirror_v1` | 企微打卡、请假、补卡、外出、加班、假期余额和审批历史镜像到 OA。 | WARCH1 稳定;外出/加班模板和打卡细项明确;运行优先门禁和用户一致性仍满足。 | OA 可查询企微 HR/考勤镜像、附件摘要、审批历史、考勤解释和审计;不把 OA 主办高敏审批迁到企微。 |
|
||||
|
||||
+6
-5
@@ -85,6 +85,7 @@
|
||||
| `WCBK1` | `issues/2026-05-19_14-11-20-wecom-approval-callback-readonly-poc-v1.csv` | `CURRENT` | 企微审批回调只读 POC 已完成公网真实事件验证:只读回调端点、验签/AES 解密/归一化服务和 runner 已部署;远端报告 `/app/artifacts/qa-flow/wecom-approval-callback-poc-20260519T083422Z/summary.json` 为 `PASS_CALLBACK_URL_VERIFIED`,`SMOKE101` 同意现有 OA-POC 补卡测试单后复跑 `/app/artifacts/qa-flow/wecom-approval-callback-poc-20260519T092648Z/summary.json` 为 `PASS_CALLBACK_EVENT_CONFIRMED`;仍不代表补偿拉取、镜像模型或深度镜像已完成 |
|
||||
| `WEGOV1` | `issues/2026-05-19_18-17-44-wecom-governance-owned-oa-archive-route-v1.csv` | `CURRENT` | 企微主办治理与 OA 自建留存路线修正:docs-only 固定企微主办域、OA 主办域、OA 留存域、OA 应急域、受控测试域、自动化/人工边界和后续顺序;WCBK1 后仍需 WACP3、WREAD1、WADM1、WARCH1,不能直接进入深度镜像 |
|
||||
| `WACP3/WACP3R` | `issues/2026-05-19_19-03-06-wecom-approval-compensation-pull-poc-v1.csv`、`issues/2026-05-19_19-42-22-wecom-approval-compensation-config-rerun-v1.csv` | `CURRENT` | 企微审批补偿拉取 POC 已实现并公网只读复跑:新增 `check:wecom-approval-compensation-poc` 和 `getapprovalinfo` 只读封装,固定 `WECOM_AGENT_SECRET`;WACP3R 改为按 `SMOKE002` creator 自动发现 OA-POC 请假/补卡模板并复跑,远端报告 `/app/artifacts/qa-flow/wecom-approval-compensation-poc-20260519T115944Z/summary.json` 为 `PASS_WITH_FINDINGS`,请假/补卡各拉到 1 条详情,finding 为回调证据文件未持久化;未写 OA/企微数据 |
|
||||
| `WREAD1` | `issues/2026-05-19_20-39-40-wecom-approval-readability-gate-v1.csv` | `CURRENT` | 企微审批可读性与误审批防护门禁自动汇总已实现并公网只读复跑:新增 `check:wecom-approval-readability-gate`,部署后先复跑 WACP3R 生成 `/app/artifacts/qa-flow/wecom-approval-compensation-poc-20260519T125515Z/summary.json`,再生成 `/app/artifacts/qa-flow/wecom-approval-readability-gate-20260519T125532Z/summary.json`,结论 `PAUSED_MANUAL_CONFIRMATION`;等待审批人真机确认请假/补卡模板级可读性和误点风险,未写 OA/企微数据 |
|
||||
|
||||
旧 Issue CSV 保留为历史审计证据:不删除、不批量回写、不移动到 `docs/archive/` 或其他归档目录。新连续合同如替代旧合同,只在新合同 `Notes` 或文档索引中说明 supersedes。
|
||||
|
||||
@@ -97,14 +98,14 @@
|
||||
| `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,WAPC2,WAPC2FLIVE,WCBK1,WEGOV1,WACP3,WACP3R` | Issue CSV | `docs/requirements-specification.md` | 2026-05-19 | `rg` | high | WEGOV1 后定义 HR/考勤企微主办、OA 自建留存、受控测试域、WACP3/WACP3R、WREAD1/WADM1/WARCH1 前置和深度镜像边界;ERP 未来不接入 OA |
|
||||
| `PRODUCT-SCOPE` | `docs/产品口径.md` | 需求事实源 | 产品边界事实源 | `CURRENT` | product | `REQ8,P2T,WFDG,WDRG,WCP1,WCPC,WHMS,WHMSLIVE,WOUA,WAPI,WRRA,WAPC1,WAPC2FLIVE,WCBK1,WEGOV1` | `rg` | none | 2026-05-19 | `rg` | high | 记录已拍板产品边界、企微/OA 设计门禁、开发路线图、WCBK1 真实回调事件证据、WEGOV1 混合主办边界和 OA 自建留存策略 |
|
||||
| `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,WPROC2,WAPC2F,WAPC2FLIVE,WCBK1,WEGOV1,WACP3,WACP3R` | Issue CSV | none | 2026-05-19 | `rg` | high | WACP3R 后记录补偿拉取 `PASS_WITH_FINDINGS`、回调证据未持久化 finding、WREAD1/WADM1/WARCH1 顺序和禁止跳阶段 |
|
||||
| `ROADMAP-WECOM` | `docs/企微优先开发路线图.md` | 开发治理 | 企微优先开发路线图事实源 | `CURRENT` | product/dev | `WDRG,WCP1,WCPC,WHMS,WHMSLIVE,WOUA,WAPI,WRRA,WOUAFIX,WRV1,WRA1,WMSG1,WAPC1,WAPC2,WPROC1,WPROC2,WAPC2F,WAPC2FLIVE,WCBK1,WEGOV1,WACP3,WACP3R,WREAD1` | Issue CSV | none | 2026-05-19 | `rg` | high | WACP3R 后记录补偿拉取 `PASS_WITH_FINDINGS`、回调证据未持久化 finding;WREAD1 自动汇总为 `PAUSED_MANUAL_CONFIRMATION`,等待真机确认,仍禁止跳过 WADM1/WARCH1 |
|
||||
| `WECOM-API-CATALOG` | `docs/企微API能力目录.md` | 集成事实源 | 企微 API 能力目录和 Secret 矩阵事实源 | `CURRENT` | product/backend/qa | `WAPI,WRRA,WAPC1,WAPC2,WPROC1,WPROC2,WAPC2F,WAPC2FLIVE,WCBK1,WEGOV1` | 官方企业微信开发者中心链接/Issue CSV | none | 2026-05-19 | `rg` | high | 企微开发先读;只保存摘要、链接和项目决策,不保存 Secret、token、完整 userid、手机号、邮箱或原始 payload;WEGOV1 后记录无完整 sandbox 假设、自建留存策略和后续 POC 顺序 |
|
||||
| `DELIVERY` | `docs/开发交付与变更管理规约.md` | 开发治理 | 开发交付事实源 | `CURRENT` | dev/qa | `GOV8,WDRG,WRRA,WAPC1,WPROC1,WPROC2,WCBK1,WEGOV1` | `rg` | none | 2026-05-19 | `rg` | high | Issue、review、commit、交付规则;WEGOV1 后采用自动化优先、受控测试域、自建留存说明和 WACP3/WREAD1/WADM1/WARCH1 前置门禁 |
|
||||
| `QA-GATE` | `docs/测试规约与回归门禁.md` | 测试验收 | 测试和回归门禁事实源 | `CURRENT` | qa | `QAF8,GOV8,WPROC1,WPROC2,WAPC2FLIVE,WCBK1,WEGOV1` | 回归脚本/Playwright | none | 2026-05-19 | `rg` | high | 测试计划、DONE 门禁、企微三层门禁、自动化优先、非自审、无完整 sandbox 假设、WREAD1 真机可读性和 WARCH1 留存验收门禁 |
|
||||
| `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,WPROC2,WAPC2F,WAPC2FLIVE,WCBK1,WEGOV1,WACP3,WACP3R` | quick_validate | none | 2026-05-19 | `quick_validate.py` | medium | 每轮交付操作入口;企微/OA 后续先读 API 能力目录、Secret 矩阵、路线图、WEGOV1 自建留存边界、WCBK1 真实回调证据和 WACP3R 补偿拉取 findings,不得跳过 WREAD1/WADM1/WARCH1 直接做考勤审批镜像 |
|
||||
| `REQ-WF` | `docs/需求-统一审批中心规约.md` | 需求事实源 | 审批模块规约 | `CURRENT` | product/backend | `AP6,WF8,WFOC,DBWFOC,WFDG,WOUA,WAPI,WRRA,WRV1,WRA1,WMSG1,WAPC1,WAPC2,WPROC1,WPROC2,WAPC2F,WAPC2FLIVE,WCBK1,WEGOV1,WACP3,WACP3R` | workflow 回归 | none | 2026-05-19 | `rg` | high | OA 主办审批、企微主办 HR/考勤审批、OA 自建留存、真实回调事件证据、WACP3R 补偿拉取 findings、WREAD1/WADM1/WARCH1 前置和 OA 应急接管边界 |
|
||||
| `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,WPROC2,WAPC2F,WAPC2FLIVE,WCBK1,WEGOV1,WACP3,WACP3R,WREAD1` | quick_validate | none | 2026-05-19 | `quick_validate.py` | medium | 每轮交付操作入口;企微/OA 后续先读 API 能力目录、Secret 矩阵、路线图、WEGOV1 自建留存边界、WACP3R 补偿拉取 findings 和 WREAD1 自动汇总状态,不得跳过人工可读性确认、WADM1/WARCH1 直接做考勤审批镜像 |
|
||||
| `REQ-WF` | `docs/需求-统一审批中心规约.md` | 需求事实源 | 审批模块规约 | `CURRENT` | product/backend | `AP6,WF8,WFOC,DBWFOC,WFDG,WOUA,WAPI,WRRA,WRV1,WRA1,WMSG1,WAPC1,WAPC2,WPROC1,WPROC2,WAPC2F,WAPC2FLIVE,WCBK1,WEGOV1,WACP3,WACP3R,WREAD1` | workflow 回归 | none | 2026-05-19 | `rg` | high | OA 主办审批、企微主办 HR/考勤审批、OA 自建留存、真实回调事件证据、WACP3R 补偿拉取 findings、WREAD1 自动汇总和人工确认前置、WADM1/WARCH1 前置及 OA 应急接管边界 |
|
||||
| `REQ-OCR` | `docs/需求-报销发票OCR规约.md` | 需求事实源 | OCR 模块规约 | `CURRENT` | product/backend | `RI6,OCR8,BIZ8` | OCR 回归 | none | 2026-05-13 | `rg` | high | OCR 和发票边界 |
|
||||
| `RULE-REIMB` | `docs/报销发票附件规则.md` | 需求事实源 | 报销发票附件规则 | `CURRENT` | product/finance | `RI6,BIZ8` | 报销回归 | none | 2026-05-13 | `rg` | high | 报销、发票、附件细则 |
|
||||
| `REQ-WECOM` | `docs/需求-企业微信基础设施规约.md` | 需求事实源 | 企业微信边界规约 | `CURRENT` | product/backend | `R7E,ORG8,WF8,ATTV1,HR-WECOM-DIRECTORY-V1,WFOC,DBWFOC,WFDG,WCP1,WCPC,WHMS,WHMSLIVE,WOUA,WAPI,WRRA,WRV1,WRA1,WMSG1,WAPC1,WAPC2,WPROC1,WPROC2,WAPC2F,WAPC2FLIVE,WCBK1,WEGOV1` | live smoke/真机反馈/Issue CSV | none | 2026-05-19 | `rg` | high | 企微优先承担 HR/考勤员工日常审批和人事/打卡能力;WEGOV1 固定 OA 自建留存和受控测试域;采购、合同、项目、报销和财务继续 OA 主办 |
|
||||
@@ -126,14 +127,14 @@
|
||||
| `docs/文档事实源状态矩阵.md` | 当前事实源 | 文档分类和恢复规则 |
|
||||
| `docs/海音达OA-MVP需求规格说明书.md` | 当前事实源 | PRD 主事实源 |
|
||||
| `docs/产品设计规则.md` | 当前事实源 | PC、移动、企业微信设计规则;DG8 主 UI/布局参考层级 |
|
||||
| `docs/企微优先开发路线图.md` | 当前事实源 | WEGOV1 后企微优先 HR/考勤改造阶段顺序、四域边界、API/Secret 口径、运行优先、审批只读 POC、自动样件、回调、补偿拉取、可读性门禁、自建留存、模型设计和禁止跳阶段 |
|
||||
| `docs/企微优先开发路线图.md` | 当前事实源 | WEGOV1 后企微优先 HR/考勤改造阶段顺序、四域边界、API/Secret 口径、运行优先、审批只读 POC、自动样件、回调、补偿拉取、WREAD1 可读性自动汇总、人工确认门禁、自建留存、模型设计和禁止跳阶段 |
|
||||
| `docs/企微API能力目录.md` | 当前事实源 | WEGOV1 后企微官方 API、Secret 矩阵、权限/可信 IP/回调模板、读写边界、无完整 sandbox 假设、运行优先、审批只读 POC 和后续 POC 决策 |
|
||||
| `docs/产品口径.md` | 当前事实源 | 产品边界和业务规则 |
|
||||
| `docs/需求-统一审批中心规约.md` | 当前事实源 | 审批、催办、超时、效率摘要 |
|
||||
| `docs/报销发票附件规则.md` | 当前事实源 | 报销、发票、附件细则 |
|
||||
| `docs/需求-报销发票OCR规约.md` | 当前事实源 | OCR 和报销模块规约 |
|
||||
| `docs/需求-企业微信基础设施规约.md` | 当前事实源 | 企业微信入口、身份、消息、deeplink、企微主办域、OA 自建留存和应急兜底 |
|
||||
| `docs/需求-请假考勤修正规约.md` | 当前事实源 | 请假、补卡、考勤修正、WAPC2/WAPC2FLIVE/WCBK1/WACP3R 证据、WEGOV1 自建留存路线和 WREAD1/WADM1/WARCH1 后续门禁 |
|
||||
| `docs/需求-请假考勤修正规约.md` | 当前事实源 | 请假、补卡、考勤修正、WAPC2/WAPC2FLIVE/WCBK1/WACP3R/WREAD1 自动汇总证据、WEGOV1 自建留存路线和 WREAD1 人工确认/WADM1/WARCH1 后续门禁 |
|
||||
| `docs/需求-采购费用审批规约.md` | 当前事实源 | 采购和费用审批 |
|
||||
| `docs/需求-合同附件归档规约.md` | 当前事实源 | 合同台账和附件归档 |
|
||||
| `docs/需求-项目轻管理规约.md` | 当前事实源 | 项目轻管理 |
|
||||
|
||||
+3
-2
@@ -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 -> WPROC2 -> WAPC2F dry-run -> WAPC2FLIVE -> WCBK1 -> WEGOV1 -> WACP3/WACP3R。
|
||||
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 -> WAPC2F dry-run -> WAPC2FLIVE -> WCBK1 -> WEGOV1 -> WACP3/WACP3R -> WREAD1 自动汇总。
|
||||
|
||||
- R6、PC6、PA6、FL6、AP6、RI6、LP6 已完成并部署。
|
||||
- ME6 自动化、公网视觉矩阵和 `ME6-3` 代表性企业微信真机门禁已完成。
|
||||
@@ -143,6 +143,7 @@ R6 -> PC6 -> PA6 -> FL6 -> AP6 -> RI6 -> LP6 -> ME6/QG6 -> DG7/MRF7 -> MIA7 -> M
|
||||
| `WCBK1` | `issues/2026-05-19_14-11-20-wecom-approval-callback-readonly-poc-v1.csv` | 当前回调只读 POC:真实 URL 和事件已确认 | 新增只读 `GET/POST /api/wecom/approval/callback`、`check:wecom-approval-callback-poc` 和 `wecom-callback` process gate;远端报告 `PASS_CALLBACK_URL_VERIFIED` 后,`SMOKE101` 处理现有 OA-POC 测试单并复跑达到 `PASS_CALLBACK_EVENT_CONFIRMED` | 不改 schema/UI;不建镜像表;不写 OA/企微业务数据;不创建新模板或审批单;后续仍需 WEGOV1/WACP3/WREAD1/WADM1/WARCH1 |
|
||||
| `WEGOV1` | `issues/2026-05-19_18-17-44-wecom-governance-owned-oa-archive-route-v1.csv` | 当前路线治理:企微主办 + OA 自建留存 | 固定 HR/考勤企微主办、财务经营和高敏业务 OA 主办、OA 留存关键企微历史、企业微信受控测试域、自动化与人工边界;后续新增 WREAD1 和 WARCH1 门禁 | docs-only;不改业务代码/API/schema/UI;不部署;不写 OA/企微数据;不进入深度镜像 |
|
||||
| `WACP3/WACP3R` | `issues/2026-05-19_19-03-06-wecom-approval-compensation-pull-poc-v1.csv`、`issues/2026-05-19_19-42-22-wecom-approval-compensation-config-rerun-v1.csv` | 当前补偿拉取 POC:runner 已部署并自动发现模板复跑 | 新增 `check:wecom-approval-compensation-poc`,固定 `WECOM_AGENT_SECRET` 只读调用 `getapprovalinfo` 和 `getapprovaldetail`;WACP3R 按 `SMOKE002` creator 自动发现 OA-POC 请假/补卡模板,远端报告为 `PASS_WITH_FINDINGS`,finding 为回调证据文件未持久化 | 不新增公开 API/schema/UI;不写 OA/企微数据;不保存完整模板 ID;不无过滤拉取高风险审批详情;不进入深度镜像 |
|
||||
| `WREAD1` | `issues/2026-05-19_20-39-40-wecom-approval-readability-gate-v1.csv` | 当前可读性门禁:自动汇总已部署,等待真机确认 | 新增 `check:wecom-approval-readability-gate`,只读汇总请假/补卡 OA-POC 证据;公网报告 `/app/artifacts/qa-flow/wecom-approval-readability-gate-20260519T125532Z/summary.json` 为 `PAUSED_MANUAL_CONFIRMATION` | 不新增公开 API/schema/UI;不写 OA/企微数据;不新建模板或审批单;未完成人工确认前不得进入 WADM1 或深度镜像 |
|
||||
|
||||
旧 Issue CSV 不删除、不批量回写、不移动到归档目录。若新治理流替代旧合同,只在新 CSV 的 `Notes` 或本索引说明 supersedes,旧文件保留历史审计语义。
|
||||
|
||||
@@ -165,7 +166,7 @@ R6 -> PC6 -> PA6 -> FL6 -> AP6 -> RI6 -> LP6 -> ME6/QG6 -> DG7/MRF7 -> MIA7 -> M
|
||||
| [海音达OA-MVP需求规格说明书](./海音达OA-MVP需求规格说明书.md) | PRD 主事实源;WAPC2FLIVE 后包含企微审批配置、只读 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/WPROC2/WAPC2F/WAPC2FLIVE/WCBK1/WEGOV1/WACP3/WACP3R 后企微优先 HR/考勤改造的阶段顺序、准入/退出门禁、并行规则、禁止跳阶段、API/Secret 口径、运行优先、消息实发、审批配置、只读 POC、process gate、自动样件 live 证据、真实回调事件证据、自建留存、补偿拉取、可读性和模型设计事实源 |
|
||||
| [企微优先开发路线图](./企微优先开发路线图.md) | WDRG/WCP1/WCPC/WHMS/WHMSLIVE/WOUA/WAPI/WRRA/WOUA-FIX/WRV1/WRA1/WMSG1/WAPC1/WAPC2/WPROC1/WPROC2/WAPC2F/WAPC2FLIVE/WCBK1/WEGOV1/WACP3/WACP3R/WREAD1 后企微优先 HR/考勤改造的阶段顺序、准入/退出门禁、并行规则、禁止跳阶段、API/Secret 口径、运行优先、消息实发、审批配置、只读 POC、process gate、自动样件 live 证据、真实回调事件证据、自建留存、补偿拉取、可读性自动汇总和模型设计事实源 |
|
||||
| [企微 API 能力目录](./企微API能力目录.md) | WAPI/WAPC1/WAPC2/WPROC1/WPROC2/WAPC2F/WAPC2FLIVE/WCBK1/WEGOV1/WACP3/WACP3R 后企微官方 API、Secret 矩阵、权限/可信 IP/回调模板、读写边界、运行优先、审批配置、只读 POC、WAPC2F dry-run、WAPC2FLIVE live 创建、WCBK1 回调真实事件 POC、无完整 sandbox 假设、补偿拉取自动发现和 WREAD1/WADM1/WARCH1 决策事实源 |
|
||||
| [产品口径](./产品口径.md) | 已拍板产品边界和业务规则;WEGOV1 后包含混合主办、自建留存、受控测试域、后续阶段顺序和深度镜像前置口径 |
|
||||
| [需求 - 统一审批中心规约](./需求-统一审批中心规约.md) | OA 主办审批、企微主办 HR/考勤员工日常审批镜像、催办、超时提醒、审批效率 |
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
# hyb-oa 管理员手册(10 人以下团队版)
|
||||
|
||||
**当前状态(2026-05-19 / WACP3R)**:R7E、PRELAUNCH、财务闭环、ATTV1、HR-WECOM-DIRECTORY-V1、WAPI、WOUA-FIX、WRV1/WRA1、WMSG1、WAPC1/WAPC2、WPROC1/WPROC2、WAPC2F dry-run、WAPC2FLIVE、WCBK1 和 WEGOV1 均已收口;SYSTEM-COMPLETE-GATE 结论为 `GO_TO_USE`。WAPI 后固定企微 API 能力目录和 Secret 矩阵:通讯录使用 `WECOM_CONTACT_SECRET`,人事助手/打卡/消息/OAuth/审批读取使用 `WECOM_AGENT_SECRET`,运行时代码不得靠多个 Secret 试错。WAPC2FLIVE 已创建 OA-POC 请假/补卡测试模板和低风险测试审批单,报告 `/app/artifacts/qa-flow/wecom-approval-fixtures-20260519T053400Z/summary.json` 为 `PASS_FIXTURE_CREATED`,自动复跑 WAPC2 为 `PASS_APPROVAL_POC`;测试模板和审批记录保留审计,不自动删除。WCBK1 已部署只读回调端点和 runner,远端完成回调 URL 验证并收到一条由 `SMOKE101` 同意现有 OA-POC 补卡测试单触发的真实审批状态事件,报告 `/app/artifacts/qa-flow/wecom-approval-callback-poc-20260519T092648Z/summary.json` 为 `PASS_CALLBACK_EVENT_CONFIRMED`。WACP3R 已新增自动发现模板 ID 路径并公网只读复跑,报告 `/app/artifacts/qa-flow/wecom-approval-compensation-poc-20260519T115944Z/summary.json` 为 `PASS_WITH_FINDINGS`:远端 `WECOM_AGENT_SECRET` 可用,已按 `SMOKE002` creator 自动发现请假/补卡 OA-POC 模板并拉取审批详情;finding 是回调证据文件未持久化,需在 WADM1/WARCH1 设计自建留存时解决。后续仍需 WREAD1 审批可读性门禁、WADM1 自建留存/镜像数据模型设计和 WARCH1 最小历史留存,不能直接进入 `wecom_attendance_approval_mirror_v1`。采购、费用、合同、项目、报销、付款、AP、代账交接和高敏附件继续 OA 主办,企业微信只做脱敏提醒和 OA deeplink。公网 `payment_batch_v1=force_on`,`erp_sync=force_off`。公司当前无 ERP/独立财务系统,且 ERP 未来不接入 OA;财务闭环以 OA 内审批、台账、付款批次、银行对公付款回单和月度代账交接为准。
|
||||
**当前状态(2026-05-19 / WREAD1 自动汇总)**:R7E、PRELAUNCH、财务闭环、ATTV1、HR-WECOM-DIRECTORY-V1、WAPI、WOUA-FIX、WRV1/WRA1、WMSG1、WAPC1/WAPC2、WPROC1/WPROC2、WAPC2F dry-run、WAPC2FLIVE、WCBK1、WEGOV1 和 WACP3/WACP3R 均已收口;SYSTEM-COMPLETE-GATE 结论为 `GO_TO_USE`。WAPI 后固定企微 API 能力目录和 Secret 矩阵:通讯录使用 `WECOM_CONTACT_SECRET`,人事助手/打卡/消息/OAuth/审批读取使用 `WECOM_AGENT_SECRET`,运行时代码不得靠多个 Secret 试错。WAPC2FLIVE 已创建 OA-POC 请假/补卡测试模板和低风险测试审批单,报告 `/app/artifacts/qa-flow/wecom-approval-fixtures-20260519T053400Z/summary.json` 为 `PASS_FIXTURE_CREATED`,自动复跑 WAPC2 为 `PASS_APPROVAL_POC`;测试模板和审批记录保留审计,不自动删除。WCBK1 已部署只读回调端点和 runner,远端完成回调 URL 验证并收到一条由 `SMOKE101` 同意现有 OA-POC 补卡测试单触发的真实审批状态事件,报告 `/app/artifacts/qa-flow/wecom-approval-callback-poc-20260519T092648Z/summary.json` 为 `PASS_CALLBACK_EVENT_CONFIRMED`。WACP3R 已新增自动发现模板 ID 路径并公网只读复跑,报告 `/app/artifacts/qa-flow/wecom-approval-compensation-poc-20260519T115944Z/summary.json` 为 `PASS_WITH_FINDINGS`;WREAD1 部署后又只读复跑补偿拉取 `/app/artifacts/qa-flow/wecom-approval-compensation-poc-20260519T125515Z/summary.json` 并生成可读性报告 `/app/artifacts/qa-flow/wecom-approval-readability-gate-20260519T125532Z/summary.json`,结论 `PAUSED_MANUAL_CONFIRMATION`。后续仍需审批人真机确认请假/补卡可读性和误点风险、WADM1 自建留存/镜像数据模型设计和 WARCH1 最小历史留存,不能直接进入 `wecom_attendance_approval_mirror_v1`。采购、费用、合同、项目、报销、付款、AP、代账交接和高敏附件继续 OA 主办,企业微信只做脱敏提醒和 OA deeplink。公网 `payment_batch_v1=force_on`,`erp_sync=force_off`。公司当前无 ERP/独立财务系统,且 ERP 未来不接入 OA;财务闭环以 OA 内审批、台账、付款批次、银行对公付款回单和月度代账交接为准。
|
||||
|
||||
## 1. 适用范围
|
||||
- 适用于 `small_team_internal` 工作区模式。
|
||||
@@ -436,7 +436,7 @@ WAPC2 请假/补卡只读 POC 操作口径:
|
||||
- `WECOM_ALLOWED_RETURN_HOSTS=oa.hyinda.com`
|
||||
5. 先让管理员用账号密码登录,在 `个人设置` 完成一次绑定,再测试企业微信工作台直达;已绑定用户从手机企业微信进入时应直接看到移动工作台。
|
||||
6. 若启用 OA 同步企业微信打卡数据,在企业微信打卡 API「可调用接口的应用」中授权 `海音达工作台`。OA 默认可复用 `WECOM_AGENT_SECRET`;只有单独创建考勤同步应用时才额外配置 `WECOM_CHECKIN_SECRET`。
|
||||
7. HR/考勤员工日常审批后续配置企业微信原生审批模板作为主流程;WAPC2FLIVE 已自动生成请假/补卡 OA-POC 低风险样件并复跑只读 POC,WCBK1 已完成真实回调事件确认;后续仍需按 WEGOV1 顺序做 WACP3 补偿拉取、WREAD1 可读性门禁、WADM1 自建留存/镜像模型和 WARCH1 最小历史留存。外出/加班模板仍需后续单独配置或 POC。报销、财务、采购、合同和项目类 OA 主办审批仍使用企业微信消息提醒并回到 OA 页面完成。
|
||||
7. HR/考勤员工日常审批后续配置企业微信原生审批模板作为主流程;WAPC2FLIVE 已自动生成请假/补卡 OA-POC 低风险样件并复跑只读 POC,WCBK1 已完成真实回调事件确认,WACP3R 已完成补偿拉取,WREAD1 自动汇总已暂停等待真机确认。后续仍需按 WEGOV1 顺序做 WREAD1 人工可读性结论、WADM1 自建留存/镜像模型和 WARCH1 最小历史留存。外出/加班模板仍需后续单独配置或 POC。报销、财务、采购、合同和项目类 OA 主办审批仍使用企业微信消息提醒并回到 OA 页面完成。
|
||||
|
||||
### 员工会看到的典型状态
|
||||
- 未绑定:登录页会提示“当前企业微信账号尚未绑定”,回退到账号密码登录。
|
||||
|
||||
+2
-2
@@ -5,7 +5,7 @@
|
||||
**状态**:模块级开发规约
|
||||
**上级事实源**:[海音达OA-MVP需求规格说明书](./海音达OA-MVP需求规格说明书.md)
|
||||
|
||||
本规约细化 `hyb-oa` 审批中心和企业微信原生审批镜像的产品边界、角色权限、状态模型、异常规则、留痕要求和验收场景。WFOC 起,审批按主办方分层;DBWFOC 后边界进一步收窄:报销、财务、采购、合同和项目类审批继续由 OA 主办;请假、补卡、外出、加班等 HR/考勤员工日常审批优先由企业微信主办,OA 同步按域镜像并保留自建历史留存、人工接管和恢复回写能力。WMSG1 已验证企业微信应用消息和 Deeplink 回流,WAPC1 后审批镜像前置工作进入配置准备清单阶段,WAPC2 已提供请假/补卡只读 POC runner,WAPC2FLIVE 已自动生成请假/补卡 OA-POC 样件并复跑通过。WCBK1 已完成只读回调 URL 验证和真实审批事件确认,结论为 `PASS_CALLBACK_EVENT_CONFIRMED`。WEGOV1 后明确先利用企微成熟审批和考勤能力,同时由 OA 保存关键历史和外部关联键。WACP3R 已自动发现 OA-POC 请假/补卡模板并公网复跑到 `PASS_WITH_FINDINGS`:补偿拉取和详情读取可用,finding 是回调证据文件未持久化。后续仍必须完成 WREAD1 审批可读性与误审批防护门禁、WADM1 自建留存/镜像数据模型设计和 WARCH1 最小历史留存,不能直接放行镜像实现。后续审批相关开发必须先从本规约拆分 Issue CSV,再进入技术方案和代码实现。
|
||||
本规约细化 `hyb-oa` 审批中心和企业微信原生审批镜像的产品边界、角色权限、状态模型、异常规则、留痕要求和验收场景。WFOC 起,审批按主办方分层;DBWFOC 后边界进一步收窄:报销、财务、采购、合同和项目类审批继续由 OA 主办;请假、补卡、外出、加班等 HR/考勤员工日常审批优先由企业微信主办,OA 同步按域镜像并保留自建历史留存、人工接管和恢复回写能力。WMSG1 已验证企业微信应用消息和 Deeplink 回流,WAPC1 后审批镜像前置工作进入配置准备清单阶段,WAPC2 已提供请假/补卡只读 POC runner,WAPC2FLIVE 已自动生成请假/补卡 OA-POC 样件并复跑通过。WCBK1 已完成只读回调 URL 验证和真实审批事件确认,结论为 `PASS_CALLBACK_EVENT_CONFIRMED`。WEGOV1 后明确先利用企微成熟审批和考勤能力,同时由 OA 保存关键历史和外部关联键。WACP3R 已自动发现 OA-POC 请假/补卡模板并公网复跑到 `PASS_WITH_FINDINGS`:补偿拉取和详情读取可用,finding 是回调证据文件未持久化。WREAD1 已新增只读可读性门禁 runner 并公网汇总到 `PAUSED_MANUAL_CONFIRMATION`,报告 `/app/artifacts/qa-flow/wecom-approval-readability-gate-20260519T125532Z/summary.json`;该状态只说明请假/补卡样例证据可用于真机检查,尚未形成模板级可读性结论。后续仍必须完成 WREAD1 人工确认、WADM1 自建留存/镜像数据模型设计和 WARCH1 最小历史留存,不能直接放行镜像实现。后续审批相关开发必须先从本规约拆分 Issue CSV,再进入技术方案和代码实现。
|
||||
|
||||
---
|
||||
|
||||
@@ -417,7 +417,7 @@ WAPC2F 是 WAPC2 后的自动化优先补缺阶段,目标是尽量用 QA-only
|
||||
- `WCBK1 回调只读 POC`:已新增只读 `GET/POST /api/wecom/approval/callback` 和 `npm run check:wecom-approval-callback-poc`。公网 synthetic 验签、解密和解析通过后,部署环境完成回调配置并达到 `PASS_CALLBACK_URL_VERIFIED`;`SMOKE101` 随后处理现有 OA-POC 补卡测试单触发真实事件,复跑结论 `PASS_CALLBACK_EVENT_CONFIRMED`,报告只保存脱敏事件证据。
|
||||
- `WEGOV1 企微主办治理与自建留存路线修正`:固定企微主办域、OA 主办域、OA 留存域和 OA 应急域;明确企业微信没有完整 sandbox 假设,默认使用受控测试域和自动化 runner。
|
||||
- `WACP3 补偿拉取 POC`:已新增 `check:wecom-approval-compensation-poc` 和 `getapprovalinfo` 只读封装;WACP3R 公网复跑为 `PASS_WITH_FINDINGS`,已按 `SMOKE002` creator 自动发现请假/补卡 OA-POC 模板并拉取审批详情。finding 为回调证据文件未持久化,需在 WADM1/WARCH1 的自建留存模型中解决。
|
||||
- `WREAD1 审批可读性与误审批防护门禁`:真机确认审批消息卡片、审批列表和详情页是否足够清楚;若快捷同意/驳回存在高误操作风险,则要求模板调整或该模板回退 OA 主办。
|
||||
- `WREAD1 审批可读性与误审批防护门禁`:已新增 `check:wecom-approval-readability-gate` 只读 runner,公网报告 `/app/artifacts/qa-flow/wecom-approval-readability-gate-20260519T125532Z/summary.json` 为 `PAUSED_MANUAL_CONFIRMATION`。下一步由审批人真机确认审批消息卡片、审批列表和详情页是否足够清楚;若快捷同意/驳回存在高误操作风险,则要求模板调整或该模板回退 OA 主办。
|
||||
- `WADM1 自建留存/镜像数据模型设计`:先设计字段、状态机、幂等键、索引、隐私保留、审计和迁移回滚方案。
|
||||
- `WARCH1 最小自建历史留存实现`:先只读保存企微审批、打卡和人事关键快照,保证企微不可用时可查看历史,再进入完整镜像增强。
|
||||
|
||||
|
||||
+3
-3
@@ -7,7 +7,7 @@
|
||||
**相关事实源**:[需求 - 统一审批中心规约](./需求-统一审批中心规约.md)、[需求 - 企业微信基础设施规约](./需求-企业微信基础设施规约.md)
|
||||
**体验总依据**:[产品设计规则基线](./产品设计规则.md)
|
||||
|
||||
本规约细化请假、补卡、外出、加班与考勤修正的产品要求。WFOC 起,企业微信作为 HR/考勤员工日常审批和打卡规则主操作端;OA 负责按域镜像、应急接管、恢复回写、查询导出和审计。WEGOV1 后进一步明确:企微成熟 HR/考勤功能优先上线,OA 不重造企业微信完整打卡/考勤/HR 产品,但必须做自建历史留存、审计、入口、消息和应急兜底。WAPC2 后,请假和补卡先通过 QA-only runner 只读验证模板详情与低风险 `sp_no` 审批详情;WAPC2FLIVE 已生成 OA-POC 样件并复跑通过;WCBK1 已完成真实回调 URL 验证和一条测试审批事件确认;WACP3R 已自动发现模板并证明补偿拉取和详情读取可用。后续还必须先完成 WREAD1 可读性与误审批防护门禁、WADM1 自建留存/镜像数据模型设计和 WARCH1 最小历史留存,这不代表审批镜像已实现。PC 和移动页面体验以产品设计规则基线为依据,不重复定义通用审批链和移动导航规则。
|
||||
本规约细化请假、补卡、外出、加班与考勤修正的产品要求。WFOC 起,企业微信作为 HR/考勤员工日常审批和打卡规则主操作端;OA 负责按域镜像、应急接管、恢复回写、查询导出和审计。WEGOV1 后进一步明确:企微成熟 HR/考勤功能优先上线,OA 不重造企业微信完整打卡/考勤/HR 产品,但必须做自建历史留存、审计、入口、消息和应急兜底。WAPC2 后,请假和补卡先通过 QA-only runner 只读验证模板详情与低风险 `sp_no` 审批详情;WAPC2FLIVE 已生成 OA-POC 样件并复跑通过;WCBK1 已完成真实回调 URL 验证和一条测试审批事件确认;WACP3R 已自动发现模板并证明补偿拉取和详情读取可用。WREAD1 已部署只读可读性门禁 runner,公网报告 `/app/artifacts/qa-flow/wecom-approval-readability-gate-20260519T125532Z/summary.json` 为 `PAUSED_MANUAL_CONFIRMATION`,等待请假/补卡真机可读性和误点风险确认。后续还必须完成 WREAD1 人工结论、WADM1 自建留存/镜像数据模型设计和 WARCH1 最小历史留存,这不代表审批镜像已实现。PC 和移动页面体验以产品设计规则基线为依据,不重复定义通用审批链和移动导航规则。
|
||||
|
||||
---
|
||||
|
||||
@@ -49,7 +49,7 @@ REQAUD 复核后,ATTV1 已完成统一入口实现,当前运行时完成度
|
||||
| 请假对团队状态/日历的影响 | `DONE` | 已批准请假可作为团队状态和日历依据。 |
|
||||
| 企业微信打卡作为原始采集 | `DONE` | 由企业微信侧承载定位、人脸、Wi-Fi、设备和防作弊能力;OA 不重造。 |
|
||||
| 企业微信打卡 API 同步 | `PARTIAL` | 已新增同步批次和企微打卡摘要表;有已授权调用打卡 API 的自建应用 Secret 和企微权限时同步,缺配置时记录 `SKIP`。`WECOM_CHECKIN_SECRET` 可作为单独考勤同步应用的显式覆盖;未配置时复用已授权工作台应用的 `WECOM_AGENT_SECRET`。 |
|
||||
| 企微 HR/考勤审批镜像 | `POC_CHAIN_PARTIAL_READY` | WAPC2 已新增请假/补卡模板详情和低风险 `sp_no` 审批详情只读 POC;WAPC2FLIVE 已完成自动样件与 WAPC2 自动复跑;WCBK1 已完成真实回调 URL 验证和一条测试审批事件确认;WACP3R 已证明请假/补卡补偿拉取和详情读取可用,finding 为回调证据文件未持久化。后续 `wecom_attendance_approval_mirror_v1` 仍需外出/加班 POC 或处理决策、WREAD1 可读性门禁、WADM1 自建留存/镜像数据模型和 WARCH1 最小历史留存。 |
|
||||
| 企微 HR/考勤审批镜像 | `POC_CHAIN_PARTIAL_READY` | WAPC2 已新增请假/补卡模板详情和低风险 `sp_no` 审批详情只读 POC;WAPC2FLIVE 已完成自动样件与 WAPC2 自动复跑;WCBK1 已完成真实回调 URL 验证和一条测试审批事件确认;WACP3R 已证明请假/补卡补偿拉取和详情读取可用,finding 为回调证据文件未持久化;WREAD1 自动汇总已部署并停在 `PAUSED_MANUAL_CONFIRMATION`,等待模板级真机确认。后续 `wecom_attendance_approval_mirror_v1` 仍需外出/加班 POC 或处理决策、WREAD1 人工可读性结论、WADM1 自建留存/镜像数据模型和 WARCH1 最小历史留存。 |
|
||||
| 员工补卡申请审批 | `DONE_AS_OA_FALLBACK` | 当前 OA 能提交补卡修正;WFOC 后正常入口迁往企微,OA 仅在应急模式开放并进入恢复回写。 |
|
||||
| 外出申请审批 | `DONE_AS_OA_FALLBACK` | 当前 OA 能提交外出修正;WFOC 后正常入口迁往企微,OA 仅在应急模式开放并进入恢复回写。 |
|
||||
| 加班申请审批 | `DONE_AS_OA_FALLBACK` | 当前 OA 能提交加班修正;WFOC 后正常入口迁往企微,OA 仅在应急模式开放并进入恢复回写。 |
|
||||
@@ -227,7 +227,7 @@ WFOC 后,正常审批规则优先以企业微信审批模板和打卡/假期
|
||||
- 已完成 A0.7/A0.8/A0.9:`WAPC2F`、`WAPC2FLIVE`、`WAPC2 自动复跑` 和 `WCBK1 回调只读 POC` 已证明请假/补卡 OA-POC 样件、只读审批详情、真实回调 URL、验签/解密和一条真实事件脱敏留证。
|
||||
- 当前 A0.10:`WEGOV1 企微主办治理与自建留存路线修正`,固定企微主办域、OA 主办域、OA 留存域、OA 应急域、受控测试域和自动化/人工边界。
|
||||
- 已完成 A0.11:`WACP3/WACP3R 补偿拉取 POC`,已证明按时间窗口获取审批单号并用 `getapprovaldetail` 拉详情;当前 finding 是 WCBK1 回调证据文件未持久化,需在 WADM1/WARCH1 的自建留存模型中解决。
|
||||
- 后续 A0.12:`WREAD1 审批可读性与误审批防护门禁`,真机确认请假、补卡、外出、加班在企业微信消息、列表和详情页中是否看得清,快捷同意/驳回是否容易误点。
|
||||
- 当前 A0.12:`WREAD1 审批可读性与误审批防护门禁`,已新增只读 runner 并公网汇总请假/补卡样例证据到 `PAUSED_MANUAL_CONFIRMATION`;下一步真机确认请假、补卡在企业微信消息、列表和详情页中是否看得清,快捷同意/驳回是否容易误点。外出/加班仍需后续样例或处理决策。
|
||||
- 后续 A0.13:`WADM1 自建留存/镜像数据模型设计`,先设计字段、状态机、幂等键、索引、隐私保留和迁移回滚。
|
||||
- 后续 A0.14:`WARCH1 最小自建历史留存实现`,只读保存企微审批、打卡和人事关键快照,先满足企微不可用时可查看历史。
|
||||
- 后续 A1:`wecom_attendance_approval_mirror_v1`,同步企微请假、补卡、外出、加班审批、附件、历史和状态回调;该命名承接旧口径 `wecom_native_approval_mirror_v1`。WAPC2FLIVE 已生成请假/补卡 OA-POC 模板和低风险 `sp_no` 样例,WACP3R 已证明请假/补卡补偿拉取;正式开发前仍必须补齐外出/加班样例或处理决策、可读性门禁、自建留存模型、最小历史留存和必要打卡细项 POC。
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
ID,Title,Description,Acceptance,Test_Method,Tools,Dev_Status,Review1_Status,Regression_Status,Files,Dependencies,Notes
|
||||
"WREAD1-0","门禁合同与边界冻结","建立 WREAD1 合同;范围锁定请假+补卡,优先使用现有 OA-POC 样例,不新建企微模板或审批单;本轮不写 OA/企微业务数据,不接新回调,不进入 wecom_attendance_approval_mirror_v1。","1) CSV 校验通过;2) 明确 WACP3R 后下一阶段为可读性与误审批防护门禁;3) 结论按模板输出;4) 缺现有样例时记录 PARTIAL_READABILITY_NEEDS_FRESH_FIXTURE 并回问;5) 不保存完整 template_id、sp_no、userid、表单值、附件、评论或原始 payload。","受影响流程:WACP3R -> WREAD1 可读性门禁 -> WADM1 准入;验证步骤:python3 /home/shunli/.codex/skills/plan/scripts/validate_issues_csv.py issues/2026-05-19_20-39-40-wecom-approval-readability-gate-v1.csv;node scripts/qa/check-process-contract.mjs --baseline --json。","python3|node|manual","DONE","DONE","DONE","issues/2026-05-19_20-39-40-wecom-approval-readability-gate-v1.csv","[]","固定边界:只读汇总和人工确认,不写 OA/企微数据,不创建模板/审批单。validate_issues_csv 与 process baseline 已通过;回滚/清理:删除本合同和事实源回写;无数据库迁移、无远端 env 修改、无 OA/企微写入。"
|
||||
"WREAD1-1","只读证据汇总 runner","新增 QA-only runner check-wecom-approval-readability-gate,汇总 WAPC2FLIVE、WCBK1、WACP3R 脱敏证据,输出请假/补卡真机确认清单和可选人工结果。","1) 新增 npm script check:wecom-approval-readability-gate;2) 默认只读已有 artifacts,不访问企微写接口,不获取写入 token;3) 支持 --manual-result=leave:<status> 和 --manual-result=missed_punch:<status>;4) 无人工结果时输出 PAUSED_MANUAL_CONFIRMATION;5) 现有样例证据不足时输出 PARTIAL_READABILITY_NEEDS_FRESH_FIXTURE。","受影响流程:自动证据汇总 -> 真机人工确认;验证步骤:cd server && node --check src/scripts/check-wecom-approval-readability-gate.js;cd server && npx vitest run tests/unit/scripts/check-wecom-approval-readability-gate.test.js --reporter=verbose。","node|vitest","DONE","DONE","DONE","server/src/scripts/check-wecom-approval-readability-gate.js|server/package.json|server/tests/unit/scripts/check-wecom-approval-readability-gate.test.js","[""WREAD1-0""]","已新增 runner、npm script 和 focused 单测;测试覆盖历史 fixture artifact 不持久化但 WACP3R 详情证据可用时仍进入 PAUSED_MANUAL_CONFIRMATION。报告隐私边界不输出手机号、邮箱、token、secret、完整 userid、完整 template_id、完整 sp_no、表单值、附件、评论或原始 payload。回滚/清理:移除 runner、package script 和单测。"
|
||||
"WREAD1-2","本地门禁","执行合同、process gate、语法、Prisma、focused 单测、文档审计、skill 校验和 diff 门禁。","1) validate_issues_csv 通过;2) process baseline 通过;3) node --check 通过;4) Prisma validate 通过;5) focused vitest 通过;6) 文档审计、skill 校验、git diff --check 通过。","受影响流程:WREAD1 本地质量门禁;验证步骤:python3 /home/shunli/.codex/skills/plan/scripts/validate_issues_csv.py issues/2026-05-19_20-39-40-wecom-approval-readability-gate-v1.csv;node scripts/qa/check-process-contract.mjs --baseline --json;cd server && node --check src/scripts/check-wecom-approval-readability-gate.js;cd server && npm run prisma:validate;cd server && npx vitest run tests/unit/scripts/check-wecom-approval-readability-gate.test.js --reporter=verbose;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;git diff --check。","python3|node|npm|vitest","DONE","DONE","DONE","N/A","[""WREAD1-1""]","本地门禁均通过;audit-docs 仅保留既有 unregistered docs files warning。额外通过 bash scripts/check-minimal.sh。回滚/清理:撤回本轮代码和文档改动。"
|
||||
"WREAD1-3","公网只读汇总","本地门禁通过后,以 SYNC_ENV=0 部署,不同步本地 env;远端执行 check:wecom-approval-readability-gate -- --format=json,汇总现有样例证据并生成手工确认清单。","1) SYNC_ENV=0 部署通过;2) /ready 正常;3) 远端 runner 生成 summary.json;4) 若现有样例证据完整,结论为 PAUSED_MANUAL_CONFIRMATION;5) 若样例不可用,结论为 PARTIAL_READABILITY_NEEDS_FRESH_FIXTURE 并暂停问答。","受影响流程:公网证据汇总 -> 真机确认;验证步骤:SYNC_ENV=0 bash deploy/trialrun/deploy.sh;远端 npm run check:wecom-approval-readability-gate -- --format=json;curl -fsS https://oa.hyinda.com/ready。","bash|ssh|curl|npm","DONE","DONE","DONE","external","[""WREAD1-2""]","SYNC_ENV=0 部署通过,/ready 返回 ok=true。部署后容器 artifacts 重建,先只读复跑 WACP3R 生成 /app/artifacts/qa-flow/wecom-approval-compensation-poc-20260519T125515Z/summary.json,再执行 WREAD1 生成 /app/artifacts/qa-flow/wecom-approval-readability-gate-20260519T125532Z/summary.json,结论 PAUSED_MANUAL_CONFIRMATION。只读执行,不调用企微写接口,不写 OA 数据。"
|
||||
"WREAD1-4","事实源回写与人工问答","回写路线图、统一审批规约、请假考勤规约、试运行报告、文档索引、事实源矩阵和项目 skill;在最终答复中按当前状态、改了什么、测试结果和需要你确认什么提问。","1) 文档记录 WREAD1 自动汇总结论和报告路径;2) 未经人工真机确认不得写 PASS_READABLE;3) 若进入 PAUSED_MANUAL_CONFIRMATION,明确等待你反馈请假/补卡卡片、列表、详情和快捷按钮风险;4) 文档审计和 skill 校验通过。","受影响流程:WREAD1 事实源和人工验收;验证步骤: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 ""WREAD1|PAUSED_MANUAL_CONFIRMATION|PARTIAL_READABILITY_NEEDS_FRESH_FIXTURE|PASS_READABLE|误审批|可读性"" docs .agents/skills/hyb-oa-delivery/SKILL.md。","node|python3|rg|manual","DONE","DONE","DONE","docs|.agents/skills/hyb-oa-delivery/SKILL.md","[""WREAD1-3""]","事实源已记录 WREAD1 自动汇总 PAUSED_MANUAL_CONFIRMATION 和报告路径;最终答复继续按问答方式等待人工真机确认。回滚/清理:git revert 本轮提交;无数据清理。"
|
||||
"WREAD1-5","提交与后续准入","提交 WREAD1 自动门禁;若人工确认尚未完成,则提交记录为自动汇总和 PAUSED 状态,待你反馈后再回填最终模板级结论。","1) git diff --check 通过;2) 中文提交完成;3) 工作区干净;4) 最终答复明确 WADM1 只能在 WREAD1 模板级结论明确后进入。","受影响流程:WREAD1 -> WADM1 准入;验证步骤:git status --short;git commit -m 'WREAD1 准备企微审批可读性门禁'。","git","DONE","DONE","DONE","N/A","[""WREAD1-4""]","本提交只代表自动汇总部分和公网 PAUSED 状态;待用户真机反馈后再回填最终模板级结论。回滚/清理:git revert 本提交。"
|
||||
|
@@ -32,6 +32,7 @@
|
||||
"check:wecom-approval-fixtures": "node src/scripts/check-wecom-approval-fixtures.js",
|
||||
"check:wecom-approval-callback-poc": "node src/scripts/check-wecom-approval-callback-poc.js",
|
||||
"check:wecom-approval-compensation-poc": "node src/scripts/check-wecom-approval-compensation-poc.js",
|
||||
"check:wecom-approval-readability-gate": "node src/scripts/check-wecom-approval-readability-gate.js",
|
||||
"check:wecom-approval-poc": "node src/scripts/check-wecom-approval-poc.js",
|
||||
"check:wecom-runtime-ready": "node src/scripts/check-wecom-runtime-ready.js",
|
||||
"resolve:wecom-oa-users": "node src/scripts/resolve-wecom-oa-user-consistency.js",
|
||||
|
||||
@@ -0,0 +1,487 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
|
||||
const {
|
||||
APPROVAL_POC_TARGETS,
|
||||
maskApprovalIdentifier,
|
||||
} = require("../services/integrations/wecomApproval");
|
||||
|
||||
const DEFAULT_REPORT_ROOT = path.resolve(__dirname, "../../../artifacts/qa-flow");
|
||||
const READABILITY_STATUSES = new Set([
|
||||
"PASS_READABLE",
|
||||
"PASS_WITH_TEMPLATE_ADJUSTMENTS",
|
||||
"FAIL_KEEP_OA_PRIMARY_FOR_THIS_TEMPLATE",
|
||||
]);
|
||||
|
||||
const REPORT_PRIVACY =
|
||||
"readability status, manual checklist, evidence statuses and masked prefixes only; no phone/email/token/secret/full userid/full template_id/full sp_no/form values/attachments/comments/raw payload";
|
||||
|
||||
function timestamp() {
|
||||
return new Date().toISOString().replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z");
|
||||
}
|
||||
|
||||
function usage() {
|
||||
return `Usage: node src/scripts/check-wecom-approval-readability-gate.js [options]
|
||||
|
||||
QA-only 企业微信审批可读性与误审批防护门禁。
|
||||
默认只汇总现有 OA-POC 证据,不写 OA/企微数据,不创建模板或审批单。
|
||||
|
||||
Options:
|
||||
--format=text|json 输出格式,默认 text。
|
||||
--artifact-root=<path> QA 报告根目录,默认 artifacts/qa-flow。
|
||||
--report-dir=<path> 输出目录,默认 artifacts/qa-flow/wecom-approval-readability-gate-<timestamp>。
|
||||
--fixtures-report=<path> 指定 WAPC2FLIVE summary.json 或目录。
|
||||
--callback-report=<path> 指定 WCBK1 summary.json 或目录。
|
||||
--compensation-report=<path> 指定 WACP3R summary.json 或目录。
|
||||
--manual-result=<target>:<status> 录入人工结论,target 为 leave 或 missed_punch。
|
||||
--manual-note=<target>:<text> 录入脱敏人工备注,不写表单值、附件、评论或完整 ID。
|
||||
--confirmed-by=<employeeNo> 人工确认人,仅记录员工号。
|
||||
--help 显示帮助。
|
||||
|
||||
人工结论:
|
||||
PASS_READABLE
|
||||
PASS_WITH_TEMPLATE_ADJUSTMENTS
|
||||
FAIL_KEEP_OA_PRIMARY_FOR_THIS_TEMPLATE
|
||||
`;
|
||||
}
|
||||
|
||||
function toSafeString(value, maxLength = 500) {
|
||||
if (value == null) return "";
|
||||
return String(value).trim().slice(0, maxLength);
|
||||
}
|
||||
|
||||
function safeMessage(value) {
|
||||
return toSafeString(value, 500)
|
||||
.replace(/access_token=[^&\s]+/gi, "access_token=***")
|
||||
.replace(/[A-Za-z0-9_-]{24,}/g, "***")
|
||||
.slice(0, 240);
|
||||
}
|
||||
|
||||
function targetKeys() {
|
||||
return APPROVAL_POC_TARGETS.map((item) => item.key);
|
||||
}
|
||||
|
||||
function parseTargetPair(value, flagName) {
|
||||
const raw = toSafeString(value, 700);
|
||||
const separatorIndex = raw.indexOf(":");
|
||||
if (separatorIndex <= 0) {
|
||||
throw new Error(`${flagName} requires <target>:<value>`);
|
||||
}
|
||||
const target = raw.slice(0, separatorIndex).trim();
|
||||
const itemValue = raw.slice(separatorIndex + 1).trim();
|
||||
if (!targetKeys().includes(target)) {
|
||||
throw new Error(`${flagName} unsupported target: ${target}`);
|
||||
}
|
||||
if (!itemValue) {
|
||||
throw new Error(`${flagName} requires non-empty value`);
|
||||
}
|
||||
return { target, value: itemValue };
|
||||
}
|
||||
|
||||
function parseArgs(argv = process.argv.slice(2), env = process.env) {
|
||||
const options = {
|
||||
format: "text",
|
||||
artifactRoot: env.WECOM_APPROVAL_READABILITY_ARTIFACT_ROOT || DEFAULT_REPORT_ROOT,
|
||||
reportDir: env.WECOM_APPROVAL_READABILITY_REPORT_DIR
|
||||
|| path.join(DEFAULT_REPORT_ROOT, `wecom-approval-readability-gate-${timestamp()}`),
|
||||
reports: {
|
||||
fixtures: "",
|
||||
callback: "",
|
||||
compensation: "",
|
||||
},
|
||||
manualResults: {},
|
||||
manualNotes: {},
|
||||
confirmedBy: "",
|
||||
help: false,
|
||||
};
|
||||
|
||||
for (let index = 0; index < argv.length; index += 1) {
|
||||
const arg = argv[index];
|
||||
if (arg === "--help" || arg === "-h") {
|
||||
options.help = true;
|
||||
} else if (arg === "--format") {
|
||||
options.format = argv[index + 1] || options.format;
|
||||
index += 1;
|
||||
} else if (arg.startsWith("--format=")) {
|
||||
options.format = arg.slice("--format=".length);
|
||||
} else if (arg === "--artifact-root") {
|
||||
options.artifactRoot = argv[index + 1] || options.artifactRoot;
|
||||
index += 1;
|
||||
} else if (arg.startsWith("--artifact-root=")) {
|
||||
options.artifactRoot = arg.slice("--artifact-root=".length);
|
||||
} else if (arg === "--report-dir") {
|
||||
options.reportDir = argv[index + 1] || options.reportDir;
|
||||
index += 1;
|
||||
} else if (arg.startsWith("--report-dir=")) {
|
||||
options.reportDir = arg.slice("--report-dir=".length);
|
||||
} else if (arg === "--fixtures-report") {
|
||||
options.reports.fixtures = argv[index + 1] || "";
|
||||
index += 1;
|
||||
} else if (arg.startsWith("--fixtures-report=")) {
|
||||
options.reports.fixtures = arg.slice("--fixtures-report=".length);
|
||||
} else if (arg === "--callback-report") {
|
||||
options.reports.callback = argv[index + 1] || "";
|
||||
index += 1;
|
||||
} else if (arg.startsWith("--callback-report=")) {
|
||||
options.reports.callback = arg.slice("--callback-report=".length);
|
||||
} else if (arg === "--compensation-report") {
|
||||
options.reports.compensation = argv[index + 1] || "";
|
||||
index += 1;
|
||||
} else if (arg.startsWith("--compensation-report=")) {
|
||||
options.reports.compensation = arg.slice("--compensation-report=".length);
|
||||
} else if (arg === "--manual-result") {
|
||||
const pair = parseTargetPair(argv[index + 1], "--manual-result");
|
||||
options.manualResults[pair.target] = pair.value;
|
||||
index += 1;
|
||||
} else if (arg.startsWith("--manual-result=")) {
|
||||
const pair = parseTargetPair(arg.slice("--manual-result=".length), "--manual-result");
|
||||
options.manualResults[pair.target] = pair.value;
|
||||
} else if (arg === "--manual-note") {
|
||||
const pair = parseTargetPair(argv[index + 1], "--manual-note");
|
||||
options.manualNotes[pair.target] = pair.value;
|
||||
index += 1;
|
||||
} else if (arg.startsWith("--manual-note=")) {
|
||||
const pair = parseTargetPair(arg.slice("--manual-note=".length), "--manual-note");
|
||||
options.manualNotes[pair.target] = pair.value;
|
||||
} else if (arg === "--confirmed-by") {
|
||||
options.confirmedBy = toSafeString(argv[index + 1], 80);
|
||||
index += 1;
|
||||
} else if (arg.startsWith("--confirmed-by=")) {
|
||||
options.confirmedBy = toSafeString(arg.slice("--confirmed-by=".length), 80);
|
||||
} else {
|
||||
throw new Error(`Unsupported argument: ${arg}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (!["text", "json"].includes(options.format)) {
|
||||
throw new Error(`Unsupported --format: ${options.format}`);
|
||||
}
|
||||
for (const [target, value] of Object.entries(options.manualResults)) {
|
||||
if (!READABILITY_STATUSES.has(value)) {
|
||||
throw new Error(`Unsupported manual result for ${target}: ${value}`);
|
||||
}
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
function resolveSummaryPath(inputPath) {
|
||||
const value = toSafeString(inputPath, 1000);
|
||||
if (!value) return "";
|
||||
if (!fs.existsSync(value)) return "";
|
||||
const stat = fs.statSync(value);
|
||||
if (stat.isDirectory()) {
|
||||
const summaryPath = path.join(value, "summary.json");
|
||||
return fs.existsSync(summaryPath) ? summaryPath : "";
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function findLatestSummary(artifactRoot, prefix) {
|
||||
const root = toSafeString(artifactRoot, 1000);
|
||||
if (!root || !fs.existsSync(root)) return "";
|
||||
const entries = fs.readdirSync(root, { withFileTypes: true })
|
||||
.filter((entry) => entry.isDirectory() && entry.name.startsWith(prefix))
|
||||
.map((entry) => path.join(root, entry.name, "summary.json"))
|
||||
.filter((summaryPath) => fs.existsSync(summaryPath))
|
||||
.sort();
|
||||
return entries[entries.length - 1] || "";
|
||||
}
|
||||
|
||||
function readJsonFile(filePath) {
|
||||
const resolved = resolveSummaryPath(filePath);
|
||||
if (!resolved) return { ok: false, path: "", reason: "report_not_found", data: null };
|
||||
try {
|
||||
const data = JSON.parse(fs.readFileSync(resolved, "utf8"));
|
||||
return { ok: true, path: resolved, reason: null, data };
|
||||
} catch (error) {
|
||||
return { ok: false, path: resolved, reason: "report_parse_failed", message: safeMessage(error?.message || error), data: null };
|
||||
}
|
||||
}
|
||||
|
||||
function loadEvidenceReports(options, deps = {}) {
|
||||
const findLatestSummaryImpl = deps.findLatestSummaryImpl || findLatestSummary;
|
||||
const readJsonFileImpl = deps.readJsonFileImpl || readJsonFile;
|
||||
const paths = {
|
||||
fixtures: resolveSummaryPath(options.reports.fixtures)
|
||||
|| findLatestSummaryImpl(options.artifactRoot, "wecom-approval-fixtures-"),
|
||||
callback: resolveSummaryPath(options.reports.callback)
|
||||
|| findLatestSummaryImpl(options.artifactRoot, "wecom-approval-callback-poc-"),
|
||||
compensation: resolveSummaryPath(options.reports.compensation)
|
||||
|| findLatestSummaryImpl(options.artifactRoot, "wecom-approval-compensation-poc-"),
|
||||
};
|
||||
return {
|
||||
fixtures: readJsonFileImpl(paths.fixtures),
|
||||
callback: readJsonFileImpl(paths.callback),
|
||||
compensation: readJsonFileImpl(paths.compensation),
|
||||
};
|
||||
}
|
||||
|
||||
function sanitizePath(filePath) {
|
||||
return toSafeString(filePath, 1000) || null;
|
||||
}
|
||||
|
||||
function targetLabel(target) {
|
||||
return APPROVAL_POC_TARGETS.find((item) => item.key === target)?.label || target;
|
||||
}
|
||||
|
||||
function maskEvidencePrefix(value) {
|
||||
const raw = toSafeString(value, 160);
|
||||
if (!raw) return null;
|
||||
return raw.endsWith("***") ? raw : maskApprovalIdentifier(raw);
|
||||
}
|
||||
|
||||
function summarizeFixtureEvidence(readResult) {
|
||||
const report = readResult.data || {};
|
||||
const liveWrite = report.checks?.liveWrite || {};
|
||||
const autoRerun = report.checks?.autoRerun || {};
|
||||
const createdTemplatePrefixes = liveWrite.createdTemplatePrefixes || autoRerun.templateIdPrefixes || {};
|
||||
const submittedApprovalPrefixes = liveWrite.submittedApprovalPrefixes || autoRerun.spNoPrefixes || {};
|
||||
const targets = targetKeys().map((target) => ({
|
||||
target,
|
||||
label: targetLabel(target),
|
||||
templateIdPrefix: maskEvidencePrefix(createdTemplatePrefixes[target]),
|
||||
spNoPrefix: maskEvidencePrefix(submittedApprovalPrefixes[target]),
|
||||
templateAvailable: Boolean(createdTemplatePrefixes[target]),
|
||||
approvalAvailable: Boolean(submittedApprovalPrefixes[target]),
|
||||
}));
|
||||
const ok = readResult.ok
|
||||
&& report.status === "PASS_FIXTURE_CREATED"
|
||||
&& autoRerun.status === "OK"
|
||||
&& targets.every((item) => item.templateAvailable && item.approvalAvailable);
|
||||
return {
|
||||
id: "wapc2flive_fixture_evidence",
|
||||
status: ok ? "OK" : (readResult.ok ? "PARTIAL" : "MISSING"),
|
||||
reportPath: sanitizePath(readResult.path),
|
||||
sourceStatus: report.status || null,
|
||||
sourceConclusion: report.conclusion || null,
|
||||
autoRerunStatus: autoRerun.status || null,
|
||||
targets,
|
||||
reason: readResult.ok ? null : readResult.reason,
|
||||
};
|
||||
}
|
||||
|
||||
function summarizeCallbackEvidence(readResult) {
|
||||
const report = readResult.data || {};
|
||||
const eventEvidence = report.checks?.eventEvidence || {};
|
||||
const ok = readResult.ok && report.status === "PASS_CALLBACK_EVENT_CONFIRMED";
|
||||
return {
|
||||
id: "wcbk1_callback_evidence",
|
||||
status: ok ? "OK" : (readResult.ok ? "PARTIAL" : "MISSING"),
|
||||
reportPath: sanitizePath(readResult.path),
|
||||
sourceStatus: report.status || null,
|
||||
sourceConclusion: report.conclusion || null,
|
||||
eventCount: Number(eventEvidence.eventCount || 0),
|
||||
lastEvent: eventEvidence.lastEvent ? {
|
||||
approvalType: eventEvidence.lastEvent.approvalType || null,
|
||||
statusLabel: eventEvidence.lastEvent.statusLabel || null,
|
||||
spNoPrefix: eventEvidence.lastEvent.spNoPrefix || null,
|
||||
templateIdPrefix: eventEvidence.lastEvent.templateIdPrefix || null,
|
||||
recordedAt: eventEvidence.lastEvent.recordedAt || null,
|
||||
} : null,
|
||||
reason: readResult.ok ? null : readResult.reason,
|
||||
};
|
||||
}
|
||||
|
||||
function summarizeCompensationEvidence(readResult) {
|
||||
const report = readResult.data || {};
|
||||
const targetChecks = Array.isArray(report.checks?.targets) ? report.checks.targets : [];
|
||||
const targets = targetKeys().map((target) => {
|
||||
const check = targetChecks.find((item) => item.target === target || item.key === target) || {};
|
||||
const details = Array.isArray(check.details) ? check.details : [];
|
||||
return {
|
||||
target,
|
||||
label: targetLabel(target),
|
||||
status: check.status || null,
|
||||
detailCount: Number(check.detailCount || details.length || 0),
|
||||
statusSummary: check.statusSummary || null,
|
||||
detailSpNoPrefixes: details.map((item) => item.spNoPrefix).filter(Boolean).slice(0, 5),
|
||||
};
|
||||
});
|
||||
const ok = readResult.ok
|
||||
&& ["PASS_COMPENSATION_POC", "PASS_WITH_FINDINGS"].includes(report.status)
|
||||
&& targets.every((item) => item.status === "OK" && item.detailCount > 0);
|
||||
return {
|
||||
id: "wacp3r_compensation_evidence",
|
||||
status: ok ? "OK" : (readResult.ok ? "PARTIAL" : "MISSING"),
|
||||
reportPath: sanitizePath(readResult.path),
|
||||
sourceStatus: report.status || null,
|
||||
sourceConclusion: report.conclusion || null,
|
||||
templateDiscoveryStatus: report.checks?.templateDiscovery?.status || null,
|
||||
findings: (report.checks?.callbackCompare?.findings || []).map((item) => ({
|
||||
id: item.id,
|
||||
target: item.target || null,
|
||||
spNoPrefix: item.spNoPrefix || null,
|
||||
})),
|
||||
targets,
|
||||
reason: readResult.ok ? null : readResult.reason,
|
||||
};
|
||||
}
|
||||
|
||||
function buildManualChecklist() {
|
||||
return targetKeys().map((target) => ({
|
||||
target,
|
||||
label: targetLabel(target),
|
||||
questions: [
|
||||
"企微消息卡片是否能看出申请类型、申请人、时间和状态?",
|
||||
"企微审批列表是否能区分请假/补卡?",
|
||||
"详情页字段是否足够审批人判断?",
|
||||
"快捷同意/驳回是否容易误点?",
|
||||
"是否需要模板字段调整或回退 OA 主办?",
|
||||
],
|
||||
}));
|
||||
}
|
||||
|
||||
function sanitizeManualNote(value) {
|
||||
return safeMessage(value)
|
||||
.replace(/SP[A-Za-z0-9_-]{8,}/g, "SP***")
|
||||
.replace(/[A-Za-z0-9_-]{16,}/g, "***");
|
||||
}
|
||||
|
||||
function buildManualResults(options) {
|
||||
return targetKeys().map((target) => ({
|
||||
target,
|
||||
label: targetLabel(target),
|
||||
status: options.manualResults[target] || "PENDING_MANUAL_CONFIRMATION",
|
||||
note: options.manualNotes[target] ? sanitizeManualNote(options.manualNotes[target]) : null,
|
||||
}));
|
||||
}
|
||||
|
||||
function evaluateConclusion({ evidence, manualResults }) {
|
||||
const sampleEvidenceOk = evidence.compensation.status === "OK";
|
||||
if (!sampleEvidenceOk) {
|
||||
return {
|
||||
status: "PARTIAL_READABILITY_NEEDS_FRESH_FIXTURE",
|
||||
conclusion: "PARTIAL_READABILITY_NEEDS_FRESH_FIXTURE",
|
||||
};
|
||||
}
|
||||
if (manualResults.some((item) => item.status === "PENDING_MANUAL_CONFIRMATION")) {
|
||||
return {
|
||||
status: "PAUSED_MANUAL_CONFIRMATION",
|
||||
conclusion: "PAUSED_MANUAL_CONFIRMATION",
|
||||
};
|
||||
}
|
||||
if (manualResults.some((item) => item.status === "FAIL_KEEP_OA_PRIMARY_FOR_THIS_TEMPLATE")) {
|
||||
return {
|
||||
status: "FAIL_KEEP_OA_PRIMARY_FOR_THIS_TEMPLATE",
|
||||
conclusion: "FAIL_KEEP_OA_PRIMARY_FOR_THIS_TEMPLATE",
|
||||
};
|
||||
}
|
||||
if (manualResults.some((item) => item.status === "PASS_WITH_TEMPLATE_ADJUSTMENTS")) {
|
||||
return {
|
||||
status: "PASS_WITH_TEMPLATE_ADJUSTMENTS",
|
||||
conclusion: "PASS_WITH_TEMPLATE_ADJUSTMENTS",
|
||||
};
|
||||
}
|
||||
return {
|
||||
status: "PASS_READABLE",
|
||||
conclusion: "PASS_READABLE",
|
||||
};
|
||||
}
|
||||
|
||||
async function executeReadabilityGate(options, deps = {}) {
|
||||
const generatedAt = deps.generatedAt || new Date().toISOString();
|
||||
const reports = loadEvidenceReports(options, deps);
|
||||
const evidence = {
|
||||
fixtures: summarizeFixtureEvidence(reports.fixtures),
|
||||
callback: summarizeCallbackEvidence(reports.callback),
|
||||
compensation: summarizeCompensationEvidence(reports.compensation),
|
||||
};
|
||||
const manualResults = buildManualResults(options);
|
||||
const conclusion = evaluateConclusion({ evidence, manualResults });
|
||||
const sampleEvidenceOk = evidence.compensation.status === "OK";
|
||||
|
||||
return {
|
||||
mode: "qa_only",
|
||||
generatedAt,
|
||||
status: conclusion.status,
|
||||
conclusion: conclusion.conclusion,
|
||||
privacy: REPORT_PRIVACY,
|
||||
scope: {
|
||||
issue: "WREAD1",
|
||||
targets: targetKeys(),
|
||||
samplePolicy: "reuse existing OA-POC leave/missed_punch samples first; do not create new WeCom templates or approvals",
|
||||
writeBehavior: "read-only evidence summary only; no OA writes, no WeCom writes, no callback setup, no mirror import",
|
||||
deepMirror: "not_started",
|
||||
nextStage: conclusion.status === "PASS_READABLE" || conclusion.status === "PASS_WITH_TEMPLATE_ADJUSTMENTS"
|
||||
? "WADM1"
|
||||
: "manual_confirmation_or_fresh_fixture_decision",
|
||||
},
|
||||
checks: {
|
||||
evidence,
|
||||
sampleEvidence: {
|
||||
status: sampleEvidenceOk ? "OK" : "PARTIAL",
|
||||
reason: sampleEvidenceOk
|
||||
? "wacp3r_compensation_detail_evidence_available"
|
||||
: "existing_oa_poc_compensation_detail_evidence_not_complete",
|
||||
fixtureEvidenceStatus: evidence.fixtures.status,
|
||||
callbackEvidenceStatus: evidence.callback.status,
|
||||
},
|
||||
manualChecklist: buildManualChecklist(),
|
||||
manualResults,
|
||||
},
|
||||
confirmedBy: options.confirmedBy || null,
|
||||
configGaps: sampleEvidenceOk ? [] : [{
|
||||
id: "readability_existing_sample",
|
||||
status: "NEEDS_CONFIG",
|
||||
reason: "existing_compensation_detail_evidence_not_complete_or_not_found",
|
||||
}],
|
||||
mainBlockers: [],
|
||||
};
|
||||
}
|
||||
|
||||
function ensureReportDir(reportDir) {
|
||||
fs.mkdirSync(reportDir, { recursive: true });
|
||||
}
|
||||
|
||||
function writeReport(reportDir, report) {
|
||||
ensureReportDir(reportDir);
|
||||
const filePath = path.join(reportDir, "summary.json");
|
||||
fs.writeFileSync(filePath, `${JSON.stringify(report, null, 2)}\n`, "utf8");
|
||||
return filePath;
|
||||
}
|
||||
|
||||
function printText(report, reportPath) {
|
||||
process.stdout.write(`[wecom-approval-readability-gate] status=${report.status} conclusion=${report.conclusion}\n`);
|
||||
process.stdout.write(`[wecom-approval-readability-gate] report=${reportPath}\n`);
|
||||
process.stdout.write(`[wecom-approval-readability-gate] privacy=${report.privacy}\n`);
|
||||
process.stdout.write(`[wecom-approval-readability-gate] targets=${report.scope.targets.join(",")}\n`);
|
||||
if (report.configGaps.length) {
|
||||
process.stdout.write(`[wecom-approval-readability-gate] configGaps=${JSON.stringify(report.configGaps)}\n`);
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const options = parseArgs(process.argv.slice(2));
|
||||
if (options.help) {
|
||||
process.stdout.write(usage());
|
||||
return;
|
||||
}
|
||||
const report = await executeReadabilityGate(options);
|
||||
const reportPath = writeReport(options.reportDir, report);
|
||||
if (options.format === "json") {
|
||||
process.stdout.write(`${JSON.stringify({ ...report, reportPath }, null, 2)}\n`);
|
||||
} else {
|
||||
printText(report, reportPath);
|
||||
}
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
main().catch((error) => {
|
||||
process.stderr.write(`[wecom-approval-readability-gate][FAIL] ${safeMessage(error?.message || error)}\n`);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
buildManualChecklist,
|
||||
buildManualResults,
|
||||
evaluateConclusion,
|
||||
executeReadabilityGate,
|
||||
findLatestSummary,
|
||||
parseArgs,
|
||||
summarizeCompensationEvidence,
|
||||
summarizeFixtureEvidence,
|
||||
writeReport,
|
||||
};
|
||||
@@ -0,0 +1,220 @@
|
||||
const fs = require("node:fs");
|
||||
const os = require("node:os");
|
||||
const path = require("node:path");
|
||||
|
||||
const {
|
||||
executeReadabilityGate,
|
||||
parseArgs,
|
||||
} = require("../../../src/scripts/check-wecom-approval-readability-gate");
|
||||
|
||||
const SENSITIVE_TEMPLATE_ID = "tpl_readability_full_identifier_must_not_leak";
|
||||
const SENSITIVE_SP_NO = "SPREADABILITYFULLIDENTIFIERMUSTNOTLEAK";
|
||||
const SENSITIVE_USER_ID = "readability_userid_full_must_not_leak";
|
||||
const SENSITIVE_FORM_VALUE = "这里是表单值,不应出现在报告里";
|
||||
|
||||
function tempDir() {
|
||||
return fs.mkdtempSync(path.join(os.tmpdir(), "wread1-test-"));
|
||||
}
|
||||
|
||||
function writeSummary(root, dirname, payload) {
|
||||
const dir = path.join(root, dirname);
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
const filePath = path.join(dir, "summary.json");
|
||||
fs.writeFileSync(filePath, JSON.stringify(payload, null, 2));
|
||||
return filePath;
|
||||
}
|
||||
|
||||
function writeOkEvidence(root) {
|
||||
writeSummary(root, "wecom-approval-fixtures-20260519T053400Z", {
|
||||
status: "PASS_FIXTURE_CREATED",
|
||||
conclusion: "PASS_FIXTURE_CREATED",
|
||||
checks: {
|
||||
liveWrite: {
|
||||
createdTemplatePrefixes: {
|
||||
leave: "tpl-leav***",
|
||||
missed_punch: "tpl-miss***",
|
||||
},
|
||||
submittedApprovalPrefixes: {
|
||||
leave: "SPLEAVE***",
|
||||
missed_punch: "SPMISS***",
|
||||
},
|
||||
},
|
||||
autoRerun: {
|
||||
status: "OK",
|
||||
},
|
||||
},
|
||||
rawShouldNotCopy: SENSITIVE_TEMPLATE_ID,
|
||||
});
|
||||
writeSummary(root, "wecom-approval-callback-poc-20260519T092648Z", {
|
||||
status: "PASS_CALLBACK_EVENT_CONFIRMED",
|
||||
conclusion: "PASS_CALLBACK_EVENT_CONFIRMED",
|
||||
checks: {
|
||||
eventEvidence: {
|
||||
eventCount: 1,
|
||||
lastEvent: {
|
||||
approvalType: "missed_punch",
|
||||
statusLabel: "已通过",
|
||||
spNoPrefix: "SPMISS***",
|
||||
templateIdPrefix: "tpl-miss***",
|
||||
recordedAt: "2026-05-19T09:26:48.000Z",
|
||||
rawShouldNotCopy: SENSITIVE_SP_NO,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
writeSummary(root, "wecom-approval-compensation-poc-20260519T115944Z", {
|
||||
status: "PASS_WITH_FINDINGS",
|
||||
conclusion: "PASS_WITH_FINDINGS",
|
||||
checks: {
|
||||
templateDiscovery: { status: "OK", discovered: { leave: SENSITIVE_TEMPLATE_ID } },
|
||||
targets: [
|
||||
{
|
||||
target: "leave",
|
||||
status: "OK",
|
||||
detailCount: 1,
|
||||
statusSummary: { "审批中": 1 },
|
||||
details: [{ spNoPrefix: "SPLEAVE***", rawShouldNotCopy: SENSITIVE_FORM_VALUE }],
|
||||
},
|
||||
{
|
||||
target: "missed_punch",
|
||||
status: "OK",
|
||||
detailCount: 1,
|
||||
statusSummary: { "已通过": 1 },
|
||||
details: [{ spNoPrefix: "SPMISS***" }],
|
||||
},
|
||||
],
|
||||
callbackCompare: {
|
||||
findings: [
|
||||
{ id: "callback_missing_for_pulled_detail", target: "leave", spNoPrefix: "SPLEAVE***" },
|
||||
],
|
||||
},
|
||||
},
|
||||
rawUserId: SENSITIVE_USER_ID,
|
||||
});
|
||||
}
|
||||
|
||||
describe("check-wecom-approval-readability-gate", () => {
|
||||
it("parses manual results and report options", () => {
|
||||
const options = parseArgs([
|
||||
"--format=json",
|
||||
"--artifact-root=/tmp/wread1-artifacts",
|
||||
"--manual-result=leave:PASS_READABLE",
|
||||
"--manual-result=missed_punch:PASS_WITH_TEMPLATE_ADJUSTMENTS",
|
||||
"--manual-note=leave:卡片可读",
|
||||
"--confirmed-by=wangshunli",
|
||||
]);
|
||||
|
||||
expect(options.format).toBe("json");
|
||||
expect(options.artifactRoot).toBe("/tmp/wread1-artifacts");
|
||||
expect(options.manualResults).toEqual({
|
||||
leave: "PASS_READABLE",
|
||||
missed_punch: "PASS_WITH_TEMPLATE_ADJUSTMENTS",
|
||||
});
|
||||
expect(options.manualNotes.leave).toBe("卡片可读");
|
||||
expect(options.confirmedBy).toBe("wangshunli");
|
||||
expect(() => parseArgs(["--manual-result=leave:PASS"])).toThrow(/Unsupported manual result/);
|
||||
});
|
||||
|
||||
it("pauses for manual confirmation when existing evidence is available", async () => {
|
||||
const root = tempDir();
|
||||
writeOkEvidence(root);
|
||||
|
||||
const report = await executeReadabilityGate({
|
||||
...parseArgs(["--artifact-root", root]),
|
||||
reportDir: path.join(root, "out"),
|
||||
}, { generatedAt: "2026-05-20T00:00:00.000Z" });
|
||||
|
||||
expect(report.status).toBe("PAUSED_MANUAL_CONFIRMATION");
|
||||
expect(report.checks.sampleEvidence.status).toBe("OK");
|
||||
expect(report.checks.evidence.fixtures.status).toBe("OK");
|
||||
expect(report.checks.evidence.compensation.status).toBe("OK");
|
||||
expect(report.checks.manualResults.map((item) => item.status)).toEqual([
|
||||
"PENDING_MANUAL_CONFIRMATION",
|
||||
"PENDING_MANUAL_CONFIRMATION",
|
||||
]);
|
||||
});
|
||||
|
||||
it("accepts WACP3R compensation detail evidence when historical fixture artifacts are not persisted", async () => {
|
||||
const root = tempDir();
|
||||
writeSummary(root, "wecom-approval-compensation-poc-20260519T115944Z", {
|
||||
status: "PASS_WITH_FINDINGS",
|
||||
conclusion: "PASS_WITH_FINDINGS",
|
||||
checks: {
|
||||
templateDiscovery: { status: "OK" },
|
||||
targets: [
|
||||
{
|
||||
target: "leave",
|
||||
status: "OK",
|
||||
details: [{ spNoPrefix: "SPLEAVE***" }],
|
||||
},
|
||||
{
|
||||
target: "missed_punch",
|
||||
status: "OK",
|
||||
details: [{ spNoPrefix: "SPMISS***" }],
|
||||
},
|
||||
],
|
||||
callbackCompare: { findings: [] },
|
||||
},
|
||||
});
|
||||
|
||||
const report = await executeReadabilityGate(parseArgs(["--artifact-root", root]), {
|
||||
generatedAt: "2026-05-20T00:00:00.000Z",
|
||||
});
|
||||
|
||||
expect(report.status).toBe("PAUSED_MANUAL_CONFIRMATION");
|
||||
expect(report.checks.sampleEvidence.status).toBe("OK");
|
||||
expect(report.checks.sampleEvidence.reason).toBe("wacp3r_compensation_detail_evidence_available");
|
||||
expect(report.checks.evidence.fixtures.status).toBe("MISSING");
|
||||
});
|
||||
|
||||
it("returns PASS_WITH_TEMPLATE_ADJUSTMENTS when one template needs changes", async () => {
|
||||
const root = tempDir();
|
||||
writeOkEvidence(root);
|
||||
|
||||
const report = await executeReadabilityGate(parseArgs([
|
||||
"--artifact-root", root,
|
||||
"--manual-result=leave:PASS_READABLE",
|
||||
"--manual-result=missed_punch:PASS_WITH_TEMPLATE_ADJUSTMENTS",
|
||||
"--manual-note=missed_punch:快捷按钮容易误点,需要提醒审批人点详情",
|
||||
"--confirmed-by=wangshunli",
|
||||
]), { generatedAt: "2026-05-20T00:00:00.000Z" });
|
||||
|
||||
expect(report.status).toBe("PASS_WITH_TEMPLATE_ADJUSTMENTS");
|
||||
expect(report.confirmedBy).toBe("wangshunli");
|
||||
expect(report.checks.manualResults.find((item) => item.target === "missed_punch").note).toContain("快捷按钮");
|
||||
});
|
||||
|
||||
it("asks for a fresh fixture when existing sample evidence is missing", async () => {
|
||||
const root = tempDir();
|
||||
writeSummary(root, "wecom-approval-compensation-poc-20260519T115944Z", {
|
||||
status: "PASS_WITH_FINDINGS",
|
||||
checks: { targets: [] },
|
||||
});
|
||||
|
||||
const report = await executeReadabilityGate(parseArgs(["--artifact-root", root]), {
|
||||
generatedAt: "2026-05-20T00:00:00.000Z",
|
||||
});
|
||||
|
||||
expect(report.status).toBe("PARTIAL_READABILITY_NEEDS_FRESH_FIXTURE");
|
||||
expect(report.configGaps.map((item) => item.id)).toContain("readability_existing_sample");
|
||||
});
|
||||
|
||||
it("does not leak full template, sp_no, userid or form values", async () => {
|
||||
const root = tempDir();
|
||||
writeOkEvidence(root);
|
||||
|
||||
const report = await executeReadabilityGate(parseArgs([
|
||||
"--artifact-root", root,
|
||||
"--manual-result=leave:PASS_READABLE",
|
||||
"--manual-result=missed_punch:PASS_READABLE",
|
||||
"--manual-note=leave:不要泄露 SPREADABILITYFULLIDENTIFIERMUSTNOTLEAK",
|
||||
]), { generatedAt: "2026-05-20T00:00:00.000Z" });
|
||||
|
||||
const serialized = JSON.stringify(report);
|
||||
expect(report.status).toBe("PASS_READABLE");
|
||||
expect(serialized).not.toContain(SENSITIVE_TEMPLATE_ID);
|
||||
expect(serialized).not.toContain(SENSITIVE_SP_NO);
|
||||
expect(serialized).not.toContain(SENSITIVE_USER_ID);
|
||||
expect(serialized).not.toContain(SENSITIVE_FORM_VALUE);
|
||||
});
|
||||
});
|
||||
在新工单中引用
屏蔽一个用户