authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-10-05 18:39:51-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-10-05 18:39:51-07:00
logecfdda5fa804582066c1aa62afea419677c24883
treef55f08cf47c81cfa55d716e05a210c5534e2905a
parent4df84a7d03ff084ff4c6dd7aef54623984690fb7
signature Signed by PGP key B5690EEEBB952194

refactor: layer variables are named layer or layerXyz (#16282)

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

401 files changed, 7288 insertions(+), 7368 deletions(-)

apps/desktop/src/app/DesktopAppIdentity.test.ts+6-6
......@@ -38,7 +38,7 @@ interface ElectronAppCalls {
3838 readonly setName: string[];
3939}
4040
41const makeElectronAppLayer = (calls: ElectronAppCalls) =>
41const layerElectronApp = (calls: ElectronAppCalls) =>
4242 Layer.succeed(ElectronApp.ElectronApp, {
4343 metadata: Effect.die("unexpected metadata read"),
4444 name: Effect.succeed("T3 Code"),
......@@ -70,7 +70,7 @@ const makeElectronAppLayer = (calls: ElectronAppCalls) =>
7070 on: () => Effect.void,
7171 } satisfies ElectronApp.ElectronApp["Service"]);
7272
73const makeAssetsLayer = (png: Option.Option<string>) =>
73const layerAssets = (png: Option.Option<string>) =>
7474 Layer.succeed(DesktopAssets.DesktopAssets, {
7575 iconPaths: Effect.succeed({
7676 ico: Option.none(),
......@@ -80,7 +80,7 @@ const makeAssetsLayer = (png: Option.Option<string>) =>
8080 resolveResourcePath: () => Effect.succeedNone,
8181 } satisfies DesktopAssets.DesktopAssets["Service"]);
8282
83const makeEnvironmentLayer = (overrides: TestEnvironmentInput = {}) => {
83const layerEnvironment = (overrides: TestEnvironmentInput = {}) => {
8484 const { env, ...environmentOverrides } = overrides;
8585 return DesktopEnvironment.layer({
8686 ...defaultEnvironmentInput,
......@@ -138,9 +138,9 @@ const withIdentity = <A, E, R>(
138138 Effect.succeed(input.packageJson ?? '{"t3codeCommitHash":"abcdef1234567890"}'),
139139 }),
140140 ),
141 Layer.provideMerge(makeAssetsLayer(input.pngIconPath ?? Option.none())),
142 Layer.provideMerge(makeElectronAppLayer(calls)),
143 Layer.provideMerge(makeEnvironmentLayer(input.environment)),
141 Layer.provideMerge(layerAssets(input.pngIconPath ?? Option.none())),
142 Layer.provideMerge(layerElectronApp(calls)),
143 Layer.provideMerge(layerEnvironment(input.environment)),
144144 ),
145145 ),
146146 );
apps/desktop/src/app/DesktopAssets.test.ts+8-8
......@@ -11,7 +11,7 @@ import * as DesktopAssets from "./DesktopAssets.ts";
1111import * as DesktopConfig from "./DesktopConfig.ts";
1212import * as DesktopEnvironment from "./DesktopEnvironment.ts";
1313
14const environmentLayer = DesktopEnvironment.layer({
14const layerEnvironment = DesktopEnvironment.layer({
1515 dirname: "/repo/apps/desktop/dist-electron",
1616 homeDirectory: "/Users/alice",
1717 platform: "darwin",
......@@ -30,7 +30,7 @@ const environmentLayer = DesktopEnvironment.layer({
3030describe("DesktopAssets", () => {
3131 it.effect("uses canonical source-tree icons for unpackaged development", () =>
3232 Effect.gen(function* () {
33 const developmentEnvironmentLayer = DesktopEnvironment.layer({
33 const layerDevelopmentEnvironment = DesktopEnvironment.layer({
3434 dirname: "/repo/apps/desktop/dist-electron",
3535 homeDirectory: "/Users/alice",
3636 platform: "linux",
......@@ -49,13 +49,13 @@ describe("DesktopAssets", () => {
4949 ),
5050 ),
5151 );
52 const fileSystemLayer = FileSystem.layerNoop({
52 const layerFileSystem = FileSystem.layerNoop({
5353 exists: (path) => Effect.succeed(String(path).includes("/assets/dev/")),
5454 });
5555 const assets = yield* DesktopAssets.DesktopAssets.pipe(
5656 Effect.provide(
5757 DesktopAssets.layer.pipe(
58 Layer.provide(Layer.merge(fileSystemLayer, developmentEnvironmentLayer)),
58 Layer.provide(Layer.merge(layerFileSystem, layerDevelopmentEnvironment)),
5959 ),
6060 ),
6161 );
......@@ -79,13 +79,13 @@ describe("DesktopAssets", () => {
7979 pathOrDescriptor: candidatePath,
8080 description: "private filesystem diagnostic",
8181 });
82 const fileSystemLayer = FileSystem.layerNoop({
82 const layerFileSystem = FileSystem.layerNoop({
8383 exists: (path) => (path === candidatePath ? Effect.fail(cause) : Effect.succeed(false)),
8484 });
85 const assetsLayer = DesktopAssets.layer.pipe(
86 Layer.provide(Layer.merge(fileSystemLayer, environmentLayer)),
85 const layerAssets = DesktopAssets.layer.pipe(
86 Layer.provide(Layer.merge(layerFileSystem, layerEnvironment)),
8787 );
88 const assets = yield* DesktopAssets.DesktopAssets.pipe(Effect.provide(assetsLayer));
88 const assets = yield* DesktopAssets.DesktopAssets.pipe(Effect.provide(layerAssets));
8989
9090 const error = yield* assets.resolveResourcePath(fileName).pipe(Effect.flip);
9191
apps/desktop/src/app/DesktopClerk.test.ts+9-9
......@@ -38,7 +38,7 @@ import * as DesktopClerk from "./DesktopClerk.ts";
3838import * as DesktopEnvironment from "./DesktopEnvironment.ts";
3939import * as DesktopPreReadyFileSystem from "./DesktopPreReadyFileSystem.ts";
4040
41const makeDesktopClerkLayer = (
41const layerDesktopClerk = (
4242 isDevelopment = true,
4343 events: string[] = [],
4444 platform: NodeJS.Platform = "darwin",
......@@ -94,7 +94,7 @@ describe("DesktopClerk", () => {
9494 });
9595
9696 return Effect.gen(function* () {
97 yield* Effect.scoped(Layer.build(makeDesktopClerkLayer(true, events)));
97 yield* Effect.scoped(Layer.build(layerDesktopClerk(true, events)));
9898
9999 assert.deepEqual(createClerkBridgeMock.mock.calls, [
100100 [
......@@ -143,7 +143,7 @@ describe("DesktopClerk", () => {
143143 Effect.runSync(
144144 Effect.scoped(
145145 Layer.build(
146 makeDesktopClerkLayer(isDevelopment, events, platform, DesktopPreReadyFileSystem.layer),
146 layerDesktopClerk(isDevelopment, events, platform, DesktopPreReadyFileSystem.layer),
147147 ),
148148 ),
149149 );
......@@ -160,7 +160,7 @@ describe("DesktopClerk", () => {
160160 });
161161
162162 return Effect.gen(function* () {
163 const error = yield* Effect.scoped(Layer.build(makeDesktopClerkLayer())).pipe(Effect.flip);
163 const error = yield* Effect.scoped(Layer.build(layerDesktopClerk())).pipe(Effect.flip);
164164
165165 assert.instanceOf(error, DesktopClerk.DesktopClerkBridgeInitializationError);
166166 assert.equal(error.stateDir, "/tmp/t3-state");
......@@ -183,7 +183,7 @@ describe("DesktopClerk", () => {
183183 });
184184
185185 return Effect.gen(function* () {
186 const exit = yield* Effect.exit(Effect.scoped(Layer.build(makeDesktopClerkLayer(false))));
186 const exit = yield* Effect.exit(Effect.scoped(Layer.build(layerDesktopClerk(false))));
187187
188188 assert.equal(exit._tag, "Failure");
189189 if (exit._tag === "Failure") {
......@@ -222,7 +222,7 @@ describe("DesktopClerk", () => {
222222 assert.equal(quit.mock.calls.length, 0);
223223 assert.deepEqual(registeredEvents, ["open-url", "second-instance"]);
224224 }).pipe(
225 Effect.provide(makeDesktopClerkLayer()),
225 Effect.provide(layerDesktopClerk()),
226226 Effect.provideService(ElectronApp.ElectronApp, electronApp),
227227 Effect.provideService(ElectronWindow.ElectronWindow, electronWindow),
228228 );
......@@ -250,7 +250,7 @@ describe("DesktopClerk", () => {
250250 assert.equal(quit.mock.calls.length, 1);
251251 assert.deepEqual(registeredEvents, []);
252252 }).pipe(
253 Effect.provide(makeDesktopClerkLayer()),
253 Effect.provide(layerDesktopClerk()),
254254 Effect.provideService(ElectronApp.ElectronApp, electronApp),
255255 Effect.provideService(ElectronWindow.ElectronWindow, electronWindow),
256256 );
......@@ -296,7 +296,7 @@ it.effect(
296296 assert.equal(event.preventDefault.mock.calls.length, 1);
297297 }).pipe(
298298 Effect.scoped,
299 Effect.provide(makeDesktopClerkLayer()),
299 Effect.provide(layerDesktopClerk()),
300300 Effect.provideService(ElectronApp.ElectronApp, electronApp),
301301 Effect.provideService(ElectronWindow.ElectronWindow, electronWindow),
302302 );
......@@ -374,7 +374,7 @@ it.effect.each(["startup", "open-url"] as const)(
374374 assert.strictEqual(delivery?.flowId, request.flowId);
375375 assert.strictEqual(delivery?.returnUrl, request.returnUrl);
376376 }).pipe(
377 Effect.provide(makeDesktopClerkLayer(true, [], "darwin", undefined, shell)),
377 Effect.provide(layerDesktopClerk(true, [], "darwin", undefined, shell)),
378378 Effect.provideService(HostProcessArguments, entry === "startup" ? ["t3", link] : ["t3"]),
379379 Effect.provideService(ElectronApp.ElectronApp, electronApp),
380380 Effect.provideService(
apps/desktop/src/app/DesktopConnectionCatalogStore.test.ts+17-17
......@@ -28,7 +28,7 @@ const encodeLegacySavedEnvironments = Schema.encodeEffect(
2828 Schema.Struct({ version: Schema.Literal(1), records: Schema.Array(Schema.Unknown) }),
2929 ),
3030);
31function makeSafeStorageLayer(available: boolean, failDecrypt: Ref.Ref<boolean> | null = null) {
31function layerSafeStorageFor(available: boolean, failDecrypt: Ref.Ref<boolean> | null = null) {
3232 return Layer.succeed(ElectronSafeStorage.ElectronSafeStorage, {
3333 isEncryptionAvailable: Effect.succeed(available),
3434 encryptString: (value) => Effect.succeed(textEncoder.encode(`encrypted:${value}`)),
......@@ -50,13 +50,13 @@ function makeSafeStorageLayer(available: boolean, failDecrypt: Ref.Ref<boolean>
5050 } satisfies ElectronSafeStorage.ElectronSafeStorage["Service"]);
5151}
5252
53function makeLayer(
53function layerFor(
5454 baseDir: string,
5555 encryptionAvailable = true,
5656 failDecrypt: Ref.Ref<boolean> | null = null,
5757 fileSystemLayer: Layer.Layer<FileSystem.FileSystem> = NodeServices.layer,
5858) {
59 const environmentLayer = DesktopEnvironment.layer({
59 const layerEnvironment = DesktopEnvironment.layer({
6060 dirname: "/repo/apps/desktop/src",
6161 homeDirectory: baseDir,
6262 platform: "darwin",
......@@ -71,20 +71,20 @@ function makeLayer(
7171 Layer.mergeAll(NodeServices.layer, DesktopConfig.layerTest({ T3CODE_HOME: baseDir })),
7272 ),
7373 );
74 const safeStorageLayer = makeSafeStorageLayer(encryptionAvailable, failDecrypt);
75 const dependencies = Layer.mergeAll(
76 environmentLayer,
77 safeStorageLayer,
74 const layerSafeStorage = layerSafeStorageFor(encryptionAvailable, failDecrypt);
75 const layerDependencies = Layer.mergeAll(
76 layerEnvironment,
77 layerSafeStorage,
7878 NodeServices.layer,
7979 fileSystemLayer,
8080 );
81 const savedEnvironmentsLayer = DesktopSavedEnvironments.layer.pipe(
82 Layer.provideMerge(dependencies),
81 const layerSavedEnvironments = DesktopSavedEnvironments.layer.pipe(
82 Layer.provideMerge(layerDependencies),
8383 );
8484
8585 return DesktopConnectionCatalogStore.layer.pipe(
86 Layer.provideMerge(savedEnvironmentsLayer),
87 Layer.provideMerge(dependencies),
86 Layer.provideMerge(layerSavedEnvironments),
87 Layer.provideMerge(layerDependencies),
8888 );
8989}
9090
......@@ -97,7 +97,7 @@ const withStore = <A, E, R>(
9797 const baseDir = yield* fileSystem.makeTempDirectoryScoped({
9898 prefix: "t3-desktop-connection-catalog-test-",
9999 });
100 return yield* effect.pipe(Effect.provide(makeLayer(baseDir, encryptionAvailable)));
100 return yield* effect.pipe(Effect.provide(layerFor(baseDir, encryptionAvailable)));
101101 }).pipe(Effect.provide(NodeServices.layer), Effect.scoped);
102102
103103describe("DesktopConnectionCatalogStore", () => {
......@@ -280,14 +280,14 @@ describe("DesktopConnectionCatalogStore", () => {
280280 method: "readFileString",
281281 pathOrDescriptor: path.join(baseDir, "userdata", "connection-catalog.json"),
282282 });
283 const fileSystemLayer = Layer.succeed(
283 const layerFileSystem = Layer.succeed(
284284 FileSystem.FileSystem,
285285 FileSystem.makeNoop({
286286 readFileString: () => Effect.fail(permissionError),
287287 }),
288288 );
289289 const store = yield* DesktopConnectionCatalogStore.DesktopConnectionCatalogStore.pipe(
290 Effect.provide(makeLayer(baseDir, true, null, fileSystemLayer)),
290 Effect.provide(layerFor(baseDir, true, null, layerFileSystem)),
291291 );
292292
293293 const error = yield* store.get.pipe(Effect.flip);
......@@ -318,14 +318,14 @@ describe("DesktopConnectionCatalogStore", () => {
318318 method: "makeDirectory",
319319 pathOrDescriptor: path.join(baseDir, "userdata"),
320320 });
321 const fileSystemLayer = Layer.succeed(
321 const layerFileSystem = Layer.succeed(
322322 FileSystem.FileSystem,
323323 FileSystem.makeNoop({
324324 makeDirectory: () => Effect.fail(permissionError),
325325 }),
326326 );
327327 const store = yield* DesktopConnectionCatalogStore.DesktopConnectionCatalogStore.pipe(
328 Effect.provide(makeLayer(baseDir, true, null, fileSystemLayer)),
328 Effect.provide(layerFor(baseDir, true, null, layerFileSystem)),
329329 );
330330
331331 const error = yield* store.set("{}").pipe(Effect.flip);
......@@ -413,7 +413,7 @@ describe("DesktopConnectionCatalogStore", () => {
413413 prefix: "t3-desktop-connection-catalog-test-",
414414 });
415415 const failDecrypt = yield* Ref.make(false);
416 const layer = makeLayer(baseDir, true, failDecrypt);
416 const layer = layerFor(baseDir, true, failDecrypt);
417417 const store = yield* DesktopConnectionCatalogStore.DesktopConnectionCatalogStore.pipe(
418418 Effect.provide(layer),
419419 );
apps/desktop/src/app/DesktopEnvironment.test.ts+2-3
......@@ -20,7 +20,7 @@ const defaultInput = {
2020 runningUnderArm64Translation: false,
2121} satisfies DesktopEnvironment.MakeDesktopEnvironmentInput;
2222
23const makeEnvironmentLayer = (
23const layerEnvironment = (
2424 overrides: Partial<DesktopEnvironment.MakeDesktopEnvironmentInput> = {},
2525 env: Record<string, string | undefined> = {},
2626) =>
......@@ -36,8 +36,7 @@ const makeEnvironmentLayer = (
3636const makeEnvironment = (
3737 overrides: Partial<DesktopEnvironment.MakeDesktopEnvironmentInput> = {},
3838 env: Record<string, string | undefined> = {},
39) =>
40 DesktopEnvironment.DesktopEnvironment.pipe(Effect.provide(makeEnvironmentLayer(overrides, env)));
39) => DesktopEnvironment.DesktopEnvironment.pipe(Effect.provide(layerEnvironment(overrides, env)));
4140
4241describe("DesktopEnvironment", () => {
4342 it.effect("derives state paths and development identity inside Effect", () =>
apps/desktop/src/app/DesktopLifecycle.test.ts+24-24
......@@ -15,7 +15,7 @@ import * as DesktopShutdown from "./DesktopShutdown.ts";
1515import * as DesktopState from "./DesktopState.ts";
1616import * as DesktopWindow from "../window/DesktopWindow.ts";
1717
18function makeElectronAppLayer(
18function layerElectronApp(
1919 appListeners: Map<string, (...args: readonly unknown[]) => void>,
2020 quit: Effect.Effect<void> = Effect.void,
2121) {
......@@ -54,13 +54,13 @@ function makeElectronAppLayer(
5454 } satisfies ElectronApp.ElectronApp["Service"]);
5555}
5656
57const electronThemeLayer = Layer.succeed(ElectronTheme.ElectronTheme, {
57const layerElectronTheme = Layer.succeed(ElectronTheme.ElectronTheme, {
5858 shouldUseDarkColors: Effect.succeed(false),
5959 setSource: () => Effect.void,
6060 onUpdated: () => Effect.void,
6161});
6262
63function makeElectronWindowLayer(destroyAll: Effect.Effect<void> = Effect.void) {
63function layerElectronWindow(destroyAll: Effect.Effect<void> = Effect.void) {
6464 return Layer.succeed(ElectronWindow.ElectronWindow, {
6565 create: () => Effect.die("unexpected window creation"),
6666 main: Effect.die("unexpected main window read"),
......@@ -76,7 +76,7 @@ function makeElectronWindowLayer(destroyAll: Effect.Effect<void> = Effect.void)
7676 });
7777}
7878
79function makeDesktopWindowLayer(
79function layerDesktopWindow(
8080 input: {
8181 readonly activate?: Effect.Effect<void>;
8282 readonly flushMainWindowBounds?: Effect.Effect<void>;
......@@ -106,23 +106,23 @@ describe("DesktopLifecycle", () => {
106106 (platform) => {
107107 const appListeners = new Map<string, (...args: readonly unknown[]) => void>();
108108 let windowsDestroyed = false;
109 const environmentLayer = Layer.succeed(DesktopEnvironment.DesktopEnvironment, {
109 const layerEnvironment = Layer.succeed(DesktopEnvironment.DesktopEnvironment, {
110110 platform,
111111 isDevelopment: false,
112112 } as DesktopEnvironment.DesktopEnvironment["Service"]);
113113
114114 const layer = DesktopLifecycle.layer.pipe(
115 Layer.provideMerge(makeElectronAppLayer(appListeners)),
116 Layer.provideMerge(electronThemeLayer),
115 Layer.provideMerge(layerElectronApp(appListeners)),
116 Layer.provideMerge(layerElectronTheme),
117117 Layer.provideMerge(
118 makeElectronWindowLayer(
118 layerElectronWindow(
119119 Effect.sync(() => {
120120 windowsDestroyed = true;
121121 }),
122122 ),
123123 ),
124 Layer.provideMerge(makeDesktopWindowLayer()),
125 Layer.provideMerge(environmentLayer),
124 Layer.provideMerge(layerDesktopWindow()),
125 Layer.provideMerge(layerEnvironment),
126126 Layer.provideMerge(DesktopShutdown.layer),
127127 Layer.provideMerge(DesktopState.layer),
128128 );
......@@ -174,7 +174,7 @@ describe("DesktopLifecycle", () => {
174174 events.push("flush");
175175 });
176176
177 const desktopShutdownLayer = Layer.succeed(DesktopShutdown.DesktopShutdown, {
177 const layerDesktopShutdown = Layer.succeed(DesktopShutdown.DesktopShutdown, {
178178 request: Effect.sync(() => {
179179 events.push("request");
180180 }).pipe(Effect.andThen(Deferred.succeed(shutdownRequested, undefined)), Effect.asVoid),
......@@ -184,18 +184,18 @@ describe("DesktopLifecycle", () => {
184184 isComplete: Deferred.isDone(allowShutdown),
185185 });
186186
187 const environmentLayer = Layer.succeed(DesktopEnvironment.DesktopEnvironment, {
187 const layerEnvironment = Layer.succeed(DesktopEnvironment.DesktopEnvironment, {
188188 platform: "darwin",
189189 isDevelopment: false,
190190 } as DesktopEnvironment.DesktopEnvironment["Service"]);
191191
192192 const layer = DesktopLifecycle.layer.pipe(
193 Layer.provideMerge(makeElectronAppLayer(appListeners, quit)),
194 Layer.provideMerge(electronThemeLayer),
195 Layer.provideMerge(makeElectronWindowLayer(destroyAll)),
196 Layer.provideMerge(makeDesktopWindowLayer({ flushMainWindowBounds })),
197 Layer.provideMerge(environmentLayer),
198 Layer.provideMerge(desktopShutdownLayer),
193 Layer.provideMerge(layerElectronApp(appListeners, quit)),
194 Layer.provideMerge(layerElectronTheme),
195 Layer.provideMerge(layerElectronWindow(destroyAll)),
196 Layer.provideMerge(layerDesktopWindow({ flushMainWindowBounds })),
197 Layer.provideMerge(layerEnvironment),
198 Layer.provideMerge(layerDesktopShutdown),
199199 Layer.provideMerge(DesktopState.layer),
200200 );
201201
......@@ -226,16 +226,16 @@ describe("DesktopLifecycle", () => {
226226 const activate = Effect.sync(() => {
227227 activationCount += 1;
228228 });
229 const environmentLayer = Layer.succeed(DesktopEnvironment.DesktopEnvironment, {
229 const layerEnvironment = Layer.succeed(DesktopEnvironment.DesktopEnvironment, {
230230 platform: "darwin",
231231 isDevelopment: false,
232232 } as DesktopEnvironment.DesktopEnvironment["Service"]);
233233 const layer = DesktopLifecycle.layer.pipe(
234 Layer.provideMerge(makeElectronAppLayer(appListeners)),
235 Layer.provideMerge(electronThemeLayer),
236 Layer.provideMerge(makeElectronWindowLayer()),
237 Layer.provideMerge(makeDesktopWindowLayer({ activate })),
238 Layer.provideMerge(environmentLayer),
234 Layer.provideMerge(layerElectronApp(appListeners)),
235 Layer.provideMerge(layerElectronTheme),
236 Layer.provideMerge(layerElectronWindow()),
237 Layer.provideMerge(layerDesktopWindow({ activate })),
238 Layer.provideMerge(layerEnvironment),
239239 Layer.provideMerge(DesktopShutdown.layer),
240240 Layer.provideMerge(DesktopState.layer),
241241 );
apps/desktop/src/app/DesktopLinuxUrlHandler.test.ts+3-3
......@@ -52,7 +52,7 @@ const mockProcess = (exitCode: number, stalled = false) =>
5252 getOutputFd: () => Stream.empty,
5353 });
5454
55const makeHandlerLayer = (
55const layerHandler = (
5656 recorded: RecordedRegistration,
5757 input: {
5858 readonly environment?: Record<string, unknown>;
......@@ -143,12 +143,12 @@ const makeHandlerLayer = (
143143
144144const runRegister = (
145145 recorded: RecordedRegistration,
146 input: Parameters<typeof makeHandlerLayer>[1] = {},
146 input: Parameters<typeof layerHandler>[1] = {},
147147) =>
148148 Effect.gen(function* () {
149149 const handler = yield* DesktopLinuxUrlHandler.DesktopLinuxUrlHandler;
150150 yield* handler.register;
151 }).pipe(Effect.provide(makeHandlerLayer(recorded, input)));
151 }).pipe(Effect.provide(layerHandler(recorded, input)));
152152
153153const emptyRecording = (): RecordedRegistration => ({
154154 directories: [],
apps/desktop/src/app/DesktopObservability.test.ts+54-54
......@@ -52,7 +52,7 @@ const environmentInput = (baseDir: string) =>
5252 runningUnderArm64Translation: false,
5353 }) satisfies DesktopEnvironment.MakeDesktopEnvironmentInput;
5454
55const makeEnvironmentLayer = (
55const layerEnvironmentFor = (
5656 baseDir: string,
5757 isDevelopment = true,
5858 env: Readonly<Record<string, string | undefined>> = {},
......@@ -77,7 +77,7 @@ interface ExportedRequest {
7777}
7878
7979/** Answers every export with a 200 and keeps what was posted for assertions. */
80const collectorLayer = (requests: Array<ExportedRequest>) =>
80const layerCollector = (requests: Array<ExportedRequest>) =>
8181 Layer.succeed(
8282 HttpClient.HttpClient,
8383 HttpClient.make((request) =>
......@@ -94,7 +94,7 @@ const collectorLayer = (requests: Array<ExportedRequest>) =>
9494 );
9595
9696// A developer's own OTEL_* variables would otherwise pick the endpoints.
97const emptyEnv = ConfigProvider.layer(ConfigProvider.fromEnv({ env: {} }));
97const layerEmptyEnv = ConfigProvider.layer(ConfigProvider.fromEnv({ env: {} }));
9898
9999const encodeObservabilitySettingsFile = Schema.encodeSync(
100100 Schema.fromJsonString(
......@@ -103,7 +103,7 @@ const encodeObservabilitySettingsFile = Schema.encodeSync(
103103);
104104
105105const writeObservabilitySettings = Effect.fn(function* (
106 environmentLayer: ReturnType<typeof makeEnvironmentLayer>,
106 environmentLayer: ReturnType<typeof layerEnvironmentFor>,
107107 observability: Readonly<Record<string, string>>,
108108) {
109109 const fileSystem = yield* FileSystem.FileSystem;
......@@ -146,15 +146,15 @@ describe("DesktopObservability", () => {
146146 const baseDir = yield* fileSystem.makeTempDirectoryScoped({
147147 prefix: "t3-desktop-observability-test-",
148148 });
149 const environmentLayer = makeEnvironmentLayer(baseDir);
149 const layerEnvironment = layerEnvironmentFor(baseDir);
150150 const tracePath = yield* Effect.gen(function* () {
151151 const environment = yield* DesktopEnvironment.DesktopEnvironment;
152152 return environment.path.join(environment.logDir, "desktop.trace.ndjson");
153 }).pipe(Effect.provide(environmentLayer));
153 }).pipe(Effect.provide(layerEnvironment));
154154 const logPath = yield* Effect.gen(function* () {
155155 const environment = yield* DesktopEnvironment.DesktopEnvironment;
156156 return environment.path.join(environment.logDir, "desktop-main.log");
157 }).pipe(Effect.provide(environmentLayer));
157 }).pipe(Effect.provide(layerEnvironment));
158158
159159 yield* Effect.scoped(
160160 Effect.gen(function* () {
......@@ -162,7 +162,7 @@ describe("DesktopObservability", () => {
162162 yield* Effect.logInfo("desktop trace event");
163163 }).pipe(
164164 Effect.withSpan("desktop-observability-test"),
165 Effect.provide(DesktopObservability.layer.pipe(Layer.provideMerge(environmentLayer))),
165 Effect.provide(DesktopObservability.layer.pipe(Layer.provideMerge(layerEnvironment))),
166166 ),
167167 );
168168
......@@ -185,7 +185,7 @@ describe("DesktopObservability", () => {
185185 assert.isFalse(yield* fileSystem.exists(logPath));
186186 }).pipe(
187187 Effect.scoped,
188 Effect.provide(Layer.mergeAll(NodeServices.layer, NodeHttpClient.layerUndici, emptyEnv)),
188 Effect.provide(Layer.mergeAll(NodeServices.layer, NodeHttpClient.layerUndici, layerEmptyEnv)),
189189 ),
190190 );
191191
......@@ -195,15 +195,15 @@ describe("DesktopObservability", () => {
195195 const baseDir = yield* fileSystem.makeTempDirectoryScoped({
196196 prefix: "t3-desktop-backend-output-log-test-",
197197 });
198 const environmentLayer = makeEnvironmentLayer(baseDir, false);
198 const layerEnvironment = layerEnvironmentFor(baseDir, false);
199199 const logPath = yield* Effect.gen(function* () {
200200 const environment = yield* DesktopEnvironment.DesktopEnvironment;
201201 return environment.path.join(environment.logDir, "server-child.log");
202 }).pipe(Effect.provide(environmentLayer));
202 }).pipe(Effect.provide(layerEnvironment));
203203 const tracePath = yield* Effect.gen(function* () {
204204 const environment = yield* DesktopEnvironment.DesktopEnvironment;
205205 return environment.path.join(environment.logDir, "desktop.trace.ndjson");
206 }).pipe(Effect.provide(environmentLayer));
206 }).pipe(Effect.provide(layerEnvironment));
207207
208208 yield* Effect.gen(function* () {
209209 const factory = yield* DesktopObservability.DesktopBackendOutputLogFactory;
......@@ -219,7 +219,7 @@ describe("DesktopObservability", () => {
219219 yield* outputLog.discardSession;
220220 }).pipe(
221221 Effect.annotateLogs({ runId: "test-run" }),
222 Effect.provide(DesktopObservability.layer.pipe(Layer.provideMerge(environmentLayer))),
222 Effect.provide(DesktopObservability.layer.pipe(Layer.provideMerge(layerEnvironment))),
223223 );
224224
225225 const log = yield* fileSystem.readFileString(logPath);
......@@ -263,7 +263,7 @@ describe("DesktopObservability", () => {
263263 );
264264 }).pipe(
265265 Effect.scoped,
266 Effect.provide(Layer.mergeAll(NodeServices.layer, NodeHttpClient.layerUndici, emptyEnv)),
266 Effect.provide(Layer.mergeAll(NodeServices.layer, NodeHttpClient.layerUndici, layerEmptyEnv)),
267267 ),
268268 );
269269
......@@ -273,11 +273,11 @@ describe("DesktopObservability", () => {
273273 const baseDir = yield* fileSystem.makeTempDirectoryScoped({
274274 prefix: "t3-desktop-backend-output-snapshot-test-",
275275 });
276 const environmentLayer = makeEnvironmentLayer(baseDir, false);
276 const layerEnvironment = layerEnvironmentFor(baseDir, false);
277277 const logPath = yield* Effect.gen(function* () {
278278 const environment = yield* DesktopEnvironment.DesktopEnvironment;
279279 return environment.path.join(environment.logDir, "server-child.log");
280 }).pipe(Effect.provide(environmentLayer));
280 }).pipe(Effect.provide(layerEnvironment));
281281
282282 yield* Effect.gen(function* () {
283283 const factory = yield* DesktopObservability.DesktopBackendOutputLogFactory;
......@@ -289,7 +289,7 @@ describe("DesktopObservability", () => {
289289 yield* outputLog.persistFailure({ details: "code=1" });
290290 }).pipe(
291291 Effect.annotateLogs({ runId: "test-run" }),
292 Effect.provide(DesktopObservability.layer.pipe(Layer.provideMerge(environmentLayer))),
292 Effect.provide(DesktopObservability.layer.pipe(Layer.provideMerge(layerEnvironment))),
293293 );
294294
295295 const records = yield* Effect.forEach(
......@@ -303,7 +303,7 @@ describe("DesktopObservability", () => {
303303 assert.equal(records.at(-1)?.annotations.details, "code=1");
304304 }).pipe(
305305 Effect.scoped,
306 Effect.provide(Layer.mergeAll(NodeServices.layer, NodeHttpClient.layerUndici, emptyEnv)),
306 Effect.provide(Layer.mergeAll(NodeServices.layer, NodeHttpClient.layerUndici, layerEmptyEnv)),
307307 ),
308308 );
309309
......@@ -313,11 +313,11 @@ describe("DesktopObservability", () => {
313313 const baseDir = yield* fileSystem.makeTempDirectoryScoped({
314314 prefix: "t3-desktop-backend-output-bound-test-",
315315 });
316 const environmentLayer = makeEnvironmentLayer(baseDir, false);
316 const layerEnvironment = layerEnvironmentFor(baseDir, false);
317317 const logPath = yield* Effect.gen(function* () {
318318 const environment = yield* DesktopEnvironment.DesktopEnvironment;
319319 return environment.path.join(environment.logDir, "server-child.log");
320 }).pipe(Effect.provide(environmentLayer));
320 }).pipe(Effect.provide(layerEnvironment));
321321 const maxBufferedBytes = 1024 * 1024;
322322 const discardedPrefixBytes = 128;
323323 const output = new Uint8Array(maxBufferedBytes + discardedPrefixBytes);
......@@ -332,7 +332,7 @@ describe("DesktopObservability", () => {
332332 yield* outputLog.writeOutputChunk("stderr", output);
333333 yield* outputLog.persistFailure({ details: "code=1" });
334334 }).pipe(
335 Effect.provide(DesktopObservability.layer.pipe(Layer.provideMerge(environmentLayer))),
335 Effect.provide(DesktopObservability.layer.pipe(Layer.provideMerge(layerEnvironment))),
336336 ),
337337 );
338338
......@@ -347,7 +347,7 @@ describe("DesktopObservability", () => {
347347 assert.isFalse(text.includes("y"));
348348 }).pipe(
349349 Effect.scoped,
350 Effect.provide(Layer.mergeAll(NodeServices.layer, NodeHttpClient.layerUndici, emptyEnv)),
350 Effect.provide(Layer.mergeAll(NodeServices.layer, NodeHttpClient.layerUndici, layerEmptyEnv)),
351351 ),
352352 );
353353
......@@ -357,11 +357,11 @@ describe("DesktopObservability", () => {
357357 const baseDir = yield* fileSystem.makeTempDirectoryScoped({
358358 prefix: "t3-desktop-backend-output-chunks-test-",
359359 });
360 const environmentLayer = makeEnvironmentLayer(baseDir, false);
360 const layerEnvironment = layerEnvironmentFor(baseDir, false);
361361 const logPath = yield* Effect.gen(function* () {
362362 const environment = yield* DesktopEnvironment.DesktopEnvironment;
363363 return environment.path.join(environment.logDir, "server-child.log");
364 }).pipe(Effect.provide(environmentLayer));
364 }).pipe(Effect.provide(layerEnvironment));
365365
366366 yield* Effect.scoped(
367367 Effect.gen(function* () {
......@@ -373,7 +373,7 @@ describe("DesktopObservability", () => {
373373 }
374374 yield* outputLog.persistFailure({ details: "code=1" });
375375 }).pipe(
376 Effect.provide(DesktopObservability.layer.pipe(Layer.provideMerge(environmentLayer))),
376 Effect.provide(DesktopObservability.layer.pipe(Layer.provideMerge(layerEnvironment))),
377377 ),
378378 );
379379
......@@ -381,7 +381,7 @@ describe("DesktopObservability", () => {
381381 assert.equal(lines.length, 258);
382382 }).pipe(
383383 Effect.scoped,
384 Effect.provide(Layer.mergeAll(NodeServices.layer, NodeHttpClient.layerUndici, emptyEnv)),
384 Effect.provide(Layer.mergeAll(NodeServices.layer, NodeHttpClient.layerUndici, layerEmptyEnv)),
385385 ),
386386 );
387387
......@@ -392,19 +392,19 @@ describe("DesktopObservability", () => {
392392 const baseDir = yield* fileSystem.makeTempDirectoryScoped({
393393 prefix: "t3-desktop-observability-test-",
394394 });
395 const environmentLayer = makeEnvironmentLayer(baseDir, true, {
395 const layerEnvironment = layerEnvironmentFor(baseDir, true, {
396396 T3CODE_OTLP_LOGS_URL: "https://collector.example.com/v1/logs",
397397 T3CODE_OTLP_HEADERS: "x-scope=desktop",
398398 });
399399 const tracePath = yield* Effect.gen(function* () {
400400 const environment = yield* DesktopEnvironment.DesktopEnvironment;
401401 return environment.path.join(environment.logDir, "desktop.trace.ndjson");
402 }).pipe(Effect.provide(environmentLayer));
402 }).pipe(Effect.provide(layerEnvironment));
403403
404404 yield* Effect.scoped(
405405 Effect.logInfo("desktop log export").pipe(
406406 Effect.withSpan("desktop-log-export-test"),
407 Effect.provide(DesktopObservability.layer.pipe(Layer.provideMerge(environmentLayer))),
407 Effect.provide(DesktopObservability.layer.pipe(Layer.provideMerge(layerEnvironment))),
408408 ),
409409 );
410410
......@@ -427,7 +427,7 @@ describe("DesktopObservability", () => {
427427 assert.lengthOf(record?.events ?? [], 0);
428428 }).pipe(
429429 Effect.scoped,
430 Effect.provide(Layer.mergeAll(NodeServices.layer, collectorLayer(requests), emptyEnv)),
430 Effect.provide(Layer.mergeAll(NodeServices.layer, layerCollector(requests), layerEmptyEnv)),
431431 );
432432 });
433433
......@@ -438,16 +438,16 @@ describe("DesktopObservability", () => {
438438 const baseDir = yield* fileSystem.makeTempDirectoryScoped({
439439 prefix: "t3-desktop-observability-test-",
440440 });
441 const environmentLayer = makeEnvironmentLayer(baseDir, true, {
441 const layerEnvironment = layerEnvironmentFor(baseDir, true, {
442442 T3CODE_OTLP_HEADERS: "x-scope=desktop",
443443 });
444 yield* writeObservabilitySettings(environmentLayer, {
444 yield* writeObservabilitySettings(layerEnvironment, {
445445 otlpLogsUrl: "https://settings.example.com/v1/logs",
446446 });
447447
448448 yield* Effect.scoped(
449449 Effect.logInfo("desktop otel export").pipe(
450 Effect.provide(DesktopObservability.layer.pipe(Layer.provideMerge(environmentLayer))),
450 Effect.provide(DesktopObservability.layer.pipe(Layer.provideMerge(layerEnvironment))),
451451 ),
452452 );
453453
......@@ -462,7 +462,7 @@ describe("DesktopObservability", () => {
462462 Effect.provide(
463463 Layer.mergeAll(
464464 NodeServices.layer,
465 collectorLayer(requests),
465 layerCollector(requests),
466466 ConfigProvider.layer(
467467 ConfigProvider.fromEnv({
468468 env: {
......@@ -484,13 +484,13 @@ describe("DesktopObservability", () => {
484484 const baseDir = yield* fileSystem.makeTempDirectoryScoped({
485485 prefix: "t3-desktop-observability-test-",
486486 });
487 const environmentLayer = makeEnvironmentLayer(baseDir, true, {
487 const layerEnvironment = layerEnvironmentFor(baseDir, true, {
488488 T3CODE_OTLP_LOGS_URL: "https://collector.example.com/v1/logs",
489489 });
490490
491491 yield* Effect.scoped(
492492 Effect.logInfo("desktop service name").pipe(
493 Effect.provide(DesktopObservability.layer.pipe(Layer.provideMerge(environmentLayer))),
493 Effect.provide(DesktopObservability.layer.pipe(Layer.provideMerge(layerEnvironment))),
494494 ),
495495 );
496496
......@@ -505,7 +505,7 @@ describe("DesktopObservability", () => {
505505 Effect.provide(
506506 Layer.mergeAll(
507507 NodeServices.layer,
508 collectorLayer(requests),
508 layerCollector(requests),
509509 ConfigProvider.layer(
510510 ConfigProvider.fromEnv({
511511 env: {
......@@ -527,16 +527,16 @@ describe("DesktopObservability", () => {
527527 const baseDir = yield* fileSystem.makeTempDirectoryScoped({
528528 prefix: "t3-desktop-observability-test-",
529529 });
530 const environmentLayer = makeEnvironmentLayer(baseDir, true, {
530 const layerEnvironment = layerEnvironmentFor(baseDir, true, {
531531 T3CODE_OTLP_HEADERS: "x-scope=desktop",
532532 });
533 yield* writeObservabilitySettings(environmentLayer, {
533 yield* writeObservabilitySettings(layerEnvironment, {
534534 otlpLogsUrl: "https://settings.example.com/v1/logs",
535535 });
536536
537537 yield* Effect.scoped(
538538 Effect.logInfo("desktop otel off").pipe(
539 Effect.provide(DesktopObservability.layer.pipe(Layer.provideMerge(environmentLayer))),
539 Effect.provide(DesktopObservability.layer.pipe(Layer.provideMerge(layerEnvironment))),
540540 ),
541541 );
542542
......@@ -546,7 +546,7 @@ describe("DesktopObservability", () => {
546546 Effect.provide(
547547 Layer.mergeAll(
548548 NodeServices.layer,
549 collectorLayer(requests),
549 layerCollector(requests),
550550 ConfigProvider.layer(
551551 ConfigProvider.fromEnv({
552552 env: {
......@@ -567,13 +567,13 @@ describe("DesktopObservability", () => {
567567 const baseDir = yield* fileSystem.makeTempDirectoryScoped({
568568 prefix: "t3-desktop-observability-test-",
569569 });
570 const environmentLayer = makeEnvironmentLayer(baseDir, true, {
570 const layerEnvironment = layerEnvironmentFor(baseDir, true, {
571571 T3CODE_OTLP_LOGS_URL: "https://collector.example.com/v1/logs",
572572 });
573573
574574 yield* Effect.scoped(
575575 Effect.void.pipe(
576 Effect.provide(DesktopObservability.layer.pipe(Layer.provideMerge(environmentLayer))),
576 Effect.provide(DesktopObservability.layer.pipe(Layer.provideMerge(layerEnvironment))),
577577 ),
578578 );
579579
......@@ -583,7 +583,7 @@ describe("DesktopObservability", () => {
583583 Effect.provide(
584584 Layer.mergeAll(
585585 NodeServices.layer,
586 collectorLayer(requests),
586 layerCollector(requests),
587587 ConfigProvider.layer(ConfigProvider.fromEnv({ env: { OTEL_SDK_DISABLED: "1" } })),
588588 ),
589589 ),
......@@ -597,8 +597,8 @@ describe("DesktopObservability", () => {
597597 const baseDir = yield* fileSystem.makeTempDirectoryScoped({
598598 prefix: "t3-desktop-observability-test-",
599599 });
600 const environmentLayer = makeEnvironmentLayer(baseDir);
601 yield* writeObservabilitySettings(environmentLayer, {
600 const layerEnvironment = layerEnvironmentFor(baseDir);
601 yield* writeObservabilitySettings(layerEnvironment, {
602602 otlpTracesUrl: "https://settings.example.com/v1/traces",
603603 otlpLogsUrl: "https://settings.example.com/v1/logs",
604604 // The main process records no metrics yet, so this endpoint must
......@@ -609,7 +609,7 @@ describe("DesktopObservability", () => {
609609 yield* Effect.scoped(
610610 Effect.logInfo("desktop log export from settings").pipe(
611611 Effect.withSpan("desktop-settings-export-test"),
612 Effect.provide(DesktopObservability.layer.pipe(Layer.provideMerge(environmentLayer))),
612 Effect.provide(DesktopObservability.layer.pipe(Layer.provideMerge(layerEnvironment))),
613613 ),
614614 );
615615
......@@ -627,7 +627,7 @@ describe("DesktopObservability", () => {
627627 );
628628 }).pipe(
629629 Effect.scoped,
630 Effect.provide(Layer.mergeAll(NodeServices.layer, collectorLayer(requests), emptyEnv)),
630 Effect.provide(Layer.mergeAll(NodeServices.layer, layerCollector(requests), layerEmptyEnv)),
631631 );
632632 });
633633
......@@ -643,7 +643,7 @@ describe("DesktopObservability", () => {
643643 Effect.logInfo("desktop log stays local").pipe(
644644 Effect.withSpan("desktop-offline-test"),
645645 Effect.provide(
646 DesktopObservability.layer.pipe(Layer.provideMerge(makeEnvironmentLayer(baseDir))),
646 DesktopObservability.layer.pipe(Layer.provideMerge(layerEnvironmentFor(baseDir))),
647647 ),
648648 ),
649649 );
......@@ -651,7 +651,7 @@ describe("DesktopObservability", () => {
651651 assert.lengthOf(requests, 0);
652652 }).pipe(
653653 Effect.scoped,
654 Effect.provide(Layer.mergeAll(NodeServices.layer, collectorLayer(requests), emptyEnv)),
654 Effect.provide(Layer.mergeAll(NodeServices.layer, layerCollector(requests), layerEmptyEnv)),
655655 );
656656 });
657657
......@@ -662,8 +662,8 @@ describe("DesktopObservability", () => {
662662 const baseDir = yield* fileSystem.makeTempDirectoryScoped({
663663 prefix: "t3-desktop-observability-test-",
664664 });
665 const environmentLayer = makeEnvironmentLayer(baseDir);
666 yield* writeObservabilitySettings(environmentLayer, {
665 const layerEnvironment = layerEnvironmentFor(baseDir);
666 yield* writeObservabilitySettings(layerEnvironment, {
667667 otlpTracesUrl: "https://settings.example.com/v1/traces",
668668 otlpLogsUrl: "https://settings.example.com/v1/logs",
669669 });
......@@ -671,7 +671,7 @@ describe("DesktopObservability", () => {
671671 yield* Effect.scoped(
672672 Effect.logInfo("desktop log stays local when disabled").pipe(
673673 Effect.withSpan("desktop-disabled-test"),
674 Effect.provide(DesktopObservability.layer.pipe(Layer.provideMerge(environmentLayer))),
674 Effect.provide(DesktopObservability.layer.pipe(Layer.provideMerge(layerEnvironment))),
675675 ),
676676 );
677677
......@@ -681,7 +681,7 @@ describe("DesktopObservability", () => {
681681 Effect.provide(
682682 Layer.mergeAll(
683683 NodeServices.layer,
684 collectorLayer(requests),
684 layerCollector(requests),
685685 ConfigProvider.layer(ConfigProvider.fromEnv({ env: { OTEL_SDK_DISABLED: "true" } })),
686686 ),
687687 ),
apps/desktop/src/app/DesktopObservability.ts+19-13
......@@ -2,9 +2,9 @@ import { PRIMARY_LOCAL_ENVIRONMENT_ID } from "@t3tools/contracts";
22import {
33 makeLocalFileTracer,
44 makeTraceSink,
5 otlpSerializationLayer,
65 type SignalExport,
76} from "@t3tools/shared/observability";
7import * as SharedObservability from "@t3tools/shared/observability";
88import * as OtelEnvironment from "@t3tools/shared/otelEnvironment";
99import {
1010 parsePersistedServerObservabilitySettings,
......@@ -559,7 +559,7 @@ const makeBackendOutputLogShape = (
559559 }),
560560 });
561561
562const backendOutputLogFactoryLayer = Layer.effect(
562const layerBackendOutputLogFactory = Layer.effect(
563563 DesktopBackendOutputLogFactory,
564564 Effect.gen(function* () {
565565 const environment = yield* DesktopEnvironment.DesktopEnvironment;
......@@ -621,7 +621,7 @@ const backendOutputLogFactoryLayer = Layer.effect(
621621 * one read of the environment and Settings, and because a process gets exactly
622622 * one logger set.
623623 */
624const telemetryLayer = Layer.unwrap(
624const layerTelemetry = Layer.unwrap(
625625 Effect.gen(function* () {
626626 const environment = yield* DesktopEnvironment.DesktopEnvironment;
627627 const endpoints = yield* resolveOtlpEndpoints;
......@@ -645,7 +645,7 @@ const telemetryLayer = Layer.unwrap(
645645 // while the OTLP logger carries every message as a log record stamped
646646 // with its trace and span ids. Keeping both would export every in-span
647647 // message twice.
648 const loggerLayer = Logger.layer(
648 const layerLogger = Logger.layer(
649649 endpoints.logs === undefined
650650 ? [Logger.consolePretty(), Logger.tracerLogger]
651651 : [
......@@ -661,11 +661,13 @@ const telemetryLayer = Layer.unwrap(
661661 ).pipe(
662662 Layer.provide(OtlpExporter.layerFlusher),
663663 Layer.provide(
664 otlpSerializationLayer(endpoints.logs?.export.protocol ?? environment.otlpProtocol),
664 SharedObservability.layerOtlpSerialization(
665 endpoints.logs?.export.protocol ?? environment.otlpProtocol,
666 ),
665667 ),
666668 );
667669
668 const tracerLayer = Layer.unwrap(
670 const layerTracer = Layer.unwrap(
669671 Effect.gen(function* () {
670672 const tracePath = environment.path.join(environment.logDir, "desktop.trace.ndjson");
671673 const sink = yield* makeTraceSink({
......@@ -682,7 +684,11 @@ const telemetryLayer = Layer.unwrap(
682684 exportInterval: `${endpoints.traces.export.exportIntervalMs} millis`,
683685 headers: endpoints.traces.export.headers,
684686 resource,
685 }).pipe(Effect.provide(otlpSerializationLayer(endpoints.traces.export.protocol)));
687 }).pipe(
688 Effect.provide(
689 SharedObservability.layerOtlpSerialization(endpoints.traces.export.protocol),
690 ),
691 );
686692 const tracer = yield* makeLocalFileTracer({
687693 filePath: tracePath,
688694 maxBytes: DESKTOP_LOG_FILE_MAX_BYTES,
......@@ -710,23 +716,23 @@ const telemetryLayer = Layer.unwrap(
710716 // exportInterval: `${endpoints.metrics.export.exportIntervalMs} millis`,
711717 // headers: endpoints.metrics.export.headers,
712718 // resource,
713 // }).pipe(Layer.provide(otlpSerializationLayer(endpoints.metrics.export.protocol)));
719 // }).pipe(Layer.provide(SharedObservability.layerOtlpSerialization(endpoints.metrics.export.protocol)));
714720
715721 // Logged once the loggers above are installed, so the warnings use them.
716 const otelWarningsLayer = Layer.effectDiscard(
722 const layerOtelWarnings = Layer.effectDiscard(
717723 Effect.forEach(endpoints.warnings, (warning) => Effect.logWarning(warning)),
718724 );
719725
720 return otelWarningsLayer.pipe(
721 Layer.provideMerge(Layer.mergeAll(loggerLayer, tracerLayer)),
726 return layerOtelWarnings.pipe(
727 Layer.provideMerge(Layer.mergeAll(layerLogger, layerTracer)),
722728 Layer.provide(OtelEnvironment.layerResourceAttributes(endpoints.resourceAttributes)),
723729 );
724730 }),
725731);
726732
727733export const layer = Layer.mergeAll(
728 backendOutputLogFactoryLayer,
729 telemetryLayer,
734 layerBackendOutputLogFactory,
735 layerTelemetry,
730736 Layer.succeed(References.MinimumLogLevel, "Info"),
731737 Layer.succeed(Tracer.MinimumTraceLevel, "Info"),
732738 Layer.succeed(References.TracerTimingEnabled, true),
apps/desktop/src/app/DesktopPreReadyPlatform.test.ts+5-5
......@@ -167,11 +167,11 @@ describe("DesktopPreReadyPlatform", () => {
167167 events.push("pre-ready");
168168 });
169169
170 const preReadyLayer = DesktopPreReadyPlatform.layer.pipe(
170 const layerPreReady = DesktopPreReadyPlatform.layer.pipe(
171171 Layer.provide(Layer.succeed(HostProcessPlatform, "darwin")),
172172 );
173173
174 const clerkShapedLayer = Layer.effect(
174 const layerClerkShaped = Layer.effect(
175175 ClerkShaped,
176176 Effect.promise(() => Promise.resolve()).pipe(
177177 Effect.map(() => {
......@@ -181,15 +181,15 @@ describe("DesktopPreReadyPlatform", () => {
181181 ),
182182 );
183183
184 const runtimeLayer = clerkShapedLayer.pipe(
184 const layerRuntime = layerClerkShaped.pipe(
185185 Layer.flatMap((clerkContext) => Layer.succeedContext(clerkContext)),
186 Layer.provideMerge(preReadyLayer),
186 Layer.provideMerge(layerPreReady),
187187 );
188188
189189 const result = yield* Effect.all({
190190 clerk: ClerkShaped,
191191 preReady: DesktopPreReadyPlatform.DesktopPreReadyElectronOptions,
192 }).pipe(Effect.provide(runtimeLayer));
192 }).pipe(Effect.provide(layerRuntime));
193193
194194 assert.deepEqual(result, {
195195 clerk: { ready: true },
apps/desktop/src/backend/DesktopBackendConfiguration.test.ts+52-52
......@@ -35,7 +35,7 @@ const isDesktopBackendObservabilitySettingsReadError = Schema.is(
3535 DesktopBackendConfiguration.DesktopBackendObservabilitySettingsReadError,
3636);
3737
38const serverExposureLayer = Layer.succeed(DesktopServerExposure.DesktopServerExposure, {
38const layerServerExposure = Layer.succeed(DesktopServerExposure.DesktopServerExposure, {
3939 getState: Effect.die("unexpected getState"),
4040 backendConfig: Effect.succeed({
4141 port: 4888,
......@@ -50,7 +50,7 @@ const serverExposureLayer = Layer.succeed(DesktopServerExposure.DesktopServerExp
5050 getAdvertisedEndpoints: Effect.succeed([]),
5151} satisfies DesktopServerExposure.DesktopServerExposure["Service"]);
5252
53function makeEnvironmentLayer(
53function layerEnvironment(
5454 baseDir: string,
5555 options?: {
5656 readonly appPath?: string;
......@@ -122,11 +122,11 @@ const withHarness = <A, E, R>(
122122 return yield* effect.pipe(
123123 Effect.provide(
124124 DesktopBackendConfiguration.layer.pipe(
125 Layer.provideMerge(serverExposureLayer),
125 Layer.provideMerge(layerServerExposure),
126126 Layer.provideMerge(DesktopAppSettings.layerTest()),
127127 Layer.provideMerge(DesktopWslEnvironment.layerTest()),
128128 Layer.provideMerge(DesktopWslServerTree.layerTest()),
129 Layer.provideMerge(makeEnvironmentLayer(baseDir)),
129 Layer.provideMerge(layerEnvironment(baseDir)),
130130 ),
131131 ),
132132 );
......@@ -182,7 +182,7 @@ const withPackagedWslHarness = <A, E, R>(
182182 mountedAppRoot,
183183 mountedEntryPath,
184184 } satisfies PackagedWslHarnessContext;
185 const serverTreeLayer = input.forbidFallback
185 const layerServerTree = input.forbidFallback
186186 ? Layer.succeed(
187187 DesktopWslServerTree.DesktopWslServerTree,
188188 DesktopWslServerTree.DesktopWslServerTree.of({
......@@ -200,9 +200,9 @@ const withPackagedWslHarness = <A, E, R>(
200200 return yield* effect(context).pipe(
201201 Effect.provide(
202202 DesktopBackendConfiguration.layer.pipe(
203 Layer.provideMerge(serverExposureLayer),
203 Layer.provideMerge(layerServerExposure),
204204 Layer.provideMerge(DesktopAppSettings.layerTest()),
205 Layer.provideMerge(serverTreeLayer),
205 Layer.provideMerge(layerServerTree),
206206 Layer.provideMerge(
207207 DesktopWslEnvironment.layerTest({
208208 isAvailable: true,
......@@ -213,7 +213,7 @@ const withPackagedWslHarness = <A, E, R>(
213213 }),
214214 ),
215215 Layer.provideMerge(
216 makeEnvironmentLayer(baseDir, {
216 layerEnvironment(baseDir, {
217217 appPath: baseDir,
218218 platform: "win32",
219219 resourcesPath: baseDir,
......@@ -276,7 +276,7 @@ describe("DesktopBackendConfiguration", () => {
276276 }).pipe(
277277 Effect.provide(
278278 DesktopBackendConfiguration.layer.pipe(
279 Layer.provideMerge(serverExposureLayer),
279 Layer.provideMerge(layerServerExposure),
280280 Layer.provideMerge(DesktopAppSettings.layerTest()),
281281 Layer.provideMerge(DesktopWslEnvironment.layerTest()),
282282 Layer.provideMerge(
......@@ -289,7 +289,7 @@ describe("DesktopBackendConfiguration", () => {
289289 ),
290290 ),
291291 Layer.provideMerge(
292 makeEnvironmentLayer(baseDir, {
292 layerEnvironment(baseDir, {
293293 appPath: `${resourcesPath}/app.asar`,
294294 platform: "win32",
295295 resourcesPath,
......@@ -338,7 +338,7 @@ describe("DesktopBackendConfiguration", () => {
338338 }).pipe(
339339 Effect.provide(
340340 DesktopBackendConfiguration.layer.pipe(
341 Layer.provideMerge(serverExposureLayer),
341 Layer.provideMerge(layerServerExposure),
342342 Layer.provideMerge(DesktopAppSettings.layerTest()),
343343 Layer.provideMerge(DesktopWslServerTree.layerTest()),
344344 Layer.provideMerge(
......@@ -363,7 +363,7 @@ describe("DesktopBackendConfiguration", () => {
363363 }),
364364 ),
365365 Layer.provideMerge(
366 makeEnvironmentLayer(baseDir, {
366 layerEnvironment(baseDir, {
367367 appPath: baseDir,
368368 platform: "win32",
369369 resourcesPath: baseDir,
......@@ -660,7 +660,7 @@ describe("DesktopBackendConfiguration", () => {
660660 }).pipe(
661661 Effect.provide(
662662 DesktopBackendConfiguration.layer.pipe(
663 Layer.provideMerge(serverExposureLayer),
663 Layer.provideMerge(layerServerExposure),
664664 Layer.provideMerge(DesktopAppSettings.layerTest()),
665665 Layer.provideMerge(DesktopWslServerTree.layerTest()),
666666 Layer.provideMerge(
......@@ -673,7 +673,7 @@ describe("DesktopBackendConfiguration", () => {
673673 }),
674674 ),
675675 Layer.provideMerge(
676 makeEnvironmentLayer(baseDir, {
676 layerEnvironment(baseDir, {
677677 appPath: baseDir,
678678 devServerUrl,
679679 isPackaged: true,
......@@ -788,7 +788,7 @@ describe("DesktopBackendConfiguration", () => {
788788 }).pipe(
789789 Effect.provide(
790790 DesktopBackendConfiguration.layer.pipe(
791 Layer.provideMerge(serverExposureLayer),
791 Layer.provideMerge(layerServerExposure),
792792 Layer.provideMerge(DesktopAppSettings.layerTest()),
793793 Layer.provideMerge(DesktopWslServerTree.layerTest()),
794794 Layer.provideMerge(
......@@ -799,7 +799,7 @@ describe("DesktopBackendConfiguration", () => {
799799 }),
800800 ),
801801 Layer.provideMerge(
802 makeEnvironmentLayer(baseDir, {
802 layerEnvironment(baseDir, {
803803 platform: "win32",
804804 otlpTracesUrl: " http://127.0.0.1:4318/v1/traces ",
805805 otlpMetricsUrl: " http://127.0.0.1:4318/v1/metrics ",
......@@ -846,12 +846,12 @@ describe("DesktopBackendConfiguration", () => {
846846 }).pipe(
847847 Effect.provide(
848848 DesktopBackendConfiguration.layer.pipe(
849 Layer.provideMerge(serverExposureLayer),
849 Layer.provideMerge(layerServerExposure),
850850 Layer.provideMerge(DesktopAppSettings.layerTest()),
851851 Layer.provideMerge(DesktopWslServerTree.layerTest()),
852852 Layer.provideMerge(DesktopWslEnvironment.layerTest()),
853853 Layer.provideMerge(
854 makeEnvironmentLayer(baseDir, { otlpLogsUrl: "http://env:4318/v1/logs" }),
854 layerEnvironment(baseDir, { otlpLogsUrl: "http://env:4318/v1/logs" }),
855855 ),
856856 ),
857857 ),
......@@ -877,7 +877,7 @@ describe("DesktopBackendConfiguration", () => {
877877 const logger = Logger.make(({ message }) => {
878878 messages.push(message);
879879 });
880 const failingFileSystemLayer = Layer.succeed(
880 const layerFailingFileSystem = Layer.succeed(
881881 FileSystem.FileSystem,
882882 FileSystem.makeNoop({
883883 readFileString: () => Effect.fail(cause),
......@@ -891,12 +891,12 @@ describe("DesktopBackendConfiguration", () => {
891891 Effect.provide(
892892 Layer.mergeAll(
893893 DesktopBackendConfiguration.layer.pipe(
894 Layer.provideMerge(serverExposureLayer),
894 Layer.provideMerge(layerServerExposure),
895895 Layer.provideMerge(DesktopAppSettings.layerTest()),
896896 Layer.provideMerge(DesktopWslServerTree.layerTest()),
897897 Layer.provideMerge(DesktopWslEnvironment.layerTest()),
898 Layer.provideMerge(makeEnvironmentLayer(baseDir)),
899 Layer.provideMerge(failingFileSystemLayer),
898 Layer.provideMerge(layerEnvironment(baseDir)),
899 Layer.provideMerge(layerFailingFileSystem),
900900 ),
901901 Logger.layer([logger], { mergeWithExisting: false }),
902902 ),
......@@ -936,12 +936,12 @@ describe("DesktopBackendConfiguration", () => {
936936 }).pipe(
937937 Effect.provide(
938938 DesktopBackendConfiguration.layer.pipe(
939 Layer.provideMerge(serverExposureLayer),
939 Layer.provideMerge(layerServerExposure),
940940 Layer.provideMerge(DesktopAppSettings.layerTest()),
941941 Layer.provideMerge(DesktopWslServerTree.layerTest()),
942942 Layer.provideMerge(DesktopWslEnvironment.layerTest()),
943943 Layer.provideMerge(
944 makeEnvironmentLayer(baseDir, {
944 layerEnvironment(baseDir, {
945945 isPackaged: false,
946946 devServerUrl: "http://127.0.0.1:5733",
947947 }),
......@@ -974,7 +974,7 @@ describe("DesktopBackendConfiguration", () => {
974974 }).pipe(
975975 Effect.provide(
976976 DesktopBackendConfiguration.layer.pipe(
977 Layer.provideMerge(serverExposureLayer),
977 Layer.provideMerge(layerServerExposure),
978978 Layer.provideMerge(DesktopAppSettings.layerTest()),
979979 Layer.provideMerge(DesktopWslServerTree.layerTest()),
980980 Layer.provideMerge(
......@@ -984,7 +984,7 @@ describe("DesktopBackendConfiguration", () => {
984984 getDistroIp: () => Option.some("172.27.0.99"),
985985 }),
986986 ),
987 Layer.provideMerge(makeEnvironmentLayer(baseDir, { platform: "win32" })),
987 Layer.provideMerge(layerEnvironment(baseDir, { platform: "win32" })),
988988 ),
989989 ),
990990 );
......@@ -1040,7 +1040,7 @@ describe("DesktopBackendConfiguration", () => {
10401040 }).pipe(
10411041 Effect.provide(
10421042 DesktopBackendConfiguration.layer.pipe(
1043 Layer.provideMerge(serverExposureLayer),
1043 Layer.provideMerge(layerServerExposure),
10441044 Layer.provideMerge(DesktopAppSettings.layerTest()),
10451045 Layer.provideMerge(DesktopWslServerTree.layerTest()),
10461046 Layer.provideMerge(
......@@ -1050,7 +1050,7 @@ describe("DesktopBackendConfiguration", () => {
10501050 getDistroIp: () => Option.some("172.27.0.99"),
10511051 }),
10521052 ),
1053 Layer.provideMerge(makeEnvironmentLayer(baseDir, { platform: "win32" })),
1053 Layer.provideMerge(layerEnvironment(baseDir, { platform: "win32" })),
10541054 ),
10551055 ),
10561056 );
......@@ -1114,7 +1114,7 @@ describe("DesktopBackendConfiguration", () => {
11141114 }).pipe(
11151115 Effect.provide(
11161116 DesktopBackendConfiguration.layer.pipe(
1117 Layer.provideMerge(serverExposureLayer),
1117 Layer.provideMerge(layerServerExposure),
11181118 Layer.provideMerge(DesktopAppSettings.layerTest()),
11191119 Layer.provideMerge(DesktopWslServerTree.layerTest()),
11201120 Layer.provideMerge(
......@@ -1124,7 +1124,7 @@ describe("DesktopBackendConfiguration", () => {
11241124 getDistroIp: () => Option.some("172.27.0.99"),
11251125 }),
11261126 ),
1127 Layer.provideMerge(makeEnvironmentLayer(baseDir, { platform: "win32" })),
1127 Layer.provideMerge(layerEnvironment(baseDir, { platform: "win32" })),
11281128 ),
11291129 ),
11301130 );
......@@ -1162,7 +1162,7 @@ describe("DesktopBackendConfiguration", () => {
11621162 }).pipe(
11631163 Effect.provide(
11641164 DesktopBackendConfiguration.layer.pipe(
1165 Layer.provideMerge(serverExposureLayer),
1165 Layer.provideMerge(layerServerExposure),
11661166 Layer.provideMerge(
11671167 DesktopAppSettings.layerTest({
11681168 ...DesktopAppSettings.DEFAULT_DESKTOP_SETTINGS,
......@@ -1172,7 +1172,7 @@ describe("DesktopBackendConfiguration", () => {
11721172 ),
11731173 Layer.provideMerge(DesktopWslServerTree.layerTest()),
11741174 Layer.provideMerge(DesktopWslEnvironment.layerTest({ isAvailable: false })),
1175 Layer.provideMerge(makeEnvironmentLayer(baseDir, { platform: "win32" })),
1175 Layer.provideMerge(layerEnvironment(baseDir, { platform: "win32" })),
11761176 ),
11771177 ),
11781178 );
......@@ -1199,7 +1199,7 @@ describe("DesktopBackendConfiguration", () => {
11991199 }).pipe(
12001200 Effect.provide(
12011201 DesktopBackendConfiguration.layer.pipe(
1202 Layer.provideMerge(serverExposureLayer),
1202 Layer.provideMerge(layerServerExposure),
12031203 Layer.provideMerge(
12041204 DesktopAppSettings.layerTest({
12051205 ...DesktopAppSettings.DEFAULT_DESKTOP_SETTINGS,
......@@ -1215,7 +1215,7 @@ describe("DesktopBackendConfiguration", () => {
12151215 distros: [{ name: "Ubuntu", isDefault: true, version: 2 }],
12161216 }),
12171217 ),
1218 Layer.provideMerge(makeEnvironmentLayer(baseDir, { platform: "win32" })),
1218 Layer.provideMerge(layerEnvironment(baseDir, { platform: "win32" })),
12191219 ),
12201220 ),
12211221 );
......@@ -1240,7 +1240,7 @@ describe("DesktopBackendConfiguration", () => {
12401240 }).pipe(
12411241 Effect.provide(
12421242 DesktopBackendConfiguration.layer.pipe(
1243 Layer.provideMerge(serverExposureLayer),
1243 Layer.provideMerge(layerServerExposure),
12441244 Layer.provideMerge(DesktopAppSettings.layerTest()),
12451245 Layer.provideMerge(DesktopWslServerTree.layerTest()),
12461246 Layer.provideMerge(
......@@ -1251,7 +1251,7 @@ describe("DesktopBackendConfiguration", () => {
12511251 }),
12521252 }),
12531253 ),
1254 Layer.provideMerge(makeEnvironmentLayer(baseDir, { platform: "win32" })),
1254 Layer.provideMerge(layerEnvironment(baseDir, { platform: "win32" })),
12551255 ),
12561256 ),
12571257 );
......@@ -1275,7 +1275,7 @@ describe("DesktopBackendConfiguration", () => {
12751275 }).pipe(
12761276 Effect.provide(
12771277 DesktopBackendConfiguration.layer.pipe(
1278 Layer.provideMerge(serverExposureLayer),
1278 Layer.provideMerge(layerServerExposure),
12791279 Layer.provideMerge(DesktopAppSettings.layerTest()),
12801280 Layer.provideMerge(DesktopWslServerTree.layerTest()),
12811281 Layer.provideMerge(
......@@ -1284,7 +1284,7 @@ describe("DesktopBackendConfiguration", () => {
12841284 distros: [{ name: "Ubuntu", isDefault: true, version: 2 }],
12851285 }),
12861286 ),
1287 Layer.provideMerge(makeEnvironmentLayer(baseDir, { platform: "win32" })),
1287 Layer.provideMerge(layerEnvironment(baseDir, { platform: "win32" })),
12881288 ),
12891289 ),
12901290 );
......@@ -1309,7 +1309,7 @@ describe("DesktopBackendConfiguration", () => {
13091309 }).pipe(
13101310 Effect.provide(
13111311 DesktopBackendConfiguration.layer.pipe(
1312 Layer.provideMerge(serverExposureLayer),
1312 Layer.provideMerge(layerServerExposure),
13131313 Layer.provideMerge(DesktopAppSettings.layerTest()),
13141314 Layer.provideMerge(
13151315 DesktopWslServerTree.layerTest({
......@@ -1326,7 +1326,7 @@ describe("DesktopBackendConfiguration", () => {
13261326 distros: [{ name: "Ubuntu", isDefault: true, version: 2 }],
13271327 }),
13281328 ),
1329 Layer.provideMerge(makeEnvironmentLayer(baseDir, { platform: "win32" })),
1329 Layer.provideMerge(layerEnvironment(baseDir, { platform: "win32" })),
13301330 ),
13311331 ),
13321332 );
......@@ -1350,7 +1350,7 @@ describe("DesktopBackendConfiguration", () => {
13501350 }).pipe(
13511351 Effect.provide(
13521352 DesktopBackendConfiguration.layer.pipe(
1353 Layer.provideMerge(serverExposureLayer),
1353 Layer.provideMerge(layerServerExposure),
13541354 Layer.provideMerge(DesktopAppSettings.layerTest()),
13551355 Layer.provideMerge(DesktopWslServerTree.layerTest()),
13561356 Layer.provideMerge(
......@@ -1359,7 +1359,7 @@ describe("DesktopBackendConfiguration", () => {
13591359 distros: [{ name: "Ubuntu", isDefault: true, version: 2 }],
13601360 }),
13611361 ),
1362 Layer.provideMerge(makeEnvironmentLayer(baseDir, { platform: "win32" })),
1362 Layer.provideMerge(layerEnvironment(baseDir, { platform: "win32" })),
13631363 ),
13641364 ),
13651365 );
......@@ -1380,7 +1380,7 @@ describe("DesktopBackendConfiguration", () => {
13801380 }).pipe(
13811381 Effect.provide(
13821382 DesktopBackendConfiguration.layer.pipe(
1383 Layer.provideMerge(serverExposureLayer),
1383 Layer.provideMerge(layerServerExposure),
13841384 Layer.provideMerge(
13851385 DesktopAppSettings.layerTest({
13861386 ...DesktopAppSettings.DEFAULT_DESKTOP_SETTINGS,
......@@ -1391,7 +1391,7 @@ describe("DesktopBackendConfiguration", () => {
13911391 ),
13921392 Layer.provideMerge(DesktopWslServerTree.layerTest()),
13931393 Layer.provideMerge(DesktopWslEnvironment.layerTest({ isAvailable: true })),
1394 Layer.provideMerge(makeEnvironmentLayer(baseDir, { platform: "win32" })),
1394 Layer.provideMerge(layerEnvironment(baseDir, { platform: "win32" })),
13951395 ),
13961396 ),
13971397 );
......@@ -1429,12 +1429,12 @@ describe("DesktopBackendConfiguration", () => {
14291429 }).pipe(
14301430 Effect.provide(
14311431 DesktopBackendConfiguration.layer.pipe(
1432 Layer.provideMerge(serverExposureLayer),
1432 Layer.provideMerge(layerServerExposure),
14331433 Layer.provideMerge(DesktopAppSettings.layerTest()),
14341434 Layer.provideMerge(DesktopWslServerTree.layerTest()),
14351435 Layer.provideMerge(DesktopWslEnvironment.layerTest()),
14361436 Layer.provideMerge(
1437 makeEnvironmentLayer(baseDir, {
1437 layerEnvironment(baseDir, {
14381438 appPath: `${resourcesPath}/app.asar`,
14391439 dirname,
14401440 isPackaged: true,
......@@ -1475,12 +1475,12 @@ describe("DesktopBackendConfiguration", () => {
14751475 }).pipe(
14761476 Effect.provide(
14771477 DesktopBackendConfiguration.layer.pipe(
1478 Layer.provideMerge(serverExposureLayer),
1478 Layer.provideMerge(layerServerExposure),
14791479 Layer.provideMerge(DesktopAppSettings.layerTest()),
14801480 Layer.provideMerge(DesktopWslServerTree.layerTest()),
14811481 Layer.provideMerge(DesktopWslEnvironment.layerTest()),
14821482 Layer.provideMerge(
1483 makeEnvironmentLayer(baseDir, {
1483 layerEnvironment(baseDir, {
14841484 dirname,
14851485 devServerUrl: "http://127.0.0.1:5733",
14861486 isPackaged: false,
......@@ -1519,7 +1519,7 @@ describe("DesktopBackendConfiguration", () => {
15191519 }).pipe(
15201520 Effect.provide(
15211521 DesktopBackendConfiguration.layer.pipe(
1522 Layer.provideMerge(serverExposureLayer),
1522 Layer.provideMerge(layerServerExposure),
15231523 Layer.provideMerge(
15241524 DesktopAppSettings.layerTest({
15251525 ...DesktopAppSettings.DEFAULT_DESKTOP_SETTINGS,
......@@ -1530,7 +1530,7 @@ describe("DesktopBackendConfiguration", () => {
15301530 ),
15311531 Layer.provideMerge(DesktopWslServerTree.layerTest()),
15321532 Layer.provideMerge(DesktopWslEnvironment.layerTest({ isAvailable: false })),
1533 Layer.provideMerge(makeEnvironmentLayer(baseDir, { platform: "win32" })),
1533 Layer.provideMerge(layerEnvironment(baseDir, { platform: "win32" })),
15341534 ),
15351535 ),
15361536 );
......@@ -1548,7 +1548,7 @@ describe("DesktopBackendConfiguration", () => {
15481548 // oxlint-disable-next-line t3code/no-manual-effect-runtime-in-tests -- This test intentionally replicates the sync IPC handler's runSync path to catch a regression to async-only resolution; it.effect would mask it.
15491549 const runtime = ManagedRuntime.make(
15501550 DesktopBackendConfiguration.layer.pipe(
1551 Layer.provideMerge(serverExposureLayer),
1551 Layer.provideMerge(layerServerExposure),
15521552 Layer.provideMerge(DesktopAppSettings.layerTest()),
15531553 Layer.provideMerge(DesktopWslServerTree.layerTest()),
15541554 Layer.provideMerge(DesktopWslEnvironment.layer),
......@@ -1562,7 +1562,7 @@ describe("DesktopBackendConfiguration", () => {
15621562 ),
15631563 ),
15641564 ),
1565 Layer.provideMerge(makeEnvironmentLayer("/tmp/t3-wsl-isavailable", { platform: "win32" })),
1565 Layer.provideMerge(layerEnvironment("/tmp/t3-wsl-isavailable", { platform: "win32" })),
15661566 Layer.provide(NodeServices.layer),
15671567 ),
15681568 );
apps/desktop/src/backend/DesktopBackendManager.test.ts+67-67
......@@ -98,7 +98,7 @@ function responseForRequest(
9898 return HttpClientResponse.fromWeb(request, new Response(null, { status }));
9999}
100100
101function httpClientLayer(
101function layerHttpClient(
102102 handler: (
103103 request: HttpClientRequest.HttpClientRequest,
104104 ) => Effect.Effect<HttpClientResponse.HttpClientResponse>,
......@@ -109,7 +109,7 @@ function httpClientLayer(
109109 );
110110}
111111
112const healthyHttpClientLayer = httpClientLayer((request) =>
112const layerHealthyHttpClient = layerHttpClient((request) =>
113113 Effect.succeed(responseForRequest(request, 200)),
114114);
115115
......@@ -152,12 +152,12 @@ function makeTestInstance(input: MakeInstanceInput) {
152152 discardSession: Effect.void,
153153 ...input.backendOutputLog,
154154 };
155 const servicesLayer = Layer.mergeAll(
155 const layerServices = Layer.mergeAll(
156156 FileSystem.layerNoop({
157157 exists: () => Effect.succeed(true),
158158 }),
159159 input.spawnerLayer,
160 input.httpClientLayer ?? healthyHttpClientLayer,
160 input.httpClientLayer ?? layerHealthyHttpClient,
161161 Layer.succeed(DesktopObservability.DesktopBackendOutputLogFactory, {
162162 forInstance: () => Effect.succeed(stubLog),
163163 } satisfies DesktopObservability.DesktopBackendOutputLogFactory["Service"]),
......@@ -187,7 +187,7 @@ function makeTestInstance(input: MakeInstanceInput) {
187187 ...(input.onPreflightFailed ? { onPreflightFailed: input.onPreflightFailed } : {}),
188188 });
189189
190 return instance.pipe(Effect.provide(servicesLayer));
190 return instance.pipe(Effect.provide(layerServices));
191191}
192192
193193describe("DesktopBackendManager", () => {
......@@ -201,7 +201,7 @@ describe("DesktopBackendManager", () => {
201201 const ready = yield* Deferred.make<void>();
202202 const exited = yield* Queue.unbounded<void>();
203203
204 const spawnerLayer = Layer.succeed(
204 const layerSpawner = Layer.succeed(
205205 ChildProcessSpawner.ChildProcessSpawner,
206206 ChildProcessSpawner.make((command) =>
207207 Effect.gen(function* () {
......@@ -229,7 +229,7 @@ describe("DesktopBackendManager", () => {
229229 ...baseConfig,
230230 bootstrap: configWithObservability,
231231 },
232 spawnerLayer,
232 spawnerLayer: layerSpawner,
233233 desktopTelemetryStream: Stream.encodeText(
234234 Stream.make('{"version":1,"type":"desktopTelemetryHello","electronPid":123}\n'),
235235 ),
......@@ -279,7 +279,7 @@ describe("DesktopBackendManager", () => {
279279 const requested = yield* Deferred.make<HttpClientRequest.HttpClientRequest>();
280280 const layer = Layer.merge(
281281 TestClock.layer(),
282 httpClientLayer((request) =>
282 layerHttpClient((request) =>
283283 Deferred.succeed(requested, request).pipe(Effect.andThen(Effect.never)),
284284 ),
285285 );
......@@ -317,7 +317,7 @@ describe("DesktopBackendManager", () => {
317317
318318 it.effect("reports bootstrap encoding failures with stable process context", () =>
319319 Effect.gen(function* () {
320 const spawnerLayer = Layer.succeed(
320 const layerSpawner = Layer.succeed(
321321 ChildProcessSpawner.ChildProcessSpawner,
322322 ChildProcessSpawner.make(() => Effect.die("unexpected backend spawn")),
323323 );
......@@ -331,7 +331,7 @@ describe("DesktopBackendManager", () => {
331331 }).pipe(
332332 Effect.flip,
333333 Effect.scoped,
334 Effect.provide(Layer.merge(spawnerLayer, healthyHttpClientLayer)),
334 Effect.provide(Layer.merge(layerSpawner, layerHealthyHttpClient)),
335335 );
336336
337337 if (error._tag !== "BackendProcessBootstrapEncodeError") {
......@@ -359,7 +359,7 @@ describe("DesktopBackendManager", () => {
359359 pathOrDescriptor: baseConfig.executablePath,
360360 description: "low-level detail that must not become the public message",
361361 });
362 const spawnerLayer = Layer.succeed(
362 const layerSpawner = Layer.succeed(
363363 ChildProcessSpawner.ChildProcessSpawner,
364364 ChildProcessSpawner.make(() => Effect.fail(spawnCause)),
365365 );
......@@ -369,7 +369,7 @@ describe("DesktopBackendManager", () => {
369369 }).pipe(
370370 Effect.flip,
371371 Effect.scoped,
372 Effect.provide(Layer.merge(spawnerLayer, healthyHttpClientLayer)),
372 Effect.provide(Layer.merge(layerSpawner, layerHealthyHttpClient)),
373373 );
374374
375375 if (error._tag !== "BackendProcessSpawnError") {
......@@ -397,7 +397,7 @@ describe("DesktopBackendManager", () => {
397397 method: "exitCode",
398398 description: "exit-status-secret-sentinel",
399399 });
400 const spawnerLayer = Layer.succeed(
400 const layerSpawner = Layer.succeed(
401401 ChildProcessSpawner.ChildProcessSpawner,
402402 ChildProcessSpawner.make(() =>
403403 Effect.succeed(
......@@ -413,7 +413,7 @@ describe("DesktopBackendManager", () => {
413413 }).pipe(
414414 Effect.flip,
415415 Effect.scoped,
416 Effect.provide(Layer.merge(spawnerLayer, healthyHttpClientLayer)),
416 Effect.provide(Layer.merge(layerSpawner, layerHealthyHttpClient)),
417417 );
418418
419419 if (error._tag !== "BackendProcessExitStatusError") {
......@@ -440,7 +440,7 @@ describe("DesktopBackendManager", () => {
440440 description: "output-stream-secret-sentinel",
441441 });
442442 const reported = yield* Deferred.make<DesktopBackendManager.BackendProcessOutputError>();
443 const spawnerLayer = Layer.succeed(
443 const layerSpawner = Layer.succeed(
444444 ChildProcessSpawner.ChildProcessSpawner,
445445 ChildProcessSpawner.make(() =>
446446 Effect.succeed(
......@@ -456,7 +456,7 @@ describe("DesktopBackendManager", () => {
456456 ...baseConfig,
457457 desktopTelemetryStream: Stream.empty,
458458 onOutputFailure: (error) => Deferred.succeed(reported, error).pipe(Effect.asVoid),
459 }).pipe(Effect.scoped, Effect.provide(Layer.merge(spawnerLayer, healthyHttpClientLayer)));
459 }).pipe(Effect.scoped, Effect.provide(Layer.merge(layerSpawner, layerHealthyHttpClient)));
460460 const error = yield* Deferred.await(reported);
461461
462462 assert.equal(exit.code.pipe(Option.getOrUndefined), 0);
......@@ -483,7 +483,7 @@ describe("DesktopBackendManager", () => {
483483 const reported = yield* Deferred.make<DesktopBackendManager.BackendProcessOutputError>();
484484 const drained = yield* Deferred.make<void>();
485485 let outputCount = 0;
486 const spawnerLayer = Layer.succeed(
486 const layerSpawner = Layer.succeed(
487487 ChildProcessSpawner.ChildProcessSpawner,
488488 ChildProcessSpawner.make(() =>
489489 Effect.succeed(
......@@ -505,7 +505,7 @@ describe("DesktopBackendManager", () => {
505505 : Deferred.succeed(drained, void 0).pipe(Effect.asVoid);
506506 },
507507 onOutputFailure: (error) => Deferred.succeed(reported, error).pipe(Effect.asVoid),
508 }).pipe(Effect.scoped, Effect.provide(Layer.merge(spawnerLayer, healthyHttpClientLayer)));
508 }).pipe(Effect.scoped, Effect.provide(Layer.merge(layerSpawner, layerHealthyHttpClient)));
509509 const error = yield* Deferred.await(reported);
510510
511511 assert.equal(exit.code.pipe(Option.getOrUndefined), 0);
......@@ -534,7 +534,7 @@ describe("DesktopBackendManager", () => {
534534 Effect.gen(function* () {
535535 const exitObserved = yield* Deferred.make<void>();
536536 const finishOutputDrain = yield* Deferred.make<void>();
537 const spawnerLayer = Layer.succeed(
537 const layerSpawner = Layer.succeed(
538538 ChildProcessSpawner.ChildProcessSpawner,
539539 ChildProcessSpawner.make(() =>
540540 Effect.succeed(
......@@ -555,7 +555,7 @@ describe("DesktopBackendManager", () => {
555555 desktopTelemetryStream: Stream.empty,
556556 onExitObserved: () => Deferred.succeed(exitObserved, void 0).pipe(Effect.asVoid),
557557 }).pipe(
558 Effect.provide(Layer.merge(spawnerLayer, healthyHttpClientLayer)),
558 Effect.provide(Layer.merge(layerSpawner, layerHealthyHttpClient)),
559559 Effect.forkChild,
560560 );
561561
......@@ -577,7 +577,7 @@ describe("DesktopBackendManager", () => {
577577 type: "setDiagnosticsDemand",
578578 enabled: true,
579579 });
580 const spawnerLayer = Layer.succeed(
580 const layerSpawner = Layer.succeed(
581581 ChildProcessSpawner.ChildProcessSpawner,
582582 ChildProcessSpawner.make(() =>
583583 Effect.succeed(
......@@ -592,7 +592,7 @@ describe("DesktopBackendManager", () => {
592592 ),
593593 );
594594 const instance = yield* makeTestInstance({
595 spawnerLayer,
595 spawnerLayer: layerSpawner,
596596 desktopTelemetryPublisher: {
597597 handleControlForSource: (_sourceId, message) =>
598598 message.type === "setDiagnosticsDemand"
......@@ -613,7 +613,7 @@ describe("DesktopBackendManager", () => {
613613 const persistedOutput = yield* Deferred.make<ReadonlyArray<string>>();
614614 const outputDrainStarted = yield* Deferred.make<void>();
615615 const outputChunks = yield* Ref.make<Array<string>>([]);
616 const spawnerLayer = Layer.succeed(
616 const layerSpawner = Layer.succeed(
617617 ChildProcessSpawner.ChildProcessSpawner,
618618 ChildProcessSpawner.make(() =>
619619 Effect.succeed(
......@@ -630,8 +630,8 @@ describe("DesktopBackendManager", () => {
630630 ),
631631 );
632632 const instance = yield* makeTestInstance({
633 spawnerLayer,
634 httpClientLayer: httpClientLayer(() => Effect.never),
633 spawnerLayer: layerSpawner,
634 httpClientLayer: layerHttpClient(() => Effect.never),
635635 backendOutputLog: {
636636 writeOutputChunk: (_streamName, chunk) =>
637637 Ref.update(outputChunks, (current) => [...current, new TextDecoder().decode(chunk)]),
......@@ -665,7 +665,7 @@ describe("DesktopBackendManager", () => {
665665 const pruneComplete = yield* Deferred.make<void>();
666666 const exited = yield* Queue.unbounded<void>();
667667
668 const spawnerLayer = Layer.succeed(
668 const layerSpawner = Layer.succeed(
669669 ChildProcessSpawner.ChildProcessSpawner,
670670 ChildProcessSpawner.make(() =>
671671 Effect.succeed(
......@@ -679,7 +679,7 @@ describe("DesktopBackendManager", () => {
679679 );
680680
681681 const instance = yield* makeTestInstance({
682 spawnerLayer,
682 spawnerLayer: layerSpawner,
683683 config: {
684684 ...baseConfig,
685685 runningDistro: "Ubuntu",
......@@ -689,7 +689,7 @@ describe("DesktopBackendManager", () => {
689689 Effect.sync(() => {
690690 prunedRuntimes.push([distro, runtimeId]);
691691 }).pipe(Effect.andThen(Deferred.succeed(pruneComplete, void 0)), Effect.asVoid),
692 httpClientLayer: httpClientLayer((request) =>
692 httpClientLayer: layerHttpClient((request) =>
693693 Effect.gen(function* () {
694694 const status = statuses.shift();
695695 assert.isDefined(status);
......@@ -741,7 +741,7 @@ describe("DesktopBackendManager", () => {
741741 const firstProbe = yield* Deferred.make<void>();
742742 const childExit = yield* Deferred.make<void>();
743743
744 const spawnerLayer = Layer.succeed(
744 const layerSpawner = Layer.succeed(
745745 ChildProcessSpawner.ChildProcessSpawner,
746746 ChildProcessSpawner.make(() =>
747747 Effect.succeed(
......@@ -757,7 +757,7 @@ describe("DesktopBackendManager", () => {
757757 // The backend stays 503 through the first *two* readiness budgets
758758 // and only becomes healthy (200) for the third round, i.e. it comes
759759 // up well after the initial 50ms budget has expired.
760 const httpLayer = httpClientLayer((request) =>
760 const layerHttp = layerHttpClient((request) =>
761761 Effect.gen(function* () {
762762 requestCount += 1;
763763 requestUrls.push(request.url);
......@@ -778,7 +778,7 @@ describe("DesktopBackendManager", () => {
778778 Effect.sync(() => {
779779 readinessTimeoutCount += 1;
780780 }),
781 }).pipe(Effect.provide(Layer.merge(spawnerLayer, httpLayer)), Effect.forkChild);
781 }).pipe(Effect.provide(Layer.merge(layerSpawner, layerHttp)), Effect.forkChild);
782782
783783 yield* Deferred.await(firstProbe);
784784 assert.equal(readyCount, 0);
......@@ -823,7 +823,7 @@ describe("DesktopBackendManager", () => {
823823 let discardedSessionCount = 0;
824824 let removedTelemetrySources = 0;
825825
826 const spawnerLayer = Layer.succeed(
826 const layerSpawner = Layer.succeed(
827827 ChildProcessSpawner.ChildProcessSpawner,
828828 ChildProcessSpawner.make(() =>
829829 Effect.gen(function* () {
......@@ -852,7 +852,7 @@ describe("DesktopBackendManager", () => {
852852 );
853853
854854 const instance = yield* makeTestInstance({
855 spawnerLayer,
855 spawnerLayer: layerSpawner,
856856 onReady: Ref.set(backendReadyFlag, true).pipe(
857857 Effect.andThen(Deferred.succeed(ready, void 0)),
858858 Effect.asVoid,
......@@ -922,7 +922,7 @@ describe("DesktopBackendManager", () => {
922922 const finishTeardown = yield* Deferred.make<void>();
923923 let startCount = 0;
924924
925 const spawnerLayer = Layer.succeed(
925 const layerSpawner = Layer.succeed(
926926 ChildProcessSpawner.ChildProcessSpawner,
927927 ChildProcessSpawner.make(() =>
928928 Effect.gen(function* () {
......@@ -953,8 +953,8 @@ describe("DesktopBackendManager", () => {
953953 );
954954
955955 const instance = yield* makeTestInstance({
956 spawnerLayer,
957 httpClientLayer: httpClientLayer(() => Effect.never),
956 spawnerLayer: layerSpawner,
957 httpClientLayer: layerHttpClient(() => Effect.never),
958958 });
959959
960960 yield* instance.start;
......@@ -992,7 +992,7 @@ describe("DesktopBackendManager", () => {
992992 method: "configResolve",
993993 description: "transient configuration failure",
994994 });
995 const spawnerLayer = Layer.succeed(
995 const layerSpawner = Layer.succeed(
996996 ChildProcessSpawner.ChildProcessSpawner,
997997 ChildProcessSpawner.make(() =>
998998 Effect.gen(function* () {
......@@ -1027,9 +1027,9 @@ describe("DesktopBackendManager", () => {
10271027 ),
10281028 );
10291029 const instance = yield* makeTestInstance({
1030 spawnerLayer,
1030 spawnerLayer: layerSpawner,
10311031 configResolve,
1032 httpClientLayer: httpClientLayer(() => Effect.never),
1032 httpClientLayer: layerHttpClient(() => Effect.never),
10331033 });
10341034
10351035 yield* instance.start;
......@@ -1062,7 +1062,7 @@ describe("DesktopBackendManager", () => {
10621062 const finishTeardown = yield* Deferred.make<void>();
10631063 let startCount = 0;
10641064
1065 const spawnerLayer = Layer.succeed(
1065 const layerSpawner = Layer.succeed(
10661066 ChildProcessSpawner.ChildProcessSpawner,
10671067 ChildProcessSpawner.make(() =>
10681068 Effect.gen(function* () {
......@@ -1093,8 +1093,8 @@ describe("DesktopBackendManager", () => {
10931093 );
10941094
10951095 const instance = yield* makeTestInstance({
1096 spawnerLayer,
1097 httpClientLayer: httpClientLayer(() => Effect.never),
1096 spawnerLayer: layerSpawner,
1097 httpClientLayer: layerHttpClient(() => Effect.never),
10981098 });
10991099
11001100 yield* instance.start;
......@@ -1128,7 +1128,7 @@ describe("DesktopBackendManager", () => {
11281128 const closed = yield* Deferred.make<void>();
11291129 const startedPids = yield* Queue.unbounded<number>();
11301130
1131 const spawnerLayer = Layer.succeed(
1131 const layerSpawner = Layer.succeed(
11321132 ChildProcessSpawner.ChildProcessSpawner,
11331133 ChildProcessSpawner.make(() =>
11341134 Effect.gen(function* () {
......@@ -1143,8 +1143,8 @@ describe("DesktopBackendManager", () => {
11431143 );
11441144
11451145 const instance = yield* makeTestInstance({
1146 spawnerLayer,
1147 httpClientLayer: httpClientLayer(() => Effect.never),
1146 spawnerLayer: layerSpawner,
1147 httpClientLayer: layerHttpClient(() => Effect.never),
11481148 onShutdown: Effect.sync(() => {
11491149 shutdownCount += 1;
11501150 }),
......@@ -1164,7 +1164,7 @@ describe("DesktopBackendManager", () => {
11641164 const failures = yield* Queue.unbounded<string>();
11651165 let startCount = 0;
11661166
1167 const spawnerLayer = Layer.succeed(
1167 const layerSpawner = Layer.succeed(
11681168 ChildProcessSpawner.ChildProcessSpawner,
11691169 ChildProcessSpawner.make(() =>
11701170 Effect.sync(() => {
......@@ -1179,8 +1179,8 @@ describe("DesktopBackendManager", () => {
11791179 );
11801180
11811181 const instance = yield* makeTestInstance({
1182 spawnerLayer,
1183 httpClientLayer: httpClientLayer(() => Effect.never),
1182 spawnerLayer: layerSpawner,
1183 httpClientLayer: layerHttpClient(() => Effect.never),
11841184 backendOutputLog: {
11851185 persistFailure: ({ details }) => Queue.offer(failures, details).pipe(Effect.asVoid),
11861186 },
......@@ -1209,13 +1209,13 @@ describe("DesktopBackendManager", () => {
12091209 Effect.gen(function* () {
12101210 let shutdownCount = 0;
12111211
1212 const spawnerLayer = Layer.succeed(
1212 const layerSpawner = Layer.succeed(
12131213 ChildProcessSpawner.ChildProcessSpawner,
12141214 ChildProcessSpawner.make(() => Effect.die("unexpected backend spawn")),
12151215 );
12161216
12171217 const instance = yield* makeTestInstance({
1218 spawnerLayer,
1218 spawnerLayer: layerSpawner,
12191219 config: {
12201220 ...baseConfig,
12211221 preflightFailure: Option.some({ reason: "preflight failed", fatal: false }),
......@@ -1238,13 +1238,13 @@ describe("DesktopBackendManager", () => {
12381238 Effect.scoped(
12391239 Effect.gen(function* () {
12401240 const failures: string[] = [];
1241 const spawnerLayer = Layer.succeed(
1241 const layerSpawner = Layer.succeed(
12421242 ChildProcessSpawner.ChildProcessSpawner,
12431243 ChildProcessSpawner.make(() => Effect.die("unexpected backend spawn")),
12441244 );
12451245
12461246 const instance = yield* makeTestInstance({
1247 spawnerLayer,
1247 spawnerLayer: layerSpawner,
12481248 config: {
12491249 ...baseConfig,
12501250 preflightFailure: Option.some({ reason: "Node.js not found", fatal: true }),
......@@ -1279,7 +1279,7 @@ describe("DesktopBackendManager", () => {
12791279 Effect.gen(function* () {
12801280 const failing = yield* Ref.make(true);
12811281 const starts = yield* Queue.unbounded<number>();
1282 const spawnerLayer = Layer.succeed(
1282 const layerSpawner = Layer.succeed(
12831283 ChildProcessSpawner.ChildProcessSpawner,
12841284 ChildProcessSpawner.make(() =>
12851285 Queue.offer(starts, 123).pipe(
......@@ -1293,7 +1293,7 @@ describe("DesktopBackendManager", () => {
12931293 );
12941294
12951295 const instance = yield* makeTestInstance({
1296 spawnerLayer,
1296 spawnerLayer: layerSpawner,
12971297 configResolve: Ref.get(failing).pipe(
12981298 Effect.map((isFailing) =>
12991299 isFailing
......@@ -1338,13 +1338,13 @@ describe("DesktopBackendManager", () => {
13381338 Effect.scoped(
13391339 Effect.gen(function* () {
13401340 const failures: string[] = [];
1341 const spawnerLayer = Layer.succeed(
1341 const layerSpawner = Layer.succeed(
13421342 ChildProcessSpawner.ChildProcessSpawner,
13431343 ChildProcessSpawner.make(() => Effect.die("unexpected backend spawn")),
13441344 );
13451345
13461346 const instance = yield* makeTestInstance({
1347 spawnerLayer,
1347 spawnerLayer: layerSpawner,
13481348 config: {
13491349 ...baseConfig,
13501350 preflightFailure: Option.some({ reason: "wslpath conversion failed", fatal: false }),
......@@ -1368,13 +1368,13 @@ describe("DesktopBackendManager", () => {
13681368 Effect.scoped(
13691369 Effect.gen(function* () {
13701370 const failures: string[] = [];
1371 const spawnerLayer = Layer.succeed(
1371 const layerSpawner = Layer.succeed(
13721372 ChildProcessSpawner.ChildProcessSpawner,
13731373 ChildProcessSpawner.make(() => Effect.die("unexpected backend spawn")),
13741374 );
13751375
13761376 const instance = yield* makeTestInstance({
1377 spawnerLayer,
1377 spawnerLayer: layerSpawner,
13781378 config: {
13791379 ...baseConfig,
13801380 preflightFailure: Option.some({
......@@ -1406,7 +1406,7 @@ describe("DesktopBackendManager", () => {
14061406 const secondClosed = yield* Deferred.make<void>();
14071407 let startCount = 0;
14081408
1409 const spawnerLayer = Layer.succeed(
1409 const layerSpawner = Layer.succeed(
14101410 ChildProcessSpawner.ChildProcessSpawner,
14111411 ChildProcessSpawner.make(() =>
14121412 Effect.gen(function* () {
......@@ -1433,8 +1433,8 @@ describe("DesktopBackendManager", () => {
14331433 );
14341434
14351435 const instance = yield* makeTestInstance({
1436 spawnerLayer,
1437 httpClientLayer: httpClientLayer(() => Effect.never),
1436 spawnerLayer: layerSpawner,
1437 httpClientLayer: layerHttpClient(() => Effect.never),
14381438 });
14391439
14401440 yield* instance.start;
......@@ -1465,7 +1465,7 @@ describe("DesktopBackendManager", () => {
14651465 const starts = yield* Queue.unbounded<number>();
14661466 let startCount = 0;
14671467
1468 const spawnerLayer = Layer.succeed(
1468 const layerSpawner = Layer.succeed(
14691469 ChildProcessSpawner.ChildProcessSpawner,
14701470 ChildProcessSpawner.make(() =>
14711471 Effect.sync(() => {
......@@ -1480,8 +1480,8 @@ describe("DesktopBackendManager", () => {
14801480 );
14811481
14821482 const instance = yield* makeTestInstance({
1483 spawnerLayer,
1484 httpClientLayer: httpClientLayer(() => Effect.never),
1483 spawnerLayer: layerSpawner,
1484 httpClientLayer: layerHttpClient(() => Effect.never),
14851485 });
14861486
14871487 yield* instance.start;
......@@ -1534,7 +1534,7 @@ describe("DesktopBackendManager", () => {
15341534 }),
15351535 ),
15361536 ),
1537 httpClientLayer: httpClientLayer(() => Effect.never),
1537 httpClientLayer: layerHttpClient(() => Effect.never),
15381538 });
15391539
15401540 const instance1 = yield* makeInstance("instance1");
......@@ -1543,7 +1543,7 @@ describe("DesktopBackendManager", () => {
15431543 yield* instance1.start;
15441544 yield* instance2.start;
15451545
1546 const mockPool = Layer.succeed(DesktopBackendPool.DesktopBackendPool, {
1546 const layerMockPool = Layer.succeed(DesktopBackendPool.DesktopBackendPool, {
15471547 list: Effect.succeed([instance1, instance2]),
15481548 get: () => Effect.succeedNone,
15491549 primary: Effect.die(new Error("primary not implemented")),
......@@ -1556,7 +1556,7 @@ describe("DesktopBackendManager", () => {
15561556 // as an ordinary interruptible effect.
15571557 const quitFiber = yield* Effect.scoped(
15581558 Effect.addFinalizer(() => DesktopApp.stopAllPoolInstances()),
1559 ).pipe(Effect.provide(mockPool), Effect.forkChild);
1559 ).pipe(Effect.provide(layerMockPool), Effect.forkChild);
15601560
15611561 const started = yield* Queue.takeN(teardownStarted, 2);
15621562 assert.deepEqual(started.toSorted(), ["instance1", "instance2"]);
apps/desktop/src/backend/DesktopBackendPool.test.ts+2-4
......@@ -41,9 +41,7 @@ function makeStubInstance(
4141 };
4242}
4343
44function makePoolLayer(
45 labelRef: Ref.Ref<string>,
46): Layer.Layer<DesktopBackendPool.DesktopBackendPool> {
44function layerPool(labelRef: Ref.Ref<string>): Layer.Layer<DesktopBackendPool.DesktopBackendPool> {
4745 return DesktopBackendPool.layer.pipe(
4846 Layer.provideMerge(
4947 Layer.mergeAll(
......@@ -144,7 +142,7 @@ describe("DesktopBackendPool", () => {
144142 Effect.gen(function* () {
145143 const labelRef = yield* Ref.make("Windows");
146144 const pool = yield* DesktopBackendPool.DesktopBackendPool.pipe(
147 Effect.provide(makePoolLayer(labelRef)),
145 Effect.provide(layerPool(labelRef)),
148146 );
149147 const primary = yield* pool.primary;
150148
apps/desktop/src/backend/DesktopLocalEnvironmentAuth.test.ts+5-5
......@@ -32,7 +32,7 @@ describe("DesktopLocalEnvironmentAuth", () => {
3232 it.effect("exchanges the desktop bootstrap credential only once", () =>
3333 Effect.gen(function* () {
3434 const requestCount = yield* Ref.make(0);
35 const httpClientLayer = Layer.succeed(
35 const layerHttpClient = Layer.succeed(
3636 HttpClient.HttpClient,
3737 HttpClient.make((request) =>
3838 Ref.update(requestCount, (count) => count + 1).pipe(
......@@ -54,7 +54,7 @@ describe("DesktopLocalEnvironmentAuth", () => {
5454 ),
5555 ),
5656 );
57 const poolLayer = Layer.succeed(DesktopBackendPool.DesktopBackendPool, {
57 const layerPool = Layer.succeed(DesktopBackendPool.DesktopBackendPool, {
5858 list: Effect.succeed([
5959 {
6060 id: PRIMARY_LOCAL_ENVIRONMENT_ID,
......@@ -63,14 +63,14 @@ describe("DesktopLocalEnvironmentAuth", () => {
6363 },
6464 ]),
6565 } as unknown as DesktopBackendPool.DesktopBackendPool["Service"]);
66 const testLayer = DesktopLocalEnvironmentAuth.layer.pipe(
67 Layer.provide(Layer.mergeAll(poolLayer, httpClientLayer)),
66 const layerTest = DesktopLocalEnvironmentAuth.layer.pipe(
67 Layer.provide(Layer.mergeAll(layerPool, layerHttpClient)),
6868 );
6969
7070 const [first, second] = yield* Effect.gen(function* () {
7171 const auth = yield* DesktopLocalEnvironmentAuth.DesktopLocalEnvironmentAuth;
7272 return yield* Effect.all([auth.getBearerToken, auth.getBearerToken]);
73 }).pipe(Effect.provide(testLayer));
73 }).pipe(Effect.provide(layerTest));
7474
7575 assert.strictEqual(first, "desktop-bearer-token");
7676 assert.strictEqual(second, "desktop-bearer-token");
apps/desktop/src/backend/DesktopNetworkInterfaces.test.ts+3-3
......@@ -15,7 +15,7 @@ vi.mock("node:os", () => ({
1515
1616import * as DesktopNetworkInterfaces from "./DesktopNetworkInterfaces.ts";
1717
18const TestLayer = DesktopNetworkInterfaces.layer.pipe(
18const layerTest = DesktopNetworkInterfaces.layer.pipe(
1919 Layer.provide(Layer.succeed(HostProcessPlatform, "linux")),
2020);
2121
......@@ -39,7 +39,7 @@ describe("DesktopNetworkInterfaces", () => {
3939 return Effect.gen(function* () {
4040 const service = yield* DesktopNetworkInterfaces.DesktopNetworkInterfaces;
4141 assert.strictEqual(yield* service.read, interfaces);
42 }).pipe(Effect.provide(TestLayer));
42 }).pipe(Effect.provide(layerTest));
4343 });
4444
4545 it.effect("preserves network interface read failures as structured defects", () => {
......@@ -60,6 +60,6 @@ describe("DesktopNetworkInterfaces", () => {
6060 assert.strictEqual(error.cause, cause);
6161 assert.equal(error.message, "Failed to read desktop network interfaces on linux.");
6262 }
63 }).pipe(Effect.provide(TestLayer));
63 }).pipe(Effect.provide(layerTest));
6464 });
6565});
apps/desktop/src/backend/DesktopServerExposure.test.ts+13-13
......@@ -38,7 +38,7 @@ const tailnetNetworkInterfaces: DesktopNetworkInterfaces.NetworkInterfaces = {
3838 ],
3939};
4040
41function mockSpawnerLayer(statusJson = "{}") {
41function layerMockSpawner(statusJson = "{}") {
4242 return Layer.succeed(
4343 ChildProcessSpawner.ChildProcessSpawner,
4444 ChildProcessSpawner.make(() =>
......@@ -61,14 +61,14 @@ function mockSpawnerLayer(statusJson = "{}") {
6161 );
6262}
6363
64function dieOnSpawnLayer() {
64function layerDieOnSpawn() {
6565 return Layer.succeed(
6666 ChildProcessSpawner.ChildProcessSpawner,
6767 ChildProcessSpawner.make(() => Effect.die("unexpected tailscale spawn")),
6868 );
6969}
7070
71function makeEnvironmentLayer(baseDir: string, env: Record<string, string | undefined> = {}) {
71function layerEnvironmentFor(baseDir: string, env: Record<string, string | undefined> = {}) {
7272 return DesktopEnvironment.layer({
7373 dirname: "/repo/apps/desktop/src",
7474 homeDirectory: baseDir,
......@@ -86,7 +86,7 @@ function makeEnvironmentLayer(baseDir: string, env: Record<string, string | unde
8686 );
8787}
8888
89function makeLayer(input: {
89function layer(input: {
9090 readonly baseDir: string;
9191 readonly networkInterfaces?: DesktopNetworkInterfaces.NetworkInterfaces;
9292 readonly env?: Record<string, string | undefined>;
......@@ -94,8 +94,8 @@ function makeLayer(input: {
9494 readonly desktopSettingsLayer?: Layer.Layer<DesktopAppSettings.DesktopAppSettings>;
9595}) {
9696 const env = { T3CODE_HOME: input.baseDir, ...input.env };
97 const environmentLayer = makeEnvironmentLayer(input.baseDir, env);
98 const networkLayer = Layer.succeed(DesktopNetworkInterfaces.DesktopNetworkInterfaces, {
97 const layerEnvironment = layerEnvironmentFor(input.baseDir, env);
98 const layerNetwork = Layer.succeed(DesktopNetworkInterfaces.DesktopNetworkInterfaces, {
9999 read: Effect.succeed(input.networkInterfaces ?? emptyNetworkInterfaces),
100100 });
101101
......@@ -103,10 +103,10 @@ function makeLayer(input: {
103103 Layer.provideMerge(input.desktopSettingsLayer ?? DesktopAppSettings.layer),
104104 Layer.provideMerge(NodeFileSystem.layer),
105105 Layer.provideMerge(NodeHttpClient.layerUndici),
106 Layer.provideMerge(input.spawnerLayer ?? mockSpawnerLayer()),
107 Layer.provideMerge(networkLayer),
106 Layer.provideMerge(input.spawnerLayer ?? layerMockSpawner()),
107 Layer.provideMerge(layerNetwork),
108108 Layer.provideMerge(DesktopConfig.layerTest(env)),
109 Layer.provideMerge(environmentLayer),
109 Layer.provideMerge(layerEnvironment),
110110 );
111111}
112112
......@@ -132,7 +132,7 @@ const withHarness = <A, E, R>(
132132 });
133133 return yield* effect.pipe(
134134 Effect.provide(
135 makeLayer({
135 layer({
136136 baseDir,
137137 networkInterfaces,
138138 env,
......@@ -247,7 +247,7 @@ describe("DesktopServerExposure", () => {
247247 path: "/tmp/desktop-settings.json",
248248 cause: diskFailure,
249249 });
250 const settingsLayer = Layer.succeed(DesktopAppSettings.DesktopAppSettings, {
250 const layerSettings = Layer.succeed(DesktopAppSettings.DesktopAppSettings, {
251251 get: Effect.succeed(DesktopAppSettings.DEFAULT_DESKTOP_SETTINGS),
252252 load: Effect.succeed(DesktopAppSettings.DEFAULT_DESKTOP_SETTINGS),
253253 setMainWindowBounds: () => Effect.die("unexpected main window bounds update"),
......@@ -301,7 +301,7 @@ describe("DesktopServerExposure", () => {
301301 }),
302302 {},
303303 undefined,
304 settingsLayer,
304 layerSettings,
305305 );
306306 });
307307
......@@ -365,7 +365,7 @@ describe("DesktopServerExposure", () => {
365365 );
366366 }),
367367 {},
368 dieOnSpawnLayer(),
368 layerDieOnSpawn(),
369369 ),
370370 );
371371
apps/desktop/src/backend/tailscaleEndpointProvider.test.ts+4-4
......@@ -9,7 +9,7 @@ import {
99 resolveTailscaleAdvertisedEndpoints,
1010} from "./tailscaleEndpointProvider.ts";
1111
12const unusedTailscaleExternalServicesLayer = Layer.mergeAll(
12const layerUnusedTailscaleExternalServices = Layer.mergeAll(
1313 Layer.succeed(
1414 HttpClient.HttpClient,
1515 HttpClient.make(() => Effect.die("unexpected Tailscale HTTPS probe")),
......@@ -93,7 +93,7 @@ describe("tailscale endpoint provider", () => {
9393 description: "MagicDNS hostname. Configure Tailscale Serve for HTTPS access.",
9494 },
9595 ]);
96 }).pipe(Effect.provide(unusedTailscaleExternalServicesLayer)),
96 }).pipe(Effect.provide(layerUnusedTailscaleExternalServices)),
9797 );
9898
9999 it.effect("uses an injected magic DNS name reader instead of spawning tailscale", () =>
......@@ -112,7 +112,7 @@ describe("tailscale endpoint provider", () => {
112112 endpoints.map((endpoint) => endpoint.httpBaseUrl),
113113 ["https://desktop.tail.ts.net/"],
114114 );
115 }).pipe(Effect.provide(unusedTailscaleExternalServicesLayer)),
115 }).pipe(Effect.provide(layerUnusedTailscaleExternalServices)),
116116 );
117117
118118 it.effect(
......@@ -148,6 +148,6 @@ describe("tailscale endpoint provider", () => {
148148 description: "HTTPS endpoint served by Tailscale Serve.",
149149 },
150150 ]);
151 }).pipe(Effect.provide(unusedTailscaleExternalServicesLayer)),
151 }).pipe(Effect.provide(layerUnusedTailscaleExternalServices)),
152152 );
153153});
apps/desktop/src/electron/ElectronMenu.test.ts+9-9
......@@ -27,7 +27,7 @@ vi.mock("electron", () => ({
2727
2828import * as ElectronMenu from "./ElectronMenu.ts";
2929
30const TestLayer = ElectronMenu.layer.pipe(
30const layerTest = ElectronMenu.layer.pipe(
3131 Layer.provide(Layer.succeed(HostProcessPlatform, "linux")),
3232);
3333
......@@ -55,7 +55,7 @@ describe("ElectronMenu", () => {
5555
5656 assert.isTrue(Option.isNone(selectedItemId));
5757 assert.equal(buildFromTemplateMock.mock.calls.length, 0);
58 }).pipe(Effect.provide(TestLayer)),
58 }).pipe(Effect.provide(layerTest)),
5959 );
6060
6161 it.effect("resolves with the clicked leaf item id", () =>
......@@ -82,7 +82,7 @@ describe("ElectronMenu", () => {
8282 });
8383
8484 assert.equal(Option.getOrNull(selectedItemId), "copy");
85 }).pipe(Effect.provide(TestLayer)),
85 }).pipe(Effect.provide(layerTest)),
8686 );
8787
8888 it.effect("resolves with none when the menu closes without a click", () =>
......@@ -119,7 +119,7 @@ describe("ElectronMenu", () => {
119119 ),
120120 ["Copy", "separator", "Delete"],
121121 );
122 }).pipe(Effect.provide(TestLayer)),
122 }).pipe(Effect.provide(layerTest)),
123123 );
124124
125125 it.effect("keeps a preceding non-destructive action in the destructive section", () =>
......@@ -145,7 +145,7 @@ describe("ElectronMenu", () => {
145145 ),
146146 ["Copy", "separator", "Archive", "Delete"],
147147 );
148 }).pipe(Effect.provide(TestLayer)),
148 }).pipe(Effect.provide(layerTest)),
149149 );
150150
151151 it.effect("defers popupTemplate side effects until the returned Effect runs", () =>
......@@ -169,7 +169,7 @@ describe("ElectronMenu", () => {
169169 assert.equal(buildFromTemplateMock.mock.calls.length, 1);
170170 assert.equal(popupMock.mock.calls.length, 1);
171171 assert.strictEqual(popupMock.mock.calls[0]?.[0].frame, frame);
172 }).pipe(Effect.provide(TestLayer)),
172 }).pipe(Effect.provide(layerTest)),
173173 );
174174
175175 it.effect("preserves application-menu failures as structured defects", () =>
......@@ -195,7 +195,7 @@ describe("ElectronMenu", () => {
195195 assert.strictEqual(error.cause, cause);
196196 assert.notInclude(error.message, cause.message);
197197 }
198 }).pipe(Effect.provide(TestLayer)),
198 }).pipe(Effect.provide(layerTest)),
199199 );
200200
201201 it.effect("preserves popup-template failures with window context", () =>
......@@ -224,7 +224,7 @@ describe("ElectronMenu", () => {
224224 assert.equal(error.itemCount, 1);
225225 assert.strictEqual(error.cause, cause);
226226 }
227 }).pipe(Effect.provide(TestLayer)),
227 }).pipe(Effect.provide(layerTest)),
228228 );
229229
230230 it.effect("preserves context-menu failures with normalized item context", () =>
......@@ -252,6 +252,6 @@ describe("ElectronMenu", () => {
252252 assert.equal(error.itemCount, 1);
253253 assert.strictEqual(error.cause, cause);
254254 }
255 }).pipe(Effect.provide(TestLayer)),
255 }).pipe(Effect.provide(layerTest)),
256256 );
257257});
apps/desktop/src/electron/ElectronProtocol.test.ts+7-7
......@@ -19,7 +19,7 @@ vi.mock("electron", () => ({
1919
2020import * as ElectronProtocol from "./ElectronProtocol.ts";
2121
22const protocolLayer = ElectronProtocol.layer.pipe(Layer.provide(NodeServices.layer));
22const layerProtocol = ElectronProtocol.layer.pipe(Layer.provide(NodeServices.layer));
2323
2424describe("ElectronProtocol", () => {
2525 beforeEach(() => {
......@@ -65,7 +65,7 @@ describe("ElectronProtocol", () => {
6565 assert.equal((yield* request("/%invalid")).status, 400);
6666 assert.equal((yield* request("/", { method: "POST" })).status, 405);
6767 assert.equal(netFetchMock.mock.calls.length, 0);
68 }).pipe(Effect.provide(Layer.merge(protocolLayer, NodeServices.layer)), Effect.scoped),
68 }).pipe(Effect.provide(Layer.merge(layerProtocol, NodeServices.layer)), Effect.scoped),
6969 );
7070
7171 it.effect("proxies the stable renderer origin to the current app server", () =>
......@@ -129,7 +129,7 @@ describe("ElectronProtocol", () => {
129129 assert.isNull(forwardedHeaders.get("referer"));
130130 assert.isNull(forwardedHeaders.get("sec-fetch-site"));
131131 assert.deepEqual(unhandleMock.mock.calls, [["t3code-dev"]]);
132 }).pipe(Effect.provide(protocolLayer)),
132 }).pipe(Effect.provide(layerProtocol)),
133133 );
134134
135135 it.effect("rejects custom protocol requests for another host", () =>
......@@ -153,7 +153,7 @@ describe("ElectronProtocol", () => {
153153
154154 assert.equal(response.status, 404);
155155 assert.equal(netFetchMock.mock.calls.length, 0);
156 }).pipe(Effect.provide(protocolLayer)),
156 }).pipe(Effect.provide(layerProtocol)),
157157 );
158158
159159 it.effect("retries transient renderer target failures", () =>
......@@ -180,7 +180,7 @@ describe("ElectronProtocol", () => {
180180
181181 assert.equal(yield* Effect.promise(() => response.text()), "ready");
182182 assert.equal(netFetchMock.mock.calls.length, 2);
183 }).pipe(Effect.provide(protocolLayer)),
183 }).pipe(Effect.provide(layerProtocol)),
184184 );
185185
186186 it.effect("preserves protocol registration failures", () =>
......@@ -203,7 +203,7 @@ describe("ElectronProtocol", () => {
203203 assert.equal(error.scheme, "t3code-dev");
204204 assert.strictEqual(error.cause, cause);
205205 assert.equal(error.message, 'Failed to register Electron protocol scheme "t3code-dev".');
206 }).pipe(Effect.provide(protocolLayer)),
206 }).pipe(Effect.provide(layerProtocol)),
207207 );
208208
209209 it.effect("preserves protocol unregistration failures", () =>
......@@ -232,7 +232,7 @@ describe("ElectronProtocol", () => {
232232 assert.strictEqual(error.cause, cause);
233233 assert.equal(error.message, 'Failed to unregister Electron protocol scheme "t3code".');
234234 }
235 }).pipe(Effect.provide(protocolLayer)),
235 }).pipe(Effect.provide(layerProtocol)),
236236 );
237237
238238 it("keeps executable sources host-restricted while allowing runtime network resources", () => {
apps/desktop/src/electron/ElectronWindow.test.ts+21-21
......@@ -57,10 +57,10 @@ vi.mock("electron", () => ({
5757
5858import * as ElectronWindow from "./ElectronWindow.ts";
5959
60const testLayer = (platform: NodeJS.Platform) =>
60const layerTestFor = (platform: NodeJS.Platform) =>
6161 ElectronWindow.layer.pipe(Layer.provide(Layer.succeed(HostProcessPlatform, platform)));
6262
63const TestLayer = testLayer("linux");
63const layerTest = layerTestFor("linux");
6464
6565function makeBrowserWindow(input: { readonly id: number; readonly destroyed: boolean }) {
6666 return {
......@@ -169,7 +169,7 @@ describe("ElectronWindow", () => {
169169 assert.equal(error.message, 'Failed to create Electron BrowserWindow "T3 Code" (1100x780).');
170170 assert.notInclude(error.message, cause.message);
171171 assert.deepEqual(browserWindowMock.mock.calls, [[options]]);
172 }).pipe(Effect.provide(TestLayer)),
172 }).pipe(Effect.provide(layerTest)),
173173 );
174174
175175 it.effect("skips windows destroyed before appearance sync runs", () =>
......@@ -187,7 +187,7 @@ describe("ElectronWindow", () => {
187187 );
188188
189189 assert.deepEqual(syncedWindows, [liveWindow]);
190 }).pipe(Effect.provide(TestLayer)),
190 }).pipe(Effect.provide(layerTest)),
191191 );
192192
193193 it.effect("preserves window enumeration failures as structured defects", () =>
......@@ -211,7 +211,7 @@ describe("ElectronWindow", () => {
211211 assert.strictEqual(error.cause, cause);
212212 assert.notInclude(error.message, cause.message);
213213 }
214 }).pipe(Effect.provide(TestLayer)),
214 }).pipe(Effect.provide(layerTest)),
215215 );
216216
217217 it.effect("preserves reveal failures with the target window", () =>
......@@ -238,7 +238,7 @@ describe("ElectronWindow", () => {
238238 assert.isNull(error.channel);
239239 assert.strictEqual(error.cause, cause);
240240 }
241 }).pipe(Effect.provide(TestLayer)),
241 }).pipe(Effect.provide(layerTest)),
242242 );
243243
244244 it.effect("uses native Windows activation without querying the foreground window", () =>
......@@ -262,7 +262,7 @@ describe("ElectronWindow", () => {
262262 assert.deepEqual(operations, ["app-focus", "show", "move-top", "focus", "native-activation"]);
263263 assert.lengthOf(loadWindowsForegroundApiMock.mock.calls, 0);
264264 assert.lengthOf(windowsForegroundFocusMock.mock.calls, 0);
265 }).pipe(Effect.provide(testLayer("win32"))),
265 }).pipe(Effect.provide(layerTestFor("win32"))),
266266 );
267267
268268 it.effect("focuses the exact T3 window before activating from a shell-hosted app", () =>
......@@ -308,7 +308,7 @@ describe("ElectronWindow", () => {
308308 },
309309 ],
310310 ]);
311 }).pipe(Effect.provide(testLayer("win32"))),
311 }).pipe(Effect.provide(layerTestFor("win32"))),
312312 );
313313
314314 it.effect("prepares the exact T3 window before a capture overlay", () =>
......@@ -335,7 +335,7 @@ describe("ElectronWindow", () => {
335335 ]);
336336 assert.lengthOf(windowsForegroundFocusMock.mock.calls, 0);
337337 assert.lengthOf(activateWindowsForegroundMock.mock.calls, 0);
338 }).pipe(Effect.provide(testLayer("win32"))),
338 }).pipe(Effect.provide(layerTestFor("win32"))),
339339 );
340340
341341 it.effect.each([4, 8])(
......@@ -362,7 +362,7 @@ describe("ElectronWindow", () => {
362362 assert.lengthOf(windowsForegroundFocusMock.mock.calls, 0);
363363 assert.lengthOf(window.getTitle.mock.calls, 0);
364364 assert.lengthOf(activateWindowsForegroundMock.mock.calls, 2);
365 }).pipe(Effect.provide(testLayer("win32"))),
365 }).pipe(Effect.provide(layerTestFor("win32"))),
366366 );
367367
368368 it.effect.each([42n, 0n])(
......@@ -381,7 +381,7 @@ describe("ElectronWindow", () => {
381381
382382 assert.lengthOf(windowsForegroundFocusMock.mock.calls, 1);
383383 assert.lengthOf(activateWindowsForegroundMock.mock.calls, 2);
384 }).pipe(Effect.provide(testLayer("win32"))),
384 }).pipe(Effect.provide(layerTestFor("win32"))),
385385 );
386386
387387 it.effect("continues native focus when the foreground query fails", () =>
......@@ -398,7 +398,7 @@ describe("ElectronWindow", () => {
398398
399399 assert.lengthOf(windowsForegroundFocusMock.mock.calls, 1);
400400 assert.lengthOf(activateWindowsForegroundMock.mock.calls, 2);
401 }).pipe(Effect.provide(testLayer("win32"))),
401 }).pipe(Effect.provide(layerTestFor("win32"))),
402402 );
403403
404404 it.effect("does not fail reveal when native focus rejects", () =>
......@@ -415,7 +415,7 @@ describe("ElectronWindow", () => {
415415
416416 assert.lengthOf(windowsForegroundFocusMock.mock.calls, 1);
417417 assert.lengthOf(activateWindowsForegroundMock.mock.calls, 2);
418 }).pipe(Effect.provide(testLayer("win32"))),
418 }).pipe(Effect.provide(layerTestFor("win32"))),
419419 );
420420
421421 it.effect("fails reveal when Windows refuses foreground activation", () =>
......@@ -441,7 +441,7 @@ describe("ElectronWindow", () => {
441441 [window.getNativeWindowHandle.mock.results[0]?.value],
442442 [window.getNativeWindowHandle.mock.results[1]?.value],
443443 ]);
444 }).pipe(Effect.provide(testLayer("win32"))),
444 }).pipe(Effect.provide(layerTestFor("win32"))),
445445 );
446446
447447 it.effect("cancels native focus when destroyed during the foreground query", () =>
......@@ -471,7 +471,7 @@ describe("ElectronWindow", () => {
471471 assert.lengthOf(windowsForegroundFocusMock.mock.calls, 0);
472472 assert.lengthOf(window.getNativeWindowHandle.mock.calls, 1);
473473 assert.lengthOf(window.getTitle.mock.calls, 0);
474 }).pipe(Effect.provide(testLayer("win32"))),
474 }).pipe(Effect.provide(layerTestFor("win32"))),
475475 );
476476
477477 it.effect("preserves message delivery failures with window and channel context", () =>
......@@ -500,7 +500,7 @@ describe("ElectronWindow", () => {
500500 assert.equal(error.channel, "desktop:update");
501501 assert.strictEqual(error.cause, cause);
502502 }
503 }).pipe(Effect.provide(TestLayer)),
503 }).pipe(Effect.provide(layerTest)),
504504 );
505505
506506 it.effect("preserves destroy failures and continues with later windows", () =>
......@@ -531,7 +531,7 @@ describe("ElectronWindow", () => {
531531 assert.strictEqual(error.cause, cause);
532532 }
533533 assert.equal(vi.mocked(laterWindow.destroy).mock.calls.length, 1);
534 }).pipe(Effect.provide(TestLayer)),
534 }).pipe(Effect.provide(layerTest)),
535535 );
536536 it.effect("an ordinary reveal on Windows does not touch the Win32 foreground helpers", () =>
537537 Effect.gen(function* () {
......@@ -545,7 +545,7 @@ describe("ElectronWindow", () => {
545545 assert.lengthOf(shellHostedForegroundMock.mock.calls, 0);
546546 assert.lengthOf(startWindowsForegroundFocusThreadMock.mock.calls, 0);
547547 assert.lengthOf(window.focus.mock.calls, 1);
548 }).pipe(Effect.provide(testLayer("win32"))),
548 }).pipe(Effect.provide(layerTestFor("win32"))),
549549 );
550550
551551 it.effect("a capture reveal on Windows uses the Win32 path only once", () =>
......@@ -559,12 +559,12 @@ describe("ElectronWindow", () => {
559559
560560 assert.lengthOf(activateWindowsForegroundMock.mock.calls, 1);
561561 assert.lengthOf(window.focus.mock.calls, 2);
562 }).pipe(Effect.provide(testLayer("win32"))),
562 }).pipe(Effect.provide(layerTestFor("win32"))),
563563 );
564564
565565 it.effect("starts the Windows focus worker lazily and closes it with the layer", () =>
566566 Effect.gen(function* () {
567 yield* ElectronWindow.ElectronWindow.pipe(Effect.provide(testLayer("win32")));
567 yield* ElectronWindow.ElectronWindow.pipe(Effect.provide(layerTestFor("win32")));
568568 assert.lengthOf(startWindowsForegroundFocusThreadMock.mock.calls, 0);
569569 assert.lengthOf(windowsForegroundCloseMock.mock.calls, 0);
570570
......@@ -575,7 +575,7 @@ describe("ElectronWindow", () => {
575575 yield* electronWindow.prepareReveal(window);
576576 assert.lengthOf(startWindowsForegroundFocusThreadMock.mock.calls, 1);
577577 assert.lengthOf(windowsForegroundCloseMock.mock.calls, 0);
578 }).pipe(Effect.provide(testLayer("win32")));
578 }).pipe(Effect.provide(layerTestFor("win32")));
579579 assert.lengthOf(windowsForegroundCloseMock.mock.calls, 1);
580580 }),
581581 );
apps/desktop/src/ipc/methods/localEnvironment.test.ts+2-2
......@@ -14,7 +14,7 @@ import { getLocalEnvironmentEnabled, setLocalEnvironmentEnabled } from "./localE
1414
1515// `relaunch` declares the lifecycle runtime services as requirements even
1616// though the mocked relaunch never touches them.
17const unusedLifecycleRuntimeLayer = Layer.mergeAll(
17const layerUnusedLifecycleRuntime = Layer.mergeAll(
1818 DesktopShutdown.layer,
1919 DesktopState.layer,
2020 Layer.succeed(
......@@ -42,7 +42,7 @@ describe("local environment IPC", () => {
4242 relaunchReasons.push(reason);
4343 }),
4444 }),
45 unusedLifecycleRuntimeLayer,
45 layerUnusedLifecycleRuntime,
4646 );
4747 return Effect.gen(function* () {
4848 yield* setLocalEnvironmentEnabled.handler(false);
apps/desktop/src/ipc/methods/sshEnvironment.test.ts+4-4
......@@ -22,7 +22,7 @@ function jsonResponse(request: HttpClientRequest.HttpClientRequest, body: unknow
2222 );
2323}
2424
25function makeHttpClientLayer(
25function layerHttpClient(
2626 handler: (
2727 request: HttpClientRequest.HttpClientRequest,
2828 ) => Effect.Effect<HttpClientResponse.HttpClientResponse, never>,
......@@ -36,7 +36,7 @@ function makeHttpClientLayer(
3636describe("SSH environment IPC", () => {
3737 it.effect("fetches and decodes the remote environment descriptor", () => {
3838 const requestUrls: string[] = [];
39 const layer = makeHttpClientLayer((request) =>
39 const layer = layerHttpClient((request) =>
4040 Effect.sync(() => {
4141 requestUrls.push(request.url);
4242 return jsonResponse(request, {
......@@ -66,7 +66,7 @@ describe("SSH environment IPC", () => {
6666 });
6767
6868 it.effect("wraps schema decode failures in a typed request error", () => {
69 const layer = makeHttpClientLayer((request) =>
69 const layer = layerHttpClient((request) =>
7070 Effect.succeed(jsonResponse(request, { environmentId: "remote-env" })),
7171 );
7272
......@@ -89,7 +89,7 @@ describe("SSH environment IPC", () => {
8989
9090 it.effect("rejects non-loopback HTTP endpoints before issuing a request", () => {
9191 let requestCount = 0;
92 const layer = makeHttpClientLayer((request) =>
92 const layer = layerHttpClient((request) =>
9393 Effect.sync(() => {
9494 requestCount += 1;
9595 return jsonResponse(request, {});
apps/desktop/src/ipc/methods/wsl.test.ts+22-22
......@@ -28,7 +28,7 @@ const invokeSetWslDistro = (distro: string | null) =>
2828const invokeSetWslOnly = (enabled: boolean) =>
2929 setWslOnly.handler(enabled).pipe(Effect.flatMap(decodeWslState));
3030
31function makeWslBackendLayer(input: { readonly onReconcile?: Effect.Effect<void> } = {}) {
31function layerWslBackend(input: { readonly onReconcile?: Effect.Effect<void> } = {}) {
3232 return Layer.succeed(
3333 DesktopWslBackend.DesktopWslBackend,
3434 DesktopWslBackend.DesktopWslBackend.of({
......@@ -38,7 +38,7 @@ function makeWslBackendLayer(input: { readonly onReconcile?: Effect.Effect<void>
3838 );
3939}
4040
41function makeLifecycleLayer(relaunchReasons: Array<string>) {
41function layerLifecycle(relaunchReasons: Array<string>) {
4242 return Layer.succeed(
4343 DesktopLifecycle.DesktopLifecycle,
4444 DesktopLifecycle.DesktopLifecycle.of({
......@@ -51,7 +51,7 @@ function makeLifecycleLayer(relaunchReasons: Array<string>) {
5151 );
5252}
5353
54const unusedLifecycleRuntimeLayer = Layer.mergeAll(
54const layerUnusedLifecycleRuntime = Layer.mergeAll(
5555 DesktopShutdown.layer,
5656 DesktopState.layer,
5757 Layer.succeed(
......@@ -114,7 +114,7 @@ describe("WSL IPC", () => {
114114 wslOnly: true,
115115 }),
116116 DesktopWslEnvironment.layerTest(),
117 makeWslBackendLayer(),
117 layerWslBackend(),
118118 ),
119119 ),
120120 ),
......@@ -129,9 +129,9 @@ describe("WSL IPC", () => {
129129 wslOnly: true,
130130 }),
131131 DesktopWslEnvironment.layerTest({ isAvailable: true }),
132 makeWslBackendLayer(),
133 makeLifecycleLayer(relaunchReasons),
134 unusedLifecycleRuntimeLayer,
132 layerWslBackend(),
133 layerLifecycle(relaunchReasons),
134 layerUnusedLifecycleRuntime,
135135 );
136136
137137 return Effect.gen(function* () {
......@@ -160,9 +160,9 @@ describe("WSL IPC", () => {
160160 wslOnly: false,
161161 }),
162162 DesktopWslEnvironment.layerTest({ isAvailable: true }),
163 makeWslBackendLayer(),
164 makeLifecycleLayer(relaunchReasons),
165 unusedLifecycleRuntimeLayer,
163 layerWslBackend(),
164 layerLifecycle(relaunchReasons),
165 layerUnusedLifecycleRuntime,
166166 );
167167
168168 return Effect.gen(function* () {
......@@ -197,13 +197,13 @@ describe("WSL IPC", () => {
197197 wslOnly: true,
198198 }),
199199 DesktopWslEnvironment.layerTest({ isAvailable: true }),
200 makeWslBackendLayer({
200 layerWslBackend({
201201 onReconcile: Effect.sync(() => {
202202 reconcileCount += 1;
203203 }),
204204 }),
205 makeLifecycleLayer(relaunchReasons),
206 unusedLifecycleRuntimeLayer,
205 layerLifecycle(relaunchReasons),
206 layerUnusedLifecycleRuntime,
207207 );
208208
209209 return Effect.gen(function* () {
......@@ -232,13 +232,13 @@ describe("WSL IPC", () => {
232232 wslOnly: false,
233233 }),
234234 DesktopWslEnvironment.layerTest({ isAvailable: true }),
235 makeWslBackendLayer({
235 layerWslBackend({
236236 onReconcile: Effect.sync(() => {
237237 reconcileCount += 1;
238238 }),
239239 }),
240 makeLifecycleLayer(relaunchReasons),
241 unusedLifecycleRuntimeLayer,
240 layerLifecycle(relaunchReasons),
241 layerUnusedLifecycleRuntime,
242242 );
243243
244244 return Effect.gen(function* () {
......@@ -261,13 +261,13 @@ describe("WSL IPC", () => {
261261 wslOnly: true,
262262 }),
263263 DesktopWslEnvironment.layerTest({ isAvailable: true }),
264 makeWslBackendLayer({
264 layerWslBackend({
265265 onReconcile: Effect.sync(() => {
266266 reconcileCount += 1;
267267 }),
268268 }),
269 makeLifecycleLayer(relaunchReasons),
270 unusedLifecycleRuntimeLayer,
269 layerLifecycle(relaunchReasons),
270 layerUnusedLifecycleRuntime,
271271 );
272272
273273 return Effect.gen(function* () {
......@@ -300,13 +300,13 @@ describe("WSL IPC", () => {
300300 wslOnly: false,
301301 }),
302302 DesktopWslEnvironment.layerTest({ isAvailable: true }),
303 makeWslBackendLayer({
303 layerWslBackend({
304304 onReconcile: Effect.sync(() => {
305305 reconcileCount += 1;
306306 }),
307307 }),
308 makeLifecycleLayer(relaunchReasons),
309 unusedLifecycleRuntimeLayer,
308 layerLifecycle(relaunchReasons),
309 layerUnusedLifecycleRuntime,
310310 );
311311
312312 return Effect.gen(function* () {
apps/desktop/src/main.ts+37-37
......@@ -82,7 +82,7 @@ if (process.argv.includes("--version")) {
8282 Electron.app.exit(0);
8383}
8484
85const desktopEnvironmentLayer = Layer.unwrap(
85const layerDesktopEnvironment = Layer.unwrap(
8686 Effect.gen(function* () {
8787 const metadata = yield* Effect.service(ElectronApp.ElectronApp).pipe(
8888 Effect.flatMap((app) => app.metadata),
......@@ -115,7 +115,7 @@ const resolveDesktopSshCliRunner = (
115115 return { archiveVersion: environment.appVersion };
116116};
117117
118const desktopSshEnvironmentLayer = Layer.unwrap(
118const layerDesktopSshEnvironment = Layer.unwrap(
119119 Effect.gen(function* () {
120120 const environment = yield* DesktopEnvironment.DesktopEnvironment;
121121 return DesktopSshEnvironment.layer({
......@@ -124,7 +124,7 @@ const desktopSshEnvironmentLayer = Layer.unwrap(
124124 }),
125125);
126126
127const electronLayer = Layer.mergeAll(
127const layerElectron = Layer.mergeAll(
128128 ElectronApp.layer,
129129 ElectronDialog.layer,
130130 ElectronMenu.layer,
......@@ -138,7 +138,7 @@ const electronLayer = Layer.mergeAll(
138138 DesktopIpc.layer(Electron.ipcMain),
139139);
140140
141const desktopFoundationLayer = Layer.mergeAll(
141const layerDesktopFoundation = Layer.mergeAll(
142142 MacPermissions.layer,
143143 DesktopState.layer,
144144 DesktopShutdown.layer,
......@@ -149,36 +149,36 @@ const desktopFoundationLayer = Layer.mergeAll(
149149 DesktopAssets.layer,
150150 DesktopObservability.layer,
151151 DesktopRendererHistory.layer,
152).pipe(Layer.provideMerge(desktopEnvironmentLayer));
152).pipe(Layer.provideMerge(layerDesktopEnvironment));
153153
154const desktopSshLayer = desktopSshEnvironmentLayer.pipe(
154const layerDesktopSsh = layerDesktopSshEnvironment.pipe(
155155 Layer.provideMerge(DesktopSshPasswordPrompts.layer()),
156156);
157157
158const desktopServerExposureLayer = DesktopServerExposure.layer.pipe(
158const layerDesktopServerExposure = DesktopServerExposure.layer.pipe(
159159 Layer.provideMerge(DesktopNetworkInterfaces.layer),
160 Layer.provideMerge(desktopFoundationLayer),
160 Layer.provideMerge(layerDesktopFoundation),
161161);
162162
163const desktopPreviewLayer = PreviewManager.layer.pipe(
163const layerDesktopPreview = PreviewManager.layer.pipe(
164164 // Merged rather than provided so the IPC handlers can reach the import
165165 // service alongside the manager; both sit on the same BrowserSession.
166166 Layer.provideMerge(BrowserImport.layer.pipe(Layer.provide(LinuxBrowserSecret.layer))),
167167 Layer.provideMerge(BrowserSession.layer),
168 Layer.provideMerge(desktopFoundationLayer),
168 Layer.provideMerge(layerDesktopFoundation),
169169);
170170
171const desktopWindowLayer = DesktopWindow.layer.pipe(
172 Layer.provideMerge(desktopServerExposureLayer),
173 Layer.provideMerge(desktopPreviewLayer),
171const layerDesktopWindow = DesktopWindow.layer.pipe(
172 Layer.provideMerge(layerDesktopServerExposure),
173 Layer.provideMerge(layerDesktopPreview),
174174);
175175
176const desktopSnapShotLayer = DesktopSnapShot.layer.pipe(
177 Layer.provideMerge(desktopWindowLayer),
178 Layer.provideMerge(desktopFoundationLayer),
176const layerDesktopSnapShot = DesktopSnapShot.layer.pipe(
177 Layer.provideMerge(layerDesktopWindow),
178 Layer.provideMerge(layerDesktopFoundation),
179179);
180const desktopAppActivationLayer = DesktopAppActivation.layer.pipe(
181 Layer.provide(desktopWindowLayer),
180const layerDesktopAppActivation = DesktopAppActivation.layer.pipe(
181 Layer.provide(layerDesktopWindow),
182182);
183183
184184// Pool layer instantiates the backend factory once for the Windows
......@@ -186,63 +186,63 @@ const desktopAppActivationLayer = DesktopAppActivation.layer.pipe(
186186// the pool now; the legacy DesktopBackendManager service is gone. The
187187// WSL second instance gets registered later in the migration. See
188188// DesktopBackendPool.ts header for the full rollout plan.
189const desktopBackendLayer = DesktopBackendPool.layer.pipe(
189const layerDesktopBackend = DesktopBackendPool.layer.pipe(
190190 Layer.provideMerge(DesktopAppIdentity.layer),
191191 Layer.provideMerge(DesktopBackendConfiguration.layer),
192192 Layer.provideMerge(DesktopWslEnvironment.layer),
193193 Layer.provideMerge(DesktopWslServerTree.layer),
194194 Layer.provideMerge(DesktopTelemetryPublisher.layer),
195 Layer.provideMerge(desktopWindowLayer),
195 Layer.provideMerge(layerDesktopWindow),
196196);
197197
198198// WSL orchestrator hangs off the backend layer because it needs the
199199// pool + configuration + serverExposure; it pulls NetService and the
200200// foundation services through the same provideMerge chain.
201const desktopWslBackendLayer = DesktopWslBackend.layer.pipe(
202 Layer.provideMerge(desktopBackendLayer),
201const layerDesktopWslBackend = DesktopWslBackend.layer.pipe(
202 Layer.provideMerge(layerDesktopBackend),
203203);
204204
205const desktopLocalEnvironmentAuthLayer = DesktopLocalEnvironmentAuth.layer.pipe(
206 Layer.provideMerge(desktopBackendLayer),
205const layerDesktopLocalEnvironmentAuth = DesktopLocalEnvironmentAuth.layer.pipe(
206 Layer.provideMerge(layerDesktopBackend),
207207);
208208
209const desktopApplicationLayer = Layer.mergeAll(
209const layerDesktopApplication = Layer.mergeAll(
210210 DesktopLifecycle.layer,
211 desktopAppActivationLayer,
211 layerDesktopAppActivation,
212212 DesktopApplicationMenu.layer,
213213 DesktopLinuxUrlHandler.layer,
214214 DesktopShellEnvironment.layer,
215 desktopSshLayer,
215 layerDesktopSsh,
216216).pipe(
217 Layer.provideMerge(desktopSnapShotLayer),
217 Layer.provideMerge(layerDesktopSnapShot),
218218 Layer.provideMerge(DesktopUpdates.layer),
219 Layer.provideMerge(desktopWslBackendLayer),
220 Layer.provideMerge(desktopLocalEnvironmentAuthLayer),
219 Layer.provideMerge(layerDesktopWslBackend),
220 Layer.provideMerge(layerDesktopLocalEnvironmentAuth),
221221);
222222
223223// Clerk resolves userData before Electron is ready, so it gets the synchronous FileSystem.
224const desktopClerkLayer = DesktopClerk.layer.pipe(
224const layerDesktopClerk = DesktopClerk.layer.pipe(
225225 Layer.provide(DesktopPreReadyFileSystem.layer),
226226 Layer.provideMerge(ElectronShell.layer),
227 Layer.provideMerge(desktopEnvironmentLayer),
227 Layer.provideMerge(layerDesktopEnvironment),
228228 Layer.provideMerge(NodeServices.layer),
229229 Layer.provideMerge(ElectronApp.layer),
230230);
231231
232const desktopApplicationRuntimeLayer = desktopApplicationLayer.pipe(
232const layerDesktopApplicationRuntime = layerDesktopApplication.pipe(
233233 Layer.provideMerge(NodeServices.layer),
234234 Layer.provideMerge(NodeHttpClient.layerUndici),
235235 Layer.provideMerge(NetService.layer),
236 Layer.provideMerge(electronLayer),
236 Layer.provideMerge(layerElectron),
237237);
238238
239239// Acquire strict pre-ready setup before Clerk. Nothing before the Clerk bridge
240240// may yield, or Electron can emit ready before Clerk registers its scheme.
241const desktopRuntimeLayer = desktopClerkLayer.pipe(
241const layerDesktopRuntime = layerDesktopClerk.pipe(
242242 Layer.flatMap((clerkContext) =>
243 desktopApplicationRuntimeLayer.pipe(Layer.provideMerge(Layer.succeedContext(clerkContext))),
243 layerDesktopApplicationRuntime.pipe(Layer.provideMerge(Layer.succeedContext(clerkContext))),
244244 ),
245245 Layer.provideMerge(DesktopPreReadyPlatform.layer),
246246);
247247
248DesktopApp.program.pipe(Effect.provide(desktopRuntimeLayer), NodeRuntime.runMain);
248DesktopApp.program.pipe(Effect.provide(layerDesktopRuntime), NodeRuntime.runMain);
apps/desktop/src/preview/BrowserImport/BrowserImport.test.ts+4-4
......@@ -34,7 +34,7 @@ const cookie = {
3434 * Dies if the import reaches session work: every case here covers a request
3535 * that must be rejected before a cookie is read or written.
3636 */
37const rejectedBeforeSession = Layer.succeed(
37const layerRejectedBeforeSession = Layer.succeed(
3838 BrowserSession.BrowserSession,
3939 BrowserSession.BrowserSession.of({
4040 getPartition: () => Effect.die("getPartition must not be reached"),
......@@ -52,7 +52,7 @@ const rejectedBeforeSession = Layer.succeed(
5252const withImporter = Effect.fnUntraced(function* () {
5353 const fileSystem = yield* FileSystem.FileSystem;
5454 const home = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3code-import-" });
55 const environment = Layer.succeed(HostProcessEnvironment, { HOME: home });
55 const layerEnvironment = Layer.succeed(HostProcessEnvironment, { HOME: home });
5656 const context = yield* sourcePathContext.pipe(
5757 Effect.provideService(HostProcessEnvironment, { HOME: home }),
5858 Effect.provideService(HostProcessPlatform, "darwin"),
......@@ -67,8 +67,8 @@ const withImporter = Effect.fnUntraced(function* () {
6767 const importer = yield* BrowserImport.BrowserImport.pipe(
6868 Effect.provide(
6969 BrowserImport.layer.pipe(
70 Layer.provide(rejectedBeforeSession),
71 Layer.provide(environment),
70 Layer.provide(layerRejectedBeforeSession),
71 Layer.provide(layerEnvironment),
7272 Layer.provide(Layer.succeed(HostProcessPlatform, "darwin")),
7373 Layer.provide(Layer.succeed(HostProcessExecutablePath, "/Applications/T3 Code.app")),
7474 Layer.provide(NodeServices.layer),
apps/desktop/src/preview/BrowserImport/ChromiumKeys.test.ts+15-15
......@@ -39,7 +39,7 @@ type CapturedCommand = {
3939 };
4040};
4141
42const helperLayer = (input: {
42const layerHelper = (input: {
4343 readonly stdout?: string;
4444 readonly stderr?: string;
4545 readonly stdoutStream?: Stream.Stream<Uint8Array>;
......@@ -83,7 +83,7 @@ describe("macOS Chromium secrets", () => {
8383 keychainAccount: "Chrome",
8484 linuxSecretApplication: undefined,
8585 } as const;
86 const noProcesses = Layer.succeed(
86 const layerNoProcesses = Layer.succeed(
8787 ChildProcessSpawner.ChildProcessSpawner,
8888 ChildProcessSpawner.make(() => Effect.die("must not spawn")),
8989 );
......@@ -93,7 +93,7 @@ describe("macOS Chromium secrets", () => {
9393 getPassword.mockReturnValue("macos-secret");
9494 const keys = yield* resolveChromiumKeys(request);
9595 expect(keys.cbcV10?.toString("hex")).toBe("3df7306fb1eac353289565a2f6b64f74");
96 }).pipe(Effect.provide(noProcesses)),
96 }).pipe(Effect.provide(layerNoProcesses)),
9797 );
9898
9999 it.effect("reports a missing keychain entry", () =>
......@@ -101,7 +101,7 @@ describe("macOS Chromium secrets", () => {
101101 getPassword.mockReturnValue(null);
102102 const error = yield* resolveChromiumKeys(request).pipe(Effect.flip);
103103 expect(error.reason).toBe("keychainItemMissing");
104 }).pipe(Effect.provide(noProcesses)),
104 }).pipe(Effect.provide(layerNoProcesses)),
105105 );
106106
107107 it.effect("preserves a denied keychain approval", () =>
......@@ -113,7 +113,7 @@ describe("macOS Chromium secrets", () => {
113113 const error = yield* resolveChromiumKeys(request).pipe(Effect.flip);
114114 expect(error.reason).toBe("needsKeychainApproval");
115115 expect(error.cause).toBe(denied);
116 }).pipe(Effect.provide(noProcesses)),
116 }).pipe(Effect.provide(layerNoProcesses)),
117117 );
118118});
119119
......@@ -131,7 +131,7 @@ describe("Linux Chromium secrets", () => {
131131 expect(keys.cbcV11Error?.reason).toBe("keychainUnavailable");
132132 }).pipe(
133133 Effect.provide(
134 helperLayer({
134 layerHelper({
135135 spawnError: PlatformError.systemError({
136136 _tag: "NotFound",
137137 module: "ChildProcess",
......@@ -171,7 +171,7 @@ describe("Linux Chromium secrets", () => {
171171 expect(keys.cbcV11).toHaveLength(16);
172172 }).pipe(
173173 Effect.provide(
174 helperLayer({ stdout: "linux-secret", capture: (value) => (captured = value) }),
174 layerHelper({ stdout: "linux-secret", capture: (value) => (captured = value) }),
175175 ),
176176 );
177177 });
......@@ -183,7 +183,7 @@ describe("Linux Chromium secrets", () => {
183183 expect(error.reason).toBe("keychainUnavailable");
184184 }).pipe(
185185 Effect.provide(
186 helperLayer({ stderr: "Cannot autolaunch D-Bus without X11 $DISPLAY", exitCode: 1 }),
186 layerHelper({ stderr: "Cannot autolaunch D-Bus without X11 $DISPLAY", exitCode: 1 }),
187187 ),
188188 ),
189189 );
......@@ -192,7 +192,7 @@ describe("Linux Chromium secrets", () => {
192192 Effect.gen(function* () {
193193 const secret = yield* readLinuxSecret("chrome");
194194 expect(secret).toBe("linux-secret \t\n");
195 }).pipe(Effect.provide(helperLayer({ stdout: "linux-secret \t\n" }))),
195 }).pipe(Effect.provide(layerHelper({ stdout: "linux-secret \t\n" }))),
196196 );
197197
198198 it.effect("drains stdout and stderr concurrently", () =>
......@@ -206,7 +206,7 @@ describe("Linux Chromium secrets", () => {
206206 );
207207
208208 const secret = yield* readLinuxSecret("chrome").pipe(
209 Effect.provide(helperLayer({ stdoutStream: stdout, stderrStream: stderr })),
209 Effect.provide(layerHelper({ stdoutStream: stdout, stderrStream: stderr })),
210210 );
211211
212212 expect(secret).toBe("linux-secret");
......@@ -226,7 +226,7 @@ describe("Linux Chromium secrets", () => {
226226 expect(captured?.options.env?.SESSION_MARKER).toBe("kept");
227227 }).pipe(
228228 Effect.provide(
229 helperLayer({
229 layerHelper({
230230 stderr: "Zugriff verweigert",
231231 exitCode: 3,
232232 capture: (value) => (captured = value),
......@@ -250,7 +250,7 @@ describe("Linux Chromium secrets", () => {
250250 linuxSecretApplication: "brave",
251251 }).pipe(Effect.flip);
252252 expect(error.reason).toBe("needsKeychainApproval");
253 }).pipe(Effect.provide(helperLayer({ stderr: "Keyring is locked", exitCode: 3 }))),
253 }).pipe(Effect.provide(layerHelper({ stderr: "Keyring is locked", exitCode: 3 }))),
254254 );
255255
256256 it.effect("keeps the v10 fallback when the Secret Service backend is unavailable", () =>
......@@ -265,7 +265,7 @@ describe("Linux Chromium secrets", () => {
265265 expect(keys.cbcV11).toBeUndefined();
266266 }).pipe(
267267 Effect.provide(
268 helperLayer({
268 layerHelper({
269269 stderr: "Cannot autolaunch D-Bus without X11 $DISPLAY",
270270 exitCode: 1,
271271 }),
......@@ -283,7 +283,7 @@ describe("Linux Chromium secrets", () => {
283283 });
284284 expect(keys.cbcV10).toHaveLength(16);
285285 expect(keys.cbcV11).toBeUndefined();
286 }).pipe(Effect.provide(helperLayer({ exitCode: 2 }))),
286 }).pipe(Effect.provide(layerHelper({ exitCode: 2 }))),
287287 );
288288});
289289
......@@ -320,7 +320,7 @@ describe("Windows Chromium secrets", () => {
320320 expect(captured?.args.join(" ")).not.toContain(wrapped.toString("base64"));
321321 }).pipe(
322322 Effect.provide(
323 helperLayer({ stdout: key.toString("base64"), capture: (value) => (captured = value) }),
323 layerHelper({ stdout: key.toString("base64"), capture: (value) => (captured = value) }),
324324 ),
325325 Effect.provideService(HostProcessEnvironment, { SystemRoot: "C:\\Windows" }),
326326 );
apps/desktop/src/preview/BrowserImport/LinuxBrowserSecret.test.ts+2-2
......@@ -38,7 +38,7 @@ it.layer(NodeServices.layer)("Linux browser secret path", (it) => {
3838 yield* fileSystem.writeFileString(filename, "helper");
3939 }
4040 const resolve = (isPackaged: boolean, platform: NodeJS.Platform = "linux") => {
41 const environment = DesktopEnvironment.layer({
41 const layerEnvironment = DesktopEnvironment.layer({
4242 dirname: path.join(root, "apps", "desktop", "dist-electron"),
4343 homeDirectory: root,
4444 platform,
......@@ -50,7 +50,7 @@ it.layer(NodeServices.layer)("Linux browser secret path", (it) => {
5050 runningUnderArm64Translation: false,
5151 }).pipe(Layer.provide(DesktopConfig.layerTest({})));
5252 return LinuxBrowserSecret.LinuxBrowserSecretPath.pipe(
53 Effect.provide(LinuxBrowserSecret.layer.pipe(Layer.provide(environment))),
53 Effect.provide(LinuxBrowserSecret.layer.pipe(Layer.provide(layerEnvironment))),
5454 );
5555 };
5656
apps/desktop/src/preview/BrowserSession.test.ts+2-2
......@@ -197,7 +197,7 @@ describe("BrowserSession", () => {
197197 method: "digest",
198198 cause: nativeCause,
199199 });
200 const failingCryptoLayer = Layer.succeed(
200 const layerFailingCrypto = Layer.succeed(
201201 Crypto.Crypto,
202202 Crypto.make({
203203 randomBytes: (size) => new Uint8Array(size),
......@@ -218,7 +218,7 @@ describe("BrowserSession", () => {
218218 "Failed to derive a desktop preview browser partition for scope environment-a.",
219219 );
220220 assert.notInclude(error.message, nativeCause.message);
221 }).pipe(Effect.provide(BrowserSession.layer.pipe(Layer.provide(failingCryptoLayer))));
221 }).pipe(Effect.provide(BrowserSession.layer.pipe(Layer.provide(layerFailingCrypto))));
222222 });
223223
224224 it.effect("preserves session scope, partition, and the Electron failure", () =>
apps/desktop/src/preview/Manager.test.ts+6-6
......@@ -234,7 +234,7 @@ vi.mock("electron", () => ({
234234 },
235235}));
236236
237const browserSessionLayer = Layer.succeed(
237const layerBrowserSession = Layer.succeed(
238238 BrowserSession.BrowserSession,
239239 BrowserSession.BrowserSession.of({
240240 getPartition: () => Effect.succeed("persist:t3code-preview-test"),
......@@ -245,7 +245,7 @@ const browserSessionLayer = Layer.succeed(
245245 }),
246246);
247247
248const environmentLayer = Layer.succeed(
248const layerEnvironment = Layer.succeed(
249249 DesktopEnvironment.DesktopEnvironment,
250250 DesktopEnvironment.DesktopEnvironment.of({
251251 browserArtifactsDir: "/tmp/t3/dev/browser-artifacts",
......@@ -256,7 +256,7 @@ const environmentLayer = Layer.succeed(
256256 } as DesktopEnvironment.DesktopEnvironment["Service"]),
257257);
258258
259const fileSystemLayer = FileSystem.layerNoop({
259const layerFileSystem = FileSystem.layerNoop({
260260 makeDirectory: (path) =>
261261 Effect.sync(() => {
262262 mkdir(path);
......@@ -275,9 +275,9 @@ const layer = PreviewManager.layer.pipe(
275275 shutdown: Effect.void,
276276 }),
277277 ),
278 Layer.provideMerge(browserSessionLayer),
279 Layer.provideMerge(environmentLayer),
280 Layer.provideMerge(fileSystemLayer),
278 Layer.provideMerge(layerBrowserSession),
279 Layer.provideMerge(layerEnvironment),
280 Layer.provideMerge(layerFileSystem),
281281 Layer.provideMerge(Path.layer),
282282 Layer.provideMerge(Layer.succeed(HostProcessPlatform, "darwin")),
283283);
apps/desktop/src/settings/DesktopAppSettings.test.ts+2-2
......@@ -39,7 +39,7 @@ const DesktopSettingsPatch = Schema.Struct({
3939const decodeDesktopSettingsPatch = Schema.decodeEffect(Schema.fromJsonString(DesktopSettingsPatch));
4040const encodeDesktopSettingsPatch = Schema.encodeEffect(Schema.fromJsonString(DesktopSettingsPatch));
4141
42function makeEnvironmentLayer(baseDir: string, appVersion = "0.0.17") {
42function layerEnvironment(baseDir: string, appVersion = "0.0.17") {
4343 return DesktopEnvironment.layer({
4444 dirname: "/repo/apps/desktop/src",
4545 homeDirectory: baseDir,
......@@ -73,7 +73,7 @@ const withSettings = <A, E, R>(
7373 return yield* effect.pipe(
7474 Effect.provide(
7575 DesktopAppSettings.layer.pipe(
76 Layer.provideMerge(makeEnvironmentLayer(baseDir, options?.appVersion)),
76 Layer.provideMerge(layerEnvironment(baseDir, options?.appVersion)),
7777 Layer.provideMerge(NodeServices.layer),
7878 ),
7979 ),
apps/desktop/src/settings/DesktopClientSettings.diagnostics.test.ts+4-7
......@@ -19,8 +19,8 @@ interface LogRecord {
1919
2020const baseDir = "/virtual-home";
2121
22function makeLayer(fileSystemLayer: Layer.Layer<FileSystem.FileSystem>) {
23 const environmentLayer = DesktopEnvironment.layer({
22function layer(fileSystemLayer: Layer.Layer<FileSystem.FileSystem>) {
23 const layerEnvironment = DesktopEnvironment.layer({
2424 dirname: "/repo/apps/desktop/src",
2525 homeDirectory: baseDir,
2626 platform: "darwin",
......@@ -37,7 +37,7 @@ function makeLayer(fileSystemLayer: Layer.Layer<FileSystem.FileSystem>) {
3737 );
3838
3939 return DesktopClientSettings.layer.pipe(
40 Layer.provideMerge(Layer.mergeAll(environmentLayer, NodeServices.layer, fileSystemLayer)),
40 Layer.provideMerge(Layer.mergeAll(layerEnvironment, NodeServices.layer, fileSystemLayer)),
4141 );
4242}
4343
......@@ -60,10 +60,7 @@ const readWithLogs = (fileSystemLayer: Layer.Layer<FileSystem.FileSystem>) => {
6060 };
6161 }).pipe(
6262 Effect.provide(
63 Layer.mergeAll(
64 makeLayer(fileSystemLayer),
65 Logger.layer([logger], { mergeWithExisting: false }),
66 ),
63 Layer.mergeAll(layer(fileSystemLayer), Logger.layer([logger], { mergeWithExisting: false })),
6764 ),
6865 );
6966};
apps/desktop/src/settings/DesktopClientSettings.test.ts+4-4
......@@ -78,8 +78,8 @@ const decodeClientSettingsJson = Schema.decodeEffect(Schema.fromJsonString(Clien
7878const decodeRecordJson = Schema.decodeEffect(
7979 Schema.fromJsonString(Schema.Record(Schema.String, Schema.Unknown)),
8080);
81function makeLayer(baseDir: string) {
82 const environmentLayer = DesktopEnvironment.layer({
81function layer(baseDir: string) {
82 const layerEnvironment = DesktopEnvironment.layer({
8383 dirname: "/repo/apps/desktop/src",
8484 homeDirectory: baseDir,
8585 platform: "darwin",
......@@ -96,7 +96,7 @@ function makeLayer(baseDir: string) {
9696 );
9797
9898 return DesktopClientSettings.layer.pipe(
99 Layer.provideMerge(environmentLayer),
99 Layer.provideMerge(layerEnvironment),
100100 Layer.provideMerge(NodeServices.layer),
101101 );
102102}
......@@ -109,7 +109,7 @@ const withClientSettings = <A, E, R>(
109109 const baseDir = yield* fileSystem.makeTempDirectoryScoped({
110110 prefix: "t3-desktop-client-settings-test-",
111111 });
112 return yield* effect.pipe(Effect.provide(makeLayer(baseDir)));
112 return yield* effect.pipe(Effect.provide(layer(baseDir)));
113113 }).pipe(Effect.provide(NodeServices.layer), Effect.scoped);
114114
115115describe("DesktopClientSettings", () => {
apps/desktop/src/settings/DesktopSavedEnvironments.test.ts+11-11
......@@ -60,7 +60,7 @@ const seedSavedEnvironmentRegistry = Effect.fn(function* (encryptedBearerToken?:
6060 yield* fileSystem.writeFileString(environment.savedEnvironmentRegistryPath, `${encoded}\n`);
6161});
6262
63function makeSafeStorageLayer(input: {
63function layerSafeStorageFor(input: {
6464 readonly available: boolean;
6565 readonly availabilityError?: unknown;
6666 readonly decryptError?: unknown;
......@@ -98,7 +98,7 @@ function makeSafeStorageLayer(input: {
9898 } satisfies ElectronSafeStorage.ElectronSafeStorage["Service"]);
9999}
100100
101function makeLayer(
101function layer(
102102 baseDir: string,
103103 options?: {
104104 readonly availableSecretStorage?: boolean;
......@@ -107,7 +107,7 @@ function makeLayer(
107107 },
108108 fileSystemLayer: Layer.Layer<FileSystem.FileSystem> = NodeServices.layer,
109109) {
110 const environmentLayer = DesktopEnvironment.layer({
110 const layerEnvironment = DesktopEnvironment.layer({
111111 dirname: "/repo/apps/desktop/src",
112112 homeDirectory: baseDir,
113113 platform: "darwin",
......@@ -123,19 +123,19 @@ function makeLayer(
123123 ),
124124 );
125125
126 const safeStorageLayer = makeSafeStorageLayer({
126 const layerSafeStorage = layerSafeStorageFor({
127127 available: options?.availableSecretStorage ?? true,
128128 availabilityError: options?.availabilityError,
129129 decryptError: options?.decryptError,
130130 });
131 const dependencies = Layer.mergeAll(
132 environmentLayer,
133 safeStorageLayer,
131 const layerDependencies = Layer.mergeAll(
132 layerEnvironment,
133 layerSafeStorage,
134134 NodeServices.layer,
135135 fileSystemLayer,
136136 );
137137
138 return DesktopSavedEnvironments.layer.pipe(Layer.provideMerge(dependencies));
138 return DesktopSavedEnvironments.layer.pipe(Layer.provideMerge(layerDependencies));
139139}
140140
141141const withSavedEnvironments = <A, E, R>(
......@@ -151,7 +151,7 @@ const withSavedEnvironments = <A, E, R>(
151151 const baseDir = yield* fileSystem.makeTempDirectoryScoped({
152152 prefix: "t3-desktop-saved-environments-test-",
153153 });
154 return yield* effect.pipe(Effect.provide(makeLayer(baseDir, options)));
154 return yield* effect.pipe(Effect.provide(layer(baseDir, options)));
155155 }).pipe(Effect.provide(NodeServices.layer), Effect.scoped);
156156
157157describe("DesktopSavedEnvironments", () => {
......@@ -346,14 +346,14 @@ describe("DesktopSavedEnvironments", () => {
346346 method: "readFileString",
347347 pathOrDescriptor: registryPath,
348348 });
349 const fileSystemLayer = Layer.succeed(
349 const layerFileSystem = Layer.succeed(
350350 FileSystem.FileSystem,
351351 FileSystem.makeNoop({
352352 readFileString: () => Effect.fail(permissionError),
353353 }),
354354 );
355355 const savedEnvironments = yield* DesktopSavedEnvironments.DesktopSavedEnvironments.pipe(
356 Effect.provide(makeLayer(baseDir, undefined, fileSystemLayer)),
356 Effect.provide(layer(baseDir, undefined, layerFileSystem)),
357357 );
358358
359359 const error = yield* savedEnvironments.getRegistry.pipe(Effect.flip);
apps/desktop/src/shell/DesktopShellEnvironment.test.ts+3-3
......@@ -70,13 +70,13 @@ function runShellEnvironment(input: {
7070 readonly handler: (command: ChildProcess.Command) => string;
7171 readonly failure?: PlatformError.PlatformError;
7272}) {
73 const environmentLayer = Layer.succeed(
73 const layerEnvironment = Layer.succeed(
7474 DesktopEnvironment.DesktopEnvironment,
7575 DesktopEnvironment.DesktopEnvironment.of({
7676 platform: input.platform,
7777 } as DesktopEnvironment.DesktopEnvironment["Service"]),
7878 );
79 const spawnerLayer = Layer.succeed(
79 const layerSpawner = Layer.succeed(
8080 ChildProcessSpawner.ChildProcessSpawner,
8181 ChildProcessSpawner.make((command) =>
8282 input.failure === undefined
......@@ -91,7 +91,7 @@ function runShellEnvironment(input: {
9191 }).pipe(
9292 Effect.provide(
9393 DesktopShellEnvironment.layer.pipe(
94 Layer.provide(Layer.mergeAll(environmentLayer, NodeServices.layer, spawnerLayer)),
94 Layer.provide(Layer.mergeAll(layerEnvironment, NodeServices.layer, layerSpawner)),
9595 ),
9696 ),
9797 );
apps/desktop/src/snapShot/DesktopSnapShot.test.ts+49-49
......@@ -484,7 +484,7 @@ import {
484484 snapShotAnimationDisplayBounds,
485485 snapShotAnimationOverlayBounds,
486486} from "./SnapShotTransition.ts";
487const testLayer = (
487const layerTest = (
488488 platform: NodeJS.Platform,
489489 fileSystemOverrides: Parameters<typeof FileSystem.layerNoop>[0] = {},
490490 initialSettings: Option.Option<ClientSettings> = Option.none(),
......@@ -639,7 +639,7 @@ function concurrentCaptureFixture(platform: NodeJS.Platform, animations: boolean
639639 const handoff = Effect.sync(() => captures[state.handoffs++]!.handoff.resolve());
640640 let randomByte = 0;
641641 const layer = Layer.mergeAll(
642 testLayer(platform, {
642 layerTest(platform, {
643643 makeDirectory: () => Effect.void,
644644 writeFile: (path, bytes) =>
645645 Effect.sync(() => {
......@@ -776,7 +776,7 @@ it.effect("rejects desktop config changes outside Niri or Hyprland", () =>
776776 assert.equal(error.message, "Config setup requires a Niri or Hyprland session.");
777777 }
778778 }),
779 ).pipe(Effect.provide(testLayer("win32"))),
779 ).pipe(Effect.provide(layerTest("win32"))),
780780);
781781
782782it.effect("reads and acknowledges queued captures through Effect services", () => {
......@@ -797,7 +797,7 @@ it.effect("reads and acknowledges queued captures through Effect services", () =
797797 windowTitle: "main.ts",
798798 },
799799 });
800 const layer = testLayer("linux", {
800 const layer = layerTest("linux", {
801801 readDirectory: () => Effect.succeed([captureId + ".json", "invalid.json"]),
802802 readFileString: (filePath) => Effect.succeed(filePath === metadataPath ? metadata : "invalid"),
803803 readFile: () => Effect.succeed(new Uint8Array([1, 2, 3])),
......@@ -841,7 +841,7 @@ it.effect("captures the active Windows window without enumerating desktop source
841841 getFileIconMock.mockReset().mockResolvedValue(fakeIcon("file"));
842842 const writtenFiles: Array<[string, Uint8Array]> = [];
843843 let metadata = "";
844 const layer = testLayer("win32", {
844 const layer = layerTest("win32", {
845845 makeDirectory: () => Effect.void,
846846 rename: () => Effect.void,
847847 writeFile: (path, bytes) =>
......@@ -897,7 +897,7 @@ it.effect.each([
897897 result: Promise.resolve(undefined),
898898 });
899899 let metadata = "";
900 const layer = testLayer("darwin", {
900 const layer = layerTest("darwin", {
901901 makeDirectory: () => Effect.void,
902902 rename: () => Effect.void,
903903 writeFileString: (_, text) =>
......@@ -1027,7 +1027,7 @@ it.effect.each(["win32", "darwin", "linux"] as const)(
10271027 }),
10281028 ).pipe(
10291029 Effect.provide(
1030 testLayer(platform, {
1030 layerTest(platform, {
10311031 makeDirectory: () => Effect.void,
10321032 rename: () => Effect.void,
10331033 writeFile: (_, bytes) =>
......@@ -1288,7 +1288,7 @@ it.effect("matches Windows accessibility windows on a scaled display", () => {
12881288 }),
12891289 ).pipe(
12901290 Effect.provide(
1291 testLayer("win32", {
1291 layerTest("win32", {
12921292 makeDirectory: () => Effect.void,
12931293 rename: () => Effect.void,
12941294 writeFile: () => Effect.void,
......@@ -1320,7 +1320,7 @@ it.effect("skips accessibility capture when the setting is disabled", () => {
13201320 accessibilityProcessWarmMock.mockClear();
13211321 accessibilityProcessReadMock.mockClear();
13221322 accessibilityByPidMock.mockClear();
1323 const layer = testLayer("win32", {
1323 const layer = layerTest("win32", {
13241324 makeDirectory: () => Effect.void,
13251325 rename: () => Effect.void,
13261326 writeFile: () => Effect.void,
......@@ -1364,7 +1364,7 @@ it.effect("keeps an accessibility helper warm only while capture data is enabled
13641364
13651365 assert.lengthOf(accessibilityProcessCoolMock.mock.calls, 1);
13661366 }),
1367 ).pipe(Effect.provide(testLayer("win32")));
1367 ).pipe(Effect.provide(layerTest("win32")));
13681368});
13691369
13701370it.effect("rejects X11 capture without registering shortcuts or loading capture backends", () => {
......@@ -1389,7 +1389,7 @@ it.effect("rejects X11 capture without registering shortcuts or loading capture
13891389 assert.lengthOf(activeWindowMock.mock.calls, 0);
13901390 }),
13911391 ).pipe(
1392 Effect.provide(testLayer("linux")),
1392 Effect.provide(layerTest("linux")),
13931393 Effect.ensuring(Effect.sync(() => vi.unstubAllEnvs())),
13941394 );
13951395});
......@@ -1476,7 +1476,7 @@ it.effect.each(["gnome", "niri", "kde", "hyprland"] as const)(
14761476 }),
14771477 ).pipe(
14781478 Effect.provide(
1479 testLayer("linux", {
1479 layerTest("linux", {
14801480 makeDirectory: () => Effect.void,
14811481 rename: () => Effect.void,
14821482 writeFile: () => Effect.void,
......@@ -1543,7 +1543,7 @@ it.effect("persists a window capture from a portal activation without a setup te
15431543 }),
15441544 ).pipe(
15451545 Effect.provide(
1546 testLayer("linux", {
1546 layerTest("linux", {
15471547 makeDirectory: () => Effect.void,
15481548 rename: () => Effect.void,
15491549 writeFile: (_, bytes) =>
......@@ -1574,7 +1574,7 @@ it.effect("does not fall back to the picker when an automatic Wayland capture fa
15741574 }),
15751575 ).pipe(
15761576 Effect.provide(
1577 testLayer("linux", {
1577 layerTest("linux", {
15781578 makeDirectory: () => Effect.void,
15791579 remove: () => Effect.void,
15801580 }),
......@@ -1649,7 +1649,7 @@ it.effect(
16491649 ).pipe(
16501650 Effect.provide(
16511651 Layer.mergeAll(
1652 testLayer("linux", {
1652 layerTest("linux", {
16531653 makeDirectory: () => Effect.void,
16541654 rename: () => Effect.void,
16551655 writeFile: () => Effect.void,
......@@ -1686,7 +1686,7 @@ it.effect("does not read unverified accessibility context for a Wayland portal c
16861686 thumbnail: { isEmpty: () => false, toPNG: () => png },
16871687 },
16881688 ]);
1689 const layer = testLayer("linux", {
1689 const layer = layerTest("linux", {
16901690 makeDirectory: () => Effect.void,
16911691 rename: () => Effect.void,
16921692 writeFile: () => Effect.void,
......@@ -1728,7 +1728,7 @@ it.effect("uses display-local macOS capture surfaces across the source and main
17281728 isDestroyed: () => false,
17291729 });
17301730 flashWindows.length = 0;
1731 const layer = testLayer("darwin", {
1731 const layer = layerTest("darwin", {
17321732 makeDirectory: () => Effect.void,
17331733 rename: () => Effect.void,
17341734 writeFileString: () => Effect.void,
......@@ -1807,7 +1807,7 @@ it.effect.each(["ready", "failed"] as const)(
18071807 }),
18081808 ).pipe(
18091809 Effect.provide(
1810 testLayer("win32", {
1810 layerTest("win32", {
18111811 makeDirectory: () => Effect.void,
18121812 rename: () => Effect.void,
18131813 writeFile: () => Effect.void,
......@@ -1848,7 +1848,7 @@ it.effect("uses the unfocused main window for a macOS cross-display transition",
18481848 },
18491849 ]);
18501850 flashWindows.length = 0;
1851 const layer = testLayer("darwin", {
1851 const layer = layerTest("darwin", {
18521852 makeDirectory: () => Effect.void,
18531853 rename: () => Effect.void,
18541854 writeFileString: () => Effect.void,
......@@ -3048,7 +3048,7 @@ it.effect("does not request permissions or create the flash during desktop start
30483048 assert.lengthOf(getSourcesMock.mock.calls, 0);
30493049 assert.lengthOf(openExternalMock.mock.calls, 0);
30503050 }),
3051 ).pipe(Effect.provide(testLayer("darwin", {}, Option.some(settings))));
3051 ).pipe(Effect.provide(layerTest("darwin", {}, Option.some(settings))));
30523052});
30533053
30543054it.effect("keeps snapshots disabled when client settings cannot be read at startup", () => {
......@@ -3064,7 +3064,7 @@ it.effect("keeps snapshots disabled when client settings cannot be read at start
30643064 yield* service.initialize;
30653065 assert.isFalse((yield* service.state).shortcutRegistered);
30663066 }),
3067 ).pipe(Effect.provide(testLayer("win32", {}, Option.none(), Effect.fail(readError))));
3067 ).pipe(Effect.provide(layerTest("win32", {}, Option.none(), Effect.fail(readError))));
30683068});
30693069
30703070it.effect("does not request macOS permissions while synchronizing enabled settings", () => {
......@@ -3083,7 +3083,7 @@ it.effect("does not request macOS permissions while synchronizing enabled settin
30833083 assert.lengthOf(getSourcesMock.mock.calls, 0);
30843084 assert.lengthOf(openExternalMock.mock.calls, 0);
30853085 }),
3086 ).pipe(Effect.provide(testLayer("darwin")));
3086 ).pipe(Effect.provide(layerTest("darwin")));
30873087});
30883088
30893089it.effect("requests macOS permissions only for an explicit enable action", () => {
......@@ -3101,7 +3101,7 @@ it.effect("requests macOS permissions only for an explicit enable action", () =>
31013101 assert.lengthOf(getSourcesMock.mock.calls, 1);
31023102 assert.lengthOf(openExternalMock.mock.calls, 1);
31033103 }),
3104 ).pipe(Effect.provide(testLayer("darwin")));
3104 ).pipe(Effect.provide(layerTest("darwin")));
31053105});
31063106
31073107it.effect("does not request macOS accessibility permission when capture data is disabled", () => {
......@@ -3119,7 +3119,7 @@ it.effect("does not request macOS accessibility permission when capture data is
31193119 assert.lengthOf(getSourcesMock.mock.calls, 1);
31203120 assert.lengthOf(openExternalMock.mock.calls, 1);
31213121 }),
3122 ).pipe(Effect.provide(testLayer("darwin")));
3122 ).pipe(Effect.provide(layerTest("darwin")));
31233123});
31243124
31253125it.effect("reports macOS permission status and requests each permission on its own", () => {
......@@ -3143,7 +3143,7 @@ it.effect("reports macOS permission status and requests each permission on its o
31433143 assert.lengthOf(getSourcesMock.mock.calls, 1);
31443144 assert.lengthOf(openExternalMock.mock.calls, 1);
31453145 }),
3146 ).pipe(Effect.provide(testLayer("darwin")));
3146 ).pipe(Effect.provide(layerTest("darwin")));
31473147});
31483148
31493149it.effect("flags revoked macOS permissions on read and re-registers once they return", () => {
......@@ -3183,7 +3183,7 @@ it.effect("flags revoked macOS permissions on read and re-registers once they re
31833183 assert.isNull(recovered.message);
31843184 assert.isTrue(recovered.shortcutRegistered);
31853185 }),
3186 ).pipe(Effect.provide(testLayer("darwin")));
3186 ).pipe(Effect.provide(layerTest("darwin")));
31873187});
31883188
31893189it.effect("rejects macOS permission actions off macOS and omits macPermissions there", () => {
......@@ -3196,7 +3196,7 @@ it.effect("rejects macOS permission actions off macOS and omits macPermissions t
31963196 const error = yield* service.setup("allow-screen-recording").pipe(Effect.flip);
31973197 assert.equal(error.reason, "unsupported-session");
31983198 }),
3199 ).pipe(Effect.provide(testLayer("win32")));
3199 ).pipe(Effect.provide(layerTest("win32")));
32003200});
32013201
32023202it.effect("registers macOS capture without accessibility permission when data is disabled", () => {
......@@ -3216,7 +3216,7 @@ it.effect("registers macOS capture without accessibility permission when data is
32163216 assert.lengthOf(accessibilityTrustedMock.mock.calls, 0);
32173217 assert.isTrue((yield* service.state).shortcutRegistered);
32183218 }),
3219 ).pipe(Effect.provide(testLayer("darwin")));
3219 ).pipe(Effect.provide(layerTest("darwin")));
32203220});
32213221
32223222it.effect("starts the Shift listener outside the Electron main process", () => {
......@@ -3240,7 +3240,7 @@ it.effect("starts the Shift listener outside the Electron main process", () => {
32403240 yield* Effect.promise(() => new Promise<void>((resolve) => queueMicrotask(resolve)));
32413241 assert.isFalse((yield* service.state).shortcutRegistered);
32423242 }),
3243 ).pipe(Effect.provide(testLayer("win32")));
3243 ).pipe(Effect.provide(layerTest("win32")));
32443244});
32453245
32463246it.effect("passes the configured modifier pair to the listener process", () => {
......@@ -3261,7 +3261,7 @@ it.effect("passes the configured modifier pair to the listener process", () => {
32613261 assert.isTrue(state.shortcutRegistered);
32623262 assert.deepEqual(shortcutForkArgs[0], ["meta"]);
32633263 }),
3264 ).pipe(Effect.provide(testLayer("win32")));
3264 ).pipe(Effect.provide(layerTest("win32")));
32653265});
32663266
32673267it.effect("registers a configured key chord instead of the Shift listener", () => {
......@@ -3289,7 +3289,7 @@ it.effect("registers a configured key chord instead of the Shift listener", () =
32893289 assert.strictEqual(registerShortcutMock.mock.calls[0]?.[0], "Control+Alt+K");
32903290 assert.lengthOf(shortcutProcesses, 0);
32913291 }),
3292 ).pipe(Effect.provide(testLayer("win32")));
3292 ).pipe(Effect.provide(layerTest("win32")));
32933293});
32943294
32953295it.effect("an unrelated client-setting change keeps the registered shortcut", () => {
......@@ -3330,7 +3330,7 @@ it.effect("an unrelated client-setting change keeps the registered shortcut", ()
33303330 ["Control+Alt+K", "Control+Alt+J"],
33313331 );
33323332 }),
3333 ).pipe(Effect.provide(testLayer("win32")));
3333 ).pipe(Effect.provide(layerTest("win32")));
33343334});
33353335
33363336it.effect("capture fails closed while snapshots are disabled", () => {
......@@ -3348,7 +3348,7 @@ it.effect("capture fails closed while snapshots are disabled", () => {
33483348 assert.lengthOf(getSourcesMock.mock.calls, 0);
33493349 assert.lengthOf(regionCaptureMock.mock.calls, 0);
33503350 }),
3351 ).pipe(Effect.provide(testLayer("win32")));
3351 ).pipe(Effect.provide(layerTest("win32")));
33523352});
33533353
33543354it.effect("keeps shortcut registration errors off the capture status", () => {
......@@ -3378,7 +3378,7 @@ it.effect("keeps shortcut registration errors off the capture status", () => {
33783378 "This shortcut is already used by the system or another app.",
33793379 );
33803380 }),
3381 ).pipe(Effect.provide(testLayer("win32")));
3381 ).pipe(Effect.provide(layerTest("win32")));
33823382});
33833383
33843384it.effect("uses an external Niri shortcut without registering an Electron accelerator", () => {
......@@ -3411,7 +3411,7 @@ it.effect("uses an external Niri shortcut without registering an Electron accele
34113411 assert.isUndefined((yield* service.state).shortcutBinding);
34123412 }),
34133413 ).pipe(
3414 Effect.provide(testLayer("linux")),
3414 Effect.provide(layerTest("linux")),
34153415 Effect.ensuring(Effect.sync(() => vi.unstubAllEnvs())),
34163416 );
34173417});
......@@ -3452,7 +3452,7 @@ it.effect("defers ordinary Wayland shortcut registration until settings are appl
34523452 assert.lengthOf(registerShortcutMock.mock.calls, 0);
34533453 }),
34543454 ).pipe(
3455 Effect.provide(testLayer("linux")),
3455 Effect.provide(layerTest("linux")),
34563456 Effect.ensuring(Effect.sync(() => vi.unstubAllEnvs())),
34573457 );
34583458});
......@@ -3472,7 +3472,7 @@ it.effect.each(["GNOME", "KDE", "niri", "Hyprland"] as const)(
34723472 assert.isFalse(state.shortcutVerified);
34733473 }),
34743474 ).pipe(
3475 Effect.provide(testLayer("linux")),
3475 Effect.provide(layerTest("linux")),
34763476 Effect.ensuring(Effect.sync(() => vi.unstubAllEnvs())),
34773477 );
34783478 },
......@@ -3539,7 +3539,7 @@ it.effect(
35393539 });
35403540 assert.isTrue((yield* service.state).shortcutVerified);
35413541 }),
3542 ).pipe(Effect.provide(testLayer("linux", {}, Option.some(settings))));
3542 ).pipe(Effect.provide(layerTest("linux", {}, Option.some(settings))));
35433543 }
35443544 }).pipe(
35453545 Effect.ensuring(
......@@ -3569,7 +3569,7 @@ it.effect("does not register a Wayland modifier-pair shortcut when enabled", ()
35693569 assert.match(state.shortcutMessage ?? "", /Modifier-pair shortcuts aren't available/);
35703570 }),
35713571 ).pipe(
3572 Effect.provide(testLayer("linux")),
3572 Effect.provide(layerTest("linux")),
35733573 Effect.ensuring(Effect.sync(() => vi.unstubAllEnvs())),
35743574 );
35753575});
......@@ -3626,7 +3626,7 @@ it.effect("exposes pending portal permission and then the real assigned shortcut
36263626 assert.equal(failed.shortcutMessage, "Permission denied");
36273627 }),
36283628 ).pipe(
3629 Effect.provide(testLayer("linux")),
3629 Effect.provide(layerTest("linux")),
36303630 Effect.ensuring(Effect.sync(() => vi.unstubAllEnvs())),
36313631 );
36323632});
......@@ -3683,7 +3683,7 @@ it.effect("verifies only native shortcut delivery and captures normally", () =>
36833683 }),
36843684 ).pipe(
36853685 Effect.provide(
3686 testLayer("linux", {
3686 layerTest("linux", {
36873687 makeDirectory: () => Effect.void,
36883688 rename: () => Effect.void,
36893689 writeFile: () => Effect.void,
......@@ -3743,7 +3743,7 @@ it.effect(
37433743 assert.isFalse((yield* service.state).shortcutRegistered);
37443744 }),
37453745 ).pipe(
3746 Effect.provide(testLayer("linux")),
3746 Effect.provide(layerTest("linux")),
37473747 Effect.ensuring(Effect.sync(() => vi.unstubAllEnvs())),
37483748 );
37493749 },
......@@ -3781,7 +3781,7 @@ it.effect(
37813781 assert.notEqual((yield* service.state).shortcutActionRegistered, true);
37823782 }),
37833783 ).pipe(
3784 Effect.provide(testLayer("linux")),
3784 Effect.provide(layerTest("linux")),
37853785 Effect.ensuring(Effect.sync(() => vi.unstubAllEnvs())),
37863786 );
37873787 },
......@@ -3796,7 +3796,7 @@ it.effect("advises about the system menu for a meta pair on Windows", () =>
37963796 assert.match(result.message ?? "", /Super \+ Super is observed/);
37973797 assert.match(result.message ?? "", /system's own menu/);
37983798 }),
3799 ).pipe(Effect.provide(testLayer("win32"))),
3799 ).pipe(Effect.provide(layerTest("win32"))),
38003800);
38013801
38023802it.effect("probes macOS modifier pairs with the flags poller", () => {
......@@ -3812,7 +3812,7 @@ it.effect("probes macOS modifier pairs with the flags poller", () => {
38123812 assert.deepEqual(spawnedPollers[0]?.args.slice(-2), ["2", "4"]);
38133813 assert.strictEqual(spawnedPollers[0]?.kill.mock.calls.length, 1);
38143814 }),
3815 ).pipe(Effect.provide(testLayer("darwin")));
3815 ).pipe(Effect.provide(layerTest("darwin")));
38163816});
38173817
38183818it.effect("registers macOS modifier pairs through the flags poller", () => {
......@@ -3836,7 +3836,7 @@ it.effect("registers macOS modifier pairs through the flags poller", () => {
38363836 assert.deepEqual(spawnedPollers[0]?.args.slice(-2), ["8", "16"]);
38373837 assert.isTrue(state.shortcutRegistered);
38383838 }),
3839 ).pipe(Effect.provide(testLayer("darwin")));
3839 ).pipe(Effect.provide(layerTest("darwin")));
38403840});
38413841
38423842it.effect("waits to apply settings while permissions are pending", () => {
......@@ -3849,7 +3849,7 @@ it.effect("waits to apply settings while permissions are pending", () => {
38493849 finishPermissionRequest = () => resolve([]);
38503850 }),
38513851 );
3852 const layer = testLayer("darwin");
3852 const layer = layerTest("darwin");
38533853
38543854 return Effect.scoped(
38553855 Effect.gen(function* () {
......@@ -3905,7 +3905,7 @@ it.effect.each([false, true])(
39053905 }),
39063906 ).pipe(
39073907 Effect.provide(
3908 testLayer("darwin", {
3908 layerTest("darwin", {
39093909 makeTempDirectoryScoped: () =>
39103910 Effect.acquireRelease(Effect.succeed("/tmp/setup-test"), () =>
39113911 Effect.sync(() => {
......@@ -3926,5 +3926,5 @@ it.effect("rejects macOS test capture on other platforms", () =>
39263926 const error = yield* service.setup("test-mac-capture").pipe(Effect.flip);
39273927 assert.equal(error.reason, "unsupported-session");
39283928 }),
3929 ).pipe(Effect.provide(testLayer("win32"))),
3929 ).pipe(Effect.provide(layerTest("win32"))),
39303930);
apps/desktop/src/ssh/DesktopSshPasswordPrompts.test.ts+8-8
......@@ -86,7 +86,7 @@ function makeTestWindow(
8686 };
8787}
8888
89function makeElectronWindowLayer(window: ReturnType<typeof makeTestWindow>["window"]) {
89function layerElectronWindow(window: ReturnType<typeof makeTestWindow>["window"]) {
9090 return Layer.succeed(
9191 ElectronWindow.ElectronWindow,
9292 ElectronWindow.ElectronWindow.of({
......@@ -105,9 +105,9 @@ function makeElectronWindowLayer(window: ReturnType<typeof makeTestWindow>["wind
105105 );
106106}
107107
108function makeLayer(window: ReturnType<typeof makeTestWindow>["window"]) {
108function layer(window: ReturnType<typeof makeTestWindow>["window"]) {
109109 return DesktopSshPasswordPrompts.layer({ passwordPromptTimeoutMs: 1_000 }).pipe(
110 Layer.provide(makeElectronWindowLayer(window)),
110 Layer.provide(layerElectronWindow(window)),
111111 Layer.provide(NodeServices.layer),
112112 Layer.provideMerge(TestClock.layer()),
113113 );
......@@ -141,7 +141,7 @@ describe("DesktopSshPasswordPrompts", () => {
141141
142142 yield* prompts.resolve({ requestId: request.requestId, password: "secret" });
143143 assert.equal(yield* Fiber.join(fiber), "secret");
144 }).pipe(Effect.provide(makeLayer(testWindow.window)), Effect.scoped);
144 }).pipe(Effect.provide(layer(testWindow.window)), Effect.scoped);
145145 });
146146
147147 it.effect("times out pending renderer prompts with a typed error", () => {
......@@ -163,7 +163,7 @@ describe("DesktopSshPasswordPrompts", () => {
163163 const error = yield* Fiber.join(fiber).pipe(Effect.flip);
164164 assert.instanceOf(error, DesktopSshPasswordPrompts.DesktopSshPromptTimedOutError);
165165 assert.equal(error.destination, "devbox");
166 }).pipe(Effect.provide(makeLayer(testWindow.window)), Effect.scoped);
166 }).pipe(Effect.provide(layer(testWindow.window)), Effect.scoped);
167167 });
168168
169169 it.effect("cleans up a prompt that fails during renderer delivery", () => {
......@@ -194,7 +194,7 @@ describe("DesktopSshPasswordPrompts", () => {
194194 .resolve({ requestId, password: "secret" })
195195 .pipe(Effect.flip);
196196 assert.instanceOf(resolveError, DesktopSshPasswordPrompts.DesktopSshPromptExpiredError);
197 }).pipe(Effect.provide(makeLayer(testWindow.window)), Effect.scoped);
197 }).pipe(Effect.provide(layer(testWindow.window)), Effect.scoped);
198198 });
199199
200200 it.effect("keeps a submitted password when a later presentation step fails", () => {
......@@ -223,7 +223,7 @@ describe("DesktopSshPasswordPrompts", () => {
223223 assert.equal(password, "secret");
224224 assert.equal(testWindow.isFocused(), false);
225225 assert.equal(testWindow.closedListenerCount(), 0);
226 }).pipe(Effect.provide(makeLayer(testWindow.window)), Effect.scoped);
226 }).pipe(Effect.provide(layer(testWindow.window)), Effect.scoped);
227227 });
228228
229229 it.effect("classifies a failed initial window availability check", () => {
......@@ -244,6 +244,6 @@ describe("DesktopSshPasswordPrompts", () => {
244244 assert.equal(error.operation, "check-window-before-request");
245245 assert.equal(error.requestId, null);
246246 assert.deepEqual(testWindow.sentMessages, []);
247 }).pipe(Effect.provide(makeLayer(testWindow.window)), Effect.scoped);
247 }).pipe(Effect.provide(layer(testWindow.window)), Effect.scoped);
248248 });
249249});
apps/desktop/src/telemetry/DesktopTelemetryPublisher.test.ts+13-13
......@@ -24,13 +24,13 @@ import * as ElectronPowerMonitor from "../electron/ElectronPowerMonitor.ts";
2424import * as DesktopTelemetryPublisher from "./DesktopTelemetryPublisher.ts";
2525import * as DesktopRendererHistory from "./DesktopRendererHistory.ts";
2626
27const historyLayer = Layer.succeed(DesktopRendererHistory.DesktopRendererHistory, {
27const layerHistory = Layer.succeed(DesktopRendererHistory.DesktopRendererHistory, {
2828 register: () => Effect.void,
2929 recordMetrics: () => Effect.void,
3030 shutdown: Effect.void,
3131});
3232
33function makeElectronAppLayer(
33function layerElectronApp(
3434 metrics: ReadonlyArray<Electron.ProcessMetric>,
3535 onMetricsRead: () => void = () => undefined,
3636) {
......@@ -65,7 +65,7 @@ describe("DesktopTelemetryPublisher", () => {
6565 Effect.gen(function* () {
6666 const pollStarted = yield* Deferred.make<void>();
6767 const blockPoll = yield* Deferred.make<void>();
68 const powerLayer = Layer.succeed(
68 const layerPower = Layer.succeed(
6969 ElectronPowerMonitor.ElectronPowerMonitor,
7070 ElectronPowerMonitor.ElectronPowerMonitor.of({
7171 isOnBatteryPower: Effect.succeed(false),
......@@ -81,7 +81,7 @@ describe("DesktopTelemetryPublisher", () => {
8181 }),
8282 );
8383 const layer = DesktopTelemetryPublisher.layer.pipe(
84 Layer.provide(Layer.mergeAll(makeElectronAppLayer([]), powerLayer, historyLayer)),
84 Layer.provide(Layer.mergeAll(layerElectronApp([]), layerPower, layerHistory)),
8585 );
8686 const scope = yield* Scope.make();
8787
......@@ -124,7 +124,7 @@ describe("DesktopTelemetryPublisher", () => {
124124 },
125125 } as Electron.ProcessMetric,
126126 ];
127 const powerLayer = Layer.succeed(
127 const layerPower = Layer.succeed(
128128 ElectronPowerMonitor.ElectronPowerMonitor,
129129 ElectronPowerMonitor.ElectronPowerMonitor.of({
130130 isOnBatteryPower: Ref.get(onBattery),
......@@ -149,10 +149,10 @@ describe("DesktopTelemetryPublisher", () => {
149149 const layer = DesktopTelemetryPublisher.layer.pipe(
150150 Layer.provide(
151151 Layer.mergeAll(
152 makeElectronAppLayer(metrics, () => {
152 layerElectronApp(metrics, () => {
153153 metricsReadCount += 1;
154154 }),
155 powerLayer,
155 layerPower,
156156 Layer.succeed(DesktopRendererHistory.DesktopRendererHistory, {
157157 register: () => Effect.void,
158158 shutdown: Effect.void,
......@@ -413,7 +413,7 @@ describe("DesktopTelemetryPublisher", () => {
413413
414414 it.effect("routes requestDesktopUpdate control messages and replays update reports", () =>
415415 Effect.gen(function* () {
416 const powerLayer = Layer.succeed(
416 const layerPower = Layer.succeed(
417417 ElectronPowerMonitor.ElectronPowerMonitor,
418418 ElectronPowerMonitor.ElectronPowerMonitor.of({
419419 isOnBatteryPower: Effect.succeed(false),
......@@ -426,7 +426,7 @@ describe("DesktopTelemetryPublisher", () => {
426426 }),
427427 );
428428 const layer = DesktopTelemetryPublisher.layer.pipe(
429 Layer.provide(Layer.mergeAll(makeElectronAppLayer([]), powerLayer, historyLayer)),
429 Layer.provide(Layer.mergeAll(layerElectronApp([]), layerPower, layerHistory)),
430430 );
431431
432432 yield* Effect.gen(function* () {
......@@ -562,7 +562,7 @@ describe("DesktopRendererHistory", () => {
562562 readonly written: Deferred.Deferred<Record>;
563563 }
564564 | undefined;
565 const fileLayer = Layer.succeed(FileSystem.FileSystem, {
565 const layerFile = Layer.succeed(FileSystem.FileSystem, {
566566 ...fileSystem,
567567 writeFile: (filePath, bytes, options) =>
568568 fileSystem.writeFile(filePath, bytes, options).pipe(
......@@ -590,7 +590,7 @@ describe("DesktopRendererHistory", () => {
590590 logDir: directory,
591591 } as DesktopEnvironment.DesktopEnvironment["Service"]),
592592 ),
593 fileLayer,
593 layerFile,
594594 ),
595595 ),
596596 );
......@@ -735,7 +735,7 @@ describe("DesktopRendererHistory", () => {
735735 );
736736 }
737737 const written = yield* Deferred.make<void>();
738 const fileLayer = Layer.succeed(FileSystem.FileSystem, {
738 const layerFile = Layer.succeed(FileSystem.FileSystem, {
739739 ...fileSystem,
740740 writeFile: (target, bytes, options) =>
741741 fileSystem
......@@ -751,7 +751,7 @@ describe("DesktopRendererHistory", () => {
751751 logDir: directory,
752752 } as DesktopEnvironment.DesktopEnvironment["Service"]),
753753 ),
754 fileLayer,
754 layerFile,
755755 ),
756756 ),
757757 );
apps/desktop/src/updates/updatesTestHarness.ts+12-12
......@@ -67,7 +67,7 @@ export function makeHarness(options: UpdatesHarnessOptions = {}) {
6767 }
6868 };
6969
70 const updaterLayer = Layer.succeed(ElectronUpdater.ElectronUpdater, {
70 const layerUpdater = Layer.succeed(ElectronUpdater.ElectronUpdater, {
7171 setFeedURL: (options) =>
7272 Effect.sync(() => {
7373 feedUrls.push(options);
......@@ -109,7 +109,7 @@ export function makeHarness(options: UpdatesHarnessOptions = {}) {
109109 ).pipe(Effect.asVoid),
110110 } satisfies ElectronUpdater.ElectronUpdater["Service"]);
111111
112 const windowLayer = Layer.succeed(ElectronWindow.ElectronWindow, {
112 const layerWindow = Layer.succeed(ElectronWindow.ElectronWindow, {
113113 create: () => Effect.die("unexpected BrowserWindow creation"),
114114 main: Effect.succeedNone,
115115 currentMainOrFirst: Effect.succeedNone,
......@@ -145,9 +145,9 @@ export function makeHarness(options: UpdatesHarnessOptions = {}) {
145145 }),
146146 waitForReady: () => Effect.succeed(true),
147147 };
148 const backendLayer = DesktopBackendPool.layerTest([stubBackendInstance]);
148 const layerBackend = DesktopBackendPool.layerTest([stubBackendInstance]);
149149
150 const environmentLayer = DesktopEnvironment.layer({
150 const layerEnvironment = DesktopEnvironment.layer({
151151 dirname: "/repo/apps/desktop/src",
152152 homeDirectory: `/tmp/t3-desktop-updates-home-${process.pid}`,
153153 platform: options.platform ?? "darwin",
......@@ -175,7 +175,7 @@ export function makeHarness(options: UpdatesHarnessOptions = {}) {
175175 ...DesktopAppSettings.DEFAULT_DESKTOP_SETTINGS,
176176 };
177177 const setUpdateChannelError = options.setUpdateChannelError;
178 const settingsLayer =
178 const layerSettings =
179179 setUpdateChannelError || options.beforeSetUpdateChannel
180180 ? Layer.succeed(DesktopAppSettings.DesktopAppSettings, {
181181 get: Effect.sync(() => testSettings),
......@@ -211,7 +211,7 @@ export function makeHarness(options: UpdatesHarnessOptions = {}) {
211211 // Tracks the restart markers installs leave, so installs stay free of real
212212 // disk I/O that would outrun the tests' settle loops.
213213 const updateRestartMarkers = new Set<string>();
214 const fileSystemLayer = FileSystem.layerNoop({
214 const layerFileSystem = FileSystem.layerNoop({
215215 readFileString: (path) =>
216216 path === "/missing/resources/package-type" && options.packageType !== undefined
217217 ? Effect.succeed(options.packageType)
......@@ -235,12 +235,12 @@ export function makeHarness(options: UpdatesHarnessOptions = {}) {
235235 });
236236
237237 const layer = DesktopUpdates.layer.pipe(
238 Layer.provide(fileSystemLayer),
239 Layer.provideMerge(updaterLayer),
240 Layer.provideMerge(windowLayer),
241 Layer.provideMerge(backendLayer),
238 Layer.provide(layerFileSystem),
239 Layer.provideMerge(layerUpdater),
240 Layer.provideMerge(layerWindow),
241 Layer.provideMerge(layerBackend),
242242 Layer.provideMerge(DesktopState.layer),
243 Layer.provideMerge(settingsLayer),
243 Layer.provideMerge(layerSettings),
244244 Layer.provideMerge(
245245 DesktopConfig.layerTest({
246246 T3CODE_HOME: `/tmp/t3-desktop-updates-test-${process.pid}`,
......@@ -249,7 +249,7 @@ export function makeHarness(options: UpdatesHarnessOptions = {}) {
249249 ...options.env,
250250 }),
251251 ),
252 Layer.provideMerge(environmentLayer),
252 Layer.provideMerge(layerEnvironment),
253253 Layer.provideMerge(NodeServices.layer),
254254 );
255255
apps/desktop/src/window/DesktopApplicationMenu.test.ts+10-10
......@@ -27,7 +27,7 @@ const environmentInput = {
2727 runningUnderArm64Translation: false,
2828} satisfies DesktopEnvironment.MakeDesktopEnvironmentInput;
2929
30const electronAppLayer = Layer.succeed(ElectronApp.ElectronApp, {
30const layerElectronApp = Layer.succeed(ElectronApp.ElectronApp, {
3131 metadata: Effect.die("unexpected metadata read"),
3232 name: Effect.succeed("T3 Code"),
3333 systemLocale: Effect.succeed("en-US"),
......@@ -49,14 +49,14 @@ const electronAppLayer = Layer.succeed(ElectronApp.ElectronApp, {
4949 on: () => Effect.void,
5050} satisfies ElectronApp.ElectronApp["Service"]);
5151
52const electronDialogLayer = Layer.succeed(ElectronDialog.ElectronDialog, {
52const layerElectronDialog = Layer.succeed(ElectronDialog.ElectronDialog, {
5353 pickFolder: () => Effect.succeedNone,
5454 pickFiles: () => Effect.succeed([]),
5555 showMessageBox: () => Effect.succeed({ response: 0, checkboxChecked: false }),
5656 showErrorBox: () => Effect.void,
5757} satisfies ElectronDialog.ElectronDialog["Service"]);
5858
59const desktopUpdatesLayer = Layer.succeed(DesktopUpdates.DesktopUpdates, {
59const layerDesktopUpdates = Layer.succeed(DesktopUpdates.DesktopUpdates, {
6060 getState: Effect.die("unexpected getState"),
6161 isActionActive: Effect.succeed(false),
6262 isInstallActive: Effect.succeed(false),
......@@ -71,7 +71,7 @@ const desktopUpdatesLayer = Layer.succeed(DesktopUpdates.DesktopUpdates, {
7171 installPrepared: () => Effect.die("unexpected installPrepared"),
7272} satisfies DesktopUpdates.DesktopUpdates["Service"]);
7373
74const makeDesktopWindowLayer = (selectedAction: Deferred.Deferred<string>) =>
74const layerDesktopWindow = (selectedAction: Deferred.Deferred<string>) =>
7575 Layer.succeed(DesktopWindow.DesktopWindow, {
7676 createMain: Effect.die("unexpected createMain"),
7777 ensureMain: Effect.die("unexpected ensureMain"),
......@@ -90,7 +90,7 @@ const makeDesktopWindowLayer = (selectedAction: Deferred.Deferred<string>) =>
9090 syncAppearance: Effect.void,
9191 } satisfies DesktopWindow.DesktopWindow["Service"]);
9292
93const makeElectronMenuLayer = (
93const layerElectronMenu = (
9494 applicationMenuTemplate: Deferred.Deferred<readonly Electron.MenuItemConstructorOptions[]>,
9595) =>
9696 Layer.succeed(ElectronMenu.ElectronMenu, {
......@@ -110,11 +110,11 @@ const configureMenu = (
110110 }).pipe(
111111 Effect.provide(
112112 DesktopApplicationMenu.layer.pipe(
113 Layer.provideMerge(makeElectronMenuLayer(applicationMenuTemplate)),
114 Layer.provideMerge(makeDesktopWindowLayer(selectedAction)),
115 Layer.provideMerge(desktopUpdatesLayer),
116 Layer.provideMerge(electronDialogLayer),
117 Layer.provideMerge(electronAppLayer),
113 Layer.provideMerge(layerElectronMenu(applicationMenuTemplate)),
114 Layer.provideMerge(layerDesktopWindow(selectedAction)),
115 Layer.provideMerge(layerDesktopUpdates),
116 Layer.provideMerge(layerElectronDialog),
117 Layer.provideMerge(layerElectronApp),
118118 Layer.provideMerge(
119119 DesktopEnvironment.layer(environmentInput).pipe(
120120 Layer.provide(Layer.mergeAll(NodeServices.layer, DesktopConfig.layerTest({}))),
apps/desktop/src/window/DesktopWindow.test.ts+51-51
......@@ -151,15 +151,15 @@ function makeFakeBrowserWindow() {
151151 };
152152}
153153
154const desktopClientSettingsLayer = Layer.mock(DesktopClientSettings.DesktopClientSettings)({
154const layerDesktopClientSettings = Layer.mock(DesktopClientSettings.DesktopClientSettings)({
155155 get: Effect.succeedNone,
156156});
157157
158const electronAppLayer = Layer.mock(ElectronApp.ElectronApp)({
158const layerElectronApp = Layer.mock(ElectronApp.ElectronApp)({
159159 quit: Effect.void,
160160});
161161
162const desktopAssetsLayer = Layer.succeed(DesktopAssets.DesktopAssets, {
162const layerDesktopAssets = Layer.succeed(DesktopAssets.DesktopAssets, {
163163 iconPaths: Effect.succeed({
164164 ico: Option.none<string>(),
165165 icns: Option.none<string>(),
......@@ -168,7 +168,7 @@ const desktopAssetsLayer = Layer.succeed(DesktopAssets.DesktopAssets, {
168168 resolveResourcePath: () => Effect.succeed(Option.none<string>()),
169169} satisfies DesktopAssets.DesktopAssets["Service"]);
170170
171const desktopServerExposureLayer = Layer.succeed(DesktopServerExposure.DesktopServerExposure, {
171const layerDesktopServerExposure = Layer.succeed(DesktopServerExposure.DesktopServerExposure, {
172172 getState: Effect.die("unexpected getState"),
173173 backendConfig: Effect.succeed({
174174 port: 3773,
......@@ -183,19 +183,19 @@ const desktopServerExposureLayer = Layer.succeed(DesktopServerExposure.DesktopSe
183183 getAdvertisedEndpoints: Effect.die("unexpected getAdvertisedEndpoints"),
184184} satisfies DesktopServerExposure.DesktopServerExposure["Service"]);
185185
186const electronMenuLayer = Layer.succeed(ElectronMenu.ElectronMenu, {
186const layerElectronMenu = Layer.succeed(ElectronMenu.ElectronMenu, {
187187 setApplicationMenu: () => Effect.void,
188188 popupTemplate: () => Effect.void,
189189 showContextMenu: () => Effect.succeedNone,
190190} satisfies ElectronMenu.ElectronMenu["Service"]);
191191
192const electronThemeLayer = Layer.succeed(ElectronTheme.ElectronTheme, {
192const layerElectronTheme = Layer.succeed(ElectronTheme.ElectronTheme, {
193193 shouldUseDarkColors: Effect.succeed(false),
194194 setSource: () => Effect.void,
195195 onUpdated: () => Effect.void,
196196} satisfies ElectronTheme.ElectronTheme["Service"]);
197197
198const desktopEnvironmentLayer = DesktopEnvironment.layer(environmentInput).pipe(
198const layerDesktopEnvironment = DesktopEnvironment.layer(environmentInput).pipe(
199199 Layer.provide(
200200 Layer.mergeAll(
201201 NodeServices.layer,
......@@ -211,7 +211,7 @@ const desktopWindowBoundsEquivalence = Schema.toEquivalence(
211211 DesktopAppSettings.DesktopWindowBoundsSchema,
212212);
213213
214function makeTestLayer(input: {
214function layerTest(input: {
215215 readonly window: Electron.BrowserWindow;
216216 readonly createCount: Ref.Ref<number>;
217217 readonly mainWindow: Ref.Ref<Option.Option<Electron.BrowserWindow>>;
......@@ -229,7 +229,7 @@ function makeTestLayer(input: {
229229 readonly onReveal?: (window: Electron.BrowserWindow) => void;
230230}) {
231231 let desktopSettings = input.desktopSettings ?? DesktopAppSettings.DEFAULT_DESKTOP_SETTINGS;
232 const desktopAppSettingsLayer = Layer.succeed(DesktopAppSettings.DesktopAppSettings, {
232 const layerDesktopAppSettings = Layer.succeed(DesktopAppSettings.DesktopAppSettings, {
233233 get: Effect.sync(() => desktopSettings),
234234 load: Effect.sync(() => desktopSettings),
235235 setMainWindowBounds: (bounds, isMaximized) =>
......@@ -263,7 +263,7 @@ function makeTestLayer(input: {
263263 applyWslWindowsFallbackInMemory: Effect.die("unexpected WSL Windows fallback"),
264264 } satisfies DesktopAppSettings.DesktopAppSettings["Service"]);
265265
266 const electronWindowLayer = Layer.succeed(ElectronWindow.ElectronWindow, {
266 const layerElectronWindow = Layer.succeed(ElectronWindow.ElectronWindow, {
267267 create: (options) =>
268268 Effect.sync(() => {
269269 input.createdWindowOptions?.push(options);
......@@ -286,18 +286,18 @@ function makeTestLayer(input: {
286286 return DesktopWindow.layer.pipe(
287287 Layer.provide(
288288 Layer.mergeAll(
289 desktopAssetsLayer,
289 layerDesktopAssets,
290290 Layer.succeed(DesktopRendererHistory.DesktopRendererHistory, {
291291 register: () => Effect.void,
292292 recordMetrics: () => Effect.void,
293293 shutdown: Effect.void,
294294 }),
295 desktopEnvironmentLayer,
296 desktopAppSettingsLayer,
297 desktopClientSettingsLayer,
298 desktopServerExposureLayer,
295 layerDesktopEnvironment,
296 layerDesktopAppSettings,
297 layerDesktopClientSettings,
298 layerDesktopServerExposure,
299299 DesktopState.layer,
300 electronAppLayer,
300 layerElectronApp,
301301 Layer.succeed(ElectronMenu.ElectronMenu, {
302302 setApplicationMenu: () => Effect.void,
303303 showContextMenu: () => Effect.succeedNone,
......@@ -315,8 +315,8 @@ function makeTestLayer(input: {
315315 input.copiedTexts?.push(text);
316316 }),
317317 } satisfies ElectronShell.ElectronShell["Service"]),
318 electronThemeLayer,
319 electronWindowLayer,
318 layerElectronTheme,
319 layerElectronWindow,
320320 Layer.mock(PreviewManager.PreviewManager)({
321321 getBrowserSession: () => Effect.succeed({} as Electron.Session),
322322 setMainWindow: () => Effect.void,
......@@ -406,24 +406,24 @@ const makeSplashScenario = (createOutcomes: readonly (Electron.BrowserWindow | n
406406 const layer = DesktopWindow.layer.pipe(
407407 Layer.provide(
408408 Layer.mergeAll(
409 desktopAssetsLayer,
409 layerDesktopAssets,
410410 Layer.succeed(DesktopRendererHistory.DesktopRendererHistory, {
411411 register: () => Effect.void,
412412 recordMetrics: () => Effect.void,
413413 shutdown: Effect.void,
414414 }),
415 desktopEnvironmentLayer,
415 layerDesktopEnvironment,
416416 DesktopAppSettings.layerTest(),
417 desktopClientSettingsLayer,
418 desktopServerExposureLayer,
419 electronAppLayer,
420 electronMenuLayer,
417 layerDesktopClientSettings,
418 layerDesktopServerExposure,
419 layerElectronApp,
420 layerElectronMenu,
421421 Layer.succeed(ElectronShell.ElectronShell, {
422422 openExternal: () => Effect.succeed(true),
423423 openSystemSettings: () => Effect.succeed(true),
424424 copyText: () => Effect.void,
425425 } satisfies ElectronShell.ElectronShell["Service"]),
426 electronThemeLayer,
426 layerElectronTheme,
427427 Layer.succeed(ElectronWindow.ElectronWindow, electronWindowShape),
428428 Layer.mock(PreviewManager.PreviewManager)({
429429 getBrowserSession: () => Effect.succeed({} as Electron.Session),
......@@ -466,7 +466,7 @@ describe("DesktopWindow", () => {
466466 focusedContents: unknown;
467467 }>();
468468 const copiedTexts: string[] = [];
469 const layer = makeTestLayer({
469 const layer = layerTest({
470470 window: host.window,
471471 createCount: yield* Ref.make(0),
472472 mainWindow: yield* Ref.make<Option.Option<Electron.BrowserWindow>>(Option.none()),
......@@ -619,7 +619,7 @@ describe("DesktopWindow", () => {
619619 const createCount = yield* Ref.make(0);
620620 const mainWindow = yield* Ref.make<Option.Option<Electron.BrowserWindow>>(Option.none());
621621 const createdWindowOptions: Electron.BrowserWindowConstructorOptions[] = [];
622 const layer = makeTestLayer({
622 const layer = layerTest({
623623 window: fakeWindow.window,
624624 createCount,
625625 mainWindow,
......@@ -653,7 +653,7 @@ describe("DesktopWindow", () => {
653653 const fakeWindow = makeFakeBrowserWindow();
654654 const createCount = yield* Ref.make(0);
655655 const mainWindow = yield* Ref.make<Option.Option<Electron.BrowserWindow>>(Option.none());
656 const layer = makeTestLayer({
656 const layer = layerTest({
657657 window: fakeWindow.window,
658658 createCount,
659659 mainWindow,
......@@ -682,7 +682,7 @@ describe("DesktopWindow", () => {
682682 const fakeWindow = makeFakeBrowserWindow();
683683 const createCount = yield* Ref.make(0);
684684 const mainWindow = yield* Ref.make<Option.Option<Electron.BrowserWindow>>(Option.none());
685 const layer = makeTestLayer({
685 const layer = layerTest({
686686 window: fakeWindow.window,
687687 createCount,
688688 mainWindow,
......@@ -728,7 +728,7 @@ describe("DesktopWindow", () => {
728728 const send = vi.spyOn(fakeWindow.window.webContents, "send");
729729 const createCount = yield* Ref.make(0);
730730 const mainWindow = yield* Ref.make<Option.Option<Electron.BrowserWindow>>(Option.none());
731 const layer = makeTestLayer({ window: fakeWindow.window, createCount, mainWindow });
731 const layer = layerTest({ window: fakeWindow.window, createCount, mainWindow });
732732
733733 yield* Effect.gen(function* () {
734734 const desktopWindow = yield* DesktopWindow.DesktopWindow;
......@@ -755,7 +755,7 @@ describe("DesktopWindow", () => {
755755 const createCount = yield* Ref.make(0);
756756 const mainWindow = yield* Ref.make<Option.Option<Electron.BrowserWindow>>(Option.none());
757757 const previewZoomReapplies: number[] = [];
758 const layer = makeTestLayer({
758 const layer = layerTest({
759759 window: fakeWindow.window,
760760 createCount,
761761 mainWindow,
......@@ -787,7 +787,7 @@ describe("DesktopWindow", () => {
787787 const fakeWindow = makeFakeBrowserWindow();
788788 const createCount = yield* Ref.make(0);
789789 const mainWindow = yield* Ref.make<Option.Option<Electron.BrowserWindow>>(Option.none());
790 const layer = makeTestLayer({ window: fakeWindow.window, createCount, mainWindow });
790 const layer = layerTest({ window: fakeWindow.window, createCount, mainWindow });
791791
792792 yield* Effect.gen(function* () {
793793 const desktopWindow = yield* DesktopWindow.DesktopWindow;
......@@ -821,7 +821,7 @@ describe("DesktopWindow", () => {
821821 const createCount = yield* Ref.make(0);
822822 const mainWindow = yield* Ref.make<Option.Option<Electron.BrowserWindow>>(Option.none());
823823 const createdWindowOptions: Electron.BrowserWindowConstructorOptions[] = [];
824 const layer = makeTestLayer({
824 const layer = layerTest({
825825 window: fakeWindow.window,
826826 createCount,
827827 mainWindow,
......@@ -849,7 +849,7 @@ describe("DesktopWindow", () => {
849849 const fakeWindow = makeFakeBrowserWindow();
850850 const createCount = yield* Ref.make(0);
851851 const mainWindow = yield* Ref.make<Option.Option<Electron.BrowserWindow>>(Option.none());
852 const layer = makeTestLayer({
852 const layer = layerTest({
853853 window: fakeWindow.window,
854854 createCount,
855855 mainWindow,
......@@ -883,7 +883,7 @@ describe("DesktopWindow", () => {
883883 const fakeWindow = makeFakeBrowserWindow();
884884 const createCount = yield* Ref.make(0);
885885 const mainWindow = yield* Ref.make<Option.Option<Electron.BrowserWindow>>(Option.none());
886 const layer = makeTestLayer({
886 const layer = layerTest({
887887 window: fakeWindow.window,
888888 createCount,
889889 mainWindow,
......@@ -910,7 +910,7 @@ describe("DesktopWindow", () => {
910910 const createCount = yield* Ref.make(0);
911911 const mainWindow = yield* Ref.make<Option.Option<Electron.BrowserWindow>>(Option.none());
912912 const mainWindowBoundsUpdates: DesktopAppSettings.DesktopWindowBounds[] = [];
913 const layer = makeTestLayer({
913 const layer = layerTest({
914914 window: fakeWindow.window,
915915 createCount,
916916 mainWindow,
......@@ -953,7 +953,7 @@ describe("DesktopWindow", () => {
953953 const mainWindow = yield* Ref.make<Option.Option<Electron.BrowserWindow>>(Option.none());
954954 const mainWindowBoundsUpdates: DesktopAppSettings.DesktopWindowBounds[] = [];
955955 const mainWindowMaximizedUpdates: boolean[] = [];
956 const layer = makeTestLayer({
956 const layer = layerTest({
957957 window: fakeWindow.window,
958958 createCount,
959959 mainWindow,
......@@ -987,7 +987,7 @@ describe("DesktopWindow", () => {
987987 const mainWindow = yield* Ref.make<Option.Option<Electron.BrowserWindow>>(Option.none());
988988 const mainWindowBoundsUpdates: DesktopAppSettings.DesktopWindowBounds[] = [];
989989 const mainWindowMaximizedUpdates: boolean[] = [];
990 const layer = makeTestLayer({
990 const layer = layerTest({
991991 window: fakeWindow.window,
992992 createCount,
993993 mainWindow,
......@@ -1024,7 +1024,7 @@ describe("DesktopWindow", () => {
10241024 const createCount = yield* Ref.make(0);
10251025 const mainWindow = yield* Ref.make<Option.Option<Electron.BrowserWindow>>(Option.none());
10261026 const mainWindowBoundsUpdates: DesktopAppSettings.DesktopWindowBounds[] = [];
1027 const layer = makeTestLayer({
1027 const layer = layerTest({
10281028 window: fakeWindow.window,
10291029 createCount,
10301030 mainWindow,
......@@ -1054,7 +1054,7 @@ describe("DesktopWindow", () => {
10541054 const createCount = yield* Ref.make(0);
10551055 const mainWindow = yield* Ref.make<Option.Option<Electron.BrowserWindow>>(Option.none());
10561056 const mainWindowBoundsUpdates: DesktopAppSettings.DesktopWindowBounds[] = [];
1057 const layer = makeTestLayer({
1057 const layer = layerTest({
10581058 window: fakeWindow.window,
10591059 createCount,
10601060 mainWindow,
......@@ -1096,7 +1096,7 @@ describe("DesktopWindow", () => {
10961096 const createCount = yield* Ref.make(0);
10971097 const mainWindow = yield* Ref.make<Option.Option<Electron.BrowserWindow>>(Option.none());
10981098 const mainWindowBoundsUpdates: DesktopAppSettings.DesktopWindowBounds[] = [];
1099 const layer = makeTestLayer({
1099 const layer = layerTest({
11001100 window: fakeWindow.window,
11011101 createCount,
11021102 mainWindow,
......@@ -1132,7 +1132,7 @@ describe("DesktopWindow", () => {
11321132 const createCount = yield* Ref.make(0);
11331133 const mainWindow = yield* Ref.make<Option.Option<Electron.BrowserWindow>>(Option.none());
11341134 const mainWindowBoundsUpdates: DesktopAppSettings.DesktopWindowBounds[] = [];
1135 const layer = makeTestLayer({
1135 const layer = layerTest({
11361136 window: fakeWindow.window,
11371137 createCount,
11381138 mainWindow,
......@@ -1180,7 +1180,7 @@ describe("DesktopWindow", () => {
11801180 const createCount = yield* Ref.make(0);
11811181 const mainWindow = yield* Ref.make<Option.Option<Electron.BrowserWindow>>(Option.none());
11821182 const createdWindowOptions: Electron.BrowserWindowConstructorOptions[] = [];
1183 const layer = makeTestLayer({
1183 const layer = layerTest({
11841184 window: fakeWindow.window,
11851185 createCount,
11861186 mainWindow,
......@@ -1218,7 +1218,7 @@ describe("DesktopWindow", () => {
12181218 const writeStarted = yield* Deferred.make<void>();
12191219 const allowWrite = yield* Deferred.make<void>();
12201220 const flushCompleted = yield* Deferred.make<void>();
1221 const layer = makeTestLayer({
1221 const layer = layerTest({
12221222 window: fakeWindow.window,
12231223 createCount,
12241224 mainWindow,
......@@ -1270,7 +1270,7 @@ describe("DesktopWindow", () => {
12701270 const fakeWindow = makeFakeBrowserWindow();
12711271 const createCount = yield* Ref.make(0);
12721272 const mainWindow = yield* Ref.make<Option.Option<Electron.BrowserWindow>>(Option.none());
1273 const layer = makeTestLayer({
1273 const layer = layerTest({
12741274 window: fakeWindow.window,
12751275 createCount,
12761276 mainWindow,
......@@ -1301,7 +1301,7 @@ describe("DesktopWindow", () => {
13011301 const fakeWindow = makeFakeBrowserWindow();
13021302 const createCount = yield* Ref.make(0);
13031303 const mainWindow = yield* Ref.make<Option.Option<Electron.BrowserWindow>>(Option.none());
1304 const layer = makeTestLayer({
1304 const layer = layerTest({
13051305 window: fakeWindow.window,
13061306 createCount,
13071307 mainWindow,
......@@ -1369,7 +1369,7 @@ describe("DesktopWindow", () => {
13691369 const createCount = yield* Ref.make(0);
13701370 const mainWindow = yield* Ref.make<Option.Option<Electron.BrowserWindow>>(Option.none());
13711371 const openedExternalUrls: unknown[] = [];
1372 const layer = makeTestLayer({
1372 const layer = layerTest({
13731373 window: fakeWindow.window,
13741374 createCount,
13751375 mainWindow,
......@@ -1515,7 +1515,7 @@ describe("DesktopWindow", () => {
15151515 });
15161516 const createCount = yield* Ref.make(0);
15171517 const mainWindow = yield* Ref.make<Option.Option<Electron.BrowserWindow>>(Option.none());
1518 const layer = makeTestLayer({
1518 const layer = layerTest({
15191519 window: fakeWindow.window,
15201520 createCount,
15211521 mainWindow,
......@@ -1550,7 +1550,7 @@ describe("DesktopWindow", () => {
15501550 const fakeWindow = makeFakeBrowserWindow();
15511551 const createCount = yield* Ref.make(0);
15521552 const mainWindow = yield* Ref.make<Option.Option<Electron.BrowserWindow>>(Option.none());
1553 const layer = makeTestLayer({
1553 const layer = layerTest({
15541554 window: fakeWindow.window,
15551555 createCount,
15561556 mainWindow,
......@@ -1596,7 +1596,7 @@ describe("DesktopWindow", () => {
15961596 const createCount = yield* Ref.make(0);
15971597 const mainWindow = yield* Ref.make<Option.Option<Electron.BrowserWindow>>(Option.none());
15981598 const onReveal = vi.fn();
1599 const layer = makeTestLayer({ window: fakeWindow.window, createCount, mainWindow, onReveal });
1599 const layer = layerTest({ window: fakeWindow.window, createCount, mainWindow, onReveal });
16001600
16011601 yield* Effect.gen(function* () {
16021602 const desktopWindow = yield* DesktopWindow.DesktopWindow;
......@@ -1620,7 +1620,7 @@ describe("DesktopWindow", () => {
16201620 Option.some(fakeWindow.window),
16211621 );
16221622 const onReveal = vi.fn();
1623 const layer = makeTestLayer({ window: fakeWindow.window, createCount, mainWindow, onReveal });
1623 const layer = layerTest({ window: fakeWindow.window, createCount, mainWindow, onReveal });
16241624 vi.mocked(fakeWindow.window.webContents.isLoadingMainFrame).mockReturnValue(true);
16251625
16261626 yield* Effect.gen(function* () {
apps/desktop/src/wsl/DesktopWslBackend.test.ts+14-14
......@@ -50,7 +50,7 @@ const primarySnapshot: DesktopBackendSnapshot = {
5050 restartScheduled: false,
5151};
5252
53const serverExposureLayer = Layer.succeed(DesktopServerExposure.DesktopServerExposure, {
53const layerServerExposure = Layer.succeed(DesktopServerExposure.DesktopServerExposure, {
5454 getState: Effect.die("unexpected getState"),
5555 backendConfig: Effect.succeed({
5656 port: 3773,
......@@ -65,7 +65,7 @@ const serverExposureLayer = Layer.succeed(DesktopServerExposure.DesktopServerExp
6565 getAdvertisedEndpoints: Effect.succeed([]),
6666} satisfies DesktopServerExposure.DesktopServerExposure["Service"]);
6767
68const backendConfigurationLayer = Layer.succeed(
68const layerBackendConfiguration = Layer.succeed(
6969 DesktopBackendConfiguration.DesktopBackendConfiguration,
7070 {
7171 resolvePrimary: Effect.die("unexpected resolvePrimary"),
......@@ -74,7 +74,7 @@ const backendConfigurationLayer = Layer.succeed(
7474 } satisfies DesktopBackendConfiguration.DesktopBackendConfiguration["Service"],
7575);
7676
77const netLayer = Layer.succeed(NetService.NetService, {
77const layerNet = Layer.succeed(NetService.NetService, {
7878 canListenOnHost: () => Effect.succeed(true),
7979 isPortAvailableOnLoopback: () => Effect.succeed(true),
8080 hasListenerOnHost: () => Effect.succeed(false),
......@@ -91,9 +91,9 @@ describe("DesktopWslBackend", () => {
9191 Effect.provide(
9292 DesktopWslBackend.layer.pipe(
9393 Layer.provide(Layer.mock(DesktopBackendPool.DesktopBackendPool, {})),
94 Layer.provide(backendConfigurationLayer),
95 Layer.provide(serverExposureLayer),
96 Layer.provide(netLayer),
94 Layer.provide(layerBackendConfiguration),
95 Layer.provide(layerServerExposure),
96 Layer.provide(layerNet),
9797 Layer.provide(Layer.mock(DesktopWslEnvironment.DesktopWslEnvironment, {})),
9898 Layer.provide(
9999 DesktopAppSettings.layerTest({
......@@ -118,7 +118,7 @@ describe("DesktopWslBackend", () => {
118118 label: "WSL (Ubuntu)",
119119 snapshot: primarySnapshot,
120120 });
121 const poolLayer = Layer.succeed(DesktopBackendPool.DesktopBackendPool, {
121 const layerPool = Layer.succeed(DesktopBackendPool.DesktopBackendPool, {
122122 get: (id) =>
123123 Effect.succeed(
124124 id === DesktopBackendPool.PRIMARY_INSTANCE_ID
......@@ -160,10 +160,10 @@ describe("DesktopWslBackend", () => {
160160 }).pipe(
161161 Effect.provide(
162162 DesktopWslBackend.layer.pipe(
163 Layer.provideMerge(poolLayer),
164 Layer.provideMerge(backendConfigurationLayer),
165 Layer.provideMerge(serverExposureLayer),
166 Layer.provideMerge(netLayer),
163 Layer.provideMerge(layerPool),
164 Layer.provideMerge(layerBackendConfiguration),
165 Layer.provideMerge(layerServerExposure),
166 Layer.provideMerge(layerNet),
167167 Layer.provideMerge(
168168 DesktopAppSettings.layerTest({
169169 ...DesktopAppSettings.DEFAULT_DESKTOP_SETTINGS,
......@@ -204,9 +204,9 @@ describe("DesktopWslBackend", () => {
204204 Effect.provide(
205205 DesktopWslBackend.layer.pipe(
206206 Layer.provideMerge(DesktopBackendPool.layerTest([primary, wsl])),
207 Layer.provideMerge(backendConfigurationLayer),
208 Layer.provideMerge(serverExposureLayer),
209 Layer.provideMerge(netLayer),
207 Layer.provideMerge(layerBackendConfiguration),
208 Layer.provideMerge(layerServerExposure),
209 Layer.provideMerge(layerNet),
210210 Layer.provideMerge(
211211 DesktopAppSettings.layerTest({
212212 ...DesktopAppSettings.DEFAULT_DESKTOP_SETTINGS,
apps/desktop/src/wsl/DesktopWslServerTree.test.ts+7-7
......@@ -16,7 +16,7 @@ import * as DesktopWslServerTree from "./DesktopWslServerTree.ts";
1616// Electron) fs, so a plain directory named server.asar exercises the full
1717// extraction path under plain Node.
1818
19const environmentLayer = (input: {
19const layerEnvironment = (input: {
2020 readonly baseDir: string;
2121 readonly resourcesPath: string;
2222 readonly appVersion?: string;
......@@ -69,7 +69,7 @@ const ensureWith = (input: {
6969 const tree = yield* DesktopWslServerTree.DesktopWslServerTree;
7070 return yield* tree.ensure;
7171 }).pipe(
72 Effect.provide(DesktopWslServerTree.layer.pipe(Layer.provideMerge(environmentLayer(input)))),
72 Effect.provide(DesktopWslServerTree.layer.pipe(Layer.provideMerge(layerEnvironment(input)))),
7373 );
7474
7575describe("DesktopWslServerTree", () => {
......@@ -182,7 +182,7 @@ describe("DesktopWslServerTree", () => {
182182 }).pipe(
183183 Effect.provide(
184184 DesktopWslServerTree.layer.pipe(
185 Layer.provideMerge(environmentLayer({ baseDir: tempDir, resourcesPath })),
185 Layer.provideMerge(layerEnvironment({ baseDir: tempDir, resourcesPath })),
186186 ),
187187 ),
188188 );
......@@ -313,7 +313,7 @@ describe("DesktopWslServerTree", () => {
313313 }).pipe(
314314 Effect.provide(
315315 DesktopWslServerTree.layer.pipe(
316 Layer.provideMerge(environmentLayer({ baseDir: tempDir, resourcesPath })),
316 Layer.provideMerge(layerEnvironment({ baseDir: tempDir, resourcesPath })),
317317 ),
318318 ),
319319 );
......@@ -340,7 +340,7 @@ describe("DesktopWslServerTree", () => {
340340 const treeRoot = path.dirname(versionDir);
341341 const extractedEntryPath = path.join(versionDir, "apps/server/dist/bin.mjs");
342342 let cleanupFailed = false;
343 const partialCleanupFileSystem = Layer.effect(
343 const layerPartialCleanupFileSystem = Layer.effect(
344344 FileSystem.FileSystem,
345345 Effect.gen(function* () {
346346 const realFileSystem = yield* FileSystem.FileSystem;
......@@ -371,8 +371,8 @@ describe("DesktopWslServerTree", () => {
371371 }).pipe(
372372 Effect.provide(
373373 DesktopWslServerTree.layer.pipe(
374 Layer.provideMerge(environmentLayer({ baseDir: tempDir, resourcesPath })),
375 Layer.provideMerge(partialCleanupFileSystem),
374 Layer.provideMerge(layerEnvironment({ baseDir: tempDir, resourcesPath })),
375 Layer.provideMerge(layerPartialCleanupFileSystem),
376376 ),
377377 ),
378378 );
apps/mobile/src/connection/background-activity.ts+2-2
......@@ -36,14 +36,14 @@ function normalizeAppState(
3636 return "unknown";
3737}
3838
39export const mobileBackgroundActivityObserverLayer = Layer.succeed(
39export const layerObserver = Layer.succeed(
4040 EnvironmentRpcSubscriptionObserver,
4141 EnvironmentRpcSubscriptionObserver.of({
4242 observe: observeMobileBackgroundActivitySubscription,
4343 }),
4444);
4545
46export const mobileBackgroundActivityReporterLayer = Layer.effectDiscard(
46export const layerReporter = Layer.effectDiscard(
4747 Effect.gen(function* () {
4848 const registry = yield* EnvironmentRegistry.EnvironmentRegistry;
4949 const storage = yield* MobileStorage.MobileStorage;
apps/mobile/src/connection/platform.ts+23-27
......@@ -29,7 +29,7 @@ import { clearThreadOutboxEnvironment } from "../state/thread-outbox-removal";
2929import { clearComposerDraftsEnvironment } from "../state/use-composer-drafts";
3030import { clearThreadComposerErrorsForEnvironment } from "../state/thread-composer-error";
3131import { mobileApplicationActiveWakeup } from "./app-state-wakeups";
32import { connectionStorageLayer } from "./storage";
32import * as ConnectionStorage from "./storage";
3333
3434function networkStatus(state: Network.NetworkState): "unknown" | "offline" | "online" {
3535 if (state.isConnected === false) {
......@@ -41,7 +41,7 @@ function networkStatus(state: Network.NetworkState): "unknown" | "offline" | "on
4141 return "unknown";
4242}
4343
44const connectivityLayer = Connectivity.layer({
44const layerConnectivity = Connectivity.layer({
4545 status: Effect.tryPromise({
4646 try: () => Network.getNetworkStateAsync(),
4747 catch: () => undefined,
......@@ -122,7 +122,7 @@ const networkPathChanges = Stream.callback<"network-changed">((queue) =>
122122 ).pipe(Effect.asVoid),
123123);
124124
125const wakeupsLayer = Wakeups.layer({
125const layerWakeups = Wakeups.layer({
126126 changes: Stream.mergeAll(
127127 [
128128 Stream.callback<"application-active-probe" | "application-active-reconnect">((queue) =>
......@@ -155,7 +155,7 @@ const wakeupsLayer = Wakeups.layer({
155155 ),
156156});
157157
158const capabilitiesLayer = Layer.effectContext(
158const layerCapabilities = Layer.effectContext(
159159 Effect.gen(function* () {
160160 const storage = yield* MobileStorage.MobileStorage;
161161 return Context.make(
......@@ -243,21 +243,17 @@ const capabilitiesLayer = Layer.effectContext(
243243 }),
244244);
245245
246const platformConnectionSourceLayer = Layer.succeed(
246const layerPlatformConnectionSource = Layer.succeed(
247247 PlatformConnectionSource.PlatformConnectionSource,
248248 PlatformConnectionSource.PlatformConnectionSource.of({
249249 registrations: Stream.empty,
250250 }),
251251);
252252
253const providedConnectionStorageLayer = connectionStorageLayer.pipe(
254 Layer.provide(Runtime.runtimeContextLayer),
255);
256const providedCapabilitiesLayer = capabilitiesLayer.pipe(
257 Layer.provide(Runtime.runtimeContextLayer),
258);
253const layerProvidedConnectionStorage = ConnectionStorage.layer.pipe(Layer.provide(Runtime.layer));
254const layerProvidedCapabilities = layerCapabilities.pipe(Layer.provide(Runtime.layer));
259255
260const environmentOwnedDataCleanupLayer = Layer.succeed(
256const layerEnvironmentOwnedDataCleanup = Layer.succeed(
261257 Persistence.EnvironmentOwnedDataCleanup,
262258 Persistence.EnvironmentOwnedDataCleanup.of({
263259 clear: (environmentId) =>
......@@ -280,24 +276,24 @@ const environmentOwnedDataCleanupLayer = Layer.succeed(
280276);
281277
282278type ConnectionPlatformLayerSource =
283 | typeof providedConnectionStorageLayer
284 | typeof Runtime.runtimeContextLayer
285 | typeof connectivityLayer
286 | typeof wakeupsLayer
287 | typeof providedCapabilitiesLayer
288 | typeof platformConnectionSourceLayer
289 | typeof environmentOwnedDataCleanupLayer;
279 | typeof layerProvidedConnectionStorage
280 | typeof Runtime.layer
281 | typeof layerConnectivity
282 | typeof layerWakeups
283 | typeof layerProvidedCapabilities
284 | typeof layerPlatformConnectionSource
285 | typeof layerEnvironmentOwnedDataCleanup;
290286
291export const connectionPlatformLayer: Layer.Layer<
287export const layer: Layer.Layer<
292288 Layer.Success<ConnectionPlatformLayerSource>,
293289 Layer.Error<ConnectionPlatformLayerSource>,
294290 Layer.Services<ConnectionPlatformLayerSource>
295291> = Layer.mergeAll(
296 providedConnectionStorageLayer,
297 Runtime.runtimeContextLayer,
298 connectivityLayer,
299 wakeupsLayer,
300 providedCapabilitiesLayer,
301 platformConnectionSourceLayer,
302 environmentOwnedDataCleanupLayer,
292 layerProvidedConnectionStorage,
293 Runtime.layer,
294 layerConnectivity,
295 layerWakeups,
296 layerProvidedCapabilities,
297 layerPlatformConnectionSource,
298 layerEnvironmentOwnedDataCleanup,
303299);
apps/mobile/src/connection/runtime.ts+19-24
......@@ -1,7 +1,7 @@
11import { Connection } from "@t3tools/client-runtime/connection";
22import { ShellSnapshotLoader } from "@t3tools/client-runtime/state/shell";
33import {
4 boundedThreadSnapshotLoaderLayer,
4 BoundedThreadSnapshotLoader,
55 ThreadHistoryController,
66} from "@t3tools/client-runtime/state/threads";
77import * as Layer from "effect/Layer";
......@@ -9,49 +9,44 @@ import { Atom } from "effect/reactivity";
99
1010import type { FoundationHotModule } from "../lib/foundation-fast-refresh";
1111import { hotSwappableAtomRuntime } from "../lib/hot-swappable-atom-runtime";
12import { runtimeContextLayer } from "../lib/runtime";
12import * as Runtime from "../lib/runtime";
1313import { appAtomRegistry } from "../state/atom-registry";
14import {
15 mobileBackgroundActivityObserverLayer,
16 mobileBackgroundActivityReporterLayer,
17} from "./background-activity";
18import { connectionPlatformLayer } from "./platform";
14import * as BackgroundActivity from "./background-activity";
15import * as ConnectionPlatform from "./platform";
1916
2017declare const module: { readonly hot?: FoundationHotModule } | undefined;
2118
22const providedConnectionPlatformLayer = connectionPlatformLayer.pipe(
23 Layer.provide(runtimeContextLayer),
24);
19const layerProvidedConnectionPlatform = ConnectionPlatform.layer.pipe(Layer.provide(Runtime.layer));
2520
26const snapshotLoaderLayer = Layer.mergeAll(
27 boundedThreadSnapshotLoaderLayer,
21const layerSnapshotLoader = Layer.mergeAll(
22 BoundedThreadSnapshotLoader.layer,
2823 ShellSnapshotLoader.layer,
2924 ThreadHistoryController.layer,
3025);
3126
3227type ConnectionLayerSource =
3328 | typeof Connection.layer
34 | typeof snapshotLoaderLayer
35 | typeof runtimeContextLayer
36 | typeof connectionPlatformLayer
37 | typeof mobileBackgroundActivityObserverLayer
38 | typeof mobileBackgroundActivityReporterLayer;
29 | typeof layerSnapshotLoader
30 | typeof Runtime.layer
31 | typeof ConnectionPlatform.layer
32 | typeof BackgroundActivity.layerObserver
33 | typeof BackgroundActivity.layerReporter;
3934
40const providedClientConnectionLayer = snapshotLoaderLayer.pipe(
35const layerProvidedClientConnection = layerSnapshotLoader.pipe(
4136 Layer.provideMerge(
4237 Connection.layerWithOptions({ usageLimitSources: true, usageLimitsCommand: true }),
4338 ),
4439 Layer.provideMerge(
4540 Layer.mergeAll(
46 runtimeContextLayer,
47 providedConnectionPlatformLayer,
48 mobileBackgroundActivityObserverLayer,
41 Runtime.layer,
42 layerProvidedConnectionPlatform,
43 BackgroundActivity.layerObserver,
4944 ),
5045 ),
5146);
5247
53const connectionLayer = mobileBackgroundActivityReporterLayer.pipe(
54 Layer.provideMerge(providedClientConnectionLayer),
48const layerConnection = BackgroundActivity.layerReporter.pipe(
49 Layer.provideMerge(layerProvidedClientConnection),
5550);
5651
5752export const connectionAtomRuntime: Atom.AtomRuntime<
......@@ -61,5 +56,5 @@ export const connectionAtomRuntime: Atom.AtomRuntime<
6156 id: "t3.mobile.connection-runtime",
6257 hotModule: typeof module === "undefined" ? undefined : module.hot,
6358 registry: appAtomRegistry,
64 layer: connectionLayer,
59 layer: layerConnection,
6560});
apps/mobile/src/connection/storage.ts+1-1
......@@ -39,7 +39,7 @@ function targetPersistenceError(
3939 });
4040}
4141
42export const connectionStorageLayer = Layer.effectContext(
42export const layer = Layer.effectContext(
4343 Effect.gen(function* () {
4444 const catalog = yield* CatalogStore.make();
4545 const githubRoutingPermissions = yield* makeGitHubRoutingPermissions({
apps/mobile/src/features/agent-awareness/liveActivityPreferences.test.ts+6-6
......@@ -43,7 +43,7 @@ const connection: SavedRemoteConnection = {
4343 bearerToken: "local-bearer",
4444};
4545
46const testLayer = Layer.mergeAll(
46const layerTest = Layer.mergeAll(
4747 Layer.succeed(ManagedRelay.ManagedRelayClient, null as never),
4848 Layer.succeed(
4949 HttpClient.HttpClient,
......@@ -88,7 +88,7 @@ describe("liveActivityPreferences", () => {
8888 connection,
8989 liveActivitiesEnabled: false,
9090 });
91 }).pipe(Effect.provide(testLayer)),
91 }).pipe(Effect.provide(layerTest)),
9292 );
9393
9494 it.effect("pushes enabled Live Activity preferences to relay registrations", () =>
......@@ -108,7 +108,7 @@ describe("liveActivityPreferences", () => {
108108 connection,
109109 liveActivitiesEnabled: true,
110110 });
111 }).pipe(Effect.provide(testLayer)),
111 }).pipe(Effect.provide(layerTest)),
112112 );
113113
114114 it.effect("keeps local preferences refreshable when signed out", () =>
......@@ -124,7 +124,7 @@ describe("liveActivityPreferences", () => {
124124 liveActivitiesEnabled: false,
125125 });
126126 expect(linkEnvironmentToCloudWithPreference).not.toHaveBeenCalled();
127 }).pipe(Effect.provide(testLayer)),
127 }).pipe(Effect.provide(layerTest)),
128128 );
129129
130130 it.effect("does not try to re-link managed relay connections without bearer credentials", () => {
......@@ -147,7 +147,7 @@ describe("liveActivityPreferences", () => {
147147 connection,
148148 liveActivitiesEnabled: true,
149149 });
150 }).pipe(Effect.provide(testLayer));
150 }).pipe(Effect.provide(layerTest));
151151 });
152152
153153 it.effect("restores relay preferences when an environment update fails", () => {
......@@ -182,6 +182,6 @@ describe("liveActivityPreferences", () => {
182182 connection,
183183 liveActivitiesEnabled: true,
184184 });
185 }).pipe(Effect.provide(testLayer));
185 }).pipe(Effect.provide(layerTest));
186186 });
187187});
apps/mobile/src/features/agent-awareness/remoteRegistration.test.ts+27-27
......@@ -15,8 +15,8 @@ import { ManagedRelay } from "@t3tools/client-runtime/relay";
1515import type { EnvironmentId } from "@t3tools/contracts";
1616import { verifyDpopProof } from "@t3tools/shared/dpop";
1717import type { SavedRemoteConnection } from "../../lib/connection";
18import { cryptoLayer } from "../cloud/dpop";
19import { managedRelayClientLayer } from "../cloud/managedRelayLayer";
18import * as Dpop from "../cloud/dpop";
19import * as ManagedRelayLayer from "../cloud/managedRelayLayer";
2020import {
2121 clearAgentAwarenessRegistrationRecord,
2222 loadAgentAwarenessRegistrationRecord,
......@@ -220,8 +220,8 @@ function savedConnection(): SavedRemoteConnection {
220220 };
221221}
222222
223const relayTestLayer = managedRelayClientLayer("https://relay.example.test").pipe(
224 Layer.provide(Layer.mergeAll(FetchHttpClient.layer, cryptoLayer)),
223const layerRelayTest = ManagedRelayLayer.layer("https://relay.example.test").pipe(
224 Layer.provide(Layer.mergeAll(FetchHttpClient.layer, Dpop.layer)),
225225);
226226
227227const runBackgroundOperations = Effect.fn("TestRemoteRegistration.runBackgroundOperations")(
......@@ -405,7 +405,7 @@ describe("makeRelayDeviceRegistrationRequest", () => {
405405
406406 expect(activity.getPushToken).toHaveBeenCalledTimes(2);
407407 expect(addPushTokenListener).toHaveBeenCalledTimes(1);
408 }).pipe(Effect.provide(relayTestLayer));
408 }).pipe(Effect.provide(layerRelayTest));
409409 });
410410
411411 it.effect("preserves Live Activity push-token lookup failures", () => {
......@@ -427,7 +427,7 @@ describe("makeRelayDeviceRegistrationRequest", () => {
427427 cause,
428428 message: "Agent awareness operation read-live-activity-push-token failed.",
429429 });
430 }).pipe(Effect.provide(relayTestLayer));
430 }).pipe(Effect.provide(layerRelayTest));
431431 });
432432
433433 it.effect(
......@@ -441,7 +441,7 @@ describe("makeRelayDeviceRegistrationRequest", () => {
441441
442442 return Effect.gen(function* () {
443443 expect(yield* registerLiveActivityPushToken({ activity: activity as never })).toBe(false);
444 }).pipe(Effect.provide(relayTestLayer));
444 }).pipe(Effect.provide(layerRelayTest));
445445 },
446446 );
447447
......@@ -465,7 +465,7 @@ describe("makeRelayDeviceRegistrationRequest", () => {
465465 expect(activity.start).not.toHaveBeenCalled();
466466 expect(activity.update).not.toHaveBeenCalled();
467467 expect(activity.end).not.toHaveBeenCalled();
468 }).pipe(Effect.provide(relayTestLayer));
468 }).pipe(Effect.provide(layerRelayTest));
469469 },
470470 );
471471
......@@ -495,7 +495,7 @@ describe("makeRelayDeviceRegistrationRequest", () => {
495495 }
496496 yield* runBackgroundOperations();
497497 expect(activity.getPushToken).toHaveBeenCalled();
498 }).pipe(Effect.provide(relayTestLayer));
498 }).pipe(Effect.provide(layerRelayTest));
499499 },
500500 );
501501
......@@ -525,7 +525,7 @@ describe("makeRelayDeviceRegistrationRequest", () => {
525525 yield* refreshAgentAwarenessRegistration();
526526
527527 expect(Notifications.getDevicePushTokenAsync).toHaveBeenCalledTimes(1);
528 }).pipe(Effect.provide(relayTestLayer));
528 }).pipe(Effect.provide(layerRelayTest));
529529 });
530530
531531 it.effect("registers the APNs device when cloud auth becomes available", () => {
......@@ -583,7 +583,7 @@ describe("makeRelayDeviceRegistrationRequest", () => {
583583 }),
584584 ).toMatchObject({ ok: true });
585585 expect(getAgentAwarenessRegistrationStatus()).toBe("registered");
586 }).pipe(Effect.provide(relayTestLayer));
586 }).pipe(Effect.provide(layerRelayTest));
587587 });
588588
589589 it.effect("marks registration failed when device registration cannot complete", () => {
......@@ -604,7 +604,7 @@ describe("makeRelayDeviceRegistrationRequest", () => {
604604 // read as enabled.
605605 yield* refreshAgentAwarenessRegistration();
606606 expect(getAgentAwarenessRegistrationStatus()).toBe("failed");
607 }).pipe(Effect.provide(relayTestLayer));
607 }).pipe(Effect.provide(layerRelayTest));
608608 });
609609
610610 it("clears registration status on cloud sign-out", () => {
......@@ -642,7 +642,7 @@ describe("makeRelayDeviceRegistrationRequest", () => {
642642 return Effect.gen(function* () {
643643 yield* runBackgroundOperations();
644644 expect(getAgentAwarenessRegistrationStatus()).toBe("unknown");
645 }).pipe(Effect.provide(relayTestLayer));
645 }).pipe(Effect.provide(layerRelayTest));
646646 });
647647
648648 it.effect("keeps a registered status when a later refresh fails", () => {
......@@ -664,7 +664,7 @@ describe("makeRelayDeviceRegistrationRequest", () => {
664664 );
665665 yield* refreshAgentAwarenessRegistration();
666666 expect(getAgentAwarenessRegistrationStatus()).toBe("registered");
667 }).pipe(Effect.provide(relayTestLayer));
667 }).pipe(Effect.provide(layerRelayTest));
668668 });
669669
670670 it.effect("does not re-register the same account when nothing has changed", () => {
......@@ -687,7 +687,7 @@ describe("makeRelayDeviceRegistrationRequest", () => {
687687 yield* refreshAgentAwarenessRegistration();
688688 expect(getAgentAwarenessRegistrationStatus()).toBe("registered");
689689 expect(saveAgentAwarenessRegistrationRecord).not.toHaveBeenCalled();
690 }).pipe(Effect.provide(relayTestLayer));
690 }).pipe(Effect.provide(layerRelayTest));
691691 });
692692
693693 it.effect("dedupes rapid activity-token re-registrations within the replay window", () => {
......@@ -735,7 +735,7 @@ describe("makeRelayDeviceRegistrationRequest", () => {
735735 vi.mocked(loadOrCreateAgentAwarenessDeviceId).mockClear();
736736 yield* refreshActiveLiveActivityRemoteRegistration();
737737 expect(loadOrCreateAgentAwarenessDeviceId).not.toHaveBeenCalled();
738 }).pipe(Effect.provide(relayTestLayer));
738 }).pipe(Effect.provide(layerRelayTest));
739739 });
740740
741741 it.effect("re-registers when the stored account identity differs", () => {
......@@ -750,7 +750,7 @@ describe("makeRelayDeviceRegistrationRequest", () => {
750750 return Effect.gen(function* () {
751751 yield* refreshAgentAwarenessRegistration();
752752 expect(saveAgentAwarenessRegistrationRecord).toHaveBeenCalledTimes(1);
753 }).pipe(Effect.provide(relayTestLayer));
753 }).pipe(Effect.provide(layerRelayTest));
754754 });
755755
756756 it.effect("coalesces simultaneous sign-in and environment connection registrations", () => {
......@@ -784,7 +784,7 @@ describe("makeRelayDeviceRegistrationRequest", () => {
784784 return Effect.gen(function* () {
785785 yield* runBackgroundOperations();
786786 expect(Notifications.getPermissionsAsync).toHaveBeenCalledTimes(1);
787 }).pipe(Effect.provide(relayTestLayer));
787 }).pipe(Effect.provide(layerRelayTest));
788788 });
789789
790790 it.effect("continues queued device registration after a failed auth lookup", () => {
......@@ -808,7 +808,7 @@ describe("makeRelayDeviceRegistrationRequest", () => {
808808
809809 expect(backgroundRuntime.pending).toHaveLength(0);
810810 expect(tokenProvider).toHaveBeenCalledTimes(2);
811 }).pipe(Effect.provide(relayTestLayer));
811 }).pipe(Effect.provide(layerRelayTest));
812812 });
813813
814814 it("only registers again when the authenticated identity changes", () => {
......@@ -852,7 +852,7 @@ describe("makeRelayDeviceRegistrationRequest", () => {
852852 return Effect.gen(function* () {
853853 yield* runBackgroundOperations();
854854 expect(Notifications.getDevicePushTokenAsync).toHaveBeenCalledTimes(1);
855 }).pipe(Effect.provide(relayTestLayer));
855 }).pipe(Effect.provide(layerRelayTest));
856856 });
857857
858858 it.effect(
......@@ -890,7 +890,7 @@ describe("makeRelayDeviceRegistrationRequest", () => {
890890 unregisterAgentAwarenessConnection(savedConnection().environmentId);
891891
892892 expect(fetchMock).not.toHaveBeenCalled();
893 }).pipe(Effect.provide(relayTestLayer));
893 }).pipe(Effect.provide(layerRelayTest));
894894 },
895895 );
896896
......@@ -988,8 +988,8 @@ describe("makeRelayDeviceRegistrationRequest", () => {
988988 }).pipe(
989989 Effect.provideService(FetchHttpClient.Fetch, globalThis.fetch),
990990 Effect.provide(
991 managedRelayClientLayer("https://permission-relay.example.test").pipe(
992 Layer.provide(Layer.mergeAll(FetchHttpClient.layer, cryptoLayer)),
991 ManagedRelayLayer.layer("https://permission-relay.example.test").pipe(
992 Layer.provide(Layer.mergeAll(FetchHttpClient.layer, Dpop.layer)),
993993 ),
994994 ),
995995 );
......@@ -1039,7 +1039,7 @@ describe("makeRelayDeviceRegistrationRequest", () => {
10391039 }
10401040 expect(getAgentAwarenessRegistrationStatus()).toBe("failed");
10411041 expect(saveAgentAwarenessRegistrationRecord).not.toHaveBeenCalled();
1042 }).pipe(Effect.provide(relayTestLayer));
1042 }).pipe(Effect.provide(layerRelayTest));
10431043 });
10441044
10451045 it.effect("registers an Android FCM token without invoking Apple Live Activities", () => {
......@@ -1082,7 +1082,7 @@ describe("makeRelayDeviceRegistrationRequest", () => {
10821082 vi.mocked(clearAndroidAgentNotifications).mockClear();
10831083 setAgentAwarenessRelayTokenProvider(() => Promise.resolve("clerk-token-user-b"), "user-b");
10841084 expect(clearAndroidAgentNotifications).toHaveBeenCalled();
1085 }).pipe(Effect.provide(relayTestLayer));
1085 }).pipe(Effect.provide(layerRelayTest));
10861086 });
10871087 it.effect(
10881088 "preserves same-account Android notifications and replays on remount and later foreground",
......@@ -1120,7 +1120,7 @@ describe("makeRelayDeviceRegistrationRequest", () => {
11201120 setAgentAwarenessRelayTokenProvider(null);
11211121 expect(clearAndroidAgentNotifications).toHaveBeenCalled();
11221122 expect(clearAgentAwarenessRegistrationRecord).toHaveBeenCalled();
1123 }).pipe(Effect.provide(relayTestLayer));
1123 }).pipe(Effect.provide(layerRelayTest));
11241124 },
11251125 );
11261126
......@@ -1137,7 +1137,7 @@ describe("makeRelayDeviceRegistrationRequest", () => {
11371137 yield* runBackgroundOperations();
11381138 expect(configureAndroidAgentNotifications).not.toHaveBeenCalled();
11391139 expect(saveAgentAwarenessRegistrationRecord).not.toHaveBeenCalled();
1140 }).pipe(Effect.provide(relayTestLayer));
1140 }).pipe(Effect.provide(layerRelayTest));
11411141 },
11421142 );
11431143});
apps/mobile/src/features/cloud/dpop.test.ts+7-11
......@@ -8,12 +8,8 @@ import * as Crypto from "effect/Crypto";
88import * as Effect from "effect/Effect";
99import { verifyDpopProof } from "@t3tools/shared/dpop";
1010
11import {
12 createDpopProof,
13 generateDpopProofKeyPair,
14 loadOrCreateDpopProofKeyPair,
15 cryptoLayer,
16} from "./dpop";
11import { createDpopProof, generateDpopProofKeyPair, loadOrCreateDpopProofKeyPair } from "./dpop";
12import * as Dpop from "./dpop";
1713
1814vi.mock("expo-crypto", () => ({
1915 CryptoDigestAlgorithm: {
......@@ -75,7 +71,7 @@ describe("mobile DPoP", () => {
7571 expect(Buffer.from(digest).toString("hex")).toBe(
7672 NodeCrypto.createHash("sha256").update("typed-array").digest("hex"),
7773 );
78 }).pipe(Effect.provide(cryptoLayer)),
74 }).pipe(Effect.provide(Dpop.layer)),
7975 );
8076
8177 it.effect("persists and reuses the installation proof key", () =>
......@@ -86,7 +82,7 @@ describe("mobile DPoP", () => {
8682
8783 expect(second.thumbprint).toBe(first.thumbprint);
8884 expect(second.privateJwk).toEqual(first.privateJwk);
89 }).pipe(Effect.provide(cryptoLayer)),
85 }).pipe(Effect.provide(Dpop.layer)),
9086 );
9187
9288 it.effect("rejects malformed persisted proof keys", () =>
......@@ -96,7 +92,7 @@ describe("mobile DPoP", () => {
9692 const error = yield* loadOrCreateDpopProofKeyPair().pipe(Effect.flip);
9793
9894 expect(error.message).toBe("Stored DPoP proof key is invalid.");
99 }).pipe(Effect.provide(cryptoLayer)),
95 }).pipe(Effect.provide(Dpop.layer)),
10096 );
10197
10298 it.effect("signs connect and bootstrap proofs with the same ephemeral proof key", () =>
......@@ -135,7 +131,7 @@ describe("mobile DPoP", () => {
135131 nowEpochSeconds: proofIat(bootstrap.proof),
136132 }),
137133 ).toMatchObject({ ok: true, thumbprint: proofKey.thumbprint });
138 }).pipe(Effect.provide(cryptoLayer)),
134 }).pipe(Effect.provide(Dpop.layer)),
139135 );
140136
141137 it.effect("signs DPoP proofs with RFC 9449 htu normalization", () =>
......@@ -161,6 +157,6 @@ describe("mobile DPoP", () => {
161157 nowEpochSeconds: proofIat(proof.proof),
162158 }),
163159 ).toMatchObject({ ok: true });
164 }).pipe(Effect.provide(cryptoLayer)),
160 }).pipe(Effect.provide(Dpop.layer)),
165161 );
166162});
apps/mobile/src/features/cloud/dpop.ts+1-1
......@@ -65,7 +65,7 @@ function toExpoDigestAlgorithm(
6565 }
6666}
6767
68export const cryptoLayer = Layer.succeed(
68export const layer = Layer.succeed(
6969 Crypto.Crypto,
7070 Crypto.make({
7171 randomBytes: ExpoCrypto.getRandomBytes,
apps/mobile/src/features/cloud/linkEnvironment.test.ts+7-7
......@@ -5,7 +5,7 @@ import * as Layer from "effect/Layer";
55import { EnvironmentId } from "@t3tools/contracts";
66import { RelayMobileClientId } from "@t3tools/contracts/relay";
77import { ManagedRelay } from "@t3tools/client-runtime/relay";
8import { remoteHttpClientLayer } from "@t3tools/client-runtime/rpc";
8import { layerRemoteHttpClient } from "@t3tools/client-runtime/rpc";
99import { HttpClient } from "effect/http";
1010
1111import * as MobileStorage from "../../persistence/mobile-storage";
......@@ -63,7 +63,7 @@ const createProofMock = vi.fn(
6363 (input: { readonly method: string; readonly url: string; readonly accessToken?: string }) =>
6464 Effect.succeed(`dpop:${input.method}:${input.url}`),
6565);
66const testDpopSignerLayer = Layer.succeed(
66const layerTestDpopSigner = Layer.succeed(
6767 ManagedRelay.ManagedRelayDpopSigner,
6868 ManagedRelay.ManagedRelayDpopSigner.of({
6969 thumbprint: Effect.succeed("client-proof-key-thumbprint"),
......@@ -71,10 +71,10 @@ const testDpopSignerLayer = Layer.succeed(
7171 }),
7272);
7373
74function cloudClientLayer() {
75 const httpClientLayer = remoteHttpClientLayer((input, init) => globalThis.fetch(input, init));
74function layerCloudClient() {
75 const layerHttpClient = layerRemoteHttpClient((input, init) => globalThis.fetch(input, init));
7676 return Layer.mergeAll(
77 httpClientLayer,
77 layerHttpClient,
7878 Layer.succeed(
7979 MobileStorage.MobileStorage,
8080 MobileStorage.MobileStorage.of({
......@@ -93,7 +93,7 @@ function cloudClientLayer() {
9393 ManagedRelay.layer({
9494 relayUrl: "https://relay.example.test",
9595 clientId: RelayMobileClientId,
96 }).pipe(Layer.provideMerge(testDpopSignerLayer), Layer.provide(httpClientLayer)),
96 }).pipe(Layer.provideMerge(layerTestDpopSigner), Layer.provide(layerHttpClient)),
9797 );
9898}
9999
......@@ -106,7 +106,7 @@ const withCloudServices = <A, E>(
106106 | ManagedRelay.ManagedRelayDpopSigner
107107 | MobileStorage.MobileStorage
108108 >,
109) => effect.pipe(Effect.provide(cloudClientLayer()));
109) => effect.pipe(Effect.provide(layerCloudClient()));
110110
111111function validLinkProof() {
112112 return "signed-environment-link-jwt";
apps/mobile/src/features/cloud/managedRelayLayer.test.ts+4-4
......@@ -9,8 +9,8 @@ import * as Effect from "effect/Effect";
99import * as Layer from "effect/Layer";
1010import { FetchHttpClient } from "effect/http";
1111
12import { cryptoLayer } from "./dpop";
13import { managedRelayClientLayer } from "./managedRelayLayer";
12import * as Dpop from "./dpop";
13import * as ManagedRelayLayer from "./managedRelayLayer";
1414
1515vi.mock("expo-crypto", () => ({
1616 CryptoDigestAlgorithm: { SHA256: "SHA-256" },
......@@ -54,8 +54,8 @@ describe("managed relay DPoP signer", () => {
5454 assert.equal(yield* signer.thumbprint, thumbprint);
5555 }).pipe(
5656 Effect.provide(
57 managedRelayClientLayer("https://relay.example.test").pipe(
58 Layer.provide(Layer.mergeAll(FetchHttpClient.layer, cryptoLayer)),
57 ManagedRelayLayer.layer("https://relay.example.test").pipe(
58 Layer.provide(Layer.mergeAll(FetchHttpClient.layer, Dpop.layer)),
5959 ),
6060 ),
6161 ),
apps/mobile/src/features/cloud/managedRelayLayer.ts+3-3
......@@ -10,7 +10,7 @@ import * as Layer from "effect/Layer";
1010import { createDpopProof, loadOrCreateDpopProofKeyPair } from "./dpop";
1111import { managedRelayAccessTokenStore } from "./managedRelayTokenStore";
1212
13const relayDpopSignerLayer = Layer.effect(
13const layerRelayDpopSigner = Layer.effect(
1414 ManagedRelay.ManagedRelayDpopSigner,
1515 Effect.gen(function* () {
1616 const crypto = yield* Crypto.Crypto;
......@@ -64,9 +64,9 @@ const relayDpopSignerLayer = Layer.effect(
6464 }),
6565);
6666
67export const managedRelayClientLayer = (relayUrl: string) =>
67export const layer = (relayUrl: string) =>
6868 ManagedRelay.layer({
6969 relayUrl,
7070 clientId: RelayMobileClientId,
7171 accessTokenStore: managedRelayAccessTokenStore,
72 }).pipe(Layer.provideMerge(relayDpopSignerLayer));
72 }).pipe(Layer.provideMerge(layerRelayDpopSigner));
apps/mobile/src/features/cloud/managedRelayState.ts+2-2
......@@ -14,11 +14,11 @@ import type { RelayClientEnvironmentRecord } from "@t3tools/contracts/relay";
1414import { AsyncResult, Atom } from "effect/reactivity";
1515import { useCallback, useEffect } from "react";
1616
17import { runtimeContextLayer } from "../../lib/runtime";
17import * as Runtime from "../../lib/runtime";
1818import { appAtomRegistry } from "../../state/atom-registry";
1919import { cloudDebugLog } from "./cloudDebugLog";
2020
21const managedRelayAtomRuntime = Atom.runtime(runtimeContextLayer);
21const managedRelayAtomRuntime = Atom.runtime(Runtime.layer);
2222
2323export const managedRelayQueryManager = createManagedRelayQueryManager(managedRelayAtomRuntime, {
2424 onQueryEvent: (event) =>
apps/mobile/src/features/observability/tracing.test.ts+12-12
......@@ -4,10 +4,10 @@ import * as Effect from "effect/Effect";
44import * as Layer from "effect/Layer";
55import { vi } from "vite-plus/test";
66
7import { remoteHttpClientLayer } from "@t3tools/client-runtime/rpc";
7import { layerRemoteHttpClient } from "@t3tools/client-runtime/rpc";
88import { withRelayClientTracing } from "@t3tools/shared/relayTracing";
99
10import { makeTracingLayer } from "./tracing";
10import * as Tracing from "./tracing";
1111
1212vi.mock("expo-constants", () => ({
1313 default: {
......@@ -19,7 +19,7 @@ vi.mock("expo-constants", () => ({
1919
2020it.effect("exports spans through the scoped mobile OTLP layer", () => {
2121 const fetchFn = vi.fn<typeof fetch>(async () => new Response(null, { status: 202 }));
22 const tracingLayer = makeTracingLayer(
22 const layerTracing = Tracing.layerFromConfig(
2323 {
2424 tracesUrl: "https://api.axiom.test/v1/traces",
2525 tracesDataset: "mobile-traces",
......@@ -29,13 +29,13 @@ it.effect("exports spans through the scoped mobile OTLP layer", () => {
2929 appVariant: "test",
3030 serviceVersion: "1.2.3",
3131 },
32 ).pipe(Layer.provide(remoteHttpClientLayer(fetchFn)));
33 const tracedApplication = Layer.effectDiscard(
32 ).pipe(Layer.provide(layerRemoteHttpClient(fetchFn)));
33 const layerTracedApplication = Layer.effectDiscard(
3434 Effect.void.pipe(Effect.withSpan("mobile.test.span"), withRelayClientTracing),
35 ).pipe(Layer.provide(tracingLayer));
35 ).pipe(Layer.provide(layerTracing));
3636
3737 return Effect.gen(function* () {
38 yield* Layer.build(tracedApplication);
38 yield* Layer.build(layerTracedApplication);
3939
4040 expect(fetchFn).not.toHaveBeenCalled();
4141 }).pipe(
......@@ -55,7 +55,7 @@ it.effect("exports spans through the scoped mobile OTLP layer", () => {
5555
5656it.effect("does not let OTLP serialization failures alter application effects", () => {
5757 const fetchFn = vi.fn<typeof fetch>(async () => new Response(null, { status: 202 }));
58 const tracingLayer = makeTracingLayer(
58 const layerTracing = Tracing.layerFromConfig(
5959 {
6060 tracesUrl: "https://api.axiom.test/v1/traces",
6161 tracesDataset: "mobile-traces",
......@@ -65,9 +65,9 @@ it.effect("does not let OTLP serialization failures alter application effects",
6565 appVariant: "test",
6666 serviceVersion: "1.2.3",
6767 },
68 ).pipe(Layer.provide(remoteHttpClientLayer(fetchFn)));
68 ).pipe(Layer.provide(layerRemoteHttpClient(fetchFn)));
6969 const failure = { durationNanos: 1n };
70 const tracedApplication = Layer.effectDiscard(
70 const layerTracedApplication = Layer.effectDiscard(
7171 Effect.fail(failure).pipe(
7272 Effect.withSpan("mobile.test.failed-span"),
7373 withRelayClientTracing,
......@@ -81,9 +81,9 @@ it.effect("does not let OTLP serialization failures alter application effects",
8181 : Effect.die(new Error("Expected the original typed failure."));
8282 }),
8383 ),
84 ).pipe(Layer.provide(tracingLayer));
84 ).pipe(Layer.provide(layerTracing));
8585
86 return Layer.build(tracedApplication).pipe(
86 return Layer.build(layerTracedApplication).pipe(
8787 Effect.scoped,
8888 Effect.andThen(
8989 Effect.sync(() => {
apps/mobile/src/features/observability/tracing.ts+4-4
......@@ -1,5 +1,5 @@
11import Constants from "expo-constants";
2import { makeRelayClientTracingLayer } from "@t3tools/shared/relayTracing";
2import * as RelayTracing from "@t3tools/shared/relayTracing";
33
44import { hasTracingPublicConfig, resolveCloudPublicConfig } from "../cloud/publicConfig";
55
......@@ -23,8 +23,8 @@ export function resolveTracingConfig(): TracingConfig | null {
2323 return { tracesUrl, tracesDataset, tracesToken };
2424}
2525
26export function makeTracingLayer(config: TracingConfig | null, resource: TracingResource) {
27 return makeRelayClientTracingLayer(config, {
26export function layerFromConfig(config: TracingConfig | null, resource: TracingResource) {
27 return RelayTracing.layer(config, {
2828 serviceName: "t3code-mobile",
2929 serviceVersion: resource.serviceVersion,
3030 runtime: "react-native",
......@@ -32,7 +32,7 @@ export function makeTracingLayer(config: TracingConfig | null, resource: Tracing
3232 });
3333}
3434
35export const tracingLayer = makeTracingLayer(resolveTracingConfig(), {
35export const layer = layerFromConfig(resolveTracingConfig(), {
3636 serviceVersion: Constants.expoConfig?.version,
3737 appVariant:
3838 typeof Constants.expoConfig?.extra?.appVariant === "string"
apps/mobile/src/lib/hot-swappable-atom-runtime.test.ts+8-8
......@@ -10,7 +10,7 @@ class RuntimeValue extends Context.Service<RuntimeValue, { readonly value: strin
1010 "t3/mobile/test/RuntimeValue",
1111) {}
1212
13function runtimeLayer(value: string, events: string[]) {
13function layerRuntime(value: string, events: string[]) {
1414 return Layer.effect(
1515 RuntimeValue,
1616 Effect.acquireRelease(
......@@ -26,7 +26,7 @@ function runtimeLayer(value: string, events: string[]) {
2626 );
2727}
2828
29function runtimeLayerWithRelease(
29function layerRuntimeWithRelease(
3030 value: string,
3131 events: string[],
3232 release: Effect.Effect<void> = Effect.sync(() => {
......@@ -56,7 +56,7 @@ describe("hotSwappableAtomRuntime", () => {
5656 id,
5757 hotModule: { accept },
5858 registry,
59 layer: runtimeLayer("first", events),
59 layer: layerRuntime("first", events),
6060 });
6161 const valueAtom = runtime.atom(RuntimeValue.pipe(Effect.map((service) => service.value)));
6262 const values: string[] = [];
......@@ -81,13 +81,13 @@ describe("hotSwappableAtomRuntime", () => {
8181 id,
8282 hotModule: { accept },
8383 registry,
84 layer: runtimeLayer("second", events),
84 layer: layerRuntime("second", events),
8585 });
8686 hotSwappableAtomRuntime({
8787 id,
8888 hotModule: { accept },
8989 registry,
90 layer: runtimeLayer("third", events),
90 layer: layerRuntime("third", events),
9191 });
9292 registry.set(unrelatedAtom, 8);
9393
......@@ -120,7 +120,7 @@ describe("hotSwappableAtomRuntime", () => {
120120 vi.stubGlobal("__DEV__", false);
121121 const registry = AtomRegistry.make();
122122 const accept = vi.fn();
123 const layer = runtimeLayer("production", []);
123 const layer = layerRuntime("production", []);
124124
125125 const first = hotSwappableAtomRuntime({
126126 id: "production",
......@@ -163,7 +163,7 @@ describe("hotSwappableAtomRuntime", () => {
163163 id,
164164 hotModule: { accept() {} },
165165 registry,
166 layer: runtimeLayerWithRelease("first", events, firstRelease),
166 layer: layerRuntimeWithRelease("first", events, firstRelease),
167167 });
168168 const valueAtom = runtime.atom(RuntimeValue.pipe(Effect.map((service) => service.value)));
169169 const values: string[] = [];
......@@ -176,7 +176,7 @@ describe("hotSwappableAtomRuntime", () => {
176176 id,
177177 hotModule: { accept() {} },
178178 registry,
179 layer: runtimeLayer("second", events),
179 layer: layerRuntime("second", events),
180180 });
181181
182182 expect(events).toEqual(["acquire:first", "release:start:first", "acquire:second"]);
apps/mobile/src/lib/runtime.ts+16-16
......@@ -2,12 +2,12 @@ import * as Layer from "effect/Layer";
22import * as ManagedRuntime from "effect/ManagedRuntime";
33import * as Socket from "effect/socket/Socket";
44
5import { remoteHttpClientLayer } from "@t3tools/client-runtime/rpc";
5import { layerRemoteHttpClient } from "@t3tools/client-runtime/rpc";
66
7import { cryptoLayer } from "../features/cloud/dpop";
8import { managedRelayClientLayer } from "../features/cloud/managedRelayLayer";
7import * as Dpop from "../features/cloud/dpop";
8import * as ManagedRelayLayer from "../features/cloud/managedRelayLayer";
99import { resolveCloudPublicConfig } from "../features/cloud/publicConfig";
10import { tracingLayer } from "../features/observability/tracing";
10import * as Tracing from "../features/observability/tracing";
1111import * as Persistence from "../persistence/layer";
1212import { disposeOnFoundationReplace, type FoundationHotModule } from "./foundation-fast-refresh";
1313
......@@ -17,32 +17,32 @@ function configuredRelayUrl(): string {
1717 return resolveCloudPublicConfig().relay.url ?? "http://relay.invalid";
1818}
1919
20const httpClientLayer = remoteHttpClientLayer(fetch);
20const layerHttpClient = layerRemoteHttpClient(fetch);
2121
2222type RuntimeLayerSource =
23 | ReturnType<typeof managedRelayClientLayer>
23 | ReturnType<typeof ManagedRelayLayer.layer>
2424 | typeof Socket.layerWebSocketConstructorGlobal
25 | typeof cryptoLayer
26 | typeof httpClientLayer
25 | typeof Dpop.layer
26 | typeof layerHttpClient
2727 | typeof Persistence.layer
28 | typeof tracingLayer;
28 | typeof Tracing.layer;
2929
30const runtimeLayer = Layer.merge(
31 managedRelayClientLayer(configuredRelayUrl()),
30const layerRuntime = Layer.merge(
31 ManagedRelayLayer.layer(configuredRelayUrl()),
3232 Socket.layerWebSocketConstructorGlobal,
3333).pipe(
34 Layer.provideMerge(cryptoLayer),
35 Layer.provideMerge(httpClientLayer),
36 Layer.provideMerge(tracingLayer.pipe(Layer.provide(httpClientLayer))),
34 Layer.provideMerge(Dpop.layer),
35 Layer.provideMerge(layerHttpClient),
36 Layer.provideMerge(Tracing.layer.pipe(Layer.provide(layerHttpClient))),
3737 Layer.provideMerge(Persistence.layer),
3838);
3939
4040export const runtime: ManagedRuntime.ManagedRuntime<
4141 Layer.Success<RuntimeLayerSource>,
4242 Layer.Error<RuntimeLayerSource>
43> = ManagedRuntime.make(runtimeLayer);
43> = ManagedRuntime.make(layerRuntime);
4444
45export const runtimeContextLayer: Layer.Layer<
45export const layer: Layer.Layer<
4646 Layer.Success<RuntimeLayerSource>,
4747 Layer.Error<RuntimeLayerSource>
4848> = Layer.effectContext(runtime.contextEffect);
apps/mobile/src/persistence/layer.ts+4-4
......@@ -6,11 +6,11 @@ import * as MobilePreferences from "./mobile-preferences";
66import * as MobileSecureStorage from "./mobile-secure-storage";
77import * as MobileStorage from "./mobile-storage";
88
9const baseLayer = Layer.merge(MobileDatabase.layer, MobileSecureStorage.layer);
10const dependentLayer = Layer.mergeAll(
9const layerBase = Layer.merge(MobileDatabase.layer, MobileSecureStorage.layer);
10const layerDependent = Layer.mergeAll(
1111 MobilePreferences.layer,
1212 MobileStorage.layer,
1313 EnvironmentCacheStore.layer,
14).pipe(Layer.provide(baseLayer));
14).pipe(Layer.provide(layerBase));
1515
16export const layer = Layer.merge(baseLayer, dependentLayer);
16export const layer = Layer.merge(layerBase, layerDependent);
apps/mobile/src/state/client-cache-state.ts+1-1
......@@ -59,7 +59,7 @@ function aggregateCacheSummary(
5959 };
6060}
6161
62const clientCacheRuntime = Atom.runtime(Runtime.runtimeContextLayer);
62const clientCacheRuntime = Atom.runtime(Runtime.layer);
6363
6464export const clientCacheSummaryAtom = clientCacheRuntime
6565 .atom(
apps/mobile/src/state/preferences.test.ts+1-1
......@@ -22,7 +22,7 @@ vi.mock("../lib/runtime", async () => {
2222 const Layer = await import("effect/Layer");
2323 return {
2424 runtime: { runPromise: vi.fn() },
25 runtimeContextLayer: Layer.empty,
25 layer: Layer.empty,
2626 };
2727});
2828
apps/mobile/src/state/preferences.ts+1-1
......@@ -136,7 +136,7 @@ export function createMobilePreferencesState(
136136 return { preferencesAtom, updatePreferencesAtom } as const;
137137}
138138
139const mobilePreferencesRuntime = Atom.runtime(Runtime.runtimeContextLayer);
139const mobilePreferencesRuntime = Atom.runtime(Runtime.layer);
140140export const mobilePreferencesState = createMobilePreferencesState(mobilePreferencesRuntime);
141141
142142export const mobilePreferencesAtom = mobilePreferencesState.preferencesAtom;
apps/server/integration/NetworkTransferMeasurement.integration.ts+4-4
......@@ -165,19 +165,19 @@ function makeWebSocketTransferRecorder(): WebSocketTransferRecorder {
165165 };
166166}
167167
168function countingWsRpcProtocolLayer(input: {
168function layerCountingWsRpcProtocol(input: {
169169 readonly url: string;
170170 readonly cookie: string;
171171 readonly recorder: WebSocketTransferRecorder;
172172}) {
173173 // Socket.makeWebSocket only ever passes its `protocols` option here.
174 const webSocketConstructorLayer = Layer.succeed(Socket.WebSocketConstructor, (url, protocols) =>
174 const layerWebSocketConstructor = Layer.succeed(Socket.WebSocketConstructor, (url, protocols) =>
175175 input.recorder.connect(url, protocols as string | string[] | undefined, input.cookie),
176176 );
177177 return RpcClient.layerProtocolSocket().pipe(
178178 Layer.provide(
179179 Socket.layerWebSocket(input.url, { openTimeout: "10 seconds" }).pipe(
180 Layer.provide(webSocketConstructorLayer),
180 Layer.provide(layerWebSocketConstructor),
181181 ),
182182 ),
183183 Layer.provide(RpcSerialization.layerJson),
......@@ -207,7 +207,7 @@ export const openMeasuredWsClient = Effect.fn("TransferBudget.openMeasuredWsClie
207207 const parent = yield* Effect.scope;
208208 const scope = yield* Scope.fork(parent);
209209 const protocol = yield* Layer.buildWithScope(
210 countingWsRpcProtocolLayer({ url: input.url, cookie: input.cookie, recorder }),
210 layerCountingWsRpcProtocol({ url: input.url, cookie: input.cookie, recorder }),
211211 scope,
212212 );
213213 const client = yield* makeCountingWsRpcClient.pipe(
apps/server/integration/transferBudgetV2.integration.test.ts+22-22
......@@ -35,8 +35,8 @@ import * as HttpApi from "effect/http-api/HttpApi";
3535import * as HttpApiBuilder from "effect/http-api/HttpApiBuilder";
3636import { HttpRouter, HttpServer } from "effect/http";
3737import { Rpc, RpcGroup, RpcServer, RpcSerialization } from "effect/rpc";
38import { SqlitePersistenceMemory } from "../src/persistence/Layers/Sqlite.ts";
39import { OrchestrationEventStoreLive } from "../src/persistence/Layers/OrchestrationEventStore.ts";
38import * as SqlitePersistence from "../src/persistence/Layers/Sqlite.ts";
39import * as OrchestrationEventStore from "../src/persistence/Layers/OrchestrationEventStore.ts";
4040import * as EventStore from "../src/orchestration-v2/EventStore.ts";
4141import * as EventSink from "../src/orchestration-v2/EventSink.ts";
4242import * as ProjectionStore from "../src/orchestration-v2/ProjectionStore.ts";
......@@ -44,8 +44,8 @@ import * as ThreadManagementService from "../src/orchestration-v2/ThreadManageme
4444import * as ProjectStore from "../src/orchestration-v2/ProjectStore.ts";
4545import * as ProjectService from "../src/project/ProjectService.ts";
4646import * as ProjectEnrichmentService from "../src/project/ProjectEnrichmentService.ts";
47import { orchestrationHttpApiLayer } from "../src/orchestration-v2/http.ts";
48import { httpCompressionLayer } from "../src/http.ts";
47import * as OrchestrationHttp from "../src/orchestration-v2/http.ts";
48import * as ServerHttp from "../src/http.ts";
4949import { subscribeOrchestrationV2Thread, subscribeOrchestrationV2Shell } from "../src/ws.ts";
5050import {
5151 measureHttpGet,
......@@ -69,15 +69,15 @@ const decodeShellSnapshot = Schema.decodeUnknownEffect(
6969 Schema.fromJsonString(Schema.toCodecJson(OrchestrationV2ShellSnapshot)),
7070);
7171
72const persistence = EventSink.layer.pipe(
72const layerPersistence = EventSink.layer.pipe(
7373 Layer.provideMerge(EventStore.layerFromOrchestrationEventStore),
7474 Layer.provideMerge(ProjectionStore.layer),
75 Layer.provideMerge(OrchestrationEventStoreLive),
76 Layer.provideMerge(SqlitePersistenceMemory),
75 Layer.provideMerge(OrchestrationEventStore.layer),
76 Layer.provideMerge(SqlitePersistence.layerMemory),
7777);
7878// The facade's import/command paths are outside this measurement. Its reads and
7979// stream delegate to the real SQL projection and event sink, as the runtime does.
80const management = Layer.unwrap(
80const layerManagement = Layer.unwrap(
8181 Effect.gen(function* () {
8282 const projections = yield* ProjectionStore.ProjectionStoreV2;
8383 const sink = yield* EventSink.EventSinkV2;
......@@ -93,7 +93,7 @@ const management = Layer.unwrap(
9393 });
9494 }),
9595);
96const enrichment = Layer.unwrap(
96const layerEnrichment = Layer.unwrap(
9797 Effect.gen(function* () {
9898 const changes = yield* PubSub.unbounded<never>();
9999 return Layer.mock(ProjectEnrichmentService.ProjectEnrichmentService)({
......@@ -108,16 +108,16 @@ const enrichment = Layer.unwrap(
108108 }),
109109);
110110// The transfer history has no project events, so shell streams never read a project shell.
111const services = management.pipe(
111const layerServices = layerManagement.pipe(
112112 Layer.provideMerge(ProjectStore.layer),
113113 Layer.provideMerge(Layer.mock(ProjectService.ProjectService)({})),
114 Layer.provideMerge(enrichment),
115 Layer.provideMerge(persistence),
114 Layer.provideMerge(layerEnrichment),
115 Layer.provideMerge(layerPersistence),
116116);
117117class TransferApi extends HttpApi.make("environment").add(
118118 EnvironmentHttpApi.groups.orchestration,
119119) {}
120const auth = Layer.succeed(EnvironmentAuthenticatedAuth, (effect) =>
120const layerAuth = Layer.succeed(EnvironmentAuthenticatedAuth, (effect) =>
121121 effect.pipe(
122122 Effect.provideService(EnvironmentAuthenticatedPrincipal, {
123123 sessionId: AuthSessionId.make("transfer-session"),
......@@ -141,22 +141,22 @@ const group = RpcGroup.make(
141141 stream: true,
142142 }),
143143);
144const handlers = group.toLayer({
144const layerHandlers = group.toLayer({
145145 [ORCHESTRATION_V2_WS_METHODS.subscribeThread]: (input) =>
146146 Stream.unwrap(subscribeOrchestrationV2Thread(input)),
147147 [ORCHESTRATION_V2_WS_METHODS.subscribeShell]: (input) =>
148148 Stream.unwrap(subscribeOrchestrationV2Shell(input)),
149149});
150const routes = Layer.mergeAll(
150const layerRoutes = Layer.mergeAll(
151151 HttpApiBuilder.layer(TransferApi).pipe(
152 Layer.provide(orchestrationHttpApiLayer),
153 Layer.provide(auth),
152 Layer.provide(OrchestrationHttp.layer),
153 Layer.provide(layerAuth),
154154 ),
155155 RpcServer.layerHttp({ group, path: "/ws", protocol: "websocket" }).pipe(
156 Layer.provide(handlers),
156 Layer.provide(layerHandlers),
157157 Layer.provide(RpcSerialization.layerJson),
158158 ),
159).pipe(Layer.provide(httpCompressionLayer), Layer.provide(NodeHttpPlatform.layer));
159).pipe(Layer.provide(ServerHttp.layerHttpCompression), Layer.provide(NodeHttpPlatform.layer));
160160
161161function difference(
162162 after: { wireBytes: number; decodedBytes: number; messages: number },
......@@ -229,9 +229,9 @@ it.live(
229229 yield* sink.write({ events: [threadCreated(provider)] });
230230 for (let index = 0; index < TRANSFER_HISTORY_TURN_COUNT; index++)
231231 yield* sink.write({ events: turnEvents(provider, index, false) });
232 const context = yield* Effect.context<Layer.Success<typeof services>>();
232 const context = yield* Effect.context<Layer.Success<typeof layerServices>>();
233233 const server = yield* Layer.build(
234 HttpRouter.serve(routes, { disableListenLog: true }).pipe(
234 HttpRouter.serve(layerRoutes, { disableListenLog: true }).pipe(
235235 Layer.provide(Layer.succeedContext(context)),
236236 Layer.provideMerge(
237237 NodeHttpServer.layer(NodeHttp.createServer, {
......@@ -334,7 +334,7 @@ it.live(
334334 reconnectSqlStatements: counter.count() - reconnectSqlStart,
335335 } satisfies TransferBudgetRun;
336336 }).pipe(
337 Effect.provide(Layer.fresh(services).pipe(Layer.provideMerge(NodeServices.layer))),
337 Effect.provide(Layer.fresh(layerServices).pipe(Layer.provideMerge(NodeServices.layer))),
338338 Effect.withTracer(counter.tracer),
339339 ),
340340 );
apps/server/scripts/measure-pr-preview.ts+4-4
......@@ -22,7 +22,7 @@ if (!repository || numbers.length === 0 || numbers.some((number) => !/^\d+$/.tes
2222
2323type Read = { graphqlRequests: number; restRequests: number; cost: number; debug: string };
2424const reads: Read[] = [];
25const measuredProcess = Layer.effect(
25const layerMeasuredProcess = Layer.effect(
2626 VcsProcess.VcsProcess,
2727 Effect.gen(function* () {
2828 const vcs = yield* VcsProcess.VcsProcess;
......@@ -47,9 +47,9 @@ const measuredProcess = Layer.effect(
4747 }),
4848).pipe(Layer.provide(VcsProcess.layer), Layer.provide(NodeServices.layer));
4949
50const services = GitHubPullRequestCli.layer.pipe(
50const layerServices = GitHubPullRequestCli.layer.pipe(
5151 Layer.provide(GitHubCli.layer),
52 Layer.provideMerge(measuredProcess),
52 Layer.provideMerge(layerMeasuredProcess),
5353);
5454
5555const decodeJson = Schema.decodeSync(Schema.fromJsonString(Schema.Unknown));
......@@ -122,7 +122,7 @@ for (const mode of ["detail", "preview"] as const) {
122122 }),
123123 );
124124 }),
125 ).pipe(Effect.provide(services)),
125 ).pipe(Effect.provide(layerServices)),
126126 );
127127 for (const row of rows) await Effect.runPromise(Console.log(encodeJson(row)));
128128}
apps/server/scripts/record-codex-app-server-replay-fixture.ts+6-6
......@@ -1145,7 +1145,7 @@ function makeRecorder({
11451145 });
11461146}
11471147
1148function makeCodexLayer({ recorder }: { readonly recorder: Recorder }) {
1148function layerCodex({ recorder }: { readonly recorder: Recorder }) {
11491149 const clientRequestMethodById = new Map<string, string>();
11501150 const serverRequestMethodById = new Map<string, string>();
11511151 const clientOptions: CodexClient.CodexAppServerClientOptions = {
......@@ -1465,7 +1465,7 @@ function runReplaySession({
14651465 return thread;
14661466 }).pipe(
14671467 Effect.provide(
1468 makeCodexLayer({
1468 layerCodex({
14691469 recorder,
14701470 }),
14711471 ),
......@@ -1486,7 +1486,7 @@ function runReplaySession({
14861486 yield* runTurnStep(client, thread.thread.id, secondStep);
14871487 }).pipe(
14881488 Effect.provide(
1489 makeCodexLayer({
1489 layerCodex({
14901490 recorder,
14911491 }),
14921492 ),
......@@ -1519,7 +1519,7 @@ function runReplaySession({
15191519 yield* runTurnStep(client, thread.thread.id, first);
15201520 yield* runTurnStep(client, thread.thread.id, second);
15211521 return thread.thread.id;
1522 }).pipe(Effect.provide(makeCodexLayer({ recorder })));
1522 }).pipe(Effect.provide(layerCodex({ recorder })));
15231523
15241524 yield* recorder.writeRecord({
15251525 type: "runtime_exit",
......@@ -1536,7 +1536,7 @@ function runReplaySession({
15361536 yield* revertCodexThread(client, threadId, rollback.numTurns);
15371537 yield* client.request("thread/resume", resumeParams);
15381538 yield* runTurnStep(client, threadId, after);
1539 }).pipe(Effect.provide(makeCodexLayer({ recorder })));
1539 }).pipe(Effect.provide(layerCodex({ recorder })));
15401540 return;
15411541 }
15421542
......@@ -1587,7 +1587,7 @@ function runReplaySession({
15871587 }
15881588 }).pipe(
15891589 Effect.provide(
1590 makeCodexLayer({
1590 layerCodex({
15911591 recorder,
15921592 }),
15931593 ),
apps/server/scripts/record-grok-acp-replay-fixture.ts+5-7
......@@ -40,10 +40,8 @@ import { provideDeterministicTestRuntime } from "../src/orchestration-v2/testkit
4040import { ORCHESTRATOR_REPLAY_FIXTURES } from "../src/orchestration-v2/testkit/fixtures/index.ts";
4141import { materializeFixtureInput } from "../src/orchestration-v2/testkit/fixtures/shared.ts";
4242import { runOrchestratorV2Scenario } from "../src/orchestration-v2/testkit/OrchestratorScenario.ts";
43import {
44 makeOrchestratorV2ReplayLayerWithRegistry,
45 makeReplayServerConfig,
46} from "../src/orchestration-v2/testkit/ProviderReplayHarness.ts";
43import { makeReplayServerConfig } from "../src/orchestration-v2/testkit/ProviderReplayHarness.ts";
44import * as ProviderReplayHarness from "../src/orchestration-v2/testkit/ProviderReplayHarness.ts";
4745import { checkpointWorkspace } from "../src/orchestration-v2/testkit/ReplayFixtureWorkspace.ts";
4846import { makeGrokAcpRuntime } from "../src/provider/acp/GrokAcpSupport.ts";
4947import { buildRuntimeInstructions } from "../src/provider/RuntimeInstructions.ts";
......@@ -444,7 +442,7 @@ const recordScenario = Effect.fn("recordGrokScenario")(function* (fixtureName: s
444442
445443 const tee = makeWireTee();
446444 const settings = { ...DEFAULT_GROK_SETTINGS, binaryPath: process.env.T3_GROK_BIN ?? "grok" };
447 const registryLayer = ProviderAdapterRegistry.makeLayerEffect(
445 const layerRegistry = ProviderAdapterRegistry.layerFromAdaptersEffect(
448446 Effect.gen(function* () {
449447 const childProcessSpawner = yield* ChildProcessSpawner.ChildProcessSpawner;
450448 const environment = yield* HostProcessEnvironment;
......@@ -532,9 +530,9 @@ const recordScenario = Effect.fn("recordGrokScenario")(function* (fixtureName: s
532530 );
533531 const result = yield* runOrchestratorV2Scenario(scenario, { afterSteps: waitForGrokIdle }).pipe(
534532 Effect.provide(
535 makeOrchestratorV2ReplayLayerWithRegistry(
533 ProviderReplayHarness.layerWithRegistry(
536534 scenario,
537 registryLayer,
535 layerRegistry,
538536 variant.runContinuationWorker === true ? { runContinuationWorker: true } : {},
539537 ),
540538 ),
apps/server/scripts/record-pi-rpc-replay-fixture.ts+4-4
......@@ -23,12 +23,12 @@ import { TestClock } from "effect/testing";
2323import { ChildProcess, ChildProcessSpawner } from "effect/process";
2424
2525import {
26 makePiProviderAdapterRegistryLayer,
2726 makePiRecordingSpawner,
2827 PI_REPLAY_ANY,
2928 PI_RPC_REPLAY_PROTOCOL,
3029 PiOrchestratorReplayHarness,
3130} from "../src/orchestration-v2/Adapters/PiAdapterV2.testkit.ts";
31import * as PiAdapterV2Testkit from "../src/orchestration-v2/Adapters/PiAdapterV2.testkit.ts";
3232import { PI_PROVIDER } from "../src/orchestration-v2/Adapters/PiAdapterV2.ts";
3333import * as IdAllocator from "../src/orchestration-v2/IdAllocator.ts";
3434import { provideDeterministicTestRuntime } from "../src/orchestration-v2/testkit/DeterministicRuntime.ts";
......@@ -251,7 +251,7 @@ const record = Effect.gen(function* () {
251251 scenario: fixture.name,
252252 entries: [],
253253 } satisfies ProviderReplayTranscript;
254 const recordingSpawner = Layer.effect(
254 const layerRecordingSpawner = Layer.effect(
255255 ChildProcessSpawner.ChildProcessSpawner,
256256 Effect.map(Effect.service(ChildProcessSpawner.ChildProcessSpawner), (live) =>
257257 makePiRecordingSpawner(live, entries),
......@@ -283,7 +283,7 @@ const record = Effect.gen(function* () {
283283 driver: PI_PROVIDER,
284284 decodeTranscript: Effect.succeed,
285285 makeProviderAdapterRegistryLayer: () =>
286 makePiProviderAdapterRegistryLayer({
286 PiAdapterV2Testkit.layer({
287287 scenario: fixture.name,
288288 binaryPath: piBinary,
289289 launchArgs,
......@@ -291,7 +291,7 @@ const record = Effect.gen(function* () {
291291 { name: "PI_CODING_AGENT_SESSION_DIR", value: sessionDir, sensitive: false },
292292 { name: "PI_SKIP_VERSION_CHECK", value: "1", sensitive: false },
293293 ],
294 spawner: recordingSpawner,
294 spawner: layerRecordingSpawner,
295295 }),
296296 },
297297 continuationOptions,
apps/server/scripts/verify-background-live.ts+4-4
......@@ -295,11 +295,11 @@ try {
295295 const ticket = Schema.decodeUnknownSync(Schema.Struct({ ticket: Schema.String }))(
296296 await ticketResponse.json(),
297297 );
298 const socket = Socket.layerWebSocket(
298 const layerSocket = Socket.layerWebSocket(
299299 `${origin.replace("http:", "ws:")}/ws?orchestrationProtocol=${ORCHESTRATION_PROTOCOL_VERSION}&wsTicket=${encodeURIComponent(ticket.ticket)}`,
300300 ).pipe(Layer.provide(Socket.layerWebSocketConstructorGlobal));
301 const protocol = RpcClient.layerProtocolSocket().pipe(
302 Layer.provide(socket),
301 const layerProtocol = RpcClient.layerProtocolSocket().pipe(
302 Layer.provide(layerSocket),
303303 Layer.provide(RpcSerialization.layerJson),
304304 );
305305 const threadId = ThreadId.make(NodeCrypto.randomUUID());
......@@ -483,7 +483,7 @@ try {
483483 .filter((message) => message.role === "assistant" && message.text.includes(secret))
484484 .map((message) => message.id),
485485 };
486 }).pipe(Effect.scoped, Effect.provide(protocol), Effect.timeout(timeoutMs));
486 }).pipe(Effect.scoped, Effect.provide(layerProtocol), Effect.timeout(timeoutMs));
487487 const finished = await Effect.runPromise(program, { signal: abort.signal });
488488 verdict = finished;
489489 // Check persistence through an actual clean server restart on the same SQLite DB.
apps/server/src/assets/AssetAccess.test.ts+40-40
......@@ -48,7 +48,7 @@ vi.mock("node:os", async (importOriginal) => {
4848 return { ...actual, homedir: vi.fn(actual.homedir) };
4949});
5050
51const configLayer = ServerConfig.ServerConfig.layerTest(process.cwd(), {
51const layerConfig = ServerConfig.ServerConfig.layerTest(process.cwd(), {
5252 prefix: "t3-asset-access-test-",
5353});
5454// A PNG header is enough for the dimension read: signature, then IHDR width and height.
......@@ -103,7 +103,7 @@ const oversizedScreenshotItem = {
103103 },
104104};
105105
106const testLayer = Layer.mergeAll(
106const layerTest = Layer.mergeAll(
107107 NodeHttpPlatform.layer,
108108 Layer.mock(Orchestrator.OrchestratorV2)({
109109 getTurnItem: ({ itemId }) =>
......@@ -115,14 +115,14 @@ const testLayer = Layer.mergeAll(
115115 : null,
116116 ),
117117 }),
118 configLayer,
118 layerConfig,
119119 WorkspacePaths.layer,
120120 ProjectFaviconResolver.layer.pipe(
121121 Layer.provide(WorkspacePaths.layer),
122122 Layer.provide(T3ProjectFileLoader.layer),
123123 ),
124 NativeAppIconResolver.layer.pipe(Layer.provide(configLayer)),
125 ServerSecretStore.layer.pipe(Layer.provide(configLayer)),
124 NativeAppIconResolver.layer.pipe(Layer.provide(layerConfig)),
125 ServerSecretStore.layer.pipe(Layer.provide(layerConfig)),
126126).pipe(Layer.provideMerge(NodeServices.layer));
127127
128128describe("AssetAccess", () => {
......@@ -198,7 +198,7 @@ describe("AssetAccess", () => {
198198 resource: { ...resource, itemId: oversizedScreenshotItem.id },
199199 }).pipe(Effect.flip);
200200 expect(oversized._tag).toBe("AssetWorkspaceAssetNotFoundError");
201 }).pipe(Effect.provide(testLayer)),
201 }).pipe(Effect.provide(layerTest)),
202202 );
203203
204204 it.effect("issues exact URLs for media and browser documents outside the workspace", () =>
......@@ -234,7 +234,7 @@ describe("AssetAccess", () => {
234234 expect(yield* resolveAsset(token, `../${name}`)).toBeNull();
235235 expect(yield* resolveAsset(`${token}tampered`, name)).toBeNull();
236236 }
237 }).pipe(Effect.provide(testLayer)),
237 }).pipe(Effect.provide(layerTest)),
238238 );
239239
240240 it.effect("reports pixel dimensions from an image header and nothing for other files", () =>
......@@ -258,7 +258,7 @@ describe("AssetAccess", () => {
258258 expect((yield* issue("shot.png")).imageDimensions).toEqual({ width: 1600, height: 900 });
259259 expect((yield* issue("clip.mp4")).imageDimensions).toBeUndefined();
260260 expect((yield* issue("broken.png")).imageDimensions).toBeUndefined();
261 }).pipe(Effect.provide(testLayer)),
261 }).pipe(Effect.provide(layerTest)),
262262 );
263263
264264 it.effect("resolves relative media paths from the thread workspace, including outside it", () =>
......@@ -284,7 +284,7 @@ describe("AssetAccess", () => {
284284 path: yield* fs.realPath(filePath),
285285 });
286286 }
287 }).pipe(Effect.provide(testLayer)),
287 }).pipe(Effect.provide(layerTest)),
288288 );
289289
290290 it.effect("resolves home-relative media paths independently of the workspace", () =>
......@@ -333,7 +333,7 @@ describe("AssetAccess", () => {
333333 } finally {
334334 homeSpy.mockRestore();
335335 }
336 }).pipe(Effect.provide(testLayer)),
336 }).pipe(Effect.provide(layerTest)),
337337 );
338338
339339 it.effect.skipIf(!symlinksSupported)(
......@@ -371,7 +371,7 @@ describe("AssetAccess", () => {
371371 },
372372 }).pipe(Effect.flip);
373373 expect(directoryError._tag).toBe("AssetWorkspaceAssetNotFoundError");
374 }).pipe(Effect.provide(testLayer)),
374 }).pipe(Effect.provide(layerTest)),
375375 );
376376
377377 it.effect.skipIf(!symlinksSupported)(
......@@ -403,7 +403,7 @@ describe("AssetAccess", () => {
403403 yield* fs.remove(filePath);
404404 yield* fs.symlink(replacementPath, filePath);
405405 expect(yield* resolveAsset(token, name)).toBeNull();
406 }).pipe(Effect.provide(testLayer)),
406 }).pipe(Effect.provide(layerTest)),
407407 );
408408
409409 it.effect.skipIf(!symlinksSupported)(
......@@ -442,7 +442,7 @@ describe("AssetAccess", () => {
442442 yield* fs.remove(filePath);
443443 yield* fs.rename(savedPath, filePath);
444444 }
445 }).pipe(Effect.provide(testLayer)),
445 }).pipe(Effect.provide(layerTest)),
446446 );
447447
448448 it.effect.skipIf(!symlinksSupported)(
......@@ -478,7 +478,7 @@ describe("AssetAccess", () => {
478478 Effect.provideService(FileSystem.FileSystem, swappingFileSystem),
479479 ),
480480 ).toBeNull();
481 }).pipe(Effect.provide(testLayer)),
481 }).pipe(Effect.provide(layerTest)),
482482 );
483483
484484 it.effect.skipIf(!symlinksSupported)(
......@@ -512,7 +512,7 @@ describe("AssetAccess", () => {
512512 expect(yield* openMediaFile(canonicalPath)).toBeNull();
513513 expect(opened).toBeDefined();
514514 expect(opened?.fd).toBe(-1);
515 }).pipe(Effect.provide(testLayer)),
515 }).pipe(Effect.provide(layerTest)),
516516 );
517517
518518 it.effect.skipIf(!symlinksSupported)(
......@@ -570,7 +570,7 @@ describe("AssetAccess", () => {
570570 Effect.provideService(FileSystem.FileSystem, swappingFileSystem),
571571 ),
572572 ).toBeNull();
573 }).pipe(Effect.provide(testLayer)),
573 }).pipe(Effect.provide(layerTest)),
574574 );
575575
576576 it.effect("keeps in-place edits readable but requires a new URL after atomic replacement", () =>
......@@ -620,7 +620,7 @@ describe("AssetAccess", () => {
620620 renewedSuffix.slice(renewedSeparator + 1),
621621 ),
622622 ).toBeNull();
623 }).pipe(Effect.provide(testLayer)),
623 }).pipe(Effect.provide(layerTest)),
624624 );
625625
626626 it.effect("issues workspace URLs that resolve the entry file and sibling assets", () =>
......@@ -661,7 +661,7 @@ describe("AssetAccess", () => {
661661 expect(yield* resolveAsset(token, "../secret.txt")).toBeNull();
662662 expect(yield* resolveAsset(token, ".env")).toBeNull();
663663 expect(yield* resolveAsset(`${token}tampered`, "report.html")).toBeNull();
664 }).pipe(Effect.provide(testLayer)),
664 }).pipe(Effect.provide(layerTest)),
665665 );
666666
667667 it.effect("rejects workspace files outside the authorized root", () =>
......@@ -695,7 +695,7 @@ describe("AssetAccess", () => {
695695 },
696696 });
697697 expect(error.cause).toBeInstanceOf(WorkspacePaths.WorkspacePathOutsideRootError);
698 }).pipe(Effect.provide(testLayer)),
698 }).pipe(Effect.provide(layerTest)),
699699 );
700700
701701 it.effect("issues draft workspace URLs without a thread", () =>
......@@ -729,7 +729,7 @@ describe("AssetAccess", () => {
729729 path: canonicalCssPath,
730730 });
731731 expect(yield* resolveAsset(token, "../secret.txt")).toBeNull();
732 }).pipe(Effect.provide(testLayer)),
732 }).pipe(Effect.provide(layerTest)),
733733 );
734734
735735 it.effect("serves absolute draft media files exactly, wherever they live", () =>
......@@ -760,7 +760,7 @@ describe("AssetAccess", () => {
760760 mimeType: "video/mp4",
761761 });
762762 expect(yield* resolveAsset(token, "other.mp4")).toBeNull();
763 }).pipe(Effect.provide(testLayer)),
763 }).pipe(Effect.provide(layerTest)),
764764 );
765765
766766 it.effect("falls back to the resource cwd for relative draft paths", () =>
......@@ -785,7 +785,7 @@ describe("AssetAccess", () => {
785785 kind: "file",
786786 path: canonicalHtmlPath,
787787 });
788 }).pipe(Effect.provide(testLayer)),
788 }).pipe(Effect.provide(layerTest)),
789789 );
790790
791791 it.effect("preserves non-missing canonical path failures when issuing asset URLs", () =>
......@@ -827,7 +827,7 @@ describe("AssetAccess", () => {
827827 },
828828 });
829829 expect(error.cause).toBe(cause);
830 }).pipe(Effect.provide(testLayer)),
830 }).pipe(Effect.provide(layerTest)),
831831 );
832832
833833 it.effect("issues exact workspace URLs for image previews", () =>
......@@ -863,7 +863,7 @@ describe("AssetAccess", () => {
863863 });
864864 expect(yield* resolveAsset(token, "other.png")).toBeNull();
865865 expect(yield* resolveAsset(token, "../icon.png")).toBeNull();
866 }).pipe(Effect.provide(testLayer)),
866 }).pipe(Effect.provide(layerTest)),
867867 );
868868
869869 it.effect("previews workspace files with literal fragment characters in their paths", () =>
......@@ -906,7 +906,7 @@ describe("AssetAccess", () => {
906906 workspaceRoot: root,
907907 }).pipe(Effect.flip);
908908 expect(error).toBeInstanceOf(AssetPreviewTypeValidationError);
909 }).pipe(Effect.provide(testLayer)),
909 }).pipe(Effect.provide(layerTest)),
910910 );
911911
912912 it.effect("issues exact attachment capabilities by attachment id", () =>
......@@ -930,7 +930,7 @@ describe("AssetAccess", () => {
930930 kind: "file",
931931 path: attachmentPath,
932932 });
933 }).pipe(Effect.provide(testLayer)),
933 }).pipe(Effect.provide(layerTest)),
934934 );
935935
936936 it.effect("serves video attachments inline", () =>
......@@ -962,7 +962,7 @@ describe("AssetAccess", () => {
962962 fileName: "demo.mp4",
963963 mimeType: "video/mp4",
964964 });
965 }).pipe(Effect.provide(testLayer)),
965 }).pipe(Effect.provide(layerTest)),
966966 );
967967 it.effect("issues signed native application icon capabilities", () =>
968968 Effect.gen(function* () {
......@@ -977,7 +977,7 @@ describe("AssetAccess", () => {
977977 new RegExp(`^${ASSET_ROUTE_PREFIX}/[^/]+/native-app-icon\\.png$`, "u"),
978978 );
979979 expect(result.expiresAt).toBeGreaterThan(0);
980 }).pipe(Effect.provide(testLayer)),
980 }).pipe(Effect.provide(layerTest)),
981981 );
982982
983983 it.effect("serves document attachments inline when a viewer requests it", () =>
......@@ -1010,7 +1010,7 @@ describe("AssetAccess", () => {
10101010 fileName: "report.pdf",
10111011 mimeType: "application/pdf",
10121012 });
1013 }).pipe(Effect.provide(testLayer)),
1013 }).pipe(Effect.provide(layerTest)),
10141014 );
10151015
10161016 it.effect("serves audio previews with their stored format and keeps saving explicit", () =>
......@@ -1044,7 +1044,7 @@ describe("AssetAccess", () => {
10441044 ...(disposition === "attachment" ? { download: true } : {}),
10451045 });
10461046 }
1047 }).pipe(Effect.provide(testLayer)),
1047 }).pipe(Effect.provide(layerTest)),
10481048 );
10491049
10501050 it.effect("keeps inline requests for other attachment types as downloads", () =>
......@@ -1072,7 +1072,7 @@ describe("AssetAccess", () => {
10721072 expect(
10731073 yield* resolveAsset(suffix.slice(0, separatorIndex), suffix.slice(separatorIndex + 1)),
10741074 ).toMatchObject({ kind: "file", path: attachmentPath, download: true });
1075 }).pipe(Effect.provide(testLayer)),
1075 }).pipe(Effect.provide(layerTest)),
10761076 );
10771077 it.effect("issues project favicon capabilities with a signed fallback", () =>
10781078 Effect.gen(function* () {
......@@ -1129,7 +1129,7 @@ describe("AssetAccess", () => {
11291129 fallbackSuffix.slice(fallbackSeparatorIndex + 1),
11301130 ),
11311131 ).toBeNull();
1132 }).pipe(Effect.provide(testLayer)),
1132 }).pipe(Effect.provide(layerTest)),
11331133 );
11341134
11351135 it.effect("issues project favicon capabilities for a saved override", () =>
......@@ -1150,7 +1150,7 @@ describe("AssetAccess", () => {
11501150
11511151 expect(result.sourcePath).toBe(path.join("brand", "custom.svg"));
11521152 expect(result.relativeUrl).toMatch(/\/v[0-9a-f]{64}-custom\.svg$/);
1153 }).pipe(Effect.provide(testLayer)),
1153 }).pipe(Effect.provide(layerTest)),
11541154 );
11551155
11561156 it.effect("issues an exact capability for a saved favicon outside the workspace", () =>
......@@ -1188,7 +1188,7 @@ describe("AssetAccess", () => {
11881188 );
11891189 expect(tamperedSuffixResult).toEqual({ kind: "file", path: canonicalPath });
11901190 expect(tamperedSuffixResult).not.toEqual({ kind: "file", path: canonicalSiblingPath });
1191 }).pipe(Effect.provide(testLayer)),
1191 }).pipe(Effect.provide(layerTest)),
11921192 );
11931193
11941194 it.effect("ignores a client favicon path hint", () =>
......@@ -1209,7 +1209,7 @@ describe("AssetAccess", () => {
12091209
12101210 expect(result.sourcePath).toBe(path.join("brand", "saved.svg"));
12111211 expect(result.relativeUrl).toMatch(/\/v[0-9a-f]{64}-saved\.svg$/);
1212 }).pipe(Effect.provide(testLayer)),
1212 }).pipe(Effect.provide(layerTest)),
12131213 );
12141214
12151215 it.effect("keeps automatic favicon resolution separate from a saved override", () =>
......@@ -1229,7 +1229,7 @@ describe("AssetAccess", () => {
12291229
12301230 expect(result.sourcePath).toBe("favicon.svg");
12311231 expect(result.relativeUrl).toMatch(/\/v[0-9a-f]{64}-favicon\.svg$/);
1232 }).pipe(Effect.provide(testLayer)),
1232 }).pipe(Effect.provide(layerTest)),
12331233 );
12341234
12351235 it.effect("rejects a resolved project favicon with a non-image extension", () =>
......@@ -1247,7 +1247,7 @@ describe("AssetAccess", () => {
12471247 }).pipe(Effect.flip);
12481248
12491249 expect(error).toBeInstanceOf(AssetPreviewTypeValidationError);
1250 }).pipe(Effect.provide(testLayer)),
1250 }).pipe(Effect.provide(layerTest)),
12511251 );
12521252
12531253 it.effect("buckets project favicon expiry after content hashing", () =>
......@@ -1272,7 +1272,7 @@ describe("AssetAccess", () => {
12721272 }).pipe(Effect.provideService(Crypto.Crypto, crossingCrypto));
12731273
12741274 expect(result.expiresAt).toBe(3 * bucketMs);
1275 }).pipe(Effect.provide(testLayer)),
1275 }).pipe(Effect.provide(layerTest)),
12761276 );
12771277
12781278 it.effect("preserves structured project favicon resolution causes", () =>
......@@ -1306,7 +1306,7 @@ describe("AssetAccess", () => {
13061306 expect(error.message).toBe("Failed to resolve project favicon.");
13071307 expect(error._tag).toBe("AssetProjectFaviconResolutionError");
13081308 expect(error.cause).toBe(resolutionCause);
1309 }).pipe(Effect.provide(testLayer)),
1309 }).pipe(Effect.provide(layerTest)),
13101310 );
13111311
13121312 it.effect("serves GitHub-hosted pull request media through the repository's credential", () =>
......@@ -1362,6 +1362,6 @@ describe("AssetAccess", () => {
13621362 const encoded = yield* Schema.encodeEffect(Schema.fromJsonString(AssetAccessError))(error);
13631363 expect(encoded).not.toContain(url);
13641364 }
1365 }).pipe(Effect.provide(testLayer)),
1365 }).pipe(Effect.provide(layerTest)),
13661366 );
13671367});
apps/server/src/assets/AttachmentUpload.test.ts+11-11
......@@ -24,7 +24,7 @@ import {
2424 validateAttachmentUploadToken,
2525} from "./AttachmentUpload.ts";
2626
27const testLayer = ServerSecretStore.layer.pipe(
27const layerTest = ServerSecretStore.layer.pipe(
2828 Layer.provideMerge(ServerConfig.layerTest(process.cwd(), { prefix: "t3-attachment-upload-" })),
2929 Layer.provideMerge(NodeServices.layer),
3030);
......@@ -62,7 +62,7 @@ describe("AttachmentUpload", () => {
6262 mimeType: "image/png",
6363 sizeBytes: 6,
6464 });
65 }).pipe(Effect.provide(testLayer)),
65 }).pipe(Effect.provide(layerTest)),
6666 );
6767
6868 it.effect("rejects tampered and malformed upload tokens", () =>
......@@ -74,7 +74,7 @@ describe("AttachmentUpload", () => {
7474 expect(yield* validateAttachmentUploadToken(`${payload}x.${signature}`)).toBeNull();
7575 expect(yield* validateAttachmentUploadToken(`${token}.extra`)).toBeNull();
7676 expect(yield* validateAttachmentUploadToken("garbage")).toBeNull();
77 }).pipe(Effect.provide(testLayer)),
77 }).pipe(Effect.provide(layerTest)),
7878 );
7979
8080 it.effect("accepts unexpired image upload tokens issued before file support", () =>
......@@ -99,7 +99,7 @@ describe("AttachmentUpload", () => {
9999 type: "image",
100100 attachmentId: issued.attachmentId,
101101 });
102 }).pipe(Effect.provide(testLayer)),
102 }).pipe(Effect.provide(layerTest)),
103103 );
104104
105105 it.effect("rejects expired upload tokens", () =>
......@@ -109,7 +109,7 @@ describe("AttachmentUpload", () => {
109109
110110 yield* TestClock.adjust("11 minutes");
111111 expect(yield* validateAttachmentUploadToken(token)).toBeNull();
112 }).pipe(Effect.provide(testLayer)),
112 }).pipe(Effect.provide(layerTest)),
113113 );
114114
115115 it.effect("removes expired pending uploads while issuing a new upload URL", () =>
......@@ -124,7 +124,7 @@ describe("AttachmentUpload", () => {
124124 yield* issueAttachmentUploadUrl(uploadInput);
125125
126126 expect(NodeFS.existsSync(stalePath)).toBe(false);
127 }).pipe(Effect.provide(testLayer)),
127 }).pipe(Effect.provide(layerTest)),
128128 );
129129
130130 it.effect("stores the expected bytes without leaving temporary files", () =>
......@@ -148,7 +148,7 @@ describe("AttachmentUpload", () => {
148148 expect(
149149 NodeFS.readdirSync(config.attachmentsDir).filter((entry) => entry.endsWith(".part")),
150150 ).toEqual([]);
151 }).pipe(Effect.provide(testLayer)),
151 }).pipe(Effect.provide(layerTest)),
152152 );
153153
154154 it.effect("streams generic files to a path with their original extension", () =>
......@@ -179,7 +179,7 @@ describe("AttachmentUpload", () => {
179179
180180 yield* deletePendingAttachment(issued.attachmentId);
181181 expect(NodeFS.readdirSync(config.attachmentsDir)).toEqual([]);
182 }).pipe(Effect.provide(testLayer)),
182 }).pipe(Effect.provide(layerTest)),
183183 );
184184
185185 it.effect("removes partial streamed uploads that exceed their signed size", () =>
......@@ -197,7 +197,7 @@ describe("AttachmentUpload", () => {
197197 status: 400,
198198 });
199199 expect(NodeFS.readdirSync(config.attachmentsDir)).toEqual([]);
200 }).pipe(Effect.provide(testLayer)),
200 }).pipe(Effect.provide(layerTest)),
201201 );
202202
203203 it.effect("removes partial streamed uploads when the upload is interrupted", () =>
......@@ -227,7 +227,7 @@ describe("AttachmentUpload", () => {
227227
228228 yield* Fiber.interrupt(upload);
229229 expect(NodeFS.readdirSync(config.attachmentsDir)).toEqual([]);
230 }).pipe(Effect.provide(testLayer)),
230 }).pipe(Effect.provide(layerTest)),
231231 );
232232
233233 it.effect("deletes pending uploads without deleting thread-owned copies", () =>
......@@ -245,6 +245,6 @@ describe("AttachmentUpload", () => {
245245
246246 expect(NodeFS.existsSync(pendingPath)).toBe(false);
247247 expect(NodeFS.existsSync(claimedPath)).toBe(true);
248 }).pipe(Effect.provide(testLayer)),
248 }).pipe(Effect.provide(layerTest)),
249249 );
250250});
apps/server/src/assets/NativeAppIconResolver.test.ts+5-5
......@@ -39,15 +39,15 @@ describe("resolveNativeAppIcon", () => {
3939 return emptyProcessHandle();
4040 }),
4141 );
42 const configLayer = ServerConfig.ServerConfig.layerTest(process.cwd(), {
42 const layerConfig = ServerConfig.ServerConfig.layerTest(process.cwd(), {
4343 prefix: "t3-native-app-icon-test-",
4444 });
45 const dependencies = Layer.mergeAll(
46 configLayer,
45 const layerDependencies = Layer.mergeAll(
46 layerConfig,
4747 Layer.succeed(HostProcessPlatform, "darwin"),
4848 Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner),
4949 ).pipe(Layer.provideMerge(NodeServices.layer));
50 const testLayer = NativeAppIconResolver.layer.pipe(Layer.provide(dependencies));
50 const layerTest = NativeAppIconResolver.layer.pipe(Layer.provide(layerDependencies));
5151 const app = { _tag: "display-name", displayName: "Review * App" } as const;
5252
5353 return Effect.gen(function* () {
......@@ -70,6 +70,6 @@ describe("resolveNativeAppIcon", () => {
7070 expect(commands).toHaveLength(257);
7171 expect(yield* resolver.resolve(app)).toBeNull();
7272 expect(commands).toHaveLength(258);
73 }).pipe(Effect.provide(testLayer));
73 }).pipe(Effect.provide(layerTest));
7474 });
7575});
apps/server/src/auth/EnvironmentAuth.test.ts+20-20
......@@ -10,7 +10,7 @@ import * as SqlClient from "effect/sql/SqlClient";
1010import * as ServerConfig from "../config.ts";
1111import * as ServerEnvironment from "../environment/ServerEnvironment.ts";
1212import * as PersistenceErrors from "../persistence/Errors.ts";
13import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts";
13import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts";
1414import * as PairingGrantStore from "./PairingGrantStore.ts";
1515import * as EnvironmentAuth from "./EnvironmentAuth.ts";
1616
......@@ -22,7 +22,7 @@ const TEST_SERVER_PORT = 13_773;
2222const isPairingCredentialIssueError = Schema.is(PairingGrantStore.PairingCredentialIssueError);
2323const isPersistenceSqlError = Schema.is(PersistenceErrors.PersistenceSqlError);
2424
25const makeServerConfigLayer = (overrides?: Partial<ServerConfig.ServerConfig["Service"]>) =>
25const layerServerConfig = (overrides?: Partial<ServerConfig.ServerConfig["Service"]>) =>
2626 Layer.effect(
2727 ServerConfig.ServerConfig,
2828 Effect.gen(function* () {
......@@ -37,12 +37,12 @@ const makeServerConfigLayer = (overrides?: Partial<ServerConfig.ServerConfig["Se
3737 }),
3838 ).pipe(Layer.provide(ServerConfig.layerTest(process.cwd(), { prefix: "t3-auth-server-test-" })));
3939
40const makeEnvironmentAuthLayer = (overrides?: Partial<ServerConfig.ServerConfig["Service"]>) =>
40const layerEnvironmentAuth = (overrides?: Partial<ServerConfig.ServerConfig["Service"]>) =>
4141 EnvironmentAuth.layer.pipe(
42 Layer.provideMerge(SqlitePersistenceMemory),
42 Layer.provideMerge(SqlitePersistence.layerMemory),
4343 Layer.provide(ServerSecretStore.layer),
44 Layer.provide(ServerEnvironment.identityLayer),
45 Layer.provide(makeServerConfigLayer(overrides)),
44 Layer.provide(ServerEnvironment.layerIdentity),
45 Layer.provide(layerServerConfig(overrides)),
4646 );
4747
4848const makeCookieRequest = (
......@@ -105,7 +105,7 @@ it.layer(NodeServices.layer)("EnvironmentAuth.layer", (it) => {
105105 expect(authenticated.scopes).toEqual(["orchestration:read"]);
106106 }).pipe(
107107 Effect.provide(
108 makeEnvironmentAuthLayer({
108 layerEnvironmentAuth({
109109 mode: "web",
110110 devUrl: new URL("http://127.0.0.1:5173"),
111111 devAuthToken: Redacted.make("reusable-dev-auth-token-that-is-long-enough"),
......@@ -141,7 +141,7 @@ it.layer(NodeServices.layer)("EnvironmentAuth.layer", (it) => {
141141 expect(error._tag).toBe("ServerAuthInvalidCredentialError");
142142 }).pipe(
143143 Effect.provide(
144 makeEnvironmentAuthLayer({
144 layerEnvironmentAuth({
145145 mode: "web",
146146 devUrl: new URL("http://127.0.0.1:5173"),
147147 devAuthToken: Redacted.make("reusable-dev-auth-token-that-is-long-enough"),
......@@ -167,7 +167,7 @@ it.layer(NodeServices.layer)("EnvironmentAuth.layer", (it) => {
167167 }
168168 }).pipe(
169169 Effect.provide(
170 makeEnvironmentAuthLayer({
170 layerEnvironmentAuth({
171171 mode: "web",
172172 devUrl: new URL("http://127.0.0.1:5173"),
173173 devAuthToken: Redacted.make("reusable-dev-auth-token-that-is-long-enough"),
......@@ -216,7 +216,7 @@ it.layer(NodeServices.layer)("EnvironmentAuth.layer", (it) => {
216216 expect((yield* sessions.verify(token)).subject).toBe("reusable-dev-token");
217217 }).pipe(
218218 Effect.provide(
219 makeEnvironmentAuthLayer({
219 layerEnvironmentAuth({
220220 mode: "web",
221221 devUrl: new URL("http://127.0.0.1:5173"),
222222 devAuthToken: Redacted.make("reusable-dev-auth-token-that-is-long-enough"),
......@@ -244,7 +244,7 @@ it.layer(NodeServices.layer)("EnvironmentAuth.layer", (it) => {
244244 ).toMatchObject({ authenticated: true, scopes: AuthAdministrativeScopes });
245245 }).pipe(
246246 Effect.provide(
247 makeEnvironmentAuthLayer({
247 layerEnvironmentAuth({
248248 mode: "web",
249249 devUrl: new URL("http://127.0.0.1:5173"),
250250 devAuthToken: Redacted.make("reusable-dev-auth-token-that-is-long-enough"),
......@@ -279,7 +279,7 @@ it.layer(NodeServices.layer)("EnvironmentAuth.layer", (it) => {
279279 }
280280 }).pipe(
281281 Effect.provide(
282 makeEnvironmentAuthLayer({
282 layerEnvironmentAuth({
283283 mode: "web",
284284 devUrl: new URL("http://127.0.0.1:5173"),
285285 devAuthToken: Redacted.make("reusable-dev-auth-token-that-is-long-enough"),
......@@ -336,7 +336,7 @@ it.layer(NodeServices.layer)("EnvironmentAuth.layer", (it) => {
336336 "relay:read",
337337 ]);
338338 expect(verified.subject).toBe("one-time-token");
339 }).pipe(Effect.provide(makeEnvironmentAuthLayer())),
339 }).pipe(Effect.provide(layerEnvironmentAuth())),
340340 );
341341
342342 it.effect("prefers a bearer token over a stale legacy cookie", () =>
......@@ -350,7 +350,7 @@ it.layer(NodeServices.layer)("EnvironmentAuth.layer", (it) => {
350350 } as never);
351351
352352 expect(verified.sessionId).toBe(bearer.sessionId);
353 }).pipe(Effect.provide(makeEnvironmentAuthLayer({ mode: "web", host: "192.168.1.50" }))),
353 }).pipe(Effect.provide(layerEnvironmentAuth({ mode: "web", host: "192.168.1.50" }))),
354354 );
355355
356356 it.effect("does not exchange ordinary pairing grants for administrative access tokens", () =>
......@@ -367,7 +367,7 @@ it.layer(NodeServices.layer)("EnvironmentAuth.layer", (it) => {
367367 .pipe(Effect.flip);
368368
369369 expect(error._tag).toBe("ServerAuthScopeNotGrantedError");
370 }).pipe(Effect.provide(makeEnvironmentAuthLayer())),
370 }).pipe(Effect.provide(layerEnvironmentAuth())),
371371 );
372372
373373 it.effect("inherits a constrained pairing grant when token exchange omits scope", () =>
......@@ -384,7 +384,7 @@ it.layer(NodeServices.layer)("EnvironmentAuth.layer", (it) => {
384384 );
385385
386386 expect(token.scope).toBe("orchestration:read");
387 }).pipe(Effect.provide(makeEnvironmentAuthLayer())),
387 }).pipe(Effect.provide(layerEnvironmentAuth())),
388388 );
389389
390390 it.effect("rotates desktop bearer sessions without accumulating authorized clients", () =>
......@@ -445,7 +445,7 @@ it.layer(NodeServices.layer)("EnvironmentAuth.layer", (it) => {
445445 expect(active.map((entry) => entry.sessionId)).toContain(pairedSession.sessionId);
446446 }).pipe(
447447 Effect.provide(
448 makeEnvironmentAuthLayer({
448 layerEnvironmentAuth({
449449 desktopBootstrapToken: "desktop-bootstrap-token",
450450 }),
451451 ),
......@@ -463,7 +463,7 @@ it.layer(NodeServices.layer)("EnvironmentAuth.layer", (it) => {
463463 expect(
464464 listedPairingLinks.find((pairingLink) => pairingLink.id === pairingCredential.id)?.subject,
465465 ).toBe("one-time-token");
466 }).pipe(Effect.provide(makeEnvironmentAuthLayer())),
466 }).pipe(Effect.provide(layerEnvironmentAuth())),
467467 );
468468
469469 it.effect("issues startup pairing URLs that bootstrap administrative sessions", () =>
......@@ -497,7 +497,7 @@ it.layer(NodeServices.layer)("EnvironmentAuth.layer", (it) => {
497497 "relay:write",
498498 ]);
499499 expect(verified.subject).toBe("administrative-bootstrap");
500 }).pipe(Effect.provide(makeEnvironmentAuthLayer())),
500 }).pipe(Effect.provide(layerEnvironmentAuth())),
501501 );
502502
503503 it.effect(
......@@ -566,7 +566,7 @@ it.layer(NodeServices.layer)("EnvironmentAuth.layer", (it) => {
566566 expect(clientsAfterRevoke[0]?.sessionId).toBe(administrativeSession.sessionId);
567567 }).pipe(
568568 Effect.provide(
569 makeEnvironmentAuthLayer({
569 layerEnvironmentAuth({
570570 desktopBootstrapToken: "desktop-bootstrap-token",
571571 }),
572572 ),
apps/server/src/auth/EnvironmentAuth.ts+4-4
......@@ -1125,9 +1125,9 @@ export const layer = Layer.effect(EnvironmentAuth, make).pipe(
11251125 Layer.provideMerge(EnvironmentAuthPolicy.layer),
11261126);
11271127
1128const storageLayer = Layer.mergeAll(ServerSecretStore.layer, SqlitePersistence.layerConfig);
1128const layerStorage = Layer.mergeAll(ServerSecretStore.layer, SqlitePersistence.layerConfig);
11291129
1130export const runtimeLayer = layer.pipe(
1131 Layer.provideMerge(storageLayer),
1132 Layer.provideMerge(ServerEnvironment.identityLayer),
1130export const layerRuntime = layer.pipe(
1131 Layer.provideMerge(layerStorage),
1132 Layer.provideMerge(ServerEnvironment.layerIdentity),
11331133);
apps/server/src/auth/EnvironmentAuthAdmin.test.ts+9-9
......@@ -5,12 +5,12 @@ import * as Layer from "effect/Layer";
55
66import * as ServerConfig from "../config.ts";
77import * as ServerEnvironment from "../environment/ServerEnvironment.ts";
8import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts";
8import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts";
99import * as EnvironmentAuth from "./EnvironmentAuth.ts";
1010import * as ServerSecretStore from "./ServerSecretStore.ts";
1111import * as SessionStore from "./SessionStore.ts";
1212
13const makeServerConfigLayer = (
13const layerServerConfig = (
1414 overrides?: Partial<Pick<ServerConfig.ServerConfig["Service"], "desktopBootstrapToken">>,
1515) =>
1616 Layer.effect(
......@@ -30,14 +30,14 @@ const makeServerConfigLayer = (
3030 ),
3131 );
3232
33const makeEnvironmentAuthLayer = (
33const layerEnvironmentAuth = (
3434 overrides?: Partial<Pick<ServerConfig.ServerConfig["Service"], "desktopBootstrapToken">>,
3535) =>
3636 EnvironmentAuth.layer.pipe(
3737 Layer.provideMerge(ServerSecretStore.layer),
38 Layer.provideMerge(SqlitePersistenceMemory),
39 Layer.provide(ServerEnvironment.identityLayer),
40 Layer.provide(makeServerConfigLayer(overrides)),
38 Layer.provideMerge(SqlitePersistence.layerMemory),
39 Layer.provide(ServerEnvironment.layerIdentity),
40 Layer.provide(layerServerConfig(overrides)),
4141 );
4242
4343it.layer(NodeServices.layer)("EnvironmentAuth administrative operations", (it) => {
......@@ -62,7 +62,7 @@ it.layer(NodeServices.layer)("EnvironmentAuth administrative operations", (it) =
6262 expect(listedBeforeRevoke[0]).not.toHaveProperty("credential");
6363 expect(revoked).toBe(true);
6464 expect(listedAfterRevoke).toHaveLength(0);
65 }).pipe(Effect.provide(makeEnvironmentAuthLayer())),
65 }).pipe(Effect.provide(layerEnvironmentAuth())),
6666 );
6767
6868 it.effect("issues bearer access token sessions without exposing raw tokens", () =>
......@@ -108,7 +108,7 @@ it.layer(NodeServices.layer)("EnvironmentAuth administrative operations", (it) =
108108 expect("token" in (listedBeforeRevoke[0] ?? {})).toBe(false);
109109 expect(revoked).toBe(true);
110110 expect(listedAfterRevoke).toHaveLength(0);
111 }).pipe(Effect.provide(makeEnvironmentAuthLayer())),
111 }).pipe(Effect.provide(layerEnvironmentAuth())),
112112 );
113113
114114 it.effect("surfaces lastConnectedAt through the listed session view", () =>
......@@ -125,6 +125,6 @@ it.layer(NodeServices.layer)("EnvironmentAuth administrative operations", (it) =
125125
126126 expect(beforeConnect[0]?.lastConnectedAt).toBeNull();
127127 expect(afterConnect[0]?.lastConnectedAt).not.toBeNull();
128 }).pipe(Effect.provide(makeEnvironmentAuthLayer())),
128 }).pipe(Effect.provide(layerEnvironmentAuth())),
129129 );
130130});
apps/server/src/auth/EnvironmentAuthPolicy.test.ts+9-11
......@@ -7,11 +7,9 @@ import * as ServerConfig from "../config.ts";
77import * as ServerEnvironment from "../environment/ServerEnvironment.ts";
88import * as EnvironmentAuthPolicy from "./EnvironmentAuthPolicy.ts";
99
10const makeEnvironmentAuthPolicyLayer = (
11 overrides?: Partial<ServerConfig.ServerConfig["Service"]>,
12) =>
10const layerEnvironmentAuthPolicy = (overrides?: Partial<ServerConfig.ServerConfig["Service"]>) =>
1311 EnvironmentAuthPolicy.layer.pipe(
14 Layer.provide(ServerEnvironment.identityLayer),
12 Layer.provide(ServerEnvironment.layerIdentity),
1513 Layer.provide(
1614 Layer.effect(
1715 ServerConfig.ServerConfig,
......@@ -42,7 +40,7 @@ it.layer(NodeServices.layer)("EnvironmentAuthPolicy.layer", (it) => {
4240 expect(descriptor.sessionCookieName).toBe("t3_session_3773");
4341 }).pipe(
4442 Effect.provide(
45 makeEnvironmentAuthPolicyLayer({
43 layerEnvironmentAuthPolicy({
4644 mode: "desktop",
4745 port: 3773,
4846 }),
......@@ -58,7 +56,7 @@ it.layer(NodeServices.layer)("EnvironmentAuthPolicy.layer", (it) => {
5856 expect(descriptor.sessionCookieName).toBe("t3_session_3774");
5957 }).pipe(
6058 Effect.provide(
61 makeEnvironmentAuthPolicyLayer({
59 layerEnvironmentAuthPolicy({
6260 mode: "desktop",
6361 port: 3774,
6462 }),
......@@ -75,7 +73,7 @@ it.layer(NodeServices.layer)("EnvironmentAuthPolicy.layer", (it) => {
7573 expect(descriptor.bootstrapMethods).toEqual(["desktop-bootstrap", "one-time-token"]);
7674 }).pipe(
7775 Effect.provide(
78 makeEnvironmentAuthPolicyLayer({
76 layerEnvironmentAuthPolicy({
7977 mode: "desktop",
8078 host: "0.0.0.0",
8179 }),
......@@ -93,7 +91,7 @@ it.layer(NodeServices.layer)("EnvironmentAuthPolicy.layer", (it) => {
9391 expect(descriptor.sessionCookieName).toMatch(/^t3_session_3773_[a-f0-9]{12}$/);
9492 }).pipe(
9593 Effect.provide(
96 makeEnvironmentAuthPolicyLayer({
94 layerEnvironmentAuthPolicy({
9795 mode: "web",
9896 host: "127.0.0.1",
9997 port: 3773,
......@@ -112,7 +110,7 @@ it.layer(NodeServices.layer)("EnvironmentAuthPolicy.layer", (it) => {
112110 expect(descriptor.sessionCookieName).toMatch(/^t3_session_[a-f0-9]{12}$/);
113111 }).pipe(
114112 Effect.provide(
115 makeEnvironmentAuthPolicyLayer({
113 layerEnvironmentAuthPolicy({
116114 mode: "web",
117115 host: "0.0.0.0",
118116 }),
......@@ -129,7 +127,7 @@ it.layer(NodeServices.layer)("EnvironmentAuthPolicy.layer", (it) => {
129127 expect(descriptor.sessionCookieName).toMatch(/^t3_session_5775_[a-f0-9]{12}$/);
130128 }).pipe(
131129 Effect.provide(
132 makeEnvironmentAuthPolicyLayer({
130 layerEnvironmentAuthPolicy({
133131 mode: "web",
134132 host: "0.0.0.0",
135133 port: 5775,
......@@ -148,7 +146,7 @@ it.layer(NodeServices.layer)("EnvironmentAuthPolicy.layer", (it) => {
148146 expect(descriptor.sessionCookieName).toMatch(/^t3_session_[a-f0-9]{12}$/);
149147 }).pipe(
150148 Effect.provide(
151 makeEnvironmentAuthPolicyLayer({
149 layerEnvironmentAuthPolicy({
152150 mode: "web",
153151 host: "192.168.1.50",
154152 }),
apps/server/src/auth/PairingGrantStore.test.ts+16-16
......@@ -10,10 +10,10 @@ import * as TestClock from "effect/testing/TestClock";
1010import * as ServerConfig from "../config.ts";
1111import * as AuthPairingLinks from "../persistence/AuthPairingLinks.ts";
1212import { PersistenceSqlError } from "../persistence/Errors.ts";
13import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts";
13import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts";
1414import * as PairingGrantStore from "./PairingGrantStore.ts";
1515
16const makeServerConfigLayer = (
16const layerServerConfig = (
1717 overrides?: Partial<Pick<ServerConfig.ServerConfig["Service"], "desktopBootstrapToken">>,
1818) =>
1919 Layer.effect(
......@@ -29,15 +29,15 @@ const makeServerConfigLayer = (
2929 Layer.provide(ServerConfig.layerTest(process.cwd(), { prefix: "t3-auth-bootstrap-test-" })),
3030 );
3131
32const makePairingGrantStoreLayer = (
32const layerPairingGrantStore = (
3333 overrides?: Partial<Pick<ServerConfig.ServerConfig["Service"], "desktopBootstrapToken">>,
3434) =>
3535 PairingGrantStore.layer.pipe(
36 Layer.provide(SqlitePersistenceMemory),
37 Layer.provide(makeServerConfigLayer(overrides)),
36 Layer.provide(SqlitePersistence.layerMemory),
37 Layer.provide(layerServerConfig(overrides)),
3838 );
3939
40const makePairingGrantStoreTestLayer = (
40const layerPairingGrantStoreTest = (
4141 overrides: Partial<AuthPairingLinks.AuthPairingLinkRepository["Service"]>,
4242) =>
4343 Layer.effect(PairingGrantStore.PairingGrantStore, PairingGrantStore.make).pipe(
......@@ -54,7 +54,7 @@ const makePairingGrantStoreTestLayer = (
5454 }),
5555 ),
5656 ),
57 Layer.provide(makeServerConfigLayer()),
57 Layer.provide(layerServerConfig()),
5858 );
5959
6060it.layer(NodeServices.layer)("PairingGrantStore.layer", (it) => {
......@@ -64,7 +64,7 @@ it.layer(NodeServices.layer)("PairingGrantStore.layer", (it) => {
6464 const issued = yield* bootstrapCredentials.issueOneTimeToken();
6565
6666 expect(issued.credential).toMatch(/^[23456789ABCDEFGHJKLMNPQRSTUVWXYZ]{12}$/);
67 }).pipe(Effect.provide(makePairingGrantStoreLayer())),
67 }).pipe(Effect.provide(layerPairingGrantStore())),
6868 );
6969
7070 it.effect("issues one-time bootstrap tokens that can only be consumed once", () =>
......@@ -87,7 +87,7 @@ it.layer(NodeServices.layer)("PairingGrantStore.layer", (it) => {
8787 expect(issued.label).toBe("Julius iPhone");
8888 expect(second._tag).toBe("UnknownBootstrapCredentialError");
8989 expect(second.message).toContain("Unknown bootstrap credential");
90 }).pipe(Effect.provide(makePairingGrantStoreLayer())),
90 }).pipe(Effect.provide(layerPairingGrantStore())),
9191 );
9292
9393 it.effect("atomically consumes a one-time token when multiple requests race", () =>
......@@ -112,7 +112,7 @@ it.layer(NodeServices.layer)("PairingGrantStore.layer", (it) => {
112112 expect(failure.failure._tag).toBe("UnknownBootstrapCredentialError");
113113 expect(failure.failure.message).toContain("Unknown bootstrap credential");
114114 }
115 }).pipe(Effect.provide(makePairingGrantStoreLayer())),
115 }).pipe(Effect.provide(layerPairingGrantStore())),
116116 );
117117
118118 it.effect("requires the bound proof key thumbprint when present", () =>
......@@ -135,7 +135,7 @@ it.layer(NodeServices.layer)("PairingGrantStore.layer", (it) => {
135135 expect(missing.message).toContain("proof key mismatch");
136136 expect(wrong.message).toContain("proof key mismatch");
137137 expect(consumed.proofKeyThumbprint).toBe("client-proof-key-thumbprint");
138 }).pipe(Effect.provide(makePairingGrantStoreLayer())),
138 }).pipe(Effect.provide(layerPairingGrantStore())),
139139 );
140140
141141 it.effect("seeds the desktop bootstrap credential as a reusable grant", () =>
......@@ -161,7 +161,7 @@ it.layer(NodeServices.layer)("PairingGrantStore.layer", (it) => {
161161 expect(third.method).toBe("desktop-bootstrap");
162162 }).pipe(
163163 Effect.provide(
164 makePairingGrantStoreLayer({
164 layerPairingGrantStore({
165165 desktopBootstrapToken: "desktop-bootstrap-token",
166166 }),
167167 ),
......@@ -186,7 +186,7 @@ it.layer(NodeServices.layer)("PairingGrantStore.layer", (it) => {
186186 }).pipe(
187187 Effect.provide(
188188 Layer.merge(
189 makePairingGrantStoreLayer({
189 layerPairingGrantStore({
190190 desktopBootstrapToken: "desktop-bootstrap-token",
191191 }),
192192 TestClock.layer(),
......@@ -217,7 +217,7 @@ it.layer(NodeServices.layer)("PairingGrantStore.layer", (it) => {
217217 expect(consumed.scopes).toEqual(change.pairingLink.scopes);
218218 expect(yield* Queue.take(changes)).toEqual({ type: "pairingLinkRemoved", id: issued.id });
219219 }
220 }).pipe(Effect.scoped, Effect.provide(makePairingGrantStoreLayer())),
220 }).pipe(Effect.scoped, Effect.provide(layerPairingGrantStore())),
221221 );
222222
223223 it.effect("lists and revokes active pairing links", () =>
......@@ -244,7 +244,7 @@ it.layer(NodeServices.layer)("PairingGrantStore.layer", (it) => {
244244 expect(activeAfterRevoke.map((entry) => entry.id)).toContain(second.id);
245245 expect(revokedConsume.message).toContain("no longer available");
246246 expect(revokedConsume._tag).toBe("UnavailableBootstrapCredentialError");
247 }).pipe(Effect.provide(makePairingGrantStoreLayer())),
247 }).pipe(Effect.provide(layerPairingGrantStore())),
248248 );
249249
250250 it.effect("identifies consume-available failures and preserves their cause", () => {
......@@ -264,7 +264,7 @@ it.layer(NodeServices.layer)("PairingGrantStore.layer", (it) => {
264264 expect(error.cause).toBe(repositoryFailure);
265265 }).pipe(
266266 Effect.provide(
267 makePairingGrantStoreTestLayer({
267 layerPairingGrantStoreTest({
268268 consumeAvailable: () => Effect.fail(repositoryFailure),
269269 }),
270270 ),
apps/server/src/auth/RpcAuthorization.test.ts+2-2
......@@ -15,8 +15,8 @@ import {
1515 RPC_REQUIRED_SCOPES,
1616 requiredScopeForRpcMethod,
1717 requiredScopeForDeviceList,
18 rpcScopeAuthorizationLayer,
1918} from "./RpcAuthorization.ts";
19import * as RpcAuthorization from "./RpcAuthorization.ts";
2020
2121describe("RPC authorization scopes", () => {
2222 it("declares exactly one scope for every RPC in the server group", () => {
......@@ -150,7 +150,7 @@ describe("RPC scope middleware", () => {
150150 group.toLayerHandler(WS_METHODS.serverRetryResourceTelemetry, () =>
151151 Effect.sync(() => handled.push("retry")).pipe(Effect.andThen(Effect.never)),
152152 ),
153 rpcScopeAuthorizationLayer([AuthOrchestrationReadScope]),
153 RpcAuthorization.layer([AuthOrchestrationReadScope]),
154154 ),
155155 ),
156156 );
apps/server/src/auth/RpcAuthorization.ts+1-1
......@@ -229,7 +229,7 @@ export const rpcAuthorizationError = (requiredScope: AuthEnvironmentScope) =>
229229 });
230230
231231/** Authorizes every RPC on one connection against that connection's session scopes. */
232export const rpcScopeAuthorizationLayer = (scopes: ReadonlyArray<AuthEnvironmentScope>) =>
232export const layer = (scopes: ReadonlyArray<AuthEnvironmentScope>) =>
233233 Layer.succeed(RpcScopeAuthorization)((effect, { rpc }) => {
234234 const requiredScope = requiredScopeForRpcMethod(rpc._tag);
235235 return scopes.includes(requiredScope)
apps/server/src/auth/ServerSecretStore.test.ts+27-28
......@@ -12,13 +12,12 @@ import * as PlatformError from "effect/PlatformError";
1212import * as ServerConfig from "../config.ts";
1313import * as ServerSecretStore from "./ServerSecretStore.ts";
1414
15const makeServerConfigLayer = () =>
15const layerServerConfig = () =>
1616 ServerConfig.layerTest(process.cwd(), { prefix: "t3-secret-store-test-" });
1717
18const makeServerSecretStoreLayer = () =>
19 Layer.provide(ServerSecretStore.layer, makeServerConfigLayer());
18const layerServerSecretStore = () => Layer.provide(ServerSecretStore.layer, layerServerConfig());
2019
21const PermissionDeniedFileSystemLayer = Layer.effect(
20const layerPermissionDeniedFileSystem = Layer.effect(
2221 FileSystem.FileSystem,
2322 Effect.gen(function* () {
2423 const fileSystem = yield* FileSystem.FileSystem;
......@@ -39,13 +38,13 @@ const PermissionDeniedFileSystemLayer = Layer.effect(
3938 }),
4039).pipe(Layer.provide(NodeServices.layer));
4140
42const makePermissionDeniedSecretStoreLayer = () =>
41const layerPermissionDeniedSecretStore = () =>
4342 ServerSecretStore.layer.pipe(
44 Layer.provide(makeServerConfigLayer()),
45 Layer.provideMerge(PermissionDeniedFileSystemLayer),
43 Layer.provide(layerServerConfig()),
44 Layer.provideMerge(layerPermissionDeniedFileSystem),
4645 );
4746
48const RenameFailureFileSystemLayer = Layer.effect(
47const layerRenameFailureFileSystem = Layer.effect(
4948 FileSystem.FileSystem,
5049 Effect.gen(function* () {
5150 const fileSystem = yield* FileSystem.FileSystem;
......@@ -66,13 +65,13 @@ const RenameFailureFileSystemLayer = Layer.effect(
6665 }),
6766).pipe(Layer.provide(NodeServices.layer));
6867
69const makeRenameFailureSecretStoreLayer = () =>
68const layerRenameFailureSecretStore = () =>
7069 ServerSecretStore.layer.pipe(
71 Layer.provide(makeServerConfigLayer()),
72 Layer.provideMerge(RenameFailureFileSystemLayer),
70 Layer.provide(layerServerConfig()),
71 Layer.provideMerge(layerRenameFailureFileSystem),
7372 );
7473
75const RemoveFailureFileSystemLayer = Layer.effect(
74const layerRemoveFailureFileSystem = Layer.effect(
7675 FileSystem.FileSystem,
7776 Effect.gen(function* () {
7877 const fileSystem = yield* FileSystem.FileSystem;
......@@ -93,13 +92,13 @@ const RemoveFailureFileSystemLayer = Layer.effect(
9392 }),
9493).pipe(Layer.provide(NodeServices.layer));
9594
96const makeRemoveFailureSecretStoreLayer = () =>
95const layerRemoveFailureSecretStore = () =>
9796 ServerSecretStore.layer.pipe(
98 Layer.provide(makeServerConfigLayer()),
99 Layer.provideMerge(RemoveFailureFileSystemLayer),
97 Layer.provide(layerServerConfig()),
98 Layer.provideMerge(layerRemoveFailureFileSystem),
10099 );
101100
102const ConcurrentReadMissFileSystemLayer = Layer.effect(
101const layerConcurrentReadMissFileSystem = Layer.effect(
103102 FileSystem.FileSystem,
104103 Effect.gen(function* () {
105104 const fileSystem = yield* FileSystem.FileSystem;
......@@ -139,10 +138,10 @@ const ConcurrentReadMissFileSystemLayer = Layer.effect(
139138 }),
140139).pipe(Layer.provide(NodeServices.layer));
141140
142const makeConcurrentCreateSecretStoreLayer = () =>
141const layerConcurrentCreateSecretStore = () =>
143142 ServerSecretStore.layer.pipe(
144 Layer.provide(makeServerConfigLayer()),
145 Layer.provideMerge(ConcurrentReadMissFileSystemLayer),
143 Layer.provide(layerServerConfig()),
144 Layer.provideMerge(layerConcurrentReadMissFileSystem),
146145 );
147146
148147it.layer(NodeServices.layer)("ServerSecretStore.layer", (it) => {
......@@ -153,7 +152,7 @@ it.layer(NodeServices.layer)("ServerSecretStore.layer", (it) => {
153152 const secret = yield* secretStore.get("missing-secret");
154153
155154 assert.isTrue(Option.isNone(secret));
156 }).pipe(Effect.provide(makeServerSecretStoreLayer())),
155 }).pipe(Effect.provide(layerServerSecretStore())),
157156 );
158157
159158 it.effect("reuses an existing secret instead of regenerating it", () =>
......@@ -164,7 +163,7 @@ it.layer(NodeServices.layer)("ServerSecretStore.layer", (it) => {
164163 const second = yield* secretStore.getOrCreateRandom("session-signing-key", 32);
165164
166165 assert.deepEqual(Array.from(second), Array.from(first));
167 }).pipe(Effect.provide(makeServerSecretStoreLayer())),
166 }).pipe(Effect.provide(layerServerSecretStore())),
168167 );
169168
170169 it.effect("returns the persisted secret when concurrent creators race", () =>
......@@ -183,13 +182,13 @@ it.layer(NodeServices.layer)("ServerSecretStore.layer", (it) => {
183182
184183 assert.deepEqual(Array.from(first), Array.from(persistedBytes));
185184 assert.deepEqual(Array.from(second), Array.from(persistedBytes));
186 }).pipe(Effect.provide(makeConcurrentCreateSecretStoreLayer())),
185 }).pipe(Effect.provide(layerConcurrentCreateSecretStore())),
187186 );
188187
189188 it.effect("uses restrictive permissions for the secret directory and files", () =>
190189 Effect.gen(function* () {
191190 const chmodCalls: Array<{ readonly path: string; readonly mode: number }> = [];
192 const recordingFileSystemLayer = Layer.effect(
191 const layerRecordingFileSystem = Layer.effect(
193192 FileSystem.FileSystem,
194193 Effect.gen(function* () {
195194 const fileSystem = yield* FileSystem.FileSystem;
......@@ -210,8 +209,8 @@ it.layer(NodeServices.layer)("ServerSecretStore.layer", (it) => {
210209 const secretStore = yield* Effect.service(ServerSecretStore.ServerSecretStore).pipe(
211210 Effect.provide(
212211 ServerSecretStore.layer.pipe(
213 Layer.provide(makeServerConfigLayer()),
214 Layer.provideMerge(recordingFileSystemLayer),
212 Layer.provide(layerServerConfig()),
213 Layer.provideMerge(layerRecordingFileSystem),
215214 ),
216215 ),
217216 );
......@@ -235,7 +234,7 @@ it.layer(NodeServices.layer)("ServerSecretStore.layer", (it) => {
235234 assert.include(error.message, "Failed to read secret session-signing-key.");
236235 assert.instanceOf(error.cause, PlatformError.PlatformError);
237236 assert.equal((error.cause as PlatformError.PlatformError).reason._tag, "PermissionDenied");
238 }).pipe(Effect.provide(makePermissionDeniedSecretStoreLayer())),
237 }).pipe(Effect.provide(layerPermissionDeniedSecretStore())),
239238 );
240239
241240 it.effect("propagates write failures instead of treating them as success", () =>
......@@ -250,7 +249,7 @@ it.layer(NodeServices.layer)("ServerSecretStore.layer", (it) => {
250249 assert.include(error.message, "Failed to persist secret session-signing-key.");
251250 assert.instanceOf(error.cause, PlatformError.PlatformError);
252251 assert.equal((error.cause as PlatformError.PlatformError).reason._tag, "PermissionDenied");
253 }).pipe(Effect.provide(makeRenameFailureSecretStoreLayer())),
252 }).pipe(Effect.provide(layerRenameFailureSecretStore())),
254253 );
255254
256255 it.effect("propagates remove failures other than missing-file errors", () =>
......@@ -263,6 +262,6 @@ it.layer(NodeServices.layer)("ServerSecretStore.layer", (it) => {
263262 assert.include(error.message, "Failed to remove secret session-signing-key.");
264263 assert.instanceOf(error.cause, PlatformError.PlatformError);
265264 assert.equal((error.cause as PlatformError.PlatformError).reason._tag, "PermissionDenied");
266 }).pipe(Effect.provide(makeRemoveFailureSecretStoreLayer())),
265 }).pipe(Effect.provide(layerRemoveFailureSecretStore())),
267266 );
268267});
apps/server/src/auth/SessionStore.test.ts+44-52
......@@ -15,15 +15,13 @@ import * as SqlClient from "effect/sql/SqlClient";
1515import * as ServerConfig from "../config.ts";
1616import * as ServerEnvironment from "../environment/ServerEnvironment.ts";
1717import { PersistenceSqlError } from "../persistence/Errors.ts";
18import {
19 makeSqlitePersistenceLive,
20 SqlitePersistenceMemory,
21} from "../persistence/Layers/Sqlite.ts";
18
19import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts";
2220import * as AuthSessions from "../persistence/AuthSessions.ts";
2321import * as SessionStore from "./SessionStore.ts";
2422import * as ServerSecretStore from "./ServerSecretStore.ts";
2523
26const makeServerConfigLayer = (overrides?: Partial<ServerConfig.ServerConfig["Service"]>) =>
24const layerServerConfig = (overrides?: Partial<ServerConfig.ServerConfig["Service"]>) =>
2725 Layer.effect(
2826 ServerConfig.ServerConfig,
2927 Effect.gen(function* () {
......@@ -35,20 +33,20 @@ const makeServerConfigLayer = (overrides?: Partial<ServerConfig.ServerConfig["Se
3533 }),
3634 ).pipe(Layer.provide(ServerConfig.layerTest(process.cwd(), { prefix: "t3-auth-session-test-" })));
3735
38const makeServerEnvironmentLayer = (environmentId: EnvironmentId) =>
36const layerServerEnvironment = (environmentId: EnvironmentId) =>
3937 Layer.succeed(ServerEnvironment.ServerEnvironmentIdentity, {
4038 getEnvironmentId: Effect.succeed(environmentId),
4139 });
4240
43const makeSessionStoreLayer = (
41const layerSessionStore = (
4442 overrides?: Partial<ServerConfig.ServerConfig["Service"]>,
4543 environmentId = EnvironmentId.make("test-environment"),
4644) =>
4745 SessionStore.layer.pipe(
48 Layer.provide(SqlitePersistenceMemory),
46 Layer.provide(SqlitePersistence.layerMemory),
4947 Layer.provide(ServerSecretStore.layer),
50 Layer.provide(makeServerEnvironmentLayer(environmentId)),
51 Layer.provide(makeServerConfigLayer(overrides)),
48 Layer.provide(layerServerEnvironment(environmentId)),
49 Layer.provide(layerServerConfig(overrides)),
5250 );
5351
5452const relaySessionInput = {
......@@ -68,13 +66,13 @@ const makeDiskSessionStoreLayer = Effect.fn("makeDiskSessionStoreLayer")(functio
6866 baseDirIsExplicit: true,
6967 });
7068 yield* ServerConfig.ensureServerDirectories(paths);
71 const persistence = makeSqlitePersistenceLive(paths.dbPath);
69 const layerPersistence = SqlitePersistence.layerFromPath(paths.dbPath);
7270 return SessionStore.layer.pipe(
73 Layer.provide(persistence),
71 Layer.provide(layerPersistence),
7472 Layer.provide(ServerSecretStore.layer),
75 Layer.provide(makeServerEnvironmentLayer(EnvironmentId.make(baseDir))),
73 Layer.provide(layerServerEnvironment(EnvironmentId.make(baseDir))),
7674 Layer.provide(
77 makeServerConfigLayer({
75 layerServerConfig({
7876 ...paths,
7977 baseDir,
8078 mode: "web",
......@@ -90,7 +88,7 @@ const repositoryFailure = new PersistenceSqlError({
9088 detail: "sqlite is unavailable",
9189});
9290
93const failingSessionLookupRepositoryLayer = Layer.succeed(AuthSessions.AuthSessionRepository, {
91const layerFailingSessionLookupRepository = Layer.succeed(AuthSessions.AuthSessionRepository, {
9492 create: () => Effect.void,
9593 createReplacingActive: () => Effect.succeed([]),
9694 createIfAbsent: () => Effect.void,
......@@ -102,15 +100,15 @@ const failingSessionLookupRepositoryLayer = Layer.succeed(AuthSessions.AuthSessi
102100 setClientConnection: () => Effect.void,
103101});
104102
105const failingSessionLookupCredentialLayer = Layer.effect(
103const layerFailingSessionLookupCredential = Layer.effect(
106104 SessionStore.SessionStore,
107105 SessionStore.make,
108106).pipe(
109 Layer.provide(failingSessionLookupRepositoryLayer),
107 Layer.provide(layerFailingSessionLookupRepository),
110108 Layer.provide(ServerSecretStore.layer),
111 Layer.provide(SqlitePersistenceMemory),
112 Layer.provide(makeServerEnvironmentLayer(EnvironmentId.make("test-environment"))),
113 Layer.provide(makeServerConfigLayer()),
109 Layer.provide(SqlitePersistence.layerMemory),
110 Layer.provide(layerServerEnvironment(EnvironmentId.make("test-environment"))),
111 Layer.provide(layerServerConfig()),
114112);
115113
116114it.layer(NodeServices.layer)("SessionStore.layer", (it) => {
......@@ -122,7 +120,7 @@ it.layer(NodeServices.layer)("SessionStore.layer", (it) => {
122120 return sessions.cookieName;
123121 }).pipe(
124122 Effect.provide(
125 makeSessionStoreLayer({ mode: "web", host: "192.168.1.50", stateDir }, environmentId),
123 layerSessionStore({ mode: "web", host: "192.168.1.50", stateDir }, environmentId),
126124 ),
127125 );
128126
......@@ -164,20 +162,20 @@ it.layer(NodeServices.layer)("SessionStore.layer", (it) => {
164162 );
165163 }).pipe(Effect.provide(layerB), Effect.scoped);
166164
167 const reopenedB = yield* makeDiskSessionStoreLayer(baseB, token);
165 const layerReopenedB = yield* makeDiskSessionStoreLayer(baseB, token);
168166 yield* Effect.gen(function* () {
169167 const sessions = yield* SessionStore.SessionStore;
170168 const dev = yield* sessions.verify(token);
171169 expect(dev.sessionId).toBe(fromA.dev.sessionId);
172170 const ticket = yield* sessions.issueWebSocketToken(dev.sessionId);
173171 expect((yield* sessions.verifyWebSocketToken(ticket.token)).sessionId).toBe(dev.sessionId);
174 }).pipe(Effect.provide(reopenedB), Effect.scoped);
172 }).pipe(Effect.provide(layerReopenedB), Effect.scoped);
175173
176 const reopenedA = yield* makeDiskSessionStoreLayer(baseA, token);
174 const layerReopenedA = yield* makeDiskSessionStoreLayer(baseA, token);
177175 yield* Effect.gen(function* () {
178176 const sessions = yield* SessionStore.SessionStore;
179177 expect((yield* Effect.flip(sessions.verify(token)))._tag).toBe("SessionTokenRevokedError");
180 }).pipe(Effect.provide(reopenedA), Effect.scoped);
178 }).pipe(Effect.provide(layerReopenedA), Effect.scoped);
181179 }),
182180 );
183181
......@@ -187,15 +185,15 @@ it.layer(NodeServices.layer)("SessionStore.layer", (it) => {
187185 const baseDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-dev-auth-rotation-" });
188186 const oldToken = "old-reusable-dev-auth-token-that-is-long-enough";
189187 const newToken = "new-reusable-dev-auth-token-that-is-long-enough";
190 const initialLayer = yield* makeDiskSessionStoreLayer(baseDir, oldToken);
188 const layerInitial = yield* makeDiskSessionStoreLayer(baseDir, oldToken);
191189 const old = yield* Effect.gen(function* () {
192190 const sessions = yield* SessionStore.SessionStore;
193191 const dev = yield* sessions.verify(oldToken);
194192 const ticket = yield* sessions.issueWebSocketToken(dev.sessionId);
195193 return { dev, ticket };
196 }).pipe(Effect.provide(initialLayer), Effect.scoped);
194 }).pipe(Effect.provide(layerInitial), Effect.scoped);
197195
198 const rotatedLayer = yield* makeDiskSessionStoreLayer(baseDir, newToken);
196 const layerRotated = yield* makeDiskSessionStoreLayer(baseDir, newToken);
199197 const rotated = yield* Effect.gen(function* () {
200198 const sessions = yield* SessionStore.SessionStore;
201199 expect((yield* Effect.flip(sessions.verify(oldToken)))._tag).toBe(
......@@ -210,9 +208,9 @@ it.layer(NodeServices.layer)("SessionStore.layer", (it) => {
210208 const dev = yield* sessions.verify(newToken);
211209 const ticket = yield* sessions.issueWebSocketToken(dev.sessionId);
212210 return { dev, ticket };
213 }).pipe(Effect.provide(rotatedLayer), Effect.scoped);
211 }).pipe(Effect.provide(layerRotated), Effect.scoped);
214212
215 const removedLayer = yield* makeDiskSessionStoreLayer(baseDir);
213 const layerRemoved = yield* makeDiskSessionStoreLayer(baseDir);
216214 yield* Effect.gen(function* () {
217215 const sessions = yield* SessionStore.SessionStore;
218216 expect((yield* Effect.flip(sessions.verify(newToken)))._tag).toBe(
......@@ -224,7 +222,7 @@ it.layer(NodeServices.layer)("SessionStore.layer", (it) => {
224222 expect(
225223 (yield* sessions.listActive()).some((row) => row.sessionId === rotated.dev.sessionId),
226224 ).toBe(true);
227 }).pipe(Effect.provide(removedLayer), Effect.scoped);
225 }).pipe(Effect.provide(layerRemoved), Effect.scoped);
228226 }),
229227 );
230228
......@@ -243,7 +241,7 @@ it.layer(NodeServices.layer)("SessionStore.layer", (it) => {
243241 }).pipe(
244242 Effect.provide(
245243 Layer.merge(
246 makeSessionStoreLayer({
244 layerSessionStore({
247245 mode: "web",
248246 devUrl: new URL("http://127.0.0.1:5173"),
249247 devAuthToken: Redacted.make("reusable-dev-auth-token-that-is-long-enough"),
......@@ -276,7 +274,7 @@ it.layer(NodeServices.layer)("SessionStore.layer", (it) => {
276274 expect(verified.client.label).toBe("Desktop app");
277275 expect(verified.client.browser).toBe("Electron");
278276 expect(verified.expiresAt?.toString()).toBe(issued.expiresAt.toString());
279 }).pipe(Effect.provide(makeSessionStoreLayer())),
277 }).pipe(Effect.provide(layerSessionStore())),
280278 );
281279 it.effect("rejects malformed session tokens", () =>
282280 Effect.gen(function* () {
......@@ -285,7 +283,7 @@ it.layer(NodeServices.layer)("SessionStore.layer", (it) => {
285283
286284 expect(error._tag).toBe("MalformedSessionTokenError");
287285 expect(error.message).toContain("Malformed session token");
288 }).pipe(Effect.provide(makeSessionStoreLayer())),
286 }).pipe(Effect.provide(layerSessionStore())),
289287 );
290288 it.effect("preserves repository failures while verifying session and websocket credentials", () =>
291289 Effect.gen(function* () {
......@@ -321,7 +319,7 @@ it.layer(NodeServices.layer)("SessionStore.layer", (it) => {
321319 currentSessionId: issued.sessionId,
322320 cause: repositoryFailure,
323321 });
324 }).pipe(Effect.provide(failingSessionLookupCredentialLayer)),
322 }).pipe(Effect.provide(layerFailingSessionLookupCredential)),
325323 );
326324 it.effect("verifies session tokens against the Effect clock", () =>
327325 Effect.gen(function* () {
......@@ -341,7 +339,7 @@ it.layer(NodeServices.layer)("SessionStore.layer", (it) => {
341339 "review:write",
342340 "relay:read",
343341 ]);
344 }).pipe(Effect.provide(Layer.merge(makeSessionStoreLayer(), TestClock.layer()))),
342 }).pipe(Effect.provide(Layer.merge(layerSessionStore(), TestClock.layer()))),
345343 );
346344
347345 it.effect("atomically replaces active sessions with the same subject and method", () =>
......@@ -382,7 +380,7 @@ it.layer(NodeServices.layer)("SessionStore.layer", (it) => {
382380 ),
383381 ).toHaveLength(1);
384382 expect(bearerVerification.filter(Option.isSome)).toHaveLength(1);
385 }).pipe(Effect.provide(makeSessionStoreLayer())),
383 }).pipe(Effect.provide(layerSessionStore())),
386384 );
387385
388386 it.effect("keeps the previous desktop session valid when replacement fails", () =>
......@@ -413,7 +411,7 @@ it.layer(NodeServices.layer)("SessionStore.layer", (it) => {
413411 expect((yield* sessions.listActive()).map((session) => session.sessionId)).toEqual([
414412 previous.sessionId,
415413 ]);
416 }).pipe(Effect.provide(Layer.mergeAll(makeSessionStoreLayer(), SqlitePersistenceMemory))),
414 }).pipe(Effect.provide(Layer.mergeAll(layerSessionStore(), SqlitePersistence.layerMemory))),
417415 );
418416
419417 it.effect("rejects websocket tokens once the parent session has expired", () =>
......@@ -437,7 +435,7 @@ it.layer(NodeServices.layer)("SessionStore.layer", (it) => {
437435 error.expiresAt.epochMilliseconds,
438436 );
439437 }
440 }).pipe(Effect.provide(Layer.merge(makeSessionStoreLayer(), TestClock.layer()))),
438 }).pipe(Effect.provide(Layer.merge(layerSessionStore(), TestClock.layer()))),
441439 );
442440
443441 it.effect("includes expiry context when session and websocket tokens expire", () =>
......@@ -475,7 +473,7 @@ it.layer(NodeServices.layer)("SessionStore.layer", (it) => {
475473 websocketError.expiresAt.epochMilliseconds,
476474 );
477475 }
478 }).pipe(Effect.provide(Layer.merge(makeSessionStoreLayer(), TestClock.layer()))),
476 }).pipe(Effect.provide(Layer.merge(layerSessionStore(), TestClock.layer()))),
479477 );
480478
481479 it.effect("lists active sessions, tracks connectivity, and revokes other sessions", () =>
......@@ -537,7 +535,7 @@ it.layer(NodeServices.layer)("SessionStore.layer", (it) => {
537535 expect(revokedClientWebSocket.sessionId).toBe(client.sessionId);
538536 expect(revokedClientWebSocket.revokedAt.epochMilliseconds).toBeGreaterThanOrEqual(0);
539537 }
540 }).pipe(Effect.provide(makeSessionStoreLayer())),
538 }).pipe(Effect.provide(layerSessionStore())),
541539 );
542540
543541 it.effect("persists lastConnectedAt on first connect and updates it after reconnect", () =>
......@@ -579,7 +577,7 @@ it.layer(NodeServices.layer)("SessionStore.layer", (it) => {
579577 expect(afterReconnect[0]?.connected).toBe(true);
580578 expect(afterReconnect[0]?.lastConnectedAt).not.toBeNull();
581579 expect(afterReconnect[0]?.lastConnectedAt?.toString()).not.toBe(firstConnectedAt?.toString());
582 }).pipe(Effect.provide(Layer.merge(makeSessionStoreLayer(), TestClock.layer()))),
580 }).pipe(Effect.provide(Layer.merge(layerSessionStore(), TestClock.layer()))),
583581 );
584582
585583 it.effect("keeps connected relay sessions visible through expiry and HTTP renewal", () =>
......@@ -619,7 +617,7 @@ it.layer(NodeServices.layer)("SessionStore.layer", (it) => {
619617 }),
620618 ]),
621619 );
622 }).pipe(Effect.provide(Layer.merge(makeSessionStoreLayer(), TestClock.layer()))),
620 }).pipe(Effect.provide(Layer.merge(layerSessionStore(), TestClock.layer()))),
623621 );
624622
625623 it.effect.each([1, 2])(
......@@ -659,10 +657,7 @@ it.layer(NodeServices.layer)("SessionStore.layer", (it) => {
659657 expect(listed).toEqual([]);
660658 }
661659 }
662 }).pipe(
663 Effect.scoped,
664 Effect.provide(Layer.merge(makeSessionStoreLayer(), TestClock.layer())),
665 ),
660 }).pipe(Effect.scoped, Effect.provide(Layer.merge(layerSessionStore(), TestClock.layer()))),
666661 );
667662
668663 it.effect.each(["revoke", "revokeAllExcept"] as const)(
......@@ -693,10 +688,7 @@ it.layer(NodeServices.layer)("SessionStore.layer", (it) => {
693688 const listed = yield* sessions.listActive();
694689 expect(listed).toHaveLength(1);
695690 expect(listed[0]?.sessionId).toBe(administrative.sessionId);
696 }).pipe(
697 Effect.scoped,
698 Effect.provide(Layer.merge(makeSessionStoreLayer(), TestClock.layer())),
699 ),
691 }).pipe(Effect.scoped, Effect.provide(Layer.merge(layerSessionStore(), TestClock.layer()))),
700692 );
701693
702694 it.effect("records client connection metadata without clearing prior values", () =>
......@@ -728,6 +720,6 @@ it.layer(NodeServices.layer)("SessionStore.layer", (it) => {
728720
729721 yield* sessions.recordClientConnection(issued.sessionId, {});
730722 expect((yield* readRow)[0]).toEqual({ surface: "mobile", appVersion: "1.3.0" });
731 }).pipe(Effect.provide(Layer.mergeAll(makeSessionStoreLayer(), SqlitePersistenceMemory))),
723 }).pipe(Effect.provide(Layer.mergeAll(layerSessionStore(), SqlitePersistence.layerMemory))),
732724 );
733725});
apps/server/src/auth/http.test.ts+14-14
......@@ -15,15 +15,15 @@ import * as HttpRouter from "effect/http/HttpRouter";
1515
1616import * as ServerConfig from "../config.ts";
1717import * as ServerEnvironment from "../environment/ServerEnvironment.ts";
18import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts";
18import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts";
1919import * as EnvironmentAuth from "./EnvironmentAuth.ts";
2020import * as ServerSecretStore from "./ServerSecretStore.ts";
21import { authHttpApiLayer, environmentAuthenticatedAuthLayer } from "./http.ts";
21import * as AuthHttp from "./http.ts";
2222
2323const DEV_TOKEN = "reusable-dev-auth-token-that-is-long-enough";
2424class AuthTestApi extends HttpApi.make("environment").add(EnvironmentHttpApi.groups.auth) {}
2525
26const configLayer = Layer.effect(
26const layerConfig = Layer.effect(
2727 ServerConfig.ServerConfig,
2828 Effect.gen(function* () {
2929 const config = yield* ServerConfig.ServerConfig;
......@@ -36,17 +36,17 @@ const configLayer = Layer.effect(
3636 }),
3737).pipe(Layer.provide(ServerConfig.layerTest(process.cwd(), { prefix: "t3-auth-http-test-" })));
3838
39const environmentAuthLayer = EnvironmentAuth.layer.pipe(
40 Layer.provide(SqlitePersistenceMemory),
39const layerEnvironmentAuth = EnvironmentAuth.layer.pipe(
40 Layer.provide(SqlitePersistence.layerMemory),
4141 Layer.provide(ServerSecretStore.layer),
42 Layer.provide(ServerEnvironment.identityLayer),
43 Layer.provide(configLayer),
42 Layer.provide(ServerEnvironment.layerIdentity),
43 Layer.provide(layerConfig),
4444);
45const routesLayer = HttpApiBuilder.layer(AuthTestApi).pipe(
46 Layer.provide(authHttpApiLayer),
47 Layer.provide(environmentAuthenticatedAuthLayer),
48 Layer.provideMerge(environmentAuthLayer),
49 Layer.provide(configLayer),
45const layerRoutes = HttpApiBuilder.layer(AuthTestApi).pipe(
46 Layer.provide(AuthHttp.layer),
47 Layer.provide(AuthHttp.layerAuthenticatedAuth),
48 Layer.provideMerge(layerEnvironmentAuth),
49 Layer.provide(layerConfig),
5050 Layer.provideMerge(
5151 HttpPlatform.layer.pipe(
5252 Layer.provideMerge(NodeServices.layer),
......@@ -81,8 +81,8 @@ it.effect("sets the selected browser session cookies through the HTTP route", ()
8181 Effect.sync(
8282 () =>
8383 [
84 HttpRouter.toWebHandler(routesLayer, { disableLogger: true }),
85 HttpRouter.toWebHandler(routesLayer, { disableLogger: true }),
84 HttpRouter.toWebHandler(layerRoutes, { disableLogger: true }),
85 HttpRouter.toWebHandler(layerRoutes, { disableLogger: true }),
8686 ] as const,
8787 ),
8888 ([environmentA, environmentB]) =>
apps/server/src/auth/http.ts+2-2
......@@ -198,7 +198,7 @@ export const requireEnvironmentScope = Effect.fn("environment.auth.requireScope"
198198 return session;
199199});
200200
201export const environmentAuthenticatedAuthLayer = Layer.effect(
201export const layerAuthenticatedAuth = Layer.effect(
202202 EnvironmentAuthenticatedAuth,
203203 Effect.gen(function* () {
204204 const serverAuth = yield* EnvironmentAuth.EnvironmentAuth;
......@@ -227,7 +227,7 @@ export const environmentAuthenticatedAuthLayer = Layer.effect(
227227 }),
228228);
229229
230export const authHttpApiLayer = HttpApiBuilder.group(
230export const layer = HttpApiBuilder.group(
231231 EnvironmentHttpApi,
232232 "auth",
233233 Effect.fnUntraced(function* (handlers) {
apps/server/src/background/BackgroundPolicy.test.ts+17-17
......@@ -53,11 +53,11 @@ function makeReport(overrides: Partial<ClientActivityReportInput> = {}): ClientA
5353 };
5454}
5555
56function makeLayer(
56function layerFor(
5757 hostPower: HostPowerSnapshot,
5858 settingsOverrides: Parameters<typeof ServerSettings.layerTest>[0] = {},
5959) {
60 const hostLayer = Layer.effect(
60 const layerHost = Layer.effect(
6161 HostPowerMonitor.HostPowerMonitor,
6262 Effect.gen(function* () {
6363 const changes = yield* PubSub.sliding<HostPowerSnapshot>(1);
......@@ -73,7 +73,7 @@ function makeLayer(
7373 }),
7474 );
7575 return BackgroundPolicy.layer.pipe(
76 Layer.provide(Layer.merge(hostLayer, ServerSettings.layerTest(settingsOverrides))),
76 Layer.provide(Layer.merge(layerHost, ServerSettings.layerTest(settingsOverrides))),
7777 );
7878}
7979
......@@ -95,7 +95,7 @@ describe("BackgroundPolicy", () => {
9595 assert.equal(yield* policy.hasDemand({ type: "vcs-status", cwd: "/other" }), false);
9696 assert.equal(yield* policy.shouldRunScopeWork({ type: "vcs-status", cwd: "/repo" }), true);
9797 assert.equal(yield* policy.shouldRunScopeWork({ type: "vcs-status", cwd: "/other" }), false);
98 }).pipe(Effect.provide(makeLayer(nominalHostPower))),
98 }).pipe(Effect.provide(layerFor(nominalHostPower))),
9999 );
100100
101101 it.effect("removes all leases for a disconnected websocket connection", () =>
......@@ -112,7 +112,7 @@ describe("BackgroundPolicy", () => {
112112 assert.equal(snapshot.activeForegroundLeaseCount, 0);
113113 assert.deepStrictEqual(snapshot.activeScopeKeys, []);
114114 assert.equal(snapshot.shouldRunOpportunisticWork, false);
115 }).pipe(Effect.provide(makeLayer(nominalHostPower))),
115 }).pipe(Effect.provide(layerFor(nominalHostPower))),
116116 );
117117
118118 it.effect("keeps leases from another session when rpc client ids are reused", () =>
......@@ -136,7 +136,7 @@ describe("BackgroundPolicy", () => {
136136 assert.equal(snapshot.activeForegroundLeaseCount, 1);
137137 assert.equal(snapshot.leases[0]?.sessionId, AuthSessionId.make("session-2"));
138138 assert.equal(snapshot.leases[0]?.clientId, "client-2");
139 }).pipe(Effect.provide(makeLayer(nominalHostPower))),
139 }).pipe(Effect.provide(layerFor(nominalHostPower))),
140140 );
141141
142142 it.effect("serializes lease mutation publications", () =>
......@@ -144,7 +144,7 @@ describe("BackgroundPolicy", () => {
144144 const firstSnapshotStarted = yield* Deferred.make<void>();
145145 const releaseFirstSnapshot = yield* Deferred.make<void>();
146146 const snapshotReads = yield* Ref.make(0);
147 const hostLayer = Layer.succeed(
147 const layerHost = Layer.succeed(
148148 HostPowerMonitor.HostPowerMonitor,
149149 HostPowerMonitor.HostPowerMonitor.of({
150150 snapshot: Ref.updateAndGet(snapshotReads, (count) => count + 1).pipe(
......@@ -162,7 +162,7 @@ describe("BackgroundPolicy", () => {
162162 }),
163163 );
164164 const layer = BackgroundPolicy.layer.pipe(
165 Layer.provide(Layer.merge(hostLayer, ServerSettings.layerTest())),
165 Layer.provide(Layer.merge(layerHost, ServerSettings.layerTest())),
166166 );
167167
168168 yield* Effect.gen(function* () {
......@@ -221,7 +221,7 @@ describe("BackgroundPolicy", () => {
221221 `client-${BackgroundPolicy.MAX_CLIENT_ACTIVITY_LEASES_PER_RPC_CLIENT}`,
222222 ),
223223 );
224 }).pipe(Effect.provide(makeLayer(nominalHostPower))),
224 }).pipe(Effect.provide(layerFor(nominalHostPower))),
225225 );
226226
227227 it.effect("host low power mode disables opportunistic work without dropping scoped demand", () =>
......@@ -239,7 +239,7 @@ describe("BackgroundPolicy", () => {
239239 assert.equal(snapshot.shouldRunOpportunisticWork, false);
240240 assert.equal(yield* policy.hasDemand({ type: "vcs-status", cwd: "/repo" }), true);
241241 assert.equal(yield* policy.shouldRunScopeWork({ type: "vcs-status", cwd: "/repo" }), false);
242 }).pipe(Effect.provide(makeLayer(constrainedHostPower))),
242 }).pipe(Effect.provide(layerFor(constrainedHostPower))),
243243 );
244244
245245 it.effect("host suspension disables scoped and opportunistic work", () =>
......@@ -258,7 +258,7 @@ describe("BackgroundPolicy", () => {
258258 assert.equal(yield* policy.shouldRunScopeWork({ type: "vcs-status", cwd: "/repo" }), false);
259259 }).pipe(
260260 Effect.provide(
261 makeLayer({
261 layerFor({
262262 ...nominalHostPower,
263263 suspended: true,
264264 stale: false,
......@@ -281,7 +281,7 @@ describe("BackgroundPolicy", () => {
281281 assert.deepStrictEqual(snapshot.activeScopeKeys, ["vcs-status:/repo"]);
282282 assert.equal(yield* policy.hasDemand({ type: "vcs-status", cwd: "/repo" }), true);
283283 assert.equal(yield* policy.shouldRunScopeWork({ type: "vcs-status", cwd: "/repo" }), false);
284 }).pipe(Effect.provide(makeLayer(nominalHostPower))),
284 }).pipe(Effect.provide(layerFor(nominalHostPower))),
285285 );
286286
287287 it.effect("keeps scoped work active after a recently used visible window loses focus", () =>
......@@ -297,7 +297,7 @@ describe("BackgroundPolicy", () => {
297297 assert.equal(snapshot.activeForegroundLeaseCount, 1);
298298 assert.equal(snapshot.shouldRunOpportunisticWork, true);
299299 assert.equal(yield* policy.shouldRunScopeWork({ type: "vcs-status", cwd: "/repo" }), true);
300 }).pipe(Effect.provide(makeLayer(nominalHostPower))),
300 }).pipe(Effect.provide(layerFor(nominalHostPower))),
301301 );
302302
303303 it.effect("pauses scoped work for a visible unfocused client without recent interaction", () =>
......@@ -313,7 +313,7 @@ describe("BackgroundPolicy", () => {
313313 assert.equal(snapshot.activeForegroundLeaseCount, 0);
314314 assert.equal(snapshot.shouldRunOpportunisticWork, false);
315315 assert.equal(yield* policy.shouldRunScopeWork({ type: "vcs-status", cwd: "/repo" }), false);
316 }).pipe(Effect.provide(makeLayer(nominalHostPower))),
316 }).pipe(Effect.provide(layerFor(nominalHostPower))),
317317 );
318318
319319 it.effect(
......@@ -329,7 +329,7 @@ describe("BackgroundPolicy", () => {
329329
330330 assert.equal(yield* policy.shouldRunScopeWork({ type: "vcs-status", cwd: "/repo" }), true);
331331 }).pipe(
332 Effect.provide(makeLayer(nominalHostPower, { backgroundActivityProfile: "performance" })),
332 Effect.provide(layerFor(nominalHostPower, { backgroundActivityProfile: "performance" })),
333333 ),
334334 );
335335
......@@ -345,7 +345,7 @@ describe("BackgroundPolicy", () => {
345345 assert.equal(yield* policy.shouldRunScopeWork({ type: "vcs-status", cwd: "/repo" }), false);
346346 }).pipe(
347347 Effect.provide(
348 makeLayer(
348 layerFor(
349349 {
350350 ...nominalHostPower,
351351 onBattery: "true",
......@@ -369,7 +369,7 @@ describe("BackgroundPolicy", () => {
369369 assert.equal(yield* policy.shouldRunScopeWork({ type: "vcs-status", cwd: "/repo" }), true);
370370 }).pipe(
371371 Effect.provide(
372 makeLayer(
372 layerFor(
373373 {
374374 ...nominalHostPower,
375375 locked: "true",
apps/server/src/background/HostPowerMonitor.test.ts+4-4
......@@ -84,11 +84,11 @@ describe("HostPowerMonitor", () => {
8484 const sampledAt = DateTime.makeUnsafe("2026-06-17T12:00:00.000Z");
8585 const desktopChanges = yield* PubSub.sliding<DesktopHostTelemetrySnapshot>(1);
8686 const diagnosticsDemandWrites = yield* Ref.make(0);
87 const receiverLayer = DesktopTelemetryReceiver.layerTest({
87 const layerReceiver = DesktopTelemetryReceiver.layerTest({
8888 changes: Stream.fromPubSub(desktopChanges),
8989 setDiagnosticsDemand: () => Ref.update(diagnosticsDemandWrites, (count) => count + 1),
9090 });
91 const layer = HostPowerMonitor.layer.pipe(Layer.provide(receiverLayer));
91 const layer = HostPowerMonitor.layer.pipe(Layer.provide(layerReceiver));
9292
9393 yield* Effect.gen(function* () {
9494 const monitor = yield* HostPowerMonitor.HostPowerMonitor;
......@@ -158,7 +158,7 @@ describe("HostPowerMonitor", () => {
158158 },
159159 };
160160 const desktopChanges = yield* PubSub.sliding<DesktopHostTelemetrySnapshot>(1);
161 const receiverLayer = DesktopTelemetryReceiver.layerTest({
161 const layerReceiver = DesktopTelemetryReceiver.layerTest({
162162 latest: Effect.succeedSome(initial),
163163 changes: Stream.empty,
164164 subscribe: Effect.gen(function* () {
......@@ -170,7 +170,7 @@ describe("HostPowerMonitor", () => {
170170 };
171171 }),
172172 });
173 const layer = HostPowerMonitor.layer.pipe(Layer.provide(receiverLayer));
173 const layer = HostPowerMonitor.layer.pipe(Layer.provide(layerReceiver));
174174
175175 yield* Effect.gen(function* () {
176176 const monitor = yield* HostPowerMonitor.HostPowerMonitor;
apps/server/src/binCli.ts+2-2
......@@ -27,7 +27,7 @@ import { themeCommand } from "./cli/theme.ts";
2727import { traceCommand } from "./cli/trace.ts";
2828import { triageCommand } from "./cli/triage.ts";
2929
30const CliRuntimeLayer = Layer.mergeAll(NodeServices.layer, NetService.layer);
30const layerCliRuntime = Layer.mergeAll(NodeServices.layer, NetService.layer);
3131
3232const connectPublicConfigMissingMessage =
3333 "T3 Connect commands are unavailable: this build is missing T3 Connect public configuration.";
......@@ -92,7 +92,7 @@ export const cli = makeCli();
9292export function runCli() {
9393 Command.run(cli, { version: packageJson.version }).pipe(
9494 Effect.scoped,
95 Effect.provide(CliRuntimeLayer),
95 Effect.provide(layerCliRuntime),
9696 NodeRuntime.runMain,
9797 );
9898}
apps/server/src/checkpointing/CheckpointDiffQuery.test.ts+6-6
......@@ -44,7 +44,7 @@ function makeProjection(): ProjectionCheckpointContext {
4444 };
4545}
4646
47function makeLayer(input: {
47function layerFor(input: {
4848 readonly projection: Effect.Effect<ProjectionCheckpointContext, OrchestratorProjectionError>;
4949 readonly diffCheckpoints?: CheckpointStore.CheckpointStore["Service"]["diffCheckpoints"];
5050}) {
......@@ -66,7 +66,7 @@ it.effect("computes V2 run diffs from projected checkpoint scopes", () => {
6666 const diffCheckpoints = vi.fn((_input: CheckpointStore.DiffCheckpointsInput) =>
6767 Effect.succeed("diff --git a/file b/file"),
6868 );
69 const layer = makeLayer({ projection: Effect.succeed(makeProjection()), diffCheckpoints });
69 const layer = layerFor({ projection: Effect.succeed(makeProjection()), diffCheckpoints });
7070
7171 return Effect.gen(function* () {
7272 const query = yield* CheckpointDiffQuery.CheckpointDiffQuery;
......@@ -92,7 +92,7 @@ it.effect("computes V2 run diffs from projected checkpoint scopes", () => {
9292});
9393
9494it.effect("preserves the typed missing-thread error contract", () => {
95 const layer = makeLayer({
95 const layer = layerFor({
9696 projection: Effect.fail(new OrchestratorProjectionError({ threadId })),
9797 });
9898
......@@ -111,7 +111,7 @@ it.effect("preserves the typed missing-thread error contract", () => {
111111});
112112
113113it.effect("preserves the typed unavailable-range error contract", () => {
114 const layer = makeLayer({ projection: Effect.succeed(makeProjection()) });
114 const layer = layerFor({ projection: Effect.succeed(makeProjection()) });
115115
116116 return Effect.gen(function* () {
117117 const query = yield* CheckpointDiffQuery.CheckpointDiffQuery;
......@@ -132,7 +132,7 @@ it.effect("preserves the typed unavailable-range error contract", () => {
132132
133133it.effect("excludes ready checkpoints from rolled-back runs", () => {
134134 const projection = makeProjection();
135 const layer = makeLayer({
135 const layer = layerFor({
136136 projection: Effect.succeed({
137137 ...projection,
138138 runs: projection.runs.map((run) =>
......@@ -170,7 +170,7 @@ it.effect("excludes ready checkpoints from rolled-back runs", () => {
170170
171171it.effect("preserves the typed missing-baseline-ref error contract", () => {
172172 const projection = makeProjection();
173 const layer = makeLayer({
173 const layer = layerFor({
174174 projection: Effect.succeed({
175175 ...projection,
176176 checkpointScopes: projection.checkpointScopes.map((scope) => ({
apps/server/src/checkpointing/CheckpointStore.test.ts+10-10
......@@ -19,19 +19,19 @@ import * as VcsDriverRegistry from "../vcs/VcsDriverRegistry.ts";
1919import * as VcsProcess from "../vcs/VcsProcess.ts";
2020import * as ServerConfig from "../config.ts";
2121
22const ServerConfigLayer = ServerConfig.ServerConfig.layerTest(process.cwd(), {
22const layerServerConfig = ServerConfig.ServerConfig.layerTest(process.cwd(), {
2323 prefix: "t3-checkpoint-store-test-",
2424});
25const VcsProcessTestLayer = VcsProcess.layer.pipe(Layer.provide(NodeServices.layer));
26const VcsDriverTestLayer = VcsDriverRegistry.layer.pipe(Layer.provide(VcsProcessTestLayer));
27const CheckpointStoreTestLayer = CheckpointStore.layer.pipe(
28 Layer.provideMerge(VcsDriverTestLayer),
25const layerVcsProcessTest = VcsProcess.layer.pipe(Layer.provide(NodeServices.layer));
26const layerVcsDriverTest = VcsDriverRegistry.layer.pipe(Layer.provide(layerVcsProcessTest));
27const layerCheckpointStoreTest = CheckpointStore.layer.pipe(
28 Layer.provideMerge(layerVcsDriverTest),
2929 Layer.provideMerge(NodeServices.layer),
3030);
31const TestLayer = CheckpointStoreTestLayer.pipe(
32 Layer.provideMerge(VcsProcessTestLayer),
33 Layer.provideMerge(VcsDriverTestLayer),
34 Layer.provideMerge(ServerConfigLayer),
31const layerTest = layerCheckpointStoreTest.pipe(
32 Layer.provideMerge(layerVcsProcessTest),
33 Layer.provideMerge(layerVcsDriverTest),
34 Layer.provideMerge(layerServerConfig),
3535 Layer.provideMerge(NodeServices.layer),
3636);
3737
......@@ -94,7 +94,7 @@ function buildLargeText(lineCount = 5_000): string {
9494 .concat("\n");
9595}
9696
97it.layer(TestLayer)("CheckpointStore.layer", (it) => {
97it.layer(layerTest)("CheckpointStore.layer", (it) => {
9898 describe("isGitRepository", () => {
9999 it.effect("returns false when no Git repository is detected", () =>
100100 Effect.gen(function* () {
apps/server/src/cli/auth.ts+1-1
......@@ -42,7 +42,7 @@ const runWithEnvironmentAuth = <A, E>(
4242 return yield* run(environmentAuth);
4343 }).pipe(
4444 Effect.provide(
45 Layer.mergeAll(EnvironmentAuth.runtimeLayer).pipe(
45 Layer.mergeAll(EnvironmentAuth.layerRuntime).pipe(
4646 Layer.provide(ServerConfig.layer(config)),
4747 Layer.provide(Layer.succeed(References.MinimumLogLevel, minimumLogLevel)),
4848 ),
apps/server/src/cli/config.test.ts+7-7
......@@ -146,7 +146,7 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => {
146146 tailscaleServeEnabled: Option.none<boolean>(),
147147 tailscaleServePort: Option.none<number>(),
148148 };
149 const configLayer = ConfigProvider.layer(
149 const layerConfig = ConfigProvider.layer(
150150 ConfigProvider.fromEnv({
151151 env: {
152152 T3CODE_DEV_AUTH_TOKEN: " reusable-dev-auth-token-that-is-long-enough ",
......@@ -154,12 +154,12 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => {
154154 }),
155155 );
156156 const web = yield* resolveServerConfig(flags, Option.none()).pipe(
157 Effect.provide(Layer.mergeAll(configLayer, NetService.layer)),
157 Effect.provide(Layer.mergeAll(layerConfig, NetService.layer)),
158158 );
159159 const desktop = yield* resolveServerConfig(
160160 { ...flags, mode: Option.some("desktop" as const) },
161161 Option.none(),
162 ).pipe(Effect.provide(Layer.mergeAll(configLayer, NetService.layer)));
162 ).pipe(Effect.provide(Layer.mergeAll(layerConfig, NetService.layer)));
163163
164164 expect(web.devAuthToken).toBeDefined();
165165 if (web.devAuthToken === undefined) {
......@@ -190,21 +190,21 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => {
190190 tailscaleServeEnabled: Option.none<boolean>(),
191191 tailscaleServePort: Option.none<number>(),
192192 };
193 const configLayer = ConfigProvider.layer(
193 const layerConfig = ConfigProvider.layer(
194194 ConfigProvider.fromEnv({ env: { T3CODE_DEV_AUTH_TOKEN: secret } }),
195195 );
196196 const error = yield* resolveServerConfig(flags, Option.none()).pipe(
197 Effect.provide(Layer.mergeAll(configLayer, NetService.layer)),
197 Effect.provide(Layer.mergeAll(layerConfig, NetService.layer)),
198198 Effect.flip,
199199 );
200200 const desktop = yield* resolveServerConfig(
201201 { ...flags, mode: Option.some("desktop" as const) },
202202 Option.none(),
203 ).pipe(Effect.provide(Layer.mergeAll(configLayer, NetService.layer)));
203 ).pipe(Effect.provide(Layer.mergeAll(layerConfig, NetService.layer)));
204204 const staticWeb = yield* resolveServerConfig(
205205 { ...flags, devUrl: Option.none() },
206206 Option.none(),
207 ).pipe(Effect.provide(Layer.mergeAll(configLayer, NetService.layer)));
207 ).pipe(Effect.provide(Layer.mergeAll(layerConfig, NetService.layer)));
208208
209209 expect(String(error)).not.toContain(secret);
210210 const serialized = yield* encodeUnknownJson(error);
apps/server/src/cli/connect.ts+8-11
......@@ -37,18 +37,15 @@ import {
3737 RELAY_URL_SECRET,
3838} from "../cloud/config.ts";
3939import { relayUrlConfig } from "../cloud/publicConfig.ts";
40import { headlessRelayClientTracingLayer } from "../cloud/relayTracing.ts";
40import * as RelayTracing from "../cloud/relayTracing.ts";
4141import * as ServerConfig from "../config.ts";
4242import * as ServerEnvironment from "../environment/ServerEnvironment.ts";
4343import * as ExternalLauncher from "../process/externalLauncher.ts";
4444import { readPersistedServerRuntimeState } from "../serverRuntimeState.ts";
4545import { projectLocationFlags, resolveCliAuthConfig } from "./config.ts";
4646import { resolveCliCommand } from "./invocation.ts";
47import {
48 bootServiceLayer,
49 offerServiceDuringOnboarding,
50 recoverServiceOnboardingOffer,
51} from "./service.ts";
47import { offerServiceDuringOnboarding, recoverServiceOnboardingOffer } from "./service.ts";
48import * as CliService from "./service.ts";
5249
5350const jsonFlag = Flag.Boolean("json").pipe(
5451 Flag.withDescription("Emit JSON instead of human-readable output."),
......@@ -434,22 +431,22 @@ const runCloudCommand = Effect.fn("cloud.cli.run_cloud_command")(function* <A, E
434431 const logLevel = yield* GlobalFlag.LogLevel;
435432 const config = yield* resolveCliAuthConfig(flags, logLevel);
436433 const minimumLogLevel = options?.quietLogs ? "Error" : config.logLevel;
437 const runtimeLayer = Layer.mergeAll(
434 const layerRuntime = Layer.mergeAll(
438435 ServerSecretStore.layer,
439436 CliTokenManager.layer.pipe(
440437 Layer.provide(ServerSecretStore.layer),
441438 Layer.provide(ExternalLauncher.layer),
442439 ),
443440 RelayClient.layerCloudflared({ baseDir: config.baseDir }),
444 EnvironmentAuth.runtimeLayer,
445 bootServiceLayer(config),
446 headlessRelayClientTracingLayer,
441 EnvironmentAuth.layerRuntime,
442 CliService.layer(config),
443 RelayTracing.layerHeadlessRelayClient,
447444 ).pipe(
448445 Layer.provideMerge(FetchHttpClient.layer),
449446 Layer.provideMerge(ServerConfig.layer(config)),
450447 Layer.provide(Layer.succeed(References.MinimumLogLevel, minimumLogLevel)),
451448 );
452 return yield* run.pipe(Effect.provide(runtimeLayer));
449 return yield* run.pipe(Effect.provide(layerRuntime));
453450});
454451
455452const connectedAs = (identity: string | null): string => (identity ? ` as ${identity}` : "");
apps/server/src/cli/pair.test.ts+2-2
......@@ -32,7 +32,7 @@ import {
3232
3333import packageJson from "../../package.json" with { type: "json" };
3434
35const CliRuntimeLayer = Layer.mergeAll(NodeServices.layer, NetService.layer);
35const layerCliRuntime = Layer.mergeAll(NodeServices.layer, NetService.layer);
3636
3737const baseState = {
3838 version: 1,
......@@ -94,7 +94,7 @@ describe("pair tailscale local target", () => {
9494const runCli = (args: ReadonlyArray<string>) => Command.runWith(cli, { version: "0.0.0" })(args);
9595
9696const provideCliTestLayers = <A, E, R>(effect: Effect.Effect<A, E, R>) =>
97 Effect.provide(effect, Layer.mergeAll(CliRuntimeLayer, TestConsole.layer));
97 Effect.provide(effect, Layer.mergeAll(layerCliRuntime, TestConsole.layer));
9898
9999// Console output accumulates across CLI runs within a test, and each
100100// Console.log call is one entry — so the latest command's output is the last
apps/server/src/cli/pair.ts+1-1
......@@ -437,7 +437,7 @@ const mintPairingLink = Effect.fn("pair.mintPairingLink")(function* (input: {
437437 });
438438 }).pipe(
439439 Effect.provide(
440 EnvironmentAuth.runtimeLayer.pipe(
440 EnvironmentAuth.layerRuntime.pipe(
441441 Layer.provide(ServerConfig.layer(input.config)),
442442 Layer.provide(Layer.succeed(References.MinimumLogLevel, input.config.logLevel)),
443443 ),
apps/server/src/cli/project.test.ts+5-8
......@@ -30,10 +30,7 @@ import * as ServerConfig from "../config.ts";
3030import * as EventSink from "../orchestration-v2/EventSink.ts";
3131import * as EventStore from "../orchestration-v2/EventStore.ts";
3232import * as ProjectionStore from "../orchestration-v2/ProjectionStore.ts";
33import {
34 OrchestrationV2EventSinkLayerLive,
35 ProjectServiceLayerLive,
36} from "../orchestration-v2/runtimeLayer.ts";
33import * as RuntimeLayer from "../orchestration-v2/runtimeLayer.ts";
3734import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts";
3835import * as ProjectEnrichmentService from "../project/ProjectEnrichmentService.ts";
3936import * as ProjectFaviconResolver from "../project/ProjectFaviconResolver.ts";
......@@ -47,9 +44,9 @@ import {
4744 projectCommandErrorFromLiveServerRequest,
4845} from "./project.ts";
4946
50const CliRuntimeLayer = Layer.mergeAll(NodeServices.layer, NetService.layer);
47const layerCliRuntime = Layer.mergeAll(NodeServices.layer, NetService.layer);
5148const runCli = (args: ReadonlyArray<string>) =>
52 Command.runWith(cli, { version: "0.0.0" })(args).pipe(Effect.provide(CliRuntimeLayer));
49 Command.runWith(cli, { version: "0.0.0" })(args).pipe(Effect.provide(layerCliRuntime));
5350
5451const makeConfig = (baseDir: string) =>
5552 Effect.gen(function* () {
......@@ -90,7 +87,7 @@ const makeConfig = (baseDir: string) =>
9087const readProjects = (baseDir: string) =>
9188 Effect.gen(function* () {
9289 const config = yield* makeConfig(baseDir);
93 const layer = ProjectServiceLayerLive.pipe(
90 const layer = RuntimeLayer.layerProjectService.pipe(
9491 Layer.provideMerge(ProjectEnrichmentService.layer),
9592 Layer.provideMerge(RepositoryIdentityResolver.layer),
9693 Layer.provideMerge(ProjectFaviconResolver.layer),
......@@ -171,7 +168,7 @@ const makeThreadPersistenceLayer = Effect.fn("ProjectCliTest.makeThreadPersisten
171168 function* (baseDir: string) {
172169 const config = yield* makeConfig(baseDir);
173170 return Layer.mergeAll(
174 OrchestrationV2EventSinkLayerLive,
171 RuntimeLayer.layerEventSink,
175172 ProjectionStore.layer,
176173 EventStore.layer,
177174 ).pipe(
apps/server/src/cli/project.ts+5-5
......@@ -25,7 +25,7 @@ import * as EnvironmentAuth from "../auth/EnvironmentAuth.ts";
2525
2626import * as ServerConfig from "../config.ts";
2727import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts";
28import { ProjectServiceLayerLive } from "../orchestration-v2/runtimeLayer.ts";
28import * as RuntimeLayer from "../orchestration-v2/runtimeLayer.ts";
2929import * as ProjectEnrichmentService from "../project/ProjectEnrichmentService.ts";
3030import * as ProjectFaviconResolver from "../project/ProjectFaviconResolver.ts";
3131import * as RepositoryIdentityResolver from "../project/RepositoryIdentityResolver.ts";
......@@ -196,7 +196,7 @@ const projectCommandUuid = Crypto.Crypto.pipe(
196196 ),
197197);
198198
199const ProjectCliRuntimeLive = ProjectServiceLayerLive.pipe(
199const layerProjectCliRuntime = RuntimeLayer.layerProjectService.pipe(
200200 Layer.provideMerge(ProjectEnrichmentService.layer),
201201 Layer.provideMerge(RepositoryIdentityResolver.layer),
202202 Layer.provideMerge(
......@@ -416,7 +416,7 @@ const runProjectMutation = Effect.fn("runProjectMutation")(function* (
416416 );
417417 }
418418
419 const offlineRuntimeLayer = ProjectCliRuntimeLive.pipe(
419 const layerOfflineRuntime = layerProjectCliRuntime.pipe(
420420 Layer.provide(ServerConfig.layer(config)),
421421 Layer.provide(Layer.succeed(References.MinimumLogLevel, minimumLogLevel)),
422422 );
......@@ -430,10 +430,10 @@ const runProjectMutation = Effect.fn("runProjectMutation")(function* (
430430 mode: "offline",
431431 });
432432 yield* Console.log(output);
433 }).pipe(Effect.provide(offlineRuntimeLayer));
433 }).pipe(Effect.provide(layerOfflineRuntime));
434434 }).pipe(
435435 Effect.provide(
436 Layer.mergeAll(EnvironmentAuth.runtimeLayer, WorkspacePaths.layer).pipe(
436 Layer.mergeAll(EnvironmentAuth.layerRuntime, WorkspacePaths.layer).pipe(
437437 Layer.provideMerge(FetchHttpClient.layer),
438438 Layer.provide(ServerConfig.layer(config)),
439439 Layer.provide(Layer.succeed(References.MinimumLogLevel, minimumLogLevel)),
apps/server/src/cli/service.ts+2-2
......@@ -13,7 +13,7 @@ import type * as ServerConfig from "../config.ts";
1313import * as ProcessRunner from "../processRunner.ts";
1414import { projectLocationFlags, resolveCliAuthConfig } from "./config.ts";
1515
16export const bootServiceLayer = (config: ServerConfig.ServerConfig["Service"]) =>
16export const layer = (config: ServerConfig.ServerConfig["Service"]) =>
1717 BootService.layer({
1818 baseDir: config.baseDir,
1919 logsDir: config.logsDir,
......@@ -107,7 +107,7 @@ const runServiceCommand = Effect.fn("cli.service.run")(function* <A, E>(
107107) {
108108 const logLevel = yield* GlobalFlag.LogLevel;
109109 const config = yield* resolveCliAuthConfig(flags, logLevel);
110 return yield* run.pipe(Effect.provide(bootServiceLayer(config)));
110 return yield* run.pipe(Effect.provide(layer(config)));
111111});
112112
113113const serviceReconcileFlags = {
apps/server/src/cli/uninstall.ts+2-2
......@@ -20,7 +20,7 @@ import { Command, Flag, GlobalFlag, Prompt } from "effect/cli";
2020import * as BootService from "../cloud/bootService.ts";
2121import { pinnedRuntimeVersionsDir } from "../cloud/pinnedRuntime.ts";
2222import { projectLocationFlags, resolveCliAuthConfig } from "./config.ts";
23import { bootServiceLayer } from "./service.ts";
23import * as CliService from "./service.ts";
2424import { findWindowsShim, launcherOwnsVersionsDir, resolveLauncherPath } from "./update.ts";
2525
2626export class CliUninstallError extends Schema.TaggedError<CliUninstallError>()(
......@@ -117,7 +117,7 @@ export const uninstallCommand = Command.make("uninstall", {
117117 const logLevel = yield* GlobalFlag.LogLevel;
118118 const config = yield* resolveCliAuthConfig(flags, logLevel);
119119 return yield* runUninstall({ baseDir: config.baseDir, assumeYes: flags.yes }).pipe(
120 Effect.provide(bootServiceLayer(config)),
120 Effect.provide(CliService.layer(config)),
121121 );
122122 }),
123123 ),
apps/server/src/cli/update.ts+2-2
......@@ -38,7 +38,7 @@ import * as ProcessRunner from "../processRunner.ts";
3838import { isProcessAlive, readPersistedServerRuntimeState } from "../serverRuntimeState.ts";
3939import { projectLocationFlags, resolveCliAuthConfig } from "./config.ts";
4040import { createUpdateProgress } from "./updateProgress.ts";
41import { bootServiceLayer } from "./service.ts";
41import * as CliService from "./service.ts";
4242
4343export class CliUpdateError extends Schema.TaggedError<CliUpdateError>()("CliUpdateError", {
4444 reason: Schema.String,
......@@ -267,7 +267,7 @@ export const updateCommand = Command.make("update", {
267267 assumeYes: flags.yes,
268268 }).pipe(
269269 Effect.provide(
270 Layer.mergeAll(bootServiceLayer(config), ProcessRunner.layer, FetchHttpClient.layer),
270 Layer.mergeAll(CliService.layer(config), ProcessRunner.layer, FetchHttpClient.layer),
271271 ),
272272 );
273273 }),
apps/server/src/cloud/CliState.test.ts+3-3
......@@ -29,7 +29,7 @@ const persistedCloudLinkSecrets = [
2929 PUBLISH_AGENT_ACTIVITY_SECRET,
3030] as const;
3131
32const makeTestLayer = () =>
32const layerTest = () =>
3333 ServerSecretStore.layer.pipe(
3434 Layer.provide(
3535 ServerConfig.layerTest(process.cwd(), {
......@@ -56,7 +56,7 @@ it.layer(NodeServices.layer)("CliState", (it) => {
5656 for (const name of persistedCloudLinkSecrets) {
5757 assert.isTrue(Option.isNone(yield* secrets.get(name)));
5858 }
59 }).pipe(Effect.provide(makeTestLayer())),
59 }).pipe(Effect.provide(layerTest())),
6060 );
6161
6262 it.effect("round-trips the desired link mode and defaults legacy links to managed", () =>
......@@ -79,6 +79,6 @@ it.layer(NodeServices.layer)("CliState", (it) => {
7979
8080 yield* CliState.setCliDesiredCloudLink(false);
8181 assert.equal(yield* CliState.readCliDesiredLinkMode, "managed");
82 }).pipe(Effect.provide(makeTestLayer())),
82 }).pipe(Effect.provide(layerTest())),
8383 );
8484});
apps/server/src/cloud/CliTokenManager.test.ts+7-7
......@@ -137,7 +137,7 @@ const tokenGranted = {
137137 }),
138138};
139139
140const makeDeviceFlowLayer = (server: DeviceFlowServer) =>
140const layerDeviceFlow = (server: DeviceFlowServer) =>
141141 Layer.succeed(
142142 HttpClient.HttpClient,
143143 HttpClient.make((request) =>
......@@ -177,7 +177,7 @@ it.layer(NodeServices.layer)("CliTokenManager.deviceAuthorizationLogin", (it) =>
177177 Effect.sync(() => {
178178 prompts.push(prompt);
179179 }),
180 ).pipe(Effect.provide(makeDeviceFlowLayer(server)), provideTestEnv, Effect.forkChild);
180 ).pipe(Effect.provide(layerDeviceFlow(server)), provideTestEnv, Effect.forkChild);
181181
182182 yield* TestClock.adjust(Duration.seconds(10));
183183 const { token, identity } = yield* Fiber.join(fiber);
......@@ -219,7 +219,7 @@ it.layer(NodeServices.layer)("CliTokenManager.deviceAuthorizationLogin", (it) =>
219219 };
220220
221221 const fiber = yield* CliTokenManager.deviceAuthorizationLogin(() => Effect.void).pipe(
222 Effect.provide(makeDeviceFlowLayer(server)),
222 Effect.provide(layerDeviceFlow(server)),
223223 provideTestEnv,
224224 Effect.forkChild,
225225 );
......@@ -245,7 +245,7 @@ it.layer(NodeServices.layer)("CliTokenManager.deviceAuthorizationLogin", (it) =>
245245 };
246246
247247 const fiber = yield* CliTokenManager.deviceAuthorizationLogin(() => Effect.void).pipe(
248 Effect.provide(makeDeviceFlowLayer(server)),
248 Effect.provide(layerDeviceFlow(server)),
249249 provideTestEnv,
250250 Effect.forkChild,
251251 );
......@@ -270,7 +270,7 @@ it.layer(NodeServices.layer)("CliTokenManager.deviceAuthorizationLogin", (it) =>
270270 };
271271
272272 const fiber = yield* CliTokenManager.deviceAuthorizationLogin(() => Effect.void).pipe(
273 Effect.provide(makeDeviceFlowLayer(server)),
273 Effect.provide(layerDeviceFlow(server)),
274274 provideTestEnv,
275275 Effect.flip,
276276 Effect.forkChild,
......@@ -291,7 +291,7 @@ it.layer(NodeServices.layer)("CliTokenManager.deviceAuthorizationLogin", (it) =>
291291 };
292292
293293 const fiber = yield* CliTokenManager.deviceAuthorizationLogin(() => Effect.void).pipe(
294 Effect.provide(makeDeviceFlowLayer(server)),
294 Effect.provide(layerDeviceFlow(server)),
295295 provideTestEnv,
296296 Effect.flip,
297297 Effect.forkChild,
......@@ -311,7 +311,7 @@ it.layer(NodeServices.layer)("CliTokenManager.deviceAuthorizationLogin", (it) =>
311311 };
312312
313313 const fiber = yield* CliTokenManager.deviceAuthorizationLogin(() => Effect.void).pipe(
314 Effect.provide(makeDeviceFlowLayer(server)),
314 Effect.provide(layerDeviceFlow(server)),
315315 provideTestEnv,
316316 Effect.flip,
317317 Effect.forkChild,
apps/server/src/cloud/CliTokenManager.ts+2-2
......@@ -461,7 +461,7 @@ export const make = Effect.gen(function* () {
461461 const hostedAppUrl = yield* hostedAppUrlConfig;
462462 const { verifier, challenge, state } = yield* makePkceRequest;
463463 const callback = yield* Deferred.make<string>();
464 const callbackRoute = HttpRouter.add(
464 const layerCallbackRoute = HttpRouter.add(
465465 "GET",
466466 "/callback",
467467 Effect.gen(function* () {
......@@ -477,7 +477,7 @@ export const make = Effect.gen(function* () {
477477 return HttpServerResponse.html(renderLoopbackAuthorizationCompleteHtml());
478478 }),
479479 );
480 yield* HttpRouter.serve(callbackRoute, {
480 yield* HttpRouter.serve(layerCallbackRoute, {
481481 disableListenLog: true,
482482 disableLogger: true,
483483 }).pipe(
apps/server/src/cloud/CloudPreferences.test.ts+2-2
......@@ -61,7 +61,7 @@ const withService = <A, E>(
6161 },
6262 { preconnect: () => {} },
6363 );
64 const dependencies = Layer.mergeAll(
64 const layerDependencies = Layer.mergeAll(
6565 Layer.mock(ServerSecretStore.ServerSecretStore)({
6666 get: (name) =>
6767 (options.failActivityRead && name === PUBLISH_AGENT_ACTIVITY_SECRET) ||
......@@ -93,7 +93,7 @@ const withService = <A, E>(
9393 const preferences = yield* CloudPreferences.CloudPreferences;
9494 return yield* body({ preferences, stored, relayCalls });
9595 }).pipe(
96 Effect.provide(CloudPreferences.layer.pipe(Layer.provide(dependencies))),
96 Effect.provide(CloudPreferences.layer.pipe(Layer.provide(layerDependencies))),
9797 Effect.provideService(FetchHttpClient.Fetch, fetch),
9898 );
9999 });
apps/server/src/cloud/ManagedEndpointRuntime.test.ts+3-3
......@@ -18,7 +18,7 @@ import * as RelayClient from "@t3tools/shared/relayClient";
1818import * as ServerSecretStore from "../auth/ServerSecretStore.ts";
1919import * as ManagedEndpointRuntime from "./ManagedEndpointRuntime.ts";
2020
21const relayClientAvailableLayer = Layer.succeed(
21const layerRelayClientAvailable = Layer.succeed(
2222 RelayClient.RelayClient,
2323 RelayClient.RelayClient.of({
2424 resolve: Effect.succeed({
......@@ -34,7 +34,7 @@ const relayClientAvailableLayer = Layer.succeed(
3434
3535const runtimeDependencies = (
3636 spawner: ReturnType<typeof ChildProcessSpawner.make>,
37 relayClientLayer = relayClientAvailableLayer,
37 relayClientLayer = layerRelayClientAvailable,
3838) =>
3939 Layer.mergeAll(
4040 Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner),
......@@ -46,7 +46,7 @@ const runtimeDependencies = (
4646
4747const buildCloudManagedEndpointRuntime = (
4848 spawner: ReturnType<typeof ChildProcessSpawner.make>,
49 relayClientLayer = relayClientAvailableLayer,
49 relayClientLayer = layerRelayClientAvailable,
5050) =>
5151 Effect.gen(function* () {
5252 const context = yield* Layer.build(
apps/server/src/cloud/environmentKeys.test.ts+3-3
......@@ -9,7 +9,7 @@ import * as ServerSecretStore from "../auth/ServerSecretStore.ts";
99import * as ServerConfig from "../config.ts";
1010import { getOrCreateEnvironmentKeyPairFromSecretStore } from "./environmentKeys.ts";
1111
12const makeServerSecretStoreLayer = () =>
12const layerServerSecretStore = () =>
1313 ServerSecretStore.layer.pipe(
1414 Layer.provide(ServerConfig.layerTest(process.cwd(), { prefix: "t3-environment-keys-test-" })),
1515 );
......@@ -28,7 +28,7 @@ it.layer(NodeServices.layer)("getOrCreateEnvironmentKeyPairFromSecretStore", (it
2828 assert.isTrue(Option.isSome(yield* secretStore.get("cloud-link-ed25519-key-pair")));
2929 assert.isTrue(Option.isNone(yield* secretStore.get("cloud-link-ed25519-private-key")));
3030 assert.isTrue(Option.isNone(yield* secretStore.get("cloud-link-ed25519-public-key")));
31 }).pipe(Effect.provide(makeServerSecretStoreLayer())),
31 }).pipe(Effect.provide(layerServerSecretStore())),
3232 );
3333
3434 it.effect("migrates a legacy keypair into the atomic secret", () =>
......@@ -42,7 +42,7 @@ it.layer(NodeServices.layer)("getOrCreateEnvironmentKeyPairFromSecretStore", (it
4242 publicKey: "public",
4343 });
4444 assert.isTrue(Option.isSome(yield* secretStore.get("cloud-link-ed25519-key-pair")));
45 }).pipe(Effect.provide(makeServerSecretStoreLayer())),
45 }).pipe(Effect.provide(layerServerSecretStore())),
4646 );
4747
4848 it.effect("uses the persisted keypair when a concurrent creator wins", () =>
apps/server/src/cloud/http.ts+1-1
......@@ -1594,7 +1594,7 @@ const cloudMintCredentialHandler = Effect.fn("environment.cloud.mintCredential")
15941594 ),
15951595);
15961596
1597export const connectHttpApiLayer = HttpApiBuilder.group(
1597export const layer = HttpApiBuilder.group(
15981598 EnvironmentHttpApi,
15991599 "connect",
16001600 Effect.fnUntraced(function* (handlers) {
apps/server/src/cloud/relayTracing.ts+7-10
......@@ -1,19 +1,16 @@
1import { makeRelayClientTracingLayer } from "@t3tools/shared/relayTracing";
1import * as RelayTracing from "@t3tools/shared/relayTracing";
22
33import { resolveRelayClientTracingConfig } from "./publicConfig.ts";
44
55const relayClientTracingConfig = resolveRelayClientTracingConfig();
66
7export const headlessRelayClientTracingLayer = makeRelayClientTracingLayer(
8 relayClientTracingConfig,
9 {
10 serviceName: "t3code-server",
11 runtime: "node",
12 client: "headless-cli",
13 },
14);
7export const layerHeadlessRelayClient = RelayTracing.layer(relayClientTracingConfig, {
8 serviceName: "t3code-server",
9 runtime: "node",
10 client: "headless-cli",
11});
1512
16export const serverRelayBrokerTracingLayer = makeRelayClientTracingLayer(relayClientTracingConfig, {
13export const layerServerRelayBroker = RelayTracing.layer(relayClientTracingConfig, {
1714 serviceName: "t3code-server",
1815 runtime: "node",
1916 client: "environment-server",
apps/server/src/device/DeviceHubProxy.test.ts+2-2
......@@ -11,7 +11,7 @@ import * as Layer from "effect/Layer";
1111import { HttpClient, HttpClientResponse, HttpRouter } from "effect/http";
1212import * as EnvironmentAuth from "../auth/EnvironmentAuth.ts";
1313import * as DeviceService from "./DeviceService.ts";
14import { deviceHubProxyRouteLayer } from "./DeviceHubProxy.ts";
14import * as DeviceHubProxy from "./DeviceHubProxy.ts";
1515
1616const disposers: Array<() => Promise<void>> = [];
1717afterEach(async () => {
......@@ -36,7 +36,7 @@ const fixture = (
3636 }),
3737 );
3838 const { handler, dispose } = HttpRouter.toWebHandler(
39 deviceHubProxyRouteLayer.pipe(
39 DeviceHubProxy.layer.pipe(
4040 Layer.provideMerge(
4141 Layer.succeed(EnvironmentAuth.EnvironmentAuth, {
4242 authenticateWebSocketUpgrade: () =>
apps/server/src/device/DeviceHubProxy.ts+1-5
......@@ -230,8 +230,4 @@ const handler = Effect.gen(function* () {
230230 return yield* proxyHttp(request, `${ready.hub.origin}${upstreamPath}`, ready.hub.origin);
231231});
232232
233export const deviceHubProxyRouteLayer = HttpRouter.add(
234 "*",
235 `${DeviceService.DEVICE_HUB_ROUTE_PREFIX}/*`,
236 handler,
237);
233export const layer = HttpRouter.add("*", `${DeviceService.DEVICE_HUB_ROUTE_PREFIX}/*`, handler);
apps/server/src/diagnostics/ProcessDiagnostics.test.ts+13-13
......@@ -31,11 +31,11 @@ function makeNativeSnapshot(
3131 };
3232}
3333
34function makeTelemetryLayer(
34function layerTelemetryFor(
3535 snapshot: ResourceMonitorSnapshotEvent,
3636 desktopSnapshot?: DesktopHostTelemetrySnapshot,
3737) {
38 const nativeLayer = NativeTelemetryClient.layerTest({
38 const layerNative = NativeTelemetryClient.layerTest({
3939 sampleNow: Effect.succeed({ generation: 0, snapshot }),
4040 health: Effect.succeed({
4141 status: "healthy",
......@@ -46,7 +46,7 @@ function makeTelemetryLayer(
4646 sampleIntervalMs: 1_000,
4747 }),
4848 });
49 const desktopLayer = desktopSnapshot
49 const layerDesktop = desktopSnapshot
5050 ? DesktopTelemetryReceiver.layerTest({
5151 latest: Effect.succeedSome(desktopSnapshot),
5252 health: Effect.succeed({
......@@ -57,7 +57,7 @@ function makeTelemetryLayer(
5757 })
5858 : DesktopTelemetryReceiver.layerTest();
5959 return ResourceTelemetry.layer.pipe(
60 Layer.provide(Layer.mergeAll(nativeLayer, desktopLayer, ResourceAttribution.layer)),
60 Layer.provide(Layer.mergeAll(layerNative, layerDesktop, ResourceAttribution.layer)),
6161 );
6262}
6363
......@@ -98,8 +98,8 @@ describe("ProcessDiagnostics", () => {
9898 ioSemantics: "storage",
9999 },
100100 ]);
101 const telemetryLayer = makeTelemetryLayer(snapshot);
102 const layer = ProcessDiagnostics.layer.pipe(Layer.provideMerge(telemetryLayer));
101 const layerTelemetry = layerTelemetryFor(snapshot);
102 const layer = ProcessDiagnostics.layer.pipe(Layer.provideMerge(layerTelemetry));
103103
104104 const diagnostics = yield* Effect.gen(function* () {
105105 const processDiagnostics = yield* ProcessDiagnostics.ProcessDiagnostics;
......@@ -116,8 +116,8 @@ describe("ProcessDiagnostics", () => {
116116 it.effect("rejects stale process identities before signaling", () =>
117117 Effect.gen(function* () {
118118 const snapshot = makeNativeSnapshot([]);
119 const telemetryLayer = makeTelemetryLayer(snapshot);
120 const layer = ProcessDiagnostics.layer.pipe(Layer.provide(telemetryLayer));
119 const layerTelemetry = layerTelemetryFor(snapshot);
120 const layer = ProcessDiagnostics.layer.pipe(Layer.provide(layerTelemetry));
121121
122122 const result = yield* Effect.service(ProcessDiagnostics.ProcessDiagnostics).pipe(
123123 Effect.flatMap((processDiagnostics) =>
......@@ -161,9 +161,9 @@ describe("ProcessDiagnostics", () => {
161161 ]);
162162 const staleTelemetry = yield* Effect.service(ResourceTelemetry.ResourceTelemetry).pipe(
163163 Effect.flatMap((telemetry) => telemetry.latest),
164 Effect.provide(makeTelemetryLayer(snapshot)),
164 Effect.provide(layerTelemetryFor(snapshot)),
165165 );
166 const telemetryLayer = Layer.succeed(
166 const layerTelemetry = Layer.succeed(
167167 ResourceTelemetry.ResourceTelemetry,
168168 ResourceTelemetry.ResourceTelemetry.of({
169169 latest: Effect.succeed(staleTelemetry),
......@@ -180,7 +180,7 @@ describe("ProcessDiagnostics", () => {
180180 retry: Effect.die("unused"),
181181 }),
182182 );
183 const layer = ProcessDiagnostics.layer.pipe(Layer.provide(telemetryLayer));
183 const layer = ProcessDiagnostics.layer.pipe(Layer.provide(layerTelemetry));
184184
185185 const result = yield* Effect.service(ProcessDiagnostics.ProcessDiagnostics).pipe(
186186 Effect.flatMap((processDiagnostics) =>
......@@ -228,7 +228,7 @@ describe("ProcessDiagnostics", () => {
228228 },
229229 ]);
230230 const sampledAt = DateTime.makeUnsafe(sampledAtUnixMs);
231 const telemetryLayer = makeTelemetryLayer(snapshot, {
231 const layerTelemetry = layerTelemetryFor(snapshot, {
232232 version: 1,
233233 type: "desktopTelemetry",
234234 sequence: 1,
......@@ -260,7 +260,7 @@ describe("ProcessDiagnostics", () => {
260260 },
261261 ],
262262 });
263 const layer = ProcessDiagnostics.layer.pipe(Layer.provide(telemetryLayer));
263 const layer = ProcessDiagnostics.layer.pipe(Layer.provide(layerTelemetry));
264264
265265 const result = yield* Effect.service(ProcessDiagnostics.ProcessDiagnostics).pipe(
266266 Effect.flatMap((processDiagnostics) =>
apps/server/src/environment/DirectEndpoints.test.ts+4-4
......@@ -135,7 +135,7 @@ const TAILSCALE_STATUS_JSON = JSON.stringify({
135135});
136136
137137/** `tailscale status --json` reporting a MagicDNS name. */
138const tailscaleUpLayer = Layer.succeed(
138const layerTailscaleUp = Layer.succeed(
139139 ChildProcessSpawner.ChildProcessSpawner,
140140 ChildProcessSpawner.make(() =>
141141 Effect.succeed(
......@@ -157,7 +157,7 @@ const tailscaleUpLayer = Layer.succeed(
157157);
158158
159159/** Answers the Serve probe with `status`. */
160const serveProbeLayer = (status: number) =>
160const layerServeProbe = (status: number) =>
161161 Layer.succeed(
162162 HttpClient.HttpClient,
163163 HttpClient.make((request) =>
......@@ -166,7 +166,7 @@ const serveProbeLayer = (status: number) =>
166166 );
167167
168168/** A loopback-only server with Tailscale Serve on, so only the Serve name can be listed. */
169const serveConfigLayer = Layer.effect(
169const layerServeConfig = Layer.effect(
170170 ServerConfig.ServerConfig,
171171 Effect.map(ServerConfig.ServerConfig, (config) => ({
172172 ...config,
......@@ -184,7 +184,7 @@ const resolveWithServe = (probeStatus: number) =>
184184 Effect.provide(
185185 DirectEndpoints.layer.pipe(
186186 Layer.provide(
187 Layer.mergeAll(serveConfigLayer, tailscaleUpLayer, serveProbeLayer(probeStatus)),
187 Layer.mergeAll(layerServeConfig, layerTailscaleUp, layerServeProbe(probeStatus)),
188188 ),
189189 ),
190190 ),
apps/server/src/environment/RemoteOpenTargets.test.ts+3-3
......@@ -17,7 +17,7 @@ const TAILSCALE_STATUS_JSON = JSON.stringify({
1717});
1818
1919/** Spawner whose `tailscale status --json` exits with the given output. */
20const spawnerLayer = (input: { readonly exitCode: number; readonly stdout: string }) =>
20const layerSpawner = (input: { readonly exitCode: number; readonly stdout: string }) =>
2121 Layer.succeed(
2222 ChildProcessSpawner.ChildProcessSpawner,
2323 ChildProcessSpawner.make(() =>
......@@ -39,7 +39,7 @@ const spawnerLayer = (input: { readonly exitCode: number; readonly stdout: strin
3939 ),
4040 );
4141
42const netLayer = (input: { readonly ipv4: boolean; readonly ipv6: boolean }) =>
42const layerNet = (input: { readonly ipv4: boolean; readonly ipv6: boolean }) =>
4343 Layer.succeed(NetService.NetService, {
4444 canListenOnHost: () => Effect.succeed(true),
4545 isPortAvailableOnLoopback: () => Effect.succeed(true),
......@@ -57,7 +57,7 @@ const resolveTargets = (input: {
5757 Effect.provideService(HostProcessHostname, input.hostname),
5858 Effect.provide(
5959 RemoteOpenTargets.layer.pipe(
60 Layer.provide(Layer.mergeAll(netLayer(input.sshd), spawnerLayer(input.tailscale))),
60 Layer.provide(Layer.mergeAll(layerNet(input.sshd), layerSpawner(input.tailscale))),
6161 ),
6262 ),
6363 );
apps/server/src/environment/ServerEnvironment.test.ts+11-11
......@@ -31,13 +31,13 @@ const isServerEnvironmentIdPersistenceError = Schema.is(
3131 ServerEnvironment.ServerEnvironmentIdPersistenceError,
3232);
3333
34const makeServerEnvironmentLayer = (baseDir: string) =>
34const layerServerEnvironment = (baseDir: string) =>
3535 ServerEnvironment.layer.pipe(
3636 Layer.provide(ServerSecretStore.layer),
3737 Layer.provide(ServerConfig.layerTest(process.cwd(), baseDir)),
3838 );
3939
40const emptySecretStoreLayer = Layer.succeed(
40const layerEmptySecretStore = Layer.succeed(
4141 ServerSecretStore.ServerSecretStore,
4242 ServerSecretStore.ServerSecretStore.of({
4343 get: () => Effect.succeedNone,
......@@ -108,7 +108,7 @@ it.layer(NodeServices.layer)("ServerEnvironmentLive", (it) => {
108108 }).pipe(
109109 Effect.provide(
110110 ServerEnvironment.layer.pipe(
111 Layer.provide(emptySecretStoreLayer),
111 Layer.provide(layerEmptySecretStore),
112112 Layer.provide(ServerConfig.layer({ ...config, mode })),
113113 ),
114114 ),
......@@ -152,7 +152,7 @@ it.layer(NodeServices.layer)("ServerEnvironmentLive", (it) => {
152152 return yield* identity.getEnvironmentId;
153153 }).pipe(
154154 Effect.tap(() => Deferred.succeed(firstInitialized, undefined)),
155 Effect.provide(Layer.fresh(ServerEnvironment.identityLayer)),
155 Effect.provide(Layer.fresh(ServerEnvironment.layerIdentity)),
156156 Effect.provideService(ServerConfig.ServerConfig, serverConfig),
157157 Effect.provideService(FileSystem.FileSystem, {
158158 ...fileSystem,
......@@ -207,11 +207,11 @@ it.layer(NodeServices.layer)("ServerEnvironmentLive", (it) => {
207207 const first = yield* Effect.gen(function* () {
208208 const serverEnvironment = yield* ServerEnvironment.ServerEnvironment;
209209 return yield* serverEnvironment.getDescriptor;
210 }).pipe(Effect.provide(makeServerEnvironmentLayer(baseDir)));
210 }).pipe(Effect.provide(layerServerEnvironment(baseDir)));
211211 const second = yield* Effect.gen(function* () {
212212 const serverEnvironment = yield* ServerEnvironment.ServerEnvironment;
213213 return yield* serverEnvironment.getDescriptor;
214 }).pipe(Effect.provide(makeServerEnvironmentLayer(baseDir)));
214 }).pipe(Effect.provide(layerServerEnvironment(baseDir)));
215215
216216 expect(first.environmentId).toBe(second.environmentId);
217217 expect(first.orchestrationProtocolVersion).toBe(ORCHESTRATION_PROTOCOL_VERSION);
......@@ -237,7 +237,7 @@ it.layer(NodeServices.layer)("ServerEnvironmentLive", (it) => {
237237 const baseDir = yield* fileSystem.makeTempDirectoryScoped({
238238 prefix: "t3-server-environment-publish-test-",
239239 });
240 const testLayer = Layer.mergeAll(
240 const layerTest = Layer.mergeAll(
241241 ServerEnvironment.layer.pipe(Layer.provide(ServerSecretStore.layer)),
242242 ServerSecretStore.layer,
243243 ).pipe(Layer.provide(ServerConfig.layerTest(process.cwd(), baseDir)));
......@@ -273,7 +273,7 @@ it.layer(NodeServices.layer)("ServerEnvironmentLive", (it) => {
273273 yield* secrets.set(PUBLISH_AGENT_ACTIVITY_SECRET, encode("false"));
274274 const disabled = yield* serverEnvironment.getDescriptor;
275275 expect(disabled.capabilities.agentActivityPublishing).toBe(false);
276 }).pipe(Effect.provide(testLayer));
276 }).pipe(Effect.provide(layerTest));
277277 }),
278278 );
279279
......@@ -342,7 +342,7 @@ it.layer(NodeServices.layer)("ServerEnvironmentLive", (it) => {
342342 description: "permission denied",
343343 pathOrDescriptor: environmentIdPath,
344344 });
345 const failingFileSystemLayer = FileSystem.layerNoop({
345 const layerFailingFileSystem = FileSystem.layerNoop({
346346 exists: () =>
347347 operation === "check" ? Effect.fail(cause) : Effect.succeed(operation === "read"),
348348 readFileString: () => Effect.fail(cause),
......@@ -359,8 +359,8 @@ it.layer(NodeServices.layer)("ServerEnvironmentLive", (it) => {
359359 }).pipe(
360360 Effect.provide(
361361 ServerEnvironment.layer.pipe(
362 Layer.provide(emptySecretStoreLayer),
363 Layer.provide(Layer.merge(ServerConfig.layer(serverConfig), failingFileSystemLayer)),
362 Layer.provide(layerEmptySecretStore),
363 Layer.provide(Layer.merge(ServerConfig.layer(serverConfig), layerFailingFileSystem)),
364364 ),
365365 ),
366366 Effect.flip,
apps/server/src/environment/ServerEnvironment.ts+2-2
......@@ -275,7 +275,7 @@ export const make = Effect.gen(function* () {
275275 });
276276});
277277
278export const identityLayer = Layer.effect(ServerEnvironmentIdentity, makeIdentity);
278export const layerIdentity = Layer.effect(ServerEnvironmentIdentity, makeIdentity);
279279
280280/**
281281 * ServerEnvironment is acquired from persisted filesystem and host-process
......@@ -284,6 +284,6 @@ export const identityLayer = Layer.effect(ServerEnvironmentIdentity, makeIdentit
284284 * ServerSecretStore backing the descriptor's publishing capability.
285285 */
286286export const layer = Layer.effect(ServerEnvironment, make).pipe(
287 Layer.provideMerge(identityLayer),
287 Layer.provideMerge(layerIdentity),
288288 Layer.provide(ProcessRunner.layer),
289289);
apps/server/src/environment/ServerEnvironmentLabel.test.ts+18-14
......@@ -27,16 +27,16 @@ interface CapturedLog {
2727
2828const runMock = vi.fn<ProcessRunner.ProcessRunner["Service"]["run"]>();
2929
30const ProcessRunnerTest = Layer.succeed(
30const layerProcessRunnerTest = Layer.succeed(
3131 ProcessRunner.ProcessRunner,
3232 ProcessRunner.ProcessRunner.of({
3333 run: (input) => runMock(input),
3434 }),
3535);
36const NoopFileSystemLayer = FileSystem.layerNoop({});
37const TestLayer = Layer.merge(NoopFileSystemLayer, ProcessRunnerTest);
38const LinuxMachineInfoLayer = Layer.merge(
39 ProcessRunnerTest,
36const layerNoopFileSystem = FileSystem.layerNoop({});
37const layerTest = Layer.merge(layerNoopFileSystem, layerProcessRunnerTest);
38const layerLinuxMachineInfo = Layer.merge(
39 layerProcessRunnerTest,
4040 FileSystem.layerNoop({
4141 exists: (path) => Effect.succeed(path === "/etc/machine-info"),
4242 readFileString: (path) =>
......@@ -65,7 +65,7 @@ describe("resolveServerEnvironmentLabel", () => {
6565 Effect.gen(function* () {
6666 const result = yield* ServerEnvironmentLabel.resolveServerEnvironmentLabel({
6767 cwdBaseName: "t3code",
68 }).pipe(Effect.provide(withHostPlatform(TestLayer, "win32", "macbook-pro")));
68 }).pipe(Effect.provide(withHostPlatform(layerTest, "win32", "macbook-pro")));
6969
7070 expect(result).toBe("macbook-pro");
7171 }),
......@@ -88,7 +88,7 @@ describe("resolveServerEnvironmentLabel", () => {
8888
8989 const result = yield* ServerEnvironmentLabel.resolveServerEnvironmentLabel({
9090 cwdBaseName: "t3code",
91 }).pipe(Effect.provide(withHostPlatform(TestLayer, "darwin", "macbook-pro")));
91 }).pipe(Effect.provide(withHostPlatform(layerTest, "darwin", "macbook-pro")));
9292
9393 expect(result).toBe("Julius's MacBook Pro");
9494 expect(runMock).toHaveBeenCalledWith(
......@@ -105,7 +105,7 @@ describe("resolveServerEnvironmentLabel", () => {
105105 Effect.gen(function* () {
106106 const result = yield* ServerEnvironmentLabel.resolveServerEnvironmentLabel({
107107 cwdBaseName: "t3code",
108 }).pipe(Effect.provide(withHostPlatform(LinuxMachineInfoLayer, "linux", "buildbox")));
108 }).pipe(Effect.provide(withHostPlatform(layerLinuxMachineInfo, "linux", "buildbox")));
109109
110110 expect(result).toBe("Build Agent 01");
111111 expect(runMock).not.toHaveBeenCalled();
......@@ -129,7 +129,7 @@ describe("resolveServerEnvironmentLabel", () => {
129129
130130 const result = yield* ServerEnvironmentLabel.resolveServerEnvironmentLabel({
131131 cwdBaseName: "t3code",
132 }).pipe(Effect.provide(withHostPlatform(TestLayer, "linux", "runner-01")));
132 }).pipe(Effect.provide(withHostPlatform(layerTest, "linux", "runner-01")));
133133
134134 expect(result).toBe("CI Runner");
135135 expect(runMock).toHaveBeenCalledWith(
......@@ -146,7 +146,7 @@ describe("resolveServerEnvironmentLabel", () => {
146146 Effect.gen(function* () {
147147 const result = yield* ServerEnvironmentLabel.resolveServerEnvironmentLabel({
148148 cwdBaseName: "t3code",
149 }).pipe(Effect.provide(withHostPlatform(TestLayer, "win32", "JULIUS-LAPTOP")));
149 }).pipe(Effect.provide(withHostPlatform(layerTest, "win32", "JULIUS-LAPTOP")));
150150
151151 expect(result).toBe("JULIUS-LAPTOP");
152152 }),
......@@ -192,7 +192,7 @@ describe("resolveServerEnvironmentLabel", () => {
192192 }).pipe(
193193 Effect.provide(
194194 Layer.mergeAll(
195 withHostPlatform(TestLayer, "darwin", "macbook-pro"),
195 withHostPlatform(layerTest, "darwin", "macbook-pro"),
196196 Logger.layer([logger], { mergeWithExisting: false }),
197197 Layer.succeed(References.MinimumLogLevel, "Debug"),
198198 ),
......@@ -216,7 +216,7 @@ describe("resolveServerEnvironmentLabel", () => {
216216 pathOrDescriptor: "/etc/machine-info",
217217 cause: fileCause,
218218 });
219 const fileSystemLayer = FileSystem.layerNoop({
219 const layerFileSystem = FileSystem.layerNoop({
220220 exists: () => Effect.fail(platformError),
221221 });
222222 runMock.mockReturnValueOnce(
......@@ -252,7 +252,11 @@ describe("resolveServerEnvironmentLabel", () => {
252252 }).pipe(
253253 Effect.provide(
254254 Layer.mergeAll(
255 withHostPlatform(Layer.merge(ProcessRunnerTest, fileSystemLayer), "linux", "buildbox"),
255 withHostPlatform(
256 Layer.merge(layerProcessRunnerTest, layerFileSystem),
257 "linux",
258 "buildbox",
259 ),
256260 Logger.layer([logger], { mergeWithExisting: false }),
257261 Layer.succeed(References.MinimumLogLevel, "Debug"),
258262 ),
......@@ -277,7 +281,7 @@ describe("resolveServerEnvironmentLabel", () => {
277281
278282 const result = yield* ServerEnvironmentLabel.resolveServerEnvironmentLabel({
279283 cwdBaseName: "t3code",
280 }).pipe(Effect.provide(withHostPlatform(TestLayer, "linux", " ")));
284 }).pipe(Effect.provide(withHostPlatform(layerTest, "linux", " ")));
281285
282286 expect(result).toBe("t3code");
283287 }),
apps/server/src/environment/ServerEnvironmentMachine.test.ts+6-6
......@@ -16,7 +16,7 @@ import {
1616
1717const runMock = vi.fn<ProcessRunner.ProcessRunner["Service"]["run"]>();
1818
19const ProcessRunnerTest = Layer.succeed(
19const layerProcessRunnerTest = Layer.succeed(
2020 ProcessRunner.ProcessRunner,
2121 ProcessRunner.ProcessRunner.of({ run: (input) => runMock(input) }),
2222);
......@@ -33,7 +33,7 @@ const processOutput = (stdout: string, code = 0) =>
3333 stderrInvalidUtf8: false,
3434 });
3535
36const dmiFileSystem = (files: Readonly<Record<string, string>>) =>
36const layerDmiFileSystem = (files: Readonly<Record<string, string>>) =>
3737 FileSystem.layerNoop({
3838 readFileString: (path) => {
3939 const name = path.slice(path.lastIndexOf("/") + 1);
......@@ -52,7 +52,7 @@ const dmiFileSystem = (files: Readonly<Record<string, string>>) =>
5252 });
5353
5454const withPlatform = (platform: NodeJS.Platform, fileSystem = FileSystem.layerNoop({})) =>
55 Layer.mergeAll(ProcessRunnerTest, fileSystem, Layer.succeed(HostProcessPlatform, platform));
55 Layer.mergeAll(layerProcessRunnerTest, fileSystem, Layer.succeed(HostProcessPlatform, platform));
5656
5757afterEach(() => {
5858 runMock.mockReset();
......@@ -188,7 +188,7 @@ describe("detectServerEnvironmentMachineKind", () => {
188188 Effect.provide(
189189 withPlatform(
190190 "linux",
191 dmiFileSystem({
191 layerDmiFileSystem({
192192 chassis_type: "3\n",
193193 sys_vendor: "GMKtec\n",
194194 product_name: "NucBox K8 Plus\n",
......@@ -208,7 +208,7 @@ describe("detectServerEnvironmentMachineKind", () => {
208208 Effect.provide(
209209 withPlatform(
210210 "linux",
211 dmiFileSystem({
211 layerDmiFileSystem({
212212 osrelease: "5.15.153.1-microsoft-standard-WSL2\n",
213213 chassis_type: "3\n",
214214 sys_vendor: "Microsoft Corporation\n",
......@@ -225,7 +225,7 @@ describe("detectServerEnvironmentMachineKind", () => {
225225 it.effect("returns null on Linux without DMI (containers, ARM boards)", () =>
226226 Effect.gen(function* () {
227227 const result = yield* detectServerEnvironmentMachineKind().pipe(
228 Effect.provide(withPlatform("linux", dmiFileSystem({}))),
228 Effect.provide(withPlatform("linux", layerDmiFileSystem({}))),
229229 );
230230
231231 expect(result).toBeNull();
apps/server/src/git/GitManager.test.ts+17-17
......@@ -53,7 +53,7 @@ import * as SourceControlProviderRegistry from "../sourceControl/SourceControlPr
5353import * as ServerConfig from "../config.ts";
5454import * as ProjectionStore from "../orchestration-v2/ProjectionStore.ts";
5555import * as ProjectStore from "../orchestration-v2/ProjectStore.ts";
56import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts";
56import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts";
5757import * as ProjectSetupScriptRunner from "../project/ProjectSetupScriptRunner.ts";
5858import * as ProviderRegistry from "../provider/Services/ProviderRegistry.ts";
5959import * as ServerSettings from "../serverSettings.ts";
......@@ -679,13 +679,13 @@ function makeManager(input?: {
679679}) {
680680 const { service: gitHubCli, ghCalls } = createGitHubCliWithFakeGh(input?.ghScenario);
681681 const textGeneration = createTextGeneration(input?.textGeneration);
682 const serverConfigLayer = ServerConfig.layerTest(process.cwd(), {
682 const layerServerConfig = ServerConfig.layerTest(process.cwd(), {
683683 prefix: "t3-git-manager-test-",
684684 });
685685
686 const serverSettingsLayer = ServerSettings.ServerSettingsService.layerTest(input?.serverSettings);
686 const layerServerSettings = ServerSettings.ServerSettingsService.layerTest(input?.serverSettings);
687687
688 const vcsDriverLayer = input?.gitConfigReads
688 const layerVcsDriver = input?.gitConfigReads
689689 ? Layer.effect(
690690 GitVcsDriver.GitVcsDriver,
691691 GitVcsDriver.make.pipe(
......@@ -702,14 +702,14 @@ function makeManager(input?: {
702702 ).pipe(
703703 Layer.provideMerge(VcsProcess.layer),
704704 Layer.provideMerge(NodeServices.layer),
705 Layer.provideMerge(serverConfigLayer),
705 Layer.provideMerge(layerServerConfig),
706706 )
707707 : GitVcsDriver.layer.pipe(
708708 Layer.provideMerge(VcsProcess.layer),
709709 Layer.provideMerge(NodeServices.layer),
710 Layer.provideMerge(serverConfigLayer),
710 Layer.provideMerge(layerServerConfig),
711711 );
712 const sourceControlRegistryLayer = Layer.effect(
712 const layerSourceControlRegistry = Layer.effect(
713713 SourceControlProviderRegistry.SourceControlProviderRegistry,
714714 (input?.sourceControlProvider === undefined
715715 ? GitHubSourceControlProvider.make
......@@ -728,7 +728,7 @@ function makeManager(input?: {
728728 ),
729729 );
730730
731 const managerLayer = Layer.mergeAll(
731 const layerManager = Layer.mergeAll(
732732 Layer.succeed(TextGeneration.TextGeneration, textGeneration),
733733 Layer.mock(ProviderRegistry.ProviderRegistry)({
734734 getProviders: Effect.succeed([]),
......@@ -739,21 +739,21 @@ function makeManager(input?: {
739739 runForThread: () => Effect.succeed({ status: "no-script" as const }),
740740 },
741741 ),
742 vcsDriverLayer,
743 serverSettingsLayer,
744 ).pipe(Layer.provideMerge(sourceControlRegistryLayer), Layer.provideMerge(NodeServices.layer));
742 layerVcsDriver,
743 layerServerSettings,
744 ).pipe(Layer.provideMerge(layerSourceControlRegistry), Layer.provideMerge(NodeServices.layer));
745745 // Built into the test's scope: the manager reads these stores after this returns.
746 const storesLayer = Layer.merge(ProjectionStore.layer, ProjectStore.layer).pipe(
747 Layer.provideMerge(SqlitePersistenceMemory),
746 const layerStores = Layer.merge(ProjectionStore.layer, ProjectStore.layer).pipe(
747 Layer.provideMerge(SqlitePersistence.layerMemory),
748748 );
749749
750750 return Effect.gen(function* () {
751 const stores = yield* Layer.build(storesLayer);
751 const stores = yield* Layer.build(layerStores);
752752 if (input?.seed !== undefined) {
753753 yield* input.seed.pipe(Effect.provideContext(stores), Effect.orDie);
754754 }
755755 const manager = yield* GitManager.make.pipe(
756 Effect.provide(managerLayer),
756 Effect.provide(layerManager),
757757 Effect.provideContext(stores),
758758 );
759759 return { manager, ghCalls };
......@@ -762,13 +762,13 @@ function makeManager(input?: {
762762
763763const asThreadId = (threadId: string) => threadId as ThreadId;
764764
765const GitManagerTestLayer = GitVcsDriver.layer.pipe(
765const layerGitManagerTest = GitVcsDriver.layer.pipe(
766766 Layer.provide(ServerConfig.layerTest(process.cwd(), { prefix: "t3-git-manager-test-" })),
767767 Layer.provideMerge(VcsProcess.layer),
768768 Layer.provideMerge(NodeServices.layer),
769769);
770770
771it.layer(GitManagerTestLayer)("GitManager", (it) => {
771it.layer(layerGitManagerTest)("GitManager", (it) => {
772772 it.effect("status includes draft PR metadata when branch already has a draft PR", () =>
773773 Effect.gen(function* () {
774774 const repoDir = yield* makeTempDir("t3code-git-manager-");
apps/server/src/git/GitWorkflowService.test.ts+9-9
......@@ -11,7 +11,7 @@ import * as GitWorkflowService from "./GitWorkflowService.ts";
1111import * as GitVcsDriver from "../vcs/GitVcsDriver.ts";
1212import * as VcsDriverRegistry from "../vcs/VcsDriverRegistry.ts";
1313
14function makeLayer(input: {
14function layer(input: {
1515 readonly detect: VcsDriverRegistry.VcsDriverRegistry["Service"]["detect"];
1616}) {
1717 return GitWorkflowService.layer.pipe(
......@@ -34,7 +34,7 @@ describe("GitWorkflowService", () => {
3434 assert.equal(isRepository, false);
3535 }).pipe(
3636 Effect.provide(
37 makeLayer({
37 layer({
3838 detect: () =>
3939 Effect.succeed({
4040 kind: "jj",
......@@ -74,7 +74,7 @@ describe("GitWorkflowService", () => {
7474 });
7575 }).pipe(
7676 Effect.provide(
77 makeLayer({
77 layer({
7878 detect: () => Effect.succeed(null),
7979 }),
8080 ),
......@@ -105,7 +105,7 @@ describe("GitWorkflowService", () => {
105105 });
106106 }).pipe(
107107 Effect.provide(
108 makeLayer({
108 layer({
109109 detect: () => Effect.succeed(null),
110110 }),
111111 ),
......@@ -117,7 +117,7 @@ describe("GitWorkflowService", () => {
117117 const remoteStatus = vi.fn();
118118 const status = vi.fn();
119119
120 const testLayer = GitWorkflowService.layer.pipe(
120 const layerTest = GitWorkflowService.layer.pipe(
121121 Layer.provide(
122122 Layer.mock(VcsDriverRegistry.VcsDriverRegistry)({
123123 detect: () => Effect.succeed(null),
......@@ -142,7 +142,7 @@ describe("GitWorkflowService", () => {
142142 assert.equal(localStatus.mock.calls.length, 0);
143143 assert.equal(remoteStatus.mock.calls.length, 0);
144144 assert.equal(status.mock.calls.length, 0);
145 }).pipe(Effect.provide(testLayer));
145 }).pipe(Effect.provide(layerTest));
146146 });
147147
148148 it.effect("returns an empty ref list when no VCS repository is detected", () =>
......@@ -159,7 +159,7 @@ describe("GitWorkflowService", () => {
159159 });
160160 }).pipe(
161161 Effect.provide(
162 makeLayer({
162 layer({
163163 detect: () => Effect.succeed(null),
164164 }),
165165 ),
......@@ -186,7 +186,7 @@ describe("GitWorkflowService", () => {
186186 expect(error.message).not.toContain(cause.detail);
187187 }).pipe(
188188 Effect.provide(
189 makeLayer({
189 layer({
190190 detect: () => Effect.fail(cause),
191191 }),
192192 ),
......@@ -214,7 +214,7 @@ describe("GitWorkflowService", () => {
214214 expect(error.message).not.toContain(cause.detail);
215215 }).pipe(
216216 Effect.provide(
217 makeLayer({
217 layer({
218218 detect: () => Effect.fail(cause),
219219 }),
220220 ),
apps/server/src/git/linkCreatedPullRequest.test.ts+14-14
......@@ -71,7 +71,7 @@ function prResult(pr: GitRunStackedActionResult["pr"]): Pick<GitRunStackedAction
7171 return { pr };
7272}
7373
74const makeDependencies = (
74const layerDependenciesFor = (
7575 dispatch: Orchestrator.OrchestratorV2Shape["dispatch"],
7676 threadShell: PullRequestTestThread | null = thread,
7777) =>
......@@ -159,7 +159,7 @@ describe("linkCreatedPullRequest", () => {
159159 url: "https://github.com/t3tools/t3code/pull/42",
160160 }),
161161 commandId,
162 }).pipe(Effect.provide(makeDependencies(dispatch)));
162 }).pipe(Effect.provide(layerDependenciesFor(dispatch)));
163163
164164 expect(yield* Ref.get(commands)).toEqual([
165165 {
......@@ -179,17 +179,17 @@ describe("linkCreatedPullRequest", () => {
179179 it.effect("dispatches nothing when the action produced no pull request", () =>
180180 Effect.gen(function* () {
181181 const { commands, dispatch } = yield* recordingDispatch();
182 const dependencies = makeDependencies(dispatch);
182 const layerDependencies = layerDependenciesFor(dispatch);
183183 yield* linkCreatedPullRequest({
184184 threadId: THREAD_ID,
185185 result: prResult({ status: "skipped_not_requested" }),
186186 commandId,
187 }).pipe(Effect.provide(dependencies));
187 }).pipe(Effect.provide(layerDependencies));
188188 yield* linkCreatedPullRequest({
189189 threadId: THREAD_ID,
190190 result: prResult({ status: "created", url: "https://github.com/t3tools/t3code/pull/42" }),
191191 commandId,
192 }).pipe(Effect.provide(dependencies));
192 }).pipe(Effect.provide(layerDependencies));
193193
194194 expect(yield* Ref.get(commands)).toEqual([]);
195195 }),
......@@ -211,14 +211,14 @@ describe("linkCreatedPullRequest", () => {
211211 url: "https://github.com/t3tools/t3code/pull/7",
212212 });
213213 yield* linkCreatedPullRequest({ threadId: THREAD_ID, result, commandId }).pipe(
214 Effect.provide(makeDependencies(rejecting)),
214 Effect.provide(layerDependenciesFor(rejecting)),
215215 );
216216 yield* linkCreatedPullRequest({ threadId: THREAD_ID, result, commandId }).pipe(
217 Effect.provide(makeDependencies(() => Effect.die(new Error("engine down")))),
217 Effect.provide(layerDependenciesFor(() => Effect.die(new Error("engine down")))),
218218 );
219219 // A thread that vanished between the action and the link is not an error either.
220220 yield* linkCreatedPullRequest({ threadId: THREAD_ID, result, commandId }).pipe(
221 Effect.provide(makeDependencies(() => Effect.die(new Error("unreachable")), null)),
221 Effect.provide(layerDependenciesFor(() => Effect.die(new Error("unreachable")), null)),
222222 );
223223 }),
224224 );
......@@ -229,7 +229,7 @@ it.effect(
229229 () =>
230230 Effect.gen(function* () {
231231 const refreshed: string[] = [];
232 const dependencies = Layer.mergeAll(
232 const layerDependencies = Layer.mergeAll(
233233 Layer.mock(Orchestrator.OrchestratorV2)({
234234 getThreadShell: () => Effect.succeed(v2PullRequestThread(thread)),
235235 }),
......@@ -246,22 +246,22 @@ it.effect(
246246 yield* refreshPushedPullRequests(
247247 { cwd: "/worktree", threadId: THREAD_ID },
248248 { push: { status: "pushed" } },
249 ).pipe(Effect.provide(dependencies));
249 ).pipe(Effect.provide(layerDependencies));
250250 yield* refreshPushedPullRequests(
251251 { cwd: project.workspaceRoot },
252252 { push: { status: "pushed" } },
253 ).pipe(Effect.provide(dependencies));
253 ).pipe(Effect.provide(layerDependencies));
254254 yield* refreshPushedPullRequests({ cwd: "/unrelated" }, { push: { status: "pushed" } }).pipe(
255 Effect.provide(dependencies),
255 Effect.provide(layerDependencies),
256256 );
257257 yield* refreshPushedPullRequests(
258258 { cwd: project.workspaceRoot, threadId: THREAD_ID },
259259 { push: { status: "skipped_not_requested" } },
260 ).pipe(Effect.provide(dependencies));
260 ).pipe(Effect.provide(layerDependencies));
261261 yield* refreshPushedPullRequests(
262262 { cwd: "/draft-worktree", projectId: PROJECT_ID },
263263 { push: { status: "pushed" } },
264 ).pipe(Effect.provide(dependencies));
264 ).pipe(Effect.provide(layerDependencies));
265265 expect(refreshed).toEqual([PROJECT_ID, PROJECT_ID, PROJECT_ID]);
266266 }),
267267);
apps/server/src/htmlRender/HtmlRender.test.ts+11-11
......@@ -30,7 +30,7 @@ import * as PreviewBrowser from "./PreviewBrowser.ts";
3030// example one T3 installed under <T3 home>/tools/chrome-headless-shell.
3131const TEST_BROWSER_ENV = "T3CODE_TEST_HEADLESS_SHELL";
3232
33const htmlRenderLayer = (
33const layerHtmlRender = (
3434 executable?: string,
3535 installed: Effect.Effect<Option.Option<string>> = Effect.succeed(
3636 Option.fromUndefinedOr(executable),
......@@ -52,7 +52,7 @@ const htmlRenderLayer = (
5252 Layer.provideMerge(ServerConfig.layerTest(process.cwd(), { prefix: "t3-html-render-" })),
5353 Layer.provideMerge(NodeServices.layer),
5454 );
55const testLayer = htmlRenderLayer();
55const layerTest = layerHtmlRender();
5656
5757const PNG_BYTES = new Uint8Array([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
5858
......@@ -94,7 +94,7 @@ describe("HtmlRender", () => {
9494 // The theme bootstrap opens the head, ahead of the page's own markup.
9595 expect(prepared.indexOf("<head>")).toBeLessThan(prepared.indexOf('<style id="t3-theme">'));
9696 expect(prepared.indexOf('<style id="t3-theme">')).toBeLessThan(prepared.indexOf("<title>"));
97 }).pipe(Effect.provide(testLayer)),
97 }).pipe(Effect.provide(layerTest)),
9898 );
9999
100100 it.effect("inlines an SVG behind processing instructions and a doctype subset", () =>
......@@ -115,7 +115,7 @@ describe("HtmlRender", () => {
115115 const prepared = yield* htmlRender.prepare(`<img src="${svg}">`);
116116
117117 expect(prepared).toContain(`data:image/svg+xml;base64,${Base64.encode(source)}`);
118 }).pipe(Effect.provide(testLayer)),
118 }).pipe(Effect.provide(layerTest)),
119119 );
120120
121121 it.effect("lists every local image it cannot read", () =>
......@@ -167,7 +167,7 @@ describe("HtmlRender", () => {
167167 upper,
168168 longer,
169169 ]);
170 }).pipe(Effect.provide(testLayer)),
170 }).pipe(Effect.provide(layerTest)),
171171 );
172172
173173 it.effect("publishes the prepared page as an html thread attachment", () =>
......@@ -197,7 +197,7 @@ describe("HtmlRender", () => {
197197 const html = yield* fileSystem.readFileString(stored ?? "");
198198 expect(html).toContain('<style id="t3-theme">');
199199 expect(html).toContain("<p>Quarterly revenue</p>");
200 }).pipe(Effect.provide(testLayer)),
200 }).pipe(Effect.provide(layerTest)),
201201 );
202202
203203 it.effect("removes the page when publishing is interrupted", () =>
......@@ -225,7 +225,7 @@ describe("HtmlRender", () => {
225225 expect(yield* storedPages).toEqual([]);
226226 }).pipe(
227227 Effect.provide(
228 htmlRenderLayer(
228 layerHtmlRender(
229229 undefined,
230230 Deferred.succeed(measuring, undefined).pipe(Effect.andThen(Effect.never)),
231231 ),
......@@ -283,7 +283,7 @@ describe("HtmlRender", () => {
283283 },
284284 ]),
285285 );
286 }).pipe(Effect.provide(htmlRenderLayer(executable)));
286 }).pipe(Effect.provide(layerHtmlRender(executable)));
287287 }),
288288 30_000,
289289 );
......@@ -314,7 +314,7 @@ describe("HtmlRender", () => {
314314 const texts = preview.consoleMessages.map((message) => message.text);
315315 expect(texts).toContain("secret: none");
316316 expect(texts.join(" ")).not.toContain("abc123");
317 }).pipe(Effect.scoped, Effect.provide(htmlRenderLayer(executable)));
317 }).pipe(Effect.scoped, Effect.provide(layerHtmlRender(executable)));
318318 }),
319319 30_000,
320320 );
......@@ -370,7 +370,7 @@ describe("HtmlRender", () => {
370370 `window.open("${origin}/popup"); location.href = "${origin}/navigate";</script>`,
371371 ].join(""),
372372 });
373 }).pipe(Effect.provide(htmlRenderLayer(executable)));
373 }).pipe(Effect.provide(layerHtmlRender(executable)));
374374 expect(requests).toEqual([]);
375375 expect(datagrams).toEqual([]);
376376 }).pipe(Effect.scoped),
......@@ -396,7 +396,7 @@ describe("HtmlRender", () => {
396396 expect(reference.heights).toEqual(
397397 HTML_RENDER_MEASURE_WIDTHS.map((width) => [width, Math.ceil(width / 2)]),
398398 );
399 }).pipe(Effect.provide(htmlRenderLayer(executable)));
399 }).pipe(Effect.provide(layerHtmlRender(executable)));
400400 }),
401401 30_000,
402402 );
apps/server/src/http.test.ts+20-21
......@@ -22,21 +22,19 @@ import * as ServerConfig from "./config.ts";
2222
2323import {
2424 assetResponseHeaders,
25 browserApiCorsLayer,
2625 assetFileResponse,
2726 downloadContentDisposition,
28 httpCompressionLayer,
2927 isLoopbackHostname,
3028 resolveDevRedirectUrl,
31 staticAndDevRouteLayer,
3229 withUntracedRequests,
3330} from "./http.ts";
31import * as ServerHttp from "./http.ts";
3432
3533describe("untraced requests", () => {
3634 it.effect("drops the HTTP server span for browser trace exports, query string included", () => {
3735 const spanNames: Array<string> = [];
3836 return Effect.gen(function* () {
39 const routes = Layer.effectDiscard(
37 const layerRoutes = Layer.effectDiscard(
4038 Effect.gen(function* () {
4139 const router = yield* HttpRouter.HttpRouter;
4240 yield* router.add("POST", "/api/observability/v1/traces", HttpServerResponse.empty());
......@@ -44,7 +42,7 @@ describe("untraced requests", () => {
4442 }),
4543 );
4644 const services = yield* Layer.build(
47 withUntracedRequests(HttpRouter.serve(routes, { disableListenLog: true })).pipe(
45 withUntracedRequests(HttpRouter.serve(layerRoutes, { disableListenLog: true })).pipe(
4846 Layer.provideMerge(NodeHttpServer.layerTest),
4947 ),
5048 );
......@@ -73,17 +71,17 @@ describe("untraced requests", () => {
7371
7472describe("browser API CORS", () => {
7573 it("accepts protocol negotiation with authenticated browser headers", async () => {
76 const routeLayer = Layer.effectDiscard(
74 const layerRoute = Layer.effectDiscard(
7775 Effect.gen(function* () {
7876 const router = yield* HttpRouter.HttpRouter;
7977 yield* router.add("GET", "/api/environment", HttpServerResponse.empty());
8078 }),
8179 );
82 const appLayer = Layer.merge(routeLayer, browserApiCorsLayer).pipe(
80 const layerApp = Layer.merge(layerRoute, ServerHttp.layerBrowserApiCors).pipe(
8381 Layer.provide(ServerConfig.layerTest(process.cwd(), { prefix: "http-cors-test-" })),
8482 Layer.provide(NodeServices.layer),
8583 );
86 const { handler, dispose } = HttpRouter.toWebHandler(appLayer, { disableLogger: true });
84 const { handler, dispose } = HttpRouter.toWebHandler(layerApp, { disableLogger: true });
8785
8886 try {
8987 const response = await handler(
......@@ -117,19 +115,20 @@ describe("browser API CORS", () => {
117115 });
118116});
119117
120const fileResponseLayer = Layer.mergeAll(NodeHttpPlatform.layer, NodeServices.layer);
118const layerFileResponse = Layer.mergeAll(NodeHttpPlatform.layer, NodeServices.layer);
121119
122120const makeStaticRequest = Effect.fn("HttpTest.makeStaticRequest")(function* (staticDir: string) {
123121 const config = yield* ServerConfig.ServerConfig;
124122 const fileSystem = yield* FileSystem.FileSystem;
125 const appLayer = Layer.merge(staticAndDevRouteLayer, httpCompressionLayer).pipe(
126 Layer.provideMerge(NodeHttpPlatform.layer),
127 );
123 const layerApp = Layer.merge(
124 ServerHttp.layerStaticAndDevRoute,
125 ServerHttp.layerHttpCompression,
126 ).pipe(Layer.provideMerge(NodeHttpPlatform.layer));
128127 // HttpRouter.serve reuses services from the layers around it before the
129128 // app's own, and NodeHttpServer.layerTest brings a real FileSystem, so the
130129 // caller's FileSystem and static directory go between the two.
131130 const services = yield* Layer.build(
132 HttpRouter.serve(appLayer, { disableListenLog: true }).pipe(
131 HttpRouter.serve(layerApp, { disableListenLog: true }).pipe(
133132 Layer.provideMerge(ServerConfig.layer({ ...config, staticDir })),
134133 Layer.provideMerge(Layer.succeed(FileSystem.FileSystem, fileSystem)),
135134 Layer.provideMerge(NodeHttpServer.layerTest),
......@@ -351,7 +350,7 @@ describe("video asset byte ranges", () => {
351350 }
352351 expect(yield* Effect.promise(() => response.text())).toBe(expected);
353352 }
354 }).pipe(Effect.provide(fileResponseLayer)),
353 }).pipe(Effect.provide(layerFileResponse)),
355354 );
356355
357356 it.effect(
......@@ -396,7 +395,7 @@ describe("video asset byte ranges", () => {
396395 );
397396 }
398397 }
399 }).pipe(Effect.provide(fileResponseLayer)),
398 }).pipe(Effect.provide(layerFileResponse)),
400399 );
401400
402401 it.effect("streams guarded file ranges, including suffixes and conditional requests", () =>
......@@ -436,7 +435,7 @@ describe("video asset byte ranges", () => {
436435 expect(response.headers.get("content-length")).toBe(String(expected.length));
437436 expect(yield* Effect.promise(() => response.text())).toBe(expected);
438437 }
439 }).pipe(Effect.provide(fileResponseLayer)),
438 }).pipe(Effect.provide(layerFileResponse)),
440439 );
441440
442441 it.effect("keeps attachment media out of the cache once its signed URL expires", () =>
......@@ -454,7 +453,7 @@ describe("video asset byte ranges", () => {
454453 );
455454 expect(response.headers.get("cache-control")).toBe("private, no-store");
456455 expect(response.headers.get("accept-ranges")).toBe("bytes");
457 }).pipe(Effect.provide(fileResponseLayer)),
456 }).pipe(Effect.provide(layerFileResponse)),
458457 );
459458
460459 it.effect("closes guarded descriptors after full, HEAD, rejected, and cancelled responses", () =>
......@@ -501,7 +500,7 @@ describe("video asset byte ranges", () => {
501500 );
502501 expect(file.handle.fd).toBe(-1);
503502 }
504 }).pipe(Effect.provide(fileResponseLayer)),
503 }).pipe(Effect.provide(layerFileResponse)),
505504 );
506505
507506 it.effect("streams exactly the requested bytes and leaves full downloads intact", () =>
......@@ -554,7 +553,7 @@ describe("video asset byte ranges", () => {
554553 expect(image.status).toBe(200);
555554 expect(image.headers.has("accept-ranges")).toBe(false);
556555 expect(yield* Effect.promise(() => image.text())).toBe("0123456789");
557 }).pipe(Effect.provide(fileResponseLayer)),
556 }).pipe(Effect.provide(layerFileResponse)),
558557 );
559558
560559 it.effect(
......@@ -584,7 +583,7 @@ describe("video asset byte ranges", () => {
584583 expect(download.status).toBe(200);
585584 expect(download.headers.get("content-disposition")).toContain("attachment;");
586585 expect(yield* Effect.promise(() => download.text())).toBe("0123456789");
587 }).pipe(Effect.provide(fileResponseLayer)),
586 }).pipe(Effect.provide(layerFileResponse)),
588587 );
589588
590589 it.effect("rejects ranges outside the file, including empty files", () =>
......@@ -608,7 +607,7 @@ describe("video asset byte ranges", () => {
608607 );
609608 expect(empty.status).toBe(416);
610609 expect(empty.headers.get("content-range")).toBe("bytes */0");
611 }).pipe(Effect.provide(fileResponseLayer)),
610 }).pipe(Effect.provide(layerFileResponse)),
612611 );
613612});
614613
apps/server/src/http.ts+9-9
......@@ -230,11 +230,11 @@ export const assetFileResponse = Effect.fn("assetFileResponse")(function* (
230230 return yield* HttpServerResponse.file(asset.path, { status, offset, bytesToRead, headers });
231231});
232232
233export const httpCompressionLayer = HttpRouter.middleware(HttpMiddleware.compression(), {
233export const layerHttpCompression = HttpRouter.middleware(HttpMiddleware.compression(), {
234234 global: true,
235235});
236236
237export const browserApiCorsLayer = Layer.unwrap(
237export const layerBrowserApiCors = Layer.unwrap(
238238 Effect.gen(function* () {
239239 const config = yield* ServerConfig.ServerConfig;
240240 const devOrigin = config.devUrl?.origin;
......@@ -297,7 +297,7 @@ const authenticateRawRouteWithScope = (
297297 }
298298 });
299299
300export const serverEnvironmentHttpApiLayer = HttpApiBuilder.group(
300export const layerServerEnvironmentHttpApi = HttpApiBuilder.group(
301301 EnvironmentHttpApi,
302302 "metadata",
303303 Effect.fnUntraced(function* (handlers) {
......@@ -320,7 +320,7 @@ class DecodeOtlpTraceRecordsError extends Data.TaggedError("DecodeOtlpTraceRecor
320320// tracing this proxy would add more server spans than it forwards.
321321// withTracerEnabled(false) drops the handler's spans, including the forward.
322322// untracedRequestsLayer drops the HTTP server span.
323export const otlpTracesProxyRouteLayer = HttpRouter.add(
323export const layerOtlpTracesProxyRoute = HttpRouter.add(
324324 "POST",
325325 OTLP_TRACES_PROXY_PATH,
326326 Effect.gen(function* () {
......@@ -382,16 +382,16 @@ const UNTRACED_REQUEST_PATHS: ReadonlySet<string> = new Set([OTLP_TRACES_PROXY_P
382382// privately, so it is provided to the served layer, never merged into the
383383// routes. Add paths here instead of providing it again. The query string is
384384// ignored, as in routing.
385const untracedRequestsLayer = Layer.succeed(HttpMiddleware.TracerDisabledWhen)((request) => {
385const layerUntracedRequests = Layer.succeed(HttpMiddleware.TracerDisabledWhen)((request) => {
386386 const queryIndex = request.url.indexOf("?");
387387 const path = queryIndex === -1 ? request.url : request.url.slice(0, queryIndex);
388388 // Webhook URLs carry their secret token in the path, so they never reach a trace.
389389 return UNTRACED_REQUEST_PATHS.has(path) || path.startsWith(`${WEBHOOK_ROUTE_PREFIX}/`);
390390});
391391
392export const withUntracedRequests = Layer.provide(untracedRequestsLayer);
392export const withUntracedRequests = Layer.provide(layerUntracedRequests);
393393
394export const assetRouteLayer = HttpRouter.add(
394export const layerAssetRoute = HttpRouter.add(
395395 "GET",
396396 `${ASSET_ROUTE_PREFIX}/*`,
397397 Effect.gen(function* () {
......@@ -444,7 +444,7 @@ export const assetRouteLayer = HttpRouter.add(
444444 }),
445445);
446446
447export const attachmentUploadRouteLayer = HttpRouter.add(
447export const layerAttachmentUploadRoute = HttpRouter.add(
448448 "POST",
449449 `${ATTACHMENT_UPLOAD_ROUTE_PREFIX}/*`,
450450 Effect.gen(function* () {
......@@ -672,7 +672,7 @@ const handleStaticAndDevRequest = Effect.fn("handleStaticAndDevRequest")(
672672);
673673
674674// Read the installed build's manifest once. Unknown files use revalidation.
675export const staticAndDevRouteLayer = Layer.unwrap(
675export const layerStaticAndDevRoute = Layer.unwrap(
676676 loadImmutableBuildAssets.pipe(
677677 Effect.map((assets) => HttpRouter.add("GET", "*", handleStaticAndDevRequest(assets))),
678678 ),
apps/server/src/keybindings.test.ts+20-23
......@@ -23,7 +23,7 @@ const encodeResolvedKeybindingFromConfig = Schema.encodeEffect(
2323const decodeResolvedKeybindingFromConfigExit = Schema.decodeUnknownExit(
2424 Keybindings.ResolvedKeybindingFromConfig,
2525);
26const makeKeybindingsLayer = () => {
26const layerKeybindings = () => {
2727 return Keybindings.layer.pipe(
2828 Layer.provideMerge(
2929 Layer.fresh(
......@@ -185,7 +185,7 @@ it.layer(NodeServices.layer)("keybindings", (it) => {
185185
186186 const persisted = yield* readKeybindingsConfig(keybindingsConfigPath);
187187 assert.deepEqual(persisted, Keybindings.DEFAULT_KEYBINDINGS);
188 }).pipe(Effect.provide(makeKeybindingsLayer())),
188 }).pipe(Effect.provide(layerKeybindings())),
189189 );
190190
191191 it.effect("uses defaults in runtime when config is malformed without overriding file", () =>
......@@ -210,7 +210,7 @@ it.layer(NodeServices.layer)("keybindings", (it) => {
210210 },
211211 ]);
212212 assert.equal(yield* fs.readFileString(keybindingsConfigPath), "{ not-json");
213 }).pipe(Effect.provide(makeKeybindingsLayer())),
213 }).pipe(Effect.provide(layerKeybindings())),
214214 );
215215
216216 it.effect("ignores invalid entries in runtime and reports them as issues", () =>
......@@ -248,7 +248,7 @@ it.layer(NodeServices.layer)("keybindings", (it) => {
248248 message: configState.issues[1]?.message ?? "",
249249 },
250250 ]);
251 }).pipe(Effect.provide(makeKeybindingsLayer())),
251 }).pipe(Effect.provide(layerKeybindings())),
252252 );
253253
254254 it.effect(
......@@ -280,7 +280,7 @@ it.layer(NodeServices.layer)("keybindings", (it) => {
280280 assert.isTrue(byCommand.has(defaultRule.command), `expected ${defaultRule.command}`);
281281 }
282282 assert.isTrue(byCommand.has("script.run-tests.run"));
283 }).pipe(Effect.provide(makeKeybindingsLayer())),
283 }).pipe(Effect.provide(layerKeybindings())),
284284 );
285285
286286 it.effect("adds a late default to an existing command once", () =>
......@@ -304,7 +304,7 @@ it.layer(NodeServices.layer)("keybindings", (it) => {
304304 yield* writeKeybindingsConfig(keybindingsConfigPath, [existing]);
305305 yield* keybindings.syncDefaultKeybindingsOnStartup;
306306 assert.deepStrictEqual(yield* backgroundRules, [existing]);
307 }).pipe(Effect.provide(makeKeybindingsLayer())),
307 }).pipe(Effect.provide(layerKeybindings())),
308308 );
309309
310310 it.effect("leaves a customized command without the late default", () =>
......@@ -324,7 +324,7 @@ it.layer(NodeServices.layer)("keybindings", (it) => {
324324 persisted.filter((entry) => entry.command === "composer.sendBackground"),
325325 [custom],
326326 );
327 }).pipe(Effect.provide(makeKeybindingsLayer())),
327 }).pipe(Effect.provide(layerKeybindings())),
328328 );
329329
330330 it.effect("keeps a late default pending while the config is full", () =>
......@@ -350,7 +350,7 @@ it.layer(NodeServices.layer)("keybindings", (it) => {
350350 yield* writeKeybindingsConfig(keybindingsConfigPath, [existing]);
351351 yield* keybindings.syncDefaultKeybindingsOnStartup;
352352 assert.isTrue(yield* hasModEnter);
353 }).pipe(Effect.provide(makeKeybindingsLayer())),
353 }).pipe(Effect.provide(layerKeybindings())),
354354 );
355355
356356 it.effect("skips conflicting default keybindings on startup and logs a detailed warning", () => {
......@@ -381,10 +381,7 @@ it.layer(NodeServices.layer)("keybindings", (it) => {
381381 );
382382 }).pipe(
383383 Effect.provide(
384 Layer.mergeAll(
385 makeKeybindingsLayer(),
386 Logger.layer([logger], { mergeWithExisting: false }),
387 ),
384 Layer.mergeAll(layerKeybindings(), Logger.layer([logger], { mergeWithExisting: false })),
388385 ),
389386 );
390387 });
......@@ -412,7 +409,7 @@ it.layer(NodeServices.layer)("keybindings", (it) => {
412409 { key: "mod+shift+r", command: "script.run-tests.run" },
413410 ]);
414411 assert.isTrue(resolved.some((entry) => entry.command === "script.run-tests.run"));
415 }).pipe(Effect.provide(makeKeybindingsLayer())),
412 }).pipe(Effect.provide(layerKeybindings())),
416413 );
417414
418415 it.effect("appends additional custom keybindings for the same command", () =>
......@@ -435,7 +432,7 @@ it.layer(NodeServices.layer)("keybindings", (it) => {
435432 { key: "mod+r", command: "script.run-tests.run" },
436433 { key: "mod+shift+r", command: "script.run-tests.run" },
437434 ]);
438 }).pipe(Effect.provide(makeKeybindingsLayer())),
435 }).pipe(Effect.provide(layerKeybindings())),
439436 );
440437
441438 it.effect("replaces only the targeted custom keybinding", () =>
......@@ -460,7 +457,7 @@ it.layer(NodeServices.layer)("keybindings", (it) => {
460457 { key: "mod+shift+r", command: "script.run-tests.run" },
461458 { key: "mod+alt+r", command: "script.run-tests.run" },
462459 ]);
463 }).pipe(Effect.provide(makeKeybindingsLayer())),
460 }).pipe(Effect.provide(layerKeybindings())),
464461 );
465462
466463 it.effect("replacing with a rule that already exists elsewhere does not duplicate it", () =>
......@@ -482,7 +479,7 @@ it.layer(NodeServices.layer)("keybindings", (it) => {
482479 const persisted = yield* readKeybindingsConfig(keybindingsConfigPath);
483480 const persistedView = persisted.map(({ key, command }) => ({ key, command }));
484481 assert.deepEqual(persistedView, [{ key: "mod+alt+r", command: "script.run-tests.run" }]);
485 }).pipe(Effect.provide(makeKeybindingsLayer())),
482 }).pipe(Effect.provide(layerKeybindings())),
486483 );
487484
488485 it.effect("removes only the targeted custom keybinding", () =>
......@@ -503,7 +500,7 @@ it.layer(NodeServices.layer)("keybindings", (it) => {
503500 const persisted = yield* readKeybindingsConfig(keybindingsConfigPath);
504501 const persistedView = persisted.map(({ key, command }) => ({ key, command }));
505502 assert.deepEqual(persistedView, [{ key: "mod+shift+r", command: "script.run-tests.run" }]);
506 }).pipe(Effect.provide(makeKeybindingsLayer())),
503 }).pipe(Effect.provide(layerKeybindings())),
507504 );
508505
509506 it.effect("refuses to overwrite malformed keybindings config", () =>
......@@ -523,7 +520,7 @@ it.layer(NodeServices.layer)("keybindings", (it) => {
523520
524521 const persistedRaw = yield* fs.readFileString(keybindingsConfigPath);
525522 assert.equal(persistedRaw, "{ not-json");
526 }).pipe(Effect.provide(makeKeybindingsLayer())),
523 }).pipe(Effect.provide(layerKeybindings())),
527524 );
528525
529526 it.effect("reports non-array config parse errors without duplicate prefix", () =>
......@@ -552,7 +549,7 @@ it.layer(NodeServices.layer)("keybindings", (it) => {
552549 });
553550 }).pipe(toDetailResult);
554551 assertFailure(secondResult, "expected JSON array");
555 }).pipe(Effect.provide(makeKeybindingsLayer())),
552 }).pipe(Effect.provide(layerKeybindings())),
556553 );
557554
558555 // chmod cannot make a directory unwritable on Windows, so the write succeeds.
......@@ -582,7 +579,7 @@ it.layer(NodeServices.layer)("keybindings", (it) => {
582579 const persisted = yield* readKeybindingsConfig(keybindingsConfigPath);
583580 const persistedView = persisted.map(({ key, command }) => ({ key, command }));
584581 assert.deepEqual(persistedView, [{ key: "mod+j", command: "terminal.toggle" }]);
585 }).pipe(Effect.provide(makeKeybindingsLayer())),
582 }).pipe(Effect.provide(layerKeybindings())),
586583 );
587584
588585 it.effect("caches loaded resolved config across repeated reads", () =>
......@@ -601,7 +598,7 @@ it.layer(NodeServices.layer)("keybindings", (it) => {
601598
602599 assert.deepEqual(first, second);
603600 assert.isTrue(second.some((entry) => entry.command === "terminal.toggle"));
604 }).pipe(Effect.provide(makeKeybindingsLayer())),
601 }).pipe(Effect.provide(layerKeybindings())),
605602 );
606603
607604 it.effect("updates cached resolved config after upsert", () =>
......@@ -623,7 +620,7 @@ it.layer(NodeServices.layer)("keybindings", (it) => {
623620
624621 assert.isTrue(loadedAfterUpsert.some((entry) => entry.command === "script.run-tests.run"));
625622 assert.isTrue(loadedAfterUpsert.some((entry) => entry.command === "terminal.toggle"));
626 }).pipe(Effect.provide(makeKeybindingsLayer())),
623 }).pipe(Effect.provide(layerKeybindings())),
627624 );
628625
629626 it.effect("serializes concurrent upserts to avoid lost updates", () =>
......@@ -653,6 +650,6 @@ it.layer(NodeServices.layer)("keybindings", (it) => {
653650 for (const command of commands) {
654651 assert.isTrue(persistedCommands.has(command), `expected persisted command ${command}`);
655652 }
656 }).pipe(Effect.provide(makeKeybindingsLayer())),
653 }).pipe(Effect.provide(layerKeybindings())),
657654 );
658655});
apps/server/src/mcp/McpDeviceToolkit.test.ts+7-7
......@@ -81,7 +81,7 @@ new DataView(png.buffer).setUint32(12, 0x49484452);
8181new DataView(png.buffer).setUint32(16, 1206);
8282new DataView(png.buffer).setUint32(20, 2622);
8383
84const DeviceServiceMock = Layer.mock(DeviceService.DeviceService)({
84const layerDeviceServiceMock = Layer.mock(DeviceService.DeviceService)({
8585 state: Effect.succeed(state),
8686 list: Effect.succeed(state),
8787 open: (input) =>
......@@ -100,9 +100,9 @@ const DeviceServiceMock = Layer.mock(DeviceService.DeviceService)({
100100 agentTarget: () => Effect.succeed(["--config", "/host.json", "--session", "thread-device"]),
101101});
102102
103const TestLayer = McpHttpServer.DeviceToolkitRegistrationLive.pipe(
103const layerTest = McpHttpServer.layerDeviceToolkit.pipe(
104104 Layer.provideMerge(McpServer.McpServer.layer),
105 Layer.provideMerge(DeviceServiceMock),
105 Layer.provideMerge(layerDeviceServiceMock),
106106 Layer.provide(ServerConfig.layerTest(process.cwd(), { prefix: "t3-mcp-device-toolkit-test-" })),
107107 Layer.provide(NodeServices.layer),
108108);
......@@ -138,11 +138,11 @@ it.effect("registers the device tools and returns the screenshot as image conten
138138 .pipe(callWith(["preview"]), Effect.provideService(McpSchema.McpServerClient, client));
139139 expect(denied.isError).toBe(true);
140140 }),
141 ).pipe(Effect.provide(TestLayer)),
141 ).pipe(Effect.provide(layerTest)),
142142);
143143
144144it.effect("rejects unavailable agent access before booting or opening a device", () => {
145 const unavailable = Layer.mock(DeviceService.DeviceService)({
145 const layerUnavailable = Layer.mock(DeviceService.DeviceService)({
146146 list: Effect.succeed(state),
147147 agentTarget: () =>
148148 Effect.fail(
......@@ -170,9 +170,9 @@ it.effect("rejects unavailable agent access before booting or opening a device",
170170 }).pipe(
171171 Effect.scoped,
172172 Effect.provide(
173 McpHttpServer.DeviceToolkitRegistrationLive.pipe(
173 McpHttpServer.layerDeviceToolkit.pipe(
174174 Layer.provideMerge(McpServer.McpServer.layer),
175 Layer.provide(unavailable),
175 Layer.provide(layerUnavailable),
176176 Layer.provide(NodeServices.layer),
177177 ),
178178 ),
apps/server/src/mcp/McpHttpServer.test.ts+17-17
......@@ -49,13 +49,13 @@ const client = McpSchema.McpServerClient.of({
4949 },
5050 getClient: Effect.die("unused"),
5151});
52const TestLayer = McpHttpServer.PreviewToolkitRegistrationLive.pipe(
52const layerTest = McpHttpServer.layerPreviewToolkit.pipe(
5353 Layer.provideMerge(McpServer.McpServer.layer),
5454 Layer.provideMerge(PreviewAutomationBroker.layer),
5555 Layer.provideMerge(ServerConfig.layerTest(process.cwd(), { prefix: "t3-mcp-http-server-test-" })),
5656 Layer.provideMerge(NodeServices.layer),
5757);
58const PullRequestsTestLayer = McpHttpServer.PullRequestsToolkitRegistrationLive.pipe(
58const layerPullRequestsTest = McpHttpServer.layerPullRequestsToolkit.pipe(
5959 Layer.provideMerge(McpServer.McpServer.layer),
6060 Layer.provide(
6161 Layer.mergeAll(
......@@ -179,7 +179,7 @@ it.effect.each([{}, { includeImage: false }])(
179179 },
180180 });
181181 }),
182 ).pipe(Effect.provide(TestLayer)),
182 ).pipe(Effect.provide(layerTest)),
183183);
184184
185185it.effect.each([
......@@ -214,7 +214,7 @@ it.effect.each([
214214 { type: "text", text: `Preview snapshot failed: ${advice}` },
215215 ]);
216216 }),
217 ).pipe(Effect.provide(TestLayer)),
217 ).pipe(Effect.provide(layerTest)),
218218);
219219
220220it.effect("tells the agent how to fall back when no desktop app can run the snapshot", () =>
......@@ -229,7 +229,7 @@ it.effect("tells the agent how to fall back when no desktop app can run the snap
229229 expect(snapshot.structuredContent).toMatchObject({
230230 error: { _tag: "PreviewAutomationNoAvailableHostError" },
231231 });
232 }).pipe(Effect.provide(TestLayer)),
232 }).pipe(Effect.provide(layerTest)),
233233);
234234
235235it.effect.each([
......@@ -350,7 +350,7 @@ it.effect.each([
350350 expect(nextDefault.structuredContent).not.toHaveProperty("accessibilityTree");
351351 expect(requests).toBe(7);
352352 }),
353 ).pipe(Effect.provide(TestLayer)),
353 ).pipe(Effect.provide(layerTest)),
354354);
355355
356356it.effect("rejects non-boolean snapshot image options before selecting a browser host", () =>
......@@ -372,7 +372,7 @@ it.effect("rejects non-boolean snapshot image options before selecting a browser
372372 error: { _tag: "AiError", operation: "snapshot", failureCount: 1 },
373373 });
374374 }
375 }).pipe(Effect.provide(TestLayer)),
375 }).pipe(Effect.provide(layerTest)),
376376);
377377
378378it.effect("saves the snapshot PNG on request and reports its path", () =>
......@@ -411,7 +411,7 @@ it.effect("saves the snapshot PNG on request and reports its path", () =>
411411 expect(others).toEqual([]);
412412 expect(only?.type === "text" ? decodeJsonText(only.text) : null).toEqual(saved);
413413 }),
414 ).pipe(Effect.provide(TestLayer)),
414 ).pipe(Effect.provide(layerTest)),
415415);
416416
417417it.effect("reports a tagged error when the screenshot cannot be saved", () =>
......@@ -433,7 +433,7 @@ it.effect("reports a tagged error when the screenshot cannot be saved", () =>
433433 error: { _tag: "PreviewScreenshotSaveError", operation: "snapshot", failureCount: 1 },
434434 });
435435 }),
436 ).pipe(Effect.provide(TestLayer)),
436 ).pipe(Effect.provide(layerTest)),
437437);
438438
439439it.effect(
......@@ -465,7 +465,7 @@ it.effect(
465465 expect(denied.content).toEqual([
466466 { type: "text", text: "MCP credential does not grant the pull-requests capability." },
467467 ]);
468 }).pipe(Effect.provide(PullRequestsTestLayer)),
468 }).pipe(Effect.provide(layerPullRequestsTest)),
469469);
470470
471471it.effect("keeps the snapshot text under the agent's output ceiling", () =>
......@@ -535,7 +535,7 @@ it.effect("keeps the snapshot text under the agent's output ceiling", () =>
535535 omitted: expect.arrayContaining(["60 older console entries"]),
536536 });
537537 }),
538 ).pipe(Effect.provide(TestLayer)),
538 ).pipe(Effect.provide(layerTest)),
539539);
540540
541541it.effect("bounds the snapshot text even when nothing but logs and the title are large", () =>
......@@ -567,7 +567,7 @@ it.effect("bounds the snapshot text even when nothing but logs and the title are
567567 expect(noticeText).toContain("url or title after 2048 characters");
568568 expect(noticeText).toContain("console entries text after 500 characters");
569569 }),
570 ).pipe(Effect.provide(TestLayer)),
570 ).pipe(Effect.provide(layerTest)),
571571);
572572
573573it.effect("bounds page text made of wide characters before dropping locators", () =>
......@@ -606,7 +606,7 @@ it.effect("bounds page text made of wide characters before dropping locators", (
606606 "visibleText after 4000 characters",
607607 );
608608 }),
609 ).pipe(Effect.provide(TestLayer)),
609 ).pipe(Effect.provide(layerTest)),
610610);
611611
612612it.effect("sheds log entries before locators when every list is full", () =>
......@@ -672,19 +672,19 @@ it.effect("sheds log entries before locators when every list is full", () =>
672672 expect(noticeText).toContain("40 of 40 actionTimeline");
673673 expect(noticeText).not.toMatch(/\d+ of \d+ interactiveElements/);
674674 }),
675 ).pipe(Effect.provide(TestLayer)),
675 ).pipe(Effect.provide(layerTest)),
676676);
677677
678678it.effect("terminates HTTP MCP sessions with DELETE", () =>
679679 Effect.scoped(
680680 Effect.gen(function* () {
681 const serverLayer = McpServer.layerHttp({
681 const layerServer = McpServer.layerHttp({
682682 name: "MCP termination test",
683683 version: "1.0.0",
684684 path: "/mcp",
685685 protocols: [McpProtocol.v2025_06_18],
686686 });
687 yield* HttpRouter.serve(serverLayer, {
687 yield* HttpRouter.serve(layerServer, {
688688 disableListenLog: true,
689689 disableLogger: true,
690690 }).pipe(Layer.build);
......@@ -872,5 +872,5 @@ it.effect("registers annotated tools and preserves authenticated request context
872872 expect(text?.type === "text" ? decodeJsonText(text.text) : null).toEqual({ toolIcon });
873873 }
874874 }),
875 ).pipe(Effect.provide(TestLayer)),
875 ).pipe(Effect.provide(layerTest)),
876876);
apps/server/src/mcp/McpHttpServer.ts+59-68
......@@ -23,47 +23,38 @@ import * as PreviewBrowser from "../htmlRender/PreviewBrowser.ts";
2323import * as McpInvocationContext from "./McpInvocationContext.ts";
2424import * as OrchestratorMcpService from "./OrchestratorMcpService.ts";
2525import { PreviewControlsToolkit } from "./toolkits/previewControls/tools.ts";
26import { PreviewControlsHandlersLive } from "./toolkits/previewControls/handlers.ts";
26import * as PreviewControlsHandlers from "./toolkits/previewControls/handlers.ts";
2727import { EnvironmentToolkit } from "./toolkits/environment/tools.ts";
28import { EnvironmentHandlersLive } from "./toolkits/environment/handlers.ts";
28import * as EnvironmentHandlers from "./toolkits/environment/handlers.ts";
2929import { ProjectToolkit } from "./toolkits/project/tools.ts";
30import { ProjectHandlersLive } from "./toolkits/project/handlers.ts";
30import * as ProjectHandlers from "./toolkits/project/handlers.ts";
3131import { AttachmentToolkit } from "./toolkits/attachment/tools.ts";
32import { AttachmentHandlersLive } from "./toolkits/attachment/handlers.ts";
32import * as AttachmentHandlers from "./toolkits/attachment/handlers.ts";
3333import { ThreadToolkit } from "./toolkits/thread/tools.ts";
34import { ThreadToolkitHandlersLive } from "./toolkits/thread/handlers.ts";
34import * as ThreadHandlers from "./toolkits/thread/handlers.ts";
3535import * as ThreadMetadataMcpService from "./ThreadMetadataMcpService.ts";
3636import * as McpSessionRegistry from "./McpSessionRegistry.ts";
3737import * as PreviewAutomationBroker from "./PreviewAutomationBroker.ts";
38import { OrchestratorToolkitHandlersLive } from "./toolkits/orchestrator/handlers.ts";
38import * as OrchestratorHandlers from "./toolkits/orchestrator/handlers.ts";
3939import { OrchestratorToolkit } from "./toolkits/orchestrator/tools.ts";
40import {
41 PreviewSnapshotToolkitHandlersLive,
42 PreviewStandardToolkitHandlersLive,
43} from "./toolkits/preview/handlers.ts";
40import * as PreviewHandlers from "./toolkits/preview/handlers.ts";
4441import {
4542 PreviewSnapshotTool,
4643 PreviewSnapshotToolkit,
4744 PreviewStandardToolkit,
4845} from "./toolkits/preview/tools.ts";
49import { WorktreeToolkitHandlersLive } from "./toolkits/worktree/handlers.ts";
46import * as WorktreeHandlers from "./toolkits/worktree/handlers.ts";
5047import { WorktreeToolkit } from "./toolkits/worktree/tools.ts";
5148import * as WorktreeMcpService from "./WorktreeMcpService.ts";
52import { PullRequestsToolkitHandlersLive } from "./toolkits/pullRequests/handlers.ts";
49import * as PullRequestsHandlers from "./toolkits/pullRequests/handlers.ts";
5350import { PullRequestsToolkit } from "./toolkits/pullRequests/tools.ts";
54import {
55 DeviceScreenshotToolkitHandlersLive,
56 DeviceStandardToolkitHandlersLive,
57} from "./toolkits/device/handlers.ts";
51import * as DeviceHandlers from "./toolkits/device/handlers.ts";
5852import {
5953 DeviceScreenshotTool,
6054 DeviceScreenshotToolkit,
6155 DeviceStandardToolkit,
6256} from "./toolkits/device/tools.ts";
63import {
64 HtmlPreviewToolkitHandlersLive,
65 HtmlRenderToolkitHandlersLive,
66} from "./toolkits/html/handlers.ts";
57import * as HtmlHandlers from "./toolkits/html/handlers.ts";
6758import { HtmlPreviewTool, HtmlPreviewToolkit, HtmlRenderToolkit } from "./toolkits/html/tools.ts";
6859
6960const unauthorized = HttpServerResponse.jsonUnsafe(
......@@ -134,7 +125,7 @@ const makeMcpAuthMiddleware = McpSessionRegistry.McpSessionRegistry.pipe(
134125 Effect.withSpan("McpHttpServer.makeAuthMiddleware"),
135126);
136127
137const McpAuthMiddlewareLive = HttpRouter.middleware<{
128const layerMcpAuthMiddleware = HttpRouter.middleware<{
138129 provides: McpInvocationContext.McpInvocationContext;
139130}>()(makeMcpAuthMiddleware).layer;
140131
......@@ -687,89 +678,89 @@ const registerHtmlPreview = Effect.fn("McpHttpServer.registerHtmlPreview")(funct
687678 );
688679});
689680
690export const HtmlToolkitRegistrationLive = Layer.mergeAll(
691 McpServer.toolkit(HtmlRenderToolkit).pipe(Layer.provide(HtmlRenderToolkitHandlersLive)),
692 Layer.effectDiscard(registerHtmlPreview()).pipe(Layer.provide(HtmlPreviewToolkitHandlersLive)),
681export const layerHtmlToolkit = Layer.mergeAll(
682 McpServer.toolkit(HtmlRenderToolkit).pipe(Layer.provide(HtmlHandlers.layerRender)),
683 Layer.effectDiscard(registerHtmlPreview()).pipe(Layer.provide(HtmlHandlers.layerPreview)),
693684).pipe(Layer.provide(HtmlRender.layer), Layer.provide(PreviewBrowser.layer));
694685
695const PreviewStandardToolkitRegistrationLive = McpServer.toolkit(PreviewStandardToolkit).pipe(
696 Layer.provide(PreviewStandardToolkitHandlersLive),
686const layerPreviewStandardToolkitRegistration = McpServer.toolkit(PreviewStandardToolkit).pipe(
687 Layer.provide(PreviewHandlers.layerStandard),
697688);
698689
699const PreviewSnapshotRegistrationLive = Layer.effectDiscard(registerPreviewSnapshot()).pipe(
700 Layer.provide(PreviewSnapshotToolkitHandlersLive),
690const layerPreviewSnapshotRegistration = Layer.effectDiscard(registerPreviewSnapshot()).pipe(
691 Layer.provide(PreviewHandlers.layerSnapshot),
701692);
702693
703export const PreviewToolkitRegistrationLive = Layer.mergeAll(
704 PreviewStandardToolkitRegistrationLive,
705 PreviewSnapshotRegistrationLive,
694export const layerPreviewToolkit = Layer.mergeAll(
695 layerPreviewStandardToolkitRegistration,
696 layerPreviewSnapshotRegistration,
706697);
707698
708export const OrchestratorToolkitRegistrationLive = McpServer.toolkit(OrchestratorToolkit).pipe(
709 Layer.provide(OrchestratorToolkitHandlersLive),
699export const layerOrchestratorToolkit = McpServer.toolkit(OrchestratorToolkit).pipe(
700 Layer.provide(OrchestratorHandlers.layer),
710701 Layer.provide(OrchestratorMcpService.layer),
711702 Layer.provide(ThreadMetadataMcpService.layer),
712703);
713704
714export const ThreadToolkitRegistrationLive = McpServer.toolkit(ThreadToolkit).pipe(
715 Layer.provide(ThreadToolkitHandlersLive),
705export const layerThreadToolkit = McpServer.toolkit(ThreadToolkit).pipe(
706 Layer.provide(ThreadHandlers.layer),
716707);
717708
718const WorktreeToolkitRegistrationLive = McpServer.toolkit(WorktreeToolkit).pipe(
719 Layer.provide(WorktreeToolkitHandlersLive),
709const layerWorktreeToolkitRegistration = McpServer.toolkit(WorktreeToolkit).pipe(
710 Layer.provide(WorktreeHandlers.layer),
720711 Layer.provide(WorktreeMcpService.layer),
721712);
722713
723const PreviewControlsRegistrationLive = McpServer.toolkit(PreviewControlsToolkit).pipe(
724 Layer.provide(PreviewControlsHandlersLive),
714const layerPreviewControlsRegistration = McpServer.toolkit(PreviewControlsToolkit).pipe(
715 Layer.provide(PreviewControlsHandlers.layer),
725716);
726717
727const EnvironmentRegistrationLive = McpServer.toolkit(EnvironmentToolkit).pipe(
728 Layer.provide(EnvironmentHandlersLive),
718const layerEnvironmentRegistration = McpServer.toolkit(EnvironmentToolkit).pipe(
719 Layer.provide(EnvironmentHandlers.layer),
729720);
730721
731const ProjectRegistrationLive = McpServer.toolkit(ProjectToolkit).pipe(
732 Layer.provide(ProjectHandlersLive),
722const layerProjectRegistration = McpServer.toolkit(ProjectToolkit).pipe(
723 Layer.provide(ProjectHandlers.layer),
733724);
734725
735const AttachmentRegistrationLive = McpServer.toolkit(AttachmentToolkit).pipe(
736 Layer.provide(AttachmentHandlersLive),
726const layerAttachmentRegistration = McpServer.toolkit(AttachmentToolkit).pipe(
727 Layer.provide(AttachmentHandlers.layer),
737728);
738729
739export const PullRequestsToolkitRegistrationLive = McpServer.toolkit(PullRequestsToolkit).pipe(
740 Layer.provide(PullRequestsToolkitHandlersLive),
730export const layerPullRequestsToolkit = McpServer.toolkit(PullRequestsToolkit).pipe(
731 Layer.provide(PullRequestsHandlers.layer),
741732);
742733
743const DeviceStandardToolkitRegistrationLive = McpServer.toolkit(DeviceStandardToolkit).pipe(
744 Layer.provide(DeviceStandardToolkitHandlersLive),
734const layerDeviceStandardToolkitRegistration = McpServer.toolkit(DeviceStandardToolkit).pipe(
735 Layer.provide(DeviceHandlers.layerStandard),
745736);
746737
747const DeviceScreenshotRegistrationLive = Layer.effectDiscard(registerDeviceScreenshot()).pipe(
748 Layer.provide(DeviceScreenshotToolkitHandlersLive),
738const layerDeviceScreenshotRegistration = Layer.effectDiscard(registerDeviceScreenshot()).pipe(
739 Layer.provide(DeviceHandlers.layerScreenshot),
749740);
750741
751export const DeviceToolkitRegistrationLive = Layer.mergeAll(
752 DeviceStandardToolkitRegistrationLive,
753 DeviceScreenshotRegistrationLive,
742export const layerDeviceToolkit = Layer.mergeAll(
743 layerDeviceStandardToolkitRegistration,
744 layerDeviceScreenshotRegistration,
754745);
755746
756const McpTransportLive = McpServer.layerHttp({
747const layerMcpTransport = McpServer.layerHttp({
757748 name: "T3 Code",
758749 version: packageJson.version,
759750 path: "/mcp",
760751 protocols: [McpProtocol.v2025_06_18],
761}).pipe(Layer.provide(McpAuthMiddlewareLive));
752}).pipe(Layer.provide(layerMcpAuthMiddleware));
762753
763754export const layer = Layer.mergeAll(
764 PreviewToolkitRegistrationLive,
765 OrchestratorToolkitRegistrationLive,
766 ThreadToolkitRegistrationLive,
767 AttachmentRegistrationLive,
768 ProjectRegistrationLive,
769 EnvironmentRegistrationLive,
770 PreviewControlsRegistrationLive,
771 WorktreeToolkitRegistrationLive,
772 PullRequestsToolkitRegistrationLive,
773 DeviceToolkitRegistrationLive,
774 HtmlToolkitRegistrationLive,
775).pipe(Layer.provideMerge(McpTransportLive));
755 layerPreviewToolkit,
756 layerOrchestratorToolkit,
757 layerThreadToolkit,
758 layerAttachmentRegistration,
759 layerProjectRegistration,
760 layerEnvironmentRegistration,
761 layerPreviewControlsRegistration,
762 layerWorktreeToolkitRegistration,
763 layerPullRequestsToolkit,
764 layerDeviceToolkit,
765 layerHtmlToolkit,
766).pipe(Layer.provideMerge(layerMcpTransport));
apps/server/src/mcp/OrchestratorMcpService.test.ts+28-22
......@@ -78,7 +78,7 @@ describe("OrchestratorMcpService", () => {
7878 providerThreads: [],
7979 } as unknown as OrchestrationV2ThreadProjection;
8080 let hasNestedWork = true;
81 const dependencies = Layer.mergeAll(
81 const layerDependencies = Layer.mergeAll(
8282 NodeServices.layer,
8383 Layer.mock(ThreadManagementService.ThreadManagementService)({
8484 getThreadRecords: (threadId) =>
......@@ -156,7 +156,7 @@ describe("OrchestratorMcpService", () => {
156156 const commandIds = yield* Ref.get(acknowledgementCommandIds);
157157 assert.equal(commandIds.length, 2);
158158 assert.notEqual(commandIds[0], commandIds[1]);
159 }).pipe(Effect.provide(OrchestratorMcpService.layer.pipe(Layer.provide(dependencies))));
159 }).pipe(Effect.provide(OrchestratorMcpService.layer.pipe(Layer.provide(layerDependencies))));
160160 }),
161161 );
162162
......@@ -195,7 +195,7 @@ describe("OrchestratorMcpService", () => {
195195 providerThreads: [],
196196 turnItems: [],
197197 } as unknown as OrchestrationV2ThreadProjection;
198 const dependencies = Layer.mergeAll(
198 const layerDependencies = Layer.mergeAll(
199199 NodeServices.layer,
200200 Layer.mock(ThreadManagementService.ThreadManagementService)({
201201 getThreadRecords: (threadId) =>
......@@ -243,7 +243,7 @@ describe("OrchestratorMcpService", () => {
243243 const settled = yield* service.taskStatus(scope, taskId);
244244 assert.equal(settled.status, "cancelled");
245245 assert.equal(yield* Ref.get(dispatched), 1);
246 }).pipe(Effect.provide(OrchestratorMcpService.layer.pipe(Layer.provide(dependencies))));
246 }).pipe(Effect.provide(OrchestratorMcpService.layer.pipe(Layer.provide(layerDependencies))));
247247 }),
248248 );
249249
......@@ -278,7 +278,7 @@ describe("OrchestratorMcpService", () => {
278278 subagents: [],
279279 providerThreads: [],
280280 } as unknown as OrchestrationV2ThreadProjection;
281 const dependencies = Layer.mergeAll(
281 const layerDependencies = Layer.mergeAll(
282282 NodeServices.layer,
283283 Layer.mock(ThreadManagementService.ThreadManagementService)({
284284 getThreadRecords: (threadId) =>
......@@ -316,7 +316,7 @@ describe("OrchestratorMcpService", () => {
316316 .pipe(Effect.flip);
317317 assert.equal(error.code, "task_not_cancellable");
318318 assert.deepEqual(yield* Ref.get(dispatched), []);
319 }).pipe(Effect.provide(OrchestratorMcpService.layer.pipe(Layer.provide(dependencies))));
319 }).pipe(Effect.provide(OrchestratorMcpService.layer.pipe(Layer.provide(layerDependencies))));
320320 }),
321321 );
322322
......@@ -352,7 +352,7 @@ describe("OrchestratorMcpService", () => {
352352 subagents: [],
353353 providerThreads: [],
354354 } as unknown as OrchestrationV2ThreadProjection;
355 const dependencies = Layer.mergeAll(
355 const layerDependencies = Layer.mergeAll(
356356 NodeServices.layer,
357357 Layer.mock(ThreadManagementService.ThreadManagementService)({
358358 getThreadRecords: (threadId) =>
......@@ -393,7 +393,7 @@ describe("OrchestratorMcpService", () => {
393393 (yield* Ref.get(dispatched)).map((command) => (command as { type: string }).type),
394394 ["thread.stop"],
395395 );
396 }).pipe(Effect.provide(OrchestratorMcpService.layer.pipe(Layer.provide(dependencies))));
396 }).pipe(Effect.provide(OrchestratorMcpService.layer.pipe(Layer.provide(layerDependencies))));
397397 }),
398398 );
399399
......@@ -429,7 +429,7 @@ describe("OrchestratorMcpService", () => {
429429 subagents: [],
430430 providerThreads: [],
431431 } as unknown as OrchestrationV2ThreadProjection;
432 const dependencies = Layer.mergeAll(
432 const layerDependencies = Layer.mergeAll(
433433 NodeServices.layer,
434434 Layer.mock(ThreadManagementService.ThreadManagementService)({
435435 getThreadRecords: (threadId) =>
......@@ -476,7 +476,7 @@ describe("OrchestratorMcpService", () => {
476476 (yield* Ref.get(dispatched)).map((command) => (command as { type: string }).type),
477477 ["thread.stop", "delegated_task.completion-delivery.dispose"],
478478 );
479 }).pipe(Effect.provide(OrchestratorMcpService.layer.pipe(Layer.provide(dependencies))));
479 }).pipe(Effect.provide(OrchestratorMcpService.layer.pipe(Layer.provide(layerDependencies))));
480480 }),
481481 );
482482});
......@@ -641,7 +641,7 @@ describe("OrchestratorMcpService provider resolution", () => {
641641 }),
642642 forkShadow,
643643 ];
644 const dependencies = Layer.mergeAll(
644 const layerDependencies = Layer.mergeAll(
645645 NodeServices.layer,
646646 Layer.mock(ThreadManagementService.ThreadManagementService)({
647647 getThreadRecords: () => Effect.succeed(parentProjection([])),
......@@ -729,7 +729,9 @@ describe("OrchestratorMcpService provider resolution", () => {
729729 assert.isTrue(
730730 fork!.constraints.includes("Driver 'forkOnly' is not registered in this build."),
731731 );
732 }).pipe(Effect.provide(OrchestratorMcpService.layer.pipe(Layer.provide(dependencies))));
732 }).pipe(
733 Effect.provide(OrchestratorMcpService.layer.pipe(Layer.provide(layerDependencies))),
734 );
733735 }),
734736 );
735737
......@@ -759,7 +761,7 @@ describe("OrchestratorMcpService provider resolution", () => {
759761 completedAt: null,
760762 };
761763 const dispatched = yield* Ref.make<ReadonlyArray<unknown>>([]);
762 const dependencies = Layer.mergeAll(
764 const layerDependencies = Layer.mergeAll(
763765 NodeServices.layer,
764766 Layer.mock(ThreadManagementService.ThreadManagementService)({
765767 getThreadRecords: (threadId) =>
......@@ -826,7 +828,9 @@ describe("OrchestratorMcpService provider resolution", () => {
826828 assert.equal(request.type, "delegated_task.request");
827829 assert.equal(request.modelSelection.instanceId, antigravityInstanceId);
828830 assert.equal(request.modelSelection.model, "ant-model");
829 }).pipe(Effect.provide(OrchestratorMcpService.layer.pipe(Layer.provide(dependencies))));
831 }).pipe(
832 Effect.provide(OrchestratorMcpService.layer.pipe(Layer.provide(layerDependencies))),
833 );
830834 }),
831835 );
832836
......@@ -854,7 +858,7 @@ describe("OrchestratorMcpService provider resolution", () => {
854858 completedAt: null,
855859 };
856860 let delegated = false;
857 const dependencies = Layer.mergeAll(
861 const layerDependencies = Layer.mergeAll(
858862 NodeServices.layer,
859863 Layer.mock(ThreadManagementService.ThreadManagementService)({
860864 getThreadRecords: (threadId) =>
......@@ -918,7 +922,7 @@ describe("OrchestratorMcpService provider resolution", () => {
918922 };
919923 assert.equal(request.modelSelection.instanceId, antigravityInstanceId);
920924 assert.equal(request.modelSelection.model, "ant-model");
921 }).pipe(Effect.provide(OrchestratorMcpService.layer.pipe(Layer.provide(dependencies))));
925 }).pipe(Effect.provide(OrchestratorMcpService.layer.pipe(Layer.provide(layerDependencies))));
922926 }),
923927 );
924928
......@@ -931,7 +935,7 @@ describe("OrchestratorMcpService provider resolution", () => {
931935 reason: "Driver 'forkOnly' is not registered in this build.",
932936 checkedAt: "2026-09-13T00:00:00.000Z",
933937 });
934 const dependencies = Layer.mergeAll(
938 const layerDependencies = Layer.mergeAll(
935939 NodeServices.layer,
936940 Layer.mock(ThreadManagementService.ThreadManagementService)({
937941 getThreadRecords: () => Effect.succeed(parentProjection([])),
......@@ -975,7 +979,7 @@ describe("OrchestratorMcpService provider resolution", () => {
975979 assert.isTrue(
976980 byDriver.message.includes("No V2 provider adapter is registered for driver forkOnly."),
977981 );
978 }).pipe(Effect.provide(OrchestratorMcpService.layer.pipe(Layer.provide(dependencies))));
982 }).pipe(Effect.provide(OrchestratorMcpService.layer.pipe(Layer.provide(layerDependencies))));
979983 }),
980984 );
981985
......@@ -1023,7 +1027,7 @@ describe("OrchestratorMcpService provider resolution", () => {
10231027 let cliInstalled = false;
10241028 const probes = yield* Ref.make(0);
10251029 const dispatched = yield* Ref.make(0);
1026 const dependencies = Layer.mergeAll(
1030 const layerDependencies = Layer.mergeAll(
10271031 NodeServices.layer,
10281032 Layer.mock(ThreadManagementService.ThreadManagementService)({
10291033 getThreadRecords: (threadId) =>
......@@ -1077,7 +1081,7 @@ describe("OrchestratorMcpService provider resolution", () => {
10771081 assert.equal(result.providerInstanceId, claudeInstanceId);
10781082 assert.equal(yield* Ref.get(probes), 2);
10791083 assert.equal(yield* Ref.get(dispatched), 1);
1080 }).pipe(Effect.provide(OrchestratorMcpService.layer.pipe(Layer.provide(dependencies))));
1084 }).pipe(Effect.provide(OrchestratorMcpService.layer.pipe(Layer.provide(layerDependencies))));
10811085 }),
10821086 );
10831087
......@@ -1167,7 +1171,7 @@ describe("OrchestratorMcpService provider resolution", () => {
11671171 for (const testCase of cases) {
11681172 const dispatched = yield* Ref.make<ReadonlyArray<unknown>>([]);
11691173 let delegated = false;
1170 const dependencies = Layer.mergeAll(
1174 const layerDependencies = Layer.mergeAll(
11711175 NodeServices.layer,
11721176 Layer.mock(ThreadManagementService.ThreadManagementService)({
11731177 getThreadRecords: (threadId) =>
......@@ -1284,7 +1288,9 @@ describe("OrchestratorMcpService provider resolution", () => {
12841288 } else {
12851289 assert.equal(request.modelSelection.model, "codex-alt-model", testCase.name);
12861290 }
1287 }).pipe(Effect.provide(OrchestratorMcpService.layer.pipe(Layer.provide(dependencies))));
1291 }).pipe(
1292 Effect.provide(OrchestratorMcpService.layer.pipe(Layer.provide(layerDependencies))),
1293 );
12881294 }
12891295 }),
12901296 );
apps/server/src/mcp/OrchestratorMcpToolkit.integration.test.ts+36-39
......@@ -64,15 +64,12 @@ import {
6464import * as ProviderAdapterRegistry from "../orchestration-v2/ProviderAdapterRegistry.ts";
6565import * as ProviderContinuationRequests from "../orchestration-v2/ProviderContinuationRequests.ts";
6666import { checkpointWorkspace } from "../orchestration-v2/testkit/ReplayFixtureWorkspace.ts";
67import {
68 makeOrchestratorV2ProviderReplayLayer,
69 makeOrchestratorV2ReplayLayerWithRegistry,
70} from "../orchestration-v2/testkit/ProviderReplayHarness.ts";
67import * as ProviderReplayHarness from "../orchestration-v2/testkit/ProviderReplayHarness.ts";
7168import {
7269 decodeProviderReplayNdjson,
7370 materializeReplayTranscriptWorkspace,
7471} from "../orchestration-v2/testkit/ReplayTranscriptNdjson.ts";
75import { makeProviderRegistryLayer } from "../provider/testUtils/providerRegistryMock.ts";
72import * as ProviderRegistryMock from "../provider/testUtils/providerRegistryMock.ts";
7673import * as ProjectService from "../project/ProjectService.ts";
7774import * as ScheduledTaskService from "../scheduledTasks/ScheduledTaskService.ts";
7875import * as SecretRequests from "../secrets/SecretRequests.ts";
......@@ -480,7 +477,7 @@ function scheduledTaskFromUpsert(input: ScheduledTaskUpsertInput): ScheduledTask
480477}
481478
482479/** In-memory server secret store for tests that exercise secret requests. */
483const memorySecretStoreLayer = Layer.sync(ServerSecretStore.ServerSecretStore, () => {
480const layerMemorySecretStore = Layer.sync(ServerSecretStore.ServerSecretStore, () => {
484481 const stored = new Map<string, Uint8Array>();
485482 return ServerSecretStore.ServerSecretStore.of({
486483 get: (name) => Effect.succeed(Option.fromNullishOr(stored.get(name))),
......@@ -498,7 +495,7 @@ const memorySecretStoreLayer = Layer.sync(ServerSecretStore.ServerSecretStore, (
498495 });
499496});
500497
501const unusedScheduledTaskStubLayer = Layer.succeed(
498const layerUnusedScheduledTaskStub = Layer.succeed(
502499 ScheduledTaskService.ScheduledTaskService,
503500 ScheduledTaskService.ScheduledTaskService.of({
504501 list: () => Effect.succeed({ tasks: [] }),
......@@ -524,7 +521,7 @@ describe("orchestrator MCP toolkit", () => {
524521 const capturedTurns = yield* Ref.make<ReadonlyArray<CapturedTurn>>([]);
525522 const parentTerminalGates = new Map<ThreadId, Deferred.Deferred<void>>();
526523 const deliveryTerminalGates = new Map<ThreadId, Deferred.Deferred<void>>();
527 const registryLayer = ProviderAdapterRegistry.makeLayer([
524 const layerRegistry = ProviderAdapterRegistry.layerFromAdapters([
528525 makeDeterministicAdapter({
529526 instanceId: codexInstanceId,
530527 driver: ProviderDriverKind.make("codex"),
......@@ -566,7 +563,7 @@ describe("orchestrator MCP toolkit", () => {
566563 const continuationOffers = yield* Ref.make<
567564 ReadonlyArray<ProviderContinuationRequests.ProviderContinuationRequest>
568565 >([]);
569 const continuationProbeLayer = Layer.succeed(
566 const layerContinuationProbe = Layer.succeed(
570567 ProviderContinuationRequests.ProviderContinuationRequests,
571568 {
572569 offer: (request) =>
......@@ -596,7 +593,7 @@ describe("orchestrator MCP toolkit", () => {
596593 expect(yield* Ref.get(continuationOffers)).toHaveLength(count);
597594 }
598595 });
599 const orchestratorLayer = makeOrchestratorV2ReplayLayerWithRegistry(
596 const layerOrchestrator = ProviderReplayHarness.layerWithRegistry(
600597 {
601598 name: "orchestrator-mcp-toolkit",
602599 runtimePolicyOverride: {
......@@ -609,13 +606,13 @@ describe("orchestrator MCP toolkit", () => {
609606 },
610607 },
611608 },
612 registryLayer,
613 ).pipe(Layer.provide(continuationProbeLayer));
614 const orchestrationLayer = Layer.merge(
615 orchestratorLayer,
616 ThreadManagementService.layer.pipe(Layer.provide(orchestratorLayer)),
609 layerRegistry,
610 ).pipe(Layer.provide(layerContinuationProbe));
611 const layerOrchestration = Layer.merge(
612 layerOrchestrator,
613 ThreadManagementService.layer.pipe(Layer.provide(layerOrchestrator)),
617614 );
618 const providerRegistryLayer = makeProviderRegistryLayer([
615 const layerProviderRegistry = ProviderRegistryMock.layer([
619616 makeProviderSnapshot({
620617 instanceId: codexInstanceId,
621618 driver: ProviderDriverKind.make("codex"),
......@@ -647,7 +644,7 @@ describe("orchestrator MCP toolkit", () => {
647644 // In-memory ScheduledTaskService stub so the schedule/list/update/
648645 // delete tools can be exercised without SQL/launch wiring.
649646 const scheduledStore = yield* Ref.make<ReadonlyArray<ScheduledTask>>([]);
650 const scheduledTaskStubLayer = Layer.succeed(
647 const layerScheduledTaskStub = Layer.succeed(
651648 ScheduledTaskService.ScheduledTaskService,
652649 ScheduledTaskService.ScheduledTaskService.of({
653650 list: () => Ref.get(scheduledStore).pipe(Effect.map((tasks) => ({ tasks }))),
......@@ -674,15 +671,15 @@ describe("orchestrator MCP toolkit", () => {
674671 triggerWebhook: () => Effect.die("unused in this test"),
675672 }),
676673 );
677 const testLayer = Layer.merge(
678 McpHttpServer.OrchestratorToolkitRegistrationLive,
679 McpHttpServer.ThreadToolkitRegistrationLive,
674 const layerTest = Layer.merge(
675 McpHttpServer.layerOrchestratorToolkit,
676 McpHttpServer.layerThreadToolkit,
680677 ).pipe(
681678 Layer.provideMerge(McpServer.McpServer.layer),
682 Layer.provideMerge(orchestrationLayer),
683 Layer.provide(registryLayer),
684 Layer.provide(providerRegistryLayer),
685 Layer.provide(scheduledTaskStubLayer),
679 Layer.provideMerge(layerOrchestration),
680 Layer.provide(layerRegistry),
681 Layer.provide(layerProviderRegistry),
682 Layer.provide(layerScheduledTaskStub),
686683 Layer.provide(
687684 Layer.mock(ProjectService.ProjectService)({
688685 getById: (id) =>
......@@ -695,8 +692,8 @@ describe("orchestrator MCP toolkit", () => {
695692 ),
696693 Layer.provideMerge(
697694 SecretRequests.layer.pipe(
698 Layer.provide(memorySecretStoreLayer),
699 Layer.provide(orchestrationLayer),
695 Layer.provide(layerMemorySecretStore),
696 Layer.provide(layerOrchestration),
700697 ),
701698 ),
702699 Layer.provide(NodeServices.layer),
......@@ -3677,7 +3674,7 @@ describe("orchestrator MCP toolkit", () => {
36773674 thirdFanoutDelivery.messageId,
36783675 ]),
36793676 );
3680 }).pipe(Effect.provide(testLayer));
3677 }).pipe(Effect.provide(layerTest));
36813678 }),
36823679 ),
36833680 );
......@@ -3690,7 +3687,7 @@ describe("orchestrator MCP toolkit", () => {
36903687 const transcript = yield* CodexOrchestratorReplayHarness.decodeTranscript(
36913688 materializeReplayTranscriptWorkspace(rawTranscript, cwd),
36923689 );
3693 const orchestratorLayer = makeOrchestratorV2ProviderReplayLayer(
3690 const layerOrchestrator = ProviderReplayHarness.layerProviderReplay(
36943691 {
36953692 name: "delegated-task-status/codex",
36963693 transcript,
......@@ -3699,30 +3696,30 @@ describe("orchestrator MCP toolkit", () => {
36993696 },
37003697 CodexOrchestratorReplayHarness,
37013698 );
3702 const orchestrationLayer = Layer.merge(
3703 orchestratorLayer,
3704 ThreadManagementService.layer.pipe(Layer.provide(orchestratorLayer)),
3699 const layerOrchestration = Layer.merge(
3700 layerOrchestrator,
3701 ThreadManagementService.layer.pipe(Layer.provide(layerOrchestrator)),
37053702 );
3706 const providerRegistryLayer = makeProviderRegistryLayer([
3703 const layerProviderRegistry = ProviderRegistryMock.layer([
37073704 makeProviderSnapshot({
37083705 instanceId: codexInstanceId,
37093706 driver: ProviderDriverKind.make("codex"),
37103707 model: codexModel,
37113708 }),
37123709 ]);
3713 const testLayer = McpHttpServer.OrchestratorToolkitRegistrationLive.pipe(
3710 const layerTest = McpHttpServer.layerOrchestratorToolkit.pipe(
37143711 Layer.provideMerge(McpServer.McpServer.layer),
3715 Layer.provideMerge(orchestrationLayer),
3712 Layer.provideMerge(layerOrchestration),
37163713 Layer.provide(
37173714 CodexOrchestratorReplayHarness.makeProviderAdapterRegistryLayer(transcript),
37183715 ),
3719 Layer.provide(providerRegistryLayer),
3720 Layer.provide(unusedScheduledTaskStubLayer),
3716 Layer.provide(layerProviderRegistry),
3717 Layer.provide(layerUnusedScheduledTaskStub),
37213718 Layer.provide(Layer.mock(ProjectService.ProjectService)({})),
37223719 Layer.provideMerge(
37233720 SecretRequests.layer.pipe(
3724 Layer.provide(memorySecretStoreLayer),
3725 Layer.provide(orchestrationLayer),
3721 Layer.provide(layerMemorySecretStore),
3722 Layer.provide(layerOrchestration),
37263723 ),
37273724 ),
37283725 Layer.provide(NodeServices.layer),
......@@ -3998,7 +3995,7 @@ describe("orchestrator MCP toolkit", () => {
39983995 latestTerminalSummary: queuedFollowupResult,
39993996 latestTerminalResultContextTransferId: null,
40003997 });
4001 }).pipe(Effect.provide(testLayer));
3998 }).pipe(Effect.provide(layerTest));
40023999 }),
40034000 ),
40044001 );
apps/server/src/mcp/PreviewAutomationBroker.test.ts+2-2
......@@ -29,7 +29,7 @@ import * as TestClock from "effect/testing/TestClock";
2929import * as RpcGroup from "effect/rpc/RpcGroup";
3030import * as RpcTest from "effect/rpc/RpcTest";
3131
32import { rpcScopeAuthorizationLayer } from "../auth/RpcAuthorization.ts";
32import * as RpcAuthorization from "../auth/RpcAuthorization.ts";
3333import * as PreviewAutomationBroker from "./PreviewAutomationBroker.ts";
3434
3535const makeBroker = PreviewAutomationBroker.make.pipe(Effect.provide(NodeServices.layer));
......@@ -1276,7 +1276,7 @@ it.effect("evicts an unanswered host and lets later calls use a healthy runtime"
12761276 group.toLayer({
12771277 [WS_METHODS.previewAutomationConnect]: (host) => Stream.unwrap(broker.connect(host)),
12781278 }),
1279 rpcScopeAuthorizationLayer([AuthOrchestrationOperateScope]),
1279 RpcAuthorization.layer([AuthOrchestrationOperateScope]),
12801280 ),
12811281 ),
12821282 );
apps/server/src/mcp/ThreadMetadataMcpService.test.ts+5-5
......@@ -23,7 +23,7 @@ const scope: McpInvocationContext.McpInvocationScope = {
2323 issuedAt: 1,
2424};
2525
26function serviceLayer(
26function layerService(
2727 getThreadShell: ThreadManagement.ThreadManagementService["Service"]["getThreadShell"],
2828) {
2929 return ThreadMetadataMcp.layer.pipe(
......@@ -51,7 +51,7 @@ const updateCallingThread = Effect.gen(function* () {
5151it.effect("reports an absent calling thread as thread_not_found", () =>
5252 Effect.gen(function* () {
5353 const error = yield* updateCallingThread.pipe(
54 Effect.provide(serviceLayer(() => Effect.succeed(null))),
54 Effect.provide(layerService(() => Effect.succeed(null))),
5555 Effect.flip,
5656 );
5757
......@@ -63,7 +63,7 @@ it.effect("keeps calling-thread storage failures as orchestration errors", () =>
6363 Effect.gen(function* () {
6464 const error = yield* updateCallingThread.pipe(
6565 Effect.provide(
66 serviceLayer(() =>
66 layerService(() =>
6767 Effect.fail(
6868 new OrchestratorProjectionError({
6969 threadId,
......@@ -115,7 +115,7 @@ it.effect("refuses to change a thread that runs above the caller's modes", () =>
115115 title: "Renamed from a narrower thread",
116116 });
117117 }).pipe(
118 Effect.provide(serviceLayer((id) => Effect.succeed((shells.get(id) ?? null) as never))),
118 Effect.provide(layerService((id) => Effect.succeed((shells.get(id) ?? null) as never))),
119119 Effect.flip,
120120 );
121121
......@@ -159,7 +159,7 @@ it.effect("refuses another thread's metadata to a thread caller whose run has en
159159 title: "Renamed after the run ended",
160160 });
161161 }).pipe(
162 Effect.provide(serviceLayer((id) => Effect.succeed((shells.get(id) ?? null) as never))),
162 Effect.provide(layerService((id) => Effect.succeed((shells.get(id) ?? null) as never))),
163163 Effect.flip,
164164 );
165165
apps/server/src/mcp/WorktreeMcpService.test.ts+2-2
......@@ -294,7 +294,7 @@ const makeHarness = (options: HarnessOptions = {}) => {
294294 // inlined so the test does not depend on the host's path module.
295295 const win32IsAbsolute = (value: string) => /^(?:[a-zA-Z]:[\\/]|[\\/])/.test(value);
296296 const posixIsAbsolute = (value: string) => value.startsWith("/");
297 const serviceLayer =
297 const layerService =
298298 options.pathSemantics === undefined
299299 ? WorktreeMcpService.layer
300300 : WorktreeMcpService.layer.pipe(
......@@ -304,7 +304,7 @@ const makeHarness = (options: HarnessOptions = {}) => {
304304 } as unknown as Path.Path),
305305 ),
306306 );
307 const layer = serviceLayer.pipe(
307 const layer = layerService.pipe(
308308 Layer.provide(
309309 Layer.mergeAll(
310310 Layer.mock(ThreadManagementService.ThreadManagementService)({
apps/server/src/mcp/toolkits/attachment/handlers.ts+1-1
......@@ -31,7 +31,7 @@ export function resolveAttachmentReferences(
3131 });
3232}
3333
34export const AttachmentHandlersLive = AttachmentToolkit.toLayer({
34export const layer = AttachmentToolkit.toLayer({
3535 t3_attachment_prepare_upload: (input) =>
3636 Effect.gen(function* () {
3737 yield* readMutationCaller();
apps/server/src/mcp/toolkits/core.test.ts+7-7
......@@ -137,7 +137,7 @@ it.effect("checks capability before accessing services through the production re
137137 expect(declaredFailure(result)).toMatchObject({ code: "capability_denied" });
138138 }).pipe(
139139 Effect.provide(
140 McpHttpServer.ThreadToolkitRegistrationLive.pipe(
140 McpHttpServer.layerThreadToolkit.pipe(
141141 Layer.provideMerge(McpServer.McpServer.layer),
142142 Layer.provide(NodeCrypto.layer),
143143 Layer.provide(Layer.mock(ThreadManagement.ThreadManagementService)({})),
......@@ -162,7 +162,7 @@ it.effect("returns a bounded public failure without serializing storage causes",
162162 });
163163 }).pipe(
164164 Effect.provide(
165 McpHttpServer.ThreadToolkitRegistrationLive.pipe(
165 McpHttpServer.layerThreadToolkit.pipe(
166166 Layer.provideMerge(McpServer.McpServer.layer),
167167 Layer.provide(NodeCrypto.layer),
168168 Layer.provide(
......@@ -203,7 +203,7 @@ it.effect("returns an HTML render reference that Codex and Claude tool rows both
203203 ).toEqual(reference);
204204 }).pipe(
205205 Effect.provide(
206 McpHttpServer.HtmlToolkitRegistrationLive.pipe(
206 McpHttpServer.layerHtmlToolkit.pipe(
207207 Layer.provideMerge(McpServer.McpServer.layer),
208208 Layer.provide(ServerConfig.layerTest(process.cwd(), { prefix: "t3-mcp-html-render-" })),
209209 Layer.provide(NodeServices.layer),
......@@ -332,7 +332,7 @@ it.effect("a client caller targets any thread within its ceiling and cannot act
332332 expect(declaredFailure(forked)).toMatchObject({ code: "target_required" });
333333 }).pipe(
334334 Effect.provide(
335 McpHttpServer.ThreadToolkitRegistrationLive.pipe(
335 McpHttpServer.layerThreadToolkit.pipe(
336336 Layer.provideMerge(McpServer.McpServer.layer),
337337 Layer.provide(NodeCrypto.layer),
338338 Layer.provide(
......@@ -379,7 +379,7 @@ it.effect("refuses act-as-caller tools to a client caller", () =>
379379 expect(declaredFailure(result)).toMatchObject({ code: "thread_credential_required" });
380380 }).pipe(
381381 Effect.provide(
382 McpHttpServer.OrchestratorToolkitRegistrationLive.pipe(
382 McpHttpServer.layerOrchestratorToolkit.pipe(
383383 Layer.provideMerge(McpServer.McpServer.layer),
384384 Layer.provide(NodeCrypto.layer),
385385 Layer.provide(Layer.mock(ThreadManagement.ThreadManagementService)({})),
......@@ -417,7 +417,7 @@ it.effect("a caller cannot rewrite a scheduled task that runs above its own mode
417417 expect(allowed.isError).toBe(false);
418418 }).pipe(
419419 Effect.provide(
420 McpHttpServer.OrchestratorToolkitRegistrationLive.pipe(
420 McpHttpServer.layerOrchestratorToolkit.pipe(
421421 Layer.provideMerge(McpServer.McpServer.layer),
422422 Layer.provide(NodeCrypto.layer),
423423 Layer.provide(Layer.mock(ThreadManagement.ThreadManagementService)({})),
......@@ -483,7 +483,7 @@ it.effect("a caller cannot interrupt a thread that runs above its own modes", ()
483483 expect(declaredFailure(result)).toMatchObject({ code: "runtime_mode_escalation_denied" });
484484 }).pipe(
485485 Effect.provide(
486 McpHttpServer.OrchestratorToolkitRegistrationLive.pipe(
486 McpHttpServer.layerOrchestratorToolkit.pipe(
487487 Layer.provideMerge(McpServer.McpServer.layer),
488488 Layer.provide(NodeCrypto.layer),
489489 Layer.provide(
apps/server/src/mcp/toolkits/device/handlers.ts+2-2
......@@ -258,8 +258,8 @@ export function pngDimensions(png: Uint8Array): { width: number; height: number
258258
259259const { device_screenshot, ...standardHandlers } = handlers;
260260
261export const DeviceStandardToolkitHandlersLive = DeviceStandardToolkit.toLayer(standardHandlers);
261export const layerStandard = DeviceStandardToolkit.toLayer(standardHandlers);
262262
263export const DeviceScreenshotToolkitHandlersLive = DeviceScreenshotToolkit.toLayer({
263export const layerScreenshot = DeviceScreenshotToolkit.toLayer({
264264 device_screenshot,
265265});
apps/server/src/mcp/toolkits/environment/handlers.ts+1-1
......@@ -44,7 +44,7 @@ const access = (writable = false) =>
4444 });
4545 return { ...context, descriptor, settings: yield* Settings.ServerSettingsService };
4646 });
47export const EnvironmentHandlersLive = EnvironmentToolkit.toLayer({
47export const layer = EnvironmentToolkit.toLayer({
4848 t3_environment_read: () =>
4949 Effect.gen(function* () {
5050 const { descriptor, settings } = yield* access();
apps/server/src/mcp/toolkits/html/handlers.ts+2-2
......@@ -58,10 +58,10 @@ const handlers = {
5858 }),
5959} satisfies Parameters<typeof HtmlToolkit.toLayer>[0];
6060
61export const HtmlPreviewToolkitHandlersLive = HtmlPreviewToolkit.toLayer({
61export const layerPreview = HtmlPreviewToolkit.toLayer({
6262 html_preview: handlers.html_preview,
6363});
6464
65export const HtmlRenderToolkitHandlersLive = HtmlRenderToolkit.toLayer({
65export const layerRender = HtmlRenderToolkit.toLayer({
6666 html_render: handlers.html_render,
6767});
apps/server/src/mcp/toolkits/orchestrator/handlers.ts+1-1
......@@ -104,4 +104,4 @@ const handlers = {
104104 }),
105105} satisfies Parameters<typeof OrchestratorToolkit.toLayer>[0];
106106
107export const OrchestratorToolkitHandlersLive = OrchestratorToolkit.toLayer(handlers);
107export const layer = OrchestratorToolkit.toLayer(handlers);
apps/server/src/mcp/toolkits/preview/handlers.ts+2-2
......@@ -233,8 +233,8 @@ const handlers = {
233233
234234const { preview_snapshot, ...standardHandlers } = handlers;
235235
236export const PreviewStandardToolkitHandlersLive = PreviewStandardToolkit.toLayer(standardHandlers);
236export const layerStandard = PreviewStandardToolkit.toLayer(standardHandlers);
237237
238export const PreviewSnapshotToolkitHandlersLive = PreviewSnapshotToolkit.toLayer({
238export const layerSnapshot = PreviewSnapshotToolkit.toLayer({
239239 preview_snapshot,
240240});
apps/server/src/mcp/toolkits/previewControls/handlers.test.ts+5-5
......@@ -14,7 +14,7 @@ import * as Stream from "effect/Stream";
1414import * as Preview from "../../../preview/Manager.ts";
1515import * as ServerSettings from "../../../serverSettings.ts";
1616import * as McpInvocationContext from "../../McpInvocationContext.ts";
17import { PreviewControlsHandlersLive } from "./handlers.ts";
17import * as PreviewControlsHandlers from "./handlers.ts";
1818import { PreviewControlsToolkit } from "./tools.ts";
1919
2020it.effect.each([
......@@ -47,7 +47,7 @@ it.effect.each([
4747 };
4848 const manager = yield* Preview.make;
4949 const tab = yield* manager.open({ threadId, url: "http://localhost:3000" });
50 const dependencies = Layer.mergeAll(
50 const layerDependencies = Layer.mergeAll(
5151 Layer.succeed(Preview.PreviewManager, manager),
5252 Layer.succeed(McpInvocationContext.McpInvocationContext, scope),
5353 Layer.mock(ServerSettings.ServerSettingsService)({
......@@ -55,14 +55,14 @@ it.effect.each([
5555 }),
5656 );
5757 const toolkit = yield* PreviewControlsToolkit.pipe(
58 Effect.provide(PreviewControlsHandlersLive.pipe(Layer.provide(dependencies))),
58 Effect.provide(PreviewControlsHandlers.layer.pipe(Layer.provide(layerDependencies))),
5959 );
6060 const listed = yield* toolkit
6161 .handle("t3_preview_list", {})
62 .pipe(Stream.unwrap, Stream.runCollect, Effect.provide(dependencies));
62 .pipe(Stream.unwrap, Stream.runCollect, Effect.provide(layerDependencies));
6363 const closed = yield* toolkit
6464 .handle("t3_preview_close", { tabId: tab.tabId })
65 .pipe(Stream.unwrap, Stream.runCollect, Effect.provide(dependencies));
65 .pipe(Stream.unwrap, Stream.runCollect, Effect.provide(layerDependencies));
6666 if (projectAccess) {
6767 expect(listed.at(-1)?.result).toMatchObject({ sessions: [tab], nextCursor: null });
6868 expect(closed.at(-1)?.result).toEqual({});
apps/server/src/mcp/toolkits/previewControls/handlers.ts+1-1
......@@ -9,7 +9,7 @@ const access = Effect.gen(function* () {
99 const scope = yield* requireThreadMcpCapability("preview");
1010 return { scope, manager: yield* Preview.PreviewManager };
1111});
12export const PreviewControlsHandlersLive = PreviewControlsToolkit.toLayer({
12export const layer = PreviewControlsToolkit.toLayer({
1313 t3_preview_list: (input) =>
1414 Effect.gen(function* () {
1515 const { scope, manager } = yield* access;
apps/server/src/mcp/toolkits/project/handlers.test.ts+13-13
......@@ -20,7 +20,7 @@ import * as ServerConfig from "../../../config.ts";
2020import * as Project from "../../../project/ProjectService.ts";
2121import * as ManagedProjectFolders from "../../../project/ManagedProjectFolders.ts";
2222import * as McpInvocationContext from "../../McpInvocationContext.ts";
23import { ProjectHandlersLive } from "./handlers.ts";
23import * as ProjectHandlers from "./handlers.ts";
2424import { ProjectToolkit } from "./tools.ts";
2525
2626it.effect("attributes a launched thread's first message to the calling thread", () =>
......@@ -41,7 +41,7 @@ it.effect("attributes a launched thread's first message to the calling thread",
4141 deletedAt: null,
4242 } as OrchestrationV2ThreadShell;
4343 let launchedSender: ThreadId | undefined;
44 const dependencies = Layer.mergeAll(
44 const layerDependencies = Layer.mergeAll(
4545 NodeCrypto.layer,
4646 Layer.succeed(McpInvocationContext.McpInvocationContext, {
4747 environmentId: EnvironmentId.make("environment"),
......@@ -79,11 +79,11 @@ it.effect("attributes a launched thread's first message to the calling thread",
7979 ),
8080 );
8181 const toolkit = yield* ProjectToolkit.pipe(
82 Effect.provide(ProjectHandlersLive.pipe(Layer.provide(dependencies))),
82 Effect.provide(ProjectHandlers.layer.pipe(Layer.provide(layerDependencies))),
8383 );
8484 const result = yield* toolkit
8585 .handle("t3_thread_launch", { title: "Audit", message: "Review the change" })
86 .pipe(Stream.unwrap, Stream.runCollect, Effect.provide(dependencies));
86 .pipe(Stream.unwrap, Stream.runCollect, Effect.provide(layerDependencies));
8787 expect(result.at(-1)?.result).toMatchObject({ projectId, modelSelection });
8888 expect(launchedSender).toBe(sourceThreadId);
8989 }),
......@@ -107,7 +107,7 @@ it.effect("launches a scratch thread into the Scratch project", () =>
107107 deletedAt: null,
108108 } as OrchestrationV2ThreadShell;
109109 const launched: Array<ThreadLaunch.ThreadLaunchInput> = [];
110 const dependencies = Layer.mergeAll(
110 const layerDependencies = Layer.mergeAll(
111111 NodeCrypto.layer,
112112 Layer.succeed(McpInvocationContext.McpInvocationContext, {
113113 environmentId: EnvironmentId.make("environment"),
......@@ -148,12 +148,12 @@ it.effect("launches a scratch thread into the Scratch project", () =>
148148 ),
149149 );
150150 const toolkit = yield* ProjectToolkit.pipe(
151 Effect.provide(ProjectHandlersLive.pipe(Layer.provide(dependencies))),
151 Effect.provide(ProjectHandlers.layer.pipe(Layer.provide(layerDependencies))),
152152 );
153153 const handle = (params: Parameters<typeof toolkit.handle<"t3_thread_launch">>[1]) =>
154154 toolkit
155155 .handle("t3_thread_launch", params)
156 .pipe(Stream.unwrap, Stream.runCollect, Effect.provide(dependencies));
156 .pipe(Stream.unwrap, Stream.runCollect, Effect.provide(layerDependencies));
157157
158158 const result = yield* handle({ title: "Notes", scratch: true, message: "Draft a list" });
159159 expect(result.at(-1)?.result).toMatchObject({ projectId: scratchProjectId });
......@@ -199,7 +199,7 @@ it.effect("starts a project from just a title when workspaceRoot is omitted", ()
199199 updatedAt: "2026-10-01T00:00:00.000Z",
200200 deletedAt: null,
201201 };
202 const dependencies = Layer.mergeAll(
202 const layerDependencies = Layer.mergeAll(
203203 NodeCrypto.layer,
204204 Layer.succeed(McpInvocationContext.McpInvocationContext, {
205205 environmentId: EnvironmentId.make("environment"),
......@@ -246,12 +246,12 @@ it.effect("starts a project from just a title when workspaceRoot is omitted", ()
246246 ),
247247 );
248248 const toolkit = yield* ProjectToolkit.pipe(
249 Effect.provide(ProjectHandlersLive.pipe(Layer.provide(dependencies))),
249 Effect.provide(ProjectHandlers.layer.pipe(Layer.provide(layerDependencies))),
250250 );
251251 const handle = (params: Parameters<typeof toolkit.handle<"t3_project_create">>[1]) =>
252252 toolkit
253253 .handle("t3_project_create", params)
254 .pipe(Stream.unwrap, Stream.runCollect, Effect.provide(dependencies));
254 .pipe(Stream.unwrap, Stream.runCollect, Effect.provide(layerDependencies));
255255
256256 const result = yield* handle({ title: "Pinball Stats" });
257257 expect(result.at(-1)?.result).toMatchObject({
......@@ -283,7 +283,7 @@ const clientLaunchHarness = (input: {
283283}) => {
284284 const projectId = ProjectId.make("project:client-target");
285285 const modelSelection = { instanceId: ProviderInstanceId.make("claude"), model: "claude-opus" };
286 const dependencies = Layer.mergeAll(
286 const layerDependencies = Layer.mergeAll(
287287 NodeCrypto.layer,
288288 Layer.succeed(McpInvocationContext.McpInvocationContext, {
289289 environmentId: EnvironmentId.make("environment"),
......@@ -329,7 +329,7 @@ const clientLaunchHarness = (input: {
329329 Layer.provide(NodeServices.layer),
330330 ),
331331 );
332 return { projectId, modelSelection, dependencies };
332 return { projectId, modelSelection, dependencies: layerDependencies };
333333};
334334
335335it.effect("a client launches at its ceiling with the project's default model", () =>
......@@ -340,7 +340,7 @@ it.effect("a client launches at its ceiling with the project's default model", (
340340 launched,
341341 });
342342 const toolkit = yield* ProjectToolkit.pipe(
343 Effect.provide(ProjectHandlersLive.pipe(Layer.provide(dependencies))),
343 Effect.provide(ProjectHandlers.layer.pipe(Layer.provide(dependencies))),
344344 );
345345 const handle = (params: Parameters<typeof toolkit.handle<"t3_thread_launch">>[1]) =>
346346 toolkit
apps/server/src/mcp/toolkits/project/handlers.ts+1-1
......@@ -38,7 +38,7 @@ const mutation = Effect.gen(function* () {
3838 );
3939 return yield* Project.ProjectService;
4040});
41export const ProjectHandlersLive = ProjectToolkit.toLayer({
41export const layer = ProjectToolkit.toLayer({
4242 t3_thread_launch: (input) =>
4343 Effect.gen(function* () {
4444 const context = yield* readMutationCaller();
apps/server/src/mcp/toolkits/pullRequests/handlers.test.ts+5-4
......@@ -23,7 +23,8 @@ import {
2323} from "../../../orchestration-v2/testkit/pullRequestFixtures.ts";
2424import * as ProjectService from "../../../project/ProjectService.ts";
2525import * as McpInvocationContext from "../../McpInvocationContext.ts";
26import { listThreadPullRequests, PullRequestsToolkitHandlersLive } from "./handlers.ts";
26import { listThreadPullRequests } from "./handlers.ts";
27import * as PullRequestsHandlers from "./handlers.ts";
2728import { PullRequestLinkFailedError, PullRequestsToolkit } from "./tools.ts";
2829
2930const PROJECT_ID = ProjectId.make("project-1");
......@@ -152,7 +153,7 @@ const makeHarness = Effect.fn("makePullRequestsToolkitHarness")(function* (
152153 yield* Ref.update(commands, (recorded) => [...recorded, command]);
153154 return { sequence: 1, storedEvents: [] };
154155 });
155 const dependencies = Layer.mergeAll(
156 const layerDependencies = Layer.mergeAll(
156157 Layer.mock(ProjectService.ProjectService)({
157158 getShell: () => Effect.succeed(Option.fromNullishOr(project)),
158159 }),
......@@ -164,7 +165,7 @@ const makeHarness = Effect.fn("makePullRequestsToolkitHarness")(function* (
164165 Layer.succeed(Crypto.Crypto, testCrypto),
165166 );
166167 const toolkit = yield* PullRequestsToolkit.pipe(
167 Effect.provide(PullRequestsToolkitHandlersLive.pipe(Layer.provide(dependencies))),
168 Effect.provide(PullRequestsHandlers.layer.pipe(Layer.provide(layerDependencies))),
168169 );
169170 const call = <Name extends keyof typeof PullRequestsToolkit.tools>(
170171 name: Name,
......@@ -179,7 +180,7 @@ const makeHarness = Effect.fn("makePullRequestsToolkitHarness")(function* (
179180 (chunk) => chunk.at(-1)!.result as Tool.Success<(typeof PullRequestsToolkit.tools)[Name]>,
180181 ),
181182 Effect.provideService(McpInvocationContext.McpInvocationContext, invocation(capabilities)),
182 Effect.provide(dependencies),
183 Effect.provide(layerDependencies),
183184 );
184185 return { commands, call };
185186});
apps/server/src/mcp/toolkits/pullRequests/handlers.ts+1-1
......@@ -379,4 +379,4 @@ const make = Effect.gen(function* () {
379379 });
380380});
381381
382export const PullRequestsToolkitHandlersLive = PullRequestsToolkit.toLayer(make);
382export const layer = PullRequestsToolkit.toLayer(make);
apps/server/src/mcp/toolkits/thread/handlers.ts+1-1
......@@ -75,7 +75,7 @@ const readQuestion = Effect.fn("mcp.readQuestion")(function* (
7575 });
7676 return { ...context, request, item };
7777});
78export const ThreadToolkitHandlersLive = ThreadToolkit.toLayer({
78export const layer = ThreadToolkit.toLayer({
7979 run_scheduled_task_now: (input) =>
8080 Effect.gen(function* () {
8181 yield* readFullAccessCaller(
apps/server/src/mcp/toolkits/worktree/handlers.ts+1-1
......@@ -48,4 +48,4 @@ const handlers = {
4848 }),
4949} satisfies Parameters<typeof WorktreeToolkit.toLayer>[0];
5050
51export const WorktreeToolkitHandlersLive = WorktreeToolkit.toLayer(handlers);
51export const layer = WorktreeToolkit.toLayer(handlers);
apps/server/src/mcp/toolkits/worktree/registration.test.ts+4-4
......@@ -26,7 +26,7 @@ import * as McpHttpServer from "../../McpHttpServer.ts";
2626import * as McpSessionRegistry from "../../McpSessionRegistry.ts";
2727import * as PreviewAutomationBroker from "../../PreviewAutomationBroker.ts";
2828
29const StubServicesLive = Layer.mergeAll(
29const layerStubServices = Layer.mergeAll(
3030 Layer.mock(Orchestrator.OrchestratorV2)({}),
3131 Layer.mock(ProjectionStore.ProjectionStoreV2)({}),
3232 Layer.mock(DeviceService.DeviceService)({}),
......@@ -66,8 +66,8 @@ const decodeToolsListPayload = Schema.decodeUnknownEffect(ToolsListPayload);
6666it.effect("production mcp layer lists worktree tools over http", () =>
6767 Effect.scoped(
6868 Effect.gen(function* () {
69 const routes = McpHttpServer.layer.pipe(Layer.provide(McpSessionRegistry.layer));
70 yield* HttpRouter.serve(routes, {
69 const layerRoutes = McpHttpServer.layer.pipe(Layer.provide(McpSessionRegistry.layer));
70 yield* HttpRouter.serve(layerRoutes, {
7171 disableListenLog: true,
7272 disableLogger: true,
7373 }).pipe(
......@@ -77,7 +77,7 @@ it.effect("production mcp layer lists worktree tools over http", () =>
7777 }),
7878 ),
7979 Layer.provide(PreviewAutomationBroker.layer),
80 Layer.provide(StubServicesLive),
80 Layer.provide(layerStubServices),
8181 Layer.build,
8282 );
8383
apps/server/src/observability/Layers/Observability.ts+14-17
......@@ -1,9 +1,6 @@
1import { httpHeaderRedactionLayer } from "@t3tools/shared/httpObservability";
2import {
3 makeLocalFileTracer,
4 makeTraceSink,
5 otlpSerializationLayer,
6} from "@t3tools/shared/observability";
1import * as HttpObservability from "@t3tools/shared/httpObservability";
2import { makeLocalFileTracer, makeTraceSink } from "@t3tools/shared/observability";
3import * as SharedObservability from "@t3tools/shared/observability";
74import * as OtelEnvironment from "@t3tools/shared/otelEnvironment";
85import * as Effect from "effect/Effect";
96import * as Layer from "effect/Layer";
......@@ -15,10 +12,10 @@ import * as OtlpTracer from "effect/observability/OtlpTracer";
1512
1613import * as ServerConfig from "../../config.ts";
1714import * as ResourceAttribution from "../../resourceTelemetry/ResourceAttribution.ts";
18import { ServerLoggerLive } from "../../serverLogger.ts";
15import * as ServerLogger from "../../serverLogger.ts";
1916import * as BrowserTraceCollector from "../BrowserTraceCollector.ts";
2017
21export const ObservabilityLive = Layer.unwrap(
18export const layer = Layer.unwrap(
2219 Effect.gen(function* () {
2320 const config = yield* ServerConfig.ServerConfig;
2421
......@@ -26,17 +23,17 @@ export const ObservabilityLive = Layer.unwrap(
2623 const metrics = config.otlpMetricsExport;
2724 // The trace serializer stays in the returned context because the browser
2825 // trace forwarder exports on the same signal.
29 const serializationLayer = otlpSerializationLayer(traces.protocol);
26 const layerSerialization = SharedObservability.layerOtlpSerialization(traces.protocol);
3027 const resource = ServerConfig.otlpResource(config);
3128 const attribution = yield* ResourceAttribution.ResourceAttribution;
3229
33 const traceReferencesLayer = Layer.mergeAll(
30 const layerTraceReferences = Layer.mergeAll(
3431 Layer.succeed(Tracer.MinimumTraceLevel, config.traceMinLevel),
3532 Layer.succeed(References.TracerTimingEnabled, config.traceTimingEnabled),
36 httpHeaderRedactionLayer,
33 HttpObservability.layer,
3734 );
3835
39 const tracerLayer = Layer.unwrap(
36 const layerTracer = Layer.unwrap(
4037 Effect.gen(function* () {
4138 const sink = yield* makeTraceSink({
4239 filePath: config.serverTracePath,
......@@ -76,7 +73,7 @@ export const ObservabilityLive = Layer.unwrap(
7673 BrowserTraceCollector.layer(sink),
7774 );
7875 }),
79 ).pipe(Layer.provide(OtlpExporter.layerFlusher), Layer.provideMerge(serializationLayer));
76 ).pipe(Layer.provide(OtlpExporter.layerFlusher), Layer.provideMerge(layerSerialization));
8077
8178 const metricsLayer =
8279 config.otlpMetricsUrl === undefined
......@@ -86,16 +83,16 @@ export const ObservabilityLive = Layer.unwrap(
8683 exportInterval: `${metrics.exportIntervalMs} millis`,
8784 headers: metrics.headers,
8885 resource,
89 }).pipe(Layer.provide(otlpSerializationLayer(metrics.protocol)));
86 }).pipe(Layer.provide(SharedObservability.layerOtlpSerialization(metrics.protocol)));
9087
9188 // Logged once the server's loggers are installed, so the warnings use them.
92 const otelWarningsLayer = Layer.effectDiscard(
89 const layerOtelWarnings = Layer.effectDiscard(
9390 Effect.forEach(config.otelEnvironment.warnings, (warning) => Effect.logWarning(warning)),
9491 );
9592
96 return otelWarningsLayer.pipe(
93 return layerOtelWarnings.pipe(
9794 Layer.provideMerge(
98 Layer.mergeAll(ServerLoggerLive, traceReferencesLayer, tracerLayer, metricsLayer),
95 Layer.mergeAll(ServerLogger.layer, layerTraceReferences, layerTracer, metricsLayer),
9996 ),
10097 Layer.provide(
10198 OtelEnvironment.layerResourceAttributes(config.otelEnvironment.resourceAttributes),
apps/server/src/orchestration-v2/AcpRegistryOrchestratorV2.live.test.ts+32-32
......@@ -23,12 +23,12 @@ import * as CheckpointStore from "../checkpointing/CheckpointStore.ts";
2323import * as BackgroundPolicy from "../background/BackgroundPolicy.ts";
2424import * as HostPowerMonitor from "../background/HostPowerMonitor.ts";
2525import * as ServerConfig from "../config.ts";
26import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts";
26import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts";
2727import * as AntigravityInstallation from "../provider/AntigravityInstallation.ts";
2828import * as CodexInstallation from "../provider/CodexInstallation.ts";
2929import * as ServerEnvironment from "../environment/ServerEnvironment.ts";
3030import * as ModelManifest from "../provider/ModelManifest.ts";
31import { ProviderInstanceRegistryHydrationLive } from "../provider/Layers/ProviderInstanceRegistryHydration.ts";
31import * as ProviderInstanceRegistryHydration from "../provider/Layers/ProviderInstanceRegistryHydration.ts";
3232import * as ProviderEventLoggers from "../provider/Layers/ProviderEventLoggers.ts";
3333import * as OpenCodeRuntime from "../provider/opencodeRuntime.ts";
3434import * as OpenCodeServerLedger from "../provider/OpenCodeServerLedger.ts";
......@@ -36,8 +36,8 @@ import * as ServerSettings from "../serverSettings.ts";
3636import * as VcsDriverRegistry from "../vcs/VcsDriverRegistry.ts";
3737import * as VcsProcess from "../vcs/VcsProcess.ts";
3838import * as Orchestrator from "./Orchestrator.ts";
39import { worktreeRepairDependenciesTestLayer } from "./ProviderTurnStartService.testkit.ts";
40import { OrchestrationV2LayerLive } from "./runtimeLayer.ts";
39import * as ProviderTurnStartServiceTestkit from "./ProviderTurnStartService.testkit.ts";
40import * as RuntimeLayer from "./runtimeLayer.ts";
4141import * as McpSessionRegistryTestkit from "../mcp/McpSessionRegistry.testkit.ts";
4242
4343// The Antigravity switch is a durable, named conformance fixture for Google's
......@@ -46,7 +46,7 @@ import * as McpSessionRegistryTestkit from "../mcp/McpSessionRegistry.testkit.ts
4646//
4747// T3_ACP_ANTIGRAVITY_LIVE=1 ../../node_modules/.bin/vp test run \
4848// src/orchestration-v2/AcpRegistryOrchestratorV2.live.test.ts
49const PlatformTestLayer = Layer.merge(
49const layerPlatformTest = Layer.merge(
5050 NodeServices.layer,
5151 Layer.mock(SourceControlProviderRegistry.SourceControlProviderRegistry)({
5252 resolveLink: () => Effect.die("unused title link"),
......@@ -64,19 +64,19 @@ const liveModelSelection = {
6464 model: "default",
6565} satisfies ModelSelection;
6666
67const serverConfigLayer = ServerConfig.layerTest(process.cwd(), {
67const layerServerConfig = ServerConfig.layerTest(process.cwd(), {
6868 prefix: "t3-acp-registry-v2-live-",
6969});
7070
71const vcsDriverRegistryLayer = VcsDriverRegistry.layer.pipe(
71const layerVcsDriverRegistry = VcsDriverRegistry.layer.pipe(
7272 Layer.provide(VcsProcess.layer),
73 Layer.provide(serverConfigLayer),
74 Layer.provide(PlatformTestLayer),
73 Layer.provide(layerServerConfig),
74 Layer.provide(layerPlatformTest),
7575);
7676
77const checkpointStoreLayer = CheckpointStore.layer.pipe(Layer.provide(vcsDriverRegistryLayer));
77const layerCheckpointStore = CheckpointStore.layer.pipe(Layer.provide(layerVcsDriverRegistry));
7878
79const serverSettingsLayer = ServerSettings.layerTest({
79const layerServerSettings = ServerSettings.layerTest({
8080 providerInstances: {
8181 [liveInstanceId]: {
8282 driver: ProviderDriverKind.make("acpRegistry"),
......@@ -89,24 +89,24 @@ const serverSettingsLayer = ServerSettings.layerTest({
8989 },
9090 },
9191});
92const backgroundPolicyLayer = BackgroundPolicy.layer.pipe(
92const layerBackgroundPolicy = BackgroundPolicy.layer.pipe(
9393 Layer.provide(Layer.effect(HostPowerMonitor.HostPowerMonitor, HostPowerMonitor.make())),
94 Layer.provide(serverSettingsLayer),
94 Layer.provide(layerServerSettings),
9595);
96const providerInstanceRegistryLayer = ProviderInstanceRegistryHydrationLive.pipe(
96const layerProviderInstanceRegistry = ProviderInstanceRegistryHydration.layer.pipe(
9797 Layer.provide(
9898 Layer.mergeAll(
99 serverConfigLayer.pipe(Layer.provide(PlatformTestLayer)),
100 serverSettingsLayer,
99 layerServerConfig.pipe(Layer.provide(layerPlatformTest)),
100 layerServerSettings,
101101 ServerSecretStore.layer.pipe(
102 Layer.provide(serverConfigLayer),
103 Layer.provide(PlatformTestLayer),
102 Layer.provide(layerServerConfig),
103 Layer.provide(layerPlatformTest),
104104 ),
105105 NodeServices.layer,
106106 FetchHttpClient.layer,
107 OpenCodeRuntime.OpenCodeRuntimeLive.pipe(
107 OpenCodeRuntime.layer.pipe(
108108 Layer.provide(OpenCodeServerLedger.layerTest),
109 Layer.provide(PlatformTestLayer),
109 Layer.provide(layerPlatformTest),
110110 ),
111111 Layer.succeed(
112112 ProviderEventLoggers.ProviderEventLoggers,
......@@ -114,9 +114,9 @@ const providerInstanceRegistryLayer = ProviderInstanceRegistryHydrationLive.pipe
114114 ),
115115 ModelManifest.layerTest,
116116 AntigravityInstallation.AntigravityInstallation.layer.pipe(
117 Layer.provide(serverConfigLayer.pipe(Layer.provide(PlatformTestLayer))),
117 Layer.provide(layerServerConfig.pipe(Layer.provide(layerPlatformTest))),
118118 Layer.provide(FetchHttpClient.layer),
119 Layer.provide(PlatformTestLayer),
119 Layer.provide(layerPlatformTest),
120120 ),
121121 // The Codex driver now resolves managed ChatGPT installs; these runs never launch Codex.
122122 Layer.mock(CodexInstallation.CodexInstallation)({
......@@ -131,17 +131,17 @@ const providerInstanceRegistryLayer = ProviderInstanceRegistryHydrationLive.pipe
131131 ),
132132);
133133
134const liveLayer = OrchestrationV2LayerLive.pipe(
134const layerLive = RuntimeLayer.layer.pipe(
135135 Layer.provide(McpSessionRegistryTestkit.layer),
136 Layer.provide(SqlitePersistenceMemory),
137 Layer.provide(checkpointStoreLayer),
138 Layer.provide(serverConfigLayer),
139 Layer.provide(serverSettingsLayer),
140 Layer.provide(providerInstanceRegistryLayer),
136 Layer.provide(SqlitePersistence.layerMemory),
137 Layer.provide(layerCheckpointStore),
138 Layer.provide(layerServerConfig),
139 Layer.provide(layerServerSettings),
140 Layer.provide(layerProviderInstanceRegistry),
141141 Layer.provide(ResetCreditCoordinator.layer),
142 Layer.provide(backgroundPolicyLayer),
143 Layer.provide(worktreeRepairDependenciesTestLayer),
144 Layer.provide(PlatformTestLayer),
142 Layer.provide(layerBackgroundPolicy),
143 Layer.provide(ProviderTurnStartServiceTestkit.layer),
144 Layer.provide(layerPlatformTest),
145145);
146146
147147const waitForIdle = Effect.fn("AcpRegistryOrchestratorV2Live.waitForIdle")(function* (
......@@ -248,7 +248,7 @@ describe.runIf(runAntigravityFixture || process.env.T3_ACP_REGISTRY_LIVE_ORCHEST
248248 finalProjection.providerTurns.map((turn) => turn.status),
249249 ["completed", "completed"],
250250 );
251 }).pipe(Effect.provide(liveLayer), Effect.scoped),
251 }).pipe(Effect.provide(layerLive), Effect.scoped),
252252 480_000,
253253 );
254254 },
apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.test.ts+90-90
......@@ -100,11 +100,11 @@ import {
100100
101101const DEFAULT_GROK_SETTINGS = Schema.decodeSync(GrokSettings)({});
102102
103const serverConfigLayer = ServerConfig.layerTest(process.cwd(), {
103const layerServerConfig = ServerConfig.layerTest(process.cwd(), {
104104 prefix: "t3-acp-v2-adapter-",
105105}).pipe(Layer.provide(NodeServices.layer));
106106
107const testLayer = Layer.mergeAll(NodeServices.layer, IdAllocator.layer, serverConfigLayer);
107const layerTest = Layer.mergeAll(NodeServices.layer, IdAllocator.layer, layerServerConfig);
108108const ACP_TEST_DRIVER = ProviderDriverKind.make("acp-test");
109109const decodeUnknownJson = Schema.decodeUnknownOption(Schema.fromJsonString(Schema.Unknown));
110110
......@@ -692,7 +692,7 @@ describe("AcpAdapterV2", () => {
692692 assert.equal(retries.at(-1)?.status, "completed");
693693 assert.equal(retries.at(-1)?.title, "Provider recovered");
694694 }
695 }).pipe(Effect.provide(testLayer), Effect.scoped),
695 }).pipe(Effect.provide(layerTest), Effect.scoped),
696696 );
697697
698698 it("preserves legacy ids and scopes v2 ids by provider instance", () => {
......@@ -777,7 +777,7 @@ describe("AcpAdapterV2", () => {
777777 assert.deepEqual(mcpServer.args, ["acp-mcp-bridge"]);
778778 assert.equal(runtimeInput?.processEnvironment?.T3_ACP_MCP_NODE, process.execPath);
779779 assert.equal(runtimeInput?.processEnvironment?.T3_ACP_MCP_ENTRYPOINT, undefined);
780 }).pipe(Effect.provide(testLayer), Effect.scoped),
780 }).pipe(Effect.provide(layerTest), Effect.scoped),
781781 );
782782
783783 it.live("refreshes ACP prompt instructions when the interaction mode changes", () =>
......@@ -904,7 +904,7 @@ describe("AcpAdapterV2", () => {
904904 "session/set_config_option",
905905 "Build should restore the native mode that T3 temporarily replaced for Plan",
906906 );
907 }).pipe(Effect.provide(testLayer), Effect.scoped),
907 }).pipe(Effect.provide(layerTest), Effect.scoped),
908908 );
909909
910910 it.effect("starts a new replay message after ACP v2 plan boundaries", () =>
......@@ -995,7 +995,7 @@ describe("AcpAdapterV2", () => {
995995 snapshot.messages.map((message) => message.text),
996996 ["before plan", "after plan"],
997997 );
998 }).pipe(Effect.provide(testLayer), Effect.scoped),
998 }).pipe(Effect.provide(layerTest), Effect.scoped),
999999 );
10001000
10011001 it.effect("keeps Devin parent paragraphs intact while projecting native child work", () =>
......@@ -1303,7 +1303,7 @@ describe("AcpAdapterV2", () => {
13031303 );
13041304 assert.equal(parentTools.length, 2);
13051305 assert.equal(parentTools[0]?.title, "Parent tool finished");
1306 }).pipe(Effect.provide(testLayer), Effect.scoped),
1306 }).pipe(Effect.provide(layerTest), Effect.scoped),
13071307 );
13081308
13091309 it.effect("projects ACP v2 fidelity updates into first-class orchestration items", () =>
......@@ -1495,7 +1495,7 @@ describe("AcpAdapterV2", () => {
14951495 afterOmittedPatch.messages.some((message) => message.text === "late authoritative text"),
14961496 "omitted late message content must preserve the authoritative text",
14971497 );
1498 }).pipe(Effect.provide(testLayer), Effect.scoped),
1498 }).pipe(Effect.provide(layerTest), Effect.scoped),
14991499 );
15001500
15011501 it.live(
......@@ -1582,7 +1582,7 @@ describe("AcpAdapterV2", () => {
15821582 nativeTurnId: "persisted-session:turn:1",
15831583 }),
15841584 );
1585 }).pipe(Effect.provide(testLayer), Effect.scoped),
1585 }).pipe(Effect.provide(layerTest), Effect.scoped),
15861586 );
15871587
15881588 it.live("preserves new-session fallback when an eager ACP session load is stale", () =>
......@@ -1689,7 +1689,7 @@ describe("AcpAdapterV2", () => {
16891689 ),
16901690 }),
16911691 );
1692 }).pipe(Effect.provide(testLayer), Effect.scoped),
1692 }).pipe(Effect.provide(layerTest), Effect.scoped),
16931693 );
16941694
16951695 it.live("cleans detached fixtures when an assertion aborts the test scope", () =>
......@@ -1728,7 +1728,7 @@ describe("AcpAdapterV2", () => {
17281728 Option.isSome(yield* waitForProcessesToExit(published)),
17291729 "detached cleanup finalizer must reap the Bash and sleep fixture",
17301730 );
1731 }).pipe(Effect.provide(testLayer)),
1731 }).pipe(Effect.provide(layerTest)),
17321732 );
17331733
17341734 it.live("replaces an unexpectedly terminated ACP runtime before the next turn", () =>
......@@ -1810,7 +1810,7 @@ describe("AcpAdapterV2", () => {
18101810 );
18111811 assert.equal(runtimeOrdinalSeen, 2);
18121812 assert.lengthOf(runtimeInputs, 2);
1813 }).pipe(Effect.provide(testLayer), Effect.scoped),
1813 }).pipe(Effect.provide(layerTest), Effect.scoped),
18141814 );
18151815
18161816 it.live("reaps detached native work when the provider exits before explicit teardown", () =>
......@@ -1895,7 +1895,7 @@ describe("AcpAdapterV2", () => {
18951895 Option.isSome(yield* waitForProcessesToExit(pids)),
18961896 "provider termination must reap the detached launcher, Bash, and sleep processes",
18971897 );
1898 }).pipe(Effect.provide(testLayer), Effect.scoped),
1898 }).pipe(Effect.provide(layerTest), Effect.scoped),
18991899 );
19001900
19011901 it.live("surfaces reduced guarantee when delegated cgroup containment is unavailable", () =>
......@@ -1952,7 +1952,7 @@ describe("AcpAdapterV2", () => {
19521952 });
19531953 yield* runtime.ensureThread({ threadId, modelSelection, runtimePolicy });
19541954 assert.equal(containment, "process-ledger-reduced-guarantee");
1955 }).pipe(Effect.provide(testLayer), Effect.scoped),
1955 }).pipe(Effect.provide(layerTest), Effect.scoped),
19561956 );
19571957
19581958 it.live("cleans a cgroup lease when the pre-exec join wrapper fails", () =>
......@@ -2030,7 +2030,7 @@ describe("AcpAdapterV2", () => {
20302030 assert.equal(createCalls, 1);
20312031 assert.isAtLeast(killCalls, 1);
20322032 assert.isAtLeast(removeCalls, 1);
2033 }).pipe(Effect.provide(testLayer), Effect.scoped),
2033 }).pipe(Effect.provide(layerTest), Effect.scoped),
20342034 );
20352035
20362036 it.effect("negotiates and executes optional native session forks through the ACP runtime", () =>
......@@ -2240,7 +2240,7 @@ describe("AcpAdapterV2", () => {
22402240 { requestId: "test-terminal-output", method: "terminal/output" },
22412241 );
22422242 assert.equal(terminalOutput.output, "Bearer target-thread-token");
2243 }).pipe(Effect.provide(testLayer), Effect.scoped),
2243 }).pipe(Effect.provide(layerTest), Effect.scoped),
22442244 );
22452245
22462246 it.effect(
......@@ -2339,7 +2339,7 @@ describe("AcpAdapterV2", () => {
23392339 { method: "fs/read_text_file", errorCode: -32601 },
23402340 ]);
23412341 assert.isFalse(yield* fileSystem.exists(probePath));
2342 }).pipe(Effect.provide(testLayer), Effect.scoped),
2342 }).pipe(Effect.provide(layerTest), Effect.scoped),
23432343 );
23442344
23452345 it.effect("does not turn an unknown permission approval into an execute grant", () =>
......@@ -2476,7 +2476,7 @@ describe("AcpAdapterV2", () => {
24762476 providerTurnId: pending.runtimeRequest.providerTurnId,
24772477 });
24782478 yield* Fiber.join(turnFiber);
2479 }).pipe(Effect.provide(testLayer), Effect.scoped),
2479 }).pipe(Effect.provide(layerTest), Effect.scoped),
24802480 );
24812481
24822482 it.effect("fails missing native ACP session ids through the typed start-turn error channel", () =>
......@@ -2538,7 +2538,7 @@ describe("AcpAdapterV2", () => {
25382538 assert.equal(error._tag, "ProviderAdapterTurnStartError");
25392539 assert.instanceOf(error.cause, ProviderAdapterProtocolError);
25402540 assert.include(String(error.cause), "missing its ACP session id");
2541 }).pipe(Effect.provide(testLayer), Effect.scoped),
2541 }).pipe(Effect.provide(layerTest), Effect.scoped),
25422542 );
25432543
25442544 it.effect("replaces the ACP session and clears conversation state on rollback", () =>
......@@ -2705,7 +2705,7 @@ describe("AcpAdapterV2", () => {
27052705 }
27062706 }
27072707 assert.equal(secondStatus, "completed");
2708 }).pipe(Effect.provide(testLayer), Effect.scoped),
2708 }).pipe(Effect.provide(layerTest), Effect.scoped),
27092709 );
27102710
27112711 it.effect("quarantines callbacks from a failed rollback replacement before retrying", () =>
......@@ -2866,7 +2866,7 @@ describe("AcpAdapterV2", () => {
28662866 status: "running",
28672867 });
28682868 assert.isFalse(yield* hasPendingBackgroundWork);
2869 }).pipe(Effect.provide(testLayer), Effect.scoped),
2869 }).pipe(Effect.provide(layerTest), Effect.scoped),
28702870 );
28712871
28722872 it.effect("keeps the original ACP session usable when a staged replacement terminates", () =>
......@@ -2982,7 +2982,7 @@ describe("AcpAdapterV2", () => {
29822982 break;
29832983 }
29842984 }
2985 }).pipe(Effect.provide(testLayer), Effect.scoped),
2985 }).pipe(Effect.provide(layerTest), Effect.scoped),
29862986 );
29872987
29882988 it.effect("closes an idle ACP session exactly once through the transition permit", () =>
......@@ -3031,7 +3031,7 @@ describe("AcpAdapterV2", () => {
30313031 yield* Scope.close(sessionScope, Exit.void);
30323032 const finalizerMethods = yield* pollProtocolMethods(protocolEvents);
30333033 assert.equal(finalizerMethods.filter((method) => method === "session/close").length, 1);
3034 }).pipe(Effect.provide(testLayer)),
3034 }).pipe(Effect.provide(layerTest)),
30353035 );
30363036
30373037 it.effect.each(["grok-build", "composer-2"])(
......@@ -3088,7 +3088,7 @@ describe("AcpAdapterV2", () => {
30883088 ).length,
30893089 model === "grok-build" ? 0 : 1,
30903090 );
3091 }).pipe(Effect.provide(testLayer), Effect.scoped),
3091 }).pipe(Effect.provide(layerTest), Effect.scoped),
30923092 );
30933093
30943094 it.live("Grok reapplies an explicit return to the session's setup-time model", () =>
......@@ -3169,7 +3169,7 @@ describe("AcpAdapterV2", () => {
31693169 ).length,
31703170 3,
31713171 );
3172 }).pipe(Effect.provide(testLayer), Effect.scoped),
3172 }).pipe(Effect.provide(layerTest), Effect.scoped),
31733173 );
31743174
31753175 it.effect("skips requested options that the active ACP session does not expose", () =>
......@@ -3218,7 +3218,7 @@ describe("AcpAdapterV2", () => {
32183218 });
32193219
32203220 assert.equal(runtime.providerSession.status, "ready");
3221 }).pipe(Effect.provide(testLayer), Effect.scoped),
3221 }).pipe(Effect.provide(layerTest), Effect.scoped),
32223222 );
32233223
32243224 it.effect("reconfigures a loaded ACP session from its own active setup metadata", () =>
......@@ -3329,7 +3329,7 @@ describe("AcpAdapterV2", () => {
33293329 rawProtocolRequestParam(event, "configId") === "model",
33303330 );
33313331 assert.lengthOf(modelConfigurationRequests, 2);
3332 }).pipe(Effect.provide(testLayer), Effect.scoped),
3332 }).pipe(Effect.provide(layerTest), Effect.scoped),
33333333 );
33343334
33353335 it.live("terminalizes an empty successful foreground Bash tool when the turn completes", () =>
......@@ -3476,7 +3476,7 @@ describe("AcpAdapterV2", () => {
34763476 Stream.runHead,
34773477 );
34783478 assert.isTrue(Option.isSome(loadAfterRestart));
3479 }).pipe(Effect.provide(testLayer), Effect.scoped),
3479 }).pipe(Effect.provide(layerTest), Effect.scoped),
34803480 );
34813481
34823482 it.effect("drains native ACP cancellation before admitting the next prompt", () =>
......@@ -3590,7 +3590,7 @@ describe("AcpAdapterV2", () => {
35903590 ),
35913591 );
35923592 assert.equal(nextTerminal.type === "turn.terminal" && nextTerminal.status, "completed");
3593 }).pipe(Effect.provide(testLayer), Effect.scoped),
3593 }).pipe(Effect.provide(layerTest), Effect.scoped),
35943594 );
35953595
35963596 it.effect("cancels pending permission requests while interrupting an ACP turn", () =>
......@@ -3691,7 +3691,7 @@ describe("AcpAdapterV2", () => {
36913691 ),
36923692 );
36933693 assert.equal(terminal.type, "turn.terminal");
3694 }).pipe(Effect.provide(testLayer), Effect.scoped),
3694 }).pipe(Effect.provide(layerTest), Effect.scoped),
36953695 );
36963696
36973697 it.live("keeps hard teardown excluded until a permission response is enqueued", () =>
......@@ -3790,7 +3790,7 @@ describe("AcpAdapterV2", () => {
37903790 yield* Deferred.succeed(releaseResponseAcknowledgement, undefined);
37913791 yield* Fiber.join(responseFiber);
37923792 yield* Fiber.join(interruptFiber);
3793 }).pipe(Effect.provide(testLayer), Effect.scoped),
3793 }).pipe(Effect.provide(layerTest), Effect.scoped),
37943794 );
37953795
37963796 it.live("carries elicitation request identity through the completed stdout write", () =>
......@@ -3879,7 +3879,7 @@ describe("AcpAdapterV2", () => {
38793879 assert.isUndefined(responseFiber.pollUnsafe());
38803880 yield* Deferred.succeed(releaseResponseAcknowledgement, undefined);
38813881 yield* Fiber.join(responseFiber);
3882 }).pipe(Effect.provide(testLayer), Effect.scoped),
3882 }).pipe(Effect.provide(layerTest), Effect.scoped),
38833883 );
38843884
38853885 it.live("auto-approves tagged MCP elicitations under full-access policy", () =>
......@@ -3947,7 +3947,7 @@ describe("AcpAdapterV2", () => {
39473947
39483948 assert.isFalse(events.some((event) => event.type === "runtime_request.updated"));
39493949 assert.isTrue(events.some((event) => event.type === "turn.terminal"));
3950 }).pipe(Effect.provide(testLayer), Effect.scoped),
3950 }).pipe(Effect.provide(layerTest), Effect.scoped),
39513951 );
39523952
39533953 it.live("fails a held native response acknowledgement before normal session close", () =>
......@@ -4055,7 +4055,7 @@ describe("AcpAdapterV2", () => {
40554055 assert.include(responseLifecycle, "failed");
40564056 yield* Deferred.succeed(releaseResponseAcknowledgement, undefined);
40574057 yield* Fiber.join(closeFiber);
4058 }).pipe(Effect.provide(testLayer), Effect.scoped),
4058 }).pipe(Effect.provide(layerTest), Effect.scoped),
40594059 );
40604060
40614061 it.live("rejects delayed native response registration when normal close wins the permit", () =>
......@@ -4169,7 +4169,7 @@ describe("AcpAdapterV2", () => {
41694169 assert.equal(responseLifecycle.filter((event) => event === "admission_rejected").length, 1);
41704170 yield* Deferred.succeed(releaseTransportClose, undefined);
41714171 yield* Fiber.join(closeFiber);
4172 }).pipe(Effect.provide(testLayer), Effect.scoped),
4172 }).pipe(Effect.provide(layerTest), Effect.scoped),
41734173 );
41744174
41754175 it.live("bounds a missing pending permission response acknowledgement", () =>
......@@ -4310,7 +4310,7 @@ describe("AcpAdapterV2", () => {
43104310 assert.include(responseLifecycle, "late_noop");
43114311 yield* Scope.close(sessionScope, Exit.void);
43124312 assert.notInclude(yield* pollProtocolMethods(protocolEvents), "session/close");
4313 }).pipe(Effect.provide(testLayer), Effect.scoped),
4313 }).pipe(Effect.provide(layerTest), Effect.scoped),
43144314 );
43154315
43164316 it.live("defers caller cancellation until a pending response acknowledgement is bounded", () =>
......@@ -4426,7 +4426,7 @@ describe("AcpAdapterV2", () => {
44264426 yield* Fiber.join(cancellationFiber);
44274427 yield* Fiber.join(interruptFiber);
44284428 assert.isTrue(yield* Deferred.isDone(releaseNativeHook));
4429 }).pipe(Effect.provide(testLayer), Effect.scoped),
4429 }).pipe(Effect.provide(layerTest), Effect.scoped),
44304430 );
44314431
44324432 it.live("waits for immediate allow and deny permission responses before hard teardown", () =>
......@@ -4531,7 +4531,7 @@ describe("AcpAdapterV2", () => {
45314531 yield* Fiber.interrupt(turnFiber).pipe(Effect.forkDetach);
45324532 }).pipe(Effect.scoped);
45334533 }
4534 }).pipe(Effect.provide(testLayer), Effect.scoped),
4534 }).pipe(Effect.provide(layerTest), Effect.scoped),
45354535 );
45364536
45374537 it.live("waits for immediate URL elicitation responses before hard teardown", () =>
......@@ -4624,7 +4624,7 @@ describe("AcpAdapterV2", () => {
46244624 yield* Deferred.succeed(releaseResponseAcknowledgement, undefined);
46254625 yield* Fiber.join(interruptFiber);
46264626 yield* Fiber.interrupt(turnFiber).pipe(Effect.forkDetach);
4627 }).pipe(Effect.provide(testLayer), Effect.scoped),
4627 }).pipe(Effect.provide(layerTest), Effect.scoped),
46284628 );
46294629
46304630 it.live("bounds a missing immediate response acknowledgement before hard teardown", () =>
......@@ -4717,7 +4717,7 @@ describe("AcpAdapterV2", () => {
47174717 });
47184718 assert.isAtLeast((yield* Clock.currentTimeMillis) - startedAt, 1_500);
47194719 yield* Fiber.interrupt(turnFiber).pipe(Effect.forkDetach);
4720 }).pipe(Effect.provide(testLayer), Effect.scoped),
4720 }).pipe(Effect.provide(layerTest), Effect.scoped),
47214721 );
47224722
47234723 it.live("rejects an elicitation response when hard teardown wins admission", () =>
......@@ -4830,7 +4830,7 @@ describe("AcpAdapterV2", () => {
48304830 assert.fail("teardown winning admission must reject the elicitation response");
48314831 }
48324832 assert.include(Cause.pretty(responseExit.cause), "No pending ACP runtime request");
4833 }).pipe(Effect.provide(testLayer), Effect.scoped),
4833 }).pipe(Effect.provide(layerTest), Effect.scoped),
48344834 );
48354835
48364836 it.effect("releases an ACP turn when cancellation times out", () =>
......@@ -4933,7 +4933,7 @@ describe("AcpAdapterV2", () => {
49334933 ),
49344934 Stream.runHead,
49354935 );
4936 }).pipe(Effect.provide(testLayer), Effect.scoped),
4936 }).pipe(Effect.provide(layerTest), Effect.scoped),
49374937 );
49384938
49394939 it.live("treats a second hard Stop as success when the turn is already gone", () =>
......@@ -5031,7 +5031,7 @@ describe("AcpAdapterV2", () => {
50315031 }
50325032 }
50335033 assert.equal(terminal, "interrupted");
5034 }).pipe(Effect.provide(testLayer), Effect.scoped),
5034 }).pipe(Effect.provide(layerTest), Effect.scoped),
50355035 );
50365036
50375037 it.effect("finalizes a settled turn held open for background work when interrupted", () =>
......@@ -5125,7 +5125,7 @@ describe("AcpAdapterV2", () => {
51255125 }
51265126 }
51275127 assert.equal(terminalStatus, "interrupted");
5128 }).pipe(Effect.provide(testLayer), Effect.scoped),
5128 }).pipe(Effect.provide(layerTest), Effect.scoped),
51295129 );
51305130
51315131 it.effect(
......@@ -5273,7 +5273,7 @@ describe("AcpAdapterV2", () => {
52735273 }
52745274 assert.equal(carriedItemStatus, "completed");
52755275 assert.equal(secondTerminalStatus, "completed");
5276 }).pipe(Effect.provide(testLayer), Effect.scoped),
5276 }).pipe(Effect.provide(layerTest), Effect.scoped),
52775277 );
52785278
52795279 it.effect(
......@@ -5422,7 +5422,7 @@ describe("AcpAdapterV2", () => {
54225422 yield* hasPendingBackgroundWork,
54235423 "hasPendingBackgroundWork must clear after carryover subagent terminals",
54245424 );
5425 }).pipe(Effect.provide(testLayer), Effect.scoped),
5425 }).pipe(Effect.provide(layerTest), Effect.scoped),
54265426 );
54275427
54285428 it.effect("handles a child terminal after carryover rehydrate", () =>
......@@ -5657,7 +5657,7 @@ describe("AcpAdapterV2", () => {
56575657
56585658 yield* Deferred.succeed(releaseSecondPromptCompletion, undefined);
56595659 yield* Fiber.join(secondTurnFiber);
5660 }).pipe(Effect.provide(testLayer), Effect.scoped),
5660 }).pipe(Effect.provide(layerTest), Effect.scoped),
56615661 );
56625662
56635663 it.effect(
......@@ -5880,7 +5880,7 @@ describe("AcpAdapterV2", () => {
58805880 "finalize-window terminal must clear the carryover pin",
58815881 );
58825882 }).pipe(Effect.provideService(Clock.Clock, blockingClock));
5883 }).pipe(Effect.provide(testLayer), Effect.scoped),
5883 }).pipe(Effect.provide(layerTest), Effect.scoped),
58845884 );
58855885
58865886 it.effect(
......@@ -6101,7 +6101,7 @@ describe("AcpAdapterV2", () => {
61016101 assert.equal(attachTerminal, "completed");
61026102 assert.equal(completedAfterAttach, 1);
61036103 assert.isFalse(yield* hasPendingBackgroundWork);
6104 }).pipe(Effect.provide(testLayer), Effect.scoped),
6104 }).pipe(Effect.provide(layerTest), Effect.scoped),
61056105 );
61066106
61076107 it.effect("finishes a settled root's carryover subagent from its structured end", () =>
......@@ -6249,7 +6249,7 @@ describe("AcpAdapterV2", () => {
62496249 yield* hasPendingBackgroundWork,
62506250 "a finished carryover subagent stops pinning",
62516251 );
6252 }).pipe(Effect.provide(testLayer), Effect.scoped),
6252 }).pipe(Effect.provide(layerTest), Effect.scoped),
62536253 );
62546254
62556255 it.effect("projects completed-root carryover eagerly and drain cannot resurrect it", () =>
......@@ -6499,7 +6499,7 @@ describe("AcpAdapterV2", () => {
64996499 "buffered spawn ACK must not create a continuation-owned turn item",
65006500 );
65016501 assert.isFalse(yield* hasPendingBackgroundWork);
6502 }).pipe(Effect.provide(testLayer), Effect.scoped),
6502 }).pipe(Effect.provide(layerTest), Effect.scoped),
65036503 );
65046504
65056505 it.effect(
......@@ -6787,7 +6787,7 @@ describe("AcpAdapterV2", () => {
67876787 "queued continuation must drain the wake content after the user run",
67886788 );
67896789 assert.isFalse(yield* hasPendingBackgroundWork);
6790 }).pipe(Effect.provide(testLayer), Effect.scoped),
6790 }).pipe(Effect.provide(layerTest), Effect.scoped),
67916791 );
67926792
67936793 it.effect(
......@@ -7030,7 +7030,7 @@ describe("AcpAdapterV2", () => {
70307030 }
70317031 assert.equal(completedSubagentTurnItems, 1);
70327032 assert.isFalse(yield* hasPendingBackgroundWork);
7033 }).pipe(Effect.provide(testLayer), Effect.scoped),
7033 }).pipe(Effect.provide(layerTest), Effect.scoped),
70347034 );
70357035
70367036 it.effect("projects an interrupted root-session end notice at the next attach", () =>
......@@ -7257,7 +7257,7 @@ describe("AcpAdapterV2", () => {
72577257 }
72587258 assert.equal(completedSubagentTurnItems, 1);
72597259 assert.isFalse(yield* hasPendingBackgroundWork);
7260 }).pipe(Effect.provide(testLayer), Effect.scoped),
7260 }).pipe(Effect.provide(layerTest), Effect.scoped),
72617261 );
72627262
72637263 it.effect(
......@@ -7528,7 +7528,7 @@ describe("AcpAdapterV2", () => {
75287528 yield* hasPendingBackgroundWork,
75297529 "pin must clear after the continuation drains, not when the child session completed",
75307530 );
7531 }).pipe(Effect.provide(testLayer), Effect.scoped),
7531 }).pipe(Effect.provide(layerTest), Effect.scoped),
75327532 );
75337533
75347534 it.effect(
......@@ -7770,7 +7770,7 @@ describe("AcpAdapterV2", () => {
77707770 yield* hasPendingBackgroundWork,
77717771 "hasPendingBackgroundWork must end false after the continuation drains",
77727772 );
7773 }).pipe(Effect.provide(testLayer), Effect.scoped),
7773 }).pipe(Effect.provide(layerTest), Effect.scoped),
77747774 );
77757775
77767776 it.effect(
......@@ -7948,7 +7948,7 @@ describe("AcpAdapterV2", () => {
79487948 1,
79497949 "settled soft steer must not respawn the ACP runtime process",
79507950 );
7951 }).pipe(Effect.provide(testLayer), Effect.scoped),
7951 }).pipe(Effect.provide(layerTest), Effect.scoped),
79527952 );
79537953
79547954 it.live("preserveRuntimeOnSettledInterrupt does not soften a mid-prompt steering interrupt", () =>
......@@ -8036,7 +8036,7 @@ describe("AcpAdapterV2", () => {
80368036 assert.fail("mid-prompt steering interrupt must still take the hard teardown path");
80378037 }
80388038 assert.include(Cause.pretty(interruptExit.cause), "session is poisoned");
8039 }).pipe(Effect.provide(testLayer), Effect.scoped),
8039 }).pipe(Effect.provide(layerTest), Effect.scoped),
80408040 );
80418041
80428042 it.live(
......@@ -8232,7 +8232,7 @@ describe("AcpAdapterV2", () => {
82328232 0,
82338233 "a cancel-backgrounded task completion must not wake a synthetic continuation run",
82348234 );
8235 }).pipe(Effect.provide(testLayer), Effect.scoped),
8235 }).pipe(Effect.provide(layerTest), Effect.scoped),
82368236 );
82378237
82388238 it.live("direct Stop quarantine drops late background task mutations from the stopped run", () =>
......@@ -8379,7 +8379,7 @@ describe("AcpAdapterV2", () => {
83798379 yield* hasPendingBackgroundWork,
83808380 "direct Stop quarantine must drop residual background task mutations from the stopped run",
83818381 );
8382 }).pipe(Effect.provide(testLayer), Effect.scoped),
8382 }).pipe(Effect.provide(layerTest), Effect.scoped),
83838383 );
83848384
83858385 it.live("production Grok interrupt flags still hard-kill and respawn on user Stop", () =>
......@@ -8505,7 +8505,7 @@ describe("AcpAdapterV2", () => {
85058505 2,
85068506 "user Stop with production Grok flags must replace the ACP runtime process",
85078507 );
8508 }).pipe(Effect.provide(testLayer), Effect.scoped),
8508 }).pipe(Effect.provide(layerTest), Effect.scoped),
85098509 );
85108510
85118511 // it.live: ownDetachedProcessGroup teardown uses wall-clock sleeps; under
......@@ -8814,7 +8814,7 @@ describe("AcpAdapterV2", () => {
88148814 "Stop quarantine must drop turn-1 subagent carryover on the respawned runtime",
88158815 );
88168816 assert.equal(secondTerminalStatus, "completed");
8817 }).pipe(Effect.provide(testLayer), Effect.scoped),
8817 }).pipe(Effect.provide(layerTest), Effect.scoped),
88188818 );
88198819
88208820 it.live("Direct Stop projects an interrupt-deferred terminal exactly once", () =>
......@@ -9009,7 +9009,7 @@ describe("AcpAdapterV2", () => {
90099009 }
90109010 assert.equal(completedAfterStop, 1);
90119011 assert.isFalse(yield* hasPendingBackgroundWork);
9012 }).pipe(Effect.provide(testLayer), Effect.scoped),
9012 }).pipe(Effect.provide(layerTest), Effect.scoped),
90139013 );
90149014
90159015 it.effect(
......@@ -9179,7 +9179,7 @@ describe("AcpAdapterV2", () => {
91799179 }
91809180 assert.equal(firstTerminalStatus, "interrupted");
91819181 assert.equal(subagentPhase, "spawn");
9182 }).pipe(Effect.provide(testLayer), Effect.scoped),
9182 }).pipe(Effect.provide(layerTest), Effect.scoped),
91839183 );
91849184
91859185 it.effect(
......@@ -9310,7 +9310,7 @@ describe("AcpAdapterV2", () => {
93109310 yield* hasPendingBackgroundWork,
93119311 "wake buffer must not stay non-empty and pin idle release after an already-handled re-report",
93129312 );
9313 }).pipe(Effect.provide(testLayer), Effect.scoped),
9313 }).pipe(Effect.provide(layerTest), Effect.scoped),
93149314 );
93159315
93169316 it.effect(
......@@ -9432,7 +9432,7 @@ describe("AcpAdapterV2", () => {
94329432 yield* first.clearIfCurrent!();
94339433 yield* lateTool("new-result-after-worker-drop");
94349434 assert.lengthOf(continuationRequests, 2);
9435 }).pipe(Effect.provide(testLayer), Effect.scoped),
9435 }).pipe(Effect.provide(layerTest), Effect.scoped),
94369436 );
94379437
94389438 it.effect("keeps a buffered continuation current when a user turn starts before dispatch", () =>
......@@ -9639,7 +9639,7 @@ describe("AcpAdapterV2", () => {
96399639 assert.equal(continuationTerminalStatus, "completed");
96409640 assert.isTrue(bufferedTextSeen, "continuation must drain the wake buffer after the user run");
96419641 assert.isFalse(yield* hasPendingBackgroundWork);
9642 }).pipe(Effect.provide(testLayer), Effect.scoped),
9642 }).pipe(Effect.provide(layerTest), Effect.scoped),
96439643 );
96449644
96459645 it.effect(
......@@ -9805,7 +9805,7 @@ describe("AcpAdapterV2", () => {
98059805 reportSeen,
98069806 "the injected-turn report must project before the turn finalizes",
98079807 );
9808 }).pipe(Effect.provide(testLayer), Effect.scoped),
9808 }).pipe(Effect.provide(layerTest), Effect.scoped),
98099809 );
98109810
98119811 it.effect(
......@@ -10007,7 +10007,7 @@ describe("AcpAdapterV2", () => {
1000710007 0,
1000810008 "settle-without-report with mid-hold ext completion must not open a wake after the report streams",
1000910009 );
10010 }).pipe(Effect.provide(testLayer), Effect.scoped),
10010 }).pipe(Effect.provide(layerTest), Effect.scoped),
1001110011 );
1001210012
1001310013 it.effect(
......@@ -10247,7 +10247,7 @@ describe("AcpAdapterV2", () => {
1024710247 0,
1024810248 "pre-settle arm must be cleared when the injected report streams; no duplicate wake",
1024910249 );
10250 }).pipe(Effect.provide(testLayer), Effect.scoped),
10250 }).pipe(Effect.provide(layerTest), Effect.scoped),
1025110251 );
1025210252
1025310253 it.effect(
......@@ -10466,7 +10466,7 @@ describe("AcpAdapterV2", () => {
1046610466 1,
1046710467 "exactly one continuation when the last running task ends post-finalize with kept midTurn marks",
1046810468 );
10469 }).pipe(Effect.provide(testLayer), Effect.scoped),
10469 }).pipe(Effect.provide(layerTest), Effect.scoped),
1047010470 );
1047110471
1047210472 it.effect(
......@@ -10681,7 +10681,7 @@ describe("AcpAdapterV2", () => {
1068110681 0,
1068210682 "interrupted turns must clear midTurn marks; B ending post-finalize must not offer",
1068310683 );
10684 }).pipe(Effect.provide(testLayer), Effect.scoped),
10684 }).pipe(Effect.provide(layerTest), Effect.scoped),
1068510685 );
1068610686
1068710687 it.effect(
......@@ -11055,7 +11055,7 @@ describe("AcpAdapterV2", () => {
1105511055 yield* hasPendingBackgroundWork,
1105611056 "wake buffer must not retain turn-1 or turn-2 in-turn-handled ack residue",
1105711057 );
11058 }).pipe(Effect.provide(testLayer), Effect.scoped),
11058 }).pipe(Effect.provide(layerTest), Effect.scoped),
1105911059 );
1106011060
1106111061 it.effect("a wake names work that ended while the previous wake was queued", () =>
......@@ -11209,7 +11209,7 @@ describe("AcpAdapterV2", () => {
1120911209 outcome: "completed",
1121011210 summary: 'Command "sleep b" finished',
1121111211 });
11212 }).pipe(Effect.provide(testLayer), Effect.scoped),
11212 }).pipe(Effect.provide(layerTest), Effect.scoped),
1121311213 );
1121411214
1121511215 it.effect("mid-turn completed mutation defers offer until finalize only when unhandled", () =>
......@@ -11538,7 +11538,7 @@ describe("AcpAdapterV2", () => {
1153811538 0,
1153911539 "post-finalize must not offer when the mid-turn completion was handled in-turn",
1154011540 );
11541 }).pipe(Effect.provide(testLayer), Effect.scoped),
11541 }).pipe(Effect.provide(layerTest), Effect.scoped),
1154211542 );
1154311543
1154411544 it.effect(
......@@ -11727,7 +11727,7 @@ describe("AcpAdapterV2", () => {
1172711727 beforeWhitespace.messages,
1172811728 "whitespace-only late chunks must not mutate the loaded history snapshot",
1172911729 );
11730 }).pipe(Effect.provide(testLayer), Effect.scoped),
11730 }).pipe(Effect.provide(layerTest), Effect.scoped),
1173111731 );
1173211732
1173311733 it.effect("empty-drain continuation turn waits the quiet window so late frames can attach", () =>
......@@ -11971,7 +11971,7 @@ describe("AcpAdapterV2", () => {
1197111971 }
1197211972 }
1197311973 assert.equal(continuationTerminal, "completed");
11974 }).pipe(Effect.provide(testLayer), Effect.scoped),
11974 }).pipe(Effect.provide(layerTest), Effect.scoped),
1197511975 );
1197611976
1197711977 it.effect("empty-drain continuation turn finalizes after the quiet window with no frames", () =>
......@@ -12161,7 +12161,7 @@ describe("AcpAdapterV2", () => {
1216112161 }
1216212162 }
1216312163 assert.equal(continuationTerminal, "completed");
12164 }).pipe(Effect.provide(testLayer), Effect.scoped),
12164 }).pipe(Effect.provide(layerTest), Effect.scoped),
1216512165 );
1216612166
1216712167 it.effect("restarts the ACP child process before the next prompt after interrupt", () =>
......@@ -12258,7 +12258,7 @@ describe("AcpAdapterV2", () => {
1225812258 Option.isSome(loadAfterRestart),
1225912259 "post-interrupt startTurn should respawn the runtime and replay session/resume",
1226012260 );
12261 }).pipe(Effect.provide(testLayer), Effect.scoped),
12261 }).pipe(Effect.provide(layerTest), Effect.scoped),
1226212262 );
1226312263
1226412264 it.effect("Windows teardown is one-shot explicitly with independent finalizer cleanup", () =>
......@@ -12304,7 +12304,7 @@ describe("AcpAdapterV2", () => {
1230412304 assert.equal(taskkillCommands.length, 1);
1230512305 yield* Scope.close(runtimeScope, Exit.void);
1230612306 assert.equal(taskkillCommands.length, 1);
12307 }).pipe(Effect.provide(testLayer)),
12307 }).pipe(Effect.provide(layerTest)),
1230812308 );
1230912309
1231012310 it("accepts only taskkill exit code zero as successful tree termination", () => {
......@@ -12407,7 +12407,7 @@ describe("AcpAdapterV2", () => {
1240712407 );
1240812408 const error = yield* Effect.flip(runtime.terminateProcessGroup!);
1240912409 assert.equal(error.detail, "mock taskkill failure");
12410 }).pipe(Effect.provide(testLayer), Effect.scoped),
12410 }).pipe(Effect.provide(layerTest), Effect.scoped),
1241112411 );
1241212412
1241312413 it.live("poisons the session when hard teardown defects and blocks replacement work", () =>
......@@ -12625,7 +12625,7 @@ describe("AcpAdapterV2", () => {
1262512625 assert.isFalse(processExists(commandSleepPid!));
1262612626 const finalizerMethods = yield* pollProtocolMethods(protocolEvents);
1262712627 assert.notInclude(finalizerMethods, "session/close");
12628 }).pipe(Effect.provide(testLayer), Effect.scoped),
12628 }).pipe(Effect.provide(layerTest), Effect.scoped),
1262912629 );
1263012630
1263112631 it.live("durably poisons start and resume when required hard teardown is unavailable", () =>
......@@ -12714,7 +12714,7 @@ describe("AcpAdapterV2", () => {
1271412714 .pipe(Effect.exit);
1271512715 if (Exit.isSuccess(resumeExit)) assert.fail("poisoned session must reject resumeThread");
1271612716 assert.include(Cause.pretty(resumeExit.cause), "session is poisoned");
12717 }).pipe(Effect.provide(testLayer), Effect.scoped),
12717 }).pipe(Effect.provide(layerTest), Effect.scoped),
1271812718 );
1271912719
1272012720 it.live("holds concurrent startTurn behind successful hard teardown and reloads once", () =>
......@@ -12827,7 +12827,7 @@ describe("AcpAdapterV2", () => {
1282712827 Stream.runHead,
1282812828 );
1282912829 }
12830 }).pipe(Effect.provide(testLayer), Effect.scoped),
12830 }).pipe(Effect.provide(layerTest), Effect.scoped),
1283112831 );
1283212832
1283312833 it.live("quarantines old-runtime callbacks after successful hard teardown", () =>
......@@ -13189,7 +13189,7 @@ describe("AcpAdapterV2", () => {
1318913189 }),
1319013190 requestRuntimeRestart: true,
1319113191 });
13192 }).pipe(Effect.provide(testLayer), Effect.scoped),
13192 }).pipe(Effect.provide(layerTest), Effect.scoped),
1319313193 );
1319413194
1319513195 it.live("keeps stale deferred cleanup inert while replacement requests remain live", () =>
......@@ -13314,7 +13314,7 @@ describe("AcpAdapterV2", () => {
1331413314 }
1331513315 replacementTerminal = event.type === "turn.terminal";
1331613316 }
13317 }).pipe(Effect.provide(testLayer), Effect.scoped),
13317 }).pipe(Effect.provide(layerTest), Effect.scoped),
1331813318 );
1331913319
1332013320 it.live("resolves owner cancellation and concurrent resume waiters after hard teardown", () =>
......@@ -13447,7 +13447,7 @@ describe("AcpAdapterV2", () => {
1344713447 const methodsAfterRestart = yield* pollProtocolMethods(protocolEvents);
1344813448 assert.equal(methodsAfterRestart.filter((method) => method === "session/resume").length, 2);
1344913449 assert.equal(methodsAfterRestart.filter((method) => method === "session/fork").length, 1);
13450 }).pipe(Effect.provide(testLayer), Effect.scoped),
13450 }).pipe(Effect.provide(layerTest), Effect.scoped),
1345113451 );
1345213452
1345313453 it.live("direct Stop skips uninterruptible ACP cancel and recovers after native teardown", () =>
......@@ -13723,7 +13723,7 @@ describe("AcpAdapterV2", () => {
1372313723 stoppedRunTextOnFollowUp,
1372413724 "stopped-run residual text must not attach to the follow-up run",
1372513725 );
13726 }).pipe(Effect.provide(testLayer), Effect.scoped),
13726 }).pipe(Effect.provide(layerTest), Effect.scoped),
1372713727 );
1372813728
1372913729 it.effect(
......@@ -13878,7 +13878,7 @@ describe("AcpAdapterV2", () => {
1387813878 carriedCompleted,
1387913879 "Direct Stop must not carry a stopped subagent into the follow-up run",
1388013880 );
13881 }).pipe(Effect.provide(testLayer), Effect.scoped),
13881 }).pipe(Effect.provide(layerTest), Effect.scoped),
1388213882 );
1388313883
1388413884 it.live("restart_active terminates native work and reloads a clean runtime", () =>
......@@ -14064,7 +14064,7 @@ describe("AcpAdapterV2", () => {
1406414064 }
1406514065 assert.equal(secondTerminal, "completed");
1406614066 assert.isFalse(staleEventSeen, "interrupted runtime events must not attach to attempt 2");
14067 }).pipe(Effect.provide(testLayer), Effect.scoped),
14067 }).pipe(Effect.provide(layerTest), Effect.scoped),
1406814068 );
1406914069});
1407014070
apps/server/src/orchestration-v2/Adapters/AcpRegistryAdapterV2.test.ts+9-9
......@@ -43,11 +43,11 @@ const decodeAcpRegistryAdapterSettings = Schema.decodeUnknownEffect(
4343 AcpRegistryAdapterV2Driver.configSchema,
4444);
4545
46const serverConfigLayer = ServerConfig.layerTest(process.cwd(), {
46const layerServerConfig = ServerConfig.layerTest(process.cwd(), {
4747 prefix: "t3-acp-registry-v2-adapter-",
4848}).pipe(Layer.provide(NodeServices.layer));
4949
50const registryLayer = Layer.succeed(
50const layerRegistry = Layer.succeed(
5151 HttpClient.HttpClient,
5252 HttpClient.make((request) =>
5353 Effect.succeed(
......@@ -83,11 +83,11 @@ const registryLayer = Layer.succeed(
8383 ),
8484);
8585
86const testLayer = Layer.mergeAll(
86const layerTest = Layer.mergeAll(
8787 NodeServices.layer,
8888 IdAllocator.layer,
89 serverConfigLayer,
90 registryLayer,
89 layerServerConfig,
90 layerRegistry,
9191 ServerSettings.layerTest(),
9292);
9393
......@@ -247,7 +247,7 @@ describe("AcpRegistryAdapterV2", () => {
247247 answer("session/set_mode", {}),
248248 ],
249249 storedModePick: "autoEdit",
250 }).pipe(Effect.provide(testLayer), Effect.scoped),
250 }).pipe(Effect.provide(layerTest), Effect.scoped),
251251 );
252252
253253 it.effect("switches a mode config option under its own id", () =>
......@@ -262,7 +262,7 @@ describe("AcpRegistryAdapterV2", () => {
262262 answer("session/set_config_option", { configOptions: [permissionModeOption("auto")] }),
263263 ],
264264 storedModePick: "auto",
265 }).pipe(Effect.provide(testLayer), Effect.scoped),
265 }).pipe(Effect.provide(layerTest), Effect.scoped),
266266 );
267267 });
268268
......@@ -339,7 +339,7 @@ describe("AcpRegistryAdapterV2", () => {
339339 fs: { readTextFile: false, writeTextFile: false },
340340 terminal: false,
341341 });
342 }).pipe(Effect.provide(testLayer), Effect.scoped),
342 }).pipe(Effect.provide(layerTest), Effect.scoped),
343343 );
344344
345345 it.effect("opens a real ACP child process resolved from registry configuration", () =>
......@@ -485,6 +485,6 @@ describe("AcpRegistryAdapterV2", () => {
485485 name: "Auto",
486486 description: null,
487487 });
488 }).pipe(Effect.provide(testLayer), Effect.scoped),
488 }).pipe(Effect.provide(layerTest), Effect.scoped),
489489 );
490490});
apps/server/src/orchestration-v2/Adapters/AcpRegistryAdapterV2.testkit.ts+5-5
......@@ -33,16 +33,16 @@ const REPLAY_SETTINGS = Schema.decodeUnknownSync(AcpRegistrySettings)({
3333 authMethodId: "replay",
3434});
3535
36function makeAcpRegistryProviderAdapterRegistryReplayLayer(
36function layerAcpRegistryProviderAdapterRegistryReplay(
3737 transcript: AcpReplayTranscript,
3838 options: { readonly replayGate?: ProviderReplayGate } = {},
3939) {
40 const serverConfigLayer = Layer.effect(
40 const layerServerConfig = Layer.effect(
4141 ServerConfig.ServerConfig,
4242 makeReplayServerConfig(`acp-registry-${transcript.scenario}`).pipe(Effect.orDie),
4343 ).pipe(Layer.provide(NodeServices.layer));
4444
45 return ProviderAdapterRegistry.makeLayerEffect(
45 return ProviderAdapterRegistry.layerFromAdaptersEffect(
4646 Effect.gen(function* () {
4747 const fileSystem = yield* FileSystem.FileSystem;
4848 const path = yield* Path.Path;
......@@ -86,7 +86,7 @@ function makeAcpRegistryProviderAdapterRegistryReplayLayer(
8686 return [adapter];
8787 }),
8888 ).pipe(
89 Layer.provide(Layer.mergeAll(serverConfigLayer, NodeServices.layer, IdAllocator.layer)),
89 Layer.provide(Layer.mergeAll(layerServerConfig, NodeServices.layer, IdAllocator.layer)),
9090 // Held inbound lines must not outlive the scenario and wedge teardown.
9191 Layer.merge(
9292 Layer.effectDiscard(
......@@ -105,5 +105,5 @@ export const AcpRegistryOrchestratorReplayHarness: OrchestratorV2ProviderReplayH
105105 decodeAcpReplayTranscript(transcript, ACP_REGISTRY_PROVIDER, {
106106 retargetProvider: true,
107107 }),
108 makeProviderAdapterRegistryLayer: makeAcpRegistryProviderAdapterRegistryReplayLayer,
108 makeProviderAdapterRegistryLayer: layerAcpRegistryProviderAdapterRegistryReplay,
109109};
apps/server/src/orchestration-v2/Adapters/AntigravityAdapterV2.test.ts+4-4
......@@ -126,7 +126,7 @@ describe("AntigravityAdapterV2 flavor", () => {
126126 });
127127});
128128
129const sessionLayer = Layer.mergeAll(
129const layerSession = Layer.mergeAll(
130130 NodeServices.layer,
131131 IdAllocator.layer,
132132 ServerConfig.layerTest(process.cwd(), { prefix: "t3-antigravity-v2-adapter-" }).pipe(
......@@ -284,7 +284,7 @@ describe("AntigravityAdapterV2 client file system", () => {
284284 context("fs/read_text_file"),
285285 );
286286 assert.equal(viaInsideLink.content, "inside");
287 }).pipe(Effect.provide(sessionLayer), Effect.scoped),
287 }).pipe(Effect.provide(layerSession), Effect.scoped),
288288 );
289289});
290290
......@@ -419,7 +419,7 @@ describe("AntigravityAdapterV2 workspace changes", () => {
419419 context,
420420 ).pipe(Effect.exit);
421421 assert.isTrue(Exit.isFailure(fromA), "the previous workspace is no longer readable");
422 }).pipe(Effect.provide(sessionLayer), Effect.scoped),
422 }).pipe(Effect.provide(layerSession), Effect.scoped),
423423 );
424424});
425425
......@@ -532,6 +532,6 @@ describe("AntigravityAdapterV2 client file system under restrictive policies", (
532532 assert.isTrue(Exit.isFailure(outsideWrite), name);
533533 assert.isFalse(yield* fileSystem.exists(path.join(outside, "x.ts")), name);
534534 }
535 }).pipe(Effect.provide(sessionLayer), Effect.scoped),
535 }).pipe(Effect.provide(layerSession), Effect.scoped),
536536 );
537537});
apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.testkit.ts+9-9
......@@ -936,7 +936,7 @@ function replayQueryRunnerService(
936936 });
937937}
938938
939function makeClaudeAgentSdkReplayQueryRunnerLayer(
939function layerClaudeAgentSdkReplayQueryRunner(
940940 transcript: ClaudeAgentSdkReplayTranscript,
941941 options: { readonly replayGate?: ProviderReplayGate } = {},
942942): Layer.Layer<ClaudeAdapterV2.ClaudeAgentSdkQueryRunner> {
......@@ -946,7 +946,7 @@ function makeClaudeAgentSdkReplayQueryRunnerLayer(
946946 );
947947}
948948
949function makeClaudeAgentSdkReplayLayer(
949function layerClaudeAgentSdkReplay(
950950 transcript: ClaudeAgentSdkReplayTranscript,
951951 options: {
952952 readonly replayGate?: ProviderReplayGate;
......@@ -974,18 +974,18 @@ function makeClaudeAgentSdkReplayLayer(
974974 );
975975}
976976
977function makeClaudeProviderAdapterRegistryReplayLayer(
977function layerClaudeProviderAdapterRegistryReplay(
978978 transcript: ClaudeAgentSdkReplayTranscript,
979979 options: {
980980 readonly replayGate?: ProviderReplayGate;
981981 readonly queryRunner?: ClaudeQueryRunner;
982982 } = {},
983983) {
984 const serverConfigLayer = Layer.effect(
984 const layerServerConfig = Layer.effect(
985985 ServerConfig.ServerConfig,
986986 makeReplayServerConfig(transcript.scenario).pipe(Effect.orDie),
987987 ).pipe(Layer.provide(NodeServices.layer));
988 return ProviderAdapterRegistry.makeDriverLayer({
988 return ProviderAdapterRegistry.layerFromDrivers({
989989 drivers: [ClaudeAdapterV2.ClaudeAdapterV2Driver],
990990 configMap: {
991991 [ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID]: {
......@@ -995,10 +995,10 @@ function makeClaudeProviderAdapterRegistryReplayLayer(
995995 }).pipe(
996996 Layer.provide(
997997 Layer.mergeAll(
998 makeClaudeAgentSdkReplayLayer(transcript, options),
998 layerClaudeAgentSdkReplay(transcript, options),
999999 IdAllocator.layer,
10001000 NodeServices.layer,
1001 serverConfigLayer,
1001 layerServerConfig,
10021002 ),
10031003 ),
10041004 );
......@@ -2824,7 +2824,7 @@ export const ClaudeOrchestratorReplayHarness: OrchestratorV2ProviderReplayHarnes
28242824 ),
28252825 ),
28262826 makeProviderAdapterRegistryLayer: (transcript, options) =>
2827 makeClaudeProviderAdapterRegistryReplayLayer(transcript, options),
2827 layerClaudeProviderAdapterRegistryReplay(transcript, options),
28282828};
28292829
28302830/**
......@@ -2837,7 +2837,7 @@ export function makeClaudeRestartReplayHarness(transcript: ClaudeAgentSdkReplayT
28372837 harness: {
28382838 ...ClaudeOrchestratorReplayHarness,
28392839 makeProviderAdapterRegistryLayer: (replayed) =>
2840 makeClaudeProviderAdapterRegistryReplayLayer(replayed, { queryRunner }),
2840 layerClaudeProviderAdapterRegistryReplay(replayed, { queryRunner }),
28412841 } satisfies typeof ClaudeOrchestratorReplayHarness,
28422842 assertComplete: replayQueryRunnerService(transcript, queryRunner).assertComplete,
28432843 };
apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts+1-1
......@@ -593,7 +593,7 @@ export function makeClaudeAgentSdkProtocolLogger(input: {
593593 };
594594}
595595
596export const claudeAgentSdkQueryRunnerLiveLayer: Layer.Layer<
596export const layerQueryRunner: Layer.Layer<
597597 ClaudeAgentSdkQueryRunner,
598598 never,
599599 Crypto.Crypto | ProviderEventLoggers.ProviderEventLoggers
apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts+9-12
......@@ -56,7 +56,7 @@ import * as ProviderEventLoggers from "../../provider/Layers/ProviderEventLogger
5656import * as IdAllocator from "../IdAllocator.ts";
5757import * as EffectWorker from "../EffectWorker.ts";
5858import * as Orchestrator from "../Orchestrator.ts";
59import { makeOrchestratorV2ReplayLayerWithRegistry } from "../testkit/ProviderReplayHarness.ts";
59import * as ProviderReplayHarness from "../testkit/ProviderReplayHarness.ts";
6060import {
6161 ProviderAdapterForkThreadError,
6262 ProviderAdapterOpenSessionError,
......@@ -67,11 +67,8 @@ import {
6767} from "../ProviderAdapter.ts";
6868import type { ProviderContinuationRequest } from "../ProviderContinuationRequests.ts";
6969import * as CodexAdapterV2 from "./CodexAdapterV2.ts";
70import {
71 makeReplayServerConfig,
72 makeCodexProviderAdapterRegistryReplayLayer,
73 withCodexReplayChildMetadata,
74} from "./CodexAdapterV2.testkit.ts";
70import { makeReplayServerConfig, withCodexReplayChildMetadata } from "./CodexAdapterV2.testkit.ts";
71import * as CodexAdapterV2Testkit from "./CodexAdapterV2.testkit.ts";
7572
7673const encodeUnknownJson = Schema.encodeUnknownSync(Schema.fromJsonString(Schema.Unknown));
7774const replayTranscriptJson = Schema.fromJsonString(CodexReplay.CodexAppServerReplayTranscript);
......@@ -720,7 +717,7 @@ describe("CodexAdapterV2 process spawning", () => {
720717 );
721718 });
722719 const factory = yield* CodexAdapterV2.CodexAppServerClientFactory.pipe(
723 Effect.provide(CodexAdapterV2.codexAppServerClientFactoryFromSettingsLayer),
720 Effect.provide(CodexAdapterV2.layerAppServerClientFactory),
724721 Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner),
725722 Effect.provideService(
726723 ProviderEventLoggers.ProviderEventLoggers,
......@@ -775,7 +772,7 @@ describe("CodexAdapterV2 process spawning", () => {
775772 }).pipe(
776773 Effect.provide(
777774 Layer.mergeAll(
778 CodexAdapterV2.codexAppServerClientFactoryFromSettingsLayer,
775 CodexAdapterV2.layerAppServerClientFactory,
779776 ServerConfig.layerTest(process.cwd(), { prefix: "t3-codex-binary-home-" }),
780777 ),
781778 ),
......@@ -4238,9 +4235,9 @@ describe("CodexAdapterV2 post-settle continuation", () => {
42384235 );
42394236 }).pipe(
42404237 Effect.provide(
4241 makeOrchestratorV2ReplayLayerWithRegistry(
4238 ProviderReplayHarness.layerWithRegistry(
42424239 { name: "codex-background-stop", runtimePolicyOverride: { cwd } },
4243 makeCodexProviderAdapterRegistryReplayLayer({
4240 CodexAdapterV2Testkit.layer({
42444241 transcript: localTranscript,
42454242 driver: replayDriver,
42464243 }),
......@@ -4361,9 +4358,9 @@ describe("CodexAdapterV2 post-settle continuation", () => {
43614358 );
43624359 }).pipe(
43634360 Effect.provide(
4364 makeOrchestratorV2ReplayLayerWithRegistry(
4361 ProviderReplayHarness.layerWithRegistry(
43654362 { name: "codex-background-stop-untracked", runtimePolicyOverride: { cwd } },
4366 makeCodexProviderAdapterRegistryReplayLayer({ transcript: localTranscript }),
4363 CodexAdapterV2Testkit.layer({ transcript: localTranscript }),
43674364 { runEffectWorker: false },
43684365 ),
43694366 ),
apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.testkit.ts+10-12
......@@ -177,18 +177,18 @@ export function makeReplayServerConfig(
177177 });
178178}
179179
180export function makeCodexProviderAdapterRegistryReplayLayer(input: {
180export function layer(input: {
181181 readonly transcript: CodexReplay.CodexAppServerReplayTranscript;
182182 readonly driver?: CodexReplay.CodexAppServerReplayDriver;
183183}) {
184 const replayLayer =
184 const layerReplay =
185185 input.driver === undefined
186186 ? CodexReplay.layerReplay(input.transcript)
187187 : CodexReplay.layerReplayWithDriver(input.driver);
188 const replayClientFactoryLayer = Layer.succeed(CodexAdapterV2.CodexAppServerClientFactory, {
188 const layerReplayClientFactory = Layer.succeed(CodexAdapterV2.CodexAppServerClientFactory, {
189189 open: (openInput) =>
190190 Effect.gen(function* () {
191 const context = yield* Layer.build(replayLayer).pipe(
191 const context = yield* Layer.build(layerReplay).pipe(
192192 Effect.mapError(
193193 (cause) =>
194194 new ProviderAdapterOpenSessionError({
......@@ -204,11 +204,11 @@ export function makeCodexProviderAdapterRegistryReplayLayer(input: {
204204 );
205205 }),
206206 });
207 const serverConfigLayer = Layer.effect(
207 const layerServerConfig = Layer.effect(
208208 ServerConfig.ServerConfig,
209209 makeReplayServerConfig(input.transcript.scenario).pipe(Effect.orDie),
210210 ).pipe(Layer.provide(NodeServices.layer));
211 const registryLayer = ProviderAdapterRegistry.makeDriverLayer({
211 const layerRegistry = ProviderAdapterRegistry.layerFromDrivers({
212212 drivers: [CodexAdapterV2.CodexAdapterV2Driver],
213213 configMap: {
214214 [CodexAdapterV2.CODEX_DEFAULT_INSTANCE_ID]: {
......@@ -218,15 +218,15 @@ export function makeCodexProviderAdapterRegistryReplayLayer(input: {
218218 }).pipe(
219219 Layer.provide(
220220 Layer.mergeAll(
221 replayClientFactoryLayer,
222 serverConfigLayer,
221 layerReplayClientFactory,
222 layerServerConfig,
223223 NodeServices.layer,
224224 IdAllocator.layer,
225225 ),
226226 ),
227227 );
228228
229 return registryLayer;
229 return layerRegistry;
230230}
231231
232232const decodeCodexAppServerReplayTranscript = Schema.decodeUnknownEffect(
......@@ -267,9 +267,7 @@ export const CodexOrchestratorReplayHarness: OrchestratorV2ProviderReplayHarness
267267 Effect.promise((signal) => replayGate.beforeEmit(entry.label, signal)),
268268 },
269269 );
270 return yield* Layer.build(
271 makeCodexProviderAdapterRegistryReplayLayer({ transcript, driver }),
272 );
270 return yield* Layer.build(layer({ transcript, driver }));
273271 }),
274272 );
275273 },
apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts+2-2
......@@ -1340,7 +1340,7 @@ export const makeCodexAppServerSpawnCommand = Effect.fn(
13401340 });
13411341});
13421342
1343const makeCodexAppServerClientFactoryCommandLayer = (
1343const layerCodexAppServerClientFactoryCommand = (
13441344 options: CodexClient.CodexAppServerClientOptions & {
13451345 readonly command: string;
13461346 readonly args?: ReadonlyArray<string>;
......@@ -1456,7 +1456,7 @@ function isSensitiveCodexProtocolKey(key: string): boolean {
14561456 );
14571457}
14581458
1459export const codexAppServerClientFactoryFromSettingsLayer: Layer.Layer<
1459export const layerAppServerClientFactory: Layer.Layer<
14601460 CodexAppServerClientFactory,
14611461 never,
14621462 ChildProcessSpawner.ChildProcessSpawner | ProviderEventLoggers
apps/server/src/orchestration-v2/Adapters/CursorAdapterV2.testkit.ts+9-10
......@@ -492,7 +492,7 @@ export function makeCursorAgentSdkReplayRunner(
492492 };
493493}
494494
495function makeCursorAgentSdkReplayLayer(
495function layerCursorAgentSdkReplay(
496496 transcript: CursorAgentSdkReplayTranscript,
497497 options?: {
498498 readonly runner?: CursorAgentSdk.CursorAgentSdkRunnerShape;
......@@ -598,20 +598,20 @@ function makeReplayServerConfig(
598598 });
599599}
600600
601export function makeCursorProviderAdapterRegistryReplayLayer(
601export function layer(
602602 transcript: CursorAgentSdkReplayTranscript,
603603 options?: {
604604 readonly runner?: CursorAgentSdk.CursorAgentSdkRunnerShape;
605605 readonly assertCompleteOnFinalize?: boolean;
606606 },
607607) {
608 const serverConfigLayer = Layer.effect(
608 const layerServerConfig = Layer.effect(
609609 ServerConfig.ServerConfig,
610610 makeReplayServerConfig(transcript.scenario).pipe(Effect.orDie),
611611 ).pipe(Layer.provide(NodeServices.layer));
612612 // Skill discovery also scans user roots under HOME; an empty HOME keeps
613613 // replays from picking up the host's own skills.
614 const hostEnvironmentLayer = Layer.effect(
614 const layerHostEnvironment = Layer.effect(
615615 HostProcessEnvironment,
616616 Effect.gen(function* () {
617617 const fs = yield* FileSystem.FileSystem;
......@@ -619,7 +619,7 @@ export function makeCursorProviderAdapterRegistryReplayLayer(
619619 return { HOME: home };
620620 }).pipe(Effect.orDie),
621621 ).pipe(Layer.provide(NodeServices.layer));
622 return ProviderAdapterRegistry.makeDriverLayer({
622 return ProviderAdapterRegistry.layerFromDrivers({
623623 drivers: [CursorAdapterV2Driver],
624624 configMap: {
625625 [CURSOR_DEFAULT_INSTANCE_ID]: {
......@@ -629,9 +629,9 @@ export function makeCursorProviderAdapterRegistryReplayLayer(
629629 }).pipe(
630630 Layer.provide(
631631 Layer.mergeAll(
632 makeCursorAgentSdkReplayLayer(transcript, options),
633 serverConfigLayer,
634 hostEnvironmentLayer,
632 layerCursorAgentSdkReplay(transcript, options),
633 layerServerConfig,
634 layerHostEnvironment,
635635 NodeServices.layer,
636636 IdAllocator.layer,
637637 ),
......@@ -666,8 +666,7 @@ export const CursorOrchestratorReplayHarness: OrchestratorV2ProviderReplayHarnes
666666 }),
667667 ),
668668 ),
669 makeProviderAdapterRegistryLayer: (transcript) =>
670 makeCursorProviderAdapterRegistryReplayLayer(transcript),
669 makeProviderAdapterRegistryLayer: (transcript) => layer(transcript),
671670};
672671
673672function sanitizeReplayText(
apps/server/src/orchestration-v2/Adapters/CursorAgentSdk.test.ts+6-6
......@@ -104,7 +104,7 @@ vi.mock("../../provider/cursorSdk.ts", () => ({
104104 },
105105}));
106106
107const testLayer = CursorAgentSdk.cursorAgentSdkRunnerLiveLayer.pipe(
107const layerTest = CursorAgentSdk.layer.pipe(
108108 Layer.provide(
109109 Layer.succeed(ProviderEventLoggers.ProviderEventLoggers, {
110110 native: {
......@@ -147,7 +147,7 @@ describe("CursorAgentSdkRunner", () => {
147147 yield* run.wait;
148148
149149 assert.deepStrictEqual(cursorSdkMock.runOperations, ["run.start"]);
150 }).pipe(Effect.provide(testLayer)),
150 }).pipe(Effect.provide(layerTest)),
151151 );
152152
153153 it.effect("retries a resumed send after cancelling its abandoned local run", () =>
......@@ -188,7 +188,7 @@ describe("CursorAgentSdkRunner", () => {
188188 cwd: process.cwd(),
189189 },
190190 ]);
191 }).pipe(Effect.provide(testLayer)),
191 }).pipe(Effect.provide(layerTest)),
192192 );
193193
194194 it.effect("logs agent.close before invoking the Cursor SDK", () =>
......@@ -221,7 +221,7 @@ describe("CursorAgentSdkRunner", () => {
221221 "sdk:agent.close",
222222 ]);
223223 assert.equal(cursorSdkMock.agentClose.mock.calls.length, 1);
224 }).pipe(Effect.provide(testLayer)),
224 }).pipe(Effect.provide(layerTest)),
225225 );
226226
227227 it.effect("logs agent.close even when the Cursor SDK close fails", () =>
......@@ -259,7 +259,7 @@ describe("CursorAgentSdkRunner", () => {
259259 ]);
260260 assert.equal(cursorSdkMock.agentClose.mock.calls.length, 1);
261261 cursorSdkMock.setAgentCloseFailure(undefined);
262 }).pipe(Effect.provide(testLayer)),
262 }).pipe(Effect.provide(layerTest)),
263263 );
264264
265265 it.effect("surfaces interaction callback failures through run.wait", () =>
......@@ -299,6 +299,6 @@ describe("CursorAgentSdkRunner", () => {
299299 assert.equal(cursorSdkMock.create.mock.calls.length, 1);
300300 assert.equal(cursorSdkMock.send.mock.calls.length, 1);
301301 assert.equal(cursorSdkMock.runWait.mock.calls.length, 1);
302 }).pipe(Effect.provide(testLayer)),
302 }).pipe(Effect.provide(layerTest)),
303303 );
304304});
apps/server/src/orchestration-v2/Adapters/CursorAgentSdk.ts+1-1
......@@ -593,7 +593,7 @@ export function makeCursorAgentSdkRunner(
593593 });
594594}
595595
596export const cursorAgentSdkRunnerLiveLayer: Layer.Layer<
596export const layer: Layer.Layer<
597597 CursorAgentSdkRunner,
598598 never,
599599 ProviderEventLoggers.ProviderEventLoggers
apps/server/src/orchestration-v2/Adapters/GrokAdapterV2.test.ts+3-3
......@@ -311,10 +311,10 @@ describe("Grok permission prompts", () => {
311311});
312312
313313describe("Grok launch permission mode", () => {
314 const serverConfigLayer = ServerConfig.layerTest(process.cwd(), {
314 const layerServerConfig = ServerConfig.layerTest(process.cwd(), {
315315 prefix: "t3-grok-v2-launch-",
316316 }).pipe(Layer.provide(NodeServices.layer));
317 const testLayer = Layer.mergeAll(NodeServices.layer, IdAllocator.layer, serverConfigLayer);
317 const layerTest = Layer.mergeAll(NodeServices.layer, IdAllocator.layer, layerServerConfig);
318318
319319 // Opens a session through the adapter's own Grok runtime factory and returns
320320 // the argv it tried to launch. The spawn fails after recording, so no
......@@ -357,7 +357,7 @@ describe("Grok launch permission mode", () => {
357357 }).pipe(
358358 // Keep the launch argv unwrapped by the Linux cgroup shim.
359359 Effect.provideService(HostProcessPlatform, "darwin"),
360 Effect.provide(testLayer),
360 Effect.provide(layerTest),
361361 );
362362
363363 const policy = (
apps/server/src/orchestration-v2/Adapters/GrokAdapterV2.testkit.ts+5-5
......@@ -33,16 +33,16 @@ import { GROK_DEFAULT_INSTANCE_ID, GROK_PROVIDER, makeGrokAdapterV2 } from "./Gr
3333
3434const DEFAULT_GROK_SETTINGS = Schema.decodeUnknownSync(GrokSettings)({});
3535
36function makeGrokProviderAdapterRegistryReplayLayer(
36function layerGrokProviderAdapterRegistryReplay(
3737 transcript: AcpReplayTranscript,
3838 options: { readonly replayGate?: ProviderReplayGate } = {},
3939) {
40 const serverConfigLayer = Layer.effect(
40 const layerServerConfig = Layer.effect(
4141 ServerConfig.ServerConfig,
4242 makeReplayServerConfig(`grok-${transcript.scenario}`).pipe(Effect.orDie),
4343 ).pipe(Layer.provide(NodeServices.layer));
4444
45 return ProviderAdapterRegistry.makeLayerEffect(
45 return ProviderAdapterRegistry.layerFromAdaptersEffect(
4646 Effect.gen(function* () {
4747 const fileSystem = yield* FileSystem.FileSystem;
4848 const path = yield* Path.Path;
......@@ -102,7 +102,7 @@ function makeGrokProviderAdapterRegistryReplayLayer(
102102 return [adapter];
103103 }),
104104 ).pipe(
105 Layer.provide(Layer.mergeAll(serverConfigLayer, NodeServices.layer, IdAllocator.layer)),
105 Layer.provide(Layer.mergeAll(layerServerConfig, NodeServices.layer, IdAllocator.layer)),
106106 // Held inbound lines must not outlive the scenario and wedge teardown.
107107 Layer.merge(
108108 Layer.effectDiscard(
......@@ -118,5 +118,5 @@ export const GrokOrchestratorReplayHarness: OrchestratorV2ProviderReplayHarness<
118118> = {
119119 driver: GROK_PROVIDER,
120120 decodeTranscript: (transcript) => decodeAcpReplayTranscript(transcript, GROK_PROVIDER),
121 makeProviderAdapterRegistryLayer: makeGrokProviderAdapterRegistryReplayLayer,
121 makeProviderAdapterRegistryLayer: layerGrokProviderAdapterRegistryReplay,
122122};
apps/server/src/orchestration-v2/Adapters/OpenCode2AdapterV2.testkit.ts+8-6
......@@ -270,20 +270,22 @@ const makeReplayAdapter = (
270270 );
271271 });
272272
273const replayServerConfig = (scenario: string) =>
273const layerReplayServerConfig = (scenario: string) =>
274274 Layer.effect(ServerConfig.ServerConfig, makeReplayServerConfig(scenario).pipe(Effect.orDie)).pipe(
275275 Layer.provide(NodeServices.layer),
276276 );
277277
278function makeRegistryLayer(
278function layerRegistry(
279279 transcript: OpenCode2ReplayTranscript,
280280 options?: { readonly replayGate?: ProviderReplayGate },
281281) {
282282 return Layer.unwrap(
283283 makeReplayAdapter(transcript, { external: true, ...options }).pipe(
284 Effect.map((adapter) => ProviderAdapterRegistry.makeLayer([adapter])),
284 Effect.map((adapter) => ProviderAdapterRegistry.layerFromAdapters([adapter])),
285285 ),
286 ).pipe(Layer.provide(Layer.mergeAll(replayServerConfig(transcript.scenario), IdAllocator.layer)));
286 ).pipe(
287 Layer.provide(Layer.mergeAll(layerReplayServerConfig(transcript.scenario), IdAllocator.layer)),
288 );
287289}
288290
289291/**
......@@ -320,7 +322,7 @@ export const openCode2ReplayRuntime = (
320322 },
321323 });
322324 }).pipe(
323 Effect.provide(Layer.mergeAll(replayServerConfig("opencode2_adapter"), IdAllocator.layer)),
325 Effect.provide(Layer.mergeAll(layerReplayServerConfig("opencode2_adapter"), IdAllocator.layer)),
324326 );
325327
326328export const OpenCode2OrchestratorReplayHarness: OrchestratorV2ProviderReplayHarness<
......@@ -340,5 +342,5 @@ export const OpenCode2OrchestratorReplayHarness: OrchestratorV2ProviderReplayHar
340342 }),
341343 ),
342344 ),
343 makeProviderAdapterRegistryLayer: makeRegistryLayer,
345 makeProviderAdapterRegistryLayer: layerRegistry,
344346};
apps/server/src/orchestration-v2/Adapters/OpenCodeAdapterV2.testkit.ts+7-7
......@@ -389,7 +389,7 @@ function makeReplayClient(controller: OpenCodeReplayController): OpencodeClient
389389 } as unknown as OpencodeClient;
390390}
391391
392function makeOpenCodeReplayRuntimeLayer(transcript: OpenCodeSdkReplayTranscript) {
392function layerOpenCodeReplayRuntime(transcript: OpenCodeSdkReplayTranscript) {
393393 return Layer.effect(
394394 OpenCodeRuntime.OpenCodeRuntime,
395395 Effect.gen(function* () {
......@@ -456,12 +456,12 @@ function makeOpenCodeReplayRuntimeLayer(transcript: OpenCodeSdkReplayTranscript)
456456 );
457457}
458458
459function makeOpenCodeProviderAdapterRegistryReplayLayer(transcript: OpenCodeSdkReplayTranscript) {
460 const serverConfigLayer = Layer.effect(
459function layerOpenCodeProviderAdapterRegistryReplay(transcript: OpenCodeSdkReplayTranscript) {
460 const layerServerConfig = Layer.effect(
461461 ServerConfig.ServerConfig,
462462 makeReplayServerConfig(transcript.scenario).pipe(Effect.orDie),
463463 ).pipe(Layer.provide(NodeServices.layer));
464 return ProviderAdapterRegistry.makeDriverLayer({
464 return ProviderAdapterRegistry.layerFromDrivers({
465465 drivers: [OpenCodeAdapterV2Driver],
466466 configMap: {
467467 [OPENCODE_DEFAULT_INSTANCE_ID]: {
......@@ -472,8 +472,8 @@ function makeOpenCodeProviderAdapterRegistryReplayLayer(transcript: OpenCodeSdkR
472472 }).pipe(
473473 Layer.provide(
474474 Layer.mergeAll(
475 makeOpenCodeReplayRuntimeLayer(transcript),
476 serverConfigLayer,
475 layerOpenCodeReplayRuntime(transcript),
476 layerServerConfig,
477477 NodeServices.layer,
478478 IdAllocator.layer,
479479 Layer.succeed(
......@@ -508,5 +508,5 @@ export const OpenCodeOrchestratorReplayHarness: OrchestratorV2ProviderReplayHarn
508508 }),
509509 ),
510510 ),
511 makeProviderAdapterRegistryLayer: makeOpenCodeProviderAdapterRegistryReplayLayer,
511 makeProviderAdapterRegistryLayer: layerOpenCodeProviderAdapterRegistryReplay,
512512};
apps/server/src/orchestration-v2/Adapters/PiAdapterV2.test.ts+46-46
......@@ -43,11 +43,11 @@ import { handoffBudget } from "../ContextHandoffBudget.ts";
4343import { makePiAdapterV2, PI_PROVIDER } from "./PiAdapterV2.ts";
4444import { makePiRpcConnection, type PiRpcRecord } from "./PiRpc.ts";
4545
46const serverConfigLayer = ServerConfig.layerTest(process.cwd(), {
46const layerServerConfig = ServerConfig.layerTest(process.cwd(), {
4747 prefix: "t3-pi-v2-adapter-",
4848}).pipe(Layer.provide(NodeServices.layer));
4949
50const testLayer = Layer.mergeAll(NodeServices.layer, IdAllocator.layer, serverConfigLayer);
50const layerTest = Layer.mergeAll(NodeServices.layer, IdAllocator.layer, layerServerConfig);
5151
5252const decodeJsonLine = Schema.decodeSync(Schema.fromJsonString(Schema.Unknown));
5353const encodeJsonLine = Schema.encodeSync(Schema.fromJsonString(Schema.Unknown));
......@@ -458,7 +458,7 @@ const expectModelFailure = (errorMessage: string) =>
458458 terminal.status === "failed" &&
459459 terminal.failure.message === errorMessage,
460460 );
461 }).pipe(Effect.scoped, Effect.provide(testLayer));
461 }).pipe(Effect.scoped, Effect.provide(layerTest));
462462
463463describe("PiAdapterV2", () => {
464464 it.effect("stops provider-initiated work that has no T3 turn owner", () =>
......@@ -481,7 +481,7 @@ describe("PiAdapterV2", () => {
481481 sessionError.type === "provider_session.updated" &&
482482 sessionError.providerSession.lastError?.includes("invisible tool execution") === true,
483483 );
484 }).pipe(Effect.scoped, Effect.provide(testLayer)),
484 }).pipe(Effect.scoped, Effect.provide(layerTest)),
485485 );
486486
487487 it.effect("injects the T3 MCP extension and bearer when a session exists", () =>
......@@ -510,7 +510,7 @@ describe("PiAdapterV2", () => {
510510 }).pipe(
511511 Effect.ensuring(Effect.sync(() => McpProviderSession.clearMcpProviderSession(THREAD_ID))),
512512 Effect.scoped,
513 Effect.provide(testLayer),
513 Effect.provide(layerTest),
514514 ),
515515 );
516516
......@@ -528,7 +528,7 @@ describe("PiAdapterV2", () => {
528528 const error = yield* runtime.resumeThread({ providerThread }).pipe(Effect.flip);
529529 assert.equal(error._tag, "ProviderAdapterResumeThreadError");
530530 assert.match(String(error.cause), /while a turn is active/);
531 }).pipe(Effect.scoped, Effect.provide(testLayer)),
531 }).pipe(Effect.scoped, Effect.provide(layerTest)),
532532 );
533533
534534 it.effect("waits for a slow Pi resume without starting a replacement", () =>
......@@ -555,7 +555,7 @@ describe("PiAdapterV2", () => {
555555 assert.isFalse(fake.allRequests().some((request) => request.type === "new_session"));
556556 yield* startTurn(runtime, providerThread, "default");
557557 yield* fake.takeRequest("prompt");
558 }).pipe(Effect.scoped, Effect.provide(testLayer)),
558 }).pipe(Effect.scoped, Effect.provide(layerTest)),
559559 );
560560
561561 it.effect("creates a distinct native session after a failed resume", () =>
......@@ -586,7 +586,7 @@ describe("PiAdapterV2", () => {
586586 );
587587 yield* startTurn(runtime, replacement, "default");
588588 yield* fake.takeRequest("prompt");
589 }).pipe(Effect.scoped, Effect.provide(testLayer)),
589 }).pipe(Effect.scoped, Effect.provide(layerTest)),
590590 );
591591
592592 it.effect.each(["veto", "same identity"] as const)(
......@@ -617,7 +617,7 @@ describe("PiAdapterV2", () => {
617617 );
618618 yield* startTurn(runtime, providerThread, "default").pipe(Effect.flip);
619619 assert.isFalse(fake.allRequests().some((request) => request.type === "prompt"));
620 }).pipe(Effect.scoped, Effect.provide(testLayer)),
620 }).pipe(Effect.scoped, Effect.provide(layerTest)),
621621 );
622622
623623 it.effect("retires a timed-out lifecycle process before a late switch can race replacement", () =>
......@@ -661,7 +661,7 @@ describe("PiAdapterV2", () => {
661661 .allRequests()
662662 .some((request) => request.type === "new_session" || request.type === "prompt"),
663663 );
664 }).pipe(Effect.scoped, Effect.provide(testLayer)),
664 }).pipe(Effect.scoped, Effect.provide(layerTest)),
665665 );
666666
667667 it.effect(
......@@ -707,7 +707,7 @@ describe("PiAdapterV2", () => {
707707 assert.equal(replacement.nativeThreadRef?.nativeId, "/fake/fresh-after-delay.jsonl");
708708 assert.isNull(replacement.contextUsage);
709709 assert.isNull(replacement.nativeConversationHeadRef);
710 }).pipe(Effect.scoped, Effect.provide(testLayer)),
710 }).pipe(Effect.scoped, Effect.provide(layerTest)),
711711 );
712712
713713 it.effect("replaces a native session when the first resume's state refresh fails", () =>
......@@ -736,7 +736,7 @@ describe("PiAdapterV2", () => {
736736 yield* startTurn(runtime, replacement, "default");
737737 yield* fake.takeRequest("prompt");
738738 assert.isTrue(fake.allRequests().some((request) => request.type === "new_session"));
739 }).pipe(Effect.scoped, Effect.provide(testLayer)),
739 }).pipe(Effect.scoped, Effect.provide(layerTest)),
740740 );
741741
742742 it.effect("retires an interrupted switch before accepting further requests", () =>
......@@ -765,7 +765,7 @@ describe("PiAdapterV2", () => {
765765 })
766766 .pipe(Effect.flip);
767767 assert.isFalse(fake.allRequests().some((request) => request.type === "new_session"));
768 }).pipe(Effect.scoped, Effect.provide(testLayer)),
768 }).pipe(Effect.scoped, Effect.provide(layerTest)),
769769 );
770770
771771 it.effect("budgets legacy native history with Pi's selected model capacity", () =>
......@@ -814,7 +814,7 @@ describe("PiAdapterV2", () => {
814814 existingProviderThread: { ...providerThread, nativeThreadRef: null },
815815 });
816816 assert.equal(runtime.getModelContextWindow?.(modelSelection("default")), 32_000);
817 }).pipe(Effect.scoped, Effect.provide(testLayer)),
817 }).pipe(Effect.scoped, Effect.provide(layerTest)),
818818 );
819819
820820 it.effect("adopts the run's provider thread identity instead of minting a second row", () =>
......@@ -856,7 +856,7 @@ describe("PiAdapterV2", () => {
856856 assert.isTrue(
857857 updated.type === "provider_thread.updated" && updated.providerThread.id === placeholder.id,
858858 );
859 }).pipe(Effect.scoped, Effect.provide(testLayer)),
859 }).pipe(Effect.scoped, Effect.provide(layerTest)),
860860 );
861861
862862 it.effect("resets applied thinking when returning to Pi default", () =>
......@@ -909,7 +909,7 @@ describe("PiAdapterV2", () => {
909909 assert.equal(replayModel["modelId"], "grok-4.6");
910910 const resetLevel = yield* fake.takeRequest("set_thinking_level");
911911 assert.equal(resetLevel["level"], "medium");
912 }).pipe(Effect.scoped, Effect.provide(testLayer)),
912 }).pipe(Effect.scoped, Effect.provide(layerTest)),
913913 );
914914
915915 it.effect("expands a selected $ skill through Pi's native skill command", () =>
......@@ -944,7 +944,7 @@ describe("PiAdapterV2", () => {
944944 );
945945 const prompt = yield* fake.takeRequest("prompt");
946946 assert.equal(prompt["message"], "/skill:repo-review Review this change please");
947 }).pipe(Effect.scoped, Effect.provide(testLayer)),
947 }).pipe(Effect.scoped, Effect.provide(layerTest)),
948948 );
949949
950950 it.effect("expands every selected $ skill through Pi native skill commands", () =>
......@@ -980,7 +980,7 @@ describe("PiAdapterV2", () => {
980980 yield* startTurn(runtime, providerThread, "default", [], "use $repo-review and $deploy");
981981 const prompt = yield* fake.takeRequest("prompt");
982982 assert.equal(prompt["message"], "/skill:repo-review /skill:deploy use and");
983 }).pipe(Effect.scoped, Effect.provide(testLayer)),
983 }).pipe(Effect.scoped, Effect.provide(layerTest)),
984984 );
985985
986986 it.effect.each([
......@@ -1060,7 +1060,7 @@ describe("PiAdapterV2", () => {
10601060 fake.allRequests().findLast((request) => request.type === "switch_session")?.sessionPath,
10611061 forkFile,
10621062 );
1063 }).pipe(Effect.scoped, Effect.provide(testLayer)),
1063 }).pipe(Effect.scoped, Effect.provide(layerTest)),
10641064 );
10651065
10661066 it.effect("presents explicitly namespaced MCP extension tools", () =>
......@@ -1101,7 +1101,7 @@ describe("PiAdapterV2", () => {
11011101 });
11021102 assert.deepEqual(event.turnItem.input, { city: "Berlin" });
11031103 }
1104 }).pipe(Effect.scoped, Effect.provide(testLayer)),
1104 }).pipe(Effect.scoped, Effect.provide(layerTest)),
11051105 );
11061106
11071107 it.effect("observes official subagent results without inventing child threads", () =>
......@@ -1193,7 +1193,7 @@ describe("PiAdapterV2", () => {
11931193 subagentItem.turnItem.type === "subagent" &&
11941194 subagentItem.turnItem.childThreadId === null,
11951195 );
1196 }).pipe(Effect.scoped, Effect.provide(testLayer)),
1196 }).pipe(Effect.scoped, Effect.provide(layerTest)),
11971197 );
11981198
11991199 it.effect("persists edit patches and write content on file change items", () =>
......@@ -1289,7 +1289,7 @@ describe("PiAdapterV2", () => {
12891289 failedEdit.turnItem.type === "file_change" &&
12901290 failedEdit.turnItem.diffStr === "Could not find the text in c.ts.",
12911291 );
1292 }).pipe(Effect.scoped, Effect.provide(testLayer)),
1292 }).pipe(Effect.scoped, Effect.provide(layerTest)),
12931293 );
12941294
12951295 it.effect("settles a command-only prompt from its deferred ack and idle probe", () =>
......@@ -1317,7 +1317,7 @@ describe("PiAdapterV2", () => {
13171317 // settles the turn as completed.
13181318 const terminal = yield* takeEvent((event) => event.type === "turn.terminal");
13191319 assert.isTrue(terminal.type === "turn.terminal" && terminal.status === "completed");
1320 }).pipe(Effect.scoped, Effect.provide(testLayer)),
1320 }).pipe(Effect.scoped, Effect.provide(layerTest)),
13211321 );
13221322
13231323 it.effect("leaves /compacted as an ordinary prompt", () =>
......@@ -1333,7 +1333,7 @@ describe("PiAdapterV2", () => {
13331333 const prompt = yield* fake.takeRequest("prompt");
13341334 assert.equal(prompt["message"], "/compacted please");
13351335 assert.isFalse(fake.allRequests().some((request) => request["type"] === "compact"));
1336 }).pipe(Effect.scoped, Effect.provide(testLayer)),
1336 }).pipe(Effect.scoped, Effect.provide(layerTest)),
13371337 );
13381338
13391339 it.effect("fails a compact that never started", () =>
......@@ -1359,7 +1359,7 @@ describe("PiAdapterV2", () => {
13591359 terminal.status === "failed" &&
13601360 terminal.failure.message === "Nothing to compact (session too small)",
13611361 );
1362 }).pipe(Effect.scoped, Effect.provide(testLayer)),
1362 }).pipe(Effect.scoped, Effect.provide(layerTest)),
13631363 );
13641364
13651365 it.effect("restarts Pi when Stop interrupts a user compact", () =>
......@@ -1389,7 +1389,7 @@ describe("PiAdapterV2", () => {
13891389 yield* fake.closeStdout;
13901390 const terminal = yield* takeEvent((event) => event.type === "turn.terminal");
13911391 assert.isTrue(terminal.type === "turn.terminal" && terminal.status === "interrupted");
1392 }).pipe(Effect.scoped, Effect.provide(testLayer)),
1392 }).pipe(Effect.scoped, Effect.provide(layerTest)),
13931393 );
13941394
13951395 it.effect("steers /compact as RPC compact instead of a prompt", () =>
......@@ -1449,7 +1449,7 @@ describe("PiAdapterV2", () => {
14491449 yield* fake.takeRequest("get_state");
14501450 const terminal = yield* takeEvent((event) => event.type === "turn.terminal");
14511451 assert.isTrue(terminal.type === "turn.terminal" && terminal.status === "completed");
1452 }).pipe(Effect.scoped, Effect.provide(testLayer)),
1452 }).pipe(Effect.scoped, Effect.provide(layerTest)),
14531453 );
14541454
14551455 it.effect("persists current xAI capacity text for the thread error banner", () =>
......@@ -1489,7 +1489,7 @@ describe("PiAdapterV2", () => {
14891489 const uiResponse = yield* fake.takeRequest("extension_ui_response");
14901490 assert.equal(uiResponse["id"], "ui-trust");
14911491 assert.equal(uiResponse["confirmed"], true);
1492 }).pipe(Effect.scoped, Effect.provide(testLayer)),
1492 }).pipe(Effect.scoped, Effect.provide(layerTest)),
14931493 );
14941494
14951495 it.effect("remembers session approvals only for identical confirmation content", () =>
......@@ -1545,7 +1545,7 @@ describe("PiAdapterV2", () => {
15451545 other.type === "runtime_request.updated" &&
15461546 other.runtimeRequest.nativeRequestRef?.nativeId === "ui-other",
15471547 );
1548 }).pipe(Effect.scoped, Effect.provide(testLayer)),
1548 }).pipe(Effect.scoped, Effect.provide(layerTest)),
15491549 );
15501550
15511551 it.effect("offers an explicit empty value for extension input dialogs", () =>
......@@ -1580,7 +1580,7 @@ describe("PiAdapterV2", () => {
15801580 const response = yield* fake.takeRequest("extension_ui_response");
15811581 assert.equal(response["value"], "");
15821582 assert.isUndefined(response["cancelled"]);
1583 }).pipe(Effect.scoped, Effect.provide(testLayer)),
1583 }).pipe(Effect.scoped, Effect.provide(layerTest)),
15841584 );
15851585
15861586 it.effect("raises bridge edit confirmations as file-change approvals", () =>
......@@ -1611,7 +1611,7 @@ describe("PiAdapterV2", () => {
16111611 `${title} should be ${requestKind}`,
16121612 );
16131613 }
1614 }).pipe(Effect.scoped, Effect.provide(testLayer)),
1614 }).pipe(Effect.scoped, Effect.provide(layerTest)),
16151615 );
16161616
16171617 it.effect("reads a thread snapshot from pi's active branch", () =>
......@@ -1644,7 +1644,7 @@ describe("PiAdapterV2", () => {
16441644 assert.equal(snapshot.messages[0]!.text, "hello pi");
16451645 assert.equal(snapshot.messages[1]!.role, "assistant");
16461646 assert.equal(snapshot.messages[1]!.text, "hello back");
1647 }).pipe(Effect.scoped, Effect.provide(testLayer)),
1647 }).pipe(Effect.scoped, Effect.provide(layerTest)),
16481648 );
16491649
16501650 it.effect("keeps snapshot message identities distinct across native sessions", () =>
......@@ -1678,7 +1678,7 @@ describe("PiAdapterV2", () => {
16781678 fake.queueMessages(messages);
16791679 const b = yield* runtime.readThreadSnapshot({ providerThread: second });
16801680 assert.notEqual(a.messages[0]!.id, b.messages[0]!.id);
1681 }).pipe(Effect.scoped, Effect.provide(testLayer)),
1681 }).pipe(Effect.scoped, Effect.provide(layerTest)),
16821682 );
16831683
16841684 it.effect("rejects a nonpersistent session UUID instead of treating it as a resumable path", () =>
......@@ -1695,7 +1695,7 @@ describe("PiAdapterV2", () => {
16951695 })
16961696 .pipe(Effect.result);
16971697 assert.equal(result._tag, "Failure");
1698 }).pipe(Effect.scoped, Effect.provide(testLayer)),
1698 }).pipe(Effect.scoped, Effect.provide(layerTest)),
16991699 );
17001700
17011701 it.effect("keeps a settled turn's late prompt rejection off the next turn", () =>
......@@ -1731,7 +1731,7 @@ describe("PiAdapterV2", () => {
17311731 assert.isTrue(
17321732 secondTerminal.type === "turn.terminal" && secondTerminal.status === "completed",
17331733 );
1734 }).pipe(Effect.scoped, Effect.provide(testLayer)),
1734 }).pipe(Effect.scoped, Effect.provide(layerTest)),
17351735 );
17361736
17371737 it.effect("shows compaction progress and completes the same activity row", () =>
......@@ -1797,7 +1797,7 @@ describe("PiAdapterV2", () => {
17971797 yield* fake.emit({ type: "agent_settled" });
17981798 const terminal = yield* takeEvent((event) => event.type === "turn.terminal");
17991799 assert.isTrue(terminal.type === "turn.terminal" && terminal.status === "completed");
1800 }).pipe(Effect.scoped, Effect.provide(testLayer)),
1800 }).pipe(Effect.scoped, Effect.provide(layerTest)),
18011801 );
18021802
18031803 it.effect("uses distinct compaction IDs for first turns in separate threads", () =>
......@@ -1855,7 +1855,7 @@ describe("PiAdapterV2", () => {
18551855 first.turnItem.ordinal === second.turnItem.ordinal &&
18561856 first.turnItem.id !== second.turnItem.id,
18571857 );
1858 }).pipe(Effect.scoped, Effect.provide(testLayer)),
1858 }).pipe(Effect.scoped, Effect.provide(layerTest)),
18591859 );
18601860
18611861 it.effect("shows aborted compactions as stopped", () =>
......@@ -1892,7 +1892,7 @@ describe("PiAdapterV2", () => {
18921892 stopped.turnItem.status === "cancelled" &&
18931893 stopped.turnItem.title === "Context compaction stopped",
18941894 );
1895 }).pipe(Effect.scoped, Effect.provide(testLayer)),
1895 }).pipe(Effect.scoped, Effect.provide(layerTest)),
18961896 );
18971897
18981898 it.effect("keeps the turn open and updates one retry row through final failure", () =>
......@@ -1987,7 +1987,7 @@ describe("PiAdapterV2", () => {
19871987 terminal.retry.maxAttempts === 3 &&
19881988 terminal.retryStartedAt === firstRetry.turnItem.startedAt,
19891989 );
1990 }).pipe(Effect.scoped, Effect.provide(testLayer)),
1990 }).pipe(Effect.scoped, Effect.provide(layerTest)),
19911991 );
19921992
19931993 it.effect("preserves exhausted retry failure through non-retrying compaction", () =>
......@@ -2051,7 +2051,7 @@ describe("PiAdapterV2", () => {
20512051 terminal.retry?.attempt === 5 &&
20522052 terminal.retry.maxAttempts === 5,
20532053 );
2054 }).pipe(Effect.scoped, Effect.provide(testLayer)),
2054 }).pipe(Effect.scoped, Effect.provide(layerTest)),
20552055 );
20562056
20572057 it.effect("marks retry progress recovered when Pi succeeds", () =>
......@@ -2102,7 +2102,7 @@ describe("PiAdapterV2", () => {
21022102 yield* fake.emit({ type: "agent_settled" });
21032103 const terminal = yield* takeEvent((event) => event.type === "turn.terminal");
21042104 assert.isTrue(terminal.type === "turn.terminal" && terminal.status === "completed");
2105 }).pipe(Effect.scoped, Effect.provide(testLayer)),
2105 }).pipe(Effect.scoped, Effect.provide(layerTest)),
21062106 );
21072107
21082108 it.effect("stops active retry progress when the turn is interrupted", () =>
......@@ -2153,7 +2153,7 @@ describe("PiAdapterV2", () => {
21532153 );
21542154 const terminal = yield* takeEvent((event) => event.type === "turn.terminal");
21552155 assert.isTrue(terminal.type === "turn.terminal" && terminal.status === "interrupted");
2156 }).pipe(Effect.scoped, Effect.provide(testLayer)),
2156 }).pipe(Effect.scoped, Effect.provide(layerTest)),
21572157 );
21582158
21592159 it.effect("keeps extension-started compaction and recovery in the settled turn", () =>
......@@ -2201,7 +2201,7 @@ describe("PiAdapterV2", () => {
22012201 yield* fake.takeRequest("get_state");
22022202 const terminal = yield* takeEvent((event) => event.type === "turn.terminal");
22032203 assert.isTrue(terminal.type === "turn.terminal" && terminal.status === "completed");
2204 }).pipe(Effect.scoped, Effect.provide(testLayer)),
2204 }).pipe(Effect.scoped, Effect.provide(layerTest)),
22052205 );
22062206
22072207 it.effect("keeps working after a settle probe fails before detached compaction", () =>
......@@ -2242,7 +2242,7 @@ describe("PiAdapterV2", () => {
22422242 yield* fake.takeRequest("get_state");
22432243 const terminal = yield* takeEvent((event) => event.type === "turn.terminal");
22442244 assert.isTrue(terminal.type === "turn.terminal" && terminal.status === "completed");
2245 }).pipe(Effect.scoped, Effect.provide(testLayer)),
2245 }).pipe(Effect.scoped, Effect.provide(layerTest)),
22462246 );
22472247
22482248 it.effect("restarts Pi when Stop interrupts detached compaction", () =>
......@@ -2279,7 +2279,7 @@ describe("PiAdapterV2", () => {
22792279 yield* fake.closeStdout;
22802280 const terminal = yield* takeEvent((event) => event.type === "turn.terminal");
22812281 assert.isTrue(terminal.type === "turn.terminal" && terminal.status === "interrupted");
2282 }).pipe(Effect.scoped, Effect.provide(testLayer)),
2282 }).pipe(Effect.scoped, Effect.provide(layerTest)),
22832283 );
22842284
22852285 it.effect("ignores an idle snapshot made stale by a steer", () =>
......@@ -2357,7 +2357,7 @@ describe("PiAdapterV2", () => {
23572357 yield* fake.takeRequest("get_state");
23582358 const terminal = yield* takeEvent((event) => event.type === "turn.terminal");
23592359 assert.isTrue(terminal.type === "turn.terminal" && terminal.status === "completed");
2360 }).pipe(Effect.scoped, Effect.provide(testLayer)),
2360 }).pipe(Effect.scoped, Effect.provide(layerTest)),
23612361 );
23622362});
23632363
apps/server/src/orchestration-v2/Adapters/PiAdapterV2.testkit.ts+5-5
......@@ -407,18 +407,18 @@ function makePiReplaySpawner(
407407 * spawner that tees a live `pi`; replay passes the transcript-backed one. The
408408 * launch arguments are part of the recorded argv, so replay reuses them.
409409 */
410export function makePiProviderAdapterRegistryLayer<E, R>(input: {
410export function layer<E, R>(input: {
411411 readonly scenario: string;
412412 readonly spawner: Layer.Layer<ChildProcessSpawner.ChildProcessSpawner, E, R>;
413413 readonly binaryPath: string;
414414 readonly launchArgs: string;
415415 readonly environment?: ProviderInstanceEnvironment;
416416}) {
417 const serverConfigLayer = Layer.effect(
417 const layerServerConfig = Layer.effect(
418418 ServerConfig.ServerConfig,
419419 makeReplayServerConfig(`pi-${input.scenario}`).pipe(Effect.orDie),
420420 ).pipe(Layer.provide(NodeServices.layer));
421 return ProviderAdapterRegistry.makeDriverLayer({
421 return ProviderAdapterRegistry.layerFromDrivers({
422422 drivers: [PiAdapterV2Driver],
423423 configMap: {
424424 [PI_PROVIDER]: {
......@@ -430,7 +430,7 @@ export function makePiProviderAdapterRegistryLayer<E, R>(input: {
430430 },
431431 }).pipe(
432432 Layer.provide(input.spawner),
433 Layer.provide(Layer.mergeAll(serverConfigLayer, NodeServices.layer, IdAllocator.layer)),
433 Layer.provide(Layer.mergeAll(layerServerConfig, NodeServices.layer, IdAllocator.layer)),
434434 );
435435}
436436
......@@ -457,7 +457,7 @@ export const PiOrchestratorReplayHarness: OrchestratorV2ProviderReplayHarness<
457457 ),
458458 ),
459459 makeProviderAdapterRegistryLayer: (transcript) =>
460 makePiProviderAdapterRegistryLayer({
460 layer({
461461 scenario: transcript.scenario,
462462 binaryPath: "pi",
463463 launchArgs: metadataString(transcript, "launchArgs"),
apps/server/src/orchestration-v2/AttachmentClaims.test.ts+11-11
......@@ -23,7 +23,7 @@ import {
2323 releaseClaimedAttachments,
2424} from "./AttachmentClaims.ts";
2525
26const testLayer = Layer.mergeAll(NodeServices.layer).pipe(
26const layerTest = Layer.mergeAll(NodeServices.layer).pipe(
2727 Layer.provideMerge(ServerConfig.layerTest(process.cwd(), { prefix: "t3-attachment-claims-" })),
2828 Layer.provideMerge(NodeServices.layer),
2929);
......@@ -62,7 +62,7 @@ describe("AttachmentClaims", () => {
6262 const claimed = yield* claimPendingAttachments({ threadId: "thread-many", attachments });
6363 expect(claimed.attachments).toEqual(attachments);
6464 expect(claimed.claimedPaths).toEqual([]);
65 }).pipe(Effect.provide(testLayer)),
65 }).pipe(Effect.provide(layerTest)),
6666 );
6767
6868 it.effect("rejects oversized image batches before copying a pending upload", () =>
......@@ -89,7 +89,7 @@ describe("AttachmentClaims", () => {
8989 if (result._tag === "Failure") expect(String(result.cause)).toContain("80 MiB");
9090 const config = yield* ServerConfig.ServerConfig;
9191 expect(NodeFS.readdirSync(config.attachmentsDir)).toHaveLength(1);
92 }).pipe(Effect.provide(testLayer)),
92 }).pipe(Effect.provide(layerTest)),
9393 );
9494
9595 it.effect("claims a pending upload into the thread store and rewrites the id", () =>
......@@ -117,7 +117,7 @@ describe("AttachmentClaims", () => {
117117 entry.startsWith("pending-"),
118118 );
119119 expect(pendingFiles).toHaveLength(1);
120 }).pipe(Effect.provide(testLayer)),
120 }).pipe(Effect.provide(layerTest)),
121121 );
122122
123123 it.effect("passes already-claimed attachments through untouched", () =>
......@@ -135,7 +135,7 @@ describe("AttachmentClaims", () => {
135135 });
136136 expect(claimed.attachments[0]).toBe(stored);
137137 expect(claimed.claimedPaths).toHaveLength(0);
138 }).pipe(Effect.provide(testLayer)),
138 }).pipe(Effect.provide(layerTest)),
139139 );
140140
141141 it.effect("rejects a pending ref whose staged file is missing", () =>
......@@ -151,7 +151,7 @@ describe("AttachmentClaims", () => {
151151 claimPendingAttachments({ threadId: "thread-claims-3", attachments: [missing] }),
152152 );
153153 expect(result._tag).toBe("Failure");
154 }).pipe(Effect.provide(testLayer)),
154 }).pipe(Effect.provide(layerTest)),
155155 );
156156
157157 it.effect("rejects a size mismatch and cleans up earlier claims from the batch", () =>
......@@ -186,7 +186,7 @@ describe("AttachmentClaims", () => {
186186 entry.startsWith("thread-claims-4-"),
187187 );
188188 expect(claimedFiles).toHaveLength(0);
189 }).pipe(Effect.provide(testLayer)),
189 }).pipe(Effect.provide(layerTest)),
190190 );
191191
192192 it.effect("rejects duplicate attachment ids before copying anything", () =>
......@@ -218,7 +218,7 @@ describe("AttachmentClaims", () => {
218218 expect(
219219 NodeFS.readdirSync(config.attachmentsDir).filter((entry) => entry.startsWith("pending-")),
220220 ).toHaveLength(1);
221 }).pipe(Effect.provide(testLayer)),
221 }).pipe(Effect.provide(layerTest)),
222222 );
223223
224224 it.effect("rolls back a copy that finishes after the claim is interrupted", () =>
......@@ -272,7 +272,7 @@ describe("AttachmentClaims", () => {
272272 expect(
273273 NodeFS.readdirSync(config.attachmentsDir).filter((entry) => entry.startsWith("pending-")),
274274 ).toHaveLength(1);
275 }).pipe(Effect.provide(testLayer)),
275 }).pipe(Effect.provide(layerTest)),
276276 );
277277
278278 it.effect("releaseClaimedAttachments removes claimed copies best-effort", () =>
......@@ -291,7 +291,7 @@ describe("AttachmentClaims", () => {
291291 expect(NodeFS.existsSync(claimed.claimedPaths[0]!)).toBe(false);
292292 // Releasing again is a no-op, not an error.
293293 yield* releaseClaimedAttachments(claimed.claimedPaths);
294 }).pipe(Effect.provide(testLayer)),
294 }).pipe(Effect.provide(layerTest)),
295295 );
296296
297297 it.effect("treats malformed ids as non-pending and passes them through", () =>
......@@ -311,6 +311,6 @@ describe("AttachmentClaims", () => {
311311 });
312312 expect(claimed.attachments[0]).toBe(bogus);
313313 expect(claimed.claimedPaths).toHaveLength(0);
314 }).pipe(Effect.provide(testLayer)),
314 }).pipe(Effect.provide(layerTest)),
315315 );
316316});
apps/server/src/orchestration-v2/BackgroundWorkStop.integration.test.ts+3-3
......@@ -31,7 +31,7 @@ import type {
3131 ProviderAdapterV2TurnInput,
3232} from "./ProviderAdapter.ts";
3333import * as ProviderAdapterRegistry from "./ProviderAdapterRegistry.ts";
34import { makeOrchestratorV2ReplayLayerWithRegistry } from "./testkit/ProviderReplayHarness.ts";
34import * as ProviderReplayHarness from "./testkit/ProviderReplayHarness.ts";
3535import { checkpointWorkspace } from "./testkit/ReplayFixtureWorkspace.ts";
3636
3737const driver = ProviderDriverKind.make("codex");
......@@ -571,9 +571,9 @@ const stopEarlierBackgroundWork = ({
571571 );
572572 }).pipe(
573573 Effect.provide(
574 makeOrchestratorV2ReplayLayerWithRegistry(
574 ProviderReplayHarness.layerWithRegistry(
575575 { name: "background-work-stop" },
576 ProviderAdapterRegistry.makeSingleLayer(adapter),
576 ProviderAdapterRegistry.layerSingle(adapter),
577577 { runEffectWorker: false },
578578 ),
579579 ),
apps/server/src/orchestration-v2/CheckpointCaptureService.test.ts+11-11
......@@ -21,7 +21,7 @@ import * as Layer from "effect/Layer";
2121import * as Ref from "effect/Ref";
2222import * as SqlClient from "effect/sql/SqlClient";
2323
24import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts";
24import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts";
2525import * as CheckpointStore from "../checkpointing/CheckpointStore.ts";
2626import { VcsProcessTimeoutError } from "@t3tools/contracts";
2727import * as CheckpointService from "./CheckpointService.ts";
......@@ -30,9 +30,9 @@ import * as EventSink from "./EventSink.ts";
3030import * as IdAllocator from "./IdAllocator.ts";
3131import * as ProjectionStore from "./ProjectionStore.ts";
3232
33const ProjectionStoreTestLayer = Layer.mergeAll(
34 ProjectionStore.layer.pipe(Layer.provideMerge(SqlitePersistenceMemory)),
35 SqlitePersistenceMemory,
33const layerProjectionStoreTest = Layer.mergeAll(
34 ProjectionStore.layer.pipe(Layer.provideMerge(SqlitePersistence.layerMemory)),
35 SqlitePersistence.layerMemory,
3636);
3737
3838const threadId = ThreadId.make("thread:checkpoint-capture-delegated");
......@@ -50,7 +50,7 @@ const modelSelection = {
5050 model: "gpt-5.4",
5151} as const;
5252
53it.layer(ProjectionStoreTestLayer)("CheckpointCaptureServiceV2", (it) => {
53it.layer(layerProjectionStoreTest)("CheckpointCaptureServiceV2", (it) => {
5454 it.effect.each([false, true])(
5555 "captures without decoding history or losing newer delegated completion, ref lookup fails=%s",
5656 (refLookupFails) =>
......@@ -265,7 +265,7 @@ it.layer(ProjectionStoreTestLayer)("CheckpointCaptureServiceV2", (it) => {
265265 );
266266
267267 const committed = yield* Ref.make<ReadonlyArray<OrchestrationV2DomainEvent>>([]);
268 const captureLayer = CheckpointCaptureService.layer.pipe(
268 const layerCapture = CheckpointCaptureService.layer.pipe(
269269 Layer.provide(
270270 Layer.mergeAll(
271271 IdAllocator.layer,
......@@ -376,7 +376,7 @@ it.layer(ProjectionStoreTestLayer)("CheckpointCaptureServiceV2", (it) => {
376376 yield* Ref.set(committed, []);
377377 yield* service.execute({ threadId, runId, scopeId });
378378 assert.deepEqual(yield* Ref.get(committed), []);
379 }).pipe(Effect.provide(captureLayer));
379 }).pipe(Effect.provide(layerCapture));
380380 }),
381381 );
382382
......@@ -451,7 +451,7 @@ it.layer(ProjectionStoreTestLayer)("CheckpointCaptureServiceV2", (it) => {
451451 });
452452
453453 const committed = yield* Ref.make<ReadonlyArray<OrchestrationV2DomainEvent>>([]);
454 const captureLayer = CheckpointCaptureService.layer.pipe(
454 const layerCapture = CheckpointCaptureService.layer.pipe(
455455 Layer.provide(
456456 Layer.mergeAll(
457457 IdAllocator.layer,
......@@ -472,7 +472,7 @@ it.layer(ProjectionStoreTestLayer)("CheckpointCaptureServiceV2", (it) => {
472472 Effect.flatMap((service) =>
473473 service.execute({ threadId: stoppedThreadId, runId: stoppedRunId, scopeId }),
474474 ),
475 Effect.provide(captureLayer),
475 Effect.provide(layerCapture),
476476 );
477477
478478 assert.deepEqual(yield* Ref.get(committed), []);
......@@ -680,7 +680,7 @@ it.layer(ProjectionStoreTestLayer)("CheckpointCaptureServiceV2", (it) => {
680680 capturedAt: cancelledAt,
681681 };
682682 const commits = yield* Ref.make(0);
683 const captureLayer = CheckpointCaptureService.layer.pipe(
683 const layerCapture = CheckpointCaptureService.layer.pipe(
684684 Layer.provide(
685685 Layer.mergeAll(
686686 IdAllocator.layer,
......@@ -716,7 +716,7 @@ it.layer(ProjectionStoreTestLayer)("CheckpointCaptureServiceV2", (it) => {
716716 runId: cancelledRunId,
717717 scopeId: cancelledScopeId,
718718 });
719 }).pipe(Effect.provide(captureLayer));
719 }).pipe(Effect.provide(layerCapture));
720720
721721 assert.equal(yield* Ref.get(commits), 1);
722722 const projected = yield* projectionStore.getCheckpointCaptureContext(cancelledThreadId, {
apps/server/src/orchestration-v2/CheckpointRestoreSafety.test.ts+2-2
......@@ -98,7 +98,7 @@ it.effect.each([
9898 checkpointScopes: [{ id: scopeId, cwd }],
9999 runs: [{ id: "later-run", ordinal: 1, status: "completed", rootNodeId: null }],
100100 } as unknown as OrchestrationV2ThreadProjection;
101 const testLayer = CheckpointRollbackService.layer.pipe(
101 const layerTest = CheckpointRollbackService.layer.pipe(
102102 Layer.provide(
103103 Layer.mergeAll(
104104 NodeServices.layer,
......@@ -175,7 +175,7 @@ it.effect.each([
175175 ),
176176 );
177177 const service = yield* CheckpointRollbackService.CheckpointRollbackServiceV2.pipe(
178 Effect.provide(testLayer),
178 Effect.provide(layerTest),
179179 );
180180 const restoreFiles = owner !== "conversation";
181181 const rejected = !["sibling", "stopped-provider", "shared-provider", "conversation"].includes(
apps/server/src/orchestration-v2/CheckpointRollbackService.test.ts+11-11
......@@ -32,7 +32,7 @@ import * as RuntimePolicy from "./RuntimePolicy.ts";
3232const unrelatedProject = Option.some({
3333 workspaceRoot: "/nonexistent/t3-rollback-project",
3434} as never);
35const checkpointRollbackServiceLayer = CheckpointRollbackService.layer.pipe(
35const layerCheckpointRollbackService = CheckpointRollbackService.layer.pipe(
3636 Layer.provide(
3737 Layer.mergeAll(
3838 NodeServices.layer,
......@@ -61,7 +61,7 @@ it.effect("rejects a non-ready checkpoint before opening a session or restoring
6161 checkpoints: [{ id: checkpointId, scopeId, status: "stale" }],
6262 checkpointScopes: [{ id: scopeId, cwd: process.cwd() }],
6363 } as unknown as OrchestrationV2ThreadProjection;
64 const testLayer = checkpointRollbackServiceLayer.pipe(
64 const layerTest = layerCheckpointRollbackService.pipe(
6565 Layer.provide(
6666 Layer.mergeAll(
6767 Layer.mock(CheckpointService.CheckpointServiceV2)({ restore }),
......@@ -103,7 +103,7 @@ it.effect("rejects a non-ready checkpoint before opening a session or restoring
103103 assert.equal(resolveRuntimePolicy.mock.calls.length, 0);
104104 assert.equal(open.mock.calls.length, 0);
105105 assert.equal(restore.mock.calls.length, 0);
106 }).pipe(Effect.provide(testLayer));
106 }).pipe(Effect.provide(layerTest));
107107});
108108
109109it.effect("rejects a rollback when another provider thread became active", () => {
......@@ -138,7 +138,7 @@ it.effect("rejects a rollback when another provider thread became active", () =>
138138 checkpoints: [{ id: checkpointId, scopeId, status: "ready" }],
139139 checkpointScopes: [{ id: scopeId, cwd: process.cwd() }],
140140 } as unknown as OrchestrationV2ThreadProjection;
141 const testLayer = checkpointRollbackServiceLayer.pipe(
141 const layerTest = layerCheckpointRollbackService.pipe(
142142 Layer.provide(
143143 Layer.mergeAll(
144144 Layer.mock(CheckpointService.CheckpointServiceV2)({ restore }),
......@@ -180,7 +180,7 @@ it.effect("rejects a rollback when another provider thread became active", () =>
180180 assert.equal(resolveRuntimePolicy.mock.calls.length, 0);
181181 assert.equal(open.mock.calls.length, 0);
182182 assert.equal(restore.mock.calls.length, 0);
183 }).pipe(Effect.provide(testLayer));
183 }).pipe(Effect.provide(layerTest));
184184});
185185
186186it.effect("rejects a rollback when provider selection changed before execution", () => {
......@@ -218,7 +218,7 @@ it.effect("rejects a rollback when provider selection changed before execution",
218218 checkpoints: [{ id: checkpointId, scopeId, status: "ready" }],
219219 checkpointScopes: [{ id: scopeId, cwd: process.cwd() }],
220220 } as unknown as OrchestrationV2ThreadProjection;
221 const testLayer = checkpointRollbackServiceLayer.pipe(
221 const layerTest = layerCheckpointRollbackService.pipe(
222222 Layer.provide(
223223 Layer.mergeAll(
224224 Layer.mock(CheckpointService.CheckpointServiceV2)({ restore }),
......@@ -260,7 +260,7 @@ it.effect("rejects a rollback when provider selection changed before execution",
260260 assert.equal(resolveRuntimePolicy.mock.calls.length, 0);
261261 assert.equal(open.mock.calls.length, 0);
262262 assert.equal(restore.mock.calls.length, 0);
263 }).pipe(Effect.provide(testLayer));
263 }).pipe(Effect.provide(layerTest));
264264});
265265
266266it.effect("reports a missing provider turn as a structured rollback failure", () => {
......@@ -289,7 +289,7 @@ it.effect("reports a missing provider turn as a structured rollback failure", ()
289289 attempts: [],
290290 providerTurns: [],
291291 } as unknown as OrchestrationV2ThreadProjection;
292 const testLayer = checkpointRollbackServiceLayer.pipe(
292 const layerTest = layerCheckpointRollbackService.pipe(
293293 Layer.provide(
294294 Layer.mergeAll(
295295 Layer.mock(CheckpointService.CheckpointServiceV2)({ restore }),
......@@ -333,7 +333,7 @@ it.effect("reports a missing provider turn as a structured rollback failure", ()
333333 );
334334 assert.equal(error.cause, undefined);
335335 assert.equal(restore.mock.calls.length, 0);
336 }).pipe(Effect.provide(testLayer));
336 }).pipe(Effect.provide(layerTest));
337337});
338338
339339it.effect.each([
......@@ -387,7 +387,7 @@ it.effect.each([
387387 activeAttemptId: `attempt-${ordinal}`,
388388 })),
389389 } as unknown as OrchestrationV2ThreadProjection;
390 const testLayer = checkpointRollbackServiceLayer.pipe(
390 const layerTest = layerCheckpointRollbackService.pipe(
391391 Layer.provide(
392392 Layer.mergeAll(
393393 Layer.mock(CheckpointService.CheckpointServiceV2)({
......@@ -466,7 +466,7 @@ it.effect.each([
466466 calls,
467467 restoreFiles ? ["provider", "files", "projection"] : ["provider", "projection"],
468468 );
469 }).pipe(Effect.provide(testLayer));
469 }).pipe(Effect.provide(layerTest));
470470});
471471
472472it.effect.skipIf(!symlinksSupported)(
apps/server/src/orchestration-v2/CheckpointScopeOwnership.test.ts+7-7
......@@ -19,10 +19,10 @@ import * as IdAllocator from "./IdAllocator.ts";
1919import { checkpointRefForScopeOrdinal } from "./CheckpointService.ts";
2020import * as ProjectionStore from "./ProjectionStore.ts";
2121import * as ThreadManagement from "./ThreadManagementService.ts";
22import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts";
23const projectionLayer = Layer.mergeAll(
24 ProjectionStore.layer.pipe(Layer.provideMerge(SqlitePersistenceMemory)),
25 SqlitePersistenceMemory,
22import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts";
23const layerProjection = Layer.mergeAll(
24 ProjectionStore.layer.pipe(Layer.provideMerge(SqlitePersistence.layerMemory)),
25 SqlitePersistence.layerMemory,
2626 IdAllocator.layer,
2727);
2828it.effect("resolves the thread baseline after a second root run replaces scope ownership", () =>
......@@ -205,7 +205,7 @@ it.effect("resolves the thread baseline after a second root run replaces scope o
205205 assert.equal(context.checkpointScopes.length, 1);
206206 assert.equal(context.checkpointScopes[0]?.runId, secondRunId);
207207
208 const queryLayer = CheckpointDiffQuery.layer.pipe(
208 const layerQuery = CheckpointDiffQuery.layer.pipe(
209209 Layer.provide(
210210 Layer.mergeAll(
211211 Layer.mock(ThreadManagement.ThreadManagementService)({
......@@ -231,7 +231,7 @@ it.effect("resolves the thread baseline after a second root run replaces scope o
231231 const result = yield* Effect.gen(function* () {
232232 const query = yield* CheckpointDiffQuery.CheckpointDiffQuery;
233233 return yield* query.getFullThreadDiff({ threadId, toTurnCount: 2 });
234 }).pipe(Effect.provide(queryLayer));
234 }).pipe(Effect.provide(layerQuery));
235235 assert.equal(result.diff, "two-run diff");
236 }).pipe(Effect.provide(projectionLayer)),
236 }).pipe(Effect.provide(layerProjection)),
237237);
apps/server/src/orchestration-v2/CheckpointService.test.ts+2-2
......@@ -47,7 +47,7 @@ it.effect.each([false, true, "interrupt"] as const)(
4747 )
4848 : Effect.succeed(true),
4949 );
50 const testLayer = CheckpointService.layer.pipe(
50 const layerTest = CheckpointService.layer.pipe(
5151 Layer.provide(
5252 Layer.mergeAll(
5353 IdAllocator.layer,
......@@ -98,6 +98,6 @@ it.effect.each([false, true, "interrupt"] as const)(
9898 cwd: scope.cwd,
9999 checkpointRef: baseline.ref,
100100 });
101 }).pipe(Effect.provide(testLayer));
101 }).pipe(Effect.provide(layerTest));
102102 },
103103);
apps/server/src/orchestration-v2/ClaudeAutomaticDelivery.integration.test.ts+9-5
......@@ -26,7 +26,7 @@ import * as Stream from "effect/Stream";
2626import * as ScheduledTaskService from "../scheduledTasks/ScheduledTaskService.ts";
2727import * as SecretRequests from "../secrets/SecretRequests.ts";
2828import * as Scheduler from "../scheduling/Scheduler.ts";
29import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts";
29import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts";
3030import * as ClaudeAdapterV2 from "./Adapters/ClaudeAdapterV2.ts";
3131import * as EffectWorker from "./EffectWorker.ts";
3232import * as EventSink from "./EventSink.ts";
......@@ -36,7 +36,7 @@ import * as Orchestrator from "./Orchestrator.ts";
3636import * as ProviderAdapterRegistry from "./ProviderAdapterRegistry.ts";
3737import * as ThreadLaunchService from "./ThreadLaunchService.ts";
3838import * as ThreadManagementService from "./ThreadManagementService.ts";
39import { makeOrchestratorV2ReplayLayerWithRegistry } from "./testkit/ProviderReplayHarness.ts";
39import * as ProviderReplayHarness from "./testkit/ProviderReplayHarness.ts";
4040import { checkpointWorkspace } from "./testkit/ReplayFixtureWorkspace.ts";
4141
4242const sessionId = "automatic-delivery-session";
......@@ -318,7 +318,11 @@ it.effect.each(["child completion", "scheduled message", "user steering"] as con
318318 Layer.provide(Layer.mock(ThreadLaunchService.ThreadLaunchService)({})),
319319 Layer.provide(Layer.mock(SecretRequests.SecretRequests)({})),
320320 Layer.provide(
321 Layer.mergeAll(NodeCrypto.layer, Scheduler.layer, SqlitePersistenceMemory),
321 Layer.mergeAll(
322 NodeCrypto.layer,
323 Scheduler.layer,
324 SqlitePersistence.layerMemory,
325 ),
322326 ),
323327 ),
324328 ),
......@@ -410,9 +414,9 @@ it.effect.each(["child completion", "scheduled message", "user steering"] as con
410414 assert.equal(offers.length, 2);
411415 }).pipe(
412416 Effect.provide(
413 makeOrchestratorV2ReplayLayerWithRegistry(
417 ProviderReplayHarness.layerWithRegistry(
414418 { name: "claude-automatic-delivery" },
415 ProviderAdapterRegistry.makeSingleLayer(adapter),
419 ProviderAdapterRegistry.layerSingle(adapter),
416420 { runEffectWorker: false },
417421 ),
418422 ),
apps/server/src/orchestration-v2/CommandReceiptStore.ts+4-4
......@@ -6,7 +6,7 @@ import * as Layer from "effect/Layer";
66import * as Option from "effect/Option";
77import * as Schema from "effect/Schema";
88
9import { OrchestrationCommandReceiptRepositoryLive } from "../persistence/Layers/OrchestrationCommandReceipts.ts";
9import * as OrchestrationCommandReceiptsLayer from "../persistence/Layers/OrchestrationCommandReceipts.ts";
1010import * as OrchestrationCommandReceipts from "../persistence/Services/OrchestrationCommandReceipts.ts";
1111
1212/**
......@@ -139,7 +139,7 @@ function toApplicationReceipt(
139139 };
140140}
141141
142const baseLayer: Layer.Layer<
142const layerBase: Layer.Layer<
143143 CommandReceiptStoreV2,
144144 never,
145145 OrchestrationCommandReceipts.OrchestrationCommandReceiptRepository
......@@ -209,6 +209,6 @@ const baseLayer: Layer.Layer<
209209 }),
210210);
211211
212export const layer = baseLayer.pipe(Layer.provide(OrchestrationCommandReceiptRepositoryLive));
212export const layer = layerBase.pipe(Layer.provide(OrchestrationCommandReceiptsLayer.layer));
213213
214export const layerFromApplicationReceipts = baseLayer;
214export const layerFromApplicationReceipts = layerBase;
apps/server/src/orchestration-v2/ContextHandoffService.test.ts+2-2
......@@ -15,7 +15,7 @@ import * as Layer from "effect/Layer";
1515import * as ContextHandoffService from "./ContextHandoffService.ts";
1616import * as IdAllocator from "./IdAllocator.ts";
1717
18const TestLayer = ContextHandoffService.layer.pipe(Layer.provide(IdAllocator.layer));
18const layerTest = ContextHandoffService.layer.pipe(Layer.provide(IdAllocator.layer));
1919
2020function importedItem(
2121 input:
......@@ -67,7 +67,7 @@ function importedItem(
6767 };
6868}
6969
70it.layer(TestLayer)("ContextHandoffService legacy import", (it) => {
70it.layer(layerTest)("ContextHandoffService legacy import", (it) => {
7171 it.effect("prepares imported history for the first native v2 turn", () =>
7272 Effect.gen(function* () {
7373 const service = yield* ContextHandoffService.ContextHandoffServiceV2;
apps/server/src/orchestration-v2/CursorOrchestratorV2.live.test.ts+34-34
......@@ -21,12 +21,12 @@ import * as CheckpointStore from "../checkpointing/CheckpointStore.ts";
2121import * as BackgroundPolicy from "../background/BackgroundPolicy.ts";
2222import * as HostPowerMonitor from "../background/HostPowerMonitor.ts";
2323import * as ServerConfig from "../config.ts";
24import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts";
24import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts";
2525import * as AntigravityInstallation from "../provider/AntigravityInstallation.ts";
2626import * as CodexInstallation from "../provider/CodexInstallation.ts";
2727import * as ServerEnvironment from "../environment/ServerEnvironment.ts";
2828import * as ModelManifest from "../provider/ModelManifest.ts";
29import { ProviderInstanceRegistryHydrationLive } from "../provider/Layers/ProviderInstanceRegistryHydration.ts";
29import * as ProviderInstanceRegistryHydration from "../provider/Layers/ProviderInstanceRegistryHydration.ts";
3030import * as ProviderEventLoggers from "../provider/Layers/ProviderEventLoggers.ts";
3131import * as OpenCodeRuntime from "../provider/opencodeRuntime.ts";
3232import * as OpenCodeServerLedger from "../provider/OpenCodeServerLedger.ts";
......@@ -34,54 +34,54 @@ import * as ServerSettings from "../serverSettings.ts";
3434import * as VcsDriverRegistry from "../vcs/VcsDriverRegistry.ts";
3535import * as VcsProcess from "../vcs/VcsProcess.ts";
3636import * as Orchestrator from "./Orchestrator.ts";
37import { worktreeRepairDependenciesTestLayer } from "./ProviderTurnStartService.testkit.ts";
37import * as ProviderTurnStartServiceTestkit from "./ProviderTurnStartService.testkit.ts";
3838import * as EffectWorker from "./EffectWorker.ts";
39import { OrchestrationV2LayerLive } from "./runtimeLayer.ts";
39import * as RuntimeLayer from "./runtimeLayer.ts";
4040import * as McpSessionRegistryTestkit from "../mcp/McpSessionRegistry.testkit.ts";
4141import { CURSOR_MODEL_SELECTION, SUBAGENT_PROMPT } from "./testkit/fixtures/shared.ts";
4242
43const PlatformTestLayer = Layer.merge(
43const layerPlatformTest = Layer.merge(
4444 NodeServices.layer,
4545 Layer.mock(SourceControlProviderRegistry.SourceControlProviderRegistry)({
4646 resolveLink: () => Effect.die("unused title link"),
4747 }),
4848);
4949
50const serverConfigLayer = ServerConfig.layerTest(process.cwd(), {
50const layerServerConfig = ServerConfig.layerTest(process.cwd(), {
5151 prefix: "t3-cursor-v2-live-",
5252});
5353
54const vcsDriverRegistryLayer = VcsDriverRegistry.layer.pipe(
54const layerVcsDriverRegistry = VcsDriverRegistry.layer.pipe(
5555 Layer.provide(VcsProcess.layer),
56 Layer.provide(serverConfigLayer),
57 Layer.provide(PlatformTestLayer),
56 Layer.provide(layerServerConfig),
57 Layer.provide(layerPlatformTest),
5858);
5959
60const checkpointStoreLayer = CheckpointStore.layer.pipe(Layer.provide(vcsDriverRegistryLayer));
60const layerCheckpointStore = CheckpointStore.layer.pipe(Layer.provide(layerVcsDriverRegistry));
6161
62const serverSettingsLayer = ServerSettings.layerTest({
62const layerServerSettings = ServerSettings.layerTest({
6363 providers: {
6464 cursor: { enabled: true },
6565 },
6666});
67const backgroundPolicyLayer = BackgroundPolicy.layer.pipe(
67const layerBackgroundPolicy = BackgroundPolicy.layer.pipe(
6868 Layer.provide(Layer.effect(HostPowerMonitor.HostPowerMonitor, HostPowerMonitor.make())),
69 Layer.provide(serverSettingsLayer),
69 Layer.provide(layerServerSettings),
7070);
71const providerInstanceRegistryLayer = ProviderInstanceRegistryHydrationLive.pipe(
71const layerProviderInstanceRegistry = ProviderInstanceRegistryHydration.layer.pipe(
7272 Layer.provide(
7373 Layer.mergeAll(
74 serverConfigLayer.pipe(Layer.provide(PlatformTestLayer)),
75 serverSettingsLayer,
74 layerServerConfig.pipe(Layer.provide(layerPlatformTest)),
75 layerServerSettings,
7676 ServerSecretStore.layer.pipe(
77 Layer.provide(serverConfigLayer),
78 Layer.provide(PlatformTestLayer),
77 Layer.provide(layerServerConfig),
78 Layer.provide(layerPlatformTest),
7979 ),
8080 NodeServices.layer,
8181 FetchHttpClient.layer,
82 OpenCodeRuntime.OpenCodeRuntimeLive.pipe(
82 OpenCodeRuntime.layer.pipe(
8383 Layer.provide(OpenCodeServerLedger.layerTest),
84 Layer.provide(PlatformTestLayer),
84 Layer.provide(layerPlatformTest),
8585 ),
8686 Layer.succeed(
8787 ProviderEventLoggers.ProviderEventLoggers,
......@@ -89,9 +89,9 @@ const providerInstanceRegistryLayer = ProviderInstanceRegistryHydrationLive.pipe
8989 ),
9090 ModelManifest.layerTest,
9191 AntigravityInstallation.AntigravityInstallation.layer.pipe(
92 Layer.provide(serverConfigLayer.pipe(Layer.provide(PlatformTestLayer))),
92 Layer.provide(layerServerConfig.pipe(Layer.provide(layerPlatformTest))),
9393 Layer.provide(FetchHttpClient.layer),
94 Layer.provide(PlatformTestLayer),
94 Layer.provide(layerPlatformTest),
9595 ),
9696 // The Codex driver now resolves managed ChatGPT installs; these runs never launch Codex.
9797 Layer.mock(CodexInstallation.CodexInstallation)({
......@@ -106,17 +106,17 @@ const providerInstanceRegistryLayer = ProviderInstanceRegistryHydrationLive.pipe
106106 ),
107107);
108108
109const liveLayer = OrchestrationV2LayerLive.pipe(
110 Layer.provide(worktreeRepairDependenciesTestLayer),
109const layerLive = RuntimeLayer.layer.pipe(
110 Layer.provide(ProviderTurnStartServiceTestkit.layer),
111111 Layer.provide(McpSessionRegistryTestkit.layer),
112 Layer.provide(SqlitePersistenceMemory),
113 Layer.provide(checkpointStoreLayer),
114 Layer.provide(serverConfigLayer),
115 Layer.provide(serverSettingsLayer),
116 Layer.provide(providerInstanceRegistryLayer),
112 Layer.provide(SqlitePersistence.layerMemory),
113 Layer.provide(layerCheckpointStore),
114 Layer.provide(layerServerConfig),
115 Layer.provide(layerServerSettings),
116 Layer.provide(layerProviderInstanceRegistry),
117117 Layer.provide(ResetCreditCoordinator.layer),
118 Layer.provide(backgroundPolicyLayer),
119 Layer.provide(PlatformTestLayer),
118 Layer.provide(layerBackgroundPolicy),
119 Layer.provide(layerPlatformTest),
120120);
121121
122122const waitForIdle = Effect.fn("CursorOrchestratorV2Live.waitForIdle")(function* (
......@@ -234,7 +234,7 @@ describe.runIf(process.env.T3_CURSOR_LIVE_ORCHESTRATOR === "1")(
234234 );
235235 assert.include(targetProjection.contextHandoffs[0]?.summaryText ?? "", marker);
236236 assert.include(assistantText(targetProjection), marker);
237 }).pipe(Effect.provide(liveLayer), Effect.scoped),
237 }).pipe(Effect.provide(layerLive), Effect.scoped),
238238 360_000,
239239 );
240240
......@@ -296,7 +296,7 @@ describe.runIf(process.env.T3_CURSOR_LIVE_ORCHESTRATOR === "1")(
296296 supervised.runs.map((run) => run.status),
297297 ["completed"],
298298 );
299 }).pipe(Effect.provide(liveLayer), Effect.scoped),
299 }).pipe(Effect.provide(layerLive), Effect.scoped),
300300 360_000,
301301 );
302302
......@@ -371,7 +371,7 @@ describe.runIf(process.env.T3_CURSOR_LIVE_ORCHESTRATOR === "1")(
371371 `child thread ${subagent.childThreadId} should contain the subagent response`,
372372 );
373373 }
374 }).pipe(Effect.provide(liveLayer), Effect.scoped),
374 }).pipe(Effect.provide(layerLive), Effect.scoped),
375375 360_000,
376376 );
377377 },
apps/server/src/orchestration-v2/DelegatedCompletionDelivery.test.ts+21-25
......@@ -23,7 +23,7 @@ import * as Stream from "effect/Stream";
2323import * as CheckpointStore from "../checkpointing/CheckpointStore.ts";
2424import * as ServerConfig from "../config.ts";
2525import * as McpSessionRegistryTestkit from "../mcp/McpSessionRegistry.testkit.ts";
26import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts";
26import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts";
2727import * as ProjectEnrichmentService from "../project/ProjectEnrichmentService.ts";
2828import * as ProjectService from "../project/ProjectService.ts";
2929import type { ProviderInstance } from "../provider/ProviderDriver.ts";
......@@ -37,21 +37,17 @@ import * as EventSink from "./EventSink.ts";
3737import * as Orchestrator from "./Orchestrator.ts";
3838import type { ProviderAdapterV2Shape } from "./ProviderAdapter.ts";
3939import { continueRestartedRun } from "./RestartContinuation.ts";
40import {
41 OrchestrationV2EventSinkLayerLive,
42 OrchestrationV2LayerLive,
43 ProjectServiceLayerLive,
44} from "./runtimeLayer.ts";
45import { worktreeRepairDependenciesTestLayer } from "./ProviderTurnStartService.testkit.ts";
40import * as RuntimeLayer from "./runtimeLayer.ts";
41import * as ProviderTurnStartServiceTestkit from "./ProviderTurnStartService.testkit.ts";
4642
47const PlatformTestLayer = Layer.merge(
43const layerPlatformTest = Layer.merge(
4844 NodeServices.layer,
4945 Layer.mock(SourceControlProviderRegistry.SourceControlProviderRegistry)({
5046 resolveLink: () => Effect.die("unused title link"),
5147 }),
5248);
5349
54const ServerConfigLayer = ServerConfig.layerTest(process.cwd(), {
50const layerServerConfig = ServerConfig.layerTest(process.cwd(), {
5551 prefix: "t3-orchestration-v2-delegated-completion-",
5652});
5753
......@@ -60,14 +56,14 @@ const modelSelection = {
6056 model: "gpt-5.4",
6157} satisfies ModelSelection;
6258
63const VcsDriverRegistryTestLayer = VcsDriverRegistry.layer.pipe(
59const layerVcsDriverRegistryTest = VcsDriverRegistry.layer.pipe(
6460 Layer.provide(VcsProcess.layer),
65 Layer.provide(ServerConfigLayer),
66 Layer.provide(PlatformTestLayer),
61 Layer.provide(layerServerConfig),
62 Layer.provide(layerPlatformTest),
6763);
6864
69const CheckpointStoreTestLayer = CheckpointStore.layer.pipe(
70 Layer.provide(VcsDriverRegistryTestLayer),
65const layerCheckpointStoreTest = CheckpointStore.layer.pipe(
66 Layer.provide(layerVcsDriverRegistryTest),
7167);
7268
7369const driver = ProviderDriverKind.make("codex");
......@@ -93,7 +89,7 @@ const providerInstance = {
9389 textGeneration: {} as ProviderInstance["textGeneration"],
9490} satisfies ProviderInstance;
9591
96const TestProviderInstanceRegistry = Layer.succeed(
92const layerTestProviderInstanceRegistry = Layer.succeed(
9793 ProviderInstanceRegistry.ProviderInstanceRegistry,
9894 {
9995 getInstance: (instanceId) =>
......@@ -105,14 +101,14 @@ const TestProviderInstanceRegistry = Layer.succeed(
105101 },
106102);
107103
108const TestLayer = Layer.mergeAll(OrchestrationV2LayerLive, OrchestrationV2EventSinkLayerLive).pipe(
109 Layer.provideMerge(ProjectServiceLayerLive),
104const layerTest = Layer.mergeAll(RuntimeLayer.layer, RuntimeLayer.layerEventSink).pipe(
105 Layer.provideMerge(RuntimeLayer.layerProjectService),
110106 Layer.provide(
111107 Layer.mock(WorkspacePaths.WorkspacePaths)({
112108 normalizeWorkspaceRoot: (workspaceRoot) => Effect.succeed(workspaceRoot),
113109 }),
114110 ),
115 Layer.provide(worktreeRepairDependenciesTestLayer),
111 Layer.provide(ProviderTurnStartServiceTestkit.layer),
116112 Layer.provide(
117113 Layer.succeed(ProjectEnrichmentService.ProjectEnrichmentService, {
118114 peek: () =>
......@@ -133,12 +129,12 @@ const TestLayer = Layer.mergeAll(OrchestrationV2LayerLive, OrchestrationV2EventS
133129 }),
134130 ),
135131 Layer.provide(McpSessionRegistryTestkit.layer),
136 Layer.provide(SqlitePersistenceMemory),
137 Layer.provide(CheckpointStoreTestLayer),
138 Layer.provide(ServerConfigLayer),
132 Layer.provide(SqlitePersistence.layerMemory),
133 Layer.provide(layerCheckpointStoreTest),
134 Layer.provide(layerServerConfig),
139135 Layer.provide(ServerSettings.layerTest()),
140 Layer.provide(TestProviderInstanceRegistry),
141 Layer.provide(PlatformTestLayer),
136 Layer.provide(layerTestProviderInstanceRegistry),
137 Layer.provide(layerPlatformTest),
142138);
143139
144140const seedParentWithTerminalTask = (input: {
......@@ -292,7 +288,7 @@ const seedParentWithTerminalTask = (input: {
292288 });
293289 });
294290
295it.layer(TestLayer)("delegated completion delivery repairs", (it) => {
291it.layer(layerTest)("delegated completion delivery repairs", (it) => {
296292 it.effect("acceptance batches pending siblings without acknowledging their results", () =>
297293 Effect.gen(function* () {
298294 const orchestrator = yield* Orchestrator.OrchestratorV2;
......@@ -905,7 +901,7 @@ const seedRestartCancelledChild = (input: {
905901 return { taskId, childThreadId, childRunId };
906902 });
907903
908it.layer(TestLayer)("delegated tasks across a server restart", (it) => {
904it.layer(layerTest)("delegated tasks across a server restart", (it) => {
909905 it.effect("holds a restart-cancelled child for its continuation's result", () =>
910906 Effect.gen(function* () {
911907 const orchestrator = yield* Orchestrator.OrchestratorV2;
apps/server/src/orchestration-v2/EffectOutbox.ts+1-1
......@@ -690,7 +690,7 @@ export const layer: Layer.Layer<EffectOutboxV2, never, SqlClient.SqlClient> = La
690690);
691691
692692/** Prunes settled effect rows once the server is active, then every hour. */
693export const pruneWorkerLive = Layer.effectDiscard(
693export const layerPruneWorker = Layer.effectDiscard(
694694 Effect.gen(function* () {
695695 const outbox = yield* EffectOutboxV2;
696696 yield* forkParked(
apps/server/src/orchestration-v2/EffectWorker.test.ts+36-36
......@@ -76,14 +76,14 @@ function restartEffect(
7676 };
7777}
7878
79function makeExecutorLayer(input: {
79function layerExecutorFor(input: {
8080 readonly events: Ref.Ref<ReadonlyArray<string>>;
8181 readonly failFirstStart?: Ref.Ref<boolean>;
8282 readonly threads?: Partial<ThreadManagementService.ThreadManagementService["Service"]>;
8383 readonly continueAfterRestart?: boolean;
8484}) {
8585 const record = (event: string) => Ref.update(input.events, (events) => [...events, event]);
86 const dependencies = Layer.mergeAll(
86 const layerDependencies = Layer.mergeAll(
8787 Layer.succeed(
8888 ProviderTurnControlService.ProviderTurnControlServiceV2,
8989 ProviderTurnControlService.ProviderTurnControlServiceV2.of({
......@@ -146,10 +146,10 @@ function makeExecutorLayer(input: {
146146 }),
147147 ),
148148 );
149 return EffectWorker.executorLayer.pipe(
149 return EffectWorker.layerExecutor.pipe(
150150 Layer.provide(
151151 Layer.mergeAll(
152 dependencies,
152 layerDependencies,
153153 Layer.mock(ThreadManagementService.ThreadManagementService)(input.threads ?? {}),
154154 ServerSettings.layerTest(
155155 input.continueAfterRestart === true ? { continueThreadsAfterServerUpdate: true } : {},
......@@ -222,7 +222,7 @@ it.effect("requeues a claim when a pre-execution worker check fails", () =>
222222 }>
223223 >([]);
224224 const executionCount = yield* Ref.make(0);
225 const outboxLayer = Layer.mock(EffectOutbox.EffectOutboxV2)({
225 const layerOutbox = Layer.mock(EffectOutbox.EffectOutboxV2)({
226226 claimNext: () => Effect.succeed(Option.some(claimedEffect)),
227227 get: () =>
228228 Effect.fail(
......@@ -235,19 +235,19 @@ it.effect("requeues a claim when a pre-execution worker check fails", () =>
235235 retry: (input) =>
236236 Ref.update(retries, (existing) => [...existing, input]).pipe(Effect.as(true)),
237237 });
238 const executorLayer = Layer.succeed(
238 const layerExecutor = Layer.succeed(
239239 EffectWorker.OrchestrationEffectExecutorV2,
240240 EffectWorker.OrchestrationEffectExecutorV2.of({
241241 execute: () => Ref.update(executionCount, (count) => count + 1),
242242 }),
243243 );
244 const workerLayer = EffectWorker.layerWithOptions({ workerId }).pipe(
245 Layer.provide(Layer.merge(outboxLayer, executorLayer)),
244 const layerWorker = EffectWorker.layerWithOptions({ workerId }).pipe(
245 Layer.provide(Layer.merge(layerOutbox, layerExecutor)),
246246 );
247247
248248 const exit = yield* EffectWorker.OrchestrationEffectWorkerV2.pipe(
249249 Effect.flatMap((worker) => worker.runOnce),
250 Effect.provide(workerLayer),
250 Effect.provide(layerWorker),
251251 Effect.exit,
252252 );
253253
......@@ -289,7 +289,7 @@ it.effect("arms cancellation before the durable pre-execution check", () =>
289289 let cancellationArmed = false;
290290 const executionCount = yield* Ref.make(0);
291291 const settlementCount = yield* Ref.make(0);
292 const outboxLayer = Layer.mock(EffectOutbox.EffectOutboxV2)({
292 const layerOutbox = Layer.mock(EffectOutbox.EffectOutboxV2)({
293293 claimNext: () => Effect.succeed(Option.some(claimedEffect)),
294294 awaitCancellation: () => {
295295 cancellationArmed = true;
......@@ -308,20 +308,20 @@ it.effect("arms cancellation before the durable pre-execution check", () =>
308308 clearCancellation: () => Effect.void,
309309 succeed: () => Ref.update(settlementCount, (count) => count + 1).pipe(Effect.as(true)),
310310 });
311 const executorLayer = Layer.succeed(
311 const layerExecutor = Layer.succeed(
312312 EffectWorker.OrchestrationEffectExecutorV2,
313313 EffectWorker.OrchestrationEffectExecutorV2.of({
314314 execute: () =>
315315 Effect.yieldNow.pipe(Effect.andThen(Ref.update(executionCount, (count) => count + 1))),
316316 }),
317317 );
318 const workerLayer = EffectWorker.layerWithOptions({ workerId }).pipe(
319 Layer.provide(Layer.merge(outboxLayer, executorLayer)),
318 const layerWorker = EffectWorker.layerWithOptions({ workerId }).pipe(
319 Layer.provide(Layer.merge(layerOutbox, layerExecutor)),
320320 );
321321
322322 const exit = yield* EffectWorker.OrchestrationEffectWorkerV2.pipe(
323323 Effect.flatMap((worker) => worker.runOnce),
324 Effect.provide(workerLayer),
324 Effect.provide(layerWorker),
325325 Effect.exit,
326326 );
327327
......@@ -359,7 +359,7 @@ it.effect("terminalizes a process-bound claim when success settlement fails", ()
359359 const retries = yield* Ref.make(0);
360360 const terminalErrors = yield* Ref.make<ReadonlyArray<string>>([]);
361361 const executionCount = yield* Ref.make(0);
362 const outboxLayer = Layer.mock(EffectOutbox.EffectOutboxV2)({
362 const layerOutbox = Layer.mock(EffectOutbox.EffectOutboxV2)({
363363 claimNext: () => Effect.succeed(Option.some(claimedEffect)),
364364 get: () => Effect.succeed(Option.some(claimedEffect)),
365365 awaitCancellation: () => Effect.never,
......@@ -376,19 +376,19 @@ it.effect("terminalizes a process-bound claim when success settlement fails", ()
376376 fail: ({ error }) =>
377377 Ref.update(terminalErrors, (existing) => [...existing, error]).pipe(Effect.as(true)),
378378 });
379 const executorLayer = Layer.succeed(
379 const layerExecutor = Layer.succeed(
380380 EffectWorker.OrchestrationEffectExecutorV2,
381381 EffectWorker.OrchestrationEffectExecutorV2.of({
382382 execute: () => Ref.update(executionCount, (count) => count + 1),
383383 }),
384384 );
385 const workerLayer = EffectWorker.layerWithOptions({ workerId }).pipe(
386 Layer.provide(Layer.merge(outboxLayer, executorLayer)),
385 const layerWorker = EffectWorker.layerWithOptions({ workerId }).pipe(
386 Layer.provide(Layer.merge(layerOutbox, layerExecutor)),
387387 );
388388
389389 const exit = yield* EffectWorker.OrchestrationEffectWorkerV2.pipe(
390390 Effect.flatMap((worker) => worker.runOnce),
391 Effect.provide(workerLayer),
391 Effect.provide(layerWorker),
392392 Effect.exit,
393393 );
394394
......@@ -424,7 +424,7 @@ it.effect("requeues a replay-safe claim when success settlement fails", () =>
424424 };
425425 const retries = yield* Ref.make(0);
426426 const terminalizations = yield* Ref.make(0);
427 const outboxLayer = Layer.mock(EffectOutbox.EffectOutboxV2)({
427 const layerOutbox = Layer.mock(EffectOutbox.EffectOutboxV2)({
428428 claimNext: () => Effect.succeed(Option.some(claimedEffect)),
429429 get: () => Effect.succeed(Option.some(claimedEffect)),
430430 awaitCancellation: () => Effect.never,
......@@ -440,17 +440,17 @@ it.effect("requeues a replay-safe claim when success settlement fails", () =>
440440 retry: () => Ref.update(retries, (count) => count + 1).pipe(Effect.as(true)),
441441 fail: () => Ref.update(terminalizations, (count) => count + 1).pipe(Effect.as(true)),
442442 });
443 const executorLayer = Layer.succeed(
443 const layerExecutor = Layer.succeed(
444444 EffectWorker.OrchestrationEffectExecutorV2,
445445 EffectWorker.OrchestrationEffectExecutorV2.of({ execute: () => Effect.void }),
446446 );
447 const workerLayer = EffectWorker.layerWithOptions({ workerId }).pipe(
448 Layer.provide(Layer.merge(outboxLayer, executorLayer)),
447 const layerWorker = EffectWorker.layerWithOptions({ workerId }).pipe(
448 Layer.provide(Layer.merge(layerOutbox, layerExecutor)),
449449 );
450450
451451 const exit = yield* EffectWorker.OrchestrationEffectWorkerV2.pipe(
452452 Effect.flatMap((worker) => worker.runOnce),
453 Effect.provide(workerLayer),
453 Effect.provide(layerWorker),
454454 Effect.exit,
455455 );
456456
......@@ -485,7 +485,7 @@ it.effect("keeps a process-bound executor failure retryable when retry settlemen
485485 };
486486 const retryAttempts = yield* Ref.make(0);
487487 const terminalizations = yield* Ref.make(0);
488 const outboxLayer = Layer.mock(EffectOutbox.EffectOutboxV2)({
488 const layerOutbox = Layer.mock(EffectOutbox.EffectOutboxV2)({
489489 claimNext: () => Effect.succeed(Option.some(claimedEffect)),
490490 get: () => Effect.succeed(Option.some(claimedEffect)),
491491 awaitCancellation: () => Effect.never,
......@@ -506,7 +506,7 @@ it.effect("keeps a process-bound executor failure retryable when retry settlemen
506506 ),
507507 fail: () => Ref.update(terminalizations, (count) => count + 1).pipe(Effect.as(true)),
508508 });
509 const executorLayer = Layer.succeed(
509 const layerExecutor = Layer.succeed(
510510 EffectWorker.OrchestrationEffectExecutorV2,
511511 EffectWorker.OrchestrationEffectExecutorV2.of({
512512 execute: () =>
......@@ -519,13 +519,13 @@ it.effect("keeps a process-bound executor failure retryable when retry settlemen
519519 ),
520520 }),
521521 );
522 const workerLayer = EffectWorker.layerWithOptions({ workerId }).pipe(
523 Layer.provide(Layer.merge(outboxLayer, executorLayer)),
522 const layerWorker = EffectWorker.layerWithOptions({ workerId }).pipe(
523 Layer.provide(Layer.merge(layerOutbox, layerExecutor)),
524524 );
525525
526526 const exit = yield* EffectWorker.OrchestrationEffectWorkerV2.pipe(
527527 Effect.flatMap((worker) => worker.runOnce),
528 Effect.provide(workerLayer),
528 Effect.provide(layerWorker),
529529 Effect.exit,
530530 );
531531
......@@ -557,7 +557,7 @@ it.effect("keeps a max-attempt replay-safe failure terminal when fail settlement
557557 };
558558 const failAttempts = yield* Ref.make(0);
559559 const retries = yield* Ref.make(0);
560 const outboxLayer = Layer.mock(EffectOutbox.EffectOutboxV2)({
560 const layerOutbox = Layer.mock(EffectOutbox.EffectOutboxV2)({
561561 claimNext: () => Effect.succeed(Option.some(claimedEffect)),
562562 get: () => Effect.succeed(Option.some(claimedEffect)),
563563 awaitCancellation: () => Effect.never,
......@@ -578,7 +578,7 @@ it.effect("keeps a max-attempt replay-safe failure terminal when fail settlement
578578 ),
579579 retry: () => Ref.update(retries, (count) => count + 1).pipe(Effect.as(true)),
580580 });
581 const executorLayer = Layer.succeed(
581 const layerExecutor = Layer.succeed(
582582 EffectWorker.OrchestrationEffectExecutorV2,
583583 EffectWorker.OrchestrationEffectExecutorV2.of({
584584 execute: () =>
......@@ -591,13 +591,13 @@ it.effect("keeps a max-attempt replay-safe failure terminal when fail settlement
591591 ),
592592 }),
593593 );
594 const workerLayer = EffectWorker.layerWithOptions({ workerId, maxAttempts: 5 }).pipe(
595 Layer.provide(Layer.merge(outboxLayer, executorLayer)),
594 const layerWorker = EffectWorker.layerWithOptions({ workerId, maxAttempts: 5 }).pipe(
595 Layer.provide(Layer.merge(layerOutbox, layerExecutor)),
596596 );
597597
598598 const exit = yield* EffectWorker.OrchestrationEffectWorkerV2.pipe(
599599 Effect.flatMap((worker) => worker.runOnce),
600 Effect.provide(workerLayer),
600 Effect.provide(layerWorker),
601601 Effect.exit,
602602 );
603603
......@@ -734,7 +734,7 @@ it.effect("safely retries after replacement cleanup succeeds and start fails", (
734734 type: "replace",
735735 replacementProviderSessionId: replacementSessionId,
736736 });
737 const layer = makeExecutorLayer({ events, failFirstStart });
737 const layer = layerExecutorFor({ events, failFirstStart });
738738
739739 const first = yield* Effect.gen(function* () {
740740 const executor = yield* EffectWorker.OrchestrationEffectExecutorV2;
......@@ -763,7 +763,7 @@ it.effect("settles a delegated child once its restart continuation fails for goo
763763 const timestamp = DateTime.formatIso(yield* DateTime.now);
764764 const events = yield* Ref.make<ReadonlyArray<string>>([]);
765765 const recovered = yield* Ref.make<ReadonlyArray<ThreadId>>([]);
766 const layer = makeExecutorLayer({
766 const layer = layerExecutorFor({
767767 events,
768768 continueAfterRestart: true,
769769 threads: {
apps/server/src/orchestration-v2/EffectWorker.ts+2-2
......@@ -78,7 +78,7 @@ export class OrchestrationEffectExecutorV2 extends Context.Service<
7878 OrchestrationEffectExecutorV2Shape
7979>()("t3/orchestration-v2/EffectWorker/OrchestrationEffectExecutorV2") {}
8080
81export const executorLayer: Layer.Layer<
81export const layerExecutor: Layer.Layer<
8282 OrchestrationEffectExecutorV2,
8383 never,
8484 | ProviderSessionManager.ProviderSessionManagerV2
......@@ -846,6 +846,6 @@ export const runDaemonWithOptions = (options: OrchestrationEffectDaemonOptions =
846846
847847export const runDaemon = runDaemonWithOptions();
848848
849const daemonLayer: Layer.Layer<never, never, OrchestrationEffectWorkerV2> = Layer.effectDiscard(
849const layerDaemon: Layer.Layer<never, never, OrchestrationEffectWorkerV2> = Layer.effectDiscard(
850850 runDaemon.pipe(Effect.forkScoped),
851851);
apps/server/src/orchestration-v2/EventSink.ts+3-3
......@@ -190,7 +190,7 @@ export class EventSinkV2 extends Context.Service<EventSinkV2, EventSinkV2Shape>(
190190/**
191191 * IMPLEMENTATIONS
192192 */
193const baseLayer: Layer.Layer<
193const layerBase: Layer.Layer<
194194 EventSinkV2,
195195 never,
196196 | CommandReceiptStore.CommandReceiptStoreV2
......@@ -873,13 +873,13 @@ const baseLayer: Layer.Layer<
873873 * important because enqueue notifications are in-memory wakeups backed by the
874874 * durable SQL queue.
875875 */
876export const layerFromStores = baseLayer;
876export const layerFromStores = layerBase;
877877
878878export const layer: Layer.Layer<
879879 EventSinkV2,
880880 never,
881881 EventStore.EventStoreV2 | ProjectionStore.ProjectionStoreV2 | SqlClient.SqlClient
882> = baseLayer.pipe(
882> = layerBase.pipe(
883883 Layer.provide(
884884 Layer.mergeAll(
885885 CommandReceiptStore.layer,
apps/server/src/orchestration-v2/EventStore.ts+5-5
......@@ -13,7 +13,7 @@ import * as Schema from "effect/Schema";
1313import * as Stream from "effect/Stream";
1414import type * as SqlClient from "effect/sql/SqlClient";
1515
16import { OrchestrationEventStoreLive } from "../persistence/Layers/OrchestrationEventStore.ts";
16import * as OrchestrationEventStoreLayer from "../persistence/Layers/OrchestrationEventStore.ts";
1717import * as OrchestrationEventStore from "../persistence/Services/OrchestrationEventStore.ts";
1818
1919export class EventStoreAppendEventsError extends Schema.TaggedError<EventStoreAppendEventsError>()(
......@@ -79,7 +79,7 @@ export class EventStoreV2 extends Context.Service<EventStoreV2, EventStoreV2Shap
7979 "t3/orchestration-v2/EventStore/EventStoreV2",
8080) {}
8181
82const baseLayer: Layer.Layer<EventStoreV2, never, OrchestrationEventStore.OrchestrationEventStore> =
82const layerBase: Layer.Layer<EventStoreV2, never, OrchestrationEventStore.OrchestrationEventStore> =
8383 Layer.effect(
8484 EventStoreV2,
8585 Effect.gen(function* () {
......@@ -149,8 +149,8 @@ const baseLayer: Layer.Layer<EventStoreV2, never, OrchestrationEventStore.Orches
149149 }),
150150 );
151151
152export const layer: Layer.Layer<EventStoreV2, never, SqlClient.SqlClient> = baseLayer.pipe(
153 Layer.provide(OrchestrationEventStoreLive),
152export const layer: Layer.Layer<EventStoreV2, never, SqlClient.SqlClient> = layerBase.pipe(
153 Layer.provide(OrchestrationEventStoreLayer.layer),
154154);
155155
156export const layerFromOrchestrationEventStore = baseLayer;
156export const layerFromOrchestrationEventStore = layerBase;
apps/server/src/orchestration-v2/FoundationPersistence.test.ts+83-75
......@@ -43,7 +43,7 @@ import * as SqlClient from "effect/sql/SqlClient";
4343import * as Statement from "effect/sql/Statement";
4444
4545import { LIVE_STREAM_MAX_ITEMS, LiveStreamBufferError } from "./LiveStreamBudget.ts";
46import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts";
46import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts";
4747import { CodexProviderCapabilitiesV2 } from "./Adapters/CodexAdapterV2.ts";
4848import * as CommandReceiptStore from "./CommandReceiptStore.ts";
4949import * as EffectOutbox from "./EffectOutbox.ts";
......@@ -60,25 +60,31 @@ import * as TurnItemPositionStore from "./TurnItemPositionStore.ts";
6060const isLiveStreamBufferError = Schema.is(LiveStreamBufferError);
6161const encodeJson = Schema.encodeEffect(Schema.fromJsonString(Schema.Unknown));
6262
63const databaseLayer = SqlitePersistenceMemory;
64const eventStoreProvided = EventStore.layer.pipe(Layer.provideMerge(databaseLayer));
65const projectionStoreProvided = ProjectionStore.layer.pipe(Layer.provideMerge(databaseLayer));
66const storesProvided = Layer.mergeAll(databaseLayer, eventStoreProvided, projectionStoreProvided);
67const eventSinkProvided = EventSink.layer.pipe(Layer.provide(storesProvided));
68const effectOutboxProvided = EffectOutbox.layer.pipe(Layer.provide(databaseLayer));
69const commandReceiptStoreProvided = CommandReceiptStore.layer.pipe(Layer.provide(databaseLayer));
63const layerDatabase = SqlitePersistence.layerMemory;
64const layerEventStoreProvided = EventStore.layer.pipe(Layer.provideMerge(layerDatabase));
65const layerProjectionStoreProvided = ProjectionStore.layer.pipe(Layer.provideMerge(layerDatabase));
66const layerStoresProvided = Layer.mergeAll(
67 layerDatabase,
68 layerEventStoreProvided,
69 layerProjectionStoreProvided,
70);
71const layerEventSinkProvided = EventSink.layer.pipe(Layer.provide(layerStoresProvided));
72const layerEffectOutboxProvided = EffectOutbox.layer.pipe(Layer.provide(layerDatabase));
73const layerCommandReceiptStoreProvided = CommandReceiptStore.layer.pipe(
74 Layer.provide(layerDatabase),
75);
7076// Its own database, for tests that act on every row in the outbox table.
71const isolatedOutboxLayer = Layer.fresh(EffectOutbox.layer.pipe(Layer.provideMerge(databaseLayer)));
72const projectionMaintenanceProvided = ProjectionMaintenance.layer.pipe(
73 Layer.provide(storesProvided),
77const layerIsolatedOutbox = Layer.fresh(EffectOutbox.layer.pipe(Layer.provideMerge(layerDatabase)));
78const layerProjectionMaintenanceProvided = ProjectionMaintenance.layer.pipe(
79 Layer.provide(layerStoresProvided),
7480);
75const TestLayer = Layer.mergeAll(
76 storesProvided,
77 eventSinkProvided,
78 effectOutboxProvided,
79 commandReceiptStoreProvided,
81const layerTest = Layer.mergeAll(
82 layerStoresProvided,
83 layerEventSinkProvided,
84 layerEffectOutboxProvided,
85 layerCommandReceiptStoreProvided,
8086 IdAllocator.layer,
81 projectionMaintenanceProvided,
87 layerProjectionMaintenanceProvided,
8288);
8389
8490const providerInstanceId = ProviderInstanceId.make("codex");
......@@ -156,7 +162,7 @@ it.effect("rebuilds event history one bounded page at a time", () =>
156162 let applied = 0;
157163 let failAfterFirstPage = false;
158164 const appliedAtRead: Array<number> = [];
159 const observedStores = Layer.mergeAll(
165 const layerObservedStores = Layer.mergeAll(
160166 Layer.succeed(EventStore.EventStoreV2, {
161167 ...eventStore,
162168 read: (input) =>
......@@ -186,7 +192,9 @@ it.effect("rebuilds event history one bounded page at a time", () =>
186192 const maintenance = yield* ProjectionMaintenance.ProjectionMaintenanceV2;
187193 return yield* maintenance.rebuild;
188194 }).pipe(
189 Effect.provide(Layer.fresh(ProjectionMaintenance.layer.pipe(Layer.provide(observedStores)))),
195 Effect.provide(
196 Layer.fresh(ProjectionMaintenance.layer.pipe(Layer.provide(layerObservedStores))),
197 ),
190198 );
191199 const rebuilt = yield* rebuild;
192200 assert.isTrue(rebuilt.valid);
......@@ -207,7 +215,7 @@ it.effect("rebuilds event history one bounded page at a time", () =>
207215 );
208216 const maintenance = yield* ProjectionMaintenance.ProjectionMaintenanceV2;
209217 assert.isTrue((yield* maintenance.verify).valid);
210 }).pipe(Effect.provide(TestLayer)),
218 }).pipe(Effect.provide(layerTest)),
211219);
212220
213221it.effect("verifies thread membership using only the thread-created partial index", () =>
......@@ -253,7 +261,7 @@ it.effect("verifies thread membership using only the thread-created partial inde
253261 const details = plan.map((row) => row.detail).join("\n");
254262 assert.match(details, /USING (?:COVERING )?INDEX orchestration_events_v2_created_threads_idx/);
255263 assert.notMatch(details, /idx_orch_events_stream_sequence|TEMP B-TREE/);
256 }).pipe(Effect.provide(TestLayer)),
264 }).pipe(Effect.provide(layerTest)),
257265);
258266
259267it.effect("keeps other database work runnable while discovering compaction candidates", () =>
......@@ -339,10 +347,10 @@ it.effect("keeps other database work runnable while discovering compaction candi
339347 assert.isBelow(result.queriesAtProbe, queries[result.table]);
340348 }
341349 }),
342 ).pipe(Effect.provide(TestLayer)),
350 ).pipe(Effect.provide(layerTest)),
343351);
344352
345it.layer(TestLayer)("orchestration V2 foundation persistence", (it) => {
353it.layer(layerTest)("orchestration V2 foundation persistence", (it) => {
346354 it.effect("projects oversized tool bodies before both replay and live RPC retention", () =>
347355 Effect.scoped(
348356 Effect.gen(function* () {
......@@ -1448,22 +1456,22 @@ it.layer(TestLayer)("orchestration V2 foundation persistence", (it) => {
14481456 assert.lengthOf(yield* outbox.listByCommandId(commandId), 1);
14491457
14501458 const executionCount = yield* Ref.make(0);
1451 const executorLayer = Layer.succeed(
1459 const layerExecutor = Layer.succeed(
14521460 EffectWorker.OrchestrationEffectExecutorV2,
14531461 EffectWorker.OrchestrationEffectExecutorV2.of({
14541462 execute: () => Ref.update(executionCount, (count) => count + 1),
14551463 }),
14561464 );
1457 const workerLayer = EffectWorker.layerWithOptions({ workerId: "recovery-worker" }).pipe(
1465 const layerWorker = EffectWorker.layerWithOptions({ workerId: "recovery-worker" }).pipe(
14581466 Layer.provide(
1459 Layer.merge(Layer.succeed(EffectOutbox.EffectOutboxV2, outbox), executorLayer),
1467 Layer.merge(Layer.succeed(EffectOutbox.EffectOutboxV2, outbox), layerExecutor),
14601468 ),
14611469 );
14621470 yield* Effect.gen(function* () {
14631471 const worker = yield* EffectWorker.OrchestrationEffectWorkerV2;
14641472 assert.isTrue(yield* worker.runOnce);
14651473 assert.isFalse(yield* worker.runOnce);
1466 }).pipe(Effect.provide(workerLayer));
1474 }).pipe(Effect.provide(layerWorker));
14671475
14681476 assert.equal(yield* Ref.get(executionCount), 1);
14691477 const storedEffect = yield* outbox.get(effect.id);
......@@ -1510,7 +1518,7 @@ it.layer(TestLayer)("orchestration V2 foundation persistence", (it) => {
15101518 const unexpectedWake = yield* outbox.awaitAvailable.pipe(Effect.forkChild);
15111519 yield* Effect.yieldNow;
15121520 assert.isUndefined(unexpectedWake.pollUnsafe());
1513 }).pipe(Effect.provide(Layer.fresh(TestLayer))),
1521 }).pipe(Effect.provide(Layer.fresh(layerTest))),
15141522 );
15151523
15161524 it.effect("does not publish a stale provider start after an interrupt wins", () =>
......@@ -1861,7 +1869,7 @@ it.layer(TestLayer)("orchestration V2 foundation persistence", (it) => {
18611869 },
18621870 ]);
18631871
1864 const executorLayer = Layer.succeed(
1872 const layerExecutor = Layer.succeed(
18651873 EffectWorker.OrchestrationEffectExecutorV2,
18661874 EffectWorker.OrchestrationEffectExecutorV2.of({
18671875 execute: () =>
......@@ -1873,11 +1881,11 @@ it.layer(TestLayer)("orchestration V2 foundation persistence", (it) => {
18731881 ),
18741882 }),
18751883 );
1876 const workerLayer = EffectWorker.layerWithOptions({
1884 const layerWorker = EffectWorker.layerWithOptions({
18771885 workerId: "cancellation-worker",
18781886 }).pipe(
18791887 Layer.provide(
1880 Layer.merge(Layer.succeed(EffectOutbox.EffectOutboxV2, outbox), executorLayer),
1888 Layer.merge(Layer.succeed(EffectOutbox.EffectOutboxV2, outbox), layerExecutor),
18811889 ),
18821890 );
18831891
......@@ -1894,7 +1902,7 @@ it.layer(TestLayer)("orchestration V2 foundation persistence", (it) => {
18941902 yield* outbox.signalCancellations(cancelledEffectIds);
18951903 assert.isTrue(yield* Fiber.join(workerFiber));
18961904 yield* Deferred.await(interrupted);
1897 }).pipe(Effect.provide(workerLayer));
1905 }).pipe(Effect.provide(layerWorker));
18981906
18991907 const cancelled = yield* outbox.get(effectId);
19001908 assert.isTrue(Option.isSome(cancelled));
......@@ -1923,7 +1931,7 @@ it.layer(TestLayer)("orchestration V2 foundation persistence", (it) => {
19231931 completedAt: null,
19241932 lastError: null,
19251933 };
1926 const outboxLayer = Layer.mock(EffectOutbox.EffectOutboxV2)({
1934 const layerOutbox = Layer.mock(EffectOutbox.EffectOutboxV2)({
19271935 claimNext: () => Effect.succeed(Option.some(claimedEffect)),
19281936 awaitCancellation: () => Effect.never,
19291937 clearCancellation: () => Effect.void,
......@@ -1939,18 +1947,18 @@ it.layer(TestLayer)("orchestration V2 foundation persistence", (it) => {
19391947 }),
19401948 ),
19411949 });
1942 const executorLayer = Layer.succeed(
1950 const layerExecutor = Layer.succeed(
19431951 EffectWorker.OrchestrationEffectExecutorV2,
19441952 EffectWorker.OrchestrationEffectExecutorV2.of({ execute: () => Effect.void }),
19451953 );
1946 const workerLayer = EffectWorker.layerWithOptions({
1954 const layerWorker = EffectWorker.layerWithOptions({
19471955 workerId: "settlement-race-worker",
1948 }).pipe(Layer.provide(Layer.merge(outboxLayer, executorLayer)));
1956 }).pipe(Layer.provide(Layer.merge(layerOutbox, layerExecutor)));
19491957
19501958 assert.isTrue(
19511959 yield* EffectWorker.OrchestrationEffectWorkerV2.pipe(
19521960 Effect.flatMap((worker) => worker.runOnce),
1953 Effect.provide(workerLayer),
1961 Effect.provide(layerWorker),
19541962 ),
19551963 );
19561964 }),
......@@ -1978,7 +1986,7 @@ it.layer(TestLayer)("orchestration V2 foundation persistence", (it) => {
19781986 lastError: null,
19791987 };
19801988 const executionCount = yield* Ref.make(0);
1981 const outboxLayer = Layer.mock(EffectOutbox.EffectOutboxV2)({
1989 const layerOutbox = Layer.mock(EffectOutbox.EffectOutboxV2)({
19821990 claimNext: () => Effect.succeed(Option.some(claimedEffect)),
19831991 get: () =>
19841992 Effect.succeed(
......@@ -1993,20 +2001,20 @@ it.layer(TestLayer)("orchestration V2 foundation persistence", (it) => {
19932001 clearCancellation: () => Effect.void,
19942002 awaitCancellation: () => Effect.never,
19952003 });
1996 const executorLayer = Layer.succeed(
2004 const layerExecutor = Layer.succeed(
19972005 EffectWorker.OrchestrationEffectExecutorV2,
19982006 EffectWorker.OrchestrationEffectExecutorV2.of({
19992007 execute: () => Ref.update(executionCount, (count) => count + 1),
20002008 }),
20012009 );
2002 const workerLayer = EffectWorker.layerWithOptions({
2010 const layerWorker = EffectWorker.layerWithOptions({
20032011 workerId: "claim-cancellation-worker",
2004 }).pipe(Layer.provide(Layer.merge(outboxLayer, executorLayer)));
2012 }).pipe(Layer.provide(Layer.merge(layerOutbox, layerExecutor)));
20052013
20062014 assert.isTrue(
20072015 yield* EffectWorker.OrchestrationEffectWorkerV2.pipe(
20082016 Effect.flatMap((worker) => worker.runOnce),
2009 Effect.provide(workerLayer),
2017 Effect.provide(layerWorker),
20102018 ),
20112019 );
20122020 assert.equal(yield* Ref.get(executionCount), 0);
......@@ -2275,7 +2283,7 @@ it.layer(TestLayer)("orchestration V2 foundation persistence", (it) => {
22752283 ["failed-old:1", "pending:1", "running:1", "succeeded-recent:1"],
22762284 );
22772285 assert.equal(yield* outbox.pruneSettled, 0);
2278 }).pipe(Effect.provide(isolatedOutboxLayer)),
2286 }).pipe(Effect.provide(layerIsolatedOutbox)),
22792287 );
22802288
22812289 it.effect("prunes settled effects hourly from the layer-owned worker", () =>
......@@ -2303,7 +2311,7 @@ it.layer(TestLayer)("orchestration V2 foundation persistence", (it) => {
23032311 Duration.subtract(EffectOutbox.SETTLED_EFFECT_RETENTION, Duration.minutes(30)),
23042312 );
23052313 yield* Layer.build(
2306 EffectOutbox.pruneWorkerLive.pipe(
2314 EffectOutbox.layerPruneWorker.pipe(
23072315 Layer.provide(Layer.succeed(EffectOutbox.EffectOutboxV2, observed)),
23082316 ),
23092317 );
......@@ -2313,7 +2321,7 @@ it.layer(TestLayer)("orchestration V2 foundation persistence", (it) => {
23132321 yield* TestClock.adjust("1 hour");
23142322 assert.equal(yield* Queue.take(runs), 1);
23152323 assert.deepEqual(yield* ids, ["effect:prune-worker:pending"]);
2316 }).pipe(Effect.scoped, Effect.provide(isolatedOutboxLayer)),
2324 }).pipe(Effect.scoped, Effect.provide(layerIsolatedOutbox)),
23172325 );
23182326
23192327 it.effect("does not emit a SQL span for an empty safety claim", () =>
......@@ -2338,7 +2346,7 @@ it.layer(TestLayer)("orchestration V2 foundation persistence", (it) => {
23382346
23392347 assert.isTrue(Option.isNone(claim));
23402348 assert.notInclude(spans, "sql.execute");
2341 }).pipe(Effect.provide(Layer.fresh(effectOutboxProvided))),
2349 }).pipe(Effect.provide(Layer.fresh(layerEffectOutboxProvided))),
23422350 );
23432351
23442352 it.effect("wakes claimers when cancellation unblocks same-thread work", () =>
......@@ -2395,7 +2403,7 @@ it.layer(TestLayer)("orchestration V2 foundation persistence", (it) => {
23952403 workerId: "cancellation-wakeup-worker",
23962404 });
23972405 }
2398 }).pipe(Effect.provide(Layer.fresh(effectOutboxProvided))),
2406 }).pipe(Effect.provide(Layer.fresh(layerEffectOutboxProvided))),
23992407 );
24002408
24012409 it.effect("keeps later thread effects behind an earlier effect waiting to retry", () =>
......@@ -2465,7 +2473,7 @@ it.layer(TestLayer)("orchestration V2 foundation persistence", (it) => {
24652473 assert.equal(Option.getOrUndefined(unblocked)?.id, "effect:foundation-retry-order:a-start");
24662474 yield* outbox.succeed({ effectId: "effect:foundation-retry-order:a-start", workerId });
24672475 yield* outbox.succeed({ effectId: "effect:foundation-retry-order:b-title", workerId });
2468 }).pipe(Effect.provide(Layer.fresh(effectOutboxProvided))),
2476 }).pipe(Effect.provide(Layer.fresh(layerEffectOutboxProvided))),
24692477 );
24702478
24712479 it.effect("executes a retry at its durable deadline instead of the liveness interval", () =>
......@@ -2476,7 +2484,7 @@ it.layer(TestLayer)("orchestration V2 foundation persistence", (it) => {
24762484 const threadId = ThreadId.make("thread:foundation-durable-retry-deadline");
24772485 const executions = yield* Ref.make(0);
24782486 const completed = yield* Deferred.make<void>();
2479 const executorLayer = Layer.succeed(
2487 const layerExecutor = Layer.succeed(
24802488 EffectWorker.OrchestrationEffectExecutorV2,
24812489 EffectWorker.OrchestrationEffectExecutorV2.of({
24822490 execute: () =>
......@@ -2493,11 +2501,11 @@ it.layer(TestLayer)("orchestration V2 foundation persistence", (it) => {
24932501 }),
24942502 }),
24952503 );
2496 const workerLayer = EffectWorker.layerWithOptions({
2504 const layerWorker = EffectWorker.layerWithOptions({
24972505 workerId: "durable-retry-deadline-worker",
24982506 }).pipe(
24992507 Layer.provide(
2500 Layer.merge(Layer.succeed(EffectOutbox.EffectOutboxV2, outbox), executorLayer),
2508 Layer.merge(Layer.succeed(EffectOutbox.EffectOutboxV2, outbox), layerExecutor),
25012509 ),
25022510 );
25032511
......@@ -2531,7 +2539,7 @@ it.layer(TestLayer)("orchestration V2 foundation persistence", (it) => {
25312539 yield* TestClock.adjust("1 millis");
25322540 yield* Deferred.await(completed);
25332541 assert.equal(yield* Ref.get(executions), 2);
2534 }).pipe(Effect.provide(workerLayer), Effect.scoped);
2542 }).pipe(Effect.provide(layerWorker), Effect.scoped);
25352543 }).pipe(Effect.provide(TestClock.layer())),
25362544 );
25372545
......@@ -2555,7 +2563,7 @@ it.layer(TestLayer)("orchestration V2 foundation persistence", (it) => {
25552563 [effectA2, { started: startedA2, release: releaseA2 }],
25562564 [effectB1, { started: startedB1, release: releaseB1 }],
25572565 ]);
2558 const executorLayer = Layer.succeed(
2566 const layerExecutor = Layer.succeed(
25592567 EffectWorker.OrchestrationEffectExecutorV2,
25602568 EffectWorker.OrchestrationEffectExecutorV2.of({
25612569 execute: (effect) => {
......@@ -2567,11 +2575,11 @@ it.layer(TestLayer)("orchestration V2 foundation persistence", (it) => {
25672575 },
25682576 }),
25692577 );
2570 const workerLayer = EffectWorker.layerWithOptions({
2578 const layerWorker = EffectWorker.layerWithOptions({
25712579 workerId: "concurrency-worker",
25722580 }).pipe(
25732581 Layer.provide(
2574 Layer.merge(Layer.succeed(EffectOutbox.EffectOutboxV2, outbox), executorLayer),
2582 Layer.merge(Layer.succeed(EffectOutbox.EffectOutboxV2, outbox), layerExecutor),
25752583 ),
25762584 );
25772585
......@@ -2625,7 +2633,7 @@ it.layer(TestLayer)("orchestration V2 foundation persistence", (it) => {
26252633 );
26262634 if (!settled) yield* Effect.yieldNow;
26272635 }
2628 }).pipe(Effect.provide(workerLayer), Effect.scoped);
2636 }).pipe(Effect.provide(layerWorker), Effect.scoped);
26292637 }),
26302638 );
26312639
......@@ -2655,7 +2663,7 @@ it.layer(TestLayer)("orchestration V2 foundation persistence", (it) => {
26552663 },
26562664 ]);
26572665
2658 const executorLayer = Layer.succeed(
2666 const layerExecutor = Layer.succeed(
26592667 EffectWorker.OrchestrationEffectExecutorV2,
26602668 EffectWorker.OrchestrationEffectExecutorV2.of({
26612669 execute: (effect) =>
......@@ -2670,12 +2678,12 @@ it.layer(TestLayer)("orchestration V2 foundation persistence", (it) => {
26702678 ),
26712679 }),
26722680 );
2673 const workerLayer = EffectWorker.layerWithOptions({
2681 const layerWorker = EffectWorker.layerWithOptions({
26742682 workerId: "no-live-reclaim-worker",
26752683 leaseDurationMs: 1,
26762684 }).pipe(
26772685 Layer.provide(
2678 Layer.merge(Layer.succeed(EffectOutbox.EffectOutboxV2, outbox), executorLayer),
2686 Layer.merge(Layer.succeed(EffectOutbox.EffectOutboxV2, outbox), layerExecutor),
26792687 ),
26802688 );
26812689
......@@ -2696,7 +2704,7 @@ it.layer(TestLayer)("orchestration V2 foundation persistence", (it) => {
26962704 assert.isTrue(yield* Fiber.join(firstFiber));
26972705 assert.isTrue(yield* worker.runOnce);
26982706 assert.deepEqual(yield* Ref.get(executions), [firstEffectId, secondEffectId]);
2699 }).pipe(Effect.provide(workerLayer));
2707 }).pipe(Effect.provide(layerWorker));
27002708 }),
27012709 );
27022710
......@@ -3348,7 +3356,7 @@ it.live("keeps claiming new work after repeated idle periods", () =>
33483356 Effect.gen(function* () {
33493357 const outbox = yield* EffectOutbox.EffectOutboxV2;
33503358 const completed = new Map<string, Deferred.Deferred<void>>();
3351 const executorLayer = Layer.succeed(
3359 const layerExecutor = Layer.succeed(
33523360 EffectWorker.OrchestrationEffectExecutorV2,
33533361 EffectWorker.OrchestrationEffectExecutorV2.of({
33543362 execute: (effect) => {
......@@ -3359,10 +3367,10 @@ it.live("keeps claiming new work after repeated idle periods", () =>
33593367 },
33603368 }),
33613369 );
3362 const workerLayer = EffectWorker.layerWithOptions({
3370 const layerWorker = EffectWorker.layerWithOptions({
33633371 workerId: "idle-wave-worker",
33643372 }).pipe(
3365 Layer.provide(Layer.merge(Layer.succeed(EffectOutbox.EffectOutboxV2, outbox), executorLayer)),
3373 Layer.provide(Layer.merge(Layer.succeed(EffectOutbox.EffectOutboxV2, outbox), layerExecutor)),
33663374 );
33673375
33683376 yield* Effect.gen(function* () {
......@@ -3384,8 +3392,8 @@ it.live("keeps claiming new work after repeated idle periods", () =>
33843392 const observed = yield* Deferred.await(completion).pipe(Effect.timeoutOption("2 seconds"));
33853393 assert.isTrue(Option.isSome(observed), `worker stopped before idle wave ${wave}`);
33863394 }
3387 }).pipe(Effect.provide(workerLayer), Effect.scoped);
3388 }).pipe(Effect.provide(TestLayer)),
3395 }).pipe(Effect.provide(layerWorker), Effect.scoped);
3396 }).pipe(Effect.provide(layerTest)),
33893397);
33903398
33913399it.effect("publishes live events in commit order across concurrent writers", () =>
......@@ -3394,7 +3402,7 @@ it.effect("publishes live events in commit order across concurrent writers", ()
33943402 const releaseFirst = yield* Deferred.make<void>();
33953403 // The first writer's post-commit wakeup stands in for any scheduler yield
33963404 // between its commit and its publish.
3397 const pausingOutbox = Layer.effect(
3405 const layerPausingOutbox = Layer.effect(
33983406 EffectOutbox.EffectOutboxV2,
33993407 Effect.gen(function* () {
34003408 const delegate = yield* EffectOutbox.EffectOutboxV2;
......@@ -3407,15 +3415,15 @@ it.effect("publishes live events in commit order across concurrent writers", ()
34073415 ),
34083416 });
34093417 }),
3410 ).pipe(Layer.provide(effectOutboxProvided));
3411 const eventSinkLayer = EventSink.layerFromStores.pipe(
3418 ).pipe(Layer.provide(layerEffectOutboxProvided));
3419 const layerEventSink = EventSink.layerFromStores.pipe(
34123420 Layer.provide(
34133421 Layer.mergeAll(
3414 storesProvided,
3415 pausingOutbox,
3416 commandReceiptStoreProvided,
3417 ProjectStore.layer.pipe(Layer.provide(databaseLayer)),
3418 TurnItemPositionStore.layer.pipe(Layer.provide(databaseLayer)),
3422 layerStoresProvided,
3423 layerPausingOutbox,
3424 layerCommandReceiptStoreProvided,
3425 ProjectStore.layer.pipe(Layer.provide(layerDatabase)),
3426 TurnItemPositionStore.layer.pipe(Layer.provide(layerDatabase)),
34193427 ),
34203428 ),
34213429 );
......@@ -3471,6 +3479,6 @@ it.effect("publishes live events in commit order across concurrent writers", ()
34713479 sequences,
34723480 [...sequences].sort((left, right) => left - right),
34733481 );
3474 }).pipe(Effect.provide(eventSinkLayer));
3475 }).pipe(Effect.provide(databaseLayer)),
3482 }).pipe(Effect.provide(layerEventSink));
3483 }).pipe(Effect.provide(layerDatabase)),
34763484);
apps/server/src/orchestration-v2/GrokOrchestratorV2.live.test.ts+32-32
......@@ -21,12 +21,12 @@ import * as CheckpointStore from "../checkpointing/CheckpointStore.ts";
2121import * as BackgroundPolicy from "../background/BackgroundPolicy.ts";
2222import * as HostPowerMonitor from "../background/HostPowerMonitor.ts";
2323import * as ServerConfig from "../config.ts";
24import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts";
24import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts";
2525import * as AntigravityInstallation from "../provider/AntigravityInstallation.ts";
2626import * as CodexInstallation from "../provider/CodexInstallation.ts";
2727import * as ServerEnvironment from "../environment/ServerEnvironment.ts";
2828import * as ModelManifest from "../provider/ModelManifest.ts";
29import { ProviderInstanceRegistryHydrationLive } from "../provider/Layers/ProviderInstanceRegistryHydration.ts";
29import * as ProviderInstanceRegistryHydration from "../provider/Layers/ProviderInstanceRegistryHydration.ts";
3030import * as ProviderEventLoggers from "../provider/Layers/ProviderEventLoggers.ts";
3131import * as OpenCodeRuntime from "../provider/opencodeRuntime.ts";
3232import * as OpenCodeServerLedger from "../provider/OpenCodeServerLedger.ts";
......@@ -34,53 +34,53 @@ import * as ServerSettings from "../serverSettings.ts";
3434import * as VcsDriverRegistry from "../vcs/VcsDriverRegistry.ts";
3535import * as VcsProcess from "../vcs/VcsProcess.ts";
3636import * as Orchestrator from "./Orchestrator.ts";
37import { worktreeRepairDependenciesTestLayer } from "./ProviderTurnStartService.testkit.ts";
38import { OrchestrationV2LayerLive } from "./runtimeLayer.ts";
37import * as ProviderTurnStartServiceTestkit from "./ProviderTurnStartService.testkit.ts";
38import * as RuntimeLayer from "./runtimeLayer.ts";
3939import * as McpSessionRegistryTestkit from "../mcp/McpSessionRegistry.testkit.ts";
4040import { GROK_MODEL_SELECTION } from "./testkit/fixtures/shared.ts";
4141
42const PlatformTestLayer = Layer.merge(
42const layerPlatformTest = Layer.merge(
4343 NodeServices.layer,
4444 Layer.mock(SourceControlProviderRegistry.SourceControlProviderRegistry)({
4545 resolveLink: () => Effect.die("unused title link"),
4646 }),
4747);
4848
49const serverConfigLayer = ServerConfig.layerTest(process.cwd(), {
49const layerServerConfig = ServerConfig.layerTest(process.cwd(), {
5050 prefix: "t3-grok-v2-live-",
5151});
5252
53const vcsDriverRegistryLayer = VcsDriverRegistry.layer.pipe(
53const layerVcsDriverRegistry = VcsDriverRegistry.layer.pipe(
5454 Layer.provide(VcsProcess.layer),
55 Layer.provide(serverConfigLayer),
56 Layer.provide(PlatformTestLayer),
55 Layer.provide(layerServerConfig),
56 Layer.provide(layerPlatformTest),
5757);
5858
59const checkpointStoreLayer = CheckpointStore.layer.pipe(Layer.provide(vcsDriverRegistryLayer));
59const layerCheckpointStore = CheckpointStore.layer.pipe(Layer.provide(layerVcsDriverRegistry));
6060
61const serverSettingsLayer = ServerSettings.layerTest({
61const layerServerSettings = ServerSettings.layerTest({
6262 providers: {
6363 grok: { enabled: true },
6464 },
6565});
66const backgroundPolicyLayer = BackgroundPolicy.layer.pipe(
66const layerBackgroundPolicy = BackgroundPolicy.layer.pipe(
6767 Layer.provide(Layer.effect(HostPowerMonitor.HostPowerMonitor, HostPowerMonitor.make())),
68 Layer.provide(serverSettingsLayer),
68 Layer.provide(layerServerSettings),
6969);
70const providerInstanceRegistryLayer = ProviderInstanceRegistryHydrationLive.pipe(
70const layerProviderInstanceRegistry = ProviderInstanceRegistryHydration.layer.pipe(
7171 Layer.provide(
7272 Layer.mergeAll(
73 serverConfigLayer.pipe(Layer.provide(PlatformTestLayer)),
74 serverSettingsLayer,
73 layerServerConfig.pipe(Layer.provide(layerPlatformTest)),
74 layerServerSettings,
7575 ServerSecretStore.layer.pipe(
76 Layer.provide(serverConfigLayer),
77 Layer.provide(PlatformTestLayer),
76 Layer.provide(layerServerConfig),
77 Layer.provide(layerPlatformTest),
7878 ),
7979 NodeServices.layer,
8080 FetchHttpClient.layer,
81 OpenCodeRuntime.OpenCodeRuntimeLive.pipe(
81 OpenCodeRuntime.layer.pipe(
8282 Layer.provide(OpenCodeServerLedger.layerTest),
83 Layer.provide(PlatformTestLayer),
83 Layer.provide(layerPlatformTest),
8484 ),
8585 Layer.succeed(
8686 ProviderEventLoggers.ProviderEventLoggers,
......@@ -88,9 +88,9 @@ const providerInstanceRegistryLayer = ProviderInstanceRegistryHydrationLive.pipe
8888 ),
8989 ModelManifest.layerTest,
9090 AntigravityInstallation.AntigravityInstallation.layer.pipe(
91 Layer.provide(serverConfigLayer.pipe(Layer.provide(PlatformTestLayer))),
91 Layer.provide(layerServerConfig.pipe(Layer.provide(layerPlatformTest))),
9292 Layer.provide(FetchHttpClient.layer),
93 Layer.provide(PlatformTestLayer),
93 Layer.provide(layerPlatformTest),
9494 ),
9595 // The Codex driver now resolves managed ChatGPT installs; these runs never launch Codex.
9696 Layer.mock(CodexInstallation.CodexInstallation)({
......@@ -105,17 +105,17 @@ const providerInstanceRegistryLayer = ProviderInstanceRegistryHydrationLive.pipe
105105 ),
106106);
107107
108const liveLayer = OrchestrationV2LayerLive.pipe(
109 Layer.provide(worktreeRepairDependenciesTestLayer),
108const layerLive = RuntimeLayer.layer.pipe(
109 Layer.provide(ProviderTurnStartServiceTestkit.layer),
110110 Layer.provide(McpSessionRegistryTestkit.layer),
111 Layer.provide(SqlitePersistenceMemory),
112 Layer.provide(checkpointStoreLayer),
113 Layer.provide(serverConfigLayer),
114 Layer.provide(serverSettingsLayer),
115 Layer.provide(providerInstanceRegistryLayer),
111 Layer.provide(SqlitePersistence.layerMemory),
112 Layer.provide(layerCheckpointStore),
113 Layer.provide(layerServerConfig),
114 Layer.provide(layerServerSettings),
115 Layer.provide(layerProviderInstanceRegistry),
116116 Layer.provide(ResetCreditCoordinator.layer),
117 Layer.provide(backgroundPolicyLayer),
118 Layer.provide(PlatformTestLayer),
117 Layer.provide(layerBackgroundPolicy),
118 Layer.provide(layerPlatformTest),
119119);
120120
121121const waitForIdle = Effect.fn("GrokOrchestratorV2Live.waitForIdle")(function* (threadId: ThreadId) {
......@@ -230,7 +230,7 @@ describe.runIf(process.env.T3_GROK_LIVE_ORCHESTRATOR === "1")("Grok V2 live orch
230230 );
231231 assert.include(targetProjection.contextHandoffs[0]?.summaryText ?? "", marker);
232232 assert.include(assistantText(targetProjection), marker);
233 }).pipe(Effect.provide(liveLayer), Effect.scoped),
233 }).pipe(Effect.provide(layerLive), Effect.scoped),
234234 360_000,
235235 );
236236});
apps/server/src/orchestration-v2/OpenCode2OrchestratorV2.live.test.ts+44-44
......@@ -48,12 +48,12 @@ import * as CheckpointStore from "../checkpointing/CheckpointStore.ts";
4848import * as BackgroundPolicy from "../background/BackgroundPolicy.ts";
4949import * as HostPowerMonitor from "../background/HostPowerMonitor.ts";
5050import * as ServerConfig from "../config.ts";
51import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts";
51import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts";
5252import * as AntigravityInstallation from "../provider/AntigravityInstallation.ts";
5353import * as CodexInstallation from "../provider/CodexInstallation.ts";
5454import * as ServerEnvironment from "../environment/ServerEnvironment.ts";
5555import * as ModelManifest from "../provider/ModelManifest.ts";
56import { ProviderInstanceRegistryHydrationLive } from "../provider/Layers/ProviderInstanceRegistryHydration.ts";
56import * as ProviderInstanceRegistryHydration from "../provider/Layers/ProviderInstanceRegistryHydration.ts";
5757import * as ProviderEventLoggers from "../provider/Layers/ProviderEventLoggers.ts";
5858import * as OpenCode2Client from "../provider/opencode2/OpenCode2Client.ts";
5959import * as OpenCodeRuntime from "../provider/opencodeRuntime.ts";
......@@ -64,8 +64,8 @@ import * as VcsProcess from "../vcs/VcsProcess.ts";
6464import * as EffectWorker from "./EffectWorker.ts";
6565import * as Orchestrator from "./Orchestrator.ts";
6666import * as ProviderInstanceRegistry from "../provider/Services/ProviderInstanceRegistry.ts";
67import { worktreeRepairDependenciesTestLayer } from "./ProviderTurnStartService.testkit.ts";
68import { OrchestrationV2LayerLive } from "./runtimeLayer.ts";
67import * as ProviderTurnStartServiceTestkit from "./ProviderTurnStartService.testkit.ts";
68import * as RuntimeLayer from "./runtimeLayer.ts";
6969import * as IdAllocator from "./IdAllocator.ts";
7070import * as ProviderContinuationRequests from "./ProviderContinuationRequests.ts";
7171import * as ProviderContinuationService from "./ProviderContinuationService.ts";
......@@ -84,7 +84,7 @@ const SWITCHED_MODEL = "opencode/mimo-v2.6-flash-free";
8484
8585/** The OpenCode servers the driver spawned, newest last, so a test can kill one by its own PID. */
8686const spawnedPids: Array<number> = [];
87const spawnedServers = Layer.succeed(
87const layerSpawnedServers = Layer.succeed(
8888 OpenCodeServerLedger.OpenCodeServerLedger,
8989 OpenCodeServerLedger.OpenCodeServerLedger.of({
9090 track: ({ pid }) =>
......@@ -100,7 +100,7 @@ const spawnedServers = Layer.succeed(
100100 * MCP server the run can see called; without it they point nowhere, as in replay.
101101 */
102102const MCP_URL = process.env.OPENCODE2_MCP_URL ?? "http://127.0.0.1/mcp";
103const mcpRegistryLayer = Layer.succeed(
103const layerMcpRegistry = Layer.succeed(
104104 McpSessionRegistry.McpSessionRegistry,
105105 McpSessionRegistry.McpSessionRegistry.of({
106106 issue: ({ threadId, providerInstanceId }) =>
......@@ -123,20 +123,20 @@ const mcpRegistryLayer = Layer.succeed(
123123 }),
124124);
125125
126const PlatformTestLayer = Layer.merge(
126const layerPlatformTest = Layer.merge(
127127 NodeServices.layer,
128128 Layer.mock(SourceControlProviderRegistry.SourceControlProviderRegistry)({
129129 resolveLink: () => Effect.die("unused title link"),
130130 }),
131131);
132const serverConfigLayer = ServerConfig.layerTest(`${ROOT}/work`, { prefix: "t3-opencode2-live-" });
133const vcsDriverRegistryLayer = VcsDriverRegistry.layer.pipe(
132const layerServerConfig = ServerConfig.layerTest(`${ROOT}/work`, { prefix: "t3-opencode2-live-" });
133const layerVcsDriverRegistry = VcsDriverRegistry.layer.pipe(
134134 Layer.provide(VcsProcess.layer),
135 Layer.provide(serverConfigLayer),
136 Layer.provide(PlatformTestLayer),
135 Layer.provide(layerServerConfig),
136 Layer.provide(layerPlatformTest),
137137);
138138// Isolated OpenCode state: the server never touches the developer's own data.
139const serverSettingsLayer = ServerSettings.layerTest({
139const layerServerSettings = ServerSettings.layerTest({
140140 providerInstances: {
141141 [INSTANCE]: {
142142 driver: ProviderDriverKind.make("opencode"),
......@@ -162,24 +162,24 @@ const serverSettingsLayer = ServerSettings.layerTest({
162162 },
163163 },
164164});
165const backgroundPolicyLayer = BackgroundPolicy.layer.pipe(
165const layerBackgroundPolicy = BackgroundPolicy.layer.pipe(
166166 Layer.provide(Layer.effect(HostPowerMonitor.HostPowerMonitor, HostPowerMonitor.make())),
167 Layer.provide(serverSettingsLayer),
167 Layer.provide(layerServerSettings),
168168);
169const providerInstanceRegistryLayer = ProviderInstanceRegistryHydrationLive.pipe(
169const layerProviderInstanceRegistry = ProviderInstanceRegistryHydration.layer.pipe(
170170 Layer.provide(
171171 Layer.mergeAll(
172 serverConfigLayer.pipe(Layer.provide(PlatformTestLayer)),
173 serverSettingsLayer,
172 layerServerConfig.pipe(Layer.provide(layerPlatformTest)),
173 layerServerSettings,
174174 ServerSecretStore.layer.pipe(
175 Layer.provide(serverConfigLayer),
176 Layer.provide(PlatformTestLayer),
175 Layer.provide(layerServerConfig),
176 Layer.provide(layerPlatformTest),
177177 ),
178178 NodeServices.layer,
179179 FetchHttpClient.layer,
180 OpenCodeRuntime.OpenCodeRuntimeLive.pipe(
181 Layer.provide(spawnedServers),
182 Layer.provide(PlatformTestLayer),
180 OpenCodeRuntime.layer.pipe(
181 Layer.provide(layerSpawnedServers),
182 Layer.provide(layerPlatformTest),
183183 ),
184184 Layer.succeed(
185185 ProviderEventLoggers.ProviderEventLoggers,
......@@ -187,9 +187,9 @@ const providerInstanceRegistryLayer = ProviderInstanceRegistryHydrationLive.pipe
187187 ),
188188 ModelManifest.layerTest,
189189 AntigravityInstallation.AntigravityInstallation.layer.pipe(
190 Layer.provide(serverConfigLayer.pipe(Layer.provide(PlatformTestLayer))),
190 Layer.provide(layerServerConfig.pipe(Layer.provide(layerPlatformTest))),
191191 Layer.provide(FetchHttpClient.layer),
192 Layer.provide(PlatformTestLayer),
192 Layer.provide(layerPlatformTest),
193193 ),
194194 // The Codex driver now resolves managed ChatGPT installs; these runs never launch Codex.
195195 Layer.mock(CodexInstallation.CodexInstallation)({
......@@ -203,22 +203,22 @@ const providerInstanceRegistryLayer = ProviderInstanceRegistryHydrationLive.pipe
203203 ),
204204 ),
205205);
206const orchestrationLayer = OrchestrationV2LayerLive.pipe(
207 Layer.provide(worktreeRepairDependenciesTestLayer),
208 Layer.provide(mcpRegistryLayer),
209 Layer.provide(SqlitePersistenceMemory),
210 Layer.provide(CheckpointStore.layer.pipe(Layer.provide(vcsDriverRegistryLayer))),
211 Layer.provide(serverConfigLayer),
212 Layer.provide(serverSettingsLayer),
206const layerOrchestration = RuntimeLayer.layer.pipe(
207 Layer.provide(ProviderTurnStartServiceTestkit.layer),
208 Layer.provide(layerMcpRegistry),
209 Layer.provide(SqlitePersistence.layerMemory),
210 Layer.provide(CheckpointStore.layer.pipe(Layer.provide(layerVcsDriverRegistry))),
211 Layer.provide(layerServerConfig),
212 Layer.provide(layerServerSettings),
213213 // Merged, not only provided: the test reads the same instance the orchestrator uses.
214 Layer.provideMerge(providerInstanceRegistryLayer),
214 Layer.provideMerge(layerProviderInstanceRegistry),
215215 Layer.provide(ResetCreditCoordinator.layer),
216 Layer.provide(backgroundPolicyLayer),
217 Layer.provide(PlatformTestLayer),
216 Layer.provide(layerBackgroundPolicy),
217 Layer.provide(layerPlatformTest),
218218);
219219
220220// Starts the continuation run a provider wake asks for, as the production layer does.
221const continuationWorkerLayer = ProviderContinuationService.workerLive.pipe(
221const layerContinuationWorker = ProviderContinuationService.layer.pipe(
222222 Layer.provide(
223223 Layer.unwrap(
224224 Effect.gen(function* () {
......@@ -234,7 +234,7 @@ const continuationWorkerLayer = ProviderContinuationService.workerLive.pipe(
234234 Layer.provide(IdAllocator.layer),
235235 Layer.provide(ProviderContinuationRequests.layer),
236236);
237const liveLayer = continuationWorkerLayer.pipe(Layer.provideMerge(orchestrationLayer));
237const layerLive = layerContinuationWorker.pipe(Layer.provideMerge(layerOrchestration));
238238
239239// How long one step may take, in seconds. A free model sometimes takes minutes
240240// before its first tool call; `OPENCODE2_STEP_WAIT` raises it for such runs.
......@@ -432,7 +432,7 @@ describe.runIf(binaryPath !== undefined && ROOT !== "")("OpenCode 2 live orchest
432432 const models = yield* assistantModels(sessionId!);
433433 assert.equal(models.at(-1), SWITCHED_MODEL);
434434 assert.notEqual(models[0], SWITCHED_MODEL);
435 }).pipe(Effect.provide(Layer.merge(liveLayer, NodeServices.layer)), Effect.scoped),
435 }).pipe(Effect.provide(Layer.merge(layerLive, NodeServices.layer)), Effect.scoped),
436436 360_000,
437437 );
438438
......@@ -542,7 +542,7 @@ describe.runIf(binaryPath !== undefined && ROOT !== "")("OpenCode 2 live orchest
542542 assert.equal(answered.runs.at(-1)?.status, "completed");
543543 const reply = answered.messages.findLast((message) => message.role === "assistant");
544544 assert.match(reply?.text ?? "", /blue/i);
545 }).pipe(Effect.provide(Layer.merge(liveLayer, NodeServices.layer)), Effect.scoped),
545 }).pipe(Effect.provide(Layer.merge(layerLive, NodeServices.layer)), Effect.scoped),
546546 600_000,
547547 );
548548
......@@ -582,7 +582,7 @@ describe.runIf(binaryPath !== undefined && ROOT !== "")("OpenCode 2 live orchest
582582 assert.equal(planned.runs.at(-1)?.status, "completed");
583583 assert.isFalse(yield* fs.exists(path.join(ROOT, "work", "plan_write_probe.txt")));
584584 assert.isTrue(yield* fs.exists(path.join(planDir, "probe-plan.md")));
585 }).pipe(Effect.provide(Layer.merge(liveLayer, NodeServices.layer)), Effect.scoped),
585 }).pipe(Effect.provide(Layer.merge(layerLive, NodeServices.layer)), Effect.scoped),
586586 360_000,
587587 );
588588
......@@ -718,7 +718,7 @@ describe.runIf(binaryPath !== undefined && ROOT !== "")("OpenCode 2 live orchest
718718 stoppedChild.turnItems.some((item) => item.status === "running"),
719719 "the stopped child shows nothing running",
720720 );
721 }).pipe(Effect.provide(Layer.merge(liveLayer, NodeServices.layer)), Effect.scoped),
721 }).pipe(Effect.provide(Layer.merge(layerLive, NodeServices.layer)), Effect.scoped),
722722 480_000,
723723 );
724724
......@@ -927,7 +927,7 @@ describe.runIf(binaryPath !== undefined && ROOT !== "")("OpenCode 2 live orchest
927927 .map((row) => String(row.id));
928928 db.close();
929929 assert.lengthOf(userMessages, 2);
930 }).pipe(Effect.provide(Layer.merge(liveLayer, NodeServices.layer)), Effect.scoped),
930 }).pipe(Effect.provide(Layer.merge(layerLive, NodeServices.layer)), Effect.scoped),
931931 600_000,
932932 );
933933
......@@ -1028,7 +1028,7 @@ describe.runIf(binaryPath !== undefined && ROOT !== "")("OpenCode 2 live orchest
10281028 compaction?.type === "compaction" ? (compaction.summary ?? "").length : 0,
10291029 0,
10301030 );
1031 }).pipe(Effect.provide(Layer.merge(liveLayer, NodeServices.layer)), Effect.scoped),
1031 }).pipe(Effect.provide(Layer.merge(layerLive, NodeServices.layer)), Effect.scoped),
10321032 360_000,
10331033 );
10341034
......@@ -1114,7 +1114,7 @@ describe.runIf(binaryPath !== undefined && ROOT !== "")("OpenCode 2 live orchest
11141114 const answer = next.turnItems.findLast((item) => item.type === "assistant_message");
11151115 assert.include(answer?.type === "assistant_message" ? answer.text : "", "sleep 60");
11161116 assert.lengthOf(next.providerThreads, 1);
1117 }).pipe(Effect.provide(Layer.merge(liveLayer, NodeServices.layer)), Effect.scoped),
1117 }).pipe(Effect.provide(Layer.merge(layerLive, NodeServices.layer)), Effect.scoped),
11181118 360_000,
11191119 );
11201120
......@@ -1175,7 +1175,7 @@ describe.runIf(binaryPath !== undefined && ROOT !== "")("OpenCode 2 live orchest
11751175 [],
11761176 "an external server gets no T3 MCP server, as with 1.x",
11771177 );
1178 }).pipe(Effect.provide(Layer.merge(liveLayer, NodeServices.layer)), Effect.scoped),
1178 }).pipe(Effect.provide(Layer.merge(layerLive, NodeServices.layer)), Effect.scoped),
11791179 360_000,
11801180 );
11811181});
apps/server/src/orchestration-v2/Orchestrator.control-reads.test.ts+13-13
......@@ -21,13 +21,13 @@ import * as DateTime from "effect/DateTime";
2121import * as Effect from "effect/Effect";
2222import * as Layer from "effect/Layer";
2323import * as SqlClient from "effect/sql/SqlClient";
24import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts";
24import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts";
2525import { CodexProviderCapabilitiesV2 } from "./Adapters/CodexAdapterV2.ts";
2626import * as Orchestrator from "./Orchestrator.ts";
2727import * as ProjectionStore from "./ProjectionStore.ts";
2828import type { ProviderAdapterV2Shape } from "./ProviderAdapter.ts";
2929import * as ProviderAdapterRegistry from "./ProviderAdapterRegistry.ts";
30import { makeOrchestratorV2ReplayLayerWithRegistry } from "./testkit/ProviderReplayHarness.ts";
30import * as ProviderReplayHarness from "./testkit/ProviderReplayHarness.ts";
3131
3232const instanceId = ProviderInstanceId.make("codex");
3333const modelSelection = { instanceId, model: "gpt-5.1-codex" };
......@@ -38,14 +38,14 @@ const adapter = {
3838 planSelectionTransition: () => Effect.succeed({ type: "apply_on_next_turn" as const }),
3939 openSession: () => Effect.die("No provider process needed for metadata controls"),
4040} as ProviderAdapterV2Shape;
41const database = SqlitePersistenceMemory;
42const testLayer = Layer.mergeAll(
43 database,
44 ProjectionStore.layer.pipe(Layer.provide(database)),
45 makeOrchestratorV2ReplayLayerWithRegistry(
41const layerDatabase = SqlitePersistence.layerMemory;
42const layerTest = Layer.mergeAll(
43 layerDatabase,
44 ProjectionStore.layer.pipe(Layer.provide(layerDatabase)),
45 ProviderReplayHarness.layerWithRegistry(
4646 { name: "control-reads" },
47 ProviderAdapterRegistry.makeLayer([adapter]),
48 { databaseLayer: database, runEffectWorker: false },
47 ProviderAdapterRegistry.layerFromAdapters([adapter]),
48 { databaseLayer: layerDatabase, runEffectWorker: false },
4949 ),
5050);
5151
......@@ -277,7 +277,7 @@ it.effect(
277277 threadId,
278278 });
279279 assert.isNotNull((yield* projections.getThread(threadId)).deletedAt);
280 }).pipe(Effect.provide(testLayer)),
280 }).pipe(Effect.provide(layerTest)),
281281);
282282
283283it.effect("implements a proposed plan that the command projection leaves out", () =>
......@@ -331,7 +331,7 @@ it.effect("implements a proposed plan that the command projection leaves out", (
331331 });
332332
333333 assert.equal((yield* projections.getPlan(threadId, planId))?.status, "completed");
334 }).pipe(Effect.provide(testLayer)),
334 }).pipe(Effect.provide(layerTest)),
335335);
336336
337337// Stop's settle follow-up runs after the provider interrupt returns, possibly
......@@ -532,7 +532,7 @@ it.effect("settles only the stopped run's background work, once", () =>
532532 `${commandItem(2)}:running`,
533533 `${commandItem(3)}:running`,
534534 ]);
535 }).pipe(Effect.provide(testLayer)),
535 }).pipe(Effect.provide(layerTest)),
536536);
537537
538538it.effect("keeps delegated child pull-request links independent of the parent", () =>
......@@ -622,5 +622,5 @@ it.effect("keeps delegated child pull-request links independent of the parent",
622622 const parentAfterChildLink = yield* projections.getThreadProjection(parentThreadId);
623623 assert.deepEqual(parentAfterChildLink.thread.linkedPullRequest, parentPullRequest);
624624 assert.deepEqual(parentAfterChildLink.thread.pullRequests, parent.thread.pullRequests);
625 }).pipe(Effect.provide(testLayer)),
625 }).pipe(Effect.provide(layerTest)),
626626);
apps/server/src/orchestration-v2/ProjectSettingsUpgrade.integration.test.ts+15-15
......@@ -15,7 +15,7 @@ import * as GitWorkflow from "../git/GitWorkflowService.ts";
1515import * as NodeSqliteClient from "@t3tools/shared/nodeSqliteClient";
1616
1717import * as McpSessionRegistryTestkit from "../mcp/McpSessionRegistry.testkit.ts";
18import { makeSqlitePersistenceLive } from "../persistence/Layers/Sqlite.ts";
18import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts";
1919import { runMigrations } from "../persistence/Migrations.ts";
2020import * as ProjectEnrichmentService from "../project/ProjectEnrichmentService.ts";
2121import * as ProjectService from "../project/ProjectService.ts";
......@@ -26,7 +26,7 @@ import * as VcsDriverRegistry from "../vcs/VcsDriverRegistry.ts";
2626import * as VcsProcess from "../vcs/VcsProcess.ts";
2727import * as WorkspacePaths from "../workspace/WorkspacePaths.ts";
2828import * as LegacyV1ThreadImporter from "./legacy/LegacyV1ThreadImporter.ts";
29import { OrchestrationV2LayerLive, ProjectServiceLayerLive } from "./runtimeLayer.ts";
29import * as RuntimeLayer from "./runtimeLayer.ts";
3030
3131const projectId = ProjectId.make("project:upgrade");
3232const icon = { kind: "emoji", emoji: "🦊" } as const;
......@@ -116,28 +116,28 @@ const unusedEnrichment = {
116116};
117117
118118/** The production V2 runtime and project service against one file-backed database. */
119const makeRuntimeLayer = (dbPath: string) => {
120 const platform = Layer.merge(
119const layerRuntime = (dbPath: string) => {
120 const layerPlatform = Layer.merge(
121121 NodeServices.layer,
122122 Layer.mock(SourceControlProviderRegistry.SourceControlProviderRegistry)({
123123 resolveLink: () => Effect.die("unused"),
124124 }),
125125 );
126 const serverConfig = ServerConfig.layerTest(process.cwd(), {
126 const layerServerConfig = ServerConfig.layerTest(process.cwd(), {
127127 prefix: "t3-project-upgrade-",
128128 });
129 const checkpointStore = CheckpointStore.layer.pipe(
129 const layerCheckpointStore = CheckpointStore.layer.pipe(
130130 Layer.provide(
131131 VcsDriverRegistry.layer.pipe(
132132 Layer.provide(VcsProcess.layer),
133 Layer.provide(serverConfig),
134 Layer.provide(platform),
133 Layer.provide(layerServerConfig),
134 Layer.provide(layerPlatform),
135135 ),
136136 ),
137137 );
138138 return Layer.mergeAll(
139 OrchestrationV2LayerLive.pipe(Layer.provide(ProjectServiceLayerLive)),
140 ProjectServiceLayerLive,
139 RuntimeLayer.layer.pipe(Layer.provide(RuntimeLayer.layerProjectService)),
140 RuntimeLayer.layerProjectService,
141141 ).pipe(
142142 Layer.provide(
143143 Layer.mock(ProjectEnrichmentService.ProjectEnrichmentService)({
......@@ -152,9 +152,9 @@ const makeRuntimeLayer = (dbPath: string) => {
152152 }),
153153 ),
154154 Layer.provide(McpSessionRegistryTestkit.layer),
155 Layer.provideMerge(makeSqlitePersistenceLive(dbPath)),
156 Layer.provide(checkpointStore),
157 Layer.provide(serverConfig),
155 Layer.provideMerge(SqlitePersistence.layerFromPath(dbPath)),
156 Layer.provide(layerCheckpointStore),
157 Layer.provide(layerServerConfig),
158158 Layer.provide(ServerSettings.layerTest()),
159159 Layer.provide(
160160 Layer.succeed(ProviderInstanceRegistry.ProviderInstanceRegistry, {
......@@ -170,7 +170,7 @@ const makeRuntimeLayer = (dbPath: string) => {
170170 pruneWorktrees: () => Effect.void,
171171 }),
172172 ),
173 Layer.provide(platform),
173 Layer.provide(layerPlatform),
174174 );
175175};
176176
......@@ -189,7 +189,7 @@ it.live("keeps project settings through the V2 migrations and the first V2 boot"
189189 assert.deepEqual(yield* readSettings, expectedSettings);
190190 const project = yield* (yield* ProjectService.ProjectService).getById(projectId);
191191 assert.equal(project._tag, "Some");
192 }).pipe(Effect.provide(makeRuntimeLayer(dbPath)));
192 }).pipe(Effect.provide(layerRuntime(dbPath)));
193193 }),
194194 ).pipe(Effect.provide(NodeServices.layer)),
195195);
apps/server/src/orchestration-v2/ProjectStore.test.ts+2-2
......@@ -5,10 +5,10 @@ import * as Layer from "effect/Layer";
55import * as Option from "effect/Option";
66import * as SqlClient from "effect/sql/SqlClient";
77
8import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts";
8import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts";
99import * as ProjectStore from "./ProjectStore.ts";
1010
11it.layer(ProjectStore.layer.pipe(Layer.provideMerge(SqlitePersistenceMemory)))(
11it.layer(ProjectStore.layer.pipe(Layer.provideMerge(SqlitePersistence.layerMemory)))(
1212 "ProjectStoreV2",
1313 (it) => {
1414 it.effect("stores a model selection without options as JSON without an options key", () =>
apps/server/src/orchestration-v2/ProjectionControlReads.test.ts+5-5
......@@ -20,7 +20,7 @@ import * as Effect from "effect/Effect";
2020import * as Layer from "effect/Layer";
2121import * as Option from "effect/Option";
2222import * as SqlClient from "effect/sql/SqlClient";
23import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts";
23import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts";
2424import * as ProjectionStore from "./ProjectionStore.ts";
2525import * as ProviderSessionManager from "./ProviderSessionManager.ts";
2626import * as ProviderTurnControlService from "./ProviderTurnControlService.ts";
......@@ -190,7 +190,7 @@ const storageCases = (["sqlite", "memory"] as const).map((storage) => ({
190190 storage,
191191 storeLayer:
192192 storage === "sqlite"
193 ? ProjectionStore.layer.pipe(Layer.provideMerge(SqlitePersistenceMemory))
193 ? ProjectionStore.layer.pipe(Layer.provideMerge(SqlitePersistence.layerMemory))
194194 : ProjectionStore.layerMemory,
195195}));
196196
......@@ -274,7 +274,7 @@ it.effect.each(storageCases)(
274274 assert.instanceOf(missingThread, ProjectionStore.ProjectionStoreThreadNotFoundError);
275275
276276 const calls: string[] = [];
277 const sessions = Layer.mock(ProviderSessionManager.ProviderSessionManagerV2)({
277 const layerSessions = Layer.mock(ProviderSessionManager.ProviderSessionManagerV2)({
278278 get: () =>
279279 Effect.succeed(
280280 Option.some({
......@@ -346,9 +346,9 @@ it.effect.each(storageCases)(
346346 }).pipe(
347347 Effect.provide(
348348 Layer.merge(ProviderTurnControlService.layer, RuntimeRequestService.layer).pipe(
349 Layer.provide(sessions),
349 Layer.provide(layerSessions),
350350 ),
351351 ),
352352 );
353 }).pipe(Effect.provide(Layer.merge(storeLayer, SqlitePersistenceMemory))),
353 }).pipe(Effect.provide(Layer.merge(storeLayer, SqlitePersistence.layerMemory))),
354354);
apps/server/src/orchestration-v2/ProjectionRecovery.test.ts+8-8
......@@ -22,14 +22,14 @@ import * as Effect from "effect/Effect";
2222import * as Layer from "effect/Layer";
2323import * as SqlClient from "effect/sql/SqlClient";
2424
25import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts";
25import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts";
2626import { CodexProviderCapabilitiesV2 } from "./Adapters/CodexAdapterV2.ts";
2727import * as EffectOutbox from "./EffectOutbox.ts";
2828import * as ProjectionStore from "./ProjectionStore.ts";
2929import { restartContinuationRun } from "./RestartContinuation.ts";
3030
31const TestLayer = Layer.mergeAll(ProjectionStore.layer, EffectOutbox.layer).pipe(
32 Layer.provideMerge(SqlitePersistenceMemory),
31const layerTest = Layer.mergeAll(ProjectionStore.layer, EffectOutbox.layer).pipe(
32 Layer.provideMerge(SqlitePersistence.layerMemory),
3333);
3434const providerInstanceId = ProviderInstanceId.make("codex");
3535const modelSelection = { instanceId: providerInstanceId, model: "gpt-5.4" };
......@@ -221,7 +221,7 @@ it.effect("selects unfinished recovery work without reading settled thread histo
221221 queued,
222222 ThreadId.make("thread:recovery:settled-599"),
223223 ]);
224 }).pipe(Effect.provide(TestLayer)),
224 }).pipe(Effect.provide(layerTest)),
225225);
226226
227227it.effect("recovers terminal subagent results until their cross-thread transfer exists", () =>
......@@ -283,7 +283,7 @@ it.effect("recovers terminal subagent results until their cross-thread transfer
283283 new Set(yield* projections.getUnreadableThreadIds()),
284284 new Set([parent, completed]),
285285 );
286 }).pipe(Effect.provide(TestLayer)),
286 }).pipe(Effect.provide(layerTest)),
287287);
288288
289289it.effect("includes shared sessions and provider-owned background rosters in recovery", () =>
......@@ -417,7 +417,7 @@ it.effect("includes shared sessions and provider-owned background rosters in rec
417417 new Set(yield* projections.getUnreadableThreadIds()),
418418 new Set([first, second]),
419419 );
420 }).pipe(Effect.provide(TestLayer)),
420 }).pipe(Effect.provide(layerTest)),
421421);
422422
423423it.effect("reads the run that owns a background roster, not a queued or resumed one", () =>
......@@ -484,7 +484,7 @@ it.effect("reads the run that owns a background roster, not a queued or resumed
484484 yield* createRun(quiet, "completed");
485485 const quietQueued = yield* createRun(quiet, "queued", { ordinal: 2, completedAt: null });
486486 assert.deepEqual(yield* runIds(quiet), [quietQueued]);
487 }).pipe(Effect.provide(TestLayer)),
487 }).pipe(Effect.provide(layerTest)),
488488);
489489
490490it.effect("marks fork descendants unreadable when their source is missing or corrupt", () =>
......@@ -513,5 +513,5 @@ it.effect("marks fork descendants unreadable when their source is missing or cor
513513 new Set(yield* projections.getUnreadableThreadIds()),
514514 new Set([fork, descendant]),
515515 );
516 }).pipe(Effect.provide(TestLayer)),
516 }).pipe(Effect.provide(layerTest)),
517517);
apps/server/src/orchestration-v2/ProjectionSettlement.test.ts+8-8
......@@ -21,11 +21,11 @@ import * as Schema from "effect/Schema";
2121import * as SqlClient from "effect/sql/SqlClient";
2222import * as Statement from "effect/sql/Statement";
2323
24import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts";
24import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts";
2525import * as ProjectionStore from "./ProjectionStore.ts";
2626import { isAutoSettlementCandidate, resolveAutoSettlementAt } from "./ThreadSettlementService.ts";
2727
28const SqlLayer = ProjectionStore.layer.pipe(Layer.provideMerge(SqlitePersistenceMemory));
28const layerSql = ProjectionStore.layer.pipe(Layer.provideMerge(SqlitePersistence.layerMemory));
2929const now = DateTime.makeUnsafe("2026-09-04T12:00:00Z");
3030const old = DateTime.subtract(now, { days: 10 });
3131const providerInstanceId = ProviderInstanceId.make("codex");
......@@ -141,7 +141,7 @@ const createItem = Effect.fn(function* (
141141});
142142
143143it.effect.each([
144 ["sql", SqlLayer],
144 ["sql", layerSql],
145145 ["memory", ProjectionStore.layerMemory],
146146] as const)(
147147 "%s: discovers settlement work with the same activity and background semantics as the shell",
......@@ -299,7 +299,7 @@ it.effect.each([
299299);
300300
301301it.effect.each([
302 ["sql", SqlLayer],
302 ["sql", layerSql],
303303 ["memory", ProjectionStore.layerMemory],
304304] as const)("%s: the user-authored message time ignores agent notifications", ([, testLayer]) =>
305305 Effect.gen(function* () {
......@@ -366,7 +366,7 @@ const pullRequestLink = (number: number) => ({
366366});
367367
368368it.effect.each([
369 ["sql", SqlLayer],
369 ["sql", layerSql],
370370 ["memory", ProjectionStore.layerMemory],
371371] as const)(
372372 "%s: lists only active threads with pull request links, oldest first",
......@@ -405,7 +405,7 @@ it.effect.each([
405405);
406406
407407it.effect.each([
408 ["sql", SqlLayer],
408 ["sql", layerSql],
409409 ["memory", ProjectionStore.layerMemory],
410410] as const)("%s: an unsettled-only shell read skips settled threads", ([, testLayer]) =>
411411 Effect.gen(function* () {
......@@ -486,7 +486,7 @@ it.effect(
486486 WHERE thread_id = ${threadId} AND role = 'user' ORDER BY updated_at DESC, message_id DESC LIMIT 1`;
487487 assert.isTrue(messagePlan.some((row) => row.detail.includes("messages_latest_user_idx")));
488488 assert.isFalse(messagePlan.some((row) => row.detail.includes("TEMP B-TREE")));
489 }).pipe(Effect.provide(SqlLayer)),
489 }).pipe(Effect.provide(layerSql)),
490490);
491491
492492it.effect("shell failure lookups stay on the thread's own turn items", () =>
......@@ -525,5 +525,5 @@ it.effect("shell failure lookups stay on the thread's own turn items", () =>
525525 const secretLookups = plan.filter((row) => row.detail.startsWith("SEARCH secret "));
526526 assert.lengthOf(secretLookups, 1);
527527 assert.include(secretLookups[0]!.detail, "turn_items_thread_run_idx");
528 }).pipe(Effect.provide(SqlLayer)),
528 }).pipe(Effect.provide(layerSql)),
529529);
apps/server/src/orchestration-v2/ProjectionStore.test.ts+5-5
......@@ -30,7 +30,7 @@ import * as Schema from "effect/Schema";
3030import * as SqlClient from "effect/sql/SqlClient";
3131import { projectThreadAwarenessV2 } from "@t3tools/shared/agentAwareness";
3232
33import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts";
33import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts";
3434import { CodexProviderCapabilitiesV2 } from "./Adapters/CodexAdapterV2.ts";
3535import * as ProjectionStore from "./ProjectionStore.ts";
3636import {
......@@ -40,9 +40,9 @@ import {
4040 THREAD_HISTORY_PAGE_POLICY,
4141} from "./threadHistoryPaging.ts";
4242
43const TestLayer = Layer.mergeAll(
44 ProjectionStore.layer.pipe(Layer.provideMerge(SqlitePersistenceMemory)),
45 SqlitePersistenceMemory,
43const layerTest = Layer.mergeAll(
44 ProjectionStore.layer.pipe(Layer.provideMerge(SqlitePersistence.layerMemory)),
45 SqlitePersistence.layerMemory,
4646);
4747const modelSelection = {
4848 instanceId: ProviderInstanceId.make("codex"),
......@@ -330,7 +330,7 @@ it.effect("memory recovery selection includes unfinished items from missing runs
330330 }).pipe(Effect.provide(ProjectionStore.layerMemory)),
331331);
332332
333it.layer(TestLayer)("ProjectionStoreV2", (it) => {
333it.layer(layerTest)("ProjectionStoreV2", (it) => {
334334 it.effect(
335335 "keeps restart-cancelled work through a stale run.updated",
336336 () => restartCancelledWorkSurvivesStaleRunUpdate,
apps/server/src/orchestration-v2/ProviderAdapterRegistry.test.ts+4-4
......@@ -65,7 +65,7 @@ const instances = [
6565 makeInstance(personalId, personalAdapter),
6666 makeInstance(workId, workAdapter),
6767] as const;
68const instanceRegistryLayer = Layer.succeed(ProviderInstanceRegistry.ProviderInstanceRegistry, {
68const layerInstanceRegistry = Layer.succeed(ProviderInstanceRegistry.ProviderInstanceRegistry, {
6969 getInstance: (instanceId) =>
7070 Effect.succeed(instances.find((instance) => instance.instanceId === instanceId)),
7171 listInstances: Effect.succeed(instances),
......@@ -73,8 +73,8 @@ const instanceRegistryLayer = Layer.succeed(ProviderInstanceRegistry.ProviderIns
7373 streamChanges: Stream.empty,
7474 subscribeChanges: Effect.never,
7575});
76const TestLayer = ProviderAdapterRegistry.layerFromProviderInstanceRegistry.pipe(
77 Layer.provide(instanceRegistryLayer),
76const layerTest = ProviderAdapterRegistry.layerFromProviderInstanceRegistry.pipe(
77 Layer.provide(layerInstanceRegistry),
7878);
7979
8080it.effect("routes two configured instances of the same driver independently", () =>
......@@ -84,7 +84,7 @@ it.effect("routes two configured instances of the same driver independently", ()
8484 assert.strictEqual(yield* registry.get(personalId), personalAdapter);
8585 assert.strictEqual(yield* registry.get(workId), workAdapter);
8686 assert.deepEqual(yield* registry.list(), [personalId, workId]);
87 }).pipe(Effect.provide(TestLayer)),
87 }).pipe(Effect.provide(layerTest)),
8888);
8989
9090const lifecycleDriver = ProviderDriverKind.make("lifecycle-test");
apps/server/src/orchestration-v2/ProviderAdapterRegistry.ts+5-5
......@@ -184,7 +184,7 @@ function makeRegistry(
184184 };
185185}
186186
187export function makeLayer(
187export function layerFromAdapters(
188188 adapters: ReadonlyArray<ProviderAdapter.ProviderAdapterV2Shape>,
189189): Layer.Layer<ProviderAdapterRegistryV2> {
190190 return Layer.succeed(
......@@ -193,7 +193,7 @@ export function makeLayer(
193193 );
194194}
195195
196export function makeLayerEffect<R, E>(
196export function layerFromAdaptersEffect<R, E>(
197197 adapters: Effect.Effect<ReadonlyArray<ProviderAdapter.ProviderAdapterV2Shape>, E, R>,
198198): Layer.Layer<ProviderAdapterRegistryV2, E, R> {
199199 return Layer.effect(
......@@ -202,10 +202,10 @@ export function makeLayerEffect<R, E>(
202202 );
203203}
204204
205export function makeSingleLayer(
205export function layerSingle(
206206 adapter: ProviderAdapter.ProviderAdapterV2Shape,
207207): Layer.Layer<ProviderAdapterRegistryV2> {
208 return makeLayer([adapter]);
208 return layerFromAdapters([adapter]);
209209}
210210
211211const decodedConfigEnabled = (config: unknown): boolean | undefined => {
......@@ -338,7 +338,7 @@ export function makeRegistryFromConfigMap<R>(input: {
338338 });
339339}
340340
341export function makeDriverLayer<R>(input: {
341export function layerFromDrivers<R>(input: {
342342 readonly drivers: ReadonlyArray<AnyProviderAdapterDriver<R>>;
343343 readonly configMap: ProviderInstanceConfigMap;
344344}): Layer.Layer<ProviderAdapterRegistryV2, ProviderAdapterRegistryBuildError, R> {
apps/server/src/orchestration-v2/ProviderContinuationService.test.ts+37-35
......@@ -88,18 +88,20 @@ const makeGuard = Effect.fnUntraced(function* (completed?: Deferred.Deferred<voi
8888 };
8989});
9090
91function testLayer(input: {
91function layerTest(input: {
9292 readonly dispatched: Queue.Queue<unknown>;
9393 readonly getThreadRecords: () => Effect.Effect<OrchestrationV2ThreadProjection>;
9494}) {
95 const threads = Layer.mock(ThreadManagementService.ThreadManagementService)({
95 const layerThreads = Layer.mock(ThreadManagementService.ThreadManagementService)({
9696 getThreadRecords: input.getThreadRecords,
9797 dispatch: (command) => Queue.offer(input.dispatched, command).pipe(Effect.as({} as never)),
9898 });
99 const worker = ProviderContinuationService.workerLive.pipe(
100 Layer.provide(Layer.mergeAll(IdAllocator.layer, ProviderContinuationRequests.layer, threads)),
99 const layerWorker = ProviderContinuationService.layer.pipe(
100 Layer.provide(
101 Layer.mergeAll(IdAllocator.layer, ProviderContinuationRequests.layer, layerThreads),
102 ),
101103 );
102 return Layer.merge(ProviderContinuationRequests.layer, worker);
104 return Layer.merge(ProviderContinuationRequests.layer, layerWorker);
103105}
104106
105107describe("ProviderContinuationService", () => {
......@@ -145,7 +147,7 @@ describe("ProviderContinuationService", () => {
145147 assert.equal(command.delegatedCompletion.taskIds.length, 2);
146148 }).pipe(
147149 Effect.provide(
148 testLayer({ dispatched, getThreadRecords: () => Effect.succeed(recovered) }),
150 layerTest({ dispatched, getThreadRecords: () => Effect.succeed(recovered) }),
149151 ),
150152 Effect.scoped,
151153 );
......@@ -171,7 +173,7 @@ describe("ProviderContinuationService", () => {
171173 });
172174 }).pipe(
173175 Effect.provide(
174 testLayer({ dispatched, getThreadRecords: () => Effect.succeed(projection) }),
176 layerTest({ dispatched, getThreadRecords: () => Effect.succeed(projection) }),
175177 ),
176178 Effect.scoped,
177179 );
......@@ -213,7 +215,7 @@ describe("ProviderContinuationService", () => {
213215 });
214216 }).pipe(
215217 Effect.provide(
216 testLayer({ dispatched, getThreadRecords: () => Effect.succeed(projection) }),
218 layerTest({ dispatched, getThreadRecords: () => Effect.succeed(projection) }),
217219 ),
218220 Effect.scoped,
219221 );
......@@ -262,7 +264,7 @@ describe("ProviderContinuationService", () => {
262264 assert.include(command.text, "task_status");
263265 }).pipe(
264266 Effect.provide(
265 testLayer({
267 layerTest({
266268 dispatched,
267269 getThreadRecords: () => Effect.succeed(delegatedProjection()),
268270 }),
......@@ -276,7 +278,7 @@ describe("ProviderContinuationService", () => {
276278 return Effect.gen(function* () {
277279 const attempts = yield* Ref.make(0);
278280 const dispatched = yield* Queue.unbounded<unknown>();
279 const threads = Layer.mock(ThreadManagementService.ThreadManagementService)({
281 const layerThreads = Layer.mock(ThreadManagementService.ThreadManagementService)({
280282 getThreadRecords: () => Effect.succeed(delegatedProjection()),
281283 dispatch: (command) =>
282284 Ref.getAndUpdate(attempts, (count) => count + 1).pipe(
......@@ -287,9 +289,9 @@ describe("ProviderContinuationService", () => {
287289 ),
288290 ),
289291 });
290 const worker = ProviderContinuationService.workerLive.pipe(
292 const layerWorker = ProviderContinuationService.layer.pipe(
291293 Layer.provide(
292 Layer.mergeAll(IdAllocator.layer, ProviderContinuationRequests.layer, threads),
294 Layer.mergeAll(IdAllocator.layer, ProviderContinuationRequests.layer, layerThreads),
293295 ),
294296 );
295297
......@@ -318,7 +320,7 @@ describe("ProviderContinuationService", () => {
318320 assert.equal(command.messageId, delegatedMessageId);
319321 assert.equal(yield* Ref.get(attempts), 2);
320322 }).pipe(
321 Effect.provide(Layer.merge(ProviderContinuationRequests.layer, worker)),
323 Effect.provide(Layer.merge(ProviderContinuationRequests.layer, layerWorker)),
322324 Effect.scoped,
323325 );
324326 });
......@@ -328,7 +330,7 @@ describe("ProviderContinuationService", () => {
328330 return Effect.gen(function* () {
329331 const attempts = yield* Ref.make(0);
330332 const disposition = yield* Ref.make<"open" | "disposed">("open");
331 const threads = Layer.mock(ThreadManagementService.ThreadManagementService)({
333 const layerThreads = Layer.mock(ThreadManagementService.ThreadManagementService)({
332334 getThreadRecords: () =>
333335 Ref.get(disposition).pipe(Effect.map((state) => delegatedProjection(state))),
334336 dispatch: () =>
......@@ -336,9 +338,9 @@ describe("ProviderContinuationService", () => {
336338 Effect.andThen(Effect.fail(new Error("simulated persistent failure") as never)),
337339 ),
338340 });
339 const worker = ProviderContinuationService.workerLive.pipe(
341 const layerWorker = ProviderContinuationService.layer.pipe(
340342 Layer.provide(
341 Layer.mergeAll(IdAllocator.layer, ProviderContinuationRequests.layer, threads),
343 Layer.mergeAll(IdAllocator.layer, ProviderContinuationRequests.layer, layerThreads),
342344 ),
343345 );
344346
......@@ -372,7 +374,7 @@ describe("ProviderContinuationService", () => {
372374 yield* Ref.set(disposition, "disposed");
373375 yield* TestClock.adjust("400 millis");
374376 }).pipe(
375 Effect.provide(Layer.merge(ProviderContinuationRequests.layer, worker)),
377 Effect.provide(Layer.merge(ProviderContinuationRequests.layer, layerWorker)),
376378 Effect.scoped,
377379 );
378380 });
......@@ -385,7 +387,7 @@ describe("ProviderContinuationService", () => {
385387 const blockedProjectionReads = yield* Ref.make(0);
386388 const blockedRequestDropped = yield* Deferred.make<void>();
387389 const state = yield* Ref.make<"open" | "blocked" | "disposed">("open");
388 const threads = Layer.mock(ThreadManagementService.ThreadManagementService)({
390 const layerThreads = Layer.mock(ThreadManagementService.ThreadManagementService)({
389391 getThreadRecords: () =>
390392 Effect.gen(function* () {
391393 const currentState = yield* Ref.get(state);
......@@ -411,9 +413,9 @@ describe("ProviderContinuationService", () => {
411413 Effect.andThen(Effect.fail(new Error("simulated persistent failure") as never)),
412414 ),
413415 });
414 const worker = ProviderContinuationService.workerLive.pipe(
416 const layerWorker = ProviderContinuationService.layer.pipe(
415417 Layer.provide(
416 Layer.mergeAll(IdAllocator.layer, ProviderContinuationRequests.layer, threads),
418 Layer.mergeAll(IdAllocator.layer, ProviderContinuationRequests.layer, layerThreads),
417419 ),
418420 );
419421 const completionRequest = {
......@@ -455,7 +457,7 @@ describe("ProviderContinuationService", () => {
455457 yield* Ref.set(state, "disposed");
456458 yield* TestClock.adjust("200 millis");
457459 }).pipe(
458 Effect.provide(Layer.merge(ProviderContinuationRequests.layer, worker)),
460 Effect.provide(Layer.merge(ProviderContinuationRequests.layer, layerWorker)),
459461 Effect.scoped,
460462 );
461463 }
......@@ -466,7 +468,7 @@ describe("ProviderContinuationService", () => {
466468 return Effect.gen(function* () {
467469 const attempts = yield* Ref.make(0);
468470 const disposition = yield* Ref.make<"open" | "disposed">("open");
469 const threads = Layer.mock(ThreadManagementService.ThreadManagementService)({
471 const layerThreads = Layer.mock(ThreadManagementService.ThreadManagementService)({
470472 getThreadRecords: () =>
471473 Ref.get(disposition).pipe(Effect.map((state) => delegatedProjection(state))),
472474 dispatch: () =>
......@@ -474,9 +476,9 @@ describe("ProviderContinuationService", () => {
474476 Effect.andThen(Effect.fail(new Error("simulated dispatch failure") as never)),
475477 ),
476478 });
477 const worker = ProviderContinuationService.workerLive.pipe(
479 const layerWorker = ProviderContinuationService.layer.pipe(
478480 Layer.provide(
479 Layer.mergeAll(IdAllocator.layer, ProviderContinuationRequests.layer, threads),
481 Layer.mergeAll(IdAllocator.layer, ProviderContinuationRequests.layer, layerThreads),
480482 ),
481483 );
482484
......@@ -502,7 +504,7 @@ describe("ProviderContinuationService", () => {
502504 yield* Effect.yieldNow;
503505 assert.equal(yield* Ref.get(attempts), 1);
504506 }).pipe(
505 Effect.provide(Layer.merge(ProviderContinuationRequests.layer, worker)),
507 Effect.provide(Layer.merge(ProviderContinuationRequests.layer, layerWorker)),
506508 Effect.scoped,
507509 );
508510 });
......@@ -531,7 +533,7 @@ describe("ProviderContinuationService", () => {
531533 assert.deepEqual(command.dispatchMode, { type: "queue_after_active" });
532534 }).pipe(
533535 Effect.provide(
534 testLayer({
536 layerTest({
535537 dispatched,
536538 getThreadRecords: () => Effect.succeed(delegatedProjection()),
537539 }),
......@@ -566,7 +568,7 @@ describe("ProviderContinuationService", () => {
566568 assert.isTrue(Option.isNone(yield* Queue.poll(dispatched)));
567569 }).pipe(
568570 Effect.provide(
569 testLayer({
571 layerTest({
570572 dispatched,
571573 getThreadRecords: () => Effect.succeed(delegatedProjection(disposition)),
572574 }),
......@@ -600,7 +602,7 @@ describe("ProviderContinuationService", () => {
600602 assert.isTrue(Option.isNone(yield* Queue.poll(dispatched)));
601603 }).pipe(
602604 Effect.provide(
603 testLayer({
605 layerTest({
604606 dispatched,
605607 getThreadRecords: () =>
606608 Effect.succeed({
......@@ -637,7 +639,7 @@ describe("ProviderContinuationService", () => {
637639 assert.isTrue(Option.isNone(yield* Queue.poll(dispatched)));
638640 }).pipe(
639641 Effect.provide(
640 testLayer({
642 layerTest({
641643 dispatched,
642644 getThreadRecords: () =>
643645 Effect.succeed({
......@@ -666,7 +668,7 @@ describe("ProviderContinuationService", () => {
666668 assert.isTrue(Option.isNone(yield* Queue.poll(dispatched)));
667669 }).pipe(
668670 Effect.provide(
669 testLayer({ dispatched, getThreadRecords: () => Effect.succeed(projection) }),
671 layerTest({ dispatched, getThreadRecords: () => Effect.succeed(projection) }),
670672 ),
671673 Effect.scoped,
672674 );
......@@ -685,7 +687,7 @@ describe("ProviderContinuationService", () => {
685687 });
686688 }).pipe(
687689 Effect.provide(
688 testLayer({ dispatched, getThreadRecords: () => Effect.succeed(projection) }),
690 layerTest({ dispatched, getThreadRecords: () => Effect.succeed(projection) }),
689691 ),
690692 Effect.scoped,
691693 );
......@@ -706,7 +708,7 @@ describe("ProviderContinuationService", () => {
706708 assert.isTrue(Option.isNone(yield* Queue.poll(dispatched)));
707709 }).pipe(
708710 Effect.provide(
709 testLayer({ dispatched, getThreadRecords: () => Effect.succeed(projection) }),
711 layerTest({ dispatched, getThreadRecords: () => Effect.succeed(projection) }),
710712 ),
711713 Effect.scoped,
712714 );
......@@ -731,7 +733,7 @@ describe("ProviderContinuationService", () => {
731733 assert.isTrue(Option.isNone(yield* Queue.poll(dispatched)));
732734 }).pipe(
733735 Effect.provide(
734 testLayer({
736 layerTest({
735737 dispatched,
736738 getThreadRecords: () =>
737739 Deferred.succeed(projectionEntered, undefined).pipe(
......@@ -766,7 +768,7 @@ describe("ProviderContinuationService", () => {
766768 assert.isTrue(Option.isNone(yield* Queue.poll(dispatched)));
767769 }).pipe(
768770 Effect.provide(
769 testLayer({
771 layerTest({
770772 dispatched,
771773 getThreadRecords: () => {
772774 projectionCalls += 1;
......@@ -801,7 +803,7 @@ describe("ProviderContinuationService", () => {
801803 assert.isTrue(Option.isNone(yield* Queue.poll(dispatched)));
802804 }).pipe(
803805 Effect.provide(
804 testLayer({
806 layerTest({
805807 dispatched,
806808 getThreadRecords: () =>
807809 Effect.succeed({
apps/server/src/orchestration-v2/ProviderContinuationService.ts+1-1
......@@ -57,7 +57,7 @@ function delegatedCompletionRetryKey(
5757 * the orchestrator selects native steering or queued delivery under its thread
5858 * lock. Adapter-buffered continuations still queue behind active work.
5959 */
60export const workerLive = Layer.effectDiscard(
60export const layer = Layer.effectDiscard(
6161 Effect.gen(function* () {
6262 const ids = yield* IdAllocator.IdAllocatorV2;
6363 const requests = yield* ProviderContinuationRequests.ProviderContinuationRequests;
apps/server/src/orchestration-v2/ProviderEventIngestor.test.ts+14-14
......@@ -27,7 +27,7 @@ import * as Layer from "effect/Layer";
2727import * as Stream from "effect/Stream";
2828import * as TestClock from "effect/testing/TestClock";
2929
30import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts";
30import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts";
3131import * as EventSink from "./EventSink.ts";
3232import * as EventStore from "./EventStore.ts";
3333import * as IdAllocator from "./IdAllocator.ts";
......@@ -42,25 +42,25 @@ import {
4242 selectInheritedBackgroundTurnItems,
4343} from "./RunExecutionService.ts";
4444
45const TestDatabaseLayer = SqlitePersistenceMemory;
46const TestStoresLayer = Layer.merge(EventStore.layer, ProjectionStore.layer).pipe(
47 Layer.provide(TestDatabaseLayer),
45const layerTestDatabase = SqlitePersistence.layerMemory;
46const layerTestStores = Layer.merge(EventStore.layer, ProjectionStore.layer).pipe(
47 Layer.provide(layerTestDatabase),
4848);
4949
50const TestEventSinkLayer = EventSink.layer.pipe(
51 Layer.provide(Layer.mergeAll(TestStoresLayer, TestDatabaseLayer)),
50const layerTestEventSink = EventSink.layer.pipe(
51 Layer.provide(Layer.mergeAll(layerTestStores, layerTestDatabase)),
5252);
5353
54const TestLayer = Layer.mergeAll(
55 TestStoresLayer,
56 TestEventSinkLayer,
54const layerTest = Layer.mergeAll(
55 layerTestStores,
56 layerTestEventSink,
5757 IdAllocator.layer,
5858 ThreadCommandExecutor.layer,
5959 ProviderEventIngestor.layer.pipe(
6060 Layer.provide(
6161 Layer.mergeAll(
62 TestStoresLayer,
63 TestEventSinkLayer,
62 layerTestStores,
63 layerTestEventSink,
6464 IdAllocator.layer,
6565 ThreadCommandExecutor.layer,
6666 ),
......@@ -133,11 +133,11 @@ function threadCreatedEvent(
133133 });
134134}
135135
136const layer = it.layer(TestLayer);
136const layer = it.layer(layerTest);
137137
138138it.effect("records accepted billed turn usage once without billing the context window", () => {
139139 const recorded: Array<Readonly<Record<string, unknown>>> = [];
140 const analytics = Layer.succeed(ProviderEventIngestor.ProviderTurnAnalytics, {
140 const layerAnalytics = Layer.succeed(ProviderEventIngestor.ProviderTurnAnalytics, {
141141 record: (properties: Readonly<Record<string, unknown>>) =>
142142 Effect.sync(() => {
143143 recorded.push(properties);
......@@ -232,7 +232,7 @@ it.effect("records accepted billed turn usage once without billing the context w
232232 interactionMode: "default",
233233 durationMs: 120,
234234 });
235 }).pipe(Effect.provide(TestLayer.pipe(Layer.provide(analytics))));
235 }).pipe(Effect.provide(layerTest.pipe(Layer.provide(layerAnalytics))));
236236});
237237
238238layer("ProviderEventIngestorV2", (it) => {
apps/server/src/orchestration-v2/ProviderEventIngestor.ts+1-1
......@@ -79,7 +79,7 @@ export class ProviderTurnAnalytics extends Context.Reference<{
7979 defaultValue: () => ({ record: () => Effect.void }),
8080}) {}
8181
82export const analyticsLive = Layer.effect(
82export const layerAnalytics = Layer.effect(
8383 ProviderTurnAnalytics,
8484 Effect.gen(function* () {
8585 const analytics = yield* AnalyticsService.AnalyticsService;
apps/server/src/orchestration-v2/ProviderRuntimeRecoveryPerformance.test.ts+7-7
......@@ -25,7 +25,7 @@ import * as Effect from "effect/Effect";
2525import * as Layer from "effect/Layer";
2626import * as SqlClient from "effect/sql/SqlClient";
2727
28import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts";
28import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts";
2929import * as ServerSettings from "../serverSettings.ts";
3030import { CodexProviderCapabilitiesV2 } from "./Adapters/CodexAdapterV2.ts";
3131import * as EffectOutbox from "./EffectOutbox.ts";
......@@ -40,14 +40,14 @@ import * as ProviderRuntimeRecovery from "./ProviderRuntimeRecoveryService.ts";
4040// `it.live` keeps a real clock: each reconcile gets a fresh command id, so the
4141// second recover cannot hide behind command receipt dedup.
4242
43const stores = Layer.mergeAll(
43const layerStores = Layer.mergeAll(
4444 EventStore.layer,
4545 ProjectionStore.layer,
4646 EffectOutbox.layer,
4747 IdAllocator.layer,
48).pipe(Layer.provideMerge(SqlitePersistenceMemory));
49const TestLayer = ProviderRuntimeRecovery.layer.pipe(
50 Layer.provideMerge(EventSink.layer.pipe(Layer.provideMerge(stores))),
48).pipe(Layer.provideMerge(SqlitePersistence.layerMemory));
49const layerTest = ProviderRuntimeRecovery.layer.pipe(
50 Layer.provideMerge(EventSink.layer.pipe(Layer.provideMerge(layerStores))),
5151 Layer.provideMerge(ServerSettings.layerTest({ continueThreadsAfterServerUpdate: true })),
5252);
5353
......@@ -440,8 +440,8 @@ const measureCrash = Effect.fn(function* (scenario: Scenario) {
440440});
441441
442442const measure = Effect.fn(function* (scenario: Scenario) {
443 const graceful = yield* measureGraceful(scenario).pipe(Effect.provide(Layer.fresh(TestLayer)));
444 const crash = yield* measureCrash(scenario).pipe(Effect.provide(Layer.fresh(TestLayer)));
443 const graceful = yield* measureGraceful(scenario).pipe(Effect.provide(Layer.fresh(layerTest)));
444 const crash = yield* measureCrash(scenario).pipe(Effect.provide(Layer.fresh(layerTest)));
445445 return { ...scenario, ...graceful, ...crash } satisfies Measurement;
446446});
447447
apps/server/src/orchestration-v2/ProviderSessionManager.test.ts+69-71
......@@ -36,7 +36,7 @@ import * as ServerEnvironment from "../environment/ServerEnvironment.ts";
3636import * as ProjectService from "../project/ProjectService.ts";
3737import * as McpProviderSession from "../mcp/McpProviderSession.ts";
3838import * as McpSessionRegistry from "../mcp/McpSessionRegistry.ts";
39import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts";
39import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts";
4040import * as ServerSettings from "../serverSettings.ts";
4141import { CodexProviderCapabilitiesV2 } from "./Adapters/CodexAdapterV2.ts";
4242import * as EventSink from "./EventSink.ts";
......@@ -56,14 +56,14 @@ import * as ProviderEventIngestor from "./ProviderEventIngestor.ts";
5656import * as ThreadCommandExecutor from "./ThreadCommandExecutor.ts";
5757import * as ProviderSessionManager from "./ProviderSessionManager.ts";
5858
59const TestDatabaseLayer = SqlitePersistenceMemory;
60const TestStoresLayer = Layer.merge(EventStore.layer, ProjectionStore.layer).pipe(
61 Layer.provide(TestDatabaseLayer),
59const layerTestDatabase = SqlitePersistence.layerMemory;
60const layerTestStores = Layer.merge(EventStore.layer, ProjectionStore.layer).pipe(
61 Layer.provide(layerTestDatabase),
6262);
63const TestEventSinkLayer = EventSink.layer.pipe(
64 Layer.provide(Layer.mergeAll(TestStoresLayer, TestDatabaseLayer)),
63const layerTestEventSink = EventSink.layer.pipe(
64 Layer.provide(Layer.mergeAll(layerTestStores, layerTestDatabase)),
6565);
66const FailingReleaseEventSinkLayer = Layer.effect(
66const layerFailingReleaseEventSink = Layer.effect(
6767 EventSink.EventSinkV2,
6868 Effect.gen(function* () {
6969 const delegate = yield* EventSink.EventSinkV2;
......@@ -79,7 +79,7 @@ const FailingReleaseEventSinkLayer = Layer.effect(
7979 : delegate.write(input),
8080 });
8181 }),
82).pipe(Layer.provide(TestEventSinkLayer));
82).pipe(Layer.provide(layerTestEventSink));
8383
8484interface FlakyReleaseWrites {
8585 /** Which release writes fail right now. */
......@@ -94,7 +94,7 @@ interface FlakyReleaseWrites {
9494}
9595
9696// Fails release writes with a defect, the way a failed SQL commit surfaces.
97const makeFlakyReleaseEventSinkLayer = (flaky: FlakyReleaseWrites) =>
97const layerFlakyReleaseEventSink = (flaky: FlakyReleaseWrites) =>
9898 Layer.effect(
9999 EventSink.EventSinkV2,
100100 Effect.gen(function* () {
......@@ -125,7 +125,7 @@ const makeFlakyReleaseEventSinkLayer = (flaky: FlakyReleaseWrites) =>
125125 }),
126126 });
127127 }),
128 ).pipe(Layer.provide(TestEventSinkLayer));
128 ).pipe(Layer.provide(layerTestEventSink));
129129
130130const CodexCapabilities: OrchestrationV2ProviderCapabilities = CodexProviderCapabilitiesV2;
131131const ExclusiveCapabilities: OrchestrationV2ProviderCapabilities = {
......@@ -392,7 +392,7 @@ function makeProviderAdapter(
392392 };
393393}
394394
395function makeTestLayer(input: {
395function layerTest(input: {
396396 readonly state: Ref.Ref<TestProviderRuntimeState>;
397397 readonly idleTimeoutMs: number;
398398 readonly maxIdlePinMs?: number;
......@@ -413,13 +413,13 @@ function makeTestLayer(input: {
413413 readonly serverSettingsLayer?: ReturnType<typeof ServerSettings.layerTest>;
414414 readonly projectServiceLayer?: Layer.Layer<ProjectService.ProjectService>;
415415}) {
416 const configuredEventSinkLayer =
416 const layerConfiguredEventSink =
417417 input.flakyReleaseWrites !== undefined
418 ? makeFlakyReleaseEventSinkLayer(input.flakyReleaseWrites)
418 ? layerFlakyReleaseEventSink(input.flakyReleaseWrites)
419419 : input.failReleaseEventWrites
420 ? FailingReleaseEventSinkLayer
421 : TestEventSinkLayer;
422 const registryLayer = ProviderAdapterRegistry.makeSingleLayer(
420 ? layerFailingReleaseEventSink
421 : layerTestEventSink;
422 const layerRegistry = ProviderAdapterRegistry.layerSingle(
423423 makeProviderAdapter(input.state, {
424424 failEventStream: input.failEventStream ?? false,
425425 ...(input.capabilities === undefined ? {} : { capabilities: input.capabilities }),
......@@ -434,33 +434,33 @@ function makeTestLayer(input: {
434434 ...(input.beforeUnload === undefined ? {} : { beforeUnload: input.beforeUnload }),
435435 }),
436436 );
437 const providerEventIngestorTestLayer = ProviderEventIngestor.layer.pipe(
437 const layerProviderEventIngestorTest = ProviderEventIngestor.layer.pipe(
438438 Layer.provide(
439439 Layer.mergeAll(
440 configuredEventSinkLayer,
440 layerConfiguredEventSink,
441441 IdAllocator.layer,
442 TestStoresLayer,
442 layerTestStores,
443443 ThreadCommandExecutor.layer,
444444 ),
445445 ),
446446 );
447447 return Layer.mergeAll(
448 TestStoresLayer,
449 configuredEventSinkLayer,
448 layerTestStores,
449 layerConfiguredEventSink,
450450 IdAllocator.layer,
451 TestMcpRegistryLayer,
451 layerTestMcpRegistry,
452452 ProviderSessionManager.layerWithOptions({
453453 idleTimeoutMs: input.idleTimeoutMs,
454454 ...(input.maxIdlePinMs === undefined ? {} : { maxIdlePinMs: input.maxIdlePinMs }),
455455 }).pipe(
456456 Layer.provide(
457457 Layer.mergeAll(
458 registryLayer,
459 configuredEventSinkLayer,
458 layerRegistry,
459 layerConfiguredEventSink,
460460 IdAllocator.layer,
461 providerEventIngestorTestLayer,
462 TestMcpRegistryLayer,
463 TestStoresLayer,
461 layerProviderEventIngestorTest,
462 layerTestMcpRegistry,
463 layerTestStores,
464464 ...(input.serverSettingsLayer === undefined ? [] : [input.serverSettingsLayer]),
465465 ...(input.projectServiceLayer === undefined ? [] : [input.projectServiceLayer]),
466466 ),
......@@ -479,7 +479,7 @@ const fakeEnvironment = ServerEnvironment.ServerEnvironment.of({
479479 getDescriptor: Effect.die("unused"),
480480});
481481
482const TestMcpRegistryLayer = Layer.effect(
482const layerTestMcpRegistry = Layer.effect(
483483 McpSessionRegistry.McpSessionRegistry,
484484 McpSessionRegistry.__testing.make(),
485485).pipe(
......@@ -520,7 +520,7 @@ function runBrowserAccessScenario(input: {
520520 >([]);
521521 const projectId = ProjectId.make("project-provider-session-manager-browser-access");
522522 const threadId = ThreadId.make("thread-provider-session-manager-browser-access");
523 const projectServiceLayer = Layer.mock(ProjectService.ProjectService)({
523 const layerProjectService = Layer.mock(ProjectService.ProjectService)({
524524 getById: (requestedProjectId) =>
525525 Effect.succeed(
526526 input.projectExists === false
......@@ -548,11 +548,11 @@ function runBrowserAccessScenario(input: {
548548 .pipe(Effect.ignore);
549549 }).pipe(
550550 Effect.provide(
551 makeTestLayer({
551 layerTest({
552552 state,
553553 idleTimeoutMs: 1_000,
554554 mcpConfigs,
555 projectServiceLayer,
555 projectServiceLayer: layerProjectService,
556556 serverSettingsLayer: ServerSettings.layerTest({
557557 enableAgentBrowserAccess: input.enableAgentBrowserAccess,
558558 projectSettingsOverrides: {
......@@ -766,7 +766,7 @@ it.effect("ProviderSessionManagerV2 opens independent sessions concurrently", ()
766766
767767 yield* effect.pipe(
768768 Effect.provide(
769 makeTestLayer({
769 layerTest({
770770 state,
771771 idleTimeoutMs: 60_000,
772772 beforeOpen,
......@@ -823,7 +823,7 @@ it.effect("ProviderSessionManagerV2 closes every live session for a provider ins
823823
824824 yield* effect.pipe(
825825 Effect.provide(
826 makeTestLayer({
826 layerTest({
827827 state,
828828 idleTimeoutMs: 60_000,
829829 }),
......@@ -882,7 +882,7 @@ it.effect("ProviderSessionManagerV2 opens a duplicate session only once", () =>
882882
883883 yield* effect.pipe(
884884 Effect.provide(
885 makeTestLayer({
885 layerTest({
886886 state,
887887 idleTimeoutMs: 60_000,
888888 beforeOpen,
......@@ -923,7 +923,7 @@ it.effect("ProviderSessionManagerV2 releases live sessions when its layer shuts
923923
924924 yield* effect.pipe(
925925 Effect.provide(
926 makeTestLayer({
926 layerTest({
927927 state,
928928 idleTimeoutMs: 60_000,
929929 }),
......@@ -972,7 +972,7 @@ it.effect("ProviderSessionManagerV2 closes event subscriptions normally on serve
972972 assert.isEmpty(yield* bufferedSubscription.events.pipe(Stream.runCollect));
973973 });
974974
975 yield* effect.pipe(Effect.provide(makeTestLayer({ state, idleTimeoutMs: 60_000 })));
975 yield* effect.pipe(Effect.provide(layerTest({ state, idleTimeoutMs: 60_000 })));
976976 }),
977977);
978978
......@@ -1040,7 +1040,7 @@ it.effect("ProviderSessionManagerV2 drains subscribers when the provider stops",
10401040 assert.equal((yield* Ref.get(state)).closeCount, 1);
10411041 });
10421042
1043 yield* effect.pipe(Effect.provide(makeTestLayer({ state, idleTimeoutMs: 60_000 })));
1043 yield* effect.pipe(Effect.provide(layerTest({ state, idleTimeoutMs: 60_000 })));
10441044 }),
10451045);
10461046
......@@ -1095,7 +1095,7 @@ it.effect(
10951095
10961096 yield* effect.pipe(
10971097 Effect.provide(
1098 makeTestLayer({
1098 layerTest({
10991099 state,
11001100 idleTimeoutMs: 1_000,
11011101 mcpConfigs,
......@@ -1150,7 +1150,7 @@ it.effect(
11501150
11511151 yield* effect.pipe(
11521152 Effect.provide(
1153 makeTestLayer({
1153 layerTest({
11541154 state,
11551155 idleTimeoutMs: 1_000,
11561156 mcpConfigs,
......@@ -1252,7 +1252,7 @@ it.effect("ProviderSessionManagerV2 revokes MCP credentials when release persist
12521252
12531253 yield* effect.pipe(
12541254 Effect.provide(
1255 makeTestLayer({
1255 layerTest({
12561256 state,
12571257 idleTimeoutMs: 1_000,
12581258 mcpConfigs,
......@@ -1322,7 +1322,7 @@ it.effect("ProviderSessionManagerV2 duplicate detach preserves replacement MCP c
13221322
13231323 yield* effect.pipe(
13241324 Effect.provide(
1325 makeTestLayer({
1325 layerTest({
13261326 state,
13271327 idleTimeoutMs: 1_000,
13281328 capabilities: ExclusiveCapabilities,
......@@ -1398,7 +1398,7 @@ it.effect(
13981398
13991399 yield* effect.pipe(
14001400 Effect.provide(
1401 makeTestLayer({
1401 layerTest({
14021402 state,
14031403 idleTimeoutMs: 1_000,
14041404 capabilities: ExclusiveCapabilities,
......@@ -1477,7 +1477,7 @@ it.effect(
14771477
14781478 yield* effect.pipe(
14791479 Effect.provide(
1480 makeTestLayer({
1480 layerTest({
14811481 state,
14821482 idleTimeoutMs: 1_000,
14831483 mcpConfigs,
......@@ -1537,9 +1537,7 @@ it.effect(
15371537 yield* manager.close(s1);
15381538 });
15391539
1540 yield* effect.pipe(
1541 Effect.provide(makeTestLayer({ state, idleTimeoutMs: 1_000, mcpConfigs })),
1542 );
1540 yield* effect.pipe(Effect.provide(layerTest({ state, idleTimeoutMs: 1_000, mcpConfigs })));
15431541 }),
15441542);
15451543
......@@ -1601,7 +1599,7 @@ it.effect(
16011599
16021600 yield* effect.pipe(
16031601 Effect.provide(
1604 makeTestLayer({
1602 layerTest({
16051603 state,
16061604 idleTimeoutMs: 1_000,
16071605 mcpConfigs,
......@@ -1656,7 +1654,7 @@ it.effect("ProviderSessionManagerV2 terminal detach revokes the thread's MCP cre
16561654 assert.isUndefined(McpProviderSession.readMcpProviderSession(threadId));
16571655 });
16581656
1659 yield* effect.pipe(Effect.provide(makeTestLayer({ state, idleTimeoutMs: 1_000, mcpConfigs })));
1657 yield* effect.pipe(Effect.provide(layerTest({ state, idleTimeoutMs: 1_000, mcpConfigs })));
16601658 }),
16611659);
16621660
......@@ -1704,7 +1702,7 @@ it.effect("ProviderSessionManagerV2 releases idle sessions without sweeping all
17041702 assert.equal(projection.providerSessions.at(-1)?.status, "stopped");
17051703 });
17061704
1707 yield* effect.pipe(Effect.provide(makeTestLayer({ state, idleTimeoutMs: 1000 })));
1705 yield* effect.pipe(Effect.provide(layerTest({ state, idleTimeoutMs: 1000 })));
17081706 }),
17091707);
17101708
......@@ -1750,7 +1748,7 @@ it.effect("ProviderSessionManagerV2 persists release when session scope close ha
17501748 });
17511749
17521750 yield* effect.pipe(
1753 Effect.provide(makeTestLayer({ state, idleTimeoutMs: 1000, hangSessionScopeClose: true })),
1751 Effect.provide(layerTest({ state, idleTimeoutMs: 1000, hangSessionScopeClose: true })),
17541752 );
17551753 }),
17561754);
......@@ -1799,7 +1797,7 @@ it.effect("ProviderSessionManagerV2 defers idle release while background work is
17991797
18001798 yield* effect.pipe(
18011799 Effect.provide(
1802 makeTestLayer({
1800 layerTest({
18031801 state,
18041802 idleTimeoutMs: 1000,
18051803 hasPendingBackgroundWork: Ref.get(pendingWork),
......@@ -1850,7 +1848,7 @@ it.effect("ProviderSessionManagerV2 releases pinned idle sessions once the pin c
18501848
18511849 yield* effect.pipe(
18521850 Effect.provide(
1853 makeTestLayer({
1851 layerTest({
18541852 state,
18551853 idleTimeoutMs: 1000,
18561854 maxIdlePinMs: 3000,
......@@ -1969,7 +1967,7 @@ it.effect(
19691967
19701968 yield* effect.pipe(
19711969 Effect.provide(
1972 makeTestLayer({
1970 layerTest({
19731971 state,
19741972 idleTimeoutMs: 1000,
19751973 // Uninterruptible so the markBusy-triggered interrupt cannot land
......@@ -2063,7 +2061,7 @@ it.effect("ProviderSessionManagerV2 does not apply a stale idle pin to a replace
20632061
20642062 yield* effect.pipe(
20652063 Effect.provide(
2066 makeTestLayer({
2064 layerTest({
20672065 state,
20682066 idleTimeoutMs: 1000,
20692067 maxIdlePinMs: 60_000,
......@@ -2176,7 +2174,7 @@ it.effect(
21762174 assert.equal(projection.providerSessions.at(-1)?.status, "stopped");
21772175 });
21782176
2179 yield* effect.pipe(Effect.provide(makeTestLayer({ state, idleTimeoutMs: 1000 })));
2177 yield* effect.pipe(Effect.provide(layerTest({ state, idleTimeoutMs: 1000 })));
21802178 }),
21812179);
21822180
......@@ -2226,7 +2224,7 @@ it.effect("ProviderSessionManagerV2 uses the same release path for runtime failu
22262224 assert.equal(projection.providerSessions.at(-1)?.lastError, "process exited");
22272225 });
22282226
2229 yield* effect.pipe(Effect.provide(makeTestLayer({ state, idleTimeoutMs: 1000 })));
2227 yield* effect.pipe(Effect.provide(layerTest({ state, idleTimeoutMs: 1000 })));
22302228 }),
22312229);
22322230
......@@ -2274,7 +2272,7 @@ it.effect("ProviderSessionManagerV2 releases sessions when provider event stream
22742272
22752273 yield* effect.pipe(
22762274 Effect.provide(
2277 makeTestLayer({
2275 layerTest({
22782276 state,
22792277 idleTimeoutMs: 1000,
22802278 failEventStream: true,
......@@ -2347,7 +2345,7 @@ it.effect("ProviderSessionManagerV2 marks pending runtime requests non-live on r
23472345 assert.equal(requestTurnItem?.status, "failed");
23482346 });
23492347
2350 yield* effect.pipe(Effect.provide(makeTestLayer({ state, idleTimeoutMs: 1000 })));
2348 yield* effect.pipe(Effect.provide(layerTest({ state, idleTimeoutMs: 1000 })));
23512349 }),
23522350);
23532351it.effect("ProviderSessionManagerV2 retries release records that failed to persist", () =>
......@@ -2408,7 +2406,7 @@ it.effect("ProviderSessionManagerV2 retries release records that failed to persi
24082406 });
24092407
24102408 yield* effect.pipe(
2411 Effect.provide(makeTestLayer({ state, idleTimeoutMs: 60_000, flakyReleaseWrites: flaky })),
2409 Effect.provide(layerTest({ state, idleTimeoutMs: 60_000, flakyReleaseWrites: flaky })),
24122410 );
24132411 }),
24142412);
......@@ -2481,7 +2479,7 @@ it.effect("ProviderSessionManagerV2 release retries leave a replacement session
24812479 });
24822480
24832481 yield* effect.pipe(
2484 Effect.provide(makeTestLayer({ state, idleTimeoutMs: 60_000, flakyReleaseWrites: flaky })),
2482 Effect.provide(layerTest({ state, idleTimeoutMs: 60_000, flakyReleaseWrites: flaky })),
24852483 );
24862484 }),
24872485);
......@@ -2572,7 +2570,7 @@ it.effect("ProviderSessionManagerV2 keeps each failed release's cleanup", () =>
25722570 });
25732571
25742572 yield* effect.pipe(
2575 Effect.provide(makeTestLayer({ state, idleTimeoutMs: 60_000, flakyReleaseWrites: flaky })),
2573 Effect.provide(layerTest({ state, idleTimeoutMs: 60_000, flakyReleaseWrites: flaky })),
25762574 );
25772575 }),
25782576);
......@@ -2638,7 +2636,7 @@ it.effect("ProviderSessionManagerV2 settles a request the event pump persists du
26382636 });
26392637
26402638 yield* effect.pipe(
2641 Effect.provide(makeTestLayer({ state, idleTimeoutMs: 60_000, flakyReleaseWrites: flaky })),
2639 Effect.provide(layerTest({ state, idleTimeoutMs: 60_000, flakyReleaseWrites: flaky })),
26422640 );
26432641 }),
26442642);
......@@ -2712,7 +2710,7 @@ it.effect("ProviderSessionManagerV2 terminalizes a pending input transcript item
27122710 assert.equal(requestTurnItem?.status, "failed");
27132711 });
27142712
2715 yield* effect.pipe(Effect.provide(makeTestLayer({ state, idleTimeoutMs: 1000 })));
2713 yield* effect.pipe(Effect.provide(layerTest({ state, idleTimeoutMs: 1000 })));
27162714 }),
27172715);
27182716
......@@ -2797,7 +2795,7 @@ it.effect("ProviderSessionManagerV2 persists session-scoped runtime requests wit
27972795 assert.equal(turnItem?.status, "waiting");
27982796 });
27992797
2800 yield* effect.pipe(Effect.provide(makeTestLayer({ state, idleTimeoutMs: 1000 })));
2798 yield* effect.pipe(Effect.provide(layerTest({ state, idleTimeoutMs: 1000 })));
28012799 }),
28022800);
28032801
......@@ -2871,7 +2869,7 @@ it.effect(
28712869
28722870 yield* effect.pipe(
28732871 Effect.provide(
2874 makeTestLayer({
2872 layerTest({
28752873 state,
28762874 idleTimeoutMs: 1000,
28772875 beforeOpen: (input) =>
......@@ -3025,7 +3023,7 @@ it.effect(
30253023 assert.equal((yield* Ref.get(state)).closeCount, 1);
30263024 });
30273025
3028 yield* effect.pipe(Effect.provide(makeTestLayer({ state, idleTimeoutMs: 1000 })));
3026 yield* effect.pipe(Effect.provide(layerTest({ state, idleTimeoutMs: 1000 })));
30293027 }),
30303028);
30313029
......@@ -3190,7 +3188,7 @@ it.effect(
31903188 assert.equal((yield* Ref.get(state)).closeCount, 1);
31913189 });
31923190
3193 yield* effect.pipe(Effect.provide(makeTestLayer({ state, idleTimeoutMs: 1000 })));
3191 yield* effect.pipe(Effect.provide(layerTest({ state, idleTimeoutMs: 1000 })));
31943192 }),
31953193);
31963194
......@@ -3294,7 +3292,7 @@ it.effect(
32943292 });
32953293
32963294 yield* effect.pipe(
3297 Effect.provide(makeTestLayer({ state, idleTimeoutMs: 1000, beforeUnload })),
3295 Effect.provide(layerTest({ state, idleTimeoutMs: 1000, beforeUnload })),
32983296 Effect.scoped,
32993297 );
33003298 }),
......@@ -3353,7 +3351,7 @@ it.effect(
33533351
33543352 yield* effect.pipe(
33553353 Effect.provide(
3356 makeTestLayer({ state, idleTimeoutMs: 1000, capabilities: ExclusiveCapabilities }),
3354 layerTest({ state, idleTimeoutMs: 1000, capabilities: ExclusiveCapabilities }),
33573355 ),
33583356 );
33593357 }),
......@@ -3404,7 +3402,7 @@ it.effect.each(["missing", "file"] as const)(
34043402 (yield* projectionStore.getThreadProjection(threadId)).providerSessions,
34053403 [],
34063404 );
3407 }).pipe(Effect.provide(makeTestLayer({ state, idleTimeoutMs: 60_000 })));
3405 }).pipe(Effect.provide(layerTest({ state, idleTimeoutMs: 60_000 })));
34083406 }).pipe(Effect.provide(NodeServices.layer)),
34093407);
34103408
......@@ -3454,7 +3452,7 @@ it.effect(
34543452 (yield* projectionStore.getThreadProjection(threadId)).providerSessions,
34553453 before.providerSessions,
34563454 );
3457 }).pipe(Effect.provide(makeTestLayer({ state, idleTimeoutMs: 60_000 })));
3455 }).pipe(Effect.provide(layerTest({ state, idleTimeoutMs: 60_000 })));
34583456 }).pipe(Effect.provide(NodeServices.layer)),
34593457);
34603458
apps/server/src/orchestration-v2/ProviderSwitchService.test.ts+12-12
......@@ -65,7 +65,7 @@ function deadSessionRecord(
6565 };
6666}
6767
68function testLayer(
68function layerTest(
6969 metadata: Readonly<Record<string, { continuationKey: string }>>,
7070 planSelectionTransition: ProviderAdapterV2Shape["planSelectionTransition"] = () =>
7171 Effect.succeed({ type: "restart_session" }),
......@@ -77,7 +77,7 @@ function testLayer(
7777 planSelectionTransition,
7878 openSession: () => Effect.die("ProviderSwitchService tests do not open sessions."),
7979 });
80 const registry = Layer.mock(ProviderAdapterRegistry.ProviderAdapterRegistryV2)({
80 const layerRegistry = Layer.mock(ProviderAdapterRegistry.ProviderAdapterRegistryV2)({
8181 get: (instanceId) =>
8282 metadata[instanceId] === undefined
8383 ? Effect.fail(
......@@ -99,7 +99,7 @@ function testLayer(
9999 });
100100 },
101101 });
102 return ProviderSwitch.layer.pipe(Layer.provide(registry));
102 return ProviderSwitch.layer.pipe(Layer.provide(layerRegistry));
103103}
104104
105105it.effect(
......@@ -115,7 +115,7 @@ it.effect(
115115 assert.deepEqual(result.releaseProviderSessionIds, [currentSessionId]);
116116 }).pipe(
117117 Effect.provide(
118 testLayer({ [currentInstanceId]: { continuationKey: "codex:account:primary" } }),
118 layerTest({ [currentInstanceId]: { continuationKey: "codex:account:primary" } }),
119119 ),
120120 ),
121121);
......@@ -140,7 +140,7 @@ it.effect.each(["stopped", "error"] as const)(
140140 assert.deepEqual(result.releaseProviderSessionIds, [currentSessionId]);
141141 }).pipe(
142142 Effect.provide(
143 testLayer({ [currentInstanceId]: { continuationKey: "codex:account:primary" } }),
143 layerTest({ [currentInstanceId]: { continuationKey: "codex:account:primary" } }),
144144 ),
145145 ),
146146);
......@@ -169,7 +169,7 @@ it.effect("releases the newest live session, not the newest record overall", ()
169169 assert.deepEqual(result.releaseProviderSessionIds, [newerLiveSessionId]);
170170 }).pipe(
171171 Effect.provide(
172 testLayer({ [currentInstanceId]: { continuationKey: "codex:account:primary" } }),
172 layerTest({ [currentInstanceId]: { continuationKey: "codex:account:primary" } }),
173173 ),
174174 ),
175175);
......@@ -192,7 +192,7 @@ it.effect("creates a fresh session with handoff when every recorded session is d
192192 assert.deepEqual(result.releaseProviderSessionIds, []);
193193 }).pipe(
194194 Effect.provide(
195 testLayer({ [currentInstanceId]: { continuationKey: "codex:account:primary" } }),
195 layerTest({ [currentInstanceId]: { continuationKey: "codex:account:primary" } }),
196196 ),
197197 ),
198198);
......@@ -246,7 +246,7 @@ it.effect("falls back to the native provider thread when every recorded session
246246 assert.deepEqual(result.releaseProviderSessionIds, []);
247247 }).pipe(
248248 Effect.provide(
249 testLayer({ [currentInstanceId]: { continuationKey: "codex:account:primary" } }),
249 layerTest({ [currentInstanceId]: { continuationKey: "codex:account:primary" } }),
250250 ),
251251 ),
252252);
......@@ -265,7 +265,7 @@ it.effect("hands off from a native provider thread after its session detaches",
265265 assert.deepEqual(result.releaseProviderSessionIds, []);
266266 }).pipe(
267267 Effect.provide(
268 testLayer({
268 layerTest({
269269 [currentInstanceId]: { continuationKey: "codex:account:primary" },
270270 codex_other: { continuationKey: "codex:account:other" },
271271 }),
......@@ -290,7 +290,7 @@ it.effect.each(["stopped", "error"] as const)(
290290 assert.deepEqual(result.releaseProviderSessionIds, []);
291291 }).pipe(
292292 Effect.provide(
293 testLayer({ [currentInstanceId]: { continuationKey: "codex:account:primary" } }, (input) =>
293 layerTest({ [currentInstanceId]: { continuationKey: "codex:account:primary" } }, (input) =>
294294 Effect.succeed(acpSelectionTransition(input)),
295295 ),
296296 ),
......@@ -309,7 +309,7 @@ it.effect("rejects a model change the dead record never negotiated support for",
309309 assert.instanceOf(result, ProviderSwitch.ProviderSwitchPlanError);
310310 }).pipe(
311311 Effect.provide(
312 testLayer({ [currentInstanceId]: { continuationKey: "codex:account:primary" } }, (input) =>
312 layerTest({ [currentInstanceId]: { continuationKey: "codex:account:primary" } }, (input) =>
313313 Effect.succeed(acpSelectionTransition(input)),
314314 ),
315315 ),
......@@ -333,7 +333,7 @@ it.effect("distinguishes compatible and incompatible instances of the same drive
333333 assert.equal(incompatible.transition.type, "create_with_handoff");
334334 }).pipe(
335335 Effect.provide(
336 testLayer({
336 layerTest({
337337 [currentInstanceId]: { continuationKey: "codex:account:primary" },
338338 codex_compatible: { continuationKey: "codex:account:primary" },
339339 codex_incompatible: { continuationKey: "codex:account:other" },
apps/server/src/orchestration-v2/ProviderTurnControlService.test.ts+6-6
......@@ -207,7 +207,7 @@ it.effect(
207207 rollbackThread: () => Effect.die("unused rollbackThread"),
208208 forkThread: () => Effect.die("unused forkThread"),
209209 };
210 const projectionLayer = Layer.succeed(
210 const layerProjection = Layer.succeed(
211211 ProjectionStore.ProjectionStoreV2,
212212 ProjectionStore.ProjectionStoreV2.of({
213213 apply: () => Effect.void,
......@@ -258,7 +258,7 @@ it.effect(
258258 getThreadSnapshotWindow: () => Effect.die("unused getThreadSnapshotWindow"),
259259 }),
260260 );
261 const sessionManagerLayer = Layer.succeed(
261 const layerSessionManager = Layer.succeed(
262262 ProviderSessionManager.ProviderSessionManagerV2,
263263 ProviderSessionManager.ProviderSessionManagerV2.of({
264264 shutdown: Effect.void,
......@@ -273,8 +273,8 @@ it.effect(
273273 detach: () => Effect.void,
274274 }),
275275 );
276 const controlLayer = ProviderTurnControlService.layer.pipe(
277 Layer.provide(Layer.merge(projectionLayer, sessionManagerLayer)),
276 const layerControl = ProviderTurnControlService.layer.pipe(
277 Layer.provide(Layer.merge(layerProjection, layerSessionManager)),
278278 );
279279
280280 const [ordinaryInterrupt, unrelatedRestart] = yield* Effect.gen(function* () {
......@@ -298,7 +298,7 @@ it.effect(
298298 }),
299299 );
300300 return [ordinary, unrelated] as const;
301 }).pipe(Effect.provide(controlLayer));
301 }).pipe(Effect.provide(layerControl));
302302
303303 assert.isTrue(Exit.isFailure(ordinaryInterrupt));
304304 assert.isTrue(Exit.isFailure(unrelatedRestart));
......@@ -314,7 +314,7 @@ it.effect(
314314 providerTurnId,
315315 interruptedAttemptId: attemptId,
316316 });
317 }).pipe(Effect.provide(controlLayer));
317 }).pipe(Effect.provide(layerControl));
318318
319319 const interrupted = yield* Ref.get(interruptedThread);
320320 assert.isNotNull(interrupted);
apps/server/src/orchestration-v2/ProviderTurnStartService.testkit.ts+1-1
......@@ -5,7 +5,7 @@ import * as Option from "effect/Option";
55import * as GitWorkflow from "../git/GitWorkflowService.ts";
66import * as ProjectService from "../project/ProjectService.ts";
77
8export const worktreeRepairDependenciesTestLayer = Layer.merge(
8export const layer = Layer.merge(
99 Layer.mock(GitWorkflow.GitWorkflowService)({
1010 pruneWorktrees: () => Effect.void,
1111 createWorktree: () => Effect.succeed({} as never),
apps/server/src/orchestration-v2/PullRequestSyncReactor.test.ts+2-2
......@@ -213,7 +213,7 @@ const makeHarness = Effect.fn("makePullRequestSyncHarness")(function* (options:
213213 return Effect.die(new Error(`Unexpected command: ${command.type}`));
214214 };
215215
216 const dependencies = Layer.mergeAll(
216 const layerDependencies = Layer.mergeAll(
217217 Layer.mock(PullRequestService.PullRequestService)({
218218 summary,
219219 stack,
......@@ -269,7 +269,7 @@ const makeHarness = Effect.fn("makePullRequestSyncHarness")(function* (options:
269269 summaryCalls,
270270 stackCalls,
271271 domainEvents,
272 layer: PullRequestSyncReactor.layer.pipe(Layer.provide(dependencies)),
272 layer: PullRequestSyncReactor.layer.pipe(Layer.provide(layerDependencies)),
273273 };
274274});
275275
apps/server/src/orchestration-v2/ResourceCleanupService.ts+1-1
......@@ -30,7 +30,7 @@ export class ResourceCleanupService extends Context.Reference<{
3030 }),
3131}) {}
3232
33export const live = Layer.effect(
33export const layer = Layer.effect(
3434 ResourceCleanupService,
3535 Effect.gen(function* () {
3636 const terminals = yield* TerminalManager.TerminalManager;
apps/server/src/orchestration-v2/RestartContinuation.test.ts+16-7
......@@ -319,7 +319,7 @@ it.effect("does not duplicate delivery and yields to newer user work or opt-out"
319319 const commands: Parameters<
320320 ThreadManagementService.ThreadManagementService["Service"]["dispatch"]
321321 >[0][] = [];
322 const threads = Layer.mock(ThreadManagementService.ThreadManagementService)({
322 const layerThreads = Layer.mock(ThreadManagementService.ThreadManagementService)({
323323 getThreadRecords: () => Effect.succeed(projection),
324324 recoverDelegatedTask: () => Effect.void,
325325 dispatch: (command) => {
......@@ -329,12 +329,16 @@ it.effect("does not duplicate delivery and yields to newer user work or opt-out"
329329 return Effect.succeed({} as never);
330330 },
331331 });
332 const enabled = Layer.merge(
333 threads,
332 const layerEnabled = Layer.merge(
333 layerThreads,
334334 ServerSettings.layerTest({ continueThreadsAfterServerUpdate: true }),
335335 );
336 yield* continueRestartedRun({ threadId, sourceRunId: runId }).pipe(Effect.provide(enabled));
337 yield* continueRestartedRun({ threadId, sourceRunId: runId }).pipe(Effect.provide(enabled));
336 yield* continueRestartedRun({ threadId, sourceRunId: runId }).pipe(
337 Effect.provide(layerEnabled),
338 );
339 yield* continueRestartedRun({ threadId, sourceRunId: runId }).pipe(
340 Effect.provide(layerEnabled),
341 );
338342 assert.lengthOf(commands, 1);
339343 assert.match(String(commands[0]!.commandId), /run:restart$/);
340344 if (commands[0]!.type === "message.dispatch")
......@@ -352,12 +356,17 @@ it.effect("does not duplicate delivery and yields to newer user work or opt-out"
352356 },
353357 ],
354358 };
355 yield* continueRestartedRun({ threadId, sourceRunId: runId }).pipe(Effect.provide(enabled));
359 yield* continueRestartedRun({ threadId, sourceRunId: runId }).pipe(
360 Effect.provide(layerEnabled),
361 );
356362 assert.lengthOf(commands, 1);
357363 projection = { ...projection, runs: [projection.runs[0]!] };
358364 yield* continueRestartedRun({ threadId, sourceRunId: runId }).pipe(
359365 Effect.provide(
360 Layer.merge(threads, ServerSettings.layerTest({ continueThreadsAfterServerUpdate: false })),
366 Layer.merge(
367 layerThreads,
368 ServerSettings.layerTest({ continueThreadsAfterServerUpdate: false }),
369 ),
361370 ),
362371 );
363372 assert.lengthOf(commands, 1);
apps/server/src/orchestration-v2/RunCompletionReads.test.ts+6-6
......@@ -13,12 +13,12 @@ import * as DateTime from "effect/DateTime";
1313import * as Effect from "effect/Effect";
1414import * as Layer from "effect/Layer";
1515import * as SqlClient from "effect/sql/SqlClient";
16import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts";
16import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts";
1717import * as ProjectionStore from "./ProjectionStore.ts";
1818
19const databaseLayer = Layer.mergeAll(
20 SqlitePersistenceMemory,
21 ProjectionStore.layer.pipe(Layer.provide(SqlitePersistenceMemory)),
19const layerDatabase = Layer.mergeAll(
20 SqlitePersistence.layerMemory,
21 ProjectionStore.layer.pipe(Layer.provide(SqlitePersistence.layerMemory)),
2222);
2323
2424it.effect.each(["sqlite", "memory"] as const)(
......@@ -155,8 +155,8 @@ it.effect.each(["sqlite", "memory"] as const)(
155155 }).pipe(
156156 Effect.provide(
157157 storage === "sqlite"
158 ? databaseLayer
159 : Layer.merge(SqlitePersistenceMemory, ProjectionStore.layerMemory),
158 ? layerDatabase
159 : Layer.merge(SqlitePersistence.layerMemory, ProjectionStore.layerMemory),
160160 ),
161161 ),
162162);
apps/server/src/orchestration-v2/RunExecutionService.test.ts+28-28
......@@ -57,7 +57,7 @@ import * as RunFinalizationService from "./RunFinalizationService.ts";
5757
5858const driver = ProviderDriverKind.make("codex");
5959
60const RunExecutionTestLayer = RunExecutionService.layer.pipe(
60const layerRunExecutionTest = RunExecutionService.layer.pipe(
6161 Layer.provide(
6262 Layer.mergeAll(
6363 Layer.mock(CheckpointService.CheckpointServiceV2)({ captureBaseline: () => Effect.void }),
......@@ -588,7 +588,7 @@ it.effect("rechecks run ownership immediately before calling the provider", () =
588588
589589 assert.equal(yield* Ref.get(guardCalls), 2);
590590 assert.equal(yield* Ref.get(providerStarts), 0);
591 }).pipe(Effect.provide(RunExecutionTestLayer)),
591 }).pipe(Effect.provide(layerRunExecutionTest)),
592592);
593593
594594it.effect("fails the run when its ownership check cannot be read before calling the provider", () =>
......@@ -600,7 +600,7 @@ it.effect("fails the run when its ownership check cannot be read before calling
600600 const runId = RunId.make("run:run-execution-start-guard-read");
601601 const attemptId = RunAttemptId.make("attempt:run-execution-start-guard-read");
602602 const providerInstanceId = ProviderInstanceId.make("codex");
603 const testLayer = RunExecutionService.layer.pipe(
603 const layerTest = RunExecutionService.layer.pipe(
604604 Layer.provide(
605605 Layer.mergeAll(
606606 Layer.mock(CheckpointService.CheckpointServiceV2)({ captureBaseline: () => Effect.void }),
......@@ -686,7 +686,7 @@ it.effect("fails the run when its ownership check cannot be read before calling
686686 },
687687 },
688688 });
689 }).pipe(Effect.provide(testLayer));
689 }).pipe(Effect.provide(layerTest));
690690
691691 assert.equal(yield* Ref.get(guardCalls), 2);
692692 assert.equal(yield* Ref.get(providerStarts), 0);
......@@ -778,7 +778,7 @@ it.effect(
778778 calls,
779779 cases.map((testCase) => testCase.expected),
780780 );
781 }).pipe(Effect.provide(RunExecutionTestLayer)),
781 }).pipe(Effect.provide(layerRunExecutionTest)),
782782);
783783
784784it.effect("refreshes MCP credential liveness before calling the provider", () =>
......@@ -849,7 +849,7 @@ it.effect("refreshes MCP credential liveness before calling the provider", () =>
849849 .pipe(Effect.ensuring(Effect.sync(() => touchActiveMcpThread.mockRestore())));
850850
851851 assert.deepEqual(yield* Ref.get(order), [`touch:${threadId}`, "start-turn"]);
852 }).pipe(Effect.provide(RunExecutionTestLayer)),
852 }).pipe(Effect.provide(layerRunExecutionTest)),
853853);
854854
855855it.effect("starts the provider when checkpoint baseline capture fails", () =>
......@@ -873,7 +873,7 @@ it.effect("starts the provider when checkpoint baseline capture fails", () =>
873873 effects: ReadonlyArray<PendingOrchestrationEffectV2>;
874874 }>
875875 >([]);
876 const testLayer = RunExecutionService.layer.pipe(
876 const layerTest = RunExecutionService.layer.pipe(
877877 Layer.provide(
878878 Layer.mergeAll(
879879 Layer.mock(CheckpointService.CheckpointServiceV2)({
......@@ -950,7 +950,7 @@ it.effect("starts the provider when checkpoint baseline capture fails", () =>
950950 },
951951 },
952952 });
953 }).pipe(Effect.provide(testLayer));
953 }).pipe(Effect.provide(layerTest));
954954
955955 assert.equal(yield* Ref.get(providerStarts), 1);
956956
......@@ -990,7 +990,7 @@ it.effect.each(["failure", "interruption", "stale-attempt", "start-guard"] as co
990990 const refreshes = yield* Ref.make(0);
991991 const guardedWrites = yield* Ref.make(0);
992992 const writes = yield* Ref.make<ReadonlyArray<ReadonlyArray<OrchestrationV2DomainEvent>>>([]);
993 const testLayer = RunExecutionService.layer.pipe(
993 const layerTest = RunExecutionService.layer.pipe(
994994 Layer.provide(
995995 Layer.mergeAll(
996996 Layer.mock(CheckpointService.CheckpointServiceV2)({
......@@ -1092,7 +1092,7 @@ it.effect.each(["failure", "interruption", "stale-attempt", "start-guard"] as co
10921092 },
10931093 },
10941094 });
1095 }).pipe(Effect.provide(testLayer), Effect.exit);
1095 }).pipe(Effect.provide(layerTest), Effect.exit);
10961096
10971097 assert.equal(yield* Ref.get(providerStarts), 0);
10981098 const events = (yield* Ref.get(writes)).flat();
......@@ -1160,7 +1160,7 @@ it.effect("keeps ingesting owned child events after the root turn terminalizes",
11601160 const subagentNodeId = NodeId.make("node:run-execution-late-child:subagent");
11611161 const childMessageIngested = yield* Deferred.make<void>();
11621162 const order = yield* Ref.make<ReadonlyArray<string>>([]);
1163 const testLayer = RunExecutionService.layer.pipe(
1163 const layerTest = RunExecutionService.layer.pipe(
11641164 Layer.provide(
11651165 Layer.mergeAll(
11661166 Layer.mock(CheckpointService.CheckpointServiceV2)({ captureBaseline: () => Effect.void }),
......@@ -1337,7 +1337,7 @@ it.effect("keeps ingesting owned child events after the root turn terminalizes",
13371337 },
13381338 },
13391339 });
1340 }).pipe(Effect.provide(testLayer));
1340 }).pipe(Effect.provide(layerTest));
13411341
13421342 const observed = yield* Deferred.await(childMessageIngested).pipe(
13431343 Effect.timeoutOption("2 seconds"),
......@@ -1585,7 +1585,7 @@ it.effect(
15851585 }>
15861586 >([]);
15871587 const ingestionDone = yield* Deferred.make<void>();
1588 const testLayer = RunExecutionService.layer.pipe(
1588 const layerTest = RunExecutionService.layer.pipe(
15891589 Layer.provide(
15901590 Layer.mergeAll(
15911591 Layer.mock(CheckpointService.CheckpointServiceV2)({
......@@ -1750,7 +1750,7 @@ it.effect(
17501750 },
17511751 },
17521752 });
1753 }).pipe(Effect.provide(testLayer));
1753 }).pipe(Effect.provide(layerTest));
17541754
17551755 const closed = yield* Deferred.await(ingestionDone).pipe(Effect.timeoutOption("2 seconds"));
17561756 assert.isTrue(Option.isSome(closed), "event subscription did not release");
......@@ -1802,7 +1802,7 @@ it.effect("drops late root provider-thread writes from a superseded attempt", ()
18021802 readonly committed: boolean;
18031803 }>
18041804 >([]);
1805 const testLayer = RunExecutionService.layer.pipe(
1805 const layerTest = RunExecutionService.layer.pipe(
18061806 Layer.provide(
18071807 Layer.mergeAll(
18081808 Layer.mock(CheckpointService.CheckpointServiceV2)({ captureBaseline: () => Effect.void }),
......@@ -1966,7 +1966,7 @@ it.effect("drops late root provider-thread writes from a superseded attempt", ()
19661966 },
19671967 },
19681968 });
1969 }).pipe(Effect.provide(testLayer));
1969 }).pipe(Effect.provide(layerTest));
19701970
19711971 const closed = yield* Deferred.await(ingestionDone).pipe(Effect.timeoutOption("2 seconds"));
19721972 assert.isTrue(
......@@ -2006,7 +2006,7 @@ it.effect(
20062006 // Probe stays true forever; open background items must pin the stream
20072007 // past ownership-loss so late turn_item completions still land.
20082008 const ingestionDone = yield* Deferred.make<void>();
2009 const testLayer = RunExecutionService.layer.pipe(
2009 const layerTest = RunExecutionService.layer.pipe(
20102010 Layer.provide(
20112011 Layer.mergeAll(
20122012 Layer.mock(CheckpointService.CheckpointServiceV2)({
......@@ -2149,7 +2149,7 @@ it.effect(
21492149 },
21502150 },
21512151 });
2152 }).pipe(Effect.provide(testLayer));
2152 }).pipe(Effect.provide(layerTest));
21532153
21542154 const closed = yield* Deferred.await(ingestionDone).pipe(Effect.timeoutOption("2 seconds"));
21552155 assert.isTrue(
......@@ -2177,7 +2177,7 @@ it.effect(
21772177 const observed = yield* Ref.make<ReadonlyArray<string>>([]);
21782178 const ingestionDone = yield* Deferred.make<void>();
21792179 const scopedProbeArgs = yield* Ref.make<ReadonlyArray<ProviderThreadId>>([]);
2180 const testLayer = RunExecutionService.layer.pipe(
2180 const layerTest = RunExecutionService.layer.pipe(
21812181 Layer.provide(
21822182 Layer.mergeAll(
21832183 Layer.mock(CheckpointService.CheckpointServiceV2)({
......@@ -2319,7 +2319,7 @@ it.effect(
23192319 },
23202320 },
23212321 });
2322 }).pipe(Effect.provide(testLayer));
2322 }).pipe(Effect.provide(layerTest));
23232323
23242324 const closed = yield* Deferred.await(ingestionDone).pipe(Effect.timeoutOption("2 seconds"));
23252325 assert.isTrue(Option.isSome(closed), "event subscription did not release");
......@@ -2346,7 +2346,7 @@ it.effect(
23462346 const written = yield* Ref.make<ReadonlyArray<OrchestrationV2DomainEvent>>([]);
23472347 const ingested = yield* Ref.make<ReadonlyArray<ProviderAdapterV2Event>>([]);
23482348 const ingestionDone = yield* Deferred.make<void>();
2349 const testLayer = RunExecutionService.layer.pipe(
2349 const layerTest = RunExecutionService.layer.pipe(
23502350 Layer.provide(
23512351 Layer.mergeAll(
23522352 Layer.mock(CheckpointService.CheckpointServiceV2)({
......@@ -2559,7 +2559,7 @@ it.effect(
25592559 },
25602560 },
25612561 });
2562 }).pipe(Effect.provide(testLayer));
2562 }).pipe(Effect.provide(layerTest));
25632563
25642564 const closed = yield* Deferred.await(ingestionDone).pipe(Effect.timeoutOption("2 seconds"));
25652565 assert.isTrue(Option.isSome(closed), "event ingestion fiber did not finish");
......@@ -2719,7 +2719,7 @@ it.effect(
27192719 const providerInstanceId = ProviderInstanceId.make("codex");
27202720 const written = yield* Ref.make<ReadonlyArray<OrchestrationV2DomainEvent>>([]);
27212721 const ingestionDone = yield* Deferred.make<void>();
2722 const testLayer = RunExecutionService.layer.pipe(
2722 const layerTest = RunExecutionService.layer.pipe(
27232723 Layer.provide(
27242724 Layer.mergeAll(
27252725 Layer.mock(CheckpointService.CheckpointServiceV2)({
......@@ -2889,7 +2889,7 @@ it.effect(
28892889 },
28902890 },
28912891 });
2892 }).pipe(Effect.provide(testLayer));
2892 }).pipe(Effect.provide(layerTest));
28932893
28942894 const closed = yield* Deferred.await(ingestionDone).pipe(Effect.timeoutOption("2 seconds"));
28952895 assert.isTrue(Option.isSome(closed), "event ingestion fiber did not finish");
......@@ -3416,7 +3416,7 @@ function captureRootRunTermination(input: {
34163416 }
34173417 }
34183418 });
3419 const testLayer = RunExecutionService.layer.pipe(
3419 const layerTest = RunExecutionService.layer.pipe(
34203420 Layer.provide(
34213421 Layer.mergeAll(
34223422 Layer.mock(CheckpointService.CheckpointServiceV2)({ captureBaseline: () => Effect.void }),
......@@ -3540,7 +3540,7 @@ function captureRootRunTermination(input: {
35403540 },
35413541 },
35423542 });
3543 }).pipe(Effect.provide(testLayer));
3543 }).pipe(Effect.provide(layerTest));
35443544
35453545 yield* Deferred.await(ingestionDone);
35463546 return { written: yield* Ref.get(writtenItems), observed: yield* Ref.get(observed) };
......@@ -3876,7 +3876,7 @@ function runBackgroundItemScenario(
38763876 const providerInstanceId = ProviderInstanceId.make("codex");
38773877 const observed = yield* Ref.make<ReadonlyArray<string>>([]);
38783878 const ingestionDone = yield* Deferred.make<void>();
3879 const testLayer = RunExecutionService.layer.pipe(
3879 const layerTest = RunExecutionService.layer.pipe(
38803880 Layer.provide(
38813881 Layer.mergeAll(
38823882 Layer.mock(CheckpointService.CheckpointServiceV2)({ captureBaseline: () => Effect.void }),
......@@ -3986,7 +3986,7 @@ function runBackgroundItemScenario(
39863986 },
39873987 },
39883988 });
3989 }).pipe(Effect.provide(testLayer));
3989 }).pipe(Effect.provide(layerTest));
39903990
39913991 const closed = yield* Deferred.await(ingestionDone).pipe(Effect.timeoutOption("2 seconds"));
39923992 assert.isTrue(Option.isSome(closed), "event ingestion fiber did not finish");
apps/server/src/orchestration-v2/RunFinalizationService.test.ts+1-1
......@@ -87,7 +87,7 @@ it.effect.each(
8787 const refreshed: string[] = [];
8888 const threadId = ThreadId.make("thread-pr-refresh");
8989 const runId = RunId.make("completed-run");
90 const layer = RunFinalization.observerLive.pipe(
90 const layer = RunFinalization.layerObserver.pipe(
9191 Layer.provide(
9292 Layer.mergeAll(
9393 Layer.mock(WorkspaceEntries.WorkspaceEntries)({ refresh: () => Effect.void }),
apps/server/src/orchestration-v2/RunFinalizationService.ts+1-1
......@@ -87,7 +87,7 @@ const make = Effect.gen(function* () {
8787
8888export const layer = Layer.effect(RunFinalizationService, make);
8989
90export const observerLive = Layer.effect(
90export const layerObserver = Layer.effect(
9191 RunFinalizationObserver,
9292 Effect.gen(function* () {
9393 const workspaceEntries = yield* WorkspaceEntries.WorkspaceEntries;
apps/server/src/orchestration-v2/RuntimePolicy.test.ts+2-2
......@@ -76,7 +76,7 @@ const providerInstanceFor = (instanceId: ProviderInstanceId) =>
7676 },
7777 }) as ProviderInstance;
7878
79const TestLayer = RuntimePolicy.layerFromProjectStore.pipe(
79const layerTest = RuntimePolicy.layerFromProjectStore.pipe(
8080 Layer.provide(
8181 Layer.succeed(ProviderInstanceRegistry.ProviderInstanceRegistry, {
8282 getInstance: (instanceId) => Effect.succeed(providerInstanceFor(instanceId)),
......@@ -109,7 +109,7 @@ const TestLayer = RuntimePolicy.layerFromProjectStore.pipe(
109109 ),
110110);
111111
112it.layer(TestLayer)("RuntimePolicyV2", (it) => {
112it.layer(layerTest)("RuntimePolicyV2", (it) => {
113113 it.effect("uses the project root for local-checkout threads", () =>
114114 Effect.gen(function* () {
115115 const policy = yield* RuntimePolicy.RuntimePolicyV2;
apps/server/src/orchestration-v2/RuntimeRequestService.test.ts+13-13
......@@ -45,7 +45,7 @@ function resolvedRuntimeRequest(
4545 };
4646}
4747
48function runtimeRequestTestLayer(
48function layerRuntimeRequestTest(
4949 projection: OrchestrationV2ThreadProjection,
5050 getSession: ProviderSessionManager.ProviderSessionManagerV2Shape["get"],
5151) {
......@@ -96,7 +96,7 @@ it.effect("forwards orchestrator-resolved runtime requests to the live adapter",
9696 },
9797 ],
9898 } as unknown as OrchestrationV2ThreadProjection;
99 const testLayer = RuntimeRequestService.layer.pipe(
99 const layerTest = RuntimeRequestService.layer.pipe(
100100 Layer.provide(
101101 Layer.mergeAll(
102102 Layer.mock(ProjectionStore.ProjectionStoreV2)({
......@@ -125,7 +125,7 @@ it.effect("forwards orchestrator-resolved runtime requests to the live adapter",
125125 requestId,
126126 decision: "accept",
127127 });
128 }).pipe(Effect.provide(testLayer));
128 }).pipe(Effect.provide(layerTest));
129129});
130130
131131it.effect("rejects expired runtime requests before invoking the live adapter", () => {
......@@ -160,7 +160,7 @@ it.effect("rejects expired runtime requests before invoking the live adapter", (
160160 },
161161 ],
162162 } as unknown as OrchestrationV2ThreadProjection;
163 const testLayer = RuntimeRequestService.layer.pipe(
163 const layerTest = RuntimeRequestService.layer.pipe(
164164 Layer.provide(
165165 Layer.mergeAll(
166166 Layer.mock(ProjectionStore.ProjectionStoreV2)({
......@@ -193,7 +193,7 @@ it.effect("rejects expired runtime requests before invoking the live adapter", (
193193 assert.isUndefined(error.cause);
194194 assert.equal(getSession.mock.calls.length, 0);
195195 assert.equal(respondToRuntimeRequest.mock.calls.length, 0);
196 }).pipe(Effect.provide(testLayer));
196 }).pipe(Effect.provide(layerTest));
197197});
198198
199199it.effect("classifies runtime request response validation failures", () => {
......@@ -204,15 +204,15 @@ it.effect("classifies runtime request response validation failures", () => {
204204 );
205205 const requestId = RuntimeRequestId.make("request-validation");
206206 const unexpectedGetSession = vi.fn(() => Effect.succeed(Option.none()));
207 const missingLayer = runtimeRequestTestLayer(
207 const layerMissing = layerRuntimeRequestTest(
208208 projectionWithRuntimeRequest(),
209209 unexpectedGetSession,
210210 );
211 const notResumableLayer = runtimeRequestTestLayer(
211 const layerNotResumable = layerRuntimeRequestTest(
212212 projectionWithRuntimeRequest(resolvedRuntimeRequest(requestId, otherProviderSessionId)),
213213 unexpectedGetSession,
214214 );
215 const inactiveLayer = runtimeRequestTestLayer(
215 const layerInactive = layerRuntimeRequestTest(
216216 projectionWithRuntimeRequest(resolvedRuntimeRequest(requestId, providerSessionId)),
217217 () => Effect.succeed(Option.none()),
218218 );
......@@ -229,7 +229,7 @@ it.effect("classifies runtime request response validation failures", () => {
229229 });
230230
231231 return Effect.gen(function* () {
232 const missing = yield* respond.pipe(Effect.provide(missingLayer));
232 const missing = yield* respond.pipe(Effect.provide(layerMissing));
233233 assert.equal(missing.reason, "request-missing");
234234 assert.equal(
235235 missing.message,
......@@ -237,7 +237,7 @@ it.effect("classifies runtime request response validation failures", () => {
237237 );
238238 assert.isUndefined(missing.cause);
239239
240 const notResumable = yield* respond.pipe(Effect.provide(notResumableLayer));
240 const notResumable = yield* respond.pipe(Effect.provide(layerNotResumable));
241241 assert.equal(notResumable.reason, "request-not-resumable");
242242 assert.equal(
243243 notResumable.message,
......@@ -245,7 +245,7 @@ it.effect("classifies runtime request response validation failures", () => {
245245 );
246246 assert.isUndefined(notResumable.cause);
247247
248 const inactive = yield* respond.pipe(Effect.provide(inactiveLayer));
248 const inactive = yield* respond.pipe(Effect.provide(layerInactive));
249249 assert.equal(inactive.reason, "provider-session-not-active");
250250 assert.equal(
251251 inactive.message,
......@@ -266,7 +266,7 @@ it.effect("preserves genuine provider session lookup failures as the cause", ()
266266 providerSessionId,
267267 cause: "lookup failed",
268268 });
269 const testLayer = runtimeRequestTestLayer(
269 const layerTest = layerRuntimeRequestTest(
270270 projectionWithRuntimeRequest(resolvedRuntimeRequest(requestId, providerSessionId)),
271271 () => Effect.fail(lookupFailure),
272272 );
......@@ -289,5 +289,5 @@ it.effect("preserves genuine provider session lookup failures as the cause", ()
289289 `Failed to respond to runtime request ${requestId} on thread ${threadId} via provider session ${providerSessionId}.`,
290290 );
291291 assert.strictEqual(error.cause, lookupFailure);
292 }).pipe(Effect.provide(testLayer));
292 }).pipe(Effect.provide(layerTest));
293293});
apps/server/src/orchestration-v2/SelectionRestart.integration.test.ts+11-11
......@@ -35,7 +35,7 @@ import {
3535 type ProviderAdapterV2TurnInput,
3636} from "./ProviderAdapter.ts";
3737import * as ProviderAdapterRegistry from "./ProviderAdapterRegistry.ts";
38import { makeOrchestratorV2ReplayLayerWithRegistry } from "./testkit/ProviderReplayHarness.ts";
38import * as ProviderReplayHarness from "./testkit/ProviderReplayHarness.ts";
3939import { checkpointWorkspace } from "./testkit/ReplayFixtureWorkspace.ts";
4040
4141const driver = ProviderDriverKind.make("codex");
......@@ -403,7 +403,7 @@ it.live("restarts selection as a new attempt and retries after old-session clean
403403 closedSessionCount: 0,
404404 failedReplacementOpen: false,
405405 });
406 const registry = ProviderAdapterRegistry.makeSingleLayer(makeRestartAdapter(state));
406 const layerRegistry = ProviderAdapterRegistry.layerSingle(makeRestartAdapter(state));
407407
408408 const result = yield* Effect.gen(function* () {
409409 const orchestrator = yield* Orchestrator.OrchestratorV2;
......@@ -480,9 +480,9 @@ it.live("restarts selection as a new attempt and retries after old-session clean
480480 return yield* Effect.die("selection restart did not complete");
481481 }).pipe(
482482 Effect.provide(
483 makeOrchestratorV2ReplayLayerWithRegistry(
483 ProviderReplayHarness.layerWithRegistry(
484484 { name: "selection-restart-lifecycle" },
485 registry,
485 layerRegistry,
486486 ),
487487 ),
488488 );
......@@ -554,7 +554,7 @@ it.live.each(["stopped", "error"] as const)(
554554 // simulated replacement-open failure is skipped.
555555 failedReplacementOpen: true,
556556 });
557 const registry = ProviderAdapterRegistry.makeSingleLayer(
557 const layerRegistry = ProviderAdapterRegistry.layerSingle(
558558 makeRestartAdapter(state, exclusiveCapabilities),
559559 );
560560
......@@ -677,7 +677,7 @@ it.live.each(["stopped", "error"] as const)(
677677 liveSessionId: liveSession.id,
678678 detachedSessionIds,
679679 };
680 }).pipe(Effect.provide(makeOrchestratorV2ReplayLayerWithRegistry({ name }, registry)));
680 }).pipe(Effect.provide(ProviderReplayHarness.layerWithRegistry({ name }, layerRegistry)));
681681
682682 const { projection, captured } = result;
683683 assert.lengthOf(projection.runs, 2);
......@@ -719,7 +719,7 @@ it.live("detaches the old provider session after an active provider handoff", ()
719719 failedReplacementOpen: false,
720720 });
721721 const targetStartCount = yield* Ref.make(0);
722 const registry = ProviderAdapterRegistry.makeLayer([
722 const layerRegistry = ProviderAdapterRegistry.layerFromAdapters([
723723 makeRestartAdapter(state, exclusiveCapabilities),
724724 makeCompletingHandoffAdapter(targetStartCount),
725725 ]);
......@@ -803,9 +803,9 @@ it.live("detaches the old provider session after an active provider handoff", ()
803803 return yield* Effect.die("active provider handoff did not complete");
804804 }).pipe(
805805 Effect.provide(
806 makeOrchestratorV2ReplayLayerWithRegistry(
806 ProviderReplayHarness.layerWithRegistry(
807807 { name: "selection-provider-handoff-lifecycle" },
808 registry,
808 layerRegistry,
809809 ),
810810 ),
811811 );
......@@ -867,7 +867,7 @@ it.live.each(["active", "idle", "selection-command", "pooled", "separate-home"]
867867 ),
868868 } satisfies ProviderAdapterV2Shape;
869869 });
870 const registry = Layer.succeed(ProviderAdapterRegistry.ProviderAdapterRegistryV2, {
870 const layerRegistry = Layer.succeed(ProviderAdapterRegistry.ProviderAdapterRegistryV2, {
871871 get: (instanceId) =>
872872 Effect.succeed(adapters.find((adapter) => adapter.instanceId === instanceId)!),
873873 list: () => Effect.succeed([providerInstanceId, targetId]),
......@@ -990,7 +990,7 @@ it.live.each(["active", "idle", "selection-command", "pooled", "separate-home"]
990990 assert.equal(returnedThread.providerInstanceId, providerInstanceId);
991991 assert.isEmpty(third.contextHandoffs);
992992 assert.deepEqual(messages, ["first", "second", "third"]);
993 }).pipe(Effect.provide(makeOrchestratorV2ReplayLayerWithRegistry({ name }, registry)));
993 }).pipe(Effect.provide(ProviderReplayHarness.layerWithRegistry({ name }, layerRegistry)));
994994 }),
995995 ),
996996);
apps/server/src/orchestration-v2/SteeringCompletion.integration.test.ts+5-5
......@@ -30,7 +30,7 @@ import {
3030 type ProviderAdapterV2TurnInput,
3131} from "./ProviderAdapter.ts";
3232import * as ProviderAdapterRegistry from "./ProviderAdapterRegistry.ts";
33import { makeOrchestratorV2ReplayLayerWithRegistry } from "./testkit/ProviderReplayHarness.ts";
33import * as ProviderReplayHarness from "./testkit/ProviderReplayHarness.ts";
3434import { checkpointWorkspace } from "./testkit/ReplayFixtureWorkspace.ts";
3535
3636const driver = ProviderDriverKind.make("codex");
......@@ -399,9 +399,9 @@ it.effect.each(
399399 assert.equal(started.length, 2);
400400 }).pipe(
401401 Effect.provide(
402 makeOrchestratorV2ReplayLayerWithRegistry(
402 ProviderReplayHarness.layerWithRegistry(
403403 { name: `steering-completion-${timing}` },
404 ProviderAdapterRegistry.makeSingleLayer(adapter),
404 ProviderAdapterRegistry.layerSingle(adapter),
405405 { runEffectWorker: false },
406406 ),
407407 ),
......@@ -514,9 +514,9 @@ const nextTurnSelectionHarness = Effect.fn("nextTurnSelectionHarness")(function*
514514 };
515515 }),
516516 };
517 const layer = makeOrchestratorV2ReplayLayerWithRegistry(
517 const layer = ProviderReplayHarness.layerWithRegistry(
518518 { name },
519 ProviderAdapterRegistry.makeSingleLayer(adapter),
519 ProviderAdapterRegistry.layerSingle(adapter),
520520 { runEffectWorker: false },
521521 );
522522 // Creates the thread and starts its first turn on `runSelection`.
apps/server/src/orchestration-v2/ThreadFork.execution.test.ts+3-3
......@@ -23,7 +23,7 @@ import * as EventSink from "./EventSink.ts";
2323import * as Orchestrator from "./Orchestrator.ts";
2424import type { ProviderAdapterV2Shape } from "./ProviderAdapter.ts";
2525import * as ProviderAdapterRegistry from "./ProviderAdapterRegistry.ts";
26import { makeOrchestratorV2ReplayLayerWithRegistry } from "./testkit/ProviderReplayHarness.ts";
26import * as ProviderReplayHarness from "./testkit/ProviderReplayHarness.ts";
2727
2828const forkCases = (["codex", "claudeAgent"] as const).flatMap((driverName) => {
2929 const driver = ProviderDriverKind.make(driverName);
......@@ -39,9 +39,9 @@ const forkCases = (["codex", "claudeAgent"] as const).flatMap((driverName) => {
3939 planSelectionTransition: () => Effect.succeed({ type: "apply_on_next_turn" }),
4040 openSession: () => Effect.die("Execution is paused after dispatch for handoff inspection"),
4141 };
42 const layer = makeOrchestratorV2ReplayLayerWithRegistry(
42 const layer = ProviderReplayHarness.layerWithRegistry(
4343 { name: `fork-boundary-${driver}` },
44 ProviderAdapterRegistry.makeLayer([adapter]),
44 ProviderAdapterRegistry.layerFromAdapters([adapter]),
4545 { runEffectWorker: false },
4646 );
4747
apps/server/src/orchestration-v2/ThreadLaunchService.test.ts+37-28
......@@ -40,12 +40,12 @@ import * as Schema from "effect/Schema";
4040import * as TestClock from "effect/testing/TestClock";
4141
4242import * as GitWorkflow from "../git/GitWorkflowService.ts";
43import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts";
43import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts";
4444import * as ProjectStore from "./ProjectStore.ts";
4545import * as ProjectService from "../project/ProjectService.ts";
4646import * as ProjectSetupScriptRunner from "../project/ProjectSetupScriptRunner.ts";
4747import * as ManagedProjectFolders from "../project/ManagedProjectFolders.ts";
48import { makeProviderRegistryLayer } from "../provider/testUtils/providerRegistryMock.ts";
48import * as ProviderRegistryMock from "../provider/testUtils/providerRegistryMock.ts";
4949import * as ServerSettings from "../serverSettings.ts";
5050import * as ScheduledTasks from "../scheduledTasks/ScheduledTaskService.ts";
5151import * as SecretRequests from "../secrets/SecretRequests.ts";
......@@ -59,7 +59,7 @@ import * as ProviderAdapterRegistry from "./ProviderAdapterRegistry.ts";
5959import * as ThreadLaunch from "./ThreadLaunchService.ts";
6060import * as ThreadManagement from "./ThreadManagementService.ts";
6161import * as ThreadTitleRegeneration from "./ThreadTitleRegenerationService.ts";
62import { makeOrchestratorV2ReplayLayerWithRegistry } from "./testkit/ProviderReplayHarness.ts";
62import * as ProviderReplayHarness from "./testkit/ProviderReplayHarness.ts";
6363
6464const projectId = ProjectId.make("project:launch-test");
6565const otherProjectId = ProjectId.make("project:launch-other");
......@@ -110,16 +110,16 @@ interface HarnessOptions {
110110}
111111
112112function makeHarness(options: HarnessOptions = {}) {
113 const database = SqlitePersistenceMemory;
114 const registry = ProviderAdapterRegistry.makeLayer([adapter]);
115 const orchestrator = makeOrchestratorV2ReplayLayerWithRegistry(
113 const layerDatabase = SqlitePersistence.layerMemory;
114 const layerRegistry = ProviderAdapterRegistry.layerFromAdapters([adapter]);
115 const layerOrchestrator = ProviderReplayHarness.layerWithRegistry(
116116 { name: "thread-launch" },
117 registry,
118 { databaseLayer: database, runEffectWorker: false },
117 layerRegistry,
118 { databaseLayer: layerDatabase, runEffectWorker: false },
119119 );
120 const threadManagement = ThreadManagement.layer.pipe(Layer.provide(orchestrator));
121 const receipts = CommandReceiptStore.layer.pipe(Layer.provide(database));
122 const outbox = EffectOutbox.layer.pipe(Layer.provide(database));
120 const layerThreadManagement = ThreadManagement.layer.pipe(Layer.provide(layerOrchestrator));
121 const layerReceipts = CommandReceiptStore.layer.pipe(Layer.provide(layerDatabase));
122 const layerOutbox = EffectOutbox.layer.pipe(Layer.provide(layerDatabase));
123123 const createWorktree = vi.fn(
124124 options.createWorktree ??
125125 ((input) =>
......@@ -143,7 +143,7 @@ function makeHarness(options: HarnessOptions = {}) {
143143 const generateThreadTitle = vi.fn(
144144 options.generateTitle ?? (() => Effect.succeed({ title: "Generated title" })),
145145 );
146 const externalServices = Layer.mergeAll(
146 const layerExternalServices = Layer.mergeAll(
147147 WorktreeSetupTracker.layer,
148148 Layer.mock(ProjectCloneTracker.ProjectCloneTracker)({ get: () => Effect.succeed(null) }),
149149 Layer.mock(TerminalManager.TerminalManager)({ close: () => Effect.void }),
......@@ -184,17 +184,24 @@ function makeHarness(options: HarnessOptions = {}) {
184184 generateBranchName,
185185 }),
186186 ServerSettings.layerTest(options.serverSettings),
187 makeProviderRegistryLayer(options.providers),
187 ProviderRegistryMock.layer(options.providers),
188188 options.managedFolders ??
189189 Layer.mock(ManagedProjectFolders.ManagedProjectFolders)({
190190 namedProjectsRoot: "/projects",
191191 folderForThread: () => Effect.succeed(Option.none()),
192192 }),
193193 );
194 const launch = ThreadLaunch.layer.pipe(
195 Layer.provide(Layer.mergeAll(externalServices, threadManagement, receipts, IdAllocator.layer)),
194 const layerLaunch = ThreadLaunch.layer.pipe(
195 Layer.provide(
196 Layer.mergeAll(
197 layerExternalServices,
198 layerThreadManagement,
199 layerReceipts,
200 IdAllocator.layer,
201 ),
202 ),
196203 );
197 const projectedProjects = Layer.mock(ProjectStore.ProjectStoreV2)({
204 const layerProjectedProjects = Layer.mock(ProjectStore.ProjectStoreV2)({
198205 get: (requestedProjectId) =>
199206 Effect.succeed(
200207 requestedProjectId === projectId
......@@ -215,17 +222,19 @@ function makeHarness(options: HarnessOptions = {}) {
215222 : Option.none(),
216223 ),
217224 });
218 const titleRegeneration = ThreadTitleRegeneration.layer.pipe(
219 Layer.provide(Layer.mergeAll(threadManagement, projectedProjects, externalServices)),
225 const layerTitleRegeneration = ThreadTitleRegeneration.layer.pipe(
226 Layer.provide(
227 Layer.mergeAll(layerThreadManagement, layerProjectedProjects, layerExternalServices),
228 ),
220229 );
221230 return {
222231 layer: Layer.mergeAll(
223 launch,
224 threadManagement,
225 titleRegeneration,
226 outbox,
227 database,
228 externalServices,
232 layerLaunch,
233 layerThreadManagement,
234 layerTitleRegeneration,
235 layerOutbox,
236 layerDatabase,
237 layerExternalServices,
229238 ),
230239 createWorktree,
231240 removeWorktree,
......@@ -288,7 +297,7 @@ it.effect.each(
288297 "attributes $createdBy-configured automations in $target threads without changing their prompt",
289298 ({ target, createdBy }) => {
290299 const harness = makeHarness();
291 const scheduledTasks = ScheduledTasks.layer.pipe(
300 const layerScheduledTasks = ScheduledTasks.layer.pipe(
292301 Layer.provide(
293302 Layer.mergeAll(
294303 harness.layer,
......@@ -345,7 +354,7 @@ it.effect.each(
345354 );
346355 assert.equal(turnItem?.text, task.prompt);
347356 assert.equal(turnItem?.scheduledTaskId, task.id);
348 }).pipe(Effect.provide(Layer.mergeAll(harness.layer, scheduledTasks)));
357 }).pipe(Effect.provide(Layer.mergeAll(harness.layer, layerScheduledTasks)));
349358 },
350359);
351360
......@@ -1955,7 +1964,7 @@ it.effect("creates a strong provider-thread mapping for an imported native sessi
19551964
19561965it.effect("shared intake preserves durable attachment bytes after a lost launch result", () => {
19571966 const harness = makeHarness();
1958 const files = ServerConfig.layerTest(process.cwd(), { prefix: "t3-message-intake-" }).pipe(
1967 const layerFiles = ServerConfig.layerTest(process.cwd(), { prefix: "t3-message-intake-" }).pipe(
19591968 Layer.provideMerge(NodeServices.layer),
19601969 );
19611970 return Effect.gen(function* () {
......@@ -2171,7 +2180,7 @@ it.effect("shared intake preserves durable attachment bytes after a lost launch
21712180 assert.isNotNull(path);
21722181 assert.deepEqual(yield* fs.readFile(path), new Uint8Array([1, 2, 3, 4]));
21732182 }
2174 }).pipe(Effect.provide(Layer.mergeAll(harness.layer, files)));
2183 }).pipe(Effect.provide(Layer.mergeAll(harness.layer, layerFiles)));
21752184});
21762185
21772186it.effect("cancels tracked setup before provider work is released", () =>
apps/server/src/orchestration-v2/ThreadManagementService.test.ts+10-10
......@@ -268,7 +268,7 @@ it.effect("classifies projection infrastructure failures separately from a missi
268268 threadId,
269269 cause: infrastructureCause,
270270 });
271 const testLayer = ThreadManagementService.layer.pipe(
271 const layerTest = ThreadManagementService.layer.pipe(
272272 Layer.provide(
273273 Layer.mock(Orchestrator.OrchestratorV2)({
274274 getThreadProjection: () => Effect.fail(projectionError),
......@@ -287,7 +287,7 @@ it.effect("classifies projection infrastructure failures separately from a missi
287287 cause: projectionError,
288288 });
289289 expect(error.message).toBe(`Unable to load thread ${threadId} in project ${projectId}.`);
290 }).pipe(Effect.provide(testLayer));
290 }).pipe(Effect.provide(layerTest));
291291});
292292
293293it.effect("uses thread-not-found only after a projection loads outside the project", () => {
......@@ -301,7 +301,7 @@ it.effect("uses thread-not-found only after a projection loads outside the proje
301301 deletedAt: null,
302302 },
303303 } as OrchestrationV2ThreadProjection;
304 const testLayer = ThreadManagementService.layer.pipe(
304 const layerTest = ThreadManagementService.layer.pipe(
305305 Layer.provide(
306306 Layer.mock(Orchestrator.OrchestratorV2)({
307307 getThreadProjection: () => Effect.succeed(projection),
......@@ -316,7 +316,7 @@ it.effect("uses thread-not-found only after a projection loads outside the proje
316316 expect(error).toBeInstanceOf(ThreadManagementService.ThreadManagementThreadNotFoundError);
317317 expect(error).toMatchObject({ projectId, threadId });
318318 expect("cause" in error).toBe(false);
319 }).pipe(Effect.provide(testLayer));
319 }).pipe(Effect.provide(layerTest));
320320});
321321
322322it.effect("preserves failed legacy materialization when reading checkpoint context", () => {
......@@ -326,7 +326,7 @@ it.effect("preserves failed legacy materialization when reading checkpoint conte
326326 operation: "hydrate transcript for",
327327 cause: new Error("checkpoint import failed"),
328328 });
329 const testLayer = ThreadManagementService.layerWithLegacyImporter.pipe(
329 const layerTest = ThreadManagementService.layerWithLegacyImporter.pipe(
330330 Layer.provide(
331331 Layer.mergeAll(
332332 Layer.mock(Orchestrator.OrchestratorV2)({
......@@ -344,7 +344,7 @@ it.effect("preserves failed legacy materialization when reading checkpoint conte
344344 const error = yield* service.getCheckpointContext(threadId).pipe(Effect.flip);
345345 expect(error).toBeInstanceOf(Orchestrator.OrchestratorProjectionError);
346346 expect(error).toMatchObject({ threadId, cause: importError });
347 }).pipe(Effect.provide(testLayer));
347 }).pipe(Effect.provide(layerTest));
348348});
349349
350350it.effect.each([
......@@ -367,7 +367,7 @@ it.effect.each([
367367 thread: { id: threadId, projectId, deletedAt: null },
368368 runs: status === "missing" ? [] : [{ id: runId, status }],
369369 }) as unknown as OrchestrationV2ThreadProjection;
370 const testLayer = ThreadManagementService.layer.pipe(
370 const layerTest = ThreadManagementService.layer.pipe(
371371 Layer.provide(
372372 Layer.mock(Orchestrator.OrchestratorV2)({
373373 getThreadEventSequence: () => Effect.succeed(0),
......@@ -387,7 +387,7 @@ it.effect.each([
387387 ),
388388 );
389389 const service = yield* ThreadManagementService.ThreadManagementService.pipe(
390 Effect.provide(testLayer),
390 Effect.provide(layerTest),
391391 );
392392 const fiber = yield* service
393393 .waitForThread({
......@@ -433,7 +433,7 @@ it.effect("waitForThread reads the run again only when the run updates", () =>
433433 let reads = 0;
434434 const stored = (sequence: number, event: object) =>
435435 ({ sequence, event: { threadId, ...event } }) as unknown as OrchestrationV2StoredEvent;
436 const testLayer = ThreadManagementService.layer.pipe(
436 const layerTest = ThreadManagementService.layer.pipe(
437437 Layer.provide(
438438 Layer.mock(Orchestrator.OrchestratorV2)({
439439 getThreadEventSequence: () => Effect.succeed(0),
......@@ -457,7 +457,7 @@ it.effect("waitForThread reads the run again only when the run updates", () =>
457457 ),
458458 );
459459 const service = yield* ThreadManagementService.ThreadManagementService.pipe(
460 Effect.provide(testLayer),
460 Effect.provide(layerTest),
461461 );
462462 const fiber = yield* service
463463 .waitForThread({ projectId, threadId, runId, timeoutMs: 60 * 60 * 1_000 })
apps/server/src/orchestration-v2/ThreadManagementService.ts+2-2
......@@ -833,7 +833,7 @@ const make = Effect.gen(function* () {
833833 });
834834});
835835
836const legacyV1ThreadImporterNoopLayer = Layer.succeed(
836const layerLegacyV1ThreadImporterNoop = Layer.succeed(
837837 LegacyV1ThreadImporter.LegacyV1ThreadImporter,
838838 LegacyV1ThreadImporter.LegacyV1ThreadImporter.of({
839839 pendingThreadCount: Effect.succeed(0),
......@@ -844,7 +844,7 @@ const legacyV1ThreadImporterNoopLayer = Layer.succeed(
844844);
845845
846846export const layer: Layer.Layer<ThreadManagementService, never, Orchestrator.OrchestratorV2> =
847 Layer.effect(ThreadManagementService, make).pipe(Layer.provide(legacyV1ThreadImporterNoopLayer));
847 Layer.effect(ThreadManagementService, make).pipe(Layer.provide(layerLegacyV1ThreadImporterNoop));
848848
849849export const layerWithLegacyImporter: Layer.Layer<
850850 ThreadManagementService,
apps/server/src/orchestration-v2/ThreadMessageIntake.test.ts+21-21
......@@ -27,11 +27,11 @@ import {
2727import * as ThreadManagementService from "./ThreadManagementService.ts";
2828import { dispatchCommand } from "./ThreadMessageIntake.ts";
2929
30const intakeTestLayer = ServerConfig.layerTest(process.cwd(), {
30const layerIntakeTest = ServerConfig.layerTest(process.cwd(), {
3131 prefix: "t3-question-intake-",
3232}).pipe(Layer.provideMerge(NodeServices.layer));
3333
34const failingDispatch = (captured: OrchestrationV2ServerCommand[]) =>
34const layerFailingDispatch = (captured: OrchestrationV2ServerCommand[]) =>
3535 Layer.mock(ThreadManagementService.ThreadManagementService)({
3636 dispatch: (command) => {
3737 captured.push(command);
......@@ -143,7 +143,7 @@ it.effect("claims question uploads and passes readable paths through the V2 requ
143143 attachmentsByQuestionId: {
144144 q: [{ type: "image", id, name: "screen.png", mimeType: "image/png", sizeBytes: 3 }],
145145 },
146 }).pipe(Effect.provide(failingDispatch(captured)), Effect.result);
146 }).pipe(Effect.provide(layerFailingDispatch(captured)), Effect.result);
147147 expect(captured).toHaveLength(1);
148148 const command = captured[0]!;
149149 expect(command.type).toBe("runtime-request.respond");
......@@ -160,7 +160,7 @@ it.effect("claims question uploads and passes readable paths through the V2 requ
160160 "Selected option",
161161 `Attached image "screen.png": "${path}"`,
162162 ]);
163 }).pipe(Effect.provide(intakeTestLayer)),
163 }).pipe(Effect.provide(layerIntakeTest)),
164164);
165165
166166it.effect("rolls back earlier question claims when a later pending upload is missing", () =>
......@@ -211,7 +211,7 @@ it.effect("rolls back earlier question claims when a later pending upload is mis
211211 },
212212 ],
213213 },
214 }).pipe(Effect.provide(failingDispatch(captured)), Effect.result);
214 }).pipe(Effect.provide(layerFailingDispatch(captured)), Effect.result);
215215 expect(result._tag).toBe("Failure");
216216 if (result._tag === "Failure") {
217217 expect(result.failure._tag).toBe("AttachmentClaimError");
......@@ -227,7 +227,7 @@ it.effect("rolls back earlier question claims when a later pending upload is mis
227227 entry.startsWith("thread-q-fail-"),
228228 ),
229229 ).toEqual([`${existingId}.png`]);
230 }).pipe(Effect.provide(intakeTestLayer)),
230 }).pipe(Effect.provide(layerIntakeTest)),
231231);
232232
233233it.effect("rolls back earlier question claims when attachment path preparation fails", () =>
......@@ -265,7 +265,7 @@ it.effect("rolls back earlier question claims when attachment path preparation f
265265 },
266266 ],
267267 },
268 }).pipe(Effect.provide(failingDispatch(captured)), Effect.result);
268 }).pipe(Effect.provide(layerFailingDispatch(captured)), Effect.result);
269269 expect(result._tag).toBe("Failure");
270270 if (result._tag === "Failure") {
271271 expect(result.failure._tag).toBe("AttachmentClaimError");
......@@ -279,7 +279,7 @@ it.effect("rolls back earlier question claims when attachment path preparation f
279279 entry.startsWith("thread-path-fail-"),
280280 ),
281281 ).toEqual([]);
282 }).pipe(Effect.provide(intakeTestLayer)),
282 }).pipe(Effect.provide(layerIntakeTest)),
283283);
284284
285285it.effect("retains claimed copies when dispatch failure may have been accepted", () =>
......@@ -308,7 +308,7 @@ it.effect("retains claimed copies when dispatch failure may have been accepted",
308308 },
309309 ],
310310 },
311 }).pipe(Effect.provide(failingDispatch(captured)), Effect.result);
311 }).pipe(Effect.provide(layerFailingDispatch(captured)), Effect.result);
312312 expect(result._tag).toBe("Failure");
313313 expect(captured).toHaveLength(1);
314314 // The response may have been accepted before the error, so the copy that
......@@ -318,7 +318,7 @@ it.effect("retains claimed copies when dispatch failure may have been accepted",
318318 );
319319 expect(threadFiles).toHaveLength(1);
320320 expect(NodeFS.existsSync(NodePath.join(config.attachmentsDir, `${pendingId}.png`))).toBe(true);
321 }).pipe(Effect.provide(intakeTestLayer)),
321 }).pipe(Effect.provide(layerIntakeTest)),
322322);
323323
324324it.effect("releases claimed copies when the command was already rejected", () =>
......@@ -370,7 +370,7 @@ it.effect("releases claimed copies when the command was already rejected", () =>
370370 ),
371371 ).toEqual([]);
372372 expect(NodeFS.existsSync(NodePath.join(config.attachmentsDir, `${pendingId}.png`))).toBe(true);
373 }).pipe(Effect.provide(intakeTestLayer)),
373 }).pipe(Effect.provide(layerIntakeTest)),
374374);
375375
376376it.effect("releases claimed copies when dispatch replays an earlier accepted response", () =>
......@@ -438,7 +438,7 @@ it.effect("releases claimed copies when dispatch replays an earlier accepted res
438438 NodeFS.readFileSync(NodePath.join(config.attachmentsDir, `${earlierAttachment.id}.png`)),
439439 ).toEqual(Buffer.from([4, 4, 4]));
440440 expect(NodeFS.existsSync(NodePath.join(config.attachmentsDir, `${pendingId}.png`))).toBe(true);
441 }).pipe(Effect.provide(intakeTestLayer)),
441 }).pipe(Effect.provide(layerIntakeTest)),
442442);
443443
444444it.effect("releases claimed copies when the recorded answer has no attachments", () =>
......@@ -489,7 +489,7 @@ it.effect("releases claimed copies when the recorded answer has no attachments",
489489 ),
490490 ).toEqual([]);
491491 expect(NodeFS.existsSync(NodePath.join(config.attachmentsDir, `${pendingId}.png`))).toBe(true);
492 }).pipe(Effect.provide(intakeTestLayer)),
492 }).pipe(Effect.provide(layerIntakeTest)),
493493);
494494
495495it.effect("releases claimed copies when the recorded answer was text-only", () =>
......@@ -540,7 +540,7 @@ it.effect("releases claimed copies when the recorded answer was text-only", () =
540540 ),
541541 ).toEqual([]);
542542 expect(NodeFS.existsSync(NodePath.join(config.attachmentsDir, `${pendingId}.png`))).toBe(true);
543 }).pipe(Effect.provide(intakeTestLayer)),
543 }).pipe(Effect.provide(layerIntakeTest)),
544544);
545545
546546it.effect("retains claimed copies referenced by a fresh recorded answer", () =>
......@@ -609,7 +609,7 @@ it.effect("retains claimed copies referenced by a fresh recorded answer", () =>
609609 entry.startsWith("thread-fresh-answer-"),
610610 ),
611611 ).toHaveLength(2);
612 }).pipe(Effect.provide(intakeTestLayer)),
612 }).pipe(Effect.provide(layerIntakeTest)),
613613);
614614
615615it.effect("releases claimed copies when preparation dies with a defect", () =>
......@@ -644,7 +644,7 @@ it.effect("releases claimed copies when preparation dies with a defect", () =>
644644 ],
645645 q2: [poisoned] as never,
646646 },
647 }).pipe(Effect.provide(failingDispatch(captured)), Effect.exit);
647 }).pipe(Effect.provide(layerFailingDispatch(captured)), Effect.exit);
648648 expect(result._tag).toBe("Failure");
649649 expect(captured).toHaveLength(0);
650650 expect(
......@@ -653,7 +653,7 @@ it.effect("releases claimed copies when preparation dies with a defect", () =>
653653 ),
654654 ).toEqual([]);
655655 expect(NodeFS.existsSync(NodePath.join(config.attachmentsDir, `${pendingId}.png`))).toBe(true);
656 }).pipe(Effect.provide(intakeTestLayer)),
656 }).pipe(Effect.provide(layerIntakeTest)),
657657);
658658
659659it.effect("a retried response re-claims the preserved pending uploads", () =>
......@@ -695,7 +695,7 @@ it.effect("a retried response re-claims the preserved pending uploads", () =>
695695 };
696696 const captured: OrchestrationV2ServerCommand[] = [];
697697 const first = yield* dispatchCommand(command).pipe(
698 Effect.provide(failingDispatch(captured)),
698 Effect.provide(layerFailingDispatch(captured)),
699699 Effect.result,
700700 );
701701 expect(first._tag).toBe("Failure");
......@@ -736,7 +736,7 @@ it.effect("a retried response re-claims the preserved pending uploads", () =>
736736 entry.startsWith("thread-retry-"),
737737 ),
738738 ).toHaveLength(2);
739 }).pipe(Effect.provide(intakeTestLayer)),
739 }).pipe(Effect.provide(layerIntakeTest)),
740740);
741741
742742it.effect("applies the image budget across all questions before dispatch", () =>
......@@ -757,10 +757,10 @@ it.effect("applies the image budget across all questions before dispatch", () =>
757757 requestId: RuntimeRequestId.make("question-image-budget"),
758758 answers: {},
759759 attachmentsByQuestionId: { q1: attachments, q2: attachments },
760 }).pipe(Effect.provide(failingDispatch(captured))),
760 }).pipe(Effect.provide(layerFailingDispatch(captured))),
761761 );
762762 expect(result._tag).toBe("Failure");
763763 if (result._tag === "Failure") expect(String(result.cause)).toContain("80 MiB");
764764 expect(captured).toEqual([]);
765 }).pipe(Effect.provide(intakeTestLayer)),
765 }).pipe(Effect.provide(layerIntakeTest)),
766766);
apps/server/src/orchestration-v2/ThreadPullRequestService.test.ts+4-4
......@@ -180,7 +180,7 @@ describe("ThreadPullRequestServiceV2 reads", () => {
180180 const reads = yield* Queue.unbounded<
181181 ThreadId | { readonly location?: string; readonly unsettledOnly?: boolean }
182182 >();
183 const dependencies = Layer.mergeAll(
183 const layerDependencies = Layer.mergeAll(
184184 Layer.mock(Orchestrator.OrchestratorV2)({
185185 streamDomainEvents: Stream.fromPubSub(events),
186186 getShellSnapshot: (options) =>
......@@ -254,7 +254,7 @@ describe("ThreadPullRequestServiceV2 reads", () => {
254254 expect(yield* Queue.take(reads)).toBe(thread.id);
255255 yield* service.drain;
256256 expect(yield* Queue.size(reads)).toBe(0);
257 }).pipe(Effect.provide(dependencies));
257 }).pipe(Effect.provide(layerDependencies));
258258 }),
259259 ),
260260 );
......@@ -273,7 +273,7 @@ describe("ThreadPullRequestServiceV2 reads", () => {
273273 readonly location?: string;
274274 readonly unsettledOnly?: boolean;
275275 }>();
276 const dependencies = Layer.mergeAll(
276 const layerDependencies = Layer.mergeAll(
277277 Layer.mock(Orchestrator.OrchestratorV2)({
278278 streamDomainEvents: Stream.never,
279279 getShellSnapshot: (options) =>
......@@ -315,7 +315,7 @@ describe("ThreadPullRequestServiceV2 reads", () => {
315315 yield* TestClock.adjust("1 minute");
316316 expect(yield* Queue.take(reads)).toEqual({ location: "active", unsettledOnly: true });
317317 yield* service.drain;
318 }).pipe(Effect.provide(dependencies));
318 }).pipe(Effect.provide(layerDependencies));
319319 }),
320320 ),
321321 );
apps/server/src/orchestration-v2/ThreadSearch.test.ts+4-4
......@@ -12,16 +12,16 @@ import * as Effect from "effect/Effect";
1212import * as Layer from "effect/Layer";
1313import * as SqlClient from "effect/sql/SqlClient";
1414
15import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts";
15import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts";
1616import * as ProjectionStore from "./ProjectionStore.ts";
1717import * as ProjectStore from "./ProjectStore.ts";
1818import * as ThreadSearch from "./ThreadSearch.ts";
1919
20const TestLayer = Layer.mergeAll(
20const layerTest = Layer.mergeAll(
2121 ThreadSearch.layer,
2222 ProjectionStore.layer,
2323 ProjectStore.layer,
24).pipe(Layer.provideMerge(SqlitePersistenceMemory));
24).pipe(Layer.provideMerge(SqlitePersistence.layerMemory));
2525
2626const providerInstanceId = ProviderInstanceId.make("codex");
2727const at = (minute: number) => DateTime.makeUnsafe(Date.UTC(2026, 8, 27, 0, minute));
......@@ -114,7 +114,7 @@ const message = (
114114 },
115115});
116116
117it.layer(TestLayer)("ThreadSearch", (it) => {
117it.layer(layerTest)("ThreadSearch", (it) => {
118118 it.effect("returns one finished user or assistant match per active thread", () =>
119119 Effect.gen(function* () {
120120 const projections = yield* ProjectionStore.ProjectionStoreV2;
apps/server/src/orchestration-v2/ThreadSettlementService.test.ts+2-2
......@@ -571,7 +571,7 @@ const makeHarness = Effect.fn("makeThreadSettlementHarness")(function* (options:
571571 ),
572572 });
573573
574 const dependencies = Layer.mergeAll(
574 const layerDependencies = Layer.mergeAll(
575575 Layer.mock(ProjectStore.ProjectStoreV2)({
576576 listShells: () => Ref.get(snapshots).pipe(Effect.map((snapshot) => snapshot.projects)),
577577 }),
......@@ -639,7 +639,7 @@ const makeHarness = Effect.fn("makeThreadSettlementHarness")(function* (options:
639639 number: 42,
640640 mergedAt: NOW,
641641 }),
642 layer: ThreadSettlementService.layer.pipe(Layer.provide(dependencies)),
642 layer: ThreadSettlementService.layer.pipe(Layer.provide(layerDependencies)),
643643 };
644644});
645645
apps/server/src/orchestration-v2/ThreadStop.test.ts+17-17
......@@ -19,7 +19,7 @@ import * as Schema from "effect/Schema";
1919import * as SqlClient from "effect/sql/SqlClient";
2020import * as TestClock from "effect/testing/TestClock";
2121
22import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts";
22import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts";
2323import { CodexProviderCapabilitiesV2 } from "./Adapters/CodexAdapterV2.ts";
2424import { OrchestrationEffectRequestV2 } from "./EffectOutbox.ts";
2525import * as Orchestrator from "./Orchestrator.ts";
......@@ -27,7 +27,7 @@ import * as ProjectionStore from "./ProjectionStore.ts";
2727import type { ProviderAdapterV2Shape } from "./ProviderAdapter.ts";
2828import * as ProviderAdapterRegistry from "./ProviderAdapterRegistry.ts";
2929import * as ThreadManagementService from "./ThreadManagementService.ts";
30import { makeOrchestratorV2ReplayLayerWithRegistry } from "./testkit/ProviderReplayHarness.ts";
30import * as ProviderReplayHarness from "./testkit/ProviderReplayHarness.ts";
3131
3232const instanceId = ProviderInstanceId.make("codex");
3333const modelSelection = { instanceId, model: "gpt-5.1-codex" };
......@@ -38,17 +38,17 @@ const adapter = {
3838 planSelectionTransition: () => Effect.succeed({ type: "apply_on_next_turn" as const }),
3939 openSession: () => Effect.die("Runs here never reach a provider"),
4040} as ProviderAdapterV2Shape;
41const database = SqlitePersistenceMemory;
41const layerDatabase = SqlitePersistence.layerMemory;
4242// No effect worker: runs stay unstarted, so Stop ends them without a provider.
43const testLayer = ThreadManagementService.layer.pipe(
43const layerTest = ThreadManagementService.layer.pipe(
4444 Layer.provideMerge(
4545 Layer.mergeAll(
46 database,
47 ProjectionStore.layer.pipe(Layer.provide(database)),
48 makeOrchestratorV2ReplayLayerWithRegistry(
46 layerDatabase,
47 ProjectionStore.layer.pipe(Layer.provide(layerDatabase)),
48 ProviderReplayHarness.layerWithRegistry(
4949 { name: "thread-stop" },
50 ProviderAdapterRegistry.makeLayer([adapter]),
51 { databaseLayer: database, runEffectWorker: false },
50 ProviderAdapterRegistry.layerFromAdapters([adapter]),
51 { databaseLayer: layerDatabase, runEffectWorker: false },
5252 ),
5353 ),
5454 ),
......@@ -210,7 +210,7 @@ it.effect("Stop ends watches, holds queues, and stops the delegated tasks under
210210 // A retried effect stops nothing twice.
211211 yield* threads.stopDelegatedTasks({ threadId: parentThreadId, commandId: stopCommandId });
212212 assert.deepEqual((yield* threadState(childThreadId)).runs, ["interrupted", "queued:held"]);
213 }).pipe(Effect.provide(testLayer)),
213 }).pipe(Effect.provide(layerTest)),
214214);
215215
216216it.effect(
......@@ -234,7 +234,7 @@ it.effect(
234234 threadId,
235235 });
236236 assert.lengthOf(again.storedEvents, 0);
237 }).pipe(Effect.provide(testLayer)),
237 }).pipe(Effect.provide(layerTest)),
238238);
239239
240240it.effect("a run Stop reached cannot delegate or start a watch, even after it ends", () =>
......@@ -319,7 +319,7 @@ it.effect("a run Stop reached cannot delegate or start a watch, even after it en
319319 });
320320 }
321321 assert.deepEqual((yield* threadState(threadId)).watched, [4]);
322 }).pipe(Effect.provide(testLayer)),
322 }).pipe(Effect.provide(layerTest)),
323323);
324324
325325it.effect("thread.stop keeps a restart continuation of the stopped run from starting", () =>
......@@ -372,7 +372,7 @@ it.effect("thread.stop keeps a restart continuation of the stopped run from star
372372 restartContinuationOfRunId: run.id,
373373 });
374374 assert.deepEqual(yield* threadState(threadId), { runs: ["cancelled"], watched: [] });
375 }).pipe(Effect.provide(testLayer)),
375 }).pipe(Effect.provide(layerTest)),
376376);
377377
378378it.effect.each(["thread.stop", "run.interrupt"] as const)(
......@@ -468,7 +468,7 @@ it.effect.each(["thread.stop", "run.interrupt"] as const)(
468468 runs: ["completed", "interrupted", "cancelled"],
469469 watched: [],
470470 });
471 }).pipe(Effect.provide(testLayer.pipe(Layer.provideMerge(TestClock.layer())))),
471 }).pipe(Effect.provide(layerTest.pipe(Layer.provideMerge(TestClock.layer())))),
472472);
473473
474474it.effect("a delegated task that cannot be stopped fails the walk after its siblings stop", () =>
......@@ -506,7 +506,7 @@ it.effect("a delegated task that cannot be stopped fails the walk after its sibl
506506 );
507507 assert.isTrue(Exit.isFailure(walked));
508508 assert.deepEqual((yield* threadState(childThreadId)).runs, ["interrupted"]);
509 }).pipe(Effect.provide(testLayer)),
509 }).pipe(Effect.provide(layerTest)),
510510);
511511
512512it.effect("thread.stop on a finished thread refuses a late agent watch", () =>
......@@ -534,7 +534,7 @@ it.effect("thread.stop on a finished thread refuses a late agent watch", () =>
534534 });
535535 assert.isTrue(Exit.isFailure(yield* Effect.exit(watch(threadId, 10))));
536536 assert.deepEqual(yield* threadState(threadId), { runs: ["completed"], watched: [] });
537 }).pipe(Effect.provide(testLayer)),
537 }).pipe(Effect.provide(layerTest)),
538538);
539539
540540it.effect("thread.stop marks a turn it cannot interrupt so a late agent watch is refused", () =>
......@@ -580,5 +580,5 @@ it.effect("thread.stop marks a turn it cannot interrupt so a late agent watch is
580580 });
581581 assert.isTrue(Exit.isFailure(yield* Effect.exit(watch(threadId, 12))));
582582 assert.deepEqual(yield* threadState(threadId), { runs: ["running"], watched: [] });
583 }).pipe(Effect.provide(testLayer)),
583 }).pipe(Effect.provide(layerTest)),
584584);
apps/server/src/orchestration-v2/ThreadTitleRegenerationService.test.ts+19-14
......@@ -16,7 +16,7 @@ import * as TestClock from "effect/testing/TestClock";
1616import * as Layer from "effect/Layer";
1717import * as Option from "effect/Option";
1818
19import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts";
19import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts";
2020import * as ProjectStore from "./ProjectStore.ts";
2121import * as ServerSettings from "../serverSettings.ts";
2222import * as TextGeneration from "../textGeneration/TextGeneration.ts";
......@@ -26,7 +26,7 @@ import type { ProviderAdapterV2Shape } from "./ProviderAdapter.ts";
2626import * as ProviderAdapterRegistry from "./ProviderAdapterRegistry.ts";
2727import * as ThreadManagement from "./ThreadManagementService.ts";
2828import * as ThreadTitleRegeneration from "./ThreadTitleRegenerationService.ts";
29import { makeOrchestratorV2ReplayLayerWithRegistry } from "./testkit/ProviderReplayHarness.ts";
29import * as ProviderReplayHarness from "./testkit/ProviderReplayHarness.ts";
3030
3131const projectId = ProjectId.make("project:title-regeneration");
3232const modelSelection = {
......@@ -47,19 +47,19 @@ function makeHarness(
4747 readonly generateTitle?: TextGeneration.TextGeneration["Service"]["generateThreadTitle"];
4848 } = {},
4949) {
50 const database = SqlitePersistenceMemory;
51 const registry = ProviderAdapterRegistry.makeLayer([adapter]);
52 const orchestrator = makeOrchestratorV2ReplayLayerWithRegistry(
50 const layerDatabase = SqlitePersistence.layerMemory;
51 const layerRegistry = ProviderAdapterRegistry.layerFromAdapters([adapter]);
52 const layerOrchestrator = ProviderReplayHarness.layerWithRegistry(
5353 { name: "thread-title-regeneration" },
54 registry,
55 { databaseLayer: database, runEffectWorker: false },
54 layerRegistry,
55 { databaseLayer: layerDatabase, runEffectWorker: false },
5656 );
57 const threadManagement = ThreadManagement.layer.pipe(Layer.provide(orchestrator));
58 const outbox = EffectOutbox.layer.pipe(Layer.provide(database));
57 const layerThreadManagement = ThreadManagement.layer.pipe(Layer.provide(layerOrchestrator));
58 const layerOutbox = EffectOutbox.layer.pipe(Layer.provide(layerDatabase));
5959 const generateThreadTitle = vi.fn(
6060 options.generateTitle ?? (() => Effect.succeed({ title: "Generated title" })),
6161 );
62 const projectedProjects = Layer.mock(ProjectStore.ProjectStoreV2)({
62 const layerProjectedProjects = Layer.mock(ProjectStore.ProjectStoreV2)({
6363 get: (requestedProjectId) =>
6464 Effect.succeed(
6565 requestedProjectId === projectId
......@@ -80,18 +80,23 @@ function makeHarness(
8080 : Option.none(),
8181 ),
8282 });
83 const titleRegeneration = ThreadTitleRegeneration.layer.pipe(
83 const layerTitleRegeneration = ThreadTitleRegeneration.layer.pipe(
8484 Layer.provide(
8585 Layer.mergeAll(
86 threadManagement,
87 projectedProjects,
86 layerThreadManagement,
87 layerProjectedProjects,
8888 Layer.mock(TextGeneration.TextGeneration)({ generateThreadTitle }),
8989 ServerSettings.layerTest({}),
9090 ),
9191 ),
9292 );
9393 return {
94 layer: Layer.mergeAll(threadManagement, titleRegeneration, outbox, database),
94 layer: Layer.mergeAll(
95 layerThreadManagement,
96 layerTitleRegeneration,
97 layerOutbox,
98 layerDatabase,
99 ),
95100 generateThreadTitle,
96101 };
97102}
apps/server/src/orchestration-v2/TurnStartReads.test.ts+6-6
......@@ -16,12 +16,12 @@ import * as DateTime from "effect/DateTime";
1616import * as Effect from "effect/Effect";
1717import * as Layer from "effect/Layer";
1818import * as SqlClient from "effect/sql/SqlClient";
19import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts";
19import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts";
2020import * as ProjectionStore from "./ProjectionStore.ts";
2121
22const databaseLayer = Layer.mergeAll(
23 SqlitePersistenceMemory,
24 ProjectionStore.layer.pipe(Layer.provide(SqlitePersistenceMemory)),
22const layerDatabase = Layer.mergeAll(
23 SqlitePersistence.layerMemory,
24 ProjectionStore.layer.pipe(Layer.provide(SqlitePersistence.layerMemory)),
2525);
2626
2727it.effect.each(["sqlite", "memory"] as const)(
......@@ -362,8 +362,8 @@ it.effect.each(["sqlite", "memory"] as const)(
362362 }).pipe(
363363 Effect.provide(
364364 storage === "sqlite"
365 ? databaseLayer
366 : Layer.merge(SqlitePersistenceMemory, ProjectionStore.layerMemory),
365 ? layerDatabase
366 : Layer.merge(SqlitePersistence.layerMemory, ProjectionStore.layerMemory),
367367 ),
368368 ),
369369);
apps/server/src/orchestration-v2/UsageLimitRecoveryWorker.ts+1-1
......@@ -106,7 +106,7 @@ const makeSweep = Effect.gen(function* () {
106106
107107// The shared scheduler derives due work from persisted failures and recovery
108108// choices, so restarts need no timer restoration or connected client.
109export const workerLive = Layer.effectDiscard(
109export const layer = Layer.effectDiscard(
110110 Effect.gen(function* () {
111111 const sweep = yield* makeSweep;
112112 const scheduler = yield* Scheduler.Scheduler;
apps/server/src/orchestration-v2/http.ts+1-1
......@@ -63,7 +63,7 @@ function selectHistoryPageFromCursorOrError(
6363 * compressible and cacheable — and then resume the WebSocket subscription via
6464 * `afterSequence`.
6565 */
66export const orchestrationHttpApiLayer = HttpApiBuilder.group(
66export const layer = HttpApiBuilder.group(
6767 EnvironmentHttpApi,
6868 "orchestration",
6969 Effect.fnUntraced(function* (handlers) {
apps/server/src/orchestration-v2/legacy/LegacyV1Cutover.integration.test.ts+29-21
......@@ -27,7 +27,7 @@ import * as Stream from "effect/Stream";
2727import * as SqlClient from "effect/sql/SqlClient";
2828
2929import { runMigrations } from "../../persistence/Migrations.ts";
30import { makeSqlitePersistenceLive } from "../../persistence/Layers/Sqlite.ts";
30import * as SqlitePersistence from "../../persistence/Layers/Sqlite.ts";
3131import Migration0042 from "../../persistence/Migrations/042_ProjectionThreadLinkedPullRequest.ts";
3232import Migration0043 from "../../persistence/Migrations/043_ProjectionThreadsUnsettledAt.ts";
3333import Migration0044 from "../../persistence/Migrations/044_ClearAutomaticProjectModelDefaults.ts";
......@@ -50,7 +50,7 @@ import {
5050 type ProviderAdapterV2Shape,
5151} from "../ProviderAdapter.ts";
5252import * as ProviderAdapterRegistry from "../ProviderAdapterRegistry.ts";
53import { makeOrchestratorV2ReplayLayerWithRegistry } from "../testkit/ProviderReplayHarness.ts";
53import * as ProviderReplayHarness from "../testkit/ProviderReplayHarness.ts";
5454import { checkpointWorkspace } from "../testkit/ReplayFixtureWorkspace.ts";
5555
5656const PROJECT_ID = "project:cutover";
......@@ -579,24 +579,32 @@ const waitForIdle = Effect.fn("LegacyV1Cutover.waitForIdle")(function* (threadId
579579 return yield* Effect.die(new Error("Cutover test timed out waiting for idle"));
580580});
581581
582const makeBootLayer = (input: {
582const layerBoot = (input: {
583583 readonly name: string;
584584 readonly dbPath: string;
585585 readonly workspace: string;
586586 readonly capturedTurns: Ref.Ref<ReadonlyArray<CapturedTurn>>;
587587}) => {
588 const databaseLayer = makeSqlitePersistenceLive(input.dbPath).pipe(
588 const layerDatabase = SqlitePersistence.layerFromPath(input.dbPath).pipe(
589589 Layer.provide(NodeServices.layer),
590590 );
591 const eventStoreProvided = EventStore.layer.pipe(Layer.provideMerge(databaseLayer));
592 const projectionStoreProvided = ProjectionStore.layer.pipe(Layer.provideMerge(databaseLayer));
593 const storesProvided = Layer.mergeAll(databaseLayer, eventStoreProvided, projectionStoreProvided);
594 const eventSinkProvided = EventSink.layer.pipe(Layer.provide(storesProvided));
595 const importerProvided = LegacyV1ThreadImporter.layer.pipe(
596 Layer.provide(Layer.mergeAll(storesProvided, eventSinkProvided)),
591 const layerEventStoreProvided = EventStore.layer.pipe(Layer.provideMerge(layerDatabase));
592 const layerProjectionStoreProvided = ProjectionStore.layer.pipe(
593 Layer.provideMerge(layerDatabase),
597594 );
598 const maintenanceProvided = ProjectionMaintenance.layer.pipe(Layer.provide(storesProvided));
599 const orchestratorProvided = makeOrchestratorV2ReplayLayerWithRegistry(
595 const layerStoresProvided = Layer.mergeAll(
596 layerDatabase,
597 layerEventStoreProvided,
598 layerProjectionStoreProvided,
599 );
600 const layerEventSinkProvided = EventSink.layer.pipe(Layer.provide(layerStoresProvided));
601 const layerImporterProvided = LegacyV1ThreadImporter.layer.pipe(
602 Layer.provide(Layer.mergeAll(layerStoresProvided, layerEventSinkProvided)),
603 );
604 const layerMaintenanceProvided = ProjectionMaintenance.layer.pipe(
605 Layer.provide(layerStoresProvided),
606 );
607 const layerOrchestratorProvided = ProviderReplayHarness.layerWithRegistry(
600608 {
601609 name: input.name,
602610 runtimePolicyOverride: {
......@@ -609,15 +617,15 @@ const makeBootLayer = (input: {
609617 },
610618 },
611619 },
612 ProviderAdapterRegistry.makeSingleLayer(makeCodexAdapter(input.capturedTurns)),
613 { databaseLayer },
620 ProviderAdapterRegistry.layerSingle(makeCodexAdapter(input.capturedTurns)),
621 { databaseLayer: layerDatabase },
614622 );
615623 return Layer.mergeAll(
616 storesProvided,
617 eventSinkProvided,
618 importerProvided,
619 maintenanceProvided,
620 orchestratorProvided,
624 layerStoresProvided,
625 layerEventSinkProvided,
626 layerImporterProvided,
627 layerMaintenanceProvided,
628 layerOrchestratorProvided,
621629 );
622630};
623631
......@@ -909,7 +917,7 @@ describe("orchestration v2 legacy v1 cutover", () => {
909917 };
910918 }).pipe(
911919 Effect.provide(
912 makeBootLayer({
920 layerBoot({
913921 name: "legacy-v1-cutover-first",
914922 dbPath: copyPath,
915923 workspace,
......@@ -1020,7 +1028,7 @@ describe("orchestration v2 legacy v1 cutover", () => {
10201028 );
10211029 }).pipe(
10221030 Effect.provide(
1023 makeBootLayer({
1031 layerBoot({
10241032 name: "legacy-v1-cutover-restart",
10251033 dbPath: copyPath,
10261034 workspace,
apps/server/src/orchestration-v2/legacy/LegacyV1ThreadImporter.test.ts+20-16
......@@ -6,7 +6,7 @@ import * as Layer from "effect/Layer";
66import * as Tracer from "effect/Tracer";
77import * as SqlClient from "effect/sql/SqlClient";
88
9import { SqlitePersistenceMemory } from "../../persistence/Layers/Sqlite.ts";
9import * as SqlitePersistence from "../../persistence/Layers/Sqlite.ts";
1010import { listLinkedPullRequestThreads } from "../../pullRequest/linkedThreads.ts";
1111import * as EventSink from "../EventSink.ts";
1212import * as EventStore from "../EventStore.ts";
......@@ -14,25 +14,29 @@ import * as LegacyV1ThreadImporter from "./LegacyV1ThreadImporter.ts";
1414import * as ProjectionMaintenance from "../ProjectionMaintenance.ts";
1515import * as ProjectionStore from "../ProjectionStore.ts";
1616
17const databaseLayer = SqlitePersistenceMemory;
18const eventStoreProvided = EventStore.layer.pipe(Layer.provideMerge(databaseLayer));
19const projectionStoreProvided = ProjectionStore.layer.pipe(Layer.provideMerge(databaseLayer));
20const storesProvided = Layer.mergeAll(databaseLayer, eventStoreProvided, projectionStoreProvided);
21const eventSinkProvided = EventSink.layer.pipe(Layer.provide(storesProvided));
22const importerProvided = LegacyV1ThreadImporter.layer.pipe(
23 Layer.provide(Layer.mergeAll(storesProvided, eventSinkProvided)),
17const layerDatabase = SqlitePersistence.layerMemory;
18const layerEventStoreProvided = EventStore.layer.pipe(Layer.provideMerge(layerDatabase));
19const layerProjectionStoreProvided = ProjectionStore.layer.pipe(Layer.provideMerge(layerDatabase));
20const layerStoresProvided = Layer.mergeAll(
21 layerDatabase,
22 layerEventStoreProvided,
23 layerProjectionStoreProvided,
2424);
25const projectionMaintenanceProvided = ProjectionMaintenance.layer.pipe(
26 Layer.provide(storesProvided),
25const layerEventSinkProvided = EventSink.layer.pipe(Layer.provide(layerStoresProvided));
26const layerImporterProvided = LegacyV1ThreadImporter.layer.pipe(
27 Layer.provide(Layer.mergeAll(layerStoresProvided, layerEventSinkProvided)),
2728);
28const TestLayer = Layer.mergeAll(
29 storesProvided,
30 eventSinkProvided,
31 importerProvided,
32 projectionMaintenanceProvided,
29const layerProjectionMaintenanceProvided = ProjectionMaintenance.layer.pipe(
30 Layer.provide(layerStoresProvided),
31);
32const layerTest = Layer.mergeAll(
33 layerStoresProvided,
34 layerEventSinkProvided,
35 layerImporterProvided,
36 layerProjectionMaintenanceProvided,
3337);
3438
35it.layer(TestLayer)("LegacyV1ThreadImporter", (it) => {
39it.layer(layerTest)("LegacyV1ThreadImporter", (it) => {
3640 it.effect("uses the created-thread index for startup migration checks", () =>
3741 Effect.gen(function* () {
3842 const sql = yield* SqlClient.SqlClient;
apps/server/src/orchestration-v2/runtimeLayer.test.ts+61-66
......@@ -43,7 +43,7 @@ import * as SqlClient from "effect/sql/SqlClient";
4343import * as CheckpointStore from "../checkpointing/CheckpointStore.ts";
4444import * as GitWorkflow from "../git/GitWorkflowService.ts";
4545import * as ServerConfig from "../config.ts";
46import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts";
46import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts";
4747import * as OrchestrationEventStore from "../persistence/Services/OrchestrationEventStore.ts";
4848import * as ProjectEnrichmentService from "../project/ProjectEnrichmentService.ts";
4949import * as ProjectService from "../project/ProjectService.ts";
......@@ -69,25 +69,20 @@ import * as PullRequestService from "../pullRequest/PullRequestService.ts";
6969import * as ProjectStore from "./ProjectStore.ts";
7070import type { ProviderAdapterV2SessionRuntime, ProviderAdapterV2Shape } from "./ProviderAdapter.ts";
7171import * as ProviderSessionManager from "./ProviderSessionManager.ts";
72import {
73 OrchestrationEventInfrastructureLayerLive,
74 OrchestrationV2EventSinkLayerLive,
75 OrchestrationV2LayerLive,
76 ProjectServiceLayerLive,
77} from "./runtimeLayer.ts";
72import * as RuntimeLayer from "./runtimeLayer.ts";
7873import { shellStreamItemFromThreadShell } from "./ShellStream.ts";
7974import { CodexProviderCapabilitiesV2 } from "./Adapters/CodexAdapterV2.ts";
8075import * as ThreadManagementService from "./ThreadManagementService.ts";
8176import * as ThreadCommandExecutor from "./ThreadCommandExecutor.ts";
8277
83const PlatformTestLayer = Layer.merge(
78const layerPlatformTest = Layer.merge(
8479 NodeServices.layer,
8580 Layer.mock(SourceControlProviderRegistry.SourceControlProviderRegistry)({
8681 resolveLink: () => Effect.die("unused title link"),
8782 }),
8883);
8984
90const ServerConfigLayer = ServerConfig.layerTest(process.cwd(), {
85const layerServerConfig = ServerConfig.layerTest(process.cwd(), {
9186 prefix: "t3-orchestration-v2-runtime-layer-",
9287});
9388
......@@ -97,20 +92,20 @@ const modelSelection = {
9792} satisfies ModelSelection;
9893const alternateInstanceId = ProviderInstanceId.make("codex_alternate");
9994
100const VcsDriverRegistryTestLayer = VcsDriverRegistry.layer.pipe(
95const layerVcsDriverRegistryTest = VcsDriverRegistry.layer.pipe(
10196 Layer.provide(VcsProcess.layer),
102 Layer.provide(ServerConfigLayer),
103 Layer.provide(PlatformTestLayer),
97 Layer.provide(layerServerConfig),
98 Layer.provide(layerPlatformTest),
10499);
105100
106const CheckpointStoreTestLayer = CheckpointStore.layer.pipe(
107 Layer.provide(VcsDriverRegistryTestLayer),
101const layerCheckpointStoreTest = CheckpointStore.layer.pipe(
102 Layer.provide(layerVcsDriverRegistryTest),
108103);
109const GitWorkflowTestLayer = Layer.mock(GitWorkflow.GitWorkflowService)({
104const layerGitWorkflowTest = Layer.mock(GitWorkflow.GitWorkflowService)({
110105 pruneWorktrees: () => Effect.void,
111106 createWorktree: () => Effect.succeed({} as never),
112107});
113const ProjectServiceTestLayer = Layer.mock(ProjectService.ProjectService)({
108const layerProjectServiceTest = Layer.mock(ProjectService.ProjectService)({
114109 getById: () => Effect.succeed(Option.none()),
115110});
116111
......@@ -150,7 +145,7 @@ const alternateProviderInstance = {
150145 },
151146} satisfies ProviderInstance;
152147
153const TestProviderInstanceRegistry = Layer.succeed(
148const layerTestProviderInstanceRegistry = Layer.succeed(
154149 ProviderInstanceRegistry.ProviderInstanceRegistry,
155150 {
156151 getInstance: (instanceId) =>
......@@ -268,41 +263,41 @@ const moveProject = (projectId: ProjectId, workspaceRoot: string, updatedAt: str
268263 }),
269264 );
270265
271const TestLayer = Layer.mergeAll(
272 OrchestrationV2LayerLive,
273 OrchestrationV2EventSinkLayerLive,
266const layerTest = Layer.mergeAll(
267 RuntimeLayer.layer,
268 RuntimeLayer.layerEventSink,
274269 ProjectStore.layer,
275270 ProjectionStore.layer,
276271 EffectOutbox.layer,
277272 ThreadCommandExecutor.layer,
278273).pipe(
279274 Layer.provide(McpSessionRegistryTestkit.layer),
280 Layer.provide(SqlitePersistenceMemory),
281 Layer.provide(CheckpointStoreTestLayer),
282 Layer.provide(ServerConfigLayer),
275 Layer.provide(SqlitePersistence.layerMemory),
276 Layer.provide(layerCheckpointStoreTest),
277 Layer.provide(layerServerConfig),
283278 Layer.provide(ServerSettings.layerTest()),
284 Layer.provide(TestProviderInstanceRegistry),
285 Layer.provide(GitWorkflowTestLayer),
286 Layer.provide(ProjectServiceTestLayer),
287 Layer.provide(PlatformTestLayer),
279 Layer.provide(layerTestProviderInstanceRegistry),
280 Layer.provide(layerGitWorkflowTest),
281 Layer.provide(layerProjectServiceTest),
282 Layer.provide(layerPlatformTest),
288283);
289284
290const LegacyImportTestLayer = OrchestrationV2LayerLive.pipe(
285const layerLegacyImportTest = RuntimeLayer.layer.pipe(
291286 Layer.provide(McpSessionRegistryTestkit.layer),
292 Layer.provideMerge(SqlitePersistenceMemory),
293 Layer.provide(CheckpointStoreTestLayer),
294 Layer.provide(ServerConfigLayer),
287 Layer.provideMerge(SqlitePersistence.layerMemory),
288 Layer.provide(layerCheckpointStoreTest),
289 Layer.provide(layerServerConfig),
295290 Layer.provide(ServerSettings.layerTest()),
296 Layer.provide(TestProviderInstanceRegistry),
297 Layer.provide(GitWorkflowTestLayer),
298 Layer.provide(ProjectServiceTestLayer),
299 Layer.provide(PlatformTestLayer),
291 Layer.provide(layerTestProviderInstanceRegistry),
292 Layer.provide(layerGitWorkflowTest),
293 Layer.provide(layerProjectServiceTest),
294 Layer.provide(layerPlatformTest),
300295);
301296
302const ProjectDeletionTestLayer = Layer.mergeAll(
303 OrchestrationV2LayerLive.pipe(Layer.provide(ProjectServiceLayerLive)),
304 ProjectServiceLayerLive,
305 OrchestrationV2EventSinkLayerLive,
297const layerProjectDeletionTest = Layer.mergeAll(
298 RuntimeLayer.layer.pipe(Layer.provide(RuntimeLayer.layerProjectService)),
299 RuntimeLayer.layerProjectService,
300 RuntimeLayer.layerEventSink,
306301 ThreadCommandExecutor.layer,
307302).pipe(
308303 Layer.provide(
......@@ -328,16 +323,16 @@ const ProjectDeletionTestLayer = Layer.mergeAll(
328323 }),
329324 ),
330325 Layer.provide(McpSessionRegistryTestkit.layer),
331 Layer.provide(SqlitePersistenceMemory),
332 Layer.provide(CheckpointStoreTestLayer),
333 Layer.provide(ServerConfigLayer),
326 Layer.provide(SqlitePersistence.layerMemory),
327 Layer.provide(layerCheckpointStoreTest),
328 Layer.provide(layerServerConfig),
334329 Layer.provide(ServerSettings.layerTest()),
335 Layer.provide(TestProviderInstanceRegistry),
336 Layer.provide(GitWorkflowTestLayer),
337 Layer.provide(PlatformTestLayer),
330 Layer.provide(layerTestProviderInstanceRegistry),
331 Layer.provide(layerGitWorkflowTest),
332 Layer.provide(layerPlatformTest),
338333);
339334
340it.layer(ProjectDeletionTestLayer)("project deletion during thread commands", (it) => {
335it.layer(layerProjectDeletionTest)("project deletion during thread commands", (it) => {
341336 it.effect("waits for an in-flight thread update before planning deletion", () =>
342337 Effect.gen(function* () {
343338 const projects = yield* ProjectService.ProjectService;
......@@ -441,11 +436,11 @@ it.layer(ProjectDeletionTestLayer)("project deletion during thread commands", (i
441436 );
442437});
443438
444const SharedApplicationDataPlaneTestLayer = Layer.mergeAll(
445 OrchestrationV2LayerLive.pipe(Layer.provide(ProjectServiceLayerLive)),
446 ProjectServiceLayerLive,
447 OrchestrationV2EventSinkLayerLive,
448 OrchestrationEventInfrastructureLayerLive,
439const layerSharedApplicationDataPlaneTest = Layer.mergeAll(
440 RuntimeLayer.layer.pipe(Layer.provide(RuntimeLayer.layerProjectService)),
441 RuntimeLayer.layerProjectService,
442 RuntimeLayer.layerEventSink,
443 RuntimeLayer.layerEventInfrastructure,
449444).pipe(
450445 Layer.provide(
451446 Layer.mock(WorkspacePaths.WorkspacePaths)({
......@@ -472,16 +467,16 @@ const SharedApplicationDataPlaneTestLayer = Layer.mergeAll(
472467 }),
473468 ),
474469 Layer.provide(McpSessionRegistryTestkit.layer),
475 Layer.provideMerge(SqlitePersistenceMemory),
476 Layer.provide(CheckpointStoreTestLayer),
477 Layer.provide(ServerConfigLayer),
470 Layer.provideMerge(SqlitePersistence.layerMemory),
471 Layer.provide(layerCheckpointStoreTest),
472 Layer.provide(layerServerConfig),
478473 Layer.provide(ServerSettings.layerTest()),
479 Layer.provide(TestProviderInstanceRegistry),
480 Layer.provide(GitWorkflowTestLayer),
481 Layer.provide(PlatformTestLayer),
474 Layer.provide(layerTestProviderInstanceRegistry),
475 Layer.provide(layerGitWorkflowTest),
476 Layer.provide(layerPlatformTest),
482477);
483478
484it.layer(TestLayer)("OrchestrationV2LayerLive", (it) => {
479it.layer(layerTest)("OrchestrationV2LayerLive", (it) => {
485480 it.effect("emits model updates separately from provider switches", () =>
486481 Effect.gen(function* () {
487482 const orchestrator = yield* Orchestrator.OrchestratorV2;
......@@ -837,7 +832,7 @@ it.layer(TestLayer)("OrchestrationV2LayerLive", (it) => {
837832 assert.deepEqual(yield* outbox.listByCommandId(commandId), []);
838833 }
839834 }
840 }).pipe(Effect.provide(Layer.fresh(TestLayer))),
835 }).pipe(Effect.provide(Layer.fresh(layerTest))),
841836 );
842837
843838 it.effect("resolves delivery intent against the active run and starts after it completes", () =>
......@@ -1478,7 +1473,7 @@ it.layer(TestLayer)("OrchestrationV2LayerLive", (it) => {
14781473 );
14791474});
14801475
1481it.layer(LegacyImportTestLayer)("OrchestrationV2 legacy import", (it) => {
1476it.layer(layerLegacyImportTest)("OrchestrationV2 legacy import", (it) => {
14821477 it.effect("hydrates imported transcripts before commands and propagates hydration failures", () =>
14831478 Effect.gen(function* () {
14841479 const sql = yield* SqlClient.SqlClient;
......@@ -1678,7 +1673,7 @@ it.layer(LegacyImportTestLayer)("OrchestrationV2 legacy import", (it) => {
16781673 );
16791674});
16801675
1681it.layer(TestLayer)("OrchestrationV2LayerLive lifecycle", (it) => {
1676it.layer(layerTest)("OrchestrationV2LayerLive lifecycle", (it) => {
16821677 it.effect("applies lifecycle commands idempotently and emits archive/removal shell deltas", () =>
16831678 Effect.gen(function* () {
16841679 const orchestrator = yield* Orchestrator.OrchestratorV2;
......@@ -4349,7 +4344,7 @@ it.layer(TestLayer)("OrchestrationV2LayerLive lifecycle", (it) => {
43494344 );
43504345});
43514346
4352it.layer(SharedApplicationDataPlaneTestLayer)("pending provider interruption", (it) => {
4347it.layer(layerSharedApplicationDataPlaneTest)("pending provider interruption", (it) => {
43534348 it.effect("interrupts a pending provider start without launching provider work", () =>
43544349 Effect.gen(function* () {
43554350 const projects = yield* ProjectService.ProjectService;
......@@ -4423,7 +4418,7 @@ it.layer(SharedApplicationDataPlaneTestLayer)("pending provider interruption", (
44234418 );
44244419});
44254420
4426it.layer(SharedApplicationDataPlaneTestLayer)("snooze projection", (it) => {
4421it.layer(layerSharedApplicationDataPlaneTest)("snooze projection", (it) => {
44274422 it.effect("carries snooze state through the V2 shell projection", () =>
44284423 Effect.gen(function* () {
44294424 const projects = yield* ProjectService.ProjectService;
......@@ -4498,7 +4493,7 @@ it.layer(SharedApplicationDataPlaneTestLayer)("snooze projection", (it) => {
44984493 );
44994494});
45004495
4501it.layer(SharedApplicationDataPlaneTestLayer)("visited projection", (it) => {
4496it.layer(layerSharedApplicationDataPlaneTest)("visited projection", (it) => {
45024497 it.effect("carries the visited watermark through the V2 shell projection", () =>
45034498 Effect.gen(function* () {
45044499 const projects = yield* ProjectService.ProjectService;
......@@ -4606,7 +4601,7 @@ it.layer(SharedApplicationDataPlaneTestLayer)("visited projection", (it) => {
46064601 );
46074602});
46084603
4609it.layer(SharedApplicationDataPlaneTestLayer)("shared application data plane", (it) => {
4604it.layer(layerSharedApplicationDataPlaneTest)("shared application data plane", (it) => {
46104605 it.effect("orders retained project transactions and V2 thread transactions in one source", () =>
46114606 Effect.gen(function* () {
46124607 const projects = yield* ProjectService.ProjectService;
......@@ -4681,7 +4676,7 @@ it.layer(SharedApplicationDataPlaneTestLayer)("shared application data plane", (
46814676 );
46824677});
46834678
4684it.layer(TestLayer)("usage-limit recovery", (it) => {
4679it.layer(layerTest)("usage-limit recovery", (it) => {
46854680 it.effect.each(["interrupted", "usage_limit"] as const)(
46864681 "manually resumes an %s run ahead of its queued message only once",
46874682 (reason) =>
apps/server/src/orchestration-v2/runtimeLayer.ts+150-138
......@@ -5,7 +5,7 @@ import * as OrchestrationCommandReceipts from "../persistence/Layers/Orchestrati
55import * as OrchestrationEventStore from "../persistence/Layers/OrchestrationEventStore.ts";
66import * as ProviderSessionRuntime from "../persistence/ProviderSessionRuntime.ts";
77import * as TextGeneration from "../textGeneration/TextGeneration.ts";
8import { ProviderAuthServiceLive } from "../provider/Layers/ProviderAuthService.ts";
8import * as ProviderAuthService from "../provider/Layers/ProviderAuthService.ts";
99import * as AgentSessionImporter from "../project/AgentSessionImporter.ts";
1010import * as AgentSessionScanner from "../project/AgentSessionScanner.ts";
1111import * as ProjectService from "../project/ProjectService.ts";
......@@ -51,57 +51,57 @@ import * as ScheduledTaskService from "../scheduledTasks/ScheduledTaskService.ts
5151import * as SecretRequests from "../secrets/SecretRequests.ts";
5252
5353/** The shared application event log and its command receipts. */
54export const OrchestrationEventInfrastructureLayerLive = Layer.mergeAll(
55 OrchestrationEventStore.OrchestrationEventStoreLive,
56 OrchestrationCommandReceipts.OrchestrationCommandReceiptRepositoryLive,
54export const layerEventInfrastructure = Layer.mergeAll(
55 OrchestrationEventStore.layer,
56 OrchestrationCommandReceipts.layer,
5757);
5858
59const runtimePolicyProvided = RuntimePolicy.layerFromProjectStore.pipe(
59const layerRuntimePolicyProvided = RuntimePolicy.layerFromProjectStore.pipe(
6060 Layer.provide(ProjectStore.layer),
6161);
6262
63const eventStoreProvided = EventStore.layerFromOrchestrationEventStore.pipe(
64 Layer.provide(OrchestrationEventInfrastructureLayerLive),
63const layerEventStoreProvided = EventStore.layerFromOrchestrationEventStore.pipe(
64 Layer.provide(layerEventInfrastructure),
6565);
66const commandReceiptStoreProvided = CommandReceiptStore.layerFromApplicationReceipts.pipe(
67 Layer.provide(OrchestrationEventInfrastructureLayerLive),
66const layerCommandReceiptStoreProvided = CommandReceiptStore.layerFromApplicationReceipts.pipe(
67 Layer.provide(layerEventInfrastructure),
6868);
6969
70const storesLayer = Layer.mergeAll(
71 OrchestrationEventInfrastructureLayerLive,
72 eventStoreProvided,
70const layerStores = Layer.mergeAll(
71 layerEventInfrastructure,
72 layerEventStoreProvided,
7373 ProjectionStore.layer,
7474 ProjectStore.layer,
75 commandReceiptStoreProvided,
75 layerCommandReceiptStoreProvided,
7676 EffectOutbox.layer,
7777 TurnItemPositionStore.layer,
7878);
7979
80export const OrchestrationV2EventSinkLayerLive = EventSink.layerFromStores.pipe(
81 Layer.provide(storesLayer),
80export const layerEventSink = EventSink.layerFromStores.pipe(Layer.provide(layerStores));
81const layerEventSinkProvided = layerEventSink;
82const layerProjectionMaintenanceProvided = ProjectionMaintenance.layer.pipe(
83 Layer.provide(layerStores),
8284);
83const eventSinkProvided = OrchestrationV2EventSinkLayerLive;
84const projectionMaintenanceProvided = ProjectionMaintenance.layer.pipe(Layer.provide(storesLayer));
85const legacyV1ThreadImporterProvided = LegacyV1ThreadImporter.layer.pipe(
86 Layer.provide(eventSinkProvided),
85const layerLegacyV1ThreadImporterProvided = LegacyV1ThreadImporter.layer.pipe(
86 Layer.provide(layerEventSinkProvided),
8787);
8888
89export const ProjectServiceLayerLive = ProjectService.layer.pipe(
89export const layerProjectService = ProjectService.layer.pipe(
9090 Layer.provide(
9191 Layer.mergeAll(
9292 ProjectStore.layer,
9393 ProjectionStore.layer,
94 eventSinkProvided,
94 layerEventSinkProvided,
9595 IdAllocator.layer,
96 legacyV1ThreadImporterProvided,
96 layerLegacyV1ThreadImporterProvided,
9797 ),
9898 ),
9999);
100100
101const providerEventIngestorProvided = ProviderEventIngestor.layer.pipe(
101const layerProviderEventIngestorProvided = ProviderEventIngestor.layer.pipe(
102102 Layer.provide(
103103 Layer.mergeAll(
104 eventSinkProvided,
104 layerEventSinkProvided,
105105 IdAllocator.layer,
106106 ProjectionStore.layer,
107107 ThreadCommandExecutor.layer,
......@@ -109,222 +109,234 @@ const providerEventIngestorProvided = ProviderEventIngestor.layer.pipe(
109109 ),
110110);
111111
112const checkpointServiceProvided = CheckpointService.layer.pipe(Layer.provide(IdAllocator.layer));
113const contextHandoffServiceProvided = ContextHandoffService.layer.pipe(
112const layerCheckpointServiceProvided = CheckpointService.layer.pipe(
113 Layer.provide(IdAllocator.layer),
114);
115const layerContextHandoffServiceProvided = ContextHandoffService.layer.pipe(
114116 Layer.provide(IdAllocator.layer),
115117);
116118
117const providerAdapterRegistryProvided = ProviderAdapterRegistry.layerFromProviderInstanceRegistry;
118const providerSwitchServiceProvided = ProviderSwitchService.layer.pipe(
119 Layer.provide(providerAdapterRegistryProvided),
119const layerProviderAdapterRegistryProvided =
120 ProviderAdapterRegistry.layerFromProviderInstanceRegistry;
121const layerProviderSwitchServiceProvided = ProviderSwitchService.layer.pipe(
122 Layer.provide(layerProviderAdapterRegistryProvided),
120123);
121124
122const providerSessionManagerProvided = ProviderSessionManager.layer.pipe(
125const layerProviderSessionManagerProvided = ProviderSessionManager.layer.pipe(
123126 Layer.provide(
124127 Layer.mergeAll(
125 providerAdapterRegistryProvided,
126 eventSinkProvided,
128 layerProviderAdapterRegistryProvided,
129 layerEventSinkProvided,
127130 IdAllocator.layer,
128 providerEventIngestorProvided,
131 layerProviderEventIngestorProvided,
129132 ProjectionStore.layer,
130133 ),
131134 ),
132135);
133136
134const providerAuthServiceProvided = ProviderAuthServiceLive.pipe(
135 Layer.provide(Layer.merge(ProjectionStore.layer, providerSessionManagerProvided)),
137const layerProviderAuthServiceProvided = ProviderAuthService.layer.pipe(
138 Layer.provide(Layer.merge(ProjectionStore.layer, layerProviderSessionManagerProvided)),
136139);
137140
138const runExecutionServiceProvided = RunExecutionService.layer.pipe(
141const layerRunExecutionServiceProvided = RunExecutionService.layer.pipe(
139142 Layer.provide(
140143 Layer.mergeAll(
141 checkpointServiceProvided,
142 eventSinkProvided,
144 layerCheckpointServiceProvided,
145 layerEventSinkProvided,
143146 IdAllocator.layer,
144 providerEventIngestorProvided,
147 layerProviderEventIngestorProvided,
145148 ),
146149 ),
147150);
148151
149const providerTurnStartServiceProvided = ProviderTurnStartService.layer.pipe(
152const layerProviderTurnStartServiceProvided = ProviderTurnStartService.layer.pipe(
150153 Layer.provide(
151154 Layer.mergeAll(
152 contextHandoffServiceProvided,
153 eventSinkProvided,
155 layerContextHandoffServiceProvided,
156 layerEventSinkProvided,
154157 IdAllocator.layer,
155158 ProjectionStore.layer,
156 providerSessionManagerProvided,
157 providerAuthServiceProvided,
158 runExecutionServiceProvided,
159 runtimePolicyProvided,
159 layerProviderSessionManagerProvided,
160 layerProviderAuthServiceProvided,
161 layerRunExecutionServiceProvided,
162 layerRuntimePolicyProvided,
160163 ),
161164 ),
162165);
163166
164const providerTurnControlServiceProvided = ProviderTurnControlService.layer.pipe(
165 Layer.provide(Layer.merge(ProjectionStore.layer, providerSessionManagerProvided)),
167const layerProviderTurnControlServiceProvided = ProviderTurnControlService.layer.pipe(
168 Layer.provide(Layer.merge(ProjectionStore.layer, layerProviderSessionManagerProvided)),
166169);
167const runtimeRequestServiceProvided = RuntimeRequestService.layer.pipe(
168 Layer.provide(Layer.merge(ProjectionStore.layer, providerSessionManagerProvided)),
170const layerRuntimeRequestServiceProvided = RuntimeRequestService.layer.pipe(
171 Layer.provide(Layer.merge(ProjectionStore.layer, layerProviderSessionManagerProvided)),
169172);
170const checkpointRollbackServiceProvided = CheckpointRollbackService.layer.pipe(
173const layerCheckpointRollbackServiceProvided = CheckpointRollbackService.layer.pipe(
171174 Layer.provide(
172175 Layer.mergeAll(
173176 ProjectStore.layer,
174 checkpointServiceProvided,
175 eventSinkProvided,
177 layerCheckpointServiceProvided,
178 layerEventSinkProvided,
176179 IdAllocator.layer,
177180 ProjectionStore.layer,
178 providerSessionManagerProvided,
179 runtimePolicyProvided,
181 layerProviderSessionManagerProvided,
182 layerRuntimePolicyProvided,
180183 ),
181184 ),
182185);
183const checkpointCaptureServiceProvided = CheckpointCaptureService.layer.pipe(
186const layerCheckpointCaptureServiceProvided = CheckpointCaptureService.layer.pipe(
184187 Layer.provide(
185188 Layer.mergeAll(
186 checkpointServiceProvided,
187 eventSinkProvided,
189 layerCheckpointServiceProvided,
190 layerEventSinkProvided,
188191 IdAllocator.layer,
189192 ProjectionStore.layer,
190193 ),
191194 ),
192195);
193const runFinalizationServiceProvided = RunFinalizationService.layer.pipe(
194 Layer.provide(Layer.merge(checkpointCaptureServiceProvided, ProjectionStore.layer)),
196const layerRunFinalizationServiceProvided = RunFinalizationService.layer.pipe(
197 Layer.provide(Layer.merge(layerCheckpointCaptureServiceProvided, ProjectionStore.layer)),
195198);
196199
197const orchestratorProvided = Orchestrator.layer.pipe(
200const layerOrchestratorProvided = Orchestrator.layer.pipe(
198201 Layer.provide(
199202 Layer.mergeAll(
200 checkpointServiceProvided,
203 layerCheckpointServiceProvided,
201204 CommandPolicy.layer,
202 storesLayer,
203 eventSinkProvided,
204 commandReceiptStoreProvided,
205 contextHandoffServiceProvided,
205 layerStores,
206 layerEventSinkProvided,
207 layerCommandReceiptStoreProvided,
208 layerContextHandoffServiceProvided,
206209 IdAllocator.layer,
207210 ProjectStore.layer,
208 providerAdapterRegistryProvided,
211 layerProviderAdapterRegistryProvided,
209212 // Same layer reference as the continuation worker and the adapter
210213 // infrastructure so layer memoization yields one shared request queue.
211214 ProviderContinuationRequests.layer,
212 providerEventIngestorProvided,
213 runtimePolicyProvided,
214 providerSessionManagerProvided,
215 providerSwitchServiceProvided,
216 runExecutionServiceProvided,
215 layerProviderEventIngestorProvided,
216 layerRuntimePolicyProvided,
217 layerProviderSessionManagerProvided,
218 layerProviderSwitchServiceProvided,
219 layerRunExecutionServiceProvided,
217220 ThreadForkService.layer,
218221 ),
219222 ),
220223);
221224
222const agentSessionImporterProvided = AgentSessionImporter.layer.pipe(
225const layerAgentSessionImporterProvided = AgentSessionImporter.layer.pipe(
223226 Layer.provide(
224227 Layer.mergeAll(
225228 AgentSessionScanner.layer,
226 ProjectServiceLayerLive,
227 orchestratorProvided,
228 eventSinkProvided,
229 layerProjectService,
230 layerOrchestratorProvided,
231 layerEventSinkProvided,
229232 IdAllocator.layer,
230233 ProviderSessionRuntime.layer,
231234 ),
232235 ),
233236);
234237
235const threadManagementProvided = ThreadManagementService.layerWithLegacyImporter.pipe(
236 Layer.provide(Layer.merge(orchestratorProvided, legacyV1ThreadImporterProvided)),
238const layerThreadManagementProvided = ThreadManagementService.layerWithLegacyImporter.pipe(
239 Layer.provide(Layer.merge(layerOrchestratorProvided, layerLegacyV1ThreadImporterProvided)),
237240);
238export const ProjectSetupScriptRunnerLayerLive = ProjectSetupScriptRunner.layer.pipe(
239 Layer.provide(ProjectServiceLayerLive),
241export const layerProjectSetupScriptRunner = ProjectSetupScriptRunner.layer.pipe(
242 Layer.provide(layerProjectService),
240243);
241const managedProjectFoldersProvided = ManagedProjectFolders.layer.pipe(
242 Layer.provide(ProjectServiceLayerLive),
244const layerManagedProjectFoldersProvided = ManagedProjectFolders.layer.pipe(
245 Layer.provide(layerProjectService),
243246);
244const threadLaunchProvided = ThreadLaunchService.layer.pipe(
247const layerThreadLaunchProvided = ThreadLaunchService.layer.pipe(
245248 Layer.provide(
246249 Layer.mergeAll(
247 ProjectServiceLayerLive,
248 ProjectSetupScriptRunnerLayerLive,
249 managedProjectFoldersProvided,
250 threadManagementProvided,
251 commandReceiptStoreProvided,
250 layerProjectService,
251 layerProjectSetupScriptRunner,
252 layerManagedProjectFoldersProvided,
253 layerThreadManagementProvided,
254 layerCommandReceiptStoreProvided,
252255 IdAllocator.layer,
253256 ),
254257 ),
255258);
256const threadLifecycleProvided = ThreadLifecycleService.layer.pipe(
257 Layer.provide(threadManagementProvided),
259const layerThreadLifecycleProvided = ThreadLifecycleService.layer.pipe(
260 Layer.provide(layerThreadManagementProvided),
261);
262const layerSecretRequestsProvided = SecretRequests.layer.pipe(
263 Layer.provide(layerThreadManagementProvided),
258264);
259const secretRequestsProvided = SecretRequests.layer.pipe(Layer.provide(threadManagementProvided));
260const scheduledTaskProvided = ScheduledTaskService.layer.pipe(
265const layerScheduledTaskProvided = ScheduledTaskService.layer.pipe(
261266 Layer.provide(
262 Layer.mergeAll(threadLaunchProvided, threadManagementProvided, secretRequestsProvided),
267 Layer.mergeAll(
268 layerThreadLaunchProvided,
269 layerThreadManagementProvided,
270 layerSecretRequestsProvided,
271 ),
263272 ),
264273);
265const providerContinuationWorkerProvided = ProviderContinuationService.workerLive.pipe(
274const layerProviderContinuationWorkerProvided = ProviderContinuationService.layer.pipe(
266275 Layer.provide(
267 Layer.mergeAll(ProviderContinuationRequests.layer, threadManagementProvided, IdAllocator.layer),
276 Layer.mergeAll(
277 ProviderContinuationRequests.layer,
278 layerThreadManagementProvided,
279 IdAllocator.layer,
280 ),
268281 ),
269282);
270const threadTitleRegenerationProvided = ThreadTitleRegenerationService.layer.pipe(
271 Layer.provide(Layer.mergeAll(threadManagementProvided, ProjectStore.layer, TextGeneration.layer)),
283const layerThreadTitleRegenerationProvided = ThreadTitleRegenerationService.layer.pipe(
284 Layer.provide(
285 Layer.mergeAll(layerThreadManagementProvided, ProjectStore.layer, TextGeneration.layer),
286 ),
272287);
273const effectExecutorProvided = EffectWorker.executorLayer.pipe(
288const layerEffectExecutorProvided = EffectWorker.layerExecutor.pipe(
274289 Layer.provide(
275290 Layer.mergeAll(
276 runFinalizationServiceProvided,
277 checkpointRollbackServiceProvided,
278 providerSessionManagerProvided,
279 providerTurnControlServiceProvided,
280 providerTurnStartServiceProvided,
281 runtimeRequestServiceProvided,
282 threadTitleRegenerationProvided,
283 threadManagementProvided,
291 layerRunFinalizationServiceProvided,
292 layerCheckpointRollbackServiceProvided,
293 layerProviderSessionManagerProvided,
294 layerProviderTurnControlServiceProvided,
295 layerProviderTurnStartServiceProvided,
296 layerRuntimeRequestServiceProvided,
297 layerThreadTitleRegenerationProvided,
298 layerThreadManagementProvided,
284299 ),
285300 ),
286301);
287const effectWorkerProvided = EffectWorker.layer.pipe(
288 Layer.provide(Layer.merge(storesLayer, effectExecutorProvided)),
302const layerEffectWorkerProvided = EffectWorker.layer.pipe(
303 Layer.provide(Layer.merge(layerStores, layerEffectExecutorProvided)),
289304);
290const providerRuntimeRecoveryProvided = ProviderRuntimeRecoveryService.layer.pipe(
305const layerProviderRuntimeRecoveryProvided = ProviderRuntimeRecoveryService.layer.pipe(
291306 Layer.provide(
292307 Layer.mergeAll(
293 effectWorkerProvided,
294 storesLayer,
295 eventSinkProvided,
308 layerEffectWorkerProvided,
309 layerStores,
310 layerEventSinkProvided,
296311 IdAllocator.layer,
297312 ProjectionStore.layer,
298313 ),
299314 ),
300315);
301316
302export const OrchestrationV2LayerLive = Layer.mergeAll(
303 orchestratorProvided,
304 threadManagementProvided,
305 effectWorkerProvided,
306 providerSessionManagerProvided,
307 providerAuthServiceProvided,
308 providerRuntimeRecoveryProvided,
309 projectionMaintenanceProvided,
310 legacyV1ThreadImporterProvided,
317export const layer = Layer.mergeAll(
318 layerOrchestratorProvided,
319 layerThreadManagementProvided,
320 layerEffectWorkerProvided,
321 layerProviderSessionManagerProvided,
322 layerProviderAuthServiceProvided,
323 layerProviderRuntimeRecoveryProvided,
324 layerProjectionMaintenanceProvided,
325 layerLegacyV1ThreadImporterProvided,
311326);
312327
313export const OrchestrationV2ProductionLayerLive = Layer.mergeAll(
314 OrchestrationV2LayerLive.pipe(Layer.provide(ProjectServiceLayerLive)),
315 ProjectServiceLayerLive,
316 managedProjectFoldersProvided,
317 threadLaunchProvided,
318 threadLifecycleProvided,
319 scheduledTaskProvided,
320 secretRequestsProvided,
321 UsageLimitRecoveryWorker.workerLive.pipe(
322 Layer.provide(Layer.mergeAll(ProjectionStore.layer, threadManagementProvided)),
328export const layerProduction = Layer.mergeAll(
329 layer.pipe(Layer.provide(layerProjectService)),
330 layerProjectService,
331 layerManagedProjectFoldersProvided,
332 layerThreadLaunchProvided,
333 layerThreadLifecycleProvided,
334 layerScheduledTaskProvided,
335 layerSecretRequestsProvided,
336 UsageLimitRecoveryWorker.layer.pipe(
337 Layer.provide(Layer.mergeAll(ProjectionStore.layer, layerThreadManagementProvided)),
323338 ),
324 providerContinuationWorkerProvided,
325 agentSessionImporterProvided,
326 EffectOutbox.pruneWorkerLive.pipe(Layer.provide(EffectOutbox.layer)),
327).pipe(
328 Layer.provide(Scheduler.layer),
329 Layer.provideMerge(OrchestrationEventInfrastructureLayerLive),
330);
339 layerProviderContinuationWorkerProvided,
340 layerAgentSessionImporterProvided,
341 EffectOutbox.layerPruneWorker.pipe(Layer.provide(EffectOutbox.layer)),
342).pipe(Layer.provide(Scheduler.layer), Layer.provideMerge(layerEventInfrastructure));
apps/server/src/orchestration-v2/testkit/ClaudeReplayFixtures.integration.test.ts+2-2
......@@ -18,7 +18,7 @@ import { provideDeterministicTestRuntime } from "./DeterministicRuntime.ts";
1818import { ORCHESTRATOR_REPLAY_FIXTURES } from "./fixtures/index.ts";
1919import { subagentInput } from "./fixtures/subagent/input.ts";
2020import { runOrchestratorV2Scenario } from "./OrchestratorScenario.ts";
21import { makeOrchestratorV2ProviderReplayLayer } from "./ProviderReplayHarness.ts";
21import * as ProviderReplayHarness from "./ProviderReplayHarness.ts";
2222import { materializeReplayTranscriptRuntimeInstructions } from "./ReplayTranscriptNdjson.ts";
2323import { CLAUDE_MODEL_SELECTION, materializeFixtureInput } from "./fixtures/shared.ts";
2424import {
......@@ -188,7 +188,7 @@ describe("Claude Agent SDK replay fixtures", () => {
188188 assert.deepEqual(after.messages, child.messages);
189189 }).pipe(
190190 Effect.provide(
191 makeOrchestratorV2ProviderReplayLayer(scenario, ClaudeOrchestratorReplayHarness),
191 ProviderReplayHarness.layerProviderReplay(scenario, ClaudeOrchestratorReplayHarness),
192192 ),
193193 provideDeterministicTestRuntime,
194194 Effect.scoped,
apps/server/src/orchestration-v2/testkit/OrchestratorReplayRecovery.integration.test.ts+9-11
......@@ -12,18 +12,16 @@ import {
1212 ClaudeOrchestratorReplayHarness,
1313 makeClaudeRestartReplayHarness,
1414} from "../Adapters/ClaudeAdapterV2.testkit.ts";
15import {
16 CodexOrchestratorReplayHarness,
17 makeCodexProviderAdapterRegistryReplayLayer,
18} from "../Adapters/CodexAdapterV2.testkit.ts";
15import { CodexOrchestratorReplayHarness } from "../Adapters/CodexAdapterV2.testkit.ts";
16import * as CodexAdapterV2Testkit from "../Adapters/CodexAdapterV2.testkit.ts";
1917import {
2018 type CursorAgentSdkReplayTranscript,
2119 CursorOrchestratorReplayHarness,
2220 makeCursorAgentSdkReplayRunner,
23 makeCursorProviderAdapterRegistryReplayLayer,
2421} from "../Adapters/CursorAdapterV2.testkit.ts";
22import * as CursorAdapterV2Testkit from "../Adapters/CursorAdapterV2.testkit.ts";
2523import * as IdAllocator from "../IdAllocator.ts";
26import { makeSqlitePersistenceLive } from "../../persistence/Layers/Sqlite.ts";
24import * as SqlitePersistence from "../../persistence/Layers/Sqlite.ts";
2725import { provideDeterministicTestRuntime } from "./DeterministicRuntime.ts";
2826import {
2927 CLAUDE_MODEL_SELECTION,
......@@ -136,12 +134,12 @@ const runCursorRecovery = Effect.fn("runCursorRecovery")(function* (input: {
136134 const { phase1Commands, phase1Steps, phase2Commands, phase2Steps } =
137135 splitAfterFirstIdle(materialized);
138136 const options = {
139 databaseLayer: makeSqlitePersistenceLive(dbPath).pipe(Layer.provide(NodeServices.layer)),
137 databaseLayer: SqlitePersistence.layerFromPath(dbPath).pipe(Layer.provide(NodeServices.layer)),
140138 };
141139 const harness = {
142140 ...CursorOrchestratorReplayHarness,
143141 makeProviderAdapterRegistryLayer: () =>
144 makeCursorProviderAdapterRegistryReplayLayer(input.transcript, {
142 CursorAdapterV2Testkit.layer(input.transcript, {
145143 runner: input.runner,
146144 assertCompleteOnFinalize: false,
147145 }),
......@@ -237,10 +235,10 @@ describe("orchestrator replay recovery", () => {
237235 const harness = {
238236 ...CodexOrchestratorReplayHarness,
239237 makeProviderAdapterRegistryLayer: () =>
240 makeCodexProviderAdapterRegistryReplayLayer({ transcript, driver }),
238 CodexAdapterV2Testkit.layer({ transcript, driver }),
241239 };
242240 const options = {
243 databaseLayer: makeSqlitePersistenceLive(dbPath).pipe(
241 databaseLayer: SqlitePersistence.layerFromPath(dbPath).pipe(
244242 Layer.provide(NodeServices.layer),
245243 ),
246244 };
......@@ -340,7 +338,7 @@ describe("orchestrator replay recovery", () => {
340338 splitAfterFirstIdle(materialized);
341339 const { harness, assertComplete } = makeClaudeRestartReplayHarness(transcript);
342340 const options = {
343 databaseLayer: makeSqlitePersistenceLive(path.join(tempDir, "state.sqlite")).pipe(
341 databaseLayer: SqlitePersistence.layerFromPath(path.join(tempDir, "state.sqlite")).pipe(
344342 Layer.provide(NodeServices.layer),
345343 ),
346344 };
apps/server/src/orchestration-v2/testkit/OrchestratorReplayRestartBackgroundNote.integration.test.ts+8-10
......@@ -12,7 +12,7 @@ import {
1212} from "../Adapters/ClaudeAdapterV2.testkit.ts";
1313import * as IdAllocator from "../IdAllocator.ts";
1414import * as EffectWorker from "../EffectWorker.ts";
15import { makeSqlitePersistenceLive } from "../../persistence/Layers/Sqlite.ts";
15import * as SqlitePersistence from "../../persistence/Layers/Sqlite.ts";
1616import { provideDeterministicTestRuntime } from "./DeterministicRuntime.ts";
1717import { CLAUDE_BACKGROUND_SUBAGENT_AFTER_ROOT_PROMPT } from "./fixtures/claude_background_subagent_after_root/input.ts";
1818import {
......@@ -20,10 +20,8 @@ import {
2020 materializeFixtureInput,
2121 projectionFor,
2222} from "./fixtures/shared.ts";
23import {
24 makeOrchestratorV2ProviderReplayLayer,
25 runOrchestratorV2ProviderReplayScenario,
26} from "./ProviderReplayHarness.ts";
23import { runOrchestratorV2ProviderReplayScenario } from "./ProviderReplayHarness.ts";
24import * as ProviderReplayHarness from "./ProviderReplayHarness.ts";
2725import { runOrchestratorV2Scenario } from "./OrchestratorScenario.ts";
2826import { checkpointWorkspace } from "./ReplayFixtureWorkspace.ts";
2927import { readProviderReplayTranscript } from "./ReplayTranscriptNdjson.ts";
......@@ -162,7 +160,7 @@ const runRestart = Effect.fn("runRestart")(function* (input: {
162160 const phase1Steps = materialized.steps.slice(0, firstIdle + 1);
163161 const phase2Steps = materialized.steps.slice(firstIdle + 1);
164162 const { harness, assertComplete } = makeClaudeRestartReplayHarness(transcript);
165 const databaseLayer = makeSqlitePersistenceLive(path.join(tempDir, "state.sqlite")).pipe(
163 const layerDatabase = SqlitePersistence.layerFromPath(path.join(tempDir, "state.sqlite")).pipe(
166164 Layer.provide(NodeServices.layer),
167165 );
168166 const scenario = (name: string, steps: typeof materialized.steps) => ({
......@@ -176,7 +174,7 @@ const runRestart = Effect.fn("runRestart")(function* (input: {
176174
177175 const before = yield* Effect.scoped(
178176 runOrchestratorV2ProviderReplayScenario(scenario("before-restart", phase1Steps), harness, {
179 databaseLayer,
177 databaseLayer: layerDatabase,
180178 }),
181179 );
182180 const settled = projectionFor(before, SCENARIO);
......@@ -193,8 +191,8 @@ const runRestart = Effect.fn("runRestart")(function* (input: {
193191 return yield* runOrchestratorV2Scenario(restartScenario);
194192 }).pipe(
195193 Effect.provide(
196 makeOrchestratorV2ProviderReplayLayer(restartScenario, harness, {
197 databaseLayer,
194 ProviderReplayHarness.layerProviderReplay(restartScenario, harness, {
195 databaseLayer: layerDatabase,
198196 recoverOnStartup: true,
199197 continueThreadsAfterServerUpdate: input.continueThreadsAfterServerUpdate,
200198 }),
......@@ -211,7 +209,7 @@ const runRestart = Effect.fn("runRestart")(function* (input: {
211209
212210 const after = yield* Effect.scoped(
213211 runOrchestratorV2ProviderReplayScenario(scenario("after-restart", phase2Steps), harness, {
214 databaseLayer,
212 databaseLayer: layerDatabase,
215213 continueThreadsAfterServerUpdate: input.continueThreadsAfterServerUpdate,
216214 }),
217215 );
apps/server/src/orchestration-v2/testkit/ProviderReplayHarness.ts+120-107
......@@ -13,7 +13,7 @@ import type { SqlError } from "effect/sql/SqlError";
1313
1414import * as CheckpointStore from "../../checkpointing/CheckpointStore.ts";
1515import * as ServerConfig from "../../config.ts";
16import { SqlitePersistenceMemory } from "../../persistence/Layers/Sqlite.ts";
16import * as SqlitePersistence from "../../persistence/Layers/Sqlite.ts";
1717import * as ServerSettings from "../../serverSettings.ts";
1818import * as ThreadManagementService from "../ThreadManagementService.ts";
1919import * as McpSessionRegistryTestkit from "../../mcp/McpSessionRegistry.testkit.ts";
......@@ -43,7 +43,7 @@ import * as ProviderSessionManager from "../ProviderSessionManager.ts";
4343import * as ProviderSwitchService from "../ProviderSwitchService.ts";
4444import * as ProviderTurnControlService from "../ProviderTurnControlService.ts";
4545import * as ProviderTurnStartService from "../ProviderTurnStartService.ts";
46import { worktreeRepairDependenciesTestLayer } from "../ProviderTurnStartService.testkit.ts";
46import * as ProviderTurnStartServiceTestkit from "../ProviderTurnStartService.testkit.ts";
4747import * as RunExecutionService from "../RunExecutionService.ts";
4848import * as RunFinalizationService from "../RunFinalizationService.ts";
4949import * as ThreadTitleRegenerationService from "../ThreadTitleRegenerationService.ts";
......@@ -206,7 +206,7 @@ export function runOrchestratorV2ProviderReplayScenario<
206206 : [],
207207 ) ?? [],
208208 );
209 const layer = makeOrchestratorV2ProviderReplayLayer(scenario, harness, {
209 const layer = layerProviderReplay(scenario, harness, {
210210 ...options,
211211 replayGate,
212212 });
......@@ -214,10 +214,7 @@ export function runOrchestratorV2ProviderReplayScenario<
214214 return runOrchestratorV2Scenario(scenario, { replayGate }).pipe(Effect.provide(layer));
215215}
216216
217export function makeOrchestratorV2ProviderReplayLayer<
218 Transcript extends ProviderReplayTranscript,
219 Error,
220>(
217export function layerProviderReplay<Transcript extends ProviderReplayTranscript, Error>(
221218 scenario: OrchestratorV2ProviderReplayScenario<Transcript>,
222219 harness: OrchestratorV2ProviderReplayHarness<Transcript, Error>,
223220 options: {
......@@ -239,14 +236,14 @@ export function makeOrchestratorV2ProviderReplayLayer<
239236 Orchestrator.OrchestratorV2 | EffectWorker.OrchestrationEffectWorkerV2 | EventSink.EventSinkV2,
240237 Error | MigrationError | PlatformError.PlatformError | SqlError
241238> {
242 const registryLayer = harness.makeProviderAdapterRegistryLayer(
239 const layerRegistry = harness.makeProviderAdapterRegistryLayer(
243240 scenario.transcript,
244241 options.replayGate === undefined ? {} : { replayGate: options.replayGate },
245242 );
246 return makeOrchestratorV2ReplayLayerWithRegistry(scenario, registryLayer, options);
243 return layerWithRegistry(scenario, layerRegistry, options);
247244}
248245
249export function makeOrchestratorV2ReplayLayerWithRegistry<Error>(
246export function layerWithRegistry<Error>(
250247 scenario: Pick<OrchestratorV2ProviderReplayScenario, "name" | "runtimePolicyOverride">,
251248 registryLayer: Layer.Layer<ProviderAdapterRegistry.ProviderAdapterRegistryV2, Error>,
252249 options: {
......@@ -267,166 +264,173 @@ export function makeOrchestratorV2ReplayLayerWithRegistry<Error>(
267264 Orchestrator.OrchestratorV2 | EffectWorker.OrchestrationEffectWorkerV2 | EventSink.EventSinkV2,
268265 Error | MigrationError | PlatformError.PlatformError | SqlError
269266> {
270 const serverConfigLayer = Layer.effect(
267 const layerServerConfig = Layer.effect(
271268 ServerConfig.ServerConfig,
272269 makeReplayServerConfig(scenario.name).pipe(Effect.orDie),
273270 ).pipe(Layer.provide(NodeServices.layer));
274 const runtimeLayer =
271 const layerRuntime =
275272 scenario.runtimePolicyOverride === undefined
276273 ? RuntimePolicy.layer
277274 : RuntimePolicy.layerWithOverride(scenario.runtimePolicyOverride).pipe(
278275 Layer.provide(RuntimePolicy.layer),
279276 );
280 const databaseLayer = options.databaseLayer ?? SqlitePersistenceMemory;
277 const layerDatabase = options.databaseLayer ?? SqlitePersistence.layerMemory;
281278 // One queue shared by the adapters, the orchestrator, and the worker, like
282279 // runtimeLayer.ts; layer memoization keeps it a single instance.
283 const continuationRequestsLayer =
280 const layerContinuationRequests =
284281 options.runContinuationWorker === true ? ProviderContinuationRequests.layer : Layer.empty;
285 const providedRegistryLayer = registryLayer.pipe(Layer.provide(continuationRequestsLayer));
286 const serverSettingsLayer = ServerSettings.layerTest({
282 const layerProvidedRegistry = registryLayer.pipe(Layer.provide(layerContinuationRequests));
283 const layerServerSettings = ServerSettings.layerTest({
287284 responseStreamingMode: "turn",
288285 ...(options.continueThreadsAfterServerUpdate === undefined
289286 ? {}
290287 : { continueThreadsAfterServerUpdate: options.continueThreadsAfterServerUpdate }),
291288 }).pipe(Layer.orDie);
292 const storesLayer = Layer.mergeAll(
289 const layerStores = Layer.mergeAll(
293290 EventStore.layer,
294291 ProjectionStore.layer,
295292 ProjectStore.layer,
296293 CommandReceiptStore.layer,
297294 EffectOutbox.layer,
298295 TurnItemPositionStore.layer,
299 ).pipe(Layer.provide(databaseLayer));
300 const eventSinkProvided = EventSink.layerFromStores.pipe(
301 Layer.provide(Layer.mergeAll(storesLayer, databaseLayer)),
296 ).pipe(Layer.provide(layerDatabase));
297 const layerEventSinkProvided = EventSink.layerFromStores.pipe(
298 Layer.provide(Layer.mergeAll(layerStores, layerDatabase)),
299 );
300 const layerCommandReceiptStoreProvided = CommandReceiptStore.layer.pipe(
301 Layer.provide(layerDatabase),
302302 );
303 const commandReceiptStoreProvided = CommandReceiptStore.layer.pipe(Layer.provide(databaseLayer));
304 const providerEventIngestorProvided = ProviderEventIngestor.layer.pipe(
303 const layerProviderEventIngestorProvided = ProviderEventIngestor.layer.pipe(
305304 Layer.provide(
306305 Layer.mergeAll(
307 storesLayer,
308 eventSinkProvided,
306 layerStores,
307 layerEventSinkProvided,
309308 IdAllocator.layer,
310309 ThreadCommandExecutor.layer,
311310 ),
312311 ),
313312 );
314 const vcsDriverRegistryLayer = VcsDriverRegistry.layer.pipe(
313 const layerVcsDriverRegistry = VcsDriverRegistry.layer.pipe(
315314 Layer.provide(VcsProcess.layer),
316 Layer.provide(serverConfigLayer),
315 Layer.provide(layerServerConfig),
317316 Layer.provide(NodeServices.layer),
318317 );
319 const checkpointStoreLayer = CheckpointStore.layer.pipe(
320 Layer.provide(vcsDriverRegistryLayer),
318 const layerCheckpointStore = CheckpointStore.layer.pipe(
319 Layer.provide(layerVcsDriverRegistry),
321320 Layer.provide(NodeServices.layer),
322321 );
323 const checkpointServiceProvided = CheckpointService.layer.pipe(
324 Layer.provide(Layer.mergeAll(checkpointStoreLayer, IdAllocator.layer)),
322 const layerCheckpointServiceProvided = CheckpointService.layer.pipe(
323 Layer.provide(Layer.mergeAll(layerCheckpointStore, IdAllocator.layer)),
325324 );
326 const contextHandoffServiceProvided = ContextHandoffService.layer.pipe(
325 const layerContextHandoffServiceProvided = ContextHandoffService.layer.pipe(
327326 Layer.provide(IdAllocator.layer),
328327 );
329 const persistenceLayer = Layer.mergeAll(
330 storesLayer,
331 eventSinkProvided,
332 commandReceiptStoreProvided,
328 const layerPersistence = Layer.mergeAll(
329 layerStores,
330 layerEventSinkProvided,
331 layerCommandReceiptStoreProvided,
333332 IdAllocator.layer,
334 providerEventIngestorProvided,
333 layerProviderEventIngestorProvided,
335334 );
336 const providerSessionManagerProvided = ProviderSessionManager.layerWithOptions({
335 const layerProviderSessionManagerProvided = ProviderSessionManager.layerWithOptions({
337336 configureMcp: false,
338337 }).pipe(
339338 Layer.provide(
340339 Layer.mergeAll(
341 providedRegistryLayer,
342 eventSinkProvided,
340 layerProvidedRegistry,
341 layerEventSinkProvided,
343342 IdAllocator.layer,
344343 McpSessionRegistryTestkit.layer,
345 providerEventIngestorProvided,
346 storesLayer,
344 layerProviderEventIngestorProvided,
345 layerStores,
347346 ),
348347 ),
349348 );
350 const providerSwitchServiceProvided = ProviderSwitchService.layer.pipe(
351 Layer.provide(providedRegistryLayer),
349 const layerProviderSwitchServiceProvided = ProviderSwitchService.layer.pipe(
350 Layer.provide(layerProvidedRegistry),
352351 );
353 const runExecutionServiceProvided = RunExecutionService.layer.pipe(
352 const layerRunExecutionServiceProvided = RunExecutionService.layer.pipe(
354353 Layer.provide(
355354 Layer.mergeAll(
356 checkpointServiceProvided,
357 eventSinkProvided,
355 layerCheckpointServiceProvided,
356 layerEventSinkProvided,
358357 IdAllocator.layer,
359 providerEventIngestorProvided,
360 serverSettingsLayer,
358 layerProviderEventIngestorProvided,
359 layerServerSettings,
361360 ),
362361 ),
363362 );
364 const providerTurnStartServiceProvided = ProviderTurnStartService.layer.pipe(
363 const layerProviderTurnStartServiceProvided = ProviderTurnStartService.layer.pipe(
365364 Layer.provide(
366365 Layer.mergeAll(
367 contextHandoffServiceProvided,
368 eventSinkProvided,
366 layerContextHandoffServiceProvided,
367 layerEventSinkProvided,
369368 IdAllocator.layer,
370 storesLayer,
371 providerSessionManagerProvided,
369 layerStores,
370 layerProviderSessionManagerProvided,
372371 Layer.mock(ProviderAuthService.ProviderAuthService)({
373372 tryHandlePromptCommand: () => Effect.succeed(false),
374373 }),
375 runExecutionServiceProvided,
376 runtimeLayer,
374 layerRunExecutionServiceProvided,
375 layerRuntime,
377376 ),
378377 ),
379378 );
380 const providerTurnControlServiceProvided = ProviderTurnControlService.layer.pipe(
381 Layer.provide(Layer.merge(storesLayer, providerSessionManagerProvided)),
379 const layerProviderTurnControlServiceProvided = ProviderTurnControlService.layer.pipe(
380 Layer.provide(Layer.merge(layerStores, layerProviderSessionManagerProvided)),
382381 );
383 const runtimeRequestServiceProvided = RuntimeRequestService.layer.pipe(
384 Layer.provide(Layer.merge(storesLayer, providerSessionManagerProvided)),
382 const layerRuntimeRequestServiceProvided = RuntimeRequestService.layer.pipe(
383 Layer.provide(Layer.merge(layerStores, layerProviderSessionManagerProvided)),
385384 );
386 const checkpointRollbackServiceProvided = CheckpointRollbackService.layer.pipe(
385 const layerCheckpointRollbackServiceProvided = CheckpointRollbackService.layer.pipe(
387386 Layer.provide(
388387 Layer.mergeAll(
389 checkpointServiceProvided,
390 eventSinkProvided,
388 layerCheckpointServiceProvided,
389 layerEventSinkProvided,
391390 IdAllocator.layer,
392 storesLayer,
393 providerSessionManagerProvided,
394 runtimeLayer,
391 layerStores,
392 layerProviderSessionManagerProvided,
393 layerRuntime,
395394 ),
396395 ),
397396 );
398 const checkpointCaptureServiceProvided = CheckpointCaptureService.layer.pipe(
397 const layerCheckpointCaptureServiceProvided = CheckpointCaptureService.layer.pipe(
399398 Layer.provide(
400 Layer.mergeAll(checkpointServiceProvided, eventSinkProvided, IdAllocator.layer, storesLayer),
399 Layer.mergeAll(
400 layerCheckpointServiceProvided,
401 layerEventSinkProvided,
402 IdAllocator.layer,
403 layerStores,
404 ),
401405 ),
402406 );
403 const runFinalizationServiceProvided = RunFinalizationService.layer.pipe(
404 Layer.provide(Layer.merge(checkpointCaptureServiceProvided, storesLayer)),
407 const layerRunFinalizationServiceProvided = RunFinalizationService.layer.pipe(
408 Layer.provide(Layer.merge(layerCheckpointCaptureServiceProvided, layerStores)),
405409 );
406 const threadTitleRegenerationTestLayer = Layer.succeed(
410 const layerThreadTitleRegenerationTest = Layer.succeed(
407411 ThreadTitleRegenerationService.ThreadTitleRegenerationService,
408412 ThreadTitleRegenerationService.ThreadTitleRegenerationService.of({
409413 execute: () => Effect.void,
410414 }),
411415 );
412 const orchestratorProvided = Orchestrator.layer.pipe(
416 const layerOrchestratorProvided = Orchestrator.layer.pipe(
413417 Layer.provide(
414418 Layer.mergeAll(
415 checkpointServiceProvided,
419 layerCheckpointServiceProvided,
416420 CommandPolicy.layer,
417 contextHandoffServiceProvided,
418 persistenceLayer,
419 providedRegistryLayer,
420 continuationRequestsLayer,
421 runtimeLayer,
422 providerSessionManagerProvided,
423 providerSwitchServiceProvided,
424 runExecutionServiceProvided,
421 layerContextHandoffServiceProvided,
422 layerPersistence,
423 layerProvidedRegistry,
424 layerContinuationRequests,
425 layerRuntime,
426 layerProviderSessionManagerProvided,
427 layerProviderSwitchServiceProvided,
428 layerRunExecutionServiceProvided,
425429 ThreadForkService.layer,
426430 ),
427431 ),
428432 );
429 const threadManagementProvided = Layer.unwrap(
433 const layerThreadManagementProvided = Layer.unwrap(
430434 Effect.gen(function* () {
431435 const orchestrator = yield* Orchestrator.OrchestratorV2;
432436 return Layer.mock(ThreadManagementService.ThreadManagementService)({
......@@ -437,49 +441,53 @@ export function makeOrchestratorV2ReplayLayerWithRegistry<Error>(
437441 delegatedTaskResultPending: orchestrator.delegatedTaskResultPending,
438442 });
439443 }),
440 ).pipe(Layer.provide(orchestratorProvided));
441 const continuationWorkerProvided =
444 ).pipe(Layer.provide(layerOrchestratorProvided));
445 const layerContinuationWorkerProvided =
442446 options.runContinuationWorker === true
443 ? ProviderContinuationService.workerLive.pipe(
447 ? ProviderContinuationService.layer.pipe(
444448 Layer.provide(
445 Layer.mergeAll(continuationRequestsLayer, threadManagementProvided, IdAllocator.layer),
449 Layer.mergeAll(
450 layerContinuationRequests,
451 layerThreadManagementProvided,
452 IdAllocator.layer,
453 ),
446454 ),
447455 )
448456 : Layer.empty;
449 const effectExecutorProvided = EffectWorker.executorLayer.pipe(
457 const layerEffectExecutorProvided = EffectWorker.layerExecutor.pipe(
450458 Layer.provide(
451459 Layer.mergeAll(
452 runFinalizationServiceProvided,
453 checkpointRollbackServiceProvided,
454 providerSessionManagerProvided,
455 providerTurnControlServiceProvided,
456 providerTurnStartServiceProvided,
457 runtimeRequestServiceProvided,
458 threadTitleRegenerationTestLayer,
459 serverSettingsLayer,
460 threadManagementProvided,
460 layerRunFinalizationServiceProvided,
461 layerCheckpointRollbackServiceProvided,
462 layerProviderSessionManagerProvided,
463 layerProviderTurnControlServiceProvided,
464 layerProviderTurnStartServiceProvided,
465 layerRuntimeRequestServiceProvided,
466 layerThreadTitleRegenerationTest,
467 layerServerSettings,
468 layerThreadManagementProvided,
461469 ),
462470 ),
463471 );
464 const effectWorkerProvided = EffectWorker.layer.pipe(
465 Layer.provide(Layer.merge(storesLayer, effectExecutorProvided)),
472 const layerEffectWorkerProvided = EffectWorker.layer.pipe(
473 Layer.provide(Layer.merge(layerStores, layerEffectExecutorProvided)),
466474 );
467 const replayRuntime = Layer.mergeAll(
468 orchestratorProvided,
469 effectWorkerProvided,
470 eventSinkProvided,
471 continuationWorkerProvided,
472 ).pipe(Layer.provide(worktreeRepairDependenciesTestLayer), Layer.provide(NodeServices.layer));
475 const layerReplayRuntime = Layer.mergeAll(
476 layerOrchestratorProvided,
477 layerEffectWorkerProvided,
478 layerEventSinkProvided,
479 layerContinuationWorkerProvided,
480 ).pipe(Layer.provide(ProviderTurnStartServiceTestkit.layer), Layer.provide(NodeServices.layer));
473481
474482 // Build the daemon from the exact worker instance exposed alongside the
475483 // orchestrator. Keeping this acquisition in the replay layer makes the
476484 // outbox lifecycle explicit and prevents test-only command-side draining.
477485 if (options.runEffectWorker === false) {
478 return replayRuntime;
486 return layerReplayRuntime;
479487 }
480488 // Built before the runtime it shares stores with, so recovery commits before
481489 // the effect worker claims anything, as in serverRuntimeStartup.
482 const startupRecovery: Layer.Layer<
490 const layerStartupRecovery: Layer.Layer<
483491 never,
484492 MigrationError | PlatformError.PlatformError | SqlError
485493 > =
......@@ -491,7 +499,12 @@ export function makeOrchestratorV2ReplayLayerWithRegistry<Error>(
491499 ).pipe(
492500 Layer.provide(ProviderRuntimeRecoveryService.layer),
493501 Layer.provide(
494 Layer.mergeAll(storesLayer, eventSinkProvided, IdAllocator.layer, serverSettingsLayer),
502 Layer.mergeAll(
503 layerStores,
504 layerEventSinkProvided,
505 IdAllocator.layer,
506 layerServerSettings,
507 ),
495508 ),
496509 )
497510 : Layer.empty;
......@@ -504,5 +517,5 @@ export function makeOrchestratorV2ReplayLayerWithRegistry<Error>(
504517 yield* EffectWorker.runDaemon.pipe(Effect.forkScoped);
505518 return orchestrator;
506519 }),
507 ).pipe(Layer.provideMerge(replayRuntime), Layer.provide(startupRecovery));
520 ).pipe(Layer.provideMerge(layerReplayRuntime), Layer.provide(layerStartupRecovery));
508521}
apps/server/src/orchestration-v2/testkit/ProviderSwitch.integration.test.ts+72-70
......@@ -32,7 +32,7 @@ import * as Stream from "effect/Stream";
3232import * as Schema from "effect/Schema";
3333import * as SqlClient from "effect/sql/SqlClient";
3434
35import { SqlitePersistenceMemory } from "../../persistence/Layers/Sqlite.ts";
35import * as SqlitePersistence from "../../persistence/Layers/Sqlite.ts";
3636import { CommandPolicyCapabilityUnsupportedError } from "../CommandPolicy.ts";
3737import { ClaudeProviderCapabilitiesV2 } from "../Adapters/ClaudeAdapterV2.ts";
3838import {
......@@ -64,7 +64,7 @@ import {
6464 CURSOR_MODEL_SELECTION,
6565 GROK_MODEL_SELECTION,
6666} from "./fixtures/shared.ts";
67import { makeOrchestratorV2ReplayLayerWithRegistry } from "./ProviderReplayHarness.ts";
67import * as ProviderReplayHarness from "./ProviderReplayHarness.ts";
6868import { checkpointWorkspace } from "./ReplayFixtureWorkspace.ts";
6969
7070const encodeJson = Schema.encodeEffect(Schema.fromJsonString(Schema.Unknown));
......@@ -444,7 +444,7 @@ describe("orchestration v2 provider switching", () => {
444444 ? { options: [{ id: "contextWindow", value: "1m" }] }
445445 : { model: `${CLAUDE_MODEL_SELECTION.model}-large` }),
446446 };
447 const registry = ProviderAdapterRegistry.makeLayer([
447 const layerRegistry = ProviderAdapterRegistry.layerFromAdapters([
448448 makeTestAdapter({
449449 instanceId: CODEX_MODEL_SELECTION.instanceId,
450450 driver: CODEX_DRIVER,
......@@ -880,7 +880,7 @@ describe("orchestration v2 provider switching", () => {
880880 }
881881 }).pipe(
882882 Effect.provide(
883 makeOrchestratorV2ReplayLayerWithRegistry(
883 ProviderReplayHarness.layerWithRegistry(
884884 {
885885 name: `handoff-${scenario}`,
886886 runtimePolicyOverride: {
......@@ -889,7 +889,7 @@ describe("orchestration v2 provider switching", () => {
889889 sandboxPolicy: { type: "readOnly" },
890890 },
891891 },
892 registry,
892 layerRegistry,
893893 ),
894894 ),
895895 );
......@@ -906,7 +906,7 @@ describe("orchestration v2 provider switching", () => {
906906 const injectedHistory = yield* Ref.make<ReadonlyArray<unknown>>([]);
907907 const failOnce = yield* Ref.make(true);
908908 const generation = yield* Ref.make(0);
909 const registry = ProviderAdapterRegistry.makeLayer([
909 const layerRegistry = ProviderAdapterRegistry.layerFromAdapters([
910910 makeTestAdapter({
911911 instanceId: CODEX_MODEL_SELECTION.instanceId,
912912 driver: CODEX_DRIVER,
......@@ -1042,7 +1042,7 @@ describe("orchestration v2 provider switching", () => {
10421042 );
10431043 }).pipe(
10441044 Effect.provide(
1045 makeOrchestratorV2ReplayLayerWithRegistry(
1045 ProviderReplayHarness.layerWithRegistry(
10461046 {
10471047 name: `handoff-retry-${failure}`,
10481048 runtimePolicyOverride: {
......@@ -1051,7 +1051,7 @@ describe("orchestration v2 provider switching", () => {
10511051 sandboxPolicy: { type: "readOnly" },
10521052 },
10531053 },
1054 registry,
1054 layerRegistry,
10551055 ),
10561056 ),
10571057 );
......@@ -1068,7 +1068,7 @@ describe("orchestration v2 provider switching", () => {
10681068 const capturedTurns = yield* Ref.make<ReadonlyArray<CapturedTurn>>([]);
10691069 const failResumeOnce = yield* Ref.make(false);
10701070 const generation = yield* Ref.make(0);
1071 const registry = ProviderAdapterRegistry.makeLayer([
1071 const layerRegistry = ProviderAdapterRegistry.layerFromAdapters([
10721072 makeTestAdapter({
10731073 instanceId: CLAUDE_MODEL_SELECTION.instanceId,
10741074 driver: CLAUDE_DRIVER,
......@@ -1171,7 +1171,7 @@ describe("orchestration v2 provider switching", () => {
11711171 assert.isTrue(turns[2]?.nativeThreadHasTurns);
11721172 }).pipe(
11731173 Effect.provide(
1174 makeOrchestratorV2ReplayLayerWithRegistry(
1174 ProviderReplayHarness.layerWithRegistry(
11751175 {
11761176 name: "resume-fallback-native-turns",
11771177 runtimePolicyOverride: {
......@@ -1180,7 +1180,7 @@ describe("orchestration v2 provider switching", () => {
11801180 sandboxPolicy: { type: "readOnly" },
11811181 },
11821182 },
1183 registry,
1183 layerRegistry,
11841184 ),
11851185 ),
11861186 );
......@@ -1215,7 +1215,7 @@ describe("orchestration v2 provider switching", () => {
12151215 const targetSelection = returning ? CODEX_MODEL_SELECTION : CLAUDE_MODEL_SELECTION;
12161216 const originalPrompt = "Keep the release marker violet and preserve the existing API.";
12171217 const partialResponse = "I checked the API and found the release configuration.";
1218 const registryLayer = ProviderAdapterRegistry.makeLayer(
1218 const layerRegistry = ProviderAdapterRegistry.layerFromAdapters(
12191219 (
12201220 [
12211221 [CODEX_MODEL_SELECTION, CODEX_DRIVER, CodexProviderCapabilitiesV2],
......@@ -1354,7 +1354,7 @@ describe("orchestration v2 provider switching", () => {
13541354 assert.notInclude(back.text, partialResponse);
13551355 }).pipe(
13561356 Effect.provide(
1357 makeOrchestratorV2ReplayLayerWithRegistry(
1357 ProviderReplayHarness.layerWithRegistry(
13581358 {
13591359 name: `handoff-${status}-${queued}-${returning}`,
13601360 runtimePolicyOverride: {
......@@ -1367,7 +1367,7 @@ describe("orchestration v2 provider switching", () => {
13671367 },
13681368 },
13691369 },
1370 registryLayer,
1370 layerRegistry,
13711371 ),
13721372 ),
13731373 );
......@@ -1387,7 +1387,7 @@ describe("orchestration v2 provider switching", () => {
13871387 const capturedTurns = yield* Ref.make<ReadonlyArray<CapturedTurn>>([]);
13881388 const started = yield* Deferred.make<void>();
13891389 const scenarioThreadId = ThreadId.make(`thread:queued-capability:${key}`);
1390 const registryLayer = ProviderAdapterRegistry.makeLayer([
1390 const layerRegistry = ProviderAdapterRegistry.layerFromAdapters([
13911391 makeTestAdapter({
13921392 instanceId: CODEX_MODEL_SELECTION.instanceId,
13931393 driver: CODEX_DRIVER,
......@@ -1477,7 +1477,7 @@ describe("orchestration v2 provider switching", () => {
14771477 assert.deepEqual(projection.thread.modelSelection, CODEX_MODEL_SELECTION);
14781478 }).pipe(
14791479 Effect.provide(
1480 makeOrchestratorV2ReplayLayerWithRegistry(
1480 ProviderReplayHarness.layerWithRegistry(
14811481 {
14821482 name: `queued-capability-${key}`,
14831483 runtimePolicyOverride: {
......@@ -1490,7 +1490,7 @@ describe("orchestration v2 provider switching", () => {
14901490 },
14911491 },
14921492 },
1493 registryLayer,
1493 layerRegistry,
14941494 ),
14951495 ),
14961496 );
......@@ -1505,7 +1505,7 @@ describe("orchestration v2 provider switching", () => {
15051505 const cwd = yield* checkpointWorkspace("queued-steer-provider-switch");
15061506 const capturedTurns = yield* Ref.make<ReadonlyArray<CapturedTurn>>([]);
15071507 const started = yield* Deferred.make<void>();
1508 const registryLayer = ProviderAdapterRegistry.makeLayer([
1508 const layerRegistry = ProviderAdapterRegistry.layerFromAdapters([
15091509 makeTestAdapter({
15101510 instanceId: CODEX_MODEL_SELECTION.instanceId,
15111511 driver: CODEX_DRIVER,
......@@ -1670,7 +1670,7 @@ describe("orchestration v2 provider switching", () => {
16701670 return yield* orchestrator.getThreadProjection(queuedThreadId);
16711671 }).pipe(
16721672 Effect.provide(
1673 makeOrchestratorV2ReplayLayerWithRegistry(
1673 ProviderReplayHarness.layerWithRegistry(
16741674 {
16751675 name: "queued-steer-provider-switch",
16761676 runtimePolicyOverride: {
......@@ -1683,7 +1683,7 @@ describe("orchestration v2 provider switching", () => {
16831683 },
16841684 },
16851685 },
1686 registryLayer,
1686 layerRegistry,
16871687 ),
16881688 ),
16891689 );
......@@ -1744,7 +1744,7 @@ describe("orchestration v2 provider switching", () => {
17441744 capturedTurns,
17451745 }),
17461746 ];
1747 const registryLayer = Layer.succeed(
1747 const layerRegistry = Layer.succeed(
17481748 ProviderAdapterRegistry.ProviderAdapterRegistryV2,
17491749 ProviderAdapterRegistry.ProviderAdapterRegistryV2.of({
17501750 get: (instanceId) => {
......@@ -1861,7 +1861,7 @@ describe("orchestration v2 provider switching", () => {
18611861 return delivered;
18621862 }).pipe(
18631863 Effect.provide(
1864 makeOrchestratorV2ReplayLayerWithRegistry(
1864 ProviderReplayHarness.layerWithRegistry(
18651865 {
18661866 name: "queued-account-switch",
18671867 runtimePolicyOverride: {
......@@ -1874,7 +1874,7 @@ describe("orchestration v2 provider switching", () => {
18741874 },
18751875 },
18761876 },
1877 registryLayer,
1877 layerRegistry,
18781878 ),
18791879 ),
18801880 );
......@@ -1900,7 +1900,7 @@ describe("orchestration v2 provider switching", () => {
19001900 const cwd = yield* checkpointWorkspace("queued-provider-switch");
19011901 const capturedTurns = yield* Ref.make<ReadonlyArray<CapturedTurn>>([]);
19021902 const started = yield* Deferred.make<void>();
1903 const registryLayer = ProviderAdapterRegistry.makeLayer([
1903 const layerRegistry = ProviderAdapterRegistry.layerFromAdapters([
19041904 makeTestAdapter({
19051905 instanceId: ProviderInstanceId.make("codex"),
19061906 driver: CODEX_DRIVER,
......@@ -1924,8 +1924,8 @@ describe("orchestration v2 provider switching", () => {
19241924 }),
19251925 ]);
19261926 const queuedThreadId = ThreadId.make("thread:queued-provider-switch");
1927 const databaseLayer = SqlitePersistenceMemory;
1928 const outboxProvided = EffectOutbox.layer.pipe(Layer.provide(databaseLayer));
1927 const layerDatabase = SqlitePersistence.layerMemory;
1928 const layerOutboxProvided = EffectOutbox.layer.pipe(Layer.provide(layerDatabase));
19291929 const projection = yield* Effect.gen(function* () {
19301930 const orchestrator = yield* Orchestrator.OrchestratorV2;
19311931 const worker = yield* EffectWorker.OrchestrationEffectWorkerV2;
......@@ -2072,7 +2072,7 @@ describe("orchestration v2 provider switching", () => {
20722072 }).pipe(
20732073 Effect.provide(
20742074 Layer.merge(
2075 makeOrchestratorV2ReplayLayerWithRegistry(
2075 ProviderReplayHarness.layerWithRegistry(
20762076 {
20772077 name: "queued-provider-switch",
20782078 runtimePolicyOverride: {
......@@ -2085,10 +2085,10 @@ describe("orchestration v2 provider switching", () => {
20852085 },
20862086 },
20872087 },
2088 registryLayer,
2089 { databaseLayer },
2088 layerRegistry,
2089 { databaseLayer: layerDatabase },
20902090 ),
2091 outboxProvided,
2091 layerOutboxProvided,
20922092 ),
20932093 ),
20942094 );
......@@ -2150,7 +2150,7 @@ describe("orchestration v2 provider switching", () => {
21502150 canConsumeHandoffSummaries: false,
21512151 },
21522152 };
2153 const registryLayer = ProviderAdapterRegistry.makeLayer([
2153 const layerRegistry = ProviderAdapterRegistry.layerFromAdapters([
21542154 makeTestAdapter({
21552155 instanceId: CODEX_MODEL_SELECTION.instanceId,
21562156 driver: CODEX_DRIVER,
......@@ -2299,7 +2299,7 @@ describe("orchestration v2 provider switching", () => {
22992299 return yield* orchestrator.getThreadProjection(rejectedThreadId);
23002300 }).pipe(
23012301 Effect.provide(
2302 makeOrchestratorV2ReplayLayerWithRegistry(
2302 ProviderReplayHarness.layerWithRegistry(
23032303 {
23042304 name: "queued-handoff-rejection",
23052305 runtimePolicyOverride: {
......@@ -2312,7 +2312,7 @@ describe("orchestration v2 provider switching", () => {
23122312 },
23132313 },
23142314 },
2315 registryLayer,
2315 layerRegistry,
23162316 ),
23172317 ),
23182318 );
......@@ -2358,7 +2358,7 @@ describe("orchestration v2 provider switching", () => {
23582358 const capturedTurns = yield* Ref.make<ReadonlyArray<CapturedTurn>>([]);
23592359 const firstTurnStarted = yield* Deferred.make<void>();
23602360 const releaseFirstTurn = yield* Deferred.make<void>();
2361 const registryLayer = ProviderAdapterRegistry.makeLayer([
2361 const layerRegistry = ProviderAdapterRegistry.layerFromAdapters([
23622362 makeTestAdapter({
23632363 instanceId: ProviderInstanceId.make("codex"),
23642364 driver: CODEX_DRIVER,
......@@ -2378,22 +2378,24 @@ describe("orchestration v2 provider switching", () => {
23782378 capturedTurns,
23792379 }),
23802380 ]);
2381 const databaseLayer = SqlitePersistenceMemory;
2382 const eventStoreProvided = EventStore.layer.pipe(Layer.provideMerge(databaseLayer));
2383 const projectionStoreProvided = ProjectionStore.layer.pipe(
2384 Layer.provideMerge(databaseLayer),
2381 const layerDatabase = SqlitePersistence.layerMemory;
2382 const layerEventStoreProvided = EventStore.layer.pipe(Layer.provideMerge(layerDatabase));
2383 const layerProjectionStoreProvided = ProjectionStore.layer.pipe(
2384 Layer.provideMerge(layerDatabase),
23852385 );
2386 const storesProvided = Layer.mergeAll(
2387 databaseLayer,
2388 eventStoreProvided,
2389 projectionStoreProvided,
2386 const layerStoresProvided = Layer.mergeAll(
2387 layerDatabase,
2388 layerEventStoreProvided,
2389 layerProjectionStoreProvided,
23902390 );
2391 const eventSinkProvided = EventSink.layer.pipe(Layer.provide(storesProvided));
2392 const importerProvided = LegacyV1ThreadImporter.layer.pipe(
2393 Layer.provide(Layer.mergeAll(storesProvided, eventSinkProvided)),
2391 const layerEventSinkProvided = EventSink.layer.pipe(Layer.provide(layerStoresProvided));
2392 const layerImporterProvided = LegacyV1ThreadImporter.layer.pipe(
2393 Layer.provide(Layer.mergeAll(layerStoresProvided, layerEventSinkProvided)),
23942394 );
2395 const maintenanceProvided = ProjectionMaintenance.layer.pipe(Layer.provide(storesProvided));
2396 const orchestratorProvided = makeOrchestratorV2ReplayLayerWithRegistry(
2395 const layerMaintenanceProvided = ProjectionMaintenance.layer.pipe(
2396 Layer.provide(layerStoresProvided),
2397 );
2398 const layerOrchestratorProvided = ProviderReplayHarness.layerWithRegistry(
23972399 {
23982400 name: "provider-switch-legacy-import",
23992401 runtimePolicyOverride: {
......@@ -2406,14 +2408,14 @@ describe("orchestration v2 provider switching", () => {
24062408 },
24072409 },
24082410 },
2409 registryLayer,
2410 { databaseLayer },
2411 layerRegistry,
2412 { databaseLayer: layerDatabase },
24112413 );
2412 const testLayer = Layer.mergeAll(
2413 storesProvided,
2414 importerProvided,
2415 maintenanceProvided,
2416 orchestratorProvided,
2414 const layerTest = Layer.mergeAll(
2415 layerStoresProvided,
2416 layerImporterProvided,
2417 layerMaintenanceProvided,
2418 layerOrchestratorProvided,
24172419 );
24182420
24192421 const projection = yield* Effect.gen(function* () {
......@@ -2586,7 +2588,7 @@ describe("orchestration v2 provider switching", () => {
25862588 const rebuilt = yield* orchestrator.getThreadProjection(importedThreadId);
25872589 assert.deepEqual(rebuilt.attempts, beforeRebuild.attempts);
25882590 return rebuilt;
2589 }).pipe(Effect.provide(testLayer));
2591 }).pipe(Effect.provide(layerTest));
25902592
25912593 const turns = yield* Ref.get(capturedTurns);
25922594 assert.deepEqual(
......@@ -2644,7 +2646,7 @@ describe("orchestration v2 provider switching", () => {
26442646 const cwd = yield* checkpointWorkspace("provider-switch");
26452647 const capturedTurns = yield* Ref.make<ReadonlyArray<CapturedTurn>>([]);
26462648 const codexNativeThreadGeneration = yield* Ref.make(0);
2647 const registryLayer = ProviderAdapterRegistry.makeLayer([
2649 const layerRegistry = ProviderAdapterRegistry.layerFromAdapters([
26482650 makeTestAdapter({
26492651 instanceId: ProviderInstanceId.make("codex"),
26502652 driver: CODEX_DRIVER,
......@@ -2753,7 +2755,7 @@ describe("orchestration v2 provider switching", () => {
27532755 return yield* waitForIdle(threadId);
27542756 }).pipe(
27552757 Effect.provide(
2756 makeOrchestratorV2ReplayLayerWithRegistry(
2758 ProviderReplayHarness.layerWithRegistry(
27572759 {
27582760 name: "provider-switch",
27592761 runtimePolicyOverride: {
......@@ -2766,7 +2768,7 @@ describe("orchestration v2 provider switching", () => {
27662768 },
27672769 },
27682770 },
2769 registryLayer,
2771 layerRegistry,
27702772 ),
27712773 ),
27722774 );
......@@ -2856,7 +2858,7 @@ describe("orchestration v2 provider switching", () => {
28562858 const targetPrompt = "What release color did we choose?";
28572859 const cwd = yield* checkpointWorkspace("cross-provider-fork");
28582860 const capturedTurns = yield* Ref.make<ReadonlyArray<CapturedTurn>>([]);
2859 const registryLayer = ProviderAdapterRegistry.makeLayer([
2861 const layerRegistry = ProviderAdapterRegistry.layerFromAdapters([
28602862 makeTestAdapter({
28612863 instanceId: ProviderInstanceId.make("codex"),
28622864 driver: CODEX_DRIVER,
......@@ -2935,7 +2937,7 @@ describe("orchestration v2 provider switching", () => {
29352937 return yield* waitForIdle(targetThreadId);
29362938 }).pipe(
29372939 Effect.provide(
2938 makeOrchestratorV2ReplayLayerWithRegistry(
2940 ProviderReplayHarness.layerWithRegistry(
29392941 {
29402942 name: "cross-provider-fork",
29412943 runtimePolicyOverride: {
......@@ -2948,7 +2950,7 @@ describe("orchestration v2 provider switching", () => {
29482950 },
29492951 },
29502952 },
2951 registryLayer,
2953 layerRegistry,
29522954 ),
29532955 ),
29542956 );
......@@ -2996,7 +2998,7 @@ describe("orchestration v2 provider switching", () => {
29962998 const targetPrompt = "What deployment marker did we choose?";
29972999 const cwd = yield* checkpointWorkspace("cursor-portable-fork");
29983000 const capturedTurns = yield* Ref.make<ReadonlyArray<CapturedTurn>>([]);
2999 const registryLayer = ProviderAdapterRegistry.makeLayer([
3001 const layerRegistry = ProviderAdapterRegistry.layerFromAdapters([
30003002 makeTestAdapter({
30013003 instanceId: ProviderInstanceId.make("cursor"),
30023004 driver: CURSOR_DRIVER,
......@@ -3071,7 +3073,7 @@ describe("orchestration v2 provider switching", () => {
30713073 return yield* waitForIdle(targetThreadId);
30723074 }).pipe(
30733075 Effect.provide(
3074 makeOrchestratorV2ReplayLayerWithRegistry(
3076 ProviderReplayHarness.layerWithRegistry(
30753077 {
30763078 name: "cursor-portable-fork",
30773079 runtimePolicyOverride: {
......@@ -3084,7 +3086,7 @@ describe("orchestration v2 provider switching", () => {
30843086 },
30853087 },
30863088 },
3087 registryLayer,
3089 layerRegistry,
30883090 ),
30893091 ),
30903092 );
......@@ -3131,7 +3133,7 @@ describe("orchestration v2 provider switching", () => {
31313133 const mergePrompt = "Report all three remembered markers.";
31323134 const cwd = yield* checkpointWorkspace("cross-provider-merge");
31333135 const capturedTurns = yield* Ref.make<ReadonlyArray<CapturedTurn>>([]);
3134 const registryLayer = ProviderAdapterRegistry.makeLayer([
3136 const layerRegistry = ProviderAdapterRegistry.layerFromAdapters([
31353137 makeTestAdapter({
31363138 instanceId: ProviderInstanceId.make("codex"),
31373139 driver: CODEX_DRIVER,
......@@ -3283,7 +3285,7 @@ describe("orchestration v2 provider switching", () => {
32833285 return yield* waitForIdle(sourceThreadId);
32843286 }).pipe(
32853287 Effect.provide(
3286 makeOrchestratorV2ReplayLayerWithRegistry(
3288 ProviderReplayHarness.layerWithRegistry(
32873289 {
32883290 name: "cross-provider-merge",
32893291 runtimePolicyOverride: {
......@@ -3296,7 +3298,7 @@ describe("orchestration v2 provider switching", () => {
32963298 },
32973299 },
32983300 },
3299 registryLayer,
3301 layerRegistry,
33003302 ),
33013303 ),
33023304 );
......@@ -3354,7 +3356,7 @@ describe("orchestration v2 provider switching", () => {
33543356 } satisfies ModelSelection;
33553357 const cwd = yield* checkpointWorkspace("custom-codex-instances");
33563358 const capturedTurns = yield* Ref.make<ReadonlyArray<CapturedTurn>>([]);
3357 const registryLayer = ProviderAdapterRegistry.makeLayer([
3359 const layerRegistry = ProviderAdapterRegistry.layerFromAdapters([
33583360 makeTestAdapter({
33593361 instanceId: personalSelection.instanceId,
33603362 driver: CODEX_DRIVER,
......@@ -3413,7 +3415,7 @@ describe("orchestration v2 provider switching", () => {
34133415 ]);
34143416 }).pipe(
34153417 Effect.provide(
3416 makeOrchestratorV2ReplayLayerWithRegistry(
3418 ProviderReplayHarness.layerWithRegistry(
34173419 {
34183420 name: "custom-codex-instances",
34193421 runtimePolicyOverride: {
......@@ -3426,7 +3428,7 @@ describe("orchestration v2 provider switching", () => {
34263428 },
34273429 },
34283430 },
3429 registryLayer,
3431 layerRegistry,
34303432 ),
34313433 ),
34323434 );
apps/server/src/persistence/Layers/OrchestrationCommandReceipts.ts+1-1
......@@ -120,7 +120,7 @@ const makeOrchestrationCommandReceiptRepository = Effect.gen(function* () {
120120 } satisfies OrchestrationCommandReceipts.OrchestrationCommandReceiptRepositoryShape;
121121});
122122
123export const OrchestrationCommandReceiptRepositoryLive = Layer.effect(
123export const layer = Layer.effect(
124124 OrchestrationCommandReceipts.OrchestrationCommandReceiptRepository,
125125 makeOrchestrationCommandReceiptRepository,
126126);
apps/server/src/persistence/Layers/OrchestrationEventStore.sequence.test.ts+7-7
......@@ -11,11 +11,11 @@ import * as SqlClient from "effect/sql/SqlClient";
1111import { runMigrations } from "../Migrations.ts";
1212import * as NodeSqliteClient from "@t3tools/shared/nodeSqliteClient";
1313import * as OrchestrationEventStore from "../Services/OrchestrationEventStore.ts";
14import { OrchestrationEventStoreLive } from "./OrchestrationEventStore.ts";
15import { SqlitePersistenceMemory } from "./Sqlite.ts";
14import * as OrchestrationEventStoreLayer from "./OrchestrationEventStore.ts";
15import * as SqlitePersistence from "./Sqlite.ts";
1616
17const eventStoreLayer = OrchestrationEventStoreLive.pipe(
18 Layer.provideMerge(SqlitePersistenceMemory),
17const layerEventStore = OrchestrationEventStoreLayer.layer.pipe(
18 Layer.provideMerge(SqlitePersistence.layerMemory),
1919);
2020const occurredAt = "2026-09-03T00:00:00.000Z";
2121const encodeJson = Schema.encodeSync(Schema.fromJsonString(Schema.Unknown));
......@@ -112,7 +112,7 @@ it.effect("keeps application and scoped agent high-water marks separate from leg
112112 assert.equal(yield* store.latestApplicationSequence, legacyProject.sequence);
113113 assert.equal(yield* store.latestAgentSequence(), 0);
114114 assert.equal(yield* store.latestAgentSequence(target), 0);
115 }).pipe(Effect.provide(Layer.fresh(eventStoreLayer))),
115 }).pipe(Effect.provide(Layer.fresh(layerEventStore))),
116116);
117117
118118it.effect(
......@@ -206,7 +206,7 @@ it.effect(
206206 [...catchUp.map((row) => row.sequence), liveSequence],
207207 );
208208 assert.isAbove(liveSequence, highWater);
209 }).pipe(Effect.provide(Layer.fresh(eventStoreLayer))),
209 }).pipe(Effect.provide(Layer.fresh(layerEventStore))),
210210);
211211
212212it.effect("uses indexed high-water lookups for populated history without OR scans", () =>
......@@ -267,7 +267,7 @@ it.effect("uses indexed high-water lookups for populated history without OR scan
267267 }
268268 }).pipe(
269269 Effect.provide(
270 OrchestrationEventStoreLive.pipe(
270 OrchestrationEventStoreLayer.layer.pipe(
271271 Layer.provideMerge(NodeSqliteClient.layer({ filename: ":memory:" })),
272272 ),
273273 ),
apps/server/src/persistence/Layers/OrchestrationEventStore.test.ts+8-6
......@@ -27,13 +27,15 @@ import {
2727import { PersistenceDecodeError } from "../Errors.ts";
2828import { toShellApplicationEvent } from "../../orchestration-v2/ShellStream.ts";
2929import * as OrchestrationEventStore from "../Services/OrchestrationEventStore.ts";
30import { OrchestrationEventStoreLive } from "./OrchestrationEventStore.ts";
31import { SqlitePersistenceMemory } from "./Sqlite.ts";
30import * as OrchestrationEventStoreLayer from "./OrchestrationEventStore.ts";
31import * as SqlitePersistence from "./Sqlite.ts";
3232const isPersistenceDecodeError = Schema.is(PersistenceDecodeError);
3333const isLiveStreamBufferError = Schema.is(LiveStreamBufferError);
3434
35const TestLayer = OrchestrationEventStoreLive.pipe(Layer.provideMerge(SqlitePersistenceMemory));
36const layer = it.layer(TestLayer);
35const layerTest = OrchestrationEventStoreLayer.layer.pipe(
36 Layer.provideMerge(SqlitePersistence.layerMemory),
37);
38const layer = it.layer(layerTest);
3739
3840layer("OrchestrationEventStore", (it) => {
3941 it.effect("retains only shell metadata from oversized replay and live application events", () =>
......@@ -464,7 +466,7 @@ it.effect.each(["high-water", "replay"] as const)(
464466 assert.isTrue(isLiveStreamBufferError(result.failure.cause));
465467 }
466468 }),
467 ).pipe(Effect.provide(Layer.fresh(TestLayer))),
469 ).pipe(Effect.provide(Layer.fresh(layerTest))),
468470);
469471
470472it.effect("releases consumed application replay pages", () =>
......@@ -516,5 +518,5 @@ it.effect("releases consumed application replay pages", () =>
516518 ),
517519 );
518520 assert.equal(count, 1501);
519 }).pipe(Effect.provide(TestLayer)),
521 }).pipe(Effect.provide(layerTest)),
520522);
apps/server/src/persistence/Layers/OrchestrationEventStore.ts+1-4
......@@ -606,7 +606,4 @@ const makeEventStore = Effect.gen(function* () {
606606 } satisfies OrchestrationEventStore.OrchestrationEventStoreShape;
607607});
608608
609export const OrchestrationEventStoreLive = Layer.effect(
610 OrchestrationEventStore.OrchestrationEventStore,
611 makeEventStore,
612);
609export const layer = Layer.effect(OrchestrationEventStore.OrchestrationEventStore, makeEventStore);
apps/server/src/persistence/Layers/Sqlite.test.ts+5-8
......@@ -10,11 +10,8 @@ import * as Effect from "effect/Effect";
1010import * as Layer from "effect/Layer";
1111import * as SqlClient from "effect/sql/SqlClient";
1212
13import {
14 SqlitePersistenceMemory,
15 WAL_SIZE_LIMIT_BYTES,
16 makeSqlitePersistenceLive,
17} from "./Sqlite.ts";
13import { WAL_SIZE_LIMIT_BYTES } from "./Sqlite.ts";
14import * as SqlitePersistence from "./Sqlite.ts";
1815
1916const lockHolderSource = `
2017const { DatabaseSync } = require("node:sqlite");
......@@ -56,7 +53,7 @@ it.effect("waits out a concurrent writer instead of failing with SQLITE_BUSY", (
5653 const rows = yield* sql<{ readonly id: number }>`SELECT id FROM busy_probe`;
5754 assert.deepEqual([...rows], [{ id: 1 }]);
5855 }).pipe(
59 Effect.provide(makeSqlitePersistenceLive(dbPath).pipe(Layer.provide(NodeServices.layer))),
56 Effect.provide(SqlitePersistence.layerFromPath(dbPath).pipe(Layer.provide(NodeServices.layer))),
6057 Effect.ensuring(Effect.sync(() => NodeFS.rmSync(tempDir, { recursive: true, force: true }))),
6158 );
6259});
......@@ -82,7 +79,7 @@ it.effect("shrinks the WAL file back to the size limit after a large write", ()
8279 yield* sql`INSERT INTO wal_probe(payload) VALUES (x'00')`;
8380 assert.isAtMost(walFileSize(), WAL_SIZE_LIMIT_BYTES);
8481 }).pipe(
85 Effect.provide(makeSqlitePersistenceLive(dbPath).pipe(Layer.provide(NodeServices.layer))),
82 Effect.provide(SqlitePersistence.layerFromPath(dbPath).pipe(Layer.provide(NodeServices.layer))),
8683 Effect.ensuring(Effect.sync(() => NodeFS.rmSync(tempDir, { recursive: true, force: true }))),
8784 );
8885});
......@@ -92,5 +89,5 @@ it.effect("applies busy_timeout in the shared persistence setup", () =>
9289 const sql = yield* SqlClient.SqlClient;
9390 const rows = yield* sql<{ readonly timeout: number }>`PRAGMA busy_timeout`;
9491 assert.equal(rows[0]?.timeout, 5000);
95 }).pipe(Effect.provide(SqlitePersistenceMemory)),
92 }).pipe(Effect.provide(SqlitePersistence.layerMemory)),
9693);
apps/server/src/persistence/Layers/Sqlite.ts+6-8
......@@ -12,7 +12,7 @@ import * as ServerConfig from "../../config.ts";
1212// Size the -wal file is cut back to on the first commit after a WAL reset.
1313export const WAL_SIZE_LIMIT_BYTES = 32 * 1024 * 1024;
1414
15const setup = Layer.effectDiscard(
15const layerSetup = Layer.effectDiscard(
1616 Effect.gen(function* () {
1717 const sql = yield* SqlClient.SqlClient;
1818 // CLI and server write from separate processes; wait rather than fail with SQLITE_BUSY.
......@@ -26,15 +26,13 @@ const setup = Layer.effectDiscard(
2626 }),
2727);
2828
29export const makeSqlitePersistenceLive = Effect.fn("makeSqlitePersistenceLive")(function* (
30 dbPath: string,
31) {
29export const layerFromPath = Effect.fn("makeSqlitePersistenceLive")(function* (dbPath: string) {
3230 const fs = yield* FileSystem.FileSystem;
3331 const path = yield* Path.Path;
3432 yield* fs.makeDirectory(path.dirname(dbPath), { recursive: true });
3533
3634 return Layer.provideMerge(
37 setup,
35 layerSetup,
3836 NodeSqliteClient.layer({
3937 filename: dbPath,
4038 spanAttributes: {
......@@ -45,8 +43,8 @@ export const makeSqlitePersistenceLive = Effect.fn("makeSqlitePersistenceLive")(
4543 );
4644}, Layer.unwrap);
4745
48export const SqlitePersistenceMemory = Layer.provideMerge(
49 setup,
46export const layerMemory = Layer.provideMerge(
47 layerSetup,
5048 NodeSqliteClient.layer({ filename: ":memory:" }),
5149);
5250
......@@ -54,6 +52,6 @@ export const layerConfig = Layer.unwrap(
5452 Effect.gen(function* () {
5553 const { dbPath } = yield* ServerConfig.ServerConfig;
5654 yield* initializeV2Database(dbPath);
57 return makeSqlitePersistenceLive(dbPath);
55 return layerFromPath(dbPath);
5856 }),
5957);
apps/server/src/persistence/RepositoryErrorCorrelation.test.ts+9-9
......@@ -8,7 +8,7 @@ import * as SqlClient from "effect/sql/SqlClient";
88import * as AuthPairingLinks from "./AuthPairingLinks.ts";
99import * as AuthSessions from "./AuthSessions.ts";
1010import * as PersistenceErrors from "./Errors.ts";
11import { SqlitePersistenceMemory } from "./Layers/Sqlite.ts";
11import * as SqlitePersistence from "./Layers/Sqlite.ts";
1212import * as ProviderSessionRuntime from "./ProviderSessionRuntime.ts";
1313
1414const issuedAt = DateTime.makeUnsafe("2026-06-20T00:00:00.000Z");
......@@ -16,12 +16,12 @@ const expiresAt = DateTime.makeUnsafe("2027-06-20T00:00:00.000Z");
1616const now = DateTime.makeUnsafe("2026-06-21T00:00:00.000Z");
1717const scopes: ReadonlyArray<AuthEnvironmentScope> = ["access:read"];
1818
19const authSessionLayer = AuthSessions.layer.pipe(Layer.provideMerge(SqlitePersistenceMemory));
20const authPairingLinkLayer = AuthPairingLinks.layer.pipe(
21 Layer.provideMerge(SqlitePersistenceMemory),
19const layerAuthSession = AuthSessions.layer.pipe(Layer.provideMerge(SqlitePersistence.layerMemory));
20const layerAuthPairingLink = AuthPairingLinks.layer.pipe(
21 Layer.provideMerge(SqlitePersistence.layerMemory),
2222);
23const providerSessionRuntimeLayer = ProviderSessionRuntime.layer.pipe(
24 Layer.provideMerge(SqlitePersistenceMemory),
23const layerProviderSessionRuntime = ProviderSessionRuntime.layer.pipe(
24 Layer.provideMerge(SqlitePersistence.layerMemory),
2525);
2626
2727describe("persistence error correlation", () => {
......@@ -101,7 +101,7 @@ describe("persistence error correlation", () => {
101101 "SQL error in AuthSessionRepository.revokeAllExcept:query",
102102 );
103103 assert.notInclude(revokeOtherError.message, DateTime.formatIso(now));
104 }).pipe(Effect.provide(authSessionLayer)),
104 }).pipe(Effect.provide(layerAuthSession)),
105105 );
106106
107107 it.effect("correlates pairing-link create and revoke failures by id only", () =>
......@@ -179,7 +179,7 @@ describe("persistence error correlation", () => {
179179 assert.deepStrictEqual(revokeError.correlation, { pairingLinkId: id });
180180 assert.notInclude(revokeError.message, credential);
181181 assert.notInclude(revokeError.message, DateTime.formatIso(now));
182 }).pipe(Effect.provide(authPairingLinkLayer)),
182 }).pipe(Effect.provide(layerAuthPairingLink)),
183183 );
184184
185185 it.effect("skips undecodable provider runtime rows and correlates SQL failures by thread", () =>
......@@ -256,6 +256,6 @@ describe("persistence error correlation", () => {
256256 );
257257 assert.notInclude(sqlFailure.message, runtimePayload);
258258 assert.notInclude(sqlFailure.message, lastSeenAt);
259 }).pipe(Effect.provide(providerSessionRuntimeLayer)),
259 }).pipe(Effect.provide(layerProviderSessionRuntime)),
260260 );
261261});
apps/server/src/persistence/initializeV2Database.test.ts+11-11
......@@ -45,15 +45,15 @@ it.effect(
4545 yield* seed;
4646 const original = NodeFS.readFileSync(sourcePath);
4747 const config = yield* ServerConfig.ServerConfig;
48 const databaseLayer = SqlitePersistence.layerConfig.pipe(
48 const layerDatabase = SqlitePersistence.layerConfig.pipe(
4949 Layer.provide(ServerConfig.layer({ ...config, dbPath: destinationPath })),
5050 );
51 const stores = Layer.mergeAll(EventStore.layer, ProjectionStore.layer).pipe(
52 Layer.provideMerge(databaseLayer),
51 const layerStores = Layer.mergeAll(EventStore.layer, ProjectionStore.layer).pipe(
52 Layer.provideMerge(layerDatabase),
5353 );
54 const sink = EventSink.layer.pipe(Layer.provide(stores));
55 const importer = LegacyV1ThreadImporter.layer.pipe(
56 Layer.provideMerge(Layer.mergeAll(stores, sink)),
54 const layerSink = EventSink.layer.pipe(Layer.provide(layerStores));
55 const layerImporter = LegacyV1ThreadImporter.layer.pipe(
56 Layer.provideMerge(Layer.mergeAll(layerStores, layerSink)),
5757 );
5858 yield* Effect.gen(function* () {
5959 const sql = yield* SqlClient.SqlClient;
......@@ -81,7 +81,7 @@ it.effect(
8181 assert.isNotNull(imported[0]?.transcript_imported_at);
8282 yield* sql`CREATE TABLE v2_work (text TEXT)`;
8383 yield* sql`INSERT INTO v2_work VALUES ('Keep V2 work')`;
84 }).pipe(Effect.provide(importer));
84 }).pipe(Effect.provide(layerImporter));
8585 assert.deepEqual(NodeFS.readFileSync(sourcePath), original);
8686 const v1 = new NodeSqlite.DatabaseSync(sourcePath);
8787 try {
......@@ -105,7 +105,7 @@ it.effect(
105105 const sql = yield* SqlClient.SqlClient;
106106 assert.equal((yield* sql`SELECT text FROM v2_work`)[0]?.text, "Keep V2 work");
107107 assert.equal((yield* sql`SELECT title FROM projection_threads`)[0]?.title, "V1 thread");
108 }).pipe(Effect.provide(databaseLayer));
108 }).pipe(Effect.provide(layerDatabase));
109109 }).pipe(
110110 Effect.provide(
111111 ServerConfig.layerTest(directory, directory).pipe(Layer.provideMerge(NodeServices.layer)),
......@@ -173,14 +173,14 @@ it.effect("starts fresh without V1 and never imports over existing V2 state", ()
173173 const destinationPath = NodePath.join(directory, "userdata", "statev2.sqlite");
174174 return Effect.gen(function* () {
175175 const config = yield* ServerConfig.ServerConfig;
176 const database = SqlitePersistence.layerConfig.pipe(
176 const layerDatabase = SqlitePersistence.layerConfig.pipe(
177177 Layer.provide(ServerConfig.layer({ ...config, dbPath: destinationPath })),
178178 );
179179 yield* Effect.gen(function* () {
180180 const sql = yield* SqlClient.SqlClient;
181181 yield* sql`CREATE TABLE v2_work (text TEXT)`;
182182 yield* sql`INSERT INTO v2_work VALUES ('fresh V2 work')`;
183 }).pipe(Effect.provide(database));
183 }).pipe(Effect.provide(layerDatabase));
184184 const sourcePath = NodePath.join(NodePath.dirname(destinationPath), "state.sqlite");
185185 assert.isFalse(NodeFS.existsSync(sourcePath));
186186 NodeFS.writeFileSync(sourcePath, "This source must never be opened once V2 exists");
......@@ -188,7 +188,7 @@ it.effect("starts fresh without V1 and never imports over existing V2 state", ()
188188 yield* Effect.gen(function* () {
189189 const sql = yield* SqlClient.SqlClient;
190190 assert.equal((yield* sql`SELECT text FROM v2_work`)[0]?.text, "fresh V2 work");
191 }).pipe(Effect.provide(database));
191 }).pipe(Effect.provide(layerDatabase));
192192 }).pipe(
193193 Effect.provide(
194194 ServerConfig.layerTest(directory, directory).pipe(Layer.provideMerge(NodeServices.layer)),
apps/server/src/preview/PortScanner.test.ts+25-25
......@@ -39,13 +39,13 @@ const processProbeFailure: ProcessRunner.ProcessRunner["Service"]["run"] = (inpu
3939 }),
4040 );
4141
42const TestProcessRunner = Layer.succeed(ProcessRunner.ProcessRunner, {
42const layerTestProcessRunner = Layer.succeed(ProcessRunner.ProcessRunner, {
4343 run: processProbeFailure,
4444});
4545
4646let integrationListeningPort: number | null = null;
4747
48const TestIntegrationNet = Layer.succeed(Net.NetService, {
48const layerTestIntegrationNet = Layer.succeed(Net.NetService, {
4949 canListenOnHost: () => Effect.succeed(true),
5050 isPortAvailableOnLoopback: (port) => Effect.sync(() => port !== integrationListeningPort),
5151 hasListenerOnHost: (port) => Effect.sync(() => port === integrationListeningPort),
......@@ -53,7 +53,7 @@ const TestIntegrationNet = Layer.succeed(Net.NetService, {
5353 findAvailablePort: (preferred) => Effect.succeed(preferred),
5454});
5555
56const makeProbeFailureLayer = (
56const layerProbeFailure = (
5757 run: ProcessRunner.ProcessRunner["Service"]["run"],
5858 fetch: typeof globalThis.fetch = globalThis.fetch,
5959) =>
......@@ -74,11 +74,11 @@ const makeProbeFailureLayer = (
7474 ),
7575 );
7676
77const TestPortDiscoveryLive = PortScanner.layer.pipe(
77const layerTestPortDiscovery = PortScanner.layer.pipe(
7878 Layer.provide(
7979 Layer.mergeAll(
80 TestProcessRunner,
81 TestIntegrationNet,
80 layerTestProcessRunner,
81 layerTestIntegrationNet,
8282 Layer.succeed(HostProcessPlatform, "win32"),
8383 FetchHttpClient.layer,
8484 ),
......@@ -87,7 +87,7 @@ const TestPortDiscoveryLive = PortScanner.layer.pipe(
8787
8888const LSOF_TEST_PORT = 43_123;
8989
90const makeLsofScannerLayer = (input: {
90const layerLsofScanner = (input: {
9191 readonly pid: () => number;
9292 readonly fetch: typeof globalThis.fetch;
9393}) =>
......@@ -205,7 +205,7 @@ const commonNonHttpServer = Effect.acquireRelease(
205205 * platform so the tests exercise the TCP-probe fallback without depending on
206206 * `lsof` being installed.
207207 */
208effectIt.layer(TestPortDiscoveryLive)("PortDiscovery integration (TCP probe fallback)", (it) => {
208effectIt.layer(layerTestPortDiscovery)("PortDiscovery integration (TCP probe fallback)", (it) => {
209209 it.effect(
210210 "scan() returns an HTTP server we just opened on a curated dev port",
211211 Effect.fn("PortScannerTest.scanFindsCommonDevServer")(function* () {
......@@ -254,7 +254,7 @@ effectIt.effect("revalidates a successful HTML probe after its cache entry expir
254254 ? Promise.resolve(new Response("hello", { headers: { "content-type": "text/html" } }))
255255 : Promise.reject(new TypeError("not HTTP"));
256256 }) as typeof globalThis.fetch;
257 const layer = makeLsofScannerLayer({ pid: () => 1234, fetch: fetchFn });
257 const layer = layerLsofScanner({ pid: () => 1234, fetch: fetchFn });
258258
259259 return Effect.gen(function* () {
260260 const scanner = yield* PortScanner.PortDiscovery;
......@@ -288,7 +288,7 @@ effectIt.effect("keeps a full configured URL when the discovered server root fai
288288 }),
289289 );
290290 }) as typeof globalThis.fetch;
291 const layer = makeLsofScannerLayer({ pid: () => 1234, fetch: fetchFn });
291 const layer = layerLsofScanner({ pid: () => 1234, fetch: fetchFn });
292292
293293 return Effect.gen(function* () {
294294 const scanner = yield* PortScanner.PortDiscovery;
......@@ -308,7 +308,7 @@ effectIt.effect("probes configured custom ports through a canonical loopback hos
308308 requests.push(String(input));
309309 return Promise.resolve(new Response("docs", { headers: { "content-type": "text/html" } }));
310310 }) as typeof globalThis.fetch;
311 const layer = makeProbeFailureLayer(processProbeFailure, fetchFn);
311 const layer = layerProbeFailure(processProbeFailure, fetchFn);
312312
313313 return Effect.gen(function* () {
314314 const scanner = yield* PortScanner.PortDiscovery;
......@@ -333,7 +333,7 @@ effectIt.effect("preserves explicit loopback hosts and bounds wildcard rewrites"
333333 requests.push(String(input));
334334 return Promise.resolve(new Response("docs", { headers: { "content-type": "text/html" } }));
335335 }) as typeof globalThis.fetch;
336 const layer = makeProbeFailureLayer(processProbeFailure, fetchFn);
336 const layer = layerProbeFailure(processProbeFailure, fetchFn);
337337
338338 return Effect.gen(function* () {
339339 const scanner = yield* PortScanner.PortDiscovery;
......@@ -354,7 +354,7 @@ effectIt.effect("projects configured paths independently for simultaneous subscr
354354 : new Response("not found", { status: 404 }),
355355 );
356356 }) as typeof globalThis.fetch;
357 const layer = makeLsofScannerLayer({ pid: () => 1234, fetch: fetchFn });
357 const layer = layerLsofScanner({ pid: () => 1234, fetch: fetchFn });
358358
359359 return Effect.gen(function* () {
360360 const scanner = yield* PortScanner.PortDiscovery;
......@@ -387,7 +387,7 @@ effectIt.effect(
387387 ? new Response("app", { headers: { "content-type": "text/html" } })
388388 : new Response("not found", { status: 404 }),
389389 )) as typeof globalThis.fetch;
390 const layer = makeLsofScannerLayer({ pid: () => 1234, fetch: fetchFn });
390 const layer = layerLsofScanner({ pid: () => 1234, fetch: fetchFn });
391391
392392 return Effect.gen(function* () {
393393 const scanner = yield* PortScanner.PortDiscovery;
......@@ -420,7 +420,7 @@ effectIt.effect("stops probing a subscriber's configured paths after its scope c
420420 : new Response("not found", { status: 404 }),
421421 );
422422 }) as typeof globalThis.fetch;
423 const layer = makeLsofScannerLayer({ pid: () => 1234, fetch: fetchFn });
423 const layer = layerLsofScanner({ pid: () => 1234, fetch: fetchFn });
424424
425425 return Effect.gen(function* () {
426426 const scanner = yield* PortScanner.PortDiscovery;
......@@ -450,7 +450,7 @@ effectIt.effect("writes no poll span while no client retains the scanner", () =>
450450 return new Tracer.NativeSpan(options);
451451 },
452452 });
453 const layer = makeProbeFailureLayer(processProbeFailure);
453 const layer = layerProbeFailure(processProbeFailure);
454454
455455 return Effect.gen(function* () {
456456 const scanner = yield* PortScanner.PortDiscovery;
......@@ -468,7 +468,7 @@ effectIt.effect("uses the current configured fragment when readiness comes from
468468 requests.push(String(input));
469469 return Promise.resolve(new Response("docs", { headers: { "content-type": "text/html" } }));
470470 }) as typeof globalThis.fetch;
471 const layer = makeLsofScannerLayer({ pid: () => 1234, fetch: fetchFn });
471 const layer = layerLsofScanner({ pid: () => 1234, fetch: fetchFn });
472472 const oldUrl = `http://localhost:${LSOF_TEST_PORT}/docs#old`;
473473 const newUrl = `http://localhost:${LSOF_TEST_PORT}/docs#new`;
474474
......@@ -488,7 +488,7 @@ effectIt.effect("shares a configured root probe with discovered-root classificat
488488 requests.push(String(input));
489489 return Promise.resolve(new Response("app", { headers: { "content-type": "text/html" } }));
490490 }) as typeof globalThis.fetch;
491 const layer = makeLsofScannerLayer({ pid: () => 1234, fetch: fetchFn });
491 const layer = layerLsofScanner({ pid: () => 1234, fetch: fetchFn });
492492
493493 return Effect.gen(function* () {
494494 const scanner = yield* PortScanner.PortDiscovery;
......@@ -517,7 +517,7 @@ effectIt.effect("starts fresh cache entries after the probing batch completes",
517517 requests.push(String(input));
518518 return Promise.resolve(new Response("app", { headers: { "content-type": "text/html" } }));
519519 }) as typeof globalThis.fetch;
520 const layer = makeLsofScannerLayer({ pid: () => 1234, fetch: fetchFn });
520 const layer = layerLsofScanner({ pid: () => 1234, fetch: fetchFn });
521521
522522 yield* Effect.gen(function* () {
523523 const scanner = yield* PortScanner.PortDiscovery;
......@@ -537,7 +537,7 @@ effectIt.effect("caches a failed web probe until its bounded cache entry expires
537537 ? Promise.resolve(new Response("hello", { headers: { "content-type": "text/html" } }))
538538 : Promise.reject(new TypeError("not HTTP"));
539539 }) as typeof globalThis.fetch;
540 const layer = makeLsofScannerLayer({ pid: () => 1234, fetch: fetchFn });
540 const layer = layerLsofScanner({ pid: () => 1234, fetch: fetchFn });
541541
542542 return Effect.gen(function* () {
543543 const scanner = yield* PortScanner.PortDiscovery;
......@@ -562,7 +562,7 @@ effectIt.effect("falls back to HTTPS and does not follow redirects while probing
562562 if (String(input).startsWith("http:")) throw new TypeError("TLS listener");
563563 return new Response(null, { status: 302, headers: { location: "https://example.com" } });
564564 }) as typeof globalThis.fetch;
565 const layer = makeLsofScannerLayer({ pid: () => 1234, fetch: fetchFn });
565 const layer = layerLsofScanner({ pid: () => 1234, fetch: fetchFn });
566566
567567 return Effect.gen(function* () {
568568 const scanner = yield* PortScanner.PortDiscovery;
......@@ -581,7 +581,7 @@ effectIt.effect(
581581 new Response("not found", { status: 404, headers: { "content-type": "text/html" } });
582582 const fetchFn = ((_input: Parameters<typeof globalThis.fetch>[0]) =>
583583 Promise.resolve(makeResponse())) as typeof globalThis.fetch;
584 const layer = makeLsofScannerLayer({ pid: () => pid, fetch: fetchFn });
584 const layer = layerLsofScanner({ pid: () => pid, fetch: fetchFn });
585585
586586 return Effect.gen(function* () {
587587 const scanner = yield* PortScanner.PortDiscovery;
......@@ -639,7 +639,7 @@ effectIt.effect("aborts HTTP and HTTPS probes when they time out", () => {
639639 signal?.addEventListener("abort", onAbort, { once: true });
640640 }
641641 })) as typeof globalThis.fetch;
642 const layer = makeLsofScannerLayer({ pid: () => 1234, fetch: fetchFn });
642 const layer = layerLsofScanner({ pid: () => 1234, fetch: fetchFn });
643643
644644 return Effect.gen(function* () {
645645 const scanner = yield* PortScanner.PortDiscovery;
......@@ -656,7 +656,7 @@ effectIt.effect("aborts HTTP and HTTPS probes when they time out", () => {
656656effectIt.effect("does not swallow process probe defects", () =>
657657 Effect.gen(function* () {
658658 const defect = new Error("unexpected process probe defect");
659 const layer = makeProbeFailureLayer(() => Effect.die(defect));
659 const layer = layerProbeFailure(() => Effect.die(defect));
660660
661661 const exit = yield* Effect.flatMap(PortScanner.PortDiscovery, (scanner) => scanner.scan()).pipe(
662662 Effect.provide(layer),
......@@ -673,7 +673,7 @@ effectIt.effect("does not swallow process probe defects", () =>
673673
674674effectIt.effect("does not swallow process probe interruption", () =>
675675 Effect.gen(function* () {
676 const layer = makeProbeFailureLayer(() => Effect.interrupt);
676 const layer = layerProbeFailure(() => Effect.interrupt);
677677
678678 const exit = yield* Effect.flatMap(PortScanner.PortDiscovery, (scanner) => scanner.scan()).pipe(
679679 Effect.provide(layer),
apps/server/src/process/externalLauncher.test.ts+34-34
......@@ -58,7 +58,7 @@ function makeMockDetachedHandle(input: MockSpawnResult & { readonly onUnref?: ()
5858 });
5959}
6060
61const testLayer = (input: {
61const layerTest = (input: {
6262 readonly platform: NodeJS.Platform;
6363 readonly env?: Record<string, string>;
6464 readonly resolveExecutable?: (command: string) => string | undefined;
......@@ -66,7 +66,7 @@ const testLayer = (input: {
6666 readonly onUnref?: () => void;
6767 readonly spawnResult?: (command: ChildProcess.StandardCommand) => MockSpawnResult | undefined;
6868}) => {
69 const spawnerLayer = Layer.succeed(
69 const layerSpawner = Layer.succeed(
7070 ChildProcessSpawner.ChildProcessSpawner,
7171 ChildProcessSpawner.make((command) =>
7272 Effect.sync(() => {
......@@ -84,7 +84,7 @@ const testLayer = (input: {
8484 );
8585
8686 return Layer.mergeAll(
87 ExternalLauncher.layer.pipe(Layer.provide(Layer.merge(NodeServices.layer, spawnerLayer))),
87 ExternalLauncher.layer.pipe(Layer.provide(Layer.merge(NodeServices.layer, layerSpawner))),
8888 Layer.succeed(HostProcessPlatform, input.platform),
8989 Layer.succeed(
9090 SpawnExecutableResolution,
......@@ -109,7 +109,7 @@ it.effect("launches the default browser through the platform command", () => {
109109 assert.equal(didUnref, true);
110110 }).pipe(
111111 Effect.provide(
112 testLayer({
112 layerTest({
113113 platform: "linux",
114114 onSpawn: (command) => {
115115 spawned = command;
......@@ -138,7 +138,7 @@ it.effect("launches an installed editor with platform-safe arguments", () =>
138138 });
139139 }).pipe(
140140 Effect.provide(
141 testLayer({
141 layerTest({
142142 platform: "win32",
143143 env: { PATH: binDir, PATHEXT: ".COM;.EXE;.BAT;.CMD" },
144144 resolveExecutable: (command) =>
......@@ -183,7 +183,7 @@ for (const platform of ["darwin", "linux"] as const) {
183183 }
184184 }).pipe(
185185 Effect.provide(
186 testLayer({
186 layerTest({
187187 platform,
188188 env: { PATH: binDir },
189189 onSpawn: (command) => spawned.push(command),
......@@ -226,7 +226,7 @@ it.effect("launches Cursor in classic IDE mode through the Windows command shim"
226226 });
227227 }).pipe(
228228 Effect.provide(
229 testLayer({
229 layerTest({
230230 platform: "win32",
231231 env: { PATH: binDir, PATHEXT: ".COM;.EXE;.BAT;.CMD" },
232232 resolveExecutable: (command) =>
......@@ -268,7 +268,7 @@ it.effect.skipIf(windowsHost)("reveals a file in Finder with open -R on macOS",
268268 });
269269 }).pipe(
270270 Effect.provide(
271 testLayer({
271 layerTest({
272272 platform: "darwin",
273273 env: { PATH: binDir },
274274 onSpawn: (command) => {
......@@ -310,7 +310,7 @@ it.effect("reveals a file in File Explorer through PowerShell on Windows", () =>
310310 return yield* launcher.resolveFileManagerRevealKind();
311311 }).pipe(
312312 Effect.provide(
313 testLayer({
313 layerTest({
314314 platform: "win32",
315315 env: { PATH: binDir, PATHEXT: ".COM;.EXE;.BAT;.CMD", SYSTEMROOT: systemRoot },
316316 onSpawn: (command) => {
......@@ -414,7 +414,7 @@ it.effect("does not advertise reveal on Windows when PowerShell is missing", ()
414414 };
415415 }).pipe(
416416 Effect.provide(
417 testLayer({
417 layerTest({
418418 platform: "win32",
419419 env: {
420420 PATH: binDir,
......@@ -458,7 +458,7 @@ it.effect.skipIf(windowsHost)(
458458 return { kind, editors };
459459 }).pipe(
460460 Effect.provide(
461 testLayer({
461 layerTest({
462462 platform: "linux",
463463 env: {
464464 PATH: binDir,
......@@ -506,7 +506,7 @@ it.effect.skipIf(windowsHost)(
506506 };
507507 }).pipe(
508508 Effect.provide(
509 testLayer({
509 layerTest({
510510 platform: "linux",
511511 env: {
512512 PATH: binDir,
......@@ -550,7 +550,7 @@ it.effect.skipIf(windowsHost)(
550550 return revealKind;
551551 }).pipe(
552552 Effect.provide(
553 testLayer({
553 layerTest({
554554 platform: "linux",
555555 env: {
556556 PATH: binDir,
......@@ -606,7 +606,7 @@ it.effect.skipIf(windowsHost)(
606606 return { editors, kind };
607607 }).pipe(
608608 Effect.provide(
609 testLayer({
609 layerTest({
610610 platform: "linux",
611611 env: {
612612 PATH: binDir,
......@@ -656,7 +656,7 @@ it.effect.skipIf(windowsHost)(
656656 });
657657 }).pipe(
658658 Effect.provide(
659 testLayer({
659 layerTest({
660660 platform: "linux",
661661 env: {
662662 PATH: binDir,
......@@ -699,7 +699,7 @@ it.effect.skipIf(windowsHost)("reveals by opening the containing directory on Li
699699 });
700700 }).pipe(
701701 Effect.provide(
702 testLayer({
702 layerTest({
703703 platform: "linux",
704704 env: { PATH: binDir, DISPLAY: ":0" },
705705 onSpawn: (command) => {
......@@ -731,7 +731,7 @@ it.effect.skipIf(windowsHost)(
731731 const editors = yield* Effect.gen(function* () {
732732 const launcher = yield* ExternalLauncher.ExternalLauncher;
733733 return yield* launcher.resolveAvailableEditors();
734 }).pipe(Effect.provide(testLayer({ platform: "linux", env: { PATH: binDir } })));
734 }).pipe(Effect.provide(layerTest({ platform: "linux", env: { PATH: binDir } })));
735735
736736 assert.equal(editors.includes("file-manager"), false);
737737 }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
......@@ -756,7 +756,7 @@ it.effect.skipIf(windowsHost)(
756756 return yield* launcher.resolveAvailableEditors();
757757 }).pipe(
758758 Effect.provide(
759 testLayer({
759 layerTest({
760760 platform: "linux",
761761 env: { PATH: binDir, DISPLAY: ":0" },
762762 onSpawn: (command) => {
......@@ -798,7 +798,7 @@ it.effect.skipIf(windowsHost)(
798798 return yield* launcher.resolveAvailableEditors();
799799 }).pipe(
800800 Effect.provide(
801 testLayer({
801 layerTest({
802802 platform: "linux",
803803 env: { PATH: binDir, DISPLAY: ":0" },
804804 spawnResult: (command) => (command.command === "xdg-mime" ? { stdout: "" } : undefined),
......@@ -828,7 +828,7 @@ it.effect.skipIf(windowsHost)(
828828 return yield* launcher.resolveAvailableEditors();
829829 }).pipe(
830830 Effect.provide(
831 testLayer({
831 layerTest({
832832 platform: "linux",
833833 env: { PATH: binDir, DISPLAY: ":0" },
834834 spawnResult: (command) =>
......@@ -863,7 +863,7 @@ it.live.skipIf(windowsHost)("a stalled handler probe drops only the file manager
863863 return yield* launcher.resolveAvailableEditors();
864864 }).pipe(
865865 Effect.provide(
866 testLayer({
866 layerTest({
867867 platform: "linux",
868868 env: { PATH: binDir, DISPLAY: ":0" },
869869 spawnResult: (command) => (command.command === "xdg-mime" ? { stall: true } : undefined),
......@@ -891,7 +891,7 @@ it.effect.skipIf(windowsHost)(
891891 const launcher = yield* ExternalLauncher.ExternalLauncher;
892892 return yield* launcher.resolveAvailableEditors();
893893 }).pipe(
894 Effect.provide(testLayer({ platform: "linux", env: { PATH: binDir, DISPLAY: ":0" } })),
894 Effect.provide(layerTest({ platform: "linux", env: { PATH: binDir, DISPLAY: ":0" } })),
895895 );
896896
897897 assert.equal(editors.includes("file-manager"), false);
......@@ -911,7 +911,7 @@ it.effect("discovers editors through the service API", () =>
911911 return yield* launcher.resolveAvailableEditors();
912912 }).pipe(
913913 Effect.provide(
914 testLayer({
914 layerTest({
915915 platform: "win32",
916916 env: { PATH: binDir, PATHEXT: ".COM;.EXE;.BAT;.CMD" },
917917 }),
......@@ -1021,7 +1021,7 @@ for (const { platform, installPath, editor, args } of [
10211021 yield* launcher.launchEditor({ editor, cwd: "/workspace with spaces/file.ts:12:4" });
10221022 }).pipe(
10231023 Effect.provide(
1024 testLayer({
1024 layerTest({
10251025 platform,
10261026 env: { HOME: home, LOCALAPPDATA: home, PATH: path.join(home, "empty") },
10271027 onSpawn: (command) => {
......@@ -1064,7 +1064,7 @@ for (const { platform, installPath, onPath } of [
10641064 return yield* launcher.resolveAvailableEditors();
10651065 }).pipe(
10661066 Effect.provide(
1067 testLayer({
1067 layerTest({
10681068 platform,
10691069 env: {
10701070 HOME: home,
......@@ -1091,17 +1091,17 @@ it.effect.skipIf(windowsHost)("ignores unusable app bundles and keeps PATH launc
10911091 const launcher = yield* ExternalLauncher.ExternalLauncher;
10921092 return yield* launcher.resolveAvailableEditors();
10931093 });
1094 const before = yield* discover.pipe(Effect.provide(testLayer({ platform: "darwin", env })));
1094 const before = yield* discover.pipe(Effect.provide(layerTest({ platform: "darwin", env })));
10951095 yield* fs.makeDirectory(executable, { recursive: true });
10961096 assert.deepEqual(
1097 yield* discover.pipe(Effect.provide(testLayer({ platform: "darwin", env }))),
1097 yield* discover.pipe(Effect.provide(layerTest({ platform: "darwin", env }))),
10981098 before,
10991099 );
11001100 yield* fs.remove(executable, { recursive: true });
11011101 yield* fs.writeFileString(executable, "#!/bin/sh\n");
11021102 yield* fs.chmod(executable, 0o644);
11031103 assert.deepEqual(
1104 yield* discover.pipe(Effect.provide(testLayer({ platform: "darwin", env }))),
1104 yield* discover.pipe(Effect.provide(layerTest({ platform: "darwin", env }))),
11051105 before,
11061106 );
11071107 yield* fs.chmod(executable, 0o755);
......@@ -1114,7 +1114,7 @@ it.effect.skipIf(windowsHost)("ignores unusable app bundles and keeps PATH launc
11141114 yield* launcher.launchEditor({ editor: "cursor", cwd: "/workspace" });
11151115 }).pipe(
11161116 Effect.provide(
1117 testLayer({
1117 layerTest({
11181118 platform: "darwin",
11191119 env: { ...env, PATH: `${env.PATH}:/usr/bin` },
11201120 onSpawn: (command) => {
......@@ -1130,7 +1130,7 @@ it.effect.skipIf(windowsHost)("ignores unusable app bundles and keeps PATH launc
11301130it.effect("memoizes editor discovery and refreshes after the cache window", () => {
11311131 let statCalls = 0;
11321132 const fileInfo = { type: "File" } as FileSystem.File.Info;
1133 const launcherLayer = ExternalLauncher.layer.pipe(
1133 const layerLauncher = ExternalLauncher.layer.pipe(
11341134 Layer.provide(
11351135 Layer.mergeAll(
11361136 FileSystem.layerNoop({
......@@ -1171,7 +1171,7 @@ it.effect("memoizes editor discovery and refreshes after the cache window", () =
11711171 }).pipe(
11721172 Effect.provide(
11731173 Layer.mergeAll(
1174 launcherLayer,
1174 layerLauncher,
11751175 Layer.succeed(HostProcessPlatform, "win32"),
11761176 ConfigProvider.layer(
11771177 ConfigProvider.fromEnv({
......@@ -1194,7 +1194,7 @@ it.effect("keeps scanning after the caller is interrupted and shares that scan",
11941194 const fileInfo = { type: "File" } as FileSystem.File.Info;
11951195 const release = Deferred.makeUnsafe<void>();
11961196 let parkedStats = 0;
1197 const launcherLayer = ExternalLauncher.layer.pipe(
1197 const layerLauncher = ExternalLauncher.layer.pipe(
11981198 Layer.provide(
11991199 Layer.mergeAll(
12001200 FileSystem.layerNoop({
......@@ -1236,7 +1236,7 @@ it.effect("keeps scanning after the caller is interrupted and shares that scan",
12361236 }).pipe(
12371237 Effect.provide(
12381238 Layer.mergeAll(
1239 launcherLayer,
1239 layerLauncher,
12401240 Layer.succeed(HostProcessPlatform, "win32"),
12411241 ConfigProvider.layer(
12421242 ConfigProvider.fromEnv({
......@@ -1260,5 +1260,5 @@ it.effect("rejects unknown editors through the service API", () =>
12601260 assert.instanceOf(error, ExternalLauncher.ExternalLauncherUnknownEditorError);
12611261 assert.equal(error.editor, "missing-editor");
12621262 assert.equal(error.message, "Unknown editor: missing-editor");
1263 }).pipe(Effect.provide(testLayer({ platform: "linux", env: { PATH: "" } }))),
1263 }).pipe(Effect.provide(layerTest({ platform: "linux", env: { PATH: "" } }))),
12641264);
apps/server/src/project/AgentSessionImporter.test.ts+2-2
......@@ -59,7 +59,7 @@ it.effect("imports messages once and preserves the provider native resume bindin
5959 },
6060 }),
6161 });
62 const testLayer = AgentSessionImporter.layer.pipe(
62 const layerTest = AgentSessionImporter.layer.pipe(
6363 Layer.provide(
6464 Layer.mergeAll(
6565 Layer.succeed(AgentSessionScanner.AgentSessionScanner, scanner),
......@@ -143,5 +143,5 @@ it.effect("imports messages once and preserves the provider native resume bindin
143143 }),
144144 ]);
145145 expect(recorded).toHaveLength(2);
146 }).pipe(Effect.provide(testLayer));
146 }).pipe(Effect.provide(layerTest));
147147});
apps/server/src/project/AgentSessionScanner.test.ts+10-10
......@@ -33,7 +33,7 @@ const makeProjectShell = (workspaceRoot: string): OrchestrationProjectShell => (
3333 updatedAt: "2026-01-01T00:00:00.000Z",
3434});
3535
36const makeProjectStoreLayer = (importedWorkspaceRoots: ReadonlyArray<string>) =>
36const layerProjectStore = (importedWorkspaceRoots: ReadonlyArray<string>) =>
3737 Layer.mock(ProjectStore.ProjectStoreV2)({
3838 listShells: () =>
3939 Effect.succeed(
......@@ -54,7 +54,7 @@ interface ScannerTestInput {
5454 readonly providerInstances?: ContractServerSettings["providerInstances"];
5555}
5656
57const makeScannerTestLayer = (input: ScannerTestInput) =>
57const layerScannerTest = (input: ScannerTestInput) =>
5858 AgentSessionScanner.layer.pipe(
5959 Layer.provide(
6060 Layer.mergeAll(
......@@ -71,7 +71,7 @@ const makeScannerTestLayer = (input: ScannerTestInput) =>
7171 input.claudeHomePath,
7272 input.configBaseDir ?? { prefix: "t3code-scanner-config-" },
7373 ),
74 makeProjectStoreLayer(input.importedWorkspaceRoots ?? []),
74 layerProjectStore(input.importedWorkspaceRoots ?? []),
7575 ),
7676 ),
7777 );
......@@ -80,7 +80,7 @@ const runScan = (input: ScannerTestInput) =>
8080 Effect.gen(function* () {
8181 const scanner = yield* AgentSessionScanner.AgentSessionScanner;
8282 return yield* scanner.scan;
83 }).pipe(Effect.provide(makeScannerTestLayer(input)));
83 }).pipe(Effect.provide(layerScannerTest(input)));
8484
8585const runRecentThreadOutcomes = (input: ScannerTestInput & { readonly workspaceRoot: string }) =>
8686 Effect.gen(function* () {
......@@ -89,7 +89,7 @@ const runRecentThreadOutcomes = (input: ScannerTestInput & { readonly workspaceR
8989 Stream.runCollect,
9090 Effect.map((outcomes) => Array.from(outcomes)),
9191 );
92 }).pipe(Effect.provide(makeScannerTestLayer(input)));
92 }).pipe(Effect.provide(layerScannerTest(input)));
9393
9494const runRecentThreads = (input: ScannerTestInput & { readonly workspaceRoot: string }) =>
9595 runRecentThreadOutcomes(input).pipe(
......@@ -1705,7 +1705,7 @@ it.layer(NodeServices.layer)("AgentSessionScanner", (it) => {
17051705 expect(scan.candidates[0]?.threadCount).toBe(5);
17061706 return yield* scanner.recentThreads(workspace).pipe(Stream.runCollect);
17071707 }).pipe(
1708 Effect.provide(makeScannerTestLayer({ claudeHomePath, codexHomePath })),
1708 Effect.provide(layerScannerTest({ claudeHomePath, codexHomePath })),
17091709 Effect.provideService(FileSystem.FileSystem, trackedFileSystem),
17101710 );
17111711
......@@ -1855,7 +1855,7 @@ it.layer(NodeServices.layer)("AgentSessionScanner", (it) => {
18551855 } else {
18561856 expect(outcomes).toEqual([{ _tag: "Skipped" }]);
18571857 }
1858 }).pipe(Effect.provide(makeScannerTestLayer({ claudeHomePath, codexHomePath })));
1858 }).pipe(Effect.provide(layerScannerTest({ claudeHomePath, codexHomePath })));
18591859 }),
18601860 );
18611861 }
......@@ -1926,7 +1926,7 @@ it.layer(NodeServices.layer)("AgentSessionScanner", (it) => {
19261926 thread: { providerSessionId: "replaced-session" },
19271927 source: { size: imported.source.size, mtimeMs: imported.source.mtimeMs },
19281928 });
1929 }).pipe(Effect.provide(makeScannerTestLayer({ claudeHomePath, codexHomePath })));
1929 }).pipe(Effect.provide(layerScannerTest({ claudeHomePath, codexHomePath })));
19301930 }),
19311931 );
19321932
......@@ -2350,7 +2350,7 @@ it.layer(NodeServices.layer)("AgentSessionScanner", (it) => {
23502350 Effect.map((items) => Array.from(items)),
23512351 );
23522352 }).pipe(
2353 Effect.provide(makeScannerTestLayer({ claudeHomePath, codexHomePath })),
2353 Effect.provide(layerScannerTest({ claudeHomePath, codexHomePath })),
23542354 Effect.provideService(FileSystem.FileSystem, simulatedFileSystem),
23552355 );
23562356
......@@ -2573,7 +2573,7 @@ it.layer(NodeServices.layer)("AgentSessionScanner", (it) => {
25732573 expect(scan.truncated).toBe(true);
25742574 return yield* scanner.recentThreads(recentWorkspace).pipe(Stream.runCollect);
25752575 }).pipe(
2576 Effect.provide(makeScannerTestLayer(input)),
2576 Effect.provide(layerScannerTest(input)),
25772577 Effect.provideService(FileSystem.FileSystem, simulatedFileSystem),
25782578 );
25792579
apps/server/src/project/ManagedProjectFolders.test.ts+13-13
......@@ -12,8 +12,8 @@ import * as Scope from "effect/Scope";
1212
1313import * as ServerConfig from "../config.ts";
1414import * as GitWorkflow from "../git/GitWorkflowService.ts";
15import { ProjectServiceLayerLive } from "../orchestration-v2/runtimeLayer.ts";
16import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts";
15import * as RuntimeLayer from "../orchestration-v2/runtimeLayer.ts";
16import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts";
1717import * as ProcessRunner from "../processRunner.ts";
1818import * as GitVcsDriver from "../vcs/GitVcsDriver.ts";
1919import * as VcsDriverRegistry from "../vcs/VcsDriverRegistry.ts";
......@@ -27,7 +27,7 @@ import * as ManagedProjectFolders from "./ManagedProjectFolders.ts";
2727
2828// Real repository detection: the service only asks the Git workflow whether
2929// the data dir is inside a checkout.
30const gitWorkflowLayer = Layer.unwrap(
30const layerGitWorkflow = Layer.unwrap(
3131 Effect.gen(function* () {
3232 const registry = yield* VcsDriverRegistry.VcsDriverRegistry;
3333 return Layer.mock(GitWorkflow.GitWorkflowService)({
......@@ -40,7 +40,7 @@ const gitWorkflowLayer = Layer.unwrap(
4040 }),
4141).pipe(Layer.provide(VcsDriverRegistry.layer.pipe(Layer.provide(VcsProcess.layer))));
4242
43const enrichmentLayer = ProjectEnrichmentService.layer.pipe(
43const layerEnrichment = ProjectEnrichmentService.layer.pipe(
4444 Layer.provide(
4545 Layer.mergeAll(
4646 Layer.succeed(RepositoryIdentityResolver.RepositoryIdentityResolver, {
......@@ -53,7 +53,7 @@ const enrichmentLayer = ProjectEnrichmentService.layer.pipe(
5353 ),
5454);
5555
56const realGitLayer = GitVcsDriver.layer.pipe(Layer.provide(VcsProcess.layer));
56const layerRealGit = GitVcsDriver.layer.pipe(Layer.provide(VcsProcess.layer));
5757
5858interface HarnessOptions {
5959 /** A git driver for failures real git cannot produce on demand. */
......@@ -68,7 +68,7 @@ interface HarnessOptions {
6868 * The service over a real ProjectService and real git, with its data dir at
6969 * `baseDir`.
7070 */
71const makeLayer = (baseDir: string, options?: HarnessOptions) =>
71const layer = (baseDir: string, options?: HarnessOptions) =>
7272 ManagedProjectFolders.layer.pipe(
7373 Layer.provide(
7474 options?.projects === undefined
......@@ -78,12 +78,12 @@ const makeLayer = (baseDir: string, options?: HarnessOptions) =>
7878 ProjectService.ProjectService.pipe(Effect.map(options.projects)),
7979 ),
8080 ),
81 Layer.provideMerge(ProjectServiceLayerLive),
82 Layer.provideMerge(enrichmentLayer),
81 Layer.provideMerge(RuntimeLayer.layerProjectService),
82 Layer.provideMerge(layerEnrichment),
8383 Layer.provideMerge(WorkspacePaths.layer),
84 Layer.provideMerge(gitWorkflowLayer),
85 Layer.provideMerge(options?.git ?? realGitLayer),
86 Layer.provideMerge(SqlitePersistenceMemory),
84 Layer.provideMerge(layerGitWorkflow),
85 Layer.provideMerge(options?.git ?? layerRealGit),
86 Layer.provideMerge(SqlitePersistence.layerMemory),
8787 Layer.provideMerge(ServerConfig.layerTest(baseDir, baseDir)),
8888 Layer.provideMerge(NodeServices.layer),
8989 );
......@@ -105,7 +105,7 @@ const withScratch = <A, E>(
105105 Effect.gen(function* () {
106106 const fileSystem = yield* FileSystem.FileSystem;
107107 const baseDir = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-managed-folders-" });
108 return yield* body({ baseDir }).pipe(Effect.provide(makeLayer(baseDir, options)));
108 return yield* body({ baseDir }).pipe(Effect.provide(layer(baseDir, options)));
109109 }).pipe(Effect.scoped, Effect.provide(NodeServices.layer));
110110
111111const git = (cwd: string, args: ReadonlyArray<string>) =>
......@@ -141,7 +141,7 @@ it.effect("offers nothing when the data dir sits inside a Git checkout", () =>
141141 assert.isTrue(Option.isNone(yield* scratch.scratchRoot));
142142 const failure = yield* Effect.flip(scratch.ensureScratchProject);
143143 assert.equal(failure._tag, "ScratchUnavailableError");
144 }).pipe(Effect.provide(makeLayer(baseDir)));
144 }).pipe(Effect.provide(layer(baseDir)));
145145 }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
146146);
147147
apps/server/src/project/ProjectEnrichmentService.test.ts+13-13
......@@ -33,7 +33,7 @@ const waitForAvailable = Effect.fn("ProjectEnrichmentServiceTest.waitForAvailabl
3333 return yield* Effect.die(`Project metadata for ${workspaceRoot} was not resolved in time.`);
3434});
3535
36const makeLayer = (
36const layer = (
3737 metadataLayer: Layer.Layer<
3838 | ProjectFaviconResolver.ProjectFaviconResolver
3939 | RepositoryIdentityResolver.RepositoryIdentityResolver
......@@ -46,7 +46,7 @@ const makeLayer = (
4646
4747it.effect("preserves either enrichment field when the other resolver fails", () =>
4848 Effect.gen(function* () {
49 const metadataLayer = Layer.merge(
49 const layerMetadata = Layer.merge(
5050 Layer.succeed(RepositoryIdentityResolver.RepositoryIdentityResolver, {
5151 resolve: (workspaceRoot) =>
5252 workspaceRoot === "/repo-fails"
......@@ -90,14 +90,14 @@ it.effect("preserves either enrichment field when the other resolver fails", ()
9090 );
9191 assert.isNull(repositoryFailure.repositoryIdentity);
9292 assert.equal(repositoryFailure.faviconPath, "/repo-fails/favicon.svg");
93 }).pipe(Effect.provide(makeLayer(metadataLayer)));
93 }).pipe(Effect.provide(layer(layerMetadata)));
9494 }),
9595);
9696
9797it.effect("publishes repository completion while favicon enrichment is still pending", () =>
9898 Effect.gen(function* () {
9999 const releaseFavicon = yield* Deferred.make<void>();
100 const metadataLayer = Layer.merge(
100 const layerMetadata = Layer.merge(
101101 Layer.succeed(RepositoryIdentityResolver.RepositoryIdentityResolver, {
102102 resolve: (workspaceRoot) => Effect.succeed(identity(workspaceRoot)),
103103 }),
......@@ -117,7 +117,7 @@ it.effect("publishes repository completion while favicon enrichment is still pen
117117 assert.isTrue(change.repositoryIdentityResolved);
118118 assert.equal(change.enrichment.repositoryIdentity?.canonicalKey, "example.test/v1/completed");
119119 assert.isNull(change.enrichment.faviconPath);
120 }).pipe(Effect.provide(makeLayer(metadataLayer)));
120 }).pipe(Effect.provide(layer(layerMetadata)));
121121 }),
122122);
123123
......@@ -125,7 +125,7 @@ it.effect("keeps repository workers available when every favicon worker is hung"
125125 Effect.gen(function* () {
126126 const faviconWorkersStarted = yield* Deferred.make<void>();
127127 const faviconStarts = yield* Ref.make(0);
128 const metadataLayer = Layer.merge(
128 const layerMetadata = Layer.merge(
129129 Layer.succeed(RepositoryIdentityResolver.RepositoryIdentityResolver, {
130130 resolve: (workspaceRoot) => Effect.succeed(identity(workspaceRoot)),
131131 }),
......@@ -158,7 +158,7 @@ it.effect("keeps repository workers available when every favicon worker is hung"
158158 assert.equal(yield* Ref.get(faviconStarts), 2);
159159 }).pipe(
160160 Effect.provide(
161 makeLayer(metadataLayer, {
161 layer(layerMetadata, {
162162 cacheCapacity: 8,
163163 maxPending: 4,
164164 concurrency: 2,
......@@ -172,7 +172,7 @@ it.effect("getAvailable returns immediately while repository identity is still u
172172 Effect.gen(function* () {
173173 const repositoryStarted = yield* Deferred.make<void>();
174174 const releaseRepository = yield* Deferred.make<void>();
175 const metadataLayer = Layer.merge(
175 const layerMetadata = Layer.merge(
176176 Layer.succeed(RepositoryIdentityResolver.RepositoryIdentityResolver, {
177177 resolve: (workspaceRoot) =>
178178 Deferred.succeed(repositoryStarted, undefined).pipe(
......@@ -205,7 +205,7 @@ it.effect("getAvailable returns immediately while repository identity is still u
205205 );
206206 assert.equal(resolved.repositoryIdentity?.canonicalKey, "example.test/v1/pending-identity");
207207 assert.isTrue(resolved.repositoryIdentityResolved);
208 }).pipe(Effect.provide(makeLayer(metadataLayer)));
208 }).pipe(Effect.provide(layer(layerMetadata)));
209209 }),
210210);
211211
......@@ -215,7 +215,7 @@ it.effect(
215215 Effect.gen(function* () {
216216 const repositoryStarted = yield* Deferred.make<void>();
217217 const releaseRepository = yield* Deferred.make<void>();
218 const metadataLayer = Layer.merge(
218 const layerMetadata = Layer.merge(
219219 Layer.succeed(RepositoryIdentityResolver.RepositoryIdentityResolver, {
220220 resolve: (workspaceRoot) => {
221221 if (workspaceRoot === "/no-remote") {
......@@ -268,7 +268,7 @@ it.effect(
268268 const failed = yield* service.peek("/fails");
269269 assert.isNull(failed.repositoryIdentity);
270270 assert.isFalse(failed.repositoryIdentityResolved);
271 }).pipe(Effect.provide(makeLayer(metadataLayer)));
271 }).pipe(Effect.provide(layer(layerMetadata)));
272272 }),
273273);
274274
......@@ -278,7 +278,7 @@ it.effect("deduplicates requests, bounds pending work, and reloads invalidated r
278278 const releaseFirst = yield* Deferred.make<void>();
279279 const repositoryCalls = yield* Ref.make<ReadonlyArray<string>>([]);
280280 const version = yield* Ref.make(1);
281 const metadataLayer = Layer.merge(
281 const layerMetadata = Layer.merge(
282282 Layer.succeed(RepositoryIdentityResolver.RepositoryIdentityResolver, {
283283 resolve: (workspaceRoot) =>
284284 Effect.gen(function* () {
......@@ -332,7 +332,7 @@ it.effect("deduplicates requests, bounds pending work, and reloads invalidated r
332332 ]);
333333 }).pipe(
334334 Effect.provide(
335 makeLayer(metadataLayer, {
335 layer(layerMetadata, {
336336 cacheCapacity: 8,
337337 maxPending: 2,
338338 concurrency: 1,
apps/server/src/project/ProjectFaviconResolver.test.ts+2-2
......@@ -12,7 +12,7 @@ import * as WorkspacePaths from "../workspace/WorkspacePaths.ts";
1212import * as ProjectFaviconResolver from "./ProjectFaviconResolver.ts";
1313import * as T3ProjectFileLoader from "./T3ProjectFileLoader.ts";
1414
15const TestLayer = Layer.empty.pipe(
15const layerTest = Layer.empty.pipe(
1616 Layer.provideMerge(
1717 ProjectFaviconResolver.layer.pipe(
1818 Layer.provide(WorkspacePaths.layer),
......@@ -49,7 +49,7 @@ const makeResolverWithFileSystem = (fileSystem: FileSystem.FileSystem) =>
4949 Effect.provideService(FileSystem.FileSystem, fileSystem),
5050 );
5151
52it.layer(TestLayer)("ProjectFaviconResolverLive", (it) => {
52it.layer(layerTest)("ProjectFaviconResolverLive", (it) => {
5353 describe("resolvePath", () => {
5454 it.effect("serves repeated resolves from cache instead of re-walking candidates", () =>
5555 Effect.gen(function* () {
apps/server/src/project/ProjectService.deletion.test.ts+14-14
......@@ -27,19 +27,19 @@ import * as ProjectionStore from "../orchestration-v2/ProjectionStore.ts";
2727import * as ProjectStore from "../orchestration-v2/ProjectStore.ts";
2828import * as ThreadCommandExecutor from "../orchestration-v2/ThreadCommandExecutor.ts";
2929import { planThreadDeletion } from "../orchestration-v2/ThreadDeletion.ts";
30import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts";
30import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts";
3131import * as WorkspacePaths from "../workspace/WorkspacePaths.ts";
3232import * as ProjectEnrichmentService from "./ProjectEnrichmentService.ts";
3333import * as ProjectFaviconResolver from "./ProjectFaviconResolver.ts";
3434import * as ProjectService from "./ProjectService.ts";
3535import * as RepositoryIdentityResolver from "./RepositoryIdentityResolver.ts";
3636
37const eventPersistenceLayer = EventSink.layer.pipe(
37const layerEventPersistence = EventSink.layer.pipe(
3838 Layer.provideMerge(Layer.merge(EventStore.layer, ProjectionStore.layer)),
3939);
40const servicesLayer = Layer.mergeAll(
41 LegacyV1ThreadImporter.layer.pipe(Layer.provideMerge(eventPersistenceLayer)),
42 ProjectionMaintenance.layer.pipe(Layer.provide(eventPersistenceLayer)),
40const layerServices = Layer.mergeAll(
41 LegacyV1ThreadImporter.layer.pipe(Layer.provideMerge(layerEventPersistence)),
42 ProjectionMaintenance.layer.pipe(Layer.provide(layerEventPersistence)),
4343 ProjectStore.layer,
4444 IdAllocator.layer,
4545 ThreadCommandExecutor.layer,
......@@ -64,7 +64,7 @@ const servicesLayer = Layer.mergeAll(
6464 ),
6565 ),
6666);
67const databaseLayer = SqlitePersistenceMemory.pipe(
67const layerDatabase = SqlitePersistence.layerMemory.pipe(
6868 Layer.provideMerge(ServerConfig.layerTest(process.cwd(), { prefix: "project-deletion-test-" })),
6969 Layer.provideMerge(NodeServices.layer),
7070);
......@@ -239,8 +239,8 @@ it.effect("retries a partial project deletion without repeating child events or
239239 ],
240240 );
241241 }
242 }).pipe(Effect.provide(servicesLayer));
243 }).pipe(Effect.provide(databaseLayer)),
242 }).pipe(Effect.provide(layerServices));
243 }).pipe(Effect.provide(layerDatabase)),
244244);
245245
246246it.effect(
......@@ -354,8 +354,8 @@ it.effect(
354354 type: "attachment.cleanup",
355355 attachmentIds: ["legacy_screenshot"],
356356 });
357 }).pipe(Effect.provide(servicesLayer));
358 }).pipe(Effect.provide(databaseLayer)),
357 }).pipe(Effect.provide(layerServices));
358 }).pipe(Effect.provide(layerDatabase)),
359359);
360360
361361it.effect("rejects a child deletion command ID already accepted for an unrelated thread", () =>
......@@ -404,8 +404,8 @@ it.effect("rejects a child deletion command ID already accepted for an unrelated
404404 WHERE thread_id = ${threadId}
405405 `;
406406 assert.deepEqual(cleanup, []);
407 }).pipe(Effect.provide(servicesLayer));
408 }).pipe(Effect.provide(databaseLayer)),
407 }).pipe(Effect.provide(layerServices));
408 }).pipe(Effect.provide(layerDatabase)),
409409);
410410
411411it.effect("deletes a project without force once its imported threads were deleted in V2", () =>
......@@ -481,6 +481,6 @@ it.effect("deletes a project without force once its imported threads were delete
481481 });
482482 assert.isNotNull(deleted.deletedAt);
483483 assert.isTrue(Option.isNone(yield* service.getById(projectId)));
484 }).pipe(Effect.provide(servicesLayer));
485 }).pipe(Effect.provide(databaseLayer)),
484 }).pipe(Effect.provide(layerServices));
485 }).pipe(Effect.provide(layerDatabase)),
486486);
apps/server/src/project/ProjectService.test.ts+24-29
......@@ -16,25 +16,22 @@ import * as IdAllocator from "../orchestration-v2/IdAllocator.ts";
1616import * as LegacyV1ThreadImporter from "../orchestration-v2/legacy/LegacyV1ThreadImporter.ts";
1717import * as ProjectionStore from "../orchestration-v2/ProjectionStore.ts";
1818import * as ProjectStore from "../orchestration-v2/ProjectStore.ts";
19import {
20 OrchestrationV2EventSinkLayerLive,
21 ProjectServiceLayerLive,
22} from "../orchestration-v2/runtimeLayer.ts";
19import * as RuntimeLayer from "../orchestration-v2/runtimeLayer.ts";
2320import * as ThreadCommandExecutor from "../orchestration-v2/ThreadCommandExecutor.ts";
24import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts";
21import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts";
2522import * as WorkspacePaths from "../workspace/WorkspacePaths.ts";
2623import * as ProjectEnrichmentService from "./ProjectEnrichmentService.ts";
2724import * as ProjectFaviconResolver from "./ProjectFaviconResolver.ts";
2825import * as ProjectService from "./ProjectService.ts";
2926import * as RepositoryIdentityResolver from "./RepositoryIdentityResolver.ts";
3027
31const workspacePathsLayer = Layer.succeed(WorkspacePaths.WorkspacePaths, {
28const layerWorkspacePaths = Layer.succeed(WorkspacePaths.WorkspacePaths, {
3229 normalizeWorkspaceRoot: (workspaceRoot) => Effect.succeed(workspaceRoot.replace(/\/$/, "")),
3330 resolveRelativePathWithinRoot: ({ workspaceRoot, relativePath }) =>
3431 Effect.succeed({ absolutePath: `${workspaceRoot}/${relativePath}`, relativePath }),
3532});
3633
37const metadataLayer = Layer.merge(
34const layerMetadata = Layer.merge(
3835 Layer.succeed(RepositoryIdentityResolver.RepositoryIdentityResolver, {
3936 resolve: (workspaceRoot) =>
4037 Effect.succeed({
......@@ -52,38 +49,36 @@ const metadataLayer = Layer.merge(
5249 }),
5350);
5451
55const makeTestLayer = (
52const layerTestFor = (
5653 projectMetadataLayer: Layer.Layer<
5754 | ProjectFaviconResolver.ProjectFaviconResolver
5855 | RepositoryIdentityResolver.RepositoryIdentityResolver
5956 >,
6057) =>
61 ProjectServiceLayerLive.pipe(
58 RuntimeLayer.layerProjectService.pipe(
6259 Layer.provideMerge(ProjectEnrichmentService.layer),
63 Layer.provideMerge(workspacePathsLayer),
60 Layer.provideMerge(layerWorkspacePaths),
6461 Layer.provideMerge(projectMetadataLayer),
65 Layer.provideMerge(SqlitePersistenceMemory),
62 Layer.provideMerge(SqlitePersistence.layerMemory),
6663 Layer.provide(ServerConfig.layerTest(process.cwd(), { prefix: "project-service-test-" })),
6764 Layer.provide(NodeServices.layer),
6865 );
6966
70const TestLayer = makeTestLayer(metadataLayer);
67const layerTest = layerTestFor(layerMetadata);
7168
7269/** Every dependency of ProjectService.make, so a test can swap one of them. */
73const ProjectServiceDependenciesLayer = Layer.mergeAll(
74 OrchestrationV2EventSinkLayerLive,
70const layerProjectServiceDependencies = Layer.mergeAll(
71 RuntimeLayer.layerEventSink,
7572 ProjectStore.layer,
7673 ProjectionStore.layer,
7774 IdAllocator.layer,
7875 ThreadCommandExecutor.layer,
7976).pipe(
80 Layer.provideMerge(
81 LegacyV1ThreadImporter.layer.pipe(Layer.provide(OrchestrationV2EventSinkLayerLive)),
82 ),
77 Layer.provideMerge(LegacyV1ThreadImporter.layer.pipe(Layer.provide(RuntimeLayer.layerEventSink))),
8378 Layer.provideMerge(ProjectEnrichmentService.layer),
84 Layer.provideMerge(workspacePathsLayer),
85 Layer.provideMerge(metadataLayer),
86 Layer.provideMerge(SqlitePersistenceMemory),
79 Layer.provideMerge(layerWorkspacePaths),
80 Layer.provideMerge(layerMetadata),
81 Layer.provideMerge(SqlitePersistence.layerMemory),
8782 Layer.provide(ServerConfig.layerTest(process.cwd(), { prefix: "project-service-race-" })),
8883 Layer.provide(NodeServices.layer),
8984);
......@@ -101,7 +96,7 @@ const waitForProject = Effect.fn("ProjectServiceTest.waitForProject")(function*
10196 return yield* Effect.die(`Project ${projectId} was not enriched in time.`);
10297});
10398
104it.layer(TestLayer)("ProjectService", (it) => {
99it.layer(layerTest)("ProjectService", (it) => {
105100 it.effect("creates, updates, resolves, snapshots, and soft-deletes projects", () =>
106101 Effect.gen(function* () {
107102 const service = yield* ProjectService.ProjectService;
......@@ -500,7 +495,7 @@ it.effect(
500495 const repositoryCalls = yield* Ref.make(0);
501496 const faviconCalls = yield* Ref.make(0);
502497
503 const slowMetadataLayer = Layer.merge(
498 const layerSlowMetadata = Layer.merge(
504499 Layer.succeed(RepositoryIdentityResolver.RepositoryIdentityResolver, {
505500 resolve: (workspaceRoot) =>
506501 Effect.gen(function* () {
......@@ -592,13 +587,13 @@ it.effect(
592587 assert.equal(secondSnapshot.projects[0]?.faviconPath, "/work/slow-enrichment/favicon.svg");
593588 assert.equal(yield* Ref.get(repositoryCalls), 1);
594589 assert.equal(yield* Ref.get(faviconCalls), 1);
595 }).pipe(Effect.provide(makeTestLayer(slowMetadataLayer)));
590 }).pipe(Effect.provide(layerTestFor(layerSlowMetadata)));
596591 }),
597592);
598593
599594it.effect("keeps project snapshots available when optional metadata enrichment fails", () =>
600595 Effect.gen(function* () {
601 const failingMetadataLayer = Layer.merge(
596 const layerFailingMetadata = Layer.merge(
602597 Layer.succeed(RepositoryIdentityResolver.RepositoryIdentityResolver, {
603598 resolve: () => Effect.succeed(null),
604599 }),
......@@ -630,14 +625,14 @@ it.effect("keeps project snapshots available when optional metadata enrichment f
630625 assert.equal(snapshot.projects[0]?.title, "Still visible");
631626 assert.isNull(snapshot.projects[0]?.repositoryIdentity ?? null);
632627 assert.isNull(snapshot.projects[0]?.faviconPath ?? null);
633 }).pipe(Effect.provide(makeTestLayer(failingMetadataLayer)));
628 }).pipe(Effect.provide(layerTestFor(layerFailingMetadata)));
634629 }),
635630);
636631
637632it.effect("invalidates workspace-derived metadata when a project moves", () =>
638633 Effect.gen(function* () {
639634 const metadataVersion = yield* Ref.make(1);
640 const versionedMetadataLayer = Layer.merge(
635 const layerVersionedMetadata = Layer.merge(
641636 Layer.succeed(RepositoryIdentityResolver.RepositoryIdentityResolver, {
642637 resolve: (workspaceRoot) =>
643638 Ref.get(metadataVersion).pipe(
......@@ -700,7 +695,7 @@ it.effect("invalidates workspace-derived metadata when a project moves", () =>
700695 )).faviconPath,
701696 "/work/original/favicon-v3.svg",
702697 );
703 }).pipe(Effect.provide(makeTestLayer(versionedMetadataLayer)));
698 }).pipe(Effect.provide(layerTestFor(layerVersionedMetadata)));
704699 }),
705700);
706701
......@@ -741,7 +736,7 @@ it.effect("serializes two projects claiming the same workspace root", () =>
741736 yield* Deferred.succeed(releaseFirst, undefined);
742737 assert.equal((yield* Fiber.join(first)).id, "project:race:first");
743738 assert.equal((yield* Fiber.join(second))._tag, "ProjectConflictError");
744 }).pipe(Effect.provide(ProjectServiceDependenciesLayer)),
739 }).pipe(Effect.provide(layerProjectServiceDependencies)),
745740);
746741
747742it.effect("rejects an update that waited on the lock while its project was deleted", () =>
......@@ -801,5 +796,5 @@ it.effect("rejects an update that waited on the lock while its project was delet
801796 Option.getOrThrow(yield* service.getById(projectId, { includeDeleted: true })).title,
802797 "Update race",
803798 );
804 }).pipe(Effect.provide(ProjectServiceDependenciesLayer)),
799 }).pipe(Effect.provide(layerProjectServiceDependencies)),
805800);
apps/server/src/project/RepositoryIdentityResolver.test.ts+11-11
......@@ -26,7 +26,7 @@ const git = (cwd: string, args: ReadonlyArray<string>) =>
2626 });
2727 }).pipe(Effect.provide(ProcessRunner.layer));
2828
29const makeRepositoryIdentityResolverTestLayer = (options: {
29const layerRepositoryIdentityResolverTest = (options: {
3030 readonly positiveCacheTtl?: Duration.Input;
3131 readonly negativeCacheTtl?: Duration.Input;
3232}) =>
......@@ -45,7 +45,7 @@ it.layer(NodeServices.layer)("RepositoryIdentityResolverLive", (it) => {
4545 let remoteUrl = "git@github.com:T3Tools/t3code.git";
4646 let refinements = 0;
4747 let refinementFails = false;
48 const processRunner = Layer.succeed(ProcessRunner.ProcessRunner, {
48 const layerProcessRunner = Layer.succeed(ProcessRunner.ProcessRunner, {
4949 run: (input) =>
5050 Effect.sync(() => {
5151 calls.push(input.args);
......@@ -63,7 +63,7 @@ it.layer(NodeServices.layer)("RepositoryIdentityResolverLive", (it) => {
6363 };
6464 }),
6565 });
66 const resolverLayer = Layer.effect(
66 const layerResolver = Layer.effect(
6767 RepositoryIdentityResolver.RepositoryIdentityResolver,
6868 RepositoryIdentityResolver.make({
6969 refine: (identity) => {
......@@ -88,7 +88,7 @@ it.layer(NodeServices.layer)("RepositoryIdentityResolverLive", (it) => {
8888 );
8989 },
9090 }),
91 ).pipe(Layer.provide(processRunner));
91 ).pipe(Layer.provide(layerProcessRunner));
9292
9393 return Effect.gen(function* () {
9494 const resolver = yield* RepositoryIdentityResolver.RepositoryIdentityResolver;
......@@ -125,13 +125,13 @@ it.layer(NodeServices.layer)("RepositoryIdentityResolverLive", (it) => {
125125 const unavailable = yield* resolver.resolve(rootPath, { refresh: true });
126126 expect(unavailable?.webUrl).toBeUndefined();
127127 expect(unavailable?.canonicalKey).toBe("ssh.forge.test/team/repo");
128 }).pipe(Effect.provide(Layer.merge(TestClock.layer(), resolverLayer)));
128 }).pipe(Effect.provide(Layer.merge(TestClock.layer(), layerResolver)));
129129 });
130130
131131 it.effect("retries Git root discovery after the negative TTL", () => {
132132 const calls: Array<ReadonlyArray<string>> = [];
133133 let rootAttempts = 0;
134 const processRunner = Layer.succeed(ProcessRunner.ProcessRunner, {
134 const layerProcessRunner = Layer.succeed(ProcessRunner.ProcessRunner, {
135135 run: (input) =>
136136 Effect.sync(() => {
137137 calls.push(input.args);
......@@ -153,10 +153,10 @@ it.layer(NodeServices.layer)("RepositoryIdentityResolverLive", (it) => {
153153 };
154154 }),
155155 });
156 const resolverLayer = Layer.effect(
156 const layerResolver = Layer.effect(
157157 RepositoryIdentityResolver.RepositoryIdentityResolver,
158158 RepositoryIdentityResolver.make(),
159 ).pipe(Layer.provide(processRunner));
159 ).pipe(Layer.provide(layerProcessRunner));
160160
161161 return Effect.gen(function* () {
162162 const resolver = yield* RepositoryIdentityResolver.RepositoryIdentityResolver;
......@@ -171,7 +171,7 @@ it.layer(NodeServices.layer)("RepositoryIdentityResolverLive", (it) => {
171171 ["-C", "/repo/packages/web", "rev-parse", "--show-toplevel"],
172172 ["-C", "/repo", "remote", "-v"],
173173 ]);
174 }).pipe(Effect.provide(Layer.merge(TestClock.layer(), resolverLayer)));
174 }).pipe(Effect.provide(Layer.merge(TestClock.layer(), layerResolver)));
175175 });
176176
177177 it.effect("normalizes equivalent GitHub remotes into a stable repository identity", () =>
......@@ -341,7 +341,7 @@ it.layer(NodeServices.layer)("RepositoryIdentityResolverLive", (it) => {
341341 Effect.provide(
342342 Layer.merge(
343343 TestClock.layer(),
344 makeRepositoryIdentityResolverTestLayer({
344 layerRepositoryIdentityResolverTest({
345345 negativeCacheTtl: Duration.millis(50),
346346 positiveCacheTtl: Duration.seconds(1),
347347 }),
......@@ -382,7 +382,7 @@ it.layer(NodeServices.layer)("RepositoryIdentityResolverLive", (it) => {
382382 Effect.provide(
383383 Layer.merge(
384384 TestClock.layer(),
385 makeRepositoryIdentityResolverTestLayer({
385 layerRepositoryIdentityResolverTest({
386386 negativeCacheTtl: Duration.millis(50),
387387 positiveCacheTtl: Duration.millis(100),
388388 }),
apps/server/src/project/T3ProjectFileLoader.test.ts+2-2
......@@ -8,7 +8,7 @@ import * as Path from "effect/Path";
88
99import * as T3ProjectFileLoader from "./T3ProjectFileLoader.ts";
1010
11const TestLayer = Layer.empty.pipe(
11const layerTest = Layer.empty.pipe(
1212 Layer.provideMerge(T3ProjectFileLoader.layer),
1313 Layer.provideMerge(NodeServices.layer),
1414);
......@@ -26,7 +26,7 @@ const writeProjectFile = Effect.fn("writeProjectFile")(function* (cwd: string, c
2626 yield* fileSystem.writeFileString(path.join(cwd, "t3.json"), contents).pipe(Effect.orDie);
2727});
2828
29it.layer(TestLayer)("T3ProjectFileLoader", (it) => {
29it.layer(layerTest)("T3ProjectFileLoader", (it) => {
3030 describe("load", () => {
3131 it.effect("loads and decodes a valid t3.json", () =>
3232 Effect.gen(function* () {
apps/server/src/project/http.ts+1-1
......@@ -29,7 +29,7 @@ export const failProjectMutation = Effect.fn("environment.projects.failMutation"
2929 return yield* failEnvironmentInternal("project_mutation_failed", cause);
3030});
3131
32export const projectHttpApiLayer = HttpApiBuilder.group(
32export const layer = HttpApiBuilder.group(
3333 EnvironmentHttpApi,
3434 "projects",
3535 Effect.fnUntraced(function* (handlers) {
apps/server/src/provider/CodexManagedRuntime.test.ts+2-2
......@@ -86,7 +86,7 @@ it.effect.each(
8686 source,
8787 version: "0.156.1",
8888 };
89 const installerLayer = Layer.mock(CodexInstallation.CodexInstallation)({
89 const layerInstaller = Layer.mock(CodexInstallation.CodexInstallation)({
9090 managedDirectory: "/isolated/tools/codex",
9191 resolve: () => Effect.succeed(executable),
9292 acquire: () =>
......@@ -183,7 +183,7 @@ it.effect.each(
183183 EnvironmentId.make("00000000-0000-4000-8000-000000000001"),
184184 ),
185185 }),
186 Effect.provide(installerLayer),
186 Effect.provide(layerInstaller),
187187 Effect.provideService(
188188 HttpClient.HttpClient,
189189 HttpClient.make((request) =>
apps/server/src/provider/Drivers/AntigravityDriver.test.ts+4-4
......@@ -127,7 +127,7 @@ const makeHarness = Effect.fn("makeAntigravityDriverHarness")(function* (
127127 handle: ChildProcessSpawner.ChildProcessHandle;
128128 }> = [];
129129
130 const installation = Layer.mock(AntigravityInstallation.AntigravityInstallation)({
130 const layerInstallation = Layer.mock(AntigravityInstallation.AntigravityInstallation)({
131131 managedDirectory: root,
132132 resolve: () =>
133133 Effect.gen(function* () {
......@@ -203,7 +203,7 @@ const makeHarness = Effect.fn("makeAntigravityDriverHarness")(function* (
203203 { name: "BROWSER", value: "must-not-run" },
204204 ].map((variable) => ({ ...variable, sensitive: false })),
205205 }).pipe(
206 Effect.provide(installation),
206 Effect.provide(layerInstallation),
207207 Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, observedSpawner),
208208 );
209209 const refresh = instance.refreshModels;
......@@ -244,7 +244,7 @@ const makeHarness = Effect.fn("makeAntigravityDriverHarness")(function* (
244244 };
245245});
246246
247const testLayer = ServerConfig.layerTest(process.cwd(), {
247const layerTest = ServerConfig.layerTest(process.cwd(), {
248248 prefix: "t3-antigravity-driver-config-",
249249}).pipe(
250250 Layer.provideMerge(NodeServices.layer),
......@@ -264,7 +264,7 @@ const testLayer = ServerConfig.layerTest(process.cwd(), {
264264 Layer.provideMerge(IdAllocator.layer),
265265);
266266
267it.layer(testLayer)("AntigravityDriver", (it) => {
267it.layer(layerTest)("AntigravityDriver", (it) => {
268268 it.effect.skipIf(windowsHost)(
269269 "preserves the Node install message when starting a standalone provider",
270270 () =>
apps/server/src/provider/Drivers/CodexDriver.test.ts+2-2
......@@ -39,7 +39,7 @@ import * as IdAllocator from "../../orchestration-v2/IdAllocator.ts";
3939import { ProviderAdapterV2RuntimePolicy } from "../../orchestration-v2/ProviderAdapter.ts";
4040import * as ProviderCredentialStore from "../ProviderCredentialStore.ts";
4141
42const testLayer = ServerConfig.layerTest(process.cwd(), {
42const layerTest = ServerConfig.layerTest(process.cwd(), {
4343 prefix: "t3-codex-driver-maintenance-",
4444}).pipe(
4545 Layer.provideMerge(NodeServices.layer),
......@@ -90,7 +90,7 @@ const noSpawn = ChildProcessSpawner.make(() =>
9090);
9191const encodeCredentials = Schema.encodeEffect(Schema.fromJsonString(Schema.Unknown));
9292
93it.layer(testLayer)("CodexDriver", (it) => {
93it.layer(layerTest)("CodexDriver", (it) => {
9494 it.effect("disconnect refreshes a restored managed account while its auth flow is idle", () =>
9595 Effect.gen(function* () {
9696 const instanceId = ProviderInstanceId.make("restored-managed-account");
apps/server/src/provider/Drivers/CursorDriver.test.ts+2-2
......@@ -21,7 +21,7 @@ import * as IdAllocator from "../../orchestration-v2/IdAllocator.ts";
2121import { ProviderAdapterV2RuntimePolicy } from "../../orchestration-v2/ProviderAdapter.ts";
2222import { Cursor } from "../cursorSdk.ts";
2323
24const testLayer = ServerSecretStore.layer.pipe(
24const layerTest = ServerSecretStore.layer.pipe(
2525 Layer.provideMerge(
2626 ServerConfig.layerTest(process.cwd(), {
2727 prefix: "t3-cursor-driver-copy-command-",
......@@ -54,7 +54,7 @@ const testLayer = ServerSecretStore.layer.pipe(
5454 ),
5555);
5656
57it.layer(testLayer)("CursorDriver", (it) => {
57it.layer(layerTest)("CursorDriver", (it) => {
5858 it.effect(
5959 "persists browser credentials, uses them for chat, and closes the SDK session on logout",
6060 () =>
apps/server/src/provider/Drivers/CursorDriver.ts+1-1
......@@ -231,7 +231,7 @@ export const CursorDriver: ProviderDriver<CursorSettings, CursorDriverEnv> = {
231231 Effect.provideService(FileSystem.FileSystem, fileSystem),
232232 Effect.provideService(Path.Path, path),
233233 Effect.map(stampSnapshot),
234 Effect.provide(CursorSdkCatalog.CursorSdkCatalogLive),
234 Effect.provide(CursorSdkCatalog.layer),
235235 );
236236
237237 const snapshotSettings = makeProviderSnapshotSettingsSource(effectiveConfig, serverSettings);
apps/server/src/provider/Drivers/GrokDriver.test.ts+2-2
......@@ -17,7 +17,7 @@ import { GrokDriver } from "./GrokDriver.ts";
1717
1818import * as IdAllocator from "../../orchestration-v2/IdAllocator.ts";
1919
20const testLayer = ServerConfig.layerTest(process.cwd(), {
20const layerTest = ServerConfig.layerTest(process.cwd(), {
2121 prefix: "t3-grok-driver-update-",
2222}).pipe(
2323 Layer.provideMerge(NodeServices.layer),
......@@ -49,7 +49,7 @@ const noSpawner = ChildProcessSpawner.make(() =>
4949// The `#!/bin/sh` stub below cannot be resolved as an executable on Windows.
5050const windowsHost = HostProcessPlatform.defaultValue() === "win32";
5151
52it.layer(testLayer)("GrokDriver", (it) => {
52it.layer(layerTest)("GrokDriver", (it) => {
5353 it.effect.skipIf(windowsHost)("updates through the configured executable's own updater", () =>
5454 Effect.gen(function* () {
5555 const fs = yield* FileSystem.FileSystem;
apps/server/src/provider/Drivers/OpenCodeDriver.test.ts+2-2
......@@ -211,7 +211,7 @@ const changingRuntime = {
211211 startOpenCodeServerProcess: () => reachedServer("start"),
212212 connectToOpenCodeServer: () => reachedServer("connect"),
213213} as unknown as OpenCodeRuntime.OpenCodeRuntimeShape;
214const updateLayer = Layer.mergeAll(
214const layerUpdate = Layer.mergeAll(
215215 ServerConfig.layerTest(process.cwd(), { prefix: "t3-opencode-driver-update-" }),
216216 IdAllocator.layer,
217217 ServerSettings.layerTest(),
......@@ -223,7 +223,7 @@ const updateLayer = Layer.mergeAll(
223223 Layer.succeed(OpenCodeRuntime.OpenCodeRuntime, changingRuntime),
224224).pipe(Layer.provideMerge(NodeServices.layer));
225225
226it.layer(updateLayer)("OpenCodeDriver updates", (it) => {
226it.layer(layerUpdate)("OpenCodeDriver updates", (it) => {
227227 it.effect("never runs the binary for a disabled instance's update check", () =>
228228 Effect.gen(function* () {
229229 versionProbes.length = 0;
apps/server/src/provider/Layers/AcpRegistryCatalog.ts+1-1
......@@ -7,7 +7,7 @@ import * as AcpRegistrySupport from "../acp/AcpRegistrySupport.ts";
77import * as AcpRegistryRuntimeCoordinator from "../acp/AcpRegistryRuntimeCoordinator.ts";
88
99/** Server-lifetime ACP Registry catalog shared by setup, snapshots, and turn launch. */
10export const AcpRegistryCatalogLive = Layer.merge(
10export const layer = Layer.merge(
1111 Layer.unwrap(
1212 Effect.gen(function* () {
1313 const config = yield* ServerConfig.ServerConfig;
apps/server/src/provider/Layers/AntigravityProvider.test.ts+2-2
......@@ -106,7 +106,7 @@ const expectedCommands = [
106106 { name: "logout", description: "Sign out of Google" },
107107];
108108
109const testLayer = Layer.merge(
109const layerTest = Layer.merge(
110110 Layer.mock(BackgroundPolicy.BackgroundPolicy)({
111111 shouldRunScopeWork: () => Effect.succeed(false),
112112 }),
......@@ -216,7 +216,7 @@ describe("Antigravity model catalog", () => {
216216 });
217217});
218218
219it.layer(testLayer)("Antigravity provider snapshots", (it) => {
219it.layer(layerTest)("Antigravity provider snapshots", (it) => {
220220 it.effect("does not probe or run helper safety checks while disabled", () =>
221221 Effect.scoped(
222222 Effect.gen(function* () {
apps/server/src/provider/Layers/CursorProvider.test.ts+3-3
......@@ -176,7 +176,7 @@ describe("checkCursorProviderStatus", () => {
176176 { CURSOR_API_KEY: "test-cursor-key" },
177177 ).pipe(
178178 Effect.provide(
179 CursorSdkCatalog.makeCursorSdkCatalogTestLayer((apiKey) => {
179 CursorSdkCatalog.layerTest((apiKey) => {
180180 expect(apiKey).toBe("test-cursor-key");
181181 return Effect.succeed({
182182 user: {
......@@ -212,7 +212,7 @@ describe("checkCursorProviderStatus", () => {
212212 CURSOR_API_KEY: "invalid-test-key",
213213 }).pipe(
214214 Effect.provide(
215 CursorSdkCatalog.makeCursorSdkCatalogTestLayer(() =>
215 CursorSdkCatalog.layerTest(() =>
216216 Effect.fail(
217217 new CursorSdkCatalog.CursorSdkCatalogError({
218218 authenticationFailure: true,
......@@ -235,7 +235,7 @@ describe("checkCursorProviderStatus", () => {
235235 Effect.gen(function* () {
236236 const provider = yield* checkCursorProviderStatus(baseCursorSettings).pipe(
237237 Effect.provide(
238 CursorSdkCatalog.makeCursorSdkCatalogTestLayer(() =>
238 CursorSdkCatalog.layerTest(() =>
239239 Effect.die("SDK catalog must not be used without CURSOR_API_KEY"),
240240 ),
241241 ),
apps/server/src/provider/Layers/CursorSdkCatalog.ts+2-4
......@@ -85,10 +85,8 @@ export const makeCursorSdkCatalog = Effect.fn("CursorSdkCatalog.make")(function*
8585 });
8686});
8787
88export const CursorSdkCatalogLive = Layer.effect(CursorSdkCatalog, makeCursorSdkCatalog());
88export const layer = Layer.effect(CursorSdkCatalog, makeCursorSdkCatalog());
8989
90export function makeCursorSdkCatalogTestLayer(
91 read: CursorSdkCatalogShape["read"],
92): Layer.Layer<CursorSdkCatalog> {
90export function layerTest(read: CursorSdkCatalogShape["read"]): Layer.Layer<CursorSdkCatalog> {
9391 return Layer.succeed(CursorSdkCatalog, CursorSdkCatalog.of({ read }));
9492}
apps/server/src/provider/Layers/OpenCodeProvider.test.ts+3-3
......@@ -314,7 +314,7 @@ it("keeps native and MCP commands while preserving compaction and separate skill
314314 );
315315});
316316
317const testLayer = Layer.succeed(OpenCodeRuntime.OpenCodeRuntime, OpenCodeRuntimeTestDouble).pipe(
317const layerTest = Layer.succeed(OpenCodeRuntime.OpenCodeRuntime, OpenCodeRuntimeTestDouble).pipe(
318318 Layer.provideMerge(ServerConfig.layerTest(process.cwd(), process.cwd())),
319319 Layer.provideMerge(NodeServices.layer),
320320);
......@@ -358,7 +358,7 @@ const checkProvider = Effect.fn("checkProvider")(function* (
358358 );
359359});
360360
361it.layer(testLayer)("checkOpenCodeProviderStatus", (it) => {
361it.layer(layerTest)("checkOpenCodeProviderStatus", (it) => {
362362 it.effect("shows a codex-style missing binary message", () =>
363363 Effect.gen(function* () {
364364 runtimeMock.state.runVersionError = new Error("spawn opencode ENOENT");
......@@ -657,7 +657,7 @@ it.layer(testLayer)("checkOpenCodeProviderStatus", (it) => {
657657 );
658658});
659659
660it.layer(testLayer)("checkOpenCodeProviderStatus with configured server URL", (it) => {
660it.layer(layerTest)("checkOpenCodeProviderStatus with configured server URL", (it) => {
661661 it.effect("does not send a local environment password to a configured server", () =>
662662 Effect.gen(function* () {
663663 const snapshot = yield* checkProvider(
apps/server/src/provider/Layers/ProviderAuthService.ts+1-4
......@@ -250,7 +250,4 @@ export const makeProviderAuthService = Effect.gen(function* () {
250250 });
251251});
252252
253export const ProviderAuthServiceLive = Layer.effect(
254 ProviderAuthService.ProviderAuthService,
255 makeProviderAuthService,
256);
253export const layer = Layer.effect(ProviderAuthService.ProviderAuthService, makeProviderAuthService);
apps/server/src/provider/Layers/ProviderInstanceRegistryHydration.ts+12-14
......@@ -29,7 +29,7 @@
2929 * ----------
3030 * On layer build we:
3131 * 1. Read the current `ServerSettings` once and use it to seed the
32 * registry's initial state via `ProviderInstanceRegistryMutableLayer`.
32 * registry's initial state via `ProviderInstanceRegistryLive.layer`.
3333 * 2. Fork a daemon fiber (lifetime tied to the layer's scope) that
3434 * acquires `ServerSettingsService.subscribeChanges` and calls
3535 * `ProviderInstanceRegistryMutator.reconcile` on every emission.
......@@ -55,13 +55,11 @@ import * as Settings from "../../serverSettings.ts";
5555import { BUILT_IN_DRIVERS, type BuiltInDriversEnv } from "../builtInDrivers.ts";
5656import * as ProviderInstanceRegistry from "../Services/ProviderInstanceRegistry.ts";
5757import * as ProviderInstanceRegistryMutator from "../Services/ProviderInstanceRegistryMutator.ts";
58import { ProviderInstanceRegistryMutableLayer } from "./ProviderInstanceRegistryLive.ts";
59import {
60 type ProviderOrchestrationAdapterInfrastructure,
61 ProviderOrchestrationAdapterInfrastructureLive,
62} from "./ProviderOrchestrationAdapterInfrastructure.ts";
58import * as ProviderInstanceRegistryLive from "./ProviderInstanceRegistryLive.ts";
59import { type ProviderOrchestrationAdapterInfrastructure } from "./ProviderOrchestrationAdapterInfrastructure.ts";
60import * as ProviderOrchestrationAdapterInfrastructureLayer from "./ProviderOrchestrationAdapterInfrastructure.ts";
6361import * as AcpRegistrySupport from "../acp/AcpRegistrySupport.ts";
64import { AcpRegistryCatalogLive } from "./AcpRegistryCatalog.ts";
62import * as AcpRegistryCatalogLayer from "./AcpRegistryCatalog.ts";
6563
6664type ProviderInstanceRegistryHydrationEnv =
6765 | Exclude<
......@@ -127,7 +125,7 @@ export const deriveProviderInstanceConfigMap = (
127125 * configs, so the only way the watcher could fail is a settings stream
128126 * tear-down, which logs and exits cleanly.
129127 */
130const SettingsWatcherLive = Layer.effectDiscard(
128const layerSettingsWatcher = Layer.effectDiscard(
131129 Effect.gen(function* () {
132130 const mutator = yield* ProviderInstanceRegistryMutator.ProviderInstanceRegistryMutator;
133131 const serverSettings = yield* Settings.ServerSettingsService;
......@@ -152,7 +150,7 @@ const SettingsWatcherLive = Layer.effectDiscard(
152150 * sync with subsequent `streamChanges` emissions.
153151 *
154152 * The Layer's two halves:
155 * - `ProviderInstanceRegistryMutableLayer` produces the registry +
153 * - `ProviderInstanceRegistryLive.layer` produces the registry +
156154 * mutator from the initial config map. Its scope owns every
157155 * per-instance child scope created during reconcile.
158156 * - `SettingsWatcherLive` consumes the mutator, acquires its settings
......@@ -163,7 +161,7 @@ const SettingsWatcherLive = Layer.effectDiscard(
163161 * The mutator tag is technically also exposed; only this module imports
164162 * it, so the visibility leak is harmless in practice.
165163 */
166export const ProviderInstanceRegistryHydrationLive: Layer.Layer<
164export const layer: Layer.Layer<
167165 ProviderInstanceRegistry.ProviderInstanceRegistry,
168166 never,
169167 ProviderInstanceRegistryHydrationEnv
......@@ -178,15 +176,15 @@ export const ProviderInstanceRegistryHydrationLive: Layer.Layer<
178176 ? ({} as ProviderInstanceConfigMap)
179177 : deriveProviderInstanceConfigMap(initialSettings);
180178
181 const mutableLayer = ProviderInstanceRegistryMutableLayer({
179 const layerMutable = ProviderInstanceRegistryLive.layer({
182180 drivers: BUILT_IN_DRIVERS,
183181 configMap: initialConfigMap,
184182 }).pipe(
185 Layer.provide(ProviderOrchestrationAdapterInfrastructureLive),
186 Layer.provide(AcpRegistryCatalogLive),
183 Layer.provide(ProviderOrchestrationAdapterInfrastructureLayer.layer),
184 Layer.provide(AcpRegistryCatalogLayer.layer),
187185 );
188186
189 return SettingsWatcherLive.pipe(Layer.provideMerge(mutableLayer));
187 return layerSettingsWatcher.pipe(Layer.provideMerge(layerMutable));
190188 }),
191189) as Layer.Layer<
192190 ProviderInstanceRegistry.ProviderInstanceRegistry,
apps/server/src/provider/Layers/ProviderInstanceRegistryLive.test.ts+22-22
......@@ -63,9 +63,9 @@ import * as OpenCodeServerLedger from "../OpenCodeServerLedger.ts";
6363import * as ResetCreditCoordinator from "./resetCreditCoordinator.ts";
6464import * as ProviderEventLoggers from "./ProviderEventLoggers.ts";
6565import { makeProviderInstanceRegistry } from "./ProviderInstanceRegistryLive.ts";
66import { ProviderOrchestrationAdapterInfrastructureLive } from "./ProviderOrchestrationAdapterInfrastructure.ts";
66import * as ProviderOrchestrationAdapterInfrastructure from "./ProviderOrchestrationAdapterInfrastructure.ts";
6767
68const TestHttpClientLive = Layer.succeed(
68const layerTestHttpClient = Layer.succeed(
6969 HttpClient.HttpClient,
7070 HttpClient.make((request) =>
7171 Effect.succeed(HttpClientResponse.fromWeb(request, Response.json({ version: "0.0.0" }))),
......@@ -74,7 +74,7 @@ const TestHttpClientLive = Layer.succeed(
7474
7575const TEST_EPOCH = DateTime.makeUnsafe("1970-01-01T00:00:00.000Z");
7676
77const BackgroundPolicyAlwaysRunLayer = Layer.mock(BackgroundPolicy.BackgroundPolicy)({
77const layerBackgroundPolicyAlwaysRun = Layer.mock(BackgroundPolicy.BackgroundPolicy)({
7878 reportClientActivity: () => Effect.void,
7979 removeRpcClient: () => Effect.void,
8080 reportHostPowerState: () => Effect.void,
......@@ -201,7 +201,7 @@ describe("ProviderInstanceRegistryLive — multi-instance codex slice", () => {
201201 // `NodeServices.layer` through `Layer.provideMerge` to satisfy that
202202 // dependency while still surfacing NodeServices to the test body (the
203203 // codex driver's `create` yields `ChildProcessSpawner` directly).
204 const baseLayer = ServerConfig.layerTest(process.cwd(), {
204 const layerBase = ServerConfig.layerTest(process.cwd(), {
205205 prefix: "provider-instance-registry-test",
206206 }).pipe(
207207 Layer.provideMerge(NodeServices.layer),
......@@ -218,9 +218,9 @@ describe("ProviderInstanceRegistryLive — multi-instance codex slice", () => {
218218 ),
219219 }),
220220 ),
221 Layer.provideMerge(BackgroundPolicyAlwaysRunLayer),
221 Layer.provideMerge(layerBackgroundPolicyAlwaysRun),
222222 Layer.provideMerge(ServerSettings.layerTest()),
223 Layer.provideMerge(TestHttpClientLive),
223 Layer.provideMerge(layerTestHttpClient),
224224 Layer.provideMerge(ServerSettings.layerTest()),
225225 Layer.provideMerge(
226226 Layer.succeed(
......@@ -231,8 +231,8 @@ describe("ProviderInstanceRegistryLive — multi-instance codex slice", () => {
231231 Layer.provideMerge(ModelManifest.layerTest),
232232 Layer.provideMerge(ResetCreditCoordinator.layerTest),
233233 );
234 const testLayer = ProviderOrchestrationAdapterInfrastructureLive.pipe(
235 Layer.provideMerge(baseLayer),
234 const layerTest = ProviderOrchestrationAdapterInfrastructure.layer.pipe(
235 Layer.provideMerge(layerBase),
236236 );
237237
238238 it.live("boots two independent codex instances from a ProviderInstanceConfigMap", () =>
......@@ -310,7 +310,7 @@ describe("ProviderInstanceRegistryLive — multi-instance codex slice", () => {
310310 // Nothing goes to the unavailable bucket — both drivers are registered.
311311 const unavailable = yield* registry.listUnavailable;
312312 expect(unavailable).toEqual([]);
313 }).pipe(Effect.provide(testLayer)),
313 }).pipe(Effect.provide(layerTest)),
314314 );
315315
316316 it.live("treats an explicit in-config enabled:false as disabling despite the envelope", () =>
......@@ -336,7 +336,7 @@ describe("ProviderInstanceRegistryLive — multi-instance codex slice", () => {
336336 expect(instance!.enabled).toBe(false);
337337 const snapshot = yield* instance!.snapshot.getSnapshot;
338338 expect(snapshot.enabled).toBe(false);
339 }).pipe(Effect.provide(testLayer)),
339 }).pipe(Effect.provide(layerTest)),
340340 );
341341
342342 it.live("reports Codex's answer when a redemption changed nothing", () =>
......@@ -374,7 +374,7 @@ describe("ProviderInstanceRegistryLive — multi-instance codex slice", () => {
374374 // The usage read fails, so the re-probe cannot confirm new limits.
375375 yield* codex!.snapshot.refresh;
376376 expect(yield* codex!.consumeResetCredit!()).toBe("alreadyRedeemed");
377 }).pipe(Effect.provide(testLayer)),
377 }).pipe(Effect.provide(layerTest)),
378378 );
379379
380380 it.live("runs Codex and Claude readiness probes from configured tilde paths", () =>
......@@ -428,7 +428,7 @@ describe("ProviderInstanceRegistryLive — multi-instance codex slice", () => {
428428 installed: true,
429429 version: "2.1.219",
430430 });
431 }).pipe(Effect.provide(testLayer)),
431 }).pipe(Effect.provide(layerTest)),
432432 );
433433
434434 const redeemClaudeReset = (claim: { result: string; usageFailsAfterClaim: boolean }) =>
......@@ -497,7 +497,7 @@ describe("ProviderInstanceRegistryLive — multi-instance codex slice", () => {
497497 }).pipe(
498498 // macOS logins live in the Keychain, where resets are never read.
499499 Effect.provideService(HostProcessPlatform, "linux"),
500 Effect.provide(testLayer),
500 Effect.provide(layerTest),
501501 );
502502
503503 it.live("refreshes Claude usage after redeeming a reset", () =>
......@@ -558,7 +558,7 @@ describe("ProviderInstanceRegistryLive — multi-instance codex slice", () => {
558558 expect(ghost.driver).toBe("ghostDriver");
559559 expect(ghost.availability).toBe("unavailable");
560560 expect(ghost.unavailableReason).toMatch(/ghostDriver/);
561 }).pipe(Effect.provide(testLayer)),
561 }).pipe(Effect.provide(layerTest)),
562562 );
563563});
564564
......@@ -575,7 +575,7 @@ describe("ProviderInstanceRegistryLive — all drivers slice", () => {
575575 // provides `OpenCodeRuntimeLive`'s deps while keeping its own outputs
576576 // surfaced; that merged layer then provides `ServerConfig.layerTest`'s
577577 // `FileSystem` dep while keeping everything else surfaced to the test.
578 const infraLayer = OpenCodeRuntime.OpenCodeRuntimeLive.pipe(
578 const layerInfra = OpenCodeRuntime.layer.pipe(
579579 Layer.provide(OpenCodeServerLedger.layerTest),
580580 Layer.provideMerge(NodeServices.layer),
581581 Layer.provideMerge(
......@@ -591,17 +591,17 @@ describe("ProviderInstanceRegistryLive — all drivers slice", () => {
591591 }),
592592 ),
593593 );
594 const baseLayer = AntigravityInstallation.AntigravityInstallation.layer.pipe(
594 const layerBase = AntigravityInstallation.AntigravityInstallation.layer.pipe(
595595 Layer.provideMerge(ServerSecretStore.layer),
596596 Layer.provideMerge(
597597 ServerConfig.layerTest(process.cwd(), {
598598 prefix: "provider-instance-registry-all-drivers-test",
599599 }),
600600 ),
601 Layer.provideMerge(infraLayer),
602 Layer.provideMerge(BackgroundPolicyAlwaysRunLayer),
601 Layer.provideMerge(layerInfra),
602 Layer.provideMerge(layerBackgroundPolicyAlwaysRun),
603603 Layer.provideMerge(ServerSettings.layerTest()),
604 Layer.provideMerge(TestHttpClientLive),
604 Layer.provideMerge(layerTestHttpClient),
605605 Layer.provideMerge(ServerSettings.layerTest()),
606606 Layer.provideMerge(
607607 Layer.succeed(
......@@ -612,8 +612,8 @@ describe("ProviderInstanceRegistryLive — all drivers slice", () => {
612612 Layer.provideMerge(ModelManifest.layerTest),
613613 Layer.provideMerge(ResetCreditCoordinator.layerTest),
614614 );
615 const testLayer = ProviderOrchestrationAdapterInfrastructureLive.pipe(
616 Layer.provideMerge(baseLayer),
615 const layerTest = ProviderOrchestrationAdapterInfrastructure.layer.pipe(
616 Layer.provideMerge(layerBase),
617617 );
618618
619619 it.live("boots one instance of every shipped driver from a single config map", () =>
......@@ -776,6 +776,6 @@ describe("ProviderInstanceRegistryLive — all drivers slice", () => {
776776 expect(openCodeSnapshot.continuation?.groupKey).toBe(
777777 `${openCodeDriverKind}:instance:${openCodeId}`,
778778 );
779 }).pipe(Effect.provide(testLayer)),
779 }).pipe(Effect.provide(layerTest)),
780780 );
781781});
apps/server/src/provider/Layers/ProviderInstanceRegistryLive.ts+1-1
......@@ -412,7 +412,7 @@ export const makeProviderInstanceRegistry = <R>(input: {
412412 * changes. Tests that exercise the mutator directly can pair this Layer
413413 * with a test-local `ServerSettingsService`.
414414 */
415export const ProviderInstanceRegistryMutableLayer = <R>(input: {
415export const layer = <R>(input: {
416416 readonly drivers: ReadonlyArray<AnyProviderDriver<R>>;
417417 readonly configMap: ProviderInstanceConfigMap;
418418}): Layer.Layer<
apps/server/src/provider/Layers/ProviderOrchestrationAdapterInfrastructure.ts+4-4
......@@ -18,10 +18,10 @@ export type ProviderOrchestrationAdapterInfrastructure =
1818 * reference the orchestration runtime provides to its continuation worker so
1919 * Effect layer memoization yields one shared queue.
2020 */
21export const ProviderOrchestrationAdapterInfrastructureLive = Layer.mergeAll(
22 ClaudeAdapterV2.claudeAgentSdkQueryRunnerLiveLayer,
23 CodexAdapterV2.codexAppServerClientFactoryFromSettingsLayer,
24 CursorAgentSdk.cursorAgentSdkRunnerLiveLayer,
21export const layer = Layer.mergeAll(
22 ClaudeAdapterV2.layerQueryRunner,
23 CodexAdapterV2.layerAppServerClientFactory,
24 CursorAgentSdk.layer,
2525 IdAllocator.layer,
2626 ProviderContinuationRequests.layer,
2727);
apps/server/src/provider/Layers/ProviderRegistry.test.ts+2704-2744
......@@ -47,14 +47,14 @@ import * as ResetCreditCoordinator from "./resetCreditCoordinator.ts";
4747import * as OpenCodeRuntime from "../opencodeRuntime.ts";
4848import * as OpenCodeServerLedger from "../OpenCodeServerLedger.ts";
4949import * as ProviderEventLoggers from "./ProviderEventLoggers.ts";
50import { ProviderInstanceRegistryHydrationLive } from "./ProviderInstanceRegistryHydration.ts";
50import * as ProviderInstanceRegistryHydration from "./ProviderInstanceRegistryHydration.ts";
5151import {
5252 mergeProviderSnapshot,
5353 mergeProviderSnapshots,
5454 selectProvidersByKind,
5555 upsertProviderWorkspaceSnapshot,
56 ProviderRegistryLive,
5756} from "./ProviderRegistry.ts";
57import * as ProviderRegistryLayer from "./ProviderRegistry.ts";
5858import * as ServerConfig from "../../config.ts";
5959import * as ServerSettingsModule from "../../serverSettings.ts";
6060import {
......@@ -92,14 +92,14 @@ const withBundledCompatibility = (snapshot: ServerProvider) =>
9292 );
9393
9494// Provider metadata checks use a bundled manifest and stubbed HTTP.
95const TestHttpClientLive = Layer.succeed(
95const layerTestHttpClient = Layer.succeed(
9696 HttpClient.HttpClient,
9797 HttpClient.make((request) =>
9898 Effect.succeed(HttpClientResponse.fromWeb(request, Response.json({ version: "0.0.0" }))),
9999 ),
100100).pipe(Layer.provideMerge(ModelManifest.layerTest));
101101
102const BackgroundPolicyAlwaysRunLayer = Layer.mock(BackgroundPolicy.BackgroundPolicy)({
102const layerBackgroundPolicyAlwaysRun = Layer.mock(BackgroundPolicy.BackgroundPolicy)({
103103 reportClientActivity: () => Effect.void,
104104 removeRpcClient: () => Effect.void,
105105 reportHostPowerState: () => Effect.void,
......@@ -191,7 +191,7 @@ function mockHandle(result: { stdout: string; stderr: string; code: number }) {
191191 });
192192}
193193
194function mockSpawnerLayer(
194function layerMockSpawner(
195195 handler: (args: ReadonlyArray<string>) => {
196196 stdout: string;
197197 stderr: string;
......@@ -234,7 +234,7 @@ function recordingMockSpawnerLayer(
234234 return { layer, commands };
235235}
236236
237function mockCommandSpawnerLayer(
237function layerMockCommandSpawner(
238238 handler: (
239239 command: string,
240240 args: ReadonlyArray<string>,
......@@ -252,7 +252,7 @@ function mockCommandSpawnerLayer(
252252 );
253253}
254254
255function failingSpawnerLayer(description: string) {
255function layerFailingSpawner(description: string) {
256256 return Layer.succeed(
257257 ChildProcessSpawner.ChildProcessSpawner,
258258 ChildProcessSpawner.make(() =>
......@@ -268,7 +268,7 @@ function failingSpawnerLayer(description: string) {
268268 );
269269}
270270
271function hangingScopedSpawnerLayer(killCalls: Ref.Ref<number>) {
271function layerHangingScopedSpawner(killCalls: Ref.Ref<number>) {
272272 return Layer.succeed(
273273 ChildProcessSpawner.ChildProcessSpawner,
274274 ChildProcessSpawner.make(() =>
......@@ -389,7 +389,7 @@ const awaitPersistedProvider = (
389389 Effect.forkScoped,
390390 );
391391
392const TestNodeServices = Layer.mergeAll(
392const layerTestNodeServices = Layer.mergeAll(
393393 NodeServices.layer,
394394 Layer.mock(CodexInstallation.CodexInstallation)({
395395 managedDirectory: "unused-managed-installation",
......@@ -400,1714 +400,737 @@ const TestNodeServices = Layer.mergeAll(
400400 }),
401401);
402402
403it.layer(Layer.mergeAll(TestNodeServices, ServerSettingsModule.layerTest(), TestHttpClientLive))(
404 "ProviderRegistry",
405 (it) => {
406 describe("checkCodexProviderStatus", () => {
407 it.effect("uses the app-server account and model list for provider status", () =>
408 Effect.gen(function* () {
409 const status = yield* checkCodexProviderStatus(defaultCodexSettings, () =>
410 Effect.succeed(
411 makeCodexProbeSnapshot({
412 skills: [
413 {
414 name: "github:gh-fix-ci",
415 path: "/Users/test/.codex/skills/gh-fix-ci/SKILL.md",
416 enabled: true,
417 displayName: "CI Debug",
418 shortDescription: "Debug failing GitHub Actions checks",
419 },
420 ],
421 }),
422 ),
423 );
424 assert.strictEqual(status.status, "ready");
425 assert.strictEqual(status.installed, true);
426 assert.strictEqual(status.version, "1.0.0");
427 assert.strictEqual(status.auth.status, "authenticated");
428 assert.strictEqual(status.auth.type, "chatgpt");
429 assert.strictEqual(status.auth.label, "ChatGPT Pro 20x Subscription");
430 assert.strictEqual(status.auth.email, "test@example.com");
431 assert.deepStrictEqual(status.models, [
432 {
433 slug: "gpt-live-codex",
434 name: "GPT Live Codex",
435 isCustom: false,
436 capabilities: codexModelCapabilities,
437 },
438 ]);
439 assert.deepStrictEqual(status.skills, [
440 {
441 name: "github:gh-fix-ci",
442 path: "/Users/test/.codex/skills/gh-fix-ci/SKILL.md",
443 enabled: true,
444 displayName: "CI Debug",
445 shortDescription: "Debug failing GitHub Actions checks",
446 },
447 ]);
448 assert.deepStrictEqual(status.slashCommands.slice(1), [
449 {
450 name: "goal",
451 description: "Set a goal Codex keeps working toward until it is done",
452 input: { hint: "Objective, or pause, resume, clear" },
453 },
454 {
455 name: "feedback",
456 description: "Send this thread and Codex logs to OpenAI",
457 input: { hint: "Describe the issue (optional)" },
458 },
459 ]);
460 }),
461 );
403it.layer(
404 Layer.mergeAll(layerTestNodeServices, ServerSettingsModule.layerTest(), layerTestHttpClient),
405)("ProviderRegistry", (it) => {
406 describe("checkCodexProviderStatus", () => {
407 it.effect("uses the app-server account and model list for provider status", () =>
408 Effect.gen(function* () {
409 const status = yield* checkCodexProviderStatus(defaultCodexSettings, () =>
410 Effect.succeed(
411 makeCodexProbeSnapshot({
412 skills: [
413 {
414 name: "github:gh-fix-ci",
415 path: "/Users/test/.codex/skills/gh-fix-ci/SKILL.md",
416 enabled: true,
417 displayName: "CI Debug",
418 shortDescription: "Debug failing GitHub Actions checks",
419 },
420 ],
421 }),
422 ),
423 );
424 assert.strictEqual(status.status, "ready");
425 assert.strictEqual(status.installed, true);
426 assert.strictEqual(status.version, "1.0.0");
427 assert.strictEqual(status.auth.status, "authenticated");
428 assert.strictEqual(status.auth.type, "chatgpt");
429 assert.strictEqual(status.auth.label, "ChatGPT Pro 20x Subscription");
430 assert.strictEqual(status.auth.email, "test@example.com");
431 assert.deepStrictEqual(status.models, [
432 {
433 slug: "gpt-live-codex",
434 name: "GPT Live Codex",
435 isCustom: false,
436 capabilities: codexModelCapabilities,
437 },
438 ]);
439 assert.deepStrictEqual(status.skills, [
440 {
441 name: "github:gh-fix-ci",
442 path: "/Users/test/.codex/skills/gh-fix-ci/SKILL.md",
443 enabled: true,
444 displayName: "CI Debug",
445 shortDescription: "Debug failing GitHub Actions checks",
446 },
447 ]);
448 assert.deepStrictEqual(status.slashCommands.slice(1), [
449 {
450 name: "goal",
451 description: "Set a goal Codex keeps working toward until it is done",
452 input: { hint: "Objective, or pause, resume, clear" },
453 },
454 {
455 name: "feedback",
456 description: "Send this thread and Codex logs to OpenAI",
457 input: { hint: "Describe the issue (optional)" },
458 },
459 ]);
460 }),
461 );
462462
463 it.effect("passes configured launch args to the Codex provider probe", () =>
464 Effect.gen(function* () {
465 let observedLaunchArgs: string | undefined;
466 const settings = decodeCodexSettings({ launchArgs: "--strict-config --enable foo" });
463 it.effect("passes configured launch args to the Codex provider probe", () =>
464 Effect.gen(function* () {
465 let observedLaunchArgs: string | undefined;
466 const settings = decodeCodexSettings({ launchArgs: "--strict-config --enable foo" });
467467
468 const status = yield* checkCodexProviderStatus(settings, (input) => {
469 observedLaunchArgs = input.launchArgs;
470 return Effect.succeed(makeCodexProbeSnapshot());
471 });
468 const status = yield* checkCodexProviderStatus(settings, (input) => {
469 observedLaunchArgs = input.launchArgs;
470 return Effect.succeed(makeCodexProbeSnapshot());
471 });
472472
473 assert.strictEqual(status.status, "ready");
474 assert.strictEqual(observedLaunchArgs, "--strict-config --enable foo");
475 }),
476 );
473 assert.strictEqual(status.status, "ready");
474 assert.strictEqual(observedLaunchArgs, "--strict-config --enable foo");
475 }),
476 );
477477
478 it.effect("returns unauthenticated when app-server requires OpenAI auth", () =>
479 Effect.gen(function* () {
480 const status = yield* checkCodexProviderStatus(defaultCodexSettings, () =>
481 Effect.succeed(
482 makeCodexProbeSnapshot({
483 account: {
484 account: null,
485 requiresOpenaiAuth: true,
486 },
487 }),
488 ),
489 );
478 it.effect("returns unauthenticated when app-server requires OpenAI auth", () =>
479 Effect.gen(function* () {
480 const status = yield* checkCodexProviderStatus(defaultCodexSettings, () =>
481 Effect.succeed(
482 makeCodexProbeSnapshot({
483 account: {
484 account: null,
485 requiresOpenaiAuth: true,
486 },
487 }),
488 ),
489 );
490490
491 assert.strictEqual(status.status, "error");
492 assert.strictEqual(status.auth.status, "unauthenticated");
493 assert.strictEqual(
494 status.message,
495 "Codex CLI is not authenticated. Run `codex login` and try again.",
496 );
497 }),
498 );
491 assert.strictEqual(status.status, "error");
492 assert.strictEqual(status.auth.status, "unauthenticated");
493 assert.strictEqual(
494 status.message,
495 "Codex CLI is not authenticated. Run `codex login` and try again.",
496 );
497 }),
498 );
499499
500 it.effect(
501 "returns ready with unknown auth when app-server does not require OpenAI auth",
502 () =>
503 Effect.gen(function* () {
504 const status = yield* checkCodexProviderStatus(defaultCodexSettings, () =>
505 Effect.succeed(
506 makeCodexProbeSnapshot({
507 account: {
508 account: null,
509 requiresOpenaiAuth: false,
510 },
511 }),
512 ),
513 );
500 it.effect("returns ready with unknown auth when app-server does not require OpenAI auth", () =>
501 Effect.gen(function* () {
502 const status = yield* checkCodexProviderStatus(defaultCodexSettings, () =>
503 Effect.succeed(
504 makeCodexProbeSnapshot({
505 account: {
506 account: null,
507 requiresOpenaiAuth: false,
508 },
509 }),
510 ),
511 );
514512
515 assert.strictEqual(status.status, "ready");
516 assert.strictEqual(status.auth.status, "unknown");
517 }),
518 );
513 assert.strictEqual(status.status, "ready");
514 assert.strictEqual(status.auth.status, "unknown");
515 }),
516 );
519517
520 it.effect("returns an api key label for codex api key auth", () =>
521 Effect.gen(function* () {
522 const status = yield* checkCodexProviderStatus(defaultCodexSettings, () =>
523 Effect.succeed(
524 makeCodexProbeSnapshot({
525 account: {
526 account: { type: "apiKey" },
527 requiresOpenaiAuth: false,
528 },
529 }),
530 ),
531 );
518 it.effect("returns an api key label for codex api key auth", () =>
519 Effect.gen(function* () {
520 const status = yield* checkCodexProviderStatus(defaultCodexSettings, () =>
521 Effect.succeed(
522 makeCodexProbeSnapshot({
523 account: {
524 account: { type: "apiKey" },
525 requiresOpenaiAuth: false,
526 },
527 }),
528 ),
529 );
532530
533 assert.strictEqual(status.status, "ready");
534 assert.strictEqual(status.auth.status, "authenticated");
535 assert.strictEqual(status.auth.type, "apiKey");
536 assert.strictEqual(status.auth.label, "OpenAI API Key");
537 }),
538 );
531 assert.strictEqual(status.status, "ready");
532 assert.strictEqual(status.auth.status, "authenticated");
533 assert.strictEqual(status.auth.type, "apiKey");
534 assert.strictEqual(status.auth.label, "OpenAI API Key");
535 }),
536 );
539537
540 it.effect("returns an Amazon Bedrock label for codex Bedrock auth", () =>
541 Effect.gen(function* () {
542 const status = yield* checkCodexProviderStatus(defaultCodexSettings, () =>
543 Effect.succeed(
544 makeCodexProbeSnapshot({
545 account: {
546 account: { type: "amazonBedrock" },
547 requiresOpenaiAuth: false,
548 },
549 }),
550 ),
538 it.effect("returns an Amazon Bedrock label for codex Bedrock auth", () =>
539 Effect.gen(function* () {
540 const status = yield* checkCodexProviderStatus(defaultCodexSettings, () =>
541 Effect.succeed(
542 makeCodexProbeSnapshot({
543 account: {
544 account: { type: "amazonBedrock" },
545 requiresOpenaiAuth: false,
546 },
547 }),
548 ),
549 );
550
551 assert.strictEqual(status.status, "ready");
552 assert.strictEqual(status.auth.status, "authenticated");
553 assert.strictEqual(status.auth.type, "amazonBedrock");
554 assert.strictEqual(status.auth.label, "Amazon Bedrock");
555 }),
556 );
557
558 it.effect.each([
559 "codex",
560 "/Applications/Custom App.app/Contents/Resources/codex",
561 "C:\\Tools\\codex.exe",
562 ])("explains how to configure a Codex executable that cannot start: %s", (binaryPath) =>
563 Effect.gen(function* () {
564 const settings = { ...defaultCodexSettings, binaryPath };
565 const status = yield* checkCodexProviderStatus(settings, (input) => {
566 assert.strictEqual(input.binaryPath, binaryPath);
567 return Effect.fail(
568 new CodexErrors.CodexAppServerSpawnError({
569 command: `${binaryPath} app-server`,
570 cause: new Error("spawn ENOENT"),
571 }),
551572 );
573 });
574 assert.strictEqual(status.status, "error");
575 assert.strictEqual(status.installed, false);
576 assert.strictEqual(status.auth.status, "unknown");
577 assert.include(status.message, binaryPath);
578 assert.include(status.message, "Settings → Providers → Codex → Binary path on the server");
579 assert.strictEqual(
580 status.message?.includes("Installing ChatGPT or Codex desktop"),
581 binaryPath === "codex",
582 );
583 assert.strictEqual(settings.binaryPath, binaryPath);
584 }),
585 );
552586
553 assert.strictEqual(status.status, "ready");
554 assert.strictEqual(status.auth.status, "authenticated");
555 assert.strictEqual(status.auth.type, "amazonBedrock");
556 assert.strictEqual(status.auth.label, "Amazon Bedrock");
557 }),
587 it.effect("closes the app-server probe scope when provider status times out", () =>
588 Effect.gen(function* () {
589 const killCalls = yield* Ref.make(0);
590 const statusFiber = yield* checkCodexProviderStatus(defaultCodexSettings).pipe(
591 Effect.provide(layerHangingScopedSpawner(killCalls)),
592 Effect.forkChild,
593 );
594
595 yield* Effect.yieldNow;
596 yield* TestClock.adjust("11 seconds");
597 yield* Effect.yieldNow;
598
599 const status = yield* Fiber.join(statusFiber);
600 assert.strictEqual(status.status, "error");
601 assert.strictEqual(
602 status.message,
603 "Timed out while checking Codex app-server provider status.",
604 );
605 assert.strictEqual(yield* Ref.get(killCalls), 1);
606 }),
607 );
608 });
609
610 describe("ProviderRegistryLive", () => {
611 it("stores workspace skills and commands without changing machine metadata", () => {
612 const provider = {
613 instanceId: ProviderInstanceId.make("codex"),
614 driver: ProviderDriverKind.make("codex"),
615 status: "ready",
616 enabled: true,
617 installed: true,
618 auth: { status: "authenticated" },
619 checkedAt: "2026-03-25T00:00:00.000Z",
620 version: "1.0.0",
621 models: [],
622 slashCommands: [{ name: "global" }],
623 skills: [{ name: "global", path: "/global/SKILL.md", enabled: true }],
624 } satisfies ServerProvider;
625 const scopedSnapshot = {
626 ...provider,
627 checkedAt: "2026-03-25T00:01:00.000Z",
628 slashCommands: [{ name: "project" }],
629 skills: [{ name: "project", path: "/project/SKILL.md", enabled: true }],
630 } satisfies ServerProvider;
631
632 const result = upsertProviderWorkspaceSnapshot(provider, "/project", scopedSnapshot);
633
634 assert.deepStrictEqual(result.slashCommands, provider.slashCommands);
635 assert.deepStrictEqual(result.skills, provider.skills);
636 assert.deepStrictEqual(result.workspaceSnapshots, [
637 {
638 cwd: "/project",
639 checkedAt: scopedSnapshot.checkedAt,
640 slashCommands: scopedSnapshot.slashCommands,
641 skills: scopedSnapshot.skills,
642 },
643 ]);
644
645 const pendingSnapshot = {
646 ...scopedSnapshot,
647 slashCommands: [COMPACT_SLASH_COMMAND],
648 slashCommandsPending: true,
649 } satisfies ProviderWorkspaceSnapshot;
650 const partial = upsertProviderWorkspaceSnapshot(result, "/project", pendingSnapshot);
651 assert.deepStrictEqual(partial.workspaceSnapshots?.[0]?.slashCommands, [{ name: "project" }]);
652 assert.strictEqual(partial.workspaceSnapshots?.[0]?.slashCommandsPending, true);
653 const otherProject = upsertProviderWorkspaceSnapshot(
654 partial,
655 "/other-project",
656 pendingSnapshot,
558657 );
658 assert.deepStrictEqual(otherProject.workspaceSnapshots?.[1]?.slashCommands, [
659 COMPACT_SLASH_COMMAND,
660 ]);
661 const recovered = upsertProviderWorkspaceSnapshot(partial, "/project", {
662 ...scopedSnapshot,
663 slashCommands: [COMPACT_SLASH_COMMAND, { name: "replacement" }],
664 });
665 assert.deepStrictEqual(recovered.workspaceSnapshots?.[0]?.slashCommands, [
666 COMPACT_SLASH_COMMAND,
667 { name: "replacement" },
668 ]);
669 assert.strictEqual(recovered.workspaceSnapshots?.[0]?.slashCommandsPending, undefined);
670 });
559671
560 it.effect.each([
561 "codex",
562 "/Applications/Custom App.app/Contents/Resources/codex",
563 "C:\\Tools\\codex.exe",
564 ])("explains how to configure a Codex executable that cannot start: %s", (binaryPath) =>
565 Effect.gen(function* () {
566 const settings = { ...defaultCodexSettings, binaryPath };
567 const status = yield* checkCodexProviderStatus(settings, (input) => {
568 assert.strictEqual(input.binaryPath, binaryPath);
569 return Effect.fail(
570 new CodexErrors.CodexAppServerSpawnError({
571 command: `${binaryPath} app-server`,
572 cause: new Error("spawn ENOENT"),
573 }),
574 );
575 });
576 assert.strictEqual(status.status, "error");
577 assert.strictEqual(status.installed, false);
578 assert.strictEqual(status.auth.status, "unknown");
579 assert.include(status.message, binaryPath);
580 assert.include(
581 status.message,
582 "Settings → Providers → Codex → Binary path on the server",
583 );
584 assert.strictEqual(
585 status.message?.includes("Installing ChatGPT or Codex desktop"),
586 binaryPath === "codex",
587 );
588 assert.strictEqual(settings.binaryPath, binaryPath);
589 }),
672 it("preserves previously discovered provider models when a refresh returns none", () => {
673 const previousProvider = {
674 instanceId: ProviderInstanceId.make("cursor"),
675 driver: ProviderDriverKind.make("cursor"),
676 status: "ready",
677 enabled: true,
678 installed: true,
679 auth: { status: "authenticated" },
680 checkedAt: "2026-04-14T00:00:00.000Z",
681 version: "2026.04.09-f2b0fcd",
682 models: [
683 {
684 slug: "claude-opus-4-6",
685 name: "Opus 4.6",
686 isCustom: false,
687 capabilities: createModelCapabilities({
688 optionDescriptors: [
689 selectDescriptor("reasoning", "Reasoning", [
690 { id: "high", label: "High", isDefault: true },
691 ]),
692 booleanDescriptor("fastMode", "Fast Mode"),
693 booleanDescriptor("thinking", "Thinking"),
694 ],
695 }),
696 },
697 ],
698 slashCommands: [{ name: "review", description: "Review changes" }],
699 skills: [
700 {
701 name: "typescript",
702 description: "TypeScript help",
703 path: "/skills/typescript/SKILL.md",
704 enabled: true,
705 },
706 ],
707 } as const satisfies ServerProvider;
708 const refreshedProvider = {
709 ...previousProvider,
710 checkedAt: "2026-04-14T00:01:00.000Z",
711 models: [],
712 slashCommands: [],
713 skills: [],
714 } satisfies ServerProvider;
715
716 assert.deepStrictEqual(mergeProviderSnapshot(previousProvider, refreshedProvider).models, [
717 ...previousProvider.models,
718 ]);
719 assert.deepStrictEqual(
720 mergeProviderSnapshot(previousProvider, refreshedProvider).slashCommands,
721 [],
590722 );
723 assert.deepStrictEqual(mergeProviderSnapshot(previousProvider, refreshedProvider).skills, []);
724 });
591725
592 it.effect("closes the app-server probe scope when provider status times out", () =>
593 Effect.gen(function* () {
594 const killCalls = yield* Ref.make(0);
595 const statusFiber = yield* checkCodexProviderStatus(defaultCodexSettings).pipe(
596 Effect.provide(hangingScopedSpawnerLayer(killCalls)),
597 Effect.forkChild,
598 );
726 it("drops custom models the refreshed snapshot no longer carries", () => {
727 const previousProvider = {
728 instanceId: ProviderInstanceId.make("claudeAgent"),
729 driver: ProviderDriverKind.make("claudeAgent"),
730 status: "ready",
731 enabled: true,
732 installed: true,
733 auth: { status: "authenticated" },
734 checkedAt: "2026-04-14T00:00:00.000Z",
735 version: "2.1.0",
736 models: [
737 {
738 slug: "claude-sonnet-4-6",
739 name: "Sonnet 4.6",
740 isCustom: false,
741 capabilities: null,
742 },
743 {
744 slug: "removed-custom",
745 name: "removed-custom",
746 isCustom: true,
747 capabilities: null,
748 },
749 ],
750 slashCommands: [],
751 skills: [],
752 } as const satisfies ServerProvider;
753 const refreshedProvider = {
754 ...previousProvider,
755 checkedAt: "2026-04-14T00:01:00.000Z",
756 models: [previousProvider.models[0]],
757 } satisfies ServerProvider;
758
759 assert.deepStrictEqual(mergeProviderSnapshot(previousProvider, refreshedProvider).models, [
760 ...refreshedProvider.models,
761 ]);
762 });
599763
600 yield* Effect.yieldNow;
601 yield* TestClock.adjust("11 seconds");
602 yield* Effect.yieldNow;
764 it("drops stale ACP Registry models missing from a completed discovery probe", () => {
765 const previousProvider = {
766 instanceId: ProviderInstanceId.make("acpRegistry_codex"),
767 driver: ProviderDriverKind.make("acpRegistry"),
768 status: "ready",
769 enabled: true,
770 installed: true,
771 auth: { status: "authenticated" },
772 checkedAt: "2026-08-13T00:00:00.000Z",
773 version: "1.2.0",
774 models: [
775 { slug: "gpt-5.6-sol", name: "GPT-5.6-Sol", isCustom: false, capabilities: null },
776 {
777 slug: "gpt-5.6-sol[low]",
778 name: "GPT-5.6-Sol (low)",
779 isCustom: false,
780 capabilities: null,
781 },
782 { slug: "default", name: "Default", isCustom: false, capabilities: null },
783 ],
784 slashCommands: [],
785 skills: [],
786 } as const satisfies ServerProvider;
787 const refreshedProvider = {
788 ...previousProvider,
789 checkedAt: "2026-08-13T00:01:00.000Z",
790 models: [{ slug: "gpt-5.6-sol", name: "GPT-5.6-Sol", isCustom: false, capabilities: null }],
791 } satisfies ServerProvider;
792
793 assert.deepStrictEqual(mergeProviderSnapshot(previousProvider, refreshedProvider).models, [
794 ...refreshedProvider.models,
795 ]);
796 });
603797
604 const status = yield* Fiber.join(statusFiber);
605 assert.strictEqual(status.status, "error");
606 assert.strictEqual(
607 status.message,
608 "Timed out while checking Codex app-server provider status.",
609 );
610 assert.strictEqual(yield* Ref.get(killCalls), 1);
611 }),
612 );
798 it("retains ACP Registry models while discovery has not completed", () => {
799 const previousProvider = {
800 instanceId: ProviderInstanceId.make("acpRegistry_codex"),
801 driver: ProviderDriverKind.make("acpRegistry"),
802 status: "ready",
803 enabled: true,
804 installed: true,
805 auth: { status: "authenticated" },
806 checkedAt: "2026-08-13T00:00:00.000Z",
807 version: "1.2.0",
808 models: [{ slug: "gpt-5.6-sol", name: "GPT-5.6-Sol", isCustom: false, capabilities: null }],
809 slashCommands: [],
810 skills: [],
811 } as const satisfies ServerProvider;
812 const checkingProvider = {
813 ...previousProvider,
814 checkedAt: "2026-08-13T00:01:00.000Z",
815 auth: { status: "unknown" },
816 models: [{ slug: "default", name: "Default", isCustom: false, capabilities: null }],
817 } satisfies ServerProvider;
818 const failedProbeProvider = {
819 ...checkingProvider,
820 status: "warning",
821 } satisfies ServerProvider;
822
823 assert.deepStrictEqual(mergeProviderSnapshot(previousProvider, checkingProvider).models, [
824 { slug: "default", name: "Default", isCustom: false, capabilities: null },
825 { slug: "gpt-5.6-sol", name: "GPT-5.6-Sol", isCustom: false, capabilities: null },
826 ]);
827 assert.deepStrictEqual(mergeProviderSnapshot(previousProvider, failedProbeProvider).models, [
828 { slug: "default", name: "Default", isCustom: false, capabilities: null },
829 { slug: "gpt-5.6-sol", name: "GPT-5.6-Sol", isCustom: false, capabilities: null },
830 ]);
613831 });
614832
615 describe("ProviderRegistryLive", () => {
616 it("stores workspace skills and commands without changing machine metadata", () => {
617 const provider = {
618 instanceId: ProviderInstanceId.make("codex"),
619 driver: ProviderDriverKind.make("codex"),
620 status: "ready",
621 enabled: true,
622 installed: true,
623 auth: { status: "authenticated" },
624 checkedAt: "2026-03-25T00:00:00.000Z",
625 version: "1.0.0",
626 models: [],
627 slashCommands: [{ name: "global" }],
628 skills: [{ name: "global", path: "/global/SKILL.md", enabled: true }],
629 } satisfies ServerProvider;
630 const scopedSnapshot = {
631 ...provider,
632 checkedAt: "2026-03-25T00:01:00.000Z",
633 slashCommands: [{ name: "project" }],
634 skills: [{ name: "project", path: "/project/SKILL.md", enabled: true }],
635 } satisfies ServerProvider;
833 it("drops stale OpenCode models missing from a successful refresh", () => {
834 const previousProvider = {
835 instanceId: ProviderInstanceId.make("opencode"),
836 driver: ProviderDriverKind.make("opencode"),
837 status: "ready",
838 enabled: true,
839 installed: true,
840 auth: { status: "authenticated" },
841 checkedAt: "2026-07-17T00:00:00.000Z",
842 version: "1.0.0",
843 models: [
844 {
845 slug: "github/gpt-5",
846 name: "GPT-5",
847 subProvider: "GitHub",
848 isCustom: false,
849 capabilities: null,
850 },
851 {
852 slug: "removed-plugin/model",
853 name: "Removed Plugin Model",
854 subProvider: "Removed Plugin",
855 isCustom: false,
856 capabilities: null,
857 },
858 ],
859 slashCommands: [],
860 skills: [],
861 } as const satisfies ServerProvider;
862 const refreshedProvider = {
863 ...previousProvider,
864 checkedAt: "2026-07-17T00:01:00.000Z",
865 models: [
866 {
867 slug: "github/gpt-5",
868 name: "GPT-5",
869 subProvider: "GitHub",
870 isCustom: false,
871 capabilities: null,
872 },
873 ],
874 } satisfies ServerProvider;
636875
637 const result = upsertProviderWorkspaceSnapshot(provider, "/project", scopedSnapshot);
876 assert.deepStrictEqual(mergeProviderSnapshot(previousProvider, refreshedProvider).models, [
877 ...refreshedProvider.models,
878 ]);
879 });
638880
639 assert.deepStrictEqual(result.slashCommands, provider.slashCommands);
640 assert.deepStrictEqual(result.skills, provider.skills);
641 assert.deepStrictEqual(result.workspaceSnapshots, [
881 it("retains stale OpenCode models when a refresh fails", () => {
882 const previousProvider = {
883 instanceId: ProviderInstanceId.make("opencode"),
884 driver: ProviderDriverKind.make("opencode"),
885 status: "ready",
886 enabled: true,
887 installed: true,
888 auth: { status: "authenticated" },
889 checkedAt: "2026-07-17T00:00:00.000Z",
890 version: "1.0.0",
891 models: [
642892 {
643 cwd: "/project",
644 checkedAt: scopedSnapshot.checkedAt,
645 slashCommands: scopedSnapshot.slashCommands,
646 skills: scopedSnapshot.skills,
893 slug: "github/gpt-5",
894 name: "GPT-5",
895 subProvider: "GitHub",
896 isCustom: false,
897 capabilities: null,
647898 },
648 ]);
899 ],
900 slashCommands: [{ name: "review", description: "Review changes" }],
901 skills: [
902 {
903 name: "typescript",
904 description: "TypeScript help",
905 path: "/skills/typescript/SKILL.md",
906 enabled: true,
907 },
908 ],
909 } as const satisfies ServerProvider;
910 const refreshedProvider = {
911 ...previousProvider,
912 status: "error",
913 auth: { status: "unknown" },
914 checkedAt: "2026-07-17T00:01:00.000Z",
915 models: [],
916 message: "Failed to refresh OpenCode models.",
917 } satisfies ServerProvider;
918
919 assert.deepStrictEqual(mergeProviderSnapshot(previousProvider, refreshedProvider).models, [
920 ...previousProvider.models,
921 ]);
922 assert.deepStrictEqual(
923 mergeProviderSnapshot(previousProvider, refreshedProvider).slashCommands,
924 previousProvider.slashCommands,
925 );
926 assert.deepStrictEqual(
927 mergeProviderSnapshot(previousProvider, refreshedProvider).skills,
928 previousProvider.skills,
929 );
930 });
649931
650 const pendingSnapshot = {
651 ...scopedSnapshot,
652 slashCommands: [COMPACT_SLASH_COMMAND],
653 slashCommandsPending: true,
654 } satisfies ProviderWorkspaceSnapshot;
655 const partial = upsertProviderWorkspaceSnapshot(result, "/project", pendingSnapshot);
656 assert.deepStrictEqual(partial.workspaceSnapshots?.[0]?.slashCommands, [
657 { name: "project" },
658 ]);
659 assert.strictEqual(partial.workspaceSnapshots?.[0]?.slashCommandsPending, true);
660 const otherProject = upsertProviderWorkspaceSnapshot(
661 partial,
662 "/other-project",
663 pendingSnapshot,
664 );
665 assert.deepStrictEqual(otherProject.workspaceSnapshots?.[1]?.slashCommands, [
666 COMPACT_SLASH_COMMAND,
667 ]);
668 const recovered = upsertProviderWorkspaceSnapshot(partial, "/project", {
669 ...scopedSnapshot,
670 slashCommands: [COMPACT_SLASH_COMMAND, { name: "replacement" }],
671 });
672 assert.deepStrictEqual(recovered.workspaceSnapshots?.[0]?.slashCommands, [
673 COMPACT_SLASH_COMMAND,
674 { name: "replacement" },
675 ]);
676 assert.strictEqual(recovered.workspaceSnapshots?.[0]?.slashCommandsPending, undefined);
677 });
932 it("classifies pending, logout, uninstall, and reconnect OpenCode inventories", () => {
933 const previousProvider = {
934 instanceId: ProviderInstanceId.make("opencode"),
935 driver: ProviderDriverKind.make("opencode"),
936 status: "ready",
937 enabled: true,
938 installed: true,
939 auth: { status: "authenticated" },
940 checkedAt: "2026-07-17T00:00:00.000Z",
941 version: "1.0.0",
942 models: [
943 {
944 slug: "github/gpt-5",
945 name: "GPT-5",
946 subProvider: "GitHub",
947 isCustom: false,
948 capabilities: null,
949 },
950 {
951 slug: "removed-plugin/model",
952 name: "Removed Plugin Model",
953 subProvider: "Removed Plugin",
954 isCustom: false,
955 capabilities: null,
956 },
957 ],
958 slashCommands: [{ name: "review", description: "Review changes" }],
959 skills: [
960 {
961 name: "typescript",
962 description: "TypeScript help",
963 path: "/skills/typescript/SKILL.md",
964 enabled: true,
965 },
966 ],
967 } as const satisfies ServerProvider;
968 const pendingProvider = {
969 ...previousProvider,
970 status: "warning",
971 installed: false,
972 auth: { status: "unknown" },
973 checkedAt: "2026-07-17T00:01:00.000Z",
974 version: null,
975 models: [],
976 message: "OpenCode provider status has not been checked in this session yet.",
977 } satisfies ServerProvider;
978 const loggedOutProvider = {
979 ...previousProvider,
980 status: "warning",
981 auth: { status: "unknown" },
982 checkedAt: "2026-07-17T00:02:00.000Z",
983 models: [],
984 slashCommands: [],
985 skills: [],
986 message: "OpenCode is available, but it did not report any connected upstream providers.",
987 } satisfies ServerProvider;
988 const missingProvider = {
989 ...previousProvider,
990 status: "error",
991 installed: false,
992 auth: { status: "unknown" },
993 checkedAt: "2026-07-17T00:03:00.000Z",
994 version: null,
995 models: [],
996 message: "OpenCode CLI (`opencode`) is not installed or not on PATH.",
997 } satisfies ServerProvider;
998 const authoritativeProvider = {
999 ...previousProvider,
1000 checkedAt: "2026-07-17T00:04:00.000Z",
1001 models: [previousProvider.models[0]!],
1002 } satisfies ServerProvider;
1003 const failedProvider = {
1004 ...authoritativeProvider,
1005 status: "error",
1006 auth: { status: "unknown" },
1007 checkedAt: "2026-07-17T00:05:00.000Z",
1008 models: [],
1009 message: "Failed to refresh OpenCode models.",
1010 } satisfies ServerProvider;
1011
1012 assert.deepStrictEqual(mergeProviderSnapshot(previousProvider, pendingProvider).models, [
1013 ...previousProvider.models,
1014 ]);
1015 assert.deepStrictEqual(mergeProviderSnapshot(previousProvider, loggedOutProvider).models, []);
1016 assert.deepStrictEqual(
1017 mergeProviderSnapshot(previousProvider, loggedOutProvider).slashCommands,
1018 [],
1019 );
1020 assert.deepStrictEqual(mergeProviderSnapshot(previousProvider, loggedOutProvider).skills, []);
1021 assert.deepStrictEqual(mergeProviderSnapshot(previousProvider, missingProvider).models, []);
6781022
679 it("preserves previously discovered provider models when a refresh returns none", () => {
680 const previousProvider = {
681 instanceId: ProviderInstanceId.make("cursor"),
682 driver: ProviderDriverKind.make("cursor"),
683 status: "ready",
684 enabled: true,
685 installed: true,
686 auth: { status: "authenticated" },
687 checkedAt: "2026-04-14T00:00:00.000Z",
688 version: "2026.04.09-f2b0fcd",
689 models: [
690 {
691 slug: "claude-opus-4-6",
692 name: "Opus 4.6",
693 isCustom: false,
694 capabilities: createModelCapabilities({
695 optionDescriptors: [
696 selectDescriptor("reasoning", "Reasoning", [
697 { id: "high", label: "High", isDefault: true },
698 ]),
699 booleanDescriptor("fastMode", "Fast Mode"),
700 booleanDescriptor("thinking", "Thinking"),
701 ],
702 }),
703 },
704 ],
705 slashCommands: [{ name: "review", description: "Review changes" }],
706 skills: [
707 {
708 name: "typescript",
709 description: "TypeScript help",
710 path: "/skills/typescript/SKILL.md",
711 enabled: true,
712 },
713 ],
714 } as const satisfies ServerProvider;
715 const refreshedProvider = {
716 ...previousProvider,
717 checkedAt: "2026-04-14T00:01:00.000Z",
718 models: [],
719 slashCommands: [],
720 skills: [],
721 } satisfies ServerProvider;
1023 const afterRemoval = mergeProviderSnapshot(previousProvider, authoritativeProvider);
1024 const afterFailure = mergeProviderSnapshot(afterRemoval, failedProvider);
7221025
723 assert.deepStrictEqual(mergeProviderSnapshot(previousProvider, refreshedProvider).models, [
724 ...previousProvider.models,
725 ]);
726 assert.deepStrictEqual(
727 mergeProviderSnapshot(previousProvider, refreshedProvider).slashCommands,
728 [],
729 );
730 assert.deepStrictEqual(
731 mergeProviderSnapshot(previousProvider, refreshedProvider).skills,
732 [],
733 );
1026 assert.deepStrictEqual(afterFailure.models, [authoritativeProvider.models[0]!]);
1027 });
1028
1029 describe("Codex model inventories", () => {
1030 const cachedProvider = {
1031 instanceId: ProviderInstanceId.make("codex-personal"),
1032 driver: ProviderDriverKind.make("codex"),
1033 status: "ready",
1034 enabled: true,
1035 installed: true,
1036 auth: { status: "authenticated" },
1037 checkedAt: "2026-09-04T19:00:00.000Z",
1038 version: "0.153.3",
1039 models: ["vega-alpha", "joule-alpha", "kindle-alpha", "ultima-alpha", "solstice-alpha"].map(
1040 (slug) => ({ slug, name: slug, isCustom: false, capabilities: null }),
1041 ),
1042 slashCommands: [],
1043 skills: [],
1044 } satisfies ServerProvider;
1045 const customModel = {
1046 slug: "custom-model",
1047 name: "Custom model",
1048 isCustom: true,
1049 capabilities: null,
1050 } as const;
1051 const refreshedProvider = {
1052 ...cachedProvider,
1053 checkedAt: "2026-09-04T19:01:00.000Z",
1054 models: [
1055 { slug: "gpt-6-astra", name: "GPT 6 Astra", isCustom: false, capabilities: null },
1056 cachedProvider.models[0]!,
1057 customModel,
1058 ],
1059 } satisfies ServerProvider;
1060 const pendingProvider = {
1061 ...cachedProvider,
1062 status: "warning",
1063 installed: false,
1064 auth: { status: "unknown" },
1065 models: [customModel],
1066 } satisfies ServerProvider;
1067 const failedProvider = {
1068 ...pendingProvider,
1069 checkedAt: "2026-09-04T19:02:00.000Z",
1070 status: "error",
1071 installed: true,
1072 } satisfies ServerProvider;
1073
1074 it("drops retired alpha models after discovery, including without OpenAI authentication", () => {
1075 for (const authStatus of ["authenticated", "unknown"] as const) {
1076 assert.deepStrictEqual(
1077 mergeProviderSnapshot(cachedProvider, {
1078 ...refreshedProvider,
1079 auth: { status: authStatus },
1080 }).models,
1081 refreshedProvider.models,
1082 );
1083 }
7341084 });
7351085
736 it("drops custom models the refreshed snapshot no longer carries", () => {
737 const previousProvider = {
738 instanceId: ProviderInstanceId.make("claudeAgent"),
739 driver: ProviderDriverKind.make("claudeAgent"),
740 status: "ready",
741 enabled: true,
742 installed: true,
743 auth: { status: "authenticated" },
744 checkedAt: "2026-04-14T00:00:00.000Z",
745 version: "2.1.0",
746 models: [
747 {
748 slug: "claude-sonnet-4-6",
749 name: "Sonnet 4.6",
750 isCustom: false,
751 capabilities: null,
752 },
753 {
754 slug: "removed-custom",
755 name: "removed-custom",
756 isCustom: true,
757 capabilities: null,
758 },
759 ],
760 slashCommands: [],
761 skills: [],
762 } as const satisfies ServerProvider;
763 const refreshedProvider = {
764 ...previousProvider,
765 checkedAt: "2026-04-14T00:01:00.000Z",
766 models: [previousProvider.models[0]],
767 } satisfies ServerProvider;
768
769 assert.deepStrictEqual(mergeProviderSnapshot(previousProvider, refreshedProvider).models, [
770 ...refreshedProvider.models,
771 ]);
772 });
773
774 it("drops stale ACP Registry models missing from a completed discovery probe", () => {
775 const previousProvider = {
776 instanceId: ProviderInstanceId.make("acpRegistry_codex"),
777 driver: ProviderDriverKind.make("acpRegistry"),
778 status: "ready",
779 enabled: true,
780 installed: true,
781 auth: { status: "authenticated" },
782 checkedAt: "2026-08-13T00:00:00.000Z",
783 version: "1.2.0",
784 models: [
785 { slug: "gpt-5.6-sol", name: "GPT-5.6-Sol", isCustom: false, capabilities: null },
786 {
787 slug: "gpt-5.6-sol[low]",
788 name: "GPT-5.6-Sol (low)",
789 isCustom: false,
790 capabilities: null,
791 },
792 { slug: "default", name: "Default", isCustom: false, capabilities: null },
793 ],
794 slashCommands: [],
795 skills: [],
796 } as const satisfies ServerProvider;
797 const refreshedProvider = {
798 ...previousProvider,
799 checkedAt: "2026-08-13T00:01:00.000Z",
800 models: [
801 { slug: "gpt-5.6-sol", name: "GPT-5.6-Sol", isCustom: false, capabilities: null },
802 ],
803 } satisfies ServerProvider;
804
805 assert.deepStrictEqual(mergeProviderSnapshot(previousProvider, refreshedProvider).models, [
806 ...refreshedProvider.models,
807 ]);
808 });
809
810 it("retains ACP Registry models while discovery has not completed", () => {
811 const previousProvider = {
812 instanceId: ProviderInstanceId.make("acpRegistry_codex"),
813 driver: ProviderDriverKind.make("acpRegistry"),
814 status: "ready",
815 enabled: true,
816 installed: true,
817 auth: { status: "authenticated" },
818 checkedAt: "2026-08-13T00:00:00.000Z",
819 version: "1.2.0",
820 models: [
821 { slug: "gpt-5.6-sol", name: "GPT-5.6-Sol", isCustom: false, capabilities: null },
822 ],
823 slashCommands: [],
824 skills: [],
825 } as const satisfies ServerProvider;
826 const checkingProvider = {
827 ...previousProvider,
828 checkedAt: "2026-08-13T00:01:00.000Z",
829 auth: { status: "unknown" },
830 models: [{ slug: "default", name: "Default", isCustom: false, capabilities: null }],
831 } satisfies ServerProvider;
832 const failedProbeProvider = {
833 ...checkingProvider,
834 status: "warning",
835 } satisfies ServerProvider;
836
837 assert.deepStrictEqual(mergeProviderSnapshot(previousProvider, checkingProvider).models, [
838 { slug: "default", name: "Default", isCustom: false, capabilities: null },
839 { slug: "gpt-5.6-sol", name: "GPT-5.6-Sol", isCustom: false, capabilities: null },
840 ]);
841 assert.deepStrictEqual(
842 mergeProviderSnapshot(previousProvider, failedProbeProvider).models,
843 [
844 { slug: "default", name: "Default", isCustom: false, capabilities: null },
845 { slug: "gpt-5.6-sol", name: "GPT-5.6-Sol", isCustom: false, capabilities: null },
846 ],
847 );
848 });
849
850 it("drops stale OpenCode models missing from a successful refresh", () => {
851 const previousProvider = {
852 instanceId: ProviderInstanceId.make("opencode"),
853 driver: ProviderDriverKind.make("opencode"),
854 status: "ready",
855 enabled: true,
856 installed: true,
857 auth: { status: "authenticated" },
858 checkedAt: "2026-07-17T00:00:00.000Z",
859 version: "1.0.0",
860 models: [
861 {
862 slug: "github/gpt-5",
863 name: "GPT-5",
864 subProvider: "GitHub",
865 isCustom: false,
866 capabilities: null,
867 },
868 {
869 slug: "removed-plugin/model",
870 name: "Removed Plugin Model",
871 subProvider: "Removed Plugin",
872 isCustom: false,
873 capabilities: null,
874 },
875 ],
876 slashCommands: [],
877 skills: [],
878 } as const satisfies ServerProvider;
879 const refreshedProvider = {
880 ...previousProvider,
881 checkedAt: "2026-07-17T00:01:00.000Z",
882 models: [
883 {
884 slug: "github/gpt-5",
885 name: "GPT-5",
886 subProvider: "GitHub",
887 isCustom: false,
888 capabilities: null,
889 },
890 ],
891 } satisfies ServerProvider;
892
893 assert.deepStrictEqual(mergeProviderSnapshot(previousProvider, refreshedProvider).models, [
894 ...refreshedProvider.models,
895 ]);
896 });
897
898 it("retains stale OpenCode models when a refresh fails", () => {
899 const previousProvider = {
900 instanceId: ProviderInstanceId.make("opencode"),
901 driver: ProviderDriverKind.make("opencode"),
902 status: "ready",
903 enabled: true,
904 installed: true,
905 auth: { status: "authenticated" },
906 checkedAt: "2026-07-17T00:00:00.000Z",
907 version: "1.0.0",
908 models: [
909 {
910 slug: "github/gpt-5",
911 name: "GPT-5",
912 subProvider: "GitHub",
913 isCustom: false,
914 capabilities: null,
915 },
916 ],
917 slashCommands: [{ name: "review", description: "Review changes" }],
918 skills: [
919 {
920 name: "typescript",
921 description: "TypeScript help",
922 path: "/skills/typescript/SKILL.md",
923 enabled: true,
924 },
925 ],
926 } as const satisfies ServerProvider;
927 const refreshedProvider = {
928 ...previousProvider,
929 status: "error",
930 auth: { status: "unknown" },
931 checkedAt: "2026-07-17T00:01:00.000Z",
932 models: [],
933 message: "Failed to refresh OpenCode models.",
934 } satisfies ServerProvider;
935
936 assert.deepStrictEqual(mergeProviderSnapshot(previousProvider, refreshedProvider).models, [
937 ...previousProvider.models,
938 ]);
939 assert.deepStrictEqual(
940 mergeProviderSnapshot(previousProvider, refreshedProvider).slashCommands,
941 previousProvider.slashCommands,
942 );
943 assert.deepStrictEqual(
944 mergeProviderSnapshot(previousProvider, refreshedProvider).skills,
945 previousProvider.skills,
946 );
947 });
948
949 it("classifies pending, logout, uninstall, and reconnect OpenCode inventories", () => {
950 const previousProvider = {
951 instanceId: ProviderInstanceId.make("opencode"),
952 driver: ProviderDriverKind.make("opencode"),
953 status: "ready",
954 enabled: true,
955 installed: true,
956 auth: { status: "authenticated" },
957 checkedAt: "2026-07-17T00:00:00.000Z",
958 version: "1.0.0",
959 models: [
960 {
961 slug: "github/gpt-5",
962 name: "GPT-5",
963 subProvider: "GitHub",
964 isCustom: false,
965 capabilities: null,
966 },
967 {
968 slug: "removed-plugin/model",
969 name: "Removed Plugin Model",
970 subProvider: "Removed Plugin",
971 isCustom: false,
972 capabilities: null,
973 },
974 ],
975 slashCommands: [{ name: "review", description: "Review changes" }],
976 skills: [
977 {
978 name: "typescript",
979 description: "TypeScript help",
980 path: "/skills/typescript/SKILL.md",
981 enabled: true,
982 },
983 ],
984 } as const satisfies ServerProvider;
985 const pendingProvider = {
986 ...previousProvider,
987 status: "warning",
988 installed: false,
989 auth: { status: "unknown" },
990 checkedAt: "2026-07-17T00:01:00.000Z",
991 version: null,
992 models: [],
993 message: "OpenCode provider status has not been checked in this session yet.",
994 } satisfies ServerProvider;
995 const loggedOutProvider = {
996 ...previousProvider,
997 status: "warning",
998 auth: { status: "unknown" },
999 checkedAt: "2026-07-17T00:02:00.000Z",
1000 models: [],
1001 slashCommands: [],
1002 skills: [],
1003 message: "OpenCode is available, but it did not report any connected upstream providers.",
1004 } satisfies ServerProvider;
1005 const missingProvider = {
1006 ...previousProvider,
1007 status: "error",
1008 installed: false,
1009 auth: { status: "unknown" },
1010 checkedAt: "2026-07-17T00:03:00.000Z",
1011 version: null,
1012 models: [],
1013 message: "OpenCode CLI (`opencode`) is not installed or not on PATH.",
1014 } satisfies ServerProvider;
1015 const authoritativeProvider = {
1016 ...previousProvider,
1017 checkedAt: "2026-07-17T00:04:00.000Z",
1018 models: [previousProvider.models[0]!],
1019 } satisfies ServerProvider;
1020 const failedProvider = {
1021 ...authoritativeProvider,
1022 status: "error",
1023 auth: { status: "unknown" },
1024 checkedAt: "2026-07-17T00:05:00.000Z",
1025 models: [],
1026 message: "Failed to refresh OpenCode models.",
1027 } satisfies ServerProvider;
1028
1029 assert.deepStrictEqual(mergeProviderSnapshot(previousProvider, pendingProvider).models, [
1030 ...previousProvider.models,
1031 ]);
1032 assert.deepStrictEqual(
1033 mergeProviderSnapshot(previousProvider, loggedOutProvider).models,
1034 [],
1035 );
1036 assert.deepStrictEqual(
1037 mergeProviderSnapshot(previousProvider, loggedOutProvider).slashCommands,
1038 [],
1039 );
1040 assert.deepStrictEqual(
1041 mergeProviderSnapshot(previousProvider, loggedOutProvider).skills,
1042 [],
1043 );
1044 assert.deepStrictEqual(mergeProviderSnapshot(previousProvider, missingProvider).models, []);
1045
1046 const afterRemoval = mergeProviderSnapshot(previousProvider, authoritativeProvider);
1047 const afterFailure = mergeProviderSnapshot(afterRemoval, failedProvider);
1048
1049 assert.deepStrictEqual(afterFailure.models, [authoritativeProvider.models[0]!]);
1050 });
1051
1052 describe("Codex model inventories", () => {
1053 const cachedProvider = {
1054 instanceId: ProviderInstanceId.make("codex-personal"),
1055 driver: ProviderDriverKind.make("codex"),
1056 status: "ready",
1057 enabled: true,
1058 installed: true,
1059 auth: { status: "authenticated" },
1060 checkedAt: "2026-09-04T19:00:00.000Z",
1061 version: "0.153.3",
1062 models: [
1063 "vega-alpha",
1064 "joule-alpha",
1065 "kindle-alpha",
1066 "ultima-alpha",
1067 "solstice-alpha",
1068 ].map((slug) => ({ slug, name: slug, isCustom: false, capabilities: null })),
1069 slashCommands: [],
1070 skills: [],
1071 } satisfies ServerProvider;
1072 const customModel = {
1073 slug: "custom-model",
1074 name: "Custom model",
1075 isCustom: true,
1076 capabilities: null,
1077 } as const;
1078 const refreshedProvider = {
1079 ...cachedProvider,
1080 checkedAt: "2026-09-04T19:01:00.000Z",
1081 models: [
1082 { slug: "gpt-6-astra", name: "GPT 6 Astra", isCustom: false, capabilities: null },
1083 cachedProvider.models[0]!,
1084 customModel,
1085 ],
1086 } satisfies ServerProvider;
1087 const pendingProvider = {
1088 ...cachedProvider,
1089 status: "warning",
1090 installed: false,
1091 auth: { status: "unknown" },
1092 models: [customModel],
1093 } satisfies ServerProvider;
1094 const failedProvider = {
1095 ...pendingProvider,
1096 checkedAt: "2026-09-04T19:02:00.000Z",
1097 status: "error",
1098 installed: true,
1099 } satisfies ServerProvider;
1100
1101 it("drops retired alpha models after discovery, including without OpenAI authentication", () => {
1102 for (const authStatus of ["authenticated", "unknown"] as const) {
1103 assert.deepStrictEqual(
1104 mergeProviderSnapshot(cachedProvider, {
1105 ...refreshedProvider,
1106 auth: { status: authStatus },
1107 }).models,
1108 refreshedProvider.models,
1109 );
1110 }
1111 });
1112
1113 it("keeps discovered models during startup and failed probes without restoring removed custom models", () => {
1114 for (const provider of [pendingProvider, failedProvider]) {
1115 assert.deepStrictEqual(
1116 mergeProviderSnapshot(
1117 {
1118 ...cachedProvider,
1119 models: [...cachedProvider.models, { ...customModel, slug: "removed-custom" }],
1120 },
1121 provider,
1122 ).models,
1123 [customModel, ...cachedProvider.models],
1124 );
1125 }
1126 });
1127
1128 it("clears discovered models after sign-out, disable, uninstall, or empty discovery", () => {
1129 const emptyProvider = { ...refreshedProvider, models: [customModel] };
1130 const clearedProviders = [
1131 { ...emptyProvider, status: "error", auth: { status: "unauthenticated" } },
1132 { ...emptyProvider, status: "disabled", enabled: false },
1133 { ...emptyProvider, status: "error", installed: false, auth: { status: "unknown" } },
1134 emptyProvider,
1135 { ...emptyProvider, models: [] },
1136 ] satisfies ReadonlyArray<ServerProvider>;
1137
1138 for (const provider of clearedProviders) {
1139 assert.deepStrictEqual(
1140 mergeProviderSnapshot(cachedProvider, provider).models,
1141 provider.models,
1142 );
1143 }
1144 });
1145
1146 it.effect("persists removals across failed refreshes and registry restarts", () =>
1147 Effect.gen(function* () {
1148 const config = yield* ServerConfig.ServerConfig;
1149 const filePath = yield* resolveProviderStatusCachePath({
1150 cacheDir: config.providerStatusCacheDir,
1151 instanceId: cachedProvider.instanceId,
1152 });
1153 yield* writeProviderStatusCache({ filePath, provider: cachedProvider });
1154 const nextProvider = yield* Ref.make<ServerProvider>(refreshedProvider);
1155 const instance = {
1156 instanceId: cachedProvider.instanceId,
1157 driverKind: cachedProvider.driver,
1158 continuationIdentity: {
1159 driverKind: cachedProvider.driver,
1160 continuationKey: "codex:instance:codex-personal",
1161 },
1162 displayName: undefined,
1163 enabled: true,
1164 snapshot: {
1165 resolveMaintenance: () =>
1166 Effect.succeed(
1167 makeManualOnlyProviderMaintenanceCapabilities({
1168 provider: cachedProvider.driver,
1169 packageName: null,
1170 }),
1171 ),
1172 getSnapshot: Effect.succeed(pendingProvider),
1173 refresh: Ref.get(nextProvider),
1174 streamChanges: Stream.empty,
1175 applyUsageLimits: () => Effect.void,
1176 },
1177 orchestrationAdapter: {} as ProviderInstance["orchestrationAdapter"],
1178 textGeneration: {} as ProviderInstance["textGeneration"],
1179 } satisfies ProviderInstance;
1180 const instanceRegistryLayer = Layer.succeed(
1181 ProviderInstanceRegistry.ProviderInstanceRegistry,
1182 {
1183 getInstance: (id) =>
1184 Effect.succeed(id === instance.instanceId ? instance : undefined),
1185 listInstances: Effect.succeed([instance]),
1186 listUnavailable: Effect.succeed([]),
1187 streamChanges: Stream.empty,
1188 subscribeChanges: Effect.flatMap(PubSub.unbounded<void>(), PubSub.subscribe),
1189 },
1190 );
1191 const retainedModels = [
1192 customModel,
1193 ...refreshedProvider.models.filter((model) => !model.isCustom),
1194 ];
1195
1196 for (const restarted of [false, true]) {
1197 yield* Effect.gen(function* () {
1198 const registry = yield* ProviderRegistry.ProviderRegistry;
1199 const expectedModels = restarted
1200 ? retainedModels
1201 : [customModel, ...cachedProvider.models];
1202 assert.deepStrictEqual((yield* registry.getProviders)[0]?.models, expectedModels);
1203
1204 yield* registry.refreshInstance(instance.instanceId);
1205 assert.deepStrictEqual(
1206 (yield* readProviderStatusCache(filePath))?.models,
1207 restarted ? retainedModels : refreshedProvider.models,
1208 );
1209
1210 yield* Ref.set(nextProvider, failedProvider);
1211 const afterFailure = yield* registry.refreshInstance(instance.instanceId);
1212 assert.deepStrictEqual(afterFailure[0]?.models, retainedModels);
1213 assert.deepStrictEqual(
1214 (yield* readProviderStatusCache(filePath))?.models,
1215 retainedModels,
1216 );
1217 }).pipe(
1218 Effect.provide(ProviderRegistryLive.pipe(Layer.provide(instanceRegistryLayer))),
1219 Effect.scoped,
1220 );
1221 }
1222 }).pipe(
1223 Effect.provide(
1224 ServerConfig.layerTest(process.cwd(), {
1225 prefix: "t3-codex-retired-model-cache-",
1226 }).pipe(Layer.provideMerge(NodeServices.layer)),
1227 ),
1228 ),
1229 );
1230 });
1231
1232 describe("Antigravity model inventories", () => {
1233 const previousProvider = {
1234 instanceId: ProviderInstanceId.make("antigravity-personal"),
1235 driver: ProviderDriverKind.make("antigravity"),
1236 status: "ready",
1237 enabled: true,
1238 installed: true,
1239 auth: { status: "authenticated" },
1240 checkedAt: "2026-09-02T00:00:00.000Z",
1241 version: "0.1.3",
1242 models: [
1243 {
1244 slug: "gemini-3.1-pro-high",
1245 name: "Gemini 3.1 Pro High",
1246 isCustom: false,
1247 capabilities: null,
1248 },
1249 {
1250 slug: "gemini-3-flash",
1251 name: "Gemini 3 Flash",
1252 isCustom: false,
1253 capabilities: null,
1254 },
1255 ],
1256 slashCommands: [],
1257 skills: [],
1258 } as const satisfies ServerProvider;
1259
1260 it("removes unavailable models after a successful refresh", () => {
1261 for (const status of ["ready", "warning"] as const) {
1262 const refreshedProvider = {
1263 ...previousProvider,
1264 status,
1265 checkedAt: "2026-09-02T00:01:00.000Z",
1266 models: [previousProvider.models[1]],
1267 } satisfies ServerProvider;
1268 const afterRefresh = mergeProviderSnapshot(previousProvider, refreshedProvider);
1269
1270 assert.deepStrictEqual(afterRefresh.models, refreshedProvider.models);
1271
1272 const afterFailure = mergeProviderSnapshot(afterRefresh, {
1273 ...refreshedProvider,
1274 status: "error",
1275 auth: { status: "unknown" },
1276 models: [],
1277 });
1278 assert.deepStrictEqual(afterFailure.models, refreshedProvider.models);
1279 }
1280 });
1281
1282 it("keeps cached models during health checks and temporary failures", () => {
1283 for (const installed of [false, true]) {
1284 const pendingProvider = {
1285 ...previousProvider,
1286 status: "warning",
1287 installed,
1288 auth: { status: "unknown" },
1289 checkedAt: "2026-09-02T00:01:00.000Z",
1290 version: installed ? previousProvider.version : null,
1291 models: [],
1292 } satisfies ServerProvider;
1293
1294 assert.deepStrictEqual(
1295 mergeProviderSnapshot(previousProvider, pendingProvider).models,
1296 previousProvider.models,
1297 );
1298 }
1299
1300 for (const authStatus of ["unknown", "authenticated"] as const) {
1301 const failedProvider = {
1302 ...previousProvider,
1303 status: "error",
1304 auth: { status: authStatus },
1305 checkedAt: "2026-09-02T00:02:00.000Z",
1306 models: [],
1307 } satisfies ServerProvider;
1308
1309 assert.deepStrictEqual(
1310 mergeProviderSnapshot(previousProvider, failedProvider).models,
1311 previousProvider.models,
1312 );
1313 }
1314 });
1315
1316 it("clears models after sign-out, disable, uninstall, or an empty successful refresh", () => {
1317 const emptyProvider = {
1318 ...previousProvider,
1319 checkedAt: "2026-09-02T00:01:00.000Z",
1320 models: [],
1321 } satisfies ServerProvider;
1322 const clearedProviders = [
1323 { ...emptyProvider, status: "warning", auth: { status: "unauthenticated" } },
1324 { ...emptyProvider, status: "error", auth: { status: "unauthenticated" } },
1325 { ...emptyProvider, status: "disabled", enabled: false },
1326 { ...emptyProvider, status: "error", enabled: false },
1327 { ...emptyProvider, status: "error", installed: false, auth: { status: "unknown" } },
1328 emptyProvider,
1329 ] satisfies ReadonlyArray<ServerProvider>;
1330
1331 for (const provider of clearedProviders) {
1332 const afterRemoval = mergeProviderSnapshot(previousProvider, provider);
1333 assert.deepStrictEqual(afterRemoval.models, []);
1334
1335 const afterFailure = mergeProviderSnapshot(afterRemoval, {
1336 ...emptyProvider,
1337 status: "error",
1338 auth: { status: "unknown" },
1339 });
1340 assert.deepStrictEqual(afterFailure.models, []);
1341 }
1342 });
1343 });
1344
1345 describe("Antigravity saved account", () => {
1346 const signedIn = {
1347 instanceId: ProviderInstanceId.make("antigravity-personal"),
1348 driver: ProviderDriverKind.make("antigravity"),
1349 status: "ready",
1350 enabled: true,
1351 installed: true,
1352 auth: { status: "authenticated", type: "oauth-personal", label: "Google account" },
1353 checkedAt: "2026-09-05T00:00:00.000Z",
1354 version: "agy_acp_server_1.1.1",
1355 models: [
1356 {
1357 slug: "gemini-3.7-flash-high",
1358 name: "Gemini 3.7 Flash",
1359 isCustom: false,
1360 capabilities: null,
1361 },
1362 ],
1363 slashCommands: [{ name: "plan" }],
1364 skills: [],
1365 } as const satisfies ServerProvider;
1366 const uncheckedMessage =
1367 "Antigravity is installed. Google account access is not checked yet.";
1368 const restartProbe = {
1369 ...signedIn,
1370 status: "warning",
1371 auth: { status: "unknown" },
1372 checkedAt: "2026-09-05T00:01:00.000Z",
1373 message: uncheckedMessage,
1374 models: [],
1375 } as const satisfies ServerProvider;
1376
1377 it("keeps the saved Google account through restart health checks", () => {
1378 const merged = mergeProviderSnapshot(signedIn, restartProbe);
1379 const { message: _uncheckedMessage, ...probeWithoutMessage } = restartProbe;
1380 assert.deepStrictEqual(merged, {
1381 ...probeWithoutMessage,
1382 status: "ready",
1383 auth: signedIn.auth,
1384 models: signedIn.models,
1385 });
1386 assert.equal("message" in merged, false);
1387 // The next periodic probe reads the merged snapshot as its previous state.
1388 assert.deepStrictEqual(mergeProviderSnapshot(merged, restartProbe), merged);
1389 });
1390
1391 it("carries the account through the boot probe and a failed probe without hiding them", () => {
1392 const booting = {
1393 ...restartProbe,
1394 installed: false,
1395 version: null,
1396 message: "Checking Antigravity availability.",
1397 } satisfies ServerProvider;
1398 assert.deepStrictEqual(mergeProviderSnapshot(signedIn, booting), {
1399 ...booting,
1400 auth: signedIn.auth,
1401 models: signedIn.models,
1402 });
1403
1404 const failed = {
1405 ...restartProbe,
1406 status: "error",
1407 message: "Antigravity did not respond to its local health check within 90 seconds.",
1408 } satisfies ServerProvider;
1409 assert.deepStrictEqual(mergeProviderSnapshot(signedIn, failed), {
1410 ...failed,
1411 auth: signedIn.auth,
1412 models: signedIn.models,
1413 });
1414 });
1415
1416 it("does not invent an account after sign-out, disable, uninstall, or for other providers", () => {
1417 const untouched = [
1418 { ...restartProbe, auth: { status: "unauthenticated" } },
1419 { ...restartProbe, status: "disabled", enabled: false },
1420 { ...restartProbe, status: "error", installed: false },
1421 { ...restartProbe, driver: ProviderDriverKind.make("codex") },
1422 // The instance was rebuilt with another sign-in method.
1423 { ...restartProbe, auth: { status: "unknown", type: "gemini-api-key" } },
1424 ] satisfies ReadonlyArray<ServerProvider>;
1425 for (const next of untouched) {
1426 const merged = mergeProviderSnapshot(signedIn, next);
1427 assert.deepStrictEqual(merged.auth, next.auth);
1428 assert.equal(merged.status, next.status);
1429 assert.equal(merged.message, next.message);
1430 }
1431 assert.deepStrictEqual(
1432 mergeProviderSnapshot({ ...signedIn, auth: { status: "unknown" } }, restartProbe).auth,
1433 { status: "unknown" },
1434 );
1435 assert.equal(
1436 mergeProviderSnapshot(
1437 { ...signedIn, driver: ProviderDriverKind.make("codex") },
1438 restartProbe,
1439 ).auth.status,
1440 "unknown",
1441 );
1442 assert.deepStrictEqual(
1443 mergeProviderSnapshot(signedIn, {
1444 ...restartProbe,
1445 auth: { status: "unknown", type: "oauth-personal" },
1446 }).auth,
1447 signedIn.auth,
1448 );
1449 });
1450 });
1451
1452 it("fills missing capabilities from the previous provider snapshot", () => {
1453 const previousProvider = {
1454 instanceId: ProviderInstanceId.make("cursor"),
1455 driver: ProviderDriverKind.make("cursor"),
1456 status: "ready",
1457 enabled: true,
1458 installed: true,
1459 auth: { status: "authenticated" },
1460 checkedAt: "2026-04-14T00:00:00.000Z",
1461 version: "2026.04.09-f2b0fcd",
1462 models: [
1463 {
1464 slug: "claude-opus-4-6",
1465 name: "Opus 4.6",
1466 isCustom: false,
1467 capabilities: createModelCapabilities({
1468 optionDescriptors: [
1469 selectDescriptor("reasoning", "Reasoning", [
1470 { id: "high", label: "High", isDefault: true },
1471 ]),
1472 booleanDescriptor("fastMode", "Fast Mode"),
1473 booleanDescriptor("thinking", "Thinking"),
1474 ],
1475 }),
1476 },
1477 ],
1478 slashCommands: [],
1479 skills: [],
1480 } as const satisfies ServerProvider;
1481 const refreshedProvider = {
1482 ...previousProvider,
1483 checkedAt: "2026-04-14T00:01:00.000Z",
1484 models: [
1485 {
1486 slug: "claude-opus-4-6",
1487 name: "Opus 4.6",
1488 isCustom: false,
1489 capabilities: createModelCapabilities({
1490 optionDescriptors: [],
1491 }),
1492 },
1493 ],
1494 } satisfies ServerProvider;
1495
1496 assert.deepStrictEqual(mergeProviderSnapshot(previousProvider, refreshedProvider).models, [
1497 ...previousProvider.models,
1498 ]);
1499 });
1500
1501 it.effect("does not run provider probes during layer construction", () =>
1502 Effect.gen(function* () {
1503 const codexDriver = ProviderDriverKind.make("codex");
1504 const codexInstanceId = ProviderInstanceId.make("codex");
1505 const initialProvider = {
1506 instanceId: codexInstanceId,
1507 driver: codexDriver,
1508 status: "warning",
1509 enabled: true,
1510 installed: false,
1511 auth: { status: "unknown" },
1512 checkedAt: "2026-06-10T00:00:00.000Z",
1513 version: null,
1514 message: "Checking Codex provider status.",
1515 models: [],
1516 slashCommands: [],
1517 skills: [],
1518 } as const satisfies ServerProvider;
1519 const refreshCalls = yield* Ref.make(0);
1520 const instance = {
1521 instanceId: codexInstanceId,
1522 driverKind: codexDriver,
1523 continuationIdentity: {
1524 driverKind: codexDriver,
1525 continuationKey: "codex:instance:codex",
1526 },
1527 displayName: undefined,
1528 enabled: true,
1529 snapshot: {
1530 resolveMaintenance: () =>
1531 Effect.succeed(
1532 makeManualOnlyProviderMaintenanceCapabilities({
1533 provider: codexDriver,
1534 packageName: null,
1535 }),
1536 ),
1537 getSnapshot: Effect.succeed(initialProvider),
1538 refresh: Ref.update(refreshCalls, (count) => count + 1).pipe(
1539 Effect.andThen(Effect.never),
1540 ),
1541 streamChanges: Stream.empty,
1542 applyUsageLimits: () => Effect.void,
1543 },
1544 orchestrationAdapter: {} as ProviderInstance["orchestrationAdapter"],
1545 textGeneration: {} as ProviderInstance["textGeneration"],
1546 } satisfies ProviderInstance;
1547 const instanceRegistryLayer = Layer.succeed(
1548 ProviderInstanceRegistry.ProviderInstanceRegistry,
1549 {
1550 getInstance: (instanceId) =>
1551 Effect.succeed(instanceId === codexInstanceId ? instance : undefined),
1552 listInstances: Effect.succeed([instance]),
1553 listUnavailable: Effect.succeed([]),
1554 streamChanges: Stream.empty,
1555 subscribeChanges: Effect.flatMap(PubSub.unbounded<void>(), PubSub.subscribe),
1556 },
1557 );
1558 const scope = yield* Scope.make();
1559 yield* Effect.addFinalizer(() => Scope.close(scope, Exit.void));
1560 const runtimeServices = yield* Layer.build(
1561 ProviderRegistryLive.pipe(
1562 Layer.provideMerge(instanceRegistryLayer),
1563 Layer.provideMerge(
1564 ServerConfig.layerTest(process.cwd(), {
1565 prefix: "t3-provider-registry-background-refresh-",
1566 }),
1567 ),
1568 Layer.provideMerge(NodeServices.layer),
1569 ),
1570 ).pipe(Scope.provide(scope));
1571 yield* Effect.gen(function* () {
1572 const registry = yield* ProviderRegistry.ProviderRegistry;
1573 assert.deepStrictEqual(yield* registry.getProviders, [initialProvider]);
1574 assert.strictEqual(yield* Ref.get(refreshCalls), 0);
1575 }).pipe(Effect.provide(runtimeServices));
1576 }),
1577 );
1578
1579 it.effect("deduplicates cwd probes and clears snapshots when an instance rebuilds", () =>
1580 Effect.gen(function* () {
1581 const driver = ProviderDriverKind.make("codex");
1582 const instanceId = ProviderInstanceId.make("codex");
1583 const machineProvider = {
1584 instanceId,
1585 driver,
1586 status: "ready",
1587 enabled: true,
1588 installed: true,
1589 auth: { status: "authenticated" },
1590 checkedAt: "2026-06-10T00:00:00.000Z",
1591 version: "1.0.0",
1592 models: [],
1593 slashCommands: [{ name: "global" }],
1594 skills: [{ name: "global", path: "/global/SKILL.md", enabled: true }],
1595 } as const satisfies ServerProvider;
1596 const scopedProvider = {
1597 ...machineProvider,
1598 checkedAt: "2026-06-10T00:01:00.000Z",
1599 slashCommands: [{ name: "project" }],
1600 skills: [{ name: "project", path: "/workspace/SKILL.md", enabled: true }],
1601 } as const satisfies ServerProvider;
1602 const pendingScopedProvider = {
1603 ...scopedProvider,
1604 status: "error",
1605 installed: false,
1606 slashCommands: [],
1607 } as const satisfies ServerProvider;
1608 const snapshotCalls = yield* Ref.make(0);
1609 const scopedResult = yield* Ref.make<ProviderWorkspaceSnapshot>({
1610 ...scopedProvider,
1611 status: "error",
1612 slashCommands: [COMPACT_SLASH_COMMAND],
1613 slashCommandsPending: true,
1614 });
1615 const cacheInvalidations = yield* Ref.make(0);
1616 const scanGate = yield* Ref.make<{
1617 readonly started: Deferred.Deferred<void>;
1618 readonly release: Deferred.Deferred<void>;
1619 } | null>(null);
1620 const returnPendingSnapshot = yield* Ref.make(true);
1621 const probeStarted = yield* Deferred.make<void>();
1622 const releaseProbe = yield* Deferred.make<void>();
1623 const makeInstance = (
1624 provider: ServerProvider,
1625 snapshotForCwd: NonNullable<ProviderInstance["snapshotForCwd"]>,
1626 ): ProviderInstance => ({
1627 instanceId,
1628 driverKind: driver,
1629 continuationIdentity: {
1630 driverKind: driver,
1631 continuationKey: "codex:instance:codex",
1632 },
1633 displayName: undefined,
1634 enabled: true,
1635 snapshot: {
1636 resolveMaintenance: () =>
1637 Effect.succeed(
1638 makeManualOnlyProviderMaintenanceCapabilities({
1639 provider: driver,
1640 packageName: null,
1641 }),
1642 ),
1643 getSnapshot: Effect.succeed(provider),
1644 refresh: Effect.succeed(provider),
1645 streamChanges: Stream.empty,
1646 applyUsageLimits: () => Effect.void,
1647 },
1648 snapshotForCwd,
1649 invalidateCaches: Ref.update(cacheInvalidations, (count) => count + 1),
1650 orchestrationAdapter: {} as ProviderInstance["orchestrationAdapter"],
1651 textGeneration: {} as ProviderInstance["textGeneration"],
1652 });
1653 const firstInstance = makeInstance(machineProvider, () =>
1654 Effect.gen(function* () {
1655 yield* Ref.update(snapshotCalls, (count) => count + 1);
1656 if (yield* Ref.get(returnPendingSnapshot)) return pendingScopedProvider;
1657 yield* Deferred.succeed(probeStarted, undefined);
1658 yield* Deferred.await(releaseProbe);
1659 const result = yield* Ref.get(scopedResult);
1660 const gate = yield* Ref.getAndSet(scanGate, null);
1661 if (gate) {
1662 yield* Deferred.succeed(gate.started, undefined);
1663 yield* Deferred.await(gate.release);
1664 }
1665 return result;
1666 }),
1667 );
1668 const rebuiltProvider = {
1669 ...machineProvider,
1670 checkedAt: "2026-06-10T00:02:00.000Z",
1671 status: "warning",
1672 installed: false,
1673 auth: { status: "unknown" },
1674 } satisfies ServerProvider;
1675 const rebuiltInstance = makeInstance(rebuiltProvider, () =>
1676 Ref.update(snapshotCalls, (count) => count + 1).pipe(Effect.as(scopedProvider)),
1677 );
1678 const registryChanges = yield* PubSub.unbounded<void>();
1679 const instancesRef = yield* Ref.make<ReadonlyArray<ProviderInstance>>([firstInstance]);
1680 const instanceRegistryLayer = Layer.succeed(
1681 ProviderInstanceRegistry.ProviderInstanceRegistry,
1682 {
1683 getInstance: (requestedId) =>
1684 Ref.get(instancesRef).pipe(
1685 Effect.map((instances) =>
1686 instances.find((instance) => instance.instanceId === requestedId),
1687 ),
1688 ),
1689 listInstances: Ref.get(instancesRef),
1690 listUnavailable: Effect.succeed([]),
1691 streamChanges: Stream.fromPubSub(registryChanges),
1692 subscribeChanges: PubSub.subscribe(registryChanges),
1693 },
1694 );
1695 const scope = yield* Scope.make();
1696 yield* Effect.addFinalizer(() => Scope.close(scope, Exit.void));
1697 const runtimeServices = yield* Layer.build(
1698 ProviderRegistryLive.pipe(
1699 Layer.provideMerge(instanceRegistryLayer),
1700 Layer.provideMerge(
1701 ServerConfig.layerTest(process.cwd(), {
1702 prefix: "t3-provider-registry-workspace-snapshot-",
1703 }),
1704 ),
1705 Layer.provideMerge(NodeServices.layer),
1706 ),
1707 ).pipe(Scope.provide(scope));
1708
1709 yield* Effect.gen(function* () {
1710 const registry = yield* ProviderRegistry.ProviderRegistry;
1711 yield* registry.refreshWorkspaceSnapshot({ instanceId, cwd: "/workspace" });
1712 assert.strictEqual((yield* registry.getProviders)[0]?.workspaceSnapshots, undefined);
1713 yield* Ref.set(returnPendingSnapshot, false);
1714 const workspaceUpdate = yield* registry.streamChanges.pipe(
1715 Stream.runHead,
1716 Effect.forkChild,
1717 );
1718 yield* Effect.yieldNow;
1719 const firstRefresh = yield* registry
1720 .refreshWorkspaceSnapshot({ instanceId, cwd: "/workspace" })
1721 .pipe(Effect.forkChild);
1722 yield* Deferred.await(probeStarted);
1723 const duplicateRefresh = yield* registry
1724 .refreshWorkspaceSnapshot({ instanceId, cwd: "/workspace" })
1725 .pipe(Effect.forkChild);
1726 yield* Effect.yieldNow;
1727 assert.strictEqual(yield* Ref.get(snapshotCalls), 2);
1728 yield* Deferred.succeed(releaseProbe, undefined);
1729 yield* Fiber.join(firstRefresh);
1730 yield* Fiber.join(duplicateRefresh);
1731 const published = yield* Fiber.join(workspaceUpdate);
1732 assert.strictEqual(published._tag, "Some");
1733 const providers = yield* registry.getProviders;
1734 assert.deepStrictEqual(providers[0]?.skills, machineProvider.skills);
1735 assert.deepStrictEqual(
1736 providers[0]?.workspaceSnapshots?.[0]?.skills,
1737 scopedProvider.skills,
1738 );
1739 assert.deepStrictEqual(providers[0]?.workspaceSnapshots?.[0]?.slashCommands, [
1740 COMPACT_SLASH_COMMAND,
1741 ]);
1742 assert.strictEqual(providers[0]?.workspaceSnapshots?.[0]?.slashCommandsPending, true);
1743 yield* Ref.set(scopedResult, {
1744 ...scopedProvider,
1745 status: "error",
1746 slashCommandsPending: false,
1747 });
1748 yield* registry.refreshWorkspaceSnapshot({ instanceId, cwd: "/workspace" });
1749 assert.strictEqual(yield* Ref.get(snapshotCalls), 3);
1750 assert.deepStrictEqual(
1751 (yield* registry.getProviders)[0]?.workspaceSnapshots?.[0]?.slashCommands,
1752 scopedProvider.slashCommands,
1753 );
1754 assert.strictEqual(
1755 (yield* registry.getProviders)[0]?.workspaceSnapshots?.[0]?.slashCommandsPending,
1756 undefined,
1757 );
1758 yield* registry.refreshWorkspaceSnapshot({ instanceId, cwd: "/workspace" });
1759 assert.strictEqual(yield* Ref.get(snapshotCalls), 3);
1760 const newSkills = [
1761 ...scopedProvider.skills,
1762 { name: "added", path: "/workspace/added/SKILL.md", enabled: true },
1763 ];
1764 yield* Ref.set(scopedResult, { ...scopedProvider, skills: newSkills });
1765 yield* registry.refreshWorkspaceSnapshot({
1766 instanceId,
1767 cwd: "/workspace",
1768 fresh: true,
1769 });
1770 assert.strictEqual(yield* Ref.get(snapshotCalls), 4);
1771 assert.strictEqual(yield* Ref.get(cacheInvalidations), 1);
1772 assert.deepStrictEqual(
1773 (yield* registry.getProviders)[0]?.workspaceSnapshots?.map((s) => s.skills),
1774 [newSkills],
1775 );
1776
1777 // A slow fresh scan that read older files must not overwrite a
1778 // newer scan that finished first.
1779 const slowStarted = yield* Deferred.make<void>();
1780 const releaseSlow = yield* Deferred.make<void>();
1781 yield* Ref.set(scanGate, { started: slowStarted, release: releaseSlow });
1782 yield* Ref.set(scopedResult, scopedProvider);
1783 const slowScan = yield* registry
1784 .refreshWorkspaceSnapshot({ instanceId, cwd: "/workspace", fresh: true })
1785 .pipe(Effect.forkChild);
1786 yield* Deferred.await(slowStarted);
1787 const latestSkills = [
1788 ...newSkills,
1789 { name: "latest", path: "/workspace/latest/SKILL.md", enabled: true },
1790 ];
1791 yield* Ref.set(scopedResult, { ...scopedProvider, skills: latestSkills });
1792 yield* registry.refreshWorkspaceSnapshot({
1793 instanceId,
1794 cwd: "/workspace",
1795 fresh: true,
1796 });
1797 yield* Deferred.succeed(releaseSlow, undefined);
1798 yield* Fiber.join(slowScan);
1799 assert.deepStrictEqual(
1800 (yield* registry.getProviders)[0]?.workspaceSnapshots?.map((s) => s.skills),
1801 [latestSkills],
1802 );
1803
1804 yield* Ref.set(instancesRef, [rebuiltInstance]);
1805 yield* PubSub.publish(registryChanges, undefined);
1806 let rebuilt = yield* registry.getProviders;
1807 for (
1808 let attempt = 0;
1809 attempt < 50 && rebuilt[0]?.checkedAt !== rebuiltProvider.checkedAt;
1810 attempt += 1
1811 ) {
1812 yield* Effect.yieldNow;
1813 rebuilt = yield* registry.getProviders;
1814 }
1815 assert.strictEqual(rebuilt[0]?.checkedAt, rebuiltProvider.checkedAt);
1816 assert.strictEqual(rebuilt[0]?.workspaceSnapshots, undefined);
1817 }).pipe(Effect.provide(runtimeServices));
1818 }),
1819 );
1820
1821 it.effect("refreshes OpenCode catalogs and preserves other providers", () =>
1822 Effect.gen(function* () {
1823 const codexDriver = ProviderDriverKind.make("codex");
1824 const openCodeDriver = ProviderDriverKind.make("opencode");
1825 const codexInstanceId = ProviderInstanceId.make("codex");
1826 const openCodeInstanceId = ProviderInstanceId.make("opencode");
1827 const codexRefreshCalls = yield* Ref.make(0);
1828 const openCodeRefreshCalls = yield* Ref.make(0);
1829 const codexProvider = {
1830 instanceId: codexInstanceId,
1831 driver: codexDriver,
1832 status: "ready",
1833 enabled: true,
1834 installed: true,
1835 auth: { status: "authenticated" },
1836 checkedAt: "2026-06-10T00:00:00.000Z",
1837 version: "1.0.0",
1838 models: [],
1839 slashCommands: [],
1840 skills: [],
1841 } as const satisfies ServerProvider;
1842 const failedOpenCodeProvider = {
1843 instanceId: openCodeInstanceId,
1844 driver: openCodeDriver,
1845 status: "error",
1846 enabled: true,
1847 installed: true,
1848 auth: { status: "unknown" },
1849 checkedAt: "2026-06-10T00:00:00.000Z",
1850 version: "1.0.0",
1851 message: "Failed to refresh OpenCode models.",
1852 models: [],
1853 slashCommands: [],
1854 skills: [],
1855 } as const satisfies ServerProvider;
1856 const recoveredOpenCodeProvider = {
1857 ...failedOpenCodeProvider,
1858 status: "ready",
1859 auth: { status: "authenticated" },
1860 checkedAt: "2026-06-10T00:01:00.000Z",
1861 message: "One upstream provider connected through OpenCode.",
1862 models: [
1863 {
1864 slug: "github/gpt-5",
1865 name: "GPT-5",
1866 subProvider: "GitHub",
1867 isCustom: false,
1868 capabilities: null,
1869 },
1870 ],
1871 } as const satisfies ServerProvider;
1872 const changedCatalogProvider = {
1873 ...recoveredOpenCodeProvider,
1874 checkedAt: "2026-06-10T00:02:00.000Z",
1875 models: [
1876 {
1877 slug: "anthropic/claude-sonnet-4",
1878 name: "Claude Sonnet 4",
1879 subProvider: "Anthropic",
1880 isCustom: false,
1881 capabilities: null,
1882 },
1883 ],
1884 } as const satisfies ServerProvider;
1885 const catalogSnapshot = yield* Ref.make<ServerProvider>(recoveredOpenCodeProvider);
1886 const instances = [
1887 {
1888 instanceId: codexInstanceId,
1889 driverKind: codexDriver,
1890 continuationIdentity: {
1891 driverKind: codexDriver,
1892 continuationKey: "codex:instance:codex",
1893 },
1894 displayName: undefined,
1895 enabled: true,
1896 snapshot: {
1897 resolveMaintenance: () =>
1898 Effect.succeed(
1899 makeManualOnlyProviderMaintenanceCapabilities({
1900 provider: codexDriver,
1901 packageName: null,
1902 }),
1903 ),
1904 getSnapshot: Effect.succeed(codexProvider),
1905 refresh: Ref.update(codexRefreshCalls, (count) => count + 1).pipe(
1906 Effect.as(codexProvider),
1907 ),
1908 streamChanges: Stream.empty,
1909 applyUsageLimits: () => Effect.void,
1910 },
1911 orchestrationAdapter: {} as ProviderInstance["orchestrationAdapter"],
1912 textGeneration: {} as ProviderInstance["textGeneration"],
1913 },
1914 {
1915 instanceId: openCodeInstanceId,
1916 driverKind: openCodeDriver,
1917 continuationIdentity: {
1918 driverKind: openCodeDriver,
1919 continuationKey: "opencode:instance:opencode",
1920 },
1921 displayName: undefined,
1922 enabled: true,
1923 snapshot: {
1924 resolveMaintenance: () =>
1925 Effect.succeed(
1926 makeManualOnlyProviderMaintenanceCapabilities({
1927 provider: openCodeDriver,
1928 packageName: null,
1929 }),
1930 ),
1931 getSnapshot: Effect.succeed(failedOpenCodeProvider),
1932 refresh: Ref.update(openCodeRefreshCalls, (count) => count + 1).pipe(
1933 Effect.andThen(Ref.get(catalogSnapshot)),
1934 ),
1935 streamChanges: Stream.empty,
1936 applyUsageLimits: () => Effect.void,
1937 },
1938 orchestrationAdapter: {} as ProviderInstance["orchestrationAdapter"],
1939 textGeneration: {} as ProviderInstance["textGeneration"],
1940 },
1941 ] satisfies ReadonlyArray<ProviderInstance>;
1942 const instanceRegistryLayer = Layer.succeed(
1943 ProviderInstanceRegistry.ProviderInstanceRegistry,
1944 {
1945 getInstance: (instanceId) =>
1946 Effect.succeed(instances.find((instance) => instance.instanceId === instanceId)),
1947 listInstances: Effect.succeed(instances),
1948 listUnavailable: Effect.succeed([]),
1949 streamChanges: Stream.empty,
1950 subscribeChanges: Effect.flatMap(PubSub.unbounded<void>(), PubSub.subscribe),
1951 },
1952 );
1953 const scope = yield* Scope.make();
1954 yield* Effect.addFinalizer(() => Scope.close(scope, Exit.void));
1955 const runtimeServices = yield* Layer.build(
1956 ProviderRegistryLive.pipe(
1957 Layer.provideMerge(instanceRegistryLayer),
1958 Layer.provideMerge(
1959 ServerConfig.layerTest(process.cwd(), {
1960 prefix: "t3-provider-registry-reconnect-refresh-",
1961 }),
1962 ),
1963 Layer.provideMerge(NodeServices.layer),
1964 ),
1965 ).pipe(Scope.provide(scope));
1966
1967 yield* Effect.gen(function* () {
1968 const registry = yield* ProviderRegistry.ProviderRegistry;
1969 const initialProviders = yield* registry.getProviders;
1970 assert.strictEqual(
1971 initialProviders.find((provider) => provider.instanceId === openCodeInstanceId)
1972 ?.status,
1973 "error",
1974 );
1975
1976 const recoveredProviders = yield* registry.refresh();
1977 assert.deepStrictEqual(
1978 recoveredProviders.find((provider) => provider.instanceId === openCodeInstanceId)
1979 ?.models,
1980 recoveredOpenCodeProvider.models,
1981 );
1982 assert.deepStrictEqual(
1983 recoveredProviders.find((provider) => provider.instanceId === codexInstanceId),
1984 withBundledCompatibility(codexProvider),
1985 );
1986
1987 yield* Ref.set(catalogSnapshot, changedCatalogProvider);
1988 const changedProviders = yield* registry.refresh();
1989 assert.deepStrictEqual(
1990 changedProviders.find((provider) => provider.instanceId === openCodeInstanceId)
1991 ?.models,
1992 changedCatalogProvider.models,
1993 );
1994 assert.deepStrictEqual(
1995 changedProviders.find((provider) => provider.instanceId === codexInstanceId),
1996 withBundledCompatibility(codexProvider),
1997 );
1998 }).pipe(Effect.provide(runtimeServices));
1999
2000 assert.strictEqual(yield* Ref.get(codexRefreshCalls), 2);
2001 assert.strictEqual(yield* Ref.get(openCodeRefreshCalls), 2);
2002 }),
2003 );
2004
2005 it("persists merged provider snapshots for the providers that were refreshed", () => {
2006 const previousProviders = [
2007 {
2008 instanceId: ProviderInstanceId.make("cursor"),
2009 driver: ProviderDriverKind.make("cursor"),
2010 status: "ready",
2011 enabled: true,
2012 installed: true,
2013 auth: { status: "authenticated" },
2014 checkedAt: "2026-04-14T00:00:00.000Z",
2015 version: "2026.04.09-f2b0fcd",
2016 models: [
2017 {
2018 slug: "claude-opus-4-6",
2019 name: "Opus 4.6",
2020 isCustom: false,
2021 capabilities: createModelCapabilities({
2022 optionDescriptors: [
2023 selectDescriptor("reasoning", "Reasoning", [
2024 { id: "high", label: "High", isDefault: true },
2025 ]),
2026 booleanDescriptor("fastMode", "Fast Mode"),
2027 booleanDescriptor("thinking", "Thinking"),
2028 ],
2029 }),
2030 },
2031 ],
2032 slashCommands: [],
2033 skills: [],
2034 },
2035 {
2036 instanceId: ProviderInstanceId.make("codex"),
2037 driver: ProviderDriverKind.make("codex"),
2038 status: "ready",
2039 enabled: true,
2040 installed: true,
2041 auth: { status: "authenticated" },
2042 checkedAt: "2026-04-14T00:00:00.000Z",
2043 version: "1.0.0",
2044 models: [],
2045 slashCommands: [],
2046 skills: [],
2047 },
2048 ] as const satisfies ReadonlyArray<ServerProvider>;
2049 const refreshedCursor = {
2050 ...previousProviders[0],
2051 checkedAt: "2026-04-14T00:01:00.000Z",
2052 models: [],
2053 } satisfies ServerProvider;
2054
2055 const mergedProviders = mergeProviderSnapshots(previousProviders, [refreshedCursor]);
2056 const persistedProviders = selectProvidersByKind(
2057 mergedProviders,
2058 new Set([ProviderDriverKind.make("cursor")]),
2059 );
2060
2061 assert.deepStrictEqual(persistedProviders, [
2062 {
2063 ...refreshedCursor,
2064 models: [...previousProviders[0].models],
2065 },
2066 ]);
2067 });
2068
2069 it.effect("persists the merged snapshot when a live update has empty models", () =>
2070 Effect.gen(function* () {
2071 const cursorDriver = ProviderDriverKind.make("cursor");
2072 const cursorInstanceId = ProviderInstanceId.make("cursor");
2073 const initialProvider = {
2074 instanceId: cursorInstanceId,
2075 driver: cursorDriver,
2076 status: "ready",
2077 enabled: true,
2078 installed: true,
2079 auth: { status: "authenticated" },
2080 checkedAt: "2026-04-14T00:00:00.000Z",
2081 version: "2026.04.09-f2b0fcd",
2082 models: [
1086 it("keeps discovered models during startup and failed probes without restoring removed custom models", () => {
1087 for (const provider of [pendingProvider, failedProvider]) {
1088 assert.deepStrictEqual(
1089 mergeProviderSnapshot(
20831090 {
2084 slug: "claude-opus-4-6",
2085 name: "Opus 4.6",
2086 isCustom: false,
2087 capabilities: createModelCapabilities({
2088 optionDescriptors: [
2089 selectDescriptor("reasoning", "Reasoning", [
2090 { id: "high", label: "High", isDefault: true },
2091 ]),
2092 ],
2093 }),
1091 ...cachedProvider,
1092 models: [...cachedProvider.models, { ...customModel, slug: "removed-custom" }],
20941093 },
2095 ],
2096 slashCommands: [],
2097 skills: [],
2098 } as const satisfies ServerProvider;
2099 const refreshedProvider = {
2100 ...initialProvider,
2101 checkedAt: "2026-04-14T00:01:00.000Z",
2102 models: [],
2103 } satisfies ServerProvider;
2104 const changes = yield* PubSub.unbounded<ServerProvider>();
1094 provider,
1095 ).models,
1096 [customModel, ...cachedProvider.models],
1097 );
1098 }
1099 });
1100
1101 it("clears discovered models after sign-out, disable, uninstall, or empty discovery", () => {
1102 const emptyProvider = { ...refreshedProvider, models: [customModel] };
1103 const clearedProviders = [
1104 { ...emptyProvider, status: "error", auth: { status: "unauthenticated" } },
1105 { ...emptyProvider, status: "disabled", enabled: false },
1106 { ...emptyProvider, status: "error", installed: false, auth: { status: "unknown" } },
1107 emptyProvider,
1108 { ...emptyProvider, models: [] },
1109 ] satisfies ReadonlyArray<ServerProvider>;
1110
1111 for (const provider of clearedProviders) {
1112 assert.deepStrictEqual(
1113 mergeProviderSnapshot(cachedProvider, provider).models,
1114 provider.models,
1115 );
1116 }
1117 });
1118
1119 it.effect("persists removals across failed refreshes and registry restarts", () =>
1120 Effect.gen(function* () {
1121 const config = yield* ServerConfig.ServerConfig;
1122 const filePath = yield* resolveProviderStatusCachePath({
1123 cacheDir: config.providerStatusCacheDir,
1124 instanceId: cachedProvider.instanceId,
1125 });
1126 yield* writeProviderStatusCache({ filePath, provider: cachedProvider });
1127 const nextProvider = yield* Ref.make<ServerProvider>(refreshedProvider);
21051128 const instance = {
2106 instanceId: cursorInstanceId,
2107 driverKind: cursorDriver,
1129 instanceId: cachedProvider.instanceId,
1130 driverKind: cachedProvider.driver,
21081131 continuationIdentity: {
2109 driverKind: cursorDriver,
2110 continuationKey: "cursor:instance:cursor",
1132 driverKind: cachedProvider.driver,
1133 continuationKey: "codex:instance:codex-personal",
21111134 },
21121135 displayName: undefined,
21131136 enabled: true,
......@@ -2115,225 +1138,728 @@ it.layer(Layer.mergeAll(TestNodeServices, ServerSettingsModule.layerTest(), Test
21151138 resolveMaintenance: () =>
21161139 Effect.succeed(
21171140 makeManualOnlyProviderMaintenanceCapabilities({
2118 provider: cursorDriver,
1141 provider: cachedProvider.driver,
21191142 packageName: null,
21201143 }),
21211144 ),
2122 getSnapshot: Effect.succeed(initialProvider),
2123 refresh: Effect.succeed(refreshedProvider),
2124 streamChanges: Stream.fromPubSub(changes),
1145 getSnapshot: Effect.succeed(pendingProvider),
1146 refresh: Ref.get(nextProvider),
1147 streamChanges: Stream.empty,
21251148 applyUsageLimits: () => Effect.void,
21261149 },
21271150 orchestrationAdapter: {} as ProviderInstance["orchestrationAdapter"],
21281151 textGeneration: {} as ProviderInstance["textGeneration"],
21291152 } satisfies ProviderInstance;
2130 const instanceRegistryLayer = Layer.succeed(
1153 const layerInstanceRegistry = Layer.succeed(
21311154 ProviderInstanceRegistry.ProviderInstanceRegistry,
21321155 {
2133 getInstance: (instanceId) =>
2134 Effect.succeed(instanceId === cursorInstanceId ? instance : undefined),
1156 getInstance: (id) =>
1157 Effect.succeed(id === instance.instanceId ? instance : undefined),
21351158 listInstances: Effect.succeed([instance]),
21361159 listUnavailable: Effect.succeed([]),
21371160 streamChanges: Stream.empty,
2138 subscribeChanges: Effect.flatMap(PubSub.unbounded<void>(), (pubsub) =>
2139 PubSub.subscribe(pubsub),
2140 ),
1161 subscribeChanges: Effect.flatMap(PubSub.unbounded<void>(), PubSub.subscribe),
21411162 },
21421163 );
2143 const scope = yield* Scope.make();
2144 yield* Effect.addFinalizer(() => Scope.close(scope, Exit.void));
2145 const runtimeServices = yield* Layer.build(
2146 ProviderRegistryLive.pipe(
2147 Layer.provideMerge(instanceRegistryLayer),
2148 Layer.provideMerge(
2149 ServerConfig.layerTest(process.cwd(), {
2150 prefix: "t3-provider-registry-merged-persist-",
2151 }),
2152 ),
2153 Layer.provideMerge(BackgroundPolicyAlwaysRunLayer),
2154 Layer.provideMerge(NodeServices.layer),
2155 ),
2156 ).pipe(Scope.provide(scope));
1164 const retainedModels = [
1165 customModel,
1166 ...refreshedProvider.models.filter((model) => !model.isCustom),
1167 ];
21571168
2158 yield* Effect.gen(function* () {
2159 const registry = yield* ProviderRegistry.ProviderRegistry;
2160 const config = yield* ServerConfig.ServerConfig;
2161 const filePath = yield* resolveProviderStatusCachePath({
2162 cacheDir: config.providerStatusCacheDir,
2163 instanceId: cursorInstanceId,
2164 });
1169 for (const restarted of [false, true]) {
1170 yield* Effect.gen(function* () {
1171 const registry = yield* ProviderRegistry.ProviderRegistry;
1172 const expectedModels = restarted
1173 ? retainedModels
1174 : [customModel, ...cachedProvider.models];
1175 assert.deepStrictEqual((yield* registry.getProviders)[0]?.models, expectedModels);
1176
1177 yield* registry.refreshInstance(instance.instanceId);
1178 assert.deepStrictEqual(
1179 (yield* readProviderStatusCache(filePath))?.models,
1180 restarted ? retainedModels : refreshedProvider.models,
1181 );
21651182
2166 assert.deepStrictEqual((yield* registry.getProviders)[0]?.models, [
2167 ...initialProvider.models,
2168 ]);
2169 const persisted = yield* awaitPersistedProvider(registry, refreshedProvider.checkedAt);
2170 yield* PubSub.publish(changes, refreshedProvider);
2171 yield* Fiber.join(persisted);
2172 const cachedProvider = yield* readProviderStatusCache(filePath);
2173
2174 assert.deepStrictEqual(
2175 cachedProvider,
2176 withBundledCompatibility({
2177 ...refreshedProvider,
2178 models: [...initialProvider.models],
2179 }),
1183 yield* Ref.set(nextProvider, failedProvider);
1184 const afterFailure = yield* registry.refreshInstance(instance.instanceId);
1185 assert.deepStrictEqual(afterFailure[0]?.models, retainedModels);
1186 assert.deepStrictEqual(
1187 (yield* readProviderStatusCache(filePath))?.models,
1188 retainedModels,
1189 );
1190 }).pipe(
1191 Effect.provide(
1192 ProviderRegistryLayer.layer.pipe(Layer.provide(layerInstanceRegistry)),
1193 ),
1194 Effect.scoped,
21801195 );
2181 }).pipe(Effect.provide(runtimeServices));
2182 }),
1196 }
1197 }).pipe(
1198 Effect.provide(
1199 ServerConfig.layerTest(process.cwd(), {
1200 prefix: "t3-codex-retired-model-cache-",
1201 }).pipe(Layer.provideMerge(NodeServices.layer)),
1202 ),
1203 ),
21831204 );
1205 });
21841206
2185 it.effect(
2186 "persists authoritative OpenCode removals without resurrecting them on a failed live refresh",
2187 () =>
2188 Effect.gen(function* () {
2189 const openCodeDriver = ProviderDriverKind.make("opencode");
2190 const openCodeInstanceId = ProviderInstanceId.make("opencode");
2191 const initialProvider = {
2192 instanceId: openCodeInstanceId,
2193 driver: openCodeDriver,
2194 status: "ready",
2195 enabled: true,
2196 installed: true,
2197 auth: { status: "authenticated" },
2198 checkedAt: "2026-07-17T00:00:00.000Z",
2199 version: "1.0.0",
2200 models: [
2201 {
2202 slug: "github/gpt-5",
2203 name: "GPT-5",
2204 subProvider: "GitHub",
2205 isCustom: false,
2206 capabilities: null,
2207 },
2208 {
2209 slug: "removed-plugin/model",
2210 name: "Removed Plugin Model",
2211 subProvider: "Removed Plugin",
2212 isCustom: false,
2213 capabilities: null,
2214 },
1207 describe("Antigravity model inventories", () => {
1208 const previousProvider = {
1209 instanceId: ProviderInstanceId.make("antigravity-personal"),
1210 driver: ProviderDriverKind.make("antigravity"),
1211 status: "ready",
1212 enabled: true,
1213 installed: true,
1214 auth: { status: "authenticated" },
1215 checkedAt: "2026-09-02T00:00:00.000Z",
1216 version: "0.1.3",
1217 models: [
1218 {
1219 slug: "gemini-3.1-pro-high",
1220 name: "Gemini 3.1 Pro High",
1221 isCustom: false,
1222 capabilities: null,
1223 },
1224 {
1225 slug: "gemini-3-flash",
1226 name: "Gemini 3 Flash",
1227 isCustom: false,
1228 capabilities: null,
1229 },
1230 ],
1231 slashCommands: [],
1232 skills: [],
1233 } as const satisfies ServerProvider;
1234
1235 it("removes unavailable models after a successful refresh", () => {
1236 for (const status of ["ready", "warning"] as const) {
1237 const refreshedProvider = {
1238 ...previousProvider,
1239 status,
1240 checkedAt: "2026-09-02T00:01:00.000Z",
1241 models: [previousProvider.models[1]],
1242 } satisfies ServerProvider;
1243 const afterRefresh = mergeProviderSnapshot(previousProvider, refreshedProvider);
1244
1245 assert.deepStrictEqual(afterRefresh.models, refreshedProvider.models);
1246
1247 const afterFailure = mergeProviderSnapshot(afterRefresh, {
1248 ...refreshedProvider,
1249 status: "error",
1250 auth: { status: "unknown" },
1251 models: [],
1252 });
1253 assert.deepStrictEqual(afterFailure.models, refreshedProvider.models);
1254 }
1255 });
1256
1257 it("keeps cached models during health checks and temporary failures", () => {
1258 for (const installed of [false, true]) {
1259 const pendingProvider = {
1260 ...previousProvider,
1261 status: "warning",
1262 installed,
1263 auth: { status: "unknown" },
1264 checkedAt: "2026-09-02T00:01:00.000Z",
1265 version: installed ? previousProvider.version : null,
1266 models: [],
1267 } satisfies ServerProvider;
1268
1269 assert.deepStrictEqual(
1270 mergeProviderSnapshot(previousProvider, pendingProvider).models,
1271 previousProvider.models,
1272 );
1273 }
1274
1275 for (const authStatus of ["unknown", "authenticated"] as const) {
1276 const failedProvider = {
1277 ...previousProvider,
1278 status: "error",
1279 auth: { status: authStatus },
1280 checkedAt: "2026-09-02T00:02:00.000Z",
1281 models: [],
1282 } satisfies ServerProvider;
1283
1284 assert.deepStrictEqual(
1285 mergeProviderSnapshot(previousProvider, failedProvider).models,
1286 previousProvider.models,
1287 );
1288 }
1289 });
1290
1291 it("clears models after sign-out, disable, uninstall, or an empty successful refresh", () => {
1292 const emptyProvider = {
1293 ...previousProvider,
1294 checkedAt: "2026-09-02T00:01:00.000Z",
1295 models: [],
1296 } satisfies ServerProvider;
1297 const clearedProviders = [
1298 { ...emptyProvider, status: "warning", auth: { status: "unauthenticated" } },
1299 { ...emptyProvider, status: "error", auth: { status: "unauthenticated" } },
1300 { ...emptyProvider, status: "disabled", enabled: false },
1301 { ...emptyProvider, status: "error", enabled: false },
1302 { ...emptyProvider, status: "error", installed: false, auth: { status: "unknown" } },
1303 emptyProvider,
1304 ] satisfies ReadonlyArray<ServerProvider>;
1305
1306 for (const provider of clearedProviders) {
1307 const afterRemoval = mergeProviderSnapshot(previousProvider, provider);
1308 assert.deepStrictEqual(afterRemoval.models, []);
1309
1310 const afterFailure = mergeProviderSnapshot(afterRemoval, {
1311 ...emptyProvider,
1312 status: "error",
1313 auth: { status: "unknown" },
1314 });
1315 assert.deepStrictEqual(afterFailure.models, []);
1316 }
1317 });
1318 });
1319
1320 describe("Antigravity saved account", () => {
1321 const signedIn = {
1322 instanceId: ProviderInstanceId.make("antigravity-personal"),
1323 driver: ProviderDriverKind.make("antigravity"),
1324 status: "ready",
1325 enabled: true,
1326 installed: true,
1327 auth: { status: "authenticated", type: "oauth-personal", label: "Google account" },
1328 checkedAt: "2026-09-05T00:00:00.000Z",
1329 version: "agy_acp_server_1.1.1",
1330 models: [
1331 {
1332 slug: "gemini-3.7-flash-high",
1333 name: "Gemini 3.7 Flash",
1334 isCustom: false,
1335 capabilities: null,
1336 },
1337 ],
1338 slashCommands: [{ name: "plan" }],
1339 skills: [],
1340 } as const satisfies ServerProvider;
1341 const uncheckedMessage =
1342 "Antigravity is installed. Google account access is not checked yet.";
1343 const restartProbe = {
1344 ...signedIn,
1345 status: "warning",
1346 auth: { status: "unknown" },
1347 checkedAt: "2026-09-05T00:01:00.000Z",
1348 message: uncheckedMessage,
1349 models: [],
1350 } as const satisfies ServerProvider;
1351
1352 it("keeps the saved Google account through restart health checks", () => {
1353 const merged = mergeProviderSnapshot(signedIn, restartProbe);
1354 const { message: _uncheckedMessage, ...probeWithoutMessage } = restartProbe;
1355 assert.deepStrictEqual(merged, {
1356 ...probeWithoutMessage,
1357 status: "ready",
1358 auth: signedIn.auth,
1359 models: signedIn.models,
1360 });
1361 assert.equal("message" in merged, false);
1362 // The next periodic probe reads the merged snapshot as its previous state.
1363 assert.deepStrictEqual(mergeProviderSnapshot(merged, restartProbe), merged);
1364 });
1365
1366 it("carries the account through the boot probe and a failed probe without hiding them", () => {
1367 const booting = {
1368 ...restartProbe,
1369 installed: false,
1370 version: null,
1371 message: "Checking Antigravity availability.",
1372 } satisfies ServerProvider;
1373 assert.deepStrictEqual(mergeProviderSnapshot(signedIn, booting), {
1374 ...booting,
1375 auth: signedIn.auth,
1376 models: signedIn.models,
1377 });
1378
1379 const failed = {
1380 ...restartProbe,
1381 status: "error",
1382 message: "Antigravity did not respond to its local health check within 90 seconds.",
1383 } satisfies ServerProvider;
1384 assert.deepStrictEqual(mergeProviderSnapshot(signedIn, failed), {
1385 ...failed,
1386 auth: signedIn.auth,
1387 models: signedIn.models,
1388 });
1389 });
1390
1391 it("does not invent an account after sign-out, disable, uninstall, or for other providers", () => {
1392 const untouched = [
1393 { ...restartProbe, auth: { status: "unauthenticated" } },
1394 { ...restartProbe, status: "disabled", enabled: false },
1395 { ...restartProbe, status: "error", installed: false },
1396 { ...restartProbe, driver: ProviderDriverKind.make("codex") },
1397 // The instance was rebuilt with another sign-in method.
1398 { ...restartProbe, auth: { status: "unknown", type: "gemini-api-key" } },
1399 ] satisfies ReadonlyArray<ServerProvider>;
1400 for (const next of untouched) {
1401 const merged = mergeProviderSnapshot(signedIn, next);
1402 assert.deepStrictEqual(merged.auth, next.auth);
1403 assert.equal(merged.status, next.status);
1404 assert.equal(merged.message, next.message);
1405 }
1406 assert.deepStrictEqual(
1407 mergeProviderSnapshot({ ...signedIn, auth: { status: "unknown" } }, restartProbe).auth,
1408 { status: "unknown" },
1409 );
1410 assert.equal(
1411 mergeProviderSnapshot(
1412 { ...signedIn, driver: ProviderDriverKind.make("codex") },
1413 restartProbe,
1414 ).auth.status,
1415 "unknown",
1416 );
1417 assert.deepStrictEqual(
1418 mergeProviderSnapshot(signedIn, {
1419 ...restartProbe,
1420 auth: { status: "unknown", type: "oauth-personal" },
1421 }).auth,
1422 signedIn.auth,
1423 );
1424 });
1425 });
1426
1427 it("fills missing capabilities from the previous provider snapshot", () => {
1428 const previousProvider = {
1429 instanceId: ProviderInstanceId.make("cursor"),
1430 driver: ProviderDriverKind.make("cursor"),
1431 status: "ready",
1432 enabled: true,
1433 installed: true,
1434 auth: { status: "authenticated" },
1435 checkedAt: "2026-04-14T00:00:00.000Z",
1436 version: "2026.04.09-f2b0fcd",
1437 models: [
1438 {
1439 slug: "claude-opus-4-6",
1440 name: "Opus 4.6",
1441 isCustom: false,
1442 capabilities: createModelCapabilities({
1443 optionDescriptors: [
1444 selectDescriptor("reasoning", "Reasoning", [
1445 { id: "high", label: "High", isDefault: true },
1446 ]),
1447 booleanDescriptor("fastMode", "Fast Mode"),
1448 booleanDescriptor("thinking", "Thinking"),
22151449 ],
2216 slashCommands: [],
2217 skills: [],
2218 } as const satisfies ServerProvider;
2219 const authoritativeProvider = {
2220 ...initialProvider,
2221 checkedAt: "2026-07-17T00:01:00.000Z",
2222 models: [initialProvider.models[0]!],
2223 } satisfies ServerProvider;
2224 const failedProvider = {
2225 ...authoritativeProvider,
2226 status: "error",
2227 auth: { status: "unknown" },
2228 checkedAt: "2026-07-17T00:02:00.000Z",
2229 models: [],
2230 message: "Failed to refresh OpenCode models.",
2231 } satisfies ServerProvider;
2232 const changes = yield* PubSub.unbounded<ServerProvider>();
2233 const instance = {
2234 instanceId: openCodeInstanceId,
2235 driverKind: openCodeDriver,
2236 continuationIdentity: {
2237 driverKind: openCodeDriver,
2238 continuationKey: "opencode:instance:opencode",
2239 },
2240 displayName: undefined,
2241 enabled: true,
2242 snapshot: {
2243 resolveMaintenance: () =>
2244 Effect.succeed(
2245 makeManualOnlyProviderMaintenanceCapabilities({
2246 provider: openCodeDriver,
2247 packageName: null,
2248 }),
2249 ),
2250 getSnapshot: Effect.succeed(initialProvider),
2251 refresh: Effect.succeed(authoritativeProvider),
2252 streamChanges: Stream.fromPubSub(changes),
2253 applyUsageLimits: () => Effect.void,
2254 },
2255 orchestrationAdapter: {} as ProviderInstance["orchestrationAdapter"],
2256 textGeneration: {} as ProviderInstance["textGeneration"],
2257 } satisfies ProviderInstance;
2258 const instanceRegistryLayer = Layer.succeed(
2259 ProviderInstanceRegistry.ProviderInstanceRegistry,
2260 {
2261 getInstance: (instanceId) =>
2262 Effect.succeed(instanceId === openCodeInstanceId ? instance : undefined),
2263 listInstances: Effect.succeed([instance]),
2264 listUnavailable: Effect.succeed([]),
2265 streamChanges: Stream.empty,
2266 subscribeChanges: Effect.flatMap(PubSub.unbounded<void>(), (pubsub) =>
2267 PubSub.subscribe(pubsub),
2268 ),
2269 },
2270 );
2271 const scope = yield* Scope.make();
2272 yield* Effect.addFinalizer(() => Scope.close(scope, Exit.void));
2273 const runtimeServices = yield* Layer.build(
2274 ProviderRegistryLive.pipe(
2275 Layer.provideMerge(instanceRegistryLayer),
2276 Layer.provideMerge(
2277 ServerConfig.layerTest(process.cwd(), {
2278 prefix: "t3-provider-registry-opencode-authoritative-persist-",
2279 }),
2280 ),
2281 Layer.provideMerge(NodeServices.layer),
1450 }),
1451 },
1452 ],
1453 slashCommands: [],
1454 skills: [],
1455 } as const satisfies ServerProvider;
1456 const refreshedProvider = {
1457 ...previousProvider,
1458 checkedAt: "2026-04-14T00:01:00.000Z",
1459 models: [
1460 {
1461 slug: "claude-opus-4-6",
1462 name: "Opus 4.6",
1463 isCustom: false,
1464 capabilities: createModelCapabilities({
1465 optionDescriptors: [],
1466 }),
1467 },
1468 ],
1469 } satisfies ServerProvider;
1470
1471 assert.deepStrictEqual(mergeProviderSnapshot(previousProvider, refreshedProvider).models, [
1472 ...previousProvider.models,
1473 ]);
1474 });
1475
1476 it.effect("does not run provider probes during layer construction", () =>
1477 Effect.gen(function* () {
1478 const codexDriver = ProviderDriverKind.make("codex");
1479 const codexInstanceId = ProviderInstanceId.make("codex");
1480 const initialProvider = {
1481 instanceId: codexInstanceId,
1482 driver: codexDriver,
1483 status: "warning",
1484 enabled: true,
1485 installed: false,
1486 auth: { status: "unknown" },
1487 checkedAt: "2026-06-10T00:00:00.000Z",
1488 version: null,
1489 message: "Checking Codex provider status.",
1490 models: [],
1491 slashCommands: [],
1492 skills: [],
1493 } as const satisfies ServerProvider;
1494 const refreshCalls = yield* Ref.make(0);
1495 const instance = {
1496 instanceId: codexInstanceId,
1497 driverKind: codexDriver,
1498 continuationIdentity: {
1499 driverKind: codexDriver,
1500 continuationKey: "codex:instance:codex",
1501 },
1502 displayName: undefined,
1503 enabled: true,
1504 snapshot: {
1505 resolveMaintenance: () =>
1506 Effect.succeed(
1507 makeManualOnlyProviderMaintenanceCapabilities({
1508 provider: codexDriver,
1509 packageName: null,
1510 }),
22821511 ),
2283 ).pipe(Scope.provide(scope));
1512 getSnapshot: Effect.succeed(initialProvider),
1513 refresh: Ref.update(refreshCalls, (count) => count + 1).pipe(
1514 Effect.andThen(Effect.never),
1515 ),
1516 streamChanges: Stream.empty,
1517 applyUsageLimits: () => Effect.void,
1518 },
1519 orchestrationAdapter: {} as ProviderInstance["orchestrationAdapter"],
1520 textGeneration: {} as ProviderInstance["textGeneration"],
1521 } satisfies ProviderInstance;
1522 const layerInstanceRegistry = Layer.succeed(
1523 ProviderInstanceRegistry.ProviderInstanceRegistry,
1524 {
1525 getInstance: (instanceId) =>
1526 Effect.succeed(instanceId === codexInstanceId ? instance : undefined),
1527 listInstances: Effect.succeed([instance]),
1528 listUnavailable: Effect.succeed([]),
1529 streamChanges: Stream.empty,
1530 subscribeChanges: Effect.flatMap(PubSub.unbounded<void>(), PubSub.subscribe),
1531 },
1532 );
1533 const scope = yield* Scope.make();
1534 yield* Effect.addFinalizer(() => Scope.close(scope, Exit.void));
1535 const runtimeServices = yield* Layer.build(
1536 ProviderRegistryLayer.layer.pipe(
1537 Layer.provideMerge(layerInstanceRegistry),
1538 Layer.provideMerge(
1539 ServerConfig.layerTest(process.cwd(), {
1540 prefix: "t3-provider-registry-background-refresh-",
1541 }),
1542 ),
1543 Layer.provideMerge(NodeServices.layer),
1544 ),
1545 ).pipe(Scope.provide(scope));
1546 yield* Effect.gen(function* () {
1547 const registry = yield* ProviderRegistry.ProviderRegistry;
1548 assert.deepStrictEqual(yield* registry.getProviders, [initialProvider]);
1549 assert.strictEqual(yield* Ref.get(refreshCalls), 0);
1550 }).pipe(Effect.provide(runtimeServices));
1551 }),
1552 );
22841553
2285 yield* Effect.gen(function* () {
2286 const registry = yield* ProviderRegistry.ProviderRegistry;
2287 const config = yield* ServerConfig.ServerConfig;
2288 const filePath = yield* resolveProviderStatusCachePath({
2289 cacheDir: config.providerStatusCacheDir,
2290 instanceId: openCodeInstanceId,
2291 });
2292
2293 const authoritativePersisted = yield* awaitPersistedProvider(
2294 registry,
2295 authoritativeProvider.checkedAt,
2296 );
2297 yield* PubSub.publish(changes, authoritativeProvider);
2298 yield* Fiber.join(authoritativePersisted);
2299 let cachedProvider = yield* readProviderStatusCache(filePath);
1554 it.effect("deduplicates cwd probes and clears snapshots when an instance rebuilds", () =>
1555 Effect.gen(function* () {
1556 const driver = ProviderDriverKind.make("codex");
1557 const instanceId = ProviderInstanceId.make("codex");
1558 const machineProvider = {
1559 instanceId,
1560 driver,
1561 status: "ready",
1562 enabled: true,
1563 installed: true,
1564 auth: { status: "authenticated" },
1565 checkedAt: "2026-06-10T00:00:00.000Z",
1566 version: "1.0.0",
1567 models: [],
1568 slashCommands: [{ name: "global" }],
1569 skills: [{ name: "global", path: "/global/SKILL.md", enabled: true }],
1570 } as const satisfies ServerProvider;
1571 const scopedProvider = {
1572 ...machineProvider,
1573 checkedAt: "2026-06-10T00:01:00.000Z",
1574 slashCommands: [{ name: "project" }],
1575 skills: [{ name: "project", path: "/workspace/SKILL.md", enabled: true }],
1576 } as const satisfies ServerProvider;
1577 const pendingScopedProvider = {
1578 ...scopedProvider,
1579 status: "error",
1580 installed: false,
1581 slashCommands: [],
1582 } as const satisfies ServerProvider;
1583 const snapshotCalls = yield* Ref.make(0);
1584 const scopedResult = yield* Ref.make<ProviderWorkspaceSnapshot>({
1585 ...scopedProvider,
1586 status: "error",
1587 slashCommands: [COMPACT_SLASH_COMMAND],
1588 slashCommandsPending: true,
1589 });
1590 const cacheInvalidations = yield* Ref.make(0);
1591 const scanGate = yield* Ref.make<{
1592 readonly started: Deferred.Deferred<void>;
1593 readonly release: Deferred.Deferred<void>;
1594 } | null>(null);
1595 const returnPendingSnapshot = yield* Ref.make(true);
1596 const probeStarted = yield* Deferred.make<void>();
1597 const releaseProbe = yield* Deferred.make<void>();
1598 const makeInstance = (
1599 provider: ServerProvider,
1600 snapshotForCwd: NonNullable<ProviderInstance["snapshotForCwd"]>,
1601 ): ProviderInstance => ({
1602 instanceId,
1603 driverKind: driver,
1604 continuationIdentity: {
1605 driverKind: driver,
1606 continuationKey: "codex:instance:codex",
1607 },
1608 displayName: undefined,
1609 enabled: true,
1610 snapshot: {
1611 resolveMaintenance: () =>
1612 Effect.succeed(
1613 makeManualOnlyProviderMaintenanceCapabilities({
1614 provider: driver,
1615 packageName: null,
1616 }),
1617 ),
1618 getSnapshot: Effect.succeed(provider),
1619 refresh: Effect.succeed(provider),
1620 streamChanges: Stream.empty,
1621 applyUsageLimits: () => Effect.void,
1622 },
1623 snapshotForCwd,
1624 invalidateCaches: Ref.update(cacheInvalidations, (count) => count + 1),
1625 orchestrationAdapter: {} as ProviderInstance["orchestrationAdapter"],
1626 textGeneration: {} as ProviderInstance["textGeneration"],
1627 });
1628 const firstInstance = makeInstance(machineProvider, () =>
1629 Effect.gen(function* () {
1630 yield* Ref.update(snapshotCalls, (count) => count + 1);
1631 if (yield* Ref.get(returnPendingSnapshot)) return pendingScopedProvider;
1632 yield* Deferred.succeed(probeStarted, undefined);
1633 yield* Deferred.await(releaseProbe);
1634 const result = yield* Ref.get(scopedResult);
1635 const gate = yield* Ref.getAndSet(scanGate, null);
1636 if (gate) {
1637 yield* Deferred.succeed(gate.started, undefined);
1638 yield* Deferred.await(gate.release);
1639 }
1640 return result;
1641 }),
1642 );
1643 const rebuiltProvider = {
1644 ...machineProvider,
1645 checkedAt: "2026-06-10T00:02:00.000Z",
1646 status: "warning",
1647 installed: false,
1648 auth: { status: "unknown" },
1649 } satisfies ServerProvider;
1650 const rebuiltInstance = makeInstance(rebuiltProvider, () =>
1651 Ref.update(snapshotCalls, (count) => count + 1).pipe(Effect.as(scopedProvider)),
1652 );
1653 const registryChanges = yield* PubSub.unbounded<void>();
1654 const instancesRef = yield* Ref.make<ReadonlyArray<ProviderInstance>>([firstInstance]);
1655 const layerInstanceRegistry = Layer.succeed(
1656 ProviderInstanceRegistry.ProviderInstanceRegistry,
1657 {
1658 getInstance: (requestedId) =>
1659 Ref.get(instancesRef).pipe(
1660 Effect.map((instances) =>
1661 instances.find((instance) => instance.instanceId === requestedId),
1662 ),
1663 ),
1664 listInstances: Ref.get(instancesRef),
1665 listUnavailable: Effect.succeed([]),
1666 streamChanges: Stream.fromPubSub(registryChanges),
1667 subscribeChanges: PubSub.subscribe(registryChanges),
1668 },
1669 );
1670 const scope = yield* Scope.make();
1671 yield* Effect.addFinalizer(() => Scope.close(scope, Exit.void));
1672 const runtimeServices = yield* Layer.build(
1673 ProviderRegistryLayer.layer.pipe(
1674 Layer.provideMerge(layerInstanceRegistry),
1675 Layer.provideMerge(
1676 ServerConfig.layerTest(process.cwd(), {
1677 prefix: "t3-provider-registry-workspace-snapshot-",
1678 }),
1679 ),
1680 Layer.provideMerge(NodeServices.layer),
1681 ),
1682 ).pipe(Scope.provide(scope));
1683
1684 yield* Effect.gen(function* () {
1685 const registry = yield* ProviderRegistry.ProviderRegistry;
1686 yield* registry.refreshWorkspaceSnapshot({ instanceId, cwd: "/workspace" });
1687 assert.strictEqual((yield* registry.getProviders)[0]?.workspaceSnapshots, undefined);
1688 yield* Ref.set(returnPendingSnapshot, false);
1689 const workspaceUpdate = yield* registry.streamChanges.pipe(
1690 Stream.runHead,
1691 Effect.forkChild,
1692 );
1693 yield* Effect.yieldNow;
1694 const firstRefresh = yield* registry
1695 .refreshWorkspaceSnapshot({ instanceId, cwd: "/workspace" })
1696 .pipe(Effect.forkChild);
1697 yield* Deferred.await(probeStarted);
1698 const duplicateRefresh = yield* registry
1699 .refreshWorkspaceSnapshot({ instanceId, cwd: "/workspace" })
1700 .pipe(Effect.forkChild);
1701 yield* Effect.yieldNow;
1702 assert.strictEqual(yield* Ref.get(snapshotCalls), 2);
1703 yield* Deferred.succeed(releaseProbe, undefined);
1704 yield* Fiber.join(firstRefresh);
1705 yield* Fiber.join(duplicateRefresh);
1706 const published = yield* Fiber.join(workspaceUpdate);
1707 assert.strictEqual(published._tag, "Some");
1708 const providers = yield* registry.getProviders;
1709 assert.deepStrictEqual(providers[0]?.skills, machineProvider.skills);
1710 assert.deepStrictEqual(
1711 providers[0]?.workspaceSnapshots?.[0]?.skills,
1712 scopedProvider.skills,
1713 );
1714 assert.deepStrictEqual(providers[0]?.workspaceSnapshots?.[0]?.slashCommands, [
1715 COMPACT_SLASH_COMMAND,
1716 ]);
1717 assert.strictEqual(providers[0]?.workspaceSnapshots?.[0]?.slashCommandsPending, true);
1718 yield* Ref.set(scopedResult, {
1719 ...scopedProvider,
1720 status: "error",
1721 slashCommandsPending: false,
1722 });
1723 yield* registry.refreshWorkspaceSnapshot({ instanceId, cwd: "/workspace" });
1724 assert.strictEqual(yield* Ref.get(snapshotCalls), 3);
1725 assert.deepStrictEqual(
1726 (yield* registry.getProviders)[0]?.workspaceSnapshots?.[0]?.slashCommands,
1727 scopedProvider.slashCommands,
1728 );
1729 assert.strictEqual(
1730 (yield* registry.getProviders)[0]?.workspaceSnapshots?.[0]?.slashCommandsPending,
1731 undefined,
1732 );
1733 yield* registry.refreshWorkspaceSnapshot({ instanceId, cwd: "/workspace" });
1734 assert.strictEqual(yield* Ref.get(snapshotCalls), 3);
1735 const newSkills = [
1736 ...scopedProvider.skills,
1737 { name: "added", path: "/workspace/added/SKILL.md", enabled: true },
1738 ];
1739 yield* Ref.set(scopedResult, { ...scopedProvider, skills: newSkills });
1740 yield* registry.refreshWorkspaceSnapshot({
1741 instanceId,
1742 cwd: "/workspace",
1743 fresh: true,
1744 });
1745 assert.strictEqual(yield* Ref.get(snapshotCalls), 4);
1746 assert.strictEqual(yield* Ref.get(cacheInvalidations), 1);
1747 assert.deepStrictEqual(
1748 (yield* registry.getProviders)[0]?.workspaceSnapshots?.map((s) => s.skills),
1749 [newSkills],
1750 );
23001751
2301 assert.deepStrictEqual(cachedProvider?.models, [authoritativeProvider.models[0]!]);
1752 // A slow fresh scan that read older files must not overwrite a
1753 // newer scan that finished first.
1754 const slowStarted = yield* Deferred.make<void>();
1755 const releaseSlow = yield* Deferred.make<void>();
1756 yield* Ref.set(scanGate, { started: slowStarted, release: releaseSlow });
1757 yield* Ref.set(scopedResult, scopedProvider);
1758 const slowScan = yield* registry
1759 .refreshWorkspaceSnapshot({ instanceId, cwd: "/workspace", fresh: true })
1760 .pipe(Effect.forkChild);
1761 yield* Deferred.await(slowStarted);
1762 const latestSkills = [
1763 ...newSkills,
1764 { name: "latest", path: "/workspace/latest/SKILL.md", enabled: true },
1765 ];
1766 yield* Ref.set(scopedResult, { ...scopedProvider, skills: latestSkills });
1767 yield* registry.refreshWorkspaceSnapshot({
1768 instanceId,
1769 cwd: "/workspace",
1770 fresh: true,
1771 });
1772 yield* Deferred.succeed(releaseSlow, undefined);
1773 yield* Fiber.join(slowScan);
1774 assert.deepStrictEqual(
1775 (yield* registry.getProviders)[0]?.workspaceSnapshots?.map((s) => s.skills),
1776 [latestSkills],
1777 );
23021778
2303 const failedPersisted = yield* awaitPersistedProvider(
2304 registry,
2305 failedProvider.checkedAt,
2306 );
2307 yield* PubSub.publish(changes, failedProvider);
2308 yield* Fiber.join(failedPersisted);
2309 cachedProvider = yield* readProviderStatusCache(filePath);
2310
2311 assert.deepStrictEqual(cachedProvider?.models, [authoritativeProvider.models[0]!]);
2312 assert.deepStrictEqual((yield* registry.getProviders)[0]?.models, [
2313 authoritativeProvider.models[0]!,
2314 ]);
2315 }).pipe(Effect.provide(runtimeServices));
2316 }),
2317 );
1779 yield* Ref.set(instancesRef, [rebuiltInstance]);
1780 yield* PubSub.publish(registryChanges, undefined);
1781 let rebuilt = yield* registry.getProviders;
1782 for (
1783 let attempt = 0;
1784 attempt < 50 && rebuilt[0]?.checkedAt !== rebuiltProvider.checkedAt;
1785 attempt += 1
1786 ) {
1787 yield* Effect.yieldNow;
1788 rebuilt = yield* registry.getProviders;
1789 }
1790 assert.strictEqual(rebuilt[0]?.checkedAt, rebuiltProvider.checkedAt);
1791 assert.strictEqual(rebuilt[0]?.workspaceSnapshots, undefined);
1792 }).pipe(Effect.provide(runtimeServices));
1793 }),
1794 );
23181795
2319 it.effect("returns the cached provider list when a manual refresh fails", () =>
2320 Effect.gen(function* () {
2321 const codexDriver = ProviderDriverKind.make("codex");
2322 const codexInstanceId = ProviderInstanceId.make("codex");
2323 const cachedProvider = {
2324 instanceId: codexInstanceId,
2325 driver: codexDriver,
2326 status: "ready",
2327 enabled: true,
2328 installed: true,
2329 auth: { status: "authenticated" },
2330 checkedAt: "2026-04-29T10:00:00.000Z",
2331 version: "1.0.0",
2332 models: [],
2333 slashCommands: [],
2334 skills: [],
2335 } as const satisfies ServerProvider;
2336 const instance = {
1796 it.effect("refreshes OpenCode catalogs and preserves other providers", () =>
1797 Effect.gen(function* () {
1798 const codexDriver = ProviderDriverKind.make("codex");
1799 const openCodeDriver = ProviderDriverKind.make("opencode");
1800 const codexInstanceId = ProviderInstanceId.make("codex");
1801 const openCodeInstanceId = ProviderInstanceId.make("opencode");
1802 const codexRefreshCalls = yield* Ref.make(0);
1803 const openCodeRefreshCalls = yield* Ref.make(0);
1804 const codexProvider = {
1805 instanceId: codexInstanceId,
1806 driver: codexDriver,
1807 status: "ready",
1808 enabled: true,
1809 installed: true,
1810 auth: { status: "authenticated" },
1811 checkedAt: "2026-06-10T00:00:00.000Z",
1812 version: "1.0.0",
1813 models: [],
1814 slashCommands: [],
1815 skills: [],
1816 } as const satisfies ServerProvider;
1817 const failedOpenCodeProvider = {
1818 instanceId: openCodeInstanceId,
1819 driver: openCodeDriver,
1820 status: "error",
1821 enabled: true,
1822 installed: true,
1823 auth: { status: "unknown" },
1824 checkedAt: "2026-06-10T00:00:00.000Z",
1825 version: "1.0.0",
1826 message: "Failed to refresh OpenCode models.",
1827 models: [],
1828 slashCommands: [],
1829 skills: [],
1830 } as const satisfies ServerProvider;
1831 const recoveredOpenCodeProvider = {
1832 ...failedOpenCodeProvider,
1833 status: "ready",
1834 auth: { status: "authenticated" },
1835 checkedAt: "2026-06-10T00:01:00.000Z",
1836 message: "One upstream provider connected through OpenCode.",
1837 models: [
1838 {
1839 slug: "github/gpt-5",
1840 name: "GPT-5",
1841 subProvider: "GitHub",
1842 isCustom: false,
1843 capabilities: null,
1844 },
1845 ],
1846 } as const satisfies ServerProvider;
1847 const changedCatalogProvider = {
1848 ...recoveredOpenCodeProvider,
1849 checkedAt: "2026-06-10T00:02:00.000Z",
1850 models: [
1851 {
1852 slug: "anthropic/claude-sonnet-4",
1853 name: "Claude Sonnet 4",
1854 subProvider: "Anthropic",
1855 isCustom: false,
1856 capabilities: null,
1857 },
1858 ],
1859 } as const satisfies ServerProvider;
1860 const catalogSnapshot = yield* Ref.make<ServerProvider>(recoveredOpenCodeProvider);
1861 const instances = [
1862 {
23371863 instanceId: codexInstanceId,
23381864 driverKind: codexDriver,
23391865 continuationIdentity: {
......@@ -2350,96 +1876,339 @@ it.layer(Layer.mergeAll(TestNodeServices, ServerSettingsModule.layerTest(), Test
23501876 packageName: null,
23511877 }),
23521878 ),
2353 getSnapshot: Effect.succeed(cachedProvider),
2354 refresh: Effect.die(new Error("simulated refresh failure")),
1879 getSnapshot: Effect.succeed(codexProvider),
1880 refresh: Ref.update(codexRefreshCalls, (count) => count + 1).pipe(
1881 Effect.as(codexProvider),
1882 ),
23551883 streamChanges: Stream.empty,
23561884 applyUsageLimits: () => Effect.void,
23571885 },
23581886 orchestrationAdapter: {} as ProviderInstance["orchestrationAdapter"],
23591887 textGeneration: {} as ProviderInstance["textGeneration"],
2360 } satisfies ProviderInstance;
2361 const instanceRegistryLayer = Layer.succeed(
2362 ProviderInstanceRegistry.ProviderInstanceRegistry,
2363 {
2364 getInstance: (instanceId) =>
2365 Effect.succeed(instanceId === codexInstanceId ? instance : undefined),
2366 listInstances: Effect.succeed([instance]),
2367 listUnavailable: Effect.succeed([]),
2368 streamChanges: Stream.empty,
2369 subscribeChanges: Effect.flatMap(PubSub.unbounded<void>(), (pubsub) =>
2370 PubSub.subscribe(pubsub),
1888 },
1889 {
1890 instanceId: openCodeInstanceId,
1891 driverKind: openCodeDriver,
1892 continuationIdentity: {
1893 driverKind: openCodeDriver,
1894 continuationKey: "opencode:instance:opencode",
1895 },
1896 displayName: undefined,
1897 enabled: true,
1898 snapshot: {
1899 resolveMaintenance: () =>
1900 Effect.succeed(
1901 makeManualOnlyProviderMaintenanceCapabilities({
1902 provider: openCodeDriver,
1903 packageName: null,
1904 }),
1905 ),
1906 getSnapshot: Effect.succeed(failedOpenCodeProvider),
1907 refresh: Ref.update(openCodeRefreshCalls, (count) => count + 1).pipe(
1908 Effect.andThen(Ref.get(catalogSnapshot)),
23711909 ),
1910 streamChanges: Stream.empty,
1911 applyUsageLimits: () => Effect.void,
23721912 },
1913 orchestrationAdapter: {} as ProviderInstance["orchestrationAdapter"],
1914 textGeneration: {} as ProviderInstance["textGeneration"],
1915 },
1916 ] satisfies ReadonlyArray<ProviderInstance>;
1917 const layerInstanceRegistry = Layer.succeed(
1918 ProviderInstanceRegistry.ProviderInstanceRegistry,
1919 {
1920 getInstance: (instanceId) =>
1921 Effect.succeed(instances.find((instance) => instance.instanceId === instanceId)),
1922 listInstances: Effect.succeed(instances),
1923 listUnavailable: Effect.succeed([]),
1924 streamChanges: Stream.empty,
1925 subscribeChanges: Effect.flatMap(PubSub.unbounded<void>(), PubSub.subscribe),
1926 },
1927 );
1928 const scope = yield* Scope.make();
1929 yield* Effect.addFinalizer(() => Scope.close(scope, Exit.void));
1930 const runtimeServices = yield* Layer.build(
1931 ProviderRegistryLayer.layer.pipe(
1932 Layer.provideMerge(layerInstanceRegistry),
1933 Layer.provideMerge(
1934 ServerConfig.layerTest(process.cwd(), {
1935 prefix: "t3-provider-registry-reconnect-refresh-",
1936 }),
1937 ),
1938 Layer.provideMerge(NodeServices.layer),
1939 ),
1940 ).pipe(Scope.provide(scope));
1941
1942 yield* Effect.gen(function* () {
1943 const registry = yield* ProviderRegistry.ProviderRegistry;
1944 const initialProviders = yield* registry.getProviders;
1945 assert.strictEqual(
1946 initialProviders.find((provider) => provider.instanceId === openCodeInstanceId)?.status,
1947 "error",
23731948 );
2374 const scope = yield* Scope.make();
2375 yield* Effect.addFinalizer(() => Scope.close(scope, Exit.void));
2376 const runtimeServices = yield* Layer.build(
2377 ProviderRegistryLive.pipe(
2378 Layer.provideMerge(instanceRegistryLayer),
2379 Layer.provideMerge(
2380 ServerConfig.layerTest(process.cwd(), {
2381 prefix: "t3-provider-registry-refresh-failure-",
1949
1950 const recoveredProviders = yield* registry.refresh();
1951 assert.deepStrictEqual(
1952 recoveredProviders.find((provider) => provider.instanceId === openCodeInstanceId)
1953 ?.models,
1954 recoveredOpenCodeProvider.models,
1955 );
1956 assert.deepStrictEqual(
1957 recoveredProviders.find((provider) => provider.instanceId === codexInstanceId),
1958 withBundledCompatibility(codexProvider),
1959 );
1960
1961 yield* Ref.set(catalogSnapshot, changedCatalogProvider);
1962 const changedProviders = yield* registry.refresh();
1963 assert.deepStrictEqual(
1964 changedProviders.find((provider) => provider.instanceId === openCodeInstanceId)?.models,
1965 changedCatalogProvider.models,
1966 );
1967 assert.deepStrictEqual(
1968 changedProviders.find((provider) => provider.instanceId === codexInstanceId),
1969 withBundledCompatibility(codexProvider),
1970 );
1971 }).pipe(Effect.provide(runtimeServices));
1972
1973 assert.strictEqual(yield* Ref.get(codexRefreshCalls), 2);
1974 assert.strictEqual(yield* Ref.get(openCodeRefreshCalls), 2);
1975 }),
1976 );
1977
1978 it("persists merged provider snapshots for the providers that were refreshed", () => {
1979 const previousProviders = [
1980 {
1981 instanceId: ProviderInstanceId.make("cursor"),
1982 driver: ProviderDriverKind.make("cursor"),
1983 status: "ready",
1984 enabled: true,
1985 installed: true,
1986 auth: { status: "authenticated" },
1987 checkedAt: "2026-04-14T00:00:00.000Z",
1988 version: "2026.04.09-f2b0fcd",
1989 models: [
1990 {
1991 slug: "claude-opus-4-6",
1992 name: "Opus 4.6",
1993 isCustom: false,
1994 capabilities: createModelCapabilities({
1995 optionDescriptors: [
1996 selectDescriptor("reasoning", "Reasoning", [
1997 { id: "high", label: "High", isDefault: true },
1998 ]),
1999 booleanDescriptor("fastMode", "Fast Mode"),
2000 booleanDescriptor("thinking", "Thinking"),
2001 ],
2002 }),
2003 },
2004 ],
2005 slashCommands: [],
2006 skills: [],
2007 },
2008 {
2009 instanceId: ProviderInstanceId.make("codex"),
2010 driver: ProviderDriverKind.make("codex"),
2011 status: "ready",
2012 enabled: true,
2013 installed: true,
2014 auth: { status: "authenticated" },
2015 checkedAt: "2026-04-14T00:00:00.000Z",
2016 version: "1.0.0",
2017 models: [],
2018 slashCommands: [],
2019 skills: [],
2020 },
2021 ] as const satisfies ReadonlyArray<ServerProvider>;
2022 const refreshedCursor = {
2023 ...previousProviders[0],
2024 checkedAt: "2026-04-14T00:01:00.000Z",
2025 models: [],
2026 } satisfies ServerProvider;
2027
2028 const mergedProviders = mergeProviderSnapshots(previousProviders, [refreshedCursor]);
2029 const persistedProviders = selectProvidersByKind(
2030 mergedProviders,
2031 new Set([ProviderDriverKind.make("cursor")]),
2032 );
2033
2034 assert.deepStrictEqual(persistedProviders, [
2035 {
2036 ...refreshedCursor,
2037 models: [...previousProviders[0].models],
2038 },
2039 ]);
2040 });
2041
2042 it.effect("persists the merged snapshot when a live update has empty models", () =>
2043 Effect.gen(function* () {
2044 const cursorDriver = ProviderDriverKind.make("cursor");
2045 const cursorInstanceId = ProviderInstanceId.make("cursor");
2046 const initialProvider = {
2047 instanceId: cursorInstanceId,
2048 driver: cursorDriver,
2049 status: "ready",
2050 enabled: true,
2051 installed: true,
2052 auth: { status: "authenticated" },
2053 checkedAt: "2026-04-14T00:00:00.000Z",
2054 version: "2026.04.09-f2b0fcd",
2055 models: [
2056 {
2057 slug: "claude-opus-4-6",
2058 name: "Opus 4.6",
2059 isCustom: false,
2060 capabilities: createModelCapabilities({
2061 optionDescriptors: [
2062 selectDescriptor("reasoning", "Reasoning", [
2063 { id: "high", label: "High", isDefault: true },
2064 ]),
2065 ],
2066 }),
2067 },
2068 ],
2069 slashCommands: [],
2070 skills: [],
2071 } as const satisfies ServerProvider;
2072 const refreshedProvider = {
2073 ...initialProvider,
2074 checkedAt: "2026-04-14T00:01:00.000Z",
2075 models: [],
2076 } satisfies ServerProvider;
2077 const changes = yield* PubSub.unbounded<ServerProvider>();
2078 const instance = {
2079 instanceId: cursorInstanceId,
2080 driverKind: cursorDriver,
2081 continuationIdentity: {
2082 driverKind: cursorDriver,
2083 continuationKey: "cursor:instance:cursor",
2084 },
2085 displayName: undefined,
2086 enabled: true,
2087 snapshot: {
2088 resolveMaintenance: () =>
2089 Effect.succeed(
2090 makeManualOnlyProviderMaintenanceCapabilities({
2091 provider: cursorDriver,
2092 packageName: null,
23822093 }),
23832094 ),
2384 Layer.provideMerge(BackgroundPolicyAlwaysRunLayer),
2385 Layer.provideMerge(NodeServices.layer),
2095 getSnapshot: Effect.succeed(initialProvider),
2096 refresh: Effect.succeed(refreshedProvider),
2097 streamChanges: Stream.fromPubSub(changes),
2098 applyUsageLimits: () => Effect.void,
2099 },
2100 orchestrationAdapter: {} as ProviderInstance["orchestrationAdapter"],
2101 textGeneration: {} as ProviderInstance["textGeneration"],
2102 } satisfies ProviderInstance;
2103 const layerInstanceRegistry = Layer.succeed(
2104 ProviderInstanceRegistry.ProviderInstanceRegistry,
2105 {
2106 getInstance: (instanceId) =>
2107 Effect.succeed(instanceId === cursorInstanceId ? instance : undefined),
2108 listInstances: Effect.succeed([instance]),
2109 listUnavailable: Effect.succeed([]),
2110 streamChanges: Stream.empty,
2111 subscribeChanges: Effect.flatMap(PubSub.unbounded<void>(), (pubsub) =>
2112 PubSub.subscribe(pubsub),
23862113 ),
2387 ).pipe(Scope.provide(scope));
2114 },
2115 );
2116 const scope = yield* Scope.make();
2117 yield* Effect.addFinalizer(() => Scope.close(scope, Exit.void));
2118 const runtimeServices = yield* Layer.build(
2119 ProviderRegistryLayer.layer.pipe(
2120 Layer.provideMerge(layerInstanceRegistry),
2121 Layer.provideMerge(
2122 ServerConfig.layerTest(process.cwd(), {
2123 prefix: "t3-provider-registry-merged-persist-",
2124 }),
2125 ),
2126 Layer.provideMerge(layerBackgroundPolicyAlwaysRun),
2127 Layer.provideMerge(NodeServices.layer),
2128 ),
2129 ).pipe(Scope.provide(scope));
23882130
2389 yield* Effect.gen(function* () {
2390 const registry = yield* ProviderRegistry.ProviderRegistry;
2131 yield* Effect.gen(function* () {
2132 const registry = yield* ProviderRegistry.ProviderRegistry;
2133 const config = yield* ServerConfig.ServerConfig;
2134 const filePath = yield* resolveProviderStatusCachePath({
2135 cacheDir: config.providerStatusCacheDir,
2136 instanceId: cursorInstanceId,
2137 });
23912138
2392 assert.deepStrictEqual(yield* registry.getProviders, [
2393 withBundledCompatibility(cachedProvider),
2394 ]);
2395 assert.deepStrictEqual(yield* registry.refresh(codexDriver), [
2396 withBundledCompatibility(cachedProvider),
2397 ]);
2398 assert.deepStrictEqual(yield* registry.refreshInstance(codexInstanceId), [
2399 withBundledCompatibility(cachedProvider),
2400 ]);
2401 }).pipe(Effect.provide(runtimeServices));
2402 }),
2403 );
2139 assert.deepStrictEqual((yield* registry.getProviders)[0]?.models, [
2140 ...initialProvider.models,
2141 ]);
2142 const persisted = yield* awaitPersistedProvider(registry, refreshedProvider.checkedAt);
2143 yield* PubSub.publish(changes, refreshedProvider);
2144 yield* Fiber.join(persisted);
2145 const cachedProvider = yield* readProviderStatusCache(filePath);
2146
2147 assert.deepStrictEqual(
2148 cachedProvider,
2149 withBundledCompatibility({
2150 ...refreshedProvider,
2151 models: [...initialProvider.models],
2152 }),
2153 );
2154 }).pipe(Effect.provide(runtimeServices));
2155 }),
2156 );
24042157
2405 it.effect("keeps consuming registry changes after one sync fails", () =>
2158 it.effect(
2159 "persists authoritative OpenCode removals without resurrecting them on a failed live refresh",
2160 () =>
24062161 Effect.gen(function* () {
2407 const codexDriver = ProviderDriverKind.make("codex");
2408 const codexInstanceId = ProviderInstanceId.make("codex");
2409 const claudeDriver = ProviderDriverKind.make("claudeAgent");
2410 const claudeInstanceId = ProviderInstanceId.make("claudeAgent");
2411 const codexProvider = {
2412 instanceId: codexInstanceId,
2413 driver: codexDriver,
2162 const openCodeDriver = ProviderDriverKind.make("opencode");
2163 const openCodeInstanceId = ProviderInstanceId.make("opencode");
2164 const initialProvider = {
2165 instanceId: openCodeInstanceId,
2166 driver: openCodeDriver,
24142167 status: "ready",
24152168 enabled: true,
24162169 installed: true,
24172170 auth: { status: "authenticated" },
2418 checkedAt: "2026-04-29T10:00:00.000Z",
2171 checkedAt: "2026-07-17T00:00:00.000Z",
24192172 version: "1.0.0",
2420 models: [],
2173 models: [
2174 {
2175 slug: "github/gpt-5",
2176 name: "GPT-5",
2177 subProvider: "GitHub",
2178 isCustom: false,
2179 capabilities: null,
2180 },
2181 {
2182 slug: "removed-plugin/model",
2183 name: "Removed Plugin Model",
2184 subProvider: "Removed Plugin",
2185 isCustom: false,
2186 capabilities: null,
2187 },
2188 ],
24212189 slashCommands: [],
24222190 skills: [],
24232191 } as const satisfies ServerProvider;
2424 const claudeProvider = {
2425 instanceId: claudeInstanceId,
2426 driver: claudeDriver,
2427 status: "ready",
2428 enabled: true,
2429 installed: true,
2430 auth: { status: "authenticated" },
2431 checkedAt: "2026-04-29T10:01:00.000Z",
2432 version: "1.0.0",
2192 const authoritativeProvider = {
2193 ...initialProvider,
2194 checkedAt: "2026-07-17T00:01:00.000Z",
2195 models: [initialProvider.models[0]!],
2196 } satisfies ServerProvider;
2197 const failedProvider = {
2198 ...authoritativeProvider,
2199 status: "error",
2200 auth: { status: "unknown" },
2201 checkedAt: "2026-07-17T00:02:00.000Z",
24332202 models: [],
2434 slashCommands: [],
2435 skills: [],
2436 } as const satisfies ServerProvider;
2437 const makeInstance = (provider: ServerProvider): ProviderInstance => ({
2438 instanceId: provider.instanceId,
2439 driverKind: provider.driver,
2203 message: "Failed to refresh OpenCode models.",
2204 } satisfies ServerProvider;
2205 const changes = yield* PubSub.unbounded<ServerProvider>();
2206 const instance = {
2207 instanceId: openCodeInstanceId,
2208 driverKind: openCodeDriver,
24402209 continuationIdentity: {
2441 driverKind: provider.driver,
2442 continuationKey: `${provider.driver}:instance:${provider.instanceId}`,
2210 driverKind: openCodeDriver,
2211 continuationKey: "opencode:instance:opencode",
24432212 },
24442213 displayName: undefined,
24452214 enabled: true,
......@@ -2447,372 +2216,636 @@ it.layer(Layer.mergeAll(TestNodeServices, ServerSettingsModule.layerTest(), Test
24472216 resolveMaintenance: () =>
24482217 Effect.succeed(
24492218 makeManualOnlyProviderMaintenanceCapabilities({
2450 provider: provider.driver,
2219 provider: openCodeDriver,
24512220 packageName: null,
24522221 }),
24532222 ),
2454 getSnapshot: Effect.succeed(provider),
2455 refresh: Effect.succeed(provider),
2456 streamChanges: Stream.empty,
2223 getSnapshot: Effect.succeed(initialProvider),
2224 refresh: Effect.succeed(authoritativeProvider),
2225 streamChanges: Stream.fromPubSub(changes),
24572226 applyUsageLimits: () => Effect.void,
24582227 },
24592228 orchestrationAdapter: {} as ProviderInstance["orchestrationAdapter"],
24602229 textGeneration: {} as ProviderInstance["textGeneration"],
2461 });
2462 const codexInstance = makeInstance(codexProvider);
2463 const claudeInstance = makeInstance(claudeProvider);
2464 const changes = yield* PubSub.unbounded<void>();
2465 const instancesRef = yield* Ref.make<ReadonlyArray<ProviderInstance>>([codexInstance]);
2466 const failNextList = yield* Ref.make(false);
2467 const wait = () => Effect.yieldNow;
2468 const instanceRegistryLayer = Layer.succeed(
2230 } satisfies ProviderInstance;
2231 const layerInstanceRegistry = Layer.succeed(
24692232 ProviderInstanceRegistry.ProviderInstanceRegistry,
24702233 {
24712234 getInstance: (instanceId) =>
2472 Ref.get(instancesRef).pipe(
2473 Effect.map((instances) =>
2474 instances.find((instance) => instance.instanceId === instanceId),
2475 ),
2476 ),
2477 listInstances: Effect.gen(function* () {
2478 const shouldFail = yield* Ref.get(failNextList);
2479 if (shouldFail) {
2480 yield* Ref.set(failNextList, false);
2481 return yield* Effect.die(new Error("simulated registry list failure"));
2482 }
2483 return yield* Ref.get(instancesRef);
2484 }),
2235 Effect.succeed(instanceId === openCodeInstanceId ? instance : undefined),
2236 listInstances: Effect.succeed([instance]),
24852237 listUnavailable: Effect.succeed([]),
2486 streamChanges: Stream.fromPubSub(changes),
2487 subscribeChanges: PubSub.subscribe(changes),
2238 streamChanges: Stream.empty,
2239 subscribeChanges: Effect.flatMap(PubSub.unbounded<void>(), (pubsub) =>
2240 PubSub.subscribe(pubsub),
2241 ),
24882242 },
24892243 );
24902244 const scope = yield* Scope.make();
24912245 yield* Effect.addFinalizer(() => Scope.close(scope, Exit.void));
24922246 const runtimeServices = yield* Layer.build(
2493 ProviderRegistryLive.pipe(
2494 Layer.provideMerge(instanceRegistryLayer),
2247 ProviderRegistryLayer.layer.pipe(
2248 Layer.provideMerge(layerInstanceRegistry),
24952249 Layer.provideMerge(
24962250 ServerConfig.layerTest(process.cwd(), {
2497 prefix: "t3-provider-registry-sync-failure-",
2251 prefix: "t3-provider-registry-opencode-authoritative-persist-",
24982252 }),
24992253 ),
2500 Layer.provideMerge(BackgroundPolicyAlwaysRunLayer),
25012254 Layer.provideMerge(NodeServices.layer),
25022255 ),
25032256 ).pipe(Scope.provide(scope));
25042257
25052258 yield* Effect.gen(function* () {
25062259 const registry = yield* ProviderRegistry.ProviderRegistry;
2507 assert.deepStrictEqual(yield* registry.getProviders, [
2508 withBundledCompatibility(codexProvider),
2509 ]);
2260 const config = yield* ServerConfig.ServerConfig;
2261 const filePath = yield* resolveProviderStatusCachePath({
2262 cacheDir: config.providerStatusCacheDir,
2263 instanceId: openCodeInstanceId,
2264 });
25102265
2511 yield* Ref.set(failNextList, true);
2512 yield* PubSub.publish(changes, undefined);
2513
2514 yield* Ref.set(instancesRef, [codexInstance, claudeInstance]);
2515 yield* PubSub.publish(changes, undefined);
2516
2517 let providers = yield* registry.getProviders;
2518 for (
2519 let attempt = 0;
2520 attempt < 50 &&
2521 !providers.some((provider) => provider.instanceId === claudeInstanceId);
2522 attempt += 1
2523 ) {
2524 yield* wait();
2525 providers = yield* registry.getProviders;
2526 }
2266 const authoritativePersisted = yield* awaitPersistedProvider(
2267 registry,
2268 authoritativeProvider.checkedAt,
2269 );
2270 yield* PubSub.publish(changes, authoritativeProvider);
2271 yield* Fiber.join(authoritativePersisted);
2272 let cachedProvider = yield* readProviderStatusCache(filePath);
25272273
2528 assert.deepStrictEqual(
2529 providers.map((provider) => provider.instanceId).toSorted(),
2530 [codexInstanceId, claudeInstanceId].toSorted(),
2274 assert.deepStrictEqual(cachedProvider?.models, [authoritativeProvider.models[0]!]);
2275
2276 const failedPersisted = yield* awaitPersistedProvider(
2277 registry,
2278 failedProvider.checkedAt,
25312279 );
2280 yield* PubSub.publish(changes, failedProvider);
2281 yield* Fiber.join(failedPersisted);
2282 cachedProvider = yield* readProviderStatusCache(filePath);
2283
2284 assert.deepStrictEqual(cachedProvider?.models, [authoritativeProvider.models[0]!]);
2285 assert.deepStrictEqual((yield* registry.getProviders)[0]?.models, [
2286 authoritativeProvider.models[0]!,
2287 ]);
25322288 }).pipe(Effect.provide(runtimeServices));
25332289 }),
2534 );
2290 );
25352291
2536 // This test intentionally avoids `mockCommandSpawnerLayer` so the real
2537 // `probeCodexAppServerProvider` path runs — including the full
2538 // `codex app-server` RPC handshake via `CodexClient.layerChildProcess`.
2539 // We point `binaryPath` at a name that cannot exist on any machine so
2540 // the real `ChildProcessSpawner` deterministically returns ENOENT; the
2541 // probe wraps that as `CodexAppServerSpawnError` and
2542 // `checkCodexProviderStatus` turns it into the user-visible "not
2543 // installed" error snapshot. If the aggregator's `syncLiveSources`
2544 // breaks — the `codex_personal`-never-probes bug we are guarding
2545 // against — that snapshot never lands in `getProviders` and the
2546 // assertions below fail.
2547 it.effect("propagates real Codex probe failures to the aggregator at boot", () =>
2548 Effect.gen(function* () {
2549 const missingBinary = `t3code_codex_missing_`;
2550 const serverSettings = yield* makeMutableServerSettingsService(
2551 decodeServerSettings(
2552 deepMerge(encodedDefaultServerSettings, {
2553 providers: {
2554 // Disable every built-in probe that would otherwise spawn
2555 // on the CI host. `enabled: false` short-circuits each
2556 // driver's probe *before* it touches the spawner, so the
2557 // test environment stays isolated from the dev
2558 // machine's PATH.
2559 codex: { enabled: false },
2560 claudeAgent: { enabled: false },
2561 cursor: { enabled: false },
2562 grok: { enabled: false },
2563 opencode: { enabled: false },
2564 },
2565 // `providerInstances` keys are branded `ProviderInstanceId`;
2566 // the branded index signature rejects plain string literals
2567 // at the TS level even though the runtime schema happily
2568 // accepts + decodes them. Cast the patch to `unknown` so
2569 // the `Schema.decodeSync` below does the real validation.
2570 providerInstances: {
2571 // Matches the shape the user had in `.t3/dev/settings.json`
2572 // when the bug was reported: a custom enabled Codex instance
2573 // pointing at a binary the server has to actually spawn.
2574 codex_personal: {
2575 driver: "codex",
2576 displayName: "Codex Personal",
2577 enabled: true,
2578 config: {
2579 binaryPath: missingBinary,
2580 homePath: `/tmp/${missingBinary}_home`,
2581 },
2582 },
2583 } as unknown as ContractServerSettings["providerInstances"],
2584 }),
2585 ),
2586 );
2587 const scope = yield* Scope.make();
2588 yield* Effect.addFinalizer(() => Scope.close(scope, Exit.void));
2589 const providerRegistryLayer = ProviderRegistryLive.pipe(
2590 Layer.provideMerge(ProviderInstanceRegistryHydrationLive),
2591 Layer.provideMerge(AntigravityInstallation.AntigravityInstallation.layer),
2592 Layer.provideMerge(
2593 Layer.succeed(ServerSettingsModule.ServerSettingsService, serverSettings),
2292 it.effect("returns the cached provider list when a manual refresh fails", () =>
2293 Effect.gen(function* () {
2294 const codexDriver = ProviderDriverKind.make("codex");
2295 const codexInstanceId = ProviderInstanceId.make("codex");
2296 const cachedProvider = {
2297 instanceId: codexInstanceId,
2298 driver: codexDriver,
2299 status: "ready",
2300 enabled: true,
2301 installed: true,
2302 auth: { status: "authenticated" },
2303 checkedAt: "2026-04-29T10:00:00.000Z",
2304 version: "1.0.0",
2305 models: [],
2306 slashCommands: [],
2307 skills: [],
2308 } as const satisfies ServerProvider;
2309 const instance = {
2310 instanceId: codexInstanceId,
2311 driverKind: codexDriver,
2312 continuationIdentity: {
2313 driverKind: codexDriver,
2314 continuationKey: "codex:instance:codex",
2315 },
2316 displayName: undefined,
2317 enabled: true,
2318 snapshot: {
2319 resolveMaintenance: () =>
2320 Effect.succeed(
2321 makeManualOnlyProviderMaintenanceCapabilities({
2322 provider: codexDriver,
2323 packageName: null,
2324 }),
2325 ),
2326 getSnapshot: Effect.succeed(cachedProvider),
2327 refresh: Effect.die(new Error("simulated refresh failure")),
2328 streamChanges: Stream.empty,
2329 applyUsageLimits: () => Effect.void,
2330 },
2331 orchestrationAdapter: {} as ProviderInstance["orchestrationAdapter"],
2332 textGeneration: {} as ProviderInstance["textGeneration"],
2333 } satisfies ProviderInstance;
2334 const layerInstanceRegistry = Layer.succeed(
2335 ProviderInstanceRegistry.ProviderInstanceRegistry,
2336 {
2337 getInstance: (instanceId) =>
2338 Effect.succeed(instanceId === codexInstanceId ? instance : undefined),
2339 listInstances: Effect.succeed([instance]),
2340 listUnavailable: Effect.succeed([]),
2341 streamChanges: Stream.empty,
2342 subscribeChanges: Effect.flatMap(PubSub.unbounded<void>(), (pubsub) =>
2343 PubSub.subscribe(pubsub),
25942344 ),
2595 Layer.provideMerge(ServerSecretStore.layer),
2345 },
2346 );
2347 const scope = yield* Scope.make();
2348 yield* Effect.addFinalizer(() => Scope.close(scope, Exit.void));
2349 const runtimeServices = yield* Layer.build(
2350 ProviderRegistryLayer.layer.pipe(
2351 Layer.provideMerge(layerInstanceRegistry),
25962352 Layer.provideMerge(
25972353 ServerConfig.layerTest(process.cwd(), {
2598 prefix: "t3-provider-registry-",
2354 prefix: "t3-provider-registry-refresh-failure-",
25992355 }),
26002356 ),
2601 Layer.provideMerge(TestHttpClientLive),
2602 Layer.provideMerge(
2603 Layer.succeed(
2604 ProviderEventLoggers.ProviderEventLoggers,
2605 ProviderEventLoggers.NoOpProviderEventLoggers,
2357 Layer.provideMerge(layerBackgroundPolicyAlwaysRun),
2358 Layer.provideMerge(NodeServices.layer),
2359 ),
2360 ).pipe(Scope.provide(scope));
2361
2362 yield* Effect.gen(function* () {
2363 const registry = yield* ProviderRegistry.ProviderRegistry;
2364
2365 assert.deepStrictEqual(yield* registry.getProviders, [
2366 withBundledCompatibility(cachedProvider),
2367 ]);
2368 assert.deepStrictEqual(yield* registry.refresh(codexDriver), [
2369 withBundledCompatibility(cachedProvider),
2370 ]);
2371 assert.deepStrictEqual(yield* registry.refreshInstance(codexInstanceId), [
2372 withBundledCompatibility(cachedProvider),
2373 ]);
2374 }).pipe(Effect.provide(runtimeServices));
2375 }),
2376 );
2377
2378 it.effect("keeps consuming registry changes after one sync fails", () =>
2379 Effect.gen(function* () {
2380 const codexDriver = ProviderDriverKind.make("codex");
2381 const codexInstanceId = ProviderInstanceId.make("codex");
2382 const claudeDriver = ProviderDriverKind.make("claudeAgent");
2383 const claudeInstanceId = ProviderInstanceId.make("claudeAgent");
2384 const codexProvider = {
2385 instanceId: codexInstanceId,
2386 driver: codexDriver,
2387 status: "ready",
2388 enabled: true,
2389 installed: true,
2390 auth: { status: "authenticated" },
2391 checkedAt: "2026-04-29T10:00:00.000Z",
2392 version: "1.0.0",
2393 models: [],
2394 slashCommands: [],
2395 skills: [],
2396 } as const satisfies ServerProvider;
2397 const claudeProvider = {
2398 instanceId: claudeInstanceId,
2399 driver: claudeDriver,
2400 status: "ready",
2401 enabled: true,
2402 installed: true,
2403 auth: { status: "authenticated" },
2404 checkedAt: "2026-04-29T10:01:00.000Z",
2405 version: "1.0.0",
2406 models: [],
2407 slashCommands: [],
2408 skills: [],
2409 } as const satisfies ServerProvider;
2410 const makeInstance = (provider: ServerProvider): ProviderInstance => ({
2411 instanceId: provider.instanceId,
2412 driverKind: provider.driver,
2413 continuationIdentity: {
2414 driverKind: provider.driver,
2415 continuationKey: `${provider.driver}:instance:${provider.instanceId}`,
2416 },
2417 displayName: undefined,
2418 enabled: true,
2419 snapshot: {
2420 resolveMaintenance: () =>
2421 Effect.succeed(
2422 makeManualOnlyProviderMaintenanceCapabilities({
2423 provider: provider.driver,
2424 packageName: null,
2425 }),
26062426 ),
2607 ),
2608 Layer.provideMerge(ModelManifest.layerTest),
2609 Layer.provideMerge(ResetCreditCoordinator.layerTest),
2610 Layer.provideMerge(
2611 OpenCodeRuntime.OpenCodeRuntimeLive.pipe(
2612 Layer.provide(OpenCodeServerLedger.layerTest),
2427 getSnapshot: Effect.succeed(provider),
2428 refresh: Effect.succeed(provider),
2429 streamChanges: Stream.empty,
2430 applyUsageLimits: () => Effect.void,
2431 },
2432 orchestrationAdapter: {} as ProviderInstance["orchestrationAdapter"],
2433 textGeneration: {} as ProviderInstance["textGeneration"],
2434 });
2435 const codexInstance = makeInstance(codexProvider);
2436 const claudeInstance = makeInstance(claudeProvider);
2437 const changes = yield* PubSub.unbounded<void>();
2438 const instancesRef = yield* Ref.make<ReadonlyArray<ProviderInstance>>([codexInstance]);
2439 const failNextList = yield* Ref.make(false);
2440 const wait = () => Effect.yieldNow;
2441 const layerInstanceRegistry = Layer.succeed(
2442 ProviderInstanceRegistry.ProviderInstanceRegistry,
2443 {
2444 getInstance: (instanceId) =>
2445 Ref.get(instancesRef).pipe(
2446 Effect.map((instances) =>
2447 instances.find((instance) => instance.instanceId === instanceId),
2448 ),
26132449 ),
2450 listInstances: Effect.gen(function* () {
2451 const shouldFail = yield* Ref.get(failNextList);
2452 if (shouldFail) {
2453 yield* Ref.set(failNextList, false);
2454 return yield* Effect.die(new Error("simulated registry list failure"));
2455 }
2456 return yield* Ref.get(instancesRef);
2457 }),
2458 listUnavailable: Effect.succeed([]),
2459 streamChanges: Stream.fromPubSub(changes),
2460 subscribeChanges: PubSub.subscribe(changes),
2461 },
2462 );
2463 const scope = yield* Scope.make();
2464 yield* Effect.addFinalizer(() => Scope.close(scope, Exit.void));
2465 const runtimeServices = yield* Layer.build(
2466 ProviderRegistryLayer.layer.pipe(
2467 Layer.provideMerge(layerInstanceRegistry),
2468 Layer.provideMerge(
2469 ServerConfig.layerTest(process.cwd(), {
2470 prefix: "t3-provider-registry-sync-failure-",
2471 }),
26142472 ),
2615 Layer.provideMerge(BackgroundPolicyAlwaysRunLayer),
2616 // NO spawner mock — `ChildProcessSpawner` is supplied by the
2617 // outer `NodeServices.layer` on `it.layer(...)` and will
2618 // genuinely spawn a subprocess. The missing-binary ENOENT is
2619 // what exercises the same failure mode as a misconfigured
2620 // production `binaryPath`.
2621 );
2622 const runtimeServices = yield* Layer.build(providerRegistryLayer).pipe(
2623 Scope.provide(scope),
2624 );
2473 Layer.provideMerge(layerBackgroundPolicyAlwaysRun),
2474 Layer.provideMerge(NodeServices.layer),
2475 ),
2476 ).pipe(Scope.provide(scope));
26252477
2626 yield* Effect.gen(function* () {
2627 const registry = yield* ProviderRegistry.ProviderRegistry;
2628 let providers = yield* registry.getProviders;
2629 for (
2630 let attempts = 0;
2631 attempts < 50 &&
2632 providers.find((provider) => provider.instanceId === "codex_personal")?.status !==
2633 "error";
2634 attempts += 1
2635 ) {
2636 yield* Effect.yieldNow;
2637 providers = yield* registry.getProviders;
2638 }
2639 const codexPersonal = providers.find(
2640 (provider) => provider.instanceId === "codex_personal",
2641 );
2642 assert.notStrictEqual(
2643 codexPersonal,
2644 undefined,
2645 `Expected the aggregator to know about codex_personal; instead saw: ${providers
2646 .map((provider) => provider.instanceId)
2647 .join(", ")}`,
2648 );
2649 assert.strictEqual(
2650 codexPersonal?.status,
2651 "error",
2652 "Real Codex probe against a missing binary should surface as 'error' in the aggregator",
2653 );
2654 assert.strictEqual(codexPersonal?.installed, false);
2655 assert.include(codexPersonal?.message, missingBinary);
2656 assert.include(codexPersonal?.message, "Settings → Providers → Codex → Binary path");
2657 }).pipe(Effect.provide(runtimeServices));
2658 }),
2659 );
2478 yield* Effect.gen(function* () {
2479 const registry = yield* ProviderRegistry.ProviderRegistry;
2480 assert.deepStrictEqual(yield* registry.getProviders, [
2481 withBundledCompatibility(codexProvider),
2482 ]);
26602483
2661 // A binary path change must rebuild Codex and publish its new probe result.
2662 it.effect("re-probes when settings change the codex binaryPath", () =>
2663 Effect.gen(function* () {
2664 const firstMissing = `t3code_codex_first_`;
2665 const secondMissing = `t3code_codex_second_`;
2666 const spawnedCommands: Array<string> = [];
2667 const secondProbeStarted = yield* Deferred.make<void>();
2668 const releaseSecondProbe = yield* Deferred.make<void>();
2669 const allowLazySettingsStream = yield* Deferred.make<void>();
2670 const mutableServerSettings = yield* makeMutableServerSettingsService(
2671 decodeServerSettings(
2672 deepMerge(encodedDefaultServerSettings, {
2673 providers: {
2674 codex: { enabled: true, binaryPath: firstMissing },
2675 claudeAgent: { enabled: false },
2676 cursor: { enabled: false },
2677 grok: { enabled: false },
2678 opencode: { enabled: false },
2484 yield* Ref.set(failNextList, true);
2485 yield* PubSub.publish(changes, undefined);
2486
2487 yield* Ref.set(instancesRef, [codexInstance, claudeInstance]);
2488 yield* PubSub.publish(changes, undefined);
2489
2490 let providers = yield* registry.getProviders;
2491 for (
2492 let attempt = 0;
2493 attempt < 50 && !providers.some((provider) => provider.instanceId === claudeInstanceId);
2494 attempt += 1
2495 ) {
2496 yield* wait();
2497 providers = yield* registry.getProviders;
2498 }
2499
2500 assert.deepStrictEqual(
2501 providers.map((provider) => provider.instanceId).toSorted(),
2502 [codexInstanceId, claudeInstanceId].toSorted(),
2503 );
2504 }).pipe(Effect.provide(runtimeServices));
2505 }),
2506 );
2507
2508 // This test intentionally avoids `mockCommandSpawnerLayer` so the real
2509 // `probeCodexAppServerProvider` path runs — including the full
2510 // `codex app-server` RPC handshake via `CodexClient.layerChildProcess`.
2511 // We point `binaryPath` at a name that cannot exist on any machine so
2512 // the real `ChildProcessSpawner` deterministically returns ENOENT; the
2513 // probe wraps that as `CodexAppServerSpawnError` and
2514 // `checkCodexProviderStatus` turns it into the user-visible "not
2515 // installed" error snapshot. If the aggregator's `syncLiveSources`
2516 // breaks — the `codex_personal`-never-probes bug we are guarding
2517 // against — that snapshot never lands in `getProviders` and the
2518 // assertions below fail.
2519 it.effect("propagates real Codex probe failures to the aggregator at boot", () =>
2520 Effect.gen(function* () {
2521 const missingBinary = `t3code_codex_missing_`;
2522 const serverSettings = yield* makeMutableServerSettingsService(
2523 decodeServerSettings(
2524 deepMerge(encodedDefaultServerSettings, {
2525 providers: {
2526 // Disable every built-in probe that would otherwise spawn
2527 // on the CI host. `enabled: false` short-circuits each
2528 // driver's probe *before* it touches the spawner, so the
2529 // test environment stays isolated from the dev
2530 // machine's PATH.
2531 codex: { enabled: false },
2532 claudeAgent: { enabled: false },
2533 cursor: { enabled: false },
2534 grok: { enabled: false },
2535 opencode: { enabled: false },
2536 },
2537 // `providerInstances` keys are branded `ProviderInstanceId`;
2538 // the branded index signature rejects plain string literals
2539 // at the TS level even though the runtime schema happily
2540 // accepts + decodes them. Cast the patch to `unknown` so
2541 // the `Schema.decodeSync` below does the real validation.
2542 providerInstances: {
2543 // Matches the shape the user had in `.t3/dev/settings.json`
2544 // when the bug was reported: a custom enabled Codex instance
2545 // pointing at a binary the server has to actually spawn.
2546 codex_personal: {
2547 driver: "codex",
2548 displayName: "Codex Personal",
2549 enabled: true,
2550 config: {
2551 binaryPath: missingBinary,
2552 homePath: `/tmp/${missingBinary}_home`,
2553 },
26792554 },
2680 }),
2555 } as unknown as ContractServerSettings["providerInstances"],
2556 }),
2557 ),
2558 );
2559 const scope = yield* Scope.make();
2560 yield* Effect.addFinalizer(() => Scope.close(scope, Exit.void));
2561 const layerProviderRegistry = ProviderRegistryLayer.layer.pipe(
2562 Layer.provideMerge(ProviderInstanceRegistryHydration.layer),
2563 Layer.provideMerge(AntigravityInstallation.AntigravityInstallation.layer),
2564 Layer.provideMerge(
2565 Layer.succeed(ServerSettingsModule.ServerSettingsService, serverSettings),
2566 ),
2567 Layer.provideMerge(ServerSecretStore.layer),
2568 Layer.provideMerge(
2569 ServerConfig.layerTest(process.cwd(), {
2570 prefix: "t3-provider-registry-",
2571 }),
2572 ),
2573 Layer.provideMerge(layerTestHttpClient),
2574 Layer.provideMerge(
2575 Layer.succeed(
2576 ProviderEventLoggers.ProviderEventLoggers,
2577 ProviderEventLoggers.NoOpProviderEventLoggers,
26812578 ),
2579 ),
2580 Layer.provideMerge(ModelManifest.layerTest),
2581 Layer.provideMerge(ResetCreditCoordinator.layerTest),
2582 Layer.provideMerge(
2583 OpenCodeRuntime.layer.pipe(Layer.provide(OpenCodeServerLedger.layerTest)),
2584 ),
2585 Layer.provideMerge(layerBackgroundPolicyAlwaysRun),
2586 // NO spawner mock — `ChildProcessSpawner` is supplied by the
2587 // outer `NodeServices.layer` on `it.layer(...)` and will
2588 // genuinely spawn a subprocess. The missing-binary ENOENT is
2589 // what exercises the same failure mode as a misconfigured
2590 // production `binaryPath`.
2591 );
2592 const runtimeServices = yield* Layer.build(layerProviderRegistry).pipe(
2593 Scope.provide(scope),
2594 );
2595
2596 yield* Effect.gen(function* () {
2597 const registry = yield* ProviderRegistry.ProviderRegistry;
2598 let providers = yield* registry.getProviders;
2599 for (
2600 let attempts = 0;
2601 attempts < 50 &&
2602 providers.find((provider) => provider.instanceId === "codex_personal")?.status !==
2603 "error";
2604 attempts += 1
2605 ) {
2606 yield* Effect.yieldNow;
2607 providers = yield* registry.getProviders;
2608 }
2609 const codexPersonal = providers.find(
2610 (provider) => provider.instanceId === "codex_personal",
26822611 );
2683 const serverSettings = {
2684 ...mutableServerSettings,
2685 streamChanges: Stream.unwrap(
2686 Deferred.await(allowLazySettingsStream).pipe(
2687 Effect.as(mutableServerSettings.streamChanges),
2688 ),
2689 ),
2690 } satisfies ServerSettingsModule.ServerSettingsService["Service"];
2691 const scope = yield* Scope.make();
2692 yield* Effect.addFinalizer(() => Scope.close(scope, Exit.void));
2693 const providerRegistryLayer = ProviderRegistryLive.pipe(
2694 Layer.provideMerge(ProviderInstanceRegistryHydrationLive),
2695 Layer.provideMerge(AntigravityInstallation.AntigravityInstallation.layer),
2696 Layer.provideMerge(
2697 Layer.succeed(ServerSettingsModule.ServerSettingsService, serverSettings),
2698 ),
2699 Layer.provideMerge(ServerSecretStore.layer),
2700 Layer.provideMerge(
2701 ServerConfig.layerTest(process.cwd(), {
2702 prefix: "t3-provider-registry-",
2703 }),
2612 assert.notStrictEqual(
2613 codexPersonal,
2614 undefined,
2615 `Expected the aggregator to know about codex_personal; instead saw: ${providers
2616 .map((provider) => provider.instanceId)
2617 .join(", ")}`,
2618 );
2619 assert.strictEqual(
2620 codexPersonal?.status,
2621 "error",
2622 "Real Codex probe against a missing binary should surface as 'error' in the aggregator",
2623 );
2624 assert.strictEqual(codexPersonal?.installed, false);
2625 assert.include(codexPersonal?.message, missingBinary);
2626 assert.include(codexPersonal?.message, "Settings → Providers → Codex → Binary path");
2627 }).pipe(Effect.provide(runtimeServices));
2628 }),
2629 );
2630
2631 // A binary path change must rebuild Codex and publish its new probe result.
2632 it.effect("re-probes when settings change the codex binaryPath", () =>
2633 Effect.gen(function* () {
2634 const firstMissing = `t3code_codex_first_`;
2635 const secondMissing = `t3code_codex_second_`;
2636 const spawnedCommands: Array<string> = [];
2637 const secondProbeStarted = yield* Deferred.make<void>();
2638 const releaseSecondProbe = yield* Deferred.make<void>();
2639 const allowLazySettingsStream = yield* Deferred.make<void>();
2640 const mutableServerSettings = yield* makeMutableServerSettingsService(
2641 decodeServerSettings(
2642 deepMerge(encodedDefaultServerSettings, {
2643 providers: {
2644 codex: { enabled: true, binaryPath: firstMissing },
2645 claudeAgent: { enabled: false },
2646 cursor: { enabled: false },
2647 grok: { enabled: false },
2648 opencode: { enabled: false },
2649 },
2650 }),
2651 ),
2652 );
2653 const serverSettings = {
2654 ...mutableServerSettings,
2655 streamChanges: Stream.unwrap(
2656 Deferred.await(allowLazySettingsStream).pipe(
2657 Effect.as(mutableServerSettings.streamChanges),
27042658 ),
2705 Layer.provideMerge(TestHttpClientLive),
2706 Layer.provideMerge(
2707 Layer.succeed(
2708 ProviderEventLoggers.ProviderEventLoggers,
2709 ProviderEventLoggers.NoOpProviderEventLoggers,
2710 ),
2659 ),
2660 } satisfies ServerSettingsModule.ServerSettingsService["Service"];
2661 const scope = yield* Scope.make();
2662 yield* Effect.addFinalizer(() => Scope.close(scope, Exit.void));
2663 const layerProviderRegistry = ProviderRegistryLayer.layer.pipe(
2664 Layer.provideMerge(ProviderInstanceRegistryHydration.layer),
2665 Layer.provideMerge(AntigravityInstallation.AntigravityInstallation.layer),
2666 Layer.provideMerge(
2667 Layer.succeed(ServerSettingsModule.ServerSettingsService, serverSettings),
2668 ),
2669 Layer.provideMerge(ServerSecretStore.layer),
2670 Layer.provideMerge(
2671 ServerConfig.layerTest(process.cwd(), {
2672 prefix: "t3-provider-registry-",
2673 }),
2674 ),
2675 Layer.provideMerge(layerTestHttpClient),
2676 Layer.provideMerge(
2677 Layer.succeed(
2678 ProviderEventLoggers.ProviderEventLoggers,
2679 ProviderEventLoggers.NoOpProviderEventLoggers,
27112680 ),
2712 Layer.provideMerge(ModelManifest.layerTest),
2713 Layer.provideMerge(ResetCreditCoordinator.layerTest),
2714 Layer.provideMerge(
2715 OpenCodeRuntime.OpenCodeRuntimeLive.pipe(
2716 Layer.provide(OpenCodeServerLedger.layerTest),
2717 ),
2681 ),
2682 Layer.provideMerge(ModelManifest.layerTest),
2683 Layer.provideMerge(ResetCreditCoordinator.layerTest),
2684 Layer.provideMerge(
2685 OpenCodeRuntime.layer.pipe(Layer.provide(OpenCodeServerLedger.layerTest)),
2686 ),
2687 Layer.updateService(ChildProcessSpawner.ChildProcessSpawner, (spawner) =>
2688 ChildProcessSpawner.make((command) => {
2689 if (command._tag !== "StandardCommand") return spawner.spawn(command);
2690 spawnedCommands.push(command.command);
2691 const beforeSpawn =
2692 command.command === secondMissing
2693 ? Deferred.succeed(secondProbeStarted, undefined).pipe(
2694 Effect.andThen(Deferred.await(releaseSecondProbe)),
2695 )
2696 : Effect.void;
2697 return beforeSpawn.pipe(Effect.andThen(spawner.spawn(command)));
2698 }),
2699 ),
2700 Layer.provideMerge(NodeServices.layer),
2701 Layer.provideMerge(layerBackgroundPolicyAlwaysRun),
2702 );
2703 const runtimeServices = yield* Layer.build(layerProviderRegistry).pipe(
2704 Scope.provide(scope),
2705 );
2706
2707 yield* Effect.gen(function* () {
2708 const registry = yield* ProviderRegistry.ProviderRegistry;
2709 const codexSnapshots = registry.streamChanges.pipe(
2710 Stream.map((providers) =>
2711 providers.find((provider) => provider.instanceId === "codex"),
27182712 ),
2719 Layer.updateService(ChildProcessSpawner.ChildProcessSpawner, (spawner) =>
2720 ChildProcessSpawner.make((command) => {
2721 if (command._tag !== "StandardCommand") return spawner.spawn(command);
2722 spawnedCommands.push(command.command);
2723 const beforeSpawn =
2724 command.command === secondMissing
2725 ? Deferred.succeed(secondProbeStarted, undefined).pipe(
2726 Effect.andThen(Deferred.await(releaseSecondProbe)),
2727 )
2728 : Effect.void;
2729 return beforeSpawn.pipe(Effect.andThen(spawner.spawn(command)));
2730 }),
2713 Stream.filter((provider): provider is ServerProvider => provider !== undefined),
2714 );
2715 const firstError = yield* Stream.toPull(
2716 codexSnapshots.pipe(Stream.filter((provider) => provider.status === "error")),
2717 );
2718 const currentCodex = (yield* registry.getProviders).find(
2719 (provider) => provider.instanceId === "codex",
2720 );
2721 const initialCodex =
2722 currentCodex?.status === "error" ? currentCodex : (yield* firstError)[0];
2723 assert.strictEqual(initialCodex?.status, "error");
2724 assert.strictEqual(initialCodex?.installed, false);
2725 assert.deepStrictEqual(spawnedCommands, [firstMissing]);
2726
2727 const pendingRebuild = yield* Stream.toPull(
2728 codexSnapshots.pipe(
2729 Stream.filter((provider) => provider.status === "warning" && !provider.installed),
27312730 ),
2732 Layer.provideMerge(NodeServices.layer),
2733 Layer.provideMerge(BackgroundPolicyAlwaysRunLayer),
27342731 );
2735 const runtimeServices = yield* Layer.build(providerRegistryLayer).pipe(
2736 Scope.provide(scope),
2732 yield* serverSettings.updateSettings({
2733 providers: {
2734 codex: { enabled: true, binaryPath: secondMissing },
2735 },
2736 });
2737 // Start the lazy stream only after publishing. A watcher that did
2738 // not subscribe before forking has already lost this update.
2739 yield* Deferred.succeed(allowLazySettingsStream, undefined);
2740
2741 // Hold the second probe until the aggregator sees the rebuilt
2742 // instance. Its next error must come from the new executable.
2743 yield* Deferred.await(secondProbeStarted);
2744 yield* pendingRebuild;
2745 const rebuiltError = yield* Stream.toPull(
2746 codexSnapshots.pipe(Stream.filter((provider) => provider.status === "error")),
27372747 );
2748 yield* Deferred.succeed(releaseSecondProbe, undefined);
2749 const [reprobedCodex] = yield* rebuiltError;
2750 assert.deepStrictEqual(spawnedCommands, [firstMissing, secondMissing]);
2751 assert.strictEqual(reprobedCodex?.status, "error");
2752 assert.strictEqual(reprobedCodex?.installed, false);
2753 }).pipe(Effect.provide(runtimeServices));
2754 }),
2755 );
27382756
2739 yield* Effect.gen(function* () {
2740 const registry = yield* ProviderRegistry.ProviderRegistry;
2741 const codexSnapshots = registry.streamChanges.pipe(
2742 Stream.map((providers) =>
2743 providers.find((provider) => provider.instanceId === "codex"),
2744 ),
2745 Stream.filter((provider): provider is ServerProvider => provider !== undefined),
2746 );
2747 const firstError = yield* Stream.toPull(
2748 codexSnapshots.pipe(Stream.filter((provider) => provider.status === "error")),
2749 );
2750 const currentCodex = (yield* registry.getProviders).find(
2751 (provider) => provider.instanceId === "codex",
2752 );
2753 const initialCodex =
2754 currentCodex?.status === "error" ? currentCodex : (yield* firstError)[0];
2755 assert.strictEqual(initialCodex?.status, "error");
2756 assert.strictEqual(initialCodex?.installed, false);
2757 assert.deepStrictEqual(spawnedCommands, [firstMissing]);
2758
2759 const pendingRebuild = yield* Stream.toPull(
2760 codexSnapshots.pipe(
2761 Stream.filter((provider) => provider.status === "warning" && !provider.installed),
2762 ),
2763 );
2764 yield* serverSettings.updateSettings({
2757 it.effect("includes unavailable instance snapshots in getProviders", () =>
2758 Effect.gen(function* () {
2759 const serverSettings = yield* makeMutableServerSettingsService(
2760 decodeServerSettings(
2761 deepMerge(encodedDefaultServerSettings, {
27652762 providers: {
2766 codex: { enabled: true, binaryPath: secondMissing },
2763 codex: { enabled: false },
2764 claudeAgent: { enabled: false },
2765 cursor: { enabled: false },
2766 grok: { enabled: false },
2767 opencode: { enabled: false },
27672768 },
2768 });
2769 // Start the lazy stream only after publishing. A watcher that did
2770 // not subscribe before forking has already lost this update.
2771 yield* Deferred.succeed(allowLazySettingsStream, undefined);
2772
2773 // Hold the second probe until the aggregator sees the rebuilt
2774 // instance. Its next error must come from the new executable.
2775 yield* Deferred.await(secondProbeStarted);
2776 yield* pendingRebuild;
2777 const rebuiltError = yield* Stream.toPull(
2778 codexSnapshots.pipe(Stream.filter((provider) => provider.status === "error")),
2779 );
2780 yield* Deferred.succeed(releaseSecondProbe, undefined);
2781 const [reprobedCodex] = yield* rebuiltError;
2782 assert.deepStrictEqual(spawnedCommands, [firstMissing, secondMissing]);
2783 assert.strictEqual(reprobedCodex?.status, "error");
2784 assert.strictEqual(reprobedCodex?.installed, false);
2785 }).pipe(Effect.provide(runtimeServices));
2786 }),
2787 );
2769 providerInstances: {
2770 ghost_main: {
2771 driver: "ghostDriver",
2772 displayName: "A fork-only driver we don't ship",
2773 enabled: false,
2774 config: { arbitrary: "payload" },
2775 },
2776 } as unknown as ContractServerSettings["providerInstances"],
2777 }),
2778 ),
2779 );
2780 const scope = yield* Scope.make();
2781 yield* Effect.addFinalizer(() => Scope.close(scope, Exit.void));
2782 const layerProviderRegistry = ProviderRegistryLayer.layer.pipe(
2783 Layer.provideMerge(ProviderInstanceRegistryHydration.layer),
2784 Layer.provideMerge(AntigravityInstallation.AntigravityInstallation.layer),
2785 Layer.provideMerge(
2786 Layer.succeed(ServerSettingsModule.ServerSettingsService, serverSettings),
2787 ),
2788 Layer.provideMerge(ServerSecretStore.layer),
2789 Layer.provideMerge(
2790 ServerConfig.layerTest(process.cwd(), {
2791 prefix: "t3-provider-registry-",
2792 }),
2793 ),
2794 Layer.provideMerge(layerTestHttpClient),
2795 Layer.provideMerge(
2796 Layer.succeed(
2797 ProviderEventLoggers.ProviderEventLoggers,
2798 ProviderEventLoggers.NoOpProviderEventLoggers,
2799 ),
2800 ),
2801 Layer.provideMerge(ModelManifest.layerTest),
2802 Layer.provideMerge(ResetCreditCoordinator.layerTest),
2803 Layer.provideMerge(
2804 OpenCodeRuntime.layer.pipe(Layer.provide(OpenCodeServerLedger.layerTest)),
2805 ),
2806 Layer.provideMerge(NodeServices.layer),
2807 Layer.provideMerge(layerBackgroundPolicyAlwaysRun),
2808 );
2809 const runtimeServices = yield* Layer.build(layerProviderRegistry).pipe(
2810 Scope.provide(scope),
2811 );
2812
2813 yield* Effect.gen(function* () {
2814 const registry = yield* ProviderRegistry.ProviderRegistry;
2815 const providers = yield* registry.getProviders;
2816 const ghost = providers.find((provider) => provider.instanceId === "ghost_main");
2817
2818 assert.notStrictEqual(ghost, undefined);
2819 assert.strictEqual(ghost?.driver, "ghostDriver");
2820 assert.strictEqual(ghost?.availability, "unavailable");
2821 assert.match(ghost?.unavailableReason ?? "", /ghostDriver/);
2822 }).pipe(Effect.provide(runtimeServices));
2823 }),
2824 );
27882825
2789 it.effect("includes unavailable instance snapshots in getProviders", () =>
2826 it.effect(
2827 "keeps Cursor disabled and skips provider probing when settings use their defaults",
2828 () =>
27902829 Effect.gen(function* () {
27912830 const serverSettings = yield* makeMutableServerSettingsService(
27922831 decodeServerSettings(
27932832 deepMerge(encodedDefaultServerSettings, {
27942833 providers: {
2795 codex: { enabled: false },
2796 claudeAgent: { enabled: false },
2797 cursor: { enabled: false },
2798 grok: { enabled: false },
2799 opencode: { enabled: false },
2800 },
2801 providerInstances: {
2802 ghost_main: {
2803 driver: "ghostDriver",
2804 displayName: "A fork-only driver we don't ship",
2834 codex: {
2835 enabled: false,
2836 },
2837 grok: {
28052838 enabled: false,
2806 config: { arbitrary: "payload" },
28072839 },
2808 } as unknown as ContractServerSettings["providerInstances"],
2840 },
28092841 }),
28102842 ),
28112843 );
2844 let cursorSpawned = false;
28122845 const scope = yield* Scope.make();
28132846 yield* Effect.addFinalizer(() => Scope.close(scope, Exit.void));
2814 const providerRegistryLayer = ProviderRegistryLive.pipe(
2815 Layer.provideMerge(ProviderInstanceRegistryHydrationLive),
2847 const layerProviderRegistry = ProviderRegistryLayer.layer.pipe(
2848 Layer.provideMerge(ProviderInstanceRegistryHydration.layer),
28162849 Layer.provideMerge(AntigravityInstallation.AntigravityInstallation.layer),
28172850 Layer.provideMerge(
28182851 Layer.succeed(ServerSettingsModule.ServerSettingsService, serverSettings),
......@@ -2823,7 +2856,7 @@ it.layer(Layer.mergeAll(TestNodeServices, ServerSettingsModule.layerTest(), Test
28232856 prefix: "t3-provider-registry-",
28242857 }),
28252858 ),
2826 Layer.provideMerge(TestHttpClientLive),
2859 Layer.provideMerge(layerTestHttpClient),
28272860 Layer.provideMerge(
28282861 Layer.succeed(
28292862 ProviderEventLoggers.ProviderEventLoggers,
......@@ -2833,546 +2866,473 @@ it.layer(Layer.mergeAll(TestNodeServices, ServerSettingsModule.layerTest(), Test
28332866 Layer.provideMerge(ModelManifest.layerTest),
28342867 Layer.provideMerge(ResetCreditCoordinator.layerTest),
28352868 Layer.provideMerge(
2836 OpenCodeRuntime.OpenCodeRuntimeLive.pipe(
2837 Layer.provide(OpenCodeServerLedger.layerTest),
2838 ),
2869 OpenCodeRuntime.layer.pipe(Layer.provide(OpenCodeServerLedger.layerTest)),
2870 ),
2871 Layer.provideMerge(layerBackgroundPolicyAlwaysRun),
2872 Layer.provideMerge(
2873 layerMockCommandSpawner((command, args) => {
2874 if (command === "cursor-agent") {
2875 cursorSpawned = true;
2876 }
2877 const joined = args.join(" ");
2878 if (joined === "--version") {
2879 return {
2880 stdout: `${command} 1.0.0\n`,
2881 stderr: "",
2882 code: 0,
2883 };
2884 }
2885 if (joined === "auth status") {
2886 return {
2887 stdout: '{"authenticated":true}\n',
2888 stderr: "",
2889 code: 0,
2890 };
2891 }
2892 throw new Error(`Unexpected args: ${command} ${joined}`);
2893 }),
28392894 ),
2840 Layer.provideMerge(NodeServices.layer),
2841 Layer.provideMerge(BackgroundPolicyAlwaysRunLayer),
2842 );
2843 const runtimeServices = yield* Layer.build(providerRegistryLayer).pipe(
2844 Scope.provide(scope),
28452895 );
2896 const runtimeServices = yield* Layer.build(
2897 Layer.mergeAll(
2898 Layer.succeed(ServerSettingsModule.ServerSettingsService, serverSettings),
2899 layerProviderRegistry,
2900 ),
2901 ).pipe(Scope.provide(scope));
28462902
28472903 yield* Effect.gen(function* () {
28482904 const registry = yield* ProviderRegistry.ProviderRegistry;
28492905 const providers = yield* registry.getProviders;
2850 const ghost = providers.find((provider) => provider.instanceId === "ghost_main");
2906 const cursorProvider = providers.find(
2907 (provider) => provider.instanceId === ProviderInstanceId.make("cursor"),
2908 );
28512909
2852 assert.notStrictEqual(ghost, undefined);
2853 assert.strictEqual(ghost?.driver, "ghostDriver");
2854 assert.strictEqual(ghost?.availability, "unavailable");
2855 assert.match(ghost?.unavailableReason ?? "", /ghostDriver/);
2910 assert.deepStrictEqual(providers.map((provider) => provider.instanceId).toSorted(), [
2911 "antigravity",
2912 "claudeAgent",
2913 "codex",
2914 "cursor",
2915 "grok",
2916 "opencode",
2917 "pi",
2918 ]);
2919 assert.strictEqual(cursorProvider?.enabled, false);
2920 assert.strictEqual(cursorProvider?.status, "disabled");
2921 assert.strictEqual(cursorProvider?.message, "Cursor is disabled in T3 Code settings.");
2922 assert.strictEqual(cursorSpawned, false);
28562923 }).pipe(Effect.provide(runtimeServices));
28572924 }),
2858 );
2925 );
28592926
2860 it.effect(
2861 "keeps Cursor disabled and skips provider probing when settings use their defaults",
2862 () =>
2863 Effect.gen(function* () {
2864 const serverSettings = yield* makeMutableServerSettingsService(
2865 decodeServerSettings(
2866 deepMerge(encodedDefaultServerSettings, {
2867 providers: {
2868 codex: {
2869 enabled: false,
2870 },
2871 grok: {
2872 enabled: false,
2873 },
2874 },
2875 }),
2876 ),
2877 );
2878 let cursorSpawned = false;
2879 const scope = yield* Scope.make();
2880 yield* Effect.addFinalizer(() => Scope.close(scope, Exit.void));
2881 const providerRegistryLayer = ProviderRegistryLive.pipe(
2882 Layer.provideMerge(ProviderInstanceRegistryHydrationLive),
2883 Layer.provideMerge(AntigravityInstallation.AntigravityInstallation.layer),
2884 Layer.provideMerge(
2885 Layer.succeed(ServerSettingsModule.ServerSettingsService, serverSettings),
2886 ),
2887 Layer.provideMerge(ServerSecretStore.layer),
2888 Layer.provideMerge(
2889 ServerConfig.layerTest(process.cwd(), {
2890 prefix: "t3-provider-registry-",
2891 }),
2892 ),
2893 Layer.provideMerge(TestHttpClientLive),
2894 Layer.provideMerge(
2895 Layer.succeed(
2896 ProviderEventLoggers.ProviderEventLoggers,
2897 ProviderEventLoggers.NoOpProviderEventLoggers,
2898 ),
2899 ),
2900 Layer.provideMerge(ModelManifest.layerTest),
2901 Layer.provideMerge(ResetCreditCoordinator.layerTest),
2902 Layer.provideMerge(
2903 OpenCodeRuntime.OpenCodeRuntimeLive.pipe(
2904 Layer.provide(OpenCodeServerLedger.layerTest),
2905 ),
2906 ),
2907 Layer.provideMerge(BackgroundPolicyAlwaysRunLayer),
2908 Layer.provideMerge(
2909 mockCommandSpawnerLayer((command, args) => {
2910 if (command === "cursor-agent") {
2911 cursorSpawned = true;
2912 }
2913 const joined = args.join(" ");
2914 if (joined === "--version") {
2915 return {
2916 stdout: `${command} 1.0.0\n`,
2917 stderr: "",
2918 code: 0,
2919 };
2920 }
2921 if (joined === "auth status") {
2922 return {
2923 stdout: '{"authenticated":true}\n',
2924 stderr: "",
2925 code: 0,
2926 };
2927 }
2928 throw new Error(`Unexpected args: ${command} ${joined}`);
2929 }),
2930 ),
2931 );
2932 const runtimeServices = yield* Layer.build(
2933 Layer.mergeAll(
2934 Layer.succeed(ServerSettingsModule.ServerSettingsService, serverSettings),
2935 providerRegistryLayer,
2936 ),
2937 ).pipe(Scope.provide(scope));
2927 it.effect("skips codex probes entirely when the provider is disabled", () =>
2928 Effect.gen(function* () {
2929 const status = yield* checkCodexProviderStatus(disabledCodexSettings).pipe(
2930 Effect.provide(layerFailingSpawner("spawn codex ENOENT")),
2931 );
2932 assert.strictEqual(status.enabled, false);
2933 assert.strictEqual(status.status, "disabled");
2934 assert.strictEqual(status.installed, false);
2935 assert.strictEqual(status.message, "Codex is disabled in T3 Code settings.");
2936 }),
2937 );
2938 });
29382939
2939 yield* Effect.gen(function* () {
2940 const registry = yield* ProviderRegistry.ProviderRegistry;
2941 const providers = yield* registry.getProviders;
2942 const cursorProvider = providers.find(
2943 (provider) => provider.instanceId === ProviderInstanceId.make("cursor"),
2944 );
2940 // ── checkClaudeProviderStatus tests ──────────────────────────
29452941
2946 assert.deepStrictEqual(providers.map((provider) => provider.instanceId).toSorted(), [
2947 "antigravity",
2948 "claudeAgent",
2949 "codex",
2950 "cursor",
2951 "grok",
2952 "opencode",
2953 "pi",
2954 ]);
2955 assert.strictEqual(cursorProvider?.enabled, false);
2956 assert.strictEqual(cursorProvider?.status, "disabled");
2957 assert.strictEqual(
2958 cursorProvider?.message,
2959 "Cursor is disabled in T3 Code settings.",
2960 );
2961 assert.strictEqual(cursorSpawned, false);
2962 }).pipe(Effect.provide(runtimeServices));
2942 describe("checkClaudeProviderStatus", () => {
2943 it.effect("returns ready when claude is installed and authenticated", () =>
2944 Effect.gen(function* () {
2945 const status = yield* checkClaudeProviderStatus(
2946 defaultClaudeSettings,
2947 claudeCapabilities(),
2948 );
2949 assert.strictEqual(status.status, "ready");
2950 assert.strictEqual(status.installed, true);
2951 assert.strictEqual(status.auth.status, "authenticated");
2952 }).pipe(
2953 Effect.provide(
2954 layerMockSpawner((args) => {
2955 const joined = args.join(" ");
2956 if (joined === "--version") return { stdout: "1.0.0\n", stderr: "", code: 0 };
2957 if (joined === "auth status")
2958 return {
2959 stdout: '{"loggedIn":true,"authMethod":"claude.ai"}\n',
2960 stderr: "",
2961 code: 0,
2962 };
2963 throw new Error(`Unexpected args: ${joined}`);
29632964 }),
2964 );
2965
2966 it.effect("skips codex probes entirely when the provider is disabled", () =>
2967 Effect.gen(function* () {
2968 const status = yield* checkCodexProviderStatus(disabledCodexSettings).pipe(
2969 Effect.provide(failingSpawnerLayer("spawn codex ENOENT")),
2970 );
2971 assert.strictEqual(status.enabled, false);
2972 assert.strictEqual(status.status, "disabled");
2973 assert.strictEqual(status.installed, false);
2974 assert.strictEqual(status.message, "Codex is disabled in T3 Code settings.");
2975 }),
2976 );
2977 });
2978
2979 // ── checkClaudeProviderStatus tests ──────────────────────────
2980
2981 describe("checkClaudeProviderStatus", () => {
2982 it.effect("returns ready when claude is installed and authenticated", () =>
2983 Effect.gen(function* () {
2984 const status = yield* checkClaudeProviderStatus(
2985 defaultClaudeSettings,
2986 claudeCapabilities(),
2987 );
2988 assert.strictEqual(status.status, "ready");
2989 assert.strictEqual(status.installed, true);
2990 assert.strictEqual(status.auth.status, "authenticated");
2991 }).pipe(
2992 Effect.provide(
2993 mockSpawnerLayer((args) => {
2994 const joined = args.join(" ");
2995 if (joined === "--version") return { stdout: "1.0.0\n", stderr: "", code: 0 };
2996 if (joined === "auth status")
2997 return {
2998 stdout: '{"loggedIn":true,"authMethod":"claude.ai"}\n',
2999 stderr: "",
3000 code: 0,
3001 };
3002 throw new Error(`Unexpected args: ${joined}`);
3003 }),
3004 ),
3005 ),
3006 );
3007
3008 it.effect("returns ready and labels Bedrock-backed Claude as authenticated", () =>
3009 Effect.gen(function* () {
3010 // Bedrock authenticates via external AWS credentials, so the SDK init
3011 // reports only `apiProvider` with no subscription or token.
3012 const status = yield* checkClaudeProviderStatus(
3013 defaultClaudeSettings,
3014 claudeCapabilities({ apiProvider: "bedrock" }),
3015 );
3016 assert.strictEqual(status.status, "ready");
3017 assert.strictEqual(status.installed, true);
3018 assert.strictEqual(status.auth.status, "authenticated");
3019 assert.strictEqual(status.auth.type, "bedrock");
3020 assert.strictEqual(status.auth.label, "Amazon Bedrock");
3021 }).pipe(
3022 Effect.provide(
3023 mockSpawnerLayer((args) => {
3024 const joined = args.join(" ");
3025 if (joined === "--version") return { stdout: "1.0.0\n", stderr: "", code: 0 };
3026 throw new Error(`Unexpected args: ${joined}`);
3027 }),
3028 ),
30292965 ),
3030 );
2966 ),
2967 );
30312968
3032 it.effect("returns a display label for claude subscription types", () =>
3033 Effect.gen(function* () {
3034 const status = yield* checkClaudeProviderStatus(
3035 defaultClaudeSettings,
3036 claudeCapabilities({ subscriptionType: "maxplan" }),
3037 );
3038 assert.strictEqual(status.status, "ready");
3039 assert.strictEqual(status.auth.status, "authenticated");
3040 assert.strictEqual(status.auth.type, "maxplan");
3041 assert.strictEqual(status.auth.label, "Claude Max Subscription");
3042 }).pipe(
3043 Effect.provide(
3044 mockSpawnerLayer((args) => {
3045 const joined = args.join(" ");
3046 if (joined === "--version") return { stdout: "1.0.0\n", stderr: "", code: 0 };
3047 if (joined === "auth status")
3048 return {
3049 stdout: '{"loggedIn":true,"authMethod":"claude.ai"}\n',
3050 stderr: "",
3051 code: 0,
3052 };
3053 throw new Error(`Unexpected args: ${joined}`);
3054 }),
3055 ),
2969 it.effect("returns ready and labels Bedrock-backed Claude as authenticated", () =>
2970 Effect.gen(function* () {
2971 // Bedrock authenticates via external AWS credentials, so the SDK init
2972 // reports only `apiProvider` with no subscription or token.
2973 const status = yield* checkClaudeProviderStatus(
2974 defaultClaudeSettings,
2975 claudeCapabilities({ apiProvider: "bedrock" }),
2976 );
2977 assert.strictEqual(status.status, "ready");
2978 assert.strictEqual(status.installed, true);
2979 assert.strictEqual(status.auth.status, "authenticated");
2980 assert.strictEqual(status.auth.type, "bedrock");
2981 assert.strictEqual(status.auth.label, "Amazon Bedrock");
2982 }).pipe(
2983 Effect.provide(
2984 layerMockSpawner((args) => {
2985 const joined = args.join(" ");
2986 if (joined === "--version") return { stdout: "1.0.0\n", stderr: "", code: 0 };
2987 throw new Error(`Unexpected args: ${joined}`);
2988 }),
30562989 ),
3057 );
2990 ),
2991 );
30582992
3059 it.effect("reads banked resets only for subscription logins", () =>
3060 Effect.gen(function* () {
3061 const check = (overrides: Partial<TestClaudeCapabilities>) =>
3062 checkClaudeProviderStatus(
3063 defaultClaudeSettings,
3064 () =>
3065 Effect.succeed({
3066 email: undefined,
3067 subscriptionType: undefined,
3068 tokenSource: undefined,
3069 apiProvider: undefined,
3070 slashCommands: [],
3071 usage: { rate_limits_available: true, rate_limits: {} },
3072 ...overrides,
3073 }),
3074 undefined,
3075 undefined,
3076 undefined,
3077 undefined,
3078 () => Effect.succeed({ availableCount: 2 }),
3079 );
3080 const subscription = yield* check({ subscriptionType: "max" });
3081 const bedrock = yield* check({ apiProvider: "bedrock" });
3082 assert.deepStrictEqual(subscription.usageLimits?.resetCredits, { availableCount: 2 });
3083 assert.strictEqual(bedrock.usageLimits?.resetCredits, undefined);
3084 }).pipe(
3085 Effect.provide(
3086 mockSpawnerLayer((args) => {
3087 const joined = args.join(" ");
3088 if (joined === "--version") return { stdout: "1.0.0\n", stderr: "", code: 0 };
3089 throw new Error(`Unexpected args: ${joined}`);
3090 }),
3091 ),
2993 it.effect("returns a display label for claude subscription types", () =>
2994 Effect.gen(function* () {
2995 const status = yield* checkClaudeProviderStatus(
2996 defaultClaudeSettings,
2997 claudeCapabilities({ subscriptionType: "maxplan" }),
2998 );
2999 assert.strictEqual(status.status, "ready");
3000 assert.strictEqual(status.auth.status, "authenticated");
3001 assert.strictEqual(status.auth.type, "maxplan");
3002 assert.strictEqual(status.auth.label, "Claude Max Subscription");
3003 }).pipe(
3004 Effect.provide(
3005 layerMockSpawner((args) => {
3006 const joined = args.join(" ");
3007 if (joined === "--version") return { stdout: "1.0.0\n", stderr: "", code: 0 };
3008 if (joined === "auth status")
3009 return {
3010 stdout: '{"loggedIn":true,"authMethod":"claude.ai"}\n',
3011 stderr: "",
3012 code: 0,
3013 };
3014 throw new Error(`Unexpected args: ${joined}`);
3015 }),
30923016 ),
3093 );
3017 ),
3018 );
30943019
3095 it.effect("does not duplicate Claude in full subscription labels", () =>
3096 Effect.gen(function* () {
3097 const status = yield* checkClaudeProviderStatus(
3020 it.effect("reads banked resets only for subscription logins", () =>
3021 Effect.gen(function* () {
3022 const check = (overrides: Partial<TestClaudeCapabilities>) =>
3023 checkClaudeProviderStatus(
30983024 defaultClaudeSettings,
3099 claudeCapabilities({
3100 subscriptionType: "Claude Max Subscription",
3101 }),
3025 () =>
3026 Effect.succeed({
3027 email: undefined,
3028 subscriptionType: undefined,
3029 tokenSource: undefined,
3030 apiProvider: undefined,
3031 slashCommands: [],
3032 usage: { rate_limits_available: true, rate_limits: {} },
3033 ...overrides,
3034 }),
3035 undefined,
3036 undefined,
3037 undefined,
3038 undefined,
3039 () => Effect.succeed({ availableCount: 2 }),
31023040 );
3103 assert.strictEqual(status.auth.status, "authenticated");
3104 assert.strictEqual(status.auth.type, "Claude Max Subscription");
3105 assert.strictEqual(status.auth.label, "Claude Max Subscription");
3106 }).pipe(
3107 Effect.provide(
3108 mockSpawnerLayer((args) => {
3109 const joined = args.join(" ");
3110 if (joined === "--version") return { stdout: "1.0.0\n", stderr: "", code: 0 };
3111 throw new Error(`Unexpected args: ${joined}`);
3112 }),
3113 ),
3041 const subscription = yield* check({ subscriptionType: "max" });
3042 const bedrock = yield* check({ apiProvider: "bedrock" });
3043 assert.deepStrictEqual(subscription.usageLimits?.resetCredits, { availableCount: 2 });
3044 assert.strictEqual(bedrock.usageLimits?.resetCredits, undefined);
3045 }).pipe(
3046 Effect.provide(
3047 layerMockSpawner((args) => {
3048 const joined = args.join(" ");
3049 if (joined === "--version") return { stdout: "1.0.0\n", stderr: "", code: 0 };
3050 throw new Error(`Unexpected args: ${joined}`);
3051 }),
31143052 ),
3115 );
3053 ),
3054 );
31163055
3117 it.effect("does not duplicate Claude in provider-prefixed subscription names", () =>
3118 Effect.gen(function* () {
3119 const status = yield* checkClaudeProviderStatus(
3120 defaultClaudeSettings,
3121 claudeCapabilities({
3122 subscriptionType: "Claude Max",
3123 }),
3124 );
3125 assert.strictEqual(status.auth.status, "authenticated");
3126 assert.strictEqual(status.auth.type, "Claude Max");
3127 assert.strictEqual(status.auth.label, "Claude Max Subscription");
3128 }).pipe(
3129 Effect.provide(
3130 mockSpawnerLayer((args) => {
3131 const joined = args.join(" ");
3132 if (joined === "--version") return { stdout: "1.0.0\n", stderr: "", code: 0 };
3133 throw new Error(`Unexpected args: ${joined}`);
3134 }),
3135 ),
3056 it.effect("does not duplicate Claude in full subscription labels", () =>
3057 Effect.gen(function* () {
3058 const status = yield* checkClaudeProviderStatus(
3059 defaultClaudeSettings,
3060 claudeCapabilities({
3061 subscriptionType: "Claude Max Subscription",
3062 }),
3063 );
3064 assert.strictEqual(status.auth.status, "authenticated");
3065 assert.strictEqual(status.auth.type, "Claude Max Subscription");
3066 assert.strictEqual(status.auth.label, "Claude Max Subscription");
3067 }).pipe(
3068 Effect.provide(
3069 layerMockSpawner((args) => {
3070 const joined = args.join(" ");
3071 if (joined === "--version") return { stdout: "1.0.0\n", stderr: "", code: 0 };
3072 throw new Error(`Unexpected args: ${joined}`);
3073 }),
31363074 ),
3137 );
3075 ),
3076 );
31383077
3139 it.effect("returns claude auth email from initialization result", () =>
3140 Effect.gen(function* () {
3141 const status = yield* checkClaudeProviderStatus(
3142 defaultClaudeSettings,
3143 claudeCapabilities({ email: "claude@example.com" }),
3144 );
3145 assert.strictEqual(status.auth.status, "authenticated");
3146 assert.strictEqual(status.auth.email, "claude@example.com");
3147 }).pipe(
3148 Effect.provide(
3149 mockSpawnerLayer((args) => {
3150 const joined = args.join(" ");
3151 if (joined === "--version") return { stdout: "1.0.0\n", stderr: "", code: 0 };
3152 if (joined === "auth status")
3153 return {
3154 stdout:
3155 '{"loggedIn":true,"authMethod":"claude.ai","account":{"email":"claude@example.com"}}\n',
3156 stderr: "",
3157 code: 0,
3158 };
3159 throw new Error(`Unexpected args: ${joined}`);
3160 }),
3161 ),
3078 it.effect("does not duplicate Claude in provider-prefixed subscription names", () =>
3079 Effect.gen(function* () {
3080 const status = yield* checkClaudeProviderStatus(
3081 defaultClaudeSettings,
3082 claudeCapabilities({
3083 subscriptionType: "Claude Max",
3084 }),
3085 );
3086 assert.strictEqual(status.auth.status, "authenticated");
3087 assert.strictEqual(status.auth.type, "Claude Max");
3088 assert.strictEqual(status.auth.label, "Claude Max Subscription");
3089 }).pipe(
3090 Effect.provide(
3091 layerMockSpawner((args) => {
3092 const joined = args.join(" ");
3093 if (joined === "--version") return { stdout: "1.0.0\n", stderr: "", code: 0 };
3094 throw new Error(`Unexpected args: ${joined}`);
3095 }),
31623096 ),
3163 );
3164
3165 it.effect("runs Claude status probes with the configured CLAUDE_CONFIG_DIR", () => {
3166 const claudeConfigDir = "/tmp/t3code-claude-home";
3167 const recorded = recordingMockSpawnerLayer((args) => {
3168 const joined = args.join(" ");
3169 if (joined === "--version") return { stdout: "1.0.0\n", stderr: "", code: 0 };
3170 if (joined === "auth status")
3171 return {
3172 stdout: '{"loggedIn":true,"authMethod":"claude.ai"}\n',
3173 stderr: "",
3174 code: 0,
3175 };
3176 throw new Error(`Unexpected args: ${joined}`);
3177 });
3097 ),
3098 );
31783099
3179 return Effect.gen(function* () {
3180 const status = yield* checkClaudeProviderStatus(
3181 {
3182 ...defaultClaudeSettings,
3183 homePath: claudeConfigDir,
3184 },
3185 claudeCapabilities(),
3186 );
3187 assert.strictEqual(status.status, "ready");
3188 // The home is resolved through the host Path before it reaches the env.
3189 assert.deepStrictEqual(
3190 recorded.commands.map((command) => command.env?.CLAUDE_CONFIG_DIR),
3191 [(yield* Path.Path).resolve(claudeConfigDir)],
3192 );
3193 }).pipe(Effect.provide(recorded.layer));
3100 it.effect("returns claude auth email from initialization result", () =>
3101 Effect.gen(function* () {
3102 const status = yield* checkClaudeProviderStatus(
3103 defaultClaudeSettings,
3104 claudeCapabilities({ email: "claude@example.com" }),
3105 );
3106 assert.strictEqual(status.auth.status, "authenticated");
3107 assert.strictEqual(status.auth.email, "claude@example.com");
3108 }).pipe(
3109 Effect.provide(
3110 layerMockSpawner((args) => {
3111 const joined = args.join(" ");
3112 if (joined === "--version") return { stdout: "1.0.0\n", stderr: "", code: 0 };
3113 if (joined === "auth status")
3114 return {
3115 stdout:
3116 '{"loggedIn":true,"authMethod":"claude.ai","account":{"email":"claude@example.com"}}\n',
3117 stderr: "",
3118 code: 0,
3119 };
3120 throw new Error(`Unexpected args: ${joined}`);
3121 }),
3122 ),
3123 ),
3124 );
3125
3126 it.effect("runs Claude status probes with the configured CLAUDE_CONFIG_DIR", () => {
3127 const claudeConfigDir = "/tmp/t3code-claude-home";
3128 const recorded = recordingMockSpawnerLayer((args) => {
3129 const joined = args.join(" ");
3130 if (joined === "--version") return { stdout: "1.0.0\n", stderr: "", code: 0 };
3131 if (joined === "auth status")
3132 return {
3133 stdout: '{"loggedIn":true,"authMethod":"claude.ai"}\n',
3134 stderr: "",
3135 code: 0,
3136 };
3137 throw new Error(`Unexpected args: ${joined}`);
31943138 });
31953139
3196 it.effect("includes probed claude slash commands in the provider snapshot", () =>
3197 Effect.gen(function* () {
3198 const status = yield* checkClaudeProviderStatus(
3199 defaultClaudeSettings,
3200 claudeCapabilities({
3201 subscriptionType: "maxplan",
3202 slashCommands: [
3203 {
3204 name: "review",
3205 description: "Review a pull request",
3206 input: { hint: "pr-or-branch" },
3207 },
3208 ],
3209 }),
3210 );
3140 return Effect.gen(function* () {
3141 const status = yield* checkClaudeProviderStatus(
3142 {
3143 ...defaultClaudeSettings,
3144 homePath: claudeConfigDir,
3145 },
3146 claudeCapabilities(),
3147 );
3148 assert.strictEqual(status.status, "ready");
3149 // The home is resolved through the host Path before it reaches the env.
3150 assert.deepStrictEqual(
3151 recorded.commands.map((command) => command.env?.CLAUDE_CONFIG_DIR),
3152 [(yield* Path.Path).resolve(claudeConfigDir)],
3153 );
3154 }).pipe(Effect.provide(recorded.layer));
3155 });
32113156
3212 assert.deepStrictEqual(status.slashCommands.slice(1), [
3213 {
3214 name: "review",
3215 description: "Review a pull request",
3216 input: { hint: "pr-or-branch" },
3217 },
3218 ]);
3219 }).pipe(
3220 Effect.provide(
3221 mockSpawnerLayer((args) => {
3222 const joined = args.join(" ");
3223 if (joined === "--version") return { stdout: "1.0.0\n", stderr: "", code: 0 };
3224 if (joined === "auth status")
3225 return {
3226 stdout: '{"loggedIn":true,"authMethod":"claude.ai"}\n',
3227 stderr: "",
3228 code: 0,
3229 };
3230 throw new Error(`Unexpected args: ${joined}`);
3231 }),
3232 ),
3157 it.effect("includes probed claude slash commands in the provider snapshot", () =>
3158 Effect.gen(function* () {
3159 const status = yield* checkClaudeProviderStatus(
3160 defaultClaudeSettings,
3161 claudeCapabilities({
3162 subscriptionType: "maxplan",
3163 slashCommands: [
3164 {
3165 name: "review",
3166 description: "Review a pull request",
3167 input: { hint: "pr-or-branch" },
3168 },
3169 ],
3170 }),
3171 );
3172
3173 assert.deepStrictEqual(status.slashCommands.slice(1), [
3174 {
3175 name: "review",
3176 description: "Review a pull request",
3177 input: { hint: "pr-or-branch" },
3178 },
3179 ]);
3180 }).pipe(
3181 Effect.provide(
3182 layerMockSpawner((args) => {
3183 const joined = args.join(" ");
3184 if (joined === "--version") return { stdout: "1.0.0\n", stderr: "", code: 0 };
3185 if (joined === "auth status")
3186 return {
3187 stdout: '{"loggedIn":true,"authMethod":"claude.ai"}\n',
3188 stderr: "",
3189 code: 0,
3190 };
3191 throw new Error(`Unexpected args: ${joined}`);
3192 }),
32333193 ),
3234 );
3194 ),
3195 );
32353196
3236 it.effect("deduplicates probed claude slash commands by name", () =>
3237 Effect.gen(function* () {
3238 const status = yield* checkClaudeProviderStatus(
3239 defaultClaudeSettings,
3240 claudeCapabilities({
3241 subscriptionType: "maxplan",
3242 slashCommands: [
3243 {
3244 name: "ui",
3245 description: "Explore and refine UI",
3246 },
3247 {
3248 name: "ui",
3249 input: { hint: "component-or-screen" },
3250 },
3251 ],
3252 }),
3253 );
3197 it.effect("deduplicates probed claude slash commands by name", () =>
3198 Effect.gen(function* () {
3199 const status = yield* checkClaudeProviderStatus(
3200 defaultClaudeSettings,
3201 claudeCapabilities({
3202 subscriptionType: "maxplan",
3203 slashCommands: [
3204 {
3205 name: "ui",
3206 description: "Explore and refine UI",
3207 },
3208 {
3209 name: "ui",
3210 input: { hint: "component-or-screen" },
3211 },
3212 ],
3213 }),
3214 );
32543215
3255 assert.deepStrictEqual(status.slashCommands, [
3256 COMPACT_SLASH_COMMAND,
3257 {
3258 name: "ui",
3259 description: "Explore and refine UI",
3260 input: { hint: "component-or-screen" },
3261 },
3262 ]);
3263 }).pipe(
3264 Effect.provide(
3265 mockSpawnerLayer((args) => {
3266 const joined = args.join(" ");
3267 if (joined === "--version") return { stdout: "1.0.0\n", stderr: "", code: 0 };
3268 if (joined === "auth status")
3269 return {
3270 stdout: '{"loggedIn":true,"authMethod":"claude.ai"}\n',
3271 stderr: "",
3272 code: 0,
3273 };
3274 throw new Error(`Unexpected args: ${joined}`);
3275 }),
3276 ),
3216 assert.deepStrictEqual(status.slashCommands, [
3217 COMPACT_SLASH_COMMAND,
3218 {
3219 name: "ui",
3220 description: "Explore and refine UI",
3221 input: { hint: "component-or-screen" },
3222 },
3223 ]);
3224 }).pipe(
3225 Effect.provide(
3226 layerMockSpawner((args) => {
3227 const joined = args.join(" ");
3228 if (joined === "--version") return { stdout: "1.0.0\n", stderr: "", code: 0 };
3229 if (joined === "auth status")
3230 return {
3231 stdout: '{"loggedIn":true,"authMethod":"claude.ai"}\n',
3232 stderr: "",
3233 code: 0,
3234 };
3235 throw new Error(`Unexpected args: ${joined}`);
3236 }),
32773237 ),
3278 );
3238 ),
3239 );
32793240
3280 it.effect("returns an api key label for claude api key auth", () =>
3281 Effect.gen(function* () {
3282 const status = yield* checkClaudeProviderStatus(
3283 defaultClaudeSettings,
3284 claudeCapabilities({ tokenSource: "ANTHROPIC_AUTH_TOKEN" }),
3285 );
3286 assert.strictEqual(status.status, "ready");
3287 assert.strictEqual(status.auth.status, "authenticated");
3288 assert.strictEqual(status.auth.type, "apiKey");
3289 assert.strictEqual(status.auth.label, "Claude API Key");
3290 }).pipe(
3291 Effect.provide(
3292 mockSpawnerLayer((args) => {
3293 const joined = args.join(" ");
3294 if (joined === "--version") return { stdout: "1.0.0\n", stderr: "", code: 0 };
3295 if (joined === "auth status")
3296 return {
3297 stdout: '{"loggedIn":true,"authMethod":"api-key"}\n',
3298 stderr: "",
3299 code: 0,
3300 };
3301 throw new Error(`Unexpected args: ${joined}`);
3302 }),
3303 ),
3241 it.effect("returns an api key label for claude api key auth", () =>
3242 Effect.gen(function* () {
3243 const status = yield* checkClaudeProviderStatus(
3244 defaultClaudeSettings,
3245 claudeCapabilities({ tokenSource: "ANTHROPIC_AUTH_TOKEN" }),
3246 );
3247 assert.strictEqual(status.status, "ready");
3248 assert.strictEqual(status.auth.status, "authenticated");
3249 assert.strictEqual(status.auth.type, "apiKey");
3250 assert.strictEqual(status.auth.label, "Claude API Key");
3251 }).pipe(
3252 Effect.provide(
3253 layerMockSpawner((args) => {
3254 const joined = args.join(" ");
3255 if (joined === "--version") return { stdout: "1.0.0\n", stderr: "", code: 0 };
3256 if (joined === "auth status")
3257 return {
3258 stdout: '{"loggedIn":true,"authMethod":"api-key"}\n',
3259 stderr: "",
3260 code: 0,
3261 };
3262 throw new Error(`Unexpected args: ${joined}`);
3263 }),
33043264 ),
3305 );
3306
3307 it.effect("returns unavailable when claude is missing", () =>
3308 Effect.gen(function* () {
3309 const status = yield* checkClaudeProviderStatus(
3310 defaultClaudeSettings,
3311 claudeCapabilities(),
3312 );
3313 assert.strictEqual(status.status, "error");
3314 assert.strictEqual(status.installed, false);
3315 assert.strictEqual(status.auth.status, "unknown");
3316 assert.strictEqual(status.message, "Claude Agent CLI (`claude`) was not found on PATH.");
3317 }).pipe(Effect.provide(failingSpawnerLayer("spawn claude ENOENT"))),
3318 );
3265 ),
3266 );
33193267
3320 it.effect("returns error when version check fails with non-zero exit code", () => {
3321 const secretStderr = "Something went wrong: secret-token-value";
3322 return Effect.gen(function* () {
3323 const status = yield* checkClaudeProviderStatus(
3324 defaultClaudeSettings,
3325 claudeCapabilities(),
3326 );
3327 assert.strictEqual(status.status, "error");
3328 assert.strictEqual(status.installed, true);
3329 assert.strictEqual(status.message, "Claude Agent CLI is installed but failed to run.");
3330 assert.ok(!(status.message ?? "").includes(secretStderr));
3331 }).pipe(
3332 Effect.provide(
3333 mockSpawnerLayer((args) => {
3334 const joined = args.join(" ");
3335 if (joined === "--version")
3336 return {
3337 stdout: "",
3338 stderr: secretStderr,
3339 code: 1,
3340 };
3341 throw new Error(`Unexpected args: ${joined}`);
3342 }),
3343 ),
3268 it.effect("returns unavailable when claude is missing", () =>
3269 Effect.gen(function* () {
3270 const status = yield* checkClaudeProviderStatus(
3271 defaultClaudeSettings,
3272 claudeCapabilities(),
33443273 );
3345 });
3346
3347 it.effect("returns warning when the Claude initialization result is unavailable", () =>
3348 Effect.gen(function* () {
3349 const status = yield* checkClaudeProviderStatus(
3350 defaultClaudeSettings,
3351 noClaudeCapabilities,
3352 );
3353 assert.strictEqual(status.status, "warning");
3354 assert.strictEqual(status.installed, true);
3355 assert.strictEqual(status.auth.status, "unknown");
3356 assert.strictEqual(
3357 status.message,
3358 "Could not verify Claude authentication status from initialization result.",
3359 );
3360 }).pipe(
3361 Effect.provide(
3362 mockSpawnerLayer((args) => {
3363 const joined = args.join(" ");
3364 if (joined === "--version") return { stdout: "1.0.0\n", stderr: "", code: 0 };
3365 if (joined === "auth status")
3366 return {
3367 stdout: '{"loggedIn":false}\n',
3368 stderr: "",
3369 code: 1,
3370 };
3371 throw new Error(`Unexpected args: ${joined}`);
3372 }),
3373 ),
3274 assert.strictEqual(status.status, "error");
3275 assert.strictEqual(status.installed, false);
3276 assert.strictEqual(status.auth.status, "unknown");
3277 assert.strictEqual(status.message, "Claude Agent CLI (`claude`) was not found on PATH.");
3278 }).pipe(Effect.provide(layerFailingSpawner("spawn claude ENOENT"))),
3279 );
3280
3281 it.effect("returns error when version check fails with non-zero exit code", () => {
3282 const secretStderr = "Something went wrong: secret-token-value";
3283 return Effect.gen(function* () {
3284 const status = yield* checkClaudeProviderStatus(
3285 defaultClaudeSettings,
3286 claudeCapabilities(),
3287 );
3288 assert.strictEqual(status.status, "error");
3289 assert.strictEqual(status.installed, true);
3290 assert.strictEqual(status.message, "Claude Agent CLI is installed but failed to run.");
3291 assert.ok(!(status.message ?? "").includes(secretStderr));
3292 }).pipe(
3293 Effect.provide(
3294 layerMockSpawner((args) => {
3295 const joined = args.join(" ");
3296 if (joined === "--version")
3297 return {
3298 stdout: "",
3299 stderr: secretStderr,
3300 code: 1,
3301 };
3302 throw new Error(`Unexpected args: ${joined}`);
3303 }),
33743304 ),
33753305 );
33763306 });
3377 },
3378);
3307
3308 it.effect("returns warning when the Claude initialization result is unavailable", () =>
3309 Effect.gen(function* () {
3310 const status = yield* checkClaudeProviderStatus(
3311 defaultClaudeSettings,
3312 noClaudeCapabilities,
3313 );
3314 assert.strictEqual(status.status, "warning");
3315 assert.strictEqual(status.installed, true);
3316 assert.strictEqual(status.auth.status, "unknown");
3317 assert.strictEqual(
3318 status.message,
3319 "Could not verify Claude authentication status from initialization result.",
3320 );
3321 }).pipe(
3322 Effect.provide(
3323 layerMockSpawner((args) => {
3324 const joined = args.join(" ");
3325 if (joined === "--version") return { stdout: "1.0.0\n", stderr: "", code: 0 };
3326 if (joined === "auth status")
3327 return {
3328 stdout: '{"loggedIn":false}\n',
3329 stderr: "",
3330 code: 1,
3331 };
3332 throw new Error(`Unexpected args: ${joined}`);
3333 }),
3334 ),
3335 ),
3336 );
3337 });
3338});
apps/server/src/provider/Layers/ProviderRegistry.ts+1-1
......@@ -326,7 +326,7 @@ const buildSnapshotSource = (instance: ProviderInstance): ProviderSnapshotSource
326326 streamChanges: instance.snapshot.streamChanges,
327327});
328328
329export const ProviderRegistryLive = Layer.effect(
329export const layer = Layer.effect(
330330 ProviderRegistry.ProviderRegistry,
331331 Effect.gen(function* () {
332332 const instanceRegistry = yield* ProviderInstanceRegistry.ProviderInstanceRegistry;
apps/server/src/provider/Layers/ProviderUsageLimitsIngestion.ts+1-1
......@@ -4,4 +4,4 @@
44 */
55import * as Layer from "effect/Layer";
66
7export const ProviderUsageLimitsIngestionLive = Layer.empty;
7export const layer = Layer.empty;
apps/server/src/provider/ModelManifest.test.ts+14-14
......@@ -333,13 +333,13 @@ const INVALID_REMOTE_MANIFESTS: ReadonlyArray<ModelManifest.ModelManifestData> =
333333 }),
334334];
335335
336const httpClientLayer = (handler: () => Response) =>
336const layerHttpClient = (handler: () => Response) =>
337337 Layer.succeed(
338338 HttpClient.HttpClient,
339339 HttpClient.make((request) => Effect.succeed(HttpClientResponse.fromWeb(request, handler()))),
340340 );
341341
342const serviceLayers = (input: {
342const layerService = (input: {
343343 readonly prefix: string;
344344 readonly response: () => Response;
345345 readonly settings?: Parameters<typeof ServerSettings.layerTest>[0];
......@@ -347,7 +347,7 @@ const serviceLayers = (input: {
347347 ServerConfig.layerTest(process.cwd(), { prefix: input.prefix }).pipe(
348348 Layer.provideMerge(NodeServices.layer),
349349 Layer.provideMerge(ServerSettings.layerTest(input.settings ?? {})),
350 Layer.provideMerge(httpClientLayer(input.response)),
350 Layer.provideMerge(layerHttpClient(input.response)),
351351 );
352352
353353describe("ModelManifest service", () => {
......@@ -373,7 +373,7 @@ describe("ModelManifest service", () => {
373373 }).pipe(
374374 Effect.scoped,
375375 Effect.provide(
376 serviceLayers({
376 layerService({
377377 prefix: "model-manifest-force-refresh-test",
378378 response: () => Response.json(fetchCount++ === 0 ? REMOTE_MANIFEST : updated),
379379 }),
......@@ -395,7 +395,7 @@ describe("ModelManifest service", () => {
395395 }).pipe(
396396 Effect.scoped,
397397 Effect.provide(
398 serviceLayers({
398 layerService({
399399 prefix: "model-manifest-force-retry-test",
400400 response: () =>
401401 fetchCount++ === 1
......@@ -418,7 +418,7 @@ describe("ModelManifest service", () => {
418418 }).pipe(
419419 Effect.scoped,
420420 Effect.provide(
421 serviceLayers({
421 layerService({
422422 prefix: "model-manifest-force-initial-retry-test",
423423 response: () =>
424424 fetchCount++ === 0
......@@ -442,7 +442,7 @@ describe("ModelManifest service", () => {
442442 }).pipe(
443443 Effect.scoped,
444444 Effect.provide(
445 serviceLayers({
445 layerService({
446446 prefix: "model-manifest-fetch-test",
447447 response: () => Response.json(REMOTE_MANIFEST),
448448 }),
......@@ -466,7 +466,7 @@ describe("ModelManifest service", () => {
466466 }).pipe(
467467 Effect.scoped,
468468 Effect.provide(
469 serviceLayers({
469 layerService({
470470 prefix: "model-manifest-stale-fetch-test",
471471 response: () => Response.json(remote),
472472 }),
......@@ -481,7 +481,7 @@ describe("ModelManifest service", () => {
481481 }).pipe(
482482 Effect.scoped,
483483 Effect.provide(
484 serviceLayers({
484 layerService({
485485 prefix: "model-manifest-malformed-test",
486486 response: () => Response.json({ version: 999, nonsense: true }),
487487 }),
......@@ -508,7 +508,7 @@ describe("ModelManifest service", () => {
508508 }).pipe(
509509 Effect.scoped,
510510 Effect.provide(
511 serviceLayers({
511 layerService({
512512 prefix: "model-manifest-last-good-test",
513513 response: () => Response.json(responses[responseIndex]),
514514 }),
......@@ -548,7 +548,7 @@ describe("ModelManifest service", () => {
548548 }).pipe(
549549 Effect.scoped,
550550 Effect.provide(
551 serviceLayers({
551 layerService({
552552 prefix: "model-manifest-newer-bundle-test",
553553 response: () => Response.json(REMOTE_MANIFEST),
554554 }),
......@@ -561,7 +561,7 @@ describe("ModelManifest service", () => {
561561 let fetchCount = 0;
562562 const service = yield* ModelManifest.make.pipe(
563563 Effect.provide(
564 httpClientLayer(() => {
564 layerHttpClient(() => {
565565 fetchCount += 1;
566566 return Response.json(REMOTE_MANIFEST);
567567 }),
......@@ -573,7 +573,7 @@ describe("ModelManifest service", () => {
573573 }).pipe(
574574 Effect.scoped,
575575 Effect.provide(
576 serviceLayers({
576 layerService({
577577 prefix: "model-manifest-optout-test",
578578 response: () => Response.json(REMOTE_MANIFEST),
579579 settings: { enableProviderUpdateChecks: false },
......@@ -607,7 +607,7 @@ it.effect("caches valid compatibility policies and keeps them after a malformed
607607 }).pipe(
608608 Effect.scoped,
609609 Effect.provide(
610 serviceLayers({
610 layerService({
611611 prefix: "model-manifest-compatibility-test",
612612 response: () =>
613613 Response.json(
apps/server/src/provider/OpenCodeServerLedger.test.ts+2-2
......@@ -195,7 +195,7 @@ describe.skipIf(observedPlatforms.length === 0)("OpenCode server startup", () =>
195195 const spawned = yield* Deferred.make<ChildProcessSpawner.ChildProcessHandle>();
196196 const recording = yield* Deferred.make<number>();
197197 const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
198 const runtimeLayer = OpenCodeRuntime.OpenCodeRuntimeLive.pipe(
198 const layerRuntime = OpenCodeRuntime.layer.pipe(
199199 Layer.provide(
200200 Layer.succeed(
201201 ChildProcessSpawner.ChildProcessSpawner,
......@@ -228,7 +228,7 @@ describe.skipIf(observedPlatforms.length === 0)("OpenCode server startup", () =>
228228 runtime.startOpenCodeServerProcess({ binaryPath, directory: tempDir, port: 4096 }),
229229 ),
230230 Effect.provideService(Scope.Scope, serverScope),
231 Effect.provide(runtimeLayer),
231 Effect.provide(layerRuntime),
232232 Effect.forkChild,
233233 );
234234 const pgid = yield* Deferred.await(recording);
apps/server/src/provider/acp/AcpRegistrySupport.test.ts+30-30
......@@ -50,7 +50,7 @@ function settings(input: Partial<AcpRegistrySettings> = {}): AcpRegistrySettings
5050 });
5151}
5252
53function resolverLayer(
53function layerResolver(
5454 execute: Parameters<typeof HttpClient.make>[0],
5555 environment: NodeJS.ProcessEnv = process.env,
5656) {
......@@ -253,7 +253,7 @@ describe("AcpRegistrySupport", () => {
253253 }).pipe(
254254 Effect.scoped,
255255 Effect.provide(
256 resolverLayer((request) => {
256 layerResolver((request) => {
257257 requests.push(request.url);
258258 return Effect.die("unexpected registry request for a local provider");
259259 }),
......@@ -310,7 +310,7 @@ describe("AcpRegistrySupport", () => {
310310 }
311311 }).pipe(
312312 Effect.scoped,
313 Effect.provide(resolverLayer(() => Effect.die("unexpected registry request"))),
313 Effect.provide(layerResolver(() => Effect.die("unexpected registry request"))),
314314 ),
315315 );
316316
......@@ -353,7 +353,7 @@ describe("AcpRegistrySupport", () => {
353353 }
354354 }).pipe(
355355 Effect.scoped,
356 Effect.provide(resolverLayer(() => Effect.die("unexpected registry request"))),
356 Effect.provide(layerResolver(() => Effect.die("unexpected registry request"))),
357357 ),
358358 );
359359
......@@ -402,7 +402,7 @@ describe("AcpRegistrySupport", () => {
402402 }).pipe(
403403 Effect.scoped,
404404 Effect.provide(
405 resolverLayer((request) => {
405 layerResolver((request) => {
406406 requests.push(request.url);
407407 return Effect.succeed(
408408 HttpClientResponse.fromWeb(request, new Response(makeRegistry(agent))),
......@@ -443,7 +443,7 @@ describe("AcpRegistrySupport", () => {
443443 }).pipe(
444444 Effect.scoped,
445445 Effect.provide(
446 resolverLayer((request) =>
446 layerResolver((request) =>
447447 Effect.succeed(HttpClientResponse.fromWeb(request, new Response(makeRegistry(agent)))),
448448 ),
449449 ),
......@@ -483,7 +483,7 @@ describe("AcpRegistrySupport", () => {
483483 }).pipe(
484484 Effect.scoped,
485485 Effect.provide(
486 resolverLayer((request) =>
486 layerResolver((request) =>
487487 Effect.succeed(HttpClientResponse.fromWeb(request, new Response(makeRegistry(agent)))),
488488 ),
489489 ),
......@@ -519,7 +519,7 @@ describe("AcpRegistrySupport", () => {
519519 }).pipe(
520520 Effect.scoped,
521521 Effect.provide(
522 resolverLayer((request) =>
522 layerResolver((request) =>
523523 Effect.succeed(HttpClientResponse.fromWeb(request, new Response(makeRegistry(agent)))),
524524 ),
525525 ),
......@@ -572,7 +572,7 @@ describe("AcpRegistrySupport", () => {
572572 }).pipe(
573573 Effect.scoped,
574574 Effect.provide(
575 resolverLayer((request) =>
575 layerResolver((request) =>
576576 Effect.succeed(HttpClientResponse.fromWeb(request, new Response(makeRegistry(agent)))),
577577 ),
578578 ),
......@@ -616,7 +616,7 @@ describe("AcpRegistrySupport", () => {
616616 }).pipe(
617617 Effect.scoped,
618618 Effect.provide(
619 resolverLayer((request) => {
619 layerResolver((request) => {
620620 requests.push(request.url);
621621 const response =
622622 request.url === registryUrl
......@@ -704,7 +704,7 @@ describe("AcpRegistrySupport", () => {
704704 }).pipe(
705705 Effect.scoped,
706706 Effect.provide(
707 resolverLayer((request) => {
707 layerResolver((request) => {
708708 requests.push(request.url);
709709 return Effect.succeed(
710710 HttpClientResponse.fromWeb(
......@@ -822,7 +822,7 @@ describe("AcpRegistrySupport", () => {
822822 }).pipe(
823823 Effect.scoped,
824824 Effect.provide(
825 resolverLayer((request) =>
825 layerResolver((request) =>
826826 Effect.succeed(
827827 HttpClientResponse.fromWeb(
828828 request,
......@@ -863,7 +863,7 @@ describe("AcpRegistrySupport", () => {
863863 }).pipe(
864864 Effect.scoped,
865865 Effect.provide(
866 resolverLayer((request) => {
866 layerResolver((request) => {
867867 requests += 1;
868868 return Effect.yieldNow.pipe(
869869 Effect.as(HttpClientResponse.fromWeb(request, new Response(makeRegistry(agent)))),
......@@ -900,7 +900,7 @@ describe("AcpRegistrySupport", () => {
900900 }).pipe(
901901 Effect.scoped,
902902 Effect.provide(
903 resolverLayer(
903 layerResolver(
904904 (request) =>
905905 Effect.succeed(
906906 HttpClientResponse.fromWeb(
......@@ -942,7 +942,7 @@ describe("AcpRegistrySupport", () => {
942942 }).pipe(
943943 Effect.scoped,
944944 Effect.provide(
945 resolverLayer((request) =>
945 layerResolver((request) =>
946946 Effect.succeed(
947947 HttpClientResponse.fromWeb(
948948 request,
......@@ -977,7 +977,7 @@ describe("AcpRegistrySupport", () => {
977977 }).pipe(
978978 Effect.scoped,
979979 Effect.provide(
980 resolverLayer((request) =>
980 layerResolver((request) =>
981981 Effect.succeed(HttpClientResponse.fromWeb(request, new Response(makeRegistry(agent)))),
982982 ),
983983 ),
......@@ -1012,7 +1012,7 @@ describe("AcpRegistrySupport", () => {
10121012 }).pipe(
10131013 Effect.scoped,
10141014 Effect.provide(
1015 resolverLayer(
1015 layerResolver(
10161016 (request) =>
10171017 Effect.succeed(
10181018 HttpClientResponse.fromWeb(
......@@ -1056,7 +1056,7 @@ describe("AcpRegistrySupport", () => {
10561056 }).pipe(
10571057 Effect.scoped,
10581058 Effect.provide(
1059 resolverLayer((request) =>
1059 layerResolver((request) =>
10601060 Effect.succeed(
10611061 HttpClientResponse.fromWeb(
10621062 request,
......@@ -1101,7 +1101,7 @@ describe("AcpRegistrySupport", () => {
11011101 }).pipe(
11021102 Effect.scoped,
11031103 Effect.provide(
1104 resolverLayer((request) => {
1104 layerResolver((request) => {
11051105 requests.push(request.url);
11061106 return Effect.succeed(
11071107 HttpClientResponse.fromWeb(request, new Response(makeRegistry(agent))),
......@@ -1141,7 +1141,7 @@ describe("AcpRegistrySupport", () => {
11411141 }).pipe(
11421142 Effect.scoped,
11431143 Effect.provide(
1144 resolverLayer((request) =>
1144 layerResolver((request) =>
11451145 Effect.succeed(
11461146 HttpClientResponse.fromWeb(request, new Response("unavailable", { status: 503 })),
11471147 ),
......@@ -1177,7 +1177,7 @@ describe("AcpRegistrySupport", () => {
11771177 }).pipe(
11781178 Effect.scoped,
11791179 Effect.provide(
1180 resolverLayer((request) => {
1180 layerResolver((request) => {
11811181 requests.push(request.url);
11821182 return Effect.succeed(
11831183 HttpClientResponse.fromWeb(request, new Response(makeRegistry(agent))),
......@@ -1211,7 +1211,7 @@ describe("AcpRegistrySupport", () => {
12111211 }).pipe(
12121212 Effect.scoped,
12131213 Effect.provide(
1214 resolverLayer((request) => {
1214 layerResolver((request) => {
12151215 requests += 1;
12161216 return Effect.succeed(
12171217 HttpClientResponse.fromWeb(request, new Response(makeRegistry(agent))),
......@@ -1240,7 +1240,7 @@ describe("AcpRegistrySupport", () => {
12401240 }).pipe(
12411241 Effect.scoped,
12421242 Effect.provide(
1243 resolverLayer((request) => {
1243 layerResolver((request) => {
12441244 requests += 1;
12451245 return Effect.succeed(HttpClientResponse.fromWeb(request, new Response("unused")));
12461246 }),
......@@ -1279,7 +1279,7 @@ describe("AcpRegistrySupport", () => {
12791279 }).pipe(
12801280 Effect.scoped,
12811281 Effect.provide(
1282 resolverLayer(
1282 layerResolver(
12831283 (request) =>
12841284 Effect.succeed(HttpClientResponse.fromWeb(request, new Response(makeRegistry(agent)))),
12851285 { PATH: "" },
......@@ -1329,7 +1329,7 @@ describe("AcpRegistrySupport", () => {
13291329 }).pipe(
13301330 Effect.scoped,
13311331 Effect.provide(
1332 resolverLayer((request) =>
1332 layerResolver((request) =>
13331333 Effect.succeed(HttpClientResponse.fromWeb(request, new Response(makeRegistry(agent)))),
13341334 ),
13351335 ),
......@@ -1395,7 +1395,7 @@ describe("AcpRegistrySupport", () => {
13951395 }).pipe(
13961396 Effect.scoped,
13971397 Effect.provide(
1398 resolverLayer((request) =>
1398 layerResolver((request) =>
13991399 Effect.succeed(HttpClientResponse.fromWeb(request, new Response("unused"))),
14001400 ),
14011401 ),
......@@ -1433,7 +1433,7 @@ describe("AcpRegistrySupport", () => {
14331433 });
14341434 }).pipe(
14351435 Effect.scoped,
1436 Effect.provide(resolverLayer(() => Effect.die("unexpected HTTP request"))),
1436 Effect.provide(layerResolver(() => Effect.die("unexpected HTTP request"))),
14371437 ),
14381438 );
14391439
......@@ -1508,7 +1508,7 @@ describe("AcpRegistrySupport", () => {
15081508 }).pipe(
15091509 Effect.scoped,
15101510 Effect.provide(
1511 resolverLayer((request) => {
1511 layerResolver((request) => {
15121512 if (request.url === registryUrl) {
15131513 return Effect.succeed(
15141514 HttpClientResponse.fromWeb(request, new Response(makeRegistry(agent))),
......@@ -1563,7 +1563,7 @@ describe("AcpRegistrySupport", () => {
15631563 }).pipe(
15641564 Effect.scoped,
15651565 Effect.provide(
1566 resolverLayer((request) =>
1566 layerResolver((request) =>
15671567 Effect.succeed(
15681568 HttpClientResponse.fromWeb(
15691569 request,
......@@ -1607,7 +1607,7 @@ describe("AcpRegistrySupport", () => {
16071607 }).pipe(
16081608 Effect.scoped,
16091609 Effect.provide(
1610 resolverLayer((request) =>
1610 layerResolver((request) =>
16111611 Effect.succeed(
16121612 HttpClientResponse.fromWeb(
16131613 request,
apps/server/src/provider/makeManagedServerProvider.test.ts+18-18
......@@ -99,7 +99,7 @@ const refreshedSnapshotSecond: ServerProvider = {
9999 message: "Refreshed provider availability again.",
100100};
101101
102function makeBackgroundPolicyLayer(shouldRunScopeWork: boolean) {
102function layerBackgroundPolicy(shouldRunScopeWork: boolean) {
103103 return Layer.mock(BackgroundPolicy.BackgroundPolicy)({
104104 reportClientActivity: () => Effect.void,
105105 removeRpcClient: () => Effect.void,
......@@ -130,11 +130,11 @@ function makeBackgroundPolicyLayer(shouldRunScopeWork: boolean) {
130130 });
131131}
132132
133const BackgroundPolicyAlwaysRunLayer = makeBackgroundPolicyLayer(true);
134const BackgroundPolicyNeverRunLayer = makeBackgroundPolicyLayer(false);
135const ServerSettingsTestLayer = ServerSettings.layerTest();
136const AlwaysRunTestLayer = Layer.merge(BackgroundPolicyAlwaysRunLayer, ServerSettingsTestLayer);
137const NeverRunTestLayer = Layer.merge(BackgroundPolicyNeverRunLayer, ServerSettingsTestLayer);
133const layerBackgroundPolicyAlwaysRun = layerBackgroundPolicy(true);
134const layerBackgroundPolicyNeverRun = layerBackgroundPolicy(false);
135const layerServerSettingsTest = ServerSettings.layerTest();
136const layerAlwaysRunTest = Layer.merge(layerBackgroundPolicyAlwaysRun, layerServerSettingsTest);
137const layerNeverRunTest = Layer.merge(layerBackgroundPolicyNeverRun, layerServerSettingsTest);
138138
139139const enrichedSnapshotSecond: ServerProvider = {
140140 ...refreshedSnapshotSecond,
......@@ -189,7 +189,7 @@ describe("makeManagedServerProvider", () => {
189189 assert.deepStrictEqual(latest, refreshedSnapshot);
190190 assert.strictEqual(yield* Ref.get(checkCalls), 1);
191191 }),
192 ).pipe(Effect.provide(AlwaysRunTestLayer)),
192 ).pipe(Effect.provide(layerAlwaysRunTest)),
193193 );
194194
195195 it.effect("skips periodic provider refreshes without foreground provider-status demand", () =>
......@@ -220,7 +220,7 @@ describe("makeManagedServerProvider", () => {
220220
221221 assert.strictEqual(yield* Ref.get(checkCalls), 1);
222222 }),
223 ).pipe(Effect.provide(Layer.mergeAll(NeverRunTestLayer, TestClock.layer()))),
223 ).pipe(Effect.provide(Layer.mergeAll(layerNeverRunTest, TestClock.layer()))),
224224 );
225225
226226 it.effect("disables periodic provider refreshes when the explicit interval is zero", () =>
......@@ -247,7 +247,7 @@ describe("makeManagedServerProvider", () => {
247247
248248 assert.strictEqual(yield* Ref.get(checkCalls), 1);
249249 }),
250 ).pipe(Effect.provide(Layer.mergeAll(AlwaysRunTestLayer, TestClock.layer()))),
250 ).pipe(Effect.provide(Layer.mergeAll(layerAlwaysRunTest, TestClock.layer()))),
251251 );
252252
253253 it.effect("keeps manual refresh when interval refresh is disabled", () =>
......@@ -281,7 +281,7 @@ describe("makeManagedServerProvider", () => {
281281 yield* provider.refresh;
282282 assert.strictEqual(yield* Ref.get(checkCalls), 2);
283283 }),
284 ).pipe(Effect.provide(Layer.mergeAll(AlwaysRunTestLayer, TestClock.layer()))),
284 ).pipe(Effect.provide(Layer.mergeAll(layerAlwaysRunTest, TestClock.layer()))),
285285 );
286286
287287 it.effect("wakes a sleeping provider refresh loop when its interval changes", () =>
......@@ -293,7 +293,7 @@ describe("makeManagedServerProvider", () => {
293293 };
294294 const serverSettingsRef = yield* Ref.make(initialServerSettings);
295295 const serverSettingsChanges = yield* PubSub.unbounded<typeof initialServerSettings>();
296 const serverSettingsLayer = Layer.succeed(
296 const layerServerSettings = Layer.succeed(
297297 ServerSettings.ServerSettingsService,
298298 ServerSettings.ServerSettingsService.of({
299299 start: Effect.void,
......@@ -326,7 +326,7 @@ describe("makeManagedServerProvider", () => {
326326 ),
327327 Effect.as(refreshedSnapshot),
328328 ),
329 }).pipe(Effect.provide(Layer.merge(BackgroundPolicyAlwaysRunLayer, serverSettingsLayer)));
329 }).pipe(Effect.provide(Layer.merge(layerBackgroundPolicyAlwaysRun, layerServerSettings)));
330330
331331 yield* Deferred.await(initialCheckDone);
332332 const nextServerSettings = {
......@@ -388,7 +388,7 @@ describe("makeManagedServerProvider", () => {
388388 assert.deepStrictEqual(latest, refreshedSnapshotSecond);
389389 assert.strictEqual(yield* Ref.get(checkCalls), 2);
390390 }),
391 ).pipe(Effect.provide(AlwaysRunTestLayer)),
391 ).pipe(Effect.provide(layerAlwaysRunTest)),
392392 );
393393
394394 it.effect("can update settings and disable periodic checks without probing again", () =>
......@@ -423,7 +423,7 @@ describe("makeManagedServerProvider", () => {
423423 assert.strictEqual(yield* Ref.get(checkCalls), 1);
424424 assert.strictEqual(yield* Ref.get(enrichmentCalls), 2);
425425 }),
426 ).pipe(Effect.provide(Layer.mergeAll(AlwaysRunTestLayer, TestClock.layer()))),
426 ).pipe(Effect.provide(Layer.mergeAll(layerAlwaysRunTest, TestClock.layer()))),
427427 );
428428
429429 it.effect("streams supplemental snapshot updates after the base provider check completes", () =>
......@@ -461,7 +461,7 @@ describe("makeManagedServerProvider", () => {
461461 assert.deepStrictEqual(updates, [refreshedSnapshot, enrichedSnapshot]);
462462 assert.deepStrictEqual(latest, enrichedSnapshot);
463463 }),
464 ).pipe(Effect.provide(AlwaysRunTestLayer)),
464 ).pipe(Effect.provide(layerAlwaysRunTest)),
465465 );
466466
467467 it.effect("ignores stale enrichment callbacks after a newer refresh advances generation", () =>
......@@ -522,7 +522,7 @@ describe("makeManagedServerProvider", () => {
522522 ]);
523523 assert.deepStrictEqual(latest, enrichedSnapshotSecond);
524524 }),
525 ).pipe(Effect.provide(AlwaysRunTestLayer)),
525 ).pipe(Effect.provide(layerAlwaysRunTest)),
526526 );
527527
528528 it.effect("applies runtime usage updates onto the published snapshot", () =>
......@@ -584,7 +584,7 @@ describe("makeManagedServerProvider", () => {
584584 });
585585 assert.deepStrictEqual(yield* provider.getSnapshot, update);
586586 }),
587 ).pipe(Effect.provide(AlwaysRunTestLayer)),
587 ).pipe(Effect.provide(layerAlwaysRunTest)),
588588 );
589589
590590 it.effect("keeps live usage windows across a failed probe and a stale enrichment", () =>
......@@ -655,6 +655,6 @@ describe("makeManagedServerProvider", () => {
655655 assert.strictEqual(refreshed.message, refreshedSnapshotSecond.message);
656656 assert.deepStrictEqual(refreshed.usageLimits?.windows, [liveWindow]);
657657 }),
658 ).pipe(Effect.provide(AlwaysRunTestLayer)),
658 ).pipe(Effect.provide(layerAlwaysRunTest)),
659659 );
660660});
apps/server/src/provider/opencode2/OpenCode2Client.events.test.ts+8-6
......@@ -54,7 +54,7 @@ const newerServerStream = Effect.gen(function* () {
5454 ];
5555});
5656
57const serving = (events: ReadonlyArray<unknown>) =>
57const layerServing = (events: ReadonlyArray<unknown>) =>
5858 Layer.succeed(
5959 HttpClient.HttpClient,
6060 HttpClient.make((request) =>
......@@ -75,7 +75,7 @@ const connect = Effect.gen(function* () {
7575});
7676
7777/** Sends `events`, then keeps the connection open without another byte, like a frozen server. */
78const servingThenSilent = (events: ReadonlyArray<unknown>) =>
78const layerServingThenSilent = (events: ReadonlyArray<unknown>) =>
7979 Layer.succeed(
8080 HttpClient.HttpClient,
8181 HttpClient.make((request) =>
......@@ -103,7 +103,9 @@ describe("OpenCode2Client events", () => {
103103 const { events } = yield* connect.pipe(
104104 Effect.provide(
105105 OpenCode2Client.layer.pipe(
106 Layer.provide(servingThenSilent([{ id: "evt_1", type: "server.connected", data: {} }])),
106 Layer.provide(
107 layerServingThenSilent([{ id: "evt_1", type: "server.connected", data: {} }]),
108 ),
107109 ),
108110 ),
109111 );
......@@ -124,7 +126,7 @@ describe("OpenCode2Client events", () => {
124126 Effect.provide(
125127 OpenCode2Client.layer.pipe(
126128 Layer.provide(
127 serving([
129 layerServing([
128130 {
129131 id: "evt_1",
130132 created: 1,
......@@ -167,7 +169,7 @@ describe("OpenCode2Client events", () => {
167169 Effect.gen(function* () {
168170 const events = yield* newerServerStream;
169171 const { events: subscribe } = yield* connect.pipe(
170 Effect.provide(OpenCode2Client.layer.pipe(Layer.provide(serving(events)))),
172 Effect.provide(OpenCode2Client.layer.pipe(Layer.provide(layerServing(events)))),
171173 );
172174 const types = yield* (yield* subscribe).pipe(
173175 Stream.map((event) => event.type),
......@@ -185,7 +187,7 @@ describe("OpenCode2Client events", () => {
185187 Effect.gen(function* () {
186188 const events = yield* newerServerStream;
187189 const { client } = yield* connect.pipe(
188 Effect.provide(OpenCode2Client.layer.pipe(Layer.provide(serving(events)))),
190 Effect.provide(OpenCode2Client.layer.pipe(Layer.provide(layerServing(events)))),
189191 );
190192 const failure = yield* client.event.subscribe().pipe(Stream.runDrain, Effect.flip);
191193 assert.strictEqual(failure._tag, "ClientError");
apps/server/src/provider/opencode2/OpenCode2Client.test.ts+2-2
......@@ -12,7 +12,7 @@ const INFO_BODY = '{"version":"2.0.18","pid":4242,"urls":[],"paths":{"tmp":"/tmp
1212/** Sends one request through the client and returns the Authorization header it carried. */
1313const authorizationFor = (password: string | Redacted.Redacted) => {
1414 const seen: Array<string | undefined> = [];
15 const capturing = Layer.succeed(
15 const layerCapturing = Layer.succeed(
1616 HttpClient.HttpClient,
1717 HttpClient.make((request) =>
1818 Effect.sync(() => {
......@@ -29,7 +29,7 @@ const authorizationFor = (password: string | Redacted.Redacted) => {
2929 const { client } = yield* opencode.connect({ baseUrl: "http://127.0.0.1:4096", password });
3030 yield* client.server.info();
3131 return seen[0];
32 }).pipe(Effect.provide(OpenCode2Client.layer.pipe(Layer.provide(capturing))));
32 }).pipe(Effect.provide(OpenCode2Client.layer.pipe(Layer.provide(layerCapturing))));
3333};
3434
3535const utf8Basic = (password: string) =>
apps/server/src/provider/opencode2/OpenCode2Server.live.test.ts+1-1
......@@ -229,7 +229,7 @@ describe.runIf(binaryPath !== undefined)("OpenCode2Server live", () => {
229229 Effect.provide(
230230 Layer.mergeAll(
231231 OpenCode2Client.layer,
232 OpenCodeRuntime.OpenCodeRuntimeLive.pipe(Layer.provide(OpenCodeServerLedger.layerTest)),
232 OpenCodeRuntime.layer.pipe(Layer.provide(OpenCodeServerLedger.layerTest)),
233233 ).pipe(Layer.provideMerge(Layer.mergeAll(NodeServices.layer, FetchHttpClient.layer))),
234234 ),
235235 ),
apps/server/src/provider/opencode2/OpenCode2Server.test.ts+19-13
......@@ -27,7 +27,7 @@ const INFO_BODY =
2727const UNAUTHORIZED_BODY = '{"_tag":"UnauthorizedError","message":"Authentication required"}';
2828const SPA_BODY = "<!doctype html><html><head><title>OpenCode</title></head></html>";
2929
30const serverReplying = (reply: {
30const layerServerReplying = (reply: {
3131 readonly status: number;
3232 readonly contentType?: string;
3333 readonly body: string;
......@@ -58,7 +58,7 @@ describe("OpenCode2Server.verifyServer", () => {
5858 it.effect("returns the version of an authenticated OpenCode 2 server", () =>
5959 Effect.gen(function* () {
6060 const version = yield* verify(
61 serverReplying({ status: 200, contentType: "application/json", body: INFO_BODY }),
61 layerServerReplying({ status: 200, contentType: "application/json", body: INFO_BODY }),
6262 );
6363 assert.strictEqual(version, "2.0.18");
6464 }),
......@@ -67,7 +67,11 @@ describe("OpenCode2Server.verifyServer", () => {
6767 it.effect("reports a rejected password, not a wrong server", () =>
6868 Effect.gen(function* () {
6969 const error = yield* verify(
70 serverReplying({ status: 401, contentType: "application/json", body: UNAUTHORIZED_BODY }),
70 layerServerReplying({
71 status: 401,
72 contentType: "application/json",
73 body: UNAUTHORIZED_BODY,
74 }),
7175 ).pipe(Effect.flip);
7276 assert.include(error.detail, "rejected the server password");
7377 }),
......@@ -76,7 +80,7 @@ describe("OpenCode2Server.verifyServer", () => {
7680 // 1.x rejects a wrong password with an empty 401, which the client cannot decode.
7781 it.effect("reports an empty-body 401 as a rejected password", () =>
7882 Effect.gen(function* () {
79 const error = yield* verify(serverReplying({ status: 401, body: "" })).pipe(Effect.flip);
83 const error = yield* verify(layerServerReplying({ status: 401, body: "" })).pipe(Effect.flip);
8084 assert.include(error.detail, "rejected the server password");
8185 }),
8286 );
......@@ -85,7 +89,7 @@ describe("OpenCode2Server.verifyServer", () => {
8589 Effect.gen(function* () {
8690 for (const status of [500, 502]) {
8791 const error = yield* verify(
88 serverReplying({ status, contentType: "text/plain", body: "upstream failed" }),
92 layerServerReplying({ status, contentType: "text/plain", body: "upstream failed" }),
8993 ).pipe(Effect.flip);
9094 assert.include(error.detail, `returned HTTP ${status}`);
9195 }
......@@ -96,7 +100,7 @@ describe("OpenCode2Server.verifyServer", () => {
96100 it.effect("rejects a server that answers with the web UI's HTML", () =>
97101 Effect.gen(function* () {
98102 const error = yield* verify(
99 serverReplying({ status: 200, contentType: "text/html", body: SPA_BODY }),
103 layerServerReplying({ status: 200, contentType: "text/html", body: SPA_BODY }),
100104 ).pipe(Effect.flip);
101105 assert.include(error.detail, "is not an OpenCode 2 server");
102106 }),
......@@ -129,24 +133,26 @@ describe("OpenCode2Server error details", () => {
129133 Effect.provide(
130134 Layer.mergeAll(
131135 OpenCode2Client.layer.pipe(Layer.provide(httpClient)),
132 OpenCodeRuntime.OpenCodeRuntimeLive.pipe(Layer.provide(OpenCodeServerLedger.layerTest)),
136 OpenCodeRuntime.layer.pipe(Layer.provide(OpenCodeServerLedger.layerTest)),
133137 ).pipe(Layer.provideMerge(NodeServices.layer)),
134138 ),
135139 );
136140
137141 it.effect("never include the server URL", () =>
138142 Effect.gen(function* () {
139 const hanging = Layer.succeed(
143 const layerHanging = Layer.succeed(
140144 HttpClient.HttpClient,
141145 HttpClient.make(() => Effect.never),
142146 );
143 const timedOut = yield* detailFor(hanging).pipe(Effect.forkChild);
147 const timedOut = yield* detailFor(layerHanging).pipe(Effect.forkChild);
144148 yield* TestClock.adjust("10 seconds");
145149 const details = [
146150 yield* detailFor(FetchHttpClient.layer),
147 yield* detailFor(serverReplying({ status: 401, body: "" })),
148 yield* detailFor(serverReplying({ status: 502, contentType: "text/plain", body: "" })),
149 yield* detailFor(serverReplying({ status: 200, contentType: "text/html", body: SPA_BODY })),
151 yield* detailFor(layerServerReplying({ status: 401, body: "" })),
152 yield* detailFor(layerServerReplying({ status: 502, contentType: "text/plain", body: "" })),
153 yield* detailFor(
154 layerServerReplying({ status: 200, contentType: "text/html", body: SPA_BODY }),
155 ),
150156 yield* Fiber.join(timedOut),
151157 ];
152158 assert.deepStrictEqual(details, [
......@@ -244,7 +250,7 @@ describe("OpenCode2Server spawned server", () => {
244250 Effect.provide(
245251 Layer.mergeAll(
246252 OpenCode2Client.layer,
247 OpenCodeRuntime.OpenCodeRuntimeLive.pipe(Layer.provide(OpenCodeServerLedger.layerTest)),
253 OpenCodeRuntime.layer.pipe(Layer.provide(OpenCodeServerLedger.layerTest)),
248254 ).pipe(Layer.provideMerge(Layer.mergeAll(NodeServices.layer, FetchHttpClient.layer))),
249255 ),
250256 ),
apps/server/src/provider/opencodeRuntime.environment.test.ts+1-1
......@@ -227,7 +227,7 @@ server.listen(0, "127.0.0.1", () => {
227227 }).pipe(
228228 Effect.scoped,
229229 Effect.provide([
230 OpenCodeRuntime.OpenCodeRuntimeLive.pipe(
230 OpenCodeRuntime.layer.pipe(
231231 Layer.provide(OpenCodeServerLedger.layerTest),
232232 Layer.provideMerge(NodeServices.layer),
233233 ),
apps/server/src/provider/opencodeRuntime.inventory.test.ts+2-2
......@@ -18,12 +18,12 @@ import {
1818import * as OpenCodeRuntime from "./opencodeRuntime.ts";
1919import * as OpenCodeServerLedger from "./OpenCodeServerLedger.ts";
2020
21const testLayer = OpenCodeRuntime.OpenCodeRuntimeLive.pipe(
21const layerTest = OpenCodeRuntime.layer.pipe(
2222 Layer.provide(OpenCodeServerLedger.layerTest),
2323 Layer.provideMerge(NodeServices.layer),
2424);
2525
26it.layer(testLayer)("OpenCodeRuntime inventory", (it) => {
26it.layer(layerTest)("OpenCodeRuntime inventory", (it) => {
2727 it.effect("aborts pending SDK requests when inventory loading is interrupted", () =>
2828 Effect.gen(function* () {
2929 const runtime = yield* OpenCodeRuntime.OpenCodeRuntime;
apps/server/src/provider/opencodeRuntime.ts+1-1
......@@ -1106,6 +1106,6 @@ export class OpenCodeRuntime extends Context.Service<OpenCodeRuntime, OpenCodeRu
11061106 "t3/provider/opencodeRuntime",
11071107) {}
11081108
1109export const OpenCodeRuntimeLive = Layer.effect(OpenCodeRuntime, makeOpenCodeRuntime).pipe(
1109export const layer = Layer.effect(OpenCodeRuntime, makeOpenCodeRuntime).pipe(
11101110 Layer.provide(NetService.layer),
11111111);
apps/server/src/provider/providerCompatibility.test.ts+3-3
......@@ -10,7 +10,7 @@ import * as Ref from "effect/Ref";
1010import * as Stream from "effect/Stream";
1111import * as ServerConfig from "../config.ts";
1212import * as ModelManifest from "./ModelManifest.ts";
13import { ProviderRegistryLive } from "./Layers/ProviderRegistry.ts";
13import * as ProviderRegistryLayer from "./Layers/ProviderRegistry.ts";
1414import * as ProviderRegistry from "./Services/ProviderRegistry.ts";
1515import * as ProviderInstanceRegistry from "./Services/ProviderInstanceRegistry.ts";
1616import type { ProviderInstance } from "./ProviderDriver.ts";
......@@ -323,7 +323,7 @@ it.effect("a remote policy refresh preserves a newer health result on the regist
323323 })),
324324 ),
325325 );
326 const dependencies = Layer.mergeAll(
326 const layerDependencies = Layer.mergeAll(
327327 Layer.succeed(ModelManifest.ModelManifest, {
328328 current: Ref.get(manifest),
329329 refresh,
......@@ -362,7 +362,7 @@ it.effect("a remote policy refresh preserves a newer health result on the regist
362362 assert.strictEqual(updated?.compatibilityAdvisory?.status, "supported");
363363 assert.strictEqual(updated?.status, "error");
364364 assert.strictEqual(updated?.message, "Authentication failed");
365 }).pipe(Effect.provide(ProviderRegistryLive.pipe(Layer.provide(dependencies))));
365 }).pipe(Effect.provide(ProviderRegistryLayer.layer.pipe(Layer.provide(layerDependencies))));
366366 }).pipe(Effect.scoped),
367367);
368368
apps/server/src/provider/providerMaintenanceRunner.test.ts+52-52
......@@ -42,7 +42,7 @@ const encoder = new TextEncoder();
4242// `{ command, args }` assertions below hold deterministically on any host
4343// (including Windows). Windows-specific resolution is covered by the dedicated
4444// win32 case at the end of this suite.
45const NonWindowsPlatform = Layer.succeed(HostProcessPlatform, "linux");
45const layerNonWindowsPlatform = Layer.succeed(HostProcessPlatform, "linux");
4646
4747function lifecycleFor(provider: ProviderDriverKind): ProviderMaintenanceCapabilities {
4848 if (provider === NATIVE_CLI_DRIVER) {
......@@ -92,7 +92,7 @@ const baseOpenCodeProvider: ServerProvider = {
9292 driver: OPENCODE_DRIVER,
9393};
9494
95const latestVersionHttpClient = (version: string) =>
95const layerLatestVersionHttpClient = (version: string) =>
9696 Layer.succeed(
9797 HttpClient.HttpClient,
9898 HttpClient.make((request) =>
......@@ -126,7 +126,7 @@ function mockHandle(result: {
126126 });
127127}
128128
129function mockSpawnerLayer(
129function layerMockSpawner(
130130 handler: (
131131 command: string,
132132 args: ReadonlyArray<string>,
......@@ -265,9 +265,9 @@ describe("providerMaintenanceRunner", () => {
265265 }).pipe(
266266 Effect.provide(
267267 Layer.mergeAll(
268 NonWindowsPlatform,
269 latestVersionHttpClient("0.0.0"),
270 mockSpawnerLayer((command, args) => {
268 layerNonWindowsPlatform,
269 layerLatestVersionHttpClient("0.0.0"),
270 layerMockSpawner((command, args) => {
271271 calls.push({ command, args });
272272 return { stdout: "updated" };
273273 }),
......@@ -294,9 +294,9 @@ describe("providerMaintenanceRunner", () => {
294294 }).pipe(
295295 Effect.provide(
296296 Layer.mergeAll(
297 NonWindowsPlatform,
298 latestVersionHttpClient("0.0.0"),
299 mockSpawnerLayer(() => ({ stdout: "updated" })),
297 layerNonWindowsPlatform,
298 layerLatestVersionHttpClient("0.0.0"),
299 layerMockSpawner(() => ({ stdout: "updated" })),
300300 ),
301301 ),
302302 );
......@@ -321,9 +321,9 @@ describe("providerMaintenanceRunner", () => {
321321 }).pipe(
322322 Effect.provide(
323323 Layer.mergeAll(
324 NonWindowsPlatform,
325 latestVersionHttpClient("0.0.0"),
326 mockSpawnerLayer(() => ({ stdout: "updated" })),
324 layerNonWindowsPlatform,
325 layerLatestVersionHttpClient("0.0.0"),
326 layerMockSpawner(() => ({ stdout: "updated" })),
327327 ),
328328 ),
329329 );
......@@ -354,9 +354,9 @@ describe("providerMaintenanceRunner", () => {
354354 }).pipe(
355355 Effect.provide(
356356 Layer.mergeAll(
357 NonWindowsPlatform,
358 latestVersionHttpClient("0.0.0"),
359 mockSpawnerLayer((_command, _args, options) => {
357 layerNonWindowsPlatform,
358 layerLatestVersionHttpClient("0.0.0"),
359 layerMockSpawner((_command, _args, options) => {
360360 seen.push(options.env);
361361 return { stdout: "updated" };
362362 }),
......@@ -395,9 +395,9 @@ describe("providerMaintenanceRunner", () => {
395395 }).pipe(
396396 Effect.provide(
397397 Layer.mergeAll(
398 NonWindowsPlatform,
399 latestVersionHttpClient("0.0.0"),
400 mockSpawnerLayer((command, args) => {
398 layerNonWindowsPlatform,
399 layerLatestVersionHttpClient("0.0.0"),
400 layerMockSpawner((command, args) => {
401401 calls.push({ command, args });
402402 return { stdout: "updated" };
403403 }),
......@@ -440,9 +440,9 @@ describe("providerMaintenanceRunner", () => {
440440 }).pipe(
441441 Effect.provide(
442442 Layer.mergeAll(
443 NonWindowsPlatform,
444 latestVersionHttpClient("0.0.0"),
445 mockSpawnerLayer((command) => {
443 layerNonWindowsPlatform,
444 layerLatestVersionHttpClient("0.0.0"),
445 layerMockSpawner((command) => {
446446 calls.push(command);
447447 return { stdout: "updated" };
448448 }),
......@@ -490,9 +490,9 @@ describe("providerMaintenanceRunner", () => {
490490 }).pipe(
491491 Effect.provide(
492492 Layer.mergeAll(
493 NonWindowsPlatform,
494 latestVersionHttpClient("0.0.0"),
495 mockSpawnerLayer((command, args) => {
493 layerNonWindowsPlatform,
494 layerLatestVersionHttpClient("0.0.0"),
495 layerMockSpawner((command, args) => {
496496 calls.push({ command, args });
497497 return { stdout: "updated" };
498498 }),
......@@ -521,9 +521,9 @@ describe("providerMaintenanceRunner", () => {
521521 }).pipe(
522522 Effect.provide(
523523 Layer.mergeAll(
524 NonWindowsPlatform,
525 latestVersionHttpClient("0.0.0"),
526 mockSpawnerLayer((command, args) => {
524 layerNonWindowsPlatform,
525 layerLatestVersionHttpClient("0.0.0"),
526 layerMockSpawner((command, args) => {
527527 calls.push({ command, args });
528528 return { stdout: "updated" };
529529 }),
......@@ -594,9 +594,9 @@ describe("providerMaintenanceRunner", () => {
594594 }).pipe(
595595 Effect.provide(
596596 Layer.mergeAll(
597 NonWindowsPlatform,
598 latestVersionHttpClient("0.124.0-alpha.3"),
599 mockSpawnerLayer((command, args) => {
597 layerNonWindowsPlatform,
598 layerLatestVersionHttpClient("0.124.0-alpha.3"),
599 layerMockSpawner((command, args) => {
600600 calls.push({ command, args });
601601 return { stdout: "updated" };
602602 }),
......@@ -619,9 +619,9 @@ describe("providerMaintenanceRunner", () => {
619619 }).pipe(
620620 Effect.provide(
621621 Layer.mergeAll(
622 NonWindowsPlatform,
623 latestVersionHttpClient("0.0.0"),
624 mockSpawnerLayer(() => ({ stderr: "permission denied", code: 1 })),
622 layerNonWindowsPlatform,
623 layerLatestVersionHttpClient("0.0.0"),
624 layerMockSpawner(() => ({ stderr: "permission denied", code: 1 })),
625625 ),
626626 ),
627627 ),
......@@ -645,9 +645,9 @@ describe("providerMaintenanceRunner", () => {
645645 }).pipe(
646646 Effect.provide(
647647 Layer.mergeAll(
648 NonWindowsPlatform,
649 latestVersionHttpClient("9.9.9"),
650 mockSpawnerLayer(() => ({ stdout: "updated" })),
648 layerNonWindowsPlatform,
649 layerLatestVersionHttpClient("9.9.9"),
650 layerMockSpawner(() => ({ stdout: "updated" })),
651651 ),
652652 ),
653653 ),
......@@ -684,9 +684,9 @@ describe("providerMaintenanceRunner", () => {
684684 }).pipe(
685685 Effect.provide(
686686 Layer.mergeAll(
687 NonWindowsPlatform,
688 latestVersionHttpClient("0.0.0"),
689 mockSpawnerLayer(() => {
687 layerNonWindowsPlatform,
688 layerLatestVersionHttpClient("0.0.0"),
689 layerMockSpawner(() => {
690690 startedLatch.resolve();
691691 return {
692692 stdout: "updated",
......@@ -761,9 +761,9 @@ describe("providerMaintenanceRunner", () => {
761761 }).pipe(
762762 Effect.provide(
763763 Layer.mergeAll(
764 NonWindowsPlatform,
765 latestVersionHttpClient("2.0.0"),
766 mockSpawnerLayer((_command, args) => {
764 layerNonWindowsPlatform,
765 layerLatestVersionHttpClient("2.0.0"),
766 layerMockSpawner((_command, args) => {
767767 calls.push(args.join(" "));
768768 if (calls.length === 1) {
769769 firstStartedLatch.resolve();
......@@ -805,9 +805,9 @@ describe("providerMaintenanceRunner", () => {
805805 }).pipe(
806806 Effect.provide(
807807 Layer.mergeAll(
808 NonWindowsPlatform,
809 latestVersionHttpClient("0.0.0"),
810 mockSpawnerLayer((_command, args) => {
808 layerNonWindowsPlatform,
809 layerLatestVersionHttpClient("0.0.0"),
810 layerMockSpawner((_command, args) => {
811811 calls.push(args.join(" "));
812812 return { stdout: "updated" };
813813 }),
......@@ -860,9 +860,9 @@ describe("providerMaintenanceRunner", () => {
860860 }).pipe(
861861 Effect.provide(
862862 Layer.mergeAll(
863 NonWindowsPlatform,
864 latestVersionHttpClient("0.0.0"),
865 mockSpawnerLayer(() => ({ stdout: "updated" })),
863 layerNonWindowsPlatform,
864 layerLatestVersionHttpClient("0.0.0"),
865 layerMockSpawner(() => ({ stdout: "updated" })),
866866 ),
867867 ),
868868 ),
......@@ -908,7 +908,7 @@ describe("providerMaintenanceRunner", () => {
908908 Layer.succeed(SpawnExecutableResolution, (command) =>
909909 command === "npm" ? "C:\\fake\\npm\\npm.cmd" : undefined,
910910 ),
911 latestVersionHttpClient("0.0.0"),
911 layerLatestVersionHttpClient("0.0.0"),
912912 Layer.succeed(
913913 ChildProcessSpawner.ChildProcessSpawner,
914914 ChildProcessSpawner.make((command) => {
......@@ -1009,9 +1009,9 @@ it.effect("refuses incompatible latest versions and unapproved or unpinnable tar
10091009 }).pipe(
10101010 Effect.provide(
10111011 Layer.mergeAll(
1012 NonWindowsPlatform,
1013 latestVersionHttpClient("3.0.0"),
1014 mockSpawnerLayer((_command, args) => {
1012 layerNonWindowsPlatform,
1013 layerLatestVersionHttpClient("3.0.0"),
1014 layerMockSpawner((_command, args) => {
10151015 calls.push(args.join(" "));
10161016 return { stdout: "installed" };
10171017 }),
apps/server/src/provider/testUtils/providerRegistryMock.ts+1-1
......@@ -18,5 +18,5 @@ const makeProviderRegistryMock = (
1818 streamChanges: Stream.empty,
1919});
2020
21export const makeProviderRegistryLayer = (providers: ReadonlyArray<ServerProvider> = []) =>
21export const layer = (providers: ReadonlyArray<ServerProvider> = []) =>
2222 Layer.succeed(ProviderRegistry.ProviderRegistry, makeProviderRegistryMock(providers));
apps/server/src/pullRequest/PullRequestService.test.ts+2-2
......@@ -25,7 +25,7 @@ import * as ServerSettingsService from "../serverSettings.ts";
2525import { PullRequestOperationError } from "@t3tools/contracts";
2626
2727import * as ProjectService from "../project/ProjectService.ts";
28import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts";
28import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts";
2929import * as PullRequestFilesViewed from "../persistence/PullRequestFilesViewed.ts";
3030import * as RepositoryIdentityResolver from "../project/RepositoryIdentityResolver.ts";
3131import * as SourceControlProviderRegistry from "../sourceControl/SourceControlProviderRegistry.ts";
......@@ -505,7 +505,7 @@ function makeService(input: {
505505 SourceControlRateLimit.layer,
506506 // The real store over a database of its own, so the environment-kept marks are exercised
507507 // through the SQL that holds them rather than through a stand-in that agrees with itself.
508 PullRequestFilesViewed.layer.pipe(Layer.provide(SqlitePersistenceMemory)),
508 PullRequestFilesViewed.layer.pipe(Layer.provide(SqlitePersistence.layerMemory)),
509509 Layer.effect(PullRequestReadCache.PullRequestReadCache, PullRequestReadCache.make).pipe(
510510 Layer.provide(KeyValueStore.layerMemory),
511511 Layer.provide(NodeServices.layer),
apps/server/src/pullRequest/http.ts+1-1
......@@ -6,7 +6,7 @@ import { annotateEnvironmentRequest, requireEnvironmentScope } from "../auth/htt
66import * as PullRequestService from "./PullRequestService.ts";
77
88/** The patch is often the largest PR payload and benefits from HTTP compression and flow control. */
9export const pullRequestHttpApiLayer = HttpApiBuilder.group(
9export const layer = HttpApiBuilder.group(
1010 EnvironmentHttpApi,
1111 "pullRequests",
1212 Effect.fnUntraced(function* (handlers) {
apps/server/src/pullRequest/linkedThreads.test.ts+2-2
......@@ -3,7 +3,7 @@ import * as Effect from "effect/Effect";
33import * as Schema from "effect/Schema";
44import * as SqlClient from "effect/sql/SqlClient";
55
6import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts";
6import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts";
77import { listLinkedPullRequestThreads } from "./linkedThreads.ts";
88
99const encodePayload = Schema.encodeEffect(Schema.fromJsonString(Schema.Unknown));
......@@ -180,5 +180,5 @@ it.effect(
180180 }),
181181 { threads: [] },
182182 );
183 }).pipe(Effect.provide(SqlitePersistenceMemory)),
183 }).pipe(Effect.provide(SqlitePersistence.layerMemory)),
184184);
apps/server/src/resourceTelemetry/ResourceTelemetry.test.ts+35-35
......@@ -152,7 +152,7 @@ describe("ResourceTelemetry", () => {
152152 childWriteBytes: 1_000,
153153 });
154154 const demandChanges = yield* Ref.make<ReadonlyArray<boolean>>([]);
155 const nativeLayer = NativeTelemetryClient.layerTest({
155 const layerNative = NativeTelemetryClient.layerTest({
156156 sampleNow: Effect.succeed(nativeGeneration(sample, 0)),
157157 health: Effect.succeed({
158158 status: "healthy",
......@@ -163,13 +163,13 @@ describe("ResourceTelemetry", () => {
163163 sampleIntervalMs: 1_000,
164164 }),
165165 });
166 const desktopLayer = DesktopTelemetryReceiver.layerTest({
166 const layerDesktop = DesktopTelemetryReceiver.layerTest({
167167 latest: Effect.succeedSome(desktopSnapshot(sampledAtUnixMs)),
168168 setDiagnosticsDemand: (enabled) =>
169169 Ref.update(demandChanges, (changes) => [...changes, enabled]),
170170 });
171 const telemetryLayer = ResourceTelemetry.layer.pipe(
172 Layer.provide(Layer.mergeAll(nativeLayer, desktopLayer, ResourceAttribution.layer)),
171 const layerTelemetry = ResourceTelemetry.layer.pipe(
172 Layer.provide(Layer.mergeAll(layerNative, layerDesktop, ResourceAttribution.layer)),
173173 );
174174
175175 const live = yield* Stream.runHead(
......@@ -177,7 +177,7 @@ describe("ResourceTelemetry", () => {
177177 const telemetry = yield* ResourceTelemetry.ResourceTelemetry;
178178 return telemetry.changes;
179179 }).pipe(Stream.unwrap),
180 ).pipe(Effect.provide(telemetryLayer));
180 ).pipe(Effect.provide(layerTelemetry));
181181
182182 expect(Option.isSome(live)).toBe(true);
183183 expect(yield* Ref.get(demandChanges)).toEqual([true, false]);
......@@ -188,7 +188,7 @@ describe("ResourceTelemetry", () => {
188188 Effect.gen(function* () {
189189 const sampledAtUnixMs = DateTime.toEpochMillis(yield* DateTime.now);
190190 const demandChanges = yield* Ref.make<ReadonlyArray<boolean>>([]);
191 const nativeLayer = NativeTelemetryClient.layerTest({
191 const layerNative = NativeTelemetryClient.layerTest({
192192 sampleNow: Effect.never,
193193 health: Effect.succeed({
194194 status: "healthy",
......@@ -199,13 +199,13 @@ describe("ResourceTelemetry", () => {
199199 sampleIntervalMs: 1_000,
200200 }),
201201 });
202 const desktopLayer = DesktopTelemetryReceiver.layerTest({
202 const layerDesktop = DesktopTelemetryReceiver.layerTest({
203203 latest: Effect.succeedSome(desktopSnapshot(sampledAtUnixMs)),
204204 setDiagnosticsDemand: (enabled) =>
205205 Ref.update(demandChanges, (changes) => [...changes, enabled]),
206206 });
207 const telemetryLayer = ResourceTelemetry.layer.pipe(
208 Layer.provide(Layer.mergeAll(nativeLayer, desktopLayer, ResourceAttribution.layer)),
207 const layerTelemetry = ResourceTelemetry.layer.pipe(
208 Layer.provide(Layer.mergeAll(layerNative, layerDesktop, ResourceAttribution.layer)),
209209 );
210210
211211 const resultFiber = yield* Stream.runHead(
......@@ -213,7 +213,7 @@ describe("ResourceTelemetry", () => {
213213 const telemetry = yield* ResourceTelemetry.ResourceTelemetry;
214214 return telemetry.changes;
215215 }).pipe(Stream.unwrap),
216 ).pipe(Effect.timeoutOption("10 millis"), Effect.provide(telemetryLayer), Effect.forkChild);
216 ).pipe(Effect.timeoutOption("10 millis"), Effect.provide(layerTelemetry), Effect.forkChild);
217217 yield* Effect.yieldNow;
218218 yield* TestClock.adjust("10 millis");
219219 const result = yield* Fiber.join(resultFiber);
......@@ -239,7 +239,7 @@ describe("ResourceTelemetry", () => {
239239 ...desktopSnapshot(sampledAtUnixMs),
240240 electronProcesses: [],
241241 };
242 const nativeLayer = NativeTelemetryClient.layerTest({
242 const layerNative = NativeTelemetryClient.layerTest({
243243 sampleNow: Effect.succeed(nativeGeneration(sample, 0)),
244244 health: Effect.succeed({
245245 status: "healthy",
......@@ -250,17 +250,17 @@ describe("ResourceTelemetry", () => {
250250 sampleIntervalMs: 1_000,
251251 }),
252252 });
253 const desktopLayer = DesktopTelemetryReceiver.layerTest({
253 const layerDesktop = DesktopTelemetryReceiver.layerTest({
254254 latest: Effect.succeedSome(desktop),
255255 });
256 const telemetryLayer = ResourceTelemetry.layer.pipe(
257 Layer.provide(Layer.mergeAll(nativeLayer, desktopLayer, ResourceAttribution.layer)),
256 const layerTelemetry = ResourceTelemetry.layer.pipe(
257 Layer.provide(Layer.mergeAll(layerNative, layerDesktop, ResourceAttribution.layer)),
258258 );
259259
260260 const snapshot = yield* Effect.gen(function* () {
261261 const telemetry = yield* ResourceTelemetry.ResourceTelemetry;
262262 return yield* telemetry.refresh;
263 }).pipe(Effect.provide(telemetryLayer));
263 }).pipe(Effect.provide(layerTelemetry));
264264
265265 expect(snapshot.processes.find((entry) => entry.identity.pid === 5_000)?.category).toBe(
266266 "electron-main",
......@@ -288,7 +288,7 @@ describe("ResourceTelemetry", () => {
288288 });
289289 const nativeSnapshots =
290290 yield* PubSub.unbounded<NativeTelemetryClient.NativeTelemetrySnapshot>();
291 const nativeLayer = NativeTelemetryClient.layerTest({
291 const layerNative = NativeTelemetryClient.layerTest({
292292 snapshots: Stream.fromPubSub(nativeSnapshots),
293293 sampleNow: Effect.never,
294294 health: Effect.succeed({
......@@ -300,10 +300,10 @@ describe("ResourceTelemetry", () => {
300300 sampleIntervalMs: 1_000,
301301 }),
302302 });
303 const telemetryLayer = ResourceTelemetry.layer.pipe(
303 const layerTelemetry = ResourceTelemetry.layer.pipe(
304304 Layer.provide(
305305 Layer.mergeAll(
306 nativeLayer,
306 layerNative,
307307 DesktopTelemetryReceiver.layerTest(),
308308 ResourceAttribution.layer,
309309 ),
......@@ -326,7 +326,7 @@ describe("ResourceTelemetry", () => {
326326 expect(DateTime.toEpochMillis(Option.getOrThrow(received).readAt)).toBe(
327327 current.sampledAtUnixMs,
328328 );
329 }).pipe(Effect.provide(telemetryLayer));
329 }).pipe(Effect.provide(layerTelemetry));
330330 }),
331331 ),
332332 );
......@@ -343,7 +343,7 @@ describe("ResourceTelemetry", () => {
343343 yield* PubSub.sliding<DesktopTelemetryReceiver.DesktopTelemetryReceiverHealth>(4);
344344 const healthSubscribed = yield* Deferred.make<void>();
345345 const finishDesktopSnapshot = yield* Deferred.make<void>();
346 const desktopLayer = DesktopTelemetryReceiver.layerTest({
346 const layerDesktop = DesktopTelemetryReceiver.layerTest({
347347 health: Ref.get(health),
348348 subscribeHealth: Effect.gen(function* () {
349349 const subscription = yield* PubSub.subscribe(healthChanges);
......@@ -361,11 +361,11 @@ describe("ResourceTelemetry", () => {
361361 }),
362362 ),
363363 });
364 const telemetryLayer = ResourceTelemetry.layer.pipe(
364 const layerTelemetry = ResourceTelemetry.layer.pipe(
365365 Layer.provide(
366366 Layer.mergeAll(
367367 NativeTelemetryClient.layerTest(),
368 desktopLayer,
368 layerDesktop,
369369 ResourceAttribution.layer,
370370 ),
371371 ),
......@@ -377,7 +377,7 @@ describe("ResourceTelemetry", () => {
377377 if (snapshot.health.desktop.status === "degraded") return snapshot;
378378 yield* Effect.yieldNow;
379379 }
380 }).pipe(Effect.provide(telemetryLayer), Effect.timeout("1 second"), Effect.forkChild);
380 }).pipe(Effect.provide(layerTelemetry), Effect.timeout("1 second"), Effect.forkChild);
381381
382382 yield* Deferred.await(healthSubscribed);
383383 const degraded: DesktopTelemetryReceiver.DesktopTelemetryReceiverHealth = {
......@@ -407,7 +407,7 @@ describe("ResourceTelemetry", () => {
407407 const rebuildStarted = yield* Deferred.make<void>();
408408 const finishRebuild = yield* Deferred.make<void>();
409409 const attributionReads = yield* Ref.make(0);
410 const attributionLayer = Layer.succeed(
410 const layerAttribution = Layer.succeed(
411411 ResourceAttribution.ResourceAttribution,
412412 ResourceAttribution.ResourceAttribution.of({
413413 record: () => Effect.void,
......@@ -428,7 +428,7 @@ describe("ResourceTelemetry", () => {
428428 ),
429429 }),
430430 );
431 const nativeLayer = NativeTelemetryClient.layerTest({
431 const layerNative = NativeTelemetryClient.layerTest({
432432 sampleNow: Effect.never,
433433 health: Effect.succeed({
434434 status: "healthy",
......@@ -439,11 +439,11 @@ describe("ResourceTelemetry", () => {
439439 sampleIntervalMs: 1_000,
440440 }),
441441 });
442 const desktopLayer = DesktopTelemetryReceiver.layerTest({
442 const layerDesktop = DesktopTelemetryReceiver.layerTest({
443443 changes: Stream.fromPubSub(desktopChanges),
444444 });
445 const telemetryLayer = ResourceTelemetry.layer.pipe(
446 Layer.provide(Layer.mergeAll(nativeLayer, desktopLayer, attributionLayer)),
445 const layerTelemetry = ResourceTelemetry.layer.pipe(
446 Layer.provide(Layer.mergeAll(layerNative, layerDesktop, layerAttribution)),
447447 );
448448
449449 yield* Effect.gen(function* () {
......@@ -461,7 +461,7 @@ describe("ResourceTelemetry", () => {
461461 nextDesktopSnapshot.sampledAtUnixMs,
462462 );
463463 expect(subscription.latest.power).toEqual(nextDesktopSnapshot.power);
464 }).pipe(Effect.provide(telemetryLayer));
464 }).pipe(Effect.provide(layerTelemetry));
465465 }),
466466 ),
467467 );
......@@ -520,7 +520,7 @@ describe("ResourceTelemetry", () => {
520520 });
521521 const nativeHealthChanges =
522522 yield* PubSub.sliding<NativeTelemetryClient.NativeTelemetryClientHealth>(4);
523 const nativeLayer = NativeTelemetryClient.layerTest({
523 const layerNative = NativeTelemetryClient.layerTest({
524524 setExternalProcesses: (processes) => Ref.set(externalProcesses, processes),
525525 readHistory: () => Effect.succeed(samples.slice(0, 2)),
526526 sampleNow: Ref.modify(sampleIndex, (index) => {
......@@ -538,7 +538,7 @@ describe("ResourceTelemetry", () => {
538538 };
539539 }),
540540 });
541 const desktopLayer = DesktopTelemetryReceiver.layerTest({
541 const layerDesktop = DesktopTelemetryReceiver.layerTest({
542542 latest: Effect.succeedSome(desktopSnapshot(startedAt)),
543543 health: Effect.succeed({
544544 status: "healthy",
......@@ -546,10 +546,10 @@ describe("ResourceTelemetry", () => {
546546 lastError: Option.none(),
547547 }),
548548 });
549 const attributionLayer = ResourceAttribution.layer;
550 const dependencies = Layer.mergeAll(nativeLayer, desktopLayer, attributionLayer);
551 const telemetryLayer = ResourceTelemetry.layer.pipe(Layer.provide(dependencies));
552 const layer = Layer.mergeAll(dependencies, telemetryLayer);
549 const layerAttribution = ResourceAttribution.layer;
550 const layerDependencies = Layer.mergeAll(layerNative, layerDesktop, layerAttribution);
551 const layerTelemetry = ResourceTelemetry.layer.pipe(Layer.provide(layerDependencies));
552 const layer = Layer.mergeAll(layerDependencies, layerTelemetry);
553553
554554 yield* Effect.gen(function* () {
555555 const telemetry = yield* ResourceTelemetry.ResourceTelemetry;
apps/server/src/review/ReviewService.test.ts+7-7
......@@ -11,7 +11,7 @@ import * as GitVcsDriver from "../vcs/GitVcsDriver.ts";
1111import * as VcsDriverRegistry from "../vcs/VcsDriverRegistry.ts";
1212import * as ReviewService from "./ReviewService.ts";
1313
14function makeLayer(input: {
14function layer(input: {
1515 readonly workspaceRoot: string;
1616 readonly baseDir: string;
1717 readonly detectCalls?: Array<{ readonly cwd: string }>;
......@@ -54,7 +54,7 @@ describe("ReviewService", () => {
5454 const error = yield* Effect.gen(function* () {
5555 const review = yield* ReviewService.ReviewService;
5656 return yield* review.getDiffPreview({ cwd: outsideRoot }).pipe(Effect.flip);
57 }).pipe(Effect.provide(makeLayer({ workspaceRoot, baseDir, detectCalls })));
57 }).pipe(Effect.provide(layer({ workspaceRoot, baseDir, detectCalls })));
5858
5959 assert.strictEqual(error._tag, "VcsRepositoryDetectionError");
6060 assert.strictEqual(error.operation, "ReviewService.getDiffPreview");
......@@ -87,7 +87,7 @@ describe("ReviewService", () => {
8787 newPath: "file.ts",
8888 })
8989 .pipe(Effect.flip);
90 }).pipe(Effect.provide(makeLayer({ workspaceRoot, baseDir, detectCalls })));
90 }).pipe(Effect.provide(layer({ workspaceRoot, baseDir, detectCalls })));
9191
9292 assert.strictEqual(error._tag, "VcsRepositoryDetectionError");
9393 assert.strictEqual(error.operation, "ReviewService.getDiffFileContents");
......@@ -112,7 +112,7 @@ describe("ReviewService", () => {
112112 return yield* review.getDiffPreview({ cwd: previous });
113113 }).pipe(
114114 Effect.provide(
115 makeLayer({
115 layer({
116116 workspaceRoot,
117117 baseDir,
118118 worktreesDirectory: "/",
......@@ -128,7 +128,7 @@ describe("ReviewService", () => {
128128 const error = yield* Effect.gen(function* () {
129129 const review = yield* ReviewService.ReviewService;
130130 return yield* review.getDiffPreview({ cwd: outsideRoot }).pipe(Effect.flip);
131 }).pipe(Effect.provide(makeLayer({ workspaceRoot, baseDir, worktreesDirectory })));
131 }).pipe(Effect.provide(layer({ workspaceRoot, baseDir, worktreesDirectory })));
132132 assert.strictEqual(error._tag, "VcsRepositoryDetectionError");
133133 }
134134 }).pipe(Effect.provide(NodeServices.layer)),
......@@ -144,7 +144,7 @@ describe("ReviewService", () => {
144144 const result = yield* Effect.gen(function* () {
145145 const review = yield* ReviewService.ReviewService;
146146 return yield* review.getDiffPreview({ cwd: workspaceRoot });
147 }).pipe(Effect.provide(makeLayer({ workspaceRoot, baseDir, detectCalls })));
147 }).pipe(Effect.provide(layer({ workspaceRoot, baseDir, detectCalls })));
148148
149149 assert.strictEqual(result.cwd, workspaceRoot);
150150 assert.deepStrictEqual(result.sources, []);
......@@ -163,7 +163,7 @@ describe("ReviewService", () => {
163163 const error = yield* Effect.gen(function* () {
164164 const review = yield* ReviewService.ReviewService;
165165 return yield* review.getDiffPreview({ cwd: invalidCwd }).pipe(Effect.flip);
166 }).pipe(Effect.provide(makeLayer({ workspaceRoot, baseDir, detectCalls })));
166 }).pipe(Effect.provide(layer({ workspaceRoot, baseDir, detectCalls })));
167167
168168 assert.strictEqual(error._tag, "VcsRepositoryDetectionError");
169169 if (error._tag !== "VcsRepositoryDetectionError") return;
apps/server/src/scheduledTasks/ScheduledTaskService.schedule.test.ts+7-7
......@@ -11,14 +11,14 @@ import * as TestClock from "effect/testing/TestClock";
1111import * as ThreadLaunchService from "../orchestration-v2/ThreadLaunchService.ts";
1212import * as ThreadManagementService from "../orchestration-v2/ThreadManagementService.ts";
1313import * as SecretRequests from "../secrets/SecretRequests.ts";
14import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts";
14import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts";
1515import * as ScheduledTaskService from "./ScheduledTaskService.ts";
1616
1717const decodeUpsertInput = Schema.decodeUnknownEffect(ScheduledTaskUpsertInput);
1818
1919it.effect("rejects a stale form save after deletion while preserving explicit-id creates", () =>
2020 Effect.gen(function* () {
21 const dependencies = Layer.mergeAll(
21 const layerDependencies = Layer.mergeAll(
2222 NodeCrypto.layer,
2323 Scheduler.layer,
2424 Layer.mock(ThreadLaunchService.ThreadLaunchService)({}),
......@@ -49,8 +49,8 @@ it.effect("rejects a stale form save after deletion while preserving explicit-id
4949 expect((yield* service.list()).tasks).toEqual([]);
5050
5151 expect((yield* service.upsert(input)).task.id).toBe(created.task.id);
52 }).pipe(Effect.provide(ScheduledTaskService.layer.pipe(Layer.provide(dependencies))));
53 }).pipe(Effect.provide(SqlitePersistenceMemory)),
52 }).pipe(Effect.provide(ScheduledTaskService.layer.pipe(Layer.provide(layerDependencies))));
53 }).pipe(Effect.provide(SqlitePersistence.layerMemory)),
5454);
5555
5656it.effect("preserves a due run when a save only pads the scheduled hour", () =>
......@@ -61,7 +61,7 @@ it.effect("preserves a due run when a save only pads the scheduled hour", () =>
6161 );
6262 yield* TestClock.setTime(DateTime.toEpochMillis(dueAt) - 1_000);
6363
64 const dependencies = Layer.mergeAll(
64 const layerDependencies = Layer.mergeAll(
6565 NodeCrypto.layer,
6666 Scheduler.layer,
6767 Layer.mock(ThreadLaunchService.ThreadLaunchService)({}),
......@@ -105,6 +105,6 @@ it.effect("preserves a due run when a save only pads the scheduled hour", () =>
105105 expect(rescheduled.task.nextRunAt).toBe(
106106 DateTime.formatIso(DateTime.toUtc(DateTime.add(dueAt, { minutes: 30 }))),
107107 );
108 }).pipe(Effect.provide(ScheduledTaskService.layer.pipe(Layer.provide(dependencies))));
109 }).pipe(Effect.provide(SqlitePersistenceMemory)),
108 }).pipe(Effect.provide(ScheduledTaskService.layer.pipe(Layer.provide(layerDependencies))));
109 }).pipe(Effect.provide(SqlitePersistence.layerMemory)),
110110);
apps/server/src/scheduledTasks/ScheduledTaskService.test.ts+4-4
......@@ -18,7 +18,7 @@ import * as TestClock from "effect/testing/TestClock";
1818import * as ThreadLaunchService from "../orchestration-v2/ThreadLaunchService.ts";
1919import * as ThreadManagementService from "../orchestration-v2/ThreadManagementService.ts";
2020import * as SecretRequests from "../secrets/SecretRequests.ts";
21import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts";
21import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts";
2222import * as ScheduledTaskService from "./ScheduledTaskService.ts";
2323
2424const isScheduledTaskError = Schema.is(ScheduledTaskError);
......@@ -154,7 +154,7 @@ it.effect("loads only due tasks and skips corrupt due rows without decoding sett
154154 last_run_status: string;
155155 }>`SELECT last_run_status FROM scheduled_tasks WHERE task_id = 'running'`;
156156 assert.equal(running[0]?.last_run_status, "running");
157 }).pipe(Effect.provide(SqlitePersistenceMemory)),
157 }).pipe(Effect.provide(SqlitePersistence.layerMemory)),
158158);
159159
160160it.effect(
......@@ -268,7 +268,7 @@ it.effect(
268268 assert.isNull(byId.get("due-huge")?.next_run_at);
269269 assert.equal(byId.get("due-bad-date")?.last_run_status, "never");
270270 assert.equal(byId.get("due-bad-date")?.run_count, 0);
271 }).pipe(Effect.provide(SqlitePersistenceMemory)),
271 }).pipe(Effect.provide(SqlitePersistence.layerMemory)),
272272);
273273
274274it.effect(
......@@ -349,5 +349,5 @@ it.effect(
349349 yield* Deferred.succeed(releaseLast, undefined);
350350 }),
351351 );
352 }).pipe(Effect.provide(SqlitePersistenceMemory)),
352 }).pipe(Effect.provide(SqlitePersistence.layerMemory)),
353353);
apps/server/src/scheduledTasks/ScheduledTaskService.webhook.test.ts+4-4
......@@ -17,7 +17,7 @@ import * as TestClock from "effect/testing/TestClock";
1717import * as ThreadLaunchService from "../orchestration-v2/ThreadLaunchService.ts";
1818import * as ThreadManagementService from "../orchestration-v2/ThreadManagementService.ts";
1919import * as SecretRequests from "../secrets/SecretRequests.ts";
20import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts";
20import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts";
2121import * as Scheduler from "../scheduling/Scheduler.ts";
2222import * as ScheduledTaskService from "./ScheduledTaskService.ts";
2323
......@@ -75,7 +75,7 @@ const withService = <A, E>(
7575 Effect.gen(function* () {
7676 const launches = yield* Queue.unbounded<LaunchInput>();
7777 const secretsByRef = new Map<string, string>();
78 const dependencies = Layer.mergeAll(
78 const layerDependencies = Layer.mergeAll(
7979 NodePlatformCrypto.layer,
8080 Scheduler.layer,
8181 Layer.mock(ThreadLaunchService.ThreadLaunchService)({
......@@ -103,8 +103,8 @@ const withService = <A, E>(
103103 return yield* Effect.gen(function* () {
104104 const service = yield* ScheduledTaskService.ScheduledTaskService;
105105 return yield* body({ service, launches, secretsByRef });
106 }).pipe(Effect.provide(ScheduledTaskService.layer.pipe(Layer.provide(dependencies))));
107 }).pipe(Effect.provide(SqlitePersistenceMemory));
106 }).pipe(Effect.provide(ScheduledTaskService.layer.pipe(Layer.provide(layerDependencies))));
107 }).pipe(Effect.provide(SqlitePersistence.layerMemory));
108108
109109it.effect("dispatches exactly the rendered prompt and logs the delivery", () =>
110110 withService(({ service, launches }) =>
apps/server/src/scheduledTasks/webhookRoute.test.ts+3-2
......@@ -27,7 +27,8 @@ import * as ServerSecretStore from "../auth/ServerSecretStore.ts";
2727import { CLOUD_MINT_PUBLIC_KEY, RELAY_ISSUER_SECRET } from "../cloud/config.ts";
2828import * as ServerEnvironment from "../environment/ServerEnvironment.ts";
2929import * as RelayDeliveryProof from "./RelayDeliveryProof.ts";
30import { WEBHOOK_MAX_BODY_BYTES, webhookHttpApiLayer } from "./webhookRoute.ts";
30import { WEBHOOK_MAX_BODY_BYTES } from "./webhookRoute.ts";
31import * as WebhookRoute from "./webhookRoute.ts";
3132
3233class WebhookTestApi extends HttpApi.make("environment").add(EnvironmentHttpApi.groups.webhooks) {}
3334
......@@ -77,7 +78,7 @@ const handlerFor = (
7778) =>
7879 HttpRouter.toWebHandler(
7980 HttpApiBuilder.layer(WebhookTestApi).pipe(
80 Layer.provide(webhookHttpApiLayer),
81 Layer.provide(WebhookRoute.layer),
8182 Layer.provide(RelayDeliveryProof.layer),
8283 Layer.provide(Layer.mock(ScheduledTaskService)({ triggerWebhook: trigger })),
8384 Layer.provide(
apps/server/src/scheduledTasks/webhookRoute.ts+1-1
......@@ -23,7 +23,7 @@ const WEBHOOK_OUTCOME_HEADER = "x-t3-hook-outcome";
2323const json = (status: number, body: Record<string, string>, outcome: string) =>
2424 HttpServerResponse.jsonUnsafe(body, { status, headers: { [WEBHOOK_OUTCOME_HEADER]: outcome } });
2525
26export const webhookHttpApiLayer = HttpApiBuilder.group(
26export const layer = HttpApiBuilder.group(
2727 EnvironmentHttpApi,
2828 "webhooks",
2929 Effect.fnUntraced(function* (handlers) {
apps/server/src/scheduling/Scheduler.integration.test.ts+7-7
......@@ -22,7 +22,7 @@ import * as ProjectionStore from "../orchestration-v2/ProjectionStore.ts";
2222import * as ThreadLaunchService from "../orchestration-v2/ThreadLaunchService.ts";
2323import * as ThreadManagementService from "../orchestration-v2/ThreadManagementService.ts";
2424import * as UsageLimitRecoveryWorker from "../orchestration-v2/UsageLimitRecoveryWorker.ts";
25import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts";
25import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts";
2626import * as ScheduledTasks from "../scheduledTasks/ScheduledTaskService.ts";
2727import * as SecretRequests from "../secrets/SecretRequests.ts";
2828import * as ServerSettings from "../serverSettings.ts";
......@@ -86,7 +86,7 @@ it.effect.each(["on time", "after restart"])(
8686 const current = yield* Ref.make(thread);
8787 const commands = yield* Ref.make<ReadonlyArray<OrchestrationV2ServerCommand>>([]);
8888 const receipts = yield* Queue.unbounded<"task" | "retry">();
89 const dependencies = Layer.mergeAll(
89 const layerDependencies = Layer.mergeAll(
9090 NodeCrypto.layer,
9191 Layer.mock(ThreadLaunchService.ThreadLaunchService)({
9292 launch: () =>
......@@ -115,10 +115,10 @@ it.effect.each(["on time", "after restart"])(
115115 }),
116116 Layer.mock(SecretRequests.SecretRequests)({}),
117117 );
118 const workers = Layer.mergeAll(
118 const layerWorkers = Layer.mergeAll(
119119 ScheduledTasks.layer,
120 UsageLimitRecoveryWorker.workerLive,
121 ).pipe(Layer.provide(dependencies), Layer.provide(Scheduler.layer));
120 UsageLimitRecoveryWorker.layer,
121 ).pipe(Layer.provide(layerDependencies), Layer.provide(Scheduler.layer));
122122 yield* Effect.gen(function* () {
123123 const tasks = yield* ScheduledTasks.ScheduledTaskService;
124124 const { task } = yield* tasks.upsert({
......@@ -151,6 +151,6 @@ it.effect.each(["on time", "after restart"])(
151151 usageLimitRecoveryRequestId: thread.limitRecovery!.requestId,
152152 text: "Continue where you left off.",
153153 });
154 }).pipe(Effect.provide(workers));
155 }).pipe(Effect.provide(SqlitePersistenceMemory)),
154 }).pipe(Effect.provide(layerWorkers));
155 }).pipe(Effect.provide(SqlitePersistence.layerMemory)),
156156);
apps/server/src/secrets/SecretRequests.test.ts+2-2
......@@ -48,7 +48,7 @@ const withService = <A, E>(
4848 let secretStatus = "pending";
4949 let failedRecords = options.failedRecords ?? 0;
5050 const requestThreadId = options.threadId ?? threadId;
51 const dependencies = Layer.mergeAll(
51 const layerDependencies = Layer.mergeAll(
5252 NodeCrypto.layer,
5353 NodeServices.layer,
5454 Layer.succeed(
......@@ -130,7 +130,7 @@ const withService = <A, E>(
130130 return yield* Effect.gen(function* () {
131131 const service = yield* SecretRequests.SecretRequests;
132132 return yield* body({ service, stored, dispatched });
133 }).pipe(Effect.provide(SecretRequests.layer.pipe(Layer.provide(dependencies))));
133 }).pipe(Effect.provide(SecretRequests.layer.pipe(Layer.provide(layerDependencies))));
134134 });
135135
136136/** The secret values in the store, leaving out the server's own salt. */
apps/server/src/server.ts+194-217
......@@ -25,22 +25,14 @@ import * as HttpApiBuilder from "effect/http-api/HttpApiBuilder";
2525import * as BackgroundPolicy from "./background/BackgroundPolicy.ts";
2626import * as HostPowerMonitor from "./background/HostPowerMonitor.ts";
2727import * as ServerConfig from "./config.ts";
28import {
29 otlpTracesProxyRouteLayer,
30 assetRouteLayer,
31 attachmentUploadRouteLayer,
32 serverEnvironmentHttpApiLayer,
33 staticAndDevRouteLayer,
34 browserApiCorsLayer,
35 httpCompressionLayer,
36 withUntracedRequests,
37} from "./http.ts";
28import { withUntracedRequests } from "./http.ts";
29import * as ServerHttp from "./http.ts";
3830import { guardHttpResponseWriteErrors } from "./httpResponseErrorGuard.ts";
3931import { fixPath } from "./os-jank.ts";
40import { websocketRpcRouteLayer } from "./ws.ts";
32import * as Ws from "./ws.ts";
4133import * as ExternalLauncher from "./process/externalLauncher.ts";
4234import * as NodePtyAdapter from "./terminal/NodePtyAdapter.ts";
43import { pullRequestHttpApiLayer } from "./pullRequest/http.ts";
35import * as PullRequestHttp from "./pullRequest/http.ts";
4436import * as PullRequestProviderRegistry from "./pullRequest/PullRequestProviderRegistry.ts";
4537import * as PullRequestService from "./pullRequest/PullRequestService.ts";
4638import * as SqlitePersistence from "./persistence/Layers/Sqlite.ts";
......@@ -53,7 +45,7 @@ import * as ResetCreditCoordinator from "./provider/Layers/resetCreditCoordinato
5345import * as ProviderEventLoggers from "./provider/Layers/ProviderEventLoggers.ts";
5446import * as OpenCodeRuntime from "./provider/opencodeRuntime.ts";
5547import * as OpenCodeServerLedger from "./provider/OpenCodeServerLedger.ts";
56import { AcpRegistryCatalogLive } from "./provider/Layers/AcpRegistryCatalog.ts";
48import * as AcpRegistryCatalog from "./provider/Layers/AcpRegistryCatalog.ts";
5749import * as CheckpointDiffQuery from "./checkpointing/CheckpointDiffQuery.ts";
5850import * as CheckpointStore from "./checkpointing/CheckpointStore.ts";
5951import * as AzureDevOpsCli from "./sourceControl/AzureDevOpsCli.ts";
......@@ -62,13 +54,13 @@ import * as GitHubCli from "./sourceControl/GitHubCli.ts";
6254import * as GitLabCli from "./sourceControl/GitLabCli.ts";
6355import * as ForgejoCli from "./sourceControl/ForgejoCli.ts";
6456import * as TextGeneration from "./textGeneration/TextGeneration.ts";
65import { ProviderInstanceRegistryHydrationLive } from "./provider/Layers/ProviderInstanceRegistryHydration.ts";
57import * as ProviderInstanceRegistryHydration from "./provider/Layers/ProviderInstanceRegistryHydration.ts";
6658import * as TerminalManager from "./terminal/Manager.ts";
6759import * as McpHttpServer from "./mcp/McpHttpServer.ts";
6860import * as McpSessionRegistry from "./mcp/McpSessionRegistry.ts";
6961import * as PreviewAutomationBroker from "./mcp/PreviewAutomationBroker.ts";
7062import * as DeviceService from "./device/DeviceService.ts";
71import { deviceHubProxyRouteLayer } from "./device/DeviceHubProxy.ts";
63import * as DeviceHubProxy from "./device/DeviceHubProxy.ts";
7264import * as PreviewManager from "./preview/Manager.ts";
7365import * as PortScanner from "./preview/PortScanner.ts";
7466import * as ProcessRunner from "./processRunner.ts";
......@@ -78,7 +70,7 @@ import * as Keybindings from "./keybindings.ts";
7870import * as ServerRuntimeStartup from "./serverRuntimeStartup.ts";
7971import * as AgentAwarenessRelay from "./relay/AgentAwarenessRelay.ts";
8072import { hasCloudPublicConfig } from "./cloud/publicConfig.ts";
81import { ProviderRegistryLive } from "./provider/Layers/ProviderRegistry.ts";
73import * as ProviderRegistryLayer from "./provider/Layers/ProviderRegistry.ts";
8274import * as ServerSettings from "./serverSettings.ts";
8375import * as ProjectEnrichmentService from "./project/ProjectEnrichmentService.ts";
8476import * as NativeAppIconResolver from "./assets/NativeAppIconResolver.ts";
......@@ -87,7 +79,7 @@ import * as CodexInstallation from "./provider/CodexInstallation.ts";
8779import * as ProviderInstanceRegistry from "./provider/Services/ProviderInstanceRegistry.ts";
8880import * as ProviderAdapterRegistry from "./orchestration-v2/ProviderAdapterRegistry.ts";
8981import * as ProviderRegistry from "./provider/Services/ProviderRegistry.ts";
90import { ProviderUsageLimitsIngestionLive } from "./provider/Layers/ProviderUsageLimitsIngestion.ts";
82import * as ProviderUsageLimitsIngestion from "./provider/Layers/ProviderUsageLimitsIngestion.ts";
9183import * as UsageLimitSources from "./usage/UsageLimitSources.ts";
9284import * as ProjectFaviconResolver from "./project/ProjectFaviconResolver.ts";
9385import * as T3ProjectFileLoader from "./project/T3ProjectFileLoader.ts";
......@@ -109,16 +101,16 @@ import * as PullRequestReadCache from "./pullRequest/PullRequestReadCache.ts";
109101import * as SourceControlRateLimit from "./sourceControl/SourceControlRateLimit.ts";
110102import * as SourceControlRepositoryService from "./sourceControl/SourceControlRepositoryService.ts";
111103import * as WorktreeSetupTracker from "./project/WorktreeSetupTracker.ts";
112import { ObservabilityLive } from "./observability/Layers/Observability.ts";
104import * as Observability from "./observability/Layers/Observability.ts";
113105import * as HeapSnapshot from "./observability/HeapSnapshot.ts";
114106import * as EventLoopMonitor from "./observability/EventLoopMonitor.ts";
115107import * as ServerEnvironment from "./environment/ServerEnvironment.ts";
116108import * as DirectEndpoints from "./environment/DirectEndpoints.ts";
117109import * as RemoteOpenTargets from "./environment/RemoteOpenTargets.ts";
118import { authHttpApiLayer, environmentAuthenticatedAuthLayer } from "./auth/http.ts";
110import * as AuthHttp from "./auth/http.ts";
119111import * as ReplayMarkers from "./auth/replayMarkers.ts";
120112import * as ServerSecretStore from "./auth/ServerSecretStore.ts";
121import { webhookHttpApiLayer } from "./scheduledTasks/webhookRoute.ts";
113import * as WebhookRoute from "./scheduledTasks/webhookRoute.ts";
122114import * as RelayDeliveryProof from "./scheduledTasks/RelayDeliveryProof.ts";
123115import * as CloudPreferences from "./cloud/CloudPreferences.ts";
124116import * as HeldHooksWaker from "./relay/HeldHooksWaker.ts";
......@@ -133,7 +125,6 @@ import {
133125} from "./cloud/config.ts";
134126import * as EnvironmentAuth from "./auth/EnvironmentAuth.ts";
135127import {
136 connectHttpApiLayer,
137128 pendingServiceUpdateExists,
138129 reconcileDesiredCloudLinkIfStillDesired,
139130 recoverManagedCloudTunnel,
......@@ -141,7 +132,8 @@ import {
141132 startManagedCloudTunnelIfOriginConfirmed,
142133 releaseManagedTunnelOnShutdown,
143134} from "./cloud/http.ts";
144import { serverRelayBrokerTracingLayer } from "./cloud/relayTracing.ts";
135import * as CloudHttp from "./cloud/http.ts";
136import * as RelayTracing from "./cloud/relayTracing.ts";
145137import { shouldRetryCloudLink } from "./cloud/relayResponse.ts";
146138import * as CloudManagedEndpointRuntime from "./cloud/ManagedEndpointRuntime.ts";
147139import {
......@@ -165,12 +157,7 @@ import * as ResourceAttribution from "./resourceTelemetry/ResourceAttribution.ts
165157import * as ResourceMonitorBinary from "./resourceTelemetry/ResourceMonitorBinary.ts";
166158import * as ResourceTelemetry from "./resourceTelemetry/ResourceTelemetry.ts";
167159import * as UsageService from "./usage/UsageService.ts";
168import {
169 OrchestrationEventInfrastructureLayerLive,
170 OrchestrationV2ProductionLayerLive,
171 ProjectServiceLayerLive,
172 ProjectSetupScriptRunnerLayerLive,
173} from "./orchestration-v2/runtimeLayer.ts";
160import * as RuntimeLayer from "./orchestration-v2/runtimeLayer.ts";
174161import * as ProjectStore from "./orchestration-v2/ProjectStore.ts";
175162import * as ThreadSearch from "./orchestration-v2/ThreadSearch.ts";
176163import * as ResourceCleanupService from "./orchestration-v2/ResourceCleanupService.ts";
......@@ -183,8 +170,8 @@ import {
183170 makePersistedServerRuntimeState,
184171 persistServerRuntimeState,
185172} from "./serverRuntimeState.ts";
186import { orchestrationHttpApiLayer } from "./orchestration-v2/http.ts";
187import { projectHttpApiLayer } from "./project/http.ts";
173import * as OrchestrationHttp from "./orchestration-v2/http.ts";
174import * as ProjectHttp from "./project/http.ts";
188175import * as NetService from "@t3tools/shared/Net";
189176import * as RelayClient from "@t3tools/shared/relayClient";
190177import { disableTailscaleServe, ensureTailscaleServe } from "@t3tools/tailscale";
......@@ -201,63 +188,63 @@ const HTTP_ROUTER_CONFIG = {
201188// already closes the websocket gracefully. Do not add an artificial drain before
202189// those finalizers get a chance to run.
203190const HTTP_PREEMPTIVE_SHUTDOWN_GRACE_MS = 0;
204const ResourceAttributionLayerLive = ResourceAttribution.layer;
205const ApplicationObservabilityLive = EventLoopMonitor.layer.pipe(
206 Layer.provideMerge(ObservabilityLive),
207 Layer.provideMerge(ResourceAttributionLayerLive),
191const layerResourceAttribution = ResourceAttribution.layer;
192const layerApplicationObservability = EventLoopMonitor.layer.pipe(
193 Layer.provideMerge(Observability.layer),
194 Layer.provideMerge(layerResourceAttribution),
208195);
209196
210const PtyAdapterLive = NodePtyAdapter.layer;
197const layerPtyAdapter = NodePtyAdapter.layer;
211198
212const ServerSettingsLayerLive = ServerSettings.layer.pipe(
199const layerServerSettings = ServerSettings.layer.pipe(
213200 Layer.provide(ServerSecretStore.layer),
214201 Layer.provideMerge(SqlitePersistence.layerConfig),
215202);
216203
217const NativeTelemetryLayerLive = NativeTelemetryClient.layer.pipe(
204const layerNativeTelemetry = NativeTelemetryClient.layer.pipe(
218205 Layer.provide(ResourceMonitorBinary.layer),
219206);
220const DesktopTelemetryReceiverLayerLive = DesktopTelemetryReceiver.layer.pipe(
221 Layer.provideMerge(ServerSettingsLayerLive),
207const layerDesktopTelemetryReceiver = DesktopTelemetryReceiver.layer.pipe(
208 Layer.provideMerge(layerServerSettings),
222209);
223210
224const ResourceTelemetryLayerLive = ResourceTelemetry.layer.pipe(
225 Layer.provideMerge(NativeTelemetryLayerLive),
226 Layer.provideMerge(DesktopTelemetryReceiverLayerLive),
211const layerResourceTelemetry = ResourceTelemetry.layer.pipe(
212 Layer.provideMerge(layerNativeTelemetry),
213 Layer.provideMerge(layerDesktopTelemetryReceiver),
227214);
228215
229const HostPowerMonitorLayerLive = HostPowerMonitor.layer.pipe(
230 Layer.provide(DesktopTelemetryReceiverLayerLive),
216const layerHostPowerMonitor = HostPowerMonitor.layer.pipe(
217 Layer.provide(layerDesktopTelemetryReceiver),
231218);
232219
233220// Reuses DesktopTelemetryReceiverLayerLive: a fresh receiver layer here
234221// would open a second reader on the desktop telemetry fd.
235const DesktopAppUpdateLayerLive = DesktopAppUpdate.layer.pipe(
236 Layer.provide(DesktopTelemetryReceiverLayerLive),
222const layerDesktopAppUpdate = DesktopAppUpdate.layer.pipe(
223 Layer.provide(layerDesktopTelemetryReceiver),
237224);
238225
239const BackgroundLayerLive = BackgroundPolicy.layer.pipe(
240 Layer.provide(HostPowerMonitorLayerLive),
241 Layer.provideMerge(ServerSettingsLayerLive),
226const layerBackground = BackgroundPolicy.layer.pipe(
227 Layer.provide(layerHostPowerMonitor),
228 Layer.provideMerge(layerServerSettings),
242229);
243230
244const UsageLayerLive = UsageService.layer.pipe(Layer.provide(ServerSettingsLayerLive));
231const layerUsage = UsageService.layer.pipe(Layer.provide(layerServerSettings));
245232
246const ResourceDiagnosticsLayerLive = Layer.mergeAll(
233const layerResourceDiagnostics = Layer.mergeAll(
247234 HostResources.layer,
248 ResourceTelemetryLayerLive,
249 ProcessDiagnostics.layer.pipe(Layer.provide(ResourceTelemetryLayerLive)),
250 ProcessResourceMonitor.layer.pipe(Layer.provide(ResourceTelemetryLayerLive)),
235 layerResourceTelemetry,
236 ProcessDiagnostics.layer.pipe(Layer.provide(layerResourceTelemetry)),
237 ProcessResourceMonitor.layer.pipe(Layer.provide(layerResourceTelemetry)),
251238);
252239
253const RelayClientLive = Layer.unwrap(
240const layerRelayClient = Layer.unwrap(
254241 Effect.gen(function* () {
255242 const config = yield* ServerConfig.ServerConfig;
256243 return RelayClient.layerCloudflared({ baseDir: config.baseDir });
257244 }),
258245);
259246
260const HttpServerLive = Layer.unwrap(
247const layerHttpServer = Layer.unwrap(
261248 Effect.gen(function* () {
262249 const config = yield* ServerConfig.ServerConfig;
263250 return NodeHttpServer.layer(() => guardHttpResponseWriteErrors(NodeHttp.createServer()), {
......@@ -275,15 +262,13 @@ const HttpServerLive = Layer.unwrap(
275262 }),
276263);
277264
278const PlatformServicesLive = NodeServices.layer;
265const layerPlatformServices = NodeServices.layer;
279266
280const PersistenceLayerLive = Layer.empty.pipe(Layer.provideMerge(SqlitePersistence.layerConfig));
267const layerPersistence = Layer.empty.pipe(Layer.provideMerge(SqlitePersistence.layerConfig));
281268
282const VcsDriverRegistryLayerLive = VcsDriverRegistry.layer.pipe(
283 Layer.provide(VcsProjectConfig.layer),
284);
269const layerVcsDriverRegistry = VcsDriverRegistry.layer.pipe(Layer.provide(VcsProjectConfig.layer));
285270
286const SourceControlProviderRegistryLayerLive = SourceControlProviderRegistry.layer.pipe(
271const layerSourceControlProviderRegistry = SourceControlProviderRegistry.layer.pipe(
287272 Layer.provide(
288273 Layer.mergeAll(
289274 AzureDevOpsCli.layer,
......@@ -294,10 +279,10 @@ const SourceControlProviderRegistryLayerLive = SourceControlProviderRegistry.lay
294279 ),
295280 ),
296281 Layer.provideMerge(GitVcsDriver.layer),
297 Layer.provideMerge(VcsDriverRegistryLayerLive),
282 Layer.provideMerge(layerVcsDriverRegistry),
298283);
299284
300const RepositoryIdentityResolverLayerLive = Layer.effect(
285const layerRepositoryIdentityResolver = Layer.effect(
301286 RepositoryIdentityResolver.RepositoryIdentityResolver,
302287 Effect.gen(function* () {
303288 const registry = yield* SourceControlProviderRegistry.SourceControlProviderRegistry;
......@@ -331,136 +316,130 @@ const RepositoryIdentityResolverLayerLive = Layer.effect(
331316 }),
332317 });
333318 }),
334).pipe(Layer.provide(SourceControlProviderRegistryLayerLive), Layer.provide(ProcessRunner.layer));
319).pipe(Layer.provide(layerSourceControlProviderRegistry), Layer.provide(ProcessRunner.layer));
335320
336const PullRequestServiceLive = PullRequestService.layer.pipe(
321const layerPullRequestService = PullRequestService.layer.pipe(
337322 Layer.provide(PullRequestProviderRegistry.layer),
338323 // Where the viewed-file marks live for a host that keeps none of its own.
339324 Layer.provide(PullRequestFilesViewed.layer),
340325 Layer.provide(PullRequestReadCache.layer),
341 Layer.provide(SourceControlProviderRegistryLayerLive),
326 Layer.provide(layerSourceControlProviderRegistry),
342327 Layer.provide(SourceControlRateLimit.layer),
343328);
344329
345const GitManagerLayerLive = GitManager.layer.pipe(
330const layerGitManager = GitManager.layer.pipe(
346331 // Per-project git settings resolve the acting thread's project.
347332 Layer.provide(Layer.merge(ProjectionStoreV2.layer, ProjectStore.layer)),
348 Layer.provideMerge(ProjectSetupScriptRunnerLayerLive),
333 Layer.provideMerge(RuntimeLayer.layerProjectSetupScriptRunner),
349334 Layer.provideMerge(WorktreeSetupTracker.layer),
350335 Layer.provideMerge(GitVcsDriver.layer),
351 Layer.provideMerge(SourceControlProviderRegistryLayerLive),
352 Layer.provideMerge(
353 TextGeneration.layer.pipe(Layer.provide(SourceControlProviderRegistryLayerLive)),
354 ),
336 Layer.provideMerge(layerSourceControlProviderRegistry),
337 Layer.provideMerge(TextGeneration.layer.pipe(Layer.provide(layerSourceControlProviderRegistry))),
355338);
356339
357const GitLayerLive = Layer.empty.pipe(
358 Layer.provideMerge(GitManagerLayerLive),
340const layerGit = Layer.empty.pipe(
341 Layer.provideMerge(layerGitManager),
359342 Layer.provideMerge(GitVcsDriver.layer),
360343);
361344
362const GitWorkflowLayerLive = GitWorkflowService.layer.pipe(
363 Layer.provideMerge(VcsDriverRegistryLayerLive),
364 Layer.provideMerge(GitLayerLive),
345const layerGitWorkflow = GitWorkflowService.layer.pipe(
346 Layer.provideMerge(layerVcsDriverRegistry),
347 Layer.provideMerge(layerGit),
365348);
366349
367const SourceControlRepositoryServiceLayerLive = SourceControlRepositoryService.layer.pipe(
350const layerSourceControlRepositoryService = SourceControlRepositoryService.layer.pipe(
368351 Layer.provideMerge(GitVcsDriver.layer),
369 Layer.provideMerge(SourceControlProviderRegistryLayerLive),
352 Layer.provideMerge(layerSourceControlProviderRegistry),
370353);
371354
372const ProjectCloneTrackerLayerLive = ProjectCloneTracker.layer.pipe(
373 Layer.provide(SourceControlRepositoryServiceLayerLive),
355const layerProjectCloneTracker = ProjectCloneTracker.layer.pipe(
356 Layer.provide(layerSourceControlRepositoryService),
374357);
375358
376const ReviewLayerLive = ReviewService.layer.pipe(
359const layerReview = ReviewService.layer.pipe(
377360 Layer.provideMerge(GitVcsDriver.layer),
378 Layer.provideMerge(VcsDriverRegistryLayerLive),
361 Layer.provideMerge(layerVcsDriverRegistry),
379362);
380363
381const VcsLayerLive = Layer.empty.pipe(
364const layerVcs = Layer.empty.pipe(
382365 Layer.provideMerge(VcsProjectConfig.layer),
383 Layer.provideMerge(VcsDriverRegistryLayerLive),
384 Layer.provideMerge(VcsProvisioningService.layer.pipe(Layer.provide(VcsDriverRegistryLayerLive))),
385 Layer.provideMerge(GitWorkflowLayerLive),
386 Layer.provideMerge(ReviewLayerLive),
387 Layer.provideMerge(SourceControlRepositoryServiceLayerLive),
388 Layer.provideMerge(ProjectCloneTrackerLayerLive),
366 Layer.provideMerge(layerVcsDriverRegistry),
367 Layer.provideMerge(VcsProvisioningService.layer.pipe(Layer.provide(layerVcsDriverRegistry))),
368 Layer.provideMerge(layerGitWorkflow),
369 Layer.provideMerge(layerReview),
370 Layer.provideMerge(layerSourceControlRepositoryService),
371 Layer.provideMerge(layerProjectCloneTracker),
389372 Layer.provideMerge(
390373 VcsStatusBroadcaster.layer.pipe(
391 Layer.provide(GitWorkflowLayerLive),
374 Layer.provide(layerGitWorkflow),
392375 // Auto-pull reads the project row. The orchestration runtime also
393376 // consumes the broadcaster (run finalization), so the policy cannot read
394377 // the store from the runtime's output.
395378 Layer.provide(
396 VcsStatusBroadcaster.autoPullPolicyLayer.pipe(Layer.provide(ProjectStore.layer)),
379 VcsStatusBroadcaster.layerAutoPullPolicy.pipe(Layer.provide(ProjectStore.layer)),
397380 ),
398381 ),
399382 ),
400383);
401384
402const CheckpointStoreLayerLive = CheckpointStore.layer.pipe(
403 Layer.provide(VcsDriverRegistryLayerLive),
404);
385const layerCheckpointStore = CheckpointStore.layer.pipe(Layer.provide(layerVcsDriverRegistry));
405386
406const PortScannerLayerLive = PortScanner.layer.pipe(Layer.provide(ProcessRunner.layer));
387const layerPortScanner = PortScanner.layer.pipe(Layer.provide(ProcessRunner.layer));
407388
408const TerminalLayerLive = TerminalManager.layer.pipe(
409 Layer.provide(PtyAdapterLive),
410 Layer.provide(PortScannerLayerLive),
411 Layer.provide(NativeTelemetryLayerLive),
389const layerTerminal = TerminalManager.layer.pipe(
390 Layer.provide(layerPtyAdapter),
391 Layer.provide(layerPortScanner),
392 Layer.provide(layerNativeTelemetry),
412393);
413394
414const PreviewLayerLive = Layer.empty.pipe(
395const layerPreview = Layer.empty.pipe(
415396 Layer.provideMerge(PreviewManager.layer),
416 Layer.provideMerge(PortScannerLayerLive),
397 Layer.provideMerge(layerPortScanner),
417398);
418399
419const DeviceLayerLive = DeviceService.layer.pipe(
420 Layer.provide(ServerSettingsLayerLive),
400const layerDevice = DeviceService.layer.pipe(
401 Layer.provide(layerServerSettings),
421402 Layer.provide(ProcessRunner.layer),
422403 Layer.provide(NetService.layer),
423404);
424405
425const WorkspaceEntriesLayerLive = WorkspaceEntries.layer.pipe(Layer.provide(WorkspacePaths.layer));
406const layerWorkspaceEntries = WorkspaceEntries.layer.pipe(Layer.provide(WorkspacePaths.layer));
426407
427const WorkspaceFileSystemLayerLive = WorkspaceFileSystem.layer.pipe(
408const layerWorkspaceFileSystem = WorkspaceFileSystem.layer.pipe(
428409 Layer.provide(WorkspacePaths.layer),
429 Layer.provide(WorkspaceEntriesLayerLive),
410 Layer.provide(layerWorkspaceEntries),
430411);
431412
432const WorkspaceLayerLive = Layer.mergeAll(
413const layerWorkspace = Layer.mergeAll(
433414 WorkspacePaths.layer,
434 WorkspaceEntriesLayerLive,
435 WorkspaceFileSystemLayerLive,
415 layerWorkspaceEntries,
416 layerWorkspaceFileSystem,
436417);
437418
438const ProjectFaviconResolverLayerLive = ProjectFaviconResolver.layer.pipe(
419const layerProjectFaviconResolver = ProjectFaviconResolver.layer.pipe(
439420 Layer.provide(WorkspacePaths.layer),
440421 Layer.provide(T3ProjectFileLoader.layer),
441422);
442423
443const ServerEnvironmentLayerLive = ServerEnvironment.layer.pipe(
444 Layer.provide(ServerSecretStore.layer),
445);
424const layerServerEnvironment = ServerEnvironment.layer.pipe(Layer.provide(ServerSecretStore.layer));
446425
447const AuthLayerLive = EnvironmentAuth.layer.pipe(
448 Layer.provideMerge(PersistenceLayerLive),
449 Layer.provide(ServerEnvironmentLayerLive),
426const layerAuth = EnvironmentAuth.layer.pipe(
427 Layer.provideMerge(layerPersistence),
428 Layer.provide(layerServerEnvironment),
450429 Layer.provide(ServerSecretStore.layer),
451430);
452431
453const CloudManagedEndpointRuntimeLive = Layer.mergeAll(
454 RelayClientLive,
432const layerCloudManagedEndpointRuntime = Layer.mergeAll(
433 layerRelayClient,
455434 CloudManagedEndpointRuntime.layer.pipe(
456435 Layer.provide(ServerSecretStore.layer),
457 Layer.provide(RelayClientLive),
436 Layer.provide(layerRelayClient),
458437 ),
459438);
460439
461440// Webhook URLs go through the relay only when the managed tunnel it forwards
462441// to is configured; otherwise clients show the environment-relative path.
463const ScheduledTaskWebhookOriginLive = Layer.effect(
442const layerScheduledTaskWebhookOrigin = Layer.effect(
464443 ScheduledTaskWebhookOrigin,
465444 Effect.gen(function* () {
466445 const secrets = yield* ServerSecretStore.ServerSecretStore;
......@@ -484,38 +463,38 @@ const ScheduledTaskWebhookOriginLive = Layer.effect(
484463 }),
485464);
486465
487const OrchestrationV2RuntimeLayerLive = OrchestrationV2ProductionLayerLive.pipe(
488 Layer.provide(ScheduledTaskWebhookOriginLive),
489 Layer.provide(ProviderEventIngestor.analyticsLive),
490 Layer.provide(CheckpointStoreLayerLive),
491 Layer.provide(GitWorkflowLayerLive),
492 Layer.provide(ResourceCleanupService.live),
466const layerOrchestrationV2Runtime = RuntimeLayer.layerProduction.pipe(
467 Layer.provide(layerScheduledTaskWebhookOrigin),
468 Layer.provide(ProviderEventIngestor.layerAnalytics),
469 Layer.provide(layerCheckpointStore),
470 Layer.provide(layerGitWorkflow),
471 Layer.provide(ResourceCleanupService.layer),
493472 Layer.provide(
494 RunFinalizationService.observerLive.pipe(
473 RunFinalizationService.layerObserver.pipe(
495474 Layer.provide(ProjectionStoreV2.layer),
496 Layer.provide(PullRequestServiceLive),
497 Layer.provide(ProjectServiceLayerLive),
475 Layer.provide(layerPullRequestService),
476 Layer.provide(RuntimeLayer.layerProjectService),
498477 ),
499478 ),
500479);
501480
502const OrchestrationApplicationLayerLive = CheckpointDiffQuery.layer.pipe(
503 Layer.provideMerge(CheckpointStoreLayerLive),
504 Layer.provideMerge(OrchestrationV2RuntimeLayerLive),
481const layerOrchestrationApplication = CheckpointDiffQuery.layer.pipe(
482 Layer.provideMerge(layerCheckpointStore),
483 Layer.provideMerge(layerOrchestrationV2Runtime),
505484);
506485
507486// Automatic thread settlement (#8600): a server-owned sweep evaluates
508487// inactivity and merged pull requests, then settles through the orchestrator
509488// so every client sees the same shelf.
510const ThreadSettlementWorkerLive = Layer.effectDiscard(
489const layerThreadSettlementWorker = Layer.effectDiscard(
511490 ThreadSettlementService.make.pipe(Effect.flatMap((service) => service.start())),
512).pipe(Layer.provide(PullRequestServiceLive), Layer.provide(ProjectionStoreV2.layer));
491).pipe(Layer.provide(layerPullRequestService), Layer.provide(ProjectionStoreV2.layer));
513492
514const ThreadPullRequestWorkerLive = Layer.effectDiscard(
493const layerThreadPullRequestWorker = Layer.effectDiscard(
515494 ThreadPullRequestService.make.pipe(Effect.flatMap((service) => service.start())),
516).pipe(Layer.provide(PullRequestServiceLive));
495).pipe(Layer.provide(layerPullRequestService));
517496
518const ProviderInstallationRefreshLive = Layer.effectDiscard(
497const layerProviderInstallationRefresh = Layer.effectDiscard(
519498 Effect.gen(function* () {
520499 const antigravity = yield* AntigravityInstallation.AntigravityInstallation;
521500 const codex = yield* CodexInstallation.CodexInstallation;
......@@ -547,15 +526,15 @@ const ProviderInstallationRefreshLive = Layer.effectDiscard(
547526 }),
548527);
549528
550const RuntimeCoreDependenciesBaseLive = Layer.mergeAll(
529const layerRuntimeCoreDependenciesBase = Layer.mergeAll(
551530 AgentAwarenessRelay.layer,
552531 // Asks T3 Connect to deliver webhooks it held while this environment was offline.
553532 HeldHooksWaker.layer,
554 ThreadSettlementWorkerLive,
533 layerThreadSettlementWorker,
555534 Layer.effectDiscard(StorageCleanup.make.pipe(Effect.flatMap((service) => service.start()))).pipe(
556535 Layer.provide(ProjectionStoreV2.layer),
557536 ),
558 ThreadPullRequestWorkerLive,
537 layerThreadPullRequestWorker,
559538 Layer.effectDiscard(
560539 Effect.gen(function* () {
561540 const service = yield* PullRequestSyncReactor.PullRequestSyncReactor;
......@@ -563,7 +542,7 @@ const RuntimeCoreDependenciesBaseLive = Layer.mergeAll(
563542 }),
564543 ).pipe(
565544 Layer.provideMerge(PullRequestSyncReactor.layer),
566 Layer.provide(PullRequestServiceLive),
545 Layer.provide(layerPullRequestService),
567546 Layer.provide(ProjectionStoreV2.layer),
568547 ),
569548 Layer.effectDiscard(
......@@ -573,38 +552,38 @@ const RuntimeCoreDependenciesBaseLive = Layer.mergeAll(
573552 }),
574553 ).pipe(
575554 Layer.provide(PullRequestWatchReactor.layer),
576 Layer.provide(PullRequestServiceLive),
555 Layer.provide(layerPullRequestService),
577556 Layer.provide(ProjectionStoreV2.layer),
578557 ),
579558 // Subscribes to `account.rate-limits.updated` so usage bars track live
580559 // telemetry instead of waiting for the next status probe.
581 ProviderUsageLimitsIngestionLive,
582 ProviderInstallationRefreshLive,
560 ProviderUsageLimitsIngestion.layer,
561 layerProviderInstallationRefresh,
583562 ReplayMarkers.layer,
584563).pipe(
585564 // Core Services
586 Layer.provideMerge(OrchestrationApplicationLayerLive),
587 Layer.provideMerge(OrchestrationEventInfrastructureLayerLive),
565 Layer.provideMerge(layerOrchestrationApplication),
566 Layer.provideMerge(RuntimeLayer.layerEventInfrastructure),
588567 Layer.provideMerge(Layer.merge(ProjectStore.layer, ThreadSearch.layer)),
589 Layer.provideMerge(ServerSettingsLayerLive),
568 Layer.provideMerge(layerServerSettings),
590569 // The asset route uses the registry's GitHub credential for private PR media.
591 Layer.provideMerge(Layer.mergeAll(SourceControlProviderRegistryLayerLive, GitHubCli.layer)),
592 Layer.provideMerge(GitLayerLive),
593 Layer.provideMerge(VcsLayerLive),
594 Layer.provideMerge(Layer.mergeAll(TerminalLayerLive, PreviewLayerLive, DeviceLayerLive)),
595 Layer.provideMerge(PersistenceLayerLive),
570 Layer.provideMerge(Layer.mergeAll(layerSourceControlProviderRegistry, GitHubCli.layer)),
571 Layer.provideMerge(layerGit),
572 Layer.provideMerge(layerVcs),
573 Layer.provideMerge(Layer.mergeAll(layerTerminal, layerPreview, layerDevice)),
574 Layer.provideMerge(layerPersistence),
596575 // Both read a user-owned file out of the state directory and stream changes
597576 // to clients; neither depends on the other.
598577 Layer.provideMerge(
599578 Layer.mergeAll(Keybindings.layer, EnvironmentTheme.layer, UsageLimitSources.layer),
600579 ),
601 Layer.provideMerge(ProviderRegistryLive),
580 Layer.provideMerge(ProviderRegistryLayer.layer),
602581 // The instance registry is the new routing keystone — text generation,
603582 // adapter lookup, and runtime ingestion all resolve `ProviderInstanceId`
604583 // through this layer. Built-in drivers come from `BUILT_IN_DRIVERS`;
605584 // `providerInstances` hydration merges `settings.providers.<kind>`
606585 // with explicit `providerInstances` entries on boot.
607 Layer.provideMerge(ProviderInstanceRegistryHydrationLive),
586 Layer.provideMerge(ProviderInstanceRegistryHydration.layer),
608587 Layer.provideMerge(
609588 Layer.mergeAll(
610589 AntigravityInstallation.AntigravityInstallation.layer,
......@@ -613,11 +592,11 @@ const RuntimeCoreDependenciesBaseLive = Layer.mergeAll(
613592 ),
614593);
615594
616const RuntimeCoreDependenciesLive = RuntimeCoreDependenciesBaseLive.pipe(
617 Layer.provideMerge(PtyAdapterLive),
595const layerRuntimeCoreDependencies = layerRuntimeCoreDependenciesBase.pipe(
596 Layer.provideMerge(layerPtyAdapter),
618597 // Search, prepare, status inspection, and turn launch share one registry
619598 // cache so every client and provider instance sees the same prepared agents.
620 Layer.provideMerge(AcpRegistryCatalogLive.pipe(Layer.provide(ServerSettingsLayerLive))),
599 Layer.provideMerge(AcpRegistryCatalog.layer.pipe(Layer.provide(layerServerSettings))),
621600 // Shared native/canonical NDJSON writers used by both the per-instance
622601 // V2 drivers and the orchestration runtime. Provide resource attribution so
623602 // the rewritten telemetry pipeline can account for logical NDJSON writes.
......@@ -634,15 +613,13 @@ const RuntimeCoreDependenciesLive = RuntimeCoreDependenciesBaseLive.pipe(
634613 // the rewritten registry reads snapshots off the instance registry and
635614 // no longer transitively provides it. Exposing it at the runtime level
636615 // keeps a single Live for all opencode consumers.
637 Layer.provideMerge(
638 OpenCodeRuntime.OpenCodeRuntimeLive.pipe(Layer.provide(OpenCodeServerLedger.layer)),
639 ),
640 Layer.provideMerge(WorkspaceLayerLive),
616 Layer.provideMerge(OpenCodeRuntime.layer.pipe(Layer.provide(OpenCodeServerLedger.layer))),
617 Layer.provideMerge(layerWorkspace),
641618 Layer.provideMerge(ProjectEnrichmentService.layer),
642 Layer.provideMerge(Layer.mergeAll(NativeAppIconResolver.layer, ProjectFaviconResolverLayerLive)),
643 Layer.provideMerge(RepositoryIdentityResolverLayerLive),
644 Layer.provideMerge(ServerEnvironmentLayerLive),
645 Layer.provideMerge(AuthLayerLive),
619 Layer.provideMerge(Layer.mergeAll(NativeAppIconResolver.layer, layerProjectFaviconResolver)),
620 Layer.provideMerge(layerRepositoryIdentityResolver),
621 Layer.provideMerge(layerServerEnvironment),
622 Layer.provideMerge(layerAuth),
646623 Layer.provideMerge(ServerSecretStore.layer),
647624 Layer.provideMerge(
648625 Layer.mergeAll(
......@@ -650,16 +627,16 @@ const RuntimeCoreDependenciesLive = RuntimeCoreDependenciesBaseLive.pipe(
650627 Layer.provide(ServerSecretStore.layer),
651628 Layer.provide(ExternalLauncher.layer),
652629 ),
653 CloudManagedEndpointRuntimeLive,
630 layerCloudManagedEndpointRuntime,
654631 ),
655632 ),
656633);
657634
658const RuntimeDependenciesLive = RuntimeCoreDependenciesLive.pipe(
635const layerRuntimeDependencies = layerRuntimeCoreDependencies.pipe(
659636 // Misc.
660 Layer.provideMerge(BackgroundLayerLive),
661 Layer.provideMerge(ResourceDiagnosticsLayerLive),
662 Layer.provideMerge(UsageLayerLive),
637 Layer.provideMerge(layerBackground),
638 Layer.provideMerge(layerResourceDiagnostics),
639 Layer.provideMerge(layerUsage),
663640 Layer.provideMerge(TraceDiagnostics.layer),
664641 Layer.provideMerge(AnalyticsService.layer),
665642 Layer.provideMerge(ExternalLauncher.layer),
......@@ -669,7 +646,7 @@ const RuntimeDependenciesLive = RuntimeCoreDependenciesLive.pipe(
669646 Layer.provide(NetService.layer),
670647);
671648
672const commandReadinessLayer = HttpRouter.middleware(
649const layerCommandReadiness = HttpRouter.middleware(
673650 (httpEffect) =>
674651 Effect.flatMap(ServerRuntimeStartup.ServerRuntimeStartup, (startup) =>
675652 startup.awaitCommandReady.pipe(Effect.orDie, Effect.andThen(httpEffect)),
......@@ -677,24 +654,24 @@ const commandReadinessLayer = HttpRouter.middleware(
677654 { global: true },
678655);
679656
680const makeRoutesLayer = Layer.mergeAll(
657const layerMakeRoutes = Layer.mergeAll(
681658 Layer.mergeAll(
682659 HttpApiBuilder.layer(EnvironmentHttpApi).pipe(
683 Layer.provide(authHttpApiLayer),
684 Layer.provide(connectHttpApiLayer.pipe(Layer.provide(CloudPreferences.layer))),
685 Layer.provide(orchestrationHttpApiLayer),
686 Layer.provide(pullRequestHttpApiLayer),
687 Layer.provide(projectHttpApiLayer),
688 Layer.provide(serverEnvironmentHttpApiLayer),
689 Layer.provide(webhookHttpApiLayer.pipe(Layer.provide(RelayDeliveryProof.layer))),
690 Layer.provide(environmentAuthenticatedAuthLayer),
660 Layer.provide(AuthHttp.layer),
661 Layer.provide(CloudHttp.layer.pipe(Layer.provide(CloudPreferences.layer))),
662 Layer.provide(OrchestrationHttp.layer),
663 Layer.provide(PullRequestHttp.layer),
664 Layer.provide(ProjectHttp.layer),
665 Layer.provide(ServerHttp.layerServerEnvironmentHttpApi),
666 Layer.provide(WebhookRoute.layer.pipe(Layer.provide(RelayDeliveryProof.layer))),
667 Layer.provide(AuthHttp.layerAuthenticatedAuth),
691668 ),
692 otlpTracesProxyRouteLayer,
693 assetRouteLayer,
694 attachmentUploadRouteLayer,
695 deviceHubProxyRouteLayer,
696 staticAndDevRouteLayer,
697 websocketRpcRouteLayer,
669 ServerHttp.layerOtlpTracesProxyRoute,
670 ServerHttp.layerAssetRoute,
671 ServerHttp.layerAttachmentUploadRoute,
672 DeviceHubProxy.layer,
673 ServerHttp.layerStaticAndDevRoute,
674 Ws.layer,
698675 ),
699676 // The MCP session registry is provided globally (shared with V2 provider
700677 // sessions) rather than inline here. The orchestrator toolkit resolves
......@@ -707,36 +684,36 @@ const makeRoutesLayer = Layer.mergeAll(
707684).pipe(
708685 // Both transports consume the same service instance, so caches single-flight across clients
709686 // and mutations observed on WebSocket invalidate patches subsequently read over HTTP.
710 Layer.provide(PullRequestServiceLive),
687 Layer.provide(layerPullRequestService),
711688 Layer.provide(PreviewAutomationBroker.layer),
712 Layer.provide(ServerSelfUpdate.layer.pipe(Layer.provide(DesktopAppUpdateLayerLive))),
713 Layer.provide(commandReadinessLayer),
714 Layer.provide(browserApiCorsLayer),
715 Layer.provide(httpCompressionLayer),
689 Layer.provide(ServerSelfUpdate.layer.pipe(Layer.provide(layerDesktopAppUpdate))),
690 Layer.provide(layerCommandReadiness),
691 Layer.provide(ServerHttp.layerBrowserApiCors),
692 Layer.provide(ServerHttp.layerHttpCompression),
716693);
717694
718const makeServerLayer = Layer.unwrap(
695const layerMakeServer = Layer.unwrap(
719696 Effect.gen(function* () {
720697 const config = yield* ServerConfig.ServerConfig;
721698 const activation = yield* Deferred.make<void>();
722699 const awaitActivation = Deferred.await(activation);
723 const activationLayer = Layer.succeed(ServerActivation.ServerActivation, awaitActivation);
700 const layerActivation = Layer.succeed(ServerActivation.ServerActivation, awaitActivation);
724701 const runtimeStateParked = yield* Deferred.make<void>();
725702 const tailscaleParked = yield* Deferred.make<void>();
726703 const cloudLinkParked = yield* Deferred.make<void>();
727704 const routesReady = yield* Deferred.make<void>();
728 const launcherLayer = ServiceLauncherClient.layer;
705 const layerLauncher = ServiceLauncherClient.layer;
729706
730707 yield* fixPath();
731708
732 const httpListeningLayer = Layer.effectDiscard(
709 const layerHttpListening = Layer.effectDiscard(
733710 Effect.gen(function* () {
734711 yield* HttpServer.HttpServer;
735712 const startup = yield* ServerRuntimeStartup.ServerRuntimeStartup;
736713 yield* startup.markHttpListening;
737714 }),
738715 );
739 const runtimeStateLayer = Layer.effectDiscard(
716 const layerRuntimeState = Layer.effectDiscard(
740717 Effect.acquireRelease(
741718 Effect.gen(function* () {
742719 yield* Deferred.succeed(runtimeStateParked, undefined).pipe(Effect.orDie);
......@@ -770,7 +747,7 @@ const makeServerLayer = Layer.unwrap(
770747 ),
771748 ),
772749 );
773 const tailscaleServeLayer = config.tailscaleServeEnabled
750 const layerTailscaleServe = config.tailscaleServeEnabled
774751 ? Layer.effectDiscard(
775752 Effect.acquireRelease(
776753 Effect.gen(function* () {
......@@ -823,7 +800,7 @@ const makeServerLayer = Layer.unwrap(
823800 ),
824801 )
825802 : Layer.empty;
826 const cloudDesiredLinkReconcileLayer = Layer.effectDiscard(
803 const layerCloudDesiredLinkReconcile = Layer.effectDiscard(
827804 Effect.gen(function* () {
828805 const releaseManagedTunnel = releaseManagedTunnelOnShutdown().pipe(
829806 Effect.timeout("10 seconds"),
......@@ -1037,7 +1014,7 @@ const makeServerLayer = Layer.unwrap(
10371014 }),
10381015 );
10391016
1040 const runtimeServicesLive = ServerRuntimeStartup.layerWithOptions({
1017 const layerRuntimeServices = ServerRuntimeStartup.layerWithOptions({
10411018 activate: Deferred.succeed(activation, undefined).pipe(Effect.asVoid),
10421019 abort: (error) => Deferred.die(activation, error).pipe(Effect.asVoid),
10431020 awaitAuxiliaryParked: Effect.all(
......@@ -1049,42 +1026,42 @@ const makeServerLayer = Layer.unwrap(
10491026 ],
10501027 { concurrency: "unbounded" },
10511028 ).pipe(Effect.asVoid),
1052 }).pipe(Layer.provideMerge(RuntimeDependenciesLive), Layer.provide(launcherLayer));
1029 }).pipe(Layer.provideMerge(layerRuntimeDependencies), Layer.provide(layerLauncher));
10531030
1054 const routesLayer = HttpRouter.serve(makeRoutesLayer.pipe(Layer.provide(launcherLayer)), {
1031 const layerRoutes = HttpRouter.serve(layerMakeRoutes.pipe(Layer.provide(layerLauncher)), {
10551032 disableLogger: !config.logWebSocketEvents,
10561033 routerConfig: HTTP_ROUTER_CONFIG,
10571034 }).pipe(
10581035 withUntracedRequests,
10591036 Layer.tap(() => Deferred.succeed(routesReady, undefined).pipe(Effect.orDie)),
10601037 );
1061 const serverApplicationLayer = Layer.mergeAll(
1062 routesLayer,
1063 httpListeningLayer,
1064 runtimeStateLayer.pipe(Layer.provide(launcherLayer)),
1065 tailscaleServeLayer,
1066 cloudDesiredLinkReconcileLayer,
1038 const layerServerApplication = Layer.mergeAll(
1039 layerRoutes,
1040 layerHttpListening,
1041 layerRuntimeState.pipe(Layer.provide(layerLauncher)),
1042 layerTailscaleServe,
1043 layerCloudDesiredLinkReconcile,
10671044 HeapSnapshot.layer,
10681045 );
10691046
1070 return serverApplicationLayer.pipe(
1071 Layer.provideMerge(runtimeServicesLive),
1047 return layerServerApplication.pipe(
1048 Layer.provideMerge(layerRuntimeServices),
10721049 Layer.provideMerge(
10731050 McpSessionRegistry.layer.pipe(
10741051 Layer.provide(ServerEnvironment.layer.pipe(Layer.provide(ServerSecretStore.layer))),
10751052 ),
10761053 ),
1077 Layer.provide(activationLayer),
1078 Layer.provideMerge(serverRelayBrokerTracingLayer),
1079 Layer.provideMerge(HttpServerLive),
1080 Layer.provide(ApplicationObservabilityLive),
1054 Layer.provide(layerActivation),
1055 Layer.provideMerge(RelayTracing.layerServerRelayBroker),
1056 Layer.provideMerge(layerHttpServer),
1057 Layer.provide(layerApplicationObservability),
10811058 Layer.provideMerge(FetchHttpClient.layer),
10821059 // PR reads, Git operations, and WebSocket discovery share one process limiter.
10831060 Layer.provide(VcsProcess.layer),
1084 Layer.provideMerge(PlatformServicesLive),
1061 Layer.provideMerge(layerPlatformServices),
10851062 );
10861063 }),
10871064);
10881065
10891066// The CLI supplies configuration.
1090export const runServer = Layer.launch(makeServerLayer);
1067export const runServer = Layer.launch(layerMakeServer);
apps/server/src/serverLogger.test.ts+11-11
......@@ -13,7 +13,7 @@ import { DEFAULT_SIGNAL_EXPORT } from "@t3tools/shared/observability";
1313import * as OtelEnvironment from "@t3tools/shared/otelEnvironment";
1414
1515import * as ServerConfig from "./config.ts";
16import { ServerLoggerLive } from "./serverLogger.ts";
16import * as ServerLogger from "./serverLogger.ts";
1717
1818interface ExportedRequest {
1919 readonly url: string;
......@@ -22,7 +22,7 @@ interface ExportedRequest {
2222}
2323
2424/** Answers every export with a 200 and keeps what was posted for assertions. */
25const collectorLayer = (requests: Array<ExportedRequest>) =>
25const layerCollector = (requests: Array<ExportedRequest>) =>
2626 Layer.succeed(
2727 HttpClient.HttpClient,
2828 HttpClient.make((request) =>
......@@ -38,7 +38,7 @@ const collectorLayer = (requests: Array<ExportedRequest>) =>
3838 ),
3939 );
4040
41const configLayer = (overrides: Partial<ServerConfig.ServerConfig["Service"]>) =>
41const layerConfig = (overrides: Partial<ServerConfig.ServerConfig["Service"]>) =>
4242 Layer.effect(
4343 ServerConfig.ServerConfig,
4444 Effect.gen(function* () {
......@@ -93,9 +93,9 @@ const logThrough = (overrides: Partial<ServerConfig.ServerConfig["Service"]>) =>
9393 const requests: Array<ExportedRequest> = [];
9494 yield* Effect.log("server logger under test").pipe(
9595 Effect.provide(
96 ServerLoggerLive.pipe(
97 Layer.provide(configLayer(overrides)),
98 Layer.provide(collectorLayer(requests)),
96 ServerLogger.layer.pipe(
97 Layer.provide(layerConfig(overrides)),
98 Layer.provide(layerCollector(requests)),
9999 ),
100100 ),
101101 );
......@@ -110,7 +110,7 @@ const logInSpanThrough = (overrides: Partial<ServerConfig.ServerConfig["Service"
110110 Effect.gen(function* () {
111111 const requests: Array<ExportedRequest> = [];
112112 const spans: Array<Tracer.NativeSpan> = [];
113 const tracerLayer = Layer.succeed(
113 const layerTracer = Layer.succeed(
114114 Tracer.Tracer,
115115 Tracer.make({
116116 span: (spanOptions) => {
......@@ -124,11 +124,11 @@ const logInSpanThrough = (overrides: Partial<ServerConfig.ServerConfig["Service"
124124 Effect.withSpan("server-logger-test"),
125125 Effect.provide(
126126 Layer.mergeAll(
127 ServerLoggerLive.pipe(
128 Layer.provide(configLayer(overrides)),
129 Layer.provide(collectorLayer(requests)),
127 ServerLogger.layer.pipe(
128 Layer.provide(layerConfig(overrides)),
129 Layer.provide(layerCollector(requests)),
130130 ),
131 tracerLayer,
131 layerTracer,
132132 ),
133133 ),
134134 );
apps/server/src/serverLogger.ts+6-6
......@@ -1,4 +1,4 @@
1import { otlpSerializationLayer } from "@t3tools/shared/observability";
1import * as SharedObservability from "@t3tools/shared/observability";
22import * as Effect from "effect/Effect";
33import * as Layer from "effect/Layer";
44import * as Logger from "effect/Logger";
......@@ -8,9 +8,9 @@ import * as OtlpLogger from "effect/observability/OtlpLogger";
88
99import * as ServerConfig from "./config.ts";
1010
11export const ServerLoggerLive = Effect.gen(function* () {
11export const layer = Effect.gen(function* () {
1212 const config = yield* ServerConfig.ServerConfig;
13 const minimumLogLevelLayer = Layer.succeed(References.MinimumLogLevel, config.logLevel);
13 const layerMinimumLogLevel = Layer.succeed(References.MinimumLogLevel, config.logLevel);
1414
1515 const logs = config.otlpLogsExport;
1616 const otlpLogger =
......@@ -35,15 +35,15 @@ export const ServerLoggerLive = Effect.gen(function* () {
3535 // Recording events on spans is also the shape OpenTelemetry is deprecating,
3636 // in favor of the log-based events this logger emits:
3737 // https://opentelemetry.io/blog/2026/deprecating-span-events/
38 const loggerLayer = Logger.layer(
38 const layerLogger = Logger.layer(
3939 otlpLogger === undefined
4040 ? [Logger.consolePretty(), Logger.tracerLogger]
4141 : [Logger.consolePretty(), otlpLogger],
4242 { mergeWithExisting: false },
4343 ).pipe(
4444 Layer.provide(OtlpExporter.layerFlusher),
45 Layer.provide(otlpSerializationLayer(logs.protocol)),
45 Layer.provide(SharedObservability.layerOtlpSerialization(logs.protocol)),
4646 );
4747
48 return Layer.mergeAll(loggerLayer, minimumLogLevelLayer);
48 return Layer.mergeAll(layerLogger, layerMinimumLogLevel);
4949}).pipe(Layer.unwrap);
apps/server/src/serverSettings.test.ts+71-71
......@@ -27,7 +27,7 @@ import * as TestClock from "effect/testing/TestClock";
2727import * as SqlClient from "effect/sql/SqlClient";
2828import * as ServerSecretStore from "./auth/ServerSecretStore.ts";
2929import * as ServerConfig from "./config.ts";
30import { SqlitePersistenceMemory } from "./persistence/Layers/Sqlite.ts";
30import * as SqlitePersistence from "./persistence/Layers/Sqlite.ts";
3131import { writeFileStringAtomically } from "./atomicWrite.ts";
3232import * as ServerSettingsModule from "./serverSettings.ts";
3333import { resolveProviderInstanceTerminalEnvironment } from "./terminal/Manager.ts";
......@@ -36,10 +36,10 @@ const decodeSettingsPatch = Schema.decodeUnknownEffect(ServerSettingsPatch);
3636const decodeServerSettings = Schema.decodeUnknownEffect(ServerSettings);
3737const decodeServerSettingsJson = Schema.decodeUnknownEffect(Schema.fromJsonString(ServerSettings));
3838
39const makeServerSettingsLayer = () =>
39const layerServerSettings = () =>
4040 ServerSettingsModule.layer.pipe(
4141 Layer.provide(ServerSecretStore.layer),
42 Layer.provideMerge(Layer.fresh(SqlitePersistenceMemory)),
42 Layer.provideMerge(Layer.fresh(SqlitePersistence.layerMemory)),
4343 Layer.provideMerge(
4444 Layer.fresh(
4545 ServerConfig.layerTest(process.cwd(), {
......@@ -50,10 +50,10 @@ const makeServerSettingsLayer = () =>
5050 );
5151
5252/** Like `makeServerSettingsLayer`, but also exposes the secret store for assertions. */
53const makeServerSettingsLayerWithSecrets = () =>
53const layerServerSettingsWithSecrets = () =>
5454 ServerSettingsModule.layer.pipe(
5555 Layer.provideMerge(ServerSecretStore.layer),
56 Layer.provideMerge(Layer.fresh(SqlitePersistenceMemory)),
56 Layer.provideMerge(Layer.fresh(SqlitePersistence.layerMemory)),
5757 Layer.provideMerge(
5858 Layer.fresh(
5959 ServerConfig.layerTest(process.cwd(), {
......@@ -63,7 +63,7 @@ const makeServerSettingsLayerWithSecrets = () =>
6363 ),
6464 );
6565
66const makeFailingSecretStoreLayer = (cause: ServerSecretStore.SecretStoreError) =>
66const layerFailingSecretStore = (cause: ServerSecretStore.SecretStoreError) =>
6767 Layer.succeed(
6868 ServerSecretStore.ServerSecretStore,
6969 ServerSecretStore.ServerSecretStore.of({
......@@ -130,7 +130,7 @@ it.layer(NodeServices.layer)("server settings", (it) => {
130130 );
131131 assert.equal(persisted.responseStreamingMode, "turn");
132132 assert.deepEqual(persisted.projectSettingsOverrides, settings.projectSettingsOverrides);
133 }).pipe(Effect.provide(makeServerSettingsLayer())),
133 }).pipe(Effect.provide(layerServerSettings())),
134134 );
135135
136136 it.effect("saves through a symlinked settings file without replacing the link", () =>
......@@ -152,7 +152,7 @@ it.layer(NodeServices.layer)("server settings", (it) => {
152152 yield* fs.readFileString(linkedSettingsPath),
153153 );
154154 assert.equal(persisted.responseStreamingMode, "paragraph");
155 }).pipe(Effect.provide(makeServerSettingsLayer())),
155 }).pipe(Effect.provide(layerServerSettings())),
156156 );
157157
158158 it.effect("reloads when the destination of a symlinked settings file changes", () =>
......@@ -178,7 +178,7 @@ it.layer(NodeServices.layer)("server settings", (it) => {
178178 const change = yield* changes.pipe(Stream.runHead, Effect.timeout("2 seconds"));
179179 assert.equal(Option.getOrUndefined(change)?.responseStreamingMode, "paragraph");
180180 }),
181 ).pipe(TestClock.withLive, Effect.provide(makeServerSettingsLayer())),
181 ).pipe(TestClock.withLive, Effect.provide(layerServerSettings())),
182182 );
183183
184184 it.effect("follows a settings link that is repointed to another directory", () =>
......@@ -213,7 +213,7 @@ it.layer(NodeServices.layer)("server settings", (it) => {
213213 const edited = yield* editChanges.pipe(Stream.runHead, Effect.timeout("2 seconds"));
214214 assert.equal(Option.getOrUndefined(edited)?.responseStreamingMode, "turn");
215215 }),
216 ).pipe(TestClock.withLive, Effect.provide(makeServerSettingsLayer())),
216 ).pipe(TestClock.withLive, Effect.provide(layerServerSettings())),
217217 );
218218
219219 it.effect("reloads when a dangling settings link gets its destination", () =>
......@@ -238,7 +238,7 @@ it.layer(NodeServices.layer)("server settings", (it) => {
238238 const change = yield* changes.pipe(Stream.runHead, Effect.timeout("2 seconds"));
239239 assert.equal(Option.getOrUndefined(change)?.responseStreamingMode, "paragraph");
240240 }),
241 ).pipe(TestClock.withLive, Effect.provide(makeServerSettingsLayer())),
241 ).pipe(TestClock.withLive, Effect.provide(layerServerSettings())),
242242 );
243243
244244 it.effect("preserves context when reading a provider environment secret fails", () => {
......@@ -253,15 +253,15 @@ it.layer(NodeServices.layer)("server settings", (it) => {
253253 resource: "provider environment secret",
254254 cause: platformCause,
255255 });
256 const configLayer = Layer.fresh(
256 const layerConfig = Layer.fresh(
257257 ServerConfig.layerTest(process.cwd(), {
258258 prefix: "t3code-server-settings-secret-failure-test-",
259259 }),
260260 );
261 const settingsLayer = ServerSettingsModule.layer.pipe(
262 Layer.provide(makeFailingSecretStoreLayer(cause)),
263 Layer.provideMerge(Layer.fresh(SqlitePersistenceMemory)),
264 Layer.provideMerge(configLayer),
261 const layerSettings = ServerSettingsModule.layer.pipe(
262 Layer.provide(layerFailingSecretStore(cause)),
263 Layer.provideMerge(Layer.fresh(SqlitePersistence.layerMemory)),
264 Layer.provideMerge(layerConfig),
265265 );
266266
267267 return Effect.gen(function* () {
......@@ -283,7 +283,7 @@ it.layer(NodeServices.layer)("server settings", (it) => {
283283 });
284284 assert.strictEqual(error.cause, cause);
285285 assert.notInclude(error.message, cause.message);
286 }).pipe(Effect.provide(settingsLayer));
286 }).pipe(Effect.provide(layerSettings));
287287 });
288288
289289 it.effect("identifies provider history query failures", () =>
......@@ -300,7 +300,7 @@ it.layer(NodeServices.layer)("server settings", (it) => {
300300 operation: "read-provider-history",
301301 settingsPath: serverConfig.settingsPath,
302302 });
303 }).pipe(Effect.provide(makeServerSettingsLayer())),
303 }).pipe(Effect.provide(layerServerSettings())),
304304 );
305305
306306 it.effect("decodes nested settings patches", () =>
......@@ -414,7 +414,7 @@ it.layer(NodeServices.layer)("server settings", (it) => {
414414 ],
415415 ),
416416 );
417 }).pipe(Effect.provide(makeServerSettingsLayer())),
417 }).pipe(Effect.provide(layerServerSettings())),
418418 );
419419
420420 it.effect("creates provider instances atomically without overwriting a concurrent add", () =>
......@@ -445,7 +445,7 @@ it.layer(NodeServices.layer)("server settings", (it) => {
445445 (yield* serverSettings.getSettings).providerInstances[instanceId]?.displayName ?? "",
446446 ),
447447 );
448 }).pipe(Effect.provide(makeServerSettingsLayer())),
448 }).pipe(Effect.provide(layerServerSettings())),
449449 );
450450
451451 it.effect("pauses provider-instance mutations while a settings snapshot is in use", () =>
......@@ -489,7 +489,7 @@ it.layer(NodeServices.layer)("server settings", (it) => {
489489 (yield* serverSettings.getSettings).providerInstances[instanceId]?.displayName,
490490 "Kilo",
491491 );
492 }).pipe(Effect.provide(makeServerSettingsLayer())),
492 }).pipe(Effect.provide(layerServerSettings())),
493493 );
494494
495495 it.effect("buffers changes after a subscription is acquired but before it is consumed", () =>
......@@ -512,7 +512,7 @@ it.layer(NodeServices.layer)("server settings", (it) => {
512512 "/usr/local/bin/codex-next",
513513 );
514514 }),
515 ).pipe(Effect.provide(makeServerSettingsLayer())),
515 ).pipe(Effect.provide(layerServerSettings())),
516516 );
517517
518518 it.effect("persists custom usage prices and removes them from the settings file", () =>
......@@ -538,7 +538,7 @@ it.layer(NodeServices.layer)("server settings", (it) => {
538538 const restored = yield* readPersisted;
539539 assert.deepStrictEqual(restored.usagePriceOverrides, {});
540540 }),
541 ).pipe(Effect.provide(makeServerSettingsLayer())),
541 ).pipe(Effect.provide(layerServerSettings())),
542542 );
543543
544544 it.effect("persists and broadcasts thread settlement settings", () =>
......@@ -566,7 +566,7 @@ it.layer(NodeServices.layer)("server settings", (it) => {
566566 assert.strictEqual(persisted.sidebarAutoSettleAfterDays, null);
567567 assert.isFalse(persisted.sidebarAutoSettleOnMerge);
568568 }),
569 ).pipe(Effect.provide(makeServerSettingsLayer())),
569 ).pipe(Effect.provide(layerServerSettings())),
570570 );
571571
572572 it.effect("preserves model when switching providers via textGenerationModelSelection", () =>
......@@ -604,7 +604,7 @@ it.layer(NodeServices.layer)("server settings", (it) => {
604604 { id: "reasoningEffort", value: "high" },
605605 ]),
606606 );
607 }).pipe(Effect.provide(makeServerSettingsLayer())),
607 }).pipe(Effect.provide(layerServerSettings())),
608608 );
609609
610610 it.effect("preserves custom provider instance text generation selections", () =>
......@@ -629,7 +629,7 @@ it.layer(NodeServices.layer)("server settings", (it) => {
629629 instanceId: ProviderInstanceId.make("claude_openrouter"),
630630 model: "openai/gpt-5.5",
631631 });
632 }).pipe(Effect.provide(makeServerSettingsLayer())),
632 }).pipe(Effect.provide(layerServerSettings())),
633633 );
634634
635635 it.effect(
......@@ -662,7 +662,7 @@ it.layer(NodeServices.layer)("server settings", (it) => {
662662 instanceId,
663663 model: "openai/gpt-5.5",
664664 });
665 }).pipe(Effect.provide(makeServerSettingsLayer())),
665 }).pipe(Effect.provide(layerServerSettings())),
666666 );
667667
668668 it.effect("preserves enabled text generation selections for non-built-in drivers", () =>
......@@ -688,7 +688,7 @@ it.layer(NodeServices.layer)("server settings", (it) => {
688688 instanceId,
689689 model: "openai/gpt-5.5",
690690 });
691 }).pipe(Effect.provide(makeServerSettingsLayer())),
691 }).pipe(Effect.provide(layerServerSettings())),
692692 );
693693
694694 it.effect(
......@@ -755,7 +755,7 @@ it.layer(NodeServices.layer)("server settings", (it) => {
755755 ServerSettingsModule.resolveSourceControlWriterModelSelection(restored),
756756 sourceControlWriterModelSelection,
757757 );
758 }).pipe(Effect.provide(makeServerSettingsLayer())),
758 }).pipe(Effect.provide(layerServerSettings())),
759759 );
760760
761761 it.effect("drops stale text generation options when resetting model selection", () =>
......@@ -788,7 +788,7 @@ it.layer(NodeServices.layer)("server settings", (it) => {
788788 instanceId: DEFAULT_SERVER_SETTINGS.textGenerationModelSelection.instanceId,
789789 model: DEFAULT_SERVER_SETTINGS.textGenerationModelSelection.model,
790790 });
791 }).pipe(Effect.provide(makeServerSettingsLayer())),
791 }).pipe(Effect.provide(layerServerSettings())),
792792 );
793793
794794 it.effect("replaces provider instance maps when clearing optional fields", () =>
......@@ -825,7 +825,7 @@ it.layer(NodeServices.layer)("server settings", (it) => {
825825 enabled: true,
826826 config: { homePath: "~/.codex" },
827827 });
828 }).pipe(Effect.provide(makeServerSettingsLayer())),
828 }).pipe(Effect.provide(layerServerSettings())),
829829 );
830830
831831 it.effect("enables previously used providers from sparse settings files", () =>
......@@ -845,7 +845,7 @@ it.layer(NodeServices.layer)("server settings", (it) => {
845845 assert.isTrue(settings.providers.opencode.enabled);
846846 assert.isFalse(settings.providers.cursor.enabled);
847847 assert.equal(settings.providers.opencode.serverUrl, "http://127.0.0.1:4096");
848 }).pipe(Effect.provide(makeServerSettingsLayer())),
848 }).pipe(Effect.provide(layerServerSettings())),
849849 );
850850
851851 it.effect("preserves existing provider instances without explicit enabled flags", () =>
......@@ -870,7 +870,7 @@ it.layer(NodeServices.layer)("server settings", (it) => {
870870 const unused = settings.providerInstances[ProviderInstanceId.make("opencode_unused")];
871871 assert.isDefined(unused);
872872 assert.isFalse(resolveProviderInstanceEnabled(unused));
873 }).pipe(Effect.provide(makeServerSettingsLayer())),
873 }).pipe(Effect.provide(layerServerSettings())),
874874 );
875875
876876 it.effect("preserves explicit provider disables in existing settings files", () =>
......@@ -894,7 +894,7 @@ it.layer(NodeServices.layer)("server settings", (it) => {
894894 assert.isFalse(settings.providerInstances[ProviderInstanceId.make("grok")]?.enabled);
895895 assert.isFalse(settings.providerInstances[ProviderInstanceId.make("opencode")]?.enabled);
896896 assert.isFalse(settings.providerInstances[ProviderInstanceId.make("cursor")]?.enabled);
897 }).pipe(Effect.provide(makeServerSettingsLayer())),
897 }).pipe(Effect.provide(layerServerSettings())),
898898 );
899899
900900 it.effect("skips a disabled provider instance when picking the text generation fallback", () =>
......@@ -912,7 +912,7 @@ it.layer(NodeServices.layer)("server settings", (it) => {
912912 const settings = yield* serverSettings.getSettings;
913913
914914 assert.equal(settings.textGenerationModelSelection.instanceId, "claudeAgent");
915 }).pipe(Effect.provide(makeServerSettingsLayer())),
915 }).pipe(Effect.provide(layerServerSettings())),
916916 );
917917
918918 it.effect("keeps unused providers disabled in existing sparse settings files", () =>
......@@ -927,7 +927,7 @@ it.layer(NodeServices.layer)("server settings", (it) => {
927927 assert.isFalse(settings.providers.grok.enabled);
928928 assert.isFalse(settings.providers.opencode.enabled);
929929 assert.isFalse(settings.providers.cursor.enabled);
930 }).pipe(Effect.provide(makeServerSettingsLayer())),
930 }).pipe(Effect.provide(layerServerSettings())),
931931 );
932932
933933 it.effect("preserves provider history when no settings file exists", () =>
......@@ -940,7 +940,7 @@ it.layer(NodeServices.layer)("server settings", (it) => {
940940 assert.isTrue(settings.providers.grok.enabled);
941941 assert.isFalse(settings.providers.opencode.enabled);
942942 assert.isFalse(settings.providers.cursor.enabled);
943 }).pipe(Effect.provide(makeServerSettingsLayer())),
943 }).pipe(Effect.provide(layerServerSettings())),
944944 );
945945
946946 it.effect("preserves provider history when the settings file is invalid", () =>
......@@ -956,7 +956,7 @@ it.layer(NodeServices.layer)("server settings", (it) => {
956956 assert.isTrue(settings.providers.cursor.enabled);
957957 assert.isFalse(settings.providers.grok.enabled);
958958 assert.isFalse(settings.providers.opencode.enabled);
959 }).pipe(Effect.provide(makeServerSettingsLayer())),
959 }).pipe(Effect.provide(layerServerSettings())),
960960 );
961961
962962 it.effect("preserves valid provider flags when another settings field is invalid", () =>
......@@ -975,7 +975,7 @@ it.layer(NodeServices.layer)("server settings", (it) => {
975975 assert.isFalse(settings.providers.cursor.enabled);
976976 assert.isTrue(settings.providers.grok.enabled);
977977 assert.isFalse(settings.providers.opencode.enabled);
978 }).pipe(Effect.provide(makeServerSettingsLayer())),
978 }).pipe(Effect.provide(layerServerSettings())),
979979 );
980980
981981 it.effect("restores providers from persisted runtime sessions", () =>
......@@ -1006,7 +1006,7 @@ it.layer(NodeServices.layer)("server settings", (it) => {
10061006 assert.isFalse(settings.providers.grok.enabled);
10071007 assert.isTrue(settings.providers.opencode.enabled);
10081008 assert.isFalse(settings.providers.cursor.enabled);
1009 }).pipe(Effect.provide(makeServerSettingsLayer())),
1009 }).pipe(Effect.provide(layerServerSettings())),
10101010 );
10111011
10121012 it.effect("persists explicit disables after a provider has been used", () =>
......@@ -1026,7 +1026,7 @@ it.layer(NodeServices.layer)("server settings", (it) => {
10261026 const raw = yield* fileSystem.readFileString(serverConfig.settingsPath);
10271027 // @effect-diagnostics-next-line preferSchemaOverJson:off
10281028 assert.isFalse(JSON.parse(raw).providers.grok.enabled);
1029 }).pipe(Effect.provide(makeServerSettingsLayer())),
1029 }).pipe(Effect.provide(layerServerSettings())),
10301030 );
10311031
10321032 it.effect("persists explicit provider enables before their first use", () =>
......@@ -1050,7 +1050,7 @@ it.layer(NodeServices.layer)("server settings", (it) => {
10501050 assert.isTrue(persisted.providers.cursor.enabled);
10511051 assert.isTrue(persisted.providers.grok.enabled);
10521052 assert.isTrue(persisted.providers.opencode.enabled);
1053 }).pipe(Effect.provide(makeServerSettingsLayer())),
1053 }).pipe(Effect.provide(layerServerSettings())),
10541054 );
10551055
10561056 it.effect("keeps optional providers disabled after a new installation writes settings", () =>
......@@ -1088,7 +1088,7 @@ it.layer(NodeServices.layer)("server settings", (it) => {
10881088 assert.isFalse(persisted.providers.grok.enabled);
10891089 assert.isFalse(persisted.providers.opencode.enabled);
10901090 assert.isUndefined(persisted.providerInstances.grok.enabled);
1091 }).pipe(Effect.provide(makeServerSettingsLayer())),
1091 }).pipe(Effect.provide(layerServerSettings())),
10921092 );
10931093
10941094 it.effect("folds a legacy in-config enabled flag into the envelope on load", () =>
......@@ -1124,7 +1124,7 @@ it.layer(NodeServices.layer)("server settings", (it) => {
11241124 driver: ProviderDriverKind.make("cursor"),
11251125 config: { enabled: "nope" },
11261126 });
1127 }).pipe(Effect.provide(makeServerSettingsLayer())),
1127 }).pipe(Effect.provide(layerServerSettings())),
11281128 );
11291129
11301130 it.effect("folds in-config enabled flags arriving through updates", () =>
......@@ -1147,7 +1147,7 @@ it.layer(NodeServices.layer)("server settings", (it) => {
11471147 enabled: false,
11481148 config: { binaryPath: "/opt/grok" },
11491149 });
1150 }).pipe(Effect.provide(makeServerSettingsLayer())),
1150 }).pipe(Effect.provide(layerServerSettings())),
11511151 );
11521152
11531153 it.effect("trims provider path settings when updates are applied", () =>
......@@ -1195,7 +1195,7 @@ it.layer(NodeServices.layer)("server settings", (it) => {
11951195 serverPassword: "secret-password",
11961196 customModels: [],
11971197 });
1198 }).pipe(Effect.provide(makeServerSettingsLayer())),
1198 }).pipe(Effect.provide(layerServerSettings())),
11991199 );
12001200
12011201 it.effect("trims observability settings when updates are applied", () =>
......@@ -1217,7 +1217,7 @@ it.layer(NodeServices.layer)("server settings", (it) => {
12171217 otlpMetricsUrl: "http://localhost:4318/v1/metrics",
12181218 otlpLogsUrl: "http://localhost:4318/v1/logs",
12191219 });
1220 }).pipe(Effect.provide(makeServerSettingsLayer())),
1220 }).pipe(Effect.provide(layerServerSettings())),
12211221 );
12221222
12231223 it.effect("defaults blank binary paths to provider executables", () =>
......@@ -1237,7 +1237,7 @@ it.layer(NodeServices.layer)("server settings", (it) => {
12371237
12381238 assert.equal(next.providers.codex.binaryPath, "codex");
12391239 assert.equal(next.providers.claudeAgent.binaryPath, "claude");
1240 }).pipe(Effect.provide(makeServerSettingsLayer())),
1240 }).pipe(Effect.provide(layerServerSettings())),
12411241 );
12421242
12431243 it.effect("writes non-default settings and explicit optional provider defaults to disk", () =>
......@@ -1299,7 +1299,7 @@ it.layer(NodeServices.layer)("server settings", (it) => {
12991299 },
13001300 automaticGitFetchInterval: 10_000,
13011301 });
1302 }).pipe(Effect.provide(makeServerSettingsLayer())),
1302 }).pipe(Effect.provide(layerServerSettings())),
13031303 );
13041304
13051305 it.effect("keeps the inline value on disk when secret migration fails", () => {
......@@ -1307,16 +1307,16 @@ it.layer(NodeServices.layer)("server settings", (it) => {
13071307 resource: "provider environment secret",
13081308 cause: new Error("Secret storage unavailable"),
13091309 });
1310 const secretLayer = Layer.effect(
1310 const layerSecret = Layer.effect(
13111311 ServerSecretStore.ServerSecretStore,
13121312 Effect.map(ServerSecretStore.ServerSecretStore, (store) => ({
13131313 ...store,
13141314 set: () => Effect.fail(cause),
13151315 })),
13161316 ).pipe(Layer.provide(ServerSecretStore.layer));
1317 const settingsLayer = ServerSettingsModule.layer.pipe(
1318 Layer.provide(secretLayer),
1319 Layer.provideMerge(Layer.fresh(SqlitePersistenceMemory)),
1317 const layerSettings = ServerSettingsModule.layer.pipe(
1318 Layer.provide(layerSecret),
1319 Layer.provideMerge(Layer.fresh(SqlitePersistence.layerMemory)),
13201320 Layer.provideMerge(
13211321 Layer.fresh(
13221322 ServerConfig.layerTest(process.cwd(), {
......@@ -1352,7 +1352,7 @@ it.layer(NodeServices.layer)("server settings", (it) => {
13521352 settings.providerInstances[instanceId]?.environment?.[0]?.value,
13531353 "inline-test-token",
13541354 );
1355 }).pipe(Effect.provide(settingsLayer));
1355 }).pipe(Effect.provide(layerSettings));
13561356 });
13571357
13581358 it.effect.each(
......@@ -1421,7 +1421,7 @@ it.layer(NodeServices.layer)("server settings", (it) => {
14211421 ),
14221422 );
14231423 assert.equal(reloaded.providerInstances[instanceId]?.environment?.[0]?.value, expected);
1424 }).pipe(Effect.provide(makeServerSettingsLayer())),
1424 }).pipe(Effect.provide(layerServerSettings())),
14251425 );
14261426
14271427 it.effect.each([true, false])(
......@@ -1449,7 +1449,7 @@ it.layer(NodeServices.layer)("server settings", (it) => {
14491449 next.providerInstances[instanceId]?.environment?.find((v) => v.sensitive)?.value,
14501450 sensitiveLast ? "secret-last" : "",
14511451 );
1452 }).pipe(Effect.provide(makeServerSettingsLayer())),
1452 }).pipe(Effect.provide(layerServerSettings())),
14531453 );
14541454
14551455 it.effect("stores sensitive provider instance environment values outside settings.json", () =>
......@@ -1513,7 +1513,7 @@ it.layer(NodeServices.layer)("server settings", (it) => {
15131513 roundTripped.providerInstances[instanceId]?.environment?.[0]?.value,
15141514 "sk-or-secret",
15151515 );
1516 }).pipe(Effect.provide(makeServerSettingsLayer())),
1516 }).pipe(Effect.provide(layerServerSettings())),
15171517 );
15181518
15191519 it.effect(
......@@ -1563,7 +1563,7 @@ it.layer(NodeServices.layer)("server settings", (it) => {
15631563 ServerSettingsModule.redactServerSettingsForClient(cleared).bitbucket.accessToken,
15641564 "",
15651565 );
1566 }).pipe(Effect.provide(makeServerSettingsLayerWithSecrets())),
1566 }).pipe(Effect.provide(layerServerSettingsWithSecrets())),
15671567 );
15681568
15691569 it.effect("removes a Bitbucket secret once its token is cleared by hand in settings.json", () =>
......@@ -1579,7 +1579,7 @@ it.layer(NodeServices.layer)("server settings", (it) => {
15791579 yield* serverSettings.updateSettings({ cursorKeychainUsageEnabled: true });
15801580
15811581 assert.isTrue(Option.isNone(yield* secrets.get("bitbucket-access-token")));
1582 }).pipe(Effect.provide(makeServerSettingsLayerWithSecrets())),
1582 }).pipe(Effect.provide(layerServerSettingsWithSecrets())),
15831583 );
15841584
15851585 it.effect("moves a hand-edited Bitbucket token into the secret store when settings load", () =>
......@@ -1606,7 +1606,7 @@ it.layer(NodeServices.layer)("server settings", (it) => {
16061606 Option.isSome(stored) ? new TextDecoder().decode(stored.value) : null,
16071607 "hand-edited-token",
16081608 );
1609 }).pipe(Effect.provide(makeServerSettingsLayerWithSecrets())),
1609 }).pipe(Effect.provide(layerServerSettingsWithSecrets())),
16101610 );
16111611
16121612 it.effect(
......@@ -1635,7 +1635,7 @@ it.layer(NodeServices.layer)("server settings", (it) => {
16351635 yield* fileSystem.readFileString(serverConfig.settingsPath),
16361636 "hand-edited-token",
16371637 );
1638 }).pipe(Effect.provide(makeServerSettingsLayer())),
1638 }).pipe(Effect.provide(layerServerSettings())),
16391639 );
16401640
16411641 it.effect("materializes provider secrets for terminal environment resolution", () =>
......@@ -1670,7 +1670,7 @@ it.layer(NodeServices.layer)("server settings", (it) => {
16701670 assert.match(environment.CODEX_HOME ?? "", /[\\/][.]codex-terminal$/);
16711671 assert.notInclude(persisted, "sk-terminal-secret");
16721672 assert.include(persisted, '"valueRedacted": true');
1673 }).pipe(Effect.provide(makeServerSettingsLayer())),
1673 }).pipe(Effect.provide(layerServerSettings())),
16741674 );
16751675 it.effect("rolls back provider secret changes when the settings file commit fails", () =>
16761676 Effect.gen(function* () {
......@@ -1691,7 +1691,7 @@ it.layer(NodeServices.layer)("server settings", (it) => {
16911691 : fileSystem.rename(fromPath, toPath),
16921692 });
16931693 const instanceId = ProviderInstanceId.make("codex_write_failure");
1694 const settingsLayer = makeServerSettingsLayer().pipe(
1694 const layerSettings = layerServerSettings().pipe(
16951695 Layer.provideMerge(Layer.succeed(FileSystem.FileSystem, failingFileSystem)),
16961696 );
16971697
......@@ -1736,7 +1736,7 @@ it.layer(NodeServices.layer)("server settings", (it) => {
17361736 ?.value,
17371737 "sk-kept",
17381738 );
1739 }).pipe(Effect.provide(settingsLayer));
1739 }).pipe(Effect.provide(layerSettings));
17401740 }),
17411741 );
17421742
......@@ -1746,7 +1746,7 @@ it.layer(NodeServices.layer)("server settings", (it) => {
17461746 const textDecoder = new TextDecoder();
17471747 const secrets = new Map<string, Uint8Array>();
17481748 let rejectNewSecret = false;
1749 const secretStoreLayer = Layer.succeed(
1749 const layerSecretStore = Layer.succeed(
17501750 ServerSecretStore.ServerSecretStore,
17511751 ServerSecretStore.ServerSecretStore.of({
17521752 get: (name) =>
......@@ -1799,9 +1799,9 @@ it.layer(NodeServices.layer)("server settings", (it) => {
17991799 }),
18001800 }),
18011801 );
1802 const settingsLayer = ServerSettingsModule.layer.pipe(
1803 Layer.provideMerge(Layer.fresh(SqlitePersistenceMemory)),
1804 Layer.provide(secretStoreLayer),
1802 const layerSettings = ServerSettingsModule.layer.pipe(
1803 Layer.provideMerge(Layer.fresh(SqlitePersistence.layerMemory)),
1804 Layer.provide(layerSecretStore),
18051805 Layer.provideMerge(
18061806 Layer.fresh(
18071807 ServerConfig.layerTest(process.cwd(), {
......@@ -1844,7 +1844,7 @@ it.layer(NodeServices.layer)("server settings", (it) => {
18441844 ?.value,
18451845 "sk-kept",
18461846 );
1847 }).pipe(Effect.provide(settingsLayer));
1847 }).pipe(Effect.provide(layerSettings));
18481848 },
18491849 );
18501850
......@@ -1928,7 +1928,7 @@ it.layer(NodeServices.layer)("server settings", (it) => {
19281928 );
19291929 assert.isTrue(persisted.projectSettingsFolded);
19301930 assert.isUndefined(persisted.projectSettingsOverrides[legacyProject]);
1931 }).pipe(Effect.provide(makeServerSettingsLayer())),
1931 }).pipe(Effect.provide(layerServerSettings())),
19321932 );
19331933
19341934 it.effect("leaves an unreadable settings.json untouched instead of folding over it", () =>
......@@ -1954,6 +1954,6 @@ it.layer(NodeServices.layer)("server settings", (it) => {
19541954 assert.deepEqual(settings.projectSettingsOverrides, {});
19551955 // The user's file is still there to repair; nothing was written over it.
19561956 assert.equal(yield* fileSystem.readFileString(serverConfig.settingsPath), broken);
1957 }).pipe(Effect.provide(makeServerSettingsLayer())),
1957 }).pipe(Effect.provide(layerServerSettings())),
19581958 );
19591959});
apps/server/src/sourceControl/AzureDevOpsCli.test.ts+2-2
......@@ -21,13 +21,13 @@ const processOutput = (stdout: string): VcsProcess.VcsProcessOutput => ({
2121
2222const mockRun = vi.fn<VcsProcess.VcsProcess["Service"]["run"]>();
2323
24const supportLayer = Layer.mergeAll(
24const layerSupport = Layer.mergeAll(
2525 Layer.mock(VcsProcess.VcsProcess)({
2626 run: mockRun,
2727 }),
2828 NodeServices.layer,
2929);
30const layer = Layer.mergeAll(AzureDevOpsCli.layer.pipe(Layer.provide(supportLayer)), supportLayer);
30const layer = Layer.mergeAll(AzureDevOpsCli.layer.pipe(Layer.provide(layerSupport)), layerSupport);
3131
3232afterEach(() => {
3333 mockRun.mockReset();
apps/server/src/sourceControl/PrTemplateDetection.test.ts+2-2
......@@ -27,7 +27,7 @@ const TEMPLATE_DIRECTORIES = [
2727 "docs/PULL_REQUEST_TEMPLATE",
2828] as const;
2929
30const PrTemplateDetectionTestLayer = GitVcsDriver.layer.pipe(
30const layerPrTemplateDetectionTest = GitVcsDriver.layer.pipe(
3131 Layer.provide(
3232 ServerConfig.layerTest(process.cwd(), {
3333 prefix: "t3-pr-template-test-",
......@@ -59,7 +59,7 @@ const runWithTempDirectory = <A, E, R>(
5959 yield* runGit(cwd, ["config", "user.name", "Test User"]);
6060 return yield* test(cwd);
6161 }),
62 ).pipe(Effect.provide(PrTemplateDetectionTestLayer));
62 ).pipe(Effect.provide(layerPrTemplateDetectionTest));
6363
6464const writeTemplate = (cwd: string, relativePath: string, contents: string) =>
6565 Effect.gen(function* () {
apps/server/src/sourceControl/SourceControlDiscovery.test.ts+7-7
......@@ -25,7 +25,7 @@ import * as SourceControlDiscovery from "./SourceControlDiscovery.ts";
2525import * as SourceControlProviderRegistry from "./SourceControlProviderRegistry.ts";
2626import { firstNonEmptyLine } from "./SourceControlProviderDiscovery.ts";
2727
28const sourceControlProviderRegistryTestLayer = (input: {
28const layerSourceControlProviderRegistryTest = (input: {
2929 readonly bitbucket: Partial<BitbucketApi.BitbucketApi["Service"]>;
3030 readonly process: Partial<VcsProcess.VcsProcess["Service"]>;
3131}) =>
......@@ -530,7 +530,7 @@ it.effect("reports implemented tools separately from locally available executabl
530530 );
531531 },
532532 } satisfies Partial<VcsProcess.VcsProcess["Service"]>;
533 const testLayer = SourceControlDiscovery.layer.pipe(
533 const layerTest = SourceControlDiscovery.layer.pipe(
534534 Layer.provide(
535535 ServerConfig.layerTest(process.cwd(), {
536536 prefix: "t3-source-control-discovery-",
......@@ -538,7 +538,7 @@ it.effect("reports implemented tools separately from locally available executabl
538538 ),
539539 Layer.provide(Layer.mock(VcsProcess.VcsProcess)(processMock)),
540540 Layer.provide(
541 sourceControlProviderRegistryTestLayer({
541 layerSourceControlProviderRegistryTest({
542542 process: processMock,
543543 bitbucket: {
544544 probeAuth: Effect.succeed({
......@@ -613,7 +613,7 @@ it.effect("reports implemented tools separately from locally available executabl
613613 const bitbucket = result.sourceControlProviders.find((item) => item.kind === "bitbucket");
614614 assert.ok(bitbucket);
615615 assert.strictEqual(bitbucket.executable, undefined);
616 }).pipe(Effect.provide(testLayer));
616 }).pipe(Effect.provide(layerTest));
617617});
618618
619619it.effect("probes provider authentication without exposing token details", () => {
......@@ -681,7 +681,7 @@ Logged in to gitlab.com as gitlab-user
681681 );
682682 },
683683 } satisfies Partial<VcsProcess.VcsProcess["Service"]>;
684 const testLayer = SourceControlDiscovery.layer.pipe(
684 const layerTest = SourceControlDiscovery.layer.pipe(
685685 Layer.provide(
686686 ServerConfig.layerTest(process.cwd(), {
687687 prefix: "t3-source-control-auth-discovery-",
......@@ -689,7 +689,7 @@ Logged in to gitlab.com as gitlab-user
689689 ),
690690 Layer.provide(Layer.mock(VcsProcess.VcsProcess)(processMock)),
691691 Layer.provide(
692 sourceControlProviderRegistryTestLayer({
692 layerSourceControlProviderRegistryTest({
693693 process: processMock,
694694 bitbucket: {
695695 probeAuth: Effect.succeed({
......@@ -748,7 +748,7 @@ Logged in to gitlab.com as gitlab-user
748748 },
749749 ],
750750 );
751 }).pipe(Effect.provide(testLayer));
751 }).pipe(Effect.provide(layerTest));
752752});
753753
754754it.effect("discovers Forgejo accounts and retains the server port", () =>
apps/server/src/sourceControl/SourceControlProviderRegistry.test.ts+4-4
......@@ -60,7 +60,7 @@ function makeRegistry(input: {
6060 }),
6161 } satisfies Partial<VcsDriver.VcsDriver["Service"]>;
6262
63 const registryLayer = Layer.mock(VcsDriverRegistry.VcsDriverRegistry)({
63 const layerRegistry = Layer.mock(VcsDriverRegistry.VcsDriverRegistry)({
6464 get: () => Effect.succeed(driver as unknown as VcsDriver.VcsDriver["Service"]),
6565 resolve:
6666 input.resolve ??
......@@ -81,7 +81,7 @@ function makeRegistry(input: {
8181 })),
8282 });
8383
84 const processLayer = Layer.mock(VcsProcess.VcsProcess)({
84 const layerProcess = Layer.mock(VcsProcess.VcsProcess)({
8585 run: () => Effect.succeed(processOutput("")),
8686 ...input.process,
8787 });
......@@ -90,8 +90,8 @@ function makeRegistry(input: {
9090 Effect.provide(
9191 Layer.mergeAll(
9292 NodeServices.layer,
93 registryLayer,
94 processLayer,
93 layerRegistry,
94 layerProcess,
9595 Layer.mock(AzureDevOpsCli.AzureDevOpsCli)({}),
9696 Layer.mock(BitbucketApi.BitbucketApi)({}),
9797 Layer.mock(GitHubCli.GitHubCli)(input.github ?? {}),
apps/server/src/sourceControl/SourceControlRepositoryService.test.ts+15-15
......@@ -54,12 +54,12 @@ function processOutput(): GitVcsDriver.ExecuteGitResult {
5454 };
5555}
5656
57function makeLayer(input: {
57function layer(input: {
5858 readonly provider?: SourceControlProvider.SourceControlProvider["Service"];
5959 readonly git?: Partial<GitVcsDriver.GitVcsDriver["Service"]>;
6060 readonly fileSystem?: FileSystem.FileSystem;
6161}) {
62 const serviceLayer = SourceControlRepositoryService.layer.pipe(
62 const layerService = SourceControlRepositoryService.layer.pipe(
6363 Layer.provide(
6464 Layer.mock(SourceControlProviderRegistry.SourceControlProviderRegistry)({
6565 resolveLink: () => undefined,
......@@ -89,11 +89,11 @@ function makeLayer(input: {
8989 );
9090
9191 return input.fileSystem
92 ? serviceLayer.pipe(
92 ? layerService.pipe(
9393 Layer.provide(Layer.succeed(FileSystem.FileSystem, input.fileSystem)),
9494 Layer.provideMerge(NodePath.layer),
9595 )
96 : serviceLayer.pipe(Layer.provideMerge(NodeServices.layer));
96 : layerService.pipe(Layer.provideMerge(NodeServices.layer));
9797}
9898
9999it.effect("looks up repositories through the requested provider without search", () => {
......@@ -116,7 +116,7 @@ it.effect("looks up repositories through the requested provider without search",
116116
117117 assert.deepStrictEqual(result, { provider: "github", ...CLONE_URLS });
118118 assert.deepStrictEqual(calls, [{ cwd: "/workspace", repository: "octocat/t3code" }]);
119 }).pipe(Effect.provide(makeLayer({ provider })));
119 }).pipe(Effect.provide(layer({ provider })));
120120});
121121
122122it.effect("preserves provider failures without deriving the repository message from them", () => {
......@@ -149,7 +149,7 @@ it.effect("preserves provider failures without deriving the repository message f
149149 "Source control repository operation lookupRepository failed for github: The source control operation could not be completed.",
150150 );
151151 assert.strictEqual(error.cause, providerCause);
152 }).pipe(Effect.provide(makeLayer({ provider })));
152 }).pipe(Effect.provide(layer({ provider })));
153153});
154154
155155it.effect("clones a looked-up repository into the requested destination", () =>
......@@ -184,7 +184,7 @@ it.effect("clones a looked-up repository into the requested destination", () =>
184184 ]);
185185 }).pipe(
186186 Effect.provide(
187 makeLayer({
187 layer({
188188 git: {
189189 execute: (input) =>
190190 Effect.sync(() => {
......@@ -226,7 +226,7 @@ it.effect("reports clone progress from git's stderr and keeps its error text on
226226 );
227227 }).pipe(
228228 Effect.provide(
229 makeLayer({
229 layer({
230230 git: {
231231 execute: (input) =>
232232 Effect.gen(function* () {
......@@ -274,7 +274,7 @@ it.effect("strips embedded credentials from the remote URL it reports", () =>
274274 });
275275 }).pipe(
276276 Effect.provide(
277 makeLayer({
277 layer({
278278 git: {
279279 execute: (input) =>
280280 Effect.sync(() => {
......@@ -316,7 +316,7 @@ it.effect("discards only a directory git wrote to", () =>
316316 assert.include(replacedError.detail, "could not be inspected");
317317 // A destination that never got created is nothing to discard.
318318 yield* service.discardClone(path.join(parent, "missing"));
319 }).pipe(Effect.provide(makeLayer({})));
319 }).pipe(Effect.provide(layer({})));
320320
321321 // The partial clone is emptied but its directory (the workspace root) stays.
322322 assert.deepStrictEqual(yield* fs.readDirectory(partial), []);
......@@ -342,7 +342,7 @@ it.effect("redacts query tokens and userinfo containing '@' from reported URLs",
342342 destinationPath: path.join(parent, "b"),
343343 });
344344 assert.equal(nested.remoteUrl, "https://github.com/octocat/t3code.git");
345 }).pipe(Effect.provide(makeLayer({})));
345 }).pipe(Effect.provide(layer({})));
346346 }).pipe(Effect.provide(NodeServices.layer)),
347347);
348348
......@@ -368,7 +368,7 @@ it.effect("preserves destination probe failures instead of treating them as miss
368368 assert.strictEqual(error.cause, fileSystemCause);
369369 }).pipe(
370370 Effect.provide(
371 makeLayer({
371 layer({
372372 fileSystem: FileSystem.makeNoop({
373373 exists: () => Effect.fail(fileSystemCause),
374374 makeDirectory: () => Effect.void,
......@@ -422,7 +422,7 @@ it.effect("publishes by creating the repository, adding a remote, and pushing up
422422 assert.deepStrictEqual(pushCalls, [{ cwd: "/workspace", remoteName: "origin" }]);
423423 }).pipe(
424424 Effect.provide(
425 makeLayer({
425 layer({
426426 provider,
427427 git: {
428428 ensureRemote: (input) =>
......@@ -464,7 +464,7 @@ it.effect("publishes to the remote name returned by ensureRemote", () => {
464464 assert.deepStrictEqual(pushCalls, [{ cwd: "/workspace", remoteName: "origin-1" }]);
465465 }).pipe(
466466 Effect.provide(
467 makeLayer({
467 layer({
468468 git: {
469469 ensureRemote: () => Effect.succeed("origin-1"),
470470 pushCurrentBranch: (cwd, _fallbackBranch, options) =>
......@@ -506,7 +506,7 @@ it.effect("publish succeeds with status remote_added when the local repo has no
506506 assert.strictEqual(pushCalls, 0);
507507 }).pipe(
508508 Effect.provide(
509 makeLayer({
509 layer({
510510 git: {
511511 execute: (input) =>
512512 input.args[0] === "rev-parse"
apps/server/src/telemetry/AnalyticsService.test.ts+20-20
......@@ -61,7 +61,7 @@ const SentBatch = Schema.fromJsonString(
6161 * before the response arrived. PostHog stores these batches, so the server
6262 * must not send them forever.
6363 */
64const acceptThenFailClient = (batches: Array<ReadonlyArray<{ readonly uuid: string }>>) =>
64const layerAcceptThenFailClient = (batches: Array<ReadonlyArray<{ readonly uuid: string }>>) =>
6565 Layer.succeed(
6666 HttpClient.HttpClient,
6767 HttpClient.make((request) =>
......@@ -90,7 +90,7 @@ it.layer(NodeServices.layer)("AnalyticsService test", (it) => {
9090 it.effect("a batch that keeps failing is retried with backoff, then dropped", () =>
9191 Effect.gen(function* () {
9292 const batches: Array<ReadonlyArray<{ readonly uuid: string }>> = [];
93 const runtimeLayer = AnalyticsService.layer.pipe(
93 const layerRuntime = AnalyticsService.layer.pipe(
9494 Layer.provideMerge(
9595 ServerConfig.ServerConfig.layerTest(process.cwd(), { prefix: "t3-telemetry-retry-" }),
9696 ),
......@@ -107,7 +107,7 @@ it.layer(NodeServices.layer)("AnalyticsService test", (it) => {
107107 Layer.mergeAll(
108108 Layer.succeed(HostProcessPlatform, "win32"),
109109 Layer.succeed(HostProcessArchitecture, "x64"),
110 acceptThenFailClient(batches),
110 layerAcceptThenFailClient(batches),
111111 ),
112112 ),
113113 );
......@@ -121,7 +121,7 @@ it.layer(NodeServices.layer)("AnalyticsService test", (it) => {
121121 for (let second = 0; second < 600; second += 1) {
122122 yield* TestClock.adjust("1 second");
123123 }
124 }).pipe(Effect.provide(runtimeLayer));
124 }).pipe(Effect.provide(layerRuntime));
125125
126126 assert.equal(batches.length, 5);
127127 const uuids = batches.map((batch) => batch.map((event) => event.uuid).join(","));
......@@ -133,12 +133,12 @@ it.layer(NodeServices.layer)("AnalyticsService test", (it) => {
133133 it.effect("flush drains all buffered events across multiple batches", () =>
134134 Effect.gen(function* () {
135135 const capturedRequests: Array<RecordedBatchRequest> = [];
136 const serverConfigLayer = ServerConfig.ServerConfig.layerTest(process.cwd(), {
136 const layerServerConfig = ServerConfig.ServerConfig.layerTest(process.cwd(), {
137137 prefix: "t3-telemetry-base-",
138138 });
139139
140 const telemetryLayer = AnalyticsService.layer.pipe(Layer.provideMerge(serverConfigLayer));
141 const configLayer = ConfigProvider.layer(
140 const layerTelemetry = AnalyticsService.layer.pipe(Layer.provideMerge(layerServerConfig));
141 const layerConfig = ConfigProvider.layer(
142142 ConfigProvider.fromUnknown({
143143 T3CODE_TELEMETRY_ENABLED: true,
144144 T3CODE_POSTHOG_KEY: "phc_test_key",
......@@ -146,7 +146,7 @@ it.layer(NodeServices.layer)("AnalyticsService test", (it) => {
146146 T3CODE_TELEMETRY_FLUSH_BATCH_SIZE: 20,
147147 }),
148148 );
149 const batchServerLayer = HttpServer.serve(
149 const layerBatchServer = HttpServer.serve(
150150 Effect.gen(function* () {
151151 const request = yield* HttpServerRequest.HttpServerRequest;
152152 if (request.method !== "POST") {
......@@ -163,8 +163,8 @@ it.layer(NodeServices.layer)("AnalyticsService test", (it) => {
163163 return HttpServerResponse.jsonUnsafe({});
164164 }),
165165 );
166 const runtimeLayer = telemetryLayer.pipe(
167 Layer.provide(configLayer),
166 const layerRuntime = layerTelemetry.pipe(
167 Layer.provide(layerConfig),
168168 Layer.provide(
169169 Layer.mergeAll(
170170 Layer.succeed(HostProcessPlatform, "linux"),
......@@ -175,7 +175,7 @@ it.layer(NodeServices.layer)("AnalyticsService test", (it) => {
175175 );
176176
177177 yield* Effect.gen(function* () {
178 yield* Layer.launch(batchServerLayer).pipe(Effect.forkScoped);
178 yield* Layer.launch(layerBatchServer).pipe(Effect.forkScoped);
179179 const telemetryIdentifier = yield* getTelemetryIdentifier;
180180 assert.equal(telemetryIdentifier !== null, true);
181181 const analytics = yield* AnalyticsService.AnalyticsService;
......@@ -185,7 +185,7 @@ it.layer(NodeServices.layer)("AnalyticsService test", (it) => {
185185 }
186186
187187 yield* analytics.flush;
188 }).pipe(Effect.provide(runtimeLayer));
188 }).pipe(Effect.provide(layerRuntime));
189189
190190 const batchRequests = capturedRequests.filter(
191191 (request): request is RecordedBatchRequest & { readonly body: RecordedBatchBody } =>
......@@ -235,26 +235,26 @@ it.layer(NodeServices.layer)("AnalyticsService test", (it) => {
235235 it.effect("does not send batch requests when telemetry is disabled", () =>
236236 Effect.gen(function* () {
237237 const capturedPaths: Array<string> = [];
238 const serverConfigLayer = ServerConfig.ServerConfig.layerTest(process.cwd(), {
238 const layerServerConfig = ServerConfig.ServerConfig.layerTest(process.cwd(), {
239239 prefix: "t3-telemetry-disabled-",
240240 });
241 const telemetryLayer = AnalyticsService.layer.pipe(Layer.provideMerge(serverConfigLayer));
242 const configLayer = ConfigProvider.layer(
241 const layerTelemetry = AnalyticsService.layer.pipe(Layer.provideMerge(layerServerConfig));
242 const layerConfig = ConfigProvider.layer(
243243 ConfigProvider.fromUnknown({
244244 T3CODE_TELEMETRY_ENABLED: false,
245245 T3CODE_POSTHOG_KEY: "phc_test_key",
246246 T3CODE_POSTHOG_HOST: "http://localhost",
247247 }),
248248 );
249 const batchServerLayer = HttpServer.serve(
249 const layerBatchServer = HttpServer.serve(
250250 Effect.gen(function* () {
251251 const request = yield* HttpServerRequest.HttpServerRequest;
252252 capturedPaths.push(request.url);
253253 return HttpServerResponse.jsonUnsafe({});
254254 }),
255255 );
256 const runtimeLayer = telemetryLayer.pipe(
257 Layer.provide(configLayer),
256 const layerRuntime = layerTelemetry.pipe(
257 Layer.provide(layerConfig),
258258 Layer.provide(
259259 Layer.mergeAll(
260260 Layer.succeed(HostProcessPlatform, "linux"),
......@@ -265,11 +265,11 @@ it.layer(NodeServices.layer)("AnalyticsService test", (it) => {
265265 );
266266
267267 yield* Effect.gen(function* () {
268 yield* Layer.launch(batchServerLayer).pipe(Effect.forkScoped);
268 yield* Layer.launch(layerBatchServer).pipe(Effect.forkScoped);
269269 const analytics = yield* AnalyticsService.AnalyticsService;
270270 yield* analytics.record("test.disabled", { index: 1 });
271271 yield* analytics.flush;
272 }).pipe(Effect.provide(runtimeLayer));
272 }).pipe(Effect.provide(layerRuntime));
273273
274274 assert.deepEqual(capturedPaths, []);
275275 }),
apps/server/src/terminal/Manager.test.ts+15-12
......@@ -37,7 +37,7 @@ import { expect } from "vite-plus/test";
3737
3838import * as ServerSecretStore from "../auth/ServerSecretStore.ts";
3939import * as ServerConfig from "../config.ts";
40import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts";
40import * as SqlitePersistence from "../persistence/Layers/Sqlite.ts";
4141import * as ProcessRunner from "../processRunner.ts";
4242import * as ServerSettings from "../serverSettings.ts";
4343import * as TerminalManager from "./Manager.ts";
......@@ -314,7 +314,7 @@ const createManager = (
314314 }),
315315 );
316316
317const withHostPlatform = (platform: NodeJS.Platform) =>
317const layerWithHostPlatform = (platform: NodeJS.Platform) =>
318318 Layer.succeed(HostProcessPlatform, platform);
319319
320320// Apply the existing line policy, then find the longest code-point-aligned byte tail.
......@@ -1153,7 +1153,7 @@ it.layer(
11531153 subprocessPollIntervalMs: 20,
11541154 }).pipe(
11551155 Effect.provideService(ProcessRunner.ProcessRunner, processRunner),
1156 Effect.provide(withHostPlatform("linux")),
1156 Effect.provide(layerWithHostPlatform("linux")),
11571157 );
11581158
11591159 yield* manager.open(openInput());
......@@ -1214,7 +1214,7 @@ it.layer(
12141214 subprocessPollIntervalMs: 20,
12151215 }).pipe(
12161216 Effect.provideService(ProcessRunner.ProcessRunner, processRunner),
1217 Effect.provide(withHostPlatform("linux")),
1217 Effect.provide(layerWithHostPlatform("linux")),
12181218 );
12191219
12201220 yield* manager.open(openInput());
......@@ -1259,7 +1259,7 @@ it.layer(
12591259 snapshotCalls += 1;
12601260 return [{ pid: 100, ppid: 9000, name: "ping.exe" }];
12611261 }),
1262 }).pipe(Effect.provide(withHostPlatform("win32")));
1262 }).pipe(Effect.provide(layerWithHostPlatform("win32")));
12631263
12641264 yield* manager.open(openInput());
12651265 yield* waitFor(
......@@ -1291,7 +1291,7 @@ it.layer(
12911291 { pid: 301, ppid: 300, name: "sleep" },
12921292 { pid: 9003, ppid: 1, name: "zsh" },
12931293 ]),
1294 }).pipe(Effect.provide(withHostPlatform("linux")));
1294 }).pipe(Effect.provide(layerWithHostPlatform("linux")));
12951295 yield* manager.open(openInput({ terminalId: "idle" }));
12961296 yield* manager.open(openInput({ terminalId: "dev-server" }));
12971297 yield* manager.open(openInput({ terminalId: "subshell" }));
......@@ -1371,7 +1371,7 @@ it.layer(
13711371 ),
13721372 }).pipe(
13731373 Effect.provideService(ProcessRunner.ProcessRunner, processRunner),
1374 Effect.provide(withHostPlatform("linux")),
1374 Effect.provide(layerWithHostPlatform("linux")),
13751375 );
13761376
13771377 yield* manager.open(openInput());
......@@ -1869,7 +1869,7 @@ it.layer(
18691869 PATH: "C:\\Windows\\System32",
18701870 SystemRoot: "C:\\Windows",
18711871 },
1872 }).pipe(Effect.provide(withHostPlatform("win32")));
1872 }).pipe(Effect.provide(layerWithHostPlatform("win32")));
18731873
18741874 yield* manager.open(openInput());
18751875
......@@ -1931,7 +1931,10 @@ it.layer(
19311931 },
19321932 }).pipe(
19331933 Effect.provide(
1934 Layer.merge(withHostPlatform("win32"), Layer.succeed(HostProcessArchitecture, "x64")),
1934 Layer.merge(
1935 layerWithHostPlatform("win32"),
1936 Layer.succeed(HostProcessArchitecture, "x64"),
1937 ),
19351938 ),
19361939 );
19371940
......@@ -1954,7 +1957,7 @@ it.layer(
19541957 PATH: "C:\\Windows\\System32",
19551958 SystemRoot: "C:\\Windows",
19561959 },
1957 }).pipe(Effect.provide(withHostPlatform("win32")));
1960 }).pipe(Effect.provide(layerWithHostPlatform("win32")));
19581961 ptyAdapter.spawnFailures.push(
19591962 new Error("spawn custom-shell.exe ENOENT"),
19601963 new Error("spawn pwsh.exe ENOENT"),
......@@ -1988,7 +1991,7 @@ it.layer(
19881991 const { manager, ptyAdapter } = yield* createManager(5, {
19891992 shellResolver: () => "/bin/sh",
19901993 env,
1991 }).pipe(Effect.provide(withHostPlatform(platform)));
1994 }).pipe(Effect.provide(layerWithHostPlatform(platform)));
19921995 yield* manager.open(
19931996 openInput({ env: runtimeColor === undefined ? {} : { COLORTERM: runtimeColor } }),
19941997 );
......@@ -2447,7 +2450,7 @@ it.layer(
24472450 Effect.provide(
24482451 ServerSettings.layer.pipe(
24492452 Layer.provide(ServerSecretStore.layer),
2450 Layer.provide(SqlitePersistenceMemory),
2453 Layer.provide(SqlitePersistence.layerMemory),
24512454 Layer.provide(
24522455 ServerConfig.layerTest(process.cwd(), { prefix: "t3code-terminal-provider-restart-" }),
24532456 ),
apps/server/src/terminal/NodePtyAdapter.test.ts+12-12
......@@ -61,7 +61,7 @@ const spawnInput = { shell: "powershell.exe", cwd: ".", cols: 80, rows: 24, env:
6161
6262const fakeNodePty = { spawn } as unknown as typeof import("node-pty");
6363
64const makeTestLayer = (platform: NodeJS.Platform = "win32") =>
64const layerTestFor = (platform: NodeJS.Platform = "win32") =>
6565 NodePtyAdapter.layer.pipe(
6666 Layer.provide(
6767 Layer.mergeAll(
......@@ -73,7 +73,7 @@ const makeTestLayer = (platform: NodeJS.Platform = "win32") =>
7373 ),
7474 );
7575
76const testLayer = makeTestLayer();
76const layerTest = layerTestFor();
7777
7878it.effect("waits for the Windows PID without requiring output", () =>
7979 Effect.gen(function* () {
......@@ -107,7 +107,7 @@ it.effect("waits for the Windows PID without requiring output", () =>
107107 assert.deepEqual(exits, [{ exitCode: 0, signal: null }]);
108108 stopData();
109109 stopExit();
110 }).pipe(Effect.provide(testLayer)),
110 }).pipe(Effect.provide(layerTest)),
111111);
112112
113113it.effect.each(["exit", "close", "error", "invalid-pid"] as const)(
......@@ -129,7 +129,7 @@ it.effect.each(["exit", "close", "error", "invalid-pid"] as const)(
129129 assert.equal(nativeProcess._socket.listenerCount("close"), 0);
130130 assert.equal(nativeProcess.events.listenerCount("exit"), 0);
131131 assert.equal(nativeProcess._agent.kill.mock.calls.length, 1);
132 }).pipe(Effect.provide(testLayer)),
132 }).pipe(Effect.provide(layerTest)),
133133);
134134
135135it.effect("cancels the Windows connection without waiting for output", () =>
......@@ -143,7 +143,7 @@ it.effect("cancels the Windows connection without waiting for output", () =>
143143 assert.equal(nativeProcess.kill.mock.calls.length, 0);
144144 assert.equal(nativeProcess._socket.listenerCount("ready_datapipe"), 0);
145145 assert.equal(nativeProcess.events.listenerCount("exit"), 0);
146 }).pipe(Effect.provide(testLayer)),
146 }).pipe(Effect.provide(layerTest)),
147147);
148148
149149it.effect("reports an incompatible Windows readiness API instead of hanging", () =>
......@@ -157,7 +157,7 @@ it.effect("reports an incompatible Windows readiness API instead of hanging", ()
157157 assert.instanceOf(error.cause, Error);
158158 assert.equal(error.cause.message, "Windows PTY readiness socket is unavailable.");
159159 assert.equal(nativeProcess._agent.kill.mock.calls.length, 1);
160 }).pipe(Effect.provide(testLayer)),
160 }).pipe(Effect.provide(layerTest)),
161161);
162162
163163it.effect.each(["win32", "linux", "darwin"] as const)(
......@@ -189,7 +189,7 @@ it.effect.each(["win32", "linux", "darwin"] as const)(
189189 ? [[undefined], [undefined], [undefined]]
190190 : [["SIGTERM"], ["SIGKILL"], [undefined]],
191191 );
192 }).pipe(Effect.provide(makeTestLayer(platform))),
192 }).pipe(Effect.provide(layerTestFor(platform))),
193193);
194194
195195it.effect("spawns through the public adapter with the provided host references", () =>
......@@ -218,7 +218,7 @@ it.effect("spawns through the public adapter with the provided host references",
218218 name: "xterm-256color",
219219 },
220220 ]);
221 }).pipe(Effect.provide(testLayer)),
221 }).pipe(Effect.provide(layerTest)),
222222);
223223
224224it.effect("preserves a caller-provided TERM in the spawn env on win32", () =>
......@@ -245,7 +245,7 @@ it.effect("preserves a caller-provided TERM in the spawn env on win32", () =>
245245 name: "xterm-256color",
246246 },
247247 ]);
248 }).pipe(Effect.provide(testLayer)),
248 }).pipe(Effect.provide(layerTest)),
249249);
250250
251251it.effect("reports native module load failures as structured startup defects", () =>
......@@ -300,7 +300,7 @@ it.effect.each([2048, 8])(
300300 nativeProcess.events.emit("exit", { exitCode: 0 });
301301 yield* Fiber.join(fiber);
302302 assert.equal(exits.length, 1);
303 }).pipe(Effect.provide(testLayer)),
303 }).pipe(Effect.provide(layerTest)),
304304);
305305
306306it.effect("replays an exit to late subscribers and respects unsubscription", () =>
......@@ -317,7 +317,7 @@ it.effect("replays an exit to late subscribers and respects unsubscription", ()
317317 assert.equal(removed.mock.calls.length, 0);
318318 assert.deepEqual(late.mock.calls, [[{ exitCode: 7, signal: 2 }]]);
319319 assert.equal(nativeProcess.events.listenerCount("exit"), 0);
320 }).pipe(Effect.provide(testLayer)),
320 }).pipe(Effect.provide(layerTest)),
321321);
322322
323323it.effect.each(["spawn", "interrupt"] as const)(
......@@ -361,5 +361,5 @@ it.effect.each(["spawn", "interrupt"] as const)(
361361 ]);
362362 assert.equal(nativeProcess.events.listenerCount("exit"), 0);
363363 assert.equal(nativeProcess._socket.listenerCount("ready_datapipe"), 0);
364 }).pipe(Effect.provide(testLayer)),
364 }).pipe(Effect.provide(layerTest)),
365365);
apps/server/src/textGeneration/ClaudeTextGeneration.test.ts+2-2
......@@ -24,7 +24,7 @@ import { makeClaudeTextGeneration } from "./ClaudeTextGeneration.ts";
2424import { writeFakeCli } from "../testUtils/fakeCli.ts";
2525const decodeClaudeSettings = Schema.decodeSync(ClaudeSettings);
2626
27const ClaudeTextGenerationTestLayer = ServerConfig.ServerConfig.layerTest(process.cwd(), {
27const layerClaudeTextGenerationTest = ServerConfig.ServerConfig.layerTest(process.cwd(), {
2828 prefix: "t3code-claude-text-generation-test-",
2929}).pipe(Layer.provideMerge(NodeServices.layer));
3030
......@@ -191,7 +191,7 @@ function withFakeClaudeEnv<A, E, R>(
191191 }).pipe(Effect.scoped);
192192}
193193
194it.layer(ClaudeTextGenerationTestLayer)("ClaudeTextGeneration", (it) => {
194it.layer(layerClaudeTextGenerationTest)("ClaudeTextGeneration", (it) => {
195195 it.effect("forwards Claude thinking settings without passing unsupported effort", () =>
196196 withFakeClaudeEnv(
197197 {
apps/server/src/textGeneration/CodexTextGeneration.test.ts+2-2
......@@ -24,7 +24,7 @@ const DEFAULT_TEST_MODEL_SELECTION = createModelSelection(
2424 "gpt-5.4-mini",
2525);
2626
27const CodexTextGenerationTestLayer = ServerConfig.ServerConfig.layerTest(process.cwd(), {
27const layerCodexTextGenerationTest = ServerConfig.ServerConfig.layerTest(process.cwd(), {
2828 prefix: "t3code-codex-text-generation-test-",
2929}).pipe(Layer.provideMerge(NodeServices.layer));
3030
......@@ -169,7 +169,7 @@ function withFakeCodexEnv<A, E, R>(
169169 }).pipe(Effect.scoped);
170170}
171171
172it.layer(CodexTextGenerationTestLayer)("CodexTextGeneration", (it) => {
172it.layer(layerCodexTextGenerationTest)("CodexTextGeneration", (it) => {
173173 it.effect.each(["gpt-5.6-luna", "openai.gpt-5.6-luna"])(
174174 "dispatches the qualified live model for %s",
175175 (selectedModel) =>
apps/server/src/textGeneration/CursorTextGeneration.test.ts+12-12
......@@ -27,7 +27,7 @@ const cursorSdkMock = vi.hoisted(() => ({
2727vi.mock("../provider/cursorSdk.ts", () => ({ Agent: { create: cursorSdkMock.create } }));
2828
2929let hasCustomPolicy = false;
30const fsLayer = FileSystem.layerNoop({
30const layerFs = FileSystem.layerNoop({
3131 exists: () => Effect.succeed(hasCustomPolicy),
3232 makeTempDirectoryScoped: () => Effect.succeed("/isolated-text-generation"),
3333});
......@@ -89,7 +89,7 @@ describe("CursorTextGeneration", () => {
8989 expect(cursorSdkMock.create).toHaveBeenLastCalledWith(
9090 expect.objectContaining({ apiKey: "second-browser-key" }),
9191 );
92 }).pipe(Effect.provide(fsLayer)),
92 }).pipe(Effect.provide(layerFs)),
9393 );
9494
9595 it.effect("uses the Cursor SDK prompt API with model parameters and API key", () =>
......@@ -138,7 +138,7 @@ describe("CursorTextGeneration", () => {
138138 enableAgentRetries: true,
139139 },
140140 });
141 }).pipe(Effect.provide(fsLayer)),
141 }).pipe(Effect.provide(layerFs)),
142142 );
143143
144144 it.effect("continues in the temp directory when the SDK cannot sandbox", () =>
......@@ -174,7 +174,7 @@ describe("CursorTextGeneration", () => {
174174 enableAgentRetries: true,
175175 },
176176 });
177 }).pipe(Effect.provide(fsLayer)),
177 }).pipe(Effect.provide(layerFs)),
178178 );
179179
180180 it.effect("does not retry Agent.create for errors other than an unsupported sandbox", () =>
......@@ -198,7 +198,7 @@ describe("CursorTextGeneration", () => {
198198
199199 expect(error.detail).toBe("Cursor SDK text generation failed.");
200200 expect(cursorSdkMock.create).toHaveBeenCalledTimes(1);
201 }).pipe(Effect.provide(fsLayer)),
201 }).pipe(Effect.provide(layerFs)),
202202 );
203203
204204 it.effect("accepts json objects with extra assistant text around them", () =>
......@@ -226,7 +226,7 @@ describe("CursorTextGeneration", () => {
226226
227227 expect(generated.subject).toBe("Update README dummy comment with attribution and date");
228228 expect(generated.body).toBe("");
229 }).pipe(Effect.provide(fsLayer)),
229 }).pipe(Effect.provide(layerFs)),
230230 );
231231
232232 it.effect("generates thread titles through Cursor SDK text generation", () =>
......@@ -250,7 +250,7 @@ describe("CursorTextGeneration", () => {
250250 });
251251
252252 expect(generated.title).toBe("Trim reconnect spinner status after resume.");
253 }).pipe(Effect.provide(fsLayer)),
253 }).pipe(Effect.provide(layerFs)),
254254 );
255255
256256 it.effect.each(["error", "cancelled"] as const)(
......@@ -284,7 +284,7 @@ describe("CursorTextGeneration", () => {
284284 : "Cursor SDK request finished with an error.",
285285 );
286286 expect(cursorSdkMock.close).toHaveBeenCalledOnce();
287 }).pipe(Effect.provide(fsLayer)),
287 }).pipe(Effect.provide(layerFs)),
288288 );
289289
290290 it.effect("fails closed when ambient sandbox policy can expand write access", () =>
......@@ -300,7 +300,7 @@ describe("CursorTextGeneration", () => {
300300 );
301301 expect(failure.detail).toContain("custom ~/.cursor/sandbox.json");
302302 expect(cursorSdkMock.prompt).not.toHaveBeenCalled();
303 }).pipe(Effect.provide(fsLayer)),
303 }).pipe(Effect.provide(layerFs)),
304304 );
305305
306306 it.effect("cancels the native run when text generation times out", () =>
......@@ -326,7 +326,7 @@ describe("CursorTextGeneration", () => {
326326 expect((yield* Fiber.join(result)).detail).toContain("timed out");
327327 expect(cursorSdkMock.cancel).toHaveBeenCalledOnce();
328328 expect(cursorSdkMock.close).toHaveBeenCalledOnce();
329 }).pipe(Effect.provide(fsLayer), Effect.scoped),
329 }).pipe(Effect.provide(layerFs), Effect.scoped),
330330 );
331331
332332 it.effect.each(["create", "send"] as const)(
......@@ -388,7 +388,7 @@ describe("CursorTextGeneration", () => {
388388 expect(cursorSdkMock.close).toHaveBeenCalledOnce();
389389 expect(wait).not.toHaveBeenCalled();
390390 if (phase === "send") expect(cursorSdkMock.cancel).toHaveBeenCalledOnce();
391 }).pipe(Effect.provide(fsLayer), Effect.scoped),
391 }).pipe(Effect.provide(layerFs), Effect.scoped),
392392 );
393393
394394 it.effect("requires CURSOR_API_KEY before calling the SDK", () =>
......@@ -410,6 +410,6 @@ describe("CursorTextGeneration", () => {
410410
411411 expect(error.detail).toBe("Sign in with Cursor or add CURSOR_API_KEY in provider settings.");
412412 expect(cursorSdkMock.prompt).not.toHaveBeenCalled();
413 }).pipe(Effect.provide(fsLayer)),
413 }).pipe(Effect.provide(layerFs)),
414414 );
415415});
apps/server/src/textGeneration/GrokTextGeneration.test.ts+2-2
......@@ -22,7 +22,7 @@ const decodeGrokSettings = Schema.decodeSync(GrokSettings);
2222const __dirname = NodePath.dirname(NodeURL.fileURLToPath(import.meta.url));
2323const mockAgentPath = NodePath.join(__dirname, "../provider/testFixtures/grok-text-mock-agent.mjs");
2424
25const GrokTextGenerationTestLayer = ServerConfig.ServerConfig.layerTest(process.cwd(), {
25const layerGrokTextGenerationTest = ServerConfig.ServerConfig.layerTest(process.cwd(), {
2626 prefix: "t3code-grok-text-generation-test-",
2727}).pipe(Layer.provideMerge(NodeServices.layer));
2828
......@@ -66,7 +66,7 @@ function readJsonRpcRequests(
6666 .map((line) => JSON.parse(line) as { method?: string; params?: Record<string, unknown> });
6767}
6868
69it.layer(GrokTextGenerationTestLayer)("GrokTextGeneration", (it) => {
69it.layer(layerGrokTextGenerationTest)("GrokTextGeneration", (it) => {
7070 it.effect("uses ACP with disabled tool capabilities and forwards the requested model id", () => {
7171 const requestLogDir = NodeFS.mkdtempSync(
7272 NodePath.join(NodeOS.tmpdir(), "t3code-grok-text-log-"),
apps/server/src/textGeneration/OpenCodeTextGeneration.test.ts+4-4
......@@ -162,7 +162,7 @@ const DEFAULT_COMMIT_MESSAGE_INPUT = {
162162
163163const OPENCODE_TEXT_GENERATION_IDLE_TTL_MS = 30_000;
164164
165const OpenCodeTextGenerationTestLayer = Layer.succeed(
165const layerOpenCodeTextGenerationTest = Layer.succeed(
166166 OpenCodeRuntime.OpenCodeRuntime,
167167 OpenCodeRuntimeTestDouble,
168168).pipe(
......@@ -175,7 +175,7 @@ const OpenCodeTextGenerationTestLayer = Layer.succeed(
175175 Layer.provideMerge(NodeServices.layer),
176176);
177177
178const OpenCodeTextGenerationExistingServerTestLayer = Layer.succeed(
178const layerOpenCodeTextGenerationExistingServerTest = Layer.succeed(
179179 OpenCodeRuntime.OpenCodeRuntime,
180180 OpenCodeRuntimeTestDouble,
181181).pipe(
......@@ -234,7 +234,7 @@ const advanceIdleClock = Effect.gen(function* () {
234234 yield* Effect.yieldNow;
235235});
236236
237it.layer(OpenCodeTextGenerationTestLayer)("OpenCodeTextGeneration", (it) => {
237it.layer(layerOpenCodeTextGenerationTest)("OpenCodeTextGeneration", (it) => {
238238 it.effect("excludes generic files from thread title generation", () =>
239239 withOpenCodeTextGeneration(DEFAULT_OPENCODE_SETTINGS, (textGeneration) =>
240240 Effect.gen(function* () {
......@@ -545,7 +545,7 @@ it.layer(OpenCodeTextGenerationTestLayer)("OpenCodeTextGeneration", (it) => {
545545 );
546546});
547547
548it.layer(OpenCodeTextGenerationExistingServerTestLayer)(
548it.layer(layerOpenCodeTextGenerationExistingServerTest)(
549549 "OpenCodeTextGeneration with configured server URL",
550550 (it) => {
551551 it.effect("does not send a local environment password to a configured server", () =>
apps/server/src/textGeneration/ThreadTitleLinks.test.ts+4-4
......@@ -9,7 +9,7 @@ import { SourceControlProviderError } from "@t3tools/contracts";
99import { resolveThreadTitleLinks } from "./ThreadTitleLinks.ts";
1010import * as SourceControlProviderRegistry from "../sourceControl/SourceControlProviderRegistry.ts";
1111
12const registry = Layer.mock(SourceControlProviderRegistry.SourceControlProviderRegistry);
12const layerRegistry = Layer.mock(SourceControlProviderRegistry.SourceControlProviderRegistry);
1313const encodeSubject = Schema.encodeSync(
1414 Schema.fromJsonString(Schema.Struct({ title: Schema.String, body: Schema.String })),
1515);
......@@ -26,7 +26,7 @@ it.effect(
2626 "https://docs.test/guide [https://forge.test/change/1] https://forge.test/change/1#discussion https://forge.test/change/1?view=full `https://forge.test/change/2` https://forge.test/change/2. https://forge.test/change/3",
2727 }).pipe(
2828 Effect.provide(
29 registry({
29 layerRegistry({
3030 resolveLink: ({ url, cwd }) =>
3131 url.host === "forge.test"
3232 ? Effect.sync(() => {
......@@ -58,7 +58,7 @@ it.effect("returns unavailable when a lookup times out while retaining successfu
5858 message: "https://forge.test/change/1 https://forge.test/change/2",
5959 }).pipe(
6060 Effect.provide(
61 registry({
61 layerRegistry({
6262 resolveLink: ({ url }) =>
6363 url.pathname.endsWith("1")
6464 ? Deferred.succeed(started, undefined).pipe(Effect.andThen(Effect.never))
......@@ -83,7 +83,7 @@ it.effect("keeps lookup failure out of generation and skips unlinked messages",
8383 ).toContain("unavailable");
8484 }).pipe(
8585 Effect.provide(
86 registry({
86 layerRegistry({
8787 resolveLink: () =>
8888 Effect.fail(
8989 new SourceControlProviderError({
apps/server/src/usage/UsageService.test.ts+24-30
......@@ -78,7 +78,7 @@ const setup = Effect.gen(function* () {
7878 };
7979});
8080
81const serviceLayers = (input: {
81const layerService = (input: {
8282 readonly prefix: string;
8383 readonly home: string;
8484 readonly settings: Parameters<typeof ServerSettings.layerTest>[0];
......@@ -197,7 +197,7 @@ describe("UsageService", () => {
197197 return yield* service.readSummary(WINDOW);
198198 }).pipe(
199199 Effect.provide(
200 serviceLayers({
200 layerService({
201201 prefix: "usage-managed-accounts",
202202 home,
203203 settings: {
......@@ -254,7 +254,7 @@ describe("UsageService", () => {
254254 for (const platform of ["linux", "win32", "darwin"] as const) {
255255 const service = yield* UsageService.make.pipe(
256256 Effect.provide(
257 serviceLayers({
257 layerService({
258258 prefix: `usage-service-cursor-no-login-${platform}`,
259259 home,
260260 settings,
......@@ -279,7 +279,7 @@ describe("UsageService", () => {
279279 });
280280 const service = yield* UsageService.make.pipe(
281281 Effect.provide(
282 serviceLayers({ prefix: "usage-service-cursor-invalid-login", home, settings }),
282 layerService({ prefix: "usage-service-cursor-invalid-login", home, settings }),
283283 ),
284284 );
285285 const summary = yield* service.readSummary(WINDOW);
......@@ -293,7 +293,7 @@ describe("UsageService", () => {
293293 const { settings, home } = yield* setup;
294294 const service = yield* UsageService.make.pipe(
295295 Effect.provide(
296 serviceLayers({
296 layerService({
297297 prefix: "usage-service-cursor-keychain-disabled",
298298 home,
299299 settings,
......@@ -339,7 +339,7 @@ describe("UsageService", () => {
339339 });
340340 const service = yield* UsageService.make.pipe(
341341 Effect.provide(
342 serviceLayers({
342 layerService({
343343 prefix: `usage-service-cursor-store-${index}`,
344344 home,
345345 settings,
......@@ -395,7 +395,7 @@ describe("UsageService", () => {
395395 }
396396 });
397397 const service = yield* UsageService.make.pipe(
398 Effect.provide(serviceLayers({ prefix: "usage-service-opencode", home, settings })),
398 Effect.provide(layerService({ prefix: "usage-service-opencode", home, settings })),
399399 );
400400 const summary = yield* service.readSummary(WINDOW);
401401 assert.strictEqual(summary.buckets[0]?.provider, "opencode");
......@@ -441,7 +441,7 @@ describe("UsageService", () => {
441441 });
442442 const service = yield* UsageService.make.pipe(
443443 Effect.provide(
444 serviceLayers({
444 layerService({
445445 prefix: "usage-service-aliased-roots-test",
446446 home,
447447 settings,
......@@ -521,7 +521,7 @@ describe("UsageService", () => {
521521 });
522522 const service = yield* UsageService.make.pipe(
523523 Effect.provide(
524 serviceLayers({
524 layerService({
525525 prefix: "usage-service-accounts-test",
526526 home,
527527 settings: {
......@@ -643,7 +643,7 @@ describe("UsageService", () => {
643643 );
644644 }).pipe(
645645 Effect.provide(
646 serviceLayers({
646 layerService({
647647 prefix: "usage-service-home-refresh-test",
648648 home,
649649 environment: { CLAUDE_CONFIG_DIR: NodePath.join(home, "host-ignored") },
......@@ -673,7 +673,7 @@ describe("UsageService", () => {
673673 yield* Effect.promise(() => NodeFSP.writeFile(transcript, claudeLine(1, 5)));
674674 const service = yield* UsageService.make.pipe(
675675 Effect.provide(
676 serviceLayers({
676 layerService({
677677 prefix: "usage-service-inherited-homes-test",
678678 home,
679679 environment: {
......@@ -748,7 +748,7 @@ describe("UsageService", () => {
748748 assert.deepStrictEqual(restored.buckets, original.buckets);
749749 }).pipe(
750750 Effect.provide(
751 serviceLayers({ prefix: "usage-service-price-overrides-test", home, settings }),
751 layerService({ prefix: "usage-service-price-overrides-test", home, settings }),
752752 ),
753753 );
754754 }).pipe(Effect.scoped),
......@@ -760,7 +760,7 @@ describe("UsageService", () => {
760760 yield* Effect.promise(() => NodeFSP.writeFile(transcript, claudeLine(1, 5)));
761761
762762 const service = yield* UsageService.make.pipe(
763 Effect.provide(serviceLayers({ prefix: "usage-service-grow-test", home, settings })),
763 Effect.provide(layerService({ prefix: "usage-service-grow-test", home, settings })),
764764 );
765765
766766 const first = yield* service.readSummary(WINDOW);
......@@ -812,7 +812,7 @@ describe("UsageService", () => {
812812 );
813813 }).pipe(
814814 Effect.provide(
815 serviceLayers({
815 layerService({
816816 prefix: "usage-service-large-record-test",
817817 home,
818818 settings,
......@@ -899,7 +899,7 @@ describe("UsageService", () => {
899899 );
900900 }).pipe(
901901 Effect.provide(
902 serviceLayers({
902 layerService({
903903 prefix: "usage-service-v4-upgrade-test",
904904 home,
905905 settings,
......@@ -995,7 +995,7 @@ describe("UsageService", () => {
995995 assert.strictEqual(outsideWindow.sources[0]?.distinctSessions, 0);
996996 }).pipe(
997997 Effect.provide(
998 serviceLayers({
998 layerService({
999999 prefix: "usage-service-cleanup-test",
10001000 home,
10011001 settings: { providers: { ...settings.providers, claudeAgent: { homePath: alias } } },
......@@ -1042,7 +1042,7 @@ describe("UsageService", () => {
10421042 const restored = yield* (yield* UsageService.make).readSummary(WINDOW);
10431043 assert.deepStrictEqual(restored.buckets, live.buckets);
10441044 }).pipe(
1045 Effect.provide(serviceLayers({ prefix: "usage-service-copy-order-test", home, settings })),
1045 Effect.provide(layerService({ prefix: "usage-service-copy-order-test", home, settings })),
10461046 );
10471047 }).pipe(Effect.scoped),
10481048 );
......@@ -1073,9 +1073,7 @@ describe("UsageService", () => {
10731073 );
10741074 assert.strictEqual(totalOutputTokens(yield* service.readSummary(WINDOW)), 12);
10751075 }).pipe(
1076 Effect.provide(
1077 serviceLayers({ prefix: "usage-service-stale-read-test", home, settings }),
1078 ),
1076 Effect.provide(layerService({ prefix: "usage-service-stale-read-test", home, settings })),
10791077 );
10801078 }).pipe(Effect.scoped),
10811079 );
......@@ -1119,7 +1117,7 @@ describe("UsageService", () => {
11191117 );
11201118 assert.strictEqual(totalOutputTokens(yield* service.readSummary(WINDOW)), 12);
11211119 }).pipe(
1122 Effect.provide(serviceLayers({ prefix: "usage-service-late-read-test", home, settings })),
1120 Effect.provide(layerService({ prefix: "usage-service-late-read-test", home, settings })),
11231121 );
11241122 }).pipe(Effect.scoped),
11251123 );
......@@ -1133,9 +1131,7 @@ describe("UsageService", () => {
11331131 await NodeFSP.utimes(transcript, lastWrite, lastWrite);
11341132 });
11351133 const service = yield* UsageService.make.pipe(
1136 Effect.provide(
1137 serviceLayers({ prefix: "usage-service-saved-window-test", home, settings }),
1138 ),
1134 Effect.provide(layerService({ prefix: "usage-service-saved-window-test", home, settings })),
11391135 );
11401136 const first = yield* service.readSummary(WINDOW);
11411137 yield* Effect.promise(() =>
......@@ -1215,7 +1211,7 @@ describe("UsageService", () => {
12151211 assert.strictEqual(original.buckets[0]?.costUsd, 0);
12161212 assert.closeTo(updated.buckets[0]?.costUsd ?? -1, 0.00006, 1e-12);
12171213 }).pipe(
1218 Effect.provide(serviceLayers({ prefix: "usage-service-price-race-test", home, settings })),
1214 Effect.provide(layerService({ prefix: "usage-service-price-race-test", home, settings })),
12191215 );
12201216 }).pipe(Effect.scoped),
12211217 );
......@@ -1228,7 +1224,7 @@ describe("UsageService", () => {
12281224 let ratesFetches = 0;
12291225 const service = yield* UsageService.make.pipe(
12301226 Effect.provide(
1231 serviceLayers({
1227 layerService({
12321228 prefix: "usage-service-flight-test",
12331229 home,
12341230 settings,
......@@ -1260,7 +1256,7 @@ describe("UsageService", () => {
12601256 let ratesFetches = 0;
12611257 const service = yield* UsageService.make.pipe(
12621258 Effect.provide(
1263 serviceLayers({
1259 layerService({
12641260 prefix: "usage-service-rates-refresh-test",
12651261 home,
12661262 settings,
......@@ -1298,9 +1294,7 @@ describe("UsageService", () => {
12981294 Effect.gen(function* () {
12991295 const { settings, home } = yield* setup;
13001296 const service = yield* UsageService.make.pipe(
1301 Effect.provide(
1302 serviceLayers({ prefix: "usage-service-interruption-test", home, settings }),
1303 ),
1297 Effect.provide(layerService({ prefix: "usage-service-interruption-test", home, settings })),
13041298 );
13051299
13061300 let orphanedAt: number | undefined;
apps/server/src/vcs/GitVcsDriver.test.ts+24-24
......@@ -24,16 +24,16 @@ import * as VcsDriverRegistry from "./VcsDriverRegistry.ts";
2424import * as VcsProcess from "./VcsProcess.ts";
2525import { runVcsDriverContractSuite } from "./testing/VcsDriverContractHarness.ts";
2626
27const ServerConfigLayer = ServerConfig.layerTest(process.cwd(), {
27const layerServerConfig = ServerConfig.layerTest(process.cwd(), {
2828 prefix: "t3-git-vcs-contract-",
2929});
30const GitContractLayer = Layer.mergeAll(GitVcsDriver.vcsLayer, GitVcsDriver.layer).pipe(
31 Layer.provide(ServerConfigLayer),
30const layerGitContract = Layer.mergeAll(GitVcsDriver.layerVcs, GitVcsDriver.layer).pipe(
31 Layer.provide(layerServerConfig),
3232 Layer.provideMerge(VcsProcess.layer),
3333 Layer.provideMerge(NodeServices.layer),
3434);
35const GitCaptureContractLayer = Layer.merge(
36 GitContractLayer,
35const layerGitCaptureContract = Layer.merge(
36 layerGitContract,
3737 ProcessRunner.layer.pipe(Layer.provide(NodeServices.layer)),
3838);
3939
......@@ -69,7 +69,7 @@ type GitContractError = GitCommandError | PlatformError.PlatformError;
6969runVcsDriverContractSuite<GitVcsDriver.GitVcsDriver, GitContractError>({
7070 name: "Git",
7171 kind: "git",
72 layer: GitContractLayer,
72 layer: layerGitContract,
7373 fixture: {
7474 createRepo: (cwd) =>
7575 Effect.gen(function* () {
......@@ -160,7 +160,7 @@ it.effect("checkpoint capture skips untracked nested repositories without a comm
160160 yield* fileSystem.readFileString(path.join(cwd, nested, "private.txt")),
161161 "nested\n",
162162 );
163 }).pipe(Effect.scoped, Effect.provide(GitContractLayer)),
163 }).pipe(Effect.scoped, Effect.provide(layerGitContract)),
164164);
165165
166166it.effect("checkpoint recovery discovers nested HEAD independently of inherited GIT_DIR", () =>
......@@ -188,7 +188,7 @@ it.effect("checkpoint recovery discovers nested HEAD independently of inherited
188188 assert.strictEqual((yield* git(["show", `${checkpointRef}:file.txt`])).stdout, "unstaged\n");
189189 assert.strictEqual((yield* git(["ls-tree", "-r", checkpointRef, "--", "empty"])).stdout, "");
190190 assert.deepEqual(yield* fs.readFile(path.join(cwd, ".git", "index")), originalIndex);
191 }).pipe(Effect.scoped, Effect.provide(GitContractLayer)),
191 }).pipe(Effect.scoped, Effect.provide(layerGitContract)),
192192);
193193
194194it.effect("checkpoint capture still fails when a clean filter rejects a file", () =>
......@@ -212,7 +212,7 @@ it.effect("checkpoint capture still fails when a clean filter rejects a file", (
212212 assert.strictEqual(result._tag, "Failure");
213213 assert.deepEqual(yield* fileSystem.readFile(path.join(cwd, ".git", "index")), originalIndex);
214214 assert.isFalse(yield* driver.checkpoints.hasCheckpointRef({ cwd, checkpointRef }));
215 }).pipe(Effect.scoped, Effect.provide(GitContractLayer)),
215 }).pipe(Effect.scoped, Effect.provide(layerGitContract)),
216216);
217217
218218it.effect("checkpoint capture refuses a truncated nested repository listing", () =>
......@@ -242,7 +242,7 @@ it.effect("checkpoint capture refuses a truncated nested repository listing", ()
242242
243243 assert.strictEqual(result._tag, "Failure");
244244 assert.isFalse(yield* driver.checkpoints.hasCheckpointRef({ cwd, checkpointRef }));
245 }).pipe(Effect.scoped, Effect.provide(GitContractLayer)),
245 }).pipe(Effect.scoped, Effect.provide(layerGitContract)),
246246);
247247
248248it.effect("checkpoint recovery refuses excessive candidates before probing", () =>
......@@ -283,7 +283,7 @@ it.effect("checkpoint recovery refuses excessive candidates before probing", ()
283283 if (result._tag === "Failure") assert.strictEqual(result.failure, stageError);
284284 assert.isFalse(yield* driver.checkpoints.hasCheckpointRef({ cwd, checkpointRef }));
285285 assert.deepEqual(yield* fs.readFile(path.join(cwd, ".git", "index")), originalIndex);
286 }).pipe(Effect.scoped, Effect.provide(GitContractLayer)),
286 }).pipe(Effect.scoped, Effect.provide(layerGitContract)),
287287);
288288
289289it.effect.each([
......@@ -389,7 +389,7 @@ it.effect.each([
389389 assert.isFalse(yield* fs.exists(`${index}.lock`));
390390 }
391391 assert.deepEqual(yield* fs.readFile(path.join(cwd, ".git", "index")), originalIndex);
392 }).pipe(Effect.scoped, Effect.provide(GitCaptureContractLayer)),
392 }).pipe(Effect.scoped, Effect.provide(layerGitCaptureContract)),
393393);
394394
395395it.effect.each(["discovery", "probe", "retry"] as const)(
......@@ -478,7 +478,7 @@ it.effect.each(["discovery", "probe", "retry"] as const)(
478478 assert.isFalse(yield* fs.exists(`${privateIndex!}.lock`));
479479 assert.isFalse(yield* driver.checkpoints.hasCheckpointRef({ cwd, checkpointRef }));
480480 assert.deepEqual(yield* fs.readFile(path.join(cwd, ".git", "index")), originalIndex);
481 }).pipe(Effect.scoped, Effect.provide(GitContractLayer)),
481 }).pipe(Effect.scoped, Effect.provide(layerGitContract)),
482482);
483483
484484it.effect("checkpoint recovery preserves interruption and removes the private index", () =>
......@@ -512,7 +512,7 @@ it.effect("checkpoint recovery preserves interruption and removes the private in
512512 assert.isDefined(privateIndex);
513513 assert.isFalse(yield* fs.exists(privateIndex!));
514514 assert.isFalse(yield* driver.checkpoints.hasCheckpointRef({ cwd, checkpointRef }));
515 }).pipe(Effect.scoped, Effect.provide(GitContractLayer)),
515 }).pipe(Effect.scoped, Effect.provide(layerGitContract)),
516516);
517517
518518it.effect("checkpoint capture does not rerun clean filters for unchanged indexed files", () =>
......@@ -545,7 +545,7 @@ it.effect("checkpoint capture does not rerun clean filters for unchanged indexed
545545 assert.strictEqual((yield* git(["show", `${checkpointRef}:file.txt`])).stdout, "changed\n");
546546 assert.strictEqual((yield* git(["show", `${checkpointRef}:stable.txt`])).stdout, "unchanged\n");
547547 assert.deepEqual(yield* fileSystem.readFile(path.join(cwd, ".git", "index")), originalIndex);
548 }).pipe(Effect.scoped, Effect.provide(GitContractLayer)),
548 }).pipe(Effect.scoped, Effect.provide(layerGitContract)),
549549);
550550
551551it.effect.each(
......@@ -643,7 +643,7 @@ it.effect.each(
643643 "working outside\n",
644644 );
645645 }
646 }).pipe(Effect.scoped, Effect.provide(GitContractLayer)),
646 }).pipe(Effect.scoped, Effect.provide(layerGitContract)),
647647);
648648
649649it.effect("checkpoint capture keeps the legacy path when Git lacks add --sparse", () =>
......@@ -683,7 +683,7 @@ it.effect("checkpoint capture keeps the legacy path when Git lacks add --sparse"
683683 yield* captureDriver.checkpoints.captureCheckpoint({ cwd, checkpointRef });
684684 assert.strictEqual((yield* git(["show", `${checkpointRef}:file.txt`])).stdout, "unstaged\n");
685685 assert.deepEqual(yield* fs.readFile(path.join(cwd, ".git/index")), originalIndex);
686 }).pipe(Effect.scoped, Effect.provide(GitContractLayer)),
686 }).pipe(Effect.scoped, Effect.provide(layerGitContract)),
687687);
688688
689689it.effect.each(["normal", "flags", "sparse"] as const)(
......@@ -737,7 +737,7 @@ it.effect.each(["normal", "flags", "sparse"] as const)(
737737 if (indexMode !== "flags")
738738 assert.strictEqual(yield* fs.readFileString(path.join(cwd, ".git/reads")), "");
739739 assert.deepEqual(yield* fs.readFile(path.join(cwd, ".git/index")), originalIndex);
740 }).pipe(Effect.scoped, Effect.provide(GitContractLayer)),
740 }).pipe(Effect.scoped, Effect.provide(layerGitContract)),
741741);
742742
743743it.effect.each([1_700_000_000, 1_700_000_000.9999])(
......@@ -767,7 +767,7 @@ it.effect.each([1_700_000_000, 1_700_000_000.9999])(
767767 assert.strictEqual((yield* git(["show", `${checkpointRef}:file.txt`])).stdout, "after!\n");
768768 assert.deepEqual(yield* fileSystem.readFile(indexPath), originalIndex);
769769 assert.deepEqual((yield* fileSystem.stat(indexPath)).mtime, originalIndexMtime);
770 }).pipe(Effect.scoped, Effect.provide(GitContractLayer)),
770 }).pipe(Effect.scoped, Effect.provide(layerGitContract)),
771771);
772772it.effect.each(
773773 [false, true].flatMap((nested) =>
......@@ -864,7 +864,7 @@ it.effect.each(
864864 "working outside\n",
865865 );
866866 }
867 }).pipe(Effect.scoped, Effect.provide(GitContractLayer)),
867 }).pipe(Effect.scoped, Effect.provide(layerGitContract)),
868868);
869869
870870it.effect("checkpoint capture preserves racy edits made after resetting the index", () =>
......@@ -907,7 +907,7 @@ it.effect("checkpoint capture preserves racy edits made after resetting the inde
907907 assert.strictEqual((yield* git(["show", `${checkpointRef}:file.txt`])).stdout, "staged\n");
908908 assert.deepEqual(yield* fileSystem.readFile(indexPath), originalIndex);
909909 assert.deepEqual((yield* fileSystem.stat(indexPath)).mtime, originalIndexMtime);
910 }).pipe(Effect.scoped, Effect.provide(GitContractLayer)),
910 }).pipe(Effect.scoped, Effect.provide(layerGitContract)),
911911);
912912
913913it.effect.each(
......@@ -985,7 +985,7 @@ it.effect.each(
985985 "added in second turn\n",
986986 );
987987 assert.deepEqual(yield* fileSystem.readFile(path.join(cwd, ".git", "index")), originalIndex);
988 }).pipe(Effect.scoped, Effect.provide(GitContractLayer)),
988 }).pipe(Effect.scoped, Effect.provide(layerGitContract)),
989989);
990990
991991it.effect.each(["missing", "invalid"] as const)(
......@@ -1012,7 +1012,7 @@ it.effect.each(["missing", "invalid"] as const)(
10121012 } else {
10131013 assert.strictEqual(yield* fileSystem.readFileString(indexPath), "invalid index");
10141014 }
1015 }).pipe(Effect.scoped, Effect.provide(GitContractLayer)),
1015 }).pipe(Effect.scoped, Effect.provide(layerGitContract)),
10161016);
10171017
10181018it.effect("restores empty checkpoints without changing paths outside the workspace", () =>
......@@ -1076,7 +1076,7 @@ it.effect("restores empty checkpoints without changing paths outside the workspa
10761076 assert.strictEqual(staged.stdout.trim(), "outside.txt");
10771077 }
10781078 }
1079 }).pipe(Effect.scoped, Effect.provide(GitContractLayer)),
1079 }).pipe(Effect.scoped, Effect.provide(layerGitContract)),
10801080);
10811081
10821082it.effect("GitVcsDriver forwards execute env to the VCS process", () => {
apps/server/src/vcs/GitVcsDriver.ts+1-1
......@@ -1257,5 +1257,5 @@ export const make = Effect.gen(function* () {
12571257 return GitVcsDriver.of(git);
12581258});
12591259
1260export const vcsLayer = Layer.effect(VcsDriver.VcsDriver, makeVcsDriver);
1260export const layerVcs = Layer.effect(VcsDriver.VcsDriver, makeVcsDriver);
12611261export const layer = Layer.effect(GitVcsDriver, make);
apps/server/src/vcs/GitVcsDriverCore.test.ts+28-28
......@@ -40,11 +40,11 @@ import * as GitVcsDriver from "./GitVcsDriver.ts";
4040
4141const encodeGitCommandError = Schema.encodeEffect(Schema.fromJsonString(GitCommandError));
4242
43const ServerConfigLayer = ServerConfig.layerTest(process.cwd(), {
43const layerServerConfig = ServerConfig.layerTest(process.cwd(), {
4444 prefix: "t3-git-vcs-driver-test-",
4545});
46const TestLayer = GitVcsDriver.layer.pipe(
47 Layer.provide(ServerConfigLayer),
46const layerTest = GitVcsDriver.layer.pipe(
47 Layer.provide(layerServerConfig),
4848 Layer.provideMerge(NodeServices.layer),
4949);
5050
......@@ -201,7 +201,7 @@ it.effect("bounds Git bursts across drivers without timing out queued commands",
201201 );
202202 assert.equal(duration.count, 16);
203203 assert.equal(duration.sum, 16_000);
204 }).pipe(Effect.provide(ServerConfigLayer.pipe(Layer.provideMerge(NodeServices.layer)))),
204 }).pipe(Effect.provide(layerServerConfig.pipe(Layer.provideMerge(NodeServices.layer)))),
205205);
206206
207207it.effect.each([{ timeoutMs: null }, { timeoutMs: 30_001 }])(
......@@ -252,7 +252,7 @@ it.effect.each([{ timeoutMs: null }, { timeoutMs: 30_001 }])(
252252 yield* Deferred.succeed(slowGate, undefined);
253253 assert.equal((yield* Fiber.join(slow)).stdout, "ok");
254254 assert.equal(active, 0);
255 }).pipe(Effect.provide(ServerConfigLayer.pipe(Layer.provideMerge(NodeServices.layer)))),
255 }).pipe(Effect.provide(layerServerConfig.pipe(Layer.provideMerge(NodeServices.layer)))),
256256);
257257
258258it.effect.each(["root", "nested", "worktree"] as const)(
......@@ -295,7 +295,7 @@ it.effect.each(["root", "nested", "worktree"] as const)(
295295 const status = yield* driver.statusDetailsLocal(statusCwd);
296296 assert.isFalse(status.hasWorkingTreeChanges);
297297 assert.include(yield* fs.readFileString(runsPath), "clean");
298 }).pipe(Effect.provide(TestLayer)),
298 }).pipe(Effect.provide(layerTest)),
299299);
300300
301301it.effect("uses stable diagnostics for every parsed non-repository command", () => {
......@@ -312,13 +312,13 @@ it.effect("uses stable diagnostics for every parsed non-repository command", ()
312312 return makeNonRepositoryHandle();
313313 }),
314314 );
315 const nodeServicesLayer = Layer.merge(
315 const layerNodeServices = Layer.merge(
316316 NodeServices.layer,
317317 Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner),
318318 );
319319 const layer = GitVcsDriver.layer.pipe(
320 Layer.provide(ServerConfigLayer),
321 Layer.provideMerge(nodeServicesLayer),
320 Layer.provide(layerServerConfig),
321 Layer.provideMerge(layerNodeServices),
322322 );
323323
324324 return Effect.gen(function* () {
......@@ -353,7 +353,7 @@ it.effect("invalidates origin remote cache when a driver mutation adds origin",
353353
354354 const after = yield* driver.statusDetailsLocal(cwd);
355355 assert.equal(after.hasOriginRemote, true);
356 }).pipe(Effect.provide(TestLayer)),
356 }).pipe(Effect.provide(layerTest)),
357357);
358358
359359it.effect("re-reads origin remote status after cache TTL expiry and bypassed invalidation", () =>
......@@ -380,7 +380,7 @@ it.effect("re-reads origin remote status after cache TTL expiry and bypassed inv
380380 // After expiry, the next call re-executes and picks up the remote
381381 const afterExpiry = yield* driver.statusDetailsLocal(cwd);
382382 assert.equal(afterExpiry.hasOriginRemote, true);
383 }).pipe(Effect.provide(TestLayer)),
383 }).pipe(Effect.provide(layerTest)),
384384);
385385
386386it.effect("coalesces concurrent ref pages into one repository snapshot", () =>
......@@ -492,7 +492,7 @@ it.effect("coalesces concurrent ref pages into one repository snapshot", () =>
492492 2,
493493 );
494494 }),
495 ).pipe(Effect.provide(ServerConfigLayer.pipe(Layer.provideMerge(NodeServices.layer)))),
495 ).pipe(Effect.provide(layerServerConfig.pipe(Layer.provideMerge(NodeServices.layer)))),
496496);
497497
498498it.effect("retries an in-flight ref snapshot invalidated by a mutation", () =>
......@@ -551,7 +551,7 @@ it.effect("retries an in-flight ref snapshot invalidated by a mutation", () =>
551551 assert.isTrue(refs.refs.some((ref) => ref.name === "feature/during-refresh"));
552552 assert.equal(yield* Ref.get(refScans), 2);
553553 }),
554 ).pipe(Effect.provide(ServerConfigLayer.pipe(Layer.provideMerge(NodeServices.layer)))),
554 ).pipe(Effect.provide(layerServerConfig.pipe(Layer.provideMerge(NodeServices.layer)))),
555555);
556556
557557it.effect("invalidates a ref snapshot when a mutation fails after changing Git", () =>
......@@ -583,7 +583,7 @@ it.effect("invalidates a ref snapshot when a mutation fails after changing Git",
583583 const refs = yield* driver.listRefs({ cwd });
584584 assert.isTrue(refs.refs.some((ref) => ref.name === "feature/partial-failure"));
585585 }),
586 ).pipe(Effect.provide(ServerConfigLayer.pipe(Layer.provideMerge(NodeServices.layer)))),
586 ).pipe(Effect.provide(layerServerConfig.pipe(Layer.provideMerge(NodeServices.layer)))),
587587);
588588
589589it.effect("fails a ref snapshot when for-each-ref exits unsuccessfully", () =>
......@@ -619,7 +619,7 @@ it.effect("fails a ref snapshot when for-each-ref exits unsuccessfully", () =>
619619 });
620620 assert.equal(yield* Ref.get(snapshotAttempts), 1);
621621 }),
622 ).pipe(Effect.provide(ServerConfigLayer.pipe(Layer.provideMerge(NodeServices.layer)))),
622 ).pipe(Effect.provide(layerServerConfig.pipe(Layer.provideMerge(NodeServices.layer)))),
623623);
624624
625625it.effect("marks the current branch when worktree metadata is unavailable", () =>
......@@ -654,7 +654,7 @@ it.effect("marks the current branch when worktree metadata is unavailable", () =
654654 assert.isTrue(refs.isRepo);
655655 assert.isTrue(refs.refs.find((ref) => ref.name === initialBranch)?.current);
656656 }),
657 ).pipe(Effect.provide(ServerConfigLayer.pipe(Layer.provideMerge(NodeServices.layer)))),
657 ).pipe(Effect.provide(layerServerConfig.pipe(Layer.provideMerge(NodeServices.layer)))),
658658);
659659
660660it.effect("ignores worktree metadata for directories that no longer exist", () =>
......@@ -690,7 +690,7 @@ it.effect("ignores worktree metadata for directories that no longer exist", () =
690690
691691 assert.equal(refs.refs.find((ref) => ref.name === "stale-worktree")?.worktreePath, null);
692692 }),
693 ).pipe(Effect.provide(ServerConfigLayer.pipe(Layer.provideMerge(NodeServices.layer)))),
693 ).pipe(Effect.provide(layerServerConfig.pipe(Layer.provideMerge(NodeServices.layer)))),
694694);
695695
696696it.effect("refreshes the current branch after an external checkout", () =>
......@@ -718,7 +718,7 @@ it.effect("refreshes the current branch after an external checkout", () =>
718718 assert.isTrue(refreshedRefs.refs.find((ref) => ref.name === "external-checkout")?.current);
719719 assert.isFalse(refreshedRefs.refs.find((ref) => ref.name === initialBranch)?.current);
720720 }),
721 ).pipe(Effect.provide(TestLayer)),
721 ).pipe(Effect.provide(layerTest)),
722722);
723723
724724it.effect("backs off and logs failed fetch attempts across linked worktrees", () =>
......@@ -818,7 +818,7 @@ it.effect("backs off and logs failed fetch attempts across linked worktrees", ()
818818 assert.equal(yield* Ref.get(fetchAttempts), 3);
819819 assert.lengthOf(warnings, 3);
820820 }),
821 ).pipe(Effect.provide(ServerConfigLayer.pipe(Layer.provideMerge(NodeServices.layer)))),
821 ).pipe(Effect.provide(layerServerConfig.pipe(Layer.provideMerge(NodeServices.layer)))),
822822);
823823
824824it.effect.each([
......@@ -927,10 +927,10 @@ it.effect.each([
927927 assert.notProperty(error, "stderr");
928928 assert.notProperty(error, "args");
929929 assert.equal(yield* Ref.get(attempts), 1);
930 }).pipe(Effect.provide(ServerConfigLayer.pipe(Layer.provideMerge(NodeServices.layer)))),
930 }).pipe(Effect.provide(layerServerConfig.pipe(Layer.provideMerge(NodeServices.layer)))),
931931);
932932
933it.layer(TestLayer)("GitVcsDriver core integration", (it) => {
933it.layer(layerTest)("GitVcsDriver core integration", (it) => {
934934 describe("process environment", () => {
935935 it.effect("preserves the caller locale for general Git subprocesses", () =>
936936 Effect.gen(function* () {
......@@ -1424,7 +1424,7 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => {
14241424 });
14251425 const driver = yield* makeGitVcsDriverCore().pipe(
14261426 Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner),
1427 Effect.provide(ServerConfigLayer),
1427 Effect.provide(layerServerConfig),
14281428 );
14291429 const branch = yield* driver.getReviewDiffPreview({
14301430 cwd,
......@@ -1463,7 +1463,7 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => {
14631463 );
14641464 const driver = yield* makeGitVcsDriverCore().pipe(
14651465 Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner),
1466 Effect.provide(ServerConfigLayer),
1466 Effect.provide(layerServerConfig),
14671467 );
14681468 const result = yield* driver.getReviewDiffPreview({ cwd }).pipe(Effect.result);
14691469 assert.isTrue(Result.isFailure(result));
......@@ -1667,7 +1667,7 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => {
16671667 });
16681668 const driver = yield* makeGitVcsDriverCore().pipe(
16691669 Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, failingLsFilesSpawner),
1670 Effect.provide(ServerConfigLayer),
1670 Effect.provide(layerServerConfig),
16711671 );
16721672 const cwd = yield* makeTmpDir();
16731673 yield* initRepoWithCommit(cwd).pipe(
......@@ -2312,7 +2312,7 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => {
23122312 );
23132313 const driver = yield* makeGitVcsDriverCore().pipe(
23142314 Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner),
2315 Effect.provide(ServerConfigLayer),
2315 Effect.provide(layerServerConfig),
23162316 );
23172317
23182318 for (const hasUpstream of [false, true]) {
......@@ -3214,7 +3214,7 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => {
32143214 );
32153215 const driver = yield* makeGitVcsDriverCore().pipe(
32163216 Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, delayedRemovalSpawner),
3217 Effect.provide(ServerConfigLayer),
3217 Effect.provide(layerServerConfig),
32183218 );
32193219 const cwd = yield* makeTmpDir();
32203220 const { initialBranch } = yield* initRepoWithCommit(cwd);
......@@ -3523,7 +3523,7 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => {
35233523 );
35243524 const driver = yield* makeGitVcsDriverCore().pipe(
35253525 Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner),
3526 Effect.provide(ServerConfigLayer),
3526 Effect.provide(layerServerConfig),
35273527 );
35283528 const fetching = yield* driver
35293529 .fetchRemote({ cwd, remoteName: "origin", refName: "main" })
......@@ -3703,7 +3703,7 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => {
37033703 );
37043704 const driver = yield* makeGitVcsDriverCore().pipe(
37053705 Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, delayedPushSpawner),
3706 Effect.provide(ServerConfigLayer),
3706 Effect.provide(layerServerConfig),
37073707 );
37083708 const cwd = yield* makeTmpDir();
37093709 const remote = yield* makeTmpDir("git-remote-");
apps/server/src/vcs/VcsProcess.test.ts+2-2
......@@ -30,10 +30,10 @@ const run = (input: VcsProcess.VcsProcessInput) =>
3030 return yield* process.run(input);
3131 });
3232
33const liveLayer = VcsProcess.layer.pipe(Layer.provide(NodeServices.layer));
33const layerLive = VcsProcess.layer.pipe(Layer.provide(NodeServices.layer));
3434
3535const provideLive = <A, E, R>(effect: Effect.Effect<A, E, R | VcsProcess.VcsProcess>) =>
36 effect.pipe(Effect.provide(liveLayer));
36 effect.pipe(Effect.provide(layerLive));
3737
3838const baseInput = {
3939 operation: "test.process-boundary",
apps/server/src/vcs/VcsProjectConfig.test.ts+8-8
......@@ -8,13 +8,13 @@ import * as Path from "effect/Path";
88
99import * as VcsProjectConfig from "./VcsProjectConfig.ts";
1010
11const TestLayer = VcsProjectConfig.layer.pipe(
11const layerTest = VcsProjectConfig.layer.pipe(
1212 Layer.provide(NodeServices.layer),
1313 Layer.provideMerge(NodeServices.layer),
1414);
1515
1616describe("VcsProjectConfig", () => {
17 it.layer(TestLayer)("uses an explicit requested VCS kind before config", (it) => {
17 it.layer(layerTest)("uses an explicit requested VCS kind before config", (it) => {
1818 it.effect("returns the requested kind", () =>
1919 Effect.gen(function* () {
2020 const config = yield* VcsProjectConfig.VcsProjectConfig;
......@@ -28,7 +28,7 @@ describe("VcsProjectConfig", () => {
2828 );
2929 });
3030
31 it.layer(TestLayer)("discovers .t3code/vcs.json from nested workspaces", (it) => {
31 it.layer(layerTest)("discovers .t3code/vcs.json from nested workspaces", (it) => {
3232 it.effect("returns the configured kind", () =>
3333 Effect.gen(function* () {
3434 const fileSystem = yield* FileSystem.FileSystem;
......@@ -54,7 +54,7 @@ describe("VcsProjectConfig", () => {
5454 );
5555 });
5656
57 it.layer(TestLayer)("continues to parent configs after a candidate inspect failure", (it) => {
57 it.layer(layerTest)("continues to parent configs after a candidate inspect failure", (it) => {
5858 it.effect("logs the failed candidate and returns the parent config", () => {
5959 const messages: unknown[] = [];
6060 const logger = Logger.make<unknown, void>(({ message }) => {
......@@ -97,7 +97,7 @@ describe("VcsProjectConfig", () => {
9797 });
9898 });
9999
100 it.layer(TestLayer)("falls back to auto when no config exists", (it) => {
100 it.layer(layerTest)("falls back to auto when no config exists", (it) => {
101101 it.effect("returns auto", () =>
102102 Effect.gen(function* () {
103103 const fileSystem = yield* FileSystem.FileSystem;
......@@ -112,7 +112,7 @@ describe("VcsProjectConfig", () => {
112112 );
113113 });
114114
115 it.layer(TestLayer)("falls back to auto when config JSON is malformed", (it) => {
115 it.layer(layerTest)("falls back to auto when config JSON is malformed", (it) => {
116116 it.effect("returns auto and logs the failed operation and path", () => {
117117 const messages: unknown[] = [];
118118 const logger = Logger.make<unknown, void>(({ message }) => {
......@@ -150,7 +150,7 @@ describe("VcsProjectConfig", () => {
150150 });
151151 });
152152
153 it.layer(TestLayer)("falls back to auto when the config path cannot be read", (it) => {
153 it.layer(layerTest)("falls back to auto when the config path cannot be read", (it) => {
154154 it.effect("retains the read failure context", () => {
155155 const messages: unknown[] = [];
156156 const logger = Logger.make<unknown, void>(({ message }) => {
......@@ -184,7 +184,7 @@ describe("VcsProjectConfig", () => {
184184 });
185185 });
186186
187 it.layer(TestLayer)("falls back to auto when config kind is invalid", (it) => {
187 it.layer(layerTest)("falls back to auto when config kind is invalid", (it) => {
188188 it.effect("returns auto", () =>
189189 Effect.gen(function* () {
190190 const fileSystem = yield* FileSystem.FileSystem;
apps/server/src/vcs/VcsProvisioningService.test.ts+4-4
......@@ -61,7 +61,7 @@ function makeDriver(calls: string[]): VcsDriver.VcsDriver["Service"] {
6161it.effect("routes repository initialization through an explicit VCS driver kind", () => {
6262 const calls: string[] = [];
6363 const driver = makeDriver(calls);
64 const testLayer = VcsProvisioningService.layer.pipe(
64 const layerTest = VcsProvisioningService.layer.pipe(
6565 Layer.provide(
6666 Layer.mock(VcsDriverRegistry.VcsDriverRegistry)({
6767 get: (kind) => (kind === "git" ? Effect.succeed(driver) : Effect.die("unexpected kind")),
......@@ -74,13 +74,13 @@ it.effect("routes repository initialization through an explicit VCS driver kind"
7474 yield* provisioning.initRepository({ cwd: "/repo", kind: "git" });
7575
7676 assert.deepStrictEqual(calls, ["git:/repo"]);
77 }).pipe(Effect.provide(testLayer));
77 }).pipe(Effect.provide(layerTest));
7878});
7979
8080it.effect("defaults repository initialization to Git until callers choose a VCS kind", () => {
8181 const calls: string[] = [];
8282 const driver = makeDriver(calls);
83 const testLayer = VcsProvisioningService.layer.pipe(
83 const layerTest = VcsProvisioningService.layer.pipe(
8484 Layer.provide(
8585 Layer.mock(VcsDriverRegistry.VcsDriverRegistry)({
8686 get: (kind) => (kind === "git" ? Effect.succeed(driver) : Effect.die("unexpected kind")),
......@@ -93,5 +93,5 @@ it.effect("defaults repository initialization to Git until callers choose a VCS
9393 yield* provisioning.initRepository({ cwd: "/repo" });
9494
9595 assert.deepStrictEqual(calls, ["default:/repo"]);
96 }).pipe(Effect.provide(testLayer));
96 }).pipe(Effect.provide(layerTest));
9797});
apps/server/src/vcs/VcsStatusBroadcaster.test.ts+34-34
......@@ -69,7 +69,7 @@ const baseStatus: VcsStatusResult = {
6969 ...baseRemoteStatus,
7070};
7171
72function makeTestLayer(state: {
72function layerTestFor(state: {
7373 currentLocalStatus: VcsStatusLocalResult;
7474 currentRemoteStatus: VcsStatusRemoteResult | null;
7575 localStatusCalls: number;
......@@ -83,7 +83,7 @@ function makeTestLayer(state: {
8383}) {
8484 return VcsStatusBroadcaster.layer.pipe(
8585 Layer.provideMerge(NodeServices.layer),
86 Layer.provide(makeBackgroundPolicyLayer(() => state.backgroundWorkEnabled !== false)),
86 Layer.provide(layerBackgroundPolicy(() => state.backgroundWorkEnabled !== false)),
8787 Layer.provide(
8888 Layer.mock(GitWorkflowService.GitWorkflowService)({
8989 localStatus: () =>
......@@ -119,7 +119,7 @@ function makeTestLayer(state: {
119119 );
120120}
121121
122function makeBackgroundPolicyLayer(shouldRunScopeWork: (scope: BackgroundScope) => boolean) {
122function layerBackgroundPolicy(shouldRunScopeWork: (scope: BackgroundScope) => boolean) {
123123 return Layer.mock(BackgroundPolicy.BackgroundPolicy)({
124124 reportClientActivity: () => Effect.void,
125125 removeRpcClient: () => Effect.void,
......@@ -162,9 +162,9 @@ describe("VcsStatusBroadcaster", () => {
162162 isDefaultRef: true,
163163 refName: "main",
164164 };
165 const testLayer = VcsStatusBroadcaster.layer.pipe(
165 const layerTest = VcsStatusBroadcaster.layer.pipe(
166166 Layer.provideMerge(NodeServices.layer),
167 Layer.provide(makeBackgroundPolicyLayer(() => true)),
167 Layer.provide(layerBackgroundPolicy(() => true)),
168168 Layer.provide(
169169 Layer.succeed(VcsStatusBroadcaster.VcsAutoPullPolicy, {
170170 isEnabled: (cwd) => Effect.succeed(cwd === configuredWorkspaceRoot),
......@@ -208,7 +208,7 @@ describe("VcsStatusBroadcaster", () => {
208208
209209 assert.equal(pullCalls, 1);
210210 assert.equal(status.behindCount, 0);
211 }).pipe(Effect.provide(testLayer));
211 }).pipe(Effect.provide(layerTest));
212212 },
213213 );
214214
......@@ -234,7 +234,7 @@ describe("VcsStatusBroadcaster", () => {
234234 assert.equal(state.remoteStatusCalls, 1);
235235 assert.equal(state.localInvalidationCalls, 0);
236236 assert.equal(state.remoteInvalidationCalls, 0);
237 }).pipe(Effect.provide(makeTestLayer(state)));
237 }).pipe(Effect.provide(layerTestFor(state)));
238238 });
239239
240240 it.effect("refreshes a loaded cwd without reusing a previous branch's PR", () => {
......@@ -271,7 +271,7 @@ describe("VcsStatusBroadcaster", () => {
271271 const refreshedBranch = yield* broadcaster.refreshPullRequestStatus("/repo");
272272 assert.deepStrictEqual(refreshedBranch, baseRemoteStatus);
273273 assert.equal(state.remoteStatusCalls, 3);
274 }).pipe(Effect.provide(makeTestLayer(state)));
274 }).pipe(Effect.provide(layerTestFor(state)));
275275 });
276276
277277 it.effect("a poll that started before the turn-end refresh cannot overwrite its PR", () => {
......@@ -280,7 +280,7 @@ describe("VcsStatusBroadcaster", () => {
280280 let remoteReads = 0;
281281 const layer = VcsStatusBroadcaster.layer.pipe(
282282 Layer.provideMerge(NodeServices.layer),
283 Layer.provide(makeBackgroundPolicyLayer(() => true)),
283 Layer.provide(layerBackgroundPolicy(() => true)),
284284 Layer.provide(
285285 Layer.mock(GitWorkflowService.GitWorkflowService)({
286286 localStatus: () => Effect.succeed(baseLocalStatus),
......@@ -326,7 +326,7 @@ describe("VcsStatusBroadcaster", () => {
326326 const layer = VcsStatusBroadcaster.layer.pipe(
327327 Layer.provide(FileSystem.layerNoop({ realPath: (path) => Effect.succeed(path) })),
328328 Layer.provideMerge(NodeServices.layer),
329 Layer.provide(makeBackgroundPolicyLayer(() => true)),
329 Layer.provide(layerBackgroundPolicy(() => true)),
330330 Layer.provide(
331331 Layer.mock(GitWorkflowService.GitWorkflowService)({
332332 localStatus: () => Effect.succeed(baseLocalStatus),
......@@ -377,7 +377,7 @@ describe("VcsStatusBroadcaster", () => {
377377 yield* broadcaster.refreshPullRequestStatus("/repo");
378378 assert.equal(state.remoteStatusCalls, 1);
379379 assert.equal(state.remoteInvalidationCalls, 0);
380 }).pipe(Effect.provide(makeTestLayer(state)));
380 }).pipe(Effect.provide(layerTestFor(state)));
381381 });
382382
383383 it.effect("refreshes the cached snapshot after explicit invalidation", () => {
......@@ -418,7 +418,7 @@ describe("VcsStatusBroadcaster", () => {
418418 assert.equal(state.remoteStatusCalls, 2);
419419 assert.equal(state.localInvalidationCalls, 1);
420420 assert.equal(state.remoteInvalidationCalls, 1);
421 }).pipe(Effect.provide(makeTestLayer(state)));
421 }).pipe(Effect.provide(layerTestFor(state)));
422422 });
423423
424424 it.effect("keeps the cached snapshot unchanged when a refresh branch fails", () => {
......@@ -431,9 +431,9 @@ describe("VcsStatusBroadcaster", () => {
431431 remoteInvalidationCalls: 0,
432432 failRemoteStatus: false,
433433 };
434 const testLayer = VcsStatusBroadcaster.layer.pipe(
434 const layerTest = VcsStatusBroadcaster.layer.pipe(
435435 Layer.provideMerge(NodeServices.layer),
436 Layer.provide(makeBackgroundPolicyLayer(() => true)),
436 Layer.provide(layerBackgroundPolicy(() => true)),
437437 Layer.provide(
438438 Layer.mock(GitWorkflowService.GitWorkflowService)({
439439 localStatus: () =>
......@@ -490,7 +490,7 @@ describe("VcsStatusBroadcaster", () => {
490490
491491 assert.isTrue(Exit.isFailure(refreshExit));
492492 assert.deepStrictEqual(cached, baseStatus);
493 }).pipe(Effect.provide(testLayer));
493 }).pipe(Effect.provide(layerTest));
494494 });
495495
496496 it.effect("refreshes only the cached local snapshot when requested", () => {
......@@ -526,7 +526,7 @@ describe("VcsStatusBroadcaster", () => {
526526 assert.equal(state.remoteStatusCalls, 1);
527527 assert.equal(state.localInvalidationCalls, 1);
528528 assert.equal(state.remoteInvalidationCalls, 0);
529 }).pipe(Effect.provide(makeTestLayer(state)));
529 }).pipe(Effect.provide(layerTestFor(state)));
530530 });
531531
532532 it.effect.skipIf(!symlinksSupported)(
......@@ -541,9 +541,9 @@ describe("VcsStatusBroadcaster", () => {
541541 localInvalidationCalls: 0,
542542 remoteInvalidationCalls: 0,
543543 };
544 const testLayer = VcsStatusBroadcaster.layer.pipe(
544 const layerTest = VcsStatusBroadcaster.layer.pipe(
545545 Layer.provideMerge(NodeServices.layer),
546 Layer.provide(makeBackgroundPolicyLayer(() => true)),
546 Layer.provide(layerBackgroundPolicy(() => true)),
547547 Layer.provide(
548548 Layer.mock(GitWorkflowService.GitWorkflowService)({
549549 localStatus: (input) =>
......@@ -590,7 +590,7 @@ describe("VcsStatusBroadcaster", () => {
590590 assert.deepStrictEqual(seenCwds, [realPath, realPath]);
591591 assert.equal(state.localStatusCalls, 1);
592592 assert.equal(state.remoteStatusCalls, 1);
593 }).pipe(Effect.provide(testLayer));
593 }).pipe(Effect.provide(layerTest));
594594 },
595595 );
596596
......@@ -631,7 +631,7 @@ describe("VcsStatusBroadcaster", () => {
631631 _tag: "remoteUpdated",
632632 remote: baseRemoteStatus,
633633 } satisfies VcsStatusStreamEvent);
634 }).pipe(Effect.provide(makeTestLayer(state)));
634 }).pipe(Effect.provide(layerTestFor(state)));
635635 });
636636
637637 it.effect("loads remote status once when periodic refreshes are disabled", () => {
......@@ -687,7 +687,7 @@ describe("VcsStatusBroadcaster", () => {
687687 assert.equal(state.remoteInvalidationCalls, 0);
688688
689689 yield* Scope.close(scope, Exit.void);
690 }).pipe(Effect.provide(Layer.merge(makeTestLayer(state), TestClock.layer())));
690 }).pipe(Effect.provide(Layer.merge(layerTestFor(state), TestClock.layer())));
691691 });
692692
693693 it.effect("retries the initial remote load when periodic refreshes are disabled", () => {
......@@ -706,9 +706,9 @@ describe("VcsStatusBroadcaster", () => {
706706 messages.push(message as ReadonlyArray<unknown>);
707707 });
708708 let firstRemoteAttemptDeferred: Deferred.Deferred<void> | null = null;
709 const testLayer = VcsStatusBroadcaster.layer.pipe(
709 const layerTest = VcsStatusBroadcaster.layer.pipe(
710710 Layer.provideMerge(NodeServices.layer),
711 Layer.provide(makeBackgroundPolicyLayer(() => true)),
711 Layer.provide(layerBackgroundPolicy(() => true)),
712712 Layer.provide(
713713 Layer.mock(GitWorkflowService.GitWorkflowService)({
714714 localStatus: () =>
......@@ -803,7 +803,7 @@ describe("VcsStatusBroadcaster", () => {
803803 }).pipe(
804804 Effect.provide(
805805 Layer.mergeAll(
806 testLayer,
806 layerTest,
807807 TestClock.layer(),
808808 Logger.layer([logger], { mergeWithExisting: false }),
809809 ),
......@@ -850,7 +850,7 @@ describe("VcsStatusBroadcaster", () => {
850850 assert.equal(state.remoteInvalidationCalls, 1);
851851
852852 yield* Scope.close(scope, Exit.void);
853 }).pipe(Effect.provide(Layer.merge(makeTestLayer(state), TestClock.layer())));
853 }).pipe(Effect.provide(Layer.merge(layerTestFor(state), TestClock.layer())));
854854 });
855855
856856 // A push from a terminal moves ahead; a PR merged on the host moves ahead-of-default.
......@@ -901,11 +901,11 @@ describe("VcsStatusBroadcaster", () => {
901901 local: pushedLocal,
902902 } satisfies VcsStatusStreamEvent);
903903 yield* Scope.close(scope, Exit.void);
904 }).pipe(Effect.provide(Layer.merge(makeTestLayer(state), TestClock.layer())));
904 }).pipe(Effect.provide(Layer.merge(layerTestFor(state), TestClock.layer())));
905905 });
906906
907907 it.effect("an explicit refresh reads local totals after the fetch", () => {
908 const state: Parameters<typeof makeTestLayer>[0] = {
908 const state: Parameters<typeof layerTestFor>[0] = {
909909 currentLocalStatus: baseLocalStatus,
910910 currentRemoteStatus: baseRemoteStatus,
911911 localStatusCalls: 0,
......@@ -934,7 +934,7 @@ describe("VcsStatusBroadcaster", () => {
934934
935935 const status = yield* Fiber.join(refresh);
936936 assert.deepStrictEqual(status.branchChanges, fetchedLocal.branchChanges);
937 }).pipe(Effect.provide(makeTestLayer(state)));
937 }).pipe(Effect.provide(layerTestFor(state)));
938938 });
939939
940940 it("backs off remote refresh failures exponentially and honors larger configured intervals", () => {
......@@ -990,9 +990,9 @@ describe("VcsStatusBroadcaster", () => {
990990 localInvalidationCalls: 0,
991991 remoteInvalidationCalls: 0,
992992 };
993 const testLayer = VcsStatusBroadcaster.layer.pipe(
993 const layerTest = VcsStatusBroadcaster.layer.pipe(
994994 Layer.provideMerge(NodeServices.layer),
995 Layer.provide(makeBackgroundPolicyLayer(() => false)),
995 Layer.provide(layerBackgroundPolicy(() => false)),
996996 Layer.provide(
997997 Layer.mock(GitWorkflowService.GitWorkflowService)({
998998 localStatus: () =>
......@@ -1029,7 +1029,7 @@ describe("VcsStatusBroadcaster", () => {
10291029 assert.isTrue(Option.isSome(snapshot));
10301030 assert.equal(state.remoteStatusCalls, 0);
10311031 assert.equal(state.remoteInvalidationCalls, 0);
1032 }).pipe(Effect.provide(testLayer));
1032 }).pipe(Effect.provide(layerTest));
10331033 });
10341034
10351035 it.effect("stops the remote poller after the last stream subscriber disconnects", () => {
......@@ -1043,9 +1043,9 @@ describe("VcsStatusBroadcaster", () => {
10431043 };
10441044 let remoteInterruptedDeferred: Deferred.Deferred<void, never> | null = null;
10451045 let remoteStartedDeferred: Deferred.Deferred<void, never> | null = null;
1046 const testLayer = VcsStatusBroadcaster.layer.pipe(
1046 const layerTest = VcsStatusBroadcaster.layer.pipe(
10471047 Layer.provideMerge(NodeServices.layer),
1048 Layer.provide(makeBackgroundPolicyLayer(() => true)),
1048 Layer.provide(layerBackgroundPolicy(() => true)),
10491049 Layer.provide(
10501050 Layer.mock(GitWorkflowService.GitWorkflowService)({
10511051 localStatus: () =>
......@@ -1115,6 +1115,6 @@ describe("VcsStatusBroadcaster", () => {
11151115 yield* Scope.close(secondScope, Exit.void).pipe(Effect.forkScoped);
11161116 yield* Deferred.await(remoteInterrupted);
11171117 assert.isTrue(Option.isSome(yield* Deferred.poll(remoteInterrupted)));
1118 }).pipe(Effect.provide(testLayer));
1118 }).pipe(Effect.provide(layerTest));
11191119 });
11201120});
apps/server/src/vcs/VcsStatusBroadcaster.ts+1-1
......@@ -149,7 +149,7 @@ export class VcsAutoPullPolicy extends Context.Reference<{
149149 defaultValue: () => ({ isEnabled: () => Effect.succeed(false) }),
150150}) {}
151151
152export const autoPullPolicyLayer = Layer.effect(
152export const layerAutoPullPolicy = Layer.effect(
153153 VcsAutoPullPolicy,
154154 Effect.gen(function* () {
155155 const projects = yield* ProjectStore.ProjectStoreV2;
apps/server/src/workspace/WorkspaceEntries.test.ts+2-2
......@@ -21,7 +21,7 @@ vi.mock("node:fs/promises", async (importOriginal) => {
2121 return { ...actual, readdir: vi.fn(actual.readdir) };
2222});
2323
24const TestLayer = Layer.empty.pipe(
24const layerTest = Layer.empty.pipe(
2525 Layer.provideMerge(WorkspaceEntries.layer.pipe(Layer.provide(WorkspacePaths.layer))),
2626 Layer.provideMerge(WorkspacePaths.layer),
2727 Layer.provideMerge(VcsProcess.layer),
......@@ -90,7 +90,7 @@ const appendSeparator = (input: string) =>
9090 : `${input}${platform === "win32" ? "\\" : "/"}`,
9191 );
9292
93it.layer(TestLayer, { excludeTestServices: true })("WorkspaceEntries", (it) => {
93it.layer(layerTest, { excludeTestServices: true })("WorkspaceEntries", (it) => {
9494 afterEach(() => {
9595 vi.restoreAllMocks();
9696 });
apps/server/src/workspace/WorkspaceFileSystem.test.ts+4-4
......@@ -17,13 +17,13 @@ import * as WorkspacePaths from "./WorkspacePaths.ts";
1717import { HostProcessPlatform } from "@t3tools/shared/hostProcess";
1818import { symlinksSupported } from "@t3tools/shared/testing/symlinks";
1919
20const ProjectLayer = WorkspaceFileSystem.layer.pipe(
20const layerProject = WorkspaceFileSystem.layer.pipe(
2121 Layer.provide(WorkspacePaths.layer),
2222 Layer.provide(WorkspaceEntries.layer.pipe(Layer.provide(WorkspacePaths.layer))),
2323);
2424
25const TestLayer = Layer.empty.pipe(
26 Layer.provideMerge(ProjectLayer),
25const layerTest = Layer.empty.pipe(
26 Layer.provideMerge(layerProject),
2727 Layer.provideMerge(WorkspaceEntries.layer.pipe(Layer.provide(WorkspacePaths.layer))),
2828 Layer.provideMerge(WorkspacePaths.layer),
2929 Layer.provideMerge(VcsDriverRegistry.layer.pipe(Layer.provide(VcsProcess.layer))),
......@@ -56,7 +56,7 @@ const writeTextFile = Effect.fn("writeTextFile")(function* (
5656 yield* fileSystem.writeFileString(absolutePath, contents).pipe(Effect.orDie);
5757});
5858
59it.layer(TestLayer, { excludeTestServices: true })("WorkspaceFileSystemLive", (it) => {
59it.layer(layerTest, { excludeTestServices: true })("WorkspaceFileSystemLive", (it) => {
6060 describe("readFile", () => {
6161 it.effect("reads UTF-8 files relative to the workspace root", () =>
6262 Effect.gen(function* () {
apps/server/src/workspace/WorkspacePaths.test.ts+2-2
......@@ -8,7 +8,7 @@ import * as PlatformError from "effect/PlatformError";
88
99import * as WorkspacePaths from "./WorkspacePaths.ts";
1010
11const TestLayer = Layer.empty.pipe(
11const layerTest = Layer.empty.pipe(
1212 Layer.provideMerge(WorkspacePaths.layer),
1313 Layer.provideMerge(NodeServices.layer),
1414);
......@@ -34,7 +34,7 @@ const writeTextFile = Effect.fn("writeTextFile")(function* (
3434 yield* fileSystem.writeFileString(absolutePath, contents).pipe(Effect.orDie);
3535});
3636
37it.layer(TestLayer)("WorkspacePathsLive", (it) => {
37it.layer(layerTest)("WorkspacePathsLive", (it) => {
3838 describe("normalizeWorkspaceRoot", () => {
3939 it.effect("resolves an existing directory", () =>
4040 Effect.gen(function* () {
apps/server/src/ws.ts+6-14
......@@ -212,11 +212,8 @@ import * as DirectEndpoints from "./environment/DirectEndpoints.ts";
212212import * as RemoteOpenTargets from "./environment/RemoteOpenTargets.ts";
213213import * as BackgroundPolicy from "./background/BackgroundPolicy.ts";
214214import * as EnvironmentAuth from "./auth/EnvironmentAuth.ts";
215import {
216 requiredScopeForDeviceList,
217 rpcAuthorizationError,
218 rpcScopeAuthorizationLayer,
219} from "./auth/RpcAuthorization.ts";
215import { requiredScopeForDeviceList, rpcAuthorizationError } from "./auth/RpcAuthorization.ts";
216import * as RpcAuthorization from "./auth/RpcAuthorization.ts";
220217import * as ProcessDiagnostics from "./diagnostics/ProcessDiagnostics.ts";
221218import * as ProcessResourceMonitor from "./diagnostics/ProcessResourceMonitor.ts";
222219import * as ResourceTelemetry from "./resourceTelemetry/ResourceTelemetry.ts";
......@@ -1181,7 +1178,7 @@ export const subscribeOrchestrationV2Shell = Effect.fn("ws.orchestrationV2.subsc
11811178 },
11821179);
11831180
1184const makeWsRpcLayer = (
1181const layerWsRpc = (
11851182 currentSession: EnvironmentAuth.AuthenticatedSession,
11861183 clientOrigin: OrchestrationClientOrigin,
11871184 clientAnalyticsProps: Readonly<Record<string, unknown>>,
......@@ -3796,7 +3793,7 @@ const makeWsRpcLayer = (
37963793 }),
37973794 );
37983795
3799export const websocketRpcRouteLayer = Layer.unwrap(
3796export const layer = Layer.unwrap(
38003797 Effect.gen(function* () {
38013798 const previewAutomationBroker = yield* PreviewAutomationBroker.PreviewAutomationBroker;
38023799 const serverSelfUpdate = yield* ServerSelfUpdate.ServerSelfUpdate;
......@@ -3840,19 +3837,14 @@ export const websocketRpcRouteLayer = Layer.unwrap(
38403837 const { protocol, httpEffect } = yield* RpcServer.makeProtocolWithHttpEffectWebsocket;
38413838 yield* RpcServer.make(ServerWsRpcGroup, { disableTracing: true }).pipe(
38423839 Effect.provideService(RpcServer.Protocol, withTerminalOutputWindow(protocol)),
3843 Effect.provide(rpcScopeAuthorizationLayer(session.scopes)),
3840 Effect.provide(RpcAuthorization.layer(session.scopes)),
38443841 Effect.forkScoped,
38453842 );
38463843 // @effect-diagnostics-next-line returnEffectInGen:off
38473844 return httpEffect;
38483845 }).pipe(
38493846 Effect.provide(
3850 makeWsRpcLayer(
3851 session,
3852 clientOrigin,
3853 clientAnalyticsProps,
3854 previewAutomationBroker,
3855 ).pipe(
3847 layerWsRpc(session, clientOrigin, clientAnalyticsProps, previewAutomationBroker).pipe(
38563848 Layer.provideMerge(RpcSerialization.layerJson),
38573849 Layer.provide(Layer.succeed(SqlClient.SqlClient, sql)),
38583850 Layer.provide(AgentSessionScanner.layer),
apps/web/src/cloud/dpop.test.ts+3-2
......@@ -4,7 +4,8 @@ import * as Effect from "effect/Effect";
44import { decodeJwt } from "jose";
55import { vi } from "vite-plus/test";
66
7import { browserCryptoLayer, createBrowserDpopProof, generateBrowserDpopKey } from "./dpop";
7import { createBrowserDpopProof, generateBrowserDpopKey } from "./dpop";
8import * as Dpop from "./dpop";
89
910describe("browser DPoP proofs", () => {
1011 it.effect("signs relay resource proofs with an access-token hash", () =>
......@@ -16,7 +17,7 @@ describe("browser DPoP proofs", () => {
1617 url: "https://relay.example.test/v1/environments/env-1/connect?ignored=true",
1718 accessToken: "relay-access-token",
1819 proofKey,
19 }).pipe(Effect.provide(browserCryptoLayer));
20 }).pipe(Effect.provide(Dpop.layer));
2021 const issuedAt = decodeJwt(proof.proof).iat;
2122 expect(issuedAt).toBeTypeOf("number");
2223
apps/web/src/cloud/dpop.ts+1-1
......@@ -27,7 +27,7 @@ const DPOP_KEY_STORE_NAME = "keys";
2727const DPOP_KEY_ID = "relay-dpop-proof-key";
2828const decodeDpopPublicJwk = Schema.decodeUnknownEffect(DpopPublicJwk);
2929
30export const browserCryptoLayer = Layer.succeed(
30export const layer = Layer.succeed(
3131 Crypto.Crypto,
3232 Crypto.make({
3333 randomBytes: (size) => globalThis.crypto.getRandomValues(new Uint8Array(size)),
apps/web/src/cloud/linkEnvironment.test.ts+11-11
......@@ -22,7 +22,7 @@ import {
2222import { type RpcSession } from "@t3tools/client-runtime/rpc";
2323import { EnvironmentRegistry } from "@t3tools/client-runtime/connection";
2424import { ManagedRelay } from "@t3tools/client-runtime/relay";
25import { remoteHttpClientLayer } from "@t3tools/client-runtime/rpc";
25import { layerRemoteHttpClient } from "@t3tools/client-runtime/rpc";
2626import { __resetDesktopPrimaryAuthForTests } from "../environments/primary/desktopAuth";
2727
2828import {
......@@ -53,7 +53,7 @@ vi.mock("./relayClientInstallDialog", () => ({
5353}));
5454
5555const createProof = vi.fn(() => Effect.succeed("dpop-proof"));
56const dpopSignerLayer = Layer.succeed(
56const layerDpopSigner = Layer.succeed(
5757 ManagedRelay.ManagedRelayDpopSigner,
5858 ManagedRelay.ManagedRelayDpopSigner.of({
5959 thumbprint: Effect.succeed("thumbprint"),
......@@ -61,18 +61,18 @@ const dpopSignerLayer = Layer.succeed(
6161 }),
6262);
6363
64function relayLayer() {
65 const http = remoteHttpClientLayer(globalThis.fetch);
64function layerRelay() {
65 const layerHttp = layerRemoteHttpClient(globalThis.fetch);
6666 return Layer.mergeAll(
67 http,
67 layerHttp,
6868 ManagedRelay.layer({
6969 relayUrl: "https://relay.example.test",
7070 clientId: RelayWebClientId,
71 }).pipe(Layer.provideMerge(dpopSignerLayer), Layer.provide(http)),
71 }).pipe(Layer.provideMerge(layerDpopSigner), Layer.provide(layerHttp)),
7272 );
7373}
7474
75function registryLayer(options?: {
75function layerRegistry(options?: {
7676 readonly status?: { readonly status: "available"; readonly version: string };
7777 readonly installEvents?: ReadonlyArray<RelayClientInstallProgressEvent>;
7878}) {
......@@ -119,8 +119,8 @@ function registryLayer(options?: {
119119 );
120120}
121121
122function services(options?: Parameters<typeof registryLayer>[0]) {
123 return Layer.mergeAll(relayLayer(), registryLayer(options));
122function layerServices(options?: Parameters<typeof layerRegistry>[0]) {
123 return Layer.mergeAll(layerRelay(), layerRegistry(options));
124124}
125125
126126function withServices<A, E>(
......@@ -131,9 +131,9 @@ function withServices<A, E>(
131131 | ManagedRelay.ManagedRelayClient
132132 | EnvironmentRegistry.EnvironmentRegistry
133133 >,
134 options?: Parameters<typeof registryLayer>[0],
134 options?: Parameters<typeof layerRegistry>[0],
135135) {
136 return effect.pipe(Effect.provide(services(options)));
136 return effect.pipe(Effect.provide(layerServices(options)));
137137}
138138
139139function bodyText(body: BodyInit | null | undefined): string {
apps/web/src/cloud/linkEnvironment.ts+5-5
......@@ -24,7 +24,7 @@ import { request, runStream } from "@t3tools/client-runtime/rpc";
2424import { makeEnvironmentHttpApiClient } from "@t3tools/client-runtime/rpc";
2525import { ManagedRelay, relayProtectedErrorMessage } from "@t3tools/client-runtime/relay";
2626
27import { primaryEnvironmentHttpLayer } from "../environments/primary/httpLayer";
27import * as PrimaryEnvironmentHttpLayer from "../environments/primary/httpLayer";
2828import { resolveCloudPublicConfig } from "./publicConfig";
2929import {
3030 finishRelayClientInstall,
......@@ -193,7 +193,7 @@ export function readPrimaryCloudLinkState(input: {
193193 return yield* client.connect
194194 .linkState({ headers: {} })
195195 .pipe(Effect.mapError(environmentApiError("Could not read environment cloud link state.")));
196 }).pipe(Effect.provide(primaryEnvironmentHttpLayer));
196 }).pipe(Effect.provide(PrimaryEnvironmentHttpLayer.layer));
197197}
198198
199199export function updatePrimaryCloudPreferences(input: {
......@@ -212,7 +212,7 @@ export function updatePrimaryCloudPreferences(input: {
212212 .pipe(
213213 Effect.mapError(environmentApiError("Could not update environment cloud preferences.")),
214214 );
215 }).pipe(Effect.provide(primaryEnvironmentHttpLayer));
215 }).pipe(Effect.provide(PrimaryEnvironmentHttpLayer.layer));
216216}
217217
218218export function unlinkPrimaryEnvironmentFromCloud(input: {
......@@ -245,7 +245,7 @@ export function unlinkPrimaryEnvironmentFromCloud(input: {
245245 ),
246246 );
247247 }
248 }).pipe(Effect.provide(primaryEnvironmentHttpLayer));
248 }).pipe(Effect.provide(PrimaryEnvironmentHttpLayer.layer));
249249}
250250
251251// "publish_only" links the environment to the relay for agent-activity
......@@ -346,5 +346,5 @@ export function linkPrimaryEnvironmentToCloud(input: {
346346 },
347347 })
348348 .pipe(Effect.mapError(environmentApiError("Could not configure environment relay access.")));
349 }).pipe(Effect.provide(primaryEnvironmentHttpLayer));
349 }).pipe(Effect.provide(PrimaryEnvironmentHttpLayer.layer));
350350}
apps/web/src/cloud/managedRelayLayer.ts+3-3
......@@ -13,7 +13,7 @@ import {
1313 type BrowserDpopKey,
1414} from "./dpop";
1515
16const relayDpopSignerLayer = Layer.effect(
16const layerRelayDpopSigner = Layer.effect(
1717 ManagedRelay.ManagedRelayDpopSigner,
1818 Effect.gen(function* () {
1919 const crypto = yield* Crypto.Crypto;
......@@ -76,7 +76,7 @@ const relayDpopSignerLayer = Layer.effect(
7676 }),
7777);
7878
79export const managedRelayClientLayer = (relayUrl: string) =>
79export const layer = (relayUrl: string) =>
8080 ManagedRelay.layer({ relayUrl, clientId: RelayWebClientId }).pipe(
81 Layer.provideMerge(relayDpopSignerLayer),
81 Layer.provideMerge(layerRelayDpopSigner),
8282 );
apps/web/src/connection/platform.ts+28-25
......@@ -41,7 +41,7 @@ import { FetchHttpClient } from "effect/http";
4141
4242import { APP_VERSION } from "../branding";
4343import { readDesktopPrimaryBearerToken } from "../environments/primary/desktopAuth";
44import { primaryEnvironmentHttpLayer } from "../environments/primary/httpLayer";
44import * as PrimaryEnvironmentHttpLayer from "../environments/primary/httpLayer";
4545import {
4646 readPrimaryEnvironmentTarget,
4747 type PrimaryEnvironmentTarget,
......@@ -56,7 +56,7 @@ import {
5656 readDesktopSecondaryBootstrapsResult,
5757 type DesktopSecondaryBootstrapsRead,
5858} from "./desktopLocal";
59import { connectionStorageLayer } from "./storage";
59import * as ConnectionStorage from "./storage";
6060import { clientPresentationMetadata } from "./clientMetadata";
6161
6262let nextObservedRpcRequestId = 0;
......@@ -68,7 +68,7 @@ function currentNetworkStatus(): "unknown" | "offline" | "online" {
6868 return navigator.onLine ? "online" : "offline";
6969}
7070
71const connectivityLayer = Connectivity.layer({
71const layerConnectivity = Connectivity.layer({
7272 status: Effect.sync(currentNetworkStatus),
7373 changes: Stream.callback((queue) =>
7474 Effect.acquireRelease(
......@@ -124,7 +124,7 @@ const networkPathChanges = Stream.callback<"network-changed">((queue) =>
124124 ).pipe(Effect.asVoid),
125125);
126126
127const wakeupsLayer = Wakeups.layer({
127const layerWakeups = Wakeups.layer({
128128 changes: Stream.mergeAll(
129129 [
130130 Stream.callback<"application-active">((queue) =>
......@@ -223,7 +223,7 @@ export const provisionDesktopSshEnvironment = Effect.fn(
223223 };
224224});
225225
226const capabilitiesLayer = Layer.effectContext(
226const layerCapabilities = Layer.effectContext(
227227 Effect.sync(() => {
228228 const presentation = ClientCapabilities.ClientPresentation.of({
229229 metadata: clientMetadata(),
......@@ -346,7 +346,10 @@ const loadPrimaryConnectionRegistration = Effect.fn(
346346)(function* (resolved: PrimaryEnvironmentTarget) {
347347 const descriptor = yield* fetchRemoteEnvironmentDescriptor({
348348 httpBaseUrl: resolved.target.httpBaseUrl,
349 }).pipe(Effect.provide(primaryEnvironmentHttpLayer), Effect.mapError(mapRemoteEnvironmentError));
349 }).pipe(
350 Effect.provide(PrimaryEnvironmentHttpLayer.layer),
351 Effect.mapError(mapRemoteEnvironmentError),
352 );
350353 return new PrimaryConnectionRegistration({
351354 target: new PrimaryConnectionTarget({
352355 environmentId: descriptor.environmentId,
......@@ -511,7 +514,7 @@ export function secondaryRegistrationsToRetainAfterTopologyRead(
511514 );
512515}
513516
514const platformConnectionSourceLayer = Layer.effect(
517const layerPlatformConnectionSource = Layer.effect(
515518 PlatformConnectionSource.PlatformConnectionSource,
516519 Effect.gen(function* () {
517520 if (isHostedStaticApp() || isLocalEnvironmentDisabled()) {
......@@ -631,7 +634,7 @@ const platformConnectionSourceLayer = Layer.effect(
631634 }),
632635);
633636
634const environmentOwnedDataCleanupLayer = Layer.succeed(
637const layerEnvironmentOwnedDataCleanup = Layer.succeed(
635638 Persistence.EnvironmentOwnedDataCleanup,
636639 Persistence.EnvironmentOwnedDataCleanup.of({
637640 clear: (environmentId) =>
......@@ -641,7 +644,7 @@ const environmentOwnedDataCleanupLayer = Layer.succeed(
641644 }),
642645);
643646
644const rpcRequestObserverLayer = Layer.succeed(
647const layerRpcRequestObserver = Layer.succeed(
645648 EnvironmentRpcRequestObserver,
646649 EnvironmentRpcRequestObserver.of({
647650 observe: ({ environmentId, method }) =>
......@@ -657,24 +660,24 @@ const rpcRequestObserverLayer = Layer.succeed(
657660);
658661
659662type ConnectionPlatformLayerSource =
660 | typeof connectionStorageLayer
661 | typeof connectivityLayer
662 | typeof wakeupsLayer
663 | typeof capabilitiesLayer
664 | typeof platformConnectionSourceLayer
665 | typeof environmentOwnedDataCleanupLayer
666 | typeof rpcRequestObserverLayer;
667
668export const connectionPlatformLayer: Layer.Layer<
663 | typeof ConnectionStorage.layer
664 | typeof layerConnectivity
665 | typeof layerWakeups
666 | typeof layerCapabilities
667 | typeof layerPlatformConnectionSource
668 | typeof layerEnvironmentOwnedDataCleanup
669 | typeof layerRpcRequestObserver;
670
671export const layer: Layer.Layer<
669672 Layer.Success<ConnectionPlatformLayerSource>,
670673 Layer.Error<ConnectionPlatformLayerSource>,
671674 Layer.Services<ConnectionPlatformLayerSource>
672675> = Layer.mergeAll(
673 connectionStorageLayer,
674 connectivityLayer,
675 wakeupsLayer,
676 capabilitiesLayer,
677 platformConnectionSourceLayer,
678 environmentOwnedDataCleanupLayer,
679 rpcRequestObserverLayer,
676 ConnectionStorage.layer,
677 layerConnectivity,
678 layerWakeups,
679 layerCapabilities,
680 layerPlatformConnectionSource,
681 layerEnvironmentOwnedDataCleanup,
682 layerRpcRequestObserver,
680683);
apps/web/src/connection/runtime.ts+19-24
......@@ -1,26 +1,21 @@
11import { Connection } from "@t3tools/client-runtime/connection";
22import { ShellSnapshotLoader } from "@t3tools/client-runtime/state/shell";
33import {
4 boundedThreadSnapshotLoaderLayer,
4 BoundedThreadSnapshotLoader,
55 ThreadHistoryController,
66} from "@t3tools/client-runtime/state/threads";
77import { PullRequestDiffLoader } from "@t3tools/client-runtime/state/pull-requests";
88import * as Layer from "effect/Layer";
99import { Atom } from "effect/reactivity";
1010
11import { runtimeContextLayer } from "../lib/runtime";
12import {
13 backgroundActivityObserverLayer,
14 backgroundActivityReporterLayer,
15} from "../lib/backgroundActivityReporter";
16import { connectionPlatformLayer } from "./platform";
11import * as Runtime from "../lib/runtime";
12import * as BackgroundActivityReporter from "../lib/backgroundActivityReporter";
13import * as ConnectionPlatform from "./platform";
1714
18const providedConnectionPlatformLayer = connectionPlatformLayer.pipe(
19 Layer.provide(runtimeContextLayer),
20);
15const layerProvidedConnectionPlatform = ConnectionPlatform.layer.pipe(Layer.provide(Runtime.layer));
2116
22const snapshotLoaderLayer = Layer.mergeAll(
23 boundedThreadSnapshotLoaderLayer,
17const layerSnapshotLoader = Layer.mergeAll(
18 BoundedThreadSnapshotLoader.layer,
2419 ShellSnapshotLoader.layer,
2520 ThreadHistoryController.layer,
2621 PullRequestDiffLoader.layer,
......@@ -28,13 +23,13 @@ const snapshotLoaderLayer = Layer.mergeAll(
2823
2924type ConnectionLayerSource =
3025 | typeof Connection.layer
31 | typeof snapshotLoaderLayer
32 | typeof runtimeContextLayer
33 | typeof connectionPlatformLayer
34 | typeof backgroundActivityObserverLayer
35 | typeof backgroundActivityReporterLayer;
26 | typeof layerSnapshotLoader
27 | typeof Runtime.layer
28 | typeof ConnectionPlatform.layer
29 | typeof BackgroundActivityReporter.layerObserver
30 | typeof BackgroundActivityReporter.layer;
3631
37const providedClientConnectionLayer = snapshotLoaderLayer.pipe(
32const layerProvidedClientConnection = layerSnapshotLoader.pipe(
3833 Layer.provideMerge(
3934 Connection.layerWithOptions({
4035 environmentThemes: true,
......@@ -44,18 +39,18 @@ const providedClientConnectionLayer = snapshotLoaderLayer.pipe(
4439 ),
4540 Layer.provideMerge(
4641 Layer.mergeAll(
47 runtimeContextLayer,
48 providedConnectionPlatformLayer,
49 backgroundActivityObserverLayer,
42 Runtime.layer,
43 layerProvidedConnectionPlatform,
44 BackgroundActivityReporter.layerObserver,
5045 ),
5146 ),
5247);
5348
54const connectionLayer = backgroundActivityReporterLayer.pipe(
55 Layer.provideMerge(providedClientConnectionLayer),
49const layerConnection = BackgroundActivityReporter.layer.pipe(
50 Layer.provideMerge(layerProvidedClientConnection),
5651);
5752
5853export const connectionAtomRuntime: Atom.AtomRuntime<
5954 Layer.Success<ConnectionLayerSource>,
6055 Layer.Error<ConnectionLayerSource>
61> = Atom.runtime(connectionLayer);
56> = Atom.runtime(layerConnection);
apps/web/src/connection/storage.ts+1-1
......@@ -457,7 +457,7 @@ export function makeBrowserGitHubRoutingPermissions(
457457 });
458458}
459459
460export const connectionStorageLayer = Layer.effectContext(
460export const layer = Layer.effectContext(
461461 Effect.gen(function* () {
462462 const database = yield* Effect.acquireRelease(openDatabase(), (database) =>
463463 Effect.sync(() => database.close()),
apps/web/src/environments/primary/httpLayer.test.ts+3-3
......@@ -4,7 +4,7 @@ import * as Effect from "effect/Effect";
44import { HttpClient } from "effect/http";
55
66import { __resetDesktopPrimaryAuthForTests } from "./desktopAuth";
7import { makePrimaryEnvironmentHttpLayer } from "./httpLayer";
7import * as PrimaryEnvironmentHttpLayer from "./httpLayer";
88
99describe("primary environment HTTP layer", { concurrent: false }, () => {
1010 afterEach(() => {
......@@ -32,7 +32,7 @@ describe("primary environment HTTP layer", { concurrent: false }, () => {
3232 const request = new Request(fetchMock.mock.calls[0]?.[0], fetchMock.mock.calls[0]?.[1]);
3333 expect(request.credentials).toBe("include");
3434 expect(request.headers.get("authorization")).toBeNull();
35 }).pipe(Effect.provide(makePrimaryEnvironmentHttpLayer()));
35 }).pipe(Effect.provide(PrimaryEnvironmentHttpLayer.layerForCurrentOrigin()));
3636 });
3737
3838 it.effect("uses bearer auth without cookies for desktop-managed primaries", () => {
......@@ -60,6 +60,6 @@ describe("primary environment HTTP layer", { concurrent: false }, () => {
6060 const request = new Request(fetchMock.mock.calls[0]?.[0], fetchMock.mock.calls[0]?.[1]);
6161 expect(request.credentials).not.toBe("include");
6262 expect(request.headers.get("authorization")).toBe("Bearer desktop-bearer-token");
63 }).pipe(Effect.provide(makePrimaryEnvironmentHttpLayer()));
63 }).pipe(Effect.provide(PrimaryEnvironmentHttpLayer.layerForCurrentOrigin()));
6464 });
6565});
apps/web/src/environments/primary/httpLayer.ts+8-8
......@@ -1,4 +1,4 @@
1import { remoteHttpClientLayer } from "@t3tools/client-runtime/rpc";
1import { layerRemoteHttpClient } from "@t3tools/client-runtime/rpc";
22import * as Effect from "effect/Effect";
33import * as Layer from "effect/Layer";
44import { FetchHttpClient, HttpClient, HttpClientRequest } from "effect/http";
......@@ -30,28 +30,28 @@ function withPrimaryBearerToken(client: HttpClient.HttpClient): HttpClient.HttpC
3030 );
3131}
3232
33export function makePrimaryEnvironmentHttpLayer() {
33export function layerForCurrentOrigin() {
3434 return Layer.unwrap(
3535 Effect.sync(() => {
36 const baseLayer = remoteHttpClientLayer(globalThis.fetch);
36 const layerBase = layerRemoteHttpClient(globalThis.fetch);
3737 if (isSameOriginBrowserPrimary()) {
3838 return Layer.merge(
39 baseLayer,
39 layerBase,
4040 Layer.succeed(FetchHttpClient.RequestInit, { credentials: "include" }),
4141 );
4242 }
4343
44 const bearerClientLayer = Layer.effect(
44 const layerBearerClient = Layer.effect(
4545 HttpClient.HttpClient,
4646 Effect.map(HttpClient.HttpClient, withPrimaryBearerToken),
47 ).pipe(Layer.provide(baseLayer));
47 ).pipe(Layer.provide(layerBase));
4848
4949 return Layer.merge(
50 bearerClientLayer,
50 layerBearerClient,
5151 Layer.succeed(FetchHttpClient.RequestInit, { credentials: "omit" }),
5252 );
5353 }),
5454 );
5555}
5656
57export const primaryEnvironmentHttpLayer = makePrimaryEnvironmentHttpLayer();
57export const layer = layerForCurrentOrigin();
apps/web/src/lib/backgroundActivityReporter.ts+2-2
......@@ -164,14 +164,14 @@ export function retainedBackgroundScopes(
164164 ).filter((scope): scope is BackgroundScope => scope !== null);
165165}
166166
167export const backgroundActivityObserverLayer = Layer.succeed(
167export const layerObserver = Layer.succeed(
168168 EnvironmentRpcSubscriptionObserver,
169169 EnvironmentRpcSubscriptionObserver.of({
170170 observe: observeBackgroundActivitySubscription,
171171 }),
172172);
173173
174export const backgroundActivityReporterLayer = Layer.effectDiscard(
174export const layer = Layer.effectDiscard(
175175 Effect.gen(function* () {
176176 if (typeof window === "undefined" || typeof document === "undefined") {
177177 return;
apps/web/src/lib/runtime.ts+21-21
......@@ -3,13 +3,13 @@ import type * as Effect from "effect/Effect";
33import * as Layer from "effect/Layer";
44import * as Socket from "effect/socket/Socket";
55
6import { remoteHttpClientLayer } from "@t3tools/client-runtime/rpc";
7import { makeRelayClientTracingLayer } from "@t3tools/shared/relayTracing";
6import { layerRemoteHttpClient } from "@t3tools/client-runtime/rpc";
7import * as RelayTracing from "@t3tools/shared/relayTracing";
88import * as PrimaryEnvironmentHttpClient from "../environments/primary/httpClient";
9import { primaryEnvironmentHttpLayer } from "../environments/primary/httpLayer";
9import * as PrimaryEnvironmentHttpLayer from "../environments/primary/httpLayer";
1010
11import { browserCryptoLayer } from "../cloud/dpop";
12import { managedRelayClientLayer } from "../cloud/managedRelayLayer";
11import * as Dpop from "../cloud/dpop";
12import * as ManagedRelayLayer from "../cloud/managedRelayLayer";
1313import { resolveCloudPublicConfig, resolveRelayTracingConfig } from "../cloud/publicConfig";
1414import * as ClientTracer from "../observability/clientTracer";
1515
......@@ -17,24 +17,24 @@ function configuredRelayUrl(): string {
1717 return resolveCloudPublicConfig().relayUrl ?? "http://relay.invalid";
1818}
1919
20const httpClientLayer = remoteHttpClientLayer((input, init) => globalThis.fetch(input, init));
21const relayTracingLayer = makeRelayClientTracingLayer(resolveRelayTracingConfig(), {
20const layerHttpClient = layerRemoteHttpClient((input, init) => globalThis.fetch(input, init));
21const layerRelayTracing = RelayTracing.layer(resolveRelayTracingConfig(), {
2222 serviceName: "t3code-web",
2323 serviceVersion: import.meta.env.APP_VERSION,
2424 runtime: "browser",
2525 client: typeof window !== "undefined" && window.desktopBridge ? "desktop" : "web",
26}).pipe(Layer.provide(httpClientLayer));
26}).pipe(Layer.provide(layerHttpClient));
2727
2828type RuntimeLayerSource =
29 | typeof httpClientLayer
30 | typeof browserCryptoLayer
29 | typeof layerHttpClient
30 | typeof Dpop.layer
3131 | typeof Socket.layerWebSocketConstructorGlobal
32 | typeof relayTracingLayer
32 | typeof layerRelayTracing
3333 | typeof ClientTracer.layer
34 | ReturnType<typeof managedRelayClientLayer>;
34 | ReturnType<typeof ManagedRelayLayer.layer>;
3535
3636const primaryHttpRuntime = ManagedRuntime.make(
37 PrimaryEnvironmentHttpClient.layer.pipe(Layer.provide(primaryEnvironmentHttpLayer)),
37 PrimaryEnvironmentHttpClient.layer.pipe(Layer.provide(PrimaryEnvironmentHttpLayer.layer)),
3838);
3939
4040export type PrimaryHttpEffectRunner = <A, E>(
......@@ -54,23 +54,23 @@ export function __setPrimaryHttpRunnerForTests(runner?: PrimaryHttpEffectRunner)
5454 primaryHttpRunner = runner ?? livePrimaryHttpRunner;
5555}
5656
57const runtimeLayer = Layer.mergeAll(
58 httpClientLayer,
59 browserCryptoLayer,
57const layerRuntime = Layer.mergeAll(
58 layerHttpClient,
59 Dpop.layer,
6060 Socket.layerWebSocketConstructorGlobal,
6161 ClientTracer.layer,
62 relayTracingLayer,
63 managedRelayClientLayer(configuredRelayUrl()).pipe(
64 Layer.provide(Layer.mergeAll(httpClientLayer, browserCryptoLayer)),
62 layerRelayTracing,
63 ManagedRelayLayer.layer(configuredRelayUrl()).pipe(
64 Layer.provide(Layer.mergeAll(layerHttpClient, Dpop.layer)),
6565 ),
6666);
6767
6868export const runtime: ManagedRuntime.ManagedRuntime<
6969 Layer.Success<RuntimeLayerSource>,
7070 Layer.Error<RuntimeLayerSource>
71> = ManagedRuntime.make(runtimeLayer);
71> = ManagedRuntime.make(layerRuntime);
7272
73export const runtimeContextLayer: Layer.Layer<
73export const layer: Layer.Layer<
7474 Layer.Success<RuntimeLayerSource>,
7575 Layer.Error<RuntimeLayerSource>
7676> = Layer.effectContext(runtime.contextEffect);
apps/web/src/observability/clientTracing.ts+4-4
......@@ -9,7 +9,7 @@ import { settleAsyncResult, squashAtomCommandFailure } from "@t3tools/client-run
99import { safeErrorLogAttributes } from "@t3tools/client-runtime/errors";
1010import { resolvePrimaryEnvironmentHttpUrl } from "../environments/primary";
1111import * as ClientTracer from "./clientTracer";
12import { primaryEnvironmentHttpLayer } from "../environments/primary/httpLayer";
12import * as PrimaryEnvironmentHttpLayer from "../environments/primary/httpLayer";
1313import { isElectron } from "../env";
1414import { APP_VERSION } from "~/branding";
1515
......@@ -24,8 +24,8 @@ const CLIENT_TRACING_RESOURCE = {
2424 },
2525} as const;
2626
27const delegateRuntimeLayer = Layer.mergeAll(
28 primaryEnvironmentHttpLayer,
27const layerDelegateRuntime = Layer.mergeAll(
28 PrimaryEnvironmentHttpLayer.layer,
2929 OtlpExporter.layerFlusher,
3030 OtlpSerialization.layerJson,
3131 Layer.succeed(HttpClient.TracerDisabledWhen, () => true),
......@@ -70,7 +70,7 @@ async function applyClientTracingConfig(config: ClientTracingConfig): Promise<vo
7070
7171 await disposeTracerRuntime(previousRuntime, previousScope);
7272
73 const runtime = ManagedRuntime.make(delegateRuntimeLayer);
73 const runtime = ManagedRuntime.make(layerDelegateRuntime);
7474 const scope = runtime.runSync(Scope.make());
7575
7676 const delegateResult = await settleAsyncResult(() =>
infra/relay/alchemy.run.ts+3-3
......@@ -12,7 +12,7 @@ import { PublishClientConfig, tokenDigest } from "./src/clientConfig.ts";
1212import * as RelayDb from "./src/db.ts";
1313import { RelayObservability } from "./src/observability.ts";
1414import { ManagedEndpointZone, RelayApiZone } from "./src/zone.ts";
15import ApiLive, { Api } from "./src/worker.ts";
15import * as RelayWorker from "./src/worker.ts";
1616
1717export default Alchemy.Stack(
1818 "T3CodeRelay",
......@@ -31,7 +31,7 @@ export default Alchemy.Stack(
3131 const managedEndpointZone = yield* ManagedEndpointZone.pipe(Effect.orDie);
3232 const relayApiZone = yield* RelayApiZone.pipe(Effect.orDie);
3333 const observability = yield* RelayObservability;
34 const api = yield* Api;
34 const api = yield* RelayWorker.Api;
3535 yield* PublishClientConfig({
3636 url: api.url,
3737 mobileTracingUrl: observability.traces.otelTracesEndpoint,
......@@ -61,5 +61,5 @@ export default Alchemy.Stack(
6161 clientTracingDataset: observability.traces.name,
6262 clientTracingToken: observability.clientIngestToken.token,
6363 };
64 }).pipe(Effect.provide(ApiLive)),
64 }).pipe(Effect.provide(RelayWorker.layer)),
6565);
infra/relay/scripts/android-push-watch.ts+7-5
......@@ -86,7 +86,7 @@ const main = Effect.gen(function* () {
8686 const connection = yield* readFile(connectionPath).pipe(
8787 Effect.flatMap(Schema.decodeUnknownEffect(Schema.fromJsonString(Connection))),
8888 );
89 const socketConstructor = Layer.succeed(
89 const layerSocketConstructor = Layer.succeed(
9090 Socket.WebSocketConstructor,
9191 // Socket.makeWebSocket only ever passes its `protocols` option here.
9292 (url, protocols) =>
......@@ -94,11 +94,13 @@ const main = Effect.gen(function* () {
9494 headers: { authorization: `Bearer ${connection.bearerToken}` },
9595 }) as unknown as globalThis.WebSocket,
9696 );
97 const protocol = RpcClient.layerProtocolSocket().pipe(
98 Layer.provide(Socket.layerWebSocket(connection.wsUrl).pipe(Layer.provide(socketConstructor))),
97 const layerProtocol = RpcClient.layerProtocolSocket().pipe(
98 Layer.provide(
99 Socket.layerWebSocket(connection.wsUrl).pipe(Layer.provide(layerSocketConstructor)),
100 ),
99101 Layer.provide(RpcSerialization.layerJson),
100102 );
101 const fcm = FcmClient.layer.pipe(
103 const layerFcm = FcmClient.layer.pipe(
102104 Layer.provide(
103105 FcmAssertionSigner.layer.pipe(
104106 Layer.provide(Layer.succeed(WebCrypto.WebCrypto, { subtle: globalThis.crypto.subtle })),
......@@ -206,7 +208,7 @@ const main = Effect.gen(function* () {
206208 }),
207209 ),
208210 );
209 }).pipe(Effect.provide(Layer.mergeAll(protocol, fcm)));
211 }).pipe(Effect.provide(Layer.mergeAll(layerProtocol, layerFcm)));
210212});
211213
212214NodeRuntime.runMain(
infra/relay/src/agentActivity/AgentActivityPublisher.test.ts+6-6
......@@ -10,7 +10,7 @@ import * as AgentActivityPublisher from "./AgentActivityPublisher.ts";
1010import * as FcmDeliveries from "./FcmDeliveries.ts";
1111import * as ApnsDeliveries from "./ApnsDeliveries.ts";
1212
13const publisherLayer = AgentActivityPublisher.layer.pipe(
13const layerPublisher = AgentActivityPublisher.layer.pipe(
1414 Layer.provide(
1515 Layer.succeed(FcmDeliveries.FcmDeliveries, {
1616 enqueue: () => Effect.succeed(null),
......@@ -225,7 +225,7 @@ describe("AgentActivityPublisher", () => {
225225 });
226226 }).pipe(
227227 Effect.provide(
228 publisherLayer.pipe(
228 layerPublisher.pipe(
229229 Layer.provide(
230230 Layer.mergeAll(
231231 Layer.succeed(AgentActivityRows.AgentActivityRows, makeAgentActivityRows()),
......@@ -298,7 +298,7 @@ describe("AgentActivityPublisher", () => {
298298 });
299299 }).pipe(
300300 Effect.provide(
301 publisherLayer.pipe(
301 layerPublisher.pipe(
302302 Layer.provide(
303303 Layer.mergeAll(
304304 Layer.succeed(
......@@ -392,7 +392,7 @@ describe("AgentActivityPublisher", () => {
392392 });
393393 }).pipe(
394394 Effect.provide(
395 publisherLayer.pipe(
395 layerPublisher.pipe(
396396 Layer.provide(
397397 Layer.mergeAll(
398398 Layer.succeed(
......@@ -498,7 +498,7 @@ describe("AgentActivityPublisher", () => {
498498 });
499499 }).pipe(
500500 Effect.provide(
501 publisherLayer.pipe(
501 layerPublisher.pipe(
502502 Layer.provide(
503503 Layer.mergeAll(
504504 Layer.succeed(
......@@ -610,7 +610,7 @@ describe("AgentActivityPublisher", () => {
610610 });
611611 }).pipe(
612612 Effect.provide(
613 publisherLayer.pipe(
613 layerPublisher.pipe(
614614 Layer.provide(
615615 Layer.mergeAll(
616616 Layer.succeed(
infra/relay/src/agentActivity/ApnsClient.test.ts+7-7
......@@ -22,7 +22,7 @@ import * as ApnsProviderTokens from "./ApnsProviderTokens.ts";
2222const isApnsJwtSigningError = Schema.is(ApnsClient.ApnsJwtSigningError);
2323const isApnsHttpRequestError = Schema.is(ApnsClient.ApnsHttpRequestError);
2424
25const TestLayer = ApnsClient.layer.pipe(
25const layerTest = ApnsClient.layer.pipe(
2626 Layer.provide(ApnsProviderTokens.layer),
2727 Layer.provide(
2828 Layer.succeed(
......@@ -76,7 +76,7 @@ describe("ApnsClient", () => {
7676 },
7777 },
7878 });
79 }).pipe(Effect.provide(TestLayer)),
79 }).pipe(Effect.provide(layerTest)),
8080 );
8181
8282 it.effect("builds a low-priority update payload", () =>
......@@ -99,7 +99,7 @@ describe("ApnsClient", () => {
9999 },
100100 },
101101 });
102 }).pipe(Effect.provide(TestLayer)),
102 }).pipe(Effect.provide(layerTest)),
103103 );
104104
105105 it.effect("builds a high-priority alerting update payload when an alert is attached", () =>
......@@ -125,7 +125,7 @@ describe("ApnsClient", () => {
125125 },
126126 },
127127 });
128 }).pipe(Effect.provide(TestLayer)),
128 }).pipe(Effect.provide(layerTest)),
129129 );
130130
131131 it.effect("builds an end payload with a dismissal date", () =>
......@@ -162,7 +162,7 @@ describe("ApnsClient", () => {
162162 "dismissal-date": 15,
163163 },
164164 });
165 }).pipe(Effect.provide(TestLayer)),
165 }).pipe(Effect.provide(layerTest)),
166166 );
167167
168168 it.effect("builds a standard APNs alert payload with routing metadata", () =>
......@@ -193,7 +193,7 @@ describe("ApnsClient", () => {
193193 threadId: "thread",
194194 deepLink: "/threads/env/thread",
195195 });
196 }).pipe(Effect.provide(TestLayer)),
196 }).pipe(Effect.provide(layerTest)),
197197 );
198198
199199 it.effect("preserves JWT signing context and the crypto cause", () =>
......@@ -237,7 +237,7 @@ describe("ApnsClient", () => {
237237 cause: expect.any(Error),
238238 message: "Failed to sign APNs JWT for key key-1.",
239239 });
240 }).pipe(Effect.provide(TestLayer)),
240 }).pipe(Effect.provide(layerTest)),
241241 );
242242
243243 it.effect("preserves APNs request context and the HTTP cause", () => {
infra/relay/src/agentActivity/ApnsDeliveries.test.ts+37-37
......@@ -141,7 +141,7 @@ const target: LiveActivities.TargetRow = {
141141 last_live_activity_delivery_at: null,
142142};
143143
144function makeLayer(input: {
144function layerFor(input: {
145145 readonly attempts: Array<DeliveryAttempts.DeliveryAttemptInput>;
146146 readonly sourceJobClaims?: ReadonlyMap<string, DeliveryAttempts.DeliverySourceJobClaimResult>;
147147 readonly queuedJobs?: Array<SignedApnsDeliveryJob>;
......@@ -284,7 +284,7 @@ describe("ApnsDeliveries", () => {
284284 expect(attempts).toHaveLength(0);
285285 }).pipe(
286286 Effect.provide(
287 makeLayer({
287 layerFor({
288288 attempts,
289289 queuedJobs,
290290 config: { ...config, apns: null },
......@@ -322,7 +322,7 @@ describe("ApnsDeliveries", () => {
322322 expect(queuedJobs).toEqual([]);
323323 expect(queuedStarts).toEqual([]);
324324 expect(attempts).toEqual([]);
325 }).pipe(Effect.provide(makeLayer({ attempts, queuedJobs, queuedStarts })));
325 }).pipe(Effect.provide(layerFor({ attempts, queuedJobs, queuedStarts })));
326326 });
327327
328328 it.effect("ends the armed card when nothing remains to show", () => {
......@@ -362,7 +362,7 @@ describe("ApnsDeliveries", () => {
362362 },
363363 ]);
364364 expect(attempts).toEqual([]);
365 }).pipe(Effect.provide(makeLayer({ attempts, queuedJobs })));
365 }).pipe(Effect.provide(layerFor({ attempts, queuedJobs })));
366366 });
367367
368368 it.effect("does not queue a remote start when Live Activities are disabled", () => {
......@@ -386,7 +386,7 @@ describe("ApnsDeliveries", () => {
386386 expect(result).toBeNull();
387387 expect(queuedJobs).toEqual([]);
388388 expect(attempts).toEqual([]);
389 }).pipe(Effect.provide(makeLayer({ attempts, queuedJobs })));
389 }).pipe(Effect.provide(layerFor({ attempts, queuedJobs })));
390390 });
391391
392392 it.effect("does not queue a duplicate remote start while a start is already queued", () => {
......@@ -410,7 +410,7 @@ describe("ApnsDeliveries", () => {
410410 expect(result).toBeNull();
411411 expect(queuedJobs).toEqual([]);
412412 expect(attempts).toEqual([]);
413 }).pipe(Effect.provide(makeLayer({ attempts, queuedJobs })));
413 }).pipe(Effect.provide(layerFor({ attempts, queuedJobs })));
414414 });
415415
416416 it.effect("queues bounded Live Activity aggregate payloads", () => {
......@@ -449,7 +449,7 @@ describe("ApnsDeliveries", () => {
449449 expect(payloadAggregate?.activities[0]?.status.length).toBeLessThanOrEqual(40);
450450 expect(payloadAggregate?.activities[0]?.deepLink).toBe("/");
451451 expect(attempts).toEqual([]);
452 }).pipe(Effect.provide(makeLayer({ attempts, queuedJobs })));
452 }).pipe(Effect.provide(layerFor({ attempts, queuedJobs })));
453453 });
454454
455455 it.effect("queues Live Activity jobs with the device's APNs routing", () => {
......@@ -481,7 +481,7 @@ describe("ApnsDeliveries", () => {
481481 },
482482 },
483483 ]);
484 }).pipe(Effect.provide(makeLayer({ attempts, queuedJobs })));
484 }).pipe(Effect.provide(layerFor({ attempts, queuedJobs })));
485485 });
486486
487487 it.effect("sends signed jobs to the device's APNs environment and bundle topic", () => {
......@@ -521,7 +521,7 @@ describe("ApnsDeliveries", () => {
521521 );
522522 }).pipe(
523523 Effect.provide(
524 makeLayer({
524 layerFor({
525525 attempts,
526526 currentTargets: [
527527 { ...target, bundle_id: "com.t3tools.t3code.preview", aps_environment: "sandbox" },
......@@ -568,7 +568,7 @@ describe("ApnsDeliveries", () => {
568568 expect(result).toBeNull();
569569 expect(queuedJobs).toEqual([]);
570570 expect(attempts).toEqual([]);
571 }).pipe(Effect.provide(makeLayer({ attempts, queuedJobs })));
571 }).pipe(Effect.provide(layerFor({ attempts, queuedJobs })));
572572 },
573573 );
574574
......@@ -612,7 +612,7 @@ describe("ApnsDeliveries", () => {
612612 },
613613 },
614614 ]);
615 }).pipe(Effect.provide(makeLayer({ attempts, queuedJobs })));
615 }).pipe(Effect.provide(layerFor({ attempts, queuedJobs })));
616616 },
617617 );
618618
......@@ -641,7 +641,7 @@ describe("ApnsDeliveries", () => {
641641
642642 expect(result).toBeNull();
643643 expect(queuedJobs).toEqual([]);
644 }).pipe(Effect.provide(makeLayer({ attempts, queuedJobs })));
644 }).pipe(Effect.provide(layerFor({ attempts, queuedJobs })));
645645 },
646646 );
647647
......@@ -672,7 +672,7 @@ describe("ApnsDeliveries", () => {
672672 },
673673 ]);
674674 expect(attempts).toEqual([]);
675 }).pipe(Effect.provide(makeLayer({ attempts, queuedJobs })));
675 }).pipe(Effect.provide(layerFor({ attempts, queuedJobs })));
676676 });
677677
678678 it.effect(
......@@ -732,7 +732,7 @@ describe("ApnsDeliveries", () => {
732732 },
733733 ]);
734734 expect(attempts).toEqual([]);
735 }).pipe(Effect.provide(makeLayer({ attempts, queuedJobs })));
735 }).pipe(Effect.provide(layerFor({ attempts, queuedJobs })));
736736 },
737737 );
738738
......@@ -774,7 +774,7 @@ describe("ApnsDeliveries", () => {
774774 },
775775 ]);
776776 expect(attempts).toEqual([]);
777 }).pipe(Effect.provide(makeLayer({ attempts, queuedJobs })));
777 }).pipe(Effect.provide(layerFor({ attempts, queuedJobs })));
778778 });
779779
780780 it.effect(
......@@ -827,7 +827,7 @@ describe("ApnsDeliveries", () => {
827827 },
828828 ]);
829829 expect(attempts).toEqual([]);
830 }).pipe(Effect.provide(makeLayer({ attempts, queuedJobs })));
830 }).pipe(Effect.provide(layerFor({ attempts, queuedJobs })));
831831 },
832832 );
833833
......@@ -852,7 +852,7 @@ describe("ApnsDeliveries", () => {
852852 expect(result).toBeNull();
853853 expect(queuedJobs).toEqual([]);
854854 expect(attempts).toEqual([]);
855 }).pipe(Effect.provide(makeLayer({ attempts, queuedJobs })));
855 }).pipe(Effect.provide(layerFor({ attempts, queuedJobs })));
856856 });
857857
858858 it.effect("queues bounded alert notification payloads", () => {
......@@ -892,7 +892,7 @@ describe("ApnsDeliveries", () => {
892892 expect(notification?.body.length).toBeLessThanOrEqual(120);
893893 expect(notification?.deepLink).toBe("/");
894894 expect(attempts).toEqual([]);
895 }).pipe(Effect.provide(makeLayer({ attempts, queuedJobs })));
895 }).pipe(Effect.provide(layerFor({ attempts, queuedJobs })));
896896 });
897897
898898 it.effect("preserves the schema cause for invalid queue payloads", () => {
......@@ -908,7 +908,7 @@ describe("ApnsDeliveries", () => {
908908 message: "Invalid APNs delivery queue job with object payload.",
909909 });
910910 expect(error.cause).toMatchObject({ _tag: "SchemaError" });
911 }).pipe(Effect.provide(makeLayer({ attempts })));
911 }).pipe(Effect.provide(layerFor({ attempts })));
912912 });
913913
914914 it.effect("skips a queued start when the user no longer has live work", () => {
......@@ -950,7 +950,7 @@ describe("ApnsDeliveries", () => {
950950 },
951951 ]);
952952 expect(clearedStarts).toMatchObject([{ userId: target.user_id, deviceId: target.device_id }]);
953 }).pipe(Effect.provide(makeLayer({ attempts, clearedStarts, activityStates: [] })));
953 }).pipe(Effect.provide(layerFor({ attempts, clearedStarts, activityStates: [] })));
954954 });
955955
956956 it.effect("processes signed jobs through APNs and records attempts", () => {
......@@ -1012,7 +1012,7 @@ describe("ApnsDeliveries", () => {
10121012 }).pipe(
10131013 Effect.provide(
10141014 Layer.mergeAll(
1015 makeLayer({ attempts }),
1015 layerFor({ attempts }),
10161016 Logger.layer([logger], { mergeWithExisting: false }),
10171017 ),
10181018 ),
......@@ -1047,7 +1047,7 @@ describe("ApnsDeliveries", () => {
10471047 );
10481048 const started = yield* Deferred.make<void>();
10491049 const results: Array<{ deviceId: string; ok: boolean }> = [];
1050 const layer = makeLayer({
1050 const layer = layerFor({
10511051 attempts,
10521052 markedDeliveries,
10531053 currentTargets: [target, secondTarget],
......@@ -1138,7 +1138,7 @@ describe("ApnsDeliveries", () => {
11381138 ]);
11391139 }).pipe(
11401140 Effect.provide(
1141 makeLayer({
1141 layerFor({
11421142 attempts,
11431143 currentTargets: [
11441144 {
......@@ -1197,7 +1197,7 @@ describe("ApnsDeliveries", () => {
11971197 expect(attempts).toEqual([]);
11981198 }).pipe(
11991199 Effect.provide(
1200 makeLayer({
1200 layerFor({
12011201 attempts,
12021202 sourceJobClaims: new Map([["job-push-duplicate", "completed"]]),
12031203 config: signingConfig,
......@@ -1251,7 +1251,7 @@ describe("ApnsDeliveries", () => {
12511251 ]);
12521252 }).pipe(
12531253 Effect.provide(
1254 makeLayer({
1254 layerFor({
12551255 attempts,
12561256 config: signingConfig,
12571257 execute,
......@@ -1311,7 +1311,7 @@ describe("ApnsDeliveries", () => {
13111311 ]);
13121312 }).pipe(
13131313 Effect.provide(
1314 makeLayer({
1314 layerFor({
13151315 attempts,
13161316 currentTargets: [
13171317 {
......@@ -1383,7 +1383,7 @@ describe("ApnsDeliveries", () => {
13831383 ]);
13841384 }).pipe(
13851385 Effect.provide(
1386 makeLayer({
1386 layerFor({
13871387 attempts,
13881388 activityStates: [
13891389 {
......@@ -1452,7 +1452,7 @@ describe("ApnsDeliveries", () => {
14521452 ]);
14531453 }).pipe(
14541454 Effect.provide(
1455 makeLayer({
1455 layerFor({
14561456 attempts,
14571457 currentTargets: [{ ...target, push_token: "apns-device-token" }],
14581458 currentActivityStates: [
......@@ -1512,7 +1512,7 @@ describe("ApnsDeliveries", () => {
15121512 expect(attempts).toEqual([]);
15131513 }).pipe(
15141514 Effect.provide(
1515 makeLayer({
1515 layerFor({
15161516 attempts,
15171517 sourceJobClaims: new Map([["job-push-in-flight", "in_flight"]]),
15181518 config: signingConfig,
......@@ -1573,7 +1573,7 @@ describe("ApnsDeliveries", () => {
15731573 ]);
15741574 }).pipe(
15751575 Effect.provide(
1576 makeLayer({
1576 layerFor({
15771577 attempts,
15781578 invalidatedTokens,
15791579 currentTargets: [
......@@ -1623,7 +1623,7 @@ describe("ApnsDeliveries", () => {
16231623 ]);
16241624 }).pipe(
16251625 Effect.provide(
1626 makeLayer({
1626 layerFor({
16271627 attempts,
16281628 clearedStarts,
16291629 activityStates: [{ ...state, updatedAt: "9999-01-01T00:00:00.000Z" }],
......@@ -1676,7 +1676,7 @@ describe("ApnsDeliveries", () => {
16761676 ]);
16771677 }).pipe(
16781678 Effect.provide(
1679 makeLayer({
1679 layerFor({
16801680 attempts,
16811681 invalidatedTokens,
16821682 activityStates: [{ ...state, updatedAt: "9999-01-01T00:00:00.000Z" }],
......@@ -1730,7 +1730,7 @@ describe("ApnsDeliveries", () => {
17301730 },
17311731 ]);
17321732 }).pipe(
1733 Effect.provide(makeLayer({ attempts, invalidatedTokens, config: signingConfig, execute })),
1733 Effect.provide(layerFor({ attempts, invalidatedTokens, config: signingConfig, execute })),
17341734 );
17351735 });
17361736});
......@@ -1938,7 +1938,7 @@ describe("queued iOS alert policy", () => {
19381938 expect(sent).toBe(scenario === "enabled" ? 1 : 0);
19391939 }).pipe(
19401940 Effect.provide(
1941 makeLayer({
1941 layerFor({
19421942 attempts: [],
19431943 config: signingConfig,
19441944 currentTargets: [
......@@ -1984,7 +1984,7 @@ describe("fast completion delivery", () => {
19841984 expect(
19851985 queuedJobs.some((x) => x.payload.alert !== null && x.payload.alert !== undefined),
19861986 ).toBe(true);
1987 }).pipe(Effect.provide(makeLayer({ attempts: [], queuedJobs, currentTargets: [device] })));
1987 }).pipe(Effect.provide(layerFor({ attempts: [], queuedJobs, currentTargets: [device] })));
19881988 });
19891989 it.effect("replays a newly visible completion without alerting", () => {
19901990 const queuedJobs: SignedApnsDeliveryJob[] = [];
......@@ -2010,7 +2010,7 @@ describe("fast completion delivery", () => {
20102010 });
20112011 expect(queuedJobs).toHaveLength(1);
20122012 expect(queuedJobs[0]?.payload.alert).toBeUndefined();
2013 }).pipe(Effect.provide(makeLayer({ attempts: [], queuedJobs })));
2013 }).pipe(Effect.provide(layerFor({ attempts: [], queuedJobs })));
20142014 });
20152015});
20162016
......@@ -2063,7 +2063,7 @@ describe("signed APNs registration metadata", () => {
20632063 );
20642064 }).pipe(
20652065 Effect.provide(
2066 makeLayer({
2066 layerFor({
20672067 attempts,
20682068 config: signingConfig,
20692069 currentTargets: [
infra/relay/src/agentActivity/FcmClient.test.ts+4-4
......@@ -55,7 +55,7 @@ const input = {
5555 alert: false,
5656};
5757
58function testLayer(requests: HttpClientRequest.HttpClientRequest[], responses: Response[]) {
58function layerTest(requests: HttpClientRequest.HttpClientRequest[], responses: Response[]) {
5959 const http = HttpClient.make((request) => {
6060 requests.push(request);
6161 const response = responses.shift();
......@@ -198,7 +198,7 @@ describe("FCM delivery", () => {
198198 });
199199 }).pipe(
200200 Effect.provide(
201 testLayer(requests, [
201 layerTest(requests, [
202202 Response.json({ access_token: "access-token" }),
203203 Response.json({ name: "one" }),
204204 Response.json({ name: "two" }),
......@@ -219,7 +219,7 @@ describe("FCM delivery", () => {
219219 expect(requests[3]!.headers.authorization).toBe("Bearer fresh-token");
220220 }).pipe(
221221 Effect.provide(
222 testLayer(requests, [
222 layerTest(requests, [
223223 Response.json({ access_token: "old-token" }),
224224 Response.json({}, { status: 401 }),
225225 Response.json({ access_token: "fresh-token" }),
......@@ -249,6 +249,6 @@ describe("FCM delivery", () => {
249249 .pipe(Effect.flip);
250250 expect(error.operation).toBe("send");
251251 expect(requests).toHaveLength(0);
252 }).pipe(Effect.provide(testLayer(requests, [])));
252 }).pipe(Effect.provide(layerTest(requests, [])));
253253 });
254254});
infra/relay/src/agentActivity/FcmDeliveries.test.ts+2-2
......@@ -98,7 +98,7 @@ function harness() {
9898 linked: true,
9999 deliveryFailure: null as FcmClient.FcmClientError | null,
100100 };
101 const services = Layer.mergeAll(
101 const layerServices = Layer.mergeAll(
102102 NodeCryptoLayer.layer,
103103 Layer.succeed(RelayConfiguration.RelayConfiguration, config),
104104 Layer.succeed(FcmDeliveryQueueSender.FcmDeliveryQueueSender, {
......@@ -198,7 +198,7 @@ function harness() {
198198 queued,
199199 marked,
200200 current,
201 layer: FcmDeliveries.layer.pipe(Layer.provide(services)),
201 layer: FcmDeliveries.layer.pipe(Layer.provide(layerServices)),
202202 job: {
203203 userId: "user",
204204 deviceId: "phone",
infra/relay/src/agentActivity/MobileRegistrations.test.ts+4-4
......@@ -20,7 +20,7 @@ import * as RelayConfiguration from "../Config.ts";
2020import * as AgentActivityPublisher from "./AgentActivityPublisher.ts";
2121import * as FcmDeliveries from "./FcmDeliveries.ts";
2222
23const publisherLayer = AgentActivityPublisher.layer.pipe(
23const layerPublisher = AgentActivityPublisher.layer.pipe(
2424 Layer.provide(
2525 Layer.succeed(FcmDeliveries.FcmDeliveries, {
2626 enqueue: () => Effect.succeed(null),
......@@ -153,13 +153,13 @@ const config = RelayConfiguration.RelayConfiguration.of({
153153 managedEndpointNamespace: undefined,
154154});
155155
156function makeRegistrationReplayLayer(input: {
156function layerRegistrationReplay(input: {
157157 readonly devices: Devices.Devices["Service"];
158158 readonly liveActivities: LiveActivities.LiveActivities["Service"];
159159 readonly queuedJobs: Array<SignedApnsDeliveryJob>;
160160}) {
161161 return MobileRegistrations.layer.pipe(
162 Layer.provide(publisherLayer),
162 Layer.provide(layerPublisher),
163163 Layer.provide(
164164 ApnsDeliveries.layer.pipe(
165165 Layer.provide(ApnsClient.layer.pipe(Layer.provide(ApnsProviderTokens.layer))),
......@@ -490,7 +490,7 @@ describe("MobileRegistrations", () => {
490490 expect(registeredDevices).toHaveLength(1);
491491 expect(queuedStarts).toEqual([]);
492492 expect(queuedJobs).toEqual([]);
493 }).pipe(Effect.provide(makeRegistrationReplayLayer({ devices, liveActivities, queuedJobs })));
493 }).pipe(Effect.provide(layerRegistrationReplay({ devices, liveActivities, queuedJobs })));
494494 },
495495 );
496496});
infra/relay/src/environments/EnvironmentConnector.test.ts+16-16
......@@ -156,7 +156,7 @@ function signHealthResponse(
156156 };
157157}
158158
159function connectorTestLayer(
159function layerConnectorTest(
160160 execute: (
161161 request: HttpClientRequest.HttpClientRequest,
162162 ) => Effect.Effect<HttpClientResponse.HttpClientResponse>,
......@@ -267,7 +267,7 @@ describe("EnvironmentConnector", () => {
267267 });
268268 }).pipe(
269269 Effect.provide(
270 connectorTestLayer(execute, {
270 layerConnectorTest(execute, {
271271 links: {
272272 ...links,
273273 getForUser: (input) => waitForPeer.pipe(Effect.andThen(links.getForUser(input))),
......@@ -326,7 +326,7 @@ describe("EnvironmentConnector", () => {
326326 label: "Connector Test Environment",
327327 },
328328 });
329 }).pipe(Effect.provide(connectorTestLayer(execute)));
329 }).pipe(Effect.provide(layerConnectorTest(execute)));
330330 });
331331
332332 it.effect("rejects manual endpoints before sending a health request", () => {
......@@ -359,7 +359,7 @@ describe("EnvironmentConnector", () => {
359359 expect(requestCount).toBe(0);
360360 }).pipe(
361361 Effect.provide(
362 connectorTestLayer(execute, {
362 layerConnectorTest(execute, {
363363 links: makeLinks({
364364 endpoint: {
365365 httpBaseUrl: "https://127.0.0.1/",
......@@ -424,7 +424,7 @@ describe("EnvironmentConnector", () => {
424424 expect(requestCount).toBe(0);
425425 }).pipe(
426426 Effect.provide(
427 connectorTestLayer(execute, {
427 layerConnectorTest(execute, {
428428 links: makeLinks({
429429 endpoint: {
430430 httpBaseUrl: "https://attacker.example.test/",
......@@ -468,7 +468,7 @@ describe("EnvironmentConnector", () => {
468468 expect(requestCount).toBe(0);
469469 }).pipe(
470470 Effect.provide(
471 connectorTestLayer(execute, {
471 layerConnectorTest(execute, {
472472 allocations: makeAllocations({
473473 userId: "user_123",
474474 environmentId: "env-connector-test",
......@@ -519,7 +519,7 @@ describe("EnvironmentConnector", () => {
519519 if (result._tag === "Failure") {
520520 expect(result.cause.toString()).toContain("EnvironmentMintResponseInvalid");
521521 }
522 }).pipe(Effect.provide(connectorTestLayer(execute)));
522 }).pipe(Effect.provide(layerConnectorTest(execute)));
523523 });
524524
525525 it.effect("reports offline status when the managed endpoint health request fails", () => {
......@@ -550,7 +550,7 @@ describe("EnvironmentConnector", () => {
550550 error: "Managed endpoint health request failed: Environment is unavailable.",
551551 traceId: expect.any(String),
552552 });
553 }).pipe(Effect.provide(connectorTestLayer(execute)));
553 }).pipe(Effect.provide(layerConnectorTest(execute)));
554554 });
555555
556556 it.effect("rejects health responses with a mismatched top-level environment id", () => {
......@@ -581,7 +581,7 @@ describe("EnvironmentConnector", () => {
581581 if (result._tag === "Failure") {
582582 expect(result.cause.toString()).toContain("EnvironmentMintResponseInvalid");
583583 }
584 }).pipe(Effect.provide(connectorTestLayer(execute)));
584 }).pipe(Effect.provide(layerConnectorTest(execute)));
585585 });
586586
587587 it.effect("rejects health responses with an unsigned top-level descriptor mutation", () => {
......@@ -617,7 +617,7 @@ describe("EnvironmentConnector", () => {
617617 if (result._tag === "Failure") {
618618 expect(result.cause.toString()).toContain("EnvironmentMintResponseInvalid");
619619 }
620 }).pipe(Effect.provide(connectorTestLayer(execute)));
620 }).pipe(Effect.provide(layerConnectorTest(execute)));
621621 });
622622
623623 it.effect("rejects health responses when the linked environment public key is malformed", () => {
......@@ -645,7 +645,7 @@ describe("EnvironmentConnector", () => {
645645 }
646646 }).pipe(
647647 Effect.provide(
648 connectorTestLayer(execute, {
648 layerConnectorTest(execute, {
649649 links: makeLinks({
650650 environmentPublicKey: "not a pem public key",
651651 }),
......@@ -696,7 +696,7 @@ describe("EnvironmentConnector", () => {
696696 wsBaseUrl: "wss://env.example.test/ws",
697697 },
698698 });
699 }).pipe(Effect.provide(connectorTestLayer(execute)));
699 }).pipe(Effect.provide(layerConnectorTest(execute)));
700700 });
701701
702702 it.effect("only accepts mint responses signed by the user's linked environment key", () => {
......@@ -725,7 +725,7 @@ describe("EnvironmentConnector", () => {
725725 if (result._tag === "Failure") {
726726 expect(result.cause.toString()).toContain("EnvironmentMintResponseInvalid");
727727 }
728 }).pipe(Effect.provide(connectorTestLayer(execute)));
728 }).pipe(Effect.provide(layerConnectorTest(execute)));
729729 });
730730
731731 it.effect("rejects mint responses when the linked environment public key is malformed", () => {
......@@ -754,7 +754,7 @@ describe("EnvironmentConnector", () => {
754754 }
755755 }).pipe(
756756 Effect.provide(
757 connectorTestLayer(execute, {
757 layerConnectorTest(execute, {
758758 links: makeLinks({
759759 environmentPublicKey: "not a pem public key",
760760 }),
......@@ -790,7 +790,7 @@ describe("EnvironmentConnector", () => {
790790 if (result._tag === "Failure") {
791791 expect(result.cause.toString()).toContain("EnvironmentMintResponseInvalid");
792792 }
793 }).pipe(Effect.provide(connectorTestLayer(execute)));
793 }).pipe(Effect.provide(layerConnectorTest(execute)));
794794 });
795795
796796 it.effect("times out hung managed endpoint mint requests", () => {
......@@ -827,6 +827,6 @@ describe("EnvironmentConnector", () => {
827827 timeoutMs: EnvironmentConnector.ENVIRONMENT_MINT_REQUEST_TIMEOUT_MS,
828828 });
829829 }
830 }).pipe(Effect.provide(Layer.merge(TestClock.layer(), connectorTestLayer(execute))));
830 }).pipe(Effect.provide(Layer.merge(TestClock.layer(), layerConnectorTest(execute))));
831831 });
832832});
infra/relay/src/environments/EnvironmentLinker.test.ts+5-5
......@@ -106,7 +106,7 @@ const makeRequest = Effect.gen(function* () {
106106 };
107107});
108108
109function testLayer(input?: {
109function layerTest(input?: {
110110 readonly upsert?: EnvironmentLinks.EnvironmentLinks["Service"]["upsert"];
111111 readonly consume?: DpopProofs.DpopProofReplay["Service"]["consume"];
112112 readonly deprovision?: ManagedEndpointProvider.ManagedEndpointProvider["Service"]["deprovision"];
......@@ -167,7 +167,7 @@ describe("EnvironmentLinker", () => {
167167 expect(persistedEnvironmentId).toBe(payload.environmentId);
168168 }).pipe(
169169 Effect.provide(
170 testLayer({
170 layerTest({
171171 upsert: (input) =>
172172 Effect.sync(() => {
173173 persistedEnvironmentId = input.proof.environmentId;
......@@ -236,7 +236,7 @@ describe("EnvironmentLinker", () => {
236236 expect(deprovisionedEnvironmentId).toBe("env-link-test");
237237 }).pipe(
238238 Effect.provide(
239 testLayer({
239 layerTest({
240240 upsert: (input) =>
241241 Effect.sync(() => {
242242 persistedEndpoint = input.endpoint.httpBaseUrl;
......@@ -277,7 +277,7 @@ describe("EnvironmentLinker", () => {
277277 expect(persisted).toBe(false);
278278 }).pipe(
279279 Effect.provide(
280 testLayer({
280 layerTest({
281281 upsert: () =>
282282 Effect.sync(() => {
283283 persisted = true;
......@@ -304,6 +304,6 @@ describe("EnvironmentLinker", () => {
304304 });
305305 }
306306 }
307 }).pipe(Effect.provide(testLayer({ consume: () => Effect.succeed(false) }))),
307 }).pipe(Effect.provide(layerTest({ consume: () => Effect.succeed(false) }))),
308308 );
309309});
infra/relay/src/environments/ManagedEndpointProvider.test.ts+41-41
......@@ -346,7 +346,7 @@ function makeTunnelLimits(
346346 });
347347}
348348
349function providerLayer(
349function layerProvider(
350350 tunnelClient = makeTunnelClient(),
351351 dnsClient = makeDnsClient(),
352352 allocations = makeAllocations(),
......@@ -492,7 +492,7 @@ describe("ManagedEndpointProvider", () => {
492492 ]);
493493 }).pipe(
494494 Effect.provide(
495 providerLayer(
495 layerProvider(
496496 makeTunnelClient(tunnelCalls),
497497 makeDnsClient(dnsCalls),
498498 makeAllocations(allocationCalls),
......@@ -515,7 +515,7 @@ describe("ManagedEndpointProvider", () => {
515515 expect(limitCalls).toEqual([{ userId: "user_ABC", environmentId: "env_ABC" }]);
516516 }).pipe(
517517 Effect.provide(
518 providerLayer(
518 layerProvider(
519519 makeTunnelClient(),
520520 makeDnsClient(),
521521 makeAllocations(),
......@@ -552,7 +552,7 @@ describe("ManagedEndpointProvider", () => {
552552 expect(allocationCalls).toEqual([]);
553553 }).pipe(
554554 Effect.provide(
555 providerLayer(
555 layerProvider(
556556 makeTunnelClient(tunnelCalls),
557557 makeDnsClient(dnsCalls),
558558 makeAllocations(allocationCalls),
......@@ -607,7 +607,7 @@ describe("ManagedEndpointProvider", () => {
607607 name: requestedName,
608608 configSrc: "cloudflare",
609609 });
610 }).pipe(Effect.provide(providerLayer(makeTunnelClient(tunnelCalls))));
610 }).pipe(Effect.provide(layerProvider(makeTunnelClient(tunnelCalls))));
611611 });
612612
613613 it.effect("formats IPv6 loopback origins as valid Cloudflare ingress service URLs", () => {
......@@ -633,7 +633,7 @@ describe("ManagedEndpointProvider", () => {
633633 ],
634634 },
635635 });
636 }).pipe(Effect.provide(providerLayer(makeTunnelClient(tunnelCalls))));
636 }).pipe(Effect.provide(layerProvider(makeTunnelClient(tunnelCalls))));
637637 });
638638
639639 it.effect("rejects non-loopback managed endpoint origins before calling Cloudflare", () => {
......@@ -660,7 +660,7 @@ describe("ManagedEndpointProvider", () => {
660660 port: 3773,
661661 });
662662 }
663 }).pipe(Effect.provide(providerLayer(makeTunnelClient(), makeDnsClient(dnsCalls))));
663 }).pipe(Effect.provide(layerProvider(makeTunnelClient(), makeDnsClient(dnsCalls))));
664664 });
665665
666666 it.effect("rejects invalid managed endpoint origin ports before calling Cloudflare", () => {
......@@ -681,7 +681,7 @@ describe("ManagedEndpointProvider", () => {
681681 if (result._tag === "Failure") {
682682 expect(result.failure._tag).toBe("ManagedEndpointOriginNotAllowed");
683683 }
684 }).pipe(Effect.provide(providerLayer(makeTunnelClient(), makeDnsClient(dnsCalls))));
684 }).pipe(Effect.provide(layerProvider(makeTunnelClient(), makeDnsClient(dnsCalls))));
685685 });
686686
687687 it.effect("reconciles an existing same-host DNS record through the DNS client", () => {
......@@ -698,7 +698,7 @@ describe("ManagedEndpointProvider", () => {
698698 expect(dnsCalls[1]?.input).toMatchObject({ dnsRecordId: "existing-record-id" });
699699 }).pipe(
700700 Effect.provide(
701 providerLayer(makeTunnelClient(), makeDnsClient(dnsCalls, [{ id: "existing-record-id" }])),
701 layerProvider(makeTunnelClient(), makeDnsClient(dnsCalls, [{ id: "existing-record-id" }])),
702702 ),
703703 );
704704 });
......@@ -707,7 +707,7 @@ describe("ManagedEndpointProvider", () => {
707707 const tunnelCalls: TunnelCall[] = [];
708708 const dnsCalls: DnsCall[] = [];
709709 const allocationCalls: AllocationCall[] = [];
710 const layer = providerLayer(
710 const layer = layerProvider(
711711 makePersistentTunnelClient(tunnelCalls),
712712 makeDnsClient(dnsCalls),
713713 makeAllocations(allocationCalls),
......@@ -754,7 +754,7 @@ describe("ManagedEndpointProvider", () => {
754754 const dnsCalls: DnsCall[] = [];
755755 const allocationCalls: AllocationCall[] = [];
756756 const dnsClient = makeDnsClient(dnsCalls);
757 const layer = providerLayer(
757 const layer = layerProvider(
758758 makePersistentTunnelClient(),
759759 dnsClient,
760760 makeAllocations(allocationCalls),
......@@ -810,7 +810,7 @@ describe("ManagedEndpointProvider", () => {
810810 deleteRecord: () => Effect.void,
811811 });
812812 const allocations = makeAllocations();
813 const layer = providerLayer(makePersistentTunnelClient(), dnsClient, allocations);
813 const layer = layerProvider(makePersistentTunnelClient(), dnsClient, allocations);
814814
815815 return Effect.gen(function* () {
816816 const provider = yield* ManagedEndpointProvider.ManagedEndpointProvider;
......@@ -846,7 +846,7 @@ describe("ManagedEndpointProvider", () => {
846846 const tunnelCalls: TunnelCall[] = [];
847847 const dnsCalls: DnsCall[] = [];
848848 const allocationCalls: AllocationCall[] = [];
849 const layer = providerLayer(
849 const layer = layerProvider(
850850 makePersistentTunnelClient(tunnelCalls),
851851 makeDnsClient(dnsCalls),
852852 makeAllocations(allocationCalls),
......@@ -890,7 +890,7 @@ describe("ManagedEndpointProvider", () => {
890890 const tunnelCalls: TunnelCall[] = [];
891891 const dnsCalls: DnsCall[] = [];
892892 const allocationCalls: AllocationCall[] = [];
893 const layer = providerLayer(
893 const layer = layerProvider(
894894 makePersistentTunnelClient(tunnelCalls),
895895 makeDnsClient(dnsCalls),
896896 makeAllocations(allocationCalls),
......@@ -931,7 +931,7 @@ describe("ManagedEndpointProvider", () => {
931931 const tunnelCalls: TunnelCall[] = [];
932932 const dnsCalls: DnsCall[] = [];
933933 const allocationCalls: AllocationCall[] = [];
934 const layer = providerLayer(
934 const layer = layerProvider(
935935 makePersistentTunnelClient(tunnelCalls),
936936 makeDnsClient(dnsCalls),
937937 makeAllocations(allocationCalls),
......@@ -974,7 +974,7 @@ describe("ManagedEndpointProvider", () => {
974974 it.effect("treats an environment without a recorded tunnel as already released", () => {
975975 const tunnelCalls: TunnelCall[] = [];
976976 const dnsCalls: DnsCall[] = [];
977 const layer = providerLayer(
977 const layer = layerProvider(
978978 makePersistentTunnelClient(tunnelCalls),
979979 makeDnsClient(dnsCalls),
980980 makeAllocations(),
......@@ -999,7 +999,7 @@ describe("ManagedEndpointProvider", () => {
999999 ...allocations,
10001000 claimRelease: () => Effect.succeed(null),
10011001 });
1002 const layer = providerLayer(makePersistentTunnelClient(tunnelCalls), makeDnsClient(), outdated);
1002 const layer = layerProvider(makePersistentTunnelClient(tunnelCalls), makeDnsClient(), outdated);
10031003
10041004 return Effect.gen(function* () {
10051005 const provider = yield* ManagedEndpointProvider.ManagedEndpointProvider;
......@@ -1024,7 +1024,7 @@ describe("ManagedEndpointProvider", () => {
10241024
10251025 it.effect("does not release a tunnel when the requested tunnel id is outdated", () => {
10261026 const tunnelCalls: TunnelCall[] = [];
1027 const layer = providerLayer(
1027 const layer = layerProvider(
10281028 makePersistentTunnelClient(tunnelCalls),
10291029 makeDnsClient(),
10301030 makeAllocations(),
......@@ -1065,7 +1065,7 @@ describe("ManagedEndpointProvider", () => {
10651065 }),
10661066 ),
10671067 });
1068 const layer = providerLayer(makePersistentTunnelClient(tunnelCalls), makeDnsClient(), replaced);
1068 const layer = layerProvider(makePersistentTunnelClient(tunnelCalls), makeDnsClient(), replaced);
10691069
10701070 return Effect.gen(function* () {
10711071 const provider = yield* ManagedEndpointProvider.ManagedEndpointProvider;
......@@ -1082,7 +1082,7 @@ describe("ManagedEndpointProvider", () => {
10821082
10831083 it.effect("does no Cloudflare work when the registered origin is unchanged", () => {
10841084 const tunnelCalls: TunnelCall[] = [];
1085 const layer = providerLayer(makePersistentTunnelClient(tunnelCalls));
1085 const layer = layerProvider(makePersistentTunnelClient(tunnelCalls));
10861086
10871087 return Effect.gen(function* () {
10881088 const provider = yield* ManagedEndpointProvider.ManagedEndpointProvider;
......@@ -1105,7 +1105,7 @@ describe("ManagedEndpointProvider", () => {
11051105
11061106 it.effect("updates Cloudflare ingress once when the registered port changes", () => {
11071107 const tunnelCalls: TunnelCall[] = [];
1108 const layer = providerLayer(makePersistentTunnelClient(tunnelCalls));
1108 const layer = layerProvider(makePersistentTunnelClient(tunnelCalls));
11091109
11101110 return Effect.gen(function* () {
11111111 const provider = yield* ManagedEndpointProvider.ManagedEndpointProvider;
......@@ -1161,7 +1161,7 @@ describe("ManagedEndpointProvider", () => {
11611161 )
11621162 : baseTunnelClient.putConfiguration(tunnelId, tunnelConfig),
11631163 });
1164 const layer = providerLayer(tunnelClient);
1164 const layer = layerProvider(tunnelClient);
11651165
11661166 return Effect.gen(function* () {
11671167 const provider = yield* ManagedEndpointProvider.ManagedEndpointProvider;
......@@ -1191,7 +1191,7 @@ describe("ManagedEndpointProvider", () => {
11911191 withClaimedTunnel: (input, effect) =>
11921192 loseClaim ? Effect.succeedNone : allocations.withClaimedTunnel(input, effect),
11931193 });
1194 const layer = providerLayer(makePersistentTunnelClient(), makeDnsClient(), changed);
1194 const layer = layerProvider(makePersistentTunnelClient(), makeDnsClient(), changed);
11951195
11961196 return Effect.gen(function* () {
11971197 const provider = yield* ManagedEndpointProvider.ManagedEndpointProvider;
......@@ -1218,7 +1218,7 @@ describe("ManagedEndpointProvider", () => {
12181218 });
12191219
12201220 it.effect("rejects an active endpoint that does not match the allocation hostname", () => {
1221 const layer = providerLayer(makePersistentTunnelClient());
1221 const layer = layerProvider(makePersistentTunnelClient());
12221222
12231223 return Effect.gen(function* () {
12241224 const provider = yield* ManagedEndpointProvider.ManagedEndpointProvider;
......@@ -1263,7 +1263,7 @@ describe("ManagedEndpointProvider", () => {
12631263 return yield* allocations.recordTunnel(input);
12641264 }),
12651265 });
1266 const layer = providerLayer(
1266 const layer = layerProvider(
12671267 makePersistentTunnelClient(tunnelCalls),
12681268 makeDnsClient(),
12691269 changed,
......@@ -1297,7 +1297,7 @@ describe("ManagedEndpointProvider", () => {
12971297 ...allocations,
12981298 recordDns: () => Effect.succeed(null),
12991299 });
1300 const layer = providerLayer(makePersistentTunnelClient(), makeDnsClient(), changed);
1300 const layer = layerProvider(makePersistentTunnelClient(), makeDnsClient(), changed);
13011301
13021302 return Effect.gen(function* () {
13031303 const provider = yield* ManagedEndpointProvider.ManagedEndpointProvider;
......@@ -1323,7 +1323,7 @@ describe("ManagedEndpointProvider", () => {
13231323 ...allocations,
13241324 withClaimedTunnel: () => Effect.succeedNone,
13251325 });
1326 const layer = providerLayer(makePersistentTunnelClient(tunnelCalls), makeDnsClient(), changed);
1326 const layer = layerProvider(makePersistentTunnelClient(tunnelCalls), makeDnsClient(), changed);
13271327
13281328 return Effect.gen(function* () {
13291329 const provider = yield* ManagedEndpointProvider.ManagedEndpointProvider;
......@@ -1353,7 +1353,7 @@ describe("ManagedEndpointProvider", () => {
13531353 withClaimedTunnel: (input, effect) =>
13541354 ++lockCount === 1 ? allocations.withClaimedTunnel(input, effect) : Effect.succeedNone,
13551355 });
1356 const layer = providerLayer(makePersistentTunnelClient(), makeDnsClient(dnsCalls), changed);
1356 const layer = layerProvider(makePersistentTunnelClient(), makeDnsClient(dnsCalls), changed);
13571357
13581358 return Effect.gen(function* () {
13591359 const provider = yield* ManagedEndpointProvider.ManagedEndpointProvider;
......@@ -1379,7 +1379,7 @@ describe("ManagedEndpointProvider", () => {
13791379 ...allocations,
13801380 markReady: () => Effect.succeed(false),
13811381 });
1382 const layer = providerLayer(makePersistentTunnelClient(), makeDnsClient(), changed);
1382 const layer = layerProvider(makePersistentTunnelClient(), makeDnsClient(), changed);
13831383
13841384 return Effect.gen(function* () {
13851385 const provider = yield* ManagedEndpointProvider.ManagedEndpointProvider;
......@@ -1411,7 +1411,7 @@ describe("ManagedEndpointProvider", () => {
14111411 connsInactiveAt: null,
14121412 }),
14131413 });
1414 const layer = providerLayer(tunnelClient, makeDnsClient(), makeAllocations());
1414 const layer = layerProvider(tunnelClient, makeDnsClient(), makeAllocations());
14151415
14161416 return Effect.gen(function* () {
14171417 const provider = yield* ManagedEndpointProvider.ManagedEndpointProvider;
......@@ -1454,7 +1454,7 @@ describe("ManagedEndpointProvider", () => {
14541454 ),
14551455 ),
14561456 });
1457 const layer = providerLayer(makePersistentTunnelClient(tunnelCalls), makeDnsClient(), replaced);
1457 const layer = layerProvider(makePersistentTunnelClient(tunnelCalls), makeDnsClient(), replaced);
14581458
14591459 return Effect.gen(function* () {
14601460 const provider = yield* ManagedEndpointProvider.ManagedEndpointProvider;
......@@ -1489,7 +1489,7 @@ describe("ManagedEndpointProvider", () => {
14891489 }),
14901490 ),
14911491 });
1492 const layer = providerLayer(tunnelClient, makeDnsClient(), makeAllocations());
1492 const layer = layerProvider(tunnelClient, makeDnsClient(), makeAllocations());
14931493
14941494 return Effect.gen(function* () {
14951495 const provider = yield* ManagedEndpointProvider.ManagedEndpointProvider;
......@@ -1512,7 +1512,7 @@ describe("ManagedEndpointProvider", () => {
15121512 ...makeTunnelClient(),
15131513 delete: () => Effect.fail(failure),
15141514 });
1515 const layer = providerLayer(tunnelClient, makeDnsClient(), makeAllocations());
1515 const layer = layerProvider(tunnelClient, makeDnsClient(), makeAllocations());
15161516
15171517 return Effect.gen(function* () {
15181518 const provider = yield* ManagedEndpointProvider.ManagedEndpointProvider;
......@@ -1551,7 +1551,7 @@ describe("ManagedEndpointProvider", () => {
15511551 }),
15521552 ),
15531553 });
1554 const layer = providerLayer(tunnelClient, makeDnsClient(), makeAllocations());
1554 const layer = layerProvider(tunnelClient, makeDnsClient(), makeAllocations());
15551555
15561556 return Effect.gen(function* () {
15571557 const provider = yield* ManagedEndpointProvider.ManagedEndpointProvider;
......@@ -1568,7 +1568,7 @@ describe("ManagedEndpointProvider", () => {
15681568 const tunnelCalls: TunnelCall[] = [];
15691569 const dnsCalls: DnsCall[] = [];
15701570 const allocationCalls: AllocationCall[] = [];
1571 const layer = providerLayer(
1571 const layer = layerProvider(
15721572 makePersistentTunnelClient(tunnelCalls),
15731573 makeDnsClient(dnsCalls),
15741574 makeAllocations(allocationCalls),
......@@ -1606,7 +1606,7 @@ describe("ManagedEndpointProvider", () => {
16061606 }
16071607 }),
16081608 });
1609 const layer = providerLayer(tunnelClient, makeDnsClient(), makeAllocations(allocationCalls));
1609 const layer = layerProvider(tunnelClient, makeDnsClient(), makeAllocations(allocationCalls));
16101610
16111611 return Effect.gen(function* () {
16121612 const provider = yield* ManagedEndpointProvider.ManagedEndpointProvider;
......@@ -1668,7 +1668,7 @@ describe("ManagedEndpointProvider", () => {
16681668 }),
16691669 ),
16701670 });
1671 const layer = providerLayer(tunnelClient, dnsClient, makeAllocations(allocationCalls));
1671 const layer = layerProvider(tunnelClient, dnsClient, makeAllocations(allocationCalls));
16721672
16731673 return Effect.gen(function* () {
16741674 const provider = yield* ManagedEndpointProvider.ManagedEndpointProvider;
......@@ -1705,7 +1705,7 @@ describe("ManagedEndpointProvider", () => {
17051705 { name: expectedManagedTunnelName("env_shared", "user_ABC"), isDeleted: false },
17061706 { name: expectedManagedTunnelName("env_shared", "user_DEF"), isDeleted: false },
17071707 ]);
1708 }).pipe(Effect.provide(providerLayer(makeTunnelClient(tunnelCalls))));
1708 }).pipe(Effect.provide(layerProvider(makeTunnelClient(tunnelCalls))));
17091709 });
17101710
17111711 it.effect("recovers when DNS creation reports failure after the record became visible", () => {
......@@ -1752,7 +1752,7 @@ describe("ManagedEndpointProvider", () => {
17521752 "listRecords",
17531753 "updateRecord",
17541754 ]);
1755 }).pipe(Effect.provide(providerLayer(makeTunnelClient(), dnsClient)));
1755 }).pipe(Effect.provide(layerProvider(makeTunnelClient(), dnsClient)));
17561756 });
17571757
17581758 it.effect("reports mismatched tunnel responses without manufacturing a cause", () => {
......@@ -1786,7 +1786,7 @@ describe("ManagedEndpointProvider", () => {
17861786 expect(error.cause).toBeUndefined();
17871787 }
17881788 expect(dnsCalls).toHaveLength(0);
1789 }).pipe(Effect.provide(providerLayer(tunnelClient, makeDnsClient(dnsCalls))));
1789 }).pipe(Effect.provide(layerProvider(tunnelClient, makeDnsClient(dnsCalls))));
17901790 });
17911791
17921792 it.effect("fails provisioning when the DNS client fails", () => {
......@@ -1824,6 +1824,6 @@ describe("ManagedEndpointProvider", () => {
18241824 if (error._tag === "ManagedEndpointProvisioningFailed") {
18251825 expect(error.cause).toBe(failure);
18261826 }
1827 }).pipe(Effect.provide(providerLayer(makeTunnelClient(), dnsClient)));
1827 }).pipe(Effect.provide(layerProvider(makeTunnelClient(), dnsClient)));
18281828 });
18291829});
infra/relay/src/hooks/HeldHooks.test.ts+2-2
......@@ -66,7 +66,7 @@ const withService = <A, E>(
6666 allocationFor("user_1", ownKey, options.ownReady ?? true),
6767 allocationFor("user_2", otherKey, true),
6868 ];
69 const dependencies = Layer.mergeAll(
69 const layerDependencies = Layer.mergeAll(
7070 Layer.succeed(RelayConfiguration.RelayConfiguration, settings),
7171 Layer.mock(EnvironmentLinks.EnvironmentLinks, {
7272 findActiveManagedForEnvironment: (input) =>
......@@ -103,7 +103,7 @@ const withService = <A, E>(
103103 return Effect.gen(function* () {
104104 const heldHooks = yield* HeldHooks.HeldHooks;
105105 return yield* body({ heldHooks, cleared, woken });
106 }).pipe(Effect.provide(HeldHooks.layer.pipe(Layer.provide(dependencies))));
106 }).pipe(Effect.provide(HeldHooks.layer.pipe(Layer.provide(layerDependencies))));
107107};
108108
109109describe("HeldHooks", () => {
infra/relay/src/hooks/HookForwarder.test.ts+6-10
......@@ -29,12 +29,8 @@ import * as HttpServerResponse from "effect/http/HttpServerResponse";
2929import * as RelayConfiguration from "../Config.ts";
3030import * as EnvironmentLinks from "../environments/EnvironmentLinks.ts";
3131import * as ManagedEndpointAllocations from "../environments/ManagedEndpointAllocations.ts";
32import {
33 RELAY_HTTP_ROUTER_CONFIG,
34 relayCors,
35 relayNotFoundRoute,
36 traceRelayHttpRequestWith,
37} from "../http/Api.ts";
32import { RELAY_HTTP_ROUTER_CONFIG, traceRelayHttpRequestWith } from "../http/Api.ts";
33import * as RelayHttpApi from "../http/Api.ts";
3834import * as HookForwarder from "./HookForwarder.ts";
3935import { RELAY_HOOK_DELIVERY_TYP, verifyRelayJwt } from "@t3tools/shared/relayJwt";
4036import * as HeldHooks from "./HeldHooks.ts";
......@@ -114,7 +110,7 @@ function makeHarness(options: Harness = {}) {
114110 new Response("ok", { status: 200, headers: { "content-type": "text/plain" } }),
115111 ),
116112 ));
117 const forwarderLayer = HookForwarder.layer.pipe(
113 const layerForwarder = HookForwarder.layer.pipe(
118114 Layer.provideMerge(HeldHooks.layer),
119115 Layer.provide(
120116 Layer.mergeAll(
......@@ -167,11 +163,11 @@ function makeHarness(options: Harness = {}) {
167163 const httpEffect = HttpRouter.toHttpEffect(
168164 Layer.mergeAll(
169165 HttpApiBuilder.layer(HttpApi.make("RelayApi").add(RelayApi.groups.hooks)).pipe(
170 Layer.provide(HookForwarder.hooksApi.pipe(Layer.provide(forwarderLayer))),
166 Layer.provide(HookForwarder.layerApi.pipe(Layer.provide(layerForwarder))),
171167 Layer.provide([NodeServices.layer, NodeHttpPlatform.layer, Etag.layerWeak]),
172168 ),
173 relayNotFoundRoute,
174 relayCors,
169 RelayHttpApi.layerNotFoundRoute,
170 RelayHttpApi.layerCors,
175171 ),
176172 ).pipe(Effect.provideService(HttpRouter.RouterConfig, RELAY_HTTP_ROUTER_CONFIG));
177173 const send = (request: Request) =>
infra/relay/src/hooks/HookForwarder.ts+1-1
......@@ -446,7 +446,7 @@ export const layer = Layer.effect(HookForwarder, make);
446446 * re-reads the encoded path segments from the request so the token and hook
447447 * id reach the environment byte for byte, and streams the body itself.
448448 */
449export const hooksApi = HttpApiBuilder.group(
449export const layerApi = HttpApiBuilder.group(
450450 RelayApi,
451451 "hooks",
452452 Effect.fnUntraced(function* (handlers) {
infra/relay/src/hooks/HookInboxObject.ts+3-3
......@@ -91,19 +91,19 @@ const deliver = (baseUrl: string, hook: HookInboxStore.HeldHook) =>
9191 Effect.provide(FetchHttpClient.layer),
9292 );
9393
94export const HookInboxObjectLive = HookInboxObject.make(
94export const layer = HookInboxObject.make(
9595 Effect.gen(function* () {
9696 const state = yield* Cloudflare.DurableObjectState;
9797 // The init phase returns the per-instance Effect, which alchemy runs once
9898 // per object; only that inner Effect may touch storage.
9999 // @effect-diagnostics-next-line returnEffectInGen:off
100100 return Effect.gen(function* () {
101 const sql = SqliteClient.layer({ storage: state.raw.storage });
101 const layerSql = SqliteClient.layer({ storage: state.raw.storage });
102102 // Inboxes are opened by endpoint key, so spans line up with the
103103 // forward spans that held their requests.
104104 const inboxId = state.raw.id.name ?? state.raw.id.toString();
105105 const run = <A, E, R>(effect: Effect.Effect<A, E, R>) =>
106 effect.pipe(Effect.provide(sql), Effect.orDie);
106 effect.pipe(Effect.provide(layerSql), Effect.orDie);
107107 yield* run(HookInboxStore.migrate);
108108
109109 /** Moves the alarm to `at`, unless one is already due sooner. */
infra/relay/src/http/Api.test.ts+44-40
......@@ -42,22 +42,17 @@ import { RELAY_MANAGED_TUNNEL_RECOVERY_TYP, signRelayJwt } from "@t3tools/shared
4242import {
4343 RELAY_HTTP_ROUTER_CONFIG,
4444 RELAY_REQUEST_DEADLINE_MS,
45 clientApi,
46 relayCors,
47 relayDocsRedirectRoute,
48 relayEnvironmentAuthLayer,
49 relayNotFoundRoute,
5045 recoverEnvironmentTunnelRecord,
5146 registerEnvironmentTunnelRecovery,
5247 relayDpopFailureReason,
5348 revokeEnvironmentLinkRecord,
54 serverApi,
5549 traceRelayHttpRequestWith,
5650 unlinkEnvironmentRecord,
5751 verifyRelayClientBearerToken,
5852 verifyEnvironmentTunnelRecoveryProof,
5953 withoutCapturedParentSpan,
6054} from "./Api.ts";
55import * as RelayHttpApi from "./Api.ts";
6156import * as RelayConfiguration from "../Config.ts";
6257import * as RelayDb from "../db.ts";
6358import * as EnvironmentCredentials from "../environments/EnvironmentCredentials.ts";
......@@ -119,7 +114,7 @@ describe("device listing compatibility", () => {
119114 iosMajorVersion: null,
120115 androidApiLevel: 36,
121116 };
122 const handlers = clientApi.pipe(
117 const layerHandlers = RelayHttpApi.layerClientApi.pipe(
123118 HttpRouter.provideRequest(
124119 Layer.mergeAll(
125120 Layer.mock(EnvironmentCredentials.EnvironmentCredentials, {}),
......@@ -160,7 +155,7 @@ describe("device listing compatibility", () => {
160155 Effect.sync(() =>
161156 HttpRouter.toWebHandler(
162157 HttpApiBuilder.layer(HttpApi.make("RelayApi").add(RelayApi.groups.client)).pipe(
163 Layer.provide(handlers),
158 Layer.provide(layerHandlers),
164159 Layer.provide(HttpServer.layerServices),
165160 ),
166161 { disableLogger: true },
......@@ -301,7 +296,7 @@ describe("relay environment authentication", () => {
301296 route: {} as never,
302297 }),
303298 Effect.provide(
304 relayEnvironmentAuthLayer.pipe(
299 RelayHttpApi.layerEnvironmentAuth.pipe(
305300 Layer.provide(Layer.succeed(EnvironmentCredentials.EnvironmentCredentials, credentials)),
306301 ),
307302 ),
......@@ -310,7 +305,7 @@ describe("relay environment authentication", () => {
310305 });
311306});
312307
313function relayUnlinkTestLayer(input?: {
308function layerRelayUnlinkTest(input?: {
314309 readonly withTransaction?: RelayDb.RelayTransactions["Service"]["withTransaction"];
315310 readonly getForUser?: EnvironmentLinks.EnvironmentLinks["Service"]["getForUser"];
316311 readonly revokeForUser?: EnvironmentLinks.EnvironmentLinks["Service"]["revokeForUser"];
......@@ -512,7 +507,7 @@ describe("relay managed tunnel recovery", () => {
512507 }).pipe(
513508 Effect.provide(
514509 Layer.merge(
515 relayUnlinkTestLayer({
510 layerRelayUnlinkTest({
516511 getForUser: () => Effect.succeed(linkedEnvironmentRecord),
517512 provision: () => Effect.die("registration must not provision a tunnel"),
518513 }),
......@@ -545,7 +540,7 @@ describe("relay managed tunnel recovery", () => {
545540 }).pipe(
546541 Effect.provide(
547542 Layer.merge(
548 relayUnlinkTestLayer({
543 layerRelayUnlinkTest({
549544 getForUser: () => Effect.succeed(linkedEnvironmentRecord),
550545 reconcileOrigin: () => Effect.succeed("recovery_required"),
551546 }),
......@@ -580,7 +575,7 @@ describe("relay managed tunnel recovery", () => {
580575 }).pipe(
581576 Effect.provide(
582577 Layer.merge(
583 relayUnlinkTestLayer({
578 layerRelayUnlinkTest({
584579 getForUser: () => Effect.succeed(linkedEnvironmentRecord),
585580 }),
586581 Layer.mock(ManagedEndpointAllocations.ManagedEndpointAllocations)({
......@@ -611,7 +606,7 @@ describe("relay managed tunnel recovery", () => {
611606 }).pipe(
612607 Effect.provide(
613608 Layer.merge(
614 relayUnlinkTestLayer({
609 layerRelayUnlinkTest({
615610 getForUser: () => Effect.succeed(linkedEnvironmentRecord),
616611 }),
617612 Layer.mock(ManagedEndpointAllocations.ManagedEndpointAllocations)({
......@@ -657,7 +652,7 @@ describe("relay managed tunnel recovery", () => {
657652 }).pipe(
658653 Effect.provide(
659654 Layer.merge(
660 relayUnlinkTestLayer({
655 layerRelayUnlinkTest({
661656 getForUser: () => Effect.succeed(linkedEnvironmentRecord),
662657 provision: () =>
663658 Effect.succeed({
......@@ -694,7 +689,7 @@ describe("relay managed tunnel recovery", () => {
694689 }).pipe(
695690 Effect.provide(
696691 Layer.merge(
697 relayUnlinkTestLayer({
692 layerRelayUnlinkTest({
698693 getForUser: () => Effect.succeed(linkedEnvironmentRecord),
699694 provision: () =>
700695 Effect.sync(() => {
......@@ -727,7 +722,7 @@ describe("relay managed tunnel recovery", () => {
727722 }).pipe(
728723 Effect.provide(
729724 Layer.merge(
730 relayUnlinkTestLayer({
725 layerRelayUnlinkTest({
731726 getForUser: () =>
732727 Effect.succeed({
733728 ...linkedEnvironmentRecord,
......@@ -764,7 +759,7 @@ describe("relay managed tunnel recovery", () => {
764759 }).pipe(
765760 Effect.provide(
766761 Layer.merge(
767 relayUnlinkTestLayer({
762 layerRelayUnlinkTest({
768763 getForUser: () => Effect.succeed(linkedEnvironmentRecord),
769764 provision: () =>
770765 Effect.succeed({
......@@ -843,7 +838,7 @@ describe("relay managed tunnel recovery", () => {
843838 }).pipe(
844839 Effect.provide(
845840 Layer.merge(
846 relayUnlinkTestLayer({
841 layerRelayUnlinkTest({
847842 getForUser: () =>
848843 Effect.sync(() => (++lookups === 1 ? linkedEnvironmentRecord : currentLink)),
849844 provision: () =>
......@@ -887,7 +882,7 @@ describe("relay environment unlink", () => {
887882 expect(calls).toEqual(["transaction", "link", "credential"]);
888883 }).pipe(
889884 Effect.provide(
890 relayUnlinkTestLayer({
885 layerRelayUnlinkTest({
891886 withTransaction: (effect) => {
892887 calls.push("transaction");
893888 return effect;
......@@ -917,7 +912,7 @@ describe("relay environment unlink", () => {
917912 managedEndpointNamespace: "dev",
918913 }).pipe(
919914 Effect.provide(
920 relayUnlinkTestLayer({
915 layerRelayUnlinkTest({
921916 getForUser: () => Effect.succeed(linkedEnvironmentRecord),
922917 revokeForUser: () => Effect.succeed(true),
923918 prepareDeprovision: () =>
......@@ -986,7 +981,7 @@ describe("relay environment unlink", () => {
986981 ]);
987982 }).pipe(
988983 Effect.provide(
989 relayUnlinkTestLayer({
984 layerRelayUnlinkTest({
990985 withTransaction: (effect) => {
991986 calls.push("transaction");
992987 return effect;
......@@ -1041,7 +1036,7 @@ describe("relay environment unlink", () => {
10411036 expect(calls).toEqual(["prepare", "transaction", "link", "credential"]);
10421037 }).pipe(
10431038 Effect.provide(
1044 relayUnlinkTestLayer({
1039 layerRelayUnlinkTest({
10451040 withTransaction: (effect) => {
10461041 calls.push("transaction");
10471042 return effect;
......@@ -1083,7 +1078,7 @@ describe("relay environment unlink", () => {
10831078 expect(calls).toEqual(["prepare", "deprovision"]);
10841079 }).pipe(
10851080 Effect.provide(
1086 relayUnlinkTestLayer({
1081 layerRelayUnlinkTest({
10871082 prepareDeprovision: () =>
10881083 Effect.sync(() => {
10891084 calls.push("prepare");
......@@ -1125,7 +1120,7 @@ describe("relay environment unlink", () => {
11251120 expect(targets).toEqual([target, target]);
11261121 }).pipe(
11271122 Effect.provide(
1128 relayUnlinkTestLayer({
1123 layerRelayUnlinkTest({
11291124 getForUser: () => Effect.sync(() => (++lookups === 1 ? linkedEnvironmentRecord : null)),
11301125 revokeForUser: () => Effect.succeed(true),
11311126 prepareDeprovision: () => Effect.succeed(target),
......@@ -1234,7 +1229,7 @@ describe("relay routing fallback", () => {
12341229 EnvironmentPublishSignatures.EnvironmentPublishSignatures["Service"]["verify"]
12351230 >[0]
12361231 > = [];
1237 const publisher = Layer.succeed(AgentActivityPublisher.AgentActivityPublisher, {
1232 const layerPublisher = Layer.succeed(AgentActivityPublisher.AgentActivityPublisher, {
12381233 publish: (input) =>
12391234 Effect.sync(() => {
12401235 published.push(input);
......@@ -1242,13 +1237,16 @@ describe("relay routing fallback", () => {
12421237 }),
12431238 replayForLiveActivityRegistration: () => Effect.succeed(null),
12441239 });
1245 const signatures = Layer.succeed(EnvironmentPublishSignatures.EnvironmentPublishSignatures, {
1246 verify: (input) =>
1247 Effect.sync(() => {
1248 verified.push(input);
1249 }),
1250 });
1251 const auth = Layer.succeed(RelayEnvironmentAuth, {
1240 const layerSignatures = Layer.succeed(
1241 EnvironmentPublishSignatures.EnvironmentPublishSignatures,
1242 {
1243 verify: (input) =>
1244 Effect.sync(() => {
1245 verified.push(input);
1246 }),
1247 },
1248 );
1249 const layerAuth = Layer.succeed(RelayEnvironmentAuth, {
12521250 environmentBearer: (effect) =>
12531251 effect.pipe(
12541252 Effect.provideService(RelayEnvironmentPrincipal, {
......@@ -1257,11 +1255,11 @@ describe("relay routing fallback", () => {
12571255 }),
12581256 ),
12591257 });
1260 const routes = HttpApiBuilder.layer(
1258 const layerRoutes = HttpApiBuilder.layer(
12611259 HttpApi.make("RelayApi").add(RelayApi.groups.server),
12621260 ).pipe(
12631261 Layer.provide(
1264 serverApi.pipe(
1262 RelayHttpApi.layerServerApi.pipe(
12651263 HttpRouter.provideRequest(
12661264 Layer.mergeAll(
12671265 Layer.succeed(RelayConfiguration.RelayConfiguration, relaySettings),
......@@ -1270,14 +1268,14 @@ describe("relay routing fallback", () => {
12701268 Layer.mock(ManagedEndpointProvider.ManagedEndpointProvider, {}),
12711269 ),
12721270 ),
1273 Layer.provide([publisher, signatures, Layer.mock(HeldHooks.HeldHooks, {})]),
1271 Layer.provide([layerPublisher, layerSignatures, Layer.mock(HeldHooks.HeldHooks, {})]),
12741272 ),
12751273 ),
1276 Layer.provide(auth),
1274 Layer.provide(layerAuth),
12771275 Layer.provide([NodeServices.layer, NodeHttpPlatform.layer, Etag.layerWeak]),
12781276 );
12791277 const httpEffect = yield* HttpRouter.toHttpEffect(
1280 Layer.mergeAll(routes, relayNotFoundRoute, relayCors),
1278 Layer.mergeAll(layerRoutes, RelayHttpApi.layerNotFoundRoute, RelayHttpApi.layerCors),
12811279 ).pipe(Effect.provideService(HttpRouter.RouterConfig, RELAY_HTTP_ROUTER_CONFIG));
12821280 const threadIds = [
12831281 "b7c8c522-d244-43dc-875f-7224fce79912",
......@@ -1319,7 +1317,11 @@ describe("relay routing fallback", () => {
13191317 Effect.gen(function* () {
13201318 const request = HttpServerRequest.fromWeb(new Request("https://relay.test/"));
13211319 const httpEffect = yield* HttpRouter.toHttpEffect(
1322 Layer.mergeAll(relayDocsRedirectRoute, relayNotFoundRoute, relayCors),
1320 Layer.mergeAll(
1321 RelayHttpApi.layerDocsRedirectRoute,
1322 RelayHttpApi.layerNotFoundRoute,
1323 RelayHttpApi.layerCors,
1324 ),
13231325 );
13241326 const response = yield* httpEffect.pipe(
13251327 Effect.provideService(HttpServerRequest.HttpServerRequest, request),
......@@ -1336,7 +1338,9 @@ describe("relay routing fallback", () => {
13361338 const request = HttpServerRequest.fromWeb(
13371339 new Request("https://relay.test/v1/environmentsd", { method: "GET" }),
13381340 );
1339 const httpEffect = yield* HttpRouter.toHttpEffect(Layer.merge(relayNotFoundRoute, relayCors));
1341 const httpEffect = yield* HttpRouter.toHttpEffect(
1342 Layer.merge(RelayHttpApi.layerNotFoundRoute, RelayHttpApi.layerCors),
1343 );
13401344 const response = yield* httpEffect.pipe(
13411345 Effect.provideService(HttpServerRequest.HttpServerRequest, request),
13421346 );
infra/relay/src/http/Api.ts+16-16
......@@ -21,7 +21,7 @@ import * as HttpTraceContext from "effect/http/HttpTraceContext";
2121import * as HttpApiBuilder from "effect/http-api/HttpApiBuilder";
2222import * as HttpApiError from "effect/http-api/HttpApiError";
2323import { encodeOAuthScope } from "@t3tools/shared/oauthScope";
24import { httpHeaderRedactionLayer } from "@t3tools/shared/httpObservability";
24import * as HttpObservability from "@t3tools/shared/httpObservability";
2525
2626import {
2727 RelayApi,
......@@ -146,7 +146,7 @@ const appendRelayTraceContextResponseHeader = Effect.gen(function* () {
146146 );
147147}).pipe(Effect.ignore);
148148
149export const relayCors = HttpRouter.middleware(
149export const layerCors = HttpRouter.middleware(
150150 Effect.fnUntraced(function* <E, R>(
151151 httpEffect: Effect.Effect<
152152 HttpServerResponse.HttpServerResponse,
......@@ -171,13 +171,13 @@ export const relayCors = HttpRouter.middleware(
171171 { global: true },
172172);
173173
174export const relayNotFoundRoute = HttpRouter.add(
174export const layerNotFoundRoute = HttpRouter.add(
175175 "*",
176176 "/*",
177177 HttpServerResponse.empty({ status: 404 }),
178178);
179179
180export const relayDocsRedirectRoute = HttpRouter.add(
180export const layerDocsRedirectRoute = HttpRouter.add(
181181 "GET",
182182 "/",
183183 HttpServerResponse.redirect("/docs"),
......@@ -280,7 +280,7 @@ export const traceRelayHttpRequest = <E, R>(
280280
281281// Webhook senders put shared secrets and signatures in headers such as
282282// x-hub-signature-256, stripe-signature, x-gitlab-token and x-webhook-key.
283const webhookHeaderRedactionLayer = Layer.effect(
283const layerWebhookHeaderRedaction = Layer.effect(
284284 Headers.CurrentRedactedNames,
285285 Effect.map(Headers.CurrentRedactedNames, (names) => [
286286 ...names,
......@@ -304,7 +304,7 @@ export const traceRelayHttpRequestWith = <E, R, LayerError, LayerRequirements>(
304304 Effect.provide(
305305 Layer.merge(
306306 tracerLayer,
307 webhookHeaderRedactionLayer.pipe(Layer.provide(httpHeaderRedactionLayer)),
307 layerWebhookHeaderRedaction.pipe(Layer.provide(HttpObservability.layer)),
308308 ),
309309 ),
310310 );
......@@ -319,7 +319,7 @@ export const withoutCapturedParentSpan = <A, E, R>(
319319 return effect.pipe(Effect.ensuring(Effect.sync(() => fiber.setContext(context))));
320320 });
321321
322export const relayClientAuthLayer = Layer.effect(
322export const layerClientAuth = Layer.effect(
323323 RelayClientAuth,
324324 Effect.gen(function* () {
325325 const config = yield* RelayConfiguration.RelayConfiguration;
......@@ -358,7 +358,7 @@ export const relayClientAuthLayer = Layer.effect(
358358 }),
359359);
360360
361export const relayEnvironmentAuthLayer = Layer.effect(
361export const layerEnvironmentAuth = Layer.effect(
362362 RelayEnvironmentAuth,
363363 Effect.gen(function* () {
364364 const credentials = yield* EnvironmentCredentials.EnvironmentCredentials;
......@@ -391,7 +391,7 @@ export const relayEnvironmentAuthLayer = Layer.effect(
391391 }),
392392);
393393
394export const relayDpopClientAuthLayer = Layer.effect(
394export const layerDpopClientAuth = Layer.effect(
395395 RelayDpopClientAuth,
396396 Effect.gen(function* () {
397397 const relayTokens = yield* RelayTokens.RelayTokens;
......@@ -438,7 +438,7 @@ function readHttpAuthorizationCredential(credential: Redacted.Redacted<string>):
438438 return Redacted.value(credential).trimStart();
439439}
440440
441export const metadataApi = HttpApiBuilder.group(
441export const layerMetadataApi = HttpApiBuilder.group(
442442 RelayApi,
443443 "metadata",
444444 Effect.fnUntraced(function* (handlers) {
......@@ -472,7 +472,7 @@ export const metadataApi = HttpApiBuilder.group(
472472 }),
473473);
474474
475export const healthApi = HttpApiBuilder.group(
475export const layerHealthApi = HttpApiBuilder.group(
476476 RelayApi,
477477 "health",
478478 Effect.fnUntraced(function* (handlers) {
......@@ -768,7 +768,7 @@ export const recoverEnvironmentTunnelRecord = Effect.fn(
768768 };
769769});
770770
771export const mobileApi = HttpApiBuilder.group(
771export const layerMobileApi = HttpApiBuilder.group(
772772 RelayApi,
773773 "mobile",
774774 Effect.fnUntraced(function* (handlers) {
......@@ -825,7 +825,7 @@ export const mobileApi = HttpApiBuilder.group(
825825 }),
826826);
827827
828export const clientApi = HttpApiBuilder.group(
828export const layerClientApi = HttpApiBuilder.group(
829829 RelayApi,
830830 "client",
831831 Effect.fnUntraced(function* (handlers) {
......@@ -1006,7 +1006,7 @@ export const clientApi = HttpApiBuilder.group(
10061006 }),
10071007);
10081008
1009export const tokenApi = HttpApiBuilder.group(
1009export const layerTokenApi = HttpApiBuilder.group(
10101010 RelayApi,
10111011 "token",
10121012 Effect.fnUntraced(function* (handlers) {
......@@ -1068,7 +1068,7 @@ export const tokenApi = HttpApiBuilder.group(
10681068 }),
10691069);
10701070
1071export const dpopClientApi = HttpApiBuilder.group(
1071export const layerDpopClientApi = HttpApiBuilder.group(
10721072 RelayApi,
10731073 "dpopClient",
10741074 Effect.fnUntraced(function* (handlers) {
......@@ -1171,7 +1171,7 @@ export const dpopClientApi = HttpApiBuilder.group(
11711171 }),
11721172);
11731173
1174export const serverApi = HttpApiBuilder.group(
1174export const layerServerApi = HttpApiBuilder.group(
11751175 RelayApi,
11761176 "server",
11771177 Effect.fnUntraced(function* (handlers) {
infra/relay/src/observability.test.ts+2-2
......@@ -10,7 +10,7 @@ import * as HttpServerResponse from "effect/http/HttpServerResponse";
1010import type { OtlpTracer } from "effect/observability";
1111
1212import * as EnvironmentConnector from "./environments/EnvironmentConnector.ts";
13import { makeRelayTraceLayer } from "./observability.ts";
13import * as Observability from "./observability.ts";
1414
1515interface ExportedRequest {
1616 readonly authorization: string | undefined;
......@@ -52,7 +52,7 @@ it.effect("exports schema error fields as span attributes", () =>
5252 Effect.withSpan("relay.test.schema_error"),
5353 Effect.exit,
5454 Effect.provide(
55 makeRelayTraceLayer({
55 Observability.layer({
5656 tracesEndpoint: "/v1/traces",
5757 tracesDatasetName: "relay-test-traces",
5858 ingestToken: Redacted.make("test-token"),
infra/relay/src/observability.ts+1-1
......@@ -212,7 +212,7 @@ const withSchemaErrorAttributes = (delegate: Tracer.Tracer): Tracer.Tracer =>
212212 ...(delegate.context ? { context: delegate.context } : {}),
213213 });
214214
215export const makeRelayTraceLayer = (input: {
215export const layer = (input: {
216216 readonly tracesEndpoint: string;
217217 readonly tracesDatasetName: string;
218218 readonly ingestToken: Redacted.Redacted<string>;
infra/relay/src/worker.ts+43-51
......@@ -21,25 +21,14 @@ import * as HttpApiScalar from "effect/http-api/HttpApiScalar";
2121import { RelayApi } from "@t3tools/contracts/relay";
2222
2323import {
24 clientApi,
25 dpopClientApi,
26 healthApi,
27 metadataApi,
28 mobileApi,
2924 RELAY_HTTP_ROUTER_CONFIG,
30 relayClientAuthLayer,
31 relayDpopClientAuthLayer,
32 relayCors,
33 relayDocsRedirectRoute,
34 relayEnvironmentAuthLayer,
35 relayNotFoundRoute,
36 serverApi,
3725 traceRelayHttpRequestWith,
38 tokenApi,
3926 withoutCapturedParentSpan,
4027} from "./http/Api.ts";
28import * as RelayHttpApi from "./http/Api.ts";
4129import { ManagedEndpointZone, RelayApiZone, RelayDeploymentConfig } from "./zone.ts";
42import { makeRelayTraceLayer, RelayObservability } from "./observability.ts";
30import { RelayObservability } from "./observability.ts";
31import * as Observability from "./observability.ts";
4332import * as DeliveryAttempts from "./agentActivity/DeliveryAttempts.ts";
4433import * as AgentActivityRows from "./agentActivity/AgentActivityRows.ts";
4534import * as Devices from "./agentActivity/Devices.ts";
......@@ -78,9 +67,10 @@ import * as MobileRegistrations from "./agentActivity/MobileRegistrations.ts";
7867import * as HookForwarder from "./hooks/HookForwarder.ts";
7968import * as HeldHooks from "./hooks/HeldHooks.ts";
8069import * as HookInbox from "./hooks/HookInbox.ts";
81import { HookInboxObject, HookInboxObjectLive } from "./hooks/HookInboxObject.ts";
70import { HookInboxObject } from "./hooks/HookInboxObject.ts";
71import * as HookInboxObjectLayer from "./hooks/HookInboxObject.ts";
8272
83const webcryptoLayer = Layer.succeed(
73const layerWebcrypto = Layer.succeed(
8474 Crypto.Crypto,
8575 Crypto.make({
8676 randomBytes: (size) => globalThis.crypto.getRandomValues(new Uint8Array(size)),
......@@ -93,7 +83,7 @@ const webcryptoLayer = Layer.succeed(
9383 }),
9484);
9585
96const httpPlatformNotSupportedLayer = Layer.succeed(HttpPlatform.HttpPlatform, {
86const layerHttpPlatformNotSupported = Layer.succeed(HttpPlatform.HttpPlatform, {
9787 platform: "web",
9888 compression: {
9989 algorithms: new Set<HttpPlatform.CompressionAlgorithm>(),
......@@ -103,14 +93,14 @@ const httpPlatformNotSupportedLayer = Layer.succeed(HttpPlatform.HttpPlatform, {
10393 fileWebResponse: () => Effect.die("Relay API does not serve file responses"),
10494});
10595
106const relayApiLayer = Layer.mergeAll(
107 healthApi,
108 metadataApi,
109 mobileApi,
110 clientApi,
111 tokenApi,
112 dpopClientApi,
113 serverApi,
96const layerRelayApi = Layer.mergeAll(
97 RelayHttpApi.layerHealthApi,
98 RelayHttpApi.layerMetadataApi,
99 RelayHttpApi.layerMobileApi,
100 RelayHttpApi.layerClientApi,
101 RelayHttpApi.layerTokenApi,
102 RelayHttpApi.layerDpopClientApi,
103 RelayHttpApi.layerServerApi,
114104);
115105
116106const CloudMintKeyPair = Alchemy.KeyPair("CloudMintKeyPair");
......@@ -120,7 +110,7 @@ const ApnsDeliveryJobSigningSecret = Alchemy.makeRandom("ApnsDeliveryJobSigningS
120110
121111export class Api extends Cloudflare.Worker<Api, {}>()("Api") {}
122112
123export const ApiLive = Api.make(
113export const layer = Api.make(
124114 RelayDeploymentConfig.pipe(
125115 Effect.map(({ relayPublicDomain }) => ({
126116 main: import.meta.filename,
......@@ -235,12 +225,12 @@ export const ApiLive = Api.make(
235225 });
236226 });
237227
238 const relayTraceLayer = Layer.unwrap(
228 const layerRelayTrace = Layer.unwrap(
239229 Effect.all({
240230 tracesDatasetName: axiomDatasetName,
241231 tracesEndpoint: axiomTracesEndpoint,
242232 ingestToken: axiomIngestToken,
243 }).pipe(Effect.map(makeRelayTraceLayer)),
233 }).pipe(Effect.map(Observability.layer)),
244234 );
245235
246236 // Each managed endpoint's held webhook requests live in its own Durable Object.
......@@ -259,7 +249,7 @@ export const ApiLive = Api.make(
259249 ),
260250 ),
261251 );
262 const hookInboxLayer = Layer.succeed(HookInbox.HookInbox, {
252 const layerHookInbox = Layer.succeed(HookInbox.HookInbox, {
263253 hold: ({ endpointKey, baseUrl, hook }) =>
264254 hookInboxes.getByName(endpointKey).hold(hook, baseUrl).pipe(inboxCall("hold", endpointKey)),
265255 wake: ({ endpointKey, baseUrl }) =>
......@@ -268,7 +258,7 @@ export const ApiLive = Api.make(
268258 hookInboxes.getByName(endpointKey).clear().pipe(inboxCall("clear", endpointKey)),
269259 });
270260
271 const runtimeLayer = Layer.empty.pipe(
261 const layerRuntime = Layer.empty.pipe(
272262 Layer.provideMerge(MobileRegistrations.layer),
273263 Layer.provideMerge(AgentActivityPublisher.layer),
274264 Layer.provideMerge(EnvironmentConnector.layer),
......@@ -313,7 +303,7 @@ export const ApiLive = Api.make(
313303 Layer.provideMerge(
314304 ApnsDeliveryQueue.layerCloudflareQueues(apnsDeliveryQueueSender, alchemyRuntimeContext),
315305 ),
316 Layer.provideMerge(Layer.mergeAll(AgentActivityRows.layer, Devices.layer, hookInboxLayer)),
306 Layer.provideMerge(Layer.mergeAll(AgentActivityRows.layer, Devices.layer, layerHookInbox)),
317307 Layer.provideMerge(EnvironmentCredentials.layer),
318308 Layer.provideMerge(
319309 Layer.mergeAll(
......@@ -331,7 +321,7 @@ export const ApiLive = Api.make(
331321 ),
332322 ),
333323 Layer.provideMerge(Layer.effect(RelayConfiguration.RelayConfiguration, loadSettings)),
334 Layer.provideMerge(webcryptoLayer),
324 Layer.provideMerge(layerWebcrypto),
335325 );
336326
337327 // Fails open: a limiter outage must not drop webhooks the environment would accept.
......@@ -351,22 +341,22 @@ export const ApiLive = Api.make(
351341 ),
352342 ),
353343 );
354 const hookRateLimiterLayer = Layer.succeed(HookForwarder.HookRateLimiter, {
344 const layerHookRateLimiter = Layer.succeed(HookForwarder.HookRateLimiter, {
355345 allowHook: allowWith(hookRateLimit),
356346 allowEndpoint: allowWith(hookEndpointRateLimit),
357347 });
358348
359 const appLayer = Layer.merge(
360 relayApiLayer,
361 HookForwarder.hooksApi.pipe(
349 const layerApp = Layer.merge(
350 layerRelayApi,
351 HookForwarder.layerApi.pipe(
362352 Layer.provide(HookForwarder.layer),
363 Layer.provide(hookRateLimiterLayer),
353 Layer.provide(layerHookRateLimiter),
364354 ),
365355 ).pipe(
366 Layer.provideMerge(relayClientAuthLayer),
367 Layer.provideMerge(relayDpopClientAuthLayer),
368 Layer.provideMerge(relayEnvironmentAuthLayer),
369 Layer.provide(runtimeLayer),
356 Layer.provideMerge(RelayHttpApi.layerClientAuth),
357 Layer.provideMerge(RelayHttpApi.layerDpopClientAuth),
358 Layer.provideMerge(RelayHttpApi.layerEnvironmentAuth),
359 Layer.provide(layerRuntime),
370360 );
371361
372362 yield* Cloudflare.Queues.consumeQueueMessages<unknown>(
......@@ -387,7 +377,7 @@ export const ApiLive = Api.make(
387377 Effect.withSpan("relay.apn_delivery_queue.process_message"),
388378 ),
389379 ),
390 Effect.provide(runtimeLayer),
380 Effect.provide(layerRuntime),
391381 ),
392382 );
393383
......@@ -404,7 +394,7 @@ export const ApiLive = Api.make(
404394 stream.pipe(
405395 Stream.withSpan("relay.fcm_delivery_queue.process_batch"),
406396 Stream.runForEach(FcmDeliveryQueueConsumer.processMessage),
407 Effect.provide(runtimeLayer),
397 Effect.provide(layerRuntime),
408398 ),
409399 );
410400
......@@ -447,24 +437,26 @@ export const ApiLive = Api.make(
447437 ).pipe(
448438 Effect.withSpan("relay.cron.prune_expired_state"),
449439 // Export cron spans to Axiom like HTTP spans; the scope flushes them before the run ends.
450 Effect.provide(Layer.merge(runtimeLayer, relayTraceLayer)),
440 Effect.provide(Layer.merge(layerRuntime, layerRelayTrace)),
451441 ),
452442 );
453443
454444 const fetch = Layer.merge(
455445 Layer.mergeAll(
456446 HttpApiBuilder.layer(RelayApi, { openapiPath: "/openapi.json" }).pipe(
457 Layer.provide(appLayer),
447 Layer.provide(layerApp),
458448 ),
459449 HttpApiScalar.layer(RelayApi, { path: "/docs" }),
460 relayDocsRedirectRoute,
461 ).pipe(Layer.provide([Etag.layerWeak, httpPlatformNotSupportedLayer, relayCors])),
462 relayNotFoundRoute,
450 RelayHttpApi.layerDocsRedirectRoute,
451 ).pipe(
452 Layer.provide([Etag.layerWeak, layerHttpPlatformNotSupported, RelayHttpApi.layerCors]),
453 ),
454 RelayHttpApi.layerNotFoundRoute,
463455 ).pipe(
464456 HttpRouter.toHttpEffect,
465457 Effect.provideService(HttpRouter.RouterConfig, RELAY_HTTP_ROUTER_CONFIG),
466458 withoutCapturedParentSpan,
467 Effect.flatMap((httpEffect) => traceRelayHttpRequestWith(httpEffect, relayTraceLayer)),
459 Effect.flatMap((httpEffect) => traceRelayHttpRequestWith(httpEffect, layerRelayTrace)),
468460 );
469461
470462 return { fetch };
......@@ -478,7 +470,7 @@ export const ApiLive = Api.make(
478470 Layer.provideMerge(Cloudflare.Tunnel.ReadWriteTunnelBinding),
479471 Layer.provideMerge(Cloudflare.DNS.ReadWriteDnsHttp),
480472 Layer.provideMerge(Cloudflare.Workers.RateLimitBinding),
481 Layer.provideMerge(HookInboxObjectLive),
473 Layer.provideMerge(HookInboxObjectLayer.layer),
482474 // The worker runtime opens its own HTTP span around ours. For webhook
483475 // paths it would record the raw URL, token included, and adopt the
484476 // sender's traceparent, so only our redacted span covers those.
......@@ -509,4 +501,4 @@ export const ApiLive = Api.make(
509501 ),
510502);
511503
512export default ApiLive;
504export default layer;
packages/client-runtime/src/authorization/layer.test.ts+2-2
......@@ -16,7 +16,7 @@ import * as TestClock from "effect/testing/TestClock";
1616
1717import { DPOP_UNKNOWN_HINT } from "../relay/errorPresentation.ts";
1818import * as ManagedRelay from "../relay/managedRelay.ts";
19import { remoteHttpClientLayer } from "../rpc/http.ts";
19import * as RpcHttp from "../rpc/http.ts";
2020import * as ClientCapabilities from "../platform/capabilities.ts";
2121import * as RemoteEnvironmentAuthorization from "./service.ts";
2222import * as TokenStore from "./tokenStore.ts";
......@@ -191,7 +191,7 @@ const makeHarness = Effect.fn("TestRemoteAuthorization.makeHarness")(function* (
191191 const layer = RemoteEnvironmentAuthorization.layer.pipe(
192192 Layer.provide(
193193 Layer.mergeAll(
194 remoteHttpClientLayer(fetch.fetchFn),
194 RpcHttp.layerRemoteHttpClient(fetch.fetchFn),
195195 Layer.succeed(ManagedRelay.ManagedRelayDpopSigner, signer),
196196 Layer.succeed(ManagedRelay.ManagedRelayClient, relay),
197197 Layer.succeed(ClientCapabilities.CloudSession, {
packages/client-runtime/src/authorization/remote.test.ts+3-2
......@@ -18,7 +18,7 @@ import {
1818 resolveRemoteWebSocketConnectionUrl,
1919} from "./remote.ts";
2020import { fetchRemoteEnvironmentDescriptor } from "../environment/descriptor.ts";
21import { remoteHttpClientLayer } from "../rpc/http.ts";
21import * as RpcHttp from "../rpc/http.ts";
2222
2323const isEnvironmentAuthInvalidError = Schema.is(EnvironmentAuthInvalidError);
2424
......@@ -49,7 +49,8 @@ const hangingFetch = () => {
4949 return { fetchFn, calls };
5050};
5151
52const provideRemoteHttp = (fetchFn: typeof fetch) => Effect.provide(remoteHttpClientLayer(fetchFn));
52const provideRemoteHttp = (fetchFn: typeof fetch) =>
53 Effect.provide(RpcHttp.layerRemoteHttpClient(fetchFn));
5354
5455const expectFetchCall = (
5556 calls: ReadonlyArray<FetchCall>,
packages/client-runtime/src/connection/layer.ts+9-9
......@@ -73,19 +73,19 @@ export const watchDiscoveredCompatibility = Effect.fn("connection.watchDiscovere
7373);
7474
7575export function layerWithOptions(options: RpcSession.RpcSessionOptions) {
76 const driverLayer = ConnectionDriver.layer.pipe(
76 const layerDriver = ConnectionDriver.layer.pipe(
7777 Layer.provide(Layer.mergeAll(ConnectionResolver.layer, RpcSession.layer(options))),
7878 );
79 const registryLayer = EnvironmentRegistry.layer.pipe(Layer.provide(driverLayer));
80 const onboardingLayer = ConnectionOnboarding.layer.pipe(Layer.provide(registryLayer));
81 const connectionServicesLayer = Layer.mergeAll(
82 registryLayer,
79 const layerRegistry = EnvironmentRegistry.layer.pipe(Layer.provide(layerDriver));
80 const layerOnboarding = ConnectionOnboarding.layer.pipe(Layer.provide(layerRegistry));
81 const layerConnectionServices = Layer.mergeAll(
82 layerRegistry,
8383 RelayEnvironmentDiscovery.layer,
84 onboardingLayer,
84 layerOnboarding,
8585 // Exposed for updating hosts too old to connect through the driver.
8686 ConnectionResolver.layer,
8787 );
88 const connectionStartupLayer = Layer.effectDiscard(
88 const layerConnectionStartup = Layer.effectDiscard(
8989 Effect.gen(function* () {
9090 const registry = yield* EnvironmentRegistry.EnvironmentRegistry;
9191 const platformSource = yield* PlatformConnectionSource.PlatformConnectionSource;
......@@ -97,8 +97,8 @@ export function layerWithOptions(options: RpcSession.RpcSessionOptions) {
9797 );
9898 }).pipe(Effect.withSpan("clientRuntime.connection.application.start")),
9999 );
100 return connectionStartupLayer.pipe(
101 Layer.provideMerge(connectionServicesLayer),
100 return layerConnectionStartup.pipe(
101 Layer.provideMerge(layerConnectionServices),
102102 Layer.provideMerge(RemoteEnvironmentAuthorization.layer),
103103 );
104104}
packages/client-runtime/src/connection/onboarding.test.ts+15-15
......@@ -8,7 +8,7 @@ import * as Effect from "effect/Effect";
88import * as Layer from "effect/Layer";
99import * as Option from "effect/Option";
1010
11import { remoteHttpClientLayer } from "../rpc/http.ts";
11import * as RpcHttp from "../rpc/http.ts";
1212import * as ClientCapabilities from "../platform/capabilities.ts";
1313import { BearerConnectionCredential, BearerConnectionProfile } from "./catalog.ts";
1414import { BearerConnectionTarget } from "./model.ts";
......@@ -18,7 +18,7 @@ import {
1818 prepareSshRegistration,
1919} from "./onboarding.ts";
2020
21const CLIENT_PRESENTATION_LAYER = Layer.succeed(
21const layerClientPresentation = Layer.succeed(
2222 ClientCapabilities.ClientPresentation,
2323 ClientCapabilities.ClientPresentation.of({
2424 metadata: {
......@@ -30,7 +30,7 @@ const CLIENT_PRESENTATION_LAYER = Layer.succeed(
3030 }),
3131);
3232
33function pairingHttpLayer(
33function layerPairingHttp(
3434 calls: Array<{ readonly url: string; readonly init: RequestInit }>,
3535 options?: {
3636 readonly failDescriptor?: boolean;
......@@ -81,7 +81,7 @@ function pairingHttpLayer(
8181 return Promise.reject(new Error(`Unexpected request: ${url}`));
8282 }) satisfies typeof fetch;
8383
84 return remoteHttpClientLayer(fetchFn);
84 return RpcHttp.layerRemoteHttpClient(fetchFn);
8585}
8686
8787describe("connection onboarding", () => {
......@@ -91,7 +91,7 @@ describe("connection onboarding", () => {
9191 const registration = yield* preparePairingRegistration({
9292 host: "remote.example.test",
9393 pairingCode: "pairing-token",
94 }).pipe(Effect.provide(Layer.mergeAll(CLIENT_PRESENTATION_LAYER, pairingHttpLayer(calls))));
94 }).pipe(Effect.provide(Layer.mergeAll(layerClientPresentation, layerPairingHttp(calls))));
9595
9696 expect(registration).toMatchObject({
9797 _tag: "BearerConnectionRegistration",
......@@ -137,8 +137,8 @@ describe("connection onboarding", () => {
137137 }).pipe(
138138 Effect.provide(
139139 Layer.mergeAll(
140 CLIENT_PRESENTATION_LAYER,
141 pairingHttpLayer(calls, { protocolVersion: ORCHESTRATION_PROTOCOL_VERSION + 1 }),
140 layerClientPresentation,
141 layerPairingHttp(calls, { protocolVersion: ORCHESTRATION_PROTOCOL_VERSION + 1 }),
142142 ),
143143 ),
144144 Effect.flip,
......@@ -158,7 +158,7 @@ describe("connection onboarding", () => {
158158 pairingCode: "pairing-token",
159159 expectedEnvironmentId: EnvironmentId.make("some-other-machine"),
160160 }).pipe(
161 Effect.provide(Layer.mergeAll(CLIENT_PRESENTATION_LAYER, pairingHttpLayer(calls))),
161 Effect.provide(Layer.mergeAll(layerClientPresentation, layerPairingHttp(calls))),
162162 Effect.flip,
163163 );
164164 expect(error).toMatchObject({ reason: "configuration" });
......@@ -178,8 +178,8 @@ describe("connection onboarding", () => {
178178 }).pipe(
179179 Effect.provide(
180180 Layer.mergeAll(
181 CLIENT_PRESENTATION_LAYER,
182 pairingHttpLayer(calls, {
181 layerClientPresentation,
182 layerPairingHttp(calls, {
183183 protocolVersion: ORCHESTRATION_PROTOCOL_VERSION - 1,
184184 selfUpdate: true,
185185 }),
......@@ -200,8 +200,8 @@ describe("connection onboarding", () => {
200200 }).pipe(
201201 Effect.provide(
202202 Layer.mergeAll(
203 CLIENT_PRESENTATION_LAYER,
204 pairingHttpLayer(calls, { protocolVersion: ORCHESTRATION_PROTOCOL_VERSION - 1 }),
203 layerClientPresentation,
204 layerPairingHttp(calls, { protocolVersion: ORCHESTRATION_PROTOCOL_VERSION - 1 }),
205205 ),
206206 ),
207207 Effect.flip,
......@@ -224,8 +224,8 @@ describe("connection onboarding", () => {
224224 }).pipe(
225225 Effect.provide(
226226 Layer.mergeAll(
227 CLIENT_PRESENTATION_LAYER,
228 pairingHttpLayer(calls, { failDescriptor: true }),
227 layerClientPresentation,
228 layerPairingHttp(calls, { failDescriptor: true }),
229229 ),
230230 ),
231231 Effect.flip,
......@@ -244,7 +244,7 @@ describe("connection onboarding", () => {
244244 host: "",
245245 pairingCode: "",
246246 }).pipe(
247 Effect.provide(Layer.mergeAll(CLIENT_PRESENTATION_LAYER, pairingHttpLayer(calls))),
247 Effect.provide(Layer.mergeAll(layerClientPresentation, layerPairingHttp(calls))),
248248 Effect.flip,
249249 );
250250
packages/client-runtime/src/connection/registry.test.ts+2-2
......@@ -420,7 +420,7 @@ const makeHarness = Effect.fn("TestEnvironmentRegistry.makeHarness")(function* (
420420 checkRoute(route).pipe(Effect.map((check) => check === "answered")),
421421 });
422422
423 const cacheLayer = Layer.succeed(Persistence.EnvironmentCacheStore, cacheStore);
423 const layerCache = Layer.succeed(Persistence.EnvironmentCacheStore, cacheStore);
424424 const layer = EnvironmentRegistry.layer.pipe(
425425 Layer.provide(
426426 Layer.mergeAll(
......@@ -436,7 +436,7 @@ const makeHarness = Effect.fn("TestEnvironmentRegistry.makeHarness")(function* (
436436 ConnectionWakeups.ConnectionWakeups.of({ changes: Stream.never }),
437437 ),
438438 Layer.succeed(ConnectionDriver.ConnectionDriver, driver),
439 cacheLayer,
439 layerCache,
440440 Layer.succeed(Persistence.EnvironmentOwnedDataCleanup, ownedDataCleanup),
441441 ),
442442 ),
packages/client-runtime/src/connection/resolver.test.ts+24-24
......@@ -32,7 +32,7 @@ import {
3232 type ConnectionTarget,
3333} from "./model.ts";
3434import * as ConnectionProfileStore from "./profileStore.ts";
35import { remoteHttpClientLayer } from "../rpc/http.ts";
35import * as RpcHttp from "../rpc/http.ts";
3636import {
3737 GitHubRoutingPermissions,
3838 gitHubRoutingConnectionKey,
......@@ -145,8 +145,8 @@ const makeDependencies = Effect.fn("TestConnectionResolver.makeDependencies")((o
145145 disconnect: () => Effect.void,
146146 });
147147
148 const dependencies = Layer.mergeAll(
149 remoteHttpClientLayer((() =>
148 const layerDependencies = Layer.mergeAll(
149 RpcHttp.layerRemoteHttpClient((() =>
150150 Promise.resolve(
151151 Response.json({
152152 environmentId: ENVIRONMENT_ID,
......@@ -183,14 +183,14 @@ const makeDependencies = Effect.fn("TestConnectionResolver.makeDependencies")((o
183183 Layer.succeed(ClientCapabilities.SshEnvironmentGateway, ssh),
184184 );
185185
186 return Effect.succeed(ConnectionResolver.layer.pipe(Layer.provide(dependencies)));
186 return Effect.succeed(ConnectionResolver.layer.pipe(Layer.provide(layerDependencies)));
187187});
188188
189189describe("ConnectionResolver", () => {
190190 it.effect("blocks an old host during discovery before opening orchestration RPC", () =>
191191 Effect.gen(function* () {
192 const brokerLayer = yield* makeDependencies({ descriptorProtocolVersion: null });
193 const broker = yield* ConnectionResolver.ConnectionResolver.pipe(Effect.provide(brokerLayer));
192 const layerBroker = yield* makeDependencies({ descriptorProtocolVersion: null });
193 const broker = yield* ConnectionResolver.ConnectionResolver.pipe(Effect.provide(layerBroker));
194194 const target = new PrimaryConnectionTarget({
195195 environmentId: ENVIRONMENT_ID,
196196 label: "Primary",
......@@ -207,10 +207,10 @@ describe("ConnectionResolver", () => {
207207
208208 it.effect("blocks an incompatible host during discovery before opening orchestration RPC", () =>
209209 Effect.gen(function* () {
210 const brokerLayer = yield* makeDependencies({
210 const layerBroker = yield* makeDependencies({
211211 descriptorProtocolVersion: ORCHESTRATION_PROTOCOL_VERSION + 1,
212212 });
213 const broker = yield* ConnectionResolver.ConnectionResolver.pipe(Effect.provide(brokerLayer));
213 const broker = yield* ConnectionResolver.ConnectionResolver.pipe(Effect.provide(layerBroker));
214214 const target = new PrimaryConnectionTarget({
215215 environmentId: ENVIRONMENT_ID,
216216 label: "Primary",
......@@ -227,8 +227,8 @@ describe("ConnectionResolver", () => {
227227
228228 it.effect("prepares a primary environment without remote capabilities", () =>
229229 Effect.gen(function* () {
230 const brokerLayer = yield* makeDependencies();
231 const broker = yield* ConnectionResolver.ConnectionResolver.pipe(Effect.provide(brokerLayer));
230 const layerBroker = yield* makeDependencies();
231 const broker = yield* ConnectionResolver.ConnectionResolver.pipe(Effect.provide(layerBroker));
232232 const target = new PrimaryConnectionTarget({
233233 environmentId: ENVIRONMENT_ID,
234234 label: "Primary",
......@@ -251,7 +251,7 @@ describe("ConnectionResolver", () => {
251251 it.effect("authorizes a desktop primary environment with its platform bearer token", () =>
252252 Effect.gen(function* () {
253253 const bearerInputs = yield* Ref.make<ReadonlyArray<{ token: string; method: string }>>([]);
254 const brokerLayer = yield* makeDependencies({
254 const layerBroker = yield* makeDependencies({
255255 primaryBearerToken: "desktop-bearer",
256256 authorizeBearer: (input) =>
257257 Ref.update(bearerInputs, (values) => [
......@@ -270,7 +270,7 @@ describe("ConnectionResolver", () => {
270270 }),
271271 ),
272272 });
273 const broker = yield* ConnectionResolver.ConnectionResolver.pipe(Effect.provide(brokerLayer));
273 const broker = yield* ConnectionResolver.ConnectionResolver.pipe(Effect.provide(layerBroker));
274274 const target = new PrimaryConnectionTarget({
275275 environmentId: ENVIRONMENT_ID,
276276 label: "Primary",
......@@ -302,7 +302,7 @@ describe("ConnectionResolver", () => {
302302 httpBaseUrl: ENDPOINT.httpBaseUrl,
303303 wsBaseUrl: ENDPOINT.wsBaseUrl,
304304 });
305 const brokerLayer = yield* makeDependencies({
305 const layerBroker = yield* makeDependencies({
306306 credentials: [["saved-1", new BearerConnectionCredential({ token: "secret-bearer" })]],
307307 authorizeBearer: (input) =>
308308 Ref.update(bearerInputs, (values) => [
......@@ -321,7 +321,7 @@ describe("ConnectionResolver", () => {
321321 }),
322322 ),
323323 });
324 const broker = yield* ConnectionResolver.ConnectionResolver.pipe(Effect.provide(brokerLayer));
324 const broker = yield* ConnectionResolver.ConnectionResolver.pipe(Effect.provide(layerBroker));
325325
326326 expect(
327327 (yield* broker.prepare(catalogEntry(target, Option.some(profile)))).socketUrl,
......@@ -336,8 +336,8 @@ describe("ConnectionResolver", () => {
336336 environmentId: ENVIRONMENT_ID,
337337 label: "Cloud",
338338 });
339 const brokerLayer = yield* makeDependencies();
340 const broker = yield* ConnectionResolver.ConnectionResolver.pipe(Effect.provide(brokerLayer));
339 const layerBroker = yield* makeDependencies();
340 const broker = yield* ConnectionResolver.ConnectionResolver.pipe(Effect.provide(layerBroker));
341341
342342 expect(yield* broker.prepare(catalogEntry(target))).toEqual({
343343 environmentId: ENVIRONMENT_ID,
......@@ -362,7 +362,7 @@ describe("ConnectionResolver", () => {
362362 environmentId: ENVIRONMENT_ID,
363363 label: "Cloud",
364364 });
365 const brokerLayer = yield* makeDependencies({
365 const layerBroker = yield* makeDependencies({
366366 authorizeDpop: (input) =>
367367 Effect.succeed({
368368 environmentId: input.expectedEnvironmentId,
......@@ -376,7 +376,7 @@ describe("ConnectionResolver", () => {
376376 },
377377 }).pipe(Effect.withSpan("test.remote.authorizeDpop")),
378378 });
379 const broker = yield* ConnectionResolver.ConnectionResolver.pipe(Effect.provide(brokerLayer));
379 const broker = yield* ConnectionResolver.ConnectionResolver.pipe(Effect.provide(layerBroker));
380380
381381 yield* broker
382382 .prepare(catalogEntry(target))
......@@ -407,7 +407,7 @@ describe("ConnectionResolver", () => {
407407 label: "SSH",
408408 target: SSH_TARGET,
409409 });
410 const brokerLayer = yield* makeDependencies({
410 const layerBroker = yield* makeDependencies({
411411 prepareSsh: (input) =>
412412 Ref.update(preparedTargets, (values) => [...values, input.target]).pipe(
413413 Effect.as({
......@@ -434,7 +434,7 @@ describe("ConnectionResolver", () => {
434434 }),
435435 ),
436436 });
437 const broker = yield* ConnectionResolver.ConnectionResolver.pipe(Effect.provide(brokerLayer));
437 const broker = yield* ConnectionResolver.ConnectionResolver.pipe(Effect.provide(layerBroker));
438438
439439 expect(
440440 (yield* broker.prepare(catalogEntry(target, Option.some(profile)))).socketUrl,
......@@ -485,7 +485,7 @@ describe("ConnectionResolver", () => {
485485 ),
486486 });
487487 let savedProfile: ConnectionProfile = profile;
488 const brokerLayer = yield* makeDependencies({
488 const layerBroker = yield* makeDependencies({
489489 profileStore: {
490490 get: () => Effect.sync(() => Option.some(savedProfile)),
491491 put: (value) =>
......@@ -518,7 +518,7 @@ describe("ConnectionResolver", () => {
518518 }),
519519 });
520520 const broker = yield* ConnectionResolver.ConnectionResolver.pipe(
521 Effect.provide(brokerLayer),
521 Effect.provide(layerBroker),
522522 );
523523 const prepare = broker
524524 .prepare(entry)
......@@ -551,10 +551,10 @@ describe("ConnectionResolver", () => {
551551 detail: "Relay environment connection timed out.",
552552 traceId: "relay-trace",
553553 });
554 const brokerLayer = yield* makeDependencies({
554 const layerBroker = yield* makeDependencies({
555555 authorizeDpop: () => Effect.fail(authorizationError),
556556 });
557 const broker = yield* ConnectionResolver.ConnectionResolver.pipe(Effect.provide(brokerLayer));
557 const broker = yield* ConnectionResolver.ConnectionResolver.pipe(Effect.provide(layerBroker));
558558 const error = yield* Effect.flip(broker.prepare(catalogEntry(target)));
559559
560560 expect(error).toBe(authorizationError);
packages/client-runtime/src/connection/supervisor.test.ts+6-6
......@@ -16,7 +16,7 @@ import * as RemoteEnvironmentAuthorization from "../authorization/service.ts";
1616import * as TokenStore from "../authorization/tokenStore.ts";
1717import * as ClientCapabilities from "../platform/capabilities.ts";
1818import * as ManagedRelay from "../relay/managedRelay.ts";
19import { remoteHttpClientLayer } from "../rpc/http.ts";
19import * as RpcHttp from "../rpc/http.ts";
2020import type { WsRpcProtocolClient } from "../rpc/protocol.ts";
2121import { fetchEnvironmentSessionState } from "../state/session.ts";
2222import type { ConnectionCatalogEntry, ConnectionRoute } from "./catalog.ts";
......@@ -233,7 +233,7 @@ const makeHarness = Effect.fn("TestConnectionHarness.make")(function* (options?:
233233 );
234234 });
235235
236 const dependencies = Layer.mergeAll(
236 const layerDependencies = Layer.mergeAll(
237237 // Jitter at its maximum, so each retry waits exactly its ceiling: 2s, 4s, 8s...
238238 Layer.succeed(Random.Random, {
239239 nextDoubleUnsafe: () => 1 - Number.EPSILON,
......@@ -261,7 +261,7 @@ const makeHarness = Effect.fn("TestConnectionHarness.make")(function* (options?:
261261 );
262262
263263 return {
264 dependencies,
264 dependencies: layerDependencies,
265265 prepareCount,
266266 sessionCount,
267267 releaseCount,
......@@ -1551,11 +1551,11 @@ describe("EnvironmentSupervisor", () => {
15511551 getAgentActivitySnapshot: unused,
15521552 resetTokenCache: Effect.void,
15531553 });
1554 const httpLayer = remoteHttpClientLayer(fetchFn);
1554 const layerHttp = RpcHttp.layerRemoteHttpClient(fetchFn);
15551555 const remoteAuthorization = yield* RemoteEnvironmentAuthorization.make.pipe(
15561556 Effect.provide(
15571557 Layer.mergeAll(
1558 httpLayer,
1558 layerHttp,
15591559 Layer.succeed(ManagedRelay.ManagedRelayDpopSigner, signer),
15601560 Layer.succeed(ManagedRelay.ManagedRelayClient, relay),
15611561 Layer.succeed(ClientCapabilities.CloudSession, {
......@@ -1593,7 +1593,7 @@ describe("EnvironmentSupervisor", () => {
15931593 prepared,
15941594 signer: Option.some(signer),
15951595 remoteAuthorization: Option.some(remoteAuthorization),
1596 }).pipe(Effect.provide(httpLayer));
1596 }).pipe(Effect.provide(layerHttp));
15971597
15981598 yield* TestClock.adjust("2 hours");
15991599 expect((yield* readSession).authenticated).toBe(true);
packages/client-runtime/src/operations/commands.performance.test.ts+2-2
......@@ -146,7 +146,7 @@ const TARGET = new PrimaryConnectionTarget({
146146 wsBaseUrl: "wss://environment.example.test",
147147});
148148
149const TEST_CRYPTO_LAYER = Layer.succeed(
149const layerTestCrypto = Layer.succeed(
150150 Crypto.Crypto,
151151 Crypto.make({
152152 randomBytes: (size) => new Uint8Array(size),
......@@ -280,6 +280,6 @@ describe("routine command transport budget", () => {
280280 });
281281 expect(currentRpcJsonBytes).toBeLessThanOrEqual(MAX_CURRENT_SEND_RPC_JSON_BYTES);
282282 expect(currentRpcJsonBytes / formerRpcJsonBytes).toBeLessThanOrEqual(0.000_06);
283 }).pipe(Effect.provide(TEST_CRYPTO_LAYER)),
283 }).pipe(Effect.provide(layerTestCrypto)),
284284 );
285285});
packages/client-runtime/src/operations/commands.test.ts+22-22
......@@ -56,7 +56,7 @@ import {
5656 updateThreadMetadata,
5757} from "./commands.ts";
5858
59const TEST_CRYPTO_LAYER = Layer.succeed(
59const layerTestCrypto = Layer.succeed(
6060 Crypto.Crypto,
6161 Crypto.make({
6262 randomBytes: (size) => new Uint8Array(size),
......@@ -170,7 +170,7 @@ describe("V2 environment commands", () => {
170170 createWorkspaceRootIfMissing: true,
171171 },
172172 ]);
173 }).pipe(Effect.provide(TEST_CRYPTO_LAYER)),
173 }).pipe(Effect.provide(layerTestCrypto)),
174174 );
175175
176176 it.effect("persists and clears project presentation and environment settings", () =>
......@@ -214,7 +214,7 @@ describe("V2 environment commands", () => {
214214 defaultThreadEnvMode: null,
215215 },
216216 ]);
217 }).pipe(Effect.provide(TEST_CRYPTO_LAYER)),
217 }).pipe(Effect.provide(layerTestCrypto)),
218218 );
219219
220220 it.effect("preserves caller command ids for idempotent V2 commands", () =>
......@@ -230,7 +230,7 @@ describe("V2 environment commands", () => {
230230 expect(commands).toEqual([
231231 { type: "thread.archive", commandId: "queued-command", threadId: "thread-1" },
232232 ]);
233 }).pipe(Effect.provide(TEST_CRYPTO_LAYER)),
233 }).pipe(Effect.provide(layerTestCrypto)),
234234 );
235235
236236 it.effect("resolves run ordinal zero to the persisted thread-start checkpoint", () =>
......@@ -274,7 +274,7 @@ describe("V2 environment commands", () => {
274274 checkpointId,
275275 },
276276 ]);
277 }).pipe(Effect.provide(TEST_CRYPTO_LAYER)),
277 }).pipe(Effect.provide(layerTestCrypto)),
278278 );
279279
280280 it.effect("preserves plan implementation provenance on V2 runs", () =>
......@@ -314,7 +314,7 @@ describe("V2 environment commands", () => {
314314 deliveryIntent: "auto",
315315 dispatchMode: { type: "start_immediately" },
316316 });
317 }).pipe(Effect.provide(TEST_CRYPTO_LAYER)),
317 }).pipe(Effect.provide(layerTestCrypto)),
318318 );
319319
320320 it.effect("preserves an existing worktree and branch during first-message launch", () =>
......@@ -358,7 +358,7 @@ describe("V2 environment commands", () => {
358358 branch: "feature",
359359 },
360360 });
361 }).pipe(Effect.provide(TEST_CRYPTO_LAYER)),
361 }).pipe(Effect.provide(layerTestCrypto)),
362362 );
363363
364364 it.effect("provisions an origin-based worktree for an existing empty thread", () =>
......@@ -398,7 +398,7 @@ describe("V2 environment commands", () => {
398398 startFromOrigin: true,
399399 },
400400 });
401 }).pipe(Effect.provide(TEST_CRYPTO_LAYER)),
401 }).pipe(Effect.provide(layerTestCrypto)),
402402 );
403403
404404 it.effect("uses server-resolved delivery intent without fetching the full projection", () =>
......@@ -434,7 +434,7 @@ describe("V2 environment commands", () => {
434434 });
435435 }
436436 expect(projectionRequests).toEqual([]);
437 }).pipe(Effect.provide(TEST_CRYPTO_LAYER)),
437 }).pipe(Effect.provide(layerTestCrypto)),
438438 );
439439
440440 it.effect("retains projection-shaped delivery for servers without command context support", () =>
......@@ -499,7 +499,7 @@ describe("V2 environment commands", () => {
499499 expect(commands.at(-1)).not.toHaveProperty("deliveryIntent");
500500 }
501501 expect(projectionRequests).toEqual([v2ThreadId, v2ThreadId, v2ThreadId, v2ThreadId]);
502 }).pipe(Effect.provide(TEST_CRYPTO_LAYER)),
502 }).pipe(Effect.provide(layerTestCrypto)),
503503 );
504504
505505 it.effect("Stop with no run left ends the thread's pull request watches", () =>
......@@ -553,7 +553,7 @@ describe("V2 environment commands", () => {
553553 watching: false,
554554 },
555555 ]);
556 }).pipe(Effect.provide(TEST_CRYPTO_LAYER)),
556 }).pipe(Effect.provide(layerTestCrypto)),
557557 );
558558
559559 it.effect.each([
......@@ -629,7 +629,7 @@ describe("V2 environment commands", () => {
629629 },
630630 ],
631631 );
632 }).pipe(Effect.provide(TEST_CRYPTO_LAYER)),
632 }).pipe(Effect.provide(layerTestCrypto)),
633633 );
634634
635635 it.effect(
......@@ -717,7 +717,7 @@ describe("V2 environment commands", () => {
717717 ]);
718718 // A text-only edit must not send an attachments replacement list.
719719 expect(commands[5]).not.toHaveProperty("attachments");
720 }).pipe(Effect.provide(TEST_CRYPTO_LAYER)),
720 }).pipe(Effect.provide(layerTestCrypto)),
721721 );
722722
723723 it.effect("delegates model selection to the server without fetching the full projection", () =>
......@@ -759,7 +759,7 @@ describe("V2 environment commands", () => {
759759 },
760760 ]);
761761 expect(projectionRequests).toEqual([]);
762 }).pipe(Effect.provide(TEST_CRYPTO_LAYER)),
762 }).pipe(Effect.provide(layerTestCrypto)),
763763 );
764764
765765 it.effect("retains provider-switch shaping for servers without command context support", () =>
......@@ -791,7 +791,7 @@ describe("V2 environment commands", () => {
791791 modelSelection: { instanceId: "claude", model: "claude-sonnet-4-6" },
792792 },
793793 ]);
794 }).pipe(Effect.provide(TEST_CRYPTO_LAYER)),
794 }).pipe(Effect.provide(layerTestCrypto)),
795795 );
796796
797797 it.effect.each([true, false])(
......@@ -821,7 +821,7 @@ describe("V2 environment commands", () => {
821821 restoreFiles,
822822 },
823823 ]);
824 }).pipe(Effect.provide(TEST_CRYPTO_LAYER)),
824 }).pipe(Effect.provide(layerTestCrypto)),
825825 );
826826
827827 it.effect("validates identified checkpoints locally for older servers", () =>
......@@ -884,7 +884,7 @@ describe("V2 environment commands", () => {
884884 }
885885 expect(projectionRequests).toEqual([v2ThreadId]);
886886 }
887 }).pipe(Effect.provide(TEST_CRYPTO_LAYER)),
887 }).pipe(Effect.provide(layerTestCrypto)),
888888 );
889889
890890 it.effect("dispatches settle and unsettle commands without timestamps", () =>
......@@ -915,7 +915,7 @@ describe("V2 environment commands", () => {
915915 reason: "user",
916916 },
917917 ]);
918 }).pipe(Effect.provide(TEST_CRYPTO_LAYER)),
918 }).pipe(Effect.provide(layerTestCrypto)),
919919 );
920920
921921 it.effect("sends an active order key without changing activity timestamps", () =>
......@@ -935,7 +935,7 @@ describe("V2 environment commands", () => {
935935 orderKey: "mf",
936936 },
937937 ]);
938 }).pipe(Effect.provide(TEST_CRYPTO_LAYER)),
938 }).pipe(Effect.provide(layerTestCrypto)),
939939 );
940940
941941 it.effect("dismisses a pending user-input request", () =>
......@@ -957,7 +957,7 @@ describe("V2 environment commands", () => {
957957 requestId: "request-1",
958958 },
959959 ]);
960 }).pipe(Effect.provide(TEST_CRYPTO_LAYER)),
960 }).pipe(Effect.provide(layerTestCrypto)),
961961 );
962962
963963 it.effect("dispatches an explicit idle start without fetching the full projection", () =>
......@@ -992,7 +992,7 @@ describe("V2 environment commands", () => {
992992 dispatchMode: { type: "start_immediately" },
993993 },
994994 ]);
995 }).pipe(Effect.provide(TEST_CRYPTO_LAYER)),
995 }).pipe(Effect.provide(layerTestCrypto)),
996996 );
997997
998998 it.effect("interrupts a known run without fetching the full projection", () =>
......@@ -1021,6 +1021,6 @@ describe("V2 environment commands", () => {
10211021 holdQueue: true,
10221022 },
10231023 ]);
1024 }).pipe(Effect.provide(TEST_CRYPTO_LAYER)),
1024 }).pipe(Effect.provide(layerTestCrypto)),
10251025 );
10261026});
packages/client-runtime/src/relay/managedRelay.test.ts+19-19
......@@ -13,19 +13,19 @@ import * as Schema from "effect/Schema";
1313import * as TestClock from "effect/testing/TestClock";
1414
1515import * as ManagedRelay from "./managedRelay.ts";
16import { remoteHttpClientLayer } from "../rpc/http.ts";
16import * as RpcHttp from "../rpc/http.ts";
1717import { NETWORK_BLOCKING_HINT } from "../errors/network.ts";
1818
1919const encodeRelayError = Schema.encodeEffect(ManagedRelay.ManagedRelayClientError);
2020const decodeRelayError = Schema.decodeUnknownEffect(ManagedRelay.ManagedRelayClientError);
2121
22function managedRelayTestLayer(
22function layerManagedRelayTest(
2323 fetchFn: typeof globalThis.fetch,
2424 relayUrl = "https://relay.example.test",
2525 accessTokenStore?: ManagedRelay.ManagedRelayAccessTokenStore,
2626) {
27 const httpClientLayer = remoteHttpClientLayer(fetchFn);
28 const signerLayer = Layer.succeed(
27 const layerHttpClient = RpcHttp.layerRemoteHttpClient(fetchFn);
28 const layerSigner = Layer.succeed(
2929 ManagedRelay.ManagedRelayDpopSigner,
3030 ManagedRelay.ManagedRelayDpopSigner.of({
3131 thumbprint: Effect.succeed("client-thumbprint"),
......@@ -37,7 +37,7 @@ function managedRelayTestLayer(
3737 relayUrl,
3838 clientId: "t3-mobile",
3939 ...(accessTokenStore ? { accessTokenStore } : {}),
40 }).pipe(Layer.provide(signerLayer), Layer.provide(httpClientLayer));
40 }).pipe(Layer.provide(layerSigner), Layer.provide(layerHttpClient));
4141}
4242
4343function clerkToken(subject: string, nonce: string): string {
......@@ -114,7 +114,7 @@ describe("ManagedRelayClient", () => {
114114 "clientRuntime.managedRelay.createRequestProof",
115115 ]),
116116 );
117 }).pipe(Effect.withTracer(tracer), Effect.provide(managedRelayTestLayer(fetchFn)));
117 }).pipe(Effect.withTracer(tracer), Effect.provide(layerManagedRelayTest(fetchFn)));
118118 });
119119
120120 it.effect("rejects unsafe relay URLs before sending credentials", () => {
......@@ -136,7 +136,7 @@ describe("ManagedRelayClient", () => {
136136 message: "Relay URL must be a secure absolute HTTPS origin.",
137137 });
138138 expect(requestCount).toBe(0);
139 }).pipe(Effect.provide(managedRelayTestLayer(fetchFn, "http://relay.example.test")));
139 }).pipe(Effect.provide(layerManagedRelayTest(fetchFn, "http://relay.example.test")));
140140 });
141141
142142 it.effect("reuses usable DPoP tokens and refreshes cleared or expiring cache entries", () => {
......@@ -195,7 +195,7 @@ describe("ManagedRelayClient", () => {
195195 yield* relayClient.resetTokenCache;
196196 yield* relayClient.getEnvironmentStatus(statusInput);
197197 expect(tokenExchangeCount).toBe(3);
198 }).pipe(Effect.provide(managedRelayTestLayer(fetchFn)));
198 }).pipe(Effect.provide(layerManagedRelayTest(fetchFn)));
199199 });
200200
201201 it.effect("uses a cached token while another scope waits for an exchange", () => {
......@@ -290,7 +290,7 @@ describe("ManagedRelayClient", () => {
290290 expect(tokenExchangeCount).toBe(2);
291291 }).pipe(
292292 Effect.ensuring(Effect.sync(() => releaseExchange.resolve())),
293 Effect.provide(managedRelayTestLayer(fetchFn, undefined, accessTokenStore)),
293 Effect.provide(layerManagedRelayTest(fetchFn, undefined, accessTokenStore)),
294294 );
295295 });
296296
......@@ -352,7 +352,7 @@ describe("ManagedRelayClient", () => {
352352 yield* Effect.gen(function* () {
353353 const relayClient = yield* ManagedRelay.ManagedRelayClient;
354354 yield* relayClient.getEnvironmentStatus(statusInput(clerkToken("user-1", "session-1")));
355 }).pipe(Effect.provide(managedRelayTestLayer(fetchFn, undefined, accessTokenStore)));
355 }).pipe(Effect.provide(layerManagedRelayTest(fetchFn, undefined, accessTokenStore)));
356356
357357 expect(tokenExchangeCount).toBe(1);
358358 expect(persistedTokens).toHaveLength(1);
......@@ -360,7 +360,7 @@ describe("ManagedRelayClient", () => {
360360 yield* Effect.gen(function* () {
361361 const relayClient = yield* ManagedRelay.ManagedRelayClient;
362362 yield* relayClient.getEnvironmentStatus(statusInput(clerkToken("user-1", "session-2")));
363 }).pipe(Effect.provide(managedRelayTestLayer(fetchFn, undefined, accessTokenStore)));
363 }).pipe(Effect.provide(layerManagedRelayTest(fetchFn, undefined, accessTokenStore)));
364364
365365 expect(tokenExchangeCount).toBe(1);
366366 });
......@@ -457,7 +457,7 @@ describe("ManagedRelayClient", () => {
457457 accessToken: "fresh-relay-token",
458458 },
459459 ]);
460 }).pipe(Effect.provide(managedRelayTestLayer(fetchFn, undefined, accessTokenStore)));
460 }).pipe(Effect.provide(layerManagedRelayTest(fetchFn, undefined, accessTokenStore)));
461461 });
462462
463463 it.effect("does not persist tokens when the Clerk subject cannot be decoded", () => {
......@@ -513,7 +513,7 @@ describe("ManagedRelayClient", () => {
513513 });
514514
515515 expect(persistedTokens).toEqual([]);
516 }).pipe(Effect.provide(managedRelayTestLayer(fetchFn, undefined, accessTokenStore)));
516 }).pipe(Effect.provide(layerManagedRelayTest(fetchFn, undefined, accessTokenStore)));
517517 });
518518
519519 it.effect("times out stalled relay environment listing requests", () => {
......@@ -536,7 +536,7 @@ describe("ManagedRelayClient", () => {
536536 timeoutMs: ManagedRelay.MANAGED_RELAY_REQUEST_TIMEOUT_MS,
537537 message: `Relay environment listing timed out. ${NETWORK_BLOCKING_HINT}`,
538538 });
539 }).pipe(Effect.provide(Layer.merge(TestClock.layer(), managedRelayTestLayer(fetchFn))));
539 }).pipe(Effect.provide(Layer.merge(TestClock.layer(), layerManagedRelayTest(fetchFn))));
540540 });
541541
542542 it.effect("suggests checking network filtering when fetch fails without a response", () => {
......@@ -555,7 +555,7 @@ describe("ManagedRelayClient", () => {
555555 const encoded = yield* encodeRelayError(error);
556556 const decoded = yield* decodeRelayError(encoded);
557557 expect(decoded.message).toBe(error.message);
558 }).pipe(Effect.provide(managedRelayTestLayer(fetchFn)));
558 }).pipe(Effect.provide(layerManagedRelayTest(fetchFn)));
559559 });
560560
561561 it.effect("does not suggest network filtering for an HTTP server error", () => {
......@@ -569,7 +569,7 @@ describe("ManagedRelayClient", () => {
569569 .listEnvironments({ clerkToken: "clerk-token" })
570570 .pipe(Effect.flip);
571571 expect(error.message).toBe("Could not list relay-managed environments.");
572 }).pipe(Effect.provide(managedRelayTestLayer(fetchFn)));
572 }).pipe(Effect.provide(layerManagedRelayTest(fetchFn)));
573573 });
574574
575575 it.effect("preserves typed relay trace IDs on client errors", () => {
......@@ -596,7 +596,7 @@ describe("ManagedRelayClient", () => {
596596 _tag: "ManagedRelayRequestFailedError",
597597 traceId: "trace-managed-relay",
598598 });
599 }).pipe(Effect.provide(managedRelayTestLayer(fetchFn)));
599 }).pipe(Effect.provide(layerManagedRelayTest(fetchFn)));
600600 });
601601
602602 it.effect("accepts generic DPoP errors from relays without the optional reason", () => {
......@@ -633,7 +633,7 @@ describe("ManagedRelayClient", () => {
633633 ) {
634634 expect(error.relayError.dpopFailureReason).toBeUndefined();
635635 }
636 }).pipe(Effect.provide(managedRelayTestLayer(fetchFn)));
636 }).pipe(Effect.provide(layerManagedRelayTest(fetchFn)));
637637 });
638638
639639 it.effect("lists account devices through the v2 Clerk bearer client endpoint", () => {
......@@ -703,6 +703,6 @@ describe("ManagedRelayClient", () => {
703703 androidApiLevel: 36,
704704 },
705705 ]);
706 }).pipe(Effect.provide(managedRelayTestLayer(fetchFn)));
706 }).pipe(Effect.provide(layerManagedRelayTest(fetchFn)));
707707 });
708708});
packages/client-runtime/src/rpc/http.ts+3-3
......@@ -8,7 +8,7 @@ import {
88 type EnvironmentResourceNotFoundError,
99 type EnvironmentScopeRequiredError,
1010} from "@t3tools/contracts";
11import { httpHeaderRedactionLayer } from "@t3tools/shared/httpObservability";
11import * as HttpObservability from "@t3tools/shared/httpObservability";
1212import * as Data from "effect/Data";
1313import * as Duration from "effect/Duration";
1414import * as Effect from "effect/Effect";
......@@ -78,12 +78,12 @@ export type RemoteEnvironmentRequestError =
7878 | RemoteEnvironmentAuthUndeclaredStatusError
7979 | RemoteEnvironmentAuthTimeoutError;
8080
81export const remoteHttpClientLayer = (
81export const layerRemoteHttpClient = (
8282 fetchFn: typeof globalThis.fetch,
8383): Layer.Layer<HttpClient.HttpClient> =>
8484 Layer.merge(
8585 FetchHttpClient.layer.pipe(Layer.provide(Layer.succeed(FetchHttpClient.Fetch, fetchFn))),
86 httpHeaderRedactionLayer,
86 HttpObservability.layer,
8787 );
8888
8989const remoteApiBaseUrl = (httpBaseUrl: string): string => {
packages/client-runtime/src/rpc/session.test.ts+2-2
......@@ -215,12 +215,12 @@ const makeFactory = Effect.fn("TestRpcSessionFactory.make")(function* (
215215 options: RpcSession.RpcSessionOptions = {},
216216) {
217217 const sockets: TestWebSocket[] = [];
218 const constructorLayer = Layer.succeed(Socket.WebSocketConstructor, (url) => {
218 const layerConstructor = Layer.succeed(Socket.WebSocketConstructor, (url) => {
219219 const socket = new TestWebSocket(url);
220220 sockets.push(socket);
221221 return socket as unknown as globalThis.WebSocket;
222222 });
223 const layer = RpcSession.layer(options).pipe(Layer.provide(constructorLayer));
223 const layer = RpcSession.layer(options).pipe(Layer.provide(layerConstructor));
224224 const factory = yield* RpcSession.RpcSessionFactory.pipe(Effect.provide(layer));
225225 return { factory, sockets };
226226});
packages/client-runtime/src/rpc/session.ts+4-4
......@@ -195,10 +195,10 @@ export const make = Effect.fn("RpcSessionFactory.make")(function* (
195195 Effect.asVoid,
196196 ),
197197 });
198 const socketLayer = Socket.layerWebSocket(connection.socketUrl, {
198 const layerSocket = Socket.layerWebSocket(connection.socketUrl, {
199199 openTimeout: SOCKET_OPEN_TIMEOUT,
200200 }).pipe(Layer.provide(Layer.succeed(Socket.WebSocketConstructor, webSocketConstructor)));
201 const protocolLayer = Layer.effect(
201 const layerProtocol = Layer.effect(
202202 RpcClient.Protocol,
203203 RpcClient.makeProtocolSocket({
204204 retryTransientErrors: false,
......@@ -207,13 +207,13 @@ export const make = Effect.fn("RpcSessionFactory.make")(function* (
207207 ).pipe(
208208 Layer.provide(
209209 Layer.mergeAll(
210 socketLayer,
210 layerSocket,
211211 RpcSerialization.layerJson,
212212 Layer.succeed(RpcClient.ConnectionHooks, hooks),
213213 ),
214214 ),
215215 );
216 const protocolContext = yield* Layer.build(protocolLayer).pipe(
216 const protocolContext = yield* Layer.build(layerProtocol).pipe(
217217 Effect.withSpan("environment.websocket.connect"),
218218 );
219219 const protocolClient = yield* makeWsRpcProtocolClient.pipe(Effect.provide(protocolContext));
packages/client-runtime/src/state/boundedThreadSnapshotHttp.test.ts+6-6
......@@ -4,8 +4,8 @@ import * as Effect from "effect/Effect";
44import * as Layer from "effect/Layer";
55
66import { PrimaryConnectionTarget, type PreparedConnection } from "../connection/model.ts";
7import { remoteHttpClientLayer } from "../rpc/http.ts";
8import { boundedThreadSnapshotLoaderLayer } from "./boundedThreadSnapshotHttp.ts";
7import * as RpcHttp from "../rpc/http.ts";
8import * as BoundedThreadSnapshotHttp from "./boundedThreadSnapshotHttp.ts";
99import * as ThreadSnapshotLoader from "./threadSnapshotHttp.ts";
1010
1111const TARGET = new PrimaryConnectionTarget({
......@@ -110,7 +110,7 @@ describe("boundedThreadSnapshotLoader", () => {
110110 }
111111 }).pipe(
112112 Effect.provide(
113 Layer.provide(boundedThreadSnapshotLoaderLayer, remoteHttpClientLayer(fetchFn)),
113 Layer.provide(BoundedThreadSnapshotHttp.layer, RpcHttp.layerRemoteHttpClient(fetchFn)),
114114 ),
115115 );
116116 });
......@@ -138,7 +138,7 @@ describe("boundedThreadSnapshotLoader", () => {
138138 expect(result).toEqual({ _tag: "missing" });
139139 }).pipe(
140140 Effect.provide(
141 Layer.provide(boundedThreadSnapshotLoaderLayer, remoteHttpClientLayer(fetchFn)),
141 Layer.provide(BoundedThreadSnapshotHttp.layer, RpcHttp.layerRemoteHttpClient(fetchFn)),
142142 ),
143143 );
144144 });
......@@ -176,7 +176,7 @@ describe("boundedThreadSnapshotLoader", () => {
176176 expect(fullCalls).toBe(1);
177177 }).pipe(
178178 Effect.provide(
179 Layer.provide(boundedThreadSnapshotLoaderLayer, remoteHttpClientLayer(fetchFn)),
179 Layer.provide(BoundedThreadSnapshotHttp.layer, RpcHttp.layerRemoteHttpClient(fetchFn)),
180180 ),
181181 );
182182 });
......@@ -206,7 +206,7 @@ describe("boundedThreadSnapshotLoader", () => {
206206 expect(fullCalls).toBe(0);
207207 }).pipe(
208208 Effect.provide(
209 Layer.provide(boundedThreadSnapshotLoaderLayer, remoteHttpClientLayer(fetchFn)),
209 Layer.provide(BoundedThreadSnapshotHttp.layer, RpcHttp.layerRemoteHttpClient(fetchFn)),
210210 ),
211211 );
212212 },
packages/client-runtime/src/state/boundedThreadSnapshotHttp.ts+1-1
......@@ -53,7 +53,7 @@ export const fetchEnvironmentBoundedThreadSnapshot = Effect.fn(
5353 * endpoint still means missing. Transient failures report `unavailable` so the
5454 * socket path remains a last resort for connectivity issues.
5555 */
56export const boundedThreadSnapshotLoaderLayer: Layer.Layer<
56export const layer: Layer.Layer<
5757 ThreadSnapshotLoader.ThreadSnapshotLoader,
5858 never,
5959 HttpClient.HttpClient
packages/client-runtime/src/state/environmentHttpAuth.test.ts+9-10
......@@ -27,16 +27,15 @@ import {
2727 type PreparedHttpAuthorization,
2828} from "../connection/model.ts";
2929import * as ManagedRelay from "../relay/managedRelay.ts";
30import { remoteHttpClientLayer, type RemoteEnvironmentRequestError } from "../rpc/http.ts";
30import { type RemoteEnvironmentRequestError } from "../rpc/http.ts";
31import * as RpcHttp from "../rpc/http.ts";
3132import * as PullRequestDiffLoader from "./pullRequestDiffHttp.ts";
3233import { withOrchestrationProtocolHeader } from "./environmentHttpAuth.ts";
3334import { fetchEnvironmentSessionState } from "./session.ts";
3435import { fetchEnvironmentShellSnapshot } from "./shellSnapshotHttp.ts";
3536import * as ThreadSnapshotLoader from "./threadSnapshotHttp.ts";
36import {
37 boundedThreadSnapshotLoaderLayer,
38 fetchEnvironmentBoundedThreadSnapshot,
39} from "./boundedThreadSnapshotHttp.ts";
37import { fetchEnvironmentBoundedThreadSnapshot } from "./boundedThreadSnapshotHttp.ts";
38import * as BoundedThreadSnapshotHttp from "./boundedThreadSnapshotHttp.ts";
4039import { fetchEnvironmentThreadHistoryPage } from "./threadHistoryHttp.ts";
4140import { v2Projection } from "./orchestrationV2TestFixtures.ts";
4241
......@@ -161,7 +160,7 @@ function makeHarness(reply: (requestNumber: number) => Response | Promise<Respon
161160 signer: Option.some(signer),
162161 remoteAuthorization: Option.some(remoteAuthorization),
163162 },
164 httpLayer: remoteHttpClientLayer(fetchFn),
163 httpLayer: RpcHttp.layerRemoteHttpClient(fetchFn),
165164 };
166165}
167166
......@@ -360,7 +359,7 @@ describe("authenticated environment HTTP requests", () => {
360359 ? credentialRejectedResponse()
361360 : Response.json(encodeBoundedSnapshot(BOUNDED_THREAD)),
362361 );
363 const loaderLayer = boundedThreadSnapshotLoaderLayer.pipe(
362 const layerLoader = BoundedThreadSnapshotHttp.layer.pipe(
364363 Layer.provide(
365364 Layer.mergeAll(
366365 harness.httpLayer,
......@@ -376,7 +375,7 @@ describe("authenticated environment HTTP requests", () => {
376375 ),
377376 );
378377 const loader = yield* ThreadSnapshotLoader.ThreadSnapshotLoader.pipe(
379 Effect.provide(loaderLayer),
378 Effect.provide(layerLoader),
380379 );
381380 const result = yield* loader.load(PREPARED, THREAD.projection.thread.id);
382381 expect(result).toEqual({
......@@ -398,7 +397,7 @@ describe("authenticated environment HTTP requests", () => {
398397 it.effect("uses the authorization service captured by the diff loader layer", () =>
399398 Effect.gen(function* () {
400399 const harness = makeHarness(() => Response.json(DIFF_RESULT));
401 const loaderLayer = PullRequestDiffLoader.layer.pipe(
400 const layerLoader = PullRequestDiffLoader.layer.pipe(
402401 Layer.provide(
403402 Layer.mergeAll(
404403 harness.httpLayer,
......@@ -414,7 +413,7 @@ describe("authenticated environment HTTP requests", () => {
414413 ),
415414 );
416415 const loader = yield* PullRequestDiffLoader.PullRequestDiffLoader.pipe(
417 Effect.provide(loaderLayer),
416 Effect.provide(layerLoader),
418417 );
419418 const result = yield* loader.load(PREPARED, DIFF);
420419
packages/client-runtime/src/state/pullRequestDiffHttp.test.ts+3-3
......@@ -4,7 +4,7 @@ import * as Effect from "effect/Effect";
44import * as Option from "effect/Option";
55
66import { PrimaryConnectionTarget, type PreparedConnection } from "../connection/model.ts";
7import { remoteHttpClientLayer } from "../rpc/http.ts";
7import * as RpcHttp from "../rpc/http.ts";
88import {
99 fetchEnvironmentPullRequestDiff,
1010 PullRequestDiffCredentialRejectedError,
......@@ -50,7 +50,7 @@ describe("fetchEnvironmentPullRequestDiff", () => {
5050 number: 42,
5151 cursor: "next-page",
5252 },
53 }).pipe(Effect.provide(remoteHttpClientLayer(fetchFn)));
53 }).pipe(Effect.provide(RpcHttp.layerRemoteHttpClient(fetchFn)));
5454
5555 expect(result).toEqual({
5656 patch: "diff --git a/file.ts b/file.ts",
......@@ -104,7 +104,7 @@ describe("fetchEnvironmentPullRequestDiff", () => {
104104 repository: "owner/repository",
105105 number: 42,
106106 },
107 }).pipe(Effect.provide(remoteHttpClientLayer(fetchFn)), Effect.flip);
107 }).pipe(Effect.provide(RpcHttp.layerRemoteHttpClient(fetchFn)), Effect.flip);
108108
109109 expect(error).toBeInstanceOf(PullRequestDiffCredentialRejectedError);
110110 expect(error).toMatchObject({
packages/client-runtime/src/state/threads.ts+1-1
......@@ -995,7 +995,7 @@ export function createEnvironmentThreadStateAtoms<R, E>(
995995
996996export * from "./archivedThreads.ts";
997997export * from "./checkpointDiff.ts";
998export * from "./boundedThreadSnapshotHttp.ts";
998export * as BoundedThreadSnapshotLoader from "./boundedThreadSnapshotHttp.ts";
999999export * as ThreadHistoryController from "./threadHistoryController.ts";
10001000// Flat so consumers' inferred types can name it.
10011001export type { ThreadHistoryLoadEarlierResult } from "./threadHistoryController.ts";
packages/effect-acp/scripts/generate.ts+2-2
......@@ -514,7 +514,7 @@ const generateCommand = Command.make(
514514 ({ skipDownload }) => generateSchemas(skipDownload),
515515).pipe(Command.withDescription("Generate Effect ACP schemas from the pinned ACP release assets."));
516516
517const runtimeLayer = Layer.mergeAll(
517const layerRuntime = Layer.mergeAll(
518518 Logger.layer([Logger.consolePretty()]),
519519 NodeServices.layer,
520520 FetchHttpClient.layer,
......@@ -522,6 +522,6 @@ const runtimeLayer = Layer.mergeAll(
522522
523523Command.run(generateCommand, { version: "0.0.0" }).pipe(
524524 Effect.scoped,
525 Effect.provide(runtimeLayer),
525 Effect.provide(layerRuntime),
526526 NodeRuntime.runMain,
527527);
packages/effect-acp/src/agent.ts+2-2
......@@ -316,7 +316,7 @@ export const make = Effect.fn("effect-acp/AcpAgent.make")(function* (
316316 method,
317317 });
318318
319 const agentHandlerLayer = AcpRpcs.AgentRpcs.toLayer(
319 const layerAgentHandler = AcpRpcs.AgentRpcs.toLayer(
320320 AcpRpcs.AgentRpcs.of({
321321 [AGENT_METHODS.initialize]: (payload, { requestId }) =>
322322 runHandler(
......@@ -423,7 +423,7 @@ export const make = Effect.fn("effect-acp/AcpAgent.make")(function* (
423423
424424 yield* RpcServer.make(AcpRpcs.AgentRpcs).pipe(
425425 Effect.provideService(RpcServer.Protocol, transport.serverProtocol),
426 Effect.provide(agentHandlerLayer),
426 Effect.provide(layerAgentHandler),
427427 Effect.forkScoped,
428428 );
429429
packages/effect-acp/src/client.test.ts+8-8
......@@ -309,8 +309,8 @@ it.layer(NodeServices.layer)("effect-acp client", (it) => {
309309 const requestContexts = yield* Ref.make<Array<AcpProtocol.AcpRequestContext>>([]);
310310 const handle = yield* makeHandle();
311311 const scope = yield* Scope.make();
312 const acpLayer = AcpClient.layerChildProcess(handle);
313 const context = yield* Layer.buildWithScope(acpLayer, scope);
312 const layerAcp = AcpClient.layerChildProcess(handle);
313 const context = yield* Layer.buildWithScope(layerAcp, scope);
314314
315315 const ext = yield* Effect.gen(function* () {
316316 const acp = yield* AcpClient.AcpClient;
......@@ -431,8 +431,8 @@ it.layer(NodeServices.layer)("effect-acp client", (it) => {
431431 Effect.gen(function* () {
432432 const handle = yield* makeHandle({ ACP_MOCK_BAD_TYPED_REQUEST: "1" });
433433 const scope = yield* Scope.make();
434 const acpLayer = AcpClient.layerChildProcess(handle);
435 const context = yield* Layer.buildWithScope(acpLayer, scope);
434 const layerAcp = AcpClient.layerChildProcess(handle);
435 const context = yield* Layer.buildWithScope(layerAcp, scope);
436436
437437 const result = yield* Effect.gen(function* () {
438438 const acp = yield* AcpClient.AcpClient;
......@@ -598,8 +598,8 @@ it.layer(NodeServices.layer)("effect-acp client", (it) => {
598598 const typedNotifications = yield* Ref.make<Array<unknown>>([]);
599599 const handle = yield* makeHandle();
600600 const scope = yield* Scope.make();
601 const acpLayer = AcpClient.layerChildProcess(handle);
602 const context = yield* Layer.buildWithScope(acpLayer, scope);
601 const layerAcp = AcpClient.layerChildProcess(handle);
602 const context = yield* Layer.buildWithScope(layerAcp, scope);
603603
604604 yield* Effect.gen(function* () {
605605 const acp = yield* AcpClient.AcpClient;
......@@ -679,8 +679,8 @@ it.layer(NodeServices.layer)("effect-acp client", (it) => {
679679 const successfulHandlers = yield* Ref.make(0);
680680 const handle = yield* makeHandle();
681681 const scope = yield* Scope.make();
682 const acpLayer = AcpClient.layerChildProcess(handle);
683 const context = yield* Layer.buildWithScope(acpLayer, scope);
682 const layerAcp = AcpClient.layerChildProcess(handle);
683 const context = yield* Layer.buildWithScope(layerAcp, scope);
684684
685685 yield* Effect.gen(function* () {
686686 const acp = yield* AcpClient.AcpClient;
packages/effect-acp/src/client.ts+2-2
......@@ -971,7 +971,7 @@ export const make = Effect.fn("effect-acp/AcpClient.make")(function* (
971971 method,
972972 });
973973
974 const clientHandlerLayer = AcpRpcs.CompatClientRpcs.toLayer(
974 const layerClientHandler = AcpRpcs.CompatClientRpcs.toLayer(
975975 AcpRpcs.CompatClientRpcs.of({
976976 [CLIENT_METHODS.session_request_permission]: (payload, { requestId }) =>
977977 runHandler(
......@@ -1101,7 +1101,7 @@ export const make = Effect.fn("effect-acp/AcpClient.make")(function* (
11011101
11021102 yield* RpcServer.make(AcpRpcs.CompatClientRpcs).pipe(
11031103 Effect.provideService(RpcServer.Protocol, transport.serverProtocol),
1104 Effect.provide(clientHandlerLayer),
1104 Effect.provide(layerClientHandler),
11051105 Effect.forkScoped,
11061106 );
11071107
packages/effect-codex-app-server/src/client.test.ts+4-4
......@@ -35,8 +35,8 @@ it.layer(NodeServices.layer)("effect-codex-app-server client", (it) => {
3535 const messageDeltas = yield* Ref.make<Array<unknown>>([]);
3636 const handle = yield* makeHandle();
3737 const scope = yield* Scope.make();
38 const clientLayer = CodexClient.layerChildProcess(handle);
39 const context = yield* Layer.buildWithScope(clientLayer, scope);
38 const layerClient = CodexClient.layerChildProcess(handle);
39 const context = yield* Layer.buildWithScope(layerClient, scope);
4040
4141 const result = yield* Effect.gen(function* () {
4242 const client = yield* CodexClient.CodexAppServerClient;
......@@ -130,8 +130,8 @@ it.layer(NodeServices.layer)("effect-codex-app-server client", (it) => {
130130 CODEX_APP_SERVER_TEST_STDERR_BYTES: String(512 * 1024),
131131 });
132132 const scope = yield* Scope.make();
133 const clientLayer = CodexClient.layerChildProcess(handle);
134 const context = yield* Layer.buildWithScope(clientLayer, scope);
133 const layerClient = CodexClient.layerChildProcess(handle);
134 const context = yield* Layer.buildWithScope(layerClient, scope);
135135
136136 const initialized = yield* Effect.gen(function* () {
137137 const client = yield* CodexClient.CodexAppServerClient;
packages/effect-codex-app-server/test/examples/codex-app-server-probe.ts+2-2
......@@ -15,7 +15,7 @@ const program = Effect.gen(function* () {
1515 shell: false,
1616 }),
1717 );
18 const codexLayer = CodexClient.layerChildProcess(handle, {
18 const layerCodex = CodexClient.layerChildProcess(handle, {
1919 logIncoming: true,
2020 logOutgoing: true,
2121 });
......@@ -61,7 +61,7 @@ const program = Effect.gen(function* () {
6161 cwds: [process.cwd()],
6262 });
6363 yield* Console.log("skills/list", skills);
64 }).pipe(Effect.provide(codexLayer));
64 }).pipe(Effect.provide(layerCodex));
6565});
6666
6767program.pipe(Effect.scoped, Effect.provide(NodeServices.layer), NodeRuntime.runMain);
packages/shared/src/httpObservability.ts+1-1
......@@ -2,7 +2,7 @@ import * as Effect from "effect/Effect";
22import * as Layer from "effect/Layer";
33import * as Headers from "effect/http/Headers";
44
5export const httpHeaderRedactionLayer = Layer.effect(
5export const layer = Layer.effect(
66 Headers.CurrentRedactedNames,
77 Effect.map(Headers.CurrentRedactedNames, (names) => [...names, "dpop"]),
88);
packages/shared/src/observability.test.ts+3-3
......@@ -99,7 +99,7 @@ const readTraceRecords = Effect.fn("readTraceRecords")(function* (tracePath: str
9999 .map((line) => decodeTraceRecordLine(line));
100100});
101101
102const makeTestLayer = (tracePath: string) =>
102const layerTest = (tracePath: string) =>
103103 Layer.mergeAll(
104104 Layer.effect(
105105 Tracer.Tracer,
......@@ -550,7 +550,7 @@ describe("observability", () => {
550550 }).pipe(Effect.withSpan("child-span"));
551551 }).pipe(Effect.withSpan("parent-span"));
552552
553 yield* program.pipe(Effect.provide(makeTestLayer(tracePath)));
553 yield* program.pipe(Effect.provide(layerTest(tracePath)));
554554 }),
555555 );
556556
......@@ -597,7 +597,7 @@ describe("observability", () => {
597597 Effect.exit(
598598 Effect.interrupt.pipe(
599599 Effect.withSpan("interrupt-span"),
600 Effect.provide(makeTestLayer(tracePath)),
600 Effect.provide(layerTest(tracePath)),
601601 ),
602602 ),
603603 );
packages/shared/src/observability.ts+1-1
......@@ -13,7 +13,7 @@ import { RotatingFileSink } from "./logging.ts";
1313
1414export const OtlpProtocol = Schema.Literals(["http/json", "http/protobuf"]);
1515export type OtlpProtocol = typeof OtlpProtocol.Type;
16export const otlpSerializationLayer = (protocol: OtlpProtocol) =>
16export const layerOtlpSerialization = (protocol: OtlpProtocol) =>
1717 protocol === "http/protobuf" ? OtlpSerialization.layerProtobuf : OtlpSerialization.layerJson;
1818
1919/**
packages/shared/src/otelEnvironment.test.ts+6-3
......@@ -488,13 +488,16 @@ describe("OtelEnvironment", () => {
488488 { name: "encoded separators", raw: "a%2Cb=x%3Dy", attributes: ["a,b"] },
489489 ])("lets the exporters' own read succeed with $name", ({ raw, attributes }) =>
490490 Effect.gen(function* () {
491 const env = ConfigProvider.layer(
491 const layerEnv = ConfigProvider.layer(
492492 ConfigProvider.fromEnv({ env: { OTEL_RESOURCE_ATTRIBUTES: raw } }),
493493 );
494 const otel = yield* OtelEnvironment.load.pipe(Effect.provide(env));
494 const otel = yield* OtelEnvironment.load.pipe(Effect.provide(layerEnv));
495495 const resource = yield* OtlpResource.fromConfig({ serviceName: "t3" }).pipe(
496496 Effect.provide(
497 Layer.provide(OtelEnvironment.layerResourceAttributes(otel.resourceAttributes), env),
497 Layer.provide(
498 OtelEnvironment.layerResourceAttributes(otel.resourceAttributes),
499 layerEnv,
500 ),
498501 ),
499502 );
500503 assert.deepStrictEqual(
packages/shared/src/relayClient.test.ts+18-18
......@@ -18,7 +18,7 @@ import * as RelayClient from "./relayClient.ts";
1818// POSIX exec bits that NTFS never reports; the win32 branch skips that check.
1919const windowsHost = HostProcessPlatform.defaultValue() === "win32";
2020
21const hostRuntimeLayer = (env: Record<string, string> = {}) =>
21const layerHostRuntime = (env: Record<string, string> = {}) =>
2222 Layer.mergeAll(
2323 Layer.succeed(HostProcessPlatform, "linux"),
2424 Layer.succeed(HostProcessArchitecture, "x64"),
......@@ -41,7 +41,7 @@ function makeHandle(exitCode = 0) {
4141 });
4242}
4343
44const makeHttpClientLayer = (bytes: Uint8Array) =>
44const layerHttpClient = (bytes: Uint8Array) =>
4545 Layer.succeed(
4646 HttpClient.HttpClient,
4747 HttpClient.make((request) =>
......@@ -51,7 +51,7 @@ const makeHttpClientLayer = (bytes: Uint8Array) =>
5151 ),
5252 );
5353
54const makeSpawnerLayer = (commands: Array<string>) =>
54const layerSpawner = (commands: Array<string>) =>
5555 Layer.succeed(
5656 ChildProcessSpawner.ChildProcessSpawner,
5757 ChildProcessSpawner.make((command) =>
......@@ -101,9 +101,9 @@ describe("RelayClient", () => {
101101 Effect.provide(
102102 Layer.mergeAll(
103103 NodeServices.layer,
104 makeHttpClientLayer(new Uint8Array()),
105 makeSpawnerLayer([]),
106 hostRuntimeLayer(),
104 layerHttpClient(new Uint8Array()),
105 layerSpawner([]),
106 layerHostRuntime(),
107107 ),
108108 ),
109109 ),
......@@ -160,9 +160,9 @@ describe("RelayClient", () => {
160160 Effect.provide(
161161 Layer.mergeAll(
162162 NodeServices.layer,
163 makeHttpClientLayer(new TextEncoder().encode("test-cloudflared-binary")),
164 makeSpawnerLayer([]),
165 hostRuntimeLayer(),
163 layerHttpClient(new TextEncoder().encode("test-cloudflared-binary")),
164 layerSpawner([]),
165 layerHostRuntime(),
166166 ),
167167 ),
168168 ),
......@@ -191,9 +191,9 @@ describe("RelayClient", () => {
191191 Effect.provide(
192192 Layer.mergeAll(
193193 NodeServices.layer,
194 makeHttpClientLayer(new TextEncoder().encode("tampered")),
195 makeSpawnerLayer([]),
196 hostRuntimeLayer(),
194 layerHttpClient(new TextEncoder().encode("tampered")),
195 layerSpawner([]),
196 layerHostRuntime(),
197197 ),
198198 ),
199199 ),
......@@ -226,9 +226,9 @@ describe("RelayClient", () => {
226226 Effect.provide(
227227 Layer.mergeAll(
228228 NodeServices.layer,
229 makeHttpClientLayer(bytes),
230 makeSpawnerLayer(commands),
231 hostRuntimeLayer(),
229 layerHttpClient(bytes),
230 layerSpawner(commands),
231 layerHostRuntime(),
232232 ),
233233 ),
234234 );
......@@ -270,9 +270,9 @@ describe("RelayClient", () => {
270270 Effect.provide(
271271 Layer.mergeAll(
272272 NodeServices.layer,
273 makeHttpClientLayer(new Uint8Array()),
274 makeSpawnerLayer([]),
275 hostRuntimeLayer(env),
273 layerHttpClient(new Uint8Array()),
274 layerSpawner([]),
275 layerHostRuntime(env),
276276 ),
277277 ),
278278 );
packages/shared/src/relayTracing.test.ts+8-11
......@@ -6,11 +6,8 @@ import * as Tracer from "effect/Tracer";
66import { FetchHttpClient } from "effect/http";
77import { vi } from "vite-plus/test";
88
9import {
10 makeRelayClientTracingLayer,
11 RelayClientTracer,
12 withRelayClientTracing,
13} from "./relayTracing.ts";
9import { RelayClientTracer, withRelayClientTracing } from "./relayTracing.ts";
10import * as RelayTracing from "./relayTracing.ts";
1411
1512function collectingTracer(spans: Array<string>): Tracer.Tracer {
1613 return Tracer.make({
......@@ -64,10 +61,10 @@ describe("withRelayClientTracing", () => {
6461
6562 it.effect("preserves nested error causes in exported relay spans", () => {
6663 const fetchFn = vi.fn<typeof fetch>(async () => new Response(null, { status: 202 }));
67 const httpClientLayer = FetchHttpClient.layer.pipe(
64 const layerHttpClient = FetchHttpClient.layer.pipe(
6865 Layer.provide(Layer.succeed(FetchHttpClient.Fetch, fetchFn)),
6966 );
70 const tracingLayer = makeRelayClientTracingLayer(
67 const layerTracing = RelayTracing.layer(
7168 {
7269 tracesUrl: "https://api.axiom.test/v1/traces",
7370 tracesDataset: "relay-traces",
......@@ -78,18 +75,18 @@ describe("withRelayClientTracing", () => {
7875 runtime: "test",
7976 client: "test",
8077 },
81 ).pipe(Layer.provide(httpClientLayer));
78 ).pipe(Layer.provide(layerHttpClient));
8279 const rootCause = new Error("relay socket closed");
8380 const failure = new Error("relay request failed", { cause: rootCause });
84 const tracedApplication = Layer.effectDiscard(
81 const layerTracedApplication = Layer.effectDiscard(
8582 Effect.fail(failure).pipe(
8683 Effect.withSpan("relay.failed-operation"),
8784 withRelayClientTracing,
8885 Effect.exit,
8986 ),
90 ).pipe(Layer.provide(tracingLayer));
87 ).pipe(Layer.provide(layerTracing));
9188
92 return Layer.build(tracedApplication).pipe(
89 return Layer.build(layerTracedApplication).pipe(
9390 Effect.scoped,
9491 Effect.andThen(
9592 Effect.sync(() => {
packages/shared/src/relayTracing.ts+3-3
......@@ -124,7 +124,7 @@ function nonInterferingTracer(delegate: Tracer.Tracer): Tracer.Tracer {
124124 });
125125}
126126
127export function makeRelayClientTracingLayer(
127export function layer(
128128 config: RelayClientTracingConfig | null,
129129 resource: RelayClientTracingResource,
130130): Layer.Layer<never, never, HttpClient.HttpClient> {
......@@ -132,7 +132,7 @@ export function makeRelayClientTracingLayer(
132132 return Layer.succeed(RelayClientTracer, Option.none());
133133 }
134134
135 const tracerLayer = OtlpTracer.layer({
135 const layerTracer = OtlpTracer.layer({
136136 url: config.tracesUrl,
137137 headers: {
138138 Authorization: `Bearer ${config.tracesToken}`,
......@@ -153,5 +153,5 @@ export function makeRelayClientTracingLayer(
153153 return Layer.effect(
154154 RelayClientTracer,
155155 Tracer.Tracer.pipe(Effect.map(nonInterferingTracer), Effect.asSome),
156 ).pipe(Layer.provide(tracerLayer));
156 ).pipe(Layer.provide(layerTracer));
157157}
packages/ssh/src/command.test.ts+9-9
......@@ -113,8 +113,8 @@ describe("ssh command", () => {
113113 const spawner = ChildProcessSpawner.make(() =>
114114 Effect.succeed(makeFailedProcess({ stdout: "Pairing token creation failed\n" })),
115115 );
116 const spawnerLayer = Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner);
117 const processLayer = Layer.mergeAll(NodeServices.layer, spawnerLayer);
116 const layerSpawner = Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner);
117 const layerProcess = Layer.mergeAll(NodeServices.layer, layerSpawner);
118118
119119 return Effect.gen(function* () {
120120 const result = yield* Effect.result(
......@@ -136,15 +136,15 @@ describe("ssh command", () => {
136136 assert.equal(result.failure.stdout, "Pairing token creation failed\n");
137137 assert.equal(result.failure.stderr, "");
138138 }
139 }).pipe(Effect.provide(processLayer));
139 }).pipe(Effect.provide(layerProcess));
140140 });
141141
142142 it.effect("redacts credentials from stdout in non-zero command failures", () => {
143143 const spawner = ChildProcessSpawner.make(() =>
144144 Effect.succeed(makeFailedProcess({ stdout: '{"credential":"pairing-secret"}\n' })),
145145 );
146 const spawnerLayer = Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner);
147 const processLayer = Layer.mergeAll(NodeServices.layer, spawnerLayer);
146 const layerSpawner = Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner);
147 const layerProcess = Layer.mergeAll(NodeServices.layer, layerSpawner);
148148
149149 return Effect.gen(function* () {
150150 const result = yield* Effect.result(
......@@ -165,13 +165,13 @@ describe("ssh command", () => {
165165 assert.equal(result.failure.message, '{"credential":"[redacted]"}');
166166 assert.equal(result.failure.stdout, '{"credential":"[redacted]"}\n');
167167 }
168 }).pipe(Effect.provide(processLayer));
168 }).pipe(Effect.provide(layerProcess));
169169 });
170170
171171 it.effect("fails commands that never finish", () => {
172172 const spawner = ChildProcessSpawner.make(() => Effect.succeed(makeNeverFinishingProcess()));
173 const spawnerLayer = Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner);
174 const processLayer = Layer.mergeAll(NodeServices.layer, spawnerLayer, TestClock.layer());
173 const layerSpawner = Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner);
174 const layerProcess = Layer.mergeAll(NodeServices.layer, layerSpawner, TestClock.layer());
175175
176176 return Effect.gen(function* () {
177177 const fiber = yield* Effect.forkChild(
......@@ -196,6 +196,6 @@ describe("ssh command", () => {
196196 if (Result.isFailure(result)) {
197197 assert.include(result.failure.message, "SSH command timed out after 1ms.");
198198 }
199 }).pipe(Effect.provide(processLayer));
199 }).pipe(Effect.provide(layerProcess));
200200 });
201201});
packages/ssh/src/tunnel.test.ts+15-15
......@@ -325,14 +325,14 @@ describe("ssh tunnel scripts", () => {
325325 return makeSuccessfulProcess('loaded nvm default\n{"remotePort":3774}\n');
326326 }),
327327 );
328 const spawnerLayer = Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner);
329 const processLayer = Layer.merge(NodeServices.layer, spawnerLayer);
328 const layerSpawner = Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner);
329 const layerProcess = Layer.merge(NodeServices.layer, layerSpawner);
330330
331331 return Effect.gen(function* () {
332332 const result = yield* SshTunnel.launchOrReuseRemoteServer(target, undefined, ARCHIVE);
333333 assert.equal(result.remotePort, 3774);
334334 assert.deepEqual(spawnedCommands[0]?.slice(-5, -1), ["sh", "-l", "-s", "--"]);
335 }).pipe(Effect.provide(processLayer));
335 }).pipe(Effect.provide(layerProcess));
336336 });
337337
338338 it.effect("allows cold remote launches to exceed the default SSH command timeout", () => {
......@@ -345,8 +345,8 @@ describe("ssh tunnel scripts", () => {
345345 const spawner = ChildProcessSpawner.make(() =>
346346 Effect.succeed(makeDelayedSuccessfulProcess('{"remotePort":3774}\n', 75_000)),
347347 );
348 const spawnerLayer = Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner);
349 const processLayer = Layer.mergeAll(NodeServices.layer, spawnerLayer, TestClock.layer());
348 const layerSpawner = Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner);
349 const layerProcess = Layer.mergeAll(NodeServices.layer, layerSpawner, TestClock.layer());
350350
351351 return Effect.gen(function* () {
352352 const fiber = yield* Effect.forkChild(
......@@ -357,7 +357,7 @@ describe("ssh tunnel scripts", () => {
357357
358358 const result = yield* Fiber.join(fiber);
359359 assert.equal(result.remotePort, 3774);
360 }).pipe(Effect.provide(processLayer));
360 }).pipe(Effect.provide(layerProcess));
361361 });
362362
363363 it.effect("gives cold archive launches a larger budget than node-script launches", () => {
......@@ -370,8 +370,8 @@ describe("ssh tunnel scripts", () => {
370370 const spawner = ChildProcessSpawner.make(() =>
371371 Effect.succeed(makeDelayedSuccessfulProcess('{"remotePort":3774}\n', 800_000)),
372372 );
373 const spawnerLayer = Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner);
374 const processLayer = Layer.mergeAll(NodeServices.layer, spawnerLayer, TestClock.layer());
373 const layerSpawner = Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner);
374 const layerProcess = Layer.mergeAll(NodeServices.layer, layerSpawner, TestClock.layer());
375375
376376 return Effect.gen(function* () {
377377 const fiber = yield* Effect.forkChild(
......@@ -382,7 +382,7 @@ describe("ssh tunnel scripts", () => {
382382
383383 const result = yield* Fiber.join(fiber);
384384 assert.equal(result.remotePort, 3774);
385 }).pipe(Effect.provide(processLayer));
385 }).pipe(Effect.provide(layerProcess));
386386 });
387387
388388 it("allows the remote port picker to run without a state file path", () => {
......@@ -453,12 +453,12 @@ describe("ssh tunnel scripts", () => {
453453`),
454454 ),
455455 );
456 const spawnerLayer = Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner);
457 const processLayer = Layer.merge(NodeServices.layer, spawnerLayer);
456 const layerSpawner = Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner);
457 const layerProcess = Layer.merge(NodeServices.layer, layerSpawner);
458458 return Effect.gen(function* () {
459459 const result = yield* SshTunnel.issueRemotePairingToken(target, undefined, ARCHIVE);
460460 assert.equal(result.credential, "LCL4R2TPHDKQ");
461 }).pipe(Effect.provide(processLayer));
461 }).pipe(Effect.provide(layerProcess));
462462 });
463463
464464 it.effect("accepts pretty-printed pairing JSON after remote shell startup noise", () => {
......@@ -481,12 +481,12 @@ describe("ssh tunnel scripts", () => {
481481`),
482482 ),
483483 );
484 const spawnerLayer = Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner);
485 const processLayer = Layer.merge(NodeServices.layer, spawnerLayer);
484 const layerSpawner = Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner);
485 const layerProcess = Layer.merge(NodeServices.layer, layerSpawner);
486486 return Effect.gen(function* () {
487487 const result = yield* SshTunnel.issueRemotePairingToken(target, undefined, ARCHIVE);
488488 assert.equal(result.credential, "LCL4R2TPHDKQ");
489 }).pipe(Effect.provide(processLayer));
489 }).pipe(Effect.provide(layerProcess));
490490 });
491491
492492 it.effect.each(["successful stop", "failed stop"] as const)(
packages/tailscale/src/tailscale.test.ts+12-12
......@@ -101,20 +101,20 @@ function neverFinishingMockHandle() {
101101
102102// The executable name depends on the host platform (`tailscale.exe` on
103103// Windows), so pin it: these tests assert the posix spelling.
104function spawnerLayer(spawner: ChildProcessSpawner.ChildProcessSpawner["Service"]) {
104function layerSpawner(spawner: ChildProcessSpawner.ChildProcessSpawner["Service"]) {
105105 return Layer.merge(
106106 Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner),
107107 Layer.succeed(HostProcessPlatform, "linux"),
108108 );
109109}
110110
111function mockSpawnerLayer(
111function layerMockSpawner(
112112 handler: (
113113 command: string,
114114 args: ReadonlyArray<string>,
115115 ) => { stdout?: string; stderr?: string; code?: number },
116116) {
117 return spawnerLayer(
117 return layerSpawner(
118118 ChildProcessSpawner.make((command) => {
119119 const childProcess = command as unknown as {
120120 readonly command: string;
......@@ -178,7 +178,7 @@ describe("tailscale", () => {
178178 );
179179
180180 it.effect("reads tailscale status through the process spawner service", () => {
181 const layer = mockSpawnerLayer((command, args) => {
181 const layer = layerMockSpawner((command, args) => {
182182 assert.equal(command, "tailscale");
183183 assert.deepEqual(args, ["status", "--json"]);
184184 return {
......@@ -203,7 +203,7 @@ describe("tailscale", () => {
203203 method: "spawn",
204204 cause: systemCause,
205205 });
206 const layer = spawnerLayer(ChildProcessSpawner.make(() => Effect.fail(cause)));
206 const layer = layerSpawner(ChildProcessSpawner.make(() => Effect.fail(cause)));
207207
208208 return Effect.gen(function* () {
209209 const error = yield* readTailscaleStatus.pipe(Effect.flip, Effect.provide(layer));
......@@ -224,7 +224,7 @@ describe("tailscale", () => {
224224 // inside an `Effect.callback` registration, so that throw arrives as a
225225 // defect rather than a typed error - the shape reproduced here.
226226 const defect = Object.assign(new Error("spawn tailscale ENOTDIR"), { code: "ENOTDIR" });
227 const layer = spawnerLayer(
227 const layer = layerSpawner(
228228 ChildProcessSpawner.make(() =>
229229 Effect.callback<never, never>(() => {
230230 throw defect;
......@@ -257,7 +257,7 @@ describe("tailscale", () => {
257257 });
258258
259259 it.effect("keeps nonzero exit diagnostics structured", () => {
260 const layer = mockSpawnerLayer(() => ({
260 const layer = layerMockSpawner(() => ({
261261 code: 7,
262262 stderr: "not logged in tskey-auth-secret-token-value",
263263 }));
......@@ -282,7 +282,7 @@ describe("tailscale", () => {
282282 });
283283
284284 it.effect("classifies unrecognized stderr without quoting it", () => {
285 const layer = mockSpawnerLayer(() => ({
285 const layer = layerMockSpawner(() => ({
286286 code: 3,
287287 stderr: "something novel went wrong for node fluffy-badger tskey-auth-secret-token-value",
288288 }));
......@@ -302,7 +302,7 @@ describe("tailscale", () => {
302302 it.effect("times out tailscale status through TestClock", () => {
303303 const layer = Layer.merge(
304304 TestClock.layer(),
305 spawnerLayer(ChildProcessSpawner.make(() => Effect.succeed(neverFinishingMockHandle()))),
305 layerSpawner(ChildProcessSpawner.make(() => Effect.succeed(neverFinishingMockHandle()))),
306306 );
307307
308308 return Effect.gen(function* () {
......@@ -322,7 +322,7 @@ describe("tailscale", () => {
322322 });
323323
324324 it.effect("configures tailscale serve through the process spawner service", () => {
325 const layer = mockSpawnerLayer((command, args) => {
325 const layer = layerMockSpawner((command, args) => {
326326 assert.equal(command, "tailscale");
327327 assert.deepEqual(args, ["serve", "--bg", "--https=8443", "http://127.0.0.1:13773"]);
328328 return {};
......@@ -332,7 +332,7 @@ describe("tailscale", () => {
332332 });
333333
334334 it.effect("retains tailscale serve exit diagnostics", () => {
335 const layer = mockSpawnerLayer(() => ({
335 const layer = layerMockSpawner(() => ({
336336 code: 1,
337337 stderr: "serve permission denied tskey-auth-secret-token-value",
338338 }));
......@@ -364,7 +364,7 @@ describe("tailscale", () => {
364364 readonly command: string;
365365 readonly args: ReadonlyArray<string>;
366366 }[] = [];
367 const layer = mockSpawnerLayer((command, args) => {
367 const layer = layerMockSpawner((command, args) => {
368368 commands.push({ command, args });
369369 assert.equal(command, "tailscale");
370370 assert.deepEqual(args, ["serve", "--https=8443", "off"]);