import assert from "node:assert/strict"; import { test } from "bun:test"; import { withWebObserveCommandRendered, withWebObserveStatusRendered } from "../hwlab-node-web-observe-render"; import { renderWebProbeScriptResult } from "./web-observe-render"; import { webProbeScriptCommandGovernance } from "./web-observe-scripts"; test("observe command renderer separates control completion from async turn terminal state", () => { const rendered = withWebObserveCommandRendered({ ok: true, status: "control-completed", command: "web-probe observe command", id: "webobs-fixture", commandId: "cmd-fixture", observerCommand: { type: "sendPrompt" }, observer: { ok: true, completedAt: "2026-07-10T01:00:01.000Z", result: { mark: "submitted" } }, control: { executionStatus: "completed", businessTurnTerminalImplied: false }, asyncTurn: { applicable: true, submissionStatus: "accepted", terminalStatus: null, terminalObserved: false, traceId: "trc_fixture" }, }); const text = String(rendered.renderedText); assert.match(text, /CONTROL.*TURN_SUBMIT.*TURN_TERMINAL/u); assert.match(text, /completed.*accepted.*not-observed.*trc_fixture/u); assert.match(text, /does not imply the asynchronous business turn reached a terminal state/u); assert.match(text, /observe status webobs-fixture --command-id cmd-fixture/u); assert.match(text, /--view turn-summary --command-id cmd-fixture/u); }); test("observe status renderer exposes the exact command phase and failed report evidence", () => { const rendered = withWebObserveStatusRendered({ ok: true, status: "running", command: "web-probe observe status", id: "webobs-fixture", node: "NC01", lane: "v03", observer: { processAlive: true, manifest: { baseUrl: "https://hwlab.example.test", targetPath: "/workbench" }, heartbeat: { status: "running", sampleSeq: 12, commandSeq: 3, updatedAt: "2026-07-10T01:00:01.000Z" }, diagnostics: { effectiveLiveness: "alive", heartbeatStale: false }, commands: { pendingCount: 0, processingCount: 0, abandonedCount: 0, failedCount: 1 }, exactCommand: { commandId: "cmd-fixture", type: "validateRealtimeFanout", phase: "failed", ok: false, error: { message: "terminal event arrived before subscriber disconnect", details: { reportPath: "artifacts/realtime-fanout/cmd-fixture/report.json", reportSha256: "sha256:fixture-report", }, }, }, tails: { samples: [], control: [], network: [] }, }, next: { status: "bun scripts/cli.ts web-probe observe status webobs-fixture", }, }); const text = String(rendered.renderedText); assert.match(text, /Exact command:/u); assert.match(text, /cmd-fixture.*validateRealtimeFanout.*failed.*false/u); assert.match(text, /sha256:fixture-report/u); assert.match(text, /terminal event arrived before subscriber disconnect/u); }); test("observe status renderer exposes existing-session refresh handoff and typed failure evidence", () => { const base = { ok: true, status: "running", command: "web-probe observe status", id: "webobs-fixture", node: "NC01", lane: "v03", next: {}, }; const rendered = withWebObserveStatusRendered({ ...base, observer: { processAlive: true, manifest: {}, heartbeat: {}, diagnostics: {}, commands: {}, exactCommand: { commandId: "cmd-existing-refresh", type: "validateExistingSessionRefresh", phase: "done", ok: true, result: { status: "passed", sessionId: "ses_existing", traceIds: ["trc_existing"], phase: "live", code: "refresh_live_handoff_validated", before: { messageCount: 2 }, after: { messageCount: 2 }, refreshReplay: { phase: "live", topic: "hwlab.event.v1", topicPartitions: [0], barrier: [{ partition: 0, endOffset: "18" }], counts: { matched: 8, replayed: 8, buffered: 1, bufferedDelivered: 1, liveDelivered: 0, deduplicated: 0 }, }, productSse: { browserSourceCount: 1, openCount: 1, connectedEventCount: 1, businessEventCount: 9, typedFailureCount: 0, livePhase: true }, forbiddenRequestCount: 0, reportSha256: "sha256:existing-refresh-report", screenshot: { sha256: "sha256:existing-refresh-image" }, }, }, tails: { samples: [], control: [], network: [] }, }, }); const text = String(rendered.renderedText); assert.match(text, /Workbench 既有 Session 刷新:/u); assert.match(text, /ses_existing.*trc_existing.*live.*refresh_live_handoff_validated.*2.*2.*1.*1.*1.*9.*0.*0/u); assert.match(text, /hwlab\.event\.v1.*0.*0:18.*8.*8.*1.*1.*0.*0.*true/u); assert.match(text, /sha256:existing-refresh-report.*sha256:existing-refresh-image/u); const failed = withWebObserveStatusRendered({ ...base, observer: { processAlive: true, manifest: {}, heartbeat: {}, diagnostics: {}, commands: {}, exactCommand: { commandId: "cmd-existing-refresh-failed", type: "validateExistingSessionRefresh", phase: "failed", ok: false, error: { message: "Kafka retention barrier gap", details: { sessionId: "ses_existing", traceIds: ["trc_existing"], phase: "retention-barrier", code: "retention_barrier_failed", failure: { phase: "retention-barrier", code: "retention_barrier_failed", streamPhase: "flushing", streamCode: "workbench_kafka_refresh_barrier_gap", message: "Kafka retention barrier gap", }, reportSha256: "sha256:failed-existing-refresh-report", }, }, }, tails: { samples: [], control: [], network: [] }, }, }); const failedText = String(failed.renderedText); assert.match(failedText, /Typed failure:/u); assert.match(failedText, /retention-barrier.*retention_barrier_failed.*flushing.*workbench_kafka_refresh_barrier_gap.*Kafka retention barrier gap/u); }); test("observe status renderer exposes bounded Workbench Kafka debug replay evidence", () => { const rendered = withWebObserveStatusRendered({ ok: true, status: "running", command: "web-probe observe status", id: "webobs-fixture", node: "NC01", lane: "v03", observer: { processAlive: true, manifest: { baseUrl: "http://internal.example.test", targetPath: "/workbench" }, heartbeat: { status: "running", sampleSeq: 12, commandSeq: 3, updatedAt: "2026-07-10T01:00:01.000Z" }, diagnostics: { effectiveLiveness: "alive", heartbeatStale: false }, commands: { pendingCount: 0, processingCount: 0, abandonedCount: 0, failedCount: 0 }, exactCommand: { commandId: "cmd-debug-replay", type: "validateWorkbenchKafkaDebugReplay", phase: "done", ok: true, result: { status: "passed", traceId: "trc_fixture", topic: "hwlab.event.debug.v1", groupId: "hwlab-v03-workbench-isolated-debug-123-fixture", phase: "terminal", code: "terminal_complete", receivedCount: 35, appliedCount: 35, layerCounts: { server: { scanned: 35, matched: 35, delivered: 35 }, client: { received: 35, decoded: 35, applied: 35 }, }, layerDom: { matchCount: 1, visibleCount: 1, connectedCount: 1, textBytes: 128, textHash: "sha256:layer-dom", }, layerDomStabilization: { stable: true, timedOut: false, sampleCount: 4, elapsedMs: 400, }, traceTimeline: { contract: "trace-sequence-authority-v1", sourceAuthorityRowCount: 35, readableSourceRowCount: 35, }, rawHwlabEventWindow: { available: true, samePage: true, unfilteredContractPresent: true, productEventSourceRequestCount: 0, counts: { received: 35, retained: 30, rejected: 0, evicted: 5, bytes: 8192 }, }, terminalStatus: "completed", reportSha256: "sha256:report", screenshot: { sha256: "sha256:image" }, }, }, tails: { samples: [], control: [], network: [] }, }, next: {}, }); const text = String(rendered.renderedText); assert.match(text, /Workbench Kafka debug replay:/u); assert.match(text, /trc_fixture.*hwlab\.event\.debug\.v1/u); assert.match(text, /terminal.*terminal_complete/u); assert.match(text, /35.*35.*completed.*sha256:image/u); assert.match(text, /SERVER_SCANNED.*CLIENT_APPLIED/u); assert.match(text, /分层 DOM 诊断:/u); assert.match(text, /1.*1.*1.*128.*sha256:layer-dom.*true.*false.*4.*400/u); assert.match(text, /trace-sequence-authority-v1.*35.*35.*true.*true.*true.*35.*30.*0.*5.*8192.*0/u); }); test("observe status renderer 展示限定作用域的产品 Trace 生命周期证据", () => { const rendered = withWebObserveStatusRendered({ ok: true, status: "running", command: "web-probe observe status", id: "webobs-fixture", node: "NC01", lane: "v03", observer: { processAlive: true, manifest: { baseUrl: "http://internal.example.test", targetPath: "/workbench" }, heartbeat: { status: "running", sampleSeq: 12, commandSeq: 4, updatedAt: "2026-07-10T01:00:01.000Z" }, diagnostics: { effectiveLiveness: "alive", heartbeatStale: false }, commands: { pendingCount: 0, processingCount: 0, abandonedCount: 0, failedCount: 0 }, exactCommand: { commandId: "cmd-trace-readable", type: "validateWorkbenchTraceReadability", phase: "done", ok: true, result: { status: "passed", traceId: "trc_fixture", startedDuringRunning: true, forcedExpansion: false, scope: { conversationCount: 1, productCardCount: 2, debugPanelCountInsideConversation: 0 }, disclosure: { openBefore: true, autoReadable: true, openAfterTerminal: true }, running: { observed: true, snapshot: { status: "running", rowCount: 2, sourceAuthorityRowCount: 2 } }, terminal: { stable: true, snapshot: { status: "completed", rowCount: 3, sourceAuthorityRowCount: 3, projectedAuthorityRowCount: 0 } }, runningRetention: { mode: "identity-overlap", passed: true, retainedRowCount: 2 }, retainedRunningRowCount: 2, reportSha256: "sha256:trace-report", screenshot: { sha256: "sha256:trace-image" }, }, }, tails: { samples: [], control: [], network: [] }, }, next: {}, }); const text = String(rendered.renderedText); assert.match(text, /Workbench 产品 Trace 可读性:/u); assert.match(text, /trc_fixture.*true.*true.*true.*true.*false.*true.*identity-overlap.*true.*2.*sha256:trace-report.*sha256:trace-image/u); assert.match(text, /CONVERSATION.*PRODUCT_CARDS.*DEBUG_IN_SCOPE/u); assert.match(text, /1.*2.*0.*running.*2.*2.*completed.*3.*3.*0.*true/u); }); test("observe status renderer 在命令失败时保留产品 Trace typed evidence", () => { const rendered = withWebObserveStatusRendered({ ok: true, status: "running", command: "web-probe observe status", id: "webobs-fixture", node: "NC01", lane: "v03", observer: { processAlive: true, manifest: {}, heartbeat: {}, diagnostics: {}, commands: {}, exactCommand: { commandId: "cmd-trace-failed", type: "validateWorkbenchTraceReadability", phase: "failed", ok: false, error: { message: "trace validation failed", details: { traceId: "trc_failed", startedDuringRunning: true, forcedExpansion: false, scope: { conversationCount: 1, productCardCount: 1, debugPanelCountInsideConversation: 0 }, disclosure: { openBefore: false, autoReadable: false, openAfterTerminal: true }, running: { observed: true, snapshot: { status: "running", rowCount: 2, sourceAuthorityRowCount: 2 } }, terminal: { stable: true, snapshot: { status: "completed", rowCount: 3, sourceAuthorityRowCount: 3, projectedAuthorityRowCount: 0 } }, runningRetention: { mode: "identity-overlap", passed: true, retainedRowCount: 2 }, retainedRunningRowCount: 2, failures: [{ code: "trace_not_auto_expanded" }], reportSha256: "sha256:failed-trace-report", }, }, }, tails: { samples: [], control: [], network: [] }, }, next: {}, }); const text = String(rendered.renderedText); assert.match(text, /trc_failed.*identity-overlap.*true.*2.*sha256:failed-trace-report/u); assert.match(text, /trace_not_auto_expanded/u); }); test("observe command renderer 在隔离重放失败时保留分层 typed evidence", () => { const rendered = withWebObserveCommandRendered({ ok: false, status: "failed", command: "web-probe observe command", id: "webobs-fixture", commandId: "cmd-debug-failed", observerCommand: { type: "validateWorkbenchKafkaDebugReplay" }, observer: { ok: false, error: { message: "debug trace missing", details: { traceId: "trc_debug_failed", topic: "hwlab.event.debug.v1", groupId: "hwlab-v03-workbench-isolated-debug-fixture", phase: "filter", code: "debug_trace_not_generated", receivedCount: 0, appliedCount: 0, layerCounts: { server: { scanned: 27, matched: 0, delivered: 0 }, client: { received: 0, decoded: 0, applied: 0 }, }, reportSha256: "sha256:failed-debug-report", }, }, }, control: { executionStatus: "failed" }, asyncTurn: {}, }); const text = String(rendered.renderedText); assert.match(text, /trc_debug_failed.*debug_trace_not_generated/u); assert.match(text, /27.*0.*0.*0.*0.*0/u); assert.match(text, /sha256:failed-debug-report/u); }); test("web-probe script render warns to promote repeated scripts into commands", () => { const commandPromotionHint = { schemaVersion: "unidesk.web-probe.command-promotion.v1", code: "prefer_repo_owned_typed_command", promotionRequiredBeforeRepeat: true, repoOwnedTypedCommand: false, sourceKind: "stdin", currentCommand: "web-probe script --node D601 --lane v03", preferredArtifact: "repo-owned-typed-command", preferredSurface: "web-probe observe command or a dedicated web-probe subcommand", action: "promote-before-repeat", message: "Before repeating this one-off script, promote the workflow to a repo-owned typed web-probe command.", valuesRedacted: true, }; const rendered = renderWebProbeScriptResult({ commandPromotionHint, 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", requiredAction: "promote-before-repeat", preferredArtifact: "repo-owned-typed-command", message: "web-probe script is a one-off exploration escape hatch; before repeating it, promote the workflow to a repo-owned typed command instead of rerunning temporary scripts.", }], hints: [ "Before rerunning this script, promote the workflow to a repo-owned typed `web-probe` command.", ], preferredCommands: { typedCommandCatalog: "bun scripts/cli.ts web-probe --help", 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 ?? ""); const marker = "UNIDESK_WEB_PROBE_COMMAND_PROMOTION_HINT "; const firstLine = text.split("\n", 1)[0] ?? ""; assert.ok(firstLine.startsWith(marker)); assert.deepEqual(JSON.parse(firstLine.slice(marker.length)), commandPromotionHint); assert.equal(Object.keys(rendered)[0], "commandPromotionHint"); assert.match(text, /WARNINGS/u); assert.match(text, /web_probe_script_ad_hoc_only/u); assert.match(text, /HINTS/u); assert.match(text, /repo-owned typed `web-probe` command/u); assert.match(text, /typedCommandCatalog: bun scripts\/cli\.ts web-probe --help/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); }); test("web-probe command governance distinguishes ad-hoc scripts from repo-owned generated commands", () => { const base = { action: "script" as const, node: "D601", lane: "v03", url: "https://hwlab.example.test", originName: "public" as const, originMode: "public" as const, originConfigPath: "config/hwlab-node-lanes.yaml#webProbe.origins.public", timeoutMs: 30000, viewport: "1920x1080", browserProxyMode: "auto" as const, browserProxyModeSource: "yaml-origin" as const, commandTimeoutSeconds: 60, scriptText: "return { ok: true };", scriptSource: { kind: "stdin" as const, path: null, byteCount: 20, sha256: "sha256:fixture", }, }; const adHoc = webProbeScriptCommandGovernance(base); assert.equal(adHoc.commandPromotionHint.promotionRequiredBeforeRepeat, true); assert.equal(adHoc.commandPromotionHint.action, "promote-before-repeat"); assert.equal(adHoc.warnings.length, 1); assert.equal(adHoc.warnings[0]?.requiredAction, "promote-before-repeat"); assert.match(adHoc.hints[0] ?? "", /repo-owned typed `web-probe` command/u); assert.deepEqual(Object.keys(adHoc.preferredCommands)[0], "typedCommandCatalog"); assert.ok(Object.values(adHoc.preferredCommands).every((command) => command.startsWith("bun scripts/cli.ts web-probe"))); const repoOwned = webProbeScriptCommandGovernance({ ...base, commandLabel: "web-probe opencode-smoke --node D601 --lane v03", suppressAdHocWarning: true, generatedHints: ["OpenCode smoke is a repo-owned typed web-probe command."], generatedPreferredCommands: { withExpectedText: "web-probe opencode-smoke --node D601 --lane v03 --expect-text ", }, scriptSource: { ...base.scriptSource, kind: "generated", path: "builtin:opencode-smoke", }, }); assert.equal(repoOwned.commandPromotionHint.promotionRequiredBeforeRepeat, false); assert.equal(repoOwned.commandPromotionHint.action, "reuse-repo-owned-typed-command"); assert.deepEqual(repoOwned.warnings, []); assert.doesNotMatch(repoOwned.hints.join("\n"), /temporary script|promote-before-repeat/iu); });