Files
pikasTech-unidesk/scripts/src/hwlab-node-web-observe-runner-kafka-debug-replay-source.ts
T
2026-07-10 18:00:24 +02:00

713 lines
35 KiB
TypeScript

// SPEC: PJ2026-0104010803 Workbench realtime visibility.
// Responsibility: Typed, isolated Workbench Kafka debug replay validation inside the persistent Web observer.
export function nodeWebObserveRunnerKafkaDebugReplaySource(): string {
return String.raw`
function parseWorkbenchKafkaDebugReplayConfig(raw) {
if (!raw) return null;
let parsed;
try {
parsed = JSON.parse(raw);
} catch (error) {
throw new Error("UNIDESK_WEB_OBSERVE_WORKBENCH_KAFKA_DEBUG_REPLAY_JSON is invalid JSON: " + (error instanceof Error ? error.message : String(error)));
}
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
throw new Error("UNIDESK_WEB_OBSERVE_WORKBENCH_KAFKA_DEBUG_REPLAY_JSON must be an object");
}
const topic = String(parsed.topic || "").trim();
const groupPrefix = String(parsed.groupPrefix || "").trim();
const productEventsPath = String(parsed.productEventsPath || "").trim();
const debugEventsPath = String(parsed.debugEventsPath || "").trim();
const completionTimeoutMs = Number(parsed.completionTimeoutMs);
const requestObservationQuietMs = Number(parsed.requestObservationQuietMs);
if (!/^[A-Za-z0-9._-]+$/u.test(topic)) throw new Error("Workbench Kafka debug replay topic is missing or invalid in the owning YAML");
if (!/^[A-Za-z0-9._-]+$/u.test(groupPrefix)) throw new Error("Workbench Kafka debug replay groupPrefix is missing or invalid in the owning YAML");
if (!productEventsPath.startsWith("/") || productEventsPath.includes("?") || productEventsPath.includes("#")) {
throw new Error("Workbench Kafka debug replay productEventsPath must be an absolute path without query or fragment in the owning YAML");
}
if (!debugEventsPath.startsWith("/") || debugEventsPath.includes("?") || debugEventsPath.includes("#")) {
throw new Error("Workbench Kafka debug replay debugEventsPath must be an absolute path without query or fragment in the owning YAML");
}
if (!Number.isInteger(completionTimeoutMs) || completionTimeoutMs < 1000 || completionTimeoutMs > 120000) {
throw new Error("Workbench Kafka debug replay completionTimeoutMs must be 1000-120000 in the owning YAML");
}
if (!Number.isInteger(requestObservationQuietMs) || requestObservationQuietMs < 100 || requestObservationQuietMs > 30000) {
throw new Error("Workbench Kafka debug replay requestObservationQuietMs must be 100-30000 in the owning YAML");
}
return {
enabled: parsed.enabled === true,
topic,
groupPrefix,
productEventsPath,
debugEventsPath,
completionTimeoutMs,
requestObservationQuietMs,
valuesRedacted: true
};
}
function workbenchKafkaDebugReplayCounts(value) {
const match = String(value || "").match(/(\d+)\s*\/\s*(\d+)\s+applied/iu);
return match ? { appliedCount: Number(match[1]), receivedCount: Number(match[2]) } : null;
}
function workbenchKafkaDebugReplayNamedCount(value, name) {
const match = String(value || "").match(new RegExp("(?:^|\\s|·)" + name + "\\s*=\\s*(-|\\d+)", "iu"));
if (!match) return undefined;
return match[1] === "-" ? null : Number(match[1]);
}
function workbenchKafkaDebugReplayLayerCounts(value) {
const text = String(value || "").replace(/\s+/gu, " ").trim();
if (!/\bserver\b/iu.test(text) || !/\bclient\b/iu.test(text)) return null;
return {
server: {
scanned: workbenchKafkaDebugReplayNamedCount(text, "scanned"),
matched: workbenchKafkaDebugReplayNamedCount(text, "matched"),
delivered: workbenchKafkaDebugReplayNamedCount(text, "delivered")
},
client: {
received: workbenchKafkaDebugReplayNamedCount(text, "received"),
decoded: workbenchKafkaDebugReplayNamedCount(text, "decoded"),
applied: workbenchKafkaDebugReplayNamedCount(text, "applied")
},
valuesRedacted: true
};
}
function workbenchKafkaDebugReplayRawCounts(value) {
const text = String(value || "").replace(/\s+/gu, " ").trim();
if (!/\breceived\s*=/iu.test(text) || !/\bretained\s*=/iu.test(text)) return null;
return {
received: workbenchKafkaDebugReplayNamedCount(text, "received"),
retained: workbenchKafkaDebugReplayNamedCount(text, "retained"),
decoded: workbenchKafkaDebugReplayNamedCount(text, "decoded"),
rejected: workbenchKafkaDebugReplayNamedCount(text, "rejected"),
evicted: workbenchKafkaDebugReplayNamedCount(text, "evicted"),
bytes: workbenchKafkaDebugReplayNamedCount(text, "bytes"),
valuesRedacted: true
};
}
function workbenchKafkaDebugReplayTypedOutcome(input) {
const status = String(input?.status || "unknown").trim() || "unknown";
const errorText = String(input?.errorText || "").trim();
const source = input?.layerCounts ? "typed-dom-v2" : "legacy-dom";
const codeMatch = errorText.match(/\b(consumer_not_ready|source_trace_missing|producer_not_invoked|topic_no_append_for_replay|record_parse_rejected|records_scanned_but_filter_mismatch|sse_write_failed|transport_failed|decoder_rejected|reducer_rejected|terminal_missing)\b/u);
const phaseByCode = {
consumer_not_ready: "consumer",
source_trace_missing: "producer",
producer_not_invoked: "producer",
topic_no_append_for_replay: "kafka",
record_parse_rejected: "server",
records_scanned_but_filter_mismatch: "server",
sse_write_failed: "server",
transport_failed: "client",
decoder_rejected: "client",
reducer_rejected: "client",
terminal_missing: "terminal"
};
if (codeMatch) return { phase: phaseByCode[codeMatch[1]], code: codeMatch[1], source, valuesRedacted: true };
if (status === "completed") return { phase: "terminal", code: "terminal_complete", source, valuesRedacted: true };
if (status === "incomplete" && /terminal|终态|未观测/iu.test(errorText)) {
return { phase: "terminal", code: "terminal_missing", source, valuesRedacted: true };
}
if (status === "incomplete") return { phase: "ui", code: "replay_incomplete", source, valuesRedacted: true };
if (status === "error") return { phase: "ui", code: "replay_error", source, valuesRedacted: true };
if (status === "closed") return { phase: "client", code: "stream_closed", source, valuesRedacted: true };
return { phase: "ui", code: "replay_" + status.replace(/[^a-z0-9_-]+/giu, "_"), source, valuesRedacted: true };
}
async function validateWorkbenchKafkaDebugReplay(command) {
const config = workbenchKafkaDebugReplay;
if (!config || config.enabled !== true) throw new Error("validateWorkbenchKafkaDebugReplay is not enabled by the owning YAML");
const reportDir = path.join(stateDir, "artifacts", "workbench-kafka-debug-replay", safeId(command.id));
const report = {
ok: false,
contract: "web-probe-workbench-kafka-debug-replay-v2",
commandId: command.id,
expected: {
topic: config.topic,
groupPrefix: config.groupPrefix,
productEventsPath: config.productEventsPath,
debugEventsPath: config.debugEventsPath,
completionTimeoutMs: config.completionTimeoutMs,
requestObservationQuietMs: config.requestObservationQuietMs,
valuesRedacted: true
},
origin,
startedAt: new Date().toISOString(),
valuesRedacted: true
};
const requests = [];
const productEventSourceRequests = [];
let requestPage = null;
let requestPhase = "setup";
const requestListener = (request) => {
let url;
try { url = new URL(request.url()); } catch { return; }
if (url.pathname === config.productEventsPath) {
productEventSourceRequests.push({ method: request.method(), path: url.pathname, phase: requestPhase, valuesRedacted: true });
return;
}
if (url.pathname !== config.debugEventsPath) return;
requests.push({
method: request.method(),
path: url.pathname,
stream: url.searchParams.get("stream"),
traceId: url.searchParams.get("traceId"),
fromBeginning: url.searchParams.get("fromBeginning"),
phase: requestPhase,
valuesRedacted: true
});
};
try {
const controlRecovery = await ensureControlPageResponsiveForCommand("validateWorkbenchKafkaDebugReplay");
const snapshot = await workbenchSessionSnapshot();
const currentPath = safeUrlPath(currentPageUrl()) || "";
if (!isWorkbenchPathname(currentPath)) throw new Error("validateWorkbenchKafkaDebugReplay requires the original Workbench page");
const sessionId = snapshot?.activeSessionId || snapshot?.routeSessionId || routeSessionIdFromUrl(currentPageUrl());
if (!sessionId) throw new Error("validateWorkbenchKafkaDebugReplay requires an existing Workbench session");
requestPage = page;
requestPage.on("request", requestListener);
const toggle = page.locator('[data-testid="workbench-kafka-debug-toggle"]').first();
const toggleVisible = await toggle.waitFor({ state: "visible", timeout: 10000 }).then(() => true).catch(() => false);
if (!toggleVisible) throw new Error("Workbench Kafka debug toggle is not visible; verify the owning YAML capability and admin session");
if (await toggle.isDisabled()) throw new Error("Workbench Kafka debug toggle is disabled; verify the YAML capability and admin session");
const toggleCount = await page.locator('[data-testid="workbench-kafka-debug-toggle"]').count();
if (toggleCount !== 1) throw new Error("Workbench Kafka debug toggle count is " + toggleCount + ", expected 1");
const toggleCheckedBefore = await toggle.isChecked();
if (!toggleCheckedBefore) await toggle.check({ timeout: 10000 });
const panel = page.locator('[data-testid="workbench-kafka-debug-panel"]').first();
const clearButton = page.locator('[data-testid="workbench-kafka-debug-clear"]').first();
const replayButton = page.locator('[data-testid="workbench-kafka-debug-replay"]').first();
const countsNode = page.locator('[data-testid="workbench-kafka-debug-counts"]').first();
await panel.waitFor({ state: "visible", timeout: 10000 });
await clearButton.waitFor({ state: "visible", timeout: 10000 });
await replayButton.waitFor({ state: "visible", timeout: 10000 });
await clearButton.click({ timeout: 10000 });
const cleared = await workbenchKafkaDebugReplayWaitForCleared(panel, countsNode, 5000);
const before = await workbenchKafkaDebugReplayPanelSnapshot(panel, countsNode);
if (!/^trc_[A-Za-z0-9_-]+$/u.test(before.traceId || "")) throw new Error("Workbench Kafka debug replay requires the current traceId");
if (await replayButton.isDisabled()) throw new Error("Workbench Kafka debug replay button is disabled for traceId " + before.traceId);
requestPhase = "debug-replay";
await replayButton.click({ timeout: 10000 });
const terminalStatus = await workbenchKafkaDebugReplayWaitForTerminal(panel, config.completionTimeoutMs);
const after = await workbenchKafkaDebugReplayWaitForStablePanel(
panel,
countsNode,
config.requestObservationQuietMs
);
Object.assign(report, {
sessionId,
traceId: after.traceId,
topic: after.topic,
groupId: after.groupId,
terminalStatus,
phase: after.typedOutcome.phase,
code: after.typedOutcome.code,
typedOutcome: after.typedOutcome,
layerCounts: after.layerCounts,
layerDom: after.layerDom,
layerDomStabilization: after.layerDomStabilization,
legacyCounts: after.counts,
valuesRedacted: true
});
const traceTimeline = await workbenchKafkaDebugReplayTraceTimelineSnapshot(panel);
report.traceTimeline = traceTimeline;
const rawHwlabEventWindow = await workbenchKafkaDebugReplayInspectRawWindow({
panel,
config,
pathBefore: currentPath,
productEventSourceRequests,
setRequestPhase: (value) => { requestPhase = value; }
});
report.rawHwlabEventWindow = rawHwlabEventWindow;
const matchingRequest = requests.find((item) => item.stream === "hwlab-debug" && item.traceId === before.traceId && item.fromBeginning === "true") || null;
const replayEvidence = {
sessionId,
traceId: after.traceId,
topic: after.topic,
groupId: after.groupId,
terminalStatus,
phase: after.typedOutcome.phase,
code: after.typedOutcome.code,
typedOutcome: after.typedOutcome,
layerCounts: after.layerCounts,
layerDom: after.layerDom,
layerDomStabilization: after.layerDomStabilization,
legacyCounts: after.counts,
traceTimeline,
rawHwlabEventWindow,
productEventSource: {
path: config.productEventsPath,
additionalRequestCountDuringTabSwitch: rawHwlabEventWindow.productEventSourceRequestCount,
requests: productEventSourceRequests.slice(-10),
valuesRedacted: true
},
valuesRedacted: true
};
Object.assign(report, replayEvidence);
if (terminalStatus !== "completed") throw workbenchKafkaDebugReplayEvidenceError("Workbench Kafka debug replay ended with status " + terminalStatus, replayEvidence);
if (!matchingRequest) throw workbenchKafkaDebugReplayEvidenceError("Workbench Kafka debug replay did not issue the expected trace-scoped fromBeginning request", replayEvidence);
if (after.traceId !== before.traceId) throw workbenchKafkaDebugReplayEvidenceError("Workbench Kafka debug replay traceId changed during replay", replayEvidence);
if (after.topic !== config.topic) throw workbenchKafkaDebugReplayEvidenceError("Workbench Kafka debug replay topic mismatch: " + (after.topic || "missing"), replayEvidence);
if (!after.groupId || !after.groupId.startsWith(config.groupPrefix + "-")) {
throw workbenchKafkaDebugReplayEvidenceError("Workbench Kafka debug replay groupId does not use the YAML group prefix", replayEvidence);
}
if (!after.deliveryText.includes("debug-replay") || !after.deliveryText.includes("fromBeginning=true")) {
throw workbenchKafkaDebugReplayEvidenceError("Workbench Kafka debug replay delivery contract is missing", replayEvidence);
}
if (!after.counts || after.counts.receivedCount <= 0 || after.counts.appliedCount <= 0) {
throw workbenchKafkaDebugReplayEvidenceError("Workbench Kafka debug replay received/applied counts must both be greater than zero", replayEvidence);
}
if (after.typedOutcome.phase !== "terminal" || after.typedOutcome.code !== "terminal_complete") {
throw workbenchKafkaDebugReplayEvidenceError("Workbench Kafka debug replay typed terminal contract is not terminal_complete", replayEvidence);
}
if (after.layerDomStabilization?.stable !== true) {
throw workbenchKafkaDebugReplayEvidenceError("Workbench Kafka debug replay layer DOM did not stabilize after terminal status", replayEvidence);
}
workbenchKafkaDebugReplayValidateLayerCounts(after.layerCounts, replayEvidence);
workbenchKafkaDebugReplayValidateTraceTimeline(traceTimeline, replayEvidence);
workbenchKafkaDebugReplayValidateRawWindow(rawHwlabEventWindow, replayEvidence);
const screenshot = await captureScreenshot("workbench-kafka-debug-replay-" + safeId(command.id));
Object.assign(report, {
ok: true,
status: "passed",
completedAt: new Date().toISOString(),
groupPrefix: config.groupPrefix,
receivedCount: after.counts.receivedCount,
appliedCount: after.counts.appliedCount,
toggle: { available: true, enabled: true, checkedBefore: toggleCheckedBefore, checkedAfter: await toggle.isChecked(), valuesRedacted: true },
clear: cleared,
request: matchingRequest,
screenshot: { path: screenshot.path, sha256: screenshot.sha256, valuesRedacted: true },
controlRecovery,
valuesRedacted: true
});
const reportArtifact = await workbenchKafkaDebugReplayWriteReport(reportDir, report);
report.reportPath = reportArtifact.path;
report.reportSha256 = reportArtifact.sha256;
await appendJsonl(files.control, eventRecord("workbench-kafka-debug-replay-validated", {
sessionId,
traceId: report.traceId,
topic: report.topic,
groupId: report.groupId,
phase: report.phase,
code: report.code,
receivedCount: report.receivedCount,
appliedCount: report.appliedCount,
layerCounts: report.layerCounts,
layerDom: report.layerDom,
layerDomStabilization: report.layerDomStabilization,
traceTimeline: report.traceTimeline,
rawHwlabEventWindow: report.rawHwlabEventWindow,
reportPath: report.reportPath,
reportSha256: report.reportSha256,
screenshot: report.screenshot,
valuesRedacted: true
}));
return {
ok: true,
status: "passed",
sessionId,
traceId: report.traceId,
topic: report.topic,
groupId: report.groupId,
groupPrefix: report.groupPrefix,
phase: report.phase,
code: report.code,
receivedCount: report.receivedCount,
appliedCount: report.appliedCount,
layerCounts: report.layerCounts,
layerDom: report.layerDom,
layerDomStabilization: report.layerDomStabilization,
traceTimeline: report.traceTimeline,
rawHwlabEventWindow: report.rawHwlabEventWindow,
productEventSource: report.productEventSource,
terminalStatus,
reportPath: report.reportPath,
reportSha256: report.reportSha256,
screenshot: report.screenshot,
valuesRedacted: true
};
} catch (error) {
Object.assign(report, {
ok: false,
status: "failed",
completedAt: new Date().toISOString(),
failure: errorSummary(error),
requests,
valuesRedacted: true
});
if (!report.screenshot) {
report.screenshot = await captureScreenshot("workbench-kafka-debug-replay-failed-" + safeId(command.id))
.then((screenshot) => ({ path: screenshot.path, sha256: screenshot.sha256, valuesRedacted: true }))
.catch((screenshotError) => ({ available: false, error: errorSummary(screenshotError), valuesRedacted: true }));
}
const reportArtifact = await workbenchKafkaDebugReplayWriteReport(reportDir, report).catch(() => null);
const wrapped = error instanceof Error ? error : new Error(String(error));
wrapped.details = {
...(wrapped.details || {}),
traceId: report.traceId || wrapped.details?.traceId || null,
topic: report.topic || wrapped.details?.topic || null,
groupId: report.groupId || wrapped.details?.groupId || null,
terminalStatus: report.terminalStatus || wrapped.details?.terminalStatus || null,
receivedCount: report.receivedCount ?? report.legacyCounts?.receivedCount ?? wrapped.details?.receivedCount ?? null,
appliedCount: report.appliedCount ?? report.legacyCounts?.appliedCount ?? wrapped.details?.appliedCount ?? null,
phase: report.phase || wrapped.details?.phase || null,
code: report.code || wrapped.details?.code || null,
layerCounts: report.layerCounts || wrapped.details?.layerCounts || null,
layerDom: report.layerDom || wrapped.details?.layerDom || null,
layerDomStabilization: report.layerDomStabilization || wrapped.details?.layerDomStabilization || null,
traceTimeline: report.traceTimeline || wrapped.details?.traceTimeline || null,
rawHwlabEventWindow: report.rawHwlabEventWindow || wrapped.details?.rawHwlabEventWindow || null,
screenshot: report.screenshot || wrapped.details?.screenshot || null,
reportPath: reportArtifact?.path || null,
reportSha256: reportArtifact?.sha256 || null,
valuesRedacted: true
};
throw wrapped;
} finally {
if (requestPage) requestPage.off("request", requestListener);
}
}
async function workbenchKafkaDebugReplayWaitForCleared(panel, countsNode, timeoutMs) {
const deadline = Date.now() + timeoutMs;
while (Date.now() <= deadline) {
const status = await panel.getAttribute("data-replay-status");
const counts = workbenchKafkaDebugReplayCounts(await countsNode.textContent());
if (status === "idle" && counts?.appliedCount === 0 && counts?.receivedCount === 0) {
return { status, ...counts, valuesRedacted: true };
}
await sleep(100);
}
throw new Error("Workbench Kafka debug panel did not clear to idle 0/0");
}
async function workbenchKafkaDebugReplayWaitForTerminal(panel, timeoutMs) {
const deadline = Date.now() + timeoutMs;
while (Date.now() <= deadline) {
const status = await panel.getAttribute("data-replay-status");
if (status === "completed") return status;
if (status === "incomplete" || status === "error" || status === "closed") return status;
await sleep(200);
}
throw new Error("Workbench Kafka debug replay did not reach a terminal panel status within " + timeoutMs + "ms");
}
async function workbenchKafkaDebugReplayWaitForTabSelected(tab, timeoutMs) {
const deadline = Date.now() + timeoutMs;
while (Date.now() <= deadline) {
if (await tab.getAttribute("aria-selected") === "true") return true;
await sleep(50);
}
return false;
}
async function workbenchKafkaDebugReplayPanelSnapshot(panel, countsNode) {
const contract = await panel.locator('[data-testid="workbench-isolated-debug-contract"]').evaluate((root) => {
const values = {};
for (const span of Array.from(root.querySelectorAll("span"))) {
const key = span.querySelector("b")?.textContent?.trim() || "";
const value = span.querySelector("code")?.textContent?.trim() || "";
if (key) values[key] = value;
}
return { values, text: root.textContent?.replace(/\s+/gu, " ").trim() || "" };
});
const status = await panel.getAttribute("data-replay-status");
const layerDom = await workbenchKafkaDebugReplayLayerDomSnapshot(panel);
const layerText = layerDom.text || "";
const errorNode = panel.locator(".isolated-debug-error").first();
const errorText = await errorNode.count() > 0 ? String(await errorNode.textContent() || "").trim() : "";
const layerCounts = workbenchKafkaDebugReplayLayerCounts(layerText);
return {
status,
traceId: contract.values.traceId || null,
topic: contract.values.topic || null,
groupId: contract.values.group || null,
deliveryText: contract.text,
counts: workbenchKafkaDebugReplayCounts(await countsNode.textContent()),
layerCounts,
layerDom,
error: errorText ? { textHash: sha256Text(errorText), textPreview: truncate(errorText, 160), textBytes: Buffer.byteLength(errorText), valuesRedacted: true } : null,
typedOutcome: workbenchKafkaDebugReplayTypedOutcome({ status, errorText, layerCounts }),
valuesRedacted: true
};
}
async function workbenchKafkaDebugReplayLayerDomSnapshot(panel) {
const selector = '[data-testid="workbench-kafka-debug-layer-counts"]';
const nodes = await panel.locator(selector).evaluateAll((elements) => elements.map((element) => {
const style = getComputedStyle(element);
const rect = element.getBoundingClientRect();
const text = element.textContent?.replace(/\s+/gu, " ").trim() || "";
return {
connected: element.isConnected,
visible: style.display !== "none" && style.visibility !== "hidden" && rect.width > 0 && rect.height > 0,
text
};
}));
const selectedIndex = nodes.findIndex((node) => node.visible);
const selected = nodes[selectedIndex >= 0 ? selectedIndex : 0] || null;
const text = selected?.text || "";
return {
selector,
matchCount: nodes.length,
visibleCount: nodes.filter((node) => node.visible).length,
connectedCount: nodes.filter((node) => node.connected).length,
selectedIndex: selectedIndex >= 0 ? selectedIndex : nodes.length > 0 ? 0 : null,
text,
textPresent: text.length > 0,
textBytes: Buffer.byteLength(text),
textHash: sha256Text(text),
textPreview: truncate(text, 240),
valuesRedacted: true
};
}
async function workbenchKafkaDebugReplayWaitForStablePanel(panel, countsNode, timeoutMs) {
const startedAtMs = Date.now();
const deadline = startedAtMs + timeoutMs;
const stableQuietMs = Math.min(500, Math.max(100, Math.floor(timeoutMs / 4)));
let latest = await workbenchKafkaDebugReplayPanelSnapshot(panel, countsNode);
let previousSignature = null;
let stableSinceMs = null;
let sampleCount = 0;
while (Date.now() <= deadline) {
latest = await workbenchKafkaDebugReplayPanelSnapshot(panel, countsNode);
sampleCount += 1;
const signature = JSON.stringify({
status: latest.status,
counts: latest.counts,
layerCounts: latest.layerCounts,
layerTextHash: latest.layerDom?.textHash,
matchCount: latest.layerDom?.matchCount,
visibleCount: latest.layerDom?.visibleCount
});
const domReady = latest.layerDom?.matchCount === 1
&& latest.layerDom?.visibleCount === 1
&& latest.layerDom?.textPresent === true
&& latest.layerCounts !== null;
if (domReady && signature === previousSignature) {
stableSinceMs ??= Date.now();
if (Date.now() - stableSinceMs >= stableQuietMs) {
return {
...latest,
layerDomStabilization: {
stable: true,
timedOut: false,
sampleCount,
elapsedMs: Date.now() - startedAtMs,
stableQuietMs,
valuesRedacted: true
}
};
}
} else {
stableSinceMs = null;
}
previousSignature = signature;
await sleep(100);
}
return {
...latest,
layerDomStabilization: {
stable: false,
timedOut: true,
sampleCount,
elapsedMs: Date.now() - startedAtMs,
stableQuietMs,
valuesRedacted: true
}
};
}
async function workbenchKafkaDebugReplayTraceTimelineSnapshot(panel) {
return panel.evaluate((root) => {
const rows = Array.from(root.querySelectorAll('[data-testid="trace-render-row"]'));
const authorityRows = rows.filter((row) => row.hasAttribute("data-event-seq-authority"));
const sourceRows = authorityRows.filter((row) => row.getAttribute("data-event-seq-authority") === "source");
const projectedRows = authorityRows.filter((row) => row.getAttribute("data-event-seq-authority") === "projected");
const readableSourceRows = sourceRows.filter((row) => Boolean(row.textContent?.replace(/\s+/gu, " ").trim()));
return {
contract: authorityRows.length > 0 ? "trace-sequence-authority-v1" : "legacy-ui",
authorityAvailable: authorityRows.length > 0,
rowCount: rows.length,
sourceAuthorityRowCount: sourceRows.length,
projectedAuthorityRowCount: projectedRows.length,
readableSourceRowCount: readableSourceRows.length,
samples: readableSourceRows.slice(0, 8).map((row) => ({
seq: row.getAttribute("data-event-seq"),
authority: row.getAttribute("data-event-seq-authority"),
kind: row.getAttribute("data-event-kind"),
status: row.getAttribute("data-event-status"),
terminal: row.getAttribute("data-terminal") === "true",
textBytes: new TextEncoder().encode(row.textContent?.replace(/\s+/gu, " ").trim() || "").byteLength
})),
valuesRedacted: true
};
});
}
async function workbenchKafkaDebugReplayInspectRawWindow(input) {
const rawTabLocator = input.panel.locator('[data-testid="workbench-kafka-debug-tab-raw"]');
const rawTabCount = await rawTabLocator.count();
if (rawTabCount === 0) {
return {
available: false,
contract: "legacy-ui",
reason: "raw-hwlab-event-tab-not-present",
productEventsPath: input.config.productEventsPath,
productEventSourceRequestCount: 0,
valuesRedacted: true
};
}
if (rawTabCount !== 1) throw new Error("Workbench raw HWLAB Event tab count is " + rawTabCount + ", expected 1");
const traceTabLocator = input.panel.locator('[data-testid="workbench-kafka-debug-tab-trace"]');
const traceTabCount = await traceTabLocator.count();
if (traceTabCount !== 1) throw new Error("Workbench Trace debug tab count is " + traceTabCount + ", expected 1");
const rawTab = rawTabLocator.first();
const traceTab = traceTabLocator.first();
const requestStart = input.productEventSourceRequests.length;
const pageUrlBefore = currentPageUrl();
input.setRequestPhase("raw-tab-switch");
await rawTab.click({ timeout: 10000 });
if (!await workbenchKafkaDebugReplayWaitForTabSelected(rawTab, 5000)) throw new Error("Workbench raw HWLAB Event tab did not become selected");
const rawCountsNode = input.panel.locator('[data-testid="workbench-raw-hwlab-counts"]').first();
const rawTextNode = input.panel.locator('[data-testid="workbench-raw-hwlab-text"]').first();
const rawContractNode = input.panel.locator('[data-testid="workbench-raw-hwlab-contract"]').first();
await rawCountsNode.waitFor({ state: "visible", timeout: 5000 });
await rawTextNode.waitFor({ state: "visible", timeout: 5000 });
await rawContractNode.waitFor({ state: "visible", timeout: 5000 });
await sleep(input.config.requestObservationQuietMs);
const counts = workbenchKafkaDebugReplayRawCounts(await rawCountsNode.textContent());
const rawText = await rawTextNode.inputValue();
const contractText = String(await rawContractNode.textContent() || "").replace(/\s+/gu, " ").trim();
input.setRequestPhase("trace-tab-restore");
await traceTab.click({ timeout: 10000 });
if (!await workbenchKafkaDebugReplayWaitForTabSelected(traceTab, 5000)) throw new Error("Workbench Trace debug tab did not restore after raw inspection");
await sleep(input.config.requestObservationQuietMs);
input.setRequestPhase("complete");
const pageUrlAfter = currentPageUrl();
const productRequests = input.productEventSourceRequests.slice(requestStart);
return {
available: true,
contract: "raw-hwlab-event-window-v1",
pageId,
samePage: safeUrlPath(pageUrlBefore) === input.pathBefore && safeUrlPath(pageUrlAfter) === input.pathBefore,
pathBefore: input.pathBefore,
pathAfter: safeUrlPath(pageUrlAfter),
productEventsPath: input.config.productEventsPath,
productEventSourceRequestCount: productRequests.length,
productEventSourceRequests: productRequests.slice(-10),
sourceContractPresent: contractText.includes("EventSource") && contractText.includes("hwlab.event.v1"),
unfilteredContractPresent: contractText.includes("no client event filter"),
counts,
textBytes: Buffer.byteLength(rawText),
textHash: sha256Text(rawText),
textPresent: rawText.length > 0,
valuesRedacted: true
};
}
function workbenchKafkaDebugReplayValidateLayerCounts(layerCounts, evidence) {
if (!layerCounts) {
throw workbenchKafkaDebugReplayEvidenceError("Workbench Kafka debug replay typed server/client count contract is missing", evidence);
}
const values = [
layerCounts.server.scanned,
layerCounts.server.matched,
layerCounts.server.delivered,
layerCounts.client.received,
layerCounts.client.decoded,
layerCounts.client.applied
];
if (values.some((value) => !Number.isInteger(value) || value <= 0)) {
throw workbenchKafkaDebugReplayEvidenceError("Workbench Kafka debug replay typed server/client counts must all be greater than zero", evidence);
}
if (layerCounts.server.delivered !== layerCounts.client.received) {
throw workbenchKafkaDebugReplayEvidenceError("Workbench Kafka debug replay server delivered and client received counts differ", evidence);
}
if (layerCounts.server.scanned < layerCounts.server.matched || layerCounts.server.matched !== layerCounts.server.delivered) {
throw workbenchKafkaDebugReplayEvidenceError("Workbench Kafka debug replay server scanned/matched/delivered counts are inconsistent", evidence);
}
if (layerCounts.client.received !== layerCounts.client.decoded || layerCounts.client.decoded !== layerCounts.client.applied) {
throw workbenchKafkaDebugReplayEvidenceError("Workbench Kafka debug replay client received/decoded/applied counts differ", evidence);
}
if (evidence?.legacyCounts && (evidence.legacyCounts.receivedCount !== layerCounts.client.received || evidence.legacyCounts.appliedCount !== layerCounts.client.applied)) {
throw workbenchKafkaDebugReplayEvidenceError("Workbench Kafka debug replay legacy reducer counts differ from the typed client layer", evidence);
}
}
function workbenchKafkaDebugReplayValidateTraceTimeline(traceTimeline, evidence) {
if (!traceTimeline.authorityAvailable) {
throw workbenchKafkaDebugReplayEvidenceError("Workbench Trace timeline source-authority contract is missing", evidence);
}
if (traceTimeline.sourceAuthorityRowCount <= 0 || traceTimeline.readableSourceRowCount <= 0) {
throw workbenchKafkaDebugReplayEvidenceError("Workbench Trace timeline did not expose a readable source-authority row", evidence);
}
}
function workbenchKafkaDebugReplayValidateRawWindow(rawWindow, evidence) {
if (!rawWindow.available) {
throw workbenchKafkaDebugReplayEvidenceError("Workbench raw HWLAB Event window contract is missing", evidence);
}
if (!rawWindow.samePage) throw workbenchKafkaDebugReplayEvidenceError("Workbench raw HWLAB Event inspection navigated away from the original page", evidence);
if (!rawWindow.sourceContractPresent) throw workbenchKafkaDebugReplayEvidenceError("Workbench raw HWLAB Event window does not declare the existing product EventSource contract", evidence);
if (!rawWindow.unfilteredContractPresent) throw workbenchKafkaDebugReplayEvidenceError("Workbench raw HWLAB Event window does not declare the unfiltered ingress contract", evidence);
if (!rawWindow.counts) throw workbenchKafkaDebugReplayEvidenceError("Workbench raw HWLAB Event counts are unreadable", evidence);
const counts = rawWindow.counts;
if ([counts.received, counts.retained, counts.decoded, counts.rejected, counts.evicted, counts.bytes].some((value) => !Number.isInteger(value) || value < 0)) {
throw workbenchKafkaDebugReplayEvidenceError("Workbench raw HWLAB Event counts are incomplete", evidence);
}
if (counts.received !== counts.decoded + counts.rejected || counts.received !== counts.retained + counts.evicted) {
throw workbenchKafkaDebugReplayEvidenceError("Workbench raw HWLAB Event bounded-buffer counts are inconsistent", evidence);
}
if (counts.received <= 0 || counts.retained <= 0 || counts.bytes <= 0 || rawWindow.textPresent !== true || rawWindow.textBytes <= 0) {
throw workbenchKafkaDebugReplayEvidenceError("Workbench raw HWLAB Event window did not retain a readable product SSE frame", evidence);
}
if (rawWindow.productEventSourceRequestCount !== 0) {
throw workbenchKafkaDebugReplayEvidenceError("Workbench raw HWLAB Event tab opened an additional product EventSource", evidence);
}
}
function workbenchKafkaDebugReplayEvidenceError(message, evidence) {
const error = new Error(message);
error.details = {
traceId: evidence?.traceId || null,
topic: evidence?.topic || null,
groupId: evidence?.groupId || null,
terminalStatus: evidence?.terminalStatus || null,
receivedCount: evidence?.legacyCounts?.receivedCount ?? null,
appliedCount: evidence?.legacyCounts?.appliedCount ?? null,
phase: evidence?.phase || null,
code: evidence?.code || null,
layerCounts: evidence?.layerCounts || null,
layerDom: evidence?.layerDom || null,
layerDomStabilization: evidence?.layerDomStabilization || null,
traceTimeline: evidence?.traceTimeline || null,
rawHwlabEventWindow: evidence?.rawHwlabEventWindow || null,
valuesRedacted: true
};
return error;
}
async function workbenchKafkaDebugReplayWriteReport(reportDir, report) {
await mkdir(reportDir, { recursive: true, mode: 0o700 });
const file = path.join(reportDir, "report.json");
await writeFile(file, JSON.stringify(sanitize(report), null, 2) + "\n", { mode: 0o600 });
const meta = await fileMeta(file);
const artifact = { kind: "workbench-kafka-debug-replay-report", path: file, byteCount: meta.byteCount, sha256: meta.sha256, commandId: activeCommandId, valuesRedacted: true };
await appendJsonl(files.artifacts, { ts: new Date().toISOString(), ...artifact });
return artifact;
}
`;
}