Skip to content

Commit fad51d1

Browse files
committed
fix: preserve pre-advisory provider status during compatibility advisory removal
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.
1 parent 5f9dc67 commit fad51d1

3 files changed

Lines changed: 64 additions & 2 deletions

File tree

apps/server/src/provider/providerCompatibility.test.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,52 @@ describe("provider compatibility", () => {
327327
});
328328
});
329329

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+
330376
it.effect("falls back to the bundled map when the remote compatibility fetch fails", () =>
331377
Effect.gen(function* () {
332378
const enriched = yield* enrichProviderSnapshotWithCompatibilityAdvisory({

apps/server/src/provider/providerCompatibility.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -325,13 +325,19 @@ function applyCompatibilityAdvisory<Snapshot extends ProviderCompatibilitySnapsh
325325
? "warning"
326326
: baseSnapshot.status;
327327

328+
const advisoryWithPreState: ServerProviderCompatibilityAdvisory = {
329+
...compatibilityAdvisory,
330+
preAdvisoryStatus: baseSnapshot.status,
331+
...(baseSnapshot.message ? { preAdvisoryMessage: baseSnapshot.message } : {}),
332+
};
333+
328334
return {
329335
...baseSnapshot,
330336
status,
331337
...(compatibilityMessage || baseSnapshot.message
332338
? { message: compatibilityMessage ?? baseSnapshot.message }
333339
: {}),
334-
compatibilityAdvisory,
340+
compatibilityAdvisory: advisoryWithPreState,
335341
} as Snapshot;
336342
}
337343

@@ -349,11 +355,19 @@ function removeExistingCompatibilityAdvisory<Snapshot extends ProviderCompatibil
349355
: undefined;
350356
if (compatibilityMessage && baseSnapshot.message === compatibilityMessage) {
351357
const { message: _message, ...snapshotWithoutCompatibilityMessage } = baseSnapshot;
358+
const restoredStatus =
359+
existingCompatibilityAdvisory.preAdvisoryStatus ?? (snapshot.enabled ? "ready" : "disabled");
352360
return {
353361
...snapshotWithoutCompatibilityMessage,
354-
status: snapshot.enabled ? "ready" : "disabled",
362+
status: restoredStatus,
363+
...(existingCompatibilityAdvisory.preAdvisoryMessage
364+
? { message: existingCompatibilityAdvisory.preAdvisoryMessage }
365+
: {}),
355366
} as Snapshot;
356367
}
368+
if (existingCompatibilityAdvisory.preAdvisoryStatus !== undefined) {
369+
return { ...baseSnapshot, status: existingCompatibilityAdvisory.preAdvisoryStatus } as Snapshot;
370+
}
357371
return baseSnapshot as Snapshot;
358372
}
359373

packages/contracts/src/server.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,8 @@ export const ServerProviderCompatibilityAdvisory = Schema.Struct({
163163
ranges: Schema.Array(ServerProviderCompatibilityRange).pipe(
164164
Schema.withDecodingDefault(Effect.succeed([])),
165165
),
166+
preAdvisoryStatus: Schema.optionalKey(ServerProviderState),
167+
preAdvisoryMessage: Schema.optionalKey(TrimmedNonEmptyString),
166168
});
167169
export type ServerProviderCompatibilityAdvisory = typeof ServerProviderCompatibilityAdvisory.Type;
168170

0 commit comments

Comments
 (0)