fix(capture): populate contentHash on captured samples to fix always-prompting remove

contentHash was left empty on every capture; hashReferencedElsewhere returns
false for empty hashes, so every remove looked like a last reference. Add
FNV-1a hashBytes to capture_paths, wire into both commit paths. NOTE: this
activates index dedup-by-hash in production — a bit-identical re-capture now
collapses onto the existing entry instead of adding a duplicate (spec-intended).
This commit is contained in:
2026-07-26 16:37:46 -04:00
parent 791a9c60eb
commit 5fabade90c
6 changed files with 133 additions and 5 deletions
+27 -3
View File
@@ -38,6 +38,7 @@
#include <cstdint>
#include <ctime>
#include <filesystem>
#include <fstream>
#include <string>
#include <vector>
@@ -224,6 +225,21 @@ std::string makeUniqueTag() {
return std::to_string(static_cast<long long>(now));
}
// Reads the whole file into a byte buffer. Returns an empty vector on any I/O
// failure (the caller then leaves contentHash empty — the safe, confirm-eliciting
// direction for an unreadable file).
std::vector<std::uint8_t> readFileBytes(const std::string& path) {
std::ifstream f(path, std::ios::binary | std::ios::ate);
if (!f) return {};
const std::streamoff size = f.tellg();
if (size <= 0) return {};
std::vector<std::uint8_t> bytes(static_cast<std::size_t>(size));
f.seekg(0);
f.read(reinterpret_cast<char*>(bytes.data()), size);
if (!f) return {};
return bytes;
}
} // namespace
CaptureResult OfflineRenderBackend::capture(const CaptureRequest& request) {
@@ -483,9 +499,17 @@ CaptureResult OfflineRenderBackend::capture(const CaptureRequest& request) {
s.lengthSeconds = request.endSeconds - request.startSeconds;
s.captureTempo = Master_GetTempo(); // BPM at capture time (verified ~4651)
s.tier = Tier::Scratch; // captures land in scratch by default
// contentHash left empty for M3: hashing the rendered file is a peaks/M2-
// adjacent concern wired in a later milestone. Empty hashes do NOT dedup, so
// this is safe (bank_model treats "" as non-participating).
// Content hash: FNV-1a over the rendered file bytes so hashReferencedElsewhere
// can identify copies in other banks and suppress the last-reference confirm when
// another bank still holds the same file. Best-effort: an unreadable file leaves
// contentHash empty — the safe, confirm-eliciting direction (bank_model treats
// "" as non-participating in dedup, which is the existing fallback semantics).
{
const std::vector<std::uint8_t> fileBytes = readFileBytes(expectedPath);
if (!fileBytes.empty()) {
s.contentHash = hashBytes(fileBytes.data(), fileBytes.size());
}
}
s.createdTimestamp = static_cast<std::int64_t>(std::time(nullptr));
result.status = CaptureStatus::Ok;