Files
secs-gem/README.md
T
raphael b2e22c3af0
tests / build-and-test (push) Successful in 3m1s
tests / thread-sanitizer (push) Successful in 3m42s
tests / tshark-dissector (push) Successful in 2m43s
tests / secs4j-interop (push) Successful in 1m1s
tests / python-interop (push) Successful in 3m43s
tests / libfuzzer (push) Successful in 3m44s
chore: remove daemon roadmap — project is complete
All phases shipped: daemon, gRPC API, Python client, GEM300 support,
operations hardening. The roadmap served its purpose; replace it with
final state documented in README + docs/*.md chapters.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-06-26 23:51:25 +02:00

296 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# secs-gem
A C++20 SECS-II / HSMS / SECS-I / GEM / GEM 300 runtime, fully
containerized. Every behavioural rule lives in YAML; the C++ is the
engine that reads them. Implements **all of E4, E5, E30, E37
(SS + GS), E39, E40, E42, E84, E87, E90, E94, E116, E120, E148,
E157**.
> **License: proprietary — see [LICENSE](LICENSE).** No use, copy,
> compile, evaluate, benchmark, or deploy without a written license
> from the copyright holder. Contact `raphael@maenle.net` for
> commercial licensing, evaluation terms, or fab deployment.
---
## Quick start
Everything runs in Docker — no compiler or build tools on the host.
```bash
docker compose run --rm builder # configure + compile
docker compose run --rm tests # 473 cases / 3 087 assertions
docker compose up --no-deps server client # live two-container demo
```
The two-container demo walks ~24 SECS transactions end-to-end
through the data model. Watch the logs interleave.
---
## Integrating your tool (pick a tier)
Three ways in, same engine underneath:
1. **Python, no SEMI knowledge** — run the `secs_gemd` daemon and
`pip install` the pure-Python client in [clients/python](clients/python):
```python
from secsgem_client import Equipment
eq = Equipment("localhost:50051")
eq.set(ChamberPressure=2.5) # variables: kwargs, not strings
@eq.command # the function name IS the command,
def START(cmd): # validated against the real equipment
run_recipe(cmd.params.get("PPID")) # — so a typo fails at startup
eq.fire(eq.names.event.ProcessStarted) # autocomplete + typo-safe
eq.listen()
```
Names come from *your* `equipment.yaml`. `@eq.command` binds a handler by
its function name; `eq.names.event.*` / `.alarm.*` / `.command.*` are
autocomplete-able, typo-checked views fetched from the live daemon — so
you rarely type a bare string. (The plain forms — `@eq.on("START")`,
`eq.fire("ProcessStarted")` — also work.)
A complete tool is ~25 lines: [clients/python/examples/mini_tool.py](clients/python/examples/mini_tool.py).
2. **Any language over gRPC** — `secs_gemd` exposes the name-based API in
[proto/secsgem/v1/equipment.proto](proto/secsgem/v1/equipment.proto)
(variables, events, alarms, control state, health stream, and the
host-command stream with the SEMI-conformant HCACK-4 contract). The
daemon owns the durable HSMS link: your tool software can restart
without the fab host ever noticing.
3. **Embedded C++** — construct a `gem::EquipmentRuntime`, call the
per-capability `register_*` functions (or `register_default_handlers`
for all of GEM), and wire behaviour with `commands.set_handler`.
`apps/secs_server.cpp` is the ~110-line canonical example.
---
## Documentation map
| File | What it covers |
|------------------------------------------------------------|-------------------------------------------------------------------------|
| [docs/](docs/00_index.md) | Guided-tour tutorial series — teach-from-zero across the protocol and the codebase |
| [docs/COMPLIANCE.md](docs/COMPLIANCE.md) | Per-capability audit against every SEMI standard implemented |
| [docs/INTEGRATION.md](docs/INTEGRATION.md) | Vendor-side tutorial: YAML → callbacks → production deploy |
| [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) | How the pieces fit + how to extend (new store / FSM / message) |
| [docs/PROOFS.md](docs/PROOFS.md) | The eight commands that prove the feature-completeness claim |
| [docs/VERIFICATION.md](docs/VERIFICATION.md) | Test plan for the external validators behind the proof table |
| [docs/BENCHMARKS.md](docs/BENCHMARKS.md) | Performance envelope (throughput, latency, memory) + how to re-run |
| [docs/MES_INTEROP.md](docs/MES_INTEROP.md) | Day-1 punch list to run against your commercial MES (59 test IDs) |
| [docs/SECURITY.md](docs/SECURITY.md) | Concrete configs: nftables, stunnel, minisign, SIEM audit-log schema |
| [docs/GLOSSARY.md](docs/GLOSSARY.md) | SEMI vocabulary: SVID, CEID, PPID, ALCD, HCACK, T-timers, … |
| [docs/FAQ.md](docs/FAQ.md) | Common questions and their canonical answers |
| [examples/pvd_tool/](examples/pvd_tool/) | Worked example: a realistic fictional PVD tool, YAML + C++ wiring |
| [LICENSE](LICENSE) | Proprietary license terms |
---
## Testing
- **Unit + integration** — `docker compose run --rm tests` runs 473
cases / 3 087 assertions across every store, FSM, codec, parser, and
persistence path.
- **Live conformance harness** — 47 wire-level checks against the
passive server.
- **Interop** — round-trips against secsgem-py (31 checks), secs4java8
(55 checks), and Wireshark's HSMS dissector (69 frames, 0 malformed).
- **Soak + fuzz** — 100 000-op property test; libFuzzer with ASan +
UBSan over `secs2::decode` and the SML parser, 0 crashes.
- **Daemon** — `secs_gemd_tests` exercises the gRPC service over real
in-process channels (125 assertions), in Release and under
ThreadSanitizer; `interop/daemon_interop.py` and
`interop/pyclient_interop.py` prove the gRPC↔HSMS bridge and the
published Python client against a live daemon with secsgem-py as host.
- **One command for all of it** — `tools/run_interop.sh` runs every
validation step (build, both unit suites, secsgem-py host, C++
conformance, Python client, daemon bridge, spool restart, tshark,
secs4java8) with a PASS/FAIL summary.
- **Config validation** — `secs_server --validate-config` rejects
malformed YAML before startup.
- **CI** — [Gitea Actions](.gitea/workflows/ci.yml) runs the full
suite plus a `-fsanitize=thread` lane on every push to `main`; all
473 cases pass clean under TSan.
Exact commands, exit codes, and per-standard test counts are in
[docs/PROOFS.md](docs/PROOFS.md); the rationale behind the external
validators is in [docs/VERIFICATION.md](docs/VERIFICATION.md).
---
## Architecture
The project is **spec-as-data**: the SEMI behavioural rules live in
YAML; the C++ is the engine that reads them.
```
┌──────────────────────────────────────────────────────────────┐
│ data/ │
│ messages.yaml SECS-II message catalog (164 msgs) │
│ control_state.yaml E30 §6.2 control transition table │
│ process_job_state.yaml E40 §6 PJ transition table │
│ control_job_state.yaml E94 §6 CJ transition table │
│ equipment.yaml SVIDs / DVIDs / ECIDs / CEIDs / │
│ alarms / recipes / commands │
└──────────────────────┬───────────────────────────────────────┘
│ (codegen at build, YAML loaded at startup)
┌──────────────────────────────────────────────────────────────┐
│ apps/ │
│ secs_server passive equipment secs_bench perf │
│ secs_client active host secs_conformance │
│ secs_interop_probe │
└──────────────────────────────────────────────────────────────┘
secsgem::config loader.hpp + validate.hpp:
YAML -> data model, with multi-error validator
surfacing every issue at once (`--validate-config`)
secsgem::gem per-standard FSM + per-store persistence
(every store accepts v ∈ [1, kVersion] for
forward-compatible schema migrations).
EquipmentDataModel composes all stores.
Router (stream, function) -> handler.
Generated messages.hpp covers 164 SxFy.
secsgem::hsms Connection (Asio): HSMS-SS + HSMS-GS, all
T-timers enforced, auto S9F3/F5/F7/F9/F11.
secsgem::secsi SECS-I Protocol FSM (E4): T1/T2/T3/T4 enforced
in-FSM, TCP transport for tunnel testing.
secsgem::secs2 Item (variant), encode/decode, Message,
SML parser/printer.
secsgem::metrics Prometheus exporter (Registry + HTTP server).
```
See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for how to extend
it (new store / FSM / message).
---
## Adding a capability
The point of "spec-as-data" is that adding behaviour almost never
requires a C++ change.
### New SVID
```yaml
# data/equipment.yaml
svids:
- {id: 4, name: ChamberTemp, units: "C", type: U4, value: 25}
```
### New host command with side effects
```yaml
host_commands:
- {name: VENT, ack: Accept, emit_ceid: 400, set_alarm: 2}
```
### New state transition
```yaml
# data/control_state.yaml
transitions:
- {from: OnlineRemote, on: host_request_offline, to: EquipmentOffline, ack: Accept}
```
### New SECS-II message
```yaml
# data/messages.yaml
- id: S6F30
stream: 6
function: 30
w: true
builder: s6f30_something
parser: parse_s6f30
body:
kind: list
struct_name: Something
fields:
- {name: field_a, shape: {kind: scalar, item_type: U4}}
- {name: field_b, shape: {kind: scalar, item_type: ASCII}}
```
`docker compose run --rm builder` regenerates `messages.hpp`. The
typed builder, parser, and struct definition appear automatically.
Run `--validate-config` after every YAML edit.
---
## Production deployment
See [docs/INTEGRATION.md](docs/INTEGRATION.md) for the full
vendor-side tutorial — wiring sensors, plugging FSMs into the tool,
persistence layout, monitoring/observability, HSMS-GS multi-MES
setup.
See [docs/SECURITY.md](docs/SECURITY.md) for concrete nftables /
stunnel / minisign / SIEM configs.
See [docs/BENCHMARKS.md](docs/BENCHMARKS.md) for the performance
envelope — roughly **140 k req/s S1F1**, **79 k req/s S1F3 (32
SVIDs)**, **572 k S6F11/s push**, **~450 bytes per PJ+CJ pair**.
Three orders of magnitude above typical fab tool load.
See [docs/MES_INTEROP.md](docs/MES_INTEROP.md) for the day-1 punch
list to run against your commercial MES before promoting from
staging to a real tool.
### Operational runbook (starter)
| Incident | First check | Mitigation |
|-------------------------------------|--------------------------------------|-------------------------------------------|
| HSMS connection flapping | T7 / T6 timer fires in logs | check MES reachability, network MTU |
| Spool depth growing | host MES connectivity / ACK rate | force-drain via S6F23, escalate to MES |
| State machine "stuck" | last state-change handler log line | host-issued offline + re-establish |
| Alarm storm | `AlarmRegistry::all()` snapshot | check upstream sensor; quench via S5F3 |
| Persistence dir growing unbounded | `du -s` + file count | sweep terminal-state records |
| Cross-tool inconsistency | `secsgem_tests` on canary tool | compare wire trace vs validator |
---
## Deferred follow-ups
- **asio `serial_port` adapter for SECS-I.** `secsi::Protocol` is
tested end-to-end over `secsi::TcpTransport`; the matching serial
driver isn't written yet. Mirror `TcpTransport` to add it.
---
## Build details
The toolchain image (`Dockerfile`) is Ubuntu 24.04 with `g++-13`,
CMake, Ninja, `libasio-dev`, `libyaml-cpp-dev`, and Python 3 for the
codegen. doctest is fetched via CMake FetchContent. Build artifacts
live in a named Docker volume so the host filesystem stays clean.
Standalone Asio is used in header-only mode (`ASIO_STANDALONE`). No
Boost dependency.
---
## Interop
Four independent external validators cross-check the codebase:
- **secsgem-py 0.3.0** (Python reference impl) — three harnesses
under `interop/`: secsgem-py active host driving the C++ passive
server (31 checks), C++ active host probing secsgem-py's passive
equipment, and a raw GEM 300 harness round-tripping S3 / S14 /
S16 / S12 through hand-crafted `SecsStreamFunction` subclasses.
- **secs4java8** (independent Java SECS implementation) — 55
cross-validation checks covering S1/S2/S3/S5/S6/S7/S10/S14/S16,
full-body GEM 300 shapes, S2F49 enhanced commands, S5F13F18
exception recovery.
- **Wireshark / tshark HSMS dissector** (independent network-protocol
authors) — 69 HSMS frames dissected on a recorded pcap, no
malformed-packet warnings.
- **libFuzzer + ASan + UBSan** — 200 000+ inputs through
`secs2::decode` and 1.4 M+ through `try_parse_sml` per 60 s lane,
0 crashes.
See [`interop/README.md`](interop/README.md) for harness-by-harness
detail and [docs/VERIFICATION.md](docs/VERIFICATION.md) for the test
plan rationale.