From 7a72e396ba4ebd833cee32919c1ea16a915caa9c Mon Sep 17 00:00:00 2001 From: Lyon <88232613+pikasTech@users.noreply.github.com> Date: Tue, 23 Jun 2026 01:04:27 +0800 Subject: [PATCH] fix(web-probe): keep sampling through startup degradation (#709) Co-authored-by: Codex --- scripts/src/hwlab-node-impl.ts | 100 +++++++++++------- .../hwlab-node-web-observe-runner-source.ts | 18 ++-- 2 files changed, 75 insertions(+), 43 deletions(-) diff --git a/scripts/src/hwlab-node-impl.ts b/scripts/src/hwlab-node-impl.ts index 55a409c2..432b806e 100644 --- a/scripts/src/hwlab-node-impl.ts +++ b/scripts/src/hwlab-node-impl.ts @@ -8135,12 +8135,12 @@ function runNodeWebProbeObserveAnalyze(options: NodeWebProbeObserveOptions, spec " loadingLongestContinuousSeconds: compact.sampleMetrics.loadingLongestContinuousSeconds ?? null,", " loadingCurrentContinuousSeconds: compact.sampleMetrics.loadingCurrentContinuousSeconds ?? null,", " loadingOverFiveSecondSegmentCount: compact.sampleMetrics.loadingOverFiveSecondSegmentCount ?? null,", - " sessionRailSampleCount: compact.sampleMetrics.sessionRailSampleCount ?? null,", - " sessionRailVisibleSampleCount: compact.sampleMetrics.sessionRailVisibleSampleCount ?? null,", - " sessionRailFallbackMajoritySampleCount: compact.sampleMetrics.sessionRailFallbackMajoritySampleCount ?? null,", - " sessionRailFallbackMaxRatio: compact.sampleMetrics.sessionRailFallbackMaxRatio ?? null,", - " sessionRailFallbackMaxVisibleCount: compact.sampleMetrics.sessionRailFallbackMaxVisibleCount ?? null,", - " sessionRailFallbackMaxCount: compact.sampleMetrics.sessionRailFallbackMaxCount ?? null,", + " sessionRailSampleCount: compact.sampleMetrics.sessionRailSampleCount ?? compact.sampleMetrics.sessionRailTitles?.summary?.sampleCount ?? null,", + " sessionRailVisibleSampleCount: compact.sampleMetrics.sessionRailVisibleSampleCount ?? compact.sampleMetrics.sessionRailTitles?.summary?.visibleSampleCount ?? null,", + " sessionRailFallbackMajoritySampleCount: compact.sampleMetrics.sessionRailFallbackMajoritySampleCount ?? compact.sampleMetrics.sessionRailTitles?.summary?.majorityFallbackSampleCount ?? null,", + " sessionRailFallbackMaxRatio: compact.sampleMetrics.sessionRailFallbackMaxRatio ?? compact.sampleMetrics.sessionRailTitles?.summary?.maxFallbackRatio ?? null,", + " sessionRailFallbackMaxVisibleCount: compact.sampleMetrics.sessionRailFallbackMaxVisibleCount ?? compact.sampleMetrics.sessionRailTitles?.summary?.maxVisibleCount ?? null,", + " sessionRailFallbackMaxCount: compact.sampleMetrics.sessionRailFallbackMaxCount ?? compact.sampleMetrics.sessionRailTitles?.summary?.maxFallbackTitleCount ?? null,", " loading: compact.sampleMetrics.loading ?? null,", " sessionRailTitles: compact.sampleMetrics.sessionRailTitles ? { summary: compact.sampleMetrics.sessionRailTitles.summary ?? null, samples: Array.isArray(compact.sampleMetrics.sessionRailTitles.samples) ? compact.sampleMetrics.sessionRailTitles.samples.slice(0, 4) : [], examples: Array.isArray(compact.sampleMetrics.sessionRailTitles.examples) ? compact.sampleMetrics.sessionRailTitles.examples.slice(0, 4) : [] } : null,", " turnTimingRows: compact.sampleMetrics.turnTimingRows ?? null,", @@ -8193,9 +8193,12 @@ function runNodeWebProbeObserveAnalyze(options: NodeWebProbeObserveOptions, spec " loadingCurrentContinuousSeconds: compact.sampleMetrics.loadingCurrentContinuousSeconds ?? null,", " loadingOverFiveSecondSegmentCount: compact.sampleMetrics.loadingOverFiveSecondSegmentCount ?? null,", " loading: compact.sampleMetrics.loading ? { summary: compact.sampleMetrics.loading.summary ?? null, longestSegments: Array.isArray(compact.sampleMetrics.loading.longestSegments) ? compact.sampleMetrics.loading.longestSegments.slice(0, 4) : [], owners: Array.isArray(compact.sampleMetrics.loading.owners) ? compact.sampleMetrics.loading.owners.slice(0, 4) : [] } : null,", - " sessionRailFallbackMajoritySampleCount: compact.sampleMetrics.sessionRailFallbackMajoritySampleCount ?? null,", - " sessionRailFallbackMaxRatio: compact.sampleMetrics.sessionRailFallbackMaxRatio ?? null,", - " sessionRailFallbackMaxCount: compact.sampleMetrics.sessionRailFallbackMaxCount ?? null,", + " sessionRailSampleCount: compact.sampleMetrics.sessionRailSampleCount ?? compact.sampleMetrics.sessionRailTitles?.summary?.sampleCount ?? null,", + " sessionRailVisibleSampleCount: compact.sampleMetrics.sessionRailVisibleSampleCount ?? compact.sampleMetrics.sessionRailTitles?.summary?.visibleSampleCount ?? null,", + " sessionRailFallbackMajoritySampleCount: compact.sampleMetrics.sessionRailFallbackMajoritySampleCount ?? compact.sampleMetrics.sessionRailTitles?.summary?.majorityFallbackSampleCount ?? null,", + " sessionRailFallbackMaxRatio: compact.sampleMetrics.sessionRailFallbackMaxRatio ?? compact.sampleMetrics.sessionRailTitles?.summary?.maxFallbackRatio ?? null,", + " sessionRailFallbackMaxVisibleCount: compact.sampleMetrics.sessionRailFallbackMaxVisibleCount ?? compact.sampleMetrics.sessionRailTitles?.summary?.maxVisibleCount ?? null,", + " sessionRailFallbackMaxCount: compact.sampleMetrics.sessionRailFallbackMaxCount ?? compact.sampleMetrics.sessionRailTitles?.summary?.maxFallbackTitleCount ?? null,", " sessionRailTitles: compact.sampleMetrics.sessionRailTitles ? { summary: compact.sampleMetrics.sessionRailTitles.summary ?? null, samples: Array.isArray(compact.sampleMetrics.sessionRailTitles.samples) ? compact.sampleMetrics.sessionRailTitles.samples.slice(0, 4) : [], examples: Array.isArray(compact.sampleMetrics.sessionRailTitles.examples) ? compact.sampleMetrics.sessionRailTitles.examples.slice(0, 4) : [] } : null,", " turnTimingRows: compact.sampleMetrics.turnTimingRows ?? null,", " turnTimingNonMonotonicCount: compact.sampleMetrics.turnTimingNonMonotonicCount ?? null,", @@ -8478,15 +8481,15 @@ function renderWebObserveAnalyzeTable(value: Record): string { webObserveText(loadingSummary?.overFiveSecondSegmentCount ?? sampleMetrics?.loadingOverFiveSecondSegmentCount), ]]), "", - "Session rail titles:", - webObserveTable(["SAMPLES", "VISIBLE", "MAJORITY", "MAX_RATIO", "MAX_VISIBLE", "MAX_FALLBACK"], [[ - webObserveText(sessionRailTitleSummary?.sampleCount ?? sampleMetrics?.sessionRailSampleCount), - webObserveText(sessionRailTitleSummary?.visibleSampleCount ?? sampleMetrics?.sessionRailVisibleSampleCount), - webObserveText(sessionRailTitleSummary?.majorityFallbackSampleCount ?? sampleMetrics?.sessionRailFallbackMajoritySampleCount), - webObserveText(sessionRailTitleSummary?.maxFallbackRatio ?? sampleMetrics?.sessionRailFallbackMaxRatio), - webObserveText(sessionRailTitleSummary?.maxVisibleCount ?? sampleMetrics?.sessionRailFallbackMaxVisibleCount), - webObserveText(sessionRailTitleSummary?.maxFallbackTitleCount ?? sampleMetrics?.sessionRailFallbackMaxCount), - ]]), + "Session rail titles:", + webObserveTable(["SAMPLES", "VISIBLE", "MAJORITY", "MAX_RATIO", "MAX_VISIBLE", "MAX_FALLBACK"], [[ + webObserveText(sessionRailTitleSummary?.sampleCount ?? sampleMetrics?.sessionRailSampleCount ?? archiveMetrics?.sessionRailSampleCount), + webObserveText(sessionRailTitleSummary?.visibleSampleCount ?? sampleMetrics?.sessionRailVisibleSampleCount ?? archiveMetrics?.sessionRailVisibleSampleCount), + webObserveText(sessionRailTitleSummary?.majorityFallbackSampleCount ?? sampleMetrics?.sessionRailFallbackMajoritySampleCount ?? archiveMetrics?.sessionRailFallbackMajoritySampleCount), + webObserveText(sessionRailTitleSummary?.maxFallbackRatio ?? sampleMetrics?.sessionRailFallbackMaxRatio ?? archiveMetrics?.sessionRailFallbackMaxRatio), + webObserveText(sessionRailTitleSummary?.maxVisibleCount ?? sampleMetrics?.sessionRailFallbackMaxVisibleCount ?? archiveMetrics?.sessionRailFallbackMaxVisibleCount), + webObserveText(sessionRailTitleSummary?.maxFallbackTitleCount ?? sampleMetrics?.sessionRailFallbackMaxCount ?? archiveMetrics?.sessionRailFallbackMaxCount), + ]]), sessionRailTitleSamples.length === 0 ? "Session rail fallback samples:\n-" : webObserveTable(["SEQ", "ROLE", "VISIBLE", "FALLBACK", "RATIO", "EXAMPLE"], sessionRailTitleSamples.map((item) => [ webObserveText(item.seq), webObserveText(item.pageRole), @@ -9638,24 +9641,49 @@ function readNodeWebProbeScriptReport( "const reportPath = process.argv[2];", "const report = JSON.parse(fs.readFileSync(reportPath, 'utf8'));", "function rec(value) { return value && typeof value === 'object' && !Array.isArray(value) ? value : {}; }", - "function compact(value, depth = 0) {", - " if (value === null || value === undefined) return value ?? null;", - " if (typeof value === 'string') return value.replace(/\\s+/gu, ' ').trim().slice(0, 240);", - " if (typeof value === 'number' || typeof value === 'boolean') return value;", - " if (depth >= 4) return '[max-depth]';", - " if (Array.isArray(value)) return value.slice(0, 6).map((item) => compact(item, depth + 1));", - " if (typeof value === 'object') {", - " const out = {};", - " for (const [key, nested] of Object.entries(value).slice(0, 12)) out[key] = compact(nested, depth + 1);", - " return out;", - " }", - " return String(value).slice(0, 240);", - "}", - "const scriptBlock = rec(report.script);", - "const compactReport = {", - " ok: report.ok === true,", - " status: typeof report.status === 'string' ? report.status : null,", - " summary: compact(report.summary),", + "function compact(value, depth = 0) {", + " if (value === null || value === undefined) return value ?? null;", + " if (typeof value === 'string') return value.replace(/\\s+/gu, ' ').trim().slice(0, 120);", + " if (typeof value === 'number' || typeof value === 'boolean') return value;", + " if (depth >= 3) return '[max-depth]';", + " if (Array.isArray(value)) return value.slice(0, 4).map((item) => compact(item, depth + 1));", + " if (typeof value === 'object') {", + " const out = {};", + " for (const [key, nested] of Object.entries(value).slice(0, 8)) out[key] = compact(nested, depth + 1);", + " return out;", + " }", + " return String(value).slice(0, 240);", + "}", + "function compactSummary(value) {", + " const summary = rec(value);", + " return {", + " ok: summary.ok === true,", + " status: typeof summary.status === 'string' ? summary.status : null,", + " degradedReason: typeof summary.degradedReason === 'string' ? summary.degradedReason : null,", + " failureKind: typeof summary.failureKind === 'string' ? summary.failureKind : null,", + " failedCondition: typeof summary.failedCondition === 'string' ? compact(summary.failedCondition) : null,", + " nextAction: typeof summary.nextAction === 'string' ? compact(summary.nextAction) : null,", + " baseUrl: typeof summary.baseUrl === 'string' ? summary.baseUrl : null,", + " finalUrl: typeof summary.finalUrl === 'string' ? summary.finalUrl : null,", + " lastUrl: typeof summary.lastUrl === 'string' ? summary.lastUrl : null,", + " scriptSha256: typeof summary.scriptSha256 === 'string' ? summary.scriptSha256 : null,", + " runDir: typeof summary.runDir === 'string' ? summary.runDir : null,", + " reportPath: typeof summary.reportPath === 'string' ? summary.reportPath : null,", + " reportSha256: typeof summary.reportSha256 === 'string' ? summary.reportSha256 : null,", + " lastScreenshot: compact(summary.lastScreenshot),", + " screenshots: compact(summary.screenshots),", + " apiMatrix: compact(summary.apiMatrix),", + " stepCount: typeof summary.stepCount === 'number' ? summary.stepCount : null,", + " lastStep: compact(summary.lastStep),", + " issueEvidence: compact(summary.issueEvidence),", + " valuesRedacted: true,", + " };", + "}", + "const scriptBlock = rec(report.script);", + "const compactReport = {", + " ok: report.ok === true,", + " status: typeof report.status === 'string' ? report.status : null,", + " summary: compactSummary(report.summary),", " issueEvidence: compact(report.issueEvidence ?? rec(report.summary).issueEvidence),", " baseUrl: typeof report.baseUrl === 'string' ? report.baseUrl : null,", " finalUrl: typeof report.finalUrl === 'string' ? report.finalUrl : null,", diff --git a/scripts/src/hwlab-node-web-observe-runner-source.ts b/scripts/src/hwlab-node-web-observe-runner-source.ts index 490739c1..22749014 100644 --- a/scripts/src/hwlab-node-web-observe-runner-source.ts +++ b/scripts/src/hwlab-node-web-observe-runner-source.ts @@ -80,13 +80,17 @@ try { await runControlCommand({ id: "startup-goto", type: "goto", path: targetPath, createdAt: new Date().toISOString(), source: "startup" }, async () => gotoTarget(targetPath)); observerPage = await context.newPage(); attachPassiveListeners(observerPage, "observer", observerPageId); - await runControlCommand({ id: "startup-observer-goto", type: "observerGoto", path: targetPath, createdAt: new Date().toISOString(), source: "startup" }, async () => { - const result = await syncObserverPageToControlSession("startup"); - if (!result?.ok) { - throw Object.assign(new Error("observer startup did not reach the target workbench route: " + (result?.failureKind || result?.reason || "observer-not-ready")), { details: result }); - } - return result; - }); + await runControlCommand({ id: "startup-observer-goto", type: "observerGoto", path: targetPath, createdAt: new Date().toISOString(), source: "startup" }, async () => { + const result = await syncObserverPageToControlSession("startup"); + if (!result?.ok) { + await appendJsonl(files.control, eventRecord("observer-startup-degraded", { + reason: result?.failureKind || result?.reason || "observer-not-ready", + result: sanitize(result), + valuesRedacted: true, + })); + } + return result ?? { ok: false, reason: "observer-not-ready", pageRole: "observer", pageId: observerPageId, valuesRedacted: true }; + }); terminalStatus = "running"; await writeManifest({ status: "running", auth: publicAuth(auth) }); await writeHeartbeat({ status: "running" });