#pragma once // owned_manifest — the pure core of the owned-file manifest seam (Phase B, B-cap). // // PURE MODULE (CLAUDE.md §load-bearing split): NO REAPER types, NO SWELL, NO // vendor/ includes. Standard library only. Unit-tested outside the DAW — the same // "small pure type + JSON round-trip" pattern as wav_trim / tab_strip. // // -- What it is -------------------------------------------------------------- // // The set of files the bank system ITSELF created — every file the capture path // writes gets recorded here. Phase R prune consumes it to tell the system's own // orphans (owned ∩ present − referenced) apart from hand-dropped files. B-cap only // WRITES and PERSISTS the manifest; no prune logic lives here (fork R-D, settled // 2026-07-24: "defer the feature, design the seam"). // // -- What it is NOT ---------------------------------------------------------- // // It is NOT a mirror of the bank index. Removing or moving an index entry does NOT // remove the file's manifest record: the manifest tracks files *created*, and prune // (Phase R) reconciles manifest-vs-index later. The ONLY thing that adds to it is // the capture add-path. There is deliberately no remove verb here. // // -- The relative-paths-only invariant --------------------------------------- // // A manifest path is ALWAYS project-relative (same invariant as Sample.relativePath // and the persisted BankModel). add() rejects an absolute path rather than guess a // relativization — the pure model has no project root, so a "normalization" would be // a guess that could point at the wrong file (mirror of BankModel::add's rejection). #include #include #include namespace reasampler::model { // Outcome of an add(). Mirrors BankModel::AddResult's honesty — the op reports what // happened rather than silently mutating on a bad request. // - Added: the path was new and recorded. // - AlreadyPresent: the path was already in the manifest (dedup no-op). // - RejectedEmptyPath: the path was empty. // - RejectedAbsolutePath: the path was absolute (relative-paths-only invariant). enum class ManifestAddResult { Added, AlreadyPresent, RejectedEmptyPath, RejectedAbsolutePath, }; // The owned-file manifest: an insertion-ordered, deduplicated set of project-relative // paths the capture path has created. Insertion order is preserved so serialize() // round-trips byte-identically (deterministic ext-state, mirror of the index). class OwnedFileManifest { public: OwnedFileManifest() = default; // Record a project-relative path as owned. Rejects an empty or absolute path (no // mutation). A path already present is a dedup no-op (AlreadyPresent), so a repeat // capture of an identical request does not double-record. ManifestAddResult add(const std::string& relativePath); // True iff the exact path string is recorded. Phase R uses this to attribute a // present file to the bank system. Exact string match — path normalization (if any) // is the caller's concern, consistent across add and query. bool contains(const std::string& relativePath) const; // The owned paths in insertion order. Phase R unions this with the on-disk file // set; here it is the round-trip + query surface. const std::vector& paths() const { return paths_; } std::size_t size() const { return paths_.size(); } bool empty() const { return paths_.empty(); } bool operator==(const OwnedFileManifest& o) const { return paths_ == o.paths_; } // -- Persistence --------------------------------------------------------- // Serialize to a JSON string (lossless round-trip): deserialize(serialize(x)) == x. // An empty manifest serializes to a well-formed empty shape (round-trips to empty). std::string serialize() const; // Parse a manifest JSON produced by serialize(). std::nullopt on malformed input // (the persist shell warns + falls back to an empty manifest, mirroring the bank / // view malformed handling). An empty/absent stored value is the caller's concern // (an empty string is not valid JSON) — the shell maps absence to a fresh manifest. static std::optional deserialize(const std::string& json); private: std::vector paths_; // insertion order; deduplicated }; } // namespace reasampler::model