Просмотр исходного кода

Preserve workspace context in session HTTP routes (#25136)

Kit Langton 4 месяцев назад
Родитель
Сommit
feeebbe7d4

+ 83 - 247
packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts

@@ -1,5 +1,5 @@
 import * as InstanceState from "@/effect/instance-state"
-import { AppRuntime } from "@/effect/app-runtime"
+import { EffectBridge } from "@/effect/bridge"
 import { Agent } from "@/agent/agent"
 import { Bus } from "@/bus"
 import { Command } from "@/command"
@@ -53,6 +53,13 @@ const mapNotFound = <A, E, R>(self: Effect.Effect<A, E, R>) =>
 export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session", (handlers) =>
   Effect.gen(function* () {
     const session = yield* Session.Service
+    const shareSvc = yield* SessionShare.Service
+    const promptSvc = yield* SessionPrompt.Service
+    const revertSvc = yield* SessionRevert.Service
+    const compactSvc = yield* SessionCompaction.Service
+    const runState = yield* SessionRunState.Service
+    const agentSvc = yield* Agent.Service
+    const permissionSvc = yield* Permission.Service
     const statusSvc = yield* SessionStatus.Service
     const todoSvc = yield* Todo.Service
     const summary = yield* SessionSummary.Service
@@ -148,14 +155,7 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session",
     })
 
     const create = Effect.fn("SessionHttpApi.create")(function* (ctx: { payload?: Session.CreateInput }) {
-      const instance = yield* InstanceState.context
-      return yield* Effect.promise(() =>
-        Instance.restore(instance, () =>
-          AppRuntime.runPromise(
-            SessionShare.Service.use((svc) => svc.create(ctx.payload)).pipe(Effect.provide(SessionShare.defaultLayer)),
-          ),
-        ),
-      )
+      return yield* shareSvc.create(ctx.payload)
     })
 
     const createRaw = Effect.fn("SessionHttpApi.createRaw")(function* (ctx: {
@@ -175,14 +175,7 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session",
     })
 
     const remove = Effect.fn("SessionHttpApi.remove")(function* (ctx: { params: { sessionID: SessionID } }) {
-      const instance = yield* InstanceState.context
-      yield* Effect.promise(() =>
-        Instance.restore(instance, () =>
-          AppRuntime.runPromise(
-            Session.Service.use((svc) => svc.remove(ctx.params.sessionID)).pipe(Effect.provide(Session.defaultLayer)),
-          ),
-        ),
-      )
+      yield* session.remove(ctx.params.sessionID)
       return true
     })
 
@@ -190,60 +183,31 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session",
       params: { sessionID: SessionID }
       payload: typeof UpdatePayload.Type
     }) {
-      const instance = yield* InstanceState.context
-      return yield* Effect.promise(() =>
-        Instance.restore(instance, () =>
-          AppRuntime.runPromise(
-            Session.Service.use((svc) =>
-              Effect.gen(function* () {
-                const current = yield* svc.get(ctx.params.sessionID)
-                if (ctx.payload.title !== undefined) {
-                  yield* svc.setTitle({ sessionID: ctx.params.sessionID, title: ctx.payload.title })
-                }
-                if (ctx.payload.permission !== undefined) {
-                  yield* svc.setPermission({
-                    sessionID: ctx.params.sessionID,
-                    permission: Permission.merge(current.permission ?? [], ctx.payload.permission),
-                  })
-                }
-                if (ctx.payload.time?.archived !== undefined) {
-                  yield* svc.setArchived({ sessionID: ctx.params.sessionID, time: ctx.payload.time.archived })
-                }
-                return yield* svc.get(ctx.params.sessionID)
-              }),
-            ).pipe(Effect.provide(Session.defaultLayer)),
-          ),
-        ),
-      )
+      const current = yield* session.get(ctx.params.sessionID)
+      if (ctx.payload.title !== undefined) {
+        yield* session.setTitle({ sessionID: ctx.params.sessionID, title: ctx.payload.title })
+      }
+      if (ctx.payload.permission !== undefined) {
+        yield* session.setPermission({
+          sessionID: ctx.params.sessionID,
+          permission: Permission.merge(current.permission ?? [], ctx.payload.permission),
+        })
+      }
+      if (ctx.payload.time?.archived !== undefined) {
+        yield* session.setArchived({ sessionID: ctx.params.sessionID, time: ctx.payload.time.archived })
+      }
+      return yield* session.get(ctx.params.sessionID)
     })
 
     const fork = Effect.fn("SessionHttpApi.fork")(function* (ctx: {
       params: { sessionID: SessionID }
       payload: typeof ForkPayload.Type
     }) {
-      const instance = yield* InstanceState.context
-      return yield* Effect.promise(() =>
-        Instance.restore(instance, () =>
-          AppRuntime.runPromise(
-            Session.Service.use((svc) =>
-              svc.fork({ sessionID: ctx.params.sessionID, messageID: ctx.payload.messageID }),
-            ).pipe(Effect.provide(Session.defaultLayer)),
-          ),
-        ),
-      )
+      return yield* session.fork({ sessionID: ctx.params.sessionID, messageID: ctx.payload.messageID })
     })
 
     const abort = Effect.fn("SessionHttpApi.abort")(function* (ctx: { params: { sessionID: SessionID } }) {
-      const instance = yield* InstanceState.context
-      yield* Effect.promise(() =>
-        Instance.restore(instance, () =>
-          AppRuntime.runPromise(
-            SessionPrompt.Service.use((svc) => svc.cancel(ctx.params.sessionID)).pipe(
-              Effect.provide(SessionPrompt.defaultLayer),
-            ),
-          ),
-        ),
-      )
+      yield* promptSvc.cancel(ctx.params.sessionID)
       return true
     })
 
