fix: add structured edge frp diagnosis

This commit is contained in:
HWLAB Code Queue
2026-05-21 17:38:14 +00:00
parent 0a066f2483
commit e9e71a7045
5 changed files with 134 additions and 28 deletions
+12
View File
@@ -37,6 +37,18 @@ The smoke classifies failures into these blockers:
- `app_health_blocker`: public HTTP reaches an app but health JSON is wrong,
missing evidence, or returns an HTTP error.
## Machine-Readable Diagnosis
Blocked `frp_blocker` and `edge_proxy_blocker` reports now include a
`diagnosis` object in `reports/dev-gate/dev-edge-health.json` with:
- `likelyLayer`: the most likely broken layer, such as `master-edge/frps`;
- `confidence`: coarse confidence for the diagnosis;
- `likelyCause`: a single-sentence blocker summary;
- `evidence`: the TCP probes that support the diagnosis;
- `notProven`: layers this probe did not rule out;
- `nextTask`: the smallest read-only follow-up or ops action.
## Current Blocker
The committed report at `reports/dev-gate/dev-edge-health.json` records a
+45 -11
View File
@@ -4,12 +4,12 @@
"reportVersion": "v1",
"issue": "pikasTech/HWLAB#36",
"taskId": "dev-edge-health",
"commitId": "7092eb981459",
"commitId": "cef2befdd156",
"acceptanceLevel": "dev_edge_health",
"devOnly": true,
"prodDisabled": true,
"status": "blocked",
"generatedAt": "2026-05-21T16:48:05.275Z",
"generatedAt": "2026-05-21T17:35:52.483Z",
"namespace": "hwlab-dev",
"endpoint": "http://74.48.78.17:6667",
"sourceContract": {
@@ -58,18 +58,18 @@
"hwlab-dev hwlab-edge-proxy, hwlab-tunnel-client, hwlab-router, and hwlab-cloud-api are observable",
"No PROD endpoint, secret read, restart, or UniDesk runtime substitute is used"
],
"summary": "public 6667 and frps control 7000 both refuse TCP connections; frps is not reachable on the master edge"
"summary": "public 6667, frps control 7000, and tunnel health 7402 all refuse TCP connections; the master-edge frps listener or its ports are most likely down or closed"
},
"blockers": [
{
"type": "network_blocker",
"scope": "frp_blocker",
"scope": "master-edge/frps",
"status": "open",
"summary": "public 6667 and frps control 7000 both refuse TCP connections; frps is not reachable on the master edge"
"summary": "public 6667, frps control 7000, and tunnel health 7402 all refuse TCP connections; the master-edge frps listener or its ports are most likely down or closed"
}
],
"edgeHealth": {
"generatedAt": "2026-05-21T16:48:05.275Z",
"generatedAt": "2026-05-21T17:35:52.483Z",
"mode": "live-read-only",
"endpoint": "http://74.48.78.17:6667",
"safety": {
@@ -131,7 +131,7 @@
"status": "error",
"code": "ECONNREFUSED",
"message": "connect ECONNREFUSED 74.48.78.17:6667",
"durationMs": 221
"durationMs": 223
},
{
"host": "74.48.78.17",
@@ -139,7 +139,7 @@
"status": "error",
"code": "ECONNREFUSED",
"message": "connect ECONNREFUSED 74.48.78.17:7000",
"durationMs": 218
"durationMs": 209
},
{
"host": "74.48.78.17",
@@ -147,7 +147,7 @@
"status": "error",
"code": "ECONNREFUSED",
"message": "connect ECONNREFUSED 74.48.78.17:7402",
"durationMs": 207
"durationMs": 1222
}
],
"publicHttp": [
@@ -158,7 +158,7 @@
"name": "TypeError",
"message": "fetch failed"
},
"durationMs": 34
"durationMs": 40
},
{
"url": "http://74.48.78.17:6667/health/live",
@@ -213,6 +213,40 @@
],
"status": "blocker",
"classification": "frp_blocker",
"blocker": "public 6667 and frps control 7000 both refuse TCP connections; frps is not reachable on the master edge"
"blocker": "public 6667, frps control 7000, and tunnel health 7402 all refuse TCP connections; the master-edge frps listener or its ports are most likely down or closed",
"diagnosis": {
"likelyLayer": "master-edge/frps",
"confidence": "high",
"likelyCause": "public 6667, frps control 7000, and tunnel health 7402 all refuse TCP connections; the master-edge frps listener or its ports are most likely down or closed",
"evidence": [
{
"host": "74.48.78.17",
"port": 6667,
"status": "error",
"code": "ECONNREFUSED",
"message": "connect ECONNREFUSED 74.48.78.17:6667"
},
{
"host": "74.48.78.17",
"port": 7000,
"status": "error",
"code": "ECONNREFUSED",
"message": "connect ECONNREFUSED 74.48.78.17:7000"
},
{
"host": "74.48.78.17",
"port": 7402,
"status": "error",
"code": "ECONNREFUSED",
"message": "connect ECONNREFUSED 74.48.78.17:7402"
}
],
"notProven": [
"k3s service readiness",
"frpc connectivity",
"edge proxy deployment"
],
"nextTask": "Restore or reopen frps on 74.48.78.17, then rerun the read-only edge smoke."
}
}
}
+7 -7
View File
@@ -15,10 +15,10 @@
],
"target": {
"ref": "origin/main",
"commitId": "6da6ab40bc356ddf6f2ebea11af935f4399e4945",
"shortCommitId": "6da6ab4"
"commitId": "cef2befdd156d3d4028f087a96e9c118b9174bf1",
"shortCommitId": "cef2bef"
},
"generatedAt": "2026-05-21T16:53:29.568Z",
"generatedAt": "2026-05-21T17:35:52.507Z",
"mode": "read-only",
"devOnly": true,
"prodDisabled": true,
@@ -51,7 +51,7 @@
"id": "target-commit-pinning",
"category": "contract",
"status": "blocked",
"summary": "deploy/catalog commitId 24eb3bf does not match origin/main 6da6ab4.",
"summary": "deploy/catalog commitId 24eb3bf does not match origin/main cef2bef.",
"evidence": []
},
{
@@ -72,7 +72,7 @@
"id": "dev-artifact-publish-report",
"category": "registry",
"status": "blocked",
"summary": "DEV artifact publish report status is blocked with 9/13 published for source 1e8d009e9531d71cef0762998663b4e3731032c6.",
"summary": "DEV artifact publish report status is blocked with 0/13 published for source 70bb9168ead272ec1f77b752df8e178e70858df4.",
"evidence": []
},
{
@@ -294,7 +294,7 @@
"status": "open",
"type": "contract_blocker",
"scope": "deploy-target",
"summary": "deploy/deploy.json and deploy/artifact-catalog.dev.json still target 24eb3bf, not origin/main 6da6ab4.",
"summary": "deploy/deploy.json and deploy/artifact-catalog.dev.json still target 24eb3bf, not origin/main cef2bef.",
"nextTask": "Publish or select a DEV artifact set for the current origin/main commit and update deploy/deploy.json plus deploy/artifact-catalog.dev.json to that immutable commit/tag."
},
{
@@ -308,7 +308,7 @@
"status": "open",
"type": "runtime_blocker",
"scope": "dev-artifact-publish",
"summary": "reports/dev-gate/dev-artifacts.json does not prove all HWLAB service artifacts for origin/main 6da6ab4; current status is blocked with 9/13 published.",
"summary": "reports/dev-gate/dev-artifacts.json does not prove all HWLAB service artifacts for origin/main cef2bef; current status is blocked with 0/13 published.",
"nextTask": "Complete DEV artifact publishing for every frozen HWLAB service at the current origin/main commit and record immutable registry digests."
},
{
+68 -8
View File
@@ -103,6 +103,7 @@ async function createDevGateReport(edgeHealth) {
const commitId = await gitCommitId();
const status = edgeHealth.status === "pass" ? "pass" : edgeHealth.status === "not_run" ? "not_run" : "blocked";
const blockerType = status === "blocked" ? blockerTypeFor(edgeHealth.classification) : null;
const blockerScope = edgeHealth.diagnosis?.likelyLayer ?? edgeHealth.classification;
return {
$schema: "https://hwlab.pikastech.local/schemas/dev-gate-report.schema.json",
@@ -170,7 +171,7 @@ async function createDevGateReport(edgeHealth) {
? [
{
type: blockerType,
scope: edgeHealth.classification,
scope: blockerScope,
status: "open",
summary: edgeHealth.blocker
}
@@ -338,7 +339,7 @@ function classify(report) {
return classifyReachableHealth(publicHealth.json);
}
if (port6667?.status === "error" && port6667.code === "ECONNREFUSED") {
return classifyRefusedPort(frpsControl, tunnelHealth);
return classifyRefusedPort(port6667, frpsControl, tunnelHealth);
}
if (port6667?.status === "timeout") {
return blocker("dns_port_firewall_blocker", "public 6667 timed out before any HTTP response");
@@ -363,21 +364,80 @@ function classifyReachableHealth(json) {
return { status: "pass", classification: "none", blocker: null };
}
function classifyRefusedPort(frpsControl, tunnelHealth) {
function classifyRefusedPort(port6667, frpsControl, tunnelHealth) {
if (frpsControl?.status === "error" && frpsControl.code === "ECONNREFUSED") {
return blocker("frp_blocker", "public 6667 and frps control 7000 both refuse TCP connections; frps is not reachable on the master edge");
const allPortsRefused = tunnelHealth?.status === "error" && tunnelHealth.code === "ECONNREFUSED";
const diagnosis = {
likelyLayer: "master-edge/frps",
confidence: "high",
likelyCause: allPortsRefused
? "public 6667, frps control 7000, and tunnel health 7402 all refuse TCP connections; the master-edge frps listener or its ports are most likely down or closed"
: "public 6667 and frps control 7000 both refuse TCP connections; the master-edge frps listener or its ports are most likely down or closed",
evidence: [
summarizeTcpProbe(port6667),
summarizeTcpProbe(frpsControl),
summarizeTcpProbe(tunnelHealth)
],
notProven: [
"k3s service readiness",
"frpc connectivity",
"edge proxy deployment"
],
nextTask: "Restore or reopen frps on 74.48.78.17, then rerun the read-only edge smoke."
};
return blocker("frp_blocker", diagnosis.likelyCause, diagnosis);
}
if (tunnelHealth?.status === "error" && tunnelHealth.code === "ECONNREFUSED") {
return blocker("frp_blocker", "public 6667 refuses TCP and tunnel health 7402 is not reachable");
return blocker("frp_blocker", "public 6667 refuses TCP and tunnel health 7402 is not reachable", {
likelyLayer: "d601/frpc",
confidence: "medium",
likelyCause: "public 6667 and tunnel health 7402 both refuse TCP connections; the D601 frp client or its remote port is the likely break",
evidence: [
summarizeTcpProbe(port6667),
summarizeTcpProbe(tunnelHealth)
],
notProven: [
"master-edge frps reachability",
"k3s service readiness"
],
nextTask: "Restore the D601 tunnel client or its remote port, then rerun the read-only edge smoke."
});
}
return blocker("edge_proxy_blocker", "public 6667 refuses TCP connections");
return blocker("edge_proxy_blocker", "public 6667 refuses TCP connections", {
likelyLayer: "master-edge/edge-proxy",
confidence: "medium",
likelyCause: "public 6667 refuses TCP while frps control 7000 stays reachable, so the public edge proxy or its listener is the likely break",
evidence: [
summarizeTcpProbe(port6667),
summarizeTcpProbe(frpsControl)
],
notProven: [
"k3s service readiness"
],
nextTask: "Repair the edge proxy or its public listener, then rerun the read-only edge smoke."
});
}
function blocker(classification, message) {
function blocker(classification, message, diagnosis = null) {
return {
status: "blocker",
classification,
blocker: message
blocker: message,
...(diagnosis ? { diagnosis } : {})
};
}
function summarizeTcpProbe(probe) {
if (!probe) {
return null;
}
return {
host: probe.host,
port: probe.port,
status: probe.status,
code: probe.code ?? null,
message: probe.message ?? null
};
}
+2 -2
View File
@@ -431,9 +431,9 @@ function validateEdgeHealthReport(reporter, edgeReport) {
reporter.check("dev-edge-health-report", "edge", "blocked", `Committed DEV edge health report is ${edgeReport.status} with classification ${classification}.`, evidence);
reporter.block({
type: "network_blocker",
scope: "dev-edge-health",
scope: edgeHealth?.diagnosis?.likelyLayer ?? "dev-edge-health",
summary,
nextTask: "Repair the master frps/public DEV edge route and rerun the read-only DEV edge health smoke before real deployment."
nextTask: edgeHealth?.diagnosis?.nextTask ?? "Repair the master frps/public DEV edge route and rerun the read-only DEV edge health smoke before real deployment."
});
}