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

767 lines
38 KiB
Python

"""Isolated host tests: python -I -S -B tests/test_container_projection_recovery.py.
Only clone code is compiled; DB, container checks and security locks are mocked.
All filesystem fixtures are under tempfile.tempdir. No real app/PG is started.
"""
import copy
import hashlib
import itertools
import json
import os
from pathlib import Path
import re
import stat
import sys
import tempfile
import traceback
from types import ModuleType, SimpleNamespace
import unittest
from unittest import mock
APP = Path(__file__).resolve().parents[1] / 'app'
PRIVATE = 'private-fixture-credential-never-a-diagnostic'
def load(name, path):
module = ModuleType(name)
module.__file__ = str(path)
exec(compile(path.read_text(encoding='utf-8'), str(path), 'exec'), module.__dict__)
return module
def encoded(value):
return (json.dumps(value, ensure_ascii=True, sort_keys=True, separators=(',', ':')) + '\n').encode('ascii')
def sha(value):
return hashlib.sha256(value).hexdigest()
class Result:
def __init__(self, rows=(), rowcount=0):
self.rows, self.rowcount = rows, rowcount
def fetchone(self):
return copy.deepcopy(self.rows[0]) if self.rows else None
def fetchall(self):
return copy.deepcopy(self.rows)
class FakeConnection:
def __init__(self, module, trace):
self.m, self.trace = module, trace
self.closed, self.broken, self.autocommit = False, False, True
self.info = SimpleNamespace(transaction_status=0)
self.statements = []
self.fail_update = self.fail_commit = None
self.rowcount = 1
self.advisory_available, self.tables_available = True, True
self.other_client_call, self.online_calls = 0, 0
self.corrupt_history = False
self.after_update = None
self.rows = []
specs = [(name, path) for name, path in (
('scan_results', 'scan_results.jsonl'), ('found_secrets', 'found_secrets.jsonl'),
('scan_errors', 'scan_errors.log'))]
specs += [(f'keycheck:p{i}:results', f'p{i}/p{i}Results.jsonl') for i in range(18)]
specs += [(f'keycheck:p{i}:status', f'p{i}/p{i}Checked.txt') for i in range(13)]
for name, path in specs:
found = name == 'found_secrets'
stream = {'stream_name': name, 'base_relative_path': path,
'current_generation': 13 if found else 0, 'rotation_bytes': 134217728,
'max_generations': 16, 'created_at': '2026-09-01T00:00:00+00:00',
'updated_at': '2026-09-15T00:00:00+00:00'}
cursor = {'stream_name': name, 'generation': stream['current_generation'],
'committed_offset': 97783145 if found else 0,
'last_append_id': 38024 if found else None, 'last_job_id': 38024 if found else None,
'last_event_id': 'a' * 32 if found else None, 'last_event_hash': 'b' * 64 if found else None,
'updated_at': '2026-09-15T00:00:01+00:00'}
self.rows.append({'stream_name': name, 'cursor_stream_name': name, 'stream': stream, 'cursor': cursor})
self.last = {'id': 38024, 'job_id': 38024, 'stream_name': 'found_secrets', 'generation': 13,
'byte_offset': 97783000, 'byte_length': 145, 'event_id': 'a' * 32,
'event_hash': 'b' * 64, 'state': 'appended'}
self.gates = {'appends': 38024, 'append_highwater': 13, 'bad_appends': 0,
'rotations': 13, 'rotation_highwater': 13, 'bad_rotations': 0,
'audits': 0, 'pending': 0, 'reservations': 0, 'queue_leases': 0,
'blob_leases': 0, 'triggers': 0, 'rules': 0}
self.counts = {'projection_streams': 34, 'projection_cursors': 34, 'projection_appends': 38024,
'projection_append_audit': 0, 'projection_rotations': 13, 'projection_jobs': 38024,
'pipeline_capacity': 1, 'pipeline_leases': 2, 'findings': 1, 'target_scans': 1,
'target_queue': 1, 'result_reservations': 1, 'result_bundles': 1,
'pipeline_quarantine': 0, 'docker_content_blobs': 1, 'other_history': 1}
self.history = {name: {'fixture': PRIVATE, 'state': name} for name in self.counts}
self.sequence = {'last_value': 38024, 'is_called': True}
self.online = {'database': 'truf', 'user_name': 'truf', 'data_directory': str(module.DATA / 'postgres-linux'),
'port': 5432, 'version_num': 160014, 'system_identifier': '2222222222',
'in_recovery': False, 'superuser': True, 'other_clients': 0,
'schema_name': 'public', 'search_path': 'public', 'read_only': 'off', 'public_create': False,
'fsync': 'on', 'full_page_writes': 'on'}
@property
def found(self):
return next(row for row in self.rows if row['stream_name'] == 'found_secrets')
@property
def updates(self):
return [(sql, params) for sql, params in self.statements if sql.startswith('UPDATE')]
def transaction(self):
db = self
class Transaction:
def __enter__(self):
self.before = copy.deepcopy((db.rows, db.history))
db.info.transaction_status = 2
db.trace.append('begin')
def __exit__(self, kind, value, tb):
db.info.transaction_status = 0
if kind is not None or db.fail_commit == 'before':
db.rows, db.history = self.before
db.trace.append('rollback')
if kind is None:
raise RuntimeError(PRIVATE)
else:
db.trace.append('commit')
if db.fail_commit == 'after':
raise RuntimeError(PRIVATE)
return Transaction()
def cursor(self, name):
db = self
class Cursor:
def __enter__(self):
return self
def __exit__(self, *args):
return False
def execute(self, sql):
db.statements.append((' '.join(sql.split()), ()))
self.table = re.search(r'FROM public\."([a-z_]+)" AS t', sql)[1]
if not sql.endswith(' ORDER BY digest') or 'COLLATE "C" AS digest' not in sql:
raise AssertionError('unbounded or unordered fixture query')
def __iter__(self):
table = self.table
if table in ('projection_streams', 'projection_cursors'):
key = 'stream' if table == 'projection_streams' else 'cursor'
hashes = sorted(sha(encoded(row[key])) for row in db.rows if row['stream_name'] != 'found_secrets')
else:
hashes = itertools.repeat(sha(encoded(db.history[table])), db.counts[table])
return ({'digest': value} for value in hashes)
return Cursor()
def execute(self, sql, params=()):
sql = ' '.join(sql.split())
self.statements.append((sql, params))
if sql.startswith('SET ') or sql == 'SELECT pg_catalog.pg_stat_clear_snapshot()':
return Result()
if 'AS other_clients' in sql:
self.online_calls += 1
row = dict(self.online)
if self.online_calls == self.other_client_call:
row['other_clients'] = 1
return Result([row])
if 'pg_try_advisory_xact_lock' in sql:
return Result([{'locked': self.advisory_available}])
if 'c.relkind AS kind' in sql:
return Result([{'name': name, 'kind': 'r'} for name in sorted(self.counts)])
if sql.startswith('LOCK TABLE '):
if not self.tables_available:
raise RuntimeError(PRIVATE)
if not sql.endswith(' IN EXCLUSIVE MODE NOWAIT'):
raise AssertionError('missing writer exclusion')
return Result()
if 'AS bad_appends' in sql:
return Result([self.gates])
if 'pg_catalog.row_to_json(s) AS stream' in sql:
return Result(self.rows)
if "c.relkind = 'S'" in sql:
return Result([{'name': 'fixture_sequence'}])
if sql.startswith('SELECT last_value, is_called'):
return Result([self.sequence])
if 'FROM public.projection_appends WHERE id = %s' in sql:
return Result([self.last] if params[0] == self.last['id'] else [])
if sql.startswith('UPDATE public.projection_'):
key = 'stream' if 'projection_streams AS s' in sql else 'cursor'
self.trace.append('update-' + key)
if self.fail_update == key:
raise RuntimeError(PRIVATE)
if json.loads(params[1]) != self.found[key]:
return Result(rowcount=0)
if self.rowcount != 1:
return Result(rowcount=self.rowcount)
if key == 'stream':
self.found[key].update(current_generation=14, updated_at=params[0])
else:
self.found[key].update(generation=14, committed_offset=0, last_append_id=None, updated_at=params[0])
if self.corrupt_history:
self.history['findings']['state'] = 'unintended mutation'
if self.after_update is not None:
self.after_update()
return Result(rowcount=1)
raise AssertionError('unexpected fixture SQL')
class ProjectionRecoveryTests(unittest.TestCase):
def setUp(self):
self.temporary = tempfile.TemporaryDirectory(prefix='truf-projection-recovery-test-')
self.addCleanup(self.temporary.cleanup)
self.root = Path(self.temporary.name)
self.m = load('projection_recovery_under_test', APP / 'container_projection_recovery.py')
self.importer = load('container_import_fixture', APP / 'container_import.py')
self.m.DATA, self.m.RUN = self.root / 'data', self.root / 'run'
self.trace = []
self.importer.os = SimpleNamespace(**vars(os))
for key in ('O_NOFOLLOW', 'O_DIRECTORY', 'O_NONBLOCK'):
setattr(self.importer.os, key, getattr(os, key, 0))
if os.name == 'nt':
fingerprint = self.importer._fingerprint
self.importer._fingerprint = lambda info: fingerprint(info)[:-1] + (0,)
self.importer._fsync_dir = lambda path: self.trace.append('directory-fsync')
self.m.os = SimpleNamespace(**vars(os))
self.m.os.fsync = lambda fd: self.trace.append('journal-fsync')
self.importer.subprocess = SimpleNamespace(Popen=mock.Mock(side_effect=AssertionError('real processes forbidden')))
for path in (self.m.DATA, self.m.DATA / 'config', self.m.DATA / 'runtime-linux/results',
self.m.DATA / 'runtime-linux/postgres', self.m.DATA / 'runtime-linux/logs',
self.m.RUN / 'control', self.m.RUN / 'authority'):
path.mkdir(mode=0o700, parents=True, exist_ok=True)
self.denied = set()
def private(path, *, directory=False):
path = Path(path)
if not path.is_relative_to(self.root) or path in self.denied:
raise RuntimeError(PRIVATE)
if path.is_symlink() or not (path.is_dir() if directory else path.is_file()):
raise RuntimeError(PRIVATE)
return path
self.runtime = SimpleNamespace(DATA=self.m.DATA, RUN=self.m.RUN,
INITIALIZED=self.m.DATA / 'initialized.json',
INITIALIZE_LOCK=self.m.DATA / 'initialize.lock',
private_path=private, require_container=mock.Mock(), _shutdown_requested=False)
outer = self
class FileLock:
fail_projector = False
def __init__(self, path):
self.path = os.path.normcase(str(path))
self.acquired = False
self.releases = 0
def __enter__(self):
if self.fail_projector:
raise RuntimeError(PRIVATE)
Path(self.path).touch(exist_ok=True)
self.acquired = True
outer.trace.append('projector-lock')
return self
def __exit__(self, *args):
self.acquired = False
self.releases += 1
outer.trace.append('projector-unlock')
class Authority:
def __init__(self):
self.acquired, self.releases = True, 0
self.data_directory = str(outer.m.DATA / 'postgres-linux')
self.endpoint_identity = encoded({'database': 'truf', 'host': '127.0.0.1', 'port': 5432,
'schema': 'public'}).decode('ascii').strip()
self.path = str(outer.m.DATA / 'runtime-linux/postgres' /
('.cluster-authority-' + sha(self.data_directory.encode('utf-8')) + '.lock'))
self.endpoint_path = str(outer.m.RUN / 'authority' /
('endpoint-' + sha(self.endpoint_identity.encode('ascii')) + '.lock'))
self.security = SimpleNamespace(PrivateFileLock=FileLock, ClusterAuthorityLock=Authority)
self.initialize_lock = FileLock(self.runtime.INITIALIZE_LOCK)
Path(self.initialize_lock.path).touch()
self.initialize_lock.acquired = True
self.authority_lock = Authority()
modules = mock.patch.dict(sys.modules, {'container_import': self.importer, 'runtime_security': self.security})
modules.start()
self.addCleanup(modules.stop)
self.db = FakeConnection(self.m, self.trace)
self.identity_path = self.m.DATA / 'runtime-linux/postgres/cluster_identity.json'
self.identity = {'pg_major': 16, 'system_identifier': '2222222222', 'data_directory': str(self.m.DATA / 'postgres-linux'),
'database': 'truf', 'user': 'truf', 'port': 5432}
self.identity_path.write_bytes(encoded(self.identity))
manifest = {'format': 'truf-windows-snapshot-v1',
'source': {'root': r'D:\truf', 'postgres_data_dir': r'S:\postgres-data',
'supervisor_stopped': True, 'postgres_stopped': True},
'database': {'version_num': 160014, 'system_identifier': '1111111111',
'database_name': 'source_fixture', 'user_name': 'source_fixture', 'port': 15432,
'data_directory': r'S:\postgres-data', 'bytes': 6, 'sha256': '1' * 64,
'table_counts': self.db.counts,
'sequence_states': {'public': {'fixture_sequence': self.db.sequence}}, 'sequence_count': 1},
'archive': {'bytes': 10240, 'sha256': '2' * 64},
'files': [{'path': path, 'size': 0, 'sha256': sha(b'')} for path in sorted(self.importer.REQUIRED_FILES)]}
self.manifest_path = self.m.DATA / 'config/windows-import-manifest.json'
raw = encoded(manifest)
self.manifest_path.write_bytes(raw)
# Test-only in-memory fixture pin; the production module keeps its reviewed pin.
self.m.APPROVED_MANIFEST_SHA256 = sha(raw)
self.journal = self.m.DATA / 'config' / self.m.JOURNAL_NAME
def call(self, **overrides):
args = {'system_identifier': '2222222222', 'manifest_sha256': self.m.APPROVED_MANIFEST_SHA256,
'initialize_lock': self.initialize_lock, 'authority_lock': self.authority_lock}
args.update(overrides)
return self.m.recover_found_secrets_projection(self.runtime, self.db, **args)
def assert_held(self):
self.assertTrue(self.initialize_lock.acquired)
self.assertTrue(self.authority_lock.acquired)
self.assertEqual((self.initialize_lock.releases, self.authority_lock.releases), (0, 0))
self.assertFalse(self.db.closed)
self.importer.subprocess.Popen.assert_not_called()
def prepare_without_commit(self):
self.db.fail_update = 'cursor'
with self.assertRaises(self.m.ProjectionRecoveryError):
self.call()
self.db.fail_update = None
self.db.statements.clear()
self.assertTrue(self.journal.is_file())
def test_success_changes_only_two_metadata_rows_and_journals_before_commit(self):
before_rows, before_history = copy.deepcopy(self.db.rows), copy.deepcopy(self.db.history)
last = copy.deepcopy(self.db.last)
result = self.call()
self.assertEqual(result['status'], 'committed')
self.assertEqual(result['journal_sha256'], sha(self.journal.read_bytes()))
saved = json.loads(self.journal.read_bytes())
record = saved['record']
self.assertEqual(saved['sha256'], sha(self.m._encoded(record)))
self.assertEqual(record['state'], 'PREPARED')
self.assertEqual(record['binding']['system_identifier'], '2222222222')
self.assertEqual(record['binding']['manifest_sha256'], self.m.APPROVED_MANIFEST_SHA256)
self.assertIn('No rotation or rename occurred', record['loss'])
self.assertEqual(record['before'], {key: before_rows[1][key] for key in ('stream', 'cursor')})
self.assertEqual(record['after'], {key: self.db.found[key] for key in ('stream', 'cursor')})
self.assertEqual(self.db.rows[:1] + self.db.rows[2:], before_rows[:1] + before_rows[2:])
self.assertEqual(self.db.history, before_history)
self.assertEqual(self.db.last, last)
for key in ('last_job_id', 'last_event_id', 'last_event_hash'):
self.assertEqual(self.db.found['cursor'][key], before_rows[1]['cursor'][key])
self.assertEqual(len(self.db.updates), 2)
for setting in ("SET LOCAL statement_timeout = '10800s'", "SET LOCAL temp_file_limit = '4GB'",
"SET LOCAL work_mem = '128MB'", 'SET LOCAL max_parallel_workers_per_gather = 0'):
self.assertIn((setting, ()), self.db.statements)
self.assertTrue(all('pg_catalog.to_jsonb' in sql for sql, _ in self.db.updates))
self.assertFalse(any(sql.startswith(('INSERT', 'DELETE', 'ALTER', 'CREATE', 'DROP', 'TRUNCATE'))
for sql, _ in self.db.statements))
self.assertLess(self.trace.index('directory-fsync'), self.trace.index('update-stream'))
self.assertLess(self.trace.index('journal-fsync'), self.trace.index('update-stream'))
self.assertLess(self.trace.index('update-cursor'), self.trace.index('commit'))
self.assertFalse(self.runtime.INITIALIZED.exists())
self.assertFalse((self.m.DATA / 'runtime-linux/results/found_secrets.jsonl').exists())
self.assertNotIn(PRIVATE, self.journal.read_text(encoding='ascii'))
self.assert_held()
def test_exact_after_retry_has_no_sql_rewrites_or_journal_replacement(self):
first = self.call()
before, metadata = self.journal.read_bytes(), self.journal.stat()
rows, history = copy.deepcopy(self.db.rows), copy.deepcopy(self.db.history)
self.db.statements.clear()
second = self.call()
self.assertEqual(second['status'], 'already-committed')
self.assertEqual(first['journal_sha256'], second['journal_sha256'])
self.assertEqual(self.db.updates, [])
self.assertEqual((self.db.rows, self.db.history), (rows, history))
self.assertEqual(self.journal.read_bytes(), before)
self.assertEqual(self.journal.stat().st_mtime_ns, metadata.st_mtime_ns)
self.assert_held()
def test_failure_after_journal_rolls_back_then_reuses_exact_intent(self):
before = copy.deepcopy(self.db.rows)
self.prepare_without_commit()
self.assertEqual(self.db.rows, before)
raw = self.journal.read_bytes()
self.assertEqual(self.call()['status'], 'committed')
self.assertEqual(self.journal.read_bytes(), raw)
self.assert_held()
def test_commit_failures_are_reconciled_from_journal_without_guessing(self):
for when in ('before', 'after'):
with self.subTest(commit_failure=when):
self.setUp_commit_case(when)
def setUp_commit_case(self, when):
before = copy.deepcopy(self.db.rows)
self.db.fail_commit = when
with self.assertRaises(self.m.ProjectionRecoveryError) as caught:
self.call()
self.assertNotIn(PRIVATE, str(caught.exception))
self.assertTrue(self.journal.exists())
self.assert_held()
self.db.fail_commit = None
self.db.statements.clear()
result = self.call()
self.assertEqual(result['status'], 'committed' if when == 'before' else 'already-committed')
self.assertEqual(len(self.db.updates), 2 if when == 'before' else 0)
# Reset only this synthetic fixture for the second independent crash window.
self.db.rows = before
self.db.rows[1]['stream']['current_generation'] = 13
self.db.rows[1]['cursor'].update(generation=13, committed_offset=97783145, last_append_id=38024)
self.journal.unlink()
def test_partial_journal_is_never_repaired_overwritten_or_committed(self):
def partial(runtime, path, payload):
with path.open('xb') as handle:
handle.write(payload[:50])
raise OSError(PRIVATE)
with mock.patch.object(self.importer, '_write', side_effect=partial), self.assertRaises(self.m.ProjectionRecoveryError):
self.call()
raw = self.journal.read_bytes()
self.assertEqual(self.db.updates, [])
with self.assertRaises(self.m.ProjectionRecoveryError):
self.call()
self.assertEqual(self.journal.read_bytes(), raw)
self.assertEqual(self.db.found['cursor']['committed_offset'], 97783145)
def test_complete_but_unsynced_journal_requires_confirmed_fsync_on_retry(self):
with mock.patch.object(self.importer, '_fsync_dir', side_effect=OSError(PRIVATE)), self.assertRaises(self.m.ProjectionRecoveryError):
self.call()
self.assertEqual(self.db.updates, [])
self.assertTrue(self.journal.exists())
with mock.patch.object(self.m.os, 'fsync', side_effect=OSError(PRIVATE)), self.assertRaises(self.m.ProjectionRecoveryError):
self.call()
self.assertEqual(self.db.updates, [])
self.assertEqual(self.call()['status'], 'committed')
def test_malformed_or_mismatched_journals_are_refused(self):
self.prepare_without_commit()
original = self.journal.read_bytes()
for case in ('truncated', 'whitespace', 'duplicate', 'checksum', 'binding', 'proof', 'after', 'before', 'state'):
value = json.loads(original)
if case == 'truncated':
raw = original[:60]
elif case == 'whitespace':
raw = original + b' '
elif case == 'duplicate':
raw = b'{"record":{},' + original[1:]
else:
record = value['record']
if case == 'binding':
record['binding']['system_identifier'] = '3333333333'
elif case == 'proof':
record['preserved']['tables']['findings']['sha256'] = '0' * 64
elif case == 'after':
record['after']['cursor']['committed_offset'] = 1
elif case == 'before':
record['before']['cursor']['last_job_id'] += 1
elif case == 'state':
record['state'] = 'COMMITTED'
value['sha256'] = '0' * 64 if case == 'checksum' else sha(encoded(record))
raw = encoded(value)
self.journal.write_bytes(raw)
self.db.statements.clear()
with self.subTest(case=case), self.assertRaises(self.m.ProjectionRecoveryError):
self.call()
self.assertEqual(self.db.updates, [])
self.assertEqual(self.journal.read_bytes(), raw)
def test_future_metadata_or_output_never_gets_rewound(self):
self.call()
stable = copy.deepcopy(self.db.rows)
for change in ({'committed_offset': 1}, {'last_event_id': 'later'}, {'generation': 15}):
self.db.rows = copy.deepcopy(stable)
self.db.found['cursor'].update(change)
if 'generation' in change:
self.db.found['stream']['current_generation'] = 15
before = copy.deepcopy(self.db.rows)
self.db.statements.clear()
with self.subTest(change=change), self.assertRaises(self.m.ProjectionRecoveryError):
self.call()
self.assertEqual(self.db.rows, before)
self.assertEqual(self.db.updates, [])
self.db.rows = stable
path = self.m.DATA / 'runtime-linux/results/found_secrets.jsonl'
path.write_bytes(b'future fixture output')
with self.assertRaises(self.m.ProjectionRecoveryError):
self.call()
self.assertEqual(path.read_bytes(), b'future fixture output')
def test_after_state_without_journal_is_refused(self):
self.call()
self.journal.unlink()
self.db.statements.clear()
with self.assertRaises(self.m.ProjectionRecoveryError):
self.call()
self.assertFalse(self.journal.exists())
self.assertEqual(self.db.updates, [])
def test_all_history_and_highwater_gates_refuse_drift(self):
original = dict(self.db.gates)
for key in original:
self.db.gates = dict(original)
self.db.gates[key] += 1
with self.subTest(gate=key), self.assertRaises(self.m.ProjectionRecoveryError):
self.call()
self.assertEqual(self.db.updates, [])
self.assertFalse(self.journal.exists())
def test_last_cursor_reference_must_match_retained_append(self):
original = dict(self.db.last)
for key, value in (('id', 1), ('job_id', 1), ('event_id', 'wrong'), ('event_hash', 'c' * 64),
('generation', 12), ('byte_offset', 0), ('byte_length', 1), ('state', 'prepared')):
self.db.last = dict(original)
self.db.last[key] = value
with self.subTest(field=key), self.assertRaises(self.m.ProjectionRecoveryError):
self.call()
self.assertEqual(self.db.updates, [])
self.assertFalse(self.journal.exists())
def test_coverage_and_reviewed_metadata_are_strict(self):
original = copy.deepcopy(self.db.rows)
for case in ('duplicate', 'missing', 'orphan', 'mismatch', 'path', 'generation', 'offset', 'null reference', 'negative', 'bool'):
self.db.rows = copy.deepcopy(original)
if case == 'duplicate':
self.db.rows.append(copy.deepcopy(self.db.rows[-1]))
elif case == 'missing':
self.db.rows[-1]['cursor_stream_name'] = None
self.db.rows[-1]['cursor'] = None
elif case == 'orphan':
self.db.rows[-1]['stream_name'] = None
self.db.rows[-1]['stream'] = None
elif case == 'mismatch':
self.db.rows[-1]['cursor_stream_name'] = 'other'
elif case == 'path':
self.db.found['stream']['base_relative_path'] = '../found_secrets.jsonl'
elif case == 'generation':
self.db.found['stream']['current_generation'] = 14
self.db.found['cursor']['generation'] = 14
elif case == 'offset':
self.db.found['cursor']['committed_offset'] -= 1
elif case == 'null reference':
self.db.found['cursor']['last_append_id'] = None
else:
self.db.found['cursor']['committed_offset'] = -1 if case == 'negative' else True
with self.subTest(case=case), self.assertRaises(self.m.ProjectionRecoveryError):
self.call()
self.assertEqual(self.db.updates, [])
def test_family_absence_includes_generations_manifest_ledger_and_links(self):
results = self.m.DATA / 'runtime-linux/results'
for name in ('found_secrets', 'found_secrets.jsonl', 'found_secrets.g000013.jsonl',
'found_secrets.manifest.json', 'found_secrets.publication-ledger.sqlite3',
'Found_Secrets.publication-ledger.sqlite3-wal'):
path = results / name
path.write_bytes(b'retained fixture must not be deleted')
with self.subTest(name=name), self.assertRaises(self.m.ProjectionRecoveryError):
self.call()
self.assertEqual(path.read_bytes(), b'retained fixture must not be deleted')
path.unlink()
self.assertEqual(self.db.updates, [])
def test_private_paths_and_journal_links_are_rejected(self):
self.denied.add(self.m.DATA / 'config')
with self.assertRaises(self.m.ProjectionRecoveryError):
self.call()
self.denied.clear()
self.prepare_without_commit()
real_lstat = Path.lstat
def linked(path, *args, **kwargs):
value = real_lstat(path, *args, **kwargs)
if path == self.journal:
return SimpleNamespace(st_mode=stat.S_IFLNK, st_file_attributes=0)
return value
with mock.patch.object(Path, 'lstat', linked), self.assertRaises(self.m.ProjectionRecoveryError):
self.call()
self.assertEqual(self.db.updates, [])
os.link(self.journal, self.root / 'journal-hardlink')
with self.assertRaises(self.m.ProjectionRecoveryError):
self.call()
self.assertEqual(self.journal.stat().st_nlink, 2)
self.assertEqual(self.db.updates, [])
def test_initialized_or_supervisor_markers_block_every_call(self):
for path in (self.runtime.INITIALIZED, self.m.RUN / 'control/supervisor.instance.json',
self.m.DATA / 'runtime-linux/logs/supervisor.pid'):
path.write_bytes(b'fixture marker')
with self.subTest(path=path.name), self.assertRaises(self.m.ProjectionRecoveryError):
self.call()
self.assertEqual(path.read_bytes(), b'fixture marker')
path.unlink()
self.assertEqual(self.db.updates, [])
def test_bindings_and_connection_mode_cannot_be_relaxed(self):
for args in ({'manifest_sha256': '0' * 64}, {'system_identifier': '1111111111'},
{'system_identifier': 'not-an-identity'}):
with self.subTest(args=args), self.assertRaises(self.m.ProjectionRecoveryError):
self.call(**args)
for attribute, bad in (('autocommit', False), ('closed', True), ('broken', True)):
old = getattr(self.db, attribute)
setattr(self.db, attribute, bad)
with self.subTest(attribute=attribute), self.assertRaises(self.m.ProjectionRecoveryError):
self.call()
setattr(self.db, attribute, old)
self.db.info.transaction_status = 2
with self.assertRaises(self.m.ProjectionRecoveryError):
self.call()
self.assertEqual(self.db.updates, [])
def test_bound_file_and_manifest_tampering_are_rejected(self):
self.manifest_path.write_bytes(self.manifest_path.read_bytes() + b' ')
with self.assertRaises(self.m.ProjectionRecoveryError):
self.call()
self.assertEqual(self.db.updates, [])
def test_outer_lock_objects_are_required_and_never_released(self):
self.initialize_lock.acquired = False
with self.assertRaises(self.m.ProjectionRecoveryError):
self.call()
self.initialize_lock.acquired = True
self.authority_lock.data_directory = 'not-the-bound-target'
with self.assertRaises(self.m.ProjectionRecoveryError):
self.call()
self.assertEqual(self.db.updates, [])
self.assert_held()
def test_live_clients_foreign_identity_schema_and_recovery_are_refused(self):
baseline = dict(self.db.online)
for key, value in (('other_clients', 1), ('system_identifier', '3333333333'), ('data_directory', '/foreign'),
('database', 'foreign'), ('port', 15432), ('version_num', 170001),
('in_recovery', True), ('superuser', False), ('search_path', 'public,other'),
('schema_name', 'other'), ('read_only', 'on'), ('public_create', True),
('fsync', 'off'), ('full_page_writes', 'off')):
self.db.online = dict(baseline)
self.db.online[key] = value
with self.subTest(field=key), self.assertRaises(self.m.ProjectionRecoveryError):
self.call()
self.assertEqual(self.db.updates, [])
def test_advisory_file_and_table_writer_conflicts_fail_closed(self):
self.db.advisory_available = False
with self.assertRaises(self.m.ProjectionRecoveryError):
self.call()
self.db.advisory_available, self.db.tables_available = True, False
with self.assertRaises(self.m.ProjectionRecoveryError):
self.call()
self.db.tables_available = True
self.security.PrivateFileLock.fail_projector = True
with self.assertRaises(self.m.ProjectionRecoveryError):
self.call()
self.assertEqual(self.db.updates, [])
self.assert_held()
def test_new_client_at_precommit_rolls_back_with_prepared_journal(self):
before = copy.deepcopy(self.db.rows)
self.db.other_client_call = 3
with self.assertRaises(self.m.ProjectionRecoveryError):
self.call()
self.assertEqual(self.db.rows, before)
self.assertTrue(self.journal.exists())
self.assertIn('rollback', self.trace)
self.assert_held()
def test_cancel_or_new_output_at_precommit_rolls_back_without_deleting_files(self):
before = copy.deepcopy(self.db.rows)
self.runtime._shutdown_requested = True
with self.assertRaises(self.m.ProjectionRecoveryError):
self.call()
self.assertFalse(self.journal.exists())
self.runtime._shutdown_requested = False
self.db.after_update = lambda: setattr(self.runtime, '_shutdown_requested', True)
with self.assertRaises(self.m.ProjectionRecoveryError):
self.call()
self.assertEqual(self.db.rows, before)
self.runtime._shutdown_requested = False
path = self.m.DATA / 'runtime-linux/results/found_secrets.jsonl'
self.db.after_update = lambda: path.write_bytes(b'external fixture output')
with self.assertRaises(self.m.ProjectionRecoveryError):
self.call()
self.assertEqual(self.db.rows, before)
self.assertEqual(path.read_bytes(), b'external fixture output')
self.assert_held()
def test_unknown_tables_counts_and_late_catalog_changes_are_refused(self):
self.db.counts['unexpected_table'] = 0
with self.assertRaises(self.m.ProjectionRecoveryError):
self.call()
del self.db.counts['unexpected_table']
self.db.counts['findings'] += 1
with self.assertRaises(self.m.ProjectionRecoveryError):
self.call()
self.db.counts['findings'] -= 1
self.assertFalse(self.journal.exists())
before = copy.deepcopy(self.db.rows)
self.db.after_update = lambda: self.db.counts.update(unexpected_table=0)
with self.assertRaises(self.m.ProjectionRecoveryError):
self.call()
self.assertEqual(self.db.rows, before)
self.assertTrue(self.journal.exists())
def test_bound_identity_and_source_identity_must_remain_independent(self):
original = self.identity_path.read_bytes()
for field, value in (('system_identifier', '3333333333'), ('data_directory', '/foreign'),
('database', 'foreign'), ('user', 'foreign'), ('port', 15432), ('pg_major', 17)):
invalid = dict(self.identity)
invalid[field] = value
self.identity_path.write_bytes(encoded(invalid))
with self.subTest(field=field), self.assertRaises(self.m.ProjectionRecoveryError):
self.call()
self.identity_path.write_bytes(encoded(dict(self.identity, system_identifier='1111111111')))
with self.assertRaises(self.m.ProjectionRecoveryError):
self.call(system_identifier='1111111111')
self.identity_path.write_bytes(original)
before = copy.deepcopy(self.db.rows)
self.db.after_update = lambda: self.identity_path.write_bytes(original + b' ')
with self.assertRaises(self.m.ProjectionRecoveryError):
self.call()
self.assertEqual(self.db.rows, before)
self.assert_held()
def test_wrong_affected_row_count_and_history_side_effects_roll_back(self):
before_rows, before_history = copy.deepcopy(self.db.rows), copy.deepcopy(self.db.history)
for count in (0, 2):
self.db.rowcount = count
with self.subTest(rowcount=count), self.assertRaises(self.m.ProjectionRecoveryError):
self.call()
self.assertEqual(self.db.rows, before_rows)
self.db.rowcount, self.db.corrupt_history = 1, True
with self.assertRaises(self.m.ProjectionRecoveryError):
self.call()
self.assertEqual(self.db.rows, before_rows)
self.assertEqual(self.db.history, before_history)
self.assert_held()
def test_retry_refuses_protected_history_or_sequence_drift(self):
self.prepare_without_commit()
self.db.history['pipeline_leases']['state'] = 'changed by another operation'
with self.assertRaises(self.m.ProjectionRecoveryError):
self.call()
self.db.history['pipeline_leases']['state'] = 'pipeline_leases'
self.db.sequence['last_value'] += 1
with self.assertRaises(self.m.ProjectionRecoveryError):
self.call()
self.assertEqual(self.db.updates, [])
def test_raw_exception_payloads_are_not_diagnostics(self):
self.db.fail_update = 'cursor'
try:
self.call()
except self.m.ProjectionRecoveryError as exc:
diagnostic = ''.join(traceback.format_exception(type(exc), exc, exc.__traceback__))
self.assertNotIn(PRIVATE, diagnostic)
self.assertIn('retain caller maintenance authority', str(exc))
else:
self.fail('fixture error was not refused')
self.assert_held()
if __name__ == '__main__':
unittest.main()