289 lines
9.6 KiB
TypeScript
289 lines
9.6 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { test } from "bun:test";
|
|
|
|
import {
|
|
buildWorkbenchProjectionEventFacts,
|
|
recordWorkbenchSessionOwner,
|
|
writeWorkbenchSessionAdmissionFact
|
|
} from "./workbench-projection-writer.ts";
|
|
import { projectionOutboxRealtimeEvents } from "./server-workbench-realtime-http.ts";
|
|
|
|
test("HTTP lifecycle records ownership without writing projection facts", async () => {
|
|
const calls = [];
|
|
const accessController = {
|
|
async recordAgentSessionOwner(input) {
|
|
calls.push(input);
|
|
return { id: input.sessionId, ownerUserId: input.ownerUserId, status: input.status };
|
|
}
|
|
};
|
|
let projectionWrites = 0;
|
|
const owner = await recordWorkbenchSessionOwner({
|
|
accessController,
|
|
runtimeStore: { writeWorkbenchFacts() { projectionWrites += 1; } },
|
|
traceId: "trc_owner_only",
|
|
ownerUserId: "usr_owner",
|
|
ownerRole: "user",
|
|
sessionId: "ses_owner_only",
|
|
projectId: "prj_owner",
|
|
status: "completed",
|
|
session: { source: "http-result" }
|
|
});
|
|
|
|
assert.equal(owner.id, "ses_owner_only");
|
|
assert.equal(calls.length, 1);
|
|
assert.equal(calls[0].traceId, "trc_owner_only");
|
|
assert.equal(projectionWrites, 0);
|
|
});
|
|
|
|
test("admission persists only the dedicated session scope fact", async () => {
|
|
const calls = [];
|
|
const runtimeStore = {
|
|
async writeWorkbenchSessionAdmissionFact(params, meta) {
|
|
calls.push({ params, meta });
|
|
return { written: true, admissionOnly: true };
|
|
},
|
|
writeWorkbenchFacts() {
|
|
throw new Error("generic projection writer must not be used by admission");
|
|
}
|
|
};
|
|
const result = await writeWorkbenchSessionAdmissionFact({
|
|
runtimeStore,
|
|
session: {
|
|
id: "ses_admission_only",
|
|
ownerUserId: "usr_owner",
|
|
ownerRole: "user",
|
|
projectId: "prj_owner",
|
|
conversationId: "cnv_owner",
|
|
lastTraceId: "trc_admission_only",
|
|
status: "running",
|
|
session: { source: "manual-session-create", launchContext: { source: "workbench" } },
|
|
createdAt: "2026-07-10T10:00:00.000Z",
|
|
updatedAt: "2026-07-10T10:00:01.000Z"
|
|
}
|
|
});
|
|
|
|
assert.equal(result.admissionOnly, true);
|
|
assert.equal(calls.length, 1);
|
|
const fact = calls[0].params.fact;
|
|
assert.equal(fact.sessionId, "ses_admission_only");
|
|
assert.equal(fact.ownerUserId, "usr_owner");
|
|
assert.equal(fact.lastTraceId, "trc_admission_only");
|
|
assert.equal(fact.projectedSeq, 0);
|
|
assert.equal(fact.sourceEventId, "ses_admission_only:session-admission");
|
|
assert.deepEqual(fact.sessionJson.launchContext, { source: "workbench", valuesRedacted: true });
|
|
});
|
|
|
|
test("admission fails closed when the dedicated durable entry is absent", async () => {
|
|
await assert.rejects(
|
|
writeWorkbenchSessionAdmissionFact({ runtimeStore: { writeWorkbenchFacts() {} }, session: { id: "ses_missing_entry" } }),
|
|
(error) => error?.code === "workbench_admission_store_unconfigured"
|
|
);
|
|
});
|
|
|
|
test("Kafka projector builds running facts from one canonical source event", () => {
|
|
const built = buildWorkbenchProjectionEventFacts({
|
|
projectedSeq: 4,
|
|
projectedAt: "2026-07-10T10:00:02.000Z",
|
|
event: {
|
|
traceId: "trc_running",
|
|
sessionId: "ses_running",
|
|
runId: "run_running",
|
|
commandId: "cmd_running",
|
|
sourceEventId: "evt_running",
|
|
sourceSeq: 7,
|
|
type: "assistant",
|
|
eventType: "assistant",
|
|
status: "running",
|
|
text: "committed delta",
|
|
createdAt: "2026-07-10T10:00:01.000Z"
|
|
}
|
|
});
|
|
|
|
assert.equal(built.written, true);
|
|
assert.equal(built.facts.sessions[0].lastTraceId, "trc_running");
|
|
assert.equal(built.facts.messages[0].text, "committed delta");
|
|
assert.equal(built.facts.turns[0].terminal, false);
|
|
assert.equal(built.facts.checkpoints[0].projectedSeq, 4);
|
|
assert.equal(built.facts.traceEvents[0].sourceEventId, "evt_running");
|
|
});
|
|
|
|
test("authoritative assistant final seals terminal with a final response", () => {
|
|
const built = buildWorkbenchProjectionEventFacts({
|
|
projectedSeq: 5,
|
|
projectedAt: "2026-07-10T10:00:04.000Z",
|
|
previousCheckpoint: {
|
|
traceId: "trc_terminal",
|
|
sessionId: "ses_terminal",
|
|
projectedSeq: 4,
|
|
sourceSeq: 4,
|
|
startedAt: "2026-07-10T10:00:00.000Z",
|
|
lastEventAt: "2026-07-10T10:00:03.000Z",
|
|
timing: { startedAt: "2026-07-10T10:00:00.000Z", lastEventAt: "2026-07-10T10:00:03.000Z", finishedAt: null, durationMs: null }
|
|
},
|
|
event: {
|
|
traceId: "trc_terminal",
|
|
sessionId: "ses_terminal",
|
|
sourceEventId: "evt_terminal",
|
|
sourceSeq: 5,
|
|
type: "assistant",
|
|
eventType: "assistant",
|
|
status: "completed",
|
|
terminal: true,
|
|
replyAuthority: true,
|
|
text: "durable final answer",
|
|
createdAt: "2026-07-10T10:00:04.000Z"
|
|
}
|
|
});
|
|
|
|
const turn = built.facts.turns[0];
|
|
assert.equal(turn.terminal, true);
|
|
assert.equal(turn.sealed, true);
|
|
assert.equal(turn.finalResponse.text, "durable final answer");
|
|
assert.equal(built.facts.messages[0].terminal, true);
|
|
assert.equal(built.facts.parts[0].partType, "final_response");
|
|
assert.equal(built.facts.checkpoints[0].projectionStatus, "caught_up");
|
|
});
|
|
|
|
test("completed terminal without final response remains visibly unsealed", () => {
|
|
const built = buildWorkbenchProjectionEventFacts({
|
|
projectedSeq: 1,
|
|
event: {
|
|
traceId: "trc_missing_final",
|
|
sessionId: "ses_missing_final",
|
|
sourceEventId: "evt_missing_final",
|
|
sourceSeq: 1,
|
|
type: "result",
|
|
eventType: "terminal",
|
|
status: "completed",
|
|
terminal: true,
|
|
createdAt: "2026-07-10T10:00:00.000Z"
|
|
}
|
|
});
|
|
|
|
const session = built.facts.sessions[0];
|
|
const message = built.facts.messages[0];
|
|
const turn = built.facts.turns[0];
|
|
const traceEvent = built.facts.traceEvents[0];
|
|
const checkpoint = built.facts.checkpoints[0];
|
|
for (const fact of [session, message, turn, traceEvent, checkpoint]) {
|
|
assert.equal(fact.status ?? "running", "running");
|
|
assert.equal(fact.terminal, false);
|
|
assert.equal(fact.sealed, false);
|
|
}
|
|
assert.equal(built.terminal, false);
|
|
assert.equal(built.sourceTerminal, true);
|
|
assert.equal(turn.finalResponse, null);
|
|
assert.equal(turn.diagnostic.terminalSealBlocked, true);
|
|
assert.equal(traceEvent.diagnostic.sourceStatus, "completed");
|
|
assert.equal(traceEvent.finishedAt, null);
|
|
assert.equal(traceEvent.durationMs, null);
|
|
assert.equal(checkpoint.projectionStatus, "projecting");
|
|
assert.equal(checkpoint.projectionHealth, "degraded");
|
|
|
|
const [sse] = projectionOutboxRealtimeEvents({
|
|
events: [{
|
|
outboxSeq: 1,
|
|
entityFamily: "traceEvents",
|
|
entityId: traceEvent.id,
|
|
projectedSeq: 1,
|
|
projectionRevision: 1,
|
|
traceId: traceEvent.traceId,
|
|
sessionId: traceEvent.sessionId,
|
|
commitType: "event",
|
|
terminal: traceEvent.terminal,
|
|
sealed: traceEvent.sealed,
|
|
payload: { family: "traceEvents", fact: traceEvent }
|
|
}]
|
|
});
|
|
assert.equal(sse.name, "workbench.trace.event");
|
|
assert.equal(sse.payload.terminal, false);
|
|
assert.equal(sse.payload.sealed, false);
|
|
assert.equal(sse.payload.snapshot.status, "running");
|
|
assert.equal(sse.payload.event.terminal, false);
|
|
});
|
|
|
|
test("late running event cannot overwrite a sealed terminal", () => {
|
|
const built = buildWorkbenchProjectionEventFacts({
|
|
projectedSeq: 10,
|
|
previousCheckpoint: {
|
|
traceId: "trc_sealed",
|
|
sessionId: "ses_sealed",
|
|
projectedSeq: 9,
|
|
sourceSeq: 9,
|
|
terminal: true,
|
|
sealed: true,
|
|
finalResponse: { text: "sealed answer", status: "completed" }
|
|
},
|
|
event: {
|
|
traceId: "trc_sealed",
|
|
sessionId: "ses_sealed",
|
|
sourceEventId: "evt_late",
|
|
sourceSeq: 8,
|
|
type: "backend",
|
|
status: "running"
|
|
}
|
|
});
|
|
|
|
assert.equal(built.written, false);
|
|
assert.equal(built.suppressedAfterSeal, true);
|
|
assert.equal(built.projectedSeq, 9);
|
|
assert.equal(built.sourceEventId, "evt_late");
|
|
assert.equal("facts" in built, false);
|
|
});
|
|
|
|
test("late terminal event cannot overwrite a different sealed terminal", () => {
|
|
const built = buildWorkbenchProjectionEventFacts({
|
|
projectedSeq: 11,
|
|
previousCheckpoint: {
|
|
traceId: "trc_sealed_terminal",
|
|
sessionId: "ses_sealed_terminal",
|
|
projectedSeq: 10,
|
|
sourceSeq: 10,
|
|
status: "completed",
|
|
terminal: true,
|
|
sealed: true,
|
|
finalResponse: { text: "immutable completed answer", status: "completed" }
|
|
},
|
|
event: {
|
|
traceId: "trc_sealed_terminal",
|
|
sessionId: "ses_sealed_terminal",
|
|
sourceEventId: "evt_late_failed_terminal",
|
|
sourceSeq: 11,
|
|
type: "terminal_status",
|
|
status: "failed",
|
|
terminal: true,
|
|
errorCode: "late-failure"
|
|
}
|
|
});
|
|
|
|
assert.equal(built.written, false);
|
|
assert.equal(built.suppressedAfterSeal, true);
|
|
assert.equal(built.projectedSeq, 10);
|
|
assert.equal(built.sourceEventId, "evt_late_failed_terminal");
|
|
assert.equal("facts" in built, false);
|
|
});
|
|
|
|
test("same canonical source event derives deterministic fact identities", () => {
|
|
const input = {
|
|
projectedSeq: 3,
|
|
projectedAt: "2026-07-10T10:00:03.000Z",
|
|
event: {
|
|
traceId: "trc_deterministic",
|
|
sessionId: "ses_deterministic",
|
|
sourceEventId: "evt_deterministic",
|
|
sourceSeq: 3,
|
|
type: "assistant",
|
|
eventType: "assistant",
|
|
status: "running",
|
|
text: "same",
|
|
createdAt: "2026-07-10T10:00:02.000Z"
|
|
}
|
|
};
|
|
const first = buildWorkbenchProjectionEventFacts(input);
|
|
const second = buildWorkbenchProjectionEventFacts(input);
|
|
|
|
assert.equal(first.facts.traceEvents[0].id, second.facts.traceEvents[0].id);
|
|
assert.equal(first.facts.messages[0].messageId, second.facts.messages[0].messageId);
|
|
assert.equal(first.facts.parts[0].partId, second.facts.parts[0].partId);
|
|
});
|