ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
hil_convergence_check_mode.py
Go to the documentation of this file.
1# SPDX-License-Identifier: MIT
2# Copyright (c) 2026 Brighton Sikarskie
3"""Validate fresh-listener dry-run and post-apply service boundaries."""
4
5from __future__ import annotations
6
7LISTENER_ACTIVE_RETRIES = 15
8LISTENER_ACTIVE_DELAY_S = 2
9ASSERT_TASK_KEYS = frozenset({"name", "ansible.builtin.assert"})
10ASSERT_ARGUMENT_KEYS = frozenset({"that", "fail_msg"})
11STAT_TASK_KEYS = frozenset({"name", "become", "ansible.builtin.stat", "register", "changed_when"})
12
13
14class CheckModeFixtureError(ValueError):
15 """A governed listener task is missing or duplicated."""
16
17
18def _named(tasks: list[dict[str, object]], name: str) -> tuple[int, dict[str, object]]:
19 """Return one uniquely named top-level task."""
20 matches = [(index, task) for index, task in enumerate(tasks) if task.get("name") == name]
21 if len(matches) != 1:
22 message = f"task {name!r} is missing or duplicated"
23 raise CheckModeFixtureError(message)
24 return matches[0]
25
26
27def _normalized(value: object) -> str:
28 """Collapse presentation whitespace without weakening expression bytes."""
29 return " ".join(str(value).split())
30
31
32def _conditions(task: dict[str, object]) -> tuple[str, ...]:
33 """Return normalized assert conditions, or an empty tuple."""
34 assertion = task.get("ansible.builtin.assert")
35 values = assertion.get("that") if isinstance(assertion, dict) else None
36 if not isinstance(values, list) or any(not isinstance(value, str) for value in values):
37 return ()
38 return tuple(_normalized(value) for value in values)
39
40
41def _exact_assert(task: dict[str, object], expected: tuple[str, ...]) -> bool:
42 """Return whether one fail-closed assertion has only its governed controls."""
43 assertion = task.get("ansible.builtin.assert")
44 return (
45 set(task) == ASSERT_TASK_KEYS
46 and isinstance(assertion, dict)
47 and set(assertion) == ASSERT_ARGUMENT_KEYS
48 and isinstance(assertion.get("fail_msg"), str)
49 and _conditions(task) == expected
50 )
51
52
53def _exact_stat(task: dict[str, object], path: str, result: str) -> bool:
54 """Return whether one identity stat is exact and does not follow links."""
55 stat = task.get("ansible.builtin.stat")
56 return (
57 set(task) == STAT_TASK_KEYS
58 and task.get("become") is True
59 and isinstance(stat, dict)
60 and stat == {"path": path, "follow": False}
61 and task.get("register") == result
62 and task.get("changed_when") is False
63 )
64
65
66def _registration_errors(
67 registration: dict[str, object], identity: dict[str, object], token: dict[str, object]
68) -> list[str]:
69 """Require an exact no-follow registration marker and token decision."""
70 identity_expected = (
71 _normalized(
72 "not dev_box_hil_runner_registration.stat.exists or "
73 "dev_box_hil_runner_registration.stat.isreg"
74 ),
75 _normalized(
76 "not dev_box_hil_runner_registration.stat.exists or "
77 "not dev_box_hil_runner_registration.stat.islnk"
78 ),
79 )
80 token_expected = _normalized(
81 "dev_box_hil_runner_registration.stat.exists or "
82 "(dev_box_hil_runner_registration_token | default('') | length > 0)"
83 )
84 if (
85 not _exact_stat(
86 registration,
87 "{{ dev_box_hil_runner_root }}/.runner",
88 "dev_box_hil_runner_registration",
89 )
90 or not _exact_assert(identity, identity_expected)
91 or not _exact_assert(token, (token_expected,))
92 ):
93 return ["hil_runner.yml: first-registration identity/token preflight is not exact"]
94 return []
95
96
97def _public_key_errors(key_stat: dict[str, object], identity: dict[str, object]) -> list[str]:
98 """Require an exact real, regular, no-follow public-key decision."""
99 identity_expected = (
100 _normalized(
101 "not dev_box_hil_runner_public_key_stat.stat.exists or "
102 "dev_box_hil_runner_public_key_stat.stat.isreg"
103 ),
104 _normalized(
105 "not dev_box_hil_runner_public_key_stat.stat.exists or "
106 "not dev_box_hil_runner_public_key_stat.stat.islnk"
107 ),
108 )
109 if not _exact_stat(
110 key_stat,
111 "{{ dev_box_hil_runner_home }}/.ssh/id_ed25519.pub",
112 "dev_box_hil_runner_public_key_stat",
113 ) or not _exact_assert(identity, identity_expected):
114 return ["hil_runner.yml: fresh-account public-key identity preflight is not exact"]
115 return []
116
117
118def _account_errors(user: dict[str, object], home: dict[str, object]) -> list[str]:
119 """Require ordinary check-mode planning for account and home creation."""
120 user_expected = {
121 "name": "{{ dev_box_hil_runner_user }}",
122 "group": "{{ dev_box_hil_runner_group }}",
123 "home": "{{ dev_box_hil_runner_home }}",
124 "shell": "/usr/sbin/nologin",
125 "system": True,
126 "create_home": True,
127 "generate_ssh_key": True,
128 "ssh_key_type": "ed25519",
129 "ssh_key_file": ".ssh/id_ed25519",
130 "ssh_key_comment": "ra8-hil@dev",
131 }
132 home_expected = {
133 "path": "{{ dev_box_hil_runner_home }}",
134 "state": "directory",
135 "owner": "{{ dev_box_hil_runner_user }}",
136 "group": "{{ dev_box_hil_runner_group }}",
137 "mode": "0700",
138 }
139 if (
140 set(user) != {"name", "become", "ansible.builtin.user"}
141 or user.get("become") is not True
142 or user.get("ansible.builtin.user") != user_expected
143 or set(home) != {"name", "become", "ansible.builtin.file"}
144 or home.get("become") is not True
145 or home.get("ansible.builtin.file") != home_expected
146 ):
147 return ["hil_runner.yml: fresh-account user/home planning is not exact"]
148 return []
149
150
151def _fresh_account_errors(tasks: list[dict[str, object]]) -> list[str]:
152 """Require token preflight, then stop before a planned key is consumed."""
153 names = (
154 "Require the fleet-derived native HIL declaration",
155 "Check whether this runner is already registered",
156 "Refuse a linked or non-regular runner registration identity",
157 "Require a short-lived token only for first registration",
158 "Install the official runner's Debian runtime dependencies",
159 "Inspect the dedicated HIL public key before account planning",
160 "Refuse a linked or non-regular dedicated HIL public key",
161 "Create the isolated HIL runner account and its dedicated SSH key",
162 "Keep the isolated runner home private",
163 "Explain the fresh-account check-mode boundary",
164 "End a fresh-account check before consuming the planned public key",
165 "Read the dedicated HIL public key",
166 )
167 found = [_named(tasks, name) for name in names]
168 indices = [index for index, _ in found]
169 declaration, registration, registration_id, token, apt = found[:5]
170 key_stat, key_id, user, home, explain, end, slurp = found[5:]
171 boundary_when = ["ansible_check_mode", "not dev_box_hil_runner_public_key_stat.stat.exists"]
172 errors = []
173 if registration[0] != declaration[0] + 1 or token[0] >= apt[0]:
174 errors.append("hil_runner.yml: registration/token preflight follows a mutator")
175 if indices != sorted(indices) or indices[6:11] != list(range(indices[5] + 1, indices[5] + 6)):
176 errors.append("hil_runner.yml: fresh-account token/key boundary order is not exact")
177 errors.extend(_registration_errors(registration[1], registration_id[1], token[1]))
178 errors.extend(_public_key_errors(key_stat[1], key_id[1]))
179 errors.extend(_account_errors(user[1], home[1]))
180 if explain[1].get("when") != boundary_when or not isinstance(
181 explain[1].get("ansible.builtin.debug"), dict
182 ):
183 errors.append("hil_runner.yml: fresh-account boundary explanation is not exact")
184 if end[1].get("when") != boundary_when or end[1].get("ansible.builtin.meta") != "end_host":
185 errors.append("hil_runner.yml: fresh-account check does not end before key consumption")
186 if end[0] >= slurp[0]:
187 errors.append("hil_runner.yml: fresh-account boundary follows its key consumer")
188 return errors
189
190
191def _fresh_package_errors(tasks: list[dict[str, object]]) -> list[str]:
192 """Require a fresh-package dry run to stop before package byte consumers."""
193 names = (
194 "Decide whether the pinned runner package must be installed",
195 "Explain the fresh-runner check-mode boundary",
196 "End a fresh-runner check before consuming planned package bytes",
197 "Install the official service launcher beside the runner",
198 "Read back the installed runner version",
199 )
200 found = [_named(tasks, name) for name in names]
201 decide, explain, end, launcher, version = found
202 boundary_when = ["ansible_check_mode", "dev_box_hil_runner_install_needed | bool"]
203 errors = []
204 if explain[0] != decide[0] + 1 or end[0] != explain[0] + 1:
205 errors.append("hil_runner.yml: fresh-runner package boundary order is not exact")
206 if end[0] >= launcher[0] or end[0] >= version[0]:
207 errors.append("hil_runner.yml: fresh-runner package boundary follows a byte consumer")
208 if explain[1].get("when") != boundary_when or not isinstance(
209 explain[1].get("ansible.builtin.debug"), dict
210 ):
211 errors.append("hil_runner.yml: fresh-runner boundary explanation is not exact")
212 if end[1].get("when") != boundary_when or end[1].get("ansible.builtin.meta") != "end_host":
213 errors.append("hil_runner.yml: fresh-runner check does not end before byte consumers")
214 return errors
215
216
217def _listener_errors(tasks: list[dict[str, object]]) -> list[str]:
218 """Require planned start in check mode and live acceptance only after apply."""
219 start_at, start = _named(tasks, "Enable and start the dedicated HIL listener")
220 verify_at, verify = _named(tasks, "Verify the dedicated HIL listener is active")
221 service = start.get("ansible.builtin.systemd_service")
222 start_expected = {
223 "name": "{{ dev_box_hil_runner_service }}",
224 "enabled": True,
225 "state": "started",
226 "daemon_reload": True,
227 }
228 errors = []
229 if (
230 set(start) != {"name", "become", "ansible.builtin.systemd_service"}
231 or start.get("become") is not True
232 or service != start_expected
233 ):
234 errors.append("hil_runner.yml: listener start check-mode planning is not exact")
235 command = verify.get("ansible.builtin.command")
236 argv = command.get("argv") if isinstance(command, dict) else None
237 allowed = {
238 "name",
239 "when",
240 "become",
241 "ansible.builtin.command",
242 "register",
243 "changed_when",
244 "check_mode",
245 "retries",
246 "delay",
247 "until",
248 }
249 if (
250 verify_at != start_at + 1
251 or set(verify) != allowed
252 or verify.get("when") != "not ansible_check_mode"
253 or verify.get("become") is not True
254 or verify.get("register") != "dev_box_hil_runner_active"
255 or verify.get("changed_when") is not False
256 or verify.get("check_mode") is not False
257 or verify.get("retries") != LISTENER_ACTIVE_RETRIES
258 or verify.get("delay") != LISTENER_ACTIVE_DELAY_S
259 or _normalized(verify.get("until"))
260 != _normalized("dev_box_hil_runner_active.stdout | trim == 'active'")
261 or argv != ["systemctl", "is-active", "{{ dev_box_hil_runner_service }}"]
262 ):
263 errors.append("hil_runner.yml: post-apply listener liveness proof is not exact")
264 return errors
265
266
267def errors(tasks: list[dict[str, object]]) -> list[str]:
268 """Return every fresh-listener check-mode boundary defect."""
269 try:
270 return _fresh_account_errors(tasks) + _fresh_package_errors(tasks) + _listener_errors(tasks)
271 except CheckModeFixtureError as error:
272 return [f"hil_runner.yml: {error}"]