@@ -251,98 +215,45 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session",
       params: { sessionID: SessionID }
       payload: typeof InitPayload.Type
     }) {
-      const instance = yield* InstanceState.context
-      yield* Effect.promise(() =>
-        Instance.restore(instance, () =>
-          AppRuntime.runPromise(
-            SessionPrompt.Service.use((svc) =>
-              svc.command({
-                sessionID: ctx.params.sessionID,
-                messageID: ctx.payload.messageID,
-                model: `${ctx.payload.providerID}/${ctx.payload.modelID}`,
-                command: Command.Default.INIT,
-                arguments: "",
-              }),
-            ).pipe(Effect.provide(SessionPrompt.defaultLayer)),
-          ),
-        ),
-      )
+      yield* promptSvc.command({
+        sessionID: ctx.params.sessionID,
+        messageID: ctx.payload.messageID,
+        model: `${ctx.payload.providerID}/${ctx.payload.modelID}`,
+        command: Command.Default.INIT,
+        arguments: "",
+      })
       return true
     })
 
     const share = Effect.fn("SessionHttpApi.share")(function* (ctx: { params: { sessionID: SessionID } }) {
-      const instance = yield* InstanceState.context
-      return yield* Effect.promise(() =>
-        Instance.restore(instance, () =>
-          AppRuntime.runPromise(
-            Effect.gen(function* () {
-              const share = yield* SessionShare.Service
-              const session = yield* Session.Service
-              yield* share.share(ctx.params.sessionID)
-              return yield* session.get(ctx.params.sessionID)
-            }).pipe(Effect.provide(SessionShare.defaultLayer)),
-          ),
-        ),
-      )
+      yield* shareSvc.share(ctx.params.sessionID).pipe(Effect.mapError(() => new HttpApiError.BadRequest({})))
+      return yield* session.get(ctx.params.sessionID)
     })
 
     const unshare = Effect.fn("SessionHttpApi.unshare")(function* (ctx: { params: { sessionID: SessionID } }) {
-      const instance = yield* InstanceState.context
-      return yield* Effect.promise(() =>
-        Instance.restore(instance, () =>
-          AppRuntime.runPromise(
-            Effect.gen(function* () {
-              const share = yield* SessionShare.Service
-              const session = yield* Session.Service
-              yield* share.unshare(ctx.params.sessionID)
-              return yield* session.get(ctx.params.sessionID)
-            }).pipe(Effect.provide(SessionShare.defaultLayer)),
-          ),
-        ),
-      )
+      yield* shareSvc.unshare(ctx.params.sessionID).pipe(Effect.mapError(() => new HttpApiError.BadRequest({})))
+      return yield* session.get(ctx.params.sessionID)
     })
 
     const summarize = Effect.fn("SessionHttpApi.summarize")(function* (ctx: {
       params: { sessionID: SessionID }
       payload: typeof SummarizePayload.Type
     }) {
-      const instance = yield* InstanceState.context
-      yield* Effect.promise(() =>
-        Instance.restore(instance, () =>
-          AppRuntime.runPromise(
-            Effect.gen(function* () {
-              const session = yield* Session.Service
-              const revert = yield* SessionRevert.Service
-              const compact = yield* SessionCompaction.Service
-              const prompt = yield* SessionPrompt.Service
-              const agent = yield* Agent.Service
-
-              yield* revert.cleanup(yield* session.get(ctx.params.sessionID))
-              const messages = yield* session.messages({ sessionID: ctx.params.sessionID })
-              const defaultAgent = yield* agent.defaultAgent()
-              const currentAgent =
-                messages.findLast((message) => message.info.role === "user")?.info.agent ?? defaultAgent
-
-              yield* compact.create({
-                sessionID: ctx.params.sessionID,
-                agent: currentAgent,
-                model: {
-                  providerID: ctx.payload.providerID,
-                  modelID: ctx.payload.modelID,
-                },
-                auto: ctx.payload.auto ?? false,
-              })
-              yield* prompt.loop({ sessionID: ctx.params.sessionID })
-            }).pipe(
-              Effect.provide(SessionRevert.defaultLayer),
-              Effect.provide(SessionCompaction.defaultLayer),
-              Effect.provide(SessionPrompt.defaultLayer),
-              Effect.provide(Agent.defaultLayer),
-              Effect.provide(Session.defaultLayer),
-            ),
-          ),
-        ),
-      )
+      yield* revertSvc.cleanup(yield* session.get(ctx.params.sessionID))
+      const messages = yield* session.messages({ sessionID: ctx.params.sessionID })
+      const defaultAgent = yield* agentSvc.defaultAgent()
+      const currentAgent = messages.findLast((message) => message.info.role === "user")?.info.agent ?? defaultAgent
+
+      yield* compactSvc.create({
+        sessionID: ctx.params.sessionID,
+        agent: currentAgent,
+        model: {
+          providerID: ctx.payload.providerID,
+          modelID: ctx.payload.modelID,
+        },
+        auto: ctx.payload.auto ?? false,
+      })
+      yield* promptSvc.loop({ sessionID: ctx.params.sessionID })
       return true
     })
 
