4"""Enforce the HIL "honest contract" gate.
6Every hil.conf under examples/ek_ra8d2/hw_validated/hil/ must declare
8 - uart_scrape (UART success-banner assertion)
9 - usb_cdc (Pi-side USB host probe)
10 - usb_hid (Pi-side USB HID host-binding probe)
11 - usb_msc (Pi-side USB Mass Storage SCSI-attach probe)
12 - jlink_memprobe (named-symbol counter advance via SWD)
13 - hil_eth_tcp (Pi-as-peer TCP/UDP/HTTP probe)
14 - rtt_scrape (SEGGER RTT banner assertion via JLink mem dump)
15 - alive ONLY if HIL_FAULT_EXPECTED=1 is also set
16 (legitimate fault-recovery test path -- the
17 handler is required to latch CFSR != 0 and the
18 app must emit a positive UART banner that is
19 not in hil_check_alive.sh's negative regex)
21Plain HIL_MODE=alive without HIL_FAULT_EXPECTED is the historical
22"PC happens to be in MRAM" loose check; it lets silently-broken apps
23pass CI and is forbidden under hw_validated/hil/. Apps that genuinely
24have no observable signal yet (panic-halt at init, no
25instrumentation) must live in hw_pending/ until they are fixed AND
29 0 every hil.conf in hw_validated/hil/ satisfies the policy
30 1 one or more hil.confs violate
31 2 usage / unreachable repo root, or a collapsed scan (see HIL_CONF_FLOOR)
34from __future__
import annotations
40from collections.abc
import Iterable
49ALLOWED_MODES: frozenset[str] = frozenset(
62def _iter_hil_confs(repo_root: pathlib.Path) -> Iterable[pathlib.Path]:
63 """Yield every hil.conf under examples/ek_ra8d2/hw_validated/hil/."""
64 hil_dir = repo_root /
"examples" /
"ek_ra8d2" /
"hw_validated" /
"hil"
65 if not hil_dir.is_dir():
67 return hil_dir.glob(
"*/hil.conf")
70def _parse_kv(conf: pathlib.Path) -> dict[str, str]:
71 """Extract simple KEY=VALUE lines (no shell expansion) from a hil.conf."""
72 out: dict[str, str] = {}
73 pat = re.compile(
r"^\s*([A-Z_][A-Z_0-9]*)\s*=\s*(.*?)\s*$")
74 for raw
in conf.read_text().splitlines():
75 line = raw.split(
"#", 1)[0].rstrip()
79 key, val = m.group(1), m.group(2)
80 if val.startswith((
'"',
"'"))
and val.endswith(val[0])
and len(val) >= 2:
86def _policy_violations(repo_root: pathlib.Path, confs: Iterable[pathlib.Path]) -> list[str]:
87 """Return one honest-contract violation for each non-asserting config."""
88 violations: list[str] = []
91 mode = kv.get(
"HIL_MODE",
"")
92 fault_expected = kv.get(
"HIL_FAULT_EXPECTED",
"0")
93 app = conf.parent.name
95 if mode
in ALLOWED_MODES
or (mode ==
"alive" and fault_expected ==
"1"):
99 f
"{conf.relative_to(repo_root)}: HIL_MODE=alive without "
100 f
"HIL_FAULT_EXPECTED=1 is forbidden under "
101 f
"hw_validated/hil/. Either:\n"
102 f
" - instrument the app (g_<x>_match symbol + "
103 f
"HIL_MODE=jlink_memprobe), OR\n"
104 f
" - add a UART success banner + HIL_MODE=uart_scrape, OR\n"
105 f
" - move the app to examples/ek_ra8d2/hw_pending/{app}/."
108 violations.append(f
"{conf.relative_to(repo_root)}: missing HIL_MODE")
111 f
"{conf.relative_to(repo_root)}: HIL_MODE={mode!r} is not "
112 f
"in the allowed set "
113 f
"({[*sorted(ALLOWED_MODES), 'alive (with HIL_FAULT_EXPECTED=1)']})"
118def selftest() -> int:
119 """Prove asserting modes pass and every loose/unknown form is rejected."""
120 with tempfile.TemporaryDirectory(prefix=
"hil-alive-policy-selftest-")
as raw:
121 root = pathlib.Path(raw)
123 def conf(name: str, text: str) -> pathlib.Path:
124 path = root / name /
"hil.conf"
126 path.write_text(text, encoding=
"ascii")
130 conf(
"uart",
"HIL_MODE=uart_scrape\n"),
131 conf(
"fault",
"HIL_MODE=alive\nHIL_FAULT_EXPECTED=1\n"),
134 conf(
"loose",
"HIL_MODE=alive\n"),
135 conf(
"missing",
"HIL_FAULT_EXPECTED=1\n"),
136 conf(
"unknown",
"HIL_MODE=not_a_probe\n"),
138 good_findings = _policy_violations(root, good)
139 bad_findings = _policy_violations(root, bad)
141 (
not good_findings,
"asserting and fault-expected modes stay quiet"),
142 (len(bad_findings) == len(bad),
"loose, missing, and unknown modes fire"),
144 failed = [label
for passed, label
in cases
if not passed]
145 for passed, label
in cases:
146 print(f
" [{'ok' if passed else 'FAIL'}] {label}")
148 print(f
"check_hil_alive_policy.py --selftest: {len(failed)} failure(s)", file=sys.stderr)
150 print(
"check_hil_alive_policy.py --selftest: all cases pass (both directions).")
155 """Fail any hw_validated HIL app whose hil.conf does not assert a real outcome.
157 The rule being enforced is that ``HIL_MODE=alive`` proves only that the
158 board did not hang -- it cannot distinguish a working app from one that
159 booted and did nothing. It is therefore allowed under hw_validated/ only
160 when ``HIL_FAULT_EXPECTED=1``, i.e. when not faulting IS the assertion.
161 Any other app claiming validation must scrape a banner or probe a symbol,
162 or move to hw_pending/.
164 A missing HIL_MODE is treated as a violation rather than a default, since
165 a silently defaulted mode is how an unasserted app would slip in.
167 Enforces HIL_CONF_FLOOR before reading anything and exits 2 below it. An
168 empty glob would otherwise report "0 findings" -- indistinguishable from a
169 fully compliant suite, and produced by the scan having read nothing.
171 Returns 1 with one remediation-bearing message per offending hil.conf, 0
172 when every conf under hw_validated/hil/ declares an asserting mode, 2 when
173 the enumeration is too small to trust.
176 if args == [
"--selftest"]:
179 print(
"usage: check_hil_alive_policy.py [--selftest]", file=sys.stderr)
181 repo_root = pathlib.Path(__file__).resolve().parents[2]
183 confs = sorted(_iter_hil_confs(repo_root))
184 if len(confs) < HIL_CONF_FLOOR:
186 f
"check_hil_alive_policy.py: FATAL -- only {len(confs)} hil.conf file(s) in "
187 f
"scope, floor is {HIL_CONF_FLOOR}.\n"
188 " A collapsed scope reports a compliant suite because it checked nothing.\n"
192 violations = _policy_violations(repo_root, confs)
196 f
"check_hil_alive_policy.py: {len(violations)} violation(s) in hw_validated/hil/:\n"
199 sys.stderr.write(f
" - {v}\n")
201 "\nThe HIL 'honest contract' rule: every app under "
202 "hw_validated/hil/ must prove its feature works end-to-end on "
203 "real hardware, not just that the chip booted.\n"
207 print(f
"check_hil_alive_policy.py: 0 findings across {len(confs)} hil.conf file(s).")
211if __name__ ==
"__main__":
void main(void)
The application entry point Reset_Handler hands control to.