import { ProjectId, ThreadId, ProviderInstanceId } from "@t3tools/contracts"; import { assert, it } from "@effect/vitest"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; import * as Option from "effect/Option"; import * as SqlClient from "effect/unstable/sql/SqlClient"; import { SqlitePersistenceMemory } from "./Sqlite.ts"; import { ProjectionProjectRepositoryLive } from "./ProjectionProjects.ts"; import { ProjectionThreadRepositoryLive } from "./ProjectionThreads.ts"; import { ProjectionProjectRepository } from "../Services/ProjectionProjects.ts"; import { ProjectionThreadRepository } from "../Services/ProjectionThreads.ts"; const projectionRepositoriesLayer = it.layer( Layer.mergeAll( ProjectionProjectRepositoryLive.pipe(Layer.provideMerge(SqlitePersistenceMemory)), ProjectionThreadRepositoryLive.pipe(Layer.provideMerge(SqlitePersistenceMemory)), SqlitePersistenceMemory, ), ); projectionRepositoriesLayer("Projection repositories", (it) => { it.effect("stores SQL NULL for missing project model options", () => Effect.gen(function* () { const projects = yield* ProjectionProjectRepository; const sql = yield* SqlClient.SqlClient; yield* projects.upsert({ projectId: ProjectId.make("project-null-options"), title: "Null options project", workspaceRoot: "/tmp/project-null-options", defaultModelSelection: { instanceId: ProviderInstanceId.make("codex"), model: "gpt-5.4", }, scripts: [], createdAt: "2026-03-24T00:00:00.000Z", updatedAt: "2026-03-24T00:00:00.000Z", deletedAt: null, }); const rows = yield* sql<{ readonly defaultModelSelection: string | null; }>` SELECT default_model_selection_json AS "defaultModelSelection" FROM projection_projects WHERE project_id = 'project-null-options' `; const row = rows[0]; if (!row) { return yield* Effect.die("Expected projection_projects row to exist."); } assert.strictEqual( row.defaultModelSelection, // @effect-diagnostics-next-line preferSchemaOverJson:off JSON.stringify({ instanceId: ProviderInstanceId.make("codex"), model: "gpt-5.4", }), ); const persisted = yield* projects.getById({ projectId: ProjectId.make("project-null-options"), }); assert.deepStrictEqual(Option.getOrNull(persisted)?.defaultModelSelection, { instanceId: ProviderInstanceId.make("codex"), model: "gpt-5.4", }); }), ); it.effect("stores JSON for thread model options", () => Effect.gen(function* () { const threads = yield* ProjectionThreadRepository; const sql = yield* SqlClient.SqlClient; yield* threads.upsert({ threadId: ThreadId.make("thread-null-options"), projectId: ProjectId.make("project-null-options"), title: "Null options thread", modelSelection: { instanceId: ProviderInstanceId.make("claudeAgent"), model: "claude-opus-4-6", }, runtimeMode: "full-access", interactionMode: "default", branch: null, worktreePath: null, latestTurnId: null, createdAt: "2026-03-24T00:00:00.000Z", updatedAt: "2026-03-24T00:00:00.000Z", archivedAt: null, settledOverride: null, settledAt: null, snoozedUntil: null, snoozedAt: null, latestUserMessageAt: null, pendingApprovalCount: 0, pendingUserInputCount: 0, hasActionableProposedPlan: 0, deletedAt: null, }); const rows = yield* sql<{ readonly modelSelection: string | null; }>` SELECT model_selection_json AS "modelSelection" FROM projection_threads WHERE thread_id = 'thread-null-options' `; const row = rows[0]; if (!row) { return yield* Effect.die("Expected projection_threads row to exist."); } assert.strictEqual( row.modelSelection, // @effect-diagnostics-next-line preferSchemaOverJson:off JSON.stringify({ instanceId: ProviderInstanceId.make("claudeAgent"), model: "claude-opus-4-6", }), ); const persisted = yield* threads.getById({ threadId: ThreadId.make("thread-null-options"), }); assert.deepStrictEqual(Option.getOrNull(persisted)?.modelSelection, { instanceId: ProviderInstanceId.make("claudeAgent"), model: "claude-opus-4-6", }); }), ); it.effect("round-trips non-null settlement values through the thread row", () => Effect.gen(function* () { const threads = yield* ProjectionThreadRepository; yield* threads.upsert({ threadId: ThreadId.make("thread-settled"), projectId: ProjectId.make("project-1"), title: "Settled thread", modelSelection: { instanceId: ProviderInstanceId.make("codex"), model: "gpt-5.4", }, runtimeMode: "full-access", interactionMode: "default", branch: null, worktreePath: null, latestTurnId: null, createdAt: "2026-03-24T00:00:00.000Z", updatedAt: "2026-03-25T00:00:00.000Z", archivedAt: null, settledOverride: "settled", settledAt: "2026-03-25T00:00:00.000Z", snoozedUntil: "2026-03-26T09:00:00.000Z", snoozedAt: "2026-03-25T00:00:00.000Z", latestUserMessageAt: null, pendingApprovalCount: 0, pendingUserInputCount: 0, hasActionableProposedPlan: 0, deletedAt: null, }); const persisted = yield* threads.getById({ threadId: ThreadId.make("thread-settled"), }); const row = Option.getOrNull(persisted); if (!row) { return yield* Effect.die("Expected settled projection_threads row to exist."); } assert.strictEqual(row.settledOverride, "settled"); assert.strictEqual(row.settledAt, "2026-03-25T00:00:00.000Z"); assert.strictEqual(row.snoozedUntil, "2026-03-26T09:00:00.000Z"); assert.strictEqual(row.snoozedAt, "2026-03-25T00:00:00.000Z"); // Un-settle to the keep-active pin and wake the snooze; confirm the // flips persist. yield* threads.upsert({ ...row, settledOverride: "active", settledAt: null, snoozedUntil: null, snoozedAt: null, }); const repersisted = yield* threads.getById({ threadId: ThreadId.make("thread-settled"), }); const updated = Option.getOrNull(repersisted); assert.strictEqual(updated?.settledOverride, "active"); assert.strictEqual(updated?.settledAt, null); assert.strictEqual(updated?.snoozedUntil, null); assert.strictEqual(updated?.snoozedAt, null); }), ); });