Files
secs-gem/FAQ.md
T
raphael b031f057af docs: customer-ready sweep + README restructure + tshark CI fix
Audit pass over the public-facing surface so a customer can read it
end-to-end without tripping on stale numbers or self-contradictions.

README + docs accuracy:
- Test counts 426 → 445, assertions 2 557 → 2 753 (verified via
  doctest run); E5 row was missing test_e5_kat (19 cases)
- Interop checks 24 → 31, COMPLIANCE.md message count 149 → 164,
  COMPLIANCE.md "291 cases / 1515 assertions" → 445 / 2 753
- README "60+ test IDs" for MES_INTEROP → actual 59
- PVD example counts: 32 SVIDs/17 CEIDs → 29/21, "~40 handlers
  in ~200 lines" → 51 in ~460, "~700 lines" → ~1,100; main.cpp
  header table-of-contents resynced with the actual 7 sections

Out-of-scope honesty (COMPLIANCE.md §8 + FAQ.md):
- Removed HSMS-GS (was both  implemented in §1 and "out of scope"
  in §8; INTEGRATION.md §7 documents using it)
- Removed multi-block SECS-I (split_message/assemble_message exist
  with 4 dedicated tests)
- Added serial-port wiring as the genuine open  item — FSM is
  tested end-to-end over TCP; only the asio serial_port glue is
  deferred
- COMPLIANCE.md intro now lists E42 and notes "E37 (SS + GS)"

README restructure:
- Moved the 8-command proof table and per-standard test-coverage
  table to a new PROOFS.md (72 lines)
- README now leads with what / Quick start / Documentation map,
  then a one-paragraph "How it's proved" linking to PROOFS.md
- Updated cross-refs in FAQ.md, GLOSSARY.md, VERIFICATION.md, and
  interop/README.md to point at PROOFS.md

CI fix — tshark-dissector job:
- interop/tshark_validate.sh hardcoded /app/build/secs_server etc.
  which only works inside the docker image.  Now derives ROOT from
  the script's own location and accepts BUILD/SERVER/CLIENT/DATA
  env overrides, so CI can run it from the workspace dir
- Verified still passes in docker (69 frames, 0 malformed)

.gitignore:
- Added build-fuzz/ and build-tsan/ (were showing as untracked)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-09 18:59:17 +02:00

228 lines
9.2 KiB
Markdown

