1090 lines
33 KiB
JavaScript
1090 lines
33 KiB
JavaScript
import assert from "node:assert/strict";
|
||
import { readFileSync } from "node:fs";
|
||
import test from "node:test";
|
||
|
||
import {
|
||
classifyRuntimeDurableReadiness,
|
||
classifyWorkbenchLiveStatus
|
||
} from "../live-status.mjs";
|
||
import { wiringPresentationFromSummary } from "../wiring-status.mjs";
|
||
|
||
const statusMatrix = JSON.parse(readFileSync(
|
||
new URL("../../../fixtures/cloud-web/durable-readiness-status-matrix.json", import.meta.url),
|
||
"utf8"
|
||
));
|
||
|
||
const okApi = Object.freeze({
|
||
ok: true,
|
||
status: 200,
|
||
data: {
|
||
serviceId: "hwlab-cloud-api",
|
||
status: "ok",
|
||
ready: true,
|
||
readiness: {
|
||
status: "ok",
|
||
ready: true,
|
||
durability: {
|
||
status: "ready",
|
||
ready: true
|
||
}
|
||
},
|
||
runtime: {
|
||
status: "ready",
|
||
durable: true,
|
||
ready: true,
|
||
liveRuntimeEvidence: true
|
||
}
|
||
}
|
||
});
|
||
|
||
const okRest = Object.freeze({
|
||
ok: true,
|
||
status: 200,
|
||
data: {
|
||
serviceId: "hwlab-cloud-api",
|
||
status: "ok",
|
||
ready: true,
|
||
readiness: {
|
||
status: "ok",
|
||
ready: true,
|
||
durability: {
|
||
status: "ready",
|
||
ready: true,
|
||
queryResult: "durable_readiness_ready"
|
||
}
|
||
},
|
||
runtime: {
|
||
adapter: "postgres",
|
||
status: "ready",
|
||
durable: true,
|
||
ready: true,
|
||
liveRuntimeEvidence: true,
|
||
connection: {
|
||
queryResult: "durable_readiness_ready"
|
||
}
|
||
},
|
||
codeAgent: {
|
||
endpoint: "POST /v1/agent/chat",
|
||
status: "available",
|
||
ready: true,
|
||
provider: "openai-responses",
|
||
backend: "hwlab-cloud-api/openai-responses",
|
||
capabilityLevel: "available"
|
||
}
|
||
}
|
||
});
|
||
|
||
const codexStdioRest = Object.freeze({
|
||
ok: true,
|
||
status: 200,
|
||
data: {
|
||
serviceId: "hwlab-cloud-api",
|
||
status: "ok",
|
||
ready: true,
|
||
readiness: {
|
||
status: "ok",
|
||
ready: true,
|
||
durability: {
|
||
status: "ready",
|
||
ready: true,
|
||
queryResult: "durable_readiness_ready"
|
||
}
|
||
},
|
||
runtime: {
|
||
adapter: "postgres",
|
||
status: "ready",
|
||
durable: true,
|
||
ready: true,
|
||
liveRuntimeEvidence: true,
|
||
connection: {
|
||
queryResult: "durable_readiness_ready"
|
||
}
|
||
},
|
||
codeAgent: {
|
||
endpoint: "POST /v1/agent/chat",
|
||
status: "codex-stdio-feasible",
|
||
ready: true,
|
||
provider: "codex-stdio",
|
||
backend: "hwlab-cloud-api/codex-mcp-stdio",
|
||
capabilityLevel: "long-lived-codex-stdio-session",
|
||
agentKind: "codex-stdio-feasible",
|
||
codexStdioFeasibility: {
|
||
canStartLongLivedCodexStdio: true
|
||
},
|
||
longLivedSessionGate: {
|
||
status: "pass",
|
||
pass: true
|
||
}
|
||
}
|
||
}
|
||
});
|
||
|
||
const readonlyOnlyRest = Object.freeze({
|
||
ok: true,
|
||
status: 200,
|
||
data: {
|
||
serviceId: "hwlab-cloud-api",
|
||
status: "degraded",
|
||
ready: false,
|
||
readiness: {
|
||
status: "ok",
|
||
ready: true,
|
||
durability: {
|
||
status: "ready",
|
||
ready: true,
|
||
queryResult: "durable_readiness_ready"
|
||
}
|
||
},
|
||
runtime: {
|
||
adapter: "postgres",
|
||
status: "ready",
|
||
durable: true,
|
||
ready: true,
|
||
liveRuntimeEvidence: true,
|
||
connection: {
|
||
queryResult: "durable_readiness_ready"
|
||
}
|
||
},
|
||
codeAgent: {
|
||
endpoint: "POST /v1/agent/chat",
|
||
status: "partial",
|
||
ready: false,
|
||
partialReady: true,
|
||
agentKind: "controlled-readonly-session-registry",
|
||
provider: "openai-responses",
|
||
backend: "hwlab-cloud-api/openai-responses",
|
||
capabilityLevel: "read-only-session-tools",
|
||
runner: {
|
||
kind: "hwlab-readonly-runner",
|
||
ready: true,
|
||
writeCapable: false
|
||
},
|
||
blockerCodes: ["codex_stdio_supervisor_disabled", "provider_token_boundary"],
|
||
codexStdioFeasibility: {
|
||
status: "blocked",
|
||
blockers: [{ code: "codex_stdio_supervisor_disabled" }]
|
||
},
|
||
longLivedSessionGate: {
|
||
status: "blocked",
|
||
pass: false
|
||
}
|
||
}
|
||
}
|
||
});
|
||
|
||
const okM3Control = Object.freeze({
|
||
ok: true,
|
||
status: 200,
|
||
data: {
|
||
status: "available",
|
||
readiness: {
|
||
status: "ready",
|
||
controlReady: true,
|
||
sourceKind: "DEV-LIVE",
|
||
evidenceLevel: "DEV-LIVE"
|
||
}
|
||
}
|
||
});
|
||
|
||
const okM3Status = Object.freeze({
|
||
ok: true,
|
||
status: 200,
|
||
data: {
|
||
status: "live",
|
||
sourceKind: "DEV-LIVE",
|
||
summary: "M3 硬件状态与可信持久化均为 DEV-LIVE。"
|
||
}
|
||
});
|
||
|
||
test("cloud web live build surface renders Beijing summary, details, missing time, and external images", async () => {
|
||
const { readFileSync } = await import("node:fs");
|
||
const html = readFileSync(new URL("../index.html", import.meta.url), "utf8");
|
||
const app = readFileSync(new URL("../app.mjs", import.meta.url), "utf8");
|
||
const styles = readFileSync(new URL("../styles.css", import.meta.url), "utf8");
|
||
|
||
assert.match(html, /id="live-build-summary"/u);
|
||
assert.match(html, /id="live-build-latest"/u);
|
||
assert.match(html, /id="live-build-list"/u);
|
||
assert.match(app, /formatBeijingTime/u);
|
||
assert.match(app, /Asia\/Shanghai/u);
|
||
assert.match(app, /liveBuildSourceLabel/u);
|
||
assert.match(app, /liveBuildDesiredStateText/u);
|
||
assert.match(app, /外部镜像或非 HWLAB 构建产物/u);
|
||
assert.doesNotMatch(app, /artifact report|artifact-report/u);
|
||
assert.match(styles, /\.live-build-summary-label\s*\{[^}]*min-width:\s*0;[^}]*max-width:\s*100%;[^}]*overflow-wrap:\s*anywhere;/su);
|
||
assert.match(styles, /\.live-build-list\s*\{[^}]*min-width:\s*0;[^}]*max-height:\s*min\(352px,\s*calc\(58dvh - 54px\)\);[^}]*overflow:\s*auto;/su);
|
||
assert.match(styles, /@media \(max-width: 860px\)[\s\S]*?\.live-build-row-head\s*\{[\s\S]*?grid-template-columns:\s*1fr;/u);
|
||
});
|
||
|
||
test("classifies fully ready workbench runtime as API 正常", () => {
|
||
const status = classifyWorkbenchLiveStatus({
|
||
healthLive: okApi,
|
||
restIndex: codexStdioRest,
|
||
health: okApi,
|
||
adapter: okApi,
|
||
m3Control: okM3Control,
|
||
m3Status: okM3Status
|
||
});
|
||
|
||
assert.equal(status.kind, "pass");
|
||
assert.equal(status.label, "API 正常");
|
||
assert.match(status.detail, /hwlab-cloud-api \/health\/live/u);
|
||
assert.equal(status.internalRawStatuses.includes("degraded"), false);
|
||
});
|
||
|
||
test("classifies controlled readonly Code Agent as read-only, not full pass", () => {
|
||
const status = classifyWorkbenchLiveStatus({
|
||
healthLive: okApi,
|
||
restIndex: readonlyOnlyRest,
|
||
health: okApi,
|
||
adapter: okApi,
|
||
m3Control: okM3Control,
|
||
m3Status: okM3Status
|
||
});
|
||
|
||
assert.equal(status.kind, "readonly");
|
||
assert.equal(status.label, "只读模式");
|
||
assert.equal(status.reasonCode, "codex_stdio_blocked_readonly_session_available");
|
||
assert.match(status.detail, /controlled-readonly-session-registry/u);
|
||
});
|
||
|
||
test("structured readiness shows current codex stdio blocker instead of stale provider or DB blockers", () => {
|
||
const status = classifyWorkbenchLiveStatus({
|
||
healthLive: okApi,
|
||
restIndex: {
|
||
ok: true,
|
||
status: 200,
|
||
data: {
|
||
serviceId: "hwlab-cloud-api",
|
||
status: "degraded",
|
||
ready: false,
|
||
readiness: {
|
||
status: "degraded",
|
||
ready: false,
|
||
durability: {
|
||
status: "ready",
|
||
ready: true,
|
||
queryResult: "durable_readiness_ready"
|
||
},
|
||
codeAgent: {
|
||
status: "blocked",
|
||
ready: false,
|
||
providerReady: true,
|
||
durableDbReady: true,
|
||
sessionRunnerReady: true,
|
||
codexStdioFeasible: false,
|
||
currentBlocker: "codex_stdio_supervisor_disabled",
|
||
currentBlockers: ["codex_stdio_supervisor_disabled"],
|
||
provider: {
|
||
status: "ready",
|
||
ready: true,
|
||
provider: "openai-responses",
|
||
backend: "hwlab-cloud-api/openai-responses",
|
||
mode: "openai",
|
||
blocker: null
|
||
},
|
||
sessionRunner: {
|
||
status: "read_only_long_lived_ready",
|
||
ready: true,
|
||
kind: "hwlab-readonly-runner",
|
||
mode: "controlled-readonly-session-registry",
|
||
capabilityLevel: "read-only-session-tools"
|
||
},
|
||
codexStdio: {
|
||
status: "blocked",
|
||
ready: false,
|
||
blocker: "codex_stdio_supervisor_disabled",
|
||
blockerCodes: ["codex_stdio_supervisor_disabled"]
|
||
}
|
||
}
|
||
},
|
||
runtime: {
|
||
adapter: "postgres",
|
||
status: "ready",
|
||
durable: true,
|
||
ready: true,
|
||
liveRuntimeEvidence: true,
|
||
connection: {
|
||
queryResult: "durable_readiness_ready"
|
||
}
|
||
},
|
||
codeAgent: {
|
||
status: "partial",
|
||
ready: false,
|
||
provider: "openai-responses",
|
||
backend: "hwlab-cloud-api/openai-responses",
|
||
capabilityLevel: "read-only-session-tools",
|
||
blockerCodes: [
|
||
"provider_unavailable",
|
||
"runtime_durable_adapter_auth_blocked"
|
||
]
|
||
}
|
||
}
|
||
},
|
||
health: okApi,
|
||
adapter: okApi,
|
||
m3Control: okM3Control,
|
||
m3Status: okM3Status
|
||
});
|
||
|
||
assert.equal(status.kind, "readonly");
|
||
assert.equal(status.reasonCode, "codex_stdio_supervisor_disabled");
|
||
assert.match(status.detail, /providerReady=true/u);
|
||
assert.match(status.detail, /durableDbReady=true/u);
|
||
assert.doesNotMatch(status.detail, /provider_unavailable|runtime_durable_adapter_auth_blocked/u);
|
||
});
|
||
|
||
test("structured readiness missing legacy codeAgent payload degrades with concrete current blocker", () => {
|
||
const status = classifyWorkbenchLiveStatus({
|
||
healthLive: {
|
||
ok: true,
|
||
status: 200,
|
||
data: {
|
||
serviceId: "hwlab-cloud-api",
|
||
status: "degraded",
|
||
ready: false,
|
||
readiness: {
|
||
status: "degraded",
|
||
ready: false,
|
||
durability: {
|
||
status: "ready",
|
||
ready: true
|
||
},
|
||
codeAgent: {
|
||
status: "blocked",
|
||
ready: false,
|
||
providerReady: false,
|
||
durableDbReady: true,
|
||
sessionRunnerReady: false,
|
||
codexStdioFeasible: false,
|
||
currentBlocker: "provider_config_blocked",
|
||
currentBlockers: ["provider_config_blocked"],
|
||
provider: {
|
||
status: "blocked",
|
||
ready: false,
|
||
provider: "openai-responses",
|
||
backend: "hwlab-cloud-api/openai-responses",
|
||
mode: "openai",
|
||
blocker: "provider_config_blocked"
|
||
},
|
||
sessionRunner: {
|
||
status: "blocked",
|
||
ready: false,
|
||
kind: "unknown",
|
||
capabilityLevel: "blocked"
|
||
},
|
||
codexStdio: {
|
||
status: "blocked",
|
||
ready: false,
|
||
blocker: "codex_stdio_supervisor_disabled",
|
||
blockerCodes: ["codex_stdio_supervisor_disabled"]
|
||
}
|
||
}
|
||
},
|
||
runtime: {
|
||
adapter: "postgres",
|
||
durable: true,
|
||
ready: true,
|
||
liveRuntimeEvidence: true
|
||
}
|
||
}
|
||
},
|
||
restIndex: okRest,
|
||
health: okApi,
|
||
adapter: okApi,
|
||
m3Control: okM3Control,
|
||
m3Status: okM3Status
|
||
});
|
||
|
||
assert.equal(status.kind, "error");
|
||
assert.equal(status.reasonCode, "provider_config_blocked");
|
||
assert.match(status.detail, /providerReady=false/u);
|
||
});
|
||
|
||
test("classifies concrete API failure with service path and HTTP status", () => {
|
||
const status = classifyWorkbenchLiveStatus({
|
||
healthLive: {
|
||
ok: false,
|
||
path: "/health/live",
|
||
status: 503,
|
||
error: "/health/live HTTP 503:runtime unavailable"
|
||
},
|
||
restIndex: okRest,
|
||
health: okApi,
|
||
adapter: okApi,
|
||
m3Control: okM3Control,
|
||
m3Status: okM3Status
|
||
});
|
||
|
||
assert.equal(status.kind, "error");
|
||
assert.equal(status.label, "API 错误");
|
||
assert.equal(status.serviceId, "hwlab-cloud-api");
|
||
assert.equal(status.apiPath, "/health/live");
|
||
assert.equal(status.reasonCode, "HTTP 503");
|
||
assert.match(status.detail, /错误:hwlab-cloud-api \/health\/live \/ HTTP 503/u);
|
||
});
|
||
|
||
test("classifies durable runtime degraded as explicit read-only mode", () => {
|
||
const status = classifyWorkbenchLiveStatus({
|
||
healthLive: {
|
||
ok: true,
|
||
status: 200,
|
||
data: {
|
||
serviceId: "hwlab-cloud-api",
|
||
status: "degraded",
|
||
ready: false,
|
||
runtime: {
|
||
durable: true,
|
||
ready: false,
|
||
liveRuntimeEvidence: false,
|
||
status: "blocked",
|
||
blocker: "runtime_durable_adapter_schema_blocked",
|
||
connection: {
|
||
queryResult: "schema_blocked"
|
||
}
|
||
},
|
||
readiness: {
|
||
status: "degraded",
|
||
ready: false,
|
||
durability: {
|
||
status: "blocked",
|
||
ready: false,
|
||
blocker: "runtime_durable_adapter_schema_blocked",
|
||
blockedLayer: "schema",
|
||
queryResult: "schema_blocked"
|
||
}
|
||
},
|
||
blockerCodes: ["runtime_durable_adapter_schema_blocked"]
|
||
}
|
||
},
|
||
restIndex: okRest,
|
||
health: okApi,
|
||
adapter: okApi,
|
||
m3Control: okM3Control,
|
||
m3Status: okM3Status
|
||
});
|
||
|
||
assert.equal(status.kind, "readonly");
|
||
assert.equal(status.label, "只读模式");
|
||
assert.equal(status.reasonCode, "runtime_durable_adapter_schema_blocked");
|
||
assert.match(status.detail, /只读:hwlab-cloud-api \/health\/live/u);
|
||
assert.match(status.detail, /runtime_durable_adapter_schema_blocked/u);
|
||
assert.match(status.detail, /schema_blocked/u);
|
||
assert.deepEqual(status.internalRawStatuses.filter((item) => item === "degraded"), ["degraded"]);
|
||
});
|
||
|
||
test("does not classify ready queryResult durable runtime as blocked", () => {
|
||
const status = classifyWorkbenchLiveStatus({
|
||
healthLive: {
|
||
ok: true,
|
||
status: 200,
|
||
data: {
|
||
serviceId: "hwlab-cloud-api",
|
||
status: "ready",
|
||
ready: true,
|
||
runtime: {
|
||
adapter: "postgres",
|
||
durable: true,
|
||
ready: true,
|
||
liveRuntimeEvidence: true,
|
||
status: "ready",
|
||
blocker: null,
|
||
connection: {
|
||
queryResult: "durable_readiness_ready"
|
||
}
|
||
},
|
||
readiness: {
|
||
status: "ready",
|
||
ready: true,
|
||
durability: {
|
||
status: "ready",
|
||
ready: true,
|
||
blocker: null,
|
||
blockedLayer: null,
|
||
queryResult: "durable_readiness_ready"
|
||
}
|
||
},
|
||
blockerCodes: []
|
||
}
|
||
},
|
||
restIndex: okRest,
|
||
health: okApi,
|
||
adapter: okApi,
|
||
m3Control: okM3Control,
|
||
m3Status: okM3Status
|
||
});
|
||
|
||
assert.equal(status.kind, "pass");
|
||
assert.equal(status.reasonCode, "ready");
|
||
assert.equal(status.probes.find((probe) => probe.apiPath === "/health/live")?.reasonCode, "ready");
|
||
});
|
||
|
||
test("classifies auth and migration durable blockers with shared runtime reason", () => {
|
||
for (const [reasonCode, queryResult, blockedLayer] of [
|
||
["runtime_durable_adapter_auth_blocked", "auth_blocked", "auth"],
|
||
["runtime_durable_adapter_migration_blocked", "migration_blocked", "migration"]
|
||
]) {
|
||
const status = classifyWorkbenchLiveStatus({
|
||
healthLive: {
|
||
ok: true,
|
||
status: 200,
|
||
data: {
|
||
serviceId: "hwlab-cloud-api",
|
||
status: "degraded",
|
||
ready: false,
|
||
runtime: {
|
||
adapter: "postgres",
|
||
durable: false,
|
||
durableRequested: true,
|
||
ready: false,
|
||
liveRuntimeEvidence: false,
|
||
status: "blocked",
|
||
blocker: reasonCode,
|
||
connection: { queryResult },
|
||
durabilityContract: { blockedLayer },
|
||
gates: {
|
||
[blockedLayer]: {
|
||
checked: true,
|
||
ready: false,
|
||
status: "blocked",
|
||
blocker: reasonCode
|
||
}
|
||
}
|
||
},
|
||
readiness: {
|
||
ready: false,
|
||
status: "blocked",
|
||
durability: {
|
||
ready: false,
|
||
status: "blocked",
|
||
blocker: reasonCode,
|
||
blockedLayer,
|
||
queryResult
|
||
}
|
||
},
|
||
blockerCodes: [reasonCode]
|
||
}
|
||
},
|
||
restIndex: okRest,
|
||
health: okApi,
|
||
adapter: okApi,
|
||
m3Control: okM3Control,
|
||
m3Status: okM3Status
|
||
});
|
||
|
||
assert.equal(status.kind, "readonly");
|
||
assert.equal(status.reasonCode, reasonCode);
|
||
assert.match(status.detail, new RegExp(reasonCode, "u"));
|
||
assert.match(status.detail, new RegExp(queryResult, "u"));
|
||
}
|
||
});
|
||
|
||
test("classifies missing probes as unverified instead of degraded", () => {
|
||
const status = classifyWorkbenchLiveStatus({});
|
||
|
||
assert.equal(status.kind, "unverified");
|
||
assert.equal(status.label, "等待验证");
|
||
assert.match(status.detail, /等待验证/u);
|
||
assert.doesNotMatch(`${status.label} ${status.detail}`, /降级/u);
|
||
});
|
||
|
||
test("classifies memory or non-durable runtime as read-only with explicit reason", () => {
|
||
const status = classifyWorkbenchLiveStatus({
|
||
healthLive: okApi,
|
||
restIndex: okRest,
|
||
health: {
|
||
ok: true,
|
||
status: 200,
|
||
data: {
|
||
serviceId: "hwlab-cloud-api",
|
||
status: "degraded",
|
||
runtime: {
|
||
adapter: "memory",
|
||
durable: false
|
||
}
|
||
}
|
||
},
|
||
adapter: okApi,
|
||
m3Control: okM3Control,
|
||
m3Status: okM3Status
|
||
});
|
||
|
||
assert.equal(status.kind, "readonly");
|
||
assert.equal(status.label, "只读模式");
|
||
assert.equal(status.reasonCode, "runtime_durable_false");
|
||
assert.match(status.detail, /runtime durable=false/u);
|
||
});
|
||
|
||
test("classifies raw degraded without durable/read-only reason as API error", () => {
|
||
const status = classifyWorkbenchLiveStatus({
|
||
healthLive: {
|
||
ok: true,
|
||
status: 200,
|
||
data: {
|
||
serviceId: "hwlab-cloud-api",
|
||
status: "degraded",
|
||
ready: false,
|
||
traceId: "trc_degraded_without_attribution"
|
||
}
|
||
},
|
||
restIndex: okRest,
|
||
health: okApi,
|
||
adapter: okApi,
|
||
m3Control: okM3Control,
|
||
m3Status: okM3Status
|
||
});
|
||
|
||
assert.equal(status.kind, "error");
|
||
assert.equal(status.label, "API 错误");
|
||
assert.equal(status.reasonCode, "ready_false_without_blocker");
|
||
assert.match(status.detail, /API readiness is not pass and no concrete blocker was provided/u);
|
||
assert.match(status.detail, /trace=trc_degraded_without_attribution/u);
|
||
assert.equal(status.internalRawStatuses.includes("degraded"), true);
|
||
assert.doesNotMatch(`${status.label} ${status.detail}`, /API 降级/u);
|
||
assert.doesNotMatch(`${status.label} ${status.detail}`, /API returned degraded/u);
|
||
});
|
||
|
||
test("classifies Code Agent provider timeout or fallback with concrete attribution", () => {
|
||
const providerTimeout = classifyWorkbenchLiveStatus({
|
||
healthLive: okApi,
|
||
restIndex: {
|
||
ok: true,
|
||
status: 200,
|
||
data: {
|
||
serviceId: "hwlab-cloud-api",
|
||
status: "ok",
|
||
ready: true,
|
||
codeAgent: {
|
||
endpoint: "POST /v1/agent/chat",
|
||
status: "blocked",
|
||
ready: false,
|
||
provider: "openai-responses",
|
||
backend: "hwlab-cloud-api/openai-responses",
|
||
traceId: "trc_provider_timeout",
|
||
error: {
|
||
providerStatus: 504
|
||
},
|
||
summary: "/v1/agent/chat timeout"
|
||
}
|
||
}
|
||
},
|
||
health: okApi,
|
||
adapter: okApi,
|
||
m3Control: okM3Control,
|
||
m3Status: okM3Status
|
||
});
|
||
assert.equal(providerTimeout.kind, "error");
|
||
assert.equal(providerTimeout.apiPath, "POST /v1/agent/chat");
|
||
assert.equal(providerTimeout.reasonCode, "provider_http_504");
|
||
assert.match(providerTimeout.detail, /hwlab-cloud-api POST \/v1\/agent\/chat/u);
|
||
assert.match(providerTimeout.detail, /trace=trc_provider_timeout/u);
|
||
|
||
const fallback = classifyWorkbenchLiveStatus({
|
||
healthLive: okApi,
|
||
restIndex: {
|
||
ok: true,
|
||
status: 200,
|
||
data: {
|
||
serviceId: "hwlab-cloud-api",
|
||
status: "ok",
|
||
ready: true,
|
||
codeAgent: {
|
||
endpoint: "POST /v1/agent/chat",
|
||
status: "available",
|
||
ready: true,
|
||
provider: "openai-responses",
|
||
backend: "hwlab-cloud-api/openai-responses",
|
||
capabilityLevel: "text-chat-only",
|
||
runner: {
|
||
kind: "openai-responses-fallback"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
health: okApi,
|
||
adapter: okApi,
|
||
m3Control: okM3Control,
|
||
m3Status: okM3Status
|
||
});
|
||
assert.equal(fallback.kind, "readonly");
|
||
assert.equal(fallback.label, "只读模式");
|
||
assert.equal(fallback.reasonCode, "fallback_text_chat_only");
|
||
assert.match(fallback.detail, /capability=text-chat-only/u);
|
||
});
|
||
|
||
test("keeps ready durable runtime green while attributing read-only mode to Code Agent session-tools", () => {
|
||
const status = classifyWorkbenchLiveStatus({
|
||
healthLive: {
|
||
ok: true,
|
||
status: 200,
|
||
data: {
|
||
serviceId: "hwlab-cloud-api",
|
||
status: "ok",
|
||
ready: true,
|
||
runtime: {
|
||
status: "ready",
|
||
ready: true,
|
||
durable: true,
|
||
liveRuntimeEvidence: true,
|
||
blocker: null,
|
||
connection: {
|
||
queryResult: "durable_readiness_ready"
|
||
}
|
||
},
|
||
readiness: {
|
||
status: "ready",
|
||
ready: true,
|
||
durability: {
|
||
status: "ready",
|
||
ready: true,
|
||
runtimeStatus: "ready",
|
||
blockedLayer: null,
|
||
blocker: null,
|
||
queryResult: "durable_readiness_ready"
|
||
}
|
||
},
|
||
codeAgent: {
|
||
endpoint: "POST /v1/agent/chat",
|
||
status: "available",
|
||
ready: true,
|
||
provider: "codex-readonly-runner",
|
||
backend: "hwlab-cloud-api/controlled-readonly-session-registry",
|
||
capabilityLevel: "read-only-session-tools",
|
||
trackingIssue: "#317",
|
||
runnerLimitations: ["not-codex-stdio", "not-write-capable", "process-local-session-registry"]
|
||
}
|
||
}
|
||
},
|
||
restIndex: {
|
||
ok: true,
|
||
status: 200,
|
||
data: {
|
||
serviceId: "hwlab-cloud-api",
|
||
status: "ok",
|
||
ready: true,
|
||
codeAgent: {
|
||
endpoint: "POST /v1/agent/chat",
|
||
status: "available",
|
||
ready: true,
|
||
provider: "codex-readonly-runner",
|
||
backend: "hwlab-cloud-api/controlled-readonly-session-registry",
|
||
capabilityLevel: "read-only-session-tools",
|
||
trackingIssue: "#317",
|
||
runnerLimitations: ["not-codex-stdio", "not-write-capable", "process-local-session-registry"]
|
||
}
|
||
}
|
||
},
|
||
health: okApi,
|
||
adapter: okApi,
|
||
m3Control: okM3Control,
|
||
m3Status: okM3Status
|
||
});
|
||
|
||
assert.equal(status.kind, "readonly");
|
||
assert.equal(status.label, "只读模式");
|
||
assert.equal(status.serviceId, "hwlab-cloud-api");
|
||
assert.equal(status.apiPath, "POST /v1/agent/chat");
|
||
assert.equal(status.reasonCode, "code_agent_read_only_session_tools");
|
||
assert.match(status.detail, /capability=read-only-session-tools/u);
|
||
assert.match(status.detail, /#317 full pass not complete/u);
|
||
assert.doesNotMatch(status.detail, /runtime durable blocked/u);
|
||
assert.doesNotMatch(status.detail, /blocked at durability/u);
|
||
const healthLiveProbe = status.probes.find((probe) => probe.apiPath === "/health/live");
|
||
assert.equal(healthLiveProbe.kind, "pass");
|
||
assert.doesNotMatch(healthLiveProbe.reason, /blocked/u);
|
||
});
|
||
|
||
test("classifies Codex stdio session facts as pass even when unrelated runtime durability is blocked", () => {
|
||
const status = classifyWorkbenchLiveStatus({
|
||
healthLive: {
|
||
ok: true,
|
||
status: 200,
|
||
data: {
|
||
serviceId: "hwlab-cloud-api",
|
||
status: "degraded",
|
||
ready: false,
|
||
readiness: {
|
||
status: "degraded",
|
||
ready: false,
|
||
durability: {
|
||
status: "blocked",
|
||
ready: false,
|
||
blocker: "runtime_durable_adapter_query_blocked"
|
||
},
|
||
codeAgent: {
|
||
status: "blocked",
|
||
ready: false,
|
||
providerReady: true,
|
||
durableDbReady: false,
|
||
sessionRunnerReady: true,
|
||
codexStdioFeasible: true,
|
||
currentBlocker: "runtime_durable_adapter_query_blocked",
|
||
currentBlockers: ["runtime_durable_adapter_query_blocked"],
|
||
provider: {
|
||
status: "ready",
|
||
ready: true,
|
||
provider: "codex-stdio",
|
||
backend: "hwlab-cloud-api/codex-mcp-stdio",
|
||
mode: "codex-stdio"
|
||
},
|
||
sessionRunner: {
|
||
status: "codex_stdio_ready",
|
||
ready: true,
|
||
kind: "codex-mcp-stdio-runner",
|
||
mode: "codex-mcp-stdio-long-lived",
|
||
capabilityLevel: "long-lived-codex-stdio-session",
|
||
longLivedSession: true,
|
||
durableSession: true,
|
||
codexStdio: true,
|
||
writeCapable: true,
|
||
blocker: null
|
||
},
|
||
codexStdio: {
|
||
status: "ready",
|
||
ready: true,
|
||
feasible: true,
|
||
blocker: null,
|
||
blockerCodes: []
|
||
},
|
||
secretMaterialRead: false,
|
||
valuesRedacted: true
|
||
}
|
||
},
|
||
codeAgent: {
|
||
endpoint: "POST /v1/agent/chat",
|
||
status: "codex-stdio-feasible",
|
||
ready: true,
|
||
provider: "codex-stdio",
|
||
backend: "hwlab-cloud-api/codex-mcp-stdio",
|
||
capabilityLevel: "long-lived-codex-stdio-session",
|
||
sessionMode: "codex-mcp-stdio-long-lived",
|
||
workspace: "/workspace/hwlab",
|
||
sandbox: "workspace-write",
|
||
runner: {
|
||
kind: "codex-mcp-stdio-runner",
|
||
sessionMode: "codex-mcp-stdio-long-lived",
|
||
codexStdio: true,
|
||
writeCapable: true,
|
||
durableSession: true
|
||
},
|
||
runnerLimitations: [
|
||
"hardware-control-via-cloud-api-only",
|
||
"no-direct-gateway-link",
|
||
"no-direct-box-simu-link",
|
||
"no-direct-patch-panel-link",
|
||
"secret-values-redacted"
|
||
],
|
||
longLivedSessionGate: {
|
||
status: "pass",
|
||
pass: true
|
||
},
|
||
codexStdioFeasibility: {
|
||
ready: true,
|
||
canStartLongLivedCodexStdio: true,
|
||
commandProbe: { ready: true },
|
||
blockerCodes: []
|
||
}
|
||
},
|
||
runtime: {
|
||
status: "degraded",
|
||
durable: true,
|
||
ready: false,
|
||
blocker: "runtime_durable_adapter_query_blocked"
|
||
}
|
||
}
|
||
},
|
||
restIndex: okRest,
|
||
health: okApi,
|
||
adapter: okApi,
|
||
m3Control: okM3Control,
|
||
m3Status: okM3Status
|
||
});
|
||
|
||
const codeAgentProbe = status.probes.find((probe) => probe.apiPath === "POST /v1/agent/chat");
|
||
assert.equal(codeAgentProbe.kind, "pass");
|
||
assert.equal(codeAgentProbe.reasonCode, "codex_stdio_ready");
|
||
assert.match(codeAgentProbe.reason, /capability=long-lived-codex-stdio-session/u);
|
||
assert.doesNotMatch(codeAgentProbe.reason, /not-codex-stdio|not-write-capable|process-local-session-registry/u);
|
||
assert.notEqual(status.reasonCode, "codex_stdio_blocked_readonly_session_available");
|
||
});
|
||
|
||
test("fixture matrix keeps durable_readiness_ready green and attributes read-only mode to Code Agent", () => {
|
||
const restPayload = {
|
||
serviceId: "hwlab-cloud-api",
|
||
status: "ok",
|
||
ready: true,
|
||
...statusMatrix.runtime.durableReady,
|
||
codeAgent: statusMatrix.codeAgent.readOnlySessionTools
|
||
};
|
||
const durable = classifyRuntimeDurableReadiness(restPayload);
|
||
const status = classifyWorkbenchLiveStatus({
|
||
healthLive: jsonProbe({
|
||
serviceId: "hwlab-cloud-api",
|
||
status: "ok",
|
||
ready: true,
|
||
...statusMatrix.runtime.durableReady
|
||
}),
|
||
restIndex: jsonProbe(restPayload),
|
||
health: okApi,
|
||
adapter: okApi,
|
||
m3Control: jsonProbe(statusMatrix.m3.controlReady),
|
||
m3Status: jsonProbe(statusMatrix.m3.trustedGreenSummary)
|
||
});
|
||
|
||
assert.equal(durable.ready, true);
|
||
assert.equal(durable.evidence.blocker, null);
|
||
assert.equal(durable.evidence.blockedLayer, null);
|
||
assert.equal(durable.evidence.queryResult, "durable_readiness_ready");
|
||
assert.equal(status.kind, "readonly");
|
||
assert.equal(status.reasonCode, "code_agent_read_only_session_tools");
|
||
assert.equal(status.apiPath, "POST /v1/agent/chat");
|
||
assert.match(status.detail, /#317 full pass not complete/u);
|
||
assert.doesNotMatch(status.detail, /runtime durable blocked|blocked at durability/u);
|
||
});
|
||
|
||
test("fixture matrix preserves runtime durable blocked attribution when durability is actually blocked", () => {
|
||
const status = classifyWorkbenchLiveStatus({
|
||
healthLive: jsonProbe(statusMatrix.runtime.durableQueryBlocked),
|
||
restIndex: okRest,
|
||
health: okApi,
|
||
adapter: okApi,
|
||
m3Control: okM3Control,
|
||
m3Status: okM3Status
|
||
});
|
||
const durable = classifyRuntimeDurableReadiness(statusMatrix.runtime.durableQueryBlocked);
|
||
|
||
assert.equal(durable.ready, false);
|
||
assert.equal(durable.reasonCode, "runtime_durable_adapter_query_blocked");
|
||
assert.equal(durable.evidence.blockedLayer, "durability_query");
|
||
assert.equal(status.kind, "readonly");
|
||
assert.equal(status.reasonCode, "runtime_durable_adapter_query_blocked");
|
||
assert.match(status.detail, /runtime_durable_adapter_query_blocked/u);
|
||
assert.match(status.detail, /query_blocked/u);
|
||
});
|
||
|
||
test("fixture matrix keeps auth and schema runtime blockers on their exact layers", () => {
|
||
for (const [name, expectedReason, expectedLayer, expectedQuery] of [
|
||
["authBlocked", "runtime_durable_adapter_auth_blocked", "auth", "auth_blocked"],
|
||
["schemaBlocked", "runtime_durable_adapter_schema_blocked", "schema", "schema_blocked"]
|
||
]) {
|
||
const durable = classifyRuntimeDurableReadiness(statusMatrix.runtime[name]);
|
||
const status = classifyWorkbenchLiveStatus({
|
||
healthLive: jsonProbe(statusMatrix.runtime[name]),
|
||
restIndex: okRest,
|
||
health: okApi,
|
||
adapter: okApi,
|
||
m3Control: okM3Control,
|
||
m3Status: okM3Status
|
||
});
|
||
|
||
assert.equal(durable.reasonCode, expectedReason);
|
||
assert.equal(durable.evidence.blockedLayer, expectedLayer);
|
||
assert.equal(durable.evidence.queryResult, expectedQuery);
|
||
assert.equal(status.kind, "readonly");
|
||
assert.equal(status.reasonCode, expectedReason);
|
||
assert.match(status.detail, new RegExp(expectedReason, "u"));
|
||
}
|
||
});
|
||
|
||
test("fixture matrix treats unknown or missing readiness as unverified, not runtime durable blocked", () => {
|
||
const status = classifyWorkbenchLiveStatus({
|
||
healthLive: jsonProbe(statusMatrix.runtime.unknownMissingReadiness)
|
||
});
|
||
|
||
assert.equal(status.kind, "unverified");
|
||
assert.equal(status.label, "等待验证");
|
||
assert.doesNotMatch(`${status.reasonCode} ${status.detail}`, /runtime durable blocked|blocked at durability/u);
|
||
});
|
||
|
||
test("fixture matrix keeps M3 trusted green summary aligned with live status and wiring presentation", () => {
|
||
const status = classifyWorkbenchLiveStatus({
|
||
healthLive: okApi,
|
||
restIndex: codexStdioRest,
|
||
health: okApi,
|
||
adapter: okApi,
|
||
m3Control: jsonProbe(statusMatrix.m3.controlReady),
|
||
m3Status: jsonProbe(statusMatrix.m3.trustedGreenSummary)
|
||
});
|
||
const wiring = wiringPresentationFromSummary(statusMatrix.m3.trustedGreenSummary);
|
||
const m3Probe = status.probes.find((probe) => probe.apiPath === "/v1/m3/status");
|
||
|
||
assert.equal(status.kind, "pass");
|
||
assert.equal(m3Probe.kind, "pass");
|
||
assert.equal(wiring.m3Live, true);
|
||
assert.equal(wiring.stateLabel, "实况已验证");
|
||
assert.equal(wiring.badgeTone, "dev-live");
|
||
});
|
||
|
||
test("classifies M3 control blockers by cloud-api/gateway/box/patch-panel layer", () => {
|
||
const status = classifyWorkbenchLiveStatus({
|
||
healthLive: okApi,
|
||
restIndex: okRest,
|
||
health: okApi,
|
||
adapter: okApi,
|
||
m3Control: {
|
||
ok: true,
|
||
status: 200,
|
||
data: {
|
||
status: "blocked",
|
||
traceId: "trc_m3_wiring_missing",
|
||
readiness: {
|
||
status: "blocked",
|
||
controlReady: false,
|
||
blocker: {
|
||
code: "m3_wiring_missing",
|
||
layer: "hwlab-patch-panel",
|
||
zh: "hwlab-patch-panel route unavailable"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
m3Status: okM3Status
|
||
});
|
||
|
||
assert.equal(status.kind, "error");
|
||
assert.equal(status.serviceId, "hwlab-patch-panel");
|
||
assert.equal(status.apiPath, "/v1/m3/io");
|
||
assert.equal(status.reasonCode, "m3_wiring_missing");
|
||
assert.match(status.detail, /hwlab-patch-panel \/v1\/m3\/io/u);
|
||
assert.match(status.detail, /trace=trc_m3_wiring_missing/u);
|
||
});
|
||
|
||
test("includes trusted M3 evidence identifiers in probe attribution", () => {
|
||
const status = classifyWorkbenchLiveStatus({
|
||
healthLive: okApi,
|
||
restIndex: okRest,
|
||
health: okApi,
|
||
adapter: okApi,
|
||
m3Control: okM3Control,
|
||
m3Status: {
|
||
ok: true,
|
||
status: 200,
|
||
data: {
|
||
status: "live",
|
||
sourceKind: "DEV-LIVE",
|
||
summary: "M3 硬件状态与可信持久化均为 DEV-LIVE。",
|
||
trust: {
|
||
operationId: "op_m3_live",
|
||
traceId: "trc_m3_live",
|
||
auditId: "aud_m3_live",
|
||
evidenceId: "evd_m3_live"
|
||
}
|
||
}
|
||
}
|
||
});
|
||
|
||
const m3Probe = status.probes.find((probe) => probe.apiPath === "/v1/m3/status");
|
||
assert.equal(status.kind, "pass");
|
||
assert.match(m3Probe?.evidenceSummary ?? "", /operation=op_m3_live/u);
|
||
assert.match(m3Probe?.evidenceSummary ?? "", /audit=aud_m3_live/u);
|
||
assert.match(m3Probe?.evidenceSummary ?? "", /evidence=evd_m3_live/u);
|
||
});
|
||
|
||
function jsonProbe(data, status = 200) {
|
||
return {
|
||
ok: true,
|
||
status,
|
||
data
|
||
};
|
||
}
|