import { CommandId, EventId, ProjectId, ThreadId, type OrchestrationEvent,} from "@t3tools/contracts";import { Effect } from "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.makeUnsafe(`event-${input.sequence}`), type: input.type, aggregateKind: input.aggregateKind, aggregateId: input.aggregateKind === "project" ? ProjectId.makeUnsafe(input.aggregateId) : ThreadId.makeUnsafe(input.aggregateId), occurredAt: input.occurredAt, commandId: input.commandId === null ? null : CommandId.makeUnsafe(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 = new Date().toISOString(); 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: "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: { provider: "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 = new Date().toISOString(); 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: "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 = new Date().toISOString(); const later = new Date(Date.parse(now) + 1_000).toISOString(); 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: "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 = new Date().toISOString(); 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: "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: "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: "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: "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, type: "thread.message-sent",Lines 1–500 of 915. Load more
import { CommandId, EventId, ProjectId, ThreadId, type OrchestrationEvent, } from "@t3tools/contracts"; import { Effect } from "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.makeUnsafe(`event-${input.sequence}`), type: input.type, aggregateKind: input.aggregateKind, aggregateId: input.aggregateKind === "project" ? ProjectId.makeUnsafe(input.aggregateId) : ThreadId.makeUnsafe(input.aggregateId), occurredAt: input.occurredAt, commandId: input.commandId === null ? null : CommandId.makeUnsafe(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 = new Date().toISOString(); 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: "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: { provider: "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 = new Date().toISOString(); 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: "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 = new Date().toISOString(); const later = new Date(Date.parse(now) + 1_000).toISOString(); 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: "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 = new Date().toISOString(); 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: "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: "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: "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: "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, type: "thread.message-sent",