1078 lines
45 KiB
C++
1078 lines
45 KiB
C++
#include "glove/container/digest.hpp"
|
|
#include "glove/container/profile.hpp"
|
|
#include "glove/container/receipt_chain.hpp"
|
|
#include "glove/container/receipt_producer.hpp"
|
|
#include "glove/supervisor/library_bundle.hpp"
|
|
#include "glove/supervisor/linux_session_filesystem.hpp"
|
|
#include "glove/supervisor/native_skill_runtime_adapter.hpp"
|
|
#include "glove/supervisor/path_alias.hpp"
|
|
|
|
#include "cgroup_v2.hpp"
|
|
#include "linux_managed_session.hpp"
|
|
#include "linux_resource_lifecycle.hpp"
|
|
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
|
|
#include <array>
|
|
#include <chrono>
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <expected>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <memory>
|
|
#include <span>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
namespace {
|
|
|
|
#define REQUIRE(cond) \
|
|
do { \
|
|
if (!(cond)) { \
|
|
std::fprintf(stderr, "REQUIRE failed: %s @ %s:%d\n", #cond, __FILE__, __LINE__); \
|
|
return 1; \
|
|
} \
|
|
} while (0)
|
|
|
|
using glove::container::profile;
|
|
using glove::container::resource_enforcement_receipt;
|
|
using glove::container::resource_limits;
|
|
using glove::container::resource_termination_cause;
|
|
using glove::container::linux_detail::cgroup_v2_root;
|
|
using glove::container::linux_detail::linux_resource_lifecycle;
|
|
using glove::supervisor::linux_detail::linux_session_filesystem;
|
|
|
|
constexpr std::string_view controller_plan_digest =
|
|
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
|
|
constexpr std::string_view audit_key =
|
|
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f";
|
|
|
|
struct requested_alias {
|
|
std::string alias;
|
|
std::filesystem::path source;
|
|
std::string target;
|
|
glove::supervisor::path_access access = glove::supervisor::path_access::ephemeral_write;
|
|
};
|
|
|
|
class temporary_tree {
|
|
public:
|
|
temporary_tree() {
|
|
std::string pattern = "/tmp/glove-managed-session-test-XXXXXX";
|
|
char* created = ::mkdtemp(pattern.data());
|
|
if (created != nullptr) {
|
|
root_ = created;
|
|
}
|
|
}
|
|
|
|
temporary_tree(const temporary_tree&) = delete;
|
|
auto operator=(const temporary_tree&) -> temporary_tree& = delete;
|
|
temporary_tree(temporary_tree&&) = delete;
|
|
auto operator=(temporary_tree&&) -> temporary_tree& = delete;
|
|
|
|
~temporary_tree() {
|
|
std::error_code error;
|
|
std::filesystem::remove_all(root_, error);
|
|
}
|
|
|
|
[[nodiscard]] auto root() const -> const std::filesystem::path& { return root_; }
|
|
|
|
private:
|
|
std::filesystem::path root_;
|
|
};
|
|
|
|
auto epoch_ms() -> std::uint64_t {
|
|
const auto now = std::chrono::system_clock::now().time_since_epoch();
|
|
return static_cast<std::uint64_t>(
|
|
std::chrono::duration_cast<std::chrono::milliseconds>(now).count()
|
|
);
|
|
}
|
|
|
|
auto policy_for(const requested_alias& requested, std::uint64_t quota)
|
|
-> glove::supervisor::path_alias_policy {
|
|
const bool read_only = requested.access == glove::supervisor::path_access::read;
|
|
return {
|
|
.alias = requested.alias,
|
|
.host_path = requested.source.string(),
|
|
.target_path = requested.target,
|
|
.max_ttl_secs = 300,
|
|
.access = {{
|
|
.access = requested.access,
|
|
.materialization = read_only ? glove::supervisor::path_materialization::bind
|
|
: glove::supervisor::path_materialization::copy,
|
|
.create_policy = read_only ? glove::supervisor::path_create_policy::never
|
|
: glove::supervisor::path_create_policy::empty_directory,
|
|
.cleanup_policy = read_only ? glove::supervisor::path_cleanup_policy::retain
|
|
: glove::supervisor::path_cleanup_policy::remove,
|
|
.max_bytes = read_only ? 0 : quota,
|
|
}},
|
|
};
|
|
}
|
|
|
|
auto make_lifecycle(
|
|
cgroup_v2_root& root,
|
|
const std::filesystem::path& materialization_root,
|
|
std::span<const requested_alias> requested_aliases,
|
|
std::string_view session_id,
|
|
const resource_limits& limits,
|
|
std::vector<glove::supervisor::resolved_library_projection>&& library_projections = {},
|
|
std::string_view runtime_id = {}
|
|
) -> std::expected<std::unique_ptr<linux_resource_lifecycle>, std::string> {
|
|
std::vector<glove::supervisor::resolved_path_grant> grants;
|
|
if (!requested_aliases.empty()) {
|
|
const auto alias_quota = limits.disk_bytes / 4U;
|
|
std::vector<glove::supervisor::path_alias_policy> policies;
|
|
policies.reserve(requested_aliases.size());
|
|
for (const auto& requested : requested_aliases) {
|
|
policies.push_back(policy_for(requested, alias_quota));
|
|
}
|
|
auto registry = glove::supervisor::path_alias_registry::build(std::move(policies));
|
|
if (!registry) {
|
|
return std::unexpected(registry.error());
|
|
}
|
|
grants.reserve(requested_aliases.size());
|
|
for (const auto& requested : requested_aliases) {
|
|
auto grant = registry->resolve({
|
|
.alias = requested.alias,
|
|
.access = requested.access,
|
|
.ttl_secs = 300,
|
|
.max_bytes =
|
|
requested.access == glove::supervisor::path_access::read ? 0 : alias_quota,
|
|
});
|
|
if (!grant) {
|
|
return std::unexpected(grant.error());
|
|
}
|
|
grants.push_back(std::move(*grant));
|
|
}
|
|
}
|
|
auto filesystem = linux_session_filesystem::create(
|
|
materialization_root.string(),
|
|
session_id,
|
|
limits.disk_bytes,
|
|
std::move(grants),
|
|
std::move(library_projections),
|
|
runtime_id
|
|
);
|
|
if (!filesystem) {
|
|
return std::unexpected(filesystem.error());
|
|
}
|
|
auto cgroup = root.create_session(session_id, limits);
|
|
if (!cgroup) {
|
|
return std::unexpected(cgroup.error());
|
|
}
|
|
return linux_resource_lifecycle::create(
|
|
std::move(*cgroup), std::move(*filesystem), limits, epoch_ms()
|
|
);
|
|
}
|
|
|
|
auto limits_for(std::uint64_t disk_bytes) -> resource_limits {
|
|
return {
|
|
.cpu_time_ms = 10'000,
|
|
.memory_bytes = std::uint64_t{128} * 1024U * 1024U,
|
|
.pids = 16,
|
|
.wall_time_ms = 5'000,
|
|
.disk_bytes = disk_bytes,
|
|
.terminal_output_bytes = std::uint64_t{1024} * 1024U,
|
|
};
|
|
}
|
|
|
|
auto launch_profile(const resource_limits& limits) -> profile {
|
|
profile value;
|
|
value.environment = {"PATH=/usr/bin:/bin:/usr/sbin:/sbin"};
|
|
value.required_limits = limits;
|
|
return value;
|
|
}
|
|
|
|
auto digest_for(std::string_view value) -> std::string {
|
|
const auto* bytes = reinterpret_cast<const unsigned char*>(value.data());
|
|
return glove::container::sha256_hex(std::span{bytes, value.size()}).value_or("");
|
|
}
|
|
|
|
auto execute_managed(
|
|
const profile& prof,
|
|
const std::vector<std::string>& argv,
|
|
std::unique_ptr<linux_resource_lifecycle> lifecycle
|
|
) -> std::expected<resource_enforcement_receipt, std::string> {
|
|
if (!lifecycle) {
|
|
return std::unexpected(std::string{"test lifecycle is required"});
|
|
}
|
|
auto binding = glove::container::linux_detail::bind_managed_session(
|
|
prof, argv, *lifecycle, controller_plan_digest
|
|
);
|
|
if (!binding) {
|
|
return std::unexpected(binding.error());
|
|
}
|
|
return glove::container::linux_detail::exec_managed_session(
|
|
prof, argv, *binding, std::move(lifecycle)
|
|
);
|
|
}
|
|
|
|
// The local REQUIRE macro expands each assertion into branches; the scenario
|
|
// itself remains a single linear launch contract.
|
|
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
|
|
auto mounted_copy_is_isolated_test(
|
|
cgroup_v2_root& root,
|
|
const std::filesystem::path& materialization_root,
|
|
const std::filesystem::path& source,
|
|
const std::filesystem::path& file_source,
|
|
const std::filesystem::path& reference_source,
|
|
std::uint64_t page
|
|
) -> int {
|
|
const auto limits = limits_for(page * 64U);
|
|
const std::vector aliases = {
|
|
requested_alias{"project", source, "/workspace/project"},
|
|
requested_alias{"single", file_source, "/workspace/single.txt"},
|
|
requested_alias{
|
|
"reference",
|
|
reference_source,
|
|
"/workspace/reference",
|
|
glove::supervisor::path_access::read,
|
|
},
|
|
};
|
|
auto lifecycle = make_lifecycle(root, materialization_root, aliases, "managed-copy", limits);
|
|
REQUIRE(lifecycle.has_value());
|
|
std::string script = "set -eu; ";
|
|
script += "test \"$(cat /workspace/project/input.txt)\" = seed; ";
|
|
script += "printf child > /workspace/project/output.txt; ";
|
|
script += "test \"$(cat /workspace/project/output.txt)\" = child; ";
|
|
script += "test \"$(cat /workspace/single.txt)\" = standalone; ";
|
|
script += "printf changed > /workspace/single.txt; ";
|
|
script += "test \"$(cat /workspace/single.txt)\" = changed; ";
|
|
script += "test \"$(cat /workspace/reference/reference.txt)\" = trusted; ";
|
|
script += "if (printf denied > /workspace/reference/blocked) 2>/dev/null; then exit 81; fi; ";
|
|
script += "test -w /tmp; test -w /var/tmp; ";
|
|
script += "test \"$(stat -c %d /tmp)\" = \"$(stat -c %d /var/tmp)\"; ";
|
|
script += "test ! -e '" + source.string() + "'; ";
|
|
script += "printf alpha; printf beta >&2";
|
|
const auto prof = launch_profile(limits);
|
|
const std::vector argv = {std::string{"/usr/bin/sh"}, std::string{"-c"}, script};
|
|
auto binding = glove::container::linux_detail::bind_managed_session(
|
|
prof, argv, **lifecycle, controller_plan_digest
|
|
);
|
|
REQUIRE(binding.has_value());
|
|
const auto producer_key_path = materialization_root.parent_path() / "managed-audit.key";
|
|
const auto producer_journal_path =
|
|
materialization_root.parent_path() / "managed-receipts.journal";
|
|
{
|
|
std::ofstream key{producer_key_path, std::ios::binary | std::ios::trunc};
|
|
key << audit_key << '\n';
|
|
key.flush();
|
|
REQUIRE(key.good());
|
|
}
|
|
REQUIRE(::chmod(producer_key_path.c_str(), 0600) == 0);
|
|
const glove::container::receipt_audit_producer_config producer_config{
|
|
.key_path = producer_key_path,
|
|
.journal_path = producer_journal_path,
|
|
};
|
|
auto producer = glove::container::receipt_audit_producer::initialize(producer_config);
|
|
REQUIRE(producer.has_value());
|
|
const auto producer_genesis = (*producer)->anchor();
|
|
REQUIRE((*producer)->acknowledge_bootstrap(producer_genesis).has_value());
|
|
auto terminal = glove::container::linux_detail::exec_managed_session_authenticated(
|
|
prof, argv, "managed-copy", *binding, std::move(*lifecycle), **producer
|
|
);
|
|
REQUIRE(terminal.has_value());
|
|
const auto durable_anchor = (*producer)->anchor();
|
|
REQUIRE(durable_anchor.sequence == 1);
|
|
producer->reset();
|
|
auto recovered =
|
|
glove::container::receipt_audit_producer::recover(producer_config, producer_genesis);
|
|
REQUIRE(recovered.has_value());
|
|
auto pending = (*recovered)->page_after(producer_genesis, 10);
|
|
REQUIRE(pending.has_value());
|
|
REQUIRE(pending->envelopes.size() == 1);
|
|
REQUIRE(pending->envelopes[0] == *terminal);
|
|
REQUIRE(terminal->receipt.profile_digest == binding->profile_digest);
|
|
REQUIRE(terminal->receipt.termination_cause == resource_termination_cause::exited);
|
|
REQUIRE(terminal->receipt.exit_code == 0);
|
|
REQUIRE(terminal->receipt.observed.disk_bytes > 0);
|
|
REQUIRE(terminal->receipt.observed.disk_bytes <= limits.disk_bytes);
|
|
REQUIRE(terminal->receipt.observed.terminal_output_bytes == 9);
|
|
auto anchor = glove::container::receipt_audit_anchor::create(audit_key);
|
|
REQUIRE(anchor.has_value());
|
|
REQUIRE(
|
|
glove::container::verify_receipt_audit_envelope(
|
|
*terminal, audit_key, "managed-copy", controller_plan_digest, **anchor
|
|
)
|
|
.has_value()
|
|
);
|
|
REQUIRE(
|
|
glove::container::validate_resource_enforcement_receipt(
|
|
terminal->receipt,
|
|
limits,
|
|
glove::container::linux_detail::managed_session_capabilities(),
|
|
glove::container::sandbox_backend::linux_production,
|
|
binding->profile_digest
|
|
)
|
|
.has_value()
|
|
);
|
|
auto tampered_digest = terminal->receipt;
|
|
tampered_digest.profile_digest[0] = tampered_digest.profile_digest[0] == 'a' ? 'b' : 'a';
|
|
REQUIRE(!glove::container::validate_resource_enforcement_receipt(
|
|
tampered_digest,
|
|
limits,
|
|
glove::container::linux_detail::managed_session_capabilities(),
|
|
glove::container::sandbox_backend::linux_production,
|
|
binding->profile_digest
|
|
)
|
|
.has_value());
|
|
auto tampered_mechanisms = terminal->receipt;
|
|
tampered_mechanisms.mechanisms.disk = glove::container::enforcement_mechanism::unavailable;
|
|
REQUIRE(!glove::container::validate_resource_enforcement_receipt(
|
|
tampered_mechanisms,
|
|
limits,
|
|
glove::container::linux_detail::managed_session_capabilities(),
|
|
glove::container::sandbox_backend::linux_production,
|
|
binding->profile_digest
|
|
)
|
|
.has_value());
|
|
auto tampered_limits = terminal->receipt;
|
|
++tampered_limits.configured_limits.wall_time_ms;
|
|
REQUIRE(!glove::container::validate_resource_enforcement_receipt(
|
|
tampered_limits,
|
|
limits,
|
|
glove::container::linux_detail::managed_session_capabilities(),
|
|
glove::container::sandbox_backend::linux_production,
|
|
binding->profile_digest
|
|
)
|
|
.has_value());
|
|
auto tampered_envelope = *terminal;
|
|
++tampered_envelope.receipt.observed.disk_bytes;
|
|
auto tamper_anchor = glove::container::receipt_audit_anchor::create(audit_key);
|
|
REQUIRE(tamper_anchor.has_value());
|
|
REQUIRE(
|
|
!glove::container::verify_receipt_audit_envelope(
|
|
tampered_envelope, audit_key, "managed-copy", controller_plan_digest, **tamper_anchor
|
|
)
|
|
.has_value()
|
|
);
|
|
REQUIRE(!std::filesystem::exists(source / "output.txt"));
|
|
REQUIRE(!std::filesystem::exists(reference_source / "blocked"));
|
|
{
|
|
std::ifstream original{file_source};
|
|
std::string contents;
|
|
original >> contents;
|
|
REQUIRE(contents == "standalone");
|
|
}
|
|
REQUIRE(std::filesystem::is_empty(materialization_root));
|
|
return 0;
|
|
}
|
|
|
|
auto managed_policy_rejection_test(
|
|
cgroup_v2_root& root,
|
|
const std::filesystem::path& materialization_root,
|
|
const std::filesystem::path& source,
|
|
std::uint64_t page
|
|
) -> int {
|
|
const auto limits = limits_for(page * 32U);
|
|
auto raw_path_lifecycle =
|
|
make_lifecycle(root, materialization_root, {}, "managed-raw-path", limits);
|
|
REQUIRE(raw_path_lifecycle.has_value());
|
|
auto raw_path_profile = launch_profile(limits);
|
|
raw_path_profile.filesystem.push_back({.path = source.string(), .writable = false});
|
|
auto raw_path =
|
|
execute_managed(raw_path_profile, {"/usr/bin/true"}, std::move(*raw_path_lifecycle));
|
|
REQUIRE(!raw_path.has_value());
|
|
REQUIRE(raw_path.error().find("lifecycle mount set") != std::string::npos);
|
|
REQUIRE(std::filesystem::is_empty(materialization_root));
|
|
|
|
auto runtime_path_lifecycle =
|
|
make_lifecycle(root, materialization_root, {}, "managed-runtime-path", limits);
|
|
REQUIRE(runtime_path_lifecycle.has_value());
|
|
auto runtime_path_profile = launch_profile(limits);
|
|
runtime_path_profile.runtime_filesystem.push_back({.path = source.string(), .writable = false});
|
|
auto runtime_path = execute_managed(
|
|
runtime_path_profile,
|
|
{"/bin/sh", "-c", "test -f " + (source / "input.txt").string()},
|
|
std::move(*runtime_path_lifecycle)
|
|
);
|
|
REQUIRE(runtime_path.has_value());
|
|
REQUIRE(std::filesystem::is_empty(materialization_root));
|
|
|
|
auto unmanaged_home_lifecycle =
|
|
make_lifecycle(root, materialization_root, {}, "managed-unbound-home", limits);
|
|
REQUIRE(unmanaged_home_lifecycle.has_value());
|
|
auto unmanaged_home_profile = launch_profile(limits);
|
|
unmanaged_home_profile.managed_home_dir = "/home/agent";
|
|
auto unmanaged_home = execute_managed(
|
|
unmanaged_home_profile, {"/usr/bin/true"}, std::move(*unmanaged_home_lifecycle)
|
|
);
|
|
REQUIRE(!unmanaged_home.has_value());
|
|
REQUIRE(unmanaged_home.error().find("exact scratch projections") != std::string::npos);
|
|
REQUIRE(std::filesystem::is_empty(materialization_root));
|
|
|
|
auto mismatched_lifecycle =
|
|
make_lifecycle(root, materialization_root, {}, "managed-limit-mismatch", limits);
|
|
REQUIRE(mismatched_lifecycle.has_value());
|
|
auto mismatched_limits = limits;
|
|
++mismatched_limits.wall_time_ms;
|
|
auto mismatched_profile = launch_profile(mismatched_limits);
|
|
auto mismatch =
|
|
execute_managed(mismatched_profile, {"/usr/bin/true"}, std::move(*mismatched_lifecycle));
|
|
REQUIRE(!mismatch.has_value());
|
|
REQUIRE(mismatch.error().find("resource limits mismatch") != std::string::npos);
|
|
REQUIRE(std::filesystem::is_empty(materialization_root));
|
|
|
|
auto tampered_lifecycle =
|
|
make_lifecycle(root, materialization_root, {}, "managed-binding-tamper", limits);
|
|
REQUIRE(tampered_lifecycle.has_value());
|
|
const auto prof = launch_profile(limits);
|
|
const std::vector argv = {std::string{"/usr/bin/true"}};
|
|
auto binding = glove::container::linux_detail::bind_managed_session(
|
|
prof, argv, **tampered_lifecycle, controller_plan_digest
|
|
);
|
|
REQUIRE(binding.has_value());
|
|
binding->profile_digest[0] = binding->profile_digest[0] == 'a' ? 'b' : 'a';
|
|
auto tampered = glove::container::linux_detail::exec_managed_session(
|
|
prof, argv, *binding, std::move(*tampered_lifecycle)
|
|
);
|
|
REQUIRE(!tampered.has_value());
|
|
REQUIRE(tampered.error().find("launch binding mismatch") != std::string::npos);
|
|
REQUIRE(std::filesystem::is_empty(materialization_root));
|
|
return 0;
|
|
}
|
|
|
|
auto codex_runtime_context_test(
|
|
cgroup_v2_root& root,
|
|
const std::filesystem::path& materialization_root,
|
|
const std::filesystem::path& library_root,
|
|
std::uint64_t page
|
|
) -> int {
|
|
constexpr std::string_view bundle =
|
|
R"({"schema_version":1,"source_library_ref":"bafy-codex","source_manifest_digest":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","entries":[{"key":"sage-core","kind":"skill","content_digest":"a8095aa5472d84253e87441d7438235d2b13c13e09de63cbb88609931b8b8947","content":"# Sage core\n"}]})";
|
|
const auto bundle_digest = digest_for(bundle);
|
|
REQUIRE(bundle_digest.size() == 64U);
|
|
const auto bundle_path = library_root / (bundle_digest + ".json");
|
|
{
|
|
std::ofstream output{bundle_path, std::ios::binary | std::ios::trunc};
|
|
output << bundle;
|
|
}
|
|
REQUIRE(::chmod(bundle_path.c_str(), 0600) == 0);
|
|
auto store = glove::supervisor::library_bundle_store::open(library_root);
|
|
REQUIRE(store.has_value());
|
|
auto projections = store->resolve_projections({{
|
|
.projection =
|
|
{
|
|
.projection_id = "sage-codex",
|
|
.content_digest = bundle_digest,
|
|
.destination_alias = "libraries",
|
|
},
|
|
.target_path = "/opt/sage/library-bundles",
|
|
}});
|
|
REQUIRE(projections.has_value());
|
|
const auto limits = limits_for(page * 32U);
|
|
auto lifecycle = make_lifecycle(
|
|
root,
|
|
materialization_root,
|
|
{},
|
|
"managed-codex-context",
|
|
limits,
|
|
std::move(*projections),
|
|
"codex"
|
|
);
|
|
REQUIRE(lifecycle.has_value());
|
|
auto prof = launch_profile(limits);
|
|
prof.environment.push_back("CODEX_HOME=/home/agent/.codex");
|
|
prof.managed_home_dir = "/home/agent";
|
|
const std::vector argv = {
|
|
std::string{"/usr/bin/sh"},
|
|
std::string{"-c"},
|
|
std::string{"test \"$HOME\" = /home/agent; test \"$CODEX_HOME\" = /home/agent/.codex; "
|
|
"test -f /home/agent/.codex/skills/sage-codex-sage-core/SKILL.md; "
|
|
"test \"$(cat /home/agent/.codex/skills/sage-codex-sage-core/SKILL.md)\" = '# "
|
|
"Sage core'"},
|
|
};
|
|
auto binding = glove::container::linux_detail::bind_managed_session(
|
|
prof, argv, **lifecycle, controller_plan_digest
|
|
);
|
|
REQUIRE(binding.has_value());
|
|
auto terminal = glove::container::linux_detail::exec_managed_session(
|
|
prof, argv, *binding, std::move(*lifecycle)
|
|
);
|
|
REQUIRE(terminal.has_value());
|
|
REQUIRE(terminal->exit_code == 0);
|
|
REQUIRE(terminal->profile_digest == binding->profile_digest);
|
|
REQUIRE(terminal->library_projections.size() == 1U);
|
|
REQUIRE(terminal->library_projections.front().content_digest == bundle_digest);
|
|
REQUIRE(std::filesystem::is_empty(materialization_root));
|
|
|
|
struct native_runtime_case {
|
|
std::string_view runtime_id;
|
|
std::string_view skill_directory;
|
|
std::string_view managed_environment;
|
|
};
|
|
|
|
for (const native_runtime_case runtime : {
|
|
native_runtime_case{"claude-code", ".claude/skills", ""},
|
|
native_runtime_case{"pi", ".pi/agent/skills", ""},
|
|
native_runtime_case{"copilot", ".copilot/skills", "COPILOT_HOME=/home/agent/.copilot"},
|
|
native_runtime_case{
|
|
"opencode", ".config/opencode/skills", "XDG_CONFIG_HOME=/home/agent/.config"
|
|
},
|
|
}) {
|
|
auto native_projections = store->resolve_projections({{
|
|
.projection =
|
|
{
|
|
.projection_id = "sage-codex",
|
|
.content_digest = bundle_digest,
|
|
.destination_alias = "libraries",
|
|
},
|
|
.target_path = "/opt/sage/library-bundles",
|
|
}});
|
|
REQUIRE(native_projections.has_value());
|
|
auto native_lifecycle = make_lifecycle(
|
|
root,
|
|
materialization_root,
|
|
{},
|
|
std::string{"managed-"} + std::string{runtime.runtime_id} + "-context",
|
|
limits,
|
|
std::move(*native_projections),
|
|
runtime.runtime_id
|
|
);
|
|
REQUIRE(native_lifecycle.has_value());
|
|
auto native_profile = launch_profile(limits);
|
|
native_profile.managed_home_dir = "/home/agent";
|
|
if (!runtime.managed_environment.empty()) {
|
|
native_profile.environment.push_back(std::string{runtime.managed_environment});
|
|
}
|
|
std::string command = "test \"$HOME\" = /home/agent; test -f /home/agent/" +
|
|
std::string{runtime.skill_directory} +
|
|
"/sage-codex-sage-core/SKILL.md";
|
|
if (!runtime.managed_environment.empty()) {
|
|
const auto separator = runtime.managed_environment.find('=');
|
|
command += "; test \"$" +
|
|
std::string{runtime.managed_environment.substr(0, separator)} + "\" = \"" +
|
|
std::string{runtime.managed_environment.substr(separator + 1)} + "\"";
|
|
}
|
|
const std::vector native_argv = {
|
|
std::string{"/usr/bin/sh"},
|
|
std::string{"-c"},
|
|
std::move(command),
|
|
};
|
|
auto native_binding = glove::container::linux_detail::bind_managed_session(
|
|
native_profile, native_argv, **native_lifecycle, controller_plan_digest
|
|
);
|
|
REQUIRE(native_binding.has_value());
|
|
auto native_terminal = glove::container::linux_detail::exec_managed_session(
|
|
native_profile, native_argv, *native_binding, std::move(*native_lifecycle)
|
|
);
|
|
REQUIRE(native_terminal.has_value());
|
|
REQUIRE(native_terminal->exit_code == 0);
|
|
REQUIRE(std::filesystem::is_empty(materialization_root));
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// A test-only image supplies pinned, operator-like harness installations under
|
|
// this one read-only runtime root. The regular containment image intentionally
|
|
// has no vendor client, so ordinary unit and workflow lanes retain that guard.
|
|
auto real_native_harness_test(
|
|
cgroup_v2_root& root,
|
|
const std::filesystem::path& materialization_root,
|
|
const std::filesystem::path& library_root,
|
|
std::uint64_t page
|
|
) -> int {
|
|
const char* configured_root = ::getenv("GLOVE_TEST_NATIVE_HARNESS_ROOT");
|
|
if (configured_root == nullptr || *configured_root == '\0') {
|
|
return 0;
|
|
}
|
|
std::error_code error;
|
|
const auto harness_root = std::filesystem::canonical(configured_root, error);
|
|
REQUIRE(!error);
|
|
REQUIRE(std::filesystem::is_directory(harness_root));
|
|
|
|
constexpr std::string_view bundle =
|
|
R"({"schema_version":1,"source_library_ref":"bafy-native","source_manifest_digest":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","entries":[{"key":"sage-core","kind":"skill","content_digest":"a8095aa5472d84253e87441d7438235d2b13c13e09de63cbb88609931b8b8947","content":"# Sage core\n"}]})";
|
|
const auto bundle_digest = digest_for(bundle);
|
|
REQUIRE(bundle_digest.size() == 64U);
|
|
const auto bundle_path = library_root / (bundle_digest + ".json");
|
|
{
|
|
std::ofstream output{bundle_path, std::ios::binary | std::ios::trunc};
|
|
output << bundle;
|
|
}
|
|
REQUIRE(::chmod(bundle_path.c_str(), 0600) == 0);
|
|
auto store = glove::supervisor::library_bundle_store::open(library_root);
|
|
REQUIRE(store.has_value());
|
|
|
|
struct harness_case {
|
|
std::string_view runtime_id;
|
|
std::string_view executable;
|
|
};
|
|
|
|
constexpr std::array<harness_case, 4> harnesses{{
|
|
{"claude-code", "claude"},
|
|
{"pi", "pi"},
|
|
{"copilot", "copilot"},
|
|
{"opencode", "opencode"},
|
|
}};
|
|
auto limits = limits_for(page * 256U);
|
|
// Vendor startup shapes differ; this is a conformance ceiling, not the
|
|
// low-limit enforcement probe elsewhere in this test suite.
|
|
limits.memory_bytes = std::uint64_t{1} * 1024U * 1024U * 1024U;
|
|
limits.pids = 64;
|
|
limits.wall_time_ms = 20'000;
|
|
limits.disk_bytes = std::uint64_t{512} * 1024U * 1024U;
|
|
const auto bin_directory = harness_root / "node_modules" / ".bin";
|
|
const auto node_directory = harness_root / "node-runtime" / "bin";
|
|
for (const auto& harness : harnesses) {
|
|
const auto adapter =
|
|
glove::supervisor::native_skill_runtime_adapter_for(harness.runtime_id);
|
|
REQUIRE(adapter.has_value());
|
|
const auto executable = bin_directory / harness.executable;
|
|
REQUIRE(std::filesystem::is_regular_file(executable));
|
|
REQUIRE(::access(executable.c_str(), X_OK) == 0);
|
|
|
|
auto projections = store->resolve_projections({{
|
|
.projection =
|
|
{
|
|
.projection_id = "sage-native",
|
|
.content_digest = bundle_digest,
|
|
.destination_alias = "libraries",
|
|
},
|
|
.target_path = "/opt/sage/library-bundles",
|
|
}});
|
|
REQUIRE(projections.has_value());
|
|
auto lifecycle = make_lifecycle(
|
|
root,
|
|
materialization_root,
|
|
{},
|
|
std::string{"managed-real-"} + std::string{harness.runtime_id},
|
|
limits,
|
|
std::move(*projections),
|
|
harness.runtime_id
|
|
);
|
|
REQUIRE(lifecycle.has_value());
|
|
auto prof = launch_profile(limits);
|
|
prof.environment = {
|
|
"PATH=" + bin_directory.string() + ":" + node_directory.string() + ":/usr/bin:/bin",
|
|
"TERM=xterm-256color",
|
|
};
|
|
for (const auto& variable : adapter->managed_environment) {
|
|
prof.environment.push_back(variable);
|
|
}
|
|
prof.runtime_filesystem.push_back({.path = harness_root.string(), .writable = false});
|
|
prof.managed_home_dir = "/home/agent";
|
|
const std::vector argv = {executable.string(), std::string{"--version"}};
|
|
auto binding = glove::container::linux_detail::bind_managed_session(
|
|
prof, argv, **lifecycle, controller_plan_digest
|
|
);
|
|
REQUIRE(binding.has_value());
|
|
auto session = glove::container::linux_detail::start_managed_pty_session(
|
|
prof,
|
|
argv,
|
|
*binding,
|
|
std::move(*lifecycle),
|
|
{
|
|
.transcript_bytes = 64U * 1024U,
|
|
.max_read_bytes = 64U * 1024U,
|
|
.max_input_frame_bytes = 64U * 1024U,
|
|
.input_timeout_ms = 1'000,
|
|
.initial_rows = 24,
|
|
.initial_columns = 80,
|
|
.refinement_evaluator = nullptr,
|
|
},
|
|
[](::pid_t) -> std::expected<void, std::string> { return {}; }
|
|
);
|
|
REQUIRE(session.has_value());
|
|
auto output = (*session)->wait_read(0, 64U * 1024U, 5'000);
|
|
REQUIRE(output.has_value());
|
|
auto terminal = (*session)->wait();
|
|
REQUIRE(terminal.has_value());
|
|
if (terminal->exit_code != 0) {
|
|
std::fprintf(
|
|
stderr,
|
|
"Glove Linux real native harness probe (%.*s) exited %d: %.*s\n",
|
|
static_cast<int>(harness.runtime_id.size()),
|
|
harness.runtime_id.data(),
|
|
terminal->exit_code.value_or(-1),
|
|
static_cast<int>(output->bytes.size()),
|
|
output->bytes.data()
|
|
);
|
|
std::fprintf(
|
|
stderr,
|
|
" termination=%u peak_memory=%llu peak_pids=%u wall=%llu\n",
|
|
static_cast<unsigned int>(terminal->termination_cause),
|
|
static_cast<unsigned long long>(terminal->observed.peak_memory_bytes),
|
|
terminal->observed.peak_pids,
|
|
static_cast<unsigned long long>(terminal->observed.wall_time_ms)
|
|
);
|
|
return 1;
|
|
}
|
|
REQUIRE(terminal->termination_cause == resource_termination_cause::exited);
|
|
REQUIRE(!output->bytes.empty());
|
|
REQUIRE(!output->truncated);
|
|
REQUIRE(terminal->profile_digest == binding->profile_digest);
|
|
REQUIRE(terminal->library_projections.size() == 1U);
|
|
REQUIRE(terminal->library_projections.front().content_digest == bundle_digest);
|
|
REQUIRE(std::filesystem::is_empty(materialization_root));
|
|
std::fprintf(
|
|
stderr,
|
|
"Glove Linux real native harness probe (%.*s) passed\n",
|
|
static_cast<int>(harness.runtime_id.size()),
|
|
harness.runtime_id.data()
|
|
);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
auto child_release_is_gated_by_durable_callback_test(
|
|
cgroup_v2_root& root, const std::filesystem::path& materialization_root, std::uint64_t page
|
|
) -> int {
|
|
const auto limits = limits_for(page * 16U);
|
|
const auto prof = launch_profile(limits);
|
|
const std::vector argv = {std::string{"/usr/bin/true"}};
|
|
|
|
auto denied_lifecycle =
|
|
make_lifecycle(root, materialization_root, {}, "managed-start-denied", limits);
|
|
REQUIRE(denied_lifecycle.has_value());
|
|
auto denied_binding = glove::container::linux_detail::bind_managed_session(
|
|
prof, argv, **denied_lifecycle, controller_plan_digest
|
|
);
|
|
REQUIRE(denied_binding.has_value());
|
|
bool denied_callback_called = false;
|
|
auto denied = glove::container::linux_detail::exec_managed_session(
|
|
prof,
|
|
argv,
|
|
*denied_binding,
|
|
std::move(*denied_lifecycle),
|
|
[&](::pid_t child) -> std::expected<void, std::string> {
|
|
denied_callback_called = child > 0;
|
|
return std::unexpected(std::string{"durable running append rejected"});
|
|
}
|
|
);
|
|
REQUIRE(!denied.has_value());
|
|
REQUIRE(denied_callback_called);
|
|
REQUIRE(denied.error().find("durable running append rejected") != std::string::npos);
|
|
REQUIRE(std::filesystem::is_empty(materialization_root));
|
|
|
|
auto accepted_lifecycle =
|
|
make_lifecycle(root, materialization_root, {}, "managed-start-accepted", limits);
|
|
REQUIRE(accepted_lifecycle.has_value());
|
|
auto accepted_binding = glove::container::linux_detail::bind_managed_session(
|
|
prof, argv, **accepted_lifecycle, controller_plan_digest
|
|
);
|
|
REQUIRE(accepted_binding.has_value());
|
|
bool accepted_callback_called = false;
|
|
auto accepted = glove::container::linux_detail::exec_managed_session(
|
|
prof,
|
|
argv,
|
|
*accepted_binding,
|
|
std::move(*accepted_lifecycle),
|
|
[&](::pid_t child) -> std::expected<void, std::string> {
|
|
accepted_callback_called = child > 0;
|
|
return {};
|
|
}
|
|
);
|
|
REQUIRE(accepted.has_value());
|
|
REQUIRE(accepted_callback_called);
|
|
REQUIRE(accepted->termination_cause == resource_termination_cause::exited);
|
|
REQUIRE(accepted->exit_code == 0);
|
|
REQUIRE(std::filesystem::is_empty(materialization_root));
|
|
return 0;
|
|
}
|
|
|
|
auto child_disk_exhaustion_test(
|
|
cgroup_v2_root& root, const std::filesystem::path& materialization_root, std::uint64_t page
|
|
) -> int {
|
|
const auto limits = limits_for(page * 16U);
|
|
auto lifecycle = make_lifecycle(root, materialization_root, {}, "managed-disk", limits);
|
|
REQUIRE(lifecycle.has_value());
|
|
const std::string script =
|
|
"dd if=/dev/urandom of=/tmp/pressure bs=4096 count=100000 status=none 2>/dev/null; "
|
|
"sleep 10";
|
|
auto terminal = execute_managed(
|
|
launch_profile(limits), {"/usr/bin/sh", "-c", script}, std::move(*lifecycle)
|
|
);
|
|
REQUIRE(terminal.has_value());
|
|
REQUIRE(terminal->termination_cause == resource_termination_cause::disk_limit);
|
|
REQUIRE(!terminal->exit_code.has_value());
|
|
REQUIRE(terminal->observed.disk_bytes > 0);
|
|
REQUIRE(terminal->observed.disk_bytes <= limits.disk_bytes);
|
|
REQUIRE(terminal->observed.wall_time_ms < limits.wall_time_ms);
|
|
REQUIRE(std::filesystem::is_empty(materialization_root));
|
|
return 0;
|
|
}
|
|
|
|
auto child_output_exhaustion_test(
|
|
cgroup_v2_root& root, const std::filesystem::path& materialization_root, std::uint64_t page
|
|
) -> int {
|
|
auto limits = limits_for(page * 16U);
|
|
limits.terminal_output_bytes = 64;
|
|
auto lifecycle = make_lifecycle(root, materialization_root, {}, "managed-output", limits);
|
|
REQUIRE(lifecycle.has_value());
|
|
const std::string script = "printf '%0128d' 0; sleep 10";
|
|
auto terminal = execute_managed(
|
|
launch_profile(limits), {"/usr/bin/sh", "-c", script}, std::move(*lifecycle)
|
|
);
|
|
REQUIRE(terminal.has_value());
|
|
REQUIRE(terminal->termination_cause == resource_termination_cause::terminal_output_limit);
|
|
REQUIRE(!terminal->exit_code.has_value());
|
|
REQUIRE(terminal->observed.terminal_output_bytes > limits.terminal_output_bytes);
|
|
REQUIRE(terminal->observed.wall_time_ms < limits.wall_time_ms);
|
|
REQUIRE(std::filesystem::is_empty(materialization_root));
|
|
return 0;
|
|
}
|
|
|
|
auto interactive_pty_attach_and_stop_test(
|
|
cgroup_v2_root& root, const std::filesystem::path& materialization_root, std::uint64_t page
|
|
) -> int {
|
|
auto limits = limits_for(page * 16U);
|
|
limits.wall_time_ms = 10'000;
|
|
const auto prof = launch_profile(limits);
|
|
const std::vector argv = {std::string{"/usr/bin/cat"}};
|
|
auto ungated_lifecycle =
|
|
make_lifecycle(root, materialization_root, {}, "managed-pty-ungated", limits);
|
|
REQUIRE(ungated_lifecycle.has_value());
|
|
auto ungated_binding = glove::container::linux_detail::bind_managed_session(
|
|
prof, argv, **ungated_lifecycle, controller_plan_digest
|
|
);
|
|
REQUIRE(ungated_binding.has_value());
|
|
auto ungated = glove::container::linux_detail::start_managed_pty_session(
|
|
prof,
|
|
argv,
|
|
*ungated_binding,
|
|
std::move(*ungated_lifecycle),
|
|
{},
|
|
glove::container::linux_detail::managed_session_start_gate{}
|
|
);
|
|
REQUIRE(!ungated.has_value());
|
|
REQUIRE(ungated.error().find("child-release gate") != std::string::npos);
|
|
REQUIRE(std::filesystem::is_empty(materialization_root));
|
|
|
|
auto denied_lifecycle =
|
|
make_lifecycle(root, materialization_root, {}, "managed-pty-denied", limits);
|
|
REQUIRE(denied_lifecycle.has_value());
|
|
auto denied_binding = glove::container::linux_detail::bind_managed_session(
|
|
prof, argv, **denied_lifecycle, controller_plan_digest
|
|
);
|
|
REQUIRE(denied_binding.has_value());
|
|
auto denied = glove::container::linux_detail::start_managed_pty_session(
|
|
prof,
|
|
argv,
|
|
*denied_binding,
|
|
std::move(*denied_lifecycle),
|
|
{},
|
|
[](::pid_t) -> std::expected<void, std::string> {
|
|
return std::unexpected(std::string{"durable PTY running append rejected"});
|
|
}
|
|
);
|
|
REQUIRE(!denied.has_value());
|
|
REQUIRE(denied.error().find("durable PTY running append rejected") != std::string::npos);
|
|
REQUIRE(std::filesystem::is_empty(materialization_root));
|
|
|
|
auto lifecycle = make_lifecycle(root, materialization_root, {}, "managed-pty", limits);
|
|
REQUIRE(lifecycle.has_value());
|
|
auto binding = glove::container::linux_detail::bind_managed_session(
|
|
prof, argv, **lifecycle, controller_plan_digest
|
|
);
|
|
REQUIRE(binding.has_value());
|
|
bool gate_called = false;
|
|
auto session = glove::container::linux_detail::start_managed_pty_session(
|
|
prof,
|
|
argv,
|
|
*binding,
|
|
std::move(*lifecycle),
|
|
{
|
|
.transcript_bytes = page,
|
|
.max_read_bytes = page,
|
|
.max_input_frame_bytes = page,
|
|
.input_timeout_ms = 1'000,
|
|
.initial_rows = 24,
|
|
.initial_columns = 80,
|
|
.refinement_evaluator = nullptr,
|
|
},
|
|
[&](::pid_t child) -> std::expected<void, std::string> {
|
|
gate_called = child > 0;
|
|
return {};
|
|
}
|
|
);
|
|
REQUIRE(session.has_value());
|
|
REQUIRE(gate_called);
|
|
REQUIRE((*session)->pid() > 0);
|
|
REQUIRE((*session)->write_input("hello from pty\n").has_value());
|
|
auto output = (*session)->wait_read(0, page, 1'000);
|
|
REQUIRE(output.has_value());
|
|
REQUIRE(output->bytes.find("hello from pty") != std::string::npos);
|
|
REQUIRE(!output->truncated);
|
|
REQUIRE((*session)->resize(48, 132).has_value());
|
|
bool stop_gate_called = false;
|
|
const glove::container::linux_detail::managed_session_stop_gate before_stop =
|
|
[&]() -> std::expected<void, std::string> {
|
|
stop_gate_called = true;
|
|
return {};
|
|
};
|
|
REQUIRE((*session)->stop(before_stop).has_value());
|
|
REQUIRE(stop_gate_called);
|
|
REQUIRE((*session)->stop(before_stop).has_value());
|
|
auto terminal = (*session)->wait();
|
|
REQUIRE(terminal.has_value());
|
|
REQUIRE(terminal->termination_cause == resource_termination_cause::signaled);
|
|
REQUIRE(!terminal->exit_code.has_value());
|
|
REQUIRE(terminal->observed.terminal_output_bytes >= output->bytes.size());
|
|
REQUIRE((*session)->wait() == terminal);
|
|
stop_gate_called = false;
|
|
REQUIRE((*session)->stop(before_stop).has_value());
|
|
REQUIRE(!stop_gate_called);
|
|
REQUIRE(std::filesystem::is_empty(materialization_root));
|
|
return 0;
|
|
}
|
|
|
|
auto declarative_refinement_evaluator_test(
|
|
cgroup_v2_root& root, const std::filesystem::path& materialization_root, std::uint64_t page
|
|
) -> int {
|
|
using namespace glove::container;
|
|
const auto limits = limits_for(page * 32U);
|
|
auto lifecycle = make_lifecycle(root, materialization_root, {}, "managed-refinement", limits);
|
|
REQUIRE(lifecycle.has_value());
|
|
refinement_execution_binding execution;
|
|
execution.schema_version = 1;
|
|
execution.variant = refinement_variant::candidate;
|
|
execution.fixture = {"fixture", std::string(64, '0'), "fixtures"};
|
|
execution.base = {"base", std::string(64, 'b'), "skills"};
|
|
execution.candidate = {"candidate", std::string(64, 'c'), "skills"};
|
|
execution.matched_context_digest = std::string(64, '0');
|
|
execution.plan_context_digest = std::string(64, 'e');
|
|
refinement_fixture_manifest fixture{
|
|
.schema = std::string{refinement_fixture_schema},
|
|
.evaluation_run_id = "run-managed",
|
|
.pair_id = "pair-managed",
|
|
.session_id = "managed-refinement",
|
|
.variant = refinement_variant::candidate,
|
|
.proposal_digest = std::string(64, 'a'),
|
|
.base_projection_digest = execution.base.content_digest,
|
|
.candidate_projection_digest = execution.candidate.content_digest,
|
|
.fixture_id = "prompt-managed",
|
|
.fixture_digest = std::string(64, 'd'),
|
|
.dataset_ref = "fixture:managed",
|
|
.dataset_fingerprint = std::string(64, 'f'),
|
|
.seed = 42,
|
|
.model =
|
|
{
|
|
.provider = "test",
|
|
.model_id = "synthetic",
|
|
.model_family = "synthetic",
|
|
.model_revision = std::nullopt,
|
|
.tier = "local",
|
|
.normalizer_version = 1,
|
|
},
|
|
.harness = "synthetic",
|
|
.module = std::nullopt,
|
|
.skill_projection_id = execution.candidate.projection_id,
|
|
.skill_projection_digest = execution.candidate.content_digest,
|
|
.matched_context_digest = std::string(64, '0'),
|
|
.assertions = {
|
|
.expected_termination = resource_termination_cause::exited,
|
|
.expected_exit_code = 0,
|
|
.required_transcript_literals = {"alpha beta", "done"},
|
|
.forbidden_transcript_literals = {"forbidden"},
|
|
.max_latency_ms = 2'000,
|
|
},
|
|
};
|
|
fixture.matched_context_digest =
|
|
refinement_fixture_context_digest(fixture, execution.plan_context_digest).value();
|
|
execution.matched_context_digest = fixture.matched_context_digest;
|
|
const auto encoded = canonical_refinement_fixture_bytes(fixture).value();
|
|
const auto encoded_bytes = std::span<const unsigned char>{
|
|
reinterpret_cast<const unsigned char*>(encoded.data()), encoded.size()
|
|
};
|
|
execution.fixture.content_digest = sha256_hex(encoded_bytes).value();
|
|
auto evaluator =
|
|
refinement_transcript_evaluator::create(encoded_bytes, fixture.session_id, execution);
|
|
REQUIRE(evaluator.has_value());
|
|
|
|
const auto prof = launch_profile(limits);
|
|
const std::vector argv = {
|
|
std::string{"/usr/bin/sh"},
|
|
std::string{"-c"},
|
|
std::string{"printf 'alpha '; printf 'beta '; printf done"},
|
|
};
|
|
auto binding = glove::container::linux_detail::bind_managed_session(
|
|
prof, argv, **lifecycle, controller_plan_digest
|
|
);
|
|
REQUIRE(binding.has_value());
|
|
auto session = glove::container::linux_detail::start_managed_pty_session(
|
|
prof,
|
|
argv,
|
|
*binding,
|
|
std::move(*lifecycle),
|
|
{
|
|
.transcript_bytes = page,
|
|
.max_read_bytes = page,
|
|
.max_input_frame_bytes = page,
|
|
.input_timeout_ms = 1'000,
|
|
.initial_rows = 24,
|
|
.initial_columns = 80,
|
|
.refinement_evaluator = *evaluator,
|
|
},
|
|
[](::pid_t) -> std::expected<void, std::string> { return {}; }
|
|
);
|
|
REQUIRE(session.has_value());
|
|
auto resource = (*session)->wait();
|
|
REQUIRE(resource.has_value());
|
|
auto refinement = (*session)->wait_refinement();
|
|
REQUIRE(refinement.has_value());
|
|
REQUIRE(refinement->has_value());
|
|
REQUIRE((*refinement)->evaluated_outcome->metrics.at("passed") == 1);
|
|
REQUIRE((*refinement)->transcript.byte_count == 15);
|
|
REQUIRE(std::filesystem::is_empty(materialization_root));
|
|
return 0;
|
|
}
|
|
|
|
auto run() -> int {
|
|
temporary_tree tree;
|
|
REQUIRE(!tree.root().empty());
|
|
const auto materialization_root = tree.root() / "materializations";
|
|
const auto source = tree.root() / "source";
|
|
const auto file_source = tree.root() / "single-source.txt";
|
|
const auto reference_source = tree.root() / "reference";
|
|
const auto library_root = tree.root() / "library";
|
|
REQUIRE(std::filesystem::create_directory(materialization_root));
|
|
REQUIRE(::chmod(materialization_root.c_str(), 0700) == 0);
|
|
REQUIRE(std::filesystem::create_directory(source));
|
|
REQUIRE(std::filesystem::create_directory(reference_source));
|
|
REQUIRE(std::filesystem::create_directory(library_root));
|
|
REQUIRE(::chmod(library_root.c_str(), 0700) == 0);
|
|
{
|
|
std::ofstream input{source / "input.txt"};
|
|
input << "seed\n";
|
|
std::ofstream single{file_source};
|
|
single << "standalone\n";
|
|
std::ofstream reference{reference_source / "reference.txt"};
|
|
reference << "trusted\n";
|
|
}
|
|
const long page_size = ::sysconf(_SC_PAGESIZE);
|
|
REQUIRE(page_size > 0);
|
|
const auto page = static_cast<std::uint64_t>(page_size);
|
|
|
|
auto root = cgroup_v2_root::prepare_for_current_process();
|
|
if (!root) {
|
|
std::fprintf(stderr, "managed-session topology unavailable: %s\n", root.error().c_str());
|
|
return 77;
|
|
}
|
|
REQUIRE(
|
|
mounted_copy_is_isolated_test(
|
|
*root, materialization_root, source, file_source, reference_source, page
|
|
) == 0
|
|
);
|
|
REQUIRE(managed_policy_rejection_test(*root, materialization_root, source, page) == 0);
|
|
REQUIRE(codex_runtime_context_test(*root, materialization_root, library_root, page) == 0);
|
|
REQUIRE(real_native_harness_test(*root, materialization_root, library_root, page) == 0);
|
|
REQUIRE(
|
|
child_release_is_gated_by_durable_callback_test(*root, materialization_root, page) == 0
|
|
);
|
|
REQUIRE(child_disk_exhaustion_test(*root, materialization_root, page) == 0);
|
|
REQUIRE(child_output_exhaustion_test(*root, materialization_root, page) == 0);
|
|
REQUIRE(interactive_pty_attach_and_stop_test(*root, materialization_root, page) == 0);
|
|
REQUIRE(declarative_refinement_evaluator_test(*root, materialization_root, page) == 0);
|
|
return 0;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
auto main() -> int {
|
|
return run();
|
|
}
|