OSMOKE1 修复 OA-only smoke 账号治理

这个提交包含在:
2026-05-23 14:46:14 +08:00
父节点 88b06346da
当前提交 5a64809004
共修改 15 个文件,包含 1209 行新增和 13 行删除
@@ -15,6 +15,15 @@ const {
const DEFAULT_REPORT_ROOT = path.resolve(__dirname, "../../../artifacts/qa-flow");
const DEFAULT_LIMIT = 200;
const MAX_REPORT_ITEMS = 200;
const OA_ONLY_SMOKE_IDENTIFIER_ENVS = [
"LIVE_SMOKE_ADMIN_IDENTIFIER",
"LIVE_SMOKE_EMPLOYEE_IDENTIFIER",
"LIVE_SMOKE_MANAGER_IDENTIFIER",
"LIVE_SMOKE_CEO_IDENTIFIER",
"LIVE_SMOKE_FINANCE_IDENTIFIER",
"LIVE_SMOKE_FINANCE_MGR_IDENTIFIER",
"LIVE_SMOKE_HR_IDENTIFIER",
];
function printHelp() {
process.stdout.write(`Usage: node src/scripts/audit-wecom-oa-user-consistency.js [options]
@@ -90,6 +99,43 @@ function normalizeKey(value) {
return raw ? raw.toLowerCase() : "";
}
function splitList(value) {
return String(value ?? "")
.split(/[\s,]+/)
.map((item) => item.trim())
.filter(Boolean);
}
function buildOaOnlySmokeIdentifierSet(env = process.env) {
const identifiers = [
...splitList(env.OA_ONLY_SMOKE_IDENTIFIERS),
...OA_ONLY_SMOKE_IDENTIFIER_ENVS.map((key) => env[key]).filter(Boolean),
];
return new Set(identifiers.map(normalizeKey).filter(Boolean));
}
function isDefaultOaOnlySmokeCandidate(employee) {
const employeeNo = String(employee?.employeeNo ?? "").trim();
const emailLocal = String(employee?.email ?? "").trim().toLowerCase().split("@")[0] || "";
const name = String(employee?.name ?? "");
return (
/^SMOKE[A-Z0-9_-]*/i.test(employeeNo)
|| /^smoke[-_.]/.test(emailLocal)
|| /^oa[-_.]?smoke[-_.]/.test(emailLocal)
|| /测试|试运行/.test(name)
);
}
function isOaOnlySmokeEmployee(employee, identifierSet) {
if (!employee) return false;
if (isDefaultOaOnlySmokeCandidate(employee)) return true;
if (!identifierSet?.size) return false;
return [employee.employeeNo, employee.email, employee.phone]
.map(normalizeKey)
.filter(Boolean)
.some((key) => identifierSet.has(key));
}
function safeMessage(value) {
return String(value ?? "")
.replace(/access_token=[^&\s]+/gi, "access_token=***")
@@ -219,8 +265,10 @@ function summarizeFindings(findings) {
}
function buildConsistencyReport({ oaEmployees, wecomProfiles, generatedAt = new Date().toISOString(), limit = DEFAULT_LIMIT }) {
const oaOnlySmokeIdentifierSet = buildOaOnlySmokeIdentifierSet();
const findings = {
matchedActive: [],
oaOnlySmokeExempted: [],
oaActiveMissingInWecom: [],
wecomActiveMissingInOa: [],
oaActiveMissingBinding: [],
@@ -308,13 +356,20 @@ function buildConsistencyReport({ oaEmployees, wecomProfiles, generatedAt = new
for (const employee of oaEmployees) {
if (isOaActive(employee) && !matchedOrConflictedEmployeeIds.has(employee.id)) {
findings.oaActiveMissingInWecom.push(sanitizeEmployee(employee));
if (isOaOnlySmokeEmployee(employee, oaOnlySmokeIdentifierSet)) {
findings.oaOnlySmokeExempted.push({
...sanitizeEmployee(employee),
reason: "oa_only_smoke_no_wecom_account_required",
});
} else {
findings.oaActiveMissingInWecom.push(sanitizeEmployee(employee));
}
}
}
const findingCounts = summarizeFindings(findings);
const findingTotal = Object.entries(findingCounts)
.filter(([key]) => key !== "matchedActive")
.filter(([key]) => !["matchedActive", "oaOnlySmokeExempted"].includes(key))
.reduce((total, [, count]) => total + count, 0);
return {
@@ -326,6 +381,7 @@ function buildConsistencyReport({ oaEmployees, wecomProfiles, generatedAt = new
oa: "active employees and wecom identity bindings",
wecom: "contact members plus hr assistant staff info",
writeBehavior: "no OA writes, no WeCom writes, no WHMS dry-run/apply API call",
oaOnlySmoke: "env-listed OA-only smoke accounts are exempt from WeCom-missing findings",
},
inputs: {
limit,
@@ -410,6 +466,7 @@ async function queryOaEmployeesReadOnly() {
function buildSkipReport({ generatedAt, reason, details, oaEmployees = [], limit }) {
const findings = {
matchedActive: [],
oaOnlySmokeExempted: [],
oaActiveMissingInWecom: [],
wecomActiveMissingInOa: [],
oaActiveMissingBinding: [],