Files
secs-gem/docs/30_repository_tour.md
T
raphael cae98d9a7d docs: chapters 30–36 — the codebase (Part 3 complete)
Seven chapters walking the implementation top-to-bottom.

30 — Repository tour.  Top-level layout, directory by directory.
The eight built binaries.  The dependency graph from TCP socket
up through EquipmentDataModel.  CMake's role.  Test layout.

31 — Spec-as-data and codegen.  Why the design choice fits SECS/
GEM specifically.  The five YAML files: messages catalog,
control/PJ/CJ transition tables, equipment dictionary.  How
tools/gen_messages.py turns messages.yaml into typed C++ at build
time.  The --validate-config multi-error validator.  How to add a
new SVID / CEID / host command / state / message without C++.

32 — Stores and the data model.  What a store IS (records + API +
change handler + optional persistence).  Every store in the
codebase mapped to the SEMI standard it serves (table of 21).
EquipmentDataModel as plain composition + cross-store convenience
methods (vid_value, compose_reports_for).  The no-locks single-
threaded contract.  How to add a new store.

33 — Transport.  hsms::Connection read path (length+payload async
chain), write path (queue + one outstanding write), timer model
(5 steady_timers + per-request T3).  The asio executor / strand
model and why it's the right shape.  secsi::Protocol as the IO-
free FSM with Action / Event variants; secsi::TcpTransport as the
asio adapter.  Pattern repeats for E84 + GEM comm-state.

34 — Codec and SML.  The four files (170 + 30 + 52 + 32 lines of
header, 229 + 220 lines of impl).  Item variant storage layout
(11 alternatives, 16 formats, shared storage where E5 permits).
encode_into recursion; decode_at with bounds checks throwing
CodecError.  Message wrapper.  SML printer + try_parse_sml +
why SML round-trips Items but not necessarily bytes.

35 — State machines and dispatch.  gem::Router as a typed
(stream, function) dispatch table.  How an S2F41 round-trip walks
through parser → store dispatch → side-effect → CEID emission →
S6F11 build → spool-aware deliver.  The 11 FSMs all sharing the
same three-property shape (pure data table + pure FSM + observer
pattern).  CEID cascading from FSM transitions to wire bytes.

36 — Persistence, validation, metrics.  Which 7 stores have file
journals + why the others don't.  Per-record file pattern (atomic
rename, partial-write safe).  Schema versioning + multi-version
read.  Multi-error YAML validator (--validate-config) + cross-file
reference checks.  Prometheus registry + HTTP exporter + worked
metric patterns from the PVD example.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-09 20:23:05 +02:00

