#pragma once #include "fgc/Config.h" #include #include namespace fgc { // Reconciling what hardware.ini DECLARES against what the rig actually turned // out to have. // // The program degrades gracefully everywhere - TestRunner skips the pitch and // IMU leaves, CamEvent falls back to encoder pitch, CaptureScheduler sends the // single-axis MOVE form. That is right for a rig that genuinely lacks them, and // indistinguishable from a FAILURE on a rig that does not: a 2-axis tower whose // pitch stops enumerating becomes a yaw-only tower, captures one elevation band, // and reports itself healthy. Declaring the assembly is what lets startup tell // those two apart. // What this run actually found, from the sources that can answer: telemetry for // the axes, the constructed sensor objects for the IMU and env sensor, camera // enumeration for the cameras. struct FoundHardware { std::vector axes; // yaw implied by any ST line, pitch by pitch_present bool imu = false; bool env = false; std::vector cameras; // by [Camera.] block name, as configured // How many of those the source actually opened. A camera can be configured // and declared and still not be on the bus, which is the whole failure this // section exists to catch - but the source reports a count, not which ones // are missing, so the shortfall is named as a number. -1 = not asked. int cameras_opened = -1; // Telemetry never arrived, so the axis list is unknown rather than empty. // Without this an unplugged MCU would report every declared axis as missing, // which is true but useless - the one fault worth naming is the serial link. bool axes_known = true; }; enum class PresenceIssue { MissingDeclared, // declared, not found - the failure this exists to catch UndeclaredFound, // found, not declared - the file is stale }; struct PresenceFinding { PresenceIssue issue; std::string subsystem; // "axis pitch", "imu", "env", "camera rgb" std::string detail; // one operator-readable sentence, naming the fix }; struct PresenceReport { std::vector findings; // False when no hardware.ini was read, or it declared nothing comparable. // Every tower is in this state until it is migrated, and it is what keeps // this change invisible during the rollout. bool reconciled = false; bool anyMissing() const; bool anyUndeclared() const; std::vector missing() const; // subsystem names, for the refusal line }; // Pure comparison; says nothing about what to do about it. PresenceReport reconcilePresence(const HardwareConfig& declared, const FoundHardware& found); enum class PresenceAction { Proceed, // nothing to reconcile, or everything matches WarnOnly, // mismatches, but policy (or --degraded) says carry on Refuse, // declared hardware is missing and this tower is strict }; struct PresenceVerdict { PresenceAction action = PresenceAction::Proceed; std::string reason; // the line to log; empty when there is nothing to say }; // `strict` is [Features] strict_presence, `degraded` is --degraded. // // Warn-only is the default deliberately: through the fleet rollout no tower may // change behaviour on upgrade just by gaining a hardware.ini. A tower flips to // strict once its file is known good, so the fleet-wide flip is a config change // rather than a release. PresenceVerdict presenceVerdict(const PresenceReport& report, bool strict, bool degraded); // Every finding as one log-ready line, most serious first. Empty when there are // none. std::vector formatPresence(const PresenceReport& report); } // namespace fgc