fix: address Copilot round 5 on the list-salvage path (#1909)

Both findings are the same shape: the fallback accepting what the strict path
would not.

- `ModernResultEnvelopeSchema` typed `ttlMs` as any number, where the codec
  requires a non-negative integer. `ttlMs: -1` (or `0.5`) plus one bad entry
  would salvage, reporting the entry and hiding the envelope violation. Tightened
  to `z.int().min(0)`.

- The item schema was the SDK's era-NEUTRAL `ToolSchema`, and the negotiated
  era's wire schema is stricter in ways the SDK does not export. Verified
  against SDK 2.0.0 on a real connection: on legacy, a tool with
  `outputSchema: { type: "array" }` is refused by the strict aggregate but
  accepted by the neutral schema; on modern, `execution` is STRIPPED by the
  strict path but kept by the neutral one. Either way a salvaged list could show
  — and let the user call — a tool the connection's era says isn't valid.

  `toolItemSchemaForEra` closes both. Replicating rules that live in the SDK's
  internals is a real cost; `listSalvage-era.test.ts` is what makes it safe to
  pay, pinning each rule to the strict path's OBSERVED behavior so a divergence
  that closes or widens fails loudly instead of rotting.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDdo1rNRVQnVRRdsqSrvG3
Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
cliffhall 2026-08-15 18:46:54 -04:00
commit 71cbe27d83

View file

@ -132,6 +132,20 @@ describe("ModernResultEnvelopeSchema", () => {
).toBe(true);
});
it("requires ttlMs to be a non-negative integer, as the codec does", () => {
// A looser `z.number()` would accept `-1` / `0.5`, so a response with a bad
// envelope AND one bad entry would salvage and hide the envelope violation.
for (const ttlMs of [-1, 0.5]) {
expect(
ModernResultEnvelopeSchema.safeParse({
resultType: "complete",
ttlMs,
cacheScope: "public",
}).success,
).toBe(false);
}
});
it("rejects a modern result missing the envelope the codec would enforce", () => {
// The raw-wire salvage path goes around that codec, so a response missing
// its cache hints AND carrying a bad entry must not salvage.