Skip to content

feat(upgrade): add binary delta patching via TRDIFF10/bsdiff#327

Merged
BYK merged 9 commits intomainfrom
feat/delta-upgrades
Mar 4, 2026
Merged

feat(upgrade): add binary delta patching via TRDIFF10/bsdiff#327
BYK merged 9 commits intomainfrom
feat/delta-upgrades

Conversation

@BYK
Copy link
Copy Markdown
Member

@BYK BYK commented Mar 3, 2026

Summary

Adds binary delta patching to sentry upgrade, reducing download sizes by 300–600x for consecutive releases (~50 KB patch vs ~29 MB full .gz).

  • Stable upgrades: fetches sentry-<platform>.patch from GitHub Releases, chains patches across versions until the cumulative size hits 60% of the full download, then falls back
  • Nightly upgrades: resolves patches via GHCR :patch-<version> manifests with from-version + per-platform sha256-<platform> annotations
  • Fallback: any error (missing patch, SHA-256 mismatch, chain too large) transparently falls back to full download

Changes

  • src/lib/bspatch.ts — streaming TRDIFF10 patcher using Bun.mmap() (zero heap for old file), DecompressionStream('zstd'), and Bun.CryptoHasher for SHA-256 verification
  • src/lib/delta-upgrade.ts — patch discovery and chain resolution for stable and nightly channels
  • src/lib/ghcr.ts — adds fetchManifest(), listTags(), downloadLayerBlob()
  • src/lib/upgrade.ts — attempts delta path first in downloadBinaryToTemp()
  • .github/workflows/ci.yml — installs zig-bsdiff v0.1.19, creates immutable :nightly-<version> tags, generates and pushes :patch-<version> manifests
  • .github/workflows/cleanup-nightlies.yml — weekly prune keeping last 30 versioned nightly tags

Testing

  • 18 unit tests + 7 property-based tests for bspatch.ts (offtin, parsePatchHeader, applyPatch with real TRDIFF10 fixtures)
  • 6 unit tests for delta-upgrade.ts
  • 1 E2E test that downloads real 0.12.0 and 0.13.0 binaries, generates a patch with zig-bsdiff, applies it, and verifies byte-identical output + SHA-256

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 3, 2026

Semver Impact of This PR

🟡 Minor (new features)

📋 Changelog Preview

This is how your changes will appear in the changelog.
Entries from this PR are highlighted with a left border (blockquote style).


New Features ✨

Trace

Other

  • (api) Add --data/-d flag and auto-detect JSON body in fields by BYK in #320
  • (formatters) Render all terminal output as markdown by BYK in #297
  • (issue-list) Global limit with fair distribution, compound cursor, and richer progress by BYK in #306
  • (project) Add project create command by betegon in #237
  • (upgrade) Add binary delta patching via TRDIFF10/bsdiff by BYK in #327

Bug Fixes 🐛

Api

  • Use numeric project ID to avoid "not actively selected" error by betegon in #312
  • Use limit param for issues endpoint page size by BYK in #309
  • Auto-correct ':' to '=' in --field values with a warning by BYK in #302

Formatters

  • Expand streaming table to fill terminal width by betegon in #314
  • Fix HTML entities and escaped underscores in table output by betegon in #313

Setup

  • Suppress agent skills and welcome messages on upgrade by BYK in #328
  • Suppress shell completion messages on upgrade by BYK in #326

Other

  • (ci) Generate JUnit XML to silence codecov-action warnings by BYK in #300
  • (nightly) Push to GHCR from artifacts dir so layer titles are bare filenames by BYK in #301
  • (region) Resolve DSN org prefix at resolution layer by BYK in #316
  • (test) Handle 0/-0 in getComparator anti-symmetry property test by BYK in #308
  • (trace-logs) Timestamp_precise is a number, not a string by BYK in #323

Internal Changes 🔧

Api

  • Upgrade @sentry/api to 0.21.0, remove raw HTTP pagination workarounds by BYK in #321
  • Wire listIssuesPaginated through @sentry/api SDK for type safety by BYK in #310

Other

  • (craft) Add sentry-release-registry target by BYK in #325

🤖 This preview updates automatically when you update the PR.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 3, 2026

Codecov Results 📊

2567 passed | Total: 2567 | Pass Rate: 100% | Execution Time: 0ms

📊 Comparison with Base Branch

Metric Change
Total Tests 📈 +104
Passed Tests 📈 +104
Failed Tests
Skipped Tests

All tests are passing successfully.

✅ Patch coverage is 90.23%. Project has 3282 uncovered lines.
✅ Project coverage is 81.46%. Comparing base (base) to head (head).

Files with missing lines (3)
File Patch % Lines
upgrade.ts 65.01% ⚠️ 127 Missing
delta-upgrade.ts 83.43% ⚠️ 56 Missing
binary.ts 89.10% ⚠️ 17 Missing
Coverage diff
@@            Coverage Diff             @@
##          main       #PR       +/-##
==========================================
+ Coverage    81.19%    81.46%    +0.27%
==========================================
  Files          123       125        +2
  Lines        17149     17700      +551
  Branches         0         0         —
==========================================
+ Hits         13923     14418      +495
- Misses        3226      3282       +56
- Partials         0         0         —

Generated by Codecov Action

BYK added 3 commits March 3, 2026 14:31
Download TRDIFF10 patch files instead of full binaries when upgrading,
falling back to full download when no patch is available or the chain
exceeds 60% of the full download size.

