oaops1-patrol-feature-review 改进 OAOPS1 巡检报告传播与脱敏
这个提交包含在:
@@ -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: `<child-report-dir>/summary.json`.
|
||||
- Step summary input/output: `<step-name>.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.<name>.status`,
|
||||
`childReports.<name>.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 <runner>.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
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -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."}
|
||||
@@ -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."}
|
||||
@@ -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.
|
||||
@@ -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`.
|
||||
@@ -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`
|
||||
+22
@@ -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 已完成后,补真实资产责任人或保管人抽查。
|
||||
+400
@@ -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."
|
||||
}
|
||||
+96
@@ -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
|
||||
}
|
||||
}
|
||||
+11
@@ -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。"
|
||||
}
|
||||
+20
@@ -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、银行流水导入、真实支付直连继续冻结。
|
||||
+168
@@ -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
|
||||
}
|
||||
}
|
||||
+284
@@ -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"
|
||||
}
|
||||
@@ -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": {}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
在新工单中引用
屏蔽一个用户