@@ -350,19 +261,15 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session",
       params: { sessionID: SessionID }
       payload: typeof PromptPayload.Type
     }) {
-      const instance = yield* InstanceState.context
+      const bridge = yield* EffectBridge.make()
       return HttpServerResponse.stream(
         Stream.fromEffect(
           Effect.promise(() =>
-            Instance.restore(instance, () =>
-              AppRuntime.runPromise(
-                SessionPrompt.Service.use((svc) =>
-                  svc.prompt({
-                    ...ctx.payload,
-                    sessionID: ctx.params.sessionID,
-                  } as unknown as SessionPrompt.PromptInput),
-                ).pipe(Effect.provide(SessionPrompt.defaultLayer)),
-              ),
+            bridge.promise(
+              promptSvc.prompt({
+                ...ctx.payload,
+                sessionID: ctx.params.sessionID,
+              } as unknown as SessionPrompt.PromptInput),
             ),
           ),
         ).pipe(
@@ -377,23 +284,23 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session",
       params: { sessionID: SessionID }
       payload: typeof PromptPayload.Type
     }) {
-      const instance = yield* InstanceState.context
+      const bridge = yield* EffectBridge.make()
       yield* Effect.sync(() => {
-        Instance.restore(instance, () => {
-          void AppRuntime.runPromise(
-            SessionPrompt.Service.use((svc) =>
-              svc.prompt({ ...ctx.payload, sessionID: ctx.params.sessionID } as unknown as SessionPrompt.PromptInput),
-            ).pipe(Effect.provide(SessionPrompt.defaultLayer)),
-          ).catch((error) => {
-            log.error("prompt_async failed", { sessionID: ctx.params.sessionID, error })
-            void Bus.publish(Session.Event.Error, {
-              sessionID: ctx.params.sessionID,
-              error: new NamedError.Unknown({
-                message: error instanceof Error ? error.message : String(error),
-              }).toObject(),
-            })
-          })
-        })
+        bridge.fork(
+          promptSvc
+            .prompt({ ...ctx.payload, sessionID: ctx.params.sessionID } as unknown as SessionPrompt.PromptInput)
+            .pipe(
+              Effect.catchCause((error) =>
+                Effect.sync(() => {
+                  log.error("prompt_async failed", { sessionID: ctx.params.sessionID, error })
+                  void Bus.publish(Session.Event.Error, {
+                    sessionID: ctx.params.sessionID,
+                    error: new NamedError.Unknown({ message: String(error) }).toObject(),
+                  })
+                }),
+              ),
+            ),
+        )
       })
       return HttpApiSchema.NoContent.make()
     })
@@ -402,111 +309,47 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session",
       params: { sessionID: SessionID }
       payload: typeof CommandPayload.Type
     }) {
-      const instance = yield* InstanceState.context
-      return yield* Effect.promise(() =>
-        Instance.restore(instance, () =>
-          AppRuntime.runPromise(
-            SessionPrompt.Service.use((svc) =>
-              svc.command({ ...ctx.payload, sessionID: ctx.params.sessionID } as SessionPrompt.CommandInput),
-            ).pipe(Effect.provide(SessionPrompt.defaultLayer)),
-          ),
-        ),
-      )
+      return yield* promptSvc.command({ ...ctx.payload, sessionID: ctx.params.sessionID } as SessionPrompt.CommandInput)
     })
 
     const shell = Effect.fn("SessionHttpApi.shell")(function* (ctx: {
       params: { sessionID: SessionID }
       payload: typeof ShellPayload.Type
     }) {
-      const instance = yield* InstanceState.context
-      return yield* Effect.promise(() =>
-        Instance.restore(instance, () =>
-          AppRuntime.runPromise(
-            SessionPrompt.Service.use((svc) =>
-              svc.shell({ ...ctx.payload, sessionID: ctx.params.sessionID } as SessionPrompt.ShellInput),
-            ).pipe(Effect.provide(SessionPrompt.defaultLayer)),
-          ),
-        ),
-      )
+      return yield* promptSvc.shell({ ...ctx.payload, sessionID: ctx.params.sessionID } as SessionPrompt.ShellInput)
     })
 
     const revert = Effect.fn("SessionHttpApi.revert")(function* (ctx: {
       params: { sessionID: SessionID }
       payload: typeof RevertPayload.Type
     }) {
-      const instance = yield* InstanceState.context
-      log.info("revert", ctx.payload)
-      return yield* Effect.promise(() =>
-        Instance.restore(instance, () =>
-          AppRuntime.runPromise(
-            SessionRevert.Service.use((svc) => svc.revert({ sessionID: ctx.params.sessionID, ...ctx.payload })).pipe(
-              Effect.provide(SessionRevert.defaultLayer),
-            ),
-          ),
-        ),
-      )
+      return yield* revertSvc.revert({ sessionID: ctx.params.sessionID, ...ctx.payload })
     })
 
     const unrevert = Effect.fn("SessionHttpApi.unrevert")(function* (ctx: { params: { sessionID: SessionID } }) {
-      const instance = yield* InstanceState.context
-      return yield* Effect.promise(() =>
-        Instance.restore(instance, () =>
-          AppRuntime.runPromise(
-            SessionRevert.Service.use((svc) => svc.unrevert({ sessionID: ctx.params.sessionID })).pipe(
-              Effect.provide(SessionRevert.defaultLayer),
-            ),
-          ),
-        ),
-      )
+      return yield* revertSvc.unrevert({ sessionID: ctx.params.sessionID })
     })
 
     const permissionRespond = Effect.fn("SessionHttpApi.permissionRespond")(function* (ctx: {
       params: { permissionID: PermissionID }
       payload: typeof PermissionResponsePayload.Type
     }) {
-      const instance = yield* InstanceState.context
-      yield* Effect.promise(() =>
-        Instance.restore(instance, () =>
-          AppRuntime.runPromise(
-            Permission.Service.use((svc) =>
-              svc.reply({ requestID: ctx.params.permissionID, reply: ctx.payload.response }),
-            ).pipe(Effect.provide(Permission.defaultLayer)),
-          ),
-        ),
-      )
+      yield* permissionSvc.reply({ requestID: ctx.params.permissionID, reply: ctx.payload.response })
       return true
     })
 
     const deleteMessage = Effect.fn("SessionHttpApi.deleteMessage")(function* (ctx: {
       params: { sessionID: SessionID; messageID: MessageID }
     }) {
-      const instance = yield* InstanceState.context
-      yield* Effect.promise(() =>
-        Instance.restore(instance, () =>
-          AppRuntime.runPromise(
-            Effect.gen(function* () {
-              const state = yield* SessionRunState.Service
-              const session = yield* Session.Service
-              yield* state.assertNotBusy(ctx.params.sessionID)
-              yield* session.removeMessage(ctx.params)
-            }).pipe(Effect.provide(SessionRunState.defaultLayer), Effect.provide(Session.defaultLayer)),
-          ),
-        ),
-      )
+      yield* runState.assertNotBusy(ctx.params.sessionID)
+      yield* session.removeMessage(ctx.params)
       return true
     })
 
     const deletePart = Effect.fn("SessionHttpApi.deletePart")(function* (ctx: {
       params: { sessionID: SessionID; messageID: MessageID; partID: PartID }
     }) {
-      const instance = yield* InstanceState.context
-      yield* Effect.promise(() =>
-        Instance.restore(instance, () =>
-          AppRuntime.runPromise(
-            Session.Service.use((svc) => svc.removePart(ctx.params)).pipe(Effect.provide(Session.defaultLayer)),
-          ),
-        ),
-      )
+      yield* session.removePart(ctx.params)
       return true
     })
 
