fix: show blocked workbench readiness

This commit is contained in:
Code Queue Review
2026-05-23 02:29:16 +00:00
parent 0a7311c3c2
commit 0492db35b8
3 changed files with 124 additions and 15 deletions
+41 -8
View File
@@ -295,7 +295,7 @@ function runStaticSmoke() {
addCheck(checks, blockers, "api-degraded-default-status", hasApiDegradedDefaultStatus(files), "Default workbench status distinguishes reachable degraded API from fully OK without exposing backend blocker details.", {
blocker: "observability_blocker",
evidence: ["healthLive.data.status === degraded", "API 降级 / 只读模式", "no Gate/diagnostics/blocker copy in default status classifier"]
evidence: ["healthLive.data.status === degraded", "ready false", "runtime_durable_adapter_query_blocked", "API 降级 / 只读模式", "可信记录受阻 / 只读模式", "no Gate/diagnostics/blocker copy in default status classifier"]
});
addCheck(checks, blockers, "code-agent-chat-primary-flow", hasCodeAgentChatContract(files), "Center Agent conversation sends to the controlled Code Agent chat endpoint and no longer uses 添加草稿 as the primary flow.", {
@@ -1090,13 +1090,27 @@ function hasSameOriginReadOnlyBoundary(app) {
function hasApiDegradedDefaultStatus({ app }) {
const statusBody = functionBody(app, "workbenchApiSurfaceStatus");
const readinessBody = functionBody(app, "workbenchSurfaceReadiness");
const durableBlockBody = functionBody(app, "hasDurableRuntimeQueryBlock");
return (
/function\s+workbenchApiSurfaceStatus\s*\(/u.test(app) &&
/live\.healthLive\?\.data\?\.status/u.test(statusBody) &&
/healthLiveStatus === "degraded"/u.test(statusBody) &&
/function\s+workbenchSurfaceReadiness\s*\(/u.test(app) &&
/function\s+hasDurableRuntimeQueryBlock\s*\(/u.test(app) &&
/surface\.durableQueryBlocked/u.test(statusBody) &&
/surface\.degraded/u.test(statusBody) &&
/function\s+readinessBooleanValues\s*\(/u.test(app) &&
/payload\.ready/u.test(functionBody(app, "readinessBooleanValues")) &&
/payload\.readiness\?\.ready/u.test(functionBody(app, "readinessBooleanValues")) &&
/readyValues\.includes\(false\)/u.test(readinessBody) &&
/runtime_durable_adapter_query_blocked/u.test(durableBlockBody) &&
/payload\.runtime\?\.blocker/u.test(durableBlockBody) &&
/payload\.readiness\?\.durability\?\.blocker/u.test(durableBlockBody) &&
/blockedLayer === "durability_query"/u.test(durableBlockBody) &&
/可信记录受阻 \/ 只读模式/u.test(statusBody) &&
/API 降级 \/ 只读模式/u.test(statusBody) &&
/同源 API 可响应但处于降级状态;当前保持只读查看和本地任务整理。/u.test(statusBody) &&
!/blocker|DB live readiness|Gate|诊断|验收|M0-M5|执行轨迹/u.test(statusBody)
/同源 API 可响应,但可信记录持久化尚未就绪;当前只保留只读查看和本地任务整理。/u.test(statusBody) &&
!/runtime_durable_adapter_query_blocked|DB live readiness|Gate|诊断|验收|M0-M5|执行轨迹/u.test(statusBody)
);
}
@@ -1770,8 +1784,8 @@ async function inspectDefaultApiStatus(page) {
methodText,
firstScreenText,
degradedStatusVisible:
liveStatus === "API 降级 / 只读模式" &&
liveDetail === "同源 API 可响应但处于降级状态;当前保持只读查看和本地任务整理。" &&
liveStatus === "可信记录受阻 / 只读模式" &&
liveDetail === "同源 API 可响应,但可信记录持久化尚未就绪;当前只保留只读查看和本地任务整理。" &&
methodText.includes("system.health"),
defaultCopyIsWorkbenchSafe: !forbidden.test(`${liveStatus} ${liveDetail} ${firstScreenText}`)
};
@@ -1841,7 +1855,7 @@ export async function runDevCloudWorkbenchLocalAgentFixtureSmoke() {
await page.goto(server.url, { waitUntil: "networkidle", timeout: 15000 });
await page.locator("#command-input").waitFor({ state: "visible", timeout: 12000 });
await page.waitForFunction(
() => document.querySelector("#live-status")?.textContent?.trim() === "API 降级 / 只读模式",
() => document.querySelector("#live-status")?.textContent?.trim() === "可信记录受阻 / 只读模式",
null,
{ timeout: 8000 }
);
@@ -2143,17 +2157,36 @@ async function handleLocalAgentFixtureApi({ request, response, url }) {
jsonResponse(response, 200, {
serviceId: "hwlab-cloud-api",
status: "degraded",
ready: false,
sourceKind: "SOURCE",
codeAgent: localSourceFixtureAvailability(),
methods: [...readOnlyRpcMethods],
runtime: {
durable: false,
ready: false,
status: "degraded",
blocker: "runtime_durable_adapter_query_blocked",
connection: {
queryAttempted: true,
queryResult: "query_blocked"
},
counts: {
gatewaySessions: gateSummary.gatewaySessionCount,
boxResources: gateSummary.boxResourceCount
}
}
},
readiness: {
status: "degraded",
ready: false,
durability: {
status: "blocked",
ready: false,
blocker: "runtime_durable_adapter_query_blocked",
blockedLayer: "durability_query",
queryResult: "query_blocked"
}
},
blockerCodes: ["runtime_durable_adapter_query_blocked"]
});
return true;
}
+68 -4
View File
@@ -528,8 +528,8 @@ function renderLiveSurface(live) {
}
function workbenchApiSurfaceStatus(live, coreProbes = [live.healthLive, live.restIndex, live.health, live.adapter]) {
const reachable = coreProbes.some((probe) => probe.ok);
if (!reachable) {
const surface = workbenchSurfaceReadiness(live, coreProbes);
if (!surface.reachable) {
return {
label: "离线查看",
tone: "source",
@@ -538,8 +538,16 @@ function workbenchApiSurfaceStatus(live, coreProbes = [live.healthLive, live.res
};
}
const healthLiveStatus = String(live.healthLive?.data?.status ?? "").trim().toLowerCase();
if (live.healthLive?.ok && healthLiveStatus === "degraded") {
if (surface.durableQueryBlocked) {
return {
label: "可信记录受阻 / 只读模式",
tone: "blocked",
methodTone: "degraded",
detail: "同源 API 可响应,但可信记录持久化尚未就绪;当前只保留只读查看和本地任务整理。"
};
}
if (surface.degraded) {
return {
label: "API 降级 / 只读模式",
tone: "degraded",
@@ -556,6 +564,62 @@ function workbenchApiSurfaceStatus(live, coreProbes = [live.healthLive, live.res
};
}
function workbenchSurfaceReadiness(live, coreProbes = [live.healthLive, live.restIndex, live.health, live.adapter]) {
const payloads = coreProbes
.filter((probe) => probe?.ok)
.map((probe) => probe.data)
.filter((payload) => payload && typeof payload === "object");
const statuses = payloads.flatMap(readinessStatusValues).map(normalizedStatusValue);
const readyValues = payloads.flatMap(readinessBooleanValues);
return {
reachable: payloads.length > 0,
degraded:
statuses.some((status) => ["blocked", "degraded", "failed"].includes(status)) ||
readyValues.includes(false),
durableQueryBlocked: payloads.some(hasDurableRuntimeQueryBlock)
};
}
function readinessStatusValues(payload) {
return [
payload.status,
payload.readiness?.status,
payload.readiness?.durability?.status,
payload.runtime?.status
].filter((value) => value !== undefined && value !== null);
}
function readinessBooleanValues(payload) {
return [
payload.ready,
payload.readiness?.ready,
payload.readiness?.durability?.ready,
payload.runtime?.ready
].filter((value) => typeof value === "boolean");
}
function hasDurableRuntimeQueryBlock(payload) {
const codes = [
...(Array.isArray(payload.blockerCodes) ? payload.blockerCodes : []),
...(Array.isArray(payload.readiness?.blockerCodes) ? payload.readiness.blockerCodes : []),
...(Array.isArray(payload.blockers) ? payload.blockers.map((item) => item?.code) : []),
...(Array.isArray(payload.readiness?.blockers) ? payload.readiness.blockers.map((item) => item?.code) : []),
payload.runtime?.blocker,
payload.readiness?.durability?.blocker
].map(normalizedStatusValue);
const queryResult = normalizedStatusValue(payload.runtime?.connection?.queryResult ?? payload.readiness?.durability?.queryResult);
const blockedLayer = normalizedStatusValue(payload.runtime?.durabilityContract?.blockedLayer ?? payload.readiness?.durability?.blockedLayer);
return (
codes.includes("runtime_durable_adapter_query_blocked") ||
queryResult === "query_blocked" ||
blockedLayer === "durability_query"
);
}
function normalizedStatusValue(value) {
return String(value ?? "").trim().toLowerCase();
}
function renderResourceTree() {
const items = [
{
+15 -3
View File
@@ -287,11 +287,23 @@ assert.match(markedLicense, /MarkedJS/);
assert.match(markedLicense, /Permission is hereby granted, free of charge/);
assert.match(html, /live-status/);
assert.match(app, /function workbenchApiSurfaceStatus/);
assert.match(functionBody(app, "workbenchApiSurfaceStatus"), /live\.healthLive\?\.data\?\.status/);
assert.match(functionBody(app, "workbenchApiSurfaceStatus"), /healthLiveStatus === "degraded"/);
assert.match(app, /function workbenchSurfaceReadiness/);
assert.match(app, /function hasDurableRuntimeQueryBlock/);
assert.match(functionBody(app, "workbenchApiSurfaceStatus"), /surface\.durableQueryBlocked/);
assert.match(functionBody(app, "workbenchApiSurfaceStatus"), /surface\.degraded/);
assert.match(app, /function readinessBooleanValues/);
assert.match(functionBody(app, "readinessBooleanValues"), /payload\.ready/);
assert.match(functionBody(app, "readinessBooleanValues"), /payload\.readiness\?\.ready/);
assert.match(functionBody(app, "workbenchSurfaceReadiness"), /readyValues\.includes\(false\)/);
assert.match(functionBody(app, "hasDurableRuntimeQueryBlock"), /runtime_durable_adapter_query_blocked/);
assert.match(functionBody(app, "hasDurableRuntimeQueryBlock"), /payload\.runtime\?\.blocker/);
assert.match(functionBody(app, "hasDurableRuntimeQueryBlock"), /payload\.readiness\?\.durability\?\.blocker/);
assert.match(functionBody(app, "hasDurableRuntimeQueryBlock"), /blockedLayer === "durability_query"/);
assert.match(app, /可信记录受阻 \/ 只读模式/);
assert.match(app, /API 降级 \/ 只读模式/);
assert.match(app, /同源 API 可响应但处于降级状态;当前保持只读查看和本地任务整理。/);
assert.doesNotMatch(functionBody(app, "workbenchApiSurfaceStatus"), /blocker|DB live readiness|Gate|诊断|验收|M0-M5|执行轨迹/u);
assert.match(app, /同源 API 可响应,但可信记录持久化尚未就绪;当前只保留只读查看和本地任务整理。/);
assert.doesNotMatch(functionBody(app, "workbenchApiSurfaceStatus"), /runtime_durable_adapter_query_blocked|DB live readiness|Gate|诊断|验收|M0-M5|执行轨迹/u);
assert.match(html, /method-list/);
assert.match(html, /quick-actions/);
assert.match(html, /编写任务/);