#include #include "InMemoryBackend.h" #include "Merkle.h" #include "NamespaceReferenceManager.h" #include "ObjectManager.h" #include // --------------------------------------------------------------------------- // Resolver — two-level reparse through the branch tree. // // NamespaceReferenceManager::Resolve rewrites // /system/branches//multigroups//... // into // /system/snapshots//... // by reading branch.target_hash (a Merkle root) from storage and // looking up . Session paths consult the per-session override map first // and fall back to the global branch when no override is set. // --------------------------------------------------------------------------- namespace { using Path = std::vector; // Pad a label into a deterministic 64-char hex string so the merkle codec can // decode it as a 32-byte payload. std::string mk_hash(char c, char d = '0') { std::string h(64, '0'); h[0] = c; h[1] = d; return h; } std::unique_ptr make_type(OBJECT_TYPE label) { auto t = std::make_unique(); t->label = label; t->methods = {OPEN, CLOSE}; return t; } void register_branch(nt::ObjectManager& om, const std::string& name, const std::string& target_hash) { auto br = std::make_unique(); br->name = name; br->target_hash = target_hash; om.Register({"system", "branches", name}, std::move(br), make_type(BRANCH)); } void register_session(nt::ObjectManager& om, const std::string& id, std::map overrides = {}) { auto s = std::make_unique(); s->branch_overrides = std::move(overrides); om.Register({"system", "sessions", id}, std::move(s), make_type(SESSION)); } // Builds a branch tree with a single (mg_name → mg_hash) entry. Returns the // branch-tree root hex. std::string build_branch_tree(nt::InMemoryBackend& store, const std::string& mg_name, const std::string& mg_hash_hex) { return nt::Merkle::Insert(store, "", mg_name, nt::hex_to_bin(mg_hash_hex)); } } // namespace TEST_CASE("Branch sub-path rewrites through the branch tree to the mg snapshot", "[resolve]") { nt::ObjectManager om; nt::InMemoryBackend store; const std::string mg_hash = mk_hash('a'); const std::string tree = build_branch_tree(store, "warehouse", mg_hash); register_branch(om, "main", tree); nt::NamespaceReferenceManager nrm(om, store); REQUIRE(nrm.Resolve({"system", "branches", "main", "multigroups", "warehouse", "relations", "items"}) == Path{"system", "snapshots", mg_hash, "relations", "items"}); } TEST_CASE("Resolve on the branch object itself is left unchanged", "[resolve]") { nt::ObjectManager om; nt::InMemoryBackend store; const std::string tree = build_branch_tree(store, "warehouse", mk_hash('a')); register_branch(om, "main", tree); nt::NamespaceReferenceManager nrm(om, store); const Path p{"system", "branches", "main"}; REQUIRE(nrm.Resolve(p) == p); } TEST_CASE("Resolve on an unborn branch leaves the path unchanged", "[resolve]") { nt::ObjectManager om; nt::InMemoryBackend store; register_branch(om, "main", ""); // unborn nt::NamespaceReferenceManager nrm(om, store); const Path p{"system", "branches", "main", "multigroups", "warehouse", "relations", "items"}; REQUIRE(nrm.Resolve(p) == p); } TEST_CASE("Resolve on an absent multigroup leaves the path unchanged", "[resolve]") { nt::ObjectManager om; nt::InMemoryBackend store; const std::string tree = build_branch_tree(store, "warehouse", mk_hash('a')); register_branch(om, "main", tree); nt::NamespaceReferenceManager nrm(om, store); const Path p{"system", "branches", "main", "multigroups", "audit", "relations", "events"}; REQUIRE(nrm.Resolve(p) == p); } TEST_CASE("Resolve on an unknown branch leaves the path unchanged", "[resolve]") { nt::ObjectManager om; nt::InMemoryBackend store; nt::NamespaceReferenceManager nrm(om, store); const Path p{"system", "branches", "ghost", "multigroups", "warehouse", "relations", "items"}; REQUIRE(nrm.Resolve(p) == p); } TEST_CASE("Non-branch paths pass through unchanged", "[resolve]") { nt::ObjectManager om; nt::InMemoryBackend store; nt::NamespaceReferenceManager nrm(om, store); const Path p{"relations", "anywhere"}; REQUIRE(nrm.Resolve(p) == p); } TEST_CASE("Session override takes precedence over the global branch HEAD", "[resolve]") { nt::ObjectManager om; nt::InMemoryBackend store; const std::string mg_global = mk_hash('a'); const std::string mg_session = mk_hash('b'); const std::string tree_global = build_branch_tree(store, "warehouse", mg_global); const std::string tree_session = build_branch_tree(store, "warehouse", mg_session); register_branch(om, "main", tree_global); register_session(om, "sess123", {{"main", tree_session}}); nt::NamespaceReferenceManager nrm(om, store); REQUIRE(nrm.Resolve({"system", "sessions", "sess123", "branches", "main", "multigroups", "warehouse", "relations", "items"}) == Path{"system", "snapshots", mg_session, "relations", "items"}); } TEST_CASE("Session resolution falls back to global branch when override is absent", "[resolve]") { nt::ObjectManager om; nt::InMemoryBackend store; const std::string mg_global = mk_hash('a'); const std::string tree_global = build_branch_tree(store, "warehouse", mg_global); register_branch(om, "main", tree_global); register_session(om, "sess123"); // no overrides nt::NamespaceReferenceManager nrm(om, store); REQUIRE(nrm.Resolve({"system", "sessions", "sess123", "branches", "main", "multigroups", "warehouse", "relations", "items"}) == Path{"system", "snapshots", mg_global, "relations", "items"}); } TEST_CASE("Session resolution with neither override nor global branch is unchanged", "[resolve]") { nt::ObjectManager om; nt::InMemoryBackend store; register_session(om, "sess123"); nt::NamespaceReferenceManager nrm(om, store); const Path p{"system", "sessions", "sess123", "branches", "main", "multigroups", "warehouse", "relations", "items"}; REQUIRE(nrm.Resolve(p) == p); } TEST_CASE("Empty input path is returned unchanged", "[resolve]") { nt::ObjectManager om; nt::InMemoryBackend store; nt::NamespaceReferenceManager nrm(om, store); REQUIRE(nrm.Resolve({}).empty()); }