- src/lib/bspatch.ts: streaming TRDIFF10 patcher using Bun.mmap(),
  DecompressionStream('zstd'), and CryptoHasher for SHA-256 verification
- src/lib/delta-upgrade.ts: patch discovery and chain resolution for
  both stable (GitHub Releases) and nightly (GHCR manifest annotations)
- src/lib/ghcr.ts: add fetchManifest(), listTags(), downloadLayerBlob()
- src/lib/upgrade.ts: attempt delta path first in downloadBinaryToTemp()
- ci.yml: install zig-bsdiff, tag :nightly-<version>, generate and push
  :patch-<version> manifests with from-version + sha256-<platform> annotations
- cleanup-nightlies.yml: weekly prune of old versioned nightly tags (keep 30)
…e case

Split chain resolution into two phases: serial metadata discovery
(lightweight manifest/release JSON) then parallel patch download via
Promise.all(). Also fix offtin() returning -0 when magnitude is 0
with sign bit set.
…ddress review

- Export pure computation functions from delta-upgrade.ts for testability
  (extractStableChain, walkNightlyChain, extractSha256, getStableTargetSha256,
   getPatchFromVersion, getPatchTargetSha256)
- Add 41 new tests for extractStableChain (single/multi-hop, edge cases,
  size threshold, MAX_CHAIN_DEPTH boundary)
- Add 10 new tests for walkNightlyChain (graph traversal, broken chains,
  threshold, depth limits)
- Add 6 tests for extractSha256 (hex parsing, prefix handling)
- Add 4 tests for getStableTargetSha256 (asset lookup, missing digest)
- Add 6 tests for getPatchFromVersion/getPatchTargetSha256 (OCI annotations)
- Add 14 new ghcr.ts tests: fetchManifest (3), listTags with pagination (8),
  downloadLayerBlob (2), plus prefix filtering
- Address PR review: replace lazy import with static import in upgrade.ts
- Total: 107 tests, 602 assertions across 4 delta-related test files
@BYK BYK force-pushed the feat/delta-upgrades branch from 7d5ca24 to ffadcd2 Compare March 3, 2026 16:42
Add comprehensive tests for async orchestration functions:
- fetchRecentReleases: success, HTTP error, network failure
- downloadStablePatch: success, 404, network failure
- resolveStableChain: single-hop, multi-hop, missing target, API failure, download failure
- buildNightlyPatchGraph: graph building, missing annotations, failed manifests, empty tags
- resolveNightlyChain: single-hop with GHCR mocks, empty graph
- applyPatchChain: SHA-256 verification, mismatch error, multi-step, permissions

Add isolated tests (mock.module for CLI_VERSION):
- resolveStableDelta: chain resolution + SHA mismatch, null on failure
- resolveNightlyDelta: null on missing .gz layer, null on empty graph
- attemptDeltaUpgrade: cross-channel rejection, chain failure, error catching

Fix orphaned extractStableChain tests that were outside their describe scope.
Export remaining async functions for direct testability.

133 tests, 643 assertions across 5 delta-upgrade test files.
@BYK BYK marked this pull request as ready for review March 3, 2026 18:20
- HIGH: Fix multi-step patch chain corruption by alternating between
  two intermediate files (A/B) instead of reading and writing the same
  file. Prevents mmap invalidation for 3+ step chains.

- MEDIUM: Use versioned nightly tag (nightly-<version>) instead of
  rolling :nightly tag for threshold calculation in resolveNightlyDelta.
  Ensures the 60% threshold reflects the target version's binary size.

- LOW: Add missing 'await' on applyPatchChain return in
  resolveNightlyDelta to preserve stack traces on rejection.

- LOW: Remove redundant chmodSync from applyPatchChain — the caller
  (downloadBinaryToTemp) already sets 0o755 for both delta and full
  download paths.

- CodeQL: Replace includes('api.github.com') with
  startsWith('https://api.github.com/') in test mocks to prevent
  incomplete URL substring sanitization.
- Move intermediate file cleanup to finally block in applyPatchChain
  to prevent file leaks when patching throws mid-chain

- Deduplicate GITHUB_RELEASES_URL: export from upgrade.ts, import in
  delta-upgrade.ts instead of defining it twice

- Deduplicate getPlatformBinaryName: move canonical definition to
  binary.ts (alongside getBinaryDownloadUrl which has identical
  platform logic), reuse in upgrade.ts and delta-upgrade.ts

- Remove redundant try-catch in tryDeltaUpgrade: attemptDeltaUpgrade
  already catches all errors and returns null
- Add missing async/await in fetchNightlyManifest to preserve stack
  traces on rejection (per project lore on bare promise returns)

- Remove redundant PREV_MANIFEST guard in CI nightly delta step that
  was checking the just-pushed :nightly tag (always succeeds). The
  real guard is the PREV_TAG check that finds the previous versioned
  nightly tag.
@BYK BYK force-pushed the feat/delta-upgrades branch from 567f3ff to a686287 Compare March 3, 2026 22:46
Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Bun.CryptoHasher.digest('hex') always returns lowercase hex, but
SHA256_DIGEST_PATTERN with the /i flag could capture uppercase from
GitHub asset digests. Normalize to lowercase to ensure comparison
always succeeds regardless of upstream casing.
@BYK BYK merged commit fe7d51d into main Mar 4, 2026
20 of 21 checks passed
@BYK BYK deleted the feat/delta-upgrades branch March 4, 2026 00:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant