17b5a2a8f939
3.4.1: a steal refuses a bundle that does not end in 'end 0', so a dropped connection cannot pass for a whole repo
- time
- 2026-10-03 17:46 UTC · 1 week ago
- machine
- Junes-MacBook-Pro.local
- agent
- claude-opus-5-5
- session
- faster whole-repo steal
- parent
- c01f0cf14856
↓ koh-17b5a2a8.tar.gzatlas at this save
files changed4 files
VERSION+1 −1src/exchange/exchange_test.v+3 −2src/exchange/steal.v+7 −2v.mod+1 −1src/exchange/exchange_test.v+3 −2
@@ -1633,12 +1633,13 @@16331633 16341634 fn test_a_bundle_lands_each_entry_and_refuses_a_cut_or_foreign_one() {16351635 mut fetched := Fetched{}1636- take_bundle(mut fetched, 'objects/aa 3\nabcobjects/bb 0\n'.bytes())!1636+ take_bundle(mut fetched, 'objects/aa 3\nabcobjects/bb 0\nend 0\n'.bytes())!16371637 assert fetched.repo_objects == {16381638 'aa': 'abc'.bytes()16391639 'bb': []u8{}16401640 }1641- for cut in ['objects/aa 4\nabc', 'objects/aa 3', 'objects/aa x\nabc', 'releases/v1 1\nx'] {1641+ for cut in ['objects/aa 3\nabc', 'objects/aa 4\nabc', 'objects/aa 3', 'objects/aa x\nabc',1642+ 'releases/v1 1\nx', 'end 0\nobjects/aa 3\nabc'] {16421643 take_bundle(mut fetched, cut.bytes()) or { continue }16431644 assert false, cut16441645 }src/exchange/steal.v+7 −2
@@ -584,8 +584,9 @@584584 return fetched585585 }586586 587-// take_bundle splits §22.7's bundle — `<key> <length>\n` and then that many bytes, over and over —588-// into what one GET of each key would have returned.587+// take_bundle splits §22.7's bundle — `<key> <length>\n` and then that many bytes, over and over,588+// until `end 0` — into what one GET of each key would have returned. A connection that drops589+// between two entries still looks like a whole bundle, so only the end line proves it arrived.589590 fn take_bundle(mut fetched Fetched, body []u8) ! {590591 mut at := 0591592 for at < body.len {@@ -598,6 +599,9 @@598599 return error('the bundle ends part way through an entry')599600 }600601 header := body[at..newline].bytestr()602+ if header == 'end 0' && newline + 1 == body.len {603+ return604+ }601605 length := strconv.atoi(header.all_after(' '))!602606 start := newline + 1603607 if length < 0 || length > body.len - start {@@ -606,6 +610,7 @@606610 take(mut fetched, header.all_before(' '), body[start..start + length])!607611 at = start + length608612 }613+ return error('the bundle was cut short — try the steal again')609614 }610615 611616 // take files one fetched key, relative to the repo: an object, a save or an event.v.mod+1 −1
@@ -1,6 +1,6 @@11 Module {22 name: 'koh3'3- version: '3.4.0'3+ version: '3.4.1'44 description: 'One SQLite file per repo, one line of history.'55 license: 'MIT'66 }