663 lines
23 KiB
JavaScript
663 lines
23 KiB
JavaScript
const PASS_STATUSES = Object.freeze(["ok", "ready", "pass", "passed", "healthy", "available", "live"]);
|
|
const ERROR_STATUSES = Object.freeze(["error", "failed", "failure"]);
|
|
const RAW_DEGRADED_STATUSES = Object.freeze(["degraded"]);
|
|
const BLOCKED_STATUSES = Object.freeze(["blocked", "unavailable", "rejected"]);
|
|
const UNVERIFIED_STATUSES = Object.freeze(["", "unknown", "pending", "loading", "probing", "unverified", "not_observed"]);
|
|
const READ_ONLY_REASON_CODES = Object.freeze([
|
|
"read_only",
|
|
"read-only",
|
|
"readonly",
|
|
"permission",
|
|
"permissions",
|
|
"forbidden",
|
|
"unauthorized",
|
|
"runtime_durable",
|
|
"runtime-durable",
|
|
"durability",
|
|
"durable",
|
|
"db_persistence",
|
|
"persistence",
|
|
"not-codex-stdio",
|
|
"not-write-capable",
|
|
"not-durable-session",
|
|
"text-chat-only",
|
|
"fallback:text-chat-only",
|
|
"codex_stdio_blocked_readonly_available",
|
|
"controlled_readonly_not_long_lived_stdio",
|
|
"codex_stdio_supervisor_disabled",
|
|
"security_blocked"
|
|
]);
|
|
|
|
export function classifyWorkbenchLiveStatus(live = {}) {
|
|
const probes = collectLiveStatusProbes(live);
|
|
const reachable = probes.some((probe) => probe.reachable);
|
|
if (!reachable) {
|
|
const failedProbe = probes.find((probe) => !probe.reachable) ?? probes[0];
|
|
return statusResult({
|
|
kind: "unverified",
|
|
label: "等待验证",
|
|
tone: "pending",
|
|
serviceId: failedProbe?.serviceId ?? "hwlab-cloud-api",
|
|
apiPath: failedProbe?.apiPath ?? "/health/live",
|
|
reasonCode: failedProbe?.reasonCode ?? "api_unverified",
|
|
detail: failedProbe
|
|
? `等待验证:${probeAttribution(failedProbe)}。`
|
|
: "等待同源 API 验证;主工作台不会把未探测状态当作通过。",
|
|
probes
|
|
});
|
|
}
|
|
|
|
const errorProbe = probes.find((probe) => probe.kind === "error");
|
|
if (errorProbe) {
|
|
return statusResult({
|
|
kind: "error",
|
|
label: "API 错误",
|
|
tone: "blocked",
|
|
serviceId: errorProbe.serviceId,
|
|
apiPath: errorProbe.apiPath,
|
|
reasonCode: errorProbe.reasonCode,
|
|
traceId: errorProbe.traceId,
|
|
detail: `错误:${probeAttribution(errorProbe)}。`,
|
|
probes
|
|
});
|
|
}
|
|
|
|
const readOnlyProbe = probes.find((probe) => probe.kind === "readonly");
|
|
if (readOnlyProbe) {
|
|
return statusResult({
|
|
kind: "readonly",
|
|
label: "只读模式",
|
|
tone: "source",
|
|
serviceId: readOnlyProbe.serviceId,
|
|
apiPath: readOnlyProbe.apiPath,
|
|
reasonCode: readOnlyProbe.reasonCode,
|
|
traceId: readOnlyProbe.traceId,
|
|
detail: `只读:${probeAttribution(readOnlyProbe)}。`,
|
|
probes
|
|
});
|
|
}
|
|
|
|
const pendingProbe = probes.find((probe) => probe.kind === "unverified");
|
|
if (pendingProbe) {
|
|
return statusResult({
|
|
kind: "unverified",
|
|
label: "等待验证",
|
|
tone: "pending",
|
|
serviceId: pendingProbe.serviceId,
|
|
apiPath: pendingProbe.apiPath,
|
|
reasonCode: pendingProbe.reasonCode,
|
|
traceId: pendingProbe.traceId,
|
|
detail: `等待验证:${probeAttribution(pendingProbe)}。`,
|
|
probes
|
|
});
|
|
}
|
|
|
|
return statusResult({
|
|
kind: "pass",
|
|
label: "API 正常",
|
|
tone: "dev-live",
|
|
serviceId: "hwlab-cloud-api",
|
|
apiPath: "/health/live",
|
|
reasonCode: "ready",
|
|
detail: "通过:hwlab-cloud-api /health/live ready;关键同源 API 已完成当前工作台只读验证。",
|
|
probes
|
|
});
|
|
}
|
|
|
|
export function collectLiveStatusProbes(live = {}) {
|
|
return [
|
|
classifyApiProbe(live.healthLive, {
|
|
serviceId: "hwlab-cloud-api",
|
|
apiPath: "/health/live",
|
|
fallbackReasonCode: "health_live_unverified"
|
|
}),
|
|
classifyApiProbe(live.restIndex, {
|
|
serviceId: "hwlab-cloud-api",
|
|
apiPath: "/v1",
|
|
fallbackReasonCode: "rest_index_unverified"
|
|
}),
|
|
classifyApiProbe(live.health, {
|
|
serviceId: "hwlab-cloud-api",
|
|
apiPath: "/json-rpc system.health",
|
|
fallbackReasonCode: "system_health_unverified"
|
|
}),
|
|
classifyApiProbe(live.adapter, {
|
|
serviceId: "hwlab-cloud-api",
|
|
apiPath: "/json-rpc cloud.adapter.describe",
|
|
fallbackReasonCode: "adapter_describe_unverified"
|
|
}),
|
|
classifyCodeAgentProbe(live),
|
|
classifyM3ControlProbe(live),
|
|
classifyM3StatusProbe(live)
|
|
].filter(Boolean);
|
|
}
|
|
|
|
function classifyApiProbe(probe, context) {
|
|
if (!probe) {
|
|
return unverifiedProbe({
|
|
...context,
|
|
reasonCode: context.fallbackReasonCode,
|
|
reason: "not_observed"
|
|
});
|
|
}
|
|
if (probe.ok !== true) {
|
|
return errorProbe({
|
|
...context,
|
|
reasonCode: probe.timeout ? "timeout" : probe.status ? `HTTP ${probe.status}` : context.fallbackReasonCode,
|
|
reason: probe.error ?? "request_failed",
|
|
httpStatus: probe.status,
|
|
traceId: traceIdFrom(probe),
|
|
evidenceSummary: evidenceSummaryFrom(probe)
|
|
});
|
|
}
|
|
|
|
const payload = probe.data;
|
|
const serviceId = payload?.serviceId ?? context.serviceId;
|
|
if (context.apiPath === "/v1" && payload?.codeAgent) {
|
|
const codeAgentProbe = classifyCodeAgentProbe({ restIndex: probe });
|
|
if (codeAgentProbe?.kind === "readonly") {
|
|
return {
|
|
...codeAgentProbe,
|
|
apiPath: context.apiPath,
|
|
rawStatus: rawStatusFrom(payload)
|
|
};
|
|
}
|
|
}
|
|
const durableReason = durableRuntimeReason(payload);
|
|
if (durableReason) {
|
|
return readOnlyProbe({
|
|
...context,
|
|
serviceId,
|
|
...durableReason,
|
|
rawStatus: rawStatusFrom(payload),
|
|
traceId: traceIdFrom(payload),
|
|
evidenceSummary: evidenceSummaryFrom(payload)
|
|
});
|
|
}
|
|
|
|
const blocker = firstBlocker(payload);
|
|
if (blocker) {
|
|
const reasonCode = String(blocker.code ?? blocker.reason ?? "blocked");
|
|
const reason = blocker.summary ?? blocker.message ?? blocker.reason ?? reasonCode;
|
|
if (isReadOnlyReason(reasonCode) || isReadOnlyReason(reason)) {
|
|
return readOnlyProbe({
|
|
...context,
|
|
serviceId,
|
|
reasonCode,
|
|
reason,
|
|
rawStatus: rawStatusFrom(payload),
|
|
traceId: traceIdFrom(payload),
|
|
evidenceSummary: evidenceSummaryFrom(payload)
|
|
});
|
|
}
|
|
return errorProbe({
|
|
...context,
|
|
serviceId,
|
|
reasonCode,
|
|
reason,
|
|
rawStatus: rawStatusFrom(payload),
|
|
traceId: traceIdFrom(payload),
|
|
evidenceSummary: evidenceSummaryFrom(payload)
|
|
});
|
|
}
|
|
|
|
const status = normalizedStatus(rawStatusFrom(payload));
|
|
if (ERROR_STATUSES.includes(status)) {
|
|
return errorProbe({
|
|
...context,
|
|
serviceId,
|
|
reasonCode: status,
|
|
reason: "API returned error status",
|
|
rawStatus: rawStatusFrom(payload),
|
|
traceId: traceIdFrom(payload),
|
|
evidenceSummary: evidenceSummaryFrom(payload)
|
|
});
|
|
}
|
|
if (RAW_DEGRADED_STATUSES.includes(status) || BLOCKED_STATUSES.includes(status) || readinessFalse(payload)) {
|
|
const reasonCode = readinessFalse(payload)
|
|
? "ready_false_without_blocker"
|
|
: status === "degraded"
|
|
? "api_degraded_without_attribution"
|
|
: status;
|
|
return errorProbe({
|
|
...context,
|
|
serviceId,
|
|
reasonCode,
|
|
reason: "API readiness is not pass and no concrete blocker was provided",
|
|
rawStatus: rawStatusFrom(payload),
|
|
traceId: traceIdFrom(payload),
|
|
evidenceSummary: evidenceSummaryFrom(payload)
|
|
});
|
|
}
|
|
if (UNVERIFIED_STATUSES.includes(status)) {
|
|
return unverifiedProbe({
|
|
...context,
|
|
serviceId,
|
|
reasonCode: context.fallbackReasonCode,
|
|
reason: "status_not_observed",
|
|
rawStatus: rawStatusFrom(payload),
|
|
traceId: traceIdFrom(payload),
|
|
evidenceSummary: evidenceSummaryFrom(payload)
|
|
});
|
|
}
|
|
return passProbe({
|
|
...context,
|
|
serviceId,
|
|
reasonCode: status || "ready",
|
|
reason: "ready",
|
|
rawStatus: rawStatusFrom(payload),
|
|
traceId: traceIdFrom(payload),
|
|
evidenceSummary: evidenceSummaryFrom(payload)
|
|
});
|
|
}
|
|
|
|
function classifyCodeAgentProbe(live = {}) {
|
|
const availability = live.restIndex?.data?.codeAgent ?? live.healthLive?.data?.codeAgent;
|
|
if (!availability) {
|
|
return unverifiedProbe({
|
|
serviceId: "hwlab-cloud-api",
|
|
apiPath: "/v1/agent/chat",
|
|
reasonCode: "code_agent_unverified",
|
|
reason: "availability_not_observed"
|
|
});
|
|
}
|
|
const serviceId = "hwlab-cloud-api";
|
|
const apiPath = availability.endpoint ?? "POST /v1/agent/chat";
|
|
const provider = availability.provider ?? "not_observed";
|
|
const backend = availability.backend ?? "not_observed";
|
|
const capability = availability.capabilityLevel ?? availability.runner?.capabilityLevel;
|
|
const providerStatus = availability.error?.providerStatus ?? availability.providerStatus;
|
|
const gate = availability.longLivedSessionGate;
|
|
const stdio = availability.codexStdio ?? availability.codexStdioFeasibility;
|
|
if (gate?.status === "pass" && stdio?.canStartLongLivedCodexStdio === true && availability.ready === true) {
|
|
return passProbe({
|
|
serviceId,
|
|
apiPath,
|
|
reasonCode: "codex_stdio_ready",
|
|
reason: `provider=codex-stdio; backend=${availability.codexStdio?.backend ?? backend}; capability=long-lived-codex-stdio-session`,
|
|
traceId: traceIdFrom(availability),
|
|
evidenceSummary: evidenceSummaryFrom(availability),
|
|
meta: { provider: "codex-stdio", backend: availability.codexStdio?.backend ?? backend, capability: "long-lived-codex-stdio-session" }
|
|
});
|
|
}
|
|
if (capability === "text-chat-only" || availability.runner?.kind === "openai-responses-fallback") {
|
|
return readOnlyProbe({
|
|
serviceId,
|
|
apiPath,
|
|
reasonCode: "fallback_text_chat_only",
|
|
reason: `provider=${provider}; backend=${backend}; capability=text-chat-only`,
|
|
traceId: traceIdFrom(availability),
|
|
evidenceSummary: evidenceSummaryFrom(availability),
|
|
meta: { provider, backend, capability }
|
|
});
|
|
}
|
|
if (availability.agentKind === "controlled-readonly-session-registry" || availability.partialReady === true) {
|
|
return readOnlyProbe({
|
|
serviceId,
|
|
apiPath,
|
|
reasonCode: "codex_stdio_blocked_readonly_available",
|
|
reason: `${availability.summary ?? "controlled-readonly-session-registry available; long-lived Codex stdio blocked"}; blockers=${blockerCodes(availability).join(",") || "not_observed"}`,
|
|
traceId: traceIdFrom(availability),
|
|
evidenceSummary: evidenceSummaryFrom(availability),
|
|
meta: { provider, backend, capability }
|
|
});
|
|
}
|
|
if (availability.status === "blocked" || availability.ready === false) {
|
|
return errorProbe({
|
|
serviceId,
|
|
apiPath,
|
|
reasonCode: providerStatus ? `provider_http_${providerStatus}` : availability.reason ?? availability.blocker ?? "code_agent_blocked",
|
|
reason: availability.summary ?? `provider=${provider}; backend=${backend}; capability=${capability ?? "unknown"}`,
|
|
traceId: traceIdFrom(availability),
|
|
meta: { provider, backend, capability }
|
|
});
|
|
}
|
|
if (availability.runner?.ready === true && availability.runner?.writeCapable === false) {
|
|
return readOnlyProbe({
|
|
serviceId,
|
|
apiPath,
|
|
reasonCode: "codex_readonly_runner",
|
|
reason: `runner=${availability.runner.kind ?? "hwlab-readonly-runner"}; capability=${capability ?? "read-only-tools"}`,
|
|
traceId: traceIdFrom(availability),
|
|
evidenceSummary: evidenceSummaryFrom(availability),
|
|
meta: { provider, backend, capability }
|
|
});
|
|
}
|
|
if (availability.status === "available" || availability.ready === true) {
|
|
return passProbe({
|
|
serviceId,
|
|
apiPath,
|
|
reasonCode: capability ?? "available",
|
|
reason: `provider=${provider}; backend=${backend}`,
|
|
traceId: traceIdFrom(availability),
|
|
evidenceSummary: evidenceSummaryFrom(availability),
|
|
meta: { provider, backend, capability }
|
|
});
|
|
}
|
|
return unverifiedProbe({
|
|
serviceId,
|
|
apiPath,
|
|
reasonCode: "code_agent_unverified",
|
|
reason: `provider=${provider}; backend=${backend}`,
|
|
traceId: traceIdFrom(availability),
|
|
evidenceSummary: evidenceSummaryFrom(availability),
|
|
meta: { provider, backend, capability }
|
|
});
|
|
}
|
|
|
|
function blockerCodes(availability) {
|
|
if (Array.isArray(availability?.blockerCodes)) return availability.blockerCodes.filter(Boolean);
|
|
if (Array.isArray(availability?.blockers)) return availability.blockers.map((blocker) => blocker?.code).filter(Boolean);
|
|
return [];
|
|
}
|
|
|
|
function classifyM3ControlProbe(live = {}) {
|
|
const probe = live.m3Control;
|
|
const context = {
|
|
serviceId: "hwlab-cloud-api",
|
|
apiPath: "/v1/m3/io",
|
|
fallbackReasonCode: "m3_io_unverified"
|
|
};
|
|
if (!probe) {
|
|
return unverifiedProbe({ ...context, reasonCode: context.fallbackReasonCode, reason: "not_observed" });
|
|
}
|
|
if (probe.ok !== true) {
|
|
return errorProbe({
|
|
...context,
|
|
reasonCode: probe.timeout ? "timeout" : probe.status ? `HTTP ${probe.status}` : "m3_io_request_failed",
|
|
reason: probe.error ?? "M3 IO readiness request failed",
|
|
httpStatus: probe.status,
|
|
traceId: traceIdFrom(probe)
|
|
});
|
|
}
|
|
const data = probe.data;
|
|
const readiness = data?.readiness ?? {};
|
|
if (data?.status === "available" && readiness.status === "ready" && readiness.controlReady === true) {
|
|
return passProbe({
|
|
...context,
|
|
reasonCode: "m3_io_ready",
|
|
reason: "cloud-api -> gateway-simu -> box-simu -> hwlab-patch-panel readiness ready",
|
|
rawStatus: data.status,
|
|
traceId: traceIdFrom(data),
|
|
evidenceSummary: evidenceSummaryFrom(data)
|
|
});
|
|
}
|
|
const blocker = readiness.blocker ?? data?.blocker;
|
|
return errorProbe({
|
|
...context,
|
|
serviceId: serviceForM3Layer(blocker?.layer) ?? context.serviceId,
|
|
reasonCode: blocker?.code ?? data?.status ?? "m3_io_not_ready",
|
|
reason: blocker?.zh ?? data?.blockedReason ?? "M3 IO readiness is not ready",
|
|
rawStatus: data?.status,
|
|
traceId: traceIdFrom(data),
|
|
evidenceSummary: evidenceSummaryFrom(data)
|
|
});
|
|
}
|
|
|
|
function classifyM3StatusProbe(live = {}) {
|
|
const probe = live.m3Status;
|
|
const context = {
|
|
serviceId: "hwlab-cloud-api",
|
|
apiPath: "/v1/m3/status",
|
|
fallbackReasonCode: "m3_status_unverified"
|
|
};
|
|
if (!probe) {
|
|
return unverifiedProbe({ ...context, reasonCode: context.fallbackReasonCode, reason: "not_observed" });
|
|
}
|
|
if (probe.ok !== true) {
|
|
return errorProbe({
|
|
...context,
|
|
reasonCode: probe.timeout ? "timeout" : probe.status ? `HTTP ${probe.status}` : "m3_status_request_failed",
|
|
reason: probe.error ?? "M3 status aggregation failed",
|
|
httpStatus: probe.status,
|
|
traceId: traceIdFrom(probe)
|
|
});
|
|
}
|
|
const data = probe.data;
|
|
const blocker = data?.blocker;
|
|
if (data?.status === "error") {
|
|
return errorProbe({
|
|
...context,
|
|
serviceId: serviceForM3Layer(blocker?.layer) ?? context.serviceId,
|
|
reasonCode: blocker?.code ?? "m3_status_error",
|
|
reason: blocker?.zh ?? data?.summary ?? "M3 status returned error",
|
|
rawStatus: data.status,
|
|
traceId: traceIdFrom(data),
|
|
evidenceSummary: evidenceSummaryFrom(data)
|
|
});
|
|
}
|
|
if (data?.status === "blocked" && blocker) {
|
|
const serviceId = serviceForM3Layer(blocker.layer) ?? (isReadOnlyReason(blocker.code) ? context.serviceId : context.serviceId);
|
|
const factory = isReadOnlyReason(blocker.code) || isReadOnlyReason(blocker.zh) ? readOnlyProbe : errorProbe;
|
|
return factory({
|
|
...context,
|
|
serviceId,
|
|
reasonCode: blocker.code ?? "m3_status_blocked",
|
|
reason: blocker.zh ?? data.summary ?? "M3 status blocked",
|
|
rawStatus: data.status,
|
|
traceId: traceIdFrom(data),
|
|
evidenceSummary: evidenceSummaryFrom(data)
|
|
});
|
|
}
|
|
if (data?.status === "blocked") {
|
|
return unverifiedProbe({
|
|
...context,
|
|
reasonCode: "m3_status_waiting_trusted_record",
|
|
reason: data.summary ?? "M3 status is waiting for trusted persistence evidence",
|
|
rawStatus: data.status,
|
|
traceId: traceIdFrom(data),
|
|
evidenceSummary: evidenceSummaryFrom(data)
|
|
});
|
|
}
|
|
if (data?.status === "live" || data?.sourceKind === "DEV-LIVE") {
|
|
return passProbe({
|
|
...context,
|
|
reasonCode: data.status ?? "dev_live",
|
|
reason: data.summary ?? "M3 status observed through cloud-api",
|
|
rawStatus: data.status,
|
|
traceId: traceIdFrom(data),
|
|
evidenceSummary: evidenceSummaryFrom(data)
|
|
});
|
|
}
|
|
return unverifiedProbe({
|
|
...context,
|
|
reasonCode: "m3_status_unverified",
|
|
reason: data?.summary ?? "M3 status not yet verified",
|
|
rawStatus: data?.status,
|
|
traceId: traceIdFrom(data),
|
|
evidenceSummary: evidenceSummaryFrom(data)
|
|
});
|
|
}
|
|
|
|
function durableRuntimeReason(payload) {
|
|
const runtime = payload?.runtime;
|
|
const durability = payload?.readiness?.durability;
|
|
const reasonCode = firstNonEmpty(
|
|
runtime?.blocker,
|
|
durability?.blocker,
|
|
runtime?.connection?.queryResult,
|
|
durability?.queryResult,
|
|
runtime?.durabilityContract?.blockedLayer,
|
|
durability?.blockedLayer,
|
|
blockerCodeStartingWith(payload, "runtime_durable_adapter_")
|
|
);
|
|
const blockedLayer = firstNonEmpty(runtime?.durabilityContract?.blockedLayer, durability?.blockedLayer);
|
|
const runtimeDurable = runtime?.durable === true || durability?.durable === true;
|
|
const runtimeExplicitlyNonDurable = runtime?.durable === false || durability?.durable === false;
|
|
const runtimeReady = runtime?.ready === true || durability?.ready === true;
|
|
const liveRuntimeEvidence = runtime?.liveRuntimeEvidence === true || durability?.liveRuntimeEvidence === true;
|
|
if (!reasonCode && runtimeDurable && runtimeReady && liveRuntimeEvidence) return null;
|
|
if (runtimeExplicitlyNonDurable) {
|
|
return {
|
|
reasonCode: "runtime_durable_false",
|
|
reason: `runtime durable=false; adapter=${firstNonEmpty(runtime?.adapter, durability?.adapter, "unknown")}`,
|
|
layer: "runtime-durable"
|
|
};
|
|
}
|
|
if (
|
|
reasonCode &&
|
|
(
|
|
isReadOnlyReason(reasonCode) ||
|
|
String(reasonCode).endsWith("_blocked") ||
|
|
normalizedStatus(blockedLayer).includes("durability")
|
|
)
|
|
) {
|
|
return {
|
|
reasonCode,
|
|
reason: `runtime durable blocked at ${blockedLayer || "durability"}; queryResult=${firstNonEmpty(runtime?.connection?.queryResult, durability?.queryResult, "unknown")}`,
|
|
layer: blockedLayer || "runtime-durable"
|
|
};
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function firstBlocker(payload) {
|
|
const blockers = [
|
|
...(Array.isArray(payload?.blockers) ? payload.blockers : []),
|
|
...(Array.isArray(payload?.readiness?.blockers) ? payload.readiness.blockers : [])
|
|
].filter(Boolean);
|
|
if (blockers.length > 0) return blockers[0];
|
|
const code = firstNonEmpty(
|
|
...(Array.isArray(payload?.blockerCodes) ? payload.blockerCodes : []),
|
|
...(Array.isArray(payload?.readiness?.blockerCodes) ? payload.readiness.blockerCodes : [])
|
|
);
|
|
return code ? { code, summary: code } : null;
|
|
}
|
|
|
|
function blockerCodeStartingWith(payload, prefix) {
|
|
return [
|
|
...(Array.isArray(payload?.blockerCodes) ? payload.blockerCodes : []),
|
|
...(Array.isArray(payload?.readiness?.blockerCodes) ? payload.readiness.blockerCodes : [])
|
|
].find((code) => String(code ?? "").startsWith(prefix));
|
|
}
|
|
|
|
function readinessFalse(payload) {
|
|
return [
|
|
payload?.ready,
|
|
payload?.readiness?.ready,
|
|
payload?.readiness?.durability?.ready,
|
|
payload?.runtime?.ready
|
|
].some((value) => value === false);
|
|
}
|
|
|
|
function rawStatusFrom(payload) {
|
|
return payload?.status ?? payload?.readiness?.status ?? payload?.runtime?.status ?? payload?.readiness?.durability?.status ?? "";
|
|
}
|
|
|
|
function statusResult(result) {
|
|
return {
|
|
serviceId: null,
|
|
apiPath: null,
|
|
reasonCode: null,
|
|
traceId: null,
|
|
internalRawStatuses: result.probes
|
|
.map((probe) => probe.rawStatus)
|
|
.filter((status) => status !== undefined && status !== null && String(status).trim() !== ""),
|
|
...result
|
|
};
|
|
}
|
|
|
|
function passProbe(options) {
|
|
return probe({ kind: "pass", reachable: true, ...options });
|
|
}
|
|
|
|
function readOnlyProbe(options) {
|
|
return probe({ kind: "readonly", reachable: true, ...options });
|
|
}
|
|
|
|
function errorProbe(options) {
|
|
return probe({ kind: "error", reachable: Boolean(options.httpStatus ? options.httpStatus >= 200 && options.httpStatus < 600 : true), ...options });
|
|
}
|
|
|
|
function unverifiedProbe(options) {
|
|
return probe({ kind: "unverified", reachable: false, ...options });
|
|
}
|
|
|
|
function probe(options) {
|
|
return {
|
|
kind: options.kind,
|
|
reachable: options.reachable,
|
|
serviceId: options.serviceId,
|
|
apiPath: options.apiPath,
|
|
reasonCode: String(options.reasonCode ?? options.kind),
|
|
reason: String(options.reason ?? options.reasonCode ?? options.kind),
|
|
httpStatus: options.httpStatus ?? null,
|
|
rawStatus: options.rawStatus ?? null,
|
|
traceId: options.traceId ?? null,
|
|
evidenceSummary: options.evidenceSummary ?? null,
|
|
meta: options.meta ?? null
|
|
};
|
|
}
|
|
|
|
function probeAttribution(probe) {
|
|
return [
|
|
`${probe.serviceId} ${probe.apiPath}`,
|
|
probe.httpStatus ? `HTTP ${probe.httpStatus}` : null,
|
|
probe.reasonCode,
|
|
probe.reason && probe.reason !== probe.reasonCode ? probe.reason : null,
|
|
probe.traceId ? `trace=${probe.traceId}` : null,
|
|
probe.evidenceSummary
|
|
].filter(Boolean).join(" / ");
|
|
}
|
|
|
|
function normalizedStatus(value) {
|
|
return String(value ?? "").trim().toLowerCase();
|
|
}
|
|
|
|
function isReadOnlyReason(value) {
|
|
const text = normalizedStatus(value);
|
|
return READ_ONLY_REASON_CODES.some((code) => text.includes(code));
|
|
}
|
|
|
|
function traceIdFrom(value) {
|
|
return firstNonEmpty(
|
|
value?.traceId,
|
|
value?.meta?.traceId,
|
|
value?.data?.traceId,
|
|
value?.data?.meta?.traceId,
|
|
value?.data?.trust?.traceId,
|
|
value?.request?.traceId,
|
|
value?.operation?.traceId,
|
|
value?.result?.traceId,
|
|
value?.trust?.traceId,
|
|
value?.evidenceState?.traceId,
|
|
value?.metadata?.traceId,
|
|
value?.error?.traceId,
|
|
value?.session?.lastTraceId,
|
|
value?.session?.currentTraceId,
|
|
value?.runnerTrace?.traceId,
|
|
value?.providerTrace?.traceId
|
|
);
|
|
}
|
|
|
|
function evidenceSummaryFrom(value) {
|
|
const payload = value?.data && typeof value.data === "object" ? value.data : value;
|
|
const parts = [
|
|
["operation", firstNonEmpty(payload?.operationId, payload?.operation?.operationId, payload?.result?.operationId, payload?.trust?.operationId)],
|
|
["audit", firstNonEmpty(payload?.auditId, payload?.audit?.auditId, payload?.auditEvent?.auditId, payload?.trust?.auditId)],
|
|
["evidence", firstNonEmpty(payload?.evidenceId, payload?.evidence?.evidenceId, payload?.evidenceRecord?.evidenceId, payload?.trust?.evidenceId)],
|
|
["session", firstNonEmpty(payload?.session?.sessionId, payload?.sessionId)]
|
|
]
|
|
.filter(([, id]) => id)
|
|
.slice(0, 4)
|
|
.map(([label, id]) => `${label}=${id}`);
|
|
return parts.length > 0 ? parts.join(" ") : null;
|
|
}
|
|
|
|
function serviceForM3Layer(layer) {
|
|
const text = normalizedStatus(layer);
|
|
if (!text) return null;
|
|
if (text.includes("patch")) return "hwlab-patch-panel";
|
|
if (text.includes("gateway")) return "hwlab-gateway-simu";
|
|
if (text.includes("box") || text.includes("resource")) return "hwlab-box-simu";
|
|
if (text.includes("runtime") || text.includes("durable")) return "hwlab-cloud-api";
|
|
return null;
|
|
}
|
|
|
|
function firstNonEmpty(...values) {
|
|
for (const value of values) {
|
|
if (value !== undefined && value !== null && String(value).trim() !== "") return String(value).trim();
|
|
}
|
|
return "";
|
|
}
|