Skip to content
Closed
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
140 commits
Select commit Hold shift + click to select a range
26bcd86
feat(salvage): total recovery for SSTs and blob files with in-place E…
polaz Aug 17, 2026
84c9d06
ci: pin official GitHub actions by major tag
polaz Aug 18, 2026
c38c81d
fix(columnar): clamp columnar scans to a restricted table's live suffix
polaz Aug 18, 2026
75d9042
fix(columnar): dedup MVCC versions inside singleton columnar segments
polaz Aug 18, 2026
0704859
fix(repair): derive punched blob frontiers during manifest-loss recovery
polaz Aug 18, 2026
0542f74
docs(recovery): blob frontier resolution section and edit-format rati…
polaz Aug 18, 2026
a4da5e7
feat(repair): live recovery-progress counters for repair and salvage
polaz Aug 18, 2026
1ddd0e8
style(repair): clippy cleanups for the frontier probe and progress wi…
polaz Aug 18, 2026
dedf9d7
fix(manifest): replace restriction sets wholesale during edit replay
polaz Aug 18, 2026
89dcf36
fix(repair): quarantine persistently unreadable blob files
polaz Aug 18, 2026
03ebc25
fix(repair): quarantine duplicate blob ids deterministically
polaz Aug 18, 2026
7f1c3cb
fix(repair): anchor the punch guard's zero runs on block structure
polaz Aug 18, 2026
088d57e
fix(repair): count salvaged tables after blob-dependency filtering
polaz Aug 18, 2026
8dec6bf
fix(compaction): roll back slice outputs when a restricted blob reope…
polaz Aug 18, 2026
8648763
style(repair): lint hygiene in the zero-filled-value guard test
polaz Aug 18, 2026
3fe4a2e
test(compaction): make the blob-reopen rollback check interleaving-ro…
polaz Aug 18, 2026
a7c8252
fix(repair): count recovered-table progress after all repair filtering
polaz Aug 18, 2026
909232c
fix(repair): set aside tables whose blob handles precede a punched fr…
polaz Aug 18, 2026
29fc2b2
fix(heal): probe actual holes when detaching a restricted SST for hea…
polaz Aug 18, 2026
017c99e
style(repair): drop a redundant clone in the frontier-handle test
polaz Aug 18, 2026
a0c76d3
ci(fuzz-heal): use the documented ignored-only selector
polaz Aug 19, 2026
136b7df
feat(salvage): blob-handle rewrite and compressed blob-file recovery
polaz Aug 19, 2026
e702929
fix(repair): validate blob frames, then salvage and remap instead of …
polaz Aug 19, 2026
5c01839
fix(compaction): refuse to punch a hard-linked SST prefix
polaz Aug 19, 2026
5581821
fix(scrub): sync healed SST bytes before refreshing the manifest digest
polaz Aug 19, 2026
a437aa4
fix(repair): publish a salvaged blob from a private temp, restore on …
polaz Aug 19, 2026
7485c8a
style(tests): satisfy the all-targets lint set
polaz Aug 19, 2026
7df5e71
docs(heal): record why a failed sync keeps its attestation
polaz Aug 19, 2026
2e992ac
fix(repair): block-verify every recovered SST, not only under salvage
polaz Aug 19, 2026
c925260
fix(scrub): open the reconcile sync handle with write access
polaz Aug 19, 2026
07cff77
fix(repair): resolve degraded-ECC verdicts without a rewrite when sal…
polaz Aug 19, 2026
e4184f5
fix(vlog): defer the blob-prefix punch while a checkpoint pause is ac…
polaz Aug 19, 2026
848c148
fix(salvage): carry the re-emitted on-disk size through the blob-hand…
polaz Aug 19, 2026
9846d1f
docs(table): correct the SST section-layout claims at the punch sites
polaz Aug 19, 2026
39c6db8
docs(repair): record the every-repair verification contract; test the…
polaz Aug 19, 2026
9dca59d
docs(scrub): record the cross-platform rename contract at the sidecar…
polaz Aug 19, 2026
13d5a3e
ci(codecov): fixed patch bar, exclude the fuzz harness
polaz Aug 20, 2026
b4e5870
fix(compaction): defer the user filter on tight-space slices
polaz Aug 20, 2026
01918eb
fix(repair): verify decompression, frame order, and metadata in blob …
polaz Aug 20, 2026
7d14636
fix(salvage): decode dictionary-compressed blobs with the caller's di…
polaz Aug 20, 2026
1d02761
fix(repair): quarantine blob files whose metadata id disagrees with t…
polaz Aug 21, 2026
a2e5e59
docs(fs): state the cross-platform replace contract on Fs::rename
polaz Aug 21, 2026
b75a7d7
fix(repair): bound a punched blob file's metadata by its live suffix
polaz Aug 21, 2026
874d269
docs(fs): note the Windows replace semantics at the StdFs::rename del…
polaz Aug 21, 2026
9a8066a
test(repair): isolate the punched-suffix range containment rejection
polaz Aug 21, 2026
b062d26
fix(fs): accept the implicit root in MemFs::sync_directory
polaz Aug 21, 2026
ee1bf21
docs(fs): state that sync_directory accepts the implicit-root spelling
polaz Aug 21, 2026
239edb7
fix(repair): complete the lagged drop of a fully punched blob file
polaz Aug 21, 2026
2722327
test(salvage): note why the bare-relative-destination pin is blob-only
polaz Aug 21, 2026
a303370
fix(table): carry the heal-hint sink across a restricted reopen
polaz Aug 21, 2026
efe2f5e
fix(salvage): claim the publish destination atomically without hard l…
polaz Aug 21, 2026
7724317
fix(repair): seed garbage accounting for a recovered punched blob prefix
polaz Aug 22, 2026
b18acfd
docs(recovery): describe the punched-prefix garbage seed
polaz Aug 22, 2026
044934b
fix(repair): set aside a fully punched blob whose drop cannot complete
polaz Aug 22, 2026
f476725
docs(recovery): quarantine, not orphan, when the lagged blob drop fails
polaz Aug 22, 2026
f462e26
test(repair): match the removal-fault path filter on Windows separators
polaz Aug 22, 2026
a74ab82
fix(repair): set aside a leftover salvage temp whose removal fails
polaz Aug 22, 2026
fa14f66
fix(repair): resolve duplicate-file aliases through the backend, not …
polaz Aug 22, 2026
d5d9f11
test(repair): name the symlink-alias fixture bindings descriptively
polaz Aug 22, 2026
7f5323b
fix(table): clamp range tombstones to a restricted view for CDC
polaz Aug 22, 2026
a3b8c64
fix(repair): fail honestly when discardable leftovers cannot be removed
polaz Aug 22, 2026
0c76a5e
fix(table): start compaction scans at a restricted view's bound
polaz Aug 22, 2026
21c346a
docs(version): state the no-bump contract for restriction sections
polaz Aug 22, 2026
d3e66b3
docs(compaction): note the restricted view stays compactable via scan
polaz Aug 22, 2026
46fbfde
fix(scrub): reconcile attributable heals of legacy delete-bitmap tables
polaz Aug 22, 2026
a667306
fix(salvage): surface a stuck temp name after a published arbitration
polaz Aug 22, 2026
6b8272a
test(table): pass the repair-context flag to metadata-bounds checks
polaz Aug 23, 2026
d4102b7
fix(salvage): unwind the fresh destination when the temp name sticks
polaz Aug 23, 2026
ff5f3a7
fix(repair): make the blob publish to table rewrite window crash-cons…
polaz Aug 23, 2026
927b985
fix(cdc): deduplicate identical events from duplicated tables in scan…
polaz Aug 23, 2026
e5889c3
fix(repair): track blob remap stamps per blob so a grown retry set st…
polaz Aug 23, 2026
0aa1db9
docs(compaction): note CDC dedup covers the unpunched sidecar-failure…
polaz Aug 23, 2026
9995eff
test(table): gate the legacy-bitmap case to columnar builds
polaz Aug 23, 2026
432d895
fix(vlog): keep a repair's own leftovers from blocking the open
polaz Aug 23, 2026
cf87d8f
fix(repair): adopt only canonical remap sidecars, bounded by their blob
polaz Aug 23, 2026
8b0d6c3
fix(compaction): install the tree-wide sinks on compaction outputs
polaz Aug 23, 2026
f86b752
refactor(repair): publish salvaged blobs under fresh ids, drop the re…
polaz Aug 23, 2026
0dd50e9
feat(recovery): report a read into a punched extent as excised
polaz Aug 23, 2026
f7d60bd
fix(verify): derive the restricted frontier by walking frames
polaz Aug 23, 2026
10568c3
fix(heal): bind every published table to the tree through one choke p…
polaz Aug 23, 2026
7b16382
ci(lint): lint the default feature set, and fix what that surfaced
polaz Aug 23, 2026
bad2e99
fix(repair): fail on an exhausted blob id space and an unmovable orig…
polaz Aug 23, 2026
ed1fbd0
fix(compaction): bind published blob files, and drop unreferenced one…
polaz Aug 24, 2026
8eeedb8
fix(cdc): keep event multiplicity a single source holds
polaz Aug 24, 2026
d58aa59
fix(tight-space): defer a paused prefix reclaim instead of dropping it
polaz Aug 24, 2026
8979674
fix(repair): publish rewrites without displacing their sources
polaz Aug 24, 2026
3f16574
fix(cdc): deliver an entry at the maximum seqno from an SST
polaz Aug 24, 2026
67afac3
perf(verify): cross a reclaimed prefix in bulk reads
polaz Aug 24, 2026
7380b7a
fix(repair): never leave an unpublished rewrite where the scan finds it
polaz Aug 24, 2026
396c4fa
docs(recovery): describe the publish model the code implements
polaz Aug 24, 2026
e07c934
fix(cdc): keep every merge operand the read path applies
polaz Aug 24, 2026
3ea9712
fix(verify): match a restriction sidecar to the file it sits beside
polaz Aug 24, 2026
29bc78e
fix(verify): bound a restricted skip by the index, not by the first z…
polaz Aug 24, 2026
4203b94
fix(repair): keep a rebuild from duplicating a committed history
polaz Aug 24, 2026
d8823df
fix(cdc): replay tied seqnos in source order, not payload order
polaz Aug 24, 2026
cb02642
fix(repair): require a physical hole before reading zeros as a reclaim
polaz Aug 24, 2026
48efc54
fix(repair): never hand a fresh id to a table a sidecar already names
polaz Aug 24, 2026
d543828
feat(fs): add an extent-local hole probe
polaz Aug 24, 2026
e5086a0
fix(repair): prove the hole under the zeroed block, not the file
polaz Aug 24, 2026
7c7cb01
fix(repair): order the table scan so a duplicate id resolves the same…
polaz Aug 24, 2026
cf4a79a
fix(fs): compile the hole probe on Linux
polaz Aug 24, 2026
bc4f303
fix(db_bench): drop a no-op clone on a string slice
polaz Aug 24, 2026
a732057
fix(cdc): replay a tied range deletion before the write it spares
polaz Aug 24, 2026
74d8b68
fix(repair): salvage a table whose leading bytes were destroyed
polaz Aug 24, 2026
054ed7d
fix(repair): require hole evidence before calling a zeroed extent rec…
polaz Aug 24, 2026
973f798
fix(salvage): drop the boundary key when a lost block held its newest…
polaz Aug 24, 2026
84f1825
fix(repair): order rebuilt L0 by table id, never by highest seqno
polaz Aug 24, 2026
86bbe6a
fix(repair): count blob files once the surviving reference set is final
polaz Aug 24, 2026
2bdc0e2
fix(ingest): bind bulk-ingested blob files before publishing them
polaz Aug 24, 2026
d03ed2d
fix(cdc): replay a tied range deletion first across sources too
polaz Aug 24, 2026
a4a470c
fix(salvage): complete the no-resurrection rule across every emit path
polaz Aug 24, 2026
9b16241
fix(salvage): make boundary suppression actually reach the emitted block
polaz Aug 24, 2026
ea51f66
fix(salvage): arm an unknown boundary for a lost block with no key range
polaz Aug 24, 2026
31776e5
fix(cdc): replay one source's tied operands in application order
polaz Aug 24, 2026
2ac54d4
fix(compaction): keep restricted views out of verbatim block reuse
polaz Aug 24, 2026
d1e701b
fix(recovery): republish a restriction sidecar that never landed
polaz Aug 24, 2026
2229fe1
fix(columnar): refuse a projected scan on a tree that merges
polaz Aug 25, 2026
dd8507e
docs(format): record why retired versions keep no public names
polaz Aug 25, 2026
a4259cc
feat(repair): report the key coverage an excluded table lost
polaz Aug 25, 2026
887b993
fix(reclaim): retain a deferred punch instead of discarding it
polaz Aug 25, 2026
8b5fce0
fix(columnar): drop a key whose newest columnar row deletes it
polaz Aug 25, 2026
6cda286
fix(repair): report an unknown seqno bound when the ingest offset is …
polaz Aug 25, 2026
d78d479
fix(columnar): emit projected seqnos in tree-global coordinates
polaz Aug 25, 2026
fdc18c3
fix(salvage): accept a tied-seqno merge operand run as ordered
polaz Aug 25, 2026
009d358
fix(recovery): validate a restriction sidecar instead of trusting its…
polaz Aug 25, 2026
218e68d
fix(reclaim): retain the extents a failed punch left behind
polaz Aug 25, 2026
36ce1c2
fix(salvage): suppress the boundary key by comparator, not bytes
polaz Aug 25, 2026
287f624
fix(cdc): capture the active memtable under the version guard
polaz Aug 25, 2026
2c4d14f
refactor(repair): remove quarantine; a repair fixes the tree or fails
polaz Aug 26, 2026
f05af1a
fix(salvage): suppress a beheaded blob chain by comparator, not bytes
polaz Aug 26, 2026
0981c02
fix(reclaim): run a deferred punch inside the mutation window
polaz Aug 26, 2026
d310966
fix(repair): require hole evidence for a blob frontier
polaz Aug 26, 2026
7e62b9b
style(tests): satisfy the lint set on the new regression tests
polaz Aug 26, 2026
1552628
refactor(comparator): state key identity once, in one place
polaz Aug 26, 2026
f1b98b8
docs(columnar): state what the duplicate-free proof rests on
polaz Aug 26, 2026
82a8154
refactor(version): one body per ordering decision, not a byte twin
polaz Aug 26, 2026
8bfbe08
refactor(file): one grammar for what a tables/ directory entry is
polaz Aug 26, 2026
828d740
docs(repair): state why unreferenced bare-id orphans are removed pre-…
polaz Aug 26, 2026
2e5b6fd
refactor(table): recover takes named params, not fourteen positions
polaz Aug 26, 2026
2f4d602
fix(tree): hold the version-history guard through range-tombstone insert
polaz Aug 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions .config/nextest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,27 @@ slow-timeout = { period = "30s", terminate-after = 4 }
filter = "test(partial_decode_) | test(lazy_block) | test(ecc_heal_scheduled) | test(block_layout_section_roundtrips_for_large_zstd) | test(compatibility_matrix_round_trips)"
slow-timeout = { period = "30s", terminate-after = 4 }

