feat(prune): R1 prune-reconcile pure core — (owned ∩ present) − referenced
Add REAPER-free/filesystem-free prune orphan-set core + additive BankBook::referencedPaths() union query, with full unit coverage.
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <climits>
|
||||
#include <unordered_set>
|
||||
|
||||
// bank_book implementation.
|
||||
//
|
||||
@@ -321,6 +322,22 @@ bool BankBook::hashReferencedElsewhere(const std::string& hash,
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<std::string> BankBook::referencedPaths() const {
|
||||
// Union across the whole book (pool first, then named banks in ordinal order —
|
||||
// banks_ is kept ordinal-sorted). De-duplicate by exact string so a file a copy
|
||||
// put in two banks appears once. Skip empty paths (they reference no file).
|
||||
std::vector<std::string> paths;
|
||||
std::unordered_set<std::string> seen;
|
||||
for (const auto& b : banks_) {
|
||||
for (const auto& s : b.index.all()) {
|
||||
if (s.relativePath.empty()) continue;
|
||||
if (seen.insert(s.relativePath).second)
|
||||
paths.push_back(s.relativePath);
|
||||
}
|
||||
}
|
||||
return paths;
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// JSON — writer
|
||||
// ===========================================================================
|
||||
|
||||
@@ -220,6 +220,17 @@ public:
|
||||
bool hashReferencedElsewhere(const std::string& hash,
|
||||
const std::string& exceptBankId) const;
|
||||
|
||||
// Every project-relative file path referenced by ANY bank in the book, pool
|
||||
// included — the union across the whole book (Phase R, prune). This is the
|
||||
// safety-critical referenced-set the prune core subtracts: a file referenced by
|
||||
// any bank (INCLUDING via a copy into a second bank) appears here, so prune never
|
||||
// reclaims it. Paths are returned VERBATIM (Sample.relativePath, exact strings —
|
||||
// no normalization), first-seen order across banks in ordinal order then sample
|
||||
// insertion order, and DE-DUPLICATED (one file referenced by N banks appears
|
||||
// once). An empty relativePath is skipped (it references no file). Additive
|
||||
// read-only query; adds no mutation and no coupling to Phase R.
|
||||
std::vector<std::string> referencedPaths() const;
|
||||
|
||||
// -- Query ---------------------------------------------------------------
|
||||
|
||||
// The bank with `id`, or nullptr. Pointer invalidated by any mutating call.
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
#include "prune_reconcile.h"
|
||||
|
||||
#include <unordered_set>
|
||||
|
||||
// prune_reconcile implementation — the one set-algebra computation, kept trivially
|
||||
// auditable: build the referenced and owned lookup sets, then walk `present` once,
|
||||
// keeping a path iff it is owned AND not referenced. Walking `present` (not owned)
|
||||
// gives the ∩-present clause for free and yields output in folder-enumeration order.
|
||||
|
||||
namespace reasampler {
|
||||
|
||||
std::vector<std::string> pruneOrphans(const std::vector<std::string>& present,
|
||||
const std::vector<std::string>& referenced,
|
||||
const std::vector<std::string>& owned) {
|
||||
// Exact-string membership — the model's canonical relative-path comparison
|
||||
// (Sample.relativePath / OwnedFileManifest::contains). std::string hashes/compares
|
||||
// byte-for-byte, so no normalization creeps in.
|
||||
const std::unordered_set<std::string> referencedSet(referenced.begin(),
|
||||
referenced.end());
|
||||
const std::unordered_set<std::string> ownedSet(owned.begin(), owned.end());
|
||||
|
||||
std::vector<std::string> orphans;
|
||||
std::unordered_set<std::string> emitted; // de-dup repeated spellings in `present`
|
||||
|
||||
for (const std::string& path : present) {
|
||||
// (owned ∩ present) − referenced: present is the walk; owned and !referenced
|
||||
// are the two membership tests; emitted guards against a duplicate `present`.
|
||||
if (ownedSet.count(path) == 0) continue; // not our leaving — skip
|
||||
if (referencedSet.count(path) != 0) continue; // some bank references it
|
||||
if (!emitted.insert(path).second) continue; // already emitted
|
||||
orphans.push_back(path);
|
||||
}
|
||||
return orphans;
|
||||
}
|
||||
|
||||
} // namespace reasampler
|
||||
@@ -0,0 +1,63 @@
|
||||
#pragma once
|
||||
// prune_reconcile — the pure core of Phase R (Reclaim), Wave 1. The safety-critical
|
||||
// "which files are orphans" decision, computed with NO filesystem I/O and NO REAPER
|
||||
// types. The mirror of view_mode_model's reconcile(liveGuids), one level DOWN: it
|
||||
// reconciles FILES ON DISK against REFERENCED FILES (the union across every bank),
|
||||
// where reconcile reconciled membership entries against live tracks.
|
||||
//
|
||||
// PURE MODULE (CLAUDE.md §load-bearing split): NO REAPER types, NO SWELL, NO
|
||||
// vendor/ includes, NO filesystem calls. Standard library only. Unit-tested outside
|
||||
// the DAW — this is the safety-critical part (it decides which bytes get deleted in
|
||||
// R2/R3), so it is hard-tested here before any I/O exists.
|
||||
//
|
||||
// -- The one computation ------------------------------------------------------
|
||||
//
|
||||
// orphans = (owned ∩ present) − referenced
|
||||
//
|
||||
// * present — files enumerated in the resolved current bank folder (R2 shell).
|
||||
// * referenced — every project-relative path referenced by ANY bank in the book,
|
||||
// pool included (union across the whole book — see BankBook::
|
||||
// referencedPaths). A file referenced by any bank — including via a
|
||||
// COPY into a second bank — is NEVER an orphan (the prune null test).
|
||||
// * owned — the owned-file manifest: the files the bank system itself created
|
||||
// (OwnedFileManifest). A present-but-unowned (hand-dropped) file is
|
||||
// NEVER reclaimed — prune reclaims only the system's own leavings.
|
||||
//
|
||||
// The three settled guardrails fall straight out of the set algebra:
|
||||
// * ∩ present — never proposes deleting a file that is not on disk (an owned-
|
||||
// but-absent manifest entry yields no orphan, no error).
|
||||
// * ∩ owned — never a hand-dropped file (ownership attribution, fork R-D).
|
||||
// * − referenced — never a file any bank references (union safety, prune null test).
|
||||
//
|
||||
// -- Path representation: EXACT-STRING match (safety-critical) -----------------
|
||||
//
|
||||
// Every path in the model is a project-relative string compared VERBATIM: Sample.
|
||||
// relativePath, OwnedFileManifest::contains (p == relativePath), and BankIndex all
|
||||
// use raw std::string equality — no separator normalization, no case-folding, no
|
||||
// trailing-slash trimming. This core MATCHES that convention exactly: it compares
|
||||
// the raw strings the shell supplies. Feeding a consistent spelling across the three
|
||||
// inputs is the R2 shell's contract (it enumerates the folder, unions the book, and
|
||||
// reads the manifest against the SAME resolved current folder). Diverging from exact
|
||||
// match here (e.g. case-insensitive compare) would be the unsafe direction — it could
|
||||
// let one spelling of a referenced file be treated as an orphan under another.
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace reasampler {
|
||||
|
||||
// Computes the prune orphan set: (owned ∩ present) − referenced.
|
||||
//
|
||||
// Returns the subset of `present` that is BOTH owned AND unreferenced, in the ORDER
|
||||
// they appear in `present` (deterministic output — mirror of the insertion-order
|
||||
// determinism the index / manifest keep; the R2 dry-run reports a stable file list).
|
||||
// Duplicate spellings within `present` are de-duplicated in the result (a folder
|
||||
// enumeration yields distinct names, but the core does not rely on that).
|
||||
//
|
||||
// Pure: no I/O, no REAPER, no hidden state. All three inputs are project-relative
|
||||
// path strings, compared by exact std::string equality (see header note).
|
||||
std::vector<std::string> pruneOrphans(const std::vector<std::string>& present,
|
||||
const std::vector<std::string>& referenced,
|
||||
const std::vector<std::string>& owned);
|
||||
|
||||
} // namespace reasampler
|
||||
Reference in New Issue
Block a user