# 19 — E42 + E148 + S9 + exception recovery ← [18 E84 — Parallel I/O handoff](18_e84_parallel_io.md) | [Back to index](00_index.md) | Next: [30 Repository tour](30_repository_tour.md) → Three remaining standards-shaped concerns to round out Part 2: - **E42** — Formatted (enhanced) Process Programs. - **E148** — Time synchronization. - **S5F9–S5F18** — Exception recovery (E5 §13 + GEM Additional). Each is narrow enough that a half-chapter would do. Together they round out the GEM 300 picture. --- ## E42 — Formatted Process Programs ### What it is E30's Process Program Management (chapter 13) covers **unformatted** recipes: the PPBODY is opaque bytes that only the equipment knows how to parse. E42 adds **formatted** PPs — the recipe has a typed SECS-II structure the host can introspect. In practice, formatted recipes are a fab-internal standard rather than a SEMI-defined schema. E42 just gives the wire shape; the fab agrees what the structure means. ### The messages | S/F | Direction | Purpose | |-------|-----------|---------------------------------------------------------------| | S7F23 | H → E | Formatted PP Send. Body: PPID + typed SECS-II body. | | S7F24 | E → H | ACKC7 reply. | | S7F25 | H → E | Formatted PP Request. Body: PPID. | | S7F26 | E → H | Formatted PP Send (back). Body: PPID + typed body. | Compare to unformatted S7F3 / S7F5: same direction pattern, just a typed body instead of opaque bytes. ### Implementation [`RecipeStore`](../include/secsgem/gem/store/recipes.hpp) carries **both** views per recipe: an unformatted PPBODY (opaque bytes) and an optional formatted body (a `secs2::Item` tree). S7F3 sends the unformatted; S7F23 sends the formatted; both are stored side by side and the host can request either via S7F5 (unformatted) or S7F25 (formatted). Tests: [`tests/test_e42_formatted_pp.cpp`](../tests/test_e42_formatted_pp.cpp) (6 cases — send formatted, request back, round-trip integrity, ACKC7 error paths). ### Why both? Some MES — and some equipment — only speak unformatted PPs. Coexistence lets a vendor ship one EAP that handles both. COMPLIANCE §4j has the audit detail. --- ## E148 — Time synchronization ### What it does In a multi-tool fab, the host and the equipment need a **common notion of time** for timestamp correlation. If tool A logs an alarm at 14:32:01 and tool B logs a related alarm at 14:31:58, did B precede A or did the clocks drift? E148 defines the wire mechanism for keeping equipment clocks in sync with a host-authoritative time source — typically NTP behind the scenes — and lets equipment **report clock quality** so the host knows how much to trust the timestamps coming off the tool. ### The messages E148 doesn't add new streams; it specialises the existing E30 clock messages: | S/F | Direction | Purpose | |-------|-----------|----------------------------------------------------------| | S2F17 | H → E | Read clock. | | S2F18 | E → H | Reply with current time string. | | S2F31 | H → E | Set clock to specified time string. | | S2F32 | E → H | TIACK reply. | The time string is **16 ASCII chars `YYYYMMDDhhmmsscc`** (E148 extended form, including hundredths). 14-char `YYYYMMDDhhmmss` (without hundredths) is the older E30 form; the codebase **accepts both on set** but emits 16 chars on read by default. See [`docs/COMPLIANCE.md`](COMPLIANCE.md) §4g. ### Clock store [`include/secsgem/gem/store/clock.hpp`](../include/secsgem/gem/store/clock.hpp) holds the wall-clock plus a **drift / quality** indicator: - `Drift_ms`: cumulative drift since last set. - `Quality`: enum from {Authoritative, GoodNTP, FreeRunning, Unreliable}. A host can read both via E120/E39 attribute access (chapter 17) or via DVID exposures (the EAP wires them). ### Why this matters Without clock sync, **alarm root-cause analysis is impossible**. SPC charts get the X-axis wrong. Yield correlations across tools fall apart. Most modern fabs run NTP on every tool's control-plane host; E148 is the mechanism for *reporting* clock state, not for synchronising it (that's NTP's job). --- ## Exception recovery — S5F9–S5F18 ### What it adds beyond base alarms E5 §13 + E30 Alarm Management (covered in chapter 13) handles alarms as **set/clear** events: an alarm goes active, the equipment fires S5F1; later it clears, equipment fires another S5F1. Simple. But some alarms aren't simple to clear. A vacuum leak that required a chamber vent + manual seal replacement can't just "go away" — the equipment has to run a recovery procedure with the host's involvement. **S5F9–S5F18** is the **exception recovery** family that handles that. ### The exception lifecycle Defined in [`include/secsgem/gem/exception_state.hpp`](../include/secsgem/gem/exception_state.hpp): ```cpp enum class ExceptionState : uint8_t { Posted = 0, // S5F9 sent; awaiting host action Recovering = 1, // S5F13 accepted; recovery in progress RecoverFailed = 2, // S5F15 reported failure; retry possible Cleared = 3, // resolved; terminal }; ``` Events: `Created` (NoState → Posted), `Recover` (host's S5F13), `RecoveryComplete`, `RecoveryFailed`, `RecoveryAbort` (host's S5F17), `Clear`. ### The messages | S/F | Direction | Purpose | |-------|-----------|--------------------------------------------------------------| | S5F9 | E → H | Exception Post. Equipment-initiated. Body: EXID + EXTYPE + EXMESSAGE + recovery-method list. | | S5F10 | H → E | Ack. | | S5F11 | E → H | Exception Clear. Equipment-initiated when condition resolves. | | S5F12 | H → E | Ack. | | S5F13 | H → E | Exception Recover. Body: EXID + which recovery method to attempt. | | S5F14 | E → H | Recovery progress. | | S5F15 | E → H | Recovery Complete (or Failed). | | S5F16 | H → E | Ack. | | S5F17 | H → E | Exception Recover Abort. Cancel an in-progress recovery. | | S5F18 | E → H | Recovery Aborted reply. | The flow: ``` 1. Vacuum leak detected. EAP calls exceptions.post(EXID=42, recovery=["vent","seal","pump-down"]). → ExceptionState: NoState → Posted. → S5F9 fires. 2. Host sees S5F9 → operator decides to attempt recovery → host sends S5F13(EXID=42, method="vent"). → ExceptionState: Posted → Recovering. 3. Recovery in progress. EAP fires S5F14 periodically with progress. 4. EAP completes the venting step. Fires recover_complete event. → ExceptionState: Recovering → Cleared. → S5F15 fires. 5. Host acknowledges (S5F16). EAP fires S5F11 to confirm the underlying condition is gone. ``` Or, the abort path: ``` 3'. Operator decides recovery isn't working → host sends S5F17. 4'. ExceptionState: Recovering → Posted. 5'. EAP can be re-instructed via another S5F13 with a different method, or the condition can clear autonomously (Clear event → Cleared state). ``` ### Code State machine: [`include/secsgem/gem/exception_state.hpp`](../include/secsgem/gem/exception_state.hpp). Store: [`include/secsgem/gem/store/exceptions.hpp`](../include/secsgem/gem/store/exceptions.hpp) — persistent, so an exception in flight survives a power cycle. Tests: [`tests/test_exceptions.cpp`](../tests/test_exceptions.cpp) (11 cases) + persistence in [`tests/test_exception_persistence.cpp`](../tests/test_exception_persistence.cpp) (5 cases). ### Why this is its own family Two reasons: 1. **State persistence.** Alarms come and go in seconds; exception recovery can span hours and a few power cycles. The store journal lets the equipment remember "we were halfway through recovery method 2 of EXID=42" across restarts. 2. **Multi-step coordination.** Each step (`S5F13` → `S5F14` → `S5F15`) is a host-supervised transaction. Base alarms can't express "host, here are three recovery options, pick one." Exception recovery is an Additional GEM capability — not every MES asks for it — but the codebase implements it because it's upstream-absent in `secsgem-py` (see [docs/COMPLIANCE.md](COMPLIANCE.md) §4k) and because the persistent state machine is a nice example of the spec-as-data pattern applied to a less-trivial FSM. --- ## The auto-S9 family (revisited) We covered the S9 wire-error replies in chapter 11. Worth re-listing here because S9 is part of the **error/exception layer** even though it's transport-level rather than application-level: | Function | Trigger | |----------|----------------------------------------------------------| | S9F1 | Unrecognized Device ID | | S9F3 | Unrecognized Stream | | S9F5 | Unrecognized Function | | S9F7 | Illegal Data (body failed to decode) | | S9F9 | Transaction Timer Timeout (T3 expired) | | S9F11 | Data Too Long (body exceeded configured cap) | | S9F13 | Conversation Timer Timeout (equipment-internal) | Implementation: [`hsms::Connection::emit_s9`](../include/secsgem/hsms/connection.hpp) called from the connection's framing and routing paths. Tested across [`tests/test_hsms_s9.cpp`](../tests/test_hsms_s9.cpp) and [`tests/test_s9_fallback.cpp`](../tests/test_s9_fallback.cpp). Difference from S5F9: S9 is **transport-level** (the bytes themselves were wrong); S5F9 is **application-level** (the equipment can't continue normal operation). --- ## End of Part 2 You now know every SECS/GEM and GEM 300 standard that this codebase implements. Twelve standards across nine chapters, each one mapped to its state machine, its messages, its store, and the tests that hold it down. Part 3 starts. We turn from "what the spec says" to "how this codebase implements it" — repository tour, codegen, the data model structure, transport internals, state-machine composition, persistence mechanics. Next: [→ 30 Repository tour](30_repository_tour.md)