# Wall-clock concurrency stress: 4 writers + 2 readers contend on one
# encrypted tree for a fixed 2-second window, then exact-equality
# assertions run over the committed tally. The test's correctness envelope
# assumes its threads actually get CPU / disk / fds during that window;
# co-scheduled with the rest of the suite it can starve and flake on
# resource pressure that has nothing to do with encryption. Claim every
# scheduler slot so it runs ALONE — the assertions stay at full strength
# (a real engine race still fires solo), only cross-test contention is
# removed. Mirrored in the ci profile below.
[[profile.default.overrides]]
filter = "test(concurrent_encrypted_no_corruption)"
threads-required = "num-cpus"

# The single-byte-bitrot heal fuzzer runs for a fixed ~45s wall-clock budget
# (`#[ignore]`d, so it only runs under `--run-ignored`), well past the 10s
# ceiling by design. Give it a generous window so a slower runner's corpus
# build does not push it into a spurious termination.
[[profile.default.overrides]]
filter = "test(fuzz_heal_bitrot)"
slow-timeout = { period = "90s", terminate-after = 2 }

# CI profile: retries for flaky tests, JUnit XML output, longer timeouts.
[profile.ci]
retries = 2
Expand All @@ -65,3 +86,16 @@ store-failure-output = true
[[profile.ci.overrides]]
filter = "test(prop_)"
slow-timeout = { period = "120s", terminate-after = 4 }

