Design intent

Declare what a board is supposed to contain, and have every revision checked against the architecture you agreed rather than against itself.

A netlist says what is wired. It cannot say what was meant. “The board has two regulators” is a fact you can read off the file; “the board should have two regulators” is a decision somebody made in a meeting, and nothing in the export records it.

A design-intent declaration writes that decision down as YAML beside the design, and check compares the board against it. It is the tier that catches a rail quietly moved to the wrong domain, a module dropped during a cost reduction, or a power-up order that got reversed when the power tree was redrawn. None of those look wrong in the schematic. They look wrong against what you said you were building.

Write a declaration

# designs/gateway/intent.yaml
name: gateway intent
modules:
  - {name: regulators, class: regulator, count: 2}
  - {name: connectors, class: connector, count: 1}
voltage_domains:
  - {name: main, nominal: 12.0, rails: [PMIC_MAIN_12V0]}
  - {name: io,   nominal: 3.3,  rails: [PMIC_CORE_3V3]}
  - {name: core, nominal: 3.3,  rails: [PMIC_IO_1V8]}
subsystems:
  - {name: power tree, nets: [PMIC_MAIN_12V0, PMIC_CORE_3V3, PMIC_IO_1V8]}
  - {name: can, nets: [CAN1_CANH, CAN1_CANL, CAN1_TXD, CAN1_RXD]}

That last voltage domain is wrong on purpose, and it is the whole point of the file. core is declared at 3.3 V while the rail it names is PMIC_IO_1V8, an actual 1.8 V rail. Nothing about the schematic is malformed. The board simply stopped matching the architecture, and only a declaration can notice.

Where the file goes

Beside the design, named intent.yaml. A design that belongs to a project needs no flag, because the descriptor defaults that name and the project finds it:

$ agni check designs/gateway --rule intent/voltage-domain-mismatch
findings by rule:
  intent/voltage-domain-mismatch 1

first 1:
  [warning] intent/voltage-domain-mismatch: PMIC_IO_1V8 (rail "PMIC_IO_1V8" is declared in voltage domain "core" (3.3V) but its name declares 1.8V)

1 finding(s) total
3 subject(s) considered by 1 rule(s) (--verdicts for the detail)

Intent is per-design, unlike naming conventions, interface profiles and seeded parameters, which are per-project. Each board has its own intended architecture, so the file lives with the board rather than with the team.

--intent-path exists for a design that belongs to no project. Reaching for it on a design whose project already declares intent is an error rather than a silent double-load, so you find out immediately instead of reading every finding twice.

The nine forms

Each form answers a question the netlist cannot, and each compiles to its own rule so a reviewer signing them off separately gets separate verdicts.

Form Declares Fails when
modules the functional blocks the board must contain, by class or MPN, optionally with a count a declared module is absent, or the count is short
voltage_domains named rails pinned to a nominal voltage a declared rail is missing, or sits on the wrong domain
subsystems a named architectural block and the nets it must instantiate its source component is absent, or any declared net is missing
protections a rail that must carry a protection device, by kind the declared rail carries no device of that kind
net_properties what a net is, rather than that it exists (a reset is active-low) the design’s structure contradicts the declaration
rail_budgets the peak current a rail draws, with an optional margin_factor the supply reaching it is rated below the peak, or below the margin
sequences the power-up order of groups of rails the gating chain is absent, or runs the other way round
strap_groups several strap nets read together as one binary number, and the value it encodes the group does not encode the declared value, or two devices collide
io_map which net lands on which pin of which device, and optionally what sits at the far end the net is on a different pin, the declared net is absent, or the far end is wrong

io_map is the largest of them in practice and the one most boards already have, usually as a spreadsheet. On any board carrying a big MCU or SoC, someone decides which peripheral lands on which pin long before the schematic exists, firmware is written against that decision, and the schematic is drawn from it. What goes wrong is ordinary: an assignment moves late, the map is updated, and one net does not get redrawn. Nothing about the resulting board is electrically wrong, so every other rule passes, and it surfaces at bring-up as a peripheral that does not respond.

io_map:
  - net: I2C_SDA
    device: U3
    pin: '9'
  - net: MCU_NRST
    device: U3
    pin: PTC11                     # the datasheet's name works as well as the designator
    to: {device: U1, pin: '5'}     # optional far end

Write the pin either way. A map is authored in the vocabulary a datasheet and a firmware header use, and a netlist answers in package designators, so both are resolved: PTE7, PTE07, pte7 and a name carrying a zero-width space pasted out of a spreadsheet all reach the same pin. A match that needed any of that says so in the verdict, so a note always means something was inferred.

It compiles to four rules rather than one. Three of them ask whether the design kept the promises the map made, and the fourth asks the opposite question: which nets the map never mentioned. That number is usually the important one. A design with sixteen hundred nets and a map declaring two hundred has two hundred checked and fourteen hundred unexamined, which is not the same as clean, and nothing else in a run tells those apart. Nets it does not name report as not-considered rather than as failures, because an undeclared net is a question nobody asked and not a fault in the board.

The three that check the promises are separated because a reviewer acts on them differently, and because two of them are opposite defects: a net the map declares and the netlist does not have is usually a real disconnection, where a net the netlist has and the map does not declare is an incomplete map. function is accepted and NOT yet evaluated, since deciding whether a function is legal on a pin needs the part’s alternate-function table; every verdict on a row carrying one says so outright.

rail_budgets is the one that joins two tiers. The declaration supplies the demand, which no design artifact carries, and a seeded datasheet parameter supplies the regulator’s capacity. Both halves have to be present or the rule stays quiet rather than guessing.

With no declaration, nothing passes

There is no built-in intent, and that absence is deliberate. A generic statement of what a board should contain says nothing, and a rule that enumerated its expectations from the design would always agree with the design. So every intent rule iterates the declaration and probes the netlist, never the reverse.

The consequence is worth knowing before you read a report. A design run with no declaration leaves its intent-bound review items reading needs-design-intent, never pass. The mechanism exists and is blocked on an input you have not supplied yet, which is a different thing from a board that was checked and found clean. Checks and reports covers the full outcome vocabulary.

What intent cannot do

It checks presence, property and order, over the netlist. It does not simulate. A declared sequence is verified as a gating chain in the connectivity, not as timing on a scope, so a board that wires the enables correctly and still browns out at power-up is outside what a declaration can see.

It is also only as good as the names. A rail the declaration calls PMIC_CORE_3V3 has to be called that on the board, which is the same dependency naming conventions exist to make explicit rather than accidental.

Where to go next

  • Checks and reports: what needs-design-intent means beside the other outcomes, and how --fail-on treats them.
  • Interface profiles: the other declarative tier, per-project rather than per-design, for the shape of a bus rather than the shape of a board.
  • How a rule gets written: where an intent declaration sits among the four ways to author a rule, and why the engine ships none of its own.