import * as NodeServices from "@effect/platform-node/NodeServices";import { assert, it } from "@effect/vitest";import * as Effect from "effect/Effect";import * as Layer from "effect/Layer";import * as SqlClient from "effect/unstable/sql/SqlClient"; import { ServerConfig } from "../../config.ts";import { SqlitePersistenceMemory } from "../../persistence/Layers/Sqlite.ts";import * as ProjectEnrichment from "../../project/ProjectEnrichmentService.ts";import * as ProjectFaviconResolver from "../../project/ProjectFaviconResolver.ts";import * as RepositoryIdentityResolver from "../../project/RepositoryIdentityResolver.ts";import { ProjectionSnapshotQuery } from "../Services/ProjectionSnapshotQuery.ts";import * as ThreadBackgroundLiveness from "../ThreadBackgroundLiveness.ts";import * as ThreadPlanProgress from "../ThreadPlanProgress.ts";import { OrchestrationProjectionSnapshotQueryLive } from "./ProjectionSnapshotQuery.ts"; const metadataLayer = Layer.merge( Layer.succeed(RepositoryIdentityResolver.RepositoryIdentityResolver, { resolve: () => Effect.succeed(null), }), Layer.succeed(ProjectFaviconResolver.ProjectFaviconResolver, { resolvePath: () => Effect.succeed(null), }),); const TestLayer = OrchestrationProjectionSnapshotQueryLive.pipe( Layer.provideMerge(ThreadBackgroundLiveness.layer), Layer.provideMerge(ThreadPlanProgress.layer), Layer.provideMerge(ProjectEnrichment.layer), Layer.provideMerge(metadataLayer), Layer.provideMerge(SqlitePersistenceMemory), Layer.provide(ServerConfig.layerTest(process.cwd(), { prefix: "projection-search-test-" })), Layer.provide(NodeServices.layer),); it.effect("search uses v2 visibility while legacy transcripts are still lazy", () => Effect.gen(function* () { const sql = yield* SqlClient.SqlClient; const query = yield* ProjectionSnapshotQuery; const now = "2026-08-29T00:00:00.000Z"; yield* sql` INSERT INTO projection_projects ( project_id, title, workspace_root, default_model_selection_json, scripts_json, created_at, updated_at, deleted_at ) VALUES ( 'project:search', 'Search', '/tmp/search', NULL, '[]', ${now}, ${now}, NULL ) `; yield* sql` INSERT INTO projection_threads ( thread_id, project_id, title, model_selection_json, runtime_mode, interaction_mode, branch, worktree_path, latest_turn_id, created_at, updated_at, archived_at, settled_override, settled_at, deleted_at ) VALUES ('thread:active', 'project:search', 'Active', '{"instanceId":"codex","model":"gpt-5.4"}', 'full-access', 'default', NULL, NULL, NULL, ${now}, ${now}, NULL, NULL, NULL, NULL), ('thread:archived', 'project:search', 'Archived', '{"instanceId":"codex","model":"gpt-5.4"}', 'full-access', 'default', NULL, NULL, NULL, ${now}, ${now}, NULL, NULL, NULL, NULL), ('thread:deleted', 'project:search', 'Deleted', '{"instanceId":"codex","model":"gpt-5.4"}', 'full-access', 'default', NULL, NULL, NULL, ${now}, ${now}, NULL, NULL, NULL, NULL), ('thread:assistant', 'project:search', 'Assistant', '{"instanceId":"codex","model":"gpt-5.4"}', 'full-access', 'default', NULL, NULL, 'turn:assistant', ${now}, ${now}, NULL, NULL, NULL, NULL) `; yield* sql` INSERT INTO projection_thread_messages ( message_id, thread_id, turn_id, role, text, attachments_json, is_streaming, created_at, updated_at ) VALUES ('message:active', 'thread:active', NULL, 'user', 'migration needle active', '[]', 0, ${now}, ${now}), ('message:archived', 'thread:archived', NULL, 'user', 'migration needle archived', '[]', 0, ${now}, ${now}), ('message:deleted', 'thread:deleted', NULL, 'user', 'migration needle deleted', '[]', 0, ${now}, ${now}), ('message:orphan-assistant', 'thread:assistant', NULL, 'assistant', 'migration needle orphan', '[]', 0, ${now}, ${now}), ('message:assistant', 'thread:assistant', 'turn:assistant', 'assistant', 'migration needle answer', '[]', 0, ${now}, ${now}) `; yield* sql` INSERT INTO projection_turns ( turn_id, thread_id, state, requested_at, started_at, completed_at, assistant_message_id, checkpoint_files_json, source_proposed_plan_thread_id, source_proposed_plan_id ) VALUES ( 'turn:assistant', 'thread:assistant', 'completed', ${now}, ${now}, ${now}, 'message:assistant', '[]', NULL, NULL ) `; yield* sql` INSERT INTO orchestration_v2_projection_threads ( thread_id, project_id, title, default_provider, provider_instance_id, runtime_mode, interaction_mode, active_provider_thread_id, created_at, updated_at, archived_at, deleted_at, payload_json ) VALUES ('thread:active', 'project:search', 'Active', 'codex', 'codex', 'full-access', 'default', NULL, ${now}, ${now}, NULL, NULL, '{}'), ('thread:archived', 'project:search', 'Archived', 'codex', 'codex', 'full-access', 'default', NULL, ${now}, ${now}, ${now}, NULL, '{}'), ('thread:deleted', 'project:search', 'Deleted', 'codex', 'codex', 'full-access', 'default', NULL, ${now}, ${now}, NULL, ${now}, '{}') `; const result = yield* query.searchThreads({ query: "migration needle", limit: 20 }); assert.deepEqual( result.matches.map((match) => [match.threadId, match.source, match.snippet]), [ ["thread:active", "user", "migration needle active"], ["thread:assistant", "assistant", "migration needle answer"], ], ); }).pipe(Effect.provide(TestLayer)),);
import * as NodeServices from "@effect/platform-node/NodeServices";
import { assert, it } from "@effect/vitest";
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
import * as SqlClient from "effect/unstable/sql/SqlClient";
import { ServerConfig } from "../../config.ts";
import { SqlitePersistenceMemory } from "../../persistence/Layers/Sqlite.ts";
import * as ProjectEnrichment from "../../project/ProjectEnrichmentService.ts";
import * as ProjectFaviconResolver from "../../project/ProjectFaviconResolver.ts";
import * as RepositoryIdentityResolver from "../../project/RepositoryIdentityResolver.ts";
import { ProjectionSnapshotQuery } from "../Services/ProjectionSnapshotQuery.ts";
import * as ThreadBackgroundLiveness from "../ThreadBackgroundLiveness.ts";
import * as ThreadPlanProgress from "../ThreadPlanProgress.ts";
import { OrchestrationProjectionSnapshotQueryLive } from "./ProjectionSnapshotQuery.ts";
const metadataLayer = Layer.merge(
Layer.succeed(RepositoryIdentityResolver.RepositoryIdentityResolver, {
resolve: () => Effect.succeed(null),
}),
Layer.succeed(ProjectFaviconResolver.ProjectFaviconResolver, {
resolvePath: () => Effect.succeed(null),
}),
);
const TestLayer = OrchestrationProjectionSnapshotQueryLive.pipe(
Layer.provideMerge(ThreadBackgroundLiveness.layer),
Layer.provideMerge(ThreadPlanProgress.layer),
Layer.provideMerge(ProjectEnrichment.layer),
Layer.provideMerge(metadataLayer),
Layer.provideMerge(SqlitePersistenceMemory),
Layer.provide(ServerConfig.layerTest(process.cwd(), { prefix: "projection-search-test-" })),
Layer.provide(NodeServices.layer),
);
it.effect("search uses v2 visibility while legacy transcripts are still lazy", () =>
Effect.gen(function* () {
const sql = yield* SqlClient.SqlClient;
const query = yield* ProjectionSnapshotQuery;
const now = "2026-08-29T00:00:00.000Z";
yield* sql`
INSERT INTO projection_projects (
project_id, title, workspace_root, default_model_selection_json,
scripts_json, created_at, updated_at, deleted_at
) VALUES (
'project:search', 'Search', '/tmp/search', NULL,
'[]', ${now}, ${now}, NULL
)
`;
yield* sql`
INSERT INTO projection_threads (
thread_id, project_id, title, model_selection_json, runtime_mode,
interaction_mode, branch, worktree_path, latest_turn_id, created_at,
updated_at, archived_at, settled_override, settled_at, deleted_at
) VALUES
('thread:active', 'project:search', 'Active', '{"instanceId":"codex","model":"gpt-5.4"}', 'full-access', 'default', NULL, NULL, NULL, ${now}, ${now}, NULL, NULL, NULL, NULL),
('thread:archived', 'project:search', 'Archived', '{"instanceId":"codex","model":"gpt-5.4"}', 'full-access', 'default', NULL, NULL, NULL, ${now}, ${now}, NULL, NULL, NULL, NULL),
('thread:deleted', 'project:search', 'Deleted', '{"instanceId":"codex","model":"gpt-5.4"}', 'full-access', 'default', NULL, NULL, NULL, ${now}, ${now}, NULL, NULL, NULL, NULL),
('thread:assistant', 'project:search', 'Assistant', '{"instanceId":"codex","model":"gpt-5.4"}', 'full-access', 'default', NULL, NULL, 'turn:assistant', ${now}, ${now}, NULL, NULL, NULL, NULL)
`;
yield* sql`
INSERT INTO projection_thread_messages (
message_id, thread_id, turn_id, role, text, attachments_json,
is_streaming, created_at, updated_at
) VALUES
('message:active', 'thread:active', NULL, 'user', 'migration needle active', '[]', 0, ${now}, ${now}),
('message:archived', 'thread:archived', NULL, 'user', 'migration needle archived', '[]', 0, ${now}, ${now}),
('message:deleted', 'thread:deleted', NULL, 'user', 'migration needle deleted', '[]', 0, ${now}, ${now}),
('message:orphan-assistant', 'thread:assistant', NULL, 'assistant', 'migration needle orphan', '[]', 0, ${now}, ${now}),
('message:assistant', 'thread:assistant', 'turn:assistant', 'assistant', 'migration needle answer', '[]', 0, ${now}, ${now})
`;
yield* sql`
INSERT INTO projection_turns (
turn_id, thread_id, state, requested_at, started_at, completed_at,
assistant_message_id, checkpoint_files_json,
source_proposed_plan_thread_id, source_proposed_plan_id
) VALUES (
'turn:assistant', 'thread:assistant', 'completed', ${now}, ${now}, ${now},
'message:assistant', '[]', NULL, NULL
)
`;
yield* sql`
INSERT INTO orchestration_v2_projection_threads (
thread_id, project_id, title, default_provider, provider_instance_id,
runtime_mode, interaction_mode, active_provider_thread_id, created_at,
updated_at, archived_at, deleted_at, payload_json
) VALUES
('thread:active', 'project:search', 'Active', 'codex', 'codex', 'full-access', 'default', NULL, ${now}, ${now}, NULL, NULL, '{}'),
('thread:archived', 'project:search', 'Archived', 'codex', 'codex', 'full-access', 'default', NULL, ${now}, ${now}, ${now}, NULL, '{}'),
('thread:deleted', 'project:search', 'Deleted', 'codex', 'codex', 'full-access', 'default', NULL, ${now}, ${now}, NULL, ${now}, '{}')
`;
const result = yield* query.searchThreads({ query: "migration needle", limit: 20 });
assert.deepEqual(
result.matches.map((match) => [match.threadId, match.source, match.snippet]),
[
["thread:active", "user", "migration needle active"],
["thread:assistant", "assistant", "migration needle answer"],
],
);
}).pipe(Effect.provide(TestLayer)),
);