frontend-patterns/tests/test_reference_consume.py
tegwick bd85fcd179 Add verified reference lookup and immutable consumer receipts
Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a1045b-933d-7aa1-b2db-104557487a79
2026-10-04 09:02:35 +02:00

176 lines
8.3 KiB
Python

"""Exercise qualified lookup, immutable event production and owner compatibility."""
from copy import deepcopy
import hashlib
import json
from pathlib import Path
import shutil
import subprocess
import sys
import pytest
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / 'scripts'))
import reference_consume as consume
@pytest.fixture(scope='module')
def published():
return consume.published()
@pytest.fixture
def decision():
return json.loads((ROOT / 'feedback/decision-input.example.json').read_text())
def test_lookup_explains_filters_and_preserves_exact_read_revisions(published):
index, pin, manifest = published
result = consume.lookup(index, pin, manifest, ['CAND-0007'], ['recoverability'])
candidate, = result['candidates']
assert result['status'] == 'matches'
assert candidate['id'] == 'CAND-0007'
assert candidate['classification']['forces']['worsens']
assert candidate['gaps'] and candidate['maturity'] == 'hypothesis'
assert candidate['contract']['revision'] == manifest['corpus_revision']
assert f'/commit/{manifest["corpus_revision"]}/' in candidate['contract']['url']
assert all(e['locator']['revision'] == pin['packaging_revision'] for e in candidate['evidence'])
assert all(e['status'] == 'proposed' and e['condition'] and e['unknown'] for e in result['relations'])
assert all(v['revision'] == pin['packaging_revision'] for v in result['entrypoints'].values())
assert result['reference']['manifest_sha256'] == pin['manifest_sha256']
assert consume.lookup(index, pin, manifest, ['CAND-0007'], ['telemetry-quality'])['status'] == 'no-match'
def test_missing_context_abstains_and_responsibility_filters_intersect(published):
index, pin, manifest = published
result = consume.lookup(index, pin, manifest)
assert result['status'] == 'needs-context' and result['candidates'] == result['relations'] == []
result = consume.lookup(index, pin, manifest, responsibilities=['mutation'], forces=['recoverability'])
assert result['candidates']
assert all('mutation' in c['classification']['responsibilities'] and
'recoverability' in c['classification']['forces']['improves'] for c in result['candidates'])
@pytest.mark.parametrize('filters', [
{'candidates': ['CAND-0018']}, {'forces': ['magic']}, {'responsibilities': ['magic']},
])
def test_unknown_or_unpublished_identity_is_not_silently_selected(published, filters):
with pytest.raises(ValueError, match='Unknown'):
consume.lookup(*published, **filters)
def test_live_research_is_independent_and_tampered_export_fails(tmp_path, published):
shutil.copytree(ROOT / 'reference', tmp_path / 'reference')
(tmp_path / '.git').symlink_to(ROOT / '.git', target_is_directory=True)
live = tmp_path / 'research/candidates/MutationOutcomeTracking.md'
live.parent.mkdir(parents=True)
live.write_text('Legitimate later research outside the frozen release.\n')
assert consume.published(tmp_path) == published
export = tmp_path / 'reference/0.1/index.json'
export.write_text('{}\n')
with pytest.raises(ValueError, match='Published export changed'):
consume.published(tmp_path)
with pytest.raises(ValueError, match='Unknown reference version'):
consume.published(tmp_path, '9.9')
def test_receipt_keeps_unknown_outcome_and_pins_verified_reference(published, decision):
index, pin, _ = published
original = deepcopy(decision)
event = consume.receipt(decision, index, pin)
assert decision == original
assert event['kind'] == 'synthetic'
assert event['outcome']['value'] == 'unknown'
assert event['outcome']['observation'] is None
assert event['outcome']['measured_decision_minutes'] is None
assert event['reference']['packaging_revision'] == pin['packaging_revision']
assert 'origin' not in event
@pytest.mark.parametrize('mutate', [
lambda d: d.update(kind=''),
lambda d: d.update(recorded_at='2999-01-01T00:00:00Z'),
lambda d: d.update(recorded_at='2026-01-01T00:00:00'),
lambda d: d.update(previous_event_sha256='invalid'),
lambda d: d.update(origin={'verified': True}),
lambda d: d['consumer'].update(revision='main'),
lambda d: d['consumer'].update(run_ref=''),
lambda d: d['context'].update(considered=['CAND-0018']),
lambda d: d['context'].update(selected=['CAND-0007']),
lambda d: d['context'].update(considered=['CAND-0007', 'CAND-0007']),
lambda d: d['decision'].update(action='applied'),
lambda d: d['outcome'].update(value='helpful'),
lambda d: d['outcome'].update(measured_decision_minutes=-1),
lambda d: d['outcome'].update(measured_rework_minutes=float('nan')),
])
def test_invalid_producer_claims_or_provenance_are_rejected(published, decision, mutate):
mutate(decision)
with pytest.raises(ValueError):
consume.receipt(decision, *published[:2])
def test_followup_and_observed_result_retain_lineage(published, decision):
decision['previous_event_sha256'] = 'a' * 64
decision['event_id'] = 'fixture-outcome-02'
decision['outcome'].update(value='mixed', basis='observed-result', stage='test',
observation='Disclosed fixture result; no consumer use.', evidence_refs=['fixture:test-01'])
event = consume.receipt(decision, *published[:2])
assert event['previous_event_sha256'] == 'a' * 64
assert event['outcome']['value'] == 'mixed'
def test_immutable_write_returns_exact_collector_index_entry(tmp_path, published, decision):
event = consume.receipt(decision, *published[:2])
path = 'research/pattern-use/fixture/event-01.json'
entry = consume.write_receipt(event, path, tmp_path)
raw = (tmp_path / path).read_bytes()
assert json.loads(raw) == event
assert entry == {'path': path, 'sha256': hashlib.sha256(raw).hexdigest()}
with pytest.raises(FileExistsError):
consume.write_receipt(event, path, tmp_path)
assert (tmp_path / path).read_bytes() == raw
@pytest.mark.parametrize('path', ['/tmp/event.json', '../event.json', 'x//event.json', 'x/./event.json', 'x\\event.json'])
def test_unsafe_paths_are_rejected_before_writing(tmp_path, published, decision, path):
with pytest.raises(ValueError, match='Unsafe receipt path'):
consume.write_receipt(consume.receipt(decision, *published[:2]), path, tmp_path)
assert list(tmp_path.iterdir()) == []
def test_symlink_escape_is_refused(tmp_path, published, decision):
(tmp_path / 'out').symlink_to(ROOT, target_is_directory=True)
with pytest.raises(ValueError, match='escapes consumer root'):
consume.write_receipt(consume.receipt(decision, *published[:2]), 'out/event.json', tmp_path)
def test_cli_generates_owner_accepted_event_and_index(tmp_path):
result = subprocess.run([sys.executable, str(ROOT / 'scripts/reference_consume.py'), '--version', '0.1', 'receipt',
'--input', str(ROOT / 'feedback/decision-input.example.json'), '--output', 'use/event.json'],
cwd=tmp_path, capture_output=True, text=True, check=True)
entry = json.loads(result.stdout)
assert entry['sha256'] == hashlib.sha256((tmp_path / entry['path']).read_bytes()).hexdigest()
owner = ROOT.parent / 'activity-core'
python = owner / '.venv/bin/python'
if not python.exists():
pytest.skip('Actual activity-core contract check requires the sibling owner environment')
script = '''import json, sys
sys.path.insert(0, sys.argv[1])
from activity_core.context_resolvers.frontend_patterns import validate_event
event = json.load(open(sys.argv[2]))
validate_event(event, event['consumer']['repo'])
'''
subprocess.run([str(python), '-B', '-c', script, str(owner / 'src'), str(tmp_path / entry['path'])],
check=True, capture_output=True, text=True)
def test_invalid_cli_decision_cannot_create_a_receipt(tmp_path, decision):
decision['decision']['action'] = 'applied' # Selection remains empty.
source = tmp_path / 'decision.json'
source.write_text(json.dumps(decision))
result = subprocess.run([sys.executable, str(ROOT / 'scripts/reference_consume.py'), 'receipt',
'--input', str(source), '--output', 'use/event.json'],
cwd=tmp_path, capture_output=True, text=True)
assert result.returncode == 1
assert 'Action/selection mismatch' in result.stderr
assert not (tmp_path / 'use').exists()