Close COMPLIANCE.md gap: Documentation (S1F19-F22)
tests / build-and-test (push) Failing after 32s

Adds the GEM "Documentation" Fundamental capability: the equipment now
self-reports which GEM capabilities it supports, and the host can
discover the DVID namelist with the same shape used for SVIDs.

Catalog (data/messages.yaml -> generated messages.hpp)

  S1F19 W   header-only                Get GEM Compliance Request
  S1F20     <L,3 <A SOFTREV>
                 <A EQPTYP>
                 <L,a <L,2 <U1 CCODE> <A CDESC>>>>
                                       Get GEM Compliance Data
  S1F21 W   <L,n <U4 VID>>             DVID Namelist Request (n=0 = all)
  S1F22     <L,n <L,3 <U4 VID>
                       <A VNAME>
                       <A UNITS>>>     DVID Namelist Data

  Codegen emits CapabilityEntry and GemCompliance structs.  S1F22 reuses
  S1F12's StatusName struct (same wire shape; dedup avoids redefinition).

Equipment data dictionary (data/equipment.yaml)

  device:                              Adds `equipment_type: "EQUIPMENT"`
                                       for the S1F20 EQPTYP field.

  capabilities:                        New section.  List of
    - {code, name}                     (CCODE, CDESC) pairs honestly
                                       reflecting what the codebase
                                       implements: 1, 2, 3, 5, 6, 7, 8,
                                       9, 11, 12, 14 (partial), 15.

  dvids:                               New section, same schema as
                                       svids:.  Demo populates two:
    - WaferCounter      (U4, units wafer)
    - ChamberPressure   (F4, units Torr)

Loader (src/config/loader.cpp + include/secsgem/config/loader.hpp)

  EquipmentDescriptor gains equipment_type and capabilities (vector of
  (uint8_t, string) pairs).  load_equipment now reads `capabilities:`
  into the descriptor and `dvids:` into model.dvids.

Server (apps/secs_server.cpp)

  router.on(1, 19) returns S1F20 with desc.software_rev,
  desc.equipment_type, and desc.capabilities converted to
  vector<CapabilityEntry>.
  router.on(1, 21) returns S1F22 built from model.dvids.all().

Client (apps/secs_client.cpp)

  Two new demo steps after Request Online and before SVID discovery:
    S1F19 -> S1F20: logs SOFTREV, EQPTYP, and every (CCODE, CDESC)
                    the equipment claims.
    S1F21 -> S1F22: logs each DVID with units.

Tests

  tests/test_messages.cpp   Round-trip S1F19/F20 with a 3-entry
                            capability list; round-trip S1F22 with two
                            DVIDs.

  tests/test_loader.cpp     Asserts equipment_type, the capabilities
                            list contains CCODE 14 (Spooling), and the
                            two DVIDs land in model.dvids.

COMPLIANCE.md

  "Documentation" Fundamental moves from  to .
  S1F19/F20 + S1F21/F22 rows in the coverage matrix flip to .
  The "what would it take" list drops the documentation-messages bullet.

Verified

  - Tests: 77 cases / 444 assertions pass.
  - Demo: client logs the full capability list received from the
    equipment, including CCODE 14 "Spooling (partial; S2F43/F44 +
    S6F23/F24)" — the equipment honestly reports its partial
    implementation rather than overclaiming.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-06-07 00:30:43 +02:00
parent 547fd2116b
commit 813e011409
9 changed files with 217 additions and 5 deletions
+41
View File
@@ -151,6 +151,47 @@ TEST_CASE("S9F13 conversation timeout carries MEXP + EDID") {
CHECK(parsed->edid == "EQUIP-01");
}
// ---- S1F19 / S1F20 Get GEM Compliance ----------------------------------
TEST_CASE("S1F19 is header-only and S1F20 round-trips capability list") {
auto req = s1f19_get_gem_compliance_request();
CHECK(req.stream == 1);
CHECK(req.function == 19);
CHECK(req.reply_expected);
CHECK_FALSE(req.body.has_value());
std::vector<CapabilityEntry> caps = {
{1, "Establish Communications"},
{5, "Status Data Collection"},
{14, "Spooling"},
};
auto m = s1f20_get_gem_compliance_data("0.1.0", "EQUIPMENT", caps);
auto parsed = parse_s1f20(m);
REQUIRE(parsed.has_value());
CHECK(parsed->softrev == "0.1.0");
CHECK(parsed->equipment_type == "EQUIPMENT");
REQUIRE(parsed->capabilities.size() == 3);
CHECK(parsed->capabilities[0].ccode == 1);
CHECK(parsed->capabilities[0].cdesc == "Establish Communications");
CHECK(parsed->capabilities[2].ccode == 14);
CHECK(parsed->capabilities[2].cdesc == "Spooling");
}
// ---- S1F21 / S1F22 DVID Namelist ---------------------------------------
TEST_CASE("S1F22 round-trip preserves DVIDs (shape matches S1F12)") {
std::vector<StatusName> dvs = {{100, "WaferCounter", "wafer"},
{101, "ChamberPressure", "Torr"}};
auto m = s1f22_data_variable_namelist_data(dvs);
auto parsed = parse_s1f22(m);
REQUIRE(parsed.has_value());
REQUIRE(parsed->size() == 2);
CHECK((*parsed)[0].id == 100);
CHECK((*parsed)[0].name == "WaferCounter");
CHECK((*parsed)[0].units == "wafer");
CHECK((*parsed)[1].id == 101);
}
// ---- Extended GEM message round-trips ------------------------------------
TEST_CASE("S2F33 define-report round-trip") {