assistant/tests/test_preflight_telemetry_d4...

201 lines
10 KiB
Python

"""Only the public wrapper records bounded program preflight telemetry."""
from copy import deepcopy
import json
import pytest
from backend.browser import tools
from backend.config import settings
from test_reply_preflight_d44 import Page, Session, find, node, speaker, target, thread, tree
@pytest.fixture(autouse=True)
def enable_reply_relation_preflight(monkeypatch):
monkeypatch.setattr(settings, 'reply_relation_preflight_enabled', True)
@pytest.fixture
def telemetry(tmp_path, monkeypatch):
task_id = 'preflight_telemetry_d44'
tools.set_prospecting_context(task_id)
tools.set_prospecting_run_context(task_id, 'f'*32, goal_version=3)
tools._reply_target_proofs[task_id] = {'private-source': {'run_id': 'old', 'profile_id': 'c'*24}}
monkeypatch.setattr(tools, 'task_doc_dir', lambda _: tmp_path)
def forbidden(*_args, **_kwargs):
raise AssertionError('preflight telemetry is not a source, operation, knowledge fact or proof')
for name in ('_operation_evidence', '_save_source', '_apply_reply_target_proof'):
monkeypatch.setattr(tools, name, forbidden)
proof = deepcopy(tools._reply_target_proofs)
yield task_id, tmp_path
assert proof == tools._reply_target_proofs
tools.clear_task_context(task_id)
async def public(task_id, page):
action = tools.build_prospecting_tools(task_id).registry.registry.actions['inspect_reply_relations']
result = await action.function(browser_session=Session(page), max_replies=5)
return json.loads(result.extracted_content)
def receipt(task_id):
return tools._task_artifacts[task_id]['preflight_observations'][-1]
def payload():
return {'read_only': True, 'scope': 'current_viewport_only', 'note_id': 'a'*24, 'status': 'checked',
'checked_count': 1, 'eligible_count': 1, 'discussion_state': 'visible_comments',
'visible_comment_count': 3, 'has_unexpanded_replies': False,
'candidates': [{'classification': 'unique_dom_deep_reply', 'eligible': True, 'observation_stable': True,
'parent_is_reply': True, 'parent_relation_observed': True, 'native_navigation_confirmed': False,
'source_selector': '#PRIVATE-LOCATOR', 'quote': 'PRIVATE-QUOTE',
'rawhref': 'https://private.invalid/?xsec_token=PRIVATE-TOKEN', 'user_id': 'c'*24}]}
def record(task_id, result):
return tools._record_reply_preflight_observation(task_id, result, deepcopy(tools._prospecting_run_context[task_id]))
async def test_public_wrapper_counts_match_real_helper_and_only_telemetry_is_added(telemetry):
task_id, directory = telemetry
page = Page(tree(thread(speaker('parent', 'c'), speaker('child', 'd', target()))))
before = deepcopy(tools._task_artifacts[task_id])
result = await public(task_id, page)
row = receipt(task_id)
assert result['telemetry_recorded'] is True
assert row['run_id'] == 'f'*32 and row['goal_version'] == 3 and row['note_id'] == 'a'*24
assert row['checked_count'] == result['checked_count'] == len(result['candidates']) == 2
assert row['eligible_count'] == result['eligible_count'] == 1
assert row['classification_counts'] == {'unique_dom_deep_reply': 1, 'needs_native_identity': 0, 'ambiguous': 0, 'unknown': 1}
assert row['discussion_state'] == 'visible_comments' and row['visible_comment_count'] == 3
assert row['has_unexpanded_replies'] is False
assert {key: value for key, value in tools._task_artifacts[task_id].items() if key != 'preflight_observations'} == before
evidence = json.loads((directory/'evidence.json').read_text())
assert evidence['preflight_observations'] == [row]
assert all(evidence[key] == [] for key in ('sources', 'source_observations', 'operation_evidence', 'learning_candidates', 'learning_outcomes'))
serialized = json.dumps(evidence)
assert 'PRIVATE' not in serialized and 'https://' not in serialized and 'selector' not in serialized
assert 'c'*24 not in serialized and 'd'*24 not in serialized
async def test_no_visible_reply_preserves_comments_not_in_view_instead_of_claiming_read_complete(telemetry):
task_id, _ = telemetry
value = tree(thread())
find(value, 'thread-root')['rect'] = {'left': 0, 'right': 300, 'top': 800, 'bottom': 835, 'width': 300, 'height': 35}
value['children'].append(node('comments', 'comments-container'))
result = await public(task_id, Page(value))
row = receipt(task_id)
assert result['status'] == row['status'] == 'no_visible_reply'
assert row['checked_count'] == row['eligible_count'] == 0
assert row['discussion_state'] == 'comments_not_in_view' and row['visible_comment_count'] == 0
assert row['has_unexpanded_replies'] is False
async def test_failed_dom_listing_records_unknown_metadata_not_invented_zero(telemetry, monkeypatch):
task_id, directory = telemetry
async def failed(*_args, **_kwargs):
raise ValueError('PRIVATE-RESPONSE https://private.invalid')
monkeypatch.setattr(tools, '_inspect_source_candidates', failed)
result = await public(task_id, Page(tree()))
row = receipt(task_id)
assert result['telemetry_recorded'] is True and row['status'] == 'unknown'
assert row['discussion_state'] == 'unknown'
assert row['visible_comment_count'] is None and row['has_unexpanded_replies'] is None
assert row['checked_count'] == row['eligible_count'] == 0
assert 'PRIVATE' not in (directory/'evidence.json').read_text()
async def test_visible_root_with_folded_replies_keeps_unexpanded_scope_distinct_from_zero_reply_candidates(telemetry):
task_id, _ = telemetry
value = tree(thread())
find(value, 'thread-replies')['children'].append(node('expand', '', '展开2条回复'))
result = await public(task_id, Page(value))
row = receipt(task_id)
assert result['status'] == row['status'] == 'no_visible_reply'
assert row['checked_count'] == 0 and row['discussion_state'] == 'visible_comments'
assert row['visible_comment_count'] == 1 and row['has_unexpanded_replies'] is True
@pytest.mark.parametrize('context', [{}, {'run_id': 'f'*32}, {'run_id': 'PRIVATE-RUN', 'goal_version': 3},
{'run_id': 'f'*32, 'goal_version': True}, {'run_id': 'f'*32, 'goal_version': 0}])
async def test_wrapper_does_not_attach_new_receipts_to_missing_or_invalid_run_goal(telemetry, monkeypatch, context):
task_id, directory = telemetry
tools._prospecting_run_context[task_id] = context
async def checked(*_args, **_kwargs):
return payload()
monkeypatch.setattr(tools, '_inspect_reply_relations', checked)
result = await public(task_id, Page(tree()))
assert result['telemetry_recorded'] is False
assert 'preflight_observations' not in tools._task_artifacts[task_id]
assert list(directory.iterdir()) == []
async def test_run_or_goal_changed_during_read_does_not_record_stale_result(telemetry, monkeypatch):
task_id, directory = telemetry
async def checked(*_args, **_kwargs):
tools._prospecting_run_context[task_id] = {'run_id': 'e'*32, 'goal_version': 4}
return payload()
monkeypatch.setattr(tools, '_inspect_reply_relations', checked)
result = await public(task_id, Page(tree()))
assert result['telemetry_recorded'] is False
assert 'preflight_observations' not in tools._task_artifacts[task_id]
assert list(directory.iterdir()) == []
@pytest.mark.parametrize('mutate', [
lambda row: row.update(checked_count=2),
lambda row: row.update(eligible_count=0),
lambda row: row.update(checked_count=True),
lambda row: row.update(candidates=row['candidates']*6, checked_count=6, eligible_count=6),
lambda row: row.update(note_id='https://private.invalid'),
lambda row: row.update(status=['PRIVATE']),
lambda row: row.update(discussion_state={'PRIVATE': 'QUOTE'}),
lambda row: row.update(visible_comment_count=-1),
lambda row: row.update(visible_comment_count=1_000_001),
lambda row: row.update(visible_comment_count=True),
lambda row: row.update(has_unexpanded_replies='PRIVATE'),
lambda row: row['candidates'][0].update(classification=['PRIVATE']),
lambda row: row['candidates'][0].update(eligible=False),
lambda row: row['candidates'][0].update(observation_stable=False),
lambda row: row['candidates'][0].update(native_navigation_confirmed=True),
])
def test_invalid_or_inconsistent_program_counts_never_persist(telemetry, mutate):
task_id, directory = telemetry
result = payload()
mutate(result)
assert record(task_id, result) is None
assert 'preflight_observations' not in tools._task_artifacts[task_id]
assert list(directory.iterdir()) == []
def test_receipt_projects_only_safe_program_fields_and_never_backfills_history(telemetry):
task_id, directory = telemetry
result = payload()
result.update(rawhref='https://private.invalid/?key=PRIVATE', quote='PRIVATE', classification_counts={'PRIVATE': 99})
row = record(task_id, result)
assert tools._valid_preflight_telemetry(row)
assert len(tools._task_artifacts[task_id]['preflight_observations']) == 1
assert 'PRIVATE' not in json.dumps(row) and 'c'*24 not in json.dumps(row)
assert 'PRIVATE' not in (directory/'evidence.json').read_text()
def test_telemetry_history_is_bounded_and_exposes_truncation_without_a_business_quota(telemetry, monkeypatch):
task_id, _ = telemetry
writes = []
monkeypatch.setattr(tools, '_persist_evidence', lambda _: writes.append(len(tools._task_artifacts[task_id]['preflight_observations'])))
first = record(task_id, payload())
for _ in range(tools._PREFLIGHT_TELEMETRY_LIMIT):
assert record(task_id, payload())
rows = tools._task_artifacts[task_id]['preflight_observations']
assert len(rows) == tools._PREFLIGHT_TELEMETRY_LIMIT and first['id'] not in {row['id'] for row in rows}
assert rows[-1]['history_truncated'] is True and max(writes) == tools._PREFLIGHT_TELEMETRY_LIMIT
assert all(tools._valid_preflight_telemetry(row) for row in rows)
def test_invalid_existing_telemetry_is_not_copied_into_a_new_receipt_or_rewritten(telemetry):
task_id, directory = telemetry
old = [{'quote': 'PRIVATE', 'rawhref': 'https://private.invalid'}]
tools._task_artifacts[task_id]['preflight_observations'] = deepcopy(old)
assert record(task_id, payload()) is None
assert tools._task_artifacts[task_id]['preflight_observations'] == old
assert list(directory.iterdir()) == []