glove/tests/host/onboarding_plan_test.cpp
trvon 22e7c2de50
fix(onboarding): harden setup plan
Derive runtime templates before setup validation so reruns preserve existing policy. Redact policy JSON by default to avoid exposing owner-local details.
2026-08-03 09:19:50 -04:00

128 lines
4.8 KiB
C++

#include "glove/host/onboarding_plan.hpp"
#include <sys/stat.h>
#include <unistd.h>
#include <cstdio>
#include <filesystem>
#include <fstream>
#include <string>
#include <system_error>
namespace {
#define REQUIRE(condition) \
do { \
if (!(condition)) { \
std::fprintf(stderr, "REQUIRE failed: %s @ %s:%d\n", #condition, __FILE__, __LINE__); \
return 1; \
} \
} while (false)
class temporary_directory {
public:
temporary_directory() {
std::string pattern = "/tmp/glove-onboarding-plan-test-XXXXXX";
if (char* created = ::mkdtemp(pattern.data()); created != nullptr) {
root_ = created;
}
}
temporary_directory(const temporary_directory&) = delete;
auto operator=(const temporary_directory&) -> temporary_directory& = delete;
~temporary_directory() {
std::error_code ignored;
std::filesystem::remove_all(root_, ignored);
}
[[nodiscard]] auto root() const -> const std::filesystem::path& { return root_; }
private:
std::filesystem::path root_;
};
auto write_owner_file(const std::filesystem::path& path, std::string_view contents, mode_t mode)
-> bool {
std::ofstream output{path};
output << contents;
output.close();
return output.good() && ::chmod(path.c_str(), mode) == 0;
}
auto run() -> int {
using namespace glove::host;
temporary_directory temporary;
REQUIRE(!temporary.root().empty());
const auto project_root = temporary.root() / "project";
const auto harness_bin = temporary.root() / "harness-bin";
REQUIRE(std::filesystem::create_directory(project_root));
REQUIRE(std::filesystem::create_directory(harness_bin));
REQUIRE(::chmod(harness_bin.c_str(), 0700) == 0);
REQUIRE(write_owner_file(harness_bin / "codex", "#!/bin/sh\nexit 0\n", 0700));
environment values{
.home = temporary.root().string(),
.xdg_config_home = std::nullopt,
.xdg_state_home = std::nullopt,
.xdg_data_home = std::nullopt,
.xdg_cache_home = std::nullopt,
.xdg_runtime_dir = std::nullopt,
.temporary_directory = std::nullopt,
};
onboarding_plan_options options{
.config_path = std::nullopt,
.protected_root = project_root,
.executable_search_paths = {harness_bin},
.selected_runtime_ids = {"codex"},
.pi_adoption = std::nullopt,
.backend = glove::supervisor::sandbox_backend::linux_production,
.hostile_content_analysis = false,
};
auto planned = plan_onboarding(options, values);
REQUIRE(planned.has_value());
REQUIRE(planned->setup.dry_run);
REQUIRE(planned->session_policy.dry_run);
REQUIRE(planned->setup.config_path.filename() == "config.json");
REQUIRE(
planned->session_policy.policy_path ==
planned->setup.config_path.parent_path() / "session-policy.json"
);
REQUIRE(
planned->protected_harness_root == planned->setup.config_path.parent_path() / "harnesses"
);
REQUIRE(planned->session_policy.runtimes.size() == 1U);
REQUIRE(planned->session_policy.runtimes.front().runtime_id == "codex");
REQUIRE(planned->setup.runtime_template_ids == std::vector<std::string>{"codex-safe"});
REQUIRE(
planned->session_policy.policy_json.find("\"egress_policy_ids\":[\"no-network\"]") !=
std::string::npos
);
REQUIRE(!std::filesystem::exists(planned->setup.config_path));
REQUIRE(!std::filesystem::exists(planned->session_policy.policy_path));
REQUIRE(!std::filesystem::exists(planned->protected_harness_root));
// A prior codex-only setup must not fail the read-only plan before it
// derives the same codex-only runtime template set.
REQUIRE(std::filesystem::create_directories(planned->setup.config_path.parent_path()));
REQUIRE(::chmod(planned->setup.config_path.parent_path().c_str(), 0700) == 0);
REQUIRE(write_config_exclusive(planned->setup.config_path, planned->setup.service));
auto rerun = plan_onboarding(options, values);
REQUIRE(rerun.has_value());
REQUIRE(rerun->setup.runtime_template_ids == std::vector<std::string>{"codex-safe"});
auto missing_search_root = options;
missing_search_root.executable_search_paths.clear();
auto invalid = plan_onboarding(missing_search_root, values);
REQUIRE(!invalid.has_value());
REQUIRE(invalid.error().find("explicit harness search directory") != std::string::npos);
return 0;
}
} // namespace
int main() {
return run();
}