From 53db11e142d7c6664d9c5c4393d94b3f9aae2d26 Mon Sep 17 00:00:00 2001 From: shunli Date: Sat, 23 May 2026 17:11:55 +0800 Subject: [PATCH] =?UTF-8?q?OALAUNCH1=20=E5=9B=BA=E5=8C=96=E6=9C=80?= =?UTF-8?q?=E7=BB=88=E5=90=AF=E7=94=A8=E9=97=A8=E7=A6=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .agents/skills/hyb-oa-delivery/SKILL.md | 1 + docs/trialrun/人工试运行报告.md | 1 + docs/员工快速上手.md | 4 +- docs/文档事实源状态矩阵.md | 13 +- docs/文档索引.md | 3 +- docs/管理员手册.md | 42 ++ ..._16-49-39-oa-final-launch-readiness-v1.csv | 10 + scripts/qa/run-oa-final-launch-readiness.sh | 481 ++++++++++++++++++ 8 files changed, 547 insertions(+), 8 deletions(-) create mode 100644 issues/2026-05-23_16-49-39-oa-final-launch-readiness-v1.csv create mode 100755 scripts/qa/run-oa-final-launch-readiness.sh diff --git a/.agents/skills/hyb-oa-delivery/SKILL.md b/.agents/skills/hyb-oa-delivery/SKILL.md index 70ba07d2..e04668d8 100644 --- a/.agents/skills/hyb-oa-delivery/SKILL.md +++ b/.agents/skills/hyb-oa-delivery/SKILL.md @@ -28,6 +28,7 @@ Read only what is relevant: - 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`. WREAD1F then fixed duplicate OA-POC template names by adding timestamp uniqueness and created fresh leave/missed-punch approvals at `/app/artifacts/qa-flow/wecom-approval-fixtures-20260519T131958Z/summary.json`; final WREAD1 manual report `/app/artifacts/qa-flow/wecom-approval-readability-gate-20260519T133628Z/summary.json` is `PASS_READABLE`. This clears leave/missed-punch readability only; mirror data model, minimum archive, emergency mode, writeback, RuleOps, and outing/overtime sample decisions remain later gates. - ASSET1 is the current unified asset registry lane after OAOPS1: reuse the existing asset module for fixed assets plus electronic document/system/repository/backup asset metadata. Number families are `FA-YYYY-NNNNNN`, `EA-DOC-YYYY-NNNNNN`, `EA-SYS-YYYY-NNNNNN`, `EA-REPO-YYYY-NNNNNN`, and `EA-BKP-YYYY-NNNNNN`; `VER-NNNNN` is historical lookup only. `EA-ACC` is deferred: never store passwords, token, Secret, private keys, source bundles, full config, confidential document bodies, complete local paths, sensitive quotes, bank statement originals, or raw invoice images in OA, repo docs, chat, or reports. Git remains the source of truth for code; OA only stores repository metadata and responsibility. Historical company directory imports may only use redacted metadata from `00_保密索引`, default dry-run, with sensitive text/path guards. - OSMOKE1 is the current OA-only smoke account governance lane after ASSET1D/OAOPS1 blockers: do not request more Enterprise WeChat accounts just to test OA-primary reimbursement/assets/finance/AP/contracts/permissions. Enterprise WeChat smoke accounts remain for HR/attendance evidence; admin/finance/hr/ceo OA regression accounts are OA-only smoke accounts and may be active in OA without WeCom binding. Use `npm run repair:oa-only-smoke` from `server/` for dry-run first; apply requires `--apply` plus `OA_ONLY_SMOKE_REPAIR_APPLY=1`, must not reset passwords, print secrets, affect real employees, or write WeCom. `audit-wecom-oa-user-consistency` classifies env-listed, explicit allowlisted, or `SMOKE*`/`smoke-*`/test-named OA-only smoke accounts as `oaOnlySmokeExempted` so they are not archived as missing in WeCom; `run-oa-real-use-daily.sh` includes OA-only smoke readiness before asset/finance UI automation. +- OALAUNCH1 is the current final-enable gate after OAOPS1/ASSET1D/OSMOKE1: do not treat ASSET1D completion or `PASS_OA_REAL_USE_DAILY_READY` as final launch approval. Use `scripts/qa/run-oa-final-launch-readiness.sh` to aggregate evidence only; it must not create business data, execute payments, write WeCom, reset/restore the production DB, print secrets, or mark final launch ready without real business closeout, 7-day stabilization, permission matrix acceptance, backup/restore drill evidence, data-governance/training acceptance, and Day 7 Go/No-Go. Valid conclusions are `PASS_OA_LAUNCH_READY`, `PASS_WITH_FINDINGS`, `PAUSED_MANUAL_CONFIRMATION`, and `SKIP/BLOCKED_BY_EXTERNAL_CONFIG`. Permission or backup/restore fixes require read-only reviewer review before launch acceptance. - 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. WARCH1 now supplies the minimum leave/missed-punch archive implementation, WARCH2 supplies the formal read-only history query surface, and WROAD2 supplies controlled ops daily checks. Do not start deep mirror until WROAD2-CLOSEOUT, WROAD2-BIZ1, WAPPR1, WRULE1, WAUTO1/WEXT1LD, WFULLSMOKE1, WTCLOSE2, WEXT1-FORMAL, WREAL1 question gates or explicit approval, and the mirror stage's own contract are complete. - 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`. diff --git a/docs/trialrun/人工试运行报告.md b/docs/trialrun/人工试运行报告.md index ae2e6315..c04ee3f5 100644 --- a/docs/trialrun/人工试运行报告.md +++ b/docs/trialrun/人工试运行报告.md @@ -10,6 +10,7 @@ DCL7 已删除本文件中 R70-R91 的长流水历史,避免旧环境、旧开 - R6 首轮小范围试运行已通过;范围是报销、请假、采购、审批、企业微信入口/通知、财务付款/导出。 - 2026-05-22 OAOPS1 已切换为当前主线:企微打卡、补卡、外出、请假等 HR/考勤先冻结在企业微信主办 + OA 留存/审计完成态;OA 系统进入 7 天小团队全员真实使用稳定化。公网 OA 允许录入真实业务数据,首批覆盖报销/PDF OCR、审批、采购/费用、合同台账、项目、付款批次、银行回单、AP 台账、代账交接、移动/企微入口和审计;新增 `scripts/qa/run-oa-real-use-daily.sh` 做每日只读巡检。阻断即修,非阻断进入反馈池;图片 OCR、复杂 BPM、预算、ERP、银行流水导入、真实支付直连、企微 HR 深镜像、应急接管、恢复回写和 RuleOps 继续冻结。 - 2026-05-23 OSMOKE1 固定 OA-only smoke 账号治理口径:企微测试账号有限,不再为了 OA 全域回归额外申请企业微信账号;admin、finance、HR、CEO 等公网 OA 回归账号只作为 OA-only smoke 使用,允许不绑定企微。企微/OA 一致性审计新增 `oaOnlySmokeExempted` 分类,命中条件限于私有 env、显式员工号 allowlist 或 `SMOKE*`/`smoke-*`/测试命名,避免再次把这些账号当作缺企微账号归档;账号恢复必须先 dry-run,真实 apply 需要 `OA_ONLY_SMOKE_REPAIR_APPLY=1`,且不打印密码、token、Secret 或完整标识。 +- 2026-05-23 OALAUNCH1 建立最终启用前收口门禁:ASSET1D 已完成资产台账公网试录和 3 条脱敏 `EA-DOC` 受控导入,OAOPS1 自动巡检已有 `PASS_OA_REAL_USE_DAILY_READY` 证据,但最终正式启用仍需补齐真实业务闭环、7 天稳定化、权限矩阵、备份恢复演练、数据治理培训和 Day 7 Go/No-Go。新增 `scripts/qa/run-oa-final-launch-readiness.sh` 作为 QA-only 汇总脚本;未达到 `PASS_OA_LAUNCH_READY` 前,不得把自动巡检通过误读为最终启用完成。 - PC6、PA6、FL6、AP6、RI6、LP6、ME6 自动化、QG6、DG7/MRF7、MIA7 已完成相应收口。 - 2026-05-13 用户完成 iPhone 12、iPhone 14、Redmi K60 企业微信代表性真机主流程复测,`ME6-3` 已按主流程门禁关闭;iPhone 12 图文上下间距偏紧按非阻断观察项记录。 - MIF7-1..7 与 MIF7T 已完成本地整改、公网部署、公网匿名复检和带凭据 live smoke/OCR 复检;2026-05-13 已将包含 `cf2bba0` 的 MIF7T 代码部署到 `https://oa.hyinda.com`。 diff --git a/docs/员工快速上手.md b/docs/员工快速上手.md index 2b8a9838..30a34bf8 100644 --- a/docs/员工快速上手.md +++ b/docs/员工快速上手.md @@ -1,6 +1,6 @@ # hyb-oa 员工快速上手(10 人以下团队版) -**当前状态(2026-05-22 / OAOPS1)**:公网正式地址为 `https://oa.hyinda.com`。系统开发完成门禁结论为 `GO_TO_USE`,当前进入 7 天小团队全员真实使用稳定化。企微打卡、补卡、外出、请假先按企业微信主办,OA 做留存、历史、审计和后续兜底;采购、合同、项目、报销、付款和财务资料仍由 OA 主办。OAOPS1 允许真实业务数据进入公网 OA 并按审计保留,但涉密合同正文、敏感报价、成本细节、银行流水原件和未脱敏票面不要上传到 OA、企业微信、仓库或聊天。 +**当前状态(2026-05-23 / OALAUNCH1)**:公网正式地址为 `https://oa.hyinda.com`。系统已进入最终启用前收口:OAOPS1 继续 7 天小团队真实使用稳定化,OALAUNCH1 负责最终 Go/No-Go 门禁。企微打卡、补卡、外出、请假先按企业微信主办,OA 做留存、历史、审计和后续兜底;采购、合同、项目、报销、付款、资产和财务资料仍由 OA 主办。当前可以按管理员安排录入真实业务数据,但在 `PASS_OA_LAUNCH_READY` 前仍属于收口期;涉密合同正文、敏感报价、成本细节和银行流水原件不要上传。必要的真实发票 PDF 只通过 OA 报销入口上传,并受权限和审计保护,不进入企业微信、仓库、聊天或自动化报告。 ## 1. 你每周主要会用到什么 - `Portal`:看公告、我的待办、我的资产、本周周报入口。 @@ -45,6 +45,7 @@ - 发票/OCR 正式入口保持 PDF-only;图片发票不作为当前正式 OCR 能力,图片只按普通附件处理。 - 如果报销已付款,系统会显示付款状态;银行实际付款由出纳在线下银行渠道完成。 - OAOPS1 期间请用真实业务数据完成报销、采购/费用、合同台账和项目关联;遇到提交、审批、付款、导出或权限阻断时,记录页面、步骤和影响等级,让管理员当天建修复合同或暂停对应流程。 +- OALAUNCH1 收口期内,请把真实业务问题按“阻断 / 非阻断 / 建议”告诉管理员;不要只在聊天里说“系统有问题”。阻断包括无法提交、无法审批、付款/AP 无法闭环、越权可见、合同或资产敏感信息泄露、备份恢复或导出证据缺失。 ## 7. 入职 / 离职时你会看到什么 - 入职时 HR 会优先在企业微信/人事助手维护人员资料,OA 后续同步生成账号、角色和绑定。 @@ -59,6 +60,7 @@ - 企业微信消息只会显示脱敏摘要和 OA 链接;采购单附件、合同正文、报价、发票、银行回单和代账资料不要上传到企业微信原生审批或企业微信附件。 - 如果 OA 页面标识为“企微镜像”,表示用于查看同步状态、历史和审计;如果标识为“OA 应急接管”,只有管理员明确通知后才在 OA 提交 HR/考勤应急申请。 - 首周遇到问题时,记录角色、页面、操作步骤和影响,不要在截图或文字里包含密码、完整票面、银行回单或敏感报价。 +- 最终启用前 7 天不新增可选功能;你看到的改动应主要是 blocker 修复、权限修复、操作说明补充或真实业务流程收口。 ## 9. 从企业微信进入 - 如果公司启用了企业微信工作台入口,优先从企业微信打开海音达工作台。 diff --git a/docs/文档事实源状态矩阵.md b/docs/文档事实源状态矩阵.md index baa3f980..4a0b1ac2 100644 --- a/docs/文档事实源状态矩阵.md +++ b/docs/文档事实源状态矩阵.md @@ -111,6 +111,7 @@ | `OAOPS1` | `issues/2026-05-22_20-09-22-oa-real-use-stabilization-v1.csv` | `CURRENT` | 当前 OA 主办域真实使用稳定化合同:企微 HR/考勤先冻结在企微主办 + OA 留存/审计完成态;公网 OA 进入 7 天小团队全员真实使用,允许真实业务数据,覆盖报销/PDF OCR、审批、采购/费用、合同台账、项目、付款批次、银行回单、AP 台账、代账交接、移动/企微入口和审计;新增 `scripts/qa/run-oa-real-use-daily.sh` 默认只读巡检,真实业务写入由人工完成;P2 冻结项继续冻结 | | `ASSET1` | `issues/2026-05-22_21-18-33-oa-asset-governance-v1.csv` | `CURRENT` | 当前统一资产台账 V1 合同:复用现有资产模块统一管理固定资产和电子资产索引;编号族为 `FA/EA-DOC/EA-SYS/EA-REPO/EA-BKP`,`EA-ACC` 暂缓;代码仓库由 Git 管源码,OA 只登记元数据;历史公司目录只允许脱敏索引 dry-run 或受控写入 | | `OSMOKE1` | `issues/2026-05-23_13-59-52-oa-only-smoke-account-governance-v1.csv` | `CURRENT` | 当前 OA-only smoke 账号治理合同:企微账号有限时不再为 OA 全域自动化额外申请企微账号;admin/finance/hr/ceo 等回归账号作为 OA-only smoke,可不绑定企微;新增修复脚本、`oaOnlySmokeExempted` 审计豁免和公网角色 readiness 检查,豁免命中限于私有 env、显式员工号 allowlist 或 `SMOKE*`/`smoke-*`/测试命名;默认 dry-run,真实修复需 `OA_ONLY_SMOKE_REPAIR_APPLY=1` | +| `OALAUNCH1` | `issues/2026-05-23_16-49-39-oa-final-launch-readiness-v1.csv` | `CURRENT` | 当前最终启用前收口与 Go/No-Go 门禁合同:在 OAOPS1/ASSET1D/OSMOKE1 之上增加真实业务闭环、7 天稳定化、权限矩阵、备份恢复演练、数据治理培训、运营支持和 Day 7 人工决策;新增 `scripts/qa/run-oa-final-launch-readiness.sh` 默认只读汇总证据;未达到 `PASS_OA_LAUNCH_READY` 前不得宣布最终正式启用完成 | | `WREAL1-PREP` | `issues/2026-05-20_14-35-42-wecom-real-trial-prep-v1.csv` | `CURRENT` | 真实小范围试用准备合同:只固化候选员工、非自审审批人、回滚联系人、允许流程、暂停条件和验收标准;当前不启用真实员工,真实试用必须等请假/补卡/外出/加班四类测试号证据、WAPPR1 审批规则门禁和 WFULLSMOKE1 全域主路径可解释通过后再问 | 旧 Issue CSV 保留为历史审计证据:不删除、不批量回写、不移动到 `docs/archive/` 或其他归档目录。新连续合同如替代旧合同,只在新合同 `Notes` 或文档索引中说明 supersedes。 @@ -119,8 +120,8 @@ | 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,WPROC2,WAPC2F,WAPC2FLIVE,WCBK1,WEGOV1,WARCH1,WARCH2,WROAD2,WARCH3,WCHECK1,WEXT1,WOPS1,WROAD2C,WROAD2B,WAPPR1,WRULE1,WAUTO1,WEXT1LD,WFULLSMOKE1,WTCLOSE2,WREAL1P,OAOPS1,ASSET1,OSMOKE1` | `rg` | none | 2026-05-22 | `node scripts/qa/audit-docs.mjs --check-registry` | medium | 判断当前该读什么;OAOPS1/ASSET1/OSMOKE1 后优先读取 OA 主办域真实使用稳定化、统一资产台账、OA-only smoke 账号治理、每日巡检和 P2 冻结口径 | -| `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,WAPC2F,WAPC2FLIVE,WCBK1,WEGOV1,WARCH1,WARCH2,WROAD2,WARCH3,WCHECK1,WEXT1,WOPS1,WROAD2C,WROAD2B,WAPPR1,WRULE1,WAUTO1,WEXT1LD,WFULLSMOKE1,WTCLOSE2,WREAL1P,OAOPS1,ASSET1,OSMOKE1` | `audit-docs` | none | 2026-05-22 | `audit-docs --check-registry` | medium | 判断文档权威性;登记当前 OAOPS1 小团队全员真实使用稳定化、ASSET1 统一资产台账、OSMOKE1 账号治理、每日巡检、阻断即修和冻结项事实 | +| `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,WAPC2F,WAPC2FLIVE,WCBK1,WEGOV1,WARCH1,WARCH2,WROAD2,WARCH3,WCHECK1,WEXT1,WOPS1,WROAD2C,WROAD2B,WAPPR1,WRULE1,WAUTO1,WEXT1LD,WFULLSMOKE1,WTCLOSE2,WREAL1P,OAOPS1,ASSET1,OSMOKE1,OALAUNCH1` | `rg` | none | 2026-05-23 | `node scripts/qa/audit-docs.mjs --check-registry` | medium | 判断当前该读什么;OAOPS1/ASSET1/OSMOKE1 后优先读取 OALAUNCH1 最终启用门禁、OA 主办域真实使用稳定化、统一资产台账、OA-only smoke 账号治理、每日巡检、备份恢复和 P2 冻结口径 | +| `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,WAPC2F,WAPC2FLIVE,WCBK1,WEGOV1,WARCH1,WARCH2,WROAD2,WARCH3,WCHECK1,WEXT1,WOPS1,WROAD2C,WROAD2B,WAPPR1,WRULE1,WAUTO1,WEXT1LD,WFULLSMOKE1,WTCLOSE2,WREAL1P,OAOPS1,ASSET1,OSMOKE1,OALAUNCH1` | `audit-docs` | none | 2026-05-23 | `audit-docs --check-registry` | medium | 判断文档权威性;登记当前 OALAUNCH1 最终启用 Go/No-Go、OAOPS1 小团队全员真实使用稳定化、ASSET1 统一资产台账、OSMOKE1 账号治理、每日巡检、阻断即修和冻结项事实 | | `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,WARCH1,WARCH2,WROAD2,WARCH3,WCHECK1,WEXT1` | Issue CSV | `docs/requirements-specification.md` | 2026-05-20 | `rg` | high | WARCH3/WCHECK1/WEXT1/WOPS1 后定义 HR/考勤企微主办、OA 自建留存、受控测试域、只读历史查询、自动巡检、打卡摘要、外出/加班 dry-run、WEXT1-FORMAL、WREAL1 前置和深度镜像边界;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 应急接管 | @@ -130,18 +131,18 @@ | `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,WREAD1,WREAD1F,WADM1,WARCH1,WARCH2,WROAD2,WARCH3,WCHECK1,WEXT1,WOPS1,WROAD2C,WROAD2B,WAPPR1,WRULE1,WAUTO1,WEXT1LD,WFULLSMOKE1,WROUTE2,WTCLOSE1,WTCLOSE2,WREAL1P,OAOPS1,ASSET1,OSMOKE1` | quick_validate | none | 2026-05-22 | `quick_validate.py` | medium | 每轮交付操作入口;企微/OA 后续先读 API 能力目录、Secret 矩阵、路线图、ASSET1 资产边界和 OSMOKE1 OA-only smoke 账号治理;不得绕过 WROAD2C/WROAD2B/WAPPR1/WRULE1/WAUTO1/WEXT1LD/WFULLSMOKE1/WTCLOSE2/WEXT1-FORMAL/WREAL1 或后续独立合同直接做完整考勤审批镜像;统一资产台账不保存密码、Secret、源码包、完整配置或涉密原文 | +| `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,WREAD1F,WADM1,WARCH1,WARCH2,WROAD2,WARCH3,WCHECK1,WEXT1,WOPS1,WROAD2C,WROAD2B,WAPPR1,WRULE1,WAUTO1,WEXT1LD,WFULLSMOKE1,WROUTE2,WTCLOSE1,WTCLOSE2,WREAL1P,OAOPS1,ASSET1,OSMOKE1,OALAUNCH1` | quick_validate | none | 2026-05-23 | `quick_validate.py` | medium | 每轮交付操作入口;企微/OA 后续先读 API 能力目录、Secret 矩阵、路线图、ASSET1 资产边界、OSMOKE1 OA-only smoke 账号治理和 OALAUNCH1 最终启用门禁;不得绕过真实业务闭环、权限矩阵、备份恢复、Day 7 Go/No-Go 或后续独立合同直接宣布最终启用完成;统一资产台账不保存密码、Secret、源码包、完整配置或涉密原文 | | `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,WREAD1F,WADM1,WARCH1,WAPPR1,WAUTO1,WEXT1LD,WFULLSMOKE1,WTCLOSE1,WRULE1,WTCLOSE2` | workflow 回归 | none | 2026-05-22 | `rg` | high | OA 主办审批、企微主办 HR/考勤审批、OA 自建留存、真实回调事件证据、WACP3R 补偿拉取 findings、WREAD1 请假/补卡真机可读性结论、WARCH1 最小历史留存、WAPPR1 审批规则规范化和 API 不支持读取规则边界、WTCLOSE1 小团队一层审批 + 管理员兜底目标 A、WRULE1 理论审批人与实际审批链校验、WTCLOSE2 测试号最终收口、WAUTO1/WEXT1LD/WFULLSMOKE1 前置和 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,WACP3,WACP3R,WREAD1,WADM1,WARCH1,WARCH2,WROAD2,WARCH3,WCHECK1,WEXT1,WROAD2B,WAPPR1,WAUTO1,WEXT1LD,WFULLSMOKE1,WTCLOSE1,WTCLOSE2` | live smoke/真机反馈/Issue CSV | none | 2026-05-22 | `rg` | high | 企微优先承担 HR/考勤员工日常审批和人事/打卡能力;WARCH2 已提供请假/补卡正式只读历史查询,WARCH3 固定受控上线自动巡检,WCHECK1 固定打卡摘要留存,WEXT1 固定外出/加班 dry-run,WROAD2B 固定三天人工业务冒烟,WAPPR1/WTCLOSE1 固定审批规则规范化和小团队目标 A,WFULLSMOKE1 固定全域主路径测试边界,WTCLOSE2 固定最终测试号自动收口,WEXT1-FORMAL 需收口后另建合同;采购、合同、项目、报销和财务继续 OA 主办 | | `REQ-ASSET` | `docs/需求-资产管理规约.md` | 需求事实源 | 统一资产台账规约 | `CURRENT` | product/backend | `ASSET1` | Issue CSV / 本地门禁 | none | 2026-05-22 | `rg` | high | 固定资产和电子资产统一台账;编号族 `FA/EA-DOC/EA-SYS/EA-REPO/EA-BKP`;`EA-ACC` 暂缓;Git 管源码,OA 只登记仓库元数据;历史资料只导脱敏索引;不保存密码、token、Secret、源码包、完整配置、涉密正文、真实完整路径、敏感报价或银行流水原件 | | `RUNBOOK-TRIAL` | `docs/试运行与自动化验证手册.md` | 操作手册 | 试运行验证手册 | `CURRENT` | qa/ops | `R7E,QAOPS8` | live readiness | none | 2026-05-13 | `rg` | medium | 本地、公网、企业微信和真实 OCR 验证 | -| `MANUAL-ADMIN` | `docs/管理员手册.md` | 操作手册 | 管理员手册 | `CURRENT` | ops/admin | `ORG8,R7E,WFOC,DBWFOC,WFDG,WCPC,WHMS,WHMSLIVE,WOUA,WAPI,WRRA,WRV1,WRA1,WMSG1,WAPC1,WAPC2,WPROC1,WPROC2,WAPC2F,WAPC2FLIVE,WCBK1,WEGOV1,WACP3,WACP3R,WREAD1,WADM1,WARCH1,WARCH2,WROAD2,WARCH3,WCHECK1,WEXT1,WOPS1,WROAD2C,WROAD2B,WAPPR1,WAUTO1,WEXT1LD,WFULLSMOKE1,WTCLOSE1,WTCLOSE2,WREAL1P,ASSET1` | 人工验收 | none | 2026-05-22 | `rg` | medium | 管理员上线、企微优先边界、Secret 矩阵、巡检和 HR/考勤门禁;补充 ASSET1 统一资产台账、编号族、电子资产索引、EA-ACC 暂缓和敏感资料不进公网 OA 的管理口径 | -| `MANUAL-EMPLOYEE` | `docs/员工快速上手.md` | 操作手册 | 员工手册 | `CURRENT` | product | `R7E,WFOC,DBWFOC,WFDG,ASSET1` | 人工验收 | none | 2026-05-22 | `rg` | low | 员工侧使用说明,HR/考勤优先企业微信,采购/合同/财务仍在 OA;补充统一资产台账、电子资产和涉密资产可见边界 | +| `MANUAL-ADMIN` | `docs/管理员手册.md` | 操作手册 | 管理员手册 | `CURRENT` | ops/admin | `ORG8,R7E,WFOC,DBWFOC,WFDG,WCPC,WHMS,WHMSLIVE,WOUA,WAPI,WRRA,WRV1,WRA1,WMSG1,WAPC1,WAPC2,WPROC1,WPROC2,WAPC2F,WAPC2FLIVE,WCBK1,WEGOV1,WACP3,WACP3R,WREAD1,WADM1,WARCH1,WARCH2,WROAD2,WARCH3,WCHECK1,WEXT1,WOPS1,WROAD2C,WROAD2B,WAPPR1,WAUTO1,WEXT1LD,WFULLSMOKE1,WTCLOSE1,WTCLOSE2,WREAL1P,ASSET1,OALAUNCH1` | 人工验收 | none | 2026-05-23 | `rg` | medium | 管理员上线、企微优先边界、Secret 矩阵、巡检、HR/考勤门禁、ASSET1 统一资产台账和 OALAUNCH1 最终启用 Go/No-Go;明确备份恢复、权限矩阵和敏感资料不进公网 OA 的管理口径 | +| `MANUAL-EMPLOYEE` | `docs/员工快速上手.md` | 操作手册 | 员工手册 | `CURRENT` | product | `R7E,WFOC,DBWFOC,WFDG,ASSET1,OALAUNCH1` | 人工验收 | none | 2026-05-23 | `rg` | low | 员工侧使用说明,HR/考勤优先企业微信,采购/合同/财务仍在 OA;补充统一资产台账、电子资产、涉密资产可见边界和 OALAUNCH1 收口期问题反馈口径 | | `R7-DRAFT` | `docs/trialrun/R7扩大试运行草案.md` | 草案 | R7 准备材料 | `DRAFT` | product/qa | `R7E` | R7EP 报告 | none | 2026-05-13 | `rg` | medium | 不代表当前 R7E 执行状态 | | `OPS-RUNBOOK` | `docs/trialrun/OPS7运维交接Runbook.md` | 运维草案 | OPS 准备材料 | `DRAFT` | ops | `OPS8,QAOPS8` | 只读检查 | none | 2026-05-13 | `rg` | medium | 不代表恢复演练已执行 | -| `TRIAL-REPORT` | `docs/trialrun/人工试运行报告.md` | 证据材料 | 试运行证据入口 | `EVIDENCE` | qa | `R7EP,OPS8,QAF8,WOUA,WAPI,WOUAFIX,WRV1,WRA1,WMSG1,WAPC1,WAPC2,WAPC2F,WAPC2FLIVE,WCBK1,WEGOV1,WROAD2,WARCH3,WCHECK1,WEXT1,WOPS1,WROAD2C,WROAD2B,WAPPR1,WAUTO1,WEXT1LD,WFULLSMOKE1,WTCLOSE1,WRULE1,WTCLOSE2,WREAL1P,ASSET1` | artifacts/qa-flow | none | 2026-05-22 | `rg` | medium | 只证明发生过;记录 WAPC2FLIVE live 样件、WCBK1 真实回调事件、企微路线证据、WTCLOSE2 测试号最终收口、WREAL1 准备和 ASSET1 统一资产台账边界,不直接放行深度镜像或公网资产导入 | +| `TRIAL-REPORT` | `docs/trialrun/人工试运行报告.md` | 证据材料 | 试运行证据入口 | `EVIDENCE` | qa | `R7EP,OPS8,QAF8,WOUA,WAPI,WOUAFIX,WRV1,WRA1,WMSG1,WAPC1,WAPC2,WAPC2F,WAPC2FLIVE,WCBK1,WEGOV1,WROAD2,WARCH3,WCHECK1,WEXT1,WOPS1,WROAD2C,WROAD2B,WAPPR1,WAUTO1,WEXT1LD,WFULLSMOKE1,WTCLOSE1,WRULE1,WTCLOSE2,WREAL1P,ASSET1,OALAUNCH1` | artifacts/qa-flow | none | 2026-05-23 | `rg` | medium | 只证明发生过;记录 WAPC2FLIVE live 样件、WCBK1 真实回调事件、企微路线证据、WTCLOSE2 测试号最终收口、WREAL1 准备、ASSET1 统一资产台账和 OALAUNCH1 最终启用门禁;不直接放行深度镜像或最终正式启用 | | `FBA-AUDIT` | `docs/trialrun/R7E后功能边界审计.md` | 证据材料 | R7E 后功能边界审计 | `EVIDENCE` | product/dev/qa | `FBA,R7E,QAOPS8,P2T` | Issue CSV | none | 2026-05-15 | `rg` | medium | 只作为功能边界、P2 候选和 Issue 草案证据,不直接派工 | | `HISTORY-MATRIX` | `docs/历史功能状态矩阵.md` | 历史材料 | 历史功能索引 | `HISTORICAL` | product | `DCL7` | git 历史 | none | 2026-05-13 | `rg` | low | 只追溯历史 | | `OLD-IMPL-PLAN` | `docs/implementation_plan.md` | 已移除历史 | 早期实施计划 | `REMOVED` | product | `DCL7` | git history | none | 2026-05-13 | manual | low | 从 git 历史恢复 | diff --git a/docs/文档索引.md b/docs/文档索引.md index b5912804..a0098e88 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 -> WPROC2 -> WAPC2F dry-run -> WAPC2FLIVE -> WCBK1 -> WEGOV1 -> WACP3/WACP3R -> WREAD1 自动汇总 -> WREAD1F 新样例 -> WREAD1 真机确认 -> WADM1 自建留存模型设计 -> WARCH1 最小审批历史留存 -> WARCH2 正式只读历史查询 -> WROAD2+OPS 受控上线与每日巡检 -> WARCH3 归档稳定化自动巡检 -> WCHECK1 打卡摘要留存 -> WEXT1 外出/加班 dry-run -> WOPS1 巡检告警降噪 -> WROAD2-CLOSEOUT 三天验收 -> WROAD2-BIZ1 三天业务冒烟 -> WAPPR1 审批规则规范化 -> WRULE1 审批链校验器 POC -> WAUTO1 自动证据汇总 -> WEXT1LD live 前 dry-run -> WFULLSMOKE1 三天全域最终目标测试 -> WROUTE2 测试号收口到最小镜像路线锁定 -> WTCLOSE1 小团队自动审批规则与测试号全域收口 dry-run -> WTCLOSE2 测试号最终收口与证据复核 -> WTCLOSE2F 远端可信 IP 执行口径修正 -> WREMOTE1 远端可信 IP 收口 -> WFULLSMOKE2 全域 smoke -> WEXT1-FORMAL/WEXT1F 正式模板外出/加班测试 -> OAOPS1 小团队全员真实使用稳定化 -> ASSET1 电子资产与固定资产统一台账 -> OSMOKE1 OA-only smoke 账号治理与公网自动化恢复(当前实现)-> 后续再按需问答 WREAL1/最小镜像。 +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 自动汇总 -> WREAD1F 新样例 -> WREAD1 真机确认 -> WADM1 自建留存模型设计 -> WARCH1 最小审批历史留存 -> WARCH2 正式只读历史查询 -> WROAD2+OPS 受控上线与每日巡检 -> WARCH3 归档稳定化自动巡检 -> WCHECK1 打卡摘要留存 -> WEXT1 外出/加班 dry-run -> WOPS1 巡检告警降噪 -> WROAD2-CLOSEOUT 三天验收 -> WROAD2-BIZ1 三天业务冒烟 -> WAPPR1 审批规则规范化 -> WRULE1 审批链校验器 POC -> WAUTO1 自动证据汇总 -> WEXT1LD live 前 dry-run -> WFULLSMOKE1 三天全域最终目标测试 -> WROUTE2 测试号收口到最小镜像路线锁定 -> WTCLOSE1 小团队自动审批规则与测试号全域收口 dry-run -> WTCLOSE2 测试号最终收口与证据复核 -> WTCLOSE2F 远端可信 IP 执行口径修正 -> WREMOTE1 远端可信 IP 收口 -> WFULLSMOKE2 全域 smoke -> WEXT1-FORMAL/WEXT1F 正式模板外出/加班测试 -> OAOPS1 小团队全员真实使用稳定化 -> ASSET1 电子资产与固定资产统一台账 -> OSMOKE1 OA-only smoke 账号治理与公网自动化恢复 -> OALAUNCH1 最终启用前收口与 Go/No-Go 门禁(当前实现)-> 后续再按需问答 WREAL1/最小镜像。 - R6、PC6、PA6、FL6、AP6、RI6、LP6 已完成并部署。 - ME6 自动化、公网视觉矩阵和 `ME6-3` 代表性企业微信真机门禁已完成。 @@ -172,6 +172,7 @@ R6 -> PC6 -> PA6 -> FL6 -> AP6 -> RI6 -> LP6 -> ME6/QG6 -> DG7/MRF7 -> MIA7 -> M | `OAOPS1` | `issues/2026-05-22_20-09-22-oa-real-use-stabilization-v1.csv` | 当前 OA 主办域真实使用稳定化:7 天小团队全员边用边修 | 新增 `scripts/qa/run-oa-real-use-daily.sh`,每日只读核验公网 ready、live smoke、feature flag、报销/PDF OCR、审批、采购/费用、合同台账、项目、付款批次、AP、移动/企微入口和隐私边界 | 允许真实业务数据进入公网 OA 并按审计保留;真实写入由人工完成;涉密合同正文、敏感报价、成本细节、银行流水原件、未脱敏票面不进仓库/聊天/报告;图片 OCR、复杂 BPM、预算、ERP、银行流水导入、真实支付直连、企微 HR 深镜像、应急接管、恢复回写、RuleOps 继续冻结 | | `ASSET1` | `issues/2026-05-22_21-18-33-oa-asset-governance-v1.csv` | 当前统一资产台账 V1:电子资产 + 固定资产共用现有资产模块 | 扩展 `AssetCard` 为 `FA/EA-DOC/EA-SYS/EA-REPO/EA-BKP`,新增资产大类、敏感级别、负责人/保管人、归档位置、外部引用、hash、历史验证编号、最近验证时间和脱敏索引 dry-run;前端资产页支持类型/敏感级别/编号搜索 | 代码仓库仍由 Git 管理;`EA-ACC` 暂缓;不保存密码、token、Secret、源码包、完整配置、涉密正文、真实完整路径、敏感报价、银行流水原件或未脱敏票面 | | `OSMOKE1` | `issues/2026-05-23_13-59-52-oa-only-smoke-account-governance-v1.csv` | 当前 OA-only smoke 账号治理:恢复公网 OA 自动化高权限测试账号,不再额外申请企微账号 | 新增 OA-only smoke 修复脚本、企微/OA 一致性审计豁免 `oaOnlySmokeExempted`、公网角色 readiness 检查,并接入 OAOPS1 每日巡检 | 只处理私有 env、显式员工号 allowlist 或 `SMOKE*`/`smoke-*`/测试命名命中的 smoke 标识;默认 dry-run,apply 必须 `OA_ONLY_SMOKE_REPAIR_APPLY=1`;不写企业微信、不重置密码、不影响真实员工、不执行 EA-DOC live 导入 | +| `OALAUNCH1` | `issues/2026-05-23_16-49-39-oa-final-launch-readiness-v1.csv` | 当前最终启用前收口与 Go/No-Go 门禁:在 OAOPS1/ASSET1D 之上增加业务闭环、权限、备份恢复、数据治理、培训和 Day 7 决策 | 新增 `scripts/qa/run-oa-final-launch-readiness.sh`,默认只读聚合 OAOPS1、ASSET1D、权限矩阵、备份恢复演练和人工 Go/No-Go 证据,输出 `PASS_OA_LAUNCH_READY` / `PASS_WITH_FINDINGS` / `PAUSED_MANUAL_CONFIRMATION` / `SKIP/BLOCKED_BY_EXTERNAL_CONFIG` | 不创建业务数据、不执行付款、不写企业微信、不恢复数据库;未达到 `PASS_OA_LAUNCH_READY` 前不得宣布最终正式启用完成 | | `WREAL1-PREP` | `issues/2026-05-20_14-35-42-wecom-real-trial-prep-v1.csv` | 当前真实试用准备:不启用真实员工 | 固化候选员工、非自审审批人、回滚联系人、允许流程、暂停条件和验收标准;真实试用必须等请假、补卡、外出、加班四类测试号证据、WAPPR1 和 WFULLSMOKE1 通过后再问 | 当前不创建外出/加班 live 样件;真实员工名单不写入 Issue 或文档,后续通过问答确认 | 旧 Issue CSV 不删除、不批量回写、不移动到归档目录。若新治理流替代旧合同,只在新 CSV 的 `Notes` 或本索引说明 supersedes,旧文件保留历史审计语义。 diff --git a/docs/管理员手册.md b/docs/管理员手册.md index 638c8be8..4e866b09 100644 --- a/docs/管理员手册.md +++ b/docs/管理员手册.md @@ -10,6 +10,7 @@ **WEXT1F 正式模板外出/加班证据脚本**:管理员或 QA 只在人工测试完成后执行 `WEXT1F_MANUAL_CONFIRMATION=SMOKE002_SUBMITTED_SMOKE101_REVIEWED bash scripts/qa/run-wecom-formal-outing-overtime-evidence.sh --remote`。人工步骤固定为:`SMOKE002` 在企业微信正式外出模板提交低风险测试单,确认审批人自动带出测试主管且不能改人;再在正式加班模板提交低风险测试单并做同样确认;`SMOKE101` 必须先打开详情页再同意或驳回,不能直接点快捷同意。缺少该人工确认标记时脚本只输出 `PAUSED_MANUAL_CONFIRMATION`,不跑企微 API。脚本从部署服务器可信 IP 读取企微审批 API,不创建模板、不自动提交审批、不写企业微信;补偿拉取必须同时按 `template_id` 和 `creator=SMOKE002` 过滤,避免读取同一正式模板下其他员工单据;仅在既有 `WECOM_ARCHIVE_WRITE=1` + `--persist-to-db` 双开关下把拉到的外出/加班详情写入 OA 自建留存,然后做离线归档读取和 WRULE1 审批链校验。 **OAOPS1 小团队真实使用稳定化**:企微打卡、补卡、外出、请假先冻结在当前完成态,日常继续由企业微信主办,OA 做留存、审计和后续兜底。当前主线回到 OA 主办域,按 7 天小团队全员真实使用稳定化执行;允许公网 OA 录入真实报销、采购/费用、合同台账、项目、付款批次和银行回单等业务数据,数据按审计保留不自动清理。新增 `scripts/qa/run-oa-real-use-daily.sh` 作为每日只读巡检;真实业务写入只由人工在公网 OA 内完成。图片 OCR、复杂 BPM、预算、ERP、银行流水导入、真实支付直连、企微 HR 深镜像、应急接管、恢复回写和 RuleOps 继续冻结。 **OSMOKE1 OA-only smoke 账号治理**:企业微信账号数量有限时,不再为了 OA 报销、资产、财务、AP、合同或权限回归额外申请企业微信账号。企微测试账号只服务 HR/考勤链路;公网 OA 自动化使用 OA-only smoke 账号,允许不绑定企微,但必须在 OA 内保持 active、角色正确且可登录。企微/OA 一致性审计会把私有 env 中列出、显式员工号 allowlist 命中,或符合 `SMOKE*`/`smoke-*`/`oa-smoke-*`/测试命名的 OA-only smoke 账号归为 `oaOnlySmokeExempted`,不再当作“缺企微账号”归档;真实员工不得用该命名绕过一致性治理。恢复账号必须先 dry-run,真实修复必须 `--apply` 且设置 `OA_ONLY_SMOKE_REPAIR_APPLY=1`,不得重置密码、不得打印凭据、不得影响真实员工。 +**OALAUNCH1 最终启用门禁**:ASSET1D 已完成资产台账公网部署、最小 UI 试录和 3 条脱敏 `EA-DOC` 受控导入;OAOPS1 自动巡检已具备公网只读证据,但正式启用前仍必须补齐真实业务闭环、权限矩阵、备份恢复演练、数据治理培训和 Day 7 Go/No-Go。新增 `scripts/qa/run-oa-final-launch-readiness.sh` 只聚合证据并输出 `PASS_OA_LAUNCH_READY`、`PASS_WITH_FINDINGS`、`PAUSED_MANUAL_CONFIRMATION` 或 `SKIP/BLOCKED_BY_EXTERNAL_CONFIG`;脚本不创建业务数据、不执行付款、不写企业微信、不做数据库恢复。未达到 `PASS_OA_LAUNCH_READY` 前,只能视为小团队稳定化收口中,不能宣布最终正式启用完成。 ## 1. 适用范围 - 适用于 `small_team_internal` 工作区模式。 @@ -439,6 +440,47 @@ ASSET1 后,资产模块统一承载固定资产和电子资产索引,不再 7 天结束后按自动报告和人工问题登记选择:接受 `PASS_OA_REAL_USE_READY` 进入体验/效率优化;接受 `PASS_WITH_FINDINGS` 但先处理指定 finding;或暂停真实使用扩展先整改。 +### 3.7 OALAUNCH1 最终启用 Go/No-Go + +OALAUNCH1 是 OAOPS1 和 ASSET1D 之后的最终启用门禁层。它不替代真实业务操作,也不替代备份恢复演练;它只把自动报告、人工验收和 Go/No-Go 结论汇总成一份脱敏证据。 + +默认汇总命令: + +```bash +bash scripts/qa/run-oa-final-launch-readiness.sh +``` + +如需同步复跑 OAOPS1 每日只读巡检,再执行: + +```bash +OALAUNCH_RUN_OAOPS1_DAILY=1 LIVE_SMOKE_BASE_URL=https://oa.hyinda.com bash scripts/qa/run-oa-final-launch-readiness.sh +``` + +7 天稳定化必须指定起始日期,避免脚本误用历史报告凑数: + +```bash +OALAUNCH_STABILIZATION_START_DATE=2026-05-23 bash scripts/qa/run-oa-final-launch-readiness.sh +``` + +正式启用前必须逐项确认: + +| 门禁 | 通过标准 | 未通过处理 | +| --- | --- | --- | +| OAOPS1 真实业务闭环 | 报销/PDF OCR/审批/付款/AP、采购/费用/项目或合同、合同台账/归档/权限均有真实样本 | 当天建修复合同,或暂停对应流程 | +| 7 天稳定化 | 每日巡检无不可解释 blocker,非阻断 finding 有 owner 和日期 | 延长稳定化或先修指定 finding | +| 权限矩阵 | 普通员工不能越权;主管、财务、HR、CEO、管理员入口和 API 权限符合角色 | 权限相关修复后必须只读 reviewer 复核 | +| 备份恢复 | 备份存在、可访问,影子库或受控恢复演练通过,记录 RTO/RPO 和负责人 | 不得直接启用;修复后重跑演练 | +| 数据治理和培训 | 员工、审批人、财务、管理员知道可上传/不可上传边界和问题上报方式 | 补培训或补手册后再 Go/No-Go | +| Day 7 Go/No-Go | 人工选择 A/B/C 并记录理由 | 缺结论时保持 `PAUSED_MANUAL_CONFIRMATION` | + +Day 7 选项固定: + +- A:接受 `PASS_OA_LAUNCH_READY`,进入小团队正式启用。 +- B:接受 `PASS_WITH_FINDINGS`,但必须指定 owner、处理日期和接受理由。 +- C:暂停启用,先整改。 + +备份恢复演练必须在不影响公网生产库的前提下完成。可以使用影子库、临时恢复库或受控恢复验证;不得为了演练重置公网数据库。证据路径可通过 `OALAUNCH_BACKUP_EVIDENCE` 传给汇总脚本。权限矩阵和真实业务闭环证据也可分别通过 `OALAUNCH_PERMISSION_EVIDENCE`、`OALAUNCH_BUSINESS_EVIDENCE` 指定脱敏证据文件。 + ## 4. 人员生命周期管理 ### 4.1 入职 1. 在 `人员管理` 创建员工账号。 diff --git a/issues/2026-05-23_16-49-39-oa-final-launch-readiness-v1.csv b/issues/2026-05-23_16-49-39-oa-final-launch-readiness-v1.csv new file mode 100644 index 00000000..b8510ec8 --- /dev/null +++ b/issues/2026-05-23_16-49-39-oa-final-launch-readiness-v1.csv @@ -0,0 +1,10 @@ +ID,Title,Description,Acceptance,Test_Method,Tools,Dev_Status,Review1_Status,Regression_Status,Files,Dependencies,Notes +OALAUNCH1-0,建立最终启用 Go/No-Go 合同,"在 OAOPS1 和 ASSET1D 之后新增最终启用门禁层,固定 P0+轻P1 启用范围、7 天稳定化、备份恢复、权限矩阵、数据治理、运营支持和冻结规则。","Issue CSV 通过校验;明确 OALAUNCH1 不替代 OAOPS1 真实业务闭环,而是在其上增加最终启用 Go/No-Go。","受影响流程:最终启用决策 -> 执行合同 -> Go/No-Go;验证步骤:python3 /home/shunli/.codex/skills/plan/scripts/validate_issues_csv.py issues/2026-05-23_16-49-39-oa-final-launch-readiness-v1.csv。","python3|manual",DONE,DONE,DONE,"issues/2026-05-23_16-49-39-oa-final-launch-readiness-v1.csv","[]","本合同只规划和聚合最终启用证据;不部署、不写公网业务数据、不修改生产配置;本地合同校验已通过。" +OALAUNCH1-1,新增最终启用证据汇总脚本,"新增 QA-only 汇总脚本,读取 OAOPS1、ASSET1D、备份恢复、权限矩阵、数据治理和人工 Go/No-Go 证据,输出脱敏 summary.json。","脚本默认只读;不创建业务数据、不上传文件、不执行付款、不写企业微信、不打印密码/token/Secret;结论限定为 PASS_OA_LAUNCH_READY、PASS_WITH_FINDINGS、PAUSED_MANUAL_CONFIRMATION 或 SKIP/BLOCKED_BY_EXTERNAL_CONFIG;任何 open gate 未关闭时不得 PASS。","受影响流程:管理员收集每日巡检和人工证据 -> 运行最终启用汇总 -> 查看缺口;验证步骤:bash -n scripts/qa/run-oa-final-launch-readiness.sh;必要时执行 OALAUNCH_SKIP_LIVE=1 bash scripts/qa/run-oa-final-launch-readiness.sh。","bash|node",DONE,DONE,DONE,"scripts/qa/run-oa-final-launch-readiness.sh","[""OALAUNCH1-0""]","默认不跑公网写入;可选公网只读检查也必须只调用 ready 和既有只读巡检;7 天窗口必须设置 OALAUNCH_STABILIZATION_START_DATE;默认运行报告 artifacts/qa-flow/oa-final-launch-readiness-20260523-170001/summary.json 为 PAUSED_MANUAL_CONFIRMATION。" +OALAUNCH1-2,回写最终启用事实源和操作清单,"更新管理员手册、员工快速上手、人工试运行报告、文档索引、事实源矩阵和项目 skill,固化最终启用前仍需补齐的门禁。","文档能检索到 OALAUNCH1、PASS_OA_LAUNCH_READY、7 天稳定化、备份恢复演练、权限矩阵、Go/No-Go 和 P2 冻结口径。","受影响流程:管理员/员工按手册执行上线前收口;验证步骤:rg -n ""OALAUNCH1|PASS_OA_LAUNCH_READY|备份恢复|权限矩阵|Go/No-Go|7 天稳定化"" docs .agents/skills/hyb-oa-delivery/SKILL.md。","rg|manual",DONE,DONE,DONE,"docs/管理员手册.md;docs/员工快速上手.md;docs/trialrun/人工试运行报告.md;docs/文档索引.md;docs/文档事实源状态矩阵.md;.agents/skills/hyb-oa-delivery/SKILL.md","[""OALAUNCH1-0""]","优先更新现有事实源,不新建孤立文档;rg、audit-docs 和 quick_validate 已通过,audit-docs 保留既有 unregistered docs files 24 warning。" +OALAUNCH1-3,OAOPS1 真实业务闭环收口门禁,"正式启用前必须补齐 OAOPS1-3、OAOPS1-4、OAOPS1-5、OAOPS1-6,不得只用自动巡检替代真实业务闭环。","至少 1 条真实报销 PDF OCR -> 审批 -> 付款批次 -> 银行回单 -> 已付款 -> AP 导出闭环;至少 1 条采购/费用审批并关联项目或合同;至少 1 条合同台账含归档位置和权限检查;每日问题登记机制可执行。","受影响流程:报销 -> 审批 -> 付款 -> AP;采购/费用 -> 审批 -> 项目/合同;合同台账 -> 归档 -> 权限;验证步骤:人工在公网 OA 完成真实业务样本,运行 run-oa-final-launch-readiness.sh 汇总 OAOPS1 状态。","manual|bash",TODO,TODO,TODO,"external|issues/2026-05-22_20-09-22-oa-real-use-stabilization-v1.csv","[""OALAUNCH1-1""]","真实票据、银行回单、合同正文和敏感报价不得进入仓库、聊天或自动化报告;阻断即建修复合同。" +OALAUNCH1-4,7 天稳定化与每日巡检门禁,"连续 7 天运行 OAOPS1 每日只读巡检并做人工日收口,最终汇总无 blocker。","每日 summary.json 可读;最终 7 天无不可解释 blocker;非阻断 finding 均有 owner、处理日期或接受理由。","受影响流程:每日巡检 -> 人工问题登记 -> Day 7 汇总;验证步骤:LIVE_SMOKE_BASE_URL=https://oa.hyinda.com bash scripts/qa/run-oa-real-use-daily.sh;Day 7 执行 OALAUNCH_STABILIZATION_START_DATE=YYYY-MM-DD run-oa-final-launch-readiness.sh。","bash|manual",TODO,TODO,TODO,"artifacts/qa-flow/oa-real-use-daily-*|scripts/qa/run-oa-real-use-daily.sh","[""OALAUNCH1-3""]","自动报告只说明运行状态;Day 7 Go/No-Go 仍需人工拍板;脚本必须按指定起始日期检查连续 7 天,不得用历史报告凑数。" +OALAUNCH1-5,权限矩阵最终验收,"正式启用前复核员工、主管、财务、HR、CEO、管理员关键入口和 API 权限,重点验证普通员工不能越权。","普通员工不能访问财务/AP、合同敏感附件、他人审批、管理入口;各业务角色能访问必要入口;权限负例报告脱敏留存。","受影响流程:登录 -> 入口可见性 -> API 直连负例 -> 审计;验证步骤:复用 OA-only smoke readiness、权限直连审计或人工浏览器验证,并将证据路径提供给 run-oa-final-launch-readiness.sh。","playwright|manual|node",TODO,TODO,TODO,"external|scripts/qa/check-oa-only-smoke-readiness.mjs","[""OALAUNCH1-1""]","涉及权限模型,实施或修复后必须只读 reviewer 评审;不得在报告中输出完整账号或密码。" +OALAUNCH1-6,备份恢复演练门禁,"正式启用前必须确认备份存在、可访问,并做一次受控恢复演练或影子库恢复验证,记录 RTO/RPO、负责人和证据路径。","备份介质或备份集合可定位;恢复演练不影响公网生产库;影子库或只读恢复验证通过;RTO/RPO 和负责人记录清楚。","受影响流程:备份检查 -> 影子恢复 -> 数据验证 -> Go/No-Go;验证步骤:按 OPS runbook 执行受控恢复演练,证据路径通过 OALAUNCH_BACKUP_EVIDENCE 提供给 run-oa-final-launch-readiness.sh。","manual|ops",TODO,TODO,TODO,"external|docs/trialrun/OPS7运维交接Runbook.md","[""OALAUNCH1-1""]","不得重置公网数据库;不得在未确认影响范围时执行 destructive restore;失败则暂停正式启用;回滚路径:只删除影子库/临时恢复环境并保留演练日志,公网生产库不做回滚动作。" +OALAUNCH1-7,数据治理培训与运营支持门禁,"最终启用前固定资料上传边界、档案保留、问题 owner、阻断处理、人工绕行和员工/审批人/财务/管理员培训清单。","手册包含可上传/不可上传边界;员工、审批人、财务、管理员知道首周操作;阻断、人工绕行和暂停标准可执行。","受影响流程:员工提交 -> 审批处理 -> 财务付款 -> 管理员日收口 -> 问题升级;验证步骤:管理员组织人工确认并在 Day 7 Go/No-Go 前记录接受、带 finding 接受或暂停。","manual",TODO,TODO,TODO,"docs/管理员手册.md;docs/员工快速上手.md","[""OALAUNCH1-2""]","合同正文、敏感报价、银行流水原件、凭据、Secret、未脱敏票面和完整本地路径不得进入仓库、聊天或报告。" +OALAUNCH1-8,OALAUNCH1 本地门禁与收口,"完成合同校验、脚本语法、文档审计、skill 校验和 diff 检查;如需公网证据,另行执行只读汇总脚本。","本地门禁通过;真实业务、权限、备份和 Day 7 Go/No-Go 未完成时保持 TODO/PAUSED,不伪造成 PASS。","受影响流程:最终启用准备 -> 本地静态门禁 -> 后续人工验收;验证步骤:validate_issues_csv.py;node scripts/qa/check-process-contract.mjs --baseline --json;node scripts/qa/check-process-contract.mjs --issue --profile general --json;bash -n scripts/qa/run-oa-final-launch-readiness.sh;node scripts/qa/audit-docs.mjs --check-registry;quick_validate.py .agents/skills/hyb-oa-delivery;git diff --check。","python3|node|bash|git",DONE,DONE,DONE,"issues/2026-05-23_16-49-39-oa-final-launch-readiness-v1.csv;scripts/qa/run-oa-final-launch-readiness.sh;docs","[""OALAUNCH1-1"",""OALAUNCH1-2""]","本轮不部署;公网正式启用必须等 OALAUNCH1-3..7 真实证据和 Day 7 Go/No-Go;本地门禁已通过,check-process-contract general 已补 rollback 后通过。" diff --git a/scripts/qa/run-oa-final-launch-readiness.sh b/scripts/qa/run-oa-final-launch-readiness.sh new file mode 100755 index 00000000..af0df84b --- /dev/null +++ b/scripts/qa/run-oa-final-launch-readiness.sh @@ -0,0 +1,481 @@ +#!/usr/bin/env bash +set -u -o pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +PUBLIC_BASE_URL="${LIVE_SMOKE_BASE_URL:-https://oa.hyinda.com}" +REPORT_DIR="${OALAUNCH_REPORT_DIR:-$ROOT_DIR/artifacts/qa-flow/oa-final-launch-readiness-$(date +%Y%m%d-%H%M%S)}" +SUMMARY_TXT="$REPORT_DIR/summary.txt" +STATUS=0 + +mkdir -p "$REPORT_DIR" +: >"$SUMMARY_TXT" + +log() { + printf '%s\n' "$*" | tee -a "$SUMMARY_TXT" +} + +run_step() { + local name="$1" + shift + local log_file="$REPORT_DIR/${name}.log" + + log ">>> [oalaunch1] $name" + if "$@" > >(tee "$log_file") 2>&1; then + log "[oalaunch1][PASS] $name" + else + local code="$?" + log "[oalaunch1][FAIL] $name exit=$code" + STATUS=1 + fi +} + +write_manual_gates() { + cat >"$REPORT_DIR/manual-gates.md" <<'EOF' +# OALAUNCH1 最终启用人工门禁 + +## Day 7 Go/No-Go +- A:接受 `PASS_OA_LAUNCH_READY`,进入小团队正式启用。 +- B:接受 `PASS_WITH_FINDINGS`,但必须指定 owner、处理日期和接受理由。 +- C:暂停启用,先整改。 + +## 阻断处理 +- A:当天新建修复合同并修复。 +- B:临时人工绕行,记录风险、补录责任人和补录方式。 +- C:暂停对应流程。 + +## 备份恢复演练 +- A:接受恢复演练结果,进入 Go/No-Go。 +- B:修复后重跑恢复演练。 +- C:暂停正式启用。 + +## 必须补齐的业务证据 +- 至少 1 条真实报销 PDF OCR -> 审批 -> 付款批次 -> 银行回单 -> 已付款 -> AP 导出。 +- 至少 1 条采购/费用审批并关联项目或合同。 +- 至少 1 条合同台账含归档位置和权限检查。 +- 资产台账 ASSET1D 已完成后,补真实资产责任人或保管人抽查。 +EOF +} + +run_optional_live_checks() { + if [[ "${OALAUNCH_RUN_OAOPS1_DAILY:-0}" == "1" ]]; then + run_step "oaops1-daily-readonly" env \ + LIVE_SMOKE_BASE_URL="$PUBLIC_BASE_URL" \ + OAOPS1_REPORT_DIR="$REPORT_DIR/oaops1-daily-readonly" \ + bash "$ROOT_DIR/scripts/qa/run-oa-real-use-daily.sh" + else + log "[oalaunch1][SKIP] oaops1-daily-readonly:默认不跑公网巡检;需要时设置 OALAUNCH_RUN_OAOPS1_DAILY=1" + fi +} + +write_summary_json() { + node - "$ROOT_DIR" "$REPORT_DIR" "$PUBLIC_BASE_URL" "$STATUS" <<'NODE' +const fs = require("node:fs"); +const path = require("node:path"); +const { execSync } = require("node:child_process"); + +const [rootDir, reportDir, baseURL, statusInput] = process.argv.slice(2); +const qaFlowDir = path.join(rootDir, "artifacts", "qa-flow"); +const oalaunchCsv = path.join(rootDir, "issues", "2026-05-23_16-49-39-oa-final-launch-readiness-v1.csv"); +const oaopsCsv = path.join(rootDir, "issues", "2026-05-22_20-09-22-oa-real-use-stabilization-v1.csv"); +const assetCsv = path.join(rootDir, "issues", "2026-05-23_10-42-03-oa-asset-deploy-and-trial-v1.csv"); + +function readJson(file) { + if (!file || !fs.existsSync(file)) return null; + try { + return JSON.parse(fs.readFileSync(file, "utf8")); + } catch { + return null; + } +} + +function parseCsv(text) { + const rows = []; + let field = ""; + let row = []; + let inQuotes = false; + for (let index = 0; index < text.length; index += 1) { + const ch = text[index]; + const next = text[index + 1]; + if (ch === '"') { + if (inQuotes && next === '"') { + field += '"'; + index += 1; + } else { + inQuotes = !inQuotes; + } + } else if (ch === "," && !inQuotes) { + row.push(field); + field = ""; + } else if ((ch === "\n" || ch === "\r") && !inQuotes) { + if (ch === "\r" && next === "\n") index += 1; + row.push(field); + if (row.some((item) => item !== "")) rows.push(row); + row = []; + field = ""; + } else { + field += ch; + } + } + if (field || row.length) { + row.push(field); + if (row.some((item) => item !== "")) rows.push(row); + } + if (!rows.length) return []; + const header = rows[0]; + return rows.slice(1).map((items) => Object.fromEntries(header.map((key, index) => [key, items[index] ?? ""]))); +} + +function readCsvRows(file) { + if (!fs.existsSync(file)) return []; + return parseCsv(fs.readFileSync(file, "utf8")); +} + +function rowById(rows, id) { + return rows.find((row) => row.ID === id) || null; +} + +function isDone(row) { + return row?.Dev_Status === "DONE" && row?.Review1_Status === "DONE" && row?.Regression_Status === "DONE"; +} + +function statuses(rows, ids) { + return Object.fromEntries(ids.map((id) => { + const row = rowById(rows, id); + return [id, { + title: row?.Title || null, + dev: row?.Dev_Status || "MISSING", + review: row?.Review1_Status || "MISSING", + regression: row?.Regression_Status || "MISSING", + done: isDone(row), + }]; + })); +} + +function allPrefixDone(rows, prefix) { + const matching = rows.filter((row) => String(row.ID || "").startsWith(prefix)); + return matching.length > 0 && matching.every(isDone); +} + +function reportRef(file) { + if (!file) return null; + const absolute = path.resolve(file); + const relative = path.relative(rootDir, absolute); + if (relative && !relative.startsWith("..") && !path.isAbsolute(relative)) { + return relative; + } + return path.basename(absolute); +} + +function latestSummary(prefixes) { + if (!fs.existsSync(qaFlowDir)) return null; + const entries = fs.readdirSync(qaFlowDir, { withFileTypes: true }) + .filter((entry) => entry.isDirectory()) + .filter((entry) => prefixes.some((prefix) => entry.name.startsWith(prefix))) + .map((entry) => { + const dir = path.join(qaFlowDir, entry.name); + const summaryPath = path.join(dir, "summary.json"); + const stat = fs.existsSync(summaryPath) ? fs.statSync(summaryPath) : fs.statSync(dir); + return { name: entry.name, dir, summaryPath, mtimeMs: stat.mtimeMs }; + }) + .filter((entry) => fs.existsSync(entry.summaryPath)) + .sort((a, b) => b.mtimeMs - a.mtimeMs); + if (!entries.length) return null; + const latest = entries[0]; + const json = readJson(latest.summaryPath) || {}; + return { + name: latest.name, + reportRef: reportRef(latest.summaryPath), + status: json.status || json.conclusion || "UNKNOWN", + findingCount: Number(json.findingCount || 0), + }; +} + +function resolveEvidenceRef(value) { + if (!value) return null; + const file = path.isAbsolute(value) ? value : path.join(rootDir, value); + return { + provided: true, + exists: fs.existsSync(file), + ref: reportRef(file), + }; +} + +function parseDayFromName(name) { + const match = String(name).match(/20\d{6}/); + return match ? match[0] : null; +} + +function normalizeDay(value) { + const text = String(value || "").trim(); + const match = text.match(/^(20\d{2})-?(\d{2})-?(\d{2})$/); + return match ? `${match[1]}${match[2]}${match[3]}` : null; +} + +function addDays(day, offset) { + const year = Number(day.slice(0, 4)); + const month = Number(day.slice(4, 6)) - 1; + const date = Number(day.slice(6, 8)); + const value = new Date(Date.UTC(year, month, date + offset)); + return `${value.getUTCFullYear()}${String(value.getUTCMonth() + 1).padStart(2, "0")}${String(value.getUTCDate()).padStart(2, "0")}`; +} + +function collectDailyReports(prefixes) { + if (!fs.existsSync(qaFlowDir)) return {}; + const byDay = {}; + for (const entry of fs.readdirSync(qaFlowDir, { withFileTypes: true })) { + if (!entry.isDirectory()) continue; + if (!prefixes.some((prefix) => entry.name.startsWith(prefix))) continue; + const day = parseDayFromName(entry.name); + if (!day) continue; + const summaryPath = path.join(qaFlowDir, entry.name, "summary.json"); + if (!fs.existsSync(summaryPath)) continue; + const json = readJson(summaryPath) || {}; + const stat = fs.statSync(summaryPath); + byDay[day] ||= []; + byDay[day].push({ + name: entry.name, + reportRef: reportRef(summaryPath), + status: json.status || json.conclusion || "UNKNOWN", + findingCount: Number(json.findingCount || 0), + mtimeMs: stat.mtimeMs, + }); + } + for (const reports of Object.values(byDay)) { + reports.sort((a, b) => b.mtimeMs - a.mtimeMs); + } + return byDay; +} + +function summarizeStabilization(prefixes) { + const startDay = normalizeDay(process.env.OALAUNCH_STABILIZATION_START_DATE || ""); + const reportsByDay = collectDailyReports(prefixes); + const observedDays = Object.keys(reportsByDay).sort(); + if (!startDay) { + return { + status: "MISSING_START_DATE", + startDay: null, + requiredDays: [], + observedDays, + dayReports: {}, + }; + } + + const requiredDays = Array.from({ length: 7 }, (_, index) => addDays(startDay, index)); + const dayReports = {}; + const missingDays = []; + const blockedDays = []; + const findingDays = []; + for (const day of requiredDays) { + const latest = reportsByDay[day]?.[0] || null; + dayReports[day] = latest ? { + name: latest.name, + reportRef: latest.reportRef, + status: latest.status, + findingCount: latest.findingCount, + } : null; + if (!latest) { + missingDays.push(day); + } else if (/BLOCKED|FAIL|ERROR/i.test(latest.status)) { + blockedDays.push(day); + } else if (latest.findingCount > 0 || /FINDING/i.test(latest.status)) { + findingDays.push(day); + } + } + + return { + status: missingDays.length ? "MISSING_DAYS" : blockedDays.length ? "HAS_BLOCKERS" : findingDays.length ? "HAS_FINDINGS" : "PASS", + startDay, + requiredDays, + observedDays, + missingDays, + blockedDays, + findingDays, + dayReports, + }; +} + +let commit = "unknown"; +try { + commit = execSync("git rev-parse --short HEAD", { cwd: rootDir, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim(); +} catch {} + +const oaopsRows = readCsvRows(oaopsCsv); +const assetRows = readCsvRows(assetCsv); +const oalaunchRows = readCsvRows(oalaunchCsv); +const oaopsRequired = ["OAOPS1-3", "OAOPS1-4", "OAOPS1-5", "OAOPS1-6"]; +const oalaunchRequired = ["OALAUNCH1-0", "OALAUNCH1-1", "OALAUNCH1-2", "OALAUNCH1-3", "OALAUNCH1-4", "OALAUNCH1-5", "OALAUNCH1-6", "OALAUNCH1-7", "OALAUNCH1-8"]; +const oaopsStatuses = statuses(oaopsRows, oaopsRequired); +const oalaunchStatuses = statuses(oalaunchRows, oalaunchRequired); +const assetDone = allPrefixDone(assetRows, "ASSET1D-"); +const latestOaops = latestSummary(["oa-real-use-daily-", "oaops1-daily-", "oaops1-daily-after-throttle-"]); +const latestAssetTrial = latestSummary(["asset1d-public-trial-"]); +const latestAssetImport = latestSummary(["asset-redacted-index-import-live-"]); + +const backupEvidence = resolveEvidenceRef(process.env.OALAUNCH_BACKUP_EVIDENCE || ""); +const permissionEvidence = resolveEvidenceRef(process.env.OALAUNCH_PERMISSION_EVIDENCE || ""); +const businessEvidence = resolveEvidenceRef(process.env.OALAUNCH_BUSINESS_EVIDENCE || ""); +const manualAccept = String(process.env.OALAUNCH_MANUAL_ACCEPT || "").trim(); +const stabilization = summarizeStabilization(["oa-real-use-daily-", "oaops1-daily-", "oaops1-daily-after-throttle-"]); + +const openGates = []; +const findings = []; + +for (const [id, state] of Object.entries(oaopsStatuses)) { + if (!state.done) openGates.push({ gate: id, reason: "OAOPS1 真实业务闭环仍未完成", state }); +} +for (const [id, state] of Object.entries(oalaunchStatuses)) { + if (!state.done) openGates.push({ gate: id, reason: "OALAUNCH1 本轮合同/脚本/事实源/门禁尚未全部回填 DONE", state }); +} +if (!assetDone) openGates.push({ gate: "ASSET1D", reason: "资产台账部署与试录未全量完成" }); +if (!latestOaops) openGates.push({ gate: "OAOPS1_DAILY_REPORT", reason: "未找到 OAOPS1 每日巡检 summary.json" }); +if (latestOaops && /BLOCKED|FAIL|ERROR/i.test(latestOaops.status)) findings.push({ source: "latest_oaops1_daily", severity: "blocker", status: latestOaops.status, reportRef: latestOaops.reportRef }); +if (latestOaops && latestOaops.findingCount > 0) findings.push({ source: "latest_oaops1_daily", severity: "finding", status: latestOaops.status, findingCount: latestOaops.findingCount, reportRef: latestOaops.reportRef }); +if (stabilization.status !== "PASS") { + const reason = stabilization.status === "MISSING_START_DATE" + ? "缺 7 天稳定化起始日期;设置 OALAUNCH_STABILIZATION_START_DATE=YYYY-MM-DD 后按连续 7 天检查" + : stabilization.status === "MISSING_DAYS" + ? "7 天稳定化连续报告不完整" + : stabilization.status === "HAS_BLOCKERS" + ? "7 天稳定化窗口内存在阻断报告" + : "7 天稳定化窗口内存在 finding,必须按 PASS_WITH_FINDINGS 人工接受"; + openGates.push({ gate: "SEVEN_DAY_STABILIZATION", reason, stabilization }); +} +if (stabilization.status === "HAS_BLOCKERS") findings.push({ source: "seven_day_stabilization", severity: "blocker", blockedDays: stabilization.blockedDays }); +if (stabilization.status === "HAS_FINDINGS") findings.push({ source: "seven_day_stabilization", severity: "finding", findingDays: stabilization.findingDays }); +if (!backupEvidence?.exists) openGates.push({ gate: "BACKUP_RESTORE_DRILL", reason: "缺备份恢复演练或影子库恢复证据;可用 OALAUNCH_BACKUP_EVIDENCE 指定脱敏证据路径" }); +if (!permissionEvidence?.exists) openGates.push({ gate: "PERMISSION_MATRIX", reason: "缺最终权限矩阵负例/角色入口证据;可用 OALAUNCH_PERMISSION_EVIDENCE 指定脱敏证据路径" }); +if (!businessEvidence?.exists && Object.values(oaopsStatuses).some((state) => !state.done)) { + openGates.push({ gate: "BUSINESS_ACCEPTANCE_EVIDENCE", reason: "缺真实业务闭环证据;可用 OALAUNCH_BUSINESS_EVIDENCE 指定脱敏证据路径" }); +} +if (!/^PASS_OA_LAUNCH_READY$|^PASS_WITH_FINDINGS$/i.test(manualAccept)) { + openGates.push({ gate: "DAY7_GO_NO_GO", reason: "缺 Day 7 人工 Go/No-Go 接受结论;可用 OALAUNCH_MANUAL_ACCEPT=PASS_OA_LAUNCH_READY 或 PASS_WITH_FINDINGS 指定" }); +} +if (findings.length > 0 && manualAccept !== "PASS_WITH_FINDINGS") { + openGates.push({ gate: "FINDING_ACCEPTANCE_REQUIRED", reason: "存在 finding 时必须由 Day 7 人工选择 PASS_WITH_FINDINGS,并指定 owner、日期和接受理由" }); +} + +let status = "PAUSED_MANUAL_CONFIRMATION"; +if (Number(statusInput || 0) !== 0 || findings.some((item) => item.severity === "blocker")) { + status = "SKIP/BLOCKED_BY_EXTERNAL_CONFIG"; +} else if (openGates.length > 0) { + status = "PAUSED_MANUAL_CONFIRMATION"; +} else if (findings.length > 0 && manualAccept === "PASS_WITH_FINDINGS") { + status = "PASS_WITH_FINDINGS"; +} else if (manualAccept === "PASS_OA_LAUNCH_READY") { + status = "PASS_OA_LAUNCH_READY"; +} else if (manualAccept === "PASS_WITH_FINDINGS") { + status = "PASS_WITH_FINDINGS"; +} + +const summary = { + issueId: "OALAUNCH1", + mode: "oa-final-launch-readiness", + status, + conclusion: status, + baseURL, + commit, + checkedAt: new Date().toISOString(), + reportRef: reportRef(reportDir), + scope: { + launchScope: "P0 + light P1", + stabilizationDaysRequired: 7, + smallTeamFormalUse: true, + wecomHrAttendanceStatus: "frozen_at_wecom_owned_oa_archive_audit", + }, + evidence: { + latestOaops, + latestAssetTrial, + latestAssetImport, + backupEvidence, + permissionEvidence, + businessEvidence, + stabilization, + }, + issueStatus: { + oaopsRequired: oaopsStatuses, + oalaunchRequired, + oalaunchStatuses, + assetDone, + }, + gates: { + openCount: openGates.length, + openGates, + findings, + }, + manualGates: { + day7GoNoGo: [ + "A:接受 PASS_OA_LAUNCH_READY,进入小团队正式启用。", + "B:接受 PASS_WITH_FINDINGS,但指定 owner 和完成日期后启用。", + "C:暂停启用,先整改。", + ], + blockerHandling: [ + "A:当天新建修复合同并修复。", + "B:临时人工绕行,记录风险。", + "C:暂停对应流程。", + ], + backupDrill: [ + "A:接受恢复演练结果,进入 Go/No-Go。", + "B:修复后重跑恢复演练。", + "C:暂停正式启用。", + ], + }, + boundaries: { + defaultReadOnly: true, + noBusinessWritesByThisScript: true, + noWeComWrites: true, + noPaymentExecution: true, + noDbResetOrRestore: true, + frozenItems: [ + "image_ocr", + "complex_bpm", + "budget_control", + "erp_write", + "bank_statement_import", + "real_payment_provider", + "wecom_hr_deep_mirror", + "emergency_takeover", + "recovery_writeback", + "ruleops", + ], + }, + privacy: "Report keeps paths/status/counts only; no passwords, tokens, Secrets, full accounts, contract bodies, bank statements, raw invoice images, or raw payloads.", +}; + +fs.writeFileSync(path.join(reportDir, "summary.json"), `${JSON.stringify(summary, null, 2)}\n`, "utf8"); +console.log(`[oalaunch1] summary=${reportRef(path.join(reportDir, "summary.json"))}`); +if (status === "SKIP/BLOCKED_BY_EXTERNAL_CONFIG") process.exitCode = 1; +NODE +} + +run_privacy_smoke() { + local sensitive_pattern='(["'\''"]?(WECOM_[A-Z0-9_]*(SECRET|TOKEN)|[A-Z0-9_]*(TOKEN|SECRET|PASSWORD|ENCODING_AES_KEY|ENCODINGAESKEY|CORP_SECRET|ACCESS_TOKEN)[A-Z0-9_]*)["'\''"]?[[:space:]]*[:=][[:space:]]*["'\''"]?[A-Za-z0-9._:/+=-]{8,}["'\''"]?)|Authorization:[[:space:]]*Bearer[[:space:]]+[A-Za-z0-9._-]{8,}|access_token=[A-Za-z0-9._-]{8,}|-----BEGIN|BEGIN [A-Z ]*KEY' + local matches + matches="$(grep -RInE "$sensitive_pattern" "$REPORT_DIR" || true)" + if [[ -n "$matches" ]]; then + printf '%s\n' "$matches" >"$REPORT_DIR/privacy-findings.txt" + log "[oalaunch1][FAIL] report-privacy-smoke matched sensitive pattern" + STATUS=1 + else + log "[oalaunch1][PASS] report-privacy-smoke" + fi +} + +finish() { + write_manual_gates + run_privacy_smoke + write_summary_json || STATUS=1 + local before_privacy_status="$STATUS" + run_privacy_smoke + if [[ "$STATUS" != "$before_privacy_status" ]]; then + write_summary_json || true + fi + local report_ref="${REPORT_DIR#$ROOT_DIR/}" + log "[oalaunch1] report=$report_ref" + exit "$STATUS" +} + +trap finish EXIT + +log "[oalaunch1] mode=oa-final-launch-readiness" +log "[oalaunch1] default=no live writes, no business writes, no DB restore" +run_optional_live_checks