oaops1-patrol-feature-review 改进 OAOPS1 巡检报告传播与脱敏
这个提交包含在:
@@ -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"
|
||||
}
|
||||
|
||||
在新工单中引用
屏蔽一个用户