WREMOTE1 完成远端收口验证
这个提交包含在:
@@ -5,6 +5,6 @@
|
|||||||
"WREMOTE1-3","准备 WREAL1 临时 env 模板","在 /tmp/hyb-oa-wreal1.env 准备真实试用临时配置占位,供用户后续填写真实员工和审批人/观察人,不进仓库。","1) 文件只在 /tmp;2) 只包含注释和空值占位;3) 不输出真实员工号;4) 后续只校验非空和脱敏。","受影响流程:WREAL1 启动前人工配置 -> 系统脱敏读取;验证步骤:test -f /tmp/hyb-oa-wreal1.env;awk -F= '/^WREAL1_/ {print $1""=""($2==""""?""EMPTY"":""SET"")}' /tmp/hyb-oa-wreal1.env 确认只输出 key 和 SET/EMPTY,不输出真实员工号。","bash","DONE","DONE","DONE","/tmp/hyb-oa-wreal1.env","[""WREMOTE1-2""]","回滚/清理:删除 /tmp/hyb-oa-wreal1.env;该文件不纳入 git。"
|
"WREMOTE1-3","准备 WREAL1 临时 env 模板","在 /tmp/hyb-oa-wreal1.env 准备真实试用临时配置占位,供用户后续填写真实员工和审批人/观察人,不进仓库。","1) 文件只在 /tmp;2) 只包含注释和空值占位;3) 不输出真实员工号;4) 后续只校验非空和脱敏。","受影响流程:WREAL1 启动前人工配置 -> 系统脱敏读取;验证步骤:test -f /tmp/hyb-oa-wreal1.env;awk -F= '/^WREAL1_/ {print $1""=""($2==""""?""EMPTY"":""SET"")}' /tmp/hyb-oa-wreal1.env 确认只输出 key 和 SET/EMPTY,不输出真实员工号。","bash","DONE","DONE","DONE","/tmp/hyb-oa-wreal1.env","[""WREMOTE1-2""]","回滚/清理:删除 /tmp/hyb-oa-wreal1.env;该文件不纳入 git。"
|
||||||
"WREMOTE1-4","只读 reviewer 审查远端与正式模板边界","涉及审批规则、权限和真实试用准入,实施后由只读 reviewer 检查是否可能误用本地结果、误授权企微写入或跳过人工门禁。","1) reviewer 只读;2) 审查 WFULLSMOKE2 本地默认阻断;3) 审查 WEXT1-FORMAL 不创建模板、不自动提交审批;4) 审查 WREAL1 信息不入仓库;5) blocker 必须修复。","受影响流程:脚本/文档修正 -> 独立评审 -> 门禁回填;验证步骤:reviewer subagent 输出无 blocker 或 blocker 已处理。","subagent|manual","DONE","DONE","DONE","N/A","[""WREMOTE1-3""]","回滚/清理:reviewer 不修改文件;如发现风险,暂停部署和远端执行。"
|
"WREMOTE1-4","只读 reviewer 审查远端与正式模板边界","涉及审批规则、权限和真实试用准入,实施后由只读 reviewer 检查是否可能误用本地结果、误授权企微写入或跳过人工门禁。","1) reviewer 只读;2) 审查 WFULLSMOKE2 本地默认阻断;3) 审查 WEXT1-FORMAL 不创建模板、不自动提交审批;4) 审查 WREAL1 信息不入仓库;5) blocker 必须修复。","受影响流程:脚本/文档修正 -> 独立评审 -> 门禁回填;验证步骤:reviewer subagent 输出无 blocker 或 blocker 已处理。","subagent|manual","DONE","DONE","DONE","N/A","[""WREMOTE1-3""]","回滚/清理:reviewer 不修改文件;如发现风险,暂停部署和远端执行。"
|
||||||
"WREMOTE1-5","本地静态门禁复跑","复跑合同、process gate、脚本语法、文档审计、skill 校验、diff 检查和最小回归。","1) validate_issues_csv 通过;2) baseline 和 issue-specific process gate 通过;3) bash -n 通过;4) audit-docs、quick_validate、git diff --check 通过;5) bash scripts/check-minimal.sh 通过。","受影响流程:本地静态门禁 -> 提交 -> 部署;验证步骤:validate_issues_csv.py <csv>;node scripts/qa/check-process-contract.mjs --baseline --json;node scripts/qa/check-process-contract.mjs --issue <csv> --profile wecom --json;bash -n scripts/qa/run-three-day-full-goal-smoke.sh scripts/qa/run-wecom-test-account-final-closeout.sh scripts/qa/collect-wecom-controlled-trial-evidence.sh;bash scripts/check-minimal.sh。","python3|node|bash|git","DONE","DONE","DONE","scripts/qa/run-three-day-full-goal-smoke.sh|scripts/qa/run-wecom-test-account-final-closeout.sh|scripts/qa/collect-wecom-controlled-trial-evidence.sh|docs/*|.agents/skills/hyb-oa-delivery/SKILL.md","[""WREMOTE1-4""]","回滚/清理:若门禁失败,保留 TODO 并暂停部署。"
|
"WREMOTE1-5","本地静态门禁复跑","复跑合同、process gate、脚本语法、文档审计、skill 校验、diff 检查和最小回归。","1) validate_issues_csv 通过;2) baseline 和 issue-specific process gate 通过;3) bash -n 通过;4) audit-docs、quick_validate、git diff --check 通过;5) bash scripts/check-minimal.sh 通过。","受影响流程:本地静态门禁 -> 提交 -> 部署;验证步骤:validate_issues_csv.py <csv>;node scripts/qa/check-process-contract.mjs --baseline --json;node scripts/qa/check-process-contract.mjs --issue <csv> --profile wecom --json;bash -n scripts/qa/run-three-day-full-goal-smoke.sh scripts/qa/run-wecom-test-account-final-closeout.sh scripts/qa/collect-wecom-controlled-trial-evidence.sh;bash scripts/check-minimal.sh。","python3|node|bash|git","DONE","DONE","DONE","scripts/qa/run-three-day-full-goal-smoke.sh|scripts/qa/run-wecom-test-account-final-closeout.sh|scripts/qa/collect-wecom-controlled-trial-evidence.sh|docs/*|.agents/skills/hyb-oa-delivery/SKILL.md","[""WREMOTE1-4""]","回滚/清理:若门禁失败,保留 TODO 并暂停部署。"
|
||||||
"WREMOTE1-6","部署并远端执行 WTCLOSE2","本地门禁通过并提交后,使用 SYNC_ENV=0 部署/同步,然后从部署服务器可信 IP 执行 WTCLOSE2 远端收口。","1) SYNC_ENV=0 部署成功;2) /ready 正常;3) bash scripts/qa/run-wecom-test-account-final-closeout.sh --remote 生成远端报告;4) 报告不泄露完整 sp_no/template_id/userid/token/Secret/表单值/原始 payload。","受影响流程:部署 -> 远端 WTCLOSE2 -> WEXT1-FORMAL 问答;验证步骤:SYNC_ENV=0 bash deploy/trialrun/deploy.sh;curl -fsS https://oa.hyinda.com/ready;bash scripts/qa/run-wecom-test-account-final-closeout.sh --remote。","bash|ssh|curl","TODO","TODO","TODO","deploy/trialrun/*|scripts/qa/run-wecom-test-account-final-closeout.sh","[""WREMOTE1-5""]","回滚/清理:若部署或远端 WTCLOSE2 阻断,记录真实 SKIP/BLOCKED_BY_EXTERNAL_CONFIG;不继续正式模板测试。"
|
"WREMOTE1-6","部署并远端执行 WTCLOSE2","本地门禁通过并提交后,使用 SYNC_ENV=0 部署/同步,然后从部署服务器可信 IP 执行 WTCLOSE2 远端收口。","1) SYNC_ENV=0 部署成功;2) /ready 正常;3) bash scripts/qa/run-wecom-test-account-final-closeout.sh --remote 生成远端报告;4) 报告不泄露完整 sp_no/template_id/userid/token/Secret/表单值/原始 payload。","受影响流程:部署 -> 远端 WTCLOSE2 -> WEXT1-FORMAL 问答;验证步骤:SYNC_ENV=0 bash deploy/trialrun/deploy.sh;curl -fsS https://oa.hyinda.com/ready;bash scripts/qa/run-wecom-test-account-final-closeout.sh --remote。","bash|ssh|curl","DONE","DONE","DONE","deploy/trialrun/*|scripts/qa/run-wecom-test-account-final-closeout.sh|server/src/services/integrations/wecomApprovalArchive.js|server/tests/unit/services/wecomApprovalArchive.test.js","[""WREMOTE1-5""]","远端部署通过,/ready OK;WTCLOSE2 报告 /app/artifacts/qa-flow/wecom-test-account-final-closeout-20260522-055840/summary.json,结论 SKIP/BLOCKED_BY_EXTERNAL_CONFIG:新补偿归档中请假/补卡各 1 条已可 WRULE1 PASS_RULE_MATCH;历史 callback-only 旧行缺 applicant/approver,外出/加班尚无正式模板归档,需 WEXT1-FORMAL 人工提交后复核;隐私扫描通过。回滚/清理:恢复脚本和解析修正;不写企微。"
|
||||||
"WREMOTE1-7","远端执行 WFULLSMOKE2 全域 smoke","远端运行 WFULLSMOKE2,允许创建 TEST-SMOKE-WFULL 标记的 OA 主办域测试数据,验证全域主路径。","1) 远端执行全域 smoke;2) 只创建 TEST-SMOKE-WFULL 标记 OA 测试业务数据;3) 不写企业微信、不真实付款、不接支付、不写 ERP、不导入银行流水;4) 输出 PASS_FULL_GOAL_SMOKE_READY、PASS_WITH_FINDINGS、PAUSED_MANUAL_CONFIRMATION 或 SKIP/BLOCKED_BY_EXTERNAL_CONFIG。","受影响流程:OA 报销 -> 采购 -> 合同/项目 -> 付款批次 -> 移动/企微入口;验证步骤:WFULLSMOKE_ALLOW_PUBLIC_WRITES=1 bash scripts/qa/run-three-day-full-goal-smoke.sh --remote;核对 summary.json 和测试标记。","bash|ssh|playwright","TODO","TODO","TODO","scripts/qa/run-three-day-full-goal-smoke.sh","[""WREMOTE1-6""]","回滚/清理:测试数据保留审计,不自动清理;若凭据缺失,记录 PASS_WITH_FINDINGS 或 PAUSED_MANUAL_CONFIRMATION。"
|
"WREMOTE1-7","远端执行 WFULLSMOKE2 全域 smoke","远端运行 WFULLSMOKE2,允许创建 TEST-SMOKE-WFULL 标记的 OA 主办域测试数据,验证全域主路径。","1) 远端执行全域 smoke;2) 只创建 TEST-SMOKE-WFULL 标记 OA 测试业务数据;3) 不写企业微信、不真实付款、不接支付、不写 ERP、不导入银行流水;4) 输出 PASS_FULL_GOAL_SMOKE_READY、PASS_WITH_FINDINGS、PAUSED_MANUAL_CONFIRMATION 或 SKIP/BLOCKED_BY_EXTERNAL_CONFIG。","受影响流程:OA 报销 -> 采购 -> 合同/项目 -> 付款批次 -> 移动/企微入口;验证步骤:WFULLSMOKE_ALLOW_PUBLIC_WRITES=1 bash scripts/qa/run-three-day-full-goal-smoke.sh --remote;核对 summary.json 和测试标记。","bash|ssh|playwright","DONE","DONE","DONE","scripts/qa/run-three-day-full-goal-smoke.sh","[""WREMOTE1-6""]","WFULLSMOKE2 远端报告 /app/artifacts/qa-flow/three-day-full-goal-smoke-20260522-055953/summary.json,结论 PASS_WITH_FINDINGS;/ready、WARCH3、WAUTO1、隐私扫描通过;OA public write flows 因缺 LIVE_SMOKE_* 业务凭据未创建测试数据;移动/企微入口 spec 未打入运行镜像,记录 finding;未写企业微信。"
|
||||||
"WREMOTE1-8","提交并报告后续人工门禁","回填 WREMOTE1 状态并中文提交;远端 WTCLOSE2/WFULLSMOKE2 结果决定是否进入正式模板外出/加班人工提交问答。","1) CSV 状态按真实验证回填;2) 中文提交包含 WREMOTE1;3) 最终说明是否已部署、远端报告路径和下一步人工操作;4) 若未远端执行,明确阻断原因。","受影响流程:提交 -> 人工 WEXT1-FORMAL 问答 -> WREAL1;验证步骤:git status --short;git log -1 --oneline。","git","TODO","TODO","TODO","issues/2026-05-22_12-34-41-wecom-remote-closeout-and-full-smoke-v1.csv|scripts/qa/run-three-day-full-goal-smoke.sh|docs/*|.agents/skills/hyb-oa-delivery/SKILL.md","[""WREMOTE1-7""]","回滚/清理:revert 本提交;WEXT1-FORMAL 和 WREAL1 仍需后续人工确认。"
|
"WREMOTE1-8","提交并报告后续人工门禁","回填 WREMOTE1 状态并中文提交;远端 WTCLOSE2/WFULLSMOKE2 结果决定是否进入正式模板外出/加班人工提交问答。","1) CSV 状态按真实验证回填;2) 中文提交包含 WREMOTE1;3) 最终说明是否已部署、远端报告路径和下一步人工操作;4) 若未远端执行,明确阻断原因。","受影响流程:提交 -> 人工 WEXT1-FORMAL 问答 -> WREAL1;验证步骤:git status --short;git log -1 --oneline。","git","DONE","DONE","DONE","issues/2026-05-22_12-34-41-wecom-remote-closeout-and-full-smoke-v1.csv|scripts/qa/run-three-day-full-goal-smoke.sh|scripts/qa/run-wecom-test-account-final-closeout.sh|server/src/services/integrations/wecomApprovalArchive.js|server/tests/unit/services/wecomApprovalArchive.test.js","[""WREMOTE1-7""]","本轮回填远端报告路径和真实结论;WEXT1-FORMAL 不自动开始,需用户按 A/B/C 选择是否由 SMOKE002 人工提交正式模板外出+加班测试单。回滚/清理:恢复本轮 CSV 状态和脚本解析/报告口径修正;不删除远端报告,正式模板人工测试另行授权。"
|
||||||
|
|||||||
|
@@ -305,8 +305,8 @@ write_manual_checklist() {
|
|||||||
|
|
||||||
## Day 2
|
## Day 2
|
||||||
- SMOKE002:继续按企微规则打卡。
|
- SMOKE002:继续按企微规则打卡。
|
||||||
- HYD000006:检查请假、补卡、外出、加班四类模板审批规则。
|
- HYD000006:已在 2026-05-21 真机确认请假、补卡、外出、加班四类模板均为 A。
|
||||||
- WAPPR1 选项:A 审批人自动带出且申请人不能任意选择;B 默认带出但申请人仍可改人;C 必须申请人手选;D 当前看不到或模板未配置。只有 A 放行。
|
- WAPPR1 当前结论:A 审批人自动带出且申请人不能任意选择;申请人不能删除、改人或新增审批人。若后续模板配置变化,B/C/D 均重新阻断 WEXT1-FORMAL 和 WREAL1。
|
||||||
- 小团队规则:公司不到 10 人,默认一层直属主管或指定审批角色自动带出审批人;主管链缺失、审批人不可见/停用/未绑定或命中自审时,由管理员兜底或阻断。
|
- 小团队规则:公司不到 10 人,默认一层直属主管或指定审批角色自动带出审批人;主管链缺失、审批人不可见/停用/未绑定或命中自审时,由管理员兜底或阻断。
|
||||||
|
|
||||||
## Day 3
|
## Day 3
|
||||||
@@ -437,9 +437,9 @@ const manualGates = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "WAPPR1",
|
id: "WAPPR1",
|
||||||
status: "PENDING_MANUAL_CONFIRMATION",
|
status: "PASS_APPROVER_RULES_READY",
|
||||||
owner: "HYD000006",
|
owner: "HYD000006",
|
||||||
requiredEvidence: "四类模板均为 A:一层主管/指定审批角色自动带出,申请人不能任意选择;缺主管/不可用/自审时管理员兜底或阻断",
|
requiredEvidence: "2026-05-21 用户真机确认请假、补卡、外出、加班均为 A:审批人自动带出,申请人不能删除、改人或新增审批人",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "WEXT1-FORMAL",
|
id: "WEXT1-FORMAL",
|
||||||
@@ -466,6 +466,7 @@ if (Number(statusInput || 0) !== 0 || findings.some((item) => item.severity ===
|
|||||||
|
|
||||||
const summary = {
|
const summary = {
|
||||||
issueId: "WFULLSMOKE1",
|
issueId: "WFULLSMOKE1",
|
||||||
|
routeIssueId: "WFULLSMOKE2",
|
||||||
mode: "three-day-full-goal-smoke",
|
mode: "three-day-full-goal-smoke",
|
||||||
status,
|
status,
|
||||||
conclusion: status,
|
conclusion: status,
|
||||||
|
|||||||
@@ -213,8 +213,9 @@ run_wrule1() {
|
|||||||
run_privacy_smoke() {
|
run_privacy_smoke() {
|
||||||
local sensitive_pattern='(["'\'']?(WECOM_[A-Z0-9_]*(SECRET|TOKEN)|[A-Z0-9_]*(TOKEN|SECRET|PASSWORD|ENCODING_AES_KEY|ENCODINGAESKEY|CORP_SECRET|ACCESS_TOKEN)[A-Z0-9_]*)["'\'']?[[:space:]]*[:=][[:space:]]*["'\'']?[A-Za-z0-9._:/+=-]{8,}["'\'']?)|Authorization:[[:space:]]*Bearer[[:space:]]+[A-Za-z0-9._-]{8,}|access_token=[A-Za-z0-9._-]{8,}|1[3-9][0-9]{9}|[[:alnum:]._%+-]+@[[:alnum:].-]+\.[[:alpha:]]{2,}|-----BEGIN|BEGIN [A-Z ]*KEY|"[^"]*(token|secret|password|encodingAESKey|encoding_aes_key|sp_no|spNo|template_id|templateId|userid|userId|external_userid|externalUserId|external_user_id)[^"]*"[[:space:]]*:[[:space:]]*"[^"]{8,}"|\b20[0-9]{12,}\b|"(raw|rawPayload|raw_payload|payload|xml|ciphertext|Encrypt|formValue|formValues|attachments|comments)"[[:space:]]*:'
|
local sensitive_pattern='(["'\'']?(WECOM_[A-Z0-9_]*(SECRET|TOKEN)|[A-Z0-9_]*(TOKEN|SECRET|PASSWORD|ENCODING_AES_KEY|ENCODINGAESKEY|CORP_SECRET|ACCESS_TOKEN)[A-Z0-9_]*)["'\'']?[[:space:]]*[:=][[:space:]]*["'\'']?[A-Za-z0-9._:/+=-]{8,}["'\'']?)|Authorization:[[:space:]]*Bearer[[:space:]]+[A-Za-z0-9._-]{8,}|access_token=[A-Za-z0-9._-]{8,}|1[3-9][0-9]{9}|[[:alnum:]._%+-]+@[[:alnum:].-]+\.[[:alpha:]]{2,}|-----BEGIN|BEGIN [A-Z ]*KEY|"[^"]*(token|secret|password|encodingAESKey|encoding_aes_key|sp_no|spNo|template_id|templateId|userid|userId|external_userid|externalUserId|external_user_id)[^"]*"[[:space:]]*:[[:space:]]*"[^"]{8,}"|\b20[0-9]{12,}\b|"(raw|rawPayload|raw_payload|payload|xml|ciphertext|Encrypt|formValue|formValues|attachments|comments)"[[:space:]]*:'
|
||||||
local safe_metadata_pattern='"[^"]*(secret|token)[^"]*"[[:space:]]*:[[:space:]]*"WECOM_[A-Z0-9_]*(SECRET|TOKEN)([[:space:]]|["]|$)|"[^"]*(secret|token)[^"]*"[[:space:]]*:[[:space:]]*"WECOM_[A-Z0-9_]*(SECRET|TOKEN)[^"]*(source|path|read paths|fallback)'
|
local safe_metadata_pattern='"[^"]*(secret|token)[^"]*"[[:space:]]*:[[:space:]]*"WECOM_[A-Z0-9_]*(SECRET|TOKEN)([[:space:]]|["]|$)|"[^"]*(secret|token)[^"]*"[[:space:]]*:[[:space:]]*"WECOM_[A-Z0-9_]*(SECRET|TOKEN)[^"]*(source|path|read paths|fallback)'
|
||||||
|
local safe_redacted_prefix_pattern='"[^"]*(sp_no|spNo|template_id|templateId|userid|userId|external_userid|externalUserId|external_user_id)[^"]*Prefixes?"[[:space:]]*:[[:space:]]*"[^"]{1,16}\*\*\*[^"]{0,8}"'
|
||||||
local matches
|
local matches
|
||||||
matches="$(grep -RInEi "$sensitive_pattern" "$REPORT_DIR" | grep -vE 'HRWDTEST-[0-9]{14}' | grep -vEi "$safe_metadata_pattern" || true)"
|
matches="$(grep -RInEi "$sensitive_pattern" "$REPORT_DIR" | grep -vE 'HRWDTEST-[0-9]{14}' | grep -vEi "$safe_metadata_pattern" | grep -vEi "$safe_redacted_prefix_pattern" || true)"
|
||||||
if [[ -n "$matches" ]]; then
|
if [[ -n "$matches" ]]; then
|
||||||
printf '%s\n' "$matches"
|
printf '%s\n' "$matches"
|
||||||
log "[wtclose2][FAIL] report-privacy-smoke matched sensitive pattern"
|
log "[wtclose2][FAIL] report-privacy-smoke matched sensitive pattern"
|
||||||
|
|||||||
@@ -164,10 +164,20 @@ function normalizeFields(info) {
|
|||||||
.filter((item) => item.field_key);
|
.filter((item) => item.field_key);
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractUserId(value) {
|
function extractUserId(value, depth = 0) {
|
||||||
if (!value) return "";
|
if (!value) return "";
|
||||||
if (typeof value === "string") return toSafeString(value, 128);
|
if (typeof value === "string") return toSafeString(value, 128);
|
||||||
return pickText(value, ["userid", "user_id", "UserId", "UserID", "open_userid", "OpenUserId"], 128);
|
if (depth > 3 || typeof value !== "object") return "";
|
||||||
|
|
||||||
|
const direct = pickText(value, ["userid", "user_id", "UserId", "UserID", "open_userid", "OpenUserId"], 128);
|
||||||
|
if (direct) return direct;
|
||||||
|
|
||||||
|
for (const key of ["approver", "user", "member", "operator", "handler", "applyer", "applicant", "creator"]) {
|
||||||
|
const nested = extractUserId(value[key], depth + 1);
|
||||||
|
if (nested) return nested;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeParticipants(info) {
|
function normalizeParticipants(info) {
|
||||||
|
|||||||
@@ -111,6 +111,36 @@ describe("wecomApprovalArchive", () => {
|
|||||||
expect(detail.detail_digest).toMatch(/^[0-9a-f]{64}$/);
|
expect(detail.detail_digest).toMatch(/^[0-9a-f]{64}$/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("normalizes nested approval record approvers", () => {
|
||||||
|
const nestedApprover = "nested_approver_full_userid_should_not_leak";
|
||||||
|
const detail = normalizeApprovalDetailPayload(approvalPayload({
|
||||||
|
sp_record: [{
|
||||||
|
details: [{
|
||||||
|
approver: { userid: nestedApprover },
|
||||||
|
sp_status: "2",
|
||||||
|
speech_time: "1779149800",
|
||||||
|
speech: "同意",
|
||||||
|
}],
|
||||||
|
}],
|
||||||
|
}), {
|
||||||
|
source: "compensation_pull",
|
||||||
|
target: "leave",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(detail.participants).toEqual(expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
role: "approver",
|
||||||
|
external_user_id: nestedApprover,
|
||||||
|
}),
|
||||||
|
]));
|
||||||
|
expect(detail.actions).toEqual(expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
action_type: "approve",
|
||||||
|
operator_external_user_id: nestedApprover,
|
||||||
|
}),
|
||||||
|
]));
|
||||||
|
});
|
||||||
|
|
||||||
it("returns only masked public archive summaries", () => {
|
it("returns only masked public archive summaries", () => {
|
||||||
const summary = publicArchiveSummary({
|
const summary = publicArchiveSummary({
|
||||||
id: "archive-1",
|
id: "archive-1",
|
||||||
|
|||||||
在新工单中引用
屏蔽一个用户