WCBK1 实现企微审批回调只读 POC

这个提交包含在:
2026-05-19 15:00:22 +08:00
父节点 a32ec1e658
当前提交 8122898348
共修改 30 个文件,包含 1650 行新增和 53 行删除
+65 -2
查看文件
@@ -22,14 +22,14 @@ const REQUIRED_COLUMNS = [
"Notes",
];
const VALID_STATUSES = new Set(["TODO", "DOING", "DONE"]);
const VALID_PROFILES = new Set(["general", "wecom", "wecom-approval-fixture", "docs-only"]);
const VALID_PROFILES = new Set(["general", "wecom", "wecom-approval-fixture", "wecom-callback", "docs-only"]);
const WAPC2F_ISSUE = "issues/2026-05-19_10-24-43-wecom-approval-fixture-auto-generate-v1.csv";
const WPROC2_ISSUE = "issues/2026-05-19_11-15-10-process-gate-automation-v1.csv";
function usage() {
return `Usage:
node scripts/qa/check-process-contract.mjs --baseline [--json]
node scripts/qa/check-process-contract.mjs --issue <csv> --profile <general|wecom|wecom-approval-fixture|docs-only> [--json]
node scripts/qa/check-process-contract.mjs --issue <csv> --profile <general|wecom|wecom-approval-fixture|wecom-callback|docs-only> [--json]
This QA-only process gate is read-only. It validates Issue CSV contracts and
current WeCom process facts without printing secrets or raw external payloads.`;
@@ -330,6 +330,67 @@ function validateWecomApprovalFixture({ result, rows }) {
}
}
function validateWecomCallback({ result, rows }) {
validateWecom({ result, rows });
const text = textOfRows(rows);
const requiredTerms = [
"WCBK1",
"GET /api/wecom/approval/callback",
"POST /api/wecom/approval/callback",
"WECOM_APPROVAL_CALLBACK_URL",
"WECOM_CALLBACK_TOKEN",
"WECOM_CALLBACK_ENCODING_AES_KEY",
"msg_signature",
"EncodingAESKey",
"验签",
"解密",
"幂等",
"只读",
"NEEDS_CONFIG",
"SKIP/BLOCKED_BY_EXTERNAL_CONFIG",
"WACP3",
"WADM1",
];
for (const term of requiredTerms) {
if (!hasTerm(text, term)) {
addError(result, "callback.required_term", `WCBK1 contract must mention ${term}`);
}
}
if (!hasAny(text, ["不使用通讯录 Secret", "不尝试其他 Secret", "不允许运行时猜 Secret", "无 fallback"])) {
addError(result, "callback.secret_source", "WCBK1 must disallow Secret fallback and runtime guessing");
}
if (!hasAny(text, ["不写 OA 业务镜像", "不落业务镜像表", "不写 OA/企微业务数据", "不写企微数据"])) {
addError(result, "callback.read_only", "WCBK1 must declare no OA/WeCom business writes");
}
const privacyTerms = [
"原始 XML",
"密文",
"完整 sp_no",
"完整 userid",
"完整 template_id",
"Token",
"EncodingAESKey",
"原始 payload",
];
for (const term of privacyTerms) {
if (!hasTerm(text, term)) {
addError(result, "callback.privacy", `WCBK1 contract must declare no ${term} in reports`);
}
}
const routePattern = /WCBK1[\s\S]{0,260}WACP3[\s\S]{0,260}WADM1/;
if (!routePattern.test(text)) {
addError(result, "callback.route_order", "WCBK1 route must show WACP3 and WADM1 before mirror implementation");
}
if (!result.errors.some((item) => item.code.startsWith("callback."))) {
addCheck(result, "profile.wecom_callback", "PASS", "WCBK1 callback config, crypto, read-only, route and privacy gates are declared");
}
}
export function evaluateIssueContract({ csvText, issuePath = "", profile = "general" }) {
const result = createResult(issuePath || "inline issue", profile);
const { header, rows } = parseIssueCsv(csvText);
@@ -341,6 +402,8 @@ export function evaluateIssueContract({ csvText, issuePath = "", profile = "gene
validateWecom({ result, rows });
} else if (profile === "wecom-approval-fixture") {
validateWecomApprovalFixture({ result, rows });
} else if (profile === "wecom-callback") {
validateWecomCallback({ result, rows });
}
return result;