328 lines
11 KiB
TypeScript
328 lines
11 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { expect, test } from "bun:test";
|
|
|
|
import { createWorkbenchKafkaSessionIndex, workbenchKafkaRailSummary } from "./workbench-kafka-session-index.ts";
|
|
|
|
test("process session index bootstraps once and serves repeated scoped queries from memory", async () => {
|
|
let queryCount = 0;
|
|
let now = 1000;
|
|
const index = createWorkbenchKafkaSessionIndex({
|
|
topic: "hwlab.event.v1",
|
|
maxEvents: 20,
|
|
maxEventsPerSession: 10,
|
|
bootstrapTimeoutMs: 5000,
|
|
rebuildIntervalMs: 60000,
|
|
rebuildCooldownMs: 1000,
|
|
scanLimit: 100,
|
|
nowMs: () => now++,
|
|
logger: quietLogger(),
|
|
queryAll: async () => {
|
|
queryCount += 1;
|
|
return completeResult([
|
|
record(0, "ses_a", "trc_a"),
|
|
record(1, "ses_b", "trc_b"),
|
|
record(2, "ses_a", "trc_a")
|
|
], "3");
|
|
}
|
|
});
|
|
|
|
assert.equal(await index.start(), true);
|
|
const first = index.query({ sessionId: "ses_a", partitionKey: "ses_a", limit: 10, scanLimit: 100, timeoutMs: 5000 });
|
|
const second = index.query({ sessionId: "ses_a", partitionKey: "ses_a", limit: 10, scanLimit: 100, timeoutMs: 5000 });
|
|
assert.equal(first.hit, true);
|
|
assert.equal(second.hit, true);
|
|
assert.equal(first.result.events.length, 2);
|
|
assert.equal(first.result.scannedCount, 2);
|
|
assert.equal(first.result.index.hit, true);
|
|
assert.equal(first.result.index.indexedEventCount, 3);
|
|
assert.equal(queryCount, 1);
|
|
expect(index.sessionSummaries().sessions).toMatchObject([
|
|
{ sessionId: "ses_a", firstUserMessagePreview: "prompt ses_a", lastUserMessageAt: "2026-07-20T00:00:01.000Z", lastTraceId: "trc_a", status: "running", valuesRedacted: true }
|
|
]);
|
|
|
|
index.observeLive(envelope("ses_a", "trc_a", 4), { topic: "hwlab.event.v1", partition: 0, offset: "3" });
|
|
const live = index.query({ sessionId: "ses_a", limit: 10 });
|
|
assert.equal(live.hit, true);
|
|
assert.equal(live.result.events.length, 3);
|
|
assert.equal(live.result.endOffsets[0].endOffset, "4");
|
|
index.stop();
|
|
});
|
|
|
|
test("process session index returns one transport-ordered result for a session set", async () => {
|
|
const index = createWorkbenchKafkaSessionIndex({
|
|
topic: "hwlab.event.v1",
|
|
maxEvents: 20,
|
|
maxEventsPerSession: 10,
|
|
bootstrapTimeoutMs: 5000,
|
|
rebuildIntervalMs: 60000,
|
|
rebuildCooldownMs: 1000,
|
|
scanLimit: 100,
|
|
logger: quietLogger(),
|
|
queryAll: async () => completeResult([
|
|
record(0, "ses_a", "trc_a"),
|
|
record(1, "ses_c", "trc_c"),
|
|
record(2, "ses_b", "trc_b")
|
|
], "3")
|
|
});
|
|
|
|
assert.equal(await index.start(), true);
|
|
const result = index.query({ sessionIds: ["ses_b", "ses_a", "ses_b"], limit: 10 });
|
|
assert.equal(result.hit, true);
|
|
assert.deepEqual(result.result.events.map((event) => event.value.sessionId), ["ses_a", "ses_b"]);
|
|
assert.deepEqual(result.result.filters.sessionIds, ["ses_a", "ses_b"]);
|
|
assert.equal(result.result.partitionKeyScoped, false);
|
|
index.stop();
|
|
});
|
|
|
|
test("single-session query reads its bucket without scanning unrelated envelopes", async () => {
|
|
let unrelatedSessionReads = 0;
|
|
const unrelated = envelope("ses_unrelated", "trc_unrelated", 1);
|
|
Object.defineProperty(unrelated, "sessionId", {
|
|
configurable: true,
|
|
enumerable: true,
|
|
get() {
|
|
unrelatedSessionReads += 1;
|
|
return "ses_unrelated";
|
|
}
|
|
});
|
|
const index = createWorkbenchKafkaSessionIndex({
|
|
topic: "hwlab.event.v1",
|
|
maxEvents: 20,
|
|
maxEventsPerSession: 10,
|
|
bootstrapTimeoutMs: 5000,
|
|
rebuildIntervalMs: 60000,
|
|
rebuildCooldownMs: 1000,
|
|
scanLimit: 100,
|
|
logger: quietLogger(),
|
|
queryAll: async () => completeResult([
|
|
record(0, "ses_target", "trc_target"),
|
|
{
|
|
topic: "hwlab.event.v1",
|
|
partition: 0,
|
|
offset: "1",
|
|
key: "ses_target",
|
|
value: {
|
|
schema: "hwlab.event.v1",
|
|
sessionId: "ses_target",
|
|
traceId: "trc_steer",
|
|
event: { type: "user", traceId: "trc_steer", targetTraceId: "trc_target", commandType: "steer" }
|
|
}
|
|
},
|
|
{
|
|
topic: "hwlab.event.v1",
|
|
partition: 0,
|
|
offset: "2",
|
|
key: "ses_unrelated",
|
|
timestamp: unrelated.event.createdAt,
|
|
valueSha256: "sha-unrelated",
|
|
value: unrelated
|
|
}
|
|
], "3")
|
|
});
|
|
|
|
assert.equal(await index.start(), true);
|
|
unrelatedSessionReads = 0;
|
|
const result = index.query({ sessionId: "ses_target", traceId: "trc_target", limit: 10 });
|
|
assert.equal(result.hit, true);
|
|
assert.equal(result.result.events.length, 2);
|
|
assert.equal(unrelatedSessionReads, 0);
|
|
index.stop();
|
|
});
|
|
|
|
test("process session index falls back instead of returning an incomplete bounded bucket", async () => {
|
|
const index = createWorkbenchKafkaSessionIndex({
|
|
topic: "hwlab.event.v1",
|
|
maxEvents: 10,
|
|
maxEventsPerSession: 1,
|
|
bootstrapTimeoutMs: 5000,
|
|
rebuildIntervalMs: 60000,
|
|
rebuildCooldownMs: 1000,
|
|
scanLimit: 100,
|
|
logger: quietLogger(),
|
|
queryAll: async () => completeResult([
|
|
record(0, "ses_overflow", "trc_overflow"),
|
|
record(1, "ses_overflow", "trc_overflow")
|
|
], "2")
|
|
});
|
|
|
|
assert.equal(await index.start(), true);
|
|
const result = index.query({ sessionId: "ses_overflow", limit: 10 });
|
|
assert.equal(result.hit, false);
|
|
assert.equal(result.fallbackReason, "session-capacity-exceeded");
|
|
assert.equal(result.warning.blocking, false);
|
|
index.stop();
|
|
});
|
|
|
|
test("process session index readiness recovers after a later successful rebuild", async () => {
|
|
let queryCount = 0;
|
|
let now = 1000;
|
|
const index = createWorkbenchKafkaSessionIndex({
|
|
topic: "hwlab.event.v1",
|
|
maxEvents: 20,
|
|
maxEventsPerSession: 10,
|
|
bootstrapTimeoutMs: 5000,
|
|
rebuildIntervalMs: 60000,
|
|
rebuildCooldownMs: 1000,
|
|
scanLimit: 100,
|
|
nowMs: () => now,
|
|
logger: quietLogger(),
|
|
queryAll: async () => {
|
|
queryCount += 1;
|
|
if (queryCount === 1) return {
|
|
completionReason: "timeout",
|
|
completion: { complete: false, barrierReached: false, retentionStartVerified: true },
|
|
reachedEndOffsets: false,
|
|
endOffsetsAvailable: true,
|
|
endOffsets: [{ partition: 0, startOffset: "0", endOffset: "1" }],
|
|
events: []
|
|
};
|
|
return completeResult([record(0, "ses_recovered", "trc_recovered")], "1");
|
|
}
|
|
});
|
|
|
|
assert.equal(await index.start(), false);
|
|
assert.equal(index.status().ready, false);
|
|
now += 1000;
|
|
assert.equal(await index.rebuild("interval"), true);
|
|
assert.equal(index.status().ready, true);
|
|
assert.equal(await index.start(), true);
|
|
assert.equal(index.sessionSummaries({ sessionIds: ["ses_recovered"] }).ok, true);
|
|
assert.equal(queryCount, 2);
|
|
index.stop();
|
|
});
|
|
|
|
test("Kafka rail projection derives title, user clock and latest-trace terminal status in partition order", () => {
|
|
const sessionId = "ses_rail";
|
|
const record = (offset, traceId, event) => ({
|
|
topic: "hwlab.event.v1",
|
|
partition: 2,
|
|
offset: String(offset),
|
|
key: sessionId,
|
|
value: { schema: "hwlab.event.v1", sessionId, hwlabSessionId: sessionId, traceId, event }
|
|
});
|
|
const summary = workbenchKafkaRailSummary(sessionId, [
|
|
record(1, "trc_old", { type: "user", text: "first title", submittedAt: "2026-07-20T00:00:01.000Z" }),
|
|
record(2, "trc_old", { type: "result", eventType: "terminal", terminal: true, terminalStatus: "completed", createdAt: "2026-07-20T00:00:05.000Z" }),
|
|
record(3, "trc_new", { type: "user", text: "second turn", submittedAt: "2026-07-20T00:01:00.000Z" }),
|
|
record(4, "trc_old", { type: "result", eventType: "terminal", terminal: true, terminalStatus: "failed" }),
|
|
record(5, "trc_new", { type: "assistant_progress", createdAt: "2026-07-20T00:02:00.000Z" }),
|
|
record(6, "trc_new", { type: "result", eventType: "terminal", terminal: true, terminalStatus: "cancelled" })
|
|
]);
|
|
|
|
expect(summary).toMatchObject({
|
|
sessionId,
|
|
firstUserMessagePreview: "first title",
|
|
lastUserMessageAt: "2026-07-20T00:01:00.000Z",
|
|
lastTraceId: "trc_new",
|
|
status: "canceled",
|
|
revision: { topic: "hwlab.event.v1", partition: 2, offset: "6" }
|
|
});
|
|
});
|
|
|
|
test("Kafka rail summaries omit sessions until their first valid user event", async () => {
|
|
const sessionId = "ses_admission_only";
|
|
const index = createWorkbenchKafkaSessionIndex({
|
|
topic: "hwlab.event.v1",
|
|
maxEvents: 20,
|
|
maxEventsPerSession: 10,
|
|
bootstrapTimeoutMs: 5000,
|
|
rebuildIntervalMs: 60000,
|
|
rebuildCooldownMs: 1000,
|
|
scanLimit: 100,
|
|
logger: quietLogger(),
|
|
queryAll: async () => completeResult([{
|
|
topic: "hwlab.event.v1",
|
|
partition: 0,
|
|
offset: "0",
|
|
key: sessionId,
|
|
value: {
|
|
schema: "hwlab.event.v1",
|
|
sessionId,
|
|
traceId: "trc_admission_only",
|
|
event: { type: "backend", text: "admission accepted" }
|
|
}
|
|
}], "1")
|
|
});
|
|
|
|
expect(await index.start()).toBe(true);
|
|
expect(index.sessionSummaries().sessions).toEqual([]);
|
|
expect(index.sessionSummaries({ sessionIds: [sessionId] }).sessions).toEqual([]);
|
|
index.stop();
|
|
});
|
|
|
|
test("Kafka rail projection follows actual turn delivery when a requested steer arrives after terminal", () => {
|
|
const sessionId = "ses_rail_steer";
|
|
const record = (offset, traceId, event) => ({
|
|
topic: "hwlab.event.v1",
|
|
partition: 2,
|
|
offset: String(offset),
|
|
key: sessionId,
|
|
value: { schema: "hwlab.event.v1", sessionId, hwlabSessionId: sessionId, traceId, event }
|
|
});
|
|
const summary = workbenchKafkaRailSummary(sessionId, [
|
|
record(1, "trc_target", { type: "user", text: "initial turn", submittedAt: "2026-07-20T00:00:01.000Z" }),
|
|
record(2, "trc_target", { type: "result", eventType: "terminal", terminal: true, terminalStatus: "completed" }),
|
|
record(3, "trc_steer", { type: "user", delivery: "turn", requestedDelivery: "steer", targetTraceId: "trc_target", text: "continue as a turn", submittedAt: "2026-07-20T00:00:03.000Z" }),
|
|
record(4, "trc_steer", { type: "assistant_progress", createdAt: "2026-07-20T00:00:04.000Z" }),
|
|
record(5, "trc_steer", { type: "result", eventType: "terminal", terminal: true, terminalStatus: "completed" })
|
|
]);
|
|
|
|
expect(summary).toMatchObject({
|
|
sessionId,
|
|
firstUserMessagePreview: "initial turn",
|
|
lastUserMessageAt: "2026-07-20T00:00:03.000Z",
|
|
lastTraceId: "trc_steer",
|
|
status: "completed",
|
|
revision: { topic: "hwlab.event.v1", partition: 2, offset: "5" }
|
|
});
|
|
});
|
|
|
|
function completeResult(events: ReturnType<typeof record>[], endOffset: string) {
|
|
return {
|
|
completionReason: "end-offset",
|
|
completion: {
|
|
complete: true,
|
|
barrierReached: true,
|
|
retentionStartVerified: true
|
|
},
|
|
reachedEndOffsets: true,
|
|
endOffsetsAvailable: true,
|
|
endOffsets: [{ partition: 0, startOffset: "0", endOffset }],
|
|
events
|
|
};
|
|
}
|
|
|
|
function record(offset: number, sessionId: string, traceId: string) {
|
|
const value = envelope(sessionId, traceId, offset + 1);
|
|
return {
|
|
topic: "hwlab.event.v1",
|
|
partition: 0,
|
|
offset: String(offset),
|
|
key: sessionId,
|
|
timestamp: value.event.createdAt,
|
|
valueSha256: `sha-${offset}`,
|
|
value
|
|
};
|
|
}
|
|
|
|
function envelope(sessionId: string, traceId: string, seq: number) {
|
|
return {
|
|
schema: "hwlab.event.v1",
|
|
eventId: `evt_${sessionId}_${seq}`,
|
|
sourceEventId: `src_${sessionId}_${seq}`,
|
|
sessionId,
|
|
traceId,
|
|
event: {
|
|
type: seq === 1 ? "user" : "assistant",
|
|
text: seq === 1 ? `prompt ${sessionId}` : "reply",
|
|
sessionId,
|
|
traceId,
|
|
createdAt: `2026-07-20T00:00:0${seq}.000Z`
|
|
}
|
|
};
|
|
}
|
|
|
|
function quietLogger() {
|
|
return { log() {}, warn() {} };
|
|
}
|