From fb45896588afb89b3151e2432682f39e98bfb51f Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Fri, 27 Feb 2026 21:27:19 -0800 Subject: [PATCH] simplify layers --- .../OrchestrationEngineHarness.integration.ts | 3 +- .../Layers/CheckpointReactor.test.ts | 9 +- .../Layers/ProjectionPipeline.test.ts | 357 ++++++++++-------- .../Layers/ProjectionPipeline.ts | 64 +++- .../Layers/ProviderSessionDirectory.test.ts | 3 +- apps/server/src/serverLayers.ts | 6 +- apps/server/src/wsServer.test.ts | 16 +- apps/server/src/wsServer.ts | 3 +- 8 files changed, 275 insertions(+), 186 deletions(-) diff --git a/apps/server/integration/OrchestrationEngineHarness.integration.ts b/apps/server/integration/OrchestrationEngineHarness.integration.ts index 484a4fa0fb7d6d10d162c3aa9b43fb951b2fde04..643c6a2e9e04f08b237940f1106234b16b958d0e 100644 --- a/apps/server/integration/OrchestrationEngineHarness.integration.ts +++ b/apps/server/integration/OrchestrationEngineHarness.integration.ts @@ -215,14 +215,13 @@ export const makeOrchestrationIntegrationHarness = Effect.gen(function* () { Layer.provide(providerSessionDirectoryLayer), Layer.provide(Layer.succeed(ProviderAdapterRegistry, registry)), ); - const checkpointStoreLayer = CheckpointStoreLive.pipe(Layer.provide(NodeServices.layer)); const runtimeServicesLayer = Layer.mergeAll( orchestrationLayer, OrchestrationProjectionSnapshotQueryLive, ProjectionCheckpointRepositoryLive, ProjectionPendingApprovalRepositoryLive, - checkpointStoreLayer, + CheckpointStoreLive, providerLayer, ); const runtimeIngestionLayer = ProviderRuntimeIngestionLive.pipe( diff --git a/apps/server/src/orchestration/Layers/CheckpointReactor.test.ts b/apps/server/src/orchestration/Layers/CheckpointReactor.test.ts index 3da8faf731ee1fc0f8d4a2eb49d3b1481ae9516f..bbec8f62b08ce919cd2043d00f7f476491b77f5d 100644 --- a/apps/server/src/orchestration/Layers/CheckpointReactor.test.ts +++ b/apps/server/src/orchestration/Layers/CheckpointReactor.test.ts @@ -233,11 +233,12 @@ describe("CheckpointReactor", () => { Layer.provide(OrchestrationCommandReceiptRepositoryLive), Layer.provide(SqlitePersistenceMemory), ); - const checkpointStoreLayer = CheckpointStoreLive.pipe(Layer.provide(NodeServices.layer)); + const layer = CheckpointReactorLive.pipe( Layer.provideMerge(orchestrationLayer), Layer.provideMerge(Layer.succeed(ProviderService, provider.service)), - Layer.provideMerge(checkpointStoreLayer), + Layer.provideMerge(CheckpointStoreLive), + Layer.provideMerge(NodeServices.layer), ); runtime = ManagedRuntime.make(layer); @@ -437,7 +438,9 @@ describe("CheckpointReactor", () => { await Effect.runPromise(Effect.sleep("40 millis")); const midReadModel = await Effect.runPromise(harness.engine.getReadModel()); - const midThread = midReadModel.threads.find((entry) => entry.id === ThreadId.makeUnsafe("thread-1")); + const midThread = midReadModel.threads.find( + (entry) => entry.id === ThreadId.makeUnsafe("thread-1"), + ); expect(midThread?.checkpoints).toHaveLength(0); harness.provider.emit({ diff --git a/apps/server/src/orchestration/Layers/ProjectionPipeline.test.ts b/apps/server/src/orchestration/Layers/ProjectionPipeline.test.ts index 39b5929deb4276ca7bccce8e9b0948f212580028..9b477f64abeb2e7f2e3fb3dbe92c1fd154fc62a8 100644 --- a/apps/server/src/orchestration/Layers/ProjectionPipeline.test.ts +++ b/apps/server/src/orchestration/Layers/ProjectionPipeline.test.ts @@ -477,146 +477,150 @@ projectionLayer("OrchestrationProjectionPipeline", (it) => { }), ); - it.effect("overwrites attachment file bytes when a message updates the same attachment index", () => - Effect.gen(function* () { - const projectionPipeline = yield* OrchestrationProjectionPipeline; - const eventStore = yield* OrchestrationEventStore; - const stateDir = fs.mkdtempSync(path.join(os.tmpdir(), "t3-projection-attachments-overwrite-")); - const now = new Date().toISOString(); - const later = new Date(Date.now() + 1_000).toISOString(); + it.effect( + "overwrites attachment file bytes when a message updates the same attachment index", + () => + Effect.gen(function* () { + const projectionPipeline = yield* OrchestrationProjectionPipeline; + const eventStore = yield* OrchestrationEventStore; + const stateDir = fs.mkdtempSync( + path.join(os.tmpdir(), "t3-projection-attachments-overwrite-"), + ); + const now = new Date().toISOString(); + const later = new Date(Date.now() + 1_000).toISOString(); - const serverConfig = { - mode: "web", - port: 0, - host: undefined, - cwd: "/tmp/project-attachments", - keybindingsConfigPath: path.join(stateDir, "keybindings.json"), - stateDir, - staticDir: undefined, - devUrl: undefined, - noBrowser: true, - authToken: undefined, - autoBootstrapProjectFromCwd: false, - logWebSocketEvents: false, - } satisfies ServerConfigShape; + const serverConfig = { + mode: "web", + port: 0, + host: undefined, + cwd: "/tmp/project-attachments", + keybindingsConfigPath: path.join(stateDir, "keybindings.json"), + stateDir, + staticDir: undefined, + devUrl: undefined, + noBrowser: true, + authToken: undefined, + autoBootstrapProjectFromCwd: false, + logWebSocketEvents: false, + } satisfies ServerConfigShape; - yield* eventStore.append({ - type: "project.created", - eventId: EventId.makeUnsafe("evt-overwrite-1"), - aggregateKind: "project", - aggregateId: ProjectId.makeUnsafe("project-overwrite"), - occurredAt: now, - commandId: CommandId.makeUnsafe("cmd-overwrite-1"), - causationEventId: null, - correlationId: CommandId.makeUnsafe("cmd-overwrite-1"), - metadata: {}, - payload: { - projectId: ProjectId.makeUnsafe("project-overwrite"), - title: "Project Overwrite", - workspaceRoot: "/tmp/project-overwrite", - defaultModel: null, - scripts: [], - createdAt: now, - updatedAt: now, - }, - }); + yield* eventStore.append({ + type: "project.created", + eventId: EventId.makeUnsafe("evt-overwrite-1"), + aggregateKind: "project", + aggregateId: ProjectId.makeUnsafe("project-overwrite"), + occurredAt: now, + commandId: CommandId.makeUnsafe("cmd-overwrite-1"), + causationEventId: null, + correlationId: CommandId.makeUnsafe("cmd-overwrite-1"), + metadata: {}, + payload: { + projectId: ProjectId.makeUnsafe("project-overwrite"), + title: "Project Overwrite", + workspaceRoot: "/tmp/project-overwrite", + defaultModel: null, + scripts: [], + createdAt: now, + updatedAt: now, + }, + }); - yield* eventStore.append({ - type: "thread.created", - eventId: EventId.makeUnsafe("evt-overwrite-2"), - aggregateKind: "thread", - aggregateId: ThreadId.makeUnsafe("thread-overwrite"), - occurredAt: now, - commandId: CommandId.makeUnsafe("cmd-overwrite-2"), - causationEventId: null, - correlationId: CommandId.makeUnsafe("cmd-overwrite-2"), - metadata: {}, - payload: { - threadId: ThreadId.makeUnsafe("thread-overwrite"), - projectId: ProjectId.makeUnsafe("project-overwrite"), - title: "Thread Overwrite", - model: "gpt-5-codex", - branch: null, - worktreePath: null, - createdAt: now, - updatedAt: now, - }, - }); + yield* eventStore.append({ + type: "thread.created", + eventId: EventId.makeUnsafe("evt-overwrite-2"), + aggregateKind: "thread", + aggregateId: ThreadId.makeUnsafe("thread-overwrite"), + occurredAt: now, + commandId: CommandId.makeUnsafe("cmd-overwrite-2"), + causationEventId: null, + correlationId: CommandId.makeUnsafe("cmd-overwrite-2"), + metadata: {}, + payload: { + threadId: ThreadId.makeUnsafe("thread-overwrite"), + projectId: ProjectId.makeUnsafe("project-overwrite"), + title: "Thread Overwrite", + model: "gpt-5-codex", + branch: null, + worktreePath: null, + createdAt: now, + updatedAt: now, + }, + }); - yield* eventStore.append({ - type: "thread.message-sent", - eventId: EventId.makeUnsafe("evt-overwrite-3"), - aggregateKind: "thread", - aggregateId: ThreadId.makeUnsafe("thread-overwrite"), - occurredAt: now, - commandId: CommandId.makeUnsafe("cmd-overwrite-3"), - causationEventId: null, - correlationId: CommandId.makeUnsafe("cmd-overwrite-3"), - metadata: {}, - payload: { - threadId: ThreadId.makeUnsafe("thread-overwrite"), - messageId: MessageId.makeUnsafe("message-overwrite"), - role: "user", - text: "first image", - attachments: [ - { - type: "image", - name: "file.png", - mimeType: "image/png", - sizeBytes: 5, - dataUrl: "data:image/png;base64,SGVsbG8=", - }, - ], - turnId: null, - streaming: false, - createdAt: now, - updatedAt: now, - }, - }); + yield* eventStore.append({ + type: "thread.message-sent", + eventId: EventId.makeUnsafe("evt-overwrite-3"), + aggregateKind: "thread", + aggregateId: ThreadId.makeUnsafe("thread-overwrite"), + occurredAt: now, + commandId: CommandId.makeUnsafe("cmd-overwrite-3"), + causationEventId: null, + correlationId: CommandId.makeUnsafe("cmd-overwrite-3"), + metadata: {}, + payload: { + threadId: ThreadId.makeUnsafe("thread-overwrite"), + messageId: MessageId.makeUnsafe("message-overwrite"), + role: "user", + text: "first image", + attachments: [ + { + type: "image", + name: "file.png", + mimeType: "image/png", + sizeBytes: 5, + dataUrl: "data:image/png;base64,SGVsbG8=", + }, + ], + turnId: null, + streaming: false, + createdAt: now, + updatedAt: now, + }, + }); - yield* eventStore.append({ - type: "thread.message-sent", - eventId: EventId.makeUnsafe("evt-overwrite-4"), - aggregateKind: "thread", - aggregateId: ThreadId.makeUnsafe("thread-overwrite"), - occurredAt: later, - commandId: CommandId.makeUnsafe("cmd-overwrite-4"), - causationEventId: null, - correlationId: CommandId.makeUnsafe("cmd-overwrite-4"), - metadata: {}, - payload: { - threadId: ThreadId.makeUnsafe("thread-overwrite"), - messageId: MessageId.makeUnsafe("message-overwrite"), - role: "user", - text: "", - attachments: [ - { - type: "image", - name: "file.png", - mimeType: "image/png", - sizeBytes: 5, - dataUrl: "data:image/png;base64,V29ybGQ=", - }, - ], - turnId: null, - streaming: false, - createdAt: now, - updatedAt: later, - }, - }); + yield* eventStore.append({ + type: "thread.message-sent", + eventId: EventId.makeUnsafe("evt-overwrite-4"), + aggregateKind: "thread", + aggregateId: ThreadId.makeUnsafe("thread-overwrite"), + occurredAt: later, + commandId: CommandId.makeUnsafe("cmd-overwrite-4"), + causationEventId: null, + correlationId: CommandId.makeUnsafe("cmd-overwrite-4"), + metadata: {}, + payload: { + threadId: ThreadId.makeUnsafe("thread-overwrite"), + messageId: MessageId.makeUnsafe("message-overwrite"), + role: "user", + text: "", + attachments: [ + { + type: "image", + name: "file.png", + mimeType: "image/png", + sizeBytes: 5, + dataUrl: "data:image/png;base64,V29ybGQ=", + }, + ], + turnId: null, + streaming: false, + createdAt: now, + updatedAt: later, + }, + }); - yield* projectionPipeline.bootstrap.pipe(Effect.provideService(ServerConfig, serverConfig)); + yield* projectionPipeline.bootstrap.pipe(Effect.provideService(ServerConfig, serverConfig)); - const attachmentPath = path.join( - stateDir, - "attachments", - "thread-overwrite", - "message-overwrite-0.png", - ); - assert.equal(fs.existsSync(attachmentPath), true); - assert.deepEqual(fs.readFileSync(attachmentPath), Buffer.from("V29ybGQ=", "base64")); - fs.rmSync(stateDir, { recursive: true, force: true }); - }), + const attachmentPath = path.join( + stateDir, + "attachments", + "thread-overwrite", + "message-overwrite-0.png", + ); + assert.equal(fs.existsSync(attachmentPath), true); + assert.deepEqual(fs.readFileSync(attachmentPath), Buffer.from("V29ybGQ=", "base64")); + fs.rmSync(stateDir, { recursive: true, force: true }); + }), ); it.effect("does not persist attachment files when projector transaction rolls back", () => @@ -624,7 +628,9 @@ projectionLayer("OrchestrationProjectionPipeline", (it) => { const projectionPipeline = yield* OrchestrationProjectionPipeline; const eventStore = yield* OrchestrationEventStore; const sql = yield* SqlClient.SqlClient; - const stateDir = fs.mkdtempSync(path.join(os.tmpdir(), "t3-projection-attachments-rollback-")); + const stateDir = fs.mkdtempSync( + path.join(os.tmpdir(), "t3-projection-attachments-rollback-"), + ); const now = new Date().toISOString(); const serverConfig = { @@ -643,12 +649,10 @@ projectionLayer("OrchestrationProjectionPipeline", (it) => { } satisfies ServerConfigShape; const appendAndProject = (event: Parameters[0]) => - eventStore - .append(event) - .pipe( - Effect.flatMap((savedEvent) => projectionPipeline.projectEvent(savedEvent)), - Effect.provideService(ServerConfig, serverConfig), - ); + eventStore.append(event).pipe( + Effect.flatMap((savedEvent) => projectionPipeline.projectEvent(savedEvent)), + Effect.provideService(ServerConfig, serverConfig), + ); yield* appendAndProject({ type: "project.created", @@ -780,12 +784,10 @@ projectionLayer("OrchestrationProjectionPipeline", (it) => { } satisfies ServerConfigShape; const appendAndProject = (event: Parameters[0]) => - eventStore - .append(event) - .pipe( - Effect.flatMap((savedEvent) => projectionPipeline.projectEvent(savedEvent)), - Effect.provideService(ServerConfig, serverConfig), - ); + eventStore.append(event).pipe( + Effect.flatMap((savedEvent) => projectionPipeline.projectEvent(savedEvent)), + Effect.provideService(ServerConfig, serverConfig), + ); yield* appendAndProject({ type: "project.created", @@ -996,12 +998,10 @@ projectionLayer("OrchestrationProjectionPipeline", (it) => { } satisfies ServerConfigShape; const appendAndProject = (event: Parameters[0]) => - eventStore - .append(event) - .pipe( - Effect.flatMap((savedEvent) => projectionPipeline.projectEvent(savedEvent)), - Effect.provideService(ServerConfig, serverConfig), - ); + eventStore.append(event).pipe( + Effect.flatMap((savedEvent) => projectionPipeline.projectEvent(savedEvent)), + Effect.provideService(ServerConfig, serverConfig), + ); yield* appendAndProject({ type: "project.created", @@ -1101,6 +1101,59 @@ projectionLayer("OrchestrationProjectionPipeline", (it) => { }), ); + it.effect("ignores unsafe thread ids for attachment cleanup paths", () => + Effect.gen(function* () { + const projectionPipeline = yield* OrchestrationProjectionPipeline; + const eventStore = yield* OrchestrationEventStore; + const stateDir = fs.mkdtempSync(path.join(os.tmpdir(), "t3-projection-attachments-unsafe-")); + const now = new Date().toISOString(); + const attachmentsRootDir = path.join(stateDir, "attachments"); + const attachmentsSentinelPath = path.join(attachmentsRootDir, "sentinel.txt"); + const stateDirSentinelPath = path.join(stateDir, "state-sentinel.txt"); + fs.mkdirSync(attachmentsRootDir, { recursive: true }); + fs.writeFileSync(attachmentsSentinelPath, "keep-attachments-root", "utf8"); + fs.writeFileSync(stateDirSentinelPath, "keep-state-dir", "utf8"); + + const serverConfig = { + mode: "web", + port: 0, + host: undefined, + cwd: "/tmp/project-attachments", + keybindingsConfigPath: path.join(stateDir, "keybindings.json"), + stateDir, + staticDir: undefined, + devUrl: undefined, + noBrowser: true, + authToken: undefined, + autoBootstrapProjectFromCwd: false, + logWebSocketEvents: false, + } satisfies ServerConfigShape; + + yield* eventStore.append({ + type: "thread.deleted", + eventId: EventId.makeUnsafe("evt-unsafe-thread-delete"), + aggregateKind: "thread", + aggregateId: ThreadId.makeUnsafe(".."), + occurredAt: now, + commandId: CommandId.makeUnsafe("cmd-unsafe-thread-delete"), + causationEventId: null, + correlationId: CorrelationId.makeUnsafe("cmd-unsafe-thread-delete"), + metadata: {}, + payload: { + threadId: ThreadId.makeUnsafe(".."), + deletedAt: now, + }, + }); + + yield* projectionPipeline.bootstrap.pipe(Effect.provideService(ServerConfig, serverConfig)); + + assert.equal(fs.existsSync(attachmentsRootDir), true); + assert.equal(fs.existsSync(attachmentsSentinelPath), true); + assert.equal(fs.existsSync(stateDirSentinelPath), true); + fs.rmSync(stateDir, { recursive: true, force: true }); + }), + ); + it.effect("resumes from projector last_applied_sequence without replaying older events", () => Effect.gen(function* () { const projectionPipeline = yield* OrchestrationProjectionPipeline; diff --git a/apps/server/src/orchestration/Layers/ProjectionPipeline.ts b/apps/server/src/orchestration/Layers/ProjectionPipeline.ts index 2ded57a17db65fd2699ad03b0b636a67f31c9b90..c518744d138d807bcec461883abae83924590357 100644 --- a/apps/server/src/orchestration/Layers/ProjectionPipeline.ts +++ b/apps/server/src/orchestration/Layers/ProjectionPipeline.ts @@ -77,6 +77,21 @@ interface AttachmentSideEffects { readonly prunedThreadRelativePaths: Map>; } +function toSafeThreadAttachmentSegment(threadId: string): string | null { + const segment = encodeURIComponent(threadId); + if ( + segment.length === 0 || + segment === "." || + segment === ".." || + segment.includes("/") || + segment.includes("\\") || + segment.includes("\0") + ) { + return null; + } + return segment; +} + function parseBase64DataUrl( dataUrl: string, ): { readonly mimeType: string; readonly base64: string } | null { @@ -114,7 +129,14 @@ const materializeAttachmentsForProjection = Effect.fn(function* (input: { const path = yield* Path.Path; const attachmentsRootDir = path.join(serverConfig.value.stateDir, "attachments"); - const threadSegment = encodeURIComponent(input.threadId); + const threadSegment = toSafeThreadAttachmentSegment(input.threadId); + if (!threadSegment) { + yield* Effect.logWarning("skipping attachment materialization for unsafe thread id", { + threadId: input.threadId, + messageId: input.messageId, + }); + return input.attachments; + } const messageSegment = encodeURIComponent(input.messageId); return yield* Effect.forEach( @@ -282,7 +304,11 @@ function collectThreadAttachmentRelativePaths( threadId: string, messages: ReadonlyArray, ): Set { - const threadPrefix = `${encodeURIComponent(threadId)}/`; + const threadSegment = toSafeThreadAttachmentSegment(threadId); + if (!threadSegment) { + return new Set(); + } + const threadPrefix = `${threadSegment}/`; const relativePaths = new Set(); for (const message of messages) { for (const attachment of message.attachments ?? []) { @@ -317,10 +343,18 @@ const runAttachmentSideEffects = Effect.fn(function* (input: { yield* Effect.forEach( input.sideEffects.deletedThreadIds, - (threadId) => { - const threadDir = input.path.join(attachmentsRootDir, encodeURIComponent(threadId)); - return input.fileSystem.remove(threadDir, { recursive: true, force: true }); - }, + (threadId) => + Effect.gen(function* () { + const threadSegment = toSafeThreadAttachmentSegment(threadId); + if (!threadSegment) { + yield* Effect.logWarning("skipping attachment cleanup for unsafe thread id", { + threadId, + }); + return; + } + const threadDir = input.path.join(attachmentsRootDir, threadSegment); + yield* input.fileSystem.remove(threadDir, { recursive: true, force: true }); + }), { concurrency: 1 }, ); @@ -330,8 +364,13 @@ const runAttachmentSideEffects = Effect.fn(function* (input: { if (input.sideEffects.deletedThreadIds.has(threadId)) { return Effect.void; } - const threadDir = input.path.join(attachmentsRootDir, encodeURIComponent(threadId)); return Effect.gen(function* () { + const threadSegment = toSafeThreadAttachmentSegment(threadId); + if (!threadSegment) { + yield* Effect.logWarning("skipping attachment prune for unsafe thread id", { threadId }); + return; + } + const threadDir = input.path.join(attachmentsRootDir, threadSegment); const entries = yield* input.fileSystem .readDirectory(threadDir, { recursive: true }) .pipe(Effect.catch(() => Effect.succeed([] as Array))); @@ -601,16 +640,7 @@ const makeOrchestrationProjectionPipeline = Effect.gen(function* () { stageFileWrite: (pendingWrite) => { attachmentSideEffects.writes.push(pendingWrite); }, - }).pipe( - Effect.provideService(Path.Path, path), - Effect.catch((cause) => - Effect.logWarning("failed to persist message attachments", { - threadId: event.payload.threadId, - messageId: event.payload.messageId, - cause, - }).pipe(Effect.as(event.payload.attachments)), - ), - ) + }).pipe(Effect.provideService(Path.Path, path)) : existingMessage?.attachments; yield* projectionThreadMessageRepository.upsert({ messageId: event.payload.messageId, diff --git a/apps/server/src/provider/Layers/ProviderSessionDirectory.test.ts b/apps/server/src/provider/Layers/ProviderSessionDirectory.test.ts index 0b223c2c7813c4c0668d57f523f0aa3b8853d0fc..471b38a5484730c523fc6521dd7a26b275d19b1c 100644 --- a/apps/server/src/provider/Layers/ProviderSessionDirectory.test.ts +++ b/apps/server/src/provider/Layers/ProviderSessionDirectory.test.ts @@ -26,6 +26,7 @@ function makeDirectoryLayer(persistenceLayer: Layer.Layer { async function createTestServer( options: { - persistenceLayer?: Layer.Layer; + persistenceLayer?: Layer.Layer< + SqlClient.SqlClient, + SqlError.SqlError | MigrationError | PlatformError.PlatformError + >; cwd?: string; autoBootstrapProjectFromCwd?: boolean; logWebSocketEvents?: boolean; @@ -614,9 +618,9 @@ describe("WebSocket Server", () => { it("includes bootstrap ids in welcome when cwd project and thread already exist", async () => { const stateDir = makeTempDir("t3code-state-bootstrap-existing-"); - const persistenceLayer = makeSqlitePersistenceLive( - path.join(stateDir, "state.sqlite"), - ).pipe(Layer.provide(NodeServices.layer)) as unknown as Layer.Layer; + const persistenceLayer = makeSqlitePersistenceLive(path.join(stateDir, "state.sqlite")).pipe( + Layer.provide(NodeServices.layer), + ); const cwd = "/test/bootstrap-existing"; server = await createTestServer({ diff --git a/apps/server/src/wsServer.ts b/apps/server/src/wsServer.ts index d382ab9d3e90bacc5c561f0d90f0c78164797802..5f4fb9d616a3d53c8ef6911c5a405167ec9a169b 100644 --- a/apps/server/src/wsServer.ts +++ b/apps/server/src/wsServer.ts @@ -23,7 +23,6 @@ import { WsPush, WsResponse, } from "@t3tools/contracts"; -import * as NodeServices from "@effect/platform-node/NodeServices"; import * as NodeHttpServer from "@effect/platform-node/NodeHttpServer"; import { Cause, @@ -787,6 +786,6 @@ export const createServer = Effect.fn(function* (): Effect.fn.Return< }); export const ServerLive = Layer.succeed(Server, { - start: createServer().pipe(Effect.provide(NodeServices.layer)), + start: createServer(), stopSignal: Effect.never, } satisfies ServerShape); -- 2.54.0