@@ -524,14 +367,7 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session",
           `Part mismatch: body.id='${payload.id}' vs partID='${ctx.params.partID}', body.messageID='${payload.messageID}' vs messageID='${ctx.params.messageID}', body.sessionID='${payload.sessionID}' vs sessionID='${ctx.params.sessionID}'`,
         )
       }
-      const instance = yield* InstanceState.context
-      return yield* Effect.promise(() =>
-        Instance.restore(instance, () =>
-          AppRuntime.runPromise(
-            Session.Service.use((svc) => svc.updatePart(payload)).pipe(Effect.provide(Session.defaultLayer)),
-          ),
-        ),
-      )
+      return yield* session.updatePart(payload)
     })
 
     return handlers

+ 8 - 0
packages/opencode/src/server/routes/instance/httpapi/server.ts

@@ -22,10 +22,14 @@ import { Provider } from "@/provider/provider"
 import { Pty } from "@/pty"
 import { Question } from "@/question"
 import { Session } from "@/session/session"
+import { SessionCompaction } from "@/session/compaction"
+import { SessionPrompt } from "@/session/prompt"
+import { SessionRevert } from "@/session/revert"
 import { SessionRunState } from "@/session/run-state"
 import { SessionStatus } from "@/session/status"
 import { SessionSummary } from "@/session/summary"
 import { Todo } from "@/session/todo"
