INTEGRATION.md §3 used to show a sensor-poll thread calling
model->svids.set_value() directly while the io_context thread reads
the same SVID for an inbound S1F3. That's a data race — there are
zero locks anywhere in EquipmentDataModel and there's no intention
to add them. The library is single-threaded by design; the doc was
just inviting trouble.
This commit makes the actual contract explicit:
- INTEGRATION.md §3: thread-safety callout box. All access must run
on the io_context that drives the HSMS connection. Sensor updates
from other threads marshal via asio::post(io.get_executor(), ...).
Same applies to set_*_change_handler callbacks (they fire on the
io_context thread; observers must be thread-safe or hand work off).
- README.md §3 (Monitoring & observability): added a paragraph noting
that hooks fire on the io_context thread, blocking I/O inside a
handler stalls the dispatcher, and metrics exporters must respect
the same contract.
- tests/test_thread_safety.cpp: two scenarios that exercise the
canonical pattern — N producer threads asio::post sensor updates
onto a worker-driven io_context; reads marshal back through the
io. Catches obvious regressions (e.g. someone adding a
"convenience" cross-thread mutator that bypasses the strand).
A passing run isn't proof of race-freedom under ThreadSanitizer —
it pins down the *pattern* customers should follow. TSan integration
is a separate workstream.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The E84StateMachine timers landed last commit but stayed theoretical —
arming was delivered via abstract callbacks the application had to
glue to a real clock. This commit ships the canonical glue:
- include/secsgem/gem/e84_asio_timers.hpp: header-only
E84AsioTimers wraps three asio::steady_timers, wires set_timer_handlers
on attach(), routes async_wait expiry back into fsm.on_timeout().
detach() cancels everything cleanly.
- tests/test_e84_asio_timers.cpp: four scenarios exercised through a
real asio::io_context with wall-clock timers — TA1 expiry,
signal-driven cancel before TA1 fires, TA3 expiry from the
Transferring state, and detach() halting further transitions.
These cover the integration the synthetic unit tests in
test_e84_timers.cpp can't reach.
- INTEGRATION.md §4.6: the vendor-side recipe — create the port,
set timeouts, make_shared<E84AsioTimers>(...)::attach(), feed signals
from your I/O bridge.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
End-to-end guide for an equipment vendor integrating the library
into a real semiconductor tool:
1. Architecture: what the runtime provides vs what the application
contributes — three boundary classes (EquipmentDataModel,
Router, hsms::Connection).
2. 30-minute first connection: YAML + minimal main() + run.
3. Wiring real sensors to SVIDs.
4. Plugging the FSMs into the tool: EPT, carriers, substrates,
E40 PJ / E94 CJ, alarms, recoverable exceptions.
5. Persistence: enable_persistence(dir) per store, storage budget,
replay semantics, current caveats.
6. Monitoring + observability: connection lifecycle hooks,
state-change handlers, S9 protocol errors.
7. Recommended deployment layout (/opt/acme-secsgem/...).
8. Integration testing checklist.
9. When to extend the runtime.
10. The honest gap between "this stack runs" and "this is a
certified GEM tool".
Cross-referenced from COMPLIANCE.md §9 distinction (stack vs tool).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>