Skip to content

In your tests

The first place to check a property is the test suite you already have.

const verdicts = checkTrace(SPEC, events)
const broken = verdicts.filter((verdict) => verdict.status === 'violated')
expect(broken).toEqual([])

events is whatever your integration test collected from onEvent. checkTrace folds the same monitor over them and hands back the verdicts — the same fold a live run uses, so a test failure and a runtime verdict mean the same thing.

This is the smaller path and it is the one most projects want. No file on disk, no CLI, no corpus to maintain.

const collected: TraceEvent[] = []
const tools = guardTools(monitor, allTools, { source, onEvent: (event) => collected.push(event) })

That is the whole setup. Run your agent against it, then assert.

That the rules you have written pass on a run you believe is good. This catches the rule that is stricter than you meant it, which is the common failure and the one that shows up as a support ticket rather than as a test failure.

That a rule fires on a run you have engineered to break it. A rule nothing has ever broken is a rule you have not tested; ltl analyze witness will synthesise the shortest trace that breaks it if you would rather not write one by hand.

Sequence integrity. events.map((event) => event.seq) equals [0, 1, 2, …], on every trace your tests produce, including runs where calls were refused. Holes and repeats are the failure mode this model is most sensitive to and the easiest to introduce.

That the rules are about tools you actually have.

expect(lintNames(SPEC, { tools })).toEqual([])

A rule naming a tool nothing provides can never fire, and it does not announce itself — it reports satisfied forever, which reads exactly like a rule that is working. lintNames is a warning rather than an error precisely so that you get to decide where it bites, and this is the place: one line, run against the tools your test already built.

When the traces outlive the test that produced them. A rule set that is edited often wants a corpus of runs it is checked against — not to find out what your agent did, but to find out whether this edit still catches what it caught. That is the corpus, and it is a different question from the one this page answers.