WCBK1 实现企微审批回调只读 POC
这个提交包含在:
@@ -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;
|
||||
|
||||
在新工单中引用
屏蔽一个用户