From d4cd7d5c33122473da22ae118c93477ef7e38310 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Wed, 23 Sep 2026 17:33:48 -0700 Subject: [PATCH] fix(providers): restore compatibility ranges for every harness (#13328) --- .../provider/Layers/ProviderRegistry.test.ts | 36 ++++++++--- apps/server/src/provider/model-manifest.json | 50 ++++++++++++++- .../provider/providerCompatibility.test.ts | 63 +++++++++++++++++++ .../src/provider/providerCompatibility.ts | 12 +++- .../providerMaintenanceRunner.test.ts | 8 ++- 5 files changed, 154 insertions(+), 15 deletions(-) diff --git a/apps/server/src/provider/Layers/ProviderRegistry.test.ts b/apps/server/src/provider/Layers/ProviderRegistry.test.ts index 354484e6d39533d98b3feb7f638e556c7947f1f8..9ddf54deb3afd685551b01e3e271818dd4f1aa47 100644 --- a/apps/server/src/provider/Layers/ProviderRegistry.test.ts +++ b/apps/server/src/provider/Layers/ProviderRegistry.test.ts @@ -38,6 +38,7 @@ import { checkClaudeProviderStatus } from "./ClaudeProvider.ts"; import * as BackgroundPolicy from "../../background/BackgroundPolicy.ts"; import { AntigravityInstallation } from "../AntigravityInstallation.ts"; import * as ModelManifest from "../ModelManifest.ts"; +import { applyProviderCompatibility } from "../providerCompatibility.ts"; import * as CodexResetCredit from "./codexResetCredit.ts"; import * as OpenCodeRuntime from "../opencodeRuntime.ts"; import * as ProviderEventLoggers from "./ProviderEventLoggers.ts"; @@ -76,6 +77,12 @@ process.env.T3CODE_CURSOR_ENABLED = "1"; const encoder = new TextEncoder(); const TEST_EPOCH = DateTime.makeUnsafe("1970-01-01T00:00:00.000Z"); +const withBundledCompatibility = (snapshot: ServerProvider) => + applyProviderCompatibility( + snapshot, + undefined, + ModelManifest.BUNDLED_MODEL_MANIFEST.compatibility, + ); // Provider metadata checks use a bundled manifest and stubbed HTTP. const TestHttpClientLive = Layer.succeed( @@ -1754,7 +1761,7 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsModule.layerTest(), Te ); assert.deepStrictEqual( recoveredProviders.find((provider) => provider.instanceId === codexInstanceId), - codexProvider, + withBundledCompatibility(codexProvider), ); yield* Ref.set(catalogSnapshot, changedCatalogProvider); @@ -1766,7 +1773,7 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsModule.layerTest(), Te ); assert.deepStrictEqual( changedProviders.find((provider) => provider.instanceId === codexInstanceId), - codexProvider, + withBundledCompatibility(codexProvider), ); }).pipe(Effect.provide(runtimeServices)); @@ -1880,10 +1887,13 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsModule.layerTest(), Te yield* Fiber.join(persisted); const cachedProvider = yield* readProviderStatusCache(filePath); - assert.deepStrictEqual(cachedProvider, { - ...refreshedProvider, - models: [...initialProvider.models], - }); + assert.deepStrictEqual( + cachedProvider, + withBundledCompatibility({ + ...refreshedProvider, + models: [...initialProvider.models], + }), + ); }).pipe(Effect.provide(runtimeServices)); }), ); @@ -2095,10 +2105,14 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsModule.layerTest(), Te yield* Effect.gen(function* () { const registry = yield* ProviderRegistry.ProviderRegistry; - assert.deepStrictEqual(yield* registry.getProviders, [cachedProvider]); - assert.deepStrictEqual(yield* registry.refresh(codexDriver), [cachedProvider]); + assert.deepStrictEqual(yield* registry.getProviders, [ + withBundledCompatibility(cachedProvider), + ]); + assert.deepStrictEqual(yield* registry.refresh(codexDriver), [ + withBundledCompatibility(cachedProvider), + ]); assert.deepStrictEqual(yield* registry.refreshInstance(codexInstanceId), [ - cachedProvider, + withBundledCompatibility(cachedProvider), ]); }).pipe(Effect.provide(runtimeServices)); }), @@ -2206,7 +2220,9 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsModule.layerTest(), Te yield* Effect.gen(function* () { const registry = yield* ProviderRegistry.ProviderRegistry; - assert.deepStrictEqual(yield* registry.getProviders, [codexProvider]); + assert.deepStrictEqual(yield* registry.getProviders, [ + withBundledCompatibility(codexProvider), + ]); yield* Ref.set(failNextList, true); yield* PubSub.publish(changes, undefined); diff --git a/apps/server/src/provider/model-manifest.json b/apps/server/src/provider/model-manifest.json index 32a103be9d16aaa4c8b42d9acb370a122b489458..69e51bcb019ae51e2f3d96cb26420e311b778b5d 100644 --- a/apps/server/src/provider/model-manifest.json +++ b/apps/server/src/provider/model-manifest.json @@ -1,12 +1,58 @@ { "version": 1, - "updatedAt": "2026-09-22T21:40:00Z", + "updatedAt": "2026-09-24T00:11:29Z", "compatibility": [ + { + "driver": "codex", + "t3CodeRange": ">=0.0.42", + "recommendedRange": ">=0.129.0", + "ranges": [ + { "range": ">=0.129.0", "status": "supported" }, + { "range": "<0.129.0", "status": "broken" } + ] + }, + { + "driver": "claudeAgent", + "t3CodeRange": ">=0.0.42", + "recommendedRange": ">=2.1.280", + "ranges": [ + { "range": ">=2.1.280", "status": "supported" }, + { "range": ">=2.1.111 <2.1.280", "status": "graceful" }, + { "range": "<2.1.111", "status": "unsupported" } + ] + }, + { + "driver": "cursor", + "t3CodeRange": ">=0.0.42", + "recommendedRange": ">=2026.05.09", + "ranges": [ + { "range": ">=2026.05.09", "status": "supported" }, + { "range": "<2026.05.09", "status": "unknown" } + ] + }, + { + "driver": "grok", + "t3CodeRange": ">=0.0.42", + "recommendedRange": ">=1.0.13", + "ranges": [ + { "range": ">=1.0.13", "status": "supported" }, + { "range": "<1.0.13", "status": "unknown" } + ] + }, { "driver": "opencode", "t3CodeRange": ">=0.0.42", "recommendedRange": ">=1.14.19", - "ranges": [{ "range": "<1.14.19", "status": "broken" }] + "ranges": [ + { "range": ">=1.14.19", "status": "supported" }, + { "range": "<1.14.19", "status": "broken" } + ] + }, + { + "driver": "antigravity", + "t3CodeRange": ">=0.0.42", + "recommendedRange": "=1.1.1", + "ranges": [{ "range": "=1.1.1", "status": "supported" }] } ], "currentModels": { diff --git a/apps/server/src/provider/providerCompatibility.test.ts b/apps/server/src/provider/providerCompatibility.test.ts index e6b4e1a608b97b28d6bda6a6fec5308883508ecc..3a43356d2caf489943516fee81ac8922462f0ce0 100644 --- a/apps/server/src/provider/providerCompatibility.test.ts +++ b/apps/server/src/provider/providerCompatibility.test.ts @@ -15,6 +15,7 @@ import { ProviderRegistry } from "./Services/ProviderRegistry.ts"; import { ProviderInstanceRegistry } from "./Services/ProviderInstanceRegistry.ts"; import type { ProviderInstance } from "./ProviderDriver.ts"; import { makeManualOnlyProviderMaintenanceCapabilities } from "./providerMaintenance.ts"; +import { BUILT_IN_DRIVERS } from "./builtInDrivers.ts"; import * as Schema from "effect/Schema"; import { applyProviderCompatibility, @@ -51,6 +52,68 @@ const provider: ServerProvider = { }; describe("provider compatibility", () => { + it("bundles a compatibility policy for every built-in harness", () => { + for (const builtIn of BUILT_IN_DRIVERS) { + assert.isDefined( + resolveProviderCompatibility( + ModelManifest.BUNDLED_MODEL_MANIFEST.compatibility, + builtIn.driverKind, + null, + ), + `Missing bundled compatibility policy for ${builtIn.driverKind}`, + ); + } + }); + + it("compares Cursor build dates without treating semver prereleases as stable", () => { + const cursor = ProviderDriverKind.make("cursor"); + const cursorPolicy: ProviderCompatibilityPolicy = { + driver: cursor, + t3CodeRange: policy.t3CodeRange, + ranges: [ + { range: "<2026.05.09", status: "unsupported" }, + { range: ">=2026.05.09", status: "supported" }, + ], + }; + for (const [version, expected] of [ + ["2026.05.08-a1b2c3d", "unsupported"], + ["2026.05.09-a1b2c3d", "supported"], + ["2026.09.22-f2b0fcd", "supported"], + ["2026.05.09", "supported"], + ["2026.05.09-beta.1", "unknown"], + ] as const) { + assert.strictEqual( + resolveProviderCompatibility([cursorPolicy], cursor, version)?.status, + expected, + ); + } + assert.strictEqual( + resolveProviderCompatibility([policy], driver, "2.0.0-a1b2c3d")?.status, + "unknown", + ); + }); + + it("recognizes Antigravity semver release tags while keeping dated candidates unknown", () => { + const antigravity = ProviderDriverKind.make("antigravity"); + const taggedPolicy = { + ...policy, + driver: antigravity, + ranges: [{ range: "=2.0.0", status: "supported" as const }], + }; + for (const [version, expected] of [ + ["agy_acp_server_2.0.0", "supported"], + ["2.0.0", "supported"], + ["agy_acp_server_2.0.1", "unknown"], + ["agy_acp_server_2.0.0-beta.1", "unknown"], + ["agy_acp_server_20260818_01_RC01", "unknown"], + ] as const) { + assert.strictEqual( + resolveProviderCompatibility([taggedPolicy], antigravity, version)?.status, + expected, + ); + } + }); + it("classifies boundaries and treats unlisted versions and release tags as unknown", () => { for (const [version, expected] of [ ["0.9.9", "broken"], diff --git a/apps/server/src/provider/providerCompatibility.ts b/apps/server/src/provider/providerCompatibility.ts index 11aedf8dca0db82cc12210492de0bed0b83b89f4..b4ce66f876038082279515390ae69be1db85250b 100644 --- a/apps/server/src/provider/providerCompatibility.ts +++ b/apps/server/src/provider/providerCompatibility.ts @@ -10,7 +10,7 @@ import * as Schema from "effect/Schema"; import packageJson from "../../package.json" with { type: "json" }; // Deliberately uses the shared CLI gate syntax: comparator groups joined by ||. -// Release tags and prereleases remain unknown instead of matching stable ranges. +// Prereleases and unrecognized release tags remain unknown. const StableVersion = TrimmedNonEmptyString.pipe( Schema.check(Schema.makeFilter((value) => /^\d+\.\d+\.\d+$/.test(value))), ); @@ -66,7 +66,15 @@ export function resolveProviderCompatibility( (entry) => entry.driver === driver && satisfiesSemverRange(t3CodeVersion, entry.t3CodeRange), ); if (!policy) return undefined; - const stable = version?.replace(/^v/, ""); + const unprefixed = version?.replace(/^v/, ""); + // Cursor appends a build hash to its date; Google's ACP runtime uses a release prefix. + // Strip only these driver-specific forms, keeping semver prereleases unknown. + const stable = + driver === "cursor" + ? unprefixed?.replace(/^(\d{4}\.\d{2}\.\d{2})-[a-f0-9]+$/, "$1") + : driver === "antigravity" + ? unprefixed?.replace(/^agy_acp_server_(\d+\.\d+\.\d+)$/, "$1") + : unprefixed; const status = stable && /^\d+\.\d+\.\d+$/.test(stable) ? (policy.ranges.find((entry) => satisfiesSemverRange(stable, entry.range))?.status ?? diff --git a/apps/server/src/provider/providerMaintenanceRunner.test.ts b/apps/server/src/provider/providerMaintenanceRunner.test.ts index 2da7c9183365768bb32f64a98a424236e5f130bb..998c301cb1fbe8bb799b6b4f5302c83254177464 100644 --- a/apps/server/src/provider/providerMaintenanceRunner.test.ts +++ b/apps/server/src/provider/providerMaintenanceRunner.test.ts @@ -211,7 +211,13 @@ function makeRegistry( const makeTestRunner = ( registry: ProviderRegistryShape, - manifest = ModelManifest.BUNDLED_MODEL_MANIFEST, + // Generic updater fixtures use synthetic versions. Keep their compatibility + // unknown so real harness minimums do not bypass the command under test. + manifest: ModelManifest.ModelManifestData = { + version: 1, + currentModels: {}, + compatibility: [{ driver: CODEX_DRIVER, t3CodeRange: ">=0.0.42", ranges: [] }], + }, ) => Effect.service(ProviderMaintenanceRunner.ProviderMaintenanceRunner).pipe( Effect.provide( -- 2.54.0