[M5+] add resolver::verify_link + provisional overlay lifecycle

This commit is contained in:
2026-05-10 10:32:58 +00:00
parent 941d5b3284
commit 816ec993cd
9 changed files with 490 additions and 2 deletions

View File

@@ -71,6 +71,25 @@ auto overlay_insert_manual(OverlayState& state, const std::string& package,
const cargoxx::linkdb::Recipe& r)
-> cargoxx::util::Result<void>;
// Insert a row from a non-manual probe. `verified_at = 0` flags the row as
// provisional; the verify-link step bumps it to the current epoch on
// success or deletes the row on failure.
auto overlay_insert_provisional(OverlayState& state, const std::string& package,
const std::string& version_range,
const cargoxx::linkdb::Recipe& r,
const std::string& source)
-> cargoxx::util::Result<void>;
auto overlay_confirm_provisional(OverlayState& state, const std::string& package,
const std::string& version_range,
const std::string& source)
-> cargoxx::util::Result<void>;
auto overlay_delete_recipe(OverlayState& state, const std::string& package,
const std::string& version_range,
const std::string& source)
-> cargoxx::util::Result<void>;
auto overlay_query(OverlayState& state, const std::string& package)
-> cargoxx::util::Result<std::vector<OverlayRow>>;
@@ -92,6 +111,20 @@ class Database {
auto add_manual(const std::string& package, const std::string& version_range,
const Recipe& r) -> util::Result<void>;
// Provisional-recipe lifecycle, used by the resolver's verify-link step.
// `source` should be one of "conan", "vcpkg", "nix-probe", or any other
// probe identifier (NOT "manual"/"curated", which have their own
// contracts).
auto insert_provisional(const std::string& package,
const std::string& version_range, const Recipe& r,
const std::string& source) -> util::Result<void>;
auto confirm_provisional(const std::string& package,
const std::string& version_range,
const std::string& source) -> util::Result<void>;
auto abort_provisional(const std::string& package,
const std::string& version_range,
const std::string& source) -> util::Result<void>;
private:
Database() = default;
std::map<std::string, std::vector<detail::CuratedRecipe>> curated_;

View File

@@ -128,6 +128,98 @@ auto overlay_insert_manual(OverlayState& state, const std::string& package,
return {};
}
namespace {
auto overlay_insert(OverlayState& state, const std::string& package,
const std::string& version_range, const Recipe& r,
const std::string& source, std::int64_t verified_at)
-> util::Result<void> {
constexpr const char* SQL =
"INSERT OR REPLACE INTO recipes "
"(package, version_range, nixpkgs_attr, find_package, targets, components, source, "
" verified_at) "
"VALUES (?, ?, ?, ?, ?, NULL, ?, ?)";
sqlite3* db = state.handle();
sqlite3_stmt* stmt = nullptr;
if (sqlite3_prepare_v2(db, SQL, -1, &stmt, nullptr) != SQLITE_OK) {
return std::unexpected(sqlite_error(db, "prepare insert"));
}
auto targets_str = nlohmann::json(r.targets).dump();
sqlite3_bind_text(stmt, 1, package.c_str(), -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 2, version_range.c_str(), -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 3, r.nixpkgs_attr.c_str(), -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 4, r.find_package.c_str(), -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 5, targets_str.c_str(), -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 6, source.c_str(), -1, SQLITE_TRANSIENT);
sqlite3_bind_int64(stmt, 7, verified_at);
auto rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
if (rc != SQLITE_DONE) {
return std::unexpected(sqlite_error(db, "step insert"));
}
return {};
}
} // namespace
auto overlay_insert_provisional(OverlayState& state, const std::string& package,
const std::string& version_range, const Recipe& r,
const std::string& source) -> util::Result<void> {
return overlay_insert(state, package, version_range, r, source, /*verified_at=*/0);
}
auto overlay_confirm_provisional(OverlayState& state, const std::string& package,
const std::string& version_range,
const std::string& source) -> util::Result<void> {
constexpr const char* SQL =
"UPDATE recipes SET verified_at = ? "
"WHERE package = ? AND version_range = ? AND source = ?";
sqlite3* db = state.handle();
sqlite3_stmt* stmt = nullptr;
if (sqlite3_prepare_v2(db, SQL, -1, &stmt, nullptr) != SQLITE_OK) {
return std::unexpected(sqlite_error(db, "prepare update"));
}
auto now = std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::system_clock::now().time_since_epoch())
.count();
sqlite3_bind_int64(stmt, 1, now);
sqlite3_bind_text(stmt, 2, package.c_str(), -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 3, version_range.c_str(), -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 4, source.c_str(), -1, SQLITE_TRANSIENT);
auto rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
if (rc != SQLITE_DONE) {
return std::unexpected(sqlite_error(db, "step update"));
}
return {};
}
auto overlay_delete_recipe(OverlayState& state, const std::string& package,
const std::string& version_range,
const std::string& source) -> util::Result<void> {
constexpr const char* SQL =
"DELETE FROM recipes WHERE package = ? AND version_range = ? AND source = ?";
sqlite3* db = state.handle();
sqlite3_stmt* stmt = nullptr;
if (sqlite3_prepare_v2(db, SQL, -1, &stmt, nullptr) != SQLITE_OK) {
return std::unexpected(sqlite_error(db, "prepare delete"));
}
sqlite3_bind_text(stmt, 1, package.c_str(), -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 2, version_range.c_str(), -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 3, source.c_str(), -1, SQLITE_TRANSIENT);
auto rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
if (rc != SQLITE_DONE) {
return std::unexpected(sqlite_error(db, "step delete"));
}
return {};
}
auto overlay_query(OverlayState& state, const std::string& package)
-> util::Result<std::vector<OverlayRow>> {
constexpr const char* SQL =
@@ -192,4 +284,45 @@ auto Database::add_manual(const std::string& package, const std::string& version
return detail::overlay_insert_manual(*overlay_, package, version_range, r);
}
namespace {
auto require_overlay(const std::unique_ptr<detail::OverlayState>& o)
-> util::Result<void> {
if (!o) {
return std::unexpected(util::Error{
util::ErrorCode::Internal, "no overlay database is open", "",
std::nullopt, std::nullopt,
});
}
return {};
}
} // namespace
auto Database::insert_provisional(const std::string& package,
const std::string& version_range,
const Recipe& r, const std::string& source)
-> util::Result<void> {
if (auto ok = require_overlay(overlay_); !ok) {
return std::unexpected(ok.error());
}
return detail::overlay_insert_provisional(*overlay_, package, version_range, r, source);
}
auto Database::confirm_provisional(const std::string& package,
const std::string& version_range,
const std::string& source) -> util::Result<void> {
if (auto ok = require_overlay(overlay_); !ok) {
return std::unexpected(ok.error());
}
return detail::overlay_confirm_provisional(*overlay_, package, version_range, source);
}
auto Database::abort_provisional(const std::string& package,
const std::string& version_range,
const std::string& source) -> util::Result<void> {
if (auto ok = require_overlay(overlay_); !ok) {
return std::unexpected(ok.error());
}
return detail::overlay_delete_recipe(*overlay_, package, version_range, source);
}
} // namespace cargoxx::linkdb