3"""Validate the authenticated bench-role entry points."""
5from __future__
import annotations
14FLEET_PAYLOAD_KEYS = 18
16BENCH_HOLDER_DECISION = (
17 "hil_bench_maintenance_record.resource == 'bench'",
18 "hil_bench_maintenance_record.lock_id == hil_bench_maintenance_lock_id",
19 "hil_bench_maintenance_record.hold_kind == 'wrapped'",
21BENCH_HOLDER_FILE_PROOF = (
22 "hil_bench_maintenance_record_stat.stat.exists",
23 "hil_bench_maintenance_record_stat.stat.isreg | default(false)",
24 "not hil_bench_maintenance_record_stat.stat.islnk | default(false)",
28class RoleError(ValueError):
29 """A required uniquely named task is missing or duplicated."""
32def _service_installer_errors(source: str) -> list[str]:
33 """Require fixed privileged Bash argv for both generated user services."""
35 tasks = yaml.safe_load(source)
36 except yaml.YAMLError:
37 return [
"dev_box transaction: malformed YAML"]
38 if not isinstance(tasks, list):
39 return [
"dev_box transaction: task list is malformed"]
41 "Install the shared CI status poller": [
44 "scripts/ci/monitor.sh",
47 "Install the workspace reaper": [
50 "scripts/dev/agent_workspace.sh",
54 found: dict[str, object] = {}
56 if isinstance(task, dict)
and task.get(
"name")
in expected:
57 command = task.get(
"ansible.builtin.command")
58 found[str(task[
"name"])] = command.get(
"argv")
if isinstance(command, dict)
else None
59 return []
if found == expected
else [
"dev_box transaction: service installer argv is not exact"]
62def _dev_shell_command_errors(source: str) -> list[str]:
63 """Reject PATH/startup-sensitive Bash commands in the dev-box transaction."""
65 tasks = yaml.safe_load(source)
66 except yaml.YAMLError:
67 return [
"dev_box transaction: malformed YAML"]
68 failures: list[str] = []
69 for task
in tasks
if isinstance(tasks, list)
else []:
70 if not isinstance(task, dict):
72 command = task.get(
"ansible.builtin.command")
73 if isinstance(command, dict):
74 raw = command.get(
"cmd")
75 argv = command.get(
"argv")
76 if isinstance(raw, str)
and re.search(
r"(^|\s)bash\s", raw):
77 failures.append(str(task.get(
"name",
"unnamed command")))
79 isinstance(argv, list)
81 isinstance(item, str)
and item.startswith(
"scripts/")
and item.endswith(
".sh")
84 and argv[:2] != [
"/bin/bash",
"-p"]
86 failures.append(str(task.get(
"name",
"unnamed command")))
91 if isinstance(task, dict)
92 if task.get(
"name") ==
"Keep the system Bash startup guard safe under nounset"
96 replace = guard.get(
"ansible.builtin.replace")
if isinstance(guard, dict)
else None
97 if not isinstance(replace, dict)
or replace.get(
"validate") !=
"/bin/bash -p -n %s":
98 failures.append(
"Bash startup guard validator")
99 return [f
"dev_box transaction: unsafe Bash boundary: {name}" for name
in failures]
102def _hil_just_errors(source: str) -> list[str]:
103 """Require every HIL recipe shell boundary to enter fixed privileged Bash."""
104 errors: list[str] = []
107 expected_environment = {
108 'export BASH_ENV := "/dev/null"',
109 'export ENV := "/dev/null"',
110 'export PYTHONHOME := ""',
111 'export PYTHONPATH := ""',
112 'export PYTHONNOUSERSITE := "1"',
113 'export RA8_TOOL_VENV := ""',
114 "export PATH := `/bin/bash -p scripts/ci/lib/host_tool_path.sh --print-path`",
116 if expected_environment - set(source.splitlines()):
117 errors.append(
"just/hil.just: public environment sanitizer is incomplete")
118 shebangs = [line.strip()
for line
in source.splitlines()
if line.lstrip().startswith(
"#!")]
119 if any(line !=
"#!/bin/bash -p" for line
in shebangs):
120 errors.append(
"just/hil.just: recipe shebang is not fixed privileged Bash")
121 executable = [line
for line
in source.splitlines()
if not line.lstrip().startswith(
"#")]
122 if any(re.search(
r"(?<!/)\bbash\b", line)
for line
in executable):
123 errors.append(
"just/hil.just: recipe invokes Bash through caller PATH")
127def dev_box_shell_boundary_errors(transaction_source: str, hil_just: str) -> list[str]:
128 """Return service, transaction-shell, and HIL-Just boundary findings."""
130 _service_installer_errors(transaction_source)
131 + _dev_shell_command_errors(transaction_source)
132 + _hil_just_errors(hil_just)
136def _pin_authority_errors(transaction_source: str, dockerfile_source: str) -> list[str]:
137 """Require every role-consumed pin to exist in the Dockerfile authority."""
139 tasks = yaml.safe_load(transaction_source)
140 except yaml.YAMLError:
141 return [
"dev box pins: malformed transaction"]
142 if not isinstance(tasks, list):
143 return [
"dev box pins: transaction is not a task list"]
147 if isinstance(task, dict)
148 and task.get(
"name") ==
"Assert every pin this role consumes is actually declared there"
150 consumed = matches[0].get(
"loop")
if len(matches) == 1
else None
151 if not isinstance(consumed, list)
or any(
not isinstance(pin, str)
for pin
in consumed):
152 return [
"dev box pins: consumed-pin census is missing or malformed"]
153 declared = set(re.findall(
r"(?m)^ARG ([A-Z0-9_]+)=", dockerfile_source))
154 missing = sorted(pin
for pin
in consumed
if pin
not in declared)
156 return [
"dev box pins: consumed names absent from Dockerfile: " +
", ".join(missing)]
160def _shell_authority_errors(root_justfile: str) -> list[str]:
161 """Require Just to enter every recipe with fixed privileged Bash."""
163 line.strip()
for line
in root_justfile.splitlines()
if line.startswith(
"set shell")
165 expected = [
'set shell := ["/bin/bash", "-puc"]']
166 return []
if definitions == expected
else [
"justfile: public shell authority is not exact"]
169def pin_and_shell_authority_errors(
170 transaction_source: str, dockerfile_source: str, root_justfile: str
172 """Return pin-declaration and public Just-shell authority findings."""
173 return _pin_authority_errors(transaction_source, dockerfile_source) + _shell_authority_errors(
178def startup_authority_selftest(prefix: tuple[str, ...]) -> list[str]:
179 """Prove presentation indentation does not change wrapper semantics."""
180 indented = tuple(f
" {line}" for line
in prefix)
183 if [line.strip()
for line
in indented] == [line.strip()
for line
in prefix]
184 else [
"indented canonical startup authority changed semantics"]
188def _tasks(source: str, label: str) -> tuple[list[dict[str, object]], list[str]]:
189 """Parse one role task list with attribution."""
191 value = yaml.safe_load(source)
192 except yaml.YAMLError:
193 return [], [f
"{label}: malformed YAML"]
194 if not isinstance(value, list)
or any(
not isinstance(item, dict)
for item
in value):
195 return [], [f
"{label}: expected a task list"]
196 return cast(list[dict[str, object]], value), []
199def _normalized(value: object) -> str:
200 """Collapse presentation whitespace without weakening expression bytes."""
201 return " ".join(str(value).split())
204def _conditions(task: dict[str, object]) -> tuple[str, ...]:
205 """Return normalized Ansible assert conditions, or an empty tuple."""
206 assertion = task.get(
"ansible.builtin.assert")
207 values = assertion.get(
"that")
if isinstance(assertion, dict)
else None
208 if not isinstance(values, list)
or any(
not isinstance(value, str)
for value
in values):
210 return tuple(_normalized(value)
for value
in values)
213def _fact_value(task: object, key: str) -> str:
214 """Return one normalized set_fact value, empty on shape drift."""
215 fact = task.get(
"ansible.builtin.set_fact")
if isinstance(task, dict)
else None
216 value = fact.get(key)
if isinstance(fact, dict)
else None
217 return _normalized(value)
220def _named(tasks: list[dict[str, object]], name: str) -> dict[str, object]:
221 """Return one uniquely named top-level task."""
222 matches = [task
for task
in tasks
if task.get(
"name") == name]
223 if len(matches) != 1:
224 message = f
"task {name!r} is missing or duplicated"
225 raise RoleError(message)
229def _include_guard_errors(
230 tasks: list[dict[str, object]],
233 expected: dict[str, object],
236 """Require an always-selected guard and its authenticated fact assertion."""
237 if len(tasks) < ROLE_PREFIX_LENGTH:
238 return [f
"{label}: authenticated role prefix is incomplete"]
242 "hil_bench_transaction_authenticated | default(false) | bool"
243 if "whole-bench" in name
244 else "dev_box_hil_mutation_authenticated | default(false) | bool"
247 include.get(
"name") != name
248 or include.get(module) != expected
249 or include.get(
"tags") != [
"always"]
250 or _conditions(assertion) != (fact,)
252 return [f
"{label}: independently selected role bypasses authentication"]
256def _health_errors(tasks: list[dict[str, object]], defaults: str) -> list[str]:
257 """Require the final health check to reuse the outer hold."""
258 name =
"Health check -- the EK-RA8D2 must be reachable over J-Link (EIL==HIL ground truth)"
260 health = _named(tasks, name)
261 except RoleError
as exc:
262 return [f
"hil_bench role: {exc}"]
263 shell = health.get(
"ansible.builtin.shell")
264 command = shell.get(
"cmd")
if isinstance(shell, dict)
else None
266 "device=$(/bin/bash -p {{ (hil_bench_repo_dir ~ "
267 "'/scripts/hil/lib/rig_contract.sh') | quote }} --default JLINK_DEVICE) && "
268 r"printf 'si 1\nspeed {{ hil_bench_jlink_speed }}\nr\nh\nq\n' "
269 '> /tmp/hil_bench_ping.jlink && JLinkExe -device "$device" -if SWD '
270 "-speed {{ hil_bench_jlink_speed }} "
271 "-autoconnect 1 -CommandFile /tmp/hil_bench_ping.jlink"
275 _normalized(command) != expected
276 or health.get(
"register") !=
"hil_bench_jlink_probe"
277 or health.get(
"changed_when")
is not False
278 or health.get(
"failed_when") !=
"'Cortex-M85' not in hil_bench_jlink_probe.stdout"
280 errors.append(
"hil_bench role: final health check can deadlock or escaped the outer hold")
281 document = yaml.safe_load(defaults)
282 if not isinstance(document, dict)
or document.get(
"hil_bench_maintenance_lock_id") !=
"":
283 errors.append(
"hil_bench defaults: direct apply does not fail closed")
284 if isinstance(document, dict)
and "hil_bench_jlink_device" in document:
285 errors.append(
"hil_bench defaults: duplicates the rig-contract J-Link device")
289def _binding_errors(tasks: list[dict[str, object]]) -> list[str]:
290 """Require controller-payload and kernel-lock authentication at the prefix."""
292 hold_expected = _normalized(
293 "ansible_check_mode or hil_bench_maintenance_lock_id is match('^[0-9a-f]{16}$')"
295 if _conditions(tasks[0]) != (hold_expected,):
296 errors.append(
"hil_bench role: maintenance hold assertion is not exact")
297 binding = tasks[1].get(
"block")
298 if tasks[1].get(
"when") !=
"not ansible_check_mode" or not isinstance(binding, list):
299 return [*errors,
"hil_bench role: live holder binding block is not exact"]
301 "Authenticate the controller and immutable fleet bench payload",
302 "Authenticate the canonical kernel-held bench lock",
304 if tuple(task.get(
"name")
for task
in binding
if isinstance(task, dict)) != expected_names:
305 return [*errors,
"hil_bench role: live capability task sequence is not exact"]
306 by_name = {task.get(
"name"): task
for task
in binding
if isinstance(task, dict)}
309 *_local_binding_errors(by_name[expected_names[0]]),
310 *_remote_binding_errors(by_name[expected_names[1]]),
314def _local_binding_errors(local: object) -> list[str]:
315 """Require the exact controller transaction and fleet payload key set."""
316 local_command = local.get(
"ansible.builtin.command")
if isinstance(local, dict)
else None
317 local_argv = local_command.get(
"argv")
if isinstance(local_command, dict)
else None
319 not isinstance(local, dict)
320 or local.get(
"delegate_to") !=
"localhost"
321 or local.get(
"become")
is not False
322 or local.get(
"changed_when")
is not False
323 or not isinstance(local_argv, list)
324 or len(local_argv) != LOCAL_AUTH_ARGC
327 "{{ playbook_dir }}/../../../.venv/bin/python3",
329 "{{ playbook_dir }}/../../../scripts/dev/fleet_transaction_auth.py",
330 "{{ inventory_hostname }}",
334 return [
"hil_bench role: fleet transaction command is not exact"]
337 if isinstance(local_argv, list)
and len(local_argv) == LOCAL_AUTH_ARGC
340 keys = re.findall(
r"'(hil_bench_[a-z0-9_]+)'\s*:", payload)
342 "{{ hil_bench_maintenance_lock_id }}",
343 "{{ hil_bench_maintenance_holder_pid | string }}",
344 "{{ hil_bench_maintenance_holder_start_ticks | string }}",
345 "{{ hil_bench_maintenance_holder_target }}",
348 len(keys) != FLEET_PAYLOAD_KEYS
349 or len(set(keys)) != FLEET_PAYLOAD_KEYS
350 or not isinstance(local_argv, list)
351 or local_argv[6:] != expected_tail
353 return [
"hil_bench role: immutable fleet payload key set is not exact"]
357def _remote_binding_errors(remote: object) -> list[str]:
358 """Require the exact by-value verifier and both reviewed digests."""
359 remote_command = remote.get(
"ansible.builtin.command")
if isinstance(remote, dict)
else None
360 remote_argv = remote_command.get(
"argv")
if isinstance(remote_command, dict)
else None
362 not isinstance(remote, dict)
363 or remote.get(
"delegate_to")
is not None
364 or remote.get(
"changed_when")
is not False
365 or not isinstance(remote_argv, list)
366 or len(remote_argv) != REMOTE_VERIFY_ARGC
367 or remote_argv[:4] != [
"/usr/bin/python3",
"-I",
"-S",
"-c"]
368 or remote_argv[5:7] != [
"{{ hil_bench_maintenance_lock_id }}",
"wrapped"]
370 return [
"hil_bench role: kernel lock verifier command is not exact"]
371 if isinstance(remote_argv, list)
and len(remote_argv) == REMOTE_VERIFY_ARGC:
372 source = _normalized(remote_argv[4])
373 digest = _normalized(remote_argv[7])
374 broker_digest = _normalized(remote_argv[8])
375 expected_source = _normalized(
376 "{{ lookup('ansible.builtin.file', "
377 "playbook_dir ~ '/../../../scripts/hil/lib/bench_lock_verify.py', rstrip=false) }}"
379 expected_digest = _normalized(
380 "{{ lookup('ansible.builtin.file', "
381 "playbook_dir ~ '/../../../scripts/hil/lib/bench_host.sh', rstrip=false) "
382 "| hash('sha256') }}"
384 expected_broker_digest = _normalized(
385 "{{ lookup('ansible.builtin.file', "
386 "playbook_dir ~ '/../../../scripts/hil/lib/bench_lock_broker.py', rstrip=false) "
387 "| hash('sha256') }}"
390 source != expected_source
391 or digest != expected_digest
392 or broker_digest != expected_broker_digest
394 return [
"hil_bench role: reviewed verifier/holder bytes are not exact"]
398def _role_prefix_errors(source: str, label: str, role: str) -> list[str]:
399 """Require each independently includable bench role to authenticate first."""
400 tasks, errors = _tasks(source, label)
403 if role ==
"hil_bench":
404 return _include_guard_errors(
406 "Authenticate the whole-bench transaction before this role",
407 "ansible.builtin.include_tasks",
408 {
"file":
"transaction_guard.yml",
"apply": {
"tags": [
"always"]}},
411 role_label =
"C6" if role ==
"c6_toolchain" else "AD2"
412 return _include_guard_errors(
414 f
"Authenticate the whole-bench transaction before the {role_label} role",
415 "ansible.builtin.include_role",
418 "tasks_from":
"transaction_guard.yml",
419 "apply": {
"tags": [
"always"]},
425def _entry_errors(source: str, label: str, name: str, file: str) -> list[str]:
426 """Require a single dynamic, always-selected transaction entry point."""
427 tasks, errors = _tasks(source, label)
428 expected = {
"file": file}
433 or tasks[0].get(
"name") != name
434 or tasks[0].get(
"ansible.builtin.include_tasks") != expected
435 or tasks[0].get(
"tags") != [
"always"]
437 return [f
"{label}: transaction entry can expose internal mutators to selectors"]
441def _entry_point_errors(inputs: dict[str, str]) -> list[str]:
442 """Validate each public dynamic transaction entry."""
446 "dev_box/tasks/main.yml",
447 "Enter the authenticated dev-box transaction",
452 "dev_box/tasks/hil_runner.yml",
453 "Enter the authenticated HIL-listener transaction",
454 "hil_runner_transaction.yml",
458 "hil_bench/tasks/main.yml",
459 "Enter the authenticated bench transaction",
464 "c6_toolchain/tasks/main.yml",
465 "Enter the authenticated C6 transaction",
470 "ad2_tools/tasks/main.yml",
471 "Enter the authenticated AD2 transaction",
475 findings: list[str] = []
476 for key, label, name, file
in entries:
477 findings.extend(_entry_errors(inputs[key], label, name, file))
481def errors(inputs: dict[str, str]) -> list[str]:
482 """Require live-holder binding before task one and no nested health lock."""
483 tasks, findings = _tasks(inputs[
"bench_role"],
"hil_bench/tasks/main.yml")
486 guard_tasks, guard_errors = _tasks(
487 inputs[
"bench_guard"],
"hil_bench/tasks/transaction_guard.yml"
491 if len(guard_tasks) < ROLE_PREFIX_LENGTH:
492 return [
"hil_bench role: live holder prefix is incomplete"]
494 "Require the fleet-owned maintenance transaction for a mutating converge",
495 "Bind this apply to the exact live wrapped bench holder",
497 if [task.get(
"name")
for task
in guard_tasks[:2]] != expected:
498 findings.append(
"hil_bench role: lock assertion/binding is not before every mutator")
499 findings.extend(_binding_errors(guard_tasks))
500 findings.extend(_health_errors(tasks, inputs[
"bench_defaults"]))
502 _role_prefix_errors(inputs[
"bench_role"],
"hil_bench/tasks/main.yml",
"hil_bench")
505 _role_prefix_errors(inputs[
"c6_role"],
"c6_toolchain/tasks/main.yml",
"c6_toolchain")
508 _role_prefix_errors(inputs[
"ad2_role"],
"ad2_tools/tasks/main.yml",
"ad2_tools")
510 return [*findings, *_entry_point_errors(inputs)]