From bcdbc197cb523c2a5d6cd41630e870526f003f59 Mon Sep 17 00:00:00 2001 From: Luiz Ferraz Date: Wed, 23 Sep 2026 01:23:07 +0000 Subject: [PATCH] fix(server): prefer the newest model manifest edit across sources - Bundle and cache now outrank an older remote manifest by updatedAt - Ship Claude Opus 5.5 as current and demote Opus 5 to legacy - Cover the stale-remote case with a live test --- .../server/src/provider/ModelManifest.test.ts | 16 +++++++++++++++ apps/server/src/provider/ModelManifest.ts | 6 +++--- apps/server/src/provider/model-manifest.json | 20 +++++++++++++++---- docs/internals/model-manifest.md | 7 ++++--- 4 files changed, 39 insertions(+), 10 deletions(-) diff --git a/apps/server/src/provider/ModelManifest.test.ts b/apps/server/src/provider/ModelManifest.test.ts index 0a2ca28feb73..6294841ae673 100644 --- a/apps/server/src/provider/ModelManifest.test.ts +++ b/apps/server/src/provider/ModelManifest.test.ts @@ -378,6 +378,22 @@ describe("ModelManifest service", () => { ), ); + it.live("keeps a newer bundled catalog when the remote manifest is an older edit", () => + Effect.gen(function* () { + const service = yield* make; + assert.deepStrictEqual(yield* service.refresh, BUNDLED_MODEL_MANIFEST); + assert.deepStrictEqual(yield* service.current, BUNDLED_MODEL_MANIFEST); + }).pipe( + Effect.scoped, + Effect.provide( + serviceLayers({ + prefix: "model-manifest-stale-remote-test", + response: () => Response.json({ ...REMOTE_MANIFEST, updatedAt: "2000-01-01T00:00:00Z" }), + }), + ), + ), + ); + it.effect("preserves the last-good remote cache when later payloads are invalid", () => { let responseIndex = 0; const responses = [REMOTE_CLAUDE_MANIFEST, ...INVALID_REMOTE_MANIFESTS]; diff --git a/apps/server/src/provider/ModelManifest.ts b/apps/server/src/provider/ModelManifest.ts index f5ad15dd8586..6fffb3631d09 100644 --- a/apps/server/src/provider/ModelManifest.ts +++ b/apps/server/src/provider/ModelManifest.ts @@ -4,9 +4,8 @@ * * Provider catalogs and legacy classification live in `model-manifest.json`. * The bundled copy ships with every release; at runtime the service refreshes - * it from the same file on `main`. Preference order is remote, then the last - * successful on-disk copy, then the bundle. A failed fetch never fails a - * provider check. + * it from the same file on `main`. The newest edit among remote, on-disk, + * and bundled copies wins. A failed fetch never fails a provider check. * * Providers with authoritative discovery can use only the classification * overlay. Providers with static catalogs can resolve presentation and @@ -398,6 +397,7 @@ export const make = Effect.gen(function* () { Effect.catchCause(() => Effect.succeed(null)), ); if (fetched === null) return manifest; + if (manifestUpdatedAtMs(fetched) < manifestUpdatedAtMs(manifest)) return manifest; manifest = fetched; fetchedAtMs = now; diff --git a/apps/server/src/provider/model-manifest.json b/apps/server/src/provider/model-manifest.json index efc22ad0a3d0..7e9369c5ab35 100644 --- a/apps/server/src/provider/model-manifest.json +++ b/apps/server/src/provider/model-manifest.json @@ -1,6 +1,6 @@ { "version": 1, - "updatedAt": "2026-09-12T00:41:55Z", + "updatedAt": "2026-09-23T00:00:00Z", "currentModels": { "codex": [ "gpt-6-astra", @@ -16,7 +16,7 @@ "providers": { "claudeAgent": { "defaults": { - "chat": "claude-fable-5-1" + "chat": "claude-opus-5-5" }, "profiles": { "fable-5": { @@ -577,11 +577,23 @@ } } }, + { + "slug": "claude-opus-5-5", + "name": "Claude Opus 5.5", + "aliases": ["opus", "opus-5.5", "claude-opus-5.5"], + "status": "current", + "profile": "opus-5", + "adapter": { + "claudeCode": { + "minVersion": "2.1.280" + } + } + }, { "slug": "claude-opus-5", "name": "Claude Opus 5", - "aliases": ["opus", "opus-5", "claude-opus-5.0", "claude-opus-5-0"], - "status": "current", + "aliases": ["opus-5", "claude-opus-5.0", "claude-opus-5-0"], + "status": "legacy", "profile": "opus-5", "adapter": { "claudeCode": { diff --git a/docs/internals/model-manifest.md b/docs/internals/model-manifest.md index 3892a1df7277..ae1ff9c4f8c9 100644 --- a/docs/internals/model-manifest.md +++ b/docs/internals/model-manifest.md @@ -6,9 +6,10 @@ releases. Failed fetches or invalid data preserve the last usable manifest. Remote data must pass both catalog-reference validation and the owning provider's adapter validation before replacing the cache. -A newer bundle outranks the cached remote manifest by `updatedAt`, so a release can -correct model data before the next successful fetch. Bump `updatedAt` whenever the -file changes. Fetch time cannot establish which copy contains the newer edit. +A newer bundle outranks cached and fetched remote manifests by `updatedAt`, so a +release can correct model data even while the remote copy lags. Bump `updatedAt` +whenever the file changes. Fetch time cannot establish which copy contains the +newer edit. Generic catalog data describes presentation and capabilities. Each provider owns its adapter schema and dispatch mappings. Claude uses the manifest for its entire