From d5cd388da20f420fba6fdacced20b9f6c3617468 Mon Sep 17 00:00:00 2001 From: Sarav Date: Wed, 23 Sep 2026 07:32:39 +0530 Subject: [PATCH 1/4] fix(workspace): route warehouse tools at the pinned workspace MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The IDE extension's pin outranks the project's stored binding inside `resolveBindingOutcome`, so identity, skills and memory all follow the panel's selection. Warehouse tool routing did not: `precedence.currentBinding` and `engine-probes.resolveBinding` read the on-disk binding cache directly, and the pin is deliberately never persisted, so routing could not see it. In a pinned session that left one turn naming two workspaces — the identity section said the pinned one while the routing section named whatever the project was linked to, with nothing telling the model which governs execution. With no prior local link, routing settled `unbound` and every warehouse call went to the local tools instead. Both readers now consult the pin first, through a single exported arm of the same resolver the other consumers use, so there is one precedence rule rather than two implementations of it. Exposed as the pin arm alone rather than pointing these callers at `resolveBindingOutcome`, because the rest of that function is not equivalent to the strict cache read they do today: with no credentials configured it answers `unknown` where the strict read answers "no binding", and the engine overlay treats those differently — one refuses and holds the datamate key, the other hands it back. Layering only the pin keeps every unpinned session on exactly the path it has now. A pin that cannot be honoured — malformed, outside its root, unresolvable credentials, or naming a workspace the account cannot see — fails closed rather than falling through to the project's link. Falling through is precisely the mismatch this fixes, and it would otherwise resurface whenever validation could not complete. Fixes #1337. Co-Authored-By: Claude Opus 5 (1M context) --- .../src/altimate/workspace/engine-probes.ts | 31 +++- .../src/altimate/workspace/precedence.ts | 19 +- .../opencode/src/altimate/workspace/state.ts | 29 +++ .../altimate/workspace/routing-pin.test.ts | 169 ++++++++++++++++++ 4 files changed, 243 insertions(+), 5 deletions(-) create mode 100644 packages/opencode/test/altimate/workspace/routing-pin.test.ts diff --git a/packages/opencode/src/altimate/workspace/engine-probes.ts b/packages/opencode/src/altimate/workspace/engine-probes.ts index 5f1d7c9dfc..1957cabab1 100644 --- a/packages/opencode/src/altimate/workspace/engine-probes.ts +++ b/packages/opencode/src/altimate/workspace/engine-probes.ts @@ -11,7 +11,7 @@ import { AltimateApi } from "@/altimate/api/client" import { AppRuntime } from "@/effect/app-runtime" import { EventV2Bridge } from "@/event-v2-bridge" import { TuiEvent } from "@/server/tui-event" -import { readLocalBindingScopedStrict } from "./state" +import { currentScope, readLocalBindingScopedStrict, resolvePinnedBindingForRouting } from "./state" import { log, syncInternals, type BindingRead, type ScopedBinding } from "./engine-seams" import type { Declared, DeclaredExtension, Toast } from "./engine-types" @@ -25,9 +25,32 @@ export const DECLARED_TIMEOUT_MS = 4_000 * same path a production one does. */ export async function resolveBinding(directory: string): Promise { try { - const binding = syncInternals.resolveBinding - ? await syncInternals.resolveBinding(directory) - : await readScoped(directory) + if (syncInternals.resolveBinding) { + const seam = await syncInternals.resolveBinding(directory) + return seam ? { kind: "bound", binding: seam } : { kind: "unbound" } + } + // altimate_change — honour the IDE extension's pin before the project's own link, so the + // engine overlay claims the key for the workspace the panel selected rather than the one the + // project happens to be linked to (#1337). Same precedence `resolveBindingOutcome` applies + // for identity, skills and memory. + const pinned = await resolvePinnedBindingForRouting(directory) + if (pinned) { + if (pinned.status !== "bound") { + // Fail closed: a pin that cannot be honoured must not silently hand the overlay back to + // the project's link, which is the mismatch this prevents. `failed` holds the key and + // retries at the next turn boundary rather than releasing it as `unbound` would. + return { kind: "failed", error: "the workspace pin could not be honoured" } + } + const scope = await currentScope() + return { + kind: "bound", + binding: { + ...pinned.binding, + scope: scope ? `${scope.tenant}|${scope.apiUrl}` : undefined, + }, + } + } + const binding = await readScoped(directory) return binding ? { kind: "bound", binding } : { kind: "unbound" } } catch (err) { log.warn("could not read the workspace binding", { err: String(err) }) diff --git a/packages/opencode/src/altimate/workspace/precedence.ts b/packages/opencode/src/altimate/workspace/precedence.ts index 56755182e1..a972d21e76 100644 --- a/packages/opencode/src/altimate/workspace/precedence.ts +++ b/packages/opencode/src/altimate/workspace/precedence.ts @@ -66,7 +66,7 @@ import { type EntryLike, type Outcome, } from "./engine-overlay" -import { readLocalBindingScopedStrict } from "./state" +import { readLocalBindingScopedStrict, resolvePinnedBindingForRouting } from "./state" import { liveBridge } from "./engine-probes" import { syncInternals } from "./engine-seams" import { canonicalType } from "../native/connections/registry" @@ -438,6 +438,23 @@ async function currentBinding(): Promise { } const directory = Instance.directory if (!directory) return { kind: "unbound" } + // altimate_change — the IDE extension's pin outranks the project's own link, as it already + // does for identity, skills and memory. Without this the identity section named the pinned + // workspace while these tools routed at whatever the project was linked to (#1337). + const pinned = await resolvePinnedBindingForRouting(directory) + if (pinned) { + if (pinned.status === "bound") { + return { + kind: "bound", + datamateId: pinned.binding.datamateId, + datamateName: pinned.binding.datamateName, + } + } + // A pin that could not be honoured is not a licence to route at the project's link — that + // is the mismatch this exists to prevent. `unreadable` disables routing without claiming + // the project is unbound. + return { kind: "unreadable", error: "the workspace pin could not be honoured" } + } const { binding } = await readLocalBindingScopedStrict(directory) return binding ? { kind: "bound", datamateId: binding.datamateId, datamateName: binding.datamateName } diff --git a/packages/opencode/src/altimate/workspace/state.ts b/packages/opencode/src/altimate/workspace/state.ts index e7de29530f..240933395c 100644 --- a/packages/opencode/src/altimate/workspace/state.ts +++ b/packages/opencode/src/altimate/workspace/state.ts @@ -651,6 +651,35 @@ async function resolvePinnedBinding(directory: string, pin: ValidPin): Promise { + const pin = readPinLogged() + if (pin.kind === "absent") return null + if (pin.kind === "invalid") return { status: "unknown" } + return resolvePinnedBinding(directory, pin) +} + export async function resolveBindingOutcome(directory: string): Promise { // altimate_change — the IDE extension's selection outranks whatever binding this project carries. // Checked before the local cache and before any server lookup: the whole point is that the panel, diff --git a/packages/opencode/test/altimate/workspace/routing-pin.test.ts b/packages/opencode/test/altimate/workspace/routing-pin.test.ts new file mode 100644 index 0000000000..32f38e9bba --- /dev/null +++ b/packages/opencode/test/altimate/workspace/routing-pin.test.ts @@ -0,0 +1,169 @@ +// altimate_change - new file +// +// The IDE extension's pin governs warehouse tool ROUTING, not just identity, skills and memory. +// +// `state-pin.test.ts` covers the pin inside `resolveBindingOutcome`, which is what skills, memory +// and the identity section read. Routing reads elsewhere — `engine-probes.resolveBinding` and +// `precedence.currentBinding` went straight to the on-disk cache — so a pinned session could name +// one workspace in the identity section and route warehouse calls at another (#1337). These cover +// the routing side of that precedence, and the refusal, which is where the damage would be. +import { afterAll, afterEach, beforeEach, describe, expect, test } from "bun:test" +import { mkdirSync, rmSync } from "node:fs" +import path from "node:path" +import os from "node:os" + +const ORIGINAL_XDG_STATE_HOME = process.env.XDG_STATE_HOME +const SANDBOX = path.join(os.tmpdir(), `altimate-routing-pin-test-${process.pid}-${Date.now()}`) +mkdirSync(path.join(SANDBOX, "state"), { recursive: true }) +process.env.XDG_STATE_HOME = path.join(SANDBOX, "state") + +const { resolvePinnedBindingForRouting, recordApprovedBinding, __resetPinValidation } = await import( + "../../../src/altimate/workspace/state" +) +const { resolveBinding } = await import("../../../src/altimate/workspace/engine-probes") +const { AltimateApi } = await import("../../../src/altimate/api/client") +const { WorkspaceApi } = await import("../../../src/altimate/workspace/api-client") + +const ROOT = path.join(SANDBOX, "project") +mkdirSync(ROOT, { recursive: true }) + +const originalIsConfigured = AltimateApi.isConfigured +const originalGetCreds = AltimateApi.getCredentials +const originalList = WorkspaceApi.listDatamates +type Creds = Awaited> + +function stubCreds() { + ;(AltimateApi as unknown as { isConfigured: () => Promise }).isConfigured = async () => true + ;(AltimateApi as unknown as { getCredentials: () => Promise }).getCredentials = async () => + ({ altimateInstanceName: "acme", altimateUrl: "https://api.test", altimateApiKey: "k" }) as Creds +} + +function stubList(rows: { id: number; name: string }[]) { + ;(WorkspaceApi as unknown as { listDatamates: () => Promise }).listDatamates = async () => rows +} + +const PIN_VARS = [ + "ALTIMATE_CODE_SERVE", + "ALTIMATE_PINNED_WORKSPACE_ID", + "ALTIMATE_PINNED_WORKSPACE_NAME", + "ALTIMATE_PINNED_WORKSPACE_ROOT", +] + +function setPin(over: Record = {}) { + const base: Record = { + ALTIMATE_CODE_SERVE: "1", + ALTIMATE_PINNED_WORKSPACE_ID: "42", + ALTIMATE_PINNED_WORKSPACE_NAME: "pinned-workspace", + ALTIMATE_PINNED_WORKSPACE_ROOT: ROOT, + ...over, + } + for (const [k, v] of Object.entries(base)) { + if (v === undefined) delete process.env[k] + else process.env[k] = v + } +} + +function clearPin() { + for (const k of PIN_VARS) delete process.env[k] +} + +/** The project's own link, naming a DIFFERENT workspace than the pin — the returning-user case + * from the report, where identity said one id and routing said another. */ +async function seedLocalLink(datamateId = 7, datamateName = "project-link") { + await recordApprovedBinding(ROOT, { + datamateId, + datamateName, + linkedAt: Date.now(), + repoRemote: "git@example.com:acme/project.git", + // Both identity keys are written explicitly: the strict reader rejects a row where either is + // `undefined` (it accepts `string | null`), so omitting one produces a cache the routing read + // cannot parse — which looks like a product failure in a test that is only mis-seeded. + projectPath: null, + } as never) +} + +beforeEach(() => { + __resetPinValidation() + stubCreds() + stubList([ + { id: 42, name: "pinned-workspace" }, + { id: 7, name: "project-link" }, + ]) + clearPin() +}) + +afterEach(() => { + clearPin() + __resetPinValidation() +}) + +afterAll(() => { + ;(AltimateApi as unknown as { isConfigured: unknown }).isConfigured = originalIsConfigured + ;(AltimateApi as unknown as { getCredentials: unknown }).getCredentials = originalGetCreds + ;(WorkspaceApi as unknown as { listDatamates: unknown }).listDatamates = originalList + if (ORIGINAL_XDG_STATE_HOME === undefined) delete process.env.XDG_STATE_HOME + else process.env.XDG_STATE_HOME = ORIGINAL_XDG_STATE_HOME + rmSync(SANDBOX, { recursive: true, force: true }) +}) + +describe("resolvePinnedBindingForRouting", () => { + test("answers null with no pin, so unpinned sessions keep reading their own link", async () => { + expect(await resolvePinnedBindingForRouting(ROOT)).toBeNull() + }) + + test("returns the pinned workspace when the account can see it", async () => { + setPin() + const outcome = await resolvePinnedBindingForRouting(ROOT) + expect(outcome?.status).toBe("bound") + expect(outcome?.status === "bound" && outcome.binding.datamateId).toBe(42) + }) + + test("refuses a malformed pin rather than falling through to the project's link", async () => { + setPin({ ALTIMATE_PINNED_WORKSPACE_ID: "not-a-number" }) + expect((await resolvePinnedBindingForRouting(ROOT))?.status).toBe("unknown") + }) + + test("refuses a pin naming a workspace this account cannot see", async () => { + stubList([{ id: 7, name: "project-link" }]) + setPin() + expect((await resolvePinnedBindingForRouting(ROOT))?.status).toBe("unknown") + }) + + test("refuses a pin for a directory outside the pinned root", async () => { + setPin() + expect((await resolvePinnedBindingForRouting(path.join(SANDBOX, "elsewhere")))?.status).toBe("unknown") + }) +}) + +describe("engine-probes.resolveBinding — the routing read", () => { + test("routes at the pin, not the project's own link", async () => { + await seedLocalLink(7) + setPin() + const read = await resolveBinding(ROOT) + expect(read.kind).toBe("bound") + // The regression: this returned 7 — the identity section said 42 in the same prompt. + expect(read.kind === "bound" && read.binding.datamateId).toBe(42) + }) + + test("still reads the project's own link when nothing is pinned", async () => { + await seedLocalLink(7) + const read = await resolveBinding(ROOT) + expect(read.kind).toBe("bound") + expect(read.kind === "bound" && read.binding.datamateId).toBe(7) + }) + + test("fails closed when the pin cannot be honoured, rather than routing at the project's link", async () => { + await seedLocalLink(7) + stubList([{ id: 7, name: "project-link" }]) + setPin() + const read = await resolveBinding(ROOT) + // Not `bound` at 7: falling back to the project's link is the confusion this prevents. + expect(read.kind).toBe("failed") + }) + + test("carries the credential scope so the engine key stays account-partitioned", async () => { + setPin() + const read = await resolveBinding(ROOT) + expect(read.kind === "bound" && read.binding.scope).toBe("acme|https://api.test") + }) +}) From d5227fe8c303da494d331caaebcd1f810b429f58 Mon Sep 17 00:00:00 2001 From: Sarav Date: Wed, 23 Sep 2026 08:00:43 +0530 Subject: [PATCH 2/4] fix(workspace): drop the unreachable engine-probes arm, keep the precedence fix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review was right that the first cut reached too far. `engine-probes.resolveBinding` is called only from `engine-overlay` at two sites, and both sit behind `if (!isEnabled() || isServe())`. The pin exists only when `ALTIMATE_CODE_SERVE=1` (`pin.ts`), which is exactly the mode that bail covers, so the pin arm added there could not run in any configuration. `manage.ts` imports its `resolveBinding` from `./state`, which is already pin-aware, so nothing else reached it either. Reverted. That removes the second finding with it: the arm re-read credentials through `currentScope()` after `resolvePinnedBinding` had validated against its own snapshot, so a credential change between the two awaits could have paired one account's validated id with another's scope. Deleting the code is a better answer than guarding it, since it had no caller. `precedence.currentBinding` is a different matter and the fix stays: `derive` gates on `isEnabled()` alone, with no `isServe()` bail, so it does run in a pinned session — which is why the reported prompt named two workspaces at once. What this does NOT do, now stated plainly rather than implied: it does not make warehouse calls execute against the pinned workspace. In serve mode `atTurnStart` records `disabled`, `SERVING` rejects that, and `derive` settles `unattributed` — routing is off there by design, because the extension runs its own engine under the same key. The reachable effect is which workspace the section names, which is the contradiction the report opens with. Tests follow the code: the engine-probes cases are replaced with precedence ones driven through `refresh()` under a real `Instance`, so they exercise the path that actually runs instead of a function no caller reaches. Co-Authored-By: Claude Opus 5 (1M context) --- .../src/altimate/workspace/engine-probes.ts | 31 +------- .../altimate/workspace/routing-pin.test.ts | 79 +++++++++++++------ 2 files changed, 59 insertions(+), 51 deletions(-) diff --git a/packages/opencode/src/altimate/workspace/engine-probes.ts b/packages/opencode/src/altimate/workspace/engine-probes.ts index 1957cabab1..5f1d7c9dfc 100644 --- a/packages/opencode/src/altimate/workspace/engine-probes.ts +++ b/packages/opencode/src/altimate/workspace/engine-probes.ts @@ -11,7 +11,7 @@ import { AltimateApi } from "@/altimate/api/client" import { AppRuntime } from "@/effect/app-runtime" import { EventV2Bridge } from "@/event-v2-bridge" import { TuiEvent } from "@/server/tui-event" -import { currentScope, readLocalBindingScopedStrict, resolvePinnedBindingForRouting } from "./state" +import { readLocalBindingScopedStrict } from "./state" import { log, syncInternals, type BindingRead, type ScopedBinding } from "./engine-seams" import type { Declared, DeclaredExtension, Toast } from "./engine-types" @@ -25,32 +25,9 @@ export const DECLARED_TIMEOUT_MS = 4_000 * same path a production one does. */ export async function resolveBinding(directory: string): Promise { try { - if (syncInternals.resolveBinding) { - const seam = await syncInternals.resolveBinding(directory) - return seam ? { kind: "bound", binding: seam } : { kind: "unbound" } - } - // altimate_change — honour the IDE extension's pin before the project's own link, so the - // engine overlay claims the key for the workspace the panel selected rather than the one the - // project happens to be linked to (#1337). Same precedence `resolveBindingOutcome` applies - // for identity, skills and memory. - const pinned = await resolvePinnedBindingForRouting(directory) - if (pinned) { - if (pinned.status !== "bound") { - // Fail closed: a pin that cannot be honoured must not silently hand the overlay back to - // the project's link, which is the mismatch this prevents. `failed` holds the key and - // retries at the next turn boundary rather than releasing it as `unbound` would. - return { kind: "failed", error: "the workspace pin could not be honoured" } - } - const scope = await currentScope() - return { - kind: "bound", - binding: { - ...pinned.binding, - scope: scope ? `${scope.tenant}|${scope.apiUrl}` : undefined, - }, - } - } - const binding = await readScoped(directory) + const binding = syncInternals.resolveBinding + ? await syncInternals.resolveBinding(directory) + : await readScoped(directory) return binding ? { kind: "bound", binding } : { kind: "unbound" } } catch (err) { log.warn("could not read the workspace binding", { err: String(err) }) diff --git a/packages/opencode/test/altimate/workspace/routing-pin.test.ts b/packages/opencode/test/altimate/workspace/routing-pin.test.ts index 32f38e9bba..14b51bb4a3 100644 --- a/packages/opencode/test/altimate/workspace/routing-pin.test.ts +++ b/packages/opencode/test/altimate/workspace/routing-pin.test.ts @@ -20,7 +20,9 @@ process.env.XDG_STATE_HOME = path.join(SANDBOX, "state") const { resolvePinnedBindingForRouting, recordApprovedBinding, __resetPinValidation } = await import( "../../../src/altimate/workspace/state" ) -const { resolveBinding } = await import("../../../src/altimate/workspace/engine-probes") +const { refresh, precedenceInternals } = await import("../../../src/altimate/workspace/precedence") +const { Instance } = await import("../../../src/project/instance") +const { SNOWFLAKE_TOOLS } = await import("./precedence-fixture") const { AltimateApi } = await import("../../../src/altimate/api/client") const { WorkspaceApi } = await import("../../../src/altimate/workspace/api-client") @@ -82,7 +84,11 @@ async function seedLocalLink(datamateId = 7, datamateName = "project-link") { } as never) } +const ORIGINAL_PILOT = process.env.ALTIMATE_WORKSPACE + beforeEach(() => { + // `derive` short-circuits on `pilot-off` before it ever reads a binding. + process.env.ALTIMATE_WORKSPACE = "1" __resetPinValidation() stubCreds() stubList([ @@ -98,6 +104,8 @@ afterEach(() => { }) afterAll(() => { + if (ORIGINAL_PILOT === undefined) delete process.env.ALTIMATE_WORKSPACE + else process.env.ALTIMATE_WORKSPACE = ORIGINAL_PILOT ;(AltimateApi as unknown as { isConfigured: unknown }).isConfigured = originalIsConfigured ;(AltimateApi as unknown as { getCredentials: unknown }).getCredentials = originalGetCreds ;(WorkspaceApi as unknown as { listDatamates: unknown }).listDatamates = originalList @@ -135,35 +143,58 @@ describe("resolvePinnedBindingForRouting", () => { }) }) -describe("engine-probes.resolveBinding — the routing read", () => { - test("routes at the pin, not the project's own link", async () => { - await seedLocalLink(7) - setPin() - const read = await resolveBinding(ROOT) - expect(read.kind).toBe("bound") - // The regression: this returned 7 — the identity section said 42 in the same prompt. - expect(read.kind === "bound" && read.binding.datamateId).toBe(42) - }) +describe("precedence.derive — the routing read", () => { + const SESSION = "ses_routing_pin" - test("still reads the project's own link when nothing is pinned", async () => { - await seedLocalLink(7) - const read = await resolveBinding(ROOT) - expect(read.kind).toBe("bound") - expect(read.kind === "bound" && read.binding.datamateId).toBe(7) + /** Serve mode never attributes an engine: `engine-overlay.atTurnStart` short-circuits on + * `isServe()` and records `disabled`, which `SERVING` rejects. So the reachable effect of the + * pin here is WHICH workspace the section names, not whether routing turns on. Stubbed to + * `undefined` to reproduce that without a live engine. */ + function unattributedEngine() { + precedenceInternals.attachOutcome = async () => undefined + } + + afterEach(() => { + delete precedenceInternals.attachOutcome + delete precedenceInternals.binding }) - test("fails closed when the pin cannot be honoured, rather than routing at the project's link", async () => { - await seedLocalLink(7) - stubList([{ id: 7, name: "project-link" }]) + async function derivedIn(directory: string) { + return await Instance.provide({ + directory, + fn: async () => { + const p = await refresh(SESSION, SNOWFLAKE_TOOLS) + await Instance.dispose() + return p + }, + }) + } + + test("names the pinned workspace, not the project's own link", async () => { + await seedLocalLink(7, "project-link") + unattributedEngine() setPin() - const read = await resolveBinding(ROOT) - // Not `bound` at 7: falling back to the project's link is the confusion this prevents. - expect(read.kind).toBe("failed") + const p = await derivedIn(ROOT) + // The regression: this said "project-link" while the identity section said the pinned one — + // two workspaces named in a single prompt. + expect(p.workspaceName).toBe("pinned-workspace") }) - test("carries the credential scope so the engine key stays account-partitioned", async () => { + test("still names the project's own link when nothing is pinned", async () => { + await seedLocalLink(7, "project-link") + unattributedEngine() + const p = await derivedIn(ROOT) + expect(p.workspaceName).toBe("project-link") + }) + + test("fails closed when the pin cannot be honoured, rather than naming the project's link", async () => { + await seedLocalLink(7, "project-link") + unattributedEngine() + stubList([{ id: 7, name: "project-link" }]) setPin() - const read = await resolveBinding(ROOT) - expect(read.kind === "bound" && read.binding.scope).toBe("acme|https://api.test") + const p = await derivedIn(ROOT) + expect(p.enabled).toBe(false) + expect(p.disabledReason).toBe("binding-unreadable") + expect(p.workspaceName).not.toBe("project-link") }) }) From f26fe15ef6e54caccf4d1186b8a2c38e1591eee4 Mon Sep 17 00:00:00 2001 From: Sarav Date: Wed, 23 Sep 2026 08:01:48 +0530 Subject: [PATCH 3/4] test(workspace): clear the binding cache between routing-pin tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CodeRabbit's point: the cache is one file under `XDG_STATE_HOME` shared by every test in the file, so a row seeded by one decided what the next one read. It happened to be harmless here — each test seeds the link it asserts on — but it made the file order-dependent by construction. Co-Authored-By: Claude Opus 5 (1M context) --- .../opencode/test/altimate/workspace/routing-pin.test.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/opencode/test/altimate/workspace/routing-pin.test.ts b/packages/opencode/test/altimate/workspace/routing-pin.test.ts index 14b51bb4a3..1ab20da70d 100644 --- a/packages/opencode/test/altimate/workspace/routing-pin.test.ts +++ b/packages/opencode/test/altimate/workspace/routing-pin.test.ts @@ -17,9 +17,8 @@ const SANDBOX = path.join(os.tmpdir(), `altimate-routing-pin-test-${process.pid} mkdirSync(path.join(SANDBOX, "state"), { recursive: true }) process.env.XDG_STATE_HOME = path.join(SANDBOX, "state") -const { resolvePinnedBindingForRouting, recordApprovedBinding, __resetPinValidation } = await import( - "../../../src/altimate/workspace/state" -) +const { resolvePinnedBindingForRouting, recordApprovedBinding, __resetPinValidation, cachePath } = + await import("../../../src/altimate/workspace/state") const { refresh, precedenceInternals } = await import("../../../src/altimate/workspace/precedence") const { Instance } = await import("../../../src/project/instance") const { SNOWFLAKE_TOOLS } = await import("./precedence-fixture") @@ -101,6 +100,10 @@ beforeEach(() => { afterEach(() => { clearPin() __resetPinValidation() + // The binding cache is a single file under `XDG_STATE_HOME`, shared by every test here, so a + // row seeded by one would otherwise decide what the next one reads. Cleared so each test states + // its own starting point and the file can be read in any order. + rmSync(cachePath(), { force: true }) }) afterAll(() => { From 9fa80061227896a4dc015db3a1031fd6505a27ce Mon Sep 17 00:00:00 2001 From: Sarav Date: Wed, 23 Sep 2026 08:30:28 +0530 Subject: [PATCH 4/4] perf(workspace): skip pin validation when the escape hatch is on MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cubic's point: a session started with `--integrations=local` has opted out of workspace routing entirely, but `currentBinding` was still resolving credentials and, once the validation TTL lapsed, making a `listDatamates` round trip — every turn — for a binding `derive` discards two lines later as `escape-hatch`. The hatch is not simply moved above the call instead. `derive` reads it AFTER the link deliberately, so a project with no link at all reports `unbound` rather than claiming a workspace it does not have; its comment says so. Declining the pin inside `currentBinding` keeps that order and leaves the opt-out path on disk, where it was. Nothing observable changes: the `escape-hatch` result carries no workspace name. Also from review, both in the new test file: - The pilot flag was set per test but restored only in `afterAll`, so it leaked into every later test including the resolver block that does not use it. - `derivedIn` disposed its instance only on the success path; a throw from `refresh` would have left the boot in `Instance`'s directory-keyed cache for the next test to reuse. Now in a `finally`. Co-Authored-By: Claude Opus 5 (1M context) --- .../src/altimate/workspace/precedence.ts | 9 ++++- .../altimate/workspace/routing-pin.test.ts | 38 +++++++++++++++++-- 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/packages/opencode/src/altimate/workspace/precedence.ts b/packages/opencode/src/altimate/workspace/precedence.ts index a972d21e76..ff070b578e 100644 --- a/packages/opencode/src/altimate/workspace/precedence.ts +++ b/packages/opencode/src/altimate/workspace/precedence.ts @@ -441,7 +441,14 @@ async function currentBinding(): Promise { // altimate_change — the IDE extension's pin outranks the project's own link, as it already // does for identity, skills and memory. Without this the identity section named the pinned // workspace while these tools routed at whatever the project was linked to (#1337). - const pinned = await resolvePinnedBindingForRouting(directory) + // + // Skipped when the escape hatch is on. Honouring the pin costs a credential resolution and, + // once the validation TTL lapses, a `listDatamates` round trip — per turn, for a session that + // `derive` is about to settle as `escape-hatch` anyway. The hatch cannot simply be moved above + // this call instead: `derive` reads it AFTER the link deliberately, so that a project with no + // link at all reports `unbound` rather than claiming a workspace it does not have. Declining + // here keeps that order and leaves the opt-out path on disk, where it was. + const pinned = escapeHatchOn() ? null : await resolvePinnedBindingForRouting(directory) if (pinned) { if (pinned.status === "bound") { return { diff --git a/packages/opencode/test/altimate/workspace/routing-pin.test.ts b/packages/opencode/test/altimate/workspace/routing-pin.test.ts index 1ab20da70d..e281ab2cf9 100644 --- a/packages/opencode/test/altimate/workspace/routing-pin.test.ts +++ b/packages/opencode/test/altimate/workspace/routing-pin.test.ts @@ -39,8 +39,13 @@ function stubCreds() { ({ altimateInstanceName: "acme", altimateUrl: "https://api.test", altimateApiKey: "k" }) as Creds } +let listCalls = 0 + function stubList(rows: { id: number; name: string }[]) { - ;(WorkspaceApi as unknown as { listDatamates: () => Promise }).listDatamates = async () => rows + ;(WorkspaceApi as unknown as { listDatamates: () => Promise }).listDatamates = async () => { + listCalls += 1 + return rows + } } const PIN_VARS = [ @@ -88,6 +93,8 @@ const ORIGINAL_PILOT = process.env.ALTIMATE_WORKSPACE beforeEach(() => { // `derive` short-circuits on `pilot-off` before it ever reads a binding. process.env.ALTIMATE_WORKSPACE = "1" + delete process.env.ALTIMATE_INTEGRATIONS + listCalls = 0 __resetPinValidation() stubCreds() stubList([ @@ -99,6 +106,12 @@ beforeEach(() => { afterEach(() => { clearPin() + delete process.env.ALTIMATE_INTEGRATIONS + // Restored per test, not only in `afterAll`: `beforeEach` sets it unconditionally, so leaving + // it set leaks the pilot into every later test in this file — including the resolver block, + // which does not use it. + if (ORIGINAL_PILOT === undefined) delete process.env.ALTIMATE_WORKSPACE + else process.env.ALTIMATE_WORKSPACE = ORIGINAL_PILOT __resetPinValidation() // The binding cache is a single file under `XDG_STATE_HOME`, shared by every test here, so a // row seeded by one would otherwise decide what the next one reads. Cleared so each test states @@ -166,9 +179,13 @@ describe("precedence.derive — the routing read", () => { return await Instance.provide({ directory, fn: async () => { - const p = await refresh(SESSION, SNOWFLAKE_TOOLS) - await Instance.dispose() - return p + // `finally`: a throw from `refresh` would otherwise leave the boot in `Instance`'s + // module-level cache keyed by directory, and the next test would reuse it. + try { + return await refresh(SESSION, SNOWFLAKE_TOOLS) + } finally { + await Instance.dispose() + } }, }) } @@ -190,6 +207,19 @@ describe("precedence.derive — the routing read", () => { expect(p.workspaceName).toBe("project-link") }) + /** Honouring a pin costs a credential read and, past the validation TTL, a `listDatamates` + * round trip. A session that opted out of workspace routing entirely should not pay that on + * every turn for an answer `derive` discards. */ + test("does not consult the pin when the escape hatch is on", async () => { + await seedLocalLink(7, "project-link") + unattributedEngine() + setPin() + process.env.ALTIMATE_INTEGRATIONS = "local" + const p = await derivedIn(ROOT) + expect(p.disabledReason).toBe("escape-hatch") + expect(listCalls).toBe(0) + }) + test("fails closed when the pin cannot be honoured, rather than naming the project's link", async () => { await seedLocalLink(7, "project-link") unattributedEngine()