232 lines
11 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.
# 30 — Repository tour
← [19 E42 + E148 + S9 — Misc](19_e42_e148_s9_misc.md) | [Back to index](00_index.md) | Next: [31 Spec-as-data + codegen](31_spec_as_data_and_codegen.md) →
You've seen what every SEMI standard *does*. Now we shift to how
this **codebase** is laid out. This chapter answers: when you
`git clone` this repo, what are you looking at?
The repo is small — about 15 k lines of C++ + tests + tooling. It
fits in your head with a little patience. By the end of this
chapter you'll know:
- What each top-level directory contains.
- Which binaries get built.
- The dependency graph between modules.
- How the build system finds and links them.
---
## Top-level layout
```
secs-gem/
├── README.md One-page project summary.
├── LICENSE Proprietary terms.
├── CMakeLists.txt Build config (CMake 3.16+, single file).
├── Dockerfile Ubuntu 24.04 + g++-13 + libasio + yaml-cpp.
├── docker-compose.yml Multi-container demo wiring.
├── .gitea/workflows/ci.yml CI pipeline.
├── include/secsgem/ Public headers. All API here.
│ ├── secs2/ E5 codec + SML.
│ ├── hsms/ E37 transport (TCP + framing).
│ ├── secsi/ E4 transport (FSM + TCP tunnel).
│ ├── config/ YAML loader + multi-error validator.
│ ├── metrics/ Prometheus exporter.
│ ├── endpoint.hpp asio::ip::tcp::endpoint factory.
│ └── gem/ E30 + every GEM 300 standard.
│ ├── store/ Per-domain bundles (SVIDs, alarms, …).
│ └── *.hpp State machines + composers.
├── src/ Implementations. Mirrors include/.
│ ├── secs2/{codec,sml}.cpp
│ ├── hsms/{header,connection}.cpp
│ ├── secsi/{header,block,protocol,tcp_transport}.cpp
│ ├── config/...
│ ├── gem/...
│ └── endpoint.cpp
├── apps/ Standalone binaries.
│ ├── secs_server.cpp Passive equipment (demo + integration target).
│ ├── secs_client.cpp Active host driving the demo flow.
│ ├── secs_conformance.cpp 47-check wire-level conformance harness.
│ ├── secs_interop_probe.cpp Probe against secsgem-py passive equip.
│ ├── secs_bench.cpp Throughput / latency / memory bench.
│ ├── fuzz_secs2_decode.cpp libFuzzer harness for secs2::decode.
│ └── fuzz_sml_parse.cpp libFuzzer harness for try_parse_sml.
├── tests/ doctest unit + integration tests.
│ └── test_*.cpp 50 files, 445 cases, 2753 assertions.
├── data/ YAML configs (the spec-as-data).
│ ├── messages.yaml SECS-II message catalog (164 msgs).
│ ├── control_state.yaml E30 §6.2 transition table.
│ ├── process_job_state.yaml E40 transition table.
│ ├── control_job_state.yaml E94 transition table.
│ └── equipment.yaml Demo SVIDs/ECIDs/CEIDs/alarms/recipes.
├── tools/ Build-time scripts.
│ └── generate_messages.py Codegen: messages.yaml → messages.hpp.
├── interop/ External-validator harnesses.
│ ├── README.md Harness-by-harness detail.
│ ├── host_vs_cpp_server.py secsgem-py active host driving us.
│ ├── passive_equipment.py secsgem-py passive equipment for us to drive.
│ ├── raw_gem300_harness.py Raw S3/S14/S16/S12 round-trip.
│ ├── tshark_validate.sh pcap + tshark HSMS dissector check.
│ ├── secs4j_validate.sh secs4java8 (Java) cross-validation.
│ └── secs4j/ Dockerfile + harness for secs4java8.
├── examples/
│ └── pvd_tool/ Worked vendor example: fictional PVD tool.
│ ├── README.md What the example shows.
│ ├── equipment.yaml Realistic SVIDs/ECIDs/CEIDs/alarms/recipes.
│ └── main.cpp Sensor sim, recipe runner, alarm monitor.
└── docs/ This guide + reference docs.
├── 00_index.md The series TOC.
├── 0151_*.md Tutorial chapters.
├── ARCHITECTURE.md One-page architecture overview.
├── COMPLIANCE.md Per-capability audit.
├── INTEGRATION.md Vendor-side production deploy.
├── PROOFS.md 8 commands proving feature-completeness.
├── VERIFICATION.md External-validator test plan.
├── BENCHMARKS.md Performance envelope.
├── MES_INTEROP.md Commercial-MES day-1 punch list.
├── SECURITY.md nftables / stunnel / minisign configs.
├── GLOSSARY.md SEMI vocabulary cheat sheet.
└── FAQ.md Canonical answers.
```
---
## The dependency graph
```
data/*.yaml
┌─────────────┼──────────────────┐
│ (codegen) │ (runtime load) │
▼ ▼ ▼
generated/messages.hpp config::loader
│ │
└──────────► gem::EquipmentDataModel
│ used by
gem::Router
│ wraps
secs2::Message ◄─── codec / SML
│ over
hsms::Connection / secsi::TcpTransport
TCP socket
```
Read it bottom-up: a TCP socket carries bytes; `hsms::Connection`
frames them into `secs2::Message`s; `gem::Router` dispatches by
`(stream, function)` to handlers; handlers read/write
`EquipmentDataModel`; the model composes per-domain stores; the
stores were built from the YAML at startup.
No layer ever calls *up* the graph. `secs2::Item` has no idea
HSMS exists. `hsms::Connection` doesn't know about CEIDs.
`gem::Router` doesn't know whether the bytes came over HSMS or
SECS-I. Strict layering is what keeps the codebase small.
---
## The binaries
Built by [`CMakeLists.txt`](../CMakeLists.txt) (one file, ~250
lines). Each binary lives in `build/` after `cmake --build`.
| Binary | Source | What it does |
|----------------------|-----------------------------------------------------------------|-------------------------------------------------------|
| `secs_server` | [`apps/secs_server.cpp`](../apps/secs_server.cpp) | Passive equipment. Listens on TCP, dispatches via Router. |
| `secs_client` | [`apps/secs_client.cpp`](../apps/secs_client.cpp) | Active host. Drives ~24 transactions in the demo. |
| `secs_conformance` | [`apps/secs_conformance.cpp`](../apps/secs_conformance.cpp) | 47 wire-level conformance checks against a live server. |
| `secs_interop_probe` | [`apps/secs_interop_probe.cpp`](../apps/secs_interop_probe.cpp) | Active host probing a secsgem-py passive equipment. |
| `secs_bench` | [`apps/secs_bench.cpp`](../apps/secs_bench.cpp) | Throughput / latency / memory harness. |
| `secsgem_tests` | All `tests/*.cpp` | The 445-case doctest binary. |
| `fuzz_secs2_decode` | [`apps/fuzz_secs2_decode.cpp`](../apps/fuzz_secs2_decode.cpp) | libFuzzer (clang only, opt-in `-DSECSGEM_FUZZ=ON`). |
| `fuzz_sml_parse` | [`apps/fuzz_sml_parse.cpp`](../apps/fuzz_sml_parse.cpp) | libFuzzer for the SML parser. |
A worked example binary `pvd_tool` (from `examples/pvd_tool/`) is
also built by the same `CMakeLists.txt` when the example is
included.
---
## How the build system finds everything
`CMakeLists.txt` does five things in order:
1. **Pull in dependencies**`find_package(Threads)`,
`find_package(yaml-cpp)`, `FetchContent` for doctest. Standalone
Asio is header-only (no link step).
2. **Run codegen** — invokes `tools/generate_messages.py` to turn
`data/messages.yaml` into `build/generated/secsgem/gem/messages.hpp`.
Listed as a custom command so it re-runs when `messages.yaml`
changes.
3. **Build the library**`add_library(secsgem ...)` with every
source under `src/` plus the generated header.
4. **Build the apps** — one `add_executable` per `apps/*.cpp`,
each linking against `secsgem`.
5. **Build the tests**`add_executable(secsgem_tests ...)` with
every `tests/*.cpp`, linked against doctest + `secsgem`.
Build flags:
- **`-DSECSGEM_TSAN=ON`** — adds `-fsanitize=thread` to a
separate build dir. CI runs this lane.
- **`-DSECSGEM_FUZZ=ON`** — requires clang; adds libFuzzer + ASan +
UBSan; builds the two fuzz harnesses.
Everything else (Release / Debug, parallelism, output dirs) is
standard CMake.
---
## Test layout
50 test files; 445 test cases; 2 753 assertions. One file per
concern. Naming is `test_<thing>.cpp` consistently:
- `test_secs2.cpp`, `test_e5_kat.cpp`, `test_sml.cpp`,
`test_messages.cpp` — codec.
- `test_hsms*.cpp` (5 files), `test_secsi*.cpp` (3 files) — transport.
- `test_control_state.cpp`, `test_communication_state.cpp`,
`test_data_model.cpp`, `test_host_handler.cpp`, `test_loader.cpp`,
`test_config_validate.cpp` — E30.
- `test_process_jobs.cpp`, `test_control_jobs.cpp`,
`test_carriers.cpp`, `test_substrates.cpp`, `test_ept.cpp`,
`test_modules.cpp`, `test_cem_objects.cpp`, `test_e84*.cpp`,
`test_e42_formatted_pp.cpp` — GEM 300.
- `test_*_persistence.cpp` (4) — file-backed journal.
- `test_robustness_fuzz.cpp` — randomized property test.
- `test_thread_safety.cpp` — TSan-validated single-threaded contract.
- `test_metrics_prometheus.cpp` — Prometheus exporter.
- `test_wire_ceid_emission.cpp` — CEID firings observed on a real socket.
- `test_live_gem300.cpp`, `test_gem300_scenario.cpp` — multi-FSM cascades.
Full per-standard breakdown:
[`docs/PROOFS.md`](PROOFS.md) "Per-standard test coverage" table.
---
## Where to go next
Now that you know what's where, the next chapter explains the
*philosophy* that makes the codebase this small: the **spec-as-data**
principle, and how the YAML files + codegen + runtime loader work
together so adding a new SVID / state / message rarely requires C++.
Next: [→ 31 Spec-as-data + codegen](31_spec_as_data_and_codegen.md)