Gate interface certifiers to connectors on this board (2.61.2).

USB-C/RJ45/PoE/DDR3 are the method, not a catalog. No connector means
no finding. Bare RJ45 is Ethernet only; PoE needs PoE evidence. HubAudio
has USB-C and PoE MagJack, not DDR. Skip-I unchanged.
This commit is contained in:
2026-09-21 12:24:54 +02:00
parent c6abdae762
commit bd3a6fd009
14 changed files with 84 additions and 97 deletions
@@ -10,8 +10,6 @@
"PE-USBC-002|J1|/USBC.CC1", "PE-USBC-002|J1|/USBC.CC1",
"PE-USBC-003|J1|", "PE-USBC-003|J1|",
"PE-USBC-004|J1|", "PE-USBC-004|J1|",
"PE-USBC-005|J1|", "PE-USBC-005|J1|"
"PE-ETH-001|PCB|",
"PE-POE-001|PCB|"
] ]
} }
@@ -219,7 +219,7 @@ def _seed() -> None:
_add("PE-ETH-004", "RECOMMENDED", "REVIEW", domain="shared", _add("PE-ETH-004", "RECOMMENDED", "REVIEW", domain="shared",
requirement="Bob Smith 75 Ω is typical for GbE; often inside MagJack.") requirement="Bob Smith 75 Ω is typical for GbE; often inside MagJack.")
_add("PE-POE-001", "TYPICAL", "INFO", domain="shared", _add("PE-POE-001", "TYPICAL", "INFO", domain="shared",
requirement="PoE certifier runs only with PoE evidence; bare RJ45 is N/A.") requirement="PoE certifier runs only with PoE evidence; skip a bare RJ45.")
_add("PE-POE-002", "TYPICAL", "INFO", domain="shared", _add("PE-POE-002", "TYPICAL", "INFO", domain="shared",
requirement="PoE class/type from 802.3af/at/bt or Class n — never invented.") requirement="PoE class/type from 802.3af/at/bt or Class n — never invented.")
_add("PE-POE-003", "MANDATORY", "RULE", domain="shared", _add("PE-POE-003", "MANDATORY", "RULE", domain="shared",
@@ -67,7 +67,11 @@ _OHM_SUFFIX = re.compile(r"^(\d*\.?\d+)([RKMG])$", re.I)
def check_interface_classes(graph: DesignGraph) -> list[Finding]: def check_interface_classes(graph: DesignGraph) -> list[Finding]:
"""USB-C, Ethernet, and PoE class + connection integrity.""" """Certify class + connection integrity for connectors actually on this graph.
USB-C, RJ45/Ethernet, PoE (only with PoE evidence) are the method — not a
catalog. No connector → no that certifier. No invented DDR/PoE/USB3/I/Z.
"""
out: list[Finding] = [] out: list[Finding] = []
out.extend(_check_usb_c(graph)) out.extend(_check_usb_c(graph))
out.extend(_check_ethernet(graph)) out.extend(_check_ethernet(graph))
@@ -79,9 +83,9 @@ def format_interface_class_context(graph: DesignGraph) -> str:
"""Plain FACT block for AI review — not the verification engine.""" """Plain FACT block for AI review — not the verification engine."""
lines = [ lines = [
"### Interface class (deterministic certifiers — do not invent)", "### Interface class (deterministic certifiers — do not invent)",
"USB-C: CC1/CC2, Rp/Rd 5.1 kΩ, VBUS/GND, SuperSpeed only if class is USB3.", "USB-C / RJ45 / PoE only if that connector is on THIS graph. "
"Ethernet: 10/100 vs GbE; magnetics required for GbE. Bob Smith is recommended.", "PoE only with PoE evidence — never because RJ45 exists. "
"PoE: only with PoE evidence. Never invent PoE on a bare RJ45. No Z/I/mm.", "No DDR3 unless a DDR device is present. No invented Z/I/mm.",
] ]
findings = check_interface_classes(graph) findings = check_interface_classes(graph)
if not findings: if not findings:
@@ -98,8 +102,7 @@ def format_interface_class_context(graph: DesignGraph) -> str:
def _check_usb_c(graph: DesignGraph) -> list[Finding]: def _check_usb_c(graph: DesignGraph) -> list[Finding]:
connectors = [c for c in graph.components.values() if _is_usb_c(c)] connectors = [c for c in graph.components.values() if _is_usb_c(c)]
if not connectors: if not connectors:
return [_na("PCB", "PE-USBC-001", "USB-C", return []
"No USB-C receptacle in the netlist (footprint/value/MPN/nets).")]
out: list[Finding] = [] out: list[Finding] = []
for comp in sorted(connectors, key=lambda c: c.reference): for comp in sorted(connectors, key=lambda c: c.reference):
out.extend(_usb_c_one(graph, comp)) out.extend(_usb_c_one(graph, comp))
@@ -248,8 +251,7 @@ def _usb_c_ss_finding(
def _check_ethernet(graph: DesignGraph) -> list[Finding]: def _check_ethernet(graph: DesignGraph) -> list[Finding]:
jacks = [c for c in graph.components.values() if _is_ethernet_jack(c)] jacks = [c for c in graph.components.values() if _is_ethernet_jack(c)]
if not jacks: if not jacks:
return [_na("PCB", "PE-ETH-001", "Ethernet", return []
"No RJ45 / Ethernet jack in the netlist.")]
out: list[Finding] = [] out: list[Finding] = []
for comp in sorted(jacks, key=lambda c: c.reference): for comp in sorted(jacks, key=lambda c: c.reference):
out.extend(_eth_one(graph, comp)) out.extend(_eth_one(graph, comp))
@@ -377,15 +379,11 @@ def _eth_bob_smith_finding(
def _check_poe(graph: DesignGraph) -> list[Finding]: def _check_poe(graph: DesignGraph) -> list[Finding]:
jacks = [c for c in graph.components.values() if _is_ethernet_jack(c)] jacks = [c for c in graph.components.values() if _is_ethernet_jack(c)]
if not jacks: if not jacks:
return [_na("PCB", "PE-POE-001", "PoE", return []
"No RJ45 — PoE is N/A (not invented).")]
out: list[Finding] = [] out: list[Finding] = []
for comp in sorted(jacks, key=lambda c: c.reference): for comp in sorted(jacks, key=lambda c: c.reference):
ev = _poe_blob(graph, comp) ev = _poe_blob(graph, comp)
if not ev: if not ev:
out.append(_na(comp.reference, "PE-POE-001", "PoE",
f"{comp.reference} is RJ45 without PoE evidence — "
"PoE not certified (bare jack is not PoE)."))
continue continue
out.extend(_poe_one(graph, comp, ev)) out.extend(_poe_one(graph, comp, ev))
return out return out
@@ -722,15 +720,6 @@ def _nets_equal(a: str, b: str) -> bool:
return _leaf(a).upper() == _leaf(b).upper() return _leaf(a).upper() == _leaf(b).upper()
def _na(designator: str, rule_id: str, kind: str, facts: str) -> Finding:
return _info(
designator, rule_id,
f"{kind} N/A — no candidate to certify.",
facts,
f"{kind} certifier runs always; absence is N/A, not ERROR.",
)
def _info( def _info(
designator: str, rule_id: str, finding: str, facts: str, requirement: str, designator: str, rule_id: str, finding: str, facts: str, requirement: str,
*, mpn: str = "", net: str | None = None, pins: list[str] | None = None, *, mpn: str = "", net: str | None = None, pins: list[str] | None = None,
@@ -36,11 +36,13 @@ notes. Do not claim the via count or size is missing if that block lists them.
Do not claim antenna keepout geometry is missing if keepout zones are listed. Do not claim antenna keepout geometry is missing if keepout zones are listed.
- Kelvin/sense pins, crystal keepout — only if named in the pintable/layout_rules. - Kelvin/sense pins, crystal keepout — only if named in the pintable/layout_rules.
- Creepage/clearance only if the extraction or IEC number is in context. - Creepage/clearance only if the extraction or IEC number is in context.
- Interface class: USB-C CC/Rd/VBUS, Ethernet 10/100 vs GbE magnetics, \ - Interface class: only connectors on THIS board. USB-C CC/Rd/VBUS if a \
PoE only with evidence. Deterministic certifiers already ran; explain \ USB-C receptacle exists; Ethernet 10/100 vs GbE if RJ45 exists; PoE only \
those FACTS. Never invent PoE on a bare RJ45 or SuperSpeed on USB2 Type-C. \ with PoE evidence (never because RJ45 exists); DDR only if a DDR device \
Never invent millimetres, Z, or I. Do not assume USB 500 mA — PE-PWR \ is present. Deterministic certifiers already ran; explain those FACTS. \
skips when the datasheet has no I_load/Imax/I_abs. Never invent a missing interface, PoE on a bare RJ45, or SuperSpeed on \
USB2 Type-C. Never invent millimetres, Z, or I. Do not assume USB 500 mA \
— PE-PWR skips when the datasheet has no I_load/Imax/I_abs.
### Evidence ### Evidence
Cite numbers from "Parsed board geometry". Say insufficient evidence ONLY \ Cite numbers from "Parsed board geometry". Say insufficient evidence ONLY \
@@ -40,10 +40,11 @@ datasheet's recommended values.
- Required external components named by the datasheet (bootstrap, \ - Required external components named by the datasheet (bootstrap, \
compensation, feedback divider, sense resistor). compensation, feedback divider, sense resistor).
- Unused / no-connect pins. - Unused / no-connect pins.
- Dedicated interface class (USB-C CC1/CC2 Rp/Rd 5.1 kΩ VBUS/GND; Ethernet \ - Dedicated interface class only for connectors on THIS netlist (USB-C \
10/100 vs GbE magnetics; PoE only with evidence). Deterministic certifiers \ CC1/CC2 Rp/Rd 5.1 kΩ VBUS/GND; Ethernet 10/100 vs GbE magnetics; PoE only \
own those FACTS. Do not invent PoE on a bare RJ45, SuperSpeed on USB 2.0 \ with evidence; DDR only if a DDR part exists). Deterministic certifiers \
Type-C, or geometry/Z/I. Do not assume USB 500 mA. own those FACTS. Do not invent a missing interface, PoE on a bare RJ45, \
SuperSpeed on USB 2.0 Type-C, or geometry/Z/I. Do not assume USB 500 mA.
Then work the areas one at a time. For EACH area, don't just confirm a \ Then work the areas one at a time. For EACH area, don't just confirm a \
part is present — ask what specific failure mode would make it wrong \ part is present — ask what specific failure mode would make it wrong \
@@ -1,34 +1,24 @@
# Certifier di classe (interfacce) # Certifier di classe (interfacce)
Michele 2026-09-21: per USB-C, Ethernet (10/100 vs GbE), PoE, DDR3 e analoghe, check dedicati (deterministici + IA). Certificare **classe** e **integrità delle connessioni**. FEM termico: no, per ora. Michele 2026-09-21: USB-C, RJ45/Ethernet, PoE, DDR3 sono **il metodo**, non un catalogo. Se **questa** scheda ha il connettore/bus, certifica classe + integrità delle connessioni. Se non c’è, non emettere quel certifier. HubAudio: J2 USB-C USB2 16P, J1 PoE 10/100 MagJack, niente DDR → niente finding DDR. RJ45 nudo ≠ PoE. FEM: no. I vs larghezza: solo con I datasheet (skip se manca).
## Cosa non è ## Cosa non è
Non è un unico check SI generico. Non è il DRC KiCad. Non è “via ampacity IPC” (traccia vs corrente è un altro motore). Non è un unico check SI generico. Non è il DRC KiCad. Non è “via ampacity IPC”. Non è un elenco di bus immaginari.
## Formato finding ## Formato finding
Classe dichiarata o inferita (FACT/REQUIREMENT da BOM, net, footprint, sheet). Se manca evidenza: INSUFFICIENT_EVIDENCE, niente geometria/Z/I inventati. Via ≠ pad ≠ track. Classe dichiarata o inferita (FACT/REQUIREMENT da BOM, net, footprint, sheet). Se manca evidenza **sullinterfaccia presente**: INSUFFICIENT_EVIDENCE, niente geometria/Z/I inventati. Via ≠ pad ≠ track. Assenza di connettore → silenzio, non N/A.
## Prima fetta (shipped 2.61.0, skip-I 2.61.1) ## Presente su questa scheda (2.61.2)
| ID | Cosa certifica | | ID | Quando gira | Cosa certifica |
| --- | --- | | --- | --- | --- |
| PE-USBC-001 | Classe USB-C USB2 vs USB3 (BOM/footprint/MPN/net) | | PE-USBC-001…005 | c’è un receptacle USB-C | classe USB2/USB3, CC1/CC2, Rd/Rp 5.1 kΩ, VBUS/GND, SuperSpeed solo se USB3 |
| PE-USBC-002 | CC1 e CC2 connessi, non NC | | PE-ETH-001…004 | c’è un RJ45 | 10/100 vs GbE, coppie TX/RX o 4 MDI, magnetics se GbE, Bob Smith REVIEW |
| PE-USBC-003 | Rd 5.1 kΩ ±10% verso GND, o Rp 56/22/10 kΩ verso VBUS; IC CC → INSUFFICIENT | | PE-POE-001…003 | evidenza PoE sul jack | classe/tipo se citati; isolamento/magnetics; **skip** se RJ45 nudo |
| PE-USBC-004 | VBUS e GND | | PE-DDR-* | solo se c’è un dispositivo DDR | non inventato su HubAudio (nessun DDR in BOM) |
| PE-USBC-005 | Coppie SuperSpeed **solo** se la classe è USB3; USB2 = N/A |
| PE-ETH-001 | Classe 10/100 vs GbE |
| PE-ETH-002 | Coppie TX/RX (10/100) o quattro MDI (GbE) |
| PE-ETH-003 | Magnetics se GbE; N/A se non GbE |
| PE-ETH-004 | Bob Smith 75 Ω (RECOMMENDED; spesso nel MagJack) |
| PE-POE-001 | Evidenza PoE, altrimenti N/A (RJ45 nudo ≠ PoE) |
| PE-POE-002 | Classe/tipo 802.3af/at/bt se citati; altrimenti INSUFFICIENT |
| PE-POE-003 | Isolamento/magnetics solo con evidenza PoE |
Motore: `periscopex/interface_class_check.py` (grafo, non geometria). Schema `MODE=run` e PCB `MODE=pcb`. LIA spiega i FACT, non certifica. Nessun I inventato (niente USB 500 mA): la corrente vs larghezza è PE-PWR, e **skip** se il datasheet non riporta I_load/Imax/I_abs. Motore: `periscopex/interface_class_check.py` (grafo, non geometria). Schema `MODE=run` e PCB `MODE=pcb`. LIA spiega i FACT, non certifica. Nessun I inventato: PE-PWR **skip** se il datasheet non riporta I_load/Imax/I_abs.
## Dopo HubAudio (parti vere): J2 `USB_C_Receptacle_USB2.0_16P` → USB-C USB2 (SS N/A); J1 ARJP11A PoE 10/100 MagJack → Ethernet 10/100 + PoE; J3/J4 fibra, J5 SMA, jack audio → non inventare bus. Niente DDR.
DDR3 (indirizzo/comando vs DQ, lunghezze, VTT/VREF). Altre periferiche con lo stesso schema.
+2 -2
View File
@@ -76,7 +76,7 @@ Mutex vs analisi e vs placement. SSE `pcb_*`. IDs `PCB-{ref}-{001}`. KiCad `/net
| PE-STCH-001 | pour GND + segnale senza via GND nel bbox | | PE-STCH-001 | pour GND + segnale senza via GND nel bbox |
| PE-USBC-001…005 | classe USB-C + CC + Rd/Rp + VBUS/GND + SuperSpeed se USB3 | | PE-USBC-001…005 | classe USB-C + CC + Rd/Rp + VBUS/GND + SuperSpeed se USB3 |
| PE-ETH-001…004 | 10/100 vs GbE; magnetics se GbE; Bob Smith REVIEW | | PE-ETH-001…004 | 10/100 vs GbE; magnetics se GbE; Bob Smith REVIEW |
| PE-POE-001…003 | PoE solo con evidenza; isolamento/magnetics; RJ45 nudo = N/A | | PE-POE-001…003 | PoE **solo** con evidenza sul jack; RJ45 nudo = skip (non N/A) |
| Creepage | **skip** senza numero datasheet/IEC | | Creepage | **skip** senza numero datasheet/IEC |
| Crystal keepout | PE-PLC-004 se kind=keepout | | Crystal keepout | PE-PLC-004 se kind=keepout |
@@ -125,7 +125,7 @@ Pintable skill **1.13.0** (`net_class` required on SI kinds). Older extracts wit
| Gap | Why it stays out | | Gap | Why it stays out |
| --- | --- | | --- | --- |
| DDR3 class certifier | After USB-C / Ethernet / PoE first slice (2.61.0) | | DDR class certifier | Only if a DDR device is on the graph. HubAudio has none — do not invent PE-DDR |
| Thermal FEM / energy | PE-THM-002 is Tj=Ta+P·θJA with measured copper/vias; no spreading/FEM | | Thermal FEM / energy | PE-THM-002 is Tj=Ta+P·θJA with measured copper/vias; no spreading/FEM |
| CPWG / OpenEMS | Field-solver export excluded from the ImpedenceFinder vendor snapshot | | CPWG / OpenEMS | Field-solver export excluded from the ImpedenceFinder vendor snapshot |
| ImpedenceFinder license | Upstream has **no LICENSE** (UNKNOWN). Not invented in-tree. See `vendor/impedancefinder/SOURCE.md` | | ImpedenceFinder license | Upstream has **no LICENSE** (UNKNOWN). Not invented in-tree. See `vendor/impedancefinder/SOURCE.md` |
@@ -2,6 +2,13 @@
What's new in Periscope. What's new in Periscope.
## 2.61.2 — 2026-09-21 — Certifiers only for connectors on this board
USB-C / RJ45 / PoE / DDR3 are the method, not a catalog. If this graph has a USB-C receptacle, run USB-C questions. If it has RJ45, run Ethernet. PoE only with PoE evidence — never because RJ45 exists. No connector → no that certifier (not N/A). HubAudio has USB-C USB2 + PoE 10/100 MagJack and no DDR: no DDR findings. Skip-I (2.61.1) unchanged. No FEM, no via IPC, no invented I/Z/PoE/SuperSpeed.
- [Fixed] Absent USB-C / RJ45 / PoE no longer emit INFO N/A.
- [Fixed] Bare RJ45 is Ethernet only; PoE is skipped without PoE evidence.
## 2.61.1 — 2026-09-21 — Skip trace-width vs current without datasheet I ## 2.61.1 — 2026-09-21 — Skip trace-width vs current without datasheet I
PE-PWR-001 (and PE-VIA-001) run only when the datasheet reports I_load / Imax / I_abs. Missing that number is a skip — not INFO INSUFFICIENT, not a typical USB 500 mA, not Iout_max-as-load. USB-C / Ethernet / PoE class certifiers still have no current/ampacity check. No via IPC table. No FEM. PE-PWR-001 (and PE-VIA-001) run only when the datasheet reports I_load / Imax / I_abs. Missing that number is a skip — not INFO INSUFFICIENT, not a typical USB 500 mA, not Iout_max-as-load. USB-C / Ethernet / PoE class certifiers still have no current/ampacity check. No via IPC table. No FEM.
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "periscope-web", "name": "periscope-web",
"version": "2.61.1", "version": "2.61.2",
"private": true, "private": true,
"scripts": { "scripts": {
"sync-version": "node scripts/sync-version.mjs", "sync-version": "node scripts/sync-version.mjs",
+3 -3
View File
@@ -2,7 +2,7 @@
Favor: perfect golden match; citation rate ignores deterministic findings; Favor: perfect golden match; citation rate ignores deterministic findings;
simple_project graph still has U1/U2/U3, the two I2C pull-up keys, simple_project graph still has U1/U2/U3, the two I2C pull-up keys,
and USB-C class certifiers on J1 (Ethernet/PoE N/A). and USB-C class certifiers on J1 (no Ethernet/PoE — simple_project has no RJ45).
Against: extra finding drops precision; missing golden key drops recall; Against: extra finding drops precision; missing golden key drops recall;
Unverified quotes are citation misses, not hits. Unverified quotes are citation misses, not hits.
""" """
@@ -88,9 +88,9 @@ def test_simple_project_eval_matches_committed_golden():
assert scores.graph_ok, scores.graph_errors assert scores.graph_ok, scores.graph_errors
assert scores.precision == 1.0 assert scores.precision == 1.0
assert scores.recall == 1.0 assert scores.recall == 1.0
assert scores.finding_count == 10 assert scores.finding_count == 8
assert scores.by_status["WARNING"] == 2 assert scores.by_status["WARNING"] == 2
assert scores.by_status["INFO"] == 8 assert scores.by_status["INFO"] == 6
def test_simple_project_eval_rejects_truncated_graph(tmp_path: Path): def test_simple_project_eval_rejects_truncated_graph(tmp_path: Path):
+30 -30
View File
@@ -128,7 +128,8 @@ def _usbc_device_graph(*, ss: bool = False, usb2_value: bool = True) -> DesignGr
return DesignGraph(components=comps, nets=nets) return DesignGraph(components=comps, nets=nets)
def test_no_connector_is_na_not_error(): def test_no_connector_emits_nothing():
"""No USB-C / no RJ45 / no PoE evidence → do not emit that certifier."""
g = DesignGraph( g = DesignGraph(
components={ components={
"U1": _comp("U1", ctype=ComponentType.IC, value="MCU", pins={"1": "GND"}), "U1": _comp("U1", ctype=ComponentType.IC, value="MCU", pins={"1": "GND"}),
@@ -136,16 +137,8 @@ def test_no_connector_is_na_not_error():
nets={"GND": _net("GND", NetType.GROUND, ("U1", "1"))}, nets={"GND": _net("GND", NetType.GROUND, ("U1", "1"))},
) )
findings = check_interface_classes(g) findings = check_interface_classes(g)
for f in findings: assert findings == []
complete_finding(f) assert not any((f.rule_id or "").startswith("PE-DDR") for f in findings)
assert _by_rule(findings, "PE-USBC-001")
assert _by_rule(findings, "PE-ETH-001")
assert _by_rule(findings, "PE-POE-001")
assert all(f.status != "ERROR" for f in findings)
assert all(f.finding_class == "INFO" for f in findings)
assert "N/A" in _by_rule(findings, "PE-USBC-001")[0].finding
assert "N/A" in _by_rule(findings, "PE-ETH-001")[0].finding
assert "N/A" in _by_rule(findings, "PE-POE-001")[0].finding
def test_usbc_rd_vbus_gnd_certified(): def test_usbc_rd_vbus_gnd_certified():
@@ -166,6 +159,7 @@ def test_usbc_rd_vbus_gnd_certified():
assert ss.finding_class == "INFO" assert ss.finding_class == "INFO"
assert "N/A" in ss.finding or "USB2" in ss.finding assert "N/A" in ss.finding or "USB2" in ss.finding
assert all(f.status != "ERROR" for f in findings if f.designator == "J2") assert all(f.status != "ERROR" for f in findings if f.designator == "J2")
assert not any((f.rule_id or "").startswith(("PE-ETH", "PE-POE", "PE-DDR")) for f in findings)
def test_usbc_missing_cc2_is_error(): def test_usbc_missing_cc2_is_error():
@@ -429,13 +423,11 @@ def test_bare_rj45_does_not_invent_poe():
findings = check_interface_classes(g) findings = check_interface_classes(g)
for f in findings: for f in findings:
complete_finding(f) complete_finding(f)
poe = _by_rule(findings, "PE-POE-001", "J1")[0] assert _by_rule(findings, "PE-ETH-001", "J1")
assert poe.status != "ERROR" assert not any((f.rule_id or "").startswith("PE-POE") for f in findings)
assert poe.finding_class == "INFO" assert not any((f.rule_id or "").startswith("PE-USBC") for f in findings)
assert "N/A" in poe.finding assert not any((f.rule_id or "").startswith("PE-DDR") for f in findings)
assert not _by_rule(findings, "PE-POE-003", "J1") or _by_rule( assert all(f.status != "ERROR" for f in findings if (f.rule_id or "").startswith("PE-ETH"))
findings, "PE-POE-003", "J1",
)[0].status != "ERROR"
def test_poe_with_evidence_requires_magnetics_isolation(): def test_poe_with_evidence_requires_magnetics_isolation():
@@ -494,10 +486,9 @@ def test_simple_project_usbc_not_false_error():
ss = _by_rule(findings, "PE-USBC-005", "J1")[0] ss = _by_rule(findings, "PE-USBC-005", "J1")[0]
assert ss.status != "ERROR" assert ss.status != "ERROR"
assert ss.evidence_status == "INSUFFICIENT" or "N/A" in ss.finding assert ss.evidence_status == "INSUFFICIENT" or "N/A" in ss.finding
assert _by_rule(findings, "PE-ETH-001")[0].status != "ERROR" assert not any((f.rule_id or "").startswith("PE-ETH") for f in findings)
assert "N/A" in _by_rule(findings, "PE-ETH-001")[0].finding assert not any((f.rule_id or "").startswith("PE-POE") for f in findings)
assert _by_rule(findings, "PE-POE-001")[0].status != "ERROR" assert not any((f.rule_id or "").startswith("PE-DDR") for f in findings)
assert "N/A" in _by_rule(findings, "PE-POE-001")[0].finding
def test_hubaudio_like_usbc_usb2_and_poe_magjack(): def test_hubaudio_like_usbc_usb2_and_poe_magjack():
@@ -538,6 +529,17 @@ def test_hubaudio_like_usbc_usb2_and_poe_magjack():
for f in findings for f in findings
if (f.rule_id or "").startswith(("PE-USBC", "PE-ETH", "PE-POE")) if (f.rule_id or "").startswith(("PE-USBC", "PE-ETH", "PE-POE"))
) )
assert not any((f.rule_id or "").startswith("PE-DDR") for f in findings)
def test_hubaudio_bom_has_usbc_and_poe_rj45_not_ddr():
"""HubAudio actual parts: J2 USB-C USB2 16P, J1 PoE 10/100 MagJack, no DDR."""
from tests.paths import REPO_ROOT
bom = (REPO_ROOT / "tests" / "fixtures" / "hubaudio_kicad_bom.csv").read_text()
assert "USB_C_Receptacle_USB2.0_16P" in bom
assert "RJ45" in bom and "PoE" in bom
assert "DDR3" not in bom and "DDR2" not in bom and "DDR4" not in bom
assert "USB3" not in bom
def test_rule_catalog_shared_not_si(): def test_rule_catalog_shared_not_si():
@@ -551,16 +553,14 @@ def test_rule_catalog_shared_not_si():
assert rec.domain == "shared" assert rec.domain == "shared"
def test_run_pcb_checks_includes_interface_class(): def test_run_pcb_checks_skips_absent_interfaces():
findings = run_pcb_checks(DesignGraph(), {}, None) findings = run_pcb_checks(DesignGraph(), {}, None)
ids = {f.rule_id for f in findings} ids = {f.rule_id for f in findings}
assert "PE-USBC-001" in ids assert "PE-USBC-001" not in ids
assert "PE-ETH-001" in ids assert "PE-ETH-001" not in ids
assert "PE-POE-001" in ids assert "PE-POE-001" not in ids
if_f = [f for f in findings if (f.rule_id or "").startswith(("PE-USBC", "PE-ETH", "PE-POE"))] if_f = [f for f in findings if (f.rule_id or "").startswith(("PE-USBC", "PE-ETH", "PE-POE", "PE-DDR"))]
assert if_f assert if_f == []
assert all(f.status != "ERROR" for f in if_f)
assert all((f.finding_id or "").startswith("PCB-") for f in if_f)
def test_merge_drops_duplicate_pcb_interface_findings(): def test_merge_drops_duplicate_pcb_interface_findings():
+1 -1
View File
@@ -48,7 +48,7 @@ def test_simple_project_without_pcb_has_no_ps_plc():
def test_simple_project_eval_has_no_placement_keys(): def test_simple_project_eval_has_no_placement_keys():
scores = eval_simple_project(SIMPLE) scores = eval_simple_project(SIMPLE)
assert scores.finding_count == 10 assert scores.finding_count == 8
assert scores.precision == 1.0 assert scores.precision == 1.0
assert scores.recall == 1.0 assert scores.recall == 1.0
assert not any(k.startswith("PE-PLC-") for k in scores.extra_keys) assert not any(k.startswith("PE-PLC-") for k in scores.extra_keys)
+1 -1
View File
@@ -54,7 +54,7 @@ def test_simple_project_without_pcb_has_no_ps_si_001():
def test_simple_project_eval_has_no_si_keys(): def test_simple_project_eval_has_no_si_keys():
scores = eval_simple_project(SIMPLE) scores = eval_simple_project(SIMPLE)
assert scores.finding_count == 10 assert scores.finding_count == 8
assert scores.precision == 1.0 assert scores.precision == 1.0
assert scores.recall == 1.0 assert scores.recall == 1.0
assert not any( assert not any(
+2 -2
View File
@@ -160,9 +160,9 @@ async def test_concurrency_bounded_by_knob_and_charging_isolated(workspace, monk
assert review_n == len(IC_MPNS) # all 5 reviewed assert review_n == len(IC_MPNS) # all 5 reviewed
if_ids = { if_ids = {
f.get("rule_id") for f in report["findings"] f.get("rule_id") for f in report["findings"]
if (f.get("rule_id") or "").startswith(("PE-USBC", "PE-ETH", "PE-POE")) if (f.get("rule_id") or "").startswith(("PE-USBC", "PE-ETH", "PE-POE", "PE-DDR"))
} }
assert "PE-USBC-001" in if_ids # N/A certifier, not a 6th IC review assert not if_ids # five ICs, no USB-C/RJ45/DDR — do not invent those certifiers
@pytest.mark.asyncio @pytest.mark.asyncio