The three layers
The operator gate has no command. A person runs it before a work step closes, and it:
- runs against a test organisation, never production
- cleans up whatever it creates
- records its results on the issue
The harness shape
A single harness file declares the test server, its two variants, and the fetch helpers that call handlers over the wire. A test file imports these and never declares its own, so every scenario boots the server and reads a response the same way. The two variants boot the same services with different server options:alwaysReplayforces the server to suspend and replay the handler at every step, so a handler that is not deterministic on replay fails here.disableRetriesfails a thrown handler error at once instead of retrying it, so an error path surfaces without waiting out a backoff.
Databases
Each CI run gets a fresh Neon branch, created from a cleantest branch that already carries seed data.
Credentials in CI
CI never holds a credential that can create real infrastructure, such as a Fly organization token. Any check that needs one of those credentials belongs to the operator gate, where a person runs it — not a CI step.When to write which test
Ask one question of the assertion: with Restate removed, would it still mean something?- Yes — write a unit test — parsing, a branch, an error-to-status mapping, a wire shape.
- No — write a Restate scenario. The assertion is about what the engine does with the handler: serialisation, retry, idempotency, suspension, replay determinism.
- Neither can be faked — use the operator gate. The check needs a real third-party system and runs by hand.
- A slow time boundary stays in the unit tier. Factor a real wait into a pure function and test that function, rather than making a scenario wait out the clock.