Files
pikasTech-HWLAB/tools/hwlab-cli/kafka-regenerate.test.ts
T

331 lines
14 KiB
TypeScript

import assert from "node:assert/strict";
import { mkdtemp, readFile, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { test } from "bun:test";
import { queryKafkaEventStream } from "../../internal/cloud/kafka-event-bridge.ts";
import { mapAgentRunRecordsToHwlabDebugEvents, runKafkaCli } from "../src/hwlab-cli/kafka-regenerate.ts";
const SESSION_ID = "ses_agentrun_5ec4e141_6abc_466d_9afe_049f7c0ac105";
const HWLAB_SESSION_ID = "ses_5ec4e141-6abc-466d-9afe-049f7c0ac105";
const TRACE_ID = "trc_mretx18t3jl4tg";
const RUN_ID = "run_a78cbcb05f2c4afaa748a3158db44998";
test("offline JSONL maps 35 stdio reconstructions to 35 ordered HWLAB debug events without runtime dependencies", async () => {
const cwd = await mkdtemp(path.join(os.tmpdir(), "hwlab-kafka-debug-jsonl-"));
const inputFile = path.join(cwd, "agentrun-events.jsonl");
const outputFile = path.join(cwd, "hwlab-events.jsonl");
const records = Array.from({ length: 35 }, (_, index) => reconstructionRecord(index + 1));
await writeFile(inputFile, records.map((record) => JSON.stringify(record)).join("\n") + "\n", "utf8");
const result = await runKafkaCli([
"regenerate", "hwlab",
"--from", "jsonl",
"--session-id", SESSION_ID,
"--trace-id", TRACE_ID,
"--jsonl-file", inputFile,
"--output-jsonl", outputFile,
"--expect-count", "35",
"--no-publish",
"--json"
], {
cwd,
env: {},
now: () => "2026-07-10T12:00:00.000Z",
async createProducer() { throw new Error("offline JSONL must not create a producer"); }
});
assert.equal(result.exitCode, 0);
assert.equal(result.payload.input.readCount, 35);
assert.equal(result.payload.output.count, 35);
assert.equal(result.payload.output.publishedCount, 0);
assert.deepEqual(result.payload.validation, {
expectedCount: 35,
oneToOne: true,
orderPreserved: true,
lineagePreserved: true,
firstSourceSeq: 1,
lastSourceSeq: 35,
inputKinds: ["stdio-derived partial reconstruction"]
});
assert.deepEqual(result.payload.runtimeDependencies, {
cloudApi: false,
database: false,
transactionalProjector: false,
kafka: false
});
const output = (await readFile(outputFile, "utf8")).trim().split("\n").map(JSON.parse);
assert.equal(output.length, 35);
assert.deepEqual(output.map((row) => row.value.debugLineage.sourceSeq), Array.from({ length: 35 }, (_, index) => index + 1));
assert.ok(output.every((row) => row.topic === "hwlab.event.debug.v1"));
assert.ok(output.every((row) => row.value.schema === "hwlab.event.debug.v1"));
assert.ok(output.every((row) => row.value.eventId === null && row.value.sourceEventId === null && row.value.outboxSeq === null));
assert.ok(output.every((row) => row.value.debugLineage.inputEventId === null && row.value.debugLineage.inputOutboxSeq === null));
assert.ok(output.every((row) => row.value.sourceEvent.partition === null && row.value.sourceEvent.offset === null));
assert.ok(output.every((row) => row.value.traceId === TRACE_ID && row.value.sessionId === HWLAB_SESSION_ID));
assert.ok(output.every((row) => row.value.debugLineage.inputSessionId === SESSION_ID));
assert.ok(output.every((row) => row.value.debugLineage.inputHwlabSessionId === HWLAB_SESSION_ID));
assert.ok(output.every((row) => row.value.reconstruction.kind === "stdio-derived partial reconstruction"));
assert.ok(output.every((row) => row.value.reconstruction.originalEventId === null && row.value.reconstruction.originalOutboxSeq === null));
assert.ok(output.every((row) => row.value.reconstruction.identityDerivation.kind === "agentrun-session-uuid-to-hwlab-session"));
assert.ok(output.every((row) => row.value.reconstruction.identityDerivation.reversible === true));
});
test("Kafka reader and producer are injected while canonical AgentRun events use strict decode and isolated output", async () => {
const cwd = await mkdtemp(path.join(os.tmpdir(), "hwlab-kafka-debug-canonical-"));
const records = Array.from({ length: 35 }, (_, index) => canonicalRecord(index + 1));
const readerCalls: any[] = [];
const producerCalls: any[] = [];
let connected = 0;
let disconnected = 0;
const result = await runKafkaCli([
"regenerate", "hwlab",
"--from", "kafka",
"--session-id", SESSION_ID,
"--trace-id", TRACE_ID,
"--input-topic", "agentrun.event.v1",
"--expect-count", "35",
"--output-jsonl", path.join(cwd, "canonical-output.jsonl"),
"--json"
], {
cwd,
env: {
HWLAB_KAFKA_BOOTSTRAP_SERVERS: "kafka.test:9092",
HWLAB_KAFKA_HWLAB_DEBUG_EVENT_TOPIC: "hwlab.event.debug.v1",
HWLAB_KAFKA_HWLAB_DEBUG_GROUP_PREFIX: "hwlab-v03-workbench-isolated-debug"
},
now: () => "2026-07-10T12:00:00.000Z",
async readKafka(input) {
readerCalls.push(input);
return { ok: true, groupId: "hwlab-v03-agentrun-event-debug-regenerate-test", events: records };
},
async createProducer() {
return {
async connect() { connected += 1; },
async send(input: any) { producerCalls.push(input); return [{ topicName: input.topic, partition: 0, baseOffset: "100" }]; },
async disconnect() { disconnected += 1; }
};
}
});
assert.equal(result.exitCode, 0);
assert.equal(readerCalls.length, 1);
assert.equal(readerCalls[0].groupIdPrefix, "hwlab-v03-workbench-isolated-debug");
assert.equal(readerCalls[0].sessionId, SESSION_ID);
assert.equal(readerCalls[0].limit, 36, "exact count verification must leave room to detect one extra event");
assert.equal(connected, 1);
assert.equal(disconnected, 1);
assert.equal(producerCalls.length, 1);
assert.equal(producerCalls[0].topic, "hwlab.event.debug.v1");
assert.equal(producerCalls[0].messages.length, 35);
const envelopes = producerCalls[0].messages.map((message: any) => JSON.parse(message.value));
assert.deepEqual(envelopes.map((event: any) => event.debugLineage.sourceSeq), Array.from({ length: 35 }, (_, index) => index + 1));
assert.deepEqual(envelopes.map((event: any) => event.eventId), Array.from({ length: 35 }, (_, index) => `hwlab:evt_${index + 1}`));
assert.deepEqual(envelopes.map((event: any) => event.debugLineage.inputOutboxSeq), Array.from({ length: 35 }, (_, index) => index + 1));
assert.ok(envelopes.every((event: any) => event.diagnostics.inputKind === "canonical-durable"));
assert.ok(envelopes.every((event: any) => event.schema === "hwlab.event.debug.v1"));
assert.equal(result.payload.output.publishedCount, 35);
assert.deepEqual(result.payload.config.outputTopic, { value: "hwlab.event.debug.v1", source: "env:HWLAB_KAFKA_HWLAB_DEBUG_EVENT_TOPIC" });
assert.deepEqual(result.payload.config.groupPrefix, { value: "hwlab-v03-workbench-isolated-debug", source: "env:HWLAB_KAFKA_HWLAB_DEBUG_GROUP_PREFIX" });
assert.equal(result.payload.validation.orderPreserved, true);
assert.equal(result.payload.validation.lineagePreserved, true);
});
test("reconstruction identity fabrication and product output topic fail closed", async () => {
const cwd = await mkdtemp(path.join(os.tmpdir(), "hwlab-kafka-debug-invalid-"));
const forged = reconstructionRecord(1);
forged.value.eventId = "evt_forged";
const inputFile = path.join(cwd, "forged.jsonl");
await writeFile(inputFile, `${JSON.stringify(forged)}\n`, "utf8");
const forgedResult = await runKafkaCli([
"regenerate", "hwlab", "--from", "jsonl", "--session-id", SESSION_ID,
"--jsonl-file", inputFile, "--no-publish"
], { cwd, env: {}, now: () => "2026-07-10T12:00:00.000Z" });
assert.equal(forgedResult.exitCode, 1);
assert.equal(forgedResult.payload.error.code, "hwlab_kafka_debug_identity_invalid");
const productResult = await runKafkaCli([
"regenerate", "hwlab", "--from", "jsonl", "--session-id", SESSION_ID,
"--jsonl-file", inputFile, "--output-topic", "hwlab.event.v1", "--no-publish"
], { cwd, env: {}, now: () => "2026-07-10T12:00:00.000Z" });
assert.equal(productResult.exitCode, 1);
assert.equal(productResult.payload.error.code, "product_topic_forbidden");
});
test("Kafka query exposes the isolated generated group and original value hash", async () => {
const valueText = JSON.stringify(canonicalRecord(1).value);
const consumerConfigs: any[] = [];
const consumer = {
async connect() {},
async subscribe() {},
async run({ eachMessage }: any) {
await eachMessage({
topic: "agentrun.event.v1",
partition: 0,
message: { offset: "1601", key: Buffer.from(SESSION_ID), value: Buffer.from(valueText), timestamp: "1783681200000" }
});
},
async stop() {},
async disconnect() {}
};
const queried = await queryKafkaEventStream({
env: { HWLAB_KAFKA_BOOTSTRAP_SERVERS: "kafka.test:9092", HWLAB_KAFKA_CLIENT_ID: "hwlab-test" },
topic: "agentrun.event.v1",
sessionId: SESSION_ID,
limit: 1,
groupIdPrefix: "hwlab-v03-workbench-isolated-debug",
kafkaFactory() {
return { consumer(config: any) { consumerConfigs.push(config); return consumer; } };
}
});
assert.match(queried.groupId, /^hwlab-v03-workbench-isolated-debug-\d+-[a-f0-9]{8}$/u);
assert.equal(consumerConfigs[0].groupId, queried.groupId);
assert.equal(queried.events[0].valueSha256, createSha256(valueText));
});
test("non-reversible AgentRun session identity stays only in debug lineage", () => {
const rawSessionId = "ses_agentrun_not_uuid";
const record = reconstructionRecord(1);
record.key = rawSessionId;
record.value.sessionId = rawSessionId;
record.value.hwlabSessionId = null;
record.value.run.sessionId = rawSessionId;
record.value.run.hwlabSessionId = null;
record.value.event.payload.sessionId = rawSessionId;
record.value.reconstruction.identityDerivation = {
kind: "none",
sourceField: "sessionId",
targetField: "hwlabSessionId",
reversible: false,
reason: "source sessionId is not ses_agentrun_<uuid-with-underscores>",
valuesPrinted: false
};
const mapped = mapAgentRunRecordsToHwlabDebugEvents([record], {
sessionId: rawSessionId,
traceId: TRACE_ID,
expectedCount: 1,
producedAt: "2026-07-10T12:00:00.000Z"
});
assert.equal(mapped.events[0].sessionId, null);
assert.equal(mapped.events[0].hwlabSessionId, null);
assert.equal(mapped.events[0].event.sessionId, null);
assert.equal(mapped.events[0].debugLineage.inputSessionId, rawSessionId);
assert.equal(mapped.events[0].reconstruction.identityDerivation.kind, "none");
});
function reconstructionRecord(seq: number): any {
const value = {
schema: "agentrun.event.reconstruction.v1",
eventType: "agentrun.event.reconstructed",
source: "agentrun-cli-stdio-reconstruction",
eventId: null,
outboxSeq: null,
sourceSeq: seq,
observedAt: `2026-07-10T11:00:${String(seq).padStart(2, "0")}.000Z`,
traceId: TRACE_ID,
sessionId: SESSION_ID,
hwlabSessionId: HWLAB_SESSION_ID,
runId: RUN_ID,
commandId: "cmd_9fff03fc124b4203b065d04bbb7c4f1e",
run: { runId: RUN_ID, sessionId: SESSION_ID, hwlabSessionId: HWLAB_SESSION_ID, status: "running", valuesPrinted: false },
command: { commandId: "cmd_9fff03fc124b4203b065d04bbb7c4f1e", runId: RUN_ID, seq: 1, type: "run", state: "running", valuesPrinted: false },
event: {
id: null,
runId: RUN_ID,
seq,
type: "backend_status",
payload: {
traceId: TRACE_ID,
sessionId: SESSION_ID,
phase: `reconstructed-${seq}`,
reconstructed: true,
reconstructionKind: "stdio-derived partial reconstruction"
},
createdAt: `2026-07-10T11:00:${String(seq).padStart(2, "0")}.000Z`
},
reconstruction: {
kind: "stdio-derived partial reconstruction",
completeLifecycle: false,
originalEventId: null,
originalOutboxSeq: null,
sourceTopic: "codex-stdio.raw.v1",
sourcePartition: 0,
sourceOffset: String(3500 + seq),
sourceFrameSeq: seq,
sourceValueSha256: `sha256:${String(seq).padStart(64, "0")}`,
identityDerivation: {
kind: "agentrun-session-uuid-to-hwlab-session",
sourceField: "sessionId",
targetField: "hwlabSessionId",
reversible: true,
sourcePrefix: "ses_agentrun_",
targetPrefix: "ses_",
separatorTransform: "underscore-to-hyphen",
valuesPrinted: false
}
},
valuesPrinted: false
};
return { topic: "agentrun.event.debug.v1", partition: null, offset: null, key: SESSION_ID, value, headers: { "x-trace-id": TRACE_ID } };
}
function canonicalRecord(seq: number): any {
const eventId = `evt_${seq}`;
const value = {
schema: "agentrun.event.v1",
eventType: "agentrun.event.committed",
source: "agentrun-manager",
eventId,
outboxSeq: seq,
sourceSeq: seq,
committedAt: `2026-07-10T11:00:${String(seq).padStart(2, "0")}.000Z`,
traceId: TRACE_ID,
sessionId: SESSION_ID,
hwlabSessionId: HWLAB_SESSION_ID,
runId: RUN_ID,
commandId: "cmd_9fff03fc124b4203b065d04bbb7c4f1e",
run: {
runId: RUN_ID,
status: "running",
terminalStatus: null,
failureKind: null,
tenantId: "tenant-test",
projectId: "project-test",
providerId: "gpt.pika",
backendProfile: "gpt.pika",
sessionId: SESSION_ID,
hwlabSessionId: HWLAB_SESSION_ID,
conversationId: null,
threadId: null,
valuesPrinted: false
},
command: {
commandId: "cmd_9fff03fc124b4203b065d04bbb7c4f1e",
runId: RUN_ID,
seq: 1,
type: "run",
state: "running",
payloadHash: "hash",
valuesPrinted: false
},
event: {
id: eventId,
runId: RUN_ID,
seq,
type: "backend_status",
payload: { traceId: TRACE_ID, sessionId: SESSION_ID, phase: `canonical-${seq}` },
createdAt: `2026-07-10T11:00:${String(seq).padStart(2, "0")}.000Z`
},
valuesPrinted: false
};
return { topic: "agentrun.event.v1", partition: 0, offset: String(1600 + seq), key: SESSION_ID, value };
}
function createSha256(value: string) {
return new Bun.CryptoHasher("sha256").update(value).digest("hex");
}