fix(pS-usage): fail-safe prune protection — in-wire owner nonce + sticky union poison, protect-all on zero identified, abort on unreadable record, rsusage_ prefix

This commit is contained in:
2026-07-28 13:32:14 -04:00
parent 5886ae1456
commit a4aeb9dcc8
16 changed files with 809 additions and 297 deletions
+31 -5
View File
@@ -306,10 +306,17 @@ constexpr std::size_t kPruneListDisplayCap = 64;
// * orphans — the FULL orphan set (owned ∩ present) referenced, in enumeration
// order, untruncated. The pure core decides; this only supplies inputs.
// * sizeByRel — per-orphan-relative on-disk byte size (0 when it could not be stat'd).
// * abortedUnreadableUsage — true iff a present rsusage_* instance-usage record could
// not be read/decoded (pS-usage fail-safe): `orphans` is left EMPTY —
// the prune must halt rather than proceed with degraded protection.
// An empty orphan set is itself the delete-side guarantee (every
// consumer of this scan deletes at most `orphans ∩ ...`), the flag is
// what lets the action TELL the user instead of claiming "no orphans".
struct PruneScan {
std::string bankDirAbs;
std::vector<std::string> orphans;
std::unordered_map<std::string, std::uint64_t> sizeByRel;
bool abortedUnreadableUsage = false;
};
// Non-throwing: readActiveProject + resolveBankFile are pure/string; every filesystem
@@ -362,14 +369,24 @@ PruneScan scanPruneOrphans(const BankBook& book, const OwnedFileManifest& owned)
// referencedPaths() unions across the whole book (pool included); owned().paths() is
// the manifest set. pS-usage: the referenced set additionally unions every LIVE
// ReaSampler 9000 instance's held captures (usage_scan reads the per-instance
// usage_* records + the live FX enumeration; sample_usage decides liveness) — a
// capture any live instance holds can NEVER be an orphan, even when its bank entry
// was deleted while the instance kept its ref. liveInstanceHeldPaths is READ-ONLY,
// rsusage_* records + the live FX enumeration; sample_usage decides liveness,
// including the protect-all net when zero instances were identified) — a capture
// any live instance holds can NEVER be an orphan, even when its bank entry was
// deleted while the instance kept its ref. liveInstanceHeldPaths is READ-ONLY,
// preserving this scan's no-write contract. This shell only enumerates, resolves,
// and stats.
scan.bankDirAbs = bankDir;
const UsageScanResult usage = liveInstanceHeldPaths(proj);
if (usage.abortPrune) {
// FAIL-SAFE ABORT: a present rsusage_* record could not be read/decoded, so the
// protected set is unknowable. Compute NO orphans — every downstream consumer
// (dry-run report, confirm set, fresh-recompute delete plan) then deletes
// nothing. The flag surfaces the reason to the action's console message.
scan.abortedUnreadableUsage = true;
return scan;
}
scan.orphans = pruneOrphans(
present, mergeReferenced(book.referencedPaths(), liveInstanceHeldPaths(proj)),
present, mergeReferenced(book.referencedPaths(), usage.heldPaths),
owned.paths());
return scan;
}
@@ -380,7 +397,13 @@ PruneReport ReaSamplerSession::pruneDryRun() const {
const PruneScan scan = scanPruneOrphans(book_, owned_);
// buildPruneReport tallies count / byte-sum / display-truncation — no report logic
// re-implemented here. An empty scan (no project / no folder) yields a zero report.
return buildPruneReport(scan.orphans, scan.sizeByRel, kPruneListDisplayCap);
PruneReport report =
buildPruneReport(scan.orphans, scan.sizeByRel, kPruneListDisplayCap);
// pS-usage fail-safe: surface the unreadable-record abort so the action halts with
// an explicit message instead of reporting "no orphaned files" (the count IS zero —
// the scan computed nothing — but the user must know the prune refused to run).
report.abortedUnreadableUsage = scan.abortedUnreadableUsage;
return report;
}
std::vector<std::string> ReaSamplerSession::pruneOrphanSet() const {
@@ -466,6 +489,9 @@ PruneDeletionResult ReaSamplerSession::pruneReclaim(
// newly-appeared orphan not in `confirmed` is never swept without its own confirm.
// Because freshOrphans is itself a pure-core output, the plan can contain NO referenced
// and NO hand-dropped file — the R-C/R-D safety survives the recompute.
// pS-usage: if THIS fresh scan hits an unreadable rsusage_* record it aborts with an
// EMPTY orphan set, so the plan below intersects to empty and nothing is deleted —
// the fail-safe holds even in the confirm→delete window, with no extra branch here.
const PruneScan scan = scanPruneOrphans(book_, owned_);
if (scan.bankDirAbs.empty()) return result; // no project / no folder -> nothing