Property-based robustness test that drives long sequences of random
tool operations against EquipmentDataModel and verifies invariants +
persistence round-trip after every action. Replaces hand-written
state-pinning tests with a generative approach that explores
combinations no human author would think to write.
Action menu (28 weighted actions covering the full standard surface):
- PJ create / event / dequeue (E40)
- CJ create / event / delete (E94)
- Carrier create / id / slot (E87)
- Substrate create / location / proc (E90)
- Alarm set / clear / enable toggle (E5 §13)
- SVID updates (E30 §6.13)
- Define-report / link-event / enable (E30 §6.6)
- Exception post / recover / complete (E5 §9, S5F9-F18)
- Module event (E157)
- EPT event (E116)
- Spool enqueue / drain / force-toggle (E30 §6.22)
Every action is "adjusted": it picks a verb at random, then checks
state-machine legality before applying. A Pause is only fired on a
Processing PJ; a Recover only on a Posted exception; pj_dequeue
skips PJs bound to active CJs (mirrors E94's "can't dequeue
CJ-bound PJ" rule the fuzz itself discovered when the first run
flagged a CJ→missing-PJ reference).
Invariants checked every 64 ticks:
- Every tracked PJ exists in the store (size matches)
- Every CJ's prjobids all exist in PJ store
- No FSM in NoState sentinel
- EPT bucket total monotonically non-decreasing
- Defined reports' VIDs all exist
- Substrate / carrier counts match enumeration
Persistence round-trip every 500 ticks:
- Fresh shadow EquipmentDataModel loads from the same journal dir
- Diffs PJ + CJ states one-by-one + carrier/substrate/exception
counts against the live model
- Catches any "mutation didn't reach disk" or
"replay didn't reconstruct state correctly" bugs
Reproducibility:
- Each TEST_CASE uses a fixed seed (0x1, 0xdeadbeef, 0xfeedface,
0xc0ffee — 8000 ops total in the fast suite)
- World keeps a rolling 20-action trace, printed on invariant
violation so the failing sequence can be pasted into a targeted
regression test
- SECSGEM_ROBUSTNESS_SOAK=1 enables a 100k-tick soak case
(~3-5 minutes in Docker; not run by default)
The very first run found a real edge case: act_pj_dequeue removed
PJs that were bound to active CJs, leaving dangling refs. Fixed
the fuzz to filter; the underlying behavior is intentional (store
trusts the application to gate), but the fuzz now mirrors the
correct E94 contract.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>