import { CLOUD_API_SERVICE_ID } from "../audit/index.mjs"; import { describeCodeAgentAvailability } from "./code-agent-chat.ts"; import { applyRuntimeDbReadinessLayers, buildDbRuntimeReadiness } from "./db-contract.ts"; import { buildCloudApiReadiness } from "./health-contract.ts"; import { M3_IO_CONTROL_ROUTE, describeM3IoControlLive } from "./m3-io-control.ts"; const STATUS = Object.freeze({ pass: "通过", blocked: "阻塞", failed: "失败", pending: "待验证", info: "信息" }); export async function buildGateDiagnosticsRows(options = {}) { const observedAt = new Date().toISOString(); const rows = []; const health = await observeHealth(options, observedAt); const m3 = await observeM3(options, observedAt); const audit = await observeAudit(options, observedAt); const evidence = await observeEvidence(options, observedAt); rows.push(...health.rows, m3.row, audit.row, evidence.row); const status = rows.some((row) => row.statusKey === "failed") ? "failed" : rows.some((row) => row.statusKey === "blocked") ? "blocked" : rows.some((row) => row.statusKey === "pending") ? "pending" : "pass"; return { serviceId: CLOUD_API_SERVICE_ID, route: "/v1/diagnostics/gate", contractVersion: "gate-diagnostics-table-v1", status, sourceKind: "LIVE-BACKEND", liveBackend: true, observedAt, rowCount: rows.length, columns: ["类别", "检查项", "状态", "归因对象", "关键细节", "证据/trace", "更新时间", "下一步"], rows, safety: { frontendHardcodedRows: false, sourceFixturePromoted: false, localStorageUsed: false, blockedIsGreen: false, hardwareControlSurface: false, codeAgentConversationSurface: false } }; } async function observeHealth(options, observedAt) { try { const env = options.env ?? process.env; const dbProbe = await buildDbRuntimeReadiness(env, options.dbProbe); const runtime = await runtimeReadiness(options.runtimeStore); const db = applyRuntimeDbReadinessLayers(dbProbe, runtime); const codeAgent = await describeCodeAgentAvailability(env, options); const readiness = buildCloudApiReadiness({ db, codeAgent, runtime }); const commitId = env.HWLAB_COMMIT_ID || env.HWLAB_GIT_SHA || "unknown"; const imageTag = env.HWLAB_IMAGE_TAG || shortHash(commitId); return { rows: [ row({ category: "部署", check: "Cloud API 实况身份", statusKey: commitId === "unknown" ? "info" : "pass", owner: CLOUD_API_SERVICE_ID, detail: `service=${CLOUD_API_SERVICE_ID}; env=dev; status=${readiness.status}; imageTag=${imageTag}`, evidence: [`commit=${commitId}`, "GET /health/live"], updatedAt: observedAt, next: readiness.ready ? "继续保持与 DEV desired state 对齐。" : "先处理下方阻塞行,再重新执行 DEV live 复核。" }), row({ category: "健康检查", check: "/health/live readiness", statusKey: readiness.ready ? "pass" : "blocked", owner: CLOUD_API_SERVICE_ID, detail: `ready=${readiness.ready}; status=${readiness.status}; blockers=${readiness.blockerCodes.join(",") || "无"}`, evidence: readiness.blockerCodes.length > 0 ? readiness.blockerCodes.map((code) => `blocker=${code}`) : ["GET /health/live"], updatedAt: observedAt, next: readiness.ready ? "无额外动作。" : "按 blocker code 分别处理 DB、runtime、Code Agent 或 M3 依赖。" }), row({ category: "DB", check: "DB live readiness", statusKey: db?.ready && db?.connected && db?.liveDbEvidence ? "pass" : "blocked", owner: "hwlab-cloud-api / DEV Postgres", detail: `ready=${Boolean(db?.ready)}; connected=${Boolean(db?.connected)}; liveDbEvidence=${Boolean(db?.liveDbEvidence)}; endpointSource=${db?.endpointSource ?? "unknown"}`, evidence: db?.blocker ? [`blocker=${db.blocker}`] : ["db.liveDbEvidence=true"], updatedAt: observedAt, next: db?.ready && db?.connected && db?.liveDbEvidence ? "继续保持 Secret URL host 与 live DB 证据分层。" : "修复 DEV DB env/连接/auth,且不打印 Secret 值。" }), row({ category: "运行态", check: "runtime durable adapter", statusKey: runtime?.durable === true && runtime?.ready === true && runtime?.liveRuntimeEvidence === true ? "pass" : "blocked", owner: "hwlab-cloud-api runtime-store", detail: `adapter=${runtime?.adapter ?? "unknown"}; durable=${Boolean(runtime?.durable)}; ready=${Boolean(runtime?.ready)}; liveRuntimeEvidence=${Boolean(runtime?.liveRuntimeEvidence)}`, evidence: [ runtime?.blocker ? `blocker=${runtime.blocker}` : null, runtime?.durabilityContract?.blockedLayer ? `blockedLayer=${runtime.durabilityContract.blockedLayer}` : null, runtime?.connection?.queryResult ? `queryResult=${runtime.connection.queryResult}` : null ].filter(Boolean), updatedAt: observedAt, next: runtime?.durable === true && runtime?.ready === true && runtime?.liveRuntimeEvidence === true ? "可作为可信记录持久化前置。" : "修复 runtime durable auth/schema/migration/readiness 后再升级可信闭环。" }), row({ category: "Code Agent", check: "provider readiness", statusKey: codeAgent?.ready && codeAgent?.status !== "blocked" ? "pass" : "blocked", owner: "hwlab-cloud-api / Code Agent provider", detail: `status=${codeAgent?.status ?? "unknown"}; provider=${codeAgent?.provider ?? "unknown"}; model=${codeAgent?.model ?? "unknown"}; backend=${codeAgent?.backend ?? "unknown"}`, evidence: [ codeAgent?.reason ? `reason=${codeAgent.reason}` : null, ...(Array.isArray(codeAgent?.missingEnv) ? codeAgent.missingEnv.map((name) => `missingEnv=${name}`) : []), "Secret 值未读取/未展示" ].filter(Boolean), updatedAt: observedAt, next: codeAgent?.ready && codeAgent?.status !== "blocked" ? "继续用受控 /v1/agent/chat 验证真实 runner 能力。" : "补齐 provider env/Secret/egress 后复测,不用 mock 回复冒充。" }) ] }; } catch (error) { return { rows: [ row({ category: "健康检查", check: "后端聚合读取", statusKey: "failed", owner: CLOUD_API_SERVICE_ID, detail: `live 聚合失败:${oneLine(error.message)}`, evidence: "GET /v1/diagnostics/gate", updatedAt: observedAt, next: "先修复 cloud-api 聚合接口,再重新刷新内部复核表。" }) ] }; } } async function observeM3(options, observedAt) { try { const payload = await describeM3IoControlLive(options); const ready = payload?.readiness?.status === "ready" && payload?.readiness?.controlReady === true; return { row: row({ category: "M3", check: "DO1 经 hwlab-patch-panel 同步到 DI1", statusKey: ready ? "pass" : payload?.readiness?.status === "pending" ? "pending" : "blocked", owner: "hwlab-cloud-api / hwlab-patch-panel", detail: payload?.readiness?.summary ?? payload?.blockedReason ?? "M3 IO readiness 未返回摘要。", evidence: [ `route=${M3_IO_CONTROL_ROUTE}`, payload?.readiness?.sourceKind ? `sourceKind=${payload.readiness.sourceKind}` : null, payload?.readiness?.blocker?.code ? `blocker=${payload.readiness.blocker.code}` : null, payload?.readiness?.blocker?.layer ? `layer=${payload.readiness.blocker.layer}` : null ].filter(Boolean), updatedAt: payload?.readiness?.observedAt ?? observedAt, next: ready ? "只表示控制路径可执行;M3 验收仍需 operation/trace/audit/evidence 闭环。" : payload?.readiness?.blocker?.zh ?? "修复 gateway-simu、box-simu 或 patch-panel 只读 readiness 后再复核。" }) }; } catch (error) { return { row: row({ category: "M3", check: "DO1 经 hwlab-patch-panel 同步到 DI1", statusKey: "failed", owner: "hwlab-cloud-api / hwlab-patch-panel", detail: `M3 live readiness 读取失败:${oneLine(error.message)}`, evidence: `GET ${M3_IO_CONTROL_ROUTE}`, updatedAt: observedAt, next: "先恢复 M3 IO readiness 接口,再验证 patch-panel 目标链路。" }) }; } } async function observeAudit(options, observedAt) { try { const result = await options.runtimeStore?.queryAuditEvents?.({ limit: 6 }); const events = Array.isArray(result?.events) ? result.events : []; return { row: row({ category: "证据", check: "audit.event.query", statusKey: events.length > 0 ? "info" : "pending", owner: "runtime-store / audit", detail: `只读查询返回 ${events.length} 条 audit;该行不单独证明 M3 通过。`, evidence: events.slice(0, 3).map((event) => [ event.auditId ? `audit=${event.auditId}` : null, event.traceId ? `trace=${event.traceId}` : null ].filter(Boolean).join(" / ")).filter(Boolean), updatedAt: observedAt, next: events.length > 0 ? "将 audit 与 operation/evidence 绑定后再判断可信闭环。" : "等待 live audit 记录,或先修复 runtime durable blocker。" }) }; } catch (error) { return { row: row({ category: "证据", check: "audit.event.query", statusKey: "blocked", owner: "runtime-store / audit", detail: `只读 audit 查询受阻:${oneLine(error.message)}`, evidence: "POST /json-rpc audit.event.query", updatedAt: observedAt, next: "修复 runtime durable/readiness 后再查询 audit,不用 SOURCE 回退冒充。" }) }; } } async function observeEvidence(options, observedAt) { try { const result = await options.runtimeStore?.queryEvidenceRecords?.({ limit: 6 }); const records = Array.isArray(result?.records) ? result.records : []; return { row: row({ category: "证据", check: "evidence.record.query", statusKey: records.length > 0 ? "info" : "pending", owner: "runtime-store / evidence", detail: `只读查询返回 ${records.length} 条 evidence;该行不单独证明 M3 通过。`, evidence: records.slice(0, 3).map((record) => [ record.evidenceId ? `evidence=${record.evidenceId}` : null, record.traceId ?? record.metadata?.traceId ? `trace=${record.traceId ?? record.metadata?.traceId}` : null ].filter(Boolean).join(" / ")).filter(Boolean), updatedAt: observedAt, next: records.length > 0 ? "将 evidence 与 operation/audit/patch-panel live report 绑定后再判断。" : "等待 live evidence 记录,或先修复 runtime durable blocker。" }) }; } catch (error) { return { row: row({ category: "证据", check: "evidence.record.query", statusKey: "blocked", owner: "runtime-store / evidence", detail: `只读 evidence 查询受阻:${oneLine(error.message)}`, evidence: "POST /json-rpc evidence.record.query", updatedAt: observedAt, next: "修复 runtime durable/readiness 后再查询 evidence,不用 SOURCE 回退冒充。" }) }; } } async function runtimeReadiness(runtimeStore) { if (typeof runtimeStore?.readiness === "function") { return runtimeStore.readiness(); } if (typeof runtimeStore?.summary === "function") { return runtimeStore.summary(); } return { adapter: "unknown", durable: false, ready: false, liveRuntimeEvidence: false, blocker: "runtime_store_unavailable" }; } function row({ category, check, statusKey, owner, detail, evidence, updatedAt, next }) { const normalizedStatusKey = normalizeStatusKey(statusKey); return { category, check, status: STATUS[normalizedStatusKey], statusKey: normalizedStatusKey, owner, detail: oneLine(detail), evidence: normalizeEvidence(evidence), updatedAt, next: oneLine(next), sourceKind: "LIVE-BACKEND" }; } function normalizeStatusKey(statusKey) { const normalized = String(statusKey ?? "").trim().toLowerCase(); if (normalized === "pass" || normalized === "passed" || normalized === "ok" || normalized === "ready") return "pass"; if (normalized === "failed" || normalized === "failure" || normalized === "error") return "failed"; if (normalized === "pending" || normalized === "unknown" || normalized === "loading") return "pending"; if (normalized === "info" || normalized === "information") return "info"; return "blocked"; } function normalizeEvidence(value) { const items = Array.isArray(value) ? value : [value]; const normalized = items.map(oneLine).filter(Boolean); return normalized.length > 0 ? normalized : ["live 后端未返回可用 evidence 字段"]; } function oneLine(value) { return String(value ?? "").replace(/\s+/gu, " ").trim().slice(0, 360); } function shortHash(value) { const text = String(value ?? "unknown"); return text === "unknown" ? "unknown" : text.slice(0, 7); }