test: live persistent-spool restart end-to-end
Adds a docker-compose service `server-spool` that runs secs_server
with --spool-dir pointed at a named volume. Two-phase Python
harness (interop/spool_persistence_test.py):
1. Enqueue phase: force-spool one S6F11(CEID=300) via the
SPOOL_ON / START / SPOOL_OFF RCMD trio, then disconnect.
2. Driver runs `docker compose restart server-spool` between
the phases — the named volume preserves the journal files.
3. Drain phase: reconnect, send S6F23(Transmit), verify the
replayed S6F11 carries CEID 300.
Surfaces a real interop bug along the way: secsgem-py 0.3.0 encodes
RSDC (and other "single-byte status" fields) as <U1>, while SEMI E5
spells them as <B>. Our `as_binary_first` was strict on Binary; now
accepts either (the byte semantics are identical, and the leniency is
symmetric with the U-type widening from the first interop commit).
Result: enqueue → docker restart → drain returns CEID 300 cleanly.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -51,11 +51,23 @@ inline std::optional<std::string> as_text_or_binary(const s2::Item& item) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Single-byte status/ack reader. SEMI E5 spells these fields as
|
||||
// `<B>` (Binary), but real peers — notably secsgem-py 0.3.0 — encode
|
||||
// RSDC, EAC, ACKC*, OFLACK, ONLACK, etc. as `<U1>`. Both formats
|
||||
// carry the same value in the same single byte, so we accept either
|
||||
// and return `nullopt` only when the item is genuinely neither.
|
||||
inline std::optional<uint8_t> as_binary_first(const s2::Item& item) {
|
||||
if (item.format() != s2::Format::Binary) return std::nullopt;
|
||||
const auto& v = item.as_bytes();
|
||||
if (v.empty()) return std::nullopt;
|
||||
return v.front();
|
||||
if (item.format() == s2::Format::Binary) {
|
||||
const auto& v = item.as_bytes();
|
||||
if (v.empty()) return std::nullopt;
|
||||
return v.front();
|
||||
}
|
||||
if (item.format() == s2::Format::U1) {
|
||||
const auto& v = std::get<std::vector<uint8_t>>(item.storage());
|
||||
if (v.empty()) return std::nullopt;
|
||||
return v.front();
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
inline std::optional<bool> as_boolean(const s2::Item& item) {
|
||||
|
||||
Reference in New Issue
Block a user