2. Run the catalog

The built-in rules, how to read a finding, and how to fail a build on one.

agni check runs the shipped rule catalog over one design. These are the general rules, true of most boards regardless of whose board it is. Your team’s own rules come later, in rungs 4 through 8. Run it from the board’s folder, as in rung 1.

Run it

$ agni check .
findings by rule:
  bulk-cap               1
  esd-protection         10
  floating-input         3
  i2c-pull-up            30
  i2c-redundant-pull-up  4
  rail-not-classified    1
  reverse-blocking-absent 1
  single-pin-net         2
  test-point-coverage    27

first 50:
  [warning] bulk-cap: Net-(D1-C) (power rail has no bulk capacitor)
  [info] esd-protection: /SFP/SFI_CONN_RX_N (externally-exposed signal net has no ESD protection)
  [info] esd-protection: /SFP/SFI_CONN_RX_P (externally-exposed signal net has no ESD protection)
  [info] esd-protection: /SFP/SFI_CONN_TX_N (externally-exposed signal net has no ESD protection)
  [info] esd-protection: /SFP/SFI_CONN_TX_P (externally-exposed signal net has no ESD protection)
  [info] esd-protection: /SFP/SH (externally-exposed signal net has no ESD protection)
  [info] esd-protection: Net-(J12-RS0) (externally-exposed signal net has no ESD protection)
  [info] esd-protection: Net-(J12-RS1) (externally-exposed signal net has no ESD protection)
  [info] esd-protection: Net-(J12-RX_{LOS}) (externally-exposed signal net has no ESD protection)
  [info] esd-protection: Net-(J12-TX_{DISABLE}) (externally-exposed signal net has no ESD protection)
  [info] esd-protection: Net-(J12-TX_{FAULT}) (externally-exposed signal net has no ESD protection)
  [warning] floating-input: /USB DP Alt mode/USBC1_DPEQ0 (net carries only input pins; nothing drives it)
  [warning] floating-input: /USB DP Alt mode/USBC1_EQ0 (net carries only input pins; nothing drives it)
  [warning] floating-input: /USB DP Alt mode/USBC1_SSEQ0 (net carries only input pins; nothing drives it)
  [error] i2c-pull-up: /CSI/I2C_MUX_SCL (I2C net has no pull-up resistor to a rail)
  [error] i2c-pull-up: /CSI/I2C_MUX_SDA (I2C net has no pull-up resistor to a rail)
  [error] i2c-pull-up: /CSI/I2C_{CAM}.SCL (I2C net has no pull-up resistor to a rail)
  [error] i2c-pull-up: /CSI/I2C_{CAM}.SDA (I2C net has no pull-up resistor to a rail)
  [error] i2c-pull-up: /CSI/MUX_I2C_4_SCL (I2C net has no pull-up resistor to a rail)
  [error] i2c-pull-up: /CSI/MUX_I2C_4_SDA (I2C net has no pull-up resistor to a rail)
  [error] i2c-pull-up: /CSI/MUX_I2C_5_SCL (I2C net has no pull-up resistor to a rail)
  [error] i2c-pull-up: /CSI/MUX_I2C_5_SDA (I2C net has no pull-up resistor to a rail)
  [error] i2c-pull-up: /CSI/MUX_I2C_6_SCL (I2C net has no pull-up resistor to a rail)
  [error] i2c-pull-up: /CSI/MUX_I2C_6_SDA (I2C net has no pull-up resistor to a rail)
  [error] i2c-pull-up: /CSI/MUX_I2C_7_SCL (I2C net has no pull-up resistor to a rail)
  [error] i2c-pull-up: /CSI/MUX_I2C_7_SDA (I2C net has no pull-up resistor to a rail)
  [error] i2c-pull-up: /Expansion connector/I2C7.SCL (I2C net has no pull-up resistor to a rail)
  [error] i2c-pull-up: /Expansion connector/I2C7.SDA (I2C net has no pull-up resistor to a rail)
  [error] i2c-pull-up: /Expansion connector/I2C8.SCL (I2C net has no pull-up resistor to a rail)
  [error] i2c-pull-up: /Expansion connector/I2C8.SDA (I2C net has no pull-up resistor to a rail)
  [error] i2c-pull-up: /SoM_IO/I2C0_SCL_1V8 (I2C net has no pull-up resistor to a rail)
  [error] i2c-pull-up: /SoM_IO/I2C0_SDA_1V8 (I2C net has no pull-up resistor to a rail)
  [error] i2c-pull-up: /SoM_IO/I2C1_SCL_1V8 (I2C net has no pull-up resistor to a rail)
  [error] i2c-pull-up: /SoM_IO/I2C1_SDA_1V8 (I2C net has no pull-up resistor to a rail)
  [error] i2c-pull-up: /SoM_IO/I2C2_SCL_1V8 (I2C net has no pull-up resistor to a rail)
  [error] i2c-pull-up: /SoM_IO/I2C2_SDA_1V8 (I2C net has no pull-up resistor to a rail)
  [error] i2c-pull-up: /SoM_IO/I2C3_SCL_1V8 (I2C net has no pull-up resistor to a rail)
  [error] i2c-pull-up: /SoM_IO/I2C3_SDA_1V8 (I2C net has no pull-up resistor to a rail)
  [error] i2c-pull-up: /SoM_IO/I2C5_SCL_1V8 (I2C net has no pull-up resistor to a rail)
  [error] i2c-pull-up: /SoM_IO/I2C5_SDA_1V8 (I2C net has no pull-up resistor to a rail)
  [error] i2c-pull-up: /USB Debug, PD/FTDI_CBUS0{slash}SDA (I2C net has no pull-up resistor to a rail)
  [error] i2c-pull-up: /USB Debug, PD/FTDI_CBUS3{slash}SCL (I2C net has no pull-up resistor to a rail)
  [error] i2c-pull-up: /USB Debug, PD/PDC_I2C3_SCL (I2C net has no pull-up resistor to a rail)
  [error] i2c-pull-up: /USB Debug, PD/PDC_I2C3_SDA (I2C net has no pull-up resistor to a rail)
  [warning] i2c-redundant-pull-up: /I2C_{SYS}.SCL (I2C net has 2 pull-up resistors to +3V3)
  [warning] i2c-redundant-pull-up: /I2C_{SYS}.SDA (I2C net has 2 pull-up resistors to +3V3)
  [warning] i2c-redundant-pull-up: /USB Debug, PD/DEBUG_SCL (I2C net has 2 pull-up resistors to +3V3)
  [warning] i2c-redundant-pull-up: /USB Debug, PD/DEBUG_SDA (I2C net has 2 pull-up resistors to +3V3)
  [warning] rail-not-classified: /USB Debug, PD/FTDI_3V3 (net "/USB Debug, PD/FTDI_3V3" declares 3.3V in its name and feeds 4 supply pin(s) (e.g. U31 pin 3), but carries no rail role, so the rail rules and net.nominal_voltage skip it. If this project names rails off the built-in vocabulary, declare its patterns in the project's conventions lexicon)
  [warning] reverse-blocking-absent: VCC_IN (connector feeds a power input with no reverse-blocking element in the path)

79 finding(s) total
24117 subject(s) considered by 35 rule(s), 615 not considered (--verdicts for the detail)

Reading one finding

Take [error] i2c-pull-up: /SoM_IO/I2C0_SCL_1V8 (I2C net has no pull-up resistor to a rail). It has four parts.

The severity is error. The rule is i2c-pull-up. The subject is the net /SoM_IO/I2C0_SCL_1V8. It names the specific thing on your board that the rule is about, and its prefix names the sheet where the net is drawn. The reason in parentheses says what is wrong in plain language.

Severity is a policy signal, not a confidence signal. An error is something that will almost certainly not work, such as an I2C bus with no pull-up, which cannot signal at all because the parts on it can only pull the line down and nothing pulls it back up. A warning is something that usually indicates a defect. An info is worth a look. None of them is a statement about how sure the tool is.

Now look at the net itself. It joins three pins, one on the module connector J1, one on R357, and one on U1, an NTS0104 level translator. R357 is a 2.2 kOhm pull-up, and the schematic marks it do-not-populate, so the rule does not count it. The translator carries 10 kOhm pull-ups of its own inside the package. That fact comes from its datasheet and appears nowhere in the netlist. So the finding is right about what the drawing shows, and the board most likely works. Rung 6 closes that gap by turning a datasheet fact into something a check can read.

Most findings on a real board read like this one. The rule is correct about the netlist, and whether it matters depends on something the netlist does not carry.

Where a finding came from

The text form is a summary. --format json gives you the whole thing:

agni check . --format json
{
  "rule": "i2c-pull-up",
  "severity": "error",
  "subject": {
    "kind": "net",
    "ref": "/SoM_IO/I2C0_SCL_1V8",
    "pin": "",
    "netId": "93b92cc31d78",
    "busId": ""
  },
  "message": "I2C net has no pull-up resistor to a rail",
  "inconclusive": false,
  "provenance": {
    "sourceFile": "jetson-agx-thor-baseboard.kicad_sch",
    "span": null,
    "nativeId": "",
    "nativeIdKind": ""
  },
  "sheets": [
    "/SoM_IO"
  ],
  "locateReason": "LOCATE_REASON_UNSPECIFIED",
  "datasheets": [],
  "context": []
}

provenance points back at the design’s source file, and sheets names the sheet where the net is drawn. Together they take you from a finding to the thing in your CAD tool. datasheets is empty here. On a datasheet-backed finding it carries the cited page and table, and rung 6 covers it. inconclusive marks a check that ran but could not decide. That matters enough to get its own rung later.

Two other formats are useful early. --format markdown organizes by severity, worst first, for pasting into a review. --format report is the long form with each rule’s full explanation.

Narrowing

Run one rule while you work on it:

agni check . --rule i2c-pull-up

Or a whole category:

agni check . --tag category=power

Failing a build

--fail-on makes check exit non-zero when anything at or above a severity is present. That flag is all you need to put it in CI:

$ agni check . --fail-on error
exit 2

The board has thirty error findings, all of them i2c-pull-up, so the command fails. A rule with no errors passes the same gate:

$ agni check . --fail-on error --rule test-point-coverage
exit 0

Starting at --fail-on error is the practical choice. It gates on the things that will not work at all, which almost nobody argues with, and it lets you tighten to warning later once the backlog is clear. On a board like this one the first job is reading those thirty, because as the finding above showed, an error can be right about the netlist and still not be a defect.

What the run says it looked at

Look at the last line of that first run again. Every run ends with it, whether or not it found anything:

24117 subject(s) considered by 35 rule(s), 615 not considered (--verdicts for the detail)

A findings list cannot give you this half. Picture two runs. One finds nothing. In the other, every rule examined the wrong thing. Both produce an identical list of findings, namely none, so the findings alone can never tell you which one you are holding.

Read the three numbers separately. 24117 considered is how many subjects were actually judged. 35 rules is how many were willing to say what they looked at. That is not the same as how many ran. Most of the catalog has no subject in scope on any given board, and a rule with nothing to say is not a gap. 615 not considered is the one worth reading closely, and it gets its own look below.

A second line can appear under that one:

2 rule(s) reported violations without stating what they examined, so silence from those is not evidence of anything

It counts rules that found something and never said what they looked at, so silence from them means nothing and the coverage number does not cover them. It is printed whenever the count is above zero. On this board it reads zero and is omitted, and the same happens for the sub-sheet read in rung 1. You will see it on a run that includes rules of your own written without a considered set. When it appears, it marks the limit of the coverage claim above it.

That line is the claim. --verdicts is the evidence, one row per subject with passes included:

$ agni check --verdicts --rule decoupling-present .
decoupling-present  10 pass
    pass  +20V                           capacitor C263 sits on the rail
    pass  +3V3                           capacitor C10 sits on the rail
    pass  +3V3_AON                       capacitor C197 sits on the rail
    pass  +5V                            capacitor C104 sits on the rail
    pass  +5V_AON                        capacitor C223 sits on the rail
    pass  /Expansion connector/+3V3_AUX  capacitor C5 sits on the rail
    pass  /USB Debug, PD/FTDI_3V3        capacitor C133 sits on the rail
    pass  FLASH_PWR                      capacitor C111 sits on the rail
    pass  VCC                            capacitor C241 sits on the rail
    pass  VCC_IN                         capacitor C269 sits on the rail

10 verdicts across 1 rule(s), 10 pass

Now the pass is checkable. It names C10 on +3V3 and C241 on VCC, so you can open the schematic and confirm that those capacitors really are on those rails. Delete one next revision and this output changes, where the findings-only view would print the same nothing before and after.

Every row passes here, so the fail column reads nothing. A net whose only supply pin is on a connector would have no row at all, because the rule leaves it out rather than passing it, and the count of rows tells the two apart.

Across the whole catalog, that table is much larger than the findings list. So the summary is the default and the rows are a flag:

$ agni check --verdicts .
24732 verdicts across 35 rule(s), 24038 pass, 79 fail, 615 not-considered

not-considered is the third outcome, and a findings list has no counterpart to it. It means the rule was willing to judge that subject and something stopped it, so it says what stopped it rather than passing on incomplete evidence.

On this board most of the 615 are one of three kinds. 319 are netclass-via-drill declining nets that carry no via, since such a net has no drill to compare. 154 are floating-input declining a net that carries a passive part, because a resistor on a net might be the pull that fixes it, might be a series element with the driver on the far side, or might be a footprint nobody stuffed, and a netlist cannot tell those apart. 111 are rail-not-classified declining a net whose name declares a voltage while no power-input pin on it confirms the net is a rail. A datasheet fills none of those, because they mark where the check’s reach ends.

The whole tool separates “checked and fine”, “never checked” and “could not tell”, and rung 9 is about reading that distinction.

Next

See it, because a list of net names is not how anyone thinks about a board.