import assert from "node:assert/strict"; import { mkdtemp, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { test } from "bun:test"; import { hwlabRuntimeLaneSpecForNode } from "../hwlab-node-lanes"; import { buildWebObserveCommandVisibility, nodeWebProbeRealtimeFanoutProfiles, nodeWebProbeWorkbenchKafkaDebugReplay, nodeWebProbeWorkbenchTraceReadability, resolveWebObserveActionJson } from "./web-probe-observe-actions"; test("realtime fanout runner contract derives topics, groups, and independent capabilities from owning YAML", () => { const profiles = nodeWebProbeRealtimeFanoutProfiles(hwlabRuntimeLaneSpecForNode("v03", "NC01")); const expectedKafka = profiles["pure-kafka-live"]?.expectedKafka as Record; assert.deepEqual(expectedKafka.topics, { stdio: "codex-stdio.raw.v1", agentrun: "agentrun.event.v1", hwlab: "hwlab.event.v1", }); assert.deepEqual(expectedKafka.groups, { directPublish: "hwlab-v03-agentrun-event-direct-publish", transactionalProjector: "hwlab-v03-agentrun-event-projector", liveSse: "hwlab-v03-workbench-live-sse", }); assert.deepEqual(expectedKafka.capabilities, { directPublish: true, liveKafkaSse: true, kafkaRefreshReplay: true, transactionalProjector: false, projectionOutboxRelay: false, projectionRealtime: false, }); }); test("Workbench Kafka debug replay runner contract is derived from owning YAML", () => { assert.deepEqual(nodeWebProbeWorkbenchKafkaDebugReplay(hwlabRuntimeLaneSpecForNode("v03", "NC01")), { enabled: true, topic: "hwlab.event.debug.v1", groupPrefix: "hwlab-v03-workbench-isolated-debug", completionTimeoutMs: 30_000, productEventsPath: "/v1/workbench/events", debugEventsPath: "/v1/workbench/debug/kafka-sse/events", requestObservationQuietMs: 3_000, valuesRedacted: true, }); }); test("Workbench 产品 Trace 可读性 runner 合同完全来自独立 owning YAML", () => { assert.deepEqual(nodeWebProbeWorkbenchTraceReadability(hwlabRuntimeLaneSpecForNode("v03", "NC01")), { enabled: true, runningCardTimeoutMs: 15_000, disclosureActionTimeoutMs: 15_000, disclosureOpenTimeoutMs: 10_000, runningReadableTimeoutMs: 480_000, terminalTimeoutMs: 480_000, domStableQuietMs: 2_000, domStableTimeoutMs: 6_000, pollIntervalMs: 100, valuesRedacted: true, }); }); test("web observe command visibility does not equate control completion with async turn completion", () => { const visibility = buildWebObserveCommandVisibility({ commandType: "sendPrompt", exitCode: 0, commandResult: { ok: true, completedAt: "2026-07-10T01:00:01.000Z", result: { chatSubmit: { status: 202, traceId: "trc_fixture", otelTraceId: "otel_fixture", }, }, }, }); assert.equal(visibility.status, "control-completed"); assert.equal(visibility.control.executionStatus, "completed"); assert.equal(visibility.control.businessTurnTerminalImplied, false); assert.equal(visibility.asyncTurn.submissionStatus, "accepted"); assert.equal(visibility.asyncTurn.terminalStatus, null); assert.equal(visibility.asyncTurn.terminalObserved, false); assert.equal(visibility.asyncTurn.terminalStatusSource, "not-evaluated-by-control-command"); }); test("web observe action JSON recovery accepts concrete start contract", () => { const resolved = resolveWebObserveActionJson({ stdout: JSON.stringify({ ok: true, command: "web-probe-observe start", jobId: "webobs-fixture", stateDir: ".state/web-observe/JD01/v03/fixture", valuesRedacted: true, }), stderr: "", exitCode: 0, timedOut: false, }, "start"); assert.equal(resolved.source, "stdout"); assert.equal(resolved.parsed?.jobId, "webobs-fixture"); assert.equal(resolved.diagnostics.stdoutContractAccepted, true); }); test("web observe action JSON recovery reads dump wrapper for status contract", async () => { const dir = await mkdtemp(join(tmpdir(), "unidesk-web-observe-status-dump-")); const dumpPath = join(dir, "status.json"); await writeFile(dumpPath, JSON.stringify({ ok: true, status: "running", jobId: "webobs-fixture", heartbeat: { status: "running" }, diagnostics: { heartbeatStale: false }, }) + "\n"); const resolved = resolveWebObserveActionJson({ stdout: JSON.stringify({ ok: true, data: { outputTruncated: true, reason: "stdout-json-bytes-exceeded-threshold", dump: { path: dumpPath }, }, }), stderr: "", exitCode: 0, timedOut: false, }, "status"); assert.equal(resolved.source, "dump"); assert.equal(resolved.parsed?.jobId, "webobs-fixture"); assert.equal(resolved.diagnostics.stdoutKind, "dump-wrapper"); assert.equal(resolved.diagnostics.dumpPath, dumpPath); }); test("web observe action JSON recovery reads SSH truncation summary dump for command contract", async () => { const dir = await mkdtemp(join(tmpdir(), "unidesk-web-observe-command-dump-")); const dumpPath = join(dir, "command.json"); await writeFile(dumpPath, JSON.stringify({ ok: true, queued: false, waitTimedOut: false, commandId: "cmd-fixture", }) + "\n"); const summary = { code: "ssh-truncation-summary", stdout: { stream: "stdout", dumpPath, forwardedBytes: 10240, thresholdBytes: 10240, }, }; const resolved = resolveWebObserveActionJson({ stdout: `UNIDESK_SSH_TRUNCATION_SUMMARY ${JSON.stringify(summary)}\n`, stderr: "", exitCode: 0, timedOut: false, }, "command"); assert.equal(resolved.source, "dump"); assert.equal(resolved.parsed?.commandId, "cmd-fixture"); assert.equal(resolved.diagnostics.stdoutKind, "ssh-truncation-summary"); assert.equal(resolved.diagnostics.dumpPath, dumpPath); }); test("web observe action JSON recovery rejects ok-only action contract", () => { const resolved = resolveWebObserveActionJson({ stdout: JSON.stringify({ ok: true }), stderr: "", exitCode: 0, timedOut: false, }, "start"); assert.equal(resolved.parsed, null); assert.equal(resolved.source, null); assert.equal(resolved.diagnostics.stdoutKind, "json"); assert.equal(resolved.diagnostics.fallbackReason, "stdout-json-contract-invalid"); assert.equal(resolved.diagnostics.stdoutContractAccepted, false); });