+import { SessionShare } from "@/share/session"
 import { Skill } from "@/skill"
 import { ToolRegistry } from "@/tool/registry"
 import { lazy } from "@/util/lazy"
@@ -134,6 +138,10 @@ export const routes = Layer.mergeAll(rootApiRoutes, instanceRoutes).pipe(
     Question.defaultLayer,
     Ripgrep.defaultLayer,
     Session.defaultLayer,
+    SessionCompaction.defaultLayer,
+    SessionPrompt.defaultLayer,
+    SessionRevert.defaultLayer,
+    SessionShare.defaultLayer,
     SessionRunState.defaultLayer,
     SessionStatus.defaultLayer,
     SessionSummary.defaultLayer,

+ 1 - 1
packages/opencode/src/session/session.ts

@@ -589,7 +589,7 @@ export const layer: Layer.Layer<Service, never, Bus.Service | Storage.Service> =
         path: sessionPath(ctx.worktree, ctx.directory),
         title: input?.title,
         permission: input?.permission,
-        workspaceID: workspace,
+        workspaceID: input?.workspaceID ?? workspace,
       })
     })
 

+ 62 - 0
packages/opencode/test/server/httpapi-session.test.ts

@@ -3,9 +3,13 @@ import { mkdir } from "node:fs/promises"
 import path from "node:path"
 import { Effect } from "effect"
 import { Flag } from "@opencode-ai/core/flag/flag"
