Files
truf-server/tests/test_supervisor_safety.py
T
2026-09-30 20:30:56 +03:00

2566 lines
114 KiB
Python

import json
import os
from pathlib import Path
import socket
import sqlite3
import subprocess
import sys
import tempfile
import threading
import time
from types import SimpleNamespace
import unittest
from unittest import mock
ROOT = Path(__file__).resolve().parents[1]
APP_DIR = ROOT / 'app'
sys.path.insert(0, str(APP_DIR))
import supervisor
import supervisor_instance
from scanner_db import ScannerDB
from process_identity import ProcessIdentity, ProcessIdentityError, current_process_identity
from paths import apply_path_config
from lifecycle_authority import (
PHASE_ACTIVE, PHASE_ACTIVATING, PHASE_FAILED_HOLD, PHASE_STOPPING,
)
from runtime_security import ClusterAuthorityLock, ensure_private_directory, harden_private_file
from supervisor_instance import (
InstanceMetadataError,
InstanceLockError,
SupervisorInstanceLock,
build_instance_metadata,
verify_instance_process,
write_instance_metadata,
)
class RecheckCommandParsingTests(unittest.TestCase):
def test_legacy_vertex_maps_to_authenticated_import_without_recheck_all(self):
parsed, error = supervisor.parse_recheck_command(['recheck', 'gcp', 'legacy-vertex'])
self.assertIsNone(error)
self.assertEqual(parsed['service'], 'gcp')
self.assertEqual(parsed['runner_args'], ['--import-legacy-gcp-vertex'])
class FakeOwnedProcess:
next_pid = 40000
def __init__(self, *args, **kwargs):
type(self).next_pid += 1
self.pid = type(self).next_pid
self.returncode = None
self.terminated = False
self.kwargs = kwargs
def poll(self):
return self.returncode
def terminate(self):
self.terminated = True
self.returncode = 0
def kill(self):
self.returncode = 1
def wait(self, timeout=None):
if self.returncode is None:
self.returncode = 0
return self.returncode
def exit_code(self):
return self.returncode
class UnkillableProcess(FakeOwnedProcess):
def terminate(self):
self.terminated = True
def kill(self):
pass
def wait(self, timeout=None):
raise subprocess.TimeoutExpired('unkillable', timeout)
def source_fixture(temp_dir, gate):
ensure_private_directory(temp_dir, reject_reparse=True)
config_path = os.path.join(temp_dir, 'config.yaml')
Path(config_path).write_text('{}\n', encoding='ascii')
harden_private_file(config_path)
ensure_private_directory(os.path.join(temp_dir, 'logs'), reject_reparse=True)
ensure_private_directory(os.path.join(temp_dir, 'state'), reject_reparse=True)
supervisor_config = {
'log_dir': os.path.join(temp_dir, 'logs'),
'state_dir': os.path.join(temp_dir, 'state'),
'defaults': {'enabled': True, 'restart': True},
}
return supervisor.ManagedSource(
'sample', config_path, str(APP_DIR), temp_dir, supervisor_config,
{'enabled': True}, dependency_gate=gate,
)
def docker_shadow_fixture(temp_dir, gate):
ensure_private_directory(temp_dir, reject_reparse=True)
config_path = os.path.join(temp_dir, 'config.yaml')
Path(config_path).write_text('{}\n', encoding='ascii')
harden_private_file(config_path)
ensure_private_directory(os.path.join(temp_dir, 'logs'), reject_reparse=True)
ensure_private_directory(os.path.join(temp_dir, 'state'), reject_reparse=True)
supervisor_config = {
'log_dir': os.path.join(temp_dir, 'logs'),
'state_dir': os.path.join(temp_dir, 'state'),
'defaults': {'enabled': True, 'restart': True, 'repeat': True},
}
return supervisor.ManagedDockerShadow(
config_path, str(APP_DIR), temp_dir, supervisor_config,
{'enabled': True}, dependency_gate=gate,
)
def prepare_supervisor_fixture(temp_dir, control_port=0):
ensure_private_directory(temp_dir, reject_reparse=True)
project_dir = os.path.join(temp_dir, 'project')
runtime_dir = os.path.join(temp_dir, 'runtime')
paths = {
'project_dir': project_dir,
'runtime_dir': runtime_dir,
'results_dir': os.path.join(runtime_dir, 'results'),
'result_spool_dir': os.path.join(runtime_dir, 'result_spool'),
'queue_dir': os.path.join(runtime_dir, 'queues'),
'state_dir': os.path.join(runtime_dir, 'state'),
'log_dir': os.path.join(runtime_dir, 'logs'),
'control_dir': os.path.join(runtime_dir, 'control'),
'keycheck_dir': os.path.join(runtime_dir, 'keychecks'),
'postman_cache_dir': os.path.join(runtime_dir, 'postman_cache'),
'gharchive_cache_dir': os.path.join(runtime_dir, 'state', 'gharchive_cache'),
'work_dir': os.path.join(temp_dir, 'work'),
'postgres_dir': os.path.join(runtime_dir, 'postgres'),
}
for path in paths.values():
ensure_private_directory(path, reject_reparse=True)
trufflehog_path = os.path.join(temp_dir, 'trufflehog.exe')
Path(trufflehog_path).write_bytes(b'fixture executable')
harden_private_file(trufflehog_path)
config_path = os.path.join(temp_dir, 'config.yaml')
value = {
'global': {
'root_dir': temp_dir,
'trufflehog_path': trufflehog_path,
**{key: value for key, value in paths.items() if key != 'postgres_dir'},
},
'supervisor': {
'interactive': False,
'autostart': False,
'control_port': control_port,
'background_start_timeout_sec': 10,
'background_activation_timeout_sec': 20,
'background_shutdown_timeout_sec': 15,
'dashboard': {'enabled': False},
'sources': {'fixture': {'enabled': True}},
},
'keychecks': {'enabled': False},
'sources': {'fixture': {'enabled': True}},
}
Path(config_path).write_text(json.dumps(value), encoding='ascii')
harden_private_file(config_path)
supervisor_log = os.path.join(paths['log_dir'], 'supervisor.log')
Path(supervisor_log).write_text('', encoding='ascii')
harden_private_file(supervisor_log)
return config_path, paths
def private_background_config(temp_dir, **values):
ensure_private_directory(temp_dir, reject_reparse=True)
control_dir = os.path.join(temp_dir, 'control')
ensure_private_directory(control_dir, reject_reparse=True)
return {'log_dir': temp_dir, 'control_dir': control_dir, **values}
class DesiredStateGateTests(unittest.TestCase):
def test_docker_shadow_is_manual_singleton_without_relaunch(self):
with tempfile.TemporaryDirectory() as temp_dir:
shadow = docker_shadow_fixture(temp_dir, supervisor.DependencyGate(ready=True))
command = shadow.build_command()
self.assertTrue(shadow.manual_only)
self.assertTrue(shadow.once)
self.assertFalse(shadow.repeat)
self.assertFalse(shadow.restart)
self.assertFalse(shadow.use_per_source_state)
self.assertEqual(shadow.mode_label(), 'manual-once')
self.assertEqual(command[5], 'docker-shadow')
self.assertEqual(command[-2:], ['--config', shadow.config_path])
self.assertEqual(supervisor.autostart_sources([shadow]), [])
shadow.desired_state = 'running'
shadow.manual_stop = False
shadow.process = FakeOwnedProcess()
shadow.process.returncode = 0
shadow.started_at = time.time()
shadow.poll()
self.assertEqual(shadow.status, 'done')
self.assertEqual(shadow.desired_state, 'stopped')
self.assertEqual(shadow.restarts, 0)
self.assertEqual(shadow.next_start_at, 0)
def test_docker_shadow_does_not_retain_intent_across_dependency_loss(self):
with tempfile.TemporaryDirectory() as temp_dir:
gate = supervisor.DependencyGate(ready=False)
shadow = docker_shadow_fixture(temp_dir, gate)
self.assertFalse(shadow.start(force=True))
self.assertEqual(shadow.desired_state, 'stopped')
self.assertFalse(shadow.runtime_blocked)
self.assertIn('unavailable', shadow.last_action_error)
def test_unresolved_handoff_exit_enters_hold_without_restart(self):
with tempfile.TemporaryDirectory() as temp_dir:
source = source_fixture(temp_dir, supervisor.DependencyGate(ready=True))
source.restart = True
source.desired_state = 'running'
source.manual_stop = False
source.process = FakeOwnedProcess()
source.process.returncode = supervisor.SOURCE_INFRASTRUCTURE_HOLD_EXIT
source.started_at = time.time()
source.poll()
self.assertEqual(source.status, 'failed')
self.assertEqual(source.desired_state, 'stopped')
self.assertTrue(source.manual_stop)
self.assertEqual(source.restarts, 0)
self.assertEqual(source.next_start_at, 0)
def test_failed_nonrepeat_batch_with_restart_disabled_stays_failed(self):
with tempfile.TemporaryDirectory() as temp_dir:
source = source_fixture(temp_dir, supervisor.DependencyGate(ready=True))
source.once = True
source.repeat = False
source.restart = False
source.desired_state = 'running'
source.manual_stop = False
source.process = FakeOwnedProcess()
source.process.returncode = 1
source.started_at = time.time()
source.poll()
self.assertEqual(source.status, 'failed')
self.assertEqual(source.desired_state, 'stopped')
self.assertEqual(source.restarts, 0)
self.assertEqual(source.next_start_at, 0)
def test_failed_periodic_keychecks_wait_for_each_interval(self):
clock = {'now': 1000.0}
with tempfile.TemporaryDirectory() as temp_dir, \
mock.patch.object(supervisor.time, 'time', side_effect=lambda: clock['now']), \
mock.patch.object(supervisor, 'open_process', side_effect=OSError('fixture process')), \
mock.patch.object(supervisor, 'OwnedProcess', side_effect=FakeOwnedProcess) as owned:
ensure_private_directory(temp_dir, reject_reparse=True)
config_path = os.path.join(temp_dir, 'config.yaml')
Path(config_path).write_text('{}\n', encoding='ascii')
harden_private_file(config_path)
ensure_private_directory(os.path.join(temp_dir, 'logs'), reject_reparse=True)
ensure_private_directory(os.path.join(temp_dir, 'state'), reject_reparse=True)
source = supervisor.ManagedKeychecks(
config_path,
str(APP_DIR),
temp_dir,
{'log_dir': os.path.join(temp_dir, 'logs'), 'state_dir': os.path.join(temp_dir, 'state')},
{
'enabled': True,
'repeat': True,
'restart': False,
'interval': 60,
'summary_tsv': os.path.join(temp_dir, 'summary.tsv'),
},
dependency_gate=supervisor.DependencyGate(ready=True),
)
source.desired_state = 'running'
source.manual_stop = False
source.process = FakeOwnedProcess()
source.process.returncode = 1
source.started_at = 900.0
source.poll()
self.assertEqual(source.status, 'waiting')
self.assertEqual(source.desired_state, 'running')
self.assertFalse(source.manual_stop)
self.assertEqual(source.last_exit_code, 1)
self.assertEqual(source.next_start_at, 1060.0)
self.assertEqual(source.restarts, 1)
self.assertEqual(source.restart_streak, 0)
self.assertEqual(source.row()[5], '1')
self.assertNotEqual(source.row()[6], '-')
self.assertEqual(source.row()[11], 'running')
scheduled = source.next_start_at
source.poll()
source.poll()
self.assertEqual(source.next_start_at, scheduled)
owned.assert_not_called()
clock['now'] = 1059.0
source.poll()
owned.assert_not_called()
clock['now'] = 1060.0
source.poll()
self.assertEqual(owned.call_count, 1)
self.assertEqual(source.status, 'running')
self.assertEqual(source.next_start_at, 0)
source.process.returncode = 1
clock['now'] = 1061.0
source.poll()
self.assertEqual(source.status, 'waiting')
self.assertEqual(source.last_exit_code, 1)
self.assertEqual(source.next_start_at, 1121.0)
self.assertEqual(source.restarts, 2)
self.assertEqual(source.restart_streak, 0)
source.poll()
self.assertEqual(owned.call_count, 1)
source.stop(final=True)
def test_manual_and_coordinated_stop_cancel_pending_periodic_launch(self):
for final in (False, True):
with self.subTest(final=final), tempfile.TemporaryDirectory() as temp_dir, \
mock.patch.object(supervisor.time, 'time', return_value=1000.0), \
mock.patch.object(supervisor, 'OwnedProcess', side_effect=FakeOwnedProcess) as owned:
source = source_fixture(temp_dir, supervisor.DependencyGate(ready=True))
source.once = True
source.repeat = True
source.restart = False
source.interval = 60
source.desired_state = 'running'
source.manual_stop = False
source.process = FakeOwnedProcess()
source.process.returncode = 1
source.started_at = 900.0
source.poll()
self.assertEqual(source.next_start_at, 1060.0)
self.assertTrue(source.stop(final=final))
self.assertEqual(source.status, 'stopped')
self.assertEqual(source.desired_state, 'stopped')
self.assertEqual(source.next_start_at, 0)
source.poll()
owned.assert_not_called()
def test_successful_repeat_batch_schedules_configured_interval(self):
with tempfile.TemporaryDirectory() as temp_dir:
source = source_fixture(temp_dir, supervisor.DependencyGate(ready=True))
source.once = True
source.repeat = True
source.restart = False
source.interval = 3600
source.desired_state = 'running'
source.manual_stop = False
source.process = FakeOwnedProcess()
source.process.returncode = 0
source.started_at = time.time()
before = time.time()
source.poll()
self.assertEqual(source.status, 'waiting')
self.assertEqual(source.desired_state, 'running')
self.assertEqual(source.restarts, 1)
self.assertGreaterEqual(source.next_start_at, before + source.interval)
def test_gate_loss_preserves_finished_failed_repeat_interval(self):
with tempfile.TemporaryDirectory() as temp_dir, mock.patch.object(
supervisor, 'OwnedProcess', side_effect=FakeOwnedProcess,
) as owned:
gate = supervisor.DependencyGate(ready=True)
source = source_fixture(temp_dir, gate)
source.once = True
source.repeat = True
source.restart = False
source.interval = 3600
source.desired_state = 'running'
source.manual_stop = False
source.process = FakeOwnedProcess()
source.process.returncode = 1
source.started_at = time.time()
before = time.time()
gate.set_ready(False)
scheduled = source.next_start_at
gate.set_ready(True)
self.assertEqual(source.last_exit_code, 1)
self.assertGreaterEqual(scheduled, before + source.interval)
self.assertEqual(source.next_start_at, scheduled)
self.assertEqual(source.status, 'waiting')
self.assertEqual(source.desired_state, 'running')
owned.assert_not_called()
def test_gate_loss_preserves_finished_successful_repeat_interval(self):
with tempfile.TemporaryDirectory() as temp_dir, mock.patch.object(
supervisor, 'OwnedProcess', side_effect=FakeOwnedProcess,
) as owned:
gate = supervisor.DependencyGate(ready=True)
source = source_fixture(temp_dir, gate)
source.once = True
source.repeat = True
source.restart = False
source.interval = 3600
source.desired_state = 'running'
source.manual_stop = False
source.process = FakeOwnedProcess()
source.process.returncode = 0
source.started_at = time.time()
before = time.time()
gate.set_ready(False)
scheduled = source.next_start_at
gate.set_ready(True)
self.assertGreaterEqual(scheduled, before + source.interval)
self.assertEqual(source.next_start_at, scheduled)
self.assertEqual(source.status, 'waiting')
self.assertEqual(source.desired_state, 'running')
owned.assert_not_called()
def test_supervisor_text_output_writer_rotates_with_bounded_retention(self):
with tempfile.TemporaryDirectory() as temp_dir:
ensure_private_directory(temp_dir, reject_reparse=True)
path = os.path.join(temp_dir, 'supervisor.log')
writer = supervisor.BoundedRotatingTextWriter(path, 64, 2)
try:
writer.write('x' * 400)
writer.flush()
finally:
writer.close()
logs = [candidate for candidate in Path(temp_dir).glob('supervisor*.log')]
self.assertLessEqual(len(logs), 3)
self.assertTrue(logs)
self.assertTrue(all(candidate.stat().st_size <= 64 for candidate in logs))
def test_live_source_output_rotates_with_bounded_segments_and_tail_reads(self):
with tempfile.TemporaryDirectory() as temp_dir:
source = source_fixture(temp_dir, supervisor.DependencyGate(ready=True))
source.log_max_mb = 1
source.log_keep = 2
source.once = True
source.repeat = False
source.restart = False
source.build_command = lambda: [
sys.executable,
'-c',
'import sys; sys.stdout.buffer.write(b"x" * (3 * 1024 * 1024 + 123)); sys.stdout.buffer.flush()',
]
self.assertTrue(source.start(force=True))
deadline = time.monotonic() + 20
while time.monotonic() < deadline and source.status == 'running':
time.sleep(0.02)
source.poll()
self.assertEqual(source.status, 'done', source.last_action_error)
paths = [Path(source.log_path), *Path(source.log_path).parent.glob('sample.*.log')]
paths = list(dict.fromkeys(path for path in paths if path.exists()))
self.assertLessEqual(len(paths), source.log_keep + 1)
self.assertTrue(paths)
self.assertTrue(all(path.stat().st_size <= 1024 * 1024 for path in paths))
tail = source.tail_log_lines(5000)
self.assertLessEqual(sum(len(line.encode('utf-8')) for line in tail), supervisor.MAX_LOG_TAIL_BYTES)
def test_owned_process_start_failure_closes_log_and_returns_false(self):
with tempfile.TemporaryDirectory() as temp_dir, mock.patch.object(
supervisor,
'OwnedProcess',
side_effect=RuntimeError('host setup failed'),
):
source = source_fixture(temp_dir, supervisor.DependencyGate(ready=True))
self.assertFalse(source.start(force=True))
self.assertEqual(source.status, 'failed')
self.assertIsNone(source.process)
self.assertIsNone(source.log_handle)
self.assertIn('host setup failed', source.last_action_error)
def test_running_intent_waits_for_ready_and_stop_pause_are_preserved(self):
with tempfile.TemporaryDirectory() as temp_dir, mock.patch.object(supervisor, 'OwnedProcess', side_effect=FakeOwnedProcess) as owned:
gate = supervisor.DependencyGate(ready=False)
source = source_fixture(temp_dir, gate)
self.assertFalse(source.start(force=True))
self.assertEqual(source.desired_state, 'running')
self.assertEqual(source.status, 'blocked')
owned.assert_not_called()
source.stop()
self.assertEqual(source.desired_state, 'stopped')
gate.set_ready(True)
owned.assert_not_called()
gate.set_ready(False)
source.resume()
source.pause()
self.assertEqual(source.desired_state, 'paused')
gate.set_ready(True)
owned.assert_not_called()
def test_readiness_loss_stops_owned_child_without_changing_running_intent(self):
with tempfile.TemporaryDirectory() as temp_dir, mock.patch.object(supervisor, 'OwnedProcess', side_effect=FakeOwnedProcess) as owned:
gate = supervisor.DependencyGate(ready=True)
source = source_fixture(temp_dir, gate)
self.assertTrue(source.start(force=True))
first = source.process
gate.set_ready(False)
self.assertTrue(first.terminated)
self.assertEqual(source.desired_state, 'running')
self.assertEqual(source.status, 'blocked')
gate.set_ready(True)
self.assertEqual(owned.call_count, 2)
self.assertEqual(source.desired_state, 'running')
self.assertEqual(source.status, 'running')
source.stop(final=True)
def test_keycheck_recheck_remains_queued_while_blocked(self):
with tempfile.TemporaryDirectory() as temp_dir, mock.patch.object(supervisor, 'OwnedProcess', side_effect=FakeOwnedProcess) as owned:
ensure_private_directory(temp_dir, reject_reparse=True)
config_path = os.path.join(temp_dir, 'config.yaml')
Path(config_path).write_text('{}\n', encoding='ascii')
harden_private_file(config_path)
ensure_private_directory(os.path.join(temp_dir, 'logs'), reject_reparse=True)
ensure_private_directory(os.path.join(temp_dir, 'state'), reject_reparse=True)
gate = supervisor.DependencyGate(ready=False)
item = supervisor.ManagedKeychecks(
config_path,
str(APP_DIR),
temp_dir,
{'log_dir': os.path.join(temp_dir, 'logs'), 'state_dir': os.path.join(temp_dir, 'state')},
{'enabled': True, 'repeat': True, 'summary_tsv': os.path.join(temp_dir, 'summary.tsv')},
dependency_gate=gate,
)
ok, message = item.start_recheck('all', ['--retry-network'])
self.assertTrue(ok)
self.assertIn('blocked', message)
item.poll()
self.assertIsNotNone(item.command_override)
owned.assert_not_called()
gate.set_ready(True)
self.assertEqual(owned.call_count, 1)
item.process.returncode = 0
item.poll()
self.assertIsNone(item.command_override)
self.assertEqual(item.desired_state, 'running')
self.assertEqual(item.status, 'waiting')
item.stop(final=True)
def test_source_and_keycheck_env_overrides_cannot_replace_managed_dsn(self):
canonical = 'postgresql://truf:secret@127.0.0.1:5432/truf'
with tempfile.TemporaryDirectory() as temp_dir:
gate = supervisor.DependencyGate(ready=True, database_url=canonical)
source = source_fixture(temp_dir, gate)
source.env_overrides = {
'SCANNER_DB_URL': 'postgresql://attacker/other',
'DATABASE_URL': 'postgresql://attacker/other',
'TRUF_MANAGED_POSTGRES_DSN': 'postgresql://attacker/other',
'PGSERVICE': 'attacker',
'PGHOSTADDR': '203.0.113.10',
}
source_env = source.build_env()
self.assertEqual(source_env['SCANNER_DB_URL'], canonical)
self.assertEqual(source_env['DATABASE_URL'], canonical)
self.assertEqual(source_env['TRUF_MANAGED_POSTGRES_DSN'], canonical)
self.assertNotIn('PGSERVICE', source_env)
self.assertNotIn('PGHOSTADDR', source_env)
item = supervisor.ManagedKeychecks(
os.path.join(temp_dir, 'config.yaml'),
str(APP_DIR),
temp_dir,
{'log_dir': os.path.join(temp_dir, 'logs'), 'state_dir': os.path.join(temp_dir, 'state')},
{'enabled': True, 'env': {'KEYCHECK_DB_URL': 'postgresql://attacker/other'}},
dependency_gate=gate,
)
self.assertEqual(item.build_env()['KEYCHECK_DB_URL'], canonical)
class ManagedConfigurationAuthorityTests(unittest.TestCase):
def test_managed_dsn_overrides_config_database_urls(self):
canonical = 'postgresql://truf:secret@127.0.0.1:5432/truf'
with tempfile.TemporaryDirectory() as temp_dir, mock.patch.dict(
os.environ,
{'TRUF_MANAGED_POSTGRES_DSN': canonical},
clear=False,
):
config_path = os.path.join(temp_dir, 'config.yaml')
Path(config_path).write_text('{}\n', encoding='ascii')
config = apply_path_config({
'global': {
'root_dir': temp_dir,
'project_dir': temp_dir,
'database_url': 'postgresql://attacker/other',
'dashboard_db_url': 'postgresql://attacker/dashboard',
},
}, config_path)
self.assertEqual(config['global']['database_url'], canonical)
self.assertEqual(config['global']['dashboard_db_url'], canonical)
self.assertEqual(
os.path.dirname(config['supervisor']['instance_file']),
config['global']['control_dir'],
)
self.assertEqual(
os.path.dirname(config['supervisor']['lock_file']),
config['global']['control_dir'],
)
class SourceRunFinalizationTests(unittest.TestCase):
def test_finalize_source_runs_closes_cycles_and_runs_transactionally(self):
connection = mock.Mock()
connection.execute.side_effect = [
SimpleNamespace(rowcount=2),
SimpleNamespace(rowcount=1),
]
with mock.patch.object(supervisor, 'connect_postgres', return_value=connection):
counts = supervisor.finalize_source_runs(
'postgresql://truf:fixture@127.0.0.1:5432/truf',
'github',
'stopped',
'controlled stop',
)
self.assertEqual(counts, (2, 1))
connection.commit.assert_called_once_with()
connection.rollback.assert_not_called()
connection.close.assert_called_once_with()
def test_source_start_does_not_run_database_finalization_before_child_handshake(self):
with tempfile.TemporaryDirectory() as temp_dir:
source = source_fixture(temp_dir, supervisor.DependencyGate(ready=True))
source.desired_state = 'running'
source.manual_stop = False
with mock.patch.object(source, 'finalize_database_runs', side_effect=AssertionError('control blocked')) as finalize, \
mock.patch.object(supervisor, 'OwnedProcess', side_effect=FakeOwnedProcess):
self.assertTrue(source.start())
finalize.assert_not_called()
self.assertEqual(source.status, 'running')
def test_scanner_child_finalizes_stale_rows_before_inserting_its_new_run(self):
with tempfile.TemporaryDirectory() as temp_dir:
database = os.path.join(temp_dir, 'scanner.db')
db = ScannerDB(db_path=database, db_url='', initialize=True)
try:
old_run = db.start_run('config', selected_source='github')
old_cycle = db.start_source_cycle(old_run, 'github', 'github', 'recent', 'fixture')
new_run = db.start_run('config', selected_source='github')
old_run_row = db.conn.execute('SELECT status FROM runs WHERE id = ?', (old_run,)).fetchone()
old_cycle_row = db.conn.execute('SELECT status FROM source_cycles WHERE id = ?', (old_cycle,)).fetchone()
new_run_row = db.conn.execute('SELECT status FROM runs WHERE id = ?', (new_run,)).fetchone()
self.assertEqual(old_run_row['status'], 'interrupted')
self.assertEqual(old_cycle_row['status'], 'interrupted')
self.assertEqual(new_run_row['status'], 'running')
indexes = db.conn.table_indexes('runs')
self.assertEqual(
indexes['idx_runs_selected_source_status']['columns'],
['selected_source', 'status', 'id'],
)
finally:
db.close()
class AuthenticatedControlTests(unittest.TestCase):
@staticmethod
def structured_source_state(source='sample', role='scanner', **overrides):
value = {
'id': source,
'source': source,
'role': role,
'lifecycle_state': 'running',
'desired_state': 'running',
'process_state': 'running',
'pid': 1234,
'enabled': True,
'dependency_blocked': False,
'startup_cleanup_pending': False,
'mode': 'loop',
'interval_seconds': 60,
'restart_enabled': True,
'restart_delay_seconds': 5,
'restart_count': 0,
'restart_streak': 0,
'last_exit_code': None,
'last_exit_at': None,
'next_scheduled_run_at': None,
'safe_error_category': '',
'auth_summary': {},
'allowed_actions': ['start', 'stop'],
}
value.update(overrides)
return value
def setUp(self):
self.shutdown_event = threading.Event()
self.server = supervisor.SupervisorControlServer(
('127.0.0.1', 0),
[],
{'shutdown_event': self.shutdown_event, 'with_postgres': True},
threading.RLock(),
'instance-test',
't' * 48,
)
self.thread = threading.Thread(target=self.server.serve_forever, daemon=True)
self.thread.start()
self.metadata = {
'instance_id': 'instance-test',
'token': 't' * 48,
'control': {'host': '127.0.0.1', 'port': self.server.server_address[1]},
}
def tearDown(self):
self.server.shutdown()
self.server.server_close()
def test_requests_require_instance_and_token_and_shutdown_returns_promptly(self):
self.assertFalse(supervisor.SupervisorControlServer.allow_reuse_address)
wrong = dict(self.metadata)
wrong['token'] = 'x' * 48
with self.assertRaises(RuntimeError):
supervisor.send_control_request(wrong, 'handshake')
handshake = supervisor.send_control_request(self.metadata, 'handshake')
self.assertEqual(handshake['instance_id'], 'instance-test')
result = supervisor.send_control_request(self.metadata, 'shutdown', with_postgres=True)
self.assertIn('shutdown requested', result)
self.assertTrue(self.shutdown_event.is_set())
def test_control_request_timeout_is_one_total_deadline(self):
sock = mock.MagicMock()
sock.__enter__.return_value = sock
sock.recv.side_effect = [b'{', b'']
with mock.patch.object(
supervisor.socket, 'create_connection', return_value=sock,
) as connect, mock.patch.object(
supervisor.time, 'monotonic', side_effect=[0, 0, 1, 2, 6],
):
with self.assertRaisesRegex(TimeoutError, '^control request timed out$'):
supervisor.send_control_request(self.metadata, 'handshake', timeout=5)
connect.assert_called_once_with(
('127.0.0.1', self.metadata['control']['port']), timeout=5,
)
self.assertEqual(
[call.args[0] for call in sock.settimeout.call_args_list], [4, 3],
)
self.assertEqual(sock.recv.call_count, 1)
def test_structured_runtime_snapshot_covers_all_managed_process_state(self):
source = mock.Mock()
source.structured_state.return_value = self.structured_source_state(
'result-ingester', 'result-ingester',
mode='singleton', interval_seconds=0,
allowed_actions=['start', 'stop', 'restart', 'pause', 'resume'],
)
controller = mock.Mock()
controller.snapshot.return_value = {
'state': 'READY', 'ready': True, 'failures': 0,
'detail': 'postgres-secret-must-not-leak',
}
dashboard = mock.Mock()
dashboard.snapshot.return_value = {
'status': 'healthy', 'desired': 'running', 'healthy': True,
'pid': 4321, 'failures': 0,
'detail': 'dashboard-secret-must-not-leak',
'url': 'https://dashboard-secret-must-not-leak',
}
self.server.managed_sources[:] = [source]
self.server.context.update({
'lifecycle_phase': PHASE_ACTIVE,
'activation_state': PHASE_ACTIVE,
'start_gate_open': True,
'postgres_controller': controller,
'dashboard_manager': dashboard,
})
snapshot = supervisor.get_runtime_snapshot(self.metadata)
self.assertEqual(snapshot['snapshot_schema'], supervisor.RUNTIME_SNAPSHOT_SCHEMA)
self.assertEqual(snapshot['runtime']['phase'], PHASE_ACTIVE)
self.assertEqual(snapshot['sources'], [source.structured_state.return_value])
self.assertEqual(snapshot['postgres']['state'], 'READY')
self.assertEqual(snapshot['dashboard']['status'], 'healthy')
serialized = json.dumps(snapshot, sort_keys=True)
self.assertNotIn('must-not-leak', serialized)
for phase in (PHASE_STOPPING, PHASE_FAILED_HOLD):
with self.subTest(phase=phase):
self.server.context['lifecycle_phase'] = phase
self.server.context['activation_state'] = phase
held = supervisor.get_runtime_snapshot(self.metadata)
self.assertEqual(held['runtime']['phase'], phase)
self.server.context['lifecycle_phase'] = PHASE_ACTIVE
self.server.context['activation_state'] = PHASE_ACTIVE
with self.assertRaisesRegex(RuntimeError, 'fields are invalid'):
supervisor.send_control_request(
self.metadata, 'runtime-snapshot', command='status',
)
source.structured_state.side_effect = RuntimeError('snapshot-secret-must-not-leak')
with self.assertRaisesRegex(RuntimeError, '^control request failed$') as raised:
supervisor.get_runtime_snapshot(self.metadata)
self.assertNotIn('must-not-leak', str(raised.exception))
def test_typed_actions_cover_managed_source_and_dashboard_functionality(self):
source = mock.Mock()
source.source = 'sample'
source.manual_only = False
source.runtime_blocked = False
source.desired_state = 'running'
source.is_running.return_value = False
source.start.return_value = True
source.stop.return_value = True
source.restart_now.return_value = True
source.pause.return_value = True
source.resume.return_value = True
source.structured_state.return_value = self.structured_source_state(
allowed_actions=list(
supervisor.MANAGED_SOURCE_LIFECYCLE_ACTIONS
+ supervisor.MANAGED_SOURCE_SETTING_ACTIONS
),
)
self.server.managed_sources[:] = [source]
self.server.context.update({
'lifecycle_phase': PHASE_ACTIVE,
'activation_state': PHASE_ACTIVE,
'start_gate_open': True,
})
with mock.patch.object(supervisor, 'run_control_command') as generic:
for action, method in (
('start', source.start), ('stop', source.stop),
('restart', source.restart_now), ('pause', source.pause),
('resume', source.resume),
):
with self.subTest(action=action):
result = supervisor.send_managed_source_action(
self.metadata, 'sample', action,
)
self.assertEqual(result['source_action'], action)
self.assertEqual(result['outcome'], 'completed')
method.assert_called()
source.is_running.return_value = True
supervisor.send_managed_source_action(self.metadata, 'sample', 'once')
source.stop.assert_called_with(timeout=10)
source.start.assert_called_with(force=True)
self.assertTrue(source.once)
self.assertFalse(source.repeat)
source.is_running.return_value = False
supervisor.send_managed_source_action(
self.metadata, 'sample', 'set-mode', mode='repeat',
)
self.assertTrue(source.once)
self.assertTrue(source.repeat)
supervisor.send_managed_source_action(
self.metadata, 'sample', 'set-interval', interval_seconds=90,
)
self.assertEqual(source.interval, 90)
with self.assertRaisesRegex(RuntimeError, 'interval is invalid'):
supervisor.send_managed_source_action(
self.metadata, 'sample', 'set-interval',
interval_seconds=supervisor.MAX_MANAGED_SOURCE_DELAY_SECONDS + 1,
)
with self.assertRaisesRegex(RuntimeError, 'interval is invalid'):
supervisor.send_managed_source_action(
self.metadata, 'sample', 'set-interval', interval_seconds=0,
)
supervisor.send_managed_source_action(
self.metadata, 'sample', 'set-restart', restart_enabled=False,
)
self.assertFalse(source.restart)
supervisor.send_managed_source_action(
self.metadata, 'sample', 'set-restart-delay', restart_delay_seconds=12,
)
self.assertEqual(source.restart_delay, 12)
with self.assertRaisesRegex(RuntimeError, 'restart delay is invalid'):
supervisor.send_managed_source_action(
self.metadata, 'sample', 'set-restart-delay',
restart_delay_seconds=supervisor.MAX_MANAGED_SOURCE_DELAY_SECONDS + 1,
)
with self.assertRaisesRegex(RuntimeError, 'restart delay is invalid'):
supervisor.send_managed_source_action(
self.metadata, 'sample', 'set-restart-delay', restart_delay_seconds=0,
)
self.server.context['status_file'] = 'status-secret-must-not-leak'
with mock.patch.object(
supervisor, 'write_status_file', side_effect=OSError('path-secret-must-not-leak'),
) as write_status:
result = supervisor.send_managed_source_action(
self.metadata, 'sample', 'start',
)
self.assertEqual(result['outcome'], 'completed')
write_status.assert_called_once_with(
self.server.context['status_file'], [source], self.server.context,
)
self.server.context.pop('status_file')
generic.assert_not_called()
dashboard = mock.Mock()
dashboard.start.return_value = True
dashboard.stop.return_value = True
dashboard.snapshot.return_value = {
'status': 'healthy', 'desired': 'running', 'healthy': True,
'pid': 1234, 'failures': 0, 'detail': '', 'url': '',
}
self.server.context['dashboard_manager'] = dashboard
with mock.patch.object(supervisor, 'run_control_command') as generic:
for action in ('start', 'stop', 'restart'):
with self.subTest(dashboard_action=action):
result = supervisor.send_dashboard_action(self.metadata, action)
self.assertEqual(result['dashboard_action'], action)
self.assertEqual(result['outcome'], 'completed')
generic.assert_not_called()
def test_typed_log_tail_is_exact_allowlisted_and_bounded(self):
source = mock.Mock()
source.source = 'sample'
source.tail_log_lines.return_value = ['first', 'second']
self.server.managed_sources[:] = [source]
self.server.context.update({
'lifecycle_phase': PHASE_ACTIVE,
'activation_state': PHASE_ACTIVE,
'start_gate_open': True,
})
with mock.patch.object(supervisor, 'run_control_command') as generic:
result = supervisor.send_managed_source_log_tail(
self.metadata, 'sample', 2,
)
self.assertEqual(result, {
'source_id': 'sample',
'line_count': 2,
'lines': ['first', 'second'],
'response_truncated': False,
})
source.tail_log_lines.assert_called_once_with(2)
generic.assert_not_called()
for source_id in ('all', 'docker', '../sample', 'sample; status'):
with self.subTest(source_id=source_id), self.assertRaisesRegex(
RuntimeError, 'managed source ID is unknown',
):
supervisor.send_managed_source_log_tail(
self.metadata, source_id, 2,
)
for line_count in (0, supervisor.MAX_LOG_TAIL_LINES + 1, True, '2'):
with self.subTest(line_count=line_count), self.assertRaisesRegex(
RuntimeError, 'line count is invalid',
):
supervisor.send_managed_source_log_tail(
self.metadata, 'sample', line_count,
)
with self.assertRaisesRegex(RuntimeError, 'fields are invalid'):
supervisor.send_control_request(
self.metadata,
'managed-source-log-tail',
source_id='sample',
line_count=2,
path='log-secret-must-not-be-accepted',
)
generic.assert_not_called()
source.tail_log_lines.return_value = ['a' * 20, 'b' * 20]
with mock.patch.object(supervisor, 'MAX_LOG_TAIL_BYTES', 32):
bounded = supervisor.send_managed_source_log_tail(
self.metadata, 'sample', 2,
)
self.assertEqual(bounded['lines'], ['b' * 20])
self.assertTrue(bounded['response_truncated'])
self.assertLessEqual(
len(json.dumps(bounded['lines'], ensure_ascii=True).encode('utf-8')),
32,
)
escaped_lines = ['emoji:\U0001f600\n', 'control:\u0000\t']
source.tail_log_lines.side_effect = None
source.tail_log_lines.return_value = escaped_lines
escaped_limit = len(json.dumps(
[escaped_lines[-1]], ensure_ascii=True, separators=(',', ':'),
).encode('utf-8'))
with mock.patch.object(supervisor, 'MAX_LOG_TAIL_BYTES', escaped_limit):
escaped = supervisor.send_managed_source_log_tail(
self.metadata, 'sample', 2,
)
self.assertEqual(escaped['lines'], [escaped_lines[-1]])
self.assertTrue(escaped['response_truncated'])
self.assertLessEqual(len(json.dumps(
escaped['lines'], ensure_ascii=True, separators=(',', ':'),
).encode('utf-8')), escaped_limit)
compact_lines = ['x', 'y']
source.tail_log_lines.return_value = compact_lines
compact_limit = len(json.dumps(
compact_lines, ensure_ascii=True, separators=(',', ':'),
).encode('utf-8'))
self.assertGreater(len(json.dumps(
compact_lines, ensure_ascii=True,
).encode('utf-8')), compact_limit)
with mock.patch.object(supervisor, 'MAX_LOG_TAIL_BYTES', compact_limit):
compact = supervisor.send_managed_source_log_tail(
self.metadata, 'sample', 2,
)
self.assertEqual(compact['lines'], compact_lines)
self.assertFalse(compact['response_truncated'])
source.tail_log_lines.return_value = ['a' * 20, 'b' * 20]
for phase in (PHASE_STOPPING, PHASE_FAILED_HOLD):
with self.subTest(phase=phase):
self.server.context['lifecycle_phase'] = phase
self.server.context['activation_state'] = phase
held = supervisor.send_managed_source_log_tail(
self.metadata, 'sample', 1,
)
self.assertEqual(held['lines'], ['b' * 20])
self.server.context['lifecycle_phase'] = PHASE_ACTIVE
self.server.context['activation_state'] = PHASE_ACTIVE
source.tail_log_lines.side_effect = RuntimeError(
'log-path-secret-must-not-leak',
)
with self.assertRaisesRegex(RuntimeError, '^managed source log tail failed$'):
supervisor.send_managed_source_log_tail(
self.metadata, 'sample', 2,
)
def test_pipeline_workers_expose_only_meaningful_typed_settings(self):
with tempfile.TemporaryDirectory() as temp_dir:
ensure_private_directory(temp_dir, reject_reparse=True)
log_dir = os.path.join(temp_dir, 'logs')
state_dir = os.path.join(temp_dir, 'state')
ensure_private_directory(log_dir, reject_reparse=True)
ensure_private_directory(state_dir, reject_reparse=True)
config_path = os.path.join(temp_dir, 'config.yaml')
Path(config_path).write_text('{}\n', encoding='ascii')
harden_private_file(config_path)
worker = supervisor.ManagedPipelineWorker(
'result-ingester', config_path, str(APP_DIR), temp_dir,
{
'log_dir': log_dir,
'state_dir': state_dir,
'defaults': {'enabled': True, 'restart': True},
},
{'enabled': True},
dependency_gate=supervisor.DependencyGate(ready=True),
)
self.assertEqual(worker.structured_state()['allowed_actions'], [
'start', 'stop', 'restart', 'pause', 'resume',
'set-restart', 'set-restart-delay',
])
self.server.managed_sources[:] = [worker]
self.server.context.update({
'lifecycle_phase': PHASE_ACTIVE,
'activation_state': PHASE_ACTIVE,
'start_gate_open': True,
})
with self.assertRaisesRegex(RuntimeError, 'unavailable'):
supervisor.send_managed_source_action(
self.metadata, 'result-ingester', 'set-mode', mode='once',
)
def test_keychecks_reject_loop_mode_but_retain_periodic_controls(self):
with tempfile.TemporaryDirectory() as temp_dir:
ensure_private_directory(temp_dir, reject_reparse=True)
log_dir = os.path.join(temp_dir, 'logs')
state_dir = os.path.join(temp_dir, 'state')
ensure_private_directory(log_dir, reject_reparse=True)
ensure_private_directory(state_dir, reject_reparse=True)
config_path = os.path.join(temp_dir, 'config.yaml')
Path(config_path).write_text('{}\n', encoding='ascii')
harden_private_file(config_path)
source = supervisor.ManagedKeychecks(
config_path, str(APP_DIR), temp_dir,
{'log_dir': log_dir, 'state_dir': state_dir},
{
'enabled': True,
'repeat': True,
'interval': 60,
'summary_tsv': os.path.join(temp_dir, 'summary.tsv'),
},
dependency_gate=supervisor.DependencyGate(ready=True),
)
self.server.managed_sources[:] = [source]
self.server.context.update({
'lifecycle_phase': PHASE_ACTIVE,
'activation_state': PHASE_ACTIVE,
'start_gate_open': True,
})
with self.assertRaisesRegex(RuntimeError, 'mode is unavailable'):
supervisor.send_managed_source_action(
self.metadata, 'keychecks', 'set-mode', mode='loop',
)
supervisor.send_managed_source_action(
self.metadata, 'keychecks', 'set-mode', mode='repeat',
)
self.assertTrue(source.once)
self.assertTrue(source.repeat)
def test_typed_mutation_rechecks_phase_after_acquiring_control_lock(self):
source = mock.Mock(
source='sample', manual_only=False, runtime_blocked=False,
desired_state='running',
)
source.start.return_value = True
self.server.managed_sources[:] = [source]
self.server.context.update({
'lifecycle_phase': PHASE_ACTIVE,
'activation_state': PHASE_ACTIVE,
'start_gate_open': True,
})
class StopOnEnter:
def __enter__(lock_self):
self.server.context['lifecycle_phase'] = PHASE_STOPPING
self.server.context['activation_state'] = PHASE_STOPPING
return lock_self
def __exit__(lock_self, exc_type, exc, traceback):
return False
original_lock = self.server.lock
self.server.lock = StopOnEnter()
try:
response = self.server.run_request({
'schema': supervisor.CONTROL_SCHEMA,
'instance_id': self.metadata['instance_id'],
'token': self.metadata['token'],
'action': 'managed-source-action',
'source_id': 'sample',
'source_action': 'start',
})
finally:
self.server.lock = original_lock
self.assertFalse(response['ok'])
self.assertIn(PHASE_STOPPING, response['error'])
source.start.assert_not_called()
def test_typed_actions_require_exact_ids_fields_and_role_capabilities(self):
source = mock.Mock()
source.source = 'manual'
source.manual_only = True
source.structured_state.return_value = self.structured_source_state(
'manual', 'docker-shadow', lifecycle_state='stopped',
desired_state='stopped', process_state='stopped', pid=None,
mode='manual', interval_seconds=0,
)
self.server.managed_sources[:] = [source]
self.server.context.update({
'lifecycle_phase': PHASE_ACTIVE,
'activation_state': PHASE_ACTIVE,
'start_gate_open': True,
})
with self.assertRaisesRegex(RuntimeError, 'unknown'):
supervisor.send_managed_source_action(self.metadata, 'all', 'start')
with self.assertRaisesRegex(RuntimeError, 'unavailable'):
supervisor.send_managed_source_action(self.metadata, 'manual', 'restart')
with self.assertRaisesRegex(RuntimeError, 'fields are invalid'):
supervisor.send_managed_source_action(
self.metadata, 'manual', 'start', interval_seconds=1,
)
for source_id in ('discovery-producer:github', 'discovery-producer:unknown'):
with self.subTest(source_id=source_id), self.assertRaisesRegex(
RuntimeError, 'unknown',
):
supervisor.send_managed_source_action(
self.metadata, source_id, 'start',
)
for source_action in (None, True, 1, [], {}, 'start; status'):
with self.subTest(source_action=source_action), self.assertRaisesRegex(
RuntimeError, 'unsupported',
):
supervisor.send_managed_source_action(
self.metadata, 'manual', source_action,
)
source.start.assert_not_called()
def test_typed_clients_reject_malformed_authenticated_responses(self):
class ResponseSocket:
def __init__(self, response):
self.response = json.dumps(response).encode('utf-8')
def __enter__(self):
return self
def __exit__(self, exc_type, exc, traceback):
return False
def settimeout(self, _timeout):
return None
def sendall(self, _payload):
return None
def shutdown(self, _direction):
return None
def recv(self, _limit):
response, self.response = self.response, b''
return response
envelope = {
'schema': supervisor.CONTROL_SCHEMA,
'instance_id': self.metadata['instance_id'],
'ok': True,
'result': {},
}
for response in (
{**envelope, 'ok': 1},
{**envelope, 'extra': True},
{
'schema': supervisor.CONTROL_SCHEMA,
'instance_id': self.metadata['instance_id'],
'ok': False,
'error': {'unsafe': True},
},
):
with self.subTest(envelope=response), mock.patch.object(
supervisor.socket, 'create_connection',
return_value=ResponseSocket(response),
), self.assertRaisesRegex(ValueError, 'control response'):
supervisor.send_control_request(self.metadata, 'handshake')
valid_source = {
'source_action': 'start', 'outcome': 'completed',
'source': self.structured_source_state(),
}
malformed_source = (
None,
{},
{**valid_source, 'source_action': 'stop'},
{**valid_source, 'outcome': 'unknown'},
{**valid_source, 'source': {'id': 'other'}},
{**valid_source, 'extra': True},
)
for result in malformed_source:
with self.subTest(source_result=result), mock.patch.object(
supervisor, 'send_control_request', return_value=result,
), self.assertRaisesRegex(ValueError, 'managed source action response'):
supervisor.send_managed_source_action(
self.metadata, 'sample', 'start',
)
valid_tail = {
'source_id': 'sample', 'line_count': 1,
'lines': ['line'], 'response_truncated': False,
}
malformed_tail = (
{**valid_tail, 'line_count': 2},
{**valid_tail, 'response_truncated': 0},
{**valid_tail, 'extra': True},
)
for result in malformed_tail:
with self.subTest(tail_result=result), mock.patch.object(
supervisor, 'send_control_request', return_value=result,
), self.assertRaisesRegex(ValueError, 'managed source log tail'):
supervisor.send_managed_source_log_tail(
self.metadata, 'sample', 1,
)
valid_dashboard = {
'dashboard_action': 'start', 'outcome': 'completed',
'dashboard': {
'id': 'dashboard', 'status': 'healthy',
'desired_state': 'running', 'process_state': 'running',
'healthy': True, 'pid': 1234, 'restart_count': 0,
'safe_error_category': '',
'allowed_actions': ['start', 'stop', 'restart'],
},
}
for result in (
{**valid_dashboard, 'dashboard_action': 'stop'},
{**valid_dashboard, 'outcome': 'unknown'},
{**valid_dashboard, 'extra': True},
):
with self.subTest(dashboard_result=result), mock.patch.object(
supervisor, 'send_control_request', return_value=result,
), self.assertRaisesRegex(ValueError, 'dashboard action response'):
supervisor.send_dashboard_action(self.metadata, 'start')
runtime = {
'pid': 1234, 'phase': PHASE_ACTIVE, 'manages_postgres': True,
'start_gate_open': True, 'shutdown_requested': False,
'runtime_failed': False,
}
valid_snapshot = {
'snapshot_schema': supervisor.RUNTIME_SNAPSHOT_SCHEMA,
'runtime': runtime,
'postgres': {
'state': 'READY', 'ready': True, 'failures': 0,
'safe_error_category': '',
},
'dashboard': valid_dashboard['dashboard'],
'sources': [self.structured_source_state()],
'pipeline': {
'ingester_ready': True, 'projector_ready': True,
'cutover_ready': True,
'ingester_state': 'active', 'projector_state': 'active',
'bundle_items': 0, 'bundle_bytes': 0,
'projection_items': 0, 'projection_bytes': 0,
'keycheck_items': 0, 'keycheck_bytes': 0,
'quarantine_items': 0, 'quarantine_bytes': 0,
},
'scan_workers': {
'active': 0, 'limit': 1, 'base_active': 0, 'base_limit': 1,
'bonus_active': 0, 'bonus_limit': 0, 'trufflehog': 0,
'sources': {},
},
}
for result in (
{**valid_snapshot, 'extra': True},
{**valid_snapshot, 'runtime': {**runtime, 'pid': True}},
{**valid_snapshot, 'sources': [
{**self.structured_source_state(), 'unknown': 'value'},
]},
):
with self.subTest(snapshot=result), mock.patch.object(
supervisor, 'send_control_request', return_value=result,
), self.assertRaisesRegex(ValueError, 'structured supervisor snapshot'):
supervisor.get_runtime_snapshot(self.metadata)
def test_many_idle_clients_use_bounded_resources_and_authenticated_control_stays_responsive(self):
idle = []
try:
for _ in range(supervisor.MAX_CONTROL_PENDING_SOCKETS * 2):
client = socket.create_connection(self.server.server_address, timeout=2)
idle.append(client)
deadline = time.monotonic() + 2
while time.monotonic() < deadline and self.server.pending_control_connections == 0:
time.sleep(0.01)
started = time.monotonic()
result = supervisor.send_control_request(self.metadata, 'handshake', timeout=3)
elapsed = time.monotonic() - started
self.assertEqual(result['instance_id'], self.metadata['instance_id'])
self.assertLess(elapsed, 2)
self.assertLessEqual(self.server.pending_control_connections, supervisor.MAX_CONTROL_PENDING_SOCKETS)
self.assertLessEqual(self.server.active_control_workers, supervisor.MAX_CONTROL_WORKERS)
control_threads = [
thread for thread in threading.enumerate()
if thread.name.startswith('supervisor-control')
]
self.assertLessEqual(len(control_threads), supervisor.MAX_CONTROL_WORKERS + 1)
finally:
for client in idle:
try:
client.close()
except OSError:
pass
def test_inactive_control_plane_requires_activation_before_runtime_requests(self):
source = mock.Mock()
activation_event = threading.Event()
context = {
'shutdown_event': threading.Event(),
'with_postgres': True,
'activation_state': PHASE_ACTIVATING,
'lifecycle_phase': PHASE_ACTIVATING,
'start_gate_open': False,
'activation_event': activation_event,
'activation_lock': threading.RLock(),
}
server = supervisor.SupervisorControlServer(
('127.0.0.1', 0), [source], context, threading.RLock(), 'inactive-instance', 'a' * 48,
)
thread = threading.Thread(target=server.serve_forever, daemon=True)
thread.start()
metadata = {
'instance_id': 'inactive-instance',
'token': 'a' * 48,
'control': {'host': '127.0.0.1', 'port': server.server_address[1]},
}
try:
handshake = supervisor.send_control_request(metadata, 'handshake')
self.assertEqual(handshake['activation_state'], PHASE_ACTIVATING)
with self.assertRaisesRegex(RuntimeError, 'ACTIVATING'):
supervisor.send_control_request(metadata, 'snapshot')
source.poll.assert_not_called()
activated = supervisor.send_control_request(metadata, 'activate')
self.assertEqual(activated['activation_state'], PHASE_ACTIVE)
self.assertTrue(activation_event.is_set())
self.assertEqual(
supervisor.send_control_request(metadata, 'handshake')['activation_state'],
PHASE_ACTIVE,
)
finally:
server.shutdown()
server.server_close()
def test_activating_phase_never_ticks_lifecycle_objects(self):
controller = mock.Mock()
dashboard = mock.Mock()
gate = mock.Mock()
context = {
'lifecycle_phase': PHASE_ACTIVATING,
'activation_state': PHASE_ACTIVATING,
'start_gate_open': False,
'postgres_controller': controller,
'dashboard_manager': dashboard,
'dependency_gate': gate,
}
supervisor.tick_supervisor_runtime(context)
controller.tick.assert_not_called()
dashboard.poll.assert_not_called()
gate.set_ready.assert_not_called()
def test_runtime_tick_honors_low_overhead_status_intervals(self):
source_gate = mock.Mock(ready=False)
context = {
'lifecycle_phase': PHASE_ACTIVE,
'start_gate_open': True,
'source_dependency_gate': source_gate,
'supervisor_config': {
'authority_check_interval_sec': 5,
'pipeline_status_refresh_sec': 2,
},
}
with (
mock.patch.object(supervisor.time, 'monotonic', return_value=100),
mock.patch.object(supervisor, 'check_runtime_authority', return_value=True),
):
supervisor.tick_supervisor_runtime(context)
self.assertEqual(context['next_authority_check_at'], 105)
self.assertEqual(context['_pipeline_status_refresh_at'], 102)
source_gate.set_ready.assert_not_called()
def test_database_gate_transition_defers_source_admission_one_tick(self):
controller = mock.Mock(ready=True)
controller.snapshot.return_value = {
'state': 'READY', 'ready': True, 'failures': 0, 'detail': '',
}
dependency_gate = supervisor.DependencyGate(ready=False)
source_gate = supervisor.DependencyGate(ready=False)
context = {
'lifecycle_phase': PHASE_ACTIVE,
'start_gate_open': True,
'postgres_controller': controller,
'dependency_gate': dependency_gate,
'source_dependency_gate': source_gate,
'next_authority_check_at': 101,
}
ready = {
'ingester_ready': True,
}
with mock.patch.object(supervisor.time, 'monotonic', return_value=100), \
mock.patch.object(supervisor, 'pipeline_status_snapshot',
return_value=ready) as pipeline:
supervisor.tick_supervisor_runtime(context)
pipeline.assert_not_called()
self.assertTrue(dependency_gate.ready)
self.assertFalse(source_gate.ready)
self.assertTrue(context['_defer_pipeline_status_until_next_tick'])
supervisor.tick_supervisor_runtime(context, pipeline_snapshot=ready)
pipeline.assert_not_called()
self.assertTrue(source_gate.ready)
self.assertNotIn('_defer_pipeline_status_until_next_tick', context)
def test_deferred_pipeline_status_never_opens_database(self):
context = {'_defer_pipeline_status_until_next_tick': True}
with mock.patch.object(supervisor, 'connect_postgres') as connect:
snapshot = supervisor.pipeline_status_snapshot(context)
self.assertFalse(snapshot['ingester_ready'])
self.assertEqual(snapshot['ingester_state'], 'starting')
connect.assert_not_called()
def test_cached_pipeline_status_never_opens_database(self):
context = {}
with mock.patch.object(supervisor, 'connect_postgres') as connect:
snapshot = supervisor.pipeline_status_snapshot(
context, allow_refresh=False,
)
self.assertFalse(snapshot['ingester_ready'])
self.assertEqual(snapshot['ingester_state'], 'starting')
connect.assert_not_called()
def test_command_failure_is_not_reported_as_transport_success(self):
source = mock.Mock(source='sample', runtime_blocked=False, last_action_error='owned process remained live')
source.stop.return_value = False
server = supervisor.SupervisorControlServer(
('127.0.0.1', 0), [source], {'shutdown_event': threading.Event(), 'with_postgres': False},
threading.RLock(), 'failure-instance', 'f' * 48,
)
thread = threading.Thread(target=server.serve_forever, daemon=True)
thread.start()
metadata = {
'instance_id': 'failure-instance',
'token': 'f' * 48,
'control': {'host': '127.0.0.1', 'port': server.server_address[1]},
}
try:
with self.assertRaisesRegex(RuntimeError, 'stop failed'):
supervisor.send_control_command(metadata, 'stop sample')
finally:
server.shutdown()
server.server_close()
def test_control_handler_returns_error_when_start_raises(self):
source = mock.Mock(source='sample', runtime_blocked=False, last_action_error='')
source.start.side_effect = RuntimeError('owned launch failed')
server = supervisor.SupervisorControlServer(
('127.0.0.1', 0), [source], {'shutdown_event': threading.Event(), 'with_postgres': False},
threading.RLock(), 'raising-instance', 'e' * 48,
)
threading.Thread(target=server.serve_forever, daemon=True).start()
metadata = {
'instance_id': 'raising-instance',
'token': 'e' * 48,
'control': {'host': '127.0.0.1', 'port': server.server_address[1]},
}
try:
with self.assertRaisesRegex(RuntimeError, 'owned launch failed'):
supervisor.send_control_command(metadata, 'start sample')
finally:
server.shutdown()
server.server_close()
def test_shutdown_phase_closes_start_and_snapshot_gates_before_ack(self):
entered = threading.Event()
release = threading.Event()
class BarrierEvent(threading.Event):
def set(self):
super().set()
entered.set()
release.wait(5)
source = mock.Mock(source='sample', runtime_blocked=False, last_action_error='')
context = {
'shutdown_event': BarrierEvent(),
'with_postgres': False,
'lifecycle_phase': PHASE_ACTIVE,
'activation_state': PHASE_ACTIVE,
'start_gate_open': True,
}
server = supervisor.SupervisorControlServer(
('127.0.0.1', 0), [source], context, threading.RLock(), 'race-instance', 'r' * 48,
)
threading.Thread(target=server.serve_forever, daemon=True).start()
metadata = {
'instance_id': 'race-instance',
'token': 'r' * 48,
'control': {'host': '127.0.0.1', 'port': server.server_address[1]},
}
results = {}
def shutdown_request():
results['shutdown'] = supervisor.send_control_request(metadata, 'shutdown')
def late_start():
try:
supervisor.send_control_command(metadata, 'start sample')
except RuntimeError as exc:
results['start_error'] = str(exc)
shutdown_thread = threading.Thread(target=shutdown_request)
shutdown_thread.start()
self.assertTrue(entered.wait(2))
start_thread = threading.Thread(target=late_start)
start_thread.start()
release.set()
shutdown_thread.join(5)
start_thread.join(5)
try:
self.assertIn('shutdown requested', results['shutdown'])
self.assertIn('STOPPING', results['start_error'])
self.assertEqual(context['lifecycle_phase'], PHASE_STOPPING)
source.start.assert_not_called()
source.row.return_value = ['sample', 'failed', '-', 'loop', '-', '-', '-', '0/0', '-', 'log', 'live', 'stopped']
snapshot = supervisor.send_control_request(metadata, 'snapshot')
self.assertEqual(snapshot['activation_state'], PHASE_STOPPING)
source.poll.assert_not_called()
finally:
server.shutdown()
server.server_close()
class InstanceAuthorityTests(unittest.TestCase):
def test_private_metadata_tracks_two_phase_activation_state(self):
with tempfile.TemporaryDirectory() as temp_dir:
ensure_private_directory(temp_dir, reject_reparse=True)
config_path = os.path.join(temp_dir, 'config.yaml')
instance_path = os.path.join(temp_dir, 'supervisor.instance.json')
Path(config_path).write_text('{}\n', encoding='ascii')
harden_private_file(config_path)
metadata = build_instance_metadata(
'nonce', supervisor.__file__, config_path, '127.0.0.1', 12345, False,
)
self.assertEqual(metadata['activation_state'], PHASE_ACTIVATING)
write_instance_metadata(instance_path, metadata)
updated = supervisor_instance.update_instance_activation(
instance_path, metadata['instance_id'], PHASE_ACTIVE,
)
self.assertEqual(updated['activation_state'], PHASE_ACTIVE)
self.assertEqual(
supervisor_instance.load_instance_metadata(instance_path)['activation_state'],
PHASE_ACTIVE,
)
def test_reused_pid_creation_identity_is_refused(self):
identity = current_process_identity()
with tempfile.TemporaryDirectory() as temp_dir:
ensure_private_directory(temp_dir)
supervisor_path = os.path.join(temp_dir, 'supervisor.py')
config_path = os.path.join(temp_dir, 'config.yaml')
Path(supervisor_path).write_text('pass\n', encoding='ascii')
Path(config_path).write_text('{}\n', encoding='ascii')
metadata = build_instance_metadata(
'nonce', supervisor_path, config_path, '127.0.0.1', 12345, False,
identity=identity,
)
metadata['process_creation_time'] += '-reused'
with self.assertRaises(ProcessIdentityError):
verify_instance_process(metadata)
def test_duplicate_verified_launch_metadata_is_detected(self):
with tempfile.TemporaryDirectory() as temp_dir:
ensure_private_directory(temp_dir)
config_path = os.path.join(temp_dir, 'config.yaml')
instance_path = os.path.join(temp_dir, 'supervisor.instance.json')
Path(config_path).write_text('{}\n', encoding='ascii')
server = supervisor.SupervisorControlServer(
('127.0.0.1', 0), [], {'shutdown_event': threading.Event(), 'with_postgres': False},
threading.RLock(), 'duplicate-instance', 'z' * 48,
)
thread = threading.Thread(target=server.serve_forever, daemon=True)
thread.start()
try:
metadata = build_instance_metadata(
'launch', supervisor.__file__, config_path, '127.0.0.1', server.server_address[1], False,
instance_id='duplicate-instance', token='z' * 48,
)
write_instance_metadata(instance_path, metadata)
retained_process = mock.Mock()
with mock.patch.object(supervisor, 'verify_instance_process', return_value=retained_process):
state, found, retained = supervisor.inspect_existing_instance(instance_path, config_path)
self.assertEqual(state, 'verified')
self.assertEqual(found['instance_id'], 'duplicate-instance')
retained.close()
finally:
server.shutdown()
server.server_close()
def test_coordinated_stop_uses_shutdown_request_and_retained_handle(self):
metadata = {
'instance_id': 'coordinated',
'manages_postgres': True,
}
class Retained:
def __init__(self):
self.waited = None
self.closed = False
def wait(self, timeout):
self.waited = timeout
return True
def close(self):
self.closed = True
def exit_code(self):
return 0
retained = Retained()
with tempfile.TemporaryDirectory() as temp_dir, \
mock.patch.object(supervisor, 'load_instance_metadata', return_value=metadata), \
mock.patch.object(supervisor, 'verify_instance_process', return_value=retained), \
mock.patch.object(supervisor, 'send_control_request', return_value='coordinated shutdown requested') as send, \
mock.patch.object(supervisor, 'remove_instance_if_matches') as remove:
result = supervisor.stop_background(
os.path.join(temp_dir, 'config.yaml'), temp_dir,
private_background_config(temp_dir, background_shutdown_timeout_sec=7),
with_postgres=True,
)
self.assertTrue(result)
self.assertEqual(send.call_args.args[1], 'shutdown')
self.assertTrue(send.call_args.kwargs['with_postgres'])
self.assertEqual(retained.waited, 7)
self.assertTrue(retained.closed)
remove.assert_called_once()
self.assertEqual(remove.call_args.args[1], 'coordinated')
self.assertTrue(remove.call_args.kwargs['lock_path'].endswith('supervisor.lock'))
def test_identity_mismatch_never_sends_shutdown(self):
with tempfile.TemporaryDirectory() as temp_dir, \
mock.patch.object(supervisor, 'load_instance_metadata', return_value={'pid': 123}), \
mock.patch.object(supervisor, 'verify_instance_process', side_effect=ProcessIdentityError('reused PID')), \
mock.patch.object(supervisor, 'send_control_request') as send:
result = supervisor.stop_background(
os.path.join(temp_dir, 'config.yaml'), temp_dir, private_background_config(temp_dir),
)
self.assertFalse(result)
send.assert_not_called()
def test_nonzero_child_exit_retains_metadata_and_fails_stop(self):
metadata = {'instance_id': 'failed-stop', 'manages_postgres': True}
retained = mock.Mock()
retained.wait.return_value = True
retained.exit_code.return_value = 7
with tempfile.TemporaryDirectory() as temp_dir, \
mock.patch.object(supervisor, 'load_instance_metadata', return_value=metadata), \
mock.patch.object(supervisor, 'verify_instance_process', return_value=retained), \
mock.patch.object(supervisor, 'send_control_request', return_value='shutdown requested'), \
mock.patch.object(supervisor, 'remove_instance_if_matches') as remove:
result = supervisor.stop_background(
os.path.join(temp_dir, 'config.yaml'),
temp_dir,
private_background_config(temp_dir),
with_postgres=True,
)
self.assertFalse(result)
remove.assert_not_called()
def test_config_drift_allows_only_command_bound_authenticated_shutdown_verification(self):
class Retained:
def __init__(self, args):
self.args = args
self.closed = False
def command_line(self):
return self.args
def close(self):
self.closed = True
identity = current_process_identity()
with tempfile.TemporaryDirectory() as temp_dir:
ensure_private_directory(temp_dir)
supervisor_path = os.path.join(temp_dir, 'supervisor.py')
config_path = os.path.join(temp_dir, 'config.yaml')
Path(supervisor_path).write_text('pass\n', encoding='ascii')
Path(config_path).write_text('version: 1\n', encoding='ascii')
metadata = build_instance_metadata(
'nonce', supervisor_path, config_path, '127.0.0.1', 12345, False,
identity=identity,
)
Path(config_path).write_text('version: 2\n', encoding='ascii')
args = [sys.executable, supervisor_path, '--config', config_path, '--background-child']
with mock.patch.object(supervisor_instance, 'verify_retained_process', return_value=Retained(args)):
with self.assertRaisesRegex(InstanceMetadataError, 'config hash drifted'):
verify_instance_process(metadata, supervisor_path, config_path)
retained = verify_instance_process(metadata, supervisor_path, config_path, allow_config_drift=True)
self.assertFalse(retained.closed)
retained.close()
unrelated = Retained([sys.executable, '-c', 'print(1)'])
with mock.patch.object(supervisor_instance, 'verify_retained_process', return_value=unrelated):
with self.assertRaisesRegex(InstanceMetadataError, 'not a background supervisor'):
verify_instance_process(metadata, supervisor_path, config_path, allow_config_drift=True)
self.assertTrue(unrelated.closed)
def test_metadata_publication_never_replaces_existing_instance(self):
with tempfile.TemporaryDirectory() as temp_dir:
ensure_private_directory(temp_dir)
config_path = os.path.join(temp_dir, 'config.yaml')
Path(config_path).write_text('{}\n', encoding='ascii')
path = os.path.join(temp_dir, 'supervisor.instance.json')
first = build_instance_metadata('one', supervisor.__file__, config_path, '127.0.0.1', 12345, False)
second = build_instance_metadata('two', supervisor.__file__, config_path, '127.0.0.1', 12346, False)
write_instance_metadata(path, first)
with self.assertRaises(FileExistsError):
write_instance_metadata(path, second)
def test_supervisor_script_hash_drift_never_grants_control(self):
identity = current_process_identity()
with tempfile.TemporaryDirectory() as temp_dir:
ensure_private_directory(temp_dir)
supervisor_path = os.path.join(temp_dir, 'supervisor.py')
config_path = os.path.join(temp_dir, 'config.yaml')
Path(supervisor_path).write_text('version = 1\n', encoding='ascii')
Path(config_path).write_text('{}\n', encoding='ascii')
metadata = build_instance_metadata(
'nonce', supervisor_path, config_path, '127.0.0.1', 12345, False,
identity=identity,
)
Path(supervisor_path).write_text('version = 2\n', encoding='ascii')
with self.assertRaisesRegex(InstanceMetadataError, 'script hash'):
verify_instance_process(
metadata,
supervisor_path,
config_path,
allow_config_drift=True,
)
def test_lifetime_lock_excludes_a_second_process(self):
with tempfile.TemporaryDirectory() as temp_dir:
control_dir = os.path.join(temp_dir, 'control')
ensure_private_directory(control_dir, reject_reparse=True)
instance_path = os.path.join(control_dir, 'supervisor.instance.json')
second_instance_path = os.path.join(temp_dir, 'custom', 'other.instance.json')
singleton_path = os.path.join(control_dir, 'supervisor.lock')
lock = SupervisorInstanceLock(instance_path, lock_path=singleton_path).acquire()
code = (
'import sys; sys.path.insert(0, sys.argv[1]); '
'from supervisor_instance import SupervisorInstanceLock, InstanceLockError; '
'lock=SupervisorInstanceLock(sys.argv[2], lock_path=sys.argv[3]); '
'\ntry: lock.acquire()\nexcept InstanceLockError: raise SystemExit(3)\n'
'lock.release(); raise SystemExit(0)'
)
try:
blocked = subprocess.run(
[sys.executable, '-c', code, str(APP_DIR), second_instance_path, singleton_path],
stdin=subprocess.DEVNULL,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
timeout=15,
check=False,
)
self.assertEqual(blocked.returncode, 3, blocked.stderr.decode(errors='replace'))
finally:
lock.release()
admitted = subprocess.run(
[sys.executable, '-c', code, str(APP_DIR), second_instance_path, singleton_path],
stdin=subprocess.DEVNULL,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
timeout=15,
check=False,
)
self.assertEqual(admitted.returncode, 0, admitted.stderr.decode(errors='replace'))
def test_concurrent_unmanaged_background_starts_are_both_rejected(self):
with tempfile.TemporaryDirectory() as temp_dir:
config_path, paths = prepare_supervisor_fixture(temp_dir)
runtime_dir = paths['runtime_dir']
instance_paths = [
os.path.join(runtime_dir, 'control', 'one.instance.json'),
os.path.join(runtime_dir, 'control', 'two.instance.json'),
]
commands = [
[
sys.executable,
str(APP_DIR / 'supervisor.py'),
'--config', config_path,
'--background',
'--instance-file', instance_path,
]
for instance_path in instance_paths
]
environment = os.environ.copy()
environment['SCANNER_DB_URL'] = 'postgresql://truf:fixture@127.0.0.1:5432/truf'
environment['DATABASE_URL'] = environment['SCANNER_DB_URL']
environment.pop('TRUF_MANAGED_POSTGRES_DSN', None)
parents = [
subprocess.Popen(
command,
cwd=temp_dir,
stdin=subprocess.DEVNULL,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
env=environment,
)
for command in commands
]
results = []
try:
for parent in parents:
output, _ = parent.communicate(timeout=20)
results.append((parent.returncode, output.decode(errors='replace')))
self.assertTrue(all(code != 0 for code, _ in results), results)
self.assertTrue(all('unmanaged PostgreSQL mutation is retired' in output for _, output in results))
self.assertFalse(any(os.path.exists(path) for path in instance_paths))
finally:
for parent in parents:
if parent.poll() is None:
parent.terminate()
parent.wait(timeout=5)
def test_background_custom_port_still_rejects_unmanaged_mutation(self):
with tempfile.TemporaryDirectory() as temp_dir:
config_path, paths = prepare_supervisor_fixture(temp_dir, control_port=0)
instance_path = os.path.join(paths['control_dir'], 'custom.instance.json')
environment = os.environ.copy()
environment['SCANNER_DB_URL'] = 'postgresql://truf:fixture@127.0.0.1:5432/truf'
environment['DATABASE_URL'] = environment['SCANNER_DB_URL']
environment.pop('TRUF_MANAGED_POSTGRES_DSN', None)
common = [sys.executable, str(APP_DIR / 'supervisor.py'), '--config', config_path]
started = subprocess.run(
[*common, '--background', '--instance-file', instance_path],
cwd=temp_dir,
stdin=subprocess.DEVNULL,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
env=environment,
timeout=20,
check=False,
)
output = started.stdout.decode(errors='replace')
self.assertNotEqual(started.returncode, 0, output)
self.assertIn('unmanaged PostgreSQL mutation is retired', output)
self.assertFalse(os.path.exists(instance_path))
def test_startup_rollback_terminates_only_the_exact_child_when_auth_is_unavailable(self):
child = FakeOwnedProcess()
child.pid = 50001
candidate = {'pid': 99999, 'launch_nonce': 'other', 'instance_id': 'other'}
with mock.patch.object(supervisor, 'send_control_request') as send, \
mock.patch.object(supervisor, 'remove_instance_if_matches') as remove:
outcome = supervisor._rollback_background_start(
child, candidate, 'unused.json', 'expected', False,
)
self.assertIn('unpublished child', outcome)
self.assertTrue(child.terminated)
send.assert_not_called()
remove.assert_not_called()
def test_lost_activation_response_uses_coordinated_shutdown_without_force(self):
child = FakeOwnedProcess()
child.pid = 50002
candidate = {
'pid': child.pid,
'launch_nonce': 'expected',
'instance_id': 'activated',
'activation_state': PHASE_ACTIVATING,
}
def request(_metadata, action, **_kwargs):
if action == 'activation':
return {'activation_state': PHASE_ACTIVE}
if action == 'shutdown':
child.returncode = 0
return 'coordinated shutdown requested'
raise AssertionError(action)
with mock.patch.object(supervisor, 'send_control_request', side_effect=request), \
mock.patch.object(supervisor, 'remove_instance_if_matches', return_value=True):
outcome = supervisor._rollback_background_start(
child, candidate, 'unused.json', 'expected', True,
activation_attempted=True, shutdown_timeout=37,
)
self.assertIn('authenticated coordinated shutdown', outcome)
self.assertFalse(child.terminated)
def test_exact_candidate_never_force_terminates_when_parent_flag_says_not_activated(self):
child = FakeOwnedProcess()
child.pid = 50004
candidate = {
'pid': child.pid,
'launch_nonce': 'expected',
'instance_id': 'exact',
'activation_state': PHASE_ACTIVATING,
}
def request(_metadata, action, **_kwargs):
if action == 'activation':
return {'activation_state': PHASE_ACTIVE}
if action == 'shutdown':
child.returncode = 0
return 'shutdown requested'
raise AssertionError(action)
with mock.patch.object(supervisor, 'send_control_request', side_effect=request), \
mock.patch.object(supervisor, 'remove_instance_if_matches', return_value=True):
outcome = supervisor._rollback_background_start(
child,
candidate,
'unused.json',
'expected',
True,
activation_attempted=False,
shutdown_timeout=29,
)
self.assertIn('authenticated coordinated shutdown', outcome)
self.assertFalse(child.terminated)
def test_uncertain_activation_timeout_never_force_terminates_postgres_root(self):
class TimedOutChild(FakeOwnedProcess):
def __init__(self):
super().__init__()
self.wait_timeouts = []
def wait(self, timeout=None):
self.wait_timeouts.append(timeout)
raise subprocess.TimeoutExpired('supervisor', timeout)
child = TimedOutChild()
child.pid = 50003
candidate = {
'pid': child.pid,
'launch_nonce': 'expected',
'instance_id': 'uncertain',
'activation_state': PHASE_ACTIVATING,
}
with mock.patch.object(supervisor, 'send_control_request', side_effect=TimeoutError('response lost')):
outcome = supervisor._rollback_background_start(
child, candidate, 'unused.json', 'expected', True,
activation_attempted=True, shutdown_timeout=41,
)
self.assertIn('left running', outcome)
self.assertFalse(child.terminated)
self.assertEqual(child.wait_timeouts, [41.0])
class ScanWorkerStatusTests(unittest.TestCase):
def test_table_signature_does_not_parse_source_logs_or_state(self):
process = mock.Mock()
process.pid = 123
process.poll.return_value = None
source = SimpleNamespace(
source='github',
status='running',
desired_state='running',
process=process,
last_exit_code=None,
restarts=0,
restart_streak=0,
runtime_blocked=False,
last_action_error='',
row=mock.Mock(side_effect=AssertionError('row must not be rendered for signatures')),
)
signature = supervisor.table_signature([source])
self.assertEqual(signature[0][:4], ('github', 'running', 'running', 123))
source.row.assert_not_called()
def test_snapshot_reports_authoritative_scan_slot_leases_by_source(self):
with tempfile.TemporaryDirectory() as temp_dir:
db_path = os.path.join(temp_dir, 'scan_limiter.db')
connection = sqlite3.connect(db_path)
try:
connection.execute('CREATE TABLE scan_slots (owner_source TEXT, child_executable TEXT)')
connection.executemany(
'INSERT INTO scan_slots(owner_source, child_executable) VALUES (?, ?)',
[('github', r'C:\Tools\trufflehog.exe'), ('github', None), ('npm', r'C:\Tools\trufflehog.exe')],
)
connection.commit()
finally:
connection.close()
snapshot = supervisor.scan_worker_snapshot({'global': {
'max_active_scans': 6,
'opportunistic_scan_slots': 1,
'scan_limiter_db': db_path,
}})
self.assertEqual(snapshot['active'], 3)
self.assertEqual(snapshot['limit'], 7)
self.assertEqual(snapshot['base_active'], 3)
self.assertEqual(snapshot['bonus_active'], 0)
self.assertEqual(snapshot['trufflehog'], 2)
self.assertEqual(snapshot['sources'], {'github': 2, 'npm': 1})
self.assertEqual(snapshot['detail'], '')
def test_runtime_table_includes_scan_worker_activity(self):
context = {'config': {'global': {'max_active_scans': 6}}}
with mock.patch.object(supervisor, 'scan_worker_snapshot', return_value={
'active': 4,
'limit': 6,
'base_active': 3,
'base_limit': 5,
'bonus_active': 1,
'bonus_limit': 1,
'trufflehog': 3,
'sources': {'github': 1, 'npm': 3},
'detail': '',
}):
lines = supervisor.build_runtime_table_lines([], context=context)
self.assertEqual(
lines[0],
'Scan workers: active=4/6 base=3/5 bonus=1/1 trufflehog=3 staging=1 sources=github:1, npm:3',
)
class DependencyFailureTests(unittest.TestCase):
def test_failed_manual_source_stop_remains_unsafe_during_dependency_loss(self):
with tempfile.TemporaryDirectory() as temp_dir:
gate = supervisor.DependencyGate(ready=True)
source = source_fixture(temp_dir, gate)
source.process = UnkillableProcess()
source.desired_state = 'running'
source.status = 'running'
self.assertFalse(source.stop())
self.assertEqual(source.desired_state, 'stopped')
with self.assertRaises(supervisor.DependencyStopError):
gate.set_ready(False)
self.assertTrue(source.is_running())
def test_uncancellable_source_outage_escalates_and_stays_unsafe(self):
with tempfile.TemporaryDirectory() as temp_dir:
gate = supervisor.DependencyGate(ready=True)
source = source_fixture(temp_dir, gate)
source.process = UnkillableProcess()
source.desired_state = 'running'
source.status = 'running'
with self.assertRaises(supervisor.DependencyStopError):
gate.set_ready(False)
self.assertTrue(source.is_running())
self.assertEqual(source.status, 'failed')
self.assertTrue(gate.stop_failures)
def test_uncancellable_dashboard_outage_escalates(self):
with tempfile.TemporaryDirectory() as temp_dir:
ensure_private_directory(temp_dir, reject_reparse=True)
logs = os.path.join(temp_dir, 'logs')
ensure_private_directory(logs, reject_reparse=True)
config_path = os.path.join(temp_dir, 'config.yaml')
Path(config_path).write_text('{}\n', encoding='ascii')
harden_private_file(config_path)
gate = supervisor.DependencyGate(ready=True)
manager = supervisor.ManagedDashboard(
config_path,
temp_dir,
{'log_dir': logs, 'dashboard': {'enabled': True}},
temp_dir,
temp_dir,
dependency_gate=gate,
process_factory=FakeOwnedProcess,
)
manager.process = UnkillableProcess()
manager.desired_state = 'running'
with self.assertRaises(supervisor.DependencyStopError):
gate.set_ready(False)
self.assertTrue(manager.fatal_stop_failure)
self.assertIsNotNone(manager.process)
manager.close()
class AuthorityAndManualCommandTests(unittest.TestCase):
def test_config_barrier_during_activating_fails_without_lifecycle_publication(self):
with tempfile.TemporaryDirectory() as temp_dir:
ensure_private_directory(temp_dir, reject_reparse=True)
script_path = os.path.join(temp_dir, 'supervisor.py')
config_path = os.path.join(temp_dir, 'config.yaml')
Path(script_path).write_text('version = 1\n', encoding='ascii')
Path(config_path).write_text('version: 1\n', encoding='ascii')
harden_private_file(script_path)
harden_private_file(config_path)
callback = mock.Mock()
context = {
'authority': supervisor.runtime_authority(config_path, script_path),
'shutdown_event': threading.Event(),
'lifecycle_phase': PHASE_ACTIVATING,
'activation_state': PHASE_ACTIVATING,
'start_gate_open': False,
'activation_callback': callback,
}
server = supervisor.SupervisorControlServer(
('127.0.0.1', 0), [], context, threading.RLock(), 'barrier-instance', 'b' * 48,
)
threading.Thread(target=server.serve_forever, daemon=True).start()
metadata = {
'instance_id': 'barrier-instance',
'token': 'b' * 48,
'control': {'host': '127.0.0.1', 'port': server.server_address[1]},
}
Path(config_path).write_text('version: 2\n', encoding='ascii')
try:
with self.assertRaisesRegex(RuntimeError, 'config authority hash drifted'):
supervisor.send_control_request(metadata, 'activate')
callback.assert_not_called()
self.assertEqual(context['lifecycle_phase'], PHASE_ACTIVATING)
self.assertFalse(context['start_gate_open'])
finally:
server.shutdown()
server.server_close()
def test_automatic_source_start_rejects_config_drift_before_launch(self):
with tempfile.TemporaryDirectory() as temp_dir, \
mock.patch.object(supervisor, 'OwnedProcess', side_effect=FakeOwnedProcess) as owned:
gate = supervisor.DependencyGate(ready=True)
source = source_fixture(temp_dir, gate)
authority = supervisor.runtime_authority(source.config_path)
context = {
'authority': authority,
'dependency_gate': gate,
'shutdown_event': threading.Event(),
'supervisor_config': {},
}
source.authority_check = lambda: supervisor.check_runtime_authority(context)
Path(source.config_path).write_text('changed: true\n', encoding='ascii')
self.assertFalse(source.start(force=True))
self.assertIn('authority', source.last_action_error)
self.assertIn('config authority hash drifted', context['authority_drift'])
owned.assert_not_called()
def test_restart_does_not_launch_when_stop_failed(self):
with tempfile.TemporaryDirectory() as temp_dir, \
mock.patch.object(supervisor, 'OwnedProcess', side_effect=FakeOwnedProcess) as owned:
source = source_fixture(temp_dir, supervisor.DependencyGate(ready=True))
source.process = UnkillableProcess()
source.desired_state = 'running'
self.assertFalse(source.restart_now())
self.assertTrue(source.process.poll() is None)
owned.assert_not_called()
def test_drift_rejects_mutation_but_authenticated_shutdown_remains_available(self):
with tempfile.TemporaryDirectory() as temp_dir:
ensure_private_directory(temp_dir, reject_reparse=True)
script_path = os.path.join(temp_dir, 'supervisor.py')
config_path = os.path.join(temp_dir, 'config.yaml')
Path(script_path).write_text('pass\n', encoding='ascii')
Path(config_path).write_text('version: 1\n', encoding='ascii')
harden_private_file(script_path)
harden_private_file(config_path)
shutdown_event = threading.Event()
context = {
'authority': supervisor.runtime_authority(config_path, script_path),
'shutdown_event': shutdown_event,
'with_postgres': False,
'supervisor_config': {'authority_drift_shutdown_grace_sec': 30},
}
Path(config_path).write_text('version: 2\n', encoding='ascii')
server = supervisor.SupervisorControlServer(
('127.0.0.1', 0), [], context, threading.RLock(), 'drift-instance', 'd' * 48,
)
threading.Thread(target=server.serve_forever, daemon=True).start()
metadata = {
'instance_id': 'drift-instance',
'token': 'd' * 48,
'control': {'host': '127.0.0.1', 'port': server.server_address[1]},
}
try:
with self.assertRaisesRegex(RuntimeError, 'config authority hash drifted'):
supervisor.send_control_command(metadata, 'set all interval 10')
self.assertFalse(shutdown_event.is_set())
self.assertIn('shutdown requested', supervisor.send_control_request(metadata, 'shutdown'))
self.assertTrue(shutdown_event.wait(1))
finally:
server.shutdown()
server.server_close()
def test_live_reload_explicitly_requires_supervisor_restart(self):
outcome = supervisor.run_control_command('reload', [], {}, threading.RLock())
self.assertFalse(outcome['success'])
self.assertIn('Live reload is disabled', outcome['output'])
class CoordinatedShutdownTests(unittest.TestCase):
def test_authority_drift_ticks_late_controller_results_and_reaches_shutdown_grace(self):
shutdown_event = threading.Event()
controller = mock.Mock()
context = {
'lifecycle_phase': PHASE_ACTIVE,
'start_gate_open': True,
'postgres_controller': controller,
'shutdown_event': shutdown_event,
'supervisor_config': {'authority_drift_shutdown_grace_sec': 5},
}
with mock.patch.object(supervisor.time, 'monotonic', side_effect=[0, 0, 6]):
supervisor.inhibit_for_authority_drift(context, 'authority drift')
supervisor.tick_supervisor_runtime(context)
controller.inhibit_lifecycle.assert_called_once()
controller.tick.assert_called_once()
self.assertTrue(shutdown_event.is_set())
def test_child_failure_prevents_postgres_stop_request(self):
source = mock.Mock(source='source')
source.stop.return_value = False
controller = mock.Mock()
context = {'postgres_controller': controller, 'supervisor_config': {}}
self.assertFalse(supervisor.coordinated_shutdown([source], context))
controller.request_stop.assert_not_called()
def test_live_dashboard_prevents_postgres_stop_even_if_stop_claims_success(self):
source = mock.Mock(source='source')
source.stop.return_value = True
source.is_running.return_value = False
dashboard = mock.Mock()
dashboard.stop.return_value = True
dashboard.process = UnkillableProcess()
controller = mock.Mock()
context = {
'postgres_controller': controller,
'dashboard_manager': dashboard,
'supervisor_config': {},
}
self.assertFalse(supervisor.coordinated_shutdown([source], context))
controller.request_stop.assert_not_called()
def test_main_propagates_failed_background_stop(self):
args = SimpleNamespace(
config='config.yaml', sources=None, dashboard=False, no_dashboard=False,
background=False, stop_background=True, background_status=False,
attach=False, cmd=None, instance_file=None, pid_file=None,
with_postgres=True,
)
runtime = ({'global': {}}, str(APP_DIR), 'results', {}, [])
with mock.patch.object(supervisor, 'parse_args', return_value=args), \
mock.patch.dict(os.environ, {supervisor.RUNTIME_BOOTSTRAP_ENV: supervisor.RUNTIME_BOOTSTRAP_VALUE}, clear=False), \
mock.patch.object(supervisor, 'sha256_file', return_value='a' * 64), \
mock.patch.object(supervisor, 'preflight_lifecycle_paths'), \
mock.patch.object(supervisor, 'load_supervisor_runtime', return_value=runtime), \
mock.patch.object(supervisor, 'stop_background', return_value=False):
self.assertEqual(supervisor.main(), 1)
def test_preflight_failure_happens_before_secret_load_or_background_log_open(self):
args = SimpleNamespace(
config='config.yaml', sources=None, dashboard=False, no_dashboard=False,
background=True, stop_background=False, background_status=False,
attach=False, cmd=None, instance_file=None, pid_file=None,
with_postgres=True,
)
runtime = ({'global': {}}, str(APP_DIR), 'results', {}, [])
with mock.patch.object(supervisor, 'parse_args', return_value=args), \
mock.patch.dict(os.environ, {supervisor.RUNTIME_BOOTSTRAP_ENV: supervisor.RUNTIME_BOOTSTRAP_VALUE}, clear=False), \
mock.patch.object(supervisor, 'sha256_file', return_value='a' * 64), \
mock.patch.object(supervisor, 'load_supervisor_runtime', return_value=runtime), \
mock.patch.object(supervisor, 'preflight_lifecycle_paths', side_effect=OSError('foreign owner')), \
mock.patch.object(supervisor, 'load_postgres_env') as load_env, \
mock.patch.object(supervisor, 'start_background') as start:
with self.assertRaisesRegex(SystemExit, 'foreign owner'):
supervisor.main()
load_env.assert_not_called()
start.assert_not_called()
def test_duplicate_direct_runtime_exits_before_any_lifecycle_construction(self):
args = SimpleNamespace(
config='config.yaml', sources=None, once=False, dry_run=False,
status_interval=None, no_clear=True, autostart=False, non_interactive=True,
background_child=False, background=False, stop_background=False,
background_status=False, attach=False, cmd=None, instance_file=None,
pid_file=None, dashboard=False, no_dashboard=False, with_postgres=True,
)
config = {
'global': {'queue_dir': 'queues'},
'sources': {'fixture': {'enabled': True}},
'keychecks': {'enabled': False},
}
runtime = (config, str(APP_DIR), 'results', {'dashboard': {'enabled': False}}, ['fixture'])
lock = mock.Mock()
lock.acquire.side_effect = supervisor.InstanceLockError('held')
with mock.patch.object(supervisor, 'parse_args', return_value=args), \
mock.patch.dict(os.environ, {supervisor.RUNTIME_BOOTSTRAP_ENV: supervisor.RUNTIME_BOOTSTRAP_VALUE}, clear=False), \
mock.patch.object(supervisor, 'sha256_file', return_value='a' * 64), \
mock.patch.object(supervisor, 'load_supervisor_runtime', return_value=runtime), \
mock.patch.object(supervisor, 'preflight_lifecycle_paths'), \
mock.patch.object(supervisor, 'validate_managed_runtime_startup'), \
mock.patch.object(supervisor, 'background_paths', return_value=('instance', 'log', 'status', 'pid')), \
mock.patch.object(supervisor, 'background_lock_path', return_value='lock'), \
mock.patch.object(supervisor, 'ClusterAuthorityLock') as cluster_lock, \
mock.patch.object(supervisor, 'SupervisorInstanceLock', return_value=lock), \
mock.patch.object(supervisor, 'ManagedSource') as source, \
mock.patch.object(supervisor, 'controller_from_config') as controller:
with self.assertRaisesRegex(SystemExit, 'duplicate lifecycle-owning'):
supervisor.main()
source.assert_not_called()
controller.assert_not_called()
cluster_lock.return_value.acquire.assert_called_once()
def test_postgres_stop_failed_makes_shutdown_fail(self):
source = mock.Mock(source='source')
source.stop.return_value = True
class Controller:
terminal = True
stop_succeeded = False
detail = 'recovery left running'
state = supervisor.PostgresState.STOP_FAILED
def request_stop(self):
pass
def close(self, wait=False):
pass
context = {'postgres_controller': Controller(), 'supervisor_config': {}}
self.assertFalse(supervisor.coordinated_shutdown([source], context))
class ImmediateExecutor:
def submit(self, function, *args):
from concurrent.futures import Future
future = Future()
try:
future.set_result(function(*args))
except BaseException as exc:
future.set_exception(exc)
return future
class DeferredExecutor:
def __init__(self):
self.futures = []
def submit(self, function, *args):
from concurrent.futures import Future
class NonCancellingFuture(Future):
def cancel(self):
return False
future = NonCancellingFuture()
self.futures.append(future)
return future
def shutdown(self, wait=False, cancel_futures=False):
pass
class Clock:
def __init__(self):
self.now = 0.0
def __call__(self):
return self.now
class DashboardManagerTests(unittest.TestCase):
def test_non_loopback_dashboard_address_is_rejected_even_when_disabled(self):
with self.assertRaisesRegex(ValueError, 'loopback'):
supervisor.dashboard_settings({
'dashboard': {'enabled': False, 'address': '0.0.0.0', 'port': 5000},
})
def test_launch_log_failure_retains_unstoppable_process_ownership(self):
with tempfile.TemporaryDirectory() as temp_dir:
ensure_private_directory(temp_dir, reject_reparse=True)
logs = os.path.join(temp_dir, 'logs')
ensure_private_directory(logs, reject_reparse=True)
config_path = os.path.join(temp_dir, 'config.yaml')
Path(config_path).write_text('{}\n', encoding='ascii')
harden_private_file(config_path)
process = UnkillableProcess()
process.stdout = object()
manager = supervisor.ManagedDashboard(
config_path,
temp_dir,
{'log_dir': logs, 'dashboard': {'enabled': True}},
temp_dir,
temp_dir,
process_factory=lambda *args, **kwargs: process,
)
try:
with mock.patch.object(
supervisor.BoundedRotatingLogPump,
'attach',
side_effect=RuntimeError('fixture attach failure'),
):
self.assertFalse(manager.start(force=True))
self.assertIs(manager.process, process)
self.assertIsNone(process.poll())
self.assertEqual(manager.status, 'failed')
self.assertTrue(manager.fatal_stop_failure)
finally:
if manager.log_pump is not None:
manager.log_pump.close()
manager.log_pump = None
manager.close()
def test_stale_health_future_cannot_mark_replacement_generation(self):
with tempfile.TemporaryDirectory() as temp_dir:
ensure_private_directory(temp_dir, reject_reparse=True)
logs = os.path.join(temp_dir, 'logs')
ensure_private_directory(logs, reject_reparse=True)
config_path = os.path.join(temp_dir, 'config.yaml')
Path(config_path).write_text('{}\n', encoding='ascii')
harden_private_file(config_path)
executor = DeferredExecutor()
manager = supervisor.ManagedDashboard(
config_path,
temp_dir,
{
'log_dir': logs,
'dashboard': {'enabled': True, 'health_interval_sec': 1},
},
temp_dir,
temp_dir,
process_factory=FakeOwnedProcess,
executor=executor,
)
self.assertTrue(manager.start())
manager.poll()
stale_future = executor.futures[0]
stale_generation = manager._future_generation
self.assertTrue(manager.stop())
self.assertTrue(manager.start(force=True))
replacement = manager.process
stale_future.set_result(False)
manager._future = stale_future
manager._future_generation = stale_generation
manager._consume_health(manager._clock())
self.assertIs(manager.process, replacement)
self.assertFalse(replacement.terminated)
self.assertEqual(manager.status, 'pending')
manager.stop()
manager.close()
def test_gate_health_backoff_recovery_and_env_authority(self):
with tempfile.TemporaryDirectory() as temp_dir:
ensure_private_directory(temp_dir, reject_reparse=True)
config_path = os.path.join(temp_dir, 'config.yaml')
Path(config_path).write_text('{}\n', encoding='ascii')
harden_private_file(config_path)
ensure_private_directory(os.path.join(temp_dir, 'logs'), reject_reparse=True)
ensure_private_directory(os.path.join(temp_dir, 'queues'), reject_reparse=True)
canonical = 'postgresql://truf:secret@127.0.0.1:5432/truf'
gate = supervisor.DependencyGate(ready=False, database_url=canonical)
clock = Clock()
probes = iter((False, False, True, True))
created = []
def factory(*args, **kwargs):
process = FakeOwnedProcess(*args, **kwargs)
created.append(process)
return process
manager = supervisor.ManagedDashboard(
config_path,
str(APP_DIR),
{
'log_dir': os.path.join(temp_dir, 'logs'),
'dashboard_log': os.path.join(temp_dir, 'logs', 'dashboard.log'),
'dashboard': {
'enabled': True,
'startup_grace_sec': 5,
'health_interval_sec': 1,
'restart_base_sec': 2,
'stable_health_sec': 0,
'env': {'SCANNER_DB_URL': 'postgresql://attacker/other'},
},
},
temp_dir,
os.path.join(temp_dir, 'queues'),
dependency_gate=gate,
process_factory=factory,
health_probe=lambda _url, _timeout: next(probes),
executor=ImmediateExecutor(),
clock=clock,
)
self.assertFalse(manager.start())
self.assertEqual(manager.status, 'blocked')
self.assertEqual(created, [])
gate.set_ready(True)
self.assertEqual(len(created), 1)
self.assertEqual(created[0].kwargs['env']['SCANNER_DB_URL'], canonical)
manager.poll()
manager.poll()
self.assertEqual(manager.status, 'pending')
clock.now = 6
manager.poll()
manager.poll()
self.assertEqual(manager.status, 'backoff')
self.assertEqual(manager.desired_state, 'running')
self.assertTrue(created[0].terminated)
clock.now = 8
manager.poll()
self.assertEqual(len(created), 2)
manager.poll()
manager.poll()
self.assertTrue(manager.healthy)
self.assertEqual(manager.failures, 0)
gate.set_ready(False)
self.assertEqual(manager.desired_state, 'running')
self.assertEqual(manager.status, 'blocked')
self.assertTrue(created[1].terminated)
gate.set_ready(True)
self.assertEqual(len(created), 3)
manager.poll()
manager.poll()
self.assertTrue(manager.healthy)
self.assertTrue(manager.stop())
manager.close()
if __name__ == '__main__':
unittest.main()