fix: 增加隔离 Kafka 重放分层诊断

This commit is contained in:
root
2026-07-10 15:33:42 +02:00
parent 8368c5801c
commit 92e9345309
9 changed files with 879 additions and 42 deletions
+110
View File
@@ -25,6 +25,7 @@ test("offline JSONL maps 35 stdio reconstructions to 35 ordered HWLAB debug even
"--from", "jsonl",
"--session-id", SESSION_ID,
"--trace-id", TRACE_ID,
"--replay-id", "rpl_offline_35",
"--jsonl-file", inputFile,
"--output-jsonl", outputFile,
"--expect-count", "35",
@@ -62,6 +63,9 @@ test("offline JSONL maps 35 stdio reconstructions to 35 ordered HWLAB debug even
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.partition === 0));
assert.ok(output.every((row) => row.value.replayId === "rpl_offline_35"));
assert.deepEqual(output.map((row) => row.value.debugReplay.index), Array.from({ length: 35 }, (_, index) => index + 1));
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));
@@ -86,7 +90,9 @@ test("Kafka reader and producer are injected while canonical AgentRun events use
"--from", "kafka",
"--session-id", SESSION_ID,
"--trace-id", TRACE_ID,
"--replay-id", "rpl_canonical_35",
"--input-topic", "agentrun.event.v1",
"--publish",
"--expect-count", "35",
"--output-jsonl", path.join(cwd, "canonical-output.jsonl"),
"--json"
@@ -121,7 +127,10 @@ test("Kafka reader and producer are injected while canonical AgentRun events use
assert.equal(producerCalls.length, 1);
assert.equal(producerCalls[0].topic, "hwlab.event.debug.v1");
assert.equal(producerCalls[0].messages.length, 35);
assert.ok(producerCalls[0].messages.every((message: any) => message.partition === 0));
const envelopes = producerCalls[0].messages.map((message: any) => JSON.parse(message.value));
assert.ok(envelopes.every((event: any) => event.replayId === "rpl_canonical_35"));
assert.ok(producerCalls[0].messages.every((message: any) => message.headers["x-debug-replay-id"] === "rpl_canonical_35"));
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));
@@ -132,6 +141,100 @@ test("Kafka reader and producer are injected while canonical AgentRun events use
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);
assert.deepEqual(result.payload.output.offsetRanges, [{
topic: "hwlab.event.debug.v1",
partition: 0,
firstOffset: "100",
lastOffset: "134",
count: 35,
valuesPrinted: false
}]);
assert.equal(result.payload.replay.replayId, "rpl_canonical_35");
assert.equal(result.payload.replay.producerInvoked, true);
assert.equal(result.payload.replay.sourceMatched, true);
assert.deepEqual(result.payload.replay.outputBarrier, result.payload.output.offsetRanges[0]);
});
test("Kafka regeneration reports source trace missing before creating a producer", async () => {
let producerCreated = false;
let readerInput: any = null;
const result = await runKafkaCli([
"regenerate", "hwlab",
"--from", "kafka",
"--session-id", SESSION_ID,
"--trace-id", "trc_missing_debug_source",
"--replay-id", "rpl_missing_debug_source",
"--group-prefix", "hwlab-v03-workbench-isolated-debug"
], {
env: {},
now: () => "2026-07-10T12:00:00.000Z",
async readKafka(input) { readerInput = input; return { events: [canonicalRecord(1)] }; },
async createProducer() { producerCreated = true; throw new Error("must not create producer"); }
});
assert.equal(result.exitCode, 1);
assert.equal(result.payload.error.code, "source_trace_missing");
assert.equal(readerInput.topic, "agentrun.event.v1");
assert.equal(producerCreated, false);
});
test("canonical Kafka regeneration preflights without publishing and preserves replayId in the publish hint", async () => {
const cwd = await mkdtemp(path.join(os.tmpdir(), "hwlab-kafka-debug-preflight-"));
let producerCreated = false;
const result = await runKafkaCli([
"regenerate", "hwlab",
"--from", "kafka",
"--session-id", SESSION_ID,
"--trace-id", TRACE_ID,
"--replay-id", "rpl_canonical_preflight",
"--group-prefix", "hwlab-v03-workbench-isolated-debug",
"--output-jsonl", path.join(cwd, "output.jsonl")
], {
cwd,
env: {},
now: () => "2026-07-10T12:00:00.000Z",
async readKafka() { return { events: [canonicalRecord(1)] }; },
async createProducer() { producerCreated = true; throw new Error("preflight must not create a producer"); }
});
assert.equal(result.exitCode, 0);
assert.equal(result.payload.config.inputTopic.value, "agentrun.event.v1");
assert.equal(result.payload.output.published, false);
assert.equal(result.payload.output.publishedCount, 0);
assert.equal(result.payload.replay.producerInvoked, false);
assert.equal(result.payload.replay.outputBarrier, null);
assert.match(result.payload.next.command, /--replay-id rpl_canonical_preflight/u);
assert.match(result.payload.next.command, /--publish/u);
assert.equal(producerCreated, false);
});
test("Kafka regeneration fails closed when publish metadata cannot form an offset barrier", async () => {
const cwd = await mkdtemp(path.join(os.tmpdir(), "hwlab-kafka-debug-barrier-"));
const result = await runKafkaCli([
"regenerate", "hwlab",
"--from", "kafka",
"--session-id", SESSION_ID,
"--trace-id", TRACE_ID,
"--replay-id", "rpl_missing_offset_barrier",
"--group-prefix", "hwlab-v03-workbench-isolated-debug",
"--publish",
"--output-jsonl", path.join(cwd, "output.jsonl")
], {
cwd,
env: {},
now: () => "2026-07-10T12:00:00.000Z",
async readKafka() { return { events: [canonicalRecord(1)] }; },
async createProducer() {
return {
async connect() {},
async send() { return []; },
async disconnect() {}
};
}
});
assert.equal(result.exitCode, 1);
assert.equal(result.payload.error.code, "debug_publish_barrier_missing");
});
test("reconstruction identity fabrication and product output topic fail closed", async () => {
@@ -161,6 +264,13 @@ test("reconstruction identity fabrication and product output topic fail closed",
], { cwd, env: {}, now: () => "2026-07-10T12:00:00.000Z" });
assert.equal(productGroupResult.exitCode, 1);
assert.equal(productGroupResult.payload.error.code, "debug_group_required");
const replayIdResult = await runKafkaCli([
"regenerate", "hwlab", "--from", "jsonl", "--session-id", SESSION_ID,
"--replay-id", "not-a-replay", "--jsonl-file", inputFile, "--no-publish"
], { cwd, env: {}, now: () => "2026-07-10T12:00:00.000Z" });
assert.equal(replayIdResult.exitCode, 1);
assert.equal(replayIdResult.payload.error.code, "invalid_replay_id");
});
test("Kafka mode requires a CLI or YAML-owned isolated group prefix", async () => {