From 61d618f97b4b6fe764f2754293327877b953831d Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 26 Jun 2026 09:45:30 +0000 Subject: [PATCH] fix: warn on ad hoc web-probe scripts --- scripts/src/hwlab-node-help.ts | 1 + .../src/hwlab-node/web-observe-render.test.ts | 40 +++++++++++++++++++ scripts/src/hwlab-node/web-observe-render.ts | 21 ++++++++++ scripts/src/hwlab-node/web-observe-scripts.ts | 31 ++++++++++++++ 4 files changed, 93 insertions(+) create mode 100644 scripts/src/hwlab-node/web-observe-render.test.ts diff --git a/scripts/src/hwlab-node-help.ts b/scripts/src/hwlab-node-help.ts index 8a42d642..8e670889 100644 --- a/scripts/src/hwlab-node-help.ts +++ b/scripts/src/hwlab-node-help.ts @@ -63,6 +63,7 @@ export function hwlabNodeWebProbeHelp(): Record { }, notes: [ "Default URL, browser proxy mode, observe/analyze thresholds, and project-management command allowlist come from config/hwlab-node-lanes.yaml webProbe.", + "`web-probe script` is an ad-hoc exploration escape hatch; repeated/high-frequency workflows must become `web-probe observe command` types or repo-owned web-probe commands.", "observe is passive by default; user actions must be explicit observe command entries in control.jsonl.", "After observe start, prefer observe status|command|stop|collect|analyze instead of repeating --node/--lane/--state-dir.", "collect views render bounded summaries from existing artifacts and do not create a second source of truth.", diff --git a/scripts/src/hwlab-node/web-observe-render.test.ts b/scripts/src/hwlab-node/web-observe-render.test.ts new file mode 100644 index 00000000..a4bba7bc --- /dev/null +++ b/scripts/src/hwlab-node/web-observe-render.test.ts @@ -0,0 +1,40 @@ +import assert from "node:assert/strict"; +import { test } from "bun:test"; + +import { renderWebProbeScriptResult } from "./web-observe-render"; + +test("web-probe script render warns to promote repeated scripts into commands", () => { + const rendered = renderWebProbeScriptResult({ + ok: true, + status: "pass", + command: "web-probe script --node D601 --lane v03", + node: "D601", + lane: "v03", + url: "https://hwlab.example.test", + warnings: [{ + code: "web_probe_script_ad_hoc_only", + severity: "warning", + message: "web-probe script is for one-off exploration; repeated or high-frequency checks must be promoted to a typed command instead of rerunning temporary scripts.", + }], + hints: [ + "Prefer `web-probe observe start` plus `web-probe observe command` for interactive flows.", + ], + preferredCommands: { + startObserver: "bun scripts/cli.ts web-probe observe start --node D601 --lane v03 --target-path /projects/mdtodo", + mdtodoSummary: "bun scripts/cli.ts web-probe observe collect --view project-mdtodo-summary", + }, + summary: { ok: true, status: "pass" }, + issueEvidence: {}, + probe: { steps: [], script: { result: { ok: true } } }, + reportLoad: { source: "stdout", path: ".state/web-probe-script/run.demo/web-probe-script-report.json" }, + result: { exitCode: 0, timedOut: false }, + }); + + const text = String(rendered.renderedText ?? ""); + assert.match(text, /WARNINGS/u); + assert.match(text, /web_probe_script_ad_hoc_only/u); + assert.match(text, /HINTS/u); + assert.match(text, /web-probe observe command/u); + assert.match(text, /startObserver: bun scripts\/cli\.ts web-probe observe start/u); + assert.match(text, /mdtodoSummary: bun scripts\/cli\.ts web-probe observe collect/u); +}); diff --git a/scripts/src/hwlab-node/web-observe-render.ts b/scripts/src/hwlab-node/web-observe-render.ts index db05b879..3fa27271 100644 --- a/scripts/src/hwlab-node/web-observe-render.ts +++ b/scripts/src/hwlab-node/web-observe-render.ts @@ -1413,6 +1413,9 @@ export function renderWebProbeScriptResult(result: Record): Rec const recoveredArtifactSummary = record(recoveredArtifacts.artifacts); const recoveredItems = webObserveArray(recoveredArtifactSummary.items).slice(-8).map((item) => record(item)); const steps = webObserveArray(probe.steps).slice(-5).map((item) => record(item)); + const warnings = webObserveArray(result.warnings).slice(0, 6); + const hints = webObserveArray(result.hints).slice(0, 6).map((item) => String(item ?? "").trim()).filter(Boolean); + const preferredCommands = record(result.preferredCommands); const resultRows = webProbeScriptRecordRows(scriptResult, 12); const evidenceRows = webProbeScriptRecordRows(issueEvidence, 12); const summaryRows = [ @@ -1462,9 +1465,27 @@ export function renderWebProbeScriptResult(result: Record): Rec recoveredItems.map((item) => [item.kind, item.byteCount, item.path]), ), "", + warnings.length === 0 + ? "WARNINGS\n-" + : [ + "WARNINGS", + webObserveTable( + ["CODE", "SEVERITY", "MESSAGE"], + warnings.map((item) => { + const warning = record(item); + return [warning.code ?? "warning", warning.severity ?? warning.level ?? "warning", warning.message ?? warning.summary ?? webProbeScriptPreview(warning)]; + }), + ), + ].join("\n"), + "", + hints.length === 0 + ? "HINTS\n-" + : ["HINTS", ...hints.map((hint) => `- ${hint}`)].join("\n"), + "", "NEXT", ` report: ${reportLoad.path ?? probe.reportPath ?? "-"}`, ` rerun: ${result.command ?? `web-probe script --node ${result.node ?? "-"} --lane ${result.lane ?? "-"}`}`, + ...Object.entries(preferredCommands).map(([name, command]) => ` ${name}: ${String(command)}`), ].join("\n"); return withWebObserveRendered(result, renderedText); } diff --git a/scripts/src/hwlab-node/web-observe-scripts.ts b/scripts/src/hwlab-node/web-observe-scripts.ts index 60b26d8a..26832d04 100644 --- a/scripts/src/hwlab-node/web-observe-scripts.ts +++ b/scripts/src/hwlab-node/web-observe-scripts.ts @@ -558,6 +558,9 @@ export function runNodeWebProbeScript( degradedReason: recoveredReport.degradedReason, result: recoveredReport.result === null ? null : compactCommandResultRedacted(recoveredReport.result, [material.password ?? ""]), }, + warnings: webProbeScriptGovernanceWarnings(options), + hints: webProbeScriptGovernanceHints(options), + preferredCommands: webProbeScriptPreferredCommands(options), recoveredArtifacts: recoveredArtifacts === null ? null : { source: recoveredArtifacts.source, degradedReason: recoveredArtifacts.degradedReason, @@ -569,6 +572,34 @@ export function runNodeWebProbeScript( }); } +function webProbeScriptGovernanceWarnings(options: NodeWebProbeScriptOptions): Record[] { + return [{ + code: "web_probe_script_ad_hoc_only", + severity: "warning", + message: "web-probe script is for one-off exploration; repeated or high-frequency checks must be promoted to a typed command instead of rerunning temporary scripts.", + node: options.node, + lane: options.lane, + valuesRedacted: true, + }]; +} + +function webProbeScriptGovernanceHints(options: NodeWebProbeScriptOptions): string[] { + return [ + "Prefer `web-probe observe start` plus `web-probe observe command` for interactive flows; use `observe collect/analyze` for repeated evidence reads.", + "If the same script is needed more than once, add or extend a reusable command type in the web-probe observe command surface.", + `For this target, start with: bun scripts/cli.ts web-probe observe start --node ${options.node} --lane ${options.lane} --target-path /projects/mdtodo`, + ]; +} + +function webProbeScriptPreferredCommands(options: NodeWebProbeScriptOptions): Record { + return { + startObserver: `bun scripts/cli.ts web-probe observe start --node ${options.node} --lane ${options.lane} --target-path /projects/mdtodo`, + mdtodoSummary: "bun scripts/cli.ts web-probe observe collect --view project-mdtodo-summary", + analyze: "bun scripts/cli.ts web-probe observe analyze ", + addCommandType: "Add a repo-owned web-probe observe command type before repeating this script.", + }; +} + export function nodeWebProbeScriptRemoteShell(options: NodeWebProbeScriptOptions, secretSpec: RuntimeSecretSpec, password: string, webProbeProxy: NodeWebProbeHostProxyEnv): string { const userScriptB64 = Buffer.from(options.scriptText, "utf8").toString("base64"); const runnerB64 = Buffer.from(nodeWebProbeScriptRunnerSource(), "utf8").toString("base64");