3"""Governed inputs and workflow policy for HIL convergence safety."""
5from __future__
import annotations
9from pathlib
import Path
10from typing
import cast
14REPO_ROOT = Path(__file__).resolve().parents[2]
15DEV_ENTRY =
"infra/ansible/roles/dev_box/tasks/hil_runner.yml"
16DEV_ROLE =
"infra/ansible/roles/dev_box/tasks/hil_runner_transaction.yml"
17DEV_MAIN_ENTRY =
"infra/ansible/roles/dev_box/tasks/main.yml"
18DEV_MAIN =
"infra/ansible/roles/dev_box/tasks/transaction.yml"
19DEV_IMAGE_LOCK =
"infra/ansible/roles/dev_box/tasks/image_lock.yml"
20DEV_GUARD =
"infra/ansible/roles/dev_box/tasks/hil_mutation_guard.yml"
21DEV_HANDLER =
"infra/ansible/roles/dev_box/handlers/main.yml"
22IDLE_HELPER =
"infra/ansible/roles/dev_box/files/ra8-hil-runner-idle-stop.py"
23BENCH_ENTRY =
"infra/ansible/roles/hil_bench/tasks/main.yml"
24BENCH_ROLE =
"infra/ansible/roles/hil_bench/tasks/transaction.yml"
25BENCH_GUARD =
"infra/ansible/roles/hil_bench/tasks/transaction_guard.yml"
26C6_ENTRY =
"infra/ansible/roles/c6_toolchain/tasks/main.yml"
27C6_ROLE =
"infra/ansible/roles/c6_toolchain/tasks/transaction.yml"
28AD2_ENTRY =
"infra/ansible/roles/ad2_tools/tasks/main.yml"
29AD2_ROLE =
"infra/ansible/roles/ad2_tools/tasks/transaction.yml"
30BENCH_DEFAULTS =
"infra/ansible/roles/hil_bench/defaults/main.yml"
31FLEET =
"scripts/dev/fleet.py"
32FLEET_BENCH =
"scripts/dev/fleet_bench.py"
33FLEET_CAPACITY_CLIENT =
"scripts/dev/fleet_capacity_client.py"
34FLEET_RECONCILE =
"scripts/dev/fleet_reconcile.py"
35FLEET_RECONCILE_ARC_SELFTEST =
"scripts/dev/fleet_reconcile_arc_selftest.py"
36FLEET_RECONCILE_PROCESS =
"scripts/dev/fleet_reconcile_process.py"
37FLEET_RUNNER =
"scripts/dev/fleet_runner_maintenance.py"
38FLEET_WSL =
"scripts/dev/fleet_wsl.py"
39FLEET_WSL_STAGE =
"scripts/dev/fleet_wsl_stage.py"
40FLEET_MODEL =
"scripts/dev/fleet_model.py"
41FLEET_RUNNER_MODEL =
"scripts/dev/fleet_runner_model.py"
42FLEET_REACH =
"scripts/dev/fleet_reach.py"
43FLEET_PATH_AUTHORITY =
"scripts/dev/fleet_path_authority.py"
44GATE =
"scripts/ci/gates/checks.sh"
45WORKFLOW =
".github/workflows/hil.yml"
46HIL_JUST =
"just/hil.just"
47DECLARATION =
"infra/fleet.yml"
49 "infra/ansible/playbooks/dev-box.yml",
50 "infra/ansible/playbooks/hil-bench.yml",
52DIRECT_DEPENDENCIES = (
53 ".devcontainer/Dockerfile",
56 "scripts/checks/check_runner_image_deps.py",
57 "scripts/checks/check_tool_versions.py",
59BASE_WORKFLOW_PATHS = (
63 "scripts/dev/fleet*.py",
64 "infra/ansible/ansible.cfg",
65 "infra/ansible/requirements.yml",
66 "scripts/checks/check_ansible_collections.py",
67 "scripts/checks/check_shebangs.py",
70 "scripts/checks/check_hil_convergence_safety.py",
71 "scripts/checks/hil_convergence_check_mode.py",
72 "scripts/checks/hil_convergence_safety_*.py",
73 "scripts/checks/shell_entrypoint_policy*.py",
78class WorkflowPolicyError(ValueError):
79 """A playbook cannot provide a safe workflow dependency closure."""
82def load_bench_transaction(root: Path) -> object:
83 """Follow the public bench role into its one owned dynamic transaction."""
84 entry_path = root / BENCH_ENTRY
85 entry = yaml.safe_load(entry_path.read_text(encoding=
"utf-8"))
86 if not isinstance(entry, list)
or len(entry) != 1
or not isinstance(entry[0], dict):
87 message =
"HIL bench role entry is not one dynamic transaction"
88 raise WorkflowPolicyError(message)
89 include = entry[0].get(
"ansible.builtin.include_tasks")
90 if not isinstance(include, dict)
or set(include) != {
"file"}:
91 message =
"HIL bench role entry does not own one task file"
92 raise WorkflowPolicyError(message)
93 relative = include[
"file"]
94 if relative !=
"transaction.yml":
95 message =
"HIL bench role transaction authority drifted"
96 raise WorkflowPolicyError(message)
97 transaction = entry_path.with_name(relative)
98 if transaction.is_symlink()
or not transaction.is_file():
99 message =
"HIL bench role transaction is unavailable or linked"
100 raise WorkflowPolicyError(message)
101 return yaml.safe_load(transaction.read_text(encoding=
"utf-8"))
104def workflow_paths(root: Path) -> tuple[str, ...]:
105 """Derive playbook/role closure, then add direct command-source inputs."""
106 derived: set[str] = set(BASE_WORKFLOW_PATHS)
107 derived.update(DIRECT_DEPENDENCIES)
108 initial_roles: list[str] = []
109 for playbook
in PLAYBOOKS:
110 document = yaml.safe_load((root / playbook).read_text(encoding=
"utf-8"))
111 if not isinstance(document, list):
112 message = f
"workflow playbook is malformed: {playbook}"
113 raise WorkflowPolicyError(message)
114 derived.add(playbook)
115 for play
in document:
116 if not isinstance(play, dict):
117 message = f
"workflow playbook has a malformed play: {playbook}"
118 raise WorkflowPolicyError(message)
119 roles = list(play.get(
"roles")
or [])
120 for section
in (
"pre_tasks",
"tasks"):
121 tasks = play.get(section)
or []
122 if not isinstance(tasks, list):
123 message = f
"workflow playbook has malformed {section}: {playbook}"
124 raise WorkflowPolicyError(message)
127 task.get(
"ansible.builtin.include_role")
if isinstance(task, dict)
else None
129 if isinstance(include, dict):
130 roles.append(include.get(
"name"))
132 message = f
"workflow playbook has no role closure: {playbook}"
133 raise WorkflowPolicyError(message)
134 initial_roles.extend(_role_name(role)
for role
in roles)
135 for role
in _role_closure(root, initial_roles):
136 derived.add(f
"infra/ansible/roles/{role}/**")
137 return tuple(sorted(derived))
140def _role_name(value: object) -> str:
141 """Return one confined static role name."""
142 if not isinstance(value, str)
or "/" in value
or value
in {
"",
".",
".."}:
143 msg = f
"workflow contains unsafe role: {value!r}"
144 raise WorkflowPolicyError(msg)
148def _role_dependencies(document: object, label: str) -> set[str]:
149 """Find nested include/import role names in one task or metadata tree."""
150 dependencies: set[str] = set()
151 if isinstance(document, list):
152 for item
in document:
153 dependencies.update(_role_dependencies(item, label))
154 elif isinstance(document, dict):
155 for key
in (
"ansible.builtin.include_role",
"ansible.builtin.import_role"):
156 include = document.get(key)
157 if isinstance(include, dict)
and "name" in include:
158 dependencies.add(_role_name(include[
"name"]))
159 elif include
is not None:
160 msg = f
"{label}: malformed {key}"
161 raise WorkflowPolicyError(msg)
162 for key, value
in document.items():
164 "ansible.builtin.include_role",
165 "ansible.builtin.import_role",
167 dependencies.update(_role_dependencies(value, label))
171def _included_task_files(path: Path, role_root: Path, seen: set[Path]) -> set[Path]:
172 """Follow static task includes with cycle and real-path confinement checks."""
173 resolved = path.resolve(strict=
True)
176 if resolved != role_root
and role_root
not in resolved.parents:
177 msg = f
"role task include escaped its owner: {path}"
178 raise WorkflowPolicyError(msg)
180 document = yaml.safe_load(resolved.read_text(encoding=
"utf-8"))
182 for task
in document
if isinstance(document, list)
else []:
183 if not isinstance(task, dict):
184 msg = f
"role task file is malformed: {path}"
185 raise WorkflowPolicyError(msg)
186 for key
in (
"ansible.builtin.include_tasks",
"ansible.builtin.import_tasks"):
187 include = task.get(key)
188 value = include.get(
"file")
if isinstance(include, dict)
else include
191 if not isinstance(value, str)
or "{{" in value
or Path(value).is_absolute():
192 msg = f
"role task include is not static: {value!r}"
193 raise WorkflowPolicyError(msg)
194 found.update(_included_task_files(resolved.parent / value, role_root, seen))
198def _one_role_dependencies(root: Path, role: str) -> set[str]:
199 """Return nested task/meta role dependencies for one exact role."""
200 roles_root = (root /
"infra/ansible/roles").resolve(strict=
True)
201 role_root = (roles_root / role).resolve(strict=
True)
202 if role_root.parent != roles_root
or (role_root /
"tasks/main.yml").is_symlink():
203 msg = f
"role path is linked or escaped: {role}"
204 raise WorkflowPolicyError(msg)
205 task_files = _included_task_files(role_root /
"tasks/main.yml", role_root, set())
206 dependencies: set[str] = set()
207 for path
in task_files:
209 _role_dependencies(yaml.safe_load(path.read_text(encoding=
"utf-8")), str(path))
211 meta = role_root /
"meta/main.yml"
213 document = yaml.safe_load(meta.read_text(encoding=
"utf-8"))
214 raw = document.get(
"dependencies")
if isinstance(document, dict)
else None
215 for item
in raw
or []:
216 value = item.get(
"role")
if isinstance(item, dict)
else item
217 dependencies.add(_role_name(value))
221def _role_closure(root: Path, initial: list[str]) -> set[str]:
222 """Return complete recursive task/meta role closure with cycle handling."""
223 closure: set[str] = set()
224 pending = list(initial)
226 role = _role_name(pending.pop())
230 pending.extend(sorted(_one_role_dependencies(root, role) - closure))
234def workflow_dependency_selftest() -> list[str]:
235 """Prove nested task includes and meta dependencies enter the trigger set."""
236 with tempfile.TemporaryDirectory(prefix=
"ra8-hil-workflow-")
as raw:
238 for playbook, role
in zip(PLAYBOOKS, (
"alpha",
"beta"), strict=
False):
239 target = root / playbook
240 target.parent.mkdir(parents=
True, exist_ok=
True)
241 target.write_text(f
"- hosts: all\n roles: [{role}]\n", encoding=
"utf-8")
243 "alpha/tasks/main.yml":
"- ansible.builtin.include_tasks: nested.yml\n",
244 "alpha/tasks/nested.yml":
"- ansible.builtin.include_role:\n name: gamma\n",
245 "beta/tasks/main.yml":
"- ansible.builtin.debug:\n msg: beta\n",
246 "beta/meta/main.yml":
"dependencies:\n - role: delta\n",
247 "gamma/tasks/main.yml":
"- ansible.builtin.debug:\n msg: gamma\n",
248 "delta/tasks/main.yml":
"- ansible.builtin.debug:\n msg: delta\n",
250 for relative, content
in fixtures.items():
251 path = root /
"infra/ansible/roles" / relative
252 path.parent.mkdir(parents=
True, exist_ok=
True)
253 path.write_text(content, encoding=
"utf-8")
254 found = set(workflow_paths(root))
255 required = {
"infra/ansible/roles/gamma/**",
"infra/ansible/roles/delta/**"}
256 return []
if required <= found
else [
"recursive task/meta role dependency escaped workflow"]
259def workflow_errors(workflow: str, declaration: str, root: Path = REPO_ROOT) -> list[str]:
260 """Bind both HIL parallelism variables and trusted path triggers to fleet."""
262 doc = yaml.safe_load(workflow)
263 fleet = yaml.safe_load(declaration)
264 except yaml.YAMLError:
265 return [
"hil workflow/fleet declaration: malformed YAML"]
266 if not isinstance(doc, dict)
or not isinstance(fleet, dict):
267 return [
"hil workflow/fleet declaration: expected mappings"]
268 expected = fleet.get(
"sizing", {}).get(
"build_parallelism")
269 environment = doc.get(
"env")
271 if not isinstance(environment, dict)
or any(
272 environment.get(name) != expected
for name
in (
"RA8_MAX_JOBS",
"CMAKE_BUILD_PARALLEL_LEVEL")
274 errors.append(
"hil.yml: both build limits must equal fleet build_parallelism")
275 triggers = doc.get(
True, doc.get(
"on"))
276 for event
in (
"push",
"pull_request"):
277 config = triggers.get(event)
if isinstance(triggers, dict)
else None
278 paths = config.get(
"paths")
if isinstance(config, dict)
else None
279 if not isinstance(paths, list)
or any(path
not in paths
for path
in workflow_paths(root)):
280 errors.append(f
"hil.yml: convergence safety paths missing from {event}")
284def _image_lock_selftest_process_tokens() -> tuple[str, ...]:
285 """Return bounded process, group, readiness, and worker-proof tokens."""
287 '[[ "${BASH_SOURCE[0]}" != "$0" ]]',
288 ' exec /usr/bin/setsid /bin/bash -p -- "$SELFTEST_IMAGE_ENTRY"',
289 "write_fake_image_runtime() {",
290 'RA8_CONTAINER_RUNTIME="$fake_runtime" RA8_IMAGE_LOCK_DIR="$managed" \\\n'
291 " cmd_ensure --rebuild",
292 'SELFTEST_DEADLINE_STEPS="${SELFTEST_DEADLINE_STEPS:?}"',
293 "bounded_child_reap() {",
294 ' bounded_process_terminal "$pid" || return 1\n if wait "$pid"; then',
295 "bounded_process_terminal() {\n"
296 ' local pid="$1" steps="${2:-$SELFTEST_DEADLINE_STEPS}" attempt\n'
297 " for ((attempt = 0; attempt < steps; ++attempt)); do",
298 "bounded_group_empty() {\n"
299 ' local pgid="$1" steps="${2:-$SELFTEST_DEADLINE_STEPS}" attempt live\n'
300 " for ((attempt = 0; attempt < steps; ++attempt)); do",
301 "bounded_group_gone() {\n"
302 ' local pgid="$1" attempt members\n'
303 " for ((attempt = 0; attempt < SELFTEST_DEADLINE_STEPS; ++attempt)); do",
304 "bounded_process_absent() {\n"
305 ' local pid="$1" attempt\n'
306 " for ((attempt = 0; attempt < SELFTEST_DEADLINE_STEPS; ++attempt)); do",
307 "wait_for_status_file() {\n"
308 ' local path="$1" pid="$2" attempt\n'
309 " for ((attempt = 0; attempt < SELFTEST_DEADLINE_STEPS; ++attempt)); do",
310 ' if process_is_terminal "$pid"; then\n'
311 ' [[ -s "$path" ]] && return 0\n'
313 "cleanup_image_lock_case() {",
314 "force_signal_controller_cleanup() {",
315 "fresh_lock_probe() {\n"
316 ' local lock="$SELFTEST_MANAGED_DIR/devcontainer-image.lock"\n'
317 ' if ! exec 7<"$lock"; then\n'
320 " if ! flock -n 7; then",
321 "worker_group_is_safe() {\n"
323 ' own_pgid="$(ps -o pgid= -p "$$" | tr -d \' \')"',
327def _image_lock_selftest_cleanup_tokens() -> tuple[str, ...]:
328 """Return group termination, lock release, and trap-lifecycle tokens."""
330 '[[ "$SELFTEST_WORKER_PGID" == "$SELFTEST_WORKER_PID" ]]',
331 '[[ "$SELFTEST_WORKER_PGID" != "$own_pgid" ]]',
332 "worker_group_signal_is_authorized() {\n"
333 ' worker_group_is_safe && [[ "$SELFTEST_WORKER_PGID" == "$SELFTEST_WORKER_PID" ]] &&\n'
334 ' shell_owns_live_child "$SELFTEST_WORKER_PID"',
335 "if worker_group_is_safe 2>/dev/null && ! bounded_group_empty "
336 '"$SELFTEST_WORKER_PGID"; then\n'
337 " if worker_group_signal_is_authorized 2>/dev/null; then\n"
338 " group_signal_authorized=1\n"
339 ' kill -TERM -- "-$SELFTEST_WORKER_PGID"',
340 'if [[ "$SELFTEST_WORKER_SHARED_GROUP" != "1" ]] &&\n'
341 " worker_group_is_safe 2>/dev/null && ! bounded_group_empty "
342 '"$SELFTEST_WORKER_PGID" 50; then\n'
343 ' if [[ "$group_signal_authorized" == "1" ]] &&\n'
344 " worker_group_signal_is_authorized 2>/dev/null; then\n"
345 ' kill -KILL -- "-$SELFTEST_WORKER_PGID"',
346 ' signal_owned_controller_group TERM "$controller" 2>/dev/null || return 1',
347 ' if ! bounded_process_terminal "$controller" "$SELFTEST_CONTROLLER_CLEANUP_STEPS"; then',
348 ' kill -KILL -- "-$controller" 2>/dev/null || return 1',
349 "release_parent_lock() {\n"
350 " local release_failed=0\n"
351 ' if [[ "$SELFTEST_PARENT_LOCK_OPEN" == "1" ]]; then\n'
352 " flock -u 8 || release_failed=1",
353 " trap image_lock_case_exit EXIT",
354 "clear_image_lock_case_traps() {\n restore_selftest_root_traps",
355 " trap 'image_lock_case_signal 129' HUP",
356 " trap 'image_lock_case_signal 130' INT",
357 " trap 'image_lock_case_signal 143' TERM",
361def _image_lock_selftest_scenario_tokens() -> tuple[str, ...]:
362 """Return attack dispatch, completion, and fallback-proof tokens."""
364 'image_lock_selftest_worker() {\n local mode="$1" managed="$2" case_dir="$3" fake_runtime',
365 'record_worker_group "$case_dir" "$mode"\n'
366 ' wait_for_worker_ack "$case_dir" "$$" || exit 124',
367 " exec 8>&-\n trap '' HUP INT TERM\n"
368 ' exec /usr/bin/setsid /bin/bash -p -- "$SELFTEST_IMAGE_ENTRY"',
369 " begin_selftest_spawn_critical abort_bound_worker_spawn || return 1",
370 ' selftest_inject_bound_exit worker "$SELFTEST_WORKER_PID"',
371 " if ! read_worker_group; then\n abort_bound_worker_spawn 1\n fi",
374 "normal | post-ready-build-hang)",
375 "start_image_lock_worker post-ready-build-hang ||",
376 " signal-controller)",
377 'wait_for_status_file "$SELFTEST_CASE_DIR/build-entered.status" "$SELFTEST_WORKER_PID"',
378 "assert_no_surviving_descendants() {",
379 "selftest_forced_build_contention() {",
380 " if fresh_lock_probe; then\n"
381 ' die "selftest: fresh lock probe accepted a held lock"\n'
383 ' fresh_lock_probe || die "selftest: fresh lock probe failed after worker completion"',
384 'reap_worker || die "selftest: normal child did not finish"',
385 'wait_for_status_file "$SELFTEST_CASE_DIR/done.status" '
386 '"$SELFTEST_WORKER_PID" ||\n'
387 ' die "selftest: forced rebuild did not complete after release"',
388 "assert_no_surviving_descendants || cleanup_failed=1",
389 "fresh_lock_probe || cleanup_failed=1",
390 'bounded_group_gone "$SELFTEST_WORKER_PGID" || cleanup_failed=1',
391 'bounded_process_absent "$pid" || return 1',
395def image_lock_cases_required_tokens() -> tuple[str, ...]:
396 """Return unique allocation and suite tokens owned by the cases helper."""
398 '"${BASH_SOURCE[1]:-missing}" -ef "$SELFTEST_CASES_PARENT"',
399 " begin_selftest_spawn_critical allocation_bound_spawn_signal ||",
400 ' selftest_controller_launcher_refusals "$tmp"',
401 ' selftest_controller_persisted_ps_failure "$tmp" ||',
402 ' run_image_lock_scenario early-exit selftest_early_exit "$tmp"',
403 ' run_image_lock_scenario pre-ready-hang selftest_pre_ready_hang "$tmp"',
404 ' run_image_lock_scenario forced-build-contention selftest_forced_build_contention "$tmp"',
405 ' run_image_lock_scenario post-ready-hang selftest_post_ready_hang "$tmp"',
406 ' run_image_lock_scenario signal-ready-timeout selftest_signal_ready_timeout "$tmp"',
407 ' run_image_lock_scenario signal-cleanup selftest_signal_cleanup "$tmp"',
411def image_lock_receipt_required_tokens() -> tuple[str, ...]:
412 """Return source authority and interfaces owned by the receipt helper."""
414 '[[ "${BASH_SOURCE[0]}" != "$0" ]] || {',
415 '"${BASH_SOURCE[1]:-missing}" -ef "$SELFTEST_LOCK_RECEIPT_PARENT"',
416 '"${DEVCONTAINER_SELFTEST_PARENT:-}" == '
417 '"$SELFTEST_LOCK_RECEIPT_PARENT_DIR/devcontainer_image.sh"',
418 "expected_image_lock_suite_receipts() {",
419 "scenario_receipt_value() {",
420 "validate_scenario_receipt_directory() {",
421 "write_scenario_receipt() {",
422 "require_scenario_receipt() {",
423 "verify_scenario_receipt_files() {",
424 "expected_cleanup_receipt() {",
425 "expected_force_cleanup_receipt() {",
426 "parent_lock_fd_is_closed() {",
427 "require_cleanup_receipt() {",
428 "require_force_cleanup_receipt() {",
429 "write_worker_cleanup_proof_file() {",
430 "require_worker_cleanup_proof_file() {",
431 "write_controller_cleanup_receipt_file() {",
432 "require_controller_cleanup_receipt_file() {",
436def image_lock_signal_required_tokens() -> tuple[str, ...]:
437 """Return unique signal-controller tokens owned by the signal helper."""
439 '"${BASH_SOURCE[1]:-missing}" -ef "$SELFTEST_SIGNAL_PARENT"',
440 "selftest_signal_ready_timeout() {",
441 "selftest_signal_cleanup() {",
442 ' [[ ! -e "$ready" && ! -L "$ready" && ! -e "$ack" && ! -L "$ack" ]] || return 1\n'
443 " begin_selftest_spawn_critical controller_bound_spawn_signal || return 1",
444 ' [[ ! -e "$ready" && ! -L "$ready" ]] || return 1\n'
445 " begin_selftest_spawn_critical controller_bound_spawn_signal || return 1",
446 ' selftest_inject_bound_exit controller "$controller_pid"',
447 ' kill -KILL -- "-$controller" 2>/dev/null || '
448 'bounded_group_gone "$controller" || return 1',
449 'selftest_signal_cleanup() {\n local tmp="$1" signal expected '
450 "controller case_dir managed launcher_mode\n for signal in HUP INT TERM; do",
451 'bounded_process_terminal "$controller" "$SELFTEST_CONTROLLER_CLEANUP_STEPS"',
452 ' force_signal_controller_cleanup "$controller" "$case_dir" "$managed" ||\n'
453 ' die "selftest: handler-hang group fallback failed"',
454 'if ! wait_for_status_file "$case_dir/controller-ready.status" "$controller"; then\n'
455 ' SELFTEST_FORCE_CLEANUP_RECEIPT=""\n'
456 ' force_signal_controller_cleanup "$controller" "$case_dir" "$managed" ||\n'
457 ' die "selftest: $signal unready-controller cleanup failed"',
461def _image_lock_selftest_semantic_tokens() -> tuple[str, ...]:
462 """Return fail-closed managed-object attack tokens."""
464 'if (RA8_IMAGE_LOCK_DIR="$tmp/missing-marker" resolve_image_lock ',
465 'if (RA8_IMAGE_LOCK_DIR="$directory" resolve_image_lock >/dev/null 2>&1); then\n'
466 ' die "selftest: symlinked managed image lock group marker passed"',
467 'if (RA8_IMAGE_LOCK_DIR="$directory" resolve_image_lock >/dev/null 2>&1); then\n'
468 ' die "selftest: multiply-linked managed image lock group marker passed"',
469 'die "selftest: wrong managed image lock directory group passed"',
470 'die "selftest: wrong managed image lock file group passed"',
471 'die "selftest: non-root managed image lock group marker passed"',
472 'die "selftest: writable managed image lock group marker passed"',
473 'die "selftest: root group in managed image lock group marker passed"',
474 'die "selftest: binary managed image lock group marker passed"',
478def image_lock_selftest_required_tokens() -> tuple[str, ...]:
479 """Return every unique token required from the image-lock selftest harness."""
481 '"${BASH_SOURCE[1]:-missing}" -ef "$SELFTEST_LOCK_HELPER_PARENT"',
482 *_image_lock_selftest_process_tokens(),
483 *_image_lock_selftest_cleanup_tokens(),
484 *_image_lock_selftest_scenario_tokens(),
485 *_image_lock_selftest_semantic_tokens(),
489def _image_selftest_source_paths() -> dict[str, str]:
490 """Return the complete image-selftest source authority mapping."""
492 "devcontainer_image":
"scripts/ci/devcontainer_image.sh",
493 "image_lock_digest":
"scripts/checks/hil_convergence_safety_image_lock_digest.py",
494 "image_harness_policy": (
"scripts/checks/hil_convergence_safety_image_harness_policy.py"),
495 "image_process_analysis": (
496 "scripts/checks/hil_convergence_safety_image_process_analysis.py"
498 "image_process_policy": (
"scripts/checks/hil_convergence_safety_image_process_policy.py"),
499 "image_subreaper_policy": (
500 "scripts/checks/hil_convergence_safety_image_subreaper_policy.py"
502 "process_source_fixtures": (
503 "scripts/checks/hil_convergence_safety_process_source_fixtures.py"
505 "raw_digest_controls":
"scripts/checks/hil_convergence_safety_raw_digest_controls.py",
506 "raw_digest_runtime":
"scripts/checks/hil_convergence_safety_raw_digest_runtime.py",
507 "runtime_cleanup":
"scripts/checks/hil_convergence_safety_runtime_cleanup.py",
508 "runtime_escape":
"scripts/checks/hil_convergence_safety_runtime_escape.py",
509 "runtime_fixtures":
"scripts/checks/hil_convergence_safety_runtime_fixtures.py",
510 "runtime_launcher":
"scripts/checks/hil_convergence_safety_runtime_launcher.py",
511 "runtime_loader":
"scripts/checks/hil_convergence_safety_runtime_loader.py",
512 "runtime_loader_harness":
"scripts/checks/hil_convergence_safety_runtime_loader_harness.py",
513 "runtime_root_swap":
"scripts/checks/hil_convergence_safety_runtime_root_swap.py",
514 "runtime_mutations":
"scripts/checks/hil_convergence_safety_runtime_mutations.py",
515 "runtime_sources":
"scripts/checks/hil_convergence_safety_runtime_sources.py",
516 "process_mutations":
"scripts/checks/hil_convergence_safety_process_mutations.py",
517 "semantic_mutations":
"scripts/checks/hil_convergence_safety_semantic_mutations.py",
518 "hil_convergence_entry":
"scripts/checks/check_hil_convergence_safety.py",
519 "source_fixtures":
"scripts/checks/hil_convergence_safety_source_fixtures.py",
520 "devcontainer_image_lock_receipts":
"scripts/ci/devcontainer_image_lock_receipts.bash",
521 "devcontainer_image_lock_selftest":
"scripts/ci/devcontainer_image_lock_selftest.bash",
522 "devcontainer_image_selftest":
"scripts/ci/devcontainer_image_selftest.bash",
523 "devcontainer_image_bound_exit_selftest": (
524 "scripts/ci/devcontainer_image_bound_exit_selftest.bash"
526 "devcontainer_image_selftest_cases":
"scripts/ci/devcontainer_image_selftest_cases.bash",
527 "devcontainer_image_signal_selftest":
"scripts/ci/devcontainer_image_signal_selftest.bash",
528 "devcontainer_image_selftest_supervisor": (
529 "scripts/ci/devcontainer_image_selftest_supervisor.py"
531 "devcontainer_image_selftest_supervisor_cases": (
532 "scripts/ci/devcontainer_image_selftest_supervisor_cases.py"
534 "devcontainer_image_selftest_process": (
535 "scripts/ci/devcontainer_image_selftest_process.py"
540def _governed_source_paths() -> dict[str, str]:
541 """Return the complete governed source-name to repository-path mapping."""
543 "dev_role": DEV_ROLE,
544 "dev_entry": DEV_ENTRY,
545 "dev_main": DEV_MAIN,
546 "dev_image_lock": DEV_IMAGE_LOCK,
547 "dev_defaults":
"infra/ansible/roles/dev_box/defaults/main.yml",
548 "dev_main_entry": DEV_MAIN_ENTRY,
549 "dev_guard": DEV_GUARD,
550 "idle_helper": IDLE_HELPER,
552 "fleet_bench": FLEET_BENCH,
553 "fleet_capacity_client": FLEET_CAPACITY_CLIENT,
554 "fleet_reconcile": FLEET_RECONCILE,
555 "fleet_reconcile_arc_selftest": FLEET_RECONCILE_ARC_SELFTEST,
556 "fleet_reconcile_process": FLEET_RECONCILE_PROCESS,
557 "fleet_runner": FLEET_RUNNER,
558 "fleet_wsl": FLEET_WSL,
559 "fleet_wsl_stage": FLEET_WSL_STAGE,
560 "fleet_model": FLEET_MODEL,
561 "fleet_runner_model": FLEET_RUNNER_MODEL,
562 "fleet_reach": FLEET_REACH,
563 "fleet_path_authority": FLEET_PATH_AUTHORITY,
565 "bench_role": BENCH_ROLE,
566 "bench_entry": BENCH_ENTRY,
567 "bench_guard": BENCH_GUARD,
569 "c6_entry": C6_ENTRY,
570 "ad2_role": AD2_ROLE,
571 "ad2_entry": AD2_ENTRY,
572 "bench_defaults": BENCH_DEFAULTS,
573 "workflow": WORKFLOW,
574 "declaration": DECLARATION,
575 "dev_playbook": PLAYBOOKS[0],
576 "bench_playbook": PLAYBOOKS[1],
577 "root_justfile":
"justfile",
578 "infra_just":
"just/infra.just",
579 "hil_just": HIL_JUST,
580 "infra_sh":
"scripts/dev/infra.sh",
581 "infra_bootstrap":
"infra/bootstrap.sh",
582 "dockerfile":
".devcontainer/Dockerfile",
583 **_image_selftest_source_paths(),
584 "dockerignore":
".dockerignore",
585 "ci_runner":
"infra/ansible/roles/ci_runner/tasks/main.yml",
586 "wsl_role":
"infra/ansible/roles/wsl_ci_host/tasks/main.yml",
587 "setup_ansible":
"scripts/dev/setup_ansible.sh",
588 "provision_toolchain":
"scripts/dev/provision_dev_box_toolchain.sh",
589 "bench_client":
"scripts/hil/lib/bench_client.sh",
590 "bench_host":
"scripts/hil/lib/bench_host.sh",
591 "bench_lock_verify":
"scripts/hil/lib/bench_lock_verify.py",
592 "bench_lock_capability":
"scripts/dev/bench_lock_capability.py",
593 "fleet_transaction_auth":
"scripts/dev/fleet_transaction_auth.py",
597def load_inputs(root: Path) -> dict[str, str]:
598 """Read all governed sources, treating the removed restart handler as empty."""
599 paths = _governed_source_paths()
600 result = {key: (root / value).read_text(encoding=
"utf-8")
for key, value
in paths.items()}
601 result[
"dev_transaction"] = result[
"dev_main"]
602 _header, separator, image_lock_tasks = result[
"dev_image_lock"].partition(
"---\n")
604 "- name: Converge the managed devcontainer image lock authority\n"
605 " ansible.builtin.include_tasks: image_lock.yml\n"
608 result[
"dev_main"] = result[
"dev_main"].replace(include, image_lock_tasks)
610 path.relative_to(root).as_posix(): path.read_text(encoding=
"utf-8")
611 for path
in sorted((root /
"scripts/hil").rglob(
"*.sh"))
614 monitor = root /
"scripts/ci/monitor.sh"
615 hil_shells[monitor.relative_to(root).as_posix()] = monitor.read_text(encoding=
"utf-8")
616 result[
"hil_shells"] = json.dumps(hil_shells, sort_keys=
True)
617 handler = root / DEV_HANDLER
618 result[
"dev_handler"] = handler.read_text(encoding=
"utf-8")
if handler.exists()
else ""
622def remove_workflow_path(inputs: dict[str, str], event: str, path: str) -> dict[str, str]:
623 """Remove one exact governed path from one workflow event."""
624 document = cast(dict[object, object], yaml.safe_load(inputs[
"workflow"]))
625 triggers = cast(dict[str, object], document.get(
True, document.get(
"on")))
626 config = cast(dict[str, object], triggers[event])
627 paths = cast(list[str], config[
"paths"])
628 if paths.count(path) != 1:
629 message = f
"workflow {event} path fixture is not unique: {path}"
630 raise ValueError(message)
632 changed = dict(inputs)
633 changed[
"workflow"] = yaml.safe_dump(document, sort_keys=
False)