From 70508f0fa28078074431a52155acef8679bbf47b Mon Sep 17 00:00:00 2001 From: Nathan Rajlich Date: Tue, 16 Jun 2026 17:42:47 -0700 Subject: [PATCH] fix(core): bump payload-compression cutoff to 5.0.0-beta.18 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The gzip/zstd FORMAT_VERSION_TABLE entries were gated on 5.0.0-beta.16, but beta.16 and beta.17 were both published (2026-06-15) before the compression PR (#2394) merged (2026-06-16) — neither contains the compression read path. The next published version is beta.18 (pending Version Packages #2451), which is the first that can decode these payloads. With the cutoff at beta.16, getRunCapabilities() reported beta.16/.17 targets as compression-capable, so a cross-deployment start()/resumeHook() (or a resilient-start probe resolving to such a target) would write zstd/gzip payloads the target cannot decode — silent replay corruption, exactly the TODO(release) hazard noted on those lines. Bump both entries (and the doc comments) to beta.18 and extend the capability test to assert beta.16/beta.17 are treated as incapable. --- .changeset/fix-compression-cutoff-beta18.md | 5 ++++ packages/core/src/capabilities.ts | 8 +++--- .../src/serialization/compression.test.ts | 25 +++++++++++++++---- 3 files changed, 29 insertions(+), 9 deletions(-) create mode 100644 .changeset/fix-compression-cutoff-beta18.md diff --git a/.changeset/fix-compression-cutoff-beta18.md b/.changeset/fix-compression-cutoff-beta18.md new file mode 100644 index 0000000000..06ec1fbd15 --- /dev/null +++ b/.changeset/fix-compression-cutoff-beta18.md @@ -0,0 +1,5 @@ +--- +'@workflow/core': patch +--- + +Fix the payload-compression capability cutoff: gzip/zstd are gated on `5.0.0-beta.18` (the first published version containing the compression read path) instead of `5.0.0-beta.16`. The previous cutoff would let a producer write compressed payloads to a beta.16/beta.17 target that cannot decode them. diff --git a/packages/core/src/capabilities.ts b/packages/core/src/capabilities.ts index a1d7844bea..4ef6b5a78e 100644 --- a/packages/core/src/capabilities.ts +++ b/packages/core/src/capabilities.ts @@ -28,8 +28,8 @@ * Commit: 7618ac36 "Wire AES-GCM encryption into serialization layer (#1251)" * https://github.com/vercel/workflow/commit/7618ac36 * - `framedByteStreams` (wire-level chunk framing for byte streams): added in `5.0.0-beta.15` - * - `gzip` (gzip payload compression): added in `5.0.0-beta.16` - * - `zstd` (zstd payload compression, preferred codec): added in `5.0.0-beta.16` + * - `gzip` (gzip payload compression): added in `5.0.0-beta.18` + * - `zstd` (zstd payload compression, preferred codec): added in `5.0.0-beta.18` * alongside gzip — they co-ship, so any run that can read one can read both. */ @@ -77,8 +77,8 @@ const FORMAT_VERSION_TABLE: ReadonlyArray<{ // compressed payloads to consumers that cannot decompress them; too-high // merely delays the optimization (safe). gzip and zstd ship together, so // they share a min version — a run that can read one can read both. - { format: SerializationFormat.GZIP, minVersion: '5.0.0-beta.16' }, - { format: SerializationFormat.ZSTD, minVersion: '5.0.0-beta.16' }, + { format: SerializationFormat.GZIP, minVersion: '5.0.0-beta.18' }, + { format: SerializationFormat.ZSTD, minVersion: '5.0.0-beta.18' }, // Future entries: // { format: SerializationFormat.CBOR, minVersion: '5.x.y' }, // { format: SerializationFormat.ENCRYPTED_V2, minVersion: '5.x.y' }, diff --git a/packages/core/src/serialization/compression.test.ts b/packages/core/src/serialization/compression.test.ts index 2d42ffa6c7..355183b7ef 100644 --- a/packages/core/src/serialization/compression.test.ts +++ b/packages/core/src/serialization/compression.test.ts @@ -441,14 +441,29 @@ describe('run capabilities for compression codecs', () => { SerializationFormat.GZIP, SerializationFormat.ZSTD, ] as const) { - it(`supports ${fmt} for core versions >= 5.0.0-beta.16`, () => { - expect( - getRunCapabilities('5.0.0-beta.16').supportedFormats.has(fmt) - ).toBe(true); + it(`supports ${fmt} for core versions >= 5.0.0-beta.18`, () => { + for (const version of [ + '5.0.0-beta.18', + '5.0.0-beta.19', + '5.0.0', + '6.0.0', + ]) + expect(getRunCapabilities(version).supportedFormats.has(fmt)).toBe( + true + ); }); it(`does not support ${fmt} for older core versions`, () => { - for (const version of ['5.0.0-beta.15', '4.2.1', '4.0.0']) { + // beta.16 and beta.17 were published before compression shipped, so a + // producer must treat those targets as compression-incapable — writing + // a codec they cannot decode is the silent-corruption failure mode. + for (const version of [ + '5.0.0-beta.17', + '5.0.0-beta.16', + '5.0.0-beta.15', + '4.2.1', + '4.0.0', + ]) { expect(getRunCapabilities(version).supportedFormats.has(fmt)).toBe( false );