fix: 增加隔离 Kafka 重放分层诊断
This commit is contained in:
@@ -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 () => {
|
||||
|
||||
Reference in New Issue
Block a user