1570 {
1571 throw std::logic_error(
1572 "running product attempt provenance is missing"
1573 );
1574 }
1576 } catch (...) {
1577 execute(database,
"ROLLBACK;");
1578 throw;
1579 }
1580}
1581
1583 const std::string_view run_id, const std::string_view status,
1584 const std::string_view manifest_ref
1585) {
1586 if (status != "succeeded" && status != "failed") {
1587 throw std::invalid_argument("run status must be succeeded or failed");
1588 }
1590 throw std::invalid_argument("run_id must be a safe stable identifier");
1591 }
1592 sqlite3* database = impl_->database.get();
1593 execute(database,
"BEGIN IMMEDIATE;");
1594 try {
1595 int attempt_number = 0;
1596 {
1597 auto current =
prepare(database, R
"SQL(
1598SELECT attempt_count FROM runs WHERE run_id=? AND status='running'
1599)SQL");
1600 bind_text(database, current.get(), 1, run_id);
1601 const int result = sqlite3_step(current.get());
1602 if (result != SQLITE_ROW) {
1603 if (result != SQLITE_DONE) {
1605 }
1606 throw std::logic_error("run is unknown or already finished");
1607 }
1608 attempt_number = sqlite3_column_int(current.get(), 0);
1609 }
1611 auto statement =
prepare(database, R
"SQL(
1612UPDATE runs SET status = ?, manifest_ref = ?, finished_at = ?
1613WHERE run_id = ? AND status = 'running'
1614)SQL");
1615 bind_text(database, statement.get(), 1, status);
1616 bind_text(database, statement.get(), 2, manifest_ref);
1617 bind_text(database, statement.get(), 3, finished_at);
1618 bind_text(database, statement.get(), 4, run_id);
1620 auto attempt =
prepare(database, R
"SQL(
1621UPDATE run_attempts SET status=?, manifest_ref=?, finished_at=?
1622WHERE run_id=? AND attempt=? AND status='running'
1623)SQL");
1624 bind_text(database, attempt.get(), 1, status);
1625 bind_text(database, attempt.get(), 2, manifest_ref);
1626 bind_text(database, attempt.get(), 3, finished_at);
1627 bind_text(database, attempt.get(), 4, run_id);
1628 if (sqlite3_bind_int(attempt.get(), 5, attempt_number) != SQLITE_OK) {
1630 }
1632 if (sqlite3_changes(database) != 1) {
1633 throw std::logic_error("running attempt provenance is missing");
1634 }
1636 } catch (...) {
1637 execute(database,
"ROLLBACK;");
1638 throw;
1639 }
1640}
1641
1643 return path_;
1644}
1645
1647 const std::filesystem::path& path,
1648 const std::filesystem::path& possible_parent
1649) {
1652 auto child_it = child.begin();
1653 for (auto parent_it = parent.begin(); parent_it != parent.end();
1654 ++parent_it, ++child_it) {
1655 if (child_it == child.end() || *child_it != *parent_it) {
1656 return false;
1657 }
1658 }
1659 return true;
1660}
1661
1663 const std::filesystem::path& target, const std::filesystem::path& inbox_root
1664) {
1667 throw std::invalid_argument(
1668 "refusing deletion inside or above the external legacy inbox"
1669 );
1670 }
1671}
1672
1674 const std::filesystem::path& lock_root, const std::string_view graph_domain,
1675 const std::string_view run_id, const std::chrono::seconds stale_after
1676) {
1677 if (graph_domain != "product_graph"
1678 && graph_domain != "research_candidate_graph") {
1679 throw std::invalid_argument("unknown graph domain");
1680 }
1682 throw std::invalid_argument("run_id must be a safe stable identifier");
1683 }
1684 if (stale_after.count() <= 0) {
1685 throw std::invalid_argument("lock stale interval must be positive");
1686 }
1688 std::filesystem::create_directories(lock_root);
1691 if (root == root.root_path()) {
1692 throw std::invalid_argument("lock root must be a scoped directory");
1693 }
1694 directory_ = root / (std::string(graph_domain) +
".lock");
1695 std::error_code error;
1696 const bool created = std::filesystem::create_directory(
directory_, error);
1697 if (!created) {
1698 if (error) {
1699 throw std::runtime_error(
1700 "cannot create graph-domain lock: " + error.message()
1701 );
1702 }
1703 const auto lock_status
1704 = std::filesystem::symlink_status(
directory_, error);
domain_lock(const std::filesystem::path &lock_root, std::string_view graph_domain, std::string_view run_id, std::chrono::seconds stale_after=std::chrono::hours(6))
std::filesystem::path directory_
void finish_run(std::string_view run_id, std::string_view status, std::string_view manifest_ref={})
const std::filesystem::path & path() const noexcept
std::string utc_timestamp()
void step_done(sqlite3 *database, sqlite3_stmt *statement)
void throw_sqlite(sqlite3 *database, std::string_view context)
void reject_symlink_components(const std::filesystem::path &path, std::string_view context)
std::filesystem::path weakly_canonical_or_absolute(const std::filesystem::path &path)
void execute(sqlite3 *database, std::string_view sql)
statement_handle prepare(sqlite3 *database, std::string_view sql)
bool is_safe_identifier(std::string_view value)
void bind_text(sqlite3 *database, sqlite3_stmt *statement, int index, std::string_view value)
bool path_is_within(const std::filesystem::path &path, const std::filesystem::path &possible_parent)
void reject_inbox_deletion_target(const std::filesystem::path &target, const std::filesystem::path &inbox_root)