From d61b254064c044ef4ef8cc24d31d8374fd8ad9d7 Mon Sep 17 00:00:00 2001 From: shunli Date: Tue, 26 May 2026 14:03:51 +0800 Subject: [PATCH] =?UTF-8?q?oaops1-patrol-feature-review=20=E6=94=B9?= =?UTF-8?q?=E8=BF=9B=20OAOPS1=20=E5=B7=A1=E6=A3=80=E6=8A=A5=E5=91=8A?= =?UTF-8?q?=E4=BC=A0=E6=92=AD=E4=B8=8E=E8=84=B1=E6=95=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .trellis/spec/hyb-oa/docs-and-evidence.md | 87 ++++ .../check.jsonl | 4 + .../implement.jsonl | 4 + .../implement.md | 37 ++ .../info.md | 155 +++++++ .../05-26-oaops1-patrol-feature-review/prd.md | 55 +++ .../manual-gates.md | 22 + .../summary.json | 400 ++++++++++++++++++ .../authenticated-readonly-probe.json | 96 +++++ .../live-smoke-readiness/summary.json | 11 + .../manual-gates.md | 20 + .../oa-only-smoke-readiness/summary.json | 168 ++++++++ .../summary.json | 284 +++++++++++++ .../task.json | 34 ++ scripts/qa/run-oa-real-use-daily.sh | 85 +++- 15 files changed, 1448 insertions(+), 14 deletions(-) create mode 100644 .trellis/tasks/05-26-oaops1-patrol-feature-review/check.jsonl create mode 100644 .trellis/tasks/05-26-oaops1-patrol-feature-review/implement.jsonl create mode 100644 .trellis/tasks/05-26-oaops1-patrol-feature-review/implement.md create mode 100644 .trellis/tasks/05-26-oaops1-patrol-feature-review/info.md create mode 100644 .trellis/tasks/05-26-oaops1-patrol-feature-review/prd.md create mode 100644 .trellis/tasks/05-26-oaops1-patrol-feature-review/research/oalaunch-final-after-patrol-fix/manual-gates.md create mode 100644 .trellis/tasks/05-26-oaops1-patrol-feature-review/research/oalaunch-final-after-patrol-fix/summary.json create mode 100644 .trellis/tasks/05-26-oaops1-patrol-feature-review/research/oaops1-after-patrol-fix-final/authenticated-readonly-probe.json create mode 100644 .trellis/tasks/05-26-oaops1-patrol-feature-review/research/oaops1-after-patrol-fix-final/live-smoke-readiness/summary.json create mode 100644 .trellis/tasks/05-26-oaops1-patrol-feature-review/research/oaops1-after-patrol-fix-final/manual-gates.md create mode 100644 .trellis/tasks/05-26-oaops1-patrol-feature-review/research/oaops1-after-patrol-fix-final/oa-only-smoke-readiness/summary.json create mode 100644 .trellis/tasks/05-26-oaops1-patrol-feature-review/research/oaops1-after-patrol-fix-final/summary.json create mode 100644 .trellis/tasks/05-26-oaops1-patrol-feature-review/task.json diff --git a/.trellis/spec/hyb-oa/docs-and-evidence.md b/.trellis/spec/hyb-oa/docs-and-evidence.md index ea4c25ee..fe2fc4b7 100644 --- a/.trellis/spec/hyb-oa/docs-and-evidence.md +++ b/.trellis/spec/hyb-oa/docs-and-evidence.md @@ -15,3 +15,90 @@ Start from `docs/文档索引.md` and `docs/文档事实源状态矩阵.md`. - For generated evidence, store report paths and conclusions, not raw secrets, raw payloads, full account identifiers, invoice originals or confidential business content. + +## Scenario: QA Readiness Report Summaries + +### 1. Scope / Trigger + +- Trigger: QA shell runners aggregate child reports for launch, patrol, + smoke or readiness gates. +- Applies to generated `summary.json`, step JSON and privacy findings under + `artifacts/qa-flow/` or Trellis task research directories. + +### 2. Signatures + +- Parent shell runner writes `summary.json`. +- Child runner summary input: `/summary.json`. +- Step summary input/output: `.step.json`. + +### 3. Contracts + +- Use repo-relative `reportRef` / `logRef` when a path is inside the repo. +- Do not write absolute local workspace paths into `summary.json`. +- Parent summaries must include child report status and child finding counts. +- Parent `findingCount` must become non-zero when any child summary reports + `PASS_WITH_FINDINGS`, `SKIP`, `PARTIAL`, `PAUSED`, `MISSING`, blocker-like + status or child `findingCount > 0`. +- Machine-readable reports may include status, count, mode, conclusion, + boolean operational flags and relative report refs. +- Machine-readable reports must not include amounts, prices, costs, balances, + bank details, invoice values, full accounts, tokens, secrets, raw payloads, + raw form values or full local paths. + +### 4. Validation & Error Matrix + +- Child `PASS` + `findingCount=0` -> parent may stay pass. +- Child `PASS_WITH_FINDINGS` -> parent records a finding. +- Child `SKIP` / `PARTIAL` / `PAUSED` / `MISSING` -> parent records + `manual_or_config`. +- Child `FAIL` / `BLOCKED` / `ERROR` -> parent records a finding or blocker + according to whether the parent step is required. +- Privacy smoke match -> write redacted finding metadata and make final + summary reflect the failure. + +### 5. Good/Base/Bad Cases + +- Good: `summary.json` contains `reportRef`, `childReports..status`, + `childReports..findingCount` and no absolute local path. +- Base: Missing runtime credentials produce `PASS_WITH_FINDINGS` or + `manual_or_config`, not a clean pass. +- Bad: Parent command exits 0 for a child `PASS_WITH_FINDINGS` and the parent + summary still says clean pass. + +### 6. Tests Required + +- `bash -n .sh` +- Run the parent runner in a missing-credential scenario and assert: + - parent status is `PASS_WITH_FINDINGS` or blocked, not clean pass; + - child finding count is visible in parent summary; + - privacy step is present in parent summary; + - `summary.json` has no full workspace path or business amount fields. +- Run the matching launch/readiness aggregator when it consumes the report. + +### 7. Wrong vs Correct + +#### Wrong + +```json +{ + "status": "PASS_OA_REAL_USE_DAILY_READY", + "reportDir": "/home/user/project/artifacts/qa-flow/run", + "childReports": {} +} +``` + +#### Correct + +```json +{ + "status": "PASS_WITH_FINDINGS", + "reportRef": "artifacts/qa-flow/run", + "childReports": { + "oaOnlySmokeReadiness": { + "status": "PASS_WITH_FINDINGS", + "reportRef": "artifacts/qa-flow/run/oa-only-smoke-readiness/summary.json", + "findingCount": 7 + } + } +} +``` diff --git a/.trellis/tasks/05-26-oaops1-patrol-feature-review/check.jsonl b/.trellis/tasks/05-26-oaops1-patrol-feature-review/check.jsonl new file mode 100644 index 00000000..9efafeec --- /dev/null +++ b/.trellis/tasks/05-26-oaops1-patrol-feature-review/check.jsonl @@ -0,0 +1,4 @@ +{"file": ".trellis/spec/hyb-oa/project-governance.md", "reason": "Check review stayed within approved Trellis task scope."} +{"file": ".trellis/spec/hyb-oa/deployment-trialrun.md", "reason": "Check no public write/deployment/payment/database boundary was crossed."} +{"file": ".trellis/spec/hyb-oa/docs-and-evidence.md", "reason": "Check findings and report references avoid raw evidence leakage."} +{"file": ".trellis/spec/hyb-oa/security-privacy.md", "reason": "Check privacy findings for path, secret and sensitive business data exposure."} diff --git a/.trellis/tasks/05-26-oaops1-patrol-feature-review/implement.jsonl b/.trellis/tasks/05-26-oaops1-patrol-feature-review/implement.jsonl new file mode 100644 index 00000000..bc9990ca --- /dev/null +++ b/.trellis/tasks/05-26-oaops1-patrol-feature-review/implement.jsonl @@ -0,0 +1,4 @@ +{"file": ".trellis/spec/hyb-oa/project-governance.md", "reason": "Review task governance and approval boundaries."} +{"file": ".trellis/spec/hyb-oa/deployment-trialrun.md", "reason": "Public OA patrol, deployment and real-data operation boundaries."} +{"file": ".trellis/spec/hyb-oa/docs-and-evidence.md", "reason": "Evidence/report handling requirements."} +{"file": ".trellis/spec/hyb-oa/security-privacy.md", "reason": "Sensitive data and full local path exclusion rules for patrol reports."} diff --git a/.trellis/tasks/05-26-oaops1-patrol-feature-review/implement.md b/.trellis/tasks/05-26-oaops1-patrol-feature-review/implement.md new file mode 100644 index 00000000..69695772 --- /dev/null +++ b/.trellis/tasks/05-26-oaops1-patrol-feature-review/implement.md @@ -0,0 +1,37 @@ +# OAOPS1 Patrol Review Execution + +## What Was Reviewed + +This task reviewed patrol/readiness behavior only. No production operation was executed. + +Reviewed files: + +- `scripts/qa/run-oa-real-use-daily.sh` +- `scripts/qa/run-oa-final-launch-readiness.sh` +- `scripts/qa/check-oa-only-smoke-readiness.mjs` +- `docs/管理员手册.md` +- `docs/文档事实源状态矩阵.md` + +## Verification + +Commands run: + +- `bash -n scripts/qa/run-oa-real-use-daily.sh` +- `bash -n scripts/qa/run-oa-final-launch-readiness.sh` +- `python3 ./.trellis/scripts/task.py validate 05-26-oaops1-patrol-feature-review` +- `node scripts/qa/check-trellis-governance.mjs` +- `git diff --check` +- `OALAUNCH_REPORT_DIR=.trellis/tasks/05-26-oaops1-patrol-feature-review/research/oalaunch-after-patrol-fix bash scripts/qa/run-oa-final-launch-readiness.sh` +- `timeout 240s env OAOPS1_REPORT_DIR=.trellis/tasks/05-26-oaops1-patrol-feature-review/research/oaops1-after-patrol-fix-final OAOPS1_SKIP_ENV_FILE=1 OAOPS1_LOGIN_STEP_COOLDOWN_SECONDS=0 OAOPS1_E2E_COOLDOWN_SECONDS=0 bash scripts/qa/run-oa-real-use-daily.sh` with escalated read-only network/browser permissions. + +Initial governance check failed only because this complex review task was missing `implement.md`. This file records that execution note. + +## Result + +The patrol feature was improved before starting the formal 7-day OAOPS1 window. + +See `info.md` for findings, implementation details and validation evidence. + +## Spec Update Decision + +The implementation changed reusable QA report behavior. Updated `.trellis/spec/hyb-oa/docs-and-evidence.md` with the QA readiness report summary contract. diff --git a/.trellis/tasks/05-26-oaops1-patrol-feature-review/info.md b/.trellis/tasks/05-26-oaops1-patrol-feature-review/info.md new file mode 100644 index 00000000..d40b0b59 --- /dev/null +++ b/.trellis/tasks/05-26-oaops1-patrol-feature-review/info.md @@ -0,0 +1,155 @@ +# OAOPS1 Patrol Review Findings + +## Review Scope + +Reviewed: + +- `scripts/qa/run-oa-real-use-daily.sh` +- `scripts/qa/run-oa-final-launch-readiness.sh` +- `scripts/qa/check-oa-only-smoke-readiness.mjs` +- `docs/管理员手册.md` +- `docs/文档事实源状态矩阵.md` + +Commands run: + +- `bash -n scripts/qa/run-oa-real-use-daily.sh` +- `bash -n scripts/qa/run-oa-final-launch-readiness.sh` + +No deployment, public OA writes, Enterprise WeChat writes, payment actions or database restore were performed. + +## Findings + +### High: Daily patrol can hide child `PASS_WITH_FINDINGS` results + +`run-oa-real-use-daily.sh` wraps child checks with `run_finding_step`, which marks a step as `PASS` whenever the command exits 0. `check-oa-only-smoke-readiness.mjs` returns exit 0 when role credentials are missing but emits summary status `PASS_WITH_FINDINGS`. The parent summary only inspects parent step status plus the local authenticated probe, so child `PASS_WITH_FINDINGS` can be lost and the daily patrol can conclude `PASS_OA_REAL_USE_DAILY_READY`. + +Why it matters: + +- `docs/管理员手册.md` requires missing live smoke credentials to be recorded as finding or SKIP, not disguised as pass. +- OALAUNCH1 reads `latestOaops.findingCount`; if the daily report loses child findings, final readiness can understate risk. + +Evidence: + +- `scripts/qa/run-oa-real-use-daily.sh` lines 63-66 mark a child command as `PASS` only by exit code. +- `scripts/qa/check-oa-only-smoke-readiness.mjs` lines 253-259 emits `PASS_WITH_FINDINGS` when credentials are skipped. +- `scripts/qa/check-oa-only-smoke-readiness.mjs` line 288 exits non-zero only for blocking failures. +- `scripts/qa/run-oa-real-use-daily.sh` lines 412-426 build findings without reading the child summary statuses. + +Recommended fix: + +- Add a report-aware step wrapper for child reports. After each child command, read its `summary.json`; promote `PASS_WITH_FINDINGS`, `SKIP`, `PARTIAL`, `PAUSED` and child `findingCount > 0` into parent findings. +- Include `oa-only-smoke-readiness` in `childReports`, not just `live-readiness`. + +### High: Privacy smoke failure can leave `summary.json` saying PASS + +`run-oa-real-use-daily.sh` writes `summary.json` before running `report-privacy-smoke`. If privacy smoke fails, it sets `STATUS=1` and exits non-zero, but the already-written summary is not regenerated. The report can therefore contain a PASS conclusion while the script fails. + +Why it matters: + +- Day 7 review is driven by `summary.json`. +- A privacy violation must be visible in the machine-readable report, not only in process exit code or console output. + +Evidence: + +- `scripts/qa/run-oa-real-use-daily.sh` lines 505-510 write summary, then run privacy smoke, then exit. +- `scripts/qa/run-oa-final-launch-readiness.sh` lines 483-486 already has the better pattern: rewrite summary when privacy status changes. + +Recommended fix: + +- Mirror the OALAUNCH1 pattern in OAOPS1: run privacy smoke after summary, then rewrite summary if status changed. +- Add an explicit `report-privacy-smoke` step/finding to the daily summary. + +### Medium: Privacy smoke prints raw matches to stdout + +OAOPS1 privacy smoke prints the full grep matches when it detects sensitive patterns. If the match is a token, phone number, email or raw key line, the scanner itself leaks it into terminal/CI logs. + +Why it matters: + +- Privacy tooling should not echo the sensitive value it detected. +- This violates the current report discipline: status/count/path summaries only. + +Evidence: + +- `scripts/qa/run-oa-real-use-daily.sh` lines 347-351 capture and print raw grep matches. + +Recommended fix: + +- Store only redacted match metadata: file path, line number and rule name. +- Do not print full matched lines. + +### Medium: Daily summary writes full local paths + +OAOPS1 step JSON and summary use absolute local paths for `logPath` and `reportDir`. Current project privacy rules prohibit complete local paths in task/docs/reports. + +Why it matters: + +- The final launch script already uses repo-relative `reportRef` for this reason. +- These daily reports may be copied into Trellis task evidence or user-facing summaries. + +Evidence: + +- `scripts/qa/run-oa-real-use-daily.sh` line 35 writes absolute `logPath`. +- `scripts/qa/run-oa-real-use-daily.sh` line 380 writes absolute missing-step `logPath`. +- `scripts/qa/run-oa-real-use-daily.sh` line 443 writes absolute `reportDir`. +- `scripts/qa/run-oa-final-launch-readiness.sh` lines 160-167 use safer relative `reportRef`. + +Recommended fix: + +- Add a `reportRef(file)` helper to OAOPS1 and use repo-relative paths when possible. +- Replace `reportDir` with `reportRef` or keep `reportDirName` only. + +### Medium: Read-only probe can record business totals, not only counts/status + +The authenticated read-only probe records any short string field that is not excluded by a narrow key regex. In observed evidence, reimbursement aggregate amount appears in the patrol report. The report policy says generated evidence should keep status/count/report-path summaries, not business amounts. + +Why it matters: + +- Aggregate amounts can still be sensitive business information in a small team. +- OALAUNCH evidence should remain safe to copy into task notes. + +Evidence: + +- `scripts/qa/run-oa-real-use-daily.sh` lines 197-207 allow numeric/boolean values and short strings. +- Observed latest daily report includes a reimbursement `totalAmount` aggregate in the authenticated probe summary. + +Recommended fix: + +- Change `summarizePayload` to an allowlist: item counts, totals as record counts only, status/mode/conclusion and booleans that are operational states. +- Drop amount, money, balance, price, cost, bank and invoice-value-like fields. + +## Overall Assessment + +The patrol function is directionally useful and already covers the right operational domains: public readiness, live smoke, feature flags, OA-only roles, read-only business probes, mobile/WeCom entry and privacy checks. + +Before starting the formal 7-day OAOPS1 window, it should be improved in a small technical task. The most important fix is report-status correctness: parent reports must faithfully include child findings and privacy failures. + +## Suggested Improvement Order + +1. Fix child summary propagation and parent `findingCount`. +2. Fix privacy-smoke summary rewrite and redacted match output. +3. Replace absolute paths with report refs. +4. Reduce authenticated probe payload summaries to operational counts/status only. +5. Re-run `bash -n`, default OAOPS1 daily, and default OALAUNCH1 readiness. + +## Implementation Result + +Implemented in `scripts/qa/run-oa-real-use-daily.sh`: + +- Step JSON now writes repo-relative `logRef` instead of absolute `logPath`. +- Daily summary now writes `reportRef` instead of absolute `reportDir`. +- Parent daily summary reads child `summary.json` for `live-smoke-readiness` and `oa-only-smoke-readiness`. +- Child `PASS_WITH_FINDINGS`, `SKIP`, `MISSING`, `PAUSED`, `PARTIAL`, blocker statuses and child finding counts now contribute to parent findings. +- Privacy smoke now writes redacted `privacy-findings.txt` metadata instead of printing raw grep matches. +- Privacy smoke now records a `report-privacy-smoke` step and summary is rewritten after privacy checks. +- Authenticated probe payload summary now uses an allowlist and excludes amount, money, price, cost, balance, bank, receipt, account, invoice, tax, fee, paid and payment fields. + +Final validation report: + +- `.trellis/tasks/05-26-oaops1-patrol-feature-review/research/oaops1-after-patrol-fix-final/summary.json` +- Status: `PASS_WITH_FINDINGS` +- Parent finding count: 4 +- `childReports.oaOnlySmokeReadiness.status`: `PASS_WITH_FINDINGS` +- `childReports.oaOnlySmokeReadiness.findingCount`: 7 +- `report-privacy-smoke`: `PASS` +- No full local path matches in `summary.json`. +- No business amount key matches in `summary.json` or `authenticated-readonly-probe.json`. diff --git a/.trellis/tasks/05-26-oaops1-patrol-feature-review/prd.md b/.trellis/tasks/05-26-oaops1-patrol-feature-review/prd.md new file mode 100644 index 00000000..75315dd7 --- /dev/null +++ b/.trellis/tasks/05-26-oaops1-patrol-feature-review/prd.md @@ -0,0 +1,55 @@ +# OAOPS1 patrol feature review + +## Goal + +审查当前 OAOPS1/OALAUNCH1 巡检功能是否足以支撑 7 天真实使用稳定化和最终启用 Go/No-Go,判断是否需要改进,并把结论、风险和建议记录到当前 Trellis task。 + +## Scope + +- 只读审查巡检相关脚本、输出报告结构和事实源要求。 +- 重点覆盖 `scripts/qa/run-oa-real-use-daily.sh` 和 `scripts/qa/run-oa-final-launch-readiness.sh`。 +- 必要时参考管理员手册、文档索引、事实源状态矩阵和 Trellis spec。 +- 用户确认“开始改进”后,允许在本 task 内对巡检报告逻辑做最小代码改进。 +- 不部署、不跑公网写入、不写企业微信、不执行付款或数据库恢复。 + +## What I Already Know + +- OALAUNCH1 当前结论为 `PAUSED_MANUAL_CONFIRMATION`,主要缺口在人工证据、7 天稳定化、权限矩阵、备份恢复演练和 Day 7 Go/No-Go。 +- OAOPS1 每日巡检脚本默认只读,真实业务写入由人工完成。 +- OALAUNCH1 最终启用脚本默认只聚合证据,不创建业务数据、不执行付款、不写企业微信、不做数据库恢复。 + +## Review Questions + +- 当前巡检是否能稳定证明 OAOPS1 的真实使用状态? +- 当前巡检是否能让 Day 7 决策人快速知道阻断项、finding 和人工动作? +- 当前巡检是否有误报/漏报、敏感信息、可维护性或长期运行问题? +- 是否有必要在进入 7 天真实使用前先改进巡检脚本? + +## Acceptance Criteria + +- [ ] 列出明确 findings,按严重程度排序,并引用具体文件/行号。 +- [ ] 说明哪些问题必须在 7 天真实使用前修,哪些可后置。 +- [ ] 给出建议实施顺序和验证命令。 +- [ ] 子报告 `PASS_WITH_FINDINGS` / `SKIP` / `PARTIAL` / `PAUSED` 能进入父巡检 summary。 +- [ ] 隐私扫描失败能进入机器可读 summary,且不打印原始敏感命中行。 +- [ ] 巡检 summary 使用相对 report ref,不写完整本地路径。 +- [ ] 只读探针摘要不输出业务金额、成本、银行、票据等敏感业务值。 +- [ ] 不泄露 Secret、完整账号、合同正文、银行资料、完整本地路径或原始 payload。 + +## Out Of Scope + +- 不执行部署。 +- 不写公网业务数据。 +- 不执行付款。 +- 不写企业微信。 +- 不做生产数据库恢复或重置。 +- 不直接启用 WREAL1 或深度镜像。 + +## Test Plan If Changes Are Approved Later + +- `bash -n scripts/qa/run-oa-real-use-daily.sh` +- `bash -n scripts/qa/run-oa-final-launch-readiness.sh` +- `bash scripts/qa/run-oa-real-use-daily.sh`,默认只读,按凭据缺失可解释为 finding/SKIP。 +- `bash scripts/qa/run-oa-final-launch-readiness.sh`,默认只读。 +- `node scripts/qa/check-trellis-governance.mjs` +- `git diff --check` diff --git a/.trellis/tasks/05-26-oaops1-patrol-feature-review/research/oalaunch-final-after-patrol-fix/manual-gates.md b/.trellis/tasks/05-26-oaops1-patrol-feature-review/research/oalaunch-final-after-patrol-fix/manual-gates.md new file mode 100644 index 00000000..9e424e02 --- /dev/null +++ b/.trellis/tasks/05-26-oaops1-patrol-feature-review/research/oalaunch-final-after-patrol-fix/manual-gates.md @@ -0,0 +1,22 @@ +# 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 已完成后,补真实资产责任人或保管人抽查。 diff --git a/.trellis/tasks/05-26-oaops1-patrol-feature-review/research/oalaunch-final-after-patrol-fix/summary.json b/.trellis/tasks/05-26-oaops1-patrol-feature-review/research/oalaunch-final-after-patrol-fix/summary.json new file mode 100644 index 00000000..3ba14486 --- /dev/null +++ b/.trellis/tasks/05-26-oaops1-patrol-feature-review/research/oalaunch-final-after-patrol-fix/summary.json @@ -0,0 +1,400 @@ +{ + "issueId": "OALAUNCH1", + "mode": "oa-final-launch-readiness", + "status": "PAUSED_MANUAL_CONFIRMATION", + "conclusion": "PAUSED_MANUAL_CONFIRMATION", + "baseURL": "https://oa.hyinda.com", + "commit": "267240b", + "checkedAt": "2026-05-26T05:54:06.208Z", + "reportRef": ".trellis/tasks/05-26-oaops1-patrol-feature-review/research/oalaunch-final-after-patrol-fix", + "scope": { + "launchScope": "P0 + light P1", + "stabilizationDaysRequired": 7, + "smallTeamFormalUse": true, + "wecomHrAttendanceStatus": "frozen_at_wecom_owned_oa_archive_audit" + }, + "evidence": { + "latestOaops": { + "name": "oa-real-use-daily-20260525-205848", + "reportRef": "artifacts/qa-flow/oa-real-use-daily-20260525-205848/summary.json", + "status": "PASS_OA_REAL_USE_DAILY_READY", + "findingCount": 0 + }, + "latestAssetTrial": { + "name": "asset1d-public-trial-20260523-155346", + "reportRef": "artifacts/qa-flow/asset1d-public-trial-20260523-155346/summary.json", + "status": "PASS_ASSET_UI_TRIAL_READY", + "findingCount": 0 + }, + "latestAssetImport": { + "name": "asset-redacted-index-import-live-20260523-162358", + "reportRef": "artifacts/qa-flow/asset-redacted-index-import-live-20260523-162358/summary.json", + "status": "PASS_EA_DOC_IMPORT_READY", + "findingCount": 0 + }, + "backupEvidence": null, + "permissionEvidence": null, + "businessEvidence": null, + "policyEvidence": { + "provided": true, + "exists": true, + "ref": "issues/2026-05-24_01-20-52-oa-policy-system-alignment-v1.csv" + }, + "stabilization": { + "status": "MISSING_START_DATE", + "startDay": null, + "requiredDays": [], + "observedDays": [ + "20260522", + "20260523", + "20260524", + "20260525" + ], + "dayReports": {} + } + }, + "issueStatus": { + "oaopsRequired": { + "OAOPS1-3": { + "title": "报销 PDF OCR 与付款闭环真实使用门禁", + "dev": "TODO", + "review": "TODO", + "regression": "TODO", + "done": false + }, + "OAOPS1-4": { + "title": "采购费用与项目关联真实使用门禁", + "dev": "TODO", + "review": "TODO", + "regression": "TODO", + "done": false + }, + "OAOPS1-5": { + "title": "合同台账与合同归档真实使用门禁", + "dev": "TODO", + "review": "TODO", + "regression": "TODO", + "done": false + }, + "OAOPS1-6": { + "title": "每日问题登记与阻断即修机制", + "dev": "TODO", + "review": "TODO", + "regression": "TODO", + "done": false + } + }, + "oalaunchRequired": [ + "OALAUNCH1-0", + "OALAUNCH1-1", + "OALAUNCH1-2", + "OALAUNCH1-3", + "OALAUNCH1-4", + "OALAUNCH1-5", + "OALAUNCH1-6", + "OALAUNCH1-7", + "OALAUNCH1-8" + ], + "oalaunchStatuses": { + "OALAUNCH1-0": { + "title": "建立最终启用 Go/No-Go 合同", + "dev": "DONE", + "review": "DONE", + "regression": "DONE", + "done": true + }, + "OALAUNCH1-1": { + "title": "新增最终启用证据汇总脚本", + "dev": "DONE", + "review": "DONE", + "regression": "DONE", + "done": true + }, + "OALAUNCH1-2": { + "title": "回写最终启用事实源和操作清单", + "dev": "DONE", + "review": "DONE", + "regression": "DONE", + "done": true + }, + "OALAUNCH1-3": { + "title": "OAOPS1 真实业务闭环收口门禁", + "dev": "TODO", + "review": "TODO", + "regression": "TODO", + "done": false + }, + "OALAUNCH1-4": { + "title": "7 天稳定化与每日巡检门禁", + "dev": "TODO", + "review": "TODO", + "regression": "TODO", + "done": false + }, + "OALAUNCH1-5": { + "title": "权限矩阵最终验收", + "dev": "DONE", + "review": "DONE", + "regression": "DONE", + "done": true + }, + "OALAUNCH1-6": { + "title": "备份恢复演练门禁", + "dev": "TODO", + "review": "TODO", + "regression": "TODO", + "done": false + }, + "OALAUNCH1-7": { + "title": "数据治理培训与运营支持门禁", + "dev": "TODO", + "review": "TODO", + "regression": "TODO", + "done": false + }, + "OALAUNCH1-8": { + "title": "OALAUNCH1 本地门禁与收口", + "dev": "DONE", + "review": "DONE", + "regression": "DONE", + "done": true + } + }, + "opolicyRequired": [ + "OPOLICY1-0", + "OPOLICY1-1", + "OPOLICY1-2", + "OPOLICY1-3", + "OPOLICY1-4", + "OPOLICY1-5", + "OPOLICY1-6", + "OPOLICY1-7" + ], + "opolicyStatuses": { + "OPOLICY1-0": { + "title": "建立制度与 OA 启用门禁对齐合同", + "dev": "DONE", + "review": "DONE", + "regression": "DONE", + "done": true + }, + "OPOLICY1-1": { + "title": "审视制度合理性和小团队裁剪口径", + "dev": "DONE", + "review": "DONE", + "regression": "DONE", + "done": true + }, + "OPOLICY1-2": { + "title": "调整制度包为小团队 V1 试运行口径", + "dev": "DONE", + "review": "DONE", + "regression": "DONE", + "done": true + }, + "OPOLICY1-3": { + "title": "回写 hyb-oa 事实源和员工/管理员口径", + "dev": "DONE", + "review": "DONE", + "regression": "DONE", + "done": true + }, + "OPOLICY1-4": { + "title": "制度参数和人工问答门禁清单", + "dev": "DONE", + "review": "DONE", + "regression": "DONE", + "done": true + }, + "OPOLICY1-5": { + "title": "评估 OA 必要小改范围", + "dev": "DONE", + "review": "DONE", + "regression": "DONE", + "done": true + }, + "OPOLICY1-6": { + "title": "OALAUNCH1 数据治理证据接入", + "dev": "DONE", + "review": "DONE", + "regression": "DONE", + "done": true + }, + "OPOLICY1-7": { + "title": "本地门禁与文档审计", + "dev": "DONE", + "review": "DONE", + "regression": "DONE", + "done": true + } + }, + "assetDone": true + }, + "gates": { + "openCount": 13, + "openGates": [ + { + "gate": "OAOPS1-3", + "reason": "OAOPS1 真实业务闭环仍未完成", + "state": { + "title": "报销 PDF OCR 与付款闭环真实使用门禁", + "dev": "TODO", + "review": "TODO", + "regression": "TODO", + "done": false + } + }, + { + "gate": "OAOPS1-4", + "reason": "OAOPS1 真实业务闭环仍未完成", + "state": { + "title": "采购费用与项目关联真实使用门禁", + "dev": "TODO", + "review": "TODO", + "regression": "TODO", + "done": false + } + }, + { + "gate": "OAOPS1-5", + "reason": "OAOPS1 真实业务闭环仍未完成", + "state": { + "title": "合同台账与合同归档真实使用门禁", + "dev": "TODO", + "review": "TODO", + "regression": "TODO", + "done": false + } + }, + { + "gate": "OAOPS1-6", + "reason": "OAOPS1 真实业务闭环仍未完成", + "state": { + "title": "每日问题登记与阻断即修机制", + "dev": "TODO", + "review": "TODO", + "regression": "TODO", + "done": false + } + }, + { + "gate": "OALAUNCH1-3", + "reason": "OALAUNCH1 本轮合同/脚本/事实源/门禁尚未全部回填 DONE", + "state": { + "title": "OAOPS1 真实业务闭环收口门禁", + "dev": "TODO", + "review": "TODO", + "regression": "TODO", + "done": false + } + }, + { + "gate": "OALAUNCH1-4", + "reason": "OALAUNCH1 本轮合同/脚本/事实源/门禁尚未全部回填 DONE", + "state": { + "title": "7 天稳定化与每日巡检门禁", + "dev": "TODO", + "review": "TODO", + "regression": "TODO", + "done": false + } + }, + { + "gate": "OALAUNCH1-6", + "reason": "OALAUNCH1 本轮合同/脚本/事实源/门禁尚未全部回填 DONE", + "state": { + "title": "备份恢复演练门禁", + "dev": "TODO", + "review": "TODO", + "regression": "TODO", + "done": false + } + }, + { + "gate": "OALAUNCH1-7", + "reason": "OALAUNCH1 本轮合同/脚本/事实源/门禁尚未全部回填 DONE", + "state": { + "title": "数据治理培训与运营支持门禁", + "dev": "TODO", + "review": "TODO", + "regression": "TODO", + "done": false + } + }, + { + "gate": "SEVEN_DAY_STABILIZATION", + "reason": "缺 7 天稳定化起始日期;设置 OALAUNCH_STABILIZATION_START_DATE=YYYY-MM-DD 后按连续 7 天检查", + "stabilization": { + "status": "MISSING_START_DATE", + "startDay": null, + "requiredDays": [], + "observedDays": [ + "20260522", + "20260523", + "20260524", + "20260525" + ], + "dayReports": {} + } + }, + { + "gate": "BACKUP_RESTORE_DRILL", + "reason": "缺备份恢复演练或影子库恢复证据;可用 OALAUNCH_BACKUP_EVIDENCE 指定脱敏证据路径" + }, + { + "gate": "PERMISSION_MATRIX", + "reason": "缺最终权限矩阵负例/角色入口证据;可用 OALAUNCH_PERMISSION_EVIDENCE 指定脱敏证据路径" + }, + { + "gate": "BUSINESS_ACCEPTANCE_EVIDENCE", + "reason": "缺真实业务闭环证据;可用 OALAUNCH_BUSINESS_EVIDENCE 指定脱敏证据路径" + }, + { + "gate": "DAY7_GO_NO_GO", + "reason": "缺 Day 7 人工 Go/No-Go 接受结论;可用 OALAUNCH_MANUAL_ACCEPT=PASS_OA_LAUNCH_READY 或 PASS_WITH_FINDINGS 指定" + } + ], + "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", + "complex_procurement", + "e_signature", + "full_e_archive", + "erp_write", + "bank_statement_import", + "ai_rule_writes", + "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." +} diff --git a/.trellis/tasks/05-26-oaops1-patrol-feature-review/research/oaops1-after-patrol-fix-final/authenticated-readonly-probe.json b/.trellis/tasks/05-26-oaops1-patrol-feature-review/research/oaops1-after-patrol-fix-final/authenticated-readonly-probe.json new file mode 100644 index 00000000..97ed9760 --- /dev/null +++ b/.trellis/tasks/05-26-oaops1-patrol-feature-review/research/oaops1-after-patrol-fix-final/authenticated-readonly-probe.json @@ -0,0 +1,96 @@ +{ + "mode": "oaops1-authenticated-readonly-probe", + "status": "PASS_WITH_FINDINGS", + "baseUrl": "https://oa.hyinda.com", + "checkedAt": "2026-05-26T05:49:02.580Z", + "readOnly": true, + "duplicateRoleChecks": "employee/finance/payment/AP are covered by oa-only-smoke-readiness to avoid repeated login throttling", + "roles": { + "admin": { + "status": "SKIP", + "reason": "missing_admin_credentials", + "maskedIdentifier": null + } + }, + "checks": [ + { + "id": "portal", + "role": "admin", + "path": "/api/portal/home", + "domain": "portal", + "status": "SKIP", + "reason": "missing_admin_credentials" + }, + { + "id": "workflow-tasks", + "role": "admin", + "path": "/api/workflow/tasks?page=1&pageSize=5", + "domain": "approval", + "status": "SKIP", + "reason": "missing_admin_credentials" + }, + { + "id": "reimbursements", + "role": "admin", + "path": "/api/reimbursements?page=1&pageSize=5", + "domain": "reimbursement", + "status": "SKIP", + "reason": "missing_admin_credentials" + }, + { + "id": "reimbursement-summary", + "role": "admin", + "path": "/api/reimbursements/stats/summary", + "domain": "reimbursement", + "status": "SKIP", + "reason": "missing_admin_credentials" + }, + { + "id": "invoice-pool", + "role": "admin", + "path": "/api/invoice-pool?page=1&pageSize=5", + "domain": "invoice_ocr", + "status": "SKIP", + "reason": "missing_admin_credentials" + }, + { + "id": "purchase-requests", + "role": "admin", + "path": "/api/purchase-requests?page=1&pageSize=5", + "domain": "procurement", + "status": "SKIP", + "reason": "missing_admin_credentials" + }, + { + "id": "contracts-registry", + "role": "admin", + "path": "/api/contracts-registry", + "domain": "contracts", + "status": "SKIP", + "reason": "missing_admin_credentials" + }, + { + "id": "projects", + "role": "admin", + "path": "/api/projects?page=1&pageSize=5", + "domain": "projects", + "status": "SKIP", + "reason": "missing_admin_credentials" + }, + { + "id": "audit-logs", + "role": "admin", + "path": "/api/audit-logs?page=1&pageSize=5", + "domain": "audit", + "status": "SKIP", + "reason": "missing_admin_credentials" + } + ], + "boundaries": { + "noBusinessWrites": true, + "noExportsByDefault": true, + "noFileUploads": true, + "noWeComWrites": true, + "noRealPaymentExecution": true + } +} diff --git a/.trellis/tasks/05-26-oaops1-patrol-feature-review/research/oaops1-after-patrol-fix-final/live-smoke-readiness/summary.json b/.trellis/tasks/05-26-oaops1-patrol-feature-review/research/oaops1-after-patrol-fix-final/live-smoke-readiness/summary.json new file mode 100644 index 00000000..8aeca539 --- /dev/null +++ b/.trellis/tasks/05-26-oaops1-patrol-feature-review/research/oaops1-after-patrol-fix-final/live-smoke-readiness/summary.json @@ -0,0 +1,11 @@ +{ + "issueId": "OAOPS1", + "baseURL": "https://oa.hyinda.com", + "commit": "267240b", + "checkedAt": "2026-05-26T05:49:02.138Z", + "status": "PASS", + "mode": "live-smoke-readiness", + "reportDir": ".trellis/tasks/05-26-oaops1-patrol-feature-review/research/oaops1-after-patrol-fix-final/live-smoke-readiness", + "groups": null, + "notes": "公网 /ready、public bundle、pending OCR、live smoke 和真实 OCR 可用性检查;缺少凭据或样票时按脚本规则记录 SKIP。" +} \ No newline at end of file diff --git a/.trellis/tasks/05-26-oaops1-patrol-feature-review/research/oaops1-after-patrol-fix-final/manual-gates.md b/.trellis/tasks/05-26-oaops1-patrol-feature-review/research/oaops1-after-patrol-fix-final/manual-gates.md new file mode 100644 index 00000000..772e3329 --- /dev/null +++ b/.trellis/tasks/05-26-oaops1-patrol-feature-review/research/oaops1-after-patrol-fix-final/manual-gates.md @@ -0,0 +1,20 @@ +# OAOPS1 每日人工收口清单 + +## 员工 +- 真实报销:上传 PDF 发票,补充必要附件,提交 OA 审批。 +- 采购/费用:在 OA 创建申请,关联项目;如涉及合同,关联或补录合同台账。 +- 合同:只录入合同摘要、编号、负责人、项目、金额、线下归档位置;涉密合同正文、敏感报价和成本细节不上传公网 OA。 + +## 审批人 +- 从 OA 待办进入详情,确认报销、采购、合同或项目上下文后再审批。 +- 退回时写清需要补充的材料,避免线下口头说明替代系统记录。 + +## 出纳/财务 +- 审批通过后创建付款批次,线下银行渠道付款。 +- 上传银行回单附件后再在 OA 标记已付款。 +- 每日抽查 AP 台账、报销导出、票据状态和付款批次状态。 + +## 管理员 +- 查看本报告 summary.json 和人工问题登记。 +- 阻断项当天建修复 Issue CSV 或暂停对应流程;非阻断体验问题进入反馈池。 +- 确认 payment_batch_v1=force_on,erp_sync=force_off;图片 OCR、预算、ERP、银行流水导入、真实支付直连继续冻结。 diff --git a/.trellis/tasks/05-26-oaops1-patrol-feature-review/research/oaops1-after-patrol-fix-final/oa-only-smoke-readiness/summary.json b/.trellis/tasks/05-26-oaops1-patrol-feature-review/research/oaops1-after-patrol-fix-final/oa-only-smoke-readiness/summary.json new file mode 100644 index 00000000..b0b0787e --- /dev/null +++ b/.trellis/tasks/05-26-oaops1-patrol-feature-review/research/oaops1-after-patrol-fix-final/oa-only-smoke-readiness/summary.json @@ -0,0 +1,168 @@ +{ + "issueId": "OSMOKE1", + "mode": "oa-only-smoke-readiness", + "status": "PASS_WITH_FINDINGS", + "conclusion": "PASS_WITH_FINDINGS", + "baseUrl": "https://oa.hyinda.com", + "checkedAt": "2026-05-26T05:49:02.288Z", + "businessReadOnly": true, + "readOnlyNote": "performs login/readiness probes only; no business data writes and no WeCom writes", + "privacy": "role/masked identifier/status/counts only; no password/token/secret/full email/full phone", + "targets": [ + { + "key": "admin", + "expectedRoles": [ + "admin", + "employee" + ], + "maskedIdentifier": null, + "login": { + "status": "SKIP", + "reason": "missing_credentials", + "code": null + }, + "roleMatch": "SKIP", + "probes": [] + }, + { + "key": "employee", + "expectedRoles": [ + "employee" + ], + "maskedIdentifier": null, + "login": { + "status": "SKIP", + "reason": "missing_credentials", + "code": null + }, + "roleMatch": "SKIP", + "probes": [] + }, + { + "key": "manager", + "expectedRoles": [ + "manager", + "employee" + ], + "maskedIdentifier": null, + "login": { + "status": "SKIP", + "reason": "missing_credentials", + "code": null + }, + "roleMatch": "SKIP", + "probes": [] + }, + { + "key": "ceo", + "expectedRoles": [ + "ceo", + "employee" + ], + "maskedIdentifier": null, + "login": { + "status": "SKIP", + "reason": "missing_credentials", + "code": null + }, + "roleMatch": "SKIP", + "probes": [] + }, + { + "key": "finance", + "expectedRoles": [ + "finance", + "employee" + ], + "maskedIdentifier": null, + "login": { + "status": "SKIP", + "reason": "missing_credentials", + "code": null + }, + "roleMatch": "SKIP", + "probes": [] + }, + { + "key": "finance_mgr", + "expectedRoles": [ + "finance", + "finance_mgr", + "employee" + ], + "maskedIdentifier": null, + "login": { + "status": "SKIP", + "reason": "missing_credentials", + "code": null + }, + "roleMatch": "SKIP", + "probes": [] + }, + { + "key": "hr", + "expectedRoles": [ + "hr", + "employee" + ], + "maskedIdentifier": null, + "login": { + "status": "SKIP", + "reason": "missing_credentials", + "code": null + }, + "roleMatch": "SKIP", + "probes": [] + } + ], + "findings": [ + { + "key": "admin", + "source": "credentials", + "status": "SKIP", + "reason": "missing_credentials" + }, + { + "key": "employee", + "source": "credentials", + "status": "SKIP", + "reason": "missing_credentials" + }, + { + "key": "manager", + "source": "credentials", + "status": "SKIP", + "reason": "missing_credentials" + }, + { + "key": "ceo", + "source": "credentials", + "status": "SKIP", + "reason": "missing_credentials" + }, + { + "key": "finance", + "source": "credentials", + "status": "SKIP", + "reason": "missing_credentials" + }, + { + "key": "finance_mgr", + "source": "credentials", + "status": "SKIP", + "reason": "missing_credentials" + }, + { + "key": "hr", + "source": "credentials", + "status": "SKIP", + "reason": "missing_credentials" + } + ], + "boundaries": { + "noBusinessWrites": true, + "noWeComWrites": true, + "noPasswordOutput": true, + "noRealEmployeeIdentifiers": true + } +} diff --git a/.trellis/tasks/05-26-oaops1-patrol-feature-review/research/oaops1-after-patrol-fix-final/summary.json b/.trellis/tasks/05-26-oaops1-patrol-feature-review/research/oaops1-after-patrol-fix-final/summary.json new file mode 100644 index 00000000..740ec357 --- /dev/null +++ b/.trellis/tasks/05-26-oaops1-patrol-feature-review/research/oaops1-after-patrol-fix-final/summary.json @@ -0,0 +1,284 @@ +{ + "issueId": "OAOPS1", + "mode": "oa-real-use-daily", + "status": "PASS_WITH_FINDINGS", + "conclusion": "PASS_WITH_FINDINGS", + "baseURL": "https://oa.hyinda.com", + "commit": "267240b", + "checkedAt": "2026-05-26T05:51:27.948Z", + "reportRef": ".trellis/tasks/05-26-oaops1-patrol-feature-review/research/oaops1-after-patrol-fix-final", + "window": { + "durationDays": 7, + "scope": "small-team-all-employees", + "realBusinessDataAllowed": true, + "fixPolicy": "blocking_same_day_issue_contract_nonblocking_feedback_pool" + }, + "oaPrimaryDomains": [ + "reimbursement_pdf_ocr", + "approval_tasks", + "procurement_expense", + "contracts_registry", + "projects", + "payment_batches", + "bank_receipt_attachments", + "ap_accounting_handoff", + "mobile_wecom_entry", + "audit" + ], + "frozenItems": [ + "image_ocr", + "complex_bpm", + "budget_control", + "erp_write", + "bank_statement_import", + "real_payment_provider", + "wecom_hr_deep_mirror", + "emergency_takeover", + "recovery_writeback", + "ruleops" + ], + "boundaries": { + "defaultReadOnlyAutomation": true, + "manualRealBusinessWritesOnly": true, + "paymentBatchExpected": "force_on", + "erpExpected": "force_off", + "actualPaymentChannel": "offline_bank_channel", + "contractSensitiveFiles": "do_not_upload_confidential_body_sensitive_quote_cost_detail_to_public_oa" + }, + "childReports": { + "liveReadiness": { + "status": "PASS", + "reportRef": ".trellis/tasks/05-26-oaops1-patrol-feature-review/research/oaops1-after-patrol-fix-final/live-smoke-readiness/summary.json", + "findingCount": 0, + "openCount": 0 + }, + "oaOnlySmokeReadiness": { + "status": "PASS_WITH_FINDINGS", + "reportRef": ".trellis/tasks/05-26-oaops1-patrol-feature-review/research/oaops1-after-patrol-fix-final/oa-only-smoke-readiness/summary.json", + "findingCount": 7, + "openCount": 0 + } + }, + "steps": { + "public-ready": { + "name": "public-ready", + "status": "PASS", + "kind": "required", + "code": 0, + "detail": "ok", + "logRef": ".trellis/tasks/05-26-oaops1-patrol-feature-review/research/oaops1-after-patrol-fix-final/public-ready.log", + "checkedAt": "2026-05-26T05:48:56.169Z" + }, + "live-smoke-env": { + "name": "live-smoke-env", + "status": "PASS", + "kind": "finding_allowed", + "code": 0, + "detail": "ok", + "logRef": ".trellis/tasks/05-26-oaops1-patrol-feature-review/research/oaops1-after-patrol-fix-final/live-smoke-env.log", + "checkedAt": "2026-05-26T05:48:56.740Z" + }, + "live-readiness": { + "name": "live-readiness", + "status": "PASS", + "kind": "finding_allowed", + "code": 0, + "detail": "ok", + "logRef": ".trellis/tasks/05-26-oaops1-patrol-feature-review/research/oaops1-after-patrol-fix-final/live-readiness.log", + "checkedAt": "2026-05-26T05:49:02.213Z" + }, + "oa-only-smoke-readiness": { + "name": "oa-only-smoke-readiness", + "status": "PASS", + "kind": "finding_allowed", + "code": 0, + "detail": "ok", + "logRef": ".trellis/tasks/05-26-oaops1-patrol-feature-review/research/oaops1-after-patrol-fix-final/oa-only-smoke-readiness.log", + "checkedAt": "2026-05-26T05:49:02.359Z" + }, + "live-feature-flags": { + "name": "live-feature-flags", + "status": "FINDING", + "kind": "finding_allowed", + "code": 1, + "detail": "finding or missing runtime credential", + "logRef": ".trellis/tasks/05-26-oaops1-patrol-feature-review/research/oaops1-after-patrol-fix-final/live-feature-flags.log", + "checkedAt": "2026-05-26T05:49:02.508Z" + }, + "authenticated-readonly-probe": { + "name": "authenticated-readonly-probe", + "status": "PASS", + "kind": "finding_allowed", + "code": 0, + "detail": "ok", + "logRef": ".trellis/tasks/05-26-oaops1-patrol-feature-review/research/oaops1-after-patrol-fix-final/authenticated-readonly-probe.log", + "checkedAt": "2026-05-26T05:49:02.665Z" + }, + "mobile-entry-matrix": { + "name": "mobile-entry-matrix", + "status": "PASS", + "kind": "finding_allowed", + "code": 0, + "detail": "ok", + "logRef": ".trellis/tasks/05-26-oaops1-patrol-feature-review/research/oaops1-after-patrol-fix-final/mobile-entry-matrix.log", + "checkedAt": "2026-05-26T05:49:24.679Z" + }, + "wecom-rendering-matrix": { + "name": "wecom-rendering-matrix", + "status": "PASS", + "kind": "finding_allowed", + "code": 0, + "detail": "ok", + "logRef": ".trellis/tasks/05-26-oaops1-patrol-feature-review/research/oaops1-after-patrol-fix-final/wecom-rendering-matrix.log", + "checkedAt": "2026-05-26T05:50:33.573Z" + }, + "wecom-entry": { + "name": "wecom-entry", + "status": "FINDING", + "kind": "finding_allowed", + "code": 1, + "detail": "finding or missing runtime credential", + "logRef": ".trellis/tasks/05-26-oaops1-patrol-feature-review/research/oaops1-after-patrol-fix-final/wecom-entry.log", + "checkedAt": "2026-05-26T05:51:27.593Z" + }, + "report-privacy-smoke": { + "name": "report-privacy-smoke", + "status": "PASS", + "kind": "required", + "code": 0, + "detail": "ok", + "logRef": ".trellis/tasks/05-26-oaops1-patrol-feature-review/research/oaops1-after-patrol-fix-final/report-privacy-smoke.log", + "checkedAt": "2026-05-26T05:51:27.805Z" + } + }, + "readonlyProbe": { + "mode": "oaops1-authenticated-readonly-probe", + "status": "PASS_WITH_FINDINGS", + "baseUrl": "https://oa.hyinda.com", + "checkedAt": "2026-05-26T05:49:02.580Z", + "readOnly": true, + "duplicateRoleChecks": "employee/finance/payment/AP are covered by oa-only-smoke-readiness to avoid repeated login throttling", + "roles": { + "admin": { + "status": "SKIP", + "reason": "missing_admin_credentials", + "maskedIdentifier": null + } + }, + "checks": [ + { + "id": "portal", + "role": "admin", + "path": "/api/portal/home", + "domain": "portal", + "status": "SKIP", + "reason": "missing_admin_credentials" + }, + { + "id": "workflow-tasks", + "role": "admin", + "path": "/api/workflow/tasks?page=1&pageSize=5", + "domain": "approval", + "status": "SKIP", + "reason": "missing_admin_credentials" + }, + { + "id": "reimbursements", + "role": "admin", + "path": "/api/reimbursements?page=1&pageSize=5", + "domain": "reimbursement", + "status": "SKIP", + "reason": "missing_admin_credentials" + }, + { + "id": "reimbursement-summary", + "role": "admin", + "path": "/api/reimbursements/stats/summary", + "domain": "reimbursement", + "status": "SKIP", + "reason": "missing_admin_credentials" + }, + { + "id": "invoice-pool", + "role": "admin", + "path": "/api/invoice-pool?page=1&pageSize=5", + "domain": "invoice_ocr", + "status": "SKIP", + "reason": "missing_admin_credentials" + }, + { + "id": "purchase-requests", + "role": "admin", + "path": "/api/purchase-requests?page=1&pageSize=5", + "domain": "procurement", + "status": "SKIP", + "reason": "missing_admin_credentials" + }, + { + "id": "contracts-registry", + "role": "admin", + "path": "/api/contracts-registry", + "domain": "contracts", + "status": "SKIP", + "reason": "missing_admin_credentials" + }, + { + "id": "projects", + "role": "admin", + "path": "/api/projects?page=1&pageSize=5", + "domain": "projects", + "status": "SKIP", + "reason": "missing_admin_credentials" + }, + { + "id": "audit-logs", + "role": "admin", + "path": "/api/audit-logs?page=1&pageSize=5", + "domain": "audit", + "status": "SKIP", + "reason": "missing_admin_credentials" + } + ], + "boundaries": { + "noBusinessWrites": true, + "noExportsByDefault": true, + "noFileUploads": true, + "noWeComWrites": true, + "noRealPaymentExecution": true + } + }, + "findingCount": 4, + "findings": [ + { + "source": "live-feature-flags", + "status": "FINDING", + "severity": "finding" + }, + { + "source": "wecom-entry", + "status": "FINDING", + "severity": "finding" + }, + { + "source": "authenticated-readonly-probe", + "status": "PASS_WITH_FINDINGS", + "severity": "manual_or_config" + }, + { + "source": "child:oaOnlySmokeReadiness", + "status": "PASS_WITH_FINDINGS", + "severity": "finding", + "findingCount": 7, + "reportRef": ".trellis/tasks/05-26-oaops1-patrol-feature-review/research/oaops1-after-patrol-fix-final/oa-only-smoke-readiness/summary.json" + } + ], + "manualGates": { + "dailyCloseout": "管理员每日查看 summary.json 和 manual-gates.md,阻断即修,非阻断入反馈池。", + "day7Closeout": [ + "A:接受 PASS_OA_REAL_USE_READY,进入下一轮体验/效率优化。", + "B:接受 PASS_WITH_FINDINGS,但先处理指定 finding。", + "C:暂停真实使用扩展,先做整改。" + ] + }, + "privacy": "status/count/report-path summaries only; no phone/email/token/Secret/EncodingAESKey/full external ids/full invoice image/full bank receipt/full contract body/raw payload" +} diff --git a/.trellis/tasks/05-26-oaops1-patrol-feature-review/task.json b/.trellis/tasks/05-26-oaops1-patrol-feature-review/task.json new file mode 100644 index 00000000..890d8a7f --- /dev/null +++ b/.trellis/tasks/05-26-oaops1-patrol-feature-review/task.json @@ -0,0 +1,34 @@ +{ + "id": "oaops1-patrol-feature-review", + "name": "oaops1-patrol-feature-review", + "title": "OAOPS1 patrol feature review", + "description": "Review OAOPS1/OALAUNCH1 patrol scripts for readiness, evidence quality, privacy and 7-day launch gate reliability.", + "status": "in_progress", + "dev_type": null, + "scope": "qa-patrol-review", + "package": null, + "priority": "P2", + "creator": "shunli", + "assignee": "shunli", + "createdAt": "2026-05-26", + "completedAt": null, + "branch": null, + "base_branch": "main", + "worktree_path": null, + "commit": null, + "pr_url": null, + "subtasks": [], + "children": [], + "parent": null, + "relatedFiles": [ + ".trellis/tasks/05-26-oaops1-patrol-feature-review/prd.md", + ".trellis/tasks/05-26-oaops1-patrol-feature-review/info.md", + "scripts/qa/run-oa-real-use-daily.sh", + "scripts/qa/run-oa-final-launch-readiness.sh", + "scripts/qa/check-oa-only-smoke-readiness.mjs", + "docs/管理员手册.md", + "docs/文档事实源状态矩阵.md" + ], + "notes": "", + "meta": {} +} diff --git a/scripts/qa/run-oa-real-use-daily.sh b/scripts/qa/run-oa-real-use-daily.sh index 1c3fd38a..961b7b07 100755 --- a/scripts/qa/run-oa-real-use-daily.sh +++ b/scripts/qa/run-oa-real-use-daily.sh @@ -22,17 +22,27 @@ write_step_json() { local kind="$3" local code="${4:-0}" local detail="${5:-}" - node - "$REPORT_DIR" "$name" "$status" "$kind" "$code" "$detail" <<'NODE' || true + node - "$ROOT_DIR" "$REPORT_DIR" "$name" "$status" "$kind" "$code" "$detail" <<'NODE' || true const fs = require("node:fs"); const path = require("node:path"); -const [reportDir, name, status, kind, code, detail] = process.argv.slice(2); +const [rootDir, reportDir, name, status, kind, code, detail] = process.argv.slice(2); + +function reportRef(file) { + const absolute = path.resolve(file); + const relative = path.relative(rootDir, absolute); + if (relative && !relative.startsWith("..") && !path.isAbsolute(relative)) { + return relative; + } + return path.basename(absolute); +} + fs.writeFileSync(path.join(reportDir, `${name}.step.json`), `${JSON.stringify({ name, status, kind, code: Number(code || 0), detail: String(detail || "").slice(0, 600), - logPath: path.resolve(path.join(reportDir, `${name}.log`)), + logRef: reportRef(path.join(reportDir, `${name}.log`)), checkedAt: new Date().toISOString(), }, null, 2)}\n`, "utf8"); NODE @@ -201,9 +211,16 @@ function summarizePayload(json) { if (typeof data === "object" && data) { const summary = {}; for (const [key, value] of Object.entries(data)) { - if (/token|secret|password|phone|email|payload|attachment|comment|form/i.test(key)) continue; - if (typeof value === "number" || typeof value === "boolean") summary[key] = value; - if (typeof value === "string" && value.length <= 60) summary[key] = value; + if (/token|secret|password|phone|email|payload|attachment|comment|form|amount|money|price|cost|balance|bank|receipt|account|invoice|tax|fee|paid|payment/i.test(key)) continue; + if (typeof value === "number" && /(count|total|size|days|pending|open|closed|passed|failed)$/i.test(key)) { + summary[key] = value; + } + if (typeof value === "boolean" && /^(enabled|disabled|ready|ok|healthy|configured|available|has[A-Z]|is[A-Z]|can[A-Z])/i.test(key)) { + summary[key] = value; + } + if (typeof value === "string" && /^(status|mode|conclusion)$/i.test(key) && /^[a-z0-9_/-]{1,60}$/i.test(value)) { + summary[key] = value; + } } return summary; } @@ -347,11 +364,19 @@ run_privacy_smoke() { local matches matches="$(grep -RInE "$sensitive_pattern" "$REPORT_DIR" || true)" if [[ -n "$matches" ]]; then - printf '%s\n' "$matches" + while IFS= read -r line; do + local match_file="${line%%:*}" + local rest="${line#*:}" + local match_line="${rest%%:*}" + local match_ref="${match_file#$ROOT_DIR/}" + printf '%s:%s:REDACTED\n' "$match_ref" "$match_line" + done <<<"$matches" >"$REPORT_DIR/privacy-findings.txt" log "[oaops1][FAIL] report-privacy-smoke matched sensitive pattern" STATUS=1 + write_step_json "report-privacy-smoke" "FAIL" "required" 1 "sensitive pattern matched; see privacy-findings.txt" else log "[oaops1][PASS] report-privacy-smoke" + write_step_json "report-privacy-smoke" "PASS" "required" 0 "ok" fi } @@ -372,21 +397,33 @@ function readJson(file) { } } +function reportRef(file) { + 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 step(name) { return readJson(path.join(reportDir, `${name}.step.json`)) || { name, status: "MISSING", kind: "unknown", - logPath: path.resolve(path.join(reportDir, `${name}.log`)), + logRef: reportRef(path.join(reportDir, `${name}.log`)), }; } function child(relativePath) { const file = path.join(reportDir, relativePath, "summary.json"); const json = readJson(file); + const findingCount = Number(json?.findingCount ?? json?.findings?.length ?? json?.gates?.findings?.length ?? 0); return { status: json?.status || json?.conclusion || "MISSING", - reportPath: fs.existsSync(file) ? path.resolve(file) : null, + reportRef: fs.existsSync(file) ? reportRef(file) : null, + findingCount: Number.isFinite(findingCount) ? findingCount : 0, + openCount: Number(json?.gates?.openCount ?? 0), }; } @@ -405,9 +442,14 @@ const stepNames = [ "mobile-entry-matrix", "wecom-rendering-matrix", "wecom-entry", + "report-privacy-smoke", ]; const steps = Object.fromEntries(stepNames.map((name) => [name, step(name)])); const readonlyProbe = readJson(path.join(reportDir, "authenticated-readonly-probe.json")); +const childReports = { + liveReadiness: child("live-smoke-readiness"), + oaOnlySmokeReadiness: child("oa-only-smoke-readiness"), +}; const findings = []; for (const [name, item] of Object.entries(steps)) { @@ -424,6 +466,17 @@ for (const [name, item] of Object.entries(steps)) { if (readonlyProbe?.status && readonlyProbe.status !== "PASS") { findings.push({ source: "authenticated-readonly-probe", status: readonlyProbe.status, severity: readonlyProbe.status === "FAIL" ? "finding" : "manual_or_config" }); } +for (const [name, item] of Object.entries(childReports)) { + const status = String(item.status || "UNKNOWN"); + if (status === "PASS" && item.findingCount <= 0 && item.openCount <= 0) continue; + if (/BLOCKED|FAIL|ERROR/i.test(status)) { + findings.push({ source: `child:${name}`, status, severity: "finding", reportRef: item.reportRef }); + } else if (/FINDING/i.test(status) || item.findingCount > 0) { + findings.push({ source: `child:${name}`, status, severity: "finding", findingCount: item.findingCount, reportRef: item.reportRef }); + } else if (/SKIP|MISSING|PAUSED|PARTIAL/i.test(status) || item.openCount > 0) { + findings.push({ source: `child:${name}`, status, severity: "manual_or_config", openCount: item.openCount, reportRef: item.reportRef }); + } +} let status = "PASS_OA_REAL_USE_DAILY_READY"; if (Number(statusInput || 0) !== 0 || findings.some((item) => item.severity === "blocker")) { @@ -440,7 +493,7 @@ const summary = { baseURL, commit, checkedAt: new Date().toISOString(), - reportDir: path.resolve(reportDir), + reportRef: reportRef(reportDir), window: { durationDays: 7, scope: "small-team-all-employees", @@ -479,9 +532,7 @@ const summary = { actualPaymentChannel: "offline_bank_channel", contractSensitiveFiles: "do_not_upload_confidential_body_sensitive_quote_cost_detail_to_public_oa", }, - childReports: { - liveReadiness: child("live-smoke-readiness"), - }, + childReports, steps, readonlyProbe, findingCount: findings.length, @@ -498,7 +549,7 @@ const summary = { }; fs.writeFileSync(path.join(reportDir, "summary.json"), `${JSON.stringify(summary, null, 2)}\n`, "utf8"); -console.log(`[oaops1] summary=${path.join(reportDir, "summary.json")}`); +console.log(`[oaops1] summary=${reportRef(path.join(reportDir, "summary.json"))}`); NODE } @@ -506,6 +557,12 @@ finish() { write_manual_checklist write_summary_json || STATUS=1 run_privacy_smoke + write_summary_json || true + local before_second_privacy_status="$STATUS" + run_privacy_smoke + if [[ "$STATUS" != "$before_second_privacy_status" ]]; then + write_summary_json || true + fi log "[oaops1] report=$REPORT_DIR" exit "$STATUS" }