import { CommandId, ProjectId, ProviderInstanceId, ThreadId, type OrchestrationReadModel, } from "@t3tools/contracts"; import * as NodeServices from "@effect/platform-node/NodeServices"; import { expect, it } from "@effect/vitest"; import * as Effect from "effect/Effect"; import { decideOrchestrationCommand } from "./decider.ts"; const NOW = "2026-01-01T00:00:00.000Z"; const PINNED_AT = "1969-12-30T00:00:00.000Z"; function makeReadModel(input: { readonly pinnedAt?: string | null; readonly pinOrderKey?: string | null; readonly archivedAt?: string | null; readonly settledOverride?: "settled" | "active" | null; readonly settledAt?: string | null; readonly snoozedUntil?: string | null; readonly snoozedAt?: string | null; }): OrchestrationReadModel { return { snapshotSequence: 0, projects: [], threads: [ { id: ThreadId.make("thread-1"), projectId: ProjectId.make("project-1"), title: "Thread", modelSelection: { instanceId: ProviderInstanceId.make("codex"), model: "gpt-5.4" }, runtimeMode: "full-access", interactionMode: "default", branch: null, worktreePath: null, latestTurn: null, createdAt: NOW, updatedAt: NOW, archivedAt: input.archivedAt ?? null, settledOverride: input.settledOverride ?? null, settledAt: input.settledAt ?? (input.settledOverride === "settled" ? NOW : null), snoozedUntil: input.snoozedUntil ?? null, snoozedAt: input.snoozedAt ?? (input.snoozedUntil != null ? PINNED_AT : null), pinnedAt: input.pinnedAt ?? null, pinOrderKey: input.pinOrderKey ?? null, deletedAt: null, messages: [], proposedPlans: [], activities: [], checkpoints: [], session: null, }, ], updatedAt: NOW, }; } it.layer(NodeServices.layer)("pinned thread decider", (it) => { it.effect("pins a thread, stamping pinnedAt and updatedAt together", () => Effect.gen(function* () { const event = yield* decideOrchestrationCommand({ command: { type: "thread.pin", commandId: CommandId.make("cmd-pin"), threadId: ThreadId.make("thread-1"), }, readModel: makeReadModel({}), }); const events = Array.isArray(event) ? event : [event]; expect(events).toHaveLength(1); expect(events[0]?.type).toBe("thread.pinned"); if (events[0]?.type === "thread.pinned") { expect(events[0].payload.pinnedAt).toBe(events[0].payload.updatedAt); } }), ); it.effect("re-pinning preserves the original pinnedAt and updatedAt", () => Effect.gen(function* () { const event = yield* decideOrchestrationCommand({ command: { type: "thread.pin", commandId: CommandId.make("cmd-pin-again"), threadId: ThreadId.make("thread-1"), }, readModel: makeReadModel({ pinnedAt: PINNED_AT }), }); const events = Array.isArray(event) ? event : [event]; expect(events[0]?.type).toBe("thread.pinned"); if (events[0]?.type === "thread.pinned") { expect(events[0].payload.pinnedAt).toBe(PINNED_AT); expect(events[0].payload.updatedAt).toBe(NOW); } }), ); it.effect("unpins a pinned thread", () => Effect.gen(function* () { const event = yield* decideOrchestrationCommand({ command: { type: "thread.unpin", commandId: CommandId.make("cmd-unpin"), threadId: ThreadId.make("thread-1"), }, readModel: makeReadModel({ pinnedAt: PINNED_AT }), }); const events = Array.isArray(event) ? event : [event]; expect(events[0]?.type).toBe("thread.unpinned"); if (events[0]?.type === "thread.unpinned") { expect(events[0].payload.updatedAt).not.toBe(NOW); } }), ); it.effect("unpinning an unpinned thread preserves updatedAt", () => Effect.gen(function* () { const event = yield* decideOrchestrationCommand({ command: { type: "thread.unpin", commandId: CommandId.make("cmd-unpin-noop"), threadId: ThreadId.make("thread-1"), }, readModel: makeReadModel({}), }); const events = Array.isArray(event) ? event : [event]; expect(events[0]?.type).toBe("thread.unpinned"); if (events[0]?.type === "thread.unpinned") { expect(events[0].payload.updatedAt).toBe(NOW); } }), ); it.effect("pinning a settled thread also un-settles it", () => Effect.gen(function* () { const event = yield* decideOrchestrationCommand({ command: { type: "thread.pin", commandId: CommandId.make("cmd-pin-settled"), threadId: ThreadId.make("thread-1"), }, readModel: makeReadModel({ settledOverride: "settled" }), }); const events = Array.isArray(event) ? event : [event]; expect(events.map((entry) => entry.type)).toEqual(["thread.pinned", "thread.unsettled"]); const unsettled = events.find((entry) => entry.type === "thread.unsettled"); if (unsettled?.type === "thread.unsettled") { expect(unsettled.payload.reason).toBe("user"); } }), ); it.effect("pinning a snoozed thread also wakes it", () => Effect.gen(function* () { const event = yield* decideOrchestrationCommand({ command: { type: "thread.pin", commandId: CommandId.make("cmd-pin-snoozed"), threadId: ThreadId.make("thread-1"), }, readModel: makeReadModel({ snoozedUntil: "1970-01-02T09:00:00.000Z" }), }); const events = Array.isArray(event) ? event : [event]; expect(events.map((entry) => entry.type)).toEqual(["thread.pinned", "thread.unsnoozed"]); }), ); it.effect("pinning an unparked thread emits only thread.pinned", () => Effect.gen(function* () { const event = yield* decideOrchestrationCommand({ command: { type: "thread.pin", commandId: CommandId.make("cmd-pin-plain"), threadId: ThreadId.make("thread-1"), }, readModel: makeReadModel({}), }); const events = Array.isArray(event) ? event : [event]; expect(events.map((entry) => entry.type)).toEqual(["thread.pinned"]); }), ); it.effect("settling a pinned thread also unpins it", () => Effect.gen(function* () { const event = yield* decideOrchestrationCommand({ command: { type: "thread.settle", commandId: CommandId.make("cmd-settle-pinned"), threadId: ThreadId.make("thread-1"), }, readModel: makeReadModel({ pinnedAt: PINNED_AT }), }); const events = Array.isArray(event) ? event : [event]; expect(events.map((entry) => entry.type)).toEqual(["thread.settled", "thread.unpinned"]); }), ); it.effect("settling an unpinned thread emits no unpin event", () => Effect.gen(function* () { const event = yield* decideOrchestrationCommand({ command: { type: "thread.settle", commandId: CommandId.make("cmd-settle-unpinned"), threadId: ThreadId.make("thread-1"), }, readModel: makeReadModel({}), }); const events = Array.isArray(event) ? event : [event]; expect(events.map((entry) => entry.type)).toEqual(["thread.settled"]); }), ); it.effect("rejects pinning an archived thread", () => Effect.gen(function* () { const error = yield* decideOrchestrationCommand({ command: { type: "thread.pin", commandId: CommandId.make("cmd-pin-archived"), threadId: ThreadId.make("thread-1"), }, readModel: makeReadModel({ archivedAt: NOW }), }).pipe(Effect.flip); expect(error._tag).toBe("OrchestrationCommandInvariantError"); }), ); it.effect("a fresh pin carries the client's order key", () => Effect.gen(function* () { const event = yield* decideOrchestrationCommand({ command: { type: "thread.pin", commandId: CommandId.make("cmd-pin-keyed"), threadId: ThreadId.make("thread-1"), orderKey: "g", }, readModel: makeReadModel({}), }); const events = Array.isArray(event) ? event : [event]; expect(events[0]?.type).toBe("thread.pinned"); if (events[0]?.type === "thread.pinned") { expect(events[0].payload.pinOrderKey).toBe("g"); } }), ); it.effect( "re-pinning ignores the incoming order key so raced pins cannot move a placed thread", () => Effect.gen(function* () { const event = yield* decideOrchestrationCommand({ command: { type: "thread.pin", commandId: CommandId.make("cmd-pin-keyed-again"), threadId: ThreadId.make("thread-1"), orderKey: "t", }, readModel: makeReadModel({ pinnedAt: PINNED_AT, pinOrderKey: "g" }), }); const events = Array.isArray(event) ? event : [event]; expect(events[0]?.type).toBe("thread.pinned"); if (events[0]?.type === "thread.pinned") { expect(events[0].payload.pinOrderKey).toBeUndefined(); } }), ); it.effect("reorders a pinned thread, stamping the new key", () => Effect.gen(function* () { const event = yield* decideOrchestrationCommand({ command: { type: "thread.pin.reorder", commandId: CommandId.make("cmd-reorder"), threadId: ThreadId.make("thread-1"), orderKey: "m", }, readModel: makeReadModel({ pinnedAt: PINNED_AT, pinOrderKey: "g" }), }); const events = Array.isArray(event) ? event : [event]; expect(events[0]?.type).toBe("thread.pin-reordered"); if (events[0]?.type === "thread.pin-reordered") { expect(events[0].payload.orderKey).toBe("m"); // A real move stamps the command time (the test clock), not the // thread's previous updatedAt. expect(events[0].payload.updatedAt).not.toBe(NOW); } }), ); it.effect("reordering onto the same key preserves updatedAt", () => Effect.gen(function* () { const event = yield* decideOrchestrationCommand({ command: { type: "thread.pin.reorder", commandId: CommandId.make("cmd-reorder-noop"), threadId: ThreadId.make("thread-1"), orderKey: "g", }, readModel: makeReadModel({ pinnedAt: PINNED_AT, pinOrderKey: "g" }), }); const events = Array.isArray(event) ? event : [event]; expect(events[0]?.type).toBe("thread.pin-reordered"); if (events[0]?.type === "thread.pin-reordered") { expect(events[0].payload.updatedAt).toBe(NOW); } }), ); it.effect("rejects reordering an unpinned thread", () => Effect.gen(function* () { const error = yield* decideOrchestrationCommand({ command: { type: "thread.pin.reorder", commandId: CommandId.make("cmd-reorder-unpinned"), threadId: ThreadId.make("thread-1"), orderKey: "m", }, readModel: makeReadModel({}), }).pipe(Effect.flip); expect(error._tag).toBe("OrchestrationCommandInvariantError"); }), ); });