[world-vercel] Add /run-id sub-export with tagged ULID encode/decode#1978
Conversation
Encodes a tag bit, 5-bit version, and 6-bit Vercel region ID into a ULID-shaped string used for workflow run IDs. Tagged values remain valid 26-char Crockford-Base32 ULIDs so they still sort and round-trip through any system that accepts ULIDs.
🦋 Changeset detectedLatest commit: 9c28bf4 The changes in this PR will be included in the next version bump. This PR includes changesets to release 17 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
🧪 E2E Test Results✅ All tests passed Summary
Details by Category✅ ▲ Vercel Production
✅ 💻 Local Development
✅ 📦 Local Production
✅ 🐘 Local Postgres
✅ 🪟 Windows
✅ 📋 Other
|
📊 Benchmark Results
workflow with no steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro workflow with 1 step💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro workflow with 10 sequential steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro workflow with 25 sequential steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro workflow with 50 sequential steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro Promise.all with 10 concurrent steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro Promise.all with 25 concurrent steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro Promise.all with 50 concurrent steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro Promise.race with 10 concurrent steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro Promise.race with 25 concurrent steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro Promise.race with 50 concurrent steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro workflow with 10 sequential data payload steps (10KB)💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro workflow with 25 sequential data payload steps (10KB)💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro workflow with 50 sequential data payload steps (10KB)💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro workflow with 10 concurrent data payload steps (10KB)💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro workflow with 25 concurrent data payload steps (10KB)💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro workflow with 50 concurrent data payload steps (10KB)💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro Stream Benchmarks (includes TTFB metrics)workflow with stream💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro stream pipeline with 5 transform steps (1MB)💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro 10 parallel streams (1MB each)💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro fan-out fan-in 10 streams (1MB each)💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro SummaryFastest Framework by WorldWinner determined by most benchmark wins
Fastest World by FrameworkWinner determined by most benchmark wins
Column Definitions
Worlds:
❌ Some benchmark jobs failed:
Check the workflow run for details. |
Add exact-string expectations for encoded outputs at known inputs, covering the default region/version pair, numeric region IDs, version overrides, boundary values (all-zero, all-max), the dirty-input overwrite case, and the lexicographic-order checks. Also adds an explicit byte-array expectation for the canonical ULID-spec example string and an additional first-char-range coverage test for isTagged.
There was a problem hiding this comment.
Pull request overview
This PR introduces a new @workflow/world-vercel/run-id sub-export that can encode/decode tagged ULID-shaped workflow run IDs, embedding a tag bit, a 5-bit version, and a 6-bit Vercel region ID while preserving ULID sortability.
Changes:
- Add a region table (
REGION_IDS) and helpers for mapping between region codes and 6-bit region IDs. - Implement a Crockford-Base32 ↔ 16-byte codec and public
encode/decode/isTaggedAPI for tagged ULIDs. - Add tests for codec correctness and encode/decode behavior; expose the new subpath export via
package.jsonand add a changeset.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/world-vercel/src/run-id/regions.ts | Adds stable region↔ID mapping and lookup helpers used by tagged run IDs. |
| packages/world-vercel/src/run-id/index.ts | Public API for encoding/decoding tagged ULIDs + re-exports/constants. |
| packages/world-vercel/src/run-id/index.test.ts | Tests for encode/decode semantics, validation, and ordering properties. |
| packages/world-vercel/src/run-id/codec.ts | Implements ULID base32 packing/unpacking and tag-bit detection helper. |
| packages/world-vercel/src/run-id/codec.test.ts | Tests for codec round-trips, validation, and tag-bit detection. |
| packages/world-vercel/package.json | Exposes ./run-id as a public package sub-export. |
| .changeset/tagged-run-id.md | Declares a minor release for the new sub-export/API surface. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
karthikscale3
left a comment
There was a problem hiding this comment.
Reviewed for blockers/regressions — none found. Change is purely additive (new sub-export, nothing in the repo consumes it yet). Three non-blocking nits below.
Address review feedback on #1978: 1. **Metadata at top of randomness, not bottom.** Place `regionId` (6 bits) in the high bits of byte[6] and `version` (5 bits) straddling bytes 6 and 7, leaving the bottom 69 bits of randomness untouched by `encode`. This means a `monotonicFactory()`-style ULID generator's intra-millisecond bottom-bit increments survive encoding intact, so consecutive `encode(ulid(), region, { version })` calls with the same metadata produce strictly increasing strings. Previously the metadata sat in the bottom 11 bits — exactly the bits the monotonic factory uses — causing same-ms collisions/inversions. 2. **DecodedRunId is now a discriminated union.** When `tagged: false`, the `regionId`, `version`, and `region` fields are typed as `null` instead of being populated with garbage bits from arbitrary ULIDs. This forces callers to discriminate on `tagged` before reading metadata. 3. **regionIdFor: keep runtime backstop, mark as ignored for coverage.** The unreachable-in-TS branch stays as a defensive runtime check for callers crossing a JS/TS boundary; an istanbul/c8 ignore comment keeps coverage tools quiet. Doc strings and tests updated accordingly. The new layout adds a test verifying that a sequence of incrementing-bottom-bit ULIDs (simulating `monotonicFactory()`) round-trips through `encode` as a strictly increasing sequence. 108/108 world-vercel tests pass; typecheck clean.
|
For anyone reviewing the new bit layout, here's what tagged ULIDs actually look like in practice (all outputs from the current branch, Plain vs. taggedThe last 14 characters of the tagged ULID are byte-identical to the source — that's the bottom 69 bits of randomness, untouched by Different regions, same source ULIDThe metadata window lights up around chars 10–12: Same region (iad1), different versionsDecoded structureencode('01ARZ3NDEKTSV4RRFFQ69G5FAV', 'fra1')
// → '41ARZ3NDEK50V4RRFFQ69G5FAV'
decode('41ARZ3NDEK50V4RRFFQ69G5FAV')
// → {
// tagged: true,
// ulid: '01ARZ3NDEK50V4RRFFQ69G5FAV', // tag bit cleared
// version: 1,
// regionId: 10,
// region: 'fra1'
// }Intra-millisecond monotonicity (the bit that motivated the layout change)Five successive This is the property the previous (bottom-bit) layout was breaking; locked in by the |
|
Backport PR opened against |
Summary
Adds a new sub-export
@workflow/world-vercel/run-idexposingencodeanddecodehelpers that produce ULID-shaped workflow run IDs carrying:4..7.1;0is reserved as a "no metadata" sentinel.iad1,fra1,sfo1).Tagged values remain valid 26-char Crockford-Base32 ULIDs, so they continue to sort lexicographically and flow through any system that accepts ULIDs.
Bit layout
Randomness: 80 bits → 69 bits preserved (~5.9 × 10²⁰ values per ms).
Region table
Covers the 21 currently-deployed Vercel compute regions plus
hel1andzrh1, which are reserved for future rollout.0=unknownsentinel.IDs are stable and append-only — never reorder or reuse, since they're encoded on the wire in every run ID emitted.
API
decodeclears only the tag bit on the returnedulidfield — the 11 metadata bits remain in place so the encoded info is recoverable from the "cleared" value too.Verification
pnpm vitest run src/run-id/→ 34/34 pass (includes exact-string assertions for all encoded outputs)pnpm typecheck→ cleanpnpm build→ emitsdist/run-id/{index,codec,regions}.{js,d.ts,...}Files
packages/world-vercel/src/run-id/regions.ts— region table +lookupRegion/regionIdForpackages/world-vercel/src/run-id/codec.ts— Crockford-Base32 ↔ 16-byte bit-packingpackages/world-vercel/src/run-id/index.ts— publicencode/decode/isTaggedAPIpackages/world-vercel/src/run-id/codec.test.ts(13 tests) +index.test.ts(21 tests)packages/world-vercel/package.json—./run-idadded toexports.changeset/tagged-run-id.md— minor bump for@workflow/world-vercel