Merge PR #229: show blocked workbench readiness

Host commander review: small frontend/smoke hardening for the deployed user workbench. Root workbench now derives status from health/live, /v1, RPC readiness, and runtime durability, showing user-facing read-only blocked copy when durable runtime is not ready while keeping raw blocker/Gate/internal wording out of default status. Runner validation was source/local only; no live deploy, restart, Secret access, or M3/M4/M5 acceptance claim.
This commit is contained in:
Lyon
2026-05-23 10:36:03 +08:00
committed by GitHub
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;
}