ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
fleet_reconcile_selftest.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2# SPDX-License-Identifier: MIT
3# Copyright (c) 2026 Brighton Sikarskie
4"""Mutation-sensitive source contracts for fleet reconciliation deployment."""
5
6from __future__ import annotations
7
8import grp
9import os
10import subprocess
11import tempfile
12from pathlib import Path
13from typing import Protocol
14
15CAPACITY_LOCK_MODE = 0o660
16UNSAFE_MOVE_COUNT = 2
17
18
19class InventoryModel(Protocol):
20 """Mutable inventory authority surface exercised by the selftest."""
21
22 INVENTORY: Path
23 HOST_VARS_DIR: Path
24
25 def validate_runtime_inventory(self, state_dir: Path) -> None:
26 """Validate one installed runtime inventory."""
27
28
29def _named_task_block(role_text: str, task_name: str) -> str:
30 """Return one Ansible task block at either top-level or block depth."""
31 lines = role_text.splitlines(keepends=True)
32 matches = [
33 index for index, line in enumerate(lines) if line.lstrip() == f"- name: {task_name}\n"
34 ]
35 if len(matches) != 1:
36 msg = f"deployment role has no unique task named {task_name!r}"
37 raise ValueError(msg)
38 start = matches[0]
39 indentation = len(lines[start]) - len(lines[start].lstrip())
40 end = len(lines)
41 for index in range(start + 1, len(lines)):
42 line = lines[index]
43 same_depth = len(line) - len(line.lstrip()) == indentation
44 if same_depth and line.lstrip().startswith("- name: "):
45 end = index
46 break
47 prefix = " " * indentation
48 return "".join(line.removeprefix(prefix) for line in lines[start + 1 : end])
49
50
51def _deployment_uv_contract_errors(role_text: str) -> list[str]:
52 """Require candidate uv repair followed by a strictly read-only proof."""
53 errors: list[str] = []
54 try:
55 check = _named_task_block(role_text, "Prove the synchronized candidate Python environment")
56 sync = _named_task_block(role_text, "Synchronize the candidate locked Python environment")
57 except ValueError as error:
58 return [str(error)]
59 if "--run" not in check or "--ensure-and-run" in check:
60 errors.append("candidate uv proof is not strictly read-only")
61 if "--ensure-and-run" not in sync:
62 errors.append("candidate uv repair cannot populate its authenticated cache")
63 return errors
64
65
66def _task(role: str, name: str, errors: list[str]) -> str:
67 """Return one required task and record its absence."""
68 try:
69 return _named_task_block(role, name)
70 except ValueError:
71 errors.append(f"deployment task is missing: {name}")
72 return ""
73
74
75def _runtime_contract_errors(role: str) -> list[str]:
76 """Check independently converged private runtime paths."""
77 errors: list[str] = []
78 name = "Converge private reconciliation runtime paths on every apply"
79 if role.splitlines().count(f"- name: {name}") != 1:
80 errors.append("runtime convergence is missing or nested under source refresh")
81 converged = _task(role, name, errors)
82 inspected = _task(role, "Inspect reconciliation runtime paths", errors)
83 paths = ("ansible-local", "dev_box_reconcile_state }}/control", "ra8-fleet-mutation")
84 metadata = ('owner: "{{ dev_box_user }}"', 'group: "{{ dev_box_user }}"', 'mode: "0700"')
85 if any(value not in inspected for value in paths) or any(
86 value not in converged for value in metadata
87 ):
88 errors.append("runtime paths are not independently private and exact")
89 return errors
90
91
92def _source_contract_errors(role: str) -> list[str]:
93 """Check installed source metadata and interrupted-swap recovery."""
94 errors: list[str] = []
95 auth = _task(role, "Authenticate the installed reconciliation source metadata", errors)
96 required = (
97 "source_status.stat.isdir",
98 "source_status.stat.islnk",
99 "source_status.stat.uid",
100 "source_status.stat.gid",
101 "source_status.stat.mode",
102 "source_realpath.rc",
103 "source_realpath.stdout == dev_box_reconcile_source",
104 "marker.stat.isreg",
105 "marker.stat.islnk",
106 "marker.stat.uid",
107 "marker.stat.gid",
108 "marker.stat.mode",
109 "marker_realpath.rc",
110 "marker_realpath.stdout",
111 "== dev_box_reconcile_source ~ '/.ra8-source-sha256'",
112 )
113 if any(value not in auth for value in required):
114 errors.append("source-stale decision does not authenticate all root/marker metadata")
115 recovery = _task(role, "Recover the sole last-good source before inspecting freshness", errors)
116 proof = (
117 '[[ ! -e "$current" && ! -L "$current"',
118 '[[ -d "$previous" && ! -L "$previous" ]]',
119 'readlink -f -- "$previous"',
120 "== 0:0:755",
121 "== 0:0:444",
122 'mv -- "$previous" "$current"',
123 )
124 if any(value not in recovery for value in proof):
125 errors.append("interrupted source swap recovery is incomplete")
126 return errors
127
128
129def _candidate_contract_errors(role: str) -> list[str]:
130 """Check candidate, systemd, activation, and retention order."""
131 names = (
132 "Create the reconciliation candidate root",
133 "Synchronize the candidate locked Python environment",
134 "Prove the synchronized candidate Python environment",
135 "Synchronize the candidate Galaxy collection set",
136 "Read the synchronized candidate Galaxy collection set",
137 "Prove the synchronized candidate Galaxy collection set",
138 "Prove the candidate reconciliation controller",
139 "Render the candidate reconciliation unit set",
140 "Validate the staged reconciliation unit set",
141 "Activate the fully proven reconciliation generation",
142 "Start reconciliation from the activated generation",
143 "Verify the activated reconciliation generation is scheduled",
144 "Retire recovery artifacts only after the timer is proven",
145 )
146 positions = [role.find(f"- name: {name}\n") for name in names]
147 if any(position < 0 for position in positions) or positions != sorted(positions):
148 return ["candidate proof, activation, or retention ordering is incomplete"]
149 return []
150
151
152def _rollback_contract_errors(role: str) -> list[str]:
153 """Check isolated, authenticated, fail-closed last-good rollback."""
154 errors: list[str] = []
155 preserve = _task(role, "Preserve each safe last-good unit", errors)
156 if "unit-previous" not in preserve or "remote_src: true" not in preserve:
157 errors.append("safe last-good units are not retained before replacement")
158 switch = _task(role, "Switch source while retaining exactly one recovery generation", errors)
159 switch_proof = (
160 "dev_box_reconcile_installed_safe",
161 'mv -- "$current" "$previous"',
162 'mv -- "$current" "$failed"',
163 )
164 if any(value not in switch for value in switch_proof):
165 errors.append("unsafe current source is not isolated from last-good recovery")
166 restore = _task(role, "Restore the exact last-good source after post-switch failure", errors)
167 restore_proof = (
168 '[[ -d "$previous" && ! -L "$previous" ]]',
169 'readlink -f -- "$previous"',
170 "== 0:0:755",
171 '[[ -f "$marker" && ! -L "$marker" ]]',
172 'readlink -f -- "$marker"',
173 "== 0:0:444",
174 'cat -- "$marker"',
175 'mv -- "$previous" "$current"',
176 )
177 if any(value not in restore for value in restore_proof):
178 errors.append("last-good source rescue lacks complete authenticated metadata proof")
179 restart = _task(role, "Restart the exact last-good reconciliation timer", errors)
180 if any(
181 value not in restart
182 for value in ("'restored' in", "enabled: true", "state: started", "daemon_reload: true")
183 ):
184 errors.append("last-good timer is not restarted only after authenticated rescue")
185 fail_closed = _task(role, "Fail closed without an authenticated last-good generation", errors)
186 if "timer remains stopped" not in fail_closed:
187 errors.append("missing/tampered last-good does not leave the timer stopped")
188 return errors
189
190
191def _deployment_runtime_contract_errors(role: str) -> list[str]:
192 """Return all deployment authority errors."""
193 return (
194 _runtime_contract_errors(role)
195 + _source_contract_errors(role)
196 + _candidate_contract_errors(role)
197 + _rollback_contract_errors(role)
198 )
199
200
201def _check_mutations(
202 role: str, mutations: tuple[tuple[str, str, str], ...], failures: list[str]
203) -> None:
204 """Require each source mutation to trigger the contract checker."""
205 for old, new, label in mutations:
206 if role.count(old) != 1:
207 failures.append(f"deployment authority fixture is not unique: {label}")
208 elif not _deployment_runtime_contract_errors(role.replace(old, new, 1)):
209 failures.append(f"deployment authority mutation stayed invisible: {label}")
210
211
212def _authority_mutations() -> tuple[tuple[str, str, str], ...]:
213 """Return runtime and installed-source mutations."""
214 return (
215 (
216 "- name: Converge private reconciliation runtime paths on every apply\n",
217 " - name: Converge private reconciliation runtime paths on every apply\n",
218 "runtime nesting",
219 ),
220 ("dev_box_reconcile_source_status.stat.uid", "source_uid_removed", "source uid"),
221 ("dev_box_reconcile_source_status.stat.mode", "source_mode_removed", "source mode"),
222 ("dev_box_reconcile_source_realpath.rc", "source_realpath_removed", "source realpath"),
223 ("dev_box_reconcile_marker.stat.uid", "marker_uid_removed", "marker uid"),
224 ("dev_box_reconcile_marker.stat.mode", "marker_mode_removed", "marker mode"),
225 ("dev_box_reconcile_marker_realpath.rc", "marker_realpath_removed", "marker realpath"),
226 (
227 "- name: Recover the sole last-good source before inspecting freshness\n",
228 "- name: Recovery removed\n",
229 "interrupted recovery",
230 ),
231 )
232
233
234def _candidate_rollback_mutations() -> tuple[tuple[str, str, str], ...]:
235 """Return candidate, systemd, retention, and fail-closed mutations."""
236 return (
237 (
238 "- name: Synchronize the candidate locked Python environment\n",
239 "- name: Candidate uv removed\n",
240 "candidate uv",
241 ),
242 (
243 "- name: Prove the synchronized candidate Galaxy collection set\n",
244 "- name: Candidate Galaxy proof removed\n",
245 "candidate Galaxy",
246 ),
247 (
248 "- name: Prove the candidate reconciliation controller\n",
249 "- name: Candidate controller proof removed\n",
250 "candidate controller",
251 ),
252 (
253 "- name: Validate the staged reconciliation unit set\n",
254 "- name: Candidate unit proof removed\n",
255 "candidate units",
256 ),
257 (
258 "- name: Preserve each safe last-good unit\n",
259 "- name: Old units discarded\n",
260 "old unit retention",
261 ),
262 (
263 "- name: Retire recovery artifacts only after the timer is proven\n",
264 "- name: Recovery artifacts retired early\n",
265 "old source retention",
266 ),
267 (
268 "- name: Fail closed without an authenticated last-good generation\n",
269 "- name: Unsafe rollback allowed\n",
270 "no authenticated previous",
271 ),
272 (
273 "- name: Restart the exact last-good reconciliation timer\n",
274 "- name: Rescue restart removed\n",
275 "rescue restart",
276 ),
277 )
278
279
280def _check_unsafe_current(role: str, failures: list[str]) -> None:
281 """Require unsafe current source to route to failed, never previous."""
282 move = ' mv -- "$current" "$failed"\n'
283 if role.count(move) != UNSAFE_MOVE_COUNT:
284 failures.append("unsafe-current isolation fixture count drifted")
285 return
286 weakened = role.replace(move, ' mv -- "$current" "$previous"\n', 1)
287 if not _deployment_runtime_contract_errors(weakened):
288 failures.append("unsafe current without previous stayed rollback-eligible")
289
290
291def _selftest_deployment_runtime_contract(repo_root: Path, failures: list[str]) -> None:
292 """Prove deployment authority and every mutation direction."""
293 path = repo_root / "infra/ansible/roles/dev_box/tasks/fleet_reconcile.yml"
294 role = path.read_text(encoding="ascii")
295 failures.extend(_deployment_runtime_contract_errors(role))
296 _check_mutations(role, _authority_mutations(), failures)
297 _check_mutations(role, _candidate_rollback_mutations(), failures)
298 _check_unsafe_current(role, failures)
299
300
301def _selftest_deployment_uv_contract(repo_root: Path, failures: list[str]) -> None:
302 """Prove the candidate uv bootstrap works in both directions."""
303 role_path = repo_root / "infra/ansible/roles/dev_box/tasks/fleet_reconcile.yml"
304 role_text = role_path.read_text(encoding="ascii")
305 failures.extend(_deployment_uv_contract_errors(role_text))
306 repair_weakened = role_text.replace("--ensure-and-run", "--run", 1)
307 if not _deployment_uv_contract_errors(repair_weakened):
308 failures.append("candidate uv repair mutation stayed invisible")
309 check_start = role_text.index("- name: Prove the synchronized candidate Python environment")
310 check_weakened = role_text[:check_start] + role_text[check_start:].replace(
311 "--run", "--ensure-and-run", 1
312 )
313 if not _deployment_uv_contract_errors(check_weakened):
314 failures.append("candidate uv check mutation stayed invisible")
315
316
317def _write_capacity_fixture(source: str, root: Path, state: Path) -> Path:
318 """Write one isolated capacity script and fake Docker implementation."""
319 script = root / "capacity.sh"
320 old_state = 'RA8_FLEET_STATE_DIR="${RA8_FLEET_STATE_DIR:-/var/lib/ra8-fleet}"'
321 script.write_text(
322 source.replace(old_state, f'RA8_FLEET_STATE_DIR="{state}"'),
323 encoding="ascii",
324 )
325 script.chmod(0o755)
326 docker = root / "docker"
327 docker.write_text(
328 "#!/bin/bash\n"
329 "set -eu\n"
330 'printf \'%s\\n\' "$*" >>"$RA8_TEST_DOCKER_LOG"\n'
331 'case "$*" in\n'
332 " *\"ps -a\"*) printf 'runner\\n' ;;\n"
333 " *\"{{.State.Status}}\"*) printf 'exited\\n' ;;\n"
334 " *\"{{.Image}}\"*) printf 'sha256:test\\n' ;;\n"
335 ' *"run --rm"*) [ "${RA8_TEST_FAIL_ADMIT:-0}" = 0 ] ;;\n'
336 "esac\n",
337 encoding="ascii",
338 )
339 docker.chmod(0o755)
340 return script
341
342
343def _capacity_environment(root: Path, commands: Path) -> dict[str, str]:
344 """Return a quiet-hours environment for the isolated capacity script."""
345 return {
346 **os.environ,
347 "PATH": f"{root}:/usr/bin:/bin",
348 "RA8_TEST_DOCKER_LOG": str(commands),
349 "RA8_FLEET_DOCKER": "docker",
350 "RA8_FLEET_STATE_GROUP": grp.getgrgid(os.getgid()).gr_name,
351 "RA8_FLEET_FULL_INSTANCES": "1",
352 "RA8_FLEET_QUIET_INSTANCES": "0",
353 "RA8_FLEET_QUIET_START": "00:00",
354 "RA8_FLEET_QUIET_END": "23:59",
355 "RA8_FLEET_QUIET_DAYS": "Mon,Tue,Wed,Thu,Fri,Sat,Sun",
356 }
357
358
359def _run_capacity(
360 argv: list[str], environment: dict[str, str]
361) -> subprocess.CompletedProcess[bytes]:
362 """Run only the exact isolated capacity fixture assembled by this selftest."""
363 # The fixture executable and complete argv are assembled above, never
364 # caller supplied.
365 return subprocess.run(argv, env=environment, check=False) # noqa: S603 -- Exact fixture executable and argv.
366
367
368def _capacity_runtime_selftest(repo_root: Path, failures: list[str]) -> None:
369 """Exercise maintenance, timer exclusion, quiet restore, and retention."""
370 source_path = repo_root / "scripts/ci/fleet_capacity.sh"
371 source = source_path.read_text(encoding="ascii")
372 with tempfile.TemporaryDirectory(prefix="ra8-capacity-selftest-") as raw:
373 root = Path(raw)
374 state = root / "state"
375 state.mkdir(mode=0o770)
376 state.chmod(0o770)
377 commands = root / "docker.log"
378 script = _write_capacity_fixture(source, root, state)
379 environment = _capacity_environment(root, commands)
380 common = [str(script), "--kind", "docker", "--container", "runner"]
381 enter = _run_capacity([*common, "maintenance-enter"], environment)
382 marker = state / "maintenance"
383 lock = state / "capacity.lock"
384 if enter.returncode or not marker.is_file():
385 failures.append("maintenance entry did not durably park the host")
386 return
387 if not lock.is_file() or lock.stat().st_mode & 0o777 != CAPACITY_LOCK_MODE:
388 failures.append("first controller did not create the shared capacity lock safely")
389 before = commands.read_text(encoding="ascii")
390 window = _run_capacity([*common, "window"], environment)
391 after = commands.read_text(encoding="ascii")
392 if window.returncode or not marker.is_file() or " start " in after[len(before) :]:
393 failures.append("capacity timer admitted a host during maintenance")
394 restore = _run_capacity([*common, "restore"], environment)
395 if restore.returncode or marker.exists():
396 failures.append("quiet-hours restore did not clear maintenance")
397 quarantine = _run_capacity([*common, "quarantine"], environment)
398 failed_environment = {
399 **environment,
400 "RA8_FLEET_QUIET_DAYS": "",
401 "RA8_TEST_FAIL_ADMIT": "1",
402 }
403 failed = _run_capacity([*common, "restore"], failed_environment)
404 if quarantine.returncode or failed.returncode == 0 or not marker.is_file():
405 failures.append("failed restore did not retain durable quarantine")
406 bypass = _run_capacity([*common, "scale", "1"], environment)
407 if bypass.returncode == 0:
408 failures.append("caller-controlled scale bypassed durable maintenance")
409
410
411def _capacity_k8s_missing_selftest(repo_root: Path, failures: list[str]) -> None:
412 """Prove only exact missing-ARS maintenance entry is accepted as parked."""
413 source = (repo_root / "scripts/ci/fleet_capacity.sh").read_text(encoding="ascii")
414 with tempfile.TemporaryDirectory(prefix="ra8-capacity-k8s-") as raw:
415 root = Path(raw)
416 state = root / "state"
417 state.mkdir(mode=0o770)
418 state.chmod(0o770)
419 script = _write_capacity_fixture(source, root, state)
420 kubectl = root / "kubectl"
421 kubectl.write_text(
422 "#!/bin/bash\n"
423 'if [ "${RA8_TEST_K8S_ERROR:-}" = notfound ]; then\n'
424 " printf '%s\n' 'Error from server (NotFound): "
425 'autoscalingrunnersets.actions.github.com "ra8-ci" not found\' >&2\n'
426 " exit 1\n"
427 "fi\n"
428 "printf '%s\n' 'Unable to connect to the server: refused' >&2\n"
429 "exit 1\n",
430 encoding="ascii",
431 )
432 kubectl.chmod(0o755)
433 environment = {
434 **os.environ,
435 "PATH": f"{root}:/usr/bin:/bin",
436 "RA8_FLEET_STATE_GROUP": grp.getgrgid(os.getgid()).gr_name,
437 "RA8_FLEET_KUBECTL": str(kubectl),
438 "RA8_FLEET_FULL_INSTANCES": "1",
439 "RA8_TEST_K8S_ERROR": "notfound",
440 }
441 common = [str(script), "--kind", "k8s", "--scale-set", "ra8-ci"]
442 enter = _run_capacity([*common, "maintenance-enter"], environment)
443 if enter.returncode or not (state / "maintenance").is_file():
444 failures.append("exact missing ARC scale set did not enter maintenance")
445 quarantine = _run_capacity([*common, "quarantine"], environment)
446 if quarantine.returncode == 0:
447 failures.append("missing ARC scale set was accepted outside maintenance entry")
448 other = _run_capacity(
449 [*common, "maintenance-enter"],
450 {**environment, "RA8_TEST_K8S_ERROR": "transport"},
451 )
452 if other.returncode == 0:
453 failures.append("non-NotFound kubectl failure was accepted as zero admission")
454
455
456def _source_contract_selftest(repo_root: Path, failures: list[str]) -> None:
457 """Require parked roles, candidate rollback, and read-only WSL checks."""
458 paths = {
459 "fleet": repo_root / "scripts/dev/fleet.py",
460 "wsl": repo_root / "scripts/dev/fleet_wsl.py",
461 "stage": repo_root / "scripts/dev/fleet_wsl_stage.py",
462 "docker": repo_root / "infra/ansible/roles/ci_runner_docker/tasks/deploy.yml",
463 "arc": repo_root / "infra/ansible/roles/ci_runner/tasks/main.yml",
464 "role": repo_root / "infra/ansible/roles/dev_box/tasks/fleet_reconcile.yml",
465 "capacity-role": repo_root / "infra/ansible/roles/fleet_capacity/tasks/main.yml",
466 "arc-play": repo_root / "infra/ansible/playbooks/ci-runner.yml",
467 }
468 texts = {name: path.read_text(encoding="ascii") for name, path in paths.items()}
469 required = (
470 ("fleet", '["-e", "fleet_reconcile_parked=true"]', 1),
471 ("fleet", '["maintenance-enter"]', 1),
472 ("docker", "if fleet_reconcile_parked | default(false) | bool", 1),
473 ("arc", "{{ 0 if fleet_reconcile_parked", 2),
474 ("fleet", "fleet_reconcile_activation_hold=true", 1),
475 ("arc", "post_renderer: >-", 1),
476 ("arc", 'spec["maxRunners"] = 0', 1),
477 ("arc", "if matches != 1:", 1),
478 ("stage", 'mode == "apply" and not installed else "--verify-cache"', 1),
479 ("wsl", 'if spec.mode == "check":', 3),
480 ("wsl", 'fws.transaction_lock_lines(spec.mode != "check")', 1),
481 ("fleet", 'sync_image=request.args.mode == "apply"', 1),
482 ("capacity-role", 'path: "{{ fleet_capacity_state_dir }}/capacity.lock"', 1),
483 ("capacity-role", 'group: "{{ fleet_capacity_state_group }}"', 2),
484 ("capacity-role", 'mode: "0660"', 1),
485 ("arc-play", " - fleet_capacity\n", 1),
486 )
487 for name, value, expected in required:
488 if texts[name].count(value) != expected:
489 failures.append("parked role or WSL read-only contract count drifted")
490 weakened = texts[name].replace(value, "", 1)
491 if weakened.count(value) == expected:
492 failures.append("source contract mutation unexpectedly stayed invisible")
493
494
495def runtime_inventory_selftest(model: InventoryModel) -> list[str]:
496 """Prove installed inventory and host variables share one exact authority."""
497 failures: list[str] = []
498 original = model.INVENTORY
499 with tempfile.TemporaryDirectory(prefix="ra8-runtime-inventory-") as raw:
500 state_dir = Path(raw)
501 inventory_dir = state_dir / "inventory"
502 inventory_dir.mkdir()
503 model.INVENTORY = inventory_dir / "hosts.ini"
504 host_vars = inventory_dir / "host_vars"
505 host_vars.symlink_to(model.HOST_VARS_DIR)
506 try:
507 model.validate_runtime_inventory(state_dir)
508 host_vars.unlink()
509 host_vars.symlink_to(state_dir)
510 try:
511 model.validate_runtime_inventory(state_dir)
512 failures.append("runtime inventory accepted a foreign host-vars authority")
513 except ValueError:
514 pass
515 finally:
516 model.INVENTORY = original
517 return failures
518
519
520def run(repo_root: Path) -> list[str]:
521 """Return deployment contract failures for the injected repository root."""
522 failures: list[str] = []
523 _selftest_deployment_runtime_contract(repo_root, failures)
524 _selftest_deployment_uv_contract(repo_root, failures)
525 _capacity_runtime_selftest(repo_root, failures)
526 _capacity_k8s_missing_selftest(repo_root, failures)
527 _source_contract_selftest(repo_root, failures)
528 return failures