The grade ladder
Every verdict carries anhonesty.grade. How strong the evidence actually was.
presence
An element appeared, or text showed up. True, and weak: a mock renders exactly the same.
state
A registered store changed. Now you know the app accepted it, not just drew it.
signal
The app fired a named signal. The app itself declaring success. Nothing beats this.
Three things to add
Everything lives insrc/reticle-dev.ts, which reticle init creates for you and which is dev-only, it self-guards on import.meta.env.DEV and is a no-op in a production build.
1. Register your store
200 without touching state gets caught precisely there, and nowhere else.
2. Emit signals
A signal is your app saying “this specific thing succeeded”. Define the names in one place so they cannot drift:emit(Sig.AUTH_GRANTED, { email }) where the thing actually succeeds. Not where you think it succeeds. In the success branch, after the state update, not in the click handler.
Emit the failures too. auth:denied is as valuable as auth:granted, for a reason the next section demonstrates.
3. Advertise the surface
reticle_capabilities returns. The app telling an agent what it considers testable, before the agent has looked at a single element. It is the cheapest and most truthful first call on an unfamiliar codebase.
Why emitting failure signals pays off
Here is a real failed login, from an app that emits both signals:observed again:
signal ‘auth:granted’ never fired — signals seen in this window: auth:deniedWithout the failure signal, that line would have stopped at “never fired”, and the agent would be guessing between a network problem, a broken handler and a wrong password. With it, the app has named its own outcome: the credentials were rejected. The 401 headline confirms it,
stateDiffs: [] proves nothing was written, and the whole diagnosis arrives in one response.
capsule.firstDivergence and blastRadius are saved to disk automatically on a failure, so the
evidence survives the agent’s context window. Look for capsuleSaved in the response.When Reticle refuses to call it a pass
This is the one that surprises people, so here it is in full. A successful login, on a backgrounded tab:verdict.pass is true. And verified is unknown, because the page never went quiet inside the observation window, so Reticle cannot promise it saw the whole story.
That is the design working as intended. A tool that reports “pass” whenever it happens to catch a matching event, without knowing whether it saw everything, is how you get a confident wrong answer.
Note the last field. When Reticle cannot tell what happened, it invites a bug report and calls the ambiguity its own defect rather than yours. If you hit an unknown you believe is wrong, that is worth sending.
Testids, briefly
Keeping it honest
Signals rot the way every convention rots: someone adds a mutation, forgets the signal, and every verdict for that flow quietly drops fromsignal to presence. Nothing goes red. The tests pass. The evidence just gets weaker, invisibly.
Start with one flow
You do not need to describe the whole app, and trying to is the slow path that gets abandoned halfway.- Pick your most important flow. The one that would be embarrassing to break.
- Register the store it reads.
- Emit a signal where it succeeds, and one where it fails.
- Add testids to the elements it touches.
- Drive it, and check
honesty.gradesayssignal.
Prove it
Use your new signals as predicates and watch the grade climb.
Lock it in
Turn the instrumented flow into a spec that runs on every pull request.