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
@@ -1473,3 +1473,56 @@ async def test_synthetic_event_query_exposes_trusted_workspace_to_tools(clean_ag
app.skill_mgr.get_skills = lambda scope: received.append(scope) or {}
get_visible_skills(app, synthetic)
assert received[0].workspace_uuid == context.workspace_uuid
@pytest.mark.asyncio
async def test_beta_diagnostics_close_releases_real_orchestrator_session(clean_agent_state):
from langbot.pkg.telemetry.diagnostics import DiagnosticsManager
plugin_connector = FakePluginConnector(
results=[{'type': 'message.completed', 'data': {'message': {'role': 'assistant', 'content': 'CANARY'}}}]
)
ap = FakeApplication(plugin_connector, clean_agent_state)
ap.instance_config = types.SimpleNamespace(data={'space': {'url': 'https://example.invalid'}})
ap.diagnostics = DiagnosticsManager(ap, version='4.11.0b2', instance_id='instance-test')
orchestrator = AgentRunOrchestrator(ap, FakeRegistry(make_descriptor()))
gen = orchestrator.run_from_query(make_query())
assert (await anext(gen)).content == 'CANARY'
run_id = plugin_connector.contexts[0]['run_id']
assert await get_session_registry().get(run_id) is not None
await gen.aclose()
assert await get_session_registry().get(run_id) is None
records = [e for e in ap.diagnostics.pending if e['operation'] == 'runner.run']
assert records[-1]['outcome'] == 'cancelled'
import json
assert 'CANARY' not in json.dumps(ap.diagnostics.pending)
@pytest.mark.asyncio
@pytest.mark.parametrize('terminal', ['run.completed', 'run.failed'])
async def test_beta_diagnostics_real_runner_terminal(clean_agent_state, terminal):
from langbot.pkg.telemetry.diagnostics import DiagnosticsManager
plugin_connector = FakePluginConnector(
results=[
{
'type': terminal,
'data': {'finish_reason': 'stop'} if terminal == 'run.completed' else {'error': 'CANARY'},
}
]
)
ap = FakeApplication(plugin_connector, clean_agent_state)
ap.instance_config = types.SimpleNamespace(data={'space': {'url': 'https://example.invalid'}})
ap.diagnostics = DiagnosticsManager(ap, version='4.11.0b2', instance_id='instance-test')
orchestrator = AgentRunOrchestrator(ap, FakeRegistry(make_descriptor()))
try:
_ = [v async for v in orchestrator.run_from_query(make_query())]
except RunnerExecutionError:
assert terminal == 'run.failed'
records = [e for e in ap.diagnostics.pending if e['operation'] == 'runner.run']
assert records[-1]['outcome'] == ('succeeded' if terminal == 'run.completed' else 'failed')
assert records[-1]['run_id'] == plugin_connector.contexts[0]['run_id']
import json
assert 'CANARY' not in json.dumps(ap.diagnostics.pending)
@@ -0,0 +1,194 @@
"""Real Quart registration boundaries with content-canary payloads."""
import asyncio
import json
import re
from types import SimpleNamespace
from unittest.mock import AsyncMock
import pytest
import quart
from langbot.pkg.api.http.controller.group import AuthType, RouterGroup
from langbot.pkg.telemetry import diagnostics as d
class Recorder:
enabled = True
def __init__(self):
self.events = []
def emit(self, kind, operation, outcome, **fields):
# A real transport projects the exception class, never its message.
error = fields.pop('error', None)
if error is not None:
fields['error_type'] = type(error).__name__
self.events.append(dict(kind=kind, operation=operation, outcome=outcome, **fields))
class Routes(RouterGroup):
path = '/management'
name = 'management'
async def initialize(self):
@self.route('/ok/<identifier>', auth_type=AuthType.NONE)
async def success(identifier):
self.ap.seen.append(d.current_span())
return self.success({'secret': identifier})
@self.route('/business', auth_type=AuthType.NONE)
async def business():
return self.fail('private-error-code', 'private-error-message')
@self.route('/auth')
async def authenticated():
raise AssertionError('must not run')
@self.route('/error', auth_type=AuthType.NONE)
async def error():
raise ValueError('private-exception-message')
@self.route('/cancel', auth_type=AuthType.NONE)
async def cancel():
raise asyncio.CancelledError('private-cancel-message')
@self.route('/stream', auth_type=AuthType.NONE)
async def stream():
async def body():
self.ap.seen.append(d.current_span())
yield b'private-stream-chunk'
self.ap.seen.append(d.current_span())
return quart.Response(body())
async def setup(manager=True):
app = quart.Quart(__name__)
ap = SimpleNamespace(seen=[])
if manager is True:
manager = Recorder()
if manager is not None:
ap.diagnostics = manager
routes = Routes(ap, app)
await routes.initialize()
return app, ap, routes
@pytest.mark.asyncio
async def test_http_success_uses_code_identity_not_path_or_payload():
app, ap, _ = await setup()
response = await app.test_client().get(
'/management/ok/private-id?token=private-query', headers={'Authorization': 'private-token'}
)
assert (await response.get_json())['data']['secret'] == 'private-id'
assert [e['outcome'] for e in ap.diagnostics.events] == ['started', 'succeeded']
event = ap.diagnostics.events[-1]
assert event['source'] == 'http'
assert re.fullmatch(r'[A-Za-z_][A-Za-z0-9_.:-]{0,127}', event['operation'])
assert ap.seen[0] is not None
assert d.current_span() is None
assert 'private-' not in json.dumps(ap.diagnostics.events)
@pytest.mark.asyncio
@pytest.mark.parametrize(
'path,status,outcome', [('business', 200, 'failed'), ('auth', 401, 'rejected'), ('error', 500, 'failed')]
)
async def test_http_business_auth_and_exception_outcomes(path, status, outcome):
app, ap, _ = await setup()
response = await app.test_client().get('/management/' + path)
assert response.status_code == status
assert ap.diagnostics.events[-1]['outcome'] == outcome
assert 'private-' not in json.dumps(ap.diagnostics.events)
@pytest.mark.asyncio
async def test_http_cancellation_propagates():
app, ap, _ = await setup()
async with app.test_request_context('/management/cancel'):
with pytest.raises(asyncio.CancelledError):
await app.full_dispatch_request()
assert ap.diagnostics.events[-1]['outcome'] == 'cancelled'
assert d.current_span() is None
@pytest.mark.asyncio
async def test_http_auth_cancellation_not_reinterpreted_as_api_key():
app, ap, routes = await setup()
routes._authenticate_support_admin = AsyncMock(return_value=None)
routes._authenticate_account = AsyncMock(side_effect=asyncio.CancelledError())
async with app.test_request_context('/management/auth', headers={'Authorization': 'Bearer private-token'}):
with pytest.raises(asyncio.CancelledError):
await app.full_dispatch_request()
assert ap.diagnostics.events[-1]['outcome'] == 'cancelled'
@pytest.mark.asyncio
async def test_http_stream_has_parent_context_without_consumer_leak():
app, ap, _ = await setup()
async with app.test_request_context('/management/stream'):
response = await app.full_dispatch_request()
assert [e['outcome'] for e in ap.diagnostics.events] == ['started']
assert d.current_span() is None
async with response.response as body:
iterator = body.__aiter__()
assert await anext(iterator) == b'private-stream-chunk'
assert d.current_span() is None
assert ap.seen[-1] is not None
with pytest.raises(StopAsyncIteration):
await anext(iterator)
assert ap.seen[0] is ap.seen[1]
assert ap.diagnostics.events[-1]['outcome'] == 'succeeded'
assert 'private-' not in json.dumps(ap.diagnostics.events)
@pytest.mark.asyncio
@pytest.mark.parametrize(
'manager', [None, SimpleNamespace(enabled=False, emit=lambda *a, **kw: pytest.fail('disabled emission'))]
)
async def test_absent_disabled_manager_preserves_result_without_context(manager):
app, ap, _ = await setup(manager)
response = await app.test_client().get('/management/ok/private-id')
assert response.status_code == 200
assert ap.seen == [None]
@pytest.mark.asyncio
async def test_broken_diagnostic_emit_never_masks_operation():
class Broken(Recorder):
def emit(self, *args, **kwargs):
raise RuntimeError('diagnostics broken')
app, _, _ = await setup(Broken())
response = await app.test_client().get('/management/ok/private-id')
assert response.status_code == 200
async with app.test_request_context('/management/cancel'):
with pytest.raises(asyncio.CancelledError):
await app.full_dispatch_request()
assert d.current_span() is None
@pytest.mark.asyncio
async def test_anonymous_handlers_have_distinct_stable_code_operations():
app, ap, routes = await setup()
@routes.route('/items/<identifier>', auth_type=AuthType.NONE, methods=['GET'])
async def _(identifier):
return routes.success()
@routes.route('/items/<identifier>', auth_type=AuthType.NONE, methods=['POST'])
async def _(identifier):
return routes.success()
@routes.route('/other', auth_type=AuthType.NONE)
async def _():
return routes.success()
await app.test_client().get('/management/items/private-id')
await app.test_client().post('/management/items/private-id')
await app.test_client().get('/management/other')
operations = [e['operation'] for e in ap.diagnostics.events if e['outcome'] == 'started']
assert len(set(operations)) == 3
assert all(re.fullmatch(r'[A-Za-z_][A-Za-z0-9_.:-]{0,127}', op) for op in operations)
assert 'private-' not in json.dumps(ap.diagnostics.events)
@@ -0,0 +1,47 @@
"""Verify identities across every source-declared management registration."""
import ast
from pathlib import Path
import re
from types import SimpleNamespace
from langbot.pkg.api.management_diagnostics import operation_id
def test_every_http_registration_has_a_distinct_wire_safe_operation():
root = Path(__file__).resolve().parents[3] / 'src/langbot/pkg/api/http/controller/groups'
identities = []
for path in root.rglob('*.py'):
module = 'langbot.pkg.api.http.controller.groups.' + '.'.join(path.relative_to(root).with_suffix('').parts)
for cls in ast.walk(ast.parse(path.read_text())):
if not isinstance(cls, ast.ClassDef):
continue
prefix = ''
for decorator in cls.decorator_list:
if (
isinstance(decorator, ast.Call)
and isinstance(decorator.func, ast.Attribute)
and decorator.func.attr == 'group_class'
):
prefix = ast.literal_eval(decorator.args[1])
for fn in ast.walk(cls):
if not isinstance(fn, ast.AsyncFunctionDef):
continue
for decorator in fn.decorator_list:
if not (
isinstance(decorator, ast.Call)
and isinstance(decorator.func, ast.Attribute)
and decorator.func.attr == 'route'
):
continue
rule = prefix + ast.literal_eval(decorator.args[0])
methods = next(
(ast.literal_eval(k.value) for k in decorator.keywords if k.arg == 'methods'), ['GET']
)
operation = operation_id(
'http', SimpleNamespace(__module__=module, __name__=fn.name), rule=rule, methods=methods
)
assert re.fullmatch(r'[A-Za-z_][A-Za-z0-9_.:-]{0,127}', operation), operation
identities.append(operation)
assert len(identities) >= 200 # Guard against accidentally scanning an empty/subset tree.
assert len(set(identities)) == len(identities)
@@ -0,0 +1,431 @@
"""CORE-DIAG-6: management ownership barriers under a foreign live span."""
import asyncio
from types import SimpleNamespace as NS
from unittest.mock import AsyncMock, patch
from uuid import uuid4
import pytest
import quart
from langbot.pkg.api import management_diagnostics as md
from langbot.pkg.api.http.context import (
ExecutionContext,
PrincipalContext,
PrincipalType,
RequestContext,
WorkspaceContext,
)
from langbot.pkg.api.http.controller.group import AuthType
from langbot.pkg.api.http.controller.groups.pipelines.embed import EmbedRouterGroup
from langbot.pkg.api.http.controller.groups.pipelines.websocket_chat import WebSocketChatRouterGroup
from langbot.pkg.api.http.service.agent import AgentService
from langbot.pkg.api.mcp.server import LangBotMCPServer
from langbot.pkg.telemetry import diagnostics as d
from tests.unit_tests.api.test_diagnostics_management_http import Recorder, Routes
MODES = ['enabled', 'disabled', 'absent', 'mismatched', 'policy_off', 'beta_off', 'broken']
def app(identity):
ap = NS(instance_config=NS(data={'space': {'url': 'https://example.invalid'}}))
ap.diagnostics = d.DiagnosticsManager(ap, version='4.11.0b2', instance_id=identity)
return ap
def owners(mode):
a, b = app('instance-A'), app('instance-B')
b_manager = b.diagnostics
if mode == 'disabled':
b.instance_config.data['space']['disable_telemetry'] = True
b.diagnostics = d.DiagnosticsManager(b, version='4.11.0b2', instance_id='instance-B')
b_manager = b.diagnostics
elif mode == 'absent':
del b.diagnostics
elif mode == 'mismatched':
b.diagnostics = a.diagnostics
elif mode in {'policy_off', 'beta_off'}:
# Also test an attached structural producer whose enabled flag stays true.
b.diagnostics = Recorder()
b.instance_config.data['space']['disable_telemetry' if mode == 'policy_off' else 'disable_beta_diagnostics'] = (
True
)
elif mode == 'broken':
b.diagnostics = NS(enabled=True, emit=lambda *a, **kw: (_ for _ in ()).throw(RuntimeError('broken')))
ca = ExecutionContext('instance-A', str(uuid4()), 1)
cb = ExecutionContext('instance-B', str(uuid4()), 1)
d.privacy.code_value('operation', 'http.isolation.parent')
parent = d.Span(a.diagnostics, 'api', 'http.isolation.parent', {'workspace_uuid': ca.workspace_uuid})
return a, b, b_manager, ca, cb, parent
@d.observe('event', 'platform.target2yiri', source='platform', stage='convert')
async def converter(native):
return native
async def nested(b, cb, seen):
md.workspace(cb)
seen.append(d.current_span())
with md.scope(b, 'websocket.isolation.inner', source='websocket'):
md.workspace(cb)
assert await converter(42) == 42
# Both an inner management boundary and an ownerless converter must be safe.
assert await converter(43) == 43
return 44
def assert_isolated(a, b, b_manager, ca, cb, parent, mode):
assert parent.fields['workspace_uuid'] == ca.workspace_uuid
assert parent.outcome is None
assert len(a.diagnostics.pending) == 1 # Only the caller's own start event.
assert not b_manager.pending if mode != 'enabled' else b_manager.pending
if isinstance(getattr(b, 'diagnostics', None), Recorder):
assert b.diagnostics.events == []
if mode == 'enabled':
assert all(e['instance_id'] == 'instance-B' for e in b_manager.pending)
assert all(e['trace_id'] != parent.fields['trace_id'] for e in b_manager.pending)
assert all(e['workspace_uuid'] != ca.workspace_uuid for e in b_manager.pending)
assert any(e['workspace_uuid'] == cb.workspace_uuid for e in b_manager.pending)
@pytest.mark.asyncio
@pytest.mark.parametrize('mode', MODES)
@pytest.mark.parametrize('shape', ['decorator', 'scope'])
async def test_nested_management_masks_foreign_parent_and_restores_caller(mode, shape):
a, b, manager, ca, cb, parent = owners(mode)
seen = []
@md.observe('http.isolation.endpoint', source='http', ap=b)
async def endpoint():
return await nested(b, cb, seen)
with parent.activate():
if shape == 'decorator':
assert await endpoint() == 44
else:
with md.scope(b, 'websocket.isolation.outer', source='websocket'):
assert await nested(b, cb, seen) == 44
assert d.current_span() is parent
assert d.current_span() is None
assert (seen[0] is not None) == (mode == 'enabled')
assert_isolated(a, b, manager, ca, cb, parent, mode)
@pytest.mark.asyncio
@pytest.mark.parametrize('mode', MODES)
async def test_actual_agent_debug_never_mutates_foreign_workspace(mode):
a, b, manager, ca, cb, parent = owners(mode)
service = AgentService(b)
seen = []
async def missing(*args):
await nested(b, cb, seen)
return None
service.get_agent = missing
with parent.activate():
with pytest.raises(ValueError, match='^Agent not found$'):
await service.debug_agent(cb, 'private-agent', {})
assert d.current_span() is parent
assert_isolated(a, b, manager, ca, cb, parent, mode)
@pytest.mark.asyncio
@pytest.mark.parametrize('mode', MODES)
@pytest.mark.parametrize('termination', ['exhaust', 'close', 'error', 'cancel'])
async def test_real_http_body_keeps_barrier_during_advancement_and_cleanup(mode, termination):
a, b, manager, ca, cb, parent = owners(mode)
web = quart.Quart(__name__)
routes = Routes(b, web)
seen, closed = [], []
failure = asyncio.CancelledError('private-cancel') if termination == 'cancel' else ValueError('private-error')
@routes.route('/isolated', auth_type=AuthType.NONE)
async def endpoint():
await nested(b, cb, seen)
async def stream():
try:
await nested(b, cb, seen)
yield b'one'
if termination in {'cancel', 'error'}:
raise failure
await nested(b, cb, seen)
finally:
await nested(b, cb, seen)
closed.append(True)
return quart.Response(stream())
# Construct with no caller parent; the later body consumer has app A's span.
async with web.test_request_context('/management/isolated'):
response = await web.full_dispatch_request()
with parent.activate():
try:
async with response.response as body:
iterator = body.__aiter__()
assert await anext(iterator) == b'one'
assert d.current_span() is parent
if termination != 'close':
with pytest.raises((StopAsyncIteration, type(failure))) as caught:
await anext(iterator)
if termination in {'cancel', 'error'}:
assert caught.value is failure
else:
assert isinstance(caught.value, StopAsyncIteration)
finally:
assert d.current_span() is parent
assert closed == [True]
assert all((span is not None) == (mode == 'enabled') for span in seen)
assert_isolated(a, b, manager, ca, cb, parent, mode)
@pytest.mark.asyncio
@pytest.mark.parametrize('mode', MODES)
async def test_real_mcp_registered_tool_masks_foreign_parent(mode):
a, b, manager, ca, cb, parent = owners(mode)
seen = []
async def get_bot(*args, **kwargs):
return {'value': await nested(b, cb, seen)}
b.bot_service = NS(get_bot=get_bot)
server = LangBotMCPServer(b)
with parent.activate(), patch('langbot.pkg.api.mcp.server._authorized', return_value=cb):
assert await server.mcp.call_tool('get_bot', {'bot_uuid': 'private-bot'})
assert d.current_span() is parent
assert_isolated(a, b, manager, ca, cb, parent, mode)
@pytest.mark.asyncio
@pytest.mark.parametrize('mode', MODES)
@pytest.mark.parametrize('group_class', [WebSocketChatRouterGroup, EmbedRouterGroup])
async def test_real_websocket_receive_masks_foreign_parent(mode, group_class):
a, b, manager, ca, cb, parent = owners(mode)
group = group_class(b, quart.Quart(__name__))
connection = NS(is_active=True, connection_id='private-id', send_queue=asyncio.Queue())
seen = []
async def handle(*args, **kwargs):
await nested(b, cb, seen)
adapter = NS(handle_websocket_message=handle)
if group_class is WebSocketChatRouterGroup:
group._revalidate_websocket_authorization = AsyncMock(return_value=cb)
else:
group._resolve_connected_bot = AsyncMock(return_value=NS(execution_context=cb))
async def receive():
connection.is_active = False
return '{"type":"message","text":"private-prompt"}'
with (
parent.activate(),
patch('quart.websocket', NS(receive=receive)),
patch(
'langbot.pkg.api.http.controller.groups.pipelines.websocket_chat.ws_connection_manager.update_activity',
new=AsyncMock(),
),
):
await group._handle_receive(connection, adapter, NS(execution_context=cb), 'private-token')
assert d.current_span() is parent
assert seen
assert_isolated(a, b, manager, ca, cb, parent, mode)
@pytest.mark.parametrize('kind', ['execution', 'request'])
def test_workspace_rejects_foreign_instance_or_foreign_active_span(kind):
a, b, _, ca, cb, parent = owners('enabled')
context = cb
if kind == 'request':
context = RequestContext(
cb.instance_uuid,
1,
'request',
'api_key',
PrincipalContext(PrincipalType.API_KEY),
WorkspaceContext(cb.workspace_uuid, None, None, frozenset()),
)
with md.scope(a, 'http.isolation.annotation', source='http'):
span = d.current_span()
md.workspace(context)
assert 'workspace_uuid' not in span.fields
md.workspace(ca)
assert span.fields['workspace_uuid'] == ca.workspace_uuid
with parent.activate():
before = dict(parent.fields)
md.workspace(ca)
md.workspace(context)
assert parent.fields == before
def test_structural_recorder_workspace_and_scope_exception_identity():
recorder = Recorder()
ap = NS(diagnostics=recorder)
ctx = ExecutionContext('instance', str(uuid4()), 1)
error = ValueError('private-error')
with pytest.raises(ValueError) as caught:
with md.scope(ap, 'http.isolation.recorder', source='http'):
md.workspace(ctx)
assert d.current_span().fields['workspace_uuid'] == ctx.workspace_uuid
raise error
assert caught.value is error
assert recorder.events[-1]['outcome'] == 'failed'
assert d.current_span() is None
@pytest.mark.asyncio
@pytest.mark.parametrize('mode', MODES[1:])
@pytest.mark.parametrize('termination', ['success', 'error', 'cancel'])
async def test_nested_off_boundary_restores_outer_annotation_and_error(mode, termination):
a, b, manager, ca, cb, parent = owners(mode)
error = asyncio.CancelledError('private-cancel') if termination == 'cancel' else ValueError('private-error')
@md.observe('http.isolation.off', source='http', ap=b)
async def endpoint():
await nested(b, cb, [])
md.outcome('failed')
if termination != 'success':
raise error
return 42
with parent.activate():
with md.scope(a, 'http.isolation.outer', source='http'):
outer = d.current_span()
if termination == 'success':
assert await endpoint() == 42
else:
with pytest.raises(type(error)) as caught:
await endpoint()
assert caught.value is error
assert d.current_span() is outer
md.workspace(ca)
assert outer.fields['workspace_uuid'] == ca.workspace_uuid
assert outer.outcome is None
assert d.current_span() is parent
assert not manager.pending
assert [e['operation'] for e in a.diagnostics.pending] == [
'http.isolation.parent',
'http.isolation.outer',
'http.isolation.outer',
]
@pytest.mark.asyncio
@pytest.mark.parametrize('mode', MODES)
@pytest.mark.parametrize('group_class', [WebSocketChatRouterGroup, EmbedRouterGroup])
async def test_websocket_send_cancel_restores_foreign_parent(mode, group_class):
a, b, manager, ca, cb, parent = owners(mode)
group = group_class(b, quart.Quart(__name__))
connection = NS(is_active=False, send_queue=asyncio.Queue())
await connection.send_queue.put({'text': 'private-answer'})
error = asyncio.CancelledError('private-cancel')
async def send(payload):
await nested(b, cb, [])
raise error
with parent.activate(), patch('quart.websocket', NS(send=send)):
with pytest.raises(asyncio.CancelledError) as caught:
await group._handle_send(connection)
assert caught.value is error
assert d.current_span() is parent
assert_isolated(a, b, manager, ca, cb, parent, mode)
@pytest.mark.asyncio
@pytest.mark.parametrize('mode', MODES)
async def test_sync_http_body_exhaustion_has_owned_context_without_consumer_leak(mode):
a, b, manager, ca, cb, parent = owners(mode)
seen, closed = [], []
def stream():
try:
for value in (b'one', b'two'):
md.workspace(cb)
seen.append(d.current_span())
yield value
finally:
seen.append(d.current_span())
closed.append(True)
@md.observe('http.isolation.sync_body', source='http', ap=b, http=True)
async def endpoint():
return quart.Response(stream())
response = await endpoint()
with parent.activate():
async with response.response as body:
chunks = []
async for chunk in body:
assert d.current_span() is parent
chunks.append(chunk)
assert d.current_span() is parent
assert chunks == [b'one', b'two']
assert closed == [True]
assert all((span is not None) == (mode == 'enabled') for span in seen)
assert_isolated(a, b, manager, ca, cb, parent, mode)
@pytest.mark.asyncio
@pytest.mark.parametrize('mode', ['enabled', 'disabled', 'absent', 'mismatched'])
@pytest.mark.parametrize('failure_stage', ['enter', 'iterator', 'advance', 'exit', None])
async def test_custom_http_body_protocol_and_cancellation_identity(mode, failure_stage):
from quart.wrappers.response import IterableBody
a, b, manager, ca, cb, parent = owners(mode)
error = asyncio.CancelledError('private-body-cancel')
seen = []
def visit(stage):
md.workspace(cb)
seen.append((stage, d.current_span()))
if stage == failure_stage:
raise error
class Body(IterableBody):
def __init__(self):
pass
async def __aenter__(self):
visit('enter')
return self
def __aiter__(self):
visit('iterator')
return self
async def __anext__(self):
visit('advance')
raise StopAsyncIteration
async def __aexit__(self, *args):
visit('exit')
@md.observe('http.isolation.body_protocol', source='http', ap=b, http=True)
async def endpoint():
response = quart.Response()
response.response = Body()
return response
response = await endpoint()
async def consume():
async with response.response as body:
assert d.current_span() is parent
async for _ in body:
pytest.fail('empty body yielded')
with parent.activate():
if failure_stage is None:
await consume()
else:
with pytest.raises(asyncio.CancelledError) as caught:
await consume()
assert caught.value is error
assert d.current_span() is parent
assert all((span is not None) == (mode == 'enabled') for _, span in seen)
assert [s for s, _ in seen].count('exit') == (0 if failure_stage in {'enter', 'iterator'} else 1)
assert_isolated(a, b, manager, ca, cb, parent, mode)
@@ -0,0 +1,163 @@
"""HTTP streaming termination, fail-open behavior and real producer privacy."""
import asyncio
import json
import re
from types import SimpleNamespace
from unittest.mock import AsyncMock
import pytest
import quart
from langbot.pkg.agent.runner import errors as runner_errors
from langbot.pkg.api import management_diagnostics as md
from langbot.pkg.api.http.controller.group import AuthType
from langbot.pkg.api.http.controller.groups.agent_debug_stream import debug_stream_response
from langbot.pkg.api.mcp.mount import MCPMount
from langbot.pkg.telemetry import diagnostics as d
from tests.unit_tests.api.test_diagnostics_management_http import Recorder, setup
@pytest.mark.asyncio
@pytest.mark.parametrize('termination', ['close', 'cancel', 'error'])
async def test_stream_termination_preserves_cleanup_and_error(termination):
app, ap, routes = await setup()
closed = []
@routes.route('/end', auth_type=AuthType.NONE)
async def end():
async def body():
try:
yield b'one'
if termination == 'error':
raise ValueError('private-error')
if termination == 'cancel':
raise asyncio.CancelledError('private-cancel')
yield b'two'
finally:
closed.append(d.current_span())
return quart.Response(body())
async with app.test_request_context('/management/end'):
response = await app.full_dispatch_request()
if termination == 'close':
async with response.response as body:
assert await anext(body.__aiter__()) == b'one'
else:
error = ValueError if termination == 'error' else asyncio.CancelledError
with pytest.raises(error):
async with response.response as body:
iterator = body.__aiter__()
assert await anext(iterator) == b'one'
await anext(iterator)
assert len(closed) == 1 and closed[0] is not None
expected = 'failed' if termination == 'error' else 'cancelled'
assert [e['outcome'] for e in ap.diagnostics.events] == ['started', expected]
assert d.current_span() is None
@pytest.mark.asyncio
async def test_debug_ndjson_error_marks_http_business_failure():
app, ap, routes = await setup()
service = SimpleNamespace(debug_agent=AsyncMock(side_effect=runner_errors.RunnerNotFoundError('private-error')))
@routes.route('/ndjson', auth_type=AuthType.NONE)
async def ndjson():
return debug_stream_response(service, object(), 'private-agent', {'text': 'private-text'})
response = await app.test_client().get('/management/ndjson')
assert response.status_code == 200
assert json.loads(await response.get_data())['kind'] == 'error'
assert ap.diagnostics.events[-1]['outcome'] == 'failed'
assert 'private-' not in json.dumps(ap.diagnostics.events)
@pytest.mark.asyncio
async def test_terminal_diagnostic_failure_preserves_success_and_stream():
class BrokenFinish(Recorder):
def emit(self, kind, operation, outcome, **fields):
if outcome != 'started':
raise RuntimeError('private-diagnostic-error')
super().emit(kind, operation, outcome, **fields)
app, _, _ = await setup(BrokenFinish())
response = await app.test_client().get('/management/stream')
assert await response.get_data() == b'private-stream-chunk'
assert d.current_span() is None
@pytest.mark.asyncio
async def test_real_manager_status_privacy_and_no_network_in_request():
app, ap, _ = await setup(None)
ap.instance_config = SimpleNamespace(data={'space': {'url': 'https://example.invalid'}})
ap.diagnostics = d.DiagnosticsManager(ap, version='4.11.0-beta.2', instance_id='instance-test', capacity=20)
ap.diagnostics.credentials = AsyncMock(side_effect=AssertionError('must not await credentials'))
for path in ['ok/private-identifier', 'business', 'auth', 'cancel']:
async with app.test_request_context('/management/' + path):
try:
await app.full_dispatch_request()
except asyncio.CancelledError:
pass
events = list(ap.diagnostics.pending)
assert len(events) == 8
assert {e['outcome'] for e in events} == {'started', 'succeeded', 'failed', 'rejected', 'cancelled'}
assert all(re.fullmatch(r'[A-Za-z_][A-Za-z0-9_.:-]{0,127}', e['operation']) for e in events)
assert 'private-' not in json.dumps(events)
ap.diagnostics.credentials.assert_not_awaited()
@pytest.mark.asyncio
@pytest.mark.parametrize(
'config,version',
[
({}, '4.11.0'),
({'disable_telemetry': True}, '4.11.0-beta.2'),
({'disable_beta_diagnostics': True}, '4.11.0-beta.2'),
],
)
async def test_real_manager_disabled_modes_do_not_install_context(config, version):
app, ap, _ = await setup(None)
ap.instance_config = SimpleNamespace(data={'space': config})
ap.diagnostics = d.DiagnosticsManager(ap, version=version, instance_id='instance-test')
response = await app.test_client().get('/management/ok/private-id')
assert response.status_code == 200
assert ap.seen == [None]
assert not ap.diagnostics.pending
@pytest.mark.asyncio
async def test_mcp_mount_observes_auth_without_headers_or_body():
ap = SimpleNamespace(
diagnostics=Recorder(), apikey_service=SimpleNamespace(authenticate_api_key=AsyncMock(return_value=None))
)
mount = MCPMount(ap)
send = AsyncMock()
fallback = AsyncMock()
await mount.wrap(fallback)(
{'type': 'http', 'path': '/mcp/private-path', 'headers': [(b'x-api-key', b'private-token')]}, AsyncMock(), send
)
assert send.call_args_list[0].args[0]['status'] == 401
assert [e['outcome'] for e in ap.diagnostics.events] == ['started', 'rejected']
assert ap.diagnostics.events[-1]['operation'] == 'mcp.request'
assert 'private-' not in json.dumps(ap.diagnostics.events)
fallback.assert_not_awaited()
@pytest.mark.asyncio
async def test_debug_boundary_source_inherits_without_duplicate_run():
recorder = Recorder()
ap = SimpleNamespace(diagnostics=recorder)
@md.observe('http.test.debug', source='webui_debug', ap=ap)
async def debug():
@d.observe('run', 'runner.run', source='agent', ap=ap)
async def run():
return 'private-content'
return await run()
assert await debug() == 'private-content'
assert len([e for e in recorder.events if e['kind'] == 'run' and e['outcome'] == 'started']) == 1
assert all(e['source'] == 'webui_debug' and e['attributes']['synthetic'] for e in recorder.events)
assert 'private-' not in json.dumps(recorder.events)
@@ -0,0 +1,173 @@
"""Management MCP, WebSocket and debug execution boundaries."""
import asyncio
import json
from types import SimpleNamespace
from unittest.mock import AsyncMock, patch
import pytest
import quart
from langbot.pkg.api.http.controller.groups.pipelines.websocket_chat import WebSocketChatRouterGroup
from langbot.pkg.api.http.controller.groups.pipelines.embed import EmbedRouterGroup
from langbot.pkg.api.mcp.server import LangBotMCPServer
from langbot.pkg.telemetry import diagnostics as d
from tests.unit_tests.api.test_diagnostics_management_http import Recorder
@pytest.mark.asyncio
async def test_mcp_real_registered_tool_has_source_and_never_content():
recorder = Recorder()
seen = []
async def get_bot(*args, **kwargs):
seen.append(d.current_span())
return {'secret': 'private-result'}
ap = SimpleNamespace(diagnostics=recorder, bot_service=SimpleNamespace(get_bot=get_bot))
server = LangBotMCPServer(ap)
with patch('langbot.pkg.api.mcp.server._authorized', return_value=object()):
result = await server.mcp.call_tool('get_bot', {'bot_uuid': 'private-bot'})
assert result
assert [e['outcome'] for e in recorder.events] == ['started', 'succeeded']
assert recorder.events[-1]['operation'] == 'mcp.get_bot'
assert recorder.events[-1]['source'] == 'mcp'
assert seen[0] is not None
assert 'private-' not in json.dumps(recorder.events)
assert d.current_span() is None
@pytest.mark.asyncio
async def test_mcp_tool_permission_rejection_is_observed():
server = LangBotMCPServer(SimpleNamespace(diagnostics=Recorder()))
with pytest.raises(Exception):
await server.mcp.call_tool('list_bots', {})
assert server.ap.diagnostics.events[-1]['outcome'] in {'rejected', 'failed'}
assert server.ap.diagnostics.events[-1]['source'] == 'mcp'
@pytest.mark.asyncio
@pytest.mark.parametrize(
'group_class,path',
[
(WebSocketChatRouterGroup, '/api/v1/pipelines/private-pipeline/ws/connect'),
(EmbedRouterGroup, '/api/v1/embed/11111111-1111-4111-8111-111111111111/ws/connect?session_id=private-session'),
],
)
async def test_real_websocket_session_auth_rejection_is_content_free(group_class, path):
app = quart.Quart(__name__)
ap = SimpleNamespace(diagnostics=Recorder())
group = group_class(ap, app)
group._authenticate_websocket = AsyncMock(side_effect=ValueError('private-token'))
if group_class is EmbedRouterGroup:
group._resolve_bot = AsyncMock(return_value=(object(), 'private-pipeline'))
await group.initialize()
async with app.test_client().websocket(path) as socket:
frame = json.loads(await socket.receive())
assert frame['type'] == 'error'
terminal = [e for e in ap.diagnostics.events if e['outcome'] != 'started']
assert terminal
assert terminal[-1]['outcome'] == 'rejected'
assert terminal[-1]['source'] == 'websocket'
assert 'private-' not in json.dumps(ap.diagnostics.events)
@pytest.mark.asyncio
@pytest.mark.parametrize('group_class', [WebSocketChatRouterGroup, EmbedRouterGroup])
async def test_websocket_received_message_boundaries_do_not_capture_frames(group_class):
app = quart.Quart(__name__)
ap = SimpleNamespace(diagnostics=Recorder())
group = group_class(ap, app)
connection = SimpleNamespace(is_active=True, connection_id='private-id', send_queue=asyncio.Queue())
adapter = SimpleNamespace(handle_websocket_message=AsyncMock())
if group_class is WebSocketChatRouterGroup:
group._revalidate_websocket_authorization = AsyncMock(return_value=object())
args = (connection, adapter, object(), 'private-token')
else:
group._resolve_connected_bot = AsyncMock(return_value=object())
args = (connection, adapter, object(), 'private-pipeline')
async def receive():
connection.is_active = False
return json.dumps({'type': 'message', 'text': 'private-prompt'})
with (
patch('quart.websocket', SimpleNamespace(receive=receive)),
patch(
'langbot.pkg.api.http.controller.groups.pipelines.websocket_chat.ws_connection_manager.update_activity',
new=AsyncMock(),
),
):
await group._handle_receive(*args)
adapter.handle_websocket_message.assert_awaited_once()
assert [e['outcome'] for e in ap.diagnostics.events] == ['started', 'succeeded']
assert ap.diagnostics.events[-1]['operation'].endswith('.message')
assert 'private-' not in json.dumps(ap.diagnostics.events)
assert d.current_span() is None
@pytest.mark.asyncio
async def test_debug_service_marks_synthetic_source_before_validation():
from langbot.pkg.api.http.service.agent import AgentService
ap = SimpleNamespace(diagnostics=Recorder())
service = AgentService(ap)
seen = []
async def get_agent(*args):
seen.append(d.current_span())
return None
service.get_agent = get_agent
with pytest.raises(ValueError):
await service.debug_agent(object(), 'private-id', {'text': 'private-prompt'})
assert seen[0].fields['source'] == 'webui_debug'
assert ap.diagnostics.events[-1]['operation'] == 'http.agent.debug_agent'
assert ap.diagnostics.events[-1]['source'] == 'webui_debug'
assert 'private-' not in json.dumps(ap.diagnostics.events)
@pytest.mark.asyncio
@pytest.mark.parametrize('group_class', [WebSocketChatRouterGroup, EmbedRouterGroup])
@pytest.mark.parametrize(
'frame,expected', [('private-invalid-json', 'rejected'), ('{"type":"private-unknown"}', 'skipped')]
)
async def test_websocket_invalid_frames_have_finite_outcomes(group_class, frame, expected):
app = quart.Quart(__name__)
ap = SimpleNamespace(diagnostics=Recorder())
group = group_class(ap, app)
connection = SimpleNamespace(is_active=True, connection_id='private-id', send_queue=asyncio.Queue())
async def receive():
connection.is_active = False
return frame
with (
patch('quart.websocket', SimpleNamespace(receive=receive)),
patch(
'langbot.pkg.api.http.controller.groups.pipelines.websocket_chat.ws_connection_manager.update_activity',
new=AsyncMock(),
),
):
await group._handle_receive(connection, object(), object(), 'private-token')
assert ap.diagnostics.events[-1]['outcome'] == expected
assert 'private-' not in json.dumps(ap.diagnostics.events)
@pytest.mark.asyncio
@pytest.mark.parametrize('group_class', [WebSocketChatRouterGroup, EmbedRouterGroup])
async def test_websocket_send_boundary_preserves_payload_and_cancellation(group_class):
app = quart.Quart(__name__)
ap = SimpleNamespace(diagnostics=Recorder())
group = group_class(ap, app)
connection = SimpleNamespace(is_active=False, send_queue=asyncio.Queue())
await connection.send_queue.put({'text': 'private-answer'})
send = AsyncMock(side_effect=asyncio.CancelledError('private-error'))
with patch('quart.websocket', SimpleNamespace(send=send)):
with pytest.raises(asyncio.CancelledError):
await group._handle_send(connection)
assert json.loads(send.call_args.args[0]) == {'text': 'private-answer'}
assert ap.diagnostics.events[-1]['outcome'] == 'cancelled'
assert ap.diagnostics.events[-1]['operation'].endswith('.send')
assert 'private-' not in json.dumps(ap.diagnostics.events)
assert d.current_span() is None
@@ -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)
@@ -0,0 +1,30 @@
"""Release artifacts must identify their actual source, not a branch label."""
import ast
import importlib.util
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[2]
SPEC = importlib.util.spec_from_file_location('stamp_build_revision', ROOT / 'scripts/stamp_build_revision.py')
assert SPEC is not None and SPEC.loader is not None
MODULE = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(MODULE)
def test_stamp_accepts_full_revision(tmp_path):
target = tmp_path / 'src/langbot/_build_info.py'
target.parent.mkdir(parents=True)
revision = 'a1' * 20
assert MODULE.stamp(tmp_path, revision) == revision
assignments = [node for node in ast.parse(target.read_text()).body if isinstance(node, ast.Assign)]
assert len(assignments) == 1
assert ast.literal_eval(assignments[0].value) == revision
@pytest.mark.parametrize('revision', ['main', 'abc123', 'A' * 40, 'a' * 39, 'a' * 41, '../secret', 'a' * 40 + '\n'])
def test_stamp_rejects_non_revision_before_write(tmp_path, revision):
with pytest.raises(ValueError):
MODULE.stamp(tmp_path, revision)
assert not (tmp_path / 'src').exists()