feat(persist): owned-file manifest seam — capture records created files (B-cap)

Pure OwnedFileManifest (relative paths, dedup, JSON round-trip) persisted
under sibling owned_files ext-state key; both capture commit paths record;
joins the R-B undo-reload set. Phase R prune consumes it later.
This commit is contained in:
2026-07-26 14:50:18 -04:00
parent 0f27fc2e55
commit 63f35fa58d
7 changed files with 674 additions and 7 deletions
+30 -1
View File
@@ -21,6 +21,7 @@
#include "bank_book.h"
#include "bank_model.h"
#include "owned_manifest.h"
#include "tail_control.h"
#include "view_mode_model.h"
@@ -58,6 +59,17 @@ inline constexpr const char* kProjExtViewKey = "view_state";
// default — graceful, but the user's saved choice would be lost).
inline constexpr const char* kProjExtTailKey = "tail_setting";
// The ext-state key holding the owned-file manifest JSON (the set of project-relative
// files the capture path itself created — Phase B B-cap seam, consumed by Phase R
// prune to distinguish the bank system's own orphans from hand-dropped files). A
// SIBLING key alongside banks/view_state/tail_setting — NOT folded into the `banks`
// blob, so it stays decoupled from bank membership (removing an index entry is not a
// manifest removal). One namespace, four content keys. FOREVER-STABLE: changing it
// strands every already-saved project's ownership record, so Phase R prune could no
// longer tell the tool's own files apart (it would fall back to an empty manifest —
// graceful, but the attribution safety net is lost until the next capture rebuilds it).
inline constexpr const char* kProjExtOwnedKey = "owned_files";
// The ext-state key holding a GUID we mint per project to establish CONTENT-BASED
// project identity (REAPER exposes no stable per-project GUID). poll() uses it to
// tell a genuine Save-As (same GUID, new .rpp path) apart from a project switch
@@ -122,6 +134,15 @@ public:
TailSetting& tail() { return tail_; }
const TailSetting& tail() const { return tail_; }
// The owned-file manifest (Phase B B-cap): the set of project-relative files the
// capture path itself created. The capture add-path records each created file here
// (main.cpp, alongside the bank add), exactly as it adds the Sample to the active
// bank; persist serializes it under the `owned_files` key on save and replaces it on
// project load / undo-reload — peer to book_/view_/tail_. Phase R prune CONSUMES it;
// B-cap only writes and persists it (no prune logic here).
OwnedFileManifest& owned() { return owned_; }
const OwnedFileManifest& owned() const { return owned_; }
// Serialize the current book (under the `banks` key), view model, and tail setting
// to the active project's ext state (namespace "reasampler"), and clear the retired
// legacy `bank_index` key. Non-destructive beyond writing our own ext-state keys.
@@ -180,6 +201,13 @@ private:
// adjusted project), so an absent key is graceful. Peer to bank_/view_.
TailSetting tail_;
// The owned-file manifest. Default empty; loadFromProject resets it to empty (or the
// stored set) on EVERY load path (peer-symmetry with book_/view_/tail_): switching to
// a project with no stored manifest must not inherit the previous project's ownership
// record, and an undo that rolled back a capture must re-read the restored manifest so
// the in-memory set matches disk. Absent key -> empty is graceful (older project).
OwnedFileManifest owned_;
// The project identity last observed by poll(), used to detect load/Save-As.
// The GUID is the PRIMARY signal (a different stored GUID = a different project
// of record = Load, immune to pointer recycling). The pointer disambiguates the
@@ -197,7 +225,8 @@ private:
// Load the book from the given project's ext state (the `banks` key, else the
// legacy `bank_index` key migrated into the pool) and resolve bank paths against
// projectDir at read time. Replaces the in-memory book. projectDir empty -> the
// projectDir at read time. Replaces the in-memory book. Also restores view_, tail_,
// and owned_ from their sibling keys on every load path. projectDir empty -> the
// book is reset to empty (unsaved project has no resolvable banks).
void loadFromProject(void* proj, const std::string& projectDir);
};