4"""Serialize fleet mutations through an independent dev-host lock guardian."""
6from __future__
import annotations
20from collections.abc
import Iterator, Sequence
21from contextlib
import contextmanager, suppress
22from dataclasses
import dataclass
23from pathlib
import Path
24from threading
import Lock
27sys.path.insert(0, str(Path(__file__).resolve().parent))
29import fleet_model
as fm
30import fleet_reach
as fr
35REGISTER_FIELD_COUNT = 2
38GUARDIAN_ERROR_STATUS = 2
39LOCK_READY = b
"RA8-FLEET-MUTATION-LOCKED\n"
40GUARDIAN_FD_ENV =
"RA8_FLEET_MUTATION_GUARDIAN_FD"
41HOLDER_CONNECT_TIMEOUT = 15
42HOLDER_ALIVE_INTERVAL = 5
44HOLDER_READY_TIMEOUT = 20
47 "fd=int(sys.argv[1]);token=os.read(fd,1);os.close(fd);"
48 "sys.exit(125) if token != b'1' else os.execvp(sys.argv[2],sys.argv[2:])"
50GUARDIAN_REPLY_TIMEOUT = 5
52 "/bin/bash --noprofile --norc -p -c '"
54 'd="$HOME/.local/state/ra8-fleet-mutation"; lock="$d/mutation.lock"; '
55 '/usr/bin/mkdir -p -- "$d"; '
56 '[ ! -L "$d" ] && [ -d "$d" ]; '
57 'uid="$(/usr/bin/id -u)"; '
58 '[ "$(/usr/bin/stat -c %u -- "$d")" = "$uid" ]; '
59 '[ "$(/usr/bin/stat -c %a -- "$d")" = 700 ]; '
60 '[ ! -e "$lock" ] || { [ ! -L "$lock" ] && [ -f "$lock" ]; }; '
61 ': >>"$lock"; /usr/bin/chmod 600 -- "$lock"; '
62 '[ "$(/usr/bin/stat -c %u -- "$lock")" = "$uid" ]; '
63 '[ "$(/usr/bin/stat -c %a -- "$lock")" = 600 ]; '
65 'path_meta="$(/usr/bin/stat -c %d:%i:%u:%a:%F -- "$lock")"; '
66 'fd_meta="$(/usr/bin/stat -Lc %d:%i:%u:%a:%F -- /proc/$$/fd/9)"; '
67 '[ "$path_meta" = "$fd_meta" ]; '
68 '/usr/bin/flock -n -E 75 9 || { rc=$?; [ "$rc" -eq 75 ] && exit 75; exit "$rc"; }; '
69 f
'printf "{LOCK_READY.decode().rstrip()}\\n"; '
70 "/bin/cat >/dev/null'"
74class MutationLockBusyError(RuntimeError):
75 """Another controller owns the dev-host fleet mutation lock."""
78class MutationLockError(RuntimeError):
79 """The dev-host fleet mutation authority could not be reached safely."""
83class _CapabilityState:
84 """Mutable process-local capability state without module rebinding."""
86 active: socket.socket |
None =
None
89_CAPABILITY_STATE = _CapabilityState()
90_CAPABILITY_LOCK = Lock()
93def authority_host(data: dict[str, Any]) -> str:
94 """Return the unique declared dev control host."""
95 names = [name
for name, host
in data[
"hosts"].items()
if host.get(
"class") ==
"dev_box"]
97 message = f
"expected one dev_box mutation authority, found {len(names)}"
98 raise MutationLockError(message)
102def _local_lock_path() -> Path:
103 """Return a validated caller-owned local authority path."""
104 home = Path(pwd.getpwuid(os.getuid()).pw_dir)
105 directory = home /
".local/state/ra8-fleet-mutation"
107 directory.mkdir(mode=DIRECTORY_MODE, parents=
True, exist_ok=
True)
108 except OSError
as error:
109 message =
"cannot create local mutation lock directory"
110 raise MutationLockError(message)
from error
111 metadata = directory.lstat()
113 stat.S_ISLNK(metadata.st_mode)
114 or not stat.S_ISDIR(metadata.st_mode)
115 or metadata.st_uid != os.getuid()
116 or stat.S_IMODE(metadata.st_mode) != DIRECTORY_MODE
118 message =
"local mutation lock directory must be real, caller-owned, mode 0700"
119 raise MutationLockError(message)
120 return directory /
"mutation.lock"
123def _exclusive(path: Path) -> object:
124 """Open and acquire a validated nonblocking local lock."""
125 flags = os.O_RDWR | os.O_CREAT | os.O_APPEND | getattr(os,
"O_NOFOLLOW", 0)
126 descriptor = os.open(path, flags, LOCK_MODE)
127 stream = os.fdopen(descriptor,
"a+", encoding=
"ascii")
130 metadata = os.fstat(descriptor)
131 path_metadata = path.lstat()
133 not stat.S_ISREG(metadata.st_mode)
134 or stat.S_ISLNK(path_metadata.st_mode)
135 or metadata.st_uid != os.getuid()
136 or stat.S_IMODE(metadata.st_mode) != LOCK_MODE
137 or (metadata.st_dev, metadata.st_ino) != (path_metadata.st_dev, path_metadata.st_ino)
139 message =
"local mutation lock must be one caller-owned mode-0600 inode"
140 raise MutationLockError(message)
141 fcntl.flock(stream, fcntl.LOCK_EX | fcntl.LOCK_NB)
149def _holder_argv(data: dict[str, Any]) -> list[str]:
150 """Build the holder-only SSH transport with bounded liveness."""
151 target = fr.ssh_target(data, authority_host(data))
155 f
"ConnectTimeout={HOLDER_CONNECT_TIMEOUT}",
157 f
"ServerAliveInterval={HOLDER_ALIVE_INTERVAL}",
159 f
"ServerAliveCountMax={HOLDER_ALIVE_COUNT}",
165def _terminate_and_reap(process: subprocess.Popen[bytes]) ->
None:
166 """Bound termination and always reap a holder transport."""
167 if process.poll()
is None:
170 process.wait(timeout=CHILD_WAIT_TIMEOUT)
171 except subprocess.TimeoutExpired:
176def _read_holder_ready(process: subprocess.Popen[bytes], timeout: float) -> bytes:
177 """Read the holder token without permitting a silent transport to hang."""
178 if process.stdout
is None:
179 message =
"dev-host lock transport has no output pipe"
180 raise MutationLockError(message)
181 readable, _, _ = select.select([process.stdout], [], [], timeout)
183 message =
"dev-host mutation lock READY handshake timed out"
184 raise MutationLockError(message)
185 return process.stdout.readline()
188def _finish_holder_handshake(process: subprocess.Popen[bytes]) ->
None:
189 """Validate READY or classify the exact pre-token holder failure."""
190 token = _read_holder_ready(process, HOLDER_READY_TIMEOUT)
191 if token == LOCK_READY:
194 status = process.wait(timeout=CHILD_WAIT_TIMEOUT)
195 except subprocess.TimeoutExpired
as error:
196 message =
"dev-host lock transport was silent before READY"
197 raise MutationLockError(message)
from error
198 if status == LOCK_BUSY_STATUS:
199 message =
"another controller owns the dev-host mutation lock"
200 raise MutationLockBusyError(message)
201 message = f
"dev-host mutation lock setup failed before READY (rc={status})"
202 raise MutationLockError(message)
205def _open_remote_holder(data: dict[str, Any]) -> subprocess.Popen[bytes]:
206 """Acquire the remote flock through a bounded token handshake."""
207 process = subprocess.Popen(
208 _holder_argv(data), stdin=subprocess.PIPE, stdout=subprocess.PIPE
211 _finish_holder_handshake(process)
212 except (MutationLockError, OSError, subprocess.SubprocessError):
213 _terminate_and_reap(process)
218def _group_exists(process_group: int) -> bool:
219 """Return whether a protected group retains any live member."""
221 entries = tuple(Path(
"/proc").iterdir())
224 for entry
in entries:
225 if not entry.name.isdigit():
228 raw = (entry /
"stat").read_bytes()
229 except FileNotFoundError:
233 closing = raw.rfind(b
")")
234 fields = raw[closing + 2 :].split()
if closing >= 0
else []
236 state, _parent, group, *_remaining = fields
239 if not group.isdigit():
241 if int(group) == process_group
and state != b
"Z":
246def _signal_groups(groups: set[int], process_signal: int) ->
None:
247 """Signal every process group registered with this guardian."""
248 for process_group
in groups:
249 with suppress(ProcessLookupError):
250 os.killpg(process_group, process_signal)
253def _guardian_loop(capability: socket.socket, holder: object) -> int:
254 """Own the holder until capability closure and every mutation group exit."""
255 groups: set[int] = set()
257 remote = holder
if isinstance(holder, subprocess.Popen)
else None
258 capability.settimeout(0.0)
260 groups = {group
for group
in groups
if _group_exists(group)}
261 if remote
is not None and remote.poll()
is not None:
262 _signal_groups(groups, signal.SIGTERM)
264 _signal_groups(groups, signal.SIGKILL)
265 with suppress(OSError):
266 capability.send(b
"LOST")
268 if released
and not groups:
270 readable, _, _ = select.select([capability], [], [], 0.2)
274 request = capability.recv(128)
275 except BlockingIOError:
280 if request == b
"PING":
281 capability.send(b
"ACK")
283 fields = request.decode(
"ascii", errors=
"strict").split()
284 if len(fields) != REGISTER_FIELD_COUNT
or fields[0] !=
"REGISTER":
285 capability.send(b
"DENY")
288 process_group = int(fields[1])
290 capability.send(b
"DENY")
292 if process_group <= 0:
293 capability.send(b
"DENY")
295 groups.add(process_group)
296 capability.send(b
"ACK")
300 data: dict[str, Any], installed_local: bool, capability: socket.socket, status_fd: int
302 """Acquire authority, publish READY, and supervise independently."""
303 holder: object |
None =
None
304 status = os.fdopen(status_fd,
"wb", buffering=0)
306 holder = _exclusive(_local_lock_path())
if installed_local
else _open_remote_holder(data)
307 status.write(b
"READY\n")
308 result = _guardian_loop(capability, holder)
309 status.write(f
"EXIT {result}\n".encode(
"ascii"))
310 except MutationLockBusyError:
311 status.write(b
"BUSY\n")
312 result = LOCK_BUSY_STATUS
318 subprocess.SubprocessError,
320 status.write(f
"ERROR {type(error).__name__}: {error}\n".encode(
"utf-8", errors=
"replace"))
321 result = GUARDIAN_ERROR_STATUS
323 if isinstance(holder, subprocess.Popen):
324 if holder.stdin
is not None:
325 with suppress(BrokenPipeError):
327 _terminate_and_reap(holder)
328 elif holder
is not None:
335def _read_status_line(descriptor: int, timeout: float) -> bytes:
336 """Read one bounded guardian status record."""
337 readable, _, _ = select.select([descriptor], [], [], timeout)
339 message =
"mutation guardian startup timed out"
340 raise MutationLockError(message)
342 while not output.endswith(b
"\n"):
343 chunk = os.read(descriptor, 1)
350def _start_guardian(data: dict[str, Any], installed_local: bool) -> tuple[int, socket.socket, int]:
351 """Fork a session-independent guardian and await authoritative readiness."""
352 parent_socket, guardian_socket = socket.socketpair(socket.AF_UNIX, socket.SOCK_SEQPACKET)
353 status_read, status_write = os.pipe()
356 parent_socket.close()
357 os.close(status_read)
358 with suppress(OSError):
360 for process_signal
in (signal.SIGTERM, signal.SIGHUP, signal.SIGINT, signal.SIGQUIT):
361 signal.signal(process_signal, signal.SIG_IGN)
362 _guardian_main(data, installed_local, guardian_socket, status_write)
363 guardian_socket.close()
364 os.close(status_write)
366 record = _read_status_line(status_read, HOLDER_READY_TIMEOUT + 5)
367 except (MutationLockError, OSError):
368 parent_socket.close()
369 with suppress(ProcessLookupError):
370 os.kill(pid, signal.SIGKILL)
372 os.close(status_read)
374 if record == b
"READY\n":
375 return pid, parent_socket, status_read
376 parent_socket.close()
378 os.close(status_read)
379 if record == b
"BUSY\n":
380 message =
"another controller owns the dev-host mutation lock"
381 raise MutationLockBusyError(message)
382 message = record.decode(
"utf-8", errors=
"replace").strip()
384 message =
"guardian exited before READY"
385 raise MutationLockError(message)
388def _socket_from_descriptor(descriptor: int) -> socket.socket:
389 """Validate and adopt one inherited AF_UNIX capability descriptor."""
390 candidate = socket.socket(fileno=descriptor)
391 if candidate.family != socket.AF_UNIX:
393 message =
"guardian descriptor is not an AF_UNIX socket"
394 raise OSError(message)
398def _capability_socket() -> socket.socket:
399 """Resolve the active inherited guardian capability, never an environment claim alone."""
400 if _CAPABILITY_STATE.active
is not None:
401 return _CAPABILITY_STATE.active
402 raw = os.environ.get(GUARDIAN_FD_ENV)
404 message =
"mutating fleet command requires a live guardian capability"
405 raise MutationLockError(message)
407 descriptor = int(raw)
408 _CAPABILITY_STATE.active = _socket_from_descriptor(descriptor)
409 except (ValueError, OSError)
as error:
410 message =
"inherited guardian capability is invalid"
411 raise MutationLockError(message)
from error
412 return _CAPABILITY_STATE.active
415def require_guardian_capability() -> None:
416 """Validate live authority and register this mutation process group."""
417 capability = _capability_socket()
418 with _CAPABILITY_LOCK:
419 prior_timeout = capability.gettimeout()
421 capability.settimeout(GUARDIAN_REPLY_TIMEOUT)
422 capability.send(f
"REGISTER {os.getpgrp()}".encode(
"ascii"))
423 if capability.recv(16) != b
"ACK":
424 message =
"mutation guardian denied the process group"
425 raise MutationLockError(message)
426 except (OSError, TimeoutError)
as error:
427 message =
"mutation guardian lease is lost"
428 raise MutationLockError(message)
from error
430 with suppress(OSError):
431 capability.settimeout(prior_timeout)
434def guardian_subprocess_kwargs() -> dict[str, object]:
435 """Prove live authority, then return the FD inheritance for a guarded spawn."""
436 capability = _capability_socket()
437 with _CAPABILITY_LOCK:
438 prior_timeout = capability.gettimeout()
440 capability.settimeout(GUARDIAN_REPLY_TIMEOUT)
441 capability.send(b
"PING")
442 if capability.recv(16) != b
"ACK":
443 message =
"mutation guardian denied child preparation"
444 raise MutationLockError(message)
445 except (OSError, TimeoutError)
as error:
446 message =
"mutation guardian was lost before child spawn"
447 raise MutationLockError(message)
from error
449 with suppress(OSError):
450 capability.settimeout(prior_timeout)
451 descriptor = capability.fileno()
452 environment = os.environ.copy()
453 environment[GUARDIAN_FD_ENV] = str(descriptor)
454 return {
"env": environment,
"pass_fds": (descriptor,)}
459 data: dict[str, Any], *, installed_local: bool =
False, on_loss: object |
None =
None
461 """Expose one guardian capability while it owns the canonical flock."""
463 if _CAPABILITY_STATE.active
is not None:
464 message =
"nested mutation guardians are forbidden"
465 raise MutationLockError(message)
466 pid, capability, status_fd = _start_guardian(data, installed_local)
467 _CAPABILITY_STATE.active = capability
468 body_completed =
False
471 body_completed =
True
473 _CAPABILITY_STATE.active =
None
475 _, wait_status = os.waitpid(pid, 0)
476 ready = select.select([status_fd], [], [], 0)[0]
477 final = _read_status_line(status_fd, 0)
if ready
else b
""
479 failed =
not os.WIFEXITED(wait_status)
or os.WEXITSTATUS(wait_status) != 0
480 if body_completed
and failed:
481 detail = final.decode(
"utf-8", errors=
"replace").strip()
482 message = detail
or "mutation guardian lost authority"
483 raise MutationLockError(message)
486def _gated_argv(descriptor: int, argv: Sequence[str]) -> list[str]:
487 """Build an inert bootstrap that execs command code only after one token."""
488 return [sys.executable,
"-I",
"-c", GATED_EXEC, str(descriptor), *argv]
491def run_locked(data: dict[str, Any], argv: Sequence[str]) -> int:
492 """Run one complete process group under an independent guardian."""
493 child: subprocess.Popen[bytes] |
None =
None
496 def forward(process_signal: int, _frame: object) ->
None:
497 nonlocal pending_signal
498 pending_signal = process_signal
499 if child
is not None:
500 with suppress(ProcessLookupError):
501 os.killpg(child.pid, process_signal)
503 handled = (signal.SIGTERM, signal.SIGHUP, signal.SIGINT, signal.SIGQUIT)
504 previous = {item: signal.signal(item, forward)
for item
in handled}
506 with mutation_lock(data):
508 return 128 + pending_signal
509 gate_read, gate_write = os.pipe()
510 blocked = signal.pthread_sigmask(signal.SIG_BLOCK, handled)
512 kwargs = guardian_subprocess_kwargs()
513 pass_fds = (*kwargs[
"pass_fds"], gate_read)
514 child = subprocess.Popen(
515 _gated_argv(gate_read, argv),
516 start_new_session=
True,
522 require_guardian_capability_for_group(child.pid)
523 queued = set(signal.sigpending()).intersection(handled)
524 cancellation = pending_signal
or (
min(queued)
if queued
else 0)
526 with suppress(ProcessLookupError):
527 os.killpg(child.pid, cancellation)
529 os.write(gate_write, b
"1")
530 except BaseException:
531 if child
is not None:
532 with suppress(ProcessLookupError):
533 os.killpg(child.pid, signal.SIGKILL)
540 signal.pthread_sigmask(signal.SIG_SETMASK, blocked)
543 for process_signal, handler
in previous.items():
544 signal.signal(process_signal, handler)
547def require_guardian_capability_for_group(process_group: int) ->
None:
548 """Register a just-created, still-inert protected process group."""
549 capability = _capability_socket()
550 with _CAPABILITY_LOCK:
551 prior_timeout = capability.gettimeout()
553 capability.settimeout(GUARDIAN_REPLY_TIMEOUT)
554 capability.send(f
"REGISTER {process_group}".encode(
"ascii"))
555 if capability.recv(16) != b
"ACK":
556 message =
"mutation guardian refused child publication"
557 raise MutationLockError(message)
558 except (OSError, TimeoutError)
as error:
559 message =
"mutation guardian was lost before child publication"
560 raise MutationLockError(message)
from error
562 with suppress(OSError):
563 capability.settimeout(prior_timeout)
566def _boundary_contract_errors(infra_text: str) -> list[str]:
567 """Require every direct wrapper mutation to enter the guardian."""
569 'MUTATION_LOCK="${ROOT}/scripts/dev/fleet_mutation_lock.py"\n',
570 ' "$PYTHON" -I "$MUTATION_LOCK" -- "$PYTHON" -I "$FLEET" "$@"\n',
571 ' fleet_mutation apply "$@"\n',
572 ' fleet_mutation register-runner "$@"\n',
573 ' fleet_mutation register-hil "$@"\n',
574 ' fleet_mutation remove "$@"\n',
575 ' fleet_mutation scale "$1" "$2"\n',
579 if all(infra_text.count(item) == 1
for item
in required)
580 else [
"a supported direct mutation bypasses the independent guardian"]
584def _capability_selftest() -> list[str]:
585 """Prove an environment claim cannot manufacture mutation authority."""
586 failures: list[str] = []
587 prior = os.environ.get(GUARDIAN_FD_ENV)
588 os.environ[GUARDIAN_FD_ENV] =
"999999"
590 require_guardian_capability()
591 failures.append(
"an environment-only guardian claim was accepted")
592 except MutationLockError:
596 os.environ.pop(GUARDIAN_FD_ENV,
None)
598 os.environ[GUARDIAN_FD_ENV] = prior
602def _bench_reentry_selftest() -> list[str]:
603 """Prove validated authority remains inheritable by a nested mutation entry."""
604 parent, guardian = socket.socketpair(socket.AF_UNIX, socket.SOCK_SEQPACKET)
605 guardian_pid = os.fork()
606 if guardian_pid == 0:
608 for expected
in (b
"PING", b
"REGISTER"):
609 request = guardian.recv(128)
610 if not request.startswith(expected):
612 guardian.send(b
"ACK")
615 descriptor = parent.fileno()
616 previous = os.environ.get(GUARDIAN_FD_ENV)
617 os.environ[GUARDIAN_FD_ENV] = str(descriptor)
618 _CAPABILITY_STATE.active =
None
619 kwargs = guardian_subprocess_kwargs()
622 _CAPABILITY_STATE.active.detach()
623 _CAPABILITY_STATE.active =
None
625 os.environ.update(kwargs[
"env"])
627 require_guardian_capability()
629 _, nested_status = os.waitpid(nested, 0)
631 _CAPABILITY_STATE.active =
None
632 _, guardian_status = os.waitpid(guardian_pid, 0)
634 os.environ.pop(GUARDIAN_FD_ENV,
None)
636 os.environ[GUARDIAN_FD_ENV] = previous
637 inherited = kwargs[
"pass_fds"] == (descriptor,)
638 if not inherited
or nested_status != 0
or guardian_status != 0:
639 return [
"nested bench mutation did not inherit and register the live guardian"]
643def _metadata_selftest() -> list[str]:
644 """Prove remote setup and holder transport retain their fail-closed clauses."""
645 failures: list[str] = []
646 clauses = (
"set -e",
"[ ! -L",
"stat -c %u",
"stat -c %a",
"%d:%i",
"exit 75")
647 if any(clause
not in REMOTE_HOLDER
for clause
in clauses):
648 failures.append(
"remote holder setup metadata checks are incomplete")
649 ready_clause = f
'printf "{LOCK_READY.decode().rstrip()}\\n"'
650 if ready_clause
not in REMOTE_HOLDER:
651 failures.append(
"remote holder READY record is not newline-delimited")
652 fake = {
"hosts": {
"dev": {
"class":
"dev_box",
"connect": {
"address":
"127.0.0.1"}}}}
653 argv = _holder_argv(fake)
654 options = (
"ConnectTimeout=15",
"ServerAliveInterval=5",
"ServerAliveCountMax=3")
655 failures.extend(f
"holder transport omits {option}" for option
in options
if option
not in argv)
659def _two_controller_selftest(path: Path) -> list[str]:
660 """Prove one live kernel lock excludes a second controller."""
661 failures: list[str] = []
662 ready_read, ready_write = os.pipe()
663 release_read, release_write = os.pipe()
667 os.close(release_write)
669 with _exclusive(path):
670 os.write(ready_write, b
"1")
671 os.read(release_read, 1)
674 os.close(ready_write)
675 os.close(release_read)
677 if os.read(ready_read, 1) != b
"1":
678 failures.append(
"first controller did not acquire the mutation lock")
680 with _exclusive(path):
681 failures.append(
"second controller acquired the live mutation lock")
682 except BlockingIOError:
685 os.write(release_write, b
"1")
686 os.close(release_write)
689 with _exclusive(path):
694def _silent_transport_selftest() -> list[str]:
695 """Prove a connected transport that emits no token is bounded and reaped."""
696 process = subprocess.Popen([
"/bin/sleep",
"60"], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
698 _read_holder_ready(process, 0.05)
699 except MutationLockError:
700 _terminate_and_reap(process)
702 _terminate_and_reap(process)
703 return [
"silent pre-token holder transport did not time out"]
704 if process.poll()
is None:
705 return [
"timed-out holder transport was not reaped"]
709def _holder_loss_selftest() -> list[str]:
710 """Prove post-token holder death kills an already-published mutation group."""
711 parent_capability, guardian_capability = socket.socketpair(
712 socket.AF_UNIX, socket.SOCK_SEQPACKET
714 guardian_pid = os.fork()
715 if guardian_pid == 0:
716 parent_capability.close()
717 holder = subprocess.Popen([
"/bin/sleep",
"0.15"])
718 result = _guardian_loop(guardian_capability, holder)
719 _terminate_and_reap(holder)
721 guardian_capability.close()
722 child = subprocess.Popen([
"/bin/sleep",
"60"], start_new_session=
True)
723 parent_capability.send(f
"REGISTER {child.pid}".encode(
"ascii"))
724 acknowledged = parent_capability.recv(16) == b
"ACK"
726 child.wait(timeout=CHILD_WAIT_TIMEOUT)
727 except subprocess.TimeoutExpired:
728 os.killpg(child.pid, signal.SIGKILL)
730 failure =
"holder death did not terminate the published mutation group"
733 parent_capability.close()
734 os.waitpid(guardian_pid, 0)
735 failures = []
if acknowledged
else [
"guardian did not publish the mutation child"]
737 failures.append(failure)
741def _cancelled_spawn_selftest(path: Path) -> list[str]:
742 """Prove cancellation closes the execution gate before command code runs."""
743 gate_read, gate_write = os.pipe()
747 token = os.read(gate_read, 1)
750 os._exit(CANCELLED_STATUS)
755 _, wait_status = os.waitpid(child, 0)
756 status = os.waitstatus_to_exitcode(wait_status)
757 if status != CANCELLED_STATUS
or path.exists():
758 return [
"cancellation before publication allowed mutation code to execute"]
762def _hard_parent_death_selftest(path: Path) -> list[str]:
763 """Prove controller SIGKILL cannot release authority before its child group."""
764 ready_read, ready_write = os.pipe()
765 controller = os.fork()
768 lock_read, lock_write = os.pipe()
769 parent_capability, guardian_capability = socket.socketpair(
770 socket.AF_UNIX, socket.SOCK_SEQPACKET
774 parent_capability.close()
776 lock = _exclusive(path)
777 os.write(lock_write, b
"L")
779 result = _guardian_loop(guardian_capability, lock)
782 guardian_capability.close()
784 if os.read(lock_read, 1) != b
"L":
787 child = subprocess.Popen(
788 [
"/bin/sleep",
"0.4"],
789 start_new_session=
True,
790 pass_fds=(parent_capability.fileno(),),
792 parent_capability.send(f
"REGISTER {child.pid}".encode(
"ascii"))
793 if parent_capability.recv(16) != b
"ACK":
795 os.write(ready_write, b
"C")
798 os.close(ready_write)
799 if os.read(ready_read, 1) != b
"C":
800 os.kill(controller, signal.SIGKILL)
801 os.waitpid(controller, 0)
802 return [
"hard-parent-death fixture did not publish its child"]
804 os.kill(controller, signal.SIGKILL)
805 os.waitpid(controller, 0)
807 with _exclusive(path):
808 return [
"controller SIGKILL released the flock while its child survived"]
809 except BlockingIOError:
811 deadline = time.monotonic() + 2
812 while time.monotonic() < deadline:
814 with _exclusive(path):
816 except BlockingIOError:
818 return [
"guardian did not release after the complete child group exited"]
821def _isolated_import_selftest() -> list[str]:
822 """Prove the exact isolated interpreter entry can load local fleet modules."""
823 result = subprocess.run(
824 [
"/usr/bin/python3",
"-I", str(Path(__file__).resolve()),
"--selftest-import"],
829 if result.returncode == 0:
831 detail = result.stderr.strip()
or f
"exit {result.returncode}"
832 return [f
"isolated mutation-lock entry failed: {detail}"]
835def run_selftest() -> list[str]:
836 """Run deterministic boundary, metadata, capability, and exclusion proofs."""
837 root = Path(__file__).resolve().parents[2]
838 infra_text = (root /
"scripts/dev/infra.sh").read_text(encoding=
"ascii")
840 _isolated_import_selftest()
841 + _boundary_contract_errors(infra_text)
842 + _capability_selftest()
843 + _metadata_selftest()
844 + _silent_transport_selftest()
845 + _holder_loss_selftest()
846 + _bench_reentry_selftest()
847 + _cancelled_spawn_selftest(Path(tempfile.gettempdir()) / f
"ra8-cancel-{os.getpid()}")
849 with tempfile.TemporaryDirectory(prefix=
"ra8-fleet-mutation-lock-")
as raw:
850 directory = Path(raw)
851 directory.chmod(DIRECTORY_MODE)
852 failures += _two_controller_selftest(directory /
"mutation.lock")
853 failures += _hard_parent_death_selftest(directory /
"parent-death.lock")
857def parse_args(argv: Sequence[str] |
None =
None) -> argparse.Namespace:
858 """Parse the offline selftest or one protected command."""
859 parser = argparse.ArgumentParser(description=__doc__)
860 parser.add_argument(
"--selftest", action=
"store_true")
861 parser.add_argument(
"--selftest-import", action=
"store_true", help=argparse.SUPPRESS)
862 parser.add_argument(
"command", nargs=argparse.REMAINDER)
863 return parser.parse_args(argv)
866def main(argv: Sequence[str] |
None =
None) -> int:
867 """Enter the lock selftest or execute one serialized fleet mutation."""
868 args = parse_args(argv)
869 if args.selftest_import:
872 failures = run_selftest()
873 for failure
in failures:
874 print(f
"fleet_mutation_lock.py --selftest: FAIL: {failure}", file=sys.stderr)
876 print(
"fleet_mutation_lock.py --selftest: PASS")
877 return int(bool(failures))
878 command = list(args.command)
879 if command[:1] == [
"--"]:
880 command = command[1:]
882 print(
"fleet-mutation-lock: a command is required", file=sys.stderr)
885 return run_locked(fm.load(), command)
886 except MutationLockBusyError
as error:
887 print(f
"fleet-mutation-lock: {error}", file=sys.stderr)
888 return LOCK_BUSY_STATUS
889 except (MutationLockError, OSError, fm.FleetError)
as error:
890 print(f
"fleet-mutation-lock: FATAL: {error}", file=sys.stderr)
894if __name__ ==
"__main__":
void main(void)
The application entry point Reset_Handler hands control to.
#define min(x, y)
Untyped minimum shim used by the SOUP's buffer clamping.