+import { registerAdaptor } from "../../src/control-plane/adaptors"
+import type { WorkspaceAdaptor } from "../../src/control-plane/types"
+import { Workspace } from "../../src/control-plane/workspace"
 import { PermissionID } from "../../src/permission/schema"
 import { ModelID, ProviderID } from "../../src/provider/schema"
 import { Instance } from "../../src/project/instance"
+import { Project } from "../../src/project/project"
 import { Server } from "../../src/server/server"
 import { SessionPaths } from "../../src/server/routes/instance/httpapi/groups/session"
 import { Session } from "@/session/session"
@@ -22,6 +26,7 @@ import { it } from "../lib/effect"
 void Log.init({ print: false })
 
 const original = Flag.OPENCODE_EXPERIMENTAL_HTTPAPI
+const originalWorkspaces = Flag.OPENCODE_EXPERIMENTAL_WORKSPACES
 
 function app(experimental = true) {
   Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = experimental
@@ -77,6 +82,28 @@ function createTextMessage(directory: string, sessionID: SessionID, text: string
   )
 }
 
+const localAdaptor = (directory: string): WorkspaceAdaptor => ({
+  name: "Local Test",
+  description: "Create a local test workspace",
+  configure: (info) => ({ ...info, name: "local-test", directory }),
+  create: async () => {
+    await mkdir(directory, { recursive: true })
+  },
+  async remove() {},
+  target: () => ({ type: "local" as const, directory }),
+})
+
+const createLocalWorkspace = (input: { projectID: Project.Info["id"]; type: string; directory: string }) =>
+  Effect.promise(async () => {
+    registerAdaptor(input.projectID, input.type, localAdaptor(input.directory))
+    return Workspace.create({
+      type: input.type,
+      branch: null,
+      extra: null,
+      projectID: input.projectID,
+    })
+  })
+
 function request(path: string, init?: RequestInit) {
   return Effect.promise(async () => app().request(path, init))
 }
@@ -108,6 +135,7 @@ function withTmp<A, E, R>(
 
 afterEach(async () => {
   Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = original
+  Flag.OPENCODE_EXPERIMENTAL_WORKSPACES = originalWorkspaces
   await Instance.disposeAll()
   await resetDatabase()
 })
@@ -226,6 +254,40 @@ describe("session HttpApi", () => {
     ),
   )
 
+  it.live(
+    "persists selected workspace id when creating a session",
+    withTmp({ git: true, config: { formatter: false, lsp: false, share: "disabled" } }, (tmp) =>
+      Effect.gen(function* () {
+        Flag.OPENCODE_EXPERIMENTAL_WORKSPACES = true
+        const project = yield* Project.use.fromDirectory(tmp.path).pipe(Effect.provide(Project.defaultLayer))
+        const workspace = yield* createLocalWorkspace({
+          projectID: project.project.id,
+          type: "session-create-workspace",
+          directory: path.join(tmp.path, ".workspace-local"),
+        })
+
+        const created = yield* requestJson<Session.Info>(`${SessionPaths.create}?workspace=${workspace.id}`, {
+          method: "POST",
+          headers: { "x-opencode-directory": tmp.path, "content-type": "application/json" },
+          body: JSON.stringify({ title: "workspace session" }),
+        })
+
+        expect(created).toMatchObject({ id: created.id, workspaceID: workspace.id })
+        expect(
+          yield* Effect.sync(() =>
+            Database.use((db) =>
+              db
+                .select({ workspaceID: SessionTable.workspace_id })
+                .from(SessionTable)
+                .where(eq(SessionTable.id, created.id))
+                .get(),
+            ),
+          ),
+        ).toEqual({ workspaceID: workspace.id })
+      }),
+    ),
+  )
+
   it.live(
     "matches legacy archived timestamp validation",
     withTmp({ git: true, config: { formatter: false, lsp: false } }, (tmp) =>