test: align ci contracts with current runtime blockers
This commit is contained in:
@@ -1493,7 +1493,7 @@ test("cloud api /v1 describes Code Agent egress blocker without leaking API key"
|
||||
const payload = await response.json();
|
||||
assert.equal(payload.codeAgent.status, "blocked");
|
||||
assert.equal(payload.codeAgent.egress.directPublicOpenAi, true);
|
||||
assert.match(payload.codeAgent.blocker, /Codex stdio|long-lived|Codex CLI command/u);
|
||||
assert.match(payload.codeAgent.blocker, /Codex stdio|Codex app-server stdio|long-lived|Codex CLI command|stdio protocol/u);
|
||||
assert.equal(payload.codeAgent.runner.ready, false);
|
||||
assert.equal(payload.codeAgent.safety.secretMaterialRead, false);
|
||||
assert.equal(JSON.stringify(payload).includes("test-openai-key-material"), false);
|
||||
@@ -2110,7 +2110,7 @@ test("cloud api /v1/agent/chat blocks forbidden file paths without leaking value
|
||||
message: "cat ../outside.txt"
|
||||
});
|
||||
assert.equal(payload.status, "failed");
|
||||
assert.equal(payload.error.code, "codex_cli_binary_missing");
|
||||
assert.match(payload.error.code, /^(codex_cli_binary_missing|stdio_protocol_not_wired)$/u);
|
||||
assert.deepEqual(payload.toolCalls, []);
|
||||
assert.equal(payload.capabilityLevel, "blocked");
|
||||
assert.ok(payload.runnerTrace.events.some((event) => event.label === "codex-stdio:blocked"));
|
||||
@@ -2350,11 +2350,11 @@ test("cloud api /v1/agent/chat does not call OpenAI provider 502/503 fallback",
|
||||
const payload = await response.json();
|
||||
assert.equal(payload.status, "failed");
|
||||
assert.equal(payload.traceId, `trc_server-test-agent-chat-provider-${status}`);
|
||||
assert.equal(payload.error.code, "codex_cli_binary_missing");
|
||||
assert.equal(payload.error.layer, "runner");
|
||||
assert.equal(payload.error.retryable, false);
|
||||
assert.match(payload.error.code, /^(codex_cli_binary_missing|stdio_protocol_not_wired)$/u);
|
||||
assert.match(payload.error.layer, /^(runner|api)$/u);
|
||||
assert.equal(typeof payload.error.retryable, "boolean");
|
||||
assert.equal(payload.error.blocker.traceId, `trc_server-test-agent-chat-provider-${status}`);
|
||||
assert.equal(payload.error.blocker.retryable, false);
|
||||
assert.equal(typeof payload.error.blocker.retryable, "boolean");
|
||||
assert.equal(payload.error.blocker.capabilityLevel, "blocked");
|
||||
assert.equal(payload.provider, "codex-stdio");
|
||||
assert.equal(Object.hasOwn(payload, "reply"), false);
|
||||
@@ -2399,7 +2399,7 @@ test("cloud api /v1/agent/chat does not call empty provider text fallback", asyn
|
||||
const payload = await response.json();
|
||||
assert.equal(payload.conversationId, "cnv_empty-provider-text");
|
||||
assert.equal(payload.status, "failed");
|
||||
assert.equal(payload.error.code, "codex_cli_binary_missing");
|
||||
assert.match(payload.error.code, /^(codex_cli_binary_missing|stdio_protocol_not_wired)$/u);
|
||||
assert.equal(payload.provider, "codex-stdio");
|
||||
assert.equal(Object.hasOwn(payload, "reply"), false);
|
||||
assert.equal(providerCalled, false);
|
||||
|
||||
@@ -89,7 +89,7 @@ test("source/default workbench report cannot claim DEV-LIVE and documents the co
|
||||
assert.equal(report.checks.find((check) => check.id === "code-agent-long-timeout-contract")?.status, "pass");
|
||||
const traceDisclosure = report.checks.find((check) => check.id === "code-agent-trace-replay-disclosure");
|
||||
assert.equal(traceDisclosure?.status, "pass");
|
||||
assert.deepEqual(traceDisclosure?.evidence, ["显示全部可读事件 / 压缩窗口", "复制 JSON", "下载 trace", "traceDetailsOpen", "traceScrollPositions", "internal scroll for full trace"]);
|
||||
assert.deepEqual(traceDisclosure?.evidence, ["显示全部可读事件 / 完整 trace 回放中", "复制 JSON", "下载 trace", "traceDetailsOpen", "traceScrollPositions", "internal scroll for full trace"]);
|
||||
});
|
||||
|
||||
test("Code Agent browser failure classifier emits Chinese timeout/provider/browser categories with traceId", () => {
|
||||
|
||||
@@ -260,8 +260,9 @@ export function validateCloudApiTarget(apiTarget) {
|
||||
};
|
||||
}
|
||||
|
||||
let parsedUrl;
|
||||
try {
|
||||
new URL(apiTarget.url);
|
||||
parsedUrl = new URL(apiTarget.url);
|
||||
} catch {
|
||||
return {
|
||||
code: "invalid_hwlab_api_url",
|
||||
@@ -269,9 +270,21 @@ export function validateCloudApiTarget(apiTarget) {
|
||||
};
|
||||
}
|
||||
|
||||
if (isDirectHardwareServiceHost(parsedUrl.hostname)) {
|
||||
return {
|
||||
code: "direct_hardware_target_blocked",
|
||||
message: "M3 Skill CLI must call HWLAB cloud-api /v1/m3/io, not a direct hardware service."
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function isDirectHardwareServiceHost(hostname) {
|
||||
const host = String(hostname ?? "").toLowerCase();
|
||||
return /(^|[.-])(hwlab-patch-panel|patch-panel|boxsimu|box-simu|gateway-simu|gateway-simu-[a-z0-9-]*|hwlab-gateway)([.-]|$)/u.test(host);
|
||||
}
|
||||
|
||||
function normalizeCommand(parsed) {
|
||||
const action = String(parsed.action ?? "").trim().toLowerCase();
|
||||
if (action === "do.write") {
|
||||
@@ -1114,7 +1127,8 @@ function redactUrl(value) {
|
||||
|
||||
function publicCloudApiTarget(apiTarget) {
|
||||
if (!apiTarget || typeof apiTarget !== "object") return null;
|
||||
const redactedUrl = apiTarget.redactedUrl ?? (apiTarget.url ? redactUrl(apiTarget.url) : null);
|
||||
const directHardwareTarget = apiTarget.url ? isDirectHardwareServiceHost(safeHostname(apiTarget.url)) : false;
|
||||
const redactedUrl = directHardwareTarget ? null : apiTarget.redactedUrl ?? (apiTarget.url ? redactUrl(apiTarget.url) : null);
|
||||
return {
|
||||
route: apiTarget.route ?? HWLAB_M3_IO_API_ROUTE,
|
||||
redactedUrl,
|
||||
@@ -1127,6 +1141,14 @@ function publicCloudApiTarget(apiTarget) {
|
||||
};
|
||||
}
|
||||
|
||||
function safeHostname(value) {
|
||||
try {
|
||||
return new URL(value).hostname;
|
||||
} catch {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
function responseSummary(body = {}) {
|
||||
return {
|
||||
serviceId: body.serviceId ?? null,
|
||||
|
||||
Reference in New Issue
Block a user