fix(server): emit fixed workspace fence headers (#26443)
This commit is contained in:
@@ -0,0 +1,20 @@
|
|||||||
|
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||||
|
import { Effect } from "effect"
|
||||||
|
import { HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/unstable/http"
|
||||||
|
import * as Fence from "@/server/shared/fence"
|
||||||
|
|
||||||
|
const ignoredMethods = new Set(["GET", "HEAD", "OPTIONS"])
|
||||||
|
|
||||||
|
export const fenceLayer = HttpRouter.middleware<{ handles: unknown }>()((effect) =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const request = yield* HttpServerRequest.HttpServerRequest
|
||||||
|
if (!Flag.OPENCODE_WORKSPACE_ID || ignoredMethods.has(request.method)) return yield* effect
|
||||||
|
|
||||||
|
const previous = Fence.load()
|
||||||
|
const response = yield* effect
|
||||||
|
const current = Fence.diff(previous, Fence.load())
|
||||||
|
if (Object.keys(current).length === 0) return response
|
||||||
|
|
||||||
|
return HttpServerResponse.setHeader(response, Fence.HEADER, JSON.stringify(current))
|
||||||
|
}),
|
||||||
|
).layer
|
||||||
@@ -82,6 +82,7 @@ import { disposeMiddleware } from "./lifecycle"
|
|||||||
import { memoMap } from "@opencode-ai/core/effect/memo-map"
|
import { memoMap } from "@opencode-ai/core/effect/memo-map"
|
||||||
import * as ServerBackend from "@/server/backend"
|
import * as ServerBackend from "@/server/backend"
|
||||||
import { errorLayer } from "./middleware/error"
|
import { errorLayer } from "./middleware/error"
|
||||||
|
import { fenceLayer } from "./middleware/fence"
|
||||||
|
|
||||||
export const context = Context.makeUnsafe<unknown>(new Map())
|
export const context = Context.makeUnsafe<unknown>(new Map())
|
||||||
|
|
||||||
@@ -173,6 +174,7 @@ export function createRoutes(corsOptions?: CorsOptions) {
|
|||||||
return Layer.mergeAll(rootApiRoutes, eventApiRoutes, instanceRoutes, docRoute, uiRoute).pipe(
|
return Layer.mergeAll(rootApiRoutes, eventApiRoutes, instanceRoutes, docRoute, uiRoute).pipe(
|
||||||
Layer.provide([
|
Layer.provide([
|
||||||
errorLayer,
|
errorLayer,
|
||||||
|
fenceLayer,
|
||||||
cors(corsOptions),
|
cors(corsOptions),
|
||||||
runtime,
|
runtime,
|
||||||
Account.defaultLayer,
|
Account.defaultLayer,
|
||||||
|
|||||||
@@ -4,8 +4,11 @@ import { describe, expect } from "bun:test"
|
|||||||
import { Config, Effect, FileSystem, Layer, Path } from "effect"
|
import { Config, Effect, FileSystem, Layer, Path } from "effect"
|
||||||
import { HttpClient, HttpClientRequest, HttpRouter, HttpServer } from "effect/unstable/http"
|
import { HttpClient, HttpClientRequest, HttpRouter, HttpServer } from "effect/unstable/http"
|
||||||
import * as Socket from "effect/unstable/socket/Socket"
|
import * as Socket from "effect/unstable/socket/Socket"
|
||||||
|
import { WorkspaceID } from "../../src/control-plane/schema"
|
||||||
import { InstancePaths } from "../../src/server/routes/instance/httpapi/groups/instance"
|
import { InstancePaths } from "../../src/server/routes/instance/httpapi/groups/instance"
|
||||||
|
import { SessionPaths } from "../../src/server/routes/instance/httpapi/groups/session"
|
||||||
import { ExperimentalHttpApiServer } from "../../src/server/routes/instance/httpapi/server"
|
import { ExperimentalHttpApiServer } from "../../src/server/routes/instance/httpapi/server"
|
||||||
|
import { HEADER as FenceHeader } from "../../src/server/shared/fence"
|
||||||
import { resetDatabase } from "../fixture/db"
|
import { resetDatabase } from "../fixture/db"
|
||||||
import { disposeAllInstances, tmpdirScoped } from "../fixture/fixture"
|
import { disposeAllInstances, tmpdirScoped } from "../fixture/fixture"
|
||||||
import { testEffect } from "../lib/effect"
|
import { testEffect } from "../lib/effect"
|
||||||
@@ -65,6 +68,28 @@ describe("instance HttpApi", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
it.live("emits a sync fence header for fixed-workspace mutations", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const originalWorkspaceID = Flag.OPENCODE_WORKSPACE_ID
|
||||||
|
Flag.OPENCODE_WORKSPACE_ID = WorkspaceID.ascending()
|
||||||
|
yield* Effect.addFinalizer(() =>
|
||||||
|
Effect.sync(() => {
|
||||||
|
Flag.OPENCODE_WORKSPACE_ID = originalWorkspaceID
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
const dir = yield* tmpdirScoped({ git: true })
|
||||||
|
const response = yield* HttpClientRequest.post(SessionPaths.create).pipe(
|
||||||
|
directoryHeader(dir),
|
||||||
|
HttpClientRequest.bodyJson({ title: "fenced" }),
|
||||||
|
Effect.flatMap(HttpClient.execute),
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.status).toBe(200)
|
||||||
|
expect(JSON.parse(response.headers[FenceHeader] ?? "{}")).not.toEqual({})
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
it.live("serves path and VCS read endpoints", () =>
|
it.live("serves path and VCS read endpoints", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const dir = yield* tmpdirScoped({ git: true })
|
const dir = yield* tmpdirScoped({ git: true })
|
||||||
|
|||||||
Reference in New Issue
Block a user