#include "NamespaceReferenceManager.h" #include "Merkle.h" namespace nt { NamespaceReferenceManager::NamespaceReferenceManager(ObjectManager& objects, IStorageBackend& storage) : objects_(objects), storage_(storage) {} // Resolves the multigroup hash that `mg_name` currently maps to inside the // branch-tree rooted at `branch_tree_root`. Returns the empty string when // the branch is unborn (empty root) or the multigroup is absent. static std::string lookup_mg_hash(IStorageBackend& storage, const std::string& branch_tree_root, const std::string& mg_name) { if (branch_tree_root.empty()) return ""; auto found = Merkle::Get(storage, branch_tree_root, mg_name); if (!found) return ""; // All-zero payload = "no snapshot bound" sentinel matching MultigroupCodec. static const Hash32 zero{}; if (*found == zero) return ""; return bin_to_hex(*found); } std::vector NamespaceReferenceManager::Resolve( std::vector path) const { constexpr int kMaxReparseDepth = 30; for (int depth = 0; depth < kMaxReparseDepth; ++depth) { // /system/sessions//branches//multigroups//... // — per-session override takes precedence; absent override falls // back to the global branch. Requires at minimum the literal // "multigroups" qualifier and an mg name. if (path.size() > 6 && path[0] == "system" && path[1] == "sessions" && path[3] == "branches" && path[5] == "multigroups") { const std::vector session_path{ "system", "sessions", path[2] }; auto* session_entry = objects_.Find(session_path); std::string branch_tree_root; if (session_entry && session_entry->object) { auto* session = dynamic_cast( session_entry->object.get()); if (session) { auto it = session->branch_overrides.find(path[4]); if (it != session->branch_overrides.end()) branch_tree_root = it->second; } } // No session-level override — defer to the global branch's HEAD. if (branch_tree_root.empty()) { const std::vector branch_path{ "system", "branches", path[4] }; auto* bentry = objects_.Find(branch_path); if (bentry && bentry->object) { auto* branch = dynamic_cast( bentry->object.get()); if (branch) branch_tree_root = branch->target_hash; } } const std::string mg_hash = lookup_mg_hash(storage_, branch_tree_root, path[6]); if (mg_hash.empty()) return path; std::vector rewritten{ "system", "snapshots", mg_hash }; for (size_t i = 7; i < path.size(); ++i) rewritten.push_back(path[i]); path = std::move(rewritten); continue; } // /system/branches//multigroups//... → // /system/snapshots//... // Requires the literal "multigroups" qualifier and an mg name. A // bare /system/branches/ path opens the branch object itself // and is returned unchanged. if (path.size() > 4 && path[0] == "system" && path[1] == "branches" && path[3] == "multigroups") { const std::vector branch_path{ "system", "branches", path[2] }; auto* entry = objects_.Find(branch_path); if (!entry || !entry->object) return path; auto* branch = dynamic_cast(entry->object.get()); if (!branch) return path; const std::string mg_hash = lookup_mg_hash(storage_, branch->target_hash, path[4]); if (mg_hash.empty()) return path; std::vector rewritten{ "system", "snapshots", mg_hash }; for (size_t i = 5; i < path.size(); ++i) rewritten.push_back(path[i]); path = std::move(rewritten); continue; } return path; } // Cycle guard tripped — surface as a lookup miss. return {}; } }