Require a SHA-256 hash in every manifest entry (closes #224)
check / check (push) Waiting to run

Loading accepted an entry whose hashes held no SHA-256 multihash, and
check and fetch, which compute only SHA-256, then reported the intact
file as a hash mismatch. Loading now refuses such an entry, naming its
path. AddFileWithHash accepts only a SHA-256 multihash, and freshen
carries an unchanged entry's SHA-256 hash over instead of its first
hash. SHA256Multihash, new in package mfer, finds that hash for both.
docs/FORMAT.md states the rule for writers and readers. Two loading
tests whose entries held an empty hash now give each entry a SHA-256
hash; the decoding-size test's path lengths change with it.

Model: opus-5-5
This commit is contained in:
2026-10-15 18:16:16 +00:00
parent 6a3f532c8d
commit 814859c4f3
7 changed files with 199 additions and 107 deletions
+10 -7
View File
@@ -33,9 +33,9 @@ var (
// modification time where one is required to carry it forward unchanged.
errEntryMissingMtime = errors.New("manifest entry has no mtime")
// errEntryMissingHash indicates a manifest entry that carries no hash
// to carry forward.
errEntryMissingHash = errors.New("manifest entry has no hash")
// errEntryMissingHash indicates a manifest entry that carries no
// SHA-256 hash to carry forward.
errEntryMissingHash = errors.New("manifest entry has no SHA-256 hash")
)
// errSignKeyNeeded indicates a signed manifest given to freshen with no
@@ -699,21 +699,24 @@ func addFileToBuilder(
// Entries reach this path only when recordEntry classified them as
// unchanged, which requires a recorded mtime, so an absent mtime here is
// an error rather than something to paper over with the Unix epoch.
// Loading refuses an entry with no hash; one that got here anyway is an
// error too, not a file to leave out of the manifest.
// Only the entry's SHA-256 hash is carried over, wherever the entry lists
// it, since check and fetch compare with no other. Loading refuses an
// entry with no SHA-256 hash; one that got here anyway is an error too,
// not a file to leave out of the manifest.
func addExistingToBuilder(b *mfer.Builder, entry *mfer.MFFilePath) error {
mtime, ok := entryMtime(entry)
if !ok {
return fmt.Errorf("%w: %s", errEntryMissingMtime, entry.GetPath())
}
if len(entry.GetHashes()) == 0 {
hash := mfer.SHA256Multihash(entry)
if hash == nil {
return fmt.Errorf("%w: %s", errEntryMissingHash, entry.GetPath())
}
err := b.AddFileWithHash(mfer.RelFilePath(entry.GetPath()),
mfer.FileSize(entry.GetSize()), mfer.ModTime(mtime),
fs.FileMode(entry.GetMode()), entry.GetHashes()[0].GetMultiHash())
fs.FileMode(entry.GetMode()), hash)
if err != nil {
return fmt.Errorf(
"manifest entry %s: %w (regenerate the manifest with mfer generate)",