feat(telemetry): add isolated beta quality diagnostics and release identity

This commit is contained in:
dadachann
2026-09-15 09:35:01 +00:00
parent 91d6de8858
commit f8123ead0a
89 changed files with 4205 additions and 175 deletions
@@ -0,0 +1,143 @@
"""Execute real Core boundaries with content canaries and early failures."""
import json
from types import SimpleNamespace
from unittest.mock import AsyncMock
from uuid import uuid4
import pytest
from langbot.pkg.telemetry import diagnostics as d
from langbot.pkg.api.http.context import ExecutionContext
from langbot.pkg.agent.runner.orchestrator import AgentRunOrchestrator
from langbot.pkg.agent.runner.reply_stream import ReplyStreamSession, ReplyStreamRequest
def make_ap():
ap = SimpleNamespace(instance_config=SimpleNamespace(data={'space': {'url': 'https://example.invalid'}}))
ap.persistence_mgr = SimpleNamespace(get_db_engine=lambda: None)
ap.diagnostics = d.DiagnosticsManager(ap, version='4.11.0b2', instance_id='instance-test')
return ap
@pytest.mark.asyncio
@pytest.mark.parametrize('processor', ['pipeline', 'agent', 'event_processor'])
async def test_real_orchestrator_prepare_failure(processor):
ap = make_ap()
registry = SimpleNamespace(get=AsyncMock(side_effect=ValueError('CANARY private runner URL')))
orchestrator = AgentRunOrchestrator(ap, registry)
context = ExecutionContext(instance_uuid='instance-test', workspace_uuid=str(uuid4()), placement_generation=1)
event = SimpleNamespace(workspace_id=context.workspace_uuid, event_type='message.received')
binding = SimpleNamespace(runner_id='CANARY', processor_type=processor)
with pytest.raises(ValueError, match='CANARY'):
await anext(orchestrator.run(event, binding, adapter_context={'_execution_context': context}))
records = ap.diagnostics.pending
assert [r['outcome'] for r in records] == ['started', 'failed']
assert records[-1]['stage'] == 'prepare'
assert records[-1]['workspace_uuid'] == context.workspace_uuid
assert records[-1]['processor_type'] == processor
assert 'CANARY' not in json.dumps(records)
@pytest.mark.asyncio
async def test_real_reply_stream_mock_is_not_platform_success():
ap = make_ap()
event = SimpleNamespace(
delivery=SimpleNamespace(reply_target={}, surface='webui', platform_capabilities={'debug_mock': True})
)
session = ReplyStreamSession(event)
# Real runtime supplies the manager at construction from the orchestrator.
session.diagnostics = ap.diagnostics
result = await session.apply(ReplyStreamRequest(stream_id=uuid4(), operation='finish', text='CANARY user reply'))
assert result['mock'] is True and result['text'] == 'CANARY user reply'
assert ap.diagnostics.pending[-1]['source'] == 'webui_debug'
assert ap.diagnostics.pending[-1]['attributes']['synthetic'] is True
assert 'CANARY' not in json.dumps(ap.diagnostics.pending)
@pytest.mark.asyncio
async def test_real_bot_route_projects_status_not_reason():
from langbot.pkg.platform.botmgr import RuntimeBot
ap = make_ap()
bot = object.__new__(RuntimeBot)
bot.ap = ap
bot.logger = SimpleNamespace(info=AsyncMock())
await bot._record_event_route_trace(
event_type='message.received',
status='not_matched',
text='CANARY secret',
reason='CANARY',
failure_code='route_not_found',
)
records = ap.diagnostics.pending
assert records[-1]['outcome'] == 'skipped'
assert records[-1]['reason_code'] == 'route_not_found'
assert 'CANARY' not in json.dumps(records)
@pytest.mark.asyncio
async def test_real_telegram_conversion_failure_before_bot_manager(monkeypatch):
from langbot.pkg.platform.adapters.telegram.adapter import TelegramAdapter
from langbot.pkg.platform.adapters.telegram.event_converter import TelegramEventConverter
ap = make_ap()
context = ExecutionContext(instance_uuid='instance-test', workspace_uuid=str(uuid4()), placement_generation=1)
from langbot.pkg.platform.logger import EventLogger
logger = EventLogger('test', ap, context, 'test')
logger.error = AsyncMock()
logger.warning = AsyncMock()
adapter = TelegramAdapter({'token': '123456:ABCDEFGHIJKLMNOPQRSTUVWXYZ_123456789'}, logger)
adapter.listeners = {}
monkeypatch.setattr(
TelegramEventConverter, '_convert_message', AsyncMock(side_effect=ValueError('CANARY conversion'))
)
update = SimpleNamespace(
message=SimpleNamespace(from_user=SimpleNamespace(is_bot=False), text='CANARY text'),
edited_message=None,
chat_member=None,
my_chat_member=None,
callback_query=None,
message_reaction=None,
)
callback = adapter.application.handlers[0][0].callback
await callback(update, None)
records = [e for e in ap.diagnostics.pending if e['stage'] == 'convert' and e['outcome'] == 'failed']
assert records and records[-1]['adapter'] == 'telegram-omni'
assert records[-1]['workspace_uuid'] == context.workspace_uuid
assert 'CANARY' not in json.dumps(ap.diagnostics.pending)
@pytest.mark.asyncio
async def test_real_interaction_ack_skip_and_failure():
from langbot.pkg.agent.runner.interaction_manager import InteractionManager
ap = make_ap()
interactions = InteractionManager(ap, store=SimpleNamespace(record_delivery_success=AsyncMock()))
await interactions.acknowledge_submission({}, SimpleNamespace(get_supported_apis=lambda: []))
assert ap.diagnostics.pending[-1]['outcome'] == 'skipped'
ap.diagnostics.pending.clear()
ap.logger = SimpleNamespace(warning=lambda *a: None)
adapter = SimpleNamespace(
get_supported_apis=lambda: ['interaction.acknowledge'],
call_platform_api=AsyncMock(side_effect=ValueError('CANARY ack')),
)
await interactions.acknowledge_submission({'delivery_result': {'secret': 'CANARY'}}, adapter)
assert ap.diagnostics.pending[-1]['outcome'] == 'failed'
assert 'CANARY' not in json.dumps(ap.diagnostics.pending)
def test_actual_adapter_capability_snapshot():
from langbot.pkg.platform.adapters.telegram.adapter import TelegramAdapter
from langbot.pkg.telemetry.diagnostic_catalog import snapshot_bot
ap = make_ap()
context = ExecutionContext(instance_uuid='instance-test', workspace_uuid=str(uuid4()), placement_generation=1)
adapter = TelegramAdapter.model_construct(config={}, listeners={})
snapshot_bot(SimpleNamespace(ap=ap, adapter=adapter, execution_context=context))
records = ap.diagnostics.pending
assert records and all(e['kind'] == 'capability' for e in records)
api_rows = [e for e in records if e['attributes']['capability_type'] == 'api']
assert any(e['operation'] == 'send_message' and e['attributes']['supported'] for e in api_rows)
assert all(e['workspace_uuid'] == context.workspace_uuid for e in records)
@@ -0,0 +1,405 @@
"""Independent-review regressions through real ownership and lifecycle boundaries."""
import asyncio
import importlib.metadata
import json
from collections.abc import AsyncGenerator
from types import SimpleNamespace as NS
from unittest.mock import AsyncMock, Mock
from uuid import uuid4
import pytest
from langbot.pkg.api.http.context import ExecutionContext
from langbot.pkg.provider.modelmgr.requester import RuntimeProvider
from langbot.pkg.telemetry import diagnostics as d
def make_ap(identity='instance-test', disabled=False):
ap = NS(instance_config=NS(data={'space': {'url': 'https://example.invalid', 'disable_telemetry': disabled}}))
ap.diagnostics = d.DiagnosticsManager(ap, version='4.11.0b2', instance_id=identity)
return ap
def context(identity='instance-test'):
return ExecutionContext(instance_uuid=identity, workspace_uuid=str(uuid4()), placement_generation=1)
@pytest.mark.asyncio
@pytest.mark.parametrize('disabled', [True, False])
async def test_real_runtime_provider_cannot_use_other_app_manager(disabled):
a, b = make_ap('instance-A'), make_ap('instance-B', disabled)
ca, cb = context('instance-A'), context('instance-B')
requester = NS(ap=b, invoke_llm=AsyncMock(return_value='business result'))
provider = RuntimeProvider(cb, NS(workspace_uuid=cb.workspace_uuid), None, requester)
model = NS(execution_context=cb, provider=provider)
parent = d.Span(a.diagnostics, 'api', 'review.parent', {'workspace_uuid': ca.workspace_uuid})
with parent.activate():
assert await provider.invoke_llm(None, model, [], execution_context=cb) == 'business result'
assert not [e for e in a.diagnostics.pending if e['operation'] == 'model.invoke_llm']
assert len(b.diagnostics.pending) == (0 if disabled else 2)
for event in b.diagnostics.pending:
assert event['instance_id'] == 'instance-B'
assert event['workspace_uuid'] == cb.workspace_uuid
assert event['trace_id'] != parent.fields['trace_id']
@pytest.mark.asyncio
@pytest.mark.parametrize('shape', ['app', 'ap', 'logger', 'requester', 'adapter', 'direct', 'absent'])
async def test_explicit_disabled_owner_blocks_parent_even_in_ownerless_children(shape):
a, b = make_ap('instance-A'), make_ap('instance-B', True)
shapes = {
'app': b,
'ap': NS(ap=b),
'logger': NS(logger=NS(ap=b)),
'requester': NS(requester=NS(ap=b)),
'adapter': NS(adapter=NS(logger=NS(ap=b))),
'direct': NS(diagnostics=b.diagnostics),
'absent': NS(ap=None),
}
@d.observe('api', 'review.child')
async def child(data):
d.event(data, 'api', 'review.point', 'succeeded')
return 42
@d.observe('api', 'review.owner')
async def call(owner):
return await child({})
@d.observe('run', 'review.stream')
async def stream(owner):
yield await child({})
parent = d.Span(a.diagnostics, 'api', 'review.parent', {})
with parent.activate():
assert await call(shapes[shape]) == 42
assert [x async for x in stream(shapes[shape])] == [42]
d.event(shapes[shape], 'api', 'review.point', 'succeeded')
assert len(a.diagnostics.pending) == 1
assert not b.diagnostics.pending
@pytest.mark.asyncio
async def test_context_cannot_select_other_instance_or_owned_workspace():
ap = make_ap()
ca, cb = context(), context('instance-other')
@d.observe('api', 'review.context')
async def call(owner, execution_context):
return 42
owner = NS(ap=ap, execution_context=ca)
with d.Span(ap.diagnostics, 'api', 'review.parent', {'workspace_uuid': ca.workspace_uuid}).activate():
assert await call(owner, cb) == 42
assert await call(owner, context()) == 42
assert not [e for e in ap.diagnostics.pending if e['operation'] == 'review.context']
# A misplaced manager attachment is not an authoritative manager for B.
other = make_ap('instance-other')
other.diagnostics = ap.diagnostics
assert await call(NS(ap=other), cb) == 42
assert not [e for e in ap.diagnostics.pending if e['operation'] == 'review.context']
@pytest.mark.asyncio
@pytest.mark.parametrize('disabled', [False, True])
@pytest.mark.parametrize('action', ['aclose', 'athrow_exit', 'athrow_value', 'athrow_cancel'])
async def test_cleanup_exception_identity_matches_native(disabled, action):
ap = make_ap(disabled=disabled)
cleanup_error = ValueError('CANARY cleanup')
async def original(owner):
try:
yield 1
finally:
raise cleanup_error
for fn in (original, d.observe('run', 'review.cleanup')(original)):
gen = fn(ap)
assert isinstance(gen, AsyncGenerator)
assert await anext(gen) == 1
with pytest.raises(ValueError) as caught:
if action == 'aclose':
await gen.aclose()
else:
error = {
'athrow_exit': GeneratorExit(),
'athrow_value': KeyError('business'),
'athrow_cancel': asyncio.CancelledError('cancel'),
}[action]
await gen.athrow(error)
assert caught.value is cleanup_error
assert d.current_span() is None
@pytest.mark.asyncio
@pytest.mark.parametrize('disabled', [False, True])
async def test_athrow_generator_exit_can_yield_and_close_remains_native(disabled):
ap = make_ap(disabled=disabled)
async def original(owner):
try:
yield 1
except GeneratorExit:
yield 2
yield 3
for fn in (original, d.observe('run', 'review.exit')(original)):
gen = fn(ap)
assert await anext(gen) == 1
assert await gen.athrow(GeneratorExit()) == 2
assert await anext(gen) == 3
await gen.aclose()
gen = fn(ap)
assert await anext(gen) == 1
with pytest.raises(RuntimeError, match='ignored GeneratorExit'):
await gen.aclose()
# The native generator is still suspended after the refused close.
assert await anext(gen) == 3
with pytest.raises(StopAsyncIteration):
await anext(gen)
await gen.aclose()
@pytest.mark.asyncio
@pytest.mark.parametrize('disabled', [False, True])
async def test_generator_send_throw_cancellation_and_primary_exception(disabled):
ap = make_ap(disabled=disabled)
entered = asyncio.Event()
primary = ValueError('CANARY business')
async def original(owner):
value = yield 1
try:
yield value
except KeyError:
yield 3
entered.set()
await asyncio.Event().wait()
for fn in (original, d.observe('run', 'review.protocol')(original)):
gen = fn(ap)
assert await anext(gen) == 1
assert await gen.asend(7) == 7
assert await gen.athrow(KeyError('throw')) == 3
entered.clear()
task = asyncio.create_task(anext(gen))
await entered.wait()
task.cancel('native cancellation')
with pytest.raises(asyncio.CancelledError, match='native cancellation'):
await task
await gen.aclose()
async def failing(owner):
yield 1
raise primary
gen = d.observe('run', 'review.primary')(failing)(ap)
assert await anext(gen) == 1
with pytest.raises(ValueError) as caught:
await anext(gen)
assert caught.value is primary
assert d.current_span() is None
@pytest.mark.asyncio
async def test_real_kook_native_entry_conversion_failure_has_trusted_workspace():
from langbot.pkg.platform.adapters.kook.adapter import KookAdapter
from langbot.pkg.platform.logger import EventLogger
ap, ctx = make_ap(), context()
logger = EventLogger('test', ap, ctx, 'test')
logger.error = AsyncMock()
adapter = KookAdapter({'token': 'test-placeholder'}, logger)
# Invalid native timestamp fails inside the real static converter.
await adapter._handle_event({'type': 255, 'msg_timestamp': 'CANARY invalid', 'workspace_uuid': str(uuid4())}, 1)
logger.error.assert_awaited_once()
failures = [e for e in ap.diagnostics.pending if e['outcome'] == 'failed']
assert any(e['stage'] == 'convert' for e in failures)
assert any(e['operation'] == 'platform.receive' for e in failures)
assert all(e['workspace_uuid'] == ctx.workspace_uuid for e in ap.diagnostics.pending)
assert all(e['adapter'] == 'kook-omni' for e in ap.diagnostics.pending)
assert 'CANARY' not in json.dumps(ap.diagnostics.pending)
@pytest.fixture
def boot_stages(monkeypatch):
from langbot.pkg.core import boot
stages_run = []
class Stage:
async def run(self, app):
stages_run.append(app)
# Earlier registry tests clear this shared dictionary; cached imports do not
# re-register stages. Own the registry per test and restore it on teardown.
monkeypatch.setattr(boot.stage, 'preregistered_stages', {'LoadConfigStage': Stage, 'GenKeysStage': Stage})
return stages_run
@pytest.mark.asyncio
@pytest.mark.parametrize('failure', ['constructor', 'metadata', 'session', 'start'])
@pytest.mark.parametrize('disabled', [False, True])
async def test_real_make_app_optional_initialization_fail_open(monkeypatch, boot_stages, failure, disabled):
from langbot.pkg.core import boot
ap = NS(
instance_config=NS(data={'space': {'url': 'https://example.invalid', 'disable_telemetry': disabled}}),
initialize=AsyncMock(),
shutdown=AsyncMock(),
)
stages_run = boot_stages
monkeypatch.setattr(boot.app, 'Application', lambda: ap)
monkeypatch.setattr(boot, 'stage_order', ['LoadConfigStage', 'GenKeysStage'])
error = RuntimeError('optional diagnostics')
if failure == 'constructor':
monkeypatch.setattr(boot.diagnostics, 'DiagnosticsManager', Mock(side_effect=error))
elif failure == 'metadata':
monkeypatch.setattr(
importlib.metadata, 'version', Mock(side_effect=importlib.metadata.PackageNotFoundError('langbot'))
)
else:
manager = NS(
start_session=AsyncMock(), start=Mock(), shutdown=AsyncMock(side_effect=RuntimeError('optional cleanup'))
)
getattr(manager, 'start_session' if failure == 'session' else 'start').side_effect = error
monkeypatch.setattr(boot.diagnostics, 'DiagnosticsManager', Mock(return_value=manager))
assert await boot.make_app(asyncio.get_running_loop()) is ap
assert len(stages_run) == 2
ap.initialize.assert_awaited_once()
ap.shutdown.assert_not_awaited()
assert getattr(ap, 'diagnostics', None) is None
@pytest.mark.asyncio
async def test_real_make_app_session_cancellation_and_shutdown_failure_preserve_primary(monkeypatch, boot_stages):
from langbot.pkg.core import boot
cancelled = asyncio.CancelledError('genuine cancellation')
ap = NS(
instance_config=NS(data={'space': {'url': 'https://example.invalid'}}),
initialize=AsyncMock(),
shutdown=AsyncMock(side_effect=RuntimeError('shutdown error')),
)
manager = NS(start_session=AsyncMock(side_effect=cancelled), start=Mock(), shutdown=AsyncMock())
monkeypatch.setattr(boot.app, 'Application', lambda: ap)
monkeypatch.setattr(boot, 'stage_order', ['GenKeysStage'])
monkeypatch.setattr(boot.diagnostics, 'DiagnosticsManager', Mock(return_value=manager))
with pytest.raises(asyncio.CancelledError) as caught:
await boot.make_app(asyncio.get_running_loop())
assert caught.value is cancelled
ap.initialize.assert_not_awaited()
ap.shutdown.assert_awaited_once()
@pytest.mark.asyncio
async def test_real_make_app_business_failure_not_masked_by_optional_shutdown(monkeypatch, boot_stages):
from langbot.pkg.core import boot
primary = ValueError('business startup')
ap = boot.app.Application()
ap.instance_config = NS(data={'space': {'url': 'https://example.invalid'}})
ap.initialize = AsyncMock(side_effect=primary)
manager = make_ap().diagnostics
manager.start_session = AsyncMock()
manager.start = Mock()
manager.shutdown = AsyncMock(side_effect=RuntimeError('optional shutdown'))
monkeypatch.setattr(boot.app, 'Application', lambda: ap)
monkeypatch.setattr(boot.diagnostics, 'DiagnosticsManager', lambda *a, **kw: manager)
monkeypatch.setattr(boot, 'stage_order', ['GenKeysStage'])
with pytest.raises(ValueError) as caught:
await boot.make_app(asyncio.get_running_loop())
assert caught.value is primary
manager.shutdown.assert_awaited_once()
@pytest.mark.asyncio
@pytest.mark.parametrize('disabled', [False, True])
async def test_protocol_rejected_calls_and_unstarted_throw_match_native(disabled):
ap = make_ap(disabled=disabled)
async def original(owner):
try:
yield 1
except GeneratorExit:
yield 2
yield 3
async def record(fn, actions):
gen = fn(ap)
results = []
for name, values in actions:
try:
results.append(('value', await getattr(gen, name)(*values)))
except BaseException as exc:
results.append((type(exc).__name__, str(exc)))
# Fully exhaust any generator left suspended by a refused close.
try:
while True:
await anext(gen)
except (StopAsyncIteration, GeneratorExit):
pass
return results
wrapped = d.observe('run', 'review.protocol_matrix')(original)
for actions in (
[('aclose', ())],
[('athrow', (GeneratorExit(),))],
[('athrow', (ValueError('unstarted'),))],
[('asend', (7,)), ('__anext__', ()), ('athrow', (ValueError('primary'),))],
[('__anext__', ()), ('aclose', ()), ('__anext__', ()), ('aclose', ())],
[('__anext__', ()), ('athrow', (GeneratorExit(),)), ('__anext__', ()), ('aclose', ())],
):
assert await record(wrapped, actions) == await record(original, actions)
@pytest.mark.asyncio
@pytest.mark.parametrize('disabled', [False, True])
async def test_native_task_cancel_cleanup_error_wins_without_extra_close(disabled):
ap = make_ap(disabled=disabled)
error = ValueError('native cleanup')
entered = asyncio.Event()
async def original(owner):
yield 1
try:
entered.set()
await asyncio.Event().wait()
finally:
raise error
for fn in (original, d.observe('run', 'review.cancel_cleanup')(original)):
gen = fn(ap)
await anext(gen)
entered.clear()
task = asyncio.create_task(anext(gen))
await entered.wait()
task.cancel()
with pytest.raises(ValueError) as caught:
await task
assert caught.value is error
await gen.aclose()
@pytest.mark.asyncio
async def test_sdk_b2_handler_consumes_observed_async_generator(tmp_path):
from langbot_plugin.runtime.io.handler import Handler, ActionResponse
from langbot_plugin.entities.io.resp import ChunkStatus
ap = make_ap()
handler = Handler(NS(), file_storage_dir=str(tmp_path))
handler._send_message = AsyncMock()
@d.observe('api', 'host.review_stream', ap=ap)
async def stream(data):
yield ActionResponse.success({'business': 'unchanged'})
handler.actions['review_stream'] = stream
await handler._handle_action({'seq_id': 1, 'action': 'review_stream', 'data': {}})
responses = [call.args[0] for call in handler._send_message.await_args_list]
assert len(responses) == 2
assert responses[0].data == {'business': 'unchanged'}
assert [response.chunk_status for response in responses] == [ChunkStatus.CONTINUE, ChunkStatus.END]
assert [e['outcome'] for e in ap.diagnostics.pending] == ['started', 'succeeded']
@@ -0,0 +1,350 @@
"""Content-free diagnostics contract and lifecycle regression tests."""
import asyncio
import json
from types import SimpleNamespace
from uuid import uuid4
import httpx
import pytest
from langbot.pkg.telemetry import diagnostics as d
def manager(version='4.11.0-beta.2', **config):
ap = SimpleNamespace(instance_config=SimpleNamespace(data={'space': {'url': 'https://example.invalid', **config}}))
ap.diagnostics = d.DiagnosticsManager(ap, version=version, instance_id='instance-test', capacity=4)
return ap.diagnostics
def test_release_gate_and_privacy():
assert not manager('4.11.0').enabled
assert manager().enabled
assert manager('4.11.0b2').enabled
assert not manager(disable_telemetry=True).enabled
assert not manager(disable_beta_diagnostics=True).enabled
m = manager()
m.emit(
'api',
'test.operation',
'failed',
attributes={'prompt': 'CANARY', 'plugin_id': 'CANARY', 'attempts': 1},
error=ValueError('CANARY https://secret/token'),
workspace_uuid=str(uuid4()),
)
payload = m.pending[0]
assert 'CANARY' not in json.dumps(payload)
assert payload['attributes'] == {'attempts': 1}
assert payload['error_type'] == 'ValueError'
assert payload['instance_id'] != payload['workspace_uuid']
assert payload['sample_rate'] == 1
@pytest.mark.asyncio
async def test_disabled_boundary_skips_all_projection(monkeypatch):
m = manager('4.11.0')
def broken(*args, **kwargs):
raise AssertionError('diagnostic machinery ran while disabled')
monkeypatch.setattr(d, 'Span', broken)
monkeypatch.setattr(d, 'result_outcome', broken)
class Service:
ap = m.ap
@d.observe('api', 'test.disabled', fields=broken)
async def call(self):
return 42
@d.observe('run', 'test.disabled', fields=broken)
async def stream(self):
yield 42
assert await Service().call() == 42
assert [v async for v in Service().stream()] == [42]
assert not m.pending
def test_bounds_and_disable_clear():
m = manager()
for _ in range(8):
m.emit('api', 'test.operation', 'succeeded')
assert len(m.pending) == 4
assert m.counters['dropped'] == 4
m.ap.instance_config.data['space']['disable_telemetry'] = True
m.emit('api', 'test.operation', 'succeeded')
assert not m.pending
@pytest.mark.asyncio
async def test_partial_ack_retries_same_identity_and_drops_rejected():
m = manager()
for _ in range(3):
m.emit('api', 'test.operation', 'succeeded')
ids = [e['event_id'] for e in m.pending]
requests = []
async def handler(req):
requests.append(json.loads(req.content))
return httpx.Response(
200,
json={
'code': 200,
'data': {'accepted_event_ids': [ids[0]], 'rejected': [{'event_id': ids[1], 'code': 'invalid_event'}]},
},
)
m.client = httpx.AsyncClient(transport=httpx.MockTransport(handler))
await m.flush_once()
assert [e['event_id'] for e in m.pending] == [ids[2]]
assert m.counters['acked'] == 1
assert m.counters['dropped'] == 1
assert requests[0]['schema_version'] == 1
await m.shutdown(drain_timeout=0)
@pytest.mark.asyncio
async def test_outage_finite_retry_and_slow_credentials():
m = manager()
m.max_attempts = 2
m.emit('run', 'test.operation', 'started')
calls = []
async def handler(req):
calls.append(req)
return httpx.Response(503)
m.client = httpx.AsyncClient(transport=httpx.MockTransport(handler))
await m.flush_once()
await m.flush_once()
assert not m.pending
assert m.counters['dropped'] == 1
assert len(calls) == 2
async def credentials(workspace):
await asyncio.sleep(60)
m.credentials = credentials
m.request_timeout = 0.01
m.emit('api', 'test.operation', 'succeeded')
await asyncio.wait_for(m.flush_once(), 0.2)
await m.shutdown(drain_timeout=0)
@pytest.mark.asyncio
async def test_inflight_disable_cancels_and_clears():
m = manager()
entered = asyncio.Event()
async def handler(req):
entered.set()
await asyncio.sleep(60)
m.client = httpx.AsyncClient(transport=httpx.MockTransport(handler))
m.start()
m.emit('api', 'test.operation', 'succeeded')
await asyncio.wait_for(entered.wait(), 1)
m.ap.instance_config.data['space']['disable_beta_diagnostics'] = True
await asyncio.sleep(0.3)
assert not m.pending
await asyncio.wait_for(m.shutdown(drain_timeout=0), 0.5)
@pytest.mark.asyncio
async def test_observe_returns_errors_and_cancellation():
m = manager()
class Service:
ap = m.ap
@d.observe('api', 'test.operation')
async def call(self, error=None):
if error:
raise error
return {'secret': 'CANARY'}
s = Service()
assert await s.call() == {'secret': 'CANARY'}
with pytest.raises(ValueError):
await s.call(ValueError('CANARY'))
m.pending.clear()
with pytest.raises(asyncio.CancelledError):
await s.call(asyncio.CancelledError())
assert m.pending[-1]['outcome'] == 'cancelled'
assert 'CANARY' not in json.dumps(m.pending)
@pytest.mark.asyncio
async def test_generator_send_throw_close_and_context_isolation():
m = manager()
m.capacity = 30
closed = []
class Service:
ap = m.ap
@d.observe('run', 'test.operation')
async def stream(self):
try:
value = yield 1
try:
yield value
except ValueError:
yield 3
finally:
closed.append(True)
gen = Service().stream()
assert await anext(gen) == 1
assert d.current_span() is None
assert await gen.asend(7) == 7
assert await gen.athrow(ValueError('CANARY')) == 3
await gen.aclose()
assert closed == [True]
assert m.pending[-1]['outcome'] == 'cancelled'
assert d.current_span() is None
@pytest.mark.asyncio
async def test_generator_early_failure_and_explicit_terminal():
m = manager()
class Service:
ap = m.ap
@d.observe('run', 'test.operation')
async def stream(self, fail):
if fail:
raise ValueError('prepare CANARY')
d.set_outcome('failed', reason_code='runner_failed')
yield 1
with pytest.raises(ValueError):
await anext(Service().stream(True))
assert m.pending[-1]['outcome'] == 'failed'
m.pending.clear()
assert [v async for v in Service().stream(False)] == [1]
assert m.pending[-1]['outcome'] == 'failed'
@pytest.mark.asyncio
async def test_projection_fault_does_not_replace_business_return(monkeypatch):
m = manager()
def broken(*args, **kwargs):
raise RuntimeError('CANARY projection')
monkeypatch.setattr(d, 'result_outcome', broken)
class Service:
ap = m.ap
@d.observe('api', 'test.projection_fault')
async def call(self):
return 42
assert await Service().call() == 42
assert m.pending[-1]['outcome'] == 'succeeded'
@pytest.mark.asyncio
async def test_workspace_batches_and_credential_failure_are_anonymous():
m = manager()
workspaces = [str(uuid4()), str(uuid4())]
for workspace in workspaces:
m.emit('api', 'test.operation', 'succeeded', workspace_uuid=workspace)
requests = []
async def broken(workspace):
raise RuntimeError('CANARY credentials')
m.credentials = broken
async def handler(request):
data = json.loads(request.content)
requests.append((data, dict(request.headers)))
return httpx.Response(
200,
json={'code': 200, 'data': {'accepted_event_ids': [e['event_id'] for e in data['events']], 'rejected': []}},
)
m.client = httpx.AsyncClient(transport=httpx.MockTransport(handler))
await m.flush_once()
await m.flush_once()
assert len(requests) == 2
assert [request[0]['events'][0]['workspace_uuid'] for request in requests] == workspaces
assert all('authorization' not in headers for _, headers in requests)
assert not m.pending
await m.shutdown(drain_timeout=0)
@pytest.mark.asyncio
async def test_summary_is_interval_delta_and_ack_replay_is_idempotent():
m = manager()
m.capacity = 20
m.emit('api', 'test.operation', 'succeeded')
m.report_transport()
first = m.pending[-1]
m.report_transport()
second = m.pending[-1]
assert first['attributes']['generated'] == 1
assert second['attributes']['generated'] == 1 # Only first summary itself.
assert first['event_id'] != second['event_id']
ids = [e['event_id'] for e in m.pending]
seen = []
async def handler(request):
body = json.loads(request.content)
seen.append([e['event_id'] for e in body['events']])
return (
httpx.Response(503)
if len(seen) == 1
else httpx.Response(200, json={'code': 200, 'data': {'accepted_event_ids': ids + ids, 'rejected': []}})
)
m.client = httpx.AsyncClient(transport=httpx.MockTransport(handler))
await m.flush_once()
await m.flush_once()
assert seen == [ids, ids]
assert m.counters['acked'] == len(ids)
assert not m.pending
await m.shutdown(drain_timeout=0)
@pytest.mark.asyncio
async def test_retention_and_bounded_shutdown():
m = manager()
m.emit('api', 'test.operation', 'succeeded')
m.retention_seconds = -1
await m.flush_once()
assert not m.pending and m.counters['dropped'] == 1
m.retention_seconds = 900
async def handler(request):
await asyncio.sleep(60)
return httpx.Response(503)
m.client = httpx.AsyncClient(transport=httpx.MockTransport(handler))
m.emit('api', 'test.operation', 'succeeded')
await asyncio.wait_for(m.shutdown(drain_timeout=0.01), 0.5)
assert not m.pending and m.client.is_closed
@pytest.mark.asyncio
async def test_session_marker_restart_and_disable(tmp_path):
marker = tmp_path / 'session.json'
m = manager()
m.marker_path = marker
await m.start_session()
assert marker.exists()
recovered = manager()
recovered.marker_path = marker
await recovered.start_session()
assert recovered.pending[-1]['attributes']['previous_session_unclean'] is True
recovered.ap.instance_config.data['space']['disable_beta_diagnostics'] = True
recovered.start()
await asyncio.sleep(0.2)
assert not marker.exists()
await recovered.shutdown(drain_timeout=0)
await m.shutdown(drain_timeout=0)