fix: repair chained imports and track blocked evidence dependencies
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s

Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a1046e-465c-7d43-8c4c-d10f0a1feadd
This commit is contained in:
tegwick 2026-10-04 11:36:06 +02:00
parent 28b567e588
commit 146f4e1dd3
21 changed files with 869 additions and 95 deletions

View file

@ -168,3 +168,115 @@ def test_empty_source_migrates_cleanly(tmp_path, destination):
assert report.events_read == 0
assert report.events_written == 0
assert report.ok is True
def test_historical_import_extends_existing_chain_and_preserves_attestation(
populated_sqlite, destination
):
path, _ = populated_sqlite
event = make_event('native-before-import')
destination.accept(event, 'native-hash')
attestation = destination.attest_chain()
old_link = destination._query(
f"SELECT chain_hash FROM {destination._events} WHERE event_id = %s",
(event.event_id,),
)[0][0]
report = migrate(path, destination)
assert report.ok
assert destination.verify_chain(attestation).intact
assert destination.verify_chain(attestation).attestation_match
assert destination._query(
f"SELECT chain_hash FROM {destination._events} WHERE event_id = %s",
(event.event_id,),
)[0][0] == old_link
destination.accept(make_event('native-after-import'), 'after-hash')
assert destination.verify_chain(attestation).intact
def test_replaying_side_tables_does_not_duplicate_evidence(populated_sqlite, destination):
path, _ = populated_sqlite
migrate(path, destination, source_id='stable-source')
replay = migrate(path, destination, source_id='stable-source')
assert replay.ok
assert replay.dead_letters_written == replay.findings_written == 0
assert len(destination.dead_letters()) == 1
assert destination.secret_findings()[0]['occurrences'] == 1
def test_incremental_finding_import_adds_only_new_occurrences(populated_sqlite, destination):
from audit_core.redaction import Finding
path, source = populated_sqlite
migrate(path, destination)
source.count_secret_findings(
sender='user-engine', source='user-engine', action='membership.added',
outcome='redacted', findings=[Finding('data.auth_token', True)],
)
assert migrate(path, destination).ok
assert migrate(path, destination).ok
assert destination.secret_findings()[0]['occurrences'] == 2
def test_import_and_native_accept_share_one_chain_lock(populated_sqlite, destination):
from concurrent.futures import ThreadPoolExecutor
path, _ = populated_sqlite
with ThreadPoolExecutor(max_workers=3) as executor:
futures = [executor.submit(migrate, path, destination) for _ in range(2)]
futures.append(executor.submit(destination.accept, make_event('concurrent'), 'native'))
results = [future.result(timeout=20) for future in futures]
assert all(result.ok for result in results[:2])
assert sum(result.events_written for result in results[:2]) == 3
assert destination.verify_chain().intact
assert destination.verify_chain().events == 4
assert len(destination.dead_letters()) == 1
assert destination.secret_findings()[0]['occurrences'] == 1
def test_matching_hash_does_not_hide_a_different_acceptance_time(populated_sqlite, destination):
path, source = populated_sqlite
migrate(path, destination)
source.db.execute("UPDATE events SET accepted_at = '2020-01-01T00:00:00+00:00' WHERE event_id = 'legacy-1'")
report = migrate(path, destination)
assert not report.ok
assert any('different historical evidence' in conflict for conflict in report.conflicts)
assert destination.verify_chain().intact
def test_failed_import_rolls_back_events_and_replay_receipts(
populated_sqlite, destination, monkeypatch
):
import audit_core.migrate_store as importer
import psycopg
from audit_core.interface import BackendUnavailableError
path, _ = populated_sqlite
original = importer._copy_findings
def fail_after_side_tables(*args):
original(*args)
raise psycopg.OperationalError('injected import failure')
monkeypatch.setattr(importer, '_copy_findings', fail_after_side_tables)
with pytest.raises(BackendUnavailableError):
migrate(path, destination)
assert destination.verify_chain().events == 0
assert destination.dead_letters() == []
assert destination.secret_findings() == []
assert destination._query(f'SELECT count(*) FROM "{destination.schema}".import_receipts', ())[0][0] == 0
monkeypatch.setattr(importer, '_copy_findings', original)
assert migrate(path, destination).ok
def test_source_relocation_with_stable_id_preserves_side_table_replay(
populated_sqlite, destination, tmp_path
):
path, _ = populated_sqlite
# Backup captures WAL-backed source data too.
import sqlite3
moved = str(tmp_path / 'moved.db')
with sqlite3.connect(path) as source, sqlite3.connect(moved) as copy:
source.backup(copy)
migrate(path, destination, source_id='same-source')
replay = migrate(moved, destination, source_id='same-source')
assert replay.ok
assert replay.events_written == replay.dead_letters_written == replay.findings_written == 0