tools/run_interop.sh runs ALL nine validation steps with a PASS/FAIL summary:
build, unit (464), daemon-unit (41), secsgem-py host vs server (31 checks),
secs_conformance (47), gRPC+secsgem-py daemon bridge, spool persistence
across restart, tshark HSMS dissector, secs4java8 (55 checks). Verified green
end-to-end. The unit suite is partly self-referential (our parsers validate
our builders); these external validators are the real oracle — now they run
with one command instead of by hand. Two bugs found by running it: unbounded
ninja at -O3 OOM-kills cc1plus in memory-constrained Docker VMs (build with
-j 2) and bash-3.2 lacks negative array subscripts.
CI: grpc deps added to the build job so secs_gemd + secs_gemd_tests build and
RUN in CI (previously the daemon silently dropped out — now fails loudly if
missing), plus a python-interop lane running py-host/conformance/daemon
harnesses against localhost in one container (no docker-in-docker).
Service hardening while in there: reject proto Values with no kind set at
the RPC edge (previously silently became ASCII ""), TODO markers for list
element formats and daemon graceful shutdown. New tests: unset-Value guard
+ a property test iterating ALL configured variables via gRPC asserting each
keeps its declared SECS-II format (daemon tests 16 -> 41 assertions).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Audit fixes for two real bugs in the gRPC service:
1. Format compliance: to_item() wrote F8/I8 regardless of the variable's
declared wire format, so values contradicted the S1F11/S1F21 namelists
(ChamberPressure is F4, WaferCounter U4; the interop trace showed <F8 2.5>
on the wire). Conversion now targets the declared format — verified
end-to-end: secsgem-py now receives <F4 2.5> in S6F11.
2. Thread safety: gRPC handler threads called resolve_variable/resolve_event,
copying live store entries (including Item values) while the io thread
mutates them. The service now snapshots the immutable name->id/format maps
at construction (before run_async, per the documented ordering); all writes
already post to the io thread. Remaining known narrow race (GetControlState
enum read) documented in DAEMON_ROADMAP.
Also: drop a stale tools/run_interop.sh reference from docker-compose.yml.
Tests: daemon in-process 16/16 (new F4/U4 format assertions), core 459/459,
secsgem-py interop green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- name_index: add resolve_event(name) -> CEID (unit-tested).
- equipment_service.hpp: extract the gRPC service + value/state conversion
into a shared header; add FireEvent (optional per-fire variable values,
then trigger the collection event by name). secs_gemd slims to main().
- test_daemon_service: real in-process gRPC integration test (client stub ->
service -> EquipmentRuntime) proving SetVariables lands in the model,
GetControlState reports the state, FireEvent and unknown-name paths behave.
Separate secs_gemd_tests target (links grpc++/proto), gated on the daemon.
Core suite 459/459 (2799 assertions); daemon gRPC tests 15/15.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extract the SECS/GEM engine wiring out of the secs_server app into a
reusable class, and stand up a language-agnostic gRPC daemon on top so a
tool's software (any language) can drive the equipment without linking C++
or knowing SEMI. Foundation for replacing a vendor's SECS/GEM server.
Engine reuse:
- EquipmentRuntime (include/secsgem/gem/runtime.hpp, src/gem/runtime.cpp):
owns io_context, passive Server, model, control-state machine, Router;
thread-safe outbound API (set_variable/emit_event/set_alarm/clear_alarm),
on_command hook, deliver_or_spool, run()/run_async()/poll()/stop().
- register_default_handlers (src/gem/default_handlers.cpp): the 56 GEM
handlers + domain emitters, relocated from secs_server so the app and the
daemon speak byte-identical GEM. secs_server.cpp reduced ~1270 -> 113 lines.
- name_index.hpp: resolve_variable(name) -> VID (the name->id binding layer).
Daemon (apps/secs_gemd.cpp, proto/secsgem/v1/equipment.proto):
- runs the engine + HSMS link on a background thread; serves the gRPC
Equipment service. Increment 1: SetVariables (name-resolved, plain
value->Item) and GetControlState. proto carries the full v1 surface
(universal + carrier/recipe/job tiers); remaining RPCs + the Subscribe
command stream are next (docs/DAEMON_ROADMAP.md).
- CMake: opt-in SECSGEM_DAEMON, protoc/grpc_cpp_plugin codegen, gracefully
skipped where protobuf/grpc++ are absent. Dockerfile gains the grpc deps.
Tests (proof): test_runtime, test_default_handlers (S1F1->S1F2, S2F41->hook),
test_name_index. Full suite 458/458, 2795 assertions; live server<->client
GEM300 demo still passes on the refactored server.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>