Introduce the framed guest_channel_transport with shared deadlines and std::jthread ownership, metadata-only retired-schema quarantine with fail-closed integrity, stateless frozen-catalog validators with capability pinned to zero, and the generic local-service proxy with descriptor-pinned endpoints, alias-only guest sockets, audit-gated delivery, and a Linux managed runtime capability derived only from a registry-bound operational factory. Sage vocabulary is confined to src/adapters/sage.
602 lines
26 KiB
C++
602 lines
26 KiB
C++
#include "glove/control/receipt_audit_protocol.hpp"
|
|
#include "glove/control/session_registry.hpp"
|
|
#include "glove/supervisor/path_alias.hpp"
|
|
#include "glove/supervisor/session_plan.hpp"
|
|
|
|
#include "apple_container_session_runtime.hpp"
|
|
|
|
#include <arpa/inet.h>
|
|
#include <netinet/in.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
|
|
#include <chrono>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <memory>
|
|
#include <optional>
|
|
#include <string>
|
|
#include <system_error>
|
|
#include <thread>
|
|
|
|
namespace {
|
|
|
|
#define REQUIRE(cond) \
|
|
do { \
|
|
if (!(cond)) { \
|
|
std::fprintf(stderr, "REQUIRE failed: %s @ %s:%d\n", #cond, __FILE__, __LINE__); \
|
|
return 1; \
|
|
} \
|
|
} while (0)
|
|
|
|
constexpr std::string_view controller_digest =
|
|
"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc";
|
|
constexpr std::string_view audit_key =
|
|
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f";
|
|
constexpr std::string_view bootstrap_secret =
|
|
"dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd";
|
|
constexpr std::uint64_t mode_a_cpu_time_ms = 60'000;
|
|
constexpr std::uint64_t mode_a_memory_bytes = 1'073'741'824;
|
|
constexpr std::uint32_t mode_a_pids = 256;
|
|
constexpr std::uint64_t mode_a_wall_time_ms = 120'000;
|
|
constexpr std::uint64_t mode_a_disk_bytes = 2'147'483'648;
|
|
constexpr std::uint64_t mode_a_terminal_output_bytes = 16'777'216;
|
|
constexpr std::uint64_t mode_a_plan_ttl_ms = 300'000;
|
|
constexpr std::string_view credential_guest_target = "/home/agent/.pi/test-auth.json";
|
|
|
|
class temporary_directory {
|
|
public:
|
|
temporary_directory() {
|
|
std::string pattern = "/tmp/glove-apple-runtime-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 error;
|
|
std::filesystem::remove_all(root_, error);
|
|
}
|
|
|
|
[[nodiscard]] auto root() const -> const std::filesystem::path& { return root_; }
|
|
|
|
private:
|
|
std::filesystem::path root_;
|
|
};
|
|
|
|
class origin_server {
|
|
public:
|
|
origin_server() = default;
|
|
origin_server(const origin_server&) = delete;
|
|
auto operator=(const origin_server&) -> origin_server& = delete;
|
|
|
|
~origin_server() {
|
|
if (descriptor_ >= 0) {
|
|
::shutdown(descriptor_, SHUT_RDWR);
|
|
::close(descriptor_);
|
|
}
|
|
if (thread_.joinable()) {
|
|
thread_.join();
|
|
}
|
|
}
|
|
|
|
auto start() -> bool {
|
|
descriptor_ = ::socket(AF_INET, SOCK_STREAM, 0);
|
|
::sockaddr_in address{};
|
|
address.sin_family = AF_INET;
|
|
address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
|
if (descriptor_ < 0 ||
|
|
::bind(descriptor_, reinterpret_cast<::sockaddr*>(&address), sizeof(address)) != 0 ||
|
|
::listen(descriptor_, 1) != 0) {
|
|
return false;
|
|
}
|
|
::sockaddr_in bound{};
|
|
::socklen_t size = sizeof(bound);
|
|
if (::getsockname(descriptor_, reinterpret_cast<::sockaddr*>(&bound), &size) != 0) {
|
|
return false;
|
|
}
|
|
port_ = ntohs(bound.sin_port);
|
|
thread_ = std::thread{[this] {
|
|
const int connection = ::accept(descriptor_, nullptr, nullptr);
|
|
if (connection >= 0) {
|
|
constexpr std::string_view marker = "ORIGIN_HELLO";
|
|
static_cast<void>(::write(connection, marker.data(), marker.size()));
|
|
::close(connection);
|
|
}
|
|
}};
|
|
return true;
|
|
}
|
|
|
|
[[nodiscard]] auto port() const noexcept -> std::uint16_t { return port_; }
|
|
|
|
private:
|
|
int descriptor_ = -1;
|
|
std::uint16_t port_ = 0;
|
|
std::thread thread_;
|
|
};
|
|
|
|
auto epoch_ms() -> std::uint64_t {
|
|
return static_cast<std::uint64_t>(std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
std::chrono::system_clock::now().time_since_epoch()
|
|
)
|
|
.count());
|
|
}
|
|
|
|
auto make_request(
|
|
std::string_view id,
|
|
std::string_view method,
|
|
std::string_view payload,
|
|
std::optional<std::string_view> idempotency_key,
|
|
std::uint64_t deadline_ms
|
|
) -> std::string {
|
|
std::string request =
|
|
"{\"jsonrpc\":\"2.0\",\"id\":\"" + std::string{id} + "\",\"method\":\"" +
|
|
std::string{method} + "\",\"params\":{\"schema_version\":1,\"bootstrap_secret\":\"" +
|
|
std::string{bootstrap_secret} + "\",\"deadline_ms\":" + std::to_string(deadline_ms);
|
|
if (idempotency_key) {
|
|
request += ",\"idempotency_key\":\"" + std::string{*idempotency_key} + "\"";
|
|
}
|
|
request += ",\"payload\":" + std::string{payload} + "}}";
|
|
return request;
|
|
}
|
|
|
|
auto run() -> int {
|
|
const auto parsed = glove::control::apple_detail::parse_apple_container_stats(
|
|
R"([{"id":"apple-unit","memoryUsageBytes":4096,"memoryLimitBytes":8192,"cpuUsageUsec":2500,"networkRxBytes":0,"networkTxBytes":0,"blockReadBytes":64,"blockWriteBytes":128,"numProcesses":3}])",
|
|
"apple-unit"
|
|
);
|
|
REQUIRE(parsed.has_value());
|
|
REQUIRE(parsed->cpu_usage_usec == 2'500U);
|
|
REQUIRE(parsed->memory_usage_bytes == 4'096U);
|
|
REQUIRE(parsed->memory_limit_bytes == 8'192U);
|
|
REQUIRE(parsed->num_processes == 3U);
|
|
REQUIRE(parsed->block_write_bytes == 128U);
|
|
REQUIRE(!glove::control::apple_detail::parse_apple_container_stats("[]", "apple-unit"));
|
|
REQUIRE(!glove::control::apple_detail::parse_apple_container_stats(
|
|
R"([{"id":"other","memoryUsageBytes":1,"memoryLimitBytes":2,"cpuUsageUsec":3,"networkRxBytes":0,"networkTxBytes":0,"blockReadBytes":0,"blockWriteBytes":0,"numProcesses":1}])",
|
|
"apple-unit"
|
|
));
|
|
const auto tmpfs = glove::control::apple_detail::apple_container_tmpfs_sizes(101U);
|
|
REQUIRE(tmpfs.has_value());
|
|
REQUIRE(tmpfs->first == 50U);
|
|
REQUIRE(tmpfs->second == 51U);
|
|
REQUIRE(tmpfs->first + tmpfs->second == 101U);
|
|
REQUIRE(!glove::control::apple_detail::apple_container_tmpfs_sizes(1U));
|
|
|
|
const char* image = std::getenv("GLOVE_APPLE_CONTAINER_IMAGE");
|
|
const char* image_digest = std::getenv("GLOVE_APPLE_CONTAINER_IMAGE_DIGEST");
|
|
if (image == nullptr || *image == '\0' || image_digest == nullptr || *image_digest == '\0') {
|
|
std::fprintf(
|
|
stderr,
|
|
"SKIP: set GLOVE_APPLE_CONTAINER_IMAGE and "
|
|
"GLOVE_APPLE_CONTAINER_IMAGE_DIGEST for the live Apple lane\n"
|
|
);
|
|
return 77;
|
|
}
|
|
const char* configured_cli = std::getenv("GLOVE_APPLE_CONTAINER_CLI");
|
|
const char* closure_digest = std::getenv("GLOVE_APPLE_CONTAINER_HARNESS_CLOSURE_DIGEST");
|
|
const bool managed_closure = closure_digest != nullptr && *closure_digest != '\0';
|
|
const bool egress_probe =
|
|
managed_closure && std::getenv("GLOVE_APPLE_CONTAINER_EGRESS_PROBE") != nullptr;
|
|
const bool secret_lane = managed_closure && !egress_probe &&
|
|
std::getenv("GLOVE_APPLE_CONTAINER_SECRET_LEASE") != nullptr;
|
|
origin_server origin;
|
|
if (egress_probe) {
|
|
REQUIRE(origin.start());
|
|
}
|
|
const std::filesystem::path cli = configured_cli != nullptr && *configured_cli != '\0'
|
|
? configured_cli
|
|
: "/usr/local/bin/container";
|
|
|
|
temporary_directory temp;
|
|
REQUIRE(!temp.root().empty());
|
|
const auto session_root = temp.root() / "sessions";
|
|
REQUIRE(std::filesystem::create_directory(session_root));
|
|
REQUIRE(::chmod(session_root.c_str(), 0700) == 0);
|
|
const auto unused_source = temp.root() / "unused-source";
|
|
REQUIRE(std::filesystem::create_directory(unused_source));
|
|
const auto credential_source = temp.root() / "credential.json";
|
|
constexpr std::string_view secret_sentinel = "apple-secret-sentinel";
|
|
if (secret_lane) {
|
|
std::ofstream output{credential_source, std::ios::binary};
|
|
output << "{\"token\":\"" << secret_sentinel << "\"}\n";
|
|
output.close();
|
|
REQUIRE(::chmod(credential_source.c_str(), 0600) == 0);
|
|
struct stat metadata{};
|
|
REQUIRE(::stat(credential_source.c_str(), &metadata) == 0);
|
|
REQUIRE((metadata.st_mode & 0777) == 0600);
|
|
REQUIRE(metadata.st_uid == ::geteuid());
|
|
}
|
|
|
|
const glove::supervisor::runtime_launch_template launch{
|
|
.runtime_discovery = {},
|
|
.executable_path = managed_closure ? "/image/managed/adapter" : "/bin/bash",
|
|
.executable_search_paths = {},
|
|
.arguments =
|
|
egress_probe
|
|
? std::vector<std::string>{"localhost", std::to_string(origin.port())}
|
|
: managed_closure ? std::vector<std::string>{
|
|
secret_lane ? "--help" : "--version",
|
|
}
|
|
: std::vector<std::string>{
|
|
"-lc",
|
|
"printf 'APPLE_READY\\n'; IFS= read -r line; "
|
|
"printf 'APPLE_ECHO:%s\\n' \"$line\"",
|
|
},
|
|
.environment = {"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"},
|
|
.read_only_paths = {},
|
|
};
|
|
const auto adapter_digest = glove::supervisor::runtime_launch_template_digest(launch);
|
|
REQUIRE(adapter_digest.has_value());
|
|
auto paths = glove::supervisor::path_alias_registry::build({
|
|
{
|
|
.alias = "unused",
|
|
.host_path = std::filesystem::canonical(unused_source).string(),
|
|
.target_path = "/unused",
|
|
.max_ttl_secs = 120,
|
|
.access = {
|
|
{
|
|
.access = glove::supervisor::path_access::ephemeral_write,
|
|
.materialization = glove::supervisor::path_materialization::copy,
|
|
.create_policy = glove::supervisor::path_create_policy::empty_directory,
|
|
.cleanup_policy = glove::supervisor::path_cleanup_policy::remove,
|
|
.max_bytes = 1'048'576,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
REQUIRE(paths.has_value());
|
|
auto validator = glove::supervisor::session_plan_validator::build(
|
|
{
|
|
.revision = 1,
|
|
.max_plan_ttl_ms = mode_a_plan_ttl_ms,
|
|
.runtime_templates =
|
|
{
|
|
{
|
|
.runtime_template_id = "apple-probe",
|
|
.runtime_id = egress_probe
|
|
? "glove-egress-probe"
|
|
: managed_closure ? "pi" : "probe",
|
|
.adapter_command_digest = *adapter_digest,
|
|
.backend = glove::supervisor::sandbox_backend::apple_container,
|
|
.allowed_path_aliases = {},
|
|
.allowed_projection_destinations = {},
|
|
.launch = launch,
|
|
.adoption = std::nullopt,
|
|
},
|
|
},
|
|
.library_projection_destinations = {},
|
|
.resource_profiles =
|
|
{
|
|
{
|
|
.cpu_time_ms = mode_a_cpu_time_ms,
|
|
.memory_bytes = mode_a_memory_bytes,
|
|
.pids = mode_a_pids,
|
|
.wall_time_ms = mode_a_wall_time_ms,
|
|
.disk_bytes = mode_a_disk_bytes,
|
|
.terminal_output_bytes = mode_a_terminal_output_bytes,
|
|
},
|
|
},
|
|
.egress_policy_ids = egress_probe
|
|
? std::vector<std::string>{"test-online"}
|
|
: std::vector<std::string>{"no-network"},
|
|
.tool_policy_ids = {"sage-readonly"},
|
|
.secret_handles =
|
|
secret_lane ? std::vector<std::string>{"test-auth"} : std::vector<std::string>{},
|
|
.egress_policies =
|
|
egress_probe
|
|
? std::vector<glove::supervisor::egress_policy>{
|
|
{
|
|
.policy_id = "test-online",
|
|
.targets =
|
|
{
|
|
{
|
|
.host = "localhost",
|
|
.port = origin.port(),
|
|
.allow_private = true,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
: std::vector<glove::supervisor::egress_policy>{},
|
|
.secret_mounts =
|
|
secret_lane
|
|
? std::vector<glove::supervisor::secret_mount_policy>{
|
|
{
|
|
.handle = "test-auth",
|
|
.runtime_id = "pi",
|
|
.source_path = credential_source.string(),
|
|
.target_path = std::string{credential_guest_target},
|
|
},
|
|
}
|
|
: std::vector<glove::supervisor::secret_mount_policy>{},
|
|
},
|
|
std::move(*paths)
|
|
);
|
|
if (!validator) {
|
|
std::fprintf(stderr, "validator build failed: %s\n", validator.error().c_str());
|
|
}
|
|
REQUIRE(validator.has_value());
|
|
auto shared_validator =
|
|
std::make_shared<const glove::supervisor::session_plan_validator>(std::move(*validator));
|
|
const auto registry_path = temp.root() / "sessions.journal";
|
|
auto registry =
|
|
glove::control::session_registry::open_or_create(registry_path, shared_validator);
|
|
REQUIRE(registry.has_value());
|
|
auto shared_registry = std::shared_ptr<glove::control::session_registry>{std::move(*registry)};
|
|
|
|
const auto audit_key_path = temp.root() / "audit.key";
|
|
{
|
|
std::ofstream output{audit_key_path, std::ios::binary};
|
|
output << audit_key << '\n';
|
|
}
|
|
REQUIRE(::chmod(audit_key_path.c_str(), 0600) == 0);
|
|
auto producer = glove::container::receipt_audit_producer::initialize({
|
|
.key_path = audit_key_path,
|
|
.journal_path = temp.root() / "receipts.journal",
|
|
});
|
|
REQUIRE(producer.has_value());
|
|
REQUIRE((*producer)->acknowledge_bootstrap((*producer)->anchor()).has_value());
|
|
|
|
auto runtime = glove::control::apple_detail::apple_container_session_runtime::create(
|
|
*shared_registry,
|
|
{
|
|
.container_cli = cli,
|
|
.image_reference = image,
|
|
.image_digest = image_digest,
|
|
.harness_closure_digest =
|
|
managed_closure ? std::optional<std::string>{closure_digest} : std::nullopt,
|
|
.sage_guest = std::nullopt,
|
|
.egress_audit = glove::audit::make_memory_sink(),
|
|
.session_root = session_root,
|
|
}
|
|
);
|
|
if (!runtime) {
|
|
std::fprintf(stderr, "runtime build failed: %s\n", runtime.error().c_str());
|
|
}
|
|
REQUIRE(runtime.has_value());
|
|
auto shared_runtime = std::shared_ptr<glove::control::session_runtime>{std::move(*runtime)};
|
|
if (!shared_runtime->lifecycle_operational()) {
|
|
REQUIRE(!shared_runtime->resource_capabilities().complete());
|
|
REQUIRE(shared_runtime->agent_runtime_adapter_schema_version() == 0);
|
|
REQUIRE(shared_runtime->managed_runtime_ids().empty());
|
|
// Lifecycle-disabled denial: every session control method must refuse
|
|
// before any side effect, and the guest observation service (which
|
|
// only constructs inside a lifecycle-gated start) never materializes.
|
|
auto gated_protocol = glove::control::receipt_audit_protocol::create(
|
|
bootstrap_secret,
|
|
*producer,
|
|
shared_validator,
|
|
shared_registry,
|
|
shared_runtime,
|
|
{},
|
|
session_root.string()
|
|
);
|
|
REQUIRE(gated_protocol.has_value());
|
|
const auto gated_now_ms = epoch_ms();
|
|
auto gated_capabilities = (*gated_protocol)
|
|
->handle_frame(
|
|
make_request(
|
|
"gated-capabilities",
|
|
"capabilities",
|
|
"null",
|
|
std::nullopt,
|
|
gated_now_ms + 1'000U
|
|
),
|
|
gated_now_ms
|
|
);
|
|
REQUIRE(gated_capabilities.has_value());
|
|
REQUIRE(
|
|
gated_capabilities->find("\"observation_intent_channel_schema_version\":0") !=
|
|
std::string::npos
|
|
);
|
|
const auto gated_start_session = std::string{
|
|
R"({"authorization":{"schema_version":1,"authorization_id":"gated-approval",)"
|
|
R"("session_id":"gated-session","controller_plan_digest":")" +
|
|
std::string(64, 'a') + R"(","plan_content_digest":")" + std::string(64, 'b') +
|
|
R"(","approved_at_ms":1,"expires_at_ms":2}})"
|
|
};
|
|
const auto records_before_gated_start = shared_registry->record_count();
|
|
auto gated_frame = (*gated_protocol)
|
|
->handle_frame(
|
|
make_request(
|
|
"gated-start",
|
|
"start_session",
|
|
gated_start_session,
|
|
"gated-start",
|
|
gated_now_ms + 1'000U
|
|
),
|
|
gated_now_ms
|
|
)
|
|
.value_or(std::string{});
|
|
REQUIRE(gated_frame.find("\"method_not_found\"") != std::string::npos);
|
|
REQUIRE(shared_registry->record_count() == records_before_gated_start);
|
|
auto gated_stop_frame = (*gated_protocol)
|
|
->handle_frame(
|
|
make_request(
|
|
"gated-stop",
|
|
"stop_session",
|
|
"{\"session_id\":\"gated-session\"}",
|
|
"gated-stop",
|
|
gated_now_ms + 2'000U
|
|
),
|
|
gated_now_ms
|
|
)
|
|
.value_or(std::string{});
|
|
REQUIRE(gated_stop_frame.find("\"method_not_found\"") != std::string::npos);
|
|
REQUIRE(shared_registry->record_count() == records_before_gated_start);
|
|
std::fprintf(
|
|
stderr,
|
|
"Apple managed lifecycle remains gated until the six-limit receipt contract is "
|
|
"complete\n"
|
|
);
|
|
return 0;
|
|
}
|
|
REQUIRE(shared_runtime->resource_capabilities().complete());
|
|
if (managed_closure) {
|
|
const std::vector<std::string> expected_runtime_ids{
|
|
"claude-code", "pi", "copilot", "opencode"
|
|
};
|
|
REQUIRE(shared_runtime->managed_runtime_ids() == expected_runtime_ids);
|
|
} else {
|
|
REQUIRE(shared_runtime->managed_runtime_ids().empty());
|
|
}
|
|
|
|
auto protocol = glove::control::receipt_audit_protocol::create(
|
|
bootstrap_secret,
|
|
*producer,
|
|
shared_validator,
|
|
shared_registry,
|
|
shared_runtime,
|
|
{},
|
|
session_root.string()
|
|
);
|
|
REQUIRE(protocol.has_value());
|
|
const auto now_ms = epoch_ms();
|
|
const auto plan =
|
|
std::string{R"({"schema_version":1,"runtime_id":")"} +
|
|
(egress_probe ? "glove-egress-probe"
|
|
: managed_closure ? "pi"
|
|
: "probe") +
|
|
R"(","runtime_template_id":"apple-probe","adapter_command_digest":")" + *adapter_digest +
|
|
R"(","sandbox_backend":"apple_container","egress_policy_id":")" +
|
|
(egress_probe ? "test-online" : "no-network") +
|
|
R"(","tool_policy_id":"sage-readonly","path_grants":[],"library_projections":[],"secret_handles":)" +
|
|
(secret_lane ? R"(["test-auth"])" : "[]") + R"(,"limits":{"cpu_time_ms":)" +
|
|
std::to_string(mode_a_cpu_time_ms) + R"(,"memory_bytes":)" +
|
|
std::to_string(mode_a_memory_bytes) + R"(,"pids":)" + std::to_string(mode_a_pids) +
|
|
R"(,"wall_time_ms":)" + std::to_string(mode_a_wall_time_ms) + R"(,"disk_bytes":)" +
|
|
std::to_string(mode_a_disk_bytes) + R"(,"terminal_output_bytes":)" +
|
|
std::to_string(mode_a_terminal_output_bytes) + R"(},"policy_revision":1,"expires_at_ms":)" +
|
|
std::to_string(now_ms + mode_a_plan_ttl_ms) + "}";
|
|
REQUIRE(plan.find(secret_sentinel) == std::string::npos);
|
|
REQUIRE(plan.find(credential_source.string()) == std::string::npos);
|
|
REQUIRE(plan.find(credential_guest_target) == std::string::npos);
|
|
auto created =
|
|
shared_registry->create("apple-live", controller_digest, plan, "apple-create", now_ms);
|
|
REQUIRE(created.has_value());
|
|
const glove::control::session_start_authorization authorization{
|
|
.schema_version = 1,
|
|
.authorization_id = "apple-approval",
|
|
.session_id = created->session_id,
|
|
.controller_plan_digest = created->controller_plan_digest,
|
|
.plan_content_digest = created->plan_content_digest,
|
|
.approved_at_ms = now_ms + 1U,
|
|
.expires_at_ms = now_ms + 30'000U,
|
|
};
|
|
const auto start_payload =
|
|
"{\"authorization\":{\"schema_version\":1,\"authorization_id\":\"" +
|
|
authorization.authorization_id + "\",\"session_id\":\"" + authorization.session_id +
|
|
"\",\"controller_plan_digest\":\"" + authorization.controller_plan_digest +
|
|
"\",\"plan_content_digest\":\"" + authorization.plan_content_digest +
|
|
"\",\"approved_at_ms\":" + std::to_string(authorization.approved_at_ms) +
|
|
",\"expires_at_ms\":" + std::to_string(authorization.expires_at_ms) + "}}";
|
|
auto start_frame = (*protocol)->handle_frame(
|
|
make_request(
|
|
"start-apple-live", "start_session", start_payload, "apple-live", now_ms + 10'000U
|
|
),
|
|
now_ms + 2U
|
|
);
|
|
if (!start_frame) {
|
|
std::fprintf(stderr, "Apple protocol start failed: %s\n", start_frame.error().c_str());
|
|
}
|
|
REQUIRE(start_frame.has_value());
|
|
REQUIRE(start_frame->find("\"result\":") != std::string::npos);
|
|
REQUIRE(start_frame->find("\"error\":") == std::string::npos);
|
|
auto started = shared_registry->status("apple-live");
|
|
REQUIRE(started.has_value());
|
|
REQUIRE(started->state == glove::control::session_state::running);
|
|
|
|
std::uint64_t cursor = 0;
|
|
std::string transcript;
|
|
const std::string expected_ready = egress_probe ? "GLOVE_EGRESS_OK"
|
|
: secret_lane ? "Usage:"
|
|
: managed_closure ? "0.84.1"
|
|
: "APPLE_READY";
|
|
for (int attempt = 0; attempt < 20 && transcript.find(expected_ready) == std::string::npos;
|
|
++attempt) {
|
|
auto page = shared_runtime->wait_read("apple-live", cursor, 65'536, 1'000);
|
|
if (!page) {
|
|
continue;
|
|
}
|
|
cursor = page->next_cursor;
|
|
transcript += page->bytes;
|
|
}
|
|
REQUIRE(transcript.find(secret_sentinel) == std::string::npos);
|
|
REQUIRE(transcript.find(credential_source.string()) == std::string::npos);
|
|
if (!managed_closure) {
|
|
REQUIRE(transcript.find(expected_ready) != std::string::npos);
|
|
REQUIRE(shared_runtime->resize("apple-live", 40, 120).has_value());
|
|
REQUIRE(shared_runtime->write_input("apple-live", "managed-input\n").has_value());
|
|
for (int attempt = 0;
|
|
attempt < 20 && transcript.find("APPLE_ECHO:managed-input") == std::string::npos;
|
|
++attempt) {
|
|
auto page = shared_runtime->wait_read("apple-live", cursor, 65'536, 1'000);
|
|
if (!page) {
|
|
continue;
|
|
}
|
|
cursor = page->next_cursor;
|
|
transcript += page->bytes;
|
|
}
|
|
REQUIRE(transcript.find("APPLE_ECHO:managed-input") != std::string::npos);
|
|
}
|
|
auto terminal = shared_runtime->wait("apple-live");
|
|
if (!terminal) {
|
|
std::fprintf(stderr, "Apple runtime wait failed: %s\n", terminal.error().c_str());
|
|
}
|
|
REQUIRE(terminal.has_value());
|
|
if (!secret_lane && transcript.find(expected_ready) == std::string::npos) {
|
|
std::fprintf(
|
|
stderr,
|
|
"Apple runtime transcript missing %s; exit code: %d; transcript: %s\n",
|
|
expected_ready.c_str(),
|
|
terminal->exit_code.value_or(-1),
|
|
transcript.c_str()
|
|
);
|
|
}
|
|
if (!secret_lane) {
|
|
REQUIRE(transcript.find(expected_ready) != std::string::npos);
|
|
}
|
|
REQUIRE(terminal->session.state == glove::control::session_state::exited);
|
|
REQUIRE(terminal->exit_code == 0);
|
|
REQUIRE(terminal->termination_cause == glove::container::resource_termination_cause::exited);
|
|
REQUIRE(shared_registry->managed_recovery_candidates()->empty());
|
|
REQUIRE(shared_runtime->cleanup("apple-live").has_value());
|
|
REQUIRE(shared_runtime->list()->empty());
|
|
if (secret_lane) {
|
|
const auto lease_root = session_root / ".credential-leases";
|
|
REQUIRE(std::filesystem::is_empty(lease_root));
|
|
REQUIRE(std::filesystem::exists(credential_source));
|
|
std::ifstream source{credential_source, std::ios::binary};
|
|
std::string source_contents{
|
|
std::istreambuf_iterator<char>{source}, std::istreambuf_iterator<char>{}
|
|
};
|
|
REQUIRE(source_contents == "{\"token\":\"apple-secret-sentinel\"}\n");
|
|
struct stat metadata{};
|
|
REQUIRE(::stat(credential_source.c_str(), &metadata) == 0);
|
|
REQUIRE((metadata.st_mode & 0777) == 0600);
|
|
REQUIRE(metadata.st_uid == ::geteuid());
|
|
|
|
for (const auto& journal_path : {temp.root() / "receipts.journal", registry_path}) {
|
|
std::ifstream journal{journal_path, std::ios::binary};
|
|
std::string journal_contents{
|
|
std::istreambuf_iterator<char>{journal}, std::istreambuf_iterator<char>{}
|
|
};
|
|
REQUIRE(journal_contents.find(secret_sentinel) == std::string::npos);
|
|
REQUIRE(journal_contents.find(credential_source.string()) == std::string::npos);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
int main() {
|
|
return run();
|
|
}
|