fix: keep a packed file's executable bit

Pinning every member's metadata made packs reproducible and took the
executable bit with it. The RetroDECK pack ships the two Voxatron engine
binaries, and extracted at 644 they cannot be run.

Git records the bit, so reading it from the source file keeps a pack the
same from any clone. Nothing else about the source's mode reaches the
archive: 2569 members ship at 644 and 942 at 755, which is what the
builder produced before the pinning.

Nothing caught this. The comparison that proved the pinning inert
checked member names, CRCs and sizes, and mode is none of those. A test
now builds a runnable payload and asserts it survives extraction.

RetroDECK rebuilds to the same bytes twice and passes its integrity
check, 2008/2008 baseline and 1551/1551 cores.
This commit is contained in:
Abdessamad Derraz committed 2026-08-23 07:58:23 +02:00
1 parent 593b277bc4
commit 8b404e500f
3 files changed
+72 -2

No files matched your search

+20
View File
@@ -155,6 +155,26 @@ with different hardware filters get separate packs. Emulator and system packs
also retain system identity, `variant_group` and requested region; same-named
regional files are never collapsed merely because their display label matches.
## Pack reproducibility
A pack is a function of its inputs. Two builds from the same collection and
the same `database.json` produce the same bytes, so a third party can rebuild
a published pack and compare it against the checksum in `SHA256SUMS.txt`.
Three things make that hold. Generated members (`README.txt`, `manifest.json`)
carry a fixed date rather than the wall clock, and the manifest's `generated`
field is read from `database.json` instead of the build time. MAME and FBNeo
romsets are rebuilt deterministically from their ROMs, so a pack does not
inherit whatever metadata the source archive happened to carry. And every
member is written with the same fixed date: `ZipFile.write` copies the source
file's mtime, which is the checkout time for a file from the collection and
the wall clock for one the build just staged in `tmp/`.
`tests/test_deterministic_zip.py` builds the same fixture twice and compares
the bytes, for both the platform and the emulator pack paths. Its fixture
holds a romset, because without one the comparison never reaches the rebuild
path and passes while the real packs still move.
## Storage tiers
| Tier | Meaning |