From 0b26610e662c86051656e7af16a83da8568100c9 Mon Sep 17 00:00:00 2001 From: Code Queue Review Date: Sat, 23 May 2026 03:57:09 +0000 Subject: [PATCH] test: accept sanitized blocked code agent evidence --- scripts/dev-cloud-workbench-smoke.test.mjs | 30 +++++++ scripts/src/code-agent-response-contract.mjs | 3 +- scripts/src/dev-cloud-workbench-smoke-lib.mjs | 9 ++- .../validate-dev-gate-report-guard.test.mjs | 81 ++++++++++++++++--- scripts/validate-dev-gate-report.mjs | 3 +- 5 files changed, 111 insertions(+), 15 deletions(-) diff --git a/scripts/dev-cloud-workbench-smoke.test.mjs b/scripts/dev-cloud-workbench-smoke.test.mjs index 04982a53..038458f9 100644 --- a/scripts/dev-cloud-workbench-smoke.test.mjs +++ b/scripts/dev-cloud-workbench-smoke.test.mjs @@ -143,6 +143,36 @@ test("Code Agent browser classifier blocks provider 502 response shapes", () => assert.equal(classification.blocker, "provider-upstream"); }); +test("Code Agent browser classifier treats sanitized 服务受阻 UI as blocked provider evidence", () => { + const summary = sanitizeAgentChatBody({ + status: "failed", + provider: "openai-responses", + model: "gpt-5.5", + backend: "hwlab-cloud-api/openai-responses", + traceId: "trc_provider_502", + error: { + code: "provider_unavailable", + message: "OpenAI Responses returned HTTP 502: request rejected", + providerStatus: 502, + missingEnv: [] + } + }, { httpStatus: 200 }); + + const classification = classifyCodeAgentBrowserJourney({ + responseSummary: summary, + httpOk: true, + httpStatus: 200, + ui: { + agentChatStatus: "服务受阻", + completedMessageVisible: false, + failedMessageVisible: true + } + }); + + assert.equal(classification.status, "blocked"); + assert.equal(classification.blocker, "provider-upstream"); +}); + test("Code Agent readiness classifier blocks completed payloads over any non-2xx HTTP status", () => { const completedPayload = { conversationId: "cnv_completed_non_2xx", diff --git a/scripts/src/code-agent-response-contract.mjs b/scripts/src/code-agent-response-contract.mjs index a24e2976..9eedae3c 100644 --- a/scripts/src/code-agent-response-contract.mjs +++ b/scripts/src/code-agent-response-contract.mjs @@ -1,5 +1,6 @@ const trustedLiveProviders = new Set(["openai-responses", "codex-cli"]); const untrustedProviderPattern = /\b(?:echo|mock|fixture|stub|sample)\b/iu; +const blockedCodeAgentUiLabels = new Set(["发送失败", "服务受阻", "BLOCKED 凭证缺口"]); export function classifyCodeAgentChatReadiness(payload, { realDevLive = false, httpStatus = null } = {}) { const providerBlock = classifyCodeAgentProviderBlock(payload, { httpStatus }); @@ -193,7 +194,7 @@ export function summarizeCodeAgentPayload(payload, { httpStatus = null, traceId export function classifyCodeAgentBrowserJourney({ responseSummary, httpOk = false, httpStatus = null, ui = null } = {}) { const providerBlock = classifyCodeAgentProviderBlock(responseSummary, { httpStatus }); const uiCompleted = ui?.agentChatStatus === "DEV-LIVE 回复" || ui?.agentChatStatus === "已回复" || ui?.completedMessageVisible === true; - const uiFailed = ui?.failedMessageVisible === true || ui?.agentChatStatus === "发送失败" || ui?.agentChatStatus === "BLOCKED 凭证缺口"; + const uiFailed = ui?.failedMessageVisible === true || blockedCodeAgentUiLabels.has(ui?.agentChatStatus); const evidence = inspectSanitizedCompletionEvidence(responseSummary); const backendCompleted = httpOk && diff --git a/scripts/src/dev-cloud-workbench-smoke-lib.mjs b/scripts/src/dev-cloud-workbench-smoke-lib.mjs index 1007e6fa..29a00671 100644 --- a/scripts/src/dev-cloud-workbench-smoke-lib.mjs +++ b/scripts/src/dev-cloud-workbench-smoke-lib.mjs @@ -148,6 +148,8 @@ const requiredCodeAgentEvidenceTerms = Object.freeze([ "message-evidence-chip" ]); +const blockedCodeAgentUiLabels = Object.freeze(["服务受阻", "BLOCKED 凭证缺口"]); + const forbiddenWritePatterns = Object.freeze([ /callRpc\(\s*["']hardware\./u, /hardware\.operation\.request/u, @@ -317,7 +319,7 @@ function runStaticSmoke() { addCheck(checks, blockers, "code-agent-provider-readiness-visibility", hasCodeAgentReadinessVisibility(files), "Workbench shows provider credential blockers and only real completed replies can become DEV-LIVE.", { blocker: "observability_blocker", - evidence: ["BLOCKED 凭证缺口", "provider_unavailable", "OPENAI_API_KEY", "hwlab-code-agent-provider/openai-api-key", "completed -> dev-live guard"] + evidence: ["服务受阻 or legacy BLOCKED 凭证缺口", "provider_unavailable classifier", "completed -> dev-live guard"] }); addCheck(checks, blockers, "code-agent-completed-evidence-visible", hasCodeAgentCompletedEvidenceVisibility(files), "Completed Code Agent replies expose provider/model/trace/conversation evidence and reject echo/mock/stub completions.", { @@ -331,7 +333,7 @@ function runStaticSmoke() { "DEV-LIVE 回复", "SOURCE 回复", "发送失败", - "BLOCKED 凭证缺口", + "服务受阻 or legacy BLOCKED 凭证缺口", "message-evidence/details/chips" ] }); @@ -1207,10 +1209,9 @@ function hasCodeAgentReadinessVisibility({ html, app }) { /codeAgentAvailability/u.test(app) && /codeAgentAvailabilityFrom/u.test(app) && /latestCompletedAgentMessage/u.test(app) && - /BLOCKED 凭证缺口/u.test(app) && + blockedCodeAgentUiLabels.some((label) => app.includes(label)) && /provider_unavailable/u.test(app) && /OPENAI_API_KEY/u.test(app) && - /hwlab-code-agent-provider\/openai-api-key/u.test(app) && /只有真实 completed 回复才标 DEV-LIVE/u.test(app) && /不能因为只有 conversationId 标为开发实况/u.test(app) && /isTrustedCodeAgentProvider/u.test(app) && diff --git a/scripts/validate-dev-gate-report-guard.test.mjs b/scripts/validate-dev-gate-report-guard.test.mjs index 52e1e005..b6334fb8 100644 --- a/scripts/validate-dev-gate-report-guard.test.mjs +++ b/scripts/validate-dev-gate-report-guard.test.mjs @@ -357,8 +357,7 @@ test("rejects accepted workbench evidence that mixes provider_unavailable blocke await assertRejected(report, /provider_unavailable, OPENAI_API_KEY, or providerStatus blockers/); }); -test("accepts blocked workbench evidence for provider HTTP 502 without promoting completion", async () => { - const report = cloneBaseReport(); +function applyProviderBlockedJourney(report, { uiLabel = "发送失败" } = {}) { const journey = report.checks.find((check) => check.id === "live-code-agent-browser-journey"); report.status = "blocked"; report.evidenceLevel = "BLOCKED"; @@ -392,7 +391,7 @@ test("accepts blocked workbench evidence for provider HTTP 502 without promoting "provider=openai-responses", "model=gpt-5.5", "traceId=trc_provider_502", - "uiStatus=发送失败", + `uiStatus=${uiLabel}`, "blocker=provider-upstream" ]; journey.observations.request.status = 200; @@ -415,7 +414,75 @@ test("accepts blocked workbench evidence for provider HTTP 502 without promoting providerStatus: 502, reason: "Code Agent HTTP non-2xx status 502 means the upstream response is blocked, not completed" }; - journey.observations.ui.agentChatStatus = "发送失败"; + journey.observations.ui.agentChatStatus = uiLabel; + journey.observations.ui.inputCleared = false; + journey.observations.ui.completedMessageVisible = false; + journey.observations.ui.failedMessageVisible = true; + journey.observations.networkEvents = [ + { + method: "POST", + status: 200, + urlPath: "/v1/agent/chat", + body: journey.observations.response + } + ]; + return report; +} + +test("accepts blocked workbench evidence for provider HTTP 502 without promoting completion", async () => { + const report = applyProviderBlockedJourney(cloneBaseReport()); + + await withReport(report, async (reportPath) => { + const result = runValidator(reportPath); + assert.equal(result.status, 0, result.stderr); + assert.match(result.stdout, /validated 1 dev-gate report JSON file/); + }); +}); + +test("accepts sanitized 服务受阻 provider blocker wording only as blocked evidence", async () => { + const report = applyProviderBlockedJourney(cloneBaseReport(), { uiLabel: "服务受阻" }); + + await withReport(report, async (reportPath) => { + const result = runValidator(reportPath); + assert.equal(result.status, 0, result.stderr); + assert.match(result.stdout, /validated 1 dev-gate report JSON file/); + }); +}); + +test("rejects sanitized 服务受阻 provider blocker evidence promoted to full workbench acceptance", async () => { + const report = fullAcceptanceReport(); + const journey = report.checks.find((check) => check.id === "live-code-agent-browser-journey"); + journey.status = "pass"; + journey.summary = "Incorrectly promoted provider blocker evidence."; + journey.evidence = [ + "POST /v1/agent/chat HTTP 200", + "status=failed", + "provider=openai-responses", + "model=gpt-5.5", + "traceId=trc_provider_502", + "uiStatus=服务受阻", + "blocker=provider-upstream" + ]; + journey.observations.response = { + status: "failed", + provider: "openai-responses", + model: "gpt-5.5", + backend: "hwlab-cloud-api/openai-responses", + traceId: "trc_provider_502", + hasReply: false, + error: { + code: "provider_unavailable", + missingEnv: [], + providerStatus: 502 + } + }; + journey.observations.classification = { + status: "blocked", + blocker: "provider-upstream", + providerStatus: 502, + reason: "Code Agent HTTP non-2xx status 502 means the upstream response is blocked, not completed" + }; + journey.observations.ui.agentChatStatus = "服务受阻"; journey.observations.ui.inputCleared = false; journey.observations.ui.completedMessageVisible = false; journey.observations.ui.failedMessageVisible = true; @@ -428,11 +495,7 @@ test("accepts blocked workbench evidence for provider HTTP 502 without promoting } ]; - await withReport(report, async (reportPath) => { - const result = runValidator(reportPath); - assert.equal(result.status, 0, result.stderr); - assert.match(result.stdout, /validated 1 dev-gate report JSON file/); - }); + await assertRejected(report, /response\.status|provider_unavailable, OPENAI_API_KEY, or providerStatus blockers/); }); test("rejects accepted workbench evidence that hides providerStatus 502 in network events", async () => { diff --git a/scripts/validate-dev-gate-report.mjs b/scripts/validate-dev-gate-report.mjs index 59b777e4..66f70fe4 100644 --- a/scripts/validate-dev-gate-report.mjs +++ b/scripts/validate-dev-gate-report.mjs @@ -42,6 +42,7 @@ const evidenceLevels = new Set(["SOURCE", "LOCAL", "DRY-RUN", "DEV-LIVE", "BLOCK const devCloudWorkbenchRuntimeIdentityMaxSkewMs = 15 * 60 * 1000; const syntheticAcceptedCodeAgentModels = new Set(["gpt-5"]); const deploymentPreflightCodeAgentErrorCode = "deployment_identity_preflight"; +const blockedCodeAgentUiLabels = new Set(["发送失败", "服务受阻", "BLOCKED 凭证缺口"]); const requiredValidationCommands = [ "node --check scripts/validate-dev-gate-report.mjs", "node scripts/validate-dev-gate-report.mjs" @@ -1548,7 +1549,7 @@ function assertDevCloudWorkbenchCodeAgentBlocked(report, label, journey) { assertObject(journey.observations.ui, `${label}.live-code-agent-browser-journey.ui`); assert.ok( - ["发送失败", "BLOCKED 凭证缺口"].includes(journey.observations.ui.agentChatStatus), + blockedCodeAgentUiLabels.has(journey.observations.ui.agentChatStatus), `${label}.live-code-agent-browser-journey.ui.agentChatStatus` ); assert.equal(journey.observations.ui.completedMessageVisible, false, `${label}.live-code-agent-browser-journey.ui.completedMessageVisible`);