Files
PaperClipAI/scripts/ci/pi-intel/qualification_observer.py

65 lines
2.8 KiB
Python

"""Normal qualification inspection budgets; no change to test or cleanup deadlines."""
import subprocess
import time
from owned_processes import process_table, command_tokens, ProcessInspectionUnavailable
class ActiveInspectionFailed(Exception):
"""Fatal observer failure, deliberately not a recoverable test RuntimeError."""
class QualificationInspection:
def __init__(self, owner, deadline, *, clock=time.monotonic):
self.owner = owner
self.deadline = deadline
self.clock = clock
def budget(self):
deadline = self.owner.stop_deadline
if deadline is None:
deadline = self.deadline
remaining = deadline - self.clock()
if remaining <= 0:
if self.owner.stop_deadline is None:
raise ActiveInspectionFailed('Qualification command observation deadline exhausted')
raise ProcessInspectionUnavailable('Qualification cleanup observation deadline exhausted')
return min(5, remaining)
def inspect(self, operation, *args, identity_recheck=False):
try:
return operation(*args, timeout=self.budget())
except (ProcessInspectionUnavailable, subprocess.SubprocessError, OSError) as error:
if self.owner.stop_deadline is None:
if (identity_recheck and isinstance(error, ProcessInspectionUnavailable)
and (error.__cause__ is None or isinstance(error.__cause__, (subprocess.CalledProcessError, ValueError)))):
# A completed ps snapshot can race natural exit/reuse.
# OwnedProcesses rechecks the exact observed identity;
# observe() below rejects any still-live unresolved role.
raise
# An actual inspection timeout/OS failure is fatal even if a
# later sample might find the process gone. Never retry it.
raise ActiveInspectionFailed('Qualification active inspection failed: ' + type(error).__name__) from error
raise
def table(self):
return self.inspect(process_table)
def argv(self, pid):
return self.inspect(command_tokens, pid, identity_recheck=True)
def observe(self, pid):
self.budget()
rows = self.owner.observe(pid)
if any(record['role'] == 'unresolved_inspection'
and rows.get(owned_pid, {}).get('started') == record['started']
for owned_pid, record in self.owner.owned.items()):
raise ActiveInspectionFailed('Qualification active process identity remains unresolved')
self.budget()
def wait_for_owned_command(child, inspection, *, sleep=time.sleep):
while child.poll() is None:
inspection.observe(child.pid)
sleep(min(.5, inspection.budget()))
inspection.observe(child.pid)