Skip to content

Lints

lintRules([SEC_01])

Returns warnings. They never fail a build, the CLI prints them to stderr, and they never affect its exit code — a warning is advice, and advice that can break your pipeline stops being advice.

Code Meaning
extreme-bound A window above 10,000 events. Obligation state is O(k), so a very wide window is usually a mistake rather than an intention.
unknown-event-kind A matcher naming a kind outside the current event model.

unknown-event-kind is deliberately a warning rather than an error. A rule set written against a newer adapter must still load on an older engine, matching nothing rather than failing to parse — which is the same forward-compatibility stance propositions takes.

lintRules reads a rule set alone, so it cannot know whether restartService is a tool you have. lintNames can, given the tools and steps that actually exist:

lintNames(SPEC, { tools })

Whatever you already hold counts as a tool set — a list of ids, a list of the tools themselves, the two mixed, or the record you handed the guard. A record is read the way the guard reads it, tool.id ?? key, so the lint cannot disagree with the thing it is checking.

lintNames(SPEC, { tools: ['queryMetrics', restartService], steps: workflow.steps })
Code Meaning
unknown-tool A rule or check names a tool nothing provides.
unknown-step A rule names a step the workflow does not have.

This is the drift the type system cannot reach. Passing the tool itself makes a rename a compile error — but a rule set loaded from JSON has only names, and tools built per run by a factory have no symbol to point at. In both cases a renamed tool leaves a rule matching something nothing emits, and such a rule does not fail. It goes quietly satisfied forever, which reads exactly like a rule that is working.

A near-miss is named, because most of these are typos:

no tool called 'restartServcie' is available, so this rule can never fire — did you mean 'restartService'?

Only the dimension you supply. Pass tools alone and steps are left alone entirely — a tool guard knows nothing about steps, and flagging every step would make the lint useless in the place it is easiest to run.

Nothing without a name. P.toolCall() is about every call, and a custom predicate carries code rather than a name. Neither can go stale, so neither is checked.

A warning, never an error — for the same reason the others are. A rule about a tool you deliberately do not guard in this run is a legitimate thing to have, and a check that refused it would be wrong more often than right. Where you want it to bite, assert on it: expect(lintNames(SPEC, { tools })).toEqual([]) in the test suite is the place it belongs.

For the questions a linter cannot answer — whether a rule can ever fire, whether two rules can hold at once, what the shortest trace that breaks one looks like — see static analysis, which reasons with a solver rather than by inspection.