Skip to content

Latest commit

 

History

History
4252 lines (3746 loc) · 230 KB

File metadata and controls

4252 lines (3746 loc) · 230 KB

lean-zip Progress

Global milestones — updated only on phase transitions and major events. Per-session details are in progress/.

Current State

  • Phase: Phase 4+ complete; Track C1 complete; Track C2 complete; proven-bounds campaign complete (0 runtime ]! across Zip/Native/ and Zip/); Track E (security audit) in progress — P0 + P1 + P2 closed, all of P3 closed (P3.1 / P3.2 / P3.4 in prior batches; P3.3 fuzz harness landed as #1602 in this batch), P4 closed, P5.1 proof-friendly helpers landed as #1608 in this batch (P5.2 filed mid-batch as #1628; P5.3 tracked as #1614 and in flight as PR #1626 at batch close). SECURITY_INVENTORY.md Recommended policy block: Rec. 1 / 3 / 5 executed in this batch (#1623 / #1618 / #1617); Rec. 2 structurally set up by #1610 (parameter added; default still 0, issue #1625 tracks the flip); Rec. 4 tracked as #1621
  • Toolchain: leanprover/lean4:v4.29.1
  • Sorries: 0
  • Sessions: ~660 completed (Feb 19 – Apr 22)
  • Source files in-repo: Zip/Spec/ 42, Zip/Native/ 7, Zip/ (FFI/archive/tar/gzip/basic/checksum) 6, ZipTest/ 24
  • Merged PRs: ~653 (approximate; authoritative count via gh pr list)
  • Spec lines: 20,606 across 42 spec files (DEFLATE-only, post-split)
  • Bare simp: 0 standalone bare simp remaining across all spec files
  • Bare simp_all: 0 across all spec files (campaign complete)
  • Zstd: Moved to https://github.com/kim-em/lean-zstd (#1487, 2026-03-27)
  • Shared utilities: Moved to https://github.com/kim-em/lean-zip-common (#1487, 2026-03-27); pulled in via require zipCommon from git
  • Proven-bounds: 0 runtime ]! across Zip/Native/ and Zip/; only 2 comment-only occurrences remain in Zip/Native/Deflate.lean
  • Track E audit checklist: plans/track-e-current-audit-checklist.md is the source of truth for "what is done vs. what remains"; the human-readable trust-boundary catalogue is SECURITY_INVENTORY.md
  • Track E Recommended policy block: fully Executed (items 1–6, post-#1710 on 2026-04-22); Missing work reads "Residual gaps: none currently open at this layer"
  • Checksum characterizing-property ladders: Adler-32 closed end-to-end (last rung _combine in #1698); CRC32 closed at the concrete-shape terminus _pair (#1701); _replicate* / _combine for CRC32 require GF(2)[x] algebra (out of scope)
  • Anchor-refresh maintenance retired (post-#2345): ~176 prior commits of line-anchor refresh work superseded; close-out wave in PR #2364 closed 15 obsolete PRs and 15 obsolete issues. inventory-reconciliation skill now refuses such work at the planner and worker layer.
  • Track E CD/EOCD + CD/LH boundary-check coverage (post-#1868): 6 / 9 archive-level EOCD consistency dimensions closed (totalEntries, disk-number, numEntriesThisDisk, ZIP64/standard-EOCD override sentinel, per-entry CD diskNumberStart, ZIP64 EOCD64 record-size); 7 / 8 per-entry CD/LH consistency dimensions closed (method, flags, version, compressedSize, uncompressedSize, crc32, lastModTime/Date — name-bytes remaining, in-flight PR #1725); ZIP64 extra-field layout-smuggling class closed at the CD/LH boundary (inner-0x0001 dataSize exactness #1785 + outer sub-field structural walk #1788 + duplicate-block rejection #1793); intra-CD stored-method (method=0) size-invariant closed (#1773); CD-parse per-entry field validations closed in the post-#1803 wave (versionNeededToExtract > 45 upper bound #1807, localOffset + 30 ≤ cdOffset archive-layout micro-invariant #1813, internalFileAttributes reserved bits #1819, GPF bit-5 patched-data #1824, name NUL-byte #1831); CD-parse filename-validation trio completed in the post-#1843 wave (cd-nul-in-name #1831, cd-path-unsafe #1840, cd-empty-name #1848); CD-parse mathematical-invariant family second column closed (uncompSize == 0 → crc == 0 #1857, sibling of stored-method size invariant #1773) and third column closed (compSize == 0 → uncompSize == 0 #1886, method-agnostic — closes the method=8 gap relative to #1773 / #1857); archive-level EOCD64 versionMadeBy spec-version upper bound closed (#1826) and companion versionNeededToExtract ≤ 63 upper bound closed (#1852), together with the earlier versionNeededToExtract ≥ 45 lower bound (issue #1758 / in-flight PR #1764) closing the EOCD64 version-field two-sided-bound dimensions (per-entry CD versionMadeBy > 63 remains in-flight PR #1820); ZIP64-trailer archive-layout invariants trio closed (per-entry localOffset + 30 ≤ cdOffset #1813, ZIP64 record eocd64Offset + 56 ≤ locatorPos #1856, archive cdOffset + cdSize ≤ eocdPos in-flight PR #1809); cross-format NUL-byte closure in Tar (UStar base name / linkname / prefix #1880 + GNU long-name / long-link #1865 + PAX record key/value #1866) plus ZIP CD-layer (#1831) spans all four user-supplied string-field layers, every one now carrying an explicit code guard (UStar layer was previously structurally NUL-safe by construction; #1880 added an explicit code guard so the closure is uniform); per-slot ZIP64-override family (opened post-#1718 by #1745 with the cdOffset slot) closed at 5 of 6 slots in the post-#1904 wave (cdSize #1905, totalEntries #1908, diskWhereCDStarts #1911, numEntriesThisDisk #1922; only numberOfThisDisk outstanding via in-flight PR #1909); local-offset → local-header validation chain (per-entry CD-parse) closed as a complete three-level trio in the post-#1904 wave (level-1 cd-entry-localoffset-past-cdstart #1813, level-2 cd-entry-past-cdend #1921, level-3 cd-bad-lh-signature #1903); PAX-record decode surface closed at both parser-differential dimensions (NUL-byte rejection #1866 + duplicate-key rejection #1899); 47 fixtures in testdata/zip/malformed/ (was 41 at #1894, 39 at #1869, 34 at #1843, 28 at #1803, 22 at #1770, 12 at #1721); 18 fixtures in testdata/tar/malformed/ (was 17 at #1894, 16 at #1869, 14 at #1843)

Milestones

Phase 1: Checksums (complete, Feb 19)

Native CRC32 and Adler32 with equivalence proofs against FFI implementations. Proved in 2 sessions. Key technique: bv_decide for bitvector reasoning.

Phase 2: DEFLATE Decompressor (complete, Feb 19)

Pure Lean DEFLATE inflate implementation (RFC 1951) with gzip/zlib wrappers. Conformance tests pass against FFI zlib. Integrated as alternative backend for ZIP and tar.gz extraction. Completed in 4 sessions.

Phase 3: Verified Decompressor (complete, Feb 23)

Correctness proofs for the DEFLATE decompressor, completed over 25 sessions (Feb 19–23). Zero sorries. The proof chain covers:

  • BitReader ↔ bytesToBits correspondence (BitstreamCorrect)
  • Canonical Huffman tree ↔ spec table correspondence (HuffmanCorrect)
  • Block-level decode correctness for all 3 block types (DecodeCorrect, DecodeComplete)
  • Dynamic Huffman tree decode correctness (DynamicTreesCorrect)
  • Stream-level inflate theorem (InflateCorrect)
  • fromLengths success implies ValidLengths (DynamicTreesCorrect)

Characterizing properties (mathematical content independent of implementation):

  • Huffman codes are prefix-free and canonical (Huffman.Spec)
  • Kraft inequality bounds symbol counts (HuffmanKraft)
  • Prefix-free codes decode deterministically (decode_prefix_free)
  • Bitstream operations correspond to LSB-first bit lists
  • LZ77 overlap semantics: copyLoop_eq_ofFn
  • RFC 1951 tables verified by decide

Algorithmic correspondence (native implementation agrees with spec decoder):

  • inflate_correct: native inflateRaw → spec decode succeeds with same result
  • inflate_complete: spec decode succeeds → native inflateRaw succeeds with same result
  • Both directions proved for stored, fixed Huffman, and dynamic Huffman blocks

Phase 4: DEFLATE Compressor + Roundtrip (complete, Feb 23–24)

Native compression + roundtrip verification. ~80 sessions. Zero sorries for the core DEFLATE roundtrip.

Compressor infrastructure:

  • Native Level 0 (stored), Level 1 (fixed Huffman), and Level 5 (dynamic Huffman) compressors, all conformance tests passing
  • Native gzip/zlib compression wrappers (GzipEncode, ZlibEncode)
  • BitWriter with formal correctness proofs (writeBits, writeHuffCode)

LZ77 layer (BB1):

  • Spec-level LZ77 greedy matcher with fundamental roundtrip theorem (resolveLZ77_matchLZ77)
  • Spec-level lazy LZ77 matcher with Level 2 roundtrip (deflateLevel2_spec_roundtrip)
  • Native LZ77 correctness: lz77Greedy_valid, lz77Greedy_encodable, lz77Greedy_size_le
  • Native lazy LZ77 correctness: lz77Lazy_valid, lz77Lazy_encodable, lz77Lazy_resolvable

Huffman layer (BB2):

  • Huffman symbol encoding functions with encodeSymbols_decodeSymbols
  • Canonical codes correctness: canonicalCodes_correct_pos
  • Huffman code length computation with Kraft equality for binary trees

Bitstream layer (BB3):

  • BitWriter correctness (BitstreamWriteCorrect)
  • readBits_complete (BitReader completeness, spec → native direction)
  • readUInt16LE_complete, readBytes_complete (BitstreamComplete)

Block framing layer (BB4):

  • Stored-block encode/decode roundtrip (encodeStored_decode)
  • Dynamic tree header roundtrip: encodeDynamicTrees_decodeDynamicTables
  • writeDynamicHeader_spec — BitWriter chain for dynamic header
  • Native emitTokens ↔ spec encodeSymbols correspondence: emitTokens_spec, emitTokensWithCodes_spec

Per-level roundtrips:

  • inflate_deflateStoredPure — Level 0 (stored blocks)
  • inflate_deflateFixed — Level 1 (fixed Huffman)
  • inflate_deflateLazy — Levels 2–4 (lazy LZ77)
  • inflate_deflateDynamic — Level 5+ (dynamic Huffman)

Capstone theorem (DeflateRoundtripProduction.lean):

theorem inflate_deflateRaw (data : ByteArray) (level : UInt8)
    (hsize : data.size < 1024 * 1024 * 1024) :
    Inflate.inflate (deflateRaw data level) = .ok data

Covers all compression levels (stored, fixed, lazy, dynamic). The 1 GiB size bound is now the native inflate's default maxOutputSize (a runtime zip-bomb guard), not a fuel/termination limitation — all recursive functions use well-founded recursion (Track C2 complete).

Proof quality reviews (80+ sessions): systematic code review across spec files, reducing proof size, extracting reusable lemmas to ZipForStd, splitting large files for maintainability, and converting bare simp to simp only. Reviews to date: Deflate (#369), DecodeComplete (#374, #442, #517), BitReaderInvariant (#382), HuffmanCorrect + HuffmanCorrectLoop (#385), InflateCorrect (#387), InflateRawSuffix (#391, #484), DeflateFixedCorrect (#404), LZ77 (#408), InflateLoopBounds (#413), DeflateDynamicFreqs (#414), DynamicTreesComplete (#440), DynamicTreesCorrect (#441), DeflateEncode (#448, #457), BitstreamCorrect (#459), HuffmanEncode (#467), EmitTokensCorrect (#470), DeflateEncodeProps (#485), BitWriterCorrect (#488, #505), DeflateStoredCorrect (#498), BitstreamWriteCorrect (#500), DeflateDynamicCorrect (#507), InflateComplete (#513), DeflateDynamicEmit (#514), HuffmanTheorems (#518), BinaryCorrect + LZ77Lazy + DeflateRoundtrip (#528), DeflateSuffix (#531), ZipForStd/ (#539), Adler32 + DeflateDynamicCorrect + DeflateStoredCorrect (#543), 5 small-count files (#545), GzipCorrect + HuffmanKraft + ZlibCorrect (#547).

Bare simp status: 0 standalone bare simps remaining across all 44 spec files (100% clean). The campaign reduced standalone bare simps from ~129 to 0 (100% reduction) over ~30 review PRs. ZipForStd/ and Native/ are also fully clean. Campaign completed Mar 4, 2026.

Phase 4+: Gzip/Zlib Framing Roundtrip (complete, Feb 24–26)

Extends the core DEFLATE roundtrip to full gzip (RFC 1952) and zlib (RFC 1950) framing, proving that the encode/decode wrappers are inverses.

Capstone theorems (zero sorries):

-- GzipCorrect.lean
theorem gzip_decompressSingle_compress (data : ByteArray) (level : UInt8)
    (hsize : data.size < 1024 * 1024 * 1024) :
    GzipDecode.decompressSingle (GzipEncode.compress data level) = .ok data

-- ZlibCorrect.lean
theorem zlib_decompressSingle_compress (data : ByteArray) (level : UInt8)
    (hsize : data.size < 1024 * 1024 * 1024) :
    ZlibDecode.decompressSingle (ZlibEncode.compress data level) = .ok data

What's proved:

  • Pure proof-friendly decoders: GzipDecode.decompressSingle, ZlibDecode.decompressSingle (no loops/mut)
  • Format lemmas: gzip header bytes, zlib CMF/FLG/CINFO/FDICT fields
  • compress_size (total output size = header + deflated + trailer)
  • Binary LE/BE read/write roundtrip proofs (BinaryCorrect.lean)
  • inflateRaw_complete: if spec decode succeeds at arbitrary offset, native inflateRaw succeeds with same result
  • Suffix invariance: decode_go_suffix (spec decode ignores trailing bits)
  • BitReader invariant infrastructure: 14 _inv lemmas tracking data, hpos, and pos ≤ data.size through all BitReader operations (all proved — split across BitReaderInvariant.lean + InflateLoopBounds.lean)
  • inflateLoop_endPos_le: endPos ≤ data.size after inflate loop
  • inflateRaw_endPos_eq: exact endPos calculation for trailer parsing
  • Encoder padding decomposition (deflateRaw_pad) and remaining.length < 8 via decode.goR (decode with remaining) variant
  • Suffix invariance chain: decodeCLSymbols_append, decodeDynamicTrees_append, decodeHuffman_go_append
  • CRC32 and ISIZE trailer byte matching (gzip)
  • Adler32 trailer byte matching (zlib)

Architecture: The original monolithic GzipCorrect.lean (1949 lines) was split into 4 focused modules: BitReaderInvariant.lean (522 lines), InflateLoopBounds.lean (614 lines), InflateRawSuffix.lean (501 lines), and GzipCorrect.lean (286 lines).

Track C1: Size Bound Improvement (complete, Feb 25 – Mar 6)

Eliminated the hard-coded size bound from all roundtrip theorems. The capstone theorem is now fully parametric in maxOutputSize:

theorem inflate_deflateRaw (data : ByteArray) (level : UInt8)
    (maxOutputSize : Nat) (hsize : data.size < maxOutputSize) :
    Zip.Native.Inflate.inflate (deflateRaw data level) maxOutputSize = .ok data

This was achieved progressively:

  • #305: raised fuel limits from 5MB to 500MB–1GiB
  • #654: generalized stored block roundtrip to parametric maxOutputSize
  • #656: generalized levels 1–4 and dynamic roundtrips, plus the capstone

The maxOutputSize parameter is now a caller-chosen zip-bomb guard, not a proof limitation. Combined with Track C2 (fuel elimination), the DEFLATE roundtrip theorem has no artificial restrictions.

Track C2: Fuel Elimination (complete, Mar 2)

Replaced all fuel-based recursion with well-founded recursion, eliminating the data size bound as a proof artifact. The 1 GiB bound in the capstone theorem is now solely the native inflate's maxOutputSize zip-bomb guard.

All 6 fuel-using functions converted to WF:

  • Spec decodeCLSymbols → WF (#328): termination_by totalCodes - acc.length
  • Native decodeCLSymbols → WF (#332): same termination measure
  • Spec decodeSymbols → WF (#337): termination_by on remaining bits
  • Native decodeHuffman.go → WF (#341): termination_by dataSize * 8 - br.bitPos
  • Spec decode.go/decode.goR → WF (#344): required deleting DeflateFuelIndep.lean (fuel independence became moot once spec functions are WF) and updating 11 downstream proof files
  • Native inflateLoop → WF (#397): termination_by dataSize * 8 - br.bitPos

Key proofs repaired during and after WF conversions:

  • inflateLoop_correct (#358): native→spec block loop correspondence
  • decodeDynamicTrees_complete (#361): composed from sub-completeness theorems
  • inflateLoop_complete (#373): spec→native block loop, strong induction
  • Proof repairs across 6 PRs (#345, #350, #351, #356, #357, #362)

Sorry count trajectory: 0 → ~15 (during conversions) → 0 (all repaired).

New skill: lean-wf-recursion (#349) capturing WF function unfolding rules, f.induct functional induction patterns, and fuel-to-WF migration checklist.

Track D: Benchmarking (in progress, Feb 25 – Mar 2)

Benchmark infrastructure comparing native Lean compression/decompression vs FFI (zlib) across all levels (0–9) with various data patterns (constant, cyclic, random, text) at sizes 1KB–1MB.

Large-input fixes:

  • lz77GreedyIter — tail-recursive LZ77 greedy matcher with proved equivalence (lz77GreedyIter_eq_lz77Greedy), replacing the stack- overflowing lz77Greedy_mainLoop for level 5+ (#372)
  • lz77LazyIter — tail-recursive LZ77 lazy matcher with proved equivalence (lz77LazyIter_eq_lz77Lazy), enabling levels 2–4 at large input sizes (#380)
  • All compression levels now work at 256KB+ inputs

Benchmark infrastructure (NativeScale.lean, NativeCompressBench.lean):

  • Decompression benchmarks added (#379): inflate, gzip decompress, zlib decompress — native vs FFI across all patterns and sizes
  • Text corpus pattern added (#390): realistic text data alongside constant, cyclic, and random patterns
  • Compression size cap removed (#390): all levels run at all sizes (was previously split into small/large tiers with restricted levels)
  • All-level compression ratios (#399): native vs FFI output sizes for levels 0–9 across all 4 data patterns
  • MB/s throughput calculations (#399) added to all benchmark sections

Track E: Zstd Native Decompressor (in progress, Mar 2–6)

Native Lean Zstd implementation, following the same methodology as DEFLATE (B-track). The core decompression infrastructure is now largely in place; the remaining work is wiring these components together for compressed block decompression and adding multi-frame support.

Frame and block layer:

  • Frame header parser (#398): magic number, frame header descriptor bit fields, optional window descriptor, dictionary ID, frame content size — per RFC 8878 §3.1.1. ZstdFrameHeader structure with parseFrameHeader.
  • Block header parsing and raw/RLE decompression (#405): 3-byte block headers (Last_Block, Block_Type, Block_Size), verbatim copy for raw blocks, single-byte repeat for RLE blocks, decompressFrame loop over blocks.
  • XXH64 checksum verification (#432): pure Lean xxHash64 implementation wired into frame decompressor for content checksum validation.
  • Compressed block header parsing (#439): parseLiteralsSection (4 literal block types × 3 size formats) and parseSequencesHeader (sequence count
    • compression mode bytes for literals/offsets/match lengths).

FSE (Finite State Entropy) infrastructure (Zip/Native/Fse.lean, ~359 lines):

  • Distribution decoder (#429): decodeFseDistribution reads compact probability distributions from the bitstream, producing normalized symbol counts.
  • Table construction (#429): buildFseTable builds the FSE decoding table (1 << accuracyLog cells) using RFC 8878 §4.1.1 position-stepping algorithm.
  • Backward bitstream reader (#452): BackwardBitReader for Zstd's MSB-first backward bitstream format (RFC 8878 §4.1), with init, readBits, isFinished.
  • Symbol decoding (#452): decodeFseSymbols decodes sequences of symbols from FSE state machine lookups.
  • decodeFseSymbolsAll (#479): variant that loops until BackwardBitReader.isFinished (for variable-length outputs like Huffman weight sequences).

Sequence execution (ZstdFrame.lean):

  • Sequence execution engine (#447): ZstdSequence structure, resolveOffset with repeat offset codes 1–3 (RFC 8878 §3.1.2.3), copyMatch with overlap semantics, executeSequences for reconstructing output from literal/match interleaving.

Huffman tree descriptors (#458, #479):

  • Direct representation parsing (#458): parseHuffmanWeightsDirect reads raw 4-bit weight nibbles, weightsToMaxBits computes max bit depth from weight distribution, buildZstdHuffmanTable constructs lookup table from weights.
  • FSE-compressed representation (#479): parseHuffmanWeightsFse decodes weight sequences via FSE tables, the common case in real-world Zstd files at default compression levels.
  • parseHuffmanTreeDescriptor dispatches between direct and FSE-compressed weight representations.

Sequence infrastructure (#466):

  • Extra bits tables (litLenExtraBits, matchLenExtraBits, litLenBaseValues, matchLenBaseValues) implementing RFC 8878 §3.1.1.3.2.1 tables.
  • Compression mode parsing: parseCompressionMode decodes 2-bit mode fields (predefined, RLE, FSE-compressed, repeat) for sequence header entries.

Multi-frame support (#490):

  • decompressMultiFrame: loops over concatenated frames (RFC 8878 §3.1), accumulating decompressed output.
  • decompressSkippableFrame: identifies and skips skippable frames (magic 0x184D2A50–0x184D2A5F).
  • decompressZstd: top-level entry point handling both standard and skippable frames.

Tests: 25+ tests in ZipTest/ZstdNative.lean covering header parsing, RLE/raw blocks, FSE distribution decoding, table construction, backward bitstream reading, sequence execution, Huffman weight parsing, compression mode parsing, multi-frame support, and checksum verification.

Huffman-compressed literals (#508, merged):

  • decodeHuffmanSymbol, decodeHuffmanStream, decodeFourHuffmanStreams for flat table lookup with BackwardBitReader
  • Handles litType 2 (Huffman-compressed) in parseLiteralsSection per RFC 8878 §3.1.1.3.1

FSE table resolution (#548, merged):

  • buildRleFseTable: constructs single-symbol FSE table for RLE mode
  • resolveSingleFseTable: dispatches on compression mode (predefined, RLE, FSE-compressed, repeat) to produce an FSE decoding table
  • resolveSequenceFseTables: resolves all three sequence tables (literal length, offset, match length) from compression modes
  • This was the critical connector between compression mode parsing and sequence decoding — completing issue #473

Treeless literals (#566, merged):

  • litType 3 (treeless) support in parseLiteralsSection: reuses the Huffman tree from a previous block via prevHuffTree parameter
  • Huffman tree state threaded through the decompressBlocks loop

Cross-block state (#572, merged):

  • executeSequences now accepts windowPrefix (prior blocks' output) and offsetHistory (repeat offset state across blocks)
  • Match offsets can reach into previous blocks' output
  • Return type extended to propagate offset history for next block

Sequences header consolidation (#556, merged):

  • Unified parseSequencesHeader variants, removing code duplication
  • Wired compression modes directly into decompressBlocks

Dictionary frame rejection (#565, merged):

  • decompressFrame now returns an error if dictionaryId ≠ 0
  • 3 validation edge case tests (dictionary rejection, checksum corruption, truncated frame)

Malformed data tests (#567, merged):

  • 12 new test cases (158 lines) covering: reserved frame header bits, content size mismatch, max window size, truncated headers, reserved block types, raw block underflow, RLE boundaries, Huffman weight validation, skippable frame overflow, trailing data detection

Zstd specification infrastructure:

  • Zip/Spec/Fse.lean (#584, merged, 133 lines): FSE distribution validity predicates (ValidAccuracyLog, ValidDistribution, ValidFseTable) with Decidable instances. Predefined distribution validity proved by decide. Sorry count reduced from 3 to 1 by proving decodeFseDistribution_accuracyLog_ge and _le (#603). decodeFseDistribution_sum_correct proved via loop invariant (#619). buildFseTable_accuracyLog_eq proved (#646); 1 sorry remains (buildFseTable_cells_size, requires forIn loop invariant).
  • Zip/Spec/XxHash.lean (#618, merged, ~180 lines): XXH64 specification predicates — prime value validation, initial state, round function properties. xxHash64_empty proved (#642). 4 sorry remaining (3 UInt64 test vectors too expensive for kernel evaluation, 1 docstring mention).
  • Zip/Spec/ZstdHuffman.lean (#606, merged, ~170 lines): Huffman weight validity predicates per RFC 8878 §4.2.1 — ValidWeights, ValidMaxBits, ValidHuffmanTable. isPow2_iff and weightSum_pos_of_exists_nonzero proved (#628). buildZstdHuffmanTable_maxBits_pos proved via WF refactoring of while loop to findMaxBitsWF (#641). 2 sorry remaining (buildZstdHuffmanTable_tableSize fill loop invariant, one other).
  • Zip/Spec/Zstd.lean (#587, merged): frame specification predicates. parseBlockHeader_blockSize_lt proved via bv_decide (#647) — corrected from false _le statement to true < 2^21 bound. 0 sorry remaining.
  • Zip/Spec/ZstdSequence.lean (#632, merged): sequence validity predicates and block size validation. executeSequences_output_length proved (#653) via refactoring executeSequences from forIn to explicit recursion. 0 sorry remaining.

Code modularity:

  • ZstdFrame.lean split (#599): monolithic 1059-line file split into ZstdHuffman.lean (400 lines), ZstdSequence.lean (392 lines), ZstdFrame.lean (302 lines). Error prefixes normalized. This resolved the merge conflict bottleneck by reducing concurrent modification surface.
  • Block size and window size validation (#613): decompressBlocks validates block size ≤ 128KB, executeSequences validates match offsets against window size. 4 validation tests added.

Code quality reviews:

  • ZstdFrame.lean audit (#583): 1059 lines reviewed. Splitting plan identified and executed via #599.
  • Fse.lean + XxHash.lean audit (#579): 481 lines reviewed. No critical issues found.
  • Stale PR triage (#611): closed 4 conflicted PRs (#561, #577, #602, #596) and 2 stale issues (#568, #558). Unblocked #540 and #552 for fresh work.
  • Label cleanup (#612): removed stale has-pr labels, reviewed and merged PR #606 (Huffman spec).
  • Final bare simp sweep (#607): completed with DeflateEncodeDynamicProps residual fixed.
  • Zstd spec PR review (#624): reviewed #618 (XxHash) and #619 (FSE sum). Noted one bare simp in #619 and CLAUDE.md modification in #618.

Self-improvement:

  • Merge conflict cascade analysis (#597): documented recovery-of-recovery anti-pattern and prevention strategies in agent-pr-recovery skill.
  • Zstd spec infrastructure retrospective (#623): created lean-zstd-spec-pattern skill documenting file structure and naming conventions. Updated lean-monad-proofs with eq_of_beq pattern and split vs by_cases decision guide.

Compressed block integration (#651, milestone):

  • Wired full compressed block pipeline end-to-end in decompressBlocks: resolveSequenceFseTables → backward bitstream → decodeSequencesexecuteSequences
  • Added PrevFseTables struct for FSE Repeat mode across blocks
  • All three Zstd block types (raw, RLE, compressed) now decompress through a unified pipeline
  • This was the critical integration step (issue #552) that had been blocked by merge conflict cascades for multiple summary periods

Zstd spec quality audit (#650):

  • Eliminated 12 bare simp occurrences across ZstdHuffman.lean and ZstdSequence.lean spec files
  • Removed xxHash64_deterministic (tautological — was just rfl)
  • Added missing docstrings to specification theorems

Test infrastructure expansion:

  • ZstdNative.lean test monolith split into 3 focused files (#630): ZstdFrameNative.lean, ZstdFseNative.lean, ZstdHuffmanNative.lean

WF refactoring (continued, #667, #671):

  • decompressBlocksWF (#667): replaced opaque forIn loop with well-founded recursion on remaining data, proved parseBlockHeader_pos_eq
  • buildZstdHuffmanTable fill loops (#671): extracted fillHuffmanTableInnerWF and fillHuffmanTableWF from opaque for loops, proved fillHuffmanTableInnerWF_preserves_size and fillHuffmanTableWF_preserves_size

Spec proofs (14-PR batch, Mar 6):

  • #664: decompressFrame_contentSize_eq and decompressFrame_checksum_valid — characterizing properties relating frame output to header metadata
  • #672: copyBytes_getElem_lt and copyMatch_getElem_lt — existing buffer content preserved during sequence execution
  • #677: decompressRawBlock_content, decompressRawBlock_pos_eq, decompressRLEBlock_content, decompressRLEBlock_pos_eq — raw and RLE block content preservation and position advance
  • #681: resolveOffset_repeat1_val, resolveOffset_repeat2_val, resolveOffset_repeat3_val — exact return values for repeat offset codes
  • #685: litLenExtraBits_size, matchLenExtraBits_size, decodeLitLenValue_small, decodeMatchLenValue_small, decodeOffsetValue_positive — RFC table sizes and decoded value bounds

Conformance testing (#680, #686):

  • End-to-end test matrix: 48 combinations of FFI compress → native decompress across 4 compression levels × 4 data patterns × 3 sizes
  • Tests in ZipTest/ZstdConformance.lean
  • 36/48 passing (12 text-pattern Huffman failures, known bug tracked in #703)

Spec proofs (12-PR batch, Mar 6):

This batch deepened the sequence execution spec toolkit in ZstdSequence.lean (now 47 theorems/lemmas, 740 lines) across three areas:

Sequence execution framework:

  • #700: executeSequences_empty (base case), executeSequences_loop_output_size_ge (output size monotonicity)
  • #714: executeSequences_loop_cons (single-step unfolding), executeSequences_loop_cons_output_size (output size composition)

Content theorems and value bounds:

  • #690: copyBytes_getElem_ge and copyMatch_getElem_ge — content of newly written bytes during sequence execution
  • #704: copyMatch_loop_getElem_ge and copyMatch_getElem_ge — general content theorem for copyMatch including overlapping regions (subsumes the non-overlapping case)
  • #705: decodeMatchLenValue_ge_three and decodeOffsetValue_ge_two — RFC 8878 value decoding bounds via decide_cbv

resolveOffset completeness:

  • #687: resolveOffset_shifted1_val, resolveOffset_shifted2_val, resolveOffset_shifted3_val — exact values and history rotation for shifted repeat offsets (litLen = 0 case)
  • #695: resolveOffset_history_valid_large and resolveOffset_history_valid_repeat — ValidOffsetHistory preservation
  • #710: resolveOffset_positive_shifted12 (shifted repeat codes 1–2 positivity), resolveOffset_positive_all (unified positivity covering all cases)

Infrastructure:

  • #694, #699: stale PR cleanup (closed #686 duplicate, #689) + composability audit identifying 4 gaps in the executeSequences proof path
  • #711: skills update — added WF helper functions, table correctness, exhaustive case analysis, loop invariant sections to lean-zstd-spec-pattern; added split/letFun chains, inline show proofs to lean-monad-proofs

Position advancement spec campaign (16-PR batch, Mar 6):

This batch focused on proving that Zstd parsing functions advance their position correctly — a prerequisite for composing position bounds into end-to-end frame position proofs.

Position specs proved:

  • #723: decompressBlocksWF_pos_gt (output monotonicity and position advancement)
  • #724: parseSequencesHeader_pos_gt, parseSequencesHeader_pos_bounded (advances by 1–4 bytes), parseSequencesHeader_byte0_zero (zero-case characterization)
  • #730: skipSkippableFrame_pos_eq (exact position formula), skipSkippableFrame_pos_gt
  • #738: buildRleFseTable structural specs, resolveSingleFseTable_predefined_pos
  • #739: parseLiteralsSection raw/RLE position advancement and huffTable specs
  • #740: resolveSingleFseTable_rle_pos (RLE advances by 1), resolveSingleFseTable_repeat_pos (repeat mode position unchanged)
  • #753: parseFrameHeader_pos_gt (strict advancement), parseFrameHeader_pos_ge (minimum bound)

Structural invariants and sorry reduction:

  • #707: weightsToMaxBits_valid proved (corrected false bound from 1 <<< (m - 1) to 1 <<< m), removing 1 sorry from ZstdHuffman.lean
  • #718: buildFseTable_size_eq (table size = 1 <<< accuracyLog), buildFseTable_accuracyLog_eq (accuracy log passthrough)
  • #733: BackwardBitReader base-case specs — isFinished characterization, readBits zero returns current state, error characterization
  • #748: buildPredefinedFseTables_accuracyLogs proved; buildPredefinedFseTables_success blocked by opaque while loop (adds 1 sorry to Fse.lean)

WF recursion:

  • #744: decompressZstd refactored from opaque while loop to well-founded recursion (decompressZstdWF with termination_by data.size - pos). Runtime guards ensure non-advancing frames throw errors. This was the last opaque while loop in the top-level Zstd pipeline.

Quality reviews:

  • #725: ZstdSequence.lean — combined duplicate loop induction proofs into single pass
  • #749: Fse.lean — replaced all 22 bare simp/simp_all with explicit simp only
  • #754: ZstdSequence.lean — eliminated 7 remaining bare simp calls

Housekeeping:

  • #729: closed stale PR #721, closed issue #701, unclaimed #703 for reassignment

Conformance testing:

  • 48/48 test matrix passing (FFI compress → native decompress, 4 levels × 4 patterns × 3 sizes)

11-PR batch (Mar 6–7): sorry reduction, position specs, quality reviews:

Sorry reduction:

  • #776: buildPredefinedFseTables_success proved — removed the last Fse.lean sorry. Used structural success proof (each loop body returns .ok) rather than WF refactoring. Added buildFseTable_ok_of_valid and forIn_pure_ok helpers. Sorry count: 4 → 3.

Position advancement specs:

  • #760: BitReader.readBits exact bitPos advancement (readBits_bitPos_eq, readBits_pos_eq). Previously the last uncovered function in the position campaign.
  • #763: decompressFrame position advancement (decompressFrame_pos_gt) — composed from parseFrameHeader_pos_gt and decompressBlocksWF_pos_gt.
  • #772: resolveSingleFseTable FSE-compressed mode position advancement (resolveSingleFseTable_fseCompressed_pos_gt). Composed from decodeFseDistribution_bitPos_ge and buildFseTable passthrough.
  • #779: parseLiteralsSection compressed path position specs — unified parseLiteralsSection_pos_gt covering all literal types (0–3).
  • #781: resolveSequenceFseTables position composition — composed three resolveSingleFseTable calls proving resolveSequenceFseTables_pos_ge.

Characterizing properties:

  • #768: windowSizeFromDescriptor positivity (windowSizeFromDescriptor_pos) and minimum bound (windowSizeFromDescriptor_ge_1024). Proved via exhaustive evaluation over all 256 UInt8 inputs.

WF refactoring:

  • #765: buildFseTable while loop refactored to WF recursion (buildFseTableStepWF). Proves buildFseTableStepWF_pos_lt for position advancement and buildFseTableStepWF_cells_size for size preservation.

Quality reviews (bare simp_all elimination):

  • #764: ZstdHuffman.lean — replaced 3 bare simp_all, extracted duplicate monadic bind peeling into buildZstdHuffmanTable_ok_elim helper (-36 lines).
  • #778: BitstreamCorrect.lean — replaced 4 bare simp_all with targeted tactics (dsimp, eq_of_beq, explicit split cases).
  • #780: BitReaderInvariant.lean — replaced 3 bare simp_all with explicit simp_all only [...] lemma lists.

11-PR batch (Mar 7): value bounds, top-level specs, quality reviews:

Track E position/value specs (5 PRs):

  • #792: readBits_value_lt_pow2 — BackwardBitReader.readBits produces value < 2^n
  • #795: buildFseTable_numBits_le — cells numBits ≤ accuracyLog (ValidFseTable condition c). Used set!_preserves_forall helper for dependent type reasoning.
  • #797: New Zip/Spec/ZstdFrame.lean — first specs for decompressZstdWF: decompressZstdWF_base, _single_standard_frame (base case and single-frame equivalence)
  • #803: readBits_totalBitsRemaining_sub, _lt — exact bit consumption tracking for BackwardBitReader.readBits. Key discovery: ↓reduceIte doesn't reduce concrete numeral comparisons.
  • #808: decompressZstdWF_single_skippable_frame, _skip_then_standard — skippable frame composition specs for the top-level decompressor

Quality reviews (5 PRs):

  • #787: Zstd.lean spec audit — deduplicated parseFrameHeader position proofs, documented grind usage (-15 lines)
  • #796: Closed stale PR #789, compressed decompressBlocksWF proof boilerplate (-37 lines)
  • #802: Zstd.lean quality pass — documented grind, converted bare simp
  • #807: XxHash.lean quality pass — converted 4 bare simp to simp only
  • #811: DeflateEncodeDynamic.lean quality pass — replaced 5 simp_all with targeted tactics (termination proofs: simp only + omega; Bool→Prop: beq_eq_false_iff_ne)

Skill updates (1 PR):

  • #798: Updated lean-zstd-spec-pattern, lean-monad-proofs, lean-zstd-patterns with position-spec campaign patterns and BackwardBitReader proof techniques

BackwardBitReader lifecycle coverage now complete:

  • init → totalBitsRemaining bound and startPos
  • readBits → value bound (val < 2^n), exact bit consumption, bitPos advancement
  • isFinished → characterization

Remaining:

  • Prove remaining sorry stubs: 3 in XxHash (UInt64 test vectors too expensive for kernel evaluation — intractable without native_decide)
  • Compose position specs into end-to-end frame position theorem
  • Spec-level decoder with correctness proofs (algorithmic correspondence between native and spec decoder, following the DEFLATE B3 pattern)
  • Compressor + roundtrip proof

12-PR batch (Mar 7): le_size campaign + characterizing properties + quality reviews:

This batch established position-within-bounds invariants (le_size theorems) across 6 Zstd parsing functions and extended characterizing properties for compressed headers and sequence parsing.

Track E le_size campaign (6 PRs):

  • #825: skipSkippableFrame_le_size, decompressRawBlock_le_size, decompressRLEBlock_le_size — frame-level position bounds
  • #835: parseBlockHeader_le_size, parseFrameHeader_le_size — header parsers stay within data bounds
  • #834: parseSequencesHeader_le_size, parseSequencesHeader_medium_encoding — sequence header position bound and medium encoding characterization

Characterizing properties (3 PRs):

  • #833: decodeHuffmanSymbol specs — bit consumption monotonicity (decodeHuffmanSymbol_pos_mono) and bounds
  • #829: parseCompressedLiteralsHeader specs — headerSize, fourStreams, regen_bound characterizing compressed literal header fields
  • #826: parseSequencesHeader_numSeq_small — small encoding extraction (1-byte case: numSeq = byte0 when 0 < byte0 < 128)

Top-level decompressor specs (1 PR):

  • #839: decompressZstdWF_output_size_ge (output monotonicity via functional induction) and decompressZstd_single_frame (API-level single-frame theorem)

Quality reviews (2 PRs):

  • #832: DeflateSuffix.lean — converted 3 bare simp_all
  • #822: Fse.lean — fixed bare simp, documented grind, combined rw calls

The le_size campaign now covers the "easy" functions (those with simple bounds checks): skipSkippableFrame, decompressRawBlock, decompressRLEBlock, parseBlockHeader, parseFrameHeader, parseSequencesHeader. Remaining le_size gaps require deeper invariants: parseLiteralsSection (variable-length compressed headers), BitReader operations, FSE distribution chain, decompressBlocks loop, and frame-level composition.

Summary: The Zstd spec infrastructure now spans 6 files with 195 theorems/lemmas: ZstdSequence (57), Fse (50), ZstdHuffman (43), Zstd (27), XxHash (12), ZstdFrame (6). Total spec line count: 4,448 lines.

11-PR batch (Mar 7–8): le_size capstone + content preservation + FSE validity + quality reviews:

This batch completed the le_size campaign to the frame level, started content preservation proofs for sequence execution, proved FSE table validity composition, and continued quality reviews across DEFLATE spec files.

Track E le_size campaign completion (3 PRs):

  • #848: parseHuffmanTreeDescriptor_le_size — tree descriptor position bounds
  • #856: decompressBlocksWF_le_size — block loop position within data bounds (composed from parseBlockHeader_le_size + per-block-type bounds)
  • #861: decompressFrame_le_size — frame-level capstone. When decompressFrame succeeds, returned position ≤ data.size. Composed from parseFrameHeader_le_size and decompressBlocksWF_le_size. This completes the le_size chain: readBitreadBitsparseLiteralsSectionparseHuffmanTreeDescriptordecompressBlocksWFdecompressFrame.

Track E content preservation (1 PR):

  • #865: executeSequences_loop_getElem_lt — sequence execution loop preserves previously written output bytes. First content preservation theorem for the Zstd sequence execution pipeline.

Track E BackwardBitReader invariants (1 PR):

  • #858: readBits_data_eq, readBits_startPos_eq, init_data_eq, init_startPos_eq — field preservation through BackwardBitReader operations.

Track E FSE table validity (1 PR):

  • #872: buildFseTable_symbol_lt (symbol indices within bounds through all 4 construction loops) and buildFseTable_valid (composed ValidFseTable predicate). Used forIn_range_preserves helper for loop invariants. Added toUInt16_toNat_lt_of_lt for UInt16 bounds reasoning.

Quality reviews (4 PRs):

  • #853: ZstdHuffman.lean spec audit — eliminated bare simp, compressed proofs, improved consistency (-121 lines net)
  • #857: DeflateEncodeDynamic.lean — converted all 7 bare simp to simp only with explicit lemma lists
  • #862: DeflateEncodeDynamic.lean quality pass — aggregated skill updates and progress documentation alongside bare simp conversions
  • #869: DeflateDynamicEmit.lean — removed 1 unused linter pragma and 2 unnecessary maxRecDepth 2048 pragmas (default settings sufficient)

Maintenance (1 PR):

  • #866: Rebased PR #857 to resolve merge conflicts in progress files

11-PR batch (Mar 8): Huffman validity, content preservation, offset history, bare simp completion, multi-frame composition:

This batch advanced Track E spec coverage across four dimensions and completed the bare simp campaign project-wide.

Track E table validity (1 PR):

  • #884: buildZstdHuffmanTable_numBits_le (cells numBits within max bit depth) and buildZstdHuffmanTable_valid (composed ValidHuffmanTable predicate). 3 helper lemmas for fill loop invariants.

Track E content/field preservation (2 PRs):

  • #887: decompressZstdWF_prefix (every byte in the initial accumulator is preserved at the same index in the result — append-only property via WF induction) and decompressZstd_empty (empty input → empty output).
  • #894: decodeHuffmanSymbol_data_eq and decodeHuffmanSymbol_startPos_eq — BackwardBitReader data and startPos fields are unchanged through Huffman symbol decoding.

Track E offset history validity (1 PR):

  • #880: resolveOffset_history_valid_of_fst_ne_zero and executeSequences_loop_history_validValidOffsetHistory threaded through the full sequence execution loop. This connects resolveOffset's per-step history validity to loop-level invariants.

Track E multi-frame composition (1 PR):

  • #901: decompressZstdWF_standard_then_standard (two consecutive standard frames produce concatenated output at WF level) and decompressZstd_two_frames (same at API level). First compositional spec for multi-frame Zstd streams.

Bare simp campaign completion (5 PRs):

  • #879: DeflateDynamicHeader.lean — bare simp + linter pragma cleanup
  • #885: DeflateEncodeDynamicProps.lean first half (lines 17–310)
  • #886: DeflateEncodeDynamicProps.lean second half (lines 311–695)
  • #892: Huffman.lean — completes the non-intentional bare simp campaign
  • #900: Fse.lean, HuffmanKraft.lean, BitstreamComplete.lean — final sweep

With #900, zero standalone bare simp calls remain anywhere in the codebase (Zip/, ZipForStd/, Native/). Only the 15 documented intentional instances in DeflateSuffix.lean persist. 4 bare simp_all calls remain in DeflateEncode, DeflateStoredCorrect, EmitTokensCorrect, and InflateCorrect (see correction in Mar 8 batch notes).

Skill updates (1 PR):

  • #891: Created lean-content-preservation skill from 16-PR batch patterns. Updated lean-zstd-patterns, lean-zstd-spec-pattern, lean-content-preservation with recurring proof strategies.

12-PR batch (Mar 8): FSE validity chain capstone, WF decoder refactors, quality reviews:

This batch completed the FSE table validity chain from predefined tables through resolveSequenceFseTables, added WF variants of two decoder functions with output size theorems, proved API-level specs for sequence history and skippable frames, and continued quality reviews across core spec files.

FSE table validity chain completion (3 PRs):

  • #911: buildPredefinedFseTables_valid — all three predefined FSE tables satisfy ValidFseTable (litLen at accuracyLog 6, matchLen at 6, offset at 5)
  • #919: resolveSingleFseTable_le_size — per-mode position bound preservation (predefined/RLE/repeat modes all return pos' ≤ data.size)
  • #924: resolveSingleFseTable_fseCompressed_valid (FSE-compressed mode produces ValidFseTable), resolveSingleFseTable_valid_ex (unified across all 4 modes), and resolveSequenceFseTables_valid (all three sequence tables valid when previous tables valid). This is the FSE validity chain capstone: buildFseTable_validresolveSingleFseTable_*_validresolveSequenceFseTables_valid.

WF decoder refactors with output size theorems (2 PRs):

  • #929: decodeHuffmanStreamWF — structurally recursive variant of decodeHuffmanStream with two composition theorems: decodeHuffmanStreamWF_size (output = acc.size + count) and decodeHuffmanStreamWF_totalBitsRemaining_le (bit budget monotonicity)
  • #930: decodeFseSymbolsWF — structurally recursive variant of decodeFseSymbols with decodeFseSymbolsWF_size (output has exactly count elements). Both WF variants replace opaque for loops that could not be unfolded in proofs.

API-level specs (2 PRs):

  • #907: executeSequences_history_valid (ValidOffsetHistory preserved through sequence execution) and executeSequences_history_size (output history maintains 3 entries). These lift loop-level invariants to the API boundary.
  • #912: decompressZstd_single_skippable (single skippable frame → empty output) and decompressZstd_skip_then_standard (skip + standard → only standard content). API-level specializations of WF-level frame theorems.

Quality reviews (4 PRs):

  • #908: ZstdHuffman.lean — extracted readBits_elim pattern, removed dead code
  • #915: Zstd.lean — unfold_except tactic macro replacing 20 identical monadic unfolding calls, grind audit (6 calls verified appropriate), removed 4 unused termination_by/decreasing_by clauses (-21 lines)
  • #918: Fse.lean — proof optimization (-87 lines, 5.5%), 2 bare simp_all eliminated
  • #926: Deflate.lean — 3 bare simp_all to targeted tactics, proof optimization (-67 lines, 6.3%), dead code removal (le_bytes_eq_encodeLEU16, 30 lines)

Skill updates (1 PR):

  • #925: Meditate: updated lean-zstd-spec-pattern with table validity chain and frame composition patterns; updated proof-review-checklist with compression and helper extraction patterns

13-PR batch (Mar 8): WF maturation, content characterization, simp_all review throughput:

This batch advanced Track E along two fronts — WF refactoring enabling output size theorems, and content characterization for raw/RLE literals and blocks — while continuing the simp_all elimination campaign across DEFLATE spec files.

Track E feature PRs (5):

  • #935: decodeFseLoop_probs_ge_neg1 — FSE loop probability bound invariant. When decodeFseLoop succeeds and all input probs have .toInt ≥ -1, all output probs also have .toInt ≥ -1. 8 supporting lemmas covering Array.push preservation, pushZeros bounds, readProbValue output bounds.
  • #940: parseHuffmanTreeDescriptor_valid (composed ValidHuffmanTable from descriptor parsing) and parseHuffmanTreeDescriptor_maxBits_pos (maxBits ≥ 1). Validity composition connecting tree descriptor parsing to table construction.
  • #943: decodeSequencesWF — structurally recursive variant of decodeSequences with decodeSequencesWF_size (output has exactly numSeq elements when numSeq > 0). Replaces opaque for loop.
  • #951: decodeFourHuffmanStreamsWF — structurally recursive variant of decodeFourHuffmanStreams with decodeFourHuffmanStreamsWF_size (output buffer size = regenSize). Combines four individual decodeHuffmanStreamWF_size.
  • #952: parseLiteralsSection_raw_eq_extract (raw literals are an exact contiguous slice of input data) and parseLiteralsSection_rle_all_eq (RLE literals are all-equal). First content characterizations for literal sections.

Track E content — block level (1 PR):

  • #955: decompressBlocksWF_single_raw and decompressBlocksWF_single_rle — single-block output characterization. When the block loop processes a single last raw/RLE block, the result is output ++ block at the correct position.

Review PRs — simp_all campaign (7):

  • #936: HuffmanCorrectLoop.lean — simp_all conversion + proof optimization
  • #939: HuffmanCorrect.lean — simp_all audit + proof optimization
  • #944: HuffmanEncodeCorrect.lean — simp_all + bare simp elimination
  • #947: DecodeCorrect.lean — simp_all conversion + proof optimization
  • #950: DecodeComplete.lean — simp_all conversion + proof optimization
  • #956: BitstreamWriteCorrect.lean — simp_all conversion + proof optimization
  • #961: DeflateFixedTables.lean — simp_all conversion + proof optimization

Summary: The Zstd spec infrastructure now spans 6 files with 272 theorems/lemmas: ZstdSequence (77), Fse (73), ZstdHuffman (65), Zstd (33), XxHash (12), ZstdFrame (12). Total spec line count: 6,267 lines.

Bare simp_all correction: The previous batch claimed "0 bare simp_all remaining" but 4 instances survived in DeflateEncode.lean, DeflateStoredCorrect.lean, EmitTokensCorrect.lean, and InflateCorrect.lean. These are pre-existing residuals, not regressions from this batch. The simp_all campaign has made substantial progress (12 simp_all only conversions across spec files) but is not yet complete.

WF refactoring status: The WF refactoring pipeline now covers 5 Zstd decoder functions: decodeFseSymbolsWF, decodeHuffmanStreamWF, decodeFourHuffmanStreamsWF, decodeSequencesWF, decompressBlocksWF. Each WF variant replaces an opaque for/while loop that could not be unfolded in proofs, enabling output size theorems and loop invariant reasoning.

Content characterization emergence: This batch introduced the first literal section content specs (raw extract, RLE all-equal) and composed them to the block loop level (single-block raw/RLE output = output ++ block). Combined with the existing executeSequences_loop_getElem_lt (sequence execution preserves previously written bytes), this represents the beginning of end-to-end content characterization for Zstd frames — though significant gaps remain between the literal/sequence level and full frame output.

10-PR batch (Mar 8): content pipeline maturation + simp_all campaign completion:

This batch advanced the Track E content pipeline from single-block characterization to multi-block composition and frame-level output, while completing the simp_all campaign down to 2 remaining instances.

Track E content pipeline — block level (3 PRs):

  • #962: decompressBlocksWF_raw_step and decompressBlocksWF_rle_step — non-last block continuation theorems. When the block loop processes a non-last raw/RLE block followed by more blocks, the intermediate output is output ++ block at the correct position.
  • #988: decompressBlocksWF_two_raw and decompressBlocksWF_two_rle — two-block composition for same-type blocks. Output = output ++ block1 ++ block2.
  • #1000: decompressBlocksWF_two_compressed_literals — two-block composition for compressed-literals blocks (non-last compressed + last compressed).

Track E content pipeline — mixed types (1 PR):

  • #997: decompressBlocksWF_raw_then_rle and decompressBlocksWF_rle_then_raw — mixed-type two-block composition. Raw followed by RLE and vice versa.

Track E content pipeline — frame level (1 PR):

  • #974: decompressFrame_raw_content and decompressFrame_rle_content — single-block frame output characterization. When a frame contains a single raw/RLE block, the frame-level output matches the block content.

Merge conflict recovery (2 PRs):

  • #989: rebased compressed sequences theorems (#977) onto master after merge conflicts from concurrent PRs
  • #982: rebased compressed literals-only theorems (#970) onto master

simp_all campaign (3 PRs):

  • #976: HuffmanTheorems + EmitTokensCorrect + DeflateStoredCorrect — converted all bare simp_all in these files to explicit alternatives. Reduces bare simp_all count from 4 to 2.
  • #996: Fse.lean + HuffmanEncode.lean + HuffmanCorrect.lean — completes the simp_all campaign across these files (0 remaining in each).
  • #995: ZstdSequence.lean — simp_all conversion + proof optimization.

Quality reviews (2 PRs):

  • #981: GzipCorrect.lean + ZlibCorrect.lean — bare simp cleanup + proof optimization
  • #971: BitReaderInvariant.lean — simp_all conversion + proof optimization

Content pipeline status: The Track E content characterization now covers:

Block type Single Step (non-last) Two-block (same) Two-block (mixed) Frame (single)
Raw done done done raw+RLE done done
RLE done done done RLE+raw done done
Compressed (literals-only) done done done
Compressed (with sequences)

simp_all campaign status: 2 bare simp_all remaining (DeflateEncode.lean, InflateCorrect.lean). Both are covered by existing review issue #968. Down from ~50+ at campaign start to 2.

Summary: The Zstd spec infrastructure now spans 6 files with 333 theorems/lemmas: ZstdSequence (84), Fse (80), ZstdHuffman (73), Zstd (59), XxHash (25), ZstdFrame (12). Total spec line count: 6,719 lines.

15-PR batch (Mar 8–9): compressed composition matrix, API-level content, Zstd benchmarking:

This batch filled in the block-level compressed composition matrix, extended frame-level content to compressed block types, lifted content theorems to the API level, added Zstd decompression benchmarking, and continued quality reviews.

Track E block-level compositions (4 PRs):

  • #1013: decompressBlocksWF_two_compressed_sequences_blocks (homogeneous compSeq pair) and decompressBlocksWF_compressed_seq_then_raw (compSeq+raw)
  • #1019: decompressBlocksWF_compressed_literals_then_raw and decompressBlocksWF_compressed_literals_then_rle (compLit+raw, compLit+RLE)
  • #1033: decompressBlocksWF_raw_then_compressed_sequences, decompressBlocksWF_rle_then_compressed_sequences, decompressBlocksWF_compressed_seq_then_rle (raw/RLE+compSeq, compSeq+RLE)
  • #1034: decompressBlocksWF_compressed_seq_then_compressed_lit and decompressBlocksWF_compressed_lit_then_compressed_seq (cross-compressed pairs)

Track E frame-level content (3 PRs):

  • #1020: decompressFrame_raw_then_rle_content and decompressFrame_rle_then_raw_content (mixed raw/RLE two-block frames)
  • #1027: decompressFrame_single_compressed_literals_content and decompressFrame_single_compressed_sequences_content (single compressed blocks)
  • #1051: decompressFrame_compressed_lit_then_raw_content and decompressFrame_compressed_lit_then_rle_content (compressed-first + raw/RLE)

Track E API-level content (2 PRs):

  • #1029: decompressZstd_two_raw_blocks_content and decompressZstd_two_rle_blocks_content (two-block raw/RLE at API level)
  • #1039: decompressZstd_single_compressed_literals and decompressZstd_single_compressed_sequences (single compressed-block at API level)

Track D (1 PR):

  • #1038: Zstd decompression benchmarking suite in ZipTest/ZstdBench.lean — native vs FFI across 4 compression levels × 4 data patterns × 3 sizes

Quality reviews (3 PRs):

  • #1032: DynamicTreesComplete + DynamicTreesCorrect cleanup
  • #1040: DecodeCorrect.lean + GzipCorrect.lean bare simp cleanup
  • #1046: ZstdFrame.lean — 1 bare simp eliminated, helper lemma extraction

Merge conflict recovery (1 PR):

  • #1023: rebased PR #1009 onto master, closed stale #1006

Self-improvement (1 PR):

  • #1024: Meditate — composition matrix scaling patterns, review campaign completion analysis, conflict resolution patterns

Block-level composition matrix status (14/16):

Raw RLE CompLit CompSeq
Raw + done done done
RLE + done done done
CompLit + done done done done
CompSeq + done done done done

2 remaining: raw+compLit and RLE+compLit (covered by open PR #1011).

Content pipeline status (updated):

Block type Single block Step (non-last) Two-block (same) Two-block (mixed) Frame (single) Frame (two-block) API (single)
Raw done done done raw+RLE done two_raw, raw+RLE two_raw
RLE done done done RLE+raw done two_rle, RLE+raw two_rle
CompLit done done done compLit+raw, +RLE done compLit+raw, +RLE done
CompSeq done done done compSeq+raw, +RLE, +compLit, compLit+compSeq done done

simp_all campaign status: 0 bare simp_all remaining (DeflateEncode and InflateCorrect instances now have explicit lemma arguments: simp_all [beq_iff_eq] and simp_all [← UInt32.toNat_inj] respectively). Campaign complete.

Summary: The Zstd spec infrastructure now spans 6 files with 361 declarations: ZstdSequence (84), Fse (81), ZstdHuffman (73), Zstd (78), XxHash (26), ZstdFrame (19). Total spec line count: 8,172 lines.

12-PR batch (Mar 9): two-block content theorem completion wave + simp_all elimination + quality reviews:

This batch completed the block-level composition matrix, extended frame-level content coverage across most two-block combinations, lifted several pairings to API level, completed the simp_all elimination campaign, and continued proof quality reviews.

Track E block-level completion (1 PR):

  • #1068: decompressBlocksWF_raw_then_compressed_literals and decompressBlocksWF_rle_then_compressed_literals — completing the 16/16 block-level composition matrix.

Track E frame-level content (4 PRs):

  • #1055: decompressFrame_compressed_seq_then_raw_content and decompressFrame_compressed_seq_then_rle_content (compSeq-first + raw/RLE)
  • #1060: decompressFrame_compressed_seq_then_compressed_lit_content and decompressFrame_compressed_lit_then_compressed_seq_content (cross-compressed)
  • #1073: decompressFrame_two_compressed_literals_blocks_content and decompressFrame_two_compressed_sequences_blocks_content (homogeneous compressed)

Track E API-level content (3 PRs):

  • #1052: decompressZstd_raw_then_rle_content and decompressZstd_rle_then_raw_content (mixed raw/RLE two-block frames at API level)
  • #1060: decompressZstd_single_raw_block_content and decompressZstd_single_rle_block_content (single raw/RLE at API level)
  • #1061: decompressZstd_compressed_lit_then_raw_content and decompressZstd_compressed_lit_then_rle_content (compLit+raw/RLE at API level)
  • #1074: decompressZstd_compressed_seq_then_compressed_lit_content and decompressZstd_compressed_lit_then_compressed_seq_content (cross-compressed at API level)

Track E FSE spec (1 PR):

  • #1067: readProbValue_pos_le_size and decodeFseDistribution_pos_le_size — position-within-bounds chain for FSE distribution decoding

simp_all campaign final completion (1 PR):

  • #1069: Converted the last 4 simp_all calls across Deflate.lean, DeflateEncode.lean, and InflateCorrect.lean to simp only with explicit lemma lists. 0 simp_all now remain across all spec files.

Quality reviews (2 PRs):

  • #1056: DeflateStoredCorrect + DeflateFixedCorrect — proof optimization
  • #1062: DeflateDynamicCorrect + DeflateDynamicFreqs — proof optimization

Self-improvement (1 PR):

  • #1066: Meditate — content pipeline template, hot file tracking, extraction heuristics

Block-level composition matrix status (16/16 — COMPLETE):

Raw RLE CompLit CompSeq
Raw + done done done done
RLE + done done done done
CompLit + done done done done
CompSeq + done done done done

Two-block content theorem coverage matrix (block / frame / API):

First \ Second Raw RLE CompLit CompSeq
Raw B/F/A B/F/A B/—/— B/—/—
RLE B/F/A B/F/A B/—/— B/—/—
CompLit B/F/A B/F/A B/F/— B/F/A
CompSeq B/F/— B/F/— B/F/A B/F/—

B = block-level, F = frame-level, A = API-level. All 4 single-block types are fully covered at all three levels.

Block-level: 16/16 complete. Frame-level: 12/16 covered (missing 4: raw+compLit, raw+compSeq, rle+compLit, rle+compSeq — all raw/RLE-first + compressed-second pairings). API-level: 8/16 covered (missing 8: the 4 frame-level gaps plus compLit+compLit, compSeq+raw, compSeq+rle, compSeq+compSeq).

The lifting pattern: block-level → frame-level → API-level. Each level composes the lower level's theorem with frame/API wrapping. Frame-level theorems add parseFrameHeader + checksum handling. API-level theorems add decompressZstdWF single-frame unwinding. The raw/RLE-first + compressed gap at frame level is the bottleneck — these require lifting the block-level compositions that include compressed-second blocks through decompressFrame.

simp_all campaign status: 0 simp_all remaining anywhere in Zip/Spec/. Campaign fully complete: all instances converted to simp only with explicit lemma lists or targeted alternatives. This is a significant proof quality milestone — every simplifier call in the spec layer now declares its dependencies explicitly.

Summary: The Zstd spec infrastructure now spans 6 files with 543 declarations: Fse (151), ZstdSequence (147), Zstd (102), ZstdHuffman (84), XxHash (32), ZstdFrame (27). Total spec line count: 9,503 lines.

10-PR batch (Mar 9–10): parsing completeness direction + API-level content completion + quality reviews:

This batch introduced a new proof direction — parsing completeness ("if input is well-formed, parsing succeeds") — and completed the two-block API-level content coverage to 14/16 combinations.

Track E two-block API-level content completion (3 PRs):

  • #1083: decompressZstd_raw_then_compressed_lit_content and decompressZstd_rle_then_compressed_lit_content (raw/RLE + compLit at API level)
  • #1087/#1095: Merge conflict fixes for PR #1076 (compSeq+raw/RLE) and PR #1075 (compSeq+raw/RLE at API level), plus ZstdFrame.lean API-level coverage audit
  • #1101: decompressZstd_raw_then_compressed_seq_content and decompressZstd_rle_then_compressed_seq_content (raw/RLE + compSeq at API level)

Track E API-level metadata validation (1 PR):

  • #1096: decompressZstd_single_frame_contentSize (when frame header declares contentSize = some n, output has exactly n bytes) and decompressZstd_single_frame_checksum (when frame has checksum enabled, xxHash64 of output matches stored checksum). These are characterizing properties — they relate frame metadata to decompressed content.

Track E parsing completeness — new proof direction (3 PRs):

  • #1097: parseBlockHeader_succeeds, decompressRawBlock_succeeds, decompressRLEBlock_succeeds — first parsing completeness theorems. Prove that when the input has enough bytes, these functions return .ok. Used bv_decide for bitwise AND reasoning in block header catch-all case.
  • #1108: parseFrameHeader_succeeds — proves frame header parsing succeeds when data has the correct magic number and enough bytes (via frameHeaderMinSize helper). Required maxRecDepth 4096 and maxHeartbeats 800000 for deeply nested monadic expressions.
  • #1114: parseCompressedLiteralsHeader_succeeds and parseLiteralsSection_succeeds_treeless — compressed literals header and treeless literals section completeness.

Quality reviews (2 PRs):

  • #1109: ZstdHuffman.lean audit — extracted parseHuffmanTreeDescriptor_ok_elim shared eliminator combining three separate case analyses (-62 lines, -4.3%).
  • #1112: Fse.lean audit — extracted decodeFseDistribution_ok_decompose helper from 5 theorems sharing identical unfolding boilerplate (-47 lines, -2.4%).

Merge conflict resolution (1 PR):

  • #1087: Resolved merge conflicts for raw/RLE+compSeq frame-level content

Parsing completeness — concept and coverage: This is the converse of existing correctness theorems. Where correctness says "if parsing returns .ok, the result satisfies properties P", completeness says "if input satisfies conditions C, parsing returns .ok". Together they give "well-formed input ↔ successful parsing". This is a prerequisite for composing "well-formed Zstd frame → decompression succeeds" end-to-end.

Current parsing completeness coverage:

  • parseBlockHeader_succeeds (pos + 3 ≤ data.size)
  • decompressRawBlock_succeeds (pos + blockSize ≤ data.size)
  • decompressRLEBlock_succeeds (pos + 1 ≤ data.size)
  • parseFrameHeader_succeeds (magic number + enough bytes per header format)
  • parseCompressedLiteralsHeader_succeeds (pos + headerSize ≤ data.size)
  • parseLiteralsSection_succeeds_treeless (treeless path, sufficient bytes)

Remaining gaps: parseLiteralsSection Huffman-compressed path, parseSequencesHeader, resolveSingleFseTable (FSE-compressed mode), resolveSequenceFseTables composition, decompressBlocks loop, full frame.

Two-block content theorem coverage matrix (block / frame / API):

First \ Second Raw RLE CompLit CompSeq
Raw B/F/A B/F/A B/F/A B/F/A
RLE B/F/A B/F/A B/F/A B/F/A
CompLit B/F/A B/F/A B/F/— B/F/A
CompSeq B/F/A B/F/A B/F/A B/F/—

B = block-level, F = frame-level, A = API-level. All 4 single-block types are fully covered at all three levels.

Block-level: 16/16 complete. Frame-level: 16/16 complete. API-level: 14/16 covered (missing 2: compLit+compLit and compSeq+compSeq — homogeneous compressed pairs).

Summary: The Zstd spec infrastructure now spans 6 files with 563 declarations: Fse (152), ZstdSequence (147), Zstd (111), ZstdHuffman (86), XxHash (32), ZstdFrame (35). Total spec line count: 10,377 lines.

15-PR batch (Mar 10–11): completeness chain deepening + API-level content completion + review campaign:

This batch deepened the parsing completeness chain to cover nearly every sub-function in the Zstd decompression pipeline, completed the two-block API-level content matrix to 16/16, and advanced the code quality review campaign to 4 of 5 Zstd spec files.

Track E parsing completeness (7 PRs):

  • #1121: parseLiteralsSection_succeeds_compressed — completeness for Huffman-compressed literals (litType=2), the most complex literal path
  • #1123: resolveSequenceFseTables_succeeds — composed completeness theorem threading three resolveSingleFseTable calls
  • #1130: parseHuffmanTreeDescriptor_succeeds_fse and parseHuffmanWeightsFse_succeeds — FSE-path Huffman tree descriptor completeness
  • #1131: BackwardBitReader_init_succeeds — backward bitstream initialization completeness
  • #1134: resolveSingleFseTable_succeeds_fse (FSE-compressed mode) and decodeSequencesWF_succeeds_zero (zero-sequence case)
  • #1135: decodeHuffmanLiterals_succeeds_single and decodeHuffmanLiterals_succeeds_four — Huffman literal decoding completeness for both 1-stream and 4-stream modes
  • #1138: parseSequencesHeader_succeeds and parseSequencesHeader_succeeds_zero — universal parsing completeness with byte0 case dispatch

Track E characterization (1 PR):

  • #1144: parseBlockHeader_lastBlock_eq, parseBlockHeader_blockType_eq, parseBlockHeader_blockSize_eq — field characterization theorems proving each field equals specific bit ranges of the 24-bit header word

Track E API-level content completion (1 PR):

  • #1151: decompressZstd_two_compressed_literals_blocks_content and decompressZstd_two_compressed_sequences_blocks_content — the final two API-level content theorems, completing the 16/16 matrix

Quality reviews (4 PRs):

  • #1139: ZstdSequence.lean — proof body compression and refactoring
  • #1145: ZstdSequence.lean — proof quality audit
  • #1152: Zstd.lean — proof quality audit with frame_from_blocks tactic macro extraction and parseBlockHeader_data_not_le helper (−406 lines, −10.3%)
  • #1153: ZstdFrame.lean — proof quality audit

Housekeeping (2 PRs):

  • #1127: Meditate — parsing completeness maturity analysis, created lean-parsing-completeness skill (255 lines)
  • #1146: Stale issue cleanup — closed 2 moot issues, removed stale labels

Parsing completeness coverage (updated):

Function Status
parseBlockHeader done
decompressRawBlock done
decompressRLEBlock done
parseFrameHeader done
parseCompressedLiteralsHeader done
parseLiteralsSection (treeless) done
parseLiteralsSection (compressed, litType=2) done
parseHuffmanWeightsDirect done
parseHuffmanWeightsFse done
parseHuffmanTreeDescriptor (direct) done
parseHuffmanTreeDescriptor (FSE) done
BackwardBitReader.init done
decodeHuffmanLiterals (1-stream, 4-stream) done
resolveSingleFseTable (predefined/RLE/repeat/FSE) done
resolveSequenceFseTables (composed) done
parseSequencesHeader done
decodeSequencesWF (zero-case) done
parseLiteralsSection (raw/RLE) gap
decompressBlocksWF (composed) gap
executeSequences gap
Frame-level composed gap

The completeness chain now covers 17 functions/paths. Remaining gaps: parseLiteralsSection raw/RLE paths, decompressBlocksWF loop composition, executeSequences, and full frame-level composition.

Two-block content theorem coverage matrix (block / frame / API) — COMPLETE:

First \ Second Raw RLE CompLit CompSeq
Raw B/F/A B/F/A B/F/A B/F/A
RLE B/F/A B/F/A B/F/A B/F/A
CompLit B/F/A B/F/A B/F/A B/F/A
CompSeq B/F/A B/F/A B/F/A B/F/A

All 16/16 combinations covered at all three levels (block, frame, API). All 4 single-block types also fully covered at all three levels.

Review campaign progress: 4 of 5 Zstd spec files audited:

  • ZstdHuffman.lean (#1109, #853) — extracted shared eliminators, −4.3%
  • Fse.lean (#1112, #822) — extracted decomposition helpers, −2.4%
  • ZstdSequence.lean (#1145, #1139) — proof compression + quality audit
  • Zstd.lean (#1152) — tactic macro extraction, −10.3%
  • ZstdFrame.lean (#1153) — proof quality audit Remaining: XxHash.lean (previously audited at #807).

Spec declaration count note: Total spec declarations dropped from 1,819 to 1,793 despite net additions — the Zstd.lean review (#1152) removed dead code and deduplicated helper lemmas more aggressively than new theorems were added. The Zstd-specific declaration count rose from 563 to 572 (net +9 new theorems).

Summary: The Zstd spec infrastructure now spans 6 files with 572 declarations: ZstdSequence (168), Fse (148), Zstd (106), ZstdHuffman (89), ZstdFrame (37), XxHash (24). Total spec line count: 10,448 lines.

12-PR batch (Mar 11–12): frame completeness chain + review campaign completion + field characterization:

This batch deepened the parsing completeness chain from individual function completeness to composed frame-level completeness for raw/RLE and compressed blocks, completed the Zstd spec file review campaign (all 6 files audited), and proved executeSequences loop completeness.

Track E parsing completeness — raw/RLE literals (1 PR):

  • #1159: parseLiteralsSection_succeeds_raw and parseLiteralsSection_succeeds_rle — completeness for raw and RLE literal section parsing. Closes the gap identified in the previous batch's completeness coverage table.

Track E composed frame-level completeness (4 PRs):

  • #1160: skipSkippableFrame_succeeds (skippable frame completeness) and decompressZstd_succeeds_single_skippable (composed skippable-frame completeness at WF level)
  • #1161: decompressBlocksWF_succeeds_single_raw and decompressBlocksWF_succeeds_single_rle — composed block-loop completeness for single raw/RLE blocks (6-step pattern: typeVal derivation → parse result → field characterization → constraints → operation result → composition)
  • #1168: decompressBlocksWF_succeeds_single_compressed_zero_seq (numSeq=0) and decompressBlocksWF_succeeds_single_compressed_sequences (numSeq>0) — composed block-loop completeness for single compressed blocks
  • #1178: decompressFrame_succeeds_single_raw and decompressFrame_succeeds_single_rle — composed frame-level completeness for single raw/RLE blocks (composes parseFrameHeader_succeeds + decompressBlocksWF_succeeds_single_*)

Track E sequence execution completeness (1 PR):

  • #1179: executeSequences_loop_succeeds and executeSequences_succeeds — completeness for the sequence execution loop under windowed mode. Added OffsetsValidForLoop predicate for per-step offset validity and foldl_litLen_add helper. 118 lines added to ZstdSequence.lean.

Track E field characterization (1 PR):

  • #1164: parseFrameHeader_frameHeaderDescriptor_eq, parseFrameHeader_windowDescriptor_eq, parseFrameHeader_dictionaryId_eq, parseFrameHeader_frameContentSize_eq — proves each parsed field equals specific byte ranges of the raw header data

Track E API-level content completion (1 PR):

  • #1151: decompressZstd_two_compressed_literals_blocks_content and decompressZstd_two_compressed_sequences_blocks_content — the final two API-level content theorems, completing the 16/16 two-block content matrix

Quality reviews (3 PRs):

  • #1152: Zstd.lean proof quality audit — extracted frame_from_blocks tactic macro (replacing 19-line frame-unwinding epilogue in ~20 theorems) and parseBlockHeader_data_not_le helper (−406 lines, −10.3%)
  • #1153: ZstdFrame.lean proof quality audit
  • #1154: Removed dead _hdict hypotheses from 20 theorem signatures across 2 Zstd spec files

Housekeeping (2 PRs):

  • #1158: ZipForStd upstreaming readiness audit — assessed 4 files (52 lemmas) for Lean/Std PR readiness
  • #1167: Closed duplicate issue #1163, updated ZstdFrame.lean content matrix docstring from "12/16" to "16/16"

Self-improvement (1 PR):

  • #1171: Meditate — updated lean-parsing-completeness skill with 6-step composed completeness pattern, updated lean-simp-tactics with Bool/Prop bridging reference

Parsing completeness coverage (updated):

Function Status
parseBlockHeader done
decompressRawBlock done
decompressRLEBlock done
parseFrameHeader done
parseCompressedLiteralsHeader done
parseLiteralsSection (treeless) done
parseLiteralsSection (compressed, litType=2) done
parseLiteralsSection (raw) done
parseLiteralsSection (RLE) done
parseHuffmanWeightsDirect done
parseHuffmanWeightsFse done
parseHuffmanTreeDescriptor (direct) done
parseHuffmanTreeDescriptor (FSE) done
BackwardBitReader.init done
decodeHuffmanLiterals (1-stream, 4-stream) done
resolveSingleFseTable (predefined/RLE/repeat/FSE) done
resolveSequenceFseTables (composed) done
parseSequencesHeader done
decodeSequencesWF (zero-case) done
skipSkippableFrame done
executeSequences done
decompressBlocksWF (single raw) done
decompressBlocksWF (single RLE) done
decompressBlocksWF (single compressed, zero-seq) done
decompressBlocksWF (single compressed, with-seq) done
decompressFrame (single raw) done
decompressFrame (single RLE) done
decompressBlocksWF (multi-block composed) gap
decompressFrame (compressed blocks) gap
decompressZstd (full end-to-end) gap

The completeness chain now covers 21 functions/paths (up from 17). Composed completeness reaches the frame level for raw/RLE single-block frames. Remaining gaps: multi-block composed completeness, compressed-block frame-level completeness, and end-to-end decompressZstd completeness.

Review campaign status (6/6 Zstd spec files — COMPLETE):

  • Fse.lean (#1112, #822, #918) — decomposition helpers, proof optimization
  • ZstdHuffman.lean (#1109, #853, #908) — shared eliminators, dead code removal
  • ZstdSequence.lean (#1145, #1139, #995) — proof compression + quality audit
  • Zstd.lean (#1152) — tactic macro extraction, −10.3%
  • ZstdFrame.lean (#1153) — proof quality audit
  • XxHash.lean (#807) — quality pass

Summary: The Zstd spec infrastructure now spans 6 files with 634 declarations: ZstdSequence (181), Fse (154), Zstd (130), ZstdHuffman (99), ZstdFrame (38), XxHash (32). Total spec line count: 11,136 lines.

14-PR batch (Mar 12): two-block compressed completeness + write-side review campaign:

This batch advanced Track E composed completeness into compressed block territory (both at block and frame/API levels) while the review campaign audited 8 DEFLATE write-side spec file pairs.

Track E composed completeness — block level (3 PRs):

  • #1198: decompressBlocksWF_succeeds_two_raw_blocks and decompressBlocksWF_succeeds_two_rle_blocks — homogeneous two-block completeness for raw and RLE blocks
  • #1205: decompressBlocksWF_succeeds_raw_then_rle, decompressBlocksWF_succeeds_rle_then_raw — heterogeneous raw/RLE two-block completeness
  • #1220: decompressBlocksWF_succeeds_raw_then_compressed_zero_seq and decompressBlocksWF_succeeds_rle_then_compressed_zero_seq — first two-block completeness involving compressed blocks (raw/RLE + compressed zero-seq)

Track E composed completeness — frame and API levels (2 PRs):

  • #1193: decompressFrame_succeeds_single_compressed_zero_seq and decompressFrame_succeeds_single_compressed_sequences — frame-level completeness for single compressed blocks (both zero-seq and with-seq)
  • #1212: decompressZstd_succeeds_single_compressed_zero_seq_frame and decompressZstd_succeeds_single_compressed_sequences_frame — end-to-end API-level completeness for single compressed blocks. Composes parseFrameHeader_succeedsdecompressFrame_succeeds_*decompressZstd_single_frame.

DEFLATE write-side review campaign (7 PRs):

  • #1189: DecodeCorrect + DecodeComplete — bare simpa conversion, dead hypothesis removal
  • #1194: HuffmanCorrect — proof audit, no changes needed
  • #1195: GzipCorrect + ZlibCorrect + DeflateRoundtrip (695 lines) — redundant have inlining, proof optimization (−18 lines)
  • #1199: BitstreamCorrect + BitstreamComplete — proof audit
  • #1202: DeflateDynamicHeader + DeflateDynamicEmit (706 lines) — merged consecutive simp only pairs, inlined dead have bindings
  • #1206: BitstreamWriteCorrect + BitWriterCorrect — proof audit
  • #1209: DeflateEncode + DeflateEncodeProps (1021 lines) — merged consecutive rw calls, removed unnecessary case split, dead variable
  • #1214: DeflateEncodeDynamic + DeflateEncodeDynamicProps (1141 lines) — replaced verbose List.reduceReplicate pattern with List.length_replicate
  • #1215: DynamicTreesCorrect + DynamicTreesComplete (1305 lines) — 2 bare simpasimpa only, 1 dead hypothesis removed. Discovered bare simp count from issue was inflated — all simp calls already used simp only.

Composed completeness coverage (updated):

Function Status
decompressBlocksWF (two raw) done
decompressBlocksWF (two RLE) done
decompressBlocksWF (raw+RLE, RLE+raw) done
decompressBlocksWF (raw/RLE + compressed zero-seq) done
decompressFrame (single compressed zero-seq) done
decompressFrame (single compressed with-seq) done
decompressZstd (single compressed zero-seq frame) done
decompressZstd (single compressed with-seq frame) done
decompressBlocksWF (raw/RLE + compressed with-seq) gap
decompressBlocksWF (compressed + raw/RLE) gap
decompressFrame (two-block compressed) gap
decompressZstd (full end-to-end multi-block) gap

The composed completeness chain now reaches the API level for single compressed blocks (both zero-seq and with-sequences). Two-block completeness covers all raw/RLE combinations and the first mixed raw/RLE + compressed pairings. Remaining gaps: compressed-first two-block completeness, and composing multi-block completeness to frame and API levels.

Review campaign progress: The write-side DEFLATE review campaign covered 8 file pairs (4,868 lines total). All files are now clean: 0 bare simp, 0 bare simp_all, no dead hypotheses. The campaign confirmed that most files had already reached review quality from previous passes — the main value was verification rather than remediation.

Summary: The Zstd spec infrastructure now spans 6 files with 454 declarations: Zstd (117), ZstdSequence (97), ZstdHuffman (88), Fse (87), ZstdFrame (39), XxHash (26). Total spec line count: 11,879 lines.

17-PR batch (Mar 12): completeness matrix expansion + DEFLATE review deepening:

This batch expanded the two-block composed completeness ("succeeds") matrix across all three levels (block, frame, API), primarily filling in compressed block combinations. Concurrently, 7 review PRs deepened the DEFLATE proof quality campaign into previously unaudited spec files.

Track E composed completeness — block level (4 PRs):

  • #1227: decompressBlocksWF_succeeds_compressed_zero_seq_then_raw and decompressBlocksWF_succeeds_compressed_zero_seq_then_rle — compressed zero-seq first + raw/RLE second
  • #1248: decompressBlocksWF_succeeds_compressed_sequences_then_raw and decompressBlocksWF_succeeds_compressed_sequences_then_rle — compressed sequences first + raw/RLE second
  • #1256: decompressBlocksWF_succeeds_compressed_zero_seq_then_compressed_zero_seq and decompressBlocksWF_succeeds_compressed_zero_seq_then_compressed_sequences — compressed-to-compressed pairs
  • #1281: decompressBlocksWF_succeeds_compressed_sequences_then_compressed_zero_seq and decompressBlocksWF_succeeds_compressed_sequences_then_compressed_sequences — remaining compressed-to-compressed pairs (conflict fix for #1264)

Track E composed completeness — frame level (3 PRs):

  • #1231: decompressFrame_succeeds_raw_then_compressed_zero_seq and decompressFrame_succeeds_rle_then_compressed_zero_seq — raw/RLE first
    • compressed zero-seq second
  • #1242: decompressFrame_succeeds_compressed_zero_seq_then_raw and decompressFrame_succeeds_compressed_zero_seq_then_rle — compressed zero-seq first + raw/RLE second
  • #1277: decompressFrame_succeeds_compressed_sequences_then_raw and decompressFrame_succeeds_compressed_sequences_then_rle — compressed sequences first + raw/RLE second (conflict fix for #1188/#1257)

Track E composed completeness — API level (3 PRs):

  • #1252: decompressZstd_succeeds_rle_then_rle_frame, decompressZstd_succeeds_rle_then_raw_frame, decompressZstd_succeeds_rle_then_compressed_zero_seq_frame — rle-first frames at API level
  • #1269: decompressZstd_succeeds_two_raw_frame, decompressZstd_succeeds_raw_then_rle_frame, decompressZstd_succeeds_raw_then_compressed_zero_seq_frame — raw-first frames at API level (conflict fix for #1253)
  • #1277: decompressZstd_succeeds_single_raw_frame and decompressZstd_succeeds_single_rle_frame — single raw/RLE at API level

Quality reviews — DEFLATE spec files (7 PRs):

  • #1230: LZ77.lean + LZ77Lazy.lean — proof quality audit
  • #1234: HuffmanKraft.lean + HuffmanTheorems.lean — proof quality audit
  • #1243: ZstdHuffman.lean — proof quality audit (refactoring)
  • #1244: ZstdFrame.lean — proof quality audit
  • #1258: DeflateStoredCorrect.lean + DeflateFixedCorrect.lean — proof quality audit
  • #1262: LZ77NativeCorrect.lean + EmitTokensCorrect.lean — proof quality audit
  • #1278: DeflateSuffix.lean + BinaryCorrect.lean — proof quality audit

Self-improvement (1 PR):

  • #1226: Meditate — 17-PR batch skill updates

Two-block composed completeness ("succeeds") matrix — current state:

Block-level (14/16):

Raw RLE CompZS CompSeq
Raw + done done done
RLE + done done done
CompZS + done done done done
CompSeq + done done done done

Missing at block level: raw+comp_sequences and rle+comp_sequences.

Frame-level (10/16 two-block + 4 single):

Raw RLE CompZS CompSeq
Raw + done done done
RLE + done done done
CompZS + done done
CompSeq + done done

Missing at frame level: all 4 compressed-to-compressed pairs plus raw+comp_sequences and rle+comp_sequences.

API-level (6/16 two-block + 5 single):

Raw RLE CompZS CompSeq
Raw + done done done
RLE + done done done
CompZS +
CompSeq +

The API level covers raw/RLE-first pairings only. Compressed-first and compressed-to-compressed API-level completeness remains to be lifted from the frame level.

Review campaign: This batch extended the DEFLATE proof quality review to 7 additional file pairs, including LZ77 (greedy + lazy), Huffman characterizing properties, and several correctness files. These are cumulative with the previous write-side campaign (#1189–#1215) and the Zstd spec review campaign (all 6 files). The codebase now has extensive review coverage across both DEFLATE and Zstd spec layers.

Summary: The Zstd spec infrastructure now spans 6 files with 501 declarations: Zstd (145), ZstdSequence (104), Fse (95), ZstdHuffman (86), ZstdFrame (48), XxHash (23). Total spec line count: 14,081 lines.

8-PR batch (Mar 12): API-level succeeds near-completion + review campaign deepening:

This batch advanced the API-level succeeds matrix to 19/21 and continued the DEFLATE proof quality review campaign across 4 more spec file pairs.

Track E API-level succeeds (3 PRs):

  • #1296: Merge conflict fix for frame-level comp_zero_seq + compressed block theorems, unblocking downstream API-level lifting
  • #1306: decompressZstd_succeeds_compressed_sequences_then_raw_frame and decompressZstd_succeeds_compressed_sequences_then_rle_frame — compressed sequences first + raw/RLE at API level
  • #1320: decompressZstd_succeeds_raw_then_compressed_sequences_frame, decompressZstd_succeeds_rle_then_compressed_sequences_frame, decompressZstd_succeeds_compressed_sequences_then_compressed_zero_seq_frame, decompressZstd_succeeds_two_compressed_sequences_frame — 4 compressed- sequences two-block pairs at API level, derived from content theorems

Quality reviews — DEFLATE spec files (4 PRs):

  • #1300: DeflateDynamicCorrect + DeflateDynamicFreqs — proof optimization
  • #1304: InflateLoopBounds + InflateRawSuffix — proof quality audit (conflict fix for #1276)
  • #1314: DynamicTreesCorrect + DynamicTreesComplete — minimal changes, files already at review quality
  • #1316: BitReaderInvariant + BitWriterCorrect — proof quality audit

Infrastructure (1 PR):

  • #1307: Meditate — merge conflict frequency analysis, completeness matrix scaling patterns

Housekeeping (3 PRs):

  • #1321: Triage stale issue #1318 (work already merged via #1306)
  • #1325: Recovered proof-review-checklist patterns from conflicted PR #1312
  • #1326: Recovered bfinal_suffix_dispatch tactic macro from PR #1312

Two-block composed completeness ("succeeds") matrix — current state:

API-level (19/21):

Raw RLE CompZS CompSeq
Raw + done done done done
RLE + done done done done
CompZS + done done
CompSeq + done done done done

Missing at API level: czs+czs and czs+cs. Both exist at frame level (from decompressFrame_succeeds_compressed_zero_seq_then_compressed_* theorems in Zstd.lean) and need only the standard API-level lift.

Frame-level completeness is 16/16 for two-block combinations plus all 4 single-block types. Block-level completeness is 16/16.

Review campaign progress: The DEFLATE proof quality review campaign now covers all 49 spec files across multiple passes. This batch audited 4 DEFLATE spec file pairs (BitReaderInvariant, BitWriterCorrect, InflateLoopBounds, InflateRawSuffix, DeflateDynamicCorrect, DeflateDynamicFreqs, DynamicTreesCorrect, DynamicTreesComplete). Most files required only minimal changes, confirming convergence toward review-complete quality.

Summary: The Zstd spec infrastructure now spans 6 files with 491 declarations: Zstd (137), ZstdSequence (97), Fse (87), ZstdHuffman (88), ZstdFrame (56), XxHash (26). Total spec line count: 15,091 lines.

5-PR batch (Mar 12): review campaign completion + Track D1 benchmark:

This batch completed the DEFLATE proof quality review campaign to cover all 49 spec files and added the first DEFLATE decompression throughput benchmark.

Quality reviews — final DEFLATE spec files (4 PRs):

  • #1327/#1328: HuffmanCorrect + HuffmanCorrectLoop proof quality audit (with merge conflict rebase)
  • #1332: Deflate.lean proof quality audit
  • #1333: Final spec file batch — Adler32, Crc32, Huffman, DeflateFixedTables. These were the last 4 unreviewed DEFLATE spec files. With this PR, all 49 spec files in Zip/Spec/ have been audited at least once.

Track D1 (1 PR):

  • #1339: DEFLATE decompression throughput benchmark (ZipTest/DeflateBench.lean). Measures native vs FFI inflate/gzip/zlib decompression across 4 data patterns (constant, cyclic, random, text) and 4 sizes (1KB–1MB). Adds the first Track D infrastructure since the compression benchmark suite (#399).

Review campaign — project-wide completion:

All 49 spec files in Zip/Spec/ have now been audited at least once across the review campaign (spanning ~30 review PRs from #369 to #1333). The campaign covered:

  • 43 DEFLATE spec files (checksums, bitstream, Huffman, LZ77, inflate, deflate encode/decode, dynamic trees, framing, roundtrip)
  • 6 Zstd spec files (Fse, XxHash, ZstdHuffman, ZstdSequence, Zstd, ZstdFrame)

Quality metrics at campaign completion:

  • 0 standalone bare simp across all spec files
  • 0 bare simp_all across DEFLATE spec files
  • 5 bare simp_all remaining in Zstd.lean only (post-campaign additions from Track E completeness work)
  • Multiple files saw significant proof compression (Zstd.lean −10.3%, ZstdHuffman.lean −4.3%, Fse.lean −2.4%)

The review campaign reached diminishing returns — most files in the final batch required minimal or no changes, confirming convergence toward stable proof quality.

11-PR batch (Mar 12–13): WellFormedBlocks all-type induction + review campaign + benchmarks:

This batch generalized the WellFormedBlocks induction predicate to all block types, expanded block-level two-block succeeds coverage, continued the Zstd proof quality review campaign, and consolidated benchmark infrastructure.

Track E feature PRs (3):

  • #1361: WellFormedSimpleBlocks induction predicate for raw/RLE N-block sequences
  • #1372: WellFormedBlocks generalized to all 8 block types (raw, RLE, compressed_literals, compressed_sequences × last/non-last). Milestone: universal induction predicate for arbitrary block sequences.
  • #1382: Block-level two-block succeeds for raw/RLE + compressed_sequences combinations (closes #1232)

Proof quality reviews (3 PRs):

  • #1359: Zstd.lean foundational sections audit — proof compression
  • #1380: Zstd.lean step theorems + raw/RLE two-block audit
  • #1383: Fse.lean proof quality audit — compressed proof patterns

Proof compression (1 PR):

  • #1358: GzipCorrect + ZlibCorrect proof pattern compression

Infrastructure (3 PRs):

  • #1356: Recovered skill updates from superseded PR #1351
  • #1368: Meditate — two-block completion + review campaign transition
  • #1377: Rebased ZstdFrame.lean review PRs (#1366 + #1371), net −149 lines

Track D (1 PR):

  • #1357: Benchmark consolidation — merged NativeCompressBench + NativeScale into unified Benchmark.lean, added Zstd multi-iteration support

Bare simp_all in Zstd.lean: 5 → 2. Zstd spec lines: 15,091 → 15,811.

14-PR batch (Mar 13): proven-bounds campaign + Zstd splitting + WellFormedBlocks completion:

This batch launched the proven-bounds campaign (converting [i]! runtime bounds checks to proven-bounds [i] access), split Spec/Zstd.lean into three files, and completed the WellFormedBlocks/WellFormedSimpleBlocks induction predicates at frame and API levels.

Proven-bounds campaign (5 PRs):

  • #1414: Deflate.lean — encoding helpers + constant tables (22 patterns)
  • #1417: DEFLATE length/distance tables — 4 patterns in decodeHuffman.go with @[simp] size lemmas for all 5 constant tables
  • #1426: ZstdHuffman.lean — 13 data[pos + i]!data[pos + i]'(by omega) in parseCompressedLiteralsHeader
  • #1427: Merge conflict fix for ZstdFrame.lean proven-bounds PR #1413
  • #1428: ZstdSequence.lean — data byte reads + history array access

Zstd spec splitting (1 PR):

  • #1409: Extracted ZstdBase.lean (542 lines, L1: base predicates) and ZstdBlockLoop.lean (493 lines, L2: block loop specs) from the 6011-line Spec/Zstd.lean. Spec/Zstd.lean remains at 6011 lines — further splitting planned.

Track E WellFormedBlocks completion (3 PRs):

  • #1393: API-level WellFormedBlocks succeeds (full predicate)
  • #1398, #1401: Frame/API-level WellFormedSimpleBlocks succeeds, including 3-block corollary (raw → RLE → raw)

Quality reviews (3 PRs):

  • #1394: ZstdSequence.lean proof quality audit
  • #1397: Zstd.lean file organization — splitting plan
  • #1402: Zstd.lean proof quality audit (frame characterization + compressed two-block) — eliminated 2 bare simp_all

Self-improvement (1 PR):

  • #1420: Meditate — created proven-bounds skill, assessed Zstd splitting readiness

Proven-bounds campaign status: 149 ]! remaining across 10 native files. Largest concentrations: Deflate.lean (53), ZstdSequence.lean (29), Fse.lean (18), ZstdHuffman.lean (14), Gzip.lean (12), DeflateDynamic.lean (11). Files fully converted: Crc32.lean, Adler32.lean, BitWriter.lean.

Remaining:

  • Zstd.lean at 6011 lines needs further splitting (plan: #1432)
  • Prove remaining sorry stubs: 4 in XxHash (UInt64 test vectors too expensive for kernel evaluation — intractable without native_decide)
  • Proven-bounds campaign: 149 ]! across 10 native files
  • Composed completeness: lift czs+czs and czs+cs to API level (19→21), compose multi-block completeness to end-to-end decompressZstd
  • Content preservation campaign: extend to N-block frames and compressed block content (with sequences)
  • Spec-level decoder with correctness proofs (algorithmic correspondence between native and spec decoder, following the DEFLATE B3 pattern)
  • Compressor + roundtrip proof

31-PR batch (Mar 13 – Apr 18): proven-bounds continuation + lean-zip-common/lean-zstd split + Lean 4.29.1 CI fixes (summarize #1511):

This batch is dominated by two strands: the proven-bounds campaign (converting [i]! runtime bounds checks to statically-proven access) and a structural split of the repository into three repos. See progress/20260418T081059Z_10fac7ec.md for the full PR breakdown.

The structural change (#1487, 2026-03-27): lean-zip was split into three repositories:

  • lean-zip-common — shared utilities (Binary, Handle, BitReader, ZipForStd, io_ffi.c)
  • lean-zstd — Zstandard decompression (13 spec files + FSE + XxHash + frame decoder + Huffman + sequences)
  • lean-zip (this repo) — zlib/DEFLATE/Gzip/ZIP/Tar with all specs and proofs

39 files were moved out; a require zipCommon from git pin replaced them. Zip/Spec/ dropped from 51 files to 42.

Proven-bounds campaign (18 PRs): pre-split coverage in ZstdHuffman/ZstdSequence/Fse/BitReader/Binary (now in sibling repos) plus DEFLATE-side work in Deflate.lean (#1446 emitTokens, #1500 canonicalCodes/findTableCode/insertLoop), DeflateDynamic.lean (#1450, #1498), Inflate.lean (#1449, #1451), Tar.lean (#1459), and Gzip.lean (#1492). A cross-file consistency audit (#1431) kept conventions aligned.

Spec file splitting (5 PRs, pre-split): Spec/Zstd.lean and ZstdFrame.lean were modularized in-repo just before the #1487 split (#1438 ZstdTwoBlock extraction; #1460/#1466 splitting assessments; #1473 ZstdFrameBase extraction; #1479 three-way Spec/Zstd.lean split). The resulting files migrated to lean-zstd.

Other PRs (4): #1468 Track C1 fuel-dependent Zstd audit, #1491 closing stale Zstd PRs post-split, #1497 Track E ZIP local-header span validation, #1501 InflateCorrect + InflateComplete proof-quality audit, #1502 localized Array.set! monotonicity lemma.

Toolchain / CI (3 PRs): Lean 4.29.1 introduced a Lake "compiled configuration invalid" regression on lake exe test. #1495 switched CI to lake test; #1506 pinned zipCommon + added lake reconfigure in CI; #1499 renamed call sites to upstream-compatible lemma names (Nat.or_two_pow_eq_add_of_lt, List.getElem_foldl_set_*, ByteArray.getElem!_push_lt) and pinned zipCommon to cleanup commit 89204d6.

Carry-over: 48 ]! remaining in Native/, all in two files — Zip/Native/Deflate.lean (42, LZ77 matcher family: tracked in new issues #1508/#1509/#1510 splitting into three clusters) and Zip/Native/DeflateDynamic.lean (6, after PR #1513 merged mid- summarize addressed 2 of the 8 from #1505). Three diag PRs (#1496, #1503, #1507) remain open as instrumentation for the Lake/Lean 4.29.1 regression.

Quality metrics: 0 sorries; Zip/Spec/ at 42 files, 20,151 LOC (DEFLATE-only — Zstd's ~16 kLOC of spec work migrated to lean-zstd); Zip/Native/ at 7 files, 1,603 LOC. Toolchain: v4.29.1.

10-PR batch (Apr 18): proven-bounds campaign completion (summarize #1537):

This batch closes out the DEFLATE-side proven-bounds campaign carried forward from the batch above. Eight feature PRs converted every remaining ]! runtime-bounds access in the LZ77 matcher family of Zip/Native/Deflate.lean (clusters A/B/C1/C2/C3 — spanning the recursive and iterative variants of lz77Greedy and lz77Lazy plus their where-local hash3/countMatch/trailing/mainLoop/ updateHashes helpers) and in the two remaining clusters of Zip/Native/DeflateDynamic.lean (cluster A emitTokensWithCodes; cluster B tokenFreqs.go). End-point: ]! in Zip/Native/Deflate.lean 24 → 0 and Zip/Native/DeflateDynamic.lean 6 → 0 as runtime reads — the only remaining ]! in Deflate.lean (2 occurrences) live inside comments, with no runtime-bounded accesses left in the compressor.

Cluster pattern that landed. The matcher plumbing grew three signature-level size invariants threaded from the top-level call down through each where-local helper: hht : hashTable.size = hashSize, hhv : hashValid.size = hashSize, and hhs : 0 < hashSize for the hash-table reads; h1 : p1 + maxLen ≤ data.size + h2 : p2 + maxLen ≤ data.size for the countMatch byte-compare loops; and h : pos + 2 < data.size on each local hash3. The iterative (lz77GreedyIter/lz77LazyIter, PRs #1517 and #1529) and recursive (lz77Greedy/lz77Lazy, PRs #1532 and #1524 + #1535) variants each required their own size-preservation lemma pair — lz77{Greedy,Lazy}_updateHashes_{fst,snd}_size, four in total — because updateHashes is private to each where-block and cannot be shared without a larger refactor. Dead-branch fallbacks for the outer hht/hhv failure are byte-identical across the iterative and lazy variants (emit one literal, advance pos + 1); the inner lookahead guard in mainLoop_lazy takes the lookahead-dropped branch and commits the already-found match. The emitTokensWithCodes dead fallback recurses without emitting — ruled out by nativeFindLengthCode_idx_bound + hlit_size, retained only to keep the elaborator happy.

Skill-file deltas (#1530). Appended two pitfalls to .claude/skills/proven-bounds/SKILL.md, bringing the numbered list from 7 to 9. Pitfall #8 records that unless rebinds the threaded mut state even when the body does not write to it, so bounds captured before unless no longer apply to reads after; remedy is to read all fields up front inside the if h : pos + N ≤ data.size guard (pattern used in the Gzip decompressor rewrite). Pitfall #9 records that List.pmap (and other @[expose] combinators) cause rfl-proved specs to unfold the skeleton simultaneously on each side and blow the kernel recursion limit; remedy is a named-helper wrapper (freqsToPairs in DeflateDynamic.lean).

Proof-quality review (#1536). Audited the six feature PRs landed across the clusters. No cross-cluster renames rose to the "wrap it" bar — hypothesis names (hht, hhv, hhs, hlit, hdist) overload by scope in ways that are consistently scope-disambiguated. getElem!_le_set!_incr was the sole repeated spec-cascade idiom that justified a local helper lemma, and #1526 already introduced it. The four size-preservation lemmas in Zip/Spec/DeflateFixedCorrect.lean (lz77{Greedy,Lazy}_updateHashes_{fst,snd}_size) remain independent rather than being collapsed, because lz77Greedy.updateHashes and lz77Lazy.updateHashes are private to their respective where-blocks — unification would require a separate refactor hoisting updateHashes to top-level. Review landed doc-only (no cleanup commit), per the issue's explicit opt-out.

Self-improvement (#1516). Post-proven-bounds-campaign meditate captured the clustering heuristic that split the Deflate matcher work into sub-issues #1508/#1509/#1510, and recorded the ]! distribution across Zip/Native/ that scoped this batch.

Quality metrics: 0 sorries across Zip/; Zip/Native/Deflate.lean and Zip/Native/DeflateDynamic.lean both at 0 runtime ]! reads (2 comment-only occurrences remain in the former); Zip/Native/ at 7 files, 1,780 LOC (up from 1,603 — +177 LOC from the threaded size hypotheses and dead-branch fallbacks); Zip/Spec/ at 42 files, 20,516 LOC (up from 20,151 — +365 LOC across DeflateFixedCorrect.lean, DeflateDynamicEmit.lean, DeflateDynamicFreqs.lean, DeflateDynamicCorrect.lean, LZ77NativeCorrect.lean); toolchain v4.29.1.

Track E carry-over. #1531 and #1533 (ZIP64 oversized-compressedSize / oversized-uncompressedSize malformed- fixture features) remain unclaimed Priority-0 work for the next planner.

11-PR batch (Apr 18 – Apr 21): Track E security-audit cluster (summarize #1563):

This batch is the first concentrated sweep of the Track E security-audit sub-roadmap. No DEFLATE / Huffman / LZ77 code path changed; no spec file was touched; grep -rc sorry Zip/ stayed at 0 throughout. The work is entirely on the trust boundary — parser guards, malformed fixtures, regression tests, and boundary documentation. The library's cryptographic and compression kernels are unchanged; what moved is how much of the attack surface is now explicitly guarded, tested, or catalogued. The corresponding Track E checklist is plans/track-e-current-audit-checklist.md; this summary reads side-by-side with it rather than restating the full matrix.

Priority 0 — ZIP unchecked-size + local-span audit (closed).

  • #1543 — ZIP64 oversized-compressedSize malformed fixture (testdata/zip/malformed/oversized-zip64-compressed-size.zip, 134 B; fires assertSpanInFile's "local data span" branch with a claimed exabyte compressedSize).
  • #1544 — ZIP64 oversized-uncompressedSize malformed fixture (testdata/zip/malformed/oversized-zip64-uncompressed-size.zip, 134 B; the exabyte claim is caught by the post-read "size mismatch" guard rather than a span check — method-0 stored payload, so fileData.size never matches entry.uncompressedSize). A later strict ZIP64 local-extra parse (landed in #1554) now rejects this fixture earlier with truncated ZIP64 local extra field; the fixture's assert was updated to reflect the stricter check.
  • #1554 — Central-directory vs. local-header consistency audit. Archive.readEntryData now parses the remaining LH fields (flags, method, crc, stdLocalCompSize, stdLocalUncompSize, ZIP64 local-extra block) and hard-errors on any CD/LH mismatch on method / compressedSize / uncompressedSize / crc, except when flag bit 3 (data-descriptor) legitimately leaves LH crc/sizes at zero. Covered by testdata/zip/malformed/cd-lh-method-mismatch.zip and cd-lh-size-mismatch.zip (both deterministic, built by scripts/build-cd-lh-mismatch.py). parseZip64Extra is reused for the LH case; a follow-up could thread Entry.flags for a bit-11 UTF-8 check.

Priority 1 — Tar partial-decoder audit (closed).

  • #1545 — Malformed PAX extended-header fixtures (testdata/tar/malformed/pax-oversized-length.tar, pax-truncated-record.tar, pax-invalid-utf8-key.tar, pax-invalid-utf8-value.tar, pax-inconsistent-length.tar — each 2048 B; built deterministically by scripts/build-pax-malformed-fixtures.lean). Every malformation was silently skipped by existing parsePaxRecords guards; no hardening needed, but the regression pins behaviour.
  • #1546 — Malformed GNU long-name / long-link fixtures (testdata/tar/malformed/gnu-longname-truncated.tar, gnu-longname-no-terminator.tar, gnu-longname-invalid-utf8.tar, gnu-longlink-truncated.tar; built by scripts/build-gnu-long-malformed-fixtures.lean). Invalid UTF-8 in long-names falls through String.fromUTF8? to a Latin-1 fallback, which is now pinned by test rather than left implicit.
  • #1550 — String.fromUTF8! callsite audit in Zip/Tar.lean. Three panicking raw-byte truncations in buildPaxEntry and create replaced by Tar.truncateUTF8 (codepoint-aware); the two remaining fromUTF8! callsites in splitPath now carry an explanatory comment (split is at an ASCII '/' byte, safe by construction). Regression coverage in new test module ZipTest/TarPathTruncation.lean.
  • #1555 — Symlink / hardlink extraction policy documented explicitly. Per-typeflag policy is now in both the Tar.extract docstring and SECURITY_INVENTORY.md's new "Symlink/hardlink extraction policy" subsection: typeRegular / typeDirectory go through Binary.isPathSafe; typeSymlink validates linkname for absolute / backslash / .. components before createSymlink; typeHardlink (now a named constant, 0x31) and all other typeflags (devices, FIFO, GNU sparse) silently skip — the payload is consumed but no filesystem entry is created. New fixtures testdata/tar/security/symlink-absolute.tar and hardlink-outside.tar plus the previously orphaned backslash-slip.tar now have asserts in ZipTest/TarFixtures.lean; built by scripts/build-symlink-hardlink-malformed-fixtures.lean.

Priority 2 — Public decompression limit policy (items 1–3 closed, item 4 open).

  • #1556 — Public decompression limit policy inventory. Added a new "Decompression Limit Inventory" section to SECURITY_INVENTORY.md cataloguing every public decompression / extraction API (16 entry points across FFI + native + archive + tar) with Entry point / Parameter / Default / Semantics of 0 / Notes columns. Flagged four known inconsistencies (FFI whole-buffer vs. streaming cap defaults; Archive.readEntryData silent 0 → 256 MiB upgrade on the native backend; native-decoder 1 GiB vs. 256 MiB default disagreement; maxCentralDirSize finite but maxEntrySize unlimited mixed semantics) and proposed a six-point "Recommended policy" with placeholder numbers (256 MiB FFI whole-buffer, 1 GiB archive per-entry, streaming FFI gains a cap, native-decoder defaults normalised). No Zip/*.lean changes — inventory + policy proposal only.
  • #1560 — Bomb-limit regression tests for Gzip.decompress, RawDeflate.decompress, and Zip.Native.GzipDecode.decompress (added to ZipTest/Gzip.lean, ZipTest/RawDeflate.lean, ZipTest/NativeGzip.lean). Each test compresses ~6200 B of mkTestData, then calls the decoder with maxDecompressedSize := 10 (or maxOutputSize := 10 for native) and asserts the error substring ("exceeds limit" for FFI, "exceeds maximum size" for native — the substrings diverge because the native path errors originate in Zip/Native/Inflate.lean, not in the C code).
  • #1561 — Bomb-limit regression tests for Archive.extract, Archive.extractFile, Tar.extract, and Tar.extractTarGzNative (added to ZipTest/Archive.lean and ZipTest/Tar.lean). Each test builds the bomb in-memory via mkTestData + Tar.create / Gzip.compress rather than committing new fixtures. The Tar test uses (maxEntrySize := 0) (maxOutputSize := 10) and asserts "exceeds maximum size" — the gzip-decode budget fails before per-entry limits are reached.
  • #1562 — Reconcile audit checklist + SECURITY_INVENTORY.md missing-work lists. Checklist line for each of the six items closed above now carries its closing PR number. SECURITY_INVENTORY.md's ZIP / Tar "Missing work" lists shrunk to genuinely-open items (bounded-read lemmas on the ZIP side, none on the tar side); completed items moved to a new per-subsystem "Recent wins" bullet list referencing the closing PRs. Documentation-only — no code changes.

Test-fixture inventory added in this batch. New fixtures landed in three families, all built by committed deterministic builders (scripts/build-*) so they can be regenerated byte-stable:

  • testdata/zip/malformed/ — 4 new: oversized-zip64-compressed-size.zip, oversized-zip64-uncompressed-size.zip, cd-lh-method-mismatch.zip, cd-lh-size-mismatch.zip (count 11 after this batch, up from 7).
  • testdata/tar/malformed/ — 9 new: 5 pax-*.tar + 4 gnu-longname/longlink-*.tar (count 12, up from 3).
  • testdata/tar/security/ — 2 new files (symlink-absolute.tar, hardlink-outside.tar) and the previously-orphaned backslash-slip.tar now exercised rather than committed-but-unused (count 6, up from 4; the other three are tar-slip.tar, tar-absolute.tar, symlink-slip.tar).

Scope discipline. Every PR in this batch deliberately stopped at the doc / fixture / test boundary. Where Zip/*.lean did change — #1550 (Tar.truncateUTF8 + ZipTest/TarPathTruncation.lean), #1554 (Archive.readEntryData LH consistency) — the changes were new guards + regression fixtures; nothing was removed or weakened, and no proof / spec file was touched. SECURITY_INVENTORY.md "Recommended policy" numbers are deliberately flagged as seed values for follow-up issues, not decisions. The library's proof corpus (42 spec files, 20,516 LOC, 0 sorries) is byte-identical to the pre-batch tree.

Quality metrics: 0 sorries across Zip/; 0 runtime ]!; Zip/Spec/ at 42 files, 20,516 LOC (unchanged); Zip/Native/ at 7 files, 1,780 LOC (unchanged); Zip/ (FFI / archive / tar / gzip / basic) at 6 files, 1,437 LOC (+ Archive.lean grew from readEntryData LH parse + guards; Tar.lean grew from truncateUTF8 + docstring); ZipTest/ at 22 files (+1 from TarPathTruncation.lean); toolchain v4.29.1.

Track E open work (post-batch). P2 item 4 (docstring + error-message policy), all of P3 (FFI adversarial validation: sanitizer harness, truncated / concatenated-gzip / repeated-inflateReset / near-limit regression tests, fuzz harness), P4 (runtime-boundary docs), and P5 (proof-friendly guard lemmas for bounded reads). Planner has already queued the next wave: #1558 / #1559 / #1564 / #1565 / #1566 track the remaining Priority 2–3 items.

15-PR batch (Apr 22): Track E security-audit follow-ups (summarize #1588):

This batch is the second concentrated wave of the Track E security-audit sub-roadmap. It builds on the 11-PR cluster captured in the prior block (summarize #1563 / PR #1568) by closing every remaining audit-checklist P-item except P3.3 (fuzz harness) and P5.* (proof-friendly guard lemmas). No DEFLATE / Huffman / LZ77 code path changed; no spec file was touched; grep -rc sorry Zip/ stayed at 0 throughout. All work sits on the trust boundary — limit-policy docstrings, FFI regression tests, sanitizer harness, allocation-site audit, and three new SECURITY_INVENTORY.md sub-sections (upstream-runtime tracking, minimized reproducer corpus, and local guard inventory). The library's cryptographic and compression kernels are unchanged; what moved is how much of the audit checklist is now formally documented and locally guarded. The corresponding checklist file is plans/track-e-current-audit-checklist.md (source of truth for checkbox state).

Priority 2 item 4 — Limit-policy docstrings (closed).

  • #1573 — Explicit limit-policy docstrings for FFI whole-buffer + streaming decompression (Zip/Basic.lean, Zip/Gzip.lean, Zip/RawDeflate.lean). Documents per-API the 0 = no limit / Some n / default-cap semantics surfaced by #1556's inventory.
  • #1586 — Public-API limit-policy docstrings for Archive.extract, Archive.extractFile, Tar.extract, Tar.extractTarGzNative (Zip/Archive.lean, Zip/Tar.lean).
  • #1594 — P2.4 final wedge: native-decoder limit-policy docstrings for Zip/Native/Inflate.lean (maxOutputSize) and Zip/Native/Gzip.lean (maxDecompressedSize).

Priority 3 — FFI adversarial validation (P3.1 / P3.2 / P3.4 closed; P3.3 open).

  • #1576 — P3.1: dedicated sanitizer script scripts/sanitize-ffi.sh rebuilds c/zlib_ffi.c under -fsanitize=address,undefined, explicitly links GCC's libasan / libubsan past Lean's bundled clang, and runs the test suite with LD_PRELOAD so ASan initialises first. The April 2026 tree is ASan + UBSan clean.
  • #1571 — P3.2 first tranche: truncated-stream regression tests for Zlib, Gzip, and RawDeflate FFI entry points (added to ZipTest/Zlib.lean, ZipTest/Gzip.lean, ZipTest/RawDeflate.lean).
  • #1572 — P3.2 second tranche: concatenated-gzip-member + zero-length-chunk regression tests (added to the same FFI test modules).
  • #1577 — P3.2 third tranche: repeated-inflateReset across concatenated gzip members + exact-fit and n−1 near-limit-output regression tests.
  • #1580 — P3.4: c/zlib_ffi.c malloc/realloc/buffer-growth audit. New "Allocation site audit (c/zlib_ffi.c)" sub-section in SECURITY_INVENTORY.md enumerates every allocation call-site (kind / overflow guard / failure mode); scripts/check-c-allocations.sh flags accidental new sites at PR-review time against the recorded baseline.

Priority 4 — Trusted runtime boundary documentation (P4.1 / P4.2 / P4.3 closed).

  • #1585 — P4.3: per-call-site local-guard inventory for Handle.read and Stream.read callsites in Zip/Archive.lean and Zip/Tar.lean. New "Local guard inventory for Handle.read and Stream.read" sub-section in SECURITY_INVENTORY.md records each readExact, readEntryData, skipEntryData, and inline-loop callsite together with the bound it relies on (claimed-vs-actual span, assertSpanInFile enclosure, etc.).
  • #1589 — P4.1: upstream-runtime tracking sub-block in SECURITY_INVENTORY.md § "Lean Runtime". Records the ZIP64 oversized-size / CD-vs-LH attack surface, pins the upstream status as "no upstream link yet — local tracking only" (dated 2026-04-22), and lists the local regression coverage (#1543 / #1544 / #1554 / #1560 / #1561) that guards the surface today.
  • #1590 — P4.2: "Minimized Reproducer Corpus" section in SECURITY_INVENTORY.md tabulating all 29 fixtures under testdata/zip/malformed/, testdata/tar/malformed/, and testdata/tar/security/ with the guard each exercises, the first-landing PR (or 481e562 for fixtures inherited from the initial lean-zlib → lean-zip import), and a {oversized allocation, partial-decoder panic, archive-slip, decompression bomb, other} class tag.

Housekeeping (4 PRs).

  • #1569 — Meditate session: patterns and friction in the Track E security-audit phase (drafted skill notes; the materialised SKILL files landed in #1579).
  • #1579 — Materialise the skill drafts from #1569 as .claude/skills/<skill>/SKILL.md files.
  • #1584 — Doc: progress entry for #1581 rebase fix.
  • #1592 — Doc: progress entry for #1590 rebase fix.

Track E open work (post-batch). Two checklist items remain:

  • P3.3 — fuzz harness for whole-buffer and streaming inflate entry points. Tracking issue: #1595 (unclaimed at the time of writing).
  • P5.1 — proof-friendly helpers for bounded reads and validated spans in Zip/Archive.lean / Zip/Tar.lean. Tracking issue: #1596 (unclaimed at the time of writing).
  • P5.2 / P5.3 — bounded-span lemmas + helper-adoption rollout; no tracking issue yet, downstream of #1596.

A bonus boundary-hardening PR is in flight outside the checklist: PR #1597 / issue #1593 caps Tar readEntryData reads at the GNU-long-name and PAX extended-header callsites (open at the time this summary lands; not counted in the 15-PR batch above).

Scope discipline. Same rule as the prior batch: every PR in this wave stopped at the doc / fixture / test boundary, plus narrow boundary-layer edits in Zip/Native/Inflate.lean, Zip/Native/Gzip.lean, Zip/Basic.lean, Zip/Gzip.lean, Zip/RawDeflate.lean, Zip/Archive.lean, and Zip/Tar.lean for the limit-policy docstring PRs (#1573 / #1586 / #1594) — none of these touched a logic guard or a spec file. The library's proof corpus (42 spec files, 20,516 LOC, 0 sorries) is byte-identical to the pre-batch tree.

Quality metrics: 0 sorries across Zip/; 0 runtime ]!; Zip/Spec/ at 42 files, 20,516 LOC (unchanged); Zip/Native/ at 7 files, 1,813 LOC (was 1,780; +33 LOC from limit-policy docstrings in Zip/Native/Inflate.lean and Zip/Native/Gzip.lean); Zip/ (FFI / archive / tar / gzip / basic) at 6 files, 1,511 LOC (was 1,437; +74 LOC across the four FFI modules + Archive.lean + Tar.lean for limit-policy docstrings); ZipTest/ at 22 files, unchanged (P3.2 added test cases inside existing modules); testdata/ fixture counts unchanged (11 / 12 / 6 across zip/malformed, tar/malformed, tar/security); 16 new progress entries in this batch; toolchain v4.29.1.

12-PR batch (Apr 22): Track E audit-completion wave (summarize #1629):

This batch is the third concentrated Track E wave. It finishes off the checklist items that were still open after summarize #1598 — P3.3 (fuzz harness) lands as #1602 and P5.1 (proof-friendly bounded-read helpers) lands as #1608 — and executes three of the five entries in the SECURITY_INVENTORY.md "Recommended policy" block (Rec. 1, 3, 5). A fourth recommendation (Rec. 2, streaming FFI default-flip) is set up structurally by #1610, which adds the maxDecompressedSize parameter to the three streaming FFI decoders but leaves the default at 0 (half-closed, no policy flip). As in the prior two Track E waves, no DEFLATE / Huffman / LZ77 logic changed; no spec file was touched; grep -rc sorry Zip/ stayed at 0 throughout. The window runs from 04:18Z (#1600 meditate) to 07:35Z (#1623 Rec. 1 default-flip) on 2026-04-22. The audit checklist source of truth remains plans/track-e-current-audit-checklist.md.

Track E feature work (7 PRs), grouped by SECURITY_INVENTORY.md recommendation or checklist item:

  • Rec. 1 — whole-buffer FFI default → 1 GiB. #1623 flips Zip.Basic.decompress, Zip.Gzip.decompress, and Zip.RawDeflate.decompress from maxDecompressedSize = 0 (no limit) to 1073741824 (1 GiB). Per-decoder bomb-limit regression tests added to ZipTest/Zlib.lean, ZipTest/Gzip.lean, and ZipTest/RawDeflate.lean.
  • Rec. 3 — per-entry archive extractor default → 1 GiB. #1618 flips Archive.extract, Archive.extractFile, Tar.extract, Tar.extractTarGz, and Tar.extractTarGzNative from maxEntrySize = 0 to 1073741824. Fast-followed by the rebase fix #1620 (superseded; its progress entry landed as the ride-along #1622).
  • Rec. 5 — native-side uniformity at 1 GiB. #1617 lifts the native Zip.Native.Inflate.inflate, Zip.Native.GzipDecode.decompress, Zip.Native.ZlibDecode.decompress, and Zip.Native.ZlibDecode.decompressAuto default maxOutputSize from the previous non-uniform mix to a single 1 GiB cap matched with the FFI side.
  • Streaming FFI maxDecompressedSize parameter (not a default flip). #1610 adds the parameter to Gzip.decompressStream, Gzip.decompressFile, and RawDeflate.decompressStream. The default stays at 0 (no limit) in this PR — it half-closes the SECURITY_INVENTORY.md Rec. 2 gap by making the cap expressible; the default flip is a separate follow-up, filed as issue #1625.
  • P3.3 — fuzz harness for whole-buffer and streaming inflate entry points. #1602 adds ZipTest/FuzzInflate.lean, a randomized harness that exercises both FFI and native backends across random seeds; harness quality was audited out-of-band by review #1607.
  • P5.1 — proof-friendly helpers for bounded reads and validated spans. #1608 lands four helpers — readBoundedSpanFromHandle, readBoundedExactFromHandle, readBoundedEntryData, and readBoundedExactFromStream — plus ZipTest/BoundedReadTest.lean exercising each. #1608 deliberately left its checklist box unticked; the box is the concern of the follow-up P5.3 caller migration.
  • Inventory-drift detector (tooling, not a recommendation). #1612 lands scripts/check-inventory-links.sh, a line-number / fixture-path drift checker for SECURITY_INVENTORY.md that fires at PR review time.

Review rounds (3 PRs). All three are doc-only and pair with a specific feature PR:

  • #1604 — standalone review of SECURITY_INVENTORY.md / audit-checklist reference drift (pairs with no single feature PR; fixes line-number drift and cross-references).
  • #1607 — quality audit of #1602 (fuzz harness) before Track E's 7-day exit-criterion window closes.
  • #1616 — quality audit of #1610 (streaming FFI maxDecompressedSize parameter).

Infrastructure (2 PRs).

  • #1600 — meditate session that catalogued the patterns and friction of the prior Track E wave (~15 PRs since #1569). Drafted skill notes in its progress entry; the SKILL.md file edits themselves were either part of the prior wave (#1579) or reserved for a later meditate.
  • #1622 — ride-along progress entry for the #1620 rebase fix of PR #1618 (per-entry maxEntrySize default flip). Doc-only.

Remaining gaps (as of batch close, 07:35Z). Per the issue body, three Track E items remained outside this batch:

  • Rec. 2 — streaming FFI default-flip. Tracked as issue #1625 at summarize-issue filing; structurally set up by #1610.
  • Rec. 4 — whole-archive maxTotalSize cap. Tracked as issue #1621 at summarize-issue filing; not started in this batch.
  • P5.2 — validated-span / bounded-read lemma proofs over the P5.1 helpers; filed fresh mid-batch as issue #1628.
  • P5.3 — caller migrations in Zip/Archive.lean / Zip/Tar.lean to consume the P5.1 helpers. The issue body flagged PR #1626 as "in flight at summarize time"; in reality #1626 had merged at 07:48:16Z, ~2 minutes before the summarize issue was filed (07:50Z). It is out of the 12-PR batch by issue scope, but the P5.3 checklist box is closed at the time this summary lands. Since batch close, the three remaining gaps above have also landed (#1630 Rec. 4 at 08:10Z, #1631 Rec. 2 at 08:28Z, #1636 P5.2 at 09:17Z); they belong to the next summarize batch.

Scope discipline. Same rule as the prior two Track E waves: the only Zip/*.lean edits in the batch are narrow parameter-addition and default-flip changes on FFI / native / extractor entry points (#1610 streaming-FFI parameter; #1617 native-side default unification; #1618 extractor maxEntrySize; #1623 FFI whole-buffer maxDecompressedSize), plus the new P5.1 helper family in Zip/Archive.lean and Zip/Tar.lean (#1608). No spec file was touched. No proof was changed. The library's proof corpus (42 spec files, 20,606 LOC, 0 sorries) remains intact; the only spec LOC movement (20,516 → 20,606) is from a spec file not in this batch's window, inherited post-#1598. The ]! count is still 0 and Zip/Native/ retains 0 runtime bounds assertions.

Quality metrics: 0 sorries across Zip/ (unchanged; the issue body's "4 → 4 XxHash" baseline claim is stale — Zip/Spec/XxHash.lean does not exist in this repository, and grep -rc sorry Zip/ sums to 0); 0 runtime ]! (unchanged); Zip/Spec/ at 42 files, 20,606 LOC (+90 LOC relative to prior summarize; not attributable to a PR in this batch); Zip/Native/ at 7 files, 1,852 LOC (was 1,813; +39 LOC from #1617 default-unification docstrings + #1608 helper support); Zip/ (FFI / archive / tar / gzip / basic / checksum) at 6 files, 1,833 LOC (was 1,511; +322 LOC from #1608 bounded-read helper family landing in Zip/Archive.lean + Zip/Tar.lean, plus the streaming-FFI parameter in #1610 and the default-flip docstring updates in #1617 / #1618 / #1623; Zip/Checksum.lean is a new sixth file present in the file list but inherited from earlier); ZipTest/ at 24 files (+2 — ZipTest/FuzzInflate.lean from #1602 and ZipTest/BoundedReadTest.lean from #1608); testdata/ fixture counts 11 / 14 / 6 (the +2 in testdata/tar/malformed/ is from pre-batch PR #1597 — it merged 03:43Z but was outside the prior summarize's stated scope); toolchain v4.29.1.

16-PR batch (Apr 22–24): Track E CD/EOCD and CD/LH dimension closure (summarize #1765):

Sixteen PRs merged across three threads in the window between summarize #1721 (merge commit 8766ba9, 23:26Z 2026-04-22) and PR #1761 (merge commit b1d38d5, 05:52Z 2026-04-24). No spec file touched; grep -rc sorry Zip/ stayed at 0.

Track E feature PRs (8). #1728 (CD/LH uncompsize + crc fixtures, closing the per-entry sextet), #1733 (EOCD totalEntries consistency), #1736 (CD/LH versionNeededToExtract one-sided LH ≤ CD), #1742 (EOCD disk-number — both fields == 0 post-ZIP64- override), #1752 (EOCD-internal numEntriesThisDisk vs. totalEntries; extends findEndOfCentralDir tuple 6 → 7), #1754 (ZIP64/standard-EOCD override sentinel, six-field "sentinel ∨ numeric match" check; lifts findEndOfCentralDir's outer container from Option to IO (Option …)), #1759 (per-entry CD diskNumberStart §4.4.11 sibling of #1742), #1761 (ZIP64 EOCD64 self-declared size of record field sanity == 44). Five archive-level EOCD dimensions landed this wave; coverage 1 / 9 → 6 / 9 closed. The SECURITY_INVENTORY.md CD-vs-EOCD Missing work bullet (line 202) is now down to comment-length only (tracked by in-flight repair PR #1743 / issue #1739).

Paired-review PRs (6). #1741 (reviews #1728), #1747 (reviews #1733), #1748 (reviews #1736), #1753 (reviews #1742), #1760 (reviews #1752). #1737 is a standalone post-ladder proof-quality audit of Zip/Native/Crc32.lean (−5 LOC, zero statement change), not paired with an in-wave feature PR. Median paired-review latency for the five feature pairs: 51 min (inside the meditate #1651 §4 ≤ 1-hour target; the #1728 → #1741 pair at 228 min is the sole outlier, crossing the overnight window between the opening housekeeping PRs and the main wave's 03:11Z resumption).

Housekeeping / infra (2). #1724 names 0xFFF7 as the private constant dataDescriptorBitMask in Zip/Archive.lean (no behavior change; readability refactor of the CD/LH flags check added by #1715). #1727 sweeps SECURITY_INVENTORY.md's CD/LH region to reconcile the throw-message-line vs. unless-line citation convention.

Scope discipline. The only source changes this wave touched Zip/Archive.lean (+147 LOC from the five archive-level checks and #1759's per-entry check; #1724 refactor is LOC-neutral) and Zip/Native/Crc32.lean (−5 LOC from #1737). Zip/Spec/ stayed at 42 × 21,067 LOC with zero edits. ZipTest/ZipFixtures.lean grew +219 LOC from nine new fixture/assert blocks. Fixtures in testdata/zip/malformed/ grew from 12 to 21 (nine new); tar fixture counts (14 / 6) unchanged.

Quality metrics: 0 sorries across Zip/ (unchanged); 0 runtime ]! across Zip/Native/ and Zip/*.lean (unchanged); bash scripts/check-inventory-links.sherrors=0, warnings=34 (34 line-anchor warnings inherited from the five-Archive-edit stack; the fixture-path and cross-reference gates stay clean).

12-PR batch (Apr 24): ZIP64 extra-field layout-smuggling closure + Track E CD-parse cadence (summarize #1797):

Twelve PRs merged in the ~5-hour window between summarize #1770 (merge commit 3679495, 06:23Z 2026-04-24) and PR #1796 (merge commit 25c4318, 11:16Z 2026-04-24). No spec file touched; grep -rc sorry Zip/ stayed at 0 throughout.

Track E feature PRs (4), grouped by closed attack-class cluster.

  • CD-parse-time intra-entry invariant (1 PR). #1773 adds the method 0 (stored) compressedSize == uncompressedSize tautological invariant to parseCentralDir. APPNOTE §4.4.5 defines method 0 as "no compression", so the equality is a tautology; crafted archives with mismatched sizes are malformed. Archive.list now rejects them at CD-parse time, before the late post-decode "size mismatch" guard at Zip/Archive.lean:811. Complements the CD/LH uncompressedSize consistency check landed earlier (cd-lh-uncompsize-mismatch.zip): that caught CD-vs-LH skew, this catches intra-CD invariant violation with no CD/LH divergence. Fixture: cd-stored-size-mismatch.zip. SECURITY_INVENTORY.md Recent wins (:343-358).

  • ZIP64 extra-field layout-smuggling (3 PRs). Three dimensions of the APPNOTE §4.5 CD/LH extra-data smuggling class closed together at the CD/LH boundary:

    • #1785 — inner-0x0001 block dataSize == 8 * N exactness check in parseZip64Extra, rejecting CD entries whose ZIP64 (headerId 0x0001) extra-field dataSize exceeds the 8 * N bytes consumed by the N sentinel-gated 32-bit standard fields (APPNOTE §4.5.3). Trailing slack past the consumed prefix is attacker-controllable and a parser-differential smuggling vector. Fixture: zip64-extra-oversized-datasize.zip. SECURITY_INVENTORY.md Recent wins (:281-289).
    • #1788 — outer sub-field structural walk validateExtraFieldStructure, invoked unconditionally on the blob at the CD site (parseCentralDir) and the LH site (readEntryData) before any sentinel guard. Pre-PR, the outer iteration lived only inside parseZip64Extra, which breaks silently on a malformed sub-field; the caller skipped parseZip64Extra entirely when no ZIP64 sentinel was set, so the anomaly was invisible in the no-sentinel case. Fixture: cd-extra-overrun-datasize.zip. SECURITY_INVENTORY.md Recent wins (:262-280).
    • #1793 — duplicate-0x0001 block rejection via a new hasDuplicateZip64Extra helper that walks the TLV structure once, invoked at both the CD-side caller (parseCentralDir) and the LH-side caller (readEntryData) before parseZip64Extra. APPNOTE §4.5 forbids more than one instance of any registered header ID per entry; for ZIP64 in particular the layout of each block depends on which standard 32-bit fields are at the 0xFFFFFFFF sentinel, so two blocks with different payloads make the resolved sizes/offset ambiguous (first-wins vs. last-wins parser-differential). Two error wordings ("duplicate ZIP64 extra field" vs "duplicate ZIP64 local extra field") keep CD/LH attribution distinct. Fixtures: cd-zip64-extra-duplicate.zip, lh-zip64-extra-duplicate.zip. SECURITY_INVENTORY.md Recent wins (:296-323).

    Together these three PRs close the ZIP64 extra-field layout- smuggling attack class at the CD/LH boundary (three dimensions: inner block self-length exactness, outer TLV structural soundness, uniqueness of the 0x0001 block).

Paired-review PRs (6). #1772 (reviews #1754), #1776 (reviews #1759), #1779 (reviews #1761), #1783 (reviews #1769), #1784 (reviews #1773), #1792 (reviews #1785). Four of the six (#1772/#1776/#1779/#1783) pair with feature PRs from the prior #1770 wave — they close out the post-wave review queue inherited across the summarize boundary. Two (#1784/#1792) pair with in-wave features: #1773 → #1784 at ~206 min and #1785 → #1792 at ~34 min — both inside the 30 min to several hours bracket noted in the Track E post-#1773 cadence entry progress/20260424T105643Z_c4046753-paired-review-1785.md §F. The remaining two in-wave feature PRs (#1788 and #1793) have no paired-review entry at batch close — they are the next planner's backlog, inherited across this summarize boundary just as the prior wave's #1754/#1759/#1761/#1769 pairs were.

Doc / chore (2).

  • #1794 (doc) — fixes the PR-number attribution in three ZipTest/ZipFixtures.lean precedence-shift comments, replacing #1770 with the correct #1773 authorship. Follow-up to the prior-wave paired-review PR #1778 §F.2 attribution-drift finding.
  • #1796 (chore) — extends scripts/check-inventory-links.sh with a placeholder-PR linter rule that fires on any (this PR) / (PR #TBD) text in the SECURITY_INVENTORY.md Recent wins / Minimized Reproducer Corpus blocks, and sweeps the outstanding placeholders in the ZIP64 extra-field Recent wins block so they reference committed #NNNN values. Closes #1786.

Scope discipline. Source-code changes this wave touched only Zip/Archive.lean (+87 net LOC: +89/−2 from four CD-parse checks — one stored-method invariant, three ZIP64 extra-field dimensions) and ZipTest/ZipFixtures.lean (+130 net LOC: +135/−5 from five new fixture/assert blocks plus the #1794 comment fixes). Zip/Spec/ stayed at 42 × 21,067 LOC with zero edits, matching the scope discipline of the prior two Track E waves.

Quality metrics: 0 sorries across Zip/ (unchanged — none of these PRs touched proofs); 0 runtime ]! across Zip/Native/ and Zip/*.lean (unchanged); bash scripts/check-inventory-links.sherrors=0, warnings=0 post-#1796 sweep (the placeholder-PR linter now fires if any (this PR) / (PR #TBD) text reappears, and all inherited line-anchor warnings have been reconciled). Fixtures in testdata/zip/malformed/ grew from 22 to 27 (five new: cd-stored-size-mismatch.zip, zip64-extra-oversized-datasize.zip, cd-extra-overrun-datasize.zip, cd-zip64-extra-duplicate.zip, lh-zip64-extra-duplicate.zip). Tar fixture counts (14 / 6) unchanged. Type mix across the 12 PRs: feature 4 / paired-review 6 / doc 1 / chore 1. Toolchain v4.29.1.

12-PR batch (Apr 24): Track E CD-parse defensive-check cadence + EOCD64 upper-bound sibling (summarize #1839):

Twelve PRs merged in the ~4.5-hour window between summarize #1803 (merge commit 6ed456d, 11:48:25Z 2026-04-24) and PR #1836 (merge commit 41bf587, 16:17:05Z 2026-04-24). No spec file touched; grep -rc sorry Zip/ stayed at 0 throughout.

Track E feature PRs (6). Each PR closes an independent CD-parse- time defensive dimension on the CD entry or the ZIP64 EOCD64 record; no two dimensions share a field.

  • CD versionNeededToExtract > 45 upper bound (#1807). Rejects CD entries claiming a versionNeededToExtract greater than 45 (the APPNOTE 6.3 ZIP64 ceiling) at parseCentralDir (Zip/Archive.lean:546), before the existing CD-vs-LH one-sided downgrade check (PR #1736) can be bypassed by both sides claiming an unsupported value. Fixture: cd-version-needed-too-high.zip. Distinct error substring ("unsupported versionNeededToExtract") from the later CD/LH downgrade check ("LH versionNeededToExtract" at :889); the .claude/skills/error-wording-catalogue/SKILL.md entries for both live side-by-side so future tests unambiguously pick the upper-bound vs. the downgrade message.
  • Per-entry localOffset + 30 ≤ cdOffset archive-layout invariant (#1813). Per-entry micro-counterpart of the in-flight archive-level macro cdOffset + cdSize ≤ eocdPos guard (issue #1799 / PR #1809). APPNOTE §4.3.6 pins the archive layout as [LH+data]* [CD] [EOCD], so every entry's LH must be readable strictly before the CD start. Pre-PR, Archive.list had no gate at all — only the extract path's late LH-signature check caught a subset of the construction. Fires at Zip/Archive.lean:665 post-ZIP64-resolution so the resolved UInt64 localOff is checked, not the 0xFFFFFFFF sentinel; uses asymmetric SpanInFile-shaped subtraction to avoid UInt64 wrap on crafted very-large values. Fixture: cd-entry-localoffset-past-cdstart.zip. SECURITY_INVENTORY.md Recent wins (:414-442).
  • internalFileAttributes reserved-bits guard (#1819). Rejects CD entries with any bit other than bit 0 ("apparent ASCII/text data") set in APPNOTE §4.4.10 internalFileAttributes (CD +36, UInt16). Mask internalAttrs &&& 0xFFFE == 0 preserves Info-ZIP text-flag interop (spot-check over testdata/zip/interop/: go-unix.zip, go-test.zip, go-crc32-not-streamed.zip set bit 0 on apparent-text files; the remaining interop fixtures use 0x0000) while rejecting smuggled reserved-bit values. Fires at Zip/Archive.lean:537 pre-ZIP64-resolution. Contiguous writer-zero UInt16 sibling of PR #1759 (CD +34 diskNumberStart): the three writer-zero CD fields +34 → +36 → EOCD.numEntriesThisDisk now fire their guards in the order the fields appear. Fixture: cd-entry-internal-attrs-reserved.zip. SECURITY_INVENTORY.md Recent wins (:443-473).
  • CD flag bit-5 (compressed patched data) rejection (#1824). Rejects CD entries whose APPNOTE §4.4.4 general-purpose flag bit 5 (PKWARE's proprietary compressed-patched-data format §4.6) is set at parseCentralDir (Zip/Archive.lean:619), pre-ZIP64-resolution. lean-zip implements neither creation nor extraction of the format; the writer emits flags = 0x0800 (bit 11 UTF-8 names) only. Third independent GPF bit dimension closed; siblings #1818 (bit 4 enhanced deflating) and #1822 (bits 7-10/12/14/15 reserved/unused) are in flight at batch close. Once those land, the GPF word is fully validated except bits 1-3 (deflate/data-descriptor) and bit 11 (UTF-8), which are legitimate. Mask-equality form (flags &&& 0x0020 == 0) matches the 0xFFF7 bit-3-masking convention in readEntryData. Fixture: cd-patched-data-flag.zip. SECURITY_INVENTORY.md Recent wins (:474-499).
  • ZIP64 EOCD64 versionMadeBy spec-version upper bound (#1826). Rejects EOCD64 records whose versionMadeBy field (APPNOTE §4.4.2.2, at bufPos + 12) carries a lower byte greater than 63 (spec version 6.3) at findEndOfCentralDir (Zip/Archive.lean:337). Only the lower byte is checked — real archives vary widely in host-OS code (upper byte: Info-ZIP emits 3, Windows producers 11 NTFS, etc.); the lower byte is a pure spec-version field with a well-defined APPNOTE maximum. Interop sweep: testdata/zip/interop/go-zip64.zip — the only interop fixture with an EOCD64 — has versionMadeBy=0x002d (low byte 45), comfortably below the bound. Archive-level counterpart to the per-entry CD versionMadeBy > 63 guard (issue #1812 / PR #1820 in flight at batch close); together the pair closes the versionMadeBy upper-bound dimension across both ZIP layers. Fixture: zip64-eocd64-versionmadeby-too-high.zip. SECURITY_INVENTORY.md Recent wins (:262-289).
  • CD entry name NUL-byte rejection (#1831). Rejects raw CD nameBytes containing 0x00 at parseCentralDir (Zip/Archive.lean:559), before the UTF-8 decode at :562-572. A NUL in the filename is a classic parser-differential / filesystem-truncation smuggling vector: POSIX open/stat treats evil.txt\x00.zip as evil.txt, while Archive.list callers and strict peer readers see the full NUL-embedded string. Guarding on the raw ByteArray before UTF-8 decode closes both the UTF-8 and Latin-1 decode branches uniformly and keeps the error message NUL-free. Simultaneously closes Archive.list (silent NUL-path surfacing) and Archive.extract (silent truncated-filename drop) dimensions. Interop pre-flight swept testdata/zip/{interop,malformed}/*.zip for pre-existing NUL-in-name fixtures — zero hits. Fixture: cd-nul-in-name.zip. SECURITY_INVENTORY.md Recent wins (:500-532).

Paired-review PRs (5). #1808 (reviews #1801, the prior-wave feature #1803 excluded from its 12-PR scope — inherited-backlog closure at 38 min), #1815 (reviews #1807, in-wave at 43 min), #1830 (reviews #1813, in-wave at 2 h 35 min), #1832 (reviews #1819, in-wave at 2 h 40 min), #1835 (reviews #1824, in-wave at 2 h 37 min). The three mid-wave in-wave pairs all crossed the 2 h mark — still inside the 30 min to several hours bracket documented in progress/20260424T105643Z_c4046753-paired-review-1785.md §F, but the median has slid from 43 min (wave start) to ~2.5 h (wave tail) as the paired-review queue filled faster than it drained. #1826 and #1831 have no paired-review entry at batch close; paired-review issues #1828 and #1833 are unclaimed in the queue, inherited across the summarize boundary the same way the prior wave's #1788/#1793 pairs were.

Doc (1). #1836 fixes a PR-number misattribution in SECURITY_INVENTORY.md's CD flag bit-5 Recent wins entry (#1820 → #1824) plus a related-class label drift. The particular drift was a real #NNNN value pointing at the wrong PR, not a placeholder; the #1796 placeholder-PR linter did not catch it, so the error was found by reviewer cross-check. A small footprint follow-up to the #1794/#1796 attribution-hygiene cluster of the prior wave.

Scope discipline. Source-code changes this wave touched only Zip/Archive.lean (999 → 1113 LOC, +114 from five new CD-parse checks — four field-per-field validations plus the archive-layout invariant — plus the EOCD64 versionMadeBy guard) and ZipTest/ZipFixtures.lean (687 → 841 LOC, +154 from six new fixture/assert blocks). Zip/Spec/ stayed at 42 files × 21,067 LOC with zero edits, matching the scope discipline of the prior three Track E waves.

Dispatcher snapshot. At batch close, coordination orient listed 11 open PRs in the merge-conflict state, all Track E CD-parse cascades from the rapid Archive.lean edits across this and the prior wave. This is flagged as a known-state observation (the pr-repair lane will drain it), not a summarize concern — the same cascading-conflict pattern follows every multi-PR Track E wave that edits a common section of parseCentralDir.

Quality metrics: 0 sorries across Zip/ (unchanged — none of these PRs touched proofs); 0 runtime ]! across Zip/Native/ and Zip/*.lean (unchanged); bash scripts/check-inventory-links.sherrors=0, warnings=0 post-wave. Fixtures in testdata/zip/malformed/ grew from 28 to 34 (six new: cd-version-needed-too-high.zip, cd-entry-localoffset-past-cdstart.zip, cd-entry-internal-attrs-reserved.zip, cd-patched-data-flag.zip, zip64-eocd64-versionmadeby-too-high.zip, cd-nul-in-name.zip). Tar fixture counts (14 / 6) unchanged. Type mix across the 12 PRs: feature 6 / paired-review 5 / doc 1. Toolchain v4.29.1.

Note: the pre-wave baseline of 28 fixtures is the actual tree count at #1803 merge, one higher than the 27 the prior summarize's Current State bullet recorded — #1801's cd-bad-method-early.zip landed 10 min before #1803 and was outside the prior summarize's 12-PR scope, so its fixture was omitted from the tally but was present in the tree.

14-PR batch (Apr 24): Track E CD-parse filename-validation trio + ZIP64 EOCD64 archive-layout / version-field bounds closure + Tar cross-format NUL-byte closure + inventory drift-tooling progression (summarize #1869):

Fourteen PRs merged in the ~3-hour window between summarize #1843 (merge commit bb2f514, 17:10:20Z 2026-04-24) and PR #1868 (merge commit 33275aa, 20:24:21Z 2026-04-24). Spanning four dominant themes — CD-parse filename-validation trio closure, ZIP64 EOCD64 archive-layout and version-field two-sided-bound invariants, Tar cross-format NUL-byte smuggling closure, and a terminating one-shot → sweep → detector progression on inventory drift — the wave consolidates three adjacent audit axes that had been expanding in parallel across the prior three waves. No spec file touched; grep -rc sorry Zip/ stayed at 0 throughout. Source edits landed only in Zip/Archive.lean (1113 → 1253 LOC, +140 from five new CD-parse and EOCD64 checks) and Zip/Tar.lean (+two NUL-byte guards at the GNU-long-name and PAX-record layers), plus ZipTest/ZipFixtures.lean + ZipTest/TarFixtures.lean fixture assertions. The filename-validation trio closes the "smuggled name" attack class at CD parse across three distinguishable forms (NUL-embedded #1831, zero-length #1848, path-unsafe #1840). The CD-parse mathematical-invariant family adds its second column — uncompSize == 0 → crc == 0 (#1857), sibling of the stored-method size invariant #1773. The ZIP64 EOCD64 archive-layout invariants trio closes at three granularities: per-entry (#1813, prior wave), archive (#1809, in-flight), ZIP64 record (#1856); the EOCD64 version- field pair versionMadeBy ≤ 63 (#1826, prior wave) + versionNeededToExtract ≤ 63 (#1852) closes the two-sided upper bounds at archive level. The Tar NUL-byte closures — GNU long-name / long-link (#1865) and PAX record key/value (#1866) — combined with the ZIP CD-layer #1831 span all three code-guarded user-supplied string-field layers (ustar base name / linkname is structurally NUL-safe by construction). Inventory drift: #1850 (one-shot misattribution fix) → #1867 (one-shot placeholder-PR and inverted-range sweep) → #1868 (automated warnings-only detector for both drift shapes), a deliberate terminating progression at this layer — the next drifts of these two shapes will be caught by CI rather than discovered by cross-check during review. Paired-review cadence tightened noticeably: three in-wave pairs landed with a median latency of ~1 h 17 min (vs. ~2 h 37 min tail in the prior wave) as the queue drained faster than new reviews arrived. Type mix across the 14 PRs: feature 7 / paired-review 4 / inventory 3. Fixtures in testdata/zip/malformed/ grew from 34 to 39 (+5: cd-path-unsafe.zip, cd-empty-name.zip, zip64-eocd64-versionneeded-too-high.zip, zip64-eocd64-overlap-locator.zip, cd-empty-entry-crc-nonzero.zip); fixtures in testdata/tar/malformed/ grew from 14 to 16 (+2: gnu-longname-nul-in-name.tar, pax-path-nul-in-value.tar). At wave close: 11 PRs in the repair queue (all Track E CD-parse cascades from the rapid Archive.lean edits across this + prior two waves), feature queue empty, review queue at 2 (#1862 EOCD64-layout paired-review, #1863 empty-entry-CRC paired-review). Toolchain v4.29.1.

Note: the issue body's title said "~13 PRs" but the actual post- #1843 window contains 14 — PR #1840 (CD path-unsafe, merged 12 min after #1843) was in-window but not tallied by the planner; it is included in this summary under the filename-validation trio framing. See the progress entry's Wave scope and PR-count note for the full accounting. Full progress file: progress/20260424T203421Z_90d1e22c-summarize-post-1843.md.

10-PR batch (Apr 24): Track E CD-parse math-invariant family column 3 + ZIP64 EOCD64 v2-record explicit-shape fixture + Tar UStar header NUL closure (summarize #1894):

Ten PRs merged in the ~2-hour 43-min window between summarize #1869 (merge commit 233fe98, 20:40:48Z 2026-04-24) and PR #1893 (merge commit 9200042, 23:23:56Z 2026-04-24). The wave's organising theme is the simultaneous closure of three Track E sub-stories that the post-#1843 summarize had flagged as in-flight or follow-up gaps: the CD-parse mathematical-invariant family second column extends to method=8 with an explicit compSize == 0 → uncompSize == 0 invariant (closing the deflate gap in the #1773 / #1857 family), the ZIP64 EOCD64 v2-record sentinel gets explicit fixture coverage against APPNOTE §4.3.14.2 strong-encryption extensions, and the cross-format NUL-byte closure extends to UStar's last user-supplied string-field layer — converting name / linkname / prefix from structurally NUL-safe by construction to explicitly code-guarded. No spec file touched; grep -rc sorry Zip/ stayed at 0 throughout.

Track E feature PRs (3).

  • #1886 — CD-parse compSize == 0 ∧ uncompSize > 0 rejection. Method-agnostic mathematical invariant at parseCentralDir, rejecting CD entries that promise non-empty uncompressed output from empty compressed input. Closes the method=8 gap in the CD-parse mathematical-invariant family — siblings #1773 (stored- method compSize == uncompSize) and #1857 (uncompSize == 0 → crc == 0) covered the prior two columns. Fixture: cd-deflate-zero-compsize.zip.
  • #1880 — Tar UStar header NUL-byte rejection. Adds explicit 0x00-byte rejection on name / linkname / prefix of the plain ustar 100-byte fields, converting the layer from structurally-NUL-safe-by-construction to explicitly code-guarded. Together with #1865 (Tar GNU long-name / long-link), #1866 (Tar PAX record key/value), and #1831 (ZIP CD entry name) this completes all four user-supplied string-field layers carrying explicit NUL-byte code guards across both the ZIP and Tar parsers. Fixture: ustar-name-nul-in-name.tar.
  • #1889 — explicit v2-shape ZIP64 EOCD64 record fixture. Adds zip64-eocd64-v2-record.zip as regression coverage for the existing PR #1761 record-size guard (size of record == 44) against APPNOTE §4.3.14.2 strong-encryption v2 EOCD64 extensions. Closes the v2-record-coverage gap flagged in the post-#1843 progress entry's Follow-up planning hooks — the guard already exists in code but had no positive-rejection fixture pinning it.

Paired-review PRs (6). #1874 reviews #1856 (EOCD64 archive-layout invariant, prior-wave carryover at 1 h 54 min), #1875 reviews #1857 (empty-entry CRC, prior-wave carryover at 1 h 50 min), #1882 reviews #1866 (Tar PAX NUL, prior-wave carryover at 1 h 46 min), #1884 reviews #1880 (Tar UStar NUL, in-wave at 29 min), #1890 reviews #1886 (CD-parse column 3, in-wave at 23 min), #1893 reviews #1889 (EOCD64 v2 fixture, in-wave at 23 min). The three in-wave pairs all landed inside 30 min — the tightest cluster in the Track E wave series so far — while the three carryover pairs drained the post-#1843 paired-review backlog at ~1 h 50 min each. Every in-wave feature PR has a matching review entry; the prior-wave #1865 paired-review remains in flight as PR #1876 (CI failed, in the repair queue at wave close), but #1856 / #1857 / #1866 — three of the four prior-wave unpaired features the post-#1843 progress entry's What remains section flagged — are all paired by the end of this wave.

Inventory bookkeeping (1). #1881 substitutes three #N placeholder-PR rows in SECURITY_INVENTORY.md (:641 → #1848, :683 → #1857, :1106 → #1866) — the one-shot follow-up the post-#1843 progress entry's What remains section anticipated, closing the three drifts the post-#1843 #1868 detector flagged that #1867's sweep could not substitute (because their feature PRs hadn't yet merged at #1867's drafting time).

Quality metrics: 0 sorries across Zip/ (unchanged — none of these PRs touched proofs); 0 runtime ]! across Zip/Native/ and Zip/*.lean (unchanged). Fixtures in testdata/zip/malformed/ grew from 39 to 41 (+2: cd-deflate-zero-compsize.zip, zip64-eocd64-v2-record.zip); fixtures in testdata/tar/malformed/ grew from 16 to 17 (+1: ustar-name-nul-in-name.tar). Type mix across the 10 PRs: feature 3 / paired-review 6 / inventory 1. Toolchain v4.29.1.

15-PR batch (Apr 24–25): Track E per-slot ZIP64-override family closure (4/5) + local-offset→LH validation trio completion + PAX duplicate-key closure + paired-review batch (summarize #1927):

Fifteen PRs merged in the ~3-hour 32-min window between summarize #1894 (merge commit 1bb9465, 00:30:23Z 2026-04-25) and PR #1926 (merge commit f7d1100, 03:07:43Z 2026-04-25); the lower bound includes four stragglers (#1896, #1897, #1899, #1903) that merged in the 67-min window between PR #1893 — the last PR captured in the #1894 summarize body — and #1904 itself merging, so the authoritative pre-wave anchor is summarize #1894 even though four of the wave's PRs predate it by clock time. The wave's organising theme: closure of two Track E sibling-trio families on the same day. The per-slot ZIP64-override family opened by #1745 (cdOffset slot, post-#1718 wave) closes 4 of its 5 remaining slots in a single ~2-hour cadence (cdSize / totalEntries / diskWhereCDStarts / numEntriesThisDisk), leaving only the numberOfThisDisk slot outstanding via in-flight PR #1909; and the local-offset → local-header validation trio opened by #1813 (level-1 cd-entry-localoffset-past-cdstart, post-#1803 wave) closes its level-2 (#1921 cd-entry-past-cdend, the existing entryEnd > cdEnd guard at Zip/Archive.lean:614-615) and level-3 (#1903 cd-bad-lh-signature, the late LH-signature guard at Zip/Archive.lean:1081) levels, so all three precedence layers now have explicit fixture coverage. Separately, #1899 closes the second parser-differential dimension on PAX-record decode (duplicate-key rejection in parsePaxRecords, companion to the prior #1866 NUL-byte closure on the same surface). No spec file touched; grep -rc sorry Zip/ stayed at 0 throughout. Source edits landed in Zip/Archive.lean (per-slot ZIP64-override fixture assertions), Zip/Tar.lean (+one duplicate-key guard at the PAX layer), ZipTest/ZipFixtures.lean + ZipTest/TarFixtures.lean fixture assertions, and SECURITY_INVENTORY.md (placeholder-PR substitutions and post-merge bookkeeping).

Track E feature PRs (6): per-slot ZIP64-override family closure (4/5).

  • #1905 — explicit per-slot fixture eocd-zip64-override-cdsize-mismatch.zip for the cdSize slot. Second slot in the 6-slot per-slot ZIP64-override family (sibling of #1745 cdOffset slot which opened the family).
  • #1908 — explicit per-slot fixture eocd-zip64-override-totalentries-mismatch.zip for the totalEntries slot. Third slot.
  • #1911 — explicit per-slot fixture eocd-zip64-override-diskcd-mismatch.zip for the diskWhereCDStarts slot. Fourth slot.
  • #1922 — explicit per-slot fixture eocd-zip64-override-entriesthisdisk-mismatch.zip for the numEntriesThisDisk slot. Fifth slot. After this PR only the numberOfThisDisk slot remains; tracked by issue #1902 with PR #1909 currently in the repair queue (merge-conflict at wave close).

Track E feature PRs continued: local-offset → local-header validation trio completion (level-2 + level-3).

  • #1903 — level-3: explicit fixture cd-bad-lh-signature.zip for the late LH-signature guard at Zip/Archive.lean:1081. Probes the third precedence layer of the local-offset → LH chain; the prior two levels were #1813 (level-1 cd-entry-localoffset-past-cdstart, post-#1803 wave) and #1921 (level-2, this wave).
  • #1921 — level-2: explicit per-entry CD-overrun fixture cd-entry-past-cdend.zip for the existing entryEnd > cdEnd guard at Zip/Archive.lean:614-615. With #1903 + #1813 this closes the trio so all three precedence layers (local-offset bound, CD-end overrun, LH-signature mismatch) carry explicit per-entry fixtures.

Track E feature PRs continued: PAX duplicate-key closure.

  • **#1899 — Tar PAX duplicate-key rejection in parsePaxRecords
    • pax-duplicate-path.tar fixture.** Closes the second parser-differential dimension on the PAX-record decode surface (companion to #1866 NUL-byte rejection on the same parsePaxRecords surface). After this PR both parser-differential dimensions on PAX records are closed (NUL-byte smuggling #1866 + duplicate-key first-wins-vs-last-wins #1899).

Inventory placeholder-PR substitution sweeps (2).

  • #1896 — substitute two #N placeholder-PR rows in SECURITY_INVENTORY.md (:766 → #1880, :1128 → #1880) post-#1880 one-shot bookkeeping.
  • #1897 — substitute five #N placeholder-PR occurrences (:261 → #1889, :740 / :777 / :1191 → #1886, :1223 → #1889) post-#1886/#1889 one-shot sweep. Note: subsequent #1928 (post-wave) added the pax-duplicate-path.tar corpus row + Tar Parser/Extractor recent-wins bullet that PR #1899 had itself omitted — see the next wave for that follow-up.

Paired-review PRs (6). #1913 reviews #1899 (PAX duplicate-key, in-wave at 1 h 34 min), #1914 reviews #1903 (level-3 LH-signature fixture, in-wave at 1 h 31 min), #1919 reviews #1905 (cdSize per-slot fixture, in-wave at 1 h 29 min), #1920 reviews #1908 (totalEntries per-slot fixture, in-wave at 1 h 27 min), #1925 reviews #1911 (diskWhereCDStarts per-slot fixture, in-wave at 1 h 39 min), #1926 reviews #1921 (level-2 CD-overrun fixture, in-wave at 38 min). Five of six in-wave pairs landed inside ~1 h 40 min — the cluster median tightens vs. the post-#1869 wave's in-wave triplet (~25 min) only because the per-slot family's review pairs spaced themselves out across the family's ~2-hour landing cadence rather than catching up at the end. Every in-wave feature PR has a matching review entry except #1922 (numEntriesThisDisk per-slot fixture, paired-review carried into the next wave as issue #1924 in the unclaimed review queue at wave close).

Quality metrics: 0 sorries across Zip/ (unchanged — none of these PRs touched proofs); 0 runtime ]! across Zip/Native/ and Zip/*.lean (unchanged). Fixtures in testdata/zip/malformed/ grew from 41 to 47 (+6: cd-bad-lh-signature.zip, cd-entry-past-cdend.zip, eocd-zip64-override-cdsize-mismatch.zip, eocd-zip64-override-totalentries-mismatch.zip, eocd-zip64-override-diskcd-mismatch.zip, eocd-zip64-override-entriesthisdisk-mismatch.zip); fixtures in testdata/tar/malformed/ grew from 17 to 18 (+1: pax-duplicate-path.tar). Type mix across the 15 PRs: feature 7 / paired-review 6 / inventory 2. At wave close: 13 PRs in the repair queue (12 carryover Track E CD-parse cascades from prior waves plus #1909 numberOfThisDisk per-slot fixture freshly entered the queue this wave); feature queue has 2 unclaimed UStar interior-NUL per-slot fixtures (#1929 linkname slot, #1930 prefix slot, both filed at 03:32Z); review queue at 1 (#1924, paired-review for #1922). Toolchain v4.29.1.

15-PR batch (Apr 25): Track E terminal closure of the 5-slot UStar interior-NUL family + terminal closure of the 2-slot GNU long-name / long-link family + first defense-in-depth-extension family closure in Track E history (summarize #1964):

Fifteen PRs merged in the ~3-hour 39-min window between summarize #1931 (merge commit b4ec7ce, 03:40:09Z 2026-04-25) and PR #1963 (merge commit 154c1f5, 07:31:44Z 2026-04-25). The wave's organising theme: two terminal per-slot fixture-family closures landing in near-simultaneous bursts on the same day, plus the first defense-in-depth extension of any per-slot fixture family in Track E history. The 5-slot UStar interior-NUL family opened by PR #1880 (name slot, post-#1869 wave) extended through the 3-slot filesystem-reaching arm (linkname PR #1934, prefix PR #1937 — terminal of the 3-slot arm) and then crossed into a new structural mode: the 4th uname slot (PR #1944) is defense-in-depth because the field does not flow into Tar.extract's filesystem operations — it is exposed only via Tar.list's entry.uname for callers routing on a trust decision. PR #1957 (gname slot) closes the 5-slot family at 5/5 slots; together with PR #1944 the 2-slot defense-in-depth arm (uname / gname) joins the closed 3-slot filesystem-reaching arm as one fully-closed 5-slot family. Concurrently, the 2-slot GNU long-name / long-link interior-NUL family — opened by PR #1865 (long-name slot, post-#1843 wave) which already added both guards in source — closes its per-slot fixture asymmetry at PR #1953 (long-link slot), reaching 2/2 closure ~30 min before the 5-slot UStar family closes at PR #1957. No spec file touched; grep -rc sorry Zip/ stayed at 0 throughout. Source edits landed only in Zip/Tar.lean (per-slot UStar interior-NUL guards extending the 3-slot forEntries block to 5 slots), plus ZipTest/TarFixtures.lean fixture assertions, four SECURITY_INVENTORY.md placeholder-PR substitution sweeps, and one error-wording-catalogue skill row registration.

Track E feature PRs (5): per-slot UStar interior-NUL family closure (#1934 / #1937 / #1944 / #1957) + per-slot GNU long-link interior-NUL fixture (#1953).

  • #1934 — explicit per-slot fixture ustar-linkname-nul-in-name.tar for the linkname slot. Second slot in the 5-slot UStar interior-NUL family (sibling of PR #1880 name slot which opened the family).
  • #1937 — explicit per-slot fixture ustar-prefix-nul-in-name.tar for the prefix slot. Third slot — closes the 3-slot filesystem-reaching arm at 3/3 slots. First fully-closed-on-master per-slot family of the post-#1928 wave (paired-reviewed by PR #1947 as the terminal-closure paired-review for the 3-slot arm).
  • #1944 — extends the UStar interior-NUL guard to the uname slot + ustar-uname-nul-in-uname.tar fixture. First defense-in-depth extension in Track E history: the uname field is a user-attestation string that does not flow into Tar.extract's filesystem operations, so the 4th-slot guard narrows the attack surface from filesystem archive-slip (the 3-slot arm's class) to parser-differential Tar.list smuggling on a caller routing on entry.uname for a trust decision.
  • #1953 — explicit per-slot fixture gnu-longlink-nul-in-link.tar for the GNU long-link slot (closes 2-slot family at 2/2). Companion to PR #1865's long-name slot fixture — both guards already existed in source from PR #1865, so this PR is fixture-only, closing the per-slot fixture asymmetry PR #1865 left behind.
  • #1957 — per-slot UStar gname interior-NUL guard + ustar-gname-nul-in-gname.tar fixture. 5th-and-final slot of the UStar family — terminal closure of the 5-slot family (3-slot filesystem-reaching arm name / linkname / prefix
    • 2-slot defense-in-depth arm uname / gname). Mirrors PR #1944's defense-in-depth structure.

Per-slot family closures and extensions (post-#1931 wave):

Family Slot count Closure / extension state at end of wave
UStar interior-NUL fs-reaching arm 3 3/3 closed at PR #1937 (this wave — terminal of the 3-slot arm)
UStar interior-NUL with uname (defense-in-depth) 4 4/4 landed at PR #1944 (this wave — first defense-in-depth extension in Track E history)
UStar interior-NUL with gname (final) 5 5/5 closed at PR #1957 (this wave — terminal of the 5-slot family)
GNU long-name / long-link 2 2/2 closed at PR #1953 (this wave — terminal; sibling of PR #1865 long-name)
EOCD ZIP64-override mismatch 6 5/6; gated on PR #1909 in repair queue (issue #1902 — carried into post-#1931 wave)
PAX path / linkpath NUL 2 1/2; path covered by PR #1866; linkpath gated on unclaimed issue #1855

The defense-in-depth distinction is structurally novel for Track E: the uname and gname fields are user-attestation strings (not filesystem-routing strings) that strict peer parsers preserve at full fidelity but lean-zip's Binary.readString would silently truncate at an interior NUL without the new guards. The 5-slot UStar interior-NUL family is the uniquely structured family in Track E so far — all earlier per-slot families had every slot reaching the filesystem (or, for the EOCD ZIP64-override family, every slot mathematically homologous). Future per-slot families that mix filesystem-reaching with defense-in-depth slots can reuse this wave's family-closure-vs-extension framing.

Paired-review PRs (5). #1940 reviews #1934 (linkname slot, in-wave at 35 min), #1947 reviews #1937 (prefix slot — terminal of 3-slot arm, in-wave at 1 h 9 min — labeled the first fully-closed-on-master per-slot family of the post-#1928 wave), #1951 reviews #1944 (uname slot — first defense-in-depth- extension paired-review in Track E history, in-wave at 53 min), #1962 reviews #1953 (long-link slot — terminal of 2-slot GNU family, in-wave at 49 min), #1963 reviews #1957 (gname slot — terminal of 5-slot UStar family, in-wave at 40 min). All five in-wave feature PRs have an in-wave matching paired-review entry, extending the post-#1904 wave's tightening cluster trend (median ~49 min vs. post-#1904's median ~1 h 31 min): every closure event now triggers a paired-review issue exactly once, and the paired-review queue drains within an hour of the closure. PR #1932 (paired-review for PR #1922 numEntriesThisDisk slot, the last unpaired feature from the post-#1904 wave) landed at the start of this wave (~12min into the window); count it as the prior-wave carryover that flushed the post-#1904 paired-review queue to zero.

Inventory placeholder-PR substitution sweeps (3).

  • #1941 — three-row sweep (SECURITY_INVENTORY.md placeholder rows for #1903, #1921, #1922 — post-#1904 follow-up).
  • #1949 — four-row sweep (placeholder rows for #1903, #1921, #1934, #1937 — sibling of PR #1941, extending coverage to the two newly-landed UStar interior-NUL fixtures).
  • #1958 — one-row sweep (placeholder row for #1944's ustar-uname-nul-in-uname.tar at line 1314 — post-#1944 one-shot).

Skill registration (1). PR #1948 — error-wording-catalogue rows for the tar interior-NUL family + PAX duplicate-key (#1899). The out-of-scope follow-up to PR #1880's paired-review surfaced as a discrete skill PR; registers the per-slot error-substring family in the catalogue so future tests can pick the right match string the first time.

Out-of-scope cleanup queued at wave close. Issue #1956 (cleanup-list registration for PR #1865's gnu-longname-nul-in-name.tar) and issue #1960 (post-#1953 + post-#1957 inventory sweep — closes two #N placeholders for gnu-longlink-nul-in-link.tar → #1953 and ustar-gname-nul-in-gname.tar → #1957) were queued at wave close; both landed in the post-#1963 follow-up window as PR #1967 and PR #1968 respectively, so they will appear in the next wave block.

Quality metrics: 0 sorries across Zip/ (unchanged — none of these PRs touched proofs); 0 runtime ]! across Zip/Native/ and Zip/*.lean (unchanged). Fixtures in testdata/zip/malformed/ unchanged at 47 (no ZIP fixtures landed this wave); fixtures in testdata/tar/malformed/ grew from 18 to 23 (+5: the four UStar interior-NUL slot fixtures ustar-linkname-nul-in-name.tar, ustar-prefix-nul-in-name.tar, ustar-uname-nul-in-uname.tar, ustar-gname-nul-in-gname.tar, plus the GNU long-link slot gnu-longlink-nul-in-link.tar). Type mix across the 15 PRs: feature 5 / paired-review 5 (in-wave) + 1 (carryover #1932) / inventory 3 / skill 1 — call it 5 / 6 / 3 / 1 if folding the carryover paired-review in, or 5 / 5 / 3 / 1 + 1 carryover. At wave close: repair queue at 14 PRs (12 carryover Track E CD-parse cascades from prior waves plus #1909 numberOfThisDisk per-slot fixture, plus the new #1959 prior-wave inventory line-anchor sweep that entered the queue with merge-conflict status); feature queue empty (all post-#1928 per-slot fixture issues — #1929 / #1930 / #1942 / #1945 / #1946 — landed via PRs #1934, #1937, #1944, #1953, #1957 by wave close); review queue empty (all in-wave paired-reviews landed; #1924 paired-review for #1922 landed as PR #1932 at the start of this wave). lake build clean (191 jobs); lake exe test all green; bash scripts/check-inventory-links.sh exits 0 with 104 warnings (pre-existing line-anchor drift). Toolchain v4.29.1.

11-PR batch (Apr 25): Track E third terminal closure of the post-#1928 wave at PR #1979 (2-slot PAX value-side override family at 2/2 — last unclosed Tar interior-NUL family) + post-closure cadence (paired-review + inventory row tightenings + Recent-wins bullet sequencing + skill updates) (summarize #1989):

Eleven PRs merged in the ~2-hour 34-min window between PR #1967 (merge commit c2786e1, 07:45:32Z 2026-04-25) and PR #1988 (merge commit 8f63bac, 10:19:43Z 2026-04-25). The window opens ~40 min before the prior summarize PR #1971 (merged 08:24:42Z) submitted: the three post-issue-creation tail PRs from the prior wave landed at 07:45–08:08Z, after issue #1964's 07:38Z creation but before #1971's 08:24Z merge — explicitly deferred to this wave per progress/20260425T082152Z_1f65eccf-summarize-post-1931.md (line 46). The wave's organising theme is the third terminal closure of the post-#1928 per-slot Tar interior-NUL family arc: PR #1979 closes the 2-slot PAX value-side override family (path / linkpath) at 2/2 by adding the per-slot linkpath regression fixture pax-linkpath-nul-in-value.tar (the symmetric valueBytes-arm guard already existed in source from PR #1866's (keyBytes / valueBytes) raw-NUL guard at parsePaxRecords, paired with PR #1866's path-slot fixture pax-path-nul-in-value.tar). With this closure, all three named per-slot Tar interior-NUL families flagged by PR #1971's wave-block remaining-work list are terminally closed and paired-reviewed:

  1. 5-slot UStar interior-NUL family — terminal at PR #1957 / paired-review #1963 (prior wave).
  2. 2-slot GNU long-name / long-link — terminal at PR #1953 / paired-review #1962 (prior wave).
  3. 2-slot PAX value-side override (path / linkpath) — terminal at PR #1979 / paired-review #1986 (this wave).

PR #1986 completes the paired-review trio (#1962 / #1963 / #1986 reviewing #1953 / #1957 / #1979 respectively). The wave is otherwise post-closure cleanup: three inventory row tightenings (#1985, #1987, #1988), one Recent-wins bullet add for the second terminal closure of the post-#1928 wave (#1978 GNU long-name / long-link, sequenced ahead of the still-pending PAX value-side bullet which remains queued under issue #1983 at wave close), and two skill updates (#1973 malformed-fixture-builder per-slot printable-prefix discipline; #1975 inventory-reconciliation terminal-closure tightening cadence). Source edits were minimal: only the new testdata/tar/malformed/pax-linkpath-nul-in-value.tar fixture and a matching ZipTest/TarFixtures.lean builder + assertion entry from PR #1979; Zip/Tar.lean itself was not touched by any wave PR. grep -rc sorry Zip/ stayed at 0 throughout.

Tail-deferred from prior wave (3): PRs landed between issue #1964's 07:38Z creation and PR #1971's 08:24Z merge.

  • #1967 (07:45Z) — test cleanup: register gnu-longname-nul-in-name.tar in ZipTest/TarFixtures.lean's cleanup-list array (one-line follow-up to PR #1865's missing cleanup-list registration; closes issue #1956).
  • #1968 (07:50Z) — inventory two-row placeholder sweep: substitute #N PR-refs in SECURITY_INVENTORY.md for gnu-longlink-nul-in-link.tar → #1953 and ustar-gname-nul-in-gname.tar → #1957 (terminal closure of the post-#1928 placeholder set per the prior wave block's tracking; closes issue #1960).
  • #1969 (08:07Z) — error-wording-catalogue skill refresh: credit PR #1957 on the UStar interior-NUL row and PR #1953 on the GNU long-name / long-link row, retire stale "deferred to follow-up planner cycle issue #1946" and "in flight via per-slot family-extension issue #1945" clauses, add the defense-in-depth annotation noting uname / gname do not reach Tar.extract.

Per-slot fixture terminal closure (1): PR #1979 — third terminal closure of the post-#1928 wave; sibling of pre-wave PR #1866 path slot.

  • #1979 (09:36Z) — explicit per-slot fixture pax-linkpath-nul-in-value.tar for the PAX linkpath slot (closes 2-slot PAX value-side override family at 2/2). Sibling of PR #1866 (path slot, pre-wave) which added both arms of the (keyBytes / valueBytes) raw-NUL guard at parsePaxRecords in one source landing but emitted only the path fixture. PR #1979 closes the per-slot fixture asymmetry — fixture-only PR (no source change), structurally parallel to PR #1953 (GNU long-link slot, prior wave) which closed the GNU 2-slot family at 2/2 with the same fixture-only shape. Third terminal closure of the post-#1928 wave after PRs #1957 (5-slot UStar) and #1953 (2-slot GNU long-name / long-link); the PAX value-side override family was the last Tar interior-NUL family flagged by PR #1971's remaining-work list, and its closure terminates the post-#1928 Tar interior-NUL closure arc.

Per-slot family closures (post-#1971 wave):

Family Slot count Closure state at end of wave
UStar interior-NUL family 5 5/5 closed at PR #1957 (prior wave — terminal)
GNU long-name / long-link 2 2/2 closed at PR #1953 (prior wave — terminal)
PAX value-side override (path / linkpath) 2 2/2 closed at PR #1979 (this wave — terminal; sibling of PR #1866 path slot)
EOCD ZIP64-override mismatch 6 5/6; gated on PR #1909 in repair queue (carried over from prior wave, merge-conflict status)

With the PAX value-side family's closure, all three named per-slot Tar interior-NUL families flagged by PR #1971's wave-block remaining-work list are terminally closed; the only remaining in-flight per-slot family at wave close is the EOCD ZIP64-override mismatch (5/6, gated on PR #1909's numberOfThisDisk slot, still in the repair queue with merge-conflict status as it has been since the prior wave).

Paired-review (1). PR #1986 reviews #1979 (PAX linkpath slot — terminal closure of the 2-slot PAX value-side override family, in-wave at 35 min) — the third terminal-closure paired-review in the post-#1928 wave arc, completing the paired-review trio (#1962 / #1963 / #1986 for #1953 / #1957 / #1979 respectively). At wave close the review queue is empty.

Recent-wins bullet add (1). PR #1978 — adds a SECURITY_INVENTORY.md Recent-wins bullet under Tar Parser/Extractor for the GNU long-name / long-link interior-NUL family closure (PRs #1865 + #1953); the second terminal-closure bullet of the post-#1928 wave after the existing UStar bullet. Also tightens the UStar bullet's sibling-of paragraph to credit the per-slot long-link slot (PR #1953). The third terminal-closure bullet (post-#1979 PAX value-side) remains queued under issue #1983 at wave close and is deferred to the next wave per the post-issue-creation tail deferral pattern (and explicitly is not yet landed at the moment of issue #1989's creation at 10:23Z).

Inventory row tightenings (3).

  • #1985 — gnu-longname-nul-in-name.tar row tightening at SECURITY_INVENTORY.md:1300: credit PR #1953 long-link slot fixture (retire stale "covered by symmetric code review rather than a dedicated fixture" clause), retire stale "PAX path/linkpath NUL-byte coverage is the sibling issue #1855" clause (#1855 was closed by PR #1866 for the path arm; the linkpath arm is separately tracked), re-anchor stale Zip/Tar.lean:588 line citation to current master :672.
  • #1987 — placeholder-PR substitution sweep (one-row): row 1341 (pax-linkpath-nul-in-value.tar) #N#1979. Closes the post-merge inventory-sweep deferral flagged in progress/20260425T093345Z_78e1378b-feature-1976.md. Sibling shape of post-#1928 placeholder-sweep precedents PR #1958 / PR #1968 / PR #1949.
  • #1988 — pax-path-nul-in-value.tar row tightening at SECURITY_INVENTORY.md:1343: per-slot family-closure annotation crediting the 2-slot PAX value-side override family closure 2/2 (PR #1866 path slot + PR #1979 linkpath slot); tightens the trailing 'trio' sibling-of paragraph from three families to four (ZIP CD name + GNU 2/2 + UStar 5/5 + PAX 2/2 — now including the PAX value-side family).

Skill updates (2).

  • #1973 — malformed-fixture-builder per-slot printable-prefix discipline section. Encodes slot-identity in the smuggled value's printable ASCII prefix (post-#1928 wave examples: evil.txt / evil.lnk / badpfx / trusted / safe.lnk — including the .lnk extension as a novel slot-identity marker for link-typed slots from PR #1953); documents the trailing-NUL invariant for interior-NUL fixtures (last byte must be non-NUL or stripTrailingNuls would erase the interior NUL the test intends); documents the writer-side override-hook pattern (pathOverride-style for transformed slots like prefix's splitPath, no override needed for verbatim-written slots like linkname / uname / gname).
  • #1975 — inventory-reconciliation terminal-closure tightening cadence + per-slot fixture-naming asymmetry. Codifies the post-#1928 wave's 5-step #1880 → #1934 → #1937 → #1944 → #1957 precedent (terminal closure → paired-review → inventory row tightening → Recent-wins bullet → placeholder sweep). Independent of in-flight #1966 malformed-fixture-builder skill update — different skill file, different cadence layer.

Post-#1928 wave audit-trail callout. The post-#1928 Tar interior-NUL closure arc is structurally complete: all three named per-slot Tar interior-NUL families flagged by the post-#1931 wave block are now terminally closed and paired-reviewed (PR #1986 completes the paired-review trio for the third closure). The post-closure cadence (paired-review + inventory row tightening + Recent-wins bullet + skill updates) continues into the next wave only for the queued PAX Recent-wins bullet (issue #1983) and the post-issue-creation tail deferral skill (issue #1984).

Post-issue-creation tail (out of wave scope). No PRs for issue #1983 (PAX Recent-wins bullet — third terminal-closure bullet of the post-#1928 wave; sibling shape of PR #1978) or issue #1984 (skill: codify the post-issue-creation tail deferral pattern in a tracked .claude/skills/ file) had landed at the moment of issue #1989's creation (2026-04-25T10:23Z); both were in the unclaimed feature queue. Per the post-issue-creation tail deferral pattern, any PRs that land between this issue's creation and the wave PR's submission will be deferred to the next wave block; this wave's scope is frozen at the 11 PRs enumerated above per the issue-body-as-source-of-truth invariant.

Quality metrics: 0 sorries across Zip/ (unchanged — none of these PRs touched proofs); 0 runtime ]! across Zip/Native/ and Zip/*.lean (unchanged). Fixtures in testdata/zip/malformed/ unchanged at 47 (no ZIP fixtures landed this wave); fixtures in testdata/tar/malformed/ grew from 23 to 24 (+1: pax-linkpath-nul-in-value.tar from PR #1979). Type mix across the 11 PRs: feature 1 (#1979) / paired-review 1 (#1986) / inventory 4 (#1968 + #1985 + #1987 + #1988) / Recent-wins bullet 1 (#1978) / skill 3 (#1969 + #1973 + #1975) / test cleanup 1 (#1967). The character is wave-cleanup rather than wave-headline: the wave carries one terminal closure plus the post-closure follow-on cadence (inventory tightening, paired-review, skill refinements), in contrast to PR #1971's wave which carried two terminal closures + one defense-in-depth-extension first. At wave close: repair queue at 14 PRs (unchanged from prior wave — no new entrants, no salvages); feature queue at 2 (issue #1983 PAX Recent-wins bullet, issue #1984 skill); review queue empty (PR #1986 drained the post-#1979 paired-review entry within 35 min of merge). lake build clean (191 jobs); lake exe test all green; bash scripts/check-inventory-links.sh exits 0 with 104 warnings (pre-existing line-anchor drift, unchanged from prior wave). Toolchain v4.29.1.

35-PR batch (Apr 25): post-#1989 wave — pure inventory-anchor refresh wave (no terminal closures, no Track E feature work) covering 8 distinct SECURITY_INVENTORY.md sub-tracks + 2 tail-deferred PRs from the prior wave; first maintenance-only wave of the post-#1928 closure arc (summarize #2074):

Thirty-five PRs merged in the ~3-hour 39-min window between PR #1996 (10:53:21Z 2026-04-25) and PR #2072 (14:32:30Z 2026-04-25), opening immediately after PR #1990 (the prior summarize wave PR) merged at 10:34:48Z. The wave's organising character is maintenance saturation: the post-#1813 / #1857 / #1886 / #1903 / #1921 CD-parse-guard wave shifted all Zip/Archive.lean line anchors uniformly by +111 lines, and the post-#1866 / #1880 / #1934 / #1937 / #1944 / #1953 / #1957 / #1979 / #1899 Tar parser growth wave shifted Zip/Tar.lean anchors by mixed +29 / +91 / +97 / +154 line amounts. Each cited row in SECURITY_INVENTORY.md then received its own atomic re-anchor PR per the established one-PR-per-row / one-PR-per-cluster cadence. There is no terminal closure in this wave; the post-#1928 Tar interior-NUL closure arc (which dominated the post-#1928 / post-#1931 / post-#1971 waves with three named per-slot family closures and their paired-reviews) is structurally complete, and the planner-side activity has shifted entirely to anchor maintenance. Source code (Zip/, c/, testdata/) was not touched by any of the 35 PRs; grep -rc sorry Zip/ stayed at 0 throughout.

Tail-deferred from prior wave (2): PRs landed between issue #1989's 10:23Z creation and PR #1990's 10:34Z merge.

  • #1996 (10:53Z) — Recent-wins bullet: add SECURITY_INVENTORY.md Recent-wins bullet under Tar Parser/Extractor for the 2-slot PAX value-side override (path / linkpath) NUL-byte silent-skip family closure 2/2 (PR #1866 path slot + PR #1979 linkpath slot — third terminal closure of the post-#1928 wave); shape sibling of PR #1978 (post-#1953 GNU bullet). Closes issue #1983 from the prior planning cycle (the queued third-terminal-closure bullet flagged at the post-#1971 wave-block close).
  • #1997 (11:02Z) — summarize-flow skill landing: codify the post-issue-creation tail deferral pattern in a tracked .claude/skills/ file (summarize-flow/SKILL.md, documenting the issue-body-as-source-of-truth invariant for summarize waves and the worked precedent of PR #1971 deferring tail PRs #1967 / #1968 / #1969). Replaces rejected #1974 (which targeted the off-limits .claude/CLAUDE.md). Closes issue #1984 from the prior planning cycle.

Inventory: Tar fixture row anchor refresh (13 PRs) — Zip/Tar.lean line citations across the SECURITY_INVENTORY.md Minimized Reproducer Corpus tar block. All shifts are subsets of the Tar parser growth wave; each PR re-anchors a single row or a same-anchor cluster.

  • #1998 (11:05Z) — row 1332: gnu-longname-nul-in-name.tar (:576 → :667 long-name primary cell + :672 → :679 long-link sibling-of clause).
  • #1999 (11:09Z) — row 1328: gnu-longlink-nul-in-link.tar (:672 → :679 primary cell + bare prose 'long-name slot at line 660''line 667').
  • #2000 (11:11Z) — rows 1330 + 1331: gnu-longname-invalid-utf8.tar (:575 → :670) + gnu-longname-no-terminator.tar (:528 → :619).
  • #2005 (11:20Z) — row 1350: ustar-uname-nul-in-uname.tar 4-anchor sweep (uname primary :532 → :537 + name / linkname / prefix sibling-throw markdown links).
  • #2007 (11:28Z) — rows 1416 / 1417 / 1418: testdata/tar/security/{hardlink-outside,symlink-absolute,symlink-slip}.tar (typeHardlink silent-skip + 2 symlink-target throws in Tar.extract; :710 → :812 / :701 → :799 / :703 → :801).
  • #2008 (11:32Z) — row 1391: bad-checksum.tar (parseHeader checksum mismatch :444 → :487).
  • #2012 (11:44Z) — row 1399: no-magic.tar (parseHeader magic check :448 → :491).
  • #2014 (11:53Z) — row 1409: truncated.tar (Tar.extract short-read throw :686 → :783).
  • #2017 (12:05Z) — row 1402: pax-inconsistent-length.tar (parsePaxRecords recordEnd silent-skip :79 → :118).
  • #2024 (12:29Z) — rows 1403 / 1404: pax-invalid-utf8-{key,value}.tar (String.fromUTF8? if-let guard :122 → :144).
  • #2037 (13:07Z) — rows 1406 / 1408: pax-oversized-length + pax-truncated-record (digitCount :95 → :108 + recordEnd :105 → :118).
  • #2043 (13:19Z) — rows 1397 + 1401: gnu-longname-oversized-size + pax-extended-oversized-size (readEntryData maxHeaderSize cap throw :222 → :252).
  • #2044 (13:22Z) — row 1416: hardlink-outside.tar re-anchor (typeHardlink silent-skip policy comment :812 → :807; substring-mismatch flavour after the typeHardlink substring drifted outside the ±2 window of PR #2007's anchor).

Inventory: ZIP fixture row anchor refresh (7 PRs) — Zip/Archive.lean line citations across the Minimized Reproducer Corpus zip block. All shifts are within the Archive CD-parse-guard / late-section growth waves.

  • #2025 (12:34Z) — row 1421: bad-crc.zip (post-extraction CRC32 verify :1088 → :1199).
  • #2032 (12:49Z) — row 1422: bad-method.zip dual-anchor (CD-parse :668 → :713 + late dispatch :1083 → :1194).
  • #2045 (13:25Z) — rows 1444 + 1457 + 1458 + 1459: cd-stored-size, oversized-compressed, oversized-zip64-compressed, oversized-zip64-uncompressed 4-row same-anchor sweep (:743 → :786).
  • #2046 (13:28Z) — rows 1456 + 1460: no-eocd + too-short 2-row same-anchor sweep (findEOCD throw :945 → :1056).
  • #2047 (13:31Z) — rows 1461 + 1463 + 1464: zip64-eocd64-{bad-recsize,versionmadeby-too-high,versionneeded-too-high}.zip 3-row contiguous-block sweep (uniform +26 shift).
  • #2052 (13:43Z) — row 1432: cd-extra-overrun-datasize.zip dual-anchor (CD-side :690 → :739 + LH-side :1006 → :1117).
  • #2053 (13:46Z) — row 1424: cd-bad-method-early.zip (:668 → :713 single-anchor; designed-in-isolation sibling of dual-anchor row 1422).

Inventory: ZIP CD/LH consistency row anchor refresh (2 PRs) — the cd-lh consistency 'metadata always agrees' / usesDataDescriptor-gated row clusters in the Minimized Reproducer Corpus zip block.

  • #2018 (12:10Z) — rows 1434 / 1435 / 1436 / 1439: 4-row contiguous-block sweep on the always-checked consistency quartet (cd-lh-{flags,method,modtime,version}-mismatch.zip, inside validateAgainstLocalHeader's top half; :1036/:1027/:1056/:1046 → :1147/:1138/:1167/:1157, uniform +111 shift).
  • #2023 (12:26Z) — rows 1433 / 1437 / 1438: 3-row contiguous-block sweep on the usesDataDescriptor-gated trio (cd-lh-{crc,size,uncompsize}-mismatch.zip, inside validateAgainstLocalHeader's bottom half; :1067/:1061/:1064 → :1178/:1172/:1175, uniform +111 shift).

Inventory: Decompression Limit Inventory anchor refresh (2 PRs) — the Public extraction APIs row clusters in the Recommended policy table (distinct from the Minimized Reproducer Corpus sweeps).

  • #2034 (13:00Z) — Archive cluster (rows 1186-1191): 6-row contiguous-block sweep on def list / def extract / def extractFile (:1098/:1122/:1167 → :1209/:1233/:1278, uniform +111 shift).
  • #2036 (13:05Z) — Tar.extractTarGzNative cluster (rows 1196-1198): 3-row contiguous-block sweep (:848 → :945 partial def + :851 → :948 maxOutputSize parameter, uniform +97 shift).

Inventory: Local guard inventory anchor refresh (8 PRs) — the Local guard inventory for Handle.read and Stream.read table rows + intro prose. This is the most concentrated sub-track of the wave because both shift waves intersect across the table.

  • #2054 (13:48Z) — row 1308: forEntries per-block-header read (:565 → :656 single-anchor, +91).
  • #2058 (13:58Z) — row 1313: extractTarGz tarStream wrapper (:793 → :890 single-anchor, +97).
  • #2059 (14:01Z) — row 1310: skipEntryData 4-anchor sweep across directory / symlink / unsupported-typeflag / Tar.list payload-skip callsites (:619/:650/:657/:671 → :767/:805/:812/:826, non-uniform +148/+155/+155/+155).
  • #2068 (14:18Z) — row 1312: Tar.extract padding loop per-chunk read (:695 → :792 single-anchor, +97).
  • #2069 (14:24Z) — row 1309: readBoundedEntryData 4-callsite GNU long-name / long-link / PAX extended / PAX global header sweep + readEntryData body cap anchors (8-anchor sweep + prose update).
  • #2070 (14:27Z) — prose lines 1290-1295: Local guard intro paragraph 5-anchor sweep mixing Zip/Archive.lean (+111) and Zip/Tar.lean (+29 / +91) shifts.
  • #2071 (14:29Z) — row 1302: readExactStream callsite + Nat→USize roundtrip dual-anchor (Zip/Archive.lean:883 → :994 / :884 → :995).
  • #2072 (14:32Z) — row 1303: readExact h tailSize EOCD tail dual-anchor (:941 → :1052 / :938 → :1049, uniform +111).

Inventory: in-prose anchor refresh (1 PR) — the Boundary-Facing Subsystems → ZIP Archive Reader/Extractor prose section.

  • #2033 (12:57Z) — row 1066: in-prose 'Symlink/hardlink extraction policy' section, Zip/Tar.lean:210 → :251 (function-rename refactor: prose updated from the old callsite to the current partial def extract definition line).

Inventory-table coverage summary at wave close.

Inventory sub-track Coverage state at wave close
Minimized Reproducer Corpus tar block Refresh sweep terminal at this wave (12 PRs cleared all stale Tar fixture-row anchors).
Minimized Reproducer Corpus zip block Refresh sweep terminal at this wave (7 PRs cleared all stale Archive fixture-row anchors).
Minimized Reproducer Corpus CD/LH consistency rows Refresh sweep terminal at this wave (2 PRs cleared the consistency quartet + usesDataDescriptor trio).
Decompression Limit Inventory Public extraction APIs Archive cluster (rows 1186-1191) + Tar extractTarGzNative cluster (rows 1196-1198) refreshed in-wave; sibling Tar extract + extractTarGz cluster (rows 1192-1195) outstanding via repair-queue PR #2035 (issue #2027).
Local guard inventory for Handle.read / Stream.read Structurally complete pending issue #2066 (rows 1305+1306) and issue #2067 (row 1307) unclaimed; PR #2073 (row 1304, issue #2065) and PR #2060 (row 1311, issue #2055) in repair queue.
Boundary-Facing Subsystems → ZIP Archive Reader/Extractor prose Row 1066 refreshed in-wave (PR #2033); other in-prose anchors not touched this wave.

Wave-character callout. This is the first maintenance-only wave of the post-#1928 closure arc — no feature PRs, no fixture additions, no Zip/ source edits. The contrast with the immediately-prior waves is sharp: post-#1971 carried 1 terminal closure (PR #1979 closing the PAX value-side family 2/2) plus its post-closure cadence; post-#1931 carried 2 terminal closures (PR #1957 5-slot UStar, PR #1953 2-slot GNU) + 1 defense-in-depth-extension family closure (PR #1944 uname slot); post-#1928 carried the opening 3 closures (PR #1880 UStar code-guard, PR #1934 linkname, PR #1937 prefix). With the post-#1971 wave terminating the named per-slot Tar interior-NUL closure arc and the planner queue empty of Track E feature issues, the post-#1989 wave is the natural wind-down: 33 of 35 PRs (~94%) are inventory re-anchor PRs, 1 is the third-terminal-closure Recent-wins bullet (#1996 — the queued post-#1979 PAX value-side bullet deferred from the post-#1971 wave's tail), and 1 is the post-issue-creation tail-deferral skill (#1997 — also deferred from the prior wave). The wave demonstrates that when concentrated parser-source growth shifts multiple inventory line citations at once, the one-PR-per-row / one-PR-per-cluster cadence scales: 33 inventory PRs in ~3.5 h, average ~6.5 min between merges, no merge-conflict collisions on the inventory file (which is append-only at the row level).

Repair queue snapshot at wave close. 19 PRs in conflict / failing CI per coordination list-pr-repair (18 merge-conflicts + 1 failed CI on PR #1876). Carry-over entirely from prior waves; this wave neither added new entrants nor salvaged any existing entries. The repair queue's 5 oldest entries (#1725 / #1743 / #1755 / #1764 / #1771) trace back to the Track E CD-parse cascade that predates the post-#1928 closure arc, while the 4 most recent entries (#2073 / #2060 / #2035 / #2013) are sibling-row inventory PRs that conflicted with in-wave landings on the same SECURITY_INVENTORY.md cluster.

Post-issue-creation tail (out of wave scope). Per the post-issue-creation tail deferral pattern codified in .claude/skills/summarize-flow/SKILL.md (landed in this wave as PR #1997 — the first wave to apply the codified pattern in the planning loop), any PRs that land between issue #2074's 14:41Z creation and this wave PR's submission are deferred to the next wave block. At the moment of issue #2074's creation, the unclaimed planner queue listed two inventory issues for the still-pending Local guard table rows (#2066 rows 1305+1306, #2067 row 1307); their PRs will appear in the next wave block per the issue-body-as- source-of-truth invariant. This wave's scope is frozen at the 35 PRs enumerated above.

Quality metrics: 0 sorries across Zip/ (unchanged — none of these PRs touched proofs); 0 runtime ]! across Zip/Native/ and Zip/*.lean (unchanged — no source edits this wave). Fixtures in testdata/zip/malformed/ unchanged at 47; fixtures in testdata/tar/malformed/ unchanged at 24. Spec-line counts unchanged (no Zip/Spec/ PRs). Type mix across the 35 PRs: inventory 33 / Recent-wins bullet 1 (#1996) / skill landing 1 (#1997) — ratio ~94% inventory. At wave close: repair queue at 19 PRs (carry-over); feature queue at 2 unclaimed inventory issues (#2066, #2067 — both Local guard table rows); review queue empty (no closure events generated paired-review entries this wave). lake build clean (191 jobs); lake exe test all green; bash scripts/check-inventory-links.sh exits 0 with 40 warnings (pre-existing line-anchor drift, slightly reduced from prior wave's 104 as 33 inventory re-anchor PRs cleared most stale citations). Toolchain v4.29.1.

12-PR batch (Apr 25): post-#2074 wave — second consecutive maintenance-only wave of the post-#1928 closure arc; pure inventory-anchor refresh wave (no terminal closures, no Track E feature work, no source code) covering three narrower shift sub-classes (CD-parse-guard +43–+45 / Archive late-section +111 / ZIP64-extra +49) (summarize #2108):

Twelve PRs merged in the ~58-min window between PR #2080 (15:02:09Z 2026-04-25) and PR #2105 (16:00:33Z 2026-04-25), opening immediately after PR #2074 (the prior summarize wave PR) merged at 14:51:36Z. The wave is structurally a shift- class refinement of the prior post-#1989 wave: where the 35-PR post-#1989 wave broad-stroked across 8 inventory sub-tracks at once, the post-#2074 wave is concentrated on three narrower shift classes that landed slightly later in the post-#1813 / #1857 / #1886 / #1903 / #1921 CD-parse-guard wave's tail — specifically the +43–+45 fine-grained CD-parse shift (5 PRs), the +49 ZIP64-extra body shift (2 PRs sharing sibling parseZip64Extra arms), and the +111 Archive late-section shift on three remaining EOCD / LH duplicate-block guards (3 PRs). One Local guard cluster row (1305+1306, PR #2080) and one cap-of-EOF guard row (1442, PR #2090) round out the wave at +111. There is no terminal closure in this wave; the post-#1928 Tar interior-NUL closure arc remains structurally complete (terminal at PR #1979 in the post-#1971 wave), and the planner-side activity continues to be anchor maintenance. Source code (Zip/, c/, testdata/) was not touched by any of the 12 PRs; grep -rc sorry Zip/ stayed at 0 throughout.

Inventory: CD-parse-guard +43–+45 sub-class (5 PRs) — the Zip/Archive.lean CD-parse late-arm guards that took the +43 or +45 shift from the CD-parse-guard wave's fine-grained tail.

  • #2082 (15:02Z) — row 1428: cd-entry-disknum-mismatch.zip CD-entry diskNumberStart-mismatch throw (:549 → :592, +43).
  • #2089 (15:23Z) — row 1430: cd-entry-localoffset-past-cdstart.zip overlap-CD throw (:728 → :771, +43).
  • #2101 (15:47Z) — row 1454: invalid-utf8-with-flag.zip invalid-UTF-8 throw (:595 → :638, +43).
  • #2104 (15:55Z) — row 1443: cd-patched-data-flag.zip patched-data flag bit 5 throw (:682 → :725, +43).
  • #2105 (16:00Z) — row 1440: cd-nul-in-name.zip CD-name NUL-byte throw (:589 → :632, +43).

Inventory: Archive late-section +111 sub-class (3 PRs) — the Zip/Archive.lean EOCD / LH-duplicate-block guards that took the +111 shift on three rows the prior wave did not reach.

  • #2091 (15:28Z) — row 1446: eocd-disknum-mismatch.zip EOCD disk-number throw (:480 → :523, +43; Archive late-section sub-class but small +43 line shift on this particular row because the throw lives in the EOCD-validate early-arm rather than the late-arm cluster).
  • #2093 (15:32Z) — row 1448: eocd-numentries-thisdisk-mismatch.zip EOCD numEntriesThisDisk throw (:484 → :533, +49).
  • #2098 (15:40Z) — row 1455: lh-zip64-extra-duplicate.zip LH duplicate ZIP64 local extra throw (:1013 → :1124, +111; LH-side counterpart of CD-side row 1445 in the ZIP64-extra sub-class below).

Inventory: ZIP64-extra +49 sub-class (2 PRs) — sibling parseZip64Extra arms at the same function body, +49 shift.

  • #2099 (15:42Z) — row 1465: zip64-extra-oversized-datasize.zip malformed-extra throw (:697 → :746).
  • #2100 (15:45Z) — row 1445: cd-zip64-extra-duplicate.zip CD-side duplicate-block throw (:694 → :743; CD-side counterpart of LH-side row 1455 PR #2098 above).

Inventory: Local guard cluster +111 (1 PR, dual-row sweep) — the readBoundedSpanFromHandle LH-guard cluster rows.

  • #2080 (15:02Z) — rows 1305+1306: readBoundedSpanFromHandle local-header + local-name+extra for {label} rows, 2-row contiguous-block sweep (:967 → :1078 row 1305, three occurrences per row; :995 → :1106 row 1306, three occurrences per row; uniform +111 shift).

Inventory: cap-of-EOF +111 (1 PR) — the cdOffset + cdSize ≤ fileSize guard row.

  • #2090 (15:26Z) — row 1442: cd-past-eof.zip archive-layout invariant throw (:946 → :1057 on the unless line, +111; convention pinned by in-flight repair-queue PR #2073's row 1304 which uses the identical line shift on the same guard).

Inventory-table coverage summary at wave close.

Inventory sub-track Coverage state at wave close
Minimized Reproducer Corpus zip block 11 of 12 wave PRs landed here; sibling rows 1429 / 1447 still pending in repair-queue PRs #2083 / #2092.
Local guard inventory for Handle.read / Stream.read Rows 1305+1306 refreshed in-wave (PR #2080); rows 1304 / 1307 / 1311 still pending in repair-queue PRs #2073 / #2081 / #2060.
Decompression Limit Inventory Public extraction APIs Unchanged from prior wave: Tar extract + extractTarGz cluster (rows 1192-1195) still outstanding via repair-queue PR #2035.
Other sub-tracks Untouched this wave (CD/LH consistency rows, in-prose anchors, Tar fixture rows already terminal at post-#1989 wave; pre-existing UStar interior-NUL via PR #1959).

Wave-character callout. This is the second consecutive maintenance-only wave of the post-#1928 closure arc, and the first wave to apply the post-issue-creation tail deferral pattern as a routine cadence (the post-#1989 wave applied it as the inaugural wave under the codified skill landed by PR #1997). All 12 PRs are inventory re-anchor PRs — no fixtures, no Track E feature, no Zip/ source edits, no skill landing, no Recent-wins bullet, no paired-review entry. The wave's narrower shift-class character (3 sub-classes vs. the prior wave's 8 sub-tracks) reflects natural saturation: the prior wave cleared the bulk of the +111 Archive late-section / +97 Tar parser growth shifts, leaving the post-#2074 wave to mop up the +43–+45 fine-grained tail and the +49 ZIP64-extra arms. With 27 inventory-link warnings remaining at wave close (down from the prior wave's 40 and the post-#1971 wave's 104), the maintenance saturation arc is nearing termination.

Repair queue snapshot at wave close. 22 PRs in conflict / failing CI per coordination list-pr-repair (21 merge-conflicts + 1 failed CI on PR #1876). The queue grew by 3 net entrants since the prior wave close (PRs #2092 / #2083 / #2081 freshly conflicted on SECURITY_INVENTORY.md, sibling-row inventory PRs that landed in-wave on the same clusters; PR #2073 was already in the queue at the prior wave close). The 5 oldest entries (#1725 / #1743 / #1755 / #1764 / #1771) trace back to the Track E CD-parse cascade that predates the post-#1928 closure arc; the 6 most recent entries (#2092 / #2083 / #2081 / #2073 / #2060 / #2035) are all sibling-row inventory PRs that conflicted with in-wave landings on the same SECURITY_INVENTORY.md clusters. Pattern: each wave's inventory landings reliably generate a fixed number of repair- queue entrants from sibling-row PRs that were in flight at wave open — the inventory conflict tax.

Post-issue-creation tail (out of wave scope). Per the post-issue-creation tail deferral pattern in .claude/skills/summarize-flow/SKILL.md, any PRs that land between issue #2108's 16:12:15Z creation and this wave PR's submission are deferred to the next wave block. At the moment this wave PR is submitted, PR #2109 has merged at 16:17:17Z (the eocd-zip64-override-nosentinel.zip row-1453 re-anchor sweep, planner-queued issue #2102 from the same planning cycle that created issue #2108). PR #2109 is out of wave scope per the issue-body-as-source-of-truth invariant — the issue's deliverables enumeration fixes the wave's PR list at 12 PRs. The unclaimed planner queue at issue #2108's creation listed three additional inventory issues (#2103 row 1426 cd-empty-entry-crc-nonzero.zip, #2106 row 1441 cd-path-unsafe.zip, #2107 Tar.lean row 1407 pax-path-nul-in-value.tar); PR #2109 plus those three issues' PRs (when landed) will appear in the next wave block. This wave's scope is frozen at the 12 PRs enumerated above.

Quality metrics: 0 sorries across Zip/ (unchanged — none of these PRs touched proofs); 0 runtime ]! across Zip/Native/ and Zip/*.lean (unchanged — no source edits this wave). Fixtures in testdata/zip/malformed/ unchanged at 47; fixtures in testdata/tar/malformed/ unchanged at 24. Spec-line counts unchanged (no Zip/Spec/ PRs). Type mix across the 12 PRs: inventory 12 / non-inventory 0 — ratio 100% inventory (vs. ~94% in the prior wave; reaching pure saturation). At wave close: repair queue at 22 PRs (up from 19 at prior wave close); feature queue at 4 unclaimed inventory issues (#2102 / #2103 / #2106 / #2107 — three Archive corpus rows + one Tar.lean PAX row); review queue empty (no closure events). lake build clean (191 jobs); lake exe test all green; bash scripts/check-inventory-links.sh exits 0 with 27 warnings (down from the prior wave's 40 as the 12 re-anchor PRs cleared most remaining stale citations). Toolchain v4.29.1.

26-PR batch (Apr 25): post-#2110 wave — third consecutive maintenance-only wave of the post-#1928 closure arc; pure inventory-anchor refresh wave (no terminal closures, no Track E feature work, no source code) covering five distinct shift sub-classes (CD-parse-guard +43-67 / Archive late-section +25-225 / PAX-UStar parser-growth +16-23 / Tar parser-growth +95-117 / writer-side ±4-6) plus a multi-anchor narrative-paragraph cluster (3 PRs); longest pure-doc inventory wave to date (summarize #2168):

Twenty-six PRs merged in the ~2h 47min window between PR #2111 (16:28:07Z 2026-04-25) and PR #2164 (19:15:16Z 2026-04-25), opening immediately after PR #2110 (the prior summarize wave PR) merged at 16:25:20Z. The wave is structurally a breadth expansion of the prior post-#2074 wave: where the 12-PR post-#2074 wave concentrated on three narrow shift classes (CD-parse-guard +43-45, Archive late-section +111, ZIP64-extra +49), the post-#2110 wave reaches across five distinct shift classes plus a multi-anchor narrative-paragraph cluster of three PRs that consolidate sibling-deferral chains within a single paragraph. The 26 PRs split as: 6 CD-parse-guard +43-67 PRs, 7 Archive late-section +25-225 PRs (including a notable +225 outlier on the row-478 stored-method size invariant narrative paragraph), 6 PAX/UStar parser-growth +1-23 PRs, 2 Tar parser-growth +95-117 PRs, 2 writer-side ±4-6 PRs, and 3 multi-anchor narrative-paragraph sweeps — including PR #2154's first 5-anchor in-row sweep in the inventory family (the cd-path-unsafe narrative paragraph). There is no terminal closure in this wave; the post-#1928 Tar interior-NUL closure arc remains structurally complete (terminal at PR #1979 in the post-#1971 wave), and the planner-side activity continues to be anchor maintenance. Source code (Zip/, c/, testdata/) was not touched by any of the 26 PRs; grep -rcF sorry Zip/ stayed at 0 throughout.

Inventory: CD-parse-guard +43-67 sub-class (6 PRs) — the Zip/Archive.lean CD-parse late-arm guards on the +43, +44, +45, and +67 fine-grained tail of the CD-parse-guard wave.

  • #2114 (16:40Z) — row 1441: cd-path-unsafe.zip row-table primary cell (:631 → :676, +45).
  • #2120 (16:55Z) — line 652: cd-patched-data-flag.zip narrative-paragraph follow-up to PR #2104's row-table update (:682 → :725, +43).
  • #2121 (17:00Z) — line 677: cd-nul-in-name.zip narrative-paragraph follow-up to PR #2105's row-table update (:589 → :632, +43).
  • #2137 (17:47Z) — line 486: CD-entry compression-method allowlist narrative paragraph (:668 → :713, +45; prose-paragraph follow-up to PR #2053's row-1424 row-table update).
  • #2146 (18:14Z) — line 478: cd-empty-name.zip narrative-paragraph dual-anchor (:535 → :578, +43; :570 → :614, +44).
  • #2161 (19:03Z) — line 414: ZIP64-extra dataSize exactness narrative (:428 → :495, +67 — fine-grained tail of the CD-parse-guard wave on the parseZip64Extra arm).

Inventory: Archive late-section +25-225 sub-class (7 PRs) — the Zip/Archive.lean late-section guards that took +25, +111, or +225 shifts in the post-#2110 cumulative wave.

  • #2111 (16:28Z) — row 1426: cd-empty-entry-crc-nonzero.zip row-table trailing cite 'CRC32 mismatch' throw (:1088 → :1199, +111).
  • #2122 (17:02Z) — line 491: CD-parse method allowlist narrative 'unsupported method' throw (:1083 → :1194, +111).
  • #2129 (17:24Z) — row 1441: cd-path-unsafe.zip row-table trailing dual-anchor on the extract-time Binary.isPathSafe call sites (:1133 → :1244; :1107 → :1248).
  • #2131 (17:30Z) — line 478: stored-method size invariant narrative 'size mismatch' throw (:976 → :1201, +225 — broadest single-anchor shift in the wave).
  • #2132 (17:33Z) — line 529: localOffset+30 ≤ cdOffset narrative 'bad local header signature' throw (:970 → :1081, +111).
  • #2133 (17:35Z) — line 814: empty-entry CRC invariant narrative 'CRC32 mismatch' throw (:1088 → :1199, +111).
  • #2162 (19:07Z) — line 347: ZIP64 EOCD64 overlap-locator narrative 'ZIP64 EOCD64 record overlaps locator' throw (:306 → :331, +25 — small tail of the Archive late-section shift on a paragraph that fell outside the +111 cluster).

Inventory: PAX/UStar parser-growth +1-23 sub-class (6 PRs) — the Zip/Tar.lean PAX and UStar parser guards that took small shifts (+1, +16, +20, +22, +23) from the post-#1899 / post-#1944 PAX-NUL-guard insertion wave.

  • #2115 (16:46Z) — row 1407: pax-path-nul-in-value.tar parsePaxRecords keyBytes NUL guard (:122 → :145, +23).
  • #2130 (17:28Z) — row 1402: pax-inconsistent-length.tar dual-anchor splitting collapsed citations across distinct guard sites (:118 → :121 eqPos scan loop; :118 → :126 silent-skip guard).
  • #2138 (17:50Z) — line 992: PAX duplicate-key narrative forEntries typePaxExtended rethrow (:669 → :691, +22).
  • #2145 (18:11Z) — row 1400: pax-duplicate-path.tar dual-anchor (:147 → :148 parsePaxRecords duplicate-key guard; :669 → :691 forEntries rethrow, +22).
  • #2155 (18:39Z) — row 1411: ustar-linkname-nul-in-name.tar dual-anchor (:517 → :533 linkname throw; :515 → :531 name-arm guard reference, +16).
  • #2156 (18:42Z) — row 1413: ustar-prefix-nul-in-name.tar triple-anchor (:519 → :535 prefix throw; :515 → :531 name-arm guard; :522 → :542 pfx ++ "/" ++ name combination, +16/+16/+20).

Inventory: Tar parser-growth +95-117 sub-class (2 PRs) — the Zip/Tar.lean Tar.extract-side guards that took +95 / +97 / +117 shifts on the Binary.isPathSafe rejection sites and the per-chunk skipEntryData cap.

  • #2123 (17:05Z) — rows 1415 / 1419 / 1420: testdata/tar/security/{backslash-slip,tar-absolute,tar-slip}.tar three-row same-anchor sweep (:639 → :756, +117).
  • #2139 (17:54Z) — row 1310: skipEntryData row trailing dual-anchor (:539 → :634, +95 per-chunk cap; :661 → :758, +97 Tar.extract maxEntrySize check).

Inventory: writer-side ±4-6 sub-class (2 PRs) — the small writer-side shift class on Zip/Archive.lean writer-internal hard-coded constants and Checksum.crc32 call sites.

  • #2153 (18:33Z) — line 853: cd-deflate-zero-compsize.zip writer-side Checksum.crc32 0 fileData call (:195 → :189, -6).
  • #2164 (19:15Z) — line 328: zip64-eocd64-versionneeded writer-side hard-coded 45 for versionNeededToExtract inside writeEndRecords (:150 → :154, +4).

Inventory: Multi-anchor narrative-paragraph sweeps (3 PRs) — in-row consolidation of sibling-deferral chains that mix multiple shift classes within a single paragraph.

  • #2147 (18:17Z) — line 794: cd-empty-entry-crc-nonzero.zip triple-anchor (:794 → :820 parse-time throw, +26 CD-parse- guard; :195 → :189 ×2 writer-side Checksum.crc32 call, -6 writer-side; mixes CD-parse with writer-side classes).
  • #2154 (18:36Z) — line 706-740: cd-path-unsafe.zip 5-anchor sweep (:631 → :676, :651 → :696, :1133 → :1244, :1107 → :1248, :1131 → :1242; first 5-anchor in-row consolidation in the inventory family, mixing CD-parse-guard with Archive late-section).
  • #2157 (18:44Z) — line 419-446: duplicate ZIP64 (headerId 0x0001) extra-block rejection narrative — triple-anchor with non-uniform shifts (:406 → :449 +43 helper def; :693 → :742 +49 CD-side caller; :983 → :1123 +140 LH-side caller; mixes three shift classes in one PR).

Inventory-table coverage summary at wave close.

Inventory sub-track Coverage state at wave close
Recommended policy narrative paragraphs 14 of 26 wave PRs landed here; sibling narrative paragraphs at lines 296 / 328 (writer-side closed in-wave by #2164) / 347 (closed in-wave by #2162) / 414 (closed in-wave by #2161) form the cluster, with line 296 (versionMadeBy upper-bound) outstanding via planner-queued #2163.
Minimized Reproducer Corpus zip block (rows 1426-1465) 4 of 26 wave PRs landed primary cells (#2111, #2114); sibling rows 1429 / 1447 still pending in repair-queue PRs #2083 / #2092 / #2144.
Minimized Reproducer Corpus tar block (rows 1393-1420) 6 of 26 wave PRs landed here (#2115, #2123, #2130, #2145, #2155, #2156); UStar interior-NUL row 1412 outstanding via repair-queue PR #2144 (terminal 6-anchor sweep).
Local guard inventory for Handle.read / Stream.read Trailing-anchor row 1310 closed by #2139; rows 1304 / 1307 / 1311 still pending in repair-queue PRs #2073 / #2081 / #2060.
Standalone audit sections (lines 1126 / 1140 / 1156) All three queued at wave close as planner-issues #2165 / #2166 / #2167; not landed in-wave.
Other sub-tracks Untouched this wave (Decompression Limit Inventory cluster rows 1192-1195 still in repair-queue PR #2035; intro-paragraph creator-side h.read row 1285 queued as #2160).

Wave-character callout. This is the third consecutive maintenance-only wave of the post-#1928 closure arc, and the longest pure-doc inventory-anchor refresh wave to date (26 PRs vs. the prior post-#2074 wave's 12, the broad-stroke post-#1989 wave's 35, the post-#1971 wave's 11, and the post-#1964 wave's 15). All 26 PRs are inventory re-anchor PRs — no fixtures, no Track E feature, no Zip/ source edits, no skill landing, no Recent-wins bullet, no paired-review entry. The wave's broader shift-class character (5 sub-classes plus the multi-anchor cluster vs. the prior wave's 3 sub-classes) reflects the natural saturation pattern: the prior wave cleared the bulk of the +43-45 / +111 / +49 clusters on the row-table side, and the post-#2110 wave reaches across to the narrative-paragraph side (16 of 26 PRs edit Recommended policy narrative paragraphs vs. row-table primary cells), with PR #2154's first 5-anchor in-row sweep marking a deliberate consolidation of sibling-deferral chains within a single paragraph. With 18 inventory-link warnings remaining at wave close (down from the prior wave's 27 and the post-#1971 wave's 104), the maintenance saturation arc is in its terminal phase — the residual 18 warnings trace exclusively to the in-flight repair-queue PRs and a handful of trailing in-source line-content heuristic mismatches.

Repair queue snapshot at wave close. 23 PRs in conflict / failing CI per coordination list-pr-repair (22 merge-conflicts + 1 failed CI on PR #1876). The queue grew by 1 net entrant since the prior wave close (PR #2144, the row 1412 6-anchor terminal sweep that conflicted on the same SECURITY_INVENTORY.md cluster as the in-wave row 1411 / 1413 sweeps PRs #2155 / #2156 — the same inventory conflict tax the prior wave flagged). The 5 oldest entries (#1725 / #1743 / #1755 / #1764 / #1771) trace back to the Track E CD-parse cascade that predates the post-#1928 closure arc; the recent entries (#2092 / #2083 / #2081 / #2073 / #2060 / #2035 / #2144) are sibling-row inventory PRs that conflicted with in-wave landings on the same SECURITY_INVENTORY.md clusters.

Post-issue-creation tail (out of wave scope). Per the post-issue-creation tail deferral pattern in .claude/skills/summarize-flow/SKILL.md, any PRs that land between issue #2168's 19:22:20Z creation and this wave PR's submission are deferred to the next wave block. At the moment issue #2168 was claimed (19:25Z), no PRs had merged after the wave's terminal PR #2164 (19:15:16Z) — the post-issue-creation tail is empty. The wave's scope is frozen at the 26 PRs the issue body enumerates with no deferrals required. The unclaimed planner queue at issue #2168's creation listed six additional inventory issues for the next wave seed (#2160 row 1285 creator-side h.read intro-paragraph; #2163 line 296 ZIP64 EOCD64 versionMadeBy narrative; #2165 / #2166 / #2167 the three standalone audit-section refreshes at lines 1126 / 1140 / 1156; sibling narrative-paragraph re-anchor at line 296 deferred from #2161 / #2164's wave PRs); their PRs (when landed) will appear in the next wave block.

Quality metrics: 0 sorries across Zip/ (unchanged — none of these PRs touched proofs); 0 runtime ]! across Zip/Native/ and Zip/*.lean (unchanged — no source edits this wave). Fixtures in testdata/zip/malformed/ unchanged at 47; fixtures in testdata/tar/malformed/ unchanged at 24. Spec-line counts unchanged (no Zip/Spec/ PRs). Type mix across the 26 PRs: inventory 26 / non-inventory 0 — ratio 100% inventory (third consecutive 100% wave; prior wave was also 100%, post-#1989 wave was ~94%). At wave close: repair queue at 23 PRs (up from 22 at prior wave close — net +1 from PR #2144); feature queue at 6 unclaimed inventory issues (#2160 / #2163 / #2165 / #2166 / #2167 plus the planner-queued sibling line-296 deferral); review queue empty (no closure events). bash scripts/check-inventory-links.sh exits 0 with 18 warnings (down from the prior wave's 27 as the 26 re-anchor PRs cleared most remaining stale citations on the narrative-paragraph and row-table sides). Toolchain v4.29.1.

12-PR batch (Apr 25): post-#2168 wave — fourth consecutive maintenance-only wave of the post-#1928 closure arc; pure inventory-anchor refresh / heuristic-suppression wave (no terminal closures, no Track E feature work, no source / test / fixture changes) covering five distinct inventory-section sub-classes (standalone audit sections / Recommended-policy narrative paragraphs / Local guard intro + table parenthetical / Minimized Reproducer Corpus rows / drift-detector heuristic suppressions) (summarize #2191):

Twelve PRs merged in the ~1h 33min window between PR #2170 (19:35:10Z 2026-04-25) and PR #2190 (21:08:32Z 2026-04-25), opening immediately after PR #2168 (the prior summarize wave PR) merged at 19:32:20Z. The wave is structurally a pivot off the shift-class axis that organized the prior three pure-doc waves (post-#1989 / post-#2074 / post-#2110): where those waves split their PRs by line-shift magnitude (CD-parse-guard +43-67 / Archive late-section +25-225 / PAX-UStar parser-growth +16-23 / Tar parser-growth +95-117 / writer-side ±4-6), the post-#2168 wave instead splits its 12 PRs across five inventory-section sub-classes — the structural sections of SECURITY_INVENTORY.md that the anchors live in, rather than the line-shift magnitudes on the source side. The 12 PRs split as: 3 standalone-audit-section refreshes (lines 1126 / 1140 / 1156), 3 Recommended-policy narrative-paragraph refreshes (lines 296 / 515 / 630), 2 Local guard sub-section refreshes (intro paragraph at line 1285 + row-1302 parenthetical), 2 Minimized Reproducer Corpus refreshes (rows 1411 / 1413 — UStar interior-NUL fixture rows), and 2 drift-detector heuristic suppressions (rows 1309 / 1310 — readEntryData and skipEntryData per-chunk-cap body anchors). There is no terminal closure in this wave; the post-#1928 Tar interior-NUL closure arc remains structurally complete, and the planner-side activity continues to be anchor maintenance. Source code (Zip/, c/, testdata/) was not touched by any of the 12 PRs; grep -rcF sorry Zip/ stayed at 0 throughout.

Inventory: standalone-audit-section sub-class (3 PRs) — the SECURITY_INVENTORY.md standalone audit sections at lines 1126 / 1140 / 1156 (sections 1 / 2 / 3 of the audit checklist), pinning the def-line anchor precedent for those sections.

  • #2172 (19:39Z) — line 1126: section 1 "ZIP metadata to Handle.read" (Zip/Archive.lean:491 → :980, +489 from cumulative parser-growth + Archive late-section wave); first standalone-audit-section anchor refresh, sets the def-line precedent.
  • #2174 (19:53Z) — line 1140: section 2 "Tar UTF-8 partial functions" (Zip/Tar.lean:246 → :317, +71 from post-#1899/#1944/#1957/#1979 Tar parser growth wave plus hasInteriorNul helper insertion); splitPath def line.
  • #2177 (20:19Z) — line 1156: section 3 "Unlimited decompression knobs" (Zip/Archive.lean:783 → :1233, +450 from cumulative Archive late-section growth wave); Archive.extract def line; closes the audit-section trio (sections 1 / 2 / 3 all on the def-line anchor precedent).

Inventory: Recommended-policy narrative-paragraph sub-class (3 PRs) — narrative-paragraph refreshes that restore fixture-cite ↔ narrative-cite agreement on three distinct guard sites.

  • #2171 (19:37Z) — line 296: ZIP64 EOCD64 versionMadeBy upper-bound throw (Zip/Archive.lean:337 → :363, +26 from later-landing throw inside findEndOfCentralDir); corrected replacement for issue #2152 (which misattributed to the adjacent record-size paragraph).
  • #2181 (20:32Z) — line 515: cd-entry-localoffset-past-cdstart per-entry archive-layout invariant (Zip/Archive.lean:728 → :771, +43 from CD-parse-guard insertion wave); restores fixture-cite ↔ narrative-cite agreement for PR #1813.
  • #2182 (20:35Z) — line 630: cd-entry-internal-attrs-reserved CD-entry internalFileAttributes reserved-bits check (Zip/Archive.lean:567 → :612, +45 from CD-parse-guard insertion wave); per-row split sibling of issue #2077 (corpus row 1429 cite, repair-queue PR #2083) — narrative-paragraph cite at line 630 vs. corpus row at line 1429 are structurally isolated.

Inventory: Local guard sub-section sub-class (2 PRs) — intro paragraph creator-side h.read exclusion + row-1302 parenthetical bare-text re-anchor.

  • #2170 (19:35Z) — line 1285: Local guard intro paragraph creator-side h.read exclusion (Zip/Tar.lean:466 → :599, +133 from post-#1899/#1944/#1957/#1979 Tar parser growth wave plus hasInteriorNul helper insertion).
  • #2178 (20:21Z) — row 1302 parenthetical: bare-text re-anchor "(inner s.read at line 695)""(line 1000)" (+305 from helper-block reordering below parseCentralDir); structurally isolated from sibling Local guard table rows in repair-queue PRs #2073 / #2081 / #2060 / #1959.

Inventory: Minimized Reproducer Corpus sub-class (2 PRs) — the UStar interior-NUL fixture rows 1411 and 1413 (sibling-deferral follow-ups to the in-#2110-wave PRs #2155 / #2156).

  • #2186 (20:54Z) — row 1411: testdata/tar/malformed/ustar-linkname-nul-in-name.tar mixed-form 2-anchor sweep — markdown-link prefix-arm parenthetical (Zip/Tar.lean:519 → :535) + bare-text "name slot at line 515""line 531" (+16 from hasInteriorNul uname/gname-arm insertion wave); sibling-deferral pattern from PR #2155 (issue #1955).
  • #2187 (20:56Z) — row 1413: testdata/tar/malformed/ustar-prefix-nul-in-name.tar bare-text 3-anchor sweep — "lines 515 / 517 / 519""lines 531 / 533 / 535" inside per-slot family-closure clause (linker-undetected drift, +16 shift); sibling-deferral pattern from PR #2156 (which explicitly took the markdown-link form only).

Inventory: drift-detector heuristic-suppression sub-class (2 PRs) — first body-anchor table-row variant of the drift-detector pattern previously seen on SECURITY_INVENTORY.md:1176-1178.

  • #2188 (20:59Z) — row 1309: readEntryData per-chunk-cap body anchor (Zip/Tar.lean:256 / :267 inside while loop, def at :249 outside the ±2 window); 1-line single-anchor doc-only sweep matching SECURITY_INVENTORY.md:1176-1178 precedent — first body-anchor table-row variant of the drift-detector pattern.
  • #2190 (21:08Z) — row 1310: skipEntryData per-chunk-cap body anchor (Zip/Tar.lean:634 inside while loop, def at :630 outside the ±2 window); immediate sibling of #2188 — same root-cause class.

Wave-character callout. This is the fourth consecutive maintenance-only wave of the post-#1928 closure arc (post-#1989 35 PRs → post-#2074 12 PRs → post-#2110 26 PRs → post-#2168 12 PRs) and the shortest of the four (joint with post-#2074 at 12 PRs each). All 12 PRs are inventory re-anchor or heuristic-suppression PRs — no fixtures, no Track E feature, no Zip/ source edits, no skill landing, no Recent-wins bullet, no paired-review entry. The wave's structural character (5 inventory-section sub-classes vs. the prior wave's 5 line-shift sub-classes) reflects a deliberate axis pivot: prior waves cleared the bulk of the +43-67 / +111 / +49 / +95-117 shift clusters along the line-shift axis on a smaller number of inventory sections, and the post-#2168 wave instead spans five different structural sections of SECURITY_INVENTORY.md (audit sections / narrative paragraphs / Local guard / corpus rows / drift-detector comments) on a smaller number of line-shift magnitudes. Group 1 (standalone-audit) closes the audit-section trio at lines 1126 / 1140 / 1156, pinning the def-line anchor precedent for those sections (planner-queued issues #2165 / #2166 / #2167 from the prior wave). Group 5 (drift-detector) opens the body-anchor table-row sub-pattern of the drift-detector heuristic-suppression class — the prior applications (SECURITY_INVENTORY.md:1176-1178) were narrative paragraphs, but the row 1309 / 1310 sweeps extend the pattern to table rows where the cited body-line anchor is inside a while loop with the def line outside the ±2 quoted-substring window. With 14 inventory-link warnings remaining at wave close (down from the prior wave's 18), the maintenance saturation arc is in its terminal phase — the residual warnings trace exclusively to in-flight repair-queue PRs and a handful of trailing in-source line-content heuristic mismatches.

Repair queue snapshot at wave close. 19 PRs in conflict + 1 failing CI per coordination list-pr-repair at issue #2191 creation time (#2144 / #2092 / #2083 / #2081 / #2073 / #2060 / #2035 / #2013 / #2006 / #1959 / #1909 / #1825 / #1820 / #1814 / #1809 / #1802 / #1777 / #1771 / #1764 + failed-CI #1876). The queue is structurally unchanged from prior wave close — no net new entrants this wave because the 12 PRs landed across 5 different inventory sections without colliding with the existing repair-queue clusters (the row 1411 / 1413 sweeps in group 4 are sibling-deferral follow-ups to in-#2110-wave PRs, not sibling-row repair PRs). The 5 oldest entries (#1725 / #1743 / #1755 / #1764 / #1771) trace back to the Track E CD-parse cascade that predates the post-#1928 closure arc.

Post-issue-creation tail (out of wave scope). Per the post-issue-creation tail deferral pattern in .claude/skills/summarize-flow/SKILL.md, any PRs that land between issue #2191's 21:24:00Z creation and this wave PR's submission are deferred to the next wave block. At the moment this wave PR is submitted, PRs #2192 (drift-detector row 1402 suppression) and #2197 (drift-detector row 1066 suppression) have merged after the wave's terminal PR #2190 (21:08:32Z) — both at PR-create timestamps strictly after issue creation (#2192 created 21:27:58Z, merged 21:29:05Z; #2197 created 21:45:05Z, merged 21:46:16Z). These two are the queued continuation of group 5 (drift-detector heuristic suppressions, siblings of #2188 / #2190 — issues #2185 / #2189 mentioned in the issue body as the queued continuation) and are out of wave scope per the issue-body-as-source-of-truth invariant; they will appear in the next wave block. Four additional inventory issues were also created post-issue (#2193 / #2194 / #2195 / #2196 — FFI Decompression Limit Inventory cluster rows 1177 / 1179 / 1180 / 1181 covering Gzip.decompress / decompressStream / decompressFile + RawDeflate.decompressStream), and remain unclaimed at wave-PR submission; their PRs (when landed) will likewise appear in the next wave block.

Quality metrics: 0 sorries across Zip/ (unchanged — none of these PRs touched proofs); 0 runtime ]! across Zip/Native/ and Zip/*.lean (unchanged — no source edits this wave). Fixtures in testdata/zip/malformed/ unchanged at 47; fixtures in testdata/tar/malformed/ unchanged at 24. Spec-line counts unchanged (no Zip/Spec/ PRs). Type mix across the 12 PRs: inventory 12 / non-inventory 0 — ratio 100% inventory (fourth consecutive 100% wave; prior wave was also 100%, post-#1989 wave was ~94%). At wave close: repair queue at 20 PRs (19 merge-conflicts + 1 failed-CI on PR #1876, structurally unchanged from prior wave close at 23 PRs net of any intra-wave queue shifts on the prior wave's seed set); feature queue at 2 unclaimed inventory issues at issue-#2191 creation (#2185 row 1402 drift-detector / #2189 row 1066 drift-detector — both queued separately as the post-#2188 / #2190 continuation; both subsequently claimed and merged in the post-issue-creation tail as PRs #2192 / #2197); review queue empty (no closure events). bash scripts/check-inventory-links.sh exits 0 with 14 warnings (down from the prior wave's 18 as the 12 re-anchor / heuristic-suppression PRs cleared further stale citations across all 5 inventory-section classes — the 4 reduction is attributable to the 2 drift-detector suppressions plus the 2 narrative/audit-section refreshes that fell within the pre-existing warning set). Toolchain v4.29.1.

Infrastructure

  • Multi-agent coordination via pod with worktree-per-session isolation
  • GitHub-based coordination (agent-plan issues, auto-merge PRs)
  • Session dispatch: planners create issues, workers claim and execute
  • ~660 sessions (Feb 19 – Apr 22)
  • ~653 merged PRs (approximate; authoritative count via gh pr list)
  • 100% module docstring coverage across all source files
  • Full linter compliance (all warnings eliminated)
  • Repository split into lean-zip + lean-zip-common + lean-zstd (#1487, 2026-03-27)
  • Agent skills: lean-wf-recursion (#349), proof-review-checklist (#386, updated #925, #1325), bare-simp-resistant pattern catalog (#386), lean-zstd-patterns (#491), agent-pr-recovery (#546, updated #597), lean-zstd-spec-pattern (#623, updated #711, #840, #925), lean-monad-proofs (updated #711, #840), lean-content-preservation (#891), lean-parsing-completeness (#1127, updated #1171), proven-bounds (#1420)