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); }), );});
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);
}),
);
});