3"""Validate the WSL managed Ansible and Python environment boundary."""
5from __future__
import annotations
10from hil_convergence_safety_ast
import assignment
as _assignment
11from hil_convergence_safety_ast
import function
as _function
12from hil_convergence_safety_ast
import module_assignment
as _module_assignment
13from hil_convergence_safety_ast
import nested_assignment
as _nested_assignment
14from hil_convergence_safety_ast
import return_strings
as _return_strings
17def _binary_errors(tree: ast.Module, render: ast.FunctionDef |
None) -> list[str]:
18 """Require the exact managed WSL playbook binary and rendered argv."""
20 "WSL_MANAGED_ROOT":
"/opt/ra8-python-tools",
21 "WSL_MANAGED_CACHE":
"/opt/ra8-python-tools-cache",
22 "WSL_ANSIBLE_PLAYBOOK":
"/opt/ra8-python-tools/bin/ansible-playbook",
23 "WSL_SYSTEM_PYTHON":
"/usr/bin/python3",
26 for name, wanted
in constants.items():
27 value = _module_assignment(tree, name)
28 if not isinstance(value, ast.Constant)
or value.value != wanted:
29 errors.append(f
"fleet WSL: {name} authority is not exact")
31 node
for node
in tree.body
if isinstance(node, ast.ClassDef)
and node.name ==
"ConvergeSpec"
38 for node
in specs[0].body
39 if isinstance(node, ast.AnnAssign)
40 and isinstance(node.target, ast.Name)
41 and node.target.id ==
"ansible_playbook"
45 wanted_field = ast.parse(
"WSL_ANSIBLE_PLAYBOOK", mode=
"eval").body
46 argv = _nested_assignment(render,
"argv")
if render
is not None else None
47 first = argv.elts[0]
if isinstance(argv, ast.List)
and argv.elts
else None
48 wanted_first = ast.parse(
"ansible_playbook", mode=
"eval").body
51 or ast.dump(field, include_attributes=
False)
52 != ast.dump(wanted_field, include_attributes=
False)
54 or ast.dump(first, include_attributes=
False)
55 != ast.dump(wanted_first, include_attributes=
False)
57 errors.append(
"fleet WSL: managed playbook executable binding is not exact")
61def _isolation_requirements() -> set[str]:
62 """Return required WSL environment-isolation commands."""
64 ' case "$name" in ANSIBLE_*) unset "$name" ;; esac',
65 ' case "$name" in PYTHONHOME|PYTHONPATH|PYTHONNOUSERSITE) unset "$name" ;; esac',
66 ' case "$name" in UV_*) unset "$name" ;; esac',
67 "export PYTHONNOUSERSITE=1",
71def _path_proof_requirements() -> set[str]:
72 """Return required managed-path identity and publication commands."""
74 ' [ -d "$1" ] && [ ! -L "$1" ] && [ "$(readlink -f -- "$1")" = "$1" ] || {',
75 ' [ -f "$1" ] && [ ! -L "$1" ] && [ "$(readlink -f -- "$1")" = "$1" ] || {',
76 "require_managed_python() {",
77 ' [ -L "$1" ] && [ "$(readlink -- "$1")" = python ] || {',
78 ' [ -L "$python_link" ] && [ "$(readlink -- "$python_link")" = "$2" ] &&',
79 ' [ "$(readlink -f -- "$1")" = "$(readlink -f -- "$2")" ] || {',
80 'require_real_dir "$(dirname "$managed_root")"',
81 'require_real_dir "$(dirname "$managed_cache")"',
82 ' ! /usr/bin/mountpoint -q -- "$1" || {',
83 "require_exact_file() {",
84 ' [ "$(stat -c %a -- "$1")" = "$2" ] || {',
85 ' file_digest="$(sha256sum -- "$1")"',
86 ' [ "${file_digest%% *}" = "$3" ] || {',
87 "/scripts/dev/bootstrap_uv.py",
88 "/scripts/dev/bootstrap_uv_exec.py",
96def _sync_requirements() -> set[str]:
97 """Return required locked uv synchronization commands."""
101 'if [ "$mode" = apply ]; then',
102 ' install -d -m 0755 -- "$managed_root"',
103 ' install -d -m 0755 -- "$managed_cache"',
104 ' refuse_mount "$managed_root"',
105 ' refuse_mount "$managed_cache"',
106 ' UV_PROJECT_ENVIRONMENT="$managed_root" UV_PYTHON_DOWNLOADS=never '
107 'UV_CACHE_DIR="$managed_cache" uv_run ',
108 ' UV_PROJECT_ENVIRONMENT="$managed_root" UV_PYTHON_DOWNLOADS=never '
109 'UV_CACHE_DIR="$managed_cache" uv_run --offline --no-cache ',
111 " --check || sync_status=$?",
112 ' if [ "$sync_status" -eq 1 ]; then',
113 ' [ "$sync_status" -eq 0 ] || exit "$sync_status"',
117def _verify_requirements() -> set[str]:
118 """Return required managed-environment verification commands."""
120 'require_managed_python "$managed_root/bin/python3" ',
121 'require_real_file "$managed_root/bin/ansible-galaxy"',
122 ' mv -f -- "$marker" "$managed_root/.ra8-infra-lock.sha256"',
123 ' sync_file "$managed_root/.ra8-infra-lock.sha256"',
124 ' sync_dir "$managed_root"',
125 'export ANSIBLE_CONFIG="$PWD/ansible.cfg"',
126 'export ANSIBLE_COLLECTIONS_PATH="$PWD/../../.ansible/collections"',
127 "export ANSIBLE_COLLECTIONS_SCAN_SYS_PATH=false",
128 " pipeline_status=(0 0)",
129 ' /dev/stdin || pipeline_status=("${PIPESTATUS[@]}")',
130 ' if [ "${pipeline_status[1]}" -eq 1 ] && [ "$mode" = check ]; then',
131 ' [ "${pipeline_status[0]}" -eq 0 ] || exit "${pipeline_status[0]}"',
132 ' [ "${pipeline_status[1]}" -eq 0 ] || exit "${pipeline_status[1]}"',
136def _required_strings(tree: ast.Module) -> bool:
137 """Return whether executable render helpers contain every safety decision."""
139 (_function(tree,
"_isolation_lines"), _isolation_requirements()),
140 (_function(tree,
"_toolchain_verify_lines"), _verify_requirements()),
142 proof_strings = set().union(
143 _return_strings(_function(tree,
"_proof_function_lines")),
144 _return_strings(_function(tree,
"_path_proof_lines")),
146 sync_strings = set().union(
148 _return_strings(_function(tree, name))
150 "_toolchain_sync_lines",
151 "_apply_environment_lines",
152 "_check_environment_lines",
157 all(wanted <= _return_strings(function)
for function, wanted
in requirements)
158 and _path_proof_requirements() <= proof_strings
159 and _sync_requirements() <= sync_strings
163def _exact_builder_contract(tree: ast.Module) -> bool:
164 """Require exact unmasked apply sync and executable-helper mode proofs."""
165 apply = _function(tree,
"_apply_environment_lines")
166 proof = _function(tree,
"_path_proof_lines")
168 next((node.value
for node
in apply.body
if isinstance(node, ast.Return)),
None)
173 next((node.value
for node
in proof.body
if isinstance(node, ast.Return)),
None)
177 if not isinstance(apply_return, ast.List)
or not isinstance(proof_return, ast.List):
179 expected_apply = ast.parse(
180 "' UV_PROJECT_ENVIRONMENT=\"$managed_root\" UV_PYTHON_DOWNLOADS=never ' "
181 "'UV_CACHE_DIR=\"$managed_cache\" uv_run ' "
185 expected_bootstrap = ast.parse(
186 "f\"require_exact_file {shlex.quote(stage + '/scripts/dev/bootstrap_uv.py')} \" "
187 'f"755 {bootstrap_digest}"',
190 expected_helper = ast.parse(
191 "f\"require_exact_file {shlex.quote(stage + '/scripts/dev/bootstrap_uv_exec.py')} \" "
192 'f"644 {helper_digest}"',
195 wanted = (expected_bootstrap, expected_helper)
197 ast.dump(item, include_attributes=
False)
198 == ast.dump(expected_apply, include_attributes=
False)
199 for item
in apply_return.elts
203 ast.dump(item, include_attributes=
False) == ast.dump(expected, include_attributes=
False)
204 for item
in proof_return.elts
206 for expected
in wanted
208 return apply_matches == 1
and proof_matches == [1, 1]
211def environment_errors(tree: ast.Module) -> list[str]:
212 """Require rendered WSL commands to scrub and bind environment controls."""
213 render = _function(tree,
"render_converge")
214 combined = _function(tree,
"_ansible_environment_lines")
215 runner = _function(tree,
"_run_script")
216 environment = _assignment(runner,
"env")
if runner
is not None else None
218 {key.value
for key
in environment.keys
if isinstance(key, ast.Constant)}
219 if isinstance(environment, ast.Dict)
222 hostile = {
"ANSIBLE_CONFIG",
"ANSIBLE_ROLES_PATH",
"PYTHONHOME",
"PYTHONPATH"}
223 render_lines = _assignment(render,
"lines")
if render
is not None else None
224 expected = ast.parse(
"_ansible_environment_lines(spec)", mode=
"eval").body
226 next((node.value
for node
in combined.body
if isinstance(node, ast.Return)),
None)
227 if combined
is not None
230 composition = ast.parse(
231 "[*_isolation_lines(), "
232 "*_path_proof_lines(spec.stage, spec.managed_root, spec.managed_cache), "
233 "*_toolchain_sync_lines(spec.stage, spec.mode, spec.system_python), "
234 "*_toolchain_verify_lines(spec.stage, spec.ansible_playbook, spec.system_python)]",
237 sync = _function(tree,
"_toolchain_sync_lines")
238 sync_strings = _return_strings(sync)
239 verify = _function(tree,
"_toolchain_verify_lines")
240 verify_strings = _return_strings(verify)
241 sync_flags = _assignment(sync,
"sync_flags")
if sync
is not None else None
242 wanted_flags = ast.parse(
243 'f"--no-config --directory {shlex.quote(stage)} sync --locked "'
244 'f"--only-group infra --no-install-project --python {shlex.quote(system_python)}"',
247 errors = _binary_errors(tree, render)
250 or ast.dump(render_lines, include_attributes=
False)
251 != ast.dump(expected, include_attributes=
False)
252 or combined_return
is None
253 or ast.dump(combined_return, include_attributes=
False)
254 != ast.dump(composition, include_attributes=
False)
255 or not _required_strings(tree)
256 or not _exact_builder_contract(tree)
258 "uv_bin" in value
or "--verify-cache" in value
or "|| true" in value
259 for value
in sync_strings | verify_strings
261 or not any(
"uv_run --no-config --directory " in value
for value
in verify_strings)
263 " export --locked --offline --only-group infra " in value
for value
in verify_strings
265 or sync_flags
is None
266 or ast.dump(sync_flags, include_attributes=
False)
267 != ast.dump(wanted_flags, include_attributes=
False)
268 or not hostile <= env_keys
270 errors.append(
"fleet WSL: rendered managed environment boundary is not exact")
274def _flatten_role_tasks(items: list[object]) -> list[dict[str, object]]:
275 """Return top-level and block-nested Ansible tasks in execution order."""
276 flattened: list[dict[str, object]] = []
278 if not isinstance(item, dict):
280 flattened.append(item)
281 for section
in (
"block",
"rescue",
"always"):
282 children = item.get(section)
283 if isinstance(children, list):
284 flattened.extend(_flatten_role_tasks(children))
288def _named_role_tasks(source: str) -> tuple[dict[str, tuple[int, dict[str, object]]], list[str]]:
289 """Parse uniquely named WSL role tasks for structural policy checks."""
291 value = yaml.safe_load(source)
292 except yaml.YAMLError:
293 return {}, [
"WSL clock: role task file is not valid YAML"]
294 if not isinstance(value, list):
295 return {}, [
"WSL clock: role task file is not a list"]
296 grouped: dict[str, list[tuple[int, dict[str, object]]]] = {}
297 for index, item
in enumerate(_flatten_role_tasks(value)):
298 if isinstance(item, dict)
and isinstance(item.get(
"name"), str):
299 grouped.setdefault(item[
"name"], []).append((index, item))
300 duplicated = [name
for name, matches
in grouped.items()
if len(matches) != 1]
302 return {}, [f
"WSL clock: duplicate task names: {', '.join(sorted(duplicated))}"]
303 return {name: matches[0]
for name, matches
in grouped.items()}, []
306def _required_role_task(
307 named: dict[str, tuple[int, dict[str, object]]], name: str, errors: list[str]
308) -> tuple[int, dict[str, object]] |
None:
309 """Return one required role task while attributing absence."""
310 task = named.get(name)
312 errors.append(f
"WSL clock: missing task {name!r}")
316def clock_errors(source: str) -> list[str]:
317 """Require slew-safe chrony readiness and clean removal ownership."""
318 named, errors = _named_role_tasks(source)
322 "Remove the managed chrony configuration",
323 "Ensure the chrony-wait override directory exists",
324 "Configure chrony-wait for slew-safe readiness",
325 "Enable chronyd and the unit that blocks until it has synchronised",
327 matches = [_required_role_task(named, name, errors)
for name
in names]
328 if any(match
is None for match
in matches):
330 removal, directory, override, enable = matches
331 if not removal[0] < directory[0] < override[0] < enable[0]:
332 errors.append(
"WSL clock: readiness task order is not exact")
333 removal_file = removal[1].get(
"ansible.builtin.file")
335 "/etc/chrony/conf.d/10-ra8-slew-not-step.conf",
336 "/etc/systemd/system/chrony-wait.service.d/10-ra8-slew-readiness.conf",
339 not isinstance(removal_file, dict)
340 or removal_file.get(
"path") !=
"{{ item }}"
341 or removal_file.get(
"state") !=
"absent"
342 or removal[1].get(
"loop") != removed
344 errors.append(
"WSL clock: removal does not retire both managed drop-ins")
345 directory_file = directory[1].get(
"ansible.builtin.file")
346 if not isinstance(directory_file, dict)
or directory_file != {
347 "path":
"/etc/systemd/system/chrony-wait.service.d",
348 "state":
"directory",
351 errors.append(
"WSL clock: chrony-wait override directory is not exact")
352 override_copy = override[1].get(
"ansible.builtin.copy")
354 "# Managed by the ra8-firmware wsl_ci_host Ansible role.\n"
355 "# A selected source is ready; remaining correction slews monotonically.\n"
356 "[Service]\nExecStart=\n"
357 "ExecStart=/usr/bin/chronyc -h 127.0.0.1,::1 waitsync 0 0 0 1\n"
359 if not isinstance(override_copy, dict)
or override_copy != {
360 "dest":
"/etc/systemd/system/chrony-wait.service.d/10-ra8-slew-readiness.conf",
364 errors.append(
"WSL clock: slew-safe chrony-wait override is not exact")
368def autostart_errors(source: str) -> list[str]:
369 """Require the Windows keep-alive to run before WSL image work."""
370 named, errors = _named_role_tasks(source)
374 "Register the Windows autostart task",
375 "Start the Windows autostart task now",
376 "Read back the task Windows actually stored",
377 "Assert the autostart task exists and is running after apply",
379 matches = [_required_role_task(named, name, errors)
for name
in names]
380 if any(match
is None for match
in matches):
382 create, start, query, assertion = matches
383 if not create[0] < start[0] < query[0] < assertion[0]:
384 errors.append(
"WSL autostart: keep-alive task order is not exact")
385 start_command = start[1].get(
"ansible.builtin.command")
386 start_argv = start_command.get(
"argv")
if isinstance(start_command, dict)
else None
388 start_argv != [
"{{ wsl_ci_host_schtasks }}",
"/Run",
"/TN",
"{{ wsl_ci_host_task_name }}"]
389 or start[1].get(
"when") !=
"not ansible_check_mode"
390 or start[1].get(
"changed_when")
is not False
392 errors.append(
"WSL autostart: immediate keep-alive start is not exact")
393 query_command = query[1].get(
"ansible.builtin.command")
394 query_argv = query_command.get(
"argv")
if isinstance(query_command, dict)
else None
396 "{{ wsl_ci_host_schtasks }}",
399 "{{ wsl_ci_host_task_name }}",
405 query_argv != expected_query
406 or query[1].get(
"register") !=
"wsl_ci_host_task_query"
407 or query[1].get(
"changed_when")
is not False
408 or query[1].get(
"failed_when")
is not False
409 or query[1].get(
"check_mode")
is not False
411 errors.append(
"WSL autostart: verbose task readback is not exact")
412 assert_module = assertion[1].get(
"ansible.builtin.assert")
413 expected_conditions = [
414 "wsl_ci_host_task_name in wsl_ci_host_task_query.stdout",
415 "ansible_check_mode or 'Running' in wsl_ci_host_task_query.stdout",
417 if not isinstance(assert_module, dict)
or assert_module.get(
"that") != expected_conditions:
418 errors.append(
"WSL autostart: running-state assertion is not exact")