223 lines
7.2 KiB
C++
223 lines
7.2 KiB
C++
#pragma once
|
|
|
|
#include <chrono>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <functional>
|
|
#include <memory>
|
|
#include <optional>
|
|
#include <string>
|
|
#include <unordered_set>
|
|
#include <vector>
|
|
|
|
#include "manager/safety_manager/include/command_ledger.h"
|
|
#include "manager/safety_manager/include/device_safety_endpoint.h"
|
|
#include "manager/safety_manager/include/safety_participant.h"
|
|
|
|
namespace cmvr::safety {
|
|
|
|
struct SafetyManagerConfig {
|
|
EnforcementMode enforcement_mode{EnforcementMode::Shadow};
|
|
std::unordered_set<std::string> enforced_device_ids;
|
|
std::chrono::milliseconds stop_all_timeout{15000};
|
|
std::chrono::milliseconds recovery_timeout{10000};
|
|
CommandLedger::Config command_ledger;
|
|
std::size_t event_history_capacity{2048};
|
|
bool fail_startup_on_missing_control_capability{false};
|
|
};
|
|
|
|
struct AdmissionResult {
|
|
AdmissionDecision decision;
|
|
std::optional<AdmissionPermit> permit;
|
|
};
|
|
|
|
struct StartupCoverageIssue {
|
|
std::string target_id;
|
|
SafetyReason reason{SafetyReason::None};
|
|
std::string detail;
|
|
};
|
|
|
|
struct StartupCoverageResult {
|
|
bool ready{false};
|
|
std::vector<StartupCoverageIssue> issues;
|
|
};
|
|
|
|
struct DeviceSafetyStateView {
|
|
DeviceSafetyDescriptor descriptor;
|
|
SafetySnapshotView safety;
|
|
device::ManagedDeviceState lifecycle{
|
|
device::ManagedDeviceState::Unknown};
|
|
device::DeviceHealthSnapshot health;
|
|
DeviceAdmissionState admission_state{DeviceAdmissionState::Observing};
|
|
std::vector<SafetyBlocker> blockers;
|
|
};
|
|
|
|
struct ParticipantResultView {
|
|
bool recorded{false};
|
|
bool success{false};
|
|
SafetyReason reason{SafetyReason::None};
|
|
std::string detail;
|
|
};
|
|
|
|
struct ParticipantSafetyStateView {
|
|
ParticipantDescriptor descriptor;
|
|
bool registered{false};
|
|
bool barrier_active{false};
|
|
bool barrier_retained{false};
|
|
std::string operation_id;
|
|
std::uint64_t safety_epoch{0};
|
|
ParticipantResultView last_request;
|
|
ParticipantResultView last_verify;
|
|
ParticipantResultView last_release;
|
|
};
|
|
|
|
struct SafetyManagerSnapshot {
|
|
SystemAdmissionState system_state{SystemAdmissionState::Starting};
|
|
std::uint64_t safety_epoch{0};
|
|
std::string service_instance_id;
|
|
EnforcementMode enforcement_mode{EnforcementMode::Shadow};
|
|
std::string active_operation_id;
|
|
std::string active_operation_phase;
|
|
std::vector<DeviceSafetyStateView> devices;
|
|
std::vector<ParticipantSafetyStateView> participants;
|
|
std::vector<SafetyEvent> recent_events;
|
|
};
|
|
|
|
struct SafetyTargetResult {
|
|
std::string target_id;
|
|
bool success{false};
|
|
SafetyReason reason{SafetyReason::None};
|
|
std::string detail;
|
|
DeviceAdmissionState before_state{DeviceAdmissionState::Observing};
|
|
DeviceAdmissionState after_state{DeviceAdmissionState::Observing};
|
|
};
|
|
|
|
struct StopAllResult {
|
|
bool success{false};
|
|
std::string operation_id;
|
|
std::uint64_t previous_safety_epoch{0};
|
|
std::uint64_t current_safety_epoch{0};
|
|
SystemAdmissionState system_state{SystemAdmissionState::Starting};
|
|
std::vector<SafetyTargetResult> targets;
|
|
};
|
|
|
|
enum class RecoveryResultCode {
|
|
Recovered,
|
|
VerifiedButStillBlocked,
|
|
BlockerRemains,
|
|
EpochMismatch,
|
|
NothingToRecover,
|
|
TimedOut,
|
|
Failed,
|
|
};
|
|
|
|
struct RecoveryRequest {
|
|
std::string recovery_id;
|
|
std::vector<std::string> device_ids;
|
|
bool all_devices{false};
|
|
std::uint64_t expected_safety_epoch{0};
|
|
bool verify_only{true};
|
|
bool restore_operational_state{false};
|
|
std::string reason;
|
|
SafetyClock::time_point deadline{SafetyClock::time_point::max()};
|
|
// Authorizes the state-changing part of recovery. Operational restore
|
|
// calls this before invoking device recovery; software-only recovery calls
|
|
// it after hardware verification and before releasing admission barriers.
|
|
// A false result leaves admission latched and prevents device recovery.
|
|
std::function<bool()> authorize_clear;
|
|
};
|
|
|
|
struct RecoveryResult {
|
|
RecoveryResultCode result{RecoveryResultCode::Failed};
|
|
std::string recovery_id;
|
|
std::uint64_t previous_safety_epoch{0};
|
|
std::uint64_t current_safety_epoch{0};
|
|
SystemAdmissionState system_state{SystemAdmissionState::Starting};
|
|
std::vector<SafetyTargetResult> targets;
|
|
};
|
|
|
|
class SafetyManager;
|
|
|
|
class DispatchGuard final {
|
|
public:
|
|
DispatchGuard() noexcept = default;
|
|
~DispatchGuard() noexcept;
|
|
DispatchGuard(DispatchGuard&& other) noexcept;
|
|
DispatchGuard& operator=(DispatchGuard&& other) noexcept;
|
|
DispatchGuard(const DispatchGuard&) = delete;
|
|
DispatchGuard& operator=(const DispatchGuard&) = delete;
|
|
|
|
bool acquired() const noexcept { return coordinator_ != nullptr; }
|
|
const HardwareCheckResult& hardwareCheck() const noexcept
|
|
{
|
|
return hardware_check_;
|
|
}
|
|
|
|
private:
|
|
friend class SafetyManager;
|
|
DispatchGuard(SafetyManager* coordinator,
|
|
std::string device_id,
|
|
HardwareCheckResult hardware_check) noexcept;
|
|
void reset_() noexcept;
|
|
|
|
SafetyManager* coordinator_{nullptr};
|
|
std::string device_id_;
|
|
HardwareCheckResult hardware_check_;
|
|
};
|
|
|
|
class SafetyManager final {
|
|
public:
|
|
explicit SafetyManager(SafetyManagerConfig config = {});
|
|
~SafetyManager();
|
|
SafetyManager(const SafetyManager&) = delete;
|
|
SafetyManager& operator=(const SafetyManager&) = delete;
|
|
|
|
bool registerDevice(DeviceSafetyRegistration registration);
|
|
bool registerParticipant(std::shared_ptr<SafetyParticipant> participant);
|
|
bool unregisterParticipant(const std::string& participant_id);
|
|
|
|
void updateDeviceRuntimeState(
|
|
const std::string& device_id,
|
|
device::ManagedDeviceState lifecycle,
|
|
device::DeviceHealthSnapshot health = {});
|
|
std::optional<std::uint64_t> advanceDeviceGeneration(
|
|
const std::string& device_id);
|
|
StartupCoverageResult validateStartupCoverage(
|
|
SafetyClock::time_point deadline);
|
|
void markStartupComplete();
|
|
|
|
AdmissionResult admit(const AdmissionRequest& request);
|
|
// Lightweight session check. This validates the coordinator-owned epoch,
|
|
// generation, freshness, and admission state without calling the device
|
|
// endpoint or entering the hardware dispatch set.
|
|
HardwareCheckResult revalidatePermit(
|
|
const AdmissionPermit& permit) const;
|
|
DispatchGuard beginDispatch(const AdmissionPermit& permit);
|
|
void quarantineDevice(const std::string& device_id,
|
|
SafetyReason reason,
|
|
std::string operation_id = {});
|
|
|
|
StopAllResult stopAll(
|
|
std::string operation_id,
|
|
SafetyClock::time_point deadline = SafetyClock::time_point::max());
|
|
RecoveryResult recover(const RecoveryRequest& request);
|
|
|
|
SafetyManagerSnapshot snapshot() const;
|
|
CommandLedger& commandLedger() noexcept;
|
|
const std::string& serviceInstanceId() const noexcept;
|
|
const SafetyManagerConfig& config() const noexcept;
|
|
|
|
private:
|
|
friend class DispatchGuard;
|
|
struct Impl;
|
|
bool publishSafetySnapshot(DeviceSafetySnapshot snapshot);
|
|
void beginShutdown() noexcept;
|
|
AdmissionDecision evaluate(const AdmissionRequest& request) const;
|
|
void endDispatch_(const std::string& device_id) noexcept;
|
|
std::unique_ptr<Impl> impl_;
|
|
};
|
|
|
|
const char* toString(RecoveryResultCode value) noexcept;
|
|
|
|
} // namespace cmvr::safety
|