import { CommandId, EventId, ProjectId, ProviderDriverKind, ThreadId, type OrchestrationEvent,} from "@t3tools/contracts";import * as Effect from "effect/Effect";import { describe, expect, it } from "vite-plus/test"; 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, settledOverride: null, settledAt: null, unsettledAt: null, snoozedUntil: null, snoozedAt: 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 settledAt = "2026-02-23T08:01:00.000Z"; const [afterRunning, afterReady] = await Effect.runPromise( Effect.flatMap( 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, }, }, }), ), (running) => Effect.map( projectEvent( running, makeEvent({ sequence: 3, type: "thread.session-set", aggregateKind: "thread", aggregateId: "thread-1", occurredAt: settledAt, commandId: "cmd-ready", payload: { threadId: "thread-1", session: { threadId: "thread-1", status: "ready", providerName: "codex", providerSessionId: "session-1", providerThreadId: "provider-thread-1", runtimeMode: "approval-required", activeTurnId: null, lastError: null, updatedAt: settledAt, }, }, }), ), (ready) => [running, ready] as const, ), ), ); const thread = afterRunning.threads[0]; expect(thread?.latestTurn?.turnId).toBe("turn-1"); expect(thread?.session?.status).toBe("running"); // Leaving the "running" session status settles the running turn with the // session timestamp as the turn end. const settledThread = afterReady.threads[0]; expect(settledThread?.latestTurn?.turnId).toBe("turn-1"); expect(settledThread?.latestTurn?.state).toBe("completed"); expect(settledThread?.latestTurn?.completedAt).toBe(settledAt); }); 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,Lines 1–500 of 960. Load more
import { CommandId, EventId, ProjectId, ProviderDriverKind, ThreadId, type OrchestrationEvent, } from "@t3tools/contracts"; import * as Effect from "effect/Effect"; import { describe, expect, it } from "vite-plus/test"; 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, settledOverride: null, settledAt: null, unsettledAt: null, snoozedUntil: null, snoozedAt: 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 settledAt = "2026-02-23T08:01:00.000Z"; const [afterRunning, afterReady] = await Effect.runPromise( Effect.flatMap( 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, }, }, }), ), (running) => Effect.map( projectEvent( running, makeEvent({ sequence: 3, type: "thread.session-set", aggregateKind: "thread", aggregateId: "thread-1", occurredAt: settledAt, commandId: "cmd-ready", payload: { threadId: "thread-1", session: { threadId: "thread-1", status: "ready", providerName: "codex", providerSessionId: "session-1", providerThreadId: "provider-thread-1", runtimeMode: "approval-required", activeTurnId: null, lastError: null, updatedAt: settledAt, }, }, }), ), (ready) => [running, ready] as const, ), ), ); const thread = afterRunning.threads[0]; expect(thread?.latestTurn?.turnId).toBe("turn-1"); expect(thread?.session?.status).toBe("running"); // Leaving the "running" session status settles the running turn with the // session timestamp as the turn end. const settledThread = afterReady.threads[0]; expect(settledThread?.latestTurn?.turnId).toBe("turn-1"); expect(settledThread?.latestTurn?.state).toBe("completed"); expect(settledThread?.latestTurn?.completedAt).toBe(settledAt); }); 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,