Interface cleanup so the report_* family matches the typo-safe ethos of
eq.names instead of leaking raw protobuf errors on a misspelled value.
- Milestone / ModuleState / JobState: importable str-enums (member == its
wire name, so plain strings still work) — autocomplete + a typo-checked
happy path. The clean rule: equipment-specific *names* live on eq.names;
fixed protocol *value-sets* are enums.
- _enum_value(): resolves an enum-or-string arg client-side and, on a bad
value, raises ValueError with a close-match hint *before* the wire. Wired
into report_job / report_substrate / report_module / request_control_state
(all previously raised a raw protobuf ValueError).
- Equipment is now a context manager (with Equipment(...) as eq: ...).
- examples/wafer_tool.py: a cluster tool tracking one wafer through one
module end-to-end (E90 + E157), showing the enums + context manager.
- tests/test_enums.py: asserts the enums stay in lockstep with the proto and
that the typo path is helpful. Wired into run_interop.sh (pyclient step).
- Interop drives both the enum and string forms on the wire + the ValueError
typo path. Docs (ch16/ch42) updated; names-vs-enums rule documented.
All Python unit tests + 25 pyclient interop checks pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Exposure: --grpc default flipped from 0.0.0.0 to 127.0.0.1 (the API is
unauthenticated by design; auth belongs to the transport), Unix-domain-
socket support (--grpc unix:///run/secs_gemd/api.sock = zero network
surface), SECURITY.md documents the contract and ch42 gained a "Running it
in production" section (which also documents the HSMS-SS single-session
assumption).
Graceful shutdown: SIGTERM/SIGINT land on an asio::signal_set on the io
thread, which nudges grpc Shutdown with a 2s deadline (cancels open
Subscribe/WatchHealth streams); Wait() returns on the MAIN thread, which
stops the engine (rt->stop() joins the io thread, so it must not run on
it). Exit 0, journal-safe, the in-code TODO is gone. --spool-dir added so
host-bound events survive daemon restarts.
Observability: --metrics serves Prometheus gauges secsgem_link_selected /
secsgem_control_state / secsgem_spool_depth, wired via the Phase-0
add_link_observer/add_control_state_observer hooks + io-thread sampling.
Deployment: deploy/secs_gemd.service — hardened systemd unit (DynamicUser,
ProtectSystem=strict, StateDirectory for the spool, UDS for the API,
TimeoutStopSec aligned with the graceful-shutdown window).
Enforcement: tools/check_daemon_ops.sh proves all three operational claims
(unix-socket gRPC accepts, all gauges present on /metrics, SIGTERM -> exit
0 + clean-stop log) — green; wired into tools/run_interop.sh (now 11
steps) and CI. CI python-interop lane also gained the pyclient and
spool-restart steps, so every harness now runs in CI.
TODO sweep: the shutdown TODO is fixed; the four remaining TODOs (nested
list formats, C2-as-text, U8>2^63, CONNECTED link state) are deliberate
deferred edge cases, each marked in code with context.
Daemon suite re-verified green (175 assertions).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
C9 — the flagship vendor example now demonstrates the intended integration
shape. examples/pvd_tool/main.cpp: 1093 -> 570 lines. The 466-line
hand-registered handler section and the hand-wired Server/Router/emit
plumbing are gone, replaced by EquipmentRuntime + register_default_handlers
(the example now serves all 56 handlers, up from its hand-picked 51) +
commands.set_handler for the START-runs-the-recipe behaviour (was a
hard-coded S2F41 router override). All domain logic — sensor simulator,
recipe runner, alarm threshold monitor, EPT cycler, Prometheus gauges —
unchanged. pvd's SVIDs 1/2 and CEIDs 400/401 match the roles: defaults, so
the built-ins bind with no config change. Verified: builds clean, boots
("registered 56 handlers", config loaded, EPT cycling), HSMS :5000 accepts,
metrics :9090 answers HTTP 200. logfn flushes per line so docker/CI logs
are visible immediately.
Writing project — new tutorial chapter docs/42_vendor_daemon_and_clients.md:
why a daemon (the host-timer argument), the proto contract and the HCACK-4
command semantics, the Python client walkthrough, EquipmentRuntime +
capability registration + roles:, the threading contract (posting API /
read_sync / hooks-on-io-thread) and primary-vs-observer slots, and a
which-tier-do-I-pick table. Indexed in 00_index Part 4. Refreshed the three
spots that still described pvd_tool's old "51 handlers in ~460 lines" shape
(ch35, ch41, pvd README) — drift killed in the same commit that made it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>