4"""Bind HIL rig consumers to one typed value and parser authority."""
6from __future__
import annotations
12from collections.abc
import Callable
13from dataclasses
import dataclass
14from pathlib
import Path
18REPO_ROOT = Path(__file__).resolve().parents[2]
19RIG_ENV =
"scripts/hil/lib/rig_env.sh"
20CONTRACT =
"scripts/hil/lib/rig_contract.sh"
21PARSER =
"scripts/hil/rig_env_parse.py"
22ANSIBLE =
"infra/ansible/roles/dev_box/tasks/hil_runner_transaction.yml"
23BENCH_ANSIBLE =
"infra/ansible/roles/hil_bench/tasks/transaction.yml"
24REMOTE_GDB_ARGS =
"scripts/dev/remote_gdb_args.py"
25GATE =
"scripts/ci/gates/checks.sh"
26EXAMPLE =
".env.example"
27ETH_SCRIPT =
"scripts/hil/eth_tcp.sh"
28HIL_DOC =
"docs/HIL_DEVELOPER_WORKFLOW.md"
29HIL_SUITE =
"docs/HIL_SUITE.md"
30FIELDS = (
"PI_HOST",
"JLINK_SN",
"JLINK_DEVICE",
"PI_REPO")
31EXPANSION = re.compile(
32 r"\$(?:PI_HOST|JLINK_SN|JLINK_DEVICE|PI_REPO)\b"
33 r"|\$\{(?:PI_HOST|JLINK_SN|JLINK_DEVICE|PI_REPO)\}"
35PI_HOST_EXPANSION = re.compile(
r"\$PI_HOST\b|\$\{PI_HOST\}")
36SOURCE_RIG = re.compile(
r"^\s*(?:source|[.])\s+[^#\n]*rig_env[.]sh", re.MULTILINE)
39@dataclass(frozen=True)
41 """Captured status and streams from one fixed-argv test child."""
48def _read(root: Path, relative: str) -> str:
50 return (root / relative).read_text(encoding=
"utf-8")
51 except (OSError, UnicodeError)
as error:
52 return f
"<UNREADABLE:{error}>"
55def _active_lines(text: str) -> list[str]:
57 line
for line
in text.splitlines()
if line.strip()
and not line.lstrip().startswith(
"#")
61def _require(condition: bool, message: str) ->
None:
63 raise AssertionError(message)
66def _named_ansible_tasks(value: object, name: str) -> list[dict[str, object]]:
67 """Find every exact task through Ansible block/rescue/always nesting."""
68 if not isinstance(value, list):
70 matches: list[dict[str, object]] = []
72 if not isinstance(item, dict):
74 if item.get(
"name") == name:
76 for section
in (
"block",
"rescue",
"always"):
77 matches.extend(_named_ansible_tasks(item.get(section), name))
81def _audit_ansible_ephemeral_reporting(text: str) -> list[str]:
82 """Bind secure controller scratch tasks to truthful no-drift reporting."""
84 document = yaml.safe_load(text)
85 except yaml.YAMLError
as error:
86 return [f
"{ANSIBLE}: cannot parse task structure: {error}"]
87 findings: list[str] = []
89 "Allocate a protected allowlist result file on the control node",
90 "Remove the protected allowlist result file",
93 tasks = _named_ansible_tasks(document, name)
95 findings.append(f
"{ANSIBLE}: expected exactly one task {name!r}; found {len(tasks)}")
98 if task.get(
"changed_when")
is not False:
99 findings.append(f
"{ANSIBLE}: {name!r} reports same-run scratch as managed drift")
100 if task.get(
"check_mode")
is not False:
101 findings.append(f
"{ANSIBLE}: {name!r} no longer executes its secure check-mode path")
105def _run_fixed(arguments: tuple[str, ...], environment: dict[str, str]) -> CommandResult:
106 """Run fixed argv without a shell and capture both output streams."""
107 with tempfile.TemporaryFile()
as stdout_file, tempfile.TemporaryFile()
as stderr_file:
109 (os.POSIX_SPAWN_DUP2, stdout_file.fileno(), 1),
110 (os.POSIX_SPAWN_DUP2, stderr_file.fileno(), 2),
112 process = os.posix_spawn(arguments[0], arguments, environment, file_actions=actions)
113 _, status = os.waitpid(process, 0)
116 stdout = stdout_file.read().decode(
"utf-8", errors=
"replace")
117 stderr = stderr_file.read().decode(
"utf-8", errors=
"replace")
118 return CommandResult(os.waitstatus_to_exitcode(status), stdout, stderr)
121def _inside_double_quotes(line: str, offset: int) -> bool:
124 for character
in line[:offset]:
127 elif character ==
"\\":
129 elif character ==
'"':
134def _quoted_expansion(line: str, match: re.Match[str]) -> bool:
135 """Accept a quote segment or an exact double-quoted argv expansion."""
138 and match.end() < len(line)
139 and line[match.start() - 1] ==
'"'
140 and line[match.end()] ==
'"'
142 return adjacent_quotes
or _inside_double_quotes(line, match.start())
145def audit_rig_env(text: str) -> list[str]:
146 """Report loader drift from the shared typed authority."""
147 findings: list[str] = []
149 'source "$_rig_lib_dir/rig_contract.sh"',
150 '"$_rig_python" -I "$_rig_parser"',
151 '--output "$_rig_result" --format nul --include-interactive',
152 "while IFS= read -r -d '' _rig_name",
153 "protected rig parser returned an unknown field",
154 "ra8_rig_contract_default JLINK_DEVICE",
155 "ra8_rig_contract_default PI_REPO",
156 "ra8_rig_validate_loaded true",
157 'ra8_rig_require "$@"',
158 "protected NUL-delimited pairs",
161 f
"{RIG_ENV}: missing shared-contract binding {token!r}"
162 for token
in required
165 if re.search(
r"PI_HOST.*=~|JLINK_(?:SN|DEVICE).*=~|PI_REPO.*=~", text):
166 findings.append(f
"{RIG_ENV}: duplicated an inline rig grammar")
167 if re.search(
r"(?:source|[.])\s+[\"']?\$\{?_rig_env_file", text):
168 findings.append(f
"{RIG_ENV}: executes the selected rig environment")
169 if re.search(
r"(^|[;(])\s*eval\b", text, re.MULTILINE):
170 findings.append(f
"{RIG_ENV}: eval is forbidden for parsed rig data")
174def audit_contract(text: str) -> list[str]:
175 """Report missing value, startup, or source-mode contract defenses."""
176 findings: list[str] = []
178 'if [[ "$-" == *p* ]]; then',
179 "unset -v BASH_ENV ENV",
180 "BASH_FUNC_*%% | BASH_FUNC_*'()') ra8_startup_env_unset+=",
181 "if ((${#ra8_startup_env_unset[@]})); then",
182 'if [[ "${BASH_SOURCE[0]}" != "$0" ]]; then',
183 "sourced rig contract refuses inherited Bash functions",
184 'exec /usr/bin/env "${ra8_startup_env_unset[@]}" -u BASH_ENV -u ENV',
185 "--descendant-selftest",
186 "PI_HOST ssh_target required",
187 "JLINK_SN identifier required",
188 "JLINK_DEVICE identifier optional R7KA8D2KF_CPU0",
189 "PI_REPO repo_path optional ra8-firmware",
190 "_ra8_rig_validate_ipv4",
191 "_ra8_rig_validate_dns",
192 "_ra8_rig_validate_repo_path",
195 f
"{CONTRACT}: missing authority token {token!r}" for token
in required
if token
not in text
197 if re.search(
r"(^|[;(])\s*eval\b", text, re.MULTILINE):
198 findings.append(f
"{CONTRACT}: eval is forbidden in the value authority")
202def audit_ansible(text: str) -> list[str]:
203 """Report Ansible parser or exact-serialization authority drift."""
204 findings: list[str] = []
206 "{{ dev_box_context_src }}/scripts/hil/rig_env_parse.py",
207 'dev_box_hil_pi_repo: "{{ dev_box_hil_rig_env.PI_REPO }}"',
208 'dev_box_hil_jlink_device: "{{ dev_box_hil_rig_env.JLINK_DEVICE }}"',
209 "PI_REPO={{ dev_box_hil_pi_repo }}",
210 "PI_REPO={{ dev_box_hil_runner_bench_repo_dir }}",
211 "dev_box_hil_runner_bench_repo_dir",
212 "dev_box_hil_runner_bench_repo_dir | dirname",
213 "dev_box_hil_runner_bench_repo_dir }}/scripts/hil/run_direct.sh",
214 'service = ["PI_HOST", "JLINK_SN", "JLINK_DEVICE", "PI_REPO"]',
215 "interactive = service.copy()",
218 f
"{ANSIBLE}: missing parser/serialization binding {token!r}"
219 for token
in required
223 'allowed = {"PI_HOST"',
225 "dev_box_hil_interactive_pi_host\n is match",
226 "dev_box_hil_rig_env.JLINK_SN is match",
227 "dev_box_hil_runner_jlink_device_default",
230 f
"{ANSIBLE}: retains duplicated parser/value grammar {token!r}"
231 for token
in forbidden
234 findings.extend(_audit_ansible_ephemeral_reporting(text))
238def audit_parser(text: str) -> list[str]:
239 """Report parser delegation, protected-I/O, or shell execution drift."""
240 findings: list[str] = []
242 'CONTRACT = Path(__file__).resolve().parent / "lib" / "rig_contract.sh"',
243 '("/bin/bash", "-p", str(CONTRACT), *arguments)',
244 'result = _run_contract("--validate", name, value)',
245 'result = _run_contract("--describe")',
247 "unsupported assignment syntax",
248 "never executes or expands",
249 "ASSIGNMENT_RE.fullmatch",
250 "LITERAL_RE.fullmatch",
251 "INTERACTIVE_FIELDS",
252 'output_format == "nul"',
255 f
"{PARSER}: missing authority/parser binding {token!r}"
256 for token
in required
259 source_reader = text.partition(
"def _read_source")[2].partition(
"def _parse_allowlisted_line")[
263 f
"{PARSER}: protected source reader lacks {token!r}"
265 "flags |= os.O_NOFOLLOW",
266 "flags |= os.O_NONBLOCK",
267 "info.st_uid != os.getuid()",
268 "stat.S_IMODE(info.st_mode) != PROTECTED_MODE",
270 if token
not in source_reader
272 if "shell=True" in text
or re.search(
r"\beval\s*\(", text):
273 findings.append(f
"{PARSER}: shell/eval execution is forbidden")
277def audit_bench_ansible(text: str) -> list[str]:
278 """Bind the bench health check directly to the rig contract default."""
280 "/scripts/hil/lib/rig_contract.sh",
281 "--default JLINK_DEVICE",
282 'JLinkExe -device "$device"',
285 f
"{BENCH_ANSIBLE}: missing device-authority binding {token!r}"
286 for token
in required
289 if "hil_bench_jlink_device" in text
or "R7KA8D2KF_CPU0" in text:
290 findings.append(f
"{BENCH_ANSIBLE}: retains an independent J-Link device authority")
294def audit_remote_gdb_args(text: str) -> list[str]:
295 """Require start-only device input and a device-free cleanup interface."""
297 "def remote_command(serial: str, port: str, *, device: str)",
298 'remote.add_argument("--device", required=True)',
299 "print(remote_command(args.serial, args.port, device=args.device))",
302 f
"{REMOTE_GDB_ARGS}: missing start/cleanup split {token!r}"
303 for token
in required
306 if re.search(
r'add_argument\("--device",\s*default=', text):
307 findings.append(f
"{REMOTE_GDB_ARGS}: device still has an independent default")
308 if 'add_parser("cleanup")' in text
or '"cleanup"' in text:
309 findings.append(f
"{REMOTE_GDB_ARGS}: cleanup retains a device-command surface")
313def audit_hil_consumers(root: Path) -> list[str]:
314 """Report unsafe typed-field interpolation across HIL shell consumers."""
315 findings: list[str] = []
316 exempt = {RIG_ENV, CONTRACT,
"scripts/hil/lib/tty_resolve.sh"}
317 for path
in sorted((root /
"scripts/hil").rglob(
"*.sh")):
318 relative = path.relative_to(root).as_posix()
319 text = path.read_text(encoding=
"utf-8")
320 active =
"\n".join(_active_lines(text))
321 if relative != CONTRACT
and "R7KA8D2KF_CPU0" in active:
322 findings.append(f
"{relative}: hard-codes the contract-owned J-Link device")
323 if re.search(
r"\b(?:source|[.])\s+[\"']?\.env\b", active):
324 findings.append(f
"{relative}: executes a remote or local .env bypass")
325 if relative
not in exempt
and EXPANSION.search(active)
and not SOURCE_RIG.search(text):
326 findings.append(f
"{relative}: expands a rig field without sourcing rig_env.sh")
327 for line_number, line
in enumerate(text.splitlines(), 1):
328 if line.lstrip().startswith(
"#"):
331 f
"{relative}:{line_number}: PI_HOST is not one quoted argv/string value"
332 for match
in PI_HOST_EXPANSION.finditer(line)
333 if not _quoted_expansion(line, match)
335 if relative
not in {RIG_ENV, CONTRACT}
and re.search(
r"\$(?:\{)?PI_REPO\b", line):
336 parameter_guard = line.strip() ==
': "${PI_REPO:?}"'
337 if not parameter_guard
and (
"printf -v" not in line
or "%q" not in line):
339 f
"{relative}:{line_number}: PI_REPO bypasses explicit %q serialization"
342 "scripts/hil/run.sh":
"rig_require PI_HOST JLINK_SN JLINK_DEVICE",
343 "scripts/hil/run_direct.sh":
"rig_require PI_HOST JLINK_SN JLINK_DEVICE",
344 "scripts/hil/run_local.sh":
"rig_require JLINK_SN JLINK_DEVICE",
345 "scripts/hil/rtt_scrape.sh":
"rig_require PI_HOST JLINK_DEVICE",
346 "scripts/hil/camera_picture.sh": (
"rig_require PI_HOST JLINK_SN JLINK_DEVICE PI_REPO"),
348 for relative, required
in required_calls.items():
349 if (root / relative).is_file()
and required
not in _read(root, relative):
350 findings.append(f
"{relative}: missing exact required-field call {required!r}")
354def audit_docs_and_ethernet(root: Path) -> list[str]:
355 """Report stale Ethernet semantics or missing contract documentation."""
356 findings: list[str] = []
357 document = _read(root, HIL_DOC)
358 suite = _read(root, HIL_SUITE)
359 ethernet = _read(root, ETH_SCRIPT)
360 defaults = _read(root,
"infra/ansible/roles/hil_bench/defaults/main.yml")
362 f
"{HIL_DOC}: missing stable HIL semantic {token!r}"
365 "interactive loader never",
366 "non-symlink, mode-0600 regular file",
367 "hil_bench_eth_iface",
369 "hil_bench_eth_sysfs_device",
370 "hil_bench_eth_phc_index",
372 "intentionally rejected",
373 "IPv6 targets, including bracketed literals, are intentionally rejected",
375 if token
not in document
378 f
"{HIL_SUITE}: missing stable Ethernet semantic {token!r}"
380 "fleet-declared built-in board-facing interface",
381 "installed policy verifies its MAC, sysfs device, PHC",
382 "USB adapters are rejected",
384 if token
not in suite
387 f
"HIL Ethernet prose retains stale auto-detection claim {stale!r}"
388 for stale
in (
"detected automatically",
"enxXX",
"usbX device")
389 if stale
in document
or stale
in suite
or stale
in ethernet
392 f
"{HIL_SUITE}: retains stale USB-interface claim {stale!r}"
393 for stale
in (
"USB-Ethernet adapter",
"enxXX",
"usbX")
397 f
"{ETH_SCRIPT}: missing installed-policy binding {token!r}"
399 '"$RA8_HIL_PRIVILEGED_HELPER" --policy-interface',
400 "verifies its permanent MAC, canonical sysfs device, PHC",
402 if token
not in ethernet
405 f
"hil_bench defaults lost fleet-owned Ethernet field {token!r}"
407 'hil_bench_eth_iface: ""',
408 'hil_bench_eth_mac: ""',
409 'hil_bench_eth_sysfs_device: ""',
410 "hil_bench_eth_phc_index: -1",
412 if token
not in defaults
417def scan(root: Path = REPO_ROOT) -> list[str]:
418 """Run every typed rig, consumer, registration, and documentation audit."""
419 findings: list[str] = []
420 findings.extend(audit_rig_env(_read(root, RIG_ENV)))
421 findings.extend(audit_contract(_read(root, CONTRACT)))
422 findings.extend(audit_parser(_read(root, PARSER)))
423 findings.extend(audit_ansible(_read(root, ANSIBLE)))
424 findings.extend(audit_bench_ansible(_read(root, BENCH_ANSIBLE)))
425 findings.extend(audit_remote_gdb_args(_read(root, REMOTE_GDB_ARGS)))
426 findings.extend(audit_hil_consumers(root))
427 findings.extend(audit_docs_and_ethernet(root))
428 gate = _read(root, GATE)
430 f
"{GATE}: rig checker is not registered as {command!r}"
432 "/bin/bash -p scripts/hil/lib/rig_contract.sh --selftest",
433 "python3 scripts/hil/rig_env_parse.py --selftest",
434 "python3 scripts/checks/check_hil_rig_contract.py --selftest",
435 "python3 scripts/checks/check_hil_rig_contract.py",
437 if command
not in gate
439 if "PI_REPO=" not in _read(root, EXAMPLE):
440 findings.append(f
"{EXAMPLE}: PI_REPO is absent from the documented contract")
446ssh() { [[ "$#" -eq 1 && "$1" == "sikar@10.0.40.103" ]]; printf 'SSH-FAKE\n'; }
447scp() { [[ "$#" -eq 2 && "$2" == "sikar@10.0.40.103:/tmp/fw.hex" ]]; printf 'SCP-FAKE\n'; }
449rig_require PI_HOST JLINK_SN JLINK_DEVICE PI_REPO
450[[ -z "${TAPO_PASS+x}" ]]
452scp fw.hex "${PI_HOST}:/tmp/fw.hex"
455 "PI_HOST=sikar@10.0.40.103\n"
456 "JLINK_SN=123456789\n"
457 "JLINK_DEVICE=R7KA8D2KF_CPU0\n"
458 "PI_REPO=/home/ra8-hil/ra8-firmware\n"
461 "PI_HOST=-oProxyCommand=bad\nJLINK_SN=1\n",
462 "PI_HOST=user@@host\nJLINK_SN=1\n",
463 "PI_HOST=$'host\\ncommand'\nJLINK_SN=1\n",
464 "PI_HOST=host\nJLINK_SN=-1\n",
465 "PI_HOST=host\nJLINK_SN=1\nPI_REPO=../repo\n",
466 'PI_HOST=host\nJLINK_SN=1\nPI_REPO="repo\'bad"\n',
470def _fake_value_cases(env_file: Path, command: tuple[str, ...]) ->
None:
471 """Prove valid argv transport and reject hostile declared values."""
472 env_file.write_text(FAKE_VALID, encoding=
"utf-8")
473 env_file.chmod(0o600)
474 environment = {
"PATH":
"/usr/bin:/bin",
"RA8_RIG_ENV": str(env_file)}
475 result = _run_fixed(command, environment)
477 not result.returncode
and result.stdout ==
"SSH-FAKE\nSCP-FAKE\n",
478 f
"valid fake transport failed: {result!r}",
480 for content
in FAKE_INVALID:
481 env_file.write_text(content, encoding=
"utf-8")
482 result = _run_fixed(command, environment)
484 result.returncode != 0
and "-FAKE" not in result.stdout,
485 f
"unsafe rig value reached fake transport: {content!r}",
489def _fake_loader_cases(env_file: Path, command: tuple[str, ...]) ->
None:
490 """Prove commands/secrets stay data and protected-path checks fail closed."""
491 marker = env_file.parent /
"executed"
494 + f
"TAPO_PASS='$(touch {marker}); literal spaces | data'\n"
495 + f
"touch {marker}\n",
498 environment = {
"PATH":
"/usr/bin:/bin",
"RA8_RIG_ENV": str(env_file)}
499 result = _run_fixed(command, environment)
501 not result.returncode
and not marker.exists(),
502 "declarative rig loader executed an unrelated command row",
504 env_file.chmod(0o644)
505 result = _run_fixed(command, environment)
506 _require(result.returncode != 0,
"interactive loader accepted mode-0644 input")
507 target = env_file.parent /
"target.env"
508 target.write_text(FAKE_VALID, encoding=
"utf-8")
511 env_file.symlink_to(target)
512 result = _run_fixed(command, environment)
513 _require(result.returncode != 0,
"interactive loader accepted a symlink input")
516def fake_transport_selftest() -> None:
517 """Prove accepted values are one argv and rejected values never reach fakes."""
518 with tempfile.TemporaryDirectory(prefix=
"ra8-rig-transport-")
as temporary:
519 env_file = Path(temporary) /
"rig.env"
526 str(REPO_ROOT / RIG_ENV),
528 _fake_value_cases(env_file, command)
529 _fake_loader_cases(env_file, command)
532def consumer_quoting_selftest() -> None:
533 """Prove the interpolation audit rejects bare option-position values."""
534 with tempfile.TemporaryDirectory(prefix=
"ra8-rig-consumer-")
as temporary:
535 root = Path(temporary)
536 script = root /
"scripts" /
"hil" /
"consumer.sh"
537 script.parent.mkdir(parents=
True)
539 '#!/bin/bash -p\nsource "$ROOT/scripts/hil/lib/rig_env.sh"\nssh $PI_HOST\n',
542 findings = audit_hil_consumers(root)
544 any(
"PI_HOST is not one quoted" in finding
for finding
in findings),
545 "consumer audit accepted an unquoted PI_HOST",
548 '#!/bin/bash -p\nsource "$ROOT/scripts/hil/lib/rig_env.sh"\nssh "$PI_HOST"\n',
551 _require(
not audit_hil_consumers(root),
"consumer audit rejected quoted PI_HOST")
553 '#!/bin/bash -p\nsource "$ROOT/scripts/hil/lib/rig_env.sh"\n'
554 "rig_require PI_HOST\ndevice R7KA8D2KF_CPU0\nsource .env\n",
557 findings = audit_hil_consumers(root)
559 any(
"hard-codes" in finding
for finding
in findings)
560 and any(
".env bypass" in finding
for finding
in findings),
561 "consumer audit accepted literal-device and executable-env bypasses",
565Mutation = tuple[Callable[[str], list[str]], str]
568def _authority_mutations(sources: dict[str, str]) -> tuple[Mutation, ...]:
569 """Build must-fire mutations for the loader, contracts, and consumers."""
573 sources[
"rig"].replace(
"ra8_rig_validate_loaded true",
": # omitted", 1),
577 sources[
"rig"].replace(
'source "$_rig_lib_dir/rig_contract.sh"',
":", 1),
581 sources[
"contract"].replace(
582 "BASH_FUNC_*%% | BASH_FUNC_*'()') ra8_startup_env_unset+=",
587 (audit_ansible, sources[
"ansible"].replace(
"rig_env_parse.py",
"other_parser.py", 1)),
590 sources[
"ansible"].replace(
"PI_REPO={{ dev_box_hil_pi_repo }}",
"", 1),
594 sources[
"ansible"].replace(
"PI_REPO={{ dev_box_hil_runner_bench_repo_dir }}",
"", 1),
598 sources[
"ansible"].replace(
599 'dev_box_hil_jlink_device: "{{ dev_box_hil_rig_env.JLINK_DEVICE }}"',
600 "dev_box_hil_runner_jlink_device_default",
606 sources[
"bench"].replace(
'JLinkExe -device "$device"',
"JLinkExe -device literal", 1),
609 audit_remote_gdb_args,
610 sources[
"remote"].replace(
611 'remote.add_argument("--device", required=True)',
612 'remote.add_argument("--device", default="literal")',
619def _ansible_reporting_mutations(sources: dict[str, str]) -> tuple[Mutation, ...]:
620 """Build must-fire mutations for same-run scratch reporting controls."""
621 allocation =
" register: dev_box_hil_rig_env_result\n"
622 allocation +=
" changed_when: false\n check_mode: false"
623 cleanup =
" - dev_box_hil_rig_env_result.path is defined\n"
624 cleanup +=
" changed_when: false\n check_mode: false"
628 sources[
"ansible"].replace(
630 allocation.replace(
" changed_when: false\n",
"", 1),
636 sources[
"ansible"].replace(
638 cleanup.replace(
" changed_when: false\n",
"", 1),
644 _append_duplicate_task(
646 "Allocate a protected allowlist result file on the control node",
651 _append_duplicate_task(
653 "Remove the protected allowlist result file",
659def _append_duplicate_task(text: str, name: str) -> str:
660 """Append one valid duplicate-name task for an exact-count mutation."""
662 f
"{text.rstrip()}\n\n- name: {name}\n"
663 " ansible.builtin.debug:\n"
664 " msg: duplicate exact-name fixture\n"
665 " changed_when: false\n"
666 " check_mode: false\n"
670def _parser_mutations(sources: dict[str, str]) -> tuple[Mutation, ...]:
671 """Build must-fire mutations for parser delegation and protected I/O."""
675 sources[
"parser"].replace(
676 'result = _run_contract("--validate", name, value)',
"return", 1
681 sources[
"parser"].replace(
'("/bin/bash", "-p", str(CONTRACT), *arguments)',
"()", 1),
683 (audit_parser, sources[
"parser"].replace(
"flags |= os.O_NOFOLLOW",
"pass", 1)),
684 (audit_parser, sources[
"parser"].replace(
"ASSIGNMENT_RE.fullmatch",
"re.match", 1)),
688def _static_mutations(sources: dict[str, str]) -> tuple[Mutation, ...]:
689 """Build exact must-fire mutations for every static authority seam."""
691 _authority_mutations(sources)
692 + _ansible_reporting_mutations(sources)
693 + _parser_mutations(sources)
697def selftest() -> None:
698 """Exercise every static binding and fake transport in both directions."""
700 "rig": _read(REPO_ROOT, RIG_ENV),
701 "contract": _read(REPO_ROOT, CONTRACT),
702 "ansible": _read(REPO_ROOT, ANSIBLE),
703 "bench": _read(REPO_ROOT, BENCH_ANSIBLE),
704 "parser": _read(REPO_ROOT, PARSER),
705 "remote": _read(REPO_ROOT, REMOTE_GDB_ARGS),
708 audit_rig_env(sources[
"rig"]),
709 audit_contract(sources[
"contract"]),
710 audit_ansible(sources[
"ansible"]),
711 audit_bench_ansible(sources[
"bench"]),
712 audit_parser(sources[
"parser"]),
713 audit_remote_gdb_args(sources[
"remote"]),
715 _require(
not any(audits),
"live approved consumer fixtures are inconsistent")
716 mutations = _static_mutations(sources)
717 for audit, mutated
in mutations:
719 bool(audit(mutated)),
720 f
"{audit.__name__} stayed quiet on a must-fire mutation",
722 fake_transport_selftest()
723 consumer_quoting_selftest()
725 "check_hil_rig_contract.py --selftest: PASS "
726 f
"({len(mutations) + 7} must-fire, 3 valid paths must-pass)"
731 """Run a selftest or scan the current repository."""
732 if sys.argv[1:] == [
"--selftest"]:
736 print(
"usage: check_hil_rig_contract.py [--selftest]", file=sys.stderr)
740 print(
"check_hil_rig_contract.py: findings:", file=sys.stderr)
741 for finding
in findings:
742 print(f
" {finding}", file=sys.stderr)
744 print(
"check_hil_rig_contract.py: shared contract, consumers, and HIL semantics agree")
748if __name__ ==
"__main__":
749 raise SystemExit(
main())
void main(void)
The application entry point Reset_Handler hands control to.