# See the default-profile override of the same filter: the wall-clock
# stress test claims the whole scheduler so cross-test contention cannot
# starve its 2-second window.
[[profile.ci.overrides]]
filter = "test(concurrent_encrypted_no_corruption)"
threads-required = "num-cpus"

# The bitrot heal fuzzer's fixed ~45s budget (see the default-profile note); a
# generous timeout keeps it from a spurious termination on a slow CI runner.
[[profile.ci.overrides]]
filter = "test(fuzz_heal_bitrot)"
slow-timeout = { period = "90s", terminate-after = 2 }
4 changes: 2 additions & 2 deletions .github/workflows/benchmark.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ jobs:
# token leakage where actions/upload-artifact captures the
# whole working tree, including the .git/config that the
# default checkout populated with GITHUB_TOKEN).
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@v7
with:
persist-credentials: false

Expand All @@ -104,7 +104,7 @@ jobs:
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'pull_request' &&
github.event.pull_request.head.repo.full_name == github.repository)
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
uses: actions/create-github-app-token@v3
with:
app-id: ${{ secrets.RELEASER_APP_ID }}
private-key: ${{ secrets.RELEASER_APP_PRIVATE_KEY }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/cleanup-branches.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Delete stale release-plz branches
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
uses: actions/github-script@v9
with:
script: |
const branches = await github.paginate(
Expand Down
58 changes: 53 additions & 5 deletions .github/workflows/coordinode-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@
if: needs.changes.outputs.code == 'false'
run: |
echo "::notice::No code paths changed — skipping format + clippy. Required-check name still emitted as success."
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@v7
if: needs.changes.outputs.code != 'false'
with:
persist-credentials: false
Expand Down Expand Up @@ -283,7 +283,7 @@
if: needs.changes.outputs.code != 'false' && needs.lint.result != 'success'
run: |
echo "::notice::Lint failed (result=${{ needs.lint.result }}) — skipping test work to avoid duplicate noise on an already-failed PR. Required-check name still emitted; fix the lint failure to unblock real test runs."
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@v7
if: needs.changes.outputs.code != 'false' && needs.lint.result == 'success'
with:
persist-credentials: false
Expand Down Expand Up @@ -327,7 +327,55 @@
working-directory: tools/sst-dump
run: cargo nextest run

fuzz-heal:
# Reproducible single-byte-bitrot fuzzer over the SST read / heal path:
# flips one bit in a corpus of SSTs (varied block size, per-KV checksum,
# columnar, compression, encryption, Page-ECC) for a fixed ~45s budget and
# asserts the read path never panics and never returns a wrong value (a
# flipped block heals via ECC or fails its checksum, never silent corruption).
# `#[ignore]`d, so it is excluded from the normal `test` job and run here with
# `--run-ignored=only`. Ubuntu-only (the invariant is platform-independent);
# not a required check.
needs: [changes, lint]
if: ${{ needs.changes.outputs.code != 'false' && needs.lint.result == 'success' }}
timeout-minutes: 15
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
with:
toolchain: stable
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
prefix-key: ubuntu-cargo
- uses: taiki-e/install-action@1ef5c5f58e85d25baaaa1704478fdd6c2921f2b5 # nextest
- name: Run bitrot heal fuzzer
# Pin the reproducer directory explicitly so the test's dump location does
# not depend on the process working directory (robust if the crate ever
# moves into a workspace subdirectory). The dump step reads the same path.
env:
FUZZ_HEAL_REPRO_DIR: ${{ github.workspace }}
# `--retries 0` overrides the ci profile's retries: the fuzzer dumps the
# EXACT failing SST, and a retry (on a non-deterministic corpus) would
# either overwrite that dump or mask a real failure that does not replay.
run: cargo nextest run --profile ci --all-features --run-ignored=only --retries 0 -E 'test(fuzz_heal_bitrot)'
Comment thread
polaz marked this conversation as resolved.
Outdated
- name: Dump reproducer on failure
if: failure()
# The corpus is not byte-deterministic (encrypted / timestamped SSTs), so
# the seed alone cannot replay a failure in those. The test writes the
# EXACT failing SST to `fuzz_heal_repro.sst`; surface it (base64) in the
# log so the case reproduces directly, no artifact upload needed.
env:
FUZZ_HEAL_REPRO_DIR: ${{ github.workspace }}
run: |
echo "=== fuzz_heal_repro.txt ==="
cat "$FUZZ_HEAL_REPRO_DIR/fuzz_heal_repro.txt" 2>/dev/null || echo "(no repro txt)"
echo "=== fuzz_heal_repro.sst (base64) ==="
base64 "$FUZZ_HEAL_REPRO_DIR/fuzz_heal_repro.sst" 2>/dev/null || echo "(no repro sst)"

no-std-check:

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: read}
# Gates the `#![no_std]` + alloc engine path. The engine modules compile
# unconditionally; only the std default trait implementations (the system
# filesystem, the io_uring backend, the system clock) stay behind
Expand All @@ -346,7 +394,7 @@
timeout-minutes: 10
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@v7
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
Expand Down Expand Up @@ -429,7 +477,7 @@
target: ${{ fromJSON(needs['cross-matrix'].outputs.targets) }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@v7
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
Expand All @@ -447,7 +495,7 @@
timeout-minutes: 20
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@v7
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@5b842231ba77f5c045dba54ac5560fed2db780e2 # nightly
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/coordinode-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
steps:
- name: Generate token (SW Release Bot)
id: app-token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
uses: actions/create-github-app-token@v3
with:
app-id: ${{ secrets.RELEASER_APP_ID }}
private-key: ${{ secrets.RELEASER_APP_PRIVATE_KEY }}
Expand All @@ -23,7 +23,7 @@ jobs:
permission-pull-requests: write

- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@v7
with:
fetch-depth: 0
# persist-credentials: false — release-plz reads the token via
Expand All @@ -44,7 +44,7 @@ jobs:

# Step 1: Create or update release PR (version bump + changelog)
- name: Create release PR
uses: release-plz/action@e8792575c7f2366cf6ff3ccc33ead9ace5b691c7 # v0.5.130
uses: release-plz/action@2eb1d8bcb770b4c48ccfaad919734b38b51958c9 # v0.5.131
with:
command: release-pr
config: .release-plz.toml
Expand All @@ -54,7 +54,7 @@ jobs:
# Step 2: If version in Cargo.toml > latest tag, create GitHub Release + tag
# This triggers release.yml → cargo publish via OIDC
- name: Create GitHub Release
uses: release-plz/action@e8792575c7f2366cf6ff3ccc33ead9ace5b691c7 # v0.5.130
uses: release-plz/action@2eb1d8bcb770b4c48ccfaad919734b38b51958c9 # v0.5.131
with:
command: release
config: .release-plz.toml
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/dependabot-auto-merge.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,22 @@ jobs:

steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@v7
with:
# gh CLI authenticates via the GH_TOKEN env var on the merge
# step; no git config credentials are needed here.
persist-credentials: false

- name: Dependabot metadata
id: metadata
uses: dependabot/fetch-metadata@25dd0e34f4fe68f24cc83900b1fe3fe149efef98 # v3.1.0
uses: dependabot/fetch-metadata@v3
with:
github-token: "${{ secrets.GITHUB_TOKEN }}"

- name: Generate App Token
id: app-token
if: steps.metadata.outputs.update-type == 'version-update:semver-minor' || steps.metadata.outputs.update-type == 'version-update:semver-patch'
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
uses: actions/create-github-app-token@v3
Comment thread
polaz marked this conversation as resolved.
with:
app-id: ${{ secrets.RELEASER_APP_ID }}
private-key: ${{ secrets.RELEASER_APP_PRIVATE_KEY }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/issue-labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Label issue by title prefix
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
uses: actions/github-script@v9
with:
script: |
const title = context.payload.issue.title.toLowerCase();
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
contents: read
id-token: write
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@v7
with:
# cargo publish authenticates via OIDC against crates.io
# (rust-lang/crates-io-auth-action below); no git config
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ mutants*
profile.json
fuzz*/**/out*

# The bitrot heal fuzzer dumps the exact failing SST here on a failure so the
# case reproduces; a runtime artifact, never committed.
fuzz_heal_repro.*

old
.claude/
.forge/
Expand Down
26 changes: 19 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -161,13 +161,25 @@ spin = { version = "0.12", default-features = false, features = ["mutex", "spin_
# userspace fast-path, concurrent readers); `spin::RwLock` is the no_std lock.
hashbrown = { version = "0.17", default-features = false, features = ["default-hasher"] }
parking_lot = { version = "0.12", optional = true }
lz4_flex = { version = "0.13.0", optional = true, default-features = false }
# Default features carry the runtime-dispatched SIMD decode kernels (avx2/bmi2/
# neon/sve/...); `default-features = false` would silently drop them and leave
# the sequence decoder on the scalar path, measurably slower on cold point reads.
# 0.0.48 is the published crates.io release; it fixes a dictionary match
# binary-tree encoder panic present in 0.0.46/0.0.47 on high (BT-strategy) levels.
structured-zstd = { version = "0.0.48", optional = true, features = ["lsm"] }
# LZ4 block codec for hot-path blocks. `default-features = false` drops the
# `std` and `frame` defaults (we use only the block API and stay no_std-capable
# via `alloc`), but the malformed-input safety flags are NOT dropped: verify and
# salvage decompress SST bytes that may be DAMAGED, so keep `checked-decode`
# (decompression bounds checks) plus the `safe-decode` / `safe-encode` pure-safe
# paths — disabling them would risk UB on adversarial/corrupt input.
lz4_flex = { version = "0.14.0", optional = true, default-features = false, features = [
"alloc",
"checked-decode",
"safe-decode",
"safe-encode",
] }
# structured-zstd default features carry the runtime-dispatched SIMD decode
# kernels (avx2/bmi2/neon/sve/...); `default-features = false` would silently
# drop them and leave the sequence decoder on the scalar path, measurably slower
# on cold point reads. 0.0.49 is the published crates.io release; it carries the
# dictionary match binary-tree encoder panic fix (introduced in 0.0.48, absent
# in 0.0.46/0.0.47 on high BT-strategy levels) plus encoder decode/parse perf.
structured-zstd = { version = "0.0.49", optional = true, features = ["lsm"] }
# `once_cell::race::OnceBox` — the no-std + alloc one-shot primitive.
# We pick it over `std::sync::OnceLock` (which has both `set` and the
# stabilised `get_or_try_init` on our 1.92 MSRV) because OnceLock is
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ LSM-tree storage engine in Rust. Embedded library; provides keyed point reads, p

## Status

On-disk format version **V5**. V5 introduces a wire-format break for filter blocks (BuRR replaces Bloom); V3 and V4 databases are not readable by this version and vice versa. Versioning is single-monotonic: every breaking format change bumps to the next version with explicit migration notes.
On-disk format version **V5** — the ONLY supported format. The engine carries no legacy decode paths, no backward-compat variations, and no in-place upgrade: a pre-V5 database fails to open with `InvalidVersion`. Versioning is single-monotonic: every breaking format change bumps to the next version with explicit migration notes. Conversion of pre-V5 databases is planned as standalone migration tooling, kept strictly outside the live engine.

## Quick start

Expand Down
27 changes: 25 additions & 2 deletions docs/data-integrity.md
Original file line number Diff line number Diff line change
Expand Up @@ -255,8 +255,31 @@ inspection, or rollback to a known-good point.
fully-valid file, quarantine the corrupt ones, and report the key range each
dropped, so one bad block costs only its own keys instead of the whole file. A
columnar segment with a damaged sidecar degrades conservatively: a torn
sub-column drops just its block, and a corrupt delete-bitmap reads as "all rows
live, pending recompaction" rather than failing the open.
sub-column drops just its block. A delete-bearing segment whose positional
delete bitmap cannot be applied (unreadable bitmap, or a bitmap whose
positioning zone map is unreadable) fails the salvage closed by default —
recovering "all rows live" would resurrect deleted rows — unless explicitly
opted in (`SalvageOptions::allow_delete_resurrection`, `sst-dump salvage
--allow-delete-resurrection`).
- **`salvage::salvage_blob_file(src, dest, &fs, id, &comparator) -> crate::Result<BlobSalvageReport>`**:
record-granular salvage of one blob (vlog) file. The `comparator` must be the
SAME `SharedComparator` the source tree was written with (pass the tree's
configured comparator, or `comparator::default_comparator()` for the default
lexicographic ordering): the salvage walk orders and validates recovered
records under it, so a mismatched comparator would mis-order the output. When a frame fails checksum,
header-CRC, or structural validation, the record stream re-syncs to the next
frame magic WHEN one is found in-bounds; if none is (for example a CRC-vouched
frame end overruns the data section), the scan terminates. Either way the
resync magic (and every frame chained after it) has an unproven boundary (it
may be nested in the damaged frame's user bytes), so the walk **drops the
entire tail past the first resync / termination** (fail closed): the
conservative loss is as much as everything after the first damaged record, not
just that one record, because a fabricated chain of checksum-valid frames is
indistinguishable from genuine ones and re-emitting it would forge records.
Only records BEFORE the first resync are recovered. The salvaged file is written COMPACTED, so it is **not a drop-in
replacement** while SST entries hold `ValueHandle::offset` values into the
source: re-target them through `BlobSalvageReport::offset_remap` first (a
source offset absent from the map is a lost record).
- **`Config::repair_with_salvage(true)`** (also `tools/sst-dump repair
--salvage`): the manifest rebuild above, but an SST that fails verification is
block-salvaged in place instead of being left out, and
Expand Down
Loading
Loading