ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
hil_convergence_safety_v8.py
Go to the documentation of this file.
1# SPDX-License-Identifier: MIT
2# Copyright (c) 2026 Brighton Sikarskie
3"""Structural checks for the HIL convergence front-door and transport edges."""
4
5from __future__ import annotations
6
7import ast
8from dataclasses import dataclass
9
10import yaml
11
12
13@dataclass(frozen=True)
14class FrontDoor:
15 """One playbook's authenticated dynamic-role contract."""
16
17 label: str
18 guard_role: str
19 guard_file: str
20 fact: str
21 roles: tuple[str, ...]
22
23
24def _function(tree: ast.Module, name: str) -> ast.FunctionDef | None:
25 """Return one unique top-level function."""
26 found = [node for node in tree.body if isinstance(node, ast.FunctionDef) and node.name == name]
27 return found[0] if len(found) == 1 else None
28
29
30def _same(node: ast.AST, source: str) -> bool:
31 """Compare executable syntax while ignoring locations and comments."""
32 expected = ast.parse(source).body[0]
33 return ast.dump(node, include_attributes=False) == ast.dump(expected, include_attributes=False)
34
35
36def _index(function: ast.FunctionDef | None, source: str) -> int:
37 """Find one unique exact top-level statement."""
38 if function is None:
39 return -1
40 found = [index for index, node in enumerate(function.body) if _same(node, source)]
41 return found[0] if len(found) == 1 else -1
42
43
44def _assigned(function: ast.FunctionDef | None, name: str) -> ast.expr | None:
45 """Return one unique top-level assignment value."""
46 if function is None:
47 return None
48 found = [
49 node.value
50 for node in function.body
51 if isinstance(node, ast.Assign)
52 and len(node.targets) == 1
53 and isinstance(node.targets[0], ast.Name)
54 and node.targets[0].id == name
55 ]
56 return found[0] if len(found) == 1 else None
57
58
59def _exact_call_argv(
60 function: ast.FunctionDef | None,
61 module: str,
62 name: str,
63 expected_source: str,
64) -> bool:
65 """Return whether one module call has the exact first positional argv."""
66 if function is None:
67 return False
68 found = [
69 node.args[0]
70 for node in ast.walk(function)
71 if isinstance(node, ast.Call)
72 and isinstance(node.func, ast.Attribute)
73 and isinstance(node.func.value, ast.Name)
74 and node.func.value.id == module
75 and node.func.attr == name
76 and node.args
77 ]
78 expected = ast.parse(expected_source, mode="eval").body
79 return len(found) == 1 and ast.dump(found[0], include_attributes=False) == ast.dump(
80 expected, include_attributes=False
81 )
82
83
84def _dispatch_order_errors(fleet: ast.Module) -> list[str]:
85 """Require selector refusal before the lock wrapper and inventory preview."""
86 errors: list[str] = []
87 converge = _function(fleet, "cmd_converge")
88 order = (
89 "refusal = _converge_refusal(args, host, plays)",
90 "if refusal:\n return _fail(refusal)",
91 "guard = _bench_guard_argv(host, plays, args)",
92 (
93 "if guard:\n guardian = _bench_guard_subprocess_kwargs("
94 "args.command, fml.guardian_subprocess_kwargs)\n"
95 " return _run(guard, cwd=fm.REPO_ROOT, subprocess_kwargs=guardian)"
96 ),
97 "rc = cmd_inventory(data, argparse.Namespace(stdout=False))",
98 )
99 indices = [_index(converge, statement) for statement in order]
100 if -1 in indices or indices != sorted(indices):
101 errors.append("fleet.py: selector/extra-var refusal is not before lock and inventory")
102 refusal = _function(fleet, "_converge_refusal")
103 boundary = (
104 'boundary = fb.control_flow_refusal(fb.FlowRequest(str(host["class"]), plays, '
105 'args.mode, args.tags, args.extra_var, bool(getattr(args, "trusted_tags", False))))'
106 )
107 if _index(refusal, boundary) < 0 or _index(refusal, "if boundary:\n return boundary") < 0:
108 errors.append("fleet.py: bench control-flow refusal is not executable and exact")
109 return errors
110
111
112def _dispatch_errors(fleet: ast.Module, bench: ast.Module) -> list[str]:
113 """Require authentication before wrapper, inventory, or preview."""
114 errors = _dispatch_order_errors(fleet)
115 guard = _function(bench, "guarded_argv")
116 auth = (
117 "if lock_id:\n"
118 " capability = _capability(request.environment)\n"
119 " if not authenticate(request.repo_root, capability):\n"
120 " raise BenchGuardError(INHERITED_LOCK_ERROR)\n"
121 " return []"
122 )
123 if _index(guard, "lock_id = _lock_id(request.environment)") < 0 or _index(guard, auth) < 0:
124 errors.append("fleet_bench.py: inherited lock is trusted without live authentication")
125 wrapper = (
126 'return ["/bin/bash", "-p", '
127 'str(request.repo_root / "scripts/hil/bench.sh"), '
128 '"run", "--intent", "Ansible bench-affecting converge", '
129 '"--for", "2h", "--wait", "2h", "--", '
130 "str(request.fleet_script), *request.original_argv]"
131 )
132 if _index(guard, wrapper) < 0:
133 errors.append("fleet_bench.py: bench wrapper is not the exact privileged Bash transport")
134 live_auth = _function(bench, "_live_lock_matches")
135 live_auth_argv = (
136 '["/bin/bash", "--noprofile", "--norc", "-p", "-c", script, '
137 '"ra8-lock", str(client), capability.lock_id, digest, broker_digest]'
138 )
139 if not _exact_call_argv(live_auth, "subprocess", "run", live_auth_argv):
140 errors.append(
141 "fleet_bench.py: live-lock verifier is not the exact privileged Bash transport"
142 )
143 bash_probe = _function(bench, "_privileged_bash_selftest")
144 probe_argv = '["/bin/bash", "--noprofile", "--norc", "-p", "-c", probe]'
145 if not _exact_call_argv(bash_probe, "subprocess", "run", probe_argv):
146 errors.append("fleet_bench.py: privileged Bash execution probe argv is not exact")
147 runtime = _function(bench, "run_selftest")
148 if _index(runtime, "failures = _privileged_bash_selftest()") < 0:
149 errors.append("fleet_bench.py: privileged Bash execution probe is not load-bearing")
150 controls = _function(bench, "control_flow_refusal")
151 required = (
152 "if request.tags and not request.trusted_tags:\n"
153 ' return "bench-affecting applies do not accept --tags"',
154 "if request.extra_vars:\n"
155 ' return "bench-affecting applies do not accept raw --extra-var"',
156 )
157 if any(_index(controls, statement) < 0 for statement in required):
158 errors.append("fleet_bench.py: arbitrary tags or extra vars remain accepted")
159 return errors
160
161
162def _native_environment_errors(runner: ast.Module, reach: ast.Module) -> list[str]:
163 """Require minimal child environment, absolute SSH, and link census."""
164 errors: list[str] = []
165 environment = _function(runner, "ansible_environment")
166 expected_clean = ast.parse(
167 '{"HOME": pwd.getpwuid(os.getuid()).pw_dir, "LANG": "C.UTF-8", '
168 '"LC_ALL": "C.UTF-8", "PATH": "/usr/bin:/bin"}',
169 mode="eval",
170 ).body
171 clean = _assigned(environment, "clean")
172 if clean is None or ast.dump(clean, include_attributes=False) != ast.dump(
173 expected_clean, include_attributes=False
174 ):
175 errors.append("fleet runner: native Ansible child environment is not minimal")
176 link_call = "link_errors = fpa.confined_link_errors(collections)"
177 if _index(environment, link_call) < 0:
178 errors.append("fleet runner: collection link census is not before callback use")
179 ssh_target = _function(reach, "ssh_target")
180 expected_ssh = ast.parse('["/usr/bin/ssh", *SSH_OPTIONS]', mode="eval").body
181 ssh_argv = _assigned(ssh_target, "argv")
182 if ssh_argv is None or ast.dump(ssh_argv, include_attributes=False) != ast.dump(
183 expected_ssh, include_attributes=False
184 ):
185 errors.append("fleet reach: SSH executable is not an absolute trusted authority")
186 return errors
187
188
189def _wsl_boundary_errors(model: ast.Module, stage: ast.Module) -> list[str]:
190 """Require env-empty WSL Bash and absolute local archive creation."""
191 errors: list[str] = []
192 remote = _function(model, "remote_shell")
193 strings = set(_return_strings(remote))
194 required = {
195 "wsl -d ",
196 " -u root -e /usr/bin/env -i HOME=/root PATH=/usr/bin:/bin /bin/bash -s",
197 "/bin/bash -s",
198 }
199 if not required <= strings:
200 errors.append("fleet model: actual WSL boundary is not env-empty before Bash")
201 archive = _function(stage, "_stage_archive")
202 tar_tool = _assigned(archive, "tar_tool")
203 expected_tar = ast.parse('Path("/usr/bin/tar")', mode="eval").body
204 if tar_tool is None or ast.dump(tar_tool, include_attributes=False) != ast.dump(
205 expected_tar, include_attributes=False
206 ):
207 errors.append("fleet WSL stage: archive tool is not fixed /usr/bin/tar")
208 return errors
209
210
211def _return_strings(function: ast.FunctionDef | None) -> list[str]:
212 """Return list-literal strings which are executed by rendered shell."""
213 if function is None:
214 return []
215 return [
216 node.value
217 for node in ast.walk(function)
218 if isinstance(node, ast.Constant) and isinstance(node.value, str)
219 ]
220
221
222def _return_lines(function: ast.FunctionDef | None) -> set[str]:
223 """Return exact nonempty shell lines from executable return literals."""
224 return {
225 line.strip()
226 for value in _return_strings(function)
227 for line in value.splitlines()
228 if line.strip()
229 }
230
231
232def _durability_errors(stage: ast.Module, wsl: ast.Module) -> list[str]:
233 """Require file/parent fsyncs on every stage/cache namespace publication."""
234 owner = _return_lines(_function(stage, "_owned_shell"))
235 required_owner = {
236 "sync_file() {",
237 "sync_dir() {",
238 'sync_dir "$parent"',
239 }
240 if not required_owner <= set(owner):
241 return ["fleet WSL stage: owned removal is not parent-fsynced"]
242 required = {
243 "stage_prepare_script": {
244 'sync_dir "$(dirname -- "$stage")"': 1,
245 'sync_dir "$incoming"': 1,
246 'sync_dir "$(dirname -- "$incoming")"': 1,
247 },
248 "stage_publish_script": {'sync_dir "$(dirname -- "$stage")"': 3},
249 "cache_prepare_script": {'sync_dir "$cache_root"': 2},
250 "cache_cleanup_script": {'sync_dir "$cache_root"': 1},
251 "cache_publish_script": {'sync_file "$part"': 1, 'sync_dir "$cache_root"': 1},
252 }
253 errors: list[str] = []
254 for name, wanted in required.items():
255 lines = [
256 line.strip()
257 for value in _return_strings(_function(stage, name))
258 for line in value.splitlines()
259 if line.strip()
260 ]
261 if any(lines.count(line) != count for line, count in wanted.items()):
262 errors.append(f"fleet WSL stage: {name} lost crash-durable publication")
263 verify_lines = _return_lines(_function(wsl, "_toolchain_verify_lines"))
264 marker_lines = {
265 'mv -f -- "$marker" "$managed_root/.ra8-infra-lock.sha256"',
266 'sync_file "$managed_root/.ra8-infra-lock.sha256"',
267 'sync_dir "$managed_root"',
268 }
269 if not marker_lines <= verify_lines:
270 errors.append("fleet WSL: managed lock marker is not crash durable")
271 return errors
272
273
274def _one_play(source: str, label: str) -> tuple[dict[str, object] | None, list[str]]:
275 """Parse one exactly shaped playbook."""
276 try:
277 document = yaml.safe_load(source)
278 except yaml.YAMLError:
279 return None, [f"{label}: malformed YAML"]
280 if not isinstance(document, list) or len(document) != 1 or not isinstance(document[0], dict):
281 return None, [f"{label}: expected one play"]
282 return document[0], []
283
284
285def _playbook_frontdoor_errors(source: str, contract: FrontDoor) -> list[str]:
286 """Require dynamic role entry beneath one always-tagged guard."""
287 play, errors = _one_play(source, contract.label)
288 if play is None:
289 return errors
290 if play.get("roles"):
291 errors.append(f"{contract.label}: static roles can bypass the authenticated front door")
292 pre = play.get("pre_tasks")
293 tasks = play.get("tasks")
294 if not isinstance(pre, list) or len(pre) != 1 or not isinstance(tasks, list):
295 errors.append(f"{contract.label}: guard/task front door is incomplete")
296 return errors
297 include = pre[0].get("ansible.builtin.include_role") if isinstance(pre[0], dict) else None
298 expected_guard = {
299 "name": contract.guard_role,
300 "tasks_from": contract.guard_file,
301 "apply": {"tags": ["always"]},
302 }
303 if include != expected_guard or pre[0].get("tags") != ["always"]:
304 errors.append(f"{contract.label}: guard include is not exact and always-selected")
305 seen: list[str] = []
306 condition = f"{contract.fact} | default(false) | bool"
307 for task in tasks:
308 role = task.get("ansible.builtin.include_role") if isinstance(task, dict) else None
309 if (
310 not isinstance(role, dict)
311 or set(role) != {"name"}
312 or task.get("when") != condition
313 or task.get("tags") != ["always"]
314 ):
315 errors.append(
316 f"{contract.label}: a role is outside the authenticated dynamic front door"
317 )
318 break
319 seen.append(str(role["name"]))
320 if not errors and tuple(seen) != contract.roles:
321 errors.append(f"{contract.label}: guarded role closure drifted")
322 return errors
323
324
325def errors(inputs: dict[str, str]) -> list[str]:
326 """Return all v8 boundary findings, failing closed on syntax errors."""
327 try:
328 trees = {
329 key: ast.parse(inputs[key])
330 for key in (
331 "fleet",
332 "fleet_bench",
333 "fleet_runner",
334 "fleet_wsl_stage",
335 "fleet_wsl",
336 "fleet_model",
337 "fleet_runner_model",
338 "fleet_reach",
339 )
340 }
341 except SyntaxError:
342 return ["HIL convergence v8: invalid governed Python"]
343 return (
344 _dispatch_errors(trees["fleet"], trees["fleet_bench"])
345 + _native_environment_errors(trees["fleet_runner"], trees["fleet_reach"])
346 + _wsl_boundary_errors(trees["fleet_runner_model"], trees["fleet_wsl_stage"])
347 + _durability_errors(trees["fleet_wsl_stage"], trees["fleet_wsl"])
348 + _playbook_frontdoor_errors(
349 inputs["dev_playbook"],
350 FrontDoor(
351 "dev-box.yml",
352 "dev_box",
353 "hil_mutation_guard.yml",
354 "dev_box_hil_mutation_authenticated",
355 ("dev_box",),
356 ),
357 )
358 + _playbook_frontdoor_errors(
359 inputs["bench_playbook"],
360 FrontDoor(
361 "hil-bench.yml",
362 "hil_bench",
363 "transaction_guard.yml",
364 "hil_bench_transaction_authenticated",
365 ("hil_bench", "c6_toolchain", "ad2_tools"),
366 ),
367 )
368 )