external_signal_net

a connector-facing signal net (not a rail, ground, no-connect, or power path), the scope the ESD rules share

What it is

external_signal_net(net) yields one row per net that leaves the board through a connector and is a SIGNAL, not power. It is the scope both ESD rules share, projected so a datalog-authored check can select the same nets the Go rules do.

A net qualifies when a connector sits on it and none of the exclusions apply: it is not a rail or ground (by name or by fact), not marked global or power-driven, not a deliberately unconnected pad, and no power pin is reachable from it through the series walk.

For hardware engineers

This is the set of lines an ESD review is actually about. A signal that leaves the board is a path for a discharge to travel into whatever drives it, and the parts on the far end of a connector are the ones a person touches.

The exclusions are what keep the question honest. A power rail arriving on the same connector is a different review with different rules (input protection: fuses, reverse blocking, inrush), so it is subtracted here rather than lumped in. A ground pin needs no clamp. A pad wired to nothing on purpose is not an exposure. Querying this relation on a real board is the fastest way to check that the engine agrees with you about which lines are exposed, before trusting any ESD verdict built on top of it.

For software engineers

A filtered projection over Nets(), 1:1 with in-scope nets. Empty on a design with no connectors, which is a genuine answer rather than a permissive one: a board that exposes nothing has no ESD question to fail.

It is the one part of the ESD guard stack that could not be composed from other relations. The protection predicates are reachability questions, and they became plain datalog once reaches carried distance (WS3-112):

reaches(?n, ?rn, ?h), ?h <= 2, component-on-net(?t, ?rn), component.class(?t, "tvs")

The scope could not, because its guards read net ATTRIBUTES (global, power_driven) and the no-connect channel, none of which have a relation of their own. Reassembling it clause by clause in datalog would eventually drop one, and a dropped guard here is a false FAIL on a rail or an unconnected pad rather than a missed defect.

Go projector

externalSignalNetFacts in stdlib/relations/facts.go walks Model.Nets() and emits a row where check.ExternalSignalNet holds. That function lives in core/check/guards.go beside the guards it calls, and it is the SAME predicate esd-protection and esd-clamp-not-tvs evaluate, so a datalog check and the Go rules cannot disagree about scope. One row per in-scope net; zero rows when the design has no connector-facing signals.

Datalog

Every net an ESD review is about:

external_signal_net(?n) => ?n

The unprotected ones, matching the shape the esd profile requirement compiles to. Nothing clamping within two series crossings:

external_signal_net(?n),
not_clamped(?n)
=> ?n

written out, since not_clamped is not built in:

clamped(?n) :- reaches(?n, ?rn, ?h), ?h <= 2, component-on-net(?t, ?rn), component.class(?t, "tvs");
exposed(?n) :- external_signal_net(?n), not clamped(?n);
exposed(?n) => ?n

Which parts sit on the exposed lines, for a quick read of what is at risk:

external_signal_net(?n), component-on-net(?r, ?n) => ?n, ?r