# FAQ
Questions we hear once per integration. Skim before you ask. If
your question isn't here and isn't obvious from the other docs,
ask once — your question probably belongs in this file and we'll
add it.
## Why is HSMS unencrypted?
Because SEMI E37 says so. HSMS is plain TCP with a 14-byte
framing header — no TLS, no auth, no nonces. Every commercial MES
on the market speaks exactly that wire, and changing it would make
us incompatible with all of them. Encryption and authentication
belong at the network layer: see [SECURITY.md](SECURITY.md) for
the stunnel.conf + nftables setup that wraps the unencrypted TCP
in mTLS without modifying the wire protocol.
## What's the difference between SVID and DVID?
**SVID** is a *status* variable — equipment state the host queries
(chamber pressure, current control state, wafer counter).
**DVID** is a *data* variable — intermediate values, typically
computed or sensor-derived, that aren't part of the equipment's
state model.
In practice fab tools blur the line. The library treats them
identically except for which message reports them: `S1F3 / S1F11`
for SVIDs, `S1F21 / S1F22` for DVIDs. Variable lookups by VID
span both (`EquipmentDataModel::vid_value`).
## Do I really need all four YAML files?
Yes for production; no for a quick "does it compile":
- `equipment.yaml` — your tool's data dictionary. Required.
- `control_state.yaml` — the E30 control state machine (HostOffline,
AttemptOnline, OnlineRemote, …). The default in `data/` works as
a starting point; you may customize transitions.
- `process_job_state.yaml` — the E40 PJ FSM. Default is spec-typical;
customize only if your tool has unusual recipe semantics.
- `control_job_state.yaml` — the E94 CJ FSM. Same.
`secs_server --validate-config` checks all four in one pass and
exits 0 / 1. Run it in CI on every config change.
## PJ vs CJ — what's the difference?
A **PJ** (E40 Process Job) is "process this batch of material with
this recipe." One PJ = one recipe run = one set of wafers. It
has its own FSM (Queued → SettingUp → Processing → ProcessComplete).
A **CJ** (E94 Control Job) is "execute these PJs in order, as a
unit, with start/pause/abort semantics." A CJ owns an ordered list
of PRJOBIDs. When the host issues `CJSTART`, the CJ promotes its
PJs through their lifecycles.
You typically need both: the MES creates a CJ containing N PJs,
then starts the CJ. PJs without a CJ are legal — they just sit in
Queued waiting for someone to select them — but most MES drives
batches through CJs.
## Who fires FSM transitions — the library or my code?
**Your code.** The library implements the FSMs (legal transitions,
validation, persistence) but it doesn't know when a wafer was
actually loaded or when a recipe step finished — those signals come
from your tool. The pattern across every store is:
```cpp
// You fire the event; the FSM validates + transitions + emits.
model->process_jobs.fire_internal("PJ-1", gem::ProcessJobEvent::SetupComplete);
model->carriers.fire_id_event("CAR-A1B2", gem::CarrierIDEvent::Read);
```
Host commands (`S2F41` RCMD=START, `S16F5` PRJSTART, `S16F27` CJSTART)
arrive via the wire and get dispatched into your registered
handlers; the handler typically calls `fire_internal` or
`on_host_command` on the relevant store.
See INTEGRATION.md §4 for the worked patterns.
## What runs on which thread?
**Everything that touches the data model runs on the io_context
thread.** There are no locks in `EquipmentDataModel`.
- The Router dispatch (incoming wire messages) — on the io_context.
- All `set_*_change_handler` callbacks — on the io_context.
- Periodic timers you register via asio — on the io_context.
If your code lives on another thread (typical for sensor polling),
marshal updates via `asio::post`:
```cpp
asio::post(io.get_executor(), [model, value] {
model->svids.set_value(100, secs2::Item::f4(value));
});
```
INTEGRATION.md §3 has the full thread-safety contract.
## How do I add a new SECS-II message?
Edit `data/messages.yaml`, add a row, rebuild. The codegen
(`tools/gen_messages.py`) emits a typed builder + parser into
`messages.hpp`. Then register a Router handler in your `main.cpp`
for the new `(stream, function)` pair. See README "Adding a
capability" or ARCHITECTURE.md for the full walkthrough.
## What's the difference between `Item::ascii("X")` and `Item::binary({'X'})`?
The wire format byte differs — `0x41 01 58` for ASCII vs
`0x21 01 58` for Binary. Some peers (notably secsgem-py) default
PPBODY to ASCII; others use Binary. Our codec accepts either via
the `BINARY_OR_ASCII` codegen type for fields the spec lists as
`ASCII | Binary | List` (the PPBODY case in S7F3/F6).
For most fields it doesn't matter — pick the format that matches
your data semantically.
## My MES sends a message that worked in `interop` but fails in production. What's going on?
Three usual suspects:
1. **U-width.** Your MES is sending `DATAID` as U1 but our handler
was strict for U4. We're lenient now via `any_unsigned_first`,
but if you have custom handlers in your code, use that helper
rather than `as_u4_scalar` for identifier fields.
2. **PPBODY direction.** Some MES send PPBODY as ASCII even when
the spec says it can be Binary. Use `as_text_or_binary` not
`as_binary`.
3. **Trailing fields.** Some MES add proprietary trailing fields
to S2F41 / S16F11 / S3F17 bodies that aren't in the standard.
Our parsers are tolerant of extras; check your handler's
assumptions.
See MES_INTEROP.md §13 for the per-MES quirk register.
## What if the spec is ambiguous on some detail?
Cross-check against the secsgem-py and secs4j wire output:
[VERIFICATION.md](VERIFICATION.md). If both peers agree on a
shape, that's the working interpretation regardless of how you read
the spec text. If they disagree, the secsgem-py output usually
wins (it's the de-facto Python reference and most MES vendors test
against it), but file the question — we may need a new test.
## Can I run this without Docker?
In principle yes — you need g++-13 (or any C++20 compiler), CMake,
Ninja, libasio-dev, libyaml-cpp-dev, python3. But every doc, every
CI lane, every test command in the repo assumes Docker. Going
off-piste means re-deriving the build on your host. We don't
support it; we don't actively break it.
## How does persistence survive a crash mid-write?
Every store uses a `.tmp + atomic rename` pattern: writes go to
`<file>.tmp`, then `rename(2)`s into place. POSIX guarantees the
rename is atomic on the same filesystem. A crash mid-write loses
the `.tmp` (corrupt-drop on next replay) but leaves the prior
record intact.
Every store's loader accepts versions in `[1, kVersion]` so future
schema bumps don't nuke old records — see README §Production
deployment "Schema migrations."
## What does the "spool" actually do?
When the host MES disconnects, the equipment can't deliver
unsolicited S5F1 alarms / S6F11 events. Without spool they'd be
lost.
With spool enabled (`SpoolStore::set_spoolable_streams({5, 6})`),
those frames queue to in-memory FIFO (and persistent disk if
`enable_persistence` is set). On the host's next SELECT, the
equipment emits `S6F25 SpoolDataReady(count)`; the host issues
`S6F23(Transmit)` to drain, or `S6F23(Purge)` to discard.
It's the GEM equivalent of an outbox. See E30 §6.22 and our
SpoolStore source.
## How is "robustness fuzz" different from "libFuzzer"?
- **Robustness fuzz** (`tests/test_robustness_fuzz.cpp`) is a
*model-level* property test. It picks random tool operations
(PJ create, alarm set, substrate move, …) respecting FSM
legality, and checks invariants after each.
- **libFuzzer** (`apps/fuzz_*.cpp`) is a *byte-level* coverage-
guided fuzzer. It feeds arbitrary bytes to the codec and
asserts no crash / UB.
They cover different concerns: robustness fuzz catches *semantic*
bugs (lost data, wrong state); libFuzzer catches *parser* bugs
(crashes, UB, buffer overruns).
## What's "conformance" vs "interop"?
- **Conformance** (`build/secs_conformance`) is *us* driving *us*
through every claimed E30 capability and asserting the spec-
mandated reply S/F. Catches our regressions against our own
understanding of the spec.
- **Interop** (`interop/*.py`, `interop/secs4j/*.java`,
`interop/tshark_validate.sh`) is third-party tools agreeing on
the wire bytes our equipment produces. Catches "we got the
spec wrong" — which conformance can't.
Both are necessary; neither replaces the other. See VERIFICATION.md.
## How do I bring this to a customer site?
Run through the five external proofs in
[the eight commands in PROOFS.md](PROOFS.md) at
the customer's network. Then walk MES_INTEROP.md against their
actual MES. Then deploy per [SECURITY.md](SECURITY.md) for the
nftables / stunnel / signing setup. Then page on the metrics from
INTEGRATION.md §6.4.
## What's not implemented?
See [COMPLIANCE.md](COMPLIANCE.md) §8 ("Explicitly out of scope")
for the honest list. The short version: tool-specific Equipment
Processing States (the engine is there, vendors plug in their
states), the serial-port driver for SECS-I (the FSM is wired
end-to-end over TCP, the asio `serial_port` glue is a deferred
follow-up), and GEM RTS certification (paid third-party gate, not
a code feature).