A1: S5F13-F18 exception recovery messages (E5 §9.5-9.7)

Catalog gains the recover-request / recover-complete / recover-abort
loop that closes E5's exception lifecycle.  Wire shapes:

  S5F13/F14  Exception Recover Request / Acknowledge
  S5F15/F16  Exception Recover Complete Notify / Acknowledge
  S5F17/F18  Exception Recover Abort Request / Acknowledge

Acks use the same AlarmAck byte already in use by S5F10/F12.  EXRESULT
on F15 is modelled as ASCII (the common case; vendor-specific richer
shapes are a YAML edit).

Round-trip tests cover all three pairs.  Server dispatch is left for a
later commit alongside the per-alarm AlarmStateMachine (Tranche C) —
this commit is wire-coverage only.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-06-07 21:08:44 +02:00
parent 90c177b7ce
commit b59c62bbc9
3 changed files with 109 additions and 0 deletions
+42
View File
@@ -498,6 +498,48 @@ TEST_CASE("S5F11 / S5F12 exception clear round-trip") {
CHECK(*ack_byte(s5f12_exception_clear_confirm(AlarmAck::Accept)) == 0);
}
TEST_CASE("S5F13 / S5F14 exception recover request round-trip") {
auto m = s5f13_exception_recover_request(42, "RETRY");
CHECK(m.stream == 5);
CHECK(m.function == 13);
CHECK(m.reply_expected);
auto parsed = parse_s5f13(m);
REQUIRE(parsed.has_value());
CHECK(parsed->exid == 42);
CHECK(parsed->exrecvra == "RETRY");
CHECK(*ack_byte(s5f14_exception_recover_ack(AlarmAck::Accept)) == 0);
}
TEST_CASE("S5F15 / S5F16 exception recover complete round-trip") {
auto m = s5f15_exception_recover_complete_notify(42, "OK");
CHECK(m.stream == 5);
CHECK(m.function == 15);
CHECK(m.reply_expected);
auto parsed = parse_s5f15(m);
REQUIRE(parsed.has_value());
CHECK(parsed->exid == 42);
CHECK(parsed->exresult == "OK");
CHECK(*ack_byte(s5f16_exception_recover_complete_ack(AlarmAck::Accept)) == 0);
}
TEST_CASE("S5F17 / S5F18 exception recover abort round-trip") {
auto m = s5f17_exception_recover_abort_request(42);
CHECK(m.stream == 5);
CHECK(m.function == 17);
CHECK(m.reply_expected);
// Scalar U4 parser returns the value directly.
auto exid = parse_s5f17(m);
REQUIRE(exid.has_value());
CHECK(*exid == 42);
CHECK(*ack_byte(s5f18_exception_recover_abort_ack(AlarmAck::Accept)) == 0);
}
TEST_CASE("S7F19 / S7F20 EPPD list") {
auto req = s7f19_current_eppd_request();
CHECK(req.stream == 7);