479 lines
19 KiB
TypeScript
479 lines
19 KiB
TypeScript
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 <observerId> --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 <assistant-substring>",
|
|
},
|
|
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);
|
|
});
|