916 lines · 27.1 KB · TypeScript
RawDownload
import {  CommandId,  EventId,  ProjectId,  ProviderDriverKind,  ThreadId,  type OrchestrationEvent,} from "@t3tools/contracts";import * as Effect from "effect/Effect";import { describe, expect, it } from "vitest"; import { createEmptyReadModel, projectEvent } from "./projector.ts"; function makeEvent(input: {  sequence: number;  type: OrchestrationEvent["type"];  occurredAt: string;  aggregateKind: OrchestrationEvent["aggregateKind"];  aggregateId: string;  commandId: string | null;  payload: unknown;}): OrchestrationEvent {  return {    sequence: input.sequence,    eventId: EventId.make(`event-${input.sequence}`),    type: input.type,    aggregateKind: input.aggregateKind,    aggregateId:      input.aggregateKind === "project"        ? ProjectId.make(input.aggregateId)        : ThreadId.make(input.aggregateId),    occurredAt: input.occurredAt,    commandId: input.commandId === null ? null : CommandId.make(input.commandId),    causationEventId: null,    correlationId: null,    metadata: {},    payload: input.payload as never,  } as OrchestrationEvent;} describe("orchestration projector", () => {  it("applies thread.created events", async () => {    const now = "2026-01-01T00:00:00.000Z";    const model = createEmptyReadModel(now);     const next = await Effect.runPromise(      projectEvent(        model,        makeEvent({          sequence: 1,          type: "thread.created",          aggregateKind: "thread",          aggregateId: "thread-1",          occurredAt: now,          commandId: "cmd-thread-create",          payload: {            threadId: "thread-1",            projectId: "project-1",            title: "demo",            modelSelection: {              provider: ProviderDriverKind.make("codex"),              model: "gpt-5-codex",            },            runtimeMode: "full-access",            branch: null,            worktreePath: null,            createdAt: now,            updatedAt: now,          },        }),      ),    );     expect(next.snapshotSequence).toBe(1);    expect(next.threads).toEqual([      {        id: "thread-1",        projectId: "project-1",        title: "demo",        modelSelection: {          instanceId: "codex",          model: "gpt-5-codex",        },        runtimeMode: "full-access",        interactionMode: "default",        branch: null,        worktreePath: null,        latestTurn: null,        createdAt: now,        updatedAt: now,        archivedAt: null,        deletedAt: null,        messages: [],        proposedPlans: [],        activities: [],        checkpoints: [],        session: null,      },    ]);  });   it("fails when event payload cannot be decoded by runtime schema", async () => {    const now = "2026-01-01T00:00:00.000Z";    const model = createEmptyReadModel(now);     await expect(      Effect.runPromise(        projectEvent(          model,          makeEvent({            sequence: 1,            type: "thread.created",            aggregateKind: "thread",            aggregateId: "thread-1",            occurredAt: now,            commandId: "cmd-invalid",            payload: {              // missing required threadId              projectId: "project-1",              title: "demo",              modelSelection: {                provider: ProviderDriverKind.make("codex"),                model: "gpt-5-codex",              },              branch: null,              worktreePath: null,              createdAt: now,              updatedAt: now,            },          }),        ),      ),    ).rejects.toBeDefined();  });   it("applies thread.archived and thread.unarchived events", async () => {    const now = "2026-01-01T00:00:00.000Z";    const later = "2026-01-01T00:00:01.000Z";    const created = await Effect.runPromise(      projectEvent(        createEmptyReadModel(now),        makeEvent({          sequence: 1,          type: "thread.created",          aggregateKind: "thread",          aggregateId: "thread-1",          occurredAt: now,          commandId: "cmd-thread-create",          payload: {            threadId: "thread-1",            projectId: "project-1",            title: "demo",            modelSelection: {              provider: ProviderDriverKind.make("codex"),              model: "gpt-5-codex",            },            runtimeMode: "full-access",            interactionMode: "default",            branch: null,            worktreePath: null,            createdAt: now,            updatedAt: now,          },        }),      ),    );     const archived = await Effect.runPromise(      projectEvent(        created,        makeEvent({          sequence: 2,          type: "thread.archived",          aggregateKind: "thread",          aggregateId: "thread-1",          occurredAt: later,          commandId: "cmd-thread-archive",          payload: {            threadId: "thread-1",            archivedAt: later,            updatedAt: later,          },        }),      ),    );    expect(archived.threads[0]?.archivedAt).toBe(later);     const unarchived = await Effect.runPromise(      projectEvent(        archived,        makeEvent({          sequence: 3,          type: "thread.unarchived",          aggregateKind: "thread",          aggregateId: "thread-1",          occurredAt: later,          commandId: "cmd-thread-unarchive",          payload: {            threadId: "thread-1",            updatedAt: later,          },        }),      ),    );    expect(unarchived.threads[0]?.archivedAt).toBeNull();  });   it("keeps projector forward-compatible for unhandled event types", async () => {    const now = "2026-01-01T00:00:00.000Z";    const model = createEmptyReadModel(now);     const next = await Effect.runPromise(      projectEvent(        model,        makeEvent({          sequence: 7,          type: "thread.turn-start-requested",          aggregateKind: "thread",          aggregateId: "thread-1",          occurredAt: "2026-01-01T00:00:00.000Z",          commandId: "cmd-unhandled",          payload: {            threadId: "thread-1",            messageId: "message-1",            runtimeMode: "approval-required",            createdAt: "2026-01-01T00:00:00.000Z",          },        }),      ),    );     expect(next.snapshotSequence).toBe(7);    expect(next.updatedAt).toBe("2026-01-01T00:00:00.000Z");    expect(next.threads).toEqual([]);  });   it("tracks latest turn id from session lifecycle events", async () => {    const createdAt = "2026-02-23T08:00:00.000Z";    const startedAt = "2026-02-23T08:00:05.000Z";    const model = createEmptyReadModel(createdAt);     const afterCreate = await Effect.runPromise(      projectEvent(        model,        makeEvent({          sequence: 1,          type: "thread.created",          aggregateKind: "thread",          aggregateId: "thread-1",          occurredAt: createdAt,          commandId: "cmd-create",          payload: {            threadId: "thread-1",            projectId: "project-1",            title: "demo",            modelSelection: {              provider: ProviderDriverKind.make("codex"),              model: "gpt-5.3-codex",            },            runtimeMode: "full-access",            branch: null,            worktreePath: null,            createdAt,            updatedAt: createdAt,          },        }),      ),    );     const afterRunning = await Effect.runPromise(      projectEvent(        afterCreate,        makeEvent({          sequence: 2,          type: "thread.session-set",          aggregateKind: "thread",          aggregateId: "thread-1",          occurredAt: startedAt,          commandId: "cmd-running",          payload: {            threadId: "thread-1",            session: {              threadId: "thread-1",              status: "running",              providerName: "codex",              providerSessionId: "session-1",              providerThreadId: "provider-thread-1",              runtimeMode: "approval-required",              activeTurnId: "turn-1",              lastError: null,              updatedAt: startedAt,            },          },        }),      ),    );     const thread = afterRunning.threads[0];    expect(thread?.latestTurn?.turnId).toBe("turn-1");    expect(thread?.session?.status).toBe("running");  });   it("updates canonical thread runtime mode from thread.runtime-mode-set", async () => {    const createdAt = "2026-02-23T08:00:00.000Z";    const updatedAt = "2026-02-23T08:00:05.000Z";    const model = createEmptyReadModel(createdAt);     const afterCreate = await Effect.runPromise(      projectEvent(        model,        makeEvent({          sequence: 1,          type: "thread.created",          aggregateKind: "thread",          aggregateId: "thread-1",          occurredAt: createdAt,          commandId: "cmd-create",          payload: {            threadId: "thread-1",            projectId: "project-1",            title: "demo",            modelSelection: {              provider: ProviderDriverKind.make("codex"),              model: "gpt-5.3-codex",            },            runtimeMode: "full-access",            branch: null,            worktreePath: null,            createdAt,            updatedAt: createdAt,          },        }),      ),    );     const afterUpdate = await Effect.runPromise(      projectEvent(        afterCreate,        makeEvent({          sequence: 2,          type: "thread.runtime-mode-set",          aggregateKind: "thread",          aggregateId: "thread-1",          occurredAt: updatedAt,          commandId: "cmd-runtime-mode-set",          payload: {            threadId: "thread-1",            runtimeMode: "approval-required",            updatedAt,          },        }),      ),    );     expect(afterUpdate.threads[0]?.runtimeMode).toBe("approval-required");    expect(afterUpdate.threads[0]?.updatedAt).toBe(updatedAt);  });   it("marks assistant messages completed with non-streaming updates", async () => {    const createdAt = "2026-02-23T09:00:00.000Z";    const deltaAt = "2026-02-23T09:00:01.000Z";    const completeAt = "2026-02-23T09:00:03.500Z";    const model = createEmptyReadModel(createdAt);     const afterCreate = await Effect.runPromise(      projectEvent(        model,        makeEvent({          sequence: 1,          type: "thread.created",          aggregateKind: "thread",          aggregateId: "thread-1",          occurredAt: createdAt,          commandId: "cmd-create",          payload: {            threadId: "thread-1",            projectId: "project-1",            title: "demo",            modelSelection: {              provider: ProviderDriverKind.make("codex"),              model: "gpt-5.3-codex",            },            runtimeMode: "full-access",            branch: null,            worktreePath: null,            createdAt,            updatedAt: createdAt,          },        }),      ),    );     const afterDelta = await Effect.runPromise(      projectEvent(        afterCreate,        makeEvent({          sequence: 2,          type: "thread.message-sent",          aggregateKind: "thread",          aggregateId: "thread-1",          occurredAt: deltaAt,          commandId: "cmd-delta",          payload: {            threadId: "thread-1",            messageId: "assistant:msg-1",            role: "assistant",            text: "hello",            turnId: "turn-1",            streaming: true,            createdAt: deltaAt,            updatedAt: deltaAt,          },        }),      ),    );     const afterComplete = await Effect.runPromise(      projectEvent(        afterDelta,        makeEvent({          sequence: 3,          type: "thread.message-sent",          aggregateKind: "thread",          aggregateId: "thread-1",          occurredAt: completeAt,          commandId: "cmd-complete",          payload: {            threadId: "thread-1",            messageId: "assistant:msg-1",            role: "assistant",            text: "",            turnId: "turn-1",            streaming: false,            createdAt: completeAt,            updatedAt: completeAt,          },        }),      ),    );     const message = afterComplete.threads[0]?.messages[0];    expect(message?.id).toBe("assistant:msg-1");    expect(message?.text).toBe("hello");    expect(message?.streaming).toBe(false);    expect(message?.updatedAt).toBe(completeAt);  });   it("prunes reverted turn messages from in-memory thread snapshot", async () => {    const createdAt = "2026-02-23T10:00:00.000Z";    const model = createEmptyReadModel(createdAt);     const afterCreate = await Effect.runPromise(      projectEvent(        model,        makeEvent({          sequence: 1,          type: "thread.created",          aggregateKind: "thread",          aggregateId: "thread-1",          occurredAt: createdAt,          commandId: "cmd-create",          payload: {            threadId: "thread-1",            projectId: "project-1",            title: "demo",            modelSelection: {              provider: ProviderDriverKind.make("codex"),              model: "gpt-5.3-codex",            },            runtimeMode: "full-access",            branch: null,            worktreePath: null,            createdAt,            updatedAt: createdAt,          },        }),      ),    );     const events: ReadonlyArray<OrchestrationEvent> = [      makeEvent({        sequence: 2,        type: "thread.message-sent",        aggregateKind: "thread",        aggregateId: "thread-1",        occurredAt: "2026-02-23T10:00:01.000Z",        commandId: "cmd-user-1",        payload: {          threadId: "thread-1",          messageId: "user-msg-1",          role: "user",          text: "First edit",          turnId: null,          streaming: false,          createdAt: "2026-02-23T10:00:01.000Z",          updatedAt: "2026-02-23T10:00:01.000Z",        },      }),      makeEvent({        sequence: 3,

Lines 1–500 of 916.