When applyCompatibilityAdvisory overwrites a provider's status/message with compatibility information, the original values (e.g. from a genuine probe failure) were permanently lost. When a subsequent advisory removal or replacement occurred, removeExistingCompatibilityAdvisory would reset status to 'ready' instead of the original pre-advisory status. Fix by recording preAdvisoryStatus and preAdvisoryMessage in the advisory object when it is applied. removeExistingCompatibilityAdvisory now restores these saved values instead of unconditionally resetting to 'ready'/'disabled'. For old advisories without these fields, the fallback behavior is unchanged.
Unified · Split
| @@ -327,6 +327,52 @@ describe("provider compatibility", () => { · 326 unmodified lines | ||
| 327 | 327 | }); |
| 328 | 328 | }); |
| 329 | 329 | |
| 330 | + it.effect("preserves genuine probe errors when the remote map relaxes a bundled advisory", () => { | |
| 331 | + const bundledMessage = | |
| 332 | + "This provider harness version 0.130.0 is known to be incompatible with this T3 Code release. Use 0.129.0."; | |
| 333 | + const probeErrorMessage = "CLI health-check failed: process exited with code 1"; | |
| 334 | + const remoteDocument = { | |
| 335 | + version: 1, | |
| 336 | + policies: [ | |
| 337 | + { | |
| 338 | + t3CodeRange: ">=0.0.0", | |
| 339 | + driver: "codex", | |
| 340 | + recommendedRange: ">=0.130.0", | |
| 341 | + recommendedVersion: "0.130.0", | |
| 342 | + ranges: [{ status: "supported", range: ">=0.130.0" }], | |
| 343 | + }, | |
| 344 | + ], | |
| 345 | + }; | |
| 346 | + | |
| 347 | + return Effect.gen(function* () { | |
| 348 | + const enriched = yield* enrichProviderSnapshotWithCompatibilityAdvisory({ | |
| 349 | + ...baseProvider, | |
| 350 | + status: "error", | |
| 351 | + message: bundledMessage, | |
| 352 | + compatibilityAdvisory: { | |
| 353 | + status: "broken", | |
| 354 | + severity: "error", | |
| 355 | + currentVersion: "0.130.0", | |
| 356 | + message: bundledMessage, | |
| 357 | + recommendedRange: "<0.130.0", | |
| 358 | + recommendedVersion: "0.129.0", | |
| 359 | + ranges: [{ status: "broken", range: ">=0.130.0" }], | |
| 360 | + preAdvisoryStatus: "error", | |
| 361 | + preAdvisoryMessage: probeErrorMessage, | |
| 362 | + }, | |
| 363 | + }).pipe( | |
| 364 | + Effect.provideService( | |
| 365 | + HttpClient.HttpClient, | |
| 366 | + jsonHttpClient(() => ({ payload: remoteDocument })), | |
| 367 | + ), | |
| 368 | + ); | |
| 369 | + | |
| 370 | + expect(enriched.status).toBe("error"); | |
| 371 | + expect(enriched.message).toBe(probeErrorMessage); | |
| 372 | + expect(enriched.compatibilityAdvisory).toMatchObject({ status: "supported" }); | |
| 373 | + }); | |
| 374 | + }); | |
| 375 | + | |
| 330 | 376 | it.effect("falls back to the bundled map when the remote compatibility fetch fails", () => |
| 331 | 377 | Effect.gen(function* () { |
| 332 | 378 | const enriched = yield* enrichProviderSnapshotWithCompatibilityAdvisory({ |
| @@ -325,13 +325,19 @@ function applyCompatibilityAdvisory<Snapshot extends ProviderCompatibilitySnapsh · 324 unmodified lines | ||
| 325 | 325 | ? "warning" |
| 326 | 326 | : baseSnapshot.status; |
| 327 | 327 | |
| 328 | + const advisoryWithPreState: ServerProviderCompatibilityAdvisory = { | |
| 329 | + ...compatibilityAdvisory, | |
| 330 | + preAdvisoryStatus: baseSnapshot.status, | |
| 331 | + ...(baseSnapshot.message ? { preAdvisoryMessage: baseSnapshot.message } : {}), | |
| 332 | + }; | |
| 333 | + | |
| 328 | 334 | return { |
| 329 | 335 | ...baseSnapshot, |
| 330 | 336 | status, |
| 331 | 337 | ...(compatibilityMessage || baseSnapshot.message |
| 332 | 338 | ? { message: compatibilityMessage ?? baseSnapshot.message } |
| 333 | 339 | : {}), |
| 334 | − compatibilityAdvisory, | |
| 340 | + compatibilityAdvisory: advisoryWithPreState, | |
| 335 | 341 | } as Snapshot; |
| 336 | 342 | } |
| 337 | 343 | |
| @@ -349,11 +355,19 @@ function removeExistingCompatibilityAdvisory<Snapshot extends ProviderCompatibil · 11 unmodified lines | ||
| 349 | 355 | : undefined; |
| 350 | 356 | if (compatibilityMessage && baseSnapshot.message === compatibilityMessage) { |
| 351 | 357 | const { message: _message, ...snapshotWithoutCompatibilityMessage } = baseSnapshot; |
| 358 | + const restoredStatus = | |
| 359 | + existingCompatibilityAdvisory.preAdvisoryStatus ?? (snapshot.enabled ? "ready" : "disabled"); | |
| 352 | 360 | return { |
| 353 | 361 | ...snapshotWithoutCompatibilityMessage, |
| 354 | − status: snapshot.enabled ? "ready" : "disabled", | |
| 362 | + status: restoredStatus, | |
| 363 | + ...(existingCompatibilityAdvisory.preAdvisoryMessage | |
| 364 | + ? { message: existingCompatibilityAdvisory.preAdvisoryMessage } | |
| 365 | + : {}), | |
| 355 | 366 | } as Snapshot; |
| 356 | 367 | } |
| 368 | + if (existingCompatibilityAdvisory.preAdvisoryStatus !== undefined) { | |
| 369 | + return { ...baseSnapshot, status: existingCompatibilityAdvisory.preAdvisoryStatus } as Snapshot; | |
| 370 | + } | |
| 357 | 371 | return baseSnapshot as Snapshot; |
| 358 | 372 | } |
| 359 | 373 | |
| @@ -163,6 +163,8 @@ export const ServerProviderCompatibilityAdvisory = Schema.Struct({ · 162 unmodified lines | ||
| 163 | 163 | ranges: Schema.Array(ServerProviderCompatibilityRange).pipe( |
| 164 | 164 | Schema.withDecodingDefault(Effect.succeed([])), |
| 165 | 165 | ), |
| 166 | + preAdvisoryStatus: Schema.optionalKey(ServerProviderState), | |
| 167 | + preAdvisoryMessage: Schema.optionalKey(TrimmedNonEmptyString), | |
| 166 | 168 | }); |
| 167 | 169 | export type ServerProviderCompatibilityAdvisory = typeof ServerProviderCompatibilityAdvisory.Type; |
